Functii
Spre deosebire de alte limbaje, limbajul C recunoaste ca module componente ale unui program numai functiile si ele nu pot fi definite în interiorul altei functii.
14.8.1. Prototipul unei functii.
Prototipul unei functii este o simpla declaratie, plasata în zona de început a programului, declaratie care are rolul de a înttiinta compilatorul asupra numarului si tipurilor de argumente primite de functie, precum si asupra tipului de rezultat returnat de catre functie.
Forma generala a prototipului unei functii este urmatoarea:
[clasa_de_memorare] tip_rezultat nume_functie([lista_tipuri_argumente]);
unde:
clasa_de_memorare poate fi de tipul static sau extern. Atributul static face ca functia respectiva sa nu fie vizibila pentru programe definite în alte fisiere. Atributul extern este optional si implicit si face functia respectiva vizibila si dintr-un alt fisier sursa;
tip_rezultat specifica tipul rezul 121h76b tatului returnat de functie. Din acest punct de vedere trebuie sa existe concordanta cu tipul de rezultat referit de instructiunea return. Daca functia nu returneaza nici un rezultat, atunci tip_rezultat este tipul void;
nume_functie este numele functiei. El trebuie sa fie unic si sa nu coincida cu vreunul dintre numele de functii standard ale limbajului C;
lista_tipuri_argumente are forma:
tip_arg_1,...,tip_arg_n
unde tip_arg_j este tipul argumentului j, j=1,2,...,n.
Daca functia nu primeste nici un argument, atunci lista_tipuri_argumente va contine numai mentiunea void.
14.8.2. Definirea unei functii.
Definirea unei functii are forma:
antet
unde antet are aproape aceeasi forma cu prototipul functiei, si anume:
tip_rezultat nume_functie([lista_tipuri_argumente]);
cu deosebirea ca lista_tipuri_argumente are acum forma:
tip_arg_1 nume_arg_1,..., tip_arg_n nume_arg_n
unde nume_arg_j este numele intern (din functie) al argumentului j, j=1,2,...,n;
este un bloc de program continând declaratii de variabile, constante si instructiuni.
14.8.3. Apelul unei functii.
O functie se apeleaza sub forma:
nume_functie(nume_arg_1,...,nume_arg_n);
unde: nume_arg_j, j=1,2,...,n, trebuie sa desemneze un argument de tipul acceptat de functie. Daca functia nu primeste nici un argument, atunci lista de argumente este vida, de forma ().
14.8.4. Instructiunea return.
Într-o functie pot exista mai multe instructiuni return sau nici una. Atingerea unei instructiuni return în cursul executiei produce întoarcerea în programul principal. Forma instructiunii return este:
return rezultat;
unde: rezultat trebuie sa fie de tipul tip_rezultat definit de prototipul functiei.
Daca functia nu produce nici un rezultat, instructiunea return are forma:
return;
Daca acest lucru se produce la sfârsitul corpului functiei, instructiunea return poate lipsi.
14.8.5. Functia main.
Functia main reprezinta, de fapt, programul principal în limbajul C. Ea nu are nevoie de prototip ci numai de definitie, care apare în forma:
[void] main([parametri])
În versiunile timpurii de C, definitia functiei main era precedata de tipul void, dar la versiunile recente mentionarea lui nu mai este decât optionala, fiind mentinuta pentru compatibilitatea cu programele sursa scrise pentru versiunile anterioare.
De obicei functia main nu are parametri. Acesta este cazul programelor lansate numai prin nume, fara parametri în linia de comanda. În aceste caz, lista de parametri este vida:
main()
În sistemul de operare DOS un program poate fi lansat cu parametri în linia de comanda, în forma generala:
nume_program param_1, param_2,...,param_n
Parametrii param_1, param_2,...,param_n trebuie preluati de program si prelucrati în corpul sau. În acest caz, functia main trebuie sa apara cu o lista formata din doi parametri:
main(int numar_par_com, char ** tabl_par)
unde: numar_par_com este numarul de parametri din linia de comanda;
**tabl_par este un pointer catre un tablou de siruri de caractere care contine cuvintele liniei de comanda, astfel:
tabl_par[0] este numele programului, adica nume_program
tabl_par[1] este primul pametru al liniei de comanda, adica param_1;
.....
tabl_par[n] este ultimul pametru al liniei de comanda, adica param_n;
14.8.6. Exemplu de folosire a functiilor într-un program C.
Cu toate ca nu a fost facuta înca o prezentare a structurii unui program C (pentru ca mai este necesara parcurgerea înca a unor notiuni), pentru întelegerea modului de folosire a functiilor, în continuare este prezentat un exemplu de program C simplu:
#include <conio.h>
#include <stdio.h>
float a=1.0, b=2.5, c; /*declarare si initializare variabile globale */
float aduna(float, float); /* declarare prototipuri functii */
main() /* definire functie main */
float aduna(float u, float v) /* definirea functiei aduna*/
Când compilatorul ajunge la functia main, el întâlneste apelul functiei aduna. Pentru a rezolva corect legaturile pe care le are de facut el trebuie sa cunoasca, daca nu chiar functia aduna, macar prototipul ei. De aceea acest prototip trebuie sa preceada apelul, asadar trebuie sa se gaseasca în partea de început a programului.
Daca, însa, este mutata definitia functiei aduna înaintea definitiei functiei main în care se gaseste apelul functiei aduna, prototipul functiei aduna nu mai este necesar, deoarece datele necesare efectuarii legaturilor necesare sunt luate chiar din definitia functiei aduna.
Ca urmare a rularii programului de mai sus, se va obtine afisarea:
c=a+b=1.000000+2.500000=3.500000
adica functia aduna returneaza suma numerelor argumente, acest rezultat fiind atribuit variabilei c.
14.8.7. Transferul argumentelor functiilor.
Reamintind cele prezentate anterior, definirea unei functii se prezinta sub forma:
tip_rezultat nume_functie(tip_1 arg_formal_1,..., tip_n arg_formal_n)
unde: tip_j, j=1,2,...,n este tipul argumentului;
arg_formal_j, j=1,2,...,n este argumentul formal (denumirea din interiorul functiei);
iar apelul functiei are forma:
nume_functie(arg_efectiv_1,...,arg_efectiv_n);
unde: arg_efectiv_j, j=1,2,...,n este argumentul efectiv (denumirea din blocul de unde se apeleaza functia).
Între arg_formal_j si arg_efectiv_j trebuie sa existe concordanta de tip, arg_efectiv_j putând sa fie o constanta, o variabila, o expresie sau o functie cu rezultatul de tipul tip_j.
Mecanismul de actiune al apelului este urmatorul:
- se atribuie lui arg_formal_j valoarea lui arg_efectiv_j;
- se executa operatiile din corpul functiei;
- se returneaza rezultatul (daca exista o returnare de rezultat).
Ca urmare, daca argumentele nu sunt de tipul adresa, indiferent ce s-ar întâmpla în corpul functiei, valorile argumentelor arg_efectiv_j nu sunt afectate.
În exemplul urmator:
#include <conio.h>
#include <stdio.h>
typedef struct punct2D;
punct2D punct_mediu(punct2D p1, punct2D p2)
main()
, b=, c;
c=punct_mediu(a,b);
printf("a(%f,%f)\n",a.x,a.y);
printf("b(%f,%f)\n",b.x,b.y);
printf("c(%f,%f)\n",c.x,c.y);
getch();
se obtine ca efect atribuirea listei de coordonate medii ale coordonatelor punctelor a si b punctului c dupa care se afiseaza:
a(0.000000,0.000000)
b(6.000000,6.000000)
c(3.000000,3.000000)
Desi în corpul functiei punctele p1 si p2 si-au schimbat între ele coordonatele, ele fiind numai copii ale punctelor a si b, aceasta inversare nu are efect în exteriorul functiei punct_mediu. Daca, însa, se modifica programul ca mai jos:
#include <conio.h>
#include <stdio.h>
typedef struct punct2D;
punct2D punct_mediu(punct2D *p1, punct2D *p2)
main()
, b=, c;
c=punct_mediu(&a,&b);
printf("a(%f,%f)\n",a.x,a.y);
printf("b(%f,%f)\n",b.x,b.y);
printf("c(%f,%f)\n",c.x,c.y);
getch();
se va obtine ca rezultat afisarea
a(6.000000,6.000000)
b(0.000000,0.000000)
c(3.000000,3.000000)
ceea ce denota acelasi efect direct al returnarii rezultatului atribuit punctului c, dar, deoarece ca parametri ai functiei punct_mediu se transmit adresele punctelor a si b, prelucrarile din interiorul functiei vor avea loc asupra originalelor si, ca urmare, la iesirea din functie punctele originale a si b vor avea coordonatele schimbate reciproc.
Acesta este un exemplu de efect lateral al unei functii si, daca nu este intentionat, poate fi o sursa de erori.
Exemplele de mai sus au avut ca mod de lucru:
a. transferul parametrilor d eintrare prin argumente efective;
b. transferul rezultatului (iesirea) prin rezultatul returnat de functie;
Este posibila asigurarea exclusiv prin argumentele functiei, atât a transmiterii parametrilor de intrare cât si a obtinerii rezultatului. În acest caz, adaptând cele doua programe de mai sus si eliminând efectul lateral nedorit, poate fi scris programul:
#include <conio.h>
#include <stdio.h>
typedef struct punct2D;
void punct_mediu(punct2D p1, punct2D p2, punct2D *pm)
main()
, b=, c;
punct_mediu(a,b,&c);
printf("a(%f,%f)\n",a.x,a.y);
printf("b(%f,%f)\n",b.x,b.y);
printf("c(%f,%f)\n",c.x,c.y);
getch();
Acum, în functia main, functia punct_mediu primeste ca argumente efective valorile punctelor a si b si apoi adresa punctului c. La terminarea executiei, la adresa punctului c se va gasi stocat rezultatul obtinut în corpul functiei punct_mediu.
Se observa deja unele mecanisme de transfer foarte avantajoase: deoarece o functie poate returna un singur rezultat, când este necesar ca ea sa aiba ca rezultat un efect asupra unui set de date, se pot folosi urmatoarele modalitati de lucru:
- returnarea unei structuri care contine un set de date;
- folosirea de variabile globale pentru variabilele din set care vor reflecta starea dupa iesirea din functie;
- folosirea de adrese ca argumente ale functiei, la iesirea din functie variabilele adresate având starea capatata în corpul functiei.
Modalitatea recomandabila este prima. Ultimele doua modalitati încalca principiul încapsularii datelor si, desi pot fi foarte utile, trebuie folosite cu foarte multa atentie.
Transmiterea argumentelor de tip tablou ale unei functii are la baza mecanismul de transmitere a adreselor descris mai sus. Numele unui tablou are semnificatia unui pointer la primul element al tabloului deci, transmiterea prin indicarea numelui tabloului nu se face prin copierea tabloului ci prin referinta la adresa lui.
Ca urmare, în programul:
#include <conio.h>
#include <stdio.h>
float suma(float tablou[], int nmax)
main()
printf("suma=%f\n",suma(tabl,3));
getch();
functia suma va returna suma elementelor tabloului, fara sa altereze valoarea acestora deoarece nu se opereaza asupra lor. Efectul rularii acestui program este afisarea:
suma=6.000000
În schimb, în programul:
#include <conio.h>
#include <stdio.h>
void rotire_stanga(float tablou[], int nmax)
main()
; int i;
rotire_stanga(tabl,3);
for(i=0;i<3;i++) printf("tabl[%i]=%f\n",i,tabl[i]);
getch();
functia rotire_stanga nu returneaza nici o valoare dar are ca efect, dupa iesirea din ea, rotirea elementelor tabloului cu o pozitie catre stânga. Efectul rularii programului va fi afisarea:
tabl[0]=2.000000
tabl[1]=3.000000
tabl[2]=1.000000
Tablourile pot fi tratate la transmitere si ca pointeri, ca în exemplul urmator, în care numai definirea functiei este modificata, restul programului ramânând neschimbat:
void rotire_stanga(float *tablou, int nmax)
În cazul tablourilor multidimensionale, lucrurile sunt putin diferite.
Fie cazul unui tablou cu doua dimensiuni, pentru care se cere incrementarea tuturor elementelor. Se poate proceda în mai multe moduri, conform exemplelor urmatoare:
a.
#include <conio.h>
#include <stdio.h>
#define dim1 2
#define dim2 2
void increment(float tablou[dim1][dim2])
main()
increment(tabl);
for(i=0;i<dim1;i++)
for(j=0;j<dim2;j++)
printf("tabl[%i][%i]=%f\n",i,j,tabl[i][j]);
getch();
b. la fel ca în cazul a, cu unica diferenta ca definirea functiei este de forma:
void increment(float tablou[][dim2])
c. se modifica functiile increment si main, astfel:
void increment(float **ptablou, int k1, int k2)
main()
float *ptab;
ptab=*tabl;
increment(&ptab,dim1,dim2);
for(i=0;i<dim1;i++)
for(j=0;j<dim2;j++)
printf("tabl[%i][%i]=%f\n",i,j,tabl[i][j]);
getch();
În cazurile a si b s-a efectuat apelul cu numele tabloului iar functia trebuia sa "vada" macar ultima dimensiune a tabloului si faptul ca are doua dimensiuni. În cazul c s-a facut uz de mecanismele de transfer a pointerilor si de dereferentiere a acestora.
|