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){... funk(3);... main funk 3000 Instr. för att lägga 3 och returadressen på stacken CALL 3000... i 3 returadressen Instruktioner Statiska data Stacken Bild 88 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 funk 3000 Instr. för att lägga funks adress i fpek Instr. för att lägga 3 och returadressen på stacken CALL *3000... fpek 7000 3000 i 3 returadressen Instruktioner Statiska data Stacken void funk(int i){... funktionspekare fpek=funk; (*fpek)(3);... Bild 89 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 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 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 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); tab(0, 1, 0.05, sin, "sin(x)"); tab(0, 1, 0.05, sinpluscos, "sin(x)+cos(x)"); return 0; Bild 93 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 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 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 Exempel 2: återanvändbar datastruktur, sid 3, tillämpningsprogram #include <stdio.h> #include <stdlib.h> #include <string.h> #include 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 5
Exempel 2: återanvändbar datastruktur, sid 4, tillämpning forts. void visa(person *p){ printf("%d\t%s\n", p->nr, p->namn); 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 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 6