IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 继承中的函数重载 -> 正文阅读

[C++知识库]C++ 继承中的函数重载

本篇博文将梳理一下C++继承体系当中的类作用域与函数重载的疑难点,参考书籍C++ Primer第五版 第15章 第6节 《继承中的类作用域》。

1. 类作用域和继承体系类作用域

概述:
  每个类都定义有自己的作用域,在类的作用域当中定义类的成员。继承体系中的类作用域存在作用域嵌套关系:派生类的作用域嵌套在基类的作用域之内。
  普通作用域具有这样的性质:内层作用域的名字会隐藏外层作用域中的相同名字;如果内层作用域中找不到对应的名字,将在外层作用域直至全局作用域中继续寻找,如果依然找不到相应的名字,编译器将报错。
  类作用域和继承体系类作用域,具有普通作用域同样的性质:派生类作用域中的名字,会隐藏基类中的同名名字。如果当前派生类作用域寻找不到名字,编译器将在基类作用域中继续寻找,直至寻找到继承体系的最顶端…

关键要点:在C++语言中,名字查找发生在类型检查之前。

1.1 虚函数与普通成员函数

概述:
  在C++继承体系中,需要我们理清两种类型的成员函数:对于那些与派生类类型相关,从而可能有不同实现的函数,应该在基类中定义为虚函数,用虚函数的覆盖override来体现多态性;对于基类希望派生类直接继承不要改变的函数,则定义为普通成员函数。

C++ Primer P528
在这里插入图片描述

1.2 静态类型与动态类型

C++ Primer P534
在这里插入图片描述

1.继承体系中,派生类对象中含有基类的部分,因此可以用基类指针指向派生类中的基类部分。
2.当变量的静态类型是指针或引用时,由于引用的内部实现等同于指针,所以我们可以把派生类对象赋值给基类指针变量或基类引用变量。
3.所以,当变量的静态类型是基类指针或基类引用时,该变量指向的内存空间可能是基类对象的空间,也可能是派生类对象的空间。

1.3 函数调用的解析过程

概述:
  区分继承体系中的虚函数与普通成员函数是很重要的。程序员在编写代码的过程中,也应该牢记这种函数区分关系。除了覆盖继承而来的虚函数外,派生类最好不要重用其他定义在基类中的名字。假如在派生类中重新定义了基类中的相同名字,且该名字不是虚函数名,可能会出现令人困惑的情况。
  下面开始一步步来分析缘由。
  
C++ Primer P549
在这里插入图片描述

1.由于每个类负责控制自己成员的各个方面,所以每个类的成员的定义只出现在自己的类作用域当中。两个类中的成员处于不同的作用域,派生类会隐藏基类中的同名实体,所以两个类的函数之间不构成重载关系。
2.如果派生类重定义了基类中的名字,编译器由于在派生类中找到了名字的定义,将不再查找基类作用域。这样就无法使用基类中的相同名字了,甚至可能会出现错误。

在这里插入图片描述

对象d的静态类型是Derived3类,编译器将从Derived3类开始寻找print的名字,由于Derived3类中没有找到,所以在Derived3类的直接基类Derived2类中继续寻找。以此类推,最终寻找到Base类中的print定义。该名字print的定义是一个void print(string)的函数,所以d.print(string(“hello”))的调用是正确的,调用的是Base类中的void print(string)。

在这里插入图片描述

函数解析的起点是类的静态类型。编译器在Derived3类中寻找print名字的定义。因为在Derived3类中找到有定义print名字,所以编译器将停止查找,接下来确定调用形式是否正确。如图所示,d.print(string(“hello”))形参是string类型,而Derived3类中定义的print函数形参是vector<int*>类型,因而发生调用错误。

在这里插入图片描述

