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.
Wyzwanie
ŁatwyZbudujmy 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ść typustd::string - Wirtualny destruktor
- Chronione pole
PaymentMethods.h: Zaimplementuj dwa konkretne procesory płatności dziedziczące poPaymentProcessor: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"
- Prywatne pole
main.cpp: Wczytaj cztery dane wejściowe (każdy w osobnym wierszu):- Identyfikator konta karty kredytowej
- Procentowa stawka opłaty za kartę kredytową (double)
- Identyfikator konta bankowego
- Nazwa banku
Utwórz oba procesory płatności i przechowaj je w tablicy wskaźników
PaymentProcessor*. Przetwórz płatność o wartości100.0za 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 $100Ponieważ 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;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online