⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 09章 继承.txt

📁 C++大学教程txt版中文版 C++大学教程txt版中文版
💻 TXT
📖 第 1 页 / 共 5 页
字号:
    软件工程视点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调用析构函数。

1 // Fig. 9.7: point2.h
2 // Definition of class Point
3 #ifndef POINT2_H
4 #define POINT2_H
5
6 class Point {
7 public:
8   Point( int = O,int = 0 );  // default constructor
9   ~Point();   // destructor
l0 protected:     // accessible by derived classes
11   int x, y;   // x and y coordinates of Point
12 };
13
14 #endif
15 // Fig. 9.7: point2.cpp
16 // Member function definitions for class Point
17 #include <iostream.h>
18 #include "peint2.h"
19
20 // Constructor for class Point
21 Point::Point( int a, int b )
22 {
23   x = a;
24   y = b;
25
26   cout << "Point constructor:"
27       << '[' << x << ", "<< y << ']' << endl;
28 }
29
30 // Destructor for class Point
31 Point::~Point()
32 {
33   cout << "Point destructor:  "
34       << '[' << x << ", "<< y << ']' << endl;
35 }
36 // Fig. 9.7: circle2.h
37 // Definition of class Circle
38 #ifndef CIRCLE2_H
39 #define CIRCLE2_H
40
41 #include "point2.h"
42
43 class Circle : public Point {
44 public:
45   // default constructor
46   Circle( double r = 0.0, int x = O, int y = 0 );
47
48   ~Circle();
49 private:
50   double radius;
51 };
52
53 #endif
54 // Fig. 9.7: circle2.cpp
55 // Member function definitions for class Circle
56 #include "circle2.h"
57
58 // Constructor for Circle calls constructor for Point
59 Circle::Circle( double r, int a, int b )
60   : Point( a, b )  // call base-class Constructor
61 {
62   radius = r;  // should validate
63   cout << "Circle constructor: radius is"
64        << radius << "[" << x << ", "<< y << ']' << endl;
65 }
66
67 // Destructor roi class Circle
68 Circle::~Circle()
69 {
70   cout << "Circle destructor: radius is "
71        << radius << " [ " << x << ", "<< y << ']'  << endl;
72 }
73 // Fig. 9.7: fig09_07.cpp
74 // Demonstrate when base-class and derived-class
75 // constructors and destructors are called.
76 #include <iostream.h>
77 #include "point2.h"
78 #include "circle2.h"
79
80 int main()
81 {
82   // Show constructor and destructor calls for Point
83   {
84     Point p( 11, 22 );
85  }
86
87   cout << endl;
88   Circle circle1( 4.5, 72, 29 );
89   cout << endl;
90   Circle circle2( 10, 5, 5 );
91   cout << endl;
92   return 0;
93 }

输出结果:
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  基类和派生类的构造函数和析构函数的调用顺序

9.10  将派生类对象隐式转换为基类对象
    尽管派生类对象也是基类对象,但是派生类类型和基类类型是不同的。在public继承中,派生类对象能作为基类对象处理。由于派生类具有对应每个基类成员的成员(派生类的成员通常比基类的成员多),所以把派生类的对象赋给基类对象是合理的。但是,反过来赋值会使派生类中基类不具有的成员没有定义,所以这是不允许的。尽管如此,提供正确的重载赋值运算符和(或)转换构造函数可以允许这种操作(见第8章)。

    常见编程错误9.4
    把派生类对象赋给其基类对象,然后试图在新的基类对象中引用只在派生类中才有的成员是十语法错误。
    注意,在本节后面提到指针时,也适用于引用。
    
    在public继承中,因为派生类对象也是基类对象,所以指向派生类对象的指针可以隐式地转换为指向基类对象的指针。
    基类指针和派生类指针与基类对象和派生类对象的混合和匹配有如下四种可能的方式:
    1.直接用基类指针引用基类的对象。
    2.直接用派生类指针引用派生类的对象。
    3.用基类指针引用一个派生类的对象。由于派生类的对象也是基类的对象,所以这种引用方式是安全的,但是用这种方法只能引用基类成员。如果试图通过基类指针引用那些只在派生类中才有的成员,编译器会报告语法错误。
    4.用派生类指针引用基类的对象。这种引用方式会导致语法错误。派生类指针必须先强制转换为基类指针。

    常见编程错误9.5
    将基类指针强制转换为派生类指针,如果用该指针引用基类对象,而基类对象中没有所要引用的派生类的成员,那么这时就会发生错误。
    将派生类对象作为基类对象可能是很方便的,但使用基类指针操作这些对象容易出问题。例如,在某个计算工资单的系统中,我们希望能够遍历关于雇员的清单并计算出每人每周的工资。但是,使用基类指针使得程序只能调用基类的工资单计算例程(如果基类中确实存在该例程)。我们需要一种方法为每一个对象(不管它是派生类对象还是基类对象)调用正确的工资单计算例程,并且这种方法只需简单地使用基类指针。解决这个问题的答案是使用第10章介绍的虚函数和多态性。

9.11  关于继承的软件工程
    我们可以用继承来定制现有的软件。为了把现有类定制成满足我们的需要的类,首先要继承现有类的属性和行为,然后添加和去除一些属性和行为。在C++中,派生类不必访问基类的源代码,但是需要能够连接到基类的目标代码。这种强大的功能对独立软件供应商(ISV)很有吸引力。
  ISV开发出具有目标代码格式的类后,他们就拥有了这些类的所有权,因而可以销售和发放使用许可证。
  用户拥有这些类后,在不必访问源代码(所有权属于ISV)的情况下,他们就能够从这些类库中派生出新的类,所有的ISV需要为目标代码提供头文件。

    软件工程视点9.6
    理论上,用户不需要看到所继承类的源代码。但实际上,根据发放许可证的经验,客户通常会需要源代码。程序员似乎还是不大愿意放心地把别人编写的代码放进自己的程序中。

    性能提示9.1
    如果性能是主要考虑,则程序员可能要浏览所继承类的源代码,以便根据性能要求调整代码。

    学生们很难认识到大型软件项目的设计者和实现者所面临的问题。有过开发这种项目经验的人都知道缩短软件开发过程的关键是鼓励软件复用。面向对象的程序设计普遍鼓励软件复用,而C++尤其提倡软件复用。
    正是继承了实用的类库才发挥出了软件复用的最大优势。随着人们对C++的兴趣不断增长,对类库感兴趣的人也将增加。正如个人电脑的出现带动了ISV生产的套装软件日益增长,C++也必将带动类库的建立和销售。因为应用程序设计者会用这些类库建立他们自己的应用程序,所以类库设计者也将因此而获得丰厚的报偿。当前随C++编译器分发的类库倾向于一定的通用性并限制使用范围。在世界范围内开发应用于各种领域的类库的时代正在来临。

    软件工程视点9. 7
    建立一个派派生类不会影响其基类的源代码和目标代码,继承这一机制保护了基类的完整性。

    基类描述了共性。所有从基类派生出来的类都继承了基类的功能。在面向对象的设计过程中,设计者先寻求井提取出构成所需基类的共性,然后再通过继承从基类派生出超出基类功能的定制派生类。

    软件工程视点9.8
    在面向对象的系统中,类常常是紧密相关的。提取出共同的属性和行为并把它们放在一个基类中,然后再通过继承生成派生类。

    正如非面向对象系统的设计者力图避免不必要的函数一样,面向对象系统的设计者也应该避免不必要的类。多余的类不仅会带来类管理上的问题,而且会阻碍软件的复用。理由很简单,因为用户难以在巨大的类集合中定位某个类权。权衡的结果还是建立较少的类,每个类都实际增加一些功能。这样的类对于某些用户来说可能功能太丰富了一点,但是他们可以屏蔽掉多余的功能,然后使之满足自己的需要。

    性能提示9.2
    大于功能需求的派生类可能会浪费内存和处理资源。因此应继承最接近要求的类。
 
    注意,因为派生类中没有列出继承来的成员,所以浏览一组派生类的声明会令人迷惑,但是派生类中确实存在继承来的成员。

    软件工程视点9.9
    派生类除了包含其基类的属性和行为外,还能够包含附加的属性和行为。继承机制能够使基类独立于派生类编译。为了把基类与派生类中增加的属性和行为组合成派生类,编译器只需要编译派生类中增加的属性和行为。

    软件工程视点9.10
    只要基类的public接口不变,对基类的修改无需修改派生类,但是派生类需要重新编译。

9.12  复合与继承的比较
    我们讨论了public继承所支持的"是"关系,还讨论把对象作为成员的"有"关系,并举了几个例子。"有"关系通过复合现有的类建立了新类。例如,假设有雇员类Employee、生日类BirthDate和电话号码类TelephonehNunber,说雇员(Employee)是—个生日(BirthDate)或电话号码(TelephoneNumber)是不对的,但是说雇员有生日和电话号码当然是合适的。

    软件工程视点9. 11
    只要成员类的public接口不变,对成员类的修改无需修改复合类,但是复合类需要重新编译。

9.13  对象的“使用”关系和“知道”关系
    继承和复合都提倡建立与现有的类有许多共性的新类来实现软件复用。还有其他一些方法可以利用类所提供的服务。尽管“人”不是一辆汽车,“人”也不能包含汽车,但“人”当然可以“使用”汽车。一个函数可以简单地向对象发出函数调用来“使用”这个对象。
    一个对象可以“知道”另外一个对象,知识网中常常存在这种关系。一个对象可以包含指向对象的指针或对该对象的引用,从而“知道”那个对象的存在。在这种情况下,可以说一个对象和另一个对象具有“知道”关系。

9.14  实例研究:类Point、Circle和Cylinder
    下面考察本章的一个练习.即点、圆、圆柱体的层次结构。我们首先开发并使用类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数据成员只能被类和其派生类的成员和友元访问。

1 // Fig. 9.8: point2.h
2 // Definition of class Point
3 #ifndef POINT2_H
4 #define POINT2_H
5
6 class Point {
7   friend ostream &operator<<( ostream &, const Point & );
8 public:
9   Point( int = 0, int = O );     // default constructor
10  void setPoint( int, int );     // set coordinates
11  int getX() const { return x; }  // get x coordinate
12  int getY() const { return y; }  // get y coordinate
13 protected:                           // accessible to derived classes
14  int x, y;     // coordinates of the point
15 } ;
16
17 #endif
18 // Fig. 9.8: point2.cpp
19 // Member functions for class Point
20 #include <iostream.h>
21 #include "point2.h"
22
23 // Constructor for class Point
24 Point::Point( int a, int b ) { setPoint( a, b ); }
25
26 // Set the x and y coordinates
27 void Point::setPoint( int a, int b )

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -