Funktionspekare, inledning: funktionsanropsmekanismen Anrop via

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