5. Lektion

Denne lektion handler Arrays og Strenge

Emnerne behandles i lærebogen på siderne 196 til 237 og 306 til 345

Vi skal i denne lektion beskæftige os med Arrays og Strings. Når jeg har valgt at tage dette sammen, er det fordi, man kan grundlæggende betragte strenge, som værende Arrays. I mange lærerbøger om C tager man pointers med på dette tidspunkt af undervisningen. Jeg syntes, det er svært nok, at få styr på arrays (lister) og strenge (tekster), så jeg vil vente til næste lektion med at beskæftige mig med pointerne (pegepinde til hukommelsesadresser).
Jeg vil starte med arrays og slutte med at se på strenge, idet man kan betragte strengene som et specialtilfælde af arrays .
Et array er en liste med variabler. Variablerne skal være af samme type for at kunne samles i et array.
Hvis man f.eks. vil beregne gennemsnitstemperaturen for en uge, kan man oprette en variabel for hver af ugens dag, lægge dem sammen og dividerer med 7. F.eks.

                        gensnit = (mantp + tirstp + onstp + torstp + fretp + lortp + sontp) / 7

En sådan beregning vil det være meget lettere at lave med et array. Opgaven herover er selvfølgelig overkommelig, men hvis der skulle beregnes for en måned eller for et år, blev det straks et meget større arbejde at få opgaven programmeret.
Med et array ser det således ud:

Eksempel 1
#define LIM 40

void main(void)
{
   float temper[LIM];
   float sum = 0;
   int num, dag = 0;
   printf("\nIndtast et 0 (nul) for at stoppe programmet\n");
   do
   {
      if ( dag >= LIM )
      {
         printf("Buffer fuld!\n");
         dag++;
         break;
      }
      printf("Indtast en temperatur for %d: ", dag);
      scanf("%f", &temper[dag]);
   }
   while ( temper[dag++] > 0 );
      num = dag - 1;
   for (dag = 0; dag < num; dag++)
       sum += temper[dag];
   printf("\nGennemsnittet er %.2f", sum/num);
}

Der er meget nyt i dette program, så lad os se på det lidt efter lidt. Først skal vi fortælle compileren, at vi vil anvende et array med decimaltal. Men compileren skal også vide, hvor mange tal der maximalt må være i array'et. Størrelsen bestemmes med #define LIM 40, som sætter en konstant begrænsning for array'et på 40 elementer. float temper(LIM) erklærer derpå en liste, som kan indeholde maximalt 40 elementer af typen float.
Når man derpå skal referere til et element i listen, skriver man f.eks. temper[3], hvilket giver det 4 element i listen. Hov - der blev sagt fjerde element i listen, ja, for det første element i listen er temper[0], eller klippet ud i pap; et array starter altid med element nummer 0 (nul). Dette betyder igen, at i ovenstående tilfælde er sidste element i listen temper[39]. Den anden erklæring float sum = 0; er meget vigtig. Det er en meget almindelig fejl i forbindelse med programmering, at man glemmer at nulstille sine sum-variabler. I scanf(), skal man huske at sætte & foran variablen, & betyder, at man aflevere resultatet af indtastningen til adressen for variablen i stedet for til variablen direkte. Dette vendes der tilbage til i næste lektion.
Der anvendes en do - while-løkke til at modtage indtastningerne, løkken kører indtil en temperatur på 0 (nul) eller mindre indtastes. Programmet kan altså ikke arbejde med negative temperaturer.
Man har også en anden måde at erklære størrelsen af et array. Størrelsen kan sættes automatisk samtidig med tilskrivning af værdier. Hvis et array tilskrives under erklæringen, som vist herunder vil array'et automatisk få størrelsen 7.

int tabel[] = {20, 10, 5, 2, 1, 50, 25};

Ovenstående kunne f.eks. være en liste med de mønter, som findes af danske kroner.

Eksempel 2
#define LIM 7 // størrelsen af array'et

void main(void)
{
   char a;
   float tabel[LIM] = { 20, 10, 5, 2, 1, .50, .25 };
   int tal, antal;
   float amount;

   printf("\nProgram som beregner,
           hvor mange mønter et indtaste beløb kan opdeles i\n");
   printf("\nIndtast beløbet i kr. (f.eks. 54.25): ");
   scanf("%f", &amount);
   for (tal=0; tal<LIM; tal++)
   {
      antal = ((int)(amount*100)) / ((int)(tabel[tal]*100));
      printf("Mønter med værdien=%6.2f kr, ", tabel[tal] );
      printf("udgør %2d af beløbet!\n", antal );
      amount = ((amount / tabel[tal]) - (float)antal) * tabel[tal];
   }
   a = getch();
}
I nogle tilfælde kan det være praktisk at kunne ændre en type på en variabel, man har erklæret. Når man ændre en variabels type, siger man, at man laver et typecast. For at kunne foretage en division med to decimaltal (float's) og aflevere resultatet i heltalsvariablen antal, ganges variabelværdierne i den første linie i tælle-løkken med 100, hvorefter der foretages et typecast, således at float-variablerne bliver til int-variabler.
Ved beregningen af amout skal antal konverteres til float, for at kunne beregne den nye amount værdi.

I nogle programmer kan der opstå compilerfejl, når man anvender floating point tal. Hvis compileren skriver:

Floating point formats not linked

Skal der tilføjes følgende linier et eller andet sted i den pågældende kildetekst.

extern void _floatconvert();
#pragam extref _floatconvert

Vi har nu set på endimensionale arrays (vektorer), lad os fortsætte med et todimensionalt array (matrix). Det gør vi med et lille "sænke slagskibe"-program.

Eksempel 3
#define Height 5
#define Width 10

void main(void)
{
   char fjende [Height] [Width] =
                   { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
                     { 0, 1, 1, 1, 1, 0, 0, 1, 0, 1 },
                     { 0, 0, 0, 0, 0, 0, 0, 1, 0, 1 },
                     { 1, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
                     { 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }
                   };
   char ven [Height] [Width];
   int x, y;
   for ( y = 0; y < Height; y++)
      for ( x = 0; x < Width; x++)
         ven[y][x] = '.';
   printf("Indtast koordinater på formen x,y (4,2).\n");
   printf("Indtast et negativt tal for at afslutte!\n");
   while ( x >= 0 )
   {
      for (y = 0; y < Height; y++)
      {
         for ( x = 0; x < Width; x++ )
             printf("%c ", ven[y][x] );
         printf("\n\n");
      }
      printf("Indtast koordinaterne for dit skud: ");
      scanf("%d,%d", &x, &y);
      if ( fjende[y][x] == 1)
         ven[y][x] = 'R'; // Ramt
      else
         ven[y][x] = 'F'; // Forbier
   } // Slut på while
} // Slut på programmet
Ideen med ovenstående program er først og fremmest at demonstrere et todimensionalt array. Kør programmet og find ud af, hvad der sker.
Her kommer et program, som finder det største af en række indtastede tal:

Eksempel 4
#define MaxSize 20

int max(int list[], int size)
{
   int t, max;

   max =list[0];
   for ( t = 1; t < size; t++)
      if ( max < list[t] )
         max = list[t];
   return(max);
}

void main(void)
{
   int list[MaxSize];
   int tal = 0;
   int num;

   printf("\nIndtast et tal, eller 0 for at slutte\n");
   do
   {
      printf("Indtast et tal: ");
      scanf("%d", &list[tal]);
   }
   while ( list[tal++] != 0 );
   num = max(list,tal-1);
   printf("Det største tal er %d", num);
}
Ideen i ovenstående eksempel er, at vise hvorledes man kan lave en funktion, som tager flere argumenter og hvor det ene argument er et array.
Funktionen max() starter med at tage det første element i listen og lægge det ind i variablen max, derefter tæller funktionen sig igennem listen og hvert element sammenlignes med max. Hvis det aktuelle element er større end max, skiftes elementets værdi ud med det, der står i max.

Strenge - konstant
Hvis vi skriver printf("%s", "Kærlig hilsen"); er "Kærlig hilsen" en streng-konstant. At det er en konstant betyder, at den er placeret et eller andet sted i hukommelsen og at værdien ikke kan ændres. Hvad man ikke kan se umiddelbart er, at C har tilføjet et tegn til strengen, det er \0. Et tegn fylder normalt 1 byte i maskinens hukommelse. Det ser ud som om der er tilføjet to tegn, men det er der ikke, det er en såkaldt escape-sekvens, som er tilføjet. Man siger, at strengen afsluttes af et terminerende 0 (nul).

Strenge - variable
Før man kan indlæse en streng i et program, skal der sættes plads af i maskinens hukommelse. Dvs. der skal erklæres en variabel på præcis samme måde, som vi har set tidligere. Forskellen i forhold til tidligere er, at der nu ikke er tale om et enkelt tal eller bogstav, men derimod en sammenhængende række af tegn (en streng).

Eksempel 5
void main(void)
{
   char fnavn[15];

   printf("\nIndtast dit navn: ");
   scanf("%s", fnavn);
   printf("Hej med dig, %s", fnavn);
}

I ovenstående program har vi erklæret variablen char fnavn[15]; som betyder, at vi har oprettet et array på 15 tegn. Betyder det så, at man kan indtaste et navn på 15 tegn, nej ikke helt, husk hvad der blev sagt ovenfor, nemlig at en streng altid afsluttes med \0. Dette tegn snupper den sidste plads i strengen, så der er rent faktisk kun plads til 14 tegn i det indtastede navn. Se figuren herunder, hvorledes systemet opfatter strengoperationer.

Generelt kan man sige om alle typer arrays, lad være med at indtaste mere, end der er sat plads af til, undgå overflow.
Hvis man er i tvivl om, hvor lang en streng behøver at være, kan man afsætte 81 tegn. Der kan være 80 tegne på tværs af en standardskærm, så en erklæring som char fnavn[81]; vil i langt de fleste tilfælde være nok.
Når du ser på programmet, opdager du, at scanf("%s", fnavn); ser anderledes ud, end det du har set tidligere. Ganske rigtigt, der mangler &, det er derfor, jeg sagde, at man skal være meget opmærksom, når man anvender scanf(). Jeg fortalte, at & betød at det som scanf() modtog skal være en adresse. Ved strenge er fnavn netop en adresse. Variablen er et array og pr. definition er navnet på variablen = adressen på det første element i strengen. Når navnet allerede er en adresse behøver det ikke &.

I/O funktionerne gets() og puts()
Jeg har tidligere nævnt at en af svaghederne ved C er uoverskueligheden af input og output kommandoer. Nu er den gal igen. Man kan ikke til scanf("%s", fnavn); indtaste Genghis Khan, det kan man godt, men det eneste som ender i maskinens hukommelse er Genghis. Der skal erindres, at tal og bogstaver, indtastet til scanf(), kunne adskilles både med Mellemrum, Tab og Return. Løsningen på det problem finder man i en anden biblioteksfunktion, den hedder gets(), på samme måde som man kunne anvende printf() til udskrift, findes der en tilsvarende strengfunktion, den hedder puts(). Begge funktioner findes i biblioteket <stdio.h>. Programmet herunder demonstrerer, hvorledes funktionerne virker. I de følgende programmer skal include's <string.h>, når der i programmerne anvendes de specielle strenghåndteringskommandoer.

Bland aldrig scanf() og gets() i et program.

F.eks. kan følgende give problemer.

scanf("%d", alder);
gets(navn);

Efter at have indtastet en alder, taster man Return, variablen alder modtager det indtastede tal fint, men Return bliver i tastaturbufferen. Derpå modtager navn en tom navn fordi, der står et Return klar i bufferen, når gets() kommer til.
Det bedste, man kan gøre, er at anvende gets() til al input, hvis man skal arbejde med strenge. En indtastning af 21 vil da blive modtaget som "21", man må derpå gribe til anvendelsen af konversionsværktøj. Værktøjet til konvertering af "21" til et tal hedder tal=atoi("21"); på samme måde findes en atof() osv.

Eksempel 6
#include <stdlib.h>
void main(void)
{
   char streng[81];
   int loeb;
   char heat;
   float tid;
   char navn[81];

   printf("\n\nIndtast løbsnummer (1, 2, osv.): ");
   gets(streng);
   loeb = atoi(streng);
   printf("\nIndtast bogstav for heat (A, B, osv.): ");
   gets(streng);
   heat = string[0];
   printf("\nIndtast deltagerens tid (48.35): ");
   gets(streng);
   tid = atof(streng);
   printf("\nIndtast deltagerens navn: ");
   gets(navn);
   printf("\nDeltager: %s har i heat %c i løb %d, ", navn, heat, loeb);
   printf("\nopnået en tid på %8.2f sekunder.", tid);
}

En anden brugbar funktion er length=strlen(navn);

Et array af strenge
Tidligere i lektionen så vi på et to-dimensionalt array. Man kan arbejde med noget tilsvarende med strenge.
Det følgende eksempel er et simpelt eksempel på en adgangskontrol.
Eksemplet beder brugeren om at indtaste et navn, hvorefter navnet sammenlignes med en navneliste i programmet, hvis det indtastede navn ikke er i listen afvises personen.

Eksempel 7
#define MAX 5
#define LEN 40

void main(void)
{
   int tal; // Tæller i løkken
   int enter=0; // Flag som rejses hvis indtastet navn er OK
   char navn[40]; // Variabel til navn som indtastes af brugeren
   char liste[MAX] [LEN] = // Array med navne
                         { "Katrine",
                           "Peter",
                           "Alistair",
                           "Francisca",
                           "Gustav"
                         };

   printf("\nIndtast dit navn: ");
   gets(navn);
   for (tal=0; tal<MAX; tal++) // Gå igennem liste
      if (strcmp (&liste[tal] [0], navn) == 0 ) // hvis navn er i liste
      {
         enter = 1; // Sæt flag til sand
         break; // Hop ud af løkken
      }
   if ( enter == 1 ) // Hvis flaget er rejst
      printf("\nVær så god at træde inden for Deres Højhed!"); // vær flink
   else // hvis flaget ikke er rejst
      printf("\nVagt! Fjern denne person fra firmaets område!"); // afvis person
} // Slut på main()

Der er to mulige resultater af at køre dette program enten får man adgang eller man bortvises.
Bemærk, at der ikke er Tuborg-parenteser omkring navnene i liste, det var der om de enkelte linier i slagskibs-eksemplet, dette skyldes, at det handler om strenge, som er array's i sig selv. Læg også mærke til, at de enkelte elementer i arrayet er adskilt med komma.
Læg endvidere mærke til rækkefølgen af index, hvor array'et bliver erklæret. Det er vigtigt, at index til antallet af strenge i listen kommer før strengstørrelsen. Dette er meget vigtigt.
I programmet bliver også introduceret en ny strenghåndteringsfunktion, det er strcmp(&liste[tal][0], navn) == 0; strcmp() sammenligner strenge, funktionen returnere et heltal afhængig af udfaldet af sammenligningen. Det er i tilfældet strcmp(streng1, streng2);

Returneret værdi        Betydning
mindre end nul               streng1 < streng2
nul                                 streng1 = streng2
større end nul                 streng1 > streng2

Betydningen af større end og mindre end er den alfabetiske rækkefølge, den streng, som er tættest ved A, er den mindste streng.
Betydningen af &liste vendes der tilbage til i næste lektion.
Denne gang er der et ekstra eksempel, det er et lille program, som kan anvendes til at undersøge, hvorledes en streng er gemt. Kør programmet! Hvad er det der sker?

Øvelse 1
Konstruer et program, som kan sortere de tal, som bliver indtastet til eksempel 4, således at tallene bringes til at stå i stigende orden. 
Tip: Brug lærebogens bublesort funktion.

Øvelse 2
Udvid programmet fra øvelse 1 med en lille menu, hvor man kan vælge enten at få udskrevet max.-værdien eller den sorterede liste.

Eksempler og løsningerne kan hentes her
Eksempel 1
Eksempel 2
Eksempel 3
Eksempel 4
Eksempel 5
Eksempel 6
Eksempel 7
Eksempel 8
Øvelse 1
Øvelse 2