千家信息网

C++的友元和虚函数是什么

发表于:2024-11-12 作者:千家信息网编辑
千家信息网最后更新 2024年11月12日,这篇文章主要介绍了C++的友元和虚函数是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++的友元和虚函数是什么文章都会有所收获,下面我们一起来看看吧。友元可以是一个
千家信息网最后更新 2024年11月12日C++的友元和虚函数是什么

这篇文章主要介绍了C++的友元和虚函数是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C++的友元和虚函数是什么文章都会有所收获,下面我们一起来看看吧。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public。

几点基本知识:

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 的友元类,而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头上。

一、首先要理解为什么派生类不应该继承基类的"友元",这一点很多C++的书讲到了

二、其次要理解它的语法机制:前面讲的,一个编译期属性与一个运行期属性相遇了……

三、要理解如果想关掉这一类欺骗,其实做不到。且来看看,该法之一是在编译期,也检查实际调用对象的类型,前面的示例代码不难做到这一点,但下面的代码中,pb来自一个形参:

Code:

A::test_2(B* pb)  {  pb->output(); //很难在编译期反查出pb的实际类型。  //因为调用test_2()的代码,可能无处不在,甚至可能在未来的代码  }

四、要理解有时候,人们其实就是在故意做这种事,最典型的做法,就是通过非虚函数调用虚函数:

Code:

class B  {  public:  void Action()  {  this->DoAction();  }  private:  virtual void DoAction() = 0; //一个私有的纯虚函数  // friend class A;  };

任何一个合格的C++程序员,都应该学会这种作法。DoAction是一个纯虚函数,这里我们更决绝一点,干脆让它是私有的,这就是逼着派生类自己去实现一个完全自我的DoAction(),假设有个class D : public B,并且听话地实现了DoAction。具体D的定义,为节省点篇幅,不写了。

注意到第11行的注释, class A 现在已经不是 B 的友员了,但不要紧,我们只是想在新版的类A中,调用Action函数,而它是public的,所以这里不需要友元来搅和。

Code:

class A  {  void test()  {  B* pb = new D; //pb 实际指向一个D对象。  pb->Action(); // Action 是 公开的,所以可以调用  }  };

pb 调用了非虚的B::Action函数,但在Action内调用了虚函数DoAction,再由于pb实际指向的是D对象,所以最终调用的是D::DoAction()——这了无新意对不对?只要学过一点C++的多态,都会懂这一点。没错,它太司空见惯了,基本上所有C++程序员每天都会在写类似的代码——这就是我想说的,有时候,看起来在调用基类的代码,但实际上在调用派生类的代码。假设我们修改了语法规则,逼着虚函数在遇上友元之后失效,那就是逼着程序员不去用friend,去将更多本来应该是private的成员,用各种该法写成public的。

五、接着,是一个看起来很简单,但却被很多人误解的概念:友元是破坏了封装了吗?错,友元其实是促进了更好的封装。它基于这样的需求:有一个类,它有那么几个成员(数据或函数),它只能对个别的其它类公开,这时,你可以考虑使用友元这项技术。如果不用,会有很多人就把那些成员直接修改为public,结果:原本应只对个别类开放的属性,变成对所有类开放了。俗气一点,法律规定老婆可以在私有场合下看老公的屁屁,如果法律强制规定不允许有这种例外,那很可能会有一些哥们,直接把屁股public出来就上街了——你若问他为什么,他也很无辜:我不过是想让我老婆方便一些。

六、读了第五点,对OO有一套的C++程序员要"笔试/鄙视"我了,好,好,我知道既使不用friend的属性,也可以美满地实现前述的,类似老婆看老公屁股的问题——我是说,通过OO技术,避开需要只对部分类开放权限的需求,而转化为第三方(比如某个接口及它的实现类)中——不管如何,你必须承认友元没有破坏封装性,因为其它解决这一问题的的,似乎更纯粹的OO技术,它们美好的地方是在于更细的类颗粒,以及更好的类组织(不美好地方是,效率差了点,以及对OO思想非得有点水准,否则会绕晕掉,为了不让Cer笑话,我们不提太多)。

七、不过,纵算如此(第六点),我还是是狡辩一句:就算是在那些看起来很纯的OO语言里,其实也有友元的影子啊。比如Java,是没有friend关键字,可以它的内部类(非静态的内部类),可以直接访问外部类,难道不是友元吗?——事实上,这也正是在C++使用friend最主旋律的用法(我甚至不用写"之一")。再如Object Pascal(Delphi),是没有friend关键字,可是只要是位于同一个代码单元(就是同一个.pas文件),则其中所有类天生就是可以互相访问啊(当然,需首先满足可见性)——这是当年我用Delphi时觉得最爽的地方之一了,既然号称更OO的语言都留了一手,为何C++不能呢? :)

八、第七点明显带有情绪化,这不符合C++之父对我们的期望: " 在C++设计中有一条指导原则,那就是,无论做什么事情,都必须相信程序员。与可能出现什么样的错误相比,更重要得多的是能做出什么好事情。C++程序员总被看作是成年人……" 。在C++的大千世界,差异性永远被尊重, 有人不爱用template,那不用就是; 有人坚决认为只要有private和public就足够了,那就把protected忘记吧,甚至有人认为virtual也是多余的——很多人就是把C++当成另一种C使用,那都可以接受。friend也一样,如果你不用,它的存在并不会给你带来什么性能损失,你要做的就是用很OO或很不OO的,但是你熟悉的方法去满足友元的需求而已。

九、一定要这个第9点。 除了友元类,更常见的其实是友元函数。很多操作符的重载,都需要全局的友元函数来减轻相关类的public出太多成员。这一下扯到"操作符重载"有用吗?哇,这是另外一个经典的问题了,它曾经引起的纠纷,比这个"友元"所带来的,要热闹上几倍呢。就此打住。

十、***一点,C++初学者如何学习这门语言众多的,又容易产生正交效应的特性呢?我有个建议:先有个基本了解,做点练习,但并不需要急着真正使用。

友元的作用是提高了程序的运行效率,即减少了类型检查和安全性检查等都需要时间开销,但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。而虚函数的作用则是为了实现多态。

关于"C++的友元和虚函数是什么"这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对"C++的友元和虚函数是什么"知识都有一定的了解,大家如果还想学习更多知识,欢迎关注行业资讯频道。

函数 C++ 代码 就是 编译 成员 程序 程序员 私有 不用 实际 知识 类型 问题 元和 属性 编译器 这是 封装 地方 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 网络服务器运维与管理 宝安区口碑好的网络技术联系方式 软件开发总监面试一般问题 魔兽世界能不能跨服务器飞行 交换机断电和服务器断电影响 网络安全生活篇的心得 南京炒股的软件开发 保障网络安全的基本性工作 r语言读取出来的数据库 柏顺软件服务器名称在哪里看 网络安全团活总结1500字 黑客网络技术服务 青少年网络安全知多少 网络安全管理规划方案 遵义网络安全技术培训朝阳行业 数据库upgrade语句 怎样导出网页的表格数据库 云服务器和云储存哪个省钱 嗨购互联网科技有限公司 腾讯云软件开发者加盟 学校网络安全管理报告 二零网络技术工作室 怎么取消数据库用户口令 我的世界网易版土豆服务器 广州熙成网络技术有限公司 河北联通沧州dns服务器地址 aw服务器 山西崇煌网络技术有限公司 云南互联网养老软件开发专业制作 哇嘎不能下载服务器列表怎么办
0