1. 程式人生 > >過載運算子與友元函式

過載運算子與友元函式

本次部落格 主要學習運算子過載和友元函式。其中
運算子過載能夠將平時用於內建型別的變數的操作符(如+,-,*,/等)用於類物件;
友元這種C++機制使得非成員函式可以訪問私有資料。

一、過載運算子

    過載運算子,將普通運算子過載擴充套件到使用者自定義型別,編譯器根據具體環境來選用。(有點像過載函式—多型:函式名相同,但函式列表不同)這樣做使程式碼看起來更加自然,同時隱藏了內部機理,強調了OOP的實質。

    過載運算子,必須需要使用運算子函式的特殊函式格式,如下

    operatorop(argument-list)

    其中,op必須是有效的C++運算子。

過載運算子是怎樣工作的?

    理解過載運算子是怎樣工作的?假定Time類,併為它定義一個operator+()成員函式,以過載+運算子,以便能夠將兩個Time類物件相加。等式:

T = t1 + t2 ;

    編譯器發現,運算元都是Time類物件,因此呼叫相應的運算子函式替換上述運算子:

T= t1. operator+( t2);

    該函式隱式地使用了t1(因為它呼叫了方法),而顯式地使用t2物件(因為它被作為引數傳遞)。

    因此,在運算子表示法中,運算子左側的物件(這裡為t1)是呼叫物件,運算子右邊的物件(這裡為t2)是作為引數被傳遞的物件。

擴充套件

    當兩個以上的物件相加時,如何轉化為函式呼叫

T = t1 + t2 +t3;

    由於+是從左向右的運算子,因此,

T= t1. operator+( t2+t3);  //第一步
T= t1. operator+( t2. operator+(t3) );//第二步

二、友元函式的引入

    在前面過載運算子的學習中,我們已經知道了,運算子左側的運算元是呼叫物件,運算子右側的運算元是引數。比如:

A = B * 2.75;

    將被轉換為下面的成員函式呼叫:

A = B.operator*(2.75);

    可是下面的語句如何辦呢?

A = 2.75 * B;

    函式左側是操作物件,而2.75不能作為操作物件,因此編譯器不能使用成員函式呼叫來替換該表示式。

    解決方案一:

    告訴每個人只能用B*2.75這種格式寫。顯然這是一種對伺服器友好,對客戶需要當心的一種解決方案。

    解決方案二:

    對該過載運算子函式進行多型實現。

Time operator*(double m,const Time & t)
{
    Return t * m;  //use t.operator(m)
}

    解決方案三:

    引入友元函式的思想來解決。

     1)首先將該函式修改非成員函式。

    對於非成員過載運算子函式來說,運算子表示式左邊的運算元對應於運算子函式的第一個引數,運算子表示式右邊的運算元對應於運算子函式的第二個引數。

     2)引入友元函式

    因此,此時使用非成員函式按所需的順序獲得運算元,2.75作為函式第一個引數,B作為第二個引數。但引發一個新問題:普通非成員函式不能直接訪問類的私有成員,然而,有一類特殊的非成員函式可以訪問類的私有成員,它們被稱為友元函式。

    格式:

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

    該原型意味著:

  • 雖然operator*()函式是在類宣告中宣告的,但它不是成員函式,因此不能使用成員運算子來呼叫。
  • 雖然operator*()函式不是成員函式,但它與成員函式的訪問許可權相同。

    總之,類的友元函式是非成員函式,其訪問許可權與成員函式相同。

    注意,編寫函式定義時,不能使用Time::限定符,不能加關鍵字friend。

三、程式碼部分

    類的宣告:

    Time類提供了用於調整和重新設定時間、顯示時間、將兩個時間相加/減的方法; 建構函式; 友元函式的宣告。

//mytime.h
#ifndef MYTIME_H_
#define MYTIME_H_

#include <iostream>

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;
        friend Time operator*(double m,const Time & t)
            { return t * m ;} //行內函數定義 
        friend std::ostream & operator<<(std::ostream &os, const Time & t);
};

#endif

    類的定義

//mytime.cpp
#include "mytime.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;

    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.hours  =  (tot2 -tot1) /60; //小時 
    diff.minutes =  (tot2 -tot1) % 60;//分鐘 

    return diff;
}

Time Time::operator*(double mult) const
{
    Time result;
    long totalminutes = hours * mult * 60 +minutes *mult; //總分鐘 

    result.hours = totalminutes /60;
    result.minutes = totalminutes % 60;

    return result;
}

std::ostream & operator<<(std::ostream & os,const Time & t) 
{
    os << t.hours << " hours," << t.minutes <<" minutes";
    return os; //物件本身返回還是物件,方便級聯使用 
}

    類的使用

//usetime.cpp
#include "mytime.h"
#include <iostream>

int main()
{
    using std::cout;
    using std::endl;

    Time t1(3,35);
    Time t2(2,48);
    Time temp;

    cout << "t1 and t2:\n";
    cout << t1 <<"; " <<t2 <<endl;

    temp = t1 + t2;  //利用過載符 operator+()
    cout << "t1+t2:   "<<temp <<endl;

    temp = t1 - t2;  //利用過載符 operator-()
    cout << "t1-t2:   "<<temp <<endl; 

    temp = t1 * 1.17;  //利用過載符 operator*() 順序 
    cout << "t1*1.17:   "<<temp <<endl;

    temp = 1.17 * t1;  //利用過載符 operator*() 逆序 
    cout << "1.17*t1:   "<<temp <<endl; 

    //利用過載符 operator<<()級聯 
    cout << "10.0*t1:   " <<10.0*t1<<"10.0*t2:   " <<10.0*t2<<endl;
    return 0;
}

    截圖:

這裡寫圖片描述

    注意:

     1. 當總的分鐘數超過59時,函式是通過整數除法和求模運算調整minutes和hours的值的。
     2. 函式的引數是引用傳遞,目的是提高效率。傳值也能實現,但是傳遞引用速度更快,使用的記憶體更少。
     3. 函式的返回值不能是引用。返回物件將建立物件的副本,函式結束時雖然銷燬了局部變數,而呼叫函式使用的是所構造的副本。然而,如果返回型別為引用,由於區域性變數在函式結束時被刪除,因此引用將指向一個不存在的物件。
       因此,不要返回指向區域性變數/臨時物件的引用。函式執行完畢後,區域性變數和臨時物件將消失,引用將指向不存在的資料。