不规范的重写:在基类函数加了virtual
关键字 ,在派生类中重写的成员函数前不加virtual
,也是构成重写的。
c++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)
接口结成和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的
继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所
以如果不实现多态,不要把函数定义为虚函数。
C++11 override 和 fifinal
//实际中我们建议多使用纯虚函数+ overrid的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果没有重写,虚函数就没有意义。
// 1.final 修饰基类的虚函数不能被派生类重写
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒适" << endl;}
};
class Car{
public:
virtual void Drive(){}
};
// 2.override 修饰派生类虚函数强制完成重写,如果没有重写会编译报错
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
多态的原理
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
cout << sizeof(Base) << endl;//8
getchar();
return 0;
}
//除了_b成员,还多一个__vfptr放在对象的前面(有些平台可能在后面)。对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。
对象中存的不是虚表,存的是虚表指针。那么虚表存在代码段。
打印虚表
思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的
指针数组,这个数组最后面放了一个nullptr
// 1.先取b的地址,强转成一个int*的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有
放nullptr,导致越界,这是编译器的问题。只需要点目录栏的-生成-清理解决方案,再编译就好了。
inline函数可以是虚函数吗?
答:不能,因为inline函数没有地址,无法把地址放到虚函数表中。
静态成员可以是虚函数吗?
答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。
构造函数可以是虚函数吗?
答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
答:可以,并且最好把基类的析构函数定义 成虚函数。
对象访问普通函数快还是虚函数更快?
答:首先如果是普通对象,是一样快的。如果是指针对象或者是 引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
虚函数表是在什么阶段生成的,存在哪的?
答:虚函数是在编译阶段就生成的,一般情况下存在代码段(常量区)的
抽象类的作用
抽象类强制重写了虚函数,另外抽象类体现出 了接口继承关系。
多态的定义和实现:添加链接描述
多态的四种表现形式 添加链接描述
虚函数表解析:添加链接描述
内存布局 添加链接描述