多載 I/O 運算子

在前面幾章中,我們學會了三種多載運算子的方法,也知道要如何多載基本的加減乘除運算子。

然而,運算子不只有加減乘除,還有其他的比如說 >、<、!= 等等。

在這章中,我們要來看要如何多載 I/O 運算子!也就是 std::cout << 中的 <<std::cin >> 中的 >>

問題

首先我們先來看看為什麼我們需要多載 I/O 運算子。

假設我們有這樣的一個類別:

class Date
{
public:
    Date(int day, int month, int year)
        : m_day(day), m_month(month), m_year(year) {}

    int getDay() const { return m_day; }
    int getMonth() const { return m_month; }
    int getYear() const { return m_year; }

private:
    int m_day;
    int m_month;
    int m_year;
};

在這之前,如果我們想要印出一個 Date 物件的資訊,我們大概會這樣做:

Date date{4, 3, 1996};

std::cout << "今天的日期是:" << date.getYear() << '/' << date.getMonth() << '/' << date.getDay();

這樣當然可行,但是不覺得很冗長很麻煩嗎?

而且,如果這個類別更加複雜,有更多的內容資訊怎麼辦?

接下來我們就來討論兩種解決方法。

解決方法ㄧ

第一個解法就是在這個 Date 類別中,另外寫一個成員函數 print()

這個函數負責印出物件的內容資訊,比如說:

class Date
{
public:
    Date(int day, int month, int year)
        : m_day(day), m_month(month), m_year(year) {}

    int getDay() const { return m_day; }
    int getMonth() const { return m_month; }
    int getYear() const { return m_year; }

    void print()
    {
        std::cout << m_year << '/' << m_month << '/' << m_day;
    }

private:
    int m_day;
    int m_month;
    int m_year;
};

如此一來,如果我們想要印出物件的資訊時,我們只需要這樣做:

Date date{4, 3, 1996};

std::cout << "今天的日期是:";
date.print();

這樣簡化很多了,但仍然有幾個缺點。

首先,我們還是需要分為兩行才能印出物件資訊。另外,這樣的方法也不夠直覺。

如果我們可以像下面這樣,那不是更好?

Date date{4, 3, 1996};

std::cout << "今天的日期是:" << date;

這就要接到我們的第二個解法。

解決方法二

第二個解法就是我們今天這章的重點啦!

我們可以來多載運算子 <<,來讓輸出資訊更為直觀且方便!

廢話不多說,我們直接來看解法:

class Date
{
public:
    Date(int day, int month, int year)
        : m_day(day), m_month(month), m_year(year) {}

    int getDay() const { return m_day; }
    int getMonth() const { return m_month; }
    int getYear() const { return m_year; }

private:
    int m_day;
    int m_month;
    int m_year;
};

std::ostream& operator<<(std::ostream& out, const Date& date)
{
    out << date.getYear() << '/' << date.getMonth() << '/' << date.getDay();
    return out;
}

這是利用普通函數多載運算子,你們也可以使用 Friend 函數做到一樣的事。

利用什麼方法在這裡不是重點,重點是下面這個東西:

std::ostream& operator<<(std::ostream& out, const Date& date)

這是什麼東西?

我們知道,a + b 這個表達式,左邊的運算元是一個整數 a,右邊是另一個整數 b

std::cout << date 呢?這個表達式右邊的運算元是 date,那左邊的運算元是什麼?

其實左邊的運算元就是一個類型是 std::ostream 的物件。

因此,我們函數中的第一個參數才是一個 std::ostream 型態的東西。

接著來看看回傳型態 std::ostream&。這個回傳型態是固定的,不能被更改!

這是因為 C++ 不允許我們複製一個 std::ostream 物件。另外,這樣的回傳型態也讓我們可以做到 函數串接 method chaining

這代表我們可以做到這樣的事:

std::cout << date1 << date2 << std::endl;

首先,std::cout << date1 被執行後,他會回傳一個 std::ostream 物件,接著 std::cout << date2 被執行,以此類推。

理解後,我們就可以透過簡單的一行來印出物件的資訊啦!

Date date{4, 3, 1996};

std::cout << "今天的日期是:" << date;

多載 >>

我們可以利用同樣的方式來多載 std::cin >> 中的 >>

只是參數的類別型態和回傳型態變為 std::istream

class Date
{
public:
    Date(int day, int month, int year)
        : m_day(day), m_month(month), m_year(year) {}

    void setDay(int day) { m_day = day; }
    void setMonth(int month) { m_month = month; }
    void setYear(int year) { m_year = year; }

private:
    int m_day;
    int m_month;
    int m_year;
};

std::istream& operator>>(std::istream& in, Date& date)
{
    int day;
    int month;
    int year;
    
    in >> day;
    in >> month;
    in >> year;
    
    date.setDay(day);
    date.setMonth(month);
    date.setYear(year);
    
    return in;
}

你可以使用 Friend 函數的方式來讓 operator>> 直接取得物件內容物,這樣會讓程式碼看起來更為簡潔。

但如果類別不是你寫的,你也沒有權限去修改那個類別,那就只能透過 getter/setter 的方式來做到這件事啦!

總結

這篇我們學到了兩個使用頻率非常高的運算子:I/O 運算子!

在現實中,我們常常會需要多載這兩個運算子來讓我們可以方便印出物件的資訊。

那麼下一章我們就繼續來看其他運算子的多載吧!

那這篇就到這裡啦!有學到東西的話歡迎留五星評價喔!