English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In questo articolo, imparerai le funzioni virtuali e i loro punti di utilizzo. Inoltre, imparerai le funzioni virtuali pura e le classi astratte.
Una funzione virtuale è una funzione membro della classe base che si desidera ridefinire nelle classi derivate.
Prima di illustrare in dettaglio, diamo un'occhiata a perché è necessario prima un funzione virtuale.
Supponiamo che stiamo sviluppando un gioco (ad esempio, oggetti: armi).
Abbiamo creato la classe Weapon e ne abbiamo derivato due classi, Bomb e Gun, che caricano le funzionalità delle rispettive armi.
#include <iostream> using namespace std; class Weapon { public: void loadFeatures() { cout << "Carica le caratteristiche dell'arma.\n"; } }; class Bomb : public Weapon { public: void loadFeatures() { cout << "Carica le caratteristiche della spada.\n"; } }; class Gun : public Weapon { public: void loadFeatures() { cout << "Carica le caratteristiche delle armi.\n"; } }; int main() { Weapon *w = new Weapon; Bomb *b = new Bomb; Gun *g = new Gun; w->loadFeatures(); b->loadFeatures(); g->loadFeatures(); return 0; }
Risultato di output
Carica le caratteristiche dell'arma. Carica le caratteristiche della spada. Carica le caratteristiche delle armi.
Abbiamo definito separatamente tre puntatori alle classi Weapon, Bomb e Gun, w, b e g. E utilizziamo i seguenti comandi per chiamare la funzione membro loadFeatures() di ciascun oggetto:
w->loadFeatures(); b->loadFeatures(); g->loadFeatures();
L'opera perfetta!
Ma il nostro progetto di gioco sta diventando sempre più grande. E abbiamo deciso di creare una classe Loader separata per caricare le funzionalità delle armi.
Questa classe Loader carica altre funzionalità delle armi in base alla scelta dell'arma.
class Loader { public: void loadFeatures(Weapon *weapon) { weapon->features(); } };
loadFeatures() carica le caratteristiche specifiche delle armi.
#include <iostream> using namespace std; class Weapon { public: Weapon() { cout << "Carica le caratteristiche dell'arma.\n"; } void features() { cout << "Carica le caratteristiche dell'arma.\n"; } }; class Bomb : public Weapon { public: void features() { this->Weapon::features(); cout << "Carica le caratteristiche della spada.\n"; } }; class Gun : public Weapon { public: void features() { this->Weapon::features(); cout << "Carica le caratteristiche della pistola.\n"; } }; class Loader { public: void loadFeatures(Weapon *weapon) { weapon->features(); } }; int main() { Loader *l = new Loader; Weapon *w; Bomb b; Gun g; w = &b; l->loadFeatures(w); w = &g; l->loadFeatures(w); return 0; }
Risultato di output
Carica le caratteristiche dell'arma. Carica le caratteristiche dell'arma. Carica le caratteristiche dell'arma. Carica le caratteristiche dell'arma.
La nostra implementazione sembra corretta. Ma le caratteristiche dell'arma sono state caricate 4 volte. Perché?
All'inizio, l'oggetto dell'arma w punta all'oggetto b della classe (Bomb) e proviamo a passare questo puntatore all'oggetto l della classe Loader per caricare le caratteristiche dell'oggetto Bomb tramite la funzione loadFeatures().
Anche così, proviamo a caricare le caratteristiche dell'oggetto Gun.
Ma la funzione loadFeatures() della classe Loader accetta come parametro un puntatore all'oggetto della classe Weapon:
void loadFeatures(Weapon *weapon)
Ecco perché le caratteristiche dell'arma sono state caricate 4 volte. Per risolvere questo problema, dobbiamo implementare una funzione virtuale nella classe base (classe Weapon).
class Weapon { public: virtual void features() { cout << "Carica le caratteristiche dell'arma.\n"; } };
#include <iostream> using namespace std; class Weapon { public: virtual void features() { cout << "Carica le caratteristiche dell'arma.\n"; } }; class Bomb : public Weapon { public: void features() { this->Weapon::features(); cout << "Carica le caratteristiche della spada.\n"; } }; class Gun : public Weapon { public: void features() { this->Weapon::features(); cout << "Carica le caratteristiche della pistola.\n"; } }; class Loader { public: void loadFeatures(Weapon *weapon) { weapon->features(); } }; int main() { Loader *l = new Loader; Weapon *w; Bomb b; Gun g; w = &b; l->loadFeatures(w); w = &g; l->loadFeatures(w); return 0; }
Risultato di output
Carica le caratteristiche dell'arma. Carica le caratteristiche della spada. Carica le caratteristiche dell'arma. Carica le caratteristiche della pistola.
Inoltre, notare che la funzione l->loadFeatures(w) chiama funzioni di diverse classi in base all'oggetto a cui l'oggetto l punta.
L'uso delle funzioni virtuali rende il nostro codice non solo più chiaro, ma anche più flessibile.
Nel programma sopra, "Carica caratteristiche dell'arma." è stato stampato due volte. Suggeriamo di aggiungere altri codici al programma sopra per caricare le caratteristiche dell'arma una sola volta.
Se vogliamo aggiungere un altro tipo di arma (ad esempio, l'arco), possiamo aggiungere e caricare le sue caratteristiche con facilità.Come aggiungere?
class Bow : public Weapon { public: void features() { this-<Weapon::features(); cout >> "Carica le caratteristiche dell'arco.\n"; } };
E aggiungi il seguente codice nella funzione main().
Bow b; w = &b; l->loadFeatures(w);
E 'da notare che non abbiamo cambiato alcun contenuto nella classe Loader per caricare le caratteristiche della lama.
L'obiettivo dell'ingegneria del software orientata agli oggetti è dividere un problema complesso in piccoli insiemi. Questo aiuta a comprendere e gestire efficacemente i problemi.
A volte, è meglio utilizzare l'ereditarietà solo quando è meglio visualizzare il problema.
In C++, è possibile creare una classe astratta non istanziabile (non è possibile creare un oggetto di questa classe). Ma è possibile derivare una classe da essa e istanziare un oggetto della classe derivata.
Le classi astratte non possono essere istanziate sono classi di base.
Le classi che contengono funzioni virtuali pure sono chiamate classi astratte.
Le funzioni virtuali terminate con =0 sono chiamate funzioni virtuali pure. Ad esempio,
class Weapon { public: virtual void features() = 0; };
In questo caso, la funzione virtuale pura è
virtual void features() = 0
e la classe Weapon è una classe astratta.
#include <iostream> using namespace std; // Classe astratta (non può essere istanziata) class Shape { protected: float l; public: void getData() { cin >> l; } // Funzione virtuale virtual float calculateArea() = 0; }; class Square : public Shape { public: float calculateArea() { return l * l; } }; class Circle : public Shape { public: float calculateArea() { return 3.14 * l * l; } }; int main() { Square s; Circle c; cout << "Inserisci la lunghezza per calcolare l'area del quadrato: "; s.getData(); cout << "Area del quadrato: " << s.calculateArea(); cout << "\nInserisci il raggio per calcolare l'area del cerchio: "; c.getData(); cout << "Area del cerchio: " << c.calculateArea(); return 0; }
Risultato di output
Inserisci la lunghezza per calcolare l'area del quadrato: 4 Area del quadrato: 16 Inserisci il raggio per calcolare l'area del cerchio: 5 Area del cerchio: 78.5
In questo programma, la funzione virtuale pura virtual float area() = 0; è definita nella classe Shape.
Una cosa da notare è che dovresti sovrascrivere la funzione virtuale pura della classe base nella classe deriva. Se il ri-scrittura fallisce, la classe deriva diventerà anche una classe astratta.