Pointers i C

Tags:    c++
Skrevet af Bruger #24 @ 16.06.2001
Hurtigt igang med pointere i C.

Forord.



Denne fil, beskriver rimelig hurtigt, pointere i C. Den tager udgangs punkt i, at læseren har et rimeligt kendskab til C, så som variabel deklarering, for/while loops etc.
Det er mit håb, at folk kunne bruge denne fil som enten reference, eller til at komme i gang med pointere i C.
Jeg tager naturligvis gerne imod seriøs kritik, og håber da ogsa folk der læser denne, vil melde tilbage, hvis:

- man føler visse omrader ikke er beskrevet godt nok.
- man finder fejl.
- man synes der skal et nyt punkt med.
- man har forslag til forbedringer.
- man har noget på hjertet.


Da jeg ikke er den store forfatter, vil jeg gå i gang med det samme.

Adresse Add og SUB , Array og pointere , Dynamisk allokering , Hvad er en pointer , Pointere som parametre , Pointer og pointer imellem , Pointer til funktion , Pointer til pointer , Pointer til struktur , Pointere og strenge

Hvad er en pointer ?

Egentlig bør vi forst lige kigge pa, hvad en variabel er. Her defineres én :

unsigned char A=5;

A har nu 2 værdier vedhæftet sig. En adresse, pa den hukommelses plads den optager, og en værdi, som er på den adresse. A er hvad man kalder et symbol for en adresse. Disse værdier er ogsa kaldt "lvalue"(left) og "rvalue"(right). lvalue er adressen, og rvalue er den værdi der skal kopieres over på denne.
Normalt er lvalue = rvalue. Altsa navnene pa hvilken side af ligheds tegnet de star. Der er dog fa regler omkring dette, sådan lige kort:

A = 5 lvalue = rvalue Ingen problemer her.
5 = A rvalue= lvalue's rvalue Det går i midlertidig ikke sa godt. Man vil ligge indholdet af A ned på adresse 5.
B = A lvalue = lvalue's rvalue man kopierer indholdet af A over i B.

En pointer er meget det samme. Her definerer vi en:

unsigned char *p;

Som vi kan se, er der kommer en unary pa (*'en), som fortæller compileren, at det er en pointer-variabel. En pointer er faktisk bare en variabel.
Unary er ++,--,*,& - altsa tegn som ikke behøver noget pa den anden side af sig. (p++, *p, &a, a--, etc.)
Der er muligt at definerer (i dette tilfælde unsigned char) pointere til alle typer, selv structs og lign.
Forskellen på en pointervariabel og en variabel er, at pointeren indeholder en adresse. Man kan igennem denne adresse manipulerer med data. En variabel er blot et symbol for en adresse, og denne kan ikke andres.
Sådan bruges en pointer:

*p = data på adressen
&p = adressen, på adressen, pointeren indeholder.

Det er muligt at bruge typecasts for pointere, lige som alm. variabler, men man skal bruge dette med omtanke. (man skal generelt bruge pointere med omtanke, da de fleste fejl med pointere er, at man skriver et sted i hukommelsen, man ikke må.)

eks.:

unsigned char *p;      

Her har vi en byte pointer der hedder p. Den kan ikke bruges som den er, fordi den er U-initialiseret - altså en pointer som ikke er sat til at pege på noget. for at bruge pointeren, skal vi bruge noget allokeret plads. Vi laver en variabel:

unsigned char tal=5;
Bemærk at, tal er af samme type som p. Nu skal vi have p til at "pege" pa variablens adresse... det gøres pa folgende made:
p = &tal;
(husk: & i pointer sammenhæng betyder adresse.* i pointer sammenhæng betyder indhold på adresse.)
Nu kan vi bruge p, fordi den peger på tal's adresse. Vi prøver at andre det den peger pa...
*p = 8;
indholdet af den adresse p peger pa er nu 8. Bevis:
printf(" %d \\n", tal);

Jeg kan anbefale at prove det af med en compiler, og se det virke. Her er programmet.
unsigned char *p, tal=5;
p = &tal;
*p = 8;
printf(" tal = %d \\n",tal);

Den skriver nu 8 på skærmen... hvorfor? fordi p peger på tal's adresse, og derfor har tal ændret indhold, da vi pillede ved den adresse p peger på... Dette er i praksis ikke meget bevendt, men senere vil vi fa bedre indsigt i, hvordan det hænger sammen.

Adresse ADD og SUB

Man kan med en pointer (i modsætning til et array) sige:

p++; (jeg går ud fra folk ved at ++, er det samme som at sige p+=1; eller p=p+1;)

I dette tilfælde vil pointeren hoppe én byte 'op' i hukommelsen, netop fordi det er en unsigned char pointer (byte-pointer).
Havde det været en unsigned long, som har en størrelse pa 4 byte's, ville en p++ resulterer i at p peger pa 4 pladser længere 'oppe' i hukommelsen.
Lige så, hvis man lavede en struct pa 20 bytes, og p blev defineret til at være en pointer til denne type, ville p++ resulterer i et spring pa 20 bytes i hukommelsen.
p++; = p+=(sizeof(TYPE)); hvor type er det, pointeren er defineret til.
En hurtig måde at teste det på kunne være: unsigned long *p,*d;
p = malloc(10*sizeof(unsigned long));
d = p;

printf(" %06p\\n",d);
d++;
printf(" %06p\\n",d);
free(p);

Malloc kommer vi til under dynamisk allokering. 10*sizeof(TYPE) betyder bare man reserverer 10 pladser i hukommelsen af typen TYPE.

Repetition:
unsigned char *p;
unsigned char tal = 5;

*p = 5;  is a dont do... fordi man ikke ved hvor p peger endnu
*p = &tal; is a dont do... fordi at den ukendte adresse p peger på, kommer til at indeholde adressen på tal.. ikke godt.
p = tal;  is a dont do... fordi man prover på at få p til at pege på den adresse som er lig indholdet af tal.. i dette eks. =5 - KRITISK! men man får nok også/burde få en compiler warning.
p = &tal;  ahh.. p peger nu på tal's adresse!.
p++;    p peger på naste type, den er defineret til, i hukommelsen
p--; p peger på forrige plads, af typen den er defineret til, i hukommelsen. Dette skal man dog undgå, da det ikke er godt for cache af skrive baglæns i hukommelsen. Det kommer vi ikke nærmere ind på her.

Vi kan nu læse fra, og proppe ting ind på den adresse p peger pa.. i dette tilfælde er det den samme hukommelses plads som tal, så derfor ville:

tal = 8; vare det samme som *p = 8;

Det er nu på sin plads at fortælle om en rigtig nyttig ting, man kan gøre, med pointere. Vi siger:
unsigned char Ckybuf[320*200];
unsigned char *p;
p = Ckybuf;

Hvis jeg nu ville fylde denne buffer op, kunne jeg gøre det på mange måder, men jeg vil lige vise én bestem måde (som for øvrigt kan bruges til mange ting):
*(p++) = 0;
Hvad sker der nu ? Fordet første er der '(  )' uden om pointeren, fordet ander er der en ++ efter pointeren.
Det der sker er, at jeg ligger et '0' ind på den adresse p peger på, og flytter pointeren til næste element i hukommelsen. DVS. at hvis jeg loopede hen over denne linie 320*200 gange, ville array'et Ckybuf, blive fyldt helt op med 0'er (eller nul-stilles). Det vil blive bruge lidt senere, og i Pointer og pointer imellem er der et eksempel på brugen af dette.


Pointer og pointer imellem.

Man kan få to pointere til at pege samme sted uden noget videre...
unsigned char *p, *q;
unsigned char tal=5;
p = &tal;
q = p;
Nu peger både p og q pa samme sted, nemlig tal.
Lad os sige vi har en unsigned char Array, som hedder Ckybuf, og en unsigned long pointer :
unsigned char Ckybuf[320*200];
unsigned long *D;
int x;
Vi vil gerne have slettet denne buffer, og vi laver vores egen rutine til dette. (altså ingen memset). Som vi ved er longwords = 4 bytes
så hvis man kunne skrive 4 bytes af gangen i stedet for 1, ville dette naturligvis være hurtigere. For at dette kan lade sig gøre, skal man
lige huske på, at 4 skal gå op i Array-størrelsen. Det vi skal bruge er en void pointer, eller et cast. vi starter med cast.
D = (unsigned long *) Ckybuf;

x = (320*200)/4;                            /* man kan vel kalde det for en slags 'loop-unrole'.*/
do{
 *(D++) = 0;
}while(--x);
Det er naturlig vis også muligt at bruge en void-pointer. En void-pointer er en pointer type der kan pege på alt. Men hvis man skal bruge den skal den have vide hvilken type den peger på, pga. forklaringen i Adresse add sub. Det skulle jo gerne vare sådan at en unsigned long pointer der bliver pluset med 1, hopper med 4 bytes i hukommelsen. Men lad os se hvordan man definere en:
void *Vprt;
Det var jo ikke svært. lad os sige følgende:
unsigned char p[1];
unsigned long D[1];
p[0]=4;
d[0]=5;

Vprt = &p;
printf(" %d \\n", *((unsigned char *)Vprt));  /* Her type caster vi void-pointeren til den ønskede type.*/
Vprt = &D;
printf(" %d \\n", *((unsigned long *)Vprt));
Nogen vil sikkert undre sig over, hvordan pointeren ser ud i printf linierne. Men man skal huske på, vi skal fortælle compileren, hvilken størrelse vores void-pointer skal arbejde efter. Bortset fra det, er der ikke noget specielt over en void pointer.

Pointere som parametre:

Det er ofte mere nyttig at sende adressen på ens data eller struct's, over til ens funktion, i forhold til at sende f.eks 10 parametre over.
På denne måde kommer der også kun 1 værdi på stacken, istedet for 10, plus det ser meget mere overskueligt ud.
Hvis man vil manipulerer med en værdi igennem en funktion kan man gøre sådan her:
a = my_mul(a,5);
hvor funktionen vil se sådan her ud:
int my_mul(int tal, int faktor){
return(tal*faktor);
}
Nu vil vi lave en funktion, der bytter om på 2 værdier. Det bliver svært på ovenstående måde, så vi overfører adresserne på variablerne. Som vi ved passer de jo perfekt til en pointer, så vi laver en funktion som nedenstående.
void swap(unsigned char *a, unsigned char *b){
unsigned char temp=0;
 temp = *a;
 *a = *b;
 *b = temp;
}
og man kalder funktionen sådan her fra main:
swap (&tal1, &tal2);      /* husk at
& = adresse, sa man overforer adressen pa variablerne til pointerne i under-rutinen.*/
Som vi kan se bliver det til :  unsigned char *a = &tal1 - og det har vi set for.

en anden swap rutine:
void swap(unsigned char *a, unsigned char *b){
 *a^=*b;
 *b^=*a;
 *a^=*b;
}
Hvis vi nu tager et array (eller en buffer) som hedder:
unsigned char Ckybuf[320*200];
Det kunne meget vel være en virtuel skærmbuffer. Nu vil vi lave en funktion, som fylder denne buffer med en bestemt værdi. Vi skal vide hvor vi skal fylde hukommelsen op, med hvilken værdi, og hvor stort et omrade:
my_fill(Ckybuf, 320*200, 255);
Ckybuf er ADRESSEN på bufferen (bliver forklaret i Array og pointere). 320*200 er antal bytes vi vil fylde, og 255 er værdien vil fylder op med. Da Ckybuf allerede er en adresse, kan vi overfører denne direkte til en pointer i vores
my_fill funktion:
void my_fill(unsigned char *buf, unsigned int antal, unsigned char tal){
do{
 *(buf++) = tal;
while(--antal);
}
Som vi kan se, bliver buf talt op i adresserne, og dette gør ikke nøget da det bare er en kopi af den pointer vi fik, fra der my_fill blev kaldt.
Dette kan naturligvis også gøres ved strukturer (struct). der ville det blive struct screeninfo *SI, i overforsels parameteret til funktionen. Og det er hvad vi kommer til nu i Pointer til struktur.
Det er naturligvis også muligt at overfører 2D arrays. f.eks:
unsigned char Ckybuf[200][320];
kaldet til f.eks my_fill vil se ens ud, hvorimod selve funktionen(og proto-typen) vil se sådan ud:
void my_fill(unsigned char buf[200][300], unsigned int antal, unsigned char tal){.
 <<code>>
}
Her bliver buf en pointer til er 2D array, og så kan det bruges som man plejer.

Pointer til struktur.

En struct er en blok af hukommelse, hvor structens navn er (lige som et array) en statisk pointer til denne blok, og member-navnene er et offset til det tilhørende data. Vi kan naturligvis lave en pointer til en sådan. Vi ser lige et eksempel, og hvordan det bruges:
typedef struct{
 int xpos, ypos;
 unsigned char col;
}Dot;

Dot prik;
Dot *D;                   /* D er en pointer til typen 'struct dot', men er uninitialized */

prik.xpos = 160;     /* alm struct behandling.*/
prik.ypos = 100;
prik.col = 255;

D = &prik;               /* Her sætter vi D til at pege på strukturen prik */

D->xpos = 80;         /* her ændre vi i strukturen prik, gennem pointeren D */
D->ypos = 50;         /* læg mærke til at når man skal bruge en "struct-pointer" bruger man -> i stedet for '.'. Det er en hurtig måde at se om det er en pointer eller ej... */
Det er ikke meget at sige til det, da det ligner alm. pointer operationer, en hel del. Her gælder det også, at hvis man laver et array af structs:
D = malloc(sizeof(Dot)*10); (se Dynamisk allokering) eller Dot prik[10];
og laver en pointer til dette:
Dot *E;
E = D; eller E = prik;
springer E til næste struct i hukommelsen, når man laver en E++; (se Adresse add sub).
Vi bruger prik fra oven over, og laver en funktion der tegner prikken... vi kan gøre det på 2 måder:
Draw_spot(D);          /* her
sender vi adressen i form af en pointer til strukturen. derfor ingen &. */
eller
Draw_spot(&prik);    /* her sender i adressen på
strukturen. derfor &. (husk på, at D allerede ER en pointer, og prik er selve
struct'en) */
Draw_spot(Dot *a){  /* vi bruger en 320*200, 256 col skærm. */
 Ckybuf[a->xpos+a->ypos*320]=a->col;
}

fordi a er en pointer til en struktur skal vi bruge -> .. altså a->xpos. Hvis a havde været strukturen selv, i underrutinen skulle vi bruge : a.xpos.
Ckybuf er i dette tilfælde vores virtuelle buffer.

Array's og pointere:

Array og pointere har mange ting tilfælles, med de er ikke helt ens.
Nogen kalder Array's statiske pointere, inc. mig selv :). F.eks.:
unsigned char Ckybuf[320*200];

Her er Ckybuf en pointer til Ckybuf[0] (og det skal man lige huske). Men man kan ikke bruge Ckybuf som en pointer, i den forstand, at man kan få den til at pege på noget andet, eller skrive Ckybuf++. Der imod hvis man har en pointer:
unsigned char *p;
p = Ckybuf;

P er en rigtig pointer variabel, og man kan bruge den som normalt.
Man kan lave en pointer som fra starten peger på et allokeret omrade:

int (*p)[10];

her peger p et sted i hukommelsen på 10 int's vi har allokeret. Men pas på, det er ikke det samme som at skrive:

int *p[10];

Fordi så får man et array af 10 pointere af typen int - kan give nogle skumle fejl.
Man bruger forøvrigt et pointer array på ganske normal vis:

p[1] = malloc(100); 
*(p[1]+50) = 10;

Her får vi den 2. pointer i array'et til at pege på 100 bytes. Herefter fylder vi et 5 tal ned på plads 50 i disse 100 bytes.
Den eneste forskel fra alm. pointer operationer er, at man vælger hvilken pointer i array'et man vil bruge, ved hjælp af []. (*(p+50) og *(p[1]+50)). 50 er her ganske simpelt et offset af (i dette tilfælde bytes) elementer, fra hvor pointeren peger. Det ligner 2D array's, og det kommer vi straks til.
Jeg vil nu kalde de forskellige typer (int, char, long, structs, etc.) for elementer, for at simpliciferer neden stående.
Hvis man skal lave noget hvor man virkelig skal passe på, hvor meget ram man allokerer, skal man tanke over folgende:

P = &Ckubuf[0]; er det samme som P = Ckybuf;

Hvis man har et array, er navnet (label) en 'statisk pointer' til det forste element i array'et. Hvis det er allokeret med en pointer, har man hele arrayet, plus en pointer, til at optage hukommelse.

som vi har set før, er :
Ckybuf[4]=5; det sammen som *(p+4)=5 ;.
(Dette er også forklaret lidt nærmere i Dynamisk allokering)
men som sagt, kan man ikke ændre adressen på array'et, det kan man på pointeren. Det er groft sagt, forskellen på Array's og pointere.
Men forskellen kommer nok forst rigtig frem, nar man snakker om flerer-dimentionelle array's.
For vi går videre, skal vi lige en tur omkring Typedef's. Jeg vil ikke komme nærmere ind på hvordan det virker, men man kan lave sin egen bruger-definerede type, som f.eks:
typedef int Ai_10[10];  /* Her laver vi vores egen
bruger-definerede type, som hedder Ai_10 (ligesom char, int etc.) */

Ai_10 S;
Vi har nu defineret S som et array af 10 ints. dvs. at:
Ai_10 S[8];
er lig 8 arrays af 10 int's stykket. Og som vi lærte i Adress_add_sub, så vil en pointer+1, hoppe typen's størrelse i hukommelsen. Med andre ord så er S[1] 10 int's længere fremme i hukommelsen end S[0]. Hvis vi lavede en pointer til dette:
Ai_10 *T;
T = S;
så ville T++ resulterer et spring i hukommelsen på 10 int's. Det bringer os til multi-dimentionelle arrays.
Nu kan det komme til at se lidt underligt ud :
Ckybuf[Y][X]; eller *(*(Ckybuf + Y) + X); eller *(Ckybuf+(X+Y*Array_Width);
Nu kommer det til at minde om noget vi så i Pointer til pointer. hvis vi tager og erstatter
*(Ckybuf +Y) med A, så står der:
*(A+X);
Nu ligner det en alm. pointer, med et offset (X = antal elementer inde i array'et). Hvis vi husker på adress_add_sub, og hvad A står for, vil Y+1 resulterer i vi er i array 2 (altså springer vi en array størrelse på 10 elementer i hukommelsen).
Så man kan sige, at i et 2D array, er den ene kolonne pointere til hvor arrays'ne ligger, og et array navn, er et label (en pointer) til en række af elementer. Derfor er det faktisk pointere til pointere. Den anden kolonne fortæller os hvor stort er array er.

Forvirret ? Prøv at brug det, så falder det hurtigt på plads.

Dynamisk allokering:


En god ting er, at hvis man har en pointer, som skal bruges til dynamisk allokering, så bør man initialiserer den
til en NULL pointer:
unsigned char *p=NULL;
NULL betyder - "brug ikke mig". C's funktioner retunerer NULL i pointeren hvis der er opstaet en fejl, f.eks.:
p = malloc(100);
/* Her allokerer vi 100 bytes, og p peger på hvor det er */ 
p indeholder nu adressen på, hvor vi har allokeret noget hukommelse. Men hvis nu, der ikke er 100 bytes ledig, til at vi kan allokere dem, vil p blive sat til NULL.
Det kan man teste sig ud af, og dermed undgå pinlige fejl.
if (p==NULL){
 printf("Program fejl: kunne ikke allokerer hukommelse. \\n");
 exit(1);
}
Igen Kan man naturligvis allokere alle typer:
p = malloc(sizeof(unsigned long)*100); /* sådan allokerer man med
forskellige størrelser/typer. */
I ovenstående eksempel, allokerer vi 400 bytes, fordi en unsigned long = 4 bytes. p er i dette tilfælde en unsigned long pointer, og vi har allokeret 100 af denne type. 
Ligeså med strukturer. Vi laver lige hurtig en struct:
struct dot{ 
 int xpos,ypos;
 unsigned char col;
};

p = malloc(sizeof(struct dot)*10);    /* Her allokerer vi hukommelse nok til 10 af ovenstaende struct's.*/
Man kan nu bruge p, lige som et Array.

p[4].xpos = 5;
Vi tager det 5. element i vores dynamisk allokerede array af structs og sætter xpos = 5;.
Vi kan naturligvis også lave en pointer af samme type og bruge denne:
struct dot *p,*D;
p = malloc(sizeof(struct dot)*10);
D = p;

D->xpos=5;
D++;
D->xpos=4;.
p[0].xpos=5;
p[1].xpos=4;
(D++)->xpos=5;
D->xpos=4;   
D->xpos= 5;
(D+1)->xpos=4;
Metode 1 Metode 2 Metode 3 Metode 4

Her tager vi første element og sætter xpos=5, hopper til næste element, og sætter xpos=4. Det kan med sikkerhed laves på flere måder. Ovenstående metoder giver præcis samme resultat.
En god hovedregel med dynamisk allokering er, HUSK AT FRIGIVE HUKOMMELSEN IGEN. Dette er speciel gældende i funktioner som allokerer hukommelse. Hvis denne funktion bliver kaldt mange gange uden at frigive sig allokerede hukommelse, vil det hobe sig op, og til sidst resulterer i et nedbrud af systemet.

free(p);
Sådan.
Det er ikke nødvendigt i main at frigive allokeret hukommelse, da det sker automatisk, Når programmet afsluttes, men alligevel synes jeg, det er en god ide at gøre. (bare for en sikkerheds skyld - ogsa fordi hvis ens hukommelse først bliver 'smadret', kan maskinen finde pa mange underlige ting.)

Pointere og strenge.

Når man arbejder med strenge, kommer man ikke uden om Array_og_pointere.
En streng i C, er et array, hvor hvert tegn (char/unsigned char) har en plads, og er termineret med en '\\0' (ogsa kaldet 0-terminering).
Denne terminering er ikke nødvendigvis det samme som NULL (med dog ofte bare 0x00).
Lad os se hvordan man kan definerer en streng:
unsigned char Streng[20] = {'H','e','l','l','o',' ','w','o','r','l','d','\\0'};
Dette er dog ikke særlig læsbart. Heldigvis kan man gøre på følgende måde, som er det samme som ovenstående:
unsigned char Streng[20] = "Hello world";
Læg mærke til vi ikke har sat nogen 0-terminering. Det er fordi, når man bruger " " sætter C selv en 0-terminering. Vi har nu afsat 20 pladser, men dette kan gøres liiidt nemmere:
unsigned char Streng[] = "Hello world";
Den tomme klamme, betyder at compileren selv reserverer nok hukommelse, til den pågældende tekst, plus en 0-terminering. Egentlig er der ikke noget nyt i dette, da det bare er et array. Man kan naturligvis dynamisk allokerer en streng, på nøjagtig samme måde som alt muligt andet data. F.eks.:
unsigned char *p;

p = malloc(12);
strcpy(p,"Hello World");

printf(" %s \\n",p);
Vi kan også lave vores egen streng-udskrivnings rutine:
void print_str(unsigned char *s){
 while(*s) putc(*(s++));
}
Jeg vil gerne lige vise et eksempel på brugen af ovenstående, så man kan se det i brug:
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

#define ScrollLength 80                  /*skærmens bredde */

unsigned char Text[]={"hejsa........Jeg er en simpel textmode scroll.....           "};
unsigned char Buffer[ScrollLength+1];

int main(void){
int x=0,Pos=0;
memset(Buffer,255,ScrollLength);
Buffer[ScrollLength]='\\0';               /* og 0-terminer den. */

while(!kbhit()){
  memcpy(Buffer,Buffer+1,ScrollLength-1);
  if(Text[Pos]=='\\0')Pos=0;
  Buffer[ScrollLength-1]=Text[Pos++];

  gotoxy(1,12);
  printf("%s\\n",Buffer);                   /* Her kunne vores egen print_str rutine blive kaldt */

  while ((inp(0x3DA) & 0x8)!=0);         // for Vertical retrace..
  while ((inp(0x3DA) & 0x8)==0);         // giver mere flydende scroll i dos-fullscreen.

}
return(0);
}
Dette skulle der heller ikke være mange overraskelser i. Et andet lille eksempel på strenge:

unsigned char MyStr[4][16]={"DJGPP/Gnu-C    ",
                            "www.delorie.com",
                            "Gratis compiler",
                            "Og Den er go'  "};

int main(void){
unsigned char c,index=0;

do{
  if(kbhit()){                    /* Test om en tast er trykket */
   c=getch();                     /* Hvis ja, find ud af hvilken*/

   if (c == '2') index--;
   else if (c == '8') index++;
   index&=3;                      /* Sørg for index er mellem 0 og 3 altid. */

   gotoxy(1,1);                   /* Og sa noget skarm flimmer */
   printf("%s\\n",MyStr[index]);

  }
}while(c!=27);                   /* Afslut hvis der trykkes ESC*/
return(0);
}
Læg mærke til hvordan strengen fra 2D-array'et bliver skrevet ud. Printf's %s vil have en pointer til strengen, og det er præcis hvad den får på denne måde.
Mht. mere specifikke streng-operationer, vil jeg ikke gå i gang med her, da det er pointere der er i fokus.

Pointer til pointer:

En pointer til en pointer, er præcis som navnet antyder. I stedet for at have en pointer til en alm. variable, har man en til en anden pointer.
Som vi har været igennem, ved vi at en pointer indeholder adressen på et sted i hukommelsen, som vi enten vil læse fra, eller skrive til.
Med en pointer til en pointer kan vi skrive en adresse i en pointer via en anden pointer. For overskuelighedens skyld skal vi lige se
hvordan man definerer en pointer til en pointer, og bruger den. Som eksempel laver vi en funktion til at allokerer n-antal bytes, hvor
vi vil bruge vores pointer fra main:
int main(void){
 unsigned char *p;

 my_allocate(&p,100);
 if(p==NULL) return(1);

 free(p);
 return(0);
}

void my_allocate(unsigned char **p2p, unsinged int n){
 *(p2p) = malloc(n);
}
Det er en ret overfladisk funktion, men et udemærket eksempel. (man kunne forestille sig at lave det til en generic hægtet-liste håndterings enhed)

Fra Pointer som parametre ved vi, at man normal kopierer en adresse fra en, over i en anden pointer. (Den fra hvor funktionen bliver kaldt, til funktionens egen pointer) Derfor duer denne metode ikke i dette tilfælde da pointeren indeholder samme adresse, når vi returnerer fra my_allocate, som den gjorde før vi kaldte my_alloc. Det betyder nu at, det hukommelse vi lige har allokeret er væk, da vi ikke har en pointer til det mere, men det optager stadig plads. Snart løber vi ind i alverdens fejl.
Som man kan se ud af ovenstående kode, ligger man adressen fra pointeren fra main over i en pointer til en pointer. Vi skal huske på at det er adresser vi arbejder med. Ligesom i Pointer og pointer imellem overfører vi adresser (på variabler fra main) til funktionen swap, som manipulerer med disse via pointere. Ligeså skal vi manipulerer med en pointers adresse, derfor en pointer til en pointer.
I bund og grund, intet nyt, ud over ** i stedet for *. men dette er fordi typen pointeren peger på, er en pointer. Prøv at brug det lidt, pludselig bliver det hele mere klart.

Pointer til funktion.

Alle funktioner ligger på en adresse. Lige så snart man har compilet ens source, ligger ens rutiner med et offset fra, hvor programmet ligger i hukommelsen.
Derfor er det også muligt at ligge en sådan adresse over i en pointer, som man kalder Funktions pointer.
En funktions pointer er en pointer som peger på en funktion, og ved brug af denne, kan man kalde og eksekvere en funktion.
Der er nogle regler for en funktions pointer, hvis man vil have parametre med. DVS. hvis en funktions pointer skal kunne pege på flere forskellige funktioner, skal disse have lignende prototype. Jeg vil gerne lige vise et lille program som bruger funktions pointere, og pointere til pointere. Det er kun et demonstration program, altså ikke meget nyttig i det virkelige liv, men det viser lidt om hvordan det virker. Men først skal vi se på hvordan man definerer en sådan:
void (*Fprt);
Hvis vi bruger swap igen:
void (*Fprt);
Fprt = swap;
printf(" %p \\n", Fprt);
Så får vi adressen på funktionen, skrevet ud.
void (*Fprt); er en void pointer. en Void pointer kan man sætte til at peget pa stort set alt. Se Pointer og pointer imellem.
Man kan også bruge parametre:
void (*Fprt)(int *a, int *b);
Ovenstående funktions pointer, er defineret til at skulle pege på en funktion, som intet returnerer (void), og skal have 2 int pointere som input.  (lige som swap i Pointer som parametre, og den kan vi jo meget vel bruge. For at forklarer ovenstående lidt nærmere:
Fprt = swap;
Nu har vi en pointer der peger på swap, og vi kan bruge denne, lige som vi bruger swap, altså:.
void swap(int a*, int *b);     /* Prototype. */
int main(void){
void (*Fprt)(int *a, int *b);
int tal1=5, tal2=10;

Fprt = swap;

printf(" a= %d, b= %d. \\n", tal1, tal2);
Fprt(&tal1,&tal2);
printf(" a= %d, b= %d. \\n", tal1, tal2):
return(0);
}
Man ser hurtigt, at funktions pointere er en smule anderledes at arbejde med, frem for alm. pointere. Kan man finde et formål til dem, er de stærke, men de kan være svære at styre. Jeg vil gerne vise et eksempel på, hvordan en funktions pointer kunne bruges.
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

#define NEWNODE 0                    /* Læg mærke til disse defines. De bliver brugt til at styre */
#define CLEARALL 1                   /* funktions pointerne.*/
#define WRITEALL 2

struct test{
 unsigned int number;
 struct test *next;
};

                                    /* Her har vi prototypes, læg mærke til det kun er navnet til forskel */
void LL_newnode (struct test **S, struct test **C);
void LL_writeall(struct test **S, struct test **C);
void LL_freeall (struct test **S, struct test **C);
                                    /* Her laver vi er array af funktions pointere. Næsten defineret ligesom forrige eksempel. */
void (*LinkedList[3])(struct test **S, struct test **C);  

int main(void){
struct test *start=NULL, *cur=NULL; /* Intet nyt her. */

LinkedList[0] = LL_newnode;         /* Her sætter vi funktions pointerne til at pege på hver */
LinkedList[1] = LL_freeall;             /* deres funktion */
LinkedList[2] = LL_writeall;


clrscr();

LinkedList[NEWNODE](&start,&cur);   /* Her kalder vi en funktion (NEWNODE) med parametre */
cur->number=3;                                       /* Vi fylder nogle test tal i listen, så vi kan se at det */
LinkedList[NEWNODE](&start,&cur);   /* virker senere..*/
cur->number=63;
LinkedList[NEWNODE](&start,&cur);
cur->number=7;
LinkedList[NEWNODE](&start,&cur);
cur->number=13;
LinkedList[NEWNODE](&start,&cur);
cur->number=80;

LinkedList[WRITEALL](&start,&cur);  /* Her kalder vi en anden funktion, i samme array, som skriver alle vores tal ud */

LinkedList[CLEARALL](&start,&cur);  /* Igen en anden funktion, som de-allokerer alle vores 'records'*/
LinkedList[WRITEALL](&start,&cur);  /* Og her laver vi lige nogle test*/

LinkedList[NEWNODE](&start,&cur);
cur->number=12;
LinkedList[WRITEALL](&start,&cur);  /* Læg mærke til pointerne start og cur. når de bliver overført sender vi adresserne på dem, fordi de skal kunne manipuleres, og derfor bliver overført til en Pointer til pointer */

start == NULL ? printf("List is empty.\\n") : printf("List is not empty\\n");

return(0);
}

void LL_newnode(struct test **S, struct test **C){ /* Nedenstaende rutiner behøves ikke meget  */
if(!*(C)){                                                                 /* forklaring, andet end jeg synes man skal ligge mærke til, hvordan pointerne  */
  (*S) = malloc(sizeof(struct test));                            /* bliver brugt.                             */
  *(C) = *(S);
  (*S)->next = NULL;
  }
else{
  (*C)->next = malloc(sizeof(struct test));
  *(C) = (*C)->next;
  (*C)->next = NULL;
}
}

void LL_writeall(struct test **S, struct test **C){
struct test *temp=*(S);
if(*(C)){
  do{
   printf("value = %d\\n",temp->number);
   temp = temp->next;
  }while(temp != NULL);
}
else printf("No values present.\\n");
}


void LL_freeall(struct test **S, struct test **C){
struct test *temp=*(S);
if(!temp){
  do{
   *(C)=temp;
   free(temp);
   temp = (*C)->next;
  }while(temp != NULL);
}
free(*(S));
*(S) = NULL;
*(C) = *(S);
}
Ovenstaende er det meste af, hvordan en hægtet-liste håndterings enhed, KUNNE se ud. Jeg springer måske lidt hurtigt frem, men jeg synes man skal prøve at køre ovenstående kode ind i en C-compiler, og lege lidt med det. Hvis man har forstået alt ned til Pointer til funktion, bør der ikke være de store vanskeligheder.
Som sagt, er en funktions pointer i bund og grund, en pointer der peger på en funktion. Og som vist, kan man se hvordan man gør med parametre, ikke meget anderledes end det der star i Pointer som parametre.


Tips:
- Sørg ALTID for at initialisere dine pointere for du bruger dem.
- Vær omhyggelig med IKKE at skrive, i noget hukommelse, du ikke må (uden for allokeret hukommelse).
- Undersøg ved dynamisk allokering, om man har fået en NULL-pointer (en pointer som ikke peger på noget)
- Brug så vidt mulig samme typer.

Hvis der er fejl, problemer, spørgsmål, så sig endelig til... Ligeså hvis der er noget som burde være med, noget som der ikke står nok om, gode forslag,
eller lign, vil jeg gerne vide det.



Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (3)

User
Bruger #3237 @ 04.02.03 11:17
Kanon-fjong!
User
Bruger #1445 @ 06.02.03 01:14
Fint, men der mangler lidt om klasser og poniter. Herved f.eks. virtuel funktioner mm.
Men man kan ikke skrive alt.... Godt arbejde

P.s Bruger selv ponter til hardware programmering (micro processer programmering) her er det smart.
User
Bruger #3116 @ 25.07.03 22:01
Nøøj!
Du skal være logget ind for at skrive en kommentar.
t