啃书《C++ Primer Plus》 面向对象部分 友元 ——友元函数、友元类、友元成员函数

友元,顾名思义,是某个类的“朋友”。这个朋友角色的特殊之处在于它可以访问类中所有的成员,包括私有、保护和公有的成员函数和成员变量。这似乎违背了类的封装特性,但这就是友元作为单独的机制出现的原因。有些类需要与其他类进行深♂度 互动,有些函数需要遍历所有类中的成员,这些都是数据封装的例外情况,但也是程序设计中真实会发生的情况。针对这类情况,友元的作用就得以体现了。

在C++中类的友元共有三大类型:

  • 友元类
  • 友元函数
  • 友元成员函数
    在这里插入图片描述

友元的声明

友元的使用情况具有特殊性,所以所有的友元关系都是单方面的,并且采用授权制。即仅允许一个类设置其他类或函数成为友元,不允许外界将它设置成为友元。
打个比方,封装好的数据有相当一部分都是私有的(private),属于私人的东西仅可以由他自己单方面授权给其他人看,而不允许其他人自作主张的将别人作为友元并且翻看它的私人物品。

由于友元仅表示类与函数或类与其他类的特殊关系,因此友元的声明不存在访问控制。也就是说友元的声明可以出现在类中的任意地方而不必考虑是否在public还是在private下。

将某元素声明成为类的友元,只需要使用关键字friend进行修饰即可:


友元函数

一个类可以将一个普通的函数当做友元,允许这个函数访问该类的内部成员。声明方法为:friend 返回类型 函数名(参数列表);

如下面程序:

#include<iostream>
using namespace std;
class A{
    friend void print(const A&); //为类A设置友元函数print,允许其访问A类的所有成员 
public:
    A(int k = 99):k(k){}
private:
    int k;
};
void print(const A& a)
{
    cout << a.k;	//打印A类对象的私有成员k
}
int main()
{
    A a;
    print(a);
}

结果如下:
在这里插入图片描述
友元函数的一种常见使用,就是在类外重载<<符号以配合cout使用,达到输出对象内容和连续使用的目的。

#include<iostream>
using namespace std;
class A{
    friend ostream& operator<<(ostream&,const A&);		//声明重载运算符<<函数为友元函数
public:
    A(int k,int a):k(k),a(a){}
private:
    int k;
    int a;
};
ostream& operator<<(ostream &os,const A &a)			//在重载运算符<<函数中打印A类对象的内容并返回ostream对象引用以连续打印
{
    os << "the value of k is " << a.k << endl;
    os << "the value of a is " << a.a << endl;
    os << "-----------------" << endl;
    return os;
}
int main()
{
    A a1(10,20);
    A a2(30,50);
    A a3(99,100);
    cout << a1 << a2 << a3;
}

运行结果如下:
在这里插入图片描述
可以看到,在这个例子中,我们将operator<<函数设置成为A类的友元函数,使得A类对象能够像内置类型那样参与cout的输出。

另外,在类中直接重载这个函数并不能实现这个要求。因为在类中重载的<<将会在对象作为该双目运算符的前一个参数时调用,也就是这样:a << cout;这么一来就很奇怪了,不能满足我们的要求。


友元类

友元类是将另一个类看作友元,在友元类中可以任意访问原类的成员。声明方法为:friend class 类名
如下面的程序:

#include<iostream>
using namespace std;
class B;	//前置声明
class A{
    friend class B;	//声明B类为友元类
public:
    A(int k):k(k){}
private:
    void show()const{cout << k << endl;}
    int k;
};
class B{
public:
    void setK(A& a,const int k){	//访问A类的私有数据成员
        a.k = k;
    }
    void showK(const A& a){		//访问A类的私有成员函数
        a.show();
    }
};
int main()
{
    A a(10086);
    B b;
    b.showK(a);
    b.setK(a,10010);
    b.showK(a);
}

结果:
在这里插入图片描述
友元类的使用并不只看上去那么简单,主要问题在于类的声明,后面会讨论到。


友元成员函数

一个类可以将另一个类的某个成员函数声明为友元成员函数,声明方式如下:friend 返回类型 所属类::成员函数名称(参数列表);

可以看到,相较于一般函数,成员函数在声明时需要使用作用域解析符指明该成员函数所属的类。

另外,考虑到成员函数可以看作是作用域为某个类的函数,声明一个友元成员函数和友元函数并没有什么区别。

我们将刚刚的程序稍作改变:

#include<iostream>
using namespace std;
class A;	//A类的前置声明

class B{
public:
    void setK(A&,const int);	
    void showK(const A&);		
};
class A{
    friend void B::setK(A&,const int);	//将B类的setK函数声明为友元成员函数
    friend void B::showK(const A&);		//将B列的showK函数声明为友元成员函数
public:
    A(int k):k(k){}
private:
    void show()const{cout << k << endl;}
    int k;
};

inline void B::showK(const A & a){	
    a.show();
}
inline void B::setK(A& a,const int k){
    a.k = k;
}

int main()
{
    A a(10086);
    B b;
    b.showK(a);
    b.setK(a,10010);
    b.showK(a);
}

运行结果同刚才相同:
在这里插入图片描述


类的声明和定义问题

使用友元的主要问题在于相关类的声明和定义究竟应该放在哪里。

例如,在类A中将类B设置成为友元,则在A类声明之前,编译器必须知道B类的存在。而B类使用A类对象的内容有需要的到完整地A类声明。这就需要在A类之前使用B类的前置声明:

class B;

class A{
	friend class B;
	..
	..
};

class B{
'''
...
};

那么,不同行为对类声明的需求如下:

  • 声明友元类,只需要知道该类的存在——前置定义
  • 使用类对象作为参数声明函数,只需要知道该类存在——前置定义
  • 使用类对象作为参数给出函数定义,需要知道类的完整内容——引用头文件或将类声明写在调用类前面

基于这三个要求,我们来回顾一下前面举出的三个程序示例。

友元函数

在友元函数中。

  • 友元函数声明形式需要用到类对象作为参数,这需要在编译函数之前对类做前置声明
  • 友元函数的实现需要完整的类,这需要在编译函数之前得到类的内容。

所以,下面的两种写法都是可以的:

class A{
	friend void print(const A&);
	...
};
void print(const A& a){
...
}
class A;				//前置声明类A
void print(const A&);	//前置声明函数f
class A{
	friend void print(const A&);
	...
}
void print(const A& a){
...
}

但是这种写法就是不可以的:

void print(const A& a){		//不合法,在编译该函数时不知道A类的存在
	...
}
class A{
	friend void print(const A&);
	...
};

友元类&友元成员函数

友元类的主要问题还是成员函数,而在友元成员函数情况中:

  • 声明友元的类需要知道友元类的存在
  • 友元类声明成员函数需要知道对方类的存在
  • 友元列给出成员函数定义需要知道对方类的全部内容

因此,下面的写法是可行的:

class B;		//前置声明B类
class A{
	friend class B;
	...
};
class B{
public:
	void print(const A& a){	//声明并给出成员函数的内联实现
	...
	}
};
class A;		//前置声明A类
class B{
public:
	void print(const A&);//仅给出成员函数声明
	...
};
class A{
	friend class B;
	...
};

void B::print(const A& a){//在得知A类具体信息后给出成员函数实现(也可以使用inline使其成为内联的)
...
}

但是下面的写法就是不可以的:

class A;		//前置声明A类
class B{
public:
	void print(const A&){	//在声明类时直接给出函数的内联实现,这是不合法的,程序编译到这里并不知道A类对象里面有什么
	...
	}
	...
};
class A{
	friend class B;
	...
};
当两个类互为友元时

当两个类互为友元时:

  • 双方声明对方为友元都需要预先知道对方的存在。
  • 双方声明使用对方的成员函数时都需要知道对方的存在
  • 双方实现使用对方的成员函数是都需要知道对方的具体内容

因此在两个类互为友元的情况下,最好将所有成员函数的声明和实现都分开来,同时两个类声明时都加上对方的前置声明。

下面两种写法都是允许的:

/*AB类的前置声明*/
class A;
class B;
/*AB类的声明*/
class A{
	friend class B;
public:
	void printB(const B&);
	...
};
class B{
	friend class A;
public:
	void printA(const A&);
	...
};
/*双方成员函数的实现*/
void A::printB(const B & b){
...
}
void B::printA(const A & a){
...
}
/*A.h*/
...
class B;
class A{
	friend class B;
public:
	void printB(const B&);
	...
};
...

/*A.cpp*/
#include"B.h"
void A::printB(const B & b){
	...
}

/*B.h*/
...
class A;
class B{
	friend class A;
public:
	void printA(const A&);
	...
};
...
/*B.cpp*/
#include"A.h"
void B::printA(const A& a){
	...
}

致谢:面向对象课程陈老师,十分认真负责,许多内容是他教授给我的。


看完文章,来关注博主一起学习鸭~~~~

啃书系列往期博客

语言基础部分:

面向对象部分:

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值