虚函数的概念
虚函数是实现C++中面向对象的三大特性之一——多态的一种技术手段。
什么是多态呢?
在基类的成员函数前,使用virtual
修饰得到的就是虚函数。在派生类中,对继承得到的虚函数,进行重写,使得派生类对继承的方法有了自己的具体实现。在外部函数中,通过基类指针调用类的接口,在程序运行时,会根据指针绑定的对象选择对应的接口,这便是多态。多态简单来说,是让同一种事物有了多种不同的表现。
虚函数的具体实现
基类指针绑定了类对象后,程序运行时,是怎么知道该运行哪个类的接口函数呢?这便是本篇文章要探究的问题。
虚函数表
C++为了实现虚函数的动态绑定,在每一个类(当然是含有虚函数的类)中,都有一张虚函数表(Virtual Table),通过指向虚函数表的指针(Virtual Table Pointer)来选择执行的虚函数。
我将从以下三种情况,和大家一起研究虚函数表的分布:
- 派生类不重写基类虚函数
- 派生类重写了基类的虚函数
- 多重继承下虚函数表的分布
1.派生类不重写基类虚函数
有如下的基类和派生类
class Base{
public:
virtual void f1(){ cout << "Base 的 f1" << endl; }
virtual void f2(){ cout << "Base 的 f2" << endl; }
};
class Derived : public Base{
public:
// 不重写基类的虚函数 f1 和 f2
// void f1(){ cout << "Derived 的 f1" << endl; }
// void f2(){ cout << "Derived 的 f2" << endl; }
// 派生类自己的虚函数 f3
virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
// 使用typedef定义一个函数指针
typedef void(*Fun)(void);
// 测试函数
void vptrTest(){
Derived d;
Base *pb = &d;
cout << "Base的大小:" << sizeof(Base) << " Derived的大小:" << sizeof(Derived) << endl;
cout << "Derived的首地址" << (int *)&d << endl;
int *pfun = ( (int *)*((int *)&d) );
cout << "虚函数表的第一个函数的地址" << pfun << endl;
Fun fun1 = (Fun)*pfun;
fun1();
pfun+=2; // 此时指针是8字节的,64位机器嘛
Fun fun2 = (Fun)*pfun;
fun2();
pfun+=2;
Fun fun3 = (Fun)*pfun;
fun3();
}
程序的运行结果:
没有被重写基类的虚函数f1(), f2()
, 派生类自己的虚函数f3()
分布情况:
2.派生类重写基类虚函数
派生类重写继承的f1()
函数
class Base{
public:
virtual void f1(){ cout << "Base 的 f1" << endl; }
virtual void f2(){ cout << "Base 的 f2" << endl; }
};
class Derived : public Base{
public:
// 不重写基类的虚函数 f1 和 f2
void f1(){ cout << "Derived 的 f1" << endl; }
// void f2(){ cout << "Derived 的 f2" << endl; }
// 派生类自己的虚函数 f3
virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
// 使用typedef定义一个函数指针
typedef void(*Fun)(void);
// 测试函数
void vptrTest(){
Derived d;
Base *pb = &d;
cout << "Base的大小:" << sizeof(Base) << " Derived的大小:" << sizeof(Derived) << endl;
cout << "Derived的首地址" << (int *)&d << endl;
int *pfun = ( (int *)*((int *)&d) );
cout << "虚函数表的第一个函数的地址" << pfun << endl;
Fun fun1 = (Fun)*pfun;
fun1();
pfun+=2; // 此时指针是8字节的,64位机器嘛
Fun fun2 = (Fun)*pfun;
fun2();
pfun+=2;
Fun fun3 = (Fun)*pfun;
fun3();
}
程序的运行结果:
所以,对于单继承模式,虚函数表的分布,可以总结为:
- case1:派生类不重写基类虚函数:虚函数表中只有基类的虚函数,存储的顺序按照函数定义的顺序来。如果派生类还有自己的虚函数,存放在基类的虚函数之后。
- case2:派生类重写了基类的虚函数:基类的虚函数被派生类的重写函数取代。位置顺序不发生改变
3.多继承情况
不重写虚函数的情况:
class Base1{
public:
virtual void f1(){ cout << "Base1 的 f1" << endl; }
virtual void f2(){ cout << "Base1 的 f2" << endl; }
};
class Base2{
public:
virtual void f1(){ cout << "Base2 的 f1" << endl; }
virtual void f2(){ cout << "Base2 的 f2" << endl; }
};
class Derived : public Base1, public Base2{
public:
// 不重写基类的虚函数 f1 和 f2
// void f1(){ cout << "Derived 的 f1" << endl; }
// void f2(){ cout << "Derived 的 f2" << endl; }
// 派生类自己的虚函数 f3
virtual void f3(){ cout << "Derived 的 f3" << endl;}
};
typedef void(*Fun)(void);
void vptrTest(){
Derived d;
Base1 *pb = &d;
cout << "Base的大小:" << sizeof(Base1) << " Derived的大小:" << sizeof(Derived) << endl;
cout << "Derived的首地址" << (int *)&d << endl;
int *pfun = ( (int *)*((int *)&d) );
cout << "虚函数表的第一个函数的地址" << pfun << endl;
Fun fun1 = (Fun)*pfun;
fun1();
pfun+=2; // 此时指针是8字节的,64位机器嘛
Fun fun2 = (Fun)*pfun;
fun2();
pfun+=2;
Fun fun3 = (Fun)*pfun;
fun3();
int *vt = (int *)&d;
vt+=2;
pfun = ( (int *)*(vt) );
Fun fun4 = (Fun)*pfun;
fun4();
pfun+=2;
Fun fun5 = (Fun)*pfun;
fun5();
}
重写虚函数f1()
:
通过这两个小demo,我们得知,在多重继承的情况,虚函数的分布成了这样:
- case1:没有重写继承的虚函数:有多少个基类,派生类就有多少张表。每一个基类都有一张单独的虚函数表。派生类自己的虚函数放在第一个基类的虚函数表中
- case2:重写继承的虚函数:基类的虚函数被派生类的重写函数取代。位置顺序不发生改变