Hàm ảo thuần túy
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 59 trên 104.
Hàm ảo thuần túy là hàm ảo không có phần triển khai trong lớp cơ sở. Hàm này được khai báo bằng cách gán = 0 cho khai báo hàm. Điều này cho trình biên dịch biết rằng các lớp dẫn xuất phải tự triển khai hàm.
class Shape {
public:
virtual double area() = 0; // Hàm ảo thuần túy
virtual ~Shape() = default;
};Khác với các hàm ảo thông thường cung cấp hành vi mặc định, hàm ảo thuần túy xác định một hợp đồng: mọi lớp dẫn xuất cụ thể phải triển khai hàm này thì mới có thể khởi tạo đối tượng. Lớp cơ sở chỉ khai báo việc gì cần làm, chứ không phải cách thức thực hiện.
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;
}
};Cả Circle và Rectangle đều phải triển khai area() vì hàm này là hàm thuần ảo trong Shape. Nếu lớp dẫn xuất không triển khai tất cả các hàm thuần ảo, lớp đó cũng trở thành lớp trừu tượng và không thể được khởi tạo.
Hàm ảo thuần túy rất cần thiết khi lớp cơ sở không thể cung cấp cách triển khai mặc định có ý nghĩa. Mỗi hình đều có diện tích, nhưng không có cách hợp lý nào để tính “diện tích hình dạng tổng quát” nếu không biết loại hình cụ thể.
Thử thách
DễHãy xây dựng một hệ thống xử lý thanh toán để minh họa cách các hàm thuần ảo thực thi một hợp đồng cho nhiều phương thức thanh toán. Bạn sẽ tạo một lớp cơ sở trừu tượng định nghĩa những việc mọi bộ xử lý thanh toán phải làm, sau đó triển khai các kiểu thanh toán cụ thể đáp ứng hợp đồng đó.
Bạn sẽ sắp xếp code trong ba tệp:
PaymentProcessor.h: Định nghĩa lớp trừu tượngPaymentProcessor, làm khuôn mẫu cho mọi phương thức thanh toán. Lớp này cần có:- Một thành viên
std::string accountIdcó phạm vi protected - Một hàm khởi tạo để khởi tạo mã tài khoản
- Một phương thức thuần ảo
processPayment(double amount): mỗi kiểu thanh toán phải triển khai phương thức này theo cách riêng - Một phương thức thuần ảo
getProcessorName()trả về mộtstd::string - Một hàm hủy ảo
- Một thành viên
PaymentMethods.h: Triển khai hai bộ xử lý thanh toán cụ thể kế thừa từPaymentProcessor:CreditCardProcessor:- Một thành viên riêng
double feePercentage(tỷ lệ phí giao dịch) - Một hàm khởi tạo nhận mã tài khoản và tỷ lệ phí
- Triển khai
processPayment()để tính phí (amount * feePercentage / 100), sau đó in ra:Credit Card [<accountId>]: Charged $<amount> (Fee: $<fee>) - Triển khai
getProcessorName()để trả về"CreditCard"
BankTransferProcessor:- Một thành viên riêng
std::string bankName - Một hàm khởi tạo nhận mã tài khoản và tên ngân hàng
- Triển khai
processPayment()để in ra:Bank Transfer [<accountId>] via <bankName>: Transferred $<amount> - Triển khai
getProcessorName()để trả về"BankTransfer"
- Một thành viên riêng
main.cpp: Đọc bốn đầu vào, mỗi đầu vào nằm trên một dòng riêng:- Mã tài khoản thẻ tín dụng
- Tỷ lệ phí thẻ tín dụng (double)
- Mã tài khoản ngân hàng
- Tên ngân hàng
Tạo cả hai bộ xử lý thanh toán và lưu chúng vào một mảng các con trỏ
PaymentProcessor*. Xử lý khoản thanh toán100.0qua từng bộ xử lý, in tên bộ xử lý trước mỗi giao dịch:Processing with <processorName>: <processPayment output>In một dòng trống giữa các bộ xử lý. Dọn dẹp các đối tượng đã cấp phát động khi hoàn tất.
Ví dụ, với các đầu vào CC-4521, 2.5, BA-7890 và 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 $100Vì PaymentProcessor có các hàm thuần ảo, bạn không thể khởi tạo trực tiếp lớp này: chỉ có thể tạo các lớp triển khai cụ thể đáp ứng hợp đồng. Điều này đảm bảo mỗi phương thức thanh toán cung cấp logic xử lý riêng.
Tự làm thử
#include <iostream>
#include <string>
#include "PaymentMethods.h"
using namespace std;
int main() {
// Đọc dữ liệu đầu vào
string ccAccountId;
double feePercentage;
string bankAccountId;
string bankName;
getline(cin, ccAccountId);
cin >> feePercentage;
cin.ignore();
getline(cin, bankAccountId);
getline(cin, bankName);
// TODO: Tạo mảng gồm 2 phần tử là con trỏ PaymentProcessor*
// TODO: Tạo các đối tượng CreditCardProcessor và BankTransferProcessor
// và lưu chúng vào mảng
// TODO: Duyệt mảng và thực hiện với từng bộ xử lý:
// 1. In ra "Processing with <processorName>:"
// 2. Gọi processPayment với số tiền 100.0
// 3. In một dòng trống giữa các bộ xử lý (không in sau bộ xử lý cuối cùng)
// TODO: Giải phóng các đối tượng được cấp phát động
return 0;
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online