我们在设计类时,一般将数据成员设计成私有的,体现面向对象的信息隐藏和封装性;这些私有的数据成员只能由类成员函数所访问,类外函数不能访问;当在某些情况下,我们可能又需要访问类对象的私有成员,那我们应该怎么办呢?
为了能够访问类私有成员,我们可以采用友元函数,在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 << "\n"
<< 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 << "\n"
<< 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 成员
面向对象程序设计的一个基本原则就是封装性和信息隐蔽,而友元函数和友元类却可以访问其他类的私有成员,在一定程度上这是封装性的小破坏;
因此进行类设计时,不推荐将整个类设置为友元类,只将必要成员函数或者普通函数设置为友元类;
由于友元有助于数据共享,使代码更加简洁,但又违背封装性和信息隐蔽,因此进行代码开发时需要做好权衡;