一个简单的C++问题请教
编译器BC3.1
程序如下
#include "iostream.h"class A
{public:
  A() {cout<<"A create"<<endl;}
   ~A() {cout<<"A delete"<<endl;}
  void afunc() {cout<<"Afunc run"<<endl;}
 }; class B:public A
 {public:
   B() {cout<<"B create"<<endl;}
   ~B() {cout<<"B delete"<<endl;}
  void bfunc() {cout<<"bfunc run"<<endl;}
};void Test(B* b)
{b->bfunc();}void main()
{A* a=new A;//1)A* a;
Test((B*)a);
……
}
运行时会显示:
A create
bfunc run
如果将A* a=new a;换成A*a(不申请内存),那么运行时会显示
bfunc run问题:
1)为什么一个指向A的指针会有bfunc这个成员函数,Test((B*)a)这个强制转换到底做了什么工作?
2)如果将A* a=new a;换成A*a为什么运行时仍会显示bfunc run?根本没有申请内存那么bfunc这个函数是放在什么地方的?
请问哪位大侠详细给我讲讲,谢谢!

解决方案 »

  1.   

    我是一个新手,但我想试着回答一下:
    1)虽然在“A* a=new A"中使a指向A,但在紧跟着的“Test((B*)a)"中(B*)a修改了指针的指向,使a指向了B,所以此时a的指向实际是B;
    2)换成A*a后,Test((B*)a)仍然发挥作用,至于bfunc放在什么地方,我也不太清楚。
      

  2.   

    问题一:
    由于A、B两个类具有简单的继承关系,从而之间可以使用强制类型转换,使用了强制类型转换后,可以使得编译器将一个对象a占据的内存看成对象b的内存表示。当然这样很不安全。因为一旦在类B的成员函数使用了不属于类A的数据成员,将引起访问异常!
    由于常规的类的成员函数在实现上并不属于类,而是属于全局的函数。只是调用时必须通过相应的类来实现(编译器的语法要求)。
      b->bfunc();在编译器眼里实际上是:bfunc(B *this)//this指针亦即传递进去的对象的内存的首地址
      根据你的两个类的定义可知:类A和类B的内存布局完全相同-------因为两者都没有数据成员;当然了在理论上他们的内存表示应该都是占用0个字节,但是这样就无法在内存中对他们的实例进行标示,从而实际上他们在内存中都占用了一个字节(在Visual C++ 测试的结果)。
      由于你是使用了简单的单继承,从而在(B*)a的转换过程中并没有发生什么!只是将a占据的内存区看作了b的内存区(这很安全,因为二者的内存表示完全一样)
      一旦你在函数bfunc()引用到了只属于B类的数据成员,程序就一定会出错!
      问题二:同问题一的回答该函数在全局数据区,不属于类B,只是在使用时必须通过类B做中介-------需要this指针!
      虽然没有申请内存,可在定义变量:  A * a;时,a中一定有一个不确定的地址值(即使是NULL)。你可以将其打印出类看一下,编译器将此地址看作对象b的内存空间的首地址,也就顺理成章了完成了函数的调用!(在函数中没有使用任何的数据成员)!
      以上是本人的一点理解!欢迎高手指点!
      ------------------------------------------------
      南京、扬子石化
      

  3.   

    To : ameba(百年孤独) 过奖!过奖!
    小弟只是最近在学习COM/COM+,看了一点相关的C++底层实现机制而已!
      

  4.   

    psusong(我心飞扬) 回答得很详细,这样的回答才是有价值的。
    gz.
      

  5.   

    To snowrain(雪雨) :
    谢谢夸奖!说句实话,小弟当初学习COM/COM+时,为了弄懂不同的虚函数指针之间是如何转化的,费了很大的劲!
    现在终于明白了!
    但是我不希望每个人都像我一样经历同样的痛苦!
      

  6.   

    psusong(我心飞扬) 的解释应该是正确的,但
    一旦你在函数bfunc()引用到了只属于B类的数据成员,程序就一定会出错!
    这句话不一定对。
    修改程序如下:
    #include "iostream.h"class A
    {
    public:
      A()
      {
      cout<<"A create"<<endl;
      }
       ~A()
       {
       cout<<"A delete"<<endl;
       }   
       void afunc()
       {
       cout<<"Afunc run"<<endl;
       }
    };
    class B:public A
    {
    int i;
    public:
    B():i(100)//此处的初始化并没有起到真正的作用原因上面已说
       {
       cout<<"B create"<<endl;
       }
       ~B()
       {
       cout<<"B delete"<<endl;
       }   
       void bfunc()
       {
       i=999;
       cout<<"bfunc run"<<endl;
       cout<<"i="<<i<<endl;
       }
    };void Test(B* b)
    {
    b->bfunc();
    }void main()
    {
    A* a=new A;//1)A* a;
    Test((B*)a);
    }
    运行的结果同原来现象差不多
      

  7.   

    希望大家看到下面的程序不会大吃一惊:
    #include "iostream.h"class A
    {public:
      A() {cout<<"A create"<<endl;}
       ~A() {cout<<"A delete"<<endl;}
      void afunc() {cout<<"Afunc run"<<endl;}
     }; class B:public A
     {public:
       B() {cout<<"B create"<<endl;}
       ~B() {cout<<"B delete"<<endl;}
      void bfunc() {cout<<"bfunc run"<<endl;}
    };void Test(B* b)
    {b->bfunc();}void main()
    {
    //A* a=new A;//1)A* a;
    char *a;
    Test((B*)a);
    }
    我不同意psusong(我心飞扬)的说法,其实(B*)a转换的只是指针
    以上根本就没有生成类的对象,或者说生成了一个临时的对象,
    与楼主的情况一样!!!!!!!!!!!!!!
      

  8.   

    To: zf925(天下哪来那么多高手) 
    我的意思是并不是说编译器生成了类的对象,只是说在编译器的内在要求中需要一个对象的存在,但是那个对象到底真正的存在与否,其实编译器并不知道,也不需要知道!(只要你调用的时候不违反使用规则!)
    实际上你所说的使用char * a;进行参数传递的函数调用!这未尝不可,只要他是个地址值即可,因为这样通过强制类型转换决可完成函数的调用!
    例如下面的调用:
    main()
    {
    .......
    char *a;
    double *fd;
    double d;
    void *v;
    Test((B*)a);
    Test((B*)fd);
    Test((B*)&d);
    Test((B*)v);
    ......
    }
    只要传递进去的是一个地址即可!但是编译器将该地址当作类B的实例的this指针!
      

  9.   

    To:rockhard(探索中...) 
    你所说的现象在Win2000 server 中不可能出现!
    因为这样在函数Test()中将引用到不属于自己的地址的变量!这在Win2000 Server 中绝对不允许!
    我怀疑你的os是win98之类!如果不出现问题,那也是因为偶然原因!决不能沾沾自喜!
      

  10.   

    比如说我把一个指向word的类型的指针强制转化成指向char的指针,那么我用该指针的到的数一定是一个char型的,因为他的机制是在指针标明的地址上取一个指针类型的东西,所以我们把类强制转换后,系统就认为你的指针指向的是强制后的类,也就是认为a是b,于是系统到a的实例中按照地址偏移量
    访问了一个函数,当然访问的是a中的fonction了
    于是输出的只是一个a
      

  11.   

    to akiy:为什么一个指向A的指针会有bfunc这个成员函数?
      

  12.   

    指针的值都是一样的,决定它的成员只是指针的类型。我一个void *的指针强制转化成B *后也就能调用 ->bfunc(),指针的类型才是关键。系统执行时可不管你的指针类型,指针类型是编译器实现的。
      

  13.   

    to psusong(我心飞扬) :
    怀疑你的os是win98之类!如果不出现问题,那也是因为偶然原因!决不能沾沾自喜! 我的OS是win2000 professional。在server下应该也是可以的,你可以试试。
    但象楼主那样:如果将A* a=new a;换成A*a(不申请内存),那么运行时产生访问错误。
      

  14.   

    >我的OS是win2000 professional。在server下应该也是可以的,你可以试试。
    >但象楼主那样:如果将A* a=new a;换成A*a(不申请内存),那么运行时产生访问错误。你没有出访问错误不代表你没有访问到不正确的内存.....
      

  15.   

    To:charlyisme(John) :
    为什么一个指向A的指针会有bfunc这个成员函数?
    -----------------------------------------------
    你怎么还认为类的成员函数就一定属于类的作用域呢?
    类的成员函数在编译器眼里是和一般的全局函数没有什么多大的区别!只是在函数的调用上有特殊的语法要求而已!
    指针的作用域内怎么会有函数呢?如果有的话,用指针的4个字节长度的内存空间如何能够表示多余的函数的地址呢?
      

  16.   

    To:rockhard(探索中...) 
    I have tested this programme in the Windows2000 Server,once you usethe variable that is only belong to the class B,the computer just  throws a exception of violate access memory!
      

  17.   

    大家讲了这么多有不就是关于this指针的解释
    以下是我的个人观点
    1.编译器在调用类对像是会用ECX寄存器存放this指针(对于ThisCALL调用协定解释)
    即所有的成员函数引用自身成员变量时都有一个this指针的偏移量,所以对像中就有类的概念,而成员函数(虚函数除外)则仅仅是一个函数,当这个函数不用this指针时(如前面的程序),this指针传给成员函数与不传给成员函数是一样的(即函数与写成全局函数执行结构是一样的),但当成员函数有引用自生成成员变量时,就要能过this指针来计算成员变量,因为传入了错误的this指针那样将会出错
    2。类型转换的问题
    先说派生的原理:
    派生是将父类成为子类的一个成员的,基类与在成员变量之前开始的(即第一基类与子类this指针相同)
    如下面A类也是从B类派生的
    class A
    {
    class B//A中生成一个类B
    {};
    }
    ////////////////////////////
    这就产生了类型转换的问题,下面分种情况分别列出
    如下:
    第一种情况:
    class A
    {
    };
    class B:(A与B不相关)
    {
    }或
    class B:public C , A(A不是B的第一基类)
    {
    }
    如果是这种情况的话将A对像指针转换成B将不安全,因为A、B关于this指针的传入偏移量是不对的第二种情况:
    class A
    {
    };
    class B:public A, <c>(A是B的第一基类)
    {
    }
    如果将B对像转换成A对像将是安全的因为A与B有相同的偏移地址注:将B强型转换成C也是安全的(这时主要是编译器的功劳,因为编译器在类型转换中用static_cast进行类型转换,如果成功就用static_cast返回的指针---所以编译器建议用static_cast进行类型转换,这样对于上面第一种情况会报错第三种情况:
    class A
    {
    };
    class B:public A, <c>(A是B的第一基类)
    {
    }
    如果将A对像转换成B对像指针将可能成功可能不成功(这里我不用“安全”----即然不成功肯定是不安全)
    因为持有A指针的对像有可能是B对像,也有可能是A对像或A的其它派生对像3。类(确切的说应是域名--name space)的作用
    类的作用有二:
    1。告诉编译器这是要有this指针的
    2。产生一个域,在域外将加在域名才能找到成员(即在编译时产生类的概念)----大家都知道静态函数的调用方式,这里我再举一个类域的使用:
    class A
    {
    public:
    enum tagLorR{LEFT=0,RIGHT};
    tagLorR fun(){m_t=RIGHT;return LEFT;};//这个谁都知道
    tagLorR m_t;
    }
    如果你想在类的外面给m_t付值,如果不准用强性类型转换的话就只用域
    即Obj.m_t=A::LEFT;
      

  18.   

    除了this_Call,BC中引以为荣的是FAST_CALL,FAST call的this指针基本相同只是传入的前两个参数用寄存器罢了
    实际是VC也可设置为fast call(只不过不是默认罢了)
      

  19.   

    once168(once168) 的回答很详细,不错!
      

  20.   

    once168(once168) 讲得还不错,大家可以看一看!