Sunteți pe pagina 1din 19

Notiuni de C++

Bibliografie: http://vega.unitbv.ro/~cataron/Courses/Poo/poo.htm 1 Clase C++ Prin intermediul mecanismului claselor, C++ ofer programatorului posibilitatea de a-i defini propriile tipuri de date. Clasele se folosesc n mod tipic pentru a defini date abstracte care nu au un corespondent n setul predefinit sau derivat de tipuri de date. Tipurile de clase ce pot fi implementate nu sunt limitate. Fiecrei clase i se asociaz urmatoarele atribute: 1) Reprezentarea clasei (datele membre) este o colecie de zero sau mai multe date de orice tip. 2) Interfaa clasei (funciile membre) este o colecie de operaii aplicabile obiectelor clasei. 3) Nivelele de acces din exteriorul clasei la membrii acesteia. Exist trei nivele de acces: private, protected i public. n general, reprezentarea este privat iar interfaa public. Despre o reprezentare declarat private sau protected spunem c este ncapsulat. Prin intermediul nivelelor de acces se realizeaz o ascundere a informaiei. 4) Specificatorul de tip (numele clasei respective) poate aprea oriunde pot aprea specificatorii de tip predefiniti. Exemplu: Screen myScreen; Screen *tempScreen = &myScreen; Screen& copy(const Screen[]); O clas cu o reprezentare ncapsulat i cu un set public de operaii se numeste tip abstract. 1.1 Definirea claselor O definiie a unei clase are dou pri: - antetul clasei - corpul clasei Antetul este format din cuvntul cheie class urmat de numele clasei. Corpul clasei este cuprins ntre {} i conine membrii clasei, urmat de ; sau de o list de declarare. Exemplu: class Screen { /* ... */ }; class Screen { /* ... */ } ecranA, ecranB; Datele membre se declar exact ca variabilele C obinuite, exceptnd faptul c nu pot fi iniializate explicit. Exemplu: class Screen { short row, col; // nr linii si coloane char *cursor; // pozitia curenta a cursorului char *screen; // matricea ecran, row*col }; Observaie: Este bine ca datele membre s se specifice n ordinea cresctoare a dimensiunii, acest fapt putnd asigura o alocare optim pe toate mainile. Un obiect al unei clase poate fi declarat membru doar dac clasa a fost deja definit. Definirea se ncheie n momentul apariiei parantezei }. Nu se pot declara membri de tipul clasei. Prin intermediul declaraiilor forward se pot folosi pointeri la clase nca nedefinite. Antetul clasei se consider declaraie forward. Exemplu: class Screen; // declaratie forward class StackScreen // lista de ecrane { int topStack; Screen *stack; }; Funciile membre se declar n corpul clasei (declararea nsemnnd specificarea prototipului). Funciile foarte scurte pot fi chiar definite, dar sunt considerate implicit inline. Exemplu: class Screen { public: void Home(); {cursor=screen;} void Move( int, int); char Get() { return *cursor; } char Get(int, int); void CheckRange(int, int); 1

}; Pentru definirea funciilor membre n exteriorul clasei se va indica faptul c funcia este membr a clasei respective. Exemplu: void Screen::CheckRange(int row, int col) //valideaza coordonatele { if( row < 1 || row > height || col < 1 || col > width) { cerr << "Coordonatele ecran (" << row << "," << col << ") in afara limitelor." << endl; exit(-1); } } void Screen::Move(int row, int col) //deplasarea absoluta a cursorului { CheckRange(row, col); //validarea noii pozitii int rowMove = (row - 1) * width; cursor = screen + rowMove + col - 1; } Funciile membre se deosebesc de funciile obinuite prin faptul c: (i) au acces la toi membrii clasei (cele obinuite doar la membrii publici) Observatie: Funciile membre ale unei clase, n general, nu au acces privilegiat la membrii altei clase. (ii) funciile membre sunt definite n domeniul clasei respective, funciile obinuite n domeniul global (fiier). Funciile membre pot fi suprancrcate doar de funcii membre ale aceleiai clase (pentru c suprancrcarea presupune un domeniu comun). Exemplu: char Screen::Get(int row, int col) { Move(row, col); //pozitioneaza cursorul return Get(); //characterul aflat la noua pozitie a cursorului } Ascunderea informaiei este un mecanism formal pentru a restrnge accesul la reprezentarea clasei. Un membru public este accesibil din orice punct al programului. Un membru protected se comport ca un membru public pentru clasele derivate i ca unul private pentru restul programului. Un membru private este accesibil doar funciilor membre. Exemplu: class Screen { public: void Home() { Move(1,1); } char Get() { return *cursor; } char Get(int, int); inline void Move(int, int); ... private: short row, col; char *cursor, *screen; }; O clasa poate conine seciuni multiple: public, protected sau private. Fiecare seciune rmne activ pn la specificarea unui alt nivel de acces. Nivelul implicit imediat dup { este private. Prin convenie, seciunile publice se plaseaz la nceputul, iar cele private spre sfritul corpului clasei. 1.2 Obiecte Definirea claselor nu implic rezervri de memorie. Acestea vor fi fcute n momentul definirii obiectelor respective. Exemplu: Screen myScreen; // se aloca spatiu pentru 4 date membre 2

Obiectele aceleiai clase pot fi iniializate, atribuite ntre ele, pot fi transmise prin valoare i pot fi returnate funciilor. Un pointer la un obiect poate fi iniializat (sau care se poate atribui) fie prin operatorul new fie prin operatorul adres &. Exemplu: Screen ecranA=ecranB; // implicit: // ecranA.row=ecranB.row; // ecranA.col=ecranB.col; // ecranA.cursor=ecranB.cursor; // ecranA.screen=ecranB.screen; Screen *ptr=new Screen; n exteriorul domeniului clasei, membrii sunt accesai prin selectorii . i -> (. cu obiectul sau referina, -> cu pointer la obiect). Exemplu: #include "screen.h" isEqual( Screen& s1, Screen *s2 ) { // returneaza 0 daca nu sunt egale si 1 in rest if( s1.GetRow() != s2->GetRow() || s1.GetCol() != s2.GetCol() ) return 0; // nu sunt egale for( int i=0; i<s1.GetRow(); ++i ) for( int j=0; j<s2->GetCol(); ++j ) if( s1.Get(i,j) != s2->Get(i,j) ) return 0; return 1; } Funcia isEqual nu are nici un drept de acces la datele clasei Screen, motiv pentru care folosete funciile de acces GetRow() i GetCol(). Exemplu: // sa definim functiile de acces class Screen { public: int GetRow() { return row; } int GetCol() { return col; } ... private: short row, col; }; Membrii clasei pot fi accesai direct din domeniul clasei fr utilizarea operatorilor de selectie. Exemplu: #include <string.h> void Screen::Copy(Screen& s) //copiaza un obiect screen in alt obiect de tip screen { delete screen; height = s.height; width = s.width; screen = cursor = new char[ height * width + 1]; strcpy( screen, s.screen ); } Observaie: Funcia de mai sus nu trateaz cazul n care obiectele de tip Screen au dimensiuni diferite. 1.3 Funcii membre Succesul sau eecul unei clase depinde de eficiena i completitudinea operaiilor puse la dispoziia programatorului prin intermediul funciilor membre (interfaa public). Exista 4 categorii de funcii membre: - de administrare - de implementare - auxiliare - de acces.

Funciile de administrare sunt folosite pentru activitatea de iniializare, asignare, alocare a memoriei i pentru conversii de tip. De obicei, aceste funcii sunt automat invocate de ctre compilator. Funcia de initializare se numete constructor (are acelai nume ca i numele clasei) i este apelat automat ori de cte ori se definete sau se aloc (cu operatorul new) un obiect al clasei. Exemplu: Screen::Screen(int high, int wid, char background) //constructor: functie de initializare //a obiectelor de tip Screen { int size = high * wid; height = high; width = wid; cursor = screen = new char[size + 1]; char *startAddr = screen; char *endAddr = screen + size; while(startAddr != endAddr) *startAddr++ = background; *startAddr = '\0'; //sfarsitul ferestrei este marcat prin NULL } n clas, constructorul va fi declarat class Screen { public: Screen( int=8, int=40, char='#' ); .... }; Declaraii de obiecte de tip Screen pot fi: Screen s1; // Screen( 8, 40, '#'); Screen *ps = new Screen(20); // Screen(20, 40, '#'); main() { Screen s(20,80,'*'); // Screen(20, 80, '*'); ... } Funciile de implementare furnizeaz facilitile tipului abstract. De exemplu, pentru clasa Screen acestea pot fi deplasri de cursor (Home(), Move(), Forward(), Back() (cu wrap_around), Up(), Down() (cu bell), Bottom(), Top()) sau cutri de text (Find("text")). void Screen::Forward() // avans in urmatoarea pozitie cu wrap_around { ++cursor; if(*cursor == '\0') Home(); } const char BELL = '\007'; void Screen::Up() // avans o linie in sus, cu BELL { if(Row() == 1) cout.put(BELL); else cursor -= width; } Funciile auxiliare nu sunt scrise pentru a fi apelate n mod normal, ci doar pentru a simplifica celelalte funcii. De cele mai multe ori sunt private. Exemplu: pentru clasa Screen, funcii auxiliare pot fi CheckRange() - verificare coordonate, Row(), Col() - determinarea liniei i coloanei curente, RemainingSpace() - calculul spatiului disponibil. int Screen::Col() // returneaza coloana curenta { int position = cursor - screen + 1; return ((position + width - 1) % width) + 1; } int Screen::RemainingSpace() 4

// spatiul disponibil, exclusiv pozitia cursorului { int size = width * height; return (screen + size - cursor - 1); } Funciile de acces permit accesul (strict controlat) la datele ncapsulate. n general, ele sunt doar funcii de citire sau de scriere. Funciile de acces sunt FOARTE IMPORTANTE. Dac apar erori, domeniul de cutare a erorii se limiteaz la aceste funcii. Exemplu: void Screen::Set(char *s) // scrie un sir de caractere in pozitia curenta { int space = RemainingSpace(); int length = strlen(s); if(space < length) { cerr << "Atentie : trunchiere!" << endl << "Spatiu disponibil : " << space << endl << "Lungimea sirului : " << length << endl; length = space; } for(int i = 0; i < length; i++) *cursor++ = *s++; } sau void Screen::Set(char ch) // scrie un caracter la pozitia curenta { if(ch == '\0') cerr << "Atentie!" << endl << "Caracterul NULL este ignorat" << endl; else *cursor = ch; } Alte funcii de acces pentru clasa Screen ar putea fi: IsEqual( char ch ); // verifica caracterul din pozitia curenta IsEqual( char *s ); // verifica sirul din pozitia curenta IsEqual( Screen& s); // compara doua ecrane Exemplu: Screen::IsEqual(Screen& s) { if( row!=s.row || col!=s.col ) // diferite return 0; char *p=screen; char *q=s.screen; if(p==q) // adreseaza aceeasi zona de memorie return 1; while( *p && *p++ = *q++ ); if( *p ) return 0; // testul de egalitate nu a ajuns la sfarsitul // ecranului return 1; } n cazul manipulrii obiectelor constante, compilatorului trebuie sa i se specifice dac o funcie modific sau nu obiectul respectiv. Am intlnit pn acum situaii de forma: const char blank=' '; blank = '\0'; // eroare Dar, prin definiia lui, un obiect nu poate fi direct modificat de un programator, ci doar prin invocarea setului de funcii care fac modificri asupra datelor acestuia. Ce se ntmpl ns n cazul apelurilor: const Screen blankScreen; blankScreen.Display(); // sigura 5

