1. 程式人生 > >C++回顧——運算子過載

C++回顧——運算子過載

運算子過載只是一種語法上的方便,實際上它是另一種函式呼叫的方式,其不同之處在於函式的引數不是出現在圓括號內,而是緊貼在一些字元旁邊;呼叫運算子時要把運算子放置在引數之間、引數之後、引數之前;編譯器決定呼叫哪一個“函式”。

在C++中,可以定義一個處理類的新運算子,函式的名字由關鍵字operator及其後緊跟的運算子組成。

一、語法
函式名字是[email protected],這裡@代表了被過載的運算子。引數的個數取決於兩個因素:
1)運算子是一元的還是二元的;
2)運算子被定義為全域性函式(對於一元是一個引數,對於二元是兩個引數)還是成員函式(對於一元沒有引數,對於二元是一個引數——此時該類的物件用做左側引數,唯一的引數是出現在運算子右側的那個運算元)。
例如:

#define MY_STRING_NEW(_T_, _size_) new _T_[_size_]
#define MY_STRING_NEW_char(_size_) MY_STRING_NEW(char, _size_)
#define MY_STRING_DELETE(_p_) delete []_p_;


class AString
{
public:
    AString(const AString &s, char c)
    {
        SetStartLen(s.Len() + 1);
        char *chars = _chars;
        unsigned
len = s.Len(); memcpy(chars, s, len); chars[len] = c; chars[(size_t)len + 1] = 0; } ~AString() { MY_STRING_DELETE(_chars); } unsigned Len() const { return _len; } void SetStartLen(unsigned len) { _chars = 0; _chars = MY_STRING_NEW_char(len + 1
); _len = len; } friend AString operator+(const AString &s, char c) { return AString(s, c); } AString &operator+=(char c) { unsigned len = _len; char *chars = _chars; chars[len++] = c; chars[len] = 0; _len = len; return *this; } private: char *_chars; unsigned _len; };

二、可過載的運算子
雖然幾乎所有C中的運算子都可以過載,但運算子過載的使用是受限制的(不能使用C中沒有意義的運算子,不能改變運算子的優先順序,不能改變運算子的引數個數等)。

1、一元運算子
全域性函式形式(非成員的友元函式):

// Non-member functions
class Integer {
    long i;
    Integer* This() { return this;}

public:
    Integer(long ll = 0) : i(ll) {}

    friend const Integer& operator+(const Integer& a); // No side effects takes const&

    friend const Integer operator-(const Integer& a);

    friend const Integer operator~(const Integer& a);

    friend Integer* operator&(Integer& a);

    friend int operator!(const Integer& a);

    friend const Integer& operator++(Integer& a); // Prefix

    friend const Integer operator++(Integer& a, int); // Postfix

    friend const Integer& operator--(Integer& a); // Prefix

    friend const Integer operator--(Integer& a, int); // Postfix
};

// Global operators
const Integer& operator+(const Integer& a) 
{
    return a;
}

const Integer operator-(const Integer& a) 
{
    return Integer(-a.i);
}

const Integer operator~(const Integer& a)
{
    return Integer(~a.i);
}

Integer* operator&(Integer& a)
{
    return a.This(); // &a is recursive
}

int operator!(const Integer& a)
{
    return !a.i;
}

// Prefix,return incremented value
const Integer& operator++(Integer& a)
{
    a.i++;
    return a;
}

// Postfix,return the value before increment
const Integer operator++(Integer& a, int)
{
    Integer before(a.i);
    a.i++;
    return before;
}

// Prefix, return decremented value
const Integer& operator--(Integer& a)
{
    a.i--;
    return a;
}

// Postfix,return the value before decrement
const Integer operator--(Integer& a, int)
{
    Integer before(a.i);
    a.i--;
    return before;
}

成員函式形式:

// Member functions 
class Byte {
    unsigned char b;
public:
    Byte(unsigned char bb = 0) : b(bb) {}
    const Byte& operator+() const
    {
        return *this;
    }

    const Byte operator-() const 
    {
        return Byte(-b);
    }

    const Byte operator~() const
    {
        return Byte(~b);
    }

    Byte operator!() const
    {
        return Byte(!b);
    }

    Byte* operator&()
    {
        return this;
    }

    const Byte& operator++() // Prefix
    {
        b++;
        return *this;
    }

