C++基础#8:类层次中的类转换

1. 引子:

利用面向对象中的继承特性,程序员可以构建类的层次。在真实的面向对象软件系统中,经常可以看到如下的情形:

ClassA  a = new ClassB();
其中,ClassB继承自ClassA。即,创建了一个类B的实例,赋值给类A的变量,这里,就涉及到面向对象中的类转换。
我们知道,C/C++语言是类型安全的。这是因为面向对象中,会进行类型的转换,有时候,必须使用显示转换,例如:


ClassA  a = new ClassB();
ClassB b = (ClassB)a;

对于类型转换,当单基派生,多基派生时,各自是如何进行类转换的,这是本节的主题。

2. 单基派生的情形:
对于
    class base
    {
    };
   
    class derived: public base        //public inheritance
    {
    };
有以下赋值兼容规则可以遵循:
(1). 派生类对象可以赋值给基类对象
   但是,因为基类对象不具有派生类对象所具有的成员,因此基类对象不能赋值给派生类对象,强转也不行。
   derived d;
   base b;
   b = d;  // OK, 但是,会产生切割问题(即对象b不能访问对象d附加的功能)
   d = b; // error
   d = (derived)b; // error

(2).派生类对象可以初始化基类的引用
   derived d;
   base& br=d; //OK
但是,如下语句却是错误的(原因同 "一.1"):
   base b;
   derived& d = b; // error: invalid initialization of reference of type 'derived&' from expression of type 'base'
  
(3).派生类对象的地址
  a.   派生类对象的地址可以赋给指向基类的指针;
      derived d;
      base *pb=&d; //OK
      这样,指向基类的指针,可以用来指向派生类对象中从基类继承下来的成员,但不包括派生类中追加的成员,除非采用强制类型转换(见例1:convert_test1.cpp);
      而且,在这种情况下,调用的是基类的成员函数,除非派生类进行了虚函数继承(见例3:convert_test3.cpp)。
     

/* 例1:convert_test1.cpp   */
      /* IDE环境: Dev-C++ 4.9.9.2 */
     
      #include <stdio.h>
      class base
      {
          public:
                 base():i(0){ };
                 int get_i(){ return i; }
                 void set_i(int x){ i = x;}
                 void display_i(){ printf(" i = %d /n", i);}
          private:
                  int i;
      };
     
      class derived: public base        //public inheritance
      {
          public:
                 derived():j(0){ };
                 int get_j(){ return j; }
                 void set_j(int x){ j = x; }
                 void display_j(){ printf(" j = %d /n", j);}
          private:
                  int j;
      };
     
      int main()
      {
      derived d;
      base *pb=&d; //OK
      pb->get_i();
      //pb->get_j(); //compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'get_j'
                     //指向基类的指针,可以用来指向派生类对象中从基类继承下来的成员,但不包括派生类中追加的成员,除非对基类指针采用强制类型转换
                    
      base *pb2 = &d;
      //pb2->set_j(4);            //compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'set_j'
      //(derived*)pb2->set_j(4); //compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'set_j'
      ((derived*)pb2)->set_j(4); //OK, 必须对基类指针进行强转,才可以访问派生类的成员
      
      pb2->display_i();         
      ((derived*)pb2)->display_j(); // OK,有必须对基类指针进行强转,才可以访问派生类的成员
     
          while(1);
      }
     
      运行结果:
        i = 0
        j = 4

代码说明:

//pb->get_j();
//compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'get_j'
编译错误,指向基类的指针,可以用来指向派生类对象中从基类继承下来的成员,但不包括派生类中追加的成员,除非对基类指针采用强制类型转换
                    
base *pb2 = &d;
//pb2->set_j(4); 
//compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'set_j'

//(derived*)pb2->set_j(4); 
//compile error:  F:/devcpptest/devcpptest/convert_test1.cpp 'class base' has no member named 'set_j'
编译错误,必须对基类指针进行强转,才可以访问派生类的成员。例如,
((derived*)pb2)->set_j(4); 是正确的。

     
     
    b.   但是,基类指针不能直接赋值给派生类指针,需要用强制类型转换:
      base *pb = new b();
      derived *pd =  pb; // error: 基类指针不能直接赋值给派生类指针, 没有用强制类型转换
      derived *pd = (derived*)pb; // OK: 基类指针直接赋值给派生类指针, 采用强制类型转换
           
      具体的例子,见例2(convert_test2.cpp):
      例2:基类指针不能直接赋值给派生类指针:

/* 例2:convert_test2.cpp   */
      /* 对于例1中的两个类base和derived,进行如下的测试: */
      /* IDE环境: Dev-C++ 4.9.9.2 */
     
      int main()
      {
     
      base *pb = new base;
      //derived *pd =  pb; // error: 基类指针不能直接赋值给派生类指针, 没有用强制类型转换
      derived *pd = (derived*)pb; // OK: 基类指针不能直接赋值给派生类指针, 采用强制类型转换
     
      pd->get_i();
      pd->get_j();
     
      pd->set_i(3);
      pd->set_j(5);
     
      pd->display_i();
      pd->display_j();
      
      while(1);
      }
     
      运行结果:
        i = 3
        j = 5
       
       
      /* 例3:convert_test3.cpp   */
      /* IDE环境: Dev-C++ 4.9.9.2 */
      /* 虚函数继承举例 */
     
      #include <stdio.h>
      class base
      {
          public:
                 base():i(0){ };
                 int get_i(){ return i; }
                 void set_i(int x){ i = x;}
                 void virtual display_i(){ printf(" base::display_i, i = %d /n", i);}  //虚函数
                
          private:
                  int i;
      };
     
      class derived: public  base        //public inheritance
      {
          public:
                 derived():j(0){ };
                 int get_j(){ return j; }
                 void set_j(int x){ j = x; }
                 void display_j(){ printf(" j = %d /n", j);}
                
                 void  display_i(){ printf(" derived::display_i,  i = %d /n", get_i());}
          private:
                  int j;
      };
     
      int main()
      {
      derived d;
      base *pb=&d; //OK
      pb->set_i(2);    // 调用基类的set_i方法。
      pb->display_i(); // 调用的是派生类的display_i方法,如果在基类中的display_i不定义成virtual,则调用的就是基类的display_i了。
     
          while(1);
      }
     
      //运行结果:
        derived::display_i,  i = 2

代码说明:

//derived *pd =  pb; // error: 基类指针不能直接赋值给派生类指针, 没有用强制类型转换
derived *pd = (derived*)pb; // OK: 基类指针不能直接赋值给派生类指针, 采用强制类型转换
     
2.多基派生:

(1).代码:

对于如下的类层次结构,
    class  base0 
    { protected : int  b0 ;} ;
   
    class  base1: public  base0
    { protected : int  b1 ; } ;
   
    class base2  : public  base0
    { protected : int  b2 ; } ;
   
    class  derived : public  base1 , public  base2
    {
           public :
               int  f ( ) ;
           private : float  d ;
    } ;
(2). 说明:对于以上代码,可以进行的转换如下:
  a.   隐含地把指向派生类的指针转换为指向基类的指针,如同单继承一样;
  b.   指向基类的指针强制类型转换为指向派生类的指针如同单继承一样;
然而,在这种情况下,会产生如下的问题,称作“多重继承中对基类成员访问的二义性”:

  c.  当试图将指向派生类的指针(derived指针)转换为指向其间接公共基类指针(base0指针)时,C++编译器不知道是从哪一个路径(base1 还是 base2)继承,
      从而导致编译错误,即使采用强制转换也不行。
  d.   同样,当派生类引用间接基类的成员时,由于有两份copy,分别来自base1 和 base2,所以编译器不知道引用的base0的成员是从哪里来的。
        如:在display()中,如下语句是错误的: printf(" base0::b0 = %d /n", b0);

 
(3). 克服这种二义性的方法:
  (1) 对指针要显示地指明全路径。
  (2) 将指针先强制转换到不会产生二义性的基类。
  (3) 显示指明成员来自哪个类。


(4). 程序举例:

例3:

多基派生的例子:
      

 /* multiinherencetest.cpp   */
       /* 多基派生 */
       /* IDE环境: Dev-C++ 4.9.9.2 */
      
       #include <stdio.h>
       class  base0 
       { protected : int  b0 ;} ;
      
       class  base1: public  base0
       { protected : int  b1 ; } ;
      
       class base2  : public  base0
       { protected : int  b2 ; } ;
      
       class  derived : public  base1 , public  base2
       {
              public :
                  int  display( )
                  {
                         //printf(" base0::b0 = %d /n", b0); // error F:/devcpptest/devcpptest/multiinherencetest.cpp reference to `b0' is ambiguous
                                                            // error F:/devcpptest/devcpptest/multiinherencetest.cpp:4 candidates are: int base0::b0 
                         printf(" base0::base1::b0 = %d /n", base1::b0); // OK
                  }  
              private : float  d ;
       } ;
      
       int main()
       {
           derived d;
           derived *pd = &d;
          
           base1 *pb1;
           base0 *pb;
          
           pb1 = pd;              // OK: 隐含地把指向派生类的指针转换为指向基类的指针。(和单继承一样)
           pb1 = (base1*)pb;      // OK:把指向基类的指针强制类型转换为指向派生类的指针。 (和单继承一样)
          
           pb = pb1;             //OK: 隐含地把指向派生类的指针转换为指向基类的指针。(和单继承一样)
          
           pd = (derived*)pb1;   //OK: 把指向基类的指针强制类型转换为指向派生类的指针。(和单继承一样)
          
           //pd = pb;           // compile error: F:/devcpptest/devcpptest/multiinherencetest.cpp invalid conversion from `base0*' to `derived*'
           //pd = (derived*)pb; // compile error: F:/devcpptest/devcpptest/multiinherencetest.cpp `base0' is an ambiguous base of `derived'
          
           //pb = pd;           // compile error: F:/devcpptest/devcpptest/multiinherencetest.cpp `base0' is an ambiguous base of `derived'
           //pb = (base0*)pd;   // compile error: F:/devcpptest/devcpptest/multiinherencetest.cpp `base0' is an ambiguous base of `derived'
          
           //用如下形式消除以上的错误:
           pb = (base0*)(base1*) pd;  //OK
          
           pd = (derived*)(base1*)pb; //OK
          
           pb = (base1*) pd;  //OK
  
           while(1);
           return 0;
       }


      
  
3. 含有公共虚基类的类层次结构:
    使用虚基类,在它的几条派生路径的汇合处,只产生一个copy,所以,就不会产生二义性。 对于公共虚基类的类层次结构,
可以:
  (1). 派生类对象的地址可以直接赋给间接公共基类的指针,并且不需要进行强制类型转换;如,
     base0 *pb = &d  // OK
  (2). 一个虚基类的引用,可以引用一个派生类的对象。如,
   base0 &rb = d;  // OK
但是:
  相反的转换是不可以的,即使使用指定路径的强制转换也不行。如,
  derived *pd = (derived*)(base1*)pb;  // compiler error
  因为系统进行内存分配时,虚基类中的数据成员在派生类对象中的布局与非虚基类时不同,所以不能将指向虚基类的指针(或引用)置回指向派生类。


(3). 程序举例:

含有公共虚基类的类层次结构举例:

 /* multiinherencetest2.cpp   */
    /* 含有虚基类的多基派生 */
    /* IDE环境: Dev-C++ 4.9.9.2 */
   
    #include <stdio.h>
   
    class  base0 
    { protected : int  b0 ;} ;
   
    class  base1: public  virtual base0
    { protected : int  b1 ; } ;
   
    class base2  : public  virtual base0
    { protected : int  b2 ; } ;
   
    class  derived : public  base1 , public  base2
    {
           public :
               int  display( )
               {
                      //printf(" base0::b0 = %d /n", b0); // error F:/devcpptest/devcpptest/multiinherencetest2.cpp reference to `b0' is ambiguous
                                                         // error F:/devcpptest/devcpptest/multiinherencetest2.cpp:4 candidates are: int base0::b0 
                      printf(" base0::base1::b0 = %d /n", base1::b0); // OK
               }  
           private : float  d ;
    } ;
   
    int main()
    {
       
        derived d;
       
        base0 *pb = &d;  // OK
       
        base0 &rb = d;  // OK      
       
        //derived *pd = (derived*)(base1*)pb; //compile error: F:/devcpptest/devcpptest/multiinherencetest2.cpp cannot convert from base `base0' to derived type `base1' via virtual base `base0'
       
        while(1);
        return 0;
    }

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liranke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值