Sunteți pe pagina 1din 116

Programare orientată obiect

Bibliografie
 Liviu Negrescu - Limbajele C si C++ pentru începători, Volumul II - Limbajul C++, Editura Albastră,
Cluj-Napoca, 1995.
 Herbert Schildt - C++ manual complet, Bucureşti , Editura Teora, 2000
 Herbert Schildt - C Manual Complet, Bucureşti, Editura Teora, 1998
 Bjarne Stroustrup - The C++ Programming Language, Adisson-Wesley, 1997
 Robert Lafore - C++ Interactive Course, Macmillan Computer Publishing 1996

 Kris Jamsa & Lars Klander - Totul despre C si C++ - Manualul fundamental de programare in C si
C++ , Teora, 2007
Resurse electronice

 Bruce Eckel - Thinking in C++, 2nd ed., Volumele 1 si 2

- http://www.mindview.net

 Peter Müller - Introduction to Object-Oriented Programming Using C++

- http://gd.tuwien.ac.at/languages/c/c++oop-pmueller/

 Frank B. Brokken - C++ Annotations Version 10.1.0

- http://www.icce.rug.nl/documents/cplusplus/

 MSDN Library – Visual Studio 2010 - Visual C++

- http://msdn.microsoft.com/en-us/library/60k1461a%28v=vs.100%29.aspx
Paradigme de programare

Paradigma defineşte un anumit stil fundamental de programare.


Cele mai frecvent utilizate paradigme de programare sunt:

 programarea procedurală (imperativă) – are la bază utilizarea procedurilor (echivalent, a funcţiilor în C).
Accentul se pune pe execuţia unor acţiuni, importanţa datelor este subevaluată. Se presupune o separare fermă
între structurile de date şi codul funcţiilor care le prelucrează => o modificare a aplicaţiei atrage după sine atât o
reproiectare a structurii de date, cât şi o reimplementare a funcţiilor de prelucrare.

 programarea modulară – termenul de modul defineşte o colecţie de funcţii înrudite împreună cu datele pe care le
prelucrează şi care se compilează independent. In acest mod, aplicaţiile se rezolvă printr-un program alcătuit din
module. Un modul poate proteja o parte din datele şi funcţiile cu care operează, preîntâmpinând utilizarea
neautorizată / eronată a funcţiilor şi alterarea nedorită a datelor.

 programarea orientată obiect (POO) – reprezintă etapa actuală, modernă de proiectare a produselor software
complexe. Într-o primă aproximare, orientarea obiect înseamnă organizarea resurselor soft sub forma unor
colecţii de obiecte, ce înglobează atât structuri de date, cât şi funcţiile de prelucrare a acestora. O clasă (de
obiecte) grupează entităţi cu proprietăţi similare. Această similitudine se referă atât la descriere (date sau
atribute ale acestora), cât şi la comportare (funcţii), dar în acelaşi timp şi la relaţii posibile cu alte obiecte.
Diferenţele dintre obiectele aceleiaşi clase se materializează în diferenţele dintre valorile datelor de descriere.
POO se mai caracterizează şi prin aceea că datele şi funcţiile (obiectele) cu care se operează într-o aplicaţie au fost
supuse unui proces de clasificare, pe baza unor trăsături identificate ca fiind comune. Acest proces conduce în
mod natural la stabilirea de ierarhii. In vârful unei ierarhii se află entitatea care are trăsăturile comune pentru
toate celelalte componente ale ierarhiei respective (elementul din vârful ierarhiei se consideră a fi cel mai
general). Toate celelalte entităţi sunt particularizări/specializări, ele moştenind trăsăturile elementelor de pe
nivelele anterioare ale ierarhiei.
Figura 1.1.Variabile globale şi locale.

Figura 1.2. Paradigma procedurală.


Figura 1.3. Paradigma modulară Figura 1.4. Paradigma orientată obiect
Un tip de date descrie un set de entităţi ce au o aceeaşi reprezentare. Mai precis, caracterizarea unui tip de
date impune două aspecte : unul legat de modul efectiv de reprezentare a datelor, iar celălalt, de operaţiile ce se
pot executa asupra datelor respective.
Tipurile de date ce au drept componente atât date, cât şi funcţii care le prelucrează se numesc tipuri de
date abstracte. Ele sunt o generalizare a tipurilor de date definite de utilizator. Un tip de date abstract este, în
acest context, reprezentarea unui concept. O definiţie echivalentă : un tip de date abstract este un tip
definit de comportamentul/ proprietăţile sale şi nu de reprezentare.
Stilul de programare care permite definirea unei ierarhii între tipuri abstracte de date pe
baza conceptului de moştenire se numeşte POO.
Principiile programării orientate obiect sunt :
- existenţa tipurilor de date abstracte (abstract data types) – ce extind setul de tipuri pre-
definite(standard) ale limbajului;
- încapsularea (encapsulation) – posibilitatea de a grupa date şi funcţii înrudite (corelate) într-
o entitate autonomă, denumită clasă;
- ascunderea informaţiei (information hiding) – posibilitatea oferită de a restricţiona
accesul la anumite date şi funcţii (de obicei se ascund detaliile de implementare ce sunt mult mai probabil
supuse modificărilor ulterioare, rămânând accesibile doar componentele ce constituie interfaţa);
- moştenirea (inheritance) – conferă posibilitatea ca o clasă sa preia funcţionalităţile unei alte
clase, ceea ce permite reutilizarea codului;
- polimorfismul (polymorphism) – semnificaţia exactă a termenului înseamnă “forme
multiple”, în POO el referă abilitatea unui obiect de a răspunde într-o manieră specifică unui anumit mesaj.
Mai concis POO înseamnă deci :
- gruparea datelor şi funcţiilor
- separarea între interfaţă şi implementare
- definirea unor operaţii dependente de tipul operanzilor
- stabilirea unei ierarhii între entităţile cu care se operează
- reutilizarea codului
Aceasta se realizează prin :
- suport pentru tipurile abstracte de date
- trimiterea de mesaje în locul apelurilor de funcţii
- compilatoare care permit implementarea conceptului de moştenire
Actualmente există limbaje de programare care oferă suport pentru POO (C++, C#, Java, Pascal, Modula-2,
Smalltalk).
Introducere în C++

 Autorul limbajului: Bjarne Stroustrup


 Limbajul C++ este construit pe structura limbajului C, fiind un superset al acestuia ( o versiune dezvoltată)
 Motivaţie : pentru a beneficia de forţa şi flexibilitatea celui mai popular şi mai important limbaj de programare :
 flexibilitate - domenii de aplicabilitate foarte diverse, se pot utiliza o varietate extrem de largă de tehnici
de programare;
 eficacitate - apropierea de limbajele de nivel coborât oferă utilizatorului posibilitatea de a folosi la
maximum resursele hardware ale maşinii de calcul;
 disponibilitate - compilatoarele de C standard şi bibliotecile asociate sunt extrem de frecvente (de la
minicalculatoare la supercalculatoare)
 portabilitate - aproape întotdeauna asigurată.
 În concluzie crearea unui limbaj C mai bun permitea corijarea micilor imperfecţiuni, fără pierderea avantajelor
deja existente.
 Obiectivele proiectării limbajului C++
• să ofere suport pentru abstractizarea datelor - clase
• să ofere suport pentru programarea orientată obiect – moştenire (inheritance), polimorfism (funcţii
virtuale, supraîncărcarea operatorilor)
• să ofere suport pentru programarea generică – tipuri parametrizate (templates)
• să fie un C mai bun - “As close to C as possible, but no closer” : execuţie eficientă, compatibilitate cu
bibliotecile limbajului C

Scurt istoric
O primă descriere a “C-ului cu clase” a fost publicată ca raport la Bell Labs în aprilie 1980 de Bjarne Stroustrup.
Obiectivul a fost acela de a facilita organizarea programelor. Se introduc : conceptul de clasă, clasă derivată, controlul
accesului public şi privat, constructori şi destructori.
In 1982 a devenit evident că succesul C-ului cu clase e limitat şi s-a căutat un succesor. Astfel s-a născut C++.
Manualul de referinţa al limbajului s-a publicat în 1984. Prima reuniune a comitetului de standardizare ANSI
C++ a avut loc în decembrie 1989. Primul proiect de standard a fost propus în 1994, iar pe 28 IX 1998 a fost aprobat
limbajul C++ ca standard ANSI/ISO.
C++ Humor

Q: How many C++ programmers does it take to change a light bulb?


A: You’re still thinking procedurally. A properly designed light bulb object would inherit a change method from a
generic light bulb class, so all you would have to do is send a light-bulb-change message.

“Fifty years of programming language research, and we end up with C++ ???.”(Richard A. O’Keefe)

“C++: Hard to learn and built to stay that way.”

“The evolution of languages: FORTRAN is a non-typed language. C is a weakly typed language. Ada is a strongly
typed language. C++ is a strongly hyped (overvalued) language.” (Ron Sercely)

“C(++) is a write-only, high-level assembler language.” (Stefan Van Baelen)

“C++ would make a decent teaching language if we could teach the ++ part without the C part.” (Michael B. Feldman)

“The great thing about Object Oriented code is that it can make small, simple problems look like large, complex ones.”

“C++ in Cantonese is pronounced ‘C ga ga’. Need I say more?” (Mark Glewwe)

“When your hammer is C++, everything begins to look like a thumb.”


THE PROGRAMMER'S QUICK GUIDE TO THE LANGUAGES ( An old gem)

The proliferation of modern programming languages (all of which seem to have stolen countless features from one
another) sometimes makes it difficult to remember what language you're currently using. This handy reference is
offered as a public service to help programmers who find themselves in such a dilemma.
TASK: Shoot yourself in the foot.

C: You shoot yourself in the foot.

C++: You accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency
medical assistance is impossible since you can't tell which are bitwise copies and which are just pointing at others and
saying, "That's me, over there."

FORTRAN: You shoot yourself in each toe, iteratively, until you run out of toes, then you read in the next foot and
repeat. If you run out of bullets, you continue with the attempts to shoot yourself anyway because you have no
exception-handling capability.

Pascal: The compiler won't let you shoot yourself in the foot.

BASIC: Shoot yourself in the foot with a water pistol. On large systems, continue until entire lower body is
waterlogged.

Visual Basic: You'll really only _appear_ to have shot yourself in the foot, but you'll have had so much fun doing it
that you won't care.

Assembler: You try to shoot yourself in the foot, only to discover you must first invent the gun, the bullet, the
trigger, and your foot.

Modula2: After realizing that you can't actually accomplish anything in this language, you shoot yourself in the
head.
Quotes: "C makes it easy to shoot yourself in the foot. C++ makes it harder, but when you do, it blows away your
whole leg." - Bjarne Stroustrup
C în C++
C++ este o extensie a limbajului C ce oferă:
- o verificare mai eficientă a validităţii tipurilor de date (strong typechecking)
- suport pentru abstractizarea datelor (data abstraction)
- suport pentru programarea orientată obiect
- suport pentru tratarea eficientă a erorilor (exception handling)
- funcţii inline
- transferul prin referinţă al parametrilor
- valori implicite pentru parametrii funcţiilor
- tipul de date logice bool
- numele structurilor, claselor, uniunilor, enumerărilor ca nume de tip
- un mecanism de control al domeniilor numelor (namespace)
- reutilizarea codului (code reuse)

Figura 1.5 Relaţia dintre C şi C++


Tabelul 1.1 Tipuri de date predefinite/standard în C/ C++

Nume Folosit pentru a stoca Exemple de valori


char Caractere ‘a’, ‘B’, ‘$’, ‘3’, ‘?’
short numere întregi de valori mici 12, -1, 100
int numere întregi de valori uzuale 730, 0, -22200
long numere întregi de valori mari 1000000000, -123456789
float numere reale de valori mici 3.7, 199.99, -16.2, 0.000125
double numere reale de valori mari 7,553.393.95, 47, -0.048512934
long double* numere reale de valori foarte mari 779123456789.09012, -345.66678342681234
* neimplementat în Microsoft Visual C++

Tabelul 1.2 Tipuri de date întregi

Numele tipului Dimensiune Domeniu


char 1 byte (8 biţi) -128 până la 127
short 2 bytes (16 biţi) -32768 pana la 32767
coincide cu short la sistemele de 16 biţi
int
sau cu long pentru cele pe 32 biţi
long 4 bytes (32 biţi) -2147483648 pana la 2147483647

Tabelul 1.3 Tipuri întregi fără semn (unsigned integers)

Numele tipului Dimensiune Domeniu


unsigned char 1 byte (8 biţi) 0 până la 255
unsigned short 2 bytes (16 biţi) 0 pana la 65535
unsigned int or unsigned coincide cu short la sistemele de 16 biţi
sau cu long pentru cele pe 32 biţi
unsigned long 4 bytes (32 biţi) 0 pana la 4294967295
Tabelul 1.4. Tipuri de date reale (floating-point)

Numele tipului Dimensiune Domeniu pentru modul Precizie


float 4 bytes (32 biţi) [1.17*10-38, 3.4*10+38] 7 cifre
double 8 bytes (64 biţi) [2.2*10-308, 1.79*10+308] 15 cifre
long double 10 bytes (80 biţi) [3.4*10-4932, 1.1*10+4932] 19 cifre

Tipuri de date derivate :


- tipul pointer
- tipul tablou

Tipuri de date definite de utilizator :


- tipul structură
- tipul uniune
- tipul enumerare
Tabelul 1.5. Tipuri de date predefinite/standard în Visual C++ 2010

Tip Dimensiune Domeniu de valori


char 1 byte [-127 , 127] sau [0 , 255]
unsigned char 1 byte [0 , 255]
signed char 1 byte [-127 , 127]
int 4 bytes [-2147483648 , 2147483647]
unsigned int 4 bytes [0 , 4294967295]
signed int 4 bytes [-2147483648 , 2147483647]
short int 2 bytes [-32768 , 32767]
unsigned short int 2 bytes [0 , 65.535]
signed short int 2 bytes [-32768 , 32767]
long int 4 bytes [-2.147.483.647 , 2.147.483.647]
unsigned long int 4 bytes [0 , 4.294.967.295]
float 4 bytes [1.17*10-38, 3.4*10+38] (~7 cifre) - domeniu pentru modul
double 8 bytes [2.2*10-308, 1.79*10+308] (~15 cifre) - domeniu pentru modul
long double 8 bytes [2.2*10-308, 1.79*10+308] (~15 cifre) - domeniu pentru modul
wchar_t 2 sau 4 bytes caractere extinse
Tabelul 1.6. Tabelul operatorilor în C++, organizaţi pe categorii :

Operatori aditivi

Addition: + Subtraction: –

Atribuire

Addition Assignment: += Assignment: = Bitwise AND Assignment: &=


Bitwise exclusive OR Assignment: ^= Bitwise inclusive OR Assignment: |= Division Assignment: /=
Left shift assignment: <<= Modulus Assignment: %= Multiplication Assignment: *=
Right shift assignment: >>= Subtraction Assignment: –=

Operatori pe biţi

Bitwise AND: & Bitwise exclusive OR: ^ Bitwise inclusive OR: |

Operatori logici

Logical AND: && Logical OR: ||

Alţi operatori

Comma: , Conditional: ? : Pointer-to-member: .* or –>*


Reference: & Scope resolution: ::

Operatori multiplicativi

Division: / Modulus: % Multiplication: *


Operatori postfixaţi

Cast: () Function call: ( ) Member access: . and –>


Postfix decrement: –– Postfix increment: ++ Subscript: [ ]

Operatori de relaţie şi egalitate

Equality: == Greater than or equal to: >= Greater than: >


Less than or equal to: <= Less than: < Not equal: !=

Operatori de deplasare

Left shift: << Right shift: >>

Operatori unari

Address-of: & delete Indirection: *


Logical Negation: ! new One's Complement: ~
Prefix decrement: –– Prefix increment: ++ sizeof
Unary Plus Operator: + Unary Negation Operator: -
Tabelul 1.7. Tabelul principalilor operatori, în ordinea priorităţilor acestora :

::
( ) [ ] . ->
- + * & ! ~ ++ -- sizeof
* / %
+ -
<< >>
< <= >= >
== !=
&
^
|
&&
||
? :
= += -= *= /= %= <<= >>= &= ^= |=
,
Instrucţiuni :
- instrucţiunea vidă
- instrucţiunea compusă
- instrucţiunea expresie
- instrucţiunea if
- instrucţiunea switch
- instrucţiunea return
- instrucţiuni de salt :
- instrucţiunea continue
- instrucţiunea break
- instrucţiunea goto
- instrucţiuni ciclice :
- instrucţiunea while
- instrucţiunea for
- instrucţiunea do-while

Clase de alocare de memorie :


- auto
- static
- registru
- dinamică (în zona heap)

Domeniile numelor :
- local
- fişier
- global

Tipuri de legături (linkage) :


- intern
- extern
Tabel cu instrucţiunile limbajului C++
Elemente noi, specifice limbajului C++

1. Operatorul de rezoluţie ::
 Operatorul de rezoluţie :: permite :
- accesul la o dată globală redefinită local
- accesul la o entitate aparţinând unui spaţiu de nume, calificând-o cu numele spaţiului urmat de
operatorul de rezoluţie
- accesul la o dată/funcţie membru ascunsă, calificând-o cu numele clasei urmat de operatorul de
rezoluţie
 Operatorul :: are prioritate maximă

2. Spaţii de nume
 Un spaţiu de nume (namespace) permite crearea de noi domenii de vizibilitate (scope)
 Sintaxa declaraţie namespace este :
namespace nume {
listă_declaratii;
}
 Declaraţia namespace permite gruparea unor entităţi (clase, obiecte şi funcţii) sub un acelaşi nume. În
acest fel, domeniul global poate fi divizat în "sub-domenii", fiecare cu numele său, evitându-se astfel
posibile coliziuni.
 Semantica declaraţiilor într-un spaţiu de nume este similară declaraţiilor globale, exceptând faptul că
domeniul numelor este restricţionat la spaţiul astfel construit.
 Cuvântul rezervat using se foloseşte pentru a importa un spaţiu de nume sau părţi ale acestuia în
domeniul de vizibilitate curent.
 Toate fişierele din biblioteca standard C++ declară entităţile folosite în spaţiul de nume std . De aceea se
utilizează directiva
using namespace std;
în toate programele ce folosesc entităţi definite în iostream.
3. Sistemul I/O

 Sistemul de I/O din C asigură o interfaţă coerentă cu utilizatorul şi independentă faţă de echipamentul
folosit.
 Forma abstractă de organizare se numeşte stream, iar instrumentul efectiv utilizat, fişier.
 Un stream este un concept abstract, o entitate logică, ce produce sau primeşte informaţii, reprezentând
practic un termen general pentru orice flux de date
 În C++ suportul pentru sistemul de I/O este asigurat de o ierarhie de clase, ale căror declaraţii se găsesc in
fişierul antet iostream (în variantele mai vechi, iostream.h)
 Sistemul de I/O din C++ operează atât cu date de tipuri predefinite, cât şi cu date de tipuri abstracte
(obiecte)
 Atunci când începe execuţia unui program în C++, se deschid automat 4 streamuri incorporate

Stream Semnificaţie Echipament implicit


cin intrare standard tastatura
cout ieşire standard ecranul
cerr ieşire pentru erori ecranul
clog versiune cu memorie tampon pentru erori ecranul

 Operatorii >> şi << au un rol extins; pe lângă cel de deplasare pe biţi, ei sunt folosiţi ca operatori de I/O
- >> operator de intrare (extracţie din stream)
- << operator de ieşire (inserţie în stream)
 Operatorii de I/O se pot înlanţui şi pot fi supraîncărcaţi pentru tipurile de date abstracte

Observaţie : Anticipând, endl este un aşa numit manipulator ce se inserează ca o dată în stream şi semnifică
trecerea la o nouă linie
Exemplul 1.

#include <iostream>

using namespace std;

int main(void)
{
int a1,a2;
double b,c;
char d,e;
char *s="Hello World!\n";
cout<<s;
cout<<"Tastati doi intregi ";
cin>>a1>>a2;
cout<<"Tastati doua nr. reale ";
cin>>b>>c;
cout<<"Tastati doua caractere ";
cin>>d>>e;
cout<<"a1="<<a1<<", b="<<b<<", c="<<c<<", d="<<d<<", e="<<e<<endl;
char sir[256];
cout<<"Sir =";
cin>>sir;
cout<<"sir="<<sir<<endl;
return 0;
}
4. Operatorul & şi tipul referinţă
 Construcţia &nume defineşte în C adresa de început a zonei de memorie alocată pentru nume
 În C++ operatorul & are şi rolul de a marca un nou tip derivat, tipul referinţă
 Tipul referinţă creează sinonime pentru nume deja existente (alias-uri)
 Spre deosebire de variabile şi pointeri, valoarea atribuită unei referinţe NU POATE FI SCHIMBATĂ; odată
ce s-a atribuit o valoare unei referinţe aceasta rămâne asociată pentru toată durata execuţiei blocului de
program
 O referinţă este în esenţă un pointer implicit
 Toate variabilele de tip referinţă trebuie iniţializate atunci când sunt create (cu excepţia cazurilor în care
sunt membre ale unei clase, parametri de funcţii sau valori returnate de funcţii)
 Tipul referinţă poate funcţiona şi ca tip returnat de o funcţie, caz în care apelul de funcţie poate figura şi în
stânga operatorului de atribuire
Observaţie : în astfel de situaţii este important ca locaţia de memorie spre care se referă să nu fie eliberată
la încheierea execuţiei funcţiei (de exemplu o referinţa către o variabilă locală nu se poate returna!)

5. Transferul prin referinţă al parametrilor


 Metoda implicită de transfer a parametrilor în C este cea prin valoare
 În C++ se implementează efectiv transferul prin referinţă al parametrilor, definind parametri formali de
tip referinţă. Aceştia creează practic sinonime pentru parametrii efectivi.

6. Operatori de alocare şi dealocare dinamică a memoriei new şi delete


 În C se pot aloca zone de memorie în memoria heap folosind funcţii de bibliotecă (malloc, calloc)
 C++ asigură un sistem propriu alternativ de alocare dinamică bazat de 2 operatori : new şi delete
 În C++ alocarea dinamică de memorie se face prin intermediul operatorului unar new
 Operatorul new returnează adresa de început a zonei de memorie alocată, sau pointerul NULL în cazul în
care nu se poate face alocarea

Observaţii : nu este necesară specificarea explicită a numărului de octeţi, new alocând memorie
suficientă pentru a stoca obiectul de tipul specificat
: new returnează automat un pointer spre tipul specificat, nu este necesară conversia explicită
de tip
 Operandul operatorului new poate fi:
- numele unui tip (predefinit sau definit de utilizator), de exemplu :
new tip(expresie_de_iniţializare)
- în cazul alocării de memorie pentru tablouri, numele tipului este urmat, între paranteze drepte de o
expresie de tip întreg ce defineşte numărul de elemente ale tabloului, de exemplu :
new tip[expresie].
Prin aceasta, se rezervă o zonă de memorie de expresie*sizeof(tip) octeţi.

Observaţie : elementele unui tablou nu se pot iniţializa la alocare.


 O zonă de memorie alocată cu new se eliberează exclusiv prin intermediul operatorului delete; în cazul
eliberării memoriei alocate pentru tablouri sintaxa aferentă este delete []

Observaţii : operatorul delete se poate folosi doar cu un pointer valid, alocat în prealabil prin utilizarea
operatorului new. Folosirea oricărui alt tip de pointer este o operaţie cu rezultat nedefinit.
 Un avantaj major al folosirii operatorilor new şi delete este posibilitatea supraîncărcării acestora.

7. Parametrii impliciţi ai funcţiilor

 Unii dintre parametrii formali ai unei funcţii pot avea valori iniţiale prestabilite
 Valorile implicite ale parametrilor se precizează doar în prototip
 Dacă numărul parametrilor de apel este mai mic decât cel din lista parametrilor formali, parametrii lipsă
vor lua valorile implicite
 Începând cu primul parametru transmis implicit, toţi cei ce urmează vor fi introduşi ca având valori
implicite

8. Supraîncărcarea funcţiilor (function overloading)


 Fiecare funcţie în C++ are un antet/prototip ce oferă următoarele informaţii
1. numele funcţiei
2. numărul argumentelor
3. tipul argumentelor
4. ordinea argumentelor
5. tipul valorii returnate
 Fiecărei funcţii în C++ i se asociază o aşa-numită semnătură, care cuprinde doar informaţiile 1-4
 Supraîncărcarea (supradefinirea) funcţiilor este un concept de programare care permite programatorului
să definească mai multe funcţii cu acelaşi nume în acelaşi domeniu de vizibilitate, cu condiţia ca acestea să
aibă semnături diferite
 Supraîncărcarea funcţiilor reprezintă o formă rudimentară de polimorfism
 Compilatorul selectează versiunea corespunzătoare pe baza tipurilor argumentelor efective de apel
 Funcţiile supraîncărcate NU pot diferi numai prin tipul valorii returnate
 La apelul unei funcţii supraîncărcate se selectează versiunea corespunzătoare pe baza principiului
adecvanţei maxime (“best-match”). Adecvat în acest context înseamnă că:
 Există o potrivire exactă
 Se poate face o conversie implicită, fără pierdere de informaţii
 Se poate face o conversie, cu pierdere de informaţii
 Există rutină de conversie definită de utilizator.
 În cazul în care nu se poate găsi o funcţie adecvată se generează un mesaj de eroare

9. Funcţii inline
 Funcţiile inline reprezintă o alternativă oferită de C++ macro-urilor din C
 Apelul unei funcţii inline se înlocuieşte la compilare prin corpul funcţiei (se expandează), eliminându-se
astfel toate operaţiile specifice apelului şi respectiv revenirii din funcţiile obişnuite.
 Folosirea funcţiilor inline poate creşte viteza de execuţie a unui program cu preţul creşterii numărului de
linii de program
 Compilatorul tratează directiva inline ca pe o indicaţie/sugestie, neexistând garanţia faptului că funcţia
va fi efectiv inline
 O funcţie inline nu poate fi recursivă şi nu poate fi referită printr-un pointer la funcţie (în astfel de situaţii
indicaţia inline este ignorată de compilator)
 O funcţie declarată inline are implicit legătură internă
 Avantaje ale utilizării funcţiilor inline faţă de macro-uri rezultă din faptul că funcţiile inline sunt
expandate de compilator, în timp ce macro-urile sunt expandate de preprocesor :
 La apelul funcţiilor inline se face verificarea adecvanţei tipului parametrilor de apel
 Expresiile folosite la apelul funcţiilor sunt evaluate o singură dată, în timp ce, în cazul macro-
urilor, acestea pot fi evaluate de mai multe ori
10. Tipul bool
 Tipul de date logice bool este un tip predefinit în C++ şi se reprezintă pe 1 byte
 Datele de acest tip pot lua valorile true sau false.
 Expresiile de condiţie au valori de tip bool în C++
 Între cele două valori există următoarele relaţii
!false == true şi respectiv !true == false

11. Tipul structură


 Tipul structură este un tip definit de utilizator ce constă dintr-o mulţime finită componente, eventual de
tipuri diferite
 Structurile se declară astfel :
struct nume {lista_elemente} lista_variabile;
unde nume este numele tipului structură introdus prin aceasta declaraţie, ce se poate folosi ca atare pe post de
tip de date, lista_elemente cuprinde declaraţii ale componentelor tipului, separate prin punct şi virgulă, iar
lista_variabile conţine variabilele de tipul structură introduse de declaraţie
 Accesul la componentele unei structuri se face folosind operatorul .(punct), atunci când se dispune de
numele variabilei structură, sau cu operatorul ->, când se dispune de adresa variabilei structură

12. Tipul uniune

 Tipul uniune este un tip definit de utilizator ce constă dintr-o mulţime finită de componente, ce ocupă
acelaşi spaţiu de memorie
 Uniunile se declară astfel :
union nume {lista_elemente} lista_variabile
unde nume este numele tipului uniune introdus prin aceasta declaraţie, ce se poate folosi ca atare pe post de tip
de date, lista_elemente cuprinde declaraţii ale componentelor tipului, separate prin punct şi virgulă, iar
lista_variabile conţine variabilele de tipul uniune introduse de declaraţie
 Accesul la componentele unei uniuni se face folosind operatorul .(punct), atunci când se dispune de
numele variabilei uniune, sau cu operatorul ->, când se dispune de adresa variabilei uniune
13.Tipul enumerat

 Tipul enumerat este un tip definit de utilizator ce constă dintr-o mulţime finită de constante, numite
enumeratori. O enumerare este deci formată dintr-un set de constante (cu reprezentare de tip întreg) care
specifică astfel toate valorile posibile pe care le poate avea o variabilă de acel tip.
 Enumerările sunt declarate în mod asemănător structurilor :
enum nume {lista_elemente} lista_variabile;
unde nume este numele tipului introdus prin aceasta enumerare, ce se poate folosi ca atare pe post de tip de
date, lista_elemente cuprinde constantele tipului, separate prin virgule, iar lista_variabile conţine variabilele de
tipul enumerat introduse de declaraţie
 Tipul enumerat este compatibil cu un tip întreg astfel ca fiecare constanta a tipului are asociată o valoare
întreaga, primul enumerator fiind implicit asociat cu valoarea 0, dacă nu se specifică explicit altfel.
 Simbolurile folsite în tipurile enumerate NU pot fi introduse şi afişate direct.
Tipuri de date abstracte în limbajul C++ - Clase

 Programarea orientată obiect are în vedere în principal organizarea globală a programului şi nu în mod
primordial detaliile de funcţionare a programului. Procesul de abstractizare permite astfel ignorarea
detaliilor de implementare

Figura 3.1. Procesul de abstractizare

 Modelul defineşte o viziune abstractă asupra problemei, care se focalizează pe aspectele legate de
identificarea proprietăţilor esenţiale ce o caracterizează (date şi operaţii)
 Tipul de date abstract este o entitate manevrată doar prin operaţiile ce definesc acel tip. Avantajele
utilizării tipurilor de date abstracte sunt:

 Programele devin independente de modul de reprezentare a datelor. Modul de reprezentare poate fi


modificat, fora însa a afecta restul programului
 Se previne modificarea accidentală a datelor. Utilizatorul tipului abstract este forţat să manipuleze
datele doar prin intermediul metodelor/operatorilor ce compun tipul abstract, astfel reducându-se
riscul alterărilor/distrugerilor datelor.
 Se permite verificarea riguroasă la compilare a utilizării corecte a datelor de tipul respectiv.

 Descrierea oricărui tip de date abstract presupune precizări asupra celor două elemente componente:

 Date
 Operaţii (inclusiv descrierea interfeţei)

Figura 3.1. Structura unui tip de date abstract (abstract data type - ADT)

 Limbajele orientate obiect permit programarea folosind tipuri abstracte de date, care, în acest context, se
numesc clase
 Crearea unei instanţe a unei clase (instanţierea), cu proprietăţi bine definite, duce la crearea unui obiect
 În concluzie, ideea fundamentală care stă la baza limbajelor orientate obiect este aceea de a combina
(încapsula) într-o singură entitate atât date, numite date membru, cât şi funcţii, numite funcţii
membru (metode), care operează asupra acelor date. O astfel de entitate se numeşte obiect.
 Abordarea unei probleme de programare din perspectiva unui limbaj orientat obiect nu mai presupune
împărţirea problemei în funcţii, ci identificarea obiectelor implicate în dezvoltarea soluţiei
Figura 3.2. Paradigma programării orientate obiect şi analogia cu departamentele unei firme
 O clasă descrie unul sau mai multe obiecte ce pot fi precizate printr-un acelaşi set de atribute si metode.
 Se spune că un obiect este o instanţiere (instanţă) a unei clase.
 Un obiect este caracterizat de:
• nume
• atribute (date) - valorile atributelor la un moment dat definesc o stare
• metode (funcţii, servicii, operaţii)
 Un obiect se identifică în mod unic prin numele său şi el defineşte o stare reprezentată de atributele sale la
un moment particular de timp
 UML (Unified Modeling Language) este un limbaj de modelare, folosit pentru a reprezenta soluţiile
sistemelor software, fundamental legat de metodologia OO (Object-Oriented) folosită pentru dezvoltarea
de software, fără a fi doar un simplu limbaj de modelare orientat pe obiecte, ci în prezent, este limbajul
universal standard pentru dezvoltatorii software
 Diagrama folosită în modelarea obiect se numeşte diagramă de clase şi ea oferă o notaţie grafică pentru
reprezentarea claselor şi a relaţiilor dintre ele. Diagramele de clase descriu cazuri generale în modelarea
sistemului.
 Clasele sunt reprezentate prin dreptunghiuri împărţite în trei compartimente şi care conţin numele clasei
(în compartimentul superior), lista de atribute ale clasei (opţional) şi lista de operaţii (opţional) cu
argumentele lor şi tipul returnat. Cele trei compartimente vor fi separate între ele prin câte o linie
orizontală.
Nume clasă
Atribute (date membru)
Metode (Funcţii membru)
Figura 3.3. Diagramă de clasă UML

 Conceptele implementate prin clase au:


• o parte protejată, numită şi privată, care exprimă partea specifică, proprie conceptului şi are
rolul de a asigura implementarea clasei (conceptului)
• o parte neprotejată, numită şi publică, ce reprezintă interfaţa cu celelalte clase ale
programului.
Observaţie: În general, partea publică poate conţine şi date membru, dar atunci datele respective nu mai sunt
protejate !
 Pentru a realiza protecţia datelor se utilizează modificatori de protecţie :
• private - accesul la componentele private se face doar prin intermediul funcţiilor membru
publice ale clasei
• protected – modificator utilizat în contextul derivării claselor
• public – acces nerestricţionat
 O clasă se specifică printr-o declaraţie de clasă :
class NumeClasa {
// implicit privat ...
// date şi funcţii membru

public:
// tot ce urmează este public până ...
// date şi funcţii membru

private:
// ... aici, unde se comută din nou pe privat până ...
// date şi funcţii membru

protected:
// ... aici, unde se comută pe protejat ...
// date şi funcţii membru

public:
// ... şi din nou public.
// date şi funcţii membru
};

 Numele clasei este un tip de dată abstract şi se poate folosi ca atare pe post de tip de date în continuare
 În cazul apelului unei funcţii membru, acesteia i se transmite implicit un pointer (constant!) către obiectul
curent care a iniţiat apelul - denumit pointerul this, ce are declaraţia implicită :
NumeClasa *const this;
 Pointerul this poate fi utilizat explicit de către programator în funcţiile membru dacă este nevoie să se
facă referire la obiectul spre care acesta pointează
 Funcţiile membru foarte simple pot fi definite în interiorul declaraţiei clasei, devenind astfel funcţii inline
 Pentru funcţiile complexe, se declară în declaraţia de tip doar prototipul
 Funcţiile membru definite în exteriorul declaraţiei clasei pot fi inline dacă se declară explicit acest lucru
 O funcţie membru care se defineşte în afara declaraţiei clasei a cărei membră este se califică folosind
numele tipului(clasei) prin intermediul operatorului de rezoluţie :
Tip_returnat NumeClasa :: nume_funcţie_membru(listă_de_parametri)
 Iniţializarea unui obiect este o problemă mult mai complexă decât iniţializarea datelor obişnuite, de aceea
se foloseşte în acest scop o funcţie membru specială, numită constructor
 Constructorul unei clase are întotdeauna acelaşi nume ca şi numele clasei
 Constructorul unei clase nu returnează nici un tip
 Dacă o clasă are definit un constructor atunci acesta se apelează automat la instanţierea unui obiect al
clasei respective, inclusiv în cazul alocării dinamice
 O clasă poate avea mai mulţi constructori care devin astfel metode supraîncarcate
 Constructorii pot avea sau nu parametri.
 Constructorul fără parametri se numeşte constructor implicit
 Dacă există definit un constructor implicit atunci nu se mai poate defini pentru clasa respectivă un
constructor cu toţi parametrii impliciţi
 În cazul în care programatorul nu defineşte explicit nici un constructor, compilatorul generează în mod
automat cod pentru un constructor implicit
 Constructorul implicit generat de compilator are doar rolul de a rezerva memorie pentru datele membru
ale clasei respective, situaţie inadecvată atunci când la crearea unui obiect este necesară alocarea dinamică
de memorie.
 Un constructor al unui obiect este apelat o singură dată pentru obiecte globale sau pentru cele locale
alocate static. Pentru obiectele locale, constructorul este apelat de fiecare dată când se creează obiectul
respectiv
 În cazul în care o clasă are cel puţin un constructor şi nici unul dintre constructori nu are parametri
impliciţi, nu se pot instanţia obiecte neiniţializate (fapt cu consecinţe „fatale” în cazul definirii tablourilor
de obiecte !
 Parametrul unui constructor poate fi de orice tip, cu excepţia tipului definit de clasa al cărei constructor
este. Sunt permise în acest context doar parametri de tip referinţă sau pointer către clasa respectivă.
NumeClasa(NumeClasa *p);
NumeClasa(NumeClasa& p);
 Constructorul NumeClasa(const NumeClasa& p) este un constructor special ce permite copierea obiectelor
şi se numeşte constructor de copiere
 Constructorul de copiere poate avea şi alţi parametri, care însă trebuie să aibă valori implicite
 Constructorul de copiere se apelează :
 la crearea unui obiect nou din unul deja existent,
 la transferul parametrilor prin valoare şi
 în cazul funcţiilor ce returnează obiecte.
 Nu este obligatorie definirea explicită a unui constructor de copiere, acesta poate fi generat automat de
către compilator; există însă situaţii în care este absolut necesară definirea unui constructor de copiere şi
anume atunci când la crearea unui obiect este necesară alocarea dinamică de memorie.
 Constructorul de copiere generat implicit de compilator realizează o copie identică, bit cu bit (bitwise), a
obiectului iniţial în obiectul ţintă
 Una din cele mai frecvente situaţii în care trebuie evitată copierea bit cu bit este cea în care, la crearea
obiectului, se alocă memorie dinamic
 În cazul în care se defineşte explicit doar constructorul de copiere, compilatorul nu generează cod pentru
constructorul implicit
 Eliberarea spaţiului de memorie ocupat de un obiect (distrugerea obiectului) se poate realiza folosind o
altă funcţie membru specială, numită destructor.
 Orice clasă are un singur destructor.
 Destructorul nu are parametri şi nu returnează nici un tip
 Numele destructorului se construieşte din numele clasei căreia îi aparţine, prefixat de simbolul ~(tilda)
~NumeClasa()
 Destructorul este apelat automat de către un program atunci când încetează existenţa unui obiect (la
încheierea execuţiei programului sau atunci când se iese din domeniul de valabilitate al obiectului); în
cazul alocării dinamice, operatorul delete asociat unui pointer ce indică spre o zonă de memorie rezervată
pentru un obiect, apelează implicit destructorul clasei
 În cazul în care programatorul nu defineşte explicit destructorul clasei, compilatorul generează în mod
automat cod pentru destructor, ce eliberează memoria alocată datelor membru (nu şi memoria eventual
alocată dinamic, caz în care trebuie definit explicit destructorul clasei)
În concluzie, declaraţia generică a unei clase poate fi descrisă astfel :

class NumeClasa
{
// date membru şi metode (implicit) private
protected:
// date membru şi metode protejate
public:
NumeClasa() ; // prototip constructor implicit
NumeClasa(lista_de_parametri) ; // prototip constructor cu parametri
NumeClasa(const NumeClasa&) ; // prototip constructor de copiere
~NumeClasa() ; // prototip destructor
// date membru şi alte metode publice
};
Exemplul 1. Clasa Complex double& retimag() // definitie - implicit inline
#include <stdio.h> {
#include <iostream> return imag;
#include <math.h> }
void afiscomplex(char* format); // prototip
using namespace std; ~Complex(){}
};
class Complex
{ void Complex::afiscomplex(char* format)//UZ DIDACTIC!
double real; {
double imag; printf(format,real,imag);
public: }
Complex()
{ int main(void)
real=0.0;imag=0.0; {
} Complex z0; // Numai cu constr de copiere=>ERR
Complex(double x, double y=0){ //:no appropriate default constructor available
real = x;imag = y; printf("z0=");
} z0.afiscomplex("%g+i%g\n");
Complex(const Complex& c){ Complex z1(1.0,2.0);
real=c.real;imag=c.imag; printf("z1="); z1.afiscomplex("%g+i%g\n");
cout<<"Constructor de copiere"<<endl; Complex z2(z1);
} printf("z2="); z2.afiscomplex("%g+i%g\n");
double mod() // definitie - implicit inline Complex z3=z2;
{ printf("z3=");
return sqrt(real*real+imag*imag); z3.afiscomplex("%g+i%g\n");
} //z3.real=100.0; cannot access private member
double& retreal() // definitie - implicit inline //declared in class 'Complex'
{ z3.retreal()=100.0;
return real; z3.retimag()=200.0;
} printf("z3="); z3.afiscomplex("%g+i%g\n");
return 0;
}
Constructori şi destructori – Exemple

Exemplul 1.

#include <iostream>
using namespace std;

class Copac {
int inalt;
public:
Copac(int iniInalt); // Constructor
Copac(const Copac&); // Ctor copiere
~Copac(); // Destructor
void creste(int c);
//inline void creste(int c);
void printinalt();
};

Copac::Copac(int iniInalt) {
inalt = iniInalt;
cout<<"Constructor "<< inalt<<endl;
}

Copac::Copac(const Copac& c) {
inalt = c.inalt;
cout<<"Constructor copiere "<< inalt<<endl;
}

Copac::~Copac() {
cout << "in destructor Copac " ;
printinalt();
}

inline void Copac::creste(int c) {


inalt += c;
}

void Copac::printinalt() {
cout << "inalt copac = " << inalt << endl;
}
Copac cg(0); // GLOBAL Rezultatul executiei :
int main() Constructor 0
{ Start main
cout<<"Start main"<<endl; Constructor 1
// Copac tt; Cannot generate default Constructor copiere 1
constructor Constructor copiere 0
// since constructors were declared Constructor 2
Copac c0(1); // constructor cu parametri local {
Copac c1(c0); // constructor de copiere Constructor 10
Copac c2=cg; //// ctor de copiere nu
dupa creare Copac inalt copac = 10
atribuire!!!
// forma invechita a ctor-ului de copiere inalt copac = 15
Copac *pc=new Copac(2); Constructor 100
for(int i=0;i<2;i++) inainte de }
{ in destructor Copac inalt copac = 15
cout << "local {" << endl; local {
Copac cl(10); Constructor 10
cout << "dupa creare Copac " ; dupa creare Copac inalt copac = 10
cl.printinalt(); inalt copac = 15
cl.creste(5); inainte de }
cl.printinalt(); in destructor Copac inalt copac = 15
static Copac cs(100); // STATIC dupa }
cout << "inainte de }" << endl; in destructor Copac inalt copac = 2
}
Stop main
cout << "dupa }" << endl;
delete pc; // fara, nu se elibereaza pc! in destructor Copac inalt copac = 0
cout<<"Stop main"<<endl; in destructor Copac inalt copac = 1
return 0; in destructor Copac inalt copac = 1
} in destructor Copac inalt copac = 100
in destructor Copac inalt copac = 0
Exemplul 2. int main()
#include <iostream> {
#include <string.h> Sir s1(70), s2("OK");
using namespace std; cout<<"s1=";
class Sir s1.afissir();
{ cout<<endl;
char *psir; cout<<"s2=";
int lung; s2.afissir();
public: cout<<endl;
Sir(char * s=""); Sir s3("C++ este un C mai bun");
Sir(int); cout<<"s3=";
Sir(const Sir&); s3.afissir();
void afissir(); cout<<endl;
~Sir(){ delete []psir;} Sir s4(s3);
}; cout<<"s4=";
s4.afissir();
Sir:: Sir (char *sursa) cout<<endl;
{ return 0;
lung=strlen(sursa); }
psir=new char[lung+1];
strcpy(psir, sursa); Rezultatul executiei :
cout<<"Constructor sursa=" <<
sursa<<endl; Constructor sursa=70
} Constructor sursa=OK
s1=
Sir:: Sir(int l) s2=OK
{ Constructor sursa=C++ este un C mai bun
lung=l; s3=C++ este un C mai bun
psir=new char[l+1]; Constructor cop. sursa=C++ este un C mai
cout<<"Constructor sursa="<<l<<endl; bun
*psir='\0'; s4=C++ este un C mai bun
}

Sir ::Sir(const Sir& sursa)


{
lung=sursa.lung;
psir=new char[lung+1];
strcpy(psir, sursa.psir);
cout<<"Constructor cop. sursa="
<<sursa.psir<<endl;
}

void Sir::afissir()
{
cout<<psir;
}
Modificatorul const
 Modificatorul const modifică tipul unei date în sensul restrângerii modului său de utilizare
 Datele declarate folosind modificatorul const nu pot fi modificate în mod direct
 Există următorele situaţii distincte semnficative în care se utilizează modificatorul const
 Declaraţii de variabile sau instanţieri de obiecte
 Declaraţii de parametri formali
 Antete/prototipuri de funcţii, fie pentru a proteja valoarea returnată de funcţie, fie pentru a semnala faptul
că funcţia membru nu modifică obiectul ce o apelează (const face parte din semnatura !)

Exemple :
1. Declaraţia variabilelor
a) const int i=3;
const double pi=3.141;
i=4; i=3; pi=3.1415926; !GRESIT

b) const char *sir=”abc”;


*sir=’A’; ! GRESIT
sir++; CORECT

c) char *const sir=”abc”;


*sir=’A’; CORECT
sir++; ! GRESIT

d) const char *const sir=”abc”;


*sir=’A’; GRESIT
sir++; ! GRESIT

e) const int i=2;


int *p=&i; ! GRESIT
const int *p=&i; CORECT

f) class NC{ ..};


const NC ob(..); // obiect constant
2. Transferul parametrilor
a) char *strcpy(char *dest, const char *sursa);

b) class NC
{…
public:
NC(const NC&);…}
const NC ob1(..);
NC ob2(ob1); // eroare in absenta lui const!

3. Protejarea valorilor returnate de funcţii


a) const char *denluna(int n)
{
static const char *tpl[]={“ilegal”,”ian”, “feb”..};
return (n<1||n>12?tpl[0]:tpl[n];
}

b) const int j=7; // global


int *func(void)
{
return &j; // !GRESIT
}
int *pp=func(); *pp=-7; !!!

const int *func(void) // corect


{
return &j; //
}
int *pp=func(); // !GRESIT
const int *pp=func(); // CORECT

4. Specificare faptului ca o funcţie membru nu modifica obiectul pentru care a fost apelată
tip_ret NC::f_membru(lista_par) const //const face parte din semnatura

void Sir::afissir() const;

const Sir s(“Nu ma modifica!”);


s.affisir(); // fara const NU se putea apela!
Funcţii şi clase friend

 Pentru a permite accesul unor funcţii ce nu sunt metode ale unei clase la datele membru protejate ale clasei s-a
definit conceptul de funcţii friend
 Prototipul unei funcţii friend se scrie astfel :
class NumeClasa
{
……..
friend tip_returnat nume_functie_friend(lista_parametri);
……..
};
 Unei funcţii friend nu i se transmite pointerul this
 Atunci când se defineşte în interiorul clasei, funcţia este inline (daca acest lucru este posibil)
 Atunci când se defineşte în afara clasei, numele funcţiei NU se califică folosind operatorul de rezoluţie
 În cazul în care se doreşte ca toate metodele unei clase să fie funcţii friend pentru o clasă dată, se declară întreaga
clasă ca fiind friend al clasei date

class NumeClasa1;
class NumeClasa2
{
……..
friend NumeClasa1;
……..
};

 Relaţia friend nu e tranzitivă


Exemplu de operare cu funcţii membru şi funcţii friend
În cazul unor funcţii membru şi funcţii friend cu aceeaşi funcţionalitate, funcţia friend are un parametru în plus!

class X
{
int i; //private
friend void functieFriend(X *, int);
public:
void functieMembru (int);
};

void functieFriend(X *ptr, int ii)// fara friend - nu se califica cu X::!


{
ptr->i=ii; // are acces la data privata
}

void X:: functieMembru(int ii) // functie membru (metoda) - se califica cu X::!


{
i=ii; // this->i=ii
}

Cele două funcţii se apelează astfel:

X obX;
.....
obX. functieMembru(55)
functieFriend(&obX,55)
Supraîncărcarea operatorilor în limbajul C++
 Tipurile abstracte de date au fost concepute astfel încât să permită folosirea lor de către utilizator într-o manieră
similară tipurilor predefinte. În acest scop se pune la dispoziţia programatorului mecanismul de supraîncărcare a
operatorilor
 Limbajul C++ permite supraîncărcarea numai pentru operatorii existenţi (nu se pot crea operatori noi)
 Nu se pot supraîncărca următorii operatori :
. (punct)
:: (rezoluţie)
.*(selecţie a membrilor prin intermediul pointerilor la membri)
?: (condiţional)
sizeof
typeid
 Nu se pot supraîncărca directivele adresate preprocesorului # şi ##
 Nu se pot crea operatori noi
 Prin supraîncărcare nu se schimbă n-aritatea, prioritatea sau asociativitatea operatorilor (acestea rămânând cele
predefinite)
 Supraîncărcarea operatorilor se poate face folosind funcţii membru sau funcţii friend
 Sintaxa generală pentru supraîncărcarea operatorilor este :
- pentru funcţii friend :
tip_returnat operator op(lista_parametri) { corp}
- pentru funcţii membru :
tip_returnat NumeClasa :: operator op(lista_parametri) { corp}
 În general, pentru acelaşi operator, funcţiile friend au un argument în plus în lista de parametri, funcţiile
membru având implicit transmis operandul pentru care au fost apelate (prin intermediul pointerului this)
Exemplul 4. Clasa Complex cu operatori supraîncărcaţi
#include <iostream >
#include <math.h>
using namespace std;

class Complex
{
double real;
double imag;
public:

Complex(double x=0.0, double y=0.0)


{
real=x;imag=y;
}

Complex(const Complex& c)
{
real=c.real;imag=c.imag;
}

double mod()
{
return sqrt(real*real+imag*imag);
}

double& retreal()
{
return real;
}

double& retimag()
{
return imag;
}

Complex operator + (const Complex &) const;


friend Complex operator + (double , const Complex& );
friend istream& operator >>(istream &, Complex&);
friend ostream& operator <<(ostream &, const Complex&);
~Complex(){}
};
Complex Complex :: operator + (const Complex& z) const
{
Complex temp;
temp.real=real+z.real;
temp.imag=imag+z.imag;
return temp;
}

Complex operator + (double x, const Complex& z)


{
Complex temp;
temp.real=x+z.real;
temp.imag=z.imag;
return temp;
}

istream& operator >>(istream & in, Complex& z)


{
in>>z.real>>z.imag;
return in;
}

ostream& operator <<(ostream &out, const Complex& z)


{
out<<z.real<<"+i"<<z.imag<<endl;
return out;
}

int main(void)
{
Complex z1;
const Complex z4(2,2);
Complex z5;
z1=z4+z5;
cout<<z1<<endl;
cout<<"z1=";
cin>>z1;
cout<<"z1= "<<z1;
z1=3.0+z4;
cout<<"z1= "<<z1;
return 0;
}
Supraîncărcarea operatorilor (continuare)

Supraîncărcarea operatorului de atribuire


 Operatorul de atribuire se poate supraîncărca numai prin funcţii membru
 Pentru a asigura un comportament similar operatorului de atribuire ce operează asupra tipurilor de date
standard, metoda ce supraîncarcă operatorul de atribuire trebuie sa returneze o referinţă către clasa căreia îi
apaţine
 O atenţie specială trebuie acordată situaţiilor în care clasa menevrează zone de memorie alocate dinamic, caz în
care trebuie asigurată operarea corectă inclusive în cazul autoatribuirii
 Pentru operatorii de tip op= este necesară supraîncărcarea explicită, nefiind suficientă supraîncărcarea separată
pentru op şi respectiv =
 Forma generală de supraîncărcare a operatorului de atribuire este :
NumeClasa& NumeClasa:: operator = (const NumeClasa & operand)
{
………
return *this;
}

Exemple:

a. Supraîncărcarea operatorului de atribuire pentru clasa Complex :

Complex& Complex::operator = (const Complex & z)


{
real= z.real;
imag=z.imag;
return *this;
}
b. Supraîncărcarea operatorului de atribuire pentru clasa Sir.
Sir& Sir::operator=(const Sir& sursa)
{
if(this != &sursa)
{
delete []psir;
lung=sursa.lung;
psir=new char[lung+1];
strcpy(psir, sursa.psir);
}
return *this;
}

 În general, pentru o clasă NumeClasa, operatorul generic op= se supraîncarcă astfel :

NumeClasa& NumeClasa:: operator op= (const NumeClasa & operand)


{
………
return *this;
}

Exemplu : Supraîncărcarea operatorului += pentru clasa Complex :

Complex& Complex::operator += (const Complex & z)


{
real=real+z.real;
imag=imag+z.imag;
return *this;
}
Supraîncărcarea operatorilor binari de relaţie

 Forma generală de supraîncărcare a operatorilor de relaţie este :

bool NumeClasa:: operator op_rel (const NumeClasa & operand) const


{
………
return expresie_relatie;
}

Exemplu : Supraîncărcarea operatorului < pentru clasa Copac.

bool Copac::operator <(const Copac& c) const


{
return inalt<c.inalt;
}

Tema : Proiectaţi clasa Copac astfel încât că se poate face si comparaţii de forma intreg < obiect din clasa Copac !

Supraîncărcarea operatorilor unari de incrementare/decrementare

 În general, pentru o clasă NumeClasa, operatorul generic, în notaţie prefixată op_inc/dec_pre se supraîncarcă
astfel :

NumeClasa& NumeClasa:: operator op_inc/dec_pre ()


{
………
return *this;
}
 În general pentru o clasă NumeClasa, operatorul generic, în notaţie postfixată op_inc/dec_post se supraîncarcă
astfel :

NumeClasa NumeClasa:: operator op_inc/dec_post (int)


// Se introduce un parametru fictiv de tip int . Chiar şi în antetul funcţiei acest parametru nu are nume.
// Compilatorul foloseşte această informaţie pentru a face distincţie între cele două notaţii
{
NumeClasa temp(*this); // se crează o copie a stării curente a obiectului
// incrementarea/decrementarea ce afectează efectiv obiectul care a apelat operatorul
………
return temp; // se returnează obiectul (o copie a acestuia mai precis!) cu starea de dinaintea
// incrementării/decrementării
}

Exemple de supraîncărcare a operatorului de incrementare pentru clasa Copac :

Copac& Copac::operator++ () // incrementare în notaţie prefixată


{
inalt++;
return *this;
}

Copac Copac::operator++ (int) // incrementare în notaţie postfixată


{
Copac temp(*this);
inalt++;
return temp;
}
Supraîncărcarea operatorului de indexare []

 În general, operatorul de indexare [] este necesar pentru a accesa date de tip tablou cu tipul de bază tip , inclusiv
pentru instanţieri de obiecte constante, se supraîncarcă cu prototipuri de forma :

tip operator[](size_t) const;


tip& operator[](size_t);

size_t corespunde unui tip de date întreg returnat de operatorul sizeof şi este definit în fişierul header cstring ca
fiind tip intreg unsigned.

Observaţie:
Pentru depanarea programelor este utilă în anumite situaţii folosirea macro-ului assert
void assert (int expression);

Daca expresia argument expression are valoarea zero (i.e. false), se afişează un mesaj pe dispozitivul standard de
eroare şi se apelează funcţia abort , încheindu-se execuţia programului.

Tipul mesajului afişat depinde de implementarea compilatorului. Un format uzual este:


Assertion failed: expression, file filename, line line number
Acest macro este dezactivat dacă înaintea includerii fişierului header assert.h este definit un macro cu numele
NDEBUG. Aceasta permite programatorului să includă oricâte apeluri ale macroului assert în codul sursă code pe
durata depanării programului, dezactivându-le apoi pentru varianta finală prin includerea liniei de mai jos la
începutul codului, înaintea includerii fişierului header assert.h:
#define NDEBUG
Exemplu. Să se construiască o clasă pentru operarea cu vectori cu elemente de tip intreg, pentru care să se
supraîncarce operatorul de indexare:

#include<iostream> VectInt::VectInt(int n)
#include <assert.h> {
m_Size=n;
using namespace std; assert(n > 0);
pVect = new int[m_Size];
class VectInt }
{
int *pVect; int main(void)
int m_Size; {
VectInt v(5);
public: int i;
for(i=0;i<5;i++)
VectInt(int n = 10); v[i]=10*i;
~VectInt() for(i=0;i<5;i++)
{ cout<<v[i]<<'\t';
delete []pVect; cout<<endl;
} return 0;
int& operator[](int i) }
{
assert (i >= 0 && i < m_Size);
return pVect[i];
}
};
Membri statici

Date membru statice

 Până în prezent, fiecare obiect al unei clase işi avea propriile date membru
 Există aplicaţii în care este eficient să existe date membru comune tuturor obiectelor clasei, cu alte cuvinte să
existe o singură copie a datei membru respective pentru toate obiectele clasei. Astefel de date membru se
numesc date membru statice.
 Datele membru statice au următoarele caracteristici :

• Se declară folosind cuvântul rezervat static


• Pot fi private/protected/public
• O dată membru statică este creată înaintea instanţierii oricărui obiect al clasei respective
• În definiţia unei clase, în cazul datelor membru statice avem de a face efectiv cu o declaraţie a membrului
static, în sensul că NU SE FACE REZERVARE DE MEMORIE pentru data respectivă. Rezervarea de
memorie se face separat în domeniul fişier (global), chiar şi pentru membri statici privati/protejaţi, care
insă pot fi accesaţi astfel doar la definiţia datei
• O dată membru statică se poate referi fie prin intermediul unui obiect al clasei, fie independent

Funcţii membru statice

 Funcţiilor membru statice nu li se transmite pointerul this şi de aceea ele nu pot accesa date membru
nestatice ; ele operează doar asupra datelor membru statice
 Ele pot fi apelate fie prin intermediul unui obiect al clasei, fie independent

Reamintim faptul că funcţiile membru ale unei clase au următoarele proprietăţi:

1. Au acces la membri protejaţi şi privaţi ai clasei


2. Sunt în domeniul clasei
3. Li se transmite pointerul this

Funcţiile membru statice au numai proprietăţile 1 şi 2.

Funcţiile friend au numai proprietatea 1.


Exemple de utilizare a datelor membru şi funcţiilor statice
#include <iostream>
using namespace std;
class Clasa
{
private:
static int a; // data membru statica privata
int b;
public:
static int c; // data membru statica publica
static void Init(int=10); //functie membru statica
void Set(int i, int j){a=i;b=j;}
friend ostream& operator<<(ostream&, const Clasa&);
};
ostream& operator<<(ostream& out, const Clasa& o)
{
out<<" a static ="<<o.a<<" b nestatic ="<<o.b<<endl;
return out;
}
void Clasa::Init(int x)
{
a=x;
//b=99; object(or object pointer) required to access non-static data member
}

int Clasa::a = 88; // definitie data membru statica


int Clasa::c; // definitie data membru statica

int main()
{
Clasa::c=100;
// fara definitie apare eroare de link : Clasa::c is an undefined
// reference
//Clasa::a=99; access to private member is not allowed

REZULTATE
cout<<"c="<<Clasa::c<<endl;
c=100
Clasa x,y;
cout<<"x="<<x; x= a static =88 b nestatic =-858993460
x.Set(1,1);
cout<<"x="<<x; x= a static =1 b nestatic =1
y.Set(2,2);
cout<<"y="<<y; y= a static =2 b nestatic =2
cout<<"x="<<x; x= a static =2 b nestatic =1
Clasa::Init(100);
cout<<"y="<<y; y= a static =100 b nestatic =2
cout<<"x="<<x; x= a static =100 b nestatic =1
return 0;
}
#include <iostream>
#include <string.h>
using namespace std;
class Angajat
{
char *nume;
static int nr_ang; // declaratie
public:
Angajat(const char *);
Angajat(const Angajat &c);
~Angajat();
const char *getNume() const;
static int getNr_ang();
};
int Angajat::getNr_ang()
{
return nr_ang;
}
Angajat::Angajat(const char *s)
{
nume=new char[strlen(s)+1];
strcpy(nume,s);
++nr_ang;
}
const char *Angajat::getNume() int main(void)
const {
{
return nume; cout<<Angajat::getNr_ang()<<endl;
} const Angajat ob("Sef");

Angajat::Angajat(const Angajat& c) cout<<Angajat::getNr_ang()<<endl;


{ Angajat *pAng1=
nume= new Angajat("Ionescu");
new char[strlen(c.nume)+1];
strcpy(nume,c.nume); cout<<Angajat::getNr_ang()<<endl;
++nr_ang; Angajat *pAng2=
} new Angajat("Popescu");

Angajat::~Angajat() cout<<Angajat::getNr_ang()<<endl;
{ Angajat a(*pAng1);
delete []nume;
--nr_ang; cout<<Angajat::getNr_ang()<<endl;
cout<<nr_ang<<endl; delete pAng1;
} delete pAng2;

// definitie cout<<Angajat::getNr_ang()<<endl;
int Angajat:: nr_ang=0; return 0;
} // REZULTATE 0 1 2 3 4 3 2 2 1 0
Mecanisme de reutilizare a codului

 În cazul aplicaţiilor complexe, creşterea eficienţei activităţii de programare impune refolosirea codului existent.
 Există trei modalităţi de reutilizare a codului:
 compunerea
 agregarea
 moştenirea

 Prin compunere, obiectul, care include un alt obiect, va avea ca elemente membre toate elementele membre ale
obiectului inclus alături de cele specifice lui, accesul la membrii obiectului inclus realizându-se în mod
corespunzător.
 Se obişnuieşte să se spună că relaţia de compunere implică “posesia” obiectului inclus. Echivalent, relaţia de
compunere este o relaţie de tip “a avea” (“has a” relationship).
 Agregarea este un caz particular de compunere în care obiectul care face referire la un alt obiect nu are
controlul/responsabilitatea existenţei obiectului la care face referire (acesta nu este efectiv o parte a sa). În mod
uzual, în astfel de cazuri, referirea la un alt obiect se face prin intermediul pointerilor sau referinţelor.
 Moştenirea reprezintă o altă modalitate prin care se pot reutiliza şi extinde clasele existente fără a fi necesară
rescrierea integrală a codului aferent.
 Se obişnuieşte să se spună că relaţia de moştenire o relaţie de tip “un fel de” (”kind of” relationship).
 Conceptul de moştenire se realizează prin intermediul mecanismului de derivare, cu ajutorul aşa-numitelor clase
derivate.
 Moştenirea poate fi simplă sau multiplă, după cum clasa derivată moşteneşte caracteristicile de la o singură clasă
de bază sau de la mai multe clase de bază.
 Derivarea unei clase se defineşte ca fiind crearea unei clase noi, numită clasă derivată, prin preluarea
componentelor unei/unor clase de bază şi controlul accesului la acestea.
 O clasa de bază este o clasă generală şi esenţa derivării este refolosirea unui comportament definit anterior într-o
clasă de bază
 Prin moştenire se defineşte o ierarhie de clase cu ajutorul căreia să se modeleze sisteme complexe. Proiectarea
unei ierarhii de clase constituie activitatea fundamentală de implementare a unei aplicaţii orientate obiect.
Exemple Compunere
1.
#include <iostream> void set(int ii)
using namespace std; {
i = ii; x.set(ii);
class X }
{ int get() const
int i; { return i * x.get(); }
public:
X(int j=0) { i = j; } int modif()
void set(int ii) { i = ii; } {
int get() const { return i; } return x.modif();
int modif() { return i = i * 47; } }
}; };

class Y int main()


{ {
int i; Y y;
public: y.set(10);
X x; // obiect component y.x.set(20);
//(Embedded object) // Acces la obiectul component
Y() { i = 0; } cout<<y.get()<<" "<<
void set(int ii) { i = ii; } y.x.modif()<<endl; //x.i = 940
int get() const { return i; } Z z;
}; z.set(100);
cout<<z.get()<<" "<<z.modif()<<endl;
class Z // ATENTIE LA ORDINEA EVALUARII
{ // Intai se apeleaza metoda modif!
int i; return 0;
X x; // Embedded object }
public:
Z(){ REZULTATE
i = 0; 10 940
} 470000 4700
2.
Fişierul Punct2D.h:

#ifndef POINT2D_H
#define POINT2D_H
#include <iostream>

class Punct2D
{
private:
int m_nX;
int m_nY;

public:
Punct2D() : m_nX(0), m_nY(0) // constructor implicit, cu lista de initializare
{}
Punct2D(int nX, int nY) : m_nX(nX), m_nY(nY)//ctor cu parametri, cu lista de initializare
{}

// Supraincarcarea operatorului de iesire


friend std::ostream& operator<<(std::ostream& out, const Punct2D &cPunct)
{
out << "(" << cPunct.GetX() << ", " << cPunct.GetY() << ")";
return out;
}
// Functii de acces
void SetPunct(int nX, int nY)
{
m_nX = nX;
m_nY = nY;
}
int GetX() const { return m_nX; }
int GetY() const { return m_nY; }
};

#endif
Fişierul Creatura.h:

#ifndef CREATURE_H
#define CREATURE_H

#include <iostream>
#include <string>
#include "Punct2D.h"

class Creatura
{
private:
std::string m_strNume; // exemplu de utilizare a clasei string
Punct2D m_cLocatie;

public:
Creatura(std::string strNume, const Punct2D &cLocatie)
: m_strNume(strNume), m_cLocatie(cLocatie)
{
}

friend std::ostream& operator<<(std::ostream& out, const Creatura &cCreatura)


{
out << cCreatura.m_strNume.c_str() << " se afla la " << cCreatura.m_cLocatie;
return out;
}

void MutaLa(int nX, int nY)


{
m_cLocatie.SetPunct(nX, nY);
}
};

#endif
Fişierul main.cpp

#include <string>
#include <iostream>
#include "Creatura.h"

using namespace std;

int main()
{
cout << "Numele creaturii: ";
string cNume;
cin >> cNume;
Creatura cCreatura(cNume, Punct2D(4, 7));

while (1)
{
cout << cCreatura << endl;
cout << "Introduceti locatia X pentru creatura (-1 pt. a incheia): ";
int nX=0;
cin >> nX;
if (nX == -1)
break;

cout << "Introduceti locatia Y pentru creatura (-1 pt. a incheia): ";
int nY=0;
cin >> nY;
if (nY == -1)
break;

cCreatura.MutaLa(nX, nY);
}

return 0;
}
//Exemplu Agregare

#include <string>
using namespace std;

class Profesor int main()


{ {
private: // Se creeaza un prof. in afara
string m_strNume; // Departamentului
public:
Profesor(string strNume) Profesor *pProfesor = new
: m_strNume(strNume) Profesor("Popescu"); // creeaza prof.
{ {
} /* Se creeaza un Departament si se
foloseste constructorul cu parametrii
string GetNume() { return m_strNume; pentru a transmite prof. */
}
}; Departament cDept(pProfesor);

class Departament }// cDept iese din domeniu si este


{ // distrus
private:
Profesor *m_pcProfesor; // pProfesor inca exista!
//Se presupune ca exista DOAR un prof.
delete pProfesor;
public:
Departament(Profesor return 0;
*pcProfesor=NULL) }
: m_pcProfesor(pcProfesor)
{
}
};
In concluzie, caracteristicile specifice compunerii şi respective agregării sunt:

In cazul compunerii, clasa ce utilizează acest mecanism :

 Tipic utilizează variabile obişnuite


 Poate folosi pointeri dacă în clasa respectivă se controlează alocarea/dealocarea
 Este responsabilă pentru crearea/distrugerea subclaselor

In cazul agregării, clasa ce utilizează acest mecanism :

 Tipic utilizează variabile de tip pointer ce pointează spre obiecte create în afara clasei
 Poate folosi şi referinţe pentru a referi obiecte create în afara clasei
 Nu este responsabilă pentru crearea/distrugerea subclaselor
 Moştenire. Derivarea claselor.
 Sintaxa derivării în C++ este :
class D : [modificator acces] B1, [modificator acces] B2, ……,[modificator acces] Bn
{
………
};
unde : modificator acces ::= [virtual][ private| protected| public]
D este clasa derivată, iar B1, B2, .... Bn, sunt clasele de bază

 Dacă nu se specifică nici un modificator de acces, moştenirea se presupune private


 Derivarea se poate face în mai multe moduri prin intermediul modificatorilor de acces :

SPECIFICATOR DE PROTECŢIE MODIFICATOR DE ACCES TIP DE ACCES


ÎN CLASA DE BAZĂ LA DERIVARE ÎN CLASA DERIVATĂ

private public INACCESIBIL


protected public PROTEJAT
public public PUBLIC

private protected INACCESIBIL


protected protected PROTEJAT
public protected PROTEJAT

private private INACCESIBIL


protected private PRIVAT
public private PRIVAT

