Documente Academic
Documente Profesional
Documente Cultură
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
- http://www.mindview.net
- http://gd.tuwien.ac.at/languages/c/c++oop-pmueller/
- http://www.icce.rug.nl/documents/cplusplus/
- http://msdn.microsoft.com/en-us/library/60k1461a%28v=vs.100%29.aspx
Paradigme de programare
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.
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
“Fifty years of programming language research, and we end up with C++ ???.”(Richard A. O’Keefe)
“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++ 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.”
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 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)
Operatori aditivi
Addition: + Subtraction: –
Atribuire
Operatori pe biţi
Operatori logici
Alţi operatori
Operatori multiplicativi
Operatori de deplasare
Operatori unari
::
( ) [ ] . ->
- + * & ! ~ ++ -- 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
Domeniile numelor :
- local
- fişier
- global
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
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>
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!)
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ţ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.
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
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
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
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:
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
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();
}
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
}
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) class NC
{…
public:
NC(const NC&);…}
const NC ob1(..);
NC ob2(ob1); // eroare in absenta lui const!
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
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;
……..
};
class X
{
int i; //private
friend void functieFriend(X *, int);
public:
void functieMembru (int);
};
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(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;
}
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)
Exemple:
Tema : Proiectaţi clasa Copac astfel încât că se poate face si comparaţii de forma intreg < obiect din clasa Copac !
În general, pentru o clasă NumeClasa, operatorul generic, în notaţie prefixată op_inc/dec_pre se supraîncarcă
astfel :
Î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 :
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.
#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
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 :
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
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() 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; } }
}; };
#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
{}
#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)
{
}
#endif
Fişierul main.cpp
#include <string>
#include <iostream>
#include "Creatura.h"
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;
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ă
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;
}
};
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
Exemplu:
class Element
{
…….
public:
Element(lista_el);
……
};
class Comp
{
Element el;
…….
public:
Comp(lista_comp);
……
};
{
..........
}
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";
}
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);
}; }
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 2.b)
#include <iostream>
using namespace std; int main ()
{
class CPolygon { CRectangle rect;
protected: CTriangle trgl;
int width, height; CPolygon * ppoly1 = ▭
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);
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 = ▭
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
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 = ▭
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ă
class Base
{
public:
void f(double x) // 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ă
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:
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):
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
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 };
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).
};
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
};
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
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
Observaţii :
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!
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);
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
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
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:
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 :
#include <fstream>
#include <iostream>
#include<stdlib.h>
int main ()
{
long pos;
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>
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
cout<<"count="<<count<<endl;
Observaţie: