Programatorul isi poate defini intr-un program variabile utilizabile in tot
programul, globale, care sunt alocate pe toata durata unui program. De asemenea
programatorul isi poate defini variabile cu utilizari locale, valabile intr-o
anumita parte a programului, ele fiind alocate in momentul in care controlul
trece prin blocul unde sunt ele definite. p8j20jb
Clasa de memorie a unei variabile determina daca o variabila este globala sau
locala.
Limbajul C defineste:
-doua tipuri de clase de memorare:
-globale
-locale si 4 tipuri de specificatori de clase de memorare:
-auto
-register
-static
-extern.
Specificatorii de clase de memorare afecteaza vizibilitatea functiilor si variabilelor
precum si clasa lor de memorare.
Vizibilitatea se refera la portiunea din programul sursa in care variabila sau
functia poate fi referita.
Plasarea declaratiilor unei functii sau variabile intr-un fisier sursa afecteaza:
-clasa de memorare
-vizibilitatea.
Declararea in afara tuturor definitiilor de functii este numita declarare la
nivel extern.
Declararea din interiorul definitiilor de functii poarta denumirea de declarare
la nivel intern.
1. DECLARARE DE VARIABILE LA NIVEL EXTERN
Aceasta declarare la nivel extern foloseste specificatorii static sau extern,
sau ei se omit cind declararile apar in afara functiilor, caz in care definim
variabile globale.
Declararile de variabile la nivel extern sunt:
-de variabile globale
-de referinte la variabile declarate in alte module.
O variabila se defineste la nivel extern o singura data intr-un fisier sursa.
Definirea ei implica:
-declararea
-initializarea (daca nu este implicita).
Daca variabila este definita cu specificatorul static, ea este vizibila numai
in fisierul sursa in care apare din momentul definirii ei, in alt fisier sursa
putind exista o alta variabila cu acelas nume (spunem ca ascundem variabila
in cadrul modulului).
Daca variabila este declarata cu specificatorul extern, ea este folosita pentru
a declara o referinta la o variabila definita in alta parte (alt modul), fiind
folosita pentru:
-a face vizibila o definitie din alt fisier sursa (alt modul)
-a face vizibila o variabila inainte de a o defini (declara) in acelas fisier
sursa.
Declararile care contin specificatorul extern nu trebuie sa contina initializari
pentru ca ele se refera deja la variabile cu valori definite sau la alte definitii.
2. DECLARARI DE VARIABILE LA NIVEL INTERN
Pentru declararile la nivel intern se pot folosi ori care din cei 4 specificatori
de clasa de memorare. Daca acest specificator este omis la declararea la nivel
intern , atunci clasa este considerata implicit auto, variabila fiind vizibila
doar in blocul in care este declarata.
Variabilele de tip auto, nu sunt initializate automat ele avind valori nedefinite.
Specificatorul de clasa de memorare register, aloca variabilei un registru al
procesorului, daca este posibil, aceasta implicind:
-un acces foarte rapid la variabila
-generarea unui cod minimal.
Daca nu este disponibil un asemenea registru, variabila respectiva este de tip
auto. De remarcat ca la variabilele de tip register nu este disponibil operatorul
de adresare, &.
O variabila declarata la nivel intern cu specificatorul static, retine valoarea
ei la parasirea blocului pe cind o variabila declarata la nivel intern cu specificatorul
auto, nu retine aceasta valoare. Variabila declarata cu static, este initializata
la compilare cu 0 (zero), deci se defineste implicit, nefiind insa reinitializata
de fiecare data cind se intra in bloc (practic putind fi o sursa de erori).
O variabila declarata cu specificatorul de clasa de memorare extern, la nivel
intern, este o referinta la o variabila cu acelas nume avind rolul de a face
variabilele definite la nivel extern (deci in afara functiilor) vizibile in
bloc (deci la nivel intern).
3. VARIABILE GLOBALE SI LOCALE, FUNCTII
Variabilele globale pot fi utilizate in tot programul si ele au:
-o definitie la nivel extern, adica in afara functiilor
-sau reprezinta o declaratie de variabile externe adica o declaratie de variabile
precedata de cuvintul cheie extern.
Variabilele globale sunt alocate in momentul compilarii la intilnirea definitiei
lor, intr-o zona de memorie prevazuta special pentru variabile globale.
Variabilele locale, au o valabilitate locala in functia in care sunt declarate.
Ele se declara imediat la inceputul functiei inaintea oricarei instructiuni
(In C++ pot fi declarate ori unde in cadrul functiei, vizibilitatea lor fiind
din locul in care au fost declarate).
Variabilele locale pot fi alocate:
-pe stiva, cele automatice (au specificatorul auto sau implicit sunt auto),
alocarea lor fiind facuta doar la executie si nu la compilare, avind valori
initiale nedefinite.
-in memorie, variabilele declarate static, ele fiind implicit initializate cu
zero. Aceste variabile nu pot fi precedate de specificatorul extern neputind
fi utilizate in alte fisiere.
Un modul contine o parte din textul sursa al unui program. El contine functii
inrudite si date pe care le prelucreaza in comun, nexistind o regula de asociere
a lor.
In acest caz o variabila statica declarata in afara corpurilor functiiilor este
locala modulului unde a fost declarata. Utilizarea ei presupune declararea ei
in prealabil sau definirea ei daca o si initializam (implicit este zero).
Variabilele globale pot fi utilizate in ori ce modul daca sunt precedate de:
-definitia sau declararea lor
-declararea cu specificatorul extern.
Observatie:
Putem avea variabile locale si globale cu acelas nume. In cadrul functiilor
care compun un modul, compilatorul mai intii cauta o variabila locala
cu numele specificat si apoi daca nu gaseste cauta o variabila globala din afara
functiei.
Exemple:
Fie urmatoarele module: a)fis1.c care contine: extern void other(); //functie definita in alt modul int l=3; void next(void); //prototip next
void main (void)
A l++; printf(“%d\n”,l); // l=4 next();
S
void next (void)
A l++; printf(“%d\n”,l); //l=5 other();
S
Modulul al doilea va fi denumit fis2.c si va contine: extern int l; //se refera la variabila l din modulul fis1.c facind vizibila
si in acest modul variabila l
void other(void)
A l++; printf(“%d\n”,l);//l=6
S b)Modulul fis3.c contine: int zi,luna,an;//variabile globale in modulul fis3.c
void f(void)
A
//functie in care pot folosi variabilele zi,luna,an
S
void g(void)
A
//si in aceasta functie se poate lucra cu variabilele zi, luna, an
S
Modulul fis4.c contine: void main(void)
A extern int zi,luna,an;//referiri la variabilele din modulul fis3.c
//aici pot lucra cu variabilele zi,luna,an
S
void p(void)
A
//aici nu pot folosi variabilele zi,luna,an
S c)Pentru a exemplifica declararea la nivel intern avem acest modul, fisc: void other(void);//prototip int l=1;
void main(void)
A static int a; register int b=0; int c=0; printf(“%d, %d, %d, %d \n”,l,a,b,c); //1, 0, 0, 0 other(); getch();
S void other(void)
A int l=16;//l e local aici si mai prioritar decit l ca si variabila globala static int a=2; a+=2; printf(“%d, %d \n”,l,a); //16, 4
S
Functiile sunt analoage cu variabilele globale, ele putind fi apelate in ori
ce modul al programului daca apelul lor este precedat in acel modul de:
-declararea ei
-prototipul ei (care inlocuieste declararea extern).
O functie este vizibila din momentul:
-declararii ei
-prin precizarea prototipului ei
-prin informarea compilatorului (nu la toate compilatoarele) inainte de declararea
ei cu o constructie de forma: tip nume_functie (); cu rol de prototip.
Daca functia este precedata de cuvintul static, functia este locala modulului
respectiv, astfel ca putem avea in alte module functii cu acelas nume.
In cadrul functiilor parametrii formali se aloca pe stiva ca si variabile automatice,
putind fi considerate variabile locale utilizabile in corpul functiei.
Alocarea are loc doar la apelul functiei, caz in care parametrilor formali alocati
li se atribuie valorile parametrilor efectivi (argumente) care le corespund.
De asemenea daca in cadrul functiei mai sunt declarate variabile automatice,
acestea sunt apoi alocate pe stiva.
La revenirea din functie are loc dealocarea intii a variabilelor automatice
definite in cadrul functiei si apoi a parametrilor, stiva ajungind la starea
dinaintea apelului.
Parametrii unei functii se considera a fi o interfata intre functii. Cu ajutorul
parametrilor efectivi se realizeaza transferul datelor de la functia care face
apelul la functia apelata, care are destinat un parametru formal pentru fiecare
parametru efectiv.
Functia apelata nu poate modifica parametrii efectivi (daca sunt transmisi prin
valoare, adica nu cu pointeri sau referinte). Implicit limbajul C foloseste
apelul prin valoare care realizeaza copierea fiecarui argument in parametrul
formal corespunzator al functiei.
In acest caz se poate folosi de la aceasta functie doar valoarea returnata,
ea fiind disponibila la revenirea dupa apelul functiei.
Variabilele globale pot fi si ele folosite la interfata intre functii (nu este
prea indicat acest mecanism de interfatare si de aceea P.O.O. rezolva acest
lucru mult mai elegant si sigur cu ajutorul claselor). In cazul lucrului cu
variabile globale, ori ce functie are acces la variabilele globale. Aceste functii
trebuie insa:
-sa cunoasca numele variabilelor globale
-sensul variabilelor globale (doar ca intrare, daor ca iesire sau mixt).
Acest lucru impune un control extrem de riguros in program a variabilelor globale,
nestapinite ele fiind o sursa mare de erori.
Totusi variabilele globale se folosesc mai ales in cazurile in care mai multe
functii folosesc rezultatele memorate in cadrul variabilelor globale.
4. INITIALIZAREA VARIABILELOR SI A TABLOURILOR
Variabilele se initializeaza prin constructii de forma: tip nume = expresie; //pentru variabile automatice sau globale static tip nume = expresie; //pentru variabile statice
La compilare, variabilele statice si globale se aloca in memorie si se initializeaza
cu expresie, unde expresie trebuie sa aiba valori constante. Daca aceste variabile
nu se initializeaza explicit atunci ele implicit se initializeaza cu zero.
La executie, variabilele automatice se aloca pe stiva si daca reprezinta parametrii
formali ai unei functii, la apelul ei cu parametrii efectivi se face initializarea
lor cu aceste valori efective. In acest caz expresiile folosite pentru initializare
pot sa nu fie constante, operanzii variabili ai unei asemenea expresii avind
valori predefinite in prealabil.
Variabilele automatice neinitializate au valori nedefinite.
Daca tipul expresiilor de initializare nu coincide cu tipul variabilei pe care
o initializeaza atunci se face conversia de tip.
Tablourile, sunt initializate functie de tipul lor astfel:
-a)tablourile globale si statice se initializeaza la compilare prin constructii
de forma:
-pentru tablouri unidimensionale: tip nume aeci = Aec0, …, eciS ;sau static tip nume aeci = Aec0, …, eciS ; unde: eci sunt expresii constante ec, este dimensiunea tabloului, care daca lipseste duce la initializarea tuturor
elementelor din tablou. Elementele care nu se initializeaza explicit sunt automat
initializate cu 0.
-pentru tablouri bi(multi)dimensionale initializarea se face pe acelas principiu
astfel:
astatici tip nume aniami = A
Aec11, ec12, …, ec1mS,
Aec21, ec22, …, ec2mS,
….
Aecn1, ecn2, …, ecnmSS; unde: n, m, ecij, i=1, …, n, si j=1, …, m sunt expresii constante. Acpladele
nu sunt obligatorii dar ele specifica modul de initializare pe linii. Variabila
n ne da numarul maxim de linii iar m numarul maxim de coloane.
-b)tablourile automatice in cadrul mediulul Borland se initializeaza la executie,
la apelul functiilor unde sunt declarate ca si parametrii formali.
Limita superioara a primului indice la tablourile uni-(multi)dimensionale poate
lipsi in urmatoarele cazuri:
-in cadrul unor declaratii care contin initializari
-in cadrul declaratiilor de parametri formali
-in cadrul declaratiilor de tablouri externe.
Exemplu: int tab ai = A0, 1, 2, 3, 4S; //initializeaza un tablou de 5 elemente double t aia3i = A
A0, 1S,
A-1S,
A1, 2, 3SS; //defineste un tablou double de 3 x 3 elemente care are valorile: ta0ia0i=0, ta0ia1i=1, ta1ia0i=-1, ta2ia0i=1, ta2ia1i=2, ta2ia2i=3, celelalte elemente fiind 0 daca tabloul este global sau nedefinite daca tabloul
este automatic.
6. POINTERI
Un pointer este o variabila care are ca si valori adrese, deci un pointer
(indicator) reprezinta adrese ale unor zone de memorie asociate unor variabile.
Variabilele pointer din punct de vedere al continutului de memorie pe care il
adreseaza pot fi de mai multe categorii:
-un pointer de date, care contine adresa unei variabile sau a unei constante
din memorie
-un pointer de functii, care contine adresa codului executabil al unei functii
-un pointer de obiecte care contine in principiu adresa unui obiect in memorie
deci adrese de date si functii. Acesti pointeri generalizeaza primele doua categorii.
Un pointer generic (void) ce poate contine adresa unui obiect oarecare.
6.1. DECLARAREA VARIABILELOR POINTER
Un pointer este asociat unui tip de variabile, sintaxa declaratiei fiind: tip * id_ptr; care inseamna ca id_ptr este numele unei variabile pointer, deci va contine
o adresa a unei zone de memorie care contine un obiect de tipul, tip. Asteriscul
din fata variabilei id_ptr, informeaza compilatorul ca trebuie sa creeze o variabila
de tip pointer.
Exemplu: int *p; //precizeaza ca p este o variabila pointer care va putea contine adrese
de memorie unde vor fi memorate date de tip intreg.
Pornind de la declaratia clasica: tip nume; la tip * nume;, putem considera ca: tip *, reprezinta un nou tip de data, si anume tipul pointer spre tip.
Observatie:
Declaratia void *id_ptr; // permite declararea unui pointer generic (void) care
nu este asociat unui tip de date precis. In acest caz:
-dimensiunea zonei de memorie adresate
-interpretarea datelor din acea zona nu sunt definite.
Exemplu: int *nptr; //nptr este pointer la intreg int *tabptra10i;//tabptr este un tablou de 10 elemente fiecare element fiind
pointer la intreg float *a, rez;//a este pointer la flotant, rez este flotant void *point_ne;//avem prin point_ne un pointer generic
Notiunea de pointer se poate exprima si prin:
-localizator
-reper
-indicator de adresa, etc. Uneori se mai utilizeaza si notiunea de referinta
dar ea practic are alta semnificatie in C++.
6.2. OPERATORI SPECIFICI PENTRU POINTERI
Daca o variabila este de tipul, tip, atunci operatorul de adresare (referentiere),
&, aplicat acestei variabile ne returneaza adresa acestei variabile, adica
returneaza noul tip de date, pointer, adica tip *.
Deci operatorul de adresare, &, asociat variabilei sau mai general obiectului
nume, obtine adresa acelei variabile sau obiect.
&nume = adresa variabilei nume
Exemplu: char c, *pc; pc=&c;
Operatorul de indirectare, (dereferentiere), *, permite accesul la continutul
variabilei (obiectului) asupra caruia se aplica.
Constructia:
*id_ptr, unde id_ptr este o variabila pointer declarata in prealabil si careia
i s-a asociat adresa unei variabile de acelas tip se citeste “continutul
de la adresa data de pointerul id_ptr”. Rezultatul acestei evaluari depinde
de tipul variabilei pointer id_ptr, adica de tipul spre care pointeaza ea.
Exemplu: void main (void)
A int l=1, j=5, *p; p=&l;// asigneaza lui p adresa lui l
*p=2;// continutul de la adresa variabilei l din 1 se face 2
(*(p=&j))++;//asigneaza aceluiasi p adresa lui j incrementind continutul
de aici la valoarea 6 printf(“%d %d\n”,l,j);//l=2, j=6
S
Observatie:Limbajul C permite (sintactic) ca ori ce tip de pointer sa pointeze
oriunde in memorie, tipul pointerului determinind modul in care va fi tratat
obiectul pointat.
Exemplu: int q; float *fp; fp=&q;// sintactic se poate ca unui pointer flotant sa i se atribuie o adresa
de intreg (care se reprezinta pe 2 octeti)
*fp=20.5;//atribuim la aceasta locatie un flotant (ce se reprezinta pe 4 octeti)
deci voi mai scrie inca in 2 octeti si deci cresc posibilitatile de eroare
Deci un pointer de un anumit tip nu va fi folosit sa pointeze o data de alt
tip.
Acest lucru va fi exemplificat si in urmatorul exemplu: int *p; float q, temp; temp=20.5; p=&temp;//p e pointer spre intregi dar l-am asociat la un flotant, sintactic
doar warning q=*p;//continutul preluat va fi incorect printf(“%f\n”,q);//va tipari o prostie
Observatie:Compilatorul permite folosirea pointerilor NULL rezultatele fiind
insa nedefinite
Exemplu: int *p;//p pointer spre intregi dar are valoarea inca NULL pentru ca nu este
asociat la nici o variabila
*p=10;//erore, pentru ca p nu pointeaza un obiect cunoscut.
Observatie: Daca un pointer vrem sa-l utilizam cu mai multe tipuri de date atunci
utilizam pointeri generici.
Exemplu: int x; float y; char c; void *p;
… p=&x;//lui p i se atibuie adresa de memorie unde pot fi intregi
*(int*)p=10;//atribuim o valoare variabilei x folosind operatorul cast
… p=&y; ;//lui p i se atibuie adresa de memorie unde pot fi flotanti
*(float*)p=10.0;
… p=&c; ;//lui p i se atibuie adresa de memorie unde pot fi caractere
*(char *)p=’a’;
La folosirea pointerilor generici nu se pot folosi atribuiri simple ci trebuie
precizate conversiile explicit prin operatori de tip cast.
Utilizarea tipului void *, pointer generic, asigura o mare flexibilitate, dar
poate duce deseori la erori. De aceea in cazul unor aplicatii concrete programatorul
trebuie sa stapineasca bine folosirea acestor tipuri.
6.3. APELUL PRIN REFERINTA UTILIZIND PARAMETRII DE TIP POINTERI
In cadrul limbajului C transferul parametrilor este implicit efectuat prin
valoare.
Daca vrem ca o functie sa modifice o variabila parametru formal, atunci trebuie
sa transmitem functiei adresa variabilei, deci folosim operatorul de adresare
& in cadrul apelului functiei cu parametrii efectivi, iar in interiorul
functiei vom accesa variabila cu ajutorul operatorului de indirectare *. In
acest caz ca si parametrii formali ai functiei pe care o realizam vom folosi
variabile de tip pointer, variabile ce pot avea ca si valoare adresa zonelor
de memorie precizate ca si parametrii efectivi la apelul functiei.
Deci cind ca si argumente se folosesc adrese, ca si parametrii formali se folosesc
pointeri unde se vor copia aceste adrese.
Exemplu: Fie doua functii cu care vrem sa interschimbam continutul a doua variabile: void schimba_1(int a, int b)
A int temp; temp=a; a=b; b=temp;
S
Daca avem: int x=1, y=2;
…. schimba_1(x, y); // vom remarca ca aceasta schimbare nu va fi efectuata schimbarea
efectuindu-se doar intern cu variabilele locale automatice de pe stiva a si
b, rezultatul nereflectindu-se asupra parametrilor actuali x si y. De aceea
se foloseste functia: void schimba_2(int *a, int *b)
A int temp; temp=*a;
*a=*b;
*b=temp;
S
Apelul in acest caz va fi: schimba_2(&x, &y);// care va efectua schimbarea continutului celor doua
variabile x si y.
Observatie:In limbajul C++ se introduce notiunea de referinta la care vom reveni,
si in acest caz shimbarea continutului celor doa variabile se poate face astfel: void schimba_3(int &a, int &b)
A int temp; temp=a; a=b; b=temp;
S iar apelul se face prin: schimba_3(x, y);
In acest caz:
-a este referinta la variabila x
-b este referinta la variabila y.
Constructia de forma: int &a=x;//spune ca variabila a este o referinta la variabila x si numai
la x (un alias al lui x).
Accesul la o variabila prin intermediul referintei se face fara utilizarea operatorului
de adresare asa cum s-a vazut din exemplul de mai sus.
Daca la apelul unei functii se foloseste ca si argument un tablou, functia va
primi doar adresa acelui tablou, adica adresa primului element al acelui tablou.
La declararea functiei, parametrii formali care sunt declarati trebuie sa fie
compatibili cu tipul tablourilor pe care le pointeaza.
Exista 3 metode care se utilizeaza pentru a declara un parametru formal ca si
parametru care sa primeasca un pointer catre un tablou. Aceste sunt:
-a)parametrul formal trebuie sa fie declarat ca un tablou de acelasi tip si
dimensiune cu tabloul folosit ca si argument la apelul functiei
-b)parametrul formal poate fi declarat ca un tablou fara dimensiune
-c)parametrul formal este declarat ca un pointer catre tablou, argumentul folosit
ca si parametru efectiv fiind adresa tabloului asupra caruia se fac operatii
in functie (cea mai folosita metoda).
Exemplu:
#include <stdio.h> void f1 (int numa5i); void f2 (int numai); void f3 (int *num); //cele 3 prototipuri
void main (void)
A int numerea5i=A1,2,3,4,5S; f1(numere); printf(“\n”); f2(numere); printf(“\n”); f3(numere); printf(“\n”);//cele 3 apeluri echivalente
S
void f1 (int numa5i)
A int l; for(l=0;l < 5; printf(“%d”, numali), l++);
S//f1
void f2 (int numai)
A int l; for(l=0;l < 5; printf(“%d”, numali), l++);
S//f2
void f3 (int *num)
A int l; for(l=0;l < 5; printf(“%d”, *(num+l)), l++);
S//f3
6.4. OPERATII ARITMETICE CU POINTERI
Cu ajutorul pointerilor se pot efectua operatii de:
-atribuire
-comparare
-adunare
-scadere
-incrementare, decrementare.
Deci pe linga operatorii * si & se mai folosesc alti 4 operatori: +, ++,
-, --.
Nu pot fi adaugate sau scazute decit cantitati intregi. Operatiile se efectueaza
relativ la tipul pointerului (int, float, char, etc.)
Conditia care se cere la efectuarea operatiilor aritmetice cu ajutorul pointerilor
este ca pointerii sa fie de acelas tip.
Consideram ca adresele care reprezinta pointeri au valori numerice intregi fara
semn, operatiile cu pointeri fiind supuse unor reguli si restrictii specifice.
6.4.1. Compararea a doi pointeri
Compararea a 2 pointeri se face cu operatorii relationali, in cazul in care
pointerii sunt inruditi, adica pointeaza pe obiecte de acelas tip.
Exemplu: int *p1, *p2, l, j; p1=&l; p2=&j; if (p1 < p2) printf (“p1= %n p2 = %n\n”,p1,p2);
Operatorii de egalitate == si != pot fi folositi pentru a compara pointeri cu
o constanta speciala NULL definita de obicei in stdio.h prin:
#define NULL 0
Daca p este un pointer generic, void *p; atunci se pot face comparatiile: p == NULL si p != NULL dar este recomandabil a folosi: p == 0 si p != 0 pentru ca in C++ nu se recomanda folosirea constantei simbolice
NULL sau:
!p si p cu care sunt echivalente.
Deci urmatoarele constructii sunt echivalente: if (p == 0) = if (!p) if (p != 0) = if (p).
6.4.2. Operatii de adunare/scadere si incrementare/decrementare
Operatia de adunare sau scadere intre un pointer de obiecte si un intreg este
conforma urmatoarelor reguli specifice:
Fie id_ptr un pointer declarat prin: tip *id_ptr;
Atunci operatiile: id_ptr + n respectiv id_ptr - n, corespund adaugarii/scaderii la adresa obtinuta in cadrul variabilei
id_ptr a valorii: n * sizeof(tip).
Analog se efectueaza si operatiile de incrementare/decrementare doar ca n =
1.
Exemplu: float *fp1, *fp2, f; //sizeof (float) =4 double *dp, d; //sizeof (double) =8, fp1 si dp ar trebui sa se refere la tablouri fp1=&f; dp=&d;
… fp2=fp1+5; //fp2 va contine adr. lui f + 5*4 dp=dp-2; //dp va contine adresa lui dp - 2*8 fp1++; //fp1 va contine adresa lui fp1 + 1*4 dp--; //dp va contine adresa lui dp - 1*8
Aceste operatii aditive cu pointeri si intregi dau rezultate utilizabile doar
atunci cind pointerul adreseaza un tablou si prin operatiile respective se produce
o deplasare in limitele tabloului respectiv.
6.4.3. Scaderea a doi pointeri
Scaderea a doi pointeri de obiecte de acelas tip este permisa, rezultatul
fiind o valoare care reprezinta diferenta de adrese divizata la dimensiunea
tipului de baza.
Adunarea acestor pointeri nu este permisa.
Exemplu: int l,ja10i; float *p1=&ja4i, *p2=&ja2i; l=p2-p1; // l= (adresa p2- adresa p1)/4=2, cite elemente sunt intre cele doua
adrese
Datorita rolului tipului la adunare si scadere, operanzii nu pot fi pointeri
void sau pointeri spre functii.
Observatie:
1. Operatorii de tip cast pot modifica semnificatia pointerilor: int *pi; float *pf;
…
*((char*)pf) ne va da primul octet din reprezentarea unei variabile de tip float
*((char*)pf+1) ne va da al doilea octet din reprezentarea unei variabile de
tip float
(char*) este folosita pentru acces pe octet la zone de marimi diferite.
2. Operatia de incrementare/decrementare se poate aplica:
-asupra pointerului insusi
-asupra obiectului pe care il pointeaza.
Exemplu: int *p, q; p=&q; q=1; printf(“Adresa lui q este %n”,p);
*p++;//asociativitate de la D la S, intai aplic ++ si apoi * printf(“\n Operatie asupra pointerului q= %d Adresa = %n”,q,p);//q=1,
Adresa va fi incrementata adica mai mare cu 2
Daca ultimele doua instructiuni le inlocuiesc cu:
(*p)++;// obtin intai continutul de la adresa p pe care apoi il incrementez
cu 1 printf(“\n Operatie asupra obiectului q= %d Adresa = %n”,q,p);//q=2,
Adr lui p nu se va modifica
6. TABLOURI SI POINTERI
In cadrul limbajului C exista o legatura intre tablouri si variabile pointeri.
Un nume de tablou fara index este un pointer constant de tipul elementelor tabloului
si are ca si valoare adresa primului element din tablou. Aceasta valoare poate
fi asignata unui alt pointer fiind posibil accesul la elementele tabloului folosind
pointeri aritmetici.
Deosebirea intre variabile de tip pointeri si nume de tablouri este:
-unei variabile pointer i se atribuie valori in timpul executiei, valorile putind
diferi la momente diferite de timp diferite
-numele unui tablou are tot timpul ca si valoare adresa primului sau element
si de aceea se zice ca el este un pointer de tip constant.
Exemplu: a) float ftaba20i, *fptr, ftemp; int l; fptr=ftab;//fptr va contine adresa primului element din ftab ftemp=ftaba0i;//este echiv. cu ftemp=*fptr
De asemenea mai avem:
&ftaba0i ? ftab ftaba0i ? *ftab
&ftabali ? ftab+l ftabali ? *(ftab+l) ftaba4i ? *(ftab+4) iar expresia:
(&ftabali-ftab) == l este totdeauna True. b)
#include <stdio.h> int aa10i=A1,2,3,4,5,6,7,8,9,10S
void main(void)
A int *p; p=a;//p=&aa0i printf(“%d %d %d \n”,*p, *(p+1), *(p+2));//afis. primele 3 elemente
ale tabloului printf(“%d %d %d \n”,aa0i, aa1i, aa2i); ));//afis. primele 3 elemente
ale tabloului
S
Observatii: a)Un pointer se poate indexa ca si cind ar fi un tablou, daca refera un tablou:
Exemplu:
#include <stdio.h> char strai =”Pointeri in C”; void main(void)
A char *p; int l; p=str; // p= Adr. tablou de caractere, sirul str for(l=0;pali;l++) printf(“%c”,pali);//pointerul p a fost folosit
indexat pentr ca pointeaza pe str care este un tablou
S b)Daca un pointer nu pointeaza pe un tablou el nu poate fi indexat:
Exemplu: char *p, ch; int l; p=&ch; for(l=0; l < 10;l++) pali=’A’+l;//Sintactic nu este eroare dar
rezultatul este neprevazut c) Daca p pointeaza pe un tablou ca la a) compilatorul C genereaza cod executabil
mai scurt pentru:
*(p+3) decit pentru pa3i sau mai general *(p+l) respectiv pali; d) Numele unui tablou este un pointer de tip constant deci nu se poate modifica
dar poate fi folosit ca si pointer aritmetic in locul indexarii: char strai;
*(str+1)=’C’; printf(“%c “, *(str+1));// dar nu e permis str++
6.1. Tablouri de pointeri
Tablourile de pointeri se definesc functie de tipul de date la care se refera
astfel:
Exemplu: int *paa20i; //tablou de 20 de pointeri la intregi int adrpoint; paa7i=&adrpoint; //al 8-lea element din tablou va contine adresa lui adrpoint
*paa7i=127; //continutul de la aceasta adresa va fi initializat cu 127
Tablourile de pointeri se folosesc mai ales la crearea de tabele de siruri care
pot fi selectate functe de anumite valori.
Exemplu: char *pai = A
“Out of range”,
“Disk full”
“Paper out”
…
S si fie functia: void Err(int nr_err) //nr_err <=dim_max tablou de pointeri la apelul efectiv
A printf(panr_erri);//tipareste mesajul specific erori nr_err, luat din tabloul
de pointeri la sirurile de caratere
S
6.2. Pointeri catre siruri constante
Prin sir constant intelegem un sir cuprins intre ghilimele.
Exemplu: a) b)
#include <stdio.h> #include <stdio.h> char *p=”unu doi trei”; void main(void) void main(void)
A ? A char *p;//pointer de tip char printf(p);//p initializat pe sir de char p=”unu doi trei” S printf(p);
S
Cele doua constructii sunt echivalente, si am putea utiliza si o a treia varianta: c) char strai =”unu doi trei”; si in functia main am putea folosi: p=str; printf(p);
Pointerii catre siruri de caractere se folosesc atunci cind se doreste tiparirea
unor constante sir lungi ele introducindu-se in general ca si in varianta b),
astfel incit daca mesajul trebuie modificat el se va shimba o singura data la
initializare. Acest mesaj se poate tiparii astfel in mai multe locuri din cadrul
programului intr-un mod foarte simplu.
6.6. INDIRECTAREA MULTIPLA
Cind un pointer pointeaza alt pointer avem un proces numit indirectare multipla.
El arata asfel:
Cind un pointer pointeaza alt pointer, primul pointer contine adresa celui de-al
doilea pointer, pointer care pointeaza locatia care va contine obiectul.
Declararea se face utilizind un asterisc suplimentar in fata numelui pointerului.
Exemplu: char **mp, *p, ch; p=&ch; mp=&p;
**mp=’A’; //lui ch i s-a asignat valoarea A prin 2 pointeri
Indirectarea multipla este utilizata in aplicatiile avansate de C putind fi
continuata la un grad de adincime mai mare, lucru care in practica nu este prea
indicat fiind greu de urmarit.
6.7. ALTE CONSIDERATII REFERITOARE LA POINTERI
Vom prezenta pe puncte unele concluzii semnificative si unele precizari practice
privind pointerii.
1. Daca un pointer p indica o variabila x, atunci expresia (*p) poate aparea
in ori ce context in care este permisa aparitia lui x.
Exemplu:
#include <stdio.h>
int f (int *p)
A return (*p)++;
S//f
void main (void)
A int x=1,y=2; y=f(&x);printf(“x= %d y= %d\n”,x,y);//x=2, y=1 y=f(&x);printf(“x= %d y= %d\n”,x,y);//x=3, y=2
S
2. Daca avem definit un tablou de dimensiunea N atunci indicii variaza de la
0 la N-1, insa este permisa pozitionarea pointerului pe elementul imediat urmator
ultimului element al tabloului, N. Acest lucru este permis datorita mecanismului
de formare a adresei fizice in memorie.
3. In cadrul tablourilor unidimensionale (vectori), compilatorul C converteste
expresia e1ae2i in *((e1)+(e2)) unde e1 si e2 sunt expresii valide si deci operatia
de indexare este comutativa adica e1ae2i ? e2ae1i.
4. Pointerii dublii ofera posibilitatea unor indirectari duble. Tot ce este
valabil la pointerii simpli este valabil si la cei dubli. Tablourile de pointeri
pot fi accesate cu pointeri dubli.
Exemplu: a) char *aa5i;//tablou de 5 pointeri catre caractere char **p;//pointer (dublu) catre caracter p=a;//p va contine adresa primului element al tabloului a, unde voi avea o adresa
spre un caracter, astfel incit p+1 va indica pe aa1i, … b)Fie 10 siruri de caractere ale caror adrese se afla intr-un tablou de pointeri,
char *sira10i si construim o functie care afiseaza aceste siruri de caractere
la un apel. Acest lucru il putem realiza in doua moduri:
-primul mod clasic: char *sira10i; void afis1_sir(char *tpai, int n)
A int l; for(l=0;l < n; l++) if (tpali != NULL) //if (tpali) printf(“%s\n”, tpali);
Safis1_sir iar apelul se efectueaza cu: afis1_sir(sir, 10);
-al doilea mod cu pointeri dubli doar functia de afisare se modifica astfel: void afis2_sir(char **tp, int n)
A char *p;
while (n--)
A p=*tp;
while (*p) putchar( *p++); putchar(‘\n’); tp++;
S//while
S//afis2_sir
Pointerii catre tablouri de tip T au o aritmetica in care unitatea este: dim_tab * sizeof(T), in timp ce pointerii spre tipul T au o aritmetica in care unitatea este sizeof
(T).
Exemplu: int (*p)a10i;//p este un pointer spre un tablou de 10 intregi cu unitatea 10*sizeof(int)
ce rezulta usor tinind cont de observatia 1 adica (*p) putem inlocui cu x, =>
int xa10i. int *pa10i;//p este un tablou de 10 pointeri spre intregi
6. In cazul in care avem tablouri cu mai multe dimensiuni care se declara prin:
T nume_tab ad1iad2i…adni; //unde T este tipul elementelor si d1, d2, …,
dn sunt constante intregi si pozitive, atunci elementele tabloului nume_tab
vor ocupa o zona contigua de memorie de dimensiunea: d1*d2*…*dn * sizeof(T), deci tabloul va fi alocat la adrese succesive
de memorie.
Fie un tablou a de tip T si dimensiuni: d1, d2, …, dn:
T aad1iad2i…adni; si presupunem indicii: i1, i2, …, in astfel incit 0 <= ik <= dk-1, unde k ia valori intre
1 si n. functia f care realizeaza corespondenta intre indicii (i1, i2, …, in)
si adresa lui aai1iai2i…aini se numeste functie de alocare a memoriei
pentru tabloul a, functia de alocare fiind identica pentru toate tablourile
cu aceiasi dimensiune si acelas tip T.
Notind cu d si i multiindicii: d = (d1, d2, …, dn) i = (i1, i2, …, in), si cu
T tipul de baza al tabloului atunci functia de alocare a memoriei pentru un
tablou oarecare a se noteaza cu: fd, T (i,a) , adica adresa elementului i al tabloului a de tipul T si multiindici
d este data in limbajul C prin: fd, T (i,a) = baza (a) + sizeof(T) *a i1d2d3…dn + i2d3d4…dn + i3d4d5…dn + in-1dn + in
i, deci tabloul este alocat la adrese succesive de memorie, ultimul indice variind
cel mai repede. Baza(a) este &aa0ia0i…a0i.
Observatii:
-Functia de alocare nu depinde de prima dimensiune, d1, de aceea uneori putem
sa o ignoram
-La tablourile unidimensionale functia de alocare devine: aaii = baza(a) + sizeof (T) * i
-Un tablou cu mai multe dimensiuni este tratat de catre compilator ca un tablou
cu o dimensiune (si anume prima) care are ca si elemente un tablou cu restul
de dimensiuni, deci nu exista limitari sintactice ale numarului de dimensiuni
in C
Exemplu: int aa2ia3i; //este privit ca si un tablou unidimensional cu 2 elemente aua0i
si aua1i, fiecare din elemente fiind la rindul lui un tablou unidimensional
cu 3 elemente: aua0i => aa0ia0i, aa0ia1i, aa0ia2i aua1i => aa1ia0i, aa1ia1i, aa1ia2i sau daca avem:
T aad1iad2i atunci avem echivalenta cu pointeri: aaiiaji = (*(a+i))aji = *(*(a+i)+j) unde la (a+i) a este vazut ca un pointer
catre un tablou de d2 obiecte de tip T adica se vor aduna d2 * sizeof (T).
7. In cazul transmiterii unui tablou cu mai multe dimensiuni la o functie, este
de preferat a folosi ca si parametru formal un pointer catre tablou, adica adresa
de inceput a acelui tablou. De asemenea se prefera folosirea pointerului de
tip void, in cadrul functiei folosindu-se un pointer specific catre tipul T
si cu conversia explicita catre acel tip.
Exemplu: Afisarea unei matrici:
#include <stdio.h>
#include <stddef.h>
#define FORMAT “%6d”
void afis_mat(void *a, int m, int n)
A int *v = (int *)a; int mn = m*n; ptrdiff_t d;//definire din stddef d ca si pointer diferenta
while ((d=v-(int*)a) < mn) printf ((d%n == n-1) ? FORMAT “\n”
: FORMAT, *v++);
S
8. Cu modificatorul const putem declara:
-pointeri constanti, care nu pot fi modificati ca si la tablouri
-pointeri catre constante, care nu pot fi modificate
Exemplu: const int a = 10, *pc =&a;//a intreg constant, pc pointer catre un intreg
constant deci pot modifica pointerul pc dar nu pot modifica continutul, *pc. int b, *const cp =&b; //cp este un pointer constant nu-l pot modifica dar
*cp pot modifica
Putem avea si cazul pointeri constanti catre constante unde nu putem modifica
nimic.
9. In limbajul C deseori se folosesc functii care returneaza pointeri. Una din
aceste functii care copiaza un sir de caractere de la o sursa la o destinatie
s-ar putea scrie astfel: char * strcpy(char *dest, const char *sursa)
A char *p = dest;
while (*p++ = *sursa++); return dest;
S//strcpy
Aceste functii care returneaza pointeri se folosesc mai ales la:
-alocarea dinamica a memoriei
-liste, arbori.
O functie care aloca in mod dinamic un spatiu pentru un obiect se numeste constructor.
O functie care primeste un pointer la o zona de memorie alocata dinamic si elibereaza
aceasta zona se numeste destructor. (Vom reveni la alocarea dinamica a memoriei)
6.8. POINTERI SPRE FUNCTII
O functie ca si o structura sau un tablou are asociata o adresa fixa de memorie.
Un pointer catre o functie va contine aceasta adresa.
Declararea unui pointer spre o functie trebuie sa precizeze:
-tipul functiei
-numarul si tipul parametrilor.
Forma generala a declaratiei este: tip (*pf) (lista_param_formali);
Este de remarcat precizarea parametrilor iar ca si regula practica declaratia
se face ca si cum am scrie un prototip de functie, numele functiei fiind substituit
de (*nume_pointer).
Exemplu: char * (*p) (char *, const char *); //este prototipul prin care p se defineste
ca fiind un pointer la o functie care:
-are 2 parametri, unul char * altul const char *
-returneaza un pointer la caracter, char *.
De remarcat ca acest prototip poate fi asociat functiei strcpy, prezentata in
precedentul subcapitol.
Numele unei functii fiind sinonim cu adresa de inceput a ei putem avea: p=strcpy; //sau p=&strcpy;
Apelul unei functii prin intermediul unui pointer se face cu constructia:
(*pf) (lista_param_actuali); iar daca functia returneaza o valoare care se poate atribui unei variabile var
(functie cu tip) atunci apelul poate fi: var=(*pf) (lista_param_actuali);
Observatie:
Standardul ANSI permite a scrie apelul fara a folosi operatorul de indirectare
* adica: pf (lista_param_actuali);sau var = pf (lista_param_actuali);
Pointerii la functii se folosesc la:
-construirea unor tablouri care sa contina adrese de functii care pot fi apelate
indirect
-folosirea functiilor polimorfice care sunt proiectate pentru a se putea aplica
ori caror tipuri de date
6.9. TRANSFERAREA DE ARGUMENTE CATRE FUNCTIA MAIN
La lansarea in executie multe aplicatii permit specificarea unor argumente
in linia de comanda.
Exemplu:
WP nume_fisier sau
WIN nume_program sub Windows 3.x.
Programele scrise in limbajul C permit introducerea de argumente in linia de
comanda, argumente care sunt transferate prin doi parametri ai functiei main,
argc si argv:
-argc, contine numarul argumentelor si este definit ca si un intreg, int argc
-argv, este un tablou de pointeri catre siruri declarat prin, char * argvai sau char **argv, unde: argva0i pointeaza pe numele programului care se lanseaza in executie argva1i pointeaza pe primul argument, s.a.m.d.
Intre argumente se accepta spatiu, tabulator ca si separator, iar daca un argument
va contine spatiu, acel argument va fi incadrat intre ghilimele.
Numele argc si argv nu sunt impuse, putem avea si alte doua nume.
Pentru ca aceste argumente se primesc sub forma de siruri de caractere, aceste
siruri trebuie convertite spre un format intern de reprezentare in memorie si
anume:
-pentru intregi, int atoi(char *str), returneaza intregul echivalent sirului
-pentru double, double atof(char *str), returneaza val. reala echivalenta sirului
-pentru long, long atol(char *str), returneaza val. long echivalenta sirului.
Aceste functii folosesc headerul stdlib.h.
Observatie:
In standardul ANSI se mai introduce un al 3-lea parametru optional ca si argument
la functia main, tot ca si un tablou de pointeri catre siruri de caractere,
care contine o descriere a mediului in care este rulat programul si depinde
de implementarea. Acest parametru nu se prea foloseste, dar ori cum cei 3 parametrii
ca sunt sau nu folositi sunt alocati pe stiva.
Exemplu: Prezentam un program poundk.exe, care transforma argumentul flotant
care i se da ca un sir de caractere la apel si reprezentind numarul de kilograme
in pounds.
#include <stdio.h>
#include <stdlib.h>
void main (int argc, char *argvai)
A double pounds; if (argc != 2) Aprintf(“Nu ati introdus arg. Kg. Reluati lansind din nou
aplicatia”); exit(1);
S else A pounds = atof(argv a1i)/0.453; printf(“%lf Pounds”,pounds);
S
S//main
7. ALTE CONSIDERENTE PRIVIND STRUCTURILE DE DATE
Am prezentat citeva notiuni legate de structuri de date de tip struct pentru
a introduce operatorii . (punct) si -> (sageata).
Am precizat ca o structura reprezinta o multime ordonata de elemente grupate
in vederea utilizarii lor in comun.
Exista mai multe formate de descriere a structurilor, cel mai folosit fiind:
struct anume_structi
A lista_declaratii;
Sanume1,nume2,…,numeni unde:
-nume_struct, este un nou tip de date definit de catre utilizator, de tip struct
cu constructia struct.
-lista_declaratii, este o lista prin care se declara componentele unei structuri,
putind contine elemente de forma: tipi elementi, unde tipi este un tip admis de limbajul C inclusiv o noua structura,
-nume1,…,numen sunt o lista de variabile de tip nume_struct, putind lipsi
la definirea structurii caz in care este obligatoriu sa fie precizat nume_struct.
Daca avem nume_struct si nu avem numei atunci putem defini ulterior alte structuri
de tip nume_struct cu declaratia: struct nume_struct nume_nou; unde
-nume_struct reprezinta un nou tip de data un tip utilizator
-nume_nou este o variabila de tip nume_struct.
Elementele unei structuri se numesc cimpuri sau membri.
Exemplu: O structura de tip catalog de carti se poate declara prin: struct catalog
A char numea20i;//nume autor char titlua40i;//titlu carte char edituraa30i;//editura long data;//data aparitiei unsigned char editia;//editia
Scarte;
Deci catalog este tipul nou de date struct cu formatul precizat mai sus.
Variabila carte este o variabila de tip catalog si deci are o structura de forma: nume 20 octeti titlu 40 octeti editura 30 octeti data 4 octeti editia 2 octeti.
Accesul la elementele unei structuri, cimpuri, se face precizind numele variabilei
de tip structura, carte, si cimpul care ne intereseaza separate prin operatorul
. (punct).
Acest acces se numeste acces prin nume calificat.
Exemplu: carte.editia=2;//asigneaza valoarea 2 cimpului editia din structura cu numele
carte care este de tip catalog
Observatii:
-1)In cazul in care se foloseste functia scanf pentru a introduce de la intrare
un cimp intr-o structura, operatorul de adresare, & se pune in fata numelui
structurii si nu in fata numelui cimpului: scanf(“%u”, &carte.editia);
-2)Un caracter dintr-un cimp ce este un tablou, de exemplu din titlu, se acceseaza
prin: carte.titlua4i;//a cincea litera din titlu
-3)De obicei nume_struct, ca si tip nou de data este totdeauna folosit, dar
cite o data cind se cunoaste exact cite variabile de acest nou tip se vor folosi
el poate fi omis.
Exemplu: struct A int a; float b; char c;
Svar1,var2;
-4)Un tablou de structuri se declara considerind struct nume_struct ca fiind
tipul tabloului.
Exemplu: struct catalog cartia100i;
… cartia16i.editia=2;//accesam cimpul editia din cea de-a 17-a structura
-5)Continutul unei variabile de structura poate fi asignat unei alte variabile
de structura de acelasi tip.
Exemplu: struct valori
A int a; float b; char c;
Svar1,var2;
… var1.a=10; var1.b=12.4; var1.c=’A’; var2=var1;//cele doua structuri vor contine aceleasi valori
-6)Dimensiunea unei structuri se determina cu operatorul sizeof care asigura
si portabilitatea programelor in cadrul diferitelor medii de programare. Lucrul
acesta este necesar pentru ca unele compilatoare solicita alinierea unor tipuri
de date la adrese de cuvint. Operatorul sizeof cind este folosit cu o structura
trebuie precedat de cuvintul cheie struct.
Exemplu:
#include <stdio.h>
struct nume_struct
A int a; float b; char c; int *p;
S s;
void main(void)
A printf(“Structura nume_struct are lungimea =%d octeti\n”,sizeof(struct
nume_struct));
S
-7)Numele elementelor unei structuri este separat de alte variabile care poarta
acelas nume, neaparind conflict deci cu alte variabile cu acelas nume.
Exemplu:
#include <stdio.h>
void main(void)
A struct tip_s
A int l; int j;
Ss; int l; l=10; s.l=100; s.j=1000; printf(“l= %d s.l= %d s.j= %d”,l,s.l,s.j);
S
-8)Structurile pot fi transferate ca si parametri functiilor, iar o functie
poate sa returneze o structura. Cind transferam o structura unei functii prin
apelarea prin valori, atunci se transfera intreaga structura. Acest mod de transfer
nu este valabil la toate compilatoarele, de aceea in general structurile nu
se transfera prin valori ci prin pointeri spre structura.
Exemplu: a)Transfer structura catre o functie:
#include <stdio.h> struct tip_s
A int a; float b; char c;
Ss; void functie (struct tip_s);//prototip
void main(void)
A s.a=10; s.b=12.4; s.c=’A’; functie(s);
S//main void functie(struct tip_s temp)
A printf(“a=%d, b=%f, c=%c \n”,temp.a,temp.b,temp.c);
S//functie b)Functie care returneaza o structura:
#include <stdio.h> struct tip_s
A int a; float b; char c;
Ss; struct tip_s f(void);//prototip
void main (void)
A s=f(); printf(“a=%d, b=%f, c=%c \n”,s.a,s.b,s.c);
S//main
struct tip_s f(void)
A struct tip_s temp; temp.a=37; temp.b=17.21 temp.c=’O’; return temp;
S//f
7.1. DECLARAREA DE POINTERI LA STRUCTURI
Un pointer catre o structura se declara la fel ca si ori ce pointer catre
ori ce alt tip de variabila.
Exemplu: struct tip_s
A int l; char stra20i;
Ss, *p;//s variabila de tip structura tip_s, p pointer catre structura tip_s
… p=&s;//pointerul p va contine adresa structurii s, de fapt va pointa pe
adresa primei componente a structurii s adica variabila l
In acest caz accesul la elementele structurii s se va face apel prin:
s.l=17; sau
(*p).l=17; sau p -> l=17;
Observatii:
Cind este folosit apelul prin valori, limbajul C transfera functiilor structurile
care sunt apelate in intregime. Daca dimensiunea este mare, transferul duce
la reducerea vitezei de lucru a programului si pot sa apara probleme si cu dimensiunea
stivei.
In aceste cazuri este recomandabil sa transferam functiei un pointer catre structuri
si nu structura insasi.
De obicei, cind accesam un element dintr-o structura printr-o variabila de structura
folosim operatorul . (punct). Acest acces se numeste acces prin nume calificat.
Cind accesam un element dintr-o structura printr-un pointer folosim operatorul
sageata, ->.
Exemplu: struct data_calend
A int zi; char lunaa11i; int an;
Sdc;
Structura dc poate fi prelucrata de functia f daca ea se apeleaza prin parametrul
efectiv &dc, adica adresa de inceput a zonei de memorie unde este alocata
structura dc prin: f(&dc);
Functia f va avea in acest caz antetul: void f(struct data_calend *p), deci p va avea ca si valoare o adresa de inceput
a zonei de memorie in care se pastreaza date de tip data_calend cum este si
&dc.
Accesul la componentele structurii in corpul functiei f nu se face cu nume calificate,
pentru ca nu este cunoscut numele structurii dc ci doar pointerul p spre aceasta
structura. Deci vom inlocui numele dc cu *p si vom avea:
(*p).zi sau p ->zi
(*p).luna sau p ->luna
(*p).an sau p ->an
Observatii:
-1)Structurile sunt date ca ori ce alte date variabile simple sau tablouri.
Ele pot fi alocate in acelas mod, adica:
-global, daca sunt declarate in afara corpului ori carei functii
-local (automatic) daca sunt declarate in corpul unei functii
-static, daca declaratia incepe cu cuvintul static.
-2)Datele elementare care sunt componente ale unei structuri se pot initializa.
Astfel dupa variabila nume de structura intre acolade si separate prin virgule
se initializeaza componentele elementare ale structurii. Considerind ddc o structura
de tip data_calend definita in prealabil, ea poate fi initializata astfel: struct data_calend ddc = A6,”septembrie”,1997S;
7.2. STRUCTURI INCLUSE UNA IN ALTA
Printre elementele unei structuri se pot gasi si alte structuri, numite structuri
incluse (inclusiv pointeri catre structura insasi caz ce se intilneste mai ales
in cadrul listelor si a arborilor definiti recursiv).
Structurile sunt referite din exterior spre interior deci se incepe cu structura
cea mai exterioara si se incheie cu structura cea mai interioara.
Exemplu: struct data_calend
A int zi; char lunaa11i; int an;
S
struct date_pers
A char numea15i; char prenumea20i; long cod; struct data_calend data_nast; struct data_calend data_angaj;
Sangajat;
Pentru a defini data nasterii vom folosi o dubla calificare astfel: angajat.data_nast.zi=9; angajat.data_nast.luna=”noiembrie”; angajat.data_nast.an=1977;
7.3. REUNIUNI
In limbajul C exista posibilitatea de a pastra in aceiasi zona de memorie
date de diferite tipuri.
Exemplu: long x;//variabilei x i se aloca o zona de memorie de 32 biti unde se pastreaza
intregi in conventia C2.
Daca vrem ca aceasta zona sa o reutilizam si sa pastram aici date de alt tip
si anume char, int sau float, pentru ca se integreaza in 32 de biti, vom face
o grupare impreuna a datelor care vrem sa fie alocate in aceiasi zona de memorie.
Pentru a realiza o astfel de grupare se foloseste o constructie similara cu
struct numita union.
Union este un nou tip definit de utilizator si-l vom numi reuniune.
Exemplu: union a
A int x; long y; double r; char c;
Su;
Constructie prin care s-a definit:
-un nou tip de data a de tip reuniune
-o variabila de tip reuniune a, cu numele u.
Pentru aceasta variabila u, se aloca o zona de memorie suficient de mare pentru
a putea pastra date care necesita numarul maxim de octeti, in cazul nostru 8
plus eventuale alinieri care pot duce la marirea zonei.
Toate aceste date sunt alocate in aceiasi zona de memorie (de 8 octeti) la un
moment dat al executiei numai una din aceste date fiind definita.
Observatie: Daca in loc de union am scrie struct, atunci prin a vom defini o
structura, u fiind o variabila de tip structura si la care toate componentele
structurii sunt definite simultan care trebuie alocate in zone diferite de memorie
in acest caz minim 15 octeti.
Accesul la elementele x, y, r, c ale reuniunii se face ca si la structuri folosind
operatorul . (punct) sau -> (sageata).
Exemplu:
Consideram reuniunea a declarata ca mai inainte. Vom declara in continuare prin: union a w, *p; //w o variabila de tip a, p pointer la o reuniune de tip a p=&w; //atribuie lui p adresa primului element din reuniune. Accesul la
componentele reuniuni se va face la momente diferite de timp cu:
w.x sau p ->x
w.y sau p ->y
w.r sau p ->r
w.c sau p ->c
Observatii:
-1)In cazul reuniunilor programatorul trebuie sa cunoasca in fiecare moment
al executiei care componenta a reuniunii este prezenta in zona alocata ei.
Astfel daca avem atribuirea:
w.x=17; si imediat dupa aceea: if (w.y > 0) nu se va semnala eroare nici la compilare nici la executie,
dar programatorul ar putea evita eroarea lucrind in mod corect.
-2)Reuniunile nu pot fi initializate ca si structurile
-3)Dimensiunea reuniunilor se poate determina ca si in cazul structurilor cu
ajutorul operatorului sizeof.
7.4. CIMPURI DE BITI (bit fields)
Un cimp de biti este un element al unei structuri care cuprinde unul sau mai
multi biti adiacenti.
Cu ajutorul cimpurilor de biti se pot accesa prin nume, unul sau mai multi biti
dintr-un octet sau cuvint.
Un cimp de biti trebuie sa se poata aloca intr-un cuvint calculator. De asemenea
mai multe cimpuri de biti daca incap pot fi pastrate in acelas cuvint calculator.
Cimpurile de biti se pot grupa formind o structura. O structura care contine
mai multe cimpuri de biti se declara astfel: struct nume_struct
A cimp1; cimp2;
… cimpn;
Snume1, nume2, …, numem;
Un cimpi se declara astfel: tip cimpi: lungime_in_biti; sau prin: tip: lungime_in_biti; // cind definim o zona neutilizata. tip este un cuvint cheie, de obicei unsigned, care inseamna ca sirul de biti
este interpretat ca un intreg fara semn. Pentru tip se mai pot folosi si:
-int
-unsigned char
-char.
Observatii:
-Cimpurile se aloca de la bitii de ordin inferior ai cuvintului spre cei de
ordin superior.
-Cimpurile cu semn se utilizeaza pentru a pastra intregi de valori mici utilizind
conventia C2. In acest caz bitul cel mai semnificativ al cimpului este bitul
de semn.
-Daca un cimp nu se poate aloca in cuvintul curent, atunci el se va aloca in
cuvintul urmator.
-Un cimp fara nume nu se poate referi. El defineste o zona neutilizata dintr-un
cuvint.
-Lungimea in biti alocata pentru un cimp de biti poate fi egala cu zero. In
acest caz data urmatoare din structura cu cimpuri de biti va fi alocata in cuvintul
urmator.
-Cimpurile de biti se pot referi folosind aceleasi conventii ca si in cazul
structurilor obisnuite.
Exemplu: struct cimp_biti
A unsigned a:2; int b:2; unsigned :3; unsigned c:2; unsigned :0//trec la urmatorul cuvint int d:5; char e:7;
Sx,y;
In acest caz pentru variabila x (si y) se aloca doua cuvinte astfel:
15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
|………………………|…c..|…………|…b..|…a..|
|……………|……….e……………|……d…….…|
Atribuiri de valori se pot face astfel: x.a=1 => cimpul a va avea valoarea 1, adica se va reprezenta prin 01 x.b=-1 => cimpul b va avea valoarea -1, adica se va reprezenta prin 11
Observatii:
-1)Unele compilatoare permit definirea de tablori de cimpuri de biti, altele
nu. Daca se pot defini, am putea utiliza o declaratie de forma: struct cimp_biti tabl_cimpa10i;
Referirea se va face prin: tabl_cimpaii.a=1;
-2)Octetul fiind cea mai mica unitate de memorie adresabila la calculatoarele
compatibile IBM PC, adresa de memorie a unui cuvint de biti nu se poate obtine
cu ajutorul operatorului de adresare, & care nu se poate aplica la un cimp
de biti.
-3)Datele pe biti duc la programe care de obicei nu sunt portabile sau cu portabilitate
redusa. Ele necesita instructiuni suplimentare cum ar fi deplasari, setari,
si/sau mascari de biti.
-4)Cimpurile de biti sunt folosite cind lucram cu date booleene permitind folosirea
eficienta a memoriei, 8 date booleene pe octet.
-5)Prelucrarea datelor pe biti se poate face si cu ajutorul operatorilor logici
pe biti care duc insa in general la un efort mai mare in programare.
7. ASIGNARI DE NUME PENTRU TIPURI DE DATE
Pina acum am constatat ca avem:
-tipuri predefinite de date, identificate prin cuvinte cheie: char, int, float,
etc.
-tipuri utilizator definite cu constructii de forma: struct nume_struct
A
…
S sau union nume_reuniune
A
…
S.
In limbajul C se poate atribui un nume unui tip predefinit sau utilizator cu
o constructie de forma: typedef tip nume_tip; unde:
-tip, este tipul predefinit sau utilizator
-nume_tip, este noul nume care se atribuie tipului definit de tip.
Noul tip, nume_tip, poate fi utilizat in continuare pentru a declara date de
acelas tip (De obicei noul tip se scrie cu litere mari, dar nu este obligatoriu
acest lucru).
Exemple:
1) typedef unsigned char BYTE;//BYTE este noul nume_tip
BYTE x, y;//definiri de variabile cu noul nume_tip
-2) typedef struct data_calend
A int zi; char lunaa11i; int an;
SDC;
DC data_nast, data_angaj, *p;
… p=&data_nast; p -> zi=8;
-3) typedef union
A char numea40i; int urmat; long cod;
SZC;
ZC sir, *p;
… p=&sir; p -> numea0i=’A’; p -> cod=17;
-4) typedef struct
A double real; double imag;
SCOMPLEX;
COMPLEX z, tza10i;
-5) typedef struct
A unsigned int x; unsigned int y; int val;
SPIXEL;
PIXEL a,b;//defineste 2 variabile a si b ce pot fi pixeli intr-o imagine
-6) typedef int * PINT;//defineste pointer la intreg
PINT p, ta100i;// este identica cu: int *p, *ta100i;
7.6. ENUMERARI (Enumeration, enum)
Tipul enumerare permite programatorului sa defineasca o lista de constante
intregi cu nume in vederea folosirii de nume sugestive pentru valori numerice.
Tipul enumerare se declara printr-un format asemanator cu cel utilizat in cadrul
structurilor si anume:
enum nume_enum
A nume0, nume1, …, numek
Sd1, d2, …, dn; unde:
-nume_enum, este numele noului tip de date utilizator introdus prin aceasta
declaratie
-nume0, nume1, …, numek, sunt nume care se vor utiliza in locul valorilor
numerice si anume numei va avea valoarea i (ca si intreg)
-d1, d2, …, dn, sunt variabile enum de noul tip nume_enum.
Exemple:-1) enum Boolean Afase,trueS bisect;// ce implica false=0 si true=1 si putem folosi
expresii conditionale de forma bisect == false sau bisect == true
-2) enum Aileg, ian, feb, mar, apr, mai, iun, iul, aug, sep, oct, nov, decS luna;
Avind aceasta declaratie putem folosi in loc de: luna=3; luna=mar;//pentru ca mar=3
-3)La declararea tipului enumerare se poate folosi cuvintul cheie typedef ca
si in cazul tipurilor utilizator struct si union astfel: typedef enum nume_enum Anume0, nume1, …, numekS nume_tip; in continuare putem folosi: nume_tip d1, d2, …, dn; typedef enum Afalse, trueSBOOL;
BOOL bisect;
-4)Daca avem o functie care returneaza doua valori 0 sau 1 putem defini noul
tip BOOL la definirea antetului functiei astfel:
BOOL f(lista_param_formali)
-5)Tipul enumerare poate fi definit impunind si alte valori diferite de cele
care rezulta implicit, adica de la 0 si tot crescind cu o unitate.
In acest caz in locul unde dorim sa impunem o noua valoare, vom folosi urmatoarea
constructie:
…, numei = eci, numei+1,… astfel incit numei va lua valoarea eci, eci fiind o expresie constanta, numei+1
va lua valoarea eci+1, si tot asa mai departe valoarea de atribuire va creste
cu o unitate la fiecare nume nou: typedef enum Aian=1, feb, mar, apr, mai, iun, iul, aug, sep, oct, nov, decS
DL;
-6)Datele de tip enum nu sunt supuse controlului compilatorului C, ele fiind
tratate ca si simple date de tip int. De asemenea aceste date nu sunt cunoscute
de functiile din bibliotecile standard. In acest caz cu ele se pot scrie expresii
care sa nu corespunda scopului pentru care s-au definit:
DL d1, d2, d3;//DL este tipul definit inainte d3 = d1+d2; d3 = d1*d2; nu sunt interpretate ca eronate datele fiind tratate ca si intregi
7.7. DATE DEFINITE RECURSIV
Declaratia de forma, struct nume_struct
A declaratii;
S; defineste componentele datei structurate de tip nume_struct.
Aceste declaratii pot defini date de diferite tipuri predefinite sau utilizator,
diferite insa de tipul nume_struct.
Se pot insa defini pointeri spre datele de tip nume_struct chiar in cadrul declaratiei
nume_struct cu o declaratie de forma: struct nume_struct
A declaratii; struct nume_struct *nume1; declaratii;
S;
Daca in loc de *nume1 avem nume1, declaratia este eronata.
Un tip utilizator este direct recursiv, daca are cel putin o componenta care
este de tip pointer spre ea insasi.
Exemple:-1) struct lista
A char *token; int count; struct lista *next;
Slinie; //lista este un tip utilizator direct recursiv, next fiind un pointer
catre lista
-2)
typedef struct tnod
A char cuvinta100i; int nr; struct tnod *urm;
STNOD; si putem defini in continuare date de tip TNOD prin:
TNOD nod, *p;
Pentru a defini tipul de data indirect recursiv, adica un tip t1 care contine
un pointer spre tip