1.重载,在同一个作用域中定义的同名不同参的一些函数为重载。

2.隐藏,若在基类中定义了某一non_virtual函数,在派生类重存在同名函数(不需要参数列表相同),基类的该函数在派生类中是不可见的,如若用派生类的实例化对象调用基类的该函数会编译错误。此种情况即为隐藏,指基类的函数在派生类中隐藏了,编译器找不到这个函数。

3.覆盖,如果基类中的某一函数被声明为虚函数,那么在派生类中如果重新定义了这个虚函数,那么我们说该函数在派生类中被覆盖了。用指向派生类的基类指针访问该函数只能调用派生类的该函数。

class Base
{
public:Base(){std::cout << "Base Constructor" << std::endl;}virtual ~Base(){std::cout << "Base Destructor" << std::endl;}public:void f()   //不带参数的f函数{cout << 0 << endl;}void f(int a)  //带一个参数的f函数{cout << a << endl;}protected:std::string Bmsg;
};class Derive : public Base
{
public:Derive(){std::cout << "Derive Constructor" << std::endl;}~Derive(){std::cout << "Derive Destructor" << std::endl;}public:void f(int a, int b)  //带两个参数的f函数{cout << a + b << endl;}private:std::string Dmsg;
};int main()
{Base b;std::cout << std::endl;Derive d;std::cout << std::endl;Base * pb = new Derive;std::cout << std::endl;b.f();     //OKb.f(1);     //OKd.f();    //errord.f(1);   //errord.f(1, 2); //OKdelete pb;pb = NULL;return 0;
}

本例中,我们定义了基类的实例化对象b,派生类的实例化对象d。

 Base b;
 Derive d;
 b.f();     //OKb.f(1);     //OK

基类的实例化对象调用自身的函数通过编译,我们说在基类中f函数被重载了。

 d.f();    //errord.f(1);      //error

通过派生类的实例化对象调用基类的f函数,因为在派生类中额外定义了一个同名函数f,我们说在派生类中基类的f函数被隐藏了,从而在派生类中这些函数不可见,编译器找不到这些函数,所以无法调用。

 d.f(1, 2); //OK

通过派生类的对象调用自身的函数通过编译,如前所述,这个函数的定义导致基类的同名函数被隐藏了。

当然,如果非要在上述条件中使用基类被隐藏的函数话,可以使用using关键字来进行标识,具体做法如下:

public:using Base::f;

像这样,因为f是public作用域中,所以我们在派生类的public作用域中添加using Base::f;这条语句就相当于我们认为地告诉编译器基类中的f函数是可见的。

 d.f();d.f(1);d.f(1, 2);

这样就可以用派生类的实例化对象调用三个f函数了。

上述内容对于虚函数同样适用,但只适用于用实例化对象访问成员函数的情况,

而对于使用多态指针的情况,即本例中定义的pb变量,请看下面的测试:

 pb->f();      //OKpb->f(1);     //OKpb->f(1, 2);  //error

我们发现,如果调用的不是虚函数,使用指针的情况和实例化对象是相同的,编译器访问的作用域就是该指针的静态类型,本例中pb的静态类型是Base类型,因为Base中没有接收两个参数的f函数,所以不能通过编译。

下面讨论多态指针和虚函数的情况,我们需要改造一下类的定义:

#include <iostream>
using namespace std;
class Base
{
public:Base(){std::cout << "Base Constructor" << std::endl;}virtual ~Base(){std::cout << "Base Destructor" << std::endl;}public:void f(){cout << 0 << endl;}void f(int a){cout << a << endl;}virtual void g()   //虚函数,不带参数{cout << "Base" << endl;}virtual void g(int a)   //虚函数,带一个参数{cout << "Base" << a << endl;}protected:std::string Bmsg;
};class Derive : public Base
{
public:Derive(){std::cout << "Derive Constructor" << std::endl;}~Derive(){std::cout << "Derive Destructor" << std::endl;}public://using Base::f;void f(int a, int b){cout << a + b << endl;}void g()   //派生类中继承虚函数性质,不带参数{cout << "Derive" << endl;}void g(int a, int b)  //同上,带两个参数{cout << "Derive" << a << " " << b << endl;}
private:std::string Dmsg;
};int main()
{Base b;std::cout << std::endl;Derive d;std::cout << std::endl;Base * pb = new Derive;std::cout << std::endl;pb->g();         //输出"Derive"pb->g(1); //输出"Base1"pb->g(1, 2); //errordelete pb;pb = NULL;return 0;
}

这个例子中在基类中增加了函数名为g的虚函数,分别是不带参数和带一个参数,如前所述,这两个函数在基类中被重载。

现在讨论用指针来访问成员函数的问题,此时pb是一个指向派生类的基类指针:

pb->g();          //输出"Derive"

编译器在处理指针问题的时候,会先判断该指针指向的对象类型,本例中指向的是派生类,所以先去派生类的作用域中寻找最佳匹配的函数(即函数名和参数列表都匹配),因为我们在派生类中定义了不带参数的g函数,所以可以通过编译,输出"Derive"。

pb->g(1);         //输出"Base1"

在第一种情况下,如果编译器没有找到匹配的函数,会向上进入到其基类的作用域中寻找,本例在基类中找到带有一个参数的g函数,所以通过编译,输出"Base1"。

pb->g(1, 2);          //error

如果按照前面两条所述,那么这条语句按理说应该会通过编译才对,因为在派生类中找到了这样的函数。

但是这只是表面现象,编译器真的可以找到这样的函数吗。我们知道,在进行多态调用函数的时候是通过虚函数表来寻找匹配函数的。在定义了一个指向派生类的基类指针时,会在分配到的内存最开始处存放所有基类虚函数的地址,然后扫描派生类,如果派生类重新定义了某个虚函数,那么会更新虚函数表,将表中这个虚函数的地址替换成派生类中该虚函数的地址。也就是之前说的覆盖。

然而如果在基类中本来就不存在某一个虚函数,而在派生类中却存在这样的虚函数,那么扫描派生类的时候就不会更新虚函数表,表中就没有该函数的地址,编译器自然是找不到这个函数的。所以无法通过编译。

可以理解为指向派生类的基类指针可以调用派生类的虚函数,但是这些虚函数必须在基类中同样存在。



												

C++学习笔记-----继承体系中函数的重载,覆盖和隐藏的区别相关推荐

  1. python学习笔记之-展平函数ravel和flatten及两者的区别

    ravel()和flatten()是将多维数据展平为一维数据,功能相同,区别在于一个是引用操作,一个是复制操作.ravel()展平数据后,修改后面的数据会影响前面的数据,而flatten()展平数据后 ...

  2. C++编程进阶5(内联函数、如何降低编译成本、处理继承体系中同名不同参的成员函数、私有虚函数)

    十七.内联函数 在https://blog.csdn.net/Master_Cui/article/details/106391552中,已经简单的说过内联函数的作用. 函数体较小的内联函数经过编译后 ...

  3. 【theano-windows】学习笔记六——theano中的循环函数scan

    前言 Scan是Theano中最基础的循环函数, 官方教程主要是通过大量的例子来说明用法. 不过在学习的时候我比较习惯先看看用途, 然后是参数说明, 最后再是研究实例. 国际惯例, 参考网址 官网关于 ...

  4. 关于虚继承(在钻石继承体系中,一定要用虚继承!)

    在钻石继承体系中,一定要用虚继承! 1.下面的代码块儿无法通过编译,原因是,A3无法确定自己到底是用哪一个父类中的函数. class A { public: virtual void f(){} vi ...

  5. Vue学习笔记进阶篇——Render函数

    本文为转载,原文:Vue学习笔记进阶篇--Render函数 基础 Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML.然而在一些场景中,你真的需要 JavaScript 的完全编 ...

  6. Hadoop学习笔记—11.MapReduce中的排序和分组

    Hadoop学习笔记-11.MapReduce中的排序和分组 一.写在之前的 1.1 回顾Map阶段四大步骤 首先,我们回顾一下在MapReduce中,排序和分组在哪里被执行: 从上图中可以清楚地看出 ...

  7. 《Go语言圣经》学习笔记 第五章函数

    <Go语言圣经>学习笔记 第五章 函数 目录 函数声明 递归 多返回值 匿名函数 可变参数 Deferred函数 Panic异常 Recover捕获异常 注:学习<Go语言圣经> ...

  8. 【theano-windows】学习笔记十七——梯度中的consider_constant

    前言 主要是在写玻尔兹曼机相关的theano时, 在计算梯度grad的时候发现一个参数名字叫做consider_constant,来看看这个到底做了什么事情 参考博客: using consider_ ...

  9. 【theano-windows】学习笔记十一——theano中与神经网络相关函数

    前言 经过softmax和MLP的学习, 我们发现thenao.tensor中除了之前的博客[theano-windows]学习笔记五--theano中张量部分函数提到的张量的定义和基本运算外, 还有 ...

最新文章

  1. 最先进的AI还不如动物聪明?首届AI-动物奥运会英国开赛!
  2. 学习笔记(十二)——虚拟机安装和pycharm远程连接Ubuntu
  3. Android IPC机制
  4. (五十六)iOS多线程之NSOperation
  5. PE文件感染和内存驻留
  6. 数据科学家数据分析师_站出来! 分析人员,数据科学家和其他所有人的领导和沟通技巧...
  7. python selenium爬虫需要账号和密码登陆的网页_如何使用selenium和requests组合实现登录页面...
  8. python做界面用什么软件好_pyqt | 做一个好用的图形界面软件
  9. 记录——《C Primer Plus (第五版)》第七章编程练习第十一题
  10. Android NDK开发之 NEON使用介绍
  11. 思想是精神的种子,改造自己的内心世界
  12. mysql数据库MyISAM存储引擎_MySQL数据库MyISAM存储引擎
  13. oppo鸿蒙系统刷机包下载,OPPO A59st官方固件rom刷机包_OPPO A59st系统升级包下载
  14. windows无法新建计算机对象,无法创建文件,详细教您无法新建文件夹怎么办
  15. 内华达大学里诺校区计算机科学,PayScal公布美国各州就业工资最高大学!加州第1竟是文理学院,纽约州也非哥大和纽大...
  16. 2022年CPU天梯图(7月更新)
  17. 前端静态资源缓存最优解以及max-age的陷阱
  18. Android studio Suggestion: use tools:overrideLibrary=”jp.wasabeef.blurry” to force usage
  19. EndNote中补充文献信息的两种方法
  20. matlab premnmx 逆函数,请帮我吧这些数据利用MATLAB premnmx语句进行归一化,高分跪求。...

热门文章

  1. sqoop 增量导入mysql_sqoop增量导入数据库
  2. nslookup type值_网络工程师之nslookup命令
  3. AM8不能下任何载附件及所有聊天记录无法登记
  4. JS判断上传文件类型
  5. MySQL数据库服务器 主从配置
  6. poj 3104 Drying(二分查找)
  7. Msfvenonm生成后门
  8. SQL注入_1-6_user-agent注入
  9. App设计灵感之十二组精美的数据图表展示App设计案例
  10. PIL Image resize 调整大小谜之操作