#include "stdio.h"
class Base
{
public:
int Bar(char x)
{
return (int)(x);
}
virtual int Bar(int x)
{
return (2 * x);
}
};
class Derived : public Base
{
public:
int Bar(char x)
{
return (int)(-x);
}
int Bar(int x)
{
return (x / 2);
}
};
int main(void)
{
Derived Obj;
Base *pObj = &Obj;
printf("%d,", pObj->Bar((char)(100)));
printf("%d,", pObj->Bar(100));
}
如果不定义虚函数
当我们定义了派生类的对象,并取它的地址赋值给基类的指针,这时编译器会自动为派生类对象进行类型转换,将派生类对象转换为基类对象,站在内存的角度来看,访问的就是基类的成员。如:
int Bar(char x)//父类中没有定义虚函数
Derived Obj;
Base *pObj = &Obj;
printf("%d,", pObj->Bar((char)(100)));// 在pObjd调用Bar(char)时 在编译时期自动把 对象类型由Derived转为Base
printf("%d,", pObj->Bar(100));// 定义了虚函数 后
在继承体系下,将父类的某个函数给成虚函数(即加上virtual关键字),在派生类中对这个虚函数进行重写,利用父类的指针或引用调用虚函数。
父
virtual int Bar(int x)
{
return (2 * x);
}
子
int Bar(int x)
{
return (x / 2);
}
printf("%d,", pObj->Bar(100));
// 在基类的函数前加上virtual关键字,
//在派生类中重写该函数,
//运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类
//就调用派生类的函数,如果对象类型是基类,就调用基类的函数。
第一:编译器在发现Father 类中有虚函数时,会自动为每个含有虚函数的类生成一份虚函数表,也叫做虚表,该表是一个一维数组,虚表里保存了虚函数的入口地址。
第二:编译器会在每个对象的前四个字节中保存一个虚表指针,即(vptr),指向对象所属类的虚表。在程序运行时的合适时机,根据对象的类型去初始化vptr,从而让vptr指向正确的虚表,从而在调用虚函数时,能找到正确的函数。
第三:所谓的合适时机,在派生类定义对象时,程序运行会自动调用构造函数,在构造函数中创建虚表并对虚表初始化。在构造子类对象时,会先调用父类的构造函数,此时,编译器只“看到了”父类,并为父类对象初始化虚表指针,令它指向父类的虚表;当调用子类的构造函数时,为子类对象初始化虚表指针,令它指向子类的虚表。运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。从而实现运行时多态。