C++之多态

文章详细阐述了C++中的多态性,包括多态的两个条件——虚函数重写和父类指针或引用调用虚函数,以及多态的本质原理。讨论了析构函数为何应为虚函数,C++的final和override关键字的用途。同时,区分了重载、重写(覆盖)和重定义(隐藏)的概念,并介绍了抽象类和虚表的相关知识。此外,还解答了关于虚函数的一些常见问题,如虚函数与inline、静态成员函数的关系等。
摘要由CSDN通过智能技术生成

目录

一、多态的概念

二、多态两个的条件

1、虚函数重写

①虚函数定义

②虚函数的重写/覆盖的条件

2、父类指针或引用去调用虚函数

 三、多态本质原理

四、析构函数的重写

五、C++新加的final、override关键字用法

 1、final的用法

2、override的用法

六、重载,重写(覆盖),重定义(隐藏)的区别

1、重载

2、重写(覆盖)

3、重定义(隐藏)

七、抽象类

八、虚表

1、虚表特性

①、同一个类型公用一个虚表

②、子类父类虚表不同

③、虚函数都会进虚表

2、多继承中的虚表

九、虚函数相关问题

1、inline函数可以是虚函数吗?

2、静态成员函数可以是虚函数吗?

3、构造函数可以是虚函数吗?

4、析构函数可以是虚函数吗?

5、拷贝构造可以是虚函数吗?

6、operator=可以是虚函数吗?

7、对象访问普通函数和虚函数哪个更快?

8、虚函数是什么阶段生成的?是存在哪里的?


一、多态的概念

多态就是指不同的对象完成某个行为,会产生不同的状态

学生,普通成年人,老年人这三种不同的对象,在坐公交车这个行为上,会产生不同的状态,这就是我们所说的多态


二、多态两个的条件

1、虚函数重写

①虚函数定义

virtual + 成员函数 构成了虚函数

注意:在继承中,菱形虚拟继承那里也用了virtual,但是这两个virtual用法完全不同,只是都用了virtual这个关键字,千万不要混为一谈

②虚函数的重写/覆盖的条件

虚函数 + 三同(函数名,参数,返回值),满足这个条件就是重写

如果不符合重写,就是我们继承知识中出现的隐藏关系

关于虚函数的重写C++有个2个特例

第一,如果子类的虚函数不加virtual,依旧构成重写,但是还是建议加上

可以理解为:它会认为子类已经继承父类的虚函数,子类只是改写虚函数的实现部分

第二,重写的协变

虚函数的返回值可以不同,但是必须是父子关系的指针或引用,这里的父子关系可以是本身继承的父子关系,也可以是其他的和本身毫不相关的父子关系

2、父类指针或引用去调用虚函数

下面举个样例帮助理解多态的两个条件:

运行结果是:

接下来具体理解上方样例:

1、首先看是否满足多态的第一个条件:虚函数的重写

A类,B类,C类都有Print函数,这三个Print函数前都有关键字virtual,满足虚函数;这三个Print函数也满足三同,即函数名,参数,返回值相同,因此满足虚函数重写的两个条件即:函数 + 三同(函数名,参数,返回值)

2、接着看是否满足多态的第二个条件:父类指针或引用去调用虚函数

如上图,有一个函数Test,它的参数是父类引用即A& a,a去调用了虚函数Print,因此满足父类指针或引用去调用虚函数

分析后得知,上方代码完美满足多态的两个条件,看运行结果也可以得知,aa,bb,cc三个不同类型的对象,完成共同的行为Print,会产生不同的状态。


 三、多态本质原理

如果符合多态的两个条件,那么调用时,会到指定对象的虚函数表(简称虚表,本质是函数指针数组)里面找到对应的虚函数地址,进行调用

下面看代码样例:

运行结果:

  

父类A中,有两个虚函数,分别是Print和Func,子类中有一个虚函数Print

Print这个虚函数满足虚函数的重写,并且中间的Test函数也满足了多态的第二个条件,父类指针或引用去调用虚函数,所以完成多态

然后通过调试,用监视窗口观察,A类成员aa和B类成员bb的具体情况:

aa:

bb:

 观察上面的监视窗口,可以看到,两个对象都有虚表_vfptr,打开虚表可以看到,都包含了两个虚函数的地址,图中我用箭头标注了,可以看到符合多态的Print函数,aa和bb的Print函数分别是各自作用域的,即A::Print()和B::Print(),地址是不同的,从而在调用Print函数时,完成的状态是不同的;而不符合多态的aa和bb的Func函数都显示A::Func(),所以地址是相同的,完成的状态自然是相同的

这里就可以非常清楚的说明了多态的调用,是在程序运行时去指向对象的虚表中找到函数的地址,然后再进行调用;而普通函数的调用,是在编译连接时确定函数的地址,运行时直接调用


四、析构函数的重写

首先,我们建议在继承中析构函数定义成虚函数

析构函数名都会被处理成的destructor,所以析构函数能够满足虚函数的重写的要求,这样可以保证在下面的情况下,p1和p2所指向的对象能够正确的调用析构函数:

 上面这种情况,如果析构函数不是虚函数,那么就不满足实际需求,我们的p1是new了一个A对象,调用A的析构函数是正确的,但是p2,new了一个B对象,调用的本该是B的析构函数,却也调用的是A的析构函数,因为这是普通调用,在编译时就会确定函数地址,只看到了A*,却没有看到后面new的对象不同,明显有很大问题。

这个场景所需要的是,p1调用A的析构,p2调用B的析构,多态的应用场景,所以我们将析构函数都变为虚函数,即:

 这时析构函数符合多态调用,p1调用A类(父类)的析构,p2调用B类(子类)的析构函数后自动调用A类(父类)的析构函数,所以运行结果就如上图所示,满足需求了

指向父类调父类的析构函数;指向子类调子类的析构函数


五、C++新加的final、override关键字用法

 1、final的用法

修饰虚函数,表示该虚函数不能再被重写

 在父类虚函数Print后面加一个final,子类就无法重写这个虚函数,报错也是这样描述的

2、override的用法

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

如图,去掉父类的virtual,在子类虚函数的后面加override,如果没有进行虚函数的重写,则会直接报错 


六、重载,重写(覆盖),重定义(隐藏)的区别

重载,重写,重定义这三种都是函数之间的关系,他们有一定的区别:

1、重载

①两个函数都在同一个作用域

②函数名必须相同,参数不同(类型,顺序,个数)

2、重写(覆盖)

①两个函数分别在基类和派生类的作用域中

②函数名,参数,返回值必须相同(除了特例:重写的协变可以返回值不同)

③两个函数必须是虚函数

3、重定义(隐藏)

①两个函数分别在基类和派生类的作用域中

②函数名相同

③两个基类和派生类的同名函数不构成重写就是重定义


七、抽象类

在虚函数的后面加上=0,这个函数就被称为纯虚函数,而包含纯虚函数的类叫做抽象类(也叫接口类)

即:

抽象类的特点是不能实例化出对象

抽象类是现实生活中没有对应的具体实体,比如说动物,并不是实体,是虚拟的统称,用老虎这个具体的实体去继承动物 

纯虚函数规范了派生类必须重写,如果不重写就会出现这种报错:

所以是间接强制去重写,不重写就不能实例化对象,而上面的override是直接强制检查重写的


八、虚表

下面都是在VS环境下的示例

1、虚表特性

①、同一个类型公用一个虚表

 

②、子类父类虚表不同

不管是否重写,子类和父类虚表都不是同一个

③、虚函数都会进虚表

父类和子类Print虚函数重写, 父类有虚函数testA,子类有虚函数testB

A的虚表中有两个虚函数,分别是A的Print和testA函数

B的虚表中有三个虚函数,分别是B的Print,A的testA和B的testB函数

A、B的虚函数都进入虚表中

2、多继承中的虚表

有一个子类C继承了两个父类A和B类

 A,B,C类具体内容如下:

重写了虚函数Print,剩下的三个类中各自的testA,testB,testC都没有重写

所以如果创建C cc,cc在内存中就有A和B两个父类的所有成员,即A和B类分别拥有的虚表和_a,_b,还有cc自己的_c,子类自己未重写的虚函数,则会在多继承中的第一个父类的虚表里,即C类中的testC()在A类的虚表里,因为class C : public A, public B代码中,先继承的A类,cc对象内存中结构是这样的:

监视窗口:

cc对象中继承的A的_vfptr(虚表)中有:C::Print()、A::testA()、C::testC();B的_vfptr(虚表)中有:C::Print()、B::testB()


九、虚函数相关问题

1、inline函数可以是虚函数吗?

可以,因为inline只是建议性关键字,并不强制内联,在完成多态时编译器会忽略inline属性,即inline关键字失效,这个函数将不再是inline,因为虚函数会放到虚函数表中需要存一个地址,而inline函数会在调用的地方展开,没有地址,所以会忽略inline属性

2、静态成员函数可以是虚函数吗?

不可以,因为静态成员函数没有this指针,使用类型::成员函数的调用方法,是在编译时决议的,而虚函数是为了实现多态,多态是在运行时去虚表找决议,所以静态成员函数是虚函数没有任何价值

3、构造函数可以是虚函数吗?

不可以,virtual函数是为了实现多态,运行时去虚表找对应的虚函数进行调用,而对象中的虚表指针都是构造函数初始化列表阶段才初始化的,所以没有意义

4、析构函数可以是虚函数吗?

可以,上面知识有说到

5、拷贝构造可以是虚函数吗?

不可以,拷贝构造也是构造函数

6、operator=可以是虚函数吗?

可以,但是没有任何实际的价值

7、对象访问普通函数和虚函数哪个更快?

在虚函数不构成多态时,一样快

虚函数构成多态时,普通函数快,因为多态调用是运行时去虚表中找虚函数地址

8、虚函数是什么阶段生成的?是存在哪里的?

编译阶段就生成好的,存在代码段(常量区)


C++三大特性之多态相关知识总结就是这些

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值