一. 什么是虚函数
1. 虚函数是面向对象编程中函数的一种特定形态,是C++中用于实现多态的一种有效机制
2. 虚函数用virtual修饰函数名,虚函数的作用是在程序的运行阶段动态的选择合适的成员函数,在定义了虚函数之后,可以在基类的派生类中对虚函数进行重定义,在派生类中重定义的函数与基类虚函数具有相同的函数返回值、函数参数列表、函数名等。如果派生类中没有重定义基类的虚函数则它是直接继承基类的虚函数
3. 定义虚函数的一般格式
class <类名>{
public:
virtual 函数返回值类型 函数名(参数列表);
private:
}
4. 虚函数实现多态例子
#include<queue> #include<stack> #include<iostream> #include<algorithm> using namespace std; //类A class A{ public: //类A的虚函数 virtual void Print(void){ cout<<"class A"<<endl; } private: }; //类B继承类A class B:public A{ public: //类B中重写A的虚函数 virtual void Print(void){ cout<<"class B"<<endl; } private: }; int main(){ //test多态 A *a = new A(); B *b = new B(); a->Print(); //输出:class A a = b; //让基类指针指向子类对象指针 a->Print(); //输出: class B getchar(); return 0; } /* 解析如下: 1. 类A中定义了一个虚函数Print,类B继承了类A之后重写了虚函数Print 2. 在main函数中,基类指针第一次调用Print函数是调用的是类A的Print 当基类指针指向子类B的指针对象后,再调用Print则调用的是类B的Print 3. 通过基类指针指向不同子类对象实现动态的调用虚函数,就是多态的实现机制 */
二. 使用虚函数应该注意哪些问题
1. 只需要在声明虚函数的类中使用关键字virtual,而在类外实现虚函数的时候是不用加上virtual的
2. 将基类的某一成员函数声明为虚函数之后,派生类中的同名函数自动成为虚函数
3. 如果声明了某个成员函数为虚函数,则在该类中不能出现这个成员函数同名并且返回值、参数列表相同的非虚函数,在该类的派生类中也是不能出现这种同名的非虚函数
4. 全局函数、类的静态成员函数、构造函数是不能声明为虚函数的
5. 析构函数可以定义为虚函数,将基类的析构函数定义为虚函数之后,当利用delete删除一个指向子类对象的基类指针之后,会先去析构子类对象再去析构基类对象。如果基类的析构函数没有声明为虚函数则只会调用基类的析构函数而无法调用子类的析构函数,可能会造成内存泄露
三. 虚函数的实现机制
1. 虚函数是通过一张虚函数表来实现的,在有虚函数的类的实例中都有一个指针专门用来指向这张虚函数表。所以当用基类的指针来操作一个子类对象的时候,这个虚函数表可以用来指明实际要调用的函数
2. C++的编译器保证了指向虚函数表的指针位于实例化对象的最前面位置,通过这个指针得到这张虚函数表,这样就可以遍历虚函数表中的函数指针,并调用相应的虚函数
3. 虚函数表的各表项为指向对应虚函数的指针,存储的就是函数指针
4. 如果一个类中含有虚函数,则编译器会为此类生成一个虚函数表,并且会为该类插入一指针指向这个虚函数表。当调用这个类的构造函数的时候,编译器会把指针指向对应的虚函数表。比如当基类指针赋给子类指针对象的时候则把基类指针对象的指针指向子类指针对象的虚函数,这样就可以实现调用子类的虚函数,从而实现多态
四. 虚函数表
(1)子类继承基类虚函数,但是没有覆盖
//基类 class A{ public: virtual void F(void){} virtual void H(void){} }; //子类 class B:public A{ virtual void G(void){} };
实际的虚函数表示这样的,虚函数表前面是基类虚函数在是子类的虚函数
(2)子类继承基类虚函数,并且覆盖了基类虚函数
//基类 class A{ public: virtual void F(void){} virtual void H(void){} }; //子类 class B:public A{ virtual void F(void){} virtual void G(void){} };
这个时候实际的虚函数表是这样的,子类覆盖了基类的虚函数在虚函数表中替换基类同名函数位置,然后是基类虚函数,然后是子类自定义虚函数,如下图所示。
(3)子类同时继承多个基类
例如子类D同时继承了基类A、B、C
//基类 class A{ public: virtual void F(void); }; class B{ public: virtual void G(void); }; class C{ public: virtual void H(void); }; //子类 class D:public A,B,C{ public: virtual void K(void); };
实际的虚函数表是这样的,这个时候D类对象有三个指针指向了这三个虚函数表,按照继承的类从上到下,每个虚函数表内部和单继承是一样的道理。
这个时候求sizeof(D) = 12,因为有三个指针指向三个虚函数表,每个指针4个字节。