天天看點

c++友元函數和友元類詳解

我們在設計類時,一般将資料成員設計成私有的,展現面向對象的資訊隐藏和封裝性;這些私有的資料成員隻能由類成員函數所通路,類外函數不能通路;當在某些情況下,我們可能又需要通路類對象的私有成員,那我們應該怎麼辦呢?

為了能夠通路類私有成員,我們可以采用友元函數,在c++中以關鍵字friend加以聲明,友元可以包括友元函數和友元類;友元函數又可以分為友元函數和友元成員函數;其一般形式分别如下:

友元函數:

friend 函數傳回類型 函數名(形參清單)

形如:

friend void Display(const CMyTime& time)

友元成員函數:

friend 函數傳回類型 類型名::函數名(形參清單)

形如:

friend void CMyDate::Display(const CMyTime& time)

友元類:

friend 類型名

形如

friend CMyDate

友元函數

友元函數就是将類外的函數,并在本類中加以friend關鍵字聲明,那麼這個函數就是本類的友元函數;

下面就将普通函數聲明為友元函數;

class CMyTime
{
public:
    CMyTime(int hour,int minute, int second);
    //全局函數Display是本類的友元函數,可以通路其私有資料成員
    friend void Display(const CMyTime& time);
private:
    int m_Hour;
    int m_Minute;
    int m_Second;
};

CMyTime::CMyTime(int hour,int minute, int second)
{
    this->m_Hour   = hour;
    this->m_Minute = minute;
    this->m_Second = second;
}           

在這個例子中,Display函數是一個全局的普通函數,不屬于任何類,沒有this指針;我們将其設定為友元函數後就能通路CMyTime類的私有成員了,否則Display函數将會報錯;

需要注意的是,為了能夠引出類的成員函數,這個友元函數的形參一般是類對象的引用或者指針;

友元函數如下:

void Display(const CMyTime& time)
{
   //因為Display函數沒有this指針,引用這些私有成員資料,需要指定對象
   cout << time.m_Hour   << ":"
        << time.m_Minute << ":"
        << time.m_Second << "
"
        << endl;
}           

測試代碼如下:

int _tmain(int argc, _TCHAR* argv[])
{
    CMyTime time(12,13,14);
    Display(time);
    return 0;
}           

運作結果:

12:13:14

友元成員函數

通常情況下,類和類之間是互相隔離的,但有可能一個類中的成員函數需要通路另一個類中的私有成員,我們則可将類成員函數設定為友元函數,則可以到達這個目的;

比如 有一個日期類(CMyDate)對象和一個時間類(CMyTime)對象,要求一次性輸出其中的日期和時間;

現在先設計一個CMyDate類,用成員函數Display完成輸出日期和時間,具體實作如下:

//提前引用聲明,表示存在CMyTime類名,類的具體内容後續再聲明
//在沒有具體聲明CMyTime類的内容時,不允許定義對象和引用函數名
//比如在引用聲明class CMyTime之後,添加對象定義CMyTime time 編譯器将報錯;
class CMyTime;  
class CMyTime* pTime = NULL;
//class CMyTime time; error 

class CMyDate
{
public:
    //構造函數
    CMyDate(int year,int month, int day);    
    //成員函數,輸出具體時間和日期
    void Display(const CMyTime& time) const; 
private:
    int m_Year;
    int m_Month;
    int m_Day;
};

CMyDate::CMyDate(int year,int month, int day)
{
    this->m_Year  = year;
    this->m_Month = month;
    this->m_Day   = day;
}

class CMyTime
{
public:
    CMyTime(int hour,int minute, int second);
    //将CMyDate類中Display函數是CMyTime的友元成員函數
    friend void CMyDate::Display(const CMyTime& time) const;
private:
    int m_Hour;
    int m_Minute;
    int m_Second;
};

//CMyDate::Display函數實作需要放在類CMyTime聲明之後,否則不清楚CMyTime類有哪些成員
void CMyDate::Display(const CMyTime& time) const
{   
   cout << m_Year  << "/"
        << m_Month << "/"
        << m_Day   << " " ;

   cout << time.m_Hour   << ":"
        << time.m_Minute << ":"
        << time.m_Second << "
"
        << endl;
}           

測試代碼如下:

int _tmain(int argc, _TCHAR* argv[])
{
    CMyTime time(12,12,12);
    CMyDate date(2017,7,16);
    //time對象作為實參,輸出時間,Display是CMyDate類成員函數,有this指針,作為日期的輸出源;
    date.Display(time);
    return 0;
}           

運作結果:

2017/7/17 12:12:12

這裡需要特别說明c++允許對類作“提前引用”的聲明,即隻先聲明類名,不包含類體;

若在目前函數中需要先引用(不是指引用符&)某個類對象作為形參,但這個類還未聲明;我們可以在檔案開頭先進行類名聲明,不聲明類體,類體聲明稍後再給出,如上述代碼中的

class CMyTime

在對一個類作了“提前引用聲明”後,可以用該類的名字去定義指向該類型對象的指針或者引用,因為定義一個指針變量和引用與這個類大小沒有關系,但是不能用于定義對象,定義對象需要聲明類體後才行,如上述代碼中的

class CMyTime* pTime = NULL和Display的形參;

友元類

在c++中我們還可以将一個類設定為另一個類的友元類,這樣友元類中的函數都可以通路另一個類的所有成員資料,并且友元類是單向的且不具備傳遞性,比如類A是類B的友元類,類B是類C的友元類,不能推出類A是類C的友元類,以及類B是類A的友元類;

友元類使用如下:

class CMyDate
{
public:
    //構造函數
    CMyDate(int year,int month, int day);    
    //成員函數
    void Display(const CMyTime& time) const; 
private:
    int m_Year;
    int m_Month;
    int m_Day;
};

class CMyTime
{
public:
    CMyTime(int hour,int minute, int second);
    //友元類, CMyDate是CMyTime的友元類
    friend  CMyDate;
private:
    int m_Hour;
    int m_Minute;
    int m_Second;
};           

測試代碼如下:

int _tmain(int argc, _TCHAR* argv[])
{
    CMyTime time(12,12,12);
    CMyDate date(2017,7,16);
    //time對象作為實參,輸出時間,Display是CMyDate類成員函數,有this指針,作為日期的輸出源;
    date.Display(time);
    return 0;
}           

運作結果:

2017/7/17 12:12:12

如果沒有了friend關鍵字聲明,在Display函數中将會報如下錯誤:

error C2248: “CMyTime::m_Hour”: 無法通路 private 成員

error C2248: “CMyTime::m_Minute”: 無法通路 private 成員

error C2248: “CMyTime::m_Second”: 無法通路 private 成員

友元利弊分析

面向對象程式設計的一個基本原則就是封裝性和資訊隐蔽,而友元函數和友元類卻可以通路其他類的私有成員,在一定程度上這是封裝性的小破壞;

是以進行類設計時,不推薦将整個類設定為友元類,隻将必要成員函數或者普通函數設定為友元類;

由于友元有助于資料共享,使代碼更加簡潔,但又違背封裝性和資訊隐蔽,是以進行代碼開發時需要做好權衡;