Đa hình (Polymorphism)
📖 Giới thiệu
Đa hình cho phép một giao diện được sử dụng cho nhiều kiểu dữ liệu khác nhau. Trong C++, đa hình được thực hiện thông qua virtual functions, function overloading và operator overloading.
Ví dụ đơn giản đầu tiên
cpp
#include <iostream>
using namespace std;
class Hinh {
public:
virtual double tinh_dien_tich() {
return 0;
}
virtual void ve() {
cout << "Vẽ hình cơ bản" << endl;
}
};
class HinhTron : public Hinh {
private:
double ban_kinh;
public:
HinhTron(double r) : ban_kinh(r) {}
double tinh_dien_tich() override {
return 3.14159 * ban_kinh * ban_kinh;
}
void ve() override {
cout << "Vẽ hình tròn" << endl;
}
};
class HinhVuong : public Hinh {
private:
double canh;
public:
HinhVuong(double c) : canh(c) {}
double tinh_dien_tich() override {
return canh * canh;
}
void ve() override {
cout << "Vẽ hình vuông" << endl;
}
};
int main() {
Hinh* hinh1 = new HinhTron(5);
Hinh* hinh2 = new HinhVuong(4);
hinh1->ve(); // Gọi phương thức của HinhTron
cout << "Diện tích: " << hinh1->tinh_dien_tich() << endl;
hinh2->ve(); // Gọi phương thức của HinhVuong
cout << "Diện tích: " << hinh2->tinh_dien_tich() << endl;
delete hinh1;
delete hinh2;
return 0;
}🔧 Cú pháp
Virtual Functions
cpp
class LopCha {
public:
virtual void phuong_thuc() {
// Implementation mặc định
}
virtual ~LopCha() {} // Virtual destructor
};
class LopCon : public LopCha {
public:
void phuong_thuc() override { // Override
// Implementation mới
}
};Pure Virtual Functions (Abstract Class)
cpp
class LopTruuTuong {
public:
virtual void phuong_thuc_bat_buoc() = 0; // Pure virtual
virtual ~LopTruuTuong() {}
};Function Overloading
cpp
class Calculator {
public:
int cong(int a, int b) { return a + b; }
double cong(double a, double b) { return a + b; }
int cong(int a, int b, int c) { return a + b + c; }
};🔬 Phân tích & Giải thích
Các loại đa hình
1. Runtime Polymorphism (Virtual Functions)
cpp
class DongVat {
public:
virtual void keu() {
cout << "Động vật kêu" << endl;
}
};
class Cho : public DongVat {
public:
void keu() override {
cout << "Gâu gâu!" << endl;
}
};
class Meo : public DongVat {
public:
void keu() override {
cout << "Meo meo!" << endl;
}
};
void cho_dong_vat_keu(DongVat* dv) {
dv->keu(); // Gọi phương thức phù hợp dựa vào kiểu thực tế
}2. Compile-time Polymorphism (Function Overloading)
cpp
class MayTinh {
public:
int tinh(int a, int b) {
return a + b;
}
double tinh(double a, double b) {
return a + b;
}
string tinh(string a, string b) {
return a + " " + b;
}
};3. Operator Overloading
cpp
class PhanSo {
private:
int tu, mau;
public:
PhanSo(int t = 0, int m = 1) : tu(t), mau(m) {}
PhanSo operator+(const PhanSo& other) {
return PhanSo(tu * other.mau + other.tu * mau, mau * other.mau);
}
bool operator==(const PhanSo& other) {
return tu * other.mau == other.tu * mau;
}
};💻 Ví dụ minh họa
Ví dụ 1: Hệ thống thanh toán
cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// Lớp trừu tượng - Phương thức thanh toán
class PaymentMethod {
protected:
string ten_phuong_thuc;
double phi_giao_dich;
public:
PaymentMethod(string ten, double phi = 0) {
ten_phuong_thuc = ten;
phi_giao_dich = phi;
}
virtual ~PaymentMethod() {}
// Pure virtual function - bắt buộc override
virtual bool xu_ly_thanh_toan(double so_tien) = 0;
// Virtual function - có thể override
virtual void hien_thong_tin() {
cout << "Phương thức: " << ten_phuong_thuc << endl;
cout << "Phí giao dịch: " << phi_giao_dich << "%" << endl;
}
// Virtual function với implementation mặc định
virtual double tinh_phi(double so_tien) {
return so_tien * phi_giao_dich / 100;
}
string lay_ten() const { return ten_phuong_thuc; }
};
// Lớp con 1 - Thẻ tín dụng
class TheTinDung : public PaymentMethod {
private:
string so_the;
string ten_chu_the;
double han_muc;
double da_su_dung;
public:
TheTinDung(string so, string ten, double han_muc_the)
: PaymentMethod("Thẻ tín dụng", 2.5) {
so_the = so;
ten_chu_the = ten;
han_muc = han_muc_the;
da_su_dung = 0;
}
bool xu_ly_thanh_toan(double so_tien) override {
double tong_tien = so_tien + tinh_phi(so_tien);
if (da_su_dung + tong_tien <= han_muc) {
da_su_dung += tong_tien;
cout << "✅ Thanh toán thành công qua thẻ tín dụng!" << endl;
cout << "Số tiền: " << so_tien << " VND" << endl;
cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
cout << "Tổng: " << tong_tien << " VND" << endl;
cout << "Đã sử dụng: " << da_su_dung << "/" << han_muc << " VND" << endl;
return true;
} else {
cout << "❌ Vượt quá hạn mức thẻ!" << endl;
return false;
}
}
void hien_thong_tin() override {
PaymentMethod::hien_thong_tin();
cout << "Số thẻ: ****" << so_the.substr(so_the.length()-4) << endl;
cout << "Chủ thẻ: " << ten_chu_the << endl;
cout << "Hạn mức: " << han_muc << " VND" << endl;
cout << "Đã sử dụng: " << da_su_dung << " VND" << endl;
}
};
// Lớp con 2 - Ví điện tử
class ViDienTu : public PaymentMethod {
private:
string so_dien_thoai;
double so_du;
string ma_pin;
public:
ViDienTu(string sdt, double du_ban_dau, string pin)
: PaymentMethod("Ví điện tử", 1.0) {
so_dien_thoai = sdt;
so_du = du_ban_dau;
ma_pin = pin;
}
bool xu_ly_thanh_toan(double so_tien) override {
double tong_tien = so_tien + tinh_phi(so_tien);
if (so_du >= tong_tien) {
so_du -= tong_tien;
cout << "✅ Thanh toán thành công qua ví điện tử!" << endl;
cout << "Số tiền: " << so_tien << " VND" << endl;
cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
cout << "Số dư còn lại: " << so_du << " VND" << endl;
return true;
} else {
cout << "❌ Số dư không đủ!" << endl;
return false;
}
}
void hien_thong_tin() override {
PaymentMethod::hien_thong_tin();
cout << "SĐT: " << so_dien_thoai << endl;
cout << "Số dư: " << so_du << " VND" << endl;
}
void nap_tien(double so_tien) {
if (so_tien > 0) {
so_du += so_tien;
cout << "Đã nạp " << so_tien << " VND. Số dư: " << so_du << " VND" << endl;
}
}
};
// Lớp con 3 - Chuyển khoản ngân hàng
class ChuyenKhoanNganHang : public PaymentMethod {
private:
string so_tai_khoan;
string ten_ngan_hang;
double so_du;
public:
ChuyenKhoanNganHang(string so_tk, string ngan_hang, double du)
: PaymentMethod("Chuyển khoản ngân hàng", 0.5) {
so_tai_khoan = so_tk;
ten_ngan_hang = ngan_hang;
so_du = du;
}
bool xu_ly_thanh_toan(double so_tien) override {
double tong_tien = so_tien + tinh_phi(so_tien);
if (so_du >= tong_tien) {
so_du -= tong_tien;
cout << "✅ Chuyển khoản thành công!" << endl;
cout << "Số tiền: " << so_tien << " VND" << endl;
cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
cout << "Từ tài khoản: " << so_tai_khoan << " (" << ten_ngan_hang << ")" << endl;
return true;
} else {
cout << "❌ Số dư tài khoản không đủ!" << endl;
return false;
}
}
void hien_thong_tin() override {
PaymentMethod::hien_thong_tin();
cout << "Số TK: ****" << so_tai_khoan.substr(so_tai_khoan.length()-4) << endl;
cout << "Ngân hàng: " << ten_ngan_hang << endl;
cout << "Số dư: " << so_du << " VND" << endl;
}
// Override tính phí (ngân hàng có phí cố định)
double tinh_phi(double so_tien) override {
if (so_tien >= 1000000) { // Miễn phí cho giao dịch lớn
return 0;
}
return max(5000.0, so_tien * phi_giao_dich / 100); // Phí tối thiểu 5000 VND
}
};
// Class xử lý thanh toán - sử dụng polymorphism
class BillProcessor {
public:
static void xu_ly_don_hang(vector<PaymentMethod*>& phuong_thuc_list, double so_tien) {
cout << "\n=== XỬ LÝ ĐÓN HÀNG ===" << endl;
cout << "Số tiền cần thanh toán: " << so_tien << " VND" << endl;
cout << "\nCác phương thức thanh toán có sẵn:" << endl;
for (int i = 0; i < phuong_thuc_list.size(); i++) {
cout << (i + 1) << ". ";
phuong_thuc_list[i]->hien_thong_tin();
cout << "------------------------" << endl;
}
// Thử thanh toán với từng phương thức
bool thanh_cong = false;
for (PaymentMethod* pm : phuong_thuc_list) {
cout << "\nThử thanh toán bằng " << pm->lay_ten() << ":" << endl;
if (pm->xu_ly_thanh_toan(so_tien)) {
thanh_cong = true;
break;
}
}
if (!thanh_cong) {
cout << "\n❌ Không thể thanh toán với bất kỳ phương thức nào!" << endl;
}
}
static void so_sanh_phi_giao_dich(vector<PaymentMethod*>& phuong_thuc_list, double so_tien) {
cout << "\n=== SO SÁNH PHÍ GIAO DỊCH ===" << endl;
cout << "Cho số tiền: " << so_tien << " VND" << endl;
for (PaymentMethod* pm : phuong_thuc_list) {
double phi = pm->tinh_phi(so_tien);
cout << pm->lay_ten() << ": " << phi << " VND" << endl;
}
}
};
int main() {
cout << "=== HỆ THỐNG THANH TOÁN ===" << endl;
// Tạo các phương thức thanh toán
vector<PaymentMethod*> phuong_thuc_list;
phuong_thuc_list.push_back(new TheTinDung("1234567890123456", "Nguyễn Văn A", 10000000));
phuong_thuc_list.push_back(new ViDienTu("0987654321", 5000000, "123456"));
phuong_thuc_list.push_back(new ChuyenKhoanNganHang("1234567890", "Vietcombank", 15000000));
// So sánh phí giao dịch
BillProcessor::so_sanh_phi_giao_dich(phuong_thuc_list, 1000000);
// Xử lý đơn hàng
BillProcessor::xu_ly_don_hang(phuong_thuc_list, 2000000);
cout << "\n=== NẠP THÊM TIỀN VÀO VÍ ===" << endl;
ViDienTu* vi = dynamic_cast<ViDienTu*>(phuong_thuc_list[1]);
if (vi) {
vi->nap_tien(3000000);
}
// Thử lại thanh toán
cout << "\n=== THỬ LẠI THANH TOÁN ===" << endl;
BillProcessor::xu_ly_don_hang(phuong_thuc_list, 1500000);
// Cleanup
for (PaymentMethod* pm : phuong_thuc_list) {
delete pm;
}
return 0;
}Ví dụ 2: Operator Overloading - Lớp Phân số
cpp
#include <iostream>
#include <numeric>
using namespace std;
class PhanSo {
private:
int tu_so;
int mau_so;
// Tìm ước chung lớn nhất
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// Rút gọn phân số
void rut_gon() {
int ucln = gcd(abs(tu_so), abs(mau_so));
tu_so /= ucln;
mau_so /= ucln;
// Đảm bảo mẫu số dương
if (mau_so < 0) {
tu_so = -tu_so;
mau_so = -mau_so;
}
}
public:
// Constructor
PhanSo(int tu = 0, int mau = 1) {
if (mau == 0) {
cout << "Lỗi: Mẫu số không thể bằng 0!" << endl;
tu_so = 0;
mau_so = 1;
} else {
tu_so = tu;
mau_so = mau;
rut_gon();
}
}
// Operator overloading for arithmetic
PhanSo operator+(const PhanSo& other) const {
int tu_moi = tu_so * other.mau_so + other.tu_so * mau_so;
int mau_moi = mau_so * other.mau_so;
return PhanSo(tu_moi, mau_moi);
}
PhanSo operator-(const PhanSo& other) const {
int tu_moi = tu_so * other.mau_so - other.tu_so * mau_so;
int mau_moi = mau_so * other.mau_so;
return PhanSo(tu_moi, mau_moi);
}
PhanSo operator*(const PhanSo& other) const {
return PhanSo(tu_so * other.tu_so, mau_so * other.mau_so);
}
PhanSo operator/(const PhanSo& other) const {
if (other.tu_so == 0) {
cout << "Lỗi: Không thể chia cho 0!" << endl;
return PhanSo(0, 1);
}
return PhanSo(tu_so * other.mau_so, mau_so * other.tu_so);
}
// Operator overloading for comparison
bool operator==(const PhanSo& other) const {
return tu_so * other.mau_so == other.tu_so * mau_so;
}
bool operator!=(const PhanSo& other) const {
return !(*this == other);
}
bool operator<(const PhanSo& other) const {
return tu_so * other.mau_so < other.tu_so * mau_so;
}
bool operator>(const PhanSo& other) const {
return other < *this;
}
bool operator<=(const PhanSo& other) const {
return *this < other || *this == other;
}
bool operator>=(const PhanSo& other) const {
return *this > other || *this == other;
}
// Operator overloading for assignment
PhanSo& operator+=(const PhanSo& other) {
*this = *this + other;
return *this;
}
PhanSo& operator-=(const PhanSo& other) {
*this = *this - other;
return *this;
}
// Operator overloading for increment/decrement
PhanSo& operator++() { // Prefix ++
tu_so += mau_so;
return *this;
}
PhanSo operator++(int) { // Postfix ++
PhanSo temp = *this;
++(*this);
return temp;
}
// Operator overloading for stream
friend ostream& operator<<(ostream& os, const PhanSo& ps) {
if (ps.mau_so == 1) {
os << ps.tu_so;
} else if (ps.tu_so == 0) {
os << "0";
} else {
os << ps.tu_so << "/" << ps.mau_so;
}
return os;
}
friend istream& operator>>(istream& is, PhanSo& ps) {
cout << "Nhập tử số: ";
is >> ps.tu_so;
cout << "Nhập mẫu số: ";
is >> ps.mau_so;
if (ps.mau_so == 0) {
cout << "Mẫu số không thể bằng 0! Đặt mẫu số = 1" << endl;
ps.mau_so = 1;
}
ps.rut_gon();
return is;
}
// Chuyển đổi thành số thực
double to_double() const {
return static_cast<double>(tu_so) / mau_so;
}
// Hiển thị thông tin
void hien_thi() const {
cout << *this;
}
};
// Function overloading examples
class MayTinhPhanSo {
public:
// Overload với số lượng tham số khác nhau
static PhanSo cong(const PhanSo& a, const PhanSo& b) {
return a + b;
}
static PhanSo cong(const PhanSo& a, const PhanSo& b, const PhanSo& c) {
return a + b + c;
}
static PhanSo cong(const PhanSo& a, int so_nguyen) {
return a + PhanSo(so_nguyen);
}
// Overload với kiểu tham số khác nhau
static void hien_thi(const PhanSo& ps) {
cout << "Phân số: " << ps << endl;
}
static void hien_thi(double so_thuc) {
cout << "Số thực: " << so_thuc << endl;
}
static void hien_thi(int so_nguyen) {
cout << "Số nguyên: " << so_nguyen << endl;
}
};
int main() {
cout << "=== CALCULATOR PHÂN SỐ ===" << endl;
// Tạo phân số
PhanSo ps1(3, 4);
PhanSo ps2(2, 6); // Sẽ tự động rút gọn thành 1/3
PhanSo ps3(5, 1); // Hiển thị như số nguyên
cout << "Phân số ban đầu:" << endl;
cout << "ps1 = " << ps1 << endl;
cout << "ps2 = " << ps2 << endl;
cout << "ps3 = " << ps3 << endl;
// Kiểm tra operator overloading
cout << "\n=== PHÉP TOÁN CƠ BẢN ===" << endl;
cout << ps1 << " + " << ps2 << " = " << (ps1 + ps2) << endl;
cout << ps1 << " - " << ps2 << " = " << (ps1 - ps2) << endl;
cout << ps1 << " * " << ps2 << " = " << (ps1 * ps2) << endl;
cout << ps1 << " / " << ps2 << " = " << (ps1 / ps2) << endl;
// So sánh
cout << "\n=== SO SÁNH ===" << endl;
cout << ps1 << " == " << ps2 << ": " << (ps1 == ps2 ? "Đúng" : "Sai") << endl;
cout << ps1 << " > " << ps2 << ": " << (ps1 > ps2 ? "Đúng" : "Sai") << endl;
cout << ps1 << " < " << ps3 << ": " << (ps1 < ps3 ? "Đúng" : "Sai") << endl;
// Assignment operators
cout << "\n=== PHÉP GÁN ===" << endl;
PhanSo ps4 = ps1;
cout << "ps4 = ps1: " << ps4 << endl;
ps4 += ps2;
cout << "ps4 += ps2: " << ps4 << endl;
// Increment operators
cout << "\n=== TĂNG GIẢM ===" << endl;
cout << "ps1 trước khi tăng: " << ps1 << endl;
cout << "++ps1: " << ++ps1 << endl;
cout << "ps1++: " << ps1++ << endl;
cout << "ps1 sau khi tăng: " << ps1 << endl;
// Function overloading
cout << "\n=== FUNCTION OVERLOADING ===" << endl;
PhanSo result1 = MayTinhPhanSo::cong(ps1, ps2);
PhanSo result2 = MayTinhPhanSo::cong(ps1, ps2, ps3);
PhanSo result3 = MayTinhPhanSo::cong(ps1, 5);
MayTinhPhanSo::hien_thi(result1);
MayTinhPhanSo::hien_thi(result2);
MayTinhPhanSo::hien_thi(result3);
MayTinhPhanSo::hien_thi(3.14159);
MayTinhPhanSo::hien_thi(42);
// Nhập phân số từ người dùng
cout << "\n=== NHẬP PHÂN SỐ MỚI ===" << endl;
PhanSo ps_moi;
// cin >> ps_moi; // Uncomment để test input
// cout << "Phân số vừa nhập: " << ps_moi << endl;
// cout << "Giá trị thập phân: " << ps_moi.to_double() << endl;
return 0;
}🏋️ Thực hành
Bài tập 1: Abstract Media Player
Tạo lớp trừu tượng MediaPlayer và các lớp con AudioPlayer, VideoPlayer:
- Pure virtual functions:
play(),pause(),stop() - Mỗi player có cách xử lý khác nhau
Bài tập 2: Overload operators cho Vector3D
Tạo lớp Vector3D với operator overloading:
- Arithmetic:
+,-,*(scalar),/ - Comparison:
==,!= - Stream:
<<,>>
Bài tập 3: Function overloading Calculator
Tạo class Calculator với các phương thức calculate() overloaded:
calculate(int, int)calculate(double, double)calculate(string, string)// Concatenation
📋 Tóm tắt
Đa hình (Polymorphism): Một giao diện, nhiều implementation
Runtime Polymorphism:
- Virtual functions
- Pure virtual functions (abstract classes)
- Virtual destructors
Compile-time Polymorphism:
- Function overloading
- Operator overloading
Lợi ích:
- Code linh hoạt và có thể mở rộng
- Dễ bảo trì
- Tái sử dụng code hiệu quả
Bài tiếp theo: Trừu tượng hóa - Tìm hiểu về abstract classes và interfaces.