0

[C++ OOP Thực Chiến] Bài 12: Hàm khởi tạo mặc định (Default Constructor) - Cách C++ "chào đời" một Object!

Chào anh em! Ở cuối [Bài 11], chúng ta đã thiết kế Class Character với một Constructor (Hàm khởi tạo) yêu cầu truyền vào 3 tham số: Tên, Máu, Sát thương.

Character hero("Bố Đời", 1000, 150); // Chạy ngon lành

Nhưng nếu bạn muốn tạo ra một nhân vật rỗng (quái vật cùi bắp, hoặc NPC đứng đường) mà không muốn mất công truyền tham số:

Character npc; // BÙM! LỖI COMPILER NGAY LẬP TỨC!

Tại sao lại có lỗi này? Để hiểu rõ, chúng ta phải đi sâu vào cơ chế Default Constructor (Hàm khởi tạo mặc định) của C++.

1. Bữa trưa miễn phí và "Giá trị rác"

Hàm khởi tạo (Constructor) là một hàm đặc biệt:

  1. Tên hàm GIỐNG HỆT tên Class.
  2. KHÔNG có kiểu trả về (kể cả void).
  3. Tự động chạy ĐÚNG 1 LẦN duy nhất khi Object được cấp phát memory.

Quy tắc ngầm thứ 1: Nếu bạn lười biếng, không thèm viết bất kỳ Constructor nào cho Class, C++ sẽ tặng bạn một "Bữa trưa miễn phí". Nó tự động sinh ra một Default Constructor vô hình không nhận tham số nào cả. Nhờ vậy, bạn mới có thể gõ Character npc; mà không bị lỗi.

Nhưng đời không như mơ! Cái Default Constructor miễn phí này làm việc rất "vô trách nhiệm". Nó chỉ xin RAM cho Object của bạn, chứ không dọn dẹp RAM. Kết quả là các thuộc tính kiểu số (int, float, con trỏ...) của bạn sẽ mang giá trị rác (Garbage Value) - những con số ngẫu nhiên còn sót lại từ phần mềm trước đó dùng vùng RAM này.

Nếu hệ thống Backend của bạn dùng giá trị rác này để tính tiền, thì toang thật sự!

2. Sự tàn nhẫn của C++: Đã cho thì được quyền đòi lại!

Quay lại với lỗi ở Bài 11. Tại sao Character npc; lại văng lỗi?

Quy tắc ngầm thứ 2: Ngay giây phút bạn tự tay định nghĩa MỘT Constructor bất kỳ (ví dụ cái Constructor nhận 3 tham số của chúng ta), C++ sẽ mặc định: "À, thằng Dev này biết cách khởi tạo rồi, mình rút lại cái Default Constructor miễn phí thôi!".

Và thế là, Class Character của bạn không còn hàm khởi tạo không-tham-số nữa. Lệnh Character npc; thất bại vì C++ không tìm thấy hàm nào khớp với việc "không truyền gì cả".

3. Code Demo: Giành lại quyền kiểm soát

Để hệ thống vừa linh hoạt (cho phép tạo NPC mặc định), vừa tùy biến (cho phép tạo Hero truyền số liệu), Kỹ sư C++ sẽ sử dụng kỹ thuật Overloading (Nạp chồng hàm) - viết nhiều Constructor cùng lúc!

Đồng thời, chúng ta sẽ tự tay khởi tạo giá trị an toàn (Default values) để dập tắt hiểm họa "giá trị rác".

#include <iostream>
#include <string>

using namespace std;

class Character {
private:
    string name;
    int hp;
    int damage;

public:
    // 1. DEFAULT CONSTRUCTOR (Hàm khởi tạo mặc định do ta tự viết)
    // Được gọi khi dùng cú pháp: Character npc;
    Character() {
        name = "Unknown NPC";
        hp = 100; // Khởi tạo an toàn, tạm biệt giá trị rác!
        damage = 10;
        cout << "[LOG] Da tao ra mot NPC mac dinh.\n";
    }

    // 2. PARAMETERIZED CONSTRUCTOR (Hàm khởi tạo có tham số)
    // Được gọi khi dùng cú pháp: Character hero("Bố Đời", 1000, 150);
    Character(string n, int h, int d) {
        name = n;
        hp = h;
        damage = d;
        cout << "[LOG] Da tao ra Hero: " << name << "\n";
    }

    void showStats() const {
        cout << "- " << name << " | HP: " << hp << " | DMG: " << damage << "\n";
    }
};

int main() {
    cout << "--- HE THONG KHOI TAO GAME ---\n";

    // C++ sẽ tự động dò tìm Constructor khớp với số lượng tham số truyền vào!

    // Trường hợp 1: Không truyền gì -> Gọi Default Constructor
    Character npc1; 
    
    // Trường hợp 2: Truyền 3 tham số -> Gọi Parameterized Constructor
    Character hero("Bố Đời", 1000, 150); 

    cout << "\n--- CHI SO NHAN VAT ---\n";
    npc1.showStats();
    hero.showStats();

    return 0;
}

Nhận xét: Trong một Class chuẩn dự án, Kỹ sư phần mềm thường sẽ LUÔN LUÔN viết một Default Constructor. Dù nó chỉ là một hàm trống hoặc gán các giá trị bằng 0, nó giúp hệ thống an toàn, không bị crash khi khai báo mảng Object (Character arr[100]; bắt buộc phải có Default Constructor mới chạy được).

Tạm kết & Gợi mở

Tuyệt vời! Bạn đã lật tẩy được cách C++ cấp phát bộ nhớ ban đầu và hiểu được tầm quan trọng của việc tự định nghĩa Default Constructor để chống lại "giá trị rác".

Tuy nhiên, hãy nhìn lại cái Constructor có tham số mà chúng ta đã viết:

Character(string n, int h, int d) {
    name = n; // ĐÂY LÀ GÁN CHỨ KHÔNG PHẢI KHỞI TẠO!
    hp = h;
}

Mặc dù code chạy đúng, nhưng dưới góc độ tối ưu hiệu năng (Performance) của một Kỹ sư C++, cách viết này cực kỳ "chậm chạp" và tốn tài nguyên. Tại sao lại như vậy? Làm sao để viết một Constructor "chuẩn Senior"?

Hẹn gặp lại anh em ở Bài 13: Phương thức khởi tạo có tham số truyền vào (Phần 1) - Phân biệt giữa "Gán" và "Khởi tạo" thực sự! Nhớ Upvote để ủng hộ mình lên bài đều đặn nhé!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.