6. Lektion

Denne lektion handler om Pointers

Emnerne behandles i lærebogen på siderne 249 til 305

Pointer er en underlig måde at håndtere data på, man kan ofte opnå samme resultat ved at anvende andre metoder til at udføre den samme funktion på. En af styrkerne i pointere er, at man kan få funktioner og procedure til at returnere mere end en variabel. Ved håndtering af strenge kan man f.eks. få disse til at fylde mindre, når de anvendes i et array. Dette har normalt kun virkelig betydning ved små maskiner eller ved meget store datamængder.

Hvad er en pointer
En pointer giver mulighed for at få fat i variabler og mere sammensatte datastrukturer uden at referere direkte til variablen. Mekanismen, som anvendes til dette, er variablens adresse. I praksis betyder det, at adressen virker som bindeled mellem variablen og programmet, som arbejder med variablen. Pointere er ikke så uforståelige, som de ofte bliver gjort til. Målet med denne lektion en langsom at opbygge en forståelse for principperne for pointere og deres anvendelse.

Hvorfor bruger man så pointere
Pointers bruges meste i tilfælde hvor det at håndtere en variabel er vanskeligt eller uhensigtsmæssigt. Grunde til at anvende pointere kan opsummeres således:

  1. Man ønsker at returnere mere end en værdi fra en funktion.
  2. At returnere arrays og strenge mere praktisk fra en funktion til en anden.
  3. At manipulere arrays på en lettere måde ved at flytte pointere til elementer i array'et i stedet for at flytte rundt på array'et hele tiden.
  4. Til at oprette complexe datastrukture, som f.eks. lænkede lister og binære træer, hvor et sæt data indeholder oplysninger om, hvor det næste datasæt i listen befinder sig.
  5. Til at overlevere informationer om hukommelsen. Anvendes f.eks. i forbindelse med malloc(), som returnere oplysninger til programmet om, hvor der findes ledig hukommelse i maskinen.

Funktionen malloc() vendes der tilbage til i en senere lektion.

Mange programmører påstår, at hvis man anvender pointere oversættes programmerne hurtigere. Den påstand holder ikke med moderne Compilere.

Du har allerede anvendt pointere i forbindelse med funktioner, i scanf() funktionen og ved udskrivning af strenge, anvendte du pointerkonstanter. Pas på, der findes også pointervariabler. Det er netop samarbejdet mellem pointerkonstanter og pointervariabler, som viser pointeres "muskler".

BEMÆRK, en pointerkonstant er en adresse til en variabel, en pointervariabel er en variabel, som indeholder en adresse.

Returnering af data fra en funktion
Du har allerede set, hvorledes man kan aflevere flere argumenter til en funktion, men man kan kun returnere en enkelt værdi fra en funktion. Pointere kan anvendes til at få flere variabler returneret til den kaldende funktion. Den teknik er måske den mest simple af de ting, man kan anvende pointere til. Men samtidig er det den mest anvendte teknik i almindelig programkonstruktion.

Eksempel 1 er blot for at repetere, hvad der sker med variabler, som sendes til en funktion ved brug af argumenter i funktionskaldet.

Eksempel 1
void get2tal(int xx, int yy)
{
   int temp;

   printf("\nAnden udskrift: xx = %d og yy = %d ", xx, yy);
   temp = xx;
   xx = yy;
   yy = temp;
   printf("\nTredie udskrift: xx = %d og yy = %d ", xx, yy);
}

void main(void)
{
   int x = 4, y = 7;

   printf("\nFørste udskrift: x = %d og y = %d ", x, y);
   get2tal(x, y);
   printf("\nFjerde udskrift: x = %d og y = %d ", x, y);
}

Når man kører programmet, ser man, at tallene, som det kan forventes, er byttet om i tredie udskrift, men der er ikke sket noget med variablerne i fjerde udskrift. Eksemplet, som kan hentes sidst i denne lektion, har fået tilføjet et par linier, som udskriver adresserne til variablerne, her ses det at der er fire forskellige adresser, der er altså oprettet fire forskellige variabler i hukommelsen.

Nu ændres programmet en lille smule. Idet der indføres pointere som variabler i proceduren. 

Eksempel 2
void get2tal(int *xx, int *yy)
{
   int *temp;

   printf("\nAnden udskrift: xx = %d og yy = %d ", *xx, *yy);
   *temp = *xx;
   *xx = *yy;
   *yy = *temp;
   printf("\nTredie udskrift: xx = %d og yy = %d ", *xx, *yy);
}

void main(void)
{
   int x = 4, y = 7;

   printf("\nFørste udskrift: x = %d og y = %d ", x, y);
   get2tal(&x, &y);
   printf("\nFjerde udskrift: x = %d og y = %d ", x, y);
}

Nu bliver det tilsyneladende kompliceret, men også kun tilsyneladende.

Når du tænker på variabler, er det med deres navne, i dette eksempel altså x og y. Hvis vi vil kende adressen, hvor x og y er gemt, skal vi skrive &x og &y. Det er de adresser, styresystemet tildeler variablerne. Når de erklæres i begyndelsen af programmet. Det er altså konstante pointere, du kan ikke ændre på adresserne, efter at variablerne er erklæret.

Sammenligner vi nu med, hvad get2tal() "tænker", så er det, at det er *xx og *yy, som er variablerne, mens xx og yy er adresserne til variablerne, disse adresser kan vi manipulere, dvs. det er variable pointere.

Det benytter vi os af i eksempel 2, hvor vi bytter værdierne i proceduren, som nævnt ovenfor var der sat fire variabler af i hukommelsen i eksempel 1, nemlig x, y, xx og yy.
I eksempel 2 er der kun to variabler, vi bytter indholdet i adresserne, hvorefter vi rent faktisk har fået byttet indholdet i de oprindelige x og y.

Konklussion
int *px, *py;

Normalt benytter jeg p foran variabelnavnene, når jeg erklærer pointervariabler, så kan jeg med det samme se hvilken type variabeler, jeg har foran mig i programmet

Klippet ud i pap, int fortæller, at de følgende pointervariabler kommer til at pege på variabler af typen heltal. * fortæller, at variablen er en pointer og at den vil indeholde en adresse. px og py er navnet på pointervariablerne.
Bemærk at * står foran en enkelt variabel, mens den ved multiplikation anvendes mellem flere variabler, så compileren vil ikke blive forvirret i denne situation.

Erklæres: int x, y; er x og y to heltalsvariabler, derefter er &x og &y, henholdsvis x og y's adresse i hukommelsen.

Erklæres: int *ptrx, *ptry; er *ptrx og *ptry heltalsvariabler, derefter er prtx og ptry adresserne til *ptrx og *ptry i hukommelsen.
Dvs. der kan sættes lighedstegn mellem x = *ptrx; og mellem &x = ptrx;

Erklæres: char navn[81]; og der skrives: printf("%s", navn); giver %s og navn strengens indhold og printf("%d", navn); giver %d og navn adressen til strengens første element.

I det følgende afprøves ovenstående påstande:

Eksempel 3
void main(void)
{
   int x = 4, y = 7;
   int *px, *py;
  
printf("\nx er lig med %d, og y er lig med %d\n", x, y);
   px = &x;
  
py = &y;
   *px = *px + 10;
   *py = *py + 10;
  
printf("\nx er lig med %d, og y er lig med %d", x, y);
}

 

I eksempel 3 erklæres to variabler x og y, samtidig gives variablerne værdierne henholdsvis 4 og 7. Styresystemet har tildelt dem hver sin adresse i hukommelsen, det er: &x og &y. I næste linie erklæres to pointervariabler *px og *py. En kontroludskrift viser derpå hvad variablerne x og y indeholder. Derpå afleveres x og y's adresser til pointervariablerne. Derpå udføres en beregning med *px og *py. En ny kontroludskrift af x og y viser, at selvom der blev manipuleret med *px og *py, er det indholdet af x og y, som er forandret.

Lad os se hvordan pointere kan påvirke arrays. Først skrives det helt traditionelt uden brug af pointere.

Eksempel 4
void main(void)
{
   int numre[] = { 92, 81, 70, 69, 58 };
   int tal;
   printf("\n");
   for (tal = 0; tal<5; tal++)
      printf("\nNummer[%d] = %d", tal, numre[tal] );
}

Dette program kan også skrives om til at anvende pointere

Eksempel 5
void main(void)
{
   int numre[] = { 92, 81, 70, 69, 58 };
   int tal;
   printf("\n");
   for (tal = 0; tal<5; tal++)
      printf("\nNummer[%d] = %d", tal, *(numre+tal) );
}

Nu er det med at holde ørene stive. numre giver adressen til første element i array'et. Når man derpå lægger 1 til skulle vi forvendte at vi fik næste celle, men her har C's fædre tænkt sig godt om, idet når der arbejdes med pointere, går C op i erklæringen og finder ud af, hvad det er for en type, der er tale om, hvorefter pegepinden flyttes det antal celler, der svarer til, at man kommer til adressen til det næste element i rækken.

Pointere og strenge
Strenge og arrays er meget tæt forbundet. Mange af biblioteksfunktionerne i C som arbejder med strenge anvender pointere. Lad os se på en som returnere en pointer.

ptr = strchr(str, 'x');

Pointeren ptr vil få en værdi svarende til adresse på den første gang man møde x i strengen str. Bemærk det er ikke positionen i strengen, som jo begynder med 0 (nul), med den faktiske hukommelsesadresse. F.eks. 8064.
i eksemplet herunder anvendes funktionen.

Eksempel 6
#include <stdio.h>

void main(void)
{
   char ch, line[81], *ptr, *strchr();

   puts("Indtast en linie der skal gennemsøges: ");
   gets(line);
   printf("\nIndtast tegne der skal søges efter i teksten: ");
   ch = getche();
   ptr = strchr(line, ch);
   printf("\nTeksten er: %s", line);
   printf("\nTeksten starter i adressen %u: \n", line);
   printf("Første gang man møder %c er på adressen %u\n", ch, ptr);
   printf("%c står på plads nr %d i teksten\n", ch, ptr-line+1);
}

Du undrer dig måske over udtrykket *strchr();? Funktionen returnere en pointer til et tegn, så det skal erklæres til at være af denne type. Man kan undlade den pågældende erklæring, hvis man i stedet includer biblioteket <string.h>. Det vil man normalt gøre, eksemplet er kun medtaget for at vise, hvorledes man kan løse en sådan opgave uden af medtage biblioteksfunktioner. 

Erklæring af et array af pointer, som peger på strenge
Der er en lille forskel, når man vil anvende pointere til array, som indeholder strenge. Lad os se på et lille eksempel, for at gøre det lettere at forstå eksemplet, vil jeg anvende et eksempel du kender fra en tidligere lektion. Det er eksemplet med adgangskontrollen.
I det følgende eksempel er array'et erklæret som strenge der peges på med pointere.

Eksempel 7
void main(void)
{
   int tal;
   int godkend = 0;
   char navn[40];
   char *liste[MAX] =
                    { "Katrine",
                      "Christian",
                      "Poul",
                      "Lotte",
                      "Henning"
                    };

   printf("Indtast dit navn: ");
   gets(navn);
   for (tal = 0; tal < MAX; tal++)
      if ( strcmp (liste[tal],navn) == 0 )
      {
         godkend = 1;
         break;
      }
   if ( godkend == 1 )
      printf("Døren er åben du kan gå ind!");
   else
      printf("Du har ingen adgang, SKRID!");
}

Hvad betyder udtrykket *liste[MAX]? Normalt skal sådan et udtryk læses på arabisk, dvs. fra højre mod venstre. Se figuren herunder.

I den tidligere version af dette program var strengene gemt i et rektangulært array med 5 rækker og 10 kolonner. I den nye version er strengen gemt i hukommelsen i en lang følge, de udgør på denne måde ikke et array i normal forstand, idet der ikke spildes plads til tomme tegn, dvs. der er ikke nogen spildt plads i hukommelsen.

Det sidste eksempel handler om at få en funktion til at ændre et array og returnere resultatet af ændringen til den kaldende funktion:

void plusti(int *ptr, int str, int kon)
{
   int k;
   for(k=0; k<str; k++)
      *(ptr+k) = *(ptr+k) + kon;
}


void main(void)
{
   int array[SIZE] = { 3, 5, 7, 9, 13 };
   int konst = 10;
   int j;

   for (j=0; j < SIZE; j++)
      printf("%d ", *(array+j) );// Kontroludskrift af array
   printf("\n");

   plusti(array, SIZE, konst); // kald til funktion

   printf("\n");
   for (j=0; j < SIZE; j++)
      printf("%d ", *(array+j) ); // Vis hvad array'et indeholder nu
}


Her forsyner det kaldende program funktionen med adressen til et array, dvs. første argument er en pointer til et array. Næste argument er størrelsen på array'et. Det sidste argument er den konstant, der skal tilføjes til hver enkelt af værdierne i array'et.

Øvelse 1
Konstruer et program, hvor man kan indtaste navne til et array, når det sidste navn er indtastet, skal navnene sorteres i alfabetisk orden
.
Tip: Brug f.eks.
if ( strlen(navn[tal] )==0 ) break; til at afslutte indtastningen af navne til array'et. 

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