1、首先,我们为什么要使用友元呢?
答:类具有封装和信息隐藏的特性,只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
为了解决上面的问题,提出了友元函数。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。
优点:提高程序的运行效率;
缺点:破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
2、那么,什么是友元函数呢?
答:友元函数就是可以直接访问类中私有成员的函数,虽然它不是成员函数。
3、使用友元需要注意什么呢?
答:1)如果类A是类B的友元,则类A(的成员函数)可以直接访问类B的私有 成员。
2)友元不能继承。也就是说,类A是类B的友元,类D是类B的派生类,则类A并不会直接是类D的友元。通俗一点,父亲的朋友,并不天生就是儿子的朋友。即:父类的友元,并不会因为继承,而成为派生类的友元。
友元类:
友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。
示例代码如下:
#include<iostream>
using namespace std;
class Internet;
class Country
{
public:
Country()
{
strcpy(cname,"中国");
}
friend class Internet;//友类的声明
protected:
char cname[30];
};
class Internet
{
public:
Internet(char *name,char *address)
{
strcpy(Internet::name,name);
strcpy(Internet::address,address);
}
void Editcname(Country &temp);
protected:
char name[20];
char address[20];
};
void Internet::Editcname(Country &temp)
{
strcpy(temp.cname,"中华人民共和国");
}
void main()
{
Internet a("中国软件开发实验室","www.cndev-lab.com");
Country b;
a.Editcname(b);
cin.get();
}
在上面的代码中我们成功的通过Internet类Editcname成员函数操作了Country类的保护成员cname。
当友元遇到了虚函数:
来看下面的几段代码:
Code:
class A;
class B
{
private:
virtual void output()
{
cout << "B::output" << endl;
}
friend class A;
};
class D : public B
{
private:
virtual void output()
{
cout << "D::output" << endl;
}
};
A 是 B 的友元类, 而D是B的派生类。 所以,若想在A中直接访问D的代码,则编译不过:
Code:
class A
{
public:
void test()
{
D d;
d.output(); //编译出错
}
};
这一点大家都没觉得有问题,毕竟书上写得都明白直观:父类的友元,并不会因为继承,而成为派生类的友元。
但若代码改成这样,编译器似乎就被欺骗了:
Code:
class A
{
public:
void test()
{
D d;
B* pb = &d;
pb->output(); //编译通过
}
};
没错,很多人会认为这种代码,就算能通过编译器,也很可能是一种不好的代码,因为它怎么看都像是在欺骗编译器。是这样吗?先不讨论。先问一个问题: 上面的08行代码,output调用的是B类的那个output,还是D类的那个呢?
回答正确并不难——既然会认定这段代码带有“欺骗”性质,而且又注意到output是一个“虚函数”的话——就能能正确地解答: 调用的是D类的。A明明只是B的友元,但却通过一个简单的类型转换,就访问了D类的那个私有函数,所以会觉得这是一种“欺骗”。
如果这是一种欺骗,那我们先来回答这个骗局为什么能成立:因为“友元”的判断(resolve),在编译期决定;而虚函数在运行期去resolve。在编译08行代码时,编译器看到*pb的类型是B,而A是B的友元,所以允许它调用output(它认为是B::output);而在运行时,由于output是虚函数,所以最终被决定到D::output头上。