继承和组合

        C++最重要的特征之一是代码重用。但是如果希望更进一步,就不能仅仅用拷贝代码和修改代码的方法,而是要做更多的工作。

一. 组合语法

         实际上,我们一直都在使用组合创建类,只不过是在用内部数据类型组合新类。

class  X
{
          
int  i;
public :
         X() { i
= 0  }
         
void   set ( int  ii) { i = ii; }
         
int  read()  const  {  return  i; }
         
int  permute() {  return  i = i * 47 ; }
};

class  Y
{
       
int  i;
       X x;
public :
       Y() { i 
= 0 ; }
       
void  f( int  ii) { i = ii; x. set (ii); }
       
int  g()  const  {  return  i * x.read(); }
       
void  permute() { x.permute(); }
};

int  main()
{
        Y y;
        y.f(
47 );
        y.permute();
}

          这是组合最常见的用法,把另一个类的对象嵌入到自己的私有成员里面,因此它们将成为内部实现的一部分。

二. 继承语法

         下面演示了继承的基本语法:

class  X
{
          
int  i;
public :
         X() { i
= 0  }
         
void   set ( int  ii) { i = ii; }
         
int  read()  const  {  return  i; }
         
int  permute() {  return  i = i * 47 ; }
};

class  Y :  public  X
{
      
int  i;          // Different from X's i
public :
       Y() { i
= 0 ; }
        
int  change()
       {
             i
= permute();         // X's permute
              return  i;
        }
        
void   set ( int  ii)
        {
               i
= ii;
               X::
set (ii);
        }
};

int  main()
{
        cout
<< " sizeof(X) =  " << sizeof (X) << endl;
        cout
<< " sizeof(Y) =  " << sizeof (Y) << endl;
        Y D;
         D.change();
         D.read();
         D.permute();
         D.
set ( 12 );
}

        Y对X进行了继承,这意味着Y将包含X中的所有数据成员和成员函数。在这种public 继承方式中,X中的私有成员仍然是私有的,但它们依旧占有存储空间,只是不可以直接的访问它们。在main()中,可以看出sizeof(Y) 是sizeof(X) 的两倍,Y的数局成员同X的成员结合在一起了。

        派生类中的set() 函数重新定义了基类中的set() 函数。这即是说,如果调用一个Y类型对象的read() 和 permute() 函数,将会使用基类中的这些函数。但如果调用一个Y类型对象的set() 函数,将会使用派生类中的重定义版本。当然我们仍可以使用作用域运算符来显示的调用基类被覆盖的函数。

      还有private, protected 的继承方式,不过很少用,它们一定程度是为了语法的完整性而加进去的。           private 继承:基类的所有成员在子类中都是private 成员。 protected 继承:基类的public 和 protected 在子类中是protected , private 仍是 private 。

三. 初始化问题

         用户自定义类型最重要的问题就是初始化,当创建一个对象时,编译器要确保调用了所有子对象的构造函数。如果没有自定义构造函数,那么编译器会自动调用默认的构造函数。若有了自定义的构造函数,那么必须使用构造函数初始化列表来进行初始化,这个和以前的常量初始化很像。

         但是这里会出现一个调用次序的问题,下面一个例子演示类这种情况:

 

// Order.cpp
// Constructor/destructor order
#include  < fstream >
using   namespace  std;

ofstream 
out ( " order.out " );

#define  CLASS(ID) class ID { 
public
        ID(
int out<< #ID " constructor "; }
        
~ ID()  out<< #ID " destructor "; }
}

CLASS(Base1);
CLASS(Member1);
CLASS(Member2);
CLASS(Member3);
CLASS(Member4);

class  Derived1:  public  Base1
{
         Member1 m1;
         Member2 m2;
public:
          Derived1(
int):m2(1),m1(2),Base1(3)
          
{
                  
out<<"Derived1 constructor ";
           }

          
~Derived1()
          
{
                    
out<<"Derived1 destructor ";
           }

}
;

class  Derived2:  public  Derived1
{
           Member3 m3;
           Member4 m4;
public:
           Derived2():m3(
1),Derived1(2),m4(3)
           
{
                      
out<<"Derived2 constructor ";
           }

           
~Derived2()
           
{
                      
out<<"Derived2 destructor ";
            }

}
;

int  main()
{
              Derived2 d2;
}

这里是输出结果:

Base1 constructor
Member1 constructor
Member2 constructor
Derived1 constructor
Member3 constructor
Member4 constructor
Derived2 constructor
Derived2 destructor
Member4 destructor
Member3 destructor
Derived1 destructor
Member2 destructor
Member1 destructor
Base1 destructor

        可以看出,构造是从类层次的最根处开始,而在每一层首先会调用基类的构造函数,然后调用成员对象构造函数。调用析构函数则按照构造函数相反的次序。注意的是:构造函数调用的次序完全不受构造函数初始化列表的影响,该次序是有成员对象在类中声明的次序所决定的。

        还有,这里使用了一个宏的字符串粘贴的技术,即#ID 在宏展开时,将作为一个字符串来处理。

 四. 名字隐藏

           如果继承一个类并且对它的成员函数重新进行定义,可能会出现两种情况。第一种是正如在基类中所进行的定义一样,在派生类的定义操作和返回类型,这称之为重定义(redefining)。另一种是基类的成员函数是虚函数的情况,叫重写(overriding)。

          如果在派生类中改变了成员函数参数列表和返回类型,那么基类的函数将被隐藏。这是重定义的一种另一种形式。

五. 非自动继承的函数

       不是所有的函数都能自动的从基类继承到派生类中。除了构造函数和析构函数不能被继承外,operator= 也不能被继承。在继承过程中,如果不亲自创建这些函数,编译器就会生成它们,当然很多时候不能满足我们的需求。

// SynthesizedFunctions.cpp
// Functions that are synthesized by the complier
#include  < iostream >
using   namespace  std;

class  GameBoard
{
public :
          GameBoard() { cout
<< " GameBoard() " ; }
          GameBoard(
const  GameBoard & )
          {
                 cout
<< " GameBoard(const GameBoard&) " ;
           }
           GameBoard
&   operator = ( const  GameBoard & )
          {
                 cout
<< " GameBoard::operator=() " ;
                 
return   * this ;
           }
            
~ GameBoard() { cout << " ~GameBoard() " ; }
};

class  Game
{
private :
          GameBoard gb;    
// Composition
public :
           
// Default GameBoard constructor called:
           Game() { cout << " Game() " ; }
           
// You must explicitly call the GameBoard
           
// copy-constructor or the default constructor
           
// is automatically called instread:
           Game( const  Game &  g):gb(g.gb)
           {
                   cout
<< " Game(const Game&) " ;
           }
           Game(
int ) { cout << " Game(int) " ; }
           Game
&   operator = ( const  Game &  g)
           {
                       
// You must explicitly call the GameBoard
                      
// assignment operator or no assignment at
                      
// all happens for gb!
                      gb = g.gb;
                      cout
<< " Game::operator=() " ;
                      
return   * this ;
             }
             
class  Other {};         // Nested class
             
// Automatic type conversion:
               operator  Other()  const
              {
                        cout
<< " Game::operator Other() "
                         
return  Other();
              }
              
~ Game() { cout << " ~Game() " ; }
};

class  Chess :  public  Game {};

void  f(Game::Other) {}

class  Checkers :  public  Game
{
public :
            
// Default base-class constructor called:
            Checkers() { cout << " Checkers()  " ; }
           
// You must explicitly call the base-class
           
// copy constructor or the default constructor
           
// will be automatically called instead:
            Checkers( const  Checkers &  c) : Game(c)
            {
                      cout
<< " Checkers(const Checkers& c) " ;
            }
             Checkers
&   operator = ( const  Checkers &  c)
             {
                     
// You must explicitly call the base-class
                     
// version of operator=() or no base-class
                     
// assignment will happen:
                    Game:: operator = (c);
                    cout
<< " Checkers::operator=() " ;
                    
return   * this ;
             }
};

int  main()
{
         Chess d1;    
// Default constructor
         cout << " -------------------------- " << endl;
         Chess d2(d1);    
// Copy-constructor
          cout << " -------------------------- " << endl;
          d1
= d2;
          cout
<< " -------------------------- " << endl;
          f(d1);
          cout
<< " -------------------------- " << endl;
          Game::Other go;
           cout
<< " -------------------------- " << endl;
           Checkers c1,c2(c1);
           cout
<< " -------------------------- " << endl;
           c1
= c2;
           cout
<< " -------------------------- " << endl;
}

这里是输出结果:

GameBoard()
Game()
--------------------------
GameBoard(const GameBoard&)
Game(const Game&)
--------------------------
GameBoard::operator=()
Game::operator=()
--------------------------
Game::operator Other()
--------------------------
--------------------------
GameBoard()
Game()
Checkers()
GameBoard(const GameBoard&)
Game(const Game&)
Checkers(const Checkers& c)
--------------------------
GameBoard::operator=()
Game::operator=()
Checkers::operator=()
--------------------------
~Game()~GameBoard()
~Game()~GameBoard()
~Game()~GameBoard()
~Game()~GameBoard()

结语:继承面临的另外一个问题就是向上类型转换,就是派生类向基类进行转换。 但是任何向上类型转换都会损失对象的类型信息。要解决这个问题就是面向对象编程的核心之一:多态性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值