探讨 C++ 虚函数 virtual

点击蓝字

31f2dc1ad61f3545e7f7b4468a1bb532.png

关注我们

来源于网络,侵删

有无虚函数的对比

C++ 中的虚函数用于解决动态多态问题,虚函数的作用是允许在派生类中重新定义与积累同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

首先写两个简单的类,类 B 继承自类 A,即 A 是基类,B 是派生类。

class A{
public:void print(){cout << "A" << endl;}
};class B : public A {
public:void print(){cout << "B" << endl;}
};int main()
{B b;    //创建一个 B 类对象 b;A &a = b;  //a 是 b 的一个 A 类引用;A *pa = &b; //pa 是一个指向 A 类对象的指针;a.print();pa->print();b.print();return 0;
}

程序中,A 类和 B 类均定义了一个同名函数 print ,但两个函数的功能不同,编译系统按照同名覆盖原则决定调用对象。

另外一点,引用的本质是指针常量,可以认为 a,pa 都指向了 b。( 注意区分常量指针与指针常量,常量指针可以类比于整型指针,即指向一个常量的指针,指针的指向可以修改;指针常量类比于整型常量,即一个指针是个常量,也就是指针只能固定的指向某一单元,指针常量的指向不可改而指向的值可以修改。)

int a, b;
int * const p1 = &a; //指针常量
const int *p2 = &b; //常量指针

执行函数后,我们发现结果为

44373233c75747a793cb8ea5e94719e4.png

因为 a 是 A 类的一个引用,所以 a 的 print( ) 依旧是 A 类的成员函数;pa 是 A 类的指针,同理;而 b 是 B 类的对象,调用的 print( ) 为 B 类的成员函数。简言之就是,没有 virtual 时,调用哪一类的成员函数取决于调用对象 a ,pa,b 在定义时的类型。而此时,若 B 类对象 b 想调用直接基类 A 的 print 函数,则应当 b.A::print( )。

这种 a,pa,b 能调用哪个同名函数在对象定义时已经确定好了的多态,我们称之为静态多态。什么是多态?同一个 print 函数在不同的对象中有不同的作用,这就呈现了多态。

这里再提一点,原本基类指针是用来指向基类对象的,如果用它指向派生类对象,此时基类指针指向的是派生类对象中的基类部分。在没有虚函数时,基类指针是无法调用派生类对象中的成员函数的。

而当我们在 A 类中 print( ) 前加上关键字 virtual,变成虚函数时

class A{
public:virtual void print(){cout << "A" << endl;}
};class B : public A{
public:void print(){cout << "B" << endl;}
};

再次执行主函数,结果为

10177c19b687e72df5a5defd2758a194.png

这是因为 virtual 跟着对象走,即调用的 print( ) 究竟是 A 类还是 B 类的成员函数取决于“ 调用者 ” a,pa 所指的对象 b 属于哪一类,而不再是取决于 a,pa 本身在定义时的类型了。

这种用基类指针或引用指向某一派生类对象,从而能够调用指针指向的派生类对象中的函数的多态,我们称为动态多态,virtual 正是实现动态多态的关键字。

虚函数表

接着刚才的话题,在 A 类中有虚函数的前提下,我们继续讨论

class C{
public:void print(){cout << "C" << endl;}
};int main()
{cout << "sizeof(A): " << sizeof(A) << endl;cout << "sizeof(B): " << sizeof(B) << endl;cout << "sizeof(C): " << sizeof(C) << endl;A a;B b;C c;cout << "sizeof(a): " << sizeof(a) << endl;cout << "sizeof(b): " << sizeof(b) << endl;cout << "sizeof(c): " << sizeof(c) << endl;return 0;
}

执行结果为

f4cf800fca7beea558507505977d48bb.png

为什么有虚函数的 A 类大小为 8 字节,继承 A 的 B 为 8 字节,而没有虚函数的 C 类是 1 字节呢?联想到 64 位操作系统下指针占8个字节内存,而 A 大小也是 8 字节,是巧合吗?事实上,在包含虚函数的类中,在该类的存储空间中,会有一个指向虚函数表的指针,正是这个指针使 A 的大小变为 8 字节。而指针所指的虚函数表本质上是 A 类中定义的所有虚函数名构成的列表。A 中只定义了一个虚函数 print( ) ,所以虚函数表中也只有一个虚函数名 print,通过这个虚函数名,再找到整个虚函数 print( ) 在内存中的存储位置 ( B 同理 ) 。

验证虚函数表的存在性

虚函数表看不见摸不着,怎么确定它的存在呢?

int main()
{A a;B b;a.print();b.print();cout << "-------------------" << endl;typedef void (*func)();    //利用函数指针 func;((func**)(&a))[0][0]();    //((func**)(&a))[0] 代表对象 a 的内存空间中的第一个元素:指向虚函数表的指针;((func**)(&b))[0][0]();   //((func**)(&a))[0][0] 表示虚函数表中第一个函数名;return 0;
}

068e594280c1be9e776fc23ccb704d2a.png

从结果中我们可以发现,((func**)(&a))[0][0](); 等效于 a.print();,即确实证明了对象 a 的内存空间中存有一个指向虚函数表的指针,虚函数表的第一个函数名正是 print 。

6ee1e07cb34dc6200cd8d2d4e86c1d74.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

ccdb637c1491365baef392eb3c820440.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337349.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ STL详解(4)

点击蓝字关注我们来源于网络&#xff0c;侵删unordered_set / unordered_multiset这两个容器的方法与上一篇 C STL简介&#xff08;3&#xff09; 提到的 set / multiset 基本一样。但是要注意的是&#xff1a;这两个是无序的&#xff0c;基于哈希表实现的&#xff0c;增删改查…

js压缩图片_Web 性能优化: 图片优化让网站大小减少 62%

图像是web上提供的最基本的内容类型之一。他们说一张图片胜过千言万语。但是如果你不小心的话&#xff0c;图片大小有时高达几十兆。因此&#xff0c;虽然网络图像需要清晰明快&#xff0c;但它们尺寸可以缩小压缩的&#xff0c;使用加载时间保持在可接受的水平。在我的网站上&…

春春幼儿园堆积木大赛_春云边车

春春幼儿园堆积木大赛我有一个部署到基于NetflixOSS的云的应用程序&#xff0c;该应用程序具有以下结构&#xff1a; 本质上是一种将信息持久保存到Cassandra集群的服务。 所有应用程序都已注册到Eureka –因此&#xff0c;在本例中&#xff0c;该服务以及Cassandra节点都已在…

C++ 标准输入的行加速

点击蓝字关注我们来源于网络&#xff0c;侵删一、前言c/c 的标准输入函数&#xff0c;在输入一行数据&#xff0c;当数据量比较大的时候&#xff0c; cin 的效率是非常低的&#xff0c;为了加快读入的效率&#xff0c;可以改用 scanf&#xff0c;当然&#xff0c;还有一种利用 …

redis安装_Redis安装

redis安装本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

C++ 自动锁

点击蓝字关注我们来源于网络&#xff0c;侵删一、锁1.锁 是 多线程编程 中一个很常用的概念&#xff0c;这里不多加介绍其原理&#xff0c;有兴趣可以参考 临界区 进行更多的了解&#xff1b;2.锁 一般会提供三个接口&#xff1a;加锁&#xff08;Lock&#xff09;、解锁&#…

C++ 浮点数精度判定

点击蓝字关注我们来源于网络&#xff0c;侵删一、引例看下下面这段代码&#xff0c;会输出什么结果呢&#xff1f;double x 0;for (int i 0; i < 10; i) {x 0.1;}printf("%d\n", x 1);输出如下&#xff1a;0引起这种反差的原因就是浮点误差&#xff0c;浮点数…

字节流和字符流哪个不刷新_不喜欢节流吗?

字节流和字符流哪个不刷新您别无选择–底层系统&#xff08;此处的JVM将为您完成选择&#xff09;。 我仍然记得2013年夏天&#xff0c;当时我正在运行一个项目&#xff0c;整个应用程序中只有1个URL使服务器瘫痪。 问题很简单-机器人决定以很高的速率索引我们的网站&#xff…

C/C++动态内存管理—(new与malloc)

点击蓝字关注我们来源于网络&#xff0c;侵删1.C/C内存分布虚拟地址空间分布&#xff1a;由C/C编译的程序占用的内存分为以下几个部分&#xff1a;栈区&#xff08;stack&#xff09;— 由编译器自动分配释放 &#xff0c;存放为运行函数而分配的局部变量、函数参数、返回数据、…

C++ sort()排序详解

点击蓝字关注我们来源自网络&#xff0c;侵删一.sort()简介1.为什么选择使用sort()我们经常会碰到排序的问题&#xff0c;如果我们不使用一些排序的方法那我们只能手撕排序&#xff0c;这样就会浪费一些时间。而且我们还需要根据需要去选择相关的排序方法&#xff1a;冒泡排序、…

性能测试流程_流性能

性能测试流程当我阅读Angelika Langer的Java性能教程时-Java 8流有多快&#xff1f; 我简直不敢相信&#xff0c;对于一个特定的操作&#xff0c;它们花费的时间比循环要长15倍。 流媒体性能真的会那么糟糕吗&#xff1f; 我必须找出答案&#xff01; 巧合的是&#xff0c;我最…

C++vector用法总结

点击蓝字关注我们来源自网络&#xff0c;侵删一.vector1. vector 说明1&#xff09;vector是C标准模板库中的部分内容&#xff0c;它是一个多功能的&#xff0c;能够操作多种数据结构和算法的模板类和函数库。2.&#xff09;vector之所以被认为是一个容器&#xff0c;是因为它能…

C++ STL 线性容器的用法

点击蓝字关注我们来源于网络&#xff0c;侵删1.vectorvector 是顺序容器的一种&#xff0c;是可变长的动态数组&#xff0c;支持随机访问迭代器&#xff0c;所有stl算法都能对 vector 进行操作。vector 容器在实现时&#xff0c;动态分配的存储空间一般都大于存放元素所需的空间…

redis复制_Redis复制

redis复制本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

JavaWeb笔记之WEB项目

一. 版本控制 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理&#xff0c;是软件配置管理的核心思想之一。 版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变&a…

c++获取数组长度

点击蓝字关注我们来源于网络&#xff0c;侵删方法一&#xff1a; 用宏函数 #define#define foo(arr) sizeof(arr)/sizeof(arr[0])int main(){int arr[4] {1,2,3,4};cout<<foo(arr)<<endl; }方法二&#xff1a;用函数模板int getArrLen1(int *a ){return sizeof(a)…

C++ 利用硬件加速矩阵乘法

点击蓝字关注我们来源于网络&#xff0c;侵删1.矩阵乘法定义2.矩阵类封装我们用 C封装了一个n m 的矩阵类&#xff0c;用二维数组来存储数据&#xff0c;定义如下&#xff1a;#define MAXN 1000 #define LL __int64class Matrix { private:int n, m;LL** pkData; public:Matri…

redis分片_Redis分片

redis分片本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

解析C++全排列

点击蓝字关注我们来源于网络&#xff0c;侵删1.C实现全排列的函数next_permutation(start,end)这个函数在暴力解决问题方面有很大作用&#xff0c;使用时需要引入头文件 < algorithm >&#xff0c;当当前序列不存在下一个序列时就会结束&#xff0c;若想得到一个序列的全…

redis开启redis_Redis聚类

redis开启redis本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#…