>> Inapoi <<

 
                                                                      
                        
                               --------
                                Curs 2
                               --------
                               
                               
-----------------------------------------------------
 Definirea claselor si a tipurilor abstracte de date
-----------------------------------------------------

  O clasa este un tip de date a carei variabile sunt obiecte. Un obiect este o variabila care are
  membri functii la fel ca si posibilitatea de memorare a valorilor de date.
  O structura este un tip definit ce permite declararea de membri variabile. Deci, pentru a 
  obtine o clasa dintr-o structura, trebuie sa adaugam functii membru.


--------------
 Incapsularea
--------------

  Combinarea unui numar de articole, cum ar fi variabilele si functiile, intr-un singur pachet,
  cum ar fi obiectul unei clase, se numeste incapsulare.Cand se defineste o functie membru, 
  definitia trebuie sa includa numele clasei, pentru ca pot exista doua sau mai multe clase care
  au functii membru cu acelasi nume. Operatorul :: se numeste operatorul rezolutiei de domeniu, 
  si serverste la un scop similar cu cel al operatorului punct. Atat operatorul punct, cat si al
  rezolutiei de domeniu precizeaza din care clasa face parte functia membru respectiva. 
  ATENTIE ! Operatorul rezolutiei de domeniu :: este folosit cu numele clasei, pe cand operatorul
  punct este folosit cu obiecte (adica variabile ale unei clase).Numele clasei care precede 
  operatorul rezolutiei de domeniu se numeste calificator de tip, deoarece acesta califica numele
  functiei la unul particular.Sintaxa generala a definitiei unei functii membru a clasei C 
  foloseste operatorul rezolutiei de domeniu :

   Tip_Returnat C :: nume_functie (lista_parametri)

   Exemplu:
    Functia afisare() din programul C++ de mai jos apartine clasei zi_din_an.   In concluzie, 
    retinem ca operatorul punct este folosit pentru apelarea unei date sau functii membru ale 
    unui obiect, iar operatorul rezolutiei de domeniu este folosit pentru definirea unei functii
    membru a unei clase.
  Programul urmator este un exemplu simplu de clasa.
  
    #include 
    
    class zi_din_an
     {
      public:
       int zi;
       int luna;
       void afisare();
     };
     
    int main()
     {
      zi_din_an azi, zi_nastere;
      
      cout << "Dati data de azi (zi luna, ex: 22 03):\n";
      cin >> azi.zi >> azi.luna;
      cout << "Dati ziua de nastere:\n";
      cin >> zi_nastere.zi >> zi_nastere.luna;
      
      cout << "Azi suntem in ";
      azi.afisare();
      cout << "Ziua dvs de nastere este ";
      zi_nastere.afisare();
      
      if (azi.luna==zi_nastere.luna 
          && azi.zi==zi_nastere.zi)
       cout << "La multi ani !\n";
      else
       cout << "O zi buna !\n";
      return 0;
     }
    
    void zi_din_an::afisare()
     {
      cout << "luna=" << luna
           << ",ziua" << zi << endl;
     }
     

---------------------------
 Membrii private si public
---------------------------

  Am vazut in exemplul precedent ca datele si functiile membru din clasa zi_din_an erau public. 
  Deci acestea erau accesibile din orice functie a programului respectiv. Sunt situatii cand nu 
  ne intereseaza modul de implementare (codificare, memorare) a datelor sau functiilor sau nu 
  dorim sa fie accesibile. Atunci le vom declara private. Acest lucru se face folosind cuvantul 
  rezervat private, lista membrilor care apar dupa acesta se numesc membri privati (date sau 
  functii private).
  Programul urmator ilustreaza aceasta tehnica pentru date private.
  
    #include 
    
    class zi_din_an
     {
      public:
       void citire();
       void afisare();
       void set(int noua_zi, int noua_luna);
       //Preconditie: noua_zi si noua_luna formeaza o data posibila
       //Postconditie: data este resetata conform cu argumentele
       int obtine_zi();
       //Returneaza ziua din luna
       int obtine_luna();
       //Returneaza luna(1-ianuarie, 2-februarie, etc. )
      private:
       int zi;
       int luna;
     };
     
   int main()
    {
     zi_din_an azi, zi_nastere;
     
     cout << "Dati data de azi "
          <<"(zi luna, ex:22 03):\n";
     azi.citire();
     cout << "Azi suntem in ";
     azi.afisare();
     
     zi_nastere.set(24,3);
     cout << "Ziua de nastere este ";
     zi_nastere.afisare();
     
     if (azi.obtine_zi()==zi_nastere.obtine_zi()
         && azi.obtine_luna()==zi_nastere.obtine_luna())
      cout << "La multi ani !\n";
     else
      cout << "O zi buna !\n";
     return 0;
    }
    
   void zi_din_an::afisare()
    {
     cout << "luna=" << luna
          << ",ziua=" << zi << endl;
    }
    
   void zi_din_an::citire()
    {
     //membrii privati pot fi folositi in definitia functiilor
     cin >> zi >> luna;
    }
    
   void zi_din_an::set(int noua_zi, int noua_luna)
    {
     zi=noua_zi;
     luna=noua_luna;
    }
    
   int zi_din_an::obtine_zi()
    {
     return zi;
    }
    
   int zi_din_an::obtine_luna()
    {
     return luna;
    }

  Cand se defineste o clasa, de obicei toti membrii variabila sunt privati. Asta inseamna ca 
  membrii variabila pot doar sa fie accesati sau modificati prin functii membru.
  Sintaxa generala este:
  
   class nume_clasa
    {
     public:
      declarare membru_1;
      declarare membru_2;
      ...
      declarare membru_n;
     private:
      declarare membru_n+1;
      ...
    };
    
  ATENTIE ! Nu uitati la sfarsitul declararii unei clase semnul ;   Functiile membru care permit
 accesarea variabilelor membru private se numesc functii accessor (de acces). In exemplul 
 precedent functiile obtine_zi si obtine_luna sunt functii accessor. Este legala folosirea 
 intructiunii de atribuire pentru obiecte. De exemplu in programul precedent daca scriem:
    zi_nastere = azi;
  aceasta este echivalenta cu 
    zi_nastere.zi = azi.zi;
    zi_nastere.luna = azi.luna;
  Mai mult, atribuirea este legala chiar daca varibilele membru sunt membri privati.


-----------------------------------------
 Comparatie intre class, struct si union
-----------------------------------------

  Structurile sunt folosite de obicei cu toate variabilele membru publice si fara functii mebru.
  Cu toate acestea, o structura C++ poate avea variabile membru private si functii membru 
  private si publice. Totusi se recomanda, folosirea structurilor in varianta clasica.
  Un union este similar cu un struct, cu exceptia faptului ca permite definirea de varibile 
  care impar spatiul de memorie.

Exemplu:
    union int_sau_long
     {
      int i;
      long l;
     } un_numar;
  Compilatorul va aloca memorie suficienta pentru memorarea variabilei un_numar pentru a incape 
  in cel mai mare element din union.
  Elementele unui union sunt accesata in aceeasi maniera ca la struct.
  

--------------
 Constructori
--------------

  Deseori, dorim sa initializam toate variabilele membru pentru un obiect cand il declaram. 
Constructorul este o functie membru care este automat apelata cand un obiect al acestei clase
 este declarat. Un constructor este folosit pentru initializarea valorilor unor sau tuturor 
variabilelor membru precum si pentru a face alte tipuri de initializari pe care le dorim.
 Exista doua exceptii fata de functiile membru obisnuite:
 1. Un constructor trebuie sa aiba acelasi nume ca al clasei.
 2. Definitia constructorului nu poate intoarce o valoare. Mai mult, nu se precizeaza tipul 
    intors, nici macar void.

 Exemplu: continuam exemplul precedent
    class zi_din_an
     {
      public:
       void citire();
       void afisare();
       void set(int noua_zi, int noua_luna);
       int obtine_zi();
       int obtine_luna();
      
       zi_din_an(int noua_zi, int noua_luna);
      
      private:
       int zi;
       int luna;
     };

  Putem intelege obiectele azi, respectiv zi_de_nastere astfel:
    zi_din_an azi(22, 3), zi_nastere(16, 4);
  Este eronat sa facem astfel (ca la functii membru obisnuite):
    zi_din_an azi, zi_nastere;
    azi.zi_din_an(22, 3);
    zi_nastere.zi_din_an(16, 4);
    
Un constructor nu poate fi apelat la fel ca functiile membru obisnuite. Definitia unui 
constructor se da in acelasi mod ca orice alta functie membru.

 Exemplu:
    zi_din_an::zi_din_an(int noua_zi, int noua_luna)
     { 
      zi=noua_zi;
      luna=noua_luna;
     }
     
Observam ca definitia constructorului este similara cu cea a functiei membru set (din programul
precedent). Diferenta este ca nu intoarce nimic (nici macar void).In general, constructorii sunt
supraincarcati, deci obiectele se pot initializa in mai multe moduri.Un constructor se poate 
apela de mai multe ori pentru un obiect. Sintaxa apelului explicit a unui constructor este 
diferita de sintaxa folosita pentru apelul altor functii membru. Un constructor se apeleaza cu 
operatorul de asignare = in loc de operatorul punct.

Exemplu: Am vazut ca putem apela constructorul in momentul declararii, astfel:
    zi_din_an azi(22,3);
   Putem acum reapela contructorul, astfel:
    azi = zi_din_an(22,3);
  De fapt un constructor este o functie ce returneaza un obiect de tipul clasei respective. 
ATENTIE ! Constructorul este privit ca o functie membru care initializeaza un obiect si poate fi
apelat folosind o sintaxa neobisnuita.


-----------------------------
 Constructori fara argumente
-----------------------------

  Daca in definitia unei clase, exista macar o definitie de constructor, atunci C++ va alege spre
  folosinta definitia potrivita.
   Exemplu:
    class C1
     {
      public:
       C1(int a, double b);
       void ceva();
      private:
       int a;
       double b;
     };
Putem apela constructorul astfel:
    C1 obiect(7,2.5);
 O declaratie de genul
    C1 obiect1;
este gresita deoarece exista in definitia clasei doar un singur constructor cu doua argumente.
Declaratia de mai sus ar fi corecta daca ori declaram obiect1 de doua argumente, ori mai 
declaram in clasa C1 un constructor fara argumente.Un constructor fara argumente se numeste 
constructor implicit, deoarece se aplica in cazul implicit cand declaram un obiect fara 
specificarea vreunui argument.

Exemplu: Redefinim clasa C1 astfel:
    class C1
     {
      public:
       C1(int a, double b);
       C1();
       void ceva();
      private:
       int a;
       double b;
     };
Acum declaratia C1 obiect1; este corecta.
ATENTIE ! Declaratia C1 obiect1; poate cauza probleme. Compilatorul ar putea crede ca este vorba
de un prototip pentru o functie numita obiect1 fara argument si care intoarce o valoare de tip 
C1.Daca am declarat un obiect al unei clase folosind constructorul implicit, atunci il putem 
initializa apoi folsind alt constructor.

Exemplu: pentru clasa de mai sus putem avea:
    C1 obiect;
    obiect = C1(4,6.7);
  In schimb nu putem avea declaratiile (motivul este explicat mai sus):
    C1 obiect();
    obiect = C1(4,6.7);

 Tipuri de date abstracte

  Un tip de data este o multime de valori impreuna cu o multime de operatii de baza definite 
  pentru aceste valori.

Exemplu: 
    int este un tip de data ce foloseste valorile
     {-32768,...,-1,0,1,...,32767} si operatiile +,-,*,/,% (si altele)
  Un tip de data se numeste tip de data abstract (ADT-Abstract Data Type) daca programatorii ce 
  folosesc acel tip nu au acces la detaliile implementarii valorilor si operatiilor.

Exemplu: tipurile predefinite (cum ar fi int) sunt tipuri de date abstracte.
Tipurile definite de programator, cum ar fi struct si class nu sunt implicit tipuri abstracte de
date.
  Daca nu sunt definite si folosite cu atentie, tipurile definite de programator pot fi folosite
  in mod neintuitiv pentru a face un program greu de inteles si de modificat. Modul cel mai bun 
  de a evita aceste probleme este sa fim siguri ca toate tipurile de date declarate de 
  programator sunt tipuri abstracte de date.
  Pentru a defini o clasa care sa fie un tip abstract de date, trebuie sa separam specificarile
  modului de folosire a tipului de catre programator de detaliile modului de implementare a 
  tipului.
  Separarea trebuie sa fie completa astfel incat la o schimbare in implementarea clasei, 
  orice program care foloseste clasa abstracta de date sa nu necesite schimbari suplimentare. 
  Un mod de a asigura aceasta separare este:
 1.Facem toate variabilele membru ale clasei membri private.
 2.Facem toate operatiile de baza de care programatorul are nevoie functii membru public ale 
   clasei (specificand modul de folosire a fiecarei functii membru specifice).
 3.Facem orice functie help a clasei membru private.
 
  Interfata unui ADT (definit ca o clasa in C++) consta in functiile membru publice impreuna cu
  comentariile care specifica modul de folosire a acestor functii membru publice. Implementarea 
  unui ADT consta in membrii privati ai clasei si definitiile functiilor membre publice si 
  private.
  Deci clasa poate fi impartita in interfata si implementare.


-------------------------------------
 Exercitii propuse spre implementare
-------------------------------------

1.Scrieti un program C++ in care definiti o clasa contbancar ce contine marea majoritate a 
  operatiilor bancare.
  De exemplu, o schita pentru definitia acestei clase poate fi:
    class contbancar
     {
      public:
       void seteaza(long lei, double dobanda);
       //Postconditie: Soldul din contul bancar este lei
       void actualizeaza();
       //Postconditie: actualizeaza soldul dupa un an
       double obtine_soldul()
       //Returneaza soldul curent
       double obtine_dobanda();
       //Returneaza dobanda din contul bancar
       void afisare(ostream& iesire);
       //Preconditie: iesire este un flux fisier de iesire sau cout
       //Postconditie: Soldul si dobanda sunt afisate catre fluxul de iesire
      private:
       double sold;
       double dobanda;
     };
2.Scrieti un program C++ in care se definiti o clasa Arbore ce contine operatiile fundamentale
asupra arborilor binari (creare, parcurgere, stergere, numarare). 
ATENTIE ! Radacina si numarul de noduri se recomanda a fi declarate private.