Limbajul C
Limbajul C
2.1.1.Limbajele C şi C++
2.1.2.Elemente de bază.
VOCABULARUL
În scrierea programelor în limbajul C/C++ pot fi folosite doar anumite simboluri care
alcătuiesc alfabetul limbajului. Acesta cuprinde:
Literele mari sau mici de la A la Z (a-z);
cuvintele compuse;
Cifrele zecimale (0-9);
Simboluri speciale:
Caractere:
operatori (Exemple: +, *, !=);
delimitatori (Exemple: blank (spaţiu), tab \t, newline \n, cu rolul de a
separa cuvintele);
Grupuri (perechi de caractere).
UNITĂŢILE LEXICALE
Identificatorii reprezintă numele unor date (constante sau variabile), sau ale unor
funcţii. Identificatorul este format dintr-un şir de litere, cifre sau caracterul de subliniere
(underscore), trebuie să înceapă cu o literă sau cu caracterul de subliniere şi să fie
sugestivi.
Cuvintele cheie sunt cuvinte ale limbajului, împrumutate din limba engleză, cărora
programatorul nu le poate da o altă utilizare. Cuvintele cheie se scriu cu litere mici şi pot
reprezenta:
Tipuri de date (Exemple: int, char, double);
Tipul unei date constă într-o mulţime de valori pentru care s-a adoptat un anumit
mod de reprezentare în memoria calculatorului şi o mulţime de operatori care pot fi
aplicaţi acestor valori. Tipul unei date determină lungimea zonei de memorie ocupată de
acea dată. În general, lungimea zonei de memorare este dependentă de calculatorul pe
care s-a implementat compilatorul. Tabelul 2.1. prezintă lungimea zonei de memorie
ocupată de fiecare tip de dată pentru compilatoarele sub MS-DOS şi UNIX/LINUX.
Tipurile de bază sunt:
char un singur octet (1 byte=8 biţi), capabil să conţină codul unui caracter din
setul
local de caractere;
int număr întreg, reflectă în mod tipic mărimea naturală din calculatorul utilizat;
float număr real, în virgulă mobilă, simplă precizie;
double număr real, în virgulă mobilă, dublă precizie.
În completare există un număr de calificatori, care se pot aplica tipurilor de bază char,
int, float sau double: short, long, signed şi unsigned. Astfel, se obţin
tipurile derivate de date. Short şi long se referă la mărimea diferită a întregilor, iar
datele de tip unsigned int sunt întotdeauna pozitive. S-a intenţionat ca short şi
long să furnizeze diferite lungimi de întregi, int reflectând mărimea cea mai "naturală"
pentru un anumit calculator. Fiecare compilator este liber să interpreteze short şi long
în mod adecvat propriului hardware; în nici un caz, însă, short nu este mai lung decât
long. Toţi aceşti calificatori pot aplicaţi tipului int. Calificatorii signed (cel implicit)
şi unsigned se aplică tipului char. Calificatorul long se aplică tipului double.
Dacă într-o declaraţie se omite tipul de bază, implicit, acesta va fi int.
Tabelul 2.1.
Tip Lungimea zonei Descriere
de memorie
ocupate (în biţi)
MS- UNIX
DOS LINUX
char 8 8 Valoarea unui singur caracter; poate fi întâlnit în expresii cu
extensie de semn
unsigned char 8 8 Aceeaşi ca la char, fară extensie de semn
signed char 8 8 Aceeaşi ca la char, cu extensie de semn obligatorie
int 16 32 Valoare întreagă
long 32 64 Valoare întreagă cu precizie mare
(long int)
long long int 32 64 Valoare întreagă cu precizie mare
short int 16 32 Valoare întreagă cu precizie mică
unsigned int 16 32 Valoare întreagă, fără semn
unsigned long 32 64 Valoare întreagă, fără semn
int
float 32 32 Valoare numerică cu zecimale, simplă precizie (6 )
double 64 64 Valoare numerică cu zecimale, dublă precizie (10 )
long double 80 128 Valoare numerică cu zecimale, dublă precizie
2.2.3. CONSTANTE
2.2.3.1.Constante întregi
Constantele întregi sunt literali numerici (compuşi din cifre), fără punct zecimal.
Constante întregi în baza 10, 8 sau 16
Dacă secvenţa de cifre este urmată de L sau l, tipul constantei este long
int.
Exemple:
145677L
897655l // tip decimal long int
Dacă secvenţa de cifre este urmată de U sau u, tipul constantei este
unsigned int.
Exemple:
65555u
Dacă secvenţa de cifre este urmată de U (u) şi L (l), tipul constantei este
unsigned long int.
Exemple: 7899UL //tip decimal unsigned long int
Valorile ordinale ale literelor mari sunt ordonate şi consecutive ('A' are codul ASCII
Constanta şir este o succesiune de zero sau mai multe caractere, încadrate de ghilimele. În
componenţa unui şir de caractere, poate intra orice caracter, deci şi caracterele escape.
Lungimea unui şir este practic nelimitată. Dacă se doreşte continuarea unui şir pe rândul
următor, se foloseşte caracterul backslash.
Caracterele componente ale unui şir sunt memorate într-o zonă continuă de memorie (la
adrese succesive). Pentru fiecare caracter se memorează codul ASCII al acestuia. După
ultimul caracter al şirului, compilatorul plasează automat caracterul NULL (\0), caracter
care reprezintă marcatorul sfârşitului de şir. Numărul de octeţi pe care este memorat un
şir va fi, deci, mai mare cu 1 decât numărul de caractere din şir.
2.2.4. VARIABILE
Spre deosebire de constante, variabilele sunt date (obiecte informaţionale) ale căror
valori se pot modifica în timpul execuţiei programului. Şi variabilele sunt caracterizate de
atributele nume, tip, valoare şi clasă de memorare. Variabilele sunt nume simbolice
utilizate pentru memorarea valorilor introduse pentru datele de intrare sau a rezultatelor.
Dacă la o constantă ne puteam referi folosind caracterele componente, la o variabilă ne
vom referi prin numele ei. Numele unei variabile ne permite accesul la valoarea ei, sau
schimbarea valorii sale, dacă este necesar acest lucru. Numele unei variabile este un
identificator ales de programator. Ca urmare, trebuie respectate regulile enumerate în
secţiunea identificatori.
Dacă o dată nu are legături cu alte date (de exemplu, relaţia de ordine), vom spune
că este o dată izolată. O dată izolată este o variabilă simplă. Dacă datele se grupează într-
un anumit mod (în tablouri - vectori, matrici - sau structuri), variabilele sunt compuse
(structurate).
2.2.5.Operaţii de intrare/ieşire
Operatorul >> se numeşte operator extractor (extrage valori din fluxul datelor de intrare,
conform tipului acestora), iar operatorul << se numeşte operator insertor (inserează
valori în fluxul datelor de ieşire, conform tipului acestora). Tipurile de date citite de la
tastatură pot fi toate tipurile numerice, caracter sau şir de caractere. Tipurile de date
transferate către ieşire pot fi: toate tipurile numerice, caracter sau şir de caractere.
Operanzii operatorului extractor (>>) pot fi doar nume de variabile. Operanzii
operatorului insertor (<<) pot fi nume de variabile (caz în care se afişează valoarea
variabilei), constante sau expresii. Utilizarea dispozitivelor şi operatorilor de intrare/ieşire
în C++ impune includerea fişierului iostream.h.
Exemple:
char c;
cout<<"Astept un caracter:"; //afişarea constantei şir de caractere,
deci a mesajului
cin>>c; //citirea valorii variabilei c, de tip caracter
int a, b, e; double d;
cin>>a>>b>>e>>d; //citirea valorilor variabilelor a, b, e, d de tip int,
int, int, double
cout<<"a="<<a<<"Valoarea expresiei a+b
este:"<<a+b<<'\n';
Datele (constante sau variabile) legate prin operatori, formează expresii (figura 2.4).
Operatorii care pot fi aplicaţi datelor (operanzilor) depind de tipul operanzilor, datorită
faptului că tipul unei date constă într-o mulţime de valori pentru care s-a adoptat un
anumit mod de reprezentare în memoria calculatorului şi o mulţime de operatori care pot
fi aplicaţi acestor valori.
2.2.6.1. OPERATORI
Operatorul unar adresă &, aplicat identificatorului unei variabile, furnizează adresa la
care este memorată aceasta. Poate fi aplicat oricărui tip de date şi se mai numeşte
operator de referenţiere.
Exemplu:
int a;
cout<<"Adresa la care este memorata variabila a
este:"<<&a;
Operatorul de atribuire (de asignare) este un operator binar care se aplică tuturor
tipurilor de variabile. Este folosit sub formele următoare:
nume_variabilă=expresie;
sau: expresie1=expresie2;
Se evaluează expresia din membrul drept, iar valoarea acesteia este atribuită variabilei
din membrul stâng. Dacă tipurile membrilor stâng şi drept diferă, se pot realiza anumite
conversii, prezentate în paragraful 2.7.
Exemplu:
float x; int a,b; x=9.18;
a=b=10;
int s; s=a+20*5; //rezultat: s=110
s=x+2; //rezultat s=11, deoarece s este int.
Aşa cum se observă în linia a 2-a din exemplul precedent, operatorul de atribuire poate fi
utilizat de mai multe ori în aceeaşi expresie. Asociativitatea operatorului are loc de la
dreapta la stânga. Astfel, mai întâi b=10, apoi a=b.
Aceşti operatori nu se aplică numerelor reale, ci numai datelor de tip întreg sau caracter.
Primul operator este unar, ceilalţi binari.
Operatorul condiţional
Este un operator ternar (necesită 3 operanzi), utilizat în construcţii de forma:
expresie1?expresie2:expresie3
Se evaluează expresia1. Dacă aceasta are o valoare diferită de zero, atunci
tipul şi valoarea întregii expresii vor fi aceleaşi cu tipul şi valoarea expresiei2.
Altfel (dacă expresie1 are valoarea zero), tipul şi valoarea întregii expresii vor
fi aceleaşi cu tipul şi valoarea expresiei3. Deci operatorul condiţional este
folosit pentru a atribui întregii expresii tipul şi valoarea expresiei2 sau a
expresiei3, în funcţie de o anumită condiţie. Acest lucru este echivalent cu:
Dacă expresie1 diferită de zero
Atunci evaluează expresie2
Altfel evaluează expresie3
Exemplu:
int semn=(x<0)?-1:1
Dacă x<0, atunci semn=-1, altfel semn=1.
Operatorul virgulă
Este utilizat în construcţii de forma:
expresie1 , expresie2
Operatorul virgulă forţează evaluarea unei expresii de la stânga la dreapta. Tipul şi
valoarea întregii expresii este dată de tipul şi valoarea expresiei2. Operatorul virgulă este
folosit în instrucţiunea for. Operatorul virgulă are cea mai mică prioritate.
Exemplu:
int x, c, y;
cout<<”Astept val. ptr. y:”; cin>>y;
x=(c=y, c<=5); /* c va primi valoarea lui y (citită); se verifică dacă c este
mai mic sau
egal cu 5. Daca nu, x=0; daca da, x=1 sau x=valoare diferită de zero)*/
x++, y--; //întâi este incrementat x, apoi este decrementat y
Operatorul sizeof()
Este un operator unar, care are ca rezultat numărul de octeţi pe care este memorată
o dată de un anumit tip. Operandul este un tip sau o dată (constantă sau variabilă)
de un anumit tip.
Exemple:
cout<<sizeof(int); // afişează numărul de octeţi pe care este memorat
un întreg (2)
cout<<sizeof(”ab6*”);// afişează 5, nr. de octeţi pe care este memorată
constanta şir ”ab6*”
Operatorul (tip)
Este un operator unar care apare în construcţii numite ”cast” şi converteşte tipul
operandului său la tipul specificat între paranteze.
Exemple:
int a; (float) a;// converteşte operandul a (care era de tip întreg) în float
Operatorul unar *
Este operator unar, numit şi operator de deferenţiere. Se aplică unei expresii de tip
pointer şi este folosit pentru a accesa conţinutul unei zone de memorie spre care
pointează operatorul. Operatorii & (adresă) şi * sunt complementari.
Exemplu: Expresia *a este înlocuită cu valoarea de la adresa conţinută în variabila
pointer a.
Operatorii paranteză
Parantezele rotunde ( ) se utilizează în expresii, pentru schimbarea ordinii de
efectuare a operaţiilor, sau la apelul funcţiilor. La apelul funcţiilor, parantezele
rotunde încadrează lista parametrilor efectivi. Din acest motiv, parantezele rotunde
sunt numite şi operatori de apel de funcţie.
Operatorii de indexare
Operatorii de indexare sunt parantezele pătrate []. Acestea includ expresii întregi
care reprezintă indici ai unui tablou.
2.2.7.EXPRESII
Prin combinarea operanzilor şi a operatorilor se obţin expresii. Tipul unei expresii este
dat de tipul rezultatului obţinut în urma evaluării acesteia. La evaluarea unei expresii se
aplică regulile de prioritate şi asociativitate a operatorilor din expresie. Ordinea de
aplicare a operatorilor poate fi schimbată prin folosirea parantezelor. La alcătuirea
expresiilor, este indicată evitarea expresiilor în care un operand apare de mai multe ori.
Sn
secvenţială
Sintaxa: ;
Instrucţiunea vidă nu are nici un efect. Se utilizează în construcţii în care se cere prezenţa
unei instrucţiuni, dar nu se execută nimic (de obicei, în instrucţiunile repetitive).
Instrucţiunea switch
În unele cazuri este necesară o decizie multiplă specială. Instrucţiunea switch permite
acest lucru. Reprezentare prin pseudocod:
Reprezentare prin schema logică (figura 3.2):
Structura ciclică cu test iniţial este implementată prin instrucţiunile while şi for.
Instrucţiunea while
Sintaxa:
while (expresie)
instructiune;
Observaţii:
1. În cazul în care la prima evaluare a expresiei, aceasta are valoarea zero, corpul
instrucţiunii while nu va fi executat niciodată.
2. Instrucţiune din corpul ciclului while poate fi compusă (un bloc), sau o altă
instrucţiune ciclică.
3. Este de dorit ca instrucţiunea din corpul ciclului while să modifice valoarea
expresiei. Dacă nu se realizează acest lucru, corpul instrucţiunii while se repetă de un
număr infinit de ori.
Exemplu:
int a=7;
while (a==7)
cout<<”Buna ziua!\n”; // ciclu infinit; se repetă la infinit afişarea
mesajului
Instrucţiunea for
În majoritatea limbajelor de programare de nivel înalt, instrucţiunea for implementează
structura ciclică cu număr cunoscut de paşi (vezi reprezentarea prin schema logică şi
pseudocod din capitolul 1). În limbajul C instrucţiunea for poate fi utilizată într-un mod
mult mai flexibil.
Reprezentare în pseudocod:
Reprezentare prin schema logică (figura 3.3.):
Instrucţiunea do-while
Sintaxa:
do instructiune;
while(expresie);
DECLARAREA TABOURILOR
Numim tablou o colecţie (grup, mulţime ordonată) de date, de acelaşi tip, situate într-o zonă de memorie continuă
(elementele tabloului se află la adrese succesive). Tablourile sunt variabile compuse (structurate), deoarece
grupează mai multe elemente. Variabilele tablou au nume, iar tipul tabloului este dat de tipul elementelor sale.
Elementele tabloului pot fi referite prin numele tabloului şi indicii (numere întregi) care reprezintă poziţia
elementului în cadrul tabloului.
În funcţie de numărul indicilor utilizaţi pentru a referi elementele tabloului, putem întâlni tablouri
unidimensionale (vectorii) sau multidimensionale (matricile sunt tablouri bidimensionale).
TABLOURI UNIDIMENSIONALE
Exemplu:
// Declararea tabloului vector
vector
int vector[6];
100 vector[0]
// Iniţializarea elementelor tabloului 101
vector[0]=100; vector[1]
102 vector[2]
vector[1]=101;
vector[2]=102; 103
vector[3]
vector[3]=103; 104 vector[4]
vector[4]=104; 105 vector[5]
vector[5]=105;
Figura 4.1.
La declararea unui vector cu iniţializarea elementelor sale, numărul maxim de elemente
ale tabloului poate fi omis, caz în care compilatorul determină automat mărimea
tabloului, în funcţie de numărul elementelor iniţializate.
Exemplu:
char tab[]={ ’A’, ’C’, ’D’, ’C’};
tab ’B’ ’C’ ’D’
’A’
1
[0] [3]
[0] [4]
Adresa elementului de indice i dintr-un tablou unidimensional poate fi calculată astfel:
adresa_elementului_i = adresa_de_bază + i∗
lungime_element
Exerciţii:
//1 Citirea elementelor unui vector:
double a[5];
int i;
for (i=0; i<5; i++)
{ cout<<”a["<<i<<”]=”; //afişarea unui mesaj prealabil citirii fiecărui
element
cin>>a[i]; //citirea valorii elementului de indice i
}
//Sau:
double a[20]; int i, n;
cout<<”Dim. Max. =”; cin>>n;
for (i=0; i<n; i++)
{ cout<<”a[“<<i<<”]=”;
cin>>a[i];
}
//2 Afişarea elementelor unui vector:
cout<<”Vectorul introdus este:\n”;
for (i=0; i<n i++)
cout<<a[i]<<’ ’;
//3 Afişarea elementelor unui vector în ordine inversă:
cout<<”Elementele vectorului în ordine inversă:\n”;
for (i=n-1; i>=0 i++)
cout<<a[i]<<’ ’;
//3 Vectorul sumă (c) a vectorilor a şi b, cu acelaşi număr de elemente:
for (i=0; i<n i++)
c[i]=a[i]+b[i];
//4 Vectorul diferenţă (c) a vectorilor a şi b, cu acelaşi număr de elemente:
for (i=0; i<n i++)
c[i]=a[i] - b[i];
//5 Produsul scalar (p) a vectorilor a şi b, cu acelaşi număr de elemente:
n −1
TABLOURI BIDIMENSIONALE
Din punct de vedere conceptual, elementele unui tablou bidimensional sunt plasate în
spaţiu pe două direcţii. Matricea reprezintă o aplicaţie naturală a tablourilor
bidimensionale.
În matematică:
q 11 q 12 q 13 . . . q 1n
q 21 q 22 q 23 . . . q 2 n
Q= . . . . . . . . . . . . . . . . . . . . . . . . . . Q m×n
q m1 q m2 q m3 . . . q mn
Exemplu:
double q[3][2]; // declararea matricii q, cu maxim3 linii şi 2 coloane, tip
double
Un program scris în limbajul C/C++ este un ansamblu de funcţii, fiecare dintre acestea
efectuând o activitate bine definită. Din punct de vedere conceptual, funcţia reprezintă o
aplicaţie definită pe o mulţime D (D=mulţimea, domeniul de definiţie), cu valori în
mulţimea C (C=mulţimea de valori, codomeniul), care îndeplineşte condiţia că oricărui
element din D îi corespunde un unic element din C.
Corpul funcţiei este un bloc, care implementează algoritmul de calcul folosit de către
funcţie. În corpul funcţiei apar (în orice ordine) declaraţii pentru variabilele locale şi
instrucţiuni. Dacă funcţia întoarce o valoare, se foloseşte instrucţiunea return
valoare. La execuţie, la întâlnirea acestei instrucţiuni, se revine în funcţia apelantă.
În limbajul C/C++ se utilizează declaraţii şi definiţii de funcţii.
Declaraţia conţine antetul funcţiei şi informează compilatorul asupra tipului, numelui
funcţiei şi a listei parametrilor formali (în care se poate indica doar tipul parametrilor
formali, nu şi numele acestora). Declaraţiile de funcţii se numesc prototipuri, şi sunt
constituite din antetul funcţiei, din care pot lipsi numele parametrilor formali.
Definiţia conţine antetul funcţiei şi corpul acesteia. Nu este admisă definirea unei funcţii
în corpul altei funcţii.
O formă învechită a antetului unei funcţii este aceea de a specifica în lista parametrilor
formali doar numele acestora, nu şi tipul. Această libertate în omiterea tipului
parametrilor constituie o sursă de erori.
tipul_valorii_returnate nume_funcţie (lista_parametrilor_
formali)
declararea_parametrilor_formali
{
declaraţii_variabile_locale
instrucţiuni
return valoare
}
Parametrii declaraţi în antetul unei funcţii sunt numiţi formali, pentru a sublinia faptul că
ei nu reprezintă valori concrete, ci numai ţin locul acestora pentru a putea exprima
procesul de calcul realizat prin funcţie. Ei se concretizează la execuţie prin apelurile
funcţiei.
Parametrii folosiţi la apelul unei funcţii sunt parametri reali, efectivi, concreţi, iar
valorile lor vor fi atribuite parametrilor formali, la execuţie. Utilizarea parametrilor
formali la implementarea funcţiilor şi atribuirea de valori concrete pentru ei, la execuţie,
reprezintă un prim nivel de abstractizare în programare. Acest mod de programare se
numeşte programare procedurală şi realizează un proces de abstractizare prin
parametri.
Variabilele declarate în interiorul unei funcţii, cât şi parametrii formali ai acesteia nu pot
fi accesaţi decât în interiorul acesteia. Aceste variabile sunt numite variabile locale şi nu
pot fi accesate din alte funcţii. Domeniul de vizibilitate a unei variabile este porţiunea de
cod la a cărei execuţie variabila respectivă este accesibilă. Deci, domeniul de vizibilitate
a unei variabile locale este funcţia în care ea a fost definită (vezi şi paragraful 6.8.).
Exemplu:
int f1(void)
{ double a,b; int c;
. . .
return c; // a, b, c - variabile locale, vizibile doar în corpul funcţiei
}
void main()
{ . . . . . . // variabile a şi b nu sunt accesibile în main()
}
Dacă în interiorul unei funcţii există instrucţiuni compuse (blocuri) care conţin declaraţii
de variabile, aceste variabile nu sunt vizibile în afara blocului.
Exemplu:
void main()
{ int a=1, b=2;
cout << "a=”<<a<<” b=”<<b<<” c=”<<c’\n’; // a=1 b=2, c
nedeclarat
. . . . . . . .
{ int a=5; b=6; int c=9;
cout << "a=”<<a<<” b=”<<b<<’\n’; // a=5 b=6 c=9
. . . . . . . .
}
cout << "a=”<<a<<” b=”<<b<<” c=”<<c’\n’; // a=1 b=6, c
nedeclarat
. . . . . . . . . . . .
}
Fiecare argument efectiv utilizat la apelul funcţiei este evaluat, iar valoarea este atribuită
parametrului formal corespunzător. În interiorul funcţiei, o copie locală a acestei valori va
fi memorată în parametrul formal. O modificare a valorii parametrului formal în
interiorul funcţiei (printr-o operaţie din corpul funcţiei), nu va modifica valoarea
parametrului efectiv, ci doar valoarea parametrului formal, deci a copiei locale a
parametrului efectiv (figura 6.1.). Faptul că variabila din programul apelant (parametrul
efectiv) şi parametrul formal sunt obiecte distincte, poate constitui un mijloc util de
protecţie. Astfel, în funcţia f1, valoarea parametrului formal intr este modificată
(alterată) prin instrucţiunea ciclică for. În schimb, valoarea parametrului efectiv (data) din
funcţia apelantă, rămâne nemodificată.
În cazul transmiterii parametrilor prin valoare, parametrii efectivi pot fi chiar expresii.
Acestea sunt evaluate, iar valoarea lor va iniţializa, la apel, parametrii formali.
Exemplu:
double psi(int a, double b)
{
if (a > 0) return a*b*2;
else return -a+3*b; }
void main()
{ int x=4; double y=12.6, z; z=psi ( 3*x+9, y-5) + 28;
cout<<”z=”<<z<<’\n’; }
Transferul valorii este însoţit de eventuale conversii de tip. Aceste conversii sunt realizate
automat de compilator, în urma verificării apelului de funcţie, pe baza informaţiilor
despre funcţie, sau sunt conversii explicite, specificate de programator, prin operatorul
”cast”.
Exemplu:
float f1(double, int);
void main()
{
int a, b; float g=f1(a, b); // conversie automată: int a -> double a
float h=f1( (double) a, b); // conversie explicită
}
Limbajul C este numit limbajul apelului prin valoare, deoarece, de fiecare dată când o
funcţie transmite argumente unei funcţii apelate, este transmisă, de fapt, o copie a
parametrilor efectivi. În acest mod, dacă valoarea parametrilor formali (iniţializaţi cu
valorile parametrilor efectivi) se modifică în interiorul funcţiei apelate, valorile
parametrilor efectivi din funcţia apelantă nu vor fi afectate.
În unele cazuri, parametrii transmişi unei funcţii pot fi pointeri (variabile care conţin
adrese). În aceste cazuri, parametrii formali ai funcţiei apelate vor fi iniţializaţi cu
valorile parametrilor efectivi, deci cu valorile unor adrese. Astfel, funcţia apelată poate
modifica conţinutul locaţiilor spre care pointează argumentele (pointerii).
Valoarea returnată de o funcţie poate fi pointer, aşa cum se observă în exemplul următor:
Exemplu:
#include <iostream.h>
double *f (double *w, int k)
{ // w conţine adr. de început a vectorului a
cout<<"w="<<w<<" *w="<<*w<<'\n'; // w= adr. lui a ;*w = a[0]=10
return w+=k;
/*incrementeaza pointerului w cu 2(val. lui k); deci w pointează către elementul de
indice 2 din vectorul a*/
}
void main()
{double a[10]={10,1,2,3,4,5,6,7,8,9}; int i=2;
cout<<"Adr. lui a este:"<<a;
double *pa=a; // double *pa; pa=a;
cout<<"pa="<<pa<<'\n' // pointerul pa conţine adresa de început a tabloului
a
// a[i] = * (a + i)
// &a[i] = a + i
pa=f(a,i); cout<<"pa="<<pa<<" *pa="<<*pa<<'\n';
// pa conţine adr. lui a[2], adica adr. a + 2 * sizeof(double);
*pa=2;
}
Exemplu:
b, br 7 12
#include <stdio.h>
#include <iostream.h>
void main() c 12
{
int b,c; Figura 6.4. Variabilele referinţă b, br
int &br=b; //br referinţă la altă variabilă (b)
br=7;
cout<<"b="<<b<<'\n'; //b=7
cout<<"br="<<br<<'\n'; //br=7
cout<<"Adr. br este:"<<&br; //Adr. br este:0xfff4
printf("Adr. b este:"<<&b<<'\n'; //Adr. b este:0xfff4
b=12; cout<<"br="<<br<<'\n'; //br=12
cout<<"b="<<b<<'\n'; //b=12
c=br; cout<<"c="<<c<<'\n'; //c=12
}
STIVE
O stiva este o structura liniara deschisa ale carei extremitati se numesc baza si varf si in
care accesul se face la un singur capat, si anume la varful stivei.
Stiva functioneaza pe principiul LIFO (Last In First Out).
Alocarea dinamica a stivei necesita cunoasterea in orice moment a pointerului pentru
varful stivei. Daca stiva este vida varful coincide cu baza si vor avea aceeasi adresa, si
anume constanta Null.
struct nod
{ int info;
nod *adr_inapoi;
} nod *v;
COADA
O coada este o lista liniara deschisa la care operatiile au loc la ambele capete.
Coada functioneaza pe principiul FIFO (First In First Out)
struct nod
{ int info;
nod *adr_urm;
} *v, *sf;
Cozi şi stive
1. Noţiuni generale
Exemplu
Un graf G este conex, daca oricare ar fi doua varfuri ale sale, exista un lant care
le leaga.
Un lant intr-un graf orientat este un sir de arce {u1, u 2, u3 , …, un} cu proprietatea ca
oricare doua arce consecutive au o extremitate comuna. Altfel spus, un lant este
un traseu care uneste prin arce doua noduri numite extremitatile lantului, fara a
tine cont de orientarea arcelor componente.
Exemplu
Graful este conex pentru ca oricum am lua doua noduri putem ajunge de la unul
la celalalt pe un traseu de tip lant. De exemplu, de la nodul 4 la nodul 2 putem
ajunge pe traseul de noduri (4,3,2) stabilind astfel lantul {u5, u3}, dar si pe traseul
de noduri (4,1,2) stabilind lantul {u6, u2}
Acest graf nu este conex.
Luand submultimea de noduri {1,2,3}, putem spune ca intre oricare doua noduri
din aceasta submultime exista cel putin un lant., de exemplu lantul {u1, u2} sau
{u3, u1}. La fel stau lucrurile cu submultimea de noduri {4,5,6}. Dar nu ptuem gasi
un lant intre un nod din prima submultime si un nod din a doua submultime.
Plecand dintr-un nod al primei submultimi si deplasandu-ne pe arce, nu avem
cum sa trecem in a doua submultime pentru ca nu exista nici un arc direct care
sa lege cele doua submultimi. De exemplu plecand din nodul 1 putem ajunge in
nodul 2 pe traseul {u3, u2}, dar de aici nu putem ajunge mai departe in nodul 4,
deci nu exista lant de la 2 la 4.
Componenta conexa
Componenta conexa a unui graf G=(X, U), reprezinta un subgraf G1=(X1, U1)
conex, a lui G, cu proprietatea ca nu exista nici un lant care sa lege un nod din X
1 cu un nod din X-X 1 (pentru orice nod, nu exista un lant intre acel nod si nodurile
care nu fac parte din subgraf).
Graful tare conex este un graf orientat G=(X, U), daca pentru oricare doua noduri
x si y apartin lui X, exista un drum de la x la y precum si un drum de la y la x.
Exemplu
Daca acest subgrafnu mai este tare conex, atunci el se numeste componenta
tare conexa.
Exemplu
Acesta este graful G=(X,U) tare conex.
Din el eliminam nodul 4.
Grafuri neorientate
Graf = orice mulţime finită V prevăzută cu o relaţie binară internă E.
Notăm graful cu G=(V, E).
1 6
In figura alaturata:
V={1,2,3,4,5,6} sunt noduri
5 E={[1,2], [1,4], [2,3],[3,4],[3,5]} sunt muchii
2
G=(V, E)=({1,2,3,4,5,6}, {[1,2], [1,4], [2,3],[3,4],[3,5]})
3 4
Lanţ compus= lanţul care nu este format numai din muchii distincte
Ciclul este elementar dacă este format doar din noduri distincte,
excepţie făcând primul şi ultimul. Lungimea unui ciclu nu poate fi 2.
Succesiunea de vârfuri 2, 3,
1 6 5, 6 reprezintă un lanţ simplu
şi elementar de lungime 3.
5
2 Lanţul 5 3 4 5 6 este simplu
3 4 dar nu este elementar.
Lanţul 5 3 4 5 3 2 este
compus şi nu este elementar.
Lanţul 3 4 5 3 reprezintă un
ciclu elementar
1 6 1 6
5 5
2 2
3 4 3 4
G G1 este graf partial al lui
G
5 5
2 2
3 4 3
G G1 este graf
partial al lui G
1 6
5
2
3 4
3 4
graf complet. Nr de noduri: 4x(4-1)/2 = 6
Graf conex = graf neorientat G=(V,E) în care pentru orice pereche de
noduri (v,w) există un lanţ care le uneşte.
1 6 1 6
5 5
2 2
3 4 3 4
graf conex nu este graf conex
Componentă conexă = subgraf al grafului de referinţă, maximal în
raport cu proprietatea de conexitate (între oricare două vârfuri există
lanţ);
1 6
5
2
3 4
graful nu este conex. Are 2 componente conexe:
1, 2 si 3, 4, 5, 6
Lanţ hamiltonian = un lanţ elementar care conţine toate nodurile
unui graf
1 6
5
2
3 4
L=[2 ,1, 6, 5, 4, 3] este lant hamiltonian
Ciclu hamiltonian = un ciclu elementar care conţine toate nodurile
grafului
1 6
5
2
3 4
C=[1,2,3,4,5,6,1] este ciclu hamiltonian
Lanţ eulerian = un lanţ simplu care conţine toate muchiile unui graf
1 6
5
2
3 4
1, daca [i,j]∈E
a[i,j]=
0, altfel
Observatii:
5
2
3 4
5
2
3 4
Nodul 2 : 1 3
Nodul 3 : 2 4 5
1 3
Nodul 4 :
3
Nodul 5 :
nod *L[20];
#include<conio.h>
#include<fstream.h>
struct nod
{int nd;
nod *next;};
nod *L[20];
void main()
{fstream f;int i,j,n;
nod *p,*q;
Observatie : In exemplul anterior adaugarea unui nou element in lista se face inainte
celorlalte din lista corespunzatoare.
Aceste doua moduri de reprezentare (prin matrice de adiacenta si prin liste de vecini) se
folosesc dupa natura problemei. Adica, daca in problema se doreste un acces frecvent la
muchii, atunci se va folosi matricea de adiacenta; daca numarul de muchii care se
reprezinta este mult mai mic dect nxn, este de preferat sa se folosesca listele de adiacenta,
pentru a se face economie de memorie.
Probleme propuse :
Componente conexe
Fie G=(V, E) un graf neorientat, unde V are n elemente (n noduri) si E are m elemente
(m muchii).
Definitie: G1=(V1, E1)este o componenta conexa daca:
- - pentru orice pereche x,y de noduri din V1 exista
un lant de la x la y (implicit si de la y la x)
- - nu exista alt subgraf al lui G, G2=(V2, E2) care
sa indeplineasca prima conditie si care sa-l contina
pe G1
1
6 7
Graful alaturat are doua
componente conexe:
2
5
- - subgraful care
contine nodurile:
3 4
12345
- - subgraful care
contine nodurile:
67
Definitie: Un graf G=(V, E)este conex daca pentru orice pereche x,y de noduri din V
exista un lant de la x la y (implicit si de la y la x).
Observatii:
- - Un graf este conex daca admite o singura componenta conexa.
- - Graful anterior nu este conex pentru ca admite doua componente conexe
Graful urmator este conex:
1
6 7
5
2
3 4
ARBORI
Un arbore este un graf conex fara nici un ciclu (n varfuri si n-1 arce).
Legaturile existente intr-un arbore sunt de tip parinte-fiu.
Nodul in care nu intra nici un arc se numeste radacina.
Un nod in care intra un arc se numeste succesor sau fiu, iar cel din care iese un arc sau
mai multe se numeste predecesor sau parinte.
Nodurile sunt organizate pe nivele, iar cele de pe ultimul nivel se numesc frunze sau
noduri terminale.
Nodurile pot contine informatii utile ce poarta denumirea de chei ale arborelui.
ARBORI BINARI
Definitie
Un arbore binar este o multime de n >= 0 noduri, care daca nu este vida, contine un nod
numit radacina, restul nodurilor formând doi arbori disjuncti numiti subarborele
stâng si subarborele drept.
Un arbore generalizat poate fi transformat intr-un arbore binar, astfel incât secventele de
noduri pentru parcurgerea inpreordine sa fie identice in cazul ambilor arbori.
o preordine - lista R, A, B
o inordine - lista A, R, B
o postordine- lista A, B, R.
Cele trei metode de traversare se concretizeaza în trei functii recursive în care Prelucrare
este operatia ce trebuie facuta asupra fiecarui nod.
Are proprietatea ca daca un nod oarecare al arborelui are cheia c, toate nodurile din
subarborele stâng al nodului au cheile mai mici decât valoarea c, respectiv toate cheile
din subarborele drept au cheile mai mari sau egale cu c. De aici rezulta procedeul de
cautare foarte simplu, prin trecerea la fiul stâng sau drept de la nodul curent, functie de
relatia dintre cheia cautata si cea a nodului curent.
Cum înaltimea minima a unui arbore binar ordonat cu n noduri este
hmin=[log2 (n+1)],
rezulta ca o cautare într-un arbore binar ordonat necesita aproximativ log2n comparatii de
chei, fata de n/2 comparatii într-o lista liniara.
Procesul de creare consta în insertia câte unui nod într-un arbore binar ordonat, care
initial este vid, astfel încât dupa insertie el sa ramâna ordonat.
Pentru aceasta se traverseaza arborele începând cu radacina si se selecteaza fiul stâng sau
drept, dupa cum cheia de inserat e mai mica sau mai mare decât a nodului parcurs. Se
repeta pâna când se ajunge la un pointer nil care se inlocuieste cu pointerul spre noul nod
creat. Pentru ca sortarea sa fie stabila, se trece la fiul drept chiar daca valoarea cheii e
egala.
Sortarea reprezinta una dintre cei mai utilizate metode de programare. Are
utilizari de la domenii precum matematica( statistica matematica ), pana la limbi(
realizarea unor dictionare ).De aceea se impune sa gasim cei mai convenabili
algoritmi
si sa prezentam avantajele si dezavantajele acestora. Ne vom rezuma la cei mai
importanti ( in cartea lui Donald Knuth –“ Algoritmi de sortare” sunt prezentati
peste
30 asemenea metode de programare).
Metodele de sortare se clasifica in metode directe si metode avansate. Metodele
directe se bazeaza„ pe algoritmi de dificultate redusa, usor de gasit si de inteles.
Metodele
directe pe care le vom lua in considerare sunt sortarea prin selectie ( SelectSort ),
sortarea prin insertie ( InsertSort ) si sortarea cu bule ( BubbleSort ).
Metodele avansate se bazeaza pe algoritmi putin mai complicati, dar care nu
necesita unostinte avansate de algoritmica. Cateva din cele mai cunoscute sunt
sortarea
rapida (QuickSort), sortarea prin interclasare (MergeSort) si sortarea cu
micsorarea incrementului( Shell Sort ).
La fiecare metoda voi descrie algoritmul, il voi exemplifica in Pseudocod si C++( la
unele metode si parametrizat), voi analiza complexitatea algoritmilor si voi propune
un
set de probleme.
Analizând complexitatea algoritmilor factorul cel mai important este timpul de
executie exprimat în "O-mare". Trebuie sã folosim mai multe criterii pentru
evaluarea
timpului de executie al algoritmului de sortare cum ar fi, numãrul de pasi(de
atribuiri) ai
algoritmului si numãrul de comparatii dintre elemente necesare pentru a sorta N
elemente
Al doilea factor important este cantitatea (volumul) de memorie suplimentarã
folositã de un algoritm de sortare. Metodele se împart în trei tipuri: cei care
sorteazã pe
loc, nu folosesc memorie suplimentarã, cu exceptie poate o micã tabelã sau stivã; cei
care
au o reprezentare de listã înlãntuitã deci folosesc N cuvinte de memorie în plus
pentru
pointerii listei; cei care au nevoie de memorie suplimentarã pentru o copie a tabelei
initiale.
Recomand ca lucrarea sa fie parcursa pas cu pas, intrucat nu exista un algoritm
perfect. In unele cazuri metodele directe sunt mai economicoase( mai putine
elemente de
comparat ), in altele cele avansate sunt mai bune. De asemenea recomand
incepatorilor ca
sa utilizeze fiecare metoda. 1. Metode directe
1.1 BubbleSort
Acesta metoda se rezuma la a compara fiecare element cu celelalte, facandu-se
interschimbarea daca elementul mai mare are indexul mai mic. Este cea mai simpla
metode de sortare si nu necesita cunoasterea detaliata a limbajului de programare.
Poate
fi folosita cu succes de catre incepatori.
Algoritmul este urmatorul:
1.3 InsertionSort
Insertia directã apartine familiei de tehnici de sortare care se bazeazã pe metoda
"jucãtorului de bridge". Este un algoritm aproape la fel de simplu ca Selection sort,
dar
poate mai flexibil. Considerãm elementele A[1]...A[i-1] fiind sortate, inserãm
elementul
A[i] în locul ce îi revine.
Fiind dat o tabelã A cu N elemente nesortate, parcurgem tabela si le inserãm
fiecare element în locul propriu între celelalte elemente considerate sortate. Pentru
fiecare
i = 2..N , elementele A[1]…A[i] sunt sortate prin inserarea lui A[i] între lista
elementelor
sortate: A[1]…A[i-1]. Elementele aflate în stânga indexului sunt în ordine sortatã
dar nu
sunt în pozitia lor finalã. Tabela este sortatã complect când indexul ajunge la
capãtul
drept al tabelei.
Algoritmul este :
Insertion(a[nmax+1])
Pentru iÅ2 pana la n
JÅi
pana cand (a[j-1]>a[i])
a[j]Åa[j-1] jÅ j-1
a[j]Åa[i]Algoritmul in C++ este :
Insertion ( type A[ ], int size )
}
int i, j;
type v;
For ( i = 2; i <= size; i++ )
}
v = A[i]; j = i;
While ( A[j-1] > v )// mut elementele cu o pozitie in
fata
{ A[j] = A[j-1]; j --; }
A[j] = v;// pun elem in poz lui
{
{
Insertion sort este un algoritm de sortare simplu, liniar: O(N) pentru tabele care
contin N elemente aproape sortate. Timpul de executie al algoritmului depinde de
numãrul inversãrilor, deci de ordinea initialã al elementelor.
2.1 QuickSort
În practicã algoritmul de sortare cel mai rapid este Quicksort numitã sortare
rapidã, care foloseste partitionarea ca idee de bazã. Este mai rapidã decât orice
metodã
de sortare simplã, se executã bine pentru fisiere sau tabele mari, dar ineficient
pentru cele
mici.Strategia de bazã folositã este "divide et impera", pentru cã este mai usor de
sortat
douã tabele mici, decât una mare. Algoritmul este usor de implementat, lucreazã
destul de
bine în diferite situatii si consumã mai putine resurse decât orice altã metodã de
sortare în
multe situatii. Necesitã numai în jur de NlogN operati în cazul general pentru a
sorta N
elemente.
Metoda QuickSort presupune gasirea pozitiei finale pe care o ocupa elemenetul de
pe prima pozitie comparandu-l cu elementele din cealalta partitie a tabelului, acest
algoritm realizandu-se pana cand partitia are 1 element.
2.2 MergeSort
Algoritmul de sortare prin interclasare se bazeazã pe urmãtoarea idee: pentru a
sorta o tabelã cu N elemente îl împãrtim în douã tabele pe care le sortez separat si
le
intrclasãm. Este o metodã de sortare care foloseste strategia de bazã "divide et
impera",
conform cãreia problema se descompune în alte douã subprobleme de acelasi tip si
dupã
rezolvarea lor rezultatele se combinã. Algoritmul sorteazã elementele în ordine
crescãtoare.
Tabelul se imparte in n/2, dupa aceea tabelele se impart in jumatate, tot asa pana
cand tabelele formate au mai putin sau cel mult de k elemente(in cazul nostru k=2-
este
cel mai usor sa compari 2 elemente).
Algoritmul presupune :
1. Afla mijlocul tabelului
2. Sorteaza prima jumatate a tabelului
3. Sorteaza a doua jumatate a tabelului
4. Imbina cele 2 jumatati Algoritmul in Pseudocod este:
2.3 ShellSort
Sortarea cu micsorarea incrementului (shellsort) este o extensie simplã al
Insertion sortului care câstigã vitezã permitând schimbarea elementelor aflate
departe.
Ideea de bazã o constituie rearanjarea elementelor din tabela A în asa fel încât,
luând
fiecare a h-a element (începând de oriunde), sã obtinem o tabelã sortatã. Astfel
spunem cã
tabela este h-sortatã. O tabelã , h-sortatã este formatã din h subtabele sortate
independent,
intercalate. Folosind astfel o procedurã pentru fiecare secventã a valorii lui h care se
terminã în 1, va produce o tabelã sortatã.
Algoritmul este :
shell-sort (a)
h←1
pana cand h<n/9
h ← 3*h+1
pana cand h>0
pentru i ← h+1 pana la n
t ← a[i]
j←i
pana cand (j>h si a[j-h] > t)
interschimba(a[j], a[j-h])
j ← j-h
h ← h/3
Algoritmul in C++ este :
Shellsort(int A[], int n)
}
int i, j, h, v;
for (h = 1; h <= n/9; h = 3*h+1);
for ( ; h > 0; h /= 3 )
for (i = h+1; i <= n; i += 1)
}
v = A[i]; j = i;
while ( j>h && A[j-h]>v )
}
A[j] = A[j-h];
j - = h;
{
A[j] = v;
{
{
Exemplu : sirul initial este {1 11 8 10 12 4 7 3 1 13 1 6 9 5 2 }
Aşa cum s-a subliniat în capitolul 1.3., rezolvarea unei probleme se poate face pe 3
direcţii:
Rezolvarea orientată pe algoritm (pe acţiune), în care organizarea datelor este
neesenţială;
Rezolvarea orientată pe date, acţiunile fiind determinate doar de organizarea datelor;
Facilităţile oferite de
programarea orientată obiect (conform lui Pascou) sunt:
1. abstractizarea datelor;
2. moştenirea;
3. încapsularea (ascunderea) informaţiei;
4. legarea dinamică (“târzie”).
ABSTRACTIZAREA DATELOR
Obiectele sunt componente software care modelează fenomene din lumea reală. În
general, un fenomen implică tipuri diferite de obiecte. Obiectele care reprezintă aceeaşi
idee sau concept sunt de acelaşi tip şi pot fi grupate în clase (concrete sau abstracte).
Clasele implementează tipuri de date (aşa cum s-a subliniat în capitolul 2, un tip de date
înseamnă o mulţime de valori pentru care s-a adoptatat un anumit mod de reprezentare şi
o muţime de operatori care pot fi aplicaţi acestor valori), deci şi operatorii destinaţi
manipulării acestora: Clasă = Date + Operaţii.
De exemplu, programatorul îşi poate defini tipul (clasa) matrice şi operatorii care pot
fi aplicaţi matricilor (* pentru înmulţirea a două matrici, + pentru adunarea a două
matrici, - pentru scăderea a două matrici, etc). Astfel, el poate folosi tipul matrice în mod
similar unui tip predefinit:
matrice A, B;
matrice C=A+B;
Tipul unui obiect (şablon al obiectului) este o clasă. O clasă se caracterizează prin:
numele clasei, atribute, funcţii şi relaţii cu alte clase.
Instanţa este un obiect dintr-o clasă (A, B, C sunt obiecte, instanţe ale clasei matrice)
şi are proprietăţile definite de clasă. Pentru o clasă definită, se pot crea mai multe instanţe
ale acesteia. Toate obiectele au o stare şi un comportament. Starea unui obiect se referă
la elementele de date conţinute în obiect şi la valorile asociate acestora (datele membre).
Comportamentul unui obiect este determinat de care acţiunile pe care obiectul poate să le
execute (metodele).
Atributele specificate în definiţia unei clase descriu valoric proprietăţile obiectelor din
clasă, sub diferite aspecte. Cele mai multe limbaje orientate obiect fac următoarea
distincţie între atribute:
atribute ale clasei (au aceeaşi valoare pentru toate instanţele clasei);
atribute ale instanţei (variază de la o instanţă la alta, fiecare instanţă având
propria copie a atributului).
În limbajul C++ atributele se numesc date membre. Toate datele membre sunt atribute
instanţă. Atributele de clasă se pot obţine în cazul datelor membre statice (aceeaşi adresă
de memorare pentru orice instanţă a clasei).
La crearea unui obiect, alocarea memoriei se poate fi face static sau dinamic (cu ajutorul
unor funcţii membre speciale, numite constructori). Eliberarea memoriei se realizează cu
ajutorul unor funcţii membre speciale, numite destructori, în momentul încheierii
existenţei obiectului respectiv.
DEFINIŢIA CLASELOR ŞI ACCESUL LA MEMBRII
LEGĂTURA CLASĂ-STRUCTURĂ-UNIUNE
Aşa cum s-a subliniat în capitolul 9, o clasă reprezintă un tip abstract de date, care
încapsulează atât elementele de date (datele membre) pentru care s-a adoptat un anumit
mod de reprezentare, cât şi operaţiile asupra datelor (funcţiile membre, metode). În
limbajul C++, structurile şi uniunile reprezintă cazuri particulare ale claselor, putând avea
nu numai date membre, câmpuri de date (vezi capitolul 8), dar şi funcţii membre. Singura
diferenţă între structuri şi uniuni constă în faptul că la uniuni, pentru memorarea valorilor
datelor membre se foloseşte aceeaşi zonă de memorie. Deosebirea esenţială între structuri
şi uniuni - pe de o parte - şi clase - pe cealată parte - constă în modul de acces la
membrii: la structuri şi uniuni membrii (datele şi metodele) sunt implicit publici, iar la
clase - implicit privaţi (membrii sunt încapsulaţi). Lipsa unor modalităţi de protecţie a
datelor, face ca tipurile de date introduse prin structuri sau uniuni să nu poată fi strict
controlate în ceea ce priveşte operaţiile executate asupra lor. În cazul claselor, modul de
acces la membrii tipului de date (în scopul protejării acestora) poate fi schimbat prin
utilizarea modificatorilor de control ai accesului: public, private, protected.
. DECLARAREA CLASELOR
Modul de declarare a unei clase este similar celui de declarare a structurilor şi a
uniunilor:
class nume_tip{
modificator_control_acces:
lista_membrilor;
} lista_variabile;
Exemplu:
class masina{
char *culoare; // dată membru la care accesul este, implicit, private
int capacit_cil; // dată membru la care accesul este, implicit, private
public:
void citire_date();
//metodă cu acces public, care permite introducerea datelor despre o instanţă a
clasei
int ret_capacit(); //metodă cu acces public
};
Membrii culoare şi capacitate_cil (accesul private) pot fi accesaţi doar prin
intermediul metodelor clasei.
Exemplu:
class persoana{
char *nume; //dată membru privată
public:
void citire_inf_pers(); //metodă publică
private:
int varsta; //dată membru privată
};
OBIECTE
Un obiect este o dată de tip definit printr-o clasă. În exerciţiul anterior, punctul c, în
funcţia main, se declară obiectul (variabila) a de tip dreptunghi. Spunem că obiectul
a este o instanţă a clasei dreptunghi. Se pot declara oricâte obiecte (instanţe) ale
clasei. Aşa cum se observă din exemplu, declararea obiectelor de un anumit tip are o
formă asemănătoare celei pentru datele de tip predefinit:
nume_clasa lista_obiecte;
Exemple:
dreptunghi a;
dreptunghi b, c, d;
Datele membru se alocă distinct pentru fiecare instanţă (atribute ale instanţei) a clasei
(pentru declararea obiectelor a, b, c, d de tip dreprunghi, vezi figura 10.1.). Excepţia de
la această regulă o constituie datele membru statice, care există într-un singur exemplar,
comun, pentru toate instanţele
a.Lun clasei. b.Lun c.Lun
Lung Lung Lung g
g g
a.la b.la lat c.la
lat lat t
a t b t c
d.Lun
Lung g
lat d.la
d t
Vizibilitate
MOŞTENIREA
În cazul moştenirii unice, fiecare clasă are doar o superclasă. Există două modalităţi de
specializare a unei clase de bază:
introducerea de extra-atribute şi extra-metode în clasa derivată (particulare doar clasei
derivate);
redefinirea membrilor în clase derivate (polimorfism).
MOŞTENIREA MULTIPLĂ
În situaţia moştenirii multiple, o clasă are mai multe superclase. Astfel, moştenirea clasei
va fi multiplă (rezultând o structură de reţea).
A A
B C D B C D
E F G H E F
Moştenirea multiplă este utilă, dar poate crea ambiguităţi (când pentru acelaşi atribut se
moştenesc valori diferite). Există mai multe strategii de rezolvare a conflictului
(părintele cel mai apropiat, cel mai depărtat, etc.). Deasemenea, este posibilă o moştenire
repetată, în care o clasă ajunge să moştenească de la aceeaşi clasă, pe drumuri diferite în
reţea (vezi figura 9.3., în care clasa E moşteneşte de la aceeaşi clasă A, pe drumurile A-
B-E, A-C-E) . Aşa cum vedea în capitolele următoare, în aceste situaţii, limbajul C++
oferă programatorului două strategii: 1) clasa E poate avea două copii ale lui A, una
pentru fiecare drum; 2) clasa E are o singură copie, iar A este clasă virtuală de bază şi
pentru C şi pentru B. Ideea moştenirii multiple poate duce la utilizarea unor clase pentru
care nu există instanţe, care să ajute doar la organizarea structurii (reţelei) de moştenire.
În plus, limbajul C++ permite un control puternic asupra atributelor şi metodelor care vor
fi moştenite.
În limbajul C++ încapsularea poate fi forţată prin controlul accesului, deoarece toate
datele şi funcţiile membre sunt caracterizate printr-un nivel de acces (rezultând astfel o
mare flexibilitate). Nivelul de acces la membrii unei clase poate fi (figura 9.4.):
public
Clasa A
private: membrii (date şi metode) la care protected
accesul este private pot fi accesaţi doar prin private
metodele clasei (nivel acces implicit);
protected: aceşti membri pot fi accesaţi
prin funcţiile membre ale clasei şi funcţiile
membre ale clasei derivate;
public: membrii la care accesul este
public pot fi accesaţi din orice punct al
domeniului de existenţă a clasei respective;
clasă
friend: aceşti membri pot fi accesaţi prin Clasa B
derivată
funcţiile membre ale funcţiei prietene
specificate.
Figura 9.4. Accesul la membrii unei clase
În limbajul C++, nivelul de acces poate preciza şi tipul de moştenire (capitolul 12).
Publică, unde în clasa derivată nivelul de acces al membrilor este acelaşi ca în clasa de
bază;
Privată, unde membrii protected şi public din clasa bază devin private în clasa
derivată.