最近看编程规范时看到关于虚函数的一些说明,参考了《深入浅出MFC》书中的说明,对虚函数有了一点想法,总结下来以便以后回顾。
虚函数为什么可以实现动态绑定实现多态?
首先需要明确类对象在内存中的分配,有两个方面需要说明。首先是类成员变量,每个类对象的成员变量在程序中都拥有自己的专有内存,保存着自己的内容。其次类成员函数对所有对象来说都是共用的,类成员函数在内存中只有一份代码。
对于非虚函数来说,编译器会根据对象的类型定位到指定的函数地址,因此无论基类的指针指向的是哪个对象,其都会调用基类的函数,这是在编译时就确定好的。
对于虚函数来说情况就不一样了,为了实现运行时多态,当我们在类中定义虚函数时,编译器会在类中添加一个指向虚函数表的指针,该指针位于类对象的起始位置。虚函数表可以看作是一个函数指针数组,里面包含的是类定义的虚函数地址。
示例如下:
class base
{
public:
virtual void show(void)
{
cout << "this is base::show" << endl;
}
public:
int a;
};
void main(void)
{
base b;
cout << sizeof(b) <<endl;
cout << &b << "\r\n" << &b.a << endl;
}
输出结果为:
我们使用基类指针指向派生类对象,在调用虚函数时,程序就会从对象内存中找到指向虚函数表的指针,虽然我们使用的是基类的指针,但指向的内存是派生类对象的专有内存,因此找到的指针指向的也是派生类的虚函数表,从而在虚函数表中找到也是派生类虚函数的地址,从而实现了运行时多态。
在举个例子:
class base
{
public:
virtual void show(void)
{
cout << "this is base::show" << endl;
}
};
class derived : public base
{
public:
virtual void show(void)
{
cout << "this is derived::show" <<endl;
}
};
void main(void)
{
derived d;
base *pBase = &d;
pBase->show();
((base *)(&d))->show();
((base)d).show();
}
输出代码的输出为
前两行输出与我们预期的一样,无论怎么变化,始终调用的是d专有内存中的内容,但第三行却出乎意料,难道调用的已经不是d的专有内存了?我们可以试验一下,在main函数里加入如下代码,
cout << &d << endl;
cout << &((base)d) << endl;
输出为
果然强制转换之后
(base)d已经不是原来的内存了,当我们执行(base)d语句时,编译器已经为我们创建了一个临时变量,该变量的类型就是base(毕竟我们要的就是base类型的变量),那么在新的内存中保存的自然也就是指向base虚函数表的指针了。