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 ricavarepiù 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 risultatoQuando 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 poterscaricare 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