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:

  1. Functia g are prototipul:

    int g(void);
    Functia f care are la apel ca parametru efectiv pe g si returneaza o valoare de tip double, are prototipul:

    double f(int(*)(void));
  2. Functia h are prototipul:

    double h(int, float);
    Functia f care are la apel ca parametru efectiv pe h si returneaza o valoare de tip int, are prototipul:

    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).

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:

  1. Se alege valoarea initiala pentru n, de exemplu 10;
  2. Se calculeaza In (Integrala);
  3. Se calculeaza I2n (se dubleaza n);
  4. Daca abs(I2n - In) < 1e-8, atunci I2n este rezultatul si procesul de calcul se intrerupe;

    Altfel se dubleaza n, se pune In = I2n si se continua cu pasul 3 de mai sus.
#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:

  1. c = (a+b)/2;
  2. daca f(c) = 0, atunci c este solutia cautata si se intrerupe procesul de calcul;
  3. daca f(a)*f(c) <= 0, atunci se pune b = c, altfel a = c;
  4. 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.
#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.

#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));
}

9. Recursivitate