Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




POINTERI

c


POINTERI

Un pointer este o variabila care are ca valori adrese. Pointerii se folosesc pentru a face referire la date cunoscute prin adresele lor. Astfel, daca p este o variabila de tip pointer care are ca valoare adresa zonei de memorie alocata pentru variabila întreaga x atunci constructia *p reprezinta chiar valoarea variabilei x.

În constructia de mai sus, *p, caracterul * se considera ca fiind un operator unar care furnizeaza valoarea din zona de memorie a carei adresa este continuta în p. Operatorul unar * are aceeasi prioritate ca si ceilalti operatori unari din limbajul C.



Daca p contine adresa zonei de memorie alocata variabilei x, vom spu 626i82g ne ca p pointeaza spre x sau ca p contine adresa lui x.

Pentru a atribui unui pointer adresa unei variabile, putem folosi operatorul unar &. Astfel, daca dorim ca p sa pointeze spre x, putem utiliza constructia:

p = &x;

În limba româna se utilizeaza si alte denumiri pentru notiunea de pointer: referinta, localizator; reper; indicator de adresa.

6.1. DECLARAŢIA DE POINTER

Un pointer se declara ca orice variabila cu deosebirea ca numele pointerului este precedat de caracterul *. Astfel, daca, de exemplu, dorim sa declaram variabila p utilizata anterior pentru a pastra adresa variabilei întregi x, vom folosi declaratia urmatoare:

int *p;

Tipul int stabileste în acest caz faptul ca p contine adrese de zone de memorie alocate datelor de tip int. Declaratia lui p se poate interpreta în felul urmator: *p reprezinta continutul zonei de memorie spre care pointeaza p, iar acest continut are tipul int.

În general, un pointer se declara prin:

tip *nume;

ceea ce înseamna ca nume este un pointer care pointeaza spre o zona de memorie ce contine o data de tipul tip.

Comparând declaratia de pointer anterioara cu una obisnuita:

tip nume;

putem considera ca:

tip *

dintr-o declaratie de pointer reprezinta tip dintr-o declaratie obisnuita. De aceea, constructia

tip *

se spune ca reprezinta un tip nou, tipul pointer.

Exista cazuri în care dorim ca un pointer sa fie utilizat cu mai multe tipuri de date. În acest caz, la declararea lui nu dorim sa specificam un tip anume. Aceasta se realizeaza folosind cuvântul cheie void:

void *nume;

Exemple:

void main (void)

2) functia permutare de mai jos realizeaza transferul parametrilor prin adresa:

void permutare (int *x, int *y) // x si y sunt pointeri

Apelul functiei permutare se face astfel:

permutare (&a, &b);

pentru a schimba valorile lui a cu b.

6.2. LEGĂTURA DINTRE POINTERI sI TABLOURI

Numele unui tablou este un pointer deoarece el are ca valoare adresa primului sau element. Totusi exista o diferenta între numele unui tablou si o variabila de tip pointer, si anume unui nume de tablou nu i se poate atribui alta adresa. Deci numele unui tablou trebuie considerat ca fiind un pointer constant.

Daca x este un parametru formal ce corespunde unui parametru efectiv care este un nume de tablou, x poate fi declarat fie ca tablou fie ca pointer spre tipul tabloului.

Exemplu:

Fie functia cu antetul urmator:

unsigned lungime (char x[ ]);

Sa presupunem ca aceasta functie determina lungimea unui sir de caractere si se poate apela prin:

l=lungime(tablou);

unde tablou este de tip caracter.

Antetul functiei lungime poate fi schimbat în felul urmator:

unsigned lungime (char *x);

Cele doua declaratii sunt identice deoarece declaratia:

char x[ ];

defineste pe x ca numele unui tablou de tip caracter; dar atunci el este un pointer spre caractere deci se poate declara prin:

char *x;

6.3. OPERAŢII CU POINTERI

Asupra pointerilor se pot face diferite operatii. Deoarece ei contin adrese atunci operatiile se realizeaza cu adrese.

6.3.1. Incrementare si decrementare

Operatorii de incrementare si decrementare se pot aplica variabilelor de tip pointer.

Efectul:

operatorul de incrementare aplicat asupra unui operand de tip pointer spre tipul tip mareste adresa continuta de operand cu numarul de octeti necesari pentru a pastra o data de tipul tip.

operatorul de decrementare se executa în mod analog, cu singura diferenta ca în loc sa se mareasca adresa, ea se micsoreaza cu numarul corespunzator de octeti.

De obicei decrementarile si incrementarile adreselor sunt mai rapide ca executie când se au în vedere prelucrari de tablouri.

Exemplu:

int tab[10];

int *p;

int i=0;

p=&tab[i];

p++; // p contine adresa lui tab[1]

// cu p se pot face referiri la orice element de tablou

6.3.2. Adunarea si scaderea unui întreg dintr-un pointer

Daca p este un pointer, sunt corecte expresiile de forma:

p+n si p-n

unde n este de tip întreg.

Efectul:

expresia p+n mareste valoarea lui p cu n*nr_tip, unde nr_tip este numarul de octeti necesari pentru a memora o data de tipul tip spre care pointeaza p;

analog expresia p-n micsoreaza valoarea lui p cu n*nr_tip.

Daca x este un tablou de tipul tip, atunci x este pointer, deci o expresie de forma:

x+n;

este corecta si deoarece x este un pointer spre primul sau element x[0], x+n va fi un pointer spre elementul x[n]. Rezulta ca valoarea elementului x[n] se poate reprezenta prin expresia:

*(x+n);

Astfel variabilele cu indici se pot înlocui prin expresii cu pointeri. Aceasta permite ca la tratarea tablourilor sa se foloseasca expresii cu pointeri în locul variabilelor cu indici. Versiunile cu pointeri sunt de obicei optime în raport cu cele realizate prin intermediul indicilor.

6.3.3. Compararea a doi pointeri

Doi pointeri care pointeaza spre elementele aceluiasi tablou pot fi comparati folosind operatorii de relatie si de egalitate. Astfel, daca p si q sunt doi pointeri care pointeaza spre elementele tab[i], respectiv tab[j] ale tabloului tab, expresiile urmatoare au sens:

p<q p!=j p= =q.

Observatii:

1o. Pointerii nu pot fi comparati decât în conditiile amintite mai sus (deci daca sunt pointeri spre elementele aceluiasi tablou).

2o. Operatorii = = si != permit compararea unui pointer si cu o constanta simbolica speciala având numele NULL. Aceste comparatii permit sa stabilim daca un pointer contine o adresa sau nu. Astfel, daca expresia:

p= = NULL

este adevarata, p nu contine o adresa. Daca expresia respectiva are valoarea fals atunci p contine o adresa. Constanta simbolica NULL este definita în fisierul stdio.h

6.3.4. Diferenta a doi pointeri

Doi pointeri care pointeaza spre elementele aceluiasi tablou pot fi scazuti. Rezultatul diferentei a doi pointeri este definit astfel: fie t un tablou de un tip oarecare si p si q doi pointeri, p contine adresa elementului t[i] iar q contine adresa elementului t[i+n]. Atunci diferenta q-p are valoarea n.

6.3.5. Exemple

Vom da câteva functii asupra sirurilor de caractere:

functia lungime

unsigned lungime (char*x)

functia copiaza

void copiaza(char *x, char *y) // copiaza din zona de adresa y

// in zona de adresa x

functia concateneaza

void concateneaza (char *x, char *y)

// concateneaza sirul de adresa y la sfarsitul sirului

// de adresa x

functia compara

int compara (char *x, char *y)

6.4. ALOCAREA DINAMICĂ A MEMORIEI

Biblioteca standard a limbajului C pune la dispozitia utilizatorului functii care permit alocarea de zone de memorie în timpul executiei programului. O astfel de zona de memorie poate fi utilizata pentru a pastra date temporare. Zona respectiva poate fi eliberata în momentul în care nu mai sunt necesare datele care au fost pastrate în ea. Alocarea de zone de memorie si eliberarea lor în timpul executiei programelor permite gestionarea optima a memoriei de catre programator. Un astfel de mijloc de gestionare a memoriei îl vom numi alocare dinamica a memoriei.

Vom indica doua functii din bibloteca limbajului C utilizate frecvent în alocarea dinamica a memoriei. Prototipurile lor se afla în fisierele standard alloc.h si stdlib.h, deci pentru a le utiliza vom include unul din aceste fisiere.

Functia malloc permite alocarea unui bloc de memorie a carui dimensiune se specifica în octeti. Functia returneaza un pointer spre începutul zonei alocate. Întrucât acest pointer trebuie sa permita memorarea oricarui tip de data în zona alocata, el este de tip void *.

Prototipul functiei este:

void *malloc (unsigned n);

unde n este numarul de octeti al zonei de memorie care se aloca. În cazul în care n este prea mare, functia returneaza pointerul NULL.

Functia free elibereaza o zona de memorie alocata prin malloc. Prototipul ei este:

void free (void *p);

unde p este pointerul returnat de malloc la alocare, deci este pointerul spre începutul zonei care se elibereaza.

Exemplu:

Functia memchar memoreaza un sir de caractere într-o zona de memorie alocata prin functia malloc. Ea returneaza adresa de început a zonei în care s-a salvat sirul de caractere, deci returneaza un pointer spre tipul char.

#include <stdio.h>

#include <alloc.h>

#include <string.h>

char *memchar (char *s)

else

return NULL;

Observatii:

1o. În fisierul stdio.h exista definitia constantei NULL.

2o. Fisierul alloc.h s-a inclus deoarece contine prototipul functiei malloc.

3o. Fisierul string.h contine prototipurile functiilor strlen si strcpy.

4o. Functia malloc se apeleaza pentru a rezerva strlen(s)+1 octeti; strlen returneaza numarul de octeti cuplati de caracterele proprii ale lui s (fara caracterul NULL). Cum în zona de memorie rezervata prin malloc se pastreaza si caracterul NULL, lungimea returnata de functia strlen s-a marit cu 1.

5o. Pointerul returnat de malloc a fost convertit spre char *, deoarece el este de tip void *. Acest pointer se atribuie lui p, deci p pointeaza spre începutul zonei de memorie alocate prin apelul functiei malloc. Se testeaza daca acest pointer este diferit de NULL (deci daca s-a putut aloca memoria de dimensiunea ceruta). În caz afirmativ, se transfera sirul prin apelul functiei strcpy, returnându-se apoi valoarea pointerului p.

6.5. POINTERI SPRE FUNCŢII

Numele unei functii este un pointer spre functia respectiva. El poate fi folosit ca parametru efectiv la apeluri de functii. În felul acesta, o functie poate transfera functiei apelate un pointer spre o functie. Aceasta, la rândul ei, poate apela functia care i-a fost transferata în acest fel.

Exemplu:

Un exemplu matematic în care este nevoie de un astfel de transfer este cel cu privire la calculul aproximativ al integralelor definite. Sa presupunem ca dorim sa calculam integrala definita din functia f(x), între limitele a si b, folosind formula trapezului:

I= h((f(a)+f(b))/2 +f(a+h)+f(a+2h)+. . . +f(a+(n-1)h)

unde

h=(b-a)/h.

În continuare construim o functie care calculeaza partea dreapta a acestei relatii. Numim aria_trapez aceasta functie.

Observatii:

1o. Deoarece functia f(x) din relatia de mai sus nu este definita în acest moment, ea trebuie sa figureze printre parametrii functiei aria_trapez, împreuna cu limitele de integrare si valoarea lui n.

2o. Functia aria_trapez returneaza valoarea aproximativa a integralei si ea se va apela printr-o expresie de atribuire, de exemplu:

aria=aria_trapez (a, b, n, f);

3o. Functia aria_trapez returneaza o valoare flotanta în dubla precizie. De asemenea, si functia f(x) returneaza o valoare flotanta în dubla precizie. De aici rezulta ca prototipul functiei aria_trapez este urmatorul:

double aria_trapez (double a, double b, int n, double (*f)());

sau

double aria_trapez (double, double, int , double (*)());

4o. Este necesar ca înaintea apelului functiei aria_trapez functia f(x) sa fie definita sau sa fie prezent prototipul ei , de exemplu:

double f();

5o. Constructia double (*f) () se interpreteaza în felul urmator:

- *f înseamna ca f este un pointer;

- (*f)() înseamna ca f este un pointer spre o functie;

- double (*f) () înseamna ca f este un pointer spre o functie care returneaza o valoare flotanta în dubla precizie.

6o. Trebuie sa se includa *f între paranteze, deoarece constructia double *f(); este corecta, dar înseamna altceva, parantezele rotunde fiind prioritare operatorului unar *. În acest caz, se declara f ca o functie ce returneaza un pointer spre o valoare flotanta în dubla precizie.

7o. Ultimul parametru formal al functiei aria_trapez corespunde parametrului efectiv f si deci el trebuie declarat ca si pointer spre o functie ce returneaza o valoare flotanta în dubla precizie. Conform observatiei 5), daca p este numele parametrului formal ce corespunde parametrului efectiv f, atunci p se declara astfel:

double (*p)();

8o. În corpul functiei aria_trapez va trebui sa apelam functia f(x) pentru a calcula valorile:

f(a), f(b), f(a+h), . . . , f(a+(n-1)h).

În momentul programarii functiei aria_trapez, nu se cunoaste numele functiei concrete, ci numai pointerul p spre ea. De aceea, vom înlocui numele functiei prin *p, deci vom folosi apelurile:

(*p)(a), (*p)(b), (*p)(a+h), . . . ,(*p)(a+(n-1)h)

double aria_trapez(double x, double y, int m, double(*p)());

Vom utiliza functia aria_trapez pentru a calcula integrala definita din functia sin(x2) pe intervalul [0,1], cu o eroare mai mica decât 10-8. Vom nota cu In urmatoare suma:

In= h((f(a)+f(b))/2 +f(a+h)+f(a+2h)+. . . +f(a+(n-1)h)

Pasii algoritmului sunt urmatorii:

Pasul 1. Se alege o valoare initiala pentru n, de exemplu 10.

Pasul 2. Se calculeaza In.

Pasul 3. Se calculeaza I2n prin dublarea lui n.

Pasul 4. Daca |In-I2n| < 10-8, algoritmul se întrerupe si valoarea integralei, cu precizia admisa, este I2n; altfel se dubleaza n, se pune In=I2n; n, si se trece la pasul 3.

#define A 0.0

#define B 1.0

#define N 10

#define EPS 1e-8

#include <stdio.h>

#include <math.h>

double sinxp(double); // prototipul functiei sin(x*x)

double aria_trapez(double, double, int, double (*)());

void main (void) // functia principala

while (vabs >= EPS);

printf ("valoarea integralei este : %g.10\n",i2n);

double aria_trapez(double x, double y, int m, double(*p)());

double sinxp (double x)

6.6. TRATAREA PARAMETRILOR DIN LINIA DE COMANDĂ

În linia de comanda folosita la apelul executiei unui program se pot utiliza diferiti parametri. Acesti parametri pot fi utilizati folosind parametrii argc si argv ai functiei principale.

Parametrul argc este de tip întreg si indica numarul de parametri din linia de comanda.

Parametrul argv este un tablou de pointeri spre zonele în care sunt pastrati parametrii liniei de comanda. Acestia se considera siruri de caractere.

Astfel antetul functiei principale va fi :

main (int argc, char *argv[ ])

Exemplu:

Consideram ca la lansarea programului prog s-au furnizat parametrii:

MARTIE 1956

În acest caz argc=4, iar tabloul argv contine pointerii:

- argv[0] - pointer spre numele programului (calea, numele si extensia .EXE

- argv[1] - pointer spre sirul "31";

- argv[2] - pointer spre sirul "MARTIE";

- argv[3] - pointer spre sirul "1991".

Observatii:

1o. Lansarea unui program se face cu prima instructiune a functiei principale. Deci parametrii argc si argv au deja în acest moment valorile indicate mai sus, putând fi analizati chiar cu prima instructiune executabila.

2o. În mod frecvent, acesti parametrii reprezinta diferite optiuni ale programului, date calendaristice, nume de fisiere, etc.

3o. argv[0] este întotdeauna pointerul spre numele fisierului cu imaginea executabila a programului.

void main ( int argc, char *argv[]) // va afisa parametrii din linia de comanda

6.7. MODIFICATORUL const

Am vazut anterior ca o constanta se defineste prin caracterele care intra în compunerea ei. De asemenea, în acelasi capitol s-a aratat ca putem atribui un nume unei constante printr-o constructie #define. Un astfel de nume se spune ca este o constanta simbolica si el se substituie prin sirul de caractere care si corespunde, în faza de preprocesare.

Un alt mod de a defini o constanta este acela de a folosi modificatorul const într-o declaratie. Printr-o astfel de declaratie, unui nume i se poate atribui o valoare constanta. În acest caz, numele respectiv nu mai este tratat de preprocesor si el poate fi folosit în program în mod analog cu numele variabilelor. Unui astfel de nume declarat cu ajutorul modificatorului const nu i se poate schimba valoarea printr-o expresie de atribuire, ca si unei variabile obisnuite.

Formatele declaratiei cu modificatorul const sunt urmatoarele:

tip const nume = valoare;

const tip nume = valoare;

tip const nume;

const tip nume;

const nume = valoare;

const nume;

Exemplu:

void main (void)

Modificatorul const se foloseste frecvent la declararea parametrilor formali de tip pointer. O astfel de declaratie are formatul:

const tip *nume_parametru_formal;

Un parametru formal declarat prin constructia :

tip *nume_parametru_formal;

corespunde unui parametru efectiv a carui valoare este o adresa. La apel, valoarea parametrului formal devine egala cu aceasta adresa. Datorita acestui fapt, functia apelata poate sa modifice data aflata la adresa respectiva. Daca se foloseste modificatorul const utilizat la declararea unui astfel de parametru formal atunci se interzice functiei apelate sa modifice data de la adresa receptionata la apel de catre parametrul formal corespunzator. Acest mecanism este folosit frecvent în cazul functiilor de tratare a sirurilor de caractere.

De exemplu functia strlen din biblioteca standard a limbajului C are prototipul:

unsigned strlen (const char *s);

Ea se apeleaza prin expresii de atribuire de forma:

i=strlen(x);

unde x este un pointer spre o zona de memorie în care se afla un sir de caractere.

Functia strlen determina lungimea sirului aflat la adresa receptionata de catre parametrul s. Ea nu are voie sa modifice sirul respectiv si din aceasta cauza parametrul s se declara folosind modificatorul const.

6.8. STIVA

Prin stiva (stack în engleza) întelegem o multime ordonata de elemente la care accesul se realizeaza conform principiului ultimul venit primul servit. În engleza stiva se mai numeste si lista LIFO (Last In First Out).

O modalitate simpla de a implementa o stiva este pastrarea elementelor ei într-un tablou unidimensional. În acest tablou se vor pastra elementele stivei unul dupa altul. De asemenea, ele se pot scoate din tablou în ordinea inversa pastrarii lor. La un moment dat se poate scoate ultimul element pus pe stiva si numai acesta.

Despre ultinul element pus în stiva se spune ca este vârful stivei, iar despre primul element ca este baza stivei.

Accesul este pemis doar la vârful stivei:

un element se poate pune pe stiva numai dupa elementul aflat în vârful stivei si dupa aceasta operatie el ajunge vârful stivei;

se poate scoate de pe stiva numai elementul aflat în vârful stivei si dupa aceasta operatie în vârful stivei ramâne elementul care a fost pus pe stiva înaintea lui.

Vom numi stack tablou de tip int afectat stivei si next variabila care indica prima pozitie libera din stiva. Deci stack[0] este baza stivei iar stack[n] va fi vârful stivei. Vom defini mai multe functii asociate tabloului stack:

- push functia care pune un element în stiva;

- pop functia care scoate un element din stiva;

- clear functia de initializare a stivei; dupa apelul ei stiva devine vida;

#define MAX 1000

static int stack[1000];

static next = 0; // indicele pentru baza stivei

void push(int x) // pune pe stiva valoarea lui x

int pop() // scoate elementul din varful stivei si returneaza valoarea lui

void clear(void) // videaza stiva



Document Info


Accesari: 1093
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2025 )