cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. operatori unari . dintre operatorii...

23
9 - 1 Cap. 9 Supraîncărcarea operatorilor şi funcţiilor Ca şi în C, limbajul C++ are posibilitatea supraîncărcării funcţiilor. Mai exact, unei funcţii (metode) i se conferă posibilitatea de a “înţelege“ şi trata în mod corespunzător mai multe liste diferite de intrare. De asemenea, în C++, operatorilor uzuali li se pot atribui semantici noi. În acest capitol se vor aborda următoarele subiecte: supraîncărcarea funcţiilor, supraîncărcarea operatorilor, definirea funcţiilor de tip operator, restricţii referitoare la funcţiile de tip operator, funcţii de tip operator şi prieten al clasei, conversia tipurilor de date etc. 9.1. Supraîncărcarea funcţiilor O funcţie supraîncărcată (overloaded function) este o funcţie cu mai multe definiţii. În capitolele anterioare s-a remarcat faptul că pentru anumite funcţii membre ale unor clase existau mai multe declaraţii (prototipuri) şi definiţii (implementări). Considerăm un exemplu în care redefinim funcţia denumită suma în trei ipostaze. Primele două se referă la adunarea a doi parametri de tip int şi respectiv double, iar ultima se referă la concatenarea a două şiruri. Exemplul 9.1. Supraîncărcarea funcţiei suma # include <stdio.h> # include <string.h> int suma(int i1, int i2) { return i1+i2; } double suma (double op1, double op2) { return op1+op2; } char *suma(char *sir1, char *sir2) { return strcat(sir1, sir2); } void main (void) { int k1 = 10, k2 = 20; printf("Suma a 2 numere intregi este = %d\n", suma(k1, k2)); double x1 = 1.1; double x2 = 2.2; printf("Suma a 2 numere reale este = %f\n", suma(x1, x2)); char *sir1 = "abc"; char *sir2 = "def"; printf("Rezultatul alipirii lui sir1 cu sir2 este = %s\n", suma(sir1, sir2)); } În funcţia main() se apelează pe rând cele trei forme ale funcţiei suma(). În interiorul programului se creează lanţul necesar de legături şi în funcţie de tipul argumentelor, la momentul execuţiei programului, se apelează metoda adecvată tratării acestui mesaj. Sunt lansate trei mesaje şi anume k1, k2 (de tip int, int), apoi x1, x2 (de tip double, double) şi în final sir1, sir2, când, de fapt, are loc concatenarea. Există, în acest exemplu, o totală coincidenţă a tipurilor argumentelor cu cele ale parametrilor. Vom considera acum un exemplu în care, pe lângă asocierea metodei adecvate de tratare a mesajului de prelucrat, compilatorul va fi obligat să efectueze şi conversiile necesare. Se va redefini şi apela o funcţie denumită valpolin() care va determina valoarea unui polinom P(x) = a 0 x n +a 1 x n-1 +...+a n într-un punct x. Se va recurge la funcţia poly() al cărei prototip, definit în fişierul antet <math.h>, este: double poly(double x, int grad_polinom, double vector_coef[]); Exemplul 9.2. Valoarea unui polinom # include <stdio.h> # include <math.h> // Primul prototip al funcţiei valpolin()

Upload: others

Post on 28-Jan-2020

8 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 1

Cap. 9 Supraîncărcarea operatorilor şi funcţiilor Ca şi în C, limbajul C++ are posibilitatea supraîncărcării funcţiilor. Mai exact, unei funcţii (metode) i se conferă posibilitatea de a “înţelege“ şi trata în mod corespunzător mai multe liste diferite de intrare. De asemenea, în C++, operatorilor uzuali li se pot atribui semantici noi. În acest capitol se vor aborda următoarele subiecte: supraîncărcarea funcţiilor, supraîncărcarea operatorilor, definirea funcţiilor de tip operator, restricţii referitoare la funcţiile de tip operator, funcţii de tip operator şi prieten al clasei, conversia tipurilor de date etc. 9.1. Supraîncărcarea funcţiilor

O funcţie supraîncărcată (overloaded function) este o funcţie cu mai multe definiţii. În capitolele anterioare s-a remarcat faptul că pentru anumite funcţii membre ale unor clase existau mai multe declaraţii (prototipuri) şi definiţii (implementări). Considerăm un exemplu în care redefinim funcţia denumită suma în trei ipostaze. Primele două se referă la adunarea a doi parametri de tip int şi respectiv double, iar ultima se referă la concatenarea a două şiruri. Exemplul 9.1. Supraîncărcarea funcţiei suma # include <stdio.h> # include <string.h> int suma(int i1, int i2) { return i1+i2; } double suma (double op1, double op2) { return op1+op2; } char *suma(char *sir1, char *sir2) { return strcat(sir1, sir2); } void main (void) { int k1 = 10, k2 = 20; printf("Suma a 2 numere intregi este = %d\n", suma(k1, k2)); double x1 = 1.1; double x2 = 2.2; printf("Suma a 2 numere reale este = %f\n", suma(x1, x2)); char *sir1 = "abc"; char *sir2 = "def"; printf("Rezultatul alipirii lui sir1 cu sir2 este = %s\n", suma(sir1, sir2)); }

În funcţia main() se apelează pe rând cele trei forme ale funcţiei suma(). În interiorul programului se creează lanţul necesar de legături şi în funcţie de tipul argumentelor, la momentul execuţiei programului, se apelează metoda adecvată tratării acestui mesaj. Sunt lansate trei mesaje şi anume k1, k2 (de tip int, int), apoi x1, x2 (de tip double, double) şi în final sir1, sir2, când, de fapt, are loc concatenarea. Există, în acest exemplu, o totală coincidenţă a tipurilor argumentelor cu cele ale parametrilor. Vom considera acum un exemplu în care, pe lângă asocierea metodei adecvate de tratare a mesajului de prelucrat, compilatorul va fi obligat să efectueze şi conversiile necesare. Se va redefini şi apela o funcţie denumită valpolin() care va determina valoarea unui polinom P(x) = a0xn+a1xn-1 +...+an într-un punct x. Se va recurge la funcţia poly() al cărei prototip, definit în fişierul antet <math.h>, este:

double poly(double x, int grad_polinom, double vector_coef[]);

Exemplul 9.2. Valoarea unui polinom # include <stdio.h> # include <math.h>

// Primul prototip al funcţiei valpolin()

Page 2: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 2

double valpolin (double x, int n, double *c); // Al doilea prototip al funcţiei valpolin()

long valpolin (int x1, int n1, double *c1);

void main(void) { // Un polinom de gradul 4, P4(x)

double coef[] = {1., -4., 6., -4., 1.}; double rez;

// Un polinom de gradul 5, P5(x) double coef1[] = {-1., 1., -1., 1., -1., 1.}; long rez1; rez = valpolin (10., 4, coef); printf ("Rezult. interpolarii polin. P4(10.) este = %lf\n", rez); rez1 = valpolin (10, 5, coef1); printf ("Rezult. interpolarii polin. P5(10) este = %ld\n", rez1); }

// Definitiile functiilor valpolin()

double valpolin (double x, int g, double c[]) { return poly (x, g, c); }

long valpolin (int x, int g, double c[]) { return (long) poly (x, g, c); }

Alegerea metodei de prelucrare se face în funcţie de tipul parametrilor din lista de apel a metodei. Mai exact, se încearcă găsirea unei metode care are acelaşi prototip cu tipurile argumentelor prezentate în mesaj. Astfel, pentru prelucrarea mesajului (10., 4, coef), adică (double, int, double[]), prelucrarea se realizează cu prima formă a lui valpolin(), iar pentru prelucrarea mesajului (10, 5, coef1), adică (int, int, double[]), prelucrarea se realizează cu cea de-a doua formă a lui valpolin(). 9.2. Definirea şi apelul funcţiilor operator Limbajul C++ permite supraîncărcarea de către programatori a operatorilor limbajului. Cu alte cuvinte, se pot redefini operatorii acestui limbaj astfel încât să efectueze operaţii definite de programator. Indiferent de modul în care se realizează supraîncărcarea operatorilor, aceştia trebuie să fie asociaţi unor clase, în sensul că cel puţin unul dintre parametri trebuie să fie un obiect al unei clase (explicit sau implicit, prin intermediul parametrului ascuns this). Observaţie. Operaţia de supraîncărcare a operatorilor nu are nici o legatură cu derivarea claselor, deci nu generează obiecte polimorfice. Avantajul utilizării operatorilor supraîncărcaţi în cadrul claselor decurge din simplificarea scrierii şi citirii unei aplicaţii, datorită formei sintactice de apel al operatorilor. Exemplul 9.3. În cazul proiectării unei clase pentru operaţiile cu polinoame, operaţia de adunare a două polinoame poate fi descrisă fie printr-o funcţie obişnuită, fie prin supraîncărcarea operatorului de adunare: A) Utilizarea unei funcţii:

class polinom { double *a; int n;

public: polinom(); // Constructorul clasei // ... friend polinom suma(polinom& a, polinom& b); // ... };

void main() {

Page 3: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 3

polinom p, q, r; r = suma(p, q); // ...

}

B) Utilizarea unui operator prieten supraîncărcat: class polinom {

double *a; int n;

public: polinom(); // Constructorul clasei // ... friend polinom operator+(polinom& a, polinom& b); // ... };

void main() { polinom p, q, r; r = p + q; // ...

}

Se observă faptul că definirea unui operator supraîncărcat reprezintă practic definirea unei funcţii, la care numele are forma predefinită:

operator <op>

unde <op> reprezintă operatorul ce se doreşte supraîncărcat. Apelul unui operator supraîncărcat se face conform sintaxei predefinite a operatorului respectiv. Deoarece un operator supraîncărcat este o funcţie asociată unei anumite clase, funcţia operator poate fi definită atât ca o funcţie membru, cât şi ca o funcţie prietenă a clasei respective. În cazul funcţiilor membru, numărul de parametri este întotdeauna mai mic cu 1 decât aritatea operatorului corespunzător, deoarece, în acest caz, un parametru este implicit (obiectul curent al clasei, reprezentat de parametrul ascuns this al funcţiei). Rezultă astfel o a treia variantă pentru clasa polinom: C) Utilizarea unui operator supraîncărcat, membru al clasei:

class polinom {

double *a; int n;

public: polinom(); // ... polinom operator+(polinom& a); // ...

}; void main() {

polinom p, q, r; r = p + q; // ...

}

Ceea ce diferă în ultimele două cazuri la apelul operatorilor este sintaxa generată de compilator pentru apel. În cazul funcţiei friend, sintaxa efectivă de apel pentru p+q este:

operator+(p, q)

pe când în cazul funcţiei membru, sintaxa este: p.operator+(q)

Page 4: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 4

În concluzie, în acest paragraf este vorba de funcţii care au un şablon în care apare cuvântul cheie operator, având sintaxa următoare:

tip_rezultat operator op (lista_de_argumente) {

Corpul funcţiei }

unde op poate fi unul din operatorii prezentaţi în tabelul următor:

+ - * / % ^ & | ~ ! , = < > <= >= ++ -- << >> == != || && += -= /= %= ^= &= |= <<= >>= [] () -> ->* new delete

Pentru determinarea numărului de argumente ale funcţiei operator, trebuie luate în considerare atât aritatea operatorului originar din limbajul C, cât şi dacă funcţia operator este o funcţie membru sau o funcţie de tip friend a unei clase.

Tabelele următoare prezintă sintaxa de apel şi forma de apel efectiv pentru operatorii unari şi binari (s-au notat cu X şi Y operanzii şi cu op operatorul) în cazul funcţiilor membru, precum şi al funcţiilor friend:

Sintaxa de apel Apel efectiv (funcţii membru) op X (op unar) X op (op unar) X op Y (op binar)

X.operator op() X.operator op() X.operator op(Y)

Sintaxa de apel Apel efectiv (funcţii friend) op X (op unar) X op (op unar) X op Y (op binar)

operator op(X) operator op(X) operator op(X,Y)

Utilizarea operatorilor supraîncărcaţi în cadrul unui program C++, trebuie să respecte anumite restricţii impuse de tratarea uniformă de către compilator a operatorilor. Astfel:

1. Nu se pot defini operatori noi, alţii decât cei prezentaţi în primul tabel; 2. Nu se poate schimba aritatea sau precedenţa (prioritatea) unui operator; 3. Operatorii nu pot fi combinaţi pentru a forma operatori noi. De exemplu, dacă s-au definit

operatorii + şi =, apelul += nu trebuie să însemne apelul lui +, urmat de apelul lui =. 4. Operatorii =,[] şi () trebuie să fie funcţii membru nestatice.

Necesitatea ultimei restricţii va fi prezentată într-un paragraf ulterior. După cum s-a specificat anterior, operatorii pot fi definiţi atât ca funcţii membru, cât şi ca funcţii prietene unei anumite clase. Excepţie fac operatorii =,(),[],-> şi ->*, care trebuie să fie întotdeauna funcţii membru. Pentru ceilalţi operatori pot fi luate în considerare următoarele sugestii de definire a operatorilor: Este indicat ca toţi operatorii unari să fie definiţi ca funcţii membru; Operatorii binari compuşi de atribuire (+=,-=,/=,*=,^=,&=,|=,%=,>>=,<<=) este

indicat să fie definiţi ca funcţii membru; Ceilalţi operatori binari este indicat să fie definiţi ca funcţii friend.

Page 5: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 5

9.3. Operatori unari Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare, datorită faptului că aceştia pot avea două forme de apel: prefixă şi postfixă. Efectul lor este acelaşi, dar valoarea de evaluare a unei expresii este diferită în cazul celor două forme de apel. Exemple:

int n = 4; if (n++ < 6) cout << ”OK\n”;

// Incrementarea lui n se face dupa // evaluarea inegalitatii n++ < 6

cout << n << endl; //n=5

if (++n < 6) cout << ”OK\n”; // Incrementarea lui n se face inainte // de evaluarea inegalitatii ++n < 6

cout << n << endl; //n=6

Rezultă astfel că există doi operatori diferiţi pentru operatorii de incrementare şi decrementare, compilatorul generând apeluri diferite pentru cele două forme ale acestora. De exemplu, în cazul unui operator de incrementare ++ definit ca o funcţie prietenă a unei clase, pentru forma prefixă ++a, se va genera un apel de forma:

operator++(a)

pe când în cazul formei postfixe a++, se va genera un apel de forma: operator++(a,int)

Parametrul auxiliar int este utilizat doar pentru a face deosebire între cei doi operatori. Un exemplu uzual de utilizare a operatorilor de incrementare şi decrementare îl constituie cazul claselor de tip container şi iterator. Un container este o colecţie de mai multe obiecte, la care se poate accesa un singur obiect la un anumit moment, aşa cum sunt listele şi vectorii. Un iterator este asociat întotdeauna unui container, el fiind responsabil cu accesarea obiectului curent din container, dar nu permite accesul la implementarea containerului. Exemplul 9.4. Se va implementa o listă ca un container. Accesul la obiectul curent din container este realizat cu ajutorul operatorului binar ->, iar trecerea la elementul următor celui curent se realizează cu ajutorul operatorului ++. Ambii operatori aparţin clasei iterator şi nu containerului.

class Node { // Clasa elementelor din container int val; Node *next;

public: Node(int v, Node *p = 0): val(v), next(p) {} ~Node() { next = 0; } int Val() const { return val; } void Print() const { cout << val << endl; } friend class List; friend class ListIterator;

};

class List { // Clasa container Node* first; void Copy(Node* p); void Delete();

public: List() { first = 0; } List(const List& l): first(0) { Copy(l.first); } ~List() { Delete(); first = 0; } void Add(int k) { // Inserare în fata listei

Page 6: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 6

Node* p = new Node(k); p->next = first; first = p;

} friend class ListIterator;

};

class ListIterator { // Clasa iterator List l; // Conteaza ordinea de declarare Node* current; // a membrilor !

public: ListIterator(List& ll): l(ll), current(l.first) {} Node* operator->() const { return current; } Node* operator++() { // operator prefix

current = current->next; return current;

} Node* operator++(int) { //operator postfix

Node* p = current; current = current->next; return p;

} void BeginIterator() // Reinitializare iteratii { current = l.first; }

};

void List::Copy(Node* p) { first = 0; for (Node* q = p; q; q = q->next)

Add(q->val); } void List::Delete() {

Node *p = first, *q; while (p) {

q = p; p = p->next; delete q;

} }

void main() { List l; l.Add(3); l.Add(7); l.Add(5); ListIterator it(l); // Atentie! Dupa afisarea lui 5, it++ devine NULL do

it->Print(); while(it++); it.BeginIterator(); // Corect. Se afiseaza 3,7,5. do

it->Print(); while(++it);

}

Page 7: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 7

9.4. Operatorul de atribuire

Operatorul de atribuire este unul dintre cei mai importanţi şi utilizaţi operatori supraîncărcaţi, fiind şi singurul operator care poate fi generat implicit de către compilator în lipsa definirii explicite a acestuia într-o clasă. Mai mult, operatorul de atribuire face parte din categoria operatorilor care trebuie să fie definiţi ca funcţie membru. Această restricţie este naturală, datorită operaţiei de atribuire:

<variabila> = <expresie>,

operatorul de atribuire fiind strâns legat de variabila ce reprezintă primul operand. Există o anumită asemănare între operaţia de atribuire şi cea de iniţializare a unei variabile, ceea ce generează o asemănare între operatorul de atribuire şi constructorul de copiere. Exemplul 9.5. Definirea şi utilizarea unei clase reprezentând numerele complexe.

class NumarComplex { double x,y;

public: // Constructor general

NumarComplex(double a=0, double b=0) {x=a; y=b;} // Constructor de copiere NumarComplex(NumarComplex& c) {x=c.x; y=c.y;} // Operator de atribuire void operator=(NumarComplex& c) {x=c.x; y=c.y;}

void Print() { cout << x << “ “ << y << endl; } }; void main() {

NumarComplex z1(2, 7); // Constructor general NumarComplex z2 = z1; // Constructor de copiere NumarComplex z3; z3 = z1; // Operator de atribuire z1.Print(); z2.Print(); z3.Print(); }

Deosebirea între atribuire şi iniţializare este semnificativă, deoarece în cazul iniţializării, în afară de iniţializarea valorilor membrilor unui obiect se alocă în plus şi memoria pentru obiectul respectiv. În cazul operatorului de atribuire, una dintre problemele care poate să apară se referă la tipul valorii returnate de acesta. Astfel, dacă tipul rezultatului este void, ca în exemplul precedent, nu se poate realiza o atribuire multiplă. De exemplu, pentru atribuirea:

z3 = z2 = z1;

compilatorul ar trebui să genereze: z3.operator=(z2.operator=(z1));

Dar z2.operator=(z1) are tipul void, pe când parametrul funcţiei z3.operator=()trebuie să fie tot o referinţă la un obiect de tipul NumarComplex. De regulă, operatorii de atribuire returnează un obiect instanţă al clasei respective sau o referinţă la un asemenea obiect. De exemplu, operatorul de atribuire pentru clasa NumarComplex din exemplul 9.4, poate fi definit astfel:

NumarComplex operator=(NumarComplex& c) { x = c.x; y = c.y; return *this;

}

Page 8: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 8

În cazul în care o clasă nu posedă un operator propriu de atribuire, compilatorul va genera un operator implicit, într-un mod asemănător constructorului de copiere: se va genera o asignare membru cu membru a datelor componente. Exemplul 9.6.

class A { public:

A& operator=(const A&) { cout << ”clasa A; operator=” << endl; return *this;

} }; class B {

A a1, a2; }; void main() {

// Se genereaza constructori impliciti pentru A si B B b1, b2; b1 = b2; // operator= implicit pentru clasa B // Se genereaza un constructor implicit pentru A A a3, a4; cout << endl; a3=a4; cout << endl;

} Programul anterior va afişa mesajul

clasa A; operator= clasa A; operator=

ceea ce înseamnă că s-a generat cate un operator de atribuire pentru membrii a1 si a2 încorporaţi în b1 si b2,urmat de mesajul:

clasa A; operator=

cand a4 este atribuit lui a3. Observaţiile de la constructorul de copiere în cazul claselor derivate ramân valabile şi în acest caz: dacă o clasă este derivată din una sau mai multe clase de bază şi conţine în plus şi date membru ce sunt instanţe ale altor clase, operatorul de atribuire generat implicit de compilator apelează mai întâi operatorii de atribuire ai claselor de bază, şi după aceea pe cei ai claselor la care aparţin obiectele membre, în ordinea specificării lor în clasa derivată. De exemplu, pentru clasa:

class D: public B2, B1 { M1 m1; M2 m2; // ...

}; o secvenţă de forma

class D d1, d2; // ... d2 = d1;

apelează operatorii de atribuire în următoarea ordine a claselor: B2,B1,M1,M2. În cazul în care o instanţă a unei clase componente sau de bază este la rândul ei o clasă derivată, regula de apel a operatorilor de atribuire se aplică în continuare recursiv. Există două restricţii importante la supraîncărcarea operatorului de atribuire, care nu se aplică la majoritatea celorlalţi operatori:

- funcţia operator= trebuie să fie nestatică, pentru a se asigura că operandul stâng al atribuirii este întotdeauna un obiect;

Page 9: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 9

- funcţia operator= nu poate fi moştenită într-o ierarhie de clase (ca şi constructorul de copiere), deoarece fiecare clasă derivată dintr-o ierarhie poate conţine şi membri specifici, iar pentru aceştia nu se poate utiliza un operator de atribuire dintr-o clasă de bază.

Exemplul 9.8. class B {

int n; public:

B(int k=0) { n=k; } B& operator=(B& b) { n=b.n; return *this; }

}; class D: public B {

int k; public:

D(int a, int b):B(a), k(b) {} D& operator=(D& d) {

k = d.k; // Copiaza membrul suplimentar B::operator=(d); // Se copiaza partea de baza return *this;

} }; void main(){

D d(1,5); D d1 = d; B b1(10); B b2 = b1; B b3 = d;

} Se observă că funcţiile operator de atribuire din clasa de bază şi clasa derivată au structuri diferite. În funcţia main() s-a utilizat totuşi operatorul de atribuire al clasei de bază, căruia i s-a transmis un obiect din clasa derivată, ceea ce este corect (datorită mecanismului de upcasting).

Pentru a înţelege mai bine cum lucrează operatorii de atribuire şi constructorii de copiere, programul din Exemplul 9.8 se prezintă într-o variantă modificată ca cea din Exemplul 9.8a.

Exemplul 9.8a class B {

int n; public:

B(int k=0) {n=k; cout << "Constructor clasa B: Obiect tip B = " << n <<endl;}

B& operator=(B& b) {cout << "Operator de atribuire in B "; n = b.n; return *this;} void Print() { cout << "n = " << n << endl; } };

class D: public B { int k;

public: D(int a, int b):B(a), k(b) {cout << "Constructor clasa D: Obiect

tip D = " << a << " " << k << endl;} D& operator=(D& d) {cout << "Operator de atribuire in D "; k = d.k; // Copiaza membrul suplimentar B::operator=(d); // Se copiaza partea de baza return *this;

} void Print() {B:: Print(); cout << "k = " << k << endl; }

};

Page 10: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 10

void main(){ D d(1,5), d1(9,7); d1 = d; d1.Print(); B b1(10); //B b2 = b1; // Se creeaza un constructor de copiere în B //b2.Print(); B b2; b2 = b1; // Se utilizeaza operatorul de atribuire din B b2.Print(); //B b3 = d1; // Se creeaza un constructor de copiere în B //b3.Print(); B b3; b3 = d1; // Se utilizeaza operatorul de atribuire din B b3.Print();

} Acest program va afişa următoarele mesaje: Constructor clasa B: Obiect tip B = 1 Constructor clasa D: Obiect tip D = 1 5 Constructor clasa B: Obiect tip B = 9 Constructor clasa D: Obiect tip D = 9 7 Operator de atribuire in D Operator de atribuire in B n = 1 k = 5 Constructor clasa B: Obiect tip B = 10 Constructor clasa B: Obiect tip B = 0 Operator de atribuire in B n = 10 Constructor clasa B: Obiect tip B = 0 Operator de atribuire in B n = 1 9.5. Operatori binari Majoritatea operatorilor supraîncărcabili sunt binari, deoarece singurul operator ternar nu se poate supraîncărca, iar operatorii unari sunt puţini. Posibilitatea de definire a operatorilor binari ca funcţii membru sau funcţii prieten diferă în funcţie de tipul lor, după cum s-a precizat anterior. Operatorii =,[],(),-> şi ->* este necesar să fie definiţi ca funcţii membru, pe când restul operatorilor binari este indicat să fie definiţi ca funcţii prieten. Avantajul utilizării operatorilor binari în funcţii operator de tip friend constă în posibilitatea conversiei automate a operanzilor, spre deosebire de funcţiile operator de tip membru, când operandul din stânga operatorului trebuie să aibă tipul de date corespunzător clasei în care s-a definit operatorul. Exemplul 9.9.

class Numar { int n;

public: Numar(int k=0): n(k) {}

// Functia operator+ este functie membru a clasei const Numar operator+(const Numar& k) const { return n + k.n; } // Functia operator- este functie friend a clasei friend const Numar operator-(const Numar&,const Numar&);

}; const Numar operator-(const Numar& n1, const Numar& n2) { return Numar (n1.n-n2.n); } void main(){

Numar a(7), b(3); a+b; // OK a+1; // OK: al doilea argument este convertit la Numar 1+a; // Eroare: primul argument trebuie sa fie Numar a-b; // OK a-1; // OK: al doilea argument este convertit la Numar

Page 11: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 11

1-a; // OK: primul argument este convertit la Numar }

Deoarece clasa Numar are un constructor de conversie de la int la Numar, la apelul unui operator de forma:

operator-(<arg1>, <arg2>)

se poate crea un obiect de tip Numar plecând de la o valoare întreagă, atât pentru primul argument, cât şi pentru al doilea. În cazul unui apel de forma:

<arg1>.operator+(<arg2>)

această conversie se poate realiza doar pentru argumentul al doilea. Exemplul 9.10. În exemplul următor vom defini o clasă numită PUNCT. Aceasta se referă la obiecte (puncte) din sistemul cartezian xOy şi conţine următoarele operaţii care se pot efectua cu coordonatele acestor obiecte:

- atribuirea, în sensul preluării conţinutului unui punct A(x, y); - adunarea şi scăderea coordonatelor a două puncte A(x, y) şi B(u, v); - rotirea razei vectoare ρ a unui punct A(x, y) cu un unghi α (radiani).

Definirea clasei în care apar o serie de redefiniri ale unor operatori utilizaţi pentru efectuarea unor operaţii cu obiecte (puncte) din planul xOy este realizată în fişierul antet PUNCT.H y yB B(u, v) yA A(x, y) ρ O α x xB xA // Fişier PUNCT.H utilizat în programul PUNCT.CPP

class PUNCT { protected:

double x, // abscisa y; // ordonata public:

PUNCT(double xx = 0, double yy = 0) // Constructorul clasei { x = xx; y = yy; } // Funcţiile membre ale clasei şi redefinirile operatorilor

PUNCT operator= (PUNCT); // Atribuirea valorii coord. x şi y double distOA(); // Lungimea razei vectoare ro

// a unui punct din plan double alfa(); // Unghiul razei ro cu axa Ox // A(x,y) + B(x,y), fără modificarea lui A(x,y) si B(x,y) PUNCT operator+ (PUNCT); // A(x,y) - B(x,y), fără modificarea lui A(x,y) si B(x,y) PUNCT operator- (PUNCT); PUNCT operator^ (double); // Rotire cu un unghi alfa void Imp (char *mesaj = " ");

Page 12: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 12

};

Programul propriu-zis care foloseşte acest fişier este următorul:

// Program PUNCT.CPP // Definiţiile propriu-zise ale operatorilor

# include <stdio.h> # include <math.h> # include "punct.h" PUNCT PUNCT :: operator= (PUNCT p) { x = p.x; y = p.y; return *this; // Se returneaza chiar obiectul curent }

double PUNCT :: distOA() { return sqrt (x*x + y*y); } // =ρ 22 yx + double PUNCT :: alfa() { return atan2 (y, x); } // α = arctg(y/x) PUNCT PUNCT :: operator+ (PUNCT p) { return PUNCT (x+p.x, y+p.y); } PUNCT PUNCT :: operator - (PUNCT p) { return PUNCT (x-p.x, y-p.y); } PUNCT PUNCT :: operator ^ (double a) { double xrotit = distOA() * cos(alfa() + a); double yrotit = distOA() * sin(alfa() + a); return PUNCT (xrotit, yrotit); }

void PUNCT :: Imp(char *m) { if (*m)

printf ("%s ", m); printf ("abscisa x = %f, ordonata y = %f\n", x, y);

} void main(void) { PUNCT a, b(10.,10.), c(-5.,-5.); double unghi = M_PI_4; // În <math.h>, pi/4 b.Imp("Punctul b de coordonate: "); c.Imp("Punctul c de coordonate: "); a = b + c; a.Imp("Punctul a = b+c de coordonate: "); a = b - c; a.Imp("Punctul a = b-c de coordonate: "); printf("Raza vectoare = %f\n", a.distOA()); printf("si azimutul = %f\n", a.alfa()); a = a ^ unghi; a.Imp("Punctul a, dupa rotire:"); printf("Raza vectoare = %f\n", a.distOA()); printf("si azimutul = %f\n", a.alfa()); }

În funcţia main() se creează trei obiecte: a, b(10., 10.) şi c(-5., -5). Folosind constanta M_PI_4, adică 4/π din fişierul <math.h>, se realizează operaţii asupra punctelor b şi c. În legătură cu modul de implementate a membrilor clasei PUNCT trebuie făcute următoarele observaţii: 1. Funcţia de tipul operator de atribuire PUNCT PUNCT :: operator= (PUNCT p) { x = p.x; y = p.y;

return *this; } // Se returneaza chiar obiectul

Page 13: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 13

recurge la operatorul this. Primul cuvânt PUNCT reprezintă tipul rezultatului acestei funcţii, iar cel de-al doilea, denumirea clasei. Funcţia preia coordonatele parametrului p (obiect de tip PUNCT) şi iniţializează variabilele private x şi y ale obiectului curent (obiectul this). Notaţia *this înseamnă transmiterea funcţiei apelante a conţinutului acestui obiect. Notaţia this înseamnă întoarcerea adresei (referinţei) obiectului curent. 2. În funcţia operator+, membră a clasei şi de tipul:

PUNCT PUNCT :: operator+ (PUNCT p) { return PUNCT(x+p.x, y+p.y); }

expresia PUNCT(x+p.x, y+p.y) echivalează cu crearea unui obiect cu durata de viaţă limitată. Rezultatul său este transmis sub forma unei structuri de tip PUNCT. Deci, se creează în mod adhoc un obiect curent de tip PUNCT şi când se ajunge la acolada “}“, obiectul “dispare“, eliberând resursele de memorie. 3. Se remarcă de asemenea o notaţie de forma:

double PUNCT :: distOA() { return sqrt (x*x + y*y); }

în care tipul rezultatului nu mai este o structură de tip PUNCT, ci un scalar de tip double. 9.6. Funcţii de tip friend şi operator

Considerăm o clasă ce conţine vectori alcătuiţi din câte trei elemente. Asupra acestora acţionează o serie de funcţii ale căror prototipuri şi definiţii sunt prezentate în listingul următor:

Exemplul 9.12. Funcţii de tip friend şi operator # ifndef VECTOR_H # define VECTOR_H # include <iostream.h> class VECT { // Definirea clasei denumită VECT

double v[3]; public:

VECT() // Funcţia constructor { v[0] = v[1] = v[2] = 0.0; } ~VECT () {} // Funcţia destructor - elimină întregul vector VECT(double x1, double x2, double x3);

// x(a,b,c) - vectorul x are elementele reale VECT(VECT &); // Constructor de copiere

// x = y ; vectorul x preia valoarea vectorului y VECT(double);

// Elementele lui x sunt o constantă reală VECT(int);

// Elementele lui x sunt o constantă întreagă VECT operator = (VECT);

// x = y ; vectorul x preia valoarea vectorului y VECT operator = (double);

// Elementele lui x vor fi o constantă reală VECT operator = (int);

// Elementele lui x vor fi o constantă întreagă friend VECT operator + (VECT, VECT); // x = a+b friend VECT operator - (VECT, VECT); // x = a-b friend VECT operator * (VECT, VECT); // x = a*b friend VECT operator / (VECT, VECT); // x = a/b friend VECT operator + (VECT); // x = +a friend VECT operator - (VECT); // x = -a

Page 14: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 14

VECT operator += (VECT); // a += b VECT operator -= (VECT); // a -= b VECT operator *= (VECT); // a *= b VECT operator /= (VECT); // a /= b double& operator[](int); // double d = a[i] friend int operator == (VECT, VECT); // int x = (a == b)

// egalitatea a doi vectori friend int operator != (VECT, VECT); // int x = (a != b)

// ingalitatea a doi vectori friend ostream& operator << (ostream &, VECT);

// cout << obiect tip VECT double ps(VECT); // x = a.ps(b) – produsul

// scalar a doi vectori };

// Definiţiile funcţiilor membru // v = w. // Valorile elementelor vectorului w sunt atribuite vectorului v VECT :: VECT(VECT &w) // Constructor de copiere { v[0] = w.v[0]; // Funcţie de atribuire v[1] = w.v[1]; v[2] = w.v[2]; }

// VECT v = scalar_v.m. // Vectorului v i se atribuie o valoare scalară în virgulă mobilă VECT :: VECT(double d) // Funcţie de atribuire { v[0] = v[1] = v[2] = d; }

// VECT v = scalar_întreg. // Vectorului v i se atribuie o valoare scalară întreagă VECT :: VECT(int k) // Funcţie de atribuire { v[0] = v[1] = v[2] = k; }

// VECT(x, y, x). // Valorile vectorului v sunt iniţializate cu trei numere reale VECT :: VECT(double x, double y, double z) // Funcţie de atribuire { v[0] = x; v[1] = y; v[2] = z; }

// v = w. // Valorile elementelor vectorului w sunt atribuite vectorului v VECT VECT :: operator = (VECT w)

// Funcţie de atribuire tip operator { v[0] = w.v[0]; v[1] = w.v[1]; v[2] = w.v[2]; return *this; // Se returnează vectorul curent v }

// VECT v = scalar_v.m. // Vectorului v i se atribuie o valoare scalară în virgulă mobilă VECT VECT :: operator = (double d)

// Funcţie de atribuire tip operator { v[0] = v[1] = v[2] = d;

Page 15: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 15

return *this; // Se returnează vectorul curent v }

// VECT v = scalar_întreg. // Vectorului v i se atribuie o valoare scalară întreagă VECT VECT :: operator = (int i)

// Funcţie de atribuire tip operator { v[0] = v[1] = v[2] = (double) i; return *this; // Se returnează vectorul curent v }

// Functii operator de tip friend // x = v1 + v2. Suma a doi vectori VECT operator + (VECT v1, VECT v2) { VECT elem( v1.v[0] + v2.v[0], v1.v[1] + v2.v[1], v1.v[2] + v2.v[2] ); return elem; // Se returnează referinta la obiectul local elem }

// x = v1 - v2. Diferenţa a doi vectori VECT operator - (VECT v1, VECT v2) { VECT elem ( v1.v[0] - v2.v[0], v1.v[1] - v2.v[1], v1.v[2] - v2.v[2] ); return elem; // Se returnează referinta la obiectul local elem }

// x = v1 * v2. Produsul elementelor corespondente a doi vectori VECT operator * (VECT v1, VECT v2) { VECT elem ( v1.v[0] * v2.v[0], v1.v[1] * v2.v[1], v1.v[2] * v2.v[2]); return elem; // Se returnează referinta la obiectul local elem }

// x = v1 / v2. Raportul elementelor corespondente a doi vectori VECT operator / (VECT v1, VECT v2) { VECT elem ( v1.v[0] / v2.v[0], v1.v[1] / v2.v[1], v1.v[2] / v2.v[2]); return elem; // Se returnează referinta la obiectul local elem }

// x = +u. Vectorului x i se atribuie valorile vectorului +u VECT operator + (VECT u) { VECT elem (+u.v[0], +u.v[1], +u.v[2]); return elem; // Se returnează referinta la obiectul local elem }

// x = -u. Vectorului x i se atribuie valorile vectorului -u VECT operator - (VECT u) { VECT elem (-u.v[0], -u.v[1], -u.v[2]); return elem; // Se returnează referinta la obiectul local elem }

// Funcţii operator membre // v += v1 sau v = v +v1 VECT VECT :: operator += (VECT v1)

Page 16: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 16

{ VECT elem ( v[0] += v1.v[0], v[1] += v1.v[1], v[2] += v1.v[2]); return *this; // Se returnează obiectul curent elem }

// v -= v1 sau v = v - v1 VECT VECT :: operator -= (VECT v1) { VECT elem ( v[0] -= v1.v[0], v[1] -= v1.v[1], v[2] -= v1.v[2]); return *this; // Se returnează obiectul curent elem }

// v *= v1 sau v = v * v1 (produsul elementelor corespondente) VECT VECT :: operator *= (VECT v1) { VECT elem ( v[0] *= v1.v[0], v[1] *= v1.v[1], v[2] *= v1.v[2]); return *this; // Se returnează obiectul curent elem } // v /= v1 sau v = v / v1 (raportul elementelor corespondente) VECT VECT :: operator /= (VECT v1) { VECT elem ( v[0] /= v1.v[0], v[1] /= v1.v[1], v[2] /= v1.v[2]); return *this; // Se returnează obiectul curent elem }

// d = u[j]. // Variabila reala d preia continutul elementelor vectorului u double& VECT :: operator [] (int j) { return v[(j >= 0) && (j < 3) ? j : 0]; }

// x = (v1 == v2). // Intregul x preia rezultatul testului de egalitate a doi vectori int operator == (VECT v1, VECT v2)

// Funcţie de tip friend si operator { return (v1.v[0] == v2.v[0] &&

v1.v[1] == v2.v[1] && v1.v[2] == v2.v[2]);

}

// x = (v1 != v2). // Intregul x preia rezultatul testului de inegalitate a doi vectori int operator != (VECT v1, VECT v2)

// Funcţie de tip friend si operator { return (v1.v[0] != v2.v[0] ||

v1.v[1] != v2.v[1] || v1.v[2] != v2.v[2]);

}

// cout << w. Afişarea conţinutului unui vector ostream & operator << (ostream& iesire, VECT w)

// Funcţie de tip friend si operator { return iesire << "adica [" << w.v[0] << "," << w.v[1]

<< "," << w.v[2] << "]";

Page 17: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 17

}

// Produsul scalar x = v1.ps(v2) double VECT :: ps (VECT v1) // Funcţie membra a clasei VECT { return (v[0]*v1.v[0] + v[1]*v1.v[1] + v[2]*v1.v[2]); }

# endif

void main(void) { VECT x, y, a(1., 2., 3.), b(-1., -2., -3.);

// Se creează obiectele x, y, a şi b cout << "a = a, " << a << endl; cout << "b = b, " << b << endl; int rez; double d; // Vectorului x i se atribuie valorile vectorului -a x = -a; // Vectorului y i se atribuie valorile vectorului +b y = +b; cout << "x = - a, " << x << endl; cout << "y = + b, " << y << endl; x = a + b; y = a - b; cout << "x = a + b, " << x << endl; cout << "y = a - b, " << y << endl; x = a * b; y = a / b; cout << "x = a * b , " << x << endl; cout << "y = a / b , " << y << endl; x += x; cout << "Dublarea lui x, x = x + x, " << x << endl; y -= y; cout << "Scaderea y = y - y, " << y << endl; rez = a == b; // rez preia rezultatul de egalitate a vectorilor a si b cout << "rez = (a == b) care da " << rez << endl; rez = a != b; // rez preia rezultatul de inegalitate a vectorilor a si b cout << "rez = (a != b) care da " << rez << endl; d = a.ps (b); // d preia produsul scalar al vectorilor a si b cout << "Produsul scalar (a.b) este " << d << endl; // cout << "\n Vectorul x are valoarea anterioara, " << x << endl; d = x[2]; cout << " d = x[2], adica " << d << endl; int i = 5; x = i; y = -i; cout << "\n x = i, " << x << endl; cout << "\n y = -i, " << y << endl; }

Operaţiile permise cu obiecte de tip VECT sunt atât cele binare cât şi cele unare. Iniţializările unui vector pot fi atât implicite (realizate prin intermediul funcţiei constructor), cât şi explicite (realizate prin intermediul unor funcţii membre ale clasei). Eliminarea din memorie a unui obiect de tip vector se face cu ajutorul destructorului ~VECT(). Se admit, de asemenea, şi următoarele operaţiuni:

Page 18: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 18

a) Atribuirea: x = w; Prototipul funcţiei de atribuire este VECT(VECT & w) sau mai pe scurt VECT(VECT &). Se vede că parametrul de atribuit w este o referinţă la un obiect de tipul VECT. // Definiţia funcţiei este:

VECT::VECT(VECT &w) { v[0] = w.v[0];

v[1] = w.v[1]; v[2] = w.v[2];

return *this; // Se returnează vectorul curent v }

Cele trei instrucţiuni de atribuire de mai sus iniţializează elementele vectorului v al obiectului curent, this.

b) Atribuirea unui scalar real sau întreg tuturor elementelor vectorului v cu funcţiile VECT(double) şi respectiv VECT(int).

c) Atribuirea efectuată prin intermediul funcţiei operator = pentru cele trei cazuri de mai sus, utilizând respectiv funcţiile:

VECT operator = (VECT); // Valorile unui vector sunt atribuite unui alt vector VECT operator = (double); // Elementelor unui vector li se atribuie o valoare reală VECT operator = (int); // Elementelor unui vector li se atribuie o valoare întreagă

Este evident faptul că cele două modalităţi de atribuire prezentate mai sus sunt echivalente. d) O serie de funcţii de tip operator sunt şi de tipul friend. În acest sens există

următoarele reguli pe care trebuie să le avem în vedere: R1. Funcţia de tip friend care are în lista de intrare un singur parametru foloseşte un operator

op ce trebuie să fie de tip unar. Dacă funcţia are o listă de intrare formată din doi parametri, operatorul op va trebui să fie unul de tip binar.

R2. În contextul unei funcţii operator şi membră a clasei, dacă op este unar, lista de intrare va trebui să fie vidă, iar când op este binar, lista va trebui să fie formată dintr-un singur parametru. Sintaxa definirii unei funcţii de tip operator şi care este şi membră a clasei este:

tip_rezultat nume_clasă :: operator op (listă_de_parametri) { Corpul funcţiei}

Sintaxa folosită la definirea unei funcţii operator care este admisă numai ca funcţie de tip friend a clasei este:

friend tip_rezultat operator op (listă_de_parametri) { Corpul funcţiei}

Menţionăm că utilizarea funcţiilor de tip friend în acest exemplu a fost motivată de realizarea unor operaţii cu vectori de tipul a+b, a-b, a*b, a/b sau a =+b, a=-b.

e) Funcţia friend ostream & operator << (ostream & VECT) este o funcţie care se ocupă de listarea conţinutului obiectelor de tip VECT. Se redefineşte de fapt operatorul << al cărui sens, în limbaj C/C++, este de deplasare spre stânga. Aici are rolul de transmitere a informaţiilor la fişierul logic de ieşire.

f) Notaţia double & operator [] (int) înseamnă redefinirea operatorului de indexare []. În sens normal, operatorul [] este folosit pentru adresarea unui element al vectorului. În contextul prezentat, acestuia i se păstreză aceeaşi semantică, deci se urmăreşte adresarea unui element din vectorul v (dată de tip private). Indicele este transmis ca argument de intrare de tip int.

g) În exemplul anterior, funcţiile declarate de tip friend, friend VECT operator op (VECT, VECT);

op având valorile +, -, *, /, == şi !=, se puteau defini şi ca funcţii membre ale clasei. În acest caz, în funcţia main() trebuia folosită notaţia: obiect1.operator op (obiect2).

h) Observaţia următoare se referă la operatorul this utilizat ca *this. Fie funcţia de tipul operator şi membră a clasei:

Page 19: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 19

VECT VECT :: operator = (VECT w) { v[0] = w.v[0];

v[1] = w.v[1]; v[2] = w.v[2]; return *this; } Valorile elementelor vectorului w sunt atribuite vectorului v. Notaţia *this înseamnă obţinerea întregului obiect drept rezultat al unei instrucţiuni de atribuire (x = y).

i) Acestă ultimă observaţie se referă la o serie de funcţii de tipul operator şi friend, ca de exemplu, funcţia

VECT operator + (VECT v1, VECT v2); În cadrul acestei funcţii s-a creat o variabilă locală (un obiect local de tip VECT) denumită

elem. Prin intermediul instrucţiunii return se transmite în exteriorul funcţiei referinţa la acest obiect local. 9.7. Conversia tipurilor de date Limbajul C permite două moduri de conversie a tipurilor de date: conversia explicită a tipurilor de date (operatorul cast, de conversie explicită), precum şi o conversie implicită a acestora. Conversia explicită nu presupune supraîncărcarea operatorilor, ci un set de operatori predefiniţi ai limbajului C++. A. Conversia explicită a tipurilor În limbajul C, operaţiile de conversie utilizând operatorul cast, se utilizează sub forma: int i; double d, e ; d = (double) i; e = (double) (4/10); În C++, utilizând operatorul cast, ultima conversie se scrie astfel e = double(4/10). Observaţie. (double)4/10 este diferit de expresia (double)(4/10). Aceasta pentru că operatorul cast are prioritatea mai mare decât operatorul “/“. De reţinut este faptul că notaţia double(4/10) este un apel de funcţii de tip cast. Astfel de exemple sunt plasate în listingul de mai jos: Exemplul 9.13. Utilizarea operatorului "cast" # include <stdio.h> void main(void) {

double num1, num2, num3; int i = 7; num1 = double (i/10); num2 = (double) i/10; // Operatorul cast are prioritate

// fata de operatorul "/" num3 = (double) (i/10); printf("num1 = %lf, num2 = %lf, num3 = %lf\n", num1, num2, num3); float j = 7; num1 = double (j/10); num2 = (double) j/10; // Operatorul cast are prioritate

// fata de operatorul "/" num3 = (double) (j/10); printf("num1 = %lf, num2 = %lf, num3 = %lf\n", num1, num2, num3);

}

Limbajul C++ suportă conversia explicită a tipurilor din C, dar are în plus o serie de operatori specifici de conversie explicită. Avantajul utilizării acestora constă în faptul că fiecare din ei tratează o anumită categorie de conversii de tip.

Page 20: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 20

Principalii operatori de conversie explicită sunt: static_cast, const_cast, dynamic_cast şi reinterpret_cast. Pentru aplelul acestora, în loc de sintaxa tradiţională:

(<tip>) <expresie>

se utilizează sintaxa: <operator_cast> ‘<’ <tip> ‘>’ <expresie>

unde <operator_cast> poate fi unul dintre operatorii menţionaţi anterior. Operatorul static_cast este principalul operator de conversie explicită şi se utilizează în general pentru conversiile bine definite pentru care se poate utiliza operatorul cast al limbajului C. Ceilalţi operatori de conversie sunt operatori specializaţi, care pot fi utilizaţi în anumite cazuri particulare. Operatorul const_cast este utilizat în cazul calificatorilor const şi volatile. Notând cu T un tip de date, acest operator permite conversia de la un tip const T sau volatile T la tipul T sau un tip derivat din T (T* de exemplu). Operatorul dynamic_cast este utilizat doar în cazul ierarhiilor de clase, pentru a realiza conversia de la o clasă aflată în partea de sus a unei ierarhii de clase la o clasă aflată în partea de jos a acesteia. Operaţia se numeşte în mod uzual downcasting şi se va discuta într-un capitol destinat polimorfismului. Observaţie. Operatorul se poate utiliza doar în cazul ierarhiilor de clase care utilizează polimorfismul (care posedă funcţii virtuale). Operatorul reinterpret_cast este utilizat în cazurile în care un obiect este privit ca o structură de biţi şi se doreşte să fie interpretat ca un obiect cu o structură complet diferită. În mod uzual, rezultatul conversiei este dependent de implementare, ceea ce înseamnă că acest tip de conversie nu este portabil. O folosire uzuală a operatorului reinterpret_cast constă în conversia între diferite tipuri de pointeri.

B. Conversia implicită a tipurilor Un exemplu de conversie automată a tipurilor de date în cadrul limbajului C apare la evaluarea expresiilor. De exemplu, în cazul definirii funcţiei următoare:

void f(double x) { cout << x << endl; }

dacă funcţia f se apelează cu un parametru întreg, f(5), compilatorul realizează o converise automată de la tipul int la double. În cazul limbajului C++ este posibil să se realizeze o conversie automată şi pentru tipurile de date definite de utilizator. Există pentru aceasta două posibiltăţi: folosind constructori de conversie sau folosind operatori de conversie. B1) În cazul utilizării constructorilor de conversie, se poate realiza conversia automată de la tipul parametrului constructorului respectiv la tipul clasei în care este definit constructorul. Exemplul 9.14.

class A { int n;

public: // constructor de conversie de la tipul int la tipul A A(int k=0) { n=k; cout<<"Constructor A"<<endl;} void Print() { cout<<n<<endl; }

}; void f(A a) {

a.Print(); } void main() {

A a(10); f(a); // nu se face conversie de tip f(5); // se face conversie int -> A

Page 21: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 21

} La al doilea apel al funcţiei f se apelează construcorul de conversie al clasei A. Programul va afişa:

Constructor A 10 Constructor A 5

Există însă cazuri când nu se doreşte să se realizeze o conversie automată (implicită) a tipurilor. În aceste situaţii se poate împiedica conversia prin plasarea cuvântului cheie explicit în faţa constructorului respectiv. Exemplul 9.15.

class B1 { public:

double x; B1(double z = 0.0): x(z) { }

}; class B2 { public:

double y; B2(double z = 0.0): y(z) { } // Se poate realiza conversia automata B1 -> B2 B2(const B1& b): y(b.x) { } void Print() { cout << y << endl; }

}; class B3 { public:

double k; B3(double z = 0.0) { k = z; } // Nu se permite conversia implicita B1 -> B3 explicit B3(const B1& b): k(b.x) { } void Print() { cout << k << endl; }

}; void f(B3 b) { b.Print(); } void g(B2 b) { b.Print(); } void main() { B1 b1; B2 b2; B3 b3; g(b1); // OK! Exista operator de conversie B1 -> B2 f(b2); // Eroare! Nu exista conversia implicita B2 -> B3 f(b1); // Eroare! Nu este permisa conversia implicita B1->B3 f(b3); // OK! Nu este nevoie de conversie f(B3(b1)); // OK! Conversia este apelata explicit de programator }

B2) A doua metodă ce permite conversia automată a tipurilor utilizează un operator de conversie. Acesta este un operator cu o sintaxă specială ce permite conversia de la tipul clasei în care se defineşte operatorul la un tip de date dorit de programator. Prototipul unui asemenea operator este:

operator <nume_clasa> ()

Se observă faptul că numele operatorului este numele clasei destinaţie a conversiei, iar tipul de date al funcţiei operator lipseşte, fiind înlocuit de cuvântul cheie operator. Exemplul 9.16.

class C1 {

Page 22: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 22

int n; public:

//Constructor de conversie: int -> C1 C1( int i = 0): n(i) { } void Print() const { cout << n << endl; }

}; class C2 {

int m; public:

//Constructor de conversie: int -> C2 C2(int i): m(i) { } //Operator de conversie: C2 -> C1 operator C1() const { return C1(m); }

}; void h(C1 c) { c.Print(); } void main() {

C1 c1(5); c1.Print(); C2 c2(10); h(c2); // Se aplica operatorul de conversie h(1); // Se aplica constructorul de conversie }

Deosebirea între cele două metode constă în faptul că în cazul constructorului de conversie, operaţia de conversie între un tip sursă şi un tip destinaţie se realizează de către obiectele tipului destinaţie, pe când în cazul operatorului de conversie obiectele tipului sursă sunt responsabile de această conversie. În mod uzual este permis un singur tip de conversie automată între două tipuri de date. În cazul în care s-ar permite mai multe metode de conversie (şi operator de conversie şi constructor de conversie), poate apare confuzie la selectarea de către compilator a tipului de conversie. Exemplul 9.17.

class B; class A { public:

operator B() const ; // Conversie A -> B }; class B { public:

B(A); // Conversie A -> B }; void f(B) { } void main() { A a; f(a); // Eroare!! Ambiguitate apel }

O altă eroare ce poate apare se referă strict la operatorul de conversie. Într-o anumită clasă este permisă o singură conversie de tip spre o altă clasă cu ajutorul acestui operator, pentru că, în caz contrar, ar putea să apară confuzie la selectarea de către compilator a operatorului dorit de conversie. Exemplul 9.18.

class A; class B; class C { public:

operator A() const ; // Conversie C -> A operator B() const ; // Conversie C -> B

}; void f(A a);

Page 23: Cap. 9 ărcarea operatorilor şi funcţiilor · 9 - 5 9.3. Operatori unari . Dintre operatorii unari, singurii operatori care pot prezenta probleme sunt cei de incrementare şi decrementare,

9 - 23

void f(B b); // Supraincarcarea functiei f void main {

C c; f(c); // Eroare!!

} În exemplul precedent, nu se poate determina care versiune a funcţiei f trebuie apelată.