虚函数 在继承 并发生 动态多态时使用(静态多态为函数重载)
父类指针指向子类对象 多态
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
}
//调用doSpeak ,speak函数的地址早就绑定好了,早绑定,静态联编,编译阶段就确定好了地址
//如果想调用猫的speak,不能提前绑定好函数的地址了,所以需要在运行时候再去确定函数地址
//动态联编,写法 doSpeak方法改为虚函数,在父类上声明虚函数,发生了多态
// 父类的引用或者指针 指向 子类对象
void doSpeak(Animal & animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
//父类指针指向子类对象 多态
Animal * animal = new Cat;
}
在构造函数中进行虚表的创建和虚表指针的初始化。虚表和静态变量一样存在全局数据区,虚表可以理解成类的静态成员,虚表指针和构造函数的执行初始化列表是初始化。虚表的创建在编译阶段
多态作用
开发有原则 开闭原则 -- 对扩展开放 对修改关闭
利用多态实现 – 利于后期扩展,结构性非常好,可读性高, 效率稍微低,发生多态内部结构复杂
虚继承
虚基类 在继承 时使用
class Base
{
public:
int age;
};
class Son:virtual public Base{};
这样Son 和 Base 公用一个 成员属性 age , 不会浪费空间(解决菱形继承问题) 解决棱形继承问题。最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类对虚基类的构造函数的调用,避免重复。为了实现虚继承引入了类似虚函数表指针的vbptr,vbptr 指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员
类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表,从这个意义上说,我们可以把虚表简单理解为类的静态数据成员。值得注意的是,虽然虚表是共享的,但是虚表指针并不是,类的每一个对象有一个属于它自己的虚表指针,如果派生类虚函数覆写了父类,则虚函数地址相同(实例话对象父类派生类掉用虚函数表指针指向要使用的虚函数地址)。 而虚继承中如果子类自身有新的虚函数,会自己在创建一张虚函数表(并在储存保留父类虚表情况下)
虚继承与继承的区别
单个虚继承,不带虚函数 1. 多了一个虚基指针
2. 虚基类位于派生类存储空间的最末尾
单个虚继承,带虚函数
3.如果派生类没有自己的虚函数,此时派生类对象不会产生虚函数指针
4.如果派生类拥有自己的虚函数,此时派生类对象就会产生自己本身的虚函数指针,并且该虚函数指针位于派生类对象存储空间的开始位置
多重继承(带虚函数) 虚继承的派生类如果 其父类 来自不用层次,则另一个父类的虚基类表会并入另一个之中。 1. 每个基类都有自己的虚函数表
2. 派生类如果有自己的虚函数,会被加入到第一个虚函数表之中
3. 内存布局中, 其基类的布局按照基类被声明时的顺序进行排列
4. 派生类会覆盖基类的虚函数,只有第一个虚函数表中存放的是 真实的被覆盖的函数的地址;其它的虚函数表中存放的并不是真实的 / 对应的虚函数的地址,而只是一条跳转指令
产生vtordisp字段条件是(4个字节储存)。
-
派生类重写了虚基类的虚函数。
-
派生类定义了构造函数或者析构函数。