抽象类
纯虚函数
在虚函数的后面写上 =0
virtual void test()=0;
抽象类的定义:包含纯虚函数的类
存在的意义:抽离出多个类中的共同方法,放入一个类
这个类没有实际的作用,不具体
抽象类特点:
- 抽象类不能创建对象,不被继承就没有意义
- 抽象类可以创建指针,让这个父类的指针指向子类对象
因为抽象类是类型,创建指针就只需要4个或8个字节,该指针就可以存放任意对象的地址- 抽象类被继承后,只有重写全部的纯虚函数,子类才能创建对象,否则该子类也是抽象类
- 纯虚函数规范了派生类必须重写,更体现出了接口继承
- 普通函数的继承是一种实现继承,子类继承了父类的具体函数,可以使用函数,继承的是,函数实现。
- 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是,接口。
将共性抽出作为父类,但是父类构造出的对象无意义
使用纯虚函数防止类创建对象
虚函数对类的影响
-类中有虚函数,大小会多出4个字节,即使有多个虚函数也是多4个字节
- 多出的4个字节是在构造对象时完成的,也就是说这4个字节是在构造函数中创建的
编译器一定会生成构造的四种情况
- 类中有虚函数,
原因:编译器需要将自己添加的4个字节进行赋值;- 虚拟继承体系中的子类;
- 继承中,父类构造函数是默认的,子类没有显式提供构造函数,会为子类生成构造;
原因:要通过子类构造去调用父类构造。- 类B中有类A的对象,且A有默认构造函数,B没有提供构造
原因:B类中包含其它类的对象,需要借助B类构造函数调用A类构造初始化成员
含有虚函数的类多出4个字节,就是因为这4个字节是指针类型,指向了一个虚表,该虚表中保存的就是类中虚函数的入口地址
单继承虚表的构建
虚表在编译阶段生成,存储在代码段
什么是虚表:创建对象后,该对象首4个字节的内容,指向的就是虚表
- 虚表中保存了类中所有虚函数的地址
- 表中存放顺序是按虚函数在类中声明的次序存放;
- 虚表是一个函数指针数组,每个元素类型是函数指针,将这个保存虚函数入口地址的变量称为虚表指针;
- 虚表指针+成员变量就是该类的对象模型;
多继承虚表的构建
与单继承不同的是,子类中新增加的虚函数会放在先继承的父类虚表中
虚表的构建规则
父类虚表的构建规则:
- 按照虚函数在类中声明的先后顺序,依次放置到虚表中
子类虚表的构建规则:
- 将父类虚表中的内容拷贝一份放置到子类虚表中;
- 如果子类重写了父类中某个虚函数,则使用子类虚函数地址,替换虚表中相同偏移量位置的父类虚函数
- 子类中新增加的虚函数按照其在类中声明的先后顺序,依次放在虚表的最后
整个虚表在vs2013环境下以NULL结束
父子类虚表的共同特点:
- 父类和子类永远都是使用各自的虚表
- 单继承中,同一个类的多个对象共享一个虚表
如何调用虚表中的虚函数
改进:
虚函数调用原理
1.如果通过父类的对象调用虚函数,就直接调用父类中的虚函数
void test (A a)
2.如果通过父类的指针或者引用调虚函数
void test(A& a);
- 先从对象前4个字节中取出虚表地址;
- 传递this,因为虚函数是成员函数;
- 从虚表中取出对应虚函数的入口地址,调用该虚函数
动态绑定与静态绑定
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。