1.可以使用作用域运算符::指定从继承体系中的哪一层开始寻找名字的定义。
2.如上图所示,使用作用域运算符指定Derived1类作为名字查找的起始作用域。从Derived1类作用域中开始寻找名字print,这样Derived2类作用域中的名字print将不会作为寻找的结果;因为Derived1类是Base类的派生类,所以能够寻找到基类Base中的print名字,print名字的定义是int print。
3.从上述示例可以看到,由于编译器是从静态类型的作用域开始寻找名字的定义,如果派生类重定义了基类中的名字,将会出现不可预料的结果。当派生类重定义了基类的普通成员函数名print,派生类对象d1,d2,d3…调用print的结果将不完全一致;然而将基类指针或引用pd1,pd2,pd3…指向派生类对象d1,d2,d3…,因为编译器会从静态类型Base类中寻找名字print的定义,pd1,pd2,pd3的调用结果却是完全相同的。

C++ Primer P549

在这里插入图片描述

关键要点:在C++语言中,名字查找发生在类型检查之前。

2. 虚函数与作用域

C++ Primer P550

在这里插入图片描述
在这里插入图片描述
概述:
  下面的几个示例将把可能的情况一一分析。由于函数调用的解析过程是从静态类型的类作用域开始寻找名字定义,如果派生类中重写的函数 void print(int)与基类虚函数 virtual void print()的形参列表不一致,派生类重写的函数 void print(int)就不是虚函数,而是普通成员函数。因此,重写的普通成员函数 void print(int)将隐藏基类中的虚函数 virtual void print(),编译器如果从派生类向上开始查找函数名字print,将会寻找到 void print(int)停止。 虚函数 virtual void print()在派生类中不会发生覆盖override。
  在这里插入图片描述

我们使用基类指针指向基类对象和派生类对象,因此存在静态类型与动态类型不一致的情况。编译器会在静态类型Base类中查找print名字的定义,发现print是虚函数,因此编译器产生的代码将在运行时确定到底运行该虚函数的哪个版本,依据是对象的动态类型。因为Derived1类中的void print(int)与基类虚函数void print()的形参列表不一致,所以Derived1类中的void print(int)函数是普通成员函数,不是虚函数;所以pb_d1->print()将使用继承基类版本的虚函数。 Derived2类中的void print()与基类虚函数void print()的形参列表一致,所以Derived2类中的void print()函数将覆盖基类版本的虚函数。

在这里插入图片描述
在这里插入图片描述

1.我们以Derived1作为基类类型,定义了Derived1基类指针,分别指向Derived1类和Derived2类对象。编译器从静态类型Derived1类中寻找print名字的定义,确定print名字定义为 void print(int),且该函数是普通成员函数,所以编译器将停止查找,Base类中的print名字(virtual void print())将被隐藏。因此,Derived1类中寻找不到void print()的函数定义,pd_d1->print() 和 pd_d2->print()调用出错。
2.因为编译器在Derived1类中寻找到的print名字(void print(int))定义为普通成员函数,所以pd_d1->print(0) 和 pd_d2->print(0)调用是不具备多态性的,调用的都是基类Derived1类中的版本。

在这里插入图片描述

编译器从静态类型Derived1类寻找不到名字print的定义,因此在Derived1类的直接基类Base继续寻找。在Base类中,print是虚函数,所以调用print函数时将发生动态绑定。pd_d1->print()将使用继承Base类中的print函数定义,pd_d2->print()将使用Derived2类中的虚函数版本。

3. 虚函数重载

C++ Primer P551
在这里插入图片描述

在这里插入图片描述

由于Derived1类中定义了print名字,因此Base类中的三个print重载函数将被隐藏,在Derived1类中使用 void print(int)和 void print(string)将找不到定义。

在这里插入图片描述

可以看到,使用using Base::print;将使得Base类中的print名字在Derived1类作用域中变为可见。这样的话,Derived1类中的 void print()将重载 Base类版本的 void print().

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-23 10:32:07  更:2021-07-23 10:33:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/6 1:40:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码