8.11. Pointeri spre functii
2019/03/20 in Programare in C
Numele unei functii este un pointer spre functia respectiva. De aceea, numele unei functii poate fi folosit ca parametru efectiv la apelul unei functii. In felul acesta, o fuctie poate transfera functiei apelate un pointer spre o functie. Functia apelata, la randul ei, poate apela functia care i-a fost transferata in acest fel.
Fie, de exemplu, f o functie care are ca parametru o alta functie. Apelul:
f(g);
unde g este numele unei functii, transfera functiei f pointerul spre functia g.
Inainte de apel, ambele functii trebuie sa fie definite, sau sa li se indice prototipurile.
Fie g de prototip:
tipg g(listag);
unde prin tipg s-a notat tipul valorii returnate de g sau void in cazul in care functia g nu returneaza nicio valoare, iar listag este lista cu tipurile parametrilor functiei g sau cuvantul void, daca g nu are parametri.
Sa definim prototipul functiei f. Asa cum s-a indicat mai sus, ea are un parametru care este un pointer spre o functie care are un prototip de forma prototipului functiei g (numai numele poate diferi).
O constructie de forma:
tip *nume
defineste pe nume ca pointer spre tipul tip.
O constructie de forma:
tip *nume(lista)
defineste pe nume ca functie care returneaza un pointer spre tip.
Prezenta parantezelor conduce la faptul ca nume este o functie si nu un pointer. Aceasta deoarece parantezele rotunde reprezinta operatori mai prioritari decat operatorul unar *. Pentru ca nume sa fie pointer, este nevoie ca operatorul unar * sa se aplice prioritar fata de parantezele care indica prezenta unei functii. Aceasta se obtine folosind o constructie de forma:
tip (*nume)(lista)
In cazul de fata nume este un pointer spre o functie. Valoarea returnata de aceasta functie are tipul tip, iar lista defineste tipurile parametrilor acestei functii.
In cazul functiei f de mai sus, parametrul ei formal se defineste astfel:
tipg(*nume_par_formal)(listag)
Amintind faptul ca intr-un prototip se pot omite numele parametrilor formali, rezulta ca prototipul functiei f poate fi scris astfel:
tipf f(tipg(*)(listag));
unde tipf este tipul valorii returnate de functia f sau void in cazul in care f nu returneaza nicio valoare.
Antetul functiei f va fi:
tipf f(tipg(*p)(listag))
unde listag defineste tipurile parametrilor functiilor care se transfera la apelurile lui f prin pointerii spre ele.
Exemple:
- Functia g are prototipul:
Functia f care are la apel ca parametru efectiv pe g si returneaza o valoare de tip double, are prototipul:int g(void);
double f(int(*)(void));
- Functia h are prototipul:
Functia f care are la apel ca parametru efectiv pe h si returneaza o valoare de tip int, are prototipul:double h(int, float);
int f(double(*)(int, float));
Fie functia f care are antetul:
tipf f(tip (*p)(lista))
Deci, parametrul ei p este un pointer spre o functie care returneaza o valoare de tipul tip, iar tipurile parametrilor acestei functii sunt definiti de lista.
La un apel de forma f(g);
lui p i se atribuie ca valoare pointerul spre functia g.
Se pune problema de a apela functia g in corpul functiei f, folosind parametrul formal p.
In acest caz, apelul obisnuit al lui g: g(...);
sau x=g(...);
se schimba inlocuind numele g
(necunoscut in momentul definirii functiei f) prin *p.
Deci in corpul functiei f, vom utiliza apeluri de forma:
(*p)(...);
sau:
x=(*p)(...);
pentru a apela functia care s-a transferat prin parametru.
Exercitii:
8.25. Sa se scrie o functie care calculeaza si returneaza valoarea aproximativa a integralei definite dintr-o functie f(x), folosind metoda trapezului.
Sa presupunem ca integrala se calculeaza de la a la b. Atunci o valoare aproximativa a integralei se determina cu ajutorul formulei:
Integrala = h*((f(a)+f(b))/2 + f(a+h) + f(a+2*h) + f(a+3*h) +...+ f(a+(n-1)*h))
unde h = (b-a)/n
.
Functia de fata calculeaza partea dreapta a acestei relatii. Ea are urmatorii parametri:
a | limita inferioara - numar de tip double; |
b | limita superioara - numar de tip double; |
n | numarul de subintervale in care s-a impartit intervalul [a, b] - intreg de tip int; |
p | pointerul spre functia f(x) din care se calculeaza integrala - aceasta are un parametru de tip double si
returneaza o valoare de tip double: double f(double x) . |
- FUNCTIA107.C - Integrala definita de f(x)
double itrapez(double a, double b, int n, double(*p)(double))
{
double h, s;
int i;
h = (b - a)/n;
s = 0.0;
for(i=0; i < n; i++)
s += (*p)(a + i*h);
s += ((*p) (a) + (*p) (b))/2;
s *= h;
return s;
}
8.26. Sa se scrie un program care calculeaza integrala definita de functia:
f(x) = sin x2
de la 0 la 1, folosind metoda trapezului. Se cere o eroare mai mica decat 1e-8
.
Pentru a obtine precizia dorita vom proceda ca mai jos:
- Se alege valoarea initiala pentru n, de exemplu 10;
- Se calculeaza
In
(Integrala); - Se calculeaza
I2n
(se dubleaza n); - Daca
abs(I2n - In) < 1e-8
, atunci I2n este rezultatul si procesul de calcul se intrerupe;
Altfel se dubleaza n, se puneIn = I2n
si se continua cu pasul 3 de mai sus.
- Programul 107 - Integrala definita de sin(x*x)
#include <stdio.h>
#include <stdlib.h>
#include "FUNCTIA107.C";
#define A 0.0
#define B 1.0
#define N 10
#define EPS 1e-8
double sinxp(double); /* prototip */
main() {
int n = N;
double in, i2n, vabs;
in = itrapez(A, B, n, sinxp);
do {
n = 2*n;
i2n = itrapez(A, B, n, sinxp);
if((vabs = in - i2n) < 0)
vabs = -vabs;
in = i2n;
} while (vabs >= EPS);
printf("integrala din sin(x*x) = %.10g\n", i2n);
printf("numarul de subintervale n = %d\n", n);
}
double sinxp(double x)
{
return sin(x*x);
}
Observatie:
Valoarea aproximativa a integralei din sin x2 in intervalul [0,1] este 0.3102683026.
Ea se obtine pentru n = 10240.
8.27. Sa se scrie o functie care determina radacina ecuatiei (1) f(x) = 0
din intervalul [a, b], cu o eroare mai mica
decat EPS = 1e-8
, stiind ca ecuatia are o singura radacina in intervalul respectiv si ca f(x) este o
functie continua pe acelasi interval.
Din conditiile enuntate mai sus rezulta ca:
f(a)*f(b) <= 0
sau:
f(a)*f(b) < 0
daca niciuna din limitele intervalului [a, b] nu este radacina a ecuatiei (1).
O metoda simpla de calcul a radacinii ecuatiei (1) este prin metoda injumatatirii:
c = (a+b)/2
;- daca
f(c) = 0, atunci c este solutia cautata si se intrerupe procesul de calcul
; - daca
f(a)*f(c) <= 0
, atunci se puneb = c
, altfela = c
; - daca
b-a < EPS
, atunci procesul de calcul se intrerupe si radacina se ia media dintre a si b.
Altfel algoritmul se reia de la pasul 1.
- FUNCTIA108.C - Radacina functie continua pe interval dat
#define EPS 1e-8
double radec(double a, double b, double(*p)(double))
{
double c, d;
if((*p)(a) == 0)
return a;
if((*p)(b) == 0)
return b;
if((*p)(a)*(*p)(b) > 0) {
printf("ecuatia sau nu are radacina in intervalul [a, b]\n");
printf("sau are radacini multiple in intervalul [a, b]\n");
exit(1);
}
do {
c = (a + b)/2;
if((d = (*p)(c)) == 0)
return c;
if((*p)(a)*d < 0)
b = c;
else
a = c;
} while( b-a >= EPS);
return (a+b)/2;
}
8.28. Sa se scrie un program care calculeaza si afiseaza radacina ecuatiei:
x - sin(x+1) = 0
din intervalul {0.5, 1] cu o eroare mai mica decat 1e-8.
- Programul 108 - Radacina ecuatie din interval dat
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "FUNCTIA108.C";
#define A 0.5
#define B 1.0
double f(double x)
{
return x-sin(x+1);
}
main() {
printf("radacina ecuatiei\n");
printf("x-sin(x+1) = 0 este %.10g\n", radec(A, B, f));
}