[C++ OOP Thực Chiến] Bài 31: Toán tử nhập xuất dùng Hàm bạn - Đưa trật tự vũ trụ C++ trở lại!
Chào anh em! Ở [Bài 30], chúng ta đã chứng kiến một cú lừa ngoạn mục. Khi cố tình nhét toán tử << vào làm Member Function (Hàm bên trong Class), mã nguồn của chúng ta bị đảo lộn thành ps << cout;.
Lý do cực kỳ đơn giản: Member Function luôn ép Object của Class đó làm chủ nhà (đứng bên trái toán tử).
Nhưng luật của vũ trụ C++ quy định: Đối với luồng xuất nhập, thằng cout (luồng xuất) và cin (luồng nhập) MỚI LÀ CHỦ NHÀ. Chúng bắt buộc phải đứng ở bên trái: cout << ps;
Để làm được điều này, chúng ta BẮT BUỘC phải đẩy toán tử << và >> ra làm Hàm tự do (Non-member). Và để hàm tự do này chọc được vào tuSo, mauSo cực nhanh mà không cần Getter, chúng ta sẽ cấp cho nó quyền Hàm bạn (Friend)!
1. Cú pháp chuẩn Senior cho cout và cin
Để nạp chồng 2 toán tử này, anh em phải nhớ kỹ 3 điều kiện sống còn:
- Tham số truyền vào: Phải nhận 2 tham số. Thằng bên trái là luồng
ostream(hoặcistream), thằng bên phải là ObjectPhanSo. - Chuyện cái dấu
const:
- Với
<<(Xuất): Ta chỉ ĐỌC phân số để in, nên bắt buộc truyềnconst PhanSo&. - Với
>>(Nhập): Ta phải GHI đè dữ liệu mới vào phân số, nên TUYỆT ĐỐI KHÔNG dùngconst, chỉ truyềnPhanSo&.
- Giá trị trả về (Mảnh ghép ăn tiền nhất): Tại sao ta có thể gõ liên tiếp
cout << a << b << c;? Đó là vì sau khi inaxong, hàm phải trả về chính cáicoutđó để nó đi in tiếp thằngb. Do vậy, kiểu trả về bắt buộc phải là Tham chiếuostream&hoặcistream&.
2. Code Demo: Vũ trụ lập lại trật tự
Hãy xem cách kết hợp friend và Non-member function để tạo ra một Class hoàn hảo:
#include <iostream>
#include <cmath>
using namespace std;
class PhanSo {
private:
int tuSo;
int mauSo;
void rutGon() {
if (mauSo == 0) {
cout << "[LỖI] Mẫu số không được bằng 0!\n";
mauSo = 1; // Sửa lỗi tạm thời để không crash
}
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(int tu = 0, int mau = 1) : tuSo(tu), mauSo(mau) { rutGon(); }
// --- CẤP QUYỀN HÀM BẠN CHO NHẬP VÀ XUẤT ---
friend ostream& operator<<(ostream& os, const PhanSo& ps);
friend istream& operator>>(istream& is, PhanSo& ps);
};
// --- ĐỊNH NGHĨA HÀM NGOÀI CLASS (NON-MEMBER) ---
// 1. TOÁN TỬ XUẤT (<<)
ostream& operator<<(ostream& os, const PhanSo& ps) {
// os đóng vai trò như cout
if (ps.mauSo == 1) {
os << ps.tuSo;
} else if (ps.tuSo == 0) {
os << "0";
} else {
os << ps.tuSo << "/" << ps.mauSo;
}
return os; // Trả về luồng để có thể nối chuỗi (cout << a << b)
}
// 2. TOÁN TỬ NHẬP (>>)
istream& operator>>(istream& is, PhanSo& ps) {
// is đóng vai trò như cin
// Vì là BẠN, chọc thẳng vào tuSo, mauSo để gán giá trị
is >> ps.tuSo >> ps.mauSo;
// Nhập xong bắt buộc phải gọi rút gọn để bảo vệ tính toàn vẹn!
ps.rutGon();
return is; // Trả về luồng để nối (cin >> a >> b)
}
int main() {
cout << "--- HE THONG NHAP XUAT CHUAN MUOC ---\n";
PhanSo ps1, ps2;
// Trật tự đã được thiết lập: cin đứng trước, ps đứng sau!
cout << "Nhap 2 phan so (tu mau tu mau): ";
cin >> ps1 >> ps2;
// cout đứng trước, in liên tiếp cực kỳ thanh lịch!
cout << "Ban vua nhap: " << ps1 << " va " << ps2 << "\n";
return 0;
}
Nhận xét: Tuyệt vời! Class PhanSo của bạn giờ đây đã hòa nhập hoàn toàn vào hệ sinh thái của C++. Nó có thể cộng, trừ và có thể in ấn trực tiếp bằng cout như một biến gốc nguyên thủy. Bạn đã chính thức xóa nhòa ranh giới giữa kiểu dữ liệu hệ thống (int, float) và kiểu dữ liệu tự định nghĩa (Class).
Tạm kết & Gợi mở
Anh em đã hoàn thiện trọn bộ kỹ năng đối với các toán tử 2 ngôi phức tạp nhất.
Bây giờ, hãy quay ngược lại với toán tử 1 ngôi. Ở [Bài 25], ta đã xử lý toán tử số đối (-ps). Cú pháp của nó rất rõ ràng: Dấu - đặt trước Object.
Nhưng C++ có một loại phép toán 1 ngôi gây lú lẫn bậc nhất: Toán tử Tăng (++) và Giảm (--).
Cùng là cộng thêm 1, nhưng tại sao ++ps (Tiền tố) và ps++ (Hậu tố) lại cho ra kết quả hoàn toàn khác nhau trong các biểu thức gán? Làm sao để C++ biết bạn đang gọi cái trước hay cái sau khi định nghĩa toán tử?
Để tránh tẩu hỏa nhập ma, chúng ta sẽ "chia để trị". Hẹn gặp lại anh em ở Bài 32: Toán tử tiền tố (Prefix Operator) - Tăng trước, tính sau! Nhớ Upvote để lấy tinh thần chiến tiếp nhé!
All Rights Reserved