Kế thừa là một trong những cột trụ quan trọng nhất của lập trình hướng đối tượng (OOP), đóng vai trò then chốt trong việc tái sử dụng và mở rộng mã nguồn. Thông qua cơ chế này, chúng ta có thể xây dựng các lớp mới dựa trên các đặc tính của những lớp đã tồn tại, giúp cấu trúc chương trình trở nên phân cấp và dễ quản lý hơn.
1. Bản chất của Kế thừa trong C++
Kế thừa cho phép một lớp (gọi là lớp dẫn xuất - Derived Class) kế thừa các thuộc tính và phương thức từ một lớp khác (gọi là lớp cơ sở - Base Class). Lớp dẫn xuất không chỉ sở hữu các đặc điểm của lớp cha mà còn có thể bổ sung thêm các thành phần mới hoặc định nghĩa lại các hành vi sẵn có.
#include <iostream>
#include <string>
// Lớp cơ sở (Base Class)
class ThanhVien {
protected:
std::string hoTen;
int tuoi;
public:
ThanhVien(std::string name, int age) : hoTen(name), tuoi(age) {}
void thongTinCoBan() {
std::cout << "Ten: " << hoTen << " | Tuoi: " << tuoi << std::endl;
}
};
// Lớp dẫn xuất (Derived Class) kế thừa public từ ThanhVien
class NhanVien : public ThanhVien {
private:
std::string phongBan;
public:
NhanVien(std::string name, int age, std::string dept)
: ThanhVien(name, age), phongBan(dept) {}
void hienThiCongViec() {
std::cout << "Nhan vien: " << hoTen << " lam viec tai: " << phongBan << std::endl;
}
};
int main() {
NhanVien nv("Nguyen Van A", 30, "Ky thuat");
nv.thongTinCoBan(); // Su dung phuong thuc tu lop cha
nv.hienThiCongViec(); // Su dung phuong thuc rieng cua lop con
return 0;
}
2. Các quyền truy cập và Kiểu kế thừa
Trong C++, phạm vi truy cập của các thành viên lớp sau khi kế thừa phụ thuộc vào hai yếu tố: Quyền truy cập gốc trong lớp cha và Kiểu kế thừa (public, protected, private).
- Thành viên Private: Luôn luôn không thể truy cập trực tiếp từ lớp dẫn xuất, bất kể kiểu kế thừa là gì.
- Thành viên Protected: Không thể truy cập từ bên ngoài lớp nhưng có thể truy cập được bởi các lớp dẫn xuất.
- Quy tắc chung: Phạm vi truy cập của thành viên ở lớp con sẽ là mức hẹp nhất giữa (Quyền truy cập gốc) và (Kiểu kế thừa). Ví dụ: Một thành viên
publiccủa lớp cha nếu được kế thừa kiểuprotectedsẽ trở thành thành viênprotectedtrong lớp con.
3. Kế thừa trong Class Template
Khi làm việc với các lớp khuôn mẫu (Templates), việc kế thừa đòi hỏi sự chỉ định rõ ràng về phạm vi (scope). Do cơ chế Instantiate (khởi tạo thực thể) theo nhu cầu của C++, trình biên dịch có thể không tự động tìm thấy các thành viên của lớp cha nếu lớp cha đó cũng là một Template.
#include <vector>
namespace MyDataStructure {
template<typename T>
class DanhSachNganXep : public std::vector<T> {
public:
void dayVao(const T& giaTri) {
// Phai su dung 'this->' hoac chi dinh ro lop co so
// Neu khong trinh bien dich se khong tim thay phuong thuc trong lop cha template
this->push_back(giaTri);
}
void loaiBo() {
if (!this->empty()) {
std::vector<T>::pop_back();
}
}
const T& xemDinh() {
return std::vector<T>::back();
}
};
}
4. Chuyển đổi giữa Lớp cơ sở và Lớp dẫn xuất (Object Slicing)
C++ cho phép thực hiện các phép gán và chuyển đổi con trỏ/tham chiếu giữa các lớp trong cùng một hệ thống kế thừa theo các quy tắc sau:
- Upcasting (Chuyển đổi lên): Đối tượng của lớp dẫn xuất có thể gán cho đối tượng lớp cơ sở, hoặc dùng con trỏ/tham chiếu của lớp cơ sở để trỏ đến đối tượng lớp dẫn xuất. Quá trình này gọi là "Slicing" (Cắt lát), vì phần dữ liệu riêng biệt của lớp con sẽ bị loại bỏ khi gán vào đối tượng lớp cha.
- Downcasting (Chuyển đổi xuống): Việc gán đối tượng lớp cha cho lớp con là không hợp lệ một cách trực tiếp. Để thực hiện điều này một cách an toàn, chúng ta cần sử dụng
dynamic_cast(yêu cầu lớp có ít nhất một hàm ảo - Virtual Function) để kiểm tra kiểu dữ liệu tại thời điểm thực thi (RTTI).
void xuLyThanhVien(ThanhVien& tv) {
tv.thongTinCoBan();
}
int main() {
NhanVien nv("Tran Thi B", 25, "Ke toan");
// Upcasting an toan: Lop con chuyen thanh tham chieu lop cha
xuLyThanhVien(nv);
ThanhVien* ptr = &nv; // Con tro lop cha tro den doi tuong lop con
return 0;
}