十四.友元

14 友元

作用:在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类 访问另一个类中的私有成员,友元的关键字为 ==friend==。

友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

提示:友元关系是单向的、不能传递的:A 是 B 的友元,B 不一定是 A 的友元;A 是 B 的友元、B 是 C 的友元,A 也不一定是 C 的友元。

14.1 全局函数做友元

作用:让一个全局函数能够访问另一个类中的私有成员。

语法:在类的内部用 friend 关键字声明该全局函数,写法为 friend void goodGay(Building* building);。friend 声明与访问权限无关,写在 public、private、protected 区域效果完全一样,通常写在类的开头。

示例:楼房类 Building 中有客厅 m_SittingRoom 和卧室 m_BedRoom 两个私有成员,全局函数 goodGay(好基友)想去参观,就必须在 Building 类的内部用 friend 声明它,否则没有访问权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>
using namespace std;

//建筑物类
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,可以访问到本类中的私有成员
friend void goodGay(Building* building);

public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

private:
string m_SittingRoom; //客厅
string m_BedRoom; //卧室
};

//全局函数
void goodGay(Building* building)
{
//下面两行之所以能访问私有成员,靠的是类内的 friend 声明;删掉 friend 后会报错
cout << "好基友全局函数正在访问:" << building->m_SittingRoom << endl;
cout << "好基友全局函数正在访问:" << building->m_BedRoom << endl;
}

void test01()
{
Building b;
goodGay(&b);
}

int main()
{
test01();

system("pause");

return 0;
}

注意:如果不写 friend void goodGay(Building* building); 这一行,goodGay 中访问 m_SittingRoom、m_BedRoom 的两行语句都会报错——类的私有成员在类外不能直接访问,加上 friend 声明后才获得访问权限。本例的运行结果为:好基友全局函数正在访问:客厅;好基友全局函数正在访问:卧室。

提示:类的公有成员本来就可以在类外直接访问,友元解决的是私有成员的访问问题;本例特意把两个成员都设为 ==private==,更能看出 friend 声明的作用。

14.2 类做友元

作用:让一个类中的所有成员函数都可以访问另一个类中的私有成员。

语法:在 Building 类的内部声明友元类,写法为 friend class GoodGay;。这条声明授权的是整个类,GoodGay 中的每个成员函数都能访问 Building 的私有成员。

示例:好基友类 GoodGay 中有一个 visit 成员函数去参观楼房 Building。Building 把 GoodGay 整个类声明为友元类,于是 visit 就能访问 Building 的私有成员 m_BedRoom。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <string>
using namespace std;

//建筑物类
class Building
{
//告诉编译器 GoodGay类 是 Building类的好朋友,GoodGay中的所有成员函数都可以访问到本类中的私有成员
friend class GoodGay;

public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

string m_SittingRoom; //客厅

private:
string m_BedRoom; //卧室
};

//好基友类
class GoodGay
{
public:
GoodGay()
{
//创建建筑物对象,用指针维护
building = new Building;
}

//参观函数,访问Building中的属性
void visit()
{
cout << "visit函数正在访问:" << building->m_SittingRoom << endl;
//GoodGay是Building的友元类,visit可以访问私有成员
cout << "visit函数正在访问:" << building->m_BedRoom << endl;
}

Building* building; //指向Building对象的指针
};

void test01()
{
GoodGay gg;
gg.visit();
}

int main()
{
test01();

system("pause");

return 0;
}

注意:GoodGay 中 new 出来的堆区对象在本例结束前没有释放,实际项目中应在析构函数中 delete,这里为聚焦友元语法从简。本例的运行结果为:visit函数正在访问:客厅;visit函数正在访问:卧室。

提示:友元关系是单向的:Building 允许 GoodGay 访问自己的私有成员,反过来 Building 依然不能访问 GoodGay 的私有成员;只想开放某一个成员函数时,用下一节的成员函数做友元。

14.3 成员函数做友元

作用:只让另一个类中的某个成员函数访问本类的私有成员,把开放范围控制到最小。

语法:在 Building 类的内部声明友元成员函数,写法为 friend void GoodGay::visit();,声明时必须带上该成员函数所属的类名和作用域符 ::。

示例:只让 GoodGay 类中的 visit 成为 Building 的友元,visit 可以访问 Building 的私有成员 m_BedRoom;visit2 没有被声明为友元,不可以访问。

注意:成员函数做友元时,声明顺序很讲究,必须按三步走:第一步,先用 class Building; 对 Building 做前向声明,因为 GoodGay 类中要存放 Building* 指针;第二步,声明 GoodGay 类,让编译器知道有 visit 这个成员函数;第三步,再声明 Building 类并在类内写 friend void GoodGay::visit();,两个类的成员函数都放在类外实现。顺序写错,编译器会报找不到 GoodGay::visit 的错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>
#include <string>
using namespace std;

//第一步:前向声明Building,告诉编译器有这个类,GoodGay中才能使用Building*指针
class Building;

//第二步:声明GoodGay类
class GoodGay
{
public:
GoodGay();

void visit(); //visit将被声明为Building的友元,可以访问Building中的私有成员
void visit2(); //visit2不是友元,不可以访问Building中的私有成员

Building* building; //指向Building对象的指针
};

//第三步:声明Building类,并在类内声明友元成员函数
class Building
{
//告诉编译器 GoodGay类中的visit成员函数 是本类的好朋友,可以访问到本类中的私有成员
friend void GoodGay::visit();

public:
Building();

string m_SittingRoom; //客厅

private:
string m_BedRoom; //卧室
};

//类外实现成员函数
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}

GoodGay::GoodGay()
{
//在堆区创建建筑物对象,用指针维护
building = new Building;
}

//visit是Building的友元,可以访问私有成员
void GoodGay::visit()
{
cout << "visit函数正在访问:" << building->m_SittingRoom << endl;
cout << "visit函数正在访问:" << building->m_BedRoom << endl;
}

//visit2不是友元,只能访问Building的公有成员
void GoodGay::visit2()
{
cout << "visit2函数正在访问:" << building->m_SittingRoom << endl;
//取消下面这行注释会报错:visit2不是友元,无权访问private成员m_BedRoom
//cout << "visit2函数正在访问:" << building->m_BedRoom << endl;
}

void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}

int main()
{
test01();

system("pause");

return 0;
}

注意:friend void GoodGay::visit(); 中的 :: 不能丢——它是在告诉编译器”是 GoodGay 类里的那个 visit”,这与全局函数做友元 friend void goodGay(Building* building); 的写法不同。本例的运行结果为:visit函数正在访问:客厅;visit函数正在访问:卧室;visit2函数正在访问:客厅。

总结:三种友元的本质都是在被访问的类内部用 friend 做一条声明:全局函数做友元直接写函数原型,类做友元写 friend class 类名; 全类开放,成员函数做友元写 friend 类名::函数名(参数); 只开放一个函数。友元使用方便,但也破坏了类的封装性,实际开发中应谨慎使用。