Sunteți pe pagina 1din 51

Tutoriale C++ - Programare Orientat pe Obiecte

Termenul de OOP (Object Oriented Programming) desemneaz disciplina Programrii Orientate pe Obiecte (POO) sau Programare Orientat Obiectual. Aceast disciplin care are la baz ideea unificrii datelor cu modalitile de prelucrare a acestora n entiti numiteobiecte. Ea a aprut din necesitatea exprimrii problemei ntr-un mod mai natural fiinei umane. Astfel unitaile care alctuiesc un program se apropie mai mult de modul nostru de a gndi dect modul de lucru al calculatorului. Facilitile oferite de programarea orientat obiectual sunt: - abstractizarea datelor; - motenirea; - ncapsularea (ascunderea) informaiei; - legarea dinamic (trzie).

Abstractizarea datelor
Obiectele care reprezint aceeai idee sau concept, sunt de acelai tip i pot fi grupate n clase (concrete sauabstracte). Clasele implementeaz tipuri de date i operatorii destinai manipulrii acestora. De exemplu, programatorul i poate defini tipul (clasa) matrice i operatorii care pot fi aplicai matricelor (* pentru nmulirea a dou matrice, + pentru adunarea a dou matrici, pentru scderea a dou matrice, etc). Astfel, el poate folosi tipul matrice n mod similar unui tip predefinit: ?

1 matrice A, B; 2 matrice C = A + B;
Tipul unui obiect (ablon al obiectului) este o clas. O clas se caracterizeaz prin: numele clasei, atribute, funcii i relaii cu alte clase. Instana este un obiect dintr-o clas (A, B, C sunt obiecte, instane ale clasei matrice) i are proprietile definite de clas. Pentru o clas definit, se pot crea mai multe instane ale acesteia. Toate obiectele au o stare i uncomportament. Starea unui obiect se refer la elementele de date coninute n obiect i la valorile asociate acestora (datele membre). Comportamentul unui obiect este determinat de care aciunile pe care obiectul poate s le execute (metodele sau funciile membre). Datele membre sunt atribute specificate n definiia unei clase i descriu proprietile obiectelor din clas, sub diferite aspecte. Exist urmtoarea distincie ntre datele membre ale unei clase: - date membre ale clasei (au aceeai valoare pentru toate instanele (obiectele) clasei) - variabile de clas; - date membre ale instanei (variaz de la o instan la alta, fiecare instan avnd propria copie a datei membre) - variabile de instan. Metodele (methods) sunt funcii membre ale clasei. Fiecare obiect are acces la un set de metode ce descriu operaiile care pot fi executate asupra lui (asupra obiectulu). Clasa conine att datele membre (variabilele membre) necesare descrierii obiectului, ct i metodele care pot fi aplicate obiectului. Astfel, gradul de abstractizare este, mult mai ridicat, iar programele devin mult mai uor de neles, depanat sau ntreinut. Reprezentarea clasei (datele membre) este o colecie de zero sau mai multe date de orice tip. Interfaa clasei (funciile membre) este o colecie de operaii aplicabile obiectelor clasei.

Motenirea
Motenirea este o caracteristic a limbajelor de programare orientate obiectual, care permite refolosirea codului i extinderea funcionalitii claselor existente. Astfel apare conceptul de clas de baz / clas derivat sau superclas / clas. Clasa de baz implementeaz toate caracterisiticile comune obiectelor ce vor deriva din baz. Prin motenire, un obiect poate prelua proprietile definite n clasa de baz. Motenirea poate fi: unic sau multipl. n cazul motenirii unice, fiecare clas are doar o superclas (clas de baz), iar n cazul motenirii multiple, o clas are mai multe superclase, astfel, motenirea clasei va fi multipl. Limbajul C++ permite un control puternic asupraatributelor (datelor membre) i metodelor care vor fi motenite.

ncapsularea (Ascunderea) Informaiei


ncapsularea informaiei reflect faptul c atributele instan i metodele unui obiect l definesc doar pe acesta. Adic obiectele nu pot schimba starea intern a altor obiecte n mod direct (ci doar prin metode puse la dispoziie de obiectul respectiv); doar metodele proprii ale obiectului pot accesa starea acestuia. Vom spune c metodele i datele membre unui obiect sunt private, ncapsulate n obiect. Fiecare tip de obiect expune o interfa pentru celelalte obiecte care specific modul cum acele obiecte pot interaciona cu el. n limbajul C++ ncapsularea poate fi forat prin controlul accesului, deoarece toate datele i funciile membre sunt caracterizate printr-un nivel de acces: public, protected, private, iar funciile friend au acces lareprezentarea privat a clasei i NU sunt funcii membre ale clasei respective. n general, reprezentarea este privat iar interfaa public.

Ascunderea informaiei este un mecanism formal pentru a restrnge accesul la reprezentarea clasei. n limbajul C++, nivelul de acces poate preciza i tipul de motenire: - Public, unde n clasa derivat nivelul de acces al membrilor motenii este acelai ca n clasa de baz; - Privat sau protected, unde membrii protected i public din clasa baz devin private sau protected n clasa derivat.

Legarea Dinamic ("Trzie")


Efectul combinat al motenirii poate determina ca o anumit metod s fie specializat (implementat) diferit (prinredefinire), pentru subclase (clase derivate) diferite. Polimorfismul reprezint comportamente diferite ale unei metode n raport cu tipul unui obiect. Mai exact, este abilitatea de a redefini metode pentru clasele derivate. O funcie virtual este o funcie membr special, invocat printr-o referin sau un pointer la o anumit clas de baz (public), funcie care este redefinit (sau nu) i n clasele derivate. Dintre toate funciile virtuale cu acelai nume, cea care este efectiv apelat se determin doar n momentul execuiei pe baza tipului actual al obiectului invocator. Fenomenul este cunoscut sub numele de legare dinamic. Limbajul C++ furnizeaz modaliti de suprancarcare a operatorilor (overloading): acelai operator are semnificaii diferite, care depind de numrul i tipul argumentelor.

Tutoriale C++ - Clase (I) - POO


Citii acest tutorial, nainte de a ncepe. Chiar dac nu-l nelegei complet, mcar s avei o idee general desprePOO. O clas este un concept extins al structurii (nregistrrii). Pe lng date membre, o clas poate conine i funcii membre. O clas poate fi asemnat cu un proiect, un ablon, o descriere. Avnd la dispoziie o clas, un programator poate s creeze obiecte din acea clas. Un obiect (sau o instan) este o instaniere a unei clase. Mai simplu, clasa este tipul, iar obiectul este variabila. Instanierea reprezint crearea unui obiect concret pe baza unei definiii sau a unui model (ablon). Clasele se declar n general cu keywordul class. ?

1 class nume_clasa { 2 specificator_de_acces1: 3 membru1; specificator_de_acces2: 4 membru2; 5 ... 6 } nume_obiecte; 7


Prima linie: class nume_clasa reprezint antetul clasei. Corpul clasei este cuprins ntre { } i conine membrii clasei, urmat de ; sau de o list de declarare (lista aceea de identificatori pe care am ntlnit-o la nregistrri;nume_obiecte). Datele membre (adic variabilele membre) nu pot fi iniializate explicit - adic direct n definiia clasei - (valabil i pentru structuri i uniuni). Specificatorii de acces stabilesc nivelul de acces din exteriorul clasei la membrii acesteia. Exist trei nivele de acces: private, protected, public, care s vedem ce semnific: 1. Membrii private ai claselor sunt accesibili numai n funciile membre sau prietene (friend) ale clasei respective. 2. Membrii protected sunt accesibili n funciile membre sau prietene, dar i n clasele derivate din clasa respectiv (clasa de baz). Mai simplu, membrii protected se comport public pentru clasele derivate i private pentru restul programului. 3. Membrii public sunt accesibili din orice punct al programului. Implicit nivelul de acces ntr-o clas (declarat cu keywordul class), imediat dup {, este private. O clas poate conine seciuni multiple cu nivele de acces diferite. Fiecare seciune rmne activ pn la specificarea unui alt nivel de acces. Acum s discutm puin i despre funciile membre, dup care trec la exemple concrete. Funciile membre se deosebesc de cele obinuite prin faptul c: - au acces la toi membrii clasei din care fac parte (cele obinuite doar la membrii publici); - funciile membre sunt definite n domeniul (de vizibilitate) clasei respective, cele obinuite n domeniul global (fiier); - funciile membre pot fi suprancrcate doar de funcii membre ale aceleiai clase (deoarece suprancrcarea (overloading) presupune un domeniu comun). Funciile membre (metodele) se declar n corpul clasei (declararea nsemnnd specificarea prototipului). Le putei defini n interiorul clasei (vor fi considerate inline) sau n afara ei (vom vedea cum se face asta).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

#include <iostream> using namespace std; class lol { // incepand de aici nivelul de acces este private bool state; public: // incepand de aici nivelul de acces este public // prototipurile functiilor; nu uitati ca numele parametrilor pot lipsi sau pot fi diferite void set(int a, int b); void setState(bool st); // definesc functia in corpul clasei void afiseaza() { for(int i=0; i < x*y; i++) { if(state == true) cout << "LOLOLOL\n"; else cout << ":((\n"; } } private: // din nou private pana la sfarsit, pana la } int x; int y; } ; // definesc functiile in afara clasei void lol::set(int a, int b) { // observati ca am acces la membrii private 'x' si 'y' x = a; y = b; // de ce? // deoarece functia este membra a clasei 'lol' } // folosesc operatorul :: pentru a defini un membru al unei clase in afara ei void lol::setState(bool st) { state = st; }

int main() { lol obi; // declar un obiect de tip 'lol' obi.set(3, 2); // pot face asta deoarece am acces la functia membra; este public obi.setState(true); obi.afiseaza(); // obi.x = 4; // ouch, eroare! membru private, nu-l pot accesa // obi.y // eroare! private

47 48 49

// obi.state return 0; }

// eroare! private

Noul lucru important din codul de mai sus este operatorul :: (scope operator sau operatorul de vizibilitate) care permite definirea unui membru n afara corpului clasei, n afara definiiei ei. Bineneles c trebuie s specificai numele clasei din care face parte membrul respectiv (liniile 28; 36). Acest operator asigur aceleai proprieti de vizibilitate pe care le-ar avea membrul, dac ar fi definit n interiorul clasei (n corpul ei), chiar dac acesta este definit n afara ei. Datele membre (toate dintr-o clas) formeaz ceea ce se numete reprezentarea clasei, n timp ce funciile membre formeaz interfaa clasei (sunt doar nite denumiri). n alte limbaje de programare, datele membre se numesc proprieti, iar funciile membre se numesc metode. Despre o reprezentare declarat private sau protected spunem c este ncapsulat. Datele membre, n acest caz, vor fi accesibile numai prin funcii special create pentru operarea cu membrii ascuni (n exemplul de mai sus am dou funcii de acest fel: set i setState, cu care iniializez membrii ascuni). ncapsularea a dus la apariia conceptului de ascundere a informaiei, mecanismul prin care accesul la reprezentarea clasei este restrns.

Constructori i destructori
Constructorul este o funcie de iniializare. Are acelai nume ca i numele clasei, i este apelat automat ori de cte ori se definete un obiect (instan) sau se aloc (cu operatorul new) un obiect al clasei. - constructorii nu au tip i nu returneaz nimic, n rest, sunt doar nite funcii membre obinuite. - constructorii nu pot fi apelai explicit (aa cum am face cu o funcie membr obinuit); acetia sunt apelai automat la iniializarea obiectului clasei. - constructorii pot fi suprancrcai. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

class point { public: point(){ x = 0; y = 0; } // Constructorul implicit (default) point(int X, int Y); // declar prototipul private: int x; int y; } ; point::point(int X, int Y) { x = X; y = Y; } int main() { point A = point(2, 3); // invocare (apelare) explicita a constructorului point B(4, 7); // forma prescurtata point o = point(); // declaratii echivalente point O; // se apeleaza constructorul implicit // Urmatoarea este nevalida // point U(); // eroare! nu reprezinta un obiect 'point' initializat cu constructor implicit // ci reprezinta declararea unei functii, fara parametri, ce

26 returneaza un obiect de tip 'point' 27 return 0; 28 } 29


Aa cum vedei n exemplul de mai sus, constructorul implicit (default) este constructorul cu lista de parametri vid(nu ia niciun argument). Mai observai c am suprancrcat constructorul, am unul implicit i unul cu doi parametri. Atunci cnd nu specific argumentele, membrii obiectului de tip point vor fi iniializai cu 0 (este apelat constructorul implicit). O alt metod de iniializare a membrilor unei clase este cea prin listele de iniializare. Listele de iniializare apar doar n definiiile constructorilor, nu i n declaraiile lor. Lista de instaniere (iniializare) apare n implementarea constructorului, ntre antetul i corpul acestuia. Lista conine operatorul (:), urmat de numele fiecrui membru i valoarea de iniializare. Listele de iniializare sunt singura modalitate prin care membrii const sau referin (cei cu & n faa numelui) pot fi iniializai. Pentru fiecare constructor va trebui s iniializai aceti membri, prin metoda descris mai sus. Membrii astfel iniializai, apar ntre lista de parametri a constructorului i corpul su. De asemenea, : indic aceast iniializare. Exemple: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

class point { public: point(): K() { x = 0; y = 0; } // lista de initializare + initializare prin asignare (atribuirea dintre acolade) point(int X, int Y); // listele de initializare nu apar la declarare!!! point(int R): K(R) {} // doar lista de initializare; observati ca intre acolade nu este nimic private: const int K; // un membru const int x; int y; } ; point::point(int X, int Y): K() // lista de initializare { x = X; y = Y; } int main() { point P(3); point O; point Q(8, 6); return 0; }

Observai c am iniializat membrul const K la fiecare definiie a constructorului. Asta deoarece variabilele const(la fel ca variabilele referin) trebuie iniializate atunci cnd sunt create, iar ntr-o clas, acestea sunt create atunci cnd un obiect al clasei este definit. n momentul acela unul dintre constructori va fi invocat, iar dac membrul constnu este iniializat n acel constructor, atunci compilatorul va genera o eroare de compilare (ncercai i vei vedei ce se ntmpl). K() l iniializeaz cu zero (0). O clas fr niciun constructor public se numete clas privat. Constructorii nu pot fi metode virtuale. n cazul n care o clas nu are nici constructor declarat de ctre programator, compilatorul genereaz un constructor implicit, fr nici un parametru, cu lista instruciunilor vid. Dac exist un constructor al programatorului, compilatorul nu mai genereaz constructorul implicit. Ca orice alt funcie n limbajul C++, constructorii pot avea parametri implicii. A nu se confunda cu constructorul implicit! Destructorii sunt funcii membre speciale invocate ori de cte ori un obiect nceteaz s mai existe (durata de via expir sau operatorul delete este aplicat asupra unui pointer de tipul clasei). Practic au funcionalitate opus constructorilor, de deiniializare. Destructorii nu au parametri, nu returneaz nimic i numele lor este numele clasei precedat de ~ (tild).

Pe scurt, destructorii pot efectua toate operaiile pe care programatorul le vrea efectuate nainte de ncetarea existenei unui obiect. Acelai lucru i pentru constructori (dar la iniializarea obiectului). ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

class lol { public: lol(){ cout << "Sunt Viu!! LOLOLOL\n"; /* XD */ } // constructor ~lol() { cout << "Sunt MORT X_X\n"; system("PAUSE"); // Asta in cazul in care nu folositi CodeBlocks // Pun PAUSE ca sa aveti timp sa vedeti textul } }; int main() { lol wtf; // constructor implicit invocat return 0; // cand se ajunge aici, programul este pe cale sa se inchida // in acest moment toate obiectele, variabilele sunt distruse, memoria este eliberata // deci, destructorii vor fi invocati }

Destructorii sunt utili atunci cnd obiectul unei clase aloc dinamic memorie pentru membrii si, iar la finalul vieii obiectului vrem s eliberm acea memorie. Obiectele dinamice nu se distrug automat, deoarece doar programatorul tie cnd nu mai este necesar un astfel de obiect. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

class point { public: point(int X, int Y); ~point() { delete x; // eliberez memoria delete y; // alocata membrilor } private: int * x; int * y; } ; point::point(int X, int Y) { x = new int; y = new int; // aloc dinamic memorie pentru cei doi membri *x = X; *y = Y; } int main() { point P(8, 6); return 0; }

Pointeri ctre clase Pointerii pot fi asociai cu tipuri de clase. Se folosesc aceiai operatori de selecie (sau selectori), punct (.) isgeat (->). Regulile sunt aceleai ca la nregistrri (structuri), noutatea fiind funciile membre, dar se acceseaz la fel. De data aceasta se ine cont de nivelul de acces. Iat un tabel cu operaiile posibile:

Expresie *x &x x.y x->y (*x).y x[0] x[1] x[n] Indicat de x Adresa lui x Membrul y al obiectului x

Se citete

Membrul y al obiectului indicat de x Membrul y al obiectului indicat de x (echivalent cu cel de sus) Primul obiect indicat de x Al doilea obiect indicat de x Al (n+1)-lea obiect indicat de x

n final, clasele pot fi declarate i cu keywordurile struct sau union. La struct nivelul de acces implicit estepublic, n rest struct este echivalent cu class. La union accesul implicit este public, dar atenie cci uniunile administreaz spaiul de memorie al membrilor total diferit de cum l administreaz struct i class.

Tutoriale C++ - Clase (II) - POO Suprancrcarea operatorilor (Overloading operators)


Pentru a uura sintaxa de folosire a claselor, putem implementa operaiile (cum ar fi adunare, scdere, citire etc.) nu prin funcii membre, ci prin suprancrcarea operatorilor. Suprancrcarea (supradefinirea, sau overloading) operatorilor permite atribuirea de noi semnificaii operatorilor uzuali (operatorilor intlnii pentru tipurile de date predefinite). Invocarea lor este transparent utilizatorului clasei, rolul lor fiind de a aduce sintaxa de utilizare a clasei la nivelul de naturalee al tipurilor fundamentale (predefinite: int, char, double etc.). Prin suprancrcarea operatorilor, operaiile care pot fi executate asupra instanelor (obiectelor) unei clase pot fi folosite ca i n cazul tipurilor de date predefinite. Exemplu: ?

1 struct lol{ 2 string lmao; float rofl; 3 } a, b, c; 4 5 6 a = b + c;


Codul de mai sus va genera o eroare, deoarece operatorul + nu a fost definit pentru clasa lol. Dar s zicem c este definit, atunci b + c este defapt o funcie ce adun membrii celor dou obiecte ntre ei i returneaz un nou obiect de tip lol (cel puin aa mi imaginez eu c definesc adunarea a dou obiecte). Sau, n loc s folosesc urmtoarea funcie (presupun c este deja definit, la fel i operatorul): ?

1 if( a.isEqual(b) )
S fie posibil invocarea echivalent: ?

1 if( a == b )
Funcia trebuie s poat accesa datele membre private ale clasei, deci supradefinirea operatorilor se poate realiza n dou moduri: printr-o funcie membr a clasei sau printr-o funcie prieten a clasei. Iat tabelul cu operatorii suprancrcabili:

Operatori Suprancrcabili

+ <<= ~ new

&=

* >>= ^=

/ ==

= != |=

< &&

> <= ||

+= >= %=

-= ++ []

*= -()

/= % , &

<< ^ ->*

>> ! -> |

delete

new[]

delete[]

Pentru lista complet i mai multe informaii despre suprancrcarea operatorilor vizitai acest site. Se declar n felul urmtor: ?

1 tip operator simbol (parametri) { /*...*/ }


Unde simbol este unul dintre simbolurile de mai sus. n continuare voi implementa o clas numit Complex, ce va reprezenta numerele complexe (presupun c tii ce sunt alea XD). ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

class Complex { public: // -- Constructorii -- // Complex(): real(), imag() {} // constructor default/implicit ; initializez cu 0 Complex(int a, int b): real(a), imag(b) {} // ---- Functia de afisare ---- // void show() // Nu dati prea multa atentie acestei functii, nu este importanta { if(imag < 0) // daca 'imag' este negativ, nu mai afisez + cout << real << imag << "i\n"; else cout << real << '+' << imag << "i\n"; } // Supraincarcarea operatorului + // Complex operator+(Complex operand); private: int real; int imag; }; // Definesc adunarea a doua numere complexe Complex Complex::operator+(Complex operand) { // returnez un obiect de tip Complex return Complex(real + operand.real, imag + operand.imag); } int main() { Complex z1(2, 3), z2(5, -9), z3; // z3 z3 numele ambele expresii sunt echivalente = z1 + z2; // invocarea implicita folosind operatorul + = z1.operator+(z2); // invocarea explicita folosind functiei

z3.show(); // 7-6i system("PAUSE"); return 0;

37 }
Un operator poate fi definit fie ca membru (aa cum l am eu n exemplul de mai sus), fie ca funcie global (dar n acest caz funcia trebuie s aib acces la membrii private ai clasei i asta se poate numai dac exist o relaie de tip prietenie (friend) ntre clas i funcie). Voi vorbi despre prieteni n tutorialul urmtor. Prin convenie, primul operand (cel stng) al unui operator membru este obiectul invocator. n exemplul meu, obiectul invocator este z1, adic obiectul care invoc / apeleaz funcia operatorului; se vede foarte clar la invocarea explicit. Operatorul de atribuire este singurul operator implementat implicit n orice clas. ?

1 z1 = z2; // nu e nevoie sa-l definiti


Bineneles c-l putei redefini dup bunul plac. De asemenea nu exist reguli stricte cu privire la tipul returnat de funciile operator i nu suntei forai s respectai funcionalitatea matematic a fiecrui operator. Adic putei defini operatorul + s scad sau s nmuleasc sau s fac orice alt lucru, dar atunci astfel de operatori ar ncurca mai mult dect ar ajuta. Mai jos avei cteva tipuri de declarare a operatorilor:

Expresie @a a@ a@b a@b a->x + - * & ! ~ ++ -++ - -

Operator

Funcie membr A::operator@() A::operator@(int) A::operator@ (B) A::operator->()

Funcie global operator@(A) operator@(A,int) operator@(A,B) -

+ - * / % ^ & | < > == != <= >= << >> && || , A::operator@ (B) = += -= *= /= %= ^= &= |= <<= >>= [] ->

a(b,c...) ()

A::operator()(B,C...) -

nlocuii @ cu operatorul respectiv; a este un obiect de tipul clasei A, b de tipul clasei B, c de tipul clasei C. Am definit un operator binar (operatorul +; binar pentru c opereaz cu 2 operanzi), acum voi defini i unul unar. Un operator care o s-mi conjuge numrul complex. Conjugatul unui numr complex de forma a + ib este a - ib, deci tot ce va trebui s fac este s schimb semnul prii imaginare. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

// clasa este definita in exemplul de mai sus // operatorii unari declarati ca functii membre, nu iau parametri void Complex::operator!() { imag = imag * (-1); // schimb semnul } int main() { Complex z(4, 9); z.show(); // 4+9i !z; z.show(); // 4-9i z.operator!(); z.show(); // 4+9i system("PAUSE"); return 0; }

Cteva reguli de suprancrcare a operatorilor:

- Nu putei defini operatori noi, cum ar fi **. - Nu putei redefini sensul operatorilor atunci cnd sunt aplicai asupra tipurilor fundamentale. - Operatorii suprancrcai pot fi fie funcii membre nonstatice, fie funcii globale. Funciile globale care acceseaz membrii private sau protected trebuie declarate ca friend (prietene) ale clasei respective. O funcie global trebuie aib cel puin un parametru de tipul clasei respective. - Operatorii respect prioritatea, gruparea i numrul operanzilor aa cum se ntmpl cnd sunt aplicai tipurilor fundamentale. - Operatorii unari declarai ca funcii membre nu iau parametri; declarai globali, iau un parametru. - Operatorii binari declarai ca funcii membre iau un parametru; declarai globali, iau doi parametri. - Dac un operator poate fi folosit att unar ct i binar (&, *, +, i -), atunci l putei suprancrca separat. - Operatorii suprancrcai nu pot avea parametri default. - Toi operatorii suprancrcai, exceptnd atribuirea (operatorul=), sunt motenii n clasele derivate. Avei aici lista cu operatorii binari, iar aici lista cu operatorii unari.

Pointerul this
this reprezint un pointer ctre obiectul a crei funcie membr este executat. Mai simplu, this reprezint pointerul ctre obiectul curent (invocator) - cel care apeleaz metoda.
?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

class CDummy { public: int isitme (CDummy& y); }; int CDummy::isitme (CDummy& y) { if (&y == this) return true; else return false; } int main () { CDummy a; CDummy* b = &a; if ( b->isitme(a) ) cout << "da, &a este b"; return 0; }

Pointerul this nu este parte a obiectului unei clase. n schimb, atunci cnd un obiect invoc o funcie membr non-static, compilatorul transmite adresa obiectului invocator, printr-un argument ascuns, ctre funcia apelat. S lum exemplu urmtoarea apelare (presupunem c totul este definit undeva): ?

1 myDate.setMonth( 3 );
Poate fi interpretat ca: ?

1 setMonth( &myDate, 3 );
Acesta este modul prin care funcia membr vede ceilali membri ai obiectului. ?

1 2 3 4 5 6

void Date::setMonth( int mn ) { month = mn; // 'this' este asumat implicit this->month = mn; // 'this' este folosit explicit; este valid, dar NU-i necesar (*this).month = mn; // echivalent cu cele de sus }

Membri statici
O clas poate conine att funcii statice ct i date statice. Membrii statici sunt denumii variabile de clas, deoarece valoarea lor este aceeai n orice obiect al clasei. Putem spune c obiectele clasei mpart ntre ele variabilele de clas. Membrii non-statici (variabile de instan sau de obiect) sunt proprii obiectelor unei clase, adic fiecare obiect are copia lui de membru. n clasa Complex, membrii non-statici sunt imag i real. Asta nseamn c obiectele z1, z2 i z3 au propriile lor copii de imag i real; deci se aloc spaiu de memorie pentru membrii non-statici de fiecare dat la definirea unui obiect. Acum voi defini o clas ce conine un membru static (declarat cu keywordul static). Cnd declarai un membru static, o singur copie este partajat (mprit), sau pus n comun, tuturor obiectelor clasei. Datele membre statice trebuie definite n domeniul global (fiierului). Membrii static const pot fi iniializai n definiia (corpul) clasei. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class Dumb { public: static int count; // membru static Dumb() { count++; } ~Dumb() { count--; } }; int Dumb::count = 0; // datele membre statice se definesc in domeniul global int main() { Dumb a; cout << a.count; // 1 Dumb b[5]; cout << b->count; // 6 Dumb * s = new Dumb; cout << Dumb::count; // 7 delete s; cout << Dumb::count; // 6 system("PAUSE"); return 0; }

Membrii statici au aceleai proprieti ca variabilele globale dar le place domeniul clasei. Din momentul n care l-ai definit, un membru static exist (i poate fi accesat) chiar dac niciun obiect al clasei nu a fost definit. ntr-un program poate exista o singur definiie a unui membru static. Deoarece sunt variabile unice pentru toate obiectele unei clase, membrii statici pot fi accesai, fie folosind numele unui obiect (liniile 14 i 16), fie numele clasei (liniile 18 i 20). Pe scurt sunt variabile globale, dar specifice unei clase. Cnd declarai variabile statice ntr-o funcie, acele variabile i menin valoarea ntre apelrile funciei. Este ca i cum ar fi globale numai c sunt vizibile doar n funcia respectiv. Astfel de variabile se iniializeaz implicit cu 0. ?

1 2 3 4 5 6

void varstatic() { static int x; // valoarea este retinuta intre apelari x++; cout << x << " "; }

7 int main() 8 { // apelez de 5 ori functia varstatic 9 for(int i=1; i <= 5; i++) 10 varstatic(); // se afiseaza 11 // 1 2 3 4 5 12 13 system("PAUSE"); 14 return 0; 15 }
Observaie: Nu trebuie confundai membrii statici ai unei clase cu datele care au clasa de memorare static! Metodele figureaz ntr-un singur exemplar, oricte instane ale clasei ar exista! O funcie membr se apeleaz totdeauna n strns dependen cu un obiect din clasa respectiv. Legtura dintre obiect i funcia membr se face prin operatorul (.) sau operatorul ->, dup cum obiectul este desemnat prin nume sau prin pointer. Metodele statice pot fi apelate independent de un obiect al clasei, folosind operatorul de rezoluie (vizibilitate) (::). Datele membre statice figureaz ntr-un singur exemplar pentru toate instanele clasei, i pot fi modificate prin metode (funcii membre) statice. Apelul unei metode statice poate fi realizat ca un apel al unei metode obinuite (vezi mai jos: q.setCrap(4, q);) sau folosind operatorul de rezoluie (vezi: Crap::setCrap(5, q);). Datorit ultimului mod de apel, n care metoda static nu este asociat unui obiect anume, n corpul funciilor statice, nu pot fi accesate dect datele membre statice! Deoarece o metod static se apeleaz independent de un obiect al clasei, pointerul this nu mai poate fi utilizat, n respectiva metod. Pentru a accesa membrii instanei nestatici, trebuie s declarai funcia cu un parametru de tip referin (sau pointer) de tipul clasei respective. Not: Dac nu avei de gnd s modificai membrii, atunci funcia poate avea un parametru simplu de tipul clasei (vezi funcia showCrap din exemplul de mai jos). ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

class Crap { public: Crap() {x = 0;} Crap(int a) {x = a;} static void setCrap(int a, Crap &asta) {asta.x = a;} // prin referinta ca sa-l pot modifica static void doubleCrap(Crap *asta) {asta->x = asta->x * 2;} // exemplu cu pointer static void showCrap(Crap asta) {cout << asta.x;} private: int x; }; int main() { Crap q; q.setCrap(4, q); // sau mai puteam... Crap::setCrap(5, q); // acum ii dau valoarea 5 lui q Crap::showCrap(q); // 10 Crap::doubleCrap(&q); q.showCrap(q); // 10 system("PAUSE"); return 0; }

La declararea obiectelor unei clase, se aloc memorie pentru datele membre ale acelui obiect. Excepie de la aceast regul o constituie datele membre statice. Acestea figureaz ntr-un singur exemplar pentru toate instanele clasei respective. Funciile membre exist ntr-un singur exemplar pentru toate instanele clasei. Cam att despre static. Dac vrei s tii mai multe intrai aici.

Funcii membre const


Atunci cnd lucrm cu obiecte constante, compilatorul trebuie s tie dac o funcie modific sau nu obiectul respectiv. Proiectantul clasei (adic voi) poate specifica faptul c o funcie membr nu modific obiectul declarnd-o i definind-o const. const trebuie s apar att n definiia funciei ct i n declararea (prototipul) ei. Modificatorul const se plaseaza ntre lista de parametri i corpul funciei. Pentru obiecte constante pot fi invocate doar funciile constante. Constructorii i destructorii fac excepie de la acest regul. Ei nu trebuie declarai const pentru a putea fi aplicai obiectelor constante. Ca oricror variabile de tip predefinit, i obiectelor de tip definit de utilizator li se poate aplica modificatorul const. Pentru un obiect constant este permis doar apelul metodelor constante, a constructorilor i a destructorilor. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

class Crap { public: Crap() {x = 0;} Crap(int a) {x = a;} // Functia modifica membrul obiectului deci nu poate fi const void setCrap(int X) {x = X;} // Functia afiseaza membrul; nu-l modifica void showCrap() const {cout << x;} private: int x; };

int main() { const Crap q(9); q.setCrap(4); // eroare! q este const, iar functia setCrap nu este q.showCrap(); // 9 system("PAUSE"); return 0; }

Tutoriale C++ - Prietenie i motenire Funcii prietene


Prietenii sunt funcii sau clase declarate cu keywordul friend; se declar n corpul clasei, n general imediat dupheader-ul (antetul) clasei, dar nu-i o regul strict. Prietenii au acces la membrii private i protected ai clasei cu care sunt prieteni. Trebuie s nelegei c prietenii NU sunt funcii membre ale claselor, ele pur i simplu au acces la membrii nepublici ai clasei. Toate suprancrcrile unei funcii friend trebuie declarate explicit friend. Dac o funcie manipuleaz obiecte din dou clase diferite, atunci fie este declarat friend n fiecare, fie este membr a uneia i friend pentru cealalt.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

#include <iostream> using namespace std; class Crap { // --- Functii prietene cu clasa Crap --- // friend void setCrap(Crap&, int); friend void showCrap(Crap&); public: Crap() {x = 0;} Crap(int a) {x = a;} private: int x; }; void setCrap(Crap &someCrap, int a) { someCrap.x = a; // functia prietena are acces la membrii nepublici } void showCrap(Crap& someCrap) { cout << someCrap.x; } int main() { Crap q; setCrap(q, 5); showCrap(q); system("PAUSE"); return 0; }

O funcie poate fi n acelai timp funcie membr a unei clase i funcie prieten a altei clase: ?

1 2 3 4 5 6 7 8 9 10 11

class cls1 { // ... int f1(int); // f1 - metod a clasei cls1 // ... }; class cls2 { // ... friend int cls1::f1(int); // f1 - prietena a clasei cls2 // ... };

n cazul n care se dorete ca toate funciile membre ale unei clase s aib acces la membrii privai ai altei clase (s fie funcii prietene), prima clas poate fi declarat clasa prieten pentru cea de-a doua clas. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

class Patrat; // declaratie forward class Dreptunghi { public: Dreptunghi(): lat(), lun() {} // Dreptunghi(int x, int y): lat(x), lun(y) {} void arie() {cout << lat * lun;} void convert(Patrat a); // am nevoie de declaratia forward altfel compilerul n-ar stii cine este Patrat private: int lat, lun; }; class Patrat { public: Patrat(): latura() {} Patrat(int l): latura(l) {} friend class Dreptunghi; // Dreptunghi este friend cu Patrat private: int latura; }; void Dreptunghi::convert(Patrat a) { lat = a.latura; // Am acces la membrul private al obiectului 'a' de tip Patrat lun = a.latura; // Deoarece sunt prieten cu el } int main() { Dreptunghi alpha; Patrat phi(5); alpha.convert(phi); alpha.arie(); // 25 system("PAUSE"); return 0; }

Declaraia forward este pur i simplu antetul clasei. n exemplul de mai sus Dreptunghi este prieten cu Patrat, iar asta nseamn c Dreptunghi poate accesa membrii nepublici ai lui Patrat. Relaia de clasa prieten nu este tranzitiv! Patrat nu are acces la membrii nepublici ai lui Dreptunghi. Trebuie s-l declarai ca friend n clasa Dreptunghi.

Motenire (Inheritance)
Motenirea permite crearea claselor prin derivare. Adic n loc s implementeze caracteristici comune altor clase, o clas poate moteni date membre i funcii ale altor clase (numite i clase de baz). La rndul ei o clas derivat poate fi clas de baz ntr-o nou derivare. Atunci cnd folosim motenirea simpl, o clas este derivat dintr-o singur clas de baz. Motenirea multiplpresupune posibilitatea ca o clas s fie derivat din mai multe clase de baz. O clas derivat poate aduga noi date membre i funcii membre, astfel nct poate fi mai cuprinztoare dect clasa ei de baz. Motenirea conduce la structuri ierarhice arborescente. O clas de baz se afl ntr-o relaie ierarhic cu clasele derivate din ea. Clasele derivate motenesc toi membrii accesibili din clasa de baz. O clas derivat nu poate accesa direct membri private ai clasei de baz. Dac o clas derivat ar putea accesa membrii private ai clasei de baz, ar nclca principiul ncapsulrii pentru obiectele clasei de baz. Informaia comun apare n clasa de baz, iar informaia specific - n clasa derivat. Clasa derivat reprezint o specializare a clasei de baz. Orice clas derivat motenete datele membru i metodele clasei de baz. Deci acestea nu trebuie redeclarate n clasa derivat. Cnd o clas motenete membrii unei alte clase, membrii clasei de baz devin membrii ai clasei derivate. La modul general, la declararea unei clase derivate, se specific o list a claselor de baz, precedate de modificatorul de acces care precizeaz tipul motenirii: ?

1 class nume_clasa_derivata : public nume_clasa_baza 2 { /*...*/ };


Specificatorul public poate fi nlocuit cu private sau protected. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

#include <iostream> #include <string> using namespace std; class Animal { public: void setAnimal(string nume){ numeAnimal = nume; } protected: string numeAnimal; }; class Caine : public Animal { // in clasa Animal, 'numeAnimal' este protected, deci este vizibil in clase derivate public: void show() {cout << "Sunt un caine si ma numesc: " << numeAnimal << '\n';} }; class Vaca : public Animal { public: void show() {cout << "Sunt o vaca si ma numesc: " << numeAnimal << '\n';} }; int main() { Caine q; Vaca c; q.setAnimal("Rex"); // functie mostenita cu acces public din clasa Animal

29 30 31 32 33 34 35 } 36 37

q.show(); c.setAnimal("Cita"); c.show(); system("PAUSE"); return 0;

Dup cum tii membrii seciunii protected se comport ca membri public pentru clasele derivate i privatepentru restul programului. Membrii private ai claselor de baz sunt inaccesibili claselor derivate! Nivelele de acces ale claselor sunt rezumate n tabelul urmtor:

Nivel de accesibilitate Membrii aceleiai clase Membrii clasei derivate

public protected private DA DA DA DA NU DA NU NU

Non-membri (restul programului) DA

DA - au acces la membrii clasei de nivelul respectiv; NU - nu au acces. La derivarea unei clase dintr-o clas de baz public, membrii public din clasa de baz devin membri public ai clasei derivate i membrii protected ai clasei de baz devin membri protected ai clasei derivate. Membrii private ai clasei de baz nu pot fi accesai direct n clasele derivate, dar pot fi accesai prin funcii membre private sau protected din clasa de baz. La derivarea unei clase dintr-o clas de baz protected, membrii public sau protected din clasa de baz devin membri protected n clasa derivat. La derivarea dintr-o clas de baz private, membrii public i protected ai clasei de baz devin membriprivate n clasa derivat. Clasa Caine, pe lng funcia show, conine i membrul numeAnimal i funcia setAnimal, motenite din clasaAnimal cu acces protected, respectiv public. Dac nu este specificat, tipul motenirii pentru clasele declarate cu class este, implicit, private, iar pentrustruct este public. Specificatorul de acces ce urmeaz dup (:), la declararea unor clase derivate, reprezint tipul motenirii sau tipul derivrii. n funcie de specificator, membrii clasei de baz neprivai (public i protected), pot fi motenii, n clase derivate, cu nivele de acces diferite dect cele din clasa de baz (tiu c am zis mai sus asta, dar reiau). Dac tipul motenirii este: - public, atunci n clasa derivat, membrii vor fi motenii cu acelai nivel pe care-l au n clasa de baz (public sau protected). Pe scurt, public nu modific nivelul de ncapsulare al membrilor motenii din clasa de baz. - protected, atunci n clasa derivat, membrii vor fi motenii cu nivelul de acces protected. - private, atunci n clasa derivat, membrii vor fi motenii cu nivelul de acces private. De exemplu, definiia clasei Caine este urmtoarea: ?

1 2 3 4 5 1 2 3 4 5 6

class Caine : public Animal // public - membrii sunt mosteniti cu nivelul de acces pe care il au in clasa Animal { public: void show() {cout << "Sunt un caine si ma numesc: " << numeAnimal << '\n';} };

O definiie echivalent ce nu presupune derivare din clasa Animal, este urmtoarea: ?

class Caine { public: void setAnimal(string nume){ numeAnimal = nume; } void show() {cout << "Sunt un caine si ma numesc: " << numeAnimal << '\n';} protected:

7 8 }; 1 2 3 4 5 6 7 8

string numeAnimal;

Dac a fi derivat private sau protected (s zicem private), atunci definiia echivalent ar fi fost: ?

class Caine { public: void show() {cout << "Sunt un caine si ma numesc: " << numeAnimal << '\n';} private: string numeAnimal; void setAnimal(string nume){ numeAnimal = nume; } };

Dac acum a ncerca s deriv din clasa Caine, n clasele derivate a avea acces numai la funcia show. Membrii clasei de baz motenii pot fi exceptai de la regulile de derivare specificndu-le numele, i numai numele, i domeniul (clasa), n seciunile de acces dorite. ?

1 2 3 4 5 6

class Caine : private Animal // membrii sunt mosteniti private { public: Animal::setAnimal; // setAnimal face exceptie si este public; atentie la functii! doar numele! void show() {cout << "Sunt un caine si ma numesc: " << numeAnimal << '\n';} };

Dac clasa derivat definete un membru cu acelai nume ca al unuia din clasa de baz, atunci membrul din clasa derivat il va domina (ascunde) pe cel din clasa de baz. Trebuie s folosii operatorul de vizibilitate (::) pentru a v referi la membrul din clasa de baz: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

class Caine : public Animal { public: int numeAnimal; // acest membru il domina pe cel din clasa de baza void show() {cout << "Sunt un caine si ma numesc: " << Animal::numeAnimal << '\n';} la numeAnimal mostenit din clasa Animal void show2() {cout << "Numele meu de cod este: " << numeAnimal << '\n';} // ma refer numeAnimal al clasei Caine }; int main() { Caine q; q.setAnimal("Rex"); q.numeAnimal = 999; q.show(); // Sunt un caine si ma numesc: Rex q.show2(); // Numele meu de cod este: 999 system("PAUSE"); return 0; }

Ce nu se motenete?
n principiu clasa de derivat motenete fiecare membru al clasei de baz, exceptnd:

- constructorii i destructorul; - funciile membre operator=(); - prietenii (friend). Constructorii claselor de baz i operatorii de asignare din clasele de baz nu se motenesc n clasele derivate. Constructorii i operatorii de asignare din clasele derivate pot, ns, s apeleze constructorii i operatorii de asignare din clasele de baz. Chiar dac destructorul i constructorii nu sunt motenii, constructorul implicit (default; cel far parametri) al clasei de baz i destructorul sunt invocai (apelai) de fiecare dat cnd un obiect al clasei derivate este creat sau distrus. n lipsa constructorului implicit compilatorul semnaleaz o eroare. La instanierea unui obiect din clasa derivat se apeleaz mai inti constructorii claselor de baz, n ordinea n care acetia apar n lista din declararea clasei derivate. La distrugerea obiectelor, se apeleaz nti destructorul clasei derivate, apoi destructorii claselor de baz. Dac clasa de baz nu are constructor implicit sau vrei ca un constructor suprancrcat (al clasei de baz) s fie apelat n locul constructorului default, atunci cnd un obiect al clasei derivate este creat, putei specifica n urmtorul fel: ?

1 (parameteri) {...}
Exemplu: ?

nume_constructor_derivat (parameteri) : nume_constructor_baza

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

class mother { public: mother () { cout << "mother: no parameters\n"; } mother (int a) { cout << "mother: int parameter\n"; } }; class daughter : public mother { public: daughter (int a) // nimic specificat; invoc constructorul implicit { cout << "daughter: int parameter\n\n"; } }; class son : public mother { public: son (int a) : mother (a) // constructor specificat: invoca mother(a) { cout << "son: int parameter\n\n"; } }; int main () { daughter cynthia (0); son daniel(0); return 0; } // Output mother: no parameters daughter: int parameter mother: int parameter son: int parameter

33
n C++ este posibil motenirea multipl. Exemplu: ?

1 class CRectangle: public CPolygon, public COutput; 2 class CTriangle: public CPolygon, public COutput;
n lista de derivare (cea care ncepe cu (:)), adugai clasele din care se deriv, nsoite de tipul motenirii, i separate prin virgul (ca-n exemplu).

Tutoriale C++ - Polimorfism (Polymorphism)


O clas derivat poate suprascrie (override) o funcie membr a clasei de baz printr-o nou versiune a acestei funcii cu aceeai semntur (adic acelai nume, tip, numr parametri, tip parametri). Dac semnturile sunt diferite, atunci ar fi vorba de suprancrcare (overload), nu se suprascriere. Cnd numele acestei funcii suprascrise este menionat n clasa derivat, este selectat automat versiunea din clasa derivat. Poate fi folosit i versiunea din clasa de baz, dar pentru asta trebuie folosit operatorul domeniu (de rezoluie). n cadrul motenirii public, obiectele clasei derivate pot fi tratate ca obiecte ale clasei de baz. Acest lucru este corect deoarece clasa derivat are membri corespunztori tuturor membrilor clasei de baz. Poate avea, ns, i ali membri, deci putem spune c o clas derivat are mai muli membri dect clasa ei de baz. Motenirea public permite ca un pointer la un obiect dintr-o clas derivat s fie convertit implicit ntr-un pointer la un obiect al clasei sale de baz deoarece un obiect al clasei derivate este i obiect al clasei de baz. Referirea unui obiect al clasei derivate printr-un pointer la clasa de baz este acceptat deoarece un obiect al clasei derivate este i obiect al clasei de baz. Printr-o astfel de secven de cod se pot referi doar membrii clasei de baz. Referirea unui obiect al clasei de baz printr-un pointer la clasa derivat este eroare de sintax. Pointerul la clasa derivat trebuie convertit mai nti la un pointer la clasa de baz. Polimorfismul este implementat prin funciile virtuale. Atunci cnd programul cere folosirea unei funcii printr-un pointer sau o referin la o clasa de baz, C++ alege suprascrierea corect din clasa derivat corespunztoare obiectului care o apeleaz. Aa cum s-a observat deja din tutorialele anterioare, unii membri (fie date membre, fie metode) ai unei clase de baz pot fi redefinii n clasele derivate din aceasta. Redefinirea unei metode a unei clase de baz ntr-o clas derivat se numete polimorfism. S lum urmtorul exemplu: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#include <iostream> #include <string> using namespace std; class OM { public: void set() { ce_fel_de = "Inteligent"; } void arata() { cout << "Sunt Inteligent" << '\n'; } void euSunt() { set(); cout << "Eu sunt un OM " << ce_fel_de << '\n'; } protected: string ce_fel_de; }; class PROST : public OM

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

{ public: void set() { ce_fel_de = "Prost"; } void arata() { cout << "Sunt Prost" << '\n'; } }; int main() { OM John, * Jane; PROST Bob; John.euSunt(); // Eu sunt un OM Inteligent Bob.euSunt(); // Eu sunt un OM Inteligent Bob.arata(); // Sunt Prost ; apelez metoda dominanta din clasa derivata Bob.OM::arata(); // Aici apelez explicit functia arata() mostenita din clasa de baza Jane = new PROST; Jane->euSunt(); // Eu sunt un OM Inteligent Jane->arata(); // Sunt Inteligent system("PAUSE"); return 0; }

Aa cum observai att John ct i Bob (momentan o ignorm pe Jane) afieaz acelai mesaj, chiar dac n clasaPROST, membrul motenit ia valoarea "Prost". V ntrebai de ce? Metoda euSunt() nu este redefinit n clasa derivat, ea este motenit i tot ea este cea care iniializeaz membrulce_fel_de prin intermediul metodei set(). n faza de compilare, compilatorul identific funcia membr euSunt(), dar nu se poate spune acelai lucru despre funcia set() care este invocat n momentul execuiei; atunci nu se tie la ce funcie ne referim i, deci, va fi invocat set() din clasa de baz. Acum s trecem la Jane. Una din principalele caracteristici ale derivrii publice (sau motenirii) este c un pointer ctre clasa de baz poate s aib ca valoare adresa unui obiect dintr-o clas derivat - n exemplul meu, Jane este un pointer ctre tipul OM, dar ia ca valoare (aloc dinamic mai trziu n program) un obiect de tipul PROST (clas derivat din OM). n aceast situaie, se apeleaz metodele din clasa pointerilor (adic tipul de baz al pointerilor), i nu din clasa obiectului spre care pointeaz pointerul. n cazul lui Jane se apeleaz metodele din clasa OM, chiar dac Jane pointeaz ctre un obiect de tip PROST. n toate cazurile prezentate anterior, identificarea metodei redefinite se realizeaz n faza de compilare. Este vorba de o legare inial, "early binding", n care toate informaiile necesare selectrii metodei (de exemplu: se cunoate c Bob este de tip PROST, deci compilatorul tie ce metod s selecteze atunci cnd este invocat; vezi liniile 32-33) sunt prezentate din timp i pot fi utilizate din faza de compilare. Problema apare atunci cnd avem pointeri ctre clasa de baz, iar ca valoare, pointerii iau adresa unui obiect dintr-o clas derivat. n acest caz, informaiile necesare identificrii metodelor redefinite sunt disponibile abia n momentul execuiei programului (deoarece nu se tie ctre cine pointeaz pointerul, dect n momentul execuiei, cnd i este atribuit o valoare). O rezolvare a identificrii metodei n momentul execuiei programului o constituie funciile virtuale.

Funcii virtuale
Identificarea unei metode supradefinite, n momentul execuiei, se numete legare ulterioar (trzie), "late binding". Metodele virtuale se declar cu keyword-ul virtual n clasa de baz, iar n clasele derivate metodele cu aceeai semntur sunt considerate implicit virtuale (nu este necesar declararea cu virtual n clasele derivate). n cazul unei funcii declarat virtual n clasa de baz i redefinit n clasa derivat, redefinirea metodei n clasa derivat are prioritate fa de definirea ei din clasa de baz. Practic aceeai funcie din clasa de baz este redefinit n clasele derivate; se implementeaz diferite versiuni ale aceleiai funcii n clasele derivate. Prototipul unei metode virtuale redefinite trebuie s coincid cu cel specificat n clasa de baz. ?

class OM

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

{ public: virtual void set() { ce_fel_de = "Inteligent"; } // functii virtual void arata() { cout << "Sunt Inteligent" << '\n'; } // virtuale void euSunt() { set(); cout << "Eu sunt un OM " << ce_fel_de << '\n'; } protected: string ce_fel_de; }; class PROST : public OM { public: void set() { ce_fel_de = "Prost"; } void arata() { cout << "Sunt Prost" << '\n'; } }; int main() { OM John, * Jane; PROST Bob; John.euSunt(); // Eu sunt un OM Inteligent Bob.euSunt(); // Eu sunt un OM Prost Bob.arata(); // Sunt Prost Bob.OM::arata(); // Sunt Inteligent Jane = new PROST; Jane->euSunt(); // Eu sunt un OM Prost Jane->arata(); // Sunt Prost system("PAUSE"); return 0; }

Observai c funciile virtuale rezolv toate problemele pe care le-am ntlnit la nceputul tutorialului. Iniializnd pointerul la clasa de baz cu un pointer la o clasa derivat (linia 31), programul va alege dinamic (n timpul rulrii) implementarea funciei set() din clasa derivat bazndu-se pe tipul obiectului apelant. Acest proces se mai numete i legare dinamic (dynamic binding). Cnd o funcie virtual este apelat referind un obiect prin nume i selecia funciei membre prin operatorul punct (.), referina este rezolvat la compilare (static binding), iar funcia virtual apelat este cea definit pentru clasa creia i aparine obiectul. n cazul unei ierarhii de clase i a unei metode virtuale a clasei de baz, toate clasele derivate care motenesc aceast metod i nu o redefinesc, o motenesc ntocmai. Pentru aceeai metod motenit i redefinit n clasele derivate, selecia se realizeaz n momentul executrii programului (legarea trzie). Funciile virtuale nu pot fi metode statice ale clasei din care fac parte. Funciile virtuale nu pot fi funcii prietene sau constructori, dar pot fi destructori. Destructorii virtuali sunt utili n situaiile n care se dorete distrugerea uniform a unor masive de date eterogene (diferite).

Metode virtuale pure


Metodele virtuale pure sunt metode care se declar n clase de baz, dar nu se definesc. O metod virtual purtrebuie s fie prezent (dac vrei putei s n-o definii, dar trebuie s-o declarai) n orice clas derivat. O funcie virtual devine pur dac declaraia sa este urmat de =0:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

class OM // clasa abstracta { public: virtual void set() { ce_fel_de = "Inteligent"; } virtual void arata() = 0; // functie virtuala pura void euSunt() { set(); cout << "Eu sunt un OM " << ce_fel_de << '\n'; } protected: string ce_fel_de; }; // ... // ... int main() { /*OM John;*/ OM * Jane; PROST Bob; // John.euSunt(); // John nu poate fi de tipul OM deoarece OM este clasa abstracta // ... // ... }

O clas cu cel puin o metod virtual pur se numete clas abstract. Clasele abstracte nu pot fi instaniate (nu se pot crea obiecte de tipul acestor clase) deoarece conin membri neimplementai (funciile virtuale pure). Rolul unei clase abstracte este acela de a crea o clas de baz din care alte clase pot moteni interfaa sau implementarea. Clasele din care pot fi instaniate obiecte sunt clase concrete. Deoarece acestea sunt folosite drept clase de baz n ierarhii de motenire, obinuim s le numim clase de baz abstracte. Dac o clas este derivat dintr-o clas de baz care conine o funcie virtual pur i nu definete acea funcie, atunci funcia este pur i n clasa derivat, iar clasa derivat este i ea abstract.

Template-uri de funcii
Template-urile (abloanele) reprezint una dintre cele mai puternice caracteristici ale limbajului C++. Acestea permit definirea, printr-un singur segment de cod, a unei ntregi game de funcii suprancrcate template de funcie sau a unei ntregi serii de clase template de clas.
Dac o funcie implementeaz operaii identice ce pot fi aplicate mai multor tipuri de dat, atunci putem defini funcia mai simplu (mai compact) cu un template. Deci nu mai este necesar suprancrcarea pentru tipuri diferite ale parametrilor. Programatorul trebuie s scrie doar o singur definiie de template de funcie. Bazndu-se pe tipurile argumentelor date explicit sau rezultate din apeluri ale acestor funcii, compilatorul genereaz funcii separate n codul obiect pentru a manipula corespunztor fiecare tip de apel. Toate definiiile de template-uri de funcii ncep prin cuvntul cheie template urmat de lista parametrilor formali de tip ai template-ului de funcie plasat ntre < >. Fiecare parametru formal de tip trebuie s fie precedat de cuvntul cheie class sau de typename. Un parametru formal de tip sau parametru template este utilizat pentru a specifica tipul argumentelor funciei, tipul valorii returnate de funcie i tipurile variabilelor declarate n funcie. Cuvintele cheie class (n acest context) i typename au semnificaia de "orice tip de dat predefinit sau definit prin program". Exemplul de mai jos definete o funcie template care calculeaz maximul dintre dou obiecte (de tip generic):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Atunci cnd compilatorul detecteaz apelul funciei maxElem, tipul parametrului generic (adic T) este nlocuit cu tipul parametrului actual (adic int) i se creeaz o funcie separat (de acest tip) care este compilat. Aa cum vedei, prin T se definete generic tipul valorii returnate de funcie i tipul parametrilor funciei. n acest program, mecanismul template-urilor face ca programatorul s nu mai fie nevoit s scrie dou funcii suprancrcate cu prototipurile: ?

template<class T> T maxElem(T a, T b) { if(a < b) return b; else return a; }

int main() { int x = 34, y = 56; float p = 12.4, q = 4.23; cout << maxElem(x, y) << '\n'; // 56 cout << maxElem(p, q); // 12.4 system("PAUSE"); return 0;

1 2

int maxElem(int a, int b); float maxElem(float a, float b);

De asemenea putem defini template-uri care accept mai muli parametri: ?

1 2 3

template<class T, typename B> // am folosit si typename T maxElem(T a, B b) {

4 5 6
}

if(a < b) return b; else return a;

P.S. T (i B) poate fi nlocuit cu orice identificator valid.

Template-uri de clase
Template-urile (abloanele) de clase se mai numesc i tipuri parametrizate i se folosesc pentru crearea claselor generice. Au aceeai funcionalitate ca i la funcii. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

#include <iostream> #include <cassert> using namespace std;

template<class T, int count> // aici am si un parametru non-tip - count class Vector { public: // --- Constructor si Destructor --- // Vector() { ptr = new T[count]; } ~Vector() { delete[] ptr; } // --- Metode de acces --- // T getAt(int index); void putAt(int index, T elem); private: T * ptr; }; // --- Implementare --- // template<class T, int count> T Vector<T, count>::getAt(int index) {

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
} } }

assert(index >= 0 && index < count); return ptr[index];

template<class T, int count> void Vector<T, count>::putAt(int index, T elem) { assert(index >= 0 && index < count); ptr[index] = elem;

int main() { Vector<int, 5> x; // Declar un vector de tip int si 5 elemente // Initializez vectorul cu niste valori for(int i = 0; i < 5; i++) x.putAt(i, i * i); // Afisez for(int i = 0; i < 5; i++) cout << x.getAt(i) << ' '; system("PAUSE"); return 0;

Observaii: 1. Definiia n afara clasei a fiecrei funcii membre ncepe cu header-ul: ?

template<class T, int count>

2. Operatorul binar domeniu :: este folosit mpreun cu numele clasei template Vector<T, count> pentru a face asocierea dintre definiia funciei membre i domeniul clasei template. 3. Un template accept i parametri non-tip. n exemplul meu, count este folosit pentru a iniializa vectorul cu un

anumit numr de elemente. 4. La funcii nu era necesar invocarea explicit cu < >, deoarece compilatorul nlocuia tipul parametrilor generici cu tipul parametrilor efectivi; la clase, ns, trebuie s transmitei tipul de dat n mod explicit (linia 34). P.S. assert este o funcie definit n cassert. Evalueaz expresia, iar dac rezultatul este false lanseaz o eroare pe care o tiprete pe ecran i termin programul prin apelul funciei abort().

Specializri Template
Exist situaii cnd vrem s implementm o anumit versiune de clas atunci cnd se transmite un anumit tiptemplate-ului. Cum facem asta? Specializm sau declarm o specializare a acelui template. De exemplu dac am o clas i vreau s-o definesc diferit atunci cnd parametrul template este char. Voi folosi urmtoarea sintax: ?

1 2 3

template<> class Nume_Clasa<tip_de_data> { ... } ;

Un exemplu concret: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// specializare template #include <iostream> using namespace std;

// template de clasa: template <class T> class mycontainer { T element; public: mycontainer (T arg) {element=arg;} T increase () {return ++element;} };

// template de clasa specializat: template <> class mycontainer <char> { char element;

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
} };

public: mycontainer (char arg) {element=arg;} char uppercase () { if ((element>='a')&&(element<='z')) element+='A'-'a'; return element; }

int main () { mycontainer<int> myint (7); mycontainer<char> mychar ('j'); cout << myint.increase() << endl; cout << mychar.uppercase() << endl; return 0;

Observaii: 1. Numele clasei este precedat de template<> pentru a indica specializarea template. 2. Toate datele membre i funcii membre trebuie definite cu tipuri concrete deoarece nu exist "motenire" ntretemplate i specializarea lui. Privii, n exemplu, membrii element, increase ...

Tutoriale C++ - Scope. Lifetime. Namespace


Termenul scope definete domeniul de accesibilitate al identificatorilor. Lifetime este perioada de existen a unui identificator.

Scope
Am mai discutat despre asta n primele tutoriale, aa c nu voi relua anumite detali. Domeniul de accesibilitate, scope, este regiunea din program n care se poate folosi un identificator. Domeniul local este domeniul unui identificator declarat ntr-un bloc, din punctul declaraiei pn la sfritul blocului.

Domeniul global (domeniul fiier) este domeniul unui identificator declarat n afara oricrei funcii sau clase, din punctul declaraiei pn la sfritul fiierului care conine codul. Domeniul clas se refer la tipurile de date introduse prin intermediul claselor. Variabilele globale i constantele globale sunt declarate n afara tuturor funciilor sau claselor. Atunci cnd o funcie declar un identificator local cu acelai nume ca cel global, identificatorul local este cel folosit n funcie. Acest principiu se numete precedena numelui sau ascunderea numelui. Folosim operatorul domeniu :: pentru a accesa variabila global.

Lifetime
Perioada de existen a unei variabile este perioada de timp n care un identificator are alocat o zon de memorie. Putem face observaia c domeniul de accesibilitate este un element legat de faza de compilare, n timp ce perioada de existen este legat de faza de execuie. Variabilele automatice sunt alocate la intrarea ntr-un bloc i dealocate la ieirea din bloc. Variabilele statice rmn alocate pe durata ntregului program. Variabilele globale sunt de tip static. Toate variabilele declarate ntr-un bloc (variabile locale) sunt automatice. Dac folosim cuvntul cheie static atunci cnd declarm o variabil, aceasta devine static i perioada ei de existen nu se va ncheia odat cu terminarea blocului. Ea va fi disponibil de la un apel al unei funcii la altul. n general, se prefer declararea unei variabile locale statice dect folosirea unei variabile globale. Ca i o variabil global, ea rmne alocat pe durata ntregului program, ns este accesibil doar n interiorul funciei n care a fost declarat.

Namespace
Un namespace (spaiu de nume) este o colecie de clase, funcii i obiecte, toate grupate sub un singur nume. n acest fel domeniul global poate fi divizat n "subdomenii", fiecare cu nume propriu. Pentru a folosi un membru al unui namespace, acesta trebuie s fie precedat de numele namespace-ului: ?

nume_namespace::membru

Alternativ, se poate folosi declaraia using naintea identificatorului care este folosit. n mod obinuit, declaraiileusing sunt plasate la nceputul fiierului n care sunt folosii membrii namespace-ului. ?

using namespace nume_namespace;

Aceast instruciune speficic faptul c membrii lui nume_namespace pot fi folosii n fiier fr a preceda fiecare identificator cu numele namespace-ului i operatorul domeniu ::. Exemple: ?

1 2 3 4 5 6 7

// Exemplu using #include <iostream> using namespace std;

namespace first { int x = 5; int y = 10;

8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
?

namespace second { double x = 3.1416; double y = 2.7183; }

int main () { using first::x; // in functia 'main' folosesc identificatorul 'x' din 'first' using second::y; // si 'y' din 'second' cout << x << endl; cout << y << endl; cout << first::y << endl; // de data asta, trebuie sa specific in mod explicit cout << second::x << endl; // namespace-ul care apartin identificatorii return 0; } de

1 2 3 4 5 6 7 8

// Exemplu using namespace #include <iostream> using namespace std;

namespace first { int x = 5; }

9 10 11 12 13

namespace second { double x = 3.1416; }

int main () {

14 15 16 17 18 19 20 21 22 23 24 25
}

{ using namespace first; // in acest bloc (observati acoladele) folosesc toti cout << x << endl; // identificatorii (numele) din 'first' }

{ using namespace second; cout << x << endl; } return 0;

Cuvintele cheie using i using namespace au validitate numai n blocul n care sunt folosii (dac sunt folosii global, atunci vor avea efect n tot fiierul). Un namespace poate fi declarat la nivel global sau n interiorul unui alt namespace. Un namespace fr nume ocup ntotdeauna domeniul global, cel din care fac parte i variabilele globale. Un alias de namespace poate fi declarat n felul urmtor: ?

namespace nume_nou = nume_curent;

Pur i simplu i mai asociez un nume unui namespace existent. ?

namespace std = Neimspeise; // xD

Tutoriale C++ - Tratarea excepiilor


Stilul C++ de tratare a excepiilor permite interceptarea tuturor excepiilor (erori de runtime) sau a tuturor excepiilor de un anumit tip. Aceasta face programul mult mai robust, reducnd probabilitatea de ntrerupere neplanificat a

programului. Tratarea excepiilor se folosete n situaia n care programul poate s i continue rularea dup ce depete eroarea care provoac excepia.

try - throw - catch


Programatorul trebuie s includ ntr-un bloc try codul care ar putea genera o eroare generatoare a unei excepii. Blocul try este urmat de unul sau mai multe blocuri catch. Fiecare bloc catch specific tipul excepiei pe care o poate detecta i trata. Dac excepia se potrivete cu tipul parametrului unuia dintre blocurile catch, se execut codul acelui catch. Dac nu este identificat niciun bloccatch care s trateze eroarea, se apeleaz funcia predefinit terminate care la rndul ei apeleaz n mod implicit funcia predefinit abort pentru ntreruperea programului. Dac ntr-un bloc try nu se genereaz nicio excepie, programul ruleaz ignornd blocurile catch asociate lui. Se folosete throw pentru a genera sau arunca (lansa) excepia. Nu se garanteaz c excepia va fi i tratat n afara funciei. Pentru aceasta, trebuie specificat o secven de cod care detecteaz sau prinde excepia i o trateaz (blocultry...catch). ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

int imparte(int a, int b) { if(b == 0) // nu pot imparti prin zero throw "Wow! Nu pot face asta!"; // arunc o exceptie in cazul asta return a / b; }

int main() { try { imparte(5, 0); // imparte va lansa exceptia; b == 0 } catch(char * ptr) // detectez exceptiile de tip char* { cout << ptr; // le tratez; in acest caz afisez mesajul }

20 21
}

system("PAUSE"); return 0;

Sau a putea folosi clasa runtime_error care face parte din biblioteca standard i este declarat n fiierul headerstdexcept. Este derivat din clasa de baz exception care face parte din fiierul header exception i care este folosit pentru a declana excepii n C++. Declanarea excepiilor cu ajutorul lui runtime_error permite adugarea unui mesaj care poate fi citit prin metoda what(). Limbajul C++ ofer o ntreag gam de clase derivate din exception, destinate diverselor tipuri de excepii care se pot declana ntr-un program. La rndul su, programatorul poate s i defineasc propriile sale clase pentru declanarea excepiilor, derivndu-le, spre exemplu, din exception sau din clasele derivate din ea. Acelai exemplu, de data aceasta cu runtime_error: ?

#include <iostream>

1 2 3 4 5 6 7

#include <exception> using namespace std;

int imparte(int a, int b) { if(b == 0) throw runtime_error("Wow! Nu pot face asta!"); return a / b; }

8 9 10 11 12 13 14 15 16 17 18 19

int main() { try { imparte(5, 0); } catch(runtime_error& e) { cout << e.what(); }

20 21 22 23 24 25
Uneori, tratarea unei excepii nu poate fi fcut n blocul catch care a detectat-o i atunci se poate decide ca excepia s fie transmis mai departe, lasnd tratarea ei pe seama altei secvene de cod (rethrowing an exception). Instruciunea throw; lanseaz din nou excepia. ?

system("PAUSE"); return 0; }

1 2 3

void aruncExceptie() { try {

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
} } exceptie }

throw "Sunt o exceptie!"; // generez o

catch(char * ptr) { throw; // nu vreau s-o tratez aici asa ca o relansez }

int main() { try { aruncExceptie();

catch(char * p)

19 20 21 22 23 24 25 26
}

{ cout << p; // Sunt o exceptie }

system("PAUSE"); return 0;

Tutoriale C++ - Citire (scriere) din (n) fiier - fstream Intrri i ieiri din fiiere
Fiierul este o zon pe un suport de stocare (exemplu hard disc) desemnat printr-un nume, care pstreaz o colecie de date (exemplu: codul programului scris cu un editor). Pentru a citi sau scrie n fiier folosim urmtoarele clase definite n header-ul fstream: ifstream i ofstream. Acestea sunt derivate din clasele, deja cunoscute, istream i ostream. Toate operaiile valabile pentru un istream: >>, get, getline sunt valabile i pentru tipul ifstream. Operaiile folosite pentru un ostream: <<, endl, se pot folosi i pentru un ofstream. Modul de utilizare a fiierelor Pentru a folosi un fiier n operaiile I/O trebuie s parcurgem patru pai: 1. Includem fiierul header fstream; 2. Declarm stream-urile folosind instruciunile de declarare; 3. Pregtim fiierele pentru citire i scriere prin metoda (funcia membr) open; 4. Specificm numele stream-ului n fiecare instruciune de citire sau de scriere.

ifstream se utilizeaz doar pentru fiiere de intrare, iar ofstream doar pentru fiiere de ieire.
?

1 2 3 4 5 6 7 8 9

#include <iostream> #include <fstream> using namespace std;

int main() { // Declar stream-urile ifstream fin; ofstream fout;

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
} // Inchid fisierele si eliberez stream-urile; Acum pot deschide alte fisiere fin.close(); fout.close(); return 0; int numar; fin >> numar; // Citesc un numar din fisier fout << "Scriu in fisier"; // Pregatesc fisierele pentru scriere si citire, deci le deschid fin.open("data.in"); fout.open("data.out");

Sau puteam mai simplu, iniializnd prin metoda constructorului: ?

int main()

1 2 3 4 5 6 7 8 9 10

{ // Declar stream-urile si deschid fisierele ifstream fin("data.in"); ofstream fout("data.out");

fout << "Scriu in fisier";

int numar; fin >> numar; // Citesc un numar din fisier

// Inchid fisierele si eliberez stream-urile;

11 12 13 14

Acum pot deschide alte fisiere fin.close(); fout.close(); return 0; }

15 16
Obiectele cin i cout nu trebuie declarate n fiecare program pentru c ele sunt declarate n fiierul iostream, citirile de la tastatur i scrierile pe ecran fiind operaii frecvente. Spre deosebire de ele, stream-urile pentru lucrul cu fiiere trebuie declarate n program pentru c fiecare aplicaie folosete propriile fiiere de date. Pentru un fiier de intrare, funcia open poziioneaz markerul de citire pe primul element din fiier. Pentru un fiier de ieire, funcia verific dac acesta exist. Dac exist, terge vechiul coninut al su. Dac nu exist, l creeaz. Apoi markerul de scriere este aezat pe prima poziie. Markerul de citire sau de scriere reprezint locul din care se va citi dintr-un stream de intrare, respectiv se va scrie ntrun stream de ieire. Un motiv pentru care un stream intr n fail state (adic a ntmpinat nite erori) este incercarea de deschidere a unui fiier de intrare care nu exist. Se folosete metoda good() pentru a verifica dac streamul se afl n fail state. Funcia returneaz false dac streamul este n fail state. De asemenea, un stream de citire dintr-un fiier intr n fail state atunci cnd se citete din fiier dincolo de caracterul EOF, cel care marcheaz finalul fiierului. Se folosete metoda eof() pentru a verifica dac markerul de citire a ajuns la sfritul fiierului. Funcia returneaztrue dac markerul este la sfrit.

C++: Operatori pe Bii Deplasarea spre stnga


Se realizeaz cu operatorul leftshift <<. Acesta mut toi biii spre stnga cu un anumit numr de locuri. ?

variabila << nr_locuri

De exemplu, numrul 8 n form binar este 00001000. Dac vreau s-i deplasez biii spre stnga cu 2 locuri, voi obine 00100000. Iar asta este forma binar a numrului32.

Deplasarea spre stnga este echivalent cu nmulirea unui numr cu o putere de-a lui 2.
?

1 2 3 4

int mult_by_pow_2(int number, int power) { return number << power; }

Dar ce se ntmpl cnd ncerci s deplasezi un numr ca 128 i care este stocat ntr-un singur byte?

Un byte (octet) conine 8 bii ! 128 == 10000000, iar dac-l deplasez spre stnga cu un singur loc (echivalent cu 128 * 2) obin 00000000, care este 0. Deci ntr-un byte (char ocup 1 byte) nu pot avea un numr mai mare ca 256 (128 * 2).

Deplasarea spre dreapta


Se realizeaz cu operatorul rightshift >>. Acesta mut toi biii spre dreapta cu un anumit numr de locuri. ?

variabila >> nr_locuri

Deplasarea spre dreapta este echivalent cu mprirea ntreag unui numr la o putere de-a lui 2.
mprire ntreag, adic rezultatul este un numr ntreg. Exemplu: 5 / 2 = 2 Acum, spaiile libere sunt completate n felul urmtor: Dac lucrezi cu tipuri signed (ex: char), atunci spaiile libere se vor completa cu primul bit din reprezentaia binar (primul de la dreapta, cel mai semnificativ) pentru a pstra semnul. 1 nseamn negativ, iar 0 este pozitiv. Dac lucrezi cu unsigned atunci spaiile se completeaz cu 0. S lum din nou 128. Dac vreau s-l stochez ntr-o variabil de tip char(1 byte), voi observa c atunci cnd l afiez mi arat -128 !! De ce? ?

1 2 3 4 5 6 7 8 9

#include <iostream> using namespace std;

int main() { char x = 128; // char (1 byte) signed // -128 !!

cout << (int)x; return 0; }

Hai s revedem forma binar a lui 128: 10000000. Observi c primul bit este 1, asta nseamn negativ i mai nseamn c ntr-un signed char nu voi putea avea niciodat un numr pozitiv mai mare ca 127 (dect dac este unsigned char). Privete tabelele de mai jos:

Signed Char 0 1 1 1 1 1 1 1 = 127

0 0 0 0 1 1 1 1

1 1 1 1 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0

= 126 = 2 = 1 = 0 = -1 = -2 = -127 = -128

Unsigned Char 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 = 127 = 126 = 2 = 1 = 0 = 255 = 254 = 129 = 128

i (&) pe bii
Acest operator face acelai lucru ca i operatorul && doar c face asta la nivelul biilor. 1 este True, iar 0 este False. ?

1 2 3 4
?

01001000 & 10111000 = -------00001000

72 & 184 = 8

Sau (|) pe bii


Similar cu ||, dar la nivelul biilor: ?

1 2 3 4
?

01001000 | 10111000 = -------11111000

72 | 184 = 248

Ce poi face cu astfel de operaii? Fii atent! S zicem c ai numrul: XX?XXXXX i vrei s afli ce bit se afl pe poziia?. Poi folosi operatorul & ca s aflii asta: ?

1 2 3 4

XX?XXXXX & 00100000 = -------00#00000

Dac ? este 1 atunci rezultatul va fi 00100000 (un numr diferit de 0). Dac ? este 0 atunci rezultatul va fi 00000000 (adic 0). Cum aflii acel numr (n acest caz, 00100000) care te ajut s aflii bitul de pe poziia n? Ai uitat deja? ncepi de la valoarea 1 (00000001) i-l deplasezi n-1 poziii spre stnga ca s obii numrul de care ai nevoie.

Complementare pe bii
Operatorul de complementare (negaie bit cu bit) ~ inverseaz (d peste cap) fiecare bit din forma binar a unui numr. Mai exact face exact ce face operatorul ! (NOT) dar la nivelul biilor. ?

1 2 3
?

00001111 ~ --------11110000

// 15

// -16

cout << ~1; // afiseaza -2 | 11111110

Sau Exclusiv (XOR) (^)


Acest operator nu are un operator boolean corespunztor (aa cum au avut cele de pn acum). Operatorul ^ (XOR) returneaz 1 numai atunci cnd unul din ce doi bii este 1. Dac ambii bii sunt 1 sau 0, XORreturneaz 0 (de aia i zice exclusiv). ?

1 2 3 4

01110010 ^ 10101010 -------11011000

Este numrul putere de-a lui 2?


Iat cum poi afla dac un numr (ntreg) este putere a lui 2, folosind operaii pe bii. ?

1 2 3 4 5 6

#include <iostream> using namespace std;

bool esteP2(int nr) { return !((nr-1) & nr); }

7 8 9 10 11 12 13 14 15 16
} return 0; if(esteP2(n)) cout << "\nEste ma!"; else cout << "\nNu-i X("; int main() { int n; cout << "Baga numarul: "; cin >> n;

17
O putere de-a lui 2 arat aa: 01000000, un ir de 0 cu un singur 1. Dac scazi 1 din el, obii 00111111. ?

01000000 - 00000001 = 00111111

Dac aplici & celor dou valori: ?

01000000 & 00111111 = 00000000

obii 0. Pe de alt parte, un numr care nu este o putere de-a lui 2, mai are un 1 n plus: 01000001. Dac aplici aceleai operaii: ?

1 2

01000001 - 00000001 = 01000000 01000000 & 01000001 = 01000000

Vei obine un numr diferit de 0. n exemplul de mai sus folosesc ! deoarece dac numrul este o putere a lui 2, funcia va returna 0 (False) i eu nu vreau asta! Vreau True, de asta inversez valoarea.

Este numrul par?


Probabil c ai observat sau nu, dar numerele pare au ntotdeauna bitul cel mai din dreapta zero (0), iar numerele impare au 1. Exemplu: ?

1 2 3 4

1 -> 0001 2 -> 0010 3 -> 0011 4 -> 0100

Deci pot testa dac un numr este par sau nu n felul urmtor: ?

1 2

int estePar(int nr) { return nr & 1; }

De exemplu: 3 & 1, n reprezentaie binar (presupun c lucrez cu 4 bii) este 0011 & 0001; n acest caz, rezultatul va fi 0001, adic 1. Deci dac numrul este par se va returna 0, altfel se returneaz 1.

C++: Clasa string


Clasa string este o specializare a clasei template basic_string. De fapt, sunt dou specializri ale basic_string:string care suport iruri de caractere pe 8-bii, i wstring, care suport iruri de caractere wide. n acest tutorial va fi analizat clasa string. Pentru a avea acces la string, includei headerul <string>. nelegei c string nu nlocuiete irurile null-terminate (vectorii de tip char sau c-stringurile), deoarece ele reprezint n continuare cea mai eficient metod de implementare a irurilor de caractere. Folosii string atunci cnd eficiena nu este o problem mare. string este o clas mare, cu muli constructori i multe funcii membre (fiecare cu multiple forme suprancrcate). Nu voi putea prezenta ntreaga clas, n schimb voi ncerca s sintetizez esenialul. Iat cei mai importani constructori ai clasei string:

Constructor Descriere string(); Creaz un obiect string gol(empty). string(const char *str); Creaz un obiect string din irul null-terminat indicar de str. string(const string &str); Creeaz un string dintr-un alt string. string(size_t n, char c); Creeaz un string format din repetiia de n ori a caracterului c.
Operatori ce se aplic obiectelor string:

Operator Semnificatie = Asignare/Atribuire + Concatenare += Concatenare atribuire == Egalitate != Inegalitate < Mai mic ca <= Mai mic sau egal cu > Mai mare ca >= Mai mare sau egal cu [] Indicare (Subscripting) << Output >> Input
Putei mixa obiecte string cu iruri null-terminate n expresii. De exemplu, putei atribui unui obiect string un ir null-terminat. Operatorul + poate fi folosit n felul urmtor (C-string este ir null-terminat): ?

1 string + string 2 string + C-string 3 C-string + string


Acest operator poate fi folosit de asemenea pentru a concatena un caracter la sfritul stringului. Clasa stringdefinete constanta npos (membru static) ce reprezint valoarea maxim pentru un element de tip size_t, adic -1. De asemenea constanta indic i sfritul unui string sau ntreg stringul. ?

1 2 3 4 5 6

#include <iostream> #include <string> using namespace std; int main() { string alp("alpha");

string brv("bravo"); 7 string chr("charlie"); 8 string dlt("delta"); 9 string str; 10 11 // Asignare 12 str = alp; 13 cout << str << ' ' << alp << '\n'; 14 15 // Concatenare + chr; str = alp + brv 16 cout << str << '\n'; 17 18 // Concatenare cu C-string 19 str = chr + dlt + "echo"; 20 cout << str << '\n'; 21 // Comparare 22 if(chr > alp+brv) cout << "charlie > alphabravo \n"; 23 if(dlt == dlt) cout << "delta == delta \n"; 24 25 // Asignare cu C-string 26 str = "Un sir null-terminat.\n"; 27 cout << str; 28 // Crearea unui string dintr-un alt string 29 str = "echo"; 30 string ech(str); 31 cout << ech << '\n'; 32 33 // Citire string cin >> str; 34 cout << str << '\n'; 35 36 // Indicare 37 // Afisez caracterul al doilea din brv. Nu uitati! brv[0] 38 este primul caracter!! 39 cout << brv[1] << '\n'; brv[1] = 'x'; 40 cout << brv << '\n'; 41 42 system("PAUSE"); 43 return 0; 44 }
Output: ?

1 2 3 4 5 6

alpha alpha alphabravocharlie charliedeltaecho charlie > alphabravo delta == delta Un sir nul-terminat. echo

7 8 9 10 11

tango // Citire de la tastatura tango r bxavo

Observai c mrimea stringurilor nu este specificat. Obiectele string i modific mrimea automat atunci cnd este necesar. Deci cnd atribuii sau concatenai un string, mrimea obiectului care primete stringul va crete automat. Cteva funcii membre ale clasei string:

Funcie string &assign(const string &strob, size_t start, size_t num); string &assign(const char *str, size_t num); string &append(const string &strob, size_t start, size_t num); string &append(const char *str, size_t num); string &insert(size_t start, const string &strob); string &insert(size_t start, const string &strob, size_t insStart, size_t num); string &replace(size_t start, size_t num, const string &strob); string &replace(size_t start, size_t orgNum, const string &strob, size_t replaceStart, size_t replaceNum); string &erase(size_t start = 0, size_t num = npos);
#include <iostream> #include <string> using namespace std;

Descriere num caractere din strob, pornind de la indicele start, vor fi atribuite obiectului invocator. Primele num caractere din stringul null-terminat str sunt atribuite obiectului invocator. num caractere din strob, pornind de la indicele start, vor fi anexate (concatenate) obiectului invocator. Primele num caractere din stringul null-terminat str sunt anexate obiectului invocator. Insereaz strob n stringul invocator pornind de la indicele start. Insereaz num caractere din strob ncepnd de la insStart n stringul invocator ncepnd de la start. nlocuiete primele num caractere, ncepnd de la start, din stringul invocator cu strob. nlocuiete orgNum caractere, ncepnd de la start, n stringul invocator cu replaceNum caractere din stringul strob ncepnd de la replaceStart. terge num caractere din stringul invocator ncepnd de la start.npos nseamn c terge pn la sfrit.

Toate funciile de mai sus returneaz o referin ctre obiectul string invocator. Iat un exemplu: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

int main() { string samuel("English do you speak it?!"); string lol("motherfucker"); // Insert samuel.insert(7, lol); samuel.insert(7, " "); cout << samuel << '\n'; // Replace samuel.replace(14, 6, "******"); cout << samuel << '\n';

15 16 // Erase 17 samuel.erase(7, 13); cout << samuel << '\n'; 18 19 system("PAUSE"); 20 return 0; 21 }
Output: ?

1 English motherufucker do you speak it?! 2 English motheru****** do you speak it?! 3 English do you speak it?!
Mai multe funcii membre utile:

Funcie size_t find(const string &strob, size_t start = 0) const; size_t rfind(const string &strob, size_t start = npos) const;

Descriere Caut, ncepnd de la start, n stringul invocator prima apariie a stringului strob. Dac este gsit returneaz indicele la care este gsit, altfel returneaz npos. Caut (n ordine invers), ncepnd de la start, n stringul invocator ultima apariie a stringuluistrob. Dac este gsit returneaz indicele la care este gsit, altfel returneaz npos. num caractere din strob, ncepnd de la start vor fi comparate cu int compare(size_t start, stringul invocator. Dac stringul invocator este mai mic ca strob se size_t num, const string returneaz o valoare negativ, dac este mai mare se returneaz o &strob) const; valoarea pozitiv, dac sunt egale se returneaz zero. Returneaz un pointer ctre o versiune null-terminat a stringului const char *c_str() const; coninut n obiectul invocator.
Exemplu final: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

#include <iostream> #include <string> using namespace std; int main() { int i; string str("Un simplu string."); i = str.find("simplu"); if(i != string::npos) cout << "Gasit pe pozitia " << i << '\n'; // Forma supraincarcata care accepta char ca parametru i = str.rfind('n'); if(i != string::npos) cout << "Gasit pe pozitia " << i << '\n'; str = "atom"; if(str.compare(0, 4, "atomic") == 0)

cout << "Egalitate\n"; 18 19 20 const char * p = str.c_str(); cout << p << '\n'; 21 22 system("PAUSE"); 23 return 0; 24 }
Mai multe detalii despre clasa string: http://www.cplusplus.com/reference/string/string/ sauhttp://msdn.microsoft.com/enus/library/xabz5s9c.aspx

C++: Alte noiuni C++ Funcii de conversie


O funcie de conversie v permite s definii o operaie ce convertete tipul vostru ntr-un alt tip. Forma general a funciei de conversie: ?

1 operator type() {return value;}


Unde type este tipul n care convertii, iar value este valoarea clasei (a tipului vostru) dup conversie. Funciile de conversie returneaz date de tipul type. Funciile de conversie nu pot avea parametri i trebuie s fie membre ale clasei pentru care sunt definite. Sunt motenite i pot fi virtuale. Urmtorul exemplu demonstreaz funciile de conversie. Clasa Fractie implementeaz o funcie de conversie ce convertete obiectele Fractie n valori double. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

#include <iostream> using namespace std; class Fractie { public: Fractie(int numarator, int numitor) { x = numarator; y = numitor; } Fractie() : x(0), y(0) {} void show() { cout << x << " / " << y << '\n'; } // Functie de conversie; Fractie -> double operator double() { return (double)x / (double)y; } private: int x, y; // numarator si numitor }; int main() { Fractie fr(3, 2); double d; fr.show(); // 3 / 2 d = fr; // Conversia Fractie -> double are loc cout << d; // 1.5 system("PAUSE"); return 0; }

Trebuie s nelegei c type poate fi orice tip (chiar i tipuri definite de voi). Conversia se aplic automat ntr-o expresie atunci cnd este necesar. Deci dac d ar fi fost int, conversia nu s-ar fi produs.

Constructori explicii
Atunci cnd declarai un constructor cu un singur parametru, implicit creai i o conversie dintre tipul argumentului ctre tipul clasei, adic putei folosi att forma ob(x) ct i ob = x. Poate sunt momente cnd nu vrei ca o conversie implicit s aib loc. n acest caz folosii cuvntul explicit. Se aplic numai constructorilor. Mai nti s vedem un exemplu fr explicit: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

#include <iostream> using namespace std; class tipuMeu { public: tipuMeu(int X) { x = X; } void show() { cout << x << '\n'; } private: int x; }; int main() { tipuMeu ob = 4; // Convertit automat in ob(4) system("PAUSE"); return 0; }

Dac declar acum constructorul ca fiind explicit: ?

#include <iostream> using namespace std; class tipuMeu { public: explicit tipuMeu(int X) { x = X; } void show() { cout << x << '\n'; } private: int x; }; int main() { // tipuMeu ob = 4; // Eroare tipuMeu ob(4); ob.show(); // 4 system("PAUSE"); return 0; }

Funcii membre const i membri mutable


Funciile membre pot fi declarate const. n acest caz this este tratat ca un pointer const. Aadar, o astfel de funcie nu poate modifica obiectul invocator. De asemenea, obiectele const nu pot invoca funcii membre non-const, dar funciile membre const pot fi apelate att de obiecte const ct i de obiecte non-const. Cuvntulconst se pune ntre lista de parametri i corpul funciei. Exemplu: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

#include <iostream> using namespace std; class lol { public: lol(int X) { x = X; } // Functie membra const void show() const { cout << x << '\n'; } void setX(int X) { x = X; } private: int x; }; int main() { const lol ob(7); ob.show(); // ob.setX(6); Eroare! system("PAUSE"); return 0; }

Observai c ob nu poate invoca funcia non-const deoarece ob este const. Dar dac vrei ca funcia setX sa fieconst i n acelai timp s poat modifica anumii membri? Deoarece ntr-o funcie const nu poi modifica starea obiectului (adic datele membre). n acest caz, datele membre care vrei s fie modificate de funciile const trebuie declarate mutable. S vedem mai nti un exemplu fr mutable: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14

#include <iostream> using namespace std; class lol { public: lol(int X) { x = X; } void show() const { cout << x << '\n'; } // Acum setX este const void setX(int X) const { x = X; // Eroare! } private: int x; };

15 16 int main() 17 { const lol ob(7); 18 ob.show(); 19 ob.setX(6); 20 system("PAUSE"); 21 return 0; 22 } 23


setX fiind funcie const, atribuirea x = X; nu este posibil. Totui dac membrul x este declarat mutable atunci eroarea va disprea. ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

#include <iostream> using namespace std; class lol { public: lol(int X) { x = X; } void show() const { cout << x << '\n'; } void setX(int X) const { x = X; // Acum nu mai sunt probleme } private: mutable int x; // Membru mutable }; int main() { const lol ob(7); ob.show(); // 7 ob.setX(6); ob.show(); // 6 system("PAUSE"); return 0; }

Deci un membru mutable poate fi modificat de funcii membre const.

Funcii membre volatile


Atunci cnd o funcie membr este declarat volatile, pointerul this este tratat ca un pointer volatile. V reamintesc c un obiect volatile poate fi modificat n program de orice altceva, cum ar fi sistemul de operare, sau un hardware, sau un thread (fir de execuie). ?

1 2 3 4

class X { public: void f2(int a) volatile; // functie membra volatile };

Specificare Linkage

n C++ putei specifica cum o funcie va fi linkeditat n program. Implicit, funciile sunt linkeditate ca funcii C++. Forma general a linkage-ului: ?

1 extern "limbaj" prototip-functie


unde limbaj denot limbajul dorit. Toate compilatoarele C++ suport linkage C i C++. Linkage-ul trebuie s fie global; nu poate fi folosit ntr-o funcie. Putei specifica un linkage pentru mai multe funcii n felul urmtor: ?

1 extern "limbaj" { 2 prototipuri 3 }


Exemplu: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

#include <iostream> using namespace std; extern "C" void func(); int main() { func(); system("PAUSE"); return 0; } void func() { cout << "Va fi linkeditata ca functie C"; }

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