Chinaunix首页 | 论坛 | 博客
  • 博客访问: 51913
  • 博文数量: 78
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-12 10:57
个人简介

A fool sees not the same tree that a wise man sees。。。

文章分类

全部博文(78)

文章存档

2016年(78)

我的朋友

分类: C/C++

2016-06-13 19:21:07

原文地址:友元详解 作者:zhenhuaqin

1.问题的提出

我们已知道类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

2.友元的分类:

2.1 友元函数

友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样。下面举一例子说明友元函数的应用。

  class Point

  {

  public:

  Point(double xx, double yy) { x=xx; y=yy; }

  void Getxy();

  friend double Distance(Point &a, Point &b);

  private:

  double x, y;

  };

  void Point::Getxy()

  {

  cout<<"("<

  }

  double Distance(Point &a, Point &b)

  {

  double dx = a.x - b.x;

  double dy = a.y - b.y;

  return sqrt(dx*dx+dy*dy);

  }

  void main()

  {

  Point p1(3.0, 4.0), p2(6.0, 8.0);

  p1.Getxy();

  p2.Getxy();

  double d = Distance(p1, p2);

  cout<<"Distance is"<

  }

 说明:在该程序中的Point类中说明了一个友元函数Distance(),它在说明时前边加friend关键字,标识它不是成员函数,而是友元函数。它的定义方法与普通函数定义一样,而不同于成员函数的定义,因为它不需要指出所属的类。但是,它可以引用类中的私有成员,函数体中a.xb.xa.yb.y都是类的私有成员,它们是通过对象引用的。在调用友元函数时,也是同普通函数的调用一样,不要像成员函数那样调用。本例中,p1.Getxy()p2.Getxy()这是成员函数的调用,要用对象来表示。而Distance(p1, p2)是友元函数的调用,它直接调用,不需要对象表示,它的参数是对象。(该程序的功能是已知两点坐标,求出两点的距离。)

  2.2 友元类

友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。

示例代码如下:

#include    

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(); 

}

在上面的代码中我们成功的通过InternetEditcname成员函数操作了Country类的保护成员cname

3.当友元遇到了虚函数:

几点基本知识:

1)如果类A是类B的友元,则类A(的成员函数)可以直接访问类B的私有成员。

2)友元不能继承。也就是说,类A是类B的友元,类D是类B的派生类,则类A并不会直接是类D的友元。通俗一点,父亲的朋友,并不天生就是儿子的朋友。

3)虚函数的基本知识就不说了。

来看下面的几段代码:

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 的友元类, DB的派生类。 所以,若想在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,而AB的友元,所以允许它调用output(它认为是B::output);而在运行时,由于output是虚函数,所以最终被决定到D::output头上。

阅读(232) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~