 Specificatorul de protecţie protected folosit în definiţia unei clase permite accesarea în clasa derivată a unor
membri care nu fac parte din secţiunea publică a clasei de bază.
 În general, modificatorul de acces protected nu este folosit ca modificator de acces în procesul de derivare a
unei clase.
 Se observă că, indiferent de specificatorul de acces (public, protected sau private) folosit, membrii din
secţiunea private a clasei de bază nu pot fi direct accesaţi în clasa derivată. Accesarea lor se face exclusiv prin
funcţiile membru publice sau protejate moştenite de la clasa de bază.
 Constructorii, destructorul şi metoda care supraîncarcă operatorul de atribuire (=) NU SE MOŞTENESC.
 Evident, deoarece funcţiile friend nu aparţin unei clase, nici acestea nu se vor moşteni!
 La construcţia unui obiect dintr-o clasă derivată se construieşte mai întâi partea corespunzătoare moştenită din
clasa de bază, în timp ce, la distrugerea unui obiect dintr-o clasă derivată se distruge mai întâi partea proprie şi
apoi cea corespunzătoare moştenirii din clasa de bază.
Exemplul 1:
#include <iostream>
using namespace std;

class Baza
{
public:
Baza()
{
cout<<"Constructor Baza"<<endl;
}
~Baza()
{
cout<<"Destructor Baza"<<endl;
}
};

class Derivat:public Baza


{ int main()
public: {
Derivat() Derivat ob_derivat;
{
cout<<"Constructor Derivat"<< return 0;
endl;
} }
~Derivat()
{ REZULTATE
cout<<"Destructor Derivat"<< Constructor Baza
endl; Constructor Derivat
} Destructor Derivat
};
Destructor Baza
Exemplul 2 :
#include <iostream> class Derivat_pv:private Baza
using namespace std; {
int k;
class Baza public:
{ Derivat_pv(int kd)
int i; {
protected: k=kd;
int j; }
public: int Retk()
Baza(){i=0;j=0;} {
void Setij(int x, int y) k=Reti();
{ j=99; //data cu acces protected
i=x; j=y; return k;
} }
int Reti(){return i;} };
int Retj(){return j;}
}; int main()
{
class Derivat_pb:public Baza Derivat_pb obj_deriv(3) ;
{ obj_deriv.Setij(-3,-99);
int k; cout<<"obj_deriv i="
public: <<obj_deriv.Reti()<<
Derivat_pb(int kd) " k="<<obj_deriv.Retk()<<endl;
{ Derivat_pv obj_deriv_pv(3) ;
k=kd; /* obj_deriv_pv.Setij(-3); // cannot
} acces Baza:Seti through a private base
int Retk() class*/
{ cout<<"k obj_deriv_pv ="<<
// i=99; access to private member Baza::i obj_deriv_pv.Retk()<<endl;
// is not allowed
k=Reti(); return 0;
j=88; }
return k; REZULTATE
} obj_deriv i=-3 k=-3
}; k obj_deriv_pv =0
Exemplul 5. Reutilizarea codului prin derivare
A. B.
#include <iostream> #include <iostream>
#include <process.h> #include <process.h>
using namespace std; using namespace std;
class Stack class Stack
{ {
protected: protected:
enum {SIZE=20}; enum {SIZE=20};
int st[SIZE]; private:
int top; int st[SIZE];
public: int top;
Stack() protected:
{ top = -1; } int get_top()
void push(int var) {
{ st[++top] = var; } return top;
int pop() }
{ return st[top--]; } public:
}; Stack()
{ top = -1; }
class Stack_d: public Stack void push(int var)
{ { st[++top] = var; }
public: int pop()
void push(int var) { return st[top--]; }
{ };
if(top >= SIZE-1) class Stack_d: public Stack
{ cout << {
"Error: stack overflow"; exit(1); } public:
Stack::push(var); void push(int var) {
} if(get_top() >= SIZE-1)
int pop() { cout <<
{ "Error: stack overflow"; exit(1); }
if(top<0) Stack::push(var);
{ }
cout << "Error: stack underflow"; int pop() {
exit(1); if(get_top()<0)
} {
return Stack::pop(); cout <<
} "Error: stack underflow";;exit(1);
}; }
return Stack::pop();
void main() }
{ };
Stack_d s; void main()
s.push(11); {
s.push(12); Stack_d s;
s.push(13); s.push(11); s.push(12); s.push(13);
cout << s.pop() << endl; cout << s.pop() << endl;
cout << s.pop() << endl; cout << s.pop() << endl;
cout << s.pop() << endl; cout << s.pop() << endl;
cout << s.pop() << endl; // oops cout << s.pop() << endl; // oops
} }
 În general există 4 combinaţii posibile între modalităţile de definire a constructorilor în clasa de bază şi în clasa
derivată :
1. Clasa de bază nu are constructori şi clasa derivată nu are constructori definiţi explicit
- În acest caz, în clasa derivată se pot instanţia doar obiecte neiniţializate, compilatorul generează
automat constructori (implicit impliciţi!)

2. Clasa de bază are constructori şi clasa derivată nu are constructori definiţi explicit
- În acest caz, clasa de bază trebuie să aibă definit constructorul implicit, iar în clasa derivată se pot
instanţia doar obiecte neiniţializate, compilatorul generează automat constructor implicit pentru clasa
derivată, care va apela automat constructorul implicit al clasei de bază.

3. Clasa de bază nu are constructori definiţi explicit şi clasa derivată are constructori
- În acest caz compilatorul generează automat constructor implicit pentru clasa de bază, doar membrii
clasei de bază accesibili în clasa derivată vor putea fi iniţializaţi

4. Atât clasa de bază cât şi clasa derivată au constructori definiţi explicit


- În acest caz, se pot transmite argumente constructorilor claselor de bază (folosind o aşa-numită listă de
iniţializare), cu condiţia ca aceştia să fie publici, cu următoarea sintaxă :
class B1; class B2; ……. class Bn;

class Deriv: public B1, public B2, ..., public Bn


{
……..
public:
Deriv(lista_argumente); // lista_argumente= lista_1 U lista_2 U .... U lista_n
…………
};

Deriv:: Deriv(lista_argumente): B1(lista_1), B2(lista_2),.... , Bn(lista_n)


{
..........
}
 O sintaxă similară celei corespunzătoare cazului 4 (cu listă de iniţializare) se poate folosi şi în cazul compunerii şi se
poate extinde şi pentru datele membru de tipuri predefinite

Exemplu:

class Element
{
…….
public:
Element(lista_el);
……
};

class Comp
{
Element el;
…….
public:
Comp(lista_comp);
……
};

Comp:: Comp(lista_comp) : el(lista_el) // Lista de initializare se descrie DOAR IN ANTET

{
..........
}

unde elementele din lista_el sunt incluse în lista_comp


Observaţie : Folosirea listei de iniţializare determină obţinerea unui cod mai eficient
Exemplul 1
#include <iostream> class Persoana
#include <string.h> {
using namespace std; Sir nume;
Sir prenume;
class Sir public:
{ Persoana(const Sir& n,const Sir& p);
char *psir; };
int lung; /*
public: Persoana:: Persoana(const Sir& n,
Sir(char * s=""); const Sir& p)
Sir(const Sir&); {
Sir& operator=(const Sir& sursa) ; cout<<"Constr. clasic\n";
~Sir(){ delete []psir;} nume=n; prenume=p;
};
} */
Sir:: Sir (char *sursa)
{ Persoana:: Persoana(const Sir& n,
lung=strlen(sursa); const Sir& p): nume(n), prenume(p)
psir=new char[lung+1]; // Lista de initializare se descrie
strcpy(psir, sursa); // DOAR IN ANTET
cout<<"Constructor sursa=" {
<<sursa<<endl; cout<<"Constr. lista de
} initializare\n";
}
Sir ::Sir(const Sir& sursa)
{ int main(void)
lung=sursa.lung; {
psir=new char[lung+1]; Persoana p1("Popescu", "Ion");
strcpy(psir, sursa.psir); return 0;
cout<<"Constructor copiere " }
<< sursa.psir<<endl;
} REZULTATE
CLASIC
Sir& Sir::operator=(const Sir& sursa) Constructor sursa=Ion
{ Constructor sursa=Popescu
if(this != &sursa) Constructor sursa=
{
Constructor sursa=
delete []psir;
lung=sursa.lung; Constr. clasic
psir=new char[lung+1]; Operator = SirPopescu
strcpy(psir, sursa.psir); Operator = SirIon
}
cout<<"Operator = Sir" LISTA DE INITIALIZARE
<<sursa.psir<<endl; Constructor sursa=Ion
return *this; Constructor sursa=Popescu
} Constructor copiere Popescu
Constructor copiere Ion
Constr. lista de initializare
Comportamentul constructorului de copiere şi al metodei care supraîncarcă operatorul de atribuire
în contextul moştenirii

 Pentru a caracteriza constructorii de copiere în contextul moştenirii, trebuie considerate următoarele situaţii:
1. Dacă nici clasele de bază, nici clasa derivată nu au definiţi explicit constructori de copiere, copierea
obiectelor se realizează bit cu bit prin cod sintetizat implicit de către compilator
2. În cazul în care clasa derivată nu are constructor de copiere şi există clase de bază ce au definiţi explicit
constructori de copiere, se sintetizează automat constructor de copiere pentru clasa derivată, ce realizează
copierea bit cu bit a datelor membru specifice clasei derivate şi pentru datele moştenite din clasele de bază
care nu au definiţi explicit constructori de copiere, realizând apelul adecvat pentru constructorii de copiere
ai claselor de bază ce au definiţi explicit astfel de constructori.
3. În cazul în care clasa derivată are definit explicit constructor de copiere şi există clase de bază ce au definiţi
explicit constructori de copiere NU SE PRESUPUNE APELUL AUTOMAT AL CONSTRUCTORILOR DE
COPIERE AI CLASELOR DE BAZĂ. APELUL CONSTRUCTORULUI DE COPIERE AL UNEI CLASE DE
BAZĂ TREBUIE MENŢIONAT EXPLICIT, ALTFEL ELEMENTELE MOŞTENITE DIN CLASELE DE BAZĂ
SE CONSTRUIESC FOLOSIND CONSTRUCTORUL IMPLICIT.

 În cazul metodei care supraîncarcă operatorul de atribuire, atunci când în clasa derivată se supraîncarcă
operatorul de atribuire , trebuie făcut apel explicit la metoda ce supraîncarcă operatorul de atribuire pentru
fiecare din clasele de bază ce au definită o astfel de metodă, altfel NU SE REALIZEAZĂ ATRIBUIRILE
AFERENTE PARŢILOR MOŞTENITE DIN ACELE CLASE.
 Precauţiile menţionate trebuie avute în vedere şi pentru cazul obiectelor înglobate (embedded sau nested).
Exemplul 2
#include <iostream>
using namespace std;
class Parinte {
int i;
public:
Parinte() : i(0) {
cout << "Constr Implic, Parinte\n";
}
Parinte(int ii) : i(ii) {
cout << "Parinte" <<" ii="<<ii<<"\n";
}
Parinte(const Parinte& b) : i(b.i) {
cout << "Constr. copiere Parinte\n";
}
Parinte& operator=(const Parinte& p)
{
i=p.i;return *this;
}
~Parinte() {
cout << "Destructor Parinte()\n";
}
friend ostream& operator<<(ostream& os, const Parinte& b) {
return os << "Parinte: " << b.i << endl;
}
};

class Clasa {
int i;
public:
Clasa() : i(0) {
cout << "Constr Implic, Clasa " <<"\n";
}
Clasa(int ii) : i(ii) {
cout << "Clasa " <<"ii="<<ii<<"\n";
}
Clasa(const Clasa& m) : i(m.i) {
cout << "Constr. copiere Clasa\n";
}
Clasa& operator=(const Clasa& c)
{
i=c.i; return *this;
}

~Clasa() {
cout << "Destructor Clasa()\n";
}
friend ostream& operator<<(ostream& os, const Clasa& m) {
return os << "Clasa: " << m.i << endl;
}
};
class Copil : public Parinte
{
int i;
Clasa m;
public:
Copil()
{
cout<<"Constr Implic, Copil\n";
}
Copil(int ii) : Parinte(ii), i(ii), m(ii)
// Se apeleaza explicit Constr Parinte cu parametri
// Daca nu se defineste explicit se sintetizeaza de catre compilator
// Daca se scrie Copil(int ii) : i(ii), m(ii) NU se initializeaza partea
// mostenita din Parinte ci se apeleaza Constructorul implicit Parinte
// De testat similar apelul constr implicit si pt. Clasa
{
cout << "Copil "<< "ii="<<ii<<"\n";
}

Copil(const Copil& c):Parinte(c),i(c.i),m(c.m)


// Daca nu se defineste explicit se sintetizeaza de catre compilator
// Daca se scrie Copil(const Copil& c):i(c.i),m(c.m){} nu se copie partea
// mostenita din Parinte ci se apeleaza ctor-ul implicit al clasei Parinte
{
cout<<"Constr. copiere Copil\n";
}
~Copil() {
cout << "Destructor Copil()\n";
}
Copil& operator=(const Copil& c)// NU merge:m(c.m)
// Daca nu se defineste explicit se sintetizeaza de catre compilator
{
Parinte:: operator= (c);
m=c.m; //Fara , nu se atribuie corespunzator datele mostenite din Parinte
i=c.i; //Fara , nu se atribuie corespunzator datele din Clasa
return *this;
}
friend ostream& operator<<(ostream& os, const Copil& c){
return os << (Parinte&)c << c.m << "Copil: " << c.i << endl;
}
};
int main(void) {
Copil c0;
Copil c1(2);
cout << "Valori in c1:\n" << c1;
cout << "Apel constr. copiere " << endl;
Copil c2(c1);
cout << "Valori in c2:\n" << c2;
c0=c2;
cout << "Valori in c0:\n" << c0;
return 0;
}
CU : Copil(int ii) : Parinte(ii), i(ii), m(ii) CU :Copil(int ii) : i(ii), m(ii)
SI Parinte:: operator=(c); m=c.m; SI Parinte:: operator=(c); m=c.m;
Constr Implic, Parinte Constr Implic, Parinte
Constr Implic, Clasa Constr Implic, Clasa
Constr Implic, Copil Constr Implic, Copil
Parinte ii=2 Constr Implic, Parinte
Clasa ii=2 Clasa ii=2
Copil ii=2 Copil ii=2
Valori in c1: Valori in c1:
Parinte: 2 Parinte: 0
Clasa: 2 Clasa: 2
Copil: 2 Copil: 2
Apel constr. copiere Apel constr. copiere
Constr. copiere Parinte Constr. copiere Parinte
Constr. copiere Clasa Constr. copiere Clasa
Constr. copiere Copil Constr. copiere Copil
Valori in c2: Valori in c2:
Parinte: 2 Parinte: 0
Clasa: 2 Clasa: 2
Copil: 2 Copil: 2
Valori in c0: Valori in c0:
Parinte: 2 Parinte: 0
Clasa: 2 Clasa: 2
Copil: 2 Copil: 2
Destructor Copil() ...................
Destructor Clasa()
Destructor Parinte() CU : Copil(int ii) : Parinte(ii), i(ii), m(ii)
Destructor Copil() SI FARA Parinte:: operator=(c); m=c.m;
Destructor Clasa()
Destructor Parinte() Constr Implic, Parinte
Destructor Copil() Constr Implic, Clasa
Destructor Clasa() Constr Implic, Copil
Destructor Parinte() Parinte ii=2
Clasa ii=2
Copil ii=2
Valori in c1:
Parinte: 2
Clasa: 2
Copil: 2
Apel constr. copiere
Constr. copiere Parinte
Constr. copiere Clasa
Constr. copiere Copil
Valori in c2:
Parinte: 2
Clasa: 2
Copil: 2
Valori in c0:
Parinte: 0
Clasa: 0
Copil: 2
Exemplul 3
#include <iostream> Joc& operator=(const Joc& j)
using namespace std; {
// Trebuie apelata explicit metoda
class TabladeJoc // care supraincarca op = in
{ // TabladeJoc
public: tj = j.tj;
TabladeJoc() { cout << "Joc::operator=()\n";
cout << "TabladeJoc()\n"; return *this;
} }
TabladeJoc(const TabladeJoc&)
{ ~Joc() { cout << "~Joc()\n"; }
cout << "TabladeJoc(const };
TabladeJoc&)\n";
} class Sah : public Joc {};
TabladeJoc& operator=(const
TabladeJoc&) class Dame : public Joc
{ {
cout << public:
"TabladeJoc::operator=()\n"; Dame()
return *this; {
} cout << "Dame()\n";
~TabladeJoc() { }
cout << "~TabladeJoc()\n";
}
}; Dame(const Dame& c) : Joc(c)
{
class Joc // Trebuie apelat explicit
{ // constructorul de copiere al
TabladeJoc tj; // Compunere // clasei de baza altfel se
public: // apeleaza constructorul implicit:
//Se apeleaza constructorul implicit cout << "Dame(const Dame& c)\n";
// TabladeJoc }
Joc()
{ Dame& operator=(const Dame& c) {
cout << "Joc()\n"; // Trebuie aplelata explicit
} // versiunea din clasa de baza
// pentru operator=() ALTFEL NU SE
// Trebuie apelat explicit constr. de // FAC ATRIBUIRI PENTRU PARTEA
// copiere TabladeJoc, altfel se // MOSTENITA DIN CLASA DE BAZA
// apeleaza constructorul implicit:
Joc::operator=(c);
Joc(const Joc& j) : tj(j.tj) // (Joc&)(*this)=c;
{ cout << "Dame::operator=()\n";
cout << "Joc(const Joc&)\n"; return *this;
} }
~Dame()
Joc(int) {
{ cout << "~Dame()\n";
cout << "Joc(int)\n"; }
} };
int main()
{
Sah s1;
Sah s2(s1);
s1 = s2; // Operator= sintetizat
Dame d1, d2(d1);
d1 = d2;
return 0;
}

TabladeJoc()
Joc()
TabladeJoc(const TabladeJoc&)
Joc(const Joc&)
TabladeJoc::operator=()
Joc::operator=()
TabladeJoc()
Joc()
Dame()
TabladeJoc(const TabladeJoc&)
Joc(const Joc&)
Dame(const Dame& c)
TabladeJoc::operator=()
Joc::operator=()
Dame::operator=()
~Dame()
~Joc()
~TabladeJoc()
~Dame()
~Joc()
~TabladeJoc()
~Joc()
~TabladeJoc()
~Joc()
~TabladeJoc
Derivarea claselor (continuare)

 În cazul moştenirii unei clase, furnizarea unei noi definiţii pentru una din funcţiile membru moştenite din clasa
de bază se poate încadra în una din următoarele două situaţii :
- se furnizează în clasa derivată aceeaşi semnătură şi acelaşi tip returnat, situaţie în care funcţia este
redefinită (redefined) în cazul funcţiilor obişnuite şi, respectiv, supradefintă (overriden) în cazul
funcţiilor virtuale;
- se modifică lista de parametri ai funcţiei sau tipul returnat, situaţie în care funcţia din clasa de bază este
ascunsă (hidden) şi deci inaccesibilă.
 Deşi cea de a doua modalitate de operare nu este greşită, ea nu se încadrează în spiritul procesului de derivare a
claselor, care exploatează în principal polimorfismul.
 În general, ori de cate ori se redefineşte o funcţie supraîncărcată din clasa de bază, toate celelalte
versiuni devin indisponibile.
Exemplul 1:

#include <iostream>
#include <string> class Derived4 : public Base {
using namespace std; public:
// Se schimba lista de parametri:
class Base { // ASCUNDERE!
public: int f(int) const {
int f() const { cout << "Derived4::f()\n";
cout << "Base::f()\n"; return 4;
return 1; }
} };
int f(string) const { return 1; }
void g() {} int main() {
}; string s("hello");
Derived1 d1;
class Derived1 : public Base { int x = d1.f();
public: d1.f(s);
void g() const {} Derived2 d2;
}; x = d2.f();
//d2.f(s);// NU: versiunea string e ascunsa
class Derived2 : public Base { Derived3 d3;
public: d3.f();
// Redefinire: //x = d3.f();// NU: versiunea cu return int
int f() const { // e ascunsa
cout << "Derived2::f()\n"; Derived4 d4;
return 2; //x = d4.f(); // versiunea f()e ascunsa
} x = d4.f(1);
}; }

class Derived3 : public Base { REZULTATE


public: Base::f()
//Se schimba tipul returnat: ASCUNDERE! Derived2::f()
void f() const { cout << Derived3::f()
"Derived3::f()\n"; }
Derived4::f()
};
Polimorfism în C++

 Termenul de polimorfism provine din limba greacă (poly + morphos) şi înseamnă, ad litteram, capacitatea unei
entităţi de a avea mai multe forme
 Pentru a ilustra conceptul de polimorfism se consideră exemplul următor în care se derivează public din clasa de
bază CPolygon clasele CRectangle şi CTriangle

Exemplul 2.a)
class CTriangle: public CPolygon {
#include <iostream> public:
using namespace std; double area ()
{ return (width * height / 2); }
class CPolygon { };
protected:
int width, height; int main () {
public: CRectangle rect;
void set_values (int a, int b) CTriangle trgl;
{ width=a; height=b;} rect.set_values (4,5);
}; trgl.set_values (4,5);
cout << rect.area() << endl;
class CRectangle: public CPolygon { cout << trgl.area() << endl;
public: return 0;
double area () }
{ return (width * height); }
};
 Mecanismul polimorfic se implementează în C++ pe baza proprietăţii pointerilor la o clasă derivată de a fi
compatibili ca tip cu pointerii la clasa de bază din care s-a făcut derivarea. Cu alte cuvinte, unui pointer la
clasa de bază i se poate atribui valoarea unui pointer la o clasă derivată din clasa respectivă
(evident în cazul accesului la datele membru, acesta va fi posibil doar pentru cele moştenite din clasa de bază).
 Inversul situaţiei nu este implicit valabil; aceasta înseamnă că unei variabile de tip pointer, care pointează către o
clasa derivată, nu i se poate atribui ca valoare un pointer către clasa de bază aferentă. Atribuirea este posibilă
doar dacă se fac conversii explicite de tip.

Exemplul de mai sus se poate rescrie:

Exemplul 2.b)

#include <iostream>
using namespace std; int main ()
{
class CPolygon { CRectangle rect;
protected: CTriangle trgl;
int width, height; CPolygon * ppoly1 = &rect;
public: CPolygon * ppoly2 = &trgl;
void set_values (int a, int b)
{ width=a; height=b; } ppoly1->set_values (4,5);
}; ppoly2->set_values (4,5);

class CRectangle: public CPolygon { // ppoly1-> area();// ERONAT


public: // ppoly2-> area();// ERONAT
double area ()
{ return (width * height); } cout << rect.area() << endl;
}; cout << trgl.area() << endl;
return 0;
}
class CTriangle: public CPolygon {
public:
double area ()
{ return (width * height / 2); }
};
 Semnificaţia polimorfismului în C++ este aceea de a furniza o singură interfaţă prin intermediul căreia să fie
disponibile mai multe metode
 Polimorfismul se implementează în C++ prin intermediul funcţiilor virtuale
 O funcţie membru a unei clase, care poate fi definită identic, cu acelaşi prototip, (termenul specific este
supradefinită) în clasele derivate se numeşte funcţie virtuală. Declaraţia unei astfel de funcţii este precedată de
cuvântul cheie virtual.
 Atenţie : termenul pentru funcţiile virtuale este de metodă supradefinită (overridden) şi nu de
supraîncărcată (overloaded), nici redefinită (redefined)
 O metodă virtuală, spre deosebire de o funcţie supraîncărcată, trebuie să aibă acelaşi prototip cu cel al metodei
corespunzătoare din clasa de bază
 Metoda supradefinită ca virtuală este mai departe virtuală şi în clasele derivate ulterior. Implicit deci,
caracterul virtual al unei metode se transmite şi metodelor corespunzătoare din clasele derivate.
 Dacă la definirea unei funcţii virtuale în clasele derivate se schimbă fie numărul, fie tipul parametrilor, ea devine
o metodă nouă, obișnuită, care ascunde celelalte implementări cu același nume din clasa de bază şi natura sa
virtuală se pierde.
 Funcţiile virtuale trebuie să fie funcţii membre ale unei clase, ceea ce nu este absolut necesar în cazul funcţiilor
redefinite prin supraîncărcare.
 Atunci când se implementează o ierarhie de clase folosind mecanismul moştenirii, funcţiile virtuale se supun
următoarei reguli : dacă o clasă derivată nu supradefineşte o funcţie virtuală, este folosită
implementarea definită în clasa de bază.
 La apelul unei metode virtuale se va executa o funcţie ce depinde de tipul obiectului care a invocat metoda
 Mecanismul polimorfic funcţionează numai în situaţiile în care se face apel la metode virtuale
prin intermediul unor pointeri la clasele de bază.
 Constructorii nu pot fi metode virtuale.
 Clasele unei ierarhii exploatate prin pointeri la clasa de bază trebuie să aibă destructorul clasei de bază
virtual.
 Destructorul unei clase de bază care conţine cel puţin o metodă virtuală trebuie să fie obligatoriu
virtual.
 Având în vedere semnificaţia mecanismului de moştenire ca implementare a relaţiei “un fel de” (kind of sau is a),
un obiect al unei clase derivate poate fi folosit drept obiect al clasei de bază (evident nu şi invers).
 Mecanismul polimorfic se pune în evidenţă şi în cazul funcţiilor ce au drept parametri obiecte ale
unei ierarhii de clase, dar numai în situaţiile în care parametrii formali corespunzători sunt
pointeri la clasele de bază ale ierarhiilor sau referinţe la clasele de bază.
Exemplul de mai sus se poate rescrie:

Exemplul 2.c)

#include <iostream>
using namespace std; int main () {
CRectangle rect;
class CPolygon { CTriangle trgl;
protected: CPolygon poly;
int width, height;
public: CPolygon * ppoly1 = &rect;
void set_values (int a, int b) CPolygon * ppoly2 = &trgl;
{ width=a; height=b; } CPolygon * ppoly3 = &poly;
virtual double area ()
{ return (0); } ppoly1->set_values (4,5);
}; ppoly2->set_values (4,5);
ppoly3->set_values (4,5);
class CRectangle: public CPolygon {
public: cout << ppoly1->area() << endl;
double area ()// merge si fara virtual! cout << ppoly2->area() << endl;
{ return (width * height); } cout << ppoly3->area() << endl;
}; return 0;
}
class CTriangle: public CPolygon {
public:
double area ()
{ return (width * height / 2); }
};
 Exemplul 3. Destructorii claselor de bază care exploatează obiecte din clase derivate prin intermediul
pointerilor la clasa de bază trebuie să fie metode virtuale

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


using namespace std; using namespace std;

class Base class Base


{ {
public: public:
Base(){ cout<<"Constructor: Base(){ cout<<"Constructor:
Base"<<endl;} Base"<<endl;}
~Base(){ cout<<"Destructor : virtual ~Base(){
Base"<<endl;} cout<<"Destructor : Base"<<endl;}
}; };

class Derived: public Base class Derived: public Base


{ {
public: public:
Derived(){ cout<<"Constructor: Derived(){ cout<<"Constructor:
Derived"<<endl;} Derived"<<endl;}
~Derived(){ cout<<"Destructor : ~Derived(){ cout<<"Destructor :
Derived"<<endl;} Derived"<<endl;}
}; };

int main(void) int main(void)


{ {
Base *Var = new Derived; Base *Var = new Derived;
delete Var; delete Var;
return 0; return 0;
} }

Constructor: Base Constructor: Base


Constructor: Derived Constructor: Derived
Destructor : Base Destructor : Derived
Destructor : Base
 În cazul în care o funcţie virtuală definită într-o clasă de bază nu realizează nici o acţiune semnificativă, ea fiind
prezentă doar pentru a permite implementarea polimorfismului, se poate folosi conceptul de funcţie pur
virtuală.
 O astfel de funcţie nu este definită în clasa de bază, aceasta conţinând doar prototipul ei.
 Sintaxa generală de declarare a unei funcţii pur virtuale este:
virtual tip nume_functie (lista_de_parametri) = 0;
 Iniţializarea cu zero a acestei funcţii spune compilatorului că în clasa de bază pentru această funcţie nu există nici
o definiţie (un corp).
 Nu se pot declara destructori pur virtuali. Chiar dacă un destructor virtual se declară ca pur virtual el trebuie să
aibă un corp vid.
 O funcţie pur virtuală trebuie obligatoriu supradefinită în clasele derivate.
 Dacă o clasă conţine cel puţin o funcţie pur virtuală, ea se numeşte clasă abstractă .
 Întrucât o clasă abstractă conţine cel puţin o funcţie virtuală care nu prezintă corp propriu, ea este incompletă şi,
în consecinţă, nu se pot crea obiecte din acea clasă. Prin urmare, clasele abstracte există doar pentru crea baza
ierarhiei de clase .

Exemplul 2.d)
#include <iostream> class CTriangle: public CPolygon {
using namespace std; public:
double area ()
class CPolygon { { return (width * height / 2); }
protected: };
int width, height; int main () {
public: CRectangle rect;
void set_values (int a, int b) CTriangle trgl;
{ width=a; height=b; } // CPolygon poly;
virtual double area()=0; // cannot instantiate abstract class
}; CPolygon * ppoly1 = &rect;
CPolygon * ppoly2 = &trgl;
class CRectangle: public CPolygon { ppoly1->set_values (4,5);
public: ppoly2->set_values (4,5);
double area () cout << ppoly1->area() << endl;
{ return (width * height); } cout << ppoly2->area() << endl;
}; return 0;
}
 Polimorfismul se poate pune în evidenţă şi în cazul funcţiilor ce au drept argumente pointeri sau referinţe la clasa
de bază

Exemplul 4. Transferul parametrilor


#include <iostream> void FFunc2(const ObjFuncVirtual &F){
using namespace std; F.Func();
class ObjFuncVirtual }
{ void FFunc3(ObjFuncVirtual *PF){
public : PF->Func();
virtual void Func(void) const {cout }
<< "Base" << endl;} int main(void)
}; {
class ObjFuncDerived1 : public ObjFuncDerived1 Ob1;
ObjFuncVirtual ObjFuncDerived2 Ob2;
{ ObjFuncDerived3 Ob3;
public: FFunc1(Ob1);
virtual void Func(void) const { FFunc1(Ob2);
cout << "Func1" << endl;} FFunc1(Ob3);
}; FFunc2(Ob1);
class ObjFuncDerived2 : public FFunc2(Ob2);
ObjFuncVirtual FFunc2(Ob3);
{ FFunc3(&Ob1);
public: FFunc3(&Ob2);
virtual void Func(void) const { FFunc3(&Ob3);
cout << "Func2" << endl;} return 0;
}; }
class ObjFuncDerived3 : public REZULTATE
ObjFuncVirtual Base
{ Base
public: Base
virtual void Func(void) const { Func1
cout << "Func3" << endl;} Func2
}; Func3
void FFunc1(ObjFuncVirtual F){ Func1
F.Func(); Func2
} Func3
Exemplul 5. Definirea în clasa derivată a unei funcţii cu acelaşi nume cu cel al unei funcţii din clasa de bază, dar
cu un număr diferit de parametri sau cu tip returnat diferit ascunde funcţia din clasa de bază!

class Base
{
public:
void f(double x) // Nu conteaza daca e virtuala sau nu
;
...
};

class Derived : public Base


{
public:
void f(char c); // Nu conteaza daca e virtuala sau nu
...

};
...

int main()
{
Derived* d = new Derived();
Base* b = d;
b->f(65.3); // OK: paseaza 65.3 catre f(double x)
d->f(65.3); // Bizar: converteste 65.3 la char ('A' daca e ASCII) si apleaza
// f(char c); NU apeleaza f(double x) care e ascunsa!!!
//warning C4244: 'argument' : conversion from 'double' to 'char', possible loss of data 

delete d;
return 0;

}
Mecanismul de implementare a polimorfismului

 Rezolvarea apelului de funcţie virtuală (conexiunea dintre apel şi adresa funcţiei care se execută ) se realizează în
timpul execuţiei programului şi nu la compilare aşa cum se întâmplă la celelalte funcţii
 În cazul funcţiilor nevirtuale, rezolvarea apelului se face în faza de compilare printr-un mecanism care se
numeşte early binding (legare timpurie, iniţială, statică)
 În cazul funcţiilor virtuale, rezolvarea apelului se face în faza de execuţie printr-un mecanism care se numeşte
late binding (legare târzie, ulterioară, dinamică)
 Concret, pentru fiecare clasă ce conţine o metodă virtuală se construieşte un tablou cu numele VTABLE
(denumiri alternative : “virtual function table”, “virtual method table” sau “dispatch table”) ce conţine adresele
metodelor virtuale ale clasei respective
 Fiecare clasă derivată îşi are propriul sau tablou VTABLE în care sunt înscrise, în aceeaşi ordine cu cea din clasa
de bază, adresele metodelor virtuale. Dacă o clasă derivată nu supradefineşte o metodă din clasa de bază, în tabel
se înscrie adresa metodei clasei de bază
 La instanţierea unui obiect al ierarhiei de clase se inserează, transparent faţă de utilizator, un pointer către
tabloul VTABLE specific clasei căreia îi aparţine obiectul şi cunoscând deplasamentul (decalajul faţă de începutul
tabelului) metodei virtuale apelate se poate obţine dinamic adresa funcţiei care trebuie executată

Transferul parametrilor în contextul moştenirii

 Un obiect al unei clase derivate poate fi tratat şi ca un obiect aparţinând clasei de bază
 Utilizarea adresei unui obiect (fie ca pointer, fie ca referinţă) şi tratarea acesteia ca fiind adresa tipului
corespunzător clasei de bază se numeşte upcasting
 Dacă transferul parametrilor se face prin referinţă sau se utilizează pointeri către clasa de bază, se realizează un
upcast al parametrilor şi se pot utiliza proprietăţile ce derivă din polimorfism
 Dacă transferul parametrilor se realizează prin valoare, atunci, dacă parametrul formal este de tipul clasei de
bază, iar parametrul efectiv este de tipul clasei derivate, nu mai are loc procesul de upcasting, obiectul clasei
derivate este ”trunchiat” (sliced) şi se pierd componentele specifice clasei derivate
 Evitarea unei astfel de situaţii se poate face construind clasa de bază ca abstractă
Exemplul. 6 - Fără metode virtuale int main(void)
#include <iostream> {
using namespace std; Paralelogram par;
Dreptunghi dre;
class Paralelogram Patrat pat;
{ Paralelogram tab[]={par, dre, pat};
public: // se apel. Constr. Cop Paralelog.
Paralelogram(){ cout<<"Constr. Paral\n";} Paralelogram *tabp[]={&par, &dre, &pat};
void afis() const { cout<< "Afis tab\n";
cout << "Paralelogram::afis" << endl; for(int i=0;i<3;i++)
} tab[i].afis();
~Paralelogram(){cout<<"Destr. Paral\n";} cout<< "Afis ptab\n";
}; for(i=0;i<3;i++)
tabp[i]->afis();
class Dreptunghi : public Paralelogram { cout<<"Display ...\n";
public: display(par);
Dreptunghi(){ cout<<"Constr. display(dre);
Dreptunghi\n";} display(pat);
void afis() const { return 0;
cout << "Dreptunghi::afis" << endl; }
}
~Dreptunghi(){cout<<"Destr. Drept\n";} Constr. Paral
}; Constr. Paral Constr. Dreptunghi
Constr. Paral Constr. Dreptunghi Constr. Patrat
class Patrat : public Dreptunghi { Afis tab
public: Paralelogram::afis Paralelogram::afis Paralelogram::afis
Patrat(){cout<<"Constr. Patrat\n";}
void afis() const {
Afis ptab
cout << "Patrat::afis" << endl; Paralelogram::afis Paralelogram::afis Paralelogram::afis
} Display ...
~Patrat(){cout<<"Destr. Patrat\n";} Paralelogram::afis Paralelogram::afis Paralelogram::afis
}; Destr. Paral
Destr. Paral
void display(Paralelogram& p) { Destr. Paral
p.afis(); Destr. Patrat Destr. Drept Destr. Paral
} Destr. Drept Destr. Paral
Destr. Paral
Exemplul 4. a) Cu metode virtuale şi destructori virtuali
#include <iostream> int main(void)
using namespace std; {
class Paralelogram Paralelogram par; Paralelogram *p=
{ new Paralelogram;
public: Dreptunghi dre; Paralelogram *pd =
Paralelogram(){ cout<<"Constr. new Dreptunghi;
Paral\n";} Patrat pat; Paralelogram *pp=
virtual void afis() const { new Patrat;
cout << "Paralelogram::afis" << Paralelogram *tabp[]= {p,pd,pp};
endl; cout<< "Afis ptab\n";
} for(int i=0;i<3;i++)
virtual ~Paralelogram(){cout<<"Destr. tabp[i]->afis();
Paral\n";} cout<<"Display ...\n";
}; display(par);display(dre);display(pat);
class Dreptunghi : public Paralelogram { for(i=0;i<3;i++)
public: delete tabp[i];
Dreptunghi(){ cout<<"Constr. return 0;
Dreptunghi\n";} }
virtual void afis() const
REZULTATE
{
cout << "Dreptunghi::afis" << endl; Constr. Paral
} Constr. Paral
virtual ~Dreptunghi(){cout<< Constr. Paral Constr. Dreptunghi
"Destr. Drept\n";} Constr. Paral Constr. Dreptunghi
}; Constr. Paral Constr. Dreptunghi Constr. Patrat
Constr. Paral Constr. Dreptunghi Constr. Patrat
class Patrat : public Dreptunghi { Afis ptab
public: Paralelogram::afis
Patrat(){cout<<"Constr. Patrat\n";}
Dreptunghi::afis
virtual void afis() const
{ Patrat::afis
cout << "Patrat::afis" << endl; Display ...
} Paralelogram::afis
virtual ~Patrat(); Dreptunghi::afis
// Numai in prototip! Patrat::afis
}; Destr. Paral
Destr. Drept Destr. Paral
Patrat::~Patrat(){ Destr. Patrat Destr. Drept Destr. Paral
cout<<"Destr. Patrat\n";} Destr. Patrat Destr. Drept Destr. Paral
void display(Paralelogram& p)
Destr. Drept Destr. Paral
{ Destr. Paral
p.afis();}
b) Cu metode virtuale şi destructori Nevirtuali
Constr. Paral Display ...
Constr. Paral Paralelogram::afis
Constr. Paral Constr. Dreptunghi Dreptunghi::afis
Constr. Paral Constr. Dreptunghi Patrat::afis
Constr. Paral Constr. Dreptunghi Constr. Patrat Destr. Paral
Constr. Paral Constr. Dreptunghi Constr. Patrat Destr. Paral
Afis ptab Destr. Paral
Paralelogram::afis Destr. Patrat Destr. Drept Destr. Paral
Dreptunghi::afis Destr. Drept Destr. Paral
Patrat::afis Destr. Paral
Exemplul 5

#include <string>
#include <iostream> class Caine: public Animal
class Animal {
{ public:
protected: Caine(std::string strNume) : Animal(strNume)
std::string m_strNume; {
}
// ATTN: constructorul este protected !
// Nu se permite crearea de obiecte de tip Animal direct, virtual const char* Glasuieste()
// Doar clasele derivate il pot utiliza! // const char* Glasuieste()
Animal(std::string strNume) : m_strNume(strNume) {
{ return "Hau Hau";
} }
};
public: // Obs. Animal::GetNume() nu e virtuala.
std::string GetNume() { return m_strNume; } // Nu e necesat pentru ca GetNume()
// virtual const char* Glasuieste() // nu e supradefinita de nici una din clasele derivate
// const char* Glasuieste()
// { void Raport(Animal rAnimal)
// return "???"; //void Raport(Animal &rAnimal)
// } {
virtual const char* Glasuieste() =0; // Atunci cand functia Glasuieste este virtuala
}; // functia Raport opereaza corect!
std::cout << rAnimal.GetNume() << " face ... " <<
class Pisica: public Animal rAnimal.Glasuieste() << std::endl;
{ }
public:
Pisica(std::string strNume) : Animal(strNume) int main()
{ {
} Pisica cPisica("Fifi");
virtual const char* Glasuieste() Caine cCaine("Gigi");
// const char* Glasuieste()
{ Raport(cPisica);
return "Miau Miau"; Raport(cCaine);
} return 0;
}; }
Exemplul 6. Exploatarea polimorfismului și late binding
virtual void puneData()
#define MAX 100 {
#include <iostream> Persoana::puneData();
#include <string.h> cout << " Publicatii=" << numPub << endl;
}
using namespace std;
class Persoana virtual ~Prof() {
{ cout << "Destructor Prof\n";}
protected: };
char* ptrNume;
public:
Persoana(char* pn=NULL) class Student : public Persoana
{ {
int lung = strlen(pn); private:
ptrNume = new char[lung+1]; char* ptrTitlu;
strcpy(ptrNume, pn); public:
} Student(char* n, char* t) :
virtual ~Persoana() =0; Persoana(n), ptrTitlu(NULL)
virtual void puneData() {
{ int lung = strlen(t);
cout << "\nNume = " << ptrNume; ptrTitlu = new char[lung+1];
} strcpy(ptrTitlu, t);
}; }
virtual ~Student()
Persoana:: ~Persoana() {
{ cout << "Destructor Student\n";
cout << "Destructor Persoana\n"; if(ptrTitlu != NULL)
if(ptrNume != NULL) delete[] ptrTitlu;
delete[] ptrNume; }
} virtual void puneData()
{
class Prof : public Persoana Persoana::puneData();
{ cout << " Subiectul Tezei = " << ptrTitlu<<endl;
private: }
int numPub; };
public:
Prof(char* n, int p) :
Persoana(n), numPub(p) {}
int main(void)
{
int j, nr;
Persoana* ptrPers[MAX];
char nume[40];
char titlu[80];
int n = 0;
char aleg;
do
{
cout << "Numele: ";
cin >>nume;
cout << "Student sau profesor(s/p): ";
cin >> aleg;
if(aleg=='s')
{
cout<<"Titlul tezei :";
cin>> titlu;
ptrPers[n] = new Student(nume, titlu);
n++;
}
else if(aleg=='p')
{
cout<<"Nr. publicatiilor:";
cin>>nr;
ptrPers[n] = new Prof(nume, nr);
n++;
}
cout << "Continuati (d/n)? ";
cin >> aleg;
} while( aleg=='d' && n<MAX);
for(j=0; j<n; j++)
ptrPers[j]->puneData();
for(j=0; j<n; j++)
delete ptrPers[j];
return 0;
}
Clase de bază virtuale. Moştenire virtuală.

 În cazul moştenirii multiple există situaţii în care o clasă de bază poate fi moştenită indirect prin intermediul
claselor ce au o aceeaşi clasă de bază, aşa cum se sugerează în figura de mai jos :

 Pentru a înlătura posibilele ambiguităţi generate de astfel de situaţii se foloseşte noţiunea de clasă de bază
virtuală şi respectiv de moştenire virtuală
 Sintaxa declarării moştenirii virtuale este:

class B;
class B1 : virtual public B { …….};
class B2 : virtual public B { …….};

class Deriv: public B1, public B2, ..., public Bn { …….};

În acest mod, în clasa Deriv se va găsi o singură parte corespunzătoare moştenirii din clasa B. În absenţa
modificatorului de acces virtual, în clasa Deriv ar fi existat n părţi identice, cu componente moştenite din clasa B,
pe filierele B1, B2 … Bn.
Exemplul 7. Moștenire virtuală Horse::Horse(COLOR color, HANDS height, int age):
Animal(age),itsColor(color),itsHeight(height)
#include <iostream> {
using namespace std; cout << "Horse constructor...\n";
typedef int HANDS; }
enum COLOR { Red, Green, Blue, Yellow, White, Black, Brown } ;
//class Bird : virtual public Animal
class Animal // common base to both horse and bird class Bird : public Animal
{ {
public: public:
Animal(int); Bird(COLOR color, bool migrates, int age);
virtual ~Animal() { cout << "Animal destructor...\n"; } virtual ~Bird() {cout << "Bird destructor...\n"; }
virtual int GetAge() const { return itsAge; } virtual void Chirp()const { cout << "Chirp... "; }
virtual void SetAge(int age) { itsAge = age; } virtual void Fly()const
private: { cout << "I can fly! I can fly! I can fly! "; }
int itsAge; virtual COLOR GetColor()const { return itsColor; }
}; virtual bool GetMigration() const { return itsMigration; }
protected:
Animal::Animal(int age):itsAge(age) COLOR itsColor;
{ bool itsMigration;
cout << "Animal constructor...\n"; };
}
Bird::Bird(COLOR color, bool migrates, int age):
//class Horse : virtual public Animal Animal(age),itsColor(color), itsMigration(migrates)
class Horse : public Animal {
{ cout << "Bird constructor...\n";
public: }
Horse(COLOR color, HANDS height, int age);
virtual ~Horse() { cout << "Horse destructor...\n"; } class Pegasus : public Horse, public Bird
virtual void Whinny()const { cout << "Whinny!... "; } {
virtual HANDS GetHeight() const { return itsHeight; } public:
virtual COLOR GetColor() const { return itsColor; } void Chirp()const { Whinny(); }
protected: Pegasus(COLOR, HANDS, bool, long, int);
HANDS itsHeight; ~Pegasus() {cout << "Pegasus destructor...\n";}
COLOR itsColor; virtual long GetNumberBelievers() const
}; { return itsNumberBelievers; }
virtual COLOR GetColor()const { return Horse::itsColor; }
private:
long itsNumberBelievers;
};
Pegasus::Pegasus( COLOR aColor,
HANDS height, bool migrates,
long NumBelieve, int age):
Horse(aColor, height,age),
Bird(aColor, migrates,age),
//Animal(age*2),
itsNumberBelievers(NumBelieve)
{
cout << "Pegasus constructor...\n";
}

