类的关系图:

一、作用域与名字查找

1.作用域的嵌套

派生类的作用域嵌套在基类之内

  1. Bulk_quote bulk;
  2. cout<< bulk.isbn();

名字isbn解析过程:

  • 因为我们是通过Bulk_quote的对象调用isbn的,所以首先在Bulk_quote中查找,这一步没有找到名字isbn。
  • 因为 Bulk_quote是Disc_quote 的派生类,所以接下来在Disc_quote中查找,仍然找不到。
  • 因为Disc_quote是Quote的派生类,所以接着查找Quote;此时找到了名字isbn,所以我们使用的isbn最终被解析为Quote中的isbn。

2.在编译时进行名字查找

成员名字的查找类型由静态类型决定

  1. //给Disc_quote添加一个成员,返回折扣政策
  2. class Disc_quote : public Quote {
  3. public :
  4. std::pair<int ,double) discount_policy() const
  5. {return {quantity,discount};}
  6. };

我们只能通过Disc_quote及其派生类对象来使用discount_policy。

  1. Bulk_quote bulk;
  2. Bulk_qoute *bulkP = &bulk; //静态类型与动态类型一致
  3. Quote *itemP = &bulk; //静态类型为Quote,动态类型不一定
  4. bulkP->discount_policy(); //正确:bulkP的类型是Bulk_quote*
  5. itemP->discount_policy(); //错误:itemP的类型是Quote*

尽管在bulk中确实含有一个名为discount_policy的成员,但是该成员对于itemP却是不可见的。

itemP的类型是Quote的指针,意味着对discount_policy的搜索将从Quote开始

显然Quote不包含名为discount_policy的成员,所以我们无法通过Quote的对象、引用或指针调用discount_policy。

3.名字冲突与继承

派生类可以重用基类中的名字,由于派生类的作用域嵌套在基类中,所以会隐藏基类的同名变量

派生类成员隐藏同名的基类成员

  1. struct Base{
  2. Base():mem(0){}
  3. protected:
  4. int mem;
  5. };
  6. struct Derived : Base{//struct默认public继承
  7. Derived(int i) : mem(i){};
  8. int get_mem() {return mem;}
  9. protected:
  10. int mem;
  11. };

get_mem返回的是在Derived中的mem

  1. Derived d(42);
  2. cout<<d.get_mem()<<endl; //打印42

4.通过作用域运算符来使用隐藏的成员

  1. struct Derived : public Base{
  2. int get_base_mem() {return Base::mem;}
  3. //...
  4. };
  5. d.get_base_mem(); //输出0

二、同名函数隐藏与虚函数覆盖

1.几种必须区分的情况

派生类函数形式 与基类同名函数的关系 形参列表 绑定方式
非虚函数 隐藏基类同名函数 可相同可不同 静态绑定
虚函数 覆盖基类虚函数 必须相同 动态绑定

使用基类的引用或指针调用虚函数时,会发生动态绑定

  1. 当派生类有基类的同名虚函数且该函数不是虚函数时,无论两个同名函数的参数是否相同。

    • 由于派生类的作用域嵌套在基类内部,所以都会隐藏同名的基类函数
    • 由于不是虚函数,所以即使两函数参数相同,也不会发生动态绑定
  1. //情况1举例
  2. class A{
  3. public :
  4. //基类的print不是虚函数
  5. void print() const
  6. {cout<<"class A"<<endl;};
  7. };
  8. class B : public A{
  9. public:
  10. //B隐藏了A的同名函数
  11. void print() const
  12. {cout<<"class B"<<endl;}
  13. };
  14. void Test_Print(const A &a){//传入基类的指针
  15. //由于基类的print不是虚函数,所以不会动态绑定
  16. //.print()的结果取决于a的静态类型
  17. //所以无论传入的是A还是B,都打印class A
  18. a.print();
  19. }
  20. int main(){
  21. A a;
  22. B b;
  23. Test_Print(a); //打印class A
  24. Test_Print(b); //打印class A;因为传入参数的静态类型是A
  25. return 0;
  26. }
  1. 当派生类有基类的同名函数且该函数是虚函数时

    • 参数列表相同,实现覆盖基类虚函数,可以发生动态绑定

      1. //情况2:参数列表相同时,虚函数被覆盖
      2. class A{
      3. public :
      4. //基类的print是虚函数
      5. void print() const
      6. {cout<<"class A"<<endl;};
      7. };
      8. /*
      9. class B和Test_Print都不变
      10. */
      11. int main(){
      12. A a;
      13. B b;
      14. Test_Print(a); //打印class A
      15. Test_Print(b); //打印class B;因为发生了动态绑定
      16. return 0;
      17. }
    • 参数列表不相同,相当于派生类定义了一个新函数隐藏了基类虚函数,基类的虚函数没有被覆盖

      1. class A{
      2. public :
      3. //基类的print是虚函数
      4. void print() const
      5. {cout<<"class A"<<endl;};
      6. };
      7. class B : public A{
      8. public:
      9. //B的print(int i)与基类虚函数同名
      10. //但参数列表不同,定义了一个新函数
      11. //基类的虚函数print()没有被覆盖
      12. void print(int i) const
      13. {cout<<"print(int i)"<<endl;}
      14. };
      15. void Test_Print(const A &a){//传入基类的指针
      16. a.print();
      17. }
      18. int main(){
      19. A a;
      20. B b;
      21. //打印class A
      22. Test_Print(a);
      23. //打印class A;
      24. //因为派生类没有重载虚函数,继续调用基类虚函数
      25. Test_Print(b);
      26. //打印print(int i)
      27. //调用派生类新增的函数print(int i)
      28. b.print(42);
      29. return 0;
      30. }

2.一个更复杂的例子

例子出自《C++ Primer》P550

  1. //类的定义
  2. class Base{
  3. public :
  4. virtual int fcn();
  5. };
  6. class D1 : public Base{
  7. public:
  8. //隐藏基类的fcn,这个fcn不是虚函数
  9. //D1继承了Base::fcn()虚函数的定义
  10. int fcn(int); //形参列表与Base中的fcn不一致
  11. virtual void f2(); //定义一个新的虚函数,它在Base中不存在
  12. };
  13. class D2 : public D1{
  14. public :
  15. int fcn(int); //是一个非虚函数,隐藏了D1::fcn(int)
  16. int fcn(); //覆盖了虚函数Base::fcn()
  17. void f2(); //覆盖了虚函数f2
  18. };
  1. //调用虚函数的例子
  2. //fcn是Base中的虚函数
  3. //D1直接继承Base的虚函数fcn
  4. //D2重载了Base的fcn
  5. Base bobj;
  6. D1 d1obj;
  7. D2 d2obj;
  8. Base *bp1 = &bobj, *bp2 = &d1jobj, *bp3 = &d2obj;
  9. bp1->fcn(); //虚调用:运行时执行Base::fcn
  10. bp2->fcn(); //虚调用:运行时执行Base::fcn
  11. bp3->fcn(); //虚调用:运行时执行D2::fcn
  12. //f2是D1中的虚函数
  13. //Base中没有定义f2
  14. //D2重载了D1的虚函数f2
  15. D1 *d1p = &d1obj
  16. D2 *d2p = &d2obj;
  17. bp2->f2(); //错误:Base对象中没有名为f2的成员
  18. d1p->f2(); //虚调用:执行D1::f2()
  19. d2p->f2(); //虚调用:执行D2::f2()
  1. //调用非虚函数的例子
  2. //fcn(int)是D1中的 非虚函数
  3. //Base中没有定义fcn(int)
  4. //D2中的fcn(int)隐藏了D1中的fcn(int)
  5. Base *p1 = &d2obj; //d2obj是D2类型的对象
  6. D1 *p2 = &d2obj;
  7. D2 *p3 = &d2obj;
  8. p1->fcn(42); //错误:Base中没有接受int的fcn
  9. p2->fcn(42); //静态绑定:调用D1::fcn(int)
  10. p3->fcn(42); //静态绑定:调用D2::fcn(int)

C++ 派生类函数重载与虚函数继承详解的更多相关文章

  1. C#虚函数virtual详解

    在面向对象编程中,有两种截然不同的继承方式:实现继承和接口继承.在实现继承时候,在Java中,所有函数默认都是virtual的,而在C#中所有函数并不默认为virtual的,但可以在基类中通过声明关键 ...

  2. C++:虚函数的详解

    5.4.2 虚函数详解 1.虚函数的定义 虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数.虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问 ...

  3. c++运算符重载和虚函数

    运算符重载与虚函数 单目运算符 接下来都以AClass作为一个类例子介绍 AClass{ int var } 区分后置++与前置++ AClass operator ++ () ++前置 一般设计为返 ...

  4. C#虚方法virtual详解

    转: http://www.cnblogs.com/jason_yjau/archive/2009/08/25/1553949.html C#虚方法virtual详解 在C++.Java等众多OOP语 ...

  5. 4.C#虚方法virtual详解

    C#虚方法virtual详解 在C++.Java等众多OOP语言里都可以看到virtual的身影,而C#作为一个完全面向对象的语言当然也不例外. 虚拟函数从C#的程序编译的角度来看,它和其它一般的函数 ...

  6. 虚方法virtual详解

    虚方法virtual详解   从C#的程序编译的角度来看,它和其它一般的函数有什么区别呢?一般函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的,也就是写死了的!而虚函数在 ...

  7. 「万字图文」史上最姨母级Java继承详解

    摘要:继承是面向对象软件技术中的一个概念.它使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用. 本文分享自华为云社区<「万字图文」史上最姨母级Java继承详解丨[奔跑吧!JAVA] ...

  8. [原创]JavaScript继承详解

    原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...

  9. 转载 LayoutInflater的inflate函数用法详解

    http://www.open-open.com/lib/view/open1328837587484.html LayoutInflater的inflate函数用法详解 LayoutInflater ...

随机推荐

  1. linux中cut命令与tr命令

    目录 一:linux中cut命令 1.cut 命令作用 2.参数 3.参数案例解析: 二:tr命令 1.tr命令作用 2.tr命令格式 3.tr命令参数 4.案例解析: 一:linux中cut命令 1 ...

  2. Android Studio IDE 插件开发

    作者:字节跳动终端技术--周宸韬 概述 这篇文章旨在向读者介绍IntelliJ IDE插件的开发流程以及常用的一些通用功能,任何基于IntelliJ开发的IDE都可以通过该方式制作插件,例如Andro ...

  3. python07day

    回顾 id == is: ==: 数值是否相同 is: 内存地址是否相同 id: 获取对象的内存地址 代码块: 一个文件.交互式命令一行都是一个代码块 同一代码块下缓存机制(字符串驻留机制) 所有数字 ...

  4. 如何在pyqt中实现亚克力磨砂效果的QLabel

    前言 Windows10 在 UWP 应用中支持亚克力画刷,可以在部件的底部绘制亚克力效果的背景图.下面我们使用 QLabel 来模拟这个磨砂过程. 实现方法 MSDN 文档中介绍了亚克力材料的配方, ...

  5. git 初始化本地项目并推送到远程

    有一个新项目,开发了一些代码之后想推送到远程,具体的操作方式和命令如下: (使用 git bash) 1.切到项目目录中,例如 E:\git\smart-open 2.初始化git仓库并在本地提交 / ...

  6. UDP数据包最大传输长度

    概念以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的.这个1500字节被称为链路层的MTU(最大传输单元). 但这并不是指链路层的长度被限制在1500字 ...

  7. Android Studio 插件(不定期更新)

    GsonFormat 根据JSONObject格式的字符串,自动生成实体类参数. 安装 1.Android studio File->Settings-->Plugins -->in ...

  8. 关于在 Linux 下多个不相干的进程互斥访问同一片共享内存的问题

    转载请注明来源:https://www.cnblogs.com/hookjc/ 这里的"不相干",定义为: 这几个进程没有父子关系,也没有 Server/Client 关系 这一片 ...

  9. JScrollPane 自动跟进 自动到滚动到最底部

    感谢大佬:https://blog.csdn.net/csdn_lqr/article/details/51068423 注:以下方法为网上摘抄 1 . JTable( 放在JScrollPane中  ...

  10. spring filter详解

    一.Filter基本工作原理 1.Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的. 2.当在 web.xml 注册了一个 ...