2.1 Declaratii
Exemple de declaratii: char ch; int count = 1; char* name = "Bjarne"; struct complexA float re,im; S complex cvar; extern complex sqrt(complex); extern
int error_number; typedef complex point; float real(complex* p)A return p->re;
S; const double pi = 3.1415926535897932385; struct user; z8d24dj
Majoritatea acestor declaratii sint de asemenea si definitii; adica ele definesc
o entitate pentru numele la care se refera. Pentru ch, count si cvar, aceasta
entitate este o cantitate corespunzatoare de memorie care sa se utilizeze ca
o variabila. Pentru real, entitatea este o functie specifica.
Pentru constanta pi entitatea este o valoare 3.1415... . Pentru complex, entitatea
este un tip nou. Pentru point, entitatea este tipul complex asa ca point devine
un sinonim pentru complex. Numai declaratiile extern complex sqrt(complex);
extern int error_number; struct user; nu sint si definitii. Adica, entitatile
la care se refera ele trebuie sa fie definita altundeva. Codul pentru functia
sqrt() trebuie sa fie specificat printr-o anumita alta declaratie, memoria pentru variabila error_number de tip
intreg trebuie sa fie alocata printr-o anumita alta declaratie a lui error_number,
iar o anumita alta declaratie a tipului user trebuie sa defineasca cum arata
acel tip. Trebuie totdeauna sa fie exact o definitie pentru fiecare nume dintr-un
program C++, dar pot fi multe declaratii si toate declaratiile trebuie sa fie
compatibile cu tipul entitatii referite, asa ca fragmentul de mai jos are doua
erori: int count; int count; // error : redefinition extern int error_number; extern short error_number; // error : type mismatch
Anumite definitii specifica o "valoare" pentru entitatile pe care
le definesc ele: struct complexA float re,im; S; typedef complex point; float real(complex* p)A
return p->re S; const double pi=3.1415926535897932385;
Pentru tipuri, functii si constante "valoarea" este permanenta. Pentru
tipuri de date neconstante valoarea initiala poate fi schimbata ulterior: int count = 1; char* name = "Bjarne";
//................ count = 2; name = "Marian";
Numai definitia char ch; nu specifica o valoare. Orice declaratie ce specifica o valoare este o definitie.
2.1.1 Domeniu
O declaratie introduce un nume intr-un domeniu; adica un nume poate fi utilizat
numai intr-o parte specifica a textului programului. Pentru un nume declarat
intr-o functie (adesea numit nume local), domeniul lui se extinde din punctul
declaratiei pina la sfirsitul blocului in care apare declaratia lui. Pentru
un nume care nu este intr-o functie sau intr-o clasa (adesea numit nume global),
domeniul se extinde din punctul declaratiei pina la sfirsitul fisierului in
care apare declaratia lui. Un nume poate fi redefinit intr-un bloc pentru a
referi o entitate diferita in blocul respectiv. Dupa iesirea din bloc numele
isi reia intelesul lui precedent. De exemplu: int x; //global x f()
A int x; //local x. Ascunde globalul x x = 1; //asignarea la x local
A int x; //ascunde primul local x x = 2; //asignarea la cel de al doilea local
S x = 3; //asignarea la primul local x
S int* p = &x; //ia adresa globalului x
Ascunderea numelor este inevitabila cind se scriu programe mari. Totusi, un
cititor poate usor sa nu observe ca un nume a fost ascuns si erorile datorate
acestui fapt sint foarte dificil de gasit. In consecinta ar trebui minimizat
numarul numelor ascunse. Utilizarea numelor de felul lui i si x pentru variabile
globale sau locale in functii mari poate sa ne conduca la erori.
Este posibil sa se utilizeze un nume global ascuns utilizind operatorul de rezolutie
a domeniului "::". De exemplu: int x; f()
Aint x = 1; // ascunde globalul x
::x = 2; // asigneaza lui x global
S
Nu exista un mod de a utiliza un nume local ascuns. Domeniul unui nume incepe
in punctul declaratiei lui; aceasta inseamna ca un nume poate fi utilizat chiar
pentru a specifica valoarea lui initiala. De exemplu: int x; f()A int x = x; S
Aceasta este legal dar este fara sens. Este posibil sa utilizam un singur nume
pentru a ne referi la doua obiecte diferite intr-un bloc fara a utiliza operatorul
"::". De exemplu: int x = 11; f()
A int y = x; // global x int x = 22; y = x; // local x
S
Variabila y este initializata cu 11, valoarea globalului x, iar apoi i se atribuie
valoarea 22 a variabilei locale x. Numele argumentelor unei functii se considera
declarate in blocul cel mai exterior functiei, deci f(int x)
A int x; // eroare
S eroare, deoarece x este declarat de doua ori in acelasi domeniu.
2.1.2 Obiecte si Lvalori
Se pot aloca si utiliza "variabile" ce nu au nume si este posibil
sa se faca atribuiri la expresii care arata straniu (de exemplu *paa+10i=7).
In consecinta, exista nevoia de nume pentru "ceva aflat in memorie".
Iata ghilimelele corespunzatoare pentru a face referire la manualul C++: "Un
obiect este o regiune de memorie; o lvaloare este o expresie care refera un
obiect" (&r.5). Cuvintul lvalue original a fost desemnat pentru a insemna
"ceva ce poate fi in partea stinga a unei atribuiri". Totusi, nu orice
lvalue poate fi utilizata in partea stinga a unei atribuiri; se poate avea o
lvaloare care face referire la o constanta (vezi &2.4).
2.1.3 Durata de viata
Daca programatorul nu specifica altfel, un obiect este creat cind definitia
lui este intilnita si distrus cind numele lui iese afara din domeniu. Obiectele
cu nume globale se creeaza si se initializeaza numai odata si "traiesc"
pina cind se termina programul. Obiectele definite printr-o declaratie cu cuvintul
cheie static se comporta la fel. De exemplu, (directiva #include <stream.h>
a fost lasata afara din exemplele din acest capitol pentru a face economie de
spatiu. Este necesar sa fie prezenta pentru exemplele care produc iesiri): int a = 1; void f()
A int b = 1; //se initializeaza la fiecare apel a lui f() static int c = 1; //se initializeaza numai odata cout << "a=" << a++ << "b=" << b++
<<"c=" <<c++ <<"\n";
S
main()
A
while(a < 4) f();
S
produce iesirea: a = 1 b = 1 c = 1 a = 2 b = 1 c = 2 a = 3 b = 1 c = 3
O variabila statica care nu este explicit initializata este initializata cu
zero (&2.4.5).
Utilizind operatorii new si delete, programatorul poate crea obiecte a caror
durata de viata poate fi controlata direct (&3.2.4).
2.2 Nume
Un nume (identificator) consta dintr-un sir de litere si cifre. Primul caracter
trebuie sa fie litera. Caracterul subliniere _ se considera a fi o litera. C++
nu impune limite asupra numarului de caractere dintr-un nume, dar anumite implementari
nu sint sub controlul scriitorului de compilatoare (in particular, incarcatorul).
Anumite medii de executie sint de asemenea necesare pentru a extinde sau restringe
setul de caractere acceptat intr-un identificator; extensiile (de exemplu, cele
care admit caracterul $ intr-un nume) produc programe neportabile. Un cuvint
cheie C++ (vezi &r.2.3) nu poate fi utilizat ca un nume. Exemple de nume: hello this_is_a_most_unusually_long_name
DEFINED fo0 bAr u_name HorseSence var0 var1 CLASS _class ___
Exemple de siruri de caractere care nu pot fi utilizate ca identificatori:
012 a fool $sys class 3var pay.dul foo-bar .name if
Literele mari si mici sint distincte, asa ca Count si count sint nume diferite,
dar nu este indicat sa se aleaga nume care difera numai putin unul de altul.
Numele care incep cu subliniere se utilizeaza de obicei pentru facilitati in
mediul de executie si de aceea nu se recomanda sa se utilizeze astfel de nume
in programele aplicative.
Cind compilatorul citeste un program, el totdeauna cauta cel mai lung sir care
poate forma un sir, asa ca var10 este un singur nume si nu numele var urmat
de numarul 10, iar elseif un singur nume, nu cuvintul cheie else urmat de if.
2.3 Tipuri
Orice nume (identificator) dintr-un program C++ are un tip asociat cu el. Acest
tip determina ce operatii pot fi aplicate asupra numelui (adica la entitatea
referita prin nume) si cum se interpreteaza aceste operatii. De exemplu: int error_number; float real(complex* p);
Intrucit error_number este declarat sa fie int, lui i se pot face atribuiri,
poate fi folosit in expresii aritmetice, etc..
Functia real, pe de alta parte, poate fi aplicata cu adresa unui complex ca
parametru al ei. Este posibil sa se ia adresa oricaruia din ei. Anumite nume,
cum ar fi int si complex, sint nume de tipuri. Un nume de tip este utilizat
pentru a specifica tipul unui alt nume intr-o declaratie. Singura alta operatie
asupra unui nume de tip este sizeof (pentru a determina cantitatea de memorie
necesara pentru a pastra un obiect de acel tip) si new (pentru alocare de memorie
libera pentru obiectele de tipul respectiv). De exemplu: main()
A int* p = new int; cout << "sizeof(int) =" << sizeof(int)
<< "\n";
S
Un nume de tip poate fi utilizat ca sa specifice explicit conversia de la un
tip la altul (&3.2.4). De exemplu: float f; char* p; long ll = long(p); // converteste p spre long int i = int(f); // converteste f spre int
2.3.1 Tipuri fundamentale
C++ are un set de tipuri fundamentale ce corespund la cele mai comune unitati
de memorie ale calculatoarelor si la cele mai fundamentale moduri de utilizare
ale lor. char short int int long int , pentru a reprezenta intregi de diferite dimensiuni; float double ,pentru a reprezenta numere in flotanta; unsigned char unsigned short int unsigned int unsigned long int ,pentru a reprezenta intregi fara semn, valori logice, vectori de biti, etc..
Pentru o notatie mai compacta, int poate fi eliminat dintr-o combinatie de multicuvinte
(de exemplu short este de fapt short int) fara a schimba intelesul; astfel long
inseamna long int iar unsigned inseamna unsigned int. In general, cind un tip
este omis intr-o declaratie, se presupune ca s-a omis int. De exemplu: const a = 1; static x;
fiecare defineste un obiect de tip int.
Intregul de tip caracter este cel mai potrivit pentru a mentine si manipula
caractere pe un calculator dat; acest tip este de obicei pe 8 biti. Dimensiunile
obiectelor din C++ sint exprimate in termeni multipli ai dimensiunii lui char,
asa ca, prin definitie sizeof(char) = 1. Depinzind de hardware, un char este
un intreg cu sau fara semn. Tipul caracter fara semn este sigur totdeauna fara
semn (unsigned char) si utilizindu-l produce programe mai portabile, dar poate
sa fie mai putin eficient decit daca este folosit ca tip char obisnuit. Motivul pentru a funiza mai multe tipuri de intregi,
mai multe tipuri de intregi fara semn si mai multe tipuri de flotante este pentru
a permite programatorului sa utilizeze avantajos caracteristicile hardware. Pe multe masini
exista diferente semnificative in cerintele de memorie, timpul de acces la memorie
si viteza de calcul dintre diferite varietati a tipurilor fundamentale. Cunoscind
o masina, de obicei este usor a alege, de exemplu, tipul de intreg potrivit
pentru o variabila particulara. A scrie cod de nivel inferior portabil cu adevarat
este foarte greu. Ceea ce este garantat in legatura cu dimensiunile tipurilor
fundamentale este: sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) sizeof(float) <= sizeof(double)
Cu toate acestea, de obicei este rezonabil sa presupunem ca tipul char poate
pastra intregi in intervalul 0..127 (el poate totdeauna pastra un caracter din
setul de caractere al masinii), ca un short si un int au cel putin 16 biti,
ca un int este apropiat de o dimensiune potrivita pentru aritmetica intregilor
si ca un long are cel putin 24 de biti. A presupune mai mult este hazardos si
chiar aceste reguli implicite nu se aplica universal (o tabela de caracteristici
hardware pentru citeva masini se poate vedea in
&r.2.6).
Tipurile de intregi fara semn sint ideale pentru a utiliza memoria ca un vector
pe biti. Utilizarea unui intreg fara semn in locul unui int pentru a cistiga
un bit in plus pentru a reprezenta intregi pozitivi aproape totdeauna nu este
o idee buna. Incercarea de a ne asigura ca anumite valori sint pozitive prin
declararea variabilelor de tip unsigned va fi ignorata prin reguli implicite
de conversie. De exemplu: unsigned surprise = -1; este legal (dar compilatorul va face un avertisment despre el).
2.3.2 Conversie implicita de tip
Tipurile fundamentale pot fi amestecate liber in expresii. Oricind este posibil,
valorile se convertesc asa ca sa nu se piarda informatie (regula exacta poata
fi gasita in &r.6.6).
Exista cazuri in care informatia se poate pierde sau chiar distruge. Atribuirea
unei valori de un tip la o variabila de un alt tip cu biti mai putini in reprezentarea
ei este in mod necesar o sursa potentiala de erori. De exemplu, sa presupunem
ca secventa urmatoare se executa pe o masina in care intregii se reprezinta
in complement fata de doi si caracterele pe 8 biti: int i1 = 256 +255; char ch = i1; //ch == 255 int i2 = ch; //i2 == ?
Un bit (cel mai semnificativ) este pierdut in atribuirea ch = i1 si ch va pastra
toti bitii 1 (adica 8 biti de 1); deci nu exista o cale ca acesta sa poata deveni
511 cind se atribuie lui i2! Dar care ar putea fi valoarea lui i2 ? Pe VAX,
unde un caracter este cu semn, raspunsul este 255. C++ nu are un mecanism la
executie care sa detecteze un astfel de tip de problema, iar detectarea la compilare
este prea dificila in general, asa ca programatorul trebuie sa fie atent la
acest fapt.
2.3.3 Tipuri derivate
---------------
Din tipurile fundamentale (si din tipurile definite de utilizator) se pot deriva
alte tipuri folosind operatorii de declaratie: pointer
& adresa
ai vector
() functie
si mecanismul de definitie de structura. De exemplu: int* a; float va10i; char* pa20i; //vector de 20 de pointeri spre caractere void f(int); struct strA short length; char* p;
S;
Regulile de compunere a tipurilor utilizind acesti operatori se explica in
detaliu in &r8.3.4. Ideea de baza este ca declararea unui tip derivat oglindeste
utilizarea lui. De exemplu: int va10i; //declara un vector i = va3i; //utilizeaza un element al vectorului int* p; //declaratie de pointer i = *p; //utilizeaza obiectul spre care se pointeaza
Toate problemele in intelegerea notatiei pentru tipuri derivate apar din cauza
faptului ca * si & sint operatori prefix iar ai si () sint postfix, asa
ca parantezele trebuie sa fie utilizate pentru a exprima tipuri in care precedenta
operatorilor este incomoda. De exemplu deoarece ai are o prioritate mai mare
decit *: int *va10i; //vectori de pointeri int (*p)a10i //pointer spre vector
Poate fi plicticos sa utilizam o declaratie pentru fiecare nume pe care vrem
sa-l introducem intr-un program, mai ales daca tipurile lor sint identice. Este
posibil sa declaram diferite nume intr-o singura declaratie; in locul unui singur
nume, declaratia pur si simplu contine o lista de nume separate prin virgula.
De exemplu, se pot declara doi intregi astfel: int x, y; //int x; int y;
Cind declaram tipuri derivate, trebuie sa observam ca operatorii se aplica numai
la nume individuale (si nu la orice alte nume din aceeasi declaratie). De exemplu: int* p, y; //int *p; int y; nu int *y; int x, *p; //int x; int *p; int va10i, *p; //int va10i; int *p;
Opinia autorului este ca astfel de constructii fac un program mai putin lizibil
si ar trebui eliminate.
2.3.4 Void
Tipul void se comporta sintactic ca un tip fundamental. El poate totusi, sa
fie utilizat numai ca parte a unui tip derivat; nu exista obiecte de tip void.
Este folosit pentru a specifica ca o functie nu returneaza o valoare sau ca
tip de baza pentru pointeri spre obiecte de tip necunoscut. void f(); //f nu returneaza o valoare void* pv; //pointer spre un obiect de tip necunoscut
Un pointer spre orice tip poate fi atribuit la o variabila de tip void*.
Pentru inceput acesta nu pare prea util, deoarece un pointer void* nu poate
fi indirectat dar aceasta restrictie este exact ceea ce face ca tipul void*
sa fie util. El se utilizeaza in primul rind pentru a transfera la functii pointeri
despre care nu se poate face presupunere asupra tipului obiectului spre care
pointeza si pentru a returna obiecte fara tip dintr-o functie.
Pentru a utiliza un astfel de obiect, trebuie sa se utilizeze conversia explicita
de tip. Astfel de functii de obicei exista la cel mai inferior nivel al sistemului
unde se manipuleaza resurse hardware reale. De exemplu: void* allocate(int size); void deallocate(void*); f()
Aint* pi = (int*)allocate(10 * sizeof(int)); char* pc = (char*)allocate(10);
//.... deallocate(pi); deallocate(pc);
S
2.3.5 Pointeri
Pentru cele mai multe tipuri T, T* este tipul pointer spre T. Adica o variabila
de tipul T* poate pastra adresa unui obiect de tipul T. Pentru pointeri spre
vectori si pointeri spre functii exista notatii mai complicate: int* pi; char** cpp; //pointer spre pointer spre caractere int (*vp)a10i //pointer spre vector de 10 elemente int (*fp)(char,char*) //pointer spre o functie care are ca parametru (char,
char*) si //returneaza un int
Operatia fundamentala asupra unui pointer este indirectarea, adica referirea
la un obiect pointat printr-un pointer spre el. Operatorul de indirectare este
unarul * (prefixat). De exemplu: char c1 = 'a'; char* p = &c1; //p pastreaza adresa lui c1 char c2 = *p; //c2 = 'a'
Variabila spre care pointeaza p este c1 si valoarea pastrata in c1 este 'a',
asa ca valoarea lui *p atribuita lui c2 este 'a'.
Este posibil sa se faca unele operatii aritmetice cu pointerii. Iata de exemplu
o functie care calculeaza numarul de caractere dintr-un sir (nesocotind 0 care
termina sirul): int strlen(char* p)
A int i = 0;
while(*p++) i++; return i;
S
Un alt mod de a gasi lungimea este ca la inceput sa gasim sfirsitul sirului
si apoi sa scadem adresa inceputului sirului din adresa sfirsitului: int strlen(char* p)
A char* q = p;
while(*q++); return(q-p-1);
S
Pointerii spre functii pot fi extrem de utili; ei se discuta in (&4.6.7).
2.3.6 Vectori
Pentru un tip T, Tasizei este tipul "vector de size elemente de tip T".
Elementele sint indexate de la 0 la size-1. De exemplu: float va3i; // un vector de 3 flotante: va0i,va1i,va2i int aa2ia5i; // doi vectori de 5 intregi char* vpca32i; // vectori de 32 de pointeri spre caractere
Un ciclu pentru a scrie valori intregi pentru caracterele mici ar putea fi scris
astfel: extern int strlen(char*); char alphaai = "abcdefghijklmnopqrstuvwxyz";
main()
Aint sz = strlen(alpha); for(int i=0; i<sz; i++)
A char ch = alphaaii; cout << "'" << chr(ch) << "'" <<
"=" << ch << "=0"
<< oct(ch) << "=0x" << hex(ch) << "\n";
S
S
Functia chr() returneaza reprezentarea sub forma de caracter a unui intreg mic;
de exemplu, chr(80) este "P" pe o masina care utilizeaza setul de
caractere ASCII. Functia oct() produce o reprezentare octala a argumentului
sau intreg, iar hex() produce o reprezentare hexazecimala a argumentului sau
intreg; chr(), oct() si hex() sint declarate in <stream.h>. Functia strlen()
a fost utilizata pentru a numara caracterele din alpha (vezi &2.4.4). Cind
se utilizeaza setul de caractere ASCII, iesirea va arata astfel:
'a' = 97 = 0141 = 0x61
'b' = 98 = 0142 = 0x62
'c' = 99 = 0143 = 0x63
Sa observam ca nu este necesar sa se specifice dimensiunea vectorului alpha;
compilatorul calculeaza numarul de caractere din sirul de caractere specificat
ca initializator. Utilizind un sir ca un initializator pentru un vector de caractere
este convenabil, dar din nefericire este unica utilizare a sirurilor. Nu exista
o atribuire similara a unui sir la un vector. De exemplu: char va9i; v = "a string"; // error
este o eroare deoarece atribuirea nu este definita pentru vectori.
Evident sirurile sint potrivite numai pentru a initializa vectori de caractere;
pentru alte tipuri trebuie sa se utilizeze o notatie mai laborioasa. Aceasta
notatie poate fi de asemenea utilizata pentru vectori de caractere. De exemplu: int v1ai = A1,2,3,4S; int v2ai = A'a','b','c','d'S; char v3ai = A1,2,3,4S; char v4ai = A'a','b','c','d'S;
Observam ca v4 este un vector de 4 (nu 5) caractere; nu este terminat printr-un
zero, asa cum cer prin conventie toate rutinele de biblioteca. Aceasta notatie
este de asemenea restrin sa la obiecte statice. Tablourile multidimensionale
sint reprezentate ca vectori de vectori si notind cu virgula pentru a separa
limitele ca in alte limbaje de programare se obtine la compilare o eroare deoarece
virgula (,) este un operator de succesiune (vezi &3.2.2). De exemplu, sa incercam: int bada5,2i; // error int va5ia2i; //correct int bad = va5,2i; // error int good = va4ia1i; // correct
O declaratie char va2ia5i; declara un vector cu doua elemente; fiecare din ele
este un vector de tip char a5i. In exemplul urmator, primul din acei vectori
este initializat cu primele 5 litere iar cel de al doilea cu primele 5 cifre: char va2ia5i = A'a','b','c','d','e','0','1','2','3','4'S; main()
A for(int i=0; i<2; i++)
A for(int j=0; j<2; j++) cout << "va" << i << "ia" << j
<< "i=" chr(vaiiaji) << " "; cout << "\n";
S
S va produce: va0ia0i=a va0ia1i=b va0ia2i=c va0ia3i=d va0ia4i=e va1ia0i=0 va1ia1i=1 va1ia2i=2 va1ia3i=3 va1ia4i=4
2.3.7 Pointeri si Vectori
In C++, pointerii si vectorii sint foarte strinsi legati. Numele unui vector
poate de asemenea, sa fie utilizat ca un pointer spre primul sau element, asa
ca exemplul cu alfabetul ar putea fi scris astfel: char alphaai = "abcdefghijklmnopqrstuvwxyz"; char* p = alpha; char
ch; while(ch = *p++); cout << chr(ch) << "=" << ch
<< "=0" << oct(ch) << "\n";
Declaratia lui p ar putea de asemenea sa fie scrisa: char* p = &alphaa0i;
Aceasta echivalenta este utilizata extensiv in apelurile de functii, in care
un argument vector este totdeauna pasat ca un pointer la primul element al vectorului;
astfel in acest exemplu: extern int strlen(char*); char vai = "Annemarie"; char* p = v; strlen(p);
strlen(v); este transferata aceeasi valoare la strlen in ambele apeluri.
Rezultatul aplicarii operatorilor +, -, ++, -- la pointeri depinde de tipul
obiectului spre care pointeaza pointerul. Cind un operator aritmetic se aplica
la un pointer spre un tip T, p este presupus ca pointeaza spre un element al
vectorului de obiecte de tip T; p+1 inseamna elementul urmator al acelui vector
iar p-1 elementul precedent. Aceasta implica faptul ca valoarea lui p+1 va fi
cu sizeof(T) mai mare decit valoarea lui p. De exemplu: main()
A char cva10i; int iva10i; char* pc = cv; int* pi = iv; cout << "char*" << long(pc+1) - long(pc) << "\n";
cout << "int*" << long(pi+1) - long(pi) << "\n";
S
va produce: char* 1 int* 4
deoarece caracterele ocupa un octet fiecare si intregii ocupa fiecare 4 octeti
pe masina mea. Valorile pointer au fost convertite spre long inainte de a face
scaderea utilizind conversia explicita de tip (&3.2.5). Ele au fost convertite
spre long si nu spre tipul int deoarece exista masini pe care un pointer nu
incape intr-un int (adica sizeof(int) < sizeof(char*)).
Scaderea de pointeri este definita numai cind ambii pointeri pointeaza spre
elemente ale aceluiasi vector (desi limbajul nu are un mod de a se asigura ca
acest lucru este adevarat). Cind se scade un pointer dintr-un altul, rezultatul
este numarul de elemente al vectorului dintre cei doi pointeri (un intreg).
Se poate adauga un intreg la un pointer sau scadea un intreg dintr-un pointer;
in ambele cazuri rezultatul este o valoare pointer. Daca acea valoare nu pointeaza
spre un element al aceluiasi vector, ca si vectorul initial, rezultatul utilizarii
valorii respective este nedefinit. De exemplu: int v1a10i; int v2a10i; int i = &v1a5i - &v1a3i; // 2 i = &v1a5i - &v2a3i; // rezultat nedefinit int* p = v2 + 2; // p==&v2a2i p = v2 - 2; // *p nedefinit
2.3.8 Structuri
Un vector este un agregat de elemente de un acelasi tip; o structura este un
agregat de elemente de orice tip. De exemplu: struct addressAchar* name; // "Jim Dandy" long number; // 61 char* street; // "South St" char* town; // "New Providence" char statea2i; // 'N' 'J' int zip; // 7974
S;
defineste un tip nou numit address care consta din elementele de care avem
nevoie pentru a trimite o scrisoare la cineva (address nu este in general destul
pentru a gestiona toate scrisorile, dar este suficient pentru un exemplu). Sa
observam punct-virgula de la sfirsit este unul din foarte putinele locuri din
C++ unde este necesar sa o avem dupa o acolada inchisa, asa ca lumea este inclinata
sa o uite.
Variabilele de tip adresa pot fi declarate exact ca si alte variabile, iar elementele
individuale pot fi accesate utilizind operatorul '.'(punct). De exemplu: address jd; jd.name = "Jim Dandy"; jd.number = 61;
Notatia utilizata pentru initializarea vectorilor poate de asemenea sa fie
utilizata pentru variabile de tip structura. De exemplu: address jd = A"Jim Dandy",61,"South St","New Providence",
A'N','J'S,7974S;
Utilizind un constructor (&5.2.4) este de obicei mai bine. Sa observam ca
jd.state nu poate fi initializat prin sirul "NJ". Sirurile sint terminate
prin caracterul '\0' asa ca "NJ" are trei caractere, adica unul in
plus decit ar incapea in jd.state.
Obiectele structura sint adesea accesate prin pointeri folosind operatorul ->.
De exemplu: void print_addr(address* p)
A cout << p->name << "\n" << p->number <<
" " << p->street
<< "\n" << p->town << "\n" <<
chr(p->statea0i)
<< chr(p->statea1i) << " " << p->zip <<
"\n";
S
Obiectele de tip structura pot fi atribuite, pasate ca si argumente la functie
si returnate ca rezultat al unei functii. De exemplu: address current; address set_current(address next)
A address prev = current; current = next; return prev;
S
Alte operatii plauzibile cum ar fi compararea (== si !=) nu sint definite. Cu
toate acestea, utilizatorul poate defini astfel de operatori (vezi cap. 6).
Nu este posibil sa se calculeze dimensiunea unui obiect de tip structura pur
si simplu insumind membri ei. Motivul pentru aceasta este ca multe masini necesita
ca obiecte de un anumit tip sa fie alocate numai la anumite adrese (un exemplu
tipic este faptul ca un intreg trebuie sa fie alocat la o adresa de cuvint)
sau pur si simplu pentru a trata astfel de obiecte mult mai eficient. Aceasta
conduce spre "goluri" in structuri. De exemplu (pe masina mea): sizeof(address)
este 24 si nu 22 cit ne-am astepta.
Sa observam ca numele unui tip devine disponibil pentru utilizare imediat dupa
ce el a fost intilnit si nu numai dupa declararea completa. De exemplu: struct linkA link* previsious; link* successor;
S;
Nu este posibil sa se declare obiecte noi de tip structura pina cind nu s-a
terminat complet declaratia, deci struct no_goodA no_goog member; S; este o eroare (compilatorul nu este in stare sa determine dimensiunea lui no_good).
Pentru a permite ca doua (sau mai multe) tipuri structura sa se refere unul
la altul, pur si simplu se admite ca sa se declare ca un nume este numele unui
tip structura. De exemplu: struct list; // to be defined later struct linkA link* pre; link* suc; list* member_of;
S; struct listA link* head; S;
Fara prima declaratie a lui list, declaratia lui link ar produce o eroare sintactica.
2.3.9 Echivalenta tipurilor
Doua tipuri structura sint diferite chiar daca ele au aceeasi membri. De exemplu: struct s1A int a; S; struct s2A int a; S; sint doua tipuri diferite, asa ca s1 x; s2 y = x; // error: type mismatch
Tipurile structura sint de asemenea diferite de tipurile fundamentale, asa ca:
s1 x; int i = x; // error: type mismatch
Exista un mecanism pentru a declara un nume nou pentru un tip, fara a introduce
un obiect nou. O declaratie prefixata prin cuvintul cheie typedef declara nu
o noua variabila de un tip dat, ci un nume nou pentru tip. De exemplu: typedef char* pchar; pchar p1,p2; char* p3 = p1; Aceasta poate fi o prescurtare convenabila.
2.3.10 Referinte
O referinta este un nume pentru un obiect. Prima utilizare a referintelor este
aceea de a specifica operatiile pentru tipuri definite de utilizator (ele se
discuta in cap. 6). Ele pot fi de asemenea utile ca argumente de functii. Notatia
X& inseamna referinta la X. De exemplu: int i = 1; int& r = i; // r si i acum se refera la acelasi obiect int x = r; // x = 1 r = 2; // i = 2
O referinta trebuie sa fie utilizata (trebuie sa fie ceva pentru ce este el
nume). Sa observam ca initializarea unei referinte este ceva cit se poate de
diferit de atribuirea la ea. In ciuda aparentelor, nici un operator nu opereaza
asupra unei referinte.
De exemplu: int ii = 0; int& rr = ii; rr++; // ii se incrementeaza cu 1 este legal, dar r++ nu incrementeaza referinta rr; ++ se aplica la un int, care
se intimpla sa fie ii. In consecinta, valoarea referintei nu poate fi schimbata
dupa initializare; ea totdeauna se refera la obiectul cu care a fost initializata
pentru a-l denumi. Pentru a primi un pointer spre obiectul notat prin referinta
rr, se poate scrie &rr. Implementarea unei referinte este un pointer (constant)
care este indirectat de fiecare data cind el este utilizat. Aceasta face initializarea
unei referinte trivial cind initializatorul este o lvaloare (un obiect la care
se poate lua adresa vezi &r5). Cu toate acestea, initializatorul pentru
T& este necesar sa nu fie o lvaloare sau chiar de tip T. In astfel de cazuri:
a1i Intii, se aplica conversia de tip daca este necesar (vezi &r6.6.8 si
&r8.5.6);
a2i Apoi valoarea rezultat este plasata intr-o variabila temporara;
a3i In final, adresa acestuia se utilizeaza ca valoare a initializatorului.
Consideram declaratia: double& dr = 1;
Interpretarea acesteia este: double* drp; // referinta reprezentata printr-un pointer double temp; temp = double(1); drp = &temp;
O referinta poate fi utilizata pentru a implementa o functie care se presupune
ca schimba valoarea argumentelor sale. int x = 1; void incr(int& aa)A aa++; S incr(x); // x = 2;
Semantica transferului de argumente se defineste ca si pentru initializare,
asa ca atunci cind este apelata functia de mai sus argumentul aa a lui incr()
devine un alt nume pentru x. Cu toate acestea, pentru a avea un program mai
lizibil este cel mai bine sa eliminam functiile care isi modifica argumentele.
Este adesea preferabil sa se returneze o valoare dintr-o functie in mod explicit
sau sa se returneze un pointer spre argument. int x = 1; int next(int p)A return p+1; S x = next(x); // x = 2 void inc(int* p)A (*p)++; S inc(&x); // x = 3
Referintele pot fi de asemenea utilizate pentru a defini functii care pot fi
utilizate atit in parttea stinga cit si in partea dreapta a unei atribuiri.
Din nou, multe din cele mai interesante utilizari ale referintei se afla in
proiectarea tipurilor netriviale definite de utilizator. Ca de exemplu, sa definim
un tablou asociativ simplu. Intii noi definim struct pair prin: struct pairA char* name; int val; S;
Ideea de baza este ca un sir are o valoare intreaga asociata cu el. Este usor
sa se defineasca o functie, find() care mentine o data structurata ce consta
dintr-o pereche pentru fiecare sir diferit ce a fost prezentat. O implementare
foarte simpla (dar ineficienta) ar fi urmatoarea: const large = 1024; static pair vecalarge+1i; pair* find(char* p)
/* mentinerea unui set de "pair": se cauta p, se returneaza
"pair"-ul respectiv daca se gaseste, altfel se returneaza un "pair"
neutilizat */
A for(int i=0; vecaii.name; i++) if(strcmp(p,vecaii.name)==0) return &vecaii; if(i==large) return &vecalarge-1i; return &vecaii;
S
Aceasta functie poate fi utilizata prin functia value() care implementeaza
un tablou de intregi indexat prin siruri de caractere: int& value(char* p)
A pair* res = find(p); if(res->name=='\0') // aici spre negasit:initializare
A res->name=new charastrlen(p)+1i; strcpy(res->name,p); res->val = 0; // valoarea initiala: 0
S return res_val;
S
Pentru un parametru sir dat, value() gaseste obiectul intreg respectiv (nu valoarea
intregului corespunzator); ea returneaza o referinta la el.
Aceasta s-ar putea utiliza astfel: const MAX = 256; //mai mare decit cel mai mare cuvint main() //numara aparitiilor fiecarui cuvint de la intrare
A char bufaMAXi;
while(cin >> buf) value(buf++); for(int i=0; vecaii.name; i++) cout << vecaii.name << ":" << vecaii.val <<
"\n";
S
Fiecare pas al ciclului citeste un cuvint de la intrarea standard cin in buf
(vezi cap.8), iar apoi se pune la zi contorul asociat cu el prin find(). In
final tabela rezultata de cuvinte diferite de la intrare, fiecare cu numarul
sau de aparitii, este imprimat. De exemplu, dindu-se intrarea aa bb bb aa aa
bb aa aa programul va produce: aa : 5 bb : 3
Este usor sa se perfectioneze aceasta intr-un tip de tablou asociativ propriu
folosind o clasa cu operatorul de selectie ai (vezi &6.7).
2.3.11 Registrii
Pe orice arhitectura de masina obiectele (mici) pot fi accesate mai rapid cind
se plaseaza intr-un registru. Ideal, compilatorul va determina strategia optima
pentru a utiliza orice registru disponibil pe masina pe care se compileaza programul.
Totusi, acest task nu este trivial, asa ca uneori este util ca programatorul
sa dea compilatorului aceasta informatie. Aceasta se face declarind un obiect
registru. De exemplu: register int i; register point cursor; register char*
p; Declaratiile de registrii ar trebui utilizate numai cind eficienta este intr-adevar
importanta. Declarind fiecare variabila ca variabila registru se va ingreuna
textul programului si se poate chiar marii dimensiunea codului si timpul de
executie (de obicei sint necesare instructiuni de a incarca un obiect si de
a memora un obiect dintr-un registru). Nu este posibil sa se ia adresa unui
nume declarat ca registru si nici nu poate fi global un astfel de nume.
2.4 Constante
C++ furnizeaza o notatie pentru valorile de tipuri fundamentale: constante caracter,
constatante intregi si constante in virgula flotanta. In plus, zero (0) poate
fi utilizat ca si o constanta pentru orice tip de pointer, iar sirurile de caractere
sint constante de tip charai. Este posibil, de asemenea, sa se specifice constante
simbolice. O constanta simbolica este un nume a carui valoare nu poate fi schimbata
in domeniul ei de existenta. In C++ exista trei feluri de constante simbolice:
(1) oricarei valori de orice tip i se poate da un nume si sa fie folosita ca
o consatnta adaugind cuvintul cheie const la definitia ei;
(2) un set de constante intregi poate fi definit ca o enumerare;
(3) orice nume de vector sau functie este o constanta.
2.4.1 Constante intregi
Constantele intregi pot fi de patru feluri: zecimale, octale, hexazecimale
si constante caracter. Constantele zecimale sint cele mai frecvent utilizate
si arata asa cum ne asteptam noi:
0 1234 976 12345678901234567890
Tipul unei constante zecimale este int cu conditia ca ea sa incapa intr-un int,
altfel ea este long. Compilatorul se cuvine sa avertizeze asupra constantelor
care sint prea lungi ca sa fie reprezentate in calculator.
O constanta care incepe cu zero urmat de x (0x) este hexazecimal, iar o constanta
care incepe cu zero urmat de o cifra este in octal.
Exemple de constante octale:
0 02 077 0123
Exemple de constante in hexazecimal:
0x0 0x2 0x38 0x53
Literele a, b, c, d, e si f sau echivalentele lor in litere mari se utilizeaza
pentru a reprezenta 10, 11, 12, 13, 14 si respectiv 15. Notatiile octale si
hexazecimale sint mai folosilositoare pentru a exprima structuri pe biti; utilizind
aceste notatii pentru a exprima numere adevarate putem ajunge la surprize. De
exemplu, pe o masina pe care un int se reprezinta ca un intreg prin complement
fata de 2 pe 16 biti, intregul 0xffff este numarul negativ -1; daca s-ar folosi
mai multi biti pentru a reprezenta un int, atunci acesta ar fi 65535.
2.4.2 Constante in flotanta
O constanta in flotanta este de tip double. Din nou compilatorul da un avertisment
despre constante flotante care sint prea mari pentru a putea fi reprezentate.
Iata citeva constante in virgula flotanta:
1.23 .23 0.23 1. 1.0 1.2e10 1.23e-15
Sa observam ca nu poate apare un spatiu in mijlocul unei constante flotante.
De exemplu:
65.43 e-21 nu este o constanta flotanta ci 4 lexicuri:
65.43 e - 21 si va cauza eroare sintactica.
Daca noi dorim o constanta de tip float, noi putem defini una de forma (&2.4.6): const float pi8 = 3.14159265;
2.4.3 Constante caracter
Desi C++ nu are un tip caracter separat pentru date, ci mai degraba un tip
intreg care poate pastra un caracter, trebuie sa avem o notatie speciala si
convenabila pentru caractere. O constanta caracter este un caracter inclus intre
caracterele apostrof: de exemplu 'a' si '0'. Astfel de constante caracter sint
constante simbolice adevarate pentru valorile intregi ale caracterelor din setul
de caractere al masinii pe care se executa programul C++ (care nu este in mod
necesar acelasi set de caractere ca si cel utilizat de calculatorul pe care
se compileaza programul). Astfel, daca noi executam programul pe o masina care
utilizeaza setul de caractere ASCII, valoarea '0' este 48; daca masina utilizeaza
setul EBCDIC, el este 240. Utilizind constantele caracter in locul notatiei
zecimale programele devin mai portabile. Citeva caractere au de asemenea notatii
standard in care se utilizeaza caracterul backslash (\):
'\b' backspace
'\f' formfeed
'\n' newline
'\r' cariage return
'\t' horizontal tab
'\v' vertical tab
'\\' backslash
'\'' simple quote
'\"' double quote
'\0' null, the integer value 0
Acestea sint caractere singulare in ciuda aparentei. Este posibil, de asemenea
sa reprezentam un caracter printr-un numar octal de o cifra, doua sau trei (\
urmat de cifre octale) sau de un numar hexazecimal de una, doua sau trei cifre(\x
urmat de cifre hexazecimale). De exemplu:
'\6' '\x6' 6 ASCII ack
'\60' '\x30' 48 ASCII '0'
'\137' '\x05f' 95 ASCII '-'
Aceasta face posibil ca sa se reprezinte fiecare caracter din setul caracterelor
masina si in particular pentru a include astfel de caractere in siruri de caractere
(vezi sectiunea urmatoare). Utilizind o notatie numerica pentru un caracter,
programul respectiv nu mai este portabil pentru masini cu seturi diferite de
caractere.
2.4.4 Siruri
Un sir constant este o secventa de caractere inclusa intre ghilimele: "this
is a string"
Orice sir constant contine cu un caracter mai mult decit cele care apar in sir;
ele toate se termina prin caracterul nul '\0', cu valoarea 0. De exemplu: sizeof("asdf")==5;
Tipul unui sir este "vector de un numar corespunzator de caractere",
asa ca "asdf" este de tipul chara5i. Sirul vid se scrie ""
(si are tipul chara1i). Sa observam ca pentru orice sir s, strlen(s) == sizeof(s)
- 1 deoarece strlen() nu numara zeroul terminal. Conventia backslash pentru
reprezentarea caracterelor negrafice pot de asemenea sa fie utilizate intr-un
sir: aceasta face posibil sa se reprezinte ghilimelele si insusi caracterul
backslash intr-un sir. Cel mai frecvent astfel de caracter este pe de parte
caracterul '\n'. De exemplu: cout << "beep at end of message\007\n"; unde 7 este valoarea ASCII a caracterului bel.
Nu este posibil sa avem un caracter newline "real" intr-un sir:
"this is not a string but a syntax error" cu toate acestea, un backslash urmat imediat de un newline poate apare intr-un
sir: ambele vor fi ignorate. De exemplu: cout << "this is\ ok" va scrie this is ok
Este posibil ca sa avem caracterul nul intr-un sir, dar majoritatea programelor
nu vor suspecta ca dupa el mai sint caractere. De exemplu, sirul "asdf\000hjkl"
va fi tratat ca "asdf" prin functii standard cum ar fi strcpy() si
strlen().
Cind se include o constanta numerica intr-un sir folosind notatia octala sau
hexazecimala, este totdeauna bine sa se utilizeze trei cifre pentru numar. Notatia
este destul de greu de utilizat fara sa apara probleme cind caracterul dupa
o constanta de acest fel este o cifra. Consideram exemplele: char v1ai="a\x0fah\0129"; // 'a' '\xfa' 'h' '\12' '9' char v2ai="a\xfah\129"; // 'a' '\xfa' 'h' '\12' '9' char v3ai="a\xfad\127"; // 'a' '\xfad' '\127'
Sa observam ca o notatie cu doua cifre hexazecimale nu este suficienta pe masini
cu 9 biti pe byte.
2.4.5 Zero
Zero (0) poate fi folosit ca o constanta de tip intreg, flotant sau pointer.
Nici un obiect nu este alocat cu adresa zero. Tipul lui zero va fi determinat
de context. Toti bitii de o dimensiune potrivita sint zero.
2.4.6 Const
Cuvintul cheie const poate fi adaugat la declaratia unui obiect pentru a face
acel obiect o constanta in loc de variabila. De exemplu: const int model = 145; const int vai = A1, 2, 3, 4S;
Deoarece la un astfel de obiect nu i se poate atribui o valoare, el trebuie
sa fie initializat. Declarind ceva ca este constant ne asiguram ca valoarea
lui nu va fi schimbata in domeniul lui: model = 165; // error model++; // error
Sa observam ca const modifica un tip; adica el restringe modul in care un obiect
poate fi utilizat, in loc sa specifice cum se aloca constanta. Este, de exemplu,
perfect rezonabil si uneori util sa declaram o functie care returneaza o constanta: const char* peek(int i)A return privateaii; S
O functie de aceasta forma ar putea fi utilizata pentru a permite cuiva sa citeasca
un sir care nu poate fi alterat.
Cu toate acestea, un compilator poate avea avantaje de pe urma unui obiect care
este o constanta in diferite moduri. Cel mai evident este faptul ca de obicei
nu este nevoie ca sa fie alocata memorie pentru constanta deoarece compilatorul
cunoaste valoarea lui. Mai mult decit atit, initializatorul pentru o constanta
este adesea (dar nu totdeauna) o expresie constanta; daca este asa, ea poate
fi evaluata la compilare. Cu toate acestea, de obicei este necesar sa se aloce
memorie pentru un vector de constante deoarece compilatorul nu poate in general
sa defineasca care elemente ale vectorului sint referite in expresii. Pe multe masini, totusi, o implementare
eficienta poate fi atinsa chiar in acest caz plasind vectori de constante in
memorii read_only.
Cind utilizam un pointer, sint implicate doua obiecte; pointerul insusi si obiectul
spre care se face pointarea.
"Prefixind" o declaratie a unui pointer cu const se construieste obiectul
ca o constanta, nu si pointerul. De exemplu: const char* pc = "asdf"; // pointer spre o constanta pca3i = 'a'; // eroare pc = "ghjk"; // ok
Pentru a declara ca pointerul insusi este o constanta si nu obiectul spre care
pointeaza, se foloseste operatorul *const: char *const cp = "asdf"; // pointer constant cpa3i = 'a'; // ok cp = "ghjk"; // eroare
Pentru a face ca sa fie constante atit obiectele, cit si pointerul spre ele,
trebuie ca ambele sa fie declarate ca si constante. De exemplu: const char *const cpe = "asdf"; // pointer constant spre
// constanta cpca3i = 'a'; // eroare cpc = "ghjk"; // eroare
Un obiect care este o constanta cind este accesat printr-un pointer poate fi
variabila cind este accesat in alt mod. Aceasta este util mai ales pentru argumentele
functiilor. Declarind un pointer_argument ca si const, functiei I se interzice
sa modifice obiectul spre care pointeaza pointerul respectiv. De exemplu: char* strcpy(char* p,const char* q);//nu poate modifica pe *q
Se poate atribui adresa unei variabile la un pointer spre o constanta deoarece
nu se intimpla nimic rau prin aceasta. Cu toate acestea, adresa unei constante
nu se poate atribui la un pointer fara restrictii deoarece aceasta ar permite
sa schimbe valoarea obiectului. De exemplu: int a = 1; const c = 2; const* p1 = &c; // ok const* p2 = &a; // ok int* p3 = &c; // eroare
*p3 = 7; // schimba valoarea lui
De obicei, daca tipul este omis intr-o declaratie, se alege int ca implicit.
2.4.7 Enumerari
O alta posibilitate pentru a defini constante intregi, care este adesea mai
convenabil decit utilizind const, este enumerarea. De exemplu: enum AASM, AUTO, BREAKS; defineste trei constante intregi, numite enumeratori si atribuie valori la acestia.
Deoarece valorile enumerator sint atribuite crescator de la zero, aceasta este
echivalent cu scrierea: const ASM = 0; const AUTO = 1; const BREAK = 2;
O enumerare poate fi definita. De exemplu: enum keyword AASM,AUTO,BREAKS;
Numele enumerarii devine un sinonim pentru int, nu un nou tip. De exemplu: keyword key; switch(key)
A case ASM: // se face ceva break; case BREAK:// face ceva break;
S va conduce la un avertisment deoarece numai doua valori au fost tratate din
cele trei.
Valorile pot fi de asemenea date explicit enumeratorilor. De exemplu: enum int16 A sign = 0100000, most_significant = 0400000, last_significant = 1
S;
Aceste valori nu este necesar sa fie distincte, crescatoare sau pozitive.
2.5 Salvarea spatiului
Cind programam aplicatii netriviale, invariabil vine vremea cind dorim mai
mult spatiu de memorie decit este disponibil sau ne putem permite. Exista doua
moduri de a obtine mai mult spatiu in afara de cel care este disponibil:
a1i Sa se puna mai mult de un obiect mic intr-un octet;
a2i Sa se utilizeze acelasi spatiu pentru a pastra diferite obiecte in momente
diferite.
Prima metoda poate fi realizata folosind cimpurile, iar cea de a doua folosind
reuniunile. Aceste constructii se descriu in sectiunile urmatoare. Deoarece
utilizarea lor tipica este pentru a optimiza pur si simplu un program si deoarece
ele sint adesea cele mai neportabile parti ale programului, programatorul trebuie
sa gindeasca de doua ori inainte de a le utiliza. Adesea o conceptie mai buna
este sa schimbe modul in care se gestioneaza datele; de exemplu, sa se insiste
mai mult asupra memoriei alocate dinamic (&3.2.6) si mai putin asupra memoriei
prealocate static.
2.5.1 Cimpuri
Se pare extravagant ca sa se utilizeze un caracter pentru a reprezenta o variabila
binara, de exemplu un comutator on/off, dar tipul char este cel mai mic obiect
care poate fi alocat independent in C++. Este posibil, totusi, sa se inmanuncheze
impreuna diferite astfel de variabile foarte mici ca si cimpuri intr-o structura.
Un membru se defineste a fi un cimp specificind numarul de biti pe care ii ocupa,
dupa numele lui. Se admit si cimpuri nedenumite; ele nu afecteaza sensul cimpurilor
denumite, dar pot fi utilizate pentru a face o aranjare mai buna insa dependenta
de masina: struct sregA unsigned enable : 1; unsigned page : 3; unsigned : 1; //neutilizat unsigned mode : 2; unsigned : 4; //neutilizat unsigned access : 1; unsigned length : 1; unsigned non_resident : 1;
S;
Aceasta se intimpla sa fie aranjarea bitilor la registru de stare 0 la DEC
PDP11/45. Un cimp trebuie sa fie de tip intreg si se utilizeaza ca alti intregi
exceptind faptul ca nu este posibil sa se ia adresa unui cimp. In modulul kernel
al unui sistem de operare sau in debugger, tipul sreg ar putea fi utilizat astfel: sreg* sr0 = (sreg*)0777572;
//........ if(sr0->access) //access violation
A//clean up the mess sr0->access = 0;
S
Cu toate acestea, utilizind cimpuri pentru a putea impacheta diferite variabile
intr-un singur octet nu neaparat se salveaza spatiu. Se salveaza spatiu la date,
dar dimensiunea codului rezultat din manipularea acestor variabile se mareste
pe majoritatea masinilor. Programele se stie ca se scurteaza semnificativ cind
variabilele binare se convertesc de la cimpuri binare la caractere! Mai mult
decit atit, de obicei este mai rapid sa se faca acces la char sau int decit
pentru a face acces la un cimp.
2.5.2 Reuniuni
Sa consideram o tabela de simboluri in care o intrare pastreaza un nume si o
valoare, iar valoarea este sau un sir sau un intreg: struct entryA char* name; char type; char* string_value; //se utilizeaza daca type == 's' int int_value; //se utilizeaza daca type == 'i'
S; void print_entry(entry* p)
Aswitch(p->type)
A case 's': cout << p->string_value; break; case 'i': cout << p->int_value; break; default : cerr << "type corrupted\n"; break;
S
S
Deoarece string_value si int_value nu pot fi utilizate in acelasi timp, evident
se pierde spatiu. Se poate recupera usor specificind ca ambii ar trebui sa fie
membri ai unei reuniuni, ca mai jos: struct entryA char* name; char type; unionA char* string_value; //used if type =='s' int int_value; //used if type =='i'
S;
S;
Aceasta lasa tot codul care foloseste pe entry neschimbat, dar asigura faptul
ca atunci cind entry se aloca, string_value si int_value sa aiba aceeasi adresa.
Aceasta implica, ca toti membri unei reuniuni sa aiba in comun acelasi spatiu
care permite pastrarea celui mai mare membru.
Utilizind reuniunea in asa fel ca totdeauna sa folosim membrul care a fost pastrat
in ea, se obtine o optimizare pura. Cu toate acestea, in programe mari, nu este
usor sa se asigure ca o reuniune se utilizeaza numai in acest mod si se pot
introduce erori subtile. Este posibil sa se incapsuleze o reuniune in asa fel
incit corespondenta intre tipul cimp si tipurile membrilor unei reuniuni sa
fie garantat ca este corecta (&5.4.6).
Reuniunile sint uneori utilizate pentru "conversie de tip" (aceasta
se face in principiu prin programe introdu-se in limbaj in afara facilitatilor
de conversie a tipului, unde este necesar sa fie facuta). De exemplu, pe VAX
acestea convertesc un int in int* pur si simplu prin echivalenta de biti. struct fudgeA unionA int i; int* p;
S;
S; fudge a; a.i = 4096; int* p = a.p; //bad usage
Cu toate acestea, aceasta nu este o conversie reala; pe anumite masini un int
si un int* nu ocupa acelasi spatiu, iar pe altele nici un intreg nu poate avea
o adresa impara. O astfel de utilizare a unei reuniuni nu este portabila si
exista un mod explicit si portabil de a specifica aceasta conversie (&3.2.5).
Reuniunile sint ocazional utilizate in mod deliberat pentru a elimina conversia
de tip. Am putea, de exemplu, utiliza un fudge pentru a gasi reprezentarea pointerului
0: fudge.p = 0; int i = fudge.i; // i nu este necesar sa fie 0
Este de asemenea posibil sa se dea un nume unei reuniuni; adica ea formeaza
un tip in adevaratul sens al lui. De exemplu, fudge ar putea fi declarata astfel: union fudgeA int i; int* p;
S; si folosita exact ca inainte. Reuniunile numite au de asemenea, utilizari proprii
(vezi &5.4.6).
2.6 Exercitii
1. (*1). Sa se execute programul "Hello, world" (&1.1.1).
2. (*1). Pentru fiecare din declaratiile din (&2.1) sa se faca urmatoarele:
daca o declaratie nu este o definitie, sa se scrie o definitie pentru ea. Daca
o declaratie este o definitie, sa se scrie o declaratie pentru ea, care nu este
de asemenea o definitie.
3. (*1). Sa se scrie declaratii pentru urmatoarele: un pointer spre un caracter;
un vector de 10 intregi; o referinta spre un vector de 10 intregi; un pointer
spre un vector de siruri de caractere; un pointer spre un pointer la un caracter;
o constanta intreaga; un pointer spre o constanta intreaga; un pointer constant
spre un intreg. Sa se initializeze fiecare din ei.
4. (*1.5). Sa se scrie un program care imprima dimensiunea tipurilor fundamentale
si a pointerului. Sa se utilizeze operatorul sizeof.
5. (*1.5). Sa se scrie un program care imprima literele 'a'..'z' si cifrele
'0'..'9' si valorile lor intregi. Sa se faca acelasi lucru pentru alte caractere
imprimabile. Sa se faca acelasi lucru, dar utilizind notatia hexazecimala.
6. (*1). Sa se imprime bitii care se folosesc pentru a reprezenta pointerul
0 pe sistemul d-voastra (&2.5.2).
7. (*1.5). Sa se scrie o functie care imprima exponentul si mantisa unui parametru
in dubla precizie.
8. (*2). Care sint valorile cele mai mari si cele mai mici pe sistemul d-voastra
pentru tipurile urmatoare: char, short, int, long, float, double, unsigned,
char*, int* si void* ? Exista mai multe restrictii asupra valorilor ? De exemplu,
poate int* sa aiba o valoare impara ? Care este cadrajul obiectelor de acele
tipuri ? De exemplu poate un int sa aiba o adresa impara ?
9. (*1). Care este cel mai lung nume local pe care il puteti utiliza intr-un
program C++ pe sistemul d-voastra ? Care este cel mai lung nume extern pe care
il puteti utiliza intr-un program C++ pe sistemul d-voastra ? Exista vreo restrictie
asupra caracterelor pe care le puteti utiliza intr-un nume ?
10. (*2). Definiti pe unu astfel: const one = 1; Incercati sa schimbati valoarea
lui one la doi. Definiti pe num prin: const numai = A1,2S; Incercati sa schimbati
valoarea lui numa1i la 2.
11. (*1). Scrieti o functie care permuta doi intregi. Sa se utilizeze int* ca
tip al argumentului. Scrieti o alta functie de permutare care utilizeaza int&
ca tip de argument.
12. (*1). Care este dimensiunea vectorului str in exemplul urmator: char strai = "a short string"; Care este lungimea sirului "a
short string"?
13. (*1.5). Sa se defineasca o tabela de nume continind numele fiecarei luni
din an si numarul de zile din fiecare luna. Sa se scrie tabela. Sa se faca aceasta
de doua ori: odata utilizind un vector pentru nume si un vector pentru numarul
de zile si odata utilizind un vector de structuri, fiecare structura pastrind
numele lunii si numarul de zile din ea.
14. (*1). Sa se utilizeze typedef pentru a defini tipurile: unsigned char, constant
unsigned char, pointer spre intreg, pointer spre pointer spre char, pointer
spre vector de caractere, vector de 7 pointeri intregi, pointer spre un vector
de 7 pointeri intregi, vector de 8 vectori de 7 pointeri intregi.