<<Pagina Materiale

 
 
Paradigme de programare. Clase 


Despre paradigme de programare

Inainte de a examina facilitatile de programare OO oferite de C++, o scurta prezentare a paradigmelor de programare. 

O paradigma permite specificarea unui model de rezolvarea a unei probleme. Un model este o reprezentare simplificata sau o abstractizare a unui sistem.

Uneori limbajul de programare in care va fi implementata solutia, ofera aceleasi constructii si mecanisme ca si paradigma; in acest caz implementarea, verificarea solutiei se face mult mai simplu. Se poate considera atunci ca o paradigma de programare este o clasa de limbaje.

De multe ori, un limbaj de programare poate apartine mai multor paradigme; astfel C++ include caracteristici ale paradigmelor imperativa si procedurala, ca predecesorul C, dar si ale celei de orientare pe obiecte.

Paradigma Imperativa

Se caracterizeaza printr-un model abstract al unui calculator cu o memorie foarte mare, modelul clasic von Neumann. Solutiile se gasesc prin prelucrari care cuprind asignari, citiri, operatii aritmetice si logice, selectii (  if-else ) pentru controlul fluxului executiei. Din aceasta clasa de limbaje fac parte cele de asamblare sau BASIC.

Paradigma Procedurala

Extinde cea anterioara cu un mecanism de abstractizare, cel al procedurilor cu parametri. Alte facilitati sunt iteratia, recursivitatea, selectia. Din aceasta clasa fac parte limbajele FORTRAN, Pascal, C.

In aceasta paradigma, un sistem se modeleaza ca un set de algoritmi ( proceduri ) si unul de date. Desi mecanismul de abstractizare oferit de aceasta paradigma, procedurile, reprezinta un mecanism puternic, dezavantajul este cel al slabei legaturi dintre procedura si datele manipulate.

Paradigma Procedurala cu Tipuri de Date Abstracte

Clasa de limbaje include CLU, Ada, Modula-2; un TDA este un model matematic pe care se defineste un set de operatori; implementarea si reprezentarea datelor pot fi realizate in variante diferite, cu performante diferite. 

De exemplu o stiva are operatorii Push, Pop, IsEmpty, si se poate implementa ca tablou, lista simplu sau dublu inlantuita.

Paradigma Orientare pe Obiecte

Notiunea de baza este cea de obiect, o entitate logica, compus din date si operatorii asupra acestora. 

Limbaje din aceasta clasa sunt Smalltalk, C++, Objective-C, CLOS (the Common Lisp Object System ).

"Daca procedurile si functiile sunt verbe, iar blocurile de date sunt substantive, un program procedural este organizat in jurul verbelor, in timp ce un program orientat pe obiecte este organizat in jurul substantivelor" - G.Booch: "Object-Oriented Analysis with Applications", Addison-Wesley, 1994.

Definitia data de Booch Programarii Orientate pe Obiecte: "POO este metoda de implementare in care programele sunt organizate ca si colectii de obiecte ce coopereaza intre ele, fiecare obiect reprezentand instanta unei clase; fiecare clasa apartine unei ierarhii de clase, clasele fiind unite prin relatii de mostenire".

Cele trei caracteristici ale paradigmei OO sunt: 

  • Incapsularea - este mecanismul care leaga datele de operatorii asupra lor; este deci mecanismul care permite crearea unui obiect; un obiect este o instantiere ( o variabila ) a unui tip, clasa, creat de utilizator;
  • Mostenirea - este mecanismul care permite crearea de noi obiecte, din cele existente; noile obiecte preiau, mostenesc, metode si date de la obiectul parinte, putand sa modifice altele; acest mecanism face posibil ca un obiect sa fie un exemplar specific al unui caz mai general;
  • Polimorfismul - este mecanismul caracterizat de expresia " o interfata, metode multiple "; permite unei interfete sa fie folosita cu o clasa generala de actiuni, deci obiecte de tipuri diferite sa execute aceeasi operatie.
Caracteristicile Paradigmei OO
Scrierea unui program OO implica determinarea obiectelor necesare; acestea vor realiza prelucrarile care definesc comportarea sistemului. Obiectele sunt responsabile pentru modificarea datelor proprii, sunt deci self-contained.

Pentru Paradigma OO ( programarea OO ) - POO au fost dezvoltate metodologii diferite de proiectare - Coad - Yourdon, Booch  Rumbaugh.

In esenta, proiectarea OO cuprinde urmatoarele activitati:

  • Identificarea entitatilor, deci a obiectelor ce apar in domeniul aplicatiei, prin evidentierea substantivelor din enuntul problemei
  • Pentru fiecare obiect se identifica datele si operatiile, prin evidentierea adjectivelor si verbelor ce caracterizeaza substantivul respectiv
  • Identificarea relatiilor dintre entitati
  • Crearea unei ierarhii de clase in C++ plecand de la aceste entitati
  • Implementarea claselor si a sistemului
  • Testarea si punerea la punct.
In general aceste activitati nu sunt facute secvential, ci prin iteratii succesive. 
Sus
Clase

In C++ clasa este un concept fundamental, este constructia prin care se definesc noi tipuri de date, prin asocierea unui set de functii la o structura de date.

Definitia unei clase presupune declararea clasei, la care se specifica:

  • numele clasei
  • lista claselor de baza din care e derivata clasa, daca exista
  • membrii clasei, atât membrii de date cât si functii.
Este posibil controlul accesului atât la datele membre cât si la functiile membre ale unei clase. In acest scop, se pot utiliza specificatorii de control ai accesului: public, private si protected; private este specificatorul implicit. Membrii privati (date si functii ) sunt accesibili numai functiilor membre si prietene ( friend ) ale clasei. Un membru public poate fi accesat de orice functie din domeniul de declaratie al clasei. Accesul la membrii protejati este similar celui la membrii privati, dar accesul se poate extinde la functiile membre si prietene ale claselor derivate din clasa respectiva. 

De exemplu, în cazul clasei Stiva, sunt ascunse utilizatorului toate detaliile de implementare ( membrii de date ):
 

class Stiva{
protected:
       int nmax;  // numarul maxim de elemente
      int *tab; // tabloul in care se vor memora elementele 
      int varf; // indexul elementului din varf

public:
        Stiva(int);  // constructor, se precizeaza dim stivei
        Stiva();     // constructor implicit
        ~Stiva();    // destructor
          BOOL Push(int);  // depunere element
        BOOL Pop(int &); // extragere element
        BOOL Top(int &); // returnarea elementului din varf 
        BOOL not_vida();
        BOOL not_plina();
};

Definirea functiilor membre se poate face fie ca functii inline, la declararea lor în cadrul clasei ( pentru functiile de mici dimensiuni ), fie în exteriorul clasei. Pentru definitiile functiilor membre aflate în afara declaratiei clasei, este necesara specificarea numelui clasei urmat de operatorul de rezolutie înaintea numelui functiei. De exemplu, definirea operatiei de adaugare în stiva se face în felul urmator:
 

BOOL Stiva::Push(int ElementNou){ 
  if (not_plina()){ //apelul metodei din aceeasi clasa 
                tab[++varf]=ElementNou;
                return TRUE;
                }
  else return FALSE;
}

Functiile membru pot avea acces la orice membru al clasei respective. Accesul la membrii de date ai instantei curente se face direct, prin numele lor.

Daca o functie nu modifica membrii date, ea se declara const:
    [inline] [static] tip_rezultat nume_functie ( lista_parametri ) [const]

Pentru apelul functiilor membre publice sau pentru accesul la datele publice ale unui obiect, din functii care nu sunt membre, se folosesc operatorii de selectie (.) si (->), ca în cazul structurilor si uniunilor din C.

Exemplu:
 

Stiva s1, *ps; //instantierea clasei Stiva
   //declararea unui obiect si a unui pointer
           ...
        ps=new Stiva(20);
           ... 
        s1.push(5);
        ps->push(8);

Observatie:

Cuvintele cheie struct si union desemneaza clase, ca si class; singura deosebire fata de class, este ca specificatorul implicit este public.

Autoreferinta. Cuvântul cheie this

Pentru a defini functiile membre sunt necesare referiri la datele membre ale clasei fara a specifica un obiect anume. La apelare, functia este informata asupra identitatii obiectului asupra caruia va actiona prin transferul unui parametru implicit care reprezinta adresa obiectului. De exemplu, în cazul apelului: s1.Push(3), functia Push() primeste si adresa stivei s1 în afara de valoarea 3.

Exista situatii în care este necesar ca adresa obiectului sa fie utilizata în definitia functiei. Ne putem referi la acest pointer prin cuvântul cheie this, declarat implicit în orice functie si initializat sa indice înspre obiectul pentru care este invocata functia membra. Principala utilizare a lui this este la scrierea functiilor care manipuleza direct pointeri.

Membrii statici ai unei clase

Fiecare obiect de tip clasa are propriile lui copii ale tuturor membrilor acelei clase. Este posibila definirea de membrii care sa fie folositi în comun de catre toate obiectele clasei.
Datele statice exista într-o singura copie, comuna tuturor obiectelor. Crearea, initializarea si accesul la aceste date statice sunt total independente de obiectele clasei.
Functiile membre statice efectueaza operatii care nu sunt asociate obiectelor individuale, ci întregii clase. Din acest motiv, la apelarea lor nu este obligatorie indicarea unui obiect. Un membru static poate fi referit si indicând numele clasei si folosind operatorul de rezolutie de domeniu.

Functii si clase friend

Functiile prietene ( friend ) pot folosi membrii privati ai unei clase, desi ele însele nu sunt membri. Functiile prietene au fost introduse pentru cazurile în care o functie coopereaza strâns cu o clasa, dar nu face parte din acea clasa. O functie prietena este declarata asemanator cu o functie membra, având prototipul în interiorul clasei, precedat de cuvântul cheie friend. Exista si posibilitatea ca una sau mai multe functii membre ale unei clase sa fie functii prietene ale altei clase. Se poate declara si o clasa Y prietena a unei clase X, în acest caz toate functiile clasei Y sunt prietene ale clasei X si au acces nelimitat la membrii privati ai clasei X.

Exemplu:

In clasa Stiva definita anterior, varful este o variabila protejata, care poate fi accesata numai din cadrul functiilor membre ale clasei Stiva. Daca se defineste clasa Test, prietena a clasei Stiva, atunci orice functie membra a clasei Test are acces direct nelimitat la campul varf a unei stive:
 

  class Stiva {
   .....
   friend class Test;
   };

  class Test {
   .....
   void o_functie();
   };

  void Test::o_functie() {
    Stiva s;
    ....
    s.varf=5; //devine o operatie permisa, pentru ca Test e clasa friend
    ....
    }

Constructori si destructori

In cazul variabilelor obisnuite, compilatorul asigura alocarea spatiului de memorie si eventual initializarea explicita cu valori initiale în declaratie. Pentru variabilele dinamice, compilatorul C nu dispune de nici o metoda de initializare si nici operatorul new nu rezolva toate situatiile. In acest caz, ramane în grija programatorului atribuirea de valori adecvate datelor înainte de utilizare. Aceasta abordare este nesatisfacatoare în multe situatii în cazul obiectelor.

Pentru crearea, initializarea, copierea si respectiv distrugerea obiectelor, în C++ se folosesc functii speciale, numite constructori si destructori. Constructorul se apeleaza automat la crearea fiecarui obiect al clasei, static, automatic sau dinamic( cu operatorul new ), inclusiv pentru obiecte temporare.

Destructorul este apelat automat la eliminarea unui obiect, la încheierea timpului sau de viata, sau poate fi solicitat prin program, cu operatorul delete.

Constructorii si destructorii se declara si se definesc similar cu celelalte functii membre, dar se disting de acestea printr-o serie de caracteristici specifice:

  • numele functiilor constructor sau destructor coincide cu numele clasei careia îi apartin; destructorii au numele precedat de caracterul ~ ( tilda ).
  • în declaratie si definitie nu se specifica nici un tip de rezultat, nici macar void
  • constructorii pot avea parametrii, inclusiv parametrii impliciti, si pot fi supradefiniti; destructorii nu au aceste proprietati
  • nu se pot utiliza pointeri catre constructori sau destructori
  • daca o clasa nu dispune de constructori si destructori definiti, compilatorul va genera automat un constructor implicit, respectiv un destructor, functii publice
Alta categorie de constructor este constructorul de copiere. Necesitatea de copiere a obiectelor intervine când un obiect este transferat ca parametru sau rezultat al unei functii, sau la crearea unui obiect temporar. Solutia oferita de C++ consta în utilizarea unui constructor special, numit constructor de copiere. In absenta unei definitii explicite în cadrul clasei, compilatorul genereaza automat un constructor de copiere care initializeaza datele noului obiect cu valorile corespunzatoare din obiectul specificat, prin copiere membru cu membru. Aceasta nu este o solutie buna în cazul în care clasa contine membrii variabile dinamice, caz în care este necesara scrierea unui constructor de copiere special.

Declaratia constructorului de copiere pentru o clasa trebuie sa specifice un parametru unic de tipul referinta de obiecte a acelei clase.

De exemplu, definitia unui constructor de copiere adecvat pentru clasa Stiva ar fi:
 

Stiva::Stiva(Stiva &s) { //constructor de copiere - in obiectul 
                         //curent se copiaza obiectul parametru
   nmax=s.nmax;
   tab=new int[nmax];
   varf=s.varf;
   for (int i=0; i<=varf; i++) tab[i]=s.tab[i];
}
Sus
Exercitii

Sa se implementeze o clasa Stiva de numere intregi, care foloseste ca reprezentare interna o lista simplu inlantuita pentru pastrarea datelor.

Sus
<<Pagina Materiale


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