blankScreen.Set('*');

// nesigura

Proiectantul clasei poate specifica faptul c o funcie membr nu modific obiectul declarnd-o i definind-o const. Cuvntul const se plaseaza ntre lista de argumente i corpul funciei. Pentru obiecte constante pot fi invocate doar funciile constante. Exemplu: class Screen { public: char Get() const { return *cursor; } IsEqual( char ch ) const; void Ok( char ch) const { *cursor=ch; } // nu modifica cursor ci valoarea obiectului pe care il // adreseaza cursor void Er( char *pCh) { cursor=pCh; } // modifica datele membre ... }; Screen::IsEqual(char ch) const { return (ch==*cursor) } Funciile const i non-const pot fi suprancrcate. Ambiguitatea se elimin prin declaraia const sau nonconst a obiectului implicat. Exemplu: class Screen { public: char Get( int x, int y); char Get( int x, int y) const; ... }; Constructorii i destructorii fac excepie de la aceste reguli. Ei nu trebuie declarai const pentru a putea fi aplicai obiectelor constante. n general, orice clas care e intens folosit trebuie s asigure posibilitatea de lucru cu obiecte constante. 2 Funcii de administrare n acest capitol vom detalia urmtoarele trei categorii de funcii de administrare: 1) constructorii i destructorii, destinai iniializrii/deiniializrii obiectelor; 2) suprancrcarea unor operatori ca o alternativ la realizarea acelorai operatii prin funcii; Exemplu: n loc de invocarea explicit a functei membre if( myScreen.isEqual(yourScreen) ) s fie posibil invocarea echivalent if( myScreen == yourScreen ) Invocarea acestor funcii membre este, n general, transparent utilizatorului clasei. Rolul lor este de a aduce sintaxa de utilizare a unei clase la nivelul de naturalee al tipurilor predefinite. 2.1 Iniializarea obiectelor Iniializarea unui obiect const n iniializarea membrilor. Dac acetia sunt publici, ei pot fi iniializai explicit. Exemplu: class Word { public: int occurs; char *string; }; Word cuvant = {0, "test"};

C++ suport un mecanism de iniializare automat a obiectelor. O funcie membr special, numit constructor, este apelata automat de ctre compilator ori de cte ori se definete un obiect sau se aloc cu operatorul new. Numele constructorului este numele clasei. Exemplu: class Word { public: Word( char*, int=0); private: int occurs; char *string; }; #include<string.h> Word::Word(char *sir, int cnt) { string = new char[strlen(sir)+1]; strcpy(string, str); occurs=cnt; } Iat cteva definiri de obiecte Word n prezena constructorului: Word w=Word("test",5); Word *pW=new Word("test"); // Notatii, prescurtari Word w("test"); // Word::Word("test",0); Word w="test"; // Word::WOrd("test",0); Observaie: - constructorii nu au tip i nu returneaz nimic, n rest, ei nu sunt dect nite funcii membre obinuite; - constructorii pot fi suprancrcai. Unul dintre cele mai utilizate tipuri de date este tipul ir de caractere. Pentru a ilustra semantica constructorilor, destructorilor i a operatorilor suprancrcai, vom construi o clas pentru acest tip. Exemplu: class String { public: String(int); String(char*); private: int len; char *str; }; Observaie: - discutai existena membrului len: este nevoie de lungime destul de multe ori nct sa fie mai avantajoasa memorarea ei ntr-un membru dect calculul ei de fiecare dat? - clasa poate fi folosita nu numai pentru lucrul cu siruri ci i ca buffer - e de o lungime specificat. String::String(char *s) { len=strlen(s); str=new char[len+1]; strcpy(str,s); } String::String(int ln) { len=ln; str=new char[len+1]; str[0]='\0'; } n momentul definirii, fiecrui obiect i se aloc spaiul pentru datele membre nestatice. Constructorii iniializeaz aceste date. Se pot transmite argumente constructorului fie n forma explicit, fie n forma prescurtat. Exemplu: String searchWord=String("test"); // invocare explicita String commonWord("cuvant"); // forma prescurtata 1 7

String inBuf=1024; String *ptrBuf=new String(1024);

// forma prescurtata 2 // new necesita forma explicita

Dac prin new nu se poate aloca spaiul necesar datelor membre, atunci constructorul nu mai este apelat, iar pointerul la clas este setat 0. Observaie: - este util s permitem definirea unui obiect String fr s cerem specificarea unor argumente. String temStr; Acest lucru poate fi fcut prin intermediul unui constructor implicit ( a nu se confunda cu constructorii care au argumente implicite !). Constructorul implicit este constructorul cu lista de argumente vid. Exemplu: String::String() // constructor implicit { len=0; st=0; } O greseala frecvent de programare este: String st(); Observaie: - aceast declaraie nu reprezint definiia unui obiect String iniializat cu constructor implicit, ci reprezint declaraia unei funcii st care nu are argumente i returneaz un obiect de tip String. - definiii corecte sunt: String st; // declaratii echivalente, se apeleaza String st=String(); // constructorul implicit Constructorii suport toate nivelele de acces definite pentru o clas. O clas fr nici un constructor public se numete clas privat (ex IntItem). Destructorii sunt funcii membre speciale invocate ori de cte ori un obiect nceteaz s mai existe ( ex. expirarea duratei de via, aplicarea operatorului delete unui pointer de tipul clasei). Destructorii nu au argumente, nu returneaz nimic i numele lor este cel al clasei precedat de caracterul ~. Exemplu: class String { public: ~String(); ... }; String::~String() { delete str; } Destructorii nu acioneaz asupra spaiului alocat datelor membre nestatice ale obiectului. Pointerii i referinele nu provoac invocarea destructorilor n momentul n care ii nceteaz existena. Programatorul trebuie s invoce explicit operatorul delete. Delete invoc destructorul obiectului adresat de pointerul furnizat ca argument. Valoarea acestui pointer trebuie s fi fost obinut n urma unui apel de operator new. Exemplu: #include "String.h" String cuvant("test"); f(){ String *p = &cuvant; String *p2 = new String("sanie"); ... delete p; // eronat ca idee, rezultat imprevizibil delete p2; // corect } Dac pointerul cruia i este aplicat delete este 0, deci nu adreseaz un obiect, destructorul nu e invocat. Deci nu e necesar s scriem: if(p2 != 0) delete p2; Pe scurt, destructorii pot efectua toate operaiile pe care programatorul le vrea efectuate nainte de ncetarea existenei unui obiect. Tablourile de obiecte se definesc la fel ca i cele de tipuri predefinite. Exemplu: const int size=16; String tab[size]; String *tab2 = new String[size]; 8

// ambele variante definesc un tablou de 16 obiecte String Tablourile pot fi iniializate apelnd explicit sau prescurtat constructorii respectivi. String aS1[] = { "schi", "zapada" }; String aS2[] = { String(), String(1024), String("test") }; String aS3[] = { 1024, String(512) }; Dac lista de iniializare este mai mic dect dimensiunea tabloului, atunci pentru elementele rmase neiniializate se apeleaz constructorul implicit. Tablourile de obiecte alocate dinamic nu pot fi iniializate explicit. n acest caz este invocat constructorul implicit (deci clasa trebuie s aiba constructor implicit). Pentru eliberarea memoriei alocate trebuie apelat explicit operatorul delete. delete tab2; // nu e sufiecient, sterge doar primul element din tab2 Programatorul trebuie sa specifice dimensiunea tabloului delete [size] tab2; // destructorul va fi invocat pentru fiecare din cele // size elemente ale tabloului tab2; Dac datele membre sunt obiecte ale altei clase: - constructorii sunt apelai ncepnd cu al celui mai interior obiect, n ordinea declarrii acestora, terminnd cu cel al clasei obiectului exterior (care conine datele membre). Exemplu: class Word { public: Word(); Word( char*, int=0 ); Word( String&, int=0 ); private: int occurs; String name; }; Constructorilor datelor membre li se pot transmite argumente prin listele de iniializare a membrilor. Word::Word(char *s, int cnt):name(s) { occurs=cnt; } Observaie: - listele de iniializare apar doar n definiiile constructorilor, nu i n declaraiile lor; - n listele de iniializare pot apare i tipuri de date predefinite; Word::Word(char *s, int cnt): name(s), occurs(cnt){} - listele de iniializare sunt singura modalitate de iniializare a datelor membre const sau referin. Execuia constructorilor cuprinde dou faze: - iniializare conform listei specificate; - asignare conform corpului functiei. Dac constructorul unui obiect necesit list de argumente, atunci obiectul respectiv trebuie s apar n lista de iniializare. Argumentul de iniializare al unui membru care este obiect al unei alte clase poate fi un obiect din aceast clas. Exemplu: String mesaj("Hello"); Word Salut(mesaj); n faza de iniializare se vor apela toi constructorii obiectelor membre, n ordinea declarrii acestora. Pentru obiectele nespecificate n lista de iniializare se vor invoca constructorii implicii. class SinAntonim { public: SinAntonim(char *s) : wd(s) {} SinAntonim(char *s1, char *s2, char *s3) : wd(s1), sinonim(s2), antonim(s3) {} ~SinAntonim(); private: String sinonim; 9

};

