Funktionspekare, inledning: funktionsanropsmekanismen Vid funktionsanrop läggs aktuella argumentvärden och återhoppsadressen på stacken, därefter sker ett hopp till adressen för funktionens första instruktion. Källkodens funktionsnamn översätts till denna adress. Exempel: void funk(int i){ ....} int main(void){ funk(3); ... main Instr. för att lägga 3 och returadressen på stacken CALL 3000 .... Instruktioner funk 3000 Statiska data i 3 returadressen Stacken Bild 88 Jozef Swiatycki DSV *:58 Anrop via pekare Det finns inget som hindrar att funktionsadressen, istället för att finnas direkt i instruktionen, hämtas från en variabel där den tidigare lagrats av programmet: main Instr. för att lägga funks adress i fpek Instr. för att lägga 3 och returadressen på stacken CALL *3000 .... Instruktioner funk 3000 Statiska data fpek 7000 i 3000 3 Stacken returadressen void funk(int i){ ....} int main(void){ ”funktionspekare” fpek=funk; (*fpek)(3); ... Bild 89 Jozef Swiatycki DSV *:58 1 Om funktionspekare Funktionspekare kan som alla andra pekarvärden lagras i variabler, i datastrukturer (t.ex. en array av funktionspekare), skickas som argument till andra funktioner och returneras från funktioner, m.a.o. användas på alla sätt som andra typer av värden kan användas på. Funktionspekare gör att man kan skriva programkomponenter vilkas beteende kan modifieras och anpassas under exekvering genom att de vid olika tillfällen kan anropa olika funktioner från samma instruktioner (det som i objektorienterade språk kallas dynamisk bindning och polimorfism). Avreferering av en funktionspekare med anropsparenteser efter innebär anrop av den funktion som pekaren pekar ut för tillfället: (*fpek)(3); ANSI-C införde möjligheten att utelämna den explicita avrefereringen: fpek(3); Ett funktionsnamn utan anropsparenteser är adressen till funktionen: fpek=funk; Bild 90 Jozef Swiatycki DSV *:58 Deklarationssyntax - exempel Eftersom kompilatorn skall kunna kontrollera argument- och returvärden vid anrop av funktioner även via pekare, måste funktionspekare deklareras med information om dessa. Deklarationssyntaxen följer anropssyntaxen och måste ta hänsyn till operatorernas precedens: void (*fpek)(int); - fpek är en pekare till en funktion som tar en int som argument och returnerar void. Parenteser kring *fpek är nödvändiga, void *fpek(int) betyder funktion som returnerar void * char *(*fpek)(char *, int); - fpek är en pekare till en funktion som tar en char * och en int som argument och returnerar en char * int (*fpekvek[10])(int); - fpekvek är en vektor med 10 pekare till funktioner som tar en int som argument och returnerar en int Anrop av en funktion via pekare i vektorn skulle ske enligt följande exempel: i=(*fpekvek[3])(137); Bild 91 Jozef Swiatycki DSV *:58 2 Deklarationssyntax - exempel (forts.) int (*getfunk(int))(char *); - getfunk är en funktion som tar en int som argument och som returnerar en pekare till en funktion som tar en char * som argument och returnerar en int void (*signal(int sig, void (*hndl)(int)))(int); - signal är en funktion som tar som argument dels en int (sig), dels en pekare (hndl) till en funktion som tar en int som argument och returnerar void. signal returnerar en pekare till en funktion som tar en int som argument och returnerar void Sådana deklarationer kan underlättas genom att ett namn för funktionspekartypen definieras med en typedef: typedef void (*Sighandl)(int); Sighandl signal(int sig, Sighandl hndl); När pekar- eller argumentnamn är oväsentliga, t.ex. vid typomvandlingar, kan de utelämnas: (int (*)(int, int)) Bild 92 Jozef Swiatycki DSV *:58 Exempel 1: funktion med funktionspekare som argument #include <stdio.h> #include <math.h> void tab(double from, double to, double step, double (*funk)(double), char *header){ double x; printf("x\t%s\n", header); printf("-----------------\n"); for(x=from; x<=to; x+=step) printf("%.2f\t%.3f\n", x, funk(x)); } double sinpluscos(double x){ return sin(x)+cos(x); } int main(void){ tab(0, 1, 0.05, sin, "sin(x)"); tab(0, 1, 0.05, sinpluscos, "sin(x)+cos(x)"); return 0; } Bild 93 Jozef Swiatycki DSV *:58 3 Exempel 2: återanvändbar datastruktur, sid 1 En sorterad array-list: Headerfilen sortarrlist.h: typedef struct sortarrstruct *Sorted_array_list; Sorted_array_list init(int (*cmp)(const void *, const void *)); void add(Sorted_array_list list, void *new); void doforall(Sorted_array_list list, void (*doit)(void *)); Obs att headerfilen endast definierar typen för pekare till något som heter struct sortarrstruct. Detta räcker för att tillämpningar skall kunna deklarera sådana pekarvariabler för att ta emot resultat av funktionen init() och för att skicka dem till de andra funktionerna. Däremot vet inte tillämpningar vad som finns inne i en struct sortarrstruct. På detta sätt skapar man dataabstraktion i C. Bild 94 Jozef Swiatycki DSV *:58 Exempel 2: återanvändbar datastruktur, sid 2 #include <stdlib.h> #include "sortarrlist.h" #define CHUNK 10 typedef struct sortarrstruct{ int siz, count; void **arr; int (*cmp)(const void *, const void *); } Sort_arr_struct; Sorted_array_list init(int (*cmp)(const void *, const void *)){ Sorted_array_list tmp = malloc(sizeof(Sort_arr_struct)); if (!tmp) return NULL; tmp->siz=CHUNK; tmp->count=0; tmp->arr=calloc(CHUNK, sizeof(void *)); if (!tmp->arr){ free(tmp); return NULL; } /* if */ tmp->cmp=cmp; return tmp; } Bild 95 Jozef Swiatycki DSV *:58 4 Exempel 2: återanvändbar datastruktur, sid 3 void add(Sorted_array_list list, void *new){ int i; if (list->count == list->siz) list->arr=realloc(list->arr, (list->siz*=2)*sizeof(void *)); for(i=list->count; i>0 && list->cmp(list->arr[i-1], new)>0; i--) list->arr[i] = list->arr[i-1]; list->arr[i] = new; list->count++; } void doforall(Sorted_array_list list, void (*doit)(void *)){ int i; for(i=0; i<list->count; i++) doit(list->arr[i]); } Bild 96 Jozef Swiatycki DSV *:58 Exempel 2: återanvändbar datastruktur, sid 3, tillämpningsprogram #include #include #include #include <stdio.h> <stdlib.h> <string.h> ”sorarrlist.h” typedef struct _person{ int nr; char namn[10]; } Person; Person *make(int nr, char *namn){ Person *pek=malloc(sizeof(Person)); pek->nr=nr; strcpy(pek->namn, namn); return pek; } int perscmp(Person *p1, Person *p2){ return strcmp(p1->namn, p2->namn); } Bild 97 Jozef Swiatycki DSV *:58 5 Exempel 2: återanvändbar datastruktur, sid 4, tillämpning forts. void visa(Person *p){ printf("%d\t%s\n", p->nr, p->namn); } int main(void){ Sorted_arr_list reg=init((int (*)(void *, void *))perscmp); add(reg, make(61, "Stefan")); add(reg, make(53, "Jozef")); add(reg, make(62, "Anna")); doforall(reg, (void (*)(void *))visa); return 0; } Bild 98 Jozef Swiatycki DSV *:58 qsort och bsearch #include <stdlib.h> void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *)); sorterar arrayen som pekas ut av base och som består av n stycken objekt, var och en av storleken size. cmp skall vara en funktion som tar två pekare till sådana objekt och returnerar enligt samma konvention som strcmp. void *bsearch(const void *key, const void *base; size_t n, size_t size, int (*cmp)(const void *, const void *)); söker med binär sökning i den sorterade arrayen som pekas ut av base och som består av n stycken objekt av storleken size. cmp skall kunna ta en pekare till ett sökbegreppet (key) och en pekare till ett objekt och returnera enligt samma konvention som strcmp. Bild 99 Jozef Swiatycki DSV *:58 6