Nạp Chồng Toán Tử Có Quan Trọng Không?

Nạp chồng (overloading) toán tử thường được người lập trình sử dụng rất nhiều trong lập trình hướng đối tượng. Vậy nó có gì thú vị các bạn cùng tìm hiểu nhé!

Các loại toán tử

Trong C++ có các loại toán tử đơn, đôi, nhập xuất.

Bảng các toán tử có thể nạp chồng trong C++

Toán tử đơn là toán tử một ngôi, nó có thể là toán tử trước, toán tử sau ví dụ ++ , –,…

Toán tử đôi là các toán tử hai ngôi: + , – , * , / …

Toán tử nhập xuất là các toán tử chuẩn hóa theo thư viện istream (toán tử nhập >>) , ostream (toán tử xuất <<)

Các bạn cùng mình xét ví dụ dưới đây để hiểu rõ hơn nhé:

#include <iostream>
using namespace std;

class ThoiGian
{
public:
    int hour;       
    int minute;
public:
    ThoiGian() : hour(0), minute(0) {}  //hàm tạo mạc định
    ThoiGian(int h, int m) : hour(h) , minute(m) {} // hàm tạo 2 tham số
    ThoiGian operator++ () // hàm nạp chồng toán tử ++ tiền tố
    {
        ++minute;        
        if (minute >= 60)
        {
            ++hour;
            minute -= 60;
        }
        return ThoiGian(hour, minute);
    }
    ThoiGian operator++(int) //hàm nạp chồng toán tử ++ hậu tố
    {
        ThoiGian T(hour, minute);
        ++minute;         
        if (minute >= 60)
        {
            ++hour;
            minute -= 60;
        }
        return T;
    }
    ThoiGian operator +(ThoiGian& Ts) //hàm nạp chồng toán tử + một ngôi
    {
        ThoiGian T;
        T.hour = this->hour + Ts.hour;
        T.minute = this->minute + Ts.minute;
        if (T.minute >= 60)
        {
            ++T.hour;
            T.minute -= 60;
        }
        return T;
    }
// nạp chồng toán tử - hai ngôi
    friend ThoiGian operator - (ThoiGian& T1, ThoiGian& T2) 
    {
        ThoiGian T;
        T.hour = T1.hour - T2.hour;
        T.minute = T1.minute - T2.minute;
        return T;
    }
    friend istream& operator >> (istream& is, ThoiGian& T) //nạp chồng toán tử nhập
    {
        cout << "hour = ";
        is >> T.hour;
        cout << "minute = ";
        is >> T.minute;
        return is;
    }
    friend ostream& operator << (ostream& os, ThoiGian& T) // nạp chồng toán tử xuất
    {
        os << T.hour << "h:" << T.minute << "p" << endl;
        return os;
    }
    friend bool operator < (ThoiGian& T1, ThoiGian& T2)
    {
        if (T1.hour == T2.hour)
        {
            if (T1.minute < T2.minute)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        else if(T1.hour < T2.hour)
        {
            return true;
        }
        return false;
    }
};
int main()
{
    ThoiGian T1(8, 59), T2(6, 24);
    ThoiGian T3;
    T3 = T1 + T2;  //test nạp chồng toán tử +
    cout << "Thoi gian hien tai cua T3 la: ";
    cout << T3; //test toán tử xuất <<
    if (T3++.minute == 24) //test nạp chồng toán tử ++ hậu tố
    {
        cout << "Hau to false" << endl;
        cout << T3;
    }
    else
    {
        cout << "Hau to true" << endl;
    }
    cout << "Thoi gian sau khi thuc hien phep toan ++ hau to la: ";
    cout << T3;
    cout << "Thoi gian hien tai cua T3 la: ";
    cout << T3;
    if (++T3.minute == 25) //test nạp chồng toán tử ++ tiền tố
    {
        cout << "Tien to true" << endl;
    }
    else
    {
        cout << "Tien to false" << endl;
        cout << T3;
    }
    cout << "Thoi gian sau khi thuc hien phep toan ++ tien to la: ";
    cout << T3;
    ThoiGian T4; 
    T4 = T1 - T2; // test nạp chồng toán tử - hai ngôi
    cout << "Thong tin T4 ";
    cout << T4;
    ThoiGian T5;
    cout << "Nhap thong tin T5" << endl;
    cin >> T5; // test nạp chồng toán tử nhập >>
    cout << "Thong tin vua nhap la: ";
    cout << T5;
    if (T5 < T4)
    {
        cout << "T5 < T4" << endl;
    }
    else
    {
        cout << "T5 > T4" << endl;
    }
    return 0;
}

Kết quả: 

           

Giải thích:

Trong phần ví dụ trên mình có hướng dẫn các bạn nạp chồng toán tử ++ tiền tố , ++ hậu tố, toán tử + một ngôi, toán tử – hai ngôi , toán tử so sánh hai ngôi, toán tử nhập >>, toán tử xuất <<

Cú pháp cho nạp chồng toán tử một ngôi:  

<kiểu trả về> operator <toán tử cần nạp chồng> (đối số 1)
{
//xử lí
return <biến theo kiểu trả về>;
} 

Đối với nạp chồng tiền tố, thì chúng ta cần phải cộng minute trước rồi mới làm việc, và khi cộng chúng ta nên chuẩn hóa thời gian lại.

Ví dụ: 

ThoiGian operator++ () // hàm nạp chồng toán tử ++ tiền tố
    {
        ++minute;        
        if (minute >= 60)
        {
            ++hour;
            minute -= 60;
        }
        return ThoiGian(hour, minute);
    }

Đối với nạp chồng hậu tố, thì chúng ta cần phải thêm 1 biến ThoiGian T để lưu giá trị thời gian thực hiện tại lại để sau khi làm xong câu lệnh chứa toán tử ++ hậu tố rồi mới tăng giá trị minute lên thêm 1 đơn vị. 

Ví dụ:

ThoiGian operator++(int) //hàm nạp chồng toán tử ++ hậu tố
    {
        ThoiGian T(hour, minute);
        ++minute;         
        if (minute >= 60)
        {
            ++hour;
            minute -= 60;
        }
        return T;
    }

Đối với toán tử + một ngôi, xét câu lệnh T3 = T1 + T2 ở khối lệnh nạp chồng, ta cần phải khai báo thêm thực thể T để chứa các giá trị của thực thể T3, this->hour chính là giá trị hour của thực thể T1 , Ts.hour chính là giá trị hour của thực thể T2 và tương tự với minute.

Xét ví dụ cụ thể như trên:

Tạo thực thể T1(8,59) và thực thể T2(10,24), khai ta thực hiện thành công phép gán T3 = T1 + T2 tức là chúng ta đã nạp chồng thành công toán tử + vầ kết quả T3

Ví dụ:

ThoiGian operator +(ThoiGian& Ts) //hàm nạp chồng toán tử + một ngôi
    {
        ThoiGian T;
        T.hour = this->hour + Ts.hour;
        T.minute = this->minute + Ts.minute;
        if (T.minute >= 60)
        {
            ++T.hour;
            T.minute -= 60;
        }
        return T;
    }

Xét tiếp đến khối lệnh: 

if (T3++.minute == 24) //test nạp chồng toán tử ++ hậu tố
    {
        cout << "Hau to false" << endl;
        cout << T3;
    }
    else
    {
        cout << "Hau to true" << endl;
    }

Khi này T3.minute hiện tại đang là 23 nên nó sẽ thực hiện khối lệnh trong else và in ra dòng “Hau to true” qua đó có thể thấy chúng ta đã overloading toán tử ++ hậu tố thành công.
Tương tự kiểm tra đối với overloading toán tử ++ tiền tố.
Tiếp đến overloading toán tử 2 ngôi, đối với toán tử hai ngôi chúng ta cần phải nạp chồng nó thông qua hàm friend (hàm friend có thể truy cập tới các biến thành viên của lớp kể cả private). Tại sao chúng ta lại phải overloading nó thông qua hàm friend? Do toán tử 2 ngôi có 2 tham số truyền vào nên nó không thể dùng con trỏ this mà phải dùng hàm friend để truy cập đến biến thành viên của lớp.
Cú pháp overloading cho toán tử 2 ngôi:

friend <kiểu trả về> operator <toán tử cần nạp chồng> (đối_số_1 , đối_số_2)
{
//khởi tạo 1 đối tượng để gán giá trị
//các phép gán giá trị
//return đối tượng trả về
}

 

Ở trên mình có nạp chồng toán tử – 2 ngôi theo đúng cú pháp như trên các bạn tham khảo và nạp chồng tương tự với các toán tử +,*,/ nhé.

friend ThoiGian operator - (ThoiGian& T1, ThoiGian& T2) // nạp chồng toán tử - hai ngôi
    {
        ThoiGian T;
        T.hour = T1.hour - T2.hour;
        T.minute = T1.minute - T2.minute;
        return T;
    }

Overloading các toán tử so sánh chúng ta chỉ cần áp dụng đúng như cú pháp mình vừa nêu trên, và mình có làm ví dụ với toán tử so sánh < các bạn tham khảo và áp dụng tương tự với các toán tử so sánh còn lại.

Mình có giải thích một chút là tại sao mình lại hay dùng tham chiếu & cho các đối số vì, khi ta truyền tham trị cho biến là ta đang dùng bản copy của nó đối với hàm, làm như này mất khá nhiều bộ nhớ, để giảm tải việc mất quá nhiều bộ nhớ chúng ta nên dùng trực tiếp nó thông qua địa chỉ, nếu bạn không muốn biến truyền vào bị thay đổi dữ liệu khi ra bên ngoài thì có thể thêm từ khóa const trước đối số truyền vào ví dụ: friend bool operator < (const ThoiGian& T1, const ThoiGian& T2) như này thì các dữ liệu thành viên của các đối số sẽ không bị thay đổi khi ra ngoài.

Tiếp đến là toán tử nhập, cú pháp:

friend istream operator << (istream &is , arg)
{
//các câu lệnh bạn muốn nhập từ bàn phím cho các biến thành viên
return is;	
}

Trong C++ ta có thư viện chuẩn iostream được kêt hợp giữa 2 thư viện chuẩn nhập (istream) và xuất (ostream) vì vậy kiểu trả về ở nạp chồng toán tử nhập sẽ là istream còn toán tử xuất sẽ là ostream

Ví dụ ở trên:

friend istream& operator >> (istream& is, ThoiGian& T) //nạp chồng toán tử nhập
    {
        cout << "hour = ";
        is >> T.hour;
        cout << "minute = ";
        is >> T.minute;
        return is;
    }

Ở đây các bạn phải bắt buộc truyền tham chiếu cho ThoiGian& T, vì khi ra khỏi chương trình thì đối số được nhập vào phải thay đổi theo main nên các bạn hết sức lưu ý với trường hợp nạp chồng toán tử nhập >> này. Và bắt buộc chúng ta phải thay cin thành is nhé (is là tên do mình đặt các bạn hoàn toàn có thể thay thế nó thành nên nào các bạn thích).

Nạp chồng toán tử xuất cũng tương tự với nạp chồng toán tử nhập, cú pháp:

friend ostream operator >> (ostream &os , arg)
{
// các câu lệnh xuất ra màn hình
return os;	
}

Ví dụ ở trên:

friend ostream& operator << (ostream& os, ThoiGian& T) // nạp chồng toán tử xuất
    {
        os << T.hour << "h:" << T.minute << "p" << endl;
        return os;
    }

Ở đây các bạn không cần thiết phải truyền tham chiếu cho ThoiGian& T vì khi ra khỏi hàm T không thay đổi nên đây là điểm khác biệt giữa nhập và xuất tuy nhiên mình vẫn khuyên khích các bạn truyền tham chiếu hơn vì lí do như mình đã kể trên. Và cout cũng phải bắt buộc thay bằng os.

Kết

Các bạn có thể thấy rằng khi nạp chồng toán tử, chúng ta xử lí các câu lệnh ở main trở nên tường minh và dễ hiểu hơn cho cả người code lẫn người đọc, do đó nó rất là quan trọng. Vì vậy các bạn nên học và sử dụng chúng thường xuyên hơn.

Qua bài viết này mình đã giới thiệu hầu hết loại nạp chồng toán tử thường gặp trong lập trình hướng đối tượng. Bài viết của mình đưa ra với mục đích như là một ví dụ cụ thể cho các bạn áp dụng đối với các bài tương tự.

Các bạn nên copy code của mình chạy thử và có gì sai sót mong các bạn comment góp ý. Hẹn gặp lại các bạn trong các bài kế tiếp!