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

📄 09章 继承.txt

📁 C++大学教程txt版中文版 C++大学教程txt版中文版
💻 TXT
📖 第 1 页 / 共 5 页
字号:
 
C++大学教程(第9章 继承)  
 
教学目标
    ●能通过继承现有的类建立新类
    ●了解继承是如何提高软件的可复用性
    ●了解基类和派生类的概念
    ●能够用多重继承从多个基类派生出新类

9.1  简介①
    本章和下一章要讨论面向对象的程序设计的两个极其重要的功能——继承(inheritance)和多态性(polymorphism)。继承是软件复用的一种形式,实现这种形式的方法是从现有的类建立新类。新类继承了现有类的属性和行为,并且为了使新类具有自己所需的功能,新类还要对这些属性和行为予以修饰。软件复用缩短了程序的开发时间,促使开发人员复用已经测试和调试好的高质量的软件,
    减少了系统投入使用后可能出现的问题。所有这些都是激动人心的。利用多态性可以编写出对现有的各种类和将要实现的类予以加工的程序。继承和多态性是处理复杂软件的一种很有效的技术。
    在建立一个新类时,程序员可以让新类继承预定义基类(baseclass)的数据成员和成员函数,而不必重新编写新的数据成员和成员函数。这种新类称为派生类(derivedclass)。派生类本身也可能会成为未来派生类的基类。对于单一继承,派生类只有一个基类。对于多重继承,派生类常常是从多个基类派生出来的,这些基类之间可能毫无关系。单一继承比较简单,我们介绍几个例子,使读者能很快成为专家。多重继承更复杂,也更容易出错,因此我们只显示简单的例子,建议读者在进一步深造之后再利用这种功能。
    派生类通常添加了其自身的数据成员和成员函数,因而通常比基类大得多。派生类比基类更具体,它代表了一组外延较小的对象。对于单一继承,派生类和基类有相同的起源。继承的真正魅力在于能够添加基类所没有的特点以及取代和改进从基类继承来的特点。
    C++提供三种继承:public、protected和private。本章以介绍public为主.附带介绍另外两种。第15章将介绍如何用private继承代替复合。第三种形式是protected继承,是C++中的新生事物,用得还不多。在public继承中,派生类的对象也是其基类的对象,但是反过来基类对象不是其派生类的对象。本章要利用“派生类对象是基类的对象”这一关系完成一些有趣的操作。例如,把各种不同但又通过继承而相关的对象连成基类对象的链表,它允许用通常的方法处理各种对象。在本章和下一章中就会看到,这是面向对象程序设计的一种重要的功能。
    本章介绍了一种新的成员访问控制形式,即protected访问。派生类及其友元允许访问protected基类成员,而其他函数则不行。
  开发软件系统的经验表明,软件系统中的大部分代码都是处理紧密相关的特殊情况。由于设计者和程序员的精力都集中于这些特殊情况,因而很难在这种系统中看到“大手笔”的程序作品。面向对象的程序设计提供了几种“见树木而知森林”的方法,有时把这个过程称为抽象。
    如果一个程序中有多种密切相关的特殊情况,通常的做法是用switch语句来区分各种特殊情况,然后对每种情况提供处理逻辑。第10章将讲述如何通过继承和多态性用更简单的逻辑取代switch逻辑。
    我们要区别“是一个对象”和“有一个对象”的差别(以下简称“是”关系和“有”关系)。“是”关系是一种继承,在“是”关系中,派生类的对象也以作为基类的对象处理。而“有”关系是一种复合(见图7.4),在这种关系中,一个类的对象拥有作为其成员的其他类的对象。
    派生类不能访问其基类的private成员,否则会破坏基类的封装性。但是,派生类能够访问基类的public成员和protected成员。基类中不应该让派生类通过继承而访问的成员要在基类中声明为privale。派生类只能通过基类public和protected接口提供的访问函数访问基类的private成员。
    继承存在的一个问题是派生类会继承它无需拥有或者不应该拥有的基类public成员函数。基类中不适合于派生类的成员可以在派生类中重新加以定义。有些情况下,不适合用public继承。
    继承所最具有吸引力的特点是新类可以从现有的类库中继承。项目开发者可以开发出自己的类库,也可以利用已广为使用的类库。基于这种观点,将来有一天,软件也可以像当今的硬件一样用标准的可复用组件进行构造。未来需要功能更强的软件,软件的这种开发方式正可以迎接这种挑战。

9.2  继承:基类和派生类
    一个类的对象经常会是另一个类的对象。例如,矩形当然是四边形(正方形、平行四边形和梯形也是这样),因此可以说矩形类Rectangle是从四边形类Quadrilateral继承而来的。在本例中,类Quadrilateral叫做基类,类Rectangle称为派生类。矩形是四边形的一种特殊类型,但是要说四边形是矩形则是不正确的。图9.1示例了几个简单的继承例子。

    基类            派生类 
    student         GraduateStudent
                    UndergraduateStudent
    Shape           Circle
                    Triangle
                    Rectangle
    Loan            CarLoan
                    HomeIpprovementLoan
                    MoregageLoan
    Employee        FacultyMember
                    StaffMember
    Account         CheckingAccount
                    SavingsAccount
 


                                 图9.1  几个简单的继承例子

    其他的面向对象程序设计语言使用了不同的术语。例如,在继承方面,smslltalk语言把基类类称为超类,派生类叫做子类。 
因为由继承而产生的派生类通常比基类大,所以超类和子类这样的术语似乎是不合适的,本书没有使用这些术语。由于派生类对象可以看成基类的对象,因此基类有更多相关对象,而派生类的相关对象更少,因此可以把基类理解为“超类”,派生类理解为“子类”。
    继承形成了树状层次结构,基类和它的派生类构成了一种层次关系。一个类可以单独存在,但是当利用继承机制使用该类时,该类就成为给其他类提供属性和行为的基类,或者成为继承其他类的属性和行为的派生类。
    下面是一个简单的继承层次结构。一个典型的大学社区有成千上万个人,他们是社区的成员。这些人由雇员(employee)和学生(student)组成。雇员又分为学院成员(faculty)和职员(staff),学院成员既可能是校长和系主任等等的管理者(administrator),也可能是教员(teacher)。这种关系构成的继承层次结构如图9.2所示。注意有些行政人员也任了课,因此我们用多重继承构成AdministratorTeacher类。由于学生常常在学校打工,职工也常常去修课,因此还可以用多重继承构成EmployeeStudent类。




                                 图9.2  大学社区成员的继承层次结构

    另外一个实际存在的继承层次结构是像图9.3那样的shape层次结构。初次学习面向对象程序设计的学生都认为现实世界中存在着大量具有层次结构的实例,也正因为如此,这些学生从来没有认真思考过现实世界中的这种层次结构是如何分门别类的,所以应该在这方面好好思考一下。





                                  图 9.3 类shape的部分层次结构

为了说明类CommissionWorker是从类Employee派生而来的,类CommissionWorker通常要作如下形式的定义;
    class CommissionWorker:public Employee{
   上述继承方法称为public继承(public inheritance),这种类型的继承是最常用的。本章还要讨论private继承(privateinheritane)和protected继承(protectedinheritance)。对于public继承来说,基类的public成员和protected成员可以分别作为派生类的public成员和protected成员而被继承。    用类似的方法处理基类对象和派生类对象是可能的。基类的属性和行为表述了基类对象及派生类对象的共性。从基类public派生出来的所有对象都可以作为基类对象处理。我们将研究很多例子。在这些例子中,我们可以利用这种关系很容易地设计程序,而非面向对象的语言(如C语言)就做不到这一点。

9.3  protected成员
    基类的public成员能够被程序中所有函数访问,private成员只能被基类的成员函数和友元访问。protected访问是public访问和private访问之间的中间层次。基类的protected成员只能被基类的成员和友元以及派生类的成员和友元访问。派生类成员简单地使用成员名就可以引用基类的public成员和protected成员。注意protected数据破坏了封装,基类protected成员改变时,所有派生类都要修改。

    软件工程视点9. 1
    一般来说,声明private类的数据成员和使用Protected方式只能是系统要满足特定性能要求时的“最后一招”。

9.4  把基类指针强制转换为派生类指针
    公有派生类的对象可作为其相应基类的对象处理,这使得一些有意义的操作成为可能。例如,从某个特定基类派生出来的各种类,尽管这些类的对象彼此之间互不相同,但是仍然能够建立这些对象的链表,只要把这些对象作为基类对象处理就可以了。然而反过来是不行的,基类的对象不能自动成为派生类的对象。

    常见编程错误9.1
    将基类对象作为派生类对象处理。

    程序员可以用显式类型转换把基类指针强制转换为派生类指针。但是,如果要复引用该指针,那么在转换前首先应该把它指向某个派生类的对象,这一点要小心。本节采用大多数编译器中常用的方法。第21章将介绍符合ANSI/ISO C++草案标准的最新编译器的新特性,包括运行时类型信息(RTFI)、dynamic_cast和typeid。

    常见编程错误9.2
    把指向基类对象的指针显式地强制转换为派生类指针,然后引用该对象中并不存在的派生类的成更会导
致运行时的逻辑错误。

    第一个例子见图9.4。第1行到第39行是类Point的定义和其成员函数的定义,第40行到第94行是类Circle的定义和其成员函数的定义,第95行到第132行是类的驱动程序,该程序演示了如何把派生类指针赋给基类指针和如何把基类指针强制转换为派生类指针。余下的部分是程序输出。
    首先看一下类Point的定义。Point的public接口包含成员函数setPoint、getX和getY。Point的数据成员x和y指定为protected,从而在防止了Point对象的用户直接访问这些数据的同时,又能够让派生类直接访问继承来的数据成员。如果将数据成员指定为private,那么就要用Point的public成员函数甚至派生类来访问这些数据。注意,由于重载的流插入运算符函数是类Point的友元,所以Point重载流插入函数能够直接引用变量x和y。因为重载的流插入运算符函数不是类Point的成员函数,所以需要通过对象来引用变量x和y(即p.x和p.y)。注意这个类提供内联的publiic成员函数getX
和getY,因此operator<<不必成为友元就可以达到良好性能。但所需public成员函数不一定在每个类的public接口中提供,因此最好还是建立友元。

1 // Fig. 9.4: point.h
2 // Definition of class Point
3 #ifndef POINT H
4 #define POINT H
5
6 class Point {
7   friend ostream &operator<<( ostream &, const Point & );
8 public:
9    Point( int = 0, int = 0 );     // 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 by derived classes
14   int x, y;      // x and y coordinates of the Point
15 };
16
17 #endif
18 // Fig. 9.4: point.cpp
19 // Member functions for class Point
20 #include <iostream.h>
21 #include "point.h"
22
23 // Constructor for class Point
24 Point::Point( int a, int b ) { setPoint( a, b ); }
25
26 // Set x and y coordinates of Point
27 void Point::setPoint{ int a, int b )
28 {
29
30   y = b;
31 }
32
33 // output Point (with overloaded stream insertion operator)
34 ostream &operator<<{ ostream &output, const Point &p )
35 {
36   output << '[' << p.x << ", "<< p.y << ']';
37
38   return output;  // enables cascaded calls
39 }
40 // Fig. 9.4: circle.h
41 // Definition of class Circle
42 #ifndef CIRCLE_H
43 #define CIRCLE_H
44
45 #include <iostream.h>
46 #include <iomanip.h>
47 #include "point.h"
48
49 class Circle : public Point {  // Circle inherits from Point
50   friend ostream &operator<<( ostream &, const Circle & );
51 public:
52   // default constructor
53   Circle( double r = 0.0, int x = O, int y = 0 );
54
55   void setRadius( double );  // set radius
56   double getRadius() const;  // return radius
57   double area() const;      // calculate area
58 protected:
59   double radius;
60 };
61
62 #endif
63 // Fig. 9.4:circle.cpp
64 // Member function definitions for class Circle
65 #include "circle.h"
66
67 // Constructor for Circle calls constructor for Point
68 // with a member initializer then initializes radius.
69 Circle::Circle( double r, int a, int b )
70   : Point( a, b )      // call base-class constructor
71 { setRadius( r ); }
72
73 // Set radius of Circle
74 void Circle::setRadius( double r )
75    { radius = ( r >= O ? r : 0 ); }
76
77 // Get radius of Circle
78 double Circle::getRadius() const { return radius; }
79
80 // Calculate area of Circle
81 double circle::area() const
82  { return 3.14159 * radius * radius; }
83
84 // Output a Circle in the form:
86 ostream &operator<<( ostream &output, const Circle &c )
87 {
88   output << "Center =" << static cast< Point >( c )
89          << "; Radius ="
90          << setiosflags( ios::fixed | ios::showpoint )
91          << setprecision( 2 ) << c.radius;
92
93   return output;  // enables cascaded calls
94 }
95 // Fig. 9.4:fig09 04.cpp
96 // Casting base-class pointers to derived-class pointers
97 #include <iostream.h>
98 #include <iomanip.h>
99 #include "point.h"
100 #include "circle.h"
101
102 int main()
103 {
104    Point *pointPtr = 0, p( 30, 50 );
105    Circle *circlePtr = 0, c( 2.7, 120, 89 );
106
107   cout << "Point p: "<< p << ,\nCircle C: "<< c << '\n';
108
109    // Treat a Circle as a Point (see only the base class part)
110    pointPtr = &C;  // assign address of Circle to pointPtr
111    cout << "\nCircle C (via *pointPtr):"
112         << *pointPtr << '\n';
113
114   // Treat a Circle as a Circle (with some Casting)
115  pointPtr = &C;  // assign address of Circle to pointPtr
116
117   // cast base-class pointer to derived-class pointer
118   circlePtr = static cast< Circle * >( pointPtr );
119   cout << "\nCircle C (via *circlePtr):\n" << *circlePtr
120        << "\nArea of C (via circlePtr):"
121       << circlePtr->area() << '\n';
122
123   // DANGEROUS: Treat a Point as a Circle
124   pointPtr = &p;  // assign address of Point to pointPtr
125
126  // cast base-class pointer to derived-class pointer
127   circlePtr = static_cast< Circle * >( pointPtr );

⌨️ 快捷键说明

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