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 in der Basisklasse keine Implementierung besitzt. Sie wird deklariert, indem der Funktionsdeklaration = 0 zugewiesen wird. Dadurch wird dem Compiler mitgeteilt, 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 gewöhnlichen virtuellen Funktionen, die ein Standardverhalten bereitstellen, definieren rein virtuelle Funktionen einen Vertrag: Jede konkrete abgeleitete Klasse muss diese Funktion implementieren, damit sie instanziiert werden kann. 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 es 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 unverzichtbar, wenn die Basisklasse keine sinnvolle Standardimplementierung bereitstellen kann. Jede Form hat eine Fläche, aber es gibt keine sinnvolle Möglichkeit, die „Fläche einer generischen Form“ zu berechnen, ohne den konkreten Formtyp zu kennen.
Aufgabe
EinfachErstellen wir ein Zahlungsverarbeitungssystem, das veranschaulicht, wie rein virtuelle Funktionen einen Vertrag über verschiedene Zahlungsmethoden hinweg durchsetzen. Du erstellst eine abstrakte Basisklasse, die festlegt, was jeder Zahlungsverarbeiter tun muss, und implementierst anschließend konkrete Zahlungstypen, die diesen Vertrag erfüllen.
Du organisierst deinen Code auf drei Dateien verteilt:
PaymentProcessor.h: Definiere eine abstrakte KlassePaymentProcessor, die als Vorlage für alle Zahlungsmethoden dient. Diese Klasse sollte Folgendes enthalten:- Ein geschütztes
std::string accountId-Member - Einen Konstruktor, der die Konto-ID initialisiert
- Eine rein virtuelle Methode
processPayment(double amount): Jeder Zahlungstyp muss diese auf eigene Weise implementieren - Eine rein virtuelle Methode
getProcessorName(), die einstd::stringzurückgibt - Einen virtuellen Destruktor
- Ein geschütztes
PaymentMethods.h: Implementiere zwei konkrete Zahlungsverarbeiter, die vonPaymentProcessorerben:CreditCardProcessor:- Ein privates
double feePercentage-Member (der Transaktionsgebührensatz) - Einen Konstruktor, der die Konto-ID und den Gebührenprozentsatz entgegennimmt
- Implementiere
processPayment(), um die Gebühr zu berechnen (amount * feePercentage / 100) und anschließend Folgendes auszugeben:Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>) - Implementiere
getProcessorName(), sodass"CreditCard"zurückgegeben wird
BankTransferProcessor:- Ein privates
std::string bankName-Member - Einen Konstruktor, der die Konto-ID und den Banknamen entgegennimmt
- Implementiere
processPayment(), sodass Folgendes ausgegeben wird:Bank Transfer [<accountId>] via <bankName>: Transferred $<amount> - Implementiere
getProcessorName(), sodass"BankTransfer"zurückgegeben wird
- Ein privates
main.cpp: Lies vier Eingaben ein (jeweils in einer eigenen Zeile):- Kreditkarten-Konto-ID
- Kreditkartengebühr in Prozent (double)
- Bankkonto-ID
- Bankname
Erstelle beide Zahlungsverarbeiter und speichere sie in einem Array aus
PaymentProcessor*-Zeigern. Verarbeite eine Zahlung von100.0über jeden Zahlungsverarbeiter und gib vor jeder Transaktion den Namen des Zahlungsverarbeiters aus:Processing with <processorName>: <processPayment output>Gib zwischen den Zahlungsverarbeitern eine Leerzeile aus. Räume deine dynamisch allozierten Objekte nach Abschluss auf.
Beispielsweise 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 besitzt, kannst du es nicht direkt instanziieren: Nur die konkreten Implementierungen, die den Vertrag erfüllen, können erstellt werden. Dadurch wird sichergestellt, dass jede Zahlungsmethode ihre eigene spezifische Verarbeitungslogik bereitstellt.
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 von PaymentProcessor*-Zeigern mit 2 Elementen
// TODO: Erstelle CreditCardProcessor- und BankTransferProcessor-Objekte
// und speichere sie im Array
// TODO: Durchlaufe das Array und für jeden Prozessor:
// 1. Print "Processing with <processorName>:"
// 2. Rufe processPayment mit Betrag 100.0 auf
// 3. Gib eine Leerzeile zwischen den Prozessoren aus (nicht nach dem letzten)
// TODO: Räume dynamisch allokierte Objekte 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