C++之多态(中篇)(最全总结)

news/2024/11/20 8:24:19/

这里接上面C++之多态(上篇)

本篇目录

  • 4.多态的原理
    • 4.2 多态的原理
    • 4.3 C++ 11 override和final
    • 4.4 重载、重写(覆盖)、隐藏(重定义)的对比 (函数之间的关系)
  • 5.抽象类
    • 5.1概念
    • 5.2接口继承和实现继承
  • 6.单继承和多继承关系的虚函数表

4.多态的原理

4.2 多态的原理

从上面虚函数的分析中我们已经知道了多态的原理,接下来我们从更深层次去探索多态。

class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}virtual void Func1(){}
};class Student :public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}virtual void Func2(){}
};int main()
{//同一个类型的对象共用一个虚表Person p1;Person p2;//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个Student s1;Student s2;return 0;
}

在这里插入图片描述
通过上图我们发现,同一个类型的对象共用同一个虚表,vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个,除此之外,我们发现,vs的监视窗口下,子类自己的虚函数Func2(), 它是在子类自己的虚函数表中的,但是vs的监视窗口却没有显示出来,下面我们用一段程序将其展示出来。
虚表的本质是一个函数指针数组
在这里插入图片描述
在这里插入图片描述

通过上图我们发现对象虚表的地址在它对象的地址处的值取前4个字节即可。

class Person
{
public:virtual void BuyTicket(){cout << "Person::买票-全价" << endl;}virtual void Func1(){cout << "Person::Func1()" << endl;}
};class Student :public Person
{
public:virtual void BuyTicket(){cout << "Student::买票-半价" << endl;}virtual void Func2(){cout << "Student::Func2()" << endl;}
};typedef void(*VFPTR)();//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{//for (size_t i = 0; table[i] != nullptr; ++i)for (size_t i = 0; i < n; ++i){printf("vft[%d]:%p->", i, table[i]);//table[i]();VFPTR pf = table[i];//有点类似与强制类型转换pf();}cout << endl;
}int main()
{//同一个类型的对象共用一个虚表Person p1;Person p2;//vs下,不管是否完成重写,子类虚表跟父类虚表都不是同一个Student s1;Student s2;//取对象头部虚函数表指针传递过去//这里的2,3是我们知道对象的虚函数的个数PrintVFTable((VFPTR*)*(int*)&p1,2);PrintVFTable((VFPTR*)*(int*)&s1,3);return 0;
}

在这里插入图片描述

4.3 C++ 11 override和final

从上面可以看出,C++对函数的重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名的字母次序写反而无法构成重载,而这种错误在编译期间是不会报出来的,只有程序运行时没有得到预期结果才来debug会得不偿失,因此:C++ 11提供了override和final这两个关键字,可以用来帮助用户检测是否重写。

1.final:修饰虚函数,表示该虚函数不能被重写

class Car
{
public:virtual void Drive()final{}
};class Benz :public Car
{
public:virtual void Drive() //error{cout << "Benz-舒适" << endl;}
};

2.override:检查派生类虚函数是否重写基类某个虚函数,如果没有编译报错

class Car
{
public:virtual void Drive(){}
};class Benz :public Car
{
public:
//检查子类虚函数是否完成重写virtual void Drive()override{cout << "Benz-舒适" << endl;}
};

4.4 重载、重写(覆盖)、隐藏(重定义)的对比 (函数之间的关系)

在这里插入图片描述

5.抽象类

5.1概念

在虚函数的后面写上 =0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BwM:public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};int main()
{//Car c;//抽象类不能实例化对象//BwM b;Car* ptr = new BwM;ptr->Drive();//多态的体现ptr = new Benz;ptr->Drive();return 0;
}

在这里插入图片描述

5.2接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

6.单继承和多继承关系的虚函数表

需要注意的是在单继承和多继承关系中, 下面我们去关注的是派生类对象的虚表模型,因为基类的虚表模型前面我们已经看过了,没什么需要特别研究的。