int main()
{
Pegasus *pPeg = new Pegasus(Red, 5, true, 10, 2);
int age = pPeg->GetAge();
cout << "This pegasus is " << age << " years old.\n";
delete pPeg;
return 0;
}
Concluzii:

 Constructorii nu pot fi funcţii virtuale


 Destructorii pot fi funcţii virtuale
 Funcţiile virtuale sunt funcţii membru nestatice
 În principiu, dacă se urmăreşte late binding-ul, funcţiile virtuale nu pot fi inline
Streamuri de intrare/ ieşire
 Operaţiile de intrare-ieşire în C++ sunt implementate prin intermediul unor obiecte ce sunt instanţieri ale
unei ierarhii de clase prezentată în Figura 1.

Figura 1. Ierarhia de clase ce formează biblioteca I/O în C++

 Obiectele care sunt instanţieri ale claselor ierarhiei ce are drept clasă de bază clasa ios se numesc
streamuri
 Un stream poate fi privit, la modul abstract, ca un flux de date, mai precis, ca o succesiune de biţi de
lungime infinită folosită ca buffer pentru a stoca date ce urmează a fi procesate. Există astfel streamuri de
intrare, streamuri de ieşire şi streamuri de intrare/ieşire, acestea din urmă asociate unor dispozitive
capabile să fie simultan surse de intrare şi de ieşire ( ca de exemplu fişierele)
 Clasa streambuf - Clasa abstractă streambuf este responsabilă de gestiunea buffer-ului utilizat pentru
crearea unei implementări eficiente a streamurilor. Prin intermediul metodelor pur virtuale clasa
streambuf furnizează cadrul de implementare a comunicării cu dispozitivele fizice asociate streamurilor
(fişiere pe diverse suporturi, consolă: monitor – tastatură). Stream-urile gestionează mereu un pointer la
obiectul asociat de un tip derivat din streambuf.
 Un streambuf poate fi privit ca o secvenţă de caractere ce poate creşte sau descreşte în concordanţă cu
cerinţele aplicaţiei. În funcţie de tipul stream-ului se asociază unul sau doi pointeri :
- un pointer put ce indică poziţia următorului caracter ce se va depune în secvenţă ca urmare a unei
operaţii de inserţie;
- un pointer get ce indică poziţia următorului caracter ce va fi extras din secvenţă ca urmare a unei
operaţii de extracţie;
 De exemplu, ostream are doar un pointer put, istream are doar un pointer get în timp ce iostream are
ambii pointeri.
 Atunci când se creează un stream, acestuia i se asociază un pointer la un obiect de tip streambuf. Ca
urmare, clasele de tip stream furnizează constructori ce au argumente de tip streambuf *.
 Toate clasele de tip stream supraîncarcă operatorii de inserţie şi respectiv de extracţie pentru a opera
asupra obiectelor de tip streambuf*.
 Clasa filebuf - este derivată din clasa streambuf şi adaugă funcţionalităţile necesare pentru
comunicarea cu fişiere.
 Clasa strstreambuf - este derivată din clasa streambuf şi adaugă funcţionalităţile necesare pentru
citirea şi scrierea caracterelor din, respectiv într-un buffer de tip şir de caractere. Citirea şi scrierea se pot
face aleatoriu în interiorul buffer-ului. Se implementează de asemenea operaţii de căutare. Zona rezervată
de un obiect strstreambuf poate fi de dimensiune fixă sau dinamică. În general stream-urile de intrare sunt
de dimensiune fixă, iar cele de ieşire, putând avea dimensiune imprevizibilă, pot fi şi dinamice. Diferenţa
faţă de fstreambuf este aceea că, în cazul unui obiect strstreambuf nu există o sursă sau destinaţie reală
pentru operaţiile de scriere, respectiv citire, astfel încât chiar buffer-ul preia unul sau ambele roluri.
 Clasa ios - este o clasă abstractă utilizată pentru a grupa o serie de funcţionalităţi comune, necesare
pentru clasele derivate. Nu s-a intenţionat crearea de obiecte de tipul ios. Clasa gestionează informaţii
despre starea stream-ului : flag-uri de eroare, flag-uri şi valori de formatare, precum şi conexiunea cu
buffer-ele utilizate la intrare/ieşire (pointerul către structura de informaţii a buffer-ului).
Operaţii de I/O standard
 Funcţionalităţile legate de operaţiile de I/O standard sunt furnizate prin intermediul bibliotecii iostream,
parte componentă a bibliotecii standard a limbajului C++ .
 Obiectele şi funcţiile bibliotecii sunt incluse în spaţiului de nume (namespace) std. Aceasta înseamnă fie
că toate obiectele şi funcţiile de I/O trebuie prefixate cu “std::”, fie se foloseşte instrucţiunea “using
namespace std;” , fie se include varianta mai veche a fişierului header “#include <iostream.h>”.
 Ierarhia de clase responsabile cu operaţiile de intrare/ieşire standard este prezentată mai jos (a se observa
folosirea moştenirii multiple):

Figura 2. Ierarhia de clase pentru operaţii I/O standard

 Clasele care au terminaţia “_withassign” au definită supraîncărcarea operatorului de atribuire, permiţând


astfel atribuirile între streamuri (redirectări).
Streamuri standard în C++

 Un stream standard este un stream pre-conectat furnizat programului de către mediul de programare
pentru comunicarea cu consola
 Există patru astfel de streamuri

1. cin — un obiect de tip istream_withassign conectat cu intrarea standard (tipic tastatura)


2. cout — un obiect de tip ostream_withassign conectat cu ieşirea standard (tipic monitorul)
3. cerr — un obiect de tip ostream_withassign conectat cu ieşirea standard pentru erori (tipic
monitorul), furnizând ieşire ne-bufferată
4. clog — un obiect de tip ostream_withassign conectat cu ieşirea standard pentru erori (tipic
monitorul), furnizând ieşire bufferată

 Operatorul de inserţie (<<) precum şi cel de extracţie (>>) sunt supraîncărcaţi pentru tipurile de date
standard
 Deoarece la supraîncărcare se returnează o referinţă la stream, operatorii de inserţie şi de extracţie se pot
aplica înlănţuiţi (evident, pentru fiecare tip de operaţie în parte)
 Operatorul de extracţie operează cu date “formatate”, ignorând spaţiile albe (blank, tabulatori şi linie
nouă).
 Un avantaj important al limbajului C++ este acela că permite supraîncărcarea operatorilor de inserţie şi
extracţie pentru tipuri abstracte de date
 Este posibilă formatarea intrărilor şi/sau a ieşirilor prin intermediul indicatorilor de format (format flags)
 Indicatorii de format sunt constante aparţinând unui tip enumerate definit în clasa ios. Aceştia reprezintă
biţi între-un dublu cuvânt x_flag ce este dată membru a clasei ios
class ios
{
public:
enum io_state { goodbit = 0, eofbit = 01, failbit = 02, badbit = 04,
hardfail = 08 };
enum open_mode { in = 01, out = 02, ate = 04, app = 010, trunc = 020,
nocreate = 040, noreplace = 0100 };
enum seek_dir { beg = 0, cur = 01, end = 02 };
enum { skipws = 01, left = 02, right = 04, internal = 010,
dec = 020, oct = 040, hex = 0100, showbase = 0200,
showpoint = 0400, uppercase = 01000, showpos = 02000,
scientific = 04000, fixed = 010000,
unitbuf = 020000, stdio = 040000 };

static const long basefield;


static const long adjustfield;
static const long floatfield;
ios(streambuf *);
virtual ~ios();
inline int bad() const;
static long bitalloc();
inline void clear(int state = 0);
inline int eof() const;
inline int fail() const;
inline char fill() const;
char fill(char);
inline long flags() const;
long flags(long);
inline int good() const;
long& iword(int);
inline int operator!();
inline operator void *();
inline int precision() const;
int precision(int);
void *& pword(int);
inline streambuf *rdbuf();
inline int rdstate() const;
long setf(long setbits, long field);
long setf(long);
static void sync_with_stdio();
inline ostream *tie() const;
ostream *tie(ostream *);
long unsetf(long);
inline int width() const;
int width(int n);
static int xalloc();

protected:
ios();
void init(streambuf *);
inline void setstate(int state);

};
Figura 2. O schiţă a definiţiei clasei ios
 În detaliu, semnificaţia indicatorilor de formatare este:

public:
………
enum {
skipws // skips whitspace on input
left // left justification
right // right justifiction
internal // pads after sign or base character
dec // decimal format for integers
oct // octal format for integers
hex // hex format for integers
showbase // show the base character for octal or hex
showpoint // show the decimal point for all floats
uppercase // uppercase A-F for hex
showpos // show + sign for numbers
scientific // use exponential notation
fixed // used oridnary decimal notation
unitbuf // flush the buffer
stdio // Flush the C stdio streams stdout and stderr after each output // operation (for programs
// that mix C and C++ output conventions).
};

 Există de asemenea grupe / câmpuri de indicatori

ios::basefield = ios::dec | ios::oct | ios::hex


ios::adjustfield = ios::left | ios::right | ios::internal
ios::floatfield = ios::scientific | ios::fixed

 Setările biţilor se pot face cu funcţii membru ale claselor ierarhiei


 Atunci când se setează un indicator care este parte a unui câmp (adjustfield, basefield sau floatfield), trebuie să
existe garanţia faptului că ceilalţi indicatori sunt resetaţi. Numai un singur indicator din grup poate fi setat la un
moment dat.
 Controlul indicatorilor de format se poate face şi cu ajutorul unor aşa-numiţi manipulatori ce sunt funcţii
membru speciale ce returnează referinţă la stream şi deci se pot insera direct în stream. Pentru utilizarea
manipulatorilor trebuie inclus fişierul header iomanip
 Un stream are asociată o stare de eroare care se defineşte cu ajutorul unor constante ale tipului enumerat
io_state definit în clasa ios.
 io_state reprezintă o colecţie de biţi (flag-uri) ce descriu starea internă a unui obiect de tip stream
 În detaliu, semnificaţia flag-urilor este:

class ios {
……….
public :
enum io_state {
goodbit , // Semnalează faptul că nu s-au produs erori.
eofbit , // Semnalează că s-a întâlnit end-of-file pe parcursul operaţiei de extracţie
failbit , // Semnalează că extracţia sau conversia au eşuat, dar stream-ul este încă utilizabil
badbit , // Semnalează că s-a produs o eroare severă, uzual într-o operaţie asupra obiectului
// asociat de tip streambuf, recuperarea din eroare fiind puţin probabilă
hardfail // Semnalează producerea unei erori din care streamul nu se poate recupera
};

 Accesul la biţii de eroare se face prin intermediul unor funcţii membru


eof() true dacă eofbit e setat, false altfel
fail() true dacă failbit sau hardfail sunt setate, false altfel
bad() true dacă badbit este setat, false altfel
good() true dacă goodbit estet setat, false altfel
clear() şterge toate flagurile (fără argumente) sau şterge un flag specificat

