[C++ OOP Thực Chiến] Bài 11: Phương thức nhận Object làm tham số - Khi các thực thể "va chạm"
Chào anh em! Trong 10 bài vừa qua, chúng ta đã thiết kế ra những Object rất xịn xò nhưng chúng lại sống... khá cô lập.
Thực tế khi làm dự án, các Object hiếm khi đứng một mình. Ví dụ:
- Hệ thống Game: Nhân vật A (Object A) tung chiêu làm giảm máu của Quái vật B (Object B).
- Hệ thống Ngân hàng: Tài khoản A (Object A) chuyển 500k sang Tài khoản B (Object B).
Làm sao để code được logic này? Rất đơn giản: Class thực chất cũng chỉ là một kiểu dữ liệu do chúng ta tự định nghĩa. Giống như bạn có thể truyền int hay string vào hàm, bạn hoàn toàn có thể truyền một Object vào phương thức của một Object khác.
Tuy nhiên, truyền thế nào để không "bóp" hiệu năng server lại là câu chuyện của đẳng cấp.
1. Ba cấp độ truyền Object vào Hàm (Cực kỳ quan trọng)
Giả sử bạn có class Character và bạn viết một phương thức attack (tấn công) nhận vào mục tiêu là một Character khác. Bạn có 3 cách để viết:
Cấp độ 1: Truyền Tham Trị (Pass by Value) - Kẻ thù của hiệu năng
void attack(Character target) { ... }
Sai lầm: Khi bạn truyền như thế này, C++ sẽ tự động tạo ra một bản copy của Object target. Nếu Object của bạn chứa hàng MB dữ liệu (hình ảnh, mảng túi đồ...), việc copy này diễn ra liên tục sẽ làm giật lag game hoặc tràn RAM server. Hơn nữa, bạn đánh vào bản copy thì thằng target thật sự ở bên ngoài... chẳng mất giọt máu nào!
Cấp độ 2: Truyền Tham Chiếu (Pass by Reference) - Giải pháp chuẩn mực
Cấp độ 2: Truyền Tham Chiếu (Pass by Reference) - Giải pháp chuẩn mực
void attack(Character& target) { ... }
Chuẩn: Thêm dấu &. Lúc này, chúng ta truyền trực tiếp "bản gốc" của target vào hàm. Không có thao tác copy nào diễn ra (tốc độ bàn thờ) và khi bạn trừ máu trong hàm, Object bên ngoài sẽ thực sự mất máu.
Cấp độ 3: Truyền Hằng Tham Chiếu (Pass by Const Reference) - Lính gác an toàn
void printStatsComparison(const Character& target) { ... }
Bá đạo: Dùng khi bạn chỉ muốn "đọc" dữ liệu của Object kia để so sánh, tính toán chứ không muốn làm thay đổi nó. Dấu & giúp tránh copy (nhanh), còn chữ const chặn đứng mọi hành vi sửa đổi dữ liệu từ bên trong hàm (an toàn).
2. Code Demo: Trận chiến RPG Kinh điển
Hãy xem cách chúng ta áp dụng tư duy trên vào một trận chiến giữa Hero và Boss. Hero sẽ tấn công Boss (làm thay đổi máu Boss), và cũng có tính năng "Soi" chỉ số Boss (chỉ đọc, không làm thay đổi máu).
#include <iostream>
#include <string>
using namespace std;
class Character {
private:
string name;
int hp;
int damage;
public:
Character(string n, int h, int d) {
name = n;
hp = h;
damage = d;
}
string getName() const { return name; }
int getHp() const { return hp; }
// Tính năng 1: TẤN CÔNG
// Bắt buộc dùng Tham chiếu (&) để thay đổi trực tiếp máu của mục tiêu
void attack(Character& target) {
cout << "[COMBAT] " << this->name << " tan cong " << target.name
<< " gay " << this->damage << " sat thuong!\n";
// Trừ máu của Object mục tiêu
target.hp -= this->damage;
if(target.hp < 0) target.hp = 0;
}
// Tính năng 2: SOI CHỈ SỐ
// Dùng Hằng Tham Chiếu (const &) vì chỉ xem, không được phép hack máu!
void inspect(const Character& target) const {
cout << "[INSPECT] " << this->name << " dang soi chi so cua " << target.name << ":\n"
<< " -> HP hien tai: " << target.hp << "\n\n";
}
};
int main() {
// Khởi tạo 2 thực thể
Character hero("Bố Đời", 1000, 150);
Character boss("Dragon", 5000, 300);
// Hero soi chỉ số của Boss
hero.inspect(boss);
// Vòng lặp chiến đấu khốc liệt
for(int i = 1; i <= 3; i++) {
hero.attack(boss);
}
// Kiểm tra lại kết quả sau trận đánh
cout << "\n--- KET QUA ---\n";
cout << "Mau cua Boss con lai: " << boss.getHp() << "\n";
return 0;
}
Nhận xét: Trong hàm attack(), this->name đại diện cho kẻ ra đòn (Hero gọi hàm), còn target.name đại diện cho nạn nhân bị đánh (Boss truyền vào). Nhờ có con trỏ this (đã học ở bài trước) và cách truyền tham chiếu &, code cực kỳ tường minh và chạy sát với thế giới thực.
Tạm kết & Gợi mở
Tuyệt vời! Bạn đã biết cách nối kết các Object lại với nhau. Giờ đây, bạn hoàn toàn có đủ kiến thức để tự code một con game text-based nhỏ hoặc một hệ thống ngân hàng cơ bản.
Nhưng hãy để ý dòng code khởi tạo nhân vật ở hàm main:
Character hero("Bố Đời", 1000, 150);
Chúng ta bắt buộc phải truyền tên, máu, và sát thương thì nhân vật mới được tạo ra (vì ta đã tự viết một Constructor nhận 3 tham số).
Điều gì sẽ xảy ra nếu người chơi chỉ muốn tạo một nhân vật mặc định (Chưa đặt tên, máu = 100, damage = 10) bằng cú pháp trống: Character npc;? Code của bạn sẽ ngay lập tức báo lỗi đỏ chót từ trình biên dịch!
Để giải quyết vấn đề linh hoạt trong việc khởi tạo Object, chúng ta cần tìm hiểu về một khái niệm cốt lõi. Hẹn gặp lại anh em ở Bài 12: Hàm khởi tạo mặc định (Default Constructor) - Cách C++ "chào đời" một Object! Nhớ Follow và Upvote series nhé!
All Rights Reserved