Menu
Coddy logo textTech

Czysto wirtualne funkcje

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 59 z 104.

Czysta funkcja wirtualna to funkcja wirtualna, która nie ma implementacji w klasie bazowej. Deklaruje się ją, przypisując = 0 do deklaracji funkcji. Informuje to kompilator, że klasy pochodne muszą dostarczyć własną implementację.

class Shape {
public:
    virtual double area() = 0;  // Czysto wirtualna funkcja
    virtual ~Shape() = default;
};

W przeciwieństwie do zwykłych funkcji wirtualnych, które zapewniają domyślne zachowanie, czysto wirtualne funkcje definiują kontrakt: każda konkretna klasa pochodna musi zaimplementować tę funkcję, aby można było tworzyć jej instancje. Klasa bazowa jedynie deklaruje, co należy zrobić, a nie jak.

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

Zarówno Circle, jak i Rectangle muszą implementować area(), ponieważ w klasie Shape jest to funkcja czysto wirtualna. Jeśli klasa pochodna nie zaimplementuje wszystkich funkcji czysto wirtualnych, sama również stanie się abstrakcyjna i nie będzie można utworzyć jej instancji.

Funkcje czysto wirtualne są niezbędne, gdy klasa bazowa nie może zapewnić sensownej implementacji domyślnej. Każdy kształt ma pole powierzchni, ale nie ma sensownego sposobu na obliczenie „pola powierzchni ogólnego kształtu” bez znajomości konkretnego typu kształtu.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system przetwarzania płatności, który pokazuje, jak czysto wirtualne funkcje wymuszają przestrzeganie kontraktu przez różne metody płatności. Utworzysz abstrakcyjną klasę bazową, która określa, co musi robić każdy procesor płatności, a następnie zaimplementujesz konkretne typy płatności spełniające ten kontrakt.

Rozmieścisz kod w trzech plikach:

  • PaymentProcessor.h: Zdefiniuj abstrakcyjną klasę PaymentProcessor, która będzie wzorcem dla wszystkich metod płatności. Klasa ta powinna zawierać:
    • Chronione pole std::string accountId
    • Konstruktor, który inicjalizuje identyfikator konta
    • Czysto wirtualną metodę processPayment(double amount): każdy typ płatności musi zaimplementować ją w inny sposób
    • Czysto wirtualną metodę getProcessorName(), która zwraca wartość typu std::string
    • Wirtualny destruktor
  • PaymentMethods.h: Zaimplementuj dwa konkretne procesory płatności dziedziczące po PaymentProcessor:

    CreditCardProcessor:

    • Prywatne pole double feePercentage (stawka opłaty transakcyjnej)
    • Konstruktor przyjmujący identyfikator konta i procentową stawkę opłaty
    • Zaimplementuj processPayment(), aby obliczać opłatę (amount * feePercentage / 100), a następnie wyświetlać:
      Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>)
    • Zaimplementuj getProcessorName(), aby zwracała "CreditCard"

    BankTransferProcessor:

    • Prywatne pole std::string bankName
    • Konstruktor przyjmujący identyfikator konta i nazwę banku
    • Zaimplementuj processPayment(), aby wyświetlać:
      Bank Transfer [<accountId>] via <bankName>: Transferred $<amount>
    • Zaimplementuj getProcessorName(), aby zwracała "BankTransfer"
  • main.cpp: Wczytaj cztery dane wejściowe (każdy w osobnym wierszu):
    1. Identyfikator konta karty kredytowej
    2. Procentowa stawka opłaty za kartę kredytową (double)
    3. Identyfikator konta bankowego
    4. Nazwa banku

    Utwórz oba procesory płatności i przechowaj je w tablicy wskaźników PaymentProcessor*. Przetwórz płatność o wartości 100.0 za pomocą każdego procesora, wyświetlając nazwę procesora przed każdą transakcją:

    Processing with <processorName>:
    <processPayment output>

    Wyświetl pusty wiersz między procesorami. Po zakończeniu zwolnij pamięć obiektów utworzonych dynamicznie.

Na przykład dla danych wejściowych CC-4521, 2.5, BA-7890 i 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

Ponieważ PaymentProcessor zawiera czysto wirtualne funkcje, nie możesz utworzyć jego instancji bezpośrednio: można tworzyć tylko konkretne implementacje spełniające kontrakt. Dzięki temu każda metoda płatności zapewnia własną, specyficzną logikę przetwarzania.

Spróbuj swoich sił

#include <iostream>
#include <string>
#include "PaymentMethods.h"

using namespace std;

int main() {
    // Wczytaj dane wejściowe
    string ccAccountId;
    double feePercentage;
    string bankAccountId;
    string bankName;
    
    getline(cin, ccAccountId);
    cin >> feePercentage;
    cin.ignore();
    getline(cin, bankAccountId);
    getline(cin, bankName);
    
    // TODO: Utwórz tablicę 2 wskaźników typu PaymentProcessor*
    
    // TODO: Utwórz obiekty CreditCardProcessor i BankTransferProcessor
    // i zapisz je w tablicy
    
    // TODO: Przejdź przez tablicę i dla każdego procesora:
    // 1. Wypisz "Processing with <processorName>:"
    // 2. Wywołaj processPayment z kwotą 100.0
    // 3. Wypisz pusty wiersz między procesorami (ale nie po ostatnim)
    
    // TODO: Zwolnij pamięć zajmowaną przez dynamicznie zaalokowane obiekty
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online