本篇博客是作者在学习c++的过程中的笔记记录,希望和各位读者一起学习交流
多态
- 面向对象中的多态
根据实际的对象类型决定函数调用语句的具体调用目标
eg:p->print()
如果p指向父类对象,则执行
void print()
{
cout << “I’m Parent”;
}
如果p指向子类对象,则执行
void print()
{
cout << “I’m Child”;
} - 解决方案
- c++中支持多态
- c++中通过使用virtual关键字对多态进行支持
- 使用virtual声明的函数被重写后即可展现多态特性
- 多态成立的三个条件
- 有继承
- 有虚函数重写
- 用父类指针(父类引用)指向子类对象
- 多态是设计模式的基础,多态是框架的基础,实现多态的基础是函数指针做函数参数
- 多态的理论基础
- 静态联编和动态联编
- 联编是指一个程序模块、代码之间相互关联的过程
- 静态联编是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编
- 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。switch语句和if语句是动态联编
- 理论联系实际
- c++与c相同,是静态编译型语言
- 在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象,所以编译器认为父类指针指向的是父类对象
- 由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象,从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编
- 多态的c++实现
virtual关键字,告诉编译器这个函数要支持多态,不要根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用。冠以virtual关键字的函数叫虚函数;虚函数分为两类:一般虚函数、纯虚函数 - 多态的实现效果:
同样的调用语句有多种不同的表现形态
- 静态联编和动态联编
- 虚析构函数
- 作用:虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,防止内存泄漏
- 如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调用父类的析构函数,而不调用子类的析构函数 - 如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类,delete掉父类的指针,先调用子类的析构函数,再调用父类的析构函数
- 重写、重载、重定义
- 函数重载
必须在同一个类中
子类无法重载父类的函数,父类同名函数将被覆盖
重载是在编译期间根据参数类型和个数决定函数调用 - 函数重写
必须发生在父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual关键字声明之后能够产生多态(如果不适用virtual,则叫重定义)
多态时在运行期间根据具体对象的类型决定函数调用
- 函数重载
- 多态原理探究
- 多态的实现原理
c++多态的实现原理- 当类中声明虚函数时,编译器会在类中生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表是由编译器自动生成与维护的
- virtual成员函数会被编译器放入虚函数表中
- 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
说明:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能真正确定真正应该调用的函数,而普通成员函数是在编译的时就确定了调用的函数。在效率上,虚函数的效率要低很多。
- 证明vptr指针存在
输出:
- 对象中的vptr指针什么时候初始化
对象在创建时,由编译器对vptr指针进行初始化
初始化vptr指针,初始化时分步的:- 当执行父类的构造函数的时候,vptr指向父类的虚函数表
- 当父类的构造函数执行完毕后,会把vptr指向子类的虚函数表
因此vptr的初始化是分步完成的
父类对象的vptr指向父类的虚函数表
子类对象的vptr指向子类的虚函数表
- 多态的实现原理
- 父类指针和子类指针的步长
- 铁律:指针也是一种数据类型,c++对象的指针p++/–,仍然可用;
- 指针运算是按照指针所指的类型进行的
p++ 《==》 p=p+1 //p = (unsigned int)basep + sizeof(*p); - 父类p++与子类p++的步长不同,不能在数组上使用多态
- 步长相同的情况:子类继承父类,但是在子类中没有添加任何的属性和方法
抽象类
- 纯虚函数
- 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本
- 纯虚函数为各派生类提供一个公共界面(接口的封装和设计、软件的模块划分)
- 语法:
virtual 类型 函数名(参数表)= 0;
- 抽象类
一个具有纯虚函数的基类称为抽象类
抽象类不能被实例化
子类继承了抽象类就必须实现纯虚函数 - 多继承的应用场景
c++中可以使用纯虚函数来实现接口
接口类中只有函数原型定义,没有任何数据的定义,接口类只是一个功能说明,不是功能实现
多重继承接口不会带来二义性和复杂性
下面的是笔者的微信公众号,欢迎关注,会持续更新c++、python、tensorflow、机器学习、深度学习等系列文章