背景
私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这固然能够带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦。友元提供了在类的成员函数外部直接访问对象的私有成员(实际上所有成员都可访问,包括 protected)的功能,朋友是值得信任的,所以可以对他们公开一些自己的隐私。友元分为两种:友元函数和友元类。
友元函数
在定义一个类的时候,可以把一些函数(包括全局函数和其他类的成员函数)声明为“友元”,这样那些函数就成为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员了。
- 友元函数写在想要访问私有成员的类的内部;
- 友元函数不受在想访问私有成员的类中位置影响,不被 private, public 和 protected 约束;
- 友元函数本质上是全局函数或者另一个类的成员函数;
- 不能将其他类的私有成员函数声明为某个类的友元函数;
- 将一个类 A 的成员函数作为另一个类 B 的友元函数时不能在 B 中直接定义友元函数,因为友元函数并不属于此类,全局函数也不能在其中声明,只能在类外声明,即友元函数在想访问私有成员的类中声明函数原型,在另一个类中声明不带 friend 的函数原型,在类外定义。
- 友元函数必须在参数表中显式指明要访问私有成员的类对象。
定义
全局函数声明为友元函数
friend 返回值类型 函数名(参数表);
类的成员函数声明为友元函数
friend 返回值类型 其他类的类名::成员函数名(参数表);
实现
类的成员函数声明为友元函数
三部曲:
- 增加前向声明,并定义一个需要实现友元函数的类,在类中声明友元函数的不带 friend 的函数原型;
- 声明友元函数原型;
- 类外补充定义(被访问私有成员的类定义之后)。
#include<iostream>
using namespace std;
/******* 第一步:前向声明 ********/
class B; // 1 处使用类 B,但是 B 尚未定义,所以提前声明
class A{
public:
// 错误,此时 B 还不完整,没有定义
// void show(B b){
// cout<<b.name<<endl;
// };
void show(B b); // 1
};
/******** 第二步:声明友元函数原型 ********/
class B{
public:
B() {name = "aaron";x = 10;}
friend void A::show(B b); // 友元函数原型
private:
string name;
protected:
int x;
};
/******* 第三步:类外定义 *******/
void A::show(B b){
cout<<b.name<<" "<<b.x<<endl;
}
int main(){
B b;
A a;
a.show(b);
}
全局函数声明为友元函数
两部曲:
- 类内声明友元函数原型
- 类外定义
#include<iostream>
using namespace std;
/********** 第一步:类内声明友元函数原型 *********/
class B{
public:
B() {name = "aaron";}
friend void show(B b); // 友元函数原型
private:
string name;
};
/******* 第二步:类外定义 *******/
void show(B b){
cout<<b.name<<endl;
}
int main(){
B b;
show(b);
}
友元类
一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有成员。在类定义中声明友元类的写法如下:
定义
在想访问私有成员的类的任意位置声明
friend class 类名;
使用
友元类中先声明一个想访问私有成员的类的对象,然后将其作为数据成员即可访问。
#include<iostream>
using namespace std;
class B;
class A{
public:
int x;
A() {x = 1;c = 'a';s = "good";}
protected:
char c;
private:
string s;
friend class B;
};
class B{
public:
void showX(){
cout<<a.x<<endl;
}
void showC(){
cout<<a.c<<endl;
}
void showS(){
cout<<a.s<<endl;
}
private:
A a;
};
int main(){
B b;
b.showX();
b.showC();
b.showS();
}
区别与联系
- 友元函数必须传入一个想访问私有成员的类对象作为参数,因此友元类和另一个类都需要创建对象;
- 友元类将被访问类对象作为数据成员,不需要额外创建被访问类对象,也不用传递参数。
- 类的私有函数不能作为友元函数,友元类的私有成员函数不能访问被访问类的私有成员。