0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在C++中如何用虚函数实现多态

Android编程精选 来源:编程学习总站 作者:写代码的牛顿 2021-09-29 14:18 次阅读

01

C++虚函数探索

C++是一门面向对象语言,在C++里运行时多态是由虚函数和纯虚函数实现的,现在我们看下在C++中如何用虚函数实现多态。先来看一段代码。

// virtual_function.cpp : 此文件包含 “main” 函数。程序执行将在此处开始并结束。 // #include 《iostream》 class Base { public: Base()

{ std::cout 《《 “Base::constructor run” 《《 std::endl; } virtual void fun1()

{ std::cout 《《 “Base::fun1 run” 《《 std::endl; } virtual void fun2() { std::cout 《《 “Base::fun2 run” 《《 std::endl; } virtual ~Base()

{ std::cout 《《 “Base::desconstructor run” 《《 std::endl; } }; class Derive : public Base { public: Derive() { std::cout 《《 “Derive::constructor run” 《《 std::endl; } void fun1() { std::cout 《《 “Derive::fun1 run” 《《 std::endl; } void fun3()

{ std::cout 《《 “Derive::fun3 run” 《《 std::endl; } ~Derive() { std::cout 《《 “Derive::desconstructor run” 《《 std::endl; } }; int main() { Derive* d = new Derive(); d-》fun1(); d-》fun2(); d-》fun3(); delete d; }

这段代码编译运行后输出了:

Base::constructor run Derive::constructor run Derive::fun1 run Base::fun2 run Derive::fun3 run Derive::desconstructor run Base::desconstructor run

这段代码里基类Base定义了虚函数fun1和fun2,派生类Derive有成员函数fun1和fun3,其中派生类覆盖了继承而来的基类虚函数fun1。在主函数里创建Derive类型对象指针d指向Derive类型对象。由于派生类Derive成员函数fun1覆盖了基类Base成员函数fun1,因此通过d调用fun1实际调用的是派生类Derive类的成员函数fun1,而继承而来的成员函数fun2没有被覆盖,则通过指针d调用fun2实际调用的是基类成员函数fun2。这里好像让看不出虚函数有什么作用,那么我们将主函数修改如下:

int main() { Base* b = new Derive(); b-》fun1(); b-》fun2(); delete b; }

在程序里我们将创建一个基类指针b并指向的是派生类,并且调用delete释放内存时使用的是基类指针b。编译运行输出结果如下:

Base::constructor run Derive::constructor run Derive::fun1 run Base::fun2 run Derive::desconstructor run Base::desconstructor run

通过基类指针b调用fun1函数,实际调用的是派生类Derive的成员函数fun1,由于在派生类Derive中没有定义成员函数fun2,因此通过基类指针b调用fun2函数,实际调用的依旧是基类Base的成员函数fun2。代码里虽然我们没有对派生类的成员函数fun1加virtual,实际上派生类的成员函数fun1是虚函数。但是对于大多数C++初学者会有2个疑问的地方。1、通过基类指针b调用fun1函数,实际调用的是派生类的成员函数fun1。2、通过delete释放内存使用的是基类指针b,会调用派生类析构函数和基类析构函数,成功释放内存,不会存在内存泄露问题。

带有虚函数的类称为虚基类,子类继承虚基类。在C++中虚基类有一个虚函数表指针保存虚函数表地址,而虚函数表保存函数地址,虚函数表并不在虚基类里,但是虚函数表指针在虚基类里,子类继承虚基类,子类也就有了虚函数表指针。那么C++是如何通过虚函数表和虚函数表指针实现多态呢?打开VS2019,并用管理员身份运行“2019开发人员命令提示符”工具,如下图所示:

输入:cl /d1 reportSingleClassLayoutXXX [filename],XXX表示类名,[filename]表示类所在的.cpp文件路径。这里我输入源文件的派生类名和源文件路径,回车输出如下:

从输出可以看出派生类从基类继承了虚函数表指针vfptr,且占用字节数大小是4字节,刚好就是一个指针占用字节数。虚函数表vftable里保存了派生类成员函数fun1,基类成员函数fun2的地址,由于派生类成员函数fun3不是虚函数,因此虚函数表里没有fun3的地址。看到这里我们就明白了,通过基类指针b调用fun1的过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类成员函数fun1的地址,调用派生类成员函数fun1。而通过基类指针b调用fun2的过程则是:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到基类成员函数fun2的地址,调用基类成员函数fun2。看到这里,第一个疑问已经解开了,关键在于虚函数表指针和虚函数表。

在C++中有虚函数的类,其析构函数默认是虚析构函数,只要是虚函数就会在虚函数表里有相应的函数地址,因此派生类里的虚函数表指针vfptr指向的虚函数表vftable必然保存着派生类析构函数的地址,类的析构过程:从继承链的最底端到最顶端依次调用析构函数,因此delete b调用过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类析构函数地址,调用析构函数。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    4326

    浏览量

    62552
  • C++
    C++
    +关注

    关注

    22

    文章

    2108

    浏览量

    73603

原文标题:C++虚函数详解

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    同样是函数,CC++中有什么区别

    同样是函数 CC++ 中有什么区别? 第一个返回值。 C语言的函数可以不写返回值类型,
    的头像 发表于 11-29 10:25 250次阅读

    C++新手容易犯的十个编程错误

    简单的总结一下 C++ 新手容易犯的一些编程错误,给新人们提供一个参考。 1 有些关键字 cpp 文件多写了 对于 C++ 类,一些关键字只要写在 .h 中就好,cpp 中就不用再
    的头像 发表于 11-15 12:42 286次阅读

    使用C语言实现函数模板

      用C语言能不能实现一个通用的函数,既能完成整数的相加,又能完成浮点数的相加?
    的头像 发表于 11-09 11:38 361次阅读

    C语言和C++结构体的区别

    同样是结构体,看看在C语言和C++中有什么区别?
    的头像 发表于 10-30 15:11 185次阅读

    ostreamc++的用法

    ostream 是 C++ 标准库中一个非常重要的类,它位于 头文件(实际上,更常见的是通过包含 头文件来间接包含 ,因为 包含了 和 )。 ostream 类及其派生类(如 std::cout
    的头像 发表于 09-20 15:11 640次阅读

    ModusToolbox 3.2c代码包含c++代码的正确步骤是什么?

    文件,但要在 main.c #include 它们时 会导致构建失败。 将 main.c 重命名为 main.cpp 会导致标准 XMC 库函数(如 XMC_GPIO_SetMo
    发表于 07-23 08:21

    C++实现类似instanceof的方法

    函数,可实际上C++没有。但是别着急,其实C++中有两种简单的方法可以实现类似Java的in
    的头像 发表于 07-18 10:16 569次阅读
    <b class='flag-5'>C++</b><b class='flag-5'>中</b><b class='flag-5'>实现</b>类似instanceof的方法

    FX2 CY7C68013A如何在C++环境中使用LoadEEPROM函数

    我使用的是 FX2 CY7C68013A 芯片。 我知道 CyUSB.NET 库中有我需要的 LoadEEPROM 函数。 请问如何在 C++ 环境而不是 C#/CLR 环境中使用该
    发表于 05-31 06:59

    C/C++两种宏实现方式

    #ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
    的头像 发表于 04-19 11:50 598次阅读

    鸿蒙OS开发实例:【Native C++

    使用DevEco Studio创建一个Native C++应用。应用采用Native C++模板,实现使用NAPI调用C标准库的功能。使用C
    的头像 发表于 04-14 11:43 2578次阅读
    鸿蒙OS开发实例:【Native <b class='flag-5'>C++</b>】

    使用 MISRA C++:2023® 避免基于范围的 for 循环中的错误

    在前两篇博客,我们 向您介绍了新的 MISRA C++ 标准 和 C++ 的历史 。在这篇博客,我们将仔细研究以 C++
    的头像 发表于 03-28 13:53 781次阅读
    使用 MISRA <b class='flag-5'>C++</b>:2023® 避免基于范围的 for 循环中的错误

    c语言,c++,java,python区别

    操作系统、嵌入式系统等对性能要求较高的场景。C语言的语法相对简单,学习曲线较平缓,也是学习其他高级语言的入门语言。 C++C++C
    的头像 发表于 02-05 14:11 2342次阅读

    如何理解运放电路短和断?

    模拟电路短和断是两个重要的概念,它们通常与运放电路有关。这两个术语描述了运放电路的一些重要现象,认识它们对于电子工程师和电路设计
    的头像 发表于 01-26 08:20 1638次阅读
    如何理解运放电路<b class='flag-5'>中</b>的<b class='flag-5'>虚</b>短和<b class='flag-5'>虚</b>断?

    C++简史:C++是如何开始的

    的 MISRA C++:2023 博客系列的第二部分。 在这篇博客,我们将深入探讨 C++ 的历史、编程语言多年来的发展历程以及它的下一步发展方向。
    的头像 发表于 01-11 09:00 575次阅读
    <b class='flag-5'>C++</b>简史:<b class='flag-5'>C++</b>是如何开始的

    基于C/C++面向对象的方式封装socket通信类

    掌握了基于 TCP 的套接字通信流程之后,为了方便使用,提高编码效率,可以对通信操作进行封装,本着有浅入深的原则,先基于 C 语言进行面向过程的函数封装,然后再基于 C++ 进行面向
    的头像 发表于 12-26 09:57 1324次阅读