Rein virtuelle Funktionen
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 59 von 104.
Eine rein virtuelle Funktion ist eine virtuelle Funktion, die keine Implementierung in der Basisklasse hat. Sie wird deklariert, indem der Funktionsdeklaration = 0 zugewiesen wird. Dies teilt dem Compiler mit, dass abgeleitete Klassen ihre eigene Implementierung bereitstellen müssen.
class Shape {
public:
virtual double area() = 0; // Rein virtuelle Funktion
virtual ~Shape() = default;
};Im Gegensatz zu regulären virtuellen Funktionen, die ein Standardverhalten bieten, definieren rein virtuelle Funktionen einen Vertrag: Jede konkrete abgeleitete Klasse muss diese Funktion implementieren, um instanziierbar zu sein. Die Basisklasse deklariert lediglich was getan werden muss, nicht wie.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
};Sowohl Circle als auch Rectangle müssen area() implementieren, da diese in Shape rein virtuell ist. Wenn eine abgeleitete Klasse nicht alle rein virtuellen Funktionen implementiert, wird sie ebenfalls abstrakt und kann nicht instanziiert werden.
Rein virtuelle Funktionen sind essenziell, wenn die Basisklasse keine sinnvolle Standardimplementierung bereitstellen kann. Jede Form hat einen Flächeninhalt, aber es gibt keine vernünftige Weise, den „Flächeninhalt einer generischen Form“ zu berechnen, ohne den spezifischen Formtyp zu kennen.
Aufgabe
EinfachLassen Sie uns ein Zahlungsverarbeitungssystem bauen, das demonstriert, wie rein virtuelle Funktionen einen Vertrag über verschiedene Zahlungsmethoden hinweg erzwingen. Sie werden eine abstrakte Basisklasse erstellen, die definiert, was jeder Zahlungsanprozessor tun muss, und dann konkrete Zahlungstypen implementieren, die diesen Vertrag erfüllen.
Sie werden Ihren Code über drei Dateien organisieren:
PaymentProcessor.h: Definieren Sie eine abstrakte KlassePaymentProcessor, die als Blaupause für alle Zahlungsmethoden dient. Diese Klasse sollte Folgendes haben:- Ein geschütztes (protected)
std::string accountIdMitglied - Einen Konstruktor, der die Account-ID initialisiert
- Eine rein virtuelle Methode
processPayment(double amount)— jeder Zahlungstyp muss diese unterschiedlich implementieren - Eine rein virtuelle Methode
getProcessorName(), die einenstd::stringzurückgibt - Einen virtuellen Destruktor
- Ein geschütztes (protected)
PaymentMethods.h: Implementieren Sie zwei konkrete Zahlungsanprozessoren, die vonPaymentProcessorerben:CreditCardProcessor:- Ein privates
double feePercentageMitglied (der Transaktionsgebührensatz) - Einen Konstruktor, der die Account-ID und den Gebührensatz entgegennimmt
- Implementieren Sie
processPayment(), um die Gebühr zu berechnen (amount * feePercentage / 100) und geben Sie dann Folgendes aus:Credit Card [<accountId>]: Charged Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;amount> (Fee: Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)lt;fee>) - Implementieren Sie
getProcessorName(), um"CreditCard"zurückzugeben
BankTransferProcessor:- Ein privates
std::string bankNameMitglied - Einen Konstruktor, der die Account-ID und den Banknamen entgegennimmt
- Implementieren Sie
processPayment(), um Folgendes auszugeben:Bank Transfer [<accountId>] via <bankName>: Transferred Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>lt;amount> - Implementieren Sie
getProcessorName(), um"BankTransfer"zurückzugeben
- Ein privates
main.cpp: Lesen Sie vier Eingaben ein (jede in einer separaten Zeile):- Kreditkarten-Account-ID
- Kreditkarten-Gebührensatz (double)
- Bankkonto-ID
- Bankname
Erstellen Sie beide Zahlungsanprozessoren und speichern Sie diese in einem Array von
PaymentProcessor*Pointern. Verarbeiten Sie eine Zahlung von100.0über jeden Prozessor und zeigen Sie den Prozessornamen vor jeder Transaktion an:Processing with <processorName>: <processPayment output>Geben Sie eine Leerzeile zwischen den Prozessoren aus. Räumen Sie Ihre dynamisch zugewiesenen Objekte auf, wenn Sie fertig sind.
Zum Beispiel mit den Eingaben CC-4521, 2.5, BA-7890 und National Bank:
Processing with CreditCard:
Credit Card [CC-4521]: Charged $100 (Fee: $2.5)
Processing with BankTransfer:
Bank Transfer [BA-7890] via National Bank: Transferred $100Da PaymentProcessor rein virtuelle Funktionen hat, können Sie ihn nicht direkt instanziieren — nur die konkreten Implementierungen, die den Vertrag erfüllen, können erstellt werden. Dies stellt sicher, dass jede Zahlungsmethode ihre eigene spezifische Verarbeitungslogik bereitstellt.
Spickzettel
Eine rein virtuelle Funktion wird deklariert, indem einer virtuellen Funktion = 0 zugewiesen wird, was anzeigt, dass sie keine Implementierung in der Basisklasse hat:
class Shape {
public:
virtual double area() = 0; // Rein virtuelle Funktion
virtual ~Shape() = default;
};Rein virtuelle Funktionen erstellen einen Vertrag, den abgeleitete Klassen implementieren müssen. Die Basisklasse definiert, was getan werden muss, nicht wie.
Abgeleitete Klassen müssen alle rein virtuellen Funktionen überschreiben, um instanziierbar zu sein:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
};Wenn eine abgeleitete Klasse nicht alle rein virtuellen Funktionen implementiert, wird sie ebenfalls abstrakt und kann nicht instanziiert werden.
Verwenden Sie rein virtuelle Funktionen, wenn die Basisklasse keine sinnvolle Standardimplementierung bereitstellen kann.
Probier es selbst
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Eingaben lesen
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Erstelle ein Array aus PaymentProcessor* Pointern mit 2 Elementen
// TODO: Erstelle CreditCardProcessor und BankTransferProcessor Objekte
// und speichere sie im Array
// TODO: Durchlaufe das Array in einer Schleife und für jeden Prozessor:
// 1. Gib "Processing with <processorName>:" aus
// 2. Rufe processPayment mit dem Betrag 100.0 auf
// 3. Gib eine Leerzeile zwischen den Prozessoren aus (nicht nach dem letzten)
// TODO: Bereinige die dynamisch allozierten Objekte
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