    const Byte operator++(int) // Postfix
    {
        Byte before(b);
        b++;
        return before;
    }

    const Byte& operator--() // Prefix
    {
        --b;
        return *this;
    }

    const Byte operator--(int)
    {
        Byte before(b);
        --b;
        return before;
    }
};

對於自增和自減,使用者所見到的是對字首和字尾版本呼叫不同的函式,實際上這兩個函式呼叫有不同的標記(編譯器為int引數傳遞一個啞元常量值用來為字尾版產生不同的標記)。

2、二元運算子
全域性函式形式:

// Non-member functions
class Integer {
    long i;

public:
    Integer(long ll = 0) : i(ll) {}

    friend const Integer operator+(const Integer& left, const Integer& right); 

    friend const Integer operator-(const Integer& left, const Integer& right);

    friend const Integer operator*(const Integer& left, const Integer& right);

    friend const Integer operator/(const Integer& left, const Integer& right);

    friend const Integer operator%(const Integer& left, const Integer& right);

    friend const Integer operator^(const Integer& left,
    const Integer& right); 

    friend const Integer operator&(const Integer& left, const Integer& right); 

    friend const Integer operator|(const Integer& left, const Integer& right); 

    friend const Integer operator<<(const Integer& left, const Integer& right); 

    friend const Integer operator>>(const Integer& left, const Integer& right);

    friend Integer& operator+=(Integer& left, const Integer& right);

    friend Integer& operator-=(Integer& left, const Integer& right);

    friend Integer& operator*=(Integer& left, const Integer& right);

    friend Integer& operator/=(Integer& left, const Integer& right);

    friend Integer& operator%=(Integer& left, const Integer& right);

    friend Integer& operator^=(Integer& left, const Integer& right);

    friend Integer& operator&=(Integer& left, const Integer& right);

    friend Integer& operator|=(Integer& left, const Integer& right);

    friend Integer& operator>>=(Integer& left, const Integer& right);

    friend Integer& operator<<=(Integer& left, const Integer& right);

    friend int operator==(const Integer& left, const Integer& right);

    friend int operator!=(const Integer& left, const Integer& right);

    friend int operator<(const Integer& left, const Integer& right);

    friend int operator>(const Integer& left, const Integer& right);

    friend int operator<=(const Integer& left, const Integer& right);

    friend int operator>=(const Integer& left, const Integer& right);

    friend int operator&&(const Integer& left, const Integer& right);

    friend int operator||(const Integer& left, const Integer& right);
};

// Global operators
const Integer operator+(const Integer& left, const Integer& right) 
{
    return Integer(left.i + right.i);
}

const Integer operator-(const Integer& left, const Integer& right) 
{
    return Integer(left.i - right.i);
}

const Integer operator*(const Integer& left, const Integer& right) 
{
    return Integer(left.i * right.i);
}

const Integer operator/(const Integer& left, const Integer& right) 
{
    ASSERT(right.i != 0);
    return Integer(left.i / right.i);
}

const Integer operator%(const Integer& left, const Integer& right) 
{
    ASSERT(right.i != 0);
    return Integer(left.i % right.i);
}

const Integer operator^(const Integer& left, const Integer& right) 
{
    return Integer(left.i ^ right.i);
}

const Integer operator&(const Integer& left, const Integer& right) 
{
    return Integer(left.i & right.i);
}

const Integer operator|(const Integer& left, const Integer& right) 
{
    return Integer(left.i | right.i);
}

const Integer operator<<(const Integer& left, const Integer& right) 
{
    return Integer(left.i << right.i);
}

const Integer operator>>(const Integer& left, const Integer& right) 
{
    return Integer(left.i >> right.i);
}

Integer& operator+=(Integer& left, const Integer& right)
{
    //if(&left == &right){}
    left.i += right.i;
    return left;
}

Integer& operator-=(Integer& left, const Integer& right)
{
    left.i -= right.i;
    return left;
}

Integer& operator*=(Integer& left, const Integer& right)
{
    left.i *= right.i;
    return left;
}

Integer& operator/=(Integer& left, const Integer& right)
{
    ASSERT(right.i != 0);
    left.i /= right.i;
    return left;
}

Integer& operator%=(Integer& left, const Integer& right)
{
    ASSERT(right.i != 0);
    left.i %= right.i;
    return left;
}

Integer& operator^=(Integer& left, const Integer& right)
{
    left.i ^= right.i;
    return left;
}

Integer& operator&=(Integer& left, const Integer& right)
{
    left.i &= right.i;
    return left;
}

Integer& operator|=(Integer& left, const Integer& right)
{
    left.i |= right.i;
    return left;
}

Integer& operator>>=(Integer& left, const Integer& right)
{
    left.i >>= right.i;
    return left;
}

Integer& operator<<=(Integer& left, const Integer& right)
{
    left.i <<= right.i;
    return left;
}

int operator==(const Integer& left, const Integer& right)
{
    return left.i == right.i;
}

int operator!=(const Integer& left, const Integer& right)
{
    return left.i != right.i;
}

int operator<(const Integer& left, const Integer& right)
{
    return left.i < right.i;
}

int operator>(const Integer& left, const Integer& right)
{
    return left.i > right.i;
}

int operator<=(const Integer& left, const Integer& right)
{
    return left.i <= right.i;
}

int operator>=(const Integer& left, const Integer& right)
{
    return left.i >= right.i;
}

int operator&&(const Integer& left, const Integer& right)
{
    return left.i && right.i;
}

int operator||(const Integer& left, const Integer& right)
{
    return left.i || right.i;
}

成員函式形式:

// Member functions 
class Byte {
    unsigned char b;
public:
    Byte(unsigned char bb = 0) : b(bb) {}
    const Byte operator+(const Byte& right) const
    {
        return Byte(b + right.b);
    }

    const Byte operator-(const Byte& right) const
    {
        return Byte(b - right.b);
    }

    const Byte operator*(const Byte& right) const
    {
        return Byte(b * right.b);
    }

    const Byte operator/(const Byte& right) const
    {
        ASSERT(right.b != 0);
        return Byte(b / right.b);
    }

    const Byte operator%(const Byte& right) const
    {
        ASSERT(right.b != 0);
        return Byte(b % right.b);
    }

    const Byte operator^(const Byte& right) const
    {
        return Byte(b ^ right.b);
    }

    const Byte operator&(const Byte& right) const
    {
        return Byte(b & right.b);
    }

    const Byte operator|(const Byte& right) const
    {
        return Byte(b | right.b);
    }

    const Byte operator<<(const Byte& right) const
    {
        return Byte(b << right.b);
    }

    const Byte operator>>(const Byte& right) const
    {
        return Byte(b >> right.b);
    }

    Byte& operator=(const Byte& right)
    {
        if( this == &right )
        {
            // self-assignment 程式碼檢測自賦值
            return *this;
        }

        b = right.b;
        return *this;
    }

    Byte& operator+=(const Byte& right)
    {
        b += right.b;
        return *this;
    }

    Byte& operator-=(const Byte& right)
    {
        b -= right.b;
        return *this;
    }

    Byte& operator*=(const Byte& right)
    {
        b *= right.b;
        return *this;
    }

    Byte& operator/=(const Byte& right)
    {
        ASSERT(right.b != 0);
        b /= right.b;
        return *this;
    }

    Byte& operator%=(const Byte& right)
    {
        ASSERT(right.b != 0);
        b %= right.b;
        return *this;
    }

    Byte& operator^=(const Byte& right)
    {
        b ^= right.b;
        return *this;
    }

    Byte& operator&=(const Byte& right)
    {
        b &= right.b;
        return *this;
    }

    Byte& operator|=(const Byte& right)
    {
        b |= right.b;
        return *this;
    }

    Byte& operator>>=(const Byte& right)
    {
        b >>= right.b;
        return *this;
    }

    Byte& operator<<=(const Byte& right)
    {
        b <<= right.b;
        return *this;
    }

    int operator==(const Byte& right) const
    {
        return b == right.b;
    }

    int operator!=(const Byte& right) const
    {
        return b != right.b;
    }

    int operator<(const Byte& right) const
    {
        return b < right.b;
    }

    int operator>(const Byte& right) const
    {
        return b > right.b;
    }

    int operator<=(const Byte& right) const
    {
        return b <= right.b;
    }

    int operator>=(const Byte& right) const
    {
        return b >= right.b;
    }

    int operator&&(const Byte& right) const
    {
        return b && right.b;
    }

    int operator||(const Byte& right) const
    {
        return b || right.b;
    }
};

