Transmiterea parametrilor unei funcții Parametrii se transmit de la funcţia apelantă la funcţia apelată prin intermediul stivei. La apelul unei funcţii, pe stivă se crează o înregistrare de activare, care cuprinde, de jos în sus: adresa de revenire din funcţie, valorile parametrilor actuali şi variabilele locale. Parametrii se transmit prin valoare în ordinea inversă celei din lista de parametrii, astfel încât primul parametru din listă să fie întotdeauna sub adresa de revenire. Dacă funcţia apelată are şi variabile locale, acestea se crează tot pe stivă în ordinea declarării. Zona de pe stivă în care se plasează valoarea parametrilor actuali, adresa de revenire şi variabilele locale poartă denumirea de context de apel, iar acest mod de transmitere a parametrilor către funcţia apelată poartă denumirea de convenţie de apel C. În afara apelului prin valoare, care transferă valorile parametrilor efectivi, mai există şi apelul prin referinţă. În acest caz, la apel nu se transferă valorile parametrilor efectivi, ci adresele acestor valori. Deci, între cele două tipuri de apel există o diferenţă esenţială, şi anume: dacă în cazul apelului prin valoare, funcţia apelată nu poate modifica parametri efectivi din funcţia care a făcut apelul, neavând acces la ei, în cazul apelului prin referinţă, funcţia apelată, dispunând de adresa parametrilor efectivi, îi poate modifica. Dacă se doreşte ca o funcţie să modifice valoarea unei variabile, apelul trebuie să conţină ca parametru pointerul la variabila respectivă. Apel prin referinţă utilizând parametrii de tip pointer La apelul unei funcţii dacă parametrul efectiv este numele unui tablou, apelul prin valoare devine prin referinţă, parametrul formal corespunzător va primi ca valoare 1
adresa primului element al tabloului. Deci, putem spune că parametrii formali declaraţi ca tablouri sunt pointeri. Exemplu: int tab[];... f(tab);... şi f are antetul void f (int v[]); La apel v are aceeaşi valoare cu tab, adică tab[0] şi v[0] exprimă acelaşi lucru, valoarea lui tab [0]. Dacă x este o variabilă simplă, atunci putem transfera la un apel, în locul valorii lui x, adresa lui x: int x;... h(x); /* se transferă valoarea lui x */... g(&x); /* se transferă adresa lui x */... În acest caz, antetele funcţiilor sunt: void h(int i) şi void g(int * p) Exemplu: Programul realizează interschimbarea a două variabile folosind o funcţie: #include <stdio.h> void interschimba(int *, int *); 2
int main() { int a=3, b=7; printf()=( %d %d \n, a,b); interschimbă (&a, &b); printf( %d %d \ n, a,b); return 0; } void interschimba(int *p, int *q) { int tmp; tmp=*p; *p=*q; *q=tmp; } Efectul apelului prin adresă este realizat prin: 1. Declararea parametrului funcţiei ca fiind un pointer; 2. Folosirea unui pointer de indirectare în corpul funcţiei. 3. Transmiterea adresei unui argument când funcţia este apelată. Alocarea dinamică a memoriei Limbajul C permite utilizatorului să aloce date atât pe stivă (date automatice) cât şi în zone de memorie care nu aparţin stivei (date globale şi statice). Alocarea datelor pe stivă se face la execuţie şi ea nu este permanentă. Astfel, dacă declaraţia : 3
tip nume ; se utilizează în corpul unei funcţii, atunci variabila nume se alocă pe stivă la fiecare apel al funcţiei respective. La revenirea din funcţie, stiva se curăţă (se readuce la starea avută înaintea apelului) şi prin aceasta variabila nume nu mai este alocată (devine nedefinită). O alocare de acest fel a memoriei se spune că este dinamică. Pentru datele globale sau statice, memoria este alocată în fazele precedente execuţiei şi alocarea rămâne valabilă până la terminarea execuţiei programului. De aceea pentru datele de acest fel se spune că alocarea este statică ( nu este dinamică). Limbajele C şi C++ oferă utilizatorului posibilitatea de a aloca dinamic memorie şi în alt mod decât cel indicat mai sus pentru datele automatice. Aceasta se realizează într-o zonă de memorie specială, distinctă de stivă. Această zonă de memorie se numeşte memorie heap. Ea poate fi gestionată prin funcţii standard. Biblioteca standard a limbajului C pune la dispoziţia utilizatorului funcţii care permit alocarea de zone de memorie în timpul execuţiei programului. O astfel de zonă de memorie poate fi utilizată pentru a păstra date temporare. Zona respectivă poate fi eliberată în momentul în care nu mai sunt necesare datele care au fost păstrate în ea. Alocarea de zone de memorie şi eliberarea lor în timpul execuţiei programelor permite gestionarea optimă a memoriei de către programator. Un astfel de mijloc de gestionare a memoriei îl vom numi alocare dinamică a memoriei. Funcţiile standard pentru gestiunea memoriei heap au prototipurile în fişierul alloc.h. Alocarea unei zone de memorie în memoria heap se realizează cu ajutorul funcţiei malloc care are prototipul: void *malloc ( unsigned n) ; Această funcţie alocă o zonă de memorie contiguă de n octeţi. Ea returnează adresa de început a zonei alocate. Această adresă reprezintă un pointer de tip void (void *). Prin intermediul acestui pointer se pot păstra date în zona de memorie alocată în acest fel. Pentru a păstra o dată de un tip dat într-o zonă de memorie alocată prin malloc este necesar să convertim 4
adresa returnată de funcţie spre tipul datei respective. Exemplu: Se cere să se aloce în memoria heap o zonă de memorie pentru a păstra n valori de tip int. În acest scop declarăm un pointer spre tipul int: int *p ; apoi apelăm funcţia malloc cu ajutorul expresiei de atribuire : p = ( int *) malloc( n*sizeof( int)) Valoarea returnată de funcţia malloc a fost convertită spre tipul int*, adică pointer spre tipul int. În continuare putem pastra şi utiliza date de tip int, folosind variabila pointer p. Funcţia malloc are ca parametru un întreg fără semn, adică acesta aparţine intervalului [0, 65.535]. În cazul în care în memoria heap nu se poate aloca o zonă de memorie contiguă de atâţia octeţi cât este valoarea parametrului de la apel, se va returna pointerul nul, adică valoarea zero. De aceea, după apelul funcţiei malloc se va testa valoarea returnată pentru a ne asigura că aceasta nu este zero. Zonele alocate prin funcţia malloc pot fi eliberate, pentru a putea fi eventual realocate, folosind funcţia standard free. Aceasta are prototipul : void free ( void *p) ; Prin apelul ei, se eliberază zona de memorie din memoria heap, spre care pointează p. (variabila p trebuie să fie obţinută printr-un apel al unei funcţii standard de alocare, cum este de exemplu malloc) Se recomandă ca această funcţie să fie apelată de îndată ce datele dintr-o zonă de memorie heap nu mai sunt necesare, astfel zona respectivă poate fi ulterior realocată. O altă funcţie standard utilă pentru a aloca zone de memorie în memoria heap este funţia calloc, cu prototipul : void *calloc ( unsigned nrelem, unsigned dimelem) ; Funcţia alocă o zonă de memorie egală cu nrelem*dimelem octeţi. Ea returnează adresa de început a zonei de memorie alocată, adresă care reprezintă un ppinter spre void. În 5
cazul în care nu se pot aloca nrelem*dimelem octeţi, funcţia returnează zero. Zona de memorie alocată cu calloc, se eliberează folosind funcţia free. Vectori alocaţi dinamic Un tablou poate fi alocat dinamic printr-o secvenţă de tipul: TIP * p; p= (TIP *) malloc(n*sizeof(tip)); Pointerul p va indica un bloc suficient de mare pentru a conţine N elemente de tipul TIP. În continuare, variabila p poate fi utilizată ca şi cum ar fi fost declarată ca un tablou de forma: TIP p[ N ]; Avantajul alocării dinamice a unui tablou este că dimensiunea sa poate fi specificată doar în timpul execuţiei. Un vector alocat dinamic se declară ca variabilă pointer care se iniţializează cu rezultatul funcţiei de alocare. Tipul variabilei pointer este determinat de tipul componentelor vectorului. Exemplul 1 : Alocarea dinamică de memorie pentru un tablou de n numere întregi: #include <stdlib.h> #include <stdio.h> int main() { int n; int * tab; int i; printf("introduceti numarul de elemente: \n"); scanf("%d", &n); 6
} if ((tab=(int *)malloc(n * sizeof(int)))==null) { printf("eroare alocare dinamica memorie!\n"); exit(1); } for (i=0; i<n; i++) printf("%d ", tab[ i ]); free(tab); return 0; Exemplu 2: Definirea și utilizarea unui vector alocat dinamic (varianta 2) #include <stdlib.h> #include <stdio.h> int main() { int n, i; int * a; // adresa vector alocat dinamic printf ("n="); scanf ("%d", &n); // dimensiune vector a=(int *) calloc (n,sizeof(int)); // aloca memorie pentru vector // sau: a=(int*) malloc (n*sizeof(int)); // citire component vector: printf ("componente vector: \n"); for (i=0;i<n;i++) scanf ("%d", &a[i]); // sau scanf ( %d, a+i); // afisare vector: for (i=0;i<n;i++) printf ("%d ",a[i]); return 0; } 7
Modificatorul CONST Constantele se pot defini prin: caracterele care o compun construcţia #define intermediul modificatorului const Formatele posibile ale unei declaraţii cu ajutorul modificatorului const sunt: 4. tip const nume = valoare; 5. tip *const nume = valoare; 6. tip const *nume = valoare; 7. const tip nume = valoare; 8. const tip *nume=valoare; Exemplu: 1) int const i = 10; y = i + 3; 2) char *const s = şir ; este un pointer constant spre zona în care se păstrază şirul de caractere format din literele s, i, r şi caracterul NUL. În acest caz valoarea lui s nu poate fi schimbată; el fiind pointer, va avea ca valoare o adresă a unei zone de memorie de dimensiune egală cu 4 octeţi. În această zonă se păstrează şirul de caractere indicat mai sus. Conţinutul acestei zone poate fi modificat. O atribuire de forma s = t; unde t este un pointer spre tipul caracter, nu este acceptată de compilator, dar atribuirile: *s = 1 ; *(s+1) = 2 ; sunt corecte. Cu ajutorul lor se schimbă caracterul s cu 1 şi i cu 2 în zona spre care pointează s; 8
Declaraţia tip const *nume = valoare; defineşte pe nume ca un pointer spre o zonă constantă. În acest caz, valoarea pointerului nume se poate schimba. De exemplu pentru: char const *s = şir ; atribuirea s = t; unde t este un pointer spre char, este corectă. În schimb, atribuirile *s = a ; *(s+1) = b ; sunt eronate, deoarece s pointează spre o zonă în care se păstrează o dată constantă. Constanta respectivă poate fi modificată, folosind un pointer diferit de s: char *p; p = (char*)s; /*p ca şi s pointează spre zona în care se păstrează şirul de caractere şir */ *p = a ; *(p+1) = b ; Declaraţia const tip nume = valoare este identică cu declaraţia (1), dacă tip nu este un tip pointer. Dacă tip este un tip pointer, adică declaraţia (4) este de forma const tip *nume = valoare; atunci ea este identică cu declaraţia (3) De obicei se foloseşte formatul (5), în locul formatului (3) pentru a declara un pointer spre o zonă constantă, a cărui valoare nu poate fi modificată direct, adică prin atribuiri în care se foloseşte nume: *nume = val ; *(nume +k) =val; Declaraţia const tip *nume; 9
se utilizează pentru a declara un parametru formal. Fie funcţia f de antet : tip f (tip *nume) La apelul funcţiei f, parametrului formal nume i se atribuie ca valoare o adresă. În acest caz, funcţia f poate modifica data păstrată în zona de memorie spre care pointează nume, folosind o atribuire de forma: *nume = valoare Dacă se doreşte protejarea datei faţă de eventualele atribuiri neautorizate, vom declara parametrul formal respectiv ca un pointer spre o dată constantă. În acest caz, antetul funcţiei devine tip f (const tip *nume) O atribuire de forma *nume=valoare este interzisă. Se recomandă declaraţia const tip *nume pentru orice parametru formal, care la apel are ca valoare adresa unei zone de memorie, al cărui conţinut nu poate fi modificat de funcţia apelată. Şiruri de caractere Limbajul C nu defineşte tipul de dată şir (String în Pascal), dar există două posibilităţi de definire a şirurilor: ca tablou de caractere: Exemplu: 1) char sir1 [30]; 2) char sir2 [10] = exemplu ; ca pointer la caractere Exemplu: 1) char *sir3; sir3= &sir1; sir3 =&sir1[0]; sir3 = sir1; sir3 = (char *) malloc(100); 2) char *sir 4 = test ; 10
folosirea unui pointer spre un şir const. Diferenţa dintre un şir iniţializat cu o constantă şir şi un pointer iniţializat tot cu o constantă şir este că şirul conţine caractere individuale, urmate de caracterul \0, iar pointerul este asigurat cu adresa şirului constant din memorie 3) char tab [ ] = Acesta este un sir ; În acest caz, avem următoarele corespondenţe : tab indică adresa caracterului A, tab + 1 indică adresa caracterului c, tab [0] indică codul ASCII al caracterului A, tab [1] indică codul ASCII al caracterului c, * tab indică codul ASCII al caracterului A, * (tab + 1) indică codul ASCII al caracterului c O constantă şir de caractere se reprezintă între ghilimele. Ultimul caracter din şir este caracterul nul ( \ ). Limbajul C pune la dispoziţia programatorului o mulţime de funcţii de prelucrare a şirurilor. Câteva dintre cele mai des utilizate le vom prezenta în continuare. Funcţiile declarate în stdio.h pentru prelucrarea şirurilor se referă la operaţii de intrare/ieşire asupra şirurilor, adică citire/afişare şir de caractere. Funcţia char *gets (char *s); citeşte caracterele de intrare până la întâlnirea caracterului Enter, care nu se adaugă în şir (în s); în plus, funcţia plasează \0 la sfârşitul şirului s şi returnează adresa primului caracter din şir; dacă se tastează CTRL/Z returnează NULL; codul lui Enter este scos din bufferul de intrare. Funcţia 11
int puts (char *s); tipăreşte şirul s, trecând cursorul pe rând nou. Funcţia scanf ( %s, s); citeşte caracterele de la tastatură până la întâlnirea primului blanc sau Enter, care nu se adaugă în şir. Această funcţie plasează \0 la sfârşitul şirului s; dacă se tastează CTRL/Z, ea returnează EOF. Codul lui blanc sau Enter rămâne în buffer-ul de intrare. Funcţia printf ( %s, s); tipăreşte şirul s. Funcţiile pentru prelucrarea şirurilor de caractere din biblioteca string.h se referă la compararea a două şiruri, copiere de şiruri, determinarea lungimii şirurilor şi altele. Funcţia unsigned strlen (const char *s); returnează lungimea unui şir de caractere. Lungimea şirului este numărul de caractere proprii, care intră în compunerea şirului respectiv, fără caracterul NUL. Exemple: 1) char *const p = Acesta este un sir ; unsigned n; n = strlen (p); n = 18 şi reprezintă numărul caracterelor proprii din compunerea şirului spre care pointează p. 2) char tab [ ] = Acesta este un sir ; int n; n = strlen (tab); 3) int n; n = strlen ( Acesta este un sir ); 12