<<Pagina Materiale

 
 
Supraincarcarea operatorilor. Operatii de intrare-iesire 


Supraincarcarea operatorilor

Functiile operator constituie un tip special de functii, care se pot utiliza pentru redefinirea operatorilor de baza care apar în C. O clasa se poate defini împreuna cu un set de operatori asociati, obtinuti prin supraîncarcarea operatorilor existenti. In acest fel, se efectueaza operatii specifice noului tip, la fel de simplu ca în cazul tipurilor standard. Procedeul consta în definirea unei functii cu numele

        operator < simbol >
Exemplu:

Se defineste o clasa NrComplex si se doreste supradefinirea operatorului de adunare, pentru adunarea a doua numere complexe. Functia operator+() trebuie sa primeasca doi parametri de tipul NrComplex si sa returneze un rezultat de tip NrComplex, avand acces direct la datele membre ale clasei.

Exista doua posibilitati de realizare:

  • ca functie friend a clasei NrComplex sau
  • ca functie membra a clasei NrComplex.
Mai jos apare supraîncarcarea operatorului + ca functie friend:
 
class NrComplex {
   float real, imaginar;
   public:
        NrComplex(float r=0, float i=0);
        friend NrComplex operator+(NrComplex, NrComplex);
       //operatorul suma definit ca functie friend a clasei
   };

NrComplex  operator+ (NrComplex c1, NrComplex c2) {
   NrComplex c;
   c.real=c1.real+c2.real;
   c.imaginar=c1.imaginar+c2.imaginar;
   return c;
  }

void main() {
   NrComplex c1(1,2), c2(5,5), s;
   s=c1+c2; //expresia c1+c2 este interpretata de compilator ca un apel al functiei 
                        //operator+(c1,c2)
}

In continuare este prezentata supraîncarcarea operatorului + ca functie  membra:
 

class NrComplex {
   float real, imaginar;
   public:
        NrComplex(float r=0, float i=0);
        NrComplex operator+(NrComplex); 
       // operatorul suma definit ca functie membra a clasei
   };

NrComplex  NrComplex::operator+ (NrComplex c2) {
   NrComplex c;
   c.real=real+c2.real;
   c.imaginar=imaginar+c2.imaginar;
   return c;
}

void main() {
   NrComplex c1(1,2), c2(5,5), s;
   s=c1+c2; //expresia c1+c2 este interpretata de compilator ca 
         //un apel al functiei c1.operator+(c2)
}

O functie membru primeste in mod implicit adresa obiectului pentru care este apelata. Functia operator+() va avea prototipul

      NrComplex operator+(NrComplex);

In cazul supradefinirii operatorului + ca functie membra, datorita transferului implicit al primului operand, apare o asimetrie in definitia functiei operator+().

Mai apare si o alta restrictie impusa de functiile operator membre ale clasei: primul operand este intotdeauna de tipul clasa respectiv, deci solutia nu este adecvata daca primul operand trebuie sa fie de un alt tip.

Alte precizari privind supradefinirea operatorilor

Se pot supradefini in C++ numai operatori existenti, deci simbolul asociat functiei operator trebuie sa fie deja definit ca operator pentru tipurile standard (nu e permisa introducerea unor simboluri noi de operatori).
De asemenea, nu se pot modifica:

  • pluralitatea (un operator unar nu poate fi supradefinit ca unul binar sau invers)
  • precedenta
  • asociativitatea.
Functia operator trebuie sa aiba cel putin un parametru (implicit sau explicit) de tipul clasa caruia ii este asociat operatorul respectiv. Aceasta restrictie implica faptul ca supradefinirea operatorilor e posibila numai pentru tipurile clasa definite in program, pentru tipurile standard operatorii isi pastreaza definitia.

Functiile operator pot fi implementate ca si functii membre a clasei sau ca si functii prietene a clasei. In particular, pentru operatorii:= [] () -> functia operator trebuie sa fie membra a clasei.

In general, pentru un operator binar op, expresia x op y este interpretata:

   x.operator op(y); // functie membra

sau

 operator op(x,y); // functie friend

In general, pentru un operator unar op, expresia x op sau op x este interpretata:

   x.operator op(); // functie membra

sau

 operator op(x); // functie friend

Supraincarcarea operatorului de atribuire

Daca se executa un constructor implicit de copiere sau o copierea implicita intre doua obiecte - shallow copy - in obiectul destinatie se insereaza referinte spre membrii celui original. Prin definirea unui constructor de copiere sau supraincarcarea operatorului de atribuire -  deep copy - se creaza cate o copie pentru fiecare membru din obiectul original, important mai ales daca acestia sunt pointeri.

Prin supraincarcarea operatorului de atribuire se obtine controlul asupra a ceea ce se intampla cand un obiect este atribuit altuia, cu atat mai important cand obiectele se creaza dinamic.

Cand se aplica operatorul de atribuire, se apeleaza operatorul de atribuire pentru clasa respectiva, care poate avea doua forme:.

   X& X::operator= (const X& x);
   const X& X::operator= (const X& x);
Operatorul de asignare trebuie sa fie o functie membru, nu friend si nu poate fi statica.

Valoarea returnata trebuie sa fie o referinta const la obiectul catre care se face asignarea - in felul acesta pot apare atribuiri multiple intr-o expresie ( este corect efectul lateral ); de obicei se returneaza *this.

   NrComplex w, x, y, z;
   w = x = y = z = NrComplex(0,0);
Returnarea unei referinte este mai eficienta decat cea a unui obiect, ocupand un spatiu mai redus; fiind const se previne modificarea accidentala a obiectelor.

Este indicat ca implementarea operatorului de atribuire sa nu permita atribuirea catre acelasi obiect - de obicei acesta este modificat:

   const X& X::operator= (const X& ob)
   {
      if ( &ob != this )
      {
        // se asigneaza datele membru
      }
      return *this;
   }

Este indicat ca orice clasa sa defineasca propriul operator de atribuire, deci lucrurile sa nu fie lasate a fi tratate implicit de compilator - cu atat mai mult daca are campuri de tip pointer. 

Daca este vorba despre o clasa derivata, atribuirea trebuie sa realizeze operatia si pentru membrii clasei de baza.

Prelucrarea comuna care se realizeaza de operatorul de atribuire si constructorul de copiere este bine sa fie plasata intr-o functie separata, care sa fie apelata din fiecare - pentru claritate si cod mai compact.

Sus
Operatii de intrare-iesire

Clasele istream, ostream, iostream

Limbajul C++, ca si limbajul C de altfel, nu contine instructiuni specifice pentru operatiile de intrare-iesire. Acestea se efectueaza cu ajutorul unor proceduri de biblioteca si constituie o aplicare a conceptelor generale ale limbajului: programare orientata pe obiecte, cu ierarhie de clase, supraîncarcarea operatorilor.

Modelul fundamental pentru constructia bibliotecii de intrare-iesire este cel de sir - stream, vazut ca un flux de date de la o anumita sursa la o anumita destinatie.

Exista doua ierarhii de clase, având ca baze clasele streambuf si ios.

Ierarhia streambuf realizeaza gestionarea tampoanelor de memorie utilizate în efectuarea operatiilor de intrare- iesire.

Ierarhia ios gestioneaza toate operatiile de intrare-iesire si pune la dispozitie o interfata de nivel înalt pentru programator.

Principalele categorii de clase sunt:

  • clasa istream - derivata dinios, pentru transferuri de la un obiect streambuf. Obiectele clasei reprezinta dispozitive de intrare.
  • clasa ostream - derivata din ios pentru transferuri catre un obiect streambuf - dispozitive de iesire
  • clasa iostream - derivata din istream si ostream pentru operatii bidirectionale
Ierarhia ios
In sistemul de intrare-iesire C++ sunt prevazute dispozitive logice predefinite:cin(intrare consola),cout(iesire consola),cerr (iesire ptr erori).

Clasele istream si ostream dispun de seturi de functii de intrare/iesire si supradefinesc cei doi operatori de deplasare bit cu bit (shift) pentru a efectua transferul de informatie cu un stream si formatarea pentru tipurile de baza:

  • operatorul << pentru operatii de iesire
  • operatorul >> pentru operatii de intrare
In clasa ostream, operatorul << este supradefinit sub forma:

    ostream& operator << (tip_de_baza);

Operatorul admite ca operanzi o expresie de un tip fundamental oarecare si, in mod implicit, adresa obiectului din clasa ostream specificat la apelare. Rolul sau este de a transfera valoarea expresiei, cu un format adecvat, catre acel stream. Operatorul returneaza ca rezultat adresa dispozitivului pentru a permite operatii de iesire multiple, inlantuite.

In clasa istream, operatorul >> este supradefinit sub forma:

   istream& operator >> (tip_de_baza);

Exemplu:
 

#include <iostream.h>
  void main(void) {
  int c;
  cin >> c; //citirea unui caracter (diferit de Enter !!!)
  cout << c;//tiparirea lui
}

Supraincarcarea operatorilor <<  si  >>  pentru tipuri de date definite de programator

Este posibila supradefinirea operatorilor de inserare/extragere din stream pentru a permite utilizarea lor si pentru tipuri de date definite de programator, pe langa cele standard.

Declaratiile operatorilor pot fi de forma:

     istream& operator>> (istream&, tip_utilizator&);

    ostream& operator<< (ostream&, tip_utilizator);

Primul argument trebuie sa fie o referinta la un obiect istream sau ostream, sau al uneia din clasele derivate acestora. De aceea, functiile operator nu pot fi membre ale clasei definite de utilizator. De regula sunt functii independente friend ale clasei definite de utilizator. Rezultatul returnat trebuie sa fie adresa obiectului stream primit ca argument, pentru a permite efectuarea unei secvente de operatii.

Exemplu:
 

   class NrComplex {
      float re, im;
     public:
      NrComplex(float r=0, float i=0) {
         re=r; im=i;
         }
      friend istream& operator >> (istream&, NrComplex&);
      friend ostream& operator << (ostream&, NrComplex); 
     };

   istream& operator >> (istream& in, NrComplex& c) {
     in>> c.re >> c.im;
     return in; 
   }

   ostream& operator << (ostream& out, NrComplex c){
     out << "Re=" << c.re << " Im=" << c.im;
     return out;
   } 

Sus
Exercitii

1.Sa se realizeze o clasa NrComplex, care implementeaza operatii pentru manipularea numerelor complexe.

Clasa NrComplex va utiliza private doua numere reale pentru partea reala, respectiv partea imaginara a numarului complex. Se vor implementa operatiile: constructori, destructor, operator +, operator +=, operator +, operator -, operator *, operator /.

2.Sa se realizeze o clasa Multime, care implementeaza operatii asupra unor multimi de litere.

Clasa Multime va utiliza private un unsigned long pentru memorarea elementelor multimii (pe biti). Se vor implementa operatiile: constructor, destructor, adaugare element (operator +), reuniune (operator +), intersectie (operator *), diferenta (operator -), test apartenenta element, test incluziune multimi (operator <).

3.Sa se realizeze o clasa String, care implementeaza operatii cu siruri de caractere.

In C, sirurile de caractere sunt reprezentate prin tablouri de caractere terminate cu caracterul nul, asupra carora opereaza functiile de biblioteca definite in "string.h", care opereaza asupra unor pointeri la char. Aceasta implementare in stil C are o serie de limitari: de exemplu, copierea si compararea sirurilor arata total diferit de copierea si compararea intregilor.(Operatorii "=" si "==" au semnificatii diferite de copiere si comparare !). Pentru a remedia acest aspect, se vor supraincarca operatorii de comparare si atribuire pentru noul tip String. O alta problema la char * o constituie alocarea dinamica: utilizatorul trebuie sa dezaloce memoria ocupata de sir cand acesta nu mai este utilizat.

Clasa String va utiliza private un tablou de caractere pentru memorarea string-ului, si va implementa urmatoarele operatii: constructor, destructor, operator =, operator [], operator const char *, operator ==, operator !=, operator <, operator >, Length.

4.Sa se realizeze o clasa Punct care implementeaza operatii asupra unor puncte din spatiul 3D.

Clasa Punct va utiliza private 3 numere reale pentru memorarea celor 3 coordonate ale punctului in spatiu, si va implementa operatiile: constructori, destructor, operator =, operator ==, operator +, operator -.

5.Sa se realizeze o clasa NrMari care implementeaza operatii aritmetice asupra unor numere intregi foarte lungi(pot avea pina la 200 de cifre).

Clasa NrMari va utiliza private un sir de caractere pentru memorarea numarului si vor fi implementate operatiile: constructori, destructor, operator +, operator -, operator *, operator /, operator <, operator >.

Sus
<<Pagina Materiale



Copyright © 2001-2002. Carmen Holotescu
All rights reserved. Published by Timsoft