C/Tabeller

Fra Wikibøker – frie læremidler
< C

(Merk: Det jeg her kaller "tabell" er array på engelsk.)

I dette kapittelet skal vi se på en ny datastruktur, nemlig tabellen. At denne er ny for oss, er egentlig ikke helt korrekt. I kapittelet om datatyper tok vi for oss en rekke med bokstaver som vi kalte en streng. En streng er en tabell med elementer av typen char.

En tabell er en rekke variabler, som kalles elementer, som henger sammen og har et felles navn. Hvis vi har en tabell kalt temperatur med 5 elementer, vil den "se" slik ut:

+---------+
|         |   temperatur[4]
+---------+
|         |   temperatur[3]
+---------+
|         |   temperatur[2]
+---------+
|         |   temperatur[1]
+---------+
|         |   temperatur[0]
+---------+

Slik kan du i alle fall tenke deg en tabell. Elementene i tabellen refereres til med nummeret (posisjonen i tabellen), mellom [ og ].


Definering av tabeller, indeksering, tilordning[rediger]

For å definere en tabell bruker vi følgende syntaks:

type navn[antall elementer]

Defineringen ligner på defineringen av vanlige variabler. Den eneste forskjellen er at vi sier hvor mange elementer tabellen skal ha, omringet av [ og ]. For å definere en tabell med navn temperatur, av typen float med 5 elementer, skriver vi altså følgende:

float temperatur[5];

Dersom vi har en rekke verdier vi vil gi elementene med en gang, gjør vi det slik:

float temperatur[5] = {10.2, 14.4, 12.8, 17.3, 19.5};

Her bruker vi { og } for å signalisere at vi har en hel blokk med verdier som skal tilordnes hvert sitt element i tabellen. Merk at dette bare kan gjøres i selve definisjonsuttrykket der vi oppretter temperatur! Det kan ikke brukes f.eks. slik:

float temperatur[5];

...

temperatur = {10.2, 14.4, 12.8, 17.3, 19.5};

Men hvordan bruker vi verdiene i tabellen? Hvordan "finner" vi dem? For å gjøre det, må vi ved å indeksere tabellen. Dette gjør vi ved hjelp av indekseringsoperatøren, []. Den tar et nummer og finner elementet i tabellen som har det nummeret:

float x = temperatur[2];  /* x = 12.8 */

Her opretter vi altså først tabellen temperatur og elementene verdier. Deretter opretter vi variabelen x og gir den verdien til element nummer 2 i temperatur. Merk at tabellindekser går fra 0 og ikke 1. Vi kan også gå andre veien og gi elementer i tabellen en ny verdi ved hjelp av indekseringsoperatøren:

temperatur[4] = 3.3;

Her vil altså det femte elementet (husk at vi teller fra 0) få verdien 3.3.

Vi kan også bruke andre variabler som indekser:

int indeks = 4;
temperatur[indeks] = 3.3;

Denne koden vil altså gjøre det samme som den over: Sette element nummer fem i temperatur til 3.3.


Iterasjon[rediger]

Tabeller har nok allerede vist seg nyttige i mange tilfeller. Nå skal vi se på hvordan vi kan "gå over" en tabell og bruke hvert element til noe. I det neste eksempelet skal vi samle temperaturene over en uke i en tabell og finne gjennomsnittstemperaturen. Det første du kanskje tenker deg er noe slikt:

#include <stdio.h>

int main(void)
{
	float temperatur[7] = {10.2, 14.4, 12.8, 17.3, 19.5, 20.3, 13.2};
	float gjennomsnitt;

	gjennomsnitt = 	temperatur[0] + temperatur[1] + 
				temperatur[2] + temperatur[3] + 
				temperatur[4] + temperatur[5] +
				temperatur[6];

	gjennomsnitt /= 7;

	printf("Gjennomsnittstemperaturen er %g.\n", gjennomsnitt);
	return 0;
}

Det ser, mildt sagt, uoversiktlig ut. Koden skriker rett og slett ut etter en totalrenovering. Hvis du husker tilbake til forrige kapittel om løkker, husker du muligens også at vi kan la en variabel begynne på 0 og øke til den er et annet tall vha. en løkke. Nå skal vi ordne programmet over ved å bruke en for-løkke:

#include <stdio.h>

int main(void)
{
	float temperatur[7] = {10.2, 14.4, 12.8, 17.3, 19.5, 20.3, 13.2};
	float gjennomsnitt;
	int indeks;

	for (indeks = 0; indeks < 7; indeks++) 
		gjennomsnitt += temperatur[indeks]
	gjennomsnitt /= 7;

	printf("Gjennomsnittstemperaturen er %g.\n", gjennomsnitt);
	return 0;
}

Det vi gjør her, er å opprette en variabel, indeks, som vi skal bruke som indeks for hvert element i temperatur. Vi bruker for-løkka til å la indeksgå fra 0 til 6. Hver gang løkka kjøres, henter vi verdien i elementet med den indeksen i tabellen, og legger det til gjennomsnitt. Etter løkka er ferdig (når indeks er 7), deler vi det på 7 og får gjennomsnittstemperaturen. Å gå over en tabell på denne måten og gjøre noe med hvert element, kaller vi å iterere over tabellen.

Strenger[rediger]

En streng er som sagt også en tabell. En streng er faktisk ikke forskjellig fra andre tabeller på noen måte; det er en helt vanlig tabell med elementer av typen char. Om vi har strengen "Hei" ser den slik ut i tabellform:

+---------+
|    H    |   
+---------+
|     e   |  
+---------+
|     i   |  
+---------+
|    \0   |  
+---------+

\0 er en escape-character slik som \n, og står for, ikke tegnet, men verdien 0. 0 brukes som termineringsverdi for strenger. Når operativsystemet får beskjed om å printe strengen, vil det iterere over den og printe tegn for tegn, til den kommer til verdien 0 som forteller den at strengen her er slutt. Hvis vi ikke hadde tatt med \0, ville printefunksjonen fortsatt utover i minnet til tilfeldigvis fant en 0. Nå tenker du kanskje hvorfor vi ikke kan bruke tegnet "0" som sluttkode eller hvorfor vi fortsatt kan skrive 0 i strengen våre? Dette er fordi 0 som i tegnet "0" blir lagret som ASCII-koden for det tegnet, nemlig 48. Når kompilatoren kommer over "\0" i en streng, vil den bytte den ut med verdien 0 fordi vi har en backslash foran, akkurat som \n egentlig blir byttet ut med 13, eller "A" blir byttet ut med 65.

Siden en streng er en helt vanlig tabell kan vi da behandle hvert element, dvs. bokstav individuelt:

#include <stdioh.>

int main(void)
{
	char ord[20];

	printf("Skriv et ord: ");
	scanf("%s", ord);
	printf("Ordets første bokstav er %c\n", ord[0]);
	return 0;
} 

Her lagres det innleste ordet i tabellen ord. 0 er indeksen til det første elementet i ord, som naturligvis er den første bokstaven som blir tastet inn. Merk at vi gir printf "%c" i kontrollstrengen, siden ord[0] er en helt vanlig char. Vi kan også lage strenger selv:

#include <stdio.h>

int main()
{
	char streng[4];
	streng[0] = 'H';
	streng[1] = 'e';
	streng[2] = 'i';
	streng[3] = 0;
	printf("%s", streng);
	return 0;
}

Her setter vi hvert element i strengen selv, og terminerer (avslutter) den med 0. Merk at apostrof brukes på enkle bokstaver, mens hermetegn brukes på strenger med flere bokstaver. Selvsagt kunne vi omskrevet programmet over til den vanlige måten vi definerer tabeller og gir dem verdier med en gang:

char streng[4] = {'H', 'e', 'i', 0};

Men når det kommer til char-tabeller er det en enklere måte å gjøre det på, som vist før:

char streng[4] = "Hei";

Alt mellom " og " blir automatisk omgjort til en streng med en nullterminator.

Senere vil vi se at det med C følger en rekke funksjoner som er laget for å jobbe med tabeller, og spesielt med strenger. Vi har f.eks. en funksjon for å finne ut hvor lang en streng er, nemlig strlen:

strlen(streng);

Denne tar en streng og returnerer hvor lang den er, ekskludert \0. strlen er deklarert i string.h. Dermed må string.h inkluderes i programmet for å bruke den. Med strlen kan vi endre litt på forrige program -- denne gangen kan vi også oppgi ordets siste bokstav:

#include <stdio.h>
#include <string.h>

int main(void)
{
	char ord[20];

	printf("Skriv et ord: ");
	scanf("%s", ord);
	printf("Den første bokstaven er %c og den siste er %c\n", ord[0], ord[strlen(ord) - 1]);
	return 0;
}

Den siste bokstaven i en streng er alltid element nr. lengde - 1. Vi må trekke fra 1 fordi strlen returnerer lengden på strengen, mens C opererer med elementer fra indeks 0, og ikke 1. Dersom strlen ikke var definert, kunne vi ha gjort følgende for å finne lengden:

#include <stdio.h>

int main()
{
	char streng[] = "Hei og hopp!";
	int lengde;

	for (lengde = 0; streng[lengde] != 0; lengde++)
	printf("Strengen %s har %d bokstaver.\n", streng, lengde);
	return 0;
}

Her itererer for-løkka over strengen og øker lengde med 1 hele tiden, helt til elementet i strengen med indeksen lengde er 0. Da vet vi at vi har nådd slutten av strengen, og lengde må da holde lengden av den.

Flerdimensjonale tabeller og indekseringsformelen[rediger]

Noen ganger er det en fordel å ha tabeller med flere kolonner. En slik tabell kaller vi en flerdimensjonal tabell. Sett at vi lager et tre på rad-spill. Da er det bedre å representere brettet slik:

+------+------+------+
|      |      |      |
+------+------+------+
|      |      |      | 
+------+------+------+
|      |      |      |
+------+------+------+

Her har vi tre kolonner og tre rader. En slik tabell kalles en todimensjonal tabell: Vi har rader (y) og kolonner (x). En slik tabell definerer vi slik i C:

int brett[3][3];

Eller bare slik:

int brett[9];

Begge resulterer i samme tabell med 9 elementer, men førstnevnte vil kunne indekseres med to indekseringsoperatører:

int brett[3][3];
int x = brett[2][1];

Her får x verdien til elementet i rad 3, kolonne 2 i brett. Husk at indekser alltid telles fra 0 og ikke 1. Vi kan også indeksere med én indekseringsoperatør:

int brett[3][3];
int x = brett[2 * 3 + 1];

Her får x verdien til elementet brett[7] (2 * 3 + 1 = 7) som er akkurat samme element. Det aritmetiske uttrykket vi brukte for å komme til element 7, kalles indekseringsformelen og er slik:

rad * antall rader + kolonne

I vårt tilfelle ville vi finne elementet i rad 3, kolonne 2. Da brukte vi 2 for raden, 3 for antall rader og 1 for kolonne, og fikk 7.

Dersom definerer brett som brett[9] og ikke brett[3][3] kan vi kun bruke indekseringsformelen.

Til slutt i dette kapittelet kommer et eksempel på hvordan vi kan bruke ting vi har lært som for, switch og tabeller, til å printe ut et tre på rad-brett basert på dataene i en tabell:

#include <stdio.h>

int main(void)
{
    int brett[3][3] = { 1, 0, 2, 0, 2, 0, 1, 0, 1 };
    int rad, kol;
    char tegn;
    
    for (rad = 0; rad < 3; rad++) {
       for (kol = 0; kol < 3; kol++) {
           switch (brett[rad][kol]) {
               /* dersom vi finner 1, er det X */
               case 1: 
                   tegn = 'X';  
                   break;
               /* dersom vi finner 2, er det O */
               case 2:
                   tegn = 'O';  
                   break;
               /* dersom vi finner noe annet, er ikke ruta tatt */
               default:
                   tegn = ' ';  
           }
           /* print tegnet og et mellomrom til neste */
           printf("%c ", tegn);
       }
       /* hopp til neste linje for den neste raden av brettet */
       printf("\n");
   }

   return 0;
}