Überschreiben von Methoden
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 51 von 104.
Methodenüberschreibung tritt auf, wenn eine abgeleitete Klasse ihre eigene Implementierung einer Methode bereitstellt, die bereits in der Basisklasse existiert. Dies ermöglicht es abgeleiteten Klassen, geerbtes Verhalten anzupassen, während die gleiche Methodensignatur beibehalten wird.
Um eine Methode zu überschreiben, definieren Sie einfach eine Methode in der abgeleiteten Klasse mit genau demselben Namen, Rückgabetyp und denselben Parametern:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Ausgabe: Woof!Die Version der abgeleiteten Klasse verdeckt die Version der Basisklasse. Sie können jedoch weiterhin über den Bereichsauflösungsoperator auf die Methode der Basisklasse zugreifen:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Version der Basisklasse aufrufen
std::cout << "Woof!" << std::endl;
}
};Es gibt eine wichtige Einschränkung bei dieser grundlegenden Form des Überschreibens. Wenn Sie über einen Basisklassen-Zeiger oder eine Referenz auf ein abgeleitetes Objekt zugreifen, wird stattdessen die Version der Basisklasse aufgerufen:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Ausgabe: Ein Geräusch (nicht Wuff!)Dies geschieht, weil der Compiler basierend auf dem Zeigertyp bestimmt, welche Methode aufgerufen werden soll, und nicht basierend auf dem tatsächlichen Objekttyp. Um echte Laufzeit-Polymorphie zu erreichen, bei der die richtige Methode basierend auf dem tatsächlichen Objekt aufgerufen wird, benötigen Sie virtuelle Funktionen, die wir in der nächsten Lektion behandeln werden.
Aufgabe
EinfachLassen Sie uns ein Benachrichtigungssystem erstellen, das das Überschreiben von Methoden (Method Overriding) in der Praxis demonstriert. Sie werden eine Basisklasse Notifier und zwei spezialisierte Benachrichtiger erstellen, die die Art und Weise der Nachrichtenzustellung anpassen.
Sie werden Ihren Code über drei Dateien organisieren:
Notifier.h: Definieren Sie eine BasisklasseNotifiermit:- Einem geschützten (protected)
std::string recipientMember - Einem Konstruktor, der einen Empfängernamen entgegennimmt und speichert
- Einer öffentlichen
send(const std::string& message)Methode, die Folgendes ausgibt:Sending to <recipient>: <message>
- Einem geschützten (protected)
EmailNotifier.h: Definieren Sie eine KlasseEmailNotifier, die öffentlich vonNotifiererbt:- Einem Konstruktor, der einen Empfänger entgegennimmt und an die Basisklasse weitergibt
- Überschreiben Sie die
send()Methode, um zuerst die Version der Basisklasse mittelsNotifier::send()aufzurufen und dann Folgendes auszugeben:[Email sent successfully]
main.cpp: Lesen Sie zwei Eingaben ein (jeweils in einer separaten Zeile):- Empfängername (String)
- Nachrichteninhalt (String)
Definieren Sie eine Klasse
SMSNotifierdirekt in der main.cpp, die öffentlich vonNotifiererbt:- Einem Konstruktor, der einen Empfänger entgegennimmt und an die Basisklasse weitergibt
- Überschreiben Sie die
send()Methode, um Folgendes auszugeben:SMS to <recipient>: <message>(wobei das Basisverhalten vollständig ersetzt wird, ohne es aufzurufen)
Erstellen Sie ein Objekt von jedem Benachrichtigungstyp (Basis
Notifier,EmailNotifierundSMSNotifier) unter Verwendung desselben Empfängers. Rufen Siesend()für jedes Objekt mit der eingegebenen Nachricht auf und geben Sie zwischen jedem Aufruf eine Trennzeile aus:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Zum Beispiel mit den Eingaben Alice und Hello there!:
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!Beachten Sie, wie EmailNotifier das Basisverhalten erweitert, indem zuerst die Methode der Elternklasse aufgerufen wird, während SMSNotifier es vollständig durch eine eigene Implementierung ersetzt. Vergessen Sie nicht die Header-Guards und binden Sie die entsprechenden Header in jeder Datei ein.
Spickzettel
Method Overriding (Methoden-Überschreiben) ermöglicht es einer abgeleiteten Klasse, eine eigene Implementierung einer Methode der Basisklasse bereitzustellen, wobei die gleiche Methodensignatur (Name, Rückgabetyp und Parameter) beibehalten wird.
Grundlegende Syntax für das Überschreiben:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Output: Woof!Die Methode der abgeleiteten Klasse verbirgt die Version der Basisklasse. Um die Methode der Basisklasse aus der abgeleiteten Klasse heraus aufzurufen, verwenden Sie den Bereichsauflösungs-Operator (Scope Resolution Operator):
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Call base class version
std::cout << "Woof!" << std::endl;
}
};Wichtige Einschränkung: Wenn auf ein abgeleitetes Objekt über einen Zeiger oder eine Referenz der Basisklasse zugegriffen wird, wird die Version der Basisklasse aufgerufen:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Some sound (not Woof!)Dies liegt daran, dass der Compiler basierend auf dem Zeigertyp bestimmt, welche Methode aufgerufen werden soll, und nicht basierend auf dem tatsächlichen Objekttyp. Für echte Laufzeit-Polymorphie verwenden Sie virtuelle Funktionen (virtual functions).
Probier es selbst
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Definiere hier die Klasse SMSNotifier, die öffentlich von Notifier erbt
// - Konstruktor, der recipient an die Basisklasse übergibt
// - Überschreibe send(), um Folgendes auszugeben: SMS to <recipient>: <message>
// (ersetze das Basisverhalten vollständig, rufe nicht die send() der Elternklasse auf)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Erstelle ein Notifier-Objekt mit recipient
// Gib "--- Base Notifier ---" aus
// Rufe send() mit message auf
// TODO: Erstelle ein EmailNotifier-Objekt mit recipient
// Gib "--- Email Notifier ---" aus
// Rufe send() mit message auf
// TODO: Erstelle ein SMSNotifier-Objekt mit recipient
// Gib "--- SMS Notifier ---" aus
// Rufe send() mit message auf
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container