C++重难点:友元

  C++由于其封装的特性,通常使用类对数据进行了隐藏和封装,类的数据成员一般都定义为私有成员。虽然在有些时候这对类中的数据是一种很好的保护措施,但是有时候也需要定义一些函数或类来访问类中的非公有成员,比如运算符重载或两个类不是继承关系,但需要共享数据的时候,这时通常我们令其他类或函数成为它的友元来解决这类问题。

友元概念

  友元机制允许一个类将对其非公有成员的访问权授予指定的函数或者类,友元的声明以friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员(友元不属于该类),所以它们不受其声明出现部分的访问控制影响(可以是公有,也可以是私有)。通常,友元声明成组地放在类定义的开始或结尾。

友元分类

  C++中一共有3种友元,即友元函数,友元类和友元成员函数。

友元函数

概念

  友元函数是指虽然不是类成员函数却能够访问类的所有成员的函数。本身是一个类外的普通函数,但函数的声明只能在类的内部,定义在类的外部。
  格式: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
#include <iostream>
using namespace std;

class A
{
public:
friend void fun(A &a); //友元函数声明
private:
int data;
};

void fun(A &a) //友元函数定义
{
a.data = 0;
cout << a.data << endl;
}

int main()
{
class A a;
fun(a);

return 0;
}

  程序结果:

1
0

注意

  1. 友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
  2. 一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
  3. 友元函数的调用与一般函数的调用方式和原理一致。
  4. 友元函数能定义在类的内部,这样的函数是隐式内联的。

友元类

概念

  如果希望一个类可以访问另一个类的非公有成员在内的所有成员,可以将一个类指定为另一类的友元类。友元类的所有成员函数都是另一个类的友元函数。
  格式:friend class 类名;

实现

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
#include <iostream>
using namespace std;

class A
{
public:
friend class B; //友元类声明
private:
int data;
};

class B
{
public:
void fun(A &a)
{
a.data = 0;
cout << a.data << endl;
}
};

int main(void)
{
class A a;
class B b;
b.fun(a);

return 0;
}

  程序结果:

1
0

注意

  1. 友元关系不能被继承,就像父亲的朋友未必是儿子的朋友。
  2. 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
  3. 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。

友元成员函数

概念

  有时候我们仅需要将特定的类成员函数成为另一个类的友元,而不是整个类。我们可以这样做:使类B中的特定成员函数成为类A的友元函数,这样类B的该成员函数就可以访问类A的所有成员了。
  但是这样做会比较麻烦,必须要排列好各种声明和定义的顺序。
  比如在上例的友元类中,将类B中的函数fun定义为A的友元成员函数:friend void B::fun(A &a) {a.data = 0; cout << a.data << endl;}。但是编译器在处理这条语句时,必须要知道类B的定义,否则无法判断B是一个类,所以需要将B的定义放在A的前面。但B中的fun函数方法又提到了A对象,所以A的定义应该在B的前面,这样就陷入了循环中,为此需要利用前向声明来避免这种问题。所以最终的排序顺序如下:

1
2
3
class A;     //前向声明
class B {...};
class A {...};

  但此时又会出现另外一个问题,即B中的fun函数是内联函数,用到了A中的数据,但A的定义在后面,所以通常将fun的声明放在前面,fun的定义使用外部定义的方法放在后面。

实现

  最终的实现如下:

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
#include <iostream>
using namespace std;

class A; //前向声明

class B
{
public:
void fun(A &a) ;
};

class A
{
public:
friend void B::fun(A &a); //友元成员函数声明
private:
int data;
};

void B::fun(A &a) //友元函数的定义
{
a.data = 0;
cout << a.data << endl;
}

int main(void)
{
class A a;
class B b;
b.fun(a);

return 0;
}

  程序结果:

1
0

小结

优点:

  1. 可以灵活地实现需要访问若干类的私有或受保护的成员才能完成的任务;
  2. 便于与其他不支持类概念的语言(如C语言、汇编等)进行混合编程;
  3. 通过使用友元函数重载可以更自然地使用C++语言的IO流库。

缺点:

  1. 一个类将对其非公有成员的访问权限授予其他函数或者类,会破坏该类的封装性,降低该类的可靠性和可维护性。
谢谢老板!