一个类中可以有 public、protected、private 三种属性的成员,通过对象可以访问 public 成员,只有本类中的函数可以访问本类的 private 成员。现在,我们来补充介绍一个例外——友元(friend)。
fnend 的意思是朋友,或者说是好友,与好友的关系显然要比一般人亲密一些。有的家庭可能会这样处理:客厅对所有来客开放,而卧室除了本家庭的成员可以进人以外,还允许好朋友进入。在C++中,这种关系以关键宇 friend 声明,中文多译为友元。友元可以访问与其有好友关系的类中的私有成员,友元包括友元函数和友元类。如果您对友元这个名词不习惯,可以按原文 friend 理解为朋友即可。
友元函数
在当前类以外定义的、不属于当前类的函数也可以在类中声明,但要在前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。
友元函数可以访问当前类中的所有成员,包括 private 属性的。
1) 将普通函数声明为友元函数。
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
|
#include<iostream>
using namespace std;
class Student{
private :
char *name;
int age;
float score;
public :
Student( char *, int , float );
friend void display(Student &); //将display声明为友元函数
};
Student::Student( char *name, int age, float score){
this ->name = name;
this ->age= age;
this ->score = score;
}
//普通成员函数
void display(Student &stu){
cout<<stu.name<< "的年龄是 " <<stu.age<< ",成绩是 " <<stu.score<<endl;
}
int main(){
Student stu( "小明" , 16, 95.5f);
display(stu);
return 0;
}
|
运行结果:
1
|
小明的年龄是 16,成绩是 95.5
|
请注意 display 是一个在类外定义的且没有使用 Student 作限定的函数,它是非成员函数,不属于任何类,它的作用是输出学生的信息。如果在 Student 类中未声明 display 函数为 friend 函数,它是不能引用 Student 中的私有成员 name、age、score 的。大家可以亲测一下,将上面程序中的第11行删去,观察编译时的信息。
现在由于声明了 display 是 Student 类的 friend 函数,所以 display 可以使用 Student 中的私有成员 name、age、score。但注意在使用这些成员变量时必须加上对象名,不能写成:
1
|
cout<<name<< "的年龄是 " <<age<< ",成绩是 " <<score<<endl;
|
因为 display 不是 Student 类的成员函数,默认不能使用 Student 类的成员,必须指定要访问的对象。
2) 将其他类的成员函数声明为友元函数
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
45
|
#include<iostream>
using namespace std;
class Address; //对Address类的提前引用声明
//声明Student类
class Student{
private :
char *name;
int age;
float score;
public :
Student( char *, int , float );
void display(Address &);
};
//声明Address类
class Address{
private :
char *province;
char *city;
char *district;
public :
Address( char *, char *, char *);
//将Student类中的成员函数display声明为友元函数
friend void Student::display(Address &);
};
Address::Address( char *province, char *city, char *district){
this ->province = province;
this ->city = city;
this ->district = district;
}
//声明Student类成构造函数和成员函数
Student::Student( char *name, int age, float score){
this ->name = name;
this ->age= age;
this ->score = score;
}
void Student::display(Address &add){
cout<<name<< "的年龄是 " <<age<< ",成绩是 " <<score<<endl;
cout<< "家庭住址:" <<add.province<< "省" <<add.city<< "市" <<add.district<< "区" <<endl;
}
int main(){
Student stu( "小明" , 16, 95.5f);
Address add( "陕西" , "西安" , "雁塔" );
stu.display(add);
return 0;
}
|
运行结果:
1
2
|
小明的年龄是 16,成绩是 95.5
家庭住址:陕西省西安市雁塔区
|
在本例中定义了两个类 Student 和 Address。程序第 26 行将 Student 类中的成员函数 display 声明为友元函数,由此,display 就可以访问 Address 类的私有成员变量了。
两点注意:
① 程序第4行对Address类进行了提前声明,是因为在Address类定义之前、在Student类中使用到了它,如果不提前声明,编译会报错,提示"Address" has not been declared。类的提前声明和函数的提前声明是一个道理。
② 程序中将 Student 类的声明和定义分开了,而将 Address 放在了中间,是因为 Student::display() 函数体中用到了 Address 类的成员,必须出现在 Address 类的类体之后(类体说明了有哪些成员)。
这里简单介绍一下类的提前声明。一般情况下,类必须在正式声明之后才能使用;但是某些情况下(如上例所示),只要做好提前声明,也可以先使用。
但是应当注意,类的提前声明的使用范围是有限的。只有在正式声明一个类以后才能用它去创建对象。如果在上面程序第4行后面增加一行:
1
|
Address obj; //企图定义一个对象
|
会在编译时出错。因为创建对象时是要为对象分配内存空间的,在正式声明类之前,编译系统无法确定应该为对象分配多大的空间。编译器只有在“见到”类体后(其实是见到成员变量),才能确定应该为对象预留多大的空间。在对一个类作了提前引用声明后,可以用该类的名字去定义指向该类型对象的指针变量或对象的引用变量(如在本例中,定义了Address类对象的引用变量)。这是因为指针变量和引用变量本身的大小是固定的,与它所指向的类对象的大小无关。
请注意程序是在定义 Student::display() 函数之前正式声明 Address 类的。这是因为在 Student::display() 函数体中要用到 Address 类的成员变量 province、city、district,如果不正式声明 Address 类,编译器就无法识别这些成员变量。
③ 一个函数可以被多个类声明为“朋友”,这样就可以引用多个类中的私有成员。
友元类
不仅可以将一个函数声明为一个类的“朋友”,而且可以将整个类(例如B类)声明为另一个类(例如A类)的“朋友”。这时B类就是A类的友元类。
友元类B中的所有函数都是A类的友元函数,可以访问A类中的所有成员。在A类的类体中用以下语句声明B类为其友元类:
1
|
friend B;
|
声明友元类的一般形式为:
1
|
friend 类名;
|
关于友元,有两点需要说明:
友元的关系是单向的而不是双向的。如果声明了 B类是A类的友元类,不等于A类是B类的友元类,A类中的成员函数不能访问B类中的私有数据。
友元的关系不能传递,如果B类是A类的友元类,C类是B类的友元类,不等于 C类是A类的友元类。
在实际开发中,除非确有必要,一般并不把整个类声明为友元类,而只将确实有需要的成员函数声明为友元函数,这样更安全一些。