c++友元函数和友元类详解

来源:互联网 发布:数据库设计大作业 编辑:程序博客网 时间:2024/04/28 08:56

我们在设计类时,一般将数据成员设计成私有的,体现面向对象的信息隐藏和封装性;这些私有的数据成员只能由类成员函数所访问,类外函数不能访问;当在某些情况下,我们可能又需要访问类对象的私有成员,那我们应该怎么办呢?

为了能够访问类私有成员,我们可以采用友元函数,在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 成员

友元利弊分析

面向对象程序设计的一个基本原则就是封装性和信息隐蔽,而友元函数和友元类却可以访问其他类的私有成员,在一定程度上这是封装性的小破坏;
因此进行类设计时,不推荐将整个类设置为友元类,只将必要成员函数或者普通函数设置为友元类;
由于友元有助于数据共享,使代码更加简洁,但又违背封装性和信息隐蔽,因此进行代码开发时需要做好权衡;