Word wd; String antonim;

S analizm urmatoarele dou situaii: 1) SinAntonim sa1("test"); 2) SinAntonim sa2("cauza", "origine", "efect"); Ordinea de apel a constructorilor este urmtoarea: 1) pentru membrul String sinonim: String() Word wd: String("test"), Word("test") String antonim: String() 2) pentru membrul String sinonim: String("origine"); Word wd: String("cauza"), Word("cauza") String antonim: String("efect") Destructorii sunt apelai n ordinea invers constructorilor. 2.2 Iniializarea datelor membre Atunci cnd un obiect este iniializat cu un obiect din aceeai clas, nu se mai invoc constructorul definit de programator, ci unul implicit, X::X(const X&), numit constructor de copiere, care realizeaz iniializarea membru cu membru. Exemplu: // constructorul de copiere implicit al clasei String String::String(const String& s) { len=s.len; str=s.str; } Acest tip de iniializare mai apare i n urmtoarele dou situaii: - transmiterea unui obiect ca argument ( pentru crearea copiei locale ); - returnarea unui obiect de o funcie. Observaie: - Iniializarea implicit membru cu membru este ns insificient n special n cazurile cnd datele membre sunt pointeri. Word Subiect( "carte" ); Word Predicat = Subiect; int occurs String name 10 char *str ------------int len 4 c a r t e \0

