继承和复合都提倡建立与现有的类有许多共性的新类来实现软件复用。还有其他一些方法可以利用类所提供的服务。尽管“人”不是一辆汽车,“人”也不能包含汽车,但“人”当然可以“使用”汽车。一个函数可以简单地向对象发出函数调用来“使用”这个对象。
一个对象可以“知道”另外一个对象,知识网中常常存在这种关系。一个对象可以包含指向对象的指针或对该对象的引用,从而“知道”那个对象的存在。在这种情况下,可以说一个对象和另一个对象具有“知道”关系。
下面考察本章的一个练习.即点、圆、圆柱体的层次结构。我们首先开发并使用类Point(图9.8).然后从类Point派生出类Circle(图9.9),最后从类Circle派生出类Cylinder(图9.10)。
图9.8列出了类Point。图中的第1行到第17行是类Point的定义。可以看到,类Point的数据成员为protected。因此.当从类Point派生出类Circle时,类Circle的成员函数不必使用访问函数就能够直接引用坐标x和y,这样可使性能更好。
第18行到第39行定义了类Point的成员函数。第40行到第57行是类Point的驱动程序。程序中的main函数必须使用访问函数getX和getY读取protected数据成员x和y的值。要记住,protected数据成员只能被类和其派生类的成员和友元访问。
// Fig. 9.8: point2.h // Definition of class Point #ifndef POINT2_H #define POINT2_H class Point { friend ostream &operator<<( ostream &, const Point & ); public: Point( int = 0, int = O ); // default constructor void setPoint( int, int ); // set coordinates int getX() const { return x; } // get x coordinate int getY() const { return y; } // get y coordinate protected: // accessible to derived classes int x, y; // coordinates of the point } ; #endif // Fig. 9.8: point2.cpp // Member functions for class Point #include <iostream.h> #include "point2.h" // Constructor for class Point Point::Point( int a, int b ) { setPoint( a, b ); } // Set the x and y coordinates void Point::setPoint( int a, int b ) { x = a; y = b; } // Output the Point ostream &operator<<( ostream &output, const Point &p ) { output << '[' << p.x << ", "<< p.y << '] '; return output; // enables cascading } // Fig. 9.8:fig09 08.cpp // Driver for class Point #include < iostream.h> #include "point2.h" int main() Point p( 72, 115 ); // instantiate Point object p // protected data of Point inaccessible to main cout << "X coordinate is" << p.getx() << "\nY coordinate is "<< p.getY(); p.setPoint( 10, 10 ); cout << "\n\nThe new location of p is "<< p << endl; return 0; }
输出结果:
X coordinate is 72
Y coordinate is 115
The new location of p is [ 10,10 ]
第二个例子是图9.9。该例子复用了图9.8中的类Point的定义和成员函数定义,分别是类Circle的定义、类Circle的成员函数的定义和类的驱动程序。类Circle对类Point的继承是public继承,这意味着类Circle的public接口包括类Point的成员函数以及Circle成员函数setRadius、getRadius和area。
注意Circle重载operator<<函数,Circle类的友元可以通过将Circle引用c强制转换为Point而输出Circle的Point部分。因此调用Point的operator<<并用相应的Point格式输出x和y坐标。
驱动程序先实例化了类Circle的一个对象,然后用“get”函数读取该对象的信息。main函数既不是类Circle的成员函数也不是其友元,因此它不能直接引用该类的protected数据。为此,程序中用"set"函数setRadius和setPoint重新设置圆的半径和圆心坐标。最后,驱动程序先将Point&(对Point对象的引用)类型的引用变量pRef初始化为Circle的对象c,然后打印出pRef,尽管它已经被初始化为一个Circle对象,但是它还“认为”自己是一个Point对象,所以该Circle对象实际上作为Point对象打印的。// Fig. 9.9: circle2.h // Definition of class Circle #ifndef CIRCLE2_H #define CIRCLE2_H #include "point2.h" class Circle : public Point { friend ostream &operator<<( ostream &, const Circle & ); public: // default constructor Circle( double r = 0.0, int x = 0, int y = 0 ); void setRadius( double ); // set radius double getRadius() const; // return radius double area() const; // calculate area protected: // accessible to derived classes double radius; // radius of the Circle }; #endif // Fig. 9.9: circle2.cpp // Member function definitions for class Circle #include <iostream.h> #include <iomanip.h> #include "circle2.h" // Constructor for Circle calls constructor for Point // with a member initializer and initializes radius Circle::Circle( double r, int a, int b ) : Point( a, b ) // call base-class constructor { setRadius( r ); } // Set radius void Circle::setRadius( double r ) { radius = ( r >= 0 r : 0 ); } // Get radius double Circle::getRadius() const { return radius; } // Calculate area of Circle double Circle::area() const { return 3.14159 * radius * radius; } // Output a circle in the form: // Center [ x, Y ]; Radius = #.## ostream &operator<<( ostream &output, const Circle &c ) { output << "Center = "<< static_cast< Point > ( c ) << "; Radius =" << setiosflags( ios::fixed | ios::showpoint ) << setprecision( 2 ) << c.radius; return output; // enables cascaded calls } // Fig. 9.9: fig09_09.cpp // Driver for class Circle #include <iostream.h> #include "point2.h" #include "circle2.h" int main() { Circle c( 2.5, 37, 43 ); cout << "X coordinate is "<< c.getX() << "\nY coordinate is "<< c.getY() << "\nRadius is "<< c.getRadius(); c.setRadius( 4.25 ); c.setPoint( 2, 2 ); cout << "\n\nThe new location and radius of C are\n" << c << "\nArea "<< c.area() << '\ n'; Point &pRef = c; cout << "\nCircle printed as a Point is: "<< pRef << endl; return 0; }
输出结果:
X coordinate is 37
Y coordinate is 43
Radius is 2.5
The new location and radius of c are
Center = [ 2,2 ]; Radius = 4.25
Area 56.74
Circle printed as a Point is: [ 2, 2 ]
最后一个例子是图9.10。该例复用了类Point和类Circle的定义以及图9.8和图9.9中的成员函数的定义,分别是类Cylinder的定义、Cylinder成员函数的定义以及类的驱动程序。类Cylinder对类Circle的继承是public继承,这意味着类Cylinder的public接口包括类Cylinder的成员函数、类Point的成员函数以及成员函数setHeight、getHeight、area(对Circle中的area重新定义)以及volume。注意Cylinder构造函数要调用直接基类Circle的构造函数,而不调用间接基类Point的构造函数。每个派生类构造函数只负责调用直接基类的构造函数(多重继承中可能有多个直接基类)。另外,注意Cylinder重载operator<<函数,Cylinder类的友元可以通过将Cylinder引用c强制转换为Circle而输出
Cylinder的Circle部分。因此调用Circle的operator<<并用相应Circle格式输出x和y坐标。
驱动程序实例化了类Cylinder的一个对象,然后用“get”函数读取该对象的信息。main函数既不是类Cylinder的成员函数也不是其友元,因此它不能直接引用类Cylinder的protected数据。为此,程序用"set"函数setHeight和setRadius以及setPoint重新设置圆柱体的高度、半径和坐标值。最后,驱动程序先把Point&(对Point对象的引用)类型的引用变量pRef初始化为类Cylinder的对象cyl,然后打印出pRef,尽管它已经被初始化为一个Cylinder对象,但是它还是“认为”自己是一个Point对象,所以该Cylinder对象实际上作为一个Point对象来打印;其次,将Circle&(对Circle的引用)类型的引用变量cRof初始化为Cylinder对象cyl,然后驱动程序打印cireleRef,尽管它已经被初始化为一个Cylinder对象,它还“认为”自己是一个Circle对象,所以该Cylinder对象实际上是作为一个Circle对象打印的,Circle的面积也同时打印出来。
这个例子很好地演示了public继承以及对protected数据成员的定义和引用,读者现在应该对继承的基本知识有了一定的了解。下一章要讨论如何用多态性编写具有继承层次结构的程序。数据抽象、继承和多态性是面向对象程序设计的关键所在。
// Fig. 9.9: circle2.h // Definition of class Circle #ifndef CIRCLE2_H #define CIRCLE2_H #include "point2.h" class Circle : public Point { friend ostream &operator<<( ostream &, const Circle & ); public: // default constructor Circle( double r = 0.0, int x = 0, int y = 0 ); void setRadius( double ); // set radius double getRadius() const; // return radius double area() const; // calculate area protected: // accessible to derived classes double radius; // radius of the Circle }; #endif // Fig. 9.9: circle2.cpp // Member function definitions for class Circle #include < iostream.h> #include < iomanip.h> #include "circle2.h" // Constructor for Circle calls constructor for Point // with a member initializer and initializes radius Circle::Circle( double r, int a, int b ) : Point( a, b ) // call base-class constructor { setRadius( r ); } // Set radius void Circle::setRadius( double r ) { radius = ( r >= 0 r : 0 ); } // Get radius double Circle::getRadius() const { return radius; } // Calculate area of Circle double Circle::area() const { return 3.14159 * radius * radius; } // Output a circle in the form: // Center [ x, Y ]; Radius = #.## ostream &operator<<( ostream &output, const Circle &c ) { output << "Center = "<< static_cast< Point > ( c ) << "; Radius =" << setiosflags( ios::fixed | ios::showpoint ) << setprecision( 2 ) << c.radius; return output; // enables cascaded calls } // Fig. 9.9: fig09_09.cpp // Driver for class Circle #include < iostream.h> #include "point2.h" #include "circle2.h" int main() { Circle c( 2.5, 37, 43 ); cout << "X coordinate is "<< c.getX() << "\nY coordinate is "<< c.getY() << "\nRadius is "<< c.getRadius(); c.setRadius( 4.25 ); c.setPoint( 2, 2 ); cout << "\n\nThe new location and radius of C are\n" << c << "\nArea "<< c.area() << '\ n'; Point &pRef = c; cout << "\nCircle printed as a Point is: "<< pRef << endl; return 0; }
输出结果:
X coordinate is 12
Y coordinate is 23
Radius is 2.5
Height is 5.7
The new location, radius, and height of cy1 are:
Center = [ 2, 2 ]; Radius= 4.25; Height= 10.00
Cylinder printed as a Point is: [ 2, 2 ]
Cylinder printed as a Circle is:
Center = [2, 2] ; Radius= 4.25
Area: 56.74