多态概念:
多态是一种运行期绑定机制,通过这种机制,实现将函数名绑定到函数具体实现代码的目的。
即将函数名动态的绑定到函数入口地址的运行期绑定机制。
从程序角度理解: 子类覆盖父类方法, 代码层面通过父类调用对应方法, 运行期根据具体对象调用对应子类的方法。
有人把多态分强多态(运行期绑定)和弱多态(编译期绑定如重载 函数名共享 泛型), 个人支持多态仅针对运行期绑定。
运行期绑定与编译期绑定
编译期绑定:编译阶段将对函数的调用绑定到函数的入口地址
运行期绑定:程序运行阶段才将函数名绑定到对应函数入口地址
C++通过vtable实现徐成员函数绑定,虚成员函数表用于支持运行时查询。
系统可以将某一个函数名绑定到虚成员函数表的特定入口地址,这种方式会带来存储和机器周期方面的开销。
多态的前提条件:
1)必须存在继承层次结构
2) 继承层次结构中的一些类必须具有同名的virtual成员函数
3) 至少有一个基类类型的指针或引用,可以用来对virtual成员函数的调用
多态作用及缺点:
作用个人理解是封装和扩展。
封装: 封装各个子类的实现细节,调用侧仅调用统一方法,实现与接口分离。
扩展: 子类功能的扩展;多个子类的扩展。
缺点: 运行期绑定会造成额外空间开销及查询开销,C++仅对需要多态的函数做运行期绑定。
接口修改影响整个继承层次。
重载、覆盖、遮蔽概念理解:
重载: 语言特性,非面向对象概念; 同一个类中同一个函数名不同函数签名即可重载。
函数签名由函数名,参数的个数、类型、顺序 决定。
const函数可以理解修饰this参数故函数名后的const也决定签名。
const参数只有指针和引用类型才支持根据const参数重载,值类型参数由于参数复制会导致编译器二义性故不支持。
覆盖: 子类继承父类的方法,子类实现父类的virtural修饰的同一个函数签名的函数则成为覆盖。
遮蔽: 子类继承父类的方法,子类实现父类未virtual修改的同一个函数名的方法。
父类的同一个函数名的所有方法均在子类不可见。
覆盖和遮蔽的父类方法均可以通过指定域标识符方式调用,但覆盖的方法 子类转换为父类后调用的依旧为子类方法
遮蔽的方法子类转换为父类后调用的是父类的方法
virutal关键字使用:
仅用在成员函数声明,不能用于定义。
基类成员函数指定,子类同一函数名的成员函数可以不用加virtual,但一般推荐加上避免后续向基类反查是否为覆盖方法。
构造函数不能是虚成员函数,但析构函数可以是虚成员函数。为避免资源释放不彻底,析构函数需要加virtual。
只有非静态成员函数才可以是虚成员函数。
抽象基类:
为了确保派生类覆盖某些虚函数,引入纯虚函数实现抽象基类。
纯虚函数通过虚函数申明即为加=0实现,类似void func()=0;, 拥有纯虚函数的类成为抽象类,不能实例化对象。
纯虚函数必须为虚函数,不能对顶层函数或者非虚成员函数设置=0;
只有覆盖了纯虚函数,没有纯虚函数的类才可以实例化对象。
抽象基类可以用作公共接口的定义,类似java的interface
运行期类别识别(RTTI):
支持在运行期对类型转型操作检查;
支持在运行期确定对象类型
支持扩展C++提供的RTTI
C++在编译期支持的类型转换操作可能会在运行期引发错误。当操作涉及对象引用或指针时容易出现错误。
通过dynamic_cast操作符做运行期类型转换的判断及转换。
基类指针可以不做显式转型指向派生类对象,但派生类指针只能强制转型才能指向基类对象。
派生类指针通过static_cast可以显式强制转型为基类对象,没有编译器错误,但容易造成运行期错误。
如访问派生类中才有的方法。static_cast不能保证类型安全。
dynamic_cast可以检测运行期类型转换动作是否类型安全(向上转型后的转换安全),
仅对多态模式有效。dynamic_cast<T*>(ptr)
typeid获取类型返回type_info对象引用。typeid(typename) or typeid(表达式)