C++最重要的特征之一是代码重用。但是如果希望更进一步,就不能仅仅用拷贝代码和修改代码的方法,而是要做更多的工作。
一. 组合语法
实际上,我们一直都在使用组合创建类,只不过是在用内部数据类型组合新类。
{
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();
}
这是组合最常见的用法,把另一个类的对象嵌入到自己的私有成员里面,因此它们将成为内部实现的一部分。
二. 继承语法
下面演示了继承的基本语法:
{
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 。
三. 初始化问题
用户自定义类型最重要的问题就是初始化,当创建一个对象时,编译器要确保调用了所有子对象的构造函数。如果没有自定义构造函数,那么编译器会自动调用默认的构造函数。若有了自定义的构造函数,那么必须使用构造函数初始化列表来进行初始化,这个和以前的常量初始化很像。
但是这里会出现一个调用次序的问题,下面一个例子演示类这种情况:































































这里是输出结果:
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= 也不能被继承。在继承过程中,如果不亲自创建这些函数,编译器就会生成它们,当然很多时候不能满足我们的需求。
// 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()
结语:继承面临的另外一个问题就是向上类型转换,就是派生类向基类进行转换。 但是任何向上类型转换都会损失对象的类型信息。要解决这个问题就是面向对象编程的核心之一:多态性。