C++进阶 —— 多态

学习C++怎么进阶? C++是目前编程语言中学习成本比较高的语言之一,C++在图形、音视频处理,通信,游戏,后端服务方面应用还比较广泛,我认为需要C++学习进阶有如下几个步骤: 1、看一下C++方面的编程书籍 1)《C++Primer》,《C++编程思想》这初级读本是所有学习C++的人必须啃的书,C++的知识点比较全。 2)《Effective C++》、《MoreEffective C++》、《C++... 阅读详情

目录

一,多态及虚函数

构成多态条件

虚函数

override和final(C++11)

重载、重写(覆盖)、隐藏(重定义)对比

二,抽象类

接口继承和实现继承

三,多态原理

静态绑定和动态绑定

四,单继承和多继承关系的虚函数表

单继承中虚函数表

多继承中虚函数表

菱形继承、菱形虚拟继承


一,多态及虚函数

  • 多态是继承关系中的不同类对象,通过基类的指针或引用调用基类的同一函数,产生了不同的行为;

构成多态条件

  • 必须通过基类的指针或引用调用虚函数;
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写;

虚函数

  • 即被virtual修饰的类成员函数,称为虚函数;

虚函数重写(覆盖)

  • 派生类中有一个跟基类完全相同的虚函数,称子类虚函数重写了基类的虚函数;
  • 派生类虚函数与基类虚函数的返回值类型、函数名、参数列表完全相同;
class Person
{
public:
	virtual void BuyTicket() {cout << "No discount!" << endl;}
};

class Stu :public Person
{
public:
	virtual void BuyTicket() {cout << "Half discount!" << endl;}
};

void Func(Person& person) {person.BuyTicket();}

int main()
{
	Person person;
	Stu stu;
	Func(person);
	Func(stu);
	return 0;
}

注:重写基类虚函数时,派生类的虚函数可不加virtual关键字,虽然也构成重写(因继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但这种写法不规范,不建议使用;

虚函数重写的两个例外

  • 协变 covariant,基类与派生类虚函数返回值类型不同,即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或引用;
  • 析构函数的重写,基类与派生类析构函数的名字不同;如基类析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类析构函数构成重写;虽然函数名不同,违背重写规则,可理解为编译器对析构函数的名称做了特殊处理;
    • 如没有虚析构,可能存在内存泄漏!
//协变
class A{};
class B:public A{};

class Person
{
public:
	virtual A* BuyTicket()
	{
		cout << "No discount!" << endl;
		return new A;
	}
};

class Stu :public Person
{
public:
	virtual B* BuyTicket()
	{
		cout << "Half discount!" << endl;
		return new B;
	}
};

void Func(Person& person) {person.BuyTicket();}

int main()
{
	Person person;
	Stu stu;
	Func(person);
	Func(stu);
	return 0;
}
//析构函数重载
class Person
{
public:
	virtual ~Person() {cout << "~Person()" << endl;}
};

class Stu :public Person
{
public:
	virtual ~Stu() {cout << "~Stu()" << endl;}
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Stu;
	delete p1;
	delete p2; //先~Stu()在~Person(),如没有虚析构函数,只调用基类的析构函数!内存泄漏!
	return 0;
}

override和final(C++11)

        C++对函数重写的要求比较严格,如函数名字母次序写反即无法构成重载,而这种错误在编译期间无法识别,只有在程序运行时没有得到预期结果才会发现得不偿失;因此C++11提供了关键字override和final,可帮助检测是否重写了;

final

  • 修饰类,防止子类继承该类;
  • 修饰虚函数,表示该虚函数不能在被重写;
class Car final{};
//不可将final类作为基类
class Benz :public Car {};
class Car
{
public:
	virtual void Drive() final {cout << "Car" << endl;}
};

class Benz :public Car
{
public:
    //报错,没有重写基类成员,基类中不存在Dri虚函数
	virtual void Drive() {cout << "Benz" << endl;}
};

override

  • 检查派生类虚函数是否重写了基类某个虚函数,如没有编译报错;
class Car
{
public:
	virtual void Drive() {cout << "Car" << endl;}
};

class Benz :public Car
{
public:
    //报错,没有重写基类成员
	virtual void Dri() override {cout << "Benz" << endl;}
};

重载、重写(覆盖)、隐藏(重定义)对比

重载

  • 两个函数在同一作用域;
  • 函数名相同和参数不同;
class fatherA
{
public:
	void print() { cout << "->" << " fatherA " << endl; }
	void print(int) { cout << "->" << " fatherA(int) " << endl; }
};

重写(覆盖)

  • 两个函数分别在基类和派生类的作用域;
  • 函数名、参数、返回值都必须相同(协变例外);
  • 两个函数都必须为虚函数;
  • 实现运行时多态性;
class fatherA
{
public:
	virtual void print() { cout << "->" << " fatherA " << endl; }
};
class childB :public fatherA
{
public:
	virtual void print() { cout << "->" << " childB " << endl; }
};

 隐藏(重定义):

  • 两个函数分别在基类和派生类作用域;
  • 函数名相同;
  • 两个基类和派生类的同名函数不构成重写就构成重定义;
class fatherA
{
public:
	void print() { cout << "->" << " fatherA " << endl; }
};
class childB :public fatherA
{
public:
	void print() { cout << "->" << " childB " << endl; }
};

  

二,抽象类

概念:

  • 在虚函数的后面添加上“= 0”,则该函数为纯虚函数;
  • 包含纯虚函数的类,称为抽象类或接口类,抽象类不能实例化对象;
  • 派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象;
  • 纯虚函数规范了派生类必须重写,另外纯虚函数体现了接口继承;

接口继承和实现继承

  • 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现;
  • 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口;所以如不需实现多态,不要把函数定义成虚函数;
class Car
{
public:
	virtual void Drive() = 0; //纯虚函数
};

class Benz :public Car
{
public:
	virtual void Drive() {cout << "Benz" << endl;}
};

class BMW :public Car
{
public:
	virtual void Drive() {cout << "BMW" << endl;}
};

void fun(Car& car) {car.Drive();}

int main()
{
	//Car car; //抽象类不可实例化对象
	Benz benz;
	BMW bmw;
	fun(benz);
	fun(bmw);

	Car* pbenz = new Benz;
	Car* pbmw = new BMW;
	pbenz->Drive();
	pbmw->Drive();
	return 0;
}

三,多态原理

class Car
{
public:
	virtual void fun() { cout << "fun()" << endl; }
protected:
	int _car;
};

int main()
{
	Car car;
	cout << sizeof(car) << endl; //结果为8
	return 0;
}

   

         上述案例结果为8,是因为除了成员变量_car,还有一个成员 _vfptr(在_car前面还是后面和平台有关),对象中的成员_vfptr为指针类型,叫做虚函数表指针;一个包含虚函数的类中,至少有一个虚函数表指针;虚函数表简称虚表,是存放该类所有虚函数地址的数组;

class Car
{
public:
	virtual void fun1() { cout << "fun1()" << endl; }
	virtual void fun2() { cout << "fun2()" << endl; }
	void fun3() { cout << "fun3()" << endl; }
protected:
	int _car;
};

class Benz :public Car
{
public:
	virtual void fun1() { cout << "Benz::fun1()" << endl; }
protected:
	int _benz;
};

int main()
{
	Car car;
	Benz benz;
	return 0;
}

  

  • 派生类对象benz,也有一个虚表指针,是继承于父类的;
  • 基类对象car和派生类对象benz虚表是不一样的,发现fun1重写了,所以虚函数的重写也叫覆盖,即指虚表中虚函数的覆盖;重写是语法上的叫法,覆盖是原理上的叫法;
  • 虚函数fun2继承下来了,放进了虚表,fun3不是虚函数,就不会放进虚表;
  • 虚函数表,本质上是存放虚函数指针的指针数组,一般情况下这个数组最后面放一个nullptr;
  • 派生类虚表生成,首先将基类中的虚表内容拷贝一份到派生类虚表中,然后派生类重写基类中的虚函数,用派生类自己的虚函数覆盖基类中的虚函数,最后派生类自己新增的虚函数按其在派生类中的声明次序增加到派生类虚表的最后;
  • 注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样,存在代码段,只是它的的指针又存到了虚表中,另外对象中存的不是虚表,存的是虚表指针;虚表存在代码段(vs平台);

满足多态以后的函数调用,是运行后到对象中去找的;

不满足多态的函数调用,是编译时就确认好的;

静态绑定和动态绑定

  • 静态绑定,又称前期绑定(早绑定),在程序编译期间确定了程序的行为,也称静态多态,如函数重载、运算符重载、模板;
  • 动态绑定,又称后期绑定(晚绑定),在程序运行时,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称动态多态;

四,单继承和多继承关系的虚函数表

单继承中虚函数表

class Car
{
public:
	virtual void fun1() { cout << "fun1()" << endl; }
	virtual void fun2() { cout << "fun2()" << endl; }
protected:
	int _car;
};

class Benz :public Car
{
public:
	virtual void fun1() { cout << "Benz::fun1()" << endl; }
	virtual void fun3() { cout << "Benz::fun3()" << endl; }
	virtual void fun4() { cout << "Benz::fun4()" << endl; }
protected:
	int _benz;
};

typedef void(*VFPTR)(); //函数指针
void PrintVTable(VFPTR vtable[])
{
	cout << "虚表地址->" << vtable << endl;
	for (int i = 0; vtable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址: 0X%x->", i, vtable[i]);
		VFPTR f = vtable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Car car;
	Benz benz;
    
    //思路:取对象头4个字节,即是虚表指针,虚表是存虚函数指针的指针数组,最后为nullptr;
    //运行时可能会崩溃,是因为编译器对虚表处理不干净,编译器问题,在点击菜单栏生成-清理解决方案;
	VFPTR* vtable_car = (VFPTR*)(*(int*)&car);
	PrintVTable(vtable_car);
	VFPTR* vtable_benz = (VFPTR*)(*(int*)&benz);
	PrintVTable(vtable_benz);
	return 0;
}

多继承中虚函数表

  • 多继承派生类的未重写的虚函数,放在第一个继承基类的虚函数表中;
class Car1
{
public:
	virtual void fun1() { cout << "Car1::fun1()" << endl; }
	virtual void fun2() { cout << "Car1::fun2()" << endl; }
protected:
	int _car1;
};

class Car2
{
public:
	virtual void fun1() { cout << "Car2::fun1()" << endl; }
	virtual void fun2() { cout << "Car2::fun2()" << endl; }
protected:
	int _car2;
};

class Benz :public Car1, public Car2
{
public:
	virtual void fun1() { cout << "Benz::fun1()" << endl; }
	virtual void fun3() { cout << "Benz::fun3()" << endl; }
protected:
	int _benz;
};

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vtable[])
{
	cout << "虚表地址->" << vtable << endl;
	for (int i = 0; vtable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址: 0X%x->", i, vtable[i]);
		VFPTR f = vtable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Benz benz;

	VFPTR* vtable_benz_car1 = (VFPTR*)(*(int*)&benz);
	PrintVTable(vtable_benz_car1);
	VFPTR* vtable_benz_car2 = (VFPTR*)(*(int*)((char*)&benz+sizeof(Car1)));
	PrintVTable(vtable_benz_car2);
	return 0;
}

菱形继承、菱形虚拟继承

c++虚函数详解 某基类中声明为virtual并在一个或多个派生类中重新定义的成员函数叫做虚函数。用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”。实现动态联编,在函数运行阶段动态的选择合适的成员函数。 在定义了虚函数后,可实现在派生类中对虚函数进行重写,从而实现统一的接口和不同的执行过程。... 阅读详情

相关推荐

C++从入门到进阶的系列书籍推荐(22本书)

要多读书,读好书!一本书要多读几遍才好,每次都可能有新的收获!特别在是有一定的工作年限和工作经验之后!读书百遍,其义自见! 下面就给大家推荐一下C++程序员进阶需要读的经典书籍: 1、C++入门书籍 C++是在C语言的基础上发展而来的,要学习C++之前需要先学习C语言。有了C语言基础之后再去学习C++。 1.1、C语言程序设计(谭浩强) 该书被公认为学习C语言程序设计的优秀教材,被全国大多数高校选用,是学习C语言的主流用书。本书比较通俗易懂,是初入者必备的书籍。 ........

dvlinker的技术专栏 4万+

C++虚函数详解

1.虚函数的使用? 1.1虚函数的定义 在实现c++多态时会用到虚函数。虚函数使用的其核心目的是通过基类访问派生类定义的函数。所谓虚函数就是在基类定义一个未实现的函数名,为了提高程序的可读性,建议后代中虚函数都加上virtual关键字。一般格式: class base { public: base(); virtual void test(); //定义的一个虚函数 private: char *basePStr; }; 上述代码在基类中定义了一个test的虚函数,所有可以在其子类重新定义父

qq_42048450的博客 14万+

AD从原理图到PCB超详细教程

我在学习AD的时候,看了网上很多教程,但是东西很杂,有很多条理不清而且废话很多,所以相信大家学习的时候也是遇到一样的困难。我就特地写一篇帖子,整理了一下AD从画原理图开始一直到画完PCB文件的所有步骤,一方面是供大家参考学习,另一方面可以方便我自己学习记忆更加深刻。我用的版本是AD16中文版,大家用新的版本AD20或者AD22应该操作都是差不多的。

人生寂寞如雪 18万+

任鸟飞逆向C++进阶篇

【课程简介】本课程为任鸟飞逆向C++进阶篇,注重基础理论的务实和简单功能的实现,学员学成后,可独立判断、分析和解决逆向问题、独立编写通用辅助。本套课程不只是一套深入学习C++的课程,更是一套深入学习汇编逆向课程、外挂与反外挂、软件安全的课程。逆向思路技术随笔,请关注我的优快云博客:https://blog.youkuaiyun.com/qq_36553941 欢迎大家来学习交流,活动折扣等信息也在其第一时间发布。PS: 课程中工具包、源码、课件请关注第一章的第一节的随课课件,工欲望善其事,必先利其器! 【学前预备知识】任鸟飞逆向C++系列课程是一条精心安排为掌握汇编逆向、外挂与反外挂、软件安全的学习路线,包括但不限于 基础篇、进阶篇(本篇)、高级篇、实战篇等,所以为保证您能学有所成请在学习本篇之前务必掌握基础篇!!!基础篇优快云学院链接:https://edu.youkuaiyun.com/course/detail/30509 【学员学成收获】1、C++编程语言进阶。2、游戏逆向分析进阶。3、游戏安全对抗理论进阶。4、独立编写通用辅助。 

从ADS到SystemVue:当简单链路预算不够用时,我的射频系统级仿真方案升级实录

本文详细介绍了从ADS到SystemVue的射频系统级仿真升级路径,重点分析了基础链路预算在多端口元件和复杂场景下的局限性,并提供了SystemVue在非线性系统验证和跨域协同分析中的优势。通过实际案例展示了如何平滑迁移工具链,提升仿真精度和效率,适用于5G、毫米波雷达等现代射频系统设计。

weixin_34233974的博客 343

从零到一:手把手教你用STM32F103C8T6打造个人专属开发板(附立创EDA工程与源码)

本文详细介绍了如何使用STM32F103C8T6微控制器从零开始设计和制作个人专属开发板。内容涵盖硬件选型、原理图设计、PCB布局、功能验证等全流程,并提供了立创EDA工程文件和源码,帮助开发者深入理解STM32开发板的硬件架构与设计要点。

weixin_26802933的博客 285

C++之继承与多态(含模版进阶)

非类型模版参数,模版特化,继承,多态

2302_81120572的博客 1039

VScode利用EIDE和cortex-debug进行stm32开发(也适用51)

你是否因为keil页面太丑而烦恼,你是否因为keil编译太慢而恼怒,ok阿,今天也是给大家带来怎么在vscode上进行stm32的编程(我不会告诉你,我搞了好几天才搞好的💔💔💔)为什么是简单介绍?因为太啰嗦了,我嫌麻烦😋,大家可以去EIDE的官方文档去看。

weixin_73952614的博客 9382

【C++进阶——多态】

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。那么在继承1. 必须通过基类的指针或者引用调用虚函数2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写public:cout << "Person->全价" << endl;virtual void BuyTicket()//被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写cout << "Student->半价" << endl;//必须通过基类的指针或者引用调用虚函数int main()

m0_74303551的博客 1006

(1)大蚂蚁即时通讯

即时通讯软件BigAnt(大蚂蚁)是杭州艾朴软件推出的一个”企业级”的即时通讯平台。该平台专为企业,其他企事业单位和政府单位而设计,目的就是为了增强内部沟通能力,提高内部员工的工作效率;同时服务器就直接置于单位内部,所有信息由单位自己管理,让单位以更安全、更可伸缩和更易于管理的方式灵活方便地使用协同和通信服务的功能! BigAnt(大蚂蚁)即时通讯软件包括服务器端和客户端。客户端功能包括单位

少安的砖厂 2234

Windows 11下Dify安装部署实战指南

本文详细介绍在Windows 11环境下部署开源LLM应用开发平台Dify的完整流程。涵盖环境准备(WSL 2启用、Docker Desktop安装配置)、源码获取、Docker容器化部署、数据库初始化、服务启动验证等核心步骤。包括环境变量配置、性能优化、安全加固、端口冲突解决、数据库连接故障排查等关键技术要点。为开发者提供从零到一的Dify本地化部署实战指南

weixin_44269410的博客 851

C++进阶-多态

【C++进阶学习】C++中的多态零、前言一、多态的概念和定义二、虚函数1、概念和定义2、虚函数重写的特例3、C++11 override 和 final4、重载/重写/重定义对比三、抽象类四、多态的原理1、虚函数表2、多态的原理3、动态绑定与静态绑定4、多继承虚函数表五、继承和多态常见的面试问题 零、前言 C++有五大特性:对象,封装,继承,抽象和多态。而本章则将学习讲解C++中的多态 一、多态的概念和定义 概念: 通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同

~ 2219

FM收音机调试-RDA5807M

收音机调试 IC型号:RDA5807M

mangofu的博客 6163

【C++进阶】多态

【C++进阶】多态

小小unicorn的博客 1056

顶级AI大模型白嫖ChatGPT Plus1个月,0成本薅顶配功能

Chat*** Plus限时免费1个月!原价20美元/月的顶配功能

BUG猿的博客 7271

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

[cpp进阶]多态和虚函数

文章目录多态的概念 多态的概念 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。 C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。 比如说: 买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。 买票这一个行为,不同的人做就有不同的行为和结果,这就是多态。 ...

weixin_53027918的博客 1242

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【C++】多态进阶

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载。 2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

石墨博客 1290

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C++进阶之多态

多态的概念 多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写 4.虚函数重写的两个例外 5.C++11 override 和 final 6.重载、覆盖(重写)、隐藏(重定义)的对比 抽象类 1.概念 2.接口继承和实现继承 多态的原理 1.虚函数表 2.多态的原理 3.动态绑定与静态绑定 单继承和多继承关系的虚函数表 1.单继承中的虚函数表 2.多继承中的虚函数表 继承和多态常见的面试问题

立志做一个高产且细节博主 875

C++进阶—>多态(3个问题了解它)

该模块属于C++进阶内容,通过三个问题(是什么?——>怎么实现??——>为什么可以这样???来理解多态这个陌生的词语,以及底层原理?在知道多态是怎样实现的,就需要知道虚函数表、虚函数表这两个词语!

2301_78704584的博客 962

C++进阶-多态1

本文介绍了C++多态的概念与实现方法。主要内容包括:1. 多态分为编译时多态(函数重载、模板)和运行时多态(虚函数机制);2. 运行时多态的实现需要满足两个条件:基类指针/引用调用虚函数,且派生类完成虚函数重写;3. 详细讲解了虚函数、虚函数重写/覆盖的语法规则;4. 介绍了协变、析构函数重写等特殊情况;5. 讲解了C++11新增的override和final关键字的作用;6. 通过实例演示了多态的应用场景和注意事项。文章强调多态在C++中的重要性,并建议在实际开发中合理使用虚函数机制。

博客是在C++/Java/Liunx/算法的学习阶段所写的博客,内容可能不是很全面 1101

【C++进阶】多态要这样学才够清楚

深入理解多态,一步到位

小羊的博客 2974
上一篇: C++进阶 —— 继承
下一篇: C++进阶 —— 二叉搜索树
挨代码
博客等级 码龄5年 1696粉丝 384原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值