天天看點

C++Primer Plus書之--運算符重載和友元函數運算符重載:友元:

運算符重載:

要重載運算符, 需要使用被稱為運算符函數的特殊函數形式, 運算符函數的格式如下:

operatorop(argument list)
           

例如:operator+()重載+運算符, operator*()重載*運算符, op必須是有效的C++運算符.

例如, 假設有一個Sales類, 并為他定義了一個operator+()成員函數, 以重載+運算符, 以便能夠将兩個Sales對象的銷售額相加, 則如果district2, sid和sara都是Sales類對象, 便可以寫這種等式:

district2 = sid + sara;
           

編譯器發現, 操作數是Sales類對象, 是以使用相應的運算符函數替換上述運算符:

district2 = sid.operator+(sara);
           

然後該函數将隐式的使用sid(因為他調用了方法), 而顯示的使用sara對象(因為他被作為參數傳遞), 來計算總和, 并傳回這個值.

來看一個重載運算符的例子:

先看不添加重載運算符函數的代碼:

// 第一個檔案
// mytime0.h
#ifndef MYTIME0_H_
#define MYTIME0_H_

class Time
{
private:
    int hours;
    int minutes;
    
public:
    // 構造函數
    Time();
    // 提供了預設參數的構造函數
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    // 最後一個const表明函數不會隐式修改成員變量
    Time Sum(const Time & t) const;
    void Show() const;
};

#endif
           

第二個檔案

// 第二個檔案
// mytime0.cpp, Time類的函數的實作檔案
#include <iostream>
#include "mytime0.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m)
{
    hours = h;
    minutes = m;
}

Time Time::Sum(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意這裡, 由于傳回類型是Time, 是以可以直接傳回sum
    // 如果傳回類型是Time & , 則不能傳回sum
    // 因為sum是臨時變量, 函數執行完畢後就會釋放, 是以不能傳回sum的引用
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes" << std::endl;    
}
           

第三個檔案:

// 第三個檔案:
// usetime0.cpp
// compile with mytime0.cpp
#include <iostream>
#include "mytime0.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 調用沒有參數的構造函數
    Time planning;
    // 調用兩個參數的構造函數
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time total;
    
    cout << "planning time = ";
    planning.Show();
    cout << endl;
    
    cout << "Coding time = ";
    coding.Show();
    cout << endl;
    
    cout << "fixing time = ";
    fixing.Show();
    cout << endl;
    
    total = coding.Sum(fixing);
    cout << "coding.Sum(fixing) = ";
    total.Show();
    cout << endl;
    
    return 0;
}
           

程式運作結果為:

C++Primer Plus書之--運算符重載和友元函數運算符重載:友元:

對Time類添加重載+操作符的代碼:

// 第一個檔案
// mytime1.h
#ifndef MYTIME0_H_
#define MYTIME0_H_

class Time
{
private:
    int hours;
    int minutes;
    
public:
    // 構造函數
    Time();
    // 提供了預設參數的構造函數
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    // 最後一個const表明函數不會隐式修改成員變量
    Time Sum(const Time & t) const;
    // 重載+運算符
    Time operator+(const Time & t) const;
    void Show() const;
    
};

#endif
           

第二個檔案

// mytime1.cpp, Time類的函數的實作檔案
#include <iostream>
#include "mytime1.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m)
{
    hours = h;
    minutes = m;
}

Time Time::Sum(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意這裡, 由于傳回類型是Time, 是以可以直接傳回sum
    // 如果傳回類型是Time & , 則不能傳回sum
    // 因為sum是臨時變量, 函數執行完畢後就會釋放, 是以不能傳回sum的引用
    return sum;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意這裡, 由于傳回類型是Time, 是以可以直接傳回sum
    // 如果傳回類型是Time & , 則不能傳回sum
    // 因為sum是臨時變量, 函數執行完畢後就會釋放, 是以不能傳回sum的引用
    return sum;
}

void Time::Show() const
{
    std::cout << hours << " hours, " << minutes << " minutes" << std::endl;    
}
           

第三個檔案

// 第三個檔案:
// usetime0.cpp
// compile with mytime1.cpp
#include <iostream>
#include "mytime1.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 調用沒有參數的構造函數
    Time planning;
    // 調用兩個參數的構造函數
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time total;
    
    cout << "planning time = ";
    planning.Show();
    cout << endl;
    
    cout << "Coding time = ";
    coding.Show();
    cout << endl;
    
    cout << "fixing time = ";
    fixing.Show();
    cout << endl;
    
    total = coding.Sum(fixing);
    cout << "coding.Sum(fixing) = ";
    total.Show();
    cout << endl;
    
    
    total = coding + fixing;
    cout << "coding + fixing = ";
    total.Show();
    cout << endl;
    
    
    Time morefixing(3, 28);
    cout << "more fixing time = ";
    morefixing.Show();
    cout << endl;
    
    total = morefixing.operator+(total);
    cout << " morefixing.operator+(total) = ";
    total.Show();
    cout << endl;
    
    return 0;
}
           

程式運作結果:

C++Primer Plus書之--運算符重載和友元函數運算符重載:友元:

C++運算符重載的限制:

1.重載後的運算符必須至少有一個操作數是使用者定義的類型, 這能防止使用者重載标準類型運算符.

2.使用運算符時不能違反運算符原來的文法規則. 例如:不能将球磨運算符(%)重載成為一個操作數:

int x;
Time t1;
// 這個不符合文法
% x;
// 非法的重載
% t1;
           

3.不能修改運算符的優先級.

4.不能建立新的運算符.

5.不能重載下面這些運算符:

sizeof

. 成員運算符

.* 成員指針運算符

:: 作用域解析運算符

?: 條件運算符

typeid 一個RTTI運算符

const_cast, dynamic_cast, reinterpret_cast, static_cast 強制類型轉換運算符

6.大多數運算符都能通過成員或非成員函數進行重載, 但下面的運算符隻能通過成員函數進行重載:

=, (), [], ->

友元:

C++提供了另一種形式的通路權限: 友元, 友元有三種:

友元函數

友元類

友元成員函數

通過讓函數稱為類的友元, 可以賦予該函數與類的成員函數相同的通路權限.

在前文的Time類示例中, 重載的乘法運算符與其他兩種重載運算符(+, -)的差别在于, 它使用了兩種不同的類型, 也就是說, 加法和減法運算符都結合兩個Time值, 而乘法運算符将一個Time值和一個double值結合在一起, 這限制了該運算符的使用方式. 例如:

A = B * 2.3;
           

将被轉換為:

A = B.operator*(2.3);
           

但下面的語句就不行了:

A = 2.3 * B;
           

這條語句就不能正确執行了

從概念上說, 2.3*B應和B*2.3是一樣的, 但第一個表達式不對應于成員函數, 因為2.3不是Time類型對象.

C++提供了一種解決辦法: 非成員函數(大多數運算符都可以通過成員或非成員函數來重載). 非成員函數不是由對象調用的, 它使用所有的值(包括對象)都是顯示參數. 這樣, 編譯器能夠将下面的表達式:

A = 2.3 * B;

與下面的非成員函數調用比對:

A = operator*(2.3, B);
           

該函數的原型如下:

Time operator*(double m, const Time & t);
           

建立友元:

建立友元函數第一步是将其原型放在類聲明中, 并在原型聲明前加上關鍵字friend:

// 需要放在類聲明中
friend Time operator*(double m, const Time & t);
           

該原型意味着:

1.雖然operator*()函數時在類聲明中聲明的, 但它不是成員函數, 是以不能使用成員運算符(.)來調用;

2.雖然operator*()函數不是成員函數, 但它與成員函數的通路權限相同.

第二步是編寫函數定義, 因為不是成員函數, 是以不要使用Time::限定符, 并且不要在定義中使用關鍵字friend:

// 注意不要用Time::限定符, 并且不要使用frined
Time operator*(double mult, const Time & t)
{
    Time result;
    long totalminutes = t.hours * mult * 60 + t.minutes * mult;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}
           

有了上述聲明和定義後:

A = 2.3 * B;
           

将被轉換為下面語句, 進而調用剛才定義的非成員友元函數:

A = operator*(2.3, B);
           

重載<<運算符

還是上面的Time類, 使用友元函數, 重載<<運算符

void operator<<(ostream & os, const Time & t)
{
    // 由于直接通路了Time類的私有變量hours和minutes, 是以必須是Time的友元函數
    os << t.hours << " hours, " << t.minutes << " minutes";
}
           

這樣就可以使用下面的語句:

cout << time1;
           

但這種實作不允許像通常那樣将重新定義的<<運算符與cout一起使用

cout << "Trip time: " << trip << " (Tuesday) \n";
           

要了解原因, 先看一個關于cout操作的知識點:

int x = 3;
int y = 6;
cout << x << y;
           

C++從左至右讀取輸出語句, 意味着它等同于:

(cout << x) << y;
           

而ostream類将operator<<()函數實作為傳回一個指向ostream對象的引用.

是以可以對友元函數采用相同的方法, 隻要修改operator<<()函數, 讓它傳回ostream對象的引用即可:

ostream & operator<<(ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}
           

我們重新看

cout << "Trip time: " << trip << " (Tuesday) \n";
           

這個語句的工作過程:

首先,

cout << "Trip time: "
           

這個将調用的是ostream中的<<定義, 它顯示字元串并傳回cout對象

cout << trip << " (Tuesday) \n";
           

将調用Time聲明顯示trip值, 并再次傳回cout對象,

cout  << " (Tuesday) \n";
           

又再次調用ostream的<<定義, 來顯示最後一個字元串.

改進之後的版本:

第一個檔案

// 第一個檔案
// mytime3.h
#ifndef MYTIME3_H_
#define MYTIME3_H_

class Time
{
private:
    int hours;
    int minutes;
    
public:
    // 構造函數
    Time();
    // 提供了預設參數的構造函數
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    // 重載+運算符
    Time operator+(const Time & t) const;
    Time operator-(const Time & t) const;
    Time operator*(double n) const;
    
    // 友元函數
    // 直接寫了方法的定義, 則實際是個内聯函數
    // 方法體裡實際調用的是operator*(double n) 這個成員函數
    friend Time operator*(double m, const Time & t)
    {return t * m;}
    // 友元函數重載了<< 運算符
    friend std::ostream & operator<<(std::ostream & os, const Time & t);
    
};

#endif
           

第二個檔案:

// 第二個檔案
// mytime3.cpp, Time類的函數的實作檔案
#include <iostream>
#include "mytime3.h"

Time::Time()
{
    hours = minutes = 0;
}

Time::Time(int h, int m)
{
    hours = h;
    minutes = m;
}

void Time::AddMin(int m)
{
    minutes += m;
    hours += minutes / 60;
    minutes %= 60;
}

void Time::AddHr(int h)
{
    hours += h;
}

void Time::Reset(int h, int m)
{
    hours = h;
    minutes = m;
}

Time Time::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    // 注意這裡, 由于傳回類型是Time, 是以可以直接傳回sum
    // 如果傳回類型是Time & , 則不能傳回sum
    // 因為sum是臨時變量, 函數執行完畢後就會釋放, 是以不能傳回sum的引用
    return sum;
}

Time Time::operator-(const Time & t) const
{
    Time diff;
    int tot1, tot2;
    tot1 = t.minutes + t.hours * 60;
    tot2 = minutes + hours * 60;
    diff.minutes = (tot2 - tot1) % 60;
    diff.hours = (tot2 - tot1) / 60;
    return diff;
}

Time Time::operator*(double mult) const
{
    Time result;
    long totalminutes = hours * mult * 60 + minutes * mult;
    result.minutes = totalminutes % 60;
    result.hours = totalminutes / 60;
    return result;
}

// 這個不是成員函數不需要Time::
// 友元函數的實作, 不需要寫friend
std::ostream & operator<<(std::ostream & os, const Time & t)
{
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}
           

第三個檔案:

// 第三個檔案:
// usetime0.cpp
// compile with mytime3.cpp
#include <iostream>
#include "mytime3.h"

int main()
{
    using std::cout;
    using std::endl;
    
    // 調用兩個參數的構造函數
    Time coding(2, 40);
    Time fixing(5, 55);
    
    Time temp;
    
    cout << "coding and fixing : " << endl;
    // 使用的是友元非成員函數 重載<< 
    cout << coding << "; " << fixing << endl;
    
    // 使用的是 operator+()
    temp = coding + fixing;
    // 使用的友元非成員函數 重載<< 
    cout << "coding + fixing : " << temp << endl;
    
    // 使用的是 operator*()
    temp = coding * 1.17;
    cout << "coding * 1.17 : " << temp << endl;
    
    // 使用的是友元非成員函數 重載<< 
    // 還使用了友元非成員函數operator*
    cout << "10.0 * fixing : " << 10.0 * fixing << endl;
    
    return 0;
}
           

程式運作結果為:

C++Primer Plus書之--運算符重載和友元函數運算符重載:友元:

繼續閱讀