C++ 友元(友元函數 && 友元類)

私有成員只能在類的成員函數內部訪問,如果想在別處訪問對象的私有成員,只能通過類提供的接口(成員函數)間接地進行。這固然能夠帶來數據隱藏的好處,利於將來程序的擴充,但也會增加程序書寫的麻煩。

C++ 是從結構化的C語言發展而來的,需要照顧結構化設計程序員的習慣,所以在對私有成員可訪問範圍的問題上不可限制太死。

C++ 設計者認爲, 如果有的程序員真的非常怕麻煩,就是想在類的成員函數外部直接訪問對象的私有成員,那還是做一點妥協以滿足他們的願望爲好,這也算是眼前利益和長遠利益的折中。因此,C++ 就有了友元(friend)的概念。打個比方,這相當於是說:朋友是值得信任的,所以可以對他們公開一些自己的隱私。

友元分爲兩種:友元函數和友元類。

 

友元函數:

在定義一個類的時候,可以把一些函數(包括全局函數和其他類的成員函數)聲明爲“友元”,這樣那些函數就成爲該類的友元函數,在友元函數內部就可以訪問該類對象的私有成員了。

將全局函數聲明爲友元的寫法如下:

friend  返回值類型  函數名(參數表);

將其他類的成員函數聲明爲友元的寫法如下:

friend  返回值類型  其他類的類名::成員函數名(參數表);

但是,不能把其他類的私有成員函數聲明爲友元。

關於友元,看下面的程序示例。

 

#include<iostream>
using namespace std;
class CCar;  //提前聲明CCar類,以便後面的CDriver類使用
class CDriver
{
public:
    void ModifyCar(CCar* pCar);  //改裝汽車
};
class CCar
{
private:
    int price;
    friend int MostExpensiveCar(CCar cars[], int total);  //聲明友元
    friend void CDriver::ModifyCar(CCar* pCar);  //聲明友元
};
void CDriver::ModifyCar(CCar* pCar)
{
    pCar->price += 1000;  //汽車改裝後價值增加
}
int MostExpensiveCar(CCar cars[], int total)  //求最貴氣車的價格
{
    int tmpMax = -1;
    for (int i = 0; i<total; ++i)
        if (cars[i].price > tmpMax)
            tmpMax = cars[i].price;
    return tmpMax;
}
int main()
{
    return 0;
}

這個程序只是爲了展示友元的用法,所以 main 函數什麼也不做。

第 3 行聲明瞭 CCar 類,CCar 類的定義在後面。之所以要提前聲明,是因爲 CDriver 類的定義中用到了 CCar 類型(第7行),而此時 CCar 類還沒有定義,編譯會報錯。

不要第 3 行,而把 CCar 類的定義寫在 CDriver 類的前面,是解決不了這個問題的,因爲 CCar 類中也用到了 CDriver 類型(第14行),把 CCar 類的定義寫在前面會導致第 14 行的 CDriver 因沒有定義而報錯。C++ 爲此提供的解決辦法是:可以簡單地將一個類的名字提前聲明,寫法如下:

class  類名;

儘管可以提前聲明,但是在一個類的定義出現之前,仍然不能有任何會導致該類對象被生成的語句。但使用該類的指針或引用是沒有問題的。

第 13 行將全局函數 MostExpensiveCar 聲明爲 CCar 類的友元,因此在第 24 行可以訪問 cars[i] 的私有成員 price。同理,第 14 行將 CDriver 類的 ModifyCar 成員函數聲明爲友元,因此在第 18 行可以訪問 pCar 指針所指向的對象的私有成員變量 price。

 

友元類:

一個類 A 可以將另一個類 B 聲明爲自己的友元,類 B 的所有成員函數就都可以訪問類 A 對象的私有成員。在類定義中聲明友元類的寫法如下:

friend  class  類名;

來看如下例程:

class CCar
{
private:
    int price;
    friend class CDriver;  //聲明 CDriver 爲友元類
};
class CDriver
{
public:
    CCar myCar;
    void ModifyCar()  //改裝汽車
    {
        myCar.price += 1000;  //因CDriver是CCar的友元類,故此處可以訪問其私有成員
    }
};
int main()
{
    return 0;
}

第 5 行將 CDriver 聲明爲 CCar 的友元類。這條語句本來就是在聲明 CDriver 是一個類,所以 CCar 類定義前面就不用聲明 CDriver 類了。第 5 行使得 CDriver 類的所有成員函數都能訪問 CCar 對象的私有成員。如果沒有第 5 行,第 13 行對 myCar 私有成員 price 的訪問就會導致編譯錯誤。

一般來說,類 A 將類 B 聲明爲友元類,則類 B 最好從邏輯上和類 A 有比較接近的關係。例如上面的例子,CDriver 代表司機,CCar 代表車,司機擁有車,所以 CDriver 類和 CCar 類從邏輯上來講關係比較密切,把 CDriver 類聲明爲 CCar 類的友元比較合理。

 

總結:

必須要注意的一點是,友元關係不存在傳遞性。也就是說,如果B是A的友元,C是B的友元,則C並不能理所當然的具有訪問A的私有成員的特權。

此外,友元函數能定義在類的內部,這樣的函數時隱式內聯的。

類和非成員函數的聲明不是必須在它們的友元聲明之前。當一個名字第一次出現在一個友元聲明中時,我們隱式的假定該名字在當前作用域中是可見的。然而,友元本身不一定真的聲明在當前作用域中。

甚至就算在類的內部定義該函數,我們也必須在類的外部提供相應的聲明從而使得函數可見。換句話說,即使我們僅僅是用聲明友元的類的成員調用該友元函數,它也必須是被聲明過的:

struct X{
    friend void f() { /* 友元函數可以定義在類的內部 */}
    X() { f(); }                    // 錯誤:f還沒有被聲明
    void g();
    void h();
};
void X::g() { return f(); }         // 錯誤:f還沒有被聲明
void f();                           // 聲明那個定義在X中的函數
void X::h() { return f(); }         // 正確:現在f的聲明在作用域中了

調用友元函數之前一定要先聲明友元函數。

關於這段代碼最重要的是理解友元聲明的作用是影響訪問權限,它本身並非普通意義上的聲明。

本文部分內容轉載於:http://c.biancheng.net/view/169.html

 

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