Funzioni virtuali pure
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 59 di 104.
Una funzione virtuale pura è una funzione virtuale che non ha un'implementazione nella classe base. Si dichiara assegnando = 0 alla dichiarazione della funzione. Questo indica al compilatore che le classi derivate devono fornire la propria implementazione.
class Shape {
public:
virtual double area() = 0; // Funzione virtuale pura
virtual ~Shape() = default;
};A differenza delle normali funzioni virtuali che forniscono un comportamento predefinito, le funzioni virtuali pure definiscono un contratto: qualsiasi classe derivata concreta deve implementare questa funzione per poter essere istanziata. La classe base dichiara semplicemente che cosa deve essere fatto, non come.
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;
}
};Sia Circle che Rectangle devono implementare area() perché è virtuale pura in Shape. Se una classe derivata non implementa tutte le funzioni virtuali pure, diventa anch'essa astratta e non può essere istanziata.
Le funzioni virtuali pure sono essenziali quando la classe base non può fornire un'implementazione predefinita significativa. Ogni forma ha un'area, ma non c'è un modo ragionevole per calcolare l'«area di una forma generica» senza conoscere il tipo specifico della forma.
Sfida
FacileCostruiamo un sistema di elaborazione dei pagamenti che mostri come le funzioni virtuali pure impongano un contratto per i diversi metodi di pagamento. Creerai una classe base astratta che definisce ciò che ogni elaboratore di pagamenti deve fare, poi implementerai tipi di pagamento concreti che rispettano tale contratto.
Organizzerai il codice in tre file:
PaymentProcessor.h: definisci una classe astrattaPaymentProcessorche funge da modello per tutti i metodi di pagamento. Questa classe deve avere:- Un membro protetto
std::string accountId - Un costruttore che inizializza l'ID del conto
- Un metodo virtuale puro
processPayment(double amount): ogni tipo di pagamento deve implementarlo in modo diverso - Un metodo virtuale puro
getProcessorName()che restituisce unstd::string - Un distruttore virtuale
- Un membro protetto
PaymentMethods.h: implementa due elaboratori di pagamenti concreti che ereditano daPaymentProcessor:CreditCardProcessor:- Un membro privato
double feePercentage(l'aliquota della commissione di transazione) - Un costruttore che accetta l'ID del conto e la percentuale della commissione
- Implementa
processPayment()per calcolare la commissione (amount * feePercentage / 100), quindi stampa:Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>) - Implementa
getProcessorName()in modo che restituisca"CreditCard"
BankTransferProcessor:- Un membro privato
std::string bankName - Un costruttore che accetta l'ID del conto e il nome della banca
- Implementa
processPayment()in modo che stampi:Bank Transfer [<accountId>] via <bankName>: Transferred $<amount> - Implementa
getProcessorName()in modo che restituisca"BankTransfer"
- Un membro privato
main.cpp: leggi quattro input (ciascuno su una riga separata):- ID del conto della carta di credito
- Percentuale della commissione della carta di credito (double)
- ID del conto bancario
- Nome della banca
Crea entrambi gli elaboratori di pagamenti e memorizzali in un array di puntatori
PaymentProcessor*. Elabora un pagamento di100.0con ciascun elaboratore, mostrando il nome dell'elaboratore prima di ogni transazione:Processing with <processorName>: <processPayment output>Stampa una riga vuota tra gli elaboratori. Al termine, libera gli oggetti allocati dinamicamente.
Per esempio, con gli input CC-4521, 2.5, BA-7890 e 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 $100Poiché PaymentProcessor ha funzioni virtuali pure, non puoi istanziarla direttamente: si possono creare solo le implementazioni concrete che rispettano il contratto. In questo modo, ogni metodo di pagamento fornisce la propria logica di elaborazione specifica.
Provalo tu
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Leggi i dati in input
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Crea un array di puntatori PaymentProcessor* con 2 elementi
// TODO: Crea oggetti CreditCardProcessor e BankTransferProcessor
// e memorizzali nell'array
// TODO: Scorri l'array e, per ogni processore:
// 1. Stampa "Processing with <processorName>:"
// 2. Chiama processPayment con l'importo 100.0
// 3. Stampa una riga vuota tra i processori (ma non dopo l'ultimo)
// TODO: Libera la memoria degli oggetti allocati dinamicamente
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online