| Autor |
Mesaj |
Admin
Administrator

Data inscrierii: 07/Dec/2013
Mesaje: 1103
|
Pointerul este o variabila care contine o adresa de memorie. Aceasta variabila contine adresa unei variabile. Avantajele utilizarii pointerilor sunt:
- ofera probabilitatea de a modifica argumentele de apelare a functiilor;
- permit o alocare dinamica a memoriei;
- pot imbunatatii eficienta anumitor rutine.
Pointerii reprezinta una din cele mai puternice caracteristici ale limbajului C, dar si periculoase. Daca pointerii nu sunt initializati perfect sau daca contin valori incorecte pot determina blocarea calculatorului, sau sa conduca la erori greu de depistat.
Variabile de tip pointer
Daca o variabila urmeaza sa contina un pointer( o adresa, aceasta trebuie declarata astfel:
tip *nume;
unde:
- tip este tipul de baza al pointerului, si specifica tipul obiectului pe care pointerul il poate puncta (indica, pointa);
- nume este numele variabilei de tip pointer;
- * - asterix – operator de indirectare.
Tipul de baza al pointerilor defineste tipul de baza catre care indica acesta. De exemplu, daca o variabila numita px contine adresa unei variabile x de tip intreg se spune ca px pointeaza pe x. Daca x se afla in memorie la adresa 200, atunci px va avea valoarea 200. Fie declaratia:
int *px, x; / px pointer la intreg, x variabila de tip intreg */
Operatorii folositi in cazul pointerilor sunt & si *. Operatorul & “adresa lui”returneaza adresa de memorie a variablilei pe care o precede.
px = & x; /* px contine adresa lui x */
Operatorul * (“de la adresa”) returneaza valoarea de la adresa pe care o precede.
y = *px; /*daca px este adresa lui x, y devine egal cu x , adica cu ce e la adresa px*/
Este important ca variabilele de tip pointer sa indice intotdeauna corect tipul de date. De exemplu, daca declarati un pointer de tipul int, compilatorul intelege ca adresa pe care o contine memoreaza o variabila de tip intreg, si nu de alt tip.
Fie urmatorul program:
# include <stdio>
void main (void)
{
int *p;
float a, b;
p = &a; /* p indica catre un float, desi este pointer de tip intreg */
b = *p;
}
Observatie. Compilatorul nu da mesaj de eroare (eventual avertisment), desi pointerului p de tip intreg i se atribuie adresa unui float (a). In final lui b nu i se atribuie valoarea lui a. Pointerul p fiind de tip intreg, vor fi transferati in b doar doi octeti, nu toti cei opt care formeaza in mod normal un numar in virgula mobila.
Valoarea unei variabile poate fi referita printr-un pointer, proces numit indirectare. Operatorul * poate fi folosit in porțiunea stanga a unei instructiuni de asignare pentru a atribui o noua valoare folosind un pointer.
Exemplu:
# include <stdio>
void main (void)
{
int a, *b;
a = 55; // variabila a primeste valoarea 55
b = &a; // b primeste adresa lui a
*b = 135; // asigneaza lui a valoarea 135 utilizând pointerul b
printf (“ valoarea lui a = %d” , a);
}
programul asigneaza lui a o valoare, indirect, folosind pointerul b.
Prin declararea unui pointer, se creaza o variabila capabila sa contina o adresa de memorie. Initializarea unui pointer nu are nici un sens, este o greseala. Exemplu:
# include <stdio>
void main (void)
{
int *a;
*a = 123; // incorect – a nu puncteaza nici un obiect
}
Printr-o instructiune de atribuire unui pointer poate sa i se atribuie un alt pointer. Exemplu:
# include <stdio>
void main (void)
}
int a = 1234;
int *b, *c;
b = &a;
c = b;
printf (“Valoarea lui a = %d Adresa lui a este %p “, a,c);
}
Aritmetica pointerilor
In plus, fata de operatorii * si &, in expresiile cu pointeri pot fi aplicati numai operatorii aritmetici +, ++, - si --. Mai mult, unui pointer ii pot fi adunate/scazute numai cantitati intregi.
Operatiile cu pointeri sunt efectuate relativ la tipul pointerului. De fiecare data cand un pointer este incrementat, el va puncta articolul urmator. De exemplu, daca avem declaratia:
int *p;
si ca el contine adresa 1000. Dupa executarea instructiunii:
p ++;
si daca tipul int se reprezinta pe 2 bytes, p va avea valoarea de adresa 1002 si nu 1001.
Pointerul p va indica spre urmatorul intreg. Acelasi lucru este valabil si pentru
decrementare. In aceleasi conditii:
p --;
determina ca p sa aibe valoarea de adresa 998.
Daca p ar fi un tip float (tipul float reprezinta de exemplu 4 bytes, octeti) si adresa lui p ar fi 1000:
float *p;
executia instructiunii:
p ++;
pointerul p va contine valoare 1004.
In concluzie de fiecare data cand este incrementat/decrementat un pointer, el va indica spre locatia din memorie a urmatorului/predecesorului element de acelasi tip cu tipul sau de baza.
Se pot aduna/scadea intregi la/sau din pointeri. De exemplu instructiunile:
int *a, *b;
. . . . . . . .
b = b – 6;
a = a +5;
face ca pointerul a sa indice al 5-lea element de acelasi tip cu a dupa cel pe care il indica in mod curent, respectiv b indica intregul aflat in memorie cu 6 intregi inaintea sa.
• In afara adunarii, scaderii dintre un pointer si un intreg, incrementarii si decrementarii, este permisa diferenta intre 2 pointeri pentru a determina numarul de obiecte de acelasi tip care separa cei doi pointeri.
• Operatiile de incrementare/decrementare pot fi aplicate asupra pointerului insusi sau asupra obiectului pe care il puncteaza. Trebuie procedat cu prudenta daca se incearca sa se incrementeze obiectul pe care il puncteaza un pointer. De exemplu:
int p;
instructiunea:
p ++;
obtine mai intai valoarea pe care o puncteaza p si apoi p este incrementat pentru a puncta elementul urmator. Pentru a incrementa obiectul pe care il puncteaza p trebuie sa folosim instructiunea:
( *p ) ++;
Parantezele fac ca valoarea punctata de p sa fie incrementata.
Compararea pointerilor
Sunt admise compararea a doi pointeri întrebuințând operatorii relationali, daca pointerii puncteaza acelasi obiect, asa cum este tabloul sau sirul de caractere.
Exemplu:
Program ce asigura întrebuințarea unei stive de tip LIFO (ultimul intrat întâiul deservit). Functiile depune() si extrage() simuleaza instructiunile push si pop. Daca valoarea introdusa de la tastatura este diferita de 0, valoarea se depune in stiva, in caz contrar se extrage ultima valoare introdusa. Bucla creata se termina la introducerea valorii –1.
# include <stdio>
# conține <stdlib>
# define DIM 100
void depune (int i); // push
int extrage (void); // pop
int *virf, *p, stiva [DIM];
void main (void)
{
int val;
virf = stiva; // indica virful stivei
p = stiva; // initializeaza p cu adresa stivei
do {
printf (“Introduceti valoarea:”);
scanf(“ %d”, &val);
if (val ! = 0)
depune(val);
else
printf (“valoarea extrasa este : %d \n”, extrage () );
} while (val! = -1);
}
void depune(int i)
{
p ++;
if (p = = (virf + DIM)) {
printf (“ Depasire stiva”);
exit (1);
}
*p = i;
}
int extrage (void)
{
if (p = = virf) }
printf(“Stiva vida”);
exit (1);
}
p - -;
return *(p + 1);
}
Variabila virf pastreza adresa din memorie a virfului stivei, implementat prin tabloul de intregi stiva. Cu ajutorul variabilei p avem acces efectiv la stiva. In functiile depune () si extrage () se efectueaza un test relational asupra pointerului p pentru a testa erorile de depasire a limitelor stivei. Astfel in functia depune(), variabila p este comparata cu sfarsitul stivei adunandu-se la varf valoarea DIM (dimensiunea stivei), iar in functia extrage() p este comparat cu varf pentru a se asigura ca nu a aparut o depasire inferioara.
Observatie. In instructiunea return sunt necesare paranteze căci fara ele instructiunea ar arata astfel:
return * p + 1 ;
si in acest caz ar returna valoarea locatiei p la care s-a adaugat valoarea 1 si nu valoarea locatiei p + 1.
Pointeri si tablouri
Intre pointeri si tablouri exista o stransa legatura, ei sunt interschimbabili. Aceasta legatura face ca implementarea lor impreuna sa fie unica si puternica.
Daca folositi numele unui tablou fara indice, generati de fapt un pointer catre primul element al tabloului. Acest lucru îngăduie ca aceasta valoare sa fie asignata unui alt pointer si astfel devine posibila accesarea elementelor tabloului folosind pointerul. De exemplu:
char sir [40], * p;
p = sir;
Variabila p a fost initializata cu adresa primului element din sir, sir[0]. Pentru a accesa al treilea element din sir se poate scrie:
sir[2]
sau
*(p + 2)
Ambele constructii vor returna al treilea element. Elementele unui tablou incep de la 0. Pentru a avea acces la al treilea element, trebuie întrebuințat indicele 2 pentru sir sau sa adaugati 2 la pointerul p, deoarece p indica efectiv spre debutantul element din sir( spre sir[0] ).
Limbajul C ofera doua metode de acces la elementele unui tablou: aritmetica pointerilor sau indicii tablourilor. întâia este mai rapida.
Exemplu:
# include <stdio>
void main (void)
{
int *p, a[5] = {1,2,3,4,5};
int i;
p = a; //asigneaza lui p adresa de inceput a tabloului
printf (“Afisarea tabloului folosind indicii tabloului \n”);
for (i =0; i <5; i++)
printf(“a[%d] = %d \n”, i, a[i] );
printf (“Afisarea tabloului folosind pointeri, nu indici de tabel \n”);
for (i = 0; i <5; i ++)
printf (“ a[%d] = %d \n”, i, * (p + i) );
}
Ambele instructiuni printf() afiseaza acelasi lucru. In expresia * (p + i) sunt necesare paranteze fiindcă operatorul * referitor la pointeri are o precedenta mai mare decat operatorul +.
Exemplu. Se cere un program care sa citeasca de la tastatura un sir de caractere, apoi sa-l afiseze cu lietere mari (folosind functia toupper () respectiv cu litere mici (functia tolower () ).
O prima versiune ar fi folosind aceea in care pentru a accesa caracterele din sir se indexeaza numele tabloului, iar alta ar fi folosirea unui pointer pentru a accesa caracterele sirului.
# include <stdio>
# include <ctype>
void main (void)
{
char sir [40], *p;
int i;
printf(“Introduceti sirul : \n”);
gets (sir);
for (i = 0; sir[i]; i++) //ultimul caracter din sir este /0
sir[i] = toupper[i];
printf (“ %s \n”, sir); // afisare cu litere mari – prima varianta
p = sir; // p pointeaza inițialul element din sir
while (*p) {
* p = toupper(p);
p ++;
}
printf (“ %s \n”, sir); // afisare cu litere mari – varianta 2
for (i = 0; sir[i]; i++)
sir[i] = tolower (sir[i]);
printf (“ %s \n ”, sir); // afisare cu litere mici – prima varianta
p = sir; // resetarea pointerului p
while (*p) {
*p = tolower(p);
p ++;
}
printf (“ %s \n “, sir); //afisare cu litere mici – a 2-a varianta
}
Instructiunea:
while (*p) {
* p = tolower (p);
p ++;
}
poate fi scrisa si astfel:
while (*p)
p++ = tolower (*p);
Asemanator modului in care se incrementeaza un pointer se poate decrementa un pointer.
Exemplu. Program care copiaza continutul unui sir intr-un alt sir in ordine inversa.
# include <stdio>
# include <string>
void main (void)
{
char sir1 = “Acesta este un sir”;
char sir2 [40], * p1, *p2;
p1 = sir1 + strlen(sir1) – 1; // p1 pointeaza sfarsitul sirului sir1
p2 = sir2;
while (p1 >=sir1)
*p2 ++ = * p1-- ;
*p2 = ‘\0’; // caracterul null incheie sirul sir2
printf (“ %s \n %s”, sir1, sir2);
}
Pointerul p1 puncteaza sfarsitul sirului sir1, iar pointerul p2 inceputul sirului sir2. Compararea pointerilor in bucla while, asigura oprirea procesului de copiere atunci cand este atins inceputul sirului sir1.
Pointeri si siruri initializate (Initializarea pointerilor)
La intalnirea unui sir initializat, compilatorul depune sirul in tabela de siruri a programului si genereaza un pointer catre el.
# include <stdio>
void main (void)
}
char *p, sir [ ] = “Acesta este un sir”;
p = sir;
printf (p);
p = “Acesta este un sir”;
printf (p);
}
Variabila p este declarata ca pointer de tip char, deci el poate puncta un tablou de caractere. La compilarea liniei:
p = “Acesta este un sir”;
compilatorul depune sirul in tabloul de siruri al programului si asigneaza lui p adresa sirului in tablou.
Programul poate fi rescris astfel:
# include <stdio>
void main (void)
{
char *p = “Acesta este un sir”;
printf(p);
}
Variabila p este initializata sa puncteze un sir.
Exemplu. Program care citeste siruri de caractere pana se introduce parola corecta.
# include <stdio>
# include <string>
char *p = “parola”;
void main (void)
{
char str [40];
do {
printf (“Introduceti parola corect \n”);
gets(s);
} while (strcmp (p, sir)) ;
}
Tablouri de pointeri
Pointerii pot fi organizati sub forma de tablouri ca oricare alt tip de date. Declararea unui tablou de pointeri de tip int de 10 elemente este:
int *x[10];
Pentru a atribui adresa unei variabile de tip intreg cu numele num elementului al doilea al tabloului de pointeri x, se va scrie:
x [1] = & num;
iar pentru a obtine valoarea num se va scrie:
*x[1].
Tablourile de pointeri sunt folosite deobicei pentru a pastra pointeri catre siruri de caractere
.
Exemplu. Functia eroare ( ) afiseaza un mesaj de eroare pe baza valorii parametrului num_err.
char * p [ ] = {
“Lipseste ; “,
“Lipseste ( “,
“Lipseste ) “,
“Lipseste { “,
“Lipseste } “
};
void eroare (int num – err)
{
print ( p[num_err]); // printf(“ %s”, p[num – err]);
}
Pointerul p pastreaza pointeri catre fiecare sir. De exemplu daca num _err = 2 se va afisa:
Lipseste )
Exemplu. Program ce foloseste un tablou bidimensional de pointeri pentru a crea un tabel de siruri care leaga diferite obiecte de locul in care se afla.
# include <stdio>
# include <sting>
char * p [ ] [2] = {
“cartea”, “in biblioteca”,
“creion”, “pe masa”,
“pix”, “in penar”,
“guma”, “sub masa”,
“ “ , “ “ //sirul null incheie tabela
}
void main (void)
{
int i;
char obiectul [40];
printf (“Introduceti numele obiectului cautat: “);
gets (obiectul);
for (i = 0; * p[i] [0]; i ++) {
if (! strcmp (obiectul, p[i] [0]))
printf (“ %s este %s \n”, obiectul, p [i] [1];
}
}
Indirectare multipla
Este posibil sa avem un pointer care sa puncteze un alt pointer. Aceasta situatie este denumita indirectare multipla sau pointer la (catre) pointer.
Pointer Pointer Variabila
Adresa Adresa Valoare
Indirectare multipla
Cand un pointer puncteaza un alt pointer, primul pointer contine adresa celui de al doilea pointer care puncteaza locatia ce contine obiectul, valoarea dorita.
Pentru a declara un pointer catre un alt pointer, trebuie plasat un asterix suplimentar in fata numelui pointerului.
De exemplu:
char ** pp;
pp este un pointer catre un alt pointer de tip char, si nu un pointer catre un caracter.
Accesarea valorii printr-un pointer catre un alt pointer, cere ca operatorul asterix sa fie fixat de doua ori. De exemplu:
p = &ch; // asigneaza lui p adresa lui ch
pp = &p; //asigneaza lui pp adresa lui p
**pp = ‘c’; // asigneaza lui ch valoarea ‘c’ folosind indirectare multipla
Observatie. Pointeri catre pointeri pot produce confuzii. De aceea, indirectarea excesiva este derutanta si sursa de erori conceptuale.
Pointeri la functii
Un pointer la o functie este o variabila care contine adresa punctului de intrare al functiei. In urma compilarii si link-editarii fiecare punct de intrare are o adresa fizica, adresa care este folosita de fiecare data cand functia este referita (apelata). Deci este normal sa avem un pointer care sa o puncteze. Folosind un pointer care puncteaza o functie, este posibil sa apelam functia folosind pointerul.
Pentru a crea o variabila care sa poata puncta (indica) o functie, trebuie declarat un pointer de acelasi tip cu tipul functiei, urmat de parametrii functiei. De exemplu, declaratia:
float (*pf) (float x, floar y);
stabileste ca pf este un pointer la o functie care returneaza un float si are 2 parametrii x si y de tip float.
Observatie. Este necesar ca, constructia *pf sa fie inclusa intre paranteze rotunde din cauza regulilor de pecedenta a operatorilor.
Pentru a asigura unui pointer adresa unei functii, trebuie folosit numele functiei fara paranteze.
Fie prototipul unei functii:
float dif (float x, float y);
si pointerul pf declarat mai sus, instructiunea
pf = dif;
este corecta. In urma acestei asignari, functia dif ( ) se poate apela indirect prin pf, prin instructiunea de forma:
reg = (*pf) (3.5, - 4.5);
Programul complet pentru acest exemplu este:
# include <stdio>
float dif (float x, float y);
void main (void)
{
float (*pf) (float x, float y);
float rez;
pf = dif; // adresa functiei se atribuie lui pf
rez = (*pf) (3.5, - 4.5);
printf (“rezult = %lf \n “, rez);
}
Una din cele mai importante utilizari ale pointerilor la functii apare atunci cand este creat un tablou de pointeri la functii, fiecare element din tablou pointeaza la o alta functie.
Pentru a apela o anumita functie va necesită sa indexam tabloul. Exemplu:
# include <stdio>
int sum (int a, int b);
int dif (int a, int b);
int mul (int a, int b);
int div (int a, int b);
int (* p[4]) (int a, int b); //
void main (void)
{
int rez;
int i, j, op;
p[0] = sum; // adresa functiei sum ( ) // n
p[1] = dif; // adresa functiei dif ( ) // n
p[2] = mul; // adresa functiei mul ( ) // n
p[3] = div; // adresa functiei div ( ) // n
printf (“Dati doua numere: “);
scanf (“ %d %d “, &i, &j);
printf (“ Optiuni: 0 – adunare, 1 – scadere, 2 – inmultire, 3 – impartire \n”);
do {
printf (“ Introduceti numarul optiunii:”);
scanf (“ %d “, &op);
} while (op <0>3);
rez = (* p[op] ) (i,j);
printf (“ %d”, rez);
}
int sum (int a, int b)
{
return a + b;
}
int dif (int a, int b);
}
return a – b;
}
int mul (int a, int b);
}
return a * b ;
{
int div (int a, int b);
{
if (b) return a/b;
else return 0;
}
Programul cere utilizatorului introducerea a doua numere i si j si op numarul operatiei. Acest numar este folosit pentru a indica tabloul de pointeri la functii, astfel incat sa se apeleze indirect functia dorita.
Sigur ca programul putea fi realizat cu instructiunea switch ( ), dar folosirea tabloului de pointeri la functii este mai eficienta.
Tabloul de pointeri la functii poate fi initializat ca orice alt tablou deci programul poate fi rescris astfel:
- - - - - -
int (* p[4] ) (int a, int b) = {sum, dif, mul, div};
Folosirea pointerilor ca parametri
Parametrii transferati unei functii C pot fi valori sau adrese. Primul tip numit si apel prin valoare, copiaza valoarea unui argument (parametru) formal al functiei. Modificarile efectuate asupra parametrului nu au consecință asupra argumentului.
Al doilea mod de transfer de argumente catre functii este apelul prin referinta (prin adresa). In acest caz este copiata adresa unui argument. In interiorul functiei adresa este folosita pentru acces la argumentul folosit efectiv la apelare, deci modificarile asupra argumentului il afecteaza. Adresa argumentului nu este altceva decat un pointer.
Exemplul 1. Program care apeleaza o functie ce inverseaza intre ele valorile a doua variabile de tip float indicate de catre argumentele sale.
# include <stdio>
void inverseaza (float * x, float * y);
void main (void)
{
float a = 12.55, b = -75.66;
inverseaza (&a,&b); // paseaza adresele lui a si b
printf (“a = %f) b = % f \n”, a, b):
}
void inverseaza (float * x, float * y)
{
float temp;
temp = *x;
* x = * y;
* y = temp;
}
Daca un tablou este folosit ca argument al unei functii, acesteia ii este pasata adresa tabloului. Instructiunile functiei apeleaza asupra continutului efectiv al tabloului si pot sa-l modifice.
Exemplul 2. Functia majuscule ( ) afiseaza caracterele unui argument de tip sir cu majuscule
# include <stdio>
# include <ctype>
void majuscule (char * sir);
void main (void)
{
char s [40];
gets(s)
majuscule(s);
}
void majuscule (char * sir)
{
int t;
for (t = 0; sir [t]; t ++) {
sir[t] = toupper(sir[t] );
printf (“ %c”, sir[t]) ;
} |
|
|
|
|
 |
|
|
|
|
Subiectul urmator
Subiectul anterior
Nu puteti crea un subiect nou in acest forum Nu puteti raspunde in subiectele acestui forum Nu puteti modifica mesajele proprii din acest forum Nu puteti sterge mesajele proprii din acest forum Nu puteti vota in chestionarele din acest forum |
Powered by phpBB
© 2001, 2002 phpBB Group
|