在運算子過載中,所有賦值運算子都有程式碼檢測自賦值,其最重要的地方是operator=,有些地方不需要,如operator+=。

3、不常用的運算子
下標運算子operator[],必須是成員函式並且它只接受一個引數。
運算子new 和delete用於控制動態儲存分配並能按許多種不同的方法進行過載。
逗號運算子“operator,”呼叫的目標不是函式引數表,而是被逗號分隔開的、沒有被括號括起來的物件。除了使語言保持一致性外,沒有許多實際用途。
operator->,當希望一個物件表象得像一個指標時,通常用它。由於這樣一個物件比一般的指標有著更多與生俱來的靈巧性,於是常被稱作靈巧指標。如果想用類包裝一個指標以使指標安全,或者在迭代器普通的用法中,這樣做會特別有用。指標間接引用運算子一定是一個成員函式,它必須返回一個物件(或物件的引用),該物件也有一個指標間接引用運算子;或必須返回一個指標,被用於選擇指標間接引用運算子箭頭所指向的內容。
operator->*是一個二元運算子(指向成員的指標間接引用運算子),它是專為模仿內部資料型別的成員指標行為而提供的。在定義operator->*時要注意它必須返回一個物件,對於這個物件,可以用正在呼叫的成員函式為引數呼叫operator()。operator()的函式呼叫必須是成員函式,它是唯一的允許在它裡面有任意個引數的函式。要想建立一個operator->*,必須先建立帶有operator()類(這是operator->*將返回物件的類)。

4、不能過載的運算子
這樣限制的原因通常是出於安全的考慮:如果這些運算子被過載,將會破壞安全機制,使事情變得困得或混淆現有的習慣。
1)成員選擇operator.;
2)成員指標間接引用operator.*;
3)域運算子::;
4)sizeof運算子;
5)條件運算子?:;

注意,運算子過載中,C/C++中沒有Fotran語言的求冪運算子operator**,不存在使用者定義的運算子(難以決定其優先順序),不能改變優先順序規則。

5、非成員運算子
運算子可能是成員運算子或非成員運算子,如果沒有什麼差異,選擇成員運算子,這樣做強調了運算子和類的聯合。當左側運算元是當前的物件時,運算子會工作得很好。
但是有時左側運算子特別是類的物件(這種情況通常出現在輸入輸出流過載operator<< 和>>,因為輸入輸出流是一個基本C++庫,我們可能想為定義的大部分類過載運算子),就需要非成員運算子。例如:

#include <iostream>
using namespace std;

class IntArray{
    enum { sz = 5 };
    int i[sz];
public:
    IntArray() { memset(i, 0, sz * sizeof(*i)); }
    int& operator[](int x)
    {
        ASSERT(x >= 0 && x < sz);
        return i[x];
    }

    friend ostream& operator<<(ostream& os, const IntArray& ia);
    friend istream& operator>>(istream& is, IntArray& ia);
};

ostream&
operator<<(ostream& os, const IntArray& ia)
{
    for (int j = 0; j < ia.sz; ++j)
    {
        os << ia.i[j];
        if ( j != ia.sz - 1 )
        {
            os << ",";
        }
    }

    os << endl;
    return os;
}

istream& operator>>(istream& is, IntArray& ia)
{
    for (int j = 0; j < ia.sz; ++j)
    {
        is >> ia.i[j];
    }

    return is;
}

三、引數和返回值
雖然可以使用任何需要的方式傳遞和返回引數,但在大部分情況下應選擇如下模式:
1)對於任何函式引數,如果僅需要從引數中讀而不改變它,預設地應當作為const引用來傳遞它;
2)返回值的型別取決於運算子的具體含義,如果使用該運算子的結果是產生一個新值,就需要產生一個作為返回值的新物件;
3)所有賦值運算子均改變左值,因此所有賦值運算子的返回值對於左值應該是非常量引用;
4)對於邏輯運算子,至少得到一個int返回值,最好是bool返回值;
5)對於自增和自減,讓字首版本是非常量的(字首版本返回改變後的物件),字尾版本是常量的(字尾版本返回改變之前的物件);
6)臨時物件自動被定為常量。

