我們在設計類時,一般将資料成員設計成私有的,展現面向對象的資訊隐藏和封裝性;這些私有的資料成員隻能由類成員函數所通路,類外函數不能通路;當在某些情況下,我們可能又需要通路類對象的私有成員,那我們應該怎麼辦呢?
為了能夠通路類私有成員,我們可以采用友元函數,在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 成員
友元利弊分析
面向對象程式設計的一個基本原則就是封裝性和資訊隐蔽,而友元函數和友元類卻可以通路其他類的私有成員,在一定程度上這是封裝性的小破壞;
是以進行類設計時,不推薦将整個類設定為友元類,隻将必要成員函數或者普通函數設定為友元類;
由于友元有助于資料共享,使代碼更加簡潔,但又違背封裝性和資訊隐蔽,是以進行代碼開發時需要做好權衡;