|
Pointers
Een beschrijving van de pointer
Eenvoudig gezegd is een pointer een geheugen adres. In plaats van een
variabele is het een pointer naar een variabele, die ergens in het
geheugen van de computer is opgeslagen. Bestudeer het programma
POINTER1.C voor een programma met gebruik van pointers.
main ()
{
int index, *pt1, *pt2;
index = 23; /* willekeurige waarde */
pt1 = &index; /* het adres van index */
pt2 = pt1;
printf ("De waarden zijn: %d, %d en %d\n", index, *pt1, *pt2);
*pt1 = 77; /* verander index */
printf ("De waarden zijn: %d, %d en %d\n", index, *pt1, *pt2);
}
Het programma begint met de declaratie van index en twee
andere variabelen, waarvan de naam begint met een sterretje. In het
eerste statement wordt aan index de waarde 77 toegekend. Dat
is geen verrassing, dat deden we vaker.
Het tweede statement zegt dat aan variabele pt1 een vreemd
uitziende waarde moet worden toegekend, namelijk de variabele
index met een ampersand ervoor. In dit voorbeeld stellen
pt1 en pt2 pointers voor, de variabele
index is slechts een integer. Nu hebben we wel een probleem.
We hebben nog niet geleerd hoe we met pointers in een C programma om
moeten gaan. Daartoe moeten we eerst wat definities geven.
De volgende drie regels zijn hierbij belangrijk en moeten goed tot
je doordringen.
- De naam van een variabele met een ampersand ervoor definieert het
adres van die variabele en is daardoor een pointer naar die variabele.
Om die reden kun je regel 6 van het programma lezen als:
pt1 krijgt de waarde van het adres van index.
- De naam van een variabele met een sterretje ervoor in de
declaratie, definieert die variabele als pointer van het gedeclareerde
type. int *pt1 declareert dus pt1 als zijnde een
pointer naar een integer.
- Een pointer met een sterretje ervoor in een statement, refereert
aan de waarde van de variabele waarnaar de pointer wijst. Regel 9 van
het programma kan worden gelezen als: de variabele waar
pt1 naar wijst krijgt de waarde 23. Omdat pt1
naar index wijst, wordt aan variabele index dus
de waarde 23 toegekend.
pt1 en pt2 zijn pointers en als zodanig bevatten
zij dus altijd een geheugenadres. In het voorbeeld krijgt
pt1 het adres van index en vervolgens wordt aan
pt2 de waarde van pt1 toegekend. Het resultaat
hiervan is dat ook pt2 naar de variabele index
wijst. Wees er echter bewust van dat een niet geinitialiseerde
pointer toch een waarde kan bevatten. Deze waarde wijst dan naar een
onbekende plaats in het geheugen en dat kan tot vervelende verrassingen
leiden.
Naar een variabele mogen meerdere pointers wijzen. In het voorbeeld
programma zijn de uitdrukkingen index, *pt1 en
*pt2 volkomen identiek. Dit wordt getoond in het
printf statement.
Op regel 9 wordt de waarde van index gewijzigd met behulp
van de pointer. Doordat er een sterretje voor de naam van de pointer
staat, wordt gerefereerd aan de inhoud van het geheugen adres waar de
pointer naar wijst. Het statement op regel 9 kent dus aan variabele
index de waarde 23 toe.
In eerste instantie denk je misschien dat er drie variabelen zijn, maar
er is er maar een. Er is een integer variabele en twee pointers die er
naar wijzen. Dit wordt getoond met de toekenning op regel 9 en het
laatste printf statement.
Pointer worden veel toegepast in C. Het verdient daarom aanbeveling
tijd uit te trekken om dit onderwerp goed te bestuderen. Compileer en
test het programma.
Declaratie van pointers
Kijk eens naar de volgende declaratie:
int index, *pt1, *pt2;
De variabele index wordt hier op de reeds bekende wijze
gedeclareerd als zijnde een variabele van het type integer. Daarachter
staan twee extra declaraties. De tweede declaratie kan worden gelezen
als: de geheugenlocatie waarnaar pt1 wijst behoort bij
een variabele van het type integer. Om die reden zeggen we
pt1 is een pointer naar int. Evenzo zeggen we
pt2 is een pointer naar int.
Een pointer moet dus altijd worden gedeclareerd als wijzend naar een
variabele van bepaald type. Wordt de pointer ooit gebruikt als wijzend
naar een variabele van een ander type, dan zal dat op uitvoeringstijd
resulteren in een Type incompatibility error. Als het type
van te voren niet bekend is, kan een pointer worden gedeclareerd
wijzend naar het type void, een loos type of zo je wilt een
universeel type. Dat gaat als volgt:
void *ptr;
De string als pointer
We hebben het terrein van de pointers al aardig ontgonnen, maar er is
meer. We weten nog niet alles, dus lees gauw verder over dit
belangrijke onderdeel van de programmeertaal C. Bestudeer het programma
POINTER2.C om de cursus te vervolgen.
main ()
{
char een, twee, *ernaar, string[40];
int *pt, lijst[100], index;
strcpy (string, "Dit is een string van karakters\n");
een = string[0];
twee = *string; /* een en twee zijn identiek */
printf ("1: een is %c en twee is %c\n", een, twee);
een = string[11];
twee = *(string+11); /* een en twee zijn identiek */
printf ("2: een is %c en twee is %c\n", een, twee);
ernaar = string + 11;
printf ("3: element 12 is %c\n", string[11]);
printf ("4: element 12 via pointer is %c\n", *ernaar);
for (index = 0; index < 100; index++)
lijst[index] = index + 100;
pt = lijst + 23;
printf ("5: element 24 is %d\n", lijst[23]);
printf ("6: element 24 via pointer is %d\n", *pt);
}
In dit programma worden verschillende variabele gedeclareerd, waaronder
twee pointers. De eerste pointer, ernaar genaamd, is een
pointer naar een character variabele en de tweede pointer,
pt genaamd, is een pointer naar een integer variabele.
Verder zijn er nog twee tabellen gedeclareerd, string en
lijst. Deze zullen worden gebruikt om de relatie te tonen
die er bestaat tussen pointers en de naam van een tabel.
In de programmeertaal C is een string variabele eigenlijk gelijk aan
een pointer naar de eerste letter van een string. Dit behoeft enige
uitleg. Kijk daarom naar het voorbeeld programma. Allereerst wordt de
variabele string gevuld met tekst, zodat we een en ander
kunnen uitproberen. De eerste letter van de string wordt toegekend aan
de variabele een wat een character variabele is. Hetzelfde
doen we voor variabele twee, echter nu via de pointer.
string wijst naar de eerste letter en *string
refereert aan de inhoud. Het gevolg is dat beide character variabelen
de letter D zullen bevatten.
In het algemeen zal string zich als pointer gedragen. Er is
echter een beperking die een echte pointer niet kent. De
waarde kan niet worden gewijzigd, zoals bij een variabele, doch is
constant. De compiler zal deze pointer zijn initiële waarde geven en
gedurende zijn levensduur zal hij naar de eerste letter van de string
wijzen.
Op regel 12 wordt aan de variabele een de twaalfde letter
van de tekst toegekend (de subscript begint immers bij 0) en krijgt
variabele twee dezelfde waarde. Het is toegestaan in C een
pointer te indexeren, zoals hier gebeurt met de waarde 11, om zodoende
een letter verderop in te string te verkrijgen. Beide variabelen
bevatten nu de letter s.
Pointer berekening
De C compiler zal, wanneer we pointers gaan indexeren, automatisch de
juiste aanpassingen maken afhankelijk van het type variabele waar de
pointer naar wijst. Zo zal bij character variabelen de pointer uit
het voorbeeld eenvoudigweg met 11 opgehoogd worden, omdat character
variabelen 8 bits ofwel 1 byte breed zijn. In geval we met een pointer
naar int werken, wordt de index verdubbeld, omdat een
integer variabele 16 bits ofwel 2 bytes breed is (op sommige computers
zelfs 4 bytes!). Later, als we de structuren behandelen, zullen we
ontdekken dat een variabele veel, heel veel bytes in het geheugen kan
beslaan. Ook bij dit soort variabelen zal de C compiler het indexeren
correct uitvoeren.
Omdat de variabele ernaar een pointer naar char
is, kan er het adres van de twaalfde letter aan worden toegekend
(string wijst naar de eerste letter; met 11 erbij wijst
ernaar dan naar de twaalfde). In principe kan de pointer
elke waarde krijgen. Het is de programmeur die ervoor moet
zorgen dat de pointer binnen de grenzen van de tabel wijst. Door altijd
op het type van de pointer te letten, zal indexering door de compiler
op de juiste wijze geschieden.
Niet alle vormen van berekening zijn toegestaan op pointers. Het moet
zinvol zijn, immers een pointer is willekeurig adres in het computer
geheugen. Het is bijvoorbeeld zinvol een constante bij een pointer op
te tellen, waardoor de pointer een beredeneerd aantal plaatsen verderop
in het geheugen wijst. Zo lijkt ook aftrekken van waarden zinvol, om de
pointer een beredeneerd aantal plaatsen terug te laten wijzen. Twee
pointers bij elkaar optellen is weinig zinvol, omdat het optellen van
twee adressen irrelevant is. Zo is ook het vermenigvuldigen van
pointers niet zinvol. Denk er maar eens over na wat je dan eigenlijk
aan het doen bent.
We gaan nog even terug naar het voorbeeld programma. De elementen van de
tabel met de naam lijst krijgen achtereenvolgens de waarden
100 tot en met 199, als gegevens om te testen. Daarna gaat pointer
pt wijzen naar het 24ste element. Dit element wordt dan op
twee verschillende manieren afgedrukt (het getal 123), om aan te tonen
dat het systeem werkelijk zal indexeren voor een integer variabele.
Probeer dit programma geheel te begrijpen, voordat je verder gaat met
het volgende hoofdstuk. Verander eens wat waarden. Compileer en test nu
dit programma.
Parameter overdracht met behulp van pointers
In de vorige les hebben we gezien dat het mogelijk is om gegevens terug
te geven vanuit een functie aan het aanroepend programma met behulp van
tabellen. Een andere manier is, je raadt het al, met behulp van
pointers. Bestudeer het programma TWEEKANT.C om te zien hoe we gegevens
twee kanten op kunnen sturen, naar een funktie en vanuit een funktie.
main ()
{
int pindas = 100;
int appels = 101;
printf ("main: De startwaarden zijn %d en %d\n", pindas, appels);
doewat (pindas, &appels);
printf ("main: De eindwaarden zijn %d en %d\n", pindas, appels);
}
doewat (noten, fruit)
int noten; /* noten is een integer */
int *fruit; /* fruit is een pointer naar een integer */
{
printf ("doewat: De startwaarden zijn %d en %d\n", noten, *fruit);
noten = 135;
*fruit = 975;
printf ("doewat: De eindwaarden zijn %d en %d\n", noten, *fruit);
}
In het hoofdprogramma worden twee integer variabelen gedeclareerd,
pindas en appels. Geen van beide is dus een
pointer. Aan beide variabelen wordt een waarde toegekend en we drukken
ze af. Dan wordt de funktie doewat() aangeroepen met twee
parameters. De eerste parameter is de variabele pindas zelf,
de tweede parameter is het adres van de variabele appels.
Hier ontstaat een klein probleem. De twee parameters zijn niet eender.
We moeten de funktie vertellen dat de eerste een integer is en de
tweede een pointer naar een integer. Dit is niet zo moeilijk. Binnen de
funktie doewat() wordt gewerkt met de variabelen
noten en fruit, waarbij de eerste is gedeclareerd
als integer en de tweede als pointer naar een integer. De aanroep in
het hoofdprogramma is daarmee in overeenstemming met de functie
definitie en de interface zal dus correct werken.
De funktie drukt de waarden van beide variabelen af, wijzigt ze beiden
met een fantasie waarde en drukt ze vervolgens nog eens af. Tot zover
zal dit je duidelijk zijn. De verrassing komt als de funktie terugkeert
en het hoofdprogramma de waarden nogmaals afdrukt. Je zult ontdekken
dat de variabele pindas weer zijn oorspronkelijke waarde
heeft, terwijl de variabele appels de waarde uit de funktie
heeft gekregen. Dit komt doordat de funktie voor pindas een
kopie heeft gemaakt. Voor de variabele appels werd een kopie
van de pointer gemaakt. Deze kopie wijst naar de originele variabele en
daardoor wordt de originele variabele veranderd. De laatste
printf() toont daarvan het bewijs.
Door het gebruik van pointers bij de aanroep van een funktie, hebben we
binnen de funktie toegang tot de gegevens erbuiten en kunnen we deze
gegeven veranderen. Na terugkeer blijven deze veranderingen dan in
tact. Wanneer je binnen de funktie de waarde van de pointer aanpast,
zal bij terugkeer de waarde van de pointer in het aanroepende programma
niet gewijzigd zijn. De pointer was immers een kopie. Je kunt hier
gebruik van maken als je wilt.
In het voorbeeld was geen sprake van een directe pointer, maar van een
adres. In veel programma's zul je echter met echte pointers werken,
zoals programma's waarin gebruik wordt gemaakt van stadaard Invoer-
en Uitvoer funkties. Die worden behandeld in de volgende lessen.
Compileer en test het programma.
In het begin zul je wat huiverig zijn in het gebruik van pointers. Je
zult echter ontdekken dat bij toenemende ervaring je meer een meer het
nut en het gemak van pointers gaat waarderen, waardoor je ze ook meer
en meer zult toepassen. Zeker op het terrein van In- en Uitvoer naar bestanden
en dynamisch geheugen aanvragen, zijn pointers onmisbaar.
|