0

[C++ OOP Thực Chiến] 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!

Chào anh em! Ở các bài trước, chúng ta đã biến Class PhanSo thành một thực thể toán học thực thụ với các phép cộng, trừ. Nhưng ở khâu hiển thị, chúng ta vẫn đang xài một cái hàm vô cùng "kém sang" là ps1.inPhanSo().

Tại sao một số nguyên int a = 5; thì được in ra bằng lệnh cout << a;, còn Phân số của chúng ta thì không? Đã đến lúc chúng ta "độ" lại toán tử xuất << và nhập >>.

Nhưng nếu chúng ta áp dụng tư duy "Member Function" (viết hàm bên trong Class) như đã làm với toán tử số đối, một hiện tượng cực kỳ quái gở sẽ xảy ra!

1. Phân tích đối thủ: cout và cin là ai?

Trước khi nạp chồng, anh em phải hiểu bản chất của câu lệnh cout << a;.

  • cout thực chất là một Object (đối tượng) có sẵn của Class ostream (Output Stream - Luồng xuất).
  • cin là một Object của Class istream (Input Stream - Luồng nhập).
  • Dấu <<>> là các toán tử 2 ngôi (cần 1 Object bên trái và 1 Object bên phải).

2. Cái bẫy của Member Function

Hãy nhớ lại quy tắc cốt lõi của Member Function ở [Bài 28]: Object gọi hàm (con trỏ this) BẮT BUỘC phải nằm ở bên TRÁI toán tử.

Nếu bạn định nghĩa toán tử << ở bên TRONG Class PhanSo, C++ sẽ mặc định Class PhanSo là chủ nhà. Tức là Object PhanSo phải nằm bên trái dấu <<.

Từ đó, cú pháp để gọi hàm sẽ bị đảo ngược hoàn toàn: ps1 << cout; thay vì cout << ps1;

Trông nó có ngược đời và "đau mắt" không? Hãy cùng xem code demo để thấy rõ sự quái gở này.

3. Code Demo: Sự ngược đời của C++

#include <iostream>

using namespace std;

class PhanSo {
private:
    int tuSo;
    int mauSo;

public:
    PhanSo(int tu = 0, int mau = 1) : tuSo(tu), mauSo(mau) {}

    // --- NẠP CHỒNG TOÁN TỬ XUẤT (Bằng Member Function) ---
    // Object PhanSo (this) sẽ nhận một ostream (cout) truyền vào
    // Trả về ostream& để có thể nối chuỗi liên tiếp
    ostream& operator<<(ostream& os) const {
        os << tuSo << "/" << mauSo;
        return os;
    }

    // --- NẠP CHỒNG TOÁN TỬ NHẬP (Bằng Member Function) ---
    istream& operator>>(istream& is) {
        is >> tuSo >> mauSo;
        return is;
    }
};

int main() {
    cout << "--- BÍ MẬT QUÁI GỞ CỦA MEMBER FUNCTION ---\n\n";

    PhanSo ps;

    // LỖI: cin >> ps; (C++ không hiểu vì cin nằm bên trái)
    
    // CÁCH GỌI ĐÚNG NHƯNG QUÁI GỞ:
    cout << "Nhap tu so va mau so (VD: 3 4): ";
    ps >> cin; // Đọc là: Phân số lấy dữ liệu từ cin???

    // LỖI: cout << "Phan so vua nhap la: " << ps; 
    
    // CÁCH GỌI ĐÚNG NHƯNG QUÁI GỞ:
    ps << (cout << "Phan so vua nhap la: "); // Đau não chưa!!!
    cout << "\n";

    // Cách gọi đơn giản hơn nhưng vẫn ngược:
    ps << cout; 
    cout << "\n";

    return 0;
}

Nhận xét: Dù chương trình VẪN CHẠY ĐÚNG logic, in ra đúng số và nhập đúng số, nhưng cú pháp ps << cout; đã phá vỡ hoàn toàn thói quen của hàng triệu Lập trình viên C++. Trong môi trường doanh nghiệp, nếu bạn đẩy đoạn code này lên Git để review, Tech Lead chắc chắn sẽ "gõ đầu" bạn vì nó quá khó đọc và không tuân thủ chuẩn mực thiết kế (Convention).

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

Bài học rút ra ở đây là: Không phải lúc nào nhét hàm vào bên trong Class cũng là cách tốt nhất! Đối với toán tử <<>>, chúng ta BẮT BUỘC phải để Object coutcin nằm ở bên TRÁI. Tức là chúng ta phải đưa phép toán này trở thành một Non-member Function (Hàm tự do nằm ngoài Class). Cú pháp mong muốn: operator<<(cout, ps1).

Nhưng nếu đưa ra ngoài, làm sao cái hàm tự do đó có thể đọc được tuSomauSo (vốn đang bị khóa chặt bởi private) để in ra màn hình mà không cần dùng đến đống Getter phiền phức?

Vũ khí "chân ái" mà chúng ta đã dùng cho phép cộng ở Bài 29 lại một lần nữa phải xuất trận!

Hẹn gặp lại anh em ở 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! Nhớ Upvote để mình có động lực ra bài sớm nhất nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí