Ü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
EinfachLass uns ein Benachrichtigungssystem erstellen, das das Überschreiben von Methoden in Aktion demonstriert. Du erstellst eine Notifier-Basisklasse und zwei spezialisierte Benachrichtigungsdienste, die anpassen, wie Nachrichten zugestellt werden.
Du organisierst deinen Code über drei Dateien:
Notifier.h: Definiere eineNotifier-Basisklasse mit:- Einem geschützten
std::string recipient-Member - 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
EmailNotifier.h: Definiere eineEmailNotifier-Klasse, die öffentlich vonNotifiererbt:- Einen Konstruktor, der einen Empfänger entgegennimmt und ihn an die Basisklasse weitergibt
- Überschreibe die
send()-Methode, sodass sie zuerst die Version der Basisklasse mitNotifier::send()aufruft und anschließend Folgendes ausgibt:[Email sent successfully]
main.cpp: Lies zwei Eingaben ein (jeweils in einer eigenen Zeile):- Empfängername (String)
- Nachrichteninhalt (String)
Definiere direkt in main.cpp eine
SMSNotifier-Klasse, die öffentlich vonNotifiererbt:- Einen Konstruktor, der einen Empfänger entgegennimmt und ihn an die Basisklasse weitergibt
- Überschreibe die
send()-Methode, sodass sie Folgendes ausgibt:SMS to <recipient>: <message>(das Verhalten der Basisklasse vollständig ersetzt, ohne sie aufzurufen)
Erstelle jeweils ein Objekt jedes Benachrichtigungstyps (die Basis-
Notifier-Klasse,EmailNotifierundSMSNotifier) unter Verwendung desselben Empfängers. Rufe bei jedemsend()mit der eingegebenen Nachricht auf und gib zwischen den einzelnen Aufrufen eine Trennlinie aus:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Bei den Eingaben Alice und Hello there! sieht die Ausgabe beispielsweise so aus:
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!Beachte, wie EmailNotifier das Verhalten der Basisklasse erweitert, indem es zuerst die Elternmethode aufruft, während SMSNotifier sie vollständig durch seine eigene Implementierung ersetzt. Vergiss nicht die Header-Guards und die passenden Header in jede Datei einzubinden.
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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler