<<Pagina Materiale

 
 
Mostenire. Template 


Conceptul de mostenire. Clase derivate

Declararea claselor derivate. Controlul accesului

Procedeul de derivare permite definirea unei clase noi, numita clasa derivata, care mosteneste proprietatile unei clase deja definite, numita clasa de baza, pe care le completeaza cu proprietati noi. 

Clasa derivata contine membrii clasei de baza, la care se adauga membri suplimentari, specifici (date si functii). Dintr-o clasa de baza pot fi derivate mai multe clase si fiecare clasa derivata poate servi ca baza pentru alte clase derivate. Se poate realiza o ierarhie de clase, care sa modeleze în mod adecvat sisteme complexe. Daca o clasa mosteneste proprietatile unei singure clase de baza, este vorba de mostenire simpla; este posibil ca o clasa sa mosteneasca proprietatile mai multor clase de baza - mostenire multipla.

Relatia de mostenire:

  • este un mecanism de abstractizare - fiecare clasa derivata este o forma diferita a abstractizarii reprezentate de clasa de baza; obiectele claselor derivate pot fi folosite oriunde este asteptat un obiect al clasei de baza;
  • permite reutilizarea codului - definit in clasa de baza.
Definirea unei clase derivate:

class nume_clasa_derivata : specificator_acces nume_clasa_baza {
   //corpul clasei
};

Exemplu:

Un sistem simplu pentru evidenta populatiei memoreaza date despre diverse categorii de persoane. Fiecare persoana este caracterizata prin nume, adresa, si eventual alte informatii, specifice categoriei din care face parte. Se presupun doua categorii: Student si Angajat. Fiecare student este caracterizat de anul universitar în care este, fiecare angajat de denumirea institutiei unde lucreaza. Se poate defini urmatoarea ierarhie de clase:
 

class Persoana {
protected:
        char *nume;
        char *adresa;
public:
        Persoana(char *N, char *A);
        ~Persoana();
        char * Nume();
        char *AdresaActuala();
        void SchimbaAdresa(char *A);
        void AfiseazaDate();
};

class Student: public Persoana {  //derivare publica
protected:
        int an;
public:
        Student(char *N, char *A, int a);
        ~Student();
        int An();
        void ActualizeazaAn(int a);
};

class Angajat: public Persoana {  //derivare publica
protected :
        char * loc_munca;
public:
        Angajat(char *N, char *A, char *LM);
        ~Angajat();
        char *LocMunca();
        void SchimbaLocMunca(char *LM);
};

Pentru a putea controla drepturile de acces ale clasei derivate asupra membrilor clasei de baza, declaratia poate contine specificatori de control al accesului. Derivarea poate fi de tip public, protected sau private. Daca nu se specifica nici unul, se considera implicit private pentru o clasa class, respectiv public, pentru una struct.

Specificatorul de acces coduce la urmatoarele reguli de accesare:

  • Membrii privati ai clasei de baza nu pot fi accesati din cea derivata;
  • Membrii protejati pot fi accesati din cadrul clasei derivate numai daca mostenirea este publica (cazul din exemplul anterior);
  • Accesul din exterior asupra membrilor clasei derivate care sunt mosteniti din membri publici ai clasei de baza ramâne posibil numai daca mostenirea a fost publica;
  • Daca specificatorul de mostenire este protected, toti membrii publici si protejati ai clasei de baza, devin membri protejati ai clasei derivate.

Constructori si destructori pentru clasa derivata

Regulile de functionare ale constructorilor si destructorilor ramân valabile si în cazul claselor derivate, tinând seama însa de aspectul particular al mostenirii datelor clasei de baza: pentru construirea unui obiect al clasei derivate, se începe prin crearea unui obiect al clasei de baza si se apeleaza constructorul clasei de baza. Apoi se creaza elementele adaugate de clasa derivata si se apeleaza constructorul clasei derivate. La eliminarea unui obiect al clasei derivate, este apelat întâi destructorul clasei derivate, apoi cel al clasei de baza.

Un exemplu de definire a constructorului clasei Student, derivata din clasa Persoana:
 

Student:: Student(char *N, char *A, int a) : Persoana(N,A) {
    an=a;
}

Redefinirea functiilor membru

Clasele Student si Angajat au acces la functia de afisare a clasei Persoana. Totusi, aceasta afiseaza numai datele comune tuturor persoanelor, fara sa poata afisa, daca persoana este student, anul în care este, sau daca este angajat, unde lucreaza. Pentru a putea face afisarea diferentiata, se poate redefini functia AfiseazaDate ca functie membra în clasele Student si Angajat. Compilatorul C++ poate distinge functiile membre pe baza tipului obiectului apelant.

Definitiile noi din clasa derivata nu înlocuiesc definitiile din clasa de baza, ci se adauga lor. Clasa derivata are la dispozitie si definitiile din clasa de baza, pe care le poate specifica utilizând operatorul de rezolutie ::.

Compatibilitatea între o clasa derivata si clasa de baza

Deoarece orice clasa derivata mosteneste proprietatile clasei de baza, se poate vorbi de un grad de compatibilitate între tipul clasei derivate si tipul clasei de baza. (In exemplul anterior, orice student este o persoana, dar nu orice persoana este un student).

Compatibilitatea se manifesta sub forma unor conversii explicite de tip, dintr-un obiect derivat într-un obiect de baza, sau dintr-un pointer sau referinta la clasa derivata într-un pointer la clasa de baza.

Exemple de conversii permise:
 

  Persoana p1, *pp1, *pp2;
  Student s1;
  p1=s1; //se copiaza doar campurile mostenite
  pp1=new Student("Ion Ionescu","str. Mare", 3);
  pp2=new Angajat("Vasile","str. XXX", "ABC S.A");
  pp1->AfiseazaDate();

Functii virtuale

Decizia asupra versiunii functiei care se apeleaza este luata în momentul compilarii, numita legare statica (early binding). Pe de alta parte, tipul obiectului adresat de un pointer este determinat de declaratie si fixat tot în momentul compilarii. S-a aratat în paragraful precedent ca este posibil ca un pointer al unui tip clasa de baza sa contina adresa unui obiect apartinând unei clase derivate. La utilizarea variabilei pointer , în cazul legarii statice, nu se tine seama de tipul obiectului adresat, si functia membra selectata este cea definita în clasa de baza. 

De exemplu, în secventa anterioara, apelul

    pp1->AfiseazaDate();

determina apelarea functiei membra a clasei Persoana, desi obiectul în cauza este un angajat si functia a fost redefinita la nivelul clasei Angajat. Aceasta reduce utilitatea redefinirii functiilor membre care s-a facut.

Pentru a se putea urmari tipul obiectului adresat de pointer la un moment oarecare, este necesara identificarea versiunii functiei care trebuie apelata în timpul executiei programului. Acest mod de lucru se numeste legare dinamica (late binding). Functiile membre pentru care se realizeaza legatura dinamica se numesc functii virtuale si se declara cu ajutorul cuvântului cheie virtual.

De exemplu, declaratia functiei AfiseazaDate, se va modifica astfel în cadrul clasei Persoana:

    virtual void AfiseazaDate();

A se retine ca destructorii pot fi declarati virtuali, constructorii, nu.

Principala forta a relatiei de mostenire este data de facilitatea de a trata obiectele claselor cuprinse intr-o ierarhie ca si cum ar fi instante ale clasei din varful ierarhiei. Aceasta se numeste polimorfism - obiecte cu caracteristici comune pot fi tratate unitar.

Cand o functie virtuala nu este redefinita de o clasa derivata, va fi folosita versiunea definita in clasa de baza. C++ admite functii virtuale pure pentru urmatoarele situatii:

  • o clasa de baza poate sa nu contina suficiente informatii pentru definirea unei functii virtuale
  • este necesara asigurarea ca se va face redefinirea functiei virtuale in clasele derivate.
O functie virtuala pura este cea care nu are definitie in clasa de baza, trebuind sa fie redefinita in toate clasele derivate - altfel apare eroare in timpul compilarii; are forma generala:

  virtual tip nume_functie(lista_parametri)=0;

Clasele care contin astfel de functii se numesc clase abstracte. Nu pot fi instantiate, dar se pot declara pointeri sau referinte la acestea.. Se pot crea obiecte numai din clase derivate din acestea, cu conditia ca sa se furnizeze corpuri pentru functiile virtuale pure.

Exemplu - vezi si Exercitiul 3:

Clasa de baza pentru obiecte grafice, poate fi definita:

 class BaseGraphicalObject {
 .....
    public:
      virtual void draw() = 0;
      .....
  }

Toate clasele derivate trebuie sa-si implementeze propria functie draw.
 

Sus
Tipuri parametrizate - template

Clase generice

S-a prezentat anterior un exemplu de implementare a clasei Stiva, ca fiind o stiva de intregi. În mod tipic, programatorii vor avea nevoie de stive de elemente apartinând unor tipuri diferite. S-ar putea sa apara necesitatea definirii unor tipuri ca: StivaDeIntregi, StivaDeCaractere, StivaDeNrComplexe, StivaDeFiguriGeometr, etc.. Toate acestea ar implica scrierea câte unei clase stiva pentru fiecare tip de elemente continute. Este probabil ca cel care scrie tipul Stiva sa nu cunoasca toate tipurile de elemente pe care ulterior, alti programatori vor dori sa le introduca în stive. Oricum, scrierea unui numar mare de asemenea stive (o familie de stive) ar fi total ineficienta în ceea ce priveste reutilizarea codului.

Solutia la aceasta problema ar fi posibilitatea ca tipul Stiva sa fie exprimat astfel încât sa primeasca drept parametru tipul elementelor.

În C++, exista posibilitatea de a crea familii de functii sau de clase cu ajutorul sabloanelor (template).

Sabloanele nu au facut parte dintre specificatiile originale ale limbajului C++, ci au fost adaugate in 1990, fiind definite de ANSI C++.

Se poate reconsidera acum exemplul stivei.
 

// Fisierul  Stiva_T.hpp

#define BOOL int
#define TRUE 1
#define FALSE 0

template <class TipElement> class Stiva {
  protected:
        int nmax;           // numarul maxim de elemente
        TipElement *tab;    // tabloul in care se vor memora elementele
        int varf;           // indexul elementului din varf
  public:
        Stiva(int n=100);
        ~Stiva();
        BOOL Push(TipElement);
        BOOL Pop(TipElement &);
        BOOL Top(TipElement &);
        BOOL not_vida();
        BOOL not_plina();
};

template <class TipElement> 
Stiva<TipElement>::Stiva(int n) {
        nmax=n;
        tab=new TipElement[nmax];
        varf=-1;
        }

template <class TipElement> 
Stiva<TipElement>::~Stiva() {
       delete tab;
}

template <class TipElement>
BOOL Stiva<TipElement>::Push(TipElement ElementNou){
        if (not_plina()) {
                tab[++varf]=ElementNou;
                return TRUE;
                }
        else return FALSE;
}

template <class TipElement>
BOOL Stiva<TipElement>::Pop(TipElement &ElementVarf) {
        if (not_vida()) {
                ElementVarf=tab[varf--];
                return TRUE;
                }
        else return FALSE;
}

template <class TipElement>
BOOL Stiva<TipElement>::Top(TipElement &ElementVarf) {
        if (not_vida()) {
                ElementVarf=tab[varf];
                return TRUE;
                }
        else return FALSE;
}

template <class TipElement>
BOOL Stiva<TipElement>::not_plina(){
       return varf<nmax-1;
}

template <class TipElement>
BOOL Stiva<TipElement>::not_vida() {
       return varf>=0;
}

Un exemplu de program care utilizeaza trei stive, o stiva de numere întregi, una de caractere si una de numere complexe este prezentat în continuare.
 

// Fisierul ProgStT.cpp

#include "Stiva_T.hpp"
#include <iostream.h>

typedef   struct c { int x,y;} complex;
// crearea unor noi tipuri
// pentru instantierea sabloanelor
typedef   Stiva<int> StivaInt; 
typedef   Stiva<char> StivaChar; 
typedef   Stiva<complex> StivaComplex;

void main(void){
  StivaInt s1(10);   // instantierea obiectelor
  StivaChar s2;
  StivaComplex s3;
  int e1;
  char e2;
  complex e3={1,3};
  s1.Push(5); s1.Pop(e1);
  s2.Push('a');  s2.Pop(e2);
  e3.x=1; e3.y=2;
  s3.Push(e3); s2.Pop(e3);
  cout<<"e1="<<e1<<"e2="<<e2<<"e3="<<e3.x<<" "<<e3.y<<"\n";
}

Definitia unui sablon de clasa nu este nici clasa, nici obiect. Sablonul unei clase este o descriere a modului în care compilatorul va genera o noua clasa, pornind de la tipurile date ca parametru. În C++, o clasa este inutila daca nu se declara o instanta (obiect) a clasei respective. La fel, un sablon este inutil daca nu se declara o instanta (clasa) a sablonului respectiv.

Un alt aspect legat de folosirea sabloanelor este legat de faptul ca ele pot fi folosite numai cu tipuri care accepta operatiile necesare sablonului. În exemplul cu stiva, este necesar ca tipul elementelor stivei sa accepte operatia de atribuire.

Deci o clasa generica se defineste:

template <class Tip> class nume_clasa {
    //definitie clasa
}

Se observa faptul ca în fisierul antet Stiva_T.hpp este inclusa atât declaratia cât si definitia sablonului Stiva, pentru a permite compilatorului sa genereze clasele necesare (instantele sablonului) fiecarui modul utilizator care foloseste clasa generica Stiva.

Functii generice

Pe langa sabloane de clasa - clase generice  - pot fi definite si sabloane de functii - functii generice. O functie generica defineste un set general de operatii care vor fi aplicate unor tipuri de date variate; tipul de date asupra caruia va opera, va fi transmis ca parametru:

template <class Tip> tip_rezultat nume_functie (lista_parametri) {
    //corp functie
}

Exemplu:

Programul de mai jos defineste o functie generica ce inverseaza intre ele valorile a doua variabile trimise ca parametri prin referinta:
 
#include <iostream.h>
//definitia functiei generice ( sablon )
template <class T> void interschimba(T &x,T &y){
  T aux;
  aux=x; x=y; y=aux;
}

main(){
  //se vor interschimba doi intregi, doua caractere, doi reali
  int i1=5,i2=-9;
  char c1='v',c2='k';
  float f1=1.2,f2=-7.4;

  cout<<intregii:<<i1<<' '<<i2<<endl;
  interschimba(i1,i2);
  cout<<interschimbati:<<i1<<' '<<i2<<endl;

  cout<<caracterele:<<i1<<' '<<i2<<endl;
  interschimba(i1,i2);
  cout<<interschimbate:<<i1<<' '<<i2<<endl;

  cout<<realii:<<i1<<' '<<i2<<endl;
  interschimba(i1,i2);
  cout<<interschimbati:<<i1<<' '<<i2<<endl;

  return 0;
}

Sus
Exercitii

1.Iata doua seturi de intrebari - primul dat la un interviu pentru un post de Entry-Level Software Engineer (C++ Programmer), al doilea la unul pentru Experienced Software Engineer (C++ Programmer).  Sa vedem cum v-ati descurca :-) Cu 8 raspunsuri corecte, postul poate fi ocupat:

Salariile anuale? Intre $30,000-$60,000, respectiv $50,000-$100,000.
   1.What’s the difference between a class and a struct? 
   2.What is a constructor? 
   3.What is meant by a "deep copy"? 
   4.What is the difference between "pass by reference" and "pass by value"? 
   5.What is a static member function? 
   6.What is meant by a derived class? 
   7.How do use command-line arguments? 
   8.What is a pure virtual function? 
   9.cout is an object of what class? 
   9.Explain how you would write a program to read a file and change every occurrence of the word "hello" to "goodby" and then write the file back out.
   1.What’s the difference between a class and a struct? 
   2.What does "return by reference" mean, and when can you return a local variable by reference? 
   3.Where should you initialize a static data member? 
   4.What is meant by protected inheritance? 
   5.What is the string class? 
   6.How would you set up a multiple file application that had five classes? 
   7.What is meant by a generic function ( parameterized manipulator )? 
   8.Explain polymorphism. 
   9.Why should you write a virtual destructor? 
 10.If you create and open a file inside a function, why might the file be closed after the function terminates?

2.Rulati programul de mai jos care defineste o clasa de baza si una derivata, observand apelul constructorilor si valorile campurilor instantelor claselor.

Definiti o clasa derivata privat din clasa Derivata, care sa aiba in plus un camp privat de tip float; cum rezolvati tiparirea campurilor de date din obiectele acestei clase?
 

#include <iostream.h>

class Baza
{
protected:
    int b;
public:
    Baza(int n);
    void print(void) const { cout << "Campul int din Baza este: " << b << endl; }
};

Baza::Baza(int n) 
{
     cout << "Apelat constructor Baza pentru obiect la adresa: " << this << endl;
     b = n;
}

class Derivata : public Baza
{
private:
    int d;
public:
    Derivata(int x,int y);
    void print(void) const;
    void printBaza(void)
     { cout << this << " - Campul int din Baza este: " << b << endl; }
};

Derivata::Derivata(int x, int y) : Baza(x)
{
     cout << "Apelat constructor Derivata pentru obiect la adresa: " << this << endl;
     d = y;
}

void Derivata::print(void) const
{
     cout << "Campul int din Derivata este: " << d << endl;
     Baza::print();
}

int main()
{
     Baza b1(5);
     b1.print();
     cout << endl;

     Derivata d1(3,4);
     d1.print();
     cout << endl;

     d1.printBaza();
     cout << endl;

     cout << "Dim int: " << sizeof(int) << endl;
     cout << "Dim obiect Baza: " << sizeof b1 << endl;
     cout << "Dim obiect Derivata: " << sizeof d1 << endl;
     return 0;
}

3.Studiati si rulati programul de mai jos care defineste o ierarhie de clase: shape - clasa abstracta de baza care are ca membru un obiect de tip location; derivate square, triangle, circle. Observati declararea functiilor virtuale pure in clasa de baza si redefinirea lor in clasele derivate, definirea constructorilor. Decomentati linia din main care instantiaza clasa abstracta si recompilati - ce observati?

Definiti clasa rectangle ( dreptunghi ) derivata din shape si clasa equilateral_triangle derivata din triangle.
 

#include <iostream.h>
#include <iomanip.h>
#include <math.h>
#include <stdlib.h>

const double pi = 3.14159;

class location  // clasa ce defineste coordonatele plane ale centrului unei figuri
{
  private:
      double x, y;
  public:
      location(double x_coord, double y_coord) { x = x_coord; y = y_coord; }
      void print_loc(void);
};

void location::print_loc(void)
{
  cout << '(' << x << ',' << y << ')';
  return;
}

class shape  // clasa abstracta
{
  protected:
      location center;  // instantiere a clasei location
  public:
      shape(double,double);
      void print_center(void);
      virtual double area(void) const = 0;      // functie virtuala pura
      virtual double perim(void) const = 0;     // functie virtuala pura
};

shape::shape(double c_x, double c_y) : center(c_x,c_y) {} 

void shape::print_center(void)
{
  center.print_loc();
  return;
}

class square : public shape  // clasa derivata
{
  private:
      double side;       //latura
  public:
      square(double c_x,double c_y, double s);
      double get_side(void) { return side; }
      double area(void) const;    //declarata const pentru ca nu modifica membrii date
      double perim(void) const;
};

square::square(double c_x, double c_y, double s) : shape(c_x,c_y)
{
  side = s;
}

double square::area(void) const
{
  return side * side;
}

double square::perim(void) const
{
  return 4*side;
}

class triangle : public shape  // clasa derivata
{
  private:
      double a,b,c;     // lungimile celor trei laturi
  public:
      triangle(double c_x,double c_y, double s1, double s2, double s3);
      void print_sides(void);
      double area(void) const;
      double perim(void) const;
};

triangle::triangle(double c_x, double c_y, double s1, double s2, double s3)
: shape(c_x,c_y)
{
  a = s1;
  b = s2;
  c = s3;
}

void triangle::print_sides(void)
{
  cout << a << ' ' << b << ' ' << c;
  return;
}

double triangle::area(void) const
{
  double s = (a + b + c) / 2.;     // semiperimetru
  return sqrt(s*(s-a)*(s-b)*(s-c));    // formula Heron
}

double triangle::perim(void) const
{
  return a+b+c;
}

class circle : public shape  // clasa derivata
{
  private:
      double radius;   // raza cercului
  public:
      circle(double c_x, double c_y, double r);
      double get_radius(void) { return radius; }
      double area(void) const;
      double perim(void) const;
};

circle::circle(double c_x, double c_y, double r) : shape(c_x,c_y)
{
  radius = r;
}

double circle::area(void) const
{
  return pi*radius*radius;
}

double circle::perim(void) const
{
  return 2*pi*radius;
}

int main()
{
  cout << setprecision(2); //numarul de cifre de la partea zecimala

//  shape sh(6,7);     // necomentata, linia da eroare, nu se poate instantia o clasa abstracta
  circle c(3,4,5);
  cout << "cerc - centru: ";
  c.print_center();
  cout << "  raza = " << c.get_radius();
  cout << "  aria = " << c.area();
  cout << "  circumferinta = " << c.perim() << endl;

  square s(5.,2.,1.);
  cout << "patrat - centru: ";
  s.print_center();
  cout << "  latura = " << s.get_side();
  cout << "  aria = " << s.area();
  cout << "  perimetru = " << s.perim() << endl;

  triangle t(0,0,3,4,5);
  cout << "triunghi - centru: ";
  t.print_center();
  cout << "  laturi = ";
  t.print_sides();
  cout << "  aria = " << t.area();
  cout << "  perimetru = " << t.perim() << endl;

  cout << "sizeof(location)=" << sizeof(location) << endl;
  cout << "sizeof(shape)=" << sizeof(shape) << endl;
  cout << "sizeof(square)=" << sizeof(square) << endl;
  cout << "sizeof(triangle)=" << sizeof(triangle) << endl;
  cout << "sizeof(circle)=" << sizeof(circle) << endl;
 

  return 0;
}

4.Scrieti o functie generica ce sa realizeze sortarea bubblesort a unui tablou. Se va scrie un program de test care va sorta un tablou de intregi si unul de reali.

Sus
<<Pagina Materiale



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