Word Subiect( carte ); int occurs String name 10 char *str ------------int len 4

Word Predicat = Subiect;

Astfel pot aprea probleme serioase dac obiectele nu exist n acelai timp i in acelai domeniu de accesibilitate. Programatorul poate aduga control asupra unor asemenea situaii, definind explicit o instan a constructorului de copiere X(const X&). Exemplu: String::String( const String& s ) { 10

len=s.len; str=new char[len+1]; strcpy(str,s.str);

Observaie: - dac o clas are definit constructorul X::X(const&), atunci trebuie s iniializeze explicit toate obiectele membre, indiferent de tipul lor. Word::Word( const Word& w) { occurs = 0; name = w.name; } S analizm acum pas cu pas ce se ntmpl n urmtoarea situaie: Word examen("scris"); Word test=examen; (i) - test este recunoscut ca fiind iniializat cu un obiect de tip Word. Exist constrcutor de copiere? Dac da, el este invocat, dac nu, are loc o iniializare membru cu membru. (ii) - exist list de iniializare pentru Word ? Nu; (iii) - exist obiecte membre? Da, i anume String name; (iv) - clasa String a definit un constructor implicit? Dac da, el este invocat, dac nu, se semnaleaz eroare de compilare; (v) - String() invocat pentru iniializarea lui test.name; (vi) - se invoc Word(const Word&), se execut name=w.name. Observai c String( const String& ) nu este invocat niciodat ! // varianta corecta Word::Word( const Word& w) : name(w.name) { occurs = w.occurs; } 2.3 Suprancrcarea operatorilor Pentru a uura sintaxa de utilizare a claselor, putem implementa operaiile nu prin funcii membre, ci prin suprancrcarea unor operatori. Acest lucru ar putea face posibil scrierea urmtorului tip de cod: Exemplu: String InBuf; ... while ( cin >> InBuf ) { if( !Inbuf ) return; if (InBuf == "gata" ) return; // prelucrare cout << "Sirul este :" << InBuf; } ... Un operator nu trebuie neaprat s fie membru, ns n acest caz trebuie s aib cel putin un argument de tip clas. Un operator se definete ca i orice alt funcie avnd n locul numelui cuvntul operator urmat de unul din operatorii predefinii posibil de suprancrcat. Aceti operatori sunt: + * / % ^ & | ~ ! , = < > <= >= ++ -<< >> == != && <<== += -= /= %= ^= &= |= <<= >>= [] () -> ->* new delete Exemplu: class String { public: String& operator = ( const String& ); String& operator = ( const char* ); int operator == ( String& ); ... }; 11

String& String::operator = (const String& s) { // asignare siruri len = s.len; delete str; // elibereaza zona alocata str = new char[len+1]; strcpy( str, s.str ); return *this; } String::operator == ( String& s ) { // returneaza 1 daca sunt egale return( strcmp( str, s.str ) == 0 ); } Observaii: - operatorii tipurilor predefinite nu pot fi modificai (nu suprancrcare, nu adugare alii noi ); - prin suprancrcarea operatorilor se pstreaza aritatea acestora (binar, unar). Un operator poate fi definit fie ca membru, fie ca nemebru al clasei. Prin convenie, primul operand (operandul stang ) al unui operator membru este obiectul invocator. Exemplu: class String { friend String& operator +( String&, String& ); ... }; sau class String { public: String& operator +( String& ); ... }; Cazul n care pentru o clas sunt definite ambele instane, poate genera situaii ambigue. class String { friend String& operator +( String&, String& ); public: String& operator +( String& ); ... }; String a("hobby"), b("computer"); String c = a+b; // ambiguitate n unele situaii, regulile de ascundere a informaiei sunt prea restrictive. Prin intermediul unor relaii de tip prietenie (friend) se poate asigura accesul unor funcii nemembre la membri nepublici ai unei clase. nainte de a discuta regulile de declarare a unui prieten, s ilustrm un exemplu n care apare necesitatea unui prieten. Exemplu: Operatorii iostream-urilor , << i >>, pot fi suprancrcai ca s trateze i tipuri de clase. Odata ce operatorii acetia sunt definii pentru o clas, obiectele de tipul acelei clase vor putea fi afiate la fel ca i tipurile predefinite. Screen myScreen; cout<<myScreen; cout<<"myScreen :"<<myScreen<<"\n"; Observaie: Operatorii de input/output solicit ca operand stng un obiect de tip iostream i ntorc obiectul iostream invocator. Astfel, operaia cout<<s trebuie implementat ca: ostream& operator<<(ostream&, screen&); Dac operatorul trebuie s aiba acces la membrii nepublici din Screen, exist urmtoarele variante: 1) s fie declarat membru: atunci poate avea un singur parametru, operandul stng fiind obiectul invocator. class Screen { public: ostream& operator << ( ostream& ); ... 12

}; Operandul stng fiind obiectul invocator, operatorul trebuie folosit cu urmtoarea sintax: myScreen<<cout; 2) s fie declarat friend: prietenii se declar n corpul clasei prin intermediul cuvntului cheie friend. Declaraiile friend se fac n general imediat dup header-ul clasei. class Screen { friend ostream& operator << (ostream&, Screen& ); public: ... ... }; ostream& operator << (ostream& os, Screen& s ) { os<<"\nLinii=" << s.row << "\nColoane="<<s,col<<'\n'; char *p=s.screen; while(*p) os.put(*p++); return os; } 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. O intreg clas poate fi declarat friend. Dac operandul stng nu este obiectul invocator, atunci operatorul trebuie definit ca functie nemembr. n plus, dac acceseaz membri nepublici, operatorul respectiv trebuie declarat friend. Exemplu: class String { friend ostream& operator << (ostream& os, String &s) { return (os<<s.str); } }; Cum am vazut n capitolele anterioare, atribuirea unui obiect altui obiect de tipul aceleiai clase se face implicit, membru cu membru, pentru toi membri nestatici. Compilatorul genereaza o instan pentru clasa respectiv, de forma: X& X::operator = (const X&); pentru rezolvarea cazurilor de atribuire dintre dou obiecte de acelasi tip. Exemplu: String articol("un"); String comun("exemplu"); Atribuirea comun = articol ; este rezolvat astfel: String& String::operator = (const String& s) { len=s.len; str=s.str; index=s.index; } La acest tip de asignare apar trei probleme: 1) ambele obiecte adreseaz aceeasi zon de memorie, deci pot aprea probleme dac nu au acelai domeniu de existen; 2) zona alocat pentru exemplu e definitiv pierdut; 3) am definit un index care permite iterarea unui ir de caractere i care trebuie s fie 0 la sfrsitul iterrii; deci dup o copiere index trebuie s fie 0, nu valoarea indexului altui obiect. Pentru rezolvarea acestor probleme, poate fi adoptat urmtoarea soluie: Exemplu: String& String::operator=(const String& s) { index=0; len=s.len; delete str; 13

str=new char[len+1]; strcpy(str, s.str); return *this;

3 Derivarea claselor 3.1 Programarea orientat pe obiecte Aceasta este caracterizat prin - motenire - legturi dinamice Programarea orientat pe obiect extinde tipurile de date abstracte astfel nct ele s permit relaii de forma tip/subtip. n loc s reimplementeze caracteristici comune altor clase, o clas poate moteni date membre i funcii ale altor clase. n C++, motenirea este suportat prin derivarea claselor. Care au fost problemele care au dus la necesitatea derivrii claselor? S lum n considerare clasa Screen, definit n capitolele anterioare. Evoluia soft-ului din ultimii ani a promovat lucrul cu ferestre. Cum putem s reprezentm o fereastr? De exemplu, ca un Screen cu faciliti suplimentare (redimensionare, posibiliti de mutare, pe un ecran s poat fi definite mai multe ferestre, etc.). - O fereastr trebuie s-i cunoasc nu numai dimensiunea, ci i poziia. De asemenea, ar avea nevoie de aceiai membri pe care i-a definit clasa Screen. Unii dintre acetia pot fi reutilizai direct, alii trebuie implementai. - O fereastr este un fel de Screen specializat. Este mai degrab un subtip extins al lui Screen dect un tip de dat independent. - La un anumit nivel de implementare, ele pot mpri date i funcii membre. Reprezentarea cea mai apropiat de aceast idee ar fi: class Window { public: // ... private: Screen base; // ... }; Problemele care apar sunt generate de faptul c datele membre ale lui Screen sunt private; nu sunt direct accesibile n Window. Pentru ca membrii Screen s fie direct accesibile ar trebui fcut modificarea public: Screen base, care duce la nclcarea principiului de incapsulare a datelor. Window w; w.base.clear(); S presupunem acum c home() a fost redefinit pentru Window. Cele dou funcii w.home(); w.base.home(); invoc dou funcii diferite, situaie care poate duce uor la erori de programare. Pentru a pstra o sintax uniform de tipul w.clear(); trebuie definit o funcie de interfa de tipul inline void Window::clear() { base.clear(); } Daca clasa Window este extins, implementarea poate da bti de cap serioase. De exemplu un Menu este un tip de Window. Va avea propriile date membre i funcii membre. Un meniu va mpri date cu Window sau chiar i cu Screen. Este nevoie de o soluie mai bun: moternirea. Derivnd Window din Screen, Window are acces la membrii nepublici Screen ca i cum ar fi definii n Window. Pentru a permite derivarea, sintaxei claselor i-au fost aduse dou modificri: 1) introducerea listelor de derivare prin care se specific - clasele de baz (motenite) - tipul motenirii (public, privat) 2) introducerea seciunii protected; membrii acestor seciuni se comport ca membri publici pentru clasele derivate public i ca privai pentru restul programului. O clasa derivat poate fi ea nsi clasa de baz ntr-o nou derivare. Exemplu: class Screen { ... }; class Window : public Screen { ... }; // mosteneste toti membri Screen class Menu : public Window { ... }; // mosteneste toti membri Window si Screen 14

Window w; w.clear().home(); Menu m; m.display(); Relaiile speciale dintre tipurile de baz i cele derivate permit efectuarea unor conversii standard predefinite. O clas derivat poate fi atribuit oricrei clase publice de baz. Exemplu: Menu m; Window &w = m; Screen *ps = &w; Motenirea a promovat un stil generic de programare n care nu este necesar cunoaterea tipului actual al obiectelor cu care se lucreaz. Fie o funcie extern: void displayScreen( Screen *s ); Funcia displayScreen va putea fi invocat cu un argument de tipul oricrei clase derivate public din Screen, indiferent de nivelul ierarhic pe care se gseste. Exemplu: Screen s; Window w; Menu m; displayScreen(&s); displayScreen(&w); displayScreen(&m); displayScreen(&IntArray); // Eroare. IntArray nu e in relatie de mostenire cu Screen Dac fiecare din clasele Screen, Window i Menu au cte o funcie membr numit display(), atunci displayScreen() poate fi definit astfel void displayScreen(Screen *s) { s->display(); } Mecanismul prin care se alege funcia membr prin intermediul unui obiect invocator pentru care n momentul compilrii se cunoate doar tipul de baz se numete legare dinamic. n C++, funciile tipului de baz supuse legturilor dinamice se declar virtual. Exemplu: class Screen { public: virtual void display(); // ... }; O funcie se va declara virtual n urmtoarele situaii: 1) clasa va fi probabil motenit ( va face obiectul unei derivari); 2) implementarea funciei este dependent de tip. Prin funciile virtuale stilul de programare nu este numai generic ci i extensibil: funcia displayScreen() nu sufer nici o modificare n cazul n care se introduce un nou tip derivat din Screen, Window sau Menu. Relaia dintre tipul de baz i cel derivat se numete ierarhie de derivare sau ierarhie de motenire. O derivare din mai multe tipuri de baz se va numi graf de derivare sau graf de motenire. Observaie: - Simula = primul limbaj care a utilizat mecanismul de clas pentru tipuri abstracte extinse - SmallTalk = limbajul care introdus termenul de orientare pe obiect pentru a denumi ncapsularea tipurilor definite de utilizator. 3.2 Reprezentarea unei derivri n cele ce urmeaz, vom analiza un graf de motenire cu ajutorul unei grdini zoologice. Pe mulimea animalelor din zoo pot fi definite mai multe niveluri de abstractizare: - la nivel de individ : PingPong - la nivel de specie: panda - la nivel de familie: urs - la nivel de regn: animale din zoo Alte tipuri de abstractizri pot fi : ierbivore, carnivore, ocrotite, etc. 15

S ilustrm o ierarhie de derivare pentru familia urilor: URS CARNIVOR ANIMALE_ZOO

PANDA

GRIZZLY

POLAR

FELINE

OCROTIT

IERBIVOR

URS

PING-PONG

FRAM LEOPARD PANDA

POLAR

PING-PONG

FRAM

Aceast ierarhie de derivare este simpl, adic fiecare clas derivat are o singur clas de baz la nivelul imediat superior. Printr-o ierarhie de motenire simpl nu pot fi ilustrate toate caracteristicile unei clase, de ex. clasa Panda, ns se poate printr-un graf de motenire multipl. Clasa AnimaleZoo cuprinde setul de membri (date i funcii) comune tuturor animalelor din zoo. Derivrile din aceast clas vor defini doar particularitile claselor respective. O clas care este baz pentru o ierarhie de derivare se numete superclas. Exemplu: // clase de baza class AnimaleZoo { ... }; class Ocrotit { ... }; class Carnivor { ... }; class Ierbivor { ... }; // derivari simple class Urs : public AnimaleZoo { ... }; // derivari multiple class Feline : public AnimaleZoo, private Carnivor { ... }; class Panda : private Ocrotit, public Urs, private Ierbivor { ... }; // obiect de tip Panda Panda PingPong; Sintaxa claselor de baz difer de cea a claselor obinuite prin dou aspecte: 1) membrii la care clasele derivate trebuie s aib acces, dar nu sunt public, devin protected; 2) funciile a cror implementare depinde de derivrile ulterioare sunt declarate virtual. Exemplu: class AnimalZoo { public: AnimalZoo ( char *nm, char *file, short loc ); virtual ~AnimalZoo(); virtual void draw(); void locate(); void inform(); protected: char *name; char *infoFile; short location; short count; }; class Urs : public AnimalZoo { public: Urs(char *nm, char *fil, short loc, char dn, char sp); ~Urs(); void locate(int); protected: char isDanger; 16

};

char specia;

Limbajul nu impune limite asupra numrului de clase de baz. ns fiecare clas de baz poate aprea o singur dat! Tipul derivrii se specific naintea fiecrei clase de baz. Implicit derivarea este de tip private. Nu este nici o restricie n specificarea unei clase de baz ca public sau privat. Membrii unei clase derivate sunt accesai conform nivelului lor de ncapsulare i pe baza numelui lor. 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. Exemplu: void ex( Urs& ursus ) { if( ursus.isDanger ) ursus.locate(HIGH); // HIGH - constanta intreaga ursus.AnimalZoo::locate(); // ursus.locate(); =>eroare, lipseste // primul argument ursus.inform(); } Unor membri ai unor clase de baz trebuie s li se specifice domeniul (clasa) atunci cnd: 1) clasa derivat utilizeaz un membru cu acelai nume; 2) sunt mai multe clase de baz care conin un membru cu acelai nume. Iniializarea membrilor claselor de baz se face prin lista de iniializare a membrilor din definitia constructorului. Exemplu: Urs::Urs( char *nm, char *fil, short loc, char dn, char cp ) : AnimalZoo( nm, fil, loc ), specie(sp), isDanger(dn) { } - Un tip de baz care nu are constructor sau care are un constructor implicit nu trebuie specificat n lista de iniializare a membrilor. - Un tip de baz trebuie s fie iniializat cu argumentele cerute de unul din constructorii si, cu un obiect de acelai tip sau dintr-un tip derivat public. Exemplu: Urs::Urs( AnimalZoo& z ) : AnimalZoo( z ) { ... }; Urs::Urs( const Urs& u ) : AnimalZoo( u ) { ... }; Constructorii claselor de baz sunt apelai naintea constructorului clasei derivate. Astfel, prin lista de iniializare a membrilor NU pot fi explicit iniializati membrii claselor de baz, pentru c acetia au fost deja iniializai. Exemplu: Urs::Urs( char *nm ) : name (nm) { ... } // eroare 3.3 Ascunderea informaiei la derivare O clas derivat motenete toi membrii clasei de baz, dar nu are nici un fel de acces la membrii privai ai acesteia. Declaraiile friend dintr-o clas derivat confer un nivel de acces la membrii clasei de baz identic cu cel al fiecrui membru al clasei derivate. Membrii neprivai ai unor clase de baz publice i pstreaz aceleai nivele de acces i n cadrul claselor derivate. Membrii neprivai ai unor clase derivate privat devin membri privai ai clasei derivate. Tabelul urmtor prezint modul n care se modific nivelul de ncapsulare al membrilor din clasa de baz n prezena derivrii. TIPUL DERIVRII PUBLIC PROTECTED PRIVATE PUBLIC PROTECTED PRIVATE NIVELUL DE PUBLIC PROTECTED PROTECTED PRIVATE NCAPSULARE DIN PROTECTED NO ACCESS NO ACCESS NO ACCESS CLASA DE BAZ PRIVATE Membrii clasei de baz pot fi exceptai de la regulile de derivare specificndu-le numele, i numai numele, n seciunile public sau protected ale tipului derivat. Noul nivel de acces trebuie s coincid cu cel din clasa de baz. Exemplu: class AnimalZoo 17

public: void inform(); // ... }; class Rozatoare : private AnimalZoo { public: AnimalZoo :: inform; // ok // ... }; class Rozatoare : private AnimalZoo { protected: AnimalZoo::inform; // eroare, nivelul de incapsulare nu // coincide cu cel din clasa de baza // ... }; 4. Funcii virtuale. 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 definit 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 dinamica. Exemplu: void ecranFinal( AnimalZoo *pz ) { // lista selectata de animale for( AnimalZoo *p=pz; p; p=p->next ) p->draw(); } Legarea dinamica ncapsuleaz detaliile ierarhiei de derivare. Ea poate fi modificat fr a modifica funciile deja scrise. O funcie virtual se declar prin cuvntul cheie virtual plasat n faa declaraiei sale (nu i a definiiei). Exemplu: class C { public: virtual int bar(); // declaratie ... }; int C::bar() { ... } // definitie Exist funcii virtuale a cror definiie nu este necesar ntr-o clas de baz: class C { public: virtual void draw(); virtual void info(); ... }; Proiectantul unei clase poate indica faptul c o funcie virtual este nedefinit pentru o anumit clas, iniializnd declaraia ei cu 0. Exemplu: class AnimalZoo { public: virtual void draw() = 0; ... };

18

Funciile virtuale care nu sunt definite pentru tipul de baz se numesc funcii virtuale pure. O clas care conine funcii virtuale pure poate fi folosit doar ca i clas de baz pentru derivari ulterioare Exemplu: AnimalZoo az; // eroare O clas care declar pentru prima dat n ierarhia de derivare o funcie virtual trebuie fie s o defineasc, fie s o declare pur. Dac o definete, atunci clasele derivate pot s nu o redefineasc. Dac o declar pur, atunci clasele derivate trebuie fie s o defineasc, fie s o declare din nou pur. - Clasele derivate pot redefini funciile virtuale motenite i i pot defini propriile funcii virtuale. Exemplu: class Urs: public AnimalZoo { public: void draw(); // redefineste mostenirea de la AnimalZoo virtual hiberneaza() { return 1;} private: void info(); ... }; Urs u; AnimalZoo& az=u; az.draw(); // se invoca Urs::draw(); - La redefinirea unor funcii virtuale, cuvntul cheie virtual nu mai este obligatoriu (funcia fiind virtual pentru ntreaga ierarhie), iar numele, semntura i tipul trebuie s coincid cu cele din prima definiie. - Nivelul de acces al unei funcii virtuale este determinat de tipul pointerului sau referinei la clasa prin care se face explicit invocarea ei. Exemplu: Urs u; AnimalZoo& az=u; az.draw(); // Urs::draw() az.info(); // Urs::info() Atentie ! Dei Urs::info este o funcie privat, ea poate fi invocat direct prin intermediul unei referine la AnimalZoo, deoarece n aceast clas, info() era public. S presupunem c lista de AnimalZoo transmis funciei ecranFinal nu mai e necesar dup execuia ei. Deci n mod firesc, avem nevoie de o secven de forma: Exemplu: for( AnimalZoo *p=pz->next; p; pz=p, p=p->next ) delete pz; // determina apel de destructor ~AnumalZoo; insuficient. - Pentru apelarea destructorului tipului invocator ntr-o ierarhie de derivare, destructorii vor fi declarai virtuali. (Destructorii se motenesc !) Exemplu: class AnimalZoo { public: virtual ~AnimalZoo(); ... };

19

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