C++ 中的友元概念和使用方法

C++ 借用類的概念實現了封裝的概念,使得對外提供接口,對內開放數據的想法得到了實現。但是如果在類外定義的函數需要訪問類內的數據成員,並且類的數據成員一般會被設定爲 private,因此就會被拒絕訪問。此時就需要使用友元的概念。

友元

藉由友元(友元函數和友元類),可以提高程序的運行效率(減少類型的安全性檢查和調用的時間開銷),但是某種程度上會破壞掉類的封裝性。也就是說友元能夠使原來不屬於類的成員(友元函數和友元類),成爲類的成員從而具備類成員的屬性。

友元函數

  • 友元函數能夠直接訪問類的私有數據成員。
  • 是在類外定義的普通函數
  • 不屬於任何類
  • 需要在類定義中聲明,聲明時在友元前加關鍵字 friend
  • 一個函數可以是多個類的友元函數,只是需要分別聲明

定義

friend datatype funcname(argument);

 全局函數作友元函數

#include <iostream>
#include <math.h>

using namespace std;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }
    friend double distance(POINT &p1,POINT &p2);

private:
    int x;
    int y;
};

double distance(POINT &p1,POINT &p2)
{
    return sqrt(pow((p1.x-p2.x),2)+pow((p1.y-p2.y),2));
}

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);

    p1.display();
    p2.display();

    cout<<"The distance between p1 and p2 is "<<distance(p1,p2)<<endl;

    return 0;
}

結果爲:

The x is 10,The y is 20
The x is 30,The y is 40
The distance between p1 and p2 is 28.2843

在上邊的程序中,我們定義了函數 distance,該函數使用類 POINT 成員作爲參數,因此需要將該函數聲明爲該類的友元。從上邊的結果可以看出:

  • 友元函數最好在類內聲明,類外定義。因爲全局函數定義在類內部會顯得有點奇怪。
  • 友元函數聲明與定義分開書寫時,定義前不需要加 friend 關鍵字

類成員函數作友元函數

#include <iostream>
#include <math.h>

using std::cout;
using std::endl;

class POINT;

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}

    POINT3(const POINT3 &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
        this->z = obj.z;
    }

    POINT3 & operator=(const POINT3 &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
        this->z = obj.z;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
    }

    double distance(POINT3 &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2)+pow((this->z-p.z),2));
    }

    double distance(POINT &p);
private:
    int x;
    int y;
    int z;
};

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    double distance(POINT &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
    }

    friend double POINT3::distance(POINT &p);
private:
    int x;
    int y;
};

double POINT3::distance(POINT &p)
{
    return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
}

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);
    POINT3 p3(50,60,70);

    p1.display();
    p2.display();
    p3.display();

    cout<<"The distance between p1 and p2 is "<<p1.distance(p2)<<endl;
    cout<<"The distance between p1 and p3 is "<<p3.distance(p1)<<endl;

    return 0;
}

結果爲:

The x is 10,The y is 20
The x is 30,The y is 40
The x is 50,The y is 60,The z is 70
The distance between p1 and p2 is 28.2843
The distance between p1 and p3 is 56.5685

上邊的程序中,我們定義了一個二維點 POINT 的類和一個三維點 POINT3 的類,並且在 POINT3 重載了函數 distance,希望在參數爲 POINT 對象或者 POINT3 對象的時候都能夠給出點之間的距離。因此:

  • 需要在 POINT 中聲明 POINT3 中的該函數爲友元函數
  • 類定義 POINT3 中使用了 POINT,因此需要在定義 POINT3 之前先聲明 POINT
  • 在 POINT3 中的函數 distance 使用了 POINT 對象的數據成員 x 和 y,因此只能先在類中聲明該函數,函數實現放在類 POINT 定義之後
  • 友元函數聲明與定義分開書寫時,定義前不需要加 friend 關鍵字

上邊先聲明類 POINT 的方式叫做前向聲明,形式爲:

class classname;

前向聲明是一種不完全的聲明,只需要聲明類名即可,因此:

  • 不能定義類的對象
  • 可以用於定義指向這個類型的指針或者引用
  • 用於聲明,使用該類作爲形參類型或者函數的返回值類型

 友元類

友元類說明該類中的所有成員函數都是另一個類的友元函數,都能夠訪問另一個類中的數據成員。

友元類可以算是友元函數的一個擴展,大大地增加了數據的單項流動,只是這種方式對於程序的封裝形式也有極大的破壞。

定義

friend class classname;
  • 定義的友元類不屬於函數成員,也不屬於數據成員,而只是一種關係聲明
  • 聲明爲友元類的類一定要在程序中定義過,定義先後順序則無所謂
#include <iostream>
#include <math.h>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    double distance(POINT &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
    }

private:
    int x;
    int y;
    friend class POINT3;
};

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):z(z)
    {
        xy.x = x;
        xy.y = y;
    }

    POINT3(const POINT3 &obj)
    {
        this->xy.x = obj.xy.x ;
        this->xy.y = obj.xy.y ;
        this->z = obj.z;
    }

    POINT3 & operator=(const POINT3 &obj)
    {
        if (this == &obj)
            return *this;
        this->xy.x = obj.xy.x;
        this->xy.y = obj.xy.y;
        this->z = obj.z;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->xy.x<<","<<"The y is "<<this->xy.y<<","<<"The z is "<<this->z<<endl;
    }

    double distance(POINT3 &p)
    {
        return sqrt(pow((this->xy.x-p.xy.x),2)+pow((this->xy.y-p.xy.y),2)+pow((this->z-p.z),2));
    }

private:
    POINT xy;
    int z;
};

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);
    POINT3 p3(50,60,70);
    POINT3 p4(80,90,100);

    p1.display();
    p2.display();
    p3.display();
    p4.display();

    cout<<"The distance between p1 and p2 is "<<p1.distance(p2)<<endl;
    cout<<"The distance between p3 and p4 is "<<p3.distance(p4)<<endl;

    return 0;
}

結果爲:

The x is 10,The y is 20
The x is 30,The y is 40
The x is 50,The y is 60,The z is 70
The x is 80,The y is 90,The z is 100
The distance between p1 and p2 is 28.2843
The distance between p3 and p4 is 51.9615

上邊的程序中,我們在 POINT 中聲明 POINT3 爲友元類,因此在 POINT3 中就能夠使用 POINT,進而訪問到 POINT 中的數據成員。

這種做法其實就是爲了避開 POINT 數據成員是 private 的限制,如果 POINT 中的數據成員都是 public 的話,當然就不需要這麼麻煩了,直接使用就可以了。

這種形式有點類似“繼承”的意思,但含義卻要比“繼承”弱的多,POINT3 只是可以將 POINT 拿過來用,而不是能夠完全繼承 POINT 的屬性,至少對於 POINT3 的對象來說,就不能直接調用 POINT 的函數,而只能在 POINT3 的成員函數中調用 POINT 的函數,因此只能是“朋友”,而不是“父子”。

注意事項

聲明

  • 友元聲明的關鍵字爲 friend
  • 友元聲明只能出現在類定義中
  • 聲明的友元不屬於函數成員,也不屬於數據成員,而只是一種關係聲明,因此不受 public、private、protected 關鍵字的影響

友元使用

  • 友元是爲不屬於類的函數或者類開放了一種數據訪問的通道,使之能夠繞過類中的權限設置,得到訪問權限
  • 友元能夠提到程序的運行效率,但是會破壞類的封裝和數據隱藏

友元關係

  • 友元關係不能夠被繼承
  • 友元關係是單向的,不具有交換性。如果需要雙向友元,就需要雙向聲明
  • 友元關係也不具有傳遞性。同樣,只要聲明過的纔是“朋友”,否則就是“陌生人”
  • 這些關係特性也是爲了在打破封裝的情況下,儘可能的保存封裝吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章