ITEEDU

9.7 public、protected和private继承

从一个基类派生一个类时,继承基类的方式有三种:public、protected和private。protected继承和private继承不常用,而且使用时必须相当小心。本书中的范例都是使用public继承(第15章将介绍用private继承作为复合的另一种形式)。图9.6总结了每种继承中派生类对基类成员的访问性。第一列包含基类成员的访问说明符。

  基类成员的
    访问说明符                            继承类型                 
                    public继承            protected继承                private继承 
    public          在派生类中为public    在派生类中为protected        在派生类中为private

                    可以由任何非static    可以直接由任何非static       可以直接由任何非static
                    成员函数、友元函数和  成员函数、友元函数访问       成员函数、友元函数访问
                    非成员函数访问 
    protecetd       在派生类中为proteced  在派生类中为protected        在派生类中private

                    可以直接由任何非static   可以直接由任何非static    可以直接由任何非static
                    成员函数访问             成员函数、友元函数访问    成员函数、友元函数访问 
    private         在派生类中隐藏        在派生类中隐藏               在派生类中隐藏

                    可以通过基类的public  可以通过基类的public         可以通过基类的public
                    或protected成员函数   或protected成员函数由        或protected成员函数
                    由非static成员函数和  非static成员函数和友         由非static成员函数和
                    友元函数访问          元函数访问                   友元函数访问
 

图 9.6 派生类对基类成员的访问性

从public基类派生某个类时,基类的public成员会成为派生类的public成员,基类的protected成员成为派生类的protected成员。派生类永远也不能直接访问基类的private成员,但可通过基类public或protected成员间接访问。

从protected基类派生一个类时,基类的public成员和protected成员成为派生类的protected成员。从private基类派生一个类时,基类的public成员和protected成员成为派生类的private成员(例如,函数成为工具函数),provate和protected继承不是“是”的关系。

9.8 直接基类和间接基类

基类既可能是派生类的直接基类,也可能是派生类的间接基类。在声明派生类时,派生类的首部要显式地列出直接基类。间接基类不是显式地列在派生类的首部,而是沿着类的多个层次向上继承。

9.9 在派生类中使用构造函数和析构函数

由于派生类继承了其基类的成员,所以在建立派生类的实例对象时,必须调用基类的构造函数来初始化派生类对象的基类成员。派生类的构造函数既可以隐式调用基类的构造函数,也可以在派生类的构造函数中通过给基类提供初始化值(利用了前面所讲过的成员初始化值语法)显式地调用基类的构造函数。

派生类不继承基类的构造函数和赋值运算符,但是派生类的构造函数和赋值运算符能调用基类的构造函数和赋值运算符。

派生类的构造函数总是先调用其基类构造函数来初始化派生类中的基类成员。如果省略了派生类的构造函数,那么就由派生类的默认构造函数调用基类的默认构造函数。析构函数的调用顺序和调用构造函数的顺序相反,因此派生类的析构函数在基类析构函数之前调用。

软件工程视点9.3

假设生成派生类对象,基类和派生类都包含其他类的对象,则在建立派生类的对象时,首先执行基类成员对象的构造函数,接着执行基类的构造函数,以后执行派生类的成员对象的构造函数,最后才执行派生类的构造函数。析构函数的调用次序与调用构造函教的次序相反。

软件工程视点9. 4

建立成员对象的顺序是对象在类定义中的声明顺序。成员初始化值的顺序不影响建立对象的顺序。

软件工程视点9. 5

对继承关系而言,基类构造函数的调用顺序是派生类定义中指定的继承顺序,派生类成员初始化值列表中指定的基类构造函数的顺序不影响对象的建立顺序。

图9.7中的程序演示了基类和派生类的构造函数及析构函数的调用顺序。程序的第1行到第35行是一个简单的Point类,包含一个构造函数、一个析构函数以及protected数据成员x和y。构造函数和析构函数打印了调用它们的Point对象的信息。

第36行到第72行是一个简单的Circle类,它是通过public继承从Point类派生出来的。类circle包含一个构造函数、一个析构函数以及Private数据成员radius。构造函数和析构函数打印了调用它们的Circle对象的信息。为初始化基类的数据成员x和y, Circle的构造函数用成员初始化值语法和传递变量a和b的值调用Point类的构造函数。

第73行到最后是层次结构Point/Circle的驱动程序。程序首先在main函数内实例化了一个Point

对象。由于该对象在进入其范围后又立即退出其范围,所以调用了Point的构造函数和析构函数。然后,程序实例化了类Circle的对象circle1。这个过程调用了类Point的构造函数,从而输出了类Circle的构造函数传递给它的值,随后再输出Circle构造函数所指定的输出内容。接着,程序实例化了类Circle的对象circle2。这个过程同样需要调用类Point和Circle的构造函数。注意,类Point的构造函数在执行Circle构造函数之前执行。main函数结束时,程序为对象circle1和circle2调用析构函数。因为调用析构函数的顺序和调用构造函数的顺序相反,所以先为对象circle2调用析构函数,调用顺序是调用完类Circle的析构函数后,再调用类Point的析构函数。为对象circle2调用完析构函数后,

再以相同的顺序为对象cirele1调用析构函数。

  // Fig. 9.7: point2.h
 // Definition of class Point
 #ifndef POINT2_H
 #define POINT2_H
 class Point {
 public:
   Point( int = O,int = 0 );  // default constructor
   ~Point();   // destructor
  protected:     // accessible by derived classes
   int x, y;   // x and y coordinates of Point
 };
 #endif
 // Fig. 9.7: point2.cpp
 // Member function definitions for class Point
 #include < iostream.h>
 #include "peint2.h"
 // Constructor for class Point
 Point::Point( int a, int b )
 {
   x = a;
   y = b;
   cout << "Point constructor:"
       << '[' << x << ", "<< y << ']' << endl;
 }
 // Destructor for class Point
 Point::~Point()
 {
   cout << "Point destructor:  "
       << '[' << x << ", "<< y << ']' << endl;
 }
 // Fig. 9.7: circle2.h
 // Definition of class Circle
 #ifndef CIRCLE2_H
 #define CIRCLE2_H
 #include "point2.h"
 class Circle : public Point {
 public:
   // default constructor
   Circle( double r = 0.0, int x = O, int y = 0 );
   ~Circle();
 private:
   double radius;
 };
 #endif
 // Fig. 9.7: circle2.cpp
 // Member function definitions for class Circle
 #include "circle2.h"
 // Constructor for Circle calls constructor for Point
 Circle::Circle( double r, int a, int b )
   : Point( a, b )  // call base-class Constructor
 {
   radius = r;  // should validate
   cout << "Circle constructor: radius is"
        << radius << "[" << x << ", "<< y << ']' << endl;
 }
 // Destructor roi class Circle
 Circle::~Circle()
 {
   cout << "Circle destructor: radius is "
        << radius << " [ " << x << ", "<< y << ']'  << endl;
 }
 // Fig. 9.7: fig09_07.cpp
 // Demonstrate when base-class and derived-class
 // constructors and destructors are called.
 #include < iostream.h>
 #include "point2.h"
 #include "circle2.h"
 int main()
 {
   // Show constructor and destructor calls for Point
   {
     Point p( 11, 22 );
  }
   cout << endl;
   Circle circle1( 4.5, 72, 29 );
   cout << endl;
   Circle circle2( 10, 5, 5 );
   cout << endl;
   return 0;
 }

输出结果:

Point constructor: [ 11, 22 ]

Point destructor: [ 11, 22 ]

Point constructor: [ 72, 29 ]

Circle constructor: radius is 4.5 [ 72, 29]

Point constructor: [ 5, 5 ]

Circle constructor: radius is 10 [ 5, 5 ]

Circle destructor: radius is 10 [ 5, 5 ]

Point destructor: [ 5, 5 ]

Circle destructor: radius is 4.5 [ 72, 29 ]

Point destructor: [ 72, 29 ]

图9.7 基类和派生类的构造函数和析构函数的调用顺序