 Un stream intrat într-o stare de eroare nu mai permite operaţii de intrare/ieşire până când condiţia de eroare
este înlăturată şi biţii de eroare sunt şterşi
 Un stream poate fi testat pentru a stabili dacă se află sau nu într-o stare de eroare într-o manieră similară
testării unei expresii logice.
 Acest lucru este posibil datorită faptului că în clasa ios este supraîncărcat operatorul de negare ! (! stream este
diferit de zero dacă cel puţin unul din biţii de eroare asociaţi stării streamului stream este setat.
 De asemenea este definită conversia unui stream într-un pointer spre void . Pointerul rezultat este nul dacă cel
puţin unul din biţii de eroare ai stării este setat. Pointerul respective se poate folosi numai pentru testarea stării
streamului.
Indicatori de format ios (formatting flags)
Indicator Semnificaţie Grupa
skipws ignoră spaţiile albe (whitespace) în intrare.
left cadrare stânga \
right cadrare dreapta - adjustfield
internal utilizează spaţii între semn sau bază şi numar /
dec zecimal. \
oct octal. - basefield
hex hexazecimal. /
showbase indicator de bază în ieşire (0 octal, 0x hexa).
showpoint afişează punctul zecimal.
uppercase utilizează majuscule X, E, şi cifre hexa ABCDEF (implicit, litere mici).
showpos ‘+’ înaintea nr. pozitive
scientific format exponential - floatfield
fixed format virgulă fixă /
unitbuf goleşte toate streamurile după inserţie
stdio goleşte stdout, stderror după inserţie
Functii membru ios
Functie Semnificatie
fill() returnează caracterul de umplere
fill(ch) setează caracterul de umplere (returnează vechiul caracter)
precision() returnează precizia (numarul de cifre afişate la partea fractionara).
precision(p) setează precizia
width() returnează lungimea campului (în caractere).
width(w) setează lungimea câmpului (în caractere).
setf(ind) setează indicatorii specificaţi.
unsetf(ind) resetează indicatorii specificaţi.
setf(ind, grupa) şterge câmpul specificat (grupa) şi apoi setează indicatorii

Manipulatori ios
Manipulator Semnificatie
ws setează ignorarea spaţiilor albe (whitespace) în intrare.
dec zecimal
oct octal
hex hexazecimal.
endl inserează linie noua şi goleşte stream-ul (de ieşire)
ends inserează caracterul nul pentru a încheia un şir de ieşire
flush goleşte stream-ul de ieşire

Manipulator Argument Semnificaţie


setw lăţimea câmpului (int) Setează lungimea câmpului
setfill caracter de umplere (int) Setează caracterul de umplere
(implicit este spaţiu).
setprecision precizie (int) Setează precizia (nr. de cifre afişate
la partea fracţionară).
setiosflags indicatori de format(long) Setează indicatorii specificaţi.
resetiosflags indicatori de format (long) Resetează indicatorii specificaţi
Functii istream

Functie Semnificaţie
>> extracţie pentru tipurile de bază şi pentru cele ce l-au
supraîncărcat
get() returnează caracterul extras din stream sau EOF
get(ch); extrage un caracter în ch, returnează referinţă la stream .
get(str) extrage caractere în vectorul str, până la ‘\0’,
returnează referinţă la stream.
get(str, MAX) extrage până la MAX caractere în vectorul str,
returnează referinţă la stream.
get(str, DELIM) extrage caractere în str până la delimitatorul specificat
(implicit ‘\n’); lasă caracterul DELIM în stream,
returnează referinţă la stream.
get(str, MAX, DELIM) extrage caractere până la MAX caractere în str sau până la
characterul DELIM ; lasa caracterul DELIM în stream,
returnează referinţă la stream.
getline(str, MAX, DELIM) extrage până la MAX caractere din str sau până la caracterul
DELIM (elimina caracterul DELIM din stream),
returnează referinta la stream.
putback(ch) inserează ultimul caracter citit inapoi în streamul de intrare
ignore(MAX, DELIM) extrage şi ignora până la MAX caractere sau până la
delimitatorul specificat (inclusiv), implicit ‘\n’)
peek(ch) citeste un caracter, lasandu-l în stream.
gcount() returnează numarul de caractere citit de un apel
(imediat precedent) de get(), getline(), read().
read(str, MAX) pentru fişiere; extrage până la MAX caractere în str până la
EOF.
seekg(positie) setează distanţa (in bytes) a pointerului de fişier fata de
inceputul fişierului
seekg(positie, seek_dir) setează distanţa (in bytes) a pointerului de fişier fata de o
pozitie specificata seek_dir, ce poate fi
ios::beg, ios::cur, ios::end.
tellg(pos) returnează poziţia (in bytes) a pointerului de fişier faţă de
începutul fişierului
Functii ostream
Functie Semnificaţie
<< inserţie formatată pentru toate tipurile standard şi pentru
cele ce l-au supraîncărcat
put(ch) inserează un caracter ch în stream.
flush() goleşte conţinutul buffer-ului şi inserează linie nouă
write(str, SIZE) inserează SIZE caractere din vectorul str în stream.
seekp(pozitie) setează distanţa în bytes a pointerului de fişier fata de
începutul fişierului
seekp(pozitie, seek_dir) setează distanţa (in bytes) a pointerului de fişier fata de o
poziţie specificată seek_dir
(poate fi ios::beg, ios::cur, or ios::end).
tellp() returnează poziţia pointerului de fişier în bytes.
Biţi de eroare (Error-status bits)

Nume Semnificatie
goodbit fară erori (nici un bit setat, valoare= 0). \
eofbit s-a atins sfârşit de fişier -
failbit operaţie eşuată ( eroare utilizator, EOF prematur). - date membru io_state
badbit operaţie invalid a(streambuf neasociat). -
hardfail eroare nerecuperabilă. /

Functie Semnificatie
eof() returnează true dacă bitul EOF e setat.
fail() returnează true dacă unul din biţii fail bit, sau bad sau hard-fail sunt setaţi
bad() returnează true dacă unul din biţii bad sau hard-fail sunt setaţi
good() returnează true dacă totul e OK; nici un bit nu e setat
clear(int=0) fără argument, şterge toti biţii de eroare; altfel setează biţii specificati
rdstate fără argument, returnează data membru state

Biţi de mod pentru functia open()


Bit Rezultat
in deschis pentru citire (implicit pentru ifstream).
out deschis pentru scriere (implicit pentru ofstream).
ate începe citirea sau scrierea la sfârşitul fişierului(AT End).
app începe scrierea la sfârşitul fişierului(APPend).
trunc dacă fişierul exista, il trunchiază la lungime 0(TRUNCate).
nocreate semnalează eroare dacă la deschiderea fişierului acesta nu exista deja
noreplace semnalează eroare dacă la deschiderea fişierului în ieşire acesta exista deja, cu
exceptia situatiilor în care ate sau app sunt setate.
binary deschide fişierul în modul binar (nu text)

Observaţii :

 În cazul manipulatorilor fără argument, setările rămân valabile până la


distrugerea streamului, în timp ce manipulatorii ce au argumente afectează doar
următorul articol din stream
 Indicatorii de format se pot seta simultan folosind operatorii pe biţi:
Exemplu: cout.setf(ios::showpos | ios::uppercase);
 Manipulatorii setiosflags(flag) şi setf(flag) setează doar indicatorii precizaţi. Dacă
un indicator este parte a unui grup, nu se şterg ceilalţi indicatori ai grupului.
 Funcţia membru setf(flag, field) este utilă pentru setarea indicatorilor ce fac parte
dintr-un grup. De asemenea manipulatorii hex, oct şi dec resetează în mod
corespunzător ceilalţi indicatori ai grupului basefield.
Exemple: cout.setf(ios::oct, ios::dec | ios::oct | ios::hex);
cout.setf(ios::right, ios::adjustfield);
Exemple :
1. Operarea cu funcţii membru
cout.setf(ios::showpos); // setează flag-ul ios::showpos
cout << 27 << endl;

Efectul execuţiei este afişarea valorii ca +27

2. În cazul citirii unui şir de caractere, se poate limita numărul caracterelor citite din
stream

#include <iomanip>
………
char buf[10];
cin >> setw(10) >> buf; // echivalent cin.width(10);

Drept rezultat, se vor citi primele 9 caractere din stream (+ ‘\0’). Celelalte
caractere rămân in stream urmând a extrase la o următoare operaţie de citire. Este
afectată doar prima citire!

3. Formatul pentru afişarea numerelor reale se pot utiliza următoarele setări:

cout.setf(ios::scientific, ios::floatfield);
cout.precision(2);
// echivalent cout << setprecision(2);
4. Operaţii I/O standard
#include <iostream>
#include<iomanip>
using namespace std;

int main()
{
int i=123;
cout.setf(ios::showpos);
cout.width(7);
cout.setf(ios::showbase);
cout.setf(ios::right,ios::adjustfield);
//cout.setf(ios::internal,ios::adjustfield);
cout<<i<<endl; +123

cout.width(5);//AFECTEAZA DOAR PRIMUL cout!


cout.setf(ios::showpos);
cout.setf(ios::left,ios::adjustfield);
cout.fill('0');
cout<<i<<endl; cin.get(); +1230
// cout<<"i="<<i<<endl; //i=000+123

cout.width(5);
cout.setf(ios::right,ios::adjustfield);
cout.fill(' ');
cout.setf(ios::showpos);
cout<<i<<endl;; cin.get(); +123
cout.setf(ios::oct,ios::basefield);
cout<<i<<endl;cin.get();// ramane setat 0173

cout.setf(ios::showbase|ios::hex,ios::basefield);
cout<<i<<endl; cin.get(); 0x7b

double pi=3.14159265;
cout.setf(ios::fixed,ios::floatfield);
cout<<pi<<endl; cin.get(); +3.141593
cout.setf(ios::scientific,ios::floatfield);
cout<<pi<<endl; cin.get(); +3.141593e+000

cout<<setw(6) <<
resetiosflags(ios::internal|ios::right)<<
setiosflags(ios::left)<<setfill('0')<<dec<<i<<endl; +12300
cin.get();

cout<<setw(6) <<
resetiosflags(ios::internal|ios::left)<<
setiosflags(ios::right)<<setfill(' ')
<<setiosflags(ios::showpos)<<i<<endl; +123
cin.get();

cout<<hex<<i<<endl; 0x7b
cout<<oct<<i<<endl; 0173
cin.get();
cout<<setprecision(7)<<pi<<endl; +3.1415927e+000
cin.get();
return 0;
}
5. Testarea stării unui stream // recuperare din eroare

// valori numerice #include <iostream>


using namespace std;
#include <iostream>
using namespace std; int main()
{
int main() double d;
{ while (cin >> d)
double d; cout << d;
while (cin >> d)
cout << d; cin.clear(); //OBLIGATORIU
return 0;
} char ch;
/* while (cin >> ch)
12.3 cout << ch;
12.366
66 return 0;
-4 }
-4
@
1
//caractere 12
23
#include <iostream> 34
using namespace std; 45
56
int main() 6a
{ ax
char ch; x^Z
while (cin >> ch)
cout << ch;
return 0;
}

abcdef
abcdef
123456
123456
^Z
Operarea cu fişiere în C++

 Operarea cu fişiere în C++ este în mare măsură similară operării cu streamurile standard
 Există trei clase dedicate exploatării fişierelor, destinate operaţiilor de intrare, ieşire şi respective intrare/ieşire:
 ifstream (derivată din istream)
 ofstream (derivată din ostream)
 fstream (derivată din iostream).
 Pentru a utiliza aceste clase trebuie inclus fişierul header fstream.
 Spre deosebire de streamurile cout, cin, cerr şi clog, care sunt deja pregătite pentru utilizare, streamurile de
tip fişier trebuie explicit construite de către programator.
 Pentru a deschide un fişier pentru citire şi/sau scriere se instanţiază un obiect aparţinând clasei
corespunzătoare de I/O (eventual cu numele/specificatorul fişierului ca parametru)
 Pentru a efectua operaţiile propriu-zise se utilizează operatorii de inserţie (<<) sau extracţie (>>)
 Pentru a închide un fişier se apelează explicit funcţia membru close() ( sau, cea ce nu este recomandat, se
poate miza pe încheierea domeniului de valabilitate a obiectului şi apelarea implicită a destructorului clasei
respective).

Exemplu :
#include <ofstream>
#include <iostream>
using namespace std;

……….
ofstream outf("Exemplu.txt"); // în directorul curent, implicit text
if (!outf)
{
cerr << "Exeplu.txt nu s-a putut deschide pentru scriere!" << endl;
exit(1);
}
outf << "Linia 1" << endl;
outf << "Linia 2" << endl;
……….
Moduri de operare cu fişiere

Moduri de deschidere a fişierelor

 Modalităţile de deschidere a unui fişier sunt descrise prin intermediul unei colecţii de biţi (flags) care specifică
modul de operare atunci când se apelează funcţia open()

Mod Semnificaţie
app Deschide un fişier în modul adăugare la sfârşit
ate Caută sfârşitul fişierului înainte de a citi/scrie
binary Deschide un fişier în mod binar (în loc de text)
in Deschide un fişier în modul citire (implicit pentru ifstream)
nocreate Deschide un fişier numai dacă acesta există deja
noreplace Deschide un fişier numai dacă acesta nu există deja
out Deschide un fişier modul scriere (implicit pentru ofstream)
trunk Şterge fişierul dacă acesta există deja

 Este posibil să se specifice mai multe moduri utilizând operatorul pe biţi | (sau)
 Observaţie ios::in şi ios::out sunt implicite pentru clasele ifstream şi ofstream respectiv.
 Dacă se optează pentru utilizarea clasei fstream (ce poate opera atât pentru citiri cât şi pentru scrieri), trebuie
specificate explicit modurile ios::in şi/sau ios::out, în funcţie de modul dorit de exploatare a fişierului.
Exemplu : fstream iofile("Sample.dat", ios::in | ios::out);
Moduri de acces în fişiere

 Fiecare clasă ce operează cu fişiere conţine un pointer pentru controlul poziţiei curente din/în care se
citeşte/scrie
 Implicit, la deschidere, pointerul este setat la începutul fişierului, cu excepţia modului de deschidere pentru
adăugare (append) , când este plasat la sfârşitul fişierului
 Există două tipuri de acces la un fişier:
- modul secvenţial - informaţia se citeşte în ordinea în care a fost scrisă
- modul direct - informaţia se poate citi/scrie în locaţii specificate
 Accesul direct se face prin manipularea pointerului către fişier folosind funcţii specifice: seekg(), pentru
operaţiile de intrare and seekp(), pentru operaţiile de ieşire. Acestea au doi parametrii: primul reprezintă un
offset ce determină numărul de octeţi cu care se face deplasamentul faţă de poziţia pointerului fişierului, iar al
doilea, este un indicator ios ce specifică poziţia pointerului faţă de care se aplică parametrul offset.
 Un offset pozitiv înseamnă deplasarea pointerului fişierului către sfârşitul fişierului, un offset negativ înseamnă
deplasarea pointerului fişierului către începutul fişierului

Indicator Semnificaţie
beg Offset-ul este relativ la începutul fişierului (implicit)
cur Offset-ul este relativ la poziţia curentă a pointerului fişierului
end Offset-ul este relativ la sfârşitul fişierului

Exemple:

inf.seekg(14, ios::cur); // deplasare înainte cu 14 bytes


inf.seekg(-18, ios::cur); // deplasare înapoi cu 18 bytes
inf.seekg(22, ios::beg); // deplasare la cel de al 22-lea byte din fisier
inf.seekg(-28, ios::end); // deplasare la cel de al 28-lea byte
// înainte de sfârşitul fişierului
inf.seekg(0, ios::beg); // deplasare la începutul fişierului
inf.seekg(0, ios::end); // deplasare la sfârşitul fişierului
 Alte două funcţii utile sunt tellg() şi tellp(), ce returnează valoarea absolută a poziţiei pointerului fişierului, în
felul acesta ele pot fi folosite şi pentru a determina dimensiunea fişierului

Exemplu :

ifstream inf("Test.txt");
inf.seekg(0, ios::end); // deplasare la sfârşitul fişierului
cout << inf.tellg(); // se afişează numărul de octeţi ai
// fişierului

 Clasa fstream este capabilă să ofere posibilitatea efectuării operaţiilor de citire şi scriere în acelaşi timp
(aproape!)
 Nu este insă posibilă comutarea între cele două operaţii în mod arbitrar, sigurul mod posibil este acela de a
efectua o operaţie de căutare a pointerului fişierului (dacă nu se doreşte deplasare pointerului, se caută poziţia
curentă a acestuia)

Exemplu :

iofile.seekg(iofile.tellg(), ios::beg); // se caută poziţia curentă


Exemplul 1. Controlul poziţiei pointerilor put şi get

#include <fstream>
#include <iostream>
#include<stdlib.h>

using namespace std;

int main ()
{
long pos;

// fstream fis("test.txt",ios::out | ios::in | ios::trunc);


fstream fis("test.txt");
if(!fis)
{
cout<<"ERR\n"; exit(1);
}

fis.write ("This is an apple",17);


pos=fis.tellp();
fis.seekp (pos-8);
fis.write (" sam",4);

char temp[256];
fis.seekg(0, ios::beg);

fis.read(temp, 17);
cout<<temp<<endl;

return 0;
}
Exemplul 2. Copiere de fisiere text

#include<iostream>
#include<stdlib>
#include <fstream>

using namespace std;

int main(int argc, char **argv)


{
if(argc!=3)
{
cout<<"Nr. Incorect de arg !\n"; exit(1);
}
ifstream sursa(argv[1]);
char linie[256];

if(sursa.fail())
cerr<<"Eroare la desch fis" <<argv[1]<<endl;
else
{
ofstream dest(argv[2]);
if(dest.fail())
cerr<<"Eroare la desch fis" <<argv[2]<<endl;
else
{
while(!sursa.eof())
{
sursa.getline(linie, sizeof(linie));
if(sursa) //.good())
{
dest<<linie<<endl;
if(dest.fail())
{
cerr<<"Eroare la scriere fis" <<argv[2]<<endl;
cin.get();
break;
}
}
}
sursa.close();
dest.close();
}
}
cout<< "Succes!"<<endl;
cin.get();
return 0;
}
Exemplul 3. Editarea unei agende telefonice

#include<iostream> fstream at("tel.txt", ios::app


#include <fstream> |ios::in | ios::out);
#include<stdlib.h> if(!at)
#include<string.h> {
using namespace std; cout<<"Nu pot deschide!!\n";
return 1;
class Agenda }
{ for(;;)
char nume[80]; {
char numar[6]; do
public: {
Agenda(){}; cout<<"1. Introducere\n";
Agenda(char *n, char *nr) cout<<"2. Afisare\n";
{ cout<<"3. Incheiere\n";
strcpy(nume,n); cout<<"Introduceti o
strcpy(numar,nr); optiune: ";
} cin>>c;
friend ostream& operator<< }while(c<'1' || c>'3');
(ostream& stream, const Agenda& a);
friend istream& operator>> switch(c)
(istream& stream, Agenda& a); {
}; case '1':
cin>>a;
ostream& operator<< cout<<"Intrarea este ";
(ostream& stream, const Agenda& a) cout<<a<<endl;
{ at<<a;
stream<<a.nume<<" "; break;
stream<<a.numar<<"\n"; case'2':
return stream; char linie[255];
}; at.seekg(0,ios::beg);
while(!at.eof())
istream& operator>> {
(istream& stream, Agenda& a) at.getline(linie,sizeof(linie),'\n');
{ cout<<linie<<endl;
cout<<"Nume "; }
stream>>a.nume; at.clear();//reset eof;
cout<<"Numar "; cout<<endl;
stream>>a.numar; break;
cout<<"\n"; case '3':
return stream; at.close();
}; return 0;
}
int main() }
{ }
Agenda a;
char c;
Exemplul 4. Prelucrare de date prin istrstream cout<<"rate="<<rate<<endl;
#include<iostream> cout<<"distance 2=" <<distance
#include<iomanip> <<endl;
#include <fstream> cin.get();
#include<string.h> return 0;
#include<strstream> }

using namespace std; Rezultate :


int main(void)
{ Err cit int
char *numbers= distance 1=1
"ABC \n 1001\n 1.2345\n Err cit long
123456789L"; count=0
rate=1001
int count;
float rate;
distance 2=0
long distance;

istrstream buff(numbers); Exemplul 5. Prelucrare date prin ostrstream


buff>>count; #include<iostream>
if(buff.fail()) #include<strstream>
{ using namespace std;
cerr<<"Err cit int\n";
buff.clear(); int main(void)
buff.ignore(10,'\n'); {
} ostrstream buff;
int i=99;
buff>>rate; char ch1='O',ch2='K';
if(buff.fail()) buff<<i<<" "<< ch1 << ch2 <<
{ endl<<'\0';
cerr<<"Err cit float\n"; char *p=buff.str();
buff.clear(); // str returneaza un pointer la
} // sirul controlat de
// obiectul de tip ostrstream
buff>>distance; cout<<"Continut :\n"<<p<<endl;
if(buff.fail()) cin.get();
{ return 0;
cerr<<"Err cit long\n"; }
buff.clear();
}
cout<<"distance 1=" << distance Rezultate :
<< endl;
Continut :
buff>>distance; 99 OK
if(buff.fail())
{
cerr<<"Err cit long\n";
buff.clear(); }

cout<<"count="<<count<<endl;
 Observaţie:

Clasele strstreambuf, istrstream, ostrstream, strstream sunt actualmente depreciate.

O listă cu substituiri valide este :

In loc de: Se folosește:

<strstream>, strstreambuf <sstream>, basic_stringbuf


<strstream>, istrstream <sstream>, basic_istringstream
<strstream>, ostrstream <sstream>, basic_ostringstream
<strstream>, strstream <sstream>, basic_stringstream

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