今天在准備計算機等級考試的時候,被一系列的友元函數給搞混了,現在結合自己的理解和查閱的資料來總結下友元函數和友元類。
百度百科上對友元函數是這樣定義的:友元函數是指某些雖然不是類成員卻能夠訪問類的所有成員的函數。。類授予它的友元特別的訪問權。通常同一個開發者會出於技術和非技術的原因,控制類的友元和成員函數(否則當你想更新你的類時,還要征得其它部分的擁有者的同意)。
為什么會有友元函數?
結合着類的特性和類中一般成員函數,我們可以這樣理解:類具有封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其他函數是無法訪問私有成員的。非成員函數可以訪問類中的公有成員,但是如果將數據成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函數多次調用時,由於參數傳遞,類型檢查和安全性檢查等都需要時間開銷,而影響程序的運行效率。
為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函數,但它需要在類體內進行說明,為了與該類的成員函數加以區別,在說明時前面加以關鍵字friend。友元不是成員函數,但是它可以訪問類中的私有成員。友元的作用在於提高程序的運行效率,但是,它破壞了類的封裝性和隱藏性,使得非成員函數可以訪問類的私有成員。
友元函數的特點是能夠訪問類中的私有成員的非成員函數。友元函數從語法上看,它與普通函數一樣,即在定義上和調用上與普通函數一樣。下面舉一例子說明友元函數的應用。
class Point { public: Point(double xx, double yy) { x=xx; y=yy; } void Getxy(); friend double Distance(Point &a, Point &b); private: double x, y; }; void Point::Getxy() { cout<<"("<<<","<<Y<<")"<<ENDL;< FONT> } double Distance(Point &a, Point &b) { double dx = a.x - b.x; double dy = a.y - b.y; return sqrt(dx*dx+dy*dy); } void main() { Point p1(3.0, 4.0), p2(6.0, 8.0); p1.Getxy(); p2.Getxy(); double d = Distance(p1, p2); cout<<"Distance is"<< FONT> }
說明:在該程序中的Point類中說明了一個友元函數Distance(),它在說明時前邊加friend關鍵字,標識它不是成員函數,而是友元函數。它的定義方法與普通函數定義一樣,而不同於成員函數的定義,因為它不需要指出所屬的類。但是,它可以引用類中的私有成員,函數體中 a.x,b.x,a.y,b.y都是類的私有成員,它們是通過對象引用的。在調用友元函數時,也是同普通函數的調用一樣,不要像成員函數那樣調用。
本例中,p1.Getxy()和p2.Getxy()這是成員函數的調用,要用對象來表示。而Distance(p1, p2)是友元函數的調用,它直接調用,不需要對象表示,它的參數是對象。
再舉幾個例子,我們來分析下全局函數作友元、其它類的成員函數作友元、運算符重載中使用友元的情況:
全局函數作友元
要使用全局函數作友元函數,也就是說全局函數中要使用當前這個類的實例,因此,全局函數的定義必須放在這個類的后面,否則,編譯時這個類的實例就成了未定義的數據類型了。
#include <iostream> #include <string> using namespace std; class ca { string id; void setId(string s) { id = s; } protected: string name; void setName(string s) { name = s; } public: void print() { cout << id << " " << name << " " << endl; } friend void fun(ca& a); //申明全局函數作友元,允許它訪問私有保護成員 }; class derive : public ca { }; //ca類的派生類 void fun(ca& a) { //作友元的函數要定義在那個類的后面 a.id = "987"; //這是ca類的私有成員 a.setName("xyz"); //這是ca類的保護成員 } int main ( ) { ca a; //a.fun(...); //友元函數不是當前類的成員函數,所以這句編譯出錯 fun(a); a.print(); derive d; fun(d); //作參數時,類型轉換為ca類 d.print(); return 0; }
運行結果 為
其它類的成員函數作友元
別的類的成員函數作友元,也就是說這2個類相互調用,這樣無論誰定義在前在后,編譯時都有沖突。要解決這個問題,只要將類的申明、定義、實裝分開就可以了。請注意例子中的解說。
#include <iostream> #include <string> using namespace std; class ca; //事先申明ca類,確保cb類的定義不出錯 class cb { //在ca類之前定義cb類,確保在ca類里申明cb的test()作友元時不出錯 public: void test(ca& a); //由於ca類事先申明,這兒不出錯 }; class ca { string id; void setId(string s) { id = s; } protected: string name; void setName(string s) { name = s; } public: void print() { cout << id << " " << name << " " << endl; } friend void cb::test(ca& a); //申明cb類的test()函數作友元,允許它訪問私有保護成員 }; void cb::test(ca& a) { //作友元的成員函數的實裝必須在ca類的后面,否則ca類的成員就未定義了。 a.id = "123"; //這是ca類的私有成員 a.setName("abc"); //這是ca類的保護成員 } int main ( ) { ca a; cb b; b.test(a); a.print(); return 0; }
運行結果:
運算符重載中使用友元
操作符重載由類的函數改為全局函數,也就是說,這個運算符不是這個類的,而是正常使用的操作符。注意重載函數的寫法與上一章略有不同。不過,改為友元方式其實並沒有必要,純粹是為了講解“友元”的用法。
#include <iostream> #include <string> using namespace std; class rect { int x1, y1, x2, y2; //矩形座標 public: rect() { x1 = 0, y1 = 0, x2 = 0, y2 = 0; } rect(int m1, int n1, int m2, int n2) { x1 = m1, y1 = n1, x2 = m2, y2 = n2; } void print() { cout << " x1=" << x1; cout << " y1=" << y1; cout << " x2=" << x2; cout << " y2=" << y2; cout << endl; } //rect operator++(); //這是類的運算符的重載 friend rect operator++(rect &ob); //這是全局運算符的重載 }; rect operator++(rect &ob) { ob.x1++, ob.y1++; ob.x2++, ob.y2++; return ob; } int main ( ) { rect r(12, 20, 50, 40); r.print(); rect obj; obj = r++; obj.print(); return 0; }
運行結果:
並不是所有操作符都可以定義成“友元”,例如“=”就不能用“友元”方式重載
友元類
友元除了前面講過的函數以外,友元還可以是類,即一個類可以作另一個類的友元。當一個類作為另一個類的友元時,這就意味着這個類的所有成員函數都是另一個類的友元函數。
使用友元類時注意:
(1) 友元關系不能被繼承。
(2) 友元關系是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的聲明。
(3) 友元關系不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明
總結起來:
(1)友元關系不可以繼承,但對已有的方法來說訪問權限不改變。
(2)如果改寫基類的方法則訪問權限改變
(3)友元關系不具有傳遞性
若類B是類A的友元,類C是B的友元,類C不一定是類A的友元。
#include <iostream> using namespace std; class CObj { public: CObj() : mX(0), mY(0) {} friend class CFriend; private: void PrintData() const { cout << "mX = " << mX << endl << "mY = " << mY << endl; } int mX; int mY; }; class CFriend { public: CFriend(int x, int y) { mObj.mX = x; //直接調用類CObj的私有數據成員 mObj.mY = y; } void ShowData() const { mObj.PrintData(); //直接調用類CObj的私有成員函數 } private: CObj mObj; }; int main() { CFriend one(3, 4); one.ShowData(); return 0; }
執行結果:
mX = 3
mY = 4
希望通過以上內容的介紹,能夠給你帶來幫助。