+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
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í