class Base1
{
public:virtual void func1(){cout << "Base1::func1" << endl;}virtual void func2(){cout << "Base1::func2" << endl;}
private:int b1 = 1;
};class Base2
{
public:virtual void func1(){cout << "Base2::func1" << endl;}virtual void func2(){cout << "Base2::func2" << endl;}
private:int b2 = 2;
};class Derive :public Base1, public Base2
{
public:virtual void func1(){cout << "Derive::func1" << endl;}virtual void func3(){cout << "Derive::func3" << endl;}
private:int d = 3;
};typedef void(*VFPTR)();//void PrintVFTable(VFPTR table[])//打印虚函数表
void PrintVFTable(VFPTR* table,size_t n)//打印虚函数表中的虚函数地址并且调用虚函数
{//for (size_t i = 0; table[i] != nullptr; ++i)for (size_t i = 0; i < n; ++i){printf("vft[%d]:%p->", i, table[i]);table[i]();VFPTR pf = table[i];//有点类似与强制类型转换pf();}cout << endl;
}int main()
{Derive d;PrintVFTable((VFPTR*)*(int*)&d,3);//打印Base1虚函数表PrintVFTable((VFPTR*)*(int*)((char*)&d + sizeof(Base1)), 2);//法一:打印Base2虚函数表//法二:打印Base2虚函数表Base2* ptr2 = &d;//切片得到Base2的地址PrintVFTable((VFPTR*)(*(int*)ptr2), 2);return 0;
}

在这里插入图片描述
在这里插入图片描述
更深层次的问题
观察上图我们发现Base1中的func1和Base2中的func1都被Derive进行了重写,它们的内容是一样的,应该指向同一份函数,但是它们的地址为什么不一样呢?

下篇文章我们会揭晓!!!


http://www.ppmy.cn/news/5509.html

相关文章

springDataRedis快速入门

SpringData是Spring中数据操作的模块&#xff0c;包含对各种数据库的集成&#xff0c;其中对Redis的集成模块就叫做SpringDataRedis&#xff0c; 官网地址:https://spring.io/projects/spring-data-redis 提供了对不同Redis客户端的整合( Lettuce和Jedis)提供了RedisTemplate…

简单Thinkphp5.1如何使用Topsdk\Topapi

一淘模板&#xff08;56admin.cn&#xff09;给大家介绍tp5.1相关知识&#xff0c;其中主要记录tp5.1是怎么使用Topsdk\Topapi&#xff08;对接淘宝客开放平台&#xff09;&#xff0c;希望对需要的朋友有所帮助&#xff01; 1、公司有一项目需要对接淘宝开放平台 先去申请帐号…

第十九章 使用系统监视器 - 配置系统监视器命名空间

文章目录第十九章 使用系统监视器 - 配置系统监视器命名空间配置系统监视器命名空间查看系统监视器状态管理应用程序监视器管理健康监视器查看系统数据第十九章 使用系统监视器 - 配置系统监视器命名空间 配置系统监视器命名空间 当一个实例启动时&#xff0c;系统监视器会在…

1362:家庭问题(family)

1362&#xff1a;家庭问题(family) 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 6732 通过数: 3529 【题目描述】 有n个人&#xff0c;编号为1,2,……n&#xff0c;另外还知道存在K个关系。一个关系的表达为二元组&#xff08;α&#xff0c;β&#xff09;形式…

vue3项目打包部署到Tomcat(亲测有效)

首先&#xff0c;要确保电脑上已经安装了jdk&#xff0c;还有Tomcat&#xff0c;而且都安装正确。 jdk下载与安装教程&#xff08;win10&#xff09; Tomcat 9.0 安装及配置教程(win10系统) Vue项目在VScode里面可以通过npm run serve可以正常运行。 下面是打包部署到tomca…

22年D2部分干货

缘起 小编入行以来&#xff0c;一直兢兢业业&#xff0c;跟随大佬们脚步&#xff0c;学习如何努力成为优秀的开发。机缘巧合&#xff0c;在2017年小编接触到了前端峰会——D2&#xff0c;里面好多大佬聊未来的趋势。记得疫情之前&#xff0c;D2采用的是线下邀请&#xff0c;可惜…

浅谈安科瑞电能预付费系统在大电力客户中的设计及应用分析

摘 要 随着我国供电企业的不断发展&#xff0c;而用电模式也在不断改革&#xff0c;预付费技术在气、电等部门得到普遍的使用&#xff0c;本文主要针对预付费系统在大电力客户中的使用情况进行分析&#xff0c;提高用电用户的缴费率&#xff0c;有效的避免了客户恶意偷窃电行…

35. 搜索插入位置

给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], target 5 输出: 2示例 2: 输入:…