眾所周知,c++控制對類物件私有部分的訪問。通常,公有類方法提供唯一的訪問途徑,但是有時候這種限制太嚴格,以至於不適合特定的程式設計問題。在這種情況下,c++提供了另外一種形式的訪問許可權:友元,友元有3種:
友元函式、友元類、友元成員函式。
通過讓函式成為類的友元(即:友元函式),可以賦予該函式與類的成員函式相同的訪問許可權。
在介紹如何成為友元前,先介紹為何需要友元。在為類過載二元運算子時(帶有兩個引數的運算子)常常需要友元。將time物件乘以實數就屬於這種情況,下面來看看。
在一般的例項中,過載乘法運算子可能需要兩種不同的型別,這裡暫定為一種是time物件,另一種是double型別。這就限制了該運算子的使用方式。記住,左側的運算元是呼叫物件,也就是說,下面的語句:
a=b*2.75;
將會被轉換為下邊的成員函式呼叫:
a=b.operator*(2.5);
但是下面的語句又將如何呢?
a=2.75*b;
從概念上來說b* 2.75;,2.75*b;相同,但是第二個表示式不對應於成員函式,因為2.75不是time型別的物件。記住,左側的運算元應該是呼叫物件,但是2.75不是物件。因此,編譯器不能使用成員函式呼叫來替換該表示式。
然而非成員函式可以解決這個問題,非成員函式不是由物件呼叫的,它使用的所有值包括物件都是顯示引數,這樣,編譯器就可以把下面的表示式:
a=2.75*b;
與下面的非成員函式的呼叫,匹配:
a= operator*(2.75,b);
該函式的原型如下:
time operator*(double m,const time& t);
使用非成員函式可以按照所需的順序獲取運算元,但是引發了乙個新的問題,非成員函式不能直接訪問類的私有變數,至少常規非成員函式不能訪問。但是有一類特殊的成員函式可以訪問私有資料,他們被稱為友元函式。
建立友元函式的第一步是將其原型放在類宣告中,並在其原型宣告前加上關鍵字friend:
friend time operator*(double m,const time& t);
該原型意味著下面兩點:
雖然operator*()函式是在類宣告中宣告的,但是他不是成員函式,因此不能使用成員運算子來呼叫;
雖然operator*()函式不是成員函式,但是它與成員函式的訪問許可權相同。
第二步是編寫函式定義。因為它不是成員函式,所以不要使用time:: 限定符。另外,不要再定義中使用關鍵字friend,定義應該如下:
time operator*(double m,const time & t)
常用的友元:過載<< 運算子
類並非只能擁有友元函式,也可以將類作為友元。在這種情況下,友元類的所有方法都可以訪問原始類的私有成員和保護成員。另外,也可以做更嚴格的限制,只將特定的成員函式指定為另乙個類的友元。哪些函式、成員函式或類成為友元是有類定義的,而不能從外部強加友情。因此,儘管友元被授予從外部訪問類的私有部分的許可權,但是但它們並不與物件導向的思想相悖;相反,它們提高了共有介面的靈活性。
什麼時候希望乙個類成為另乙個類的友元呢?我們來看乙個例子,假設我們需要編寫乙個模擬電視機和遙控器的簡單程式。決定定義乙個tv類和乙個romote類,來分別表示電視機和遙控器。很明顯,這兩個類之間應該存在某種關係,但是是什麼關係呢?遙控器並非電視機,反之亦然,因此包含或著私有繼承和保護繼承的has-a關係也不適合用。事實上,遙控器可以改變電視機的狀態,這表明應將romote類作為tv類的乙個友元。
首選定義tv類。可以用一組狀態成員(描述電視各個方面的變數)來表示電視機。下面是一些可能的狀態:
開關;
頻道設定;
音量設定;
有線電視或天線調節模式;
tv調諧或a/v輸入。
下面的語句將romote宣告為友元類:
friend class remote;
友元宣告可以位於公有、私有或保護部分。其所在的位置無關緊要。由於remote類提到了tv類,所以編譯器必須了解了tv類後,才能處理remote類,為此,最簡單的方法是首先定義tv類。也可以使用前向宣告(forward delaration)這將稍後介紹。
程式清單:tv.h
在程式清單中,大多數類方法都被定義為內聯的。除建構函式外,所有的romote方法都將乙個tv物件引用作為引數,這表明遙控器要針對特定的電視機。下列的程式清單將列出其餘的定義。音量設定函式將音量成員增減乙個單位,除非聲音達到最大或者最小。頻道選擇函式使用迴圈方式,最低的頻道設定為1,它位於最高的頻道設定maxchannel之後。
程式清單:tv.cpp
下屬清單是乙個簡短的程式,可以測試一些特性。另外,可使用同乙個遙控器控制兩台不同的電視機。
程式清單:use_tv.cpp
從上乙個例子的**可知,大多數remote方法都是用tv類的公有介面實現的。這意味著這些方法不是真正需要作為友元。事實上,直接訪問tv成員的remote方法是remote:: set_chan(),因此它是唯一需要作為友元的方法。確實可以選擇僅讓特定的類成員成為另乙個類的友元,但是這樣做稍微有點麻煩,必須小心的排列各種宣告和定義的順序。下面介紹這其中的原因。
讓remote:: set_chan()成為tv類的友元的方法是,在tv類的宣告中將其宣告為友元:
class tv
; 然而,要讓編譯器能夠處理該條語句,它必須知道remote的定義。否則,他無法知道remote是乙個類,而set_chan是這個類的方法。這意味著應將remote的定義放到tv的定義的前面。然而,remote的方法提到了tv的定義,這意味這tv的定義應將位於remote的定義之前。從而造成了死迴圈。
要避開這種死迴圈的方法是,使用前向宣告。為此,需要在remote的定義前面插入下面的語句:
class tv; //前向宣告
這樣,排列的順序應該如下:
class tv; //前向宣告
class remote{};
class tv{};
能否像下面這樣排列呢?
class tv; //前向宣告
class tv{};
class remote{};
答案是不能
原因在於,編譯器在tv類的宣告中看到了remote的乙個方法是被宣告為tv類的友元之前,應該先看到remote 類的宣告和set_chan()方法的宣告。
還有乙個麻煩,上述程式清單的remote宣告包含了內聯**,例如
void onoff(tv & t)
由於這將呼叫tv的乙個方法,所以這時編譯器必須看到了tv類的宣告,這樣才能知道tv有哪些方法,但正如看到的,該宣告位於remote宣告的後面。這種問題的解決辦法是,使remote的宣告中只包含方法宣告,並將實際的定義放在tv類之後。這樣,排列的順序將如下:
class tv; //前向宣告
class remote; // tv- using methods as prototypes only
class tv;
// put remote method definitions here
remote 方法的原型與下面的類似:
void onoff(tv & t);
檢查該原型時,所有的編譯器都需要知道tv是乙個類,而前向宣告提供了這樣的資訊。當編譯器到達真正的方法定義時,他已經讀取了tv類的宣告,並擁有了編譯這些方法所需要的資訊。通過在方法定義中使用inline關鍵字,仍然可以使其成為內聯方法。、
在前面介紹過,內聯函式的鏈結性是內部的,這意味著函式定義必須在使用函式的檔案中。在這個例子中,內聯函式的定義應該位於標頭檔案中,因此在使用函式的檔案中包含標頭檔案可確保將定義放在正確的位置。也可以將定義放在實現檔案中,但是必須刪除關鍵字inline,那麼這樣函式的鏈結性將是外部的。
順便說一句,讓整個remote類成為友元並不需要前向宣告,因為友元語句本身已經指出remote是乙個類:
friend class remote;
友元函式 友元類 友元成員函式
注意友元成員函式定義的3個步驟 include include include using namespace std class a class c class a class b void c display const a a 3.最後定義display,此時才可以使用a的私有成員 void ...
友元函式 友元類 友元成員函式
一般來說,類內的私有資料是對外不可見的,但在有些情況下,我們需要在類外對該類的私有資料進行訪問,這就需要用到一種新技術 友元 friend 即在宣告前新增關鍵字friend。友元關係是單向的,即如果a是b的友元,但b不一定是a的友元 友元關係無傳遞性,即如果a是b的友元,b是c的友元,但a不一定是c...
友元函式 友元類 友元成員函式
有些情況下,允許非成員函式訪問類中的私有成員,但又阻止一般的訪問,這種情況,就需要用到友元。使用friend宣告友元,友元的定義只能在類的內部 有些情況下,全域性函式需要訪問類中的私有成員,這種函式叫友元函式 如 客人可以訪問客廳,但一般客人不可以去主人臥室,關係較好的朋友才能訪問私有的臥室,這時就...