Lezione 3 - Ereditarieta' e polimorfismo in C++

Se una classe A ha bisogno di una classe B gia' definita, anche in C++ abbiamo la scelta tra usare o estendere la classe definendo una sottoclasse. Nel primo caso si parla di relazione di aggregazione o composizione (A contiene B) e come al solito in C++ abbiamo una complicazione rispetto a Java: Nel primo caso non abbiamo bisogno di creare l'oggetto, mentre nel secondo caso dobbiamo creare e poi ricordarci di distruggere l'oggetto con gli operatori new e delete. L'operatore di accesso e' differente nei due casi(. o ->). Una classe puo' ereditare piu' di una classe:per questo non esiste un super() come in Java; al posto di super si usa l'operatore :: (scope operator:operatore di visibilita') cioe' B::nomemetodo(). Invece per i costruttori al posto di super() si usa la sintassi NomeSuperclasse().
E' possibile fare una rappresentazione grafica delle classi dove il primo tipo di relazione (A contiene B dichiarata senza puntatori) viene chiamato composizione ed indicato con una freccia a rombo piena orizzontale, il secondo tipo (A contiene *B) aggregazione freccia a rombo vuota orizzontale,il terzo tipo ereditarieta' freccia con punta a triangolo vuoto verso l'alto. Nella composizione possiamo pensare che a tutti gli effetti che B e' una parte di A e viene creato e distrutto durante la vita di A. Nell'aggregazione, B puo' essere preesistente ad A e potrebbe sopravvivere a questo.
Abbiamo semplificato moltissimo le relazioni possibili tra 2 classi:la situazione reale e' molto complessa e puo' essere caratterizzata in questo modo.Per quale ragione la classe A usa B? Le ragioni possono essere diversissime ma possiamo cercare di classificarle e disporle in una gerarchia che va da forte a debole.In pratica UML dispone di 4 livelli rappresentati in modo diverso.
  1. A contiene B in senso forte(composizione) e' rappresentata dalla freccia a rombo pieno diretta da B ad A. A e' il tutto , B una parte. A e' il tavolo, B una gamba del tavolo.
  2. A contiene B in senso debole(aggregazione) e' rappresentata da una freccia a rombo vuota.A e' un comitato, B e' una persona.
  3. A e' collegato a B ma non possiamo dire che A e' il tutto e B una parte. Questa relazione(detta di associazione) e' rappresentata da una freccia da B ad A.Tutti i casi che non sono classificabili come 1,2 o 4 vanno a finire qui.
  4. A e' collegato a B in maniera molto debole.Esempio tipico:siamo costretti a usare B perche' il richiamo di un metodo richiede un oggetto di questo tipo ma B non e' usato da nessun'altra parte.La relazione e' rappresentata da una freccia tratteggiata e viene detta di dipendenza.
Possiamo rivedere i 4 tipi in ordine inverso: dipendenza debole,dipendenza forte,contenimento debole, contenimento forte.
Se il collegamento da B ad A ha il simbolo dalla sola parte di A si intende che da B si puo' navigare fino ad A(che da A si possa andare a B e' sottinteso). Per indicare invece che la navigazione e' unilaterale:A conosce B ma non viceversa, bisogna inserire una freccia anche dalla parte di B.
Infine e' possibile mettere dei numeri alle due estremita' per indicare la molteplicita' delle istanze di A collegate a istanze di B. Ad esempio quanti comitati abbiamo e quante persone possono appartenervi.

Un'altra complicazione di C++ e' che spesso, per questioni di efficienza, molti metodi sono indicati come inline. Anzi questo e' il comportamento di default se si definisce il metodo nell'header. Questo implica che in effetti non c'e' un binding dinamico. Infatti in C++ avviene sempre il solito link statico e come vedremo fra poco, per avere un link dinamico come in Java e di conseguenza il polimorfismo, occorre dichiarare esplicitamente i metodi come virtuali.

Ricordiamo che il polimorfismo nei linguaggi ad oggetti, consiste nel fatto che possiamo avere delle variabili che si riferiscono ad oggetti dei quali non conosciamo il tipo all'atto della compilazione.Quando richiamiamo un metodo di questi oggetti ,l'oggetto dovra' rispondere secondo il tipo effettivo all'atto dell'esecuzione.

Ricordiamo che in Java ogni classe e' una sottoclasse di un'altra classe a partire dalla classe Object e puo' ridefinire qualsiasi metodo della superclasse. Inoltre e' possibile definire delle classi astratte che stanno li solo per realizzare una interfaccia in quanto hanno dei metodi astratti(senza codice) che devono essere necessariamente ridefiniti dalle sottoclassi. All'atto dell'esecuzione ogni chiamata di metodo viene trattata in maniera dinamica ,cercando nella gerarchia delle classi. Questa possibilita' di definire un'interfaccia comune per oggetti di tipo diverso ma parenti, e' la base del polimorfismo.
In C++ non esiste una gerarchia unica e una classe puo' anche non avere genitori. E' inoltre permessa l'ereditarieta' multipla con classi derivate da due o piu' superclassi. Questo elimina la necessita' di dover definire quelle particolari classi astratte chiamate Interface presenti in Java solo perche' una classe non poteva estenderne piu' di una.

La chiamata dinamica (dinamic linking) non e' il solo comportamento possibile anzi il comportamento normale di default e' di creare un normale programma con tutte le chiamate risolte prima dell'esecuzione(infatti esiste anche una fase di link).Per complicare ulteriormente le cose , ogni metodo e' di default inline percio' il suo codice potrebbe essere incluso direttamente nel sorgente senza bisogno di link!
Per questo motivo, se si vuole ottenere il polimorfismo e percio' il link dinamico, occorre dichiarare i metodi corrispondenti esplicitamente come virtual e inoltre, all'atto dell'esecuzione, si deve accedere all'oggetto tramite puntatore!Cioe' solo i metodi richiamati con p->nomeMetodo() possono essere polimorfici.
Un metodo virtuale senza definizione (cioe' astratto) definisce la classe come astratta.
I metodi virtuali si dice che sono richiamati non direttamente ma attraverso una tabella di funzioni virtuali. Quindi il polimorfismo funziona solo con metodi virtuali di oggetti cui si accede con i puntatori.


Programma 1: Realizza in C++ un oggetto BigCounter derivato da un oggetto Counter .S

    file Calcola.cc

class Counter {
 public:
  Counter():value(0){};    
  Counter(int n):value(n){};    
  int getValue() {
   return value; } 
  void increment(){value++;}                  
 private:
  int value;  
 }; 
class BigCounter : public Counter{
 public:
  BigCounter(int n):Counter(n){}
  void increment(){Counter::increment();Counter::increment();}
 };
#include < iostream>     
 main() {  
       
 Counter c1(5); BigCounter c2(5);
                                    
 cout << c1.getValue() << endl;
 cout << c2.getValue() << endl;
 c1.increment();c2.increment();
 cout << c1.getValue() << endl;
 cout << c2.getValue() << endl;
        
}
Riguardo all'ereditarieta' troverete nei documenti che descrivono il C++ talvolta questa regole:
  1. Se la classe base ha un metodo definito normalmente allora questo NON VA ridefinito nella classe derivata.
  2. Se la classe base ha un metodo virtuale definito normalmente allora questo PUO' essere definito nella classe derivata.
  3. Se una classe base ha un metodo definito senza codice con la scrittura:
       virtual dichiarazionemetodo =0;
    allora il metodo DEVE essere ridefinito nella classe derivata
Il programma appena scritto sembra contraddire la prima regola. In effetti la prima regola e' solo un consiglio di buona programmazione object-oriented ma il compilatore non si arrabbiera' se la violate.Il motivo per cui viene enunciata questa regola "strana" (almeno per chi viene da Java) e' di abituare i programmatori a usare l'attributo "virtual" altrimenti il polimorfismo non viene attuato.



Programma 2: Realizza 2 oggetti BigCounter e Accumulator con una medesima interfaccia di stampa .S

    file Calcola.cc

#include < iostream>     
class StatusPrinter{
 public:
  virtual void printStatus() =0;
 }; 
class Counter {
 public:
  Counter():value(0){};    
  Counter(int n):value(n){};    
  int getValue() {
   return value; } 
  void increment(){value++;}                  
 protected:
  int value;  
 }; 
class Accumulator:public StatusPrinter {
 public:
  Accumulator():sum(0){}
  Accumulator(int n):sum(n){}
  void add(int n){sum += n;} 
  int getSum(){return sum;}  
   void printStatus(){cout << "Sono un accumulatore e il mio contenuto e' "<< sum << endl;}
 private:
  int sum;
 }; 
class BigCounter :public Counter , public StatusPrinter{
 public:
  void increment(){Counter::increment();Counter::increment();}
  void printStatus(){cout << "Sono un contatore e il mio contenuto e' "<< Counter::value << endl;}
 }; 

 main() {  
       
 BigCounter *c1 =new BigCounter();
 Accumulator *c2 = new Accumulator();
                                    
 cout << c1->getValue() << endl;
 cout << c2->getSum() << endl;
 c1->increment();c2->add(5);
 c1->printStatus();c2->printStatus();       
}
Questo programma funziona anche se non dichiariamo printStatus come virtual! Come mai? Il compilatore riesce a fare un normale link statico ai metodi derivati perche' conosce il loro tipo . Invece questa variazione del codice del main funziona solo con la dichiarazione virtual:
void interroga(StatusPrinter* s){ s->printStatus();}
 main() {  
       
 BigCounter *c1 =new BigCounter();
 Accumulator *c2 = new Accumulator();
 StatusPrinter *p;
                                    
 cout << c1->getValue() << endl;
 cout << c2->getSum() << endl;
 c1->increment();c2->add(5);
 p = c1; interroga(p);
 p = c2; interroga(p);
}
A causa dell'ereditarieta' multipla l'operazione di cast tra classi in C++ e' molto piu' complessa che in Java. In effetti l'unica forma di cast esistente in Java indicata con
(nuovo_tipo) espressione
in C++ e' presente ma solo per compatibilita' con C e si usa di solito solo per conversioni tra dati di tipo elementare.Per le classi sono stati introdotti 4 nuovi tipi di cast(!?!):
reinterpret_cast <nuovo_tipo> (espressione)
    dynamic_cast <nuovo_tipo> (espressione)
     static_cast <nuovo_tipo> (espressione)
      const_cast <nuovo_tipo> (espressione)
reinterpret_cast converte un puntatore di un tipo a un puntatore di un'altro tipo o anche ad un'intero. Non viene fatto alcun controllo se l'oggetto puntato e' davvero anche del nuovo tipo.

Se si desidera fare questo controllo si usa il dynamic_cast. Infatti un risultato null indica che l'oggetto puntato non e' del nuovo tipo. Nel caso del programma precedente potremmo usare un dynamic_cast per riconoscere se un oggetto StatusPrinter e' Counter oppure Accumulator.

static_cast serve a fare upcast e downcast tra classi derivate. Nel nostro caso se abbiamo un BigCounter , possiamo usare questo cast per trasformarlo in Counter.

Infine il const_cast permette di eliminare o aggiungere dal tipo la specifica const.




Programma 3: Mostra il polimorfismo in azione con una classe Persona e due classi derivate Studente e Professore ognuna delle quali risponde in maniera diversa allo stesso messaggio di print. .S

    file Poli.cc

#include < iostream.h>
#include < string>

class Persona {
public: 
  Persona(string s):nome(s) {  }
  virtual void print() { cout << "Il mio nome e' " << nome << endl; }
protected:
  string nome;
};

class Studente : public Persona {
public: 
  Studente(string s, float g) : Persona(s),media(g) { }
  void print() { cout << "Il mio nome e' " << nome
     << " e la mia media e' " << media << endl; }
private:
  float media;
};

class Professore : public Persona {
public: 
  Professore(string s, int n) : Persona(s), pubblicazioni(n) { }
  void print() { cout << "Il mio nome e' " << nome
     << " ed ho  " << pubblicazioni << " pubblicazioni" << endl; }
private:
  int pubblicazioni;
};

int main()
{
  Persona* p;
  Persona x(string("Giuseppe"));
  p = &x;
  p->print();
  Studente y(string("Giovanni"), 21.);
  p = &y;
  p->print();
  Professore z(string("Antonio"), 7);
  p = &z;
  p->print();  
        return 0;
}



Programma 4: Mostra la necessita' di definire distruttori virtual per gestire correttamente la distruzione di oggetti contenuti con puntatori .S

    file Distruttori.cc


#include < iostream>
class A {
 public :
  A(){p = new int[2];cout << "Allocato un vettore di 2 interi" << endl;}
  virtual ~A() {delete [] p; cout << "Distrutto un vettore di 2 interi" << endl;}
 private:
  int* p;
};
class B : public A{
 public :
  B(){q = new int[20];cout << "Allocato un vettore di 20 interi" << endl;}
  ~B() {delete [] q; cout << "Distrutto un vettore di 20 interi" << endl;}
 private:
  int* q;
};
int main()
{
 for (int i = 0; i < 5; i++){
     A* a = new B;
     delete a;
    }
}



Programma 5: Mostra l'uso di classi interne per realizzare un modello di classe che riesca a memorizzare una serie di oggetti in una struttura a lista .S

    file Lista.cc


#include < iostream>
#include < string>
const int null = 0;
template< class T> class list{
protected:
        class Nodo{
        public:
                Nodo*  next;
                 T*     val;
                Nodo(Nodo* n, T* v) {next = n; val =v; } ;
        };
        Nodo*   head;   // head of singly linked list
        Nodo*   cur;    // last selected item
public:
        int     number; // number of items in list
        list() {head = null; cur=null; number=0; };
        ~list(){ 
        while ( head != null){
                cur = head;
                delete head->val;
                head = head->next;
                delete cur;}
        };
void    enter(T* item) {
        Nodo* temp = head;
        if (item != null){
                head = new Nodo( temp, item);
                number ++;}
        };
T*      first() {cur=head; 
        if (cur != null) {return (cur->val);}
        else { return null;}
        };
T*      next() {
        if (cur != null){ 
                cur = cur->next;
                if (cur != null){return (cur->val);}
                else {return null;}}
        else {return null;}
        };
T*      current() { 
                if (cur != null){ return (cur->val);}
                else {return null;}
         };
bool    sequel(){
        if (cur == null){ return false;}
        else {return cur->next != null;}
        };
void    remove(T* item) {
        Nodo**    temp = &head;  // temp contains the location of the pointer to the item
                                // temp is initialized with the location of head
        for (Nodo** temp = &head; *temp == null; temp = &((*temp)->next)){ 
                if ((*temp)->val == item){
                        Nodo*  rm = *temp;
                        delete item;
                        delete rm;
                        temp = &((*temp)->next);
                        if (cur == *temp) cur = null;}  // cur points to removed item,
                                                        // so cur is set to null
                }
        };      
void    print(){
        cout << " list with " << number <<" entries \n" << flush;
        int k =0;
        Nodo* temp = head; 
        while (temp != null){ 
                if ( cur == temp) { cout << "current ";}
                else { cout << "        ";};
                cout << " item "<< ++k << ":   " << flush;
                cout << *temp->val << endl;
                temp = temp->next;}
        };

};
int main()
{
 string s1="ciccio",s2="caio",s3="sempronio";
 list< string>* l = new list < string>;
 l->print();
 l->enter(&s1);
 l->enter(&s2);
 l->enter(&s3);
 l->print();
 return 0;
 }




Programma 6: Aggiungi un iteratore al programma precedente .S

    file list.h

 
#ifndef LIST_H
#define LIST_H
template <class T> class ListIterator;
const int null = 0;
template<class T> class list{
friend class ListIterator <T>
private:

        class Nodo{
        public:
                Nodo*  next;
                 T*     val;
                Nodo(Nodo* n, T* v) {next = n; val =v; } ;
        };
        Nodo*   cur;    // last selected item
        Nodo*   head;   // head of singly linked list
public:
        int     number; // number of items in list
        list() {head = null; cur=null; number=0; };
        ~list(){ 
        while ( head != null){
                cur = head;
                delete head->val;
                head = head->next;
                delete cur;}
        };
void    enter(T* item) {
        Nodo* temp = head;
        if (item != null){
                head = new Nodo( temp, item);
                number ++;}
        };
T*      first() {cur=head; 
        if (cur != null) {return (cur->val);}
        else { return null;}
        };
T*      next() {
        if (cur != null){ 
                cur = cur->next;
                if (cur != null){return (cur->val);}
                else {return null;}}
        else {return null;}
        };
T*      current() { 
                if (cur != null){ return (cur->val);}
                else {return null;}
         };
bool    sequel(){
        if (cur == null){ return false;}
        else {return cur->next != null;}
        };
void    remove(T* item) {
        Nodo**    temp = &head;  // temp contains the location of the pointer to the item
                                // temp is initialized with the location of head
        for (Nodo** temp = &head; *temp == null; temp = &((*temp)->next)){ 
                if ((*temp)->val == item){
                        Nodo*  rm = *temp;
                        delete item;
                        delete rm;
                        temp = &((*temp)->next);
                        if (cur == *temp) cur = null;}  // cur points to removed item,
                                                        // so cur is set to null
                }
        };      
void    print(){
        cout << " list with " << number <<" entries \n" << flush;
        int k =0;
        Nodo* temp = head; 
        while (temp != null){ 
                if ( cur == temp) { cout << "current ";}
                else { cout << "        ";};
                cout << " item "<< ++k << ":   " << flush;
                cout << *temp->val << endl;
                temp = temp->next;}
        };

};

#endif

    file ListIterator.h


#ifndef LISTITERATOR_H
#define LISTITERATOR_H
#include "list.h"
template< class T>
class ListIterator
{
public:
  ListIterator(const list& l):_list(&l),_current(0){ }
 T* current() const{if (_current == 0) return 0; else return _current->val; }
 bool next(){if(_list->number == 0) return false;
              if(_current == 0){
                _current = _list->head;return true;}
                else{
                _current = _current->next;
                 if(_current != 0)return true;
                  else {return false;}
                
               }
 }
 void rewind(){_current=0; }
private:
 const list * _list;
 list::Nodo* _current;
};
#endif

    file Lista.cc

#include < iostream>
#include < string>
#include "list.h"
#include "ListIterator.h"
int main()
{
 string s1="ciccio",s2="caio",s3="sempronio";
 list< string> l ;
 l.print();
 l.enter(&s1);
 l.enter(&s2);
 l.enter(&s3);
 l.print();
 ListIterator< string> it(l);
 while(it.next())cout << *(it.current()) << endl;
 return 0;
 }


Quali files includere e dove?- Quando si lavora in C++ il problema di quali header includere e dove includerli diventa subito grosso. Abbiamo gia' visto in questo programma l'uso del preprocessore per evitare l'inclusione magari ricorsiva di infinite copie di un header. Ma esistono altri trucchi per evitare tutti quei problemi creati da inclusioni di definizioni non necessarie. Uno di questi consiste nell'evitare di includere un header nel file .h di una classe se questa inclusione serve solo per la definizione. Supponiamo che nell'header della classe Pluto voi usiate una classe Pippo; invece di includere Pippo.h nell'header di Pluto e poi ritrovarvi con Pippo.h inclusa in tutte le classi che richiamano Pluto, potete procedere nel modo seguente:
  1. Inserite in Pluto.h l'istruzione
    class Pippo;
  2. In Pluto.cc inserite invece:
    #include "Pippo.h"
Il risultato sara' che l'include di Pluto ora non carica anche Pippo. La prima istruzione realizza cio' che si chiama in gergo una forward declaration e in certi casi e' sufficiente. Per cui potete fare a meno della seconda istruzione. Le regole da usare sono complesse e di solito si procede per tentativi.
  • Provate solo 1
  • Se non basta aggiungete 2
  • Se non basta, procedete nella maniera normale, usando l'include nell'header file.



Programma 7: Ora usa il programma dell'esercizio precedente per costruire una lista di Persona come quelle definite nell'programma 3.Definisci una funzione interroga che ricava informazioni su questi oggetti richiamando il metodo polimorfico print di Persona. Quindi mostra il polimorfismo in azione caricando in maniera random oggetti di tipo diverso nella lista e mostrando che ognuno risponde in maniera corretta. Infine fai vedere che l'iteratore con la chiamata al metodo polimorfico funziona anche con nuovi tipi di dati che ereditano da Persona che non esistevano quando il codice e' stato scritto. S

    file Poli.cc


#include <iostream.h>
#include <string>
#include "list.h"
#include "ListIterator.h"

class Persona {
public: 
  Persona(string s):nome(s) {  }
  virtual void print() { cout << "Il mio nome e' " << nome << endl; }
protected:
  string nome;
};

class Studente : public Persona {
public: 
  Studente(string s, float g) : Persona(s),media(g) { }
  void print() { cout << "Il mio nome e' " << nome
     << " e la mia media e' " << media << endl; }
private:
  float media;
};

class Professore : public Persona {
public: 
  Professore(string s, int n) : Persona(s), pubblicazioni(n) { }
  void print() { cout << "Il mio nome e' " << nome
     << " ed ho  " << pubblicazioni << " pubblicazioni" << endl; }
private:
  int pubblicazioni;
};
void interroga(Persona *p){p->print();}
int main()
{
  list <Persona> p;
  Persona *x = new Persona(string("Giuseppe"));
  p.enter(x); 
  x = new Studente(string("Giovanni"), 21.);
  p.enter(x); 
  x = new Professore(string("Antonio"), 7);
  p.enter(x); 
  ListIterator<Persona> it(p);
  while(it.next())  interroga(it.current()) ;
        return 0;
}

    file Poli1.cc

#include <iostream.h>
#include <time.h>
#include <string>
#include "list.h"
#include "ListIterator.h"

class Persona {
public: 
  Persona(string s):nome(s) {  }
  virtual void print() { cout << "Il mio nome e' " << nome << endl; }
protected:
  string nome;
};

class Studente : public Persona {
public: 
  Studente(string s, float g) : Persona(s),media(g) { }
  void print() { cout << "Il mio nome e' " << nome
     << " e la mia media e' " << media << endl; }
private:
  float media;
};

class Professore : public Persona {
public: 
  Professore(string s, int n) : Persona(s), pubblicazioni(n) { }
  void print() { cout << "Il mio nome e' " << nome
     << " ed ho  " << pubblicazioni << " pubblicazioni" << endl; }
private:
  int pubblicazioni;
};
void interroga(Persona *p){p->print();}
int main()
{
  list <Persona> p;
  Persona *x;
  int seed = time(null);
  srand(seed);
  if(rand()<(RAND_MAX/2))x = new Persona(string("Giuseppe"));
    else x = new Studente(string("Giuseppe"),30.);
  p.enter(x); 
  if(rand()<(RAND_MAX/2))x = new Studente(string("Giovanni"), 21.);
    else x = new Professore(string("Giovanni"),100.);
  p.enter(x); 
  if(rand()<(RAND_MAX/2))x = new Professore(string("Antonio"), 7);
    else x = new Persona(string("Antonio"));
  p.enter(x); 
  ListIterator<Persona> it(p);
  while(it.next())  interroga(it.current()) ;
        return 0;
}

    file Poli2.cc

#include <iostream.h>
#include <time.h>
#include <string>
#include "list.h"
#include "ListIterator.h"

class Persona {
public: 
  Persona(string s):nome(s) {  }
  virtual void print() { cout << "Il mio nome e' " << nome << endl; }
protected:
  string nome;
};

class Studente : public Persona {
public: 
  Studente(string s, float g) : Persona(s),media(g) { }
  void print() { cout << "Il mio nome e' " << nome
     << " e la mia media e' " << media << endl; }
private:
  float media;
};

class Professore : public Persona {
public: 
  Professore(string s, int n) : Persona(s), pubblicazioni(n) { }
  void print() { cout << "Il mio nome e' " << nome
     << " ed ho  " << pubblicazioni << " pubblicazioni" << endl; }
private:
  int pubblicazioni;
};

class Dottorando : public Persona {
public: 
  Dottorando (string s, int n) : Persona(s),numero_anni(n) { }
  void print() { cout << "Il mio nome e' " << nome
     << " e sono un dottorando con " << numero_anni << " anni di frequenza" << endl; }
private:
  int numero_anni;
};
void interroga(Persona *p){p->print();}
int main()
{
  list <Persona> p;
  Persona *x;
  int seed = time(null);
  srand(seed);
  if(rand()<(RAND_MAX/2))x = new Persona(string("Giuseppe"));
    else x = new Studente(string("Giuseppe"),30.);
  p.enter(x); 
  if(rand()<(RAND_MAX/2))x = new Studente(string("Giovanni"), 21.);
    else x = new Professore(string("Giovanni"),100.);
  p.enter(x); 
  if(rand()<(RAND_MAX/2))x = new Professore(string("Antonio"), 7);
    else x = new Persona(string("Antonio"));
  p.enter(x); 
  x = new Dottorando(string("Mario"),2);
  p.enter(x);
  ListIterator<Persona> it(p);
  while(it.next())  interroga(it.current()) ;
        return 0;
}
Lo strano nome polimorfismo viene dal fatto che uno stesso oggetto puo' essere di tipi diversi a seconda della sua posizione nella gerarchia stabilita dall'ereditarieta'.Cioe' puo' assumere piu' forme. Questo concetto e' valido anche nella vita di tutti i giorni in cui e' inteso che ad esempio, che un gatto e' al tempo stesso un felino,un mammifero, un animale,etc.. Questo permette di definire dei metodi o delle funzioni polimorfiche alle quali ogni oggetto risponde secondo le proprie caratteristiche. Dato che queste caratteristiche sono di solito note solo all'atto dell'esecuzione, ecco che il polimorfismo richiede il link(o binding) dinamico dei metodi. Per avere questo link dinamico il C++ richiede la dichiarazione dei metodi polimorfici come virtual e l'uso di puntatori per accedere agli stessi. Inoltre, per il modo particolare di funzionare del programma C++, anche i distruttori vanno dichiarati come virtual.
INDIETRO a Imparate C++ in 3 giorni
INDIETRO a Da Java al C++
Maintained by Giuseppe Zito: info@zitogiuseppe.com
Ultimo aggiornamento: