Bản thiết kế · Attribute · Method
Private · Protected · Public
Base class · Derived class · Hệ thống phân cấp
Virtual · Override · vtable
Pure virtual · Interface
Định nghĩa, khởi tạo instance và sử dụng object
Attribute (data member) và method (member function)
Access modifier, friend function & friend class
Khởi tạo, nạp chồng và vòng đời của object
Cấp phát động, toán tử mũi tên, tập hợp object
Base class & derived class, các chế độ kế thừa
Thứ tự thực thi và danh sách khởi tạo
Protected access, ghi đè (override), upcasting
Nhiều base class, mơ hồ tên và Diamond Problem
Pure virtual function và mẫu interface trong C++
Virtual function, override và cơ chế vtable
Định nghĩa class với dữ liệu được đóng gói (private), giao tiếp có kiểm soát (public), và danh sách khởi tạo trong constructor.
Áp dụng đúng các access modifier và khai báo friend function/class khi cần thiết.
Cấp phát và giải phóng object và array động bằng new, delete, và toán tử mũi tên.
Hiện thực inheritance đơn, đa cấp, và đa kế thừa đồng thời ngăn chặn diamond problem.
Sử dụng virtual function và con trỏ base class cho dynamic binding tại runtime với cơ chế vtable dispatch.
Xây dựng hợp đồng pure virtual (= 0) và phát triển hệ thống mô-đun như MiniPOS V3.0.
struct BankAccount {
string owner;
double balance;
};
BankAccount acc = {"Alice", 1000000};
acc.balance = -999999; // Anyone can corrupt!Một object đóng gói trạng thái (attribute) và hành vi (method) lại với nhau. Dữ liệu nội bộ được ẩn đi sau quyền truy cập private. Các thay đổi chỉ được phép thực hiện thông qua các member function public đã được kiểm tra.
Private fields
Public methods
Logic nghiệp vụ
Định nghĩa Classes · Khởi tạo Objects · Blueprint và Instance
Cú Pháp Class · Khai Báo Member · Quy Tắc Dấu Chấm Phẩy
Một Class là kiểu do người dùng định nghĩa — một khuôn mẫu xác định những attribute và method mà tất cả các object thuộc kiểu này sẽ có. Bản thân nó không cấp phát bộ nhớ cho dữ liệu.
Một Object là instance cụ thể của một class, chiếm bộ nhớ thực tế trong thời gian chạy. Nhiều object có thể được tạo ra từ cùng một class, mỗi object lưu trữ trạng thái độc lập của riêng nó.
class ClassName {
private:
// Private attributes (hidden from outside)
DataType attribute1;
DataType attribute2;
public:
// Public methods (accessible from outside)
void setAttribute1(DataType val);
DataType getAttribute1() const;
void executeAction();
}; // Note: semicolon is MANDATORY!Sử dụng từ khóa class theo sau là tên class viết theo kiểu PascalCase.
Bao toàn bộ khai báo member trong { ... }.
Dấu ngoặc đóng phải kết thúc bằng ;. Thiếu dấu này sẽ gây lỗi biên dịch.
Khác với struct (mặc định là public), class mặc định tất cả các member là private.
Khởi tạo Objects · Toán tử Dot · Khởi tạo trên Stack và Heap
#include <iostream>
#include <string>
using namespace std;
class Product {
private:
string name;
double price;
public:
void init(string n, double p) {
name = n;
price = (p >= 0) ? p : 0;
}
void display() const {
cout << name << " - " << price << " VND" << endl;
}
};
int main() {
Product item1;
item1.init("Mechanical Keyboard", 1200000);
item1.display(); // dot operator
// item1.price = -500; // COMPILE ERROR!
return 0;
}Truy cập các method và biến public trên một object instance: item1.display()
Trình biên dịch ngăn mọi truy cập bên ngoài vào các member private. Các giá trị không hợp lệ không thể được gán vào price.
Biểu diễn Dữ liệu (Trạng thái) · Các Thao tác Chức năng (Hành vi)
Lưu trữ Trạng thái · Các loại Attribute · Dung lượng bộ nhớ trên mỗi Instance
class Student {
private:
string studentId; // e.g., "B23DCCN001"
string fullName; // Họ tên sinh viên
double midtermScore; // Thang điểm 0.0 - 10.0
double finalScore; // Thang điểm 0.0 - 10.0
};Attributes (biến member / trường dữ liệu) lưu trữ dữ liệu xác định trạng thái của một object. Mỗi instance của Student có một vùng nhớ riêng biệt cho các attribute này.
studentId: "B23DCCN001"
fullName: "Nguyen Van An"
midtermScore: 8.5
finalScore: 7.5
studentId: "B23DCCN002"
fullName: "Le Thi Binh"
midtermScore: 9.0
finalScore: 9.5
Định nghĩa nội tuyến · Toán tử Scope Resolution (::) · Từ khoá const trong method
class Circle {
private:
double radius;
public:
void setRadius(double r); // Chỉ khai báo
// Định nghĩa bên trong: được xem là inline
double getRadius() const { return radius; }
double calcArea() const; // Chỉ khai báo
};
// Định nghĩa BÊN NGOÀI dùng Scope Resolution (::)
void Circle::setRadius(double r) {
radius = (r > 0) ? r : 0.1;
}
double Circle::calcArea() const {
return 3.14159 * radius * radius;
}Đặt các khai báo gọn gàng bên trong class (hoặc file header .h) để có interface rõ ràng, súc tích.
Triển khai các method bằng cú pháp ClassName::MethodName trong file nguồn .cpp.
Đánh dấu các method chỉ đọc dữ liệu bằng const để đảm bảo const-correctness và cho phép gọi trên các đối tượng const.
Điều gì xảy ra nếu bạn định nghĩa một class trong C++ mà không chỉ định access modifier nào cho các member của nó?
Tại sao các member function chỉ đọc (như calcArea()) nên được khai báo với từ khoá const?
Toán tử nào được dùng để định nghĩa một method của class ở bên ngoài thân class?
Trong C++, mức truy cập mặc định của class là private. Ngược lại, struct mặc định là public. Đây là sự khác biệt cú pháp cơ bản giữa class và struct trong C++.
Thêm từ khoá const vào cuối method đảm bảo rằng method đó sẽ không làm thay đổi *this. Nếu một object được khai báo là const Circle c(5.0);, thì chỉ có các member function được đánh dấu const mới có thể được gọi trên nó.
Cú pháp ReturnType ClassName::MethodName(params) cho trình biên dịch biết rằng MethodName thuộc về scope của ClassName. Toán tử :: dùng để xác định namespace membership.
Access Modifier · Các Biện Pháp Bảo Vệ Encapsulation · Friend Function & Friend Class
Private · Protected · Public — Kiểm Soát Khả Năng Hiển Thị Dữ Liệu · Bảo Vệ Trạng Thái Object Theo Chiều Sâu
class VaultAccount {
private:
double secretBalance; // CHỈ trong VaultAccount
protected:
string accountTier; // VaultAccount + các class con
public:
string ownerName; // Truy cập được từ bất kỳ đâu
};class BankAccount {
private:
string accountNumber;
double balance;
public:
BankAccount(string id, double init) {
accountNumber = id;
balance = (init >= 0) ? init : 0;
}
// Getter: Truy cập chỉ đọc
double getBalance() const { return balance; }
// Setter với ràng buộc quy tắc nghiệp vụ
bool deposit(double amount) {
if (amount <= 0) return false;
balance += amount;
return true;
}
bool withdraw(double amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
};acc.balance = -1000 là bất khả thi — field là private.
Lệnh rút tiền không thể vượt quá số dư hiện có. Class tự quản lý quy tắc của mình.
Mọi thay đổi đều đi qua các setter có kiểm tra, giúp dễ dàng truy vết lỗi.
Ngoại lệ Encapsulation có Kiểm soát · Từ khoá friend
class Transaction;
class Account {
private:
double balance;
string pinCode;
friend class Transaction; // Grant full access
public:
Account(double b, string pin)
: balance(b), pinCode(pin) {}
};
class Transaction {
public:
bool transfer(Account& from, Account& to,
double amount, string pin) {
if (from.pinCode != pin) return false; // private!
if (from.balance < amount) return false;
from.balance -= amount; // private!
to.balance += amount;
return true;
}
};Account không thể truy cập dữ liệu private của Transaction chỉ vì Transaction là friend của Account.
Các class con của Transaction KHÔNG được kế thừa đặc quyền friendship.
Friendship là sự liên kết có chủ đích. Lạm dụng sẽ phá vỡ encapsulation và khiến code khó bảo trì hơn.
Khởi tạo object · Danh sách khởi tạo member · Dọn dẹp trong destructor
Khởi tạo vòng đời tự động · Constructor mặc định & có tham số
class Product {
private:
string name;
double price;
int stock;
public:
// 1. Default Constructor
Product()
: name("Unnamed"), price(0.0), stock(0) {}
// 2. Parameterized with Member Initializer List
Product(string n, double p, int s)
: name(n),
price(p >= 0 ? p : 0.0),
stock(s >= 0 ? s : 0) {
// Body for secondary setup
}
};Tên constructor phải trùng khớp chính xác với tên class.
Kể cả void — trình biên dịch tự xử lý điều này.
Khởi tạo trực tiếp các member trong quá trình cấp phát bộ nhớ — tránh việc default-construction + gán lại thừa. Bắt buộc đối với các member const và tham chiếu.
class Order {
private:
int orderId;
string customerName;
double totalAmount;
public:
// Overload 1: Empty order
Order() : orderId(0), customerName("Guest"), totalAmount(0.0) {}
// Overload 2: Standard order
Order(int id, string name) : orderId(id), customerName(name), totalAmount(0.0) {}
// Overload 3: Completed order with amount
Order(int id, string name, double total) : orderId(id), customerName(name), totalAmount(total) {}
};
int main() {
Order o1; // Calls Overload 1
Order o2(101, "Nguyen Van A"); // Calls Overload 2
Order o3(102, "Tran Thi B", 450000.0); // Calls Overload 3
return 0;
}Không có đối số — tạo đơn hàng mặc định cho Guest
ID + tên — đơn hàng đang mở, chưa có tổng tiền
ID + tên + tổng tiền — bản ghi đơn hàng đầy đủ
Giải phóng object · Giải phóng tài nguyên · Thứ tự dọn dẹp tự động
class DynamicArray {
private:
int* data;
int size;
public:
DynamicArray(int s) : size(s) {
data = new int[size]; // Acquire resource
cout << "Allocated array of size " << size << endl;
}
~DynamicArray() {
delete[] data; // Release deterministically!
cout << "Deallocated array of size " << size << endl;
}
};Được đặt tên là ~ClassName() — trình biên dịch tự động nhận diện.
Không nhận đối số, không có kiểu return. Một class chỉ có duy nhất MỘT destructor.
C++ đảm bảo destructor luôn được thực thi ngay cả khi hàm thoát sớm hoặc ném ngoại lệ — ngăn rò rỉ bộ nhớ và file handle.
Nếu class Beta được khai báo là friend của class Alpha, điều nào sau đây là đúng?
Khi các object trên stack ra khỏi scope, destructor của chúng chạy theo thứ tự nào?
Quan hệ friend trong C++ có tính một chiều và không có tính đối xứng. Khai báo friend class Beta; bên trong Alpha cho phép Beta truy cập các thành phần bí mật của Alpha, nhưng Alpha không được phép truy cập ngược lại vào Beta.
Các stack frame được thu hồi theo thứ tự Vào Sau Ra Trước (LIFO). object được khởi tạo cuối cùng sẽ bị hủy đầu tiên, phù hợp với mô hình bộ nhớ stack của lời gọi hàm.
Cấp phát bộ nhớ trên heap (new/delete) · Truy cập qua pointer (->) · Mảng các instance
Con trỏ đến Class · Khởi tạo trên Heap · Toán tử Mũi Tên (->)
class Product {
private:
string name;
double price;
public:
Product(string n, double p) : name(n), price(p) {}
void display() const {
cout << name << ": " << price << " VND" << endl;
}
};
int main() {
// 1. Pointer to STACK object
Product p1("Mouse", 250000);
Product* ptr1 = &p1;
ptr1->display(); // Arrow operator
// 2. Dynamic HEAP object
Product* ptr2 = new Product("Monitor 27-inch", 4500000);
ptr2->display();
delete ptr2; // Mandatory cleanup!
ptr2 = nullptr;
return 0;
}Toán tử arrow là cú pháp rút gọn cho việc giải tham chiếu kết hợp với truy cập member:
Trỏ đến vùng nhớ stack. Bộ nhớ được giải phóng tự động khi thoát khỏi scope.
Trỏ đến vùng nhớ heap. Bắt buộc phải gọi delete tường minh để tránh rò rỉ bộ nhớ.
Mảng Cố Định Các Instance · Khởi Tạo · Duyệt Tập Hợp
class Employee {
private:
int id;
string name;
public:
Employee() : id(0), name("N/A") {} // REQUIRED!
Employee(int i, string n) : id(i), name(n) {}
void print() const {
cout << "[" << id << "] " << name << endl;
}
};
int main() {
Employee staff[3] = {
Employee(101, "Nguyen Van A"),
Employee(102, "Tran Thi B"),
Employee(103, "Le Van C")
};
for (int i = 0; i < 3; i++) {
staff[i].print(); // dot operator with index
}
return 0;
}ID: 101, Tên: "Nguyen Van A"
ID: 102, Tên: "Tran Thi B"
ID: 103, Tên: "Le Van C"
Quan hệ "Is-A" · Base Class & Derived Class · Tái sử dụng code không lặp lại
Cú Pháp Dẫn Xuất · Xây Dựng Quan Hệ Cha-Con
// Base Class (Parent)
class Employee {
protected:
string name;
double baseSalary;
public:
Employee(string n, double s)
: name(n), baseSalary(s) {}
void printBase() const {
cout << name << " | Base: " << baseSalary;
}
};
// Derived Class: "Developer IS AN Employee"
class Developer : public Employee {
private:
string techStack;
public:
Developer(string n, double s, string tech)
: Employee(n, s), techStack(tech) {}
void printDev() const {
printBase();
cout << " | Tech: " << techStack << endl;
}
};Developer tự động có name và baseSalary — không cần khai báo lại.
Developer IS AN Employee. Bất cứ nơi nào cần Employee, Developer đều có thể được sử dụng.
Developer bổ sung techStack trên nền base class — chuyên biệt hóa mà không cần sửa đổi.
Các Chế Độ Inheritance: Public, Protected và Private
Biểu diễn quan hệ subtyping thực sự ("Là-Một"). Một instance của Developer có thể thay thế cho Employee ở bất kỳ đâu cần thiết.
Các member private của base class không bao giờ có thể truy cập trực tiếp trong lớp con, bất kể chế độ inheritance nào được chọn.
Chuỗi Constructor · Thứ Tự Khởi Tạo từ Base · Thứ Tự Hủy Ngược Lại
Khởi tạo Base Class tường minh · Thứ tự Khởi tạo
class Base {
public:
Base() {
cout << "1. Base Constructor" << endl;
}
Base(int x) {
cout << "1. Base Parameterized: " << x << endl;
}
};
class Derived : public Base {
public:
// Explicitly pass parameter up to Base
Derived(int x, int y) : Base(x) {
cout << "2. Derived Constructor: " << y << endl;
}
};
int main() {
Derived obj(10, 20);
return 0;
}Constructor của base class chạy đầu tiên để thiết lập nền tảng — khởi tạo các member được kế thừa.
Sau khi nền tảng của lớp cha đã sẵn sàng, constructor của derived class mới thực thi phần thân để bổ sung hành vi chuyên biệt.
Hủy theo thứ tự LIFO · Giải phóng tài nguyên của lớp con trước
class Base {
public:
~Base() {
cout << "2. Base Destructor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "1. Derived Destructor" << endl;
}
};
int main() {
{
Derived d;
} // Scope kết thúc — cả hai destructor được gọi
return 0;
}Nền tảng được xây dựng trước khi thêm các tính năng chuyên biệt.
Lớp con bổ sung tính năng trên nền tảng đã sẵn sàng.
Lớp con có thể phụ thuộc vào tài nguyên của base khi dọn dẹp — phải giải phóng tài nguyên của chính nó trước.
Nền tảng chỉ được tháo dỡ sau khi tất cả các lớp phụ thuộc đã hoàn tất.
Protected Members · Ghi Đè Method · Upcasting và Chuyển Đổi Kiểu
Định danh protected · Giải quyết Scope bị ẩn với Base::
class Account {
protected:
double balance; // accessible to child classes
public:
Account(double b) : balance(b) {}
void printStatement() const {
cout << "Balance: " << balance << endl;
}
};
class SavingsAccount : public Account {
private:
double interestRate;
public:
SavingsAccount(double b, double r)
: Account(b), interestRate(r) {}
void applyMonthlyInterest() {
balance += balance * interestRate; // OK! protected
}
void printStatement() const {
Account::printStatement(); // Explicit base call
cout << "Interest Rate: "
<< interestRate * 100 << "%" << endl;
}
};Là cầu nối giữa đóng gói hoàn toàn (private) và truy cập tự do (public). Có thể truy cập trong class và các lớp con — không cho phép bên ngoài.
Dùng Account::printStatement() để gọi tường minh phiên bản của base class khi method đã bị override trong lớp con.
Định nghĩa lại Hành vi · Ẩn Tên Tĩnh
class Printer {
public:
void print() const {
cout << "Standard Black-and-White Print" << endl;
}
};
class ColorPrinter : public Printer {
public:
// Override print() với hành vi chuyên biệt
void print() const {
cout << "Vibrant High-Definition Color Print" << endl;
}
};
int main() {
Printer p;
p.print(); // Standard Black-and-White Print
ColorPrinter cp;
cp.print(); // Vibrant Color Print
cp.Printer::print(); // Gọi tường minh phiên bản base
return 0;
}Một derived class định nghĩa lại một method có cùng tên và chữ ký với base class để cung cấp hành vi chuyên biệt cho kiểu dữ liệu con đó.
Giải quyết tại thời điểm biên dịch (static binding) — kiểu của pointer quyết định
Giải quyết tại thời điểm chạy (dynamic binding) — kiểu thực của object quyết định
Upcasting · Base Pointers trỏ đến Derived Objects · Vấn đề Static Binding
class Employee {
public:
void introduce() const {
cout << "I am a general employee." << endl;
}
};
class Developer : public Employee {
public:
void introduce() const {
cout << "I am a C++ software architect." << endl;
}
};
int main() {
Developer dev;
// UPCASTING: Always safe and implicit
Employee* empPtr = &dev;
// What does this print?
empPtr->introduce();
return 0;
}Khi không có virtual, trình biên dịch kiểm tra kiểu của pointer (Employee*) tại compile time và liên kết với Employee::introduce().
Chúng ta có một object kiểu Developer nhưng hệ thống lại hoạt động như một Employee thông thường. upcasting đơn thuần chưa đủ để thực hiện runtime dispatch.
Chúng ta cần Virtual Functions & Polymorphism để xác định kiểu object thực sự tại runtime — sẽ được trình bày ở phần tiếp theo.
Nếu class Vehicle có protected int speed;, những hàm nào có thể truy cập speed?
Constructor thực thi theo thứ tự nào khi khởi tạo class Manager : public Employee?
Điều gì xảy ra khi một method đã được override (không phải virtual) được gọi thông qua Base* ptr = new Derived();?
protected giúp thuộc tính (attribute) ẩn với người dùng bên ngoài nhưng vẫn cho phép các derived class truy cập. Đây chính là mục đích cốt lõi của nó — hỗ trợ inheritance mà không để lộ hoàn toàn dữ liệu.
Các base class luôn được khởi tạo trước derived class. Điều này đảm bảo rằng các member được kế thừa đã tồn tại và được khởi tạo hoàn chỉnh trước khi thân constructor của lớp con bắt đầu thực thi.
Khi không có virtual, C++ sử dụng static binding (liên kết sớm). Trình biên dịch ràng buộc lời gọi dựa trên kiểu pointer được khai báo (Base*), bỏ qua kiểu đối tượng thực tế trên heap tại runtime.
Nhiều Base Class · Vấn đề Nhập nhằng · Diamond Problem & Virtual Inheritance
Cú pháp · Kết hợp các Interface · Kế thừa nhiều Base Class State
class Printable {
public:
void print() const {
cout << "Rendering to paper..." << endl;
}
};
class Serializable {
public:
void serialize() const {
cout << "Saving state to JSON..." << endl;
}
};
// Invoice inherits from BOTH base classes
class Invoice : public Printable, public Serializable {
private:
double amount;
public:
Invoice(double a) : amount(a) {}
};
int main() {
Invoice inv(500000);
inv.print(); // from Printable
inv.serialize(); // from Serializable
return 0;
}C++ cho phép một class kế thừa (inheritance) khả năng từ hai hoặc nhiều base class cùng lúc, sử dụng danh sách phân cách bằng dấu phẩy trong khai báo class.
Cung cấp hành vi print()
Cung cấp hành vi serialize()
Kết hợp cả hai khả năng + dữ liệu riêng
Sự Mơ Hồ Trong Phân Cấp Đa Đường · Giải Quyết Tổ Tiên Trùng Lặp bằng Virtual Inheritance
// VẤN ĐỀ: Hai bản sao của Device::id trong SmartPhone!
class Device { public: int id; };
class Phone : public Device {};
class Camera : public Device {};
class SmartPhone : public Phone, public Camera {};
int main() {
SmartPhone sp;
sp.id = 10; // LỖI BIÊN DỊCH: Mơ hồ!
}
// GIẢI PHÁP: Virtual Inheritance
class Device { public: int id; };
class Phone : virtual public Device {};
class Camera : virtual public Device {};
class SmartPhone : public Phone, public Camera {};
int main() {
SmartPhone sp;
sp.id = 1001; // Đúng MỘT Device dùng chung!
return 0;
}
Hàm pure virtual (= 0) · Lớp chưa hoàn chỉnh · Hợp đồng thiết kế
Pure Virtual Functions · Ngăn Chặn Khởi Tạo Trực Tiếp · Bắt Buộc Subtyping
class Shape {
public:
// Pure virtual: defines WHAT, not HOW
virtual double calcArea() const = 0;
virtual double calcPerimeter() const = 0;
virtual ~Shape() {} // Mandatory virtual destructor!
};
// Shape s; // COMPILE ERROR: Cannot instantiate!
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// Must implement all pure virtuals!
double calcArea() const override {
return 3.14159 * radius * radius;
}
double calcPerimeter() const override {
return 2 * 3.14159 * radius;
}
};Một pure virtual function được khai báo với = 0 ở cuối. Nó định nghĩa cái gì cần làm, không phải làm thế nào.
Bất kỳ class nào có ít nhất một pure virtual function đều không thể được tạo trực tiếp bằng new hoặc cấp phát trên stack.
Một derived class phải override TẤT CẢ các pure virtual function — nếu không, nó cũng trở thành abstract.
Luôn khai báo destructor của base class là virtual khi class có các virtual function.
Pure Virtual · Không Có Data Member · Abstraction Đa Hình Hoàn Toàn
// C++ Interface: all pure virtual, no data members
class IPayable {
public:
virtual bool processPayment(double amount) = 0;
virtual string getProviderName() const = 0;
virtual ~IPayable() {}
};
class VNPTPayGateway : public IPayable {
public:
bool processPayment(double amount) override {
cout << "Processing " << amount
<< " VND via VNPT Pay..." << endl;
return true;
}
string getProviderName() const override {
return "VNPT Pay";
}
};
class VietQRMethod : public IPayable {
public:
bool processPayment(double amount) override {
cout << "Generating VietQR for "
<< amount << " VND..." << endl;
return true;
}
string getProviderName() const override {
return "VietQR";
}
};Static vs Dynamic Binding · Từ khoá virtual · Cơ chế vtable
Bộ chỉ định virtual · Bộ chỉ định override · Dynamic Binding tại Runtime
class Animal {
public:
virtual void speak() const {
cout << "Generic creature sound" << endl;
}
virtual ~Animal() {}
};
class Cat : public Animal {
public:
void speak() const override {
cout << "Meow Meow!" << endl;
}
};
class Dog : public Animal {
public:
void speak() const override {
cout << "Woof Woof!" << endl;
}
};
void triggerSound(const Animal* a) {
a->speak(); // Runtime dispatch!
}
int main() {
Cat myCat;
Dog myDog;
triggerSound(&myCat); // Meow Meow!
triggerSound(&myDog); // Woof Woof!
return 0;
}Đặt virtual trước một method sẽ yêu cầu trình biên dịch kích hoạt dynamic (late) binding cho hàm đó.
Dùng override trong derived class để yêu cầu trình biên dịch xác minh rằng bạn đang thực sự override một virtual function — tránh lỗi sai tên method âm thầm.
triggerSound nhận vào một Animal* nhưng gọi đúng cài đặt dựa trên kiểu object thực tế tại runtime.
Tập hợp Dị Thể · Cơ chế Virtual Table (vtable)
// Mọi class có virtual functions đều có một vptr ẩn
// Object Cat trong bộ nhớ:
// ┌─────────────────────────┐
// │ vptr ──────────────────►│ vtable: Cat::speak() │
// ├─────────────────────────┤ └──────────────────────┘
// │ Các attribute của Cat │
// └─────────────────────────┘
// Quy trình Runtime Dispatch 3 bước:
// 1. Lấy con trỏ object: a
// 2. Theo a->vptr → vtable của Cat
// 3. Nhảy đến Cat::speak()
// Virtual Destructor là BẮT BUỘC:
class Base {
public:
virtual ~Base() {} // Không có dòng này: destructor của derived bị bỏ qua!
};Compiler tự động chèn một hidden pointer (vptr) vào mọi object của class có virtual functions.
Tại runtime, vptr trỏ đến virtual table của class — một bảng tra cứu chứa các địa chỉ hàm.
Địa chỉ hàm chính xác được lấy từ vtable và được gọi — thực hiện runtime dispatch đến Cat::speak() ngay cả khi truy cập qua Animal*.
Bản thiết kế vs Thực thể · Blueprint định nghĩa cấu trúc, instance lưu trữ trạng thái
Trạng thái (data member) + Hành vi (member function) — thống nhất trong một kiểu dữ liệu
Các access modifier: private / protected / public — nguyên tắc vàng trong thiết kế class
Đặc quyền có chọn lọc — ngoại lệ một chiều, không được kế thừa
Danh sách khởi tạo, nạp chồng, RAII — vòng đời xác định
new/delete, toán tử mũi tên (->), tập hợp liên tiếp trong bộ nhớ
Đơn cấp, đa cấp, đa kế thừa — quan hệ Is-A và tái sử dụng mã nguồn
public (99%), protected, private — kiểm soát sự lan truyền quyền truy cập
Khởi tạo Base→Child, huỷ Child→Base (LIFO)
Giải quyết diamond problem — chỉ một instance dùng chung của lớp tổ tiên
Pure virtual (= 0), mẫu interface trong C++ — hợp đồng thiết kế
virtual, override, vtable — runtime dispatch thông qua con trỏ base class
Đóng gói (encapsulation) các attribute sku, price, stock với virtual display() và calcValue() — nền tảng cho polymorphism.
FoodProduct (thêm kiểm tra ngày hết hạn) và Electronics (thêm tính toán bảo hành) — các lớp chuyên biệt hoá thông qua override.
Pure virtual calcDiscount(double subtotal) = 0 được triển khai bởi PercentDiscount và VipFixedDiscount.
Quản lý một tập hợp đa hình (polymorphic) Product* inventory[100] với dynamic dispatch tự động khi thanh toán.
void Store::sell(int index, int qty,
IDiscountStrategy* promo) {
double baseTotal =
inventory[index]->getPrice() * qty;
double finalTotal = baseTotal -
(promo ? promo->calcDiscount(baseTotal) : 0);
inventory[index]->deductStock(qty);
cout << "Final Bill: "
<< finalTotal << " VND" << endl;
}
Chương 3 — Lập trình hướng đối tượng với C++