通过pShape指向不同的对象, 将会调用不同方法,可以再创建一个CCircle类继承于CShape类来观测这种结果。
CShape* pShape = new CCircle();
pShape->display();//调用CCircle的display方法
pShape = new CRect;
pShape->display();//调用CShape的display方法
如果你还不是很清楚虚函数的用法或者说你不打算深入探究虚函数的实现原理,建议一下内容就不要看了。
只要一个类有虚函数(继承下来的或者是本身的),它就会有一个vptr,vptr是一个指针,执行一个vbtl虚函数表。你可以把vbtl想象成一个指针数组,它的数组的元素是一个个的函数指针,指向它自己的虚函数,一般还会有一个指向typeinfo的结构的指针,以实现运行时刻类型识别。
简单说来,现在CRect有一个虚函数display,那么他的vbtl会有两个元素,一个是typeinfo指针,一个是dispaly方法指针。
2)简单看看typeinfo:
CShape* pShape = &rect1;
if(typeid(rect1) == typeid(CRect) && typeid(*pShape) == typeid(CRect)){
TRACE_FUCTION_AND_LINE("rect1 is type CRect");
TRACE_FUCTION_AND_LINE("rect1 name:(%s) raw_name:(%s)", typeid(rect1).name(), typeid(rect1).raw_name());
}
通过上述代码可以清楚看到可以在运行时候判断一个对象时什么类型,即使将rect1的地址转换父类指针,依然可以判断出它实际是什么类型。
typeinfo是一个类一个,编译器编译的时候静态静态分配,每一个带有虚函数的类的对象都会有一个指针指向它,同一个类的对象指针应该一致下面开始测试这一猜想。
看有些书上说的是typeinfo的结构指针位于vtbl虚表的第0个item上,但是我在vc++编译调试没能找到,第0个item上是虚函数display的地址。
于是又改在仿linux环境,MINGW下测试,在第-1个item选项上找到了typeinfo的地址,很是欣慰。但是在windows上还是始终找不到,猜测是在-1的item选项上,不过这个选项应该还有其他的东西,我这么推断的主要原因是除了-1和0item这两个位置,它们的前后地址都是0. 只是-1这个item的位置应该被微软又封装了一下,而不是单纯的指向typeinfo结构。
以下为测试代码:
const type_info* ptypeidinfo = &(typeid(rect1));
TRACE_FUCTION_AND_LINE("ptypeidinfo: %p", ptypeidinfo);
int *p = (int*)&rect1;
int *pp = (int*)(p[0]);//vptr
type_info *prectinfo = (type_info*) (*(pp-1));//pp-0: virtual function address
TRACE_FUCTION_AND_LINE("prectinfo: %p", prectinfo);
vs2008输出:
main: 36] ptypeidinfo: 003A9004
main: 40] prectinfo: 003A7748
MINGW输出:
main: 36] ptypeidinfo: 004085a4
main: 40] prectinfo: 004085a4
3)回到问题 (上面的例子没有考虑带有虚函数的情况):
父类和子类都有一个虚函数,这样每一个子类对象的父类部分也就自动向下偏移,因此可以看到现在的fakerect是第二个int变量被改变了,这个比较容易理解,对于SetColor方法来说,它的参数是CShape,那么他就CShape的m_color的偏移地址(此处为4)进行赋值。试想另外一种情况,父类没有虚函数,子类有虚函数,这样父类占用的内存为4,子类的内存占用仍有vptr的4个字节内存。
测试:可以将父类的display虚函数注释掉,打印结果如下:
fakerect:1, 1, 1, 1
fakerect:1, 20, 1, 1
此时父类对象部分就不是位于子类对象起始部分,子类对象最起始是vptr,而SetColor还根据它本身的参数CShape类型来进行偏移。对于CShape来说,它看到的仅仅是四个字节的m_color,它对m_color进行赋值的时候,就会对pShape指针的最开始四个字节赋值,然而传递过去的fackrect对象的首地址却是fake1变量,而被改变的确是fake2变量,这个到底是怎么回事呢??
传递过去的首地址是fake1,SetColor改变也是传递过去的首地址,而最终被改变的是fake2,这个肯定是有一些内部转换机制在作怪,猜测应该讲传递过去的( CRect* pfakerect = (CRect*)&fakerect; pfakerect->SetColor(20);) pfakerect指针在被强转为CShape*的时候地址被自动偏移了4个字节,下面进行测试:
CShape类的SetColor方法,打印this指针地址:
void CShape::SetColor(int colore)
{
//TRACE_FUCTION_AND_LINE("");
TRACE_FUCTION_AND_LINE("pShape:%p", this);
m_color = colore;
}
main.cpp程序,打印传递过去的pfakerect地址:
CRect* pfakerect = (CRect*)&fakerect;
TRACE_FUCTION_AND_LINE("fakerect:%p", pfakerect);
pfakerect->SetColor(20);
输出结果:
main: 27] fakerect:002DF958
CShape::SetColor: 12] pShape:002DF95C
可以清楚的看到传递过去的地址是002DF958,而setColor在运行的时候处理的首地址却是002DF95C,这个应该是pfakerect转化为pShape时候的自动偏移的。
为了更清楚的感觉到这种变化,现在直接在main.cpp加入测试代码:
main.cpp:
CRect* pfakerect = (CRect*)&fakerect;
TRACE_FUCTION_AND_LINE("fakerect:%p", pfakerect);
pfakerect->SetColor(20);
CShape* pfakeshape = pfakerect;
TRACE_FUCTION_AND_LINE("pfakeshape:%p", pfakeshape);
输出结果如下