Sunteți pe pagina 1din 37

6.

Structura programelor în C++

Pentru a utiliza intrările şi ieşirile “standard” trebuie inclusă clasa <iostream.h>. Acest
pachet, iostream, defineşte, în mod automat, o variabilă (obiect) denumit cout, care acceptă date
pentru ieşirea standard. Pentru a trimite date la ieşirea standard se va utiliza operatorul <<. În C
acest operator este un operator de deplasare la stânga bit cu bit. În C++ se poate supradefini
(redefini) un operator, adică i se poate da o nouă semnificaţie când este utilizat cu un obiect de un
anumit tip. Deci cu obiecte iostream operatorul << înseamnă “trimite la”:
cout << “Salut!”;
Un program C sau C++ este o colecţie de variabile, definiţii de funcţii şi apeluri de funcţii.
Când începe programul el execută codul iniţializat şi apelează o funcţie specială main(). Definiţia
unei funcţii, simple, este de forma:
int functie () {
// aici este codul funcţiei (acesta este un comentariu)
};
Şi funcţia main() urmează aceste reguli, deci are un tip returnat, care este de tip int. Declaraţiile
sunt delimitate de ; în timp ce compilatorul ignoră spaţiile albe şi trecerea la o linie nouă. În C şi C+
+ comentariile sunt delimitate de /* şi */, şi ele pot include şi mai multe linii, în timp ce în C++ se
mai pot adăuga comentarii şi cu // la începutul unei linii noi.
Să exemplificăm acum structura unui program simplu:
// Salut.cpp
// programul tipareste mesajul “Salut!”
#include <iostream.h>
void main (void) {
cout << “Salut!” << endl;
};

Funcţia specială iostream, endl are ca efect trecerea pe o linie nouă, după tipărirea mesajului
“Salut!”. Un text inclus între ghilimele este denumit şir de caractere. Compilatorul rezervă spaţiu
de memorie pentru caracterele unui şir de caractere şi memorează echivalentul ASCII pentru fiecare
caracter, iar pentru delimitarea unui astfel de şir compilatorul va pune valoarea 0, indicând sfârşitul
şirului. Pentru includerea de caractere speciale în interiorul unui astfel de şir se utilizează secvenţele
de evitare, care constau din caracterul \ urmat de un cod special.
Un alt exemplu simplu de tipărire a unor valori numerice şi nenumerice:
// Tipariri.cpp
// programul tipareste diferite valori numerice si caracter
#include <iostream.h>
#include <conio.h>
void main (void) { clrscr();
cout << “numar in zecimal: ” << dec << 15 << endl;
cout << “ in octal: ” << oct << 15 << endl;
cout << “ in hexa: ” << hex << 15 << endl;
cout << “numar in virgula mobila: ” << 3.14159 << endl;
cout << “caracter netiparibil (secventa de evitare): ” << char(27) << endl;
getch()
};

Acest exemplu arată cum tipăreşte clasa iostream diverse valori utilizând modificatori (care
sunt prezenţi şi în C, dar sub altă formă), care nu tipăresc, dar modifică starea ieşirii (dec, oct, hex),
permiţând, în acest caz tipărirea aceleiaşi valori în diferite baze. Numerele reale, adică în virgulă

1
mobilă, sunt detectate în mod automat de către compilator. Pentru tipărirea oricărui caracter,
tipăribil sau nu, se utilizează char, care seamănă cu un apel de funcţie char().
Concatenarea de şiruri de caractere este realizată de preprocesor, dacă şirurile de caractere,
între ghilimele, sunt adiacente; în acest fel se va obţine un singur şir de caractere, ca în exemplul
următor:
// Concat.cpp
// programul concateneaza siruri de caractere
#include <iostream.h>
void main (void) {
cout << “Acesta este un exemplu de concatenare a unor linii ”
“de text prea lungi pentru a fi puse pe o singura linie ”
“la editare, si atunci textul a fost descompus pe ”
“multe linii ce vor fi concatenate la afisare pe ecran\n”
};

O declaraţie din program poate fi continuată pe oricâte linii, din acest motiv nu vedeţi la
sfârşitul fiecărei linii caracterul ;. Şirurile de caractere de pe fiecare linie, din exemplul următor vor
fi concatenate.
Pentru introducerea datelor, deci pentru citirea lor, se va utiliza, tot din clasa iostream,
intrarea standard (tastatura) denumită cin (“console input”). În mod normal cin aşteaptă date de la
consolă, dar această intrare poate fi redirectată şi de la alte surse. Operatorul iostream utilizat cu cin
este <<. De exemplu, programul următor citeşte un număr zecimal şi-l tipăreşte în octal şi în
hexazecimal:
// Convers.cpp
// programul citeste un numar zecimal si-l tipareste in octal si hexa
#include <iostream.h>
#include <conio.h>
void main (void) { clrscr();
int numar;
cout << “Introduceti un numar zecimal: ”;
cin >> numar;
cout << “numarul in octal: 0” << oct << numar << endl;
cout << “numarul in hexa: 0x” << hex << numar << endl;
getch();
};

În concluzie, intrările şi ieşirile sunt furnizate în C++ de anumite obiecte, denumite şi


“fluxuri”, care sunt incluse în fişierul <iostream.h>. Doi operatori sunt supraîncărcaţi pentru a
realiza aceste operaţii: << pentru ieşire, respectiv >> pentru intrare.
Intrările şi ieşirile standard, de la tastatură şi respectiv display, pot fi redirectate, astfel că
intrarea poate fi luată de la un fişier iar ieşirea poate fi şi ea transmisă la un fişier. Pentru a redirecta
I/O în linia de comandă pentru anumite sisteme de operare (Unix/Linux, DOS) se utilizează
semnele ‘<’ pentru intrare şi ‘>’ pentru ieşire. Astfel comanda următoare:
program < intrare > iesire
are următoarea semnificaţie: programul program care, în mod norma, citea date de la consolă şi
afişa rezultate la display, acum intrarea va fi redirectată de la fişierul intrare, iar ieşirea va fi
redirectată la fişierul iesire.
O astfel de comandă poate fi dată şi din C sau C++, la fel cum poate fi apelat orice program,
utilizând funcţia C Standard system(), care este declarată în fişierul antet <cstdlib>, astfel:
// apelsys.cpp
// executia comenzii anterioare dintr-un program C sau C++

2
#include <cstdlib>
void main (void) {
system (“program < intrare > iesire”);
};

Dacă se utilizează >> în loc de > ieşirea va fi adăugată la fişierul iesire în loc să fie scrisă
peste conţinutul anterior, ca în exemplul anterior.

6.1. Tipuri de date în C++


Tipurile de date pot fi predefinite (intrinseci) sau abstracte. Cele predefinite sunt, aproape
identice între C şi C++. În schimb tipurile de date definite de utilizator sunt cele create ca nişte
clase şi sunt referite ca tipuri abstracte de date.
Pentru tipurile predefinite valorile minime şi maxime (pentru diferite tipuri) sunt definite în
fişierele antet de sistem limits.h şi float.h (sau în C++: #include <climits> sau #include <cfloat>).
Înainte ca bool să devină parte a C++, tendinţa era de a utiliza diferite tehnici de producere a
comportării Booleane. În C++ standard, tipul bool poate avea două stări exprimate de constantele
true (care se converteşte la un întreg 1) şi false (care se converteşte la întregul 0).

Element Utilizare cu bool


&& || ! are argumente şi returnează valori bool
< > <= >= == != furnizează rezultate bool
if, for, while, do expresiile condiţionale sunt convertite la bool
? : primul operand se converteşte la valoare bool

Compilatorul va converti implicit de la un întreg, int, la bool.

6.2. Pointeri şi referinţe (adrese)


O variabilă ce păstrează (memorează) adresa unui obiect (variabilă, funcţie etc.) este
denumită pointer (în C se mai utilizează termenul de referinţă, dar pe care aici nu-l vom utiliza,
deoarece în C++ are altă semnificaţie, descrisă în continuare). Operatorul care defineşte un pointer
este *, care în acest context nu mai semnifică înmulţire. În acest fel variabila este referită indirect
prin intermediul variabilei ce memorează adresa ei. La definirea unui pointer trebuie să se specifice
tipul variabilei referite de acesta, întrucât în funcţie de aceasta începând de la adresa respectivă,
memorată în variabila pointer, se vor interpreta (decodifica) un număr diferit de octeţi: 2 pentru
tipul int, 4 pentru tipul long int, tot 4 pentru tipul float, dar vor fi interpretaşi în alt mod faţă de tipul
long int etc.
De ce se doreşte să se modifice o variabilă indirect, utilizând o altă variabilă, care conţine
adresa variabilei de modificat, respective, ca un proxy?
Răspunsul la această întrebare are două scopuri:
1. Pentru a modifica “obiecte exterioare” din interiorul unei funcţii; aceasta reprezintă
utilizarea cea mai frecventă a pointerilor, şi
2. Pentru a permite implementarea multor alte tehnici de programare.
De obicei, când se transmite un argument la o funcţie, o copie a argumentului este realizată în
interiorul funcţiei; această metodă de transfer a parametrilor poartă numele de transfer prin valoare.
Să considerăm exemplul următor.
// transval.cpp
// exemplificare a transferului parametrilor prin valoare la o functie
#include <iostream.h>
void functie ( int a )

3
{
cout << “a = ” << a << endl; // se tipareste valoarea lui “a” primita de functie
a = 10;
cout << “a = ” << a << endl; // se tipareste valoarea lui “a” modificata anterior
}
int main ()
{
int x = 1000;
cout << “x = ” << x << endl; // tiparire x din functia main()
functie ( x ); // apel functie cu parametrul x = 1000;
cout << “x = ” << x << endl; // tiparire x din functia main()
};

În funcţia functie(), a este variabilă locală. Rezultatul execuţiei programului va fi următorul:


x = 1000
a = 1000
a = 10
x =1000
Când suntem în funcţia functie(), x este un obiect exterior şi modificarea variabilei locale a
nu va afecta obiectul exterior (x), deoarece se află în locaţii de memorie separate. Dar, dacă dorim
să modificăm acest obiect exterior, x, atunci trebuie să utilizăm un pointer. Un pointer este un alias
pentru altă variabilă. Dacă dorim acest lucru exemplul anterior devine:
// transptr.cpp
// exemplificare a transferului parametrilor prin pointer la o functie
#include <iostream.h>
void functie ( int *p )
{
cout << “*p = ” << *p << endl; // se tipareste valoarea referita de “p”
*p = 10;
cout << “p = ” << p << endl; // se tipareste valoarea lui “p” (adresa lui x)
}
int main ()
{
int x = 1000;
cout << “x = ” << x << endl; // tiparire x din functia main()
cout << “&x = ” << &x << endl; // tiparire adresa lui x
functie ( &x ); // apel functie cu parametrul adresa lui x;
cout << “x = ” << x << endl; // tiparire x din functia main()
};

Rezultatul execuţiei programului va fi următorul:


x = 1000
&x = 8FA3FFF4 (această valoare depinde de sistem)
*p = 1000
p = 8FA3FFF4 (această valoare depinde de sistem)
x =10
Acum functie() are ca argument un pointer (&x), deci o adresă, iar dereferenţierea acestuia permite
modificarea obiectului exterior, x. În acest fel transmiterea unui pointer la o funcţie va permite
funcţiei să modifice obiectul exterior.
Pe lângă această modalitate de modificare a unui obiect exterior, în C++ mai există o
modalitate de a transmite o adresă unei funcţii, şi anume aceasta este transmitere prin referinţă şi

4
există şi în alte limbaje nu numai în C++. Idea de bază a utilizării referinţei este aceeaşi ca în cazul
demonstrat anterior al utilizării pointerilor: se poate transmite adresa unui argument al funcţiei
utilizând o referinţă. Diferenţa între referinţă şi pointer este că apelul unei funcţii care primeşte o
referinţă este mai clară, sintactic, decât apelul unei funcţii care primeşte un pointer. Programul
anterior modificat pentru a transfera parametrii prin referinţe, va fi următorul:
// transref.cpp
// exemplificare a transferului parametrilor prin referinta la o functie
#include <iostream.h>
void functie ( int &r )
{
cout << “r = ” << r << endl; // se tipareste valoarea referita de “r”
cout << “&r = “ << &r << endl; // se tipareste adresa lui “r” (referinta)
r = 10;
cout << “r = ” << r << endl; // se tipareste valoarea referita “r”
}
int main ()
{
int x = 1000;
cout << “x = ” << x << endl; // tiparire x din functia main()
cout << “&x = ” << &x << endl; // tiparire adresa lui x
functie ( x ); // apelul functiei seamana cu transmiterea prin valoare
// dar este, de fapt, transmitere prin referinta;
cout << “x = ” << x << endl; // tiparire x din functia main()
};

Rezultatul execuţiei programului va fi următorul:


x = 1000
&x = 8FA5FFF4 (această valoare depinde de sistem)
r = 1000
&r = 8FA5FFF4 (această valoare depinde de sistem)
r =10
x = 10
În lista de argumente a funcţiei în loc de int * pentru a transmite un pointer se pune int &
pentru a transmite o referinţă. La definirea funcţiei se pune doar r şi se obţine valoarea variabilei pe
care r o referă. Dacă se fac atribuiri lui r acestea se atribuie variabilei pe care r o referă. La apelul
funcţiei se pune doar x ca argument al funcţiei. Deci acest mod de transfer al parametrilor, prin
referinţă, permite, la fel ca în cazul pointerilor, modificarea unui obiect exterior.
La definirea unei referinţe, la fel ca la definirea unui pointer, trebuie să se specifice tipul
variabilei referite de acesta, întrucât în funcţie de aceasta începând de la adresa respectivă,
memorată în variabila referinţă, se vor interpreta (decodifica) un număr diferit de octeţi: 2 pentru
tipul int, 4 pentru tipul long int, tot 4 pentru tipul float, dar vor fi interpretaşi în alt mod faţă de tipul
long int etc.
În cazul funcţiilor, argumentele acestora pot lua şi valori implicite. Astfel pentru o declaraţie
de forma:
float funct (char, int = 10, char* = "sir");
apelurile următoare vor considera pentru unele argumente valori implicite:
funct ( c, n, "alt sir init.");
funct ( c, n ); // echivalent cu funct(c, 10, "sir");
funct ( c ); // echivalent cu funct(c, 10, "sir");
funct ( ) ; // eroare
Aşa cum o funcţie poate primi un parametru prin referinţă, tot aşa ea poate returna o
referinţă ca rezultat al funcţiei. Pentru o declaraţie de forma:
5
int& f ( ) { . . . . . return x;}
funcţia va returna o referinţă (a lui x), deci un sinonim al obiectului (se creează o variabilă,
pe stivă, ce conţine această valoare). f() este o referinţă pentru variabila x, deci este o valoare-
stânga. Spre deosebire de o declaraţie de forma:
int f ( ) { . . . . . return x;}
care va returna valoarea lui x, şi deci creează o variabilă pe stivă ce conţine această valoare; f() nu
este o valoare-stânga.
Trebuie menţionat că numai ultimele argumente pot lua implicit valori, deci mai puţin primul
argument, declaraţia următoare reprezentând o eroare:
float funct (char = 'c', int , char* = "sir"); // eroare
Bineînţeles, pentru oricare dintre cele trei modalităţi de transfer a argumentelor la o funcţie
(valoare, pointer sau referinţă), datele transmise pot fi oricare dintre tipurile de bază (char, int, float
şi double), împreună cu oricare dintre modificatorii corespunzători (signed, unsigned, short şi long).
Pentru pointer se mai poate utiliza şi tipul void care, în general, înseamnă nimic-nici un tip
de date, dar în acest caz înseamnă că orice tip de adresă poate fi asignată la un astfel de pointer.
Trebuie menţionat, însă, că după o astfel de atribuire se pierde orice informaţie asupra tipului său, şi
pentru a dereferenţia un astfel de pointer, void, trebuie făcută conversia la tipul corect referit, ca în
exemplul următor.
// Punctvoid.cpp
void main (void)
{
void *vp
char c;
float f;
int i = 1000; // se pot face atribuiri la orice tip:
vp = &c;
vp = &f;
vp = &i;
// dar nu se poate dereferentia un pointer fara a-i specifica tipul:
// *vp = 3; va genera o eroare la compilare
// dereferentierea se poate face doar daca se realizeaza conversia la acel tip:
*(( int *) vp) = 3;
}

Conversia ( int *) vp spune compilatorului că tipul void va fi tratat ca tipul int, şi atunci
pointerul poate fi dereferenţiat. Este recomandat să nu se utilizeze, decât în cazuri speciale, pointeri
de tipul void întrucât este permis ca ei să fie convertiţi la orice tip, ceea ce ar putea conduce, din
neatenţie, la blocarea programului sau chiar a calculatorului.
În schimb nu se poate avea o referinţă de tipul void, din motive ce vor fi explicate ulterior.
Programarea orientată pe obiecte introduce noţiunea de obiect, noţiune ce este caracterizată
de trei atribute: tip, adresă şi valoare. În acest mod sunt văzute toate obiectele: variabilele, pointerii,
vectorii (tablourile), clasele şi structurile.
Definiţiile acestora se pot reface prin raportare la noţiunea de obiect; de exemplu o variabilă
de tip întreg este un obiect (ce are un tip, o adresă şi o valoare), în care valoarea este un număr
întreg; un pointer este un obiect a cărui valoare este un număr cu semnificaţie de adresă. În mod
asemănător se pot defini şi celelalte obiecte.
Atributul const poate fi utilizat în C++ pentru a exprima o constantă (în C este numai
#define): const int N = 10;
int vector [N];
Trebuie reamintit sensul acestui atribut în funcţie de poziţia sa în declaraţii, pentru a nu se
crea confuzii pentru unele declaraţii. De exemplu, declaraţia:
const int *ptr;
6
echivalentă sau se citeşte ca:
(const int) *ptr;
adică ptr este un pointer la o constantă de tip int. Pentru următoarea secvenţă de declaraţii:
int i;
const int *ptr = &i;
valoarea variabilei i nu se poate modifica prin intermediul pointerului, adică nu putem scrie:
*ptr = 10 ; /* va fi o eroare */
în schimb putem modifica variabila i: i = 10;
Pentru o declaraţie de forma:
int i;
int * const q = &i;
q este un pointer constant, deci care nu poate fi modificat, către o valoare de tip întreg, i. Nu se
poate modifica valoarea pointerului q, dar se poate modifica valoarea referită de acesta:
*q = 10;
În fine, dacă vrem ca atât pointerul cât şi valoarea referită de acesta să nu poată fi modificate
în cadrul blocului respectiv, declaraţia va fi de forma:
const int * const *ptr = &i;

Pointer la o funcţie
Între C şi C++ sunt unele incompatibilităţi, pe lângă diferenţa majoră dintre ele care este, de
fapt, programarea orientată pe obiecte, specifică limbajului C++. Astfel, în C++, pentru toate
funcţiile trebuie specificat tipul valorii returnate de aceasta. În C dacă nu se specifică tipul returnat
se presupune că este de tip int. O funcţie care nu returnează valori are tipul void.
Odată o funcţie compilată şi încărcată pentru execuţie, ea ocupă o zonă de memorie, şi acea
zonă de memorie, şi deci însăşi funcţia are o adresă. Se poate utiliza adresa funcţiei cu un pointer. la
fel cum se utilizează adresa unei variabile. Declararea şi utilizarea unui pointer la o funcţie este un
pic opacă, la început, dar urmează formatul restului limbajului.
Definirea unui pointer la o funcţie care nu are argumente şi nu returnează nici o valoare se
face astfel:
void ( * ptr_functie) ( );
Pentru a citi o astfel de declaraţie, şi nu numai, se procedează, în general, astfel: se porneşte din
“mijloc”, adică de la numele variabilei “ptr_functie”, după care se caută către dreapta cel mai
apropiat element (nimic, în acest caz, deoarece vă opreşte paranteza rotundă dreapta), apoi se caută
către stânga (şi se întâlneşte un pointer specificat prin asterisc, *), apoi se caută iar către dreapta (şi
se află o listă vidă de argumente ‘()’, care indică o funcţie fără argumente), apoi se caută la stânga
(void va indica că funcţia nu returnează nici o valoare). Această căutare dreapta-stânga-dreapta este
valabilă pentru cele mai multe dintre declaraţii. Dacă nu se pun parantezele funcţiei, atunci
declaraţia:
void * ptr_functie ( );
defineşte o funcţie care returnează un pointer, void *, şi nu un pointer la o funcţie.
Să considerăm câteva decşaraţii şi definiţii şi să vedem care este semnificaţie lor:
void * ( * ( * fp1) (int) ) [10];
float ( * ( * fp2) (int, int, float) ) ( int );
typedef double ( *( *( *fp3) ( ) ) [10] ) ( );
fp3 q;
int ( *( *f4 () ) [10] ) ();
Prima are următoarea semnificaţie: fp1 este un pointer la o funcţie care are un argument
întreg şi returnează un pointer la un tablou de 10 pointeri de tip void,
Cea de-a doua: fp2 este un pointer la o funcţie care are trei argumente (int, int, float) şi
returnează un pointer la o funcţie care ia un argument int şi returnează o valoare float.
A treia: fp3 este un pointer la o funcţie fără argumente şi returnează un pointer la un tablou
de pointeri la funcţii care nu au argumente şi returnează o valoare de tip double.
7
Şi, în fine, ultima declaraţie: fp4 este o funcţie care returnează un pointer la un tablou de 10
pointeri la funcţii care returnează întregi, int.
Pentru a utiliza un pointer la o funcţie, el trebuie asignat la adresa unei funcţii. Pentru a
apela funcţia trebuie dereferenţiat pointerul în acelaşi mod în care a fost declarat. Următorul
exemplu arată cum un pointer la o funcţie este definit şi utilizat:

// ptrfunct.cpp
// definirea si utilizarea unui pointer la o functie
#include <iostream.h>
void functie ( ) {
cout << “functie ( ) apelata . . . !” << endl;}
int main ( ) {
void ( * fp1 ) (); // definirea unui pointer la functie
fp1 = functie; // initializarea lui cu adresa lui ‘functie()’
( * fp1 ) ( ); // prin dereferentiere se apeleaza ‘functie()’
void ( * fp2 ) () = functie; // definire si initializare simultana
( *fp2 ) (); // si, din nou, apelul ‘functie’-i
}

De reţinut că la iniţializarea unui pointer la o funcţie cu adresa unei funcţii, aceasta nu este
urmată de lista de argumente şi de parantezele între care sunt incluse acestea (fp1 = functie).
O altă utilizare şi construcţie interesantă este crearea unui tablou de pointeri la funcţii.
Pentru a selecta o funcţie nu trebuie decât să se specifice indexul în cadrul tabloului şi să se
dereferenţieze pointerul; aceasta permite conceptul de cod selectat din tablou, în loc să se utilizeze
declaraţii condiţionale sau de selecţie, se selectează funcţia de executat pe baza stării unei variabile.
Metoda este foarte utilă dacă se adaugă şi şterg, des, funcţii din tablou, sau dacă se doreşte crearea
şi modificarea unui astfel de tablou dinamic.
Următorul exemplu creează nişte funcţii utilizând un macro preprocesor, apoi creează un
tablou de pointeri către aceste funcţii, utilizând o iniţializare automată.

// tab_func.cpp
// Utilizarea unui tablou de pointeri la functii
#include <iostream.h>
#include <conio.h>

// macro pentru definirea de functii "N":


#define DF(N) void N( ) { \
cout << "\tFunctia \""#N"\" a fost apelata . . .\n" << endl;}

// definirea a 3 functii utilizand macro-ul precedent:


DF ( alfa ); DF ( beta ); DF ( gama );

// definirea tabloului de pointeri la cele 4 functii:


void ( * func_tablou [] ) ( ) = { alfa, beta, gama };

int main () {clrscr();


while ( 1 )
{cout << "\nApasati o tasta de la '1' la '3' , sau 'q' pt. iesire"
<< endl;
char car;
cin >> car ;

8
if ( car == 'q' )
break; // se termina ciclul 'while'
if ( car < '1' || car > '3' )
continue;
( *func_tablou [ car - '1' ] ) ( );
}
}

6.3. Domeniul de valabilitate (sau durata de viaţă) a unei variabile

Regulile care stabilesc domeniul de valabilitate (sau durata de viaţă, cum mai este denumit)
a unei variabile specifică unde variabila este validă, adică poate fi referită, unde este creată şi unde
este distrusă. Domeniul de valabilitate (sau vizibilitate) a unei variabile se extinde din punctul unde
a fost definită şi până la prima acoladă închisă, corespunzătoare primei acolade deschise înainte de
definirea variabilei. Deci, domeniul de valabilitate este definit de “cel mai apropiat” set (pereche) de
acolade, ca în exemplul următor:
//domviata.cpp
// cum se defineste domeniul de valabilitate al unei variabile
int main ()
{
int var1;
// este vizibila variabila: ‘var1’
{
// este vizibila ‘var1’
...
int var2;
// vizibila ‘var2’, si bineinteles si ‘var1’
...
{
// vizibile: ‘var1’ & ‘var2’
int var3;
// vizibile: ‘var1’, ‘var2’ si ‘var3’
}
// vizibile: ‘var1’ si ‘var2’; nu mai este vizibila ‘var3’
...
}
// vizibila doar ‘var1’; nu mai sunt vizibile: ‘var2’ si ‘var3’
};
// a fost distrusa si ‘var1’

Variabilele pot fi utilizate doar pe durata lor de vizibilitate.


Există o diferenţă semnificativă între C şi C++, la definirea variabilelor. Şi anume, în C,
utilizatorul este forţat să definească toate variabilele la începutul unui bloc (domeniu de
valabilitate), astfel încât compilatorul, la crearea blocului, va aloca spaţiu pentru acele variabile.
Acest mod de definire a variabilelor presupune cunoaşterea tuturor variabilelor ce vor fi folosite în
program, altfel ori de câte ori se utilizează o nouă variabilă se va reveni la partea declarativă pentru
definirea ei, ceea ce poate fi o sursă de erori.
În schimb, C++ permite definirea variabilelor oriunde în cadrul blocului, deci se poate defini
o variabilă chiar înainte de utilizarea ei. În plus, variabila poate fi şi iniţializată în momentul
definirii ei, ceea ce elimină o anumită clasă de erori; ele pot fi definite şi în cadrul expresiilor de
9
control (în partea de iniţializare) ale ciclurilor for şi while, în interiorul unei declaraţii condiţionale
if şi în interiorul unui selector switch, ca în secvenţa de program următoare:
// defi_var.cpp
// definire variabile in momentul utilizarii lor
#include <iostream.h>
#include <conio.h>
int main ()
{ clrscr();
// . . .
{ // incepe un nou bloc (domeniu de valabilitate)
int a = 0;
// . . .
for ( int i = 0 ; i < 100 ; i++ )
{
a++; // utilizarea variabilei definita in exteriorul acestui bloc
int b = 10; // definire variabila locala acestui bloc
// . . .
}
int b = 1; // variabila cu acelasi nume, ca mai sus, dar diferita de aceea
} // sfarsitul blocului ce-l continea pe 'a'
cout << "Introduceti un sir de caractere ce se termina cu '#':" << endl;
char c;
cin >> c;
while ( c != '#')
{
cout << c <<". ";
char car = c;
if ( car >= '0' && car <= '9' )
cout << "Ati tastat o cifra!" << endl;
else
cout << "Ati tastat: " << car << endl;
cin >> c;
}
cout << "Tastati una din cifrele: 0, 1, sau 2: ";
int i = 3;
cin >> i;
switch ( i )
{
case 0 : cout << "Ati tastat 0!" << endl; break;
case 1 : cout << "Ati tastat 1!" << endl; break;
case 2 : cout << "Ati tastat 2!" << endl; break;
default : cout << "Nu ati tastat una din aceste cifre!" << endl;
}
getch();
};

Definind variabila ce controlează un ciclu, în cazul prezentat for, chiar în cadrul ciclului,
aceeaşi variabilă poate fi refolosită pentru a controla un nou ciclu; i este un nume clasic pentru a
controla un ciclu, iar în acest mod de a o defini, nu mai este necesară inventarea de noi nume de
variabile care să controleze alte cicluri.

10
7. Trecerea de la C la C++

Trei proprietăţi principale caracterizează un limbaj de programare orientat pe obiecte:


 Încapsularea: combină o structură de date cu funcţii (acţiuni sau metode) dedicate
pentru a manipula datele: încapsularea este realizată prin intermediul unui nou mecanism de
structurare şi de tipuri de date – clasa (class);
 Moştenirea: permite construirea de noi clase derivate, prin moştenirea datelor şi
funcţiilor de la una sau mai multe clase de bază, definite anterior, fiind posibilă redefinirea
sau adăugarea de noi date şi acţiuni (funcţii). Aceasta creează o ierarhie de clase.
 Polimorfismul: dând unei acţiuni un nume sau un simbol care este partajat în sus şi în
jos o ierarhie de clase, cu fiecare clasă din ierarhie implementează acţiunea într-un mod
propriu fiecăreia.

C++ oferă întreaga putere a programării orientate pe obiecte:


 mai mult control asupra structurii şi modularităţii programului:
 capacitatea de a crea noi tipuri de date cu operatorii lor proprii specializaţi;
 instrumente pentru a ajuta la crearea de cod reutilizabil.

7.1. Încapsularea

În C-ul tradiţional soluţia uzuală era să se pună structurile de date şi funcţiile într-un singur
fişier sursă compilat separat, într-o încercare de a trata codul şi datele ca un singur modul. Nu există
o relaţie explicită între date şi cod, şi oricine poate accesa datele direct fără a utiliza funcţiile
furnizate. Aceasta poate conduce la anumite probleme. De exemplu, dacă pentru a reprezenta nişte
date s-a ales un tablou, dar ulterior se schimbă reprezentarea acestora cu o listă înlănţuită. Un alt
programator lucrând la acelaşi proiect poate decide să scrie alte funcţii pentru un acces mai bun la
date, care manipulează direct tabloul, dar problema este ca acel tablou nu mai există. Iată un
exemplu clasic de a descrie datele şi funcţiile în C pentru o structură de tip "Punct".

// "pctstruc.cpp" - definirea clasica, ca structura, a unui punct si a


// functiilor asociate acestei structuri (initializare, deplasare).
// Transmiterea parametrilor la functii se face prin referinta.

#include <iostream.h>
#include <conio.h>

class Punct {
public:
int x, y;
};
void initX (Punct& a, int x0) { a.x = x0;}
void initY (Punct& a, int y0) { a.y = y0;}
void deplasare (Punct& a, int dx, int dy) {
a.x += dx; a.y += dy;
}
void afisare (const Punct& a) {
cout << "[" << a.x << "," << a.y << "]\n";
}
11
void sageata() { cout << "->";}
void main (void) {
Punct a;
clrscr();
initX(a,4); initY(a,4);
cout << "\nPozitia punctului \"a\" dupa initializare este:\n\t";
afisare(a);
deplasare(a,-4,-3);
cout << "\nDupa deplasamentul (-4,-3) punctul \"a\" devine:\n\t";
sageata();
afisare(a);
}

În C++ o singură entitate, clasa, definită cu struct, union, sau class, combină funcţiile
(denumite şi funcţii membre) şi date (cunoscute ca date membre). Unei clase i se dă un nume util pe
care îl poţi folosi pentru a declara instanţe sau obiecte de tipul clasei. Iată aceeaşi problemă
transpusă în C++.
// "pctclass.cpp" - definirea unei clase "Punct" si a functiilor asociate,
// fara a utiliza un constructor, dar utilizand functii de initializare

#include <iostream.h>
#include <conio.h>

class Punct {
int x, y;
public:
void initX (int x0) { x = x0;}

void initY (int y0) { y = y0;}

void deplasare (int dx, int dy)


{ x += dx; y += dy;}

void afisare ()
{ cout << "[" << x << "," << y << "]";}

void sageata()
{ cout << "->";}

};

void main (void) {


Punct a;
clrscr();
a.initX(4); a.initY(4);
cout << "Dupa declarare si apelul functiiilor de init. pct. \"a\":\n\t";
a.afisare();
a.deplasare(-4,-4);
cout << "\nDupa deplasarea cu (-4,4) punctul \"a\" devine:\n\t";
a.sageata();
a.afisare();

12
};

Diferenţa majoră între clasele din C++ şi structurile din C constă în accesibilitatea
membrilor. Dacă membrii unei structuri C sunt liber disponibili pentru orice expresie sau funcţie din
domeniul lor. În C++ se poate controla accesul la membrii unei structuri sau clase (date sau cod)
prin declararea de membrii individuali ca fiind publici, privaţi sau protejaţi (public, private sau
protected).
Structurile şi uniunile din C++ oferă mai mult decât cele din C: ele pot conţine declaraţii şi
definiţii de funcţii precum şi date membre. În C++ cuvintele cheie struct, union şi class pot fi
utilizate pentru a defini clase.
 O clasă definită cu struct este simplu o clasă în care toţi membrii sunt implicit public
(dar se poate modifica acest aranjament, dacă se doreşte).
 O clasă definită cu union are toţi membrii public (acest nivel de acces nu poate fi
modificat).
 Într-o clasă definită cu class membrii sunt privaţi, private, implicit (dar există moduri
de modificare a nivelelor de acces).
De obicei, se poate restricţiona accesul datelor membre la funcţiile membre: datele membre se fac
private şi funcţiile membre se fac public.
Prin crearea unei clase se poate asigura că datele de tip private pot fi accesate şi manipulate
numai prin funcţiile membre publice ale clasei, ce au fost create pentru acest scop. Acum se poate
schimba oricând structura datelor de la un tablou, de exemplu, la o listă înlănţuită, sau la alt tip. Va
trebui, desigur, să se rescrie funcţiile membre pentru a manipula noua structură de date, dar dacă
numele şi argumentele funcţiilor sunt neschimbate, programe din altă parte a sistemului nu vor fi
afectate de aceste îmbunătăţiri.
În figura următoare se prezintă, comparativ, modurile în care C şi C++ furnizează accesul la
o structură:

struct data class


{ { datele
.... ...
} ...
// codul care operează
// asupra datelor // funcţiile membre funcţiile

{ constructor(. . .)
init(. . .); get(. . .)
get(. . .); sort(. . .)
sort(. . .); print(. . .)
print(. . .);
...
} }

Conceptul de clasă conduce la idea abstractizării datelor. În acelaşi timp concepţia clasică,
din C care vede un program ca o colecţie de funcţii cu datele ca nişte componente de rangul al
doilea, este modificată. Clasele din C++ văd în mod egal datele şi funcţiile, ca parteneri
interdependenţi.

7.2. Supradefinirea (supraîncărcarea)

În C putem avea doar o funcţie cu un anumit nume, chiar dacă valoarea returnată de funcţie
este de alt tip decât cel al funcţiei deja declarate. Spre deosebire, în C++ se pot suprapune
13
(supradefini) funcţii, ceea ce înseamnă că putem avea mai multe funcţii cu acelaşi nume, dar care
lucrează cu tipuri de date diferite; de exemplu:
int calcul (int numar);
float calcul (float float_numar);
double calcul (double double_numar);
Cât timp argumentele sunt diferite, C++ are grijă să apeleze funcţia corespunzătoare pentru
argumentele date. De exemplu, pentru următoarele apeluri, vor fi apelate următoarele funcţii:
calcul (10); // varianta ‘int’
calcul(2.5); // varianta ‘double’
calcul(2.5F); // varianta ‘float’
De asemenea, C++ permite suprapunerea şi redefinirea (denumită şi supraîncărcarea)
operatorilor (cum ar fi +, *, etc.) astfel încât ei să nu lucreze numai cu numere, ci şi cu obiecte
grafice, şiruri sau ceea ce este adecvat pentru o clasă.

7.3. Funcţiile membre

Să adăugăm o funcţie simplă clasei Punct: GetX. Funcţia membru poate fi adăugată la o
clasă în două moduri:
- fie se defineşte funcţia în interiorul clasei;
- fie se declară în interiorul clasei, iar apoi este definită în afara clasei;
Cu prima metodă funcţia se declară astfel:
class Punct {
public:
int X;
int Y;
int GetX () { return X; } // definire functie membru inline
};

Definirea funcţiei membre inline (adică inserarea codului funcţiei chiar acolo, în cadrul
clasei) urmează sintaxa obişnuită din C, pentru definirea funcţiilor.
Cu cea de-a doua metodă funcţia se declară astfel:
class Punct {
public:
int X;
int Y;
};
int Punct :: GetX() {
return X; // definire functie membru in afara clasei
};
Şi în acest caz definirea se poate face inline, dar aici trebuie folosit explicit cuvântul cheie
inline. De asemenea, trebuie folosit operatorul de rezoluţie (::) la definirea funcţiei Punct :: GetX,
deoarece trebuie informat compilatorul C++, cărei clase îi aparţine funcţia GetX. De asemenea,
operatorul precizează că şi corpul funcţiei este în domeniul clasei, şi deci X-ul returnat de funcţie
este X-ul membru al clasei Punct. Pentru definirea în interiorul clasei nu este nevoie de această
precizare, întrucât funcţia CetX aparţine, evident, clasei Punct.
Pentru a apela o funcţie membră a unei clase trebuie specificat obiectul asupra căruia
operează, astfel:
numele_obiectului_clasei . nume_functie_membru (lista_de _argumente);

14
Operatorul “.” realizează selecţia componentei clasei, atât pentru datele membre cât şi
pentru funcţiile membre. La fel se poate utiliza operatorul de selecţie indirectă, dacă referirea la un
obiect al unei clase se face printr-un ponter:
ptr_Punct -> GetX();

7.4. Funcţii inline

O funcţie membră a unei clase poate fi declarată în clasa respectivă şi definită oriunde (în
interiorul sau exteriorul clasei). Dacă funcţia este declarată şi definită în interiorul clasei ea este
numită funcţie inline.
Uneori compilatorul poate reduce (simplifica) apelul unei funcţii prin substituirea apelului
funcţiei, direct cu codul compilat al corpului funcţiei. Acest proces, denumit expandare în linie
(inline) a blocului funcţiei, nu afectează rezoluţia (scope) numelui funcţiei sau a argumentelor sale.
Expandarea inline nu este întotdeauna posibilă. Specificatorul inline este o necesitate (sau indicaţie)
pentru compilator, pentru o expandare inline.
Pentru funcţii relativ mici, frecvent utilizate, inline este implicit sau explicit.; aşa se
întâmplă cu funcţiile operator care implementează supraîncărcarea operatorilor.
De exemplu următoarea declaraţie de clasă:
int i;
class X {
public:
char *fct (void) {return i;} // este implicit "inline"
char *i; };
este echivelentă cu:
inline char * X : fct (void) { return i;}
unde fct() este definită în exteriorul clasei.

7.5. Constructori şi destructori

Sunt două tipuri speciale de funcţii membre: constructori şi destructori, care joacă un rol
cheie în C++.
Iniţializarea datelor se poate face, de exemplu, astfel:
a.initX(4); a.initY(4);
Dar acest mod este strâns legat de un anumit obiect şi la fel ar trebui făcut pentru fiecare
obiect. O altă modalitate, mai generală, este de a construi o funcţie care să iniţializeze astfel de
obiecte, transmise ca parametru:
void InitPunct ( Punct * ptr_Punct, int noul_X, int noul_Y) {
ptr_Punct -> X = noulX;
ptr_Punct -> Y = noulY;
Aceasta este o rezolvare, dar nu cea mai bună întrucât trebuie specificat ca parametru tipul
clasei şi obiectul respectiv de iniţializat, deoarece această funcţie de iniţializare nu este funcţie
membră a clasei respective.
Din aceste motive este necesar un constructor, deci o funcţie membră de un tip special, şi
care specifică cum un nou obiect al clasei va fi creat, prin alocare de memorie şi iniţializare.
Definiţia sa poate include cod pentru alocarea de memorie, asignarea de valori la membrii, conversii
de la un tip la altul şi orice altceva ce poate fi util. Constructorii pot fi definiţi de utilizator sau C++
îi poate genera implicit. Constructorii pot fi apelaţi explicit sau implicit. Compilatorul C++ apelează
automat constructorul corespunzător oricând se defineşte un nou obiect al unei clase. Aceasta se

15
poate realiza în declararea datelor, când se copiază un obiect, sau prin alocarea dinamică a unui nou
obiect utilizând operatorul new.
Destructorii, aşa cum sugerează şi numele, distrug obiectele clasei anterior create de un
constructor, prin ştergerea valorilor şi eliberarea memoriei. Ca şi în cazul constructorilor,
destructorii pot fi apelaţi explicit, utilizând operatorul delete, sau implicit, când un obiect iese din
domeniul său de valabilitate. Daca nu se defineşte un destructor pentru o clasă, C++ generează o
versiune implicită.
În modul următor se adaugă un constructor clasei Punct:
class Punct {
int X;
int Y;
int GetX () { return X };
Punct ( int noulX, int noulY); // declararea constructorului
};
Punct :: Punct ( int noulX, int noulY) // definire constructor
{
X = noulX;
Y = noulY;
Vizibil = true;
};
Aici constructorul este definit în afara definiţiei clasei, dar poate fi definit în interiorul
clasei, ca funcţii inline. De reţinut că numele constructorului este identic cu cel al clasei, Punct, în
acest exemplu. Constructorul poate avea argumente şi poate apela orice funcţii şi date membre, ale
clasei sale. Un constructor nu are niciodată un tip returnat, nici măcar void. Acum se poate declara
un nou obiect Punct astfel:
Punct Origine ( 0, 0);
Pot fi definiţi mai mulţi constructori pentru o clasă, şi ca în cazul funcţiilor suprapuse,
versiunea adecvată va fi invocată, în mod automat, în concordanţă cu lista de parametrii implicaţi.
Dacă nu vă definiţi propriul constructor, C++ va genera un constructor implicit, fără argumente.
La definirea constructorului pot fi asociate valori implicite pentru argumentele funcţiei:
Punct :: Punct ( int noulX =0 , int noulY =0 ) // definire constructor cu valori initiale
iar declaraţia:
Punct Origine(1);
va iniţializa X cu 1, iar Y cu 0, implicit.

7.6. Controlul accesului la membri: private, public şi protected

Accesul la datele şi funcţiile membre ale unei structuri este public, implicit, adică orice
declaraţie în interiorul domeniului de viaţă poate citi sau modifica datele unei structuri ale unei
clase. Acest lucru nu este de dorit în majoritatea cazurilor, ci pentru a evita unele probleme se
preferă ascunderea datelor sau a informaţiei, făcând datele membre private sau protejate, şi
furnizând o interfaţă autorizată pentru accesul la ele. Regula generală este de a face toate datele
private astfel ca ele să poată fi accesate numai prin funcţii membre publice. Sunt destul de rare
situaţiile când sunt necesare date membre publice, în locul celor private sau protejate. După cum
unele funcţii membre implicate numai în operaţii interne pot fi făcute mai degrabă private sau
protejate, decât publice.
Există trei cuvinte cheie care specifică tipul de control al accesului la membrii unei structuri sau
clase:
private – membrii ce urmează după acest tip pot fi accesaţi doar de funcţiile membre
declarate doar în aceeaşi clasă;

16
protected - membrii ce urmează după acest tip pot fi accesaţi de funcţiile membre declarate
doar în acceaşi clasă şi de funcţiile membre ale claselor ce sunt derivate din această clasă;
public - membrii ce urmează după acest tip pot fi accesaţi de oriunde din domeniul de
definiţie al clasei.
Pentru exemplul anterior putem defini datele membre ca private, iar funcţiile membre publice:
class Punct {
private:
int X;
int Y;
public:
int GetX () { return X };
Punct ( int noulX, int noulY);
};
Deoarece o clasă este implicit publică, astfel că trebuie utilizat private pentru a specifica
partea privată, şi apoi public pentru partea de acces general.
class Punct {
int X; // implicit sunt ‘private’
int Y;
public:
int GetX (); // specificat accesul ‘public’
Punct ( int noulX, int noulY);
};
Deci modificatorul private nu este necesar pentru datele membre, ele sunt implicit private.
Funcţiile membre trebuie declarate public pentru a fi utilizate în afara clasei pentru a iniţializa şi a
da valori obiectelor. Se poate repeta specificarea controlului accesului cât de des este necesar:
class Angajat {
float salariu;
char ocupatie[20];
public:
char nume[30];
char depart[20];
private:
int verific_eroare (void);
public:
Angajat( float salariu, char * ocupatie, char * depart);
};

Deci, în acest exemplu, avem datele membre salariu, ocupatie sunt implicit private; nume şi
depart sunt declarate publice; funcţia membru verific_eroare este declarată privată, deci va fi
folosită pentru uz intern, iar constructorul Angajat este declarat public.
Iată, în continuare, exemplul complet utilizând clasa Punct, care defineşte un constructor
pentru un punct din plan şi-l afişează pe ecran, înainte şi după "deplasarea" punctului:

// "pctclasc.cpp" - definirea clasei "Punct" si a constructorului asociat


#include <iostream.h>
#include <conio.h>
class Punct {
int x, y;
public:
Punct (int a, int b) {
x = a; y = b;
}; // constructorul
17
void deplasare (int, int);
void afisare();
};
void Punct :: deplasare (int dx, int dy) {
x += dx; y += dy;
}
void Punct :: afisare () {
cout << "[x= " << x << " , y= " << y << "]" << endl;
}
// fisierul main.cpp
void main () {
Punct a(1,1), b(5,5);
clrscr();
cout << "\nPunctele definite si initializate de constructor:\n\t";
a.afisare();
cout << "\t"; b.afisare();
a.deplasare(-1,-1);
cout << "\nDupa un deplasament (-1,-1), punctul \"a\" devine:\n";
cout << "\t\t-> ";
a.afisare();
b.deplasare(-3,2);
cout << "\nDupa un deplasament (-3,0), punctul \"b\" devine:\n";
cout << "\t\t-> ";
b.afisare();
}

7.7. Moştenirea

Pentru a înţelege mai bine conceptul de moştenire în programarea orientată pe obiecte


(POO) vom considera un exemplu demonstrativ luat din natură. Ramurile descriptive ale ştiinţei au
nevoie de mult timp pentru clasificarea obiectelor în concordanţă cu anumite tratate. Ea ajută adesea
la organizarea clasificării noastre ca un arbore genealogic cu o singură categorie globală la rădăcină,
şi cu ramuri (subcategorii) ce ies unele din altele, şi aşa mai departe.
De exemplu, insectele sunt clasificate ca în figura următoare: în două categorii, cu aripi şi
fără aripi; în prima categorie sunt, de asemenea, un mare număr de subcategorii: molii, fluturi,
muşte etc. Acest proces de clasificare este denumit taxonomie. Întrebarea pe care ne. Întrebările pe
care ni le punem în încercarea de a clasifica unele animale sau obiecte noi sunt următoarele:
Cât sunt de similare cu altele din aceeaşi clasă generală?
Cât sunt de diferite?
insecte

cu aripi fără aripi

18
molii fluturi muşte
Fiecare clasă diferită are un set de caracteristici şi comportamente care o definesc. Se începe
de la vârful arborelui genealogic şi începem să coborâm pe ramuri, punând acele întrebări pe acest
drum. Nivelurile cele mai înalte sunt cele mai generale, iar întrebările sunt cele mai simple: Cu aripi
sau fără aripi? Fiecare nivel este mult mai specific decât cel anterior, şi mai puţin general.
Odată ce o caracteristică este definită, toate categoriile aparţinând acelei definiţii includ acea
caracteristică. Astfel, odată ce s-a identificat o insectă ca aparţinând ca un membru al ordinului
diptere (muşte), nu mai este nevoie să se marcheze că o muscă are o pereche de aripi. Speciile
muşte moştenesc acea caracteristică de la ordinul lor.
POO este procesul de construire de ierarhii de clase, iar un mecanism important adus de C+
+ este acela prin care tipurile de clase pot moşteni caracteristici de la tipuri mai generale. Acest
mecanism este denumit moştenire, şi aceasta furnizează funcţiile comune, permiţând în acelaşi timp
specializarea lor, cât este necesar. Dacă o clasă D moşteneşte o clasă B, se spune că D este clasa
derivată şi B este clasa de bază.
Nu este o sarcină uşoară de a stabili ierarhia de clase ideală pentru o anumită aplicaţie.
Înainte de a scrie o linie de program C++ trebuie bine gândit asupra claselor ce sunt necesare la acel
nivel. Pe măsură ce aplicaţia se dezvoltă, veţi constata că sunt necesare alte noi clase care alterează
fundamental întreaga ierarhie de clase. Taxonomia insectelor a fost dezvoltată în sute de ani şi tot
este subiect de schimbări şi dezbateri.
O clasă poate combina proprietăţile mai multor clase stabilite anterior. Versiuni C++ oferă
un astfel de mecanism, denumit moştenire multiplă, care pentru o clasă derivată permite moştenirea
de la mai multe clase de bază.
Clasele care moştenesc de la clasele de bază sunt denumite clase derivate. Punctelor definite
anterior le pot fi asociate şi alte atribute, în contextul grafic în care sunt utilizate, cum ar fi
vizibilitatea, culoarea etc. Pentru a construi o clasă derivată şi pentru a exemplifica moştenirea, vom
construi clasa Punct ca o clasă derivată dintr-o clasă de bază Pozitie, care conţine coordonatele X,
Y ale punctului. Clasa Punct va moşteni totul de la clasa Pozitie şi îi adaugă alte informaţii.
Cele două clase înrudite, precum şi funcţiile membre, sunt definite, în fişierul puncth.h,
astfel:
/* puncth.h - fisier antet descriere clase, pt. exemplificare mostenire;
sunt definite doua clase:
- Pozitie, descrie coordonatele X si Y, si este mostenita de
- Punct, care descrie daca un Punct este vizibil sau ascuns */

#include <iostream.h>
enum Boolean {false, true}; // valori logice codificate: false = 0, true = 1.
class Pozitie {
protected: // permite clasei derivate acces la date private
int X; // o "Pozitie" in plan este declarata prin coordonate
int Y;
public:
Pozitie ( int X0, int Y0); // constructorul clasei "Pozitie"
int GetX(); // furnizeaza "x"-ul pozitiei curente
int GetY(); // furnizeaza "y"-ul pozitiei curente
};

19
class Punct : public Pozitie { // clasa derivata din clasa Pozitie
// 'public' derivat inseamna ca X si Y sunt protejate in Punct
protected:
Boolean Vizibil; // clasele derivate vor avea nevoie de acces
public:
Punct ( int X0, int Y0); // constructor
void Afisare(); // afisare "Punct" pe ecran
void Ascunde(); // sterge "Punct" pe ecran
Boolean EsteVizibil(); // furnizeaza "starea" punctului
void MutaLa ( int noulX, int noulY); // muta punctul la o noua poz.
};
// functiile membre pentru clasa Pozitie
Pozitie :: Pozitie (int X0, int Y0) {
X = X0;
Y = Y0;
};
int Pozitie :: GetX (void) {
return X;
};

int Pozitie :: GetY (void) {


return Y;
};
// functiile membre pentru clasa Punct:

Punct :: Punct ( int X0, int Y0) : Pozitie (X0, Y0) {


Vizibil = false; // se mostenesc constructorii din Punct si Pozitie
};
void Punct :: Afisare (void) {
Vizibil = true;
putpixel ( X, Y, getcolor()); // utilizeaza culoarea implicita
};

void Punct :: Ascunde (void) {


Vizibil = false; // "stegerea" se face prin afisarea unui pixel ce
putpixel ( X, Y, getbkcolor()); // utilizeaza culoarea background
};
Boolean Punct :: EsteVizibil (void) {
return Vizibil; // "starea" punctului: vizibil sau nu
};
void Punct :: MutaLa (int noulX, int noulY) {
Ascunde(); // se face punctul curent invizibil
X = noulX; // schimba coordonatele X si Y la noua pozitie
Y = noulY;
Afisare(); // afiseaza punctul la noua locatie
};

Procesul acesta, de a construi clase derivate dintr-o clasă de bază, poate continua. O mare
parte a procesului de proiectare a aplicaţiilor constă în construirea acestei ierarhii de clase şi
exprimarea arborelui de clase în aplicaţii.
Datele declarate în cele două clase sunt protected şi deci sunt accesibile acestora, dar nu
altora. Reamintim că la declararea unei clase cu class accesul implicit este private, în timp ce
20
declararea cu struct are accesul implicit public, dacă nu este specificat în mod explicit controlul
accesului printr-un modificator de acces:
class B : modificator_acces A { // A este clasa de bază
......... // B este clasa derivată
}

Modificatorul de acces este utilizat pentru a modifica accesibilitatea implicită a membrilor


moşteniţi, după cum se poate vedea în următoarea tabelă:

accesul în clasa de bază modificatorul de acces accesul moştenit


public public public
private public nu este accesibil
protected public protected
public private private
private private nu este accesibil
protected private private

Într-o clasă derivată accesul la elementele clasei de bază poate fi făcut mai restrictiv, dar
niciodată nu poate fi mai puţin restrictiv (adică cu drepturi de acces mai mari). Astfel pentru
membrii moşteniţi avem control asupra moştenirii nivelului lor de acces prin utilizarea adecvată a
modific0atorilor de acces. Nivelul de acces al membrului unei clase de bază, cum este el văzut de
clasa de bază, poate să nu fie acelaşi cu nivelul de acces cu cel văzut de clasa sa derivată.
O clasă poate fi derivată în context privat sau public din clasa ei de bază.
După cum se poate vedea şi din tabelă derivarea public a unei clase lasă nivelul de acces
nemodificat. Derivarea în context private converteşte membrii cu acces public şi protected din clasa
de bază în membrii de tip private în clasa derivată; membrii private vor rămâne tot de tip private.
O clasă derivată moşteneşte toţi membrii clasei sale de bază, dar poate folosi numai membrii
protected şi public ai clasei sale de bază. Membrii private ai clasei de bază nu sunt direct disponibili
pentru membrii clasei derivate.
Se recomandă ca întotdeauna să se specifice public sau private, oricare, chiar dacă este
implicit, pentru a evita confuziile. În dezvoltarea unui program, de obicei, declaraţiile pentru
fiecare clasă sau grup de clase înrudite se pun într-un fişier antet separat, iar definiţiile pentru
funcţiile membre ce nu sunt inline se pun într-un fişier sursă separat. Această modularizare are
avantajele ei: se pot distribui clasele în forma obiect către alţi programatori. Ei pot deriva clase
specializate noi din cele disponibile, fără a fi nevoie de codul lor sursă.
Se poate dezvolta acum un “modul” compilat separat conţinând clasele Pozitie şi Punct. În
fişierul puncth.h primele sunt puse declaraţiile pentru cele două clase. Declararea clasei Punct
derivată din Pozitie se face astfel:
classs Punct : public Pozitie { . . . . .
Cuvântul cheie public este necesar pentru a asigura că funcţiile membre ale clasei derivate,
Punct, pot accesa membrii protejaţi X şi Y din clasa de bază, Pozitie. Pe lângă membrii poziţiei X şi
Y, Punct moşteneşte funcţiile membre GetX() şi GetY() din Pozitie. Clasa Punct adaugă data
membru protejată Vizibil, de tipul enumerare Boolean, şi cinci funcţii membre publice, incluzând
constructorul Punct :: Punct.
Fişierul puncth.h conţine şi definiţiile pentru toate funcţiile membre ale celor două clase, dar
ele puteau fi definite şi separat în alt fişier, care să fie "legat" cu fişierul care apelează funcţiile
respective.
Acest exemplu introduce importantul concept al constructorilor clasei de bază. Când este
definit un obiect Punct, vrem să utilizăm faptul că deja clasa sa de bază, Pozitie, are definit propriul
său constructor. Definiţia constructorului Punct :: Punct( int X0, int Y0) începe cu două puncte şi o
21
referinţă la constructorul de bază Pozitie (X0, Y0). Aceasta specifică, mai întâi, că şi constructorul
Punct va apela constructorul Pozitie cu argumentele X0 şi Y0 şi deci crearea şi iniţializarea datelor
membre X şi Y. Apoi este invocat corpul constructorului Punct care creează şi iniţializează data
membru Vizibil. Specificând explicit un constructor de bază se salvează cod, în exemple mai mari
salvarea este mult mai semnificativă. De fapt, constructorii claselor derivate, întotdeauna, apelează,
mai întâi, un constructor al clasei de bază, pentru a asigura că datele membre moştenite sunt corect
create şi iniţializate. Dacă clasa de bază este şi ea derivată, procesul de apelare a constructorilor de
bază va continua în jos, mai departe, ierarhic.
Dacă nu se defineşte un constructor pentru o clasă X, atunci C++ va genera un constructor
implicit de forma X :: X(); adică un constructor fără argumente. Dacă constructorul clasei derivate
nu invocă explicit unul dintre constructorii săi de bază, sau nu s-a definit un constructor de bază,
atunci va fi invocat constructorul implicit al clasei de bază.
Iată, în continuare, programul principal pentru a demonstra posibilităţile claselor Punct şi
Pozitie.
// pixel.cpp - demonstreaza posibilitatile claselor Punct si Pozitie

#include <graphics.h> // biblioteca grafica


#include <conio.h> // functia getch()
#include "puncth.h" // declararea claselor Punct si Pozitie

int main () {
// initializare sistem grafic
int graphdriver = DETECT, graphmode;
initgraph(&graphdriver, &graphmode, "");

// mutarea unui punct de-a lungul ecranului


Punct PunctA(100,50); // punctul initial de coordonate (100,50)
PunctA.Afisare(); // facut vizibil
getch(); //asteapta apasarea unei taste
PunctA.MutaLa(300,150); // punctul este mutat la pozitia (300,150)
getch();
PunctA.Ascunde(); // este 'ascuns' punctul
getch();
closegraph(); // revenire in mod text
return 0;
}

7.7.1. Extinderea claselor

Avantajul utilizării claselor este dat de modul în care noi obiecte pot fi realizate şi li se poate
da funcţionalitatea corespunzătoare. Utilizând clasele deja definite, Pozitie şi Punct, să derivăm o
nouă clasă, Cerc, împreună cu funcţiile care afişează, ascund, măresc, mută sau micşorează cercuri.

// cerc.cpp - definirea clasei "Cerc" derivata din clasa Punct


// Se definesc si functii de operare cu obiecte din noua clasa:
// Afisare(), Ascunde(), Marire(), Micsorare(), MutaLa();
// trecerea de la un cerc la altul, mai mare sau mai mic
// se face prin apasarea unei taste oarecare
#include <graphics.h>
#include "puncth.h"
22
#include <conio.h>
class Cerc : Punct { // derivate obiectele private din clasa Punct
int Raza; // implicit private

public:
Cerc ( int X0, int Y0, int Razainit );
void Afisare ();
void Ascunde ();
void Marire ( int MarireCu );
void Micsorare ( int MicsorareCu );
void MutaLa ( int noulX, int noulY );
};
Cerc :: Cerc ( int X0, int Y0, int Razainit ) :
Punct ( X0, Y0 )
{ // constructorul "Cerc" mosteneste constructorul "Punct"
Raza = Razainit;
};
void Cerc :: Afisare ( void )
{ // functie afisare (desenare) cerc cu centrul (X, Y) si Raza
Vizibil = true; // Indicator ce specifica "starea" punctelor cercului
circle ( X, Y, Raza );
}
void Cerc :: Ascunde ( void )
{ // se "ascunde" cercul trasand unul identic cu culoarea fundalului
unsigned int TempCuloare; // pentru a salva culoarea curenta
TempCuloare = getcolor();
setcolor (getbkcolor()); // se pune culoarea la valoarea fundalului
Vizibil = false;
circle ( X, Y, Raza); // cerc de culoare fundal, pentru a-l sterge
setcolor ( TempCuloare ); // se reface culoarea salvata
};
void Cerc :: Marire ( int MarireCu )
{ // "marirea" unui cerc la unul de o raza mai mare
Ascunde(); // se "sterge" cercul anterior
Raza += MarireCu; // se mareste raza cu vloarea "MarireCu"
if (Raza < 0) // evitare raza negativa
Raza = 0;
Afisare(); // se traseaza cercul cu noua raza
};
void Cerc :: Micsorare ( int MicsorareCu)
{ // "micsorarea" unui cerc la unul de raza mai mica
Marire ( -MicsorareCu ); // "Marire" raza cerc cu "-MicsorareCu"
};
void Cerc :: MutaLa (int noulX, int noulY)
{ // se muta un cerc de la (X,Y) la un nou centru (noulX,noulY)
Ascunde(); // sterge vechiul cerc
X = noulX; // noul centru al cercului
Y = noulY;
Afisare(); // deseneaza cerc de aceeasi raza la noua locatie
};
void main (void)
{
23
// initializare sistem grafic
int graphicdriver = DETECT, graphmode;
initgraph ( &graphicdriver, &graphmode, "" );
Cerc CercEx ( 100, 200, 50 ); // definire obiect CercEx (exemplu cerc)
CercEx.Afisare(); // il deseneaza
getch();
CercEx.MutaLa ( 350, 250 ); // muta cercul la noile coordonate
getch();
CercEx.Marire ( 100 ); // se mareste raza cercului cu 100 pixeli
getch();
CercEx.Micsorare ( 25 ); // se micsoreaza raza cercului ce 25 pixeli
getch();
closegraph();
};

Funcţiile membre din Cerc au nevoie de acces la diferite date membre din clasele Cerc,
Punct şi Pozitie. De exemplu, întrucât Raza este definită ca private în Cerc, ea este accesibilă pentru
Cerc::Marire(), ea este accesibilă numai funcţiilor membre din Cerc.
Să examinăm acum funcţia membru Cerc :: Ascunde(). Aceasta trebuie să acceadă data
Vizibil din clasa sa de bază Punct. Ea este protejată în Punct, iar Cerc este derivată, implicit privat,
din Punct. Astfel conform regulilor menţionate anterior, Vizibil este privată în Cerc, şi accesibilă la
fel ca şi Raza. Dacă, însă, ar fi fost definită ca privată în Punct atunci nu ar fi fost accesibilă
funcţiilor membre din Cerc. Astfel poate aţi fi tentaţi să faceţi public, dar atunci Vizibil ar fi devenit
accesibilă şi funcţiilor nemembre. Funcţiile membre ale unei clase derivate pot accesa un membru
protejat, fără a-l expune unui abuz public.
Să considerăm şi cazul Cerc :: Afisare(), care are nevoie de acces la membrii X şi Y din
Pozitie, pentru a desena cercul. Cerc nu este derivată direct din Pozitie şi deci drepturile de acces nu
sunt evidente imediat. Cerc este derivată din Punct, care este derivat din Pozitie. Să urmărim
declaraţiile de acces:
- membrii X şi Y sunt declaraţi protected în Pozitie;
- Punct este derivat public din Pozitie, astfel că Punct moşteneşte membrii X şi Y ca
protected;
- Cerc este derivat din Punct utilizând derivarea implicită private;
- deci Cerc va moşteni membrii X şi Y ca private. Cerc :: Afisare() poate accesa X şi Y, care
rămân încă protected în Pozitie.
Pentru a compila şi edita legăturile pentru cerc.cpp, puncth.h şi graphics.h trebuie specificate
aceste fişiere sursă în declaraţiile preprocesor, sau dacă se definesc în mai multe module fişierele
utilizate trebuie specificate în fişierul proiect .prj.

7.8. Moştenirea multiplă

Pentru a exemplifica moştenirea multiplă, adică o clasă care moşteneşte de la mai mult de o
clasă de bază, vom extinde exemplul anterior pentru a afişa un text în cadrul cercului.
O primă idee ar fi aceea de a adăuga un membru data de tip şir de caractere (string), la clasa
Cerc, şi apoi de a adăuga codul la Cerc::Afisare() astfel încât acesta să afişeze textul cu cercul
desenat în jurul său. Dar cercul şi textul sunt lucruri total diferite. Se poate deriva o nouă clasă
direct din clasa Cerc şi să-i dăm proprietăţi de text. Dar când avem de-a face cu funcţionalităţi total
diferite, este mai bine să creăm noi clase de bază “fundamentale”, şi apoi să derivăm clasele ce
combină caracteristicile corespunzătoare.
Pentru aceasta vom defini o nouă clasă, denumită MesajG, care afişează un şir de caractere
(string) pe ecran, în modul grafic, începând din punctul de coordonate X, Y. Această clasă va fi
24
celălalt părinte pentru clasa CercM; clasa CercM va moşteni MesajG :: Afisare() şi o va folosi
pentru a desena textul. Relaţiile dintre toate clasele implicate vor fi prezentate în figura următoare.

25
class Pozitie : {
int X;
int Y;
...
}

class Punct : Pozitie { class MesajG: Pozitie{


int Vizibil; char mes;
... int Font;
} int Camp
}

class Cerc : Punct {


int Raza;
...
}

class CercM : Cerc, MesajG {


...
}

// cercmm.cpp - exemplifica mostenirea multipla: se defineste o noua clasa


// CercM , care se obtine prin lantul de derivari succesive:
// Pozitie -> Punct -> Cerc
// Pozitie -> Punct -> MesajG
// ( Cerc , MesajG ) -> CercM
// si care este un cerc cu un mesaj, afisat in interiorul sau

#include <graphics.h>
#include "puncth.h"
#include <conio.h>
#include <string.h>

class Cerc : public Punct { // clasa Cerc, derivata din clasa Punct,
// iar aceasta a fost derivata din clasa Pozitie
protected :
int Raza;
public:
Cerc (int X0, int Y0, int Razainit); // constructorul
26
void Afisare (void); // desenarea cercului
};

class MesajG : public Pozitie { // afiseaza un mesaj in modul grafic


char * mes; // mesajul de afisat
int Font; // fontul BGI utilizat pentru afisarea mesajului
int Camp; // dimensiunea campului in care e afisat textul
public:
MesajG (int mesX, int mesY, int mesFont, int CampDimens,
char * text); // Initializare mesaj
void Afisare (void); // afiseaza mesajul
};

class CercM : Cerc, MesajG { // mosteneste de la ambele clase


public:
CercM (int cercmX, int cercmY, int cercmRaza, int Font, char * mes);
void Afisare (void); // afiseaza cercul cu mesajul din interior
};

// Functiile membre pentru clasa Cerc

Cerc :: Cerc (int X0, int Y0, int Razainit): // Constructor Cerc
Punct (X0, Y0) // initializare membrii mosteniti
// invoca, de asemenea, constructorul Pozitie
{
Raza = Razainit;
};

void Cerc :: Afisare (void)


{ // deseneaza cercul de centru (X,Y) si Raza
Vizibil = true;
circle (X, Y, Raza); // deseneaza cercul
}

// Functiile membre pentru clasa MesajG

// constructorul MesajG
MesajG :: MesajG (int mesX, int mesY, int mesFont, int CampDimens,
char * text ): Pozitie ( mesX, mesY)
{ // coordonatele pentru centrarea textului
Font = mesFont; // fonturile standard definite in graphics.h
Camp = CampDimens; // dimensiunea campului in care se pune textul
mes = text; //pointer la mesaj
};

void MesajG :: Afisare (void)


{ // afisarea mesajului, in modul grafic
int dimens = Camp / (8 * strlen (mes)); // cate 8 pixeli de caracter
settextjustify (CENTER_TEXT, CENTER_TEXT); // centrare in cerc
settextstyle (Font, HORIZ_DIR, dimens); // daca dimens > 1 se mareste
outtextxy (X, Y, mes); // textul si se afiseaza textul
}
27
// Functiile membre pentru clasa CercM
// Constructorul CercM
CercM :: CercM ( int cercmX, int cercmY, int cercmRaza, int Font, char * mes ):
Cerc ( cercmX, cercmY, cercmRaza ),
MesajG ( cercmX, cercmY, Font, 2*cercmRaza, mes) { }

void CercM :: Afisare (void)


{ // desenare cerc si afisarea mesajului in interiorul cercului
Cerc :: Afisare ();
MesajG :: Afisare();
}

void main (void) {


int graphdriver = DETECT, graphmode;
initgraph (&graphdriver, &graphmode, "");
CercM Mic (250, 100, 25, SANS_SERIF_FONT, "Tu");
Mic.Afisare();
CercM Mediu (250, 150, 100, TRIPLEX_FONT, "Lumea");
Mediu.Afisare();
CercM Mare (250, 250, 225, GOTHIC_FONT, "Universul");
Mare.Afisare();
getch();
closegraph();
}

În corpul definiţiei CercM :: Afisare() sunt două apeluri de funcţii Cerc :: Afisare (); şi
respectiv MesajG :: Afisare();. Această sintaxă prezintă o altă utilizare obişnuită a operatorului ::
(operatorul domeniului de definiţie). Când se doreşte apelarea unei funcţii moştenite, cum ar fi
Afisare(), compilatorul are nevoie de informaţii care să-i spună care Afisare() este necesară. Fără
utilizarea “suprapunerii” domeniului de definiţie, Afisare() ar putea referi Afisare() din domeniul
curent, şi anume Cerc::Afisare(). Pentru a apela Afisare() din alt domeniu, presupunând că este
permis accesul, trebuie furnizat numele clasei respective urmat de :: şi numele funcţiei. Dacă se
doreşte apelarea unei funcţii ce nu este membru, numită tot Afisare(), va trebui utilizat ::Afisare()
fără a fi precedat de nume de clasă.
O funcţie membru de un nume dat în clasa derivată se va “suprapune” peste funcţia membru
de acelaşi nume din clasa de bază, dar se poate lua cea din urmă utilizând ::.
Deoarece CercM moşteneşte atât de la CercM cât şi de la MesajG, constructorul CercM
poate iniţializa convenabil prin apelul ambilor constructori de bază:
CercM :: CercM ( int cercmX, int cercmY, int cercmRaza, int Font, char * mes ):
Cerc ( cercmX, cercmY, cercmRaza ),
MesajG ( cercmX, cercmY, Font, 2*cercmRaza, mes) { }

Corpul constructorului este gol deoarece totul este realizat în lista de iniţializare membru;
după : se introduce o listă de expresii de iniţializare separate prin virgule; s-a mai întâlnit o versiune
mai simplă a acestei sintaxe în constructorii clasei de bază, utilizaţi în definirea claselor Punct şi
Cerc. Când se invocă constructorul CercM, prin declararea obiectului CercM, se realizează o serie
de activităţi. Prima, este apelat constructorul Cerc. Acesta apelează, apoi, constructorul Punct, care
la rândul său apelează constructorul Pozitie. În final, este apelat constructorul MesajG, care
apelează constructorul Pozitie din propria sa copie pentru X şi Y ai clasei sale de bază. Argumentele
date constructorului CercM sunt transmise pentru a iniţializa membrii date corespunzători ai
claselor de bază.
Când sunt apelaţi destructorii (când un obiect iese din domeniul de valabilitate, de exemplu),
28
secvenţa de eliberare a memoriei (dealocare) este inversa celei utilizate pe durata construcţiei.
Ieşirea programului anterior, cercmm.cpp, este următoarea:

Tu

Lumea

Universul

7.9. Funcţii virtuale

Fiecare tip de clasă în ierarhia anterioară reprezintă un tip diferit de figură pe ecran: un punct sau un
cerc. În mod asemănător pentru a defini şi alte clase pentru reprezentarea altor figuri: linii, pătrate,
arce etc. se pot scrie funcţii membre pentru fiecare obiect pe care vrem să-l desenăm pe ecran. În
filozofia orientată obiect se poate spune că toate aceste figuri grafice au capacitatea să se afişeze ele
singure pe ecran.
Ce este diferit pentru fiecare tip de obiect este modul în care el trebuie să se prezinte el însuşi pe
ecran. Un punct este desenat cu o rutină care are nevoie doar de poziţia X, Y şi, eventual valoarea
culorii. Un cerc are nevoie de o rutină complexă pentru a se afişa, luând în consideraţie nu numai X
şi Y dar şi raza. Mergând mai departe, un arc are nevoie de un unghi de start şi unul de sfârşit, şi un
algoritm diferit de desenare. Aceeaşi situaţie este şi pentru ascunderea, deplasarea şi manipularea
altor forme de bază.
Funcţiile membre obişnuite, după cum s-a văzut, permit definirea unei funcţii Afisare() pentru
fiecare clasă a unei forme. Dar, modulele grafice bazate pe clasele şi funcţiile membre existente vor
necesita modificări ale codului sursă şi recompilări de fiecare dată când s-a introdus o nouă clasă
pentru o formă cu propria sa funcţie membru Afisare(). Motivul este că mecanismele C++ permit, în
esenţă, doar trei moduri de a rezolva aceastăproblemă: care Afisare() va fi referită ?:
1. Distincţia realizată prin semnătura argumentelor; Afisare(int, char) nu este aceeaşi
funcţie cu Afisare(char*, float), de exemplu.
2. Utilizarea operatorului domeniului de valabilitate, adică Cerc :: Afisare() este
distinctă de Punct :: Afisare() şi de :: Afisare().
3. Domeniul de valabilitate al obiectului clasei: CercM.Afisare() invocă Cerc ::
Afisare(), în timp ce PunctP.Afisare() invocă Punct :: Afisare(). La fel este şi în cazul
pointerilor la obiecte: PunctP -> Afisare() invocă Punct :: Afisare().
Toate aceste funcţii de domeniu de valabilitate, au fost făcute la momentul compilării, un
mecanism care este referit ca editare timpurie sau statică a legăturilor.
O bară de instrumente grafice tipică ar trebui să furnizeze utilizatorului definiţiile claselor în
fişiere sursă împreună cu codul precompilat .OBJ şi .LIB pentru funcţiile membre. Cu restricţiile
legării timpurii, utilizatorul nu poate adăuga uşor noi forme de clase, şi chiar dezvoltatorul are unele
probleme în extinderea pachetului. C++ oferă un mecanism pentru rezolvarea acestei probleme:
29
editarea întârziată sau dinamică a legăturilor prin intermediul unor funcţii membre speciale,
denumite funcţii virtuale.
Conceptul constă în aceea că apelurile funcţiei virtuale sunt rezolvate la momentul execuţiei,
de unde şi termenul de editarea întârziată sau dinamică a legăturilor. Practic, aceasta înseamnă că
decizia care dintre funcţiile Afisare() este apelată poate fi amânată până tipul obiectului invocat este
cunoscut în timpul execuţiei. O funcţie virtuală Afisare(), “ascunsă” într-o clasă B în biblioteca
precompilată a barei de instrumente, nu este legată la obiectele lui B în modul în care sunt legate
funcţiile membre obişnuite ale lui B. Se poate crea o clasă D, derivată din B pentru forma proprie
dorită, şi se scriu funcţiile corespunzătoare (punând propria Afisare, aşa cum era). Se compilează şi
leagă, apoi, codul OBJ şi LIB respectiv, la cel al barei de instrumente. Apeluri făcute la Afisare(), fie
din funcţiile membre existente ale lui B, fie din funcţii noi care au fost scrise pentru D, vor referi
automat corect Afisare(). Această rezoluţie (domeniu de valabilitate) este realizată în întregime pe
tipul de obiect invocat de apel.
Să considerăm funcţia membru Cerc :: MutaLa din cerc.cpp:
void Cerc :: MutaLa (int noulX, int noulY)
{ // se muta un cerc de la (X,Y) la un nou centru (noulX,noulY)
Ascunde(); // sterge vechiul cerc
X = noulX; // noul centru al cercului
Y = noulY;
Afisare(); // deseneaza cerc de aceeasi raza la noua locatie
};
Această definiţie este similară cu Punct :: MutaLa() aflată în Cerc din clasa de bază Punct.
De fapt, valorea returnată, numele funcţiei, numărul şi tipul argumentelor formale (cunoscute ca
semnătura funcţiei), şi chiar însăşi corpul funcţiei, apar a fi identice. Dacă C++ întâlneşte două
apeluri de funcţii utilizând acelaşi nume de funcţie dar cu semnături diferite, el este destul de
deştept pentru a rezolva posibilele ambiguităţi determinate de suprapunerea numelor funcţiilor. În
C++ funcţiile membre cu semnături diferite sunt funcţii diferite, chiar dacă împart acelaşi nume.
Dar, cele două MutaLa() nu oferă, la prima vedere, nici o deosebire pentru compilator, astfel
ca acesta să poată determina care din cele două se intenţionează să se apeleze. Răspunsul, după cum
s-a văzut, cu funcţii membre obişnuite, este că compilatorul determină funcţia ţintă din tipul clasei
pentru obiectul implicat de apel.
Astfel, de ce nu moşteneşte Cerc pe MutaLa() din Punct, întocmai cum Cerc moşteneşte
GetX() şi GetY() din Punct (via Pozitie)? Motivul, desigur, este că Ascunde() şi Afisare() apelate în
Cerc::MutaLa() nu sunt aceleaşi cu Ascunde() şi Afisare() apelate în Punct::MutaLa(). Numai
numele şi semnăturile sunt aceleaşi. Moştenirea MutaLa() de la Punct ar duce la apelarea greşită
Ascunde() şi Afisare(), când se încearcă mutarea unui cerc, deoarece versiunile din Punct ale acestor
două funcţii ar fi limitate la MutaLa() din Punct (şi de aici la fel pentru Cerc) la momentul
compilării (editarea timpurie a legăturilor). Răspunsul, după cum se poate deduce, este să se declare
Ascunde() şi Afisare() ca funcţii virtuale. Aceasta va întârzia editarea legăturilor, astfel ca versiunile
corecte ale lui Ascunde() şi Afisare() să fie invocate când este apelată efectiv MutaLa(), pentru a
muta un punct, un cerc sau orice altceva.
Trebuie subliniat că dacă dorim să precompilăm definiţiile claselor noastre şi funcţiile membre
pentru Pozitie, Punct şi Cerc într-o bibliotecă simplă, de sine stătătoare, cu sursa implementată
împreună cu alte secrete, nu putem şti sigur, dinainte, obiectele care i se vor cere lui MutaLa() să le
mute. Funcţiile virtuale nu numai că furnizează acest avantaj tehnic, dar ele furnizează şi un avantaj
conceptual care constituie nucleul POO. Ne putem concentra pe dezvoltarea claselor şi metodelor
reutilizabile cu mai puţină anxietate asupra conflictului claselor. Utilizarea funcţiilor virtuale şi a
moştenirii multiple în C++ face extensibilitatea mult mai naturală. Se poate moşteni tot ce au
clasele, şi apoi se pot adăuga noi facilităţi necesare pentru a face noile obiecte să lucreze într-un
mod obişnuit. Clasele pe care le definim şi versiunile lor ale funcţiilor virtuale devin o adevărată
extensie a unei ierarhii ordonate de facilităţi.

30
Sintaxa pentru definirea unei funcţii virtuale este simplă: se adaugă calificativul virtual la prima
declarare a funcţiei membru:

virtual void Ascunde ();

virtual void Afisare )();


Doar funcţiile membre pot fi declarate virtuale. O funcţie odată declarată virtual nu trebuie
să fie redeclarată în orice clasă derivată cu aceeaşi semnătură de argumente formale, ci cu un tip de
returnare diferit.
Dacă se redeclară Afisare() cu aceeaşi semnătură de argumente formale şi acelaşi tip
returnat, noua Afisare() devine automat virtuală, fie că se utilizează sau nu atributul virtual. Această
nouă funcţie virtuală Afisare() se spune că se suprapune pe Afisare() din clasa sa de bază. Se poate
redeclara Afisare() cu o semnătură de argumente formale diferită (fie că se schimbă sau nu tipul
returnat) dar mecanismul virtual este inoperabil pentru această versiune Afisare(). Funcţia Afisare()
corespunzătoare care este apelată va depinde doar de clasa obiectului pentru care Afisare() este
invocată, chiar dacă apelul este invocat via un pointer (sau referinţă) pentru clasa de bază. De
exemplu:
Cerc CercN;
Punct * ptr_PunctP = &CercN; // pointer la Cerc asignat la pointer la clasa de bază, Punct
ptr_PunctP -> Afisare(); // se apeleză Cerc::Afisare;
se pot reconstrui fişierele anterioare, redefinind funcţiile virtuale; punctv.h şi cercv.cpp sunt
versiunile pentru puncth.h şi cerc.cpp cu Afisare() şi Ascunde() făcute virtuale. Se compilează
cercv.cpp cu punctv.h. El va rula exact ca cerc.cpp. În program este utilizat şi un pointer pentru a
referi fie un punct, fie un cerc, după cum este iniţializat, şi pentru apela adecvat funcţiile respective.
Pointerul este definit de tipul Punct pentru a putea referi atât obiecte din clasa de bază (Punct) cât şi
din clasa derivată (Cerc). Diferenţele între versiunile anterioare şi cea virtuală pot fi totalizate după
cum urmează:
- în punctv.h funcţiile Afisare() şi Ascunde() din Punct au fost declarate virtual.
Funcţiile Afisare() şi Ascunde() derivate din clasa Cerc în cercv.cpp au aceeaşi semnătură
a argumentelor şi valori returnate ca versiunea de bază din Punct; aceasta implică că ele
sunt, de asemenea, virtuale, chiar dacă cuvântul virtual nu este utilizat în declaraţiile lor:
- în cercv.cpp, Cerc nu mai are funcţia membru proprie MutaLa();
- acum, derivăm public Cerc din Punct pentru a permite accesul la MutaLa().
Deci, în concluzie, semnificaţia acestor modificări este următoarea: obiectele Cerc pot apela
MutaLa() moştenită din Punct. Afisare() şi Ascunde() apelate de MutaLa() vor fi editate (legate) la
momentul execuţiei la Afisare() şi Ascunde() proprii clasei Cerc. Obiectele de tip Punct ce apelează
MutaLa() vor invoca versiunile din Punct.
Fişierul punctv.h este prezentat în continuare.

/* punctv.h - fisier antet descriere clase, pt. exemplificare mostenire si


functii virtuale ("Afisare" si "Ascunde").
Sunt definite datele si metodele pentru doua clase:
- Pozitie, descrie coordonatele X si Y, si este mostenita de
- Punct, care descrie daca un Punct este vizibil sau nu */

#include <iostream.h>
enum Boolean {false, true}; // valori logice codificate: false = 0, true = 1.
class Pozitie {
protected: // permite clasei derivate acces la date private
int X; // o "Pozitie" in plan este declarata prin coordonate
int Y;
public:
31
Pozitie ( int X0, int Y0); // constructorul clasei "Pozitie"
int GetX(); // furnizeaza "x"-ul pozitiei curente
int GetY(); // furnizeaza "y"-ul pozitiei curente
};

class Punct : public Pozitie { // clasa derivata din clasa Pozitie


// 'public' derivat inseamna ca X si Y sunt protejate in Punct
protected:
Boolean Vizibil; // clasele derivate vor avea nevoie de acces
public:
Punct ( int X0, int Y0); // constructor
virtual void Afisare(); // afisare "Punct" pe ecran
virtual void Ascunde(); // sterge "Punct" pe ecran
Boolean EsteVizibil(); // furnizeaza "starea" punctului
void MutaLa ( int noulX, int noulY); // muta punctul la o noua poz.
};
// functiile membre pentru clasa Pozitie
Pozitie :: Pozitie (int X0, int Y0) {
X = X0;
Y = Y0;
};
int Pozitie :: GetX (void) {
return X;
};
int Pozitie :: GetY (void) {
return Y;
};
// functiile membre pentru clasa Punct:
Punct :: Punct ( int X0, int Y0) : Pozitie (X0, Y0) {
Vizibil = false; // se mostenesc constructorii din Punct si Pozitie
};
void Punct :: Afisare (void) {
Vizibil = true;
putpixel ( X, Y, getcolor()); // utilizeaza culoarea implicita
};
void Punct :: Ascunde (void) {
Vizibil = false; // "stegerea" se face prin afisarea unui pixel ce
putpixel ( X, Y, getbkcolor()); // utilizeaza culoarea background
};
Boolean Punct :: EsteVizibil (void) {
return Vizibil; // "starea" punctului: vizibil sau nu
};
void Punct :: MutaLa (int noulX, int noulY) {
Ascunde(); // se face punctul curent invizibil
X = noulX; // schimba coordonatele X si Y la noua pozitie
Y = noulY;
Afisare(); // afiseaza punctul la noua locatie
};

Fişierul cercv.cpp conţine clasa Cerc şi funcţia main() care exemplifică utilizarea funcţiilor
virtuale.

32
// cercv.cpp - derivare clasa "Cerc" din "Punct" si definire fctii virtuale.
// Se redefinesc functii din clasa Punct pentru obiecte din noua clasa:
// Afisare(), Ascunde(); si se definesc functiile: Marire(), Micsorare();
// Functia MutaLa() din clasa "Punct" va fi mostenita in clasa "Cerc",
// iar la apelul functiilor membre Afisare() si Ascunde(), din MutaLa()
// vor fi "legate" functiile (cu aceeasi semnatura) redefinite in "Cerc".
// Trecerea de la un cerc la altul, mai mare sau mai mic, sau de la un punct
// la altul se face prin apasarea unei taste oarecare
#include <graphics.h>
#include "punctv.h"
#include <conio.h>

class Cerc : public Punct { // derivate obiectele private din clasa Punct
int Raza; // implicit private
public:
Cerc ( int X0, int Y0, int Razainit );
void Afisare (); // aceste doua functii sunt redefinite
void Ascunde (); // iar functia "MutaLa()" este mostenita de "Cerc"
void Marire ( int MarireCu );
void Micsorare ( int MicsorareCu );
};
Cerc :: Cerc ( int X0, int Y0, int Razainit ) :
Punct ( X0, Y0 )
{ // constructorul "Cerc" mosteneste constructorul "Punct"
Raza = Razainit;
};
void Cerc :: Afisare ( void )
{ // functie afisare (desenare) cerc cu centrul (X, Y) si Raza
Vizibil = true; // Indicator ce specifica "starea" punctelor cercului
circle ( X, Y, Raza );
}
void Cerc :: Ascunde ( void )
{ // se "ascunde" cercul trasand unul identic cu culoarea fundalului
unsigned int TempCuloare; // pentru a salva culoarea curenta
TempCuloare = getcolor();
setcolor (getbkcolor()); // se pune culoarea la valoarea fundalului
Vizibil = false;
circle ( X, Y, Raza); // cerc de culoare fundal, pentru a-l sterge
setcolor ( TempCuloare ); // se reface culoarea salvata
};
void Cerc :: Marire ( int MarireCu )
{ // "marirea" unui cerc la unul de o raza mai mare
Ascunde(); // se "sterge" cercul anterior
Raza += MarireCu; // se mareste raza cu vloarea "MarireCu"
if (Raza < 0) // evitare raza negativa
Raza = 0;
Afisare(); // se traseaza cercul cu noua raza
};
void Cerc :: Micsorare ( int MicsorareCu)
{ // "micsorarea" unui cerc la unul de raza mai mica
Marire ( -MicsorareCu ); // "Marire" raza cerc cu "-MicsorareCu"
};
33
void main (void)
{
// initializare sistem grafic
int graphicdriver = DETECT, graphmode;
initgraph ( &graphicdriver, &graphmode, "" );
Punct *ptr; // pointer utilizat pentru a referi atat Punct cat si Cerc
Cerc CercEx ( 100, 200, 50 ); // definire obiect CercEx (exemplu cerc)
ptr = &CercEx;
ptr->Afisare(); // "afisare" cerc
getch();
CercEx.MutaLa ( 350, 250 ); // muta cercul la noile coordonate folosind
getch();// fctia MutaLa() din "Punct", care apeleaza Ascunde() si Afiseaza()
// din clasa "Cerc"
ptr->MutaLa ( 250, 200 ); // muta cercul utilizand pointer
getch();
CercEx.Marire ( 100 ); // se mareste raza cercului cu 100 pixeli
getch();
CercEx.Micsorare ( 25 ); // se micsoreaza raza cercului ce 25 pixeli
getch();
ptr->Ascunde(); // "ascunde" cercul
getch();
Punct PunctEx ( 200, 200 );
ptr = &PunctEx;
ptr->Afisare(); // "afisare" punct
getch();
ptr->MutaLa ( 300, 300 ); // muta punctul utilizand pointer, prin apelul
// functiei MutaLa() din "Punct", care foloseste metodele
// Ascunde() si Afiseaza() din clasa "Punct"
getch();
PunctEx.MutaLa( 250, 250 );
getch();
closegraph();
};

Vom exemplifica în continuare aceste posibilităţi pentru o nouă clasă Linie, care trasează un contur
rectangular între două puncte din plan, mai întâi pe direcţia x (abscisă, adică pe orizontală), iar apoi
pe y (ordonată, adică pe verticală). Vom prezenta acest exemplu în trei variante pentru a înţelege
toate aceste concepte şi utilizarea lor.
În prima variantă, clasa Linie, derivată din clasele: Pozitie -> Punct -> Linie, moşteneşte din clasa
Pozitie coordonatele X, Y şi defineşte noul constructor al clasei Linie() pe baza constructorului
clasei Punct. De asemenea sunt definite trei noi funcţii proprii clasei Linie: Trasare(), Sterge() şi
MutaLinie(), fără a utiliza funcţiile moştenite din clasa Punct.

/* linie.h - fisier antet descriere clase, pentru exemplificare mostenire;


sunt definite trei clase:
- Pozitie, descrie coordonatele X si Y, si este mostenita de
- Punct, care descrie daca un Punct este vizibil sau ascuns
- Linie, care descrie si traseaza o linie rectangulara intre 2 puncte.
Derivarea claselor se face in ordinea: Pozitie -> Punct -> Linie.
Clasa "Linie" mosteneste "Pozitie" (X, Y) si definste constructorul Linie() pe

34
baza constructorului din clasa Punct().
Pentru noua clasa sunt definite functii noi: Trasare(), Sterge() si MutaLinie(),
fara utilizarea functiiilor din clasele mostenite.
*/
#include <iostream.h>
enum Boolean {false, true}; // valori logice codificate: false = 0, true = 1.
class Pozitie {
protected: // permite clasei derivate acces la date private
int X;
int Y;
public:
Pozitie (int X0, int Y0);
int GetX();
int GetY();
};
class Punct : public Pozitie { // clasa derivata din clasa Pozitie
// 'public' derivat inseamna ca X si Y sunt protejate in Punct
protected:
Boolean Vizibil; // clasele derivate vor avea nevoie de acces
public:
Punct (int X0, int Y0); // constructor
void Afisare (void); // afisare punct (pixel)
void Ascunde (void); // sterge punct
Boolean EsteVizibil (void); // returneaza "starea" punctului
void MutaLa (int noulX, int noulY); // muta punctul pe ecran
};
class Linie : public Punct { // clasa derivata din clasa Punct
// 'public' derivat inseamna ca X si Y sunt protejate in Linie
protected:
int U; // linia este definita de un punct initial (X,Y)
int V; // si punctul final (U,V)
Boolean Vizibila; // clasele derivate vor avea nevoie de acces
public:
Linie ( int X0, int Y0, int X1, int Y1); // constructor
void Trasare(); // deseneaza o linie rectangulara
void Sterge(); // sterge linia desenata
Boolean EsteVizibila(); // "starea" liniei
void MutaLinie (int noulX, int noulY); // muta linia intr-un nou pct.
};
// functiile membre pentru clasa Pozitie:
Pozitie :: Pozitie (int X0, int Y0) {
X = X0;
Y = Y0;
};
int Pozitie :: GetX (void) {
return X;
};
int Pozitie :: GetY (void) {
return Y;
};
// functiile membre pentru clasa Punct:
Punct :: Punct ( int X0, int Y0) : Pozitie (X0, Y0) { // constructorul
35
Vizibil = false; // mosteneste constructorul de la Pozitie
};
void Punct :: Afisare (void) {
Vizibil = true;
putpixel (X, Y, getcolor()); // utilizeaza culoarea implicita
};
void Punct :: Ascunde (void) {
Vizibil = false; // se sterge linia prin redesenare
putpixel (X, Y, getbkcolor()); // utilizand culoarea de background
};
Boolean Punct :: EsteVizibil (void) {
return Vizibil;
};
void Punct :: MutaLa (int noulX, int noulY) {
Ascunde(); // face punctul curent invizibil
X = noulX; // schimba coordonatele X si Y la noua pozitie
Y = noulY;
Afisare(); // afiseaza punctul la noua locatie
};
// functiile membre pentru clasa Linie:
Linie :: Linie ( int X0, int Y0, int X1, int Y1) : Punct (X0, Y0) {
U = X1; // constructorul mosteneste constructorul de la Punct
V = Y1;
Vizibila = false;
};
// definire functie de trasare linie
void Linie :: Trasare (void) {
Vizibila = true;
if ( X<U )
for (int i = X; i <= U; i++)
putpixel (i, Y, getcolor()); // culoarea implicita
else
for (i = X; i >= U; i--)
putpixel (i, Y, getcolor());

if ( Y<V )
for (i = Y; i <= V; i++)
putpixel (U, i, getcolor()); // culoarea implicita
else
for (i = Y; i >= V; i--)
putpixel (U, i, getcolor());
};
// definire functie de stergere linie
void Linie :: Sterge (void) {
Vizibila = false;
if ( X<U )
for (int i = X; i <= U; i++)
putpixel (i, Y, getbkcolor()); // sterge cu bkcolor
else
for (i = X; i >= U; i--)
putpixel (i, Y, getbkcolor());

36
if ( Y<V )
for (i = Y; i <= V; i++)
putpixel (U, i, getbkcolor()); // backgroundul sterge
else
for (i = Y; i >= V; i--)
putpixel (U, i, getbkcolor());

};
Boolean Linie :: EsteVizibila (void) {
return Vizibila;
};
void Linie :: MutaLinie (int noulX, int noulY) {
int deltaX = noulX-X;
int deltaY = noulY-Y;
Sterge(); // face linia curenta invizibila
X = noulX; // modifica coordonatele ce definesc linia
Y = noulY; // (X,Y) si (U,V) cu deplasamentul deltaX,
U = U + deltaX; // respectiv deltaY
V = V + deltaY;
Trasare(); // afiseaza linia pentru noile coordonate
};

Definirea funcţiei main() care utilizează clasele definite în fişierul antet "linie.h" pentru
trasarea, mutarea şi ştergerea unei linii.

// linie.cpp - demonstreaza utilizarea clasei Linie (<-Punct<-Pozitie)


#include <graphics.h> // biblioteca grafica
#include <conio.h> // functia getch()
#include "linie.h" // declararea claselor Punct si Pozitie
void main (void) {
// initializare sistem grafic
int graphdriver = DETECT, graphmode;
initgraph(&graphdriver, &graphmode, "");
// mutarea unei linii rectangulare pe ecran
Linie L1(300, 200, 100, 50); // linie initiala: (300,200)-(100,200)-(100,50)
L1.Trasare(); // facuta vizibila
getch(); //asteapta apasarea unei taste
L1.MutaLinie(450,320); // linia este mutata incepand de la noul X(450,320)
getch();
L1.Sterge(); // este 'ascunsa' linia
getch();
closegraph(); // revenire in mod text
}

37

S-ar putea să vă placă și