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