Tipul char
este folosit pentru lucrul cu caractere. O dată de acest tip va reprezenta un singur caracter. Pentru a stoca mai multe caractere vom folosi un tablou cu elemente char
sau un string
.
char
O variabilă de tip char
se declară astfel:
char C;
Valoarea unei variabile de tip char
(sau signed char
) este un număr natural cuprins între -128
și 127
. Valorile cuprinse între 0
și 127
corespund caracterelor din codul ASCII.
Similar, datele de tip unsigned char
au valori între 0
și 255
. Observăm că ambele tipuri conțin valorile care corespund caracterelor din codul ASCII.
Un literal (valoare) de tip char
este un caracter din codul ASCII, delimitat de caractere apostrof ‘.
Putem inițializa o variabilă de tip char
atribuindu-i un literal de tip char sau o valoare numerică. Dacă valoarea numerică nu aparține intervalului de valori corespunzător, aceasta va fi trunchiată.
char C; C = 'A'; C = 65;
Atenție! “ – ghilimele delimitează șiruri de caractere. Un șir de caractere format dintr-un singur caracter nu este același lucru cu un caracter.
“A” ≠ ‘A’
!
Deși datele de tip char
memorează numere întregi, la citirea și afișarea lor se va lucra cu caractere.
char C = 'A'; cout << C; // A C = 65; cout << C; // A
Afișarea unei date de tip char
se face astfel:
char C; cin >> C; ...
În urma citirii de la tastatură unei variabile de tip char
, aceasta va reprezenta caracterul introdus. Dacă se introduc mai multe caractere, se va citi doar primul dintre ele.
char x; cin >> x; // introducem A cout << x; // A
char x; cin >> x; // introducem 145 cout << x; // 1
char x, y; cin >> x >> y; // introducem A B cout << x << endl; // A cout << y << endl; // B
char x, y; cin >> x >> y; // introducem AB cout << x << endl; // A cout << y << endl; // B
char x, y; cin >> x >> y; // introducem ABC cout << x << endl; // A cout << y << endl; // B
char x, y; cin >> x >> y; // introducem 65 66 cout << x << endl; // 6 cout << y << endl; // 5
Valorile de tip char
pot fi convertite la alte tipuri.
char x; x = 65; // conversie implicită de la int la char cout << x; // A cout << (int) x; // 65 int n = 65; cout << (char) n; //A
Cu datele de tip char
se pot face toate operațiile uzuale cu numere. Valoarea de tip char va fi convertită implicit la int
, apoi se vor face operațiile.
char x = 'A'; cout << x + 1; // 66 cout << (char)(x + 1); // B
char x = 'A'; x ++; cout << x; // B
O problemă frecventă este determinarea, pentru o literă mare, a literei mici corespunzătoare, sau invers. Rezolvarea se bazează pe faptul că, în codul ASCII, literele mari sunt poziționate înaintea celor mici, iar diferența dintre codul ASCII a unei litere mici și codul ASCII a literei mari corespunzătoare este aceeași pentru toate literele (32
).
Transformarea se va face scăzând această valoare din litera mică, sau adunând-o la litere mare:
int dif = 'a' - 'A'; // 32 char x = 'k'; x = x - dif; cout << x; // K
C++ ne permite să considerăm o valoare de un anumit tip ca fiind de alt tip. Aceasta se numește conversie de tip.
În C++ sunt două feluri de conversii de tip:
Conversia implicită intervine de la sine când într-o operație avem operanzi de tipuri diferite. Compilatorul consideră automat ambele date de același tip pentru a putea efectua operația.
Conversia implicită se face doar dacă operanzii sunt de tipuri compatibile – vezi mai jos o listă de compatibilități pentru tipurile numerice. Dacă tipurile nu sunt compatibile obținem eroare de sintaxă!
Conversie de la int la double
#include <iostream> using namespace std; int main() { int n = 5; double x; /// conversie de la int la double x = n; cout << "n = " << n << endl; cout << "x = " << x << endl; return 0; }
n = 5 x = 5
Conversie de la double la int
#include <iostream> using namespace std; int main() { int n; double x = 5.75; /// conversie de la double la int n = x; cout << "n = " << n << endl; cout << "x = " << x << endl; return 0; }
n = 5 x = 5.75
Deoarece o dată de tip int
memorează numere întregi, în timpul conversiei zecimalele valorii lui x
s-au pierdut.
Așa cum am văzut în exemplul anterior, conversia de tip poate duce la pierderea (trunchierea) unor date. Acest lucru nu este neapărat rău, dar trebuie să fim conștienți de această situație.
În fapt, conversia este de două feluri:
În primul exemplu de mai sus a avut loc o promovare de la int
la double
, iar în al doilea exemplu a avut loc o retrogradare de la double
la int
. Cea de-a doua a condus la pierderea unor date.
În situațiile în care este posibil, compilatorul realizează conversiile implicite prin promovare.
De exemplu, rezultatul expresiei 2 + 1.5
va fi 3.5
, nu 3
. Are loc promovarea valorii 2
la tipul double
, nu retrogradarea lui 1.5
la int
, ceea ce ar fi însemnat pierderi de date!
Conversia explicită a datelor înseamnă modificarea manuală, de către programator, a tipurilor de date folosite într-o expresie. Se mai numește și type casting.
Are sintaxa:
(TIP) expresie
sau
TIP(expresie)
Observații
expresiei
, considerat de tipul de date TIP
;TIP
trebuie să fie un nume de tip format dintr-un singur cuvânt. Astfel, expresia unsigned int(expresie)
este greșită.Exemplu
char c='A'; cout << (int) c << endl; // 65 cout << char(97) << endl; // a
Notă: Limbajul C++ oferă și următorii operatori de conversie: static_cast
, dynamic_cast
, const_cast
și reinterpret_cast
.
-1
devine cea mai mare valoare a tipului fără semn, -2
devine a doua cea mai mare valoare, etc.bool
rezultatul va fi:
true
, dacă valoarea inițială este nenulă;false
, dacă valoarea inițială este 0
;float
, double
la un tip întreg, valoare se va trunchia, pierzându-se partea zecimală. Dacă rezultatul nu se încadrează în limitele tipului întreg, comportamentul programului devine impredictibil.Regulile de mai sus se aplică atât pentru conversia implicită, cât și pentru cea explicită.
Să presupunem că dorim să determinăm media aritmetică a trei numere întregi. Desigur, răspunsul este suma numerelor împărțită la 3
, dar trebuie ținut cont de faptul că împărțirea a doi întregi este câtul împărțirii. Pentru a obține media corectă va trebui să facem anumite conversii.
int a , b, c; cin >> a >> b >> c; int S = a + b + c; /// cout << S / 3; // gresit - impartire intreaga cout << S / 3.0;
A avut loc conversia implicită a lui S
la double
(promovare).
int a , b, c; cin >> a >> b >> c; int S = a + b + c; cout << 1.0 * S / 3;
A avut loc conversia implicită a lui S
, apoi a lui 3
, la double
(promovări).
int a , b, c; cin >> a >> b >> c; int S = a + b + c; cout << (double)S / 3;
Mai întâi se convertește explicit valoarea lui S
la double
, apoi are loc conversia implicită la double
a lui 3
.
În numeroase situații avem operații cu date de tip int
, dar rezultatul depășește limita maximă (sau minimă) a acestui tip. Astfel se produce depășirea de tip – overflow. Soluția este utilizarea unor conversii prin care operațiile se realizează într-un tip de date mai larg, de exemplu long long
.
Exemplu:
int n = 1000000; cout << n * n << endl; /// posibil -727379968 - overflow cout << 1LL * n * n << endl; /// corect 1000000000000 cout << (long long) n * n << endl; /// corect
La realizarea conversiilor trebuie ținut cont de precedența operatorilor. De exemplu, secvența de mai jos nu obține rezultatul corect.
int n = 1000000; cout << 1LL * n + n * n << endl; cout << (long long)n + n * n << endl;
Mai multe detalii despre conversii sunt diponibile aici: en.cppreference.com/w/cpp/language/implicit_conversion.
Operatorul condițional este singurul operator ternar (cu trei operanzi) din C++. Sintaxa lui este:
ExpresieConditionala ? Expresie1 : Expresie2
și se evaluează astfel:
ExpresieConditionala
. Rezultatul său va fi convertit implicit la bool
.ExpresieConditionala
este true
, se evaluează Expresie1
și rezultatul său va fi rezultatul operației ?
ExpresieConditionala
este false
, se evaluează Expresie2
și rezultatul său va fi rezultatul operației ?
Expresie2
și Expresie3
trebuie să aibă rezultate de același tip, sau de tipuri compatibile.
int x; cin >> x; cout << (x % 2 == 0? "par" : "impar");
?
poate fi înlocuit cu instrucțiunea if
. Secvența de mai sus poate fi rescrisă astfel:int x;
cin >> x;
if (x % 2 == 0) cout << “par”;
else cout << “impar”;
cout << (x > 0? “pozitiv” : x == 0 ? “nul” : “negativ”);
int x;
cin >> x;
cout << (x == 1? 1 : “diferit de 1”); // error: operands to ?: have different types ‘int’ and ‘const char*’
Expresie1
și Expresie2
sunt de tip lvalue
(de exemplu, variabile), rezultatul operației este chiar data corespunzătoare, nu valoarea ei, care poate fi apoi supusă unei atribuiri:int x = 1, y = 2, a = 10;
((a % 2 == 0) ? x : y) = 5;
cout << x << “ “ << y << endl; // 5 2
Cea mai fecventă eroare este să uităm prioritatea operatorilor. Operatorul condițional are prioritate scăzută și este probabil să facem diverse erori!
În general, incrementarea unei date înseamnă mărirea valorii sale, de obicei cu 1
, iar decrementarea unei date înseamnă micșorarea valorii sale.
Aceste operații sunt foarte frecvente. De aceea, numeroase limbaje de programare (inclusiv C/C++, Java, Javascript, C#, PHP) pun la dispoziția programatorilor operatori care fac tocmai acest lucru.
Operatorul de incrementare este ++
, iar cel de decrementare este --
. Sunt operatori unari și se pot aplica doar datelor (variabile sau operații care au rezultat de tip lvalue
– element de tablou, câmp al unei structuri, etc.).
Operatorii de incrementare/decrementare nu se pot aplica pentru constante sau pentru operații care au ca rezultat valori (operații aritmetice, comparații, etc.).
De regulă, operatorii unari sunt prefixați (sunt plasați înaintea operandului, de exemplu - x
). Operatorii de incrementare și decrementare pot fi atât prefixați ( se scriu înaintea operandului), cât și postfixați (se scriu după operand). Efectul lor este același (incrementarea/decrementarea operandului), dar rezultatul diferă.
Operația de incrementare a variabilei X
poate fi:
X ++
. Efectul expresiei este mărirea valorii lui X
cu 1
, iar rezultatul operației este valoarea inițială a lui X
.++ X
. Efectul expresiei este mărirea valorii lui X
cu 1
, iar rezultatul operației este chiar variabila X
(cu valoarea mărită, bineînțeles).int x = 5 , y = 10; y = x ++; // y primeste valoare lui (x++), adica valoarea initiala a lui x cout << x << " " << y; // 6 5
int x = 5 , y = 10; y = ++ x; // y primeste valoare lui (++x), adica valoarea marita a lui x cout << x << " " << y; // 6 6
lvalue
– de exemplu i se poate aplica din nou operatorul de incrementare sau cel de decrementare.Exemple:
int x = 5 , y = 10; y = (++ x) ++; // x devine 7, y devine 6 cout << x << " " << y;
int x = 5 , y = 10; y = ++ (x ++); // eroare, rezultatul lui x ++ nu este lvalue cout << x << " " << y;
int x = 5 , y = 10; y = ++ ++ x; // x devine 7, y devine y cout << x << " " << y;
Postincrementarea are prioritate mai mare decât preincrementarea (vezi aici prioritatea operatorilor). Din acest motiv următoarea secvență este greșită sintactic.
int x = 5 , y = 10; y = ++ x ++; // eroare; prioritate are x ++, dar rezultatul sau nu este lvalue cout << x << " " << y;
Decrementarea respectă toate proprietățile incrementării, cu observația că valoarea datei asupra căreia se aplică se va micșora cu 1
.
Operația de decrementare a variabilei X
poate fi:
X --
. Efectul expresiei este micșorarea valorii lui X
cu 1
, iar rezultatul operației este valoarea inițială a lui X
.-- X
. Efectul expresiei este micșorarea valorii lui X
cu 1
, iar rezultatul operației este chiar variabila X
.Înlănțuirea metodelor (method chaining) este un stil de programare ce poate fi utilizat în contextul programării orientate pe obiecte. Constă în faptul că metodele vor avea ca rezultat obiecte. În acest mod apelurile metodelor pot fi înlănțuite într-o singură instrucțiune, evitând declararea unor variabile care să stocheze rezultate intermediare.
O situație particulară este cascadarea metodelor. Constă în faptul că metodele returnează tocmai obiectul curent (instanța curentă). Astfel apelurile lor sunt înlănțuite, făcând programul ușor de înțeles și evitând “efortul” necesar pentru alegerea unor nume pentru variabile.
Cascadarea metodelor este posibilă în C++ datorită pointerului this
– pointer la obiectul curent. Pentru a realiza cascadarea trebuie îndeplinite următoarele:
this
, adică * this
.Un exemplu C++ binecunoscut este operatorul de inserție în flux <<
, care are ca rezultat obiectul din stânga. Acest fapt permite scrie unei instrucțiuni de forma:
cout << a << " " << b;
Ea este echivalentă cu următoarea secvență:
cout << a; cout << " "; cout << b;
Programul de mai jos este o rezolvare pentru problema #sum00 (suma a două numere):
#include <iostream> using namespace std; class T{ private: int a , b; public: T & Citire(){ cin >> a >> b; return * this; } T & Suma(){ cout << a + b; return * this; } }; int main() { T().Citire().Suma(); return 0; }
Considerăm următoarele două clase:
class A{ private: int n; public: A & Citire(){ cin >> n; return * this; } A & Afisare(){ cout << n << endl; return * this; } A & Dublare(){ n *= 2; return * this; } }; class B{ private: int n; public: B Citire(){ cin >> n; return * this; } B Afisare(){ cout << n << endl; return * this; } B Dublare(){ n *= 2; return * this; } };
Ele diferă doar prin faptul că metodele clasei A
returnează însuși obiectul curent, în timp ce metodele clasei B
returnează o copie a obiectului curent.
Următoarele două secvențe vor afișa același lucru, deși mecanismele sunt diferite:
Cascadare
A().Citire().Dublare().Afisare();
Înlănțuire (fără cascadare)
B().Citire().Dublare().Afisare();
Diferențele sunt vizibile în următoarele exemple:
Cascadare
A x; x.Citire().Dublare(); x.Afisare();
Dacă se citește 2
, se va afișa 4
. Toate apelurile se aplică la același obiect, și anume x
.
Înlănțuire (fără cascadare)
B y; y.Citire().Dublare(); y.Afisare();
Dacă se citește 2
, se va afișa 2
. Metoda y.Citire()
va citi în câmpul n
valoarea 2
, dar va returna o copie a lui y
, asupra căreia se va aplica apelul Dublare()
. Se va dubla câmpul n
din copie, câmpul n
din y
râmânând nemodificat!
Cuvântul polimorfism se referă la proprietatea unor substanțe, ființe, obiecte de a avea mai multe forme.
În contextul programării orientate pe obiecte, polimorfismul se referă la posibilitatea claselor de a avea mai multe metode cu același nume, dar cu efecte și rezultate diferite.
În C++ polimorfismul poate fi implementat prin:
Supraîncărcarea funcțiilor și operatorilor sunt tratate în acest articol: www.pbinfo.ro/articole/25851/supraincarcarea-functiilor-si-a-operatorilor.
Suprascrierea funcțiilor (overriding) se referă la situația ca într-o ierarhie de clase să avem metode cu același nume, dar cu efecte diferite. Considerăm clasele Animal
și Caine
.
#include<iostream> using namespace std; class Animal{ public: void vorbeste() { cout << "Animalul vorbeste." << endl; } }; class Caine: public Animal{ public: void vorbeste() { cout << "Cainele latra." << endl; } }; int main(){ Caine C; Animal A; C.vorbeste(); A.vorbeste(); C.Animal::vorbeste(); // A.Caine::vorbeste(); // imposibil }
În exemplul anterior:
Caine
este derivată din clasa Animal
;C
este de tip Caine
, obiectul A
este de tip Animal
; ambele sunt obiecte statice;vorbeste()
este disponibilă în ambele clase, fiind suprascrisă în clasa derivatăvorbeste()
C.Animal::vorbeste();
– invers nu este posibil!Constatăm că pentru obiectele referite static accesarea metodelor suprascrise este rezolvată elegant, putând accesa orice metodă disponibilă în obiectul curent (proprie obiectului curent sau proprie aflate mai “sus” în ierarhie).
Să vedem cum putem accesa membri claselor de bază/derivată în cazul pointerilor. Folosind clasele definite mai sus, considerăm următorul exemplu:
Caine C; Animal A; Animal * p; p = & A; p->vorbeste(); // Animalul vorbeste. p = & C; p->vorbeste(); // Animalul vorbeste. (?!?) Caine * q; q = & C; q->vorbeste(); // q = & A; // imposibil
Constatăm că:
q
) poate memora doar adresa unui obiect al acesteia;p
) poate memora atât adresa unui obiect al clasei de bază, cât și adresa unui obiect al clasei derivate;C++ oferă un mecanism prin care se alege metoda accesată printr-un pointer dinamic, la execuția programului, în funcție de clasa din care face obiectul referit de pointer (de bază sau derivată). Acest mecanism este reprezentat de funcțiile virtuale.
Declararea unei funcții (metode) virtuale este precedată de cuvântul C++ rezervat virtual
în clasa de bază:
virtual tipRezultat metoda(parametri)
Exemplu:
#include<iostream> using namespace std; class Animal{ public: virtual void vorbeste() { cout << "Animalul vorbeste." << endl; } }; class Caine: public Animal{ public: void vorbeste() { cout << "Cainele latra." << endl; } }; int main(){ Caine C; Animal A; Animal * p; p = & A; p->vorbeste(); // Animalul vorbeste. p = & C; p->vorbeste(); // Cainele latra. (!) }
Constatăm că:
p
este declarat pointer la clasa de bază;p
poate memora fie adresa unui obiect din clasa de bază, fie adresa unui obiect din clasa derivată;Încapsularea presupune și ascunderea datelor sensibile ale unui obiect. Acest lucru este necesar din cel puțin două motive:
0
, într-un interval cunoscut – nu putem avea 157
sau 1204578
de ani!Astfel, datele membru ale unui clase sunt declarate private. Acest lucru duce la respectarea regulilor de mai sus, dar conduce totodată și la imposibilitatea de a vedea din afara clasei care este valoare unei date membru private sau de a o schimba. Aceste operații pot fi făcute prin intermediul metodelor GET și SET (getter-e și setter-e).
O metodă GET este o metodă publică a clasei care returnează valoarea ueni date membru private.
O metodă SET este o metodă publică a clasei care atribuie unei date membru private o anumită valoare.
void
sau este obiectul curent, pentru a putea înlănțui apelul metodelor.În exemplul de mai jos implementăm clasa Fractie
, înzestrând-o cu metode GET și SET. Folosim aceste metode la scrierea unei funcții care determină suma a două fracții.
#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; // proprietăți void Simplifica(); // metodă privată public: Fractie(int _numarator = 0, int _numitor = 1); // constructor Fractie(const Fractie &); // constructor de copiere Fractie & Citeste(); // metodă publică Fractie & Scrie(); // metodă publică // metode GET int Numarator(); int Numitor(); // metode SET Fractie & Numarator(int); Fractie & Numitor(int); }; Fractie::Fractie(const Fractie & F) { // constructor de copiere numarator = F.numarator; numitor = F.numitor; Simplifica(); } Fractie::Fractie(int _numarator /* = 0 */, int _numitor /* = 1 */) { // constructor numitor = _numitor, numarator = _numarator; Simplifica(); } int Fractie::Numarator() { //metoda GET return numarator; } int Fractie::Numitor() { //metoda GET return numitor; } Fractie & Fractie::Numarator(int x) { numarator = x; Simplifica(); return * this; } Fractie & Fractie::Numitor(int x) { // validam valoarea lui x if(x == 0) { cerr << "Numitor nul" << endl; return * this; } numitor = x; if(x < 0) numarator *= -1, numitor *= -1; Simplifica(); return * this; } Fractie & Fractie::Citeste() { // citeste numaratorul si numitorul obiectului curent // returnează obiectul curent int a , b; cin >> a >> b; // validam valorile citite if(b == 0) { cerr << "Numitor nul"<<endl; return * this; } if(b < 0) a = -a , b = -b; numarator = a, numitor = b; Simplifica(); return * this; } Fractie & Fractie::Scrie() { // afisează numaratorul si numitorul obiectului curent // returnează obiectul curent cout << numarator << " " << numitor <<endl; return * this; } void Fractie::Simplifica() { //metoda privata; realizeaza simplificarea fractiei merorate în obiectul curent int a = abs(numarator), b = abs(numitor); while(b) { int r = a % b; a = b; b = r; } numarator /= a; numitor /= a; } Fractie Suma(Fractie F, Fractie G) { // funcție oarecare. Accesam datele private ale obiectelor prin metodele GET si SET int x = F.Numarator() * G.Numitor() + F.Numitor() * G.Numarator(), y = F.Numitor() * G.Numitor(); Fractie R; R.Numarator(x).Numitor(y); // apelam metodele SET return R; // s-a apelat costructorul de copiere } int main() { Fractie a , b; a.Citeste(), b.Citeste(); Suma(a , b).Scrie(); return 0; }
Derivarea claselor (moștenirea) este unul dintre principiile fundamentale ale programării orientate pe obiecte. Ne permite să definim o clasă pornind de la altă clasă, creată anterior, și facilitează crearea și întreținerea aplicațiilor. Tototdată permite reutilizarea codului și micșorează timpul de implementare a programelor.
La crearea unei clase, în loc să scriem date și funcții membre complet noi, putem să precizăm că noua clasă va moșteni membri unei clase existente. Se obțin astfel ierarhii de clase.
Moștenirea are sens numai când obiectele din clasa moștenită fac parte din obiectele clasei de bază – relația dintre ele este de tipul Is A (este un/o). De exemplu:
Paralelogram
poate fi derivată din clasa Patrulater
.Caine
poate fi derivată din clasa Animal
.Fie B
o clasă care există deja, iar D
clasa care moștenește membrii clasei B
. Spunem că:
B
este clasa de bază;D
este clasa derivată;D
s-a obținut prin derivarea clasei B
;B
a fost derivată și s-a obținut clasa D
;D
moștenește clasa B
;În C++, o clasă poate fi derivată pornind de la mai multe clase de bază. Totodată, specificatorii de acces cunoscuți (public
, private
, protected
) sunt folosiți și pentru a preciza modul în care se face derivarea. Efectul lor este legat de vizibilitatea membrilor clasei de bază în clasa derivată.
Cea mai simplă formă de derivarea a unei clase este prezentată mai jos:
class D: public B{ // membri noi ai clasei derivate }
Observații:
B
este clasa de bază, care trebuie să fie deja definită;D
este clasa derivată;public
, face ca:
public
din clasa de bază B
rămân public
și în clasa derivată D
– pot fi accesați din afara clasei D
;protected
din clasa de bază B
rămân protected
și în clasa derivată D
– pot fi accesați din clasa derivata D
și vor putea fi acesați din alte clase vor moșteni clasa D
;private
din clasa de bază B
nu pot fi accesați din clasa derivată D
;public
putem folosi private
sau protected
. Semnificația lor va fi discutată ceva mai târziu!#include<iostream> using namespace std; class Animal{ private: protected: int varsta; public: Animal(){ varsta = 0; cout << "S-a nascut un animal." << endl; } ~Animal(){ cout << "Animalul a murit." << endl; } void creste() { ++ varsta; cout << "Animalul are " << varsta << (varsta == 1 ? " an":" ani") << endl; } }; class Caine: public Animal{ public: Caine(){ cout << "S-a nascut un caine." << endl; } ~Caine(){ cout << "Cainele a murit." << endl; } void latra() { for(int i = 1 ; i <= varsta ; i ++) cout << "Ham " ; cout << endl; } void creste() { ++ varsta; cout << "Cainele are " << varsta << (varsta == 1 ? " an":" ani") << endl; } }; int main(){ Caine X; X.creste(); X.Animal::creste(); X.latra(); X.creste(); X.latra(); }
S-a nascut un animal. S-a nascut un caine. Cainele are 1 an Animalul are 2 ani Ham Ham Cainele are 3 ani Ham Ham Ham Cainele a murit. Animalul a murit.
Animal
este clasa de bază, iar Caine
este clasa derivată;latra()
) am accesat proprietatea varsta
din clasa de bază:
varsta
a fost declarată protected
private
, nu putea fi accesată din clasa derivatămain()
, pentru obiectul X
de tip Caine
am accesat:
latra()
, disponibilă doar în clasa derivată;creste()
, moștenită din clasa de bază;X.creste(); // Cainele are 1 an
X.Animal::creste(); // Animalul are 2 ani
int main(){ Caine X; X.creste(); // Cainele are 1 an X.latra(); // Ham Animal A; A = X; A.creste(); // Animalul are 2 ani // X = A; eroare de sintaxă
}
A
este de tipul Animal
(clasa de bază), iar X
este de tipul Caine
(clasa derivată). Atunci:
A = X;
este corectă. În A
se vor copia doar membri din X
care fac parte din clasa de bază.X = A;
nu este corectă. Clasa derivată conține și membri care nu aparțin clasei de bază, ce nu pot fi inițializați prin această atribuire!Accesul la membrii (date sau funcții) clasei de bază în clasa derivată se face în funcție de regulile de acces la membrii clasei de bază și în funcție de modalitatea în care s-a făcut derivarea.
Sunt trei moduri de declarare a membrilor unei clase:
public
– pot fi accesați din afara clasei, dar și din interiorul eiprotected
– nu pot fi accesați din exteriorul clasei (de bază sau derivate). Pot fi accesați din interiorul clasei de bază și din interiorul claselor derivateprivate
– pot fi accesați numai din interiorul clasei.Modalitățile de derivarea ale unei clase sunt:
class D: public B{ … }
B
rămân publici și în clasa D
;B
rămân protejați în clasa D
;B
sunt inaccesibili în clasa D
;class D: protected B{ … }
B
devin protejați și în clasa D
;B
rămân protejați în clasa D
;B
sunt inaccesibili în clasa D
;class D: private B{ … }
B
devin privați și în clasa D
;B
devin privați în clasa D
;B
sunt inaccesibili în clasa D
;Supraîncărcarea funcțiilor (eng. functions overloading) se referă la posibilitatea ca în același program să fie declarate și definite mai multe funcții cu același nume, care să difere prin parametri. La apelul unei funcții supraîncărcate, compilatorul stabilește care dintre declarații se potrivește cu apelul respectiv, comparând numărul și tipul parametrilor actuali cu cel al parametrilor formali.
Metodele unei clase, fiind funcții, pot fi supraîncărcate. Totodată, în interiorul unei clase pot fi definiți și supraîncărcați operatori (+
, -
, *
, etc.), care permit utilizarea naturală a obiectelor, în raport cu înțelesul lor.
De exemplu, fracțiile pot fi adunate, scăzute, etc. O clasa care implementează fracții poate fi îmbogățită cu operațiile naturale de adunare, scădere, înmulțire, împărțire, dar și cu comparații, incrementări/decrementări, citire, afișare, etc.
În acest fel lucrul cu obiecte devine natural, evitând apelul explicit al unor metode care poate părea forțat, în anumite situații.
De știut:
În exemplul următor, definim pentru clasa Fracție
două metode creste()
:
1
;n
, care va mări valoarea fracției încapsulate în obiect cu n
.#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; public: void afiseaza() /// metodă pentru afișarea fractiei { cout << numarator << "/" << numitor << endl; } Fractie(int a , int b) /// constructor { if(b < 0) a = -a, b = -b; numarator = a, numitor = b; } Fractie & creste() { numarator += numitor; return *this; } Fractie & creste(int n) { numarator += n * numitor; return *this; } }; int main(){ Fractie x(1 , 4); x.afiseaza(); x.creste(); x.afiseaza(); x.creste(3); x.afiseaza(); return 0; }
Programul de mai sus va afișa:
1/4 5/4 17/4
Putem supraîncărca aproape toți operatorii C++ predefiniți. În acest mod putem folosi operatorii și pentru tipurile definite de noi, nu doar pentru cele predefinite.
Nu putem supraîncărca operatori pentru tipurile de bază: int
, double
, etc.
Nu putem supraîncărca operatori care nu există. De exemplu, nu putem adăuga operatorul #
.
Nu putem schimba prioritatea operatorilor.
Operatorii sunt funcții cu un nume special: cuvântul rezervat operator
, urmat de simbolul operatorului pe care îl definim. La fel ca orice altă funcție, și operatorii au tip al rezultatului și listă de parametri.
Operatorii care pot fi supraîncărcați sunt:
+
, -
, *
, /
, %
^
, &
, |
, ~
,,
=
<
, >
, <=
, >=
, ==
, !=
,!
, &&
, ||
++
, --
<<
, >>
,+=
, -=
, /=
, %=
, ^=
, &=
, |=
, *=
, <<=
, >>=
,[]
, ()
->
, ->*
new
, new []
, delete
, delete []
Următorii operatori nu pot fi supraîncărcați:
::
, .*
, .
, ?:
Sunt două modalități de defini operatori pentru o clasă:
Putem folosi metodele când operatorul este unar, sau când este binar și primul operand este obiect al clasei pentru care definim operatorul. Dacă operatorul este binar și primul operator nu este obiect al clasei, vom defini operatorul prin intermediul funcțiilor prietene.
În programul de mai jos definim o clasă Fractie
în care vom defini și supraîncărca operatorul +
pentru a implementa adunarea fracțiilor și adunarea fracțiilor cu întreg. Distingem următoarele cazuri:
F + G
, unde F
și G
sunt fracții, poate fi implementată atât printr-o metodă cât și ca funcție prietenă;F + n
, unde F
este fracție și n
este număr întreg, poate fi implementată atât printr-o metodă cât și ca funcție prietenă;n + F
, unde F
este fracție și n
este număr întreg, va fi implementată printr-o funcție prietenă.Situația de mai sus se datorează faptului că, la implementarea operatorului ca metodă, obiectul curent, pentru care se implementează metoda, este primul operand.
#include <iostream> using namespace std; class Fractie{ private: int numarator, numitor; public: void afiseaza() /// metodă pentru afișarea fractiei { cout << numarator << "/" << numitor << endl; } Fractie(int a = 0, int b = 1) /// constructor { if(b < 0) a = -a, b = -b; numarator = a, numitor = b; } Fractie operator+ (Fractie F) { Fractie R; R.numarator = numarator * F.numitor + numitor * F.numarator; R.numitor = numitor * F.numitor; return R; } Fractie operator+ (int n) { Fractie R; R.numarator = numarator + n * numitor; R.numitor = numitor; return R; } friend Fractie operator + (int n, Fractie F) { Fractie R; R.numarator = F.numarator + n * F.numitor; R.numitor = F.numitor; return R; } }; int main(){ Fractie X(1 , 4), Y(2, 3); Fractie R = X + Y; R.afiseaza(); R = X + 2; R.afiseaza(); R = 2 + X; R.afiseaza(); return 0; }
Programul de mai sus va afișa:
11/12 9/4 9/4