Documente Academic
Documente Profesional
Documente Cultură
Tabela 7.1:: Obiect - PTR
Tabela 7.1:: Obiect - PTR
Introducere
Pointerii sunt variabile care contin adresa de memorie a unei alte variabile. Din aceste considerente,
pointerii se numesc si variabile de adresa.
Presupunem ca avem o variabila de tip întreg numita entitate localizata la adresa de memorie 0x1000
(adresele sunt automat asigante variabilelor de catre compilator). Daca dorim sa referim aceasta
variabila prin intermediul unui pointer entitate_ptr, atunci valoarea pointerului va fi 0x1000
(entitate_ptr = 0x1000), astfel spunem ca entitate_ptr "arata" spre variabila entitate (Pentru a se evita
confuziile, se recomanda ca numele pointerilor sa aiba sufixul _ptr).
Pentru a întelege mai bine mecanismul de functionare a pointerilor, introducem o analogie pointeri -
adrese postale, în care adresa de memorie este adresa postala, numele variabilei pointer este numele
cladirii, iar entitatea referita este cladirea propriu-zisa. Aceasta analogie este prezentata în tabelul 7.1.
Se observa ca doi pointeri diferiti (Fac_AC si Fac_ETC) au aceeasi valoare, indicând spre aceeasi
locatie, astfel referind aceeasi entitate.
Operatii cu pointeri
Operatorii specifici pointerilor sunt: operatorul de dereferentiere * (pentru un pointer, returneaza entitatea
referita de pointer) si operatorul de adresa & (pentru o entitate, returneaza adresa entitatii). Exemplul urmator
explica folosirea acestor operatori. Variabila pointer obiect_ptr primeste adresa variabilei obiect, astfel
obiect_ptr va indica spre zona de memorie unde este memorata variabila obiect. În ultima linie, se stocheaza
valoarea 5 la zona de memorie spre care arata obiect_ptr, adica în variabila obiect.
obiect=4;
obiect_ptr=&obiect; /* obiect_ptr va indica
spre obiect */
printf("%d\n", *obiect_ptr); /* afiseaza valoarea
spre care indica
obiect_ptr */
*obiect_ptr=5; /* atribuie lui obiect
valoarea 5 */
Folosirea gresita a operatorilor poate duce la functionari incorecte a programului sau, în cazul fericit, la erori
semnalate de compilator:
*variabila
1
este ilegal, prin aceasta se solicita entitatea referita de o variabila care nu este un pointer;
&variabila_ptr
este legal, dar "neobisnuit", se solicita adresa lui variabila_ptr, adica un pointer la un pointer.
int obiect;
int *obiect_ptr1;
int *obiect_ptr2;
obiect_ptr1 = &obiect;
obiect_ptr2 = obiect_ptr1;
/* obiect_ptr1 si obiect_ptr2 vor indica spre
aceasi locatie de memorie */
printf("%d %d\n", *obiect_ptr1, *obiect_ptr2);
Un pointer special definit în limbajul C este pointerul NULL, care nu indica spre nimic (adresa spre care arata
acest pointer este 0). Acest pointer este definit în stdio.h si în stdlib.h ca fiind (void *) 0, adica un pointer spre o
locatie de tip void si care arata spre locatia de memorie 0.
O situatie în care pointerii sunt utili este declararea structurilor recursive. În C nu se permite ca o structura sa
contina câmpuri de tipul aceleiasi structuri. Aceasta restrictie poate fi evitata folosind pointeri la structuri, dupa
cum se arata în exemplul urmator:
persoana *p;
Pentru a referi un câmp folosind un pointer spre o structura, se foloseste operatorul de dereferentiere: (*p).nume.
Echivalent cu acest operator exista în C un alt operator pentru accesarea câmpurilor din structurile indicate de
pointeri: p- nume.
În C, transmiterea parametrilor la functii se face "prin valoare", singurul rezultat vizibil în exterior
fiind valoarea pe care o returneaza functia. Pentru ca o functie sa poata returna mai multe valori sau
pentru ca modificarile asupra parametrilor sa se propage în exteriorul functiei, se folosesc pointeri.
Acest concept este ilustrat de urmatoarea analogie: Presupunem ca se întâlnesc doua persoane A si B,
A poate doar sa vorbeasca, iar B poate doar sa asculte. Cum poate B sa îi transmita informatii lui A ?
Simplu: A îi spune: "Lasa raspunsul tau în cutia postala numarul x". Astfel, în momentul când se
transmite un pointer catre o functie, functiei i se "comunica" adresa variabilei transmise ca parametru,
putând astfel face modificari asupra acelei variabile.
În exemplul urmator se arata cum functia inc_contor va modifica valoarea variabilei contor.
#include <stdio.h>
2
void inc_contor(int *contor_ptr)
{
(*contor_ptr)++;
}
void main(void)
{
int contor = 0;
while (contor<10)
inc_contor(&contor);
printf("contor: %d\n",contor);
}
Din functia main se transmite spre functia inc_contor nu variabila care trebuie incrementata, ci adresa
sa. Functia inc_contor primeste ca parametru nu un întreg, ci un pointer la un întreg. Astfel, cunoscând
adresa variabilei contor, functia poate modifica valoarea acelei variabile, referind-o indirect prin
intermediul pointerului.
Pointeri si tablouri
În C, exista o strânsa legatura între pointeri si tablouri. Când se foloseste numele unui tablou (fara
index), se genereaza un pointer catre primul element al tabloului. Astfel, în C se pot transmite tablouri
ca parametri în functii (de fapt, se transmite adresa de început a tabloului). Alt avantaj al tratarii
unitare pointer-tablou este aplicarea aritmeticii pointerilor pentru accesarea elementelor unui tablou.
Identitate pointer-tablou se reflecta cel mai bine în operatiile cu siruri de caractere, unde sirul de
caractere este un tablou de caractere sau un pointer la caracter.
Prezentam în cele ce urmeaza unele aspecte ale relatiei dintre pointeri si tablouri.
int tablou[5];
int *tablou_ptr;
tablou_ptr=&tablou[0];
Dupa secventa de mai sus, pointerul tablou_ptr va indica spre primul element al tabloului, deci spre
începutul zonei de memorie unde este stocat tabloul. Deoarece variabila tablou este vazuta în C ca un
pointer, acelasi efect s-ar fi obtinut prin atribuirea: tablou_ptr=tablou;.
Atentie! *(tablou_ptr++) va returna valoarea lui tablou[0], dupa care se va incrementa valoarea lui
tablou_ptr, aratând spre tablou[1]. În schimb, (*tablou_ptr)++ va incrementa valoarea spre care arata
tablou_ptr, returnând astfel tablou[0]++.
#include <stdio.h>
void main(void)
{
int t[4]={0, 1, 2, 3};
3
int *p=&t[1];
Pointeri la functii
Limbajul C permite nu doar referirea unor date, ci si referirea functiilor (pointeri la functii). Aceasta
nu difera mult de referirea datelor, un pointer la o functie retinând adresa de memorie unde începe
rutina care implementeaza functia.
Astfel, daca avem implementata o functie f1 si un pointer spre functie f1_ptr, secventa urmatoare va
arata cum se poate realiza prin intermediul unui pointer apelul unei functii:
#include <stdio.h>
void f1(int a)
{
printf("%d\n",a);
}
void main(void)
{
void (*f1_ptr)(int);
În C, deoarece limbajul ofera o mare libertate de exprimare, este foarte usor pentru un programator
neatent sa faca erori care nu sunt semnalate la compilare, dar care duc la comportari neasteptate ale
programului.
4
int *un_pointer;
*un_pointer=5;
În aceasta situatie, deoarece un_pointer este neinitializat, valoarea 5 este scrisa în memorie la o locatie
aleatoare, poate chiar rezervata altei variabile. În cazul în care un pointer nu arata spre o zona de
memorie rezervata, trebuie alocata o zona de memorie în mod explicit, folosind functia malloc:
un_pointer=(int *)malloc(sizeof(int));
Functia malloc rezerva spatiu de memorie si returneaza adresa spatiului rezervat; zona de memorie
referita de un pointer poate fi eliberata folosind functia free. Ambele functii sunt definite în stdlib.h.
O alta greseala des întâlnita este referirea pointerilor spre alte locatii de memorie:
int obiect=5;
int *obiect_ptr;
obiect_ptr = obiect;
Lipsa operatorului de adresa & face ca obiect_ptr sa indice spre locatia de memorie aflata la adresa 5.
Urmatorul exemplu ilustreaza un stil de programare "daunator", care strica lizibilitatea codului.
Limbajul C este foarte flexibil, fapt ce poate duce, în special în cazul pointerilor, la rezultate
imprevizibile:
Problema rezolvata
Sa se scrie un program care gestioneaza date despre un grup de studenti. Pentru fiecare student se
memoreaza numele si numarul sau matricol. Programul trebuie sa implementeze urmatoarele operatii:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <conio.h>
typedef struct
{
char *nume;
int nr;
} student;
/*--------------------------------------------------------*/
/* */
/* functia eroare afiseaza un mesaj de eroare,in */
/* cazul in care nu poate fi alocata dinamic o zona */
/* de memorie; apoi se opreste executia programului */
/* */
/*--------------------------------------------------------*/
void eroare(void)
{
puts(" \n **** eroare alocare dinamica de memorie ****");
exit(1);
}
/*--------------------------------------------------------*/
/* */
/* citeste numarul de studenti si datele acestora, */
/* aloca dinamic spatiu pentru tabloul de studenti, */
/* memoreaza numele si nr. matricol ale studentilor */
/* */
/*--------------------------------------------------------*/
6
/* citeste numarul de studenti */
printf("\n dati numarul de studenti:");
scanf("%d", n);
/*--------------------------------------------------------*/
/* */
/* functia comp1 compara numele a doi studenti ale caror */
/* adrese sunt date de pointerii p si r */
/* */
/*--------------------------------------------------------*/
/*--------------------------------------------------------*/
/* */
/* functia comp2 compara nr. matricol a doi studenti ale */
7
/* caror adrese sunt date de pointerii p si r */
/* */
/*--------------------------------------------------------*/
/*--------------------------------------------------------*/
/* */
/* functia cauta studentul cu adresa data de parametrul s */
/* in tabelul cu adresa indicata de parametrul tab; */
/* tabelul este ordonat dupa criteriul specificat prin */
/* parametrul f */
/* */
/*--------------------------------------------------------*/
/*--------------------------------------------------------*/
/* */
/* elibereaza spatiul de memorie detinut de campurile nume*/
/* ale celor n studenti din tabel si apoi zona detinuta */
/* de tabel */
/* */
/*--------------------------------------------------------*/
/*--------------------------------------------------------*/
8
/* */
/* functia meniu afiseaza meniul programului */
/* */
/*--------------------------------------------------------*/
void meniu(void)
{
puts("\n c, C --- citeste tabel studenti");
puts(" a, A --- afiseaza tabel studenti");
puts(" n, N --- ordoneaza dupa nume");
puts(" r, R --- ordoneaza dupa numar matricol");
puts(" f, F --- cauta dupa nume");
puts(" l, L --- cauta dupa numar matricol");
puts(" x, X --- iesire din program");
}
void main(void)
{
char opt;
int n; /* numarul de studenti */
char nume[30];
student s;
pstud tabel=NULL; /* adresa tabloului cu studenti */
while (1)
{
meniu();
opt=tolower(getche());
switch (opt)
{
case 'c':
if(tabel) /* daca a existat anterior un
alt tablou in memorie */
elibereaza(tabel,n);
citeste(&n, &tabel);
break;
case 'a':
afiseaza(n,tabel);
break;
case 'n':
qsort(tabel, n, SIZE, comp1);
break;
case 'r':
qsort(tabel, n, SIZE, comp2);
break;
case 'f':
printf("\n dati numele:");
scanf("%s", nume);
if (!(s.nume=(char *)malloc(strlen(nume)+1)))
eroare();
strcpy(s.nume,nume);
cauta(&s, tabel, n, comp1);
free(s.nume); /* elibereaza spatiul alocat pentru nume */
9
break;
case 'l':
printf("\n dati numarul matricol:");
scanf("%d", &s.nr);
cauta(&s, tabel, n, comp2);
break;
case 'x':
exit(0);
default:
puts("comanda gresita");
}
}
}
Observatii:
Din considerente de economie de memorie, implementarea tabloului cu studenti s-a facut dinamic;
dupa ce s-a citit numarul de studenti, s-a alocat spatiul corespunzator (vezi linia /* citeste1 */). În plus,
în tablou, în câmpul nume se pastreaza nu sirul de caractere pentru numele studentilor, ci un pointer la
sirul de caractere. Astfel, în loc de a rezerva, pentru toate numele, acelasi numar de caractere, în
momentul în care se cunoaste exact numele unui student, se aloca dinamic un spatiu de memorie de
dimensiune egala cu lungimea acestuia (vezi linia /* citeste2 */). Adresa acestui spatiu se pastreza în
câmpul nume.
Reamintim ca în limbajul C, mecanismul de transmitere a parametrilor este prin valoare. Prin urmare,
o functie nu poate modifica valoarea unui parametru actual, chiar daca modifica parametrul formal
corespondent. Pentru situatiile în care se doreste sa se transmita informatie de la functia apelata la cea
apelanta prin intermediul parametrilor se procedeaza in felul urmator: la apelul functiei se transmite
adresa obiectului care urmeaza sa se modifice, iar, în cadrul functiei, parametrul corespondent se
declara de tipul pointer. Astfel, functia apelata cunoaste adresa obiectului respectiv si îl poate
modifica, operând indirect asupra lui, prin intermediul pointerului. Din aceste considerente, deoarece
citirea numarului de studenti si alocarea spatiului pentru tablou se face în functia citeste, parametrii
formali corespunzatori int *n si pstud *tab vor fi de tipul pointer. La apelul functiei citeste se vor
transmite nu valorile lui n si tabel, ci adresele acestora: &n, &tabel.
La accesarea elementelor tabloului de studenti s-a folosit atât lucrul cu pointeri în functiile citeste si
afiseaza, cât si varianta cu indici în functia elibereaza.
Pentru compararea, dupa nume, a doi studenti s-a implementat functia comp1. Similar, functia comp2
realizeaza compararea dupa numarul matricol.
Pentru sortarea tabloului se apeleaza functia de biblioteca qsort. Aceasta are urmatorul prototip:
void qsort(void *tab, int nr_elem, int nr_oct_elem, int (*fcmp)(void *, void *));
Parametrii indica adresa tabloului de sortat, numarul elementelor tabloului, marimea în octeti a fiecarui
element si adresa functiei de comparare.
Dupa sortarea tabloului, cautarea unui student se face cu functia de cautare binara bsearch:
void *bsearch(void *cheie, void *tab, int nr_elem, int nr_octeti_elem, int (*fcmp)(void *,void *));
10
Parametrii indica cheia cautata, adresa tabloului, numarul elementelor tabloului, marimea în octeti a
fiecarui element si adresa functiei de comparare. Functia returneaza adresa primului element care
corespunde cheii, respectiv valoarea NULL daca nu exista un astfel de element.
În functia cauta, pentru a afla numarul de ordine al unui student în tabela, se scade din adresa
elementului de tablou corespunzator acestuia (t) adresa de început a tabelei (tab).
Pentru ca programul sa functioneze corect, comenzile f si l trebuie date dupa ce în prealabil s-au dat
comenzile n si respectiv r. (Se utilizeaza pentru cautarea unui student o functie de cautare binara, deci
tabela trebuie sa fie anterior sortata!)
11