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 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.

challenge icon

Aufgabe

Einfach

Erstellen 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 Klasse PaymentProcessor, 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 ein std::string zurückgibt
    • Einen virtuellen Destruktor
  • PaymentMethods.h: Implementiere zwei konkrete Zahlungsverarbeiter, die von PaymentProcessor erben:

    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
  • main.cpp: Lies vier Eingaben ein (jeweils in einer eigenen Zeile):
    1. Kreditkarten-Konto-ID
    2. Kreditkartengebühr in Prozent (double)
    3. Bankkonto-ID
    4. Bankname

    Erstelle beide Zahlungsverarbeiter und speichere sie in einem Array aus PaymentProcessor*-Zeigern. Verarbeite eine Zahlung von 100.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 $100

Da 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;
}
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

Übe selbstständig: Online-C++-Compiler