C++系列(友元)

我們提到過C++中存在一種朋友關係,這種朋友關係如果體現在函數上,那麼我們就稱之爲友元函數;如果體現在類上,我們就稱之爲友元類。

友元函數

對於我們定義函數的情形來說,一種情況是將函數定義爲全局函數,另一種情況是將函數定義在一個類當中,使其成爲類的一個成員函數。如果將全局函數聲明爲友元,則成爲友元全局函數;如果將一個類的成員函數聲明爲另外一個類的友元函數,那麼稱該成員函數爲友元成員函數。

友元全局函數

我們先來看一個例子

我們定義了一個座標類(Coordinate),那麼,如果我們想要定義一個友元,怎麼辦呢?我們就要使用關鍵字:friend。要定義一個友元函數,就只需要將關鍵字friend加在函數聲明的前面,最後加上分號即可,同時一定要傳入當前這個類的一個對象或者是一個引用或者是指針,總之,能夠通過這個函數能夠訪問到這個對象的私有的成員或者是受保護的成員(如上面的:friend void printXY(Coordinate &c);)。下面我們來看一看,對於Coordinate這個類來說,他的私有的成員有哪些??一個是m_iX代表的是橫座標,一個是m_iY代表的是縱座標。下面我們來看一看我們提到的使用方法。

在第一個方框中,我們寫出的第一段程序叫做printXY函數,其是打印橫縱座標。在打印橫縱座標的時候嗎,我們需要傳入一個Coordinate的對象或者是引用(這兒傳入的是引用),需要給大家指出的是傳入引用或者指針,其傳遞效率更高,執行速度更快,所以在這提倡傳入引用或者指針,而不提倡直接傳入對象的方式。printXY函數在訪問的時候,我們只是使用cout來打印一下橫座標和縱座標。請大家注意我們使用的訪問方法式使用這個對象去直接訪問它的私有成員。如果我們沒有將printXY聲明爲Coordinate的友元,那麼如果我們這樣寫的話,編譯器一定會報錯。但是當前情況下,我們已經將printXY這個函數聲明爲Coordinate這個類的友元了,所以我們通過這樣的直接訪問形式是可以順利編譯通過的。當我們在mian函數當中去調用printXY函數的時候,我們需要先實例化一個Coordinate的對象,然後將這個對象傳遞進去,請大家注意,因爲我們需要的參數是一個引用,所以我們傳遞的時候直接傳入對象名就可以了,而不需要在對象名前面再加一個取地址符號(&)了。關於全局友元函數的定義和使用方法就先說這麼多,後續通過代碼實踐進一步加深映像。


#ifndef COORDINATE_H
#define COORDINATE_H

class Coordinate
{
public:
    Coordinate(int x ,int y);
    Coordinate& operator-();//減號運算符重載
    Coordinate& operator+();//加號運算符重載
    Coordinate operator +(const Coordinate &coor);//二元運算符重載
    void  showxy();
    friend void printxy(Coordinate &c);//友元全局函數

private:
    int m_ix;
    int m_iy;
};

#endif // COORDINATE_H

#include <iostream>
#include "coordinate.h"
#include "time.h"
#include "match.h"

using namespace std;

void printxy(Coordinate &c)//友元全局函數
{
 cout<<"friend m_ix=="<<c.m_ix<<" m_iy=="<<c.m_iy<<endl;

}

int main()
{

    Coordinate coorl(3,5);
    printxy(coorl);//友元全局函數
	
    return 0;
}


友元成員函數

我們還是通過一個例子來說明問題

在這我們還是以Coordinate這個類爲例。定義的時候仍然使用關鍵字friend。請大家注意後面的寫法,我們使用的函數仍然叫做printXY,但是此時的printXY函數並不是一個全局函數,而是一個成員函數,其是Circle這個類中的成員函數。所以,我們要將Circle中的成員函數printXY聲明爲Coordinate這個類的友元,那麼,我們就需要將Circle這個類寫出來,然後加上(::)再接printXY,這樣就可以將一個類的成員函數聲明爲另外一個類的友元了。

在main函數當中,我們先實例化了一個Coordinate類的對象coor,然後又實例化了一個Circle類的對象circle。在Circle類中的printXY的實現方法與前面所講到的全局函數的實現方法一樣。通過這樣的調用,我們可以發現,如果我們將Circle的printXY聲明爲Coordinate的友元,那麼我們在printXY實現的時候就可以直接訪問c這個對象下面的m_iX和m_iY,而m_iX和m_iY都是Coordinate下的私有成員,所以通過這樣的行爲就能夠體現出友元給我們帶來的方便。當然,友元給我們帶來方便的同時,也給我們帶來了一定的風險。當我們將Circle中的printXY這個函數聲明爲Coordinate這個類的友元函數之後,也就破壞了Coordinate這個類的封裝性,此時對於數據的直接訪問雖然是方便了,但是如果我們不小心改變了這個數據的值也不易擦覺,所以,風險和方便往往是一對相互矛盾。我們除非有特殊的需要,否則一般情況下不建議大家過度使用友元。

友元類

友元類的定義與友元函數的定義非常相似,也是使用關鍵字friend,後面跟一個類的類名即可。需要大家特別注意的是,如果我們要聲明一個友元類的時候,需要在當前這個類的前面先聲明這個類,如下所示:

上面我們聲明瞭Circle類爲Coordinate類的友元類,而且在Coordinate類前面也聲明瞭一下Circle類。

當我們將Circle這個類聲明爲Coordinate類的友元類之後,我們就可以在Circle這個類當中去定義一個Coordinate的對象了,並且可以通過這個對象任意訪問Coordinate這個類當中的私有的數據成員和成員函數(在上面的例子中,我們只定義了數據成員,而沒有定義成員函數)。

我們來看一下實際定義Circle類的時候是如何做的?

我們看到,實際定義Circle的時候,我們就是在訪問限定符private的下面定義了一個Coordinate的對象m_coor,在任何Circle當中的成員函數中,都可以通過這個對象來訪問Coordinate中私有的數據成員或者成員函數。

關於友元的注意事項

  • 友元關係不可以傳遞(比如:B是A的朋友,C是B的朋友,但C未必就是A的朋友)
  • 友元關係的單向性(比如:A是B的朋友,B不一定就是A的朋友,所以在聲明有緣的時候,一定要搞清楚到底A是B的友元,還是B是A的友元)
  • 友元聲明的形式及數量不受限制(可以既有友元函數也有友元類,而且聲明數量也不受限制)

注意:

友元只是封裝的一種補充,其並不是一個很好的語法。也就是說,是不得已而爲之所用的語法,如果在前期設計巧妙的話,實際上是可以避開友元的。這就是說,友元的使用破壞了封裝性,使得類的封裝性看上去更差,從而也體現了一種定向暴露的思想(我把誰當做朋友,也就相當於我把數據定向的暴露給誰了)。

#ifndef MATCH_H
#define MATCH_H
#include "time.h"
#include <iostream>
using namespace std;

class Time;//提前聲明class Time


class Match//Match爲Time的朋友需要引用Time,如上提前聲明class Time
{
public:
    Match();
    void printT(Time &t);
    void STime(Time &t);


};

#endif // MATCH_H

#ifndef TIME_H
#define TIME_H
#include "match.h"


class Time
{
public:
    Time();
     //friend void printT(Time &t);//友元成員函數(不能訪問私有成員????)
    friend class Match;//Match爲Time的友元類,故Time對於Match是沒有權限的
    void showTime();

private:

    int x;
    int y;
};

#endif // TIME_H


#include <iostream>
#include "coordinate.h"
#include "time.h"
#include "match.h"

using namespace std;

void printxy(Coordinate &c)//友元全局函數
{
 cout<<"friend m_ix=="<<c.m_ix<<" m_iy=="<<c.m_iy<<endl;

}

int main()
{
    //友元成員函數&友元類
    Time t;
    Match m;
    m.printT(t);//Match是Time的友元類,printT裏直接使用Time的private成員
    m.STime(t);
    t.showTime();
    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章