一个关于基类和派生类指针的问题
直接上程序吧:
- C/C++ code
#include <iostream>using namespace std;//没有使用虚函数的继承派生关系class Base{public: Base(int i = 0):ival(i){} void getVal() { cout<<ival<<endl; }private: int ival;};class Derived:public Base{public: Derived(int i = 0, int j = 1):Base(i),ival(j){} void getVal() { cout<<ival<<endl; }private: int ival;};//使用了虚函数的继承派生关系class Base1{public: Base1(int i = 0):ival(i){} virtual void getVal() { cout<<ival<<endl; }private: int ival;};class Derived1:public Base1{public: Derived1(int i = 0, int j = 1):Base1(i),ival(j){} void getVal() { cout<<ival<<endl; }private: int ival;};void useBaseObj(Base b){ b.getVal();}void useDerivedObj(Derived d){ d.getVal();}void useBasePtr(Base *pb){ pb->getVal();}void useDerivedPtr(Derived *pd){ pd->getVal();}void useBase1Obj(Base1 b){ b.getVal();}void useDerived1Obj(Derived1 d){ d.getVal();}void useBase1Ptr(Base1 *pb){ pb->getVal();}void useDerived1Ptr(Derived1 *pd){ pd->getVal();}int main(){ Base b; Derived d; Base *pb = &b; Derived *pd = &d; useBaseObj(b);//基类实参,基类形参,调用基类函数 useDerivedObj(d);//派生类实参,基类形参,自动转化,调用基类函数// useDerivedObj((Derived)b);//无法用基类(自动的)构造出一个派生类 useDerivedObj(d);//派生类实参,派生类形参,调用派生类函数 cout<<endl; useBasePtr(pb);//指向基类形参,指向基类实参,调用基类函数 useBasePtr(pd);//指向基类形参,指向派生类实参,调用静态类型-基类函数// useDerivedPtr(pb);//指向基类实参,指向派生类形参,无法自动转化 useDerivedPtr((Derived*)pb);//强制类型转化,打印结果为随机数 //为什么? useDerivedPtr(pd);//指向派生类实参,指向派生类形参,调用派生类 cout<<endl; pb = new Derived;//静态类型为指向基类,动态类型为指向派生类 useBasePtr(pb);//调用基类 useDerivedPtr((Derived*)pb);//调用派生类 cout<<endl; Base1 b1; Derived1 d1; useBase1Obj(b1);//基类实参-基类形参,调用基类函数 useBase1Obj(d1);//派生类实参-基类形参,发生派生类到基类的转化,调用基类// useDerivedObj((Derived)b);//基类实参,派生类形参,无法类型转化,函数报错 useDerived1Obj(d1);//派生类实参-派生类形参,调用派生类函数 cout<<endl; Base1 *pb1 = &b1; Derived1 *pd1 = &d1; useBase1Ptr(pb1);//指向基类的实参,指向基类的形参,调用基类函数 useBase1Ptr(pd1);//指向派生类实参,指向基类的形参,根据多态,调用派生类// useDerivedPtr(pb1);//指向基类的实参,指向派生类形参,不匹配 useDerived1Ptr((Derived1*)pb1);//通过强制类型转换使其匹配,调用基类函数 //为什么? useDerived1Ptr(pd1);//指向派生类的实参,指向派生类的形参,调用派生类 cout<<endl; pb1 = new Derived1;//形参静态类型为基类,动态类型为派生类 useBase1Ptr(pb1);//根据多态,调用派生类 useDerived1Ptr((Derived1*)pb1);//直接调用派生类 return 0;}
程序中的注释部分有两处不明白,请指教?
PS:其他的注释部分应该都没啥问题吧?
[解决办法]
第一个问题的答案:
当你将pb强制转换为Derived类指针,并输出,实际上,UseDerivedPtr会认为传进来的就是一个Derived类型的对象的指针。所以,它会试图输出Derived对象的ival值。而对于一个Derived对象来说,它应该包含两个int成员,一个是基类的,而另一个是Derived自身定义的。所以,Derived对象的大小(size)应该是8字节,而Base对象只有4字节。
当你定义Base对象时,它自身包含的4字节以外的内容对它来说都是无意义的。所以,你把它的指针传给UserDerivedPtr,则后者会输出这个无意义的值,即随机数。
第二个问题的答案:
这个问题就需要用编译器实现的理论来回答了。对于没有虚函数的派生来说,编译器所做的中间代码转换是非常简单的。即直接找到指针对应的原始类类型,然后取查找它的函数地址,并执行,例如:
Base b;
Derived d;
Base* pb = &b;
Derived* pd = &d;
下面的方法调用及其对应的编译器中间转换结果分别为:
b.getVal() --> Base::getVal(&b);
d.getVal() --> Derived::getVal(&d);
pb->getVal() --> Base::getVal(pb);
pd->getVal() --> Derived::getVal(pd);
(注意):
pd = (Derived*)pb;
pd->getVal() --> Derived::getVal(pd);
这也解释了第一个问题的现象。
之所以这么翻译,是因为对于没有虚函数的类而言,所有的调用都是可以在编译器确定的。编译器看见对象、指针的类型,就能确定需要调用的方法到底是哪一个。
而如果有虚函数,情况就完全不一样。只要类拥有虚函数,则在对象的内存布局中,就会隐含添加一个虚函数表指针(vtable),这个函数表指针指向一个数组,该数组每一个元素对应该类所拥有的虚函数地址。
编译器在对这种情况做中间翻译时,就不一样了:
Base1 b1;
Derived1 d1;
Base1* pb1 = &b1;
Derived1* pd1 = &d1;
调用及中间翻译为:
b1.getVal() --> Base1::getVal(&b1);
d1.getVal() --> Derived1::getVal(&d1);
pb1->getVal() --> (*pb1->vtable[0])(pb1); // 这个是使用函数指针进行函数调用的语法
pd1->getVal() --> (*pd1->vtable[0])(pd1);
基于对象的方法调用和前面还是一样的,没有任何区别。而有趣的地方在于使用指针进行方法调用。如果对象拥有虚函数表,且被调方法是虚方法。则编译器会从虚函数表中取出该方法的地址,而不是直接确定。
虽然你的代码不正确,但最有趣的地方也可由它来说明:
Derived1* pd1 = (Derived1*)pb1;
pd1->getVal(); --> (*pd1->vtable[0])(pd1);
这和上面的中间翻译没有区别,正是这样,才使得行为得以保证。
即使指针类型进行了强制转换,但编译器看到的只是一个“整数”,该整数意味着一个内存地址。此外,它的类型是Derived1对象的指针。但pb1所指向的内存没有发生任何改变,也就是说,它的虚函数表没有发生变化,依然指向Base1的虚函数表。所以当执行被转换后的代码时,所执行的依然是Base1的getVal()方法。
[解决办法]
useDerivedPtr((Derived*)pb);//强制类型转化,打印结果为随机数
//为什么?
这里的 pb 是 Base 类的指针, Derived 是 Base 类的子类,由基类强制转换成子类,那么子类的数据会丢失。
应为基类没有包含子类的数据
useDerived1Ptr((Derived1*)pb1);//通过强制类型转换使其匹配,调用基类函数
//为什么?
这里则反之,子类是包含有基类的数据的,所以强制转换后数据不会丢失
[解决办法]
不能不基类的对象或指向基类的指针,因为基类中的成员并不包含派生类的,由基类转换到派生类之后,转换出来的对象访问派生类中特有的成员的时候,但其转换来的对象里面并不包含这些成员函数的值,所以访问会出现问题,当然可以用一种动态转换,但这种转换时把基类类型的指针或引用指向派生类,可以把这种基类型的指针或引用转化为派生类的。
例如:
base b;
base *pb;
derive d;
pb=&d;
derive *pd;
pd=dynamic_cast<derive *>(pb);
像这种转换是可以实现的!