Am cam ramas in urma cu lectiile din curs. Azi mi-am facut putin timp sa fac o scurta introducere in pointeri. Veti vedea ca este intr-adevar o introducere scurta. Nu am avut suficient timp (si nici dispozitia necesara) sa explic toate aplicatiile pointerilor dar e suficient sa va prindeti despre ce si cum fac pointerii. Deci, here it goes:
Unul termen foarte greu de inteles pentru incepatorii in C este pointerul. Ce este
un pointer ? La ce este bun ? De ce sa folosim pointeri cand tot ce fac e sa ne
ingreuneze munca ? Incerc sa explic chestia asta mai jos.
Incepem cu prima intrebare, si anume "Ce este un pointer ?"
In lectiile anterioare ati vazut ca exista diferite tipuri de date in C. Exista int
si long pentru numere intregi, char pentru caractere, float si double pentru numere
reale. Pointerul este un tip nou de data dar care spre deosebire de celelalte tipuri
memoreaza doar adrese.
Ce inseamna asta ? Ca sa intelegeti ce inseamna trebuie sa va familiarizati cu cativa
termeni intalniti in programare. Cand concepeti un program folositi, bineinteles,
variabile. La definirea unei variabile stiti ca e important sa cunoasteti doua
lucruri: tipul si numele acesteia. In timpul rularii unui program apare un nou termen
de care trebuie tinut cont si anume adresa variabilei. Un pointer este o variabila
care tine minte acest al treilea termen, adica adresa unei variabile.
Cum se declara un pointer ?
Inainte de a folosi un pointer trebuie sa stim spre ce fel de resursa va pointa, adica
sa stim ce tip de variabila se gaseste la adresa care o memoreaza. In functie de tipul
de variabila care se gaseste la adresa respectiva, se declara pointerul. Adica, daca
avem nevoie, de exemplu, de un pointer care sa pointeze catre o variabila de tip int
il vom declara in felul urmator:
int *pInt;
Dupa cum vedeti un pointer se declara ca si o variabila normala, in cazul nostru de tip
int, cu diferenta ca numele variabilei are un asterisc ( * ) in fata ceea ce ne spune
ca este un pointer, adica este o variabila care pastreaza o adresa a unei variabile de
tip int, nu este o variabila de tip int. Ca o conventie de nume majoritatea
programatoril prefera ca atunci cand se creaza un pointer sa i se dea un nume care incepe
cu litera p (de la pointer). Astfel de fiecare data cand folositi variabila respectiva
va amintit ca e pointer, nu o variabila normala.
Prin analogie va puteti da seama cum se declara pointeri pentru alte tipuri de date:
char *pCh; // un pointer catre un caracter
int *float; // un pointer catre un numar intreg
Ok. E usor de declarat un pointer, dar cum ii spun sa memoreze o adresa ?
Ei, daca tot am declarat un pointer e bine sa si memoreze ceva si ca sa fie util trebuie
sa memoreze o adresa. De fapt, asta e primul lucru care trebuie sa-l faceti dupa ce ati
declarat un pointer: sa-i dati o valoare, sa-l faceti sa pointeze spre ceva.
Pentru a-i spune unui pointer adresa care trebuie sa o memoreze se foloseste semnul egal
ca in atribuirile normale cu diferenta ca inaintea variabilei a carei adrese trebuie
memorate se pune semnul '&' care ii spune compilatorului ca acolo NU valoarea variabilei
trebuie data ca rvalue, ci adresa acesteia.
OK, am invatat sa asociem o adresa trebuie sa vedem si cum se poate citi (afisa)
valoarea care se gaseste la adresa respectiva. Asta se face ca si cum s-ar citi o
valoare normala doar ca numele pointerului este prefixat de un asterisc ( * ). Asta spune
compilatorului sa NU se citeasca/afiseze continutul pointerului si ceea ce se gaseste
la adresa care este tinuta in pointer.
Sa vedem si un exemplu concret care va ajuta sa intelegeti mai bine functionarea
pointerilor:
#include <stdio.h>
int main(){
int i; // o variabila de tip int
int *pInt // un pointer catre un int
pInt = &i; // pInt citeste adresa lui i
// afisam valoarea lui i si valoarea spre care pointeaza
// pointerul pInt
printf("n%d - %d\n",i,*pInt);
}
Rezultatul ar trebui sa fie: 10 - 10
Primul 10 este valoarea atribuita variabilei i. Al doilea 10 este valoarea care se gaseste
la adresa care este memorata de pointerul pInt. Cum pInt memoreaza adresa lui i, *pInt
va fi 10 adica valoarea lui i.
Haideti sa ne mai jucam putin cu pointerii. Incercati urmatorul program:
#include <stdio.h>
int main(){
int i, j;
int *pInt;
i = 5;
j = 2;
pInt = &i;
printf("*pInt are valoarea %d\n",*pInt);
pInt = &j;
printf("*pInt are valoarea %d\n",*pInt);
}
Dupa ce compilati si rulati codul de mai sus va trebui sa vedeti doua linii. Pe prima
linie ne spune ca *pInt are valoarea 5 iar pe cea de-a doua linie afisata ne spune
ca *pInt are valoarea 2.
De ce ? Simplu... i ia valoarea 5 si j ia valoarea 2. Inainte de primul printf() pInt
memoreaza adresa variabilei i, adica *pInt va afisa valoarea variabilei i care este 5.
Inainte de al doilea printf() pInt memoreaza adresa variabilei j iar la afisare pe
ecran apare 2, adica valoarea variabilei j la care pointeaza acum pInt.
Haideti sa intram la chestii ceva mai avansate cum ar fi folosirea pointerilor in
lucrul cu sirurile de caractere. Daca lucrati cu un singur caracter, utilizarea
pointerilor nu se deosebeste de utilizarea lor in numerele intregi. In schimb daca
folositi siruri de caractere apare o functionalitate in plus si anume posibilitatea
de a incrementa sau decrementa un pointer.
Sa vedem un exemplu:
int main(){
int i;
char sir[] = "abcdefghi";
char *pChar;
pChar = sir;
for( i = 0 ; i < strlen(sir) ; i++ )
printf("%c",*(pChar + i));
}
Codul de mai sus declara variabila sir si o initializeaza cu sirul de caractere
"abcdefghi". Mai jos declaram un pointer, pChar, care memoreaza date de tip char.
Mai jos punem in pChar adresa sirului de caractere declarat mai sus ( sir ).
Bun, avem un sir de caractere, un pointer catre un sir de caractere... now what ?
Afisam sirul de caractere afisand fiecare caracter in parte. Probabil va puneti
intrebarea "Cum sa afisam un sir daca pChar pointeaza spre un caracter ?". Ei bine,
intr-adevar pointerul pointeaza spre un caracter dar in cazul sirurilor de caractere,
care in memorie sunt reprezentate ca un sir lung de adrese consecutive, pointerul
pointeaza catre primul caracter din acel sir. Adica, pChar = &sir s-ar traduce ca
"Memoreaza primul caracter din sirul sir." Dupa cum am spus mai sus, toate caracterele
sunt reprezentate ca zone consecutive din memorie iar pointerul il putem incrementa
pentru a afisa continutul de la fiecare zona in parte.
In cazul de fata am preferat sa nu incrementez pointerul si sa-i adaug un numar. Dupa
cum vedeti am folosit o bucla for in care i este initializat cu valoarea 0 iar conditia
de oprire e ca i sa fie mai mic decat lungimea sirului de caractere.
La fiecare iteratie se va afisa caracterul care se gaseste la adresa pointata de pChar
la care se adauga valoarea lui i. Ce inseamna asta ? Se va afisa din sir "a i-a"
valoare. La prima trecere prin bucla i este 0, deci valoarea afisata va fi pChar + 0,
adica va afisa primul caracter din sir pentru ca, dupa cum am spus mai sus, cand unui
pointer ii atribuim adresa unui sir de fapt ii dam adresa primului caracter din sir.
La a doua iteratie se va afisa valoarea de la adresa pChar + 1, adica de la inceputul
sirului se "merge" distanta de 1 caracter si este afisat. La fel si mai departe.
Acelasi lucru putea fi scris si de forma:
int main(){
int i;
char sir[] = "abcdefghi";
char *pChar;
pChar = sir;
for( i = 0 ; i < strlen(sir) ; i++ ){
printf("%c",*pChar++);
}
}
Codul de mai sus si codul anterior sunt echivalente cu diferenta ca nu se apeleaza
pChar + deplasament cum era in primul caz ci se afiseaza doar pChar care este
incrementat dupa fiecare afisare. Observati ca am folosit operatorul de incrementare
dupa valoarea variabilei, adica mai intai va fi afisat *pChar si apoi va fi incrementat.
Cam asta ar fi ideea de utilizare a pointerilor. Bineinteles, pointerii sunt mult mai
complecsi decat am prezentat eu aici. In randurile de mai sus am facut doar o prezentare
scurta a ceea ce se numeste pointer.
Pentru a va obisnui cu pointerii pun mai jos inca un exemplu care e util sa il studiati
si sa il intelegeti:
Transmiterea unui pointer ca parametru intr-o functie:
#include <stdio.h>
void afiseaza(int *pInt);
int main(){
int i;
i = 5;
afiseaza(&i);
}
void afiseaza(int *pInt){
printf("%d",*pInt);
}
In codul de mai sus se poate vedea cum transmitem ca parametru functiei afiseaza() o adresa
in locul unei valori. Cazul de fata este doar exemplificativ si nu are o valoare practica
deosebita dar in cadrul unui program complex este o tehnica utila dintr-un motiv simplu:
daca valoarea transmisa este foarte mare ( ex. o structura de 10MB ) nu este deloc util
sa apelam functia dand ca parametru variabila. In schimb putem transmite doar adresa
variabilei care urmeaza a fi prelucrate astfel evitand incarcarea memoriei.
Daca aveti nelamuriri, astept sa le postati aici