返回值優化:
通過傳值方式返回要建立新物件時,應注意使用的形式,如在operator+:
return Integer(left.i + right.i);
這是臨時物件語法,它是說:“建立一個Integer物件並返回它”。如果建立一個有名字的區域性物件並返回它,結果是不一樣的,如下:
Integer tmp(left.i + right.i);
return tmp;
將發生三件事:
1)建立tmp物件;
2)拷貝建構函式把tmp拷貝到外部返回值的儲存單元裡;
3)當tmp在作用域的結尾是呼叫解構函式。
相反,“返回臨時物件”的方式是完全不同的,當編譯器看到我們這樣做時,它明白對建立的物件沒有其他需求,只是返回它,所以編譯器直接地把這個物件建立在外部返回值的記憶體單元。因為不是真正建立一個區域性物件,所以僅需要一個普通建構函式呼叫(不需要拷貝建構函式),且不會呼叫解構函式,故效率比較高。這種方式常被稱作返回值優化。

四、引用計數
如果物件需要大量的記憶體或過高的初始化,我們也許想避免拷貝(拷貝構造和operator=),解決辦法是使用引用計數。可以使一塊儲存單元具有智慧,它知道有多少物件指向它。拷貝建構函式或賦值運算意味著把另外的指標指向現在的儲存單元並增加引用計數,消除意味著減小引用計數,如果引用計數為0則意味著銷燬這個物件。如果向物件執行寫入操作,則需使用寫拷貝技術。在向這塊儲存單元寫之前,應該確信沒有其他人使用它,如果引用計數大於1,在寫之前必須拷貝這塊儲存單元,這樣就不會影響他人了。

五、自動型別轉換
在C/C++中,如果編譯器看到一個表示式或函式呼叫使用了一個不合適的型別,它經常會執行一個自動型別轉換。在C++中,可以通過定義自動型別轉換函式來為使用者定義型別達到相同效果(特殊型別的建構函式和過載的運算子)。
1、建構函式轉換
如果定義一個建構函式,這個建構函式能把另一型別物件(或引用)作為它的單個引數,那麼這個建構函式允許編譯器執行自動型別轉換。但是有時通過建構函式自動型別可能出現問題,為了避開這個麻煩,可以通過在前面加關鍵字explicit(只能用於建構函式)。

2、運算子轉換
可以建立一個成員函式,這個函式通過在關鍵字operator後緊跟想要轉換到的型別的方法,將當前型別轉換為希望的型別。

注意,用建構函式轉換,目的類執行轉換;使用運算子,是源類執行轉換。使用建構函式技術沒有辦法實現從使用者定義型別向內建型別轉換,這隻有運算子過載可能做到。

相關推薦

C++回顧——運算子過載

運算子過載只是一種語法上的方便,實際上它是另一種函式呼叫的方式,其不同之處在於函式的引數不是出現在圓括號內,而是緊貼在一些字元旁邊;呼叫運算子時要把運算子放置在引數之間、引數之後、引數之前;編譯器決定呼叫哪一個“函式”。 在C++中,可以定義一個處理類的新運算

C/C++】運算子過載

C++ 中允許programmer 根據自身需要過載一系列的運算子,比如過載==運算子就比定義 equals() 函式名了的多。但是儘量不要過載表意不明的運算子。 常用的過載運算子有 : =, ==, <,>, <<, >>

c++成員運算子過載和友元運算子過載的比較(以++,--運算子為例)

1、對雙目運算子而言,成員運算子過載函式引數列表中含有一個引數,而友元運算子過載函式引數列表含有兩個引數;對單目運算子而言,成員運算子過載函式引數列表中沒有引數,而友元運算子過載函式引數列表含有一個引數。 2、雙目運算子一班可以被過載為友元運算子和成員函式運算

C++哪些運算子過載可以過載

運算子過載是C++極為重要的語言特性之一,本文將用程式碼例項回答——C++哪些運算子可以過載?如何過載?實現運算子過載時需要注意哪些? 哪些運算子可以過載,哪些不可過載? C++98,C++0x,C++11對“哪些運算子過載可以過載”有一致的規定,具體如下: 其中,很少

C++的運算子過載

C++中預定義的運算子的操作物件只能是基本資料型別。但實際上,對於許多使用者自定義型別(例如類),也需要類似的運算操作。這時就必須在C++中重新定義這些運算子,賦予已有運算子新的功能,使它能夠用於特定型別執行特定的操作。運算子過載的實質是函式過載,它提供了C++的可擴充套

C#中運算子過載的幾點注意

這是一篇簡記,因此不做特別的排版 1、運算子過載不能多型 這是最容易出問題的地方,看下面的程式碼 過載者如下: public class Father { public int value; public static implicit operat

C++_運算子過載的注意事項

1、過載操作符沒必要一定是成員函式,還可以是友元函式。 2、過載操作符函式為成員函式主要是你需要操作類內部的成員, 必須是成員函式或友元函式才行。 3、至於由深淺拷貝的原因要使其成為成員函式,這個不知道。 4、如果運算子被過載為全域性函式,那麼只有一個

C++回顧之前置++、後置++、不等號!及賦值運算子過載

        運算子過載的主要目的是為了讓類物件能像普通資料型別一樣能夠進行加減乘除,自加自減等操作,非常直觀方便。現在來回顧C++的自加減(分前置與後置)以及不等號非運算子,賦值運算子的過載。         1 ++過載         (1)前置++運算子的過載方式

C++運算子過載詳細解說及程式碼編寫

一、不能過載的運算子:   (1) "."(類成員訪問運算子)   (2)" .*"(類成員指標訪問運算子)   (3) "::"(域運算子)   (4)"sizeof"(長度運算子)   (5) " ?:"(條件運算子)

C++類和物件.四個預設成員函式(賦值運算子過載

1.(1)類的定義   類就是具有相同資料和相同操作的一組物件的集合。 學生類定義: class student {//成員變數char* name;int age;int sex;//成員函式void speak(){cout<<name<<"年

C++ 運算子過載 運算子屬於左邊的

class A { public: int a; A operator+(const A &a) { A res; res.a = this->a+a.a; return res; } }; int main() { A a1,a2; a1.a = 1;

大數加減乘除---C++運算子過載

#include <iostream> #include <typeinfo> #include <string> #include <vector> #include <iterator> using namespace std; tem

C++ 略坑的運算子過載練習

補充程式碼,使程式按要求輸出    #include <iostream> using namespace std; template <class T> class Add{ public: // 在此處補充你的程式碼 }; int mai

C++ 看上去有點奇怪的運算子過載

總時間限制:  1000ms   記憶體限制:  65536kB // 在此處補充你的程式碼 描述 下面的MyInt類只有一個成員變數。MyInt類內部的部分程式碼被隱藏了。假設下面的程式能編譯通過,且輸出結果是: 4,1 請寫出被隱

YTUOJ——C++時間類的運算子過載

題目描述 C++時間類的運算子過載 定義一個時間類Time,其資料成員為表示時間的小時(hour)、分(minute),秒(second)。 過載運算子“+”,使之能用於時間物件的加法運算;過載運算子“<<”,使之能用於時間物件的輸出操作。 (1)參加運算的兩個運算元可以都是時間

C++對運算子進行過載學習筆記

1.  一個類的成員函式是暗含著 this 指標的,eg; 1 #include<iostream> 2 using namespace std; 3 4 class A{ 5 public: 6 A(){ x = 0;} 7 double get

C++ 運算子過載operator

//MyTime.h #ifndef MYTIME_H_ #define MYTIME_H_ class Time { private: int hours; int minutes; public: Time(); Time(int h, int m = 0); void AddMin

c++學習總結(四)——運算子過載與標準模板庫(STL)

一、心得總結     運算子過載使得使用者自定義的資料以一種更簡潔的方式工作。例如在做ATM模擬系統時,使用過載“<”來比較時間,可以簡化程式,減少程式碼。另外,我們也可以過載運算子函式,將運算子用於操作自定義的資料型別。過載運算子函式可以對運算子做出新的解釋,即定義使用

《隨筆二十二》—— C++中的“ 運算子過載

目錄 前言 過載運算子的兩種形式 運算子成員函式  和 運算子友元函式的比較 前言 ● 為什麼要對運算子進行過載: C++預定義中的運算子的操作物件只侷限於基本的內建資料型別,但是對於我們自定義的型別是沒有辦法操作的。但是大多時候我們需

c++中運算子過載

為什麼要過載 運算子過載能夠讓一個運算子根據運算子兩側的型別呼叫不同的函式,實現多重功能,精簡優化程式碼。 過載方式 返回值 operator 運算子 (形參列表) 舉例:實現兩個時間相