Chương 3 — Lập trình hướng đối tượng với C++

INT1339 — Ngôn ngữ lập trình C++

Classes

Bản thiết kế · Attribute · Method

Encapsulation

Private · Protected · Public

Inheritance

Base class · Derived class · Hệ thống phân cấp

Polymorphism

Virtual · Override · vtable

Abstraction

Pure virtual · Interface

Nội dung Chương 3

Part I

Classes — Giáo trình Chương 5

01

Khái niệm Object Classes

Định nghĩa, khởi tạo instance và sử dụng object

02

Thành phần của Class

Attribute (data member) và method (member function)

03

Phạm vi truy cập của Class

Access modifier, friend function & friend class

04

Constructor và Destructor

Khởi tạo, nạp chồng và vòng đời của object

05

Object Pointer & Array

Cấp phát động, toán tử mũi tên, tập hợp object

Part II

Inheritance & Polymorphism — Chương 6

01

Khái niệm Inheritance

Base class & derived class, các chế độ kế thừa

02

Constructor & Destructor trong Inheritance

Thứ tự thực thi và danh sách khởi tạo

03

Truy cập Member trong Inheritance

Protected access, ghi đè (override), upcasting

04

Đa kế thừa (Multiple Inheritance)

Nhiều base class, mơ hồ tên và Diamond Problem

05

Abstract Base Class

Pure virtual function và mẫu interface trong C++

06

Polymorphism & Dynamic Binding

Virtual function, override và cơ chế vtable

Sau Chương Này, Sinh Viên Có Thể:

1

Nắm Vững Kiến Trúc Class

Đị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.

2

Kiểm Soát Scope & Friendship

Áp dụng đúng các access modifier và khai báo friend function/class khi cần thiết.

3

Quản Lý Bộ Nhớ Object

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.

4

Xây Dựng Hệ Thống Inheritance

Hiện thực inheritance đơn, đa cấp, và đa kế thừa đồng thời ngăn chặn diamond problem.

5

Hiện Thực Dynamic Polymorphism

Sử dụng virtual function và con trỏ base class cho dynamic binding tại runtime với cơ chế vtable dispatch.

6

Thiết Kế Với Abstract Class

Xây dựng hợp đồng pure virtual (= 0) và phát triển hệ thống mô-đun như MiniPOS V3.0.

Từ Struct đến Class: Sự Chuyển Đổi Mô Hình OOP

Chương 2: Plain Data Struct

struct BankAccount {
  string owner;
  double balance;
};
BankAccount acc = {"Alice", 1000000};
acc.balance = -999999; // Anyone can corrupt!

Giải Pháp OOP: The Class

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.

Trạng Thái Ẩn

Private fields

Giao Tiếp An Toàn

Public methods

Ghi Có Kiểm Tra

Logic nghiệp vụ

Part I · Classes

Khái Niệm Object Classes

Định nghĩa Classes · Khởi tạo Objects · Blueprint và Instance

Part I · Classes

Định Nghĩa Object Classes

Cú Pháp Class · Khai Báo Member · Quy Tắc Dấu Chấm Phẩy

Class vs Object: Bản Thiết Kế & Ngôi Nhà

Class — Bản Thiết Kế

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.

Object — Instance Cụ Thể

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ó.

Cú Pháp và Cấu Trúc Class

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!

Các Quy Tắc Quan Trọng

1

Định Danh PascalCase

Sử dụng từ khóa class theo sau là tên class viết theo kiểu PascalCase.

2

Thân Class Trong Dấu Ngoặc Nhọn

Bao toàn bộ khai báo member trong { ... }.

3

Dấu Chấm Phẩy Bắt Buộc

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.

4

Mặc Định: private

Khác với struct (mặc định là public), class mặc định tất cả các member là private.

Part I · Classes

Sử dụng Object Classes

Khởi tạo Objects · Toán tử Dot · Khởi tạo trên Stack và Heap

Tạo Object và Truy Cập Member

#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;
}

Các Cơ Chế Chính

Toán Tử Dot (.)

Truy cập các method và biến public trên một object instance: item1.display()

Encapsulation Được Đảm Bảo

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.

Part I · Classes

Thành Phần của Class: Attributes & Methods

Biểu diễn Dữ liệu (Trạng thái) · Các Thao tác Chức năng (Hành vi)

Part I · Classes

Class Attributes

Lưu trữ Trạng thái · Các loại Attribute · Dung lượng bộ nhớ trên mỗi Instance

Attributes: Biểu Diễn Trạng Thái của Object

Data Member trong Code

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.

Độc Lập về Bộ Nhớ

student1

studentId: "B23DCCN001"
fullName: "Nguyen Van An"
midtermScore: 8.5
finalScore: 7.5

student2

studentId: "B23DCCN002"
fullName: "Le Thi Binh"
midtermScore: 9.0
finalScore: 9.5

Part I · Classes

Class Methods

Định nghĩa nội tuyến · Toán tử Scope Resolution (::) · Từ khoá const trong method

Hàm thành viên (Member Functions): Bên trong vs Bên ngoài Class

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;
}

Thực hành tốt nhất

Khai báo bên trong

Đặ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.

Định nghĩa bên ngoài

Triển khai các method bằng cú pháp ClassName::MethodName trong file nguồn .cpp.

const cho dữ liệu chỉ đọc

Đá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.

Kiểm Tra: Định Nghĩa Class & Các Thành Phần

Câu hỏi 1

Đ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ó?

  • A. Tất cả các member mặc định là public
  • B. Tất cả các member mặc định là protected
  • C. Tất cả các member mặc định là private
  • D. Code sẽ phát sinh lỗi biên dịch

Câu hỏi 2

Tại sao các member function chỉ đọc (như calcArea()) nên được khai báo với từ khoá const?

  • A. Để giúp chúng chạy nhanh hơn bằng cách lưu bộ nhớ đệm kết quả
  • B. Để đảm bảo chúng không thể thay đổi bất kỳ attribute nào và cho phép gọi trên các const object
  • C. Để làm cho hàm có thể truy cập mà không cần object
  • D. Để cho phép hàm trả về nhiều giá trị

Câu hỏi 3

Toán tử nào được dùng để định nghĩa một method của class ở bên ngoài thân class?

  • A. Toán tử mũi tên (->)
  • B. Toán tử chấm (.)
  • C. Toán tử scope resolution (::)
  • D. Toán tử lấy địa chỉ (&)

Checkpoint: Định Nghĩa Class & Các Thành Phần — Đáp Án

Q1: C — Tất cả member mặc định là private ✓

Trong C++, mức truy cập mặc định của classprivate. 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++.

Q2: B — Đảm bảo không thay đổi dữ liệu, cho phép gọi trên đối tượng const ✓

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ó.

Q3: C — Toán Tử Scope Resolution (::) ✓

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.

Part I · Classes

Phạm Vi Truy Cập & Friend trong Class

Access Modifier · Các Biện Pháp Bảo Vệ Encapsulation · Friend Function & Friend Class

Part I · Classes

Phạm Vi Truy Cập

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

Access Modifier: Trụ Cột của Encapsulation

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
};

Getter, Setter và Kiểm Tra Logic Nghiệp Vụ

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;
  }
};

Tại Sao Điều Này Quan Trọng

Không Thể Thay Đổi Trực Tiếp

acc.balance = -1000 là bất khả thi — field là private.

Bất Biến Được Đảm Bảo

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.

Điểm Kiểm Soát Duy Nhất

Mọi thay đổi đều đi qua các setter có kiểm tra, giúp dễ dàng truy vết lỗi.

Part I · Classes

Friend Functions

Ngoại lệ Encapsulation có Kiểm soát · Từ khoá friend

Friend Class: Các Hệ Thống Con Liên Kết Chặt Chẽ

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;
  }
};

Quy Tắc Thiết Kế Cho Friendship

Không Có Tính Đối Xứng

Account không thể truy cập dữ liệu private của Transaction chỉ vì Transaction là friend của Account.

Không Được Kế Thừa

Các class con của Transaction KHÔNG được kế thừa đặc quyền friendship.

Dùng Tiết Kiệm

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.

Part I · Classes

Constructors và Destructors

Khởi tạo object · Danh sách khởi tạo member · Dọn dẹp trong destructor

Part I · Classes

Constructors

Khởi tạo vòng đời tự động · Constructor mặc định & có tham số

Constructor: Khởi Tạo Object An Toàn

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
  }
};

Các Điểm Cốt Lõi của Constructor

Cùng Tên với Class

Tên constructor phải trùng khớp chính xác với tên class.

Không Có Kiểu Trả Về

Kể cả void — trình biên dịch tự xử lý điều này.

Initializer Lists

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.

Constructor Overloading: Nhiều Cách Khởi Tạo Đối Tượng

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;
}

Overload 1

Không có đối số — tạo đơn hàng mặc định cho Guest

Overload 2

ID + tên — đơn hàng đang mở, chưa có tổng tiền

Overload 3

ID + tên + tổng tiền — bản ghi đơn hàng đầy đủ

Part I · Classes

Destructor

Giải phóng object · Giải phóng tài nguyên · Thứ tự dọn dẹp tự động

Destructor: Thu Hồi Tài Nguyên Tất Định

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;
  }
};

Quy Tắc Destructor

Tiền Tố Dấu Ngã

Được đặt tên là ~ClassName() — trình biên dịch tự động nhận diện.

Không Có Tham Số

Không nhận đối số, không có kiểu return. Một class chỉ có duy nhất MỘT destructor.

Nguyên Tắc RAII

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.

Kiểm Tra: Phạm Vi Truy Cập & Vòng Đời

Câu hỏi 1

Nếu class Beta được khai báo là friend của class Alpha, điều nào sau đây là đúng?

  • A. Alpha có thể truy cập các member private của Beta
  • B. Beta có thể truy cập các member private của Alpha
  • C. Cả hai class đều có thể truy cập member private của nhau
  • D. Các lớp con của Beta kế thừa quyền truy cập vào Alpha

Câu hỏi 2

Khi các object trên stack ra khỏi scope, destructor của chúng chạy theo thứ tự nào?

  • A. Tạo trước, hủy trước (FIFO)
  • B. Tạo sau, hủy trước (LIFO / Thứ tự ngược)
  • C. Theo thứ tự bảng chữ cái của tên biến
  • D. Destructor chỉ chạy khi được gọi thủ công

Kiểm Tra: Phạm Vi Truy Cập & Vòng Đời — Đáp Án

Q1: B — Beta có thể truy cập các member private của Alpha ✓

Quan hệ friend trong C++ có tính một chiềukhô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.

Q2: B — Tạo sau, hủy trước (LIFO) ✓

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.

Part I · Classes

Object Pointer & Object Array

Cấp phát bộ nhớ trên heap (new/delete) · Truy cập qua pointer (->) · Mảng các instance

Part I · Classes

Object Pointers

Con trỏ đến Class · Khởi tạo trên Heap · Toán tử Mũi Tên (->)

Con Trỏ Object và Cấp Phát Heap

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ử Mũi Tên (->)

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:

Stack Pointer

Trỏ đến vùng nhớ stack. Bộ nhớ được giải phóng tự động khi thoát khỏi scope.

Heap Pointer

Trỏ đến vùng nhớ heap. Bắt buộc phải gọi delete tường minh để tránh rò rỉ bộ nhớ.

Part I · Classes

Mảng Đối Tượng

Mảng Cố Định Các Instance · Khởi Tạo · Duyệt Tập Hợp

Object Arrays: Tập hợp các Thực thể

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;
}

Bố cục Bộ nhớ

staff[0]

ID: 101, Tên: "Nguyen Van A"

staff[1]

ID: 102, Tên: "Tran Thi B"

staff[2]

ID: 103, Tên: "Le Van C"

Part II · Inheritance & Polymorphism

Khái niệm Inheritance

Quan hệ "Is-A" · Base Class & Derived Class · Tái sử dụng code không lặp lại

Part II · Inheritance

Khai Báo Inheritance

Cú Pháp Dẫn Xuất · Xây Dựng Quan Hệ Cha-Con

Inheritance: Mô Hình Hóa Quan Hệ Phân Cấp

// 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;
  }
};

Inheritance Mang Lại Điều Gì

Tái Sử Dụng Code

Developer tự động có namebaseSalary — không cần khai báo lại.

Quan Hệ Is-A

Developer IS AN Employee. Bất cứ nơi nào cần Employee, Developer đều có thể được sử dụng.

Mở Rộng (Extension)

Developer bổ sung techStack trên nền base class — chuyên biệt hóa mà không cần sửa đổi.

Part II · Inheritance

Các Chế Độ Dẫn Xuất

Các Chế Độ Inheritance: Public, Protected và Private

Các Chế Độ inheritance: public, protected, và private

Dùng public 99% trường hợp

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

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.

Part II · Inheritance

Constructor & Destructor trong Inheritance

Chuỗi Constructor · Thứ Tự Khởi Tạo từ Base · Thứ Tự Hủy Ngược Lại

Part II · Inheritance

Constructors trong Inheritance

Khởi tạo Base Class tường minh · Thứ tự Khởi tạo

Chuỗi Constructor: Quy tắc Base-First

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;
}

Quy tắc Base-First

Bước 1: Base chạy trước

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.

Bước 2: Derived chạy sau

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.

Part II · Inheritance

Destructor trong Kế Thừa

Hủy theo thứ tự LIFO · Giải phóng tài nguyên của lớp con trước

Destructors trong Inheritance: Quy tắc Derived-First

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;
}

Tại Sao Theo Thứ Tự Ngược?

Base Được Khởi Tạo Trước

Nền tảng được xây dựng trước khi thêm các tính năng chuyên biệt.

Derived Được Khởi Tạo Sau

Lớp con bổ sung tính năng trên nền tảng đã sẵn sàng.

Derived Bị Huỷ Trước

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.

Base Bị Huỷ Sau Cùng

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.

Part II · Inheritance

Truy Cập Member Kế Thừa

Protected Members · Ghi Đè Method · Upcasting và Chuyển Đổi Kiểu

Part II · Inheritance

Sử dụng Member của Base Class

Định danh protected · Giải quyết Scope bị ẩn với Base::

Thành Viên Protected và Giải Quyết Phạm Vi

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;
  }
};

Khái Niệm Chính

Vai trò của protected

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.

Scope Resolution (Base::)

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.

Part II · Inheritance

Ghi đè Method của Base Class

Định nghĩa lại Hành vi · Ẩn Tên Tĩnh

Ghi đè Method trong Derived Class

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;
}

Cơ chế Override

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 đó.

Không có virtual

Giải quyết tại thời điểm biên dịch (static binding) — kiểu của pointer quyết định

Có virtual

Giải quyết tại thời điểm chạy (dynamic binding) — kiểu thực của object quyết định

Part II · Inheritance

Chuyển Đổi Kiểu: Base & Derived

Upcasting · Base Pointers trỏ đến Derived Objects · Vấn đề Static Binding

Upcasting: Con Trỏ Base Trỏ Đến Đối Tượng Derived

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;
}

Vấn Đề Static Binding

Tại Sao Base Được Gọi?

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().

Vấn Đề

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.

Giải Pháp

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.

Kiểm Tra: Khái Niệm & Cơ Chế Inheritance

Câu hỏi 1

Nếu class Vehicleprotected int speed;, những hàm nào có thể truy cập speed?

  • A. Chỉ các member function của Vehicle
  • B. Các member function của Vehicle và bất kỳ derived class nào
  • C. Bất kỳ hàm bên ngoài nào trong main()
  • D. Chỉ các friend function của Vehicle

Câu hỏi 2

Constructor thực thi theo thứ tự nào khi khởi tạo class Manager : public Employee?

  • A. Constructor của Manager chạy trước, sau đó mới đến Employee
  • B. Constructor của Employee chạy trước, sau đó mới đến Manager
  • C. Chỉ có constructor của Manager chạy
  • D. Cả hai chạy đồng thời trên các luồng song song

Câu hỏi 3

Đ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();?

  • A. Method của derived class được thực thi
  • B. Method của base class được thực thi (static binding tại thời điểm biên dịch)
  • C. Chương trình ném ra một ngoại lệ tại runtime
  • D. Trình biên dịch từ chối build chương trình

Checkpoint: Inheritance Concepts & Mechanics — Đáp Án

C1: B — Vehicle và tất cả các derived class ✓

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.

C2: B — Employee trước, rồi mới đến Manager ✓

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.

C3: B — Phương thức của base class (static binding) ✓

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.

Part II · Inheritance

Multiple Inheritance

Nhiều Base Class · Vấn đề Nhập nhằng · Diamond Problem & Virtual Inheritance

Part II · Inheritance

Khai Báo Multiple Inheritance

Cú pháp · Kết hợp các Interface · Kế thừa nhiều Base Class State

Multiple Inheritance: Cú Pháp và Khả Năng

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;
}

Nhận Xét Thiết Kế

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.

Printable

Cung cấp hành vi print()

Serializable

Cung cấp hành vi serialize()

Invoice

Kết hợp cả hai khả năng + dữ liệu riêng

Part II · Inheritance

Diamond Problem

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

Diamond Problem và Virtual Base Classes

// 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;
}

Cấu trúc Kế thừa Hình Thoi

Part II · Inheritance

Lớp Cơ Sở Abstract

Hàm pure virtual (= 0) · Lớp chưa hoàn chỉnh · Hợp đồng thiết kế

Part II · Inheritance

Khai Báo Abstract Base Classes

Pure Virtual Functions · Ngăn Chặn Khởi Tạo Trực Tiếp · Bắt Buộc Subtyping

Abstract Class: Hợp Đồng Thuần Túy

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;
  }
};

Quy Tắc Abstract Class

1

Cú Pháp = 0

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.

2

Không Thể Khởi Tạ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.

3

Derived Class Phải Cài Đặt

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.

4

Virtual Destructor

Luôn khai báo destructor của base class là virtual khi class có các virtual function.

Part II · Inheritance

Mẫu Interface trong C++

Pure Virtual · Không Có Data Member · Abstraction Đa Hình Hoàn Toàn

Pure Interfaces: Kiến Trúc Plug-and-Play

// 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";
  }
};

Interface vs Abstract Class

Part II · Polymorphism

Polymorphism & Dynamic Binding

Static vs Dynamic Binding · Từ khoá virtual · Cơ chế vtable

Part II · Polymorphism

Virtual Methods và Runtime Dispatch

Bộ chỉ định virtual · Bộ chỉ định override · Dynamic Binding tại Runtime

Polymorphism: Một Interface, Vô Số Hành Vi

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ừ khoá virtual Thay Đổi Mọi Thứ Như Thế Nào

Từ khoá virtual

Đặ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 đó.

Đặc tả override

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.

Runtime Dispatch

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.

Part II · Polymorphism

Polymorphism trong Thực Tiễn

Tập hợp Dị Thể · Cơ chế Virtual Table (vtable)

Bên Trong Cơ Chế: vtable Hoạt Động Như Thế Nào

// 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!
};

Cơ Chế vtable

01

Chèn vptr

Compiler tự động chèn một hidden pointer (vptr) vào mọi object của class có virtual functions.

02

Tra Cứu vtable

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.

03

Nhảy Đến 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*.

Tóm Tắt Chương 3: Kiến Trúc OOP Toàn Diện

Part I · Classes

Chương 5 Giáo Trình

Khái Niệm Class & Object

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

Attribute & Method

Trạng thái (data member) + Hành vi (member function) — thống nhất trong một kiểu dữ liệu

Encapsulation

Các access modifier: private / protected / public — nguyên tắc vàng trong thiết kế class

Hàm & Class friend

Đặc quyền có chọn lọc — ngoại lệ một chiều, không được kế thừa

Constructor & Destructor

Danh sách khởi tạo, nạp chồng, RAII — vòng đời xác định

Pointer & Array của Object

new/delete, toán tử mũi tên (->), tập hợp liên tiếp trong bộ nhớ

Part II · Inheritance & Polymorphism

Chương 6 Giáo Trình

Phân Cấp Inheritance

Đơn cấp, đa cấp, đa kế thừa — quan hệ Is-A và tái sử dụng mã nguồn

Chế Độ Dẫn Xuất

public (99%), protected, private — kiểm soát sự lan truyền quyền truy cập

Thứ Tự Constructor & Destructor

Khởi tạo Base→Child, huỷ Child→Base (LIFO)

Virtual Inheritance

Giải quyết diamond problem — chỉ một instance dùng chung của lớp tổ tiên

Abstract Class & Interface

Pure virtual (= 0), mẫu interface trong C++ — hợp đồng thiết kế

Dynamic Polymorphism

virtual, override, vtable — runtime dispatch thông qua con trỏ base class

Workshop: Kiến Trúc MiniPOS V3.0 & Các Bước Tiếp Theo

Bản Thiết Kế Triển Khai MiniPOS V3.0

01

Base class: Product

Đóng gói (encapsulation) các attribute sku, price, stock với virtual display()calcValue() — nền tảng cho polymorphism.

02

Các Derived class

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.

03

Interface IDiscountStrategy

Pure virtual calcDiscount(double subtotal) = 0 được triển khai bởi PercentDiscountVipFixedDiscount.

04

Class Store

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.

Đoạn Code Thanh Toán Đa Hình (Polymorphic Checkout)

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;
}