南联网站建设,公司网站开发流程,鹰潭网站商城建设,济南城乡建设网站注#xff1a;以下示例均是在VS2019环境下
一、多态的概念 通俗来讲#xff0c;多态就是多种形态#xff0c;当不同的对象去完成某个行为时#xff0c;会产生出不同的状态。即不同继承关系的类对象#xff0c;去调用同一函数时#xff0c;产生不同的行为。 比如”叫“这…注以下示例均是在VS2019环境下
一、多态的概念 通俗来讲多态就是多种形态当不同的对象去完成某个行为时会产生出不同的状态。即不同继承关系的类对象去调用同一函数时产生不同的行为。 比如”叫“这个行为不同的动物发出的声音是不同的。
二、多态的定义及实现
1.多态的构成条件
1必须是在继承环境下。
2被调用的函数必须是虚函数且派生类中必须对该虚函数进行重写。
3必须通过基类的指针或引用去调用虚函数。
2.虚函数 被virtual修饰的类成员函数。
3.虚函数的重写
3.1虚函数的重写覆盖 派生类中有一个跟基类中完全相同的虚函数即派生类虚函数与基类虚函数的返回值类型、函数名、参数列表都完全相同称为子类的虚函数重写了基类的虚函数。
注意
1 在重写基类虚函数时派生类的虚函数不加virtual关键字修饰时也可以构成重写因为基类的虚函数被继承下来了在派生类中依旧保持虚函数属性但是这种写法不太规范不建议这样使用。
2基类和子类虚函数的访问权限可以不同但是一般都会将基类的虚函数设置为public。
3.2虚函数重写的两个例外
1协变基类与派生类虚函数返回值类型不同 派生类重写基类虚函数时与基类虚函数的返回值类型不同。即基类虚函数返回基类对象的指针或引用派生类虚函数返回派生类对象的指针或引用时称为协变。
注意 基类和派生类必须属于同一继承体系基类的返回值和派生类的返回值必须属于同一继承体系。但是返回值和基类、派生类不一定属于同一继承体系。
2析构函数的重写基类与派生类析构函数名不同 如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否使用virtual关键字修饰都与基类析构函数构成重写但是析构函数名不同。 函数名不同看起来似乎违背了重写的规则单其实是编译器对析构函数名做了特殊处理编译后析构函数名称统一处理成了destructor。
4.C11——override和final 在某些情况下由于疏忽可能会导致函数名字不同而无法构成重写而这种错误在编译期间是不会报错的只有在程序运行时没有得到预期结果才通过调试寻找错误代价较高。 因此C11提供了override和final两个关键字可以帮助用户检测是否重写。
4.1override关键字 检查派生类虚函数是否重写了基类某个虚函数若没有则编译报错。 4.2final关键字 只能修饰虚函数。 修饰虚函数表示该虚函数不能再被重写。 5.重载、重写覆盖、隐藏重定义的对比 注意
1重写和重定义必须在继承体系内。
2重写只能是成员函数而且是重写基类的虚函数重定义既可以是成员函数也可以是成员变量。
三、抽象类
1.抽象类概念
1在虚函数后面加上0则该函数为纯虚函数。
2包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化对象。
3派生类继承抽象类后也不能直接实例化对象必须对基类所有纯虚函数进行重写后才能实例化对象否则派生类也是抽象类。
4纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口的继承。 注意纯虚函数可以不用写函数体写了不影响但没有意义。
2.接口继承和实现继承
2.1实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。
2.2接口继承 虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。 所有若不需要实现多态不要把成员函数定义为虚函数。
四、多态的原理
1.虚函数表
只要类中存在虚函数对象大小就会多4个字节 该指针指向的是一段连续的空间即虚函数表里面存放的是虚函数入口地址且顺序与定义顺序相同。
对象模型 基类虚表
1虚函数入口地址存放顺序与虚函数定义顺序相同。
2一个类的多个对象公用同一个虚表
2.虚函数与虚表存放位置
1虚函数和普通函数一样存放在代码段虚表存放的是虚函数指针。
2对象中存放的是虚表指针不是虚表。
3在vs环境下虚表是存放在代码段的。
3.静态多态与动态多态
3.1静态多态 在程序编译阶段就已经确定了程序的行为也叫静态绑定、前期绑定早绑定比如函数重载、模板。
3.2动态多态 在程序运行时根据程序拿到的具体类型确定程序的具体行为调用具体的函数比如根据基类的指针或引用指向不同类的对象选择对应的虚函数进行调用。也叫动态绑定、后期绑定晚绑定。
五、单、多继承中的虚函数表
5.1单继承中的虚函数表
基类虚表
1按虚函数声明顺序存放入口地址。
2一个类的多个对象公用同一个虚表
子类虚表
1子类有自己独立的虚表不与父类共用。
2子类会将基类虚表拷贝一份放入自己的虚表中。
3如果子类重写了基类的某个虚函数就会用子类自己虚函数的地址去覆盖虚表中被重写的基类虚函数的地址。
4子类自己定义的虚函数其地址入口会按声明顺序依次存放在虚表的末尾。
5.2多继承中的虚函数表
基类虚表
1按虚函数声明顺序存放入口地址。
2一个类的多个对象公用同一个虚表
子类虚表
1子类会将基类虚表拷贝一份放入自己的虚表中。
2如果子类重写了基类的某个虚函数就会用子类自己虚函数的地址去覆盖虚表中被重写的基类虚函数的地址。
3子类自己新增的虚函数会将其入口地址添加在第一张虚表之后第一张虚表即子类第一个继承的基类所拷贝的虚表。
六、常见问题
1.什么是多态
2.什么是重载、重写、重定义
3.多态的实现原理 虚表的构造
虚函数的调用原理
1获取对象虚函数表指针对象前4个字节
2传递this指针
3从虚表中找到对应虚函数的入口地址
4调用该虚函数
4.inline函数可以是虚函数吗 语法上可以。不过编译器会忽略inline属性这个函数就不再是inline因为虚函数的地址入口需要放入到虚表中去。
5.静态成员可以是虚函数吗 不可以。因为静态成员函数没有this指针使用“类名::成员函数”的调用方式无法访问虚函数表所以静态成员不能放入虚函数表。
6.构造函数可以是虚函数吗 不可以。因为对象的虚函数表指针是在构造函数初始化列表阶段才初始化的。
7.析构函数可以是虚函数吗什么场景是 可以。最好是把基类的析构函数定义为虚函数。场景在继承体系中基类的析构函数最好设置为虚函数防止用基类的指针去销毁派生类对象时只调用基类的析构函数而不调用子类的析构函数。
8.对象访问普通函数快还是虚函数快 若是普通对象则一样快若是指针对象或引用对象则调用普通函数快。因为通过指针或引用访问虚函数时需要在运行过程中去查询虚表才能确定函数入口地址而普通对象在编译时就已经确定了函数的入口地址。
9.虚函数表是在哪个阶段生成的存放在哪儿 虚函数表在编译阶段生成一般情况存放在代码段常量区。
10.菱形继承的问题虚拟原理 注意不要混淆虚函数表和虚基表。
11.什么是抽象类抽象类的作用
……
作用抽象类规范了派生类必须重写纯虚函数另外纯虚函数更体现出了接口的继承。