+3

[C++ OOP Thực Chiến] Bài 32: Toán tử tiền tố (Prefix Operator) - Tăng trước, tính sau!

Chào anh em! Trong các vòng lặp for hay while, chúng ta dùng ++ii++ nhiều như cơm bữa. Đối với kiểu int, cả hai đều là cộng thêm 1 đơn vị.

Nhưng khi đưa vào một biểu thức phức tạp như int a = ++i;, câu chuyện bắt đầu rẽ nhánh:

  • Tiền tố (++i): Tăng biến i lên 1 TRƯỚC, sau đó mới lấy giá trị mới đó gán cho a. (Tăng trước, tính sau).

Hôm nay, chúng ta sẽ nạp chồng toán tử tiền tố ++ cho Class PhanSo. Trong toán học, tăng 1 đơn vị cho phân số nghĩa là: ab+1=a+bb\frac{a}{b} + 1 = \frac{a + b}{b}. Tức là ta chỉ cần lấy Tử số cộng thêm Mẫu số là xong!

1. Phân tích Cú pháp & Luật chơi

Vì toán tử tiền tố ++ps là toán tử 1 ngôi và tác động trực tiếp lên chính Object gọi nó, cách tốt nhất là triển khai nó dưới dạng Member Function (Hàm thành viên bên trong Class).

  • Tham số: KHÔNG CÓ (Vì nó dùng luôn con trỏ this).
  • Chữ const: KHÔNG ĐƯỢC DÙNG (Vì bản chất của phép ++ là làm thay đổi giá trị của Object gốc).

2. Bí mật của Giá trị trả về: Tại sao lại là Tham chiếu (&)?

Đây là câu hỏi phân loại ứng viên khi phỏng vấn C++. Kiểu trả về của hàm operator++() bắt buộc phải là Tham chiếu (PhanSo&), chứ không phải là một Object tạo mới (PhanSo). Tại sao?

C++ cho phép bạn viết những đoạn code "ảo ma" như thế này: ++(++ps);

  • Lần 1: ++ps chạy, tăng phân số lên 1, và nó phải trả về chính cái phân số gốc đó.
  • Lần 2: Phép ++ bên ngoài tiếp tục bám vào cái phân số gốc vừa được trả về để tăng thêm 1 lần nữa.

Nếu bạn trả về một bản copy (bỏ dấu &), phép ++ thứ 2 sẽ tăng giá trị trên một cái bản sao rác vô danh, và Object gốc của bạn bị sai logic hoàn toàn!

3. Code Demo: Tăng tốc Phân số

Hãy cùng xem cách triển khai cực kỳ ngắn gọn bên trong pháo đài Class:

#include <iostream>
#include <cmath>

using namespace std;

class PhanSo {
private:
    int tuSo;
    int mauSo;

    void rutGon() {
        if (mauSo == 0) mauSo = 1;
        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(); }

    friend ostream& operator<<(ostream& os, const PhanSo& ps) {
        if (ps.mauSo == 1) os << ps.tuSo;
        else if (ps.tuSo == 0) os << "0";
        else os << ps.tuSo << "/" << ps.mauSo;
        return os;
    }

    // --- NẠP CHỒNG TOÁN TỬ TIỀN TỐ (++ps) ---
    // Trả về tham chiếu của chính Object hiện tại (*this)
    PhanSo& operator++() {
        // Tăng phân số lên 1: (a/b) + 1 = (a+b)/b
        this->tuSo = this->tuSo + this->mauSo;
        
        // Trút gọn lại cho an toàn
        this->rutGon();
        
        // Trả về chính Object đang gọi hàm
        return *this; 
    }
};

int main() {
    cout << "--- HE THONG TOAN TU TIEN TO ---\n";

    PhanSo ps(1, 2); // 1/2
    cout << "Ban dau: ps = " << ps << "\n";

    // 1. Tăng bình thường
    ++ps;
    cout << "Sau khi ++ps: ps = " << ps << "\n"; // (1+2)/2 = 3/2

    // 2. Tăng và gán cùng lúc (Tăng trước, gán sau)
    PhanSo a = ++ps; 
    cout << "\nThu nghiem: a = ++ps\n";
    cout << "-> Gia tri cua a : " << a << "\n";  // 5/2
    cout << "-> Gia tri cua ps: " << ps << "\n"; // 5/2 (Cả 2 giống nhau)

    // 3. Test cú pháp lồng nhau ảo ma
    ++(++ps);
    cout << "\nSau khi ++(++ps): ps = " << ps << "\n"; // (5+2+2)/2 = 9/2

    return 0;
}

Nhận xét: Việc trả về *this (giá trị mà con trỏ this đang trỏ tới) và ép kiểu trả về là tham chiếu (&) đã giúp toán tử tiền tố của chúng ta hoạt động mượt mà, tuân thủ đúng 100% định luật toán học của C++ nguyên thủy.

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

Xong tiền tố! Code khá nhàn và cực kỳ hợp logic. Nhưng khoan...

Toán tử Tiền tố ++ps và Hậu tố ps++ đều dùng chung một ký hiệu là dấu cộng kép ++. Nếu chúng ta tiếp tục viết thêm một hàm PhanSo& operator++() cho cái Hậu tố, trình biên dịch C++ sẽ báo lỗi trùng lặp (Duplicate) ngay lập tức vì 2 hàm có tên và tham số giống hệt nhau!

Làm sao để C++ phân biệt được khi nào người dùng gõ ++ps (Tiền tố) và khi nào người dùng gõ ps++ (Hậu tố) để gọi đúng hàm?

Có một cú "hack" cực kỳ dị biệt của những nhà sáng lập C++ để xử lý vụ này. Hẹn gặp lại anh em ở Bài 33: Toán tử hậu tố (Postfix Operator) - Tham số bù nhìn và cú lừa lịch sử! Nhớ thả Upvote để tiếp lửa cho series nhé!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.