[C++ OOP Thực Chiến] Bài 29: Nạp chồng toán tử (+) bằng Hàm bạn - "Chân ái" của hệ thống tính toán
Chào anh em! Ở [Bài 28], chúng ta đã rơi vào một thế bế tắc kinh điển trong thiết kế C++:
- Viết phép cộng
+ở TRONG Class: Truy cập dữ liệu dễ dàng, nhưng lỗi khi gõ5 + ps1(Mất tính giao hoán). - Viết phép cộng
+ở NGOÀI Class: Tính giao hoán được bảo đảm, nhưng code nát bét vì phải gọigetTuSo(),getMauSo()liên tục.
Hôm nay, chúng ta sẽ lôi một "vũ khí bí mật" đã học ở Bài 22 ra để dứt điểm trận chiến này: Hàm bạn (Friend Function).
1. Tại sao Hàm bạn lại là "Viên đạn bạc" (Silver Bullet)?
Hàm bạn sở hữu đặc tính "lai" hoàn hảo giữa 2 trường phái trên:
- Nó là hàm tự do (Non-member): Đứng ngoài Class, nên khi gõ
a + b, C++ sẽ dịch thànhoperator+(a, b). Cả thằng bên trái (a) và bên phải (b) đều bình đẳng. Tính giao hoán toán học được bảo toàn tuyệt đối! - Nó được cấp quyền Bạn (Friend): Vì là bạn chí cốt, nó có thể chọc thẳng vào két sắt private để lấy a.tuSo * b.mauSo mà không cần thông qua bất kỳ hàm Getter rườm rà nào cả. Code siêu ngắn, siêu nhanh!
Trong các thư viện toán học của C++ (như xử lý ma trận, số phức), 99% các toán tử 2 ngôi (+, -, *, /, ==, !=) đều được thiết kế dưới dạng Hàm bạn.
2. Code Demo: Phép cộng hoàn hảo
Hãy xem cách thiết kế một phép cộng Phân số đáp ứng mọi tiêu chuẩn khắt khe nhất của một hệ thống Backend:
#include <iostream>
#include <cmath>
using namespace std;
class PhanSo {
private:
int tuSo;
int mauSo;
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:
// Constructor cho phép ép kiểu ngầm định (int -> PhanSo)
// Ví dụ: Số 5 sẽ tự động biến thành PhanSo(5, 1)
PhanSo(int tu = 0, int mau = 1) : 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";
}
// --- KHAI BÁO HÀM BẠN CHO PHÉP CỘNG ---
friend PhanSo operator+(const PhanSo& a, const PhanSo& b);
};
// --- ĐỊNH NGHĨA HÀM NGOÀI CLASS (KHÔNG CÓ CHỮ FRIEND) ---
PhanSo operator+(const PhanSo& a, const PhanSo& b) {
// NHỜ QUYỀN LÀM BẠN: Truy cập thẳng private, dẹp bỏ Getter!
int tuMoi = (a.tuSo * b.mauSo) + (b.tuSo * a.mauSo);
int mauMoi = a.mauSo * b.mauSo;
return PhanSo(tuMoi, mauMoi);
}
int main() {
cout << "--- HE THONG CONG PHAN SO HOAN HAO ---\n";
PhanSo ps1(1, 2); // 1/2
PhanSo ps2(3, 4); // 3/4
cout << "PS1: "; ps1.inPhanSo();
cout << "PS2: "; ps2.inPhanSo();
// 1. Cộng 2 object Phân số với nhau
PhanSo tong1 = ps1 + ps2;
cout << "\nTong (ps1 + ps2): "; tong1.inPhanSo(); // 5/4
// 2. Tính giao hoán: Phân số + Số nguyên (Nhờ Constructor ép kiểu ngầm định)
PhanSo tong2 = ps1 + 5;
// C++ dịch thành: operator+(ps1, PhanSo(5, 1))
cout << "Tong (ps1 + 5) : "; tong2.inPhanSo(); // 11/2
// 3. Tính giao hoán: Số nguyên + Phân số (Thứ mà Member Function khóc thét)
PhanSo tong3 = 10 + ps1;
// C++ dịch thành: operator+(PhanSo(10, 1), ps1)
cout << "Tong (10 + ps1) : "; tong3.inPhanSo(); // 21/2
return 0;
}
Nhận xét: Tuyệt vời! Hệ thống của bạn giờ đây thông minh đến mức có thể nhận diện và tính toán hỗn hợp giữa các kiểu dữ liệu khác nhau mà mã nguồn vẫn sạch sẽ, chuẩn kiến trúc.
Tạm kết & Gợi mở
Chúng ta đã thành công rực rỡ với các phép toán học (+, -). Giờ đây, thay vì gọi cong(), chúng ta gõ dấu + cực kỳ sang chảnh.
Nhưng khoan đã, hãy nhìn lại hàm main(). Để in phân số ra màn hình, chúng ta VẪN ĐANG PHẢI gọi cái hàm ps1.inPhanSo(). Tại sao chúng ta không thể dùng toán tử xuất chuẩn của C++ là cout << ps1? Lẽ nào Object của chúng ta vẫn "kém sang" hơn so với một biến int bình thường?
Việc nạp chồng toán tử nhập (>>) và xuất (<<) là bắt buộc để Class của bạn hoàn thiện 100%.
Nhưng nếu chúng ta cố tình đưa toán tử << vào làm Member Function (Phương thức của lớp) thay vì Hàm bạn, một điều cực kỳ quái gở về cú pháp sẽ xảy ra. Nó quái gở đến mức nào?
Hẹn gặp lại anh em ở Bài 30: Toán tử nhập xuất dùng member function - Bí mật quái gở của cout và cin! Nhớ thả Upvote để tiếp lửa cho series nhé!
All rights reserved