十三、多态
4. 抽象类
概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
#include<iostream> using namespace std; // 抽象类 class Car { public: // 纯虚函数 virtual void Drive() = 0; }; int main() { // 抽象类不能实例化对象 Car c1; return 0; }
#include<iostream> using namespace std; // 抽象类 class Car { public: // 纯虚函数 virtual void Drive() = 0; }; // 子类必须重载纯虚函数,否则也不能实例化对象 class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒适" << endl; } }; class BMW :public Car { public: virtual void Drive() { cout << "BMW-操控" << endl; } }; int main() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); return 0; }
接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
静态绑定和动态绑定
静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载。
动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
5. 单继承和多继承关系的虚函数表
单继承中的虚函数表
虚函数表在哪个区域?。
#include<iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } private: int _d = 2; }; int main() { int i = 1; static int j = 2; int* p1 = new int; const char* p2 = "XXXXXXX"; printf("栈:%p\n", &i); printf("静态区:%p\n", &j); printf("堆:%p\n", p1); printf("常量区:%p\n", p2); Base b; Derive d; Base* p3 = &b; Derive* p4 = &d; // 一般情况下虚函数表都存在对象中的起始位置,所以强转成int*便可读取前4个字节 printf("Base虚函数表地址:%p\n", *(int*)p3); printf("Derive虚函数表地址:%p\n", *(int*)p4); return 0; }
从上面的结果来看,很明显,虚函数表存在于常量区。
我们想打印一下虚函数表,应该怎么实现呢?我们知道虚函数表的地址,但是不知道结束条件,无法遍历指针数组。
我们发现vs下虚函数表(指针数组)都是以空指针结束,由此就可以判定结束条件从而打印虚函数表。
#include<iostream> using namespace std; class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } virtual void Func3() { cout << "Derive::Func3()" << endl; } private: int _d = 2; }; // 函数指针 typedef void(*VF_PTR)(); // 打印虚函数表,本质上是打印(虚函数)指针数组 void PrintVFT(VF_PTR* vft) { for (size_t i = 0; vft[i] != nullptr; ++i) { printf("[%d]:%p->", i, vft[i]); // 回调函数 VF_PTR f = vft[i]; f(); } } int main() { Derive d; // 强转成int*来找到虚函数表地址,然后强转成指针数组传参 PrintVFT((VF_PTR*)(*(int*)&d)); return 0; }
多继承中的虚函数表
我们来看看下面这种情况:
class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual void func1() { cout << "Base2::func1" << endl; } virtual void func2() { cout << "Base2::func2" << endl; } private: int b2; }; // Derive 继承了 Base1 和 Base2 class Derive : public Base1, public Base2 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1; };
Derive继承了两个父类,那他的对象应该有多少个虚表?我们应该这样看,我们将Derive的对象分成三份,Base1部分、Base2部分和自己的部分。因此Derive应该有两个虚表,每继承一个父类,其内部就有一个虚表。
那么:
int main() { Derive d; // 这里切片会有问题吗 Base1* ptr1 = &d; Base2* ptr2 = &d; return 0; }
对象d中有Base1的部分和Base2的部分,切片会导致 ptr1 和 ptr2 一样吗?当然不会,编译器会自动产生偏移进行切片的。
这里 ptr1指向的地址和d的地址一样,但是这两个意义是不一样的,一个是对象d的首地址,一个是Base1切片部分的首地址。ptr2 和 ptr1之间产生了偏移 。
那么新问题:Derive自己的虚函数放在哪个虚函数表里呢?
#include<iostream> using namespace std; class Base1 { public: virtual void func1() { cout << "Base1::func1" << endl; } virtual void func2() { cout << "Base1::func2" << endl; } private: int b1; }; class Base2 { public: virtual void func1() { cout << "Base2::func1" << endl; } virtual void func2() { cout << "Base2::func2" << endl; } private: int b2; }; // Derive 继承了 Base1 和 Base2 class Derive : public Base1, public Base2 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1; }; typedef void(*VFPTR)(); void PrintVFT(VFPTR* vft) { for (size_t i = 0; vft[i] != nullptr; ++i) { printf("[%d]:%p->", i, vft[i]); VFPTR f = vft[i]; f(); } cout << endl; } int main() { Derive d; Base1* ptr1 = &d; Base2* ptr2 = &d; // 打印Base1的虚函数表 PrintVFT((VFPTR*)(*(int*)ptr1)); // 打印Base2的虚函数表 PrintVFT((VFPTR*)(*(int*)ptr2)); return 0; }
由此可见,子类自己的虚函数放在了第一个继承的虚函数表里 。