C++ Polymorphismus mit Beispiel

โšก Intelligente Zusammenfassung

Polymorphismus in C++ Es ermรถglicht einer einzelnen Memberfunktion, viele Formen anzunehmen und sich je nach aufrufendem Objekt unterschiedlich zu verhalten. Es basiert auf Vererbung und tritt in zwei Varianten auf, die entweder zur Kompilierzeit oder zur Laufzeit aufgelรถst werden.

  • ๐Ÿ”ท Definition: Polymorphismus bedeutet โ€žviele Formenโ€œ und ermรถglicht es einer Memberfunktion, sich je nach aufrufendem Objekt unterschiedlich zu verhalten.
  • ๐Ÿงญ Zwei Arten: C++ bietet Kompilierzeit-Polymorphismus (statischen Polymorphismus) und Laufzeit-Polymorphismus (dynamischen Polymorphismus).
  • ๐Ÿงฉ Kompilierzeit: Funktionsรผberladung und Operatorรผberladung wรคhlen wรคhrend der Kompilierung die richtige Funktion aus.
  • ๐Ÿ” Laufzeit: Funktionsรผberschreibung und virtuelle Funktionen lรถsen den Aufruf wรคhrend der Programmausfรผhrung auf.
  • โš™๏ธ Virtuelle Funktionen: Das Schlรผsselwort virtual in einer Basisklasse ermรถglicht die spรคte Bindung รผber einen Basisklassenzeiger.
  • ๐Ÿค– KI-Unterstรผtzung: GitHub Copilot und รคhnliche Assistenten generieren รผberladene, รผberschriebene und virtuelle Funktionen aus einem kurzen Kommentar.

C++ Polymorphie

Was ist Polymorphismus in C++?

In C++Polymorphismus bewirkt, dass sich eine Memberfunktion je nach aufrufendem Objekt unterschiedlich verhรคlt. Polymorphismus ist ein griechisches Wort und bedeutet โ€žvielgestaltigโ€œ. Er tritt auf, wenn eine Hierarchie von Funktionen existiert. Unterricht Verwandtschaft durch Vererbung.

Angenommen, wir haben die Funktion makeSound(). Wenn eine Katze diese Funktion aufruft, erzeugt sie ein Miaugerรคusch. Wenn eine Kuh dieselbe Funktion aufruft, gibt sie das Mรคhgerรคusch von sich.

Polymorphismus in C++ makeSound-Beispiel

Obwohl wir eine Funktion haben, verhรคlt sie sich unter verschiedenen Umstรคnden unterschiedlich. Die Funktion hat viele Formen; daher haben wir Polymorphismus erreicht.

Nachdem die Kernidee steht, C++ ordnet dieses Verhalten in zwei unterschiedliche Kategorien ein.

Arten des Polymorphismus

C++ unterstรผtzt zwei Arten von Polymorphismus:

  • Polymorphismus zur Kompilierungszeit und
  • Laufzeitpolymorphismus.

Arten von Polymorphismus in C++

Die erste Kategorie lรถst jeden Funktionsaufruf auf, bevor das Programm รผberhaupt ausgefรผhrt wird.

Kompilierzeitpolymorphismus

Sie rufen die รผberladenen Funktionen auf, indem Sie die Anzahl und den Typ der Argumente abgleichen. Die Informationen sind wรคhrend der Kompilierungszeit vorhanden. Das bedeutet, dass die C++ Der Compiler wรคhlt zur Kompilierzeit die richtige Funktion aus.

Polymorphismus zur Kompilierungszeit wird durch Funktions- und Operatorรผberladung erreicht.

Funktionsรผberlastung

Eine Funktionsรผberladung tritt auf, wenn wir viele Funktionen mit รคhnlichen Namen, aber unterschiedlichen Argumenten haben. Die Argumente kรถnnen sich hinsichtlich der Anzahl oder Art unterscheiden.

Beispiel 1

#include <iostream> 
using namespace std;

void test(int i) {
	cout << " The int is " << i << endl;
}
void test(double  f) {
	cout << " The float is " << f << endl;
}
void test(char const *ch) {
	cout << " The char* is " << ch << endl;
}

int main() {
	test(5);
	test(5.5);
	test("five");
	return 0;
}

Ausgang:

C++ Funktionsรผberladung Beispiel 1 Ausgabe

Hier ist ein Screenshot des Codes:

C++ Funktionsรผberladung Beispiel 1 Code

Code Erlรคuterung:

  1. Fรผgen Sie die iostream-Header-Datei in unseren Code ein. Wir werden seine Funktionen nutzen kรถnnen.
  2. Fรผgen Sie den std-Namespace in unseren Code ein. Wir kรถnnen seine Klassen verwenden, ohne es aufzurufen.
  3. Erstellen Sie eine Funktion namens test, die einen ganzzahligen Parameter i akzeptiert. Das { markiert den Anfang des Hauptteils des Funktionstests.
  4. Anweisung, die ausgefรผhrt werden soll, wenn der obige Funktionstest aufgerufen/aufgerufen wird.
  5. Ende des Hauptteils des obigen Funktionstests.
  6. Erstellen Sie eine Funktion namens test, die einen Float-Parameter f akzeptiert. Das { markiert den Anfang des Hauptteils des Funktionstests.
  7. Anweisung, die ausgefรผhrt werden soll, wenn der obige Funktionstest aufgerufen/aufgerufen wird.
  8. Ende des Hauptteils des obigen Funktionstests.
  9. Erstellen Sie eine Funktion namens test, die einen Zeichenparameter ch akzeptiert. Das { markiert den Anfang des Hauptteils des Funktionstests.
  10. Anweisung, die ausgefรผhrt werden soll, wenn der obige Funktionstest aufgerufen/aufgerufen wird.
  11. Ende des Hauptteils des obigen Funktionstests.
  12. Rufen Sie die Funktion main() auf. Das { markiert den Anfang des Funktionskรถrpers.
  13. Rufen Sie den Funktionstest auf und รผbergeben Sie ihm 5 als Wert des Arguments. Dadurch wird die Testfunktion aufgerufen, die ein ganzzahliges Argument akzeptiert, also die erste Testfunktion.
  14. Rufen Sie den Funktionstest auf und รผbergeben Sie ihm 5.5 als Wert des Arguments. Dadurch wird die Testfunktion aufgerufen, die ein Float-Argument akzeptiert, also die zweite Testfunktion.
  15. Rufen Sie die Funktion test auf und รผbergeben Sie ihr fรผnf als Wert des Arguments. Dadurch wird die Testfunktion aufgerufen, die ein Zeichenargument akzeptiert, also die dritte Testfunktion.
  16. Das Programm muss einen Wert zurรผckgeben, wenn es erfolgreich ausgefรผhrt wird.
  17. Das Ende des Hauptteils der main()-Funktion.

Wir haben drei Funktionen mit demselben Namen, aber unterschiedlichen Argumenttypen. Wir haben Polymorphismus erreicht.

Operator รœberlastung

In Operator Overloading definieren wir eine neue Bedeutung fรผr eine C++ OperatorEs รคndert auch die Funktionsweise des Operators. Beispielsweise kรถnnen wir den +-Operator so definieren, dass er zwei Zeichenketten verkettet. Wir kennen ihn als Additionsoperator fรผr numerische Werte. Nach unserer Definition addiert er, wenn er zwischen ganzen Zahlen steht, diese. Steht er zwischen Zeichenketten, verkettet er sie.

Beispiel 2

#include<iostream> 
using namespace std;

class ComplexNum {
private:
	int real, over;
public:
	ComplexNum(int rl = 0, int ov = 0) {
		real = rl;   
		over = ov; 
	}

	ComplexNum operator + (ComplexNum const &obj) {
		ComplexNum result;
		result.real = real + obj.real;
		result.over = over + obj.over;
		return result;
	}
	void print() { 
		cout << real << " + i" << over << endl; 
	}
};
int main()
{
	ComplexNum c1(10, 2), c2(3, 7);
	ComplexNum c3 = c1+c2;
	c3.print();
}

Ausgang:

C++ Operatorรผberladung Beispiel 2 Ausgabe

Hier ist ein Screenshot des Codes:

C++ Operatorรผberladung Beispiel 2 Code

C++ Operatorรผberladung Beispiel 2 Ergebnis

Code Erlรคuterung:

  1. Binden Sie die iostream-Header-Datei in unser Programm ein, um dessen Funktionen nutzen zu kรถnnen.
  2. Fรผgen Sie den std-Namespace in unser Programm ein, um seine Klassen zu verwenden, ohne ihn aufzurufen.
  3. Erstellen Sie eine Klasse mit dem Namen ComplexNum. Die { markiert den Anfang des Klassenkรถrpers.
  4. Verwenden Sie den Zugriffsmodifikator โ€žprivateโ€œ, um Variablen als privat zu markieren, was bedeutet, dass auf sie nur innerhalb der Klasse zugegriffen werden kann.
  5. Definieren Sie zwei ganzzahlige Variablen, reell und รผber.
  6. Verwenden Sie den Zugriffsmodifikator โ€žpublicโ€œ, um den Konstruktor als รถffentlich zu kennzeichnen. Dies bedeutet, dass er auch von auรŸerhalb der Klasse zugรคnglich ist.
  7. Erstellen Sie den Klassenkonstruktor und initialisieren Sie die Variablen.
  8. Initialisieren Sie den Wert der Variablen real.
  9. Initialisieren Sie den Wert der Variablen รผber.
  10. Ende des Konstruktorkรถrpers.
  11. Wir mรผssen die Bedeutung des +-Operators รผberschreiben.
  12. Erstellen Sie den Datentyp โ€žErgebnisโ€œ vom Typ โ€žComplexNumโ€œ.
  13. Verwenden Sie den Operator + mit komplexen Zahlen. Diese Zeile addiert den Realteil einer Zahl zum Realteil einer anderen Zahl.
  14. Verwenden Sie den Operator + mit komplexen Zahlen. Diese Zeile addiert den Imaginรคrteil einer Zahl zum Imaginรคrteil einer anderen Zahl.
  15. Bei erfolgreicher Ausfรผhrung gibt das Programm den Wert der Variablen result zurรผck.
  16. Ende der Definition der neuen Bedeutung des +-Operators, d. h. รœberladung.
  17. Rufen Sie die print()-Methode auf.
  18. Drucken Sie die neue komplexe Zahl nach der Addition auf der Konsole.
  19. Ende des Hauptteils der print()-Funktion.
  20. Ende des Hauptteils der ComplexNum-Klasse.
  21. Rufen Sie die Funktion main() auf.
  22. รœbergeben Sie die Werte der zu addierenden reellen und komplexen Teile. Der erste Teil von c1 wird zum ersten Teil von c2 addiert, also 10+3. Der zweite Teil von c1 wird zum zweiten Teil von c addiert, also 2+7.
  23. Fรผhren Sie eine Operation mit dem รผberladenen +-Operator aus und speichern Sie das Ergebnis in der Variablen c3.
  24. Geben Sie den Wert der Variablen c3 auf der Konsole aus.
  25. Ende des Hauptteils der Funktion main().

Wรคhrend bei der Kompilierzeit-Polymorphie frรผhzeitig entschieden wird, verschiebt die zweite Kategorie die Wahl bis zur Ausfรผhrung.

Laufzeitpolymorphismus

Dies geschieht, wenn die Methode eines Objekts zur Laufzeit und nicht zur Kompilierungszeit aufgerufen/aufgerufen wird. Laufzeitpolymorphismus wird durch Funktionsรผberschreibung erreicht. Die aufzurufende/aufzurufende Funktion wird zur Laufzeit festgelegt.

Funktion รผberschreiben

Funktionsรผberschreibungen treten auf, wenn einer Funktion der Basisklasse eine neue Definition in einer abgeleiteten Klasse gegeben wird. Zu diesem Zeitpunkt kรถnnen wir sagen, dass die Basisfunktion รผberschrieben wurde.

Beispielsweise:

#include <iostream>
using namespace std;
class Mammal {

public:
	void eat() {

		cout << "Mammals eat...";
	}

};

class Cow: public Mammal {

public:
	void eat() {

		cout << "Cows eat grass...";
	}
};
int main(void) {

	Cow c = Cow();

	c.eat();

	return 0;

}

Ausgang:

C++ Funktion, die die Ausgabe รผberschreibt

Hier ist ein Screenshot des Codes:

C++ Funktionsรผberschreibungscode

Code Erlรคuterung:

  1. Importieren Sie die iostream-Header-Datei in unser Programm, um dessen Funktionen zu nutzen.
  2. Fรผgen Sie den std-Namespace in unser Programm ein, um seine Klassen zu verwenden, ohne ihn aufzurufen.
  3. Erstellen Sie eine Klasse mit dem Namen Mammal. Das { markiert den Anfang des Klassenkรถrpers.
  4. Verwenden Sie den Modifikator fรผr den รถffentlichen Zugriff, um die Funktion, die wir erstellen mรถchten, als รถffentlich zugรคnglich festzulegen. Es wird von auรŸerhalb dieser Klasse zugรคnglich sein.
  5. Erstellen Sie eine รถffentliche Funktion namens eat. Das { markiert den Anfang des Funktionskรถrpers.
  6. Gibt die der cout-Funktion hinzugefรผgte Anweisung aus, wenn die Funktion eat() aufgerufen wird.
  7. Das Ende des Funktionskรถrpers eat().
  8. Ende des Hauptteils der Klasse Sรคugetier.
  9. Erstellen Sie eine Klasse mit dem Namen Cow, die die Mammal-Klasse erbt. Cow ist die abgeleitete Klasse, wรคhrend Mammal die Basisklasse ist. Das { markiert den Anfang dieser Klasse.
  10. Verwenden Sie den Modifikator โ€žpublic accessโ€œ, um die Funktion, die wir erstellen mรถchten, als รถffentlich zugรคnglich zu markieren. Es wird von auรŸerhalb dieser Klasse zugรคnglich sein.
  11. รœberschreiben Sie die Funktion eat(), die in der Basisklasse definiert wurde. Das { markiert den Anfang des Funktionskรถrpers.
  12. Die Anweisung, die beim Aufruf dieser Funktion auf der Konsole ausgegeben werden soll.
  13. Ende des Hauptteils der Funktion eat().
  14. Ende des Rumpfes der Klasse Cow.
  15. Rufen Sie die Funktion main() auf. Das { markiert den Anfang des Hauptteils dieser Funktion.
  16. Erstellen Sie eine Instanz der Cow-Klasse und geben Sie ihr den Namen c.
  17. Rufen Sie die in der Cow-Klasse definierte Funktion eat() auf.
  18. Das Programm muss bei erfolgreichem Abschluss einen Wert zurรผckgeben.
  19. Ende der main()-Funktion.

C++ Virtuelle Funktion

Eine virtuelle Funktion ist eine weitere Mรถglichkeit, Laufzeitpolymorphismus zu implementieren in C++. Es handelt sich um eine spezielle Funktion, die in einer Basisklasse definiert und in der abgeleiteten Klasse neu definiert wird. Um eine virtuelle Funktion zu deklarieren, sollten Sie das Schlรผsselwort virtual verwenden. Das Schlรผsselwort sollte der Deklaration der Funktion in der Basisklasse vorangehen.

Wenn eine virtuelle Funktionsklasse geerbt wird, definiert die virtuelle Klasse die virtuelle Funktion entsprechend ihren Anforderungen neu. Zum Beispiel:

#include <iostream>  
using namespace std;
class ClassA {
		public:
		virtual void show() {
			cout << "The show() function in base class invoked..." << endl;
		}
	};
	class ClassB :public ClassA {
	public:
		void show() 	{
			cout << "The show() function in derived class invoked...";
		}
	};
	int main() {
		ClassA* a;   
		ClassB b;
		a = &b;
		a->show();      
	}

Ausgang:

C++ virtuelle Funktionsausgabe

Hier ist ein Screenshot des Codes:

C++ virtueller Funktionscode

Code Erlรคuterung:

  1. Fรผgen Sie die iostream-Headerdatei in den Code ein, um ihre Funktionen zu nutzen.
  2. Fรผgen Sie den std-Namespace in unseren Code ein, um seine Klassen zu verwenden, ohne ihn aufzurufen.
  3. Erstellen Sie eine Klasse mit dem Namen ClassA.
  4. Verwenden Sie den Modifikator fรผr den รถffentlichen Zugriff, um ein Klassenmitglied als รถffentlich zugรคnglich zu markieren.
  5. Erstellen Sie eine virtuelle Funktion namens show(). Es wird eine รถffentliche Veranstaltung sein.
  6. Der Text, der gedruckt werden soll, wenn show() aufgerufen wird. Das endl ist ein C++ Schlรผsselwort, das Zeilenende bedeutet. Es bewegt den Mauszeiger zur nรคchsten Zeile.
  7. Ende des Kรถrpers der virtuellen Funktion show().
  8. Ende des Rumpfes der Klasse ClassA.
  9. Erstellen einer neuen Klasse mit dem Namen ClassB, die die Klasse ClassA erbt. KlasseA wird zur Basisklasse, wรคhrend KlasseB zur abgeleiteten Klasse wird.
  10. Verwenden Sie den Modifikator fรผr den รถffentlichen Zugriff, um ein Klassenmitglied als รถffentlich zugรคnglich zu markieren.
  11. Definieren Sie die in der Basisklasse abgeleitete virtuelle Funktion show() neu.
  12. Der Text, der auf der Konsole gedruckt werden soll, wenn die in der abgeleiteten Klasse definierte Funktion show() aufgerufen wird.
  13. Ende des Hauptteils der show()-Funktion.
  14. Ende des Hauptteils der abgeleiteten Klasse, ClassB.
  15. Rufen Sie die Funktion main() auf. Die Programmlogik sollte in den Hauptteil eingefรผgt werden.
  16. Erstellen Sie eine Zeigervariable mit dem Namen a. Es zeigt auf die Klasse mit dem Namen ClassA.
  17. Erstellen Sie eine Instanz der Klasse mit dem Namen ClassB. Die Instanz erhรคlt den Namen b.
  18. Ordnen Sie die in der Adresse b gespeicherten Werte der Variablen a zu.
  19. Rufen Sie die in der abgeleiteten Klasse definierte Funktion show() auf. Die spรคte Bindung wurde implementiert.
  20. Ende des Hauptteils der main()-Funktion.

Nachdem beide Mechanismen erlรคutert wurden, werden sie in der folgenden Tabelle einander gegenรผbergestellt.

Polymorphismus zur Kompilierungszeit vs. Laufzeitpolymorphismus

Hier sind die Hauptunterschiede zwischen den beiden:

Polymorphismus zur Kompilierungszeit Laufzeitpolymorphismus
Es wird auch als frรผhe Bindung oder statischer Polymorphismus bezeichnet. Es wird auch als spรคte/dynamische Bindung oder dynamischer Polymorphismus bezeichnet.
Die Methode wird wรคhrend der Kompilierungszeit aufgerufen/aufgerufen Die Methode wird zur Laufzeit aufgerufen/aufgerufen
Implementiert durch Funktionsรผberladung und Operatorรผberladung Implementiert รผber Methodenรผberschreibung und virtuelle Funktionen
Beispiel: Methodenรผberladung. Viele Methoden haben mรถglicherweise รคhnliche Namen, aber eine unterschiedliche Anzahl oder Art von Argumenten Beispiel: Methodenรผberschreibung. Viele Methoden haben mรถglicherweise einen รคhnlichen Namen und denselben Prototyp.
Schnellere Ausfรผhrung, da die Methodenfindung wรคhrend der Kompilierzeit erfolgt. Langsamere Ausfรผhrung, da die Methodenfindung erst zur Laufzeit erfolgt.
Less Da zur Kompilierungszeit alles bekannt ist, ist Flexibilitรคt bei der Problemlรถsung gegeben. Da die Methoden wรคhrend der Laufzeit ermittelt werden, ist viel Flexibilitรคt beim Lรถsen komplexer Probleme gegeben.

Hรคufig gestellte Fragen

Durch รœberladen werden mehrere Funktionen mit demselben Namen, aber unterschiedlichen Parametern definiert, die ohne Vererbung zur Kompilierzeit aufgelรถst werden. Durch รœberschreiben wird eine geerbte Basisklassenfunktion mit derselben Signatur in einer abgeleiteten Klasse neu definiert; die Auflรถsung erfolgt zur Laufzeit, wenn die Funktion als virtuell deklariert wird.

Eine rein virtuelle Funktion wird mit `= 0` deklariert und hat keinen Rumpf in der Basisklasse. Jede Klasse, die eine solche Funktion enthรคlt, wird zu einer absoluten virtuellen Funktion.tract-Klasse, die nicht direkt instanziiert werden kann und daher abgeleitete Klassen zwingt, ihre eigene Implementierung bereitzustellen.

Wenn eine Klasse virtuelle Funktionen deklariert, erstellt der Compiler eine virtuelle Tabelle (vtable) mit Funktionszeigern. Jedes Objekt speichert einen versteckten Zeiger auf seine Klassen-vtable, sodass ein Basisklassenzeiger zur Laufzeit durch spรคte Bindung die korrekte รผberschriebene Funktion aufruft.

Ja. Templates ermรถglichen es einer Funktion oder Klasse, mit vielen Datentypen zu arbeiten, wobei der Compiler wรคhrend der Kompilierung typspezifischen Code generiert. Diese generische Programmierung ist eine Form des statischen Polymorphismus, die die Funktions- und Operatorรผberladung ergรคnzt.

Wenn Sie ein abgeleitetes Objekt รผber einen Basisklassenzeiger lรถschen, garantiert nur ein virtueller Destruktor, dass auch der Destruktor des abgeleiteten Objekts ausgefรผhrt wird. Andernfalls ist die Bereinigung unvollstรคndig und es kommt zu Speicherlecks. Durch die Deklaration des Basisklassendestruktors als virtuell verhรคlt sich die Objektzerstรถrung polymorph.

Polymorphismus ermรถglicht es einer Schnittstelle, viele Objekttypen zu verwalten, wodurch doppelter Code vermieden und die Wartung vereinfacht wird. Er unterstรผtzt flexible, erweiterbare Designs, bei denen neue abgeleitete Klassen ohne ร„nderungen am bestehenden Code eingebunden werden kรถnnen, und ermรถglicht leistungsstarke Muster, die auf Basisklassenzeigern und -referenzen basieren.

Ja. KI-Programmierassistenten wandeln kurze Kommentare in รผberladene Funktionen, virtuelle Basisklassen und abgeleitete รœberschreibungen um. Sie erledigen wiederkehrenden Boilerplate-Code schnell, dennoch sollten Sie vor dem Kompilieren des generierten Codes virtuelle Schlรผsselwรถrter, Funktionssignaturen und die Sicherheit des Destruktors รผberprรผfen.

Ja. GitHub-Copilot Es schlรคgt wรคhrend der Eingabe virtuelle Funktionsdeklarationen, รœberschreibungen abgeleiteter Klassen und Operatorรผberladungen vor. Dies beschleunigt den Boilerplate-Code fรผr Vererbungshierarchien. Sie sollten jedoch vor dem Kompilieren das Schlรผsselwort `virtual`, รผbereinstimmende Signaturen und das korrekte Verhalten der spรคten Bindung รผberprรผfen.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: