Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 Klasse PaymentProcessor, die als Blaupause für alle Zahlungsmethoden dient. Diese Klasse sollte Folgendes haben:
    • Ein geschütztes (protected) std::string accountId Mitglied
    • 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 einen std::string zurückgibt
    • Einen virtuellen Destruktor
  • PaymentMethods.h: Implementieren Sie zwei konkrete Zahlungsanprozessoren, die von PaymentProcessor erben:

    CreditCardProcessor:

    • Ein privates double feePercentage Mitglied (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 bankName Mitglied
    • 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
  • main.cpp: Lesen Sie vier Eingaben ein (jede in einer separaten Zeile):
    1. Kreditkarten-Account-ID
    2. Kreditkarten-Gebührensatz (double)
    3. Bankkonto-ID
    4. Bankname

    Erstellen Sie beide Zahlungsanprozessoren und speichern Sie diese in einem Array von PaymentProcessor* Pointern. Verarbeiten Sie eine Zahlung von 100.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 $100

Da 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;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung