[C++ OOP Thực Chiến] Bài 22: Hàm bạn - Lớp bạn (Phần 1) - Nghệ thuật "mở cửa hậu" an toàn
Chào anh em! Ở [Bài 21], hệ thống PhanSo của chúng ta đã được bảo vệ hoàn hảo bằng private. Không một ai từ bên ngoài có thể tự ý thay đổi tuSo hay mauSo. Tính Đóng gói (Encapsulation) đang phát huy sức mạnh tối đa.
Nhưng hãy tưởng tượng, bạn muốn viết một hàm tự do (global function) ở bên ngoài để thực hiện phép cộng 2 phân số: congPhanSo(PhanSo a, PhanSo b).
Vì hàm này nằm NGOÀI class, nó hoàn toàn mù tịt và bị hệ thống cấm truy cập vào a.tuSo hay b.mauSo. Nếu ép dùng Getter/Setter thì code sẽ dài dòng và mất đi tính "thực chiến". C++ mang đến một giải pháp tinh tế hơn rất nhiều: Hàm bạn (Friend Function).
1. Bản chất của Hàm bạn (Friend Function)
Hãy tưởng tượng Class PhanSo là căn nhà của bạn. Các thuộc tính private là két sắt trong phòng ngủ. Người lạ đến nhà (Hàm bình thường) thì chỉ được ngồi ở phòng khách (truy cập các hàm public).
Tuy nhiên, bạn có một đứa bạn chí cốt. Bạn chủ động cấp quyền cho nó: "Ê, tao cho phép mày vào phòng ngủ mở két sắt của tao luôn!". Đứa bạn đó chính là Hàm bạn.
Đặc điểm cốt lõi:
- Hàm bạn KHÔNG PHẢI là một phương thức (method) của Class. Nó là một hàm tự do.
- Nhưng nó được Class "cấp quyền" để truy cập trực tiếp vào vùng
privatevàprotectedcủa Class đó. - Quyền làm bạn phải do Class chủ động ban cho, chứ hàm bên ngoài không thể tự nhận vơ là "bạn" được.
2. Hai "Quy tắc thép" khi dùng Friend Function
Để không bị lỗi Syntax đỏ lòm, anh em phải nhớ kỹ 2 quy tắc này:
- Vị trí khai báo: Bên trong Class, bạn thêm từ khóa
friendtrước khai báo hàm. Điều này giống như việc dán giấy báo: "Hàm này là bạn tôi". - Khi định nghĩa (viết ruột hàm): Vì nó là hàm tự do, nên lúc định nghĩa bên ngoài, bạn KHÔNG ĐƯỢC dùng từ khóa
friendvà cũng KHÔNG DÙNG toán tử phân giảiPhanSo::. Và tất nhiên, nó không hề có con trỏthis.
3. Code Demo: Viết hàm Cộng Phân Số
Hãy cùng xem cách "vượt rào" an toàn để cộng 2 phân số mà không cần dùng bất kỳ Getter nào:
#include <iostream>
#include <cmath>
using namespace std;
class PhanSo {
private:
int tuSo;
int mauSo;
// Helper: Rút gọn phân số (Đã học ở bài trước)
void rutGon() {
if (mauSo < 0) { tuSo = -tuSo; mauSo = -mauSo; }
int a = abs(tuSo), b = abs(mauSo);
while (b != 0) { int temp = b; b = a % b; a = temp; }
int ucln = (a == 0) ? 1 : a;
tuSo /= ucln; mauSo /= ucln;
}
public:
PhanSo() : tuSo(0), mauSo(1) {}
PhanSo(int tu, int mau) : tuSo(tu), mauSo(mau) { rutGon(); }
void inPhanSo() const {
if (mauSo == 1) cout << tuSo << "\n";
else if (tuSo == 0) cout << "0\n";
else cout << tuSo << "/" << mauSo << "\n";
}
// TỜ GIẤY CHỨNG NHẬN: Khai báo hàm congPhanSo là BẠN của Class này
friend PhanSo congPhanSo(const PhanSo& a, const PhanSo& b);
};
// --- ĐỊNH NGHĨA HÀM BẠN (Nằm hoàn toàn bên ngoài Class) ---
// LƯU Ý: Không có chữ 'friend', không có 'PhanSo::'
PhanSo congPhanSo(const PhanSo& a, const PhanSo& b) {
// NHỜ QUYỀN LÀM BẠN: Có thể gọi trực tiếp a.tuSo, b.mauSo (private)
int tuSoMoi = (a.tuSo * b.mauSo) + (b.tuSo * a.mauSo);
int mauSoMoi = a.mauSo * b.mauSo;
// Trả về một Object phân số mới (Nó sẽ tự động gọi rút gọn nhờ Constructor)
return PhanSo(tuSoMoi, mauSoMoi);
}
int main() {
cout << "--- HE THONG TINH TOAN PHAN SO ---\n";
PhanSo ps1(1, 4); // 1/4
PhanSo ps2(2, 4); // 2/4 -> 1/2
cout << "Phan so 1: "; ps1.inPhanSo();
cout << "Phan so 2: "; ps2.inPhanSo();
// Gọi hàm tự do như C thông thường
PhanSo ketQua = congPhanSo(ps1, ps2);
cout << "-> Tong 2 phan so: ";
ketQua.inPhanSo(); // Kỳ vọng: 3/4
return 0;
}
Nhận xét: Kỹ thuật friend giúp thao tác trên Object trở nên vô cùng tự nhiên. Bạn không cần phải viết những thứ dư thừa như a.getTuSo() * b.getMauSo(). Code trông sạch sẽ, đậm chất toán học và tối ưu hiệu năng tuyệt đối!
Tạm kết & Gợi mở
Tuyệt vời! Hàm bạn (Friend function) đã chứng minh được sức mạnh khi muốn tương tác sâu vào dữ liệu bảo mật của Class mà không làm hỏng kiến trúc Đóng gói tổng thể.
Nhưng hãy suy nghĩ rộng ra một chút. Giả sử hệ thống Backend của bạn đang phát triển lớn hơn. Bạn có hẳn một Class tên là MayTinhToan chứa hàng chục phương thức phức tạp (cộng, trừ, nhân, chia, lấy đạo hàm, tích phân...). Tất cả các phương thức đó đều cần truy cập vào private của Class PhanSo.
Chẳng lẽ bạn lại mở file PhanSo.h ra và copy-paste chữ friend cho cả chục cái hàm đó? Việc này sẽ biến mã nguồn của bạn thành một mớ rác bảo trì.
Làm sao để cấp quyền "bạn thân" cho toàn bộ một hệ thống khác chỉ bằng 1 dòng code duy nhất? Hẹn gặp lại anh em ở Bài 23: Hàm bạn - Lớp bạn (Phần 2) - Cấp quyền đại trà với Friend Class! Đừng quên thả Upvote để tiếp lửa cho series nhé!
All rights reserved