读书人

一个关于基类和派生类指针的有关问题

发布时间: 2012-11-09 10:18:48 作者: rapoo

一个关于基类和派生类指针的问题
直接上程序吧:

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);
像这种转换是可以实现的!

读书人网 >C++

热点推荐