12 Meer over pointers 12.1 Arrays van pointers Omdat pointers zelf variabelen zijn, is het mogelijk arrays van pointers te maken. arrayelement is een pointer naar een of ander object. In de declaratie
Elk
int *ptr[4]; is de variabele ptr een array van vier elementen die pointers naar een integer zijn. Als x en t integer variabelen zijn, geeft de toekenning ptr[3]= &x; het adres van de variabele x als waarde aan het laatste element van de array. Als we de declaraties int v4[4], v7[7], v2[2], v3[3]; hebben, kunnen we het nulde element van ptr een waarde geven die het eerste element in de integer array v4 adresseert, namelijk met de toekenning ptr[0]= &v4 [0]; of, gebruikelijker, met: ptr[0l = v4; Als we het basisadres van de arrays v4, v7, v2 en v3 aan opeenvolgende elementen van de array ptr toekennen, hebben we in feite een tweedimensionale array bestaande uit eendimensionale arrays van ongelijke lengte. De array heeft niet meer de gebruikelijke rechthoekige vorm. We kunnen ons deze tweedimensionale array voorstellen zoals in volgende figuur is afgebeeld.
1
De noodzakelijke toekenningen zijn: ptr[0l = v4; ptr[1] = v7; ptr[2]= v2; ptr[4]= v3; De array v7 kan worden afgedrukt met de functieaanroep print_rij(ptr[1], 7); als de functie print_rij zo geprogrammeerd is: void print_rij(int *pt, int n) { int k; for (k = 0; k < n; k++) printf("%d\n", *pt++);
/* lusteller
*/
/* voor elk element /* print en schuif op
*/ */
} Meestal worden arrays van pointers voor strings gebruikt. We demonstreren dit gebruik met een programma dat een reeks tekstregels op alfabetische volgorde zet. In volgend voorbeeld zullen we een methode gebruiken om regels tekst te sorteren. Daarvoor hebben we een geschikte datarepresentatie nodig die efficiënt en handig met regels tekst van verschillende lengte kan werken. Een mogelijke oplossing is naar de regels rechtstreeks met pointers te verwijzen. Omdat er waarschijnlijk meer dan één regel tekst zal zijn, is er een array van pointers nodig. In een grote array regelbuffer staan de regels die gesorteerd moeten worden, en wel gewoon achter elkaar. In een tweede array regelpointer staan pointers naar de plaatsen in regelbuffer waar de bijbehorende regel begint. Dus regelpointer [k] is de positie in regelbuffer waar de k-de regel begint, voor k >= 0. Volgende figuur illustreert deze opzet.
2
Als in de algoritme een tweetal regels moet worden verwisseld, worden alleen de pointers verwisseld, niet de tekstregels zelf. Dat voorkomt dat we grote hoeveelheden data moeten verhuizen. Ook het geheugenbeheer wordt vereenvoudigd, omdat we regels van verschillende lengte moeten verwisselen. Het sorteerprogramma leest regels tekst in de datastructuur, sorteert ze (door de pointers te veranderen) en drukt ze vervolgens af. Omdat de array regelpointer een of andere vaste grootte zal hebben, is er een maximum gesteld aan het aantal regels dat kan worden opgeslagen. Er bestaat ook een maximum voor de totale ruimte die in regelbuffer aanwezig is. Als die ruimte bij het lezen van de invoer wordt overschreden, is dit een fout. De functie die verantwoordelijk is voor de invoer, levert een statuscode die aangeeft of de invoer geslaagd is of dat de limieten zijn overschreden. De invoer wordt afgesloten met de afsluiten "ZZZ". Het sorteringsalgoritme dat we nu gaan beschrijven noemen we pc_sort. Het is een krachtig en eenvoudig algoritme dat een rij kan sorteren met het absolute minimum aan verplaatsingen van de data. Dit is zeer belangrijk wanneer we bijvoorbeeld ingewikkelde structures (met veel velden) moeten sorteren op een of ander veld.
0
aantal - 1
1
klaar vmin
loper
We werken met drie variabelen: • klaar: is een variabele die aangeeft tot waar de rij al gesorteerd is. Deze variabele loopt van plaats 0 tot plaats aantal-1 (aantal-1 niet inbegrepen, want voor deze klaar zijn er geen elementen meer te vergelijken) • vmin: nummer van het voorlopige minimum (kleinste element) tussen klaar en loper. 3
• loper: loopt van klaar tot en met aantal-1 op zoek naar een element in de rij die kleiner is dan het element waarvan het nummer onthouden wordt in vmin. Het is duidelijk dat op het einde van de procedure klaar helemaal achteraan zit en de rij dus gesorteerd is. Een aantal interessante functies zijn:
Syntax #include <stdio.h> char *gets(char *s); Description Gets a string from stdin. gets collects a string of characters terminated by a new line from the standard input stream stdin and puts it into s. The new line is replaced by a null character (\0) in s. gets allows input strings to contain certain whitespace characters (spaces, tabs). gets returns when it encounters a new line; everything up to the new line is copied into s. The gets function is not length-terminated. If the input string is sufficiently large, data can be overwritten and corrupted. Return Value On success, gets returns the string argument s. On end-of-file or error, it returns NULL
Syntax #include <string.h> size_t strlen(const char *s); Description Calculates the length of a string. strlen calculates the length of s. Return Value strlen returns the number of characters in s, not counting the null-terminating character.
4
Syntax #include <string.h> char *strcpy(char *dest, const char *src); Description Copies one string into another. Copies string src to dest, stopping after the terminating null character has been moved. Return Value strcpy returns dest.
Syntax #include <string.h> int strcmp(const char *s1, const char *s2); Description Compares one string to another. strcmp performs an unsigned comparison of s1 to s2, starting with the first character in each string and continuing with subsequent characters until the corresponding characters differ or until the end of the strings is reached. Return Value If s1 is... less than s2 the same as s2 greater than s2
strcmp returns a value that is... <0 == 0 >0
Syntax #include <string.h> char *strcat(char *dest, const char *src); Description Appends one string to another. strcat appends a copy of src to the end of dest. The length of the resulting string is strlen(dest) + strlen(src). Return Value strcat returns a pointer to the concatenated strings.
5
/* ** Lees een reeks tekstregels en voer ze in alfabetische volgorde uit. De regels worden eerst ** opgeslagen en dan met de algoritme 'pc_sort' gesorteerd. De invoer wordt afgesloten met ** de regel "ZZZ". */ #include <stdio.h> #include <string.h> #define MAXCHARS #define MAXREGELS #define MAXTEKST
10000 200 128
#define AFSLUITER
"ZZZ"
enum status { TEVEELREGELS, TEVEELCHARS, GESLAAGD }; typedef enum status Status; Status lees_regels (char *[ ], char [ ], int *); void pc_sort (char *[ ], int); void print_regels (char *[ ], int); int main() { char regelbuffer[MAXCHARS]; /* regels tekst */ char *regelpointer[MAXREGELS]; /* rij pointers naar beginplaatsen van de regels */ int aantalregels; /* aantal invoerregels */ Status afloopinlees; afloopinlees=lees_regels(regelpointer,regelbuffer,&aantalregels); switch (afloopinlees) { case GESLAAGD : pc_sort (regelpointer,aantalregels); print_regels (regelpointer,aantalregels); break; case TEVEELREGELS : printf("Te veel regels!\n"); break; case TEVEELCHARS : printf("Te veel tekens!\n"); break; default: break; } } 6
Status lees_regels(char *regelpointer[ ], char regelbuffer[ ], int *aantalregels) { char *buffer, *eindebuffer; /* pointers naar begin & einde van regelbuffer */ char regel[MAXTEKST]; /* Tijdelijke buffer voor inhoud van 1 regel */ buffer=regelbuffer; /* zet beginpointer */ eindebuffer=regelbuffer+MAXCHARS; /* zet eindebuffer: eerste niet-beschikbare plaats */ *aantalregels=0; while (gets(regel), strcmp(regel,AFSLUITER) != 0) /* gebruik komma operator: eerst wordt gets(regel) uitgevoerd, daarna de test strcmp(regel,AFSLUITER) != 0 */ if (*aantalregels==MAXREGELS) return (TEVEELREGELS); /* te veel regels */ else if (buffer+strlen(regel)+1>=eindebuffer) return (TEVEELCHARS); /* geen plaats meer om de tijdelijke regel te copiëren naar de regelbuffer */ else { regelpointer[(*aantalregels)++]=buffer; /* vul pointer nieuwe regel in en incrementeer aantalregels achteraf */ strcpy (buffer,regel); /* copiëer regel naar de plaats waar buffer naar wijst */ buffer+=strlen(regel)+1; /* zet buffer pointer klaar voor nieuwe regel let op: +1 omwille van '\0' */ } return (GESLAAGD); }
7
void pc_sort(char *regelpointer[ ], int aantalregels) { int klaar, vmin, loper; char *hulp; for (klaar=0; klaar < aantalregels-1; klaar++) { vmin=klaar; for (loper=klaar+1; loper
12.2 Pointers naar pointers Een formeel argument van een functie gedeclareerd als zijnde van type 'array van T', waarin T een of ander type is, wordt behandeld alsof het van type 'pointer naar T' is. Pointers en arrays zijn equivalent en deze verandering blijft onzichtbaar voor de programmeur en wordt automatisch door de C-compiler aangebracht. Neem bijvoorbeeld deze functie: int somarray(int a[ ], int n) { int som=0, k; /* lopend totaal en teller */ for (k = 0; k < n; k++) som += a[k]; return (som); } Het argument a had de programmeur ook kunnen declareren als int *a Dit is in feite de vorm die de C-compiler eraan geeft. 8
Als een argument van een functie wordt gedeclareerd als zijnde van type 'array van T' en T van type 'pointer naar S' is, voor een of ander type S, dan krijgt het argument het type 'pointer naar pointer naar S'. Neem bijvoorbeeld de functie print-regels in vorig programma, met de declaratie: void print_regels (char *regelpointer, int aantalregels) De C-compiler maakt hiervan: void print_regels (char **regelpointer, int aantalregels) Dus regelpointer heeft als waarde het basisadres van een reeks geheugenplaatsen (dat wil zeggen een array), die elk het adres zijn van het eerste karakter van een string. Volgende figuur geeft deze situatie schematisch weer. Het object regelpointer bezit de waarde die het adres is van de array van adressen. Het object *regelpointer is het eerste adres in die array en is daarom het adres van het eerste karakter van de eerste string. Het object **regelpointer is het eerste karakter van de eerste string.
Let goed op de prioriteit en de associativiteit van de indirectie-operator * en de andere operatoren. Let vooral op de operatoren * (indirectie), ++ (increment) en -- (decrement), die allemaal dezelfde prioriteit hebben en waarvan de associativiteit van rechts naar links loopt. Dus **regelpointer++ verwijst naar het eerste karakter van de eerste string (en verhoogt regelpointer met 1); en ** ++ regelpointer verwijst naar het eerste karakter van de tweede string (nadat regelpointer eerst met 1 is verhoogd). Op grond van het bovenstaande kunnen we de functie print_regels in termen van deze indirecties op meer niveaus herschrijven: void print_regels (char **regelpointer, int aantalregels) { int k; for (k = 0; k < aantalregels; k++) printf("%s\n", *regelpointer++); }
9
12.3 Argumenten op de commandoregel Normaal bevindt een C-programma zich in een omgeving die verschaft wordt door het besturingssysteem. In die omgeving is het gewoonlijk mogelijk in een commandoregel die een programma aan het werk zet, argumenten mee te geven. Neem bijvoorbeeld een programma som dat wordt aangeroepen met twee argumenten op de commandoregel. Deze argumenten zijn twee integers die bij elkaar moeten worden opgeteld en waarvan de som moet worden afgedrukt. Het programma kan zo worden geactiveerd: som 123 456 De uitvoer van het programma is dan natuurlijk 579. De functie main van een programma wordt vaak gedefinieerd met twee formele argumenten, die gewoonlijk argv en argc worden genoemd. Het argument argc is het aantal argumenten op de commandoregel. Omdat de naam van het programma daarbij wordt meegeteld, is argc altijd groter dan 0. In het voorbeeld van de aanroep van het programma som heeft argc de waarde 3. Het argument argv is een array van pointers naar char - pointers naar het eerste karakter in de strings die als argumenten fungeren. Omdat de naam van het programma ook als argument geldt, is argv[0] de naam van het programma. In het voorbeeld zijn argv[0], argv[1] en argv[2] respectievelijk som, 123 en 456. Het eerste echte argument is argv[1]. Het laatste argument is argv[argc-1].Het programma som kan bijvoorbeeld als volgt worden geschreven. /* **Bepaal de som van twee integers die als argumenten op de commandoregel worden **meegegeven. */ #include <stdio.h> #include <stdlib.h> main(int argc, char *argv[]) { if (argc != 3) /*controleer aantal argumenten */ printf("Gebruik de vorm: %s getal getal\n", argv [0]); else printf("%d\n", atoi(argv[1]) +atoi(argv[2]); }
Hieronder nog enkele interessante functies:
10
Syntax #include <stdlib.h> int atoi(const char *s); Description Converts a string to an integer. atoi converts a string pointed to by s to int; atoi recognizes (in the following order) An optional string of tabs and spaces An optional sign A string of digits The characters must match this generic format: [ws] [sn] [ddd] In this function, the first unrecognized character ends the conversion. There are no provisions for overflow in atoi (results are undefined). Return Value atoi returns the converted value of the input string. If the string cannot be converted to a number of the corresponding type (int), atoi returns 0.
Syntax #include <stdlib.h> char *itoa(int value, char *string, int radix); Description Converts an integer to a string. itoa converts value to a null-terminated string and stores the result in string. With itoa, value is an integer. radix specifies the base to be used in converting value; it must be between 2 and 36, inclusive. If value is negative and radix is 10, the first character of string is the minus sign (-). Note: The space allocated for string must be large enough to hold the returned string, including the terminating null character (\0). itoa can return up to 17 bytes. Return Value itoa returns a pointer to string.
Er bestaan nog varianten, zoals atof (array to double), ltoa, …
11
Hier volgt nog een eenvoudig voorbeeld van een programma met argumenten in de commandoregel. Het heet echo en het echoot zijn argumenten naar de standaarduitvoer, met een spatie tussen de strings. Dus als het commando is: echo Mijn eerste programma dan is de uitvoer: Mijn eerste programma In dit voorbeeld is argc gelijk aan 4 en zijn argv[0], argv[1], argv[1] en argv[3] achtereenvolgens echo, Mijn, eerste en programma. U ziet dat de aan een string voorafgaande witruimtekarakters niet in de argumenten zijn opgenomen. Ze dienen op de commandoregel alleen als scheiders. Het programma is als volgt. Echo alle argumenten uit de commandoregel, gescheiden door één spatie. #include <stdio.h> #define NEWLINE
'\n'
main(int argc, char *argv[ ]) { int k; for (k = l; k < argc; k++) /* voor elk argument */ printf ("%s%c", argv[k ], k != argc-1 ? ' ' : NEWLINE) ; }
12
Een ander voorbeeld is een C programma dat eenvoudige rekenkundige bewerkingen vanaf de parameterlijn kan oplossen: #include <stdio.h> #include <stdlib.h> #include <string.h> /* een operator in commentaar schrijven we als een . dus : eerste.tweede een . kan dus +, -, * of / zijn + in commentaar kan ook een - zijn * in commentaar kan ook een / zijn */ int getfunc(char s[ ], int lim); void cleanfunc(char s[ ]); main (int argc, char *argv[ ]) { double eerste,tweede,derde; int i; char uitdruk[256]="",oper1, oper2; if (argc<2) { printf("?"); getfunc(uitdruk,256); } else { for (i=1;i<argc;i++) strcat(uitdruk,argv[i]); cleanfunc(uitdruk); } i=0; eerste=atof(&uitdruk[i]); /* eerste operand gevonden */ oper1=' '; tweede=0.; i=1; /* we gaan op zoek naar de eerste operator */ while (!((uitdruk[i]=='+')||(uitdruk[i]=='-')||(uitdruk[i]=='*')|| (uitdruk[i]=='/'))&&(uitdruk[i]!='\0')&&(uitdruk[i]!='=')) i++; if ((uitdruk[i]=='\0')||(uitdruk[i]=='=')) goto eval; oper1=uitdruk[i++]; /* eerste operator gevonden */ tweede=atof(&uitdruk[i]); /* tweede operand gevonden */ getopt2: /* op zoek naar tweede operand */ i++; /* getal is minstens 1 char lang */ while (!((uitdruk[i]=='+')||(uitdruk[i]=='-')||(uitdruk[i]=='*')|| 13
(uitdruk[i]=='/'))&&(uitdruk[i]!='\0')&&(uitdruk[i]!='=')) i++; if (uitdruk[i]=='=') uitdruk[i]='\0'; if (uitdruk[i]!='\0') /* tweede operator gevonden */ { oper2=uitdruk[i++]; derde=atof(&uitdruk[i]); /* en derde operand */ if ((oper1=='*')||(oper1=='/')) /* het is een uitdrukking van de vorm: eerste*tweede.derde eerste*tweede mag men uitrekenen */ { switch(oper1) { case '*' : eerste = eerste * tweede;break; case '/' : eerste = eerste / tweede;break; default : eerste = 0;break; } oper1=oper2; tweede=derde; goto getopt2; /* terug op zoek naar operator en operand */ } if ((oper2=='*')||(oper2=='/')) /* het is een uitdrukking van de vorm: eerste.tweede*derde tweede*derde mag men uitrekenen */ { switch(oper2) { case '*' : tweede = tweede * derde;break; case '/' : tweede = tweede / derde;break; default : tweede = 0;break; } goto getopt2; /* terug op zoek naar operator en operand */ } if ((oper2=='+')||(oper2=='-')) /* het is een uitdrukking van de vorm: eerste+tweede+derde eerste+tweede mag men uitrekenen alle combinaties met een * worden hierboven uitgetest */ { switch(oper1) { case '+' : eerste = eerste + tweede;break; case '-' : eerste = eerste - tweede;break; default : eerste = 0;break; } oper1=oper2; tweede=derde; goto getopt2; /* terug op zoek naar operator en operand */ } } eval: if (uitdruk[i]=='=') uitdruk[i]='\0'; 14
if (oper1!=' ') switch(oper1) /* eerste.tweede uitrekenen */ { case '*' : eerste = eerste * tweede;break; case '+' : eerste = eerste + tweede;break; case '-' : eerste = eerste - tweede;break; case '/' : eerste = eerste / tweede;break; default : eerste = 0;break; } printf("%s=%lf\n",uitdruk,eerste); return 0; }
int getfunc(char s[], int lim) {int c,i; for (i=0;i
Veel C-programma's worden geschreven met deze opzet van de argumenten. Vaak zijn de argumenten namen van files die door het programma moeten worden verwerkt, zodat het programma zo kan worden gemaakt dat deze filenamen tijdens de uitvoering van het programma kunnen worden opgegeven. Soms worden deze argumenten aangevuld met opties die het effect van het programma nog verder kunnen veranderen. Opties worden vaak opgegeven als een of meer karakters voorafgegaan door een koppelteken (-). Stel bijvoorbeeld dat er invoer en uitvoer van en naar een bestand moet en dat het programma gestart wordt met: prog -ixxxx.yyy -ozzzz.vvv 15
Maar: de volgorde van de parameters mag geen invloed hebben: prog -ozzzz.vvv -ixxxx.yyy moet net hetzelfde doen! Dit kan men als volgt oplossen: #include <stdio.h> #include <string.h>
/* om stringfuncties te kennen: */ /* strcpy, strcmp, strcat, strlen, strstr, enz.. */
int main(int argc, char *argv[]) { char naaminfile[256]="standard.in", naamoutfile[256]="standard.out"; // Volgende variabelen dienen voor het onderzoeken van de parameterlijn: char optletter; char *optarg; int optnummer; // Met volgende bijhorende code: for (optnummer=1;optnummer<argc;optnummer++) // voor alle parameters, behalve de naam van het programma { optletter=argv[optnummer][1]; // de eerste character is steeds een - teken optarg= &argv[optnummer][2]; // onthoud het adres van het begin van de parameter // type oproep: programmanaam -inaam1 -onaam2 // andere codes: verwaarlozen. switch(optletter) { case 'i': strcpy(naaminfile,optarg); break; case 'o': strcpy(naamoutfile,optarg); break; default: break; /* rest : niet bekijken */ } } /* daarna kan het programma echt beginnen met zijn taak */ … … … }
16
12.4 Dynamische geheugenallocatie
#include <stdio.h> #include <stdlib.h> struct knoop { long int int struct knoop struct knoop };
waarde; freq; *links; *rechts;
struct knoop *newknoop (void); struct knoop *voegbij (struct knoop *, long int); struct knoop *voegtussen (struct knoop *, long int); void boomprint (struct knoop *); void rijprint (struct knoop *); main() {long int getal; struct knoop *proot; /*begin programma: rootpointer wijst nog naar niets.*/ proot=NULL; opnieuw: /*is een boomstructuur of een lineaire structuur gewenst?*/ printf ("Keuze:\n 1:boomstructuur\n 2:lineaire structuur\n"); scanf ("%ld",&getal); printf ("\nUw keuze: %1ld\n",getal); /*indien getal = 1 : gebruik boomstructuur = 2 : gebruik lineaire structuur = iets anders : stel vraag opnieuw */ if (!((getal == 1) || (getal == 2))) goto opnieuw;
17
if (getal == 1) /*men wenst te werken met een boomstructuur*/ { /*vraag een getal*/ printf("boomstructuur\n"); printf ("geef positief getal - negatief om te eindigen\n"); scanf ("%ld",&getal); /*zolang het getal niet negatief is, voeg het bij in de boomstructuur.*/ while (getal >= 0) { proot=voegbij(proot, getal); /*printf ("geef positief getal\n");*/ scanf ("%ld",&getal); } /*druk de boomstructuur af.*/ boomprint (proot); } else /*men wenst te werken met een lineaire structuur*/ { /*vraag een getal*/ printf("lineaire structuur\n"); printf ("geef positief getal - negatief om te eindigen\n"); scanf ("%ld",&getal); /*zolang het getal niet negatief is, voeg het bij in de lineaire structuur.*/ while (getal >= 0) { proot=voegtussen(proot, getal); /*printf ("geef positief getal\n");*/ scanf ("%ld",&getal); } /*druk de lineaire structuur af.*/ rijprint (proot); }; printf ("\n einde programma"); return 0; } struct knoop *newknoop (void) { return (struct knoop *) malloc(sizeof(struct knoop)); } void boomprint (struct knoop *p) { if (p!=NULL) { boomprint (p->links); printf("%8ld : %8d\n",p->waarde,p->freq); boomprint (p->rechts); } }
18
void rijprint (struct knoop *p) { while (p!=NULL) { printf("%8ld : %8d\n",p->waarde,p->freq); p=p->rechts; } } struct knoop *voegbij (struct knoop *p, long int getal) { if (p == NULL) /*het is een nieuw getal*/ { p=newknoop(); /*nieuwe struct bijmaken*/ if (p==NULL) printf("geen geheugen meer vrij\n"); p->waarde=getal; /*vul getalwaarde in*/ p->freq=1; /*het getal komt de eerste keer voor*/ p->links=NULL; /*er zitten geen getallen lager in*/ p->rechts=NULL; /*de boomstructuur*/ } else if (getal == p->waarde) (p->freq)++; /*het getal komt eenmaal meer voor*/ else if (getal < p->waarde) p->links=voegbij(p->links,getal); /* het getal moet links worden bijgeplaatst en de pointer naar links eventueel aangepast*/ else p->rechts=voegbij(p->rechts,getal); /* het getal moet rechts worden bijgeplaatst en de pointer naar rechts eventueel aangepast*/ return p; /*stuur de (aangepaste) pointer terug*/ } /*zie $: hier wordt een dubbelgelinkte rij gebruikt: de pointers wijzen zowel naar het vorige als naar het volgende element in de rij getallen. in vele toepassingen is dit niet nodig, en komt men toe met een enkel gelinkte rij waarbij er alleen een pointer naar het volgende element van de rij beschikbaar is. men heeft dan wel problemen bij het terug vrijgeven (free) van deze structures: men moet dan telkens van voor af aan in de rij herbeginnen als het laatste element gewist is.*/
19
struct knoop *voegtussen (struct knoop *p, long int getal) { struct knoop *p1, *p2, *p3; if (p == NULL) /*de rij is nog leeg*/ /*het is een nieuw getal*/ { p=newknoop(); /*nieuwe struct bijmaken*/ if (p==NULL) printf("geen geheugen meer vrij\n"); p->waarde=getal; /*vul getalwaarde in*/ p->freq=1; /*het getal komt de eerste keer voor*/ p->links=NULL; /*links=begin van de rij; zie $*/ p->rechts=NULL; /*rechts: nog niets*/ } else { /*begin te zoeken naar een knoop met waarde groter of gelijk aan getal*/ p1=p; p2=NULL; /*p2 is eerst NULL : eerste struct*/ while ((p1!=NULL) && (p1->waarde < getal)) { /*p2 volgt p1*/ p2=p1; p1=p1->rechts; } /*indien p1==NULL wordt de < test niet meer gedaan*/ if (p1==NULL) { /*we zaten aan het einde van de rij*/ p1=newknoop(); if (p1==NULL) printf("geen geheugen meer vrij\n"); p1->waarde=getal; p1->freq=1; p1->links=p2; /*zie $*/ p1->rechts=NULL; p2->rechts=p1; } else if (p1->waarde == getal) (p1->freq)++; /*er moet geen knoop bijkomen*/ else { /*er moet een knoop tussenkomen*/ p3=newknoop(); if (p3==NULL) printf("geen geheugen meer vrij\n"); p3->waarde=getal; p3->freq=1; if (p2==NULL) /*het is de eerste knoop*/ { p2=p=p3; p3->links=NULL; /*zie $*/ } else { p2->rechts=p3; p3->links=p2; /*zie $*/ } 20
p3->rechts=p1; p1->links=p3;
/*zie $*/
} } return p;
/*stuur de (aangepaste) pointer terug*/
}
Hoe kan men dit programma verklaren?. Het is de bedoeling een aantal positieve cijfers in te typen en deze cijfers gesorteerd bij te houden. Het programma laat toe op twee manieren te werken: enerzijds met behulp van een boomstructuur, waarbij aan de linkerkant kleinere cijfers staan en aan de rechterkant hogere cijfers en anderzijds met behulp van een (dubbel gelinkte) rij. Bekijken we nu in detail enkele bevelen: struct knoop { long int int struct knoop struct knoop };
waarde; freq; *links; *rechts;
Een veld van een struct mag gerust een pointer zijn naar opnieuw hetzelfde struct type. Alle pointerstypes gebruiken immers toch hetzelfde aantal bytes, want de adressen kunnen overal in het geheugen staan. We schrijven volgende functies (om het geheel binnen redelijke dimenties te houden): struct knoop *newknoop (void); aanvraag nieuwe geheugenruimte struct knoop *voegbij (struct knoop *, long int); pas boom aan struct knoop *voegtussen (struct knoop *, long int); pas rij aan void boomprint (struct knoop *); print boom af void rijprint (struct knoop *); print rij af Een pointer die naar niets wijst, schrijft men als volgt: proot=NULL;
21
De kern van de dynamische geheugenallocatie zit in volgende functie: struct knoop *newknoop (void) { return (struct knoop *) malloc(sizeof(struct knoop)); } malloc is een functie die een pointer geeft naar een pas aangemaakte ruimte in het geheugen. Als enige parameter geeft men het aantal bytes. Syntax #include <stdlib.h> or #include void *malloc(size_t size); Description malloc allocates a block of size bytes from the memory heap. It allows a program to allocate memory explicitly as it's needed, and in the exact amounts needed. Allocates main memory.The heap is used for dynamic allocation of variable-sized blocks of memory. Many data structures, for example, trees and lists, naturally employ heap memory allocation. For 16-bit programs, all the space between the end of the data segment and the top of the program stack is available for use in the small data models, except for a small margin immediately before the top of the stack. This margin is intended to allow the application some room to make the stack larger, in addition to a small amount needed by DOS. In the large data models, all the space beyond the program stack to the end of available memory is available for the heap. Return Value On success, malloc returns a pointer to the newly allocated block of memory. If not enough space exists for the new block, it returns NULL. The contents of the block are left unchanged. If the argument size == 0, malloc returns NULL. De cast (struct knoop) forceert dat deze functie een pointer weergeeft naar het gewenste type, want malloc geeft een pointer naar het type void. Het aantal bytes om het type struct weer te geven, kan gevonden worden met de functie sizeof. Pointers worden veel gebruikt. De notatie p-> is een korte notatie, zoals in volgend voorbeeld: void rijprint (struct knoop *p) { while (p!=NULL) { printf("%8ld : %8d\n",p->waarde,p->freq); p=p->rechts; } } 22