C++_虚继承_虚函数_纯虚函数(多继承的二义性,多态)

基本信息

每一个类都有一个虚表,以及虚表指针; 虚表的内容是编译器决定的,虚表中用于存放虚函数的指针, 程序运行时的类型信息等;
每个多态对象都存放着一个指向当前类型的虚表的指针, 该指针在构造函数中被赋值, 一般来说当调用当前这个类的构造函数, 则虚表指针就指向当前类的虚表

虚继承

用于解决多重继承的过程中成员访问的二义性(菱形继承)
格式:class 类名 : virtual 继承方式 基类类名
注:在虚继承的过程中,编译器会为子类创建一个虚表,以及一个虚基表指针(占用对象空间)指向虚表(不占用对象空间)
例:

#include<iostream>
using namespace std;
class A{private:int a;public:A(){cout<<"A()"<<endl;}~A(){cout<<"~A()"<<endl;}
};
class B:virtual public A{private:int b;public:B(){cout<<"B()"<<endl;}~B(){cout<<"~B()"<<endl;}
};
class C:virtual public A{private:int c;public:C(){cout<<"C()"<<endl;}~C(){cout<<"~C()"<<endl;}
};
class D:public B,public C{private:int d;public:D(){cout<<"D()"<<endl;}~D(){cout<<"~D()"<<endl;}
};
int main(){//64位虚表指针为8个字节
cout<<sizeof(A)<<endl; //输出4
cout<<sizeof(B)<<endl;//输出  B本身(4)+A(4)+虚表指针(8)=16
cout<<sizeof(C)<<endl;//输出与B相同
cout<<sizeof(D)<<endl;//输出 B+C+D本身-A(一份的A)=32    

上述代码中: B, C虚继承于A, 他们二者”共享”一份A(用集合的方式理解,A属于B, C的交集), 当D继承B,C的时候只会保留一份A在D中
若未采用虚继承在D创建对象的时候,会创建2份的A,出现冗余, 使用虚继承继承基类的两个虚指针,并调整虚指针与虚基类首地址的偏移量,使得继承过程中只保留一份的A
当使用虚继承的过程中,虚基类被共享,无论继承多少次,虚表指针都只会指向一份的虚基类,对象模型中只会存有一份的虚基类对象

虚函数

在基类中使用virtual修饰的成员函数,当函数声明为虚函数时,告诉编译器不要静态链接到该函数,而是根据程序的运行过程中动态地根据该对象类型来调用函数, 就所谓的多态
注:当基类成员函数声明为virtual, 子类进行对virtual重写,那么重写后的函数都为虚函数(即使该函数前面没写virtual关键字); 虚函数成员的virtual关键字只能出现在类中定义的函数原型前面, 不能出现在类外成员函数实现的前面
虚函数一般不声明为inline函数, inline函数属于静态绑定, 而虚函数的调用是动态绑定, 如果将虚函数作为inline函数也不会出错
构造函数, 静态函数, 复制构造函数不可做为虚函数, 原因只在于虚函数为动态绑定;

#include <iostream> 
using namespace std;
class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}virtual  int area(){//编译时,生成一个虚表以及虚表指针cout << "Parent class area :" <<endl;return 0;}
};
class Rectangle: public Shape{public:Rectangle( int a=0, int b=0):Shape(a, b) { }virtual int area () {//为虚函数,即使没有关键字virtualcout << "Rectangle class area :" <<endl;return (width * height); }
};
class Triangle: public Shape{public:Triangle( int a=0, int b=0):Shape(a, b) { }virtual int area (){cout << "Triangle class area :" <<endl;return (width * height / 2); }
};
int main( )
{Shape *shape;Rectangle rec(10,7);Triangle  tri(10,5);shape = &rec;shape->area(); // 调用矩形的求面积函数 areashape = &tri;// 存储三角形的地址shape->area(); // 调用三角形的求面积函数 areareturn 0;
}

此时,编译器看的是指针的内容(决定了能够调用哪些虚函数),它指向的对象类型决定了该调用谁的虚函数。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

动态绑定的底层实现:
虚表指针需要初始化才能调用虚函数,虚表指针在构造对象的时候初始化(初始化顺序与构造函数的调用顺序一致),当构造函数发现BASE具有虚函数,虚指针指向BASE的虚表中的虚函数,当执行构造子类对象的时候,子类中虚指针指向子类虚表中的虚函数;当对象创建好后,虚表指针指向的是子类的虚函数,从而对象调用虚函数时,实现多态

注:虚表会被继承,当子类重写虚函数的时候,那么虚表中的虚函数地址则会改变
在动态分配内存的时候,析构函数必须是虚函数(利用动态绑定)防止不会调用所需的析构函数
使用虚函数意味着多态,多态必须具备的三个条件:继承关系; 继承的过程中必须有同名的虚函数; 存在基类的指针或引用,通过该指针或引用调用虚函数

虚析构函数
析构函数可作为虚函数, 方便父类指针知道该调用哪个子类的析构函数(析构函数的多态)
一般情况下, 如果涉及到多态, 则将析构函数设置为virtual

#include<iostream>
using namespace std;
class BASE{public:~BASE(){cout<<"父类析构函数"<<endl;}
};
class Derive:public BASE{public:Derive(){} ~Derive(){cout<<"子类析构函数"<<endl;}
};
int main(){BASE *b=new Derive();delete b;return 0;
} 

//上述代码输出:父类析构函数,由于BASE指针偏移量的问题,未将父类析构函数设置为虚析构函数,导致静态绑定只会释放父类内存区,不会释放子类内存区,导致内存泄漏;
为了避免这种错误应将父类,子类的析构函数设置为virtual,则会解决这个问题
注: 子类对象析构函数的调用顺序,先调用子类析构函数, 然后调用父类析构函数, 调用顺序与构造函数调用顺序相反

纯虚函数 (抽象类)

在基类重定义纯虚函数,以便在派生类中重新定义该函数来适用于对象,纯虚函数就相当于接口,用于规范派生类行为
包含纯虚函数的类是抽象类,不能实例化, 当子类继承抽象类时,若没有实现纯虚函数, 则子类还是抽象类
语法: virtual void function()=0;
等于0表示没有函数体,

class Shape {protected:int width, height;public:Shape( int a=0, int b=0){width = a;height = b;}// pure virtual functionvirtual int area() = 0;
};

注: C++中,父类中的纯虚函可以有实现方式, 但是编译器会忽略, 父类依旧为抽象类, 由于抽象类不能实例化, 但是可以定义指针或引用, 通过指针和引用依旧能实现多态, 与Java中抽象类实现多态的方式一致
总结:

  • 虚继承: 虚表基表, 虚基指针主要用来记录偏移量(虚基指针在虚基表上),以保证多继承的过程中只复制一份的基类
  • 虚函数: 虚表,虚指针主要用于指向虚函数(虚指针指向虚表中的虚函数地址)
  • 纯虚函数: 就是接口, 规范子类行为

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

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

相关文章

数组的合并和升序排列_leetcode 33 搜索旋转排序数组

给你一个升序排列的整数数组 nums &#xff0c;和一个整数 target 。假设按照升序排序的数组在预先未知的某个点上进行了旋转。&#xff08;例如&#xff0c;数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] &#xff09;。请你在数组中搜索 target &#xff0c;如果数组中存在…

[LeetCode] [C++] 206 Reverse Linked List 反转单项链表

题目要求 Reverse a singly linked list.LeetCode 206在线测试 问题描述 给定一个单项链表&#xff0c;将其反转后返回链表头节点。 思路分析1 可以完整的遍历一遍链表&#xff0c;将链表的每个节点的值存在数组中&#xff0c;然后反向遍历数组重新生存一个新 链表。这样做需要…

defconfig、 .config

最近在裁剪内核慢慢关注这个问题 defconfig、 .config defconfig 一般在arch/arm64/configs/目录下&#xff0c;是一个简单的内核配置&#xff0c;是没有展开的。 .config一般是用来执行make menuconfig的基础配置 从.config到defcong不是简单的复制操作&#xff0c;而是mak…

递归过程中语句执行顺序

递归的两种模式 模式一 //递归的过程中在"递"的过程中解决问题 function function_name(Max_argument){if(end_condition){end;}else{solve;function_name(Min_argument);//问题规模逐渐减小} } 注:位于递归函数前的语句和函数具有顺序性 模式二 //递归的过程中…

qq面板(仿版,未完待续中。。。。)---2017-04-24

主要实现效果&#xff1a; 1、点击对话&#xff0c;显示对话&#xff1b;点击联系人&#xff0c;显示联系人 2、在联系人界面&#xff1a; 实现好友列表的展开与折叠&#xff1b;&#xff08;图12&#xff09; 实现鼠标移到好友列表上的背景颜色的变化&#xff1b;&#xff08;…

苹果企业证书_苹果签名经常掉签原因大汇总

苹果签名就是数字签名&#xff0c;是基于非对称加密算法来实现的&#xff0c;对称加密就是通过非对称加密算法实现的&#xff0c;对称加密是通过同一份秘钥加密解密数据&#xff0c;非对称加密有两份秘钥&#xff0c;分别是公钥和私钥&#xff0c;用公钥进行加密的数据只能使用…

漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?

1. 锁的由来&#xff1f;学习linux的时候&#xff0c;肯定会遇到各种和锁相关的知识&#xff0c;有时候自己学好了一点&#xff0c;感觉半桶水的自己已经可以华山论剑了&#xff0c;又突然冒出一个新的知识点&#xff0c;我看到新知识点的时候&#xff0c;有时间也是一脸的懵逼…

C++_顺序容器

顺序容器类型 顺序容器 vector: 支持快速随机访问list: 支持快速插入与删除deque: 双端队列 顺序适配器 - stack: 后进先出(LIFO)堆栈 - queue: 先进先出(FIFO)队列 - priority_queue: 有优先级管理的队列 上述顺序容器包含于以下头文件中:< vector >,< list &…

python画tan_Python入门之三角函数tan()函数实例详解

描述tan() 返回x弧度的正弦值。语法以下是 tan() 方法的语法:import mathmath.tan(x)注意&#xff1a;tan()是不能直接访问的&#xff0c;需要导入 math 模块&#xff0c;然后通过 math 静态对象调用该方法。参数x -- 一个数值。返回值返回x弧度的正弦值&#xff0c;数值在 -1 …

课程作业一

由于代码的难点部分是王源写的。。所以开始我选择了重写。。但是重写好像比我想象的复杂太多&#xff0c;加上时间分配不够所以现在还没有完成。。先提交随笔写好后补上代码。。我道歉认罚。。把这次当成一个教训。。 转载于:https://www.cnblogs.com/daydreams/p/6759372.html…

不废话~就是抽奖~

不废话就是抽奖 在公众号回复-抽奖- 获取抽奖二维码参与抽奖

C++关联容器总结一

关联容器 关联容器: 通过键(key)储存与读取元素 顺序容器: 通过元素在容器中的位置顺序储存,访问 关联容器类型 map 关联数组&#xff1a;元素通过键来存储和读取 set 大小可变的集合&#xff0c;支持通过键实现的快速读取, 具有集合的性质 multimap 支持同一个键多次出现…

威纶触摸屏与电脑连接_PLC与这7种设备的连接方式,一看就懂!

点击上方电工小青年&#xff0c;关注并星标专业的电工电气领域自媒体&#xff0c;不容错过欢迎转发朋友圈~PLC常见的输入设备有按钮、行程开关、接近开关、转换开关、拨码器、各种传感器等&#xff0c;输出设备有继电器、接触器、电磁阀等。正确地连接输入和输出电路&#xff0…

okHttp源码解析------待续

看该篇文章前首先要熟悉okHttp的使用&#xff0c;建议先读OkHttp的简单使用 本文的源码解析参考链接&#xff1a;okhttp3总和解析 1.从URL请求处理开始分析 由异步将请求加入调度方法开始引入正题&#xff1a; getClient().newCall(request).enqueue(new Callback() {Overridep…

Android studio JNI jni实例

1.Jni的作用 1.Jni的作用 Java是一种比较高级的语言&#xff0c;Java调用c库&#xff0c;调用c库是必不可少的&#xff0c;所以Jni就应运而生了。看了这个文章的同学&#xff0c;应该能够自己写个APK装在自己的手机里面吧&#xff0c;以前刚开始做android的时候&#xff0c;写…

二叉树先序遍历,中序遍历,后序遍历,层次遍历学习总结及完整C/C++代码

伪代码阐述 先序遍历 先序遍历:先访问根节点, 然后深入左子树,直到不能深入时再深入右子树 由定义可得递归式 void travPre_R(BinNodePosi* x,VISIT& visit){if(!X) return; //到达叶子节点,开始回归visit(x->data);//向左子树深入的过程中便开始进行对每个节点的数据…

nedc工况_东南DX3 EV续航升级 NEDC综合工况续航451公里

东南DX3 EV续航升级版车型曝光&#xff0c;电池系统能量密度由之前的141Wh/kg提升至了161Wh/kg&#xff0c;NEDC综合工况续航也由老款产品的351km提升至了451km。近日&#xff0c;工信部公布了2019年第7批《新能源汽车推广应用推荐车型目录》&#xff0c;东南DX3 EV续航升级版车…

不笑找我系列 | 程序员爆笑漫画十条

原创翻译~ 转载请说明出处~~~~~~~~ 1、如果你让码农给你做个事情&#xff0c;比如修个灯泡&#xff0c;他会这样去执行你的指令 2、分享一个码农发现并解决bug的过程&#xff0c;实在是符合我们码农的人设 3、码农的一天&#xff0c;像极了我的一天 4、至今为止&#xff0c;没…

AVL树学习总结

AVL树 平衡二叉树的缺点 由于平衡二叉搜索树的search(), insert(),remove()接口的运行时间与二叉树的高度成正比,所以若不能有效控制树高, 从平均复杂度来看,二叉平衡搜索树并不能让人满意 理想平衡 二叉树的性能取决于树的高度,只有当左右子树的高度接近时才能达到理想平衡…

nginx编译安装_Nginx编译安装nginx-upsync-module模块以实现动态负载

安装依赖包OpenSSL在官网下载页下到最新稳定版1.0.2q。PCRE在 PCRE 官网可以找到下载地址&#xff0c;这里选择8.x的最高版本 pcre-8.42.tar.gz。zlibzlib 直接选择官网首页最新的zlib-1.2.11.tar.gz。下载nginx 源码包及nginx-upsync-module模块源码这里下载的是nginx稳定版ng…