虚函数
1.概念
在C++中,派生类继承了基类的成员变量和成员函数,但有时,派生类不希望执行基类中定义好的某些成员函数,
这时,派生类可以通过虚函数这一机制来重新定义成员函数。例如。
class A { public: virtual void fun() { cout << "A" << endl; } }; class B : public A { public: void fun() { cout << "B" << endl; } }; int main() { A a; B b; a.fun(); b.fun(); return 0; }
输出结果:A B
当我们在基类的成员函数声明时加上virtual关键字,就使得fun()函数成为了虚函数,在派生类中,可以重新定义
虚函数,是他的操作符合派生类的需要。
2.应用
虚函数最多的应用是发生在当一个基类的指针或者引用指向一个派生类对象时,由于指针或者引用是基类类型的,所以
会调用基类的成员函数,而当我们定义了虚函数时,就会调用该对象,也就是派生类的方法。
class A { public: virtual void fun() { cout << "A" << endl; } }; class B : public A { public: void fun() { cout << "B" << endl; } }; int main() { A* a = new B; a->fun(); return 0; }
输出结果:B
即使指针a的类型是基类类型,但是在定义了虚函数的前提下,会优先执行所指对象的成员函数。
3.虚函数的调用
对虚函数的调用可能在运行时才被解析,当 虚函数通过指针或者引用来调用时,编译器在运行时
才能知道应该匹配那个对象的成员函数。当我们使用一个普通类型来调用虚函数时,不会出现这种情况。
4.回避虚函数
有时候,我们不希望虚函数的调用发生动态绑定,可以使用域作用符来指定它执行那个特定的版本。
int main() { A* a = new B; a->fun(); a->A::fun(); return 0; }
输出结果: B A
5.虚函数表
C++通过使用虚函数表来管理虚函数,每个有虚函数的类都会得到一张虚函数表,该表被所有该类的
实例共享,每个虚函数占据表的一行,如果有N个虚函数,虚函数表就有N*4个字节的大小。
每个类对象实例有一个指向虚函数表的指针,在对象实例的最高位置。看一个栗子
class A { public: virtual void fun() { cout << "A" << endl; } private: int a; }; class B : public A { public: void fun() { cout << "B" << endl; } private: int b; }; int main() { // A* a = new B; // a->fun(); // a->A::fun(); A a; B b; cout << sizeof(a) << endl; cout << sizeof(b) << endl; return 0; }
输出结果: 8,12
类A只有一个int的成员变量,类B继承了类A,同时自己有一个成员变量,那么结果应该是4和8,为什么是
8和12呢?
就是在类对象中,保存了一份指向虚函数表的指针。
而在虚函数表中,保存了一系列的函数指针,来指向我们要调用的函数,在虚函数被调用时,首先通过指向虚函
数表的指针来指向虚函数表,然后遍历该表,找到我们要执行的虚函数。
在没有重写虚函数的继承体系中,虚函数表依次是基类的虚函数,派生类的虚函数
在重写了虚函数的继承体系中,派生类中重写的虚函数会覆盖基类的虚函数。
6.纯虚函数与抽象基类
纯虚函数的声明在函数名前加上virtual,在函数后加上=0;纯虚函数不需要实现,
它的作用就是交给派生类去完成的。只要一个类中有一个纯虚函数,那么该类就是一个
抽象基类,抽象基类不能实例化,它的作用就是让别的类去继承它,去实现他的纯虚函
数,相当于提供了一个接口。