start     Articole     Despre mine     Contact     Cursul ABCprog    

Ce sunt pointerii din limbajul C si care e legatura lor cu vectorii

Ai vazut in lectia trecuta ca vectorii ne ofera posibilitatea sa lucram cu variabile generice. Nu e nevoie, adica, sa stim numele variabilei, ci doar indexul ei in vector (adica distanta (exprimata in numar de elemente) de la primul element din vector pana la ea).

Altfel spus, e ca si cum un vector ar fi un bloc de apartamente similare in care fiecare apartament e un element din vector. Putem face operatii generice cu apartamentele fara sa stim altceva despre ele decat numarul lor.

Putem, de exemplu, sa mutam mobila din apartamentul lui Gigel in apartamentul lui Costel cu o instructiune de genul:

    apart[3] = apart[19];

Instructiuni impersonale ca acest apart[x] = apart[y] sunt foarte puternice in programare (unde abstractizarea si generalizarea sunt la putere).

Cum ar fi daca am extinde treaba asta si mai mult?

Cum ar fi daca ai putea sa scrii secvente (sau “bucatele”) de program in care sa faci operatii cu variabile generice oarecare (care sa nu fie, adica, neaparat plasate intr-un vector)?

Vestea buna este ca se poate, asa cum ai vazut in lectia despre pointeri.

Poti, adica, sa scrii o bucata de program care interschimba continuturile a doua cani fara sa le amestece (folosind o a treia cana) astfel:

    cana_extra = cana_1;
    cana_1 = cana_2;
    cana_2 = cana_extra;

Bineinteles, o astfel de secventa de instructiuni va avea ca efect mutarea valorii variabilei cana_2 in variabila cana_1 si mutarea valorii care era la inceput in cana_1 in cana_2.

Ai putea face ca secventa asta sa fie cat mai generica (pentru a o putea reutiliza in program (sau in alte programe)) daca toate canile ar fi plasate intr-un vector (numit, sa zicem, cana) astfel:

    cana_extra = cana[x];
    cana[x] = cana[y];
    cana[y] = cana_extra;

Daca avem grija ca inainte de asta in x si y sa punem indecsii celor doua cani care ne intereseaza, secventa asta de program va putea efectua interschimbarea continuturilor a oricaror doua cani din vector.

Dar pointerii permit o generalizare si mai mare. Cu ajutorul lor putem spune ceva de genul:

    cana_extra = cana de la adresa A1
    cana de la adresa A1 = cana de la adresa A2
    cana de la adresa A2 = cana_extra

Ramane de vazut doar cum putem face urmatoarele doua lucruri:

  1. Cum putem defini o astfel de variabila de tip pointer?
  2. Cum putem pune in ea adresa unei variabile?
  3. Cum putem sa accesam (adica sa citim sau sa scriem valoarea din) locatia de memorie catre care “arata” pointerul?

Sa le luam pe rand…

Definirea si folosirea variabilelor de tip pointer in C


(1) Definirea unui pointer

O variabila de tip int o pot defini in limbajul C asa:

    int x;

Un pointer catre o variabila de tip int (de fapt, catre o locatie de memorie in care se gaseste o valoare de tip int) il definesc astfel:

    int *p;

(2) Aflarea adresei unei variabile

Pun in p adresa lui x astfel:

    p = &x;

(Puteam sa pun si adresa unui element dintr-un vector, daca doream asta. De exemplu, adresa celui de-al doilea element dintr-un vector numit v o pot afla cu &v[1], care este echivalent cu &(v[1]).)

(3) Accesarea memoriei printr-un pointer

Pot acum sa accesez valoarea din locatia de memorie “pointata” de catre pointerul p scriind *p.

Uite un exemplu:

    #include <stdio.h>

    void main() 
    {
	// 4 variabile:
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	// o variabila "extra" (adica "in plus")
	int e;
	// doi pointeri:
	int *p1;
	int *p2;
	
	//initializare 1
	p1 = &a;
	p2 = &b;
	/*
	// initializare 2
	p1 = &c;
	p2 = &d;
	*/
	
	// interschimbare valori
	e = (*p1);
	(*p1) = (*p2);
	(*p2) = e;
	
	printf("a = %d; b = %d \n", a, b);
	printf("c = %d; d = %d \n", c, d);
    }

Daca am schimba in program cele doua instructiuni de sub “initializare 1” cu cele doua instructiuni de sub “initializare 2” (care in program apar ca un comentariu multi-linie), atunci secventa de trei instructiuni de sub “interschimbare valori” ar interschimba continuturile variabilelor c si d (in loc sa interschimbe continuturile variabilelor a si b, asa cum o face programul acum). (Chiar te rog sa experimentezi si varianta asta.)

Am putea zice ca pointerii sunt o generalizare a ideii de vectori — caci pentru a-i putea intelege si utiliza e necesar sa ne imaginam ca memoria calculatorului (adica acel loc in care sunt puse toate variabilele) e ca un fel de vector urias de octeti (adica elemente de memorie in care se pot pune valori ce pot fi reprezentate pe 8 biti).

Legatura dintre pointeri si vectori in C

Deja am tot vorbit in analogiile facute aici de legatura dintre pointeri si vectori. Insa in limbajul C legatura dintre ei e mult mai reala si “palpabila” decat ar putea-o sugera discutiile anterioare.

Mai exact, daca intr-un program am definit un vector numit v:

    int v[10];

atunci numele vectorului, v, este un pointer catre primul element din vector.

Adica v inseamna “adresa primului element din vectorul v” — adica &v[0].

Deci pot accesa elementul v[0] si cu operatorul * de la pointeri, scriind *v. Similar, pot accesa un element de pe o pozitie oarecare din vector cu *(v+indexul_elementului).

De fapt, notatia v[index] e doar o forma mai eleganta de a scrie *(v+index).

Da, asta inseamna si ca in programul anterior in loc de (*p1) puteam sa scriu p1[0]. (Doar ca in acest caz notatia asta parca mai mult ar fi complicat lucrurile in loc sa le simplifice.)

Practic, singura diferenta intre a scrie:

    int v[10];

si:

    int *v;

e ca in primul caz se aloca si memoria pentru elementele vectorului v, in vreme ce in al doilea caz v e doar un pointer “ratacitor” (= care nu s-a asezat la casa lui).

Si mai e o diferenta: In primul caz nu am voie sa scriu ceva de genul:

    v = ...;

Adica v din prima definitie e un pointer “legat” de primul element dintre cele 10 alocate in memorie si nu se poate muta de acolo.

(Vectorul e ca un pointer, doar ca e obligatoriu “fidel” primului sau element.)

Inseamna ca definitia “int v[10];” e oarecum echivalenta cu:

  • aloca in memorie un bloc continuu de 10 elemente de tip int;
  • adresa primului element pune-o in pointerul v;
  • si fa-l pe v constant.

Dar cu matricile cum e in C?

Matricile in limbajul C sunt vectori de vectori, adica… pointeri catre pointeri?!…

Asa ar fi normal, dar… nu chiar!

Sa luam ca exemplu o matrice cu 3 linii si 5 coloane:

    int m[3][5];

Se pare ca m e un vector de 3 elemente (fiecare dintre ele de tip vector de 5 elemente). Adica ceva de genul:

    (int[5]) m[3];

Deci m e un pointer (constant) catre tipul int[5] — adica locatii de memorie de sizeof(int)*5 octeti.

Asta inseamna ca atunci cand in program scriu m[2][1], ceea ce se intampla “in culise” e, de fapt:

    *(m + 2*5 + 1)

adica se acceseaza elementul (de tip int) de la adresa m+2*5+1.

Numai ca m e vazut de program ca un pointer catre pointer catre o variabila de tip int (adica int **), ceea ce inseamna ca pentru a putea accesa astfel direct memoria trebuie sa ii zic sa-l vada pe m ca pe un pointer simplu catre o variabila de tip int (adica int *). Adica daca vreau sa scriu in program asa ceva va trebui sa scriu, de fapt, astfel:

    *((int *)m + 2*5 +1 )

Cred ca desenul urmator e lamuritor:

Este clar acum de ce adresa elementului de pe linia lin si coloana col din matricea m (adica adresa elementului m[lin][col]) este ((int *)m + lin*5 + col)?

Dar daca am avea o matrice tridimensionala in C?

Cum este ea salvata in memorie si cum se realizeaza in realitate accesarea elementelor?

Stii cum arata un cub Rubik, da? E un cub format din 3*3*3 (=27) cubulete mai mici. Am putea reprezenta intr-un program C un astfel de cub printr-o matrice 3x3x3:

    int cub[3][3][3];

Deci cub este un pointer (constant) catre elemente de tipul int[3][3] (adica matrici 2D de 3x3 elemente de tip int).

Tipul de date int[m][n] inseamna in realitate — asa cum am vazut mai devreme — o bucata de memorie formata din m*n bucatele alaturate in care putem pune valori de tip int (adica valori ce ocupa sizeof(int) bucatele indivizibile de memorie (sau “atomi” de memorie”; adica octeti (= 8 biti))).

(Dar compilatorul de C il vede ca pe cub ca fiind de tip int**, asa ca daca vreau sa accesez direct bucata de memorie in care e salvat continutul lui, trebuie sa ii zic compilatorului sa il trateze ca pe un int*.)

Iata cum as putea numerota atat direct, cat si printr-un pointer (vezi comentariile din dreapta instructiunilor), colturile cubului ca in desenul anterior:

    int cub[3][3][3];
    int *p = cub;
    cub[0][0][0] = 1; // *((int*)p + 0*(3*3) + 0*3 + 0) = 1
    cub[0][0][2] = 2; // *((int*)p + 0*(3*3) + 0*3 + 2) = 2
    cub[0][2][0] = 3; // *((int*)p + 0*(3*3) + 2*3 + 0) = 3
    cub[0][2][2] = 4; // *((int*)p + 0*(3*3) + 2*3 + 2) = 4
    cub[2][0][0] = 5; // *((int*)p + 2*(3*3) + 0*3 + 0) = 5
    cub[2][0][2] = 6; // *((int*)p + 2*(3*3) + 0*3 + 2) = 6
    cub[2][2][0] = 7; // *((int*)p + 2*(3*3) + 2*3 + 0) = 7
    cub[2][2][2] = 8; // *((int*)p + 2*(3*3) + 2*3 + 2) = 8

Si iata cum as putea initializa direct o astfel de matrice 3D in C:

Vectori de vectori in C

Daca vreau neaparat, pot construi in C si matrici care sa fie cu adevarat vectori de vectori, asa:

    int * m[3];

In cazul asta ar trebui sa am grija sa aloc manual fiecare linie din matrice (sa zicem, de tip int[5] — ca sa-mi iasa o matrice 3x5) si apoi sa salvez in m[0] adresa primei linii, in m[1] adresa celei de-a doua linii si in m[2] adresa celei de-a treia linii.

Apoi as putea accesa elementul de pe linia 2 si coloana 1 din matricea m scriind in program asa:

    *(*(m+2) + 1)

adica:

    *(m[2] + 1)

sau asa:

    (m[2])[1]

ceea ce e echivalent cu:

    m[2][1]

Vom vedea intr-o lectie ulterioara care e treaba cu alocarea “manuala” a memoriei pentru variabile in C.

Iar in lectia urmatoare din aceasta serie de lectii de programare in C pentru incepatori vom vedea cum ne pot ajuta pointerii sa dam ca parametri functiilor variabile, si nu doar valori.

Cam lunga si obositoare lectia asta, stiu. Dar… vorba aia: “no pain, no gain” (= “nu ai paine, nici gaini“, sau ceva de genul 🙂 ).

“Durerea” pe care poate o simti acum e “febra musculara” a neuronilor. O sa-ti treaca, nu te ingrijora. Neuronii tai or sa-si revina si vor fi si mai puternici decat inainte.

Hai sa incheiem cu o intrebare mult mai simpla decat pare:

Daca intr-un program ai o variabila de tip int numita x, e corect sa scrii in program (*&x)? Oare ce efect ar putea avea treaba asta?…

 

Alatura-te celor peste 7900 de oameni din armata noastra de creiere cu muschi si vei primi testul care iti va spune daca ai sau nu minte de programator.

In plus, vei fi mereu la curent cu tot ce pun la cale.

(Vei primi automat un email in care ti se va solicita acordul de prelucrare a datelor cu caracter personal.)

 

Cu drag,

Florin Bîrleanu





Loading Facebook Comments ...