[C++ OOP Thực Chiến] Bài 25: Nạp chồng toán tử 1 ngôi - "Đảo chiều" thực thể với Unary Minus
Chào anh em! Ở [Bài 24], chúng ta đã biết sức mạnh của việc Nạp chồng toán tử (Operator Overloading). Hôm nay, chúng ta sẽ áp dụng nó vào thực tế với bài toán đầu tiên: Toán tử số đối (Unary Minus -).
Hãy tưởng tượng bạn có một phân số ps1 = 1/2. Khi bạn gõ -ps1, C++ phải trả về một phân số mới là -1/2. Và quan trọng nhất, chúng ta sẽ nhúng thẳng logic này vào bên trong Class bằng kỹ thuật Member Function (Phương thức của lớp).
1. Phân tích Toán tử 1 ngôi (Unary Operator)
Toán tử 1 ngôi là những ký hiệu chỉ cần ĐÚNG 1 Object để hoạt động. Ví dụ:
- Dấu âm:
-ps - Tăng/Giảm 1 đơn vị:
++ps,--ps - Phủ định logic:
!ps
Bí mật của Member Function: Khi bạn viết Operator dưới dạng Member Function, Object gọi hàm (bên trái toán tử, hoặc bị toán tử gắn vào) sẽ ngầm định được truyền vào thông qua con trỏ this. Do đó, hàm operator- của chúng ta sẽ KHÔNG CÓ THAM SỐ NÀO CẢ! (Vì chính nó đã tự lấy dữ liệu của nó ra xài rồi).
2. Quy tắc "Const" sinh tử
Có một câu hỏi phỏng vấn rất hay gài Junior: "Khi gọi -ps1, thì bản thân thằng ps1 có bị đổi dấu không?"
Câu trả lời là KHÔNG! Trong toán học, khi bạn gọi int a = 5; int b = -a;, biến a vẫn là 5, biến b mới là -5. Phép đối dấu tạo ra một giá trị mới, không làm thay đổi giá trị gốc.
Vì vậy, hàm operator- của chúng ta BẮT BUỘC phải có từ khóa const ở cuối để thề độc rằng: "Tôi chỉ đọc dữ liệu để tạo ra Object mới, tuyệt đối không đụng chạm đến bản gốc!".
3. Code Demo: Đảo dấu Phân số
Hãy cùng xem cách Hiếu - một Backend Dev thiết kế logic này gọn gàng như thế nào nhé:
#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
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";
}
// --- NẠP CHỒNG TOÁN TỬ SỐ ĐỐI (UNARY MINUS) ---
// Kiểu trả về là một Object PhanSo mới
// KHÔNG có tham số vì dùng luôn data của Object hiện tại
// Bắt buộc có const để bảo vệ Object gốc
PhanSo operator-() const {
// Trả về một phân số mới với tử số bị đảo dấu
return PhanSo(-tuSo, mauSo);
}
};
int main() {
cout << "--- HE THONG DAO CHIEU PHAN SO ---\n";
PhanSo ps1(3, 4); // Phân số dương: 3/4
PhanSo ps2(-2, 5); // Phân số âm: -2/5
cout << "ps1 ban dau: "; ps1.inPhanSo();
// GỌI PHÉP THUẬT:
PhanSo ps1_dao = -ps1;
cout << "Sau khi goi -ps1: "; ps1_dao.inPhanSo();
// Kiểm tra tính toàn vẹn của Object gốc
cout << "Kiem tra lai ps1 (Van phai la 3/4): "; ps1.inPhanSo();
cout << "\n-------------------\n";
cout << "ps2 ban dau: "; ps2.inPhanSo();
// Số âm thêm dấu âm thành số dương!
PhanSo ps2_dao = -ps2;
cout << "Sau khi goi -ps2: "; ps2_dao.inPhanSo();
return 0;
}
Nhận xét: Cú pháp -ps1 trông thật sự hoàn hảo và tự nhiên. C++ đã ngầm dịch nó thành ps1.operator-() và mọi thứ diễn ra trơn tru đúng với quy luật toán học.
Tạm kết & Gợi mở
Tuyệt vời! Anh em đã nếm thử sức mạnh của Operator Overloading. Bằng cách dùng Member Function, chúng ta thao túng trực tiếp Object cực kỳ nhanh gọn.
Tuy nhiên, mình muốn dừng lại một nhịp trước khi chúng ta đi vào các toán tử phức tạp hơn như +, -, *, /. Xuyên suốt dự án PhanSo, chúng ta đã cố gắng giấu nhẹm tuSo và mauSo vào private, sau đó phải loay hoay dùng friend class hoặc operator để tương tác với chúng.
Nhưng trong thực tế đi làm, đôi khi Front-end chỉ đơn giản là gọi API muốn XEM cái tử số của bạn bằng bao nhiêu để hiển thị lên UI, hoặc muốn CẬP NHẬT lại mẫu số theo một input mới từ form. Chúng ta không thể lúc nào cũng lôi "hàng nóng" (Operator/Friend) ra dùng cho những nghiệp vụ đơn giản như vậy được.
Đã đến lúc chúng ta nhìn lại 2 khái niệm tưởng chừng như cơ bản nhất nhưng lại bị 90% Junior code sai chuẩn thiết kế. Hẹn gặp lại anh em ở Bài 26: Getter - Setter là gì? Nghệ thuật kiểm soát cổng ra vào dữ liệu! Nhớ Upvote để ủng hộ mình ra bài mới nhé!
All Rights Reserved