C++ Polymorphismus
PolymorphismusWörtlich bedeutet es mehrere Formen.
Wenn zwischen Klassen eine hierarchische Struktur besteht und die Klassen durch Vererbung miteinander verbunden sind, wird Polymorphismus verwendet.
In C++ ist Polymorphismus eines der wichtigen Merkmale der objektorientierten Programmierung.
C++-Polymorphismus ermöglicht es, Zeiger oder Referenzen auf die Basisklasse zu verwenden, um überschriebene Methoden abgeleiteter Klassen aufzurufen, sodass dieselbe Schnittstelle unterschiedliche Verhaltensweisen zeigen kann.
Polymorphismus macht Code flexibler und allgemeiner. Das Programm kann über Zeiger oder Referenzen auf die Basisklasse mit Objekten unterschiedlicher Typen arbeiten, ohne Objekttypen explizit unterscheiden zu müssen. Dadurch wird Code erweiterbarer; wenn beispielsweise eine neue Formklasse hinzugefügt wird, muss das Hauptprogramm nicht geändert werden.
Hier sind einige wichtige Punkte zum Polymorphismus:
Virtuelle Funktionen:
- Das Deklarieren einer Funktion in der Basisklasse als virtuelle Funktion verwendet das Schlüsselwort
virtual。 - Eine abgeleitete Klasse kann diese virtuelle Funktion überschreiben.
- Wenn eine virtuelle Funktion aufgerufen wird, wird anhand des tatsächlichen Typs des Objekts bestimmt, welche Version der Funktion aufgerufen wird.
Dynamische Bindung:
- Auch als späte Bindung bekannt, wird die konkrete Implementierung eines Funktionsaufrufs zur Laufzeit bestimmt.
- Es erfordert die Verwendung eines Zeigers oder einer Referenz auf die Basisklasse, um virtuelle Funktionen aufzurufen; zur Laufzeit entscheidet der Compiler anhand des tatsächlichen Typs des Objekts, welche Funktion aufgerufen wird.
Reine virtuelle Funktionen:
- Eine Klasse, die reine virtuelle Funktionen enthält, wird als abstrakte Klasse bezeichnet, und sie kann nicht direkt instanziiert werden.
- Eine rein virtuelle Funktion hat keinen Funktionskörper; bei der Deklaration verwendet sie das Schlüsselwort
= 0。 - Sie zwingt abgeleitete Klassen, konkrete Implementierungen bereitzustellen.
Der Implementierungsmechanismus der Polymorphie:
- Virtuelle Funktionstabelle (V-Table): Die C++-Laufzeit verwendet eine virtuelle Funktionstabelle, um Polymorphie zu implementieren. Jede Klasse, die virtuelle Funktionen enthält, besitzt eine virtuelle Funktionstabelle, die Zeiger auf alle virtuellen Funktionen in der Klasse speichert.
- Virtueller Funktionszeiger (V-Ptr): Das Objekt enthält einen Zeiger auf die virtuelle Funktionstabelle der Klasse.
Die Vorteile der Verwendung von Polymorphie:
- Code-WiederverwendungDurch Basisklassenzeiger oder Basisklassenreferenzen können Objekte unterschiedlicher Typen abgeleiteter Klassen manipuliert werden, wodurch Code-Wiederverwendung erreicht wird.
- ErweiterbarkeitWenn eine neue abgeleitete Klasse hinzugefügt wird, muss Code, der von der Basisklasse abhängt, nicht geändert werden; man muss nur sicherstellen, dass die neue Klasse die virtuellen Funktionen korrekt überschreibt.
- EntkopplungPolymorphie ermöglicht einen modulareren Programmentwurf und reduziert die Kopplung zwischen Klassen.
Hinweise:
- Polymorphie tritt nur auf, wenn eine virtuelle Funktion über einen Basisklassenzeiger oder eine Basisklassenreferenz aufgerufen wird.
- Wenn ein Objekt einer abgeleiteten Klasse direkt verwendet wird, um eine Funktion aufzurufen, dann wird die Version in der abgeleiteten Klasse aufgerufen, nicht die Version in der Basisklasse.
- Polymorphie erfordert Laufzeit-Typinformationen (RTTI), was den Programm-Overhead erhöhen kann.
Beispiel 1
Betrachten wir die Anwendung der Polymorphie anhand eines einfachen Beispiels:
Beispiel 1
using namespace std;
// Basisklasse Animal
class Animal {
public:
// Virtuelle Funktion sound, die eine Schnittstelle für verschiedene Tiere bereitstellt, um Geräusche zu machen
virtual void sound() const {
cout << "Ein Tier macht ein Geräusch" << endl;
}
// Virtueller Destruktor stellt sicher, dass Objekte abgeleiteter Klassen ordnungsgemäß zerstört werden
virtual ~Animal() {
cout << "Tier zerstört" << endl;
}
};
// Abgeleitete Klasse Dog, die von Animal erbt
class Dog : public Animal {
public:
// Die Methode sound überschreiben
void sound() const⟦⟧ override {
cout << "Hund bellt" << endl;
}
~Dog() {
cout << "Dog zerstört" << endl;
}
};
// Abgeleitete Klasse Cat, die von Animal erbt
class Cat : public Animal {
public:
// Die Methode sound überschreiben
void sound() const⟦⟧ override {
cout << "Cat miaut" << endl;
}
~Cat() {
cout << "Cat zerstört" << endl;
}
};
// Polymorphismus testen
int main() {
Animal* animalPtr; // Zeiger auf Basisklasse
// Dog-Objekt erstellen und mit einem Animal-Zeiger darauf zeigen
animalPtr = new Dog();
animalPtr->sound(); // sound-Methode von Dog aufrufen
delete animalPtr; // Speicher freigeben, Destruktoren von Dog und Animal aufrufen
// Cat-Objekt erstellen und mit einem Animal-Zeiger darauf zeigen
animalPtr = new Cat();
animalPtr->sound(); // sound-Methode von Cat aufrufen
delete animalPtr; // Speicher freigeben, Destruktoren von Cat und Animal aufrufen
return 0;
}
Die Ausgabe der Programmausführung ist:
Dog barks Dog destroyed Animal destroyed Cat meows Cat destroyed Animal destroyed
Code-Erklärung
BasisklasseAnimal:
AnimalDie Klasse definiert eine virtuelle Funktionsound(), die eine virtuelle Funktion ist (virtual), die verwendet wird, um das Verhalten von Tieren, die Geräusche machen, darzustellen.~Animal()Ein virtueller Destruktor stellt sicher, dass beim Freigeben eines Objekts einer abgeleiteten Klasse, auf das ein Basisklassenzeiger zeigt, der Destruktor der abgeleiteten Klasse korrekt aufgerufen wird, wodurch Speicherlecks vermieden werden.
abgeleitete KlasseDogundCat:
DogundCatAlle Klassen erben vonAnimalKlassen leiten sich davon ab, und jede implementiertsound()Methode.Dogvonsound()Ausgabe "Dog barks";Catvonsound()Gibt "Cat meows" aus. Dadurch zeigt dieselbe Methode (sound()) in verschiedenen Klassen unterschiedliche Verhaltensweisen.
main-Funktionmain():
- Erstelle einen Zeiger auf eine Basisklasse
animalPtr。 - Nutzung
new Dog()ErstelleDogObjekt, weisen Sie seine Adresse zuanimalPtr. Zu diesem Zeitpunkt der AufrufanimalPtr->sound()Es wird "Dog barks" ausgeben, weilanimalPtrzeigt tatsächlich aufDogObjekt. - release
Dogwenn das Objekt, zuerst aufrufenDogDestruktor, dann aufrufenAnimalden Destruktor von. - Nutzung
new Cat()ErstelleCatObjekt und zuweisen ananimalPtr, dann aufrufenanimalPtr->sound(), gibt "Cat meows" aus, was polymorphes Verhalten zeigt.
Schlüsselkonzepte
virtuelle Funktion: Durch die Verwendung in der Basisklasse
virtualSchlüsselwort, um virtuelle Funktionen zu deklarieren, können abgeleitete Klassen diese Funktion überschreiben, sodass zur Laufzeit die richtige Funktion basierend auf dem Objekttyp aufgerufen wird.dynamische Bindung: C++-Polymorphismus wird durch dynamische Bindung implementiert. Zur Laufzeit wird der Basisklassenzeiger
animalPtrabhängig vom tatsächlichen Typ des Objekts, auf das er zeigt (DogoderCat) die entsprechende aufrufensound()Methode.virtueller DestruktorIn einer Basisklasse mit polymorphem Verhalten sollte der Destruktor deklariert werden als
virtual, um sicherzustellen, dass beim Löschen eines Objekts einer abgeleiteten Klasse der Destruktor der abgeleiteten Klasse aufgerufen wird, wodurch Ressourcenlecks vermieden werden.
Beispiel 2
Im folgenden Beispiel implementieren wir eine gemeinsame Basisklasse Shape und zwei abgeleitete Klassen, Rectangle und Triangle, mittels Polymorphie.
Das Aufrufen verschiedener Methoden abgeleiteter Klassen über einen Basisklassenzeiger demonstriert das Merkmal der dynamischen Bindung von Polymorphie.
Beispiel 2
Wenn der obige Code kompiliert und ausgeführt wird, liefert er die folgenden Ergebnisse:
Rectangle Area: Rectangle class area: 70 Triangle Area: Triangle class area: 25
Codeanalyse
Definition der Klasse Shape:
Shapeist eine abstrakte Basisklasse, die eine virtuelle Funktion definiertarea()。area()ist eine virtuelle Funktion zur Berechnung der Fläche und verwendet dasvirtualSchlüsselwort, damit in abgeleiteten Klassen die Funktion überschrieben werden kann, wodurch Polymorphie erreicht wird.widthundheightJaprotectedEigenschaft, kann nurShapein der Klasse und ihren abgeleiteten Klassen zugegriffen werden.
// 基类 Shape,表示形状
class Shape {
protected:
int width, height; // 宽度和高度
public:
// 构造函数,带有默认参数
Shape(int a = 0, int b = 0) : width(a), height(b) { }
// 虚函数 area,用于计算面积
virtual int area() {
cout << "Shape class area: " << endl;
return 0;
}
};
Definition der Rectangle-Klasse:
RectangleerbtShapeKlasse und überschriebarea()Methode berechnet die Fläche des Rechtecks.area()Die Methode verwendetoverrideSchlüsselwort, das angibt, dass dies eine Überschreibung der Basisklasse istShapevonarea()Methodenüberschreibung.Rectangle::area()returnwidth * height, d. h. die Fläche des Rechtecks.
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Rectangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算矩形面积
int area() override {
cout << "Rectangle class area: " << endl;
return width * height;
}
};Definition der Triangle-Klasse:
Triangledie Klasse erbt auch vonShape, und überschreibtarea()Methode, die zur Berechnung der Fläche des Rechtecks verwendet wird.Triangle::area()returnwidth * height / 2, dies ist die Formel für die Fläche eines Dreiecks.
// 派生类 Triangle,表示三角形
class Triangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Triangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算三角形面积
int area() override {
cout << "Triangle class area: " << endl;
return (width * height / 2);
}
};
Polymorphes Verhalten in der main-Funktion:
- Ein Zeiger auf die Basisklasse wird definiert
shape, dieser Zeiger kann auf jedesShapeKlassenobjekt oder ein Objekt seiner abgeleiteten Klassen zeigen. - Zuerst richten Sie
shapeden Zeiger aufRectangleObjektrec, dann aufrufenshape->area(). Daarea()eine virtuelle Funktion ist, und an dieser Stelle wird er dynamisch anRectangle::area()gebunden, wodurch die Fläche des Rechtecks ausgegeben wird. - Dann richten Sie
shapeden Zeiger aufTriangleObjekttri, rufen Sieshape->area()wird dynamisch gebunden anTriangle::area(), gibt die Fläche des Dreiecks aus.
// 主函数
int main() {
Shape *shape; // 基类指针
Rectangle rec(10, 7); // 矩形对象
Triangle tri(10, 5); // 三角形对象
// 将基类指针指向矩形对象,并调用 area 函数
shape = &rec;
cout << "Rectangle Area: " << shape->area() << endl;
// 将基类指针指向三角形对象,并调用 area 函数
shape = &tri;
cout << "Triangle Area: " << shape->area() << endl;
return 0;
}
Schlüsselkonzepte
virtuelle Funktion: in der Basisklasse
Shapevirtuelle Funktionen werden definiert inarea(). Die Rolle einer virtuellen Funktion besteht darin, abgeleiteten Klassen zu ermöglichen, diese Funktion zu überschreiben und zur Laufzeit die passende Funktionsimplementierung basierend auf dem tatsächlichen Objekttyp, auf den der Zeiger zeigt, aufzurufen.dynamische Bindung: weil
area()ist eine virtuelle Funktion,shape->area()beim Aufruf, zur Laufzeit, basierend aufshapetatsächlichen Typ des Objekts, auf das er zeigt (RectangleoderTriangle), um die entsprechendearea()Implementierung aufzurufen. Dieser Mechanismus, bei dem zur Laufzeit entschieden wird, welche Funktion aufgerufen wird, heißt dynamische Bindung und ist der Kern der Polymorphie.Polymorphie von Basisklassenzeigern: der Basisklassenzeiger
shapekann auf jedes Objekt zeigen, das abgeleitet ist vonShapedes Objekts. Wennshapebeim Zeigen auf unterschiedliche Objekte abgeleiteter Klassen, das Aufrufenshape->area()wird unterschiedliche Verhaltensweisen erzeugen, was die Eigenschaften der Polymorphie widerspiegelt.- kann eine Implementierung in der Basisklasse haben. Normalerweise bieten virtuelle Funktionen eine Standardimplementierung in der Basisklasse, aber abgeleitete Klassen können wählen, sie zu überschreiben.
- dynamische Bindung: zur Laufzeit die entsprechende Funktionsversion basierend auf dem tatsächlichen Typ des Objekts aufrufen.
- Optionales ÜberschreibenAbgeleitete Klassen können virtuelle Funktionen optional überschreiben, aber es ist nicht zwingend erforderlich.
- muss in der Basisklasse deklariert werden als
= 0, was anzeigt, dass es keine Implementierung gibt, und abgeleitete Klassen müssen sie überschreiben. - Abstrakte Klasse: Eine Klasse, die reine virtuelle Funktionen enthält, kann nicht direkt instanziiert werden; ein Objekt kann nur erstellt werden, wenn alle reinen virtuellen Funktionen von einer abgeleiteten Klasse implementiert werden.
- SchnittstellendefinitionReine virtuelle Funktionen werden normalerweise verwendet, um Schnittstellen zu definieren, sodass abgeleitete Klassen spezifische Verhaltensweisen implementieren können.
virtuelle Funktion
Virtuelle Funktionbedeutet, in der Basisklasse das Schlüsselwortvirtualzur Deklaration der Funktion zu verwenden.
Virtuelle Funktionermöglicht es Unterklassen, sie zu überschreiben, sodass zur Laufzeit die überschriebene Version der Unterklasse über einen Zeiger oder eine Referenz auf die Basisklasse aufgerufen wird, wodurch dynamische Bindung umgesetzt wird.
Was wir wollen, ist, dass an jedem Punkt im Programm die aufzurufende Funktion entsprechend dem Typ des aufgerufenen Objekts ausgewählt werden kann. Diese Operation wirddynamische Bindungoderspäte Bindung genannt.
Funktionen:
Beispiel
using namespace std;
class Animal {
public:
virtual void sound() { // Virtuelle Funktion
cout << "Ein Tier macht ein Geräusch" << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // Virtuelle Funktion überschreiben
cout << "Hund bellt" << endl;
}
};
int main() {
Animal *animal = new Dog();
animal->sound(); // Ausgabe: Dog barks
delete animal;
}
Im obigen Code ist sound eine virtuelle Funktion der Klasse Animal. Wenn sound() über den Animal*-Zeiger animal aufgerufen wird, wählt das Programm Dog::sound() basierend auf dem tatsächlichen Objekttyp (Dog) aus.
Rein virtuelle Funktion
Möglicherweise möchten Sie eine virtuelle Funktion in der Basisklasse definieren, damit Sie die Funktion in einer abgeleiteten Klasse neu definieren können, um besser zum Objekt zu passen, aber Sie können keine sinnvolle Implementierung für die virtuelle Funktion in der Basisklasse bereitstellen. In diesem Fall wird eine rein virtuelle Funktion verwendet.
Eine rein virtuelle Funktion ist eine virtuelle Funktion ohne Implementierung; in der Basisklasse wird sie mit= 0deklariert.
Eine rein virtuelle Funktion bedeutet, dass die Basisklasse eine Schnittstelle definiert, aber die spezifische Implementierung liegt in der Verantwortung der abgeleiteten Klasse.
Rein virtuelle Funktionen machen die Basisklasse zu einer abstrakten Klasse, die nicht instanziiert werden kann.
Funktionen:
Wir können die virtuelle Funktion area() in der Basisklasse wie folgt umschreiben:
= 0teilt dem Compiler mit, dass die Funktion keinen Rumpf hat; die obige virtuelle Funktion istReine virtuelle Funktion。
Vergleich von virtuellen Funktionen und reinen virtuellen Funktionen
| Funktionen | Virtuell | Reine virtuelle Funktion |
|---|---|---|
| Definition | Wird in der Basisklasse verwendetvirtualDeklaration, mit Implementierung | Wird in der Basisklasse verwendet= 0Deklaration, ohne Implementierung |
| Wird in der abgeleiteten Klasse überschrieben | Unterklassen können überschreiben | Die Unterklasse muss implementieren |
| Abstraktheit | kann die Klasse instanziieren | macht die Klasse zu einer abstrakten Klasse, die nicht instanziiert werden kann |
| Zweck | bietet Standardverhalten, das Unterklassen überschreiben können | definiert eine Schnittstelle und zwingt abgeleitete Klassen, bestimmte Verhaltensweisen zu implementieren |