11) Funzioni virtuali e polimorfismo funzioni di base virtual

Versione Completa   Stampa   Cerca   Utenti   Iscriviti     Condividi : FacebookTwitter
JehovaZorobabele
00mercoledì 3 agosto 2016 18:19
Polimorfismo, overloading, funzioni virtuali di base, puntatore della classe , biding


L’obbiettivo del polimorfismo è di avere una funzione base e da questa base si deve ricavare
più funzioni da un’ unica base   allo stesso modo non è l’overloadin che le funzioni sono diverse e il compilatore sceglie una in base alla differenza  , nel polimorfismo sono uguali virtuali e gerarchiche il polimorfismo possiamo vederlo dietro un termosifone non ha importanza il dietro se c’è una caldaia a gas oppure un camino o diversamente una stufa a pellet rimane sempre un termosifone che riscalda una stanza e il termosifone è l’ interfaccia un unico oggetto che è modificabile

Eravamo rimasti con il rendere virtuali le classi ereditate per non aver la confusione dentro il compilatore , invece ora possiamo operare diversamente  per uguale scopo, rendere la  funzione dentro la classe base in modo virtuale e usare un puntatore della classe per specificare quale funzione possiamo usare, una funzione virtuale normalmente rimane come le normali funzioni ,  usando il polimorfismo si può accedere con un puntatore di classe , per creare questo puntatore va creato dentro gli oggetti della classe padre  in modo che sia riconosciuto dagli oggetti figli un esempio creiamo  3 classi una padre 2 classi figlie create  dalla classe base  tutte con la stessa funzione di nome solo la funzione  dentro la classe base è dichiarata virtual  nella creazione degli oggetti c’è il puntatore *p è creato con l’ oggetto della classe base vediamo :



#include<iostream> using namespace std;

class base{public:
         virtual void func()   { cout << " funzione di base \n";       }};

class figlia1 : public base {public:
                   void func()  { cout << " funzione di figlia1 \n";       }};

class figlia2 : public base { public:
                   void func()  { cout << " funzione di figlia2 \n";       }};
int main() {
         base   b, *p ;        figlia1 f;     figlia2  i;

         p = &b;       p->func();            p = &f;        p->func();  p = &i; p->func();

         cout << "\n\n Per uscire premere un tasto + invio: ";  char zx; cin >> zx;  }

Il puntatore *p  legge dentro l’ indirizzo delle varie classi  p= & tramite  l’ operatore  & cosi trova le varie funzioni membro di ogni classe , utilizzando l’ operatore freccia ->  perché si usa il puntatore,  altrimenti per leggere i membri delle classi si usava il punto . come si utilizza normalmente , comunque potevamo farlo ugualmente chiamando con gli oggetti b.f.i. ognuno  deve richiamare il suo membro b.func(); f.func(); i.func() e il programma va ugualmente  ma non sfrutta i vantaggi delle funzioni virtuali

Una funzione virtuale può sembrare uguale alle funzioni usate per l’oveloading  invece no per 2 aspetti ,con l’ overloadind, 2 funzioni uguali non possono essere uguali al limite deve cambiare i parametri  mentre le funzioni virtuali devono essere proprio uguali poi special modo si deve copiare fedelmente il prototipo specificato dentro la classe altrimenti il compilatore non considera più funzioni virtuali ma funzioni  per overloading , le funzioni virtuali non possono essere  static e friend e per chiudere  le funzioni costruttore non possono essere virtuali mentre i distruttori si 


 Potevamo chiamare diversamente le funzioni  nel programma precedente utilizzando anzi che i puntatori  si poteva utilizzare una funzione di chiamata tramite l’ indirizzo &,   per la funzione virtuale vediamo un altro programma rimane tutto come su eccetto per il puntatore , inoltre viene inserita una nuova funzione ( void f ) che richiama la classe base  inserisco solo la parte modificata :

void f (base &b) { b.funz();}      int main () {

             base   ba;    figlia1  
f1;    figlia2   f2; 

             f(ba);  f(f1); f(f2);



creazione degli oggetti  ba  f1  f2   chiamata alle funzioni tramite  la funzione f  con il richiamo degli oggetti ba f1 f2 le stesse funzioni uguali funz ereditate da base rimangono ugualmente  virtuali come è il prototipo che è la prima dentro la funzione base,  se noi facciamo  un cambio di programma la figlia2 anzi che ereditare da base, eredita da figlia1, il programma nei 2 modi d’esecuzione  viene sempre eseguito   con lo stesso risultato

Quando una funzione virtual non viene ridefinita nelle nuove versioni ereditate  il compilatore utilizza la funzione  di base ,oppure utilizza la funzione più vicina secondo la gerarchia,  siccome con l’ eredità una classe nuova ereditata sono gerarchiche anche le funzioni virtuali sono gerarchiche,  pertanto se una funzione virtuale non ridefinita ma solo chiamata  il compilatore utilizzerà la funzione più vicina alla chiamata,  nel caso nostro se figlia2  è derivata dalla classe base  usa funz di base,  se invece è derivata da figlia1 usa la funzione di figlia1 ma se entrambe deriva dalla base si usa quella più vicina ridefinita nostro caso è figlia1

Possiamo avere 3 diverse funzione ma legate dallo stesso nome come ad esempio leggi() ,
e questo deve leggere delle condizioni differenti ma stesso tempo uguali, allora cosi si crea nella classe base una funzione virtuale pura che grosso modo è la base per le altre funzioni che avranno la stessa mansione per creare una funzione pura virtuale

virtual tipo  nome_funzione (parametri)=0;

Ora ogni classe derivata deve avere la stessa funzione con una propria definizione un esempio facciamo 4 funzioni dove si calcola diversamente 2 numeri  e li si legge con un’ unica interfaccia abbiamo 4 operazioni diverse l’ interfaccia è sempre    virtual void leggi (int a,int b) =0 ; ecco il programma scritto con 4 classi diverse ma tutte usano la funzione void leggi(int a,int b) che è l’ interfaccia base  :

#include<iostream>  using namespace std;

class
base{ public:      virtual void leggi(int a, int b) = 0; };



class somma : public base{ public: void leggi(int a , int b)  {cout << a + b <<" : = 30 +
2 \n";} };

class sottrazione :public base{ public: void leggi(int a , int b){cout << a - b << " : = 30 - 2
\n";} };

class moltiplica :public base{ public: void leggi(int a , int b) {cout << a * b << " : = 30 * 2 \n";}};

class dividi :public base{public:  void leggi(int a , int b)  {cout << a / b <<" : = 30 / 2 \n"; } };

int main () {   somma s;  sottrazione o;      moltiplica m;     dividi d;

   s.leggi(30 , 2);    o.leggi(30 , 2);    m.leggi(30, 2);      d.leggi(30, 2);

    cout << "\n\n premere un tasto + invio per uscire: ";  char zx; cin >> zx; }



Con  gli oggetti  s, o, m, d  siamo andati a leggere dentro le varie funzioni leggi , se nella funzione virtuale pura togliamo un parametro int b ecco che il compilatore non compila più e da errore, il che si presume che la base del programma sia proprio (virtual void leggi(int a, int b) = 0; ) infatti questa è l’ interfaccia comune per tutte e 4  funzioni delle  4 classi derivate,  una volta che si usa una funzione virtuale pura  tutte le classi derivate deve avere necessariamente  la stessa funzione ridefinita caso contrario il compilatore protesta qui non abbiamo usato l’ overloading ma il polimorfismo abbiamo usato 4 funzioni [ void leggi(int a,int b) ] la classe base che abbiamo usato per inserire la funzione virtuale pura si chiama classe astratta e qui non serve la creazioni di alcun oggetto come avete visto dentro non c’è nessun oggetto nella classe base, esiste solo il prototipo delle funzione virtuale  [ leggi() ], ma nella classe base possiamo usare sempre i puntatori alla suddetta classe base   base  b ,*p  cosi per accedere  usiamo l’ operatore freccia -> come visto nei precedenti esempi la funzione virtuale è una interfaccia  primaria dove si costruisce un esempio di base per poi creare più tipi diversi meglio specificati nelle classi figlie,  un esempio desideriamo conoscere la differenza tra litro e centilitri è uguale come la differenza  tra chilo e quintale oppure tra un metro e un chilometro   o tra un metro è i pollici l’ interfaccia rimane sempre la stessa anche se poi alla fine i dati sono diversi,  la funzione virtuale crea questo tipo di base che poi nelle nuove funzioni saranno implementate  secondo quello che ci serve nel dettaglio in questo modo si crea 2 sistemi di rilegatura definito in inglese  il binding anticipato e ritardato  nella legatura ritardata fa parte quella porzione di programma che non si conosce finché non viene avviato il programma  un esempio un puntatore va e legge nella casella di memoria  x, ma finché in x non è scritto nulla non si sa che cosa ci sia dentro la particella di memoria, mentre il biding  ritardato è quando usiamo una domanda e dobbiamo inserire i dati con il cin >>  in poche parole il binding ritardato è tutto quello che il compilatore non conosce prima di compilare il programma , all’ inverso il binding anticipato è tutto quello che il compilatore riconosce fin da subito se noi scriviamo una classe una variabile ecc… già il compilatore conosce queste cose visto che le possiamo leggere con l’ aiuto dell’ intellisense  quello è tutte quelle funzioni operazioni anticipate fin da prima di avviare il compilatore  
 


Ciao grazie da ByMpt-Zorobabele

Testo PDF aggiornato con questo ultimo capitolo  inserito lo trovate :

http://www.freeforumzone.com/d/11266858/Elenco-figure-dei-comandi-della-programmazione-C-TESTO-PDF/discussione.aspx

Anche qui di queste discussioni che inserisco faccio un testo PDF da poter
scaricare tutto il mio materiale è da utilizzare come desiderate  per il testo PDF vedi cartella dedicata , ogni volta cambia ubicazione del testo  allora cambio il file e il testo lo inserisco dentro la cartella < file aggiornato ad oggi >

http://www.freeforumzone.com/d/11266858/Elenco-figure-dei-comandi-della-programmazione-C-TESTO-PDF/discussione.aspx

Questa è la versione 'lo-fi' del Forum Per visualizzare la versione completa clicca qui
Tutti gli orari sono GMT+01:00. Adesso sono le 14:21.
Copyright © 2000-2024 FFZ srl - www.freeforumzone.com