C++中的友元函數和友元類


今天在准備計算機等級考試的時候,被一系列的友元函數給搞混了,現在結合自己的理解和查閱的資料來總結下友元函數和友元類。

百度百科上對友元函數是這樣定義的:友元函數是指某些雖然不是類成員卻能夠訪問類的所有成員的函數。。類授予它的友元特別的訪問權。通常同一個開發者會出於技術和非技術的原因,控制類的友元和成員函數(否則當你想更新你的類時,還要征得其它部分的擁有者的同意)。

為什么會有友元函數?

結合着類的特性和類中一般成員函數,我們可以這樣理解:類具有封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其他函數是無法訪問私有成員的。非成員函數可以訪問類中的公有成員,但是如果將數據成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函數多次調用時,由於參數傳遞,類型檢查和安全性檢查等都需要時間開銷,而影響程序的運行效率。

為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函數,但它需要在類體內進行說明,為了與該類的成員函數加以區別,在說明時前面加以關鍵字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

希望通過以上內容的介紹,能夠給你帶來幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM