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

📄 09章 继承.txt

📁 C++大学教程txt版中文版 C++大学教程txt版中文版
💻 TXT
📖 第 1 页 / 共 5 页
字号:
128   cout << "\nPoint p (via *circlePtr):\n" << *circlePtr
129       << "\nArea of object circlePtr points to:"
130       << circlePtr->area() << endl;
131   return 0;
132 }

输出结果:
Point p: [ 30, 50]
Circle c: Center = [ 120, 89]; Radius = 2.70

Circle c(via *circlePtr):[ 120,89 ]

Circle c( via *circlePtr ):
Center = [ 120,89] ;Radius = 2.70
Area of c (via circlePtr): 22.90

oint p( via *circlePtr ):
Center = [ 30, 50]; Radius = 0.00
Area of object circlePtr points to: 0.00

                                       图 9.4 把基类指针强制转换为派生类指针

类Circle继承了类Point,类定义的第一行指定了这种继承是public继承:

class Circle : public Point { // Circle inherits from Point

Point和Circle重载的流插入运算符输出了这两个对象的信息。然后,驱动程序将派生类指针(对象c的地址)赋绐基类指针pointPtr并用Point的operator<<输出Circle的对象c,并复引用指针*pointPtr。
注意只显示Circle对象c的Point部分。对public继承,总是可以将派生类指针赋给基类,因为派生类对象也是基类对象。基类指针只“看到”派生类对象的基类部分。编译器进行派生类指针向基类指针的隐式转换。
    随后,程序将派生类指针(对象c的地址)赋给基类指针pointPtr,并将pointPtr强制转换回Circle*类型,强制转换后的结果赋给指针circlePtr。使用Circle重载流插入运算符输出Circle的对象c并复引用指针*circlePtr。然后通过指针circlePtr输出Circle对象c的面积。因为该指针一直指向Circle对象,所以输出了该对象的合法面积值。
    因为把基类指针直接赋给派生类指针蕴含着危险性,所以编译器不允许这么做,也不执行隐式转换。使用显式类型转换是告诉编译器程序员已经知道了这种危险性。正确地使用指针是程序员的责任,因此编译器允许有危险的转换。
    接着,程序演示了将基类指引(对象p的地址)赋给基类指针pointPtr,并将pointPtr强制转换为Circle*类型,强制转换操作的结果赋给了circlePtr。Point对象p用Circle的operator<<输出,并复引用指针*circlePtr。注意半径元素输出为0(实际上不存在,因为circlePtr实际上针对Point对象)。将Point作为Circle输出就会导致radius为未定义的值(这里刚好为0),因为指针总是指向Point对象。Point对象没有radius成员,因此输出circlePtr所指radius数据成员内存地址中的值。circlePtr所指对象的面积(Point对象P)也是通过circlePtr输出。注意面积值为0.00,这是根据radius“未定义”的值算出的。显然,访问不存在的数据成员是很危险的。调用不存在的成员函数可能使程序崩溃。
    本节介绍指针转换的机制。为下一章介绍多态与面向对象编程打下了基础。

9.5  使用成员函数
    当从基类派生出一个派生类时,派生类的成员函数可能需要访问基类的某些成员函数。
    软件工程视点9. 2
    派生类不能直接访问其基类的private成员。
  
    这是C++中关键的软件工程视点。如果派生类能访问其基类的private成员,那么就会破坏基类的封装性。隐藏private成员有助于测试、调试和正确地修改系统。如果派生类能访问其基类的private成员,那么从派生类派生出的类也应该能访问这些成员,这样就会传递对private数据的访问权,从而使封装所带来的益处在整个类层次上损失殆尽。

9.6  在派生类中重定义基类成员    
    派生类可以通过提供同样签名的新版本(如果签名不同,则是函数重载而不是函数重定义)重新定义基类成员函数。派生类引用该函数时会自动选择派生类中的版本。作用域运算符可用来从派生类中访问基类的该成员函数的版本。

    常见编程错误9.3
    派生类中重新定义基类的成员函数时,为完成某些附加工作.派生类版本通常要调用基类中的该函数版本。不使用作用域运算符会由于派生类成员函数实际上调用了自身而引起无穷递归。这样会使系统用光内存,是致命的运行时错误。

    考察一个简单的类Employee,它存储雇员的姓(成员firstName)和名(成员lastName)。这种信息对于所有雇员(包括Employee的派生类的雇员)是很普遍的。现在假设从雇员类Employee派生出了小时工类HourlyWorker、计件工类PieceWorker、老板类Boss和销售员类CommissionWorker。小时工每周工作40小时,超过40小时部分的报酬是平时的1.5倍;计件工是按生产的工作计算报酬的,每件的报酬是固定的,假设他只生成一种类型的工件,因而类PieceWorker的private数据成员是生产的工件数量和每件的报酬;老板每周有固定的薪水;销售员每周有小部分固定的基本工资加上其每周销售额的固定百分比。为简单起见,此处只研究类Empbyee和派生类HourlyWorker。
    本章的第二个例子见图9.5。第1行到47行分别是类Employee的定义和其成员函数的定义,第48行到94行分别是类HoudyWorker的定义和其成员函数的定义,第95行到结束是类继承层次Employee/HourlyWorker的驱动程序,该程序很简单,仅仅建立并初始化了类HourlyWorker的对象,然后调用类HourlyWorker的成员函数print输出对象的数据。

1 // Fig. 9.5: employ.h
2 // Definition of class Employee
3 #ifndef EMPLOY_H
4 #define EMPLOY_H
5
6 class Employee {
7 public:
8   Employee( const char *, const char * );  // constructor
9   void print() const;  // output first an last name
10  ~Employee();         // destructor
11 private:
12   char *firstName;    // dynamically allocated string
13   char * lastName;    // dynamically allocated string
14 } ;
15
16 #endif
17 // Fig. 9.5: employ.cpp
18 // Member function definitions for class Employee
19 #include <string.h>
21 #include <assert.h>
22 #include "employ.h"
23
24 // Constructor dynamically allocates space for the
25 // first and last name and uses strcpy to copy
26 // the first and last names into the object.
27 Employee::Employee( const char *first, const char *last )
28 {
29   firstName = new char( strlen( first ) + 1);
30   assert( firstName != 0 ); // terminate if not allocated
31   strcpy( firstName, first );
32
33   lastName = new char( strlen( last ) + 1 );
34   assert( lastName != 0 );  // terminate if not allocated
35   strcpy( lastName, last );
36 }
37
38 // Output employee name
39 void Employee::print() const
40   {cout << firstName << ' ' << lastName; }
41
42 // Destructor deallocates dynamically allocated memory
43 Employee::~Employee()
44 {
45   delete [] firstName;  // reclaim dynamic memory
46   delete [] lastName;   // reclaim dynamic memory
47 }
48 // Fig. 9.5: hourly.h
49 // Definition of class HourlyWorker
50 #ifndef HOURLY_H
51 #define HOURLY_H
52
53 #include "employ.h"
54
55 class HourlyWorker : public Employee {
56 public:
57   HourlyWorker( const char*, const char*, double, double );
58   double getPayO const;  // calculate and return salary
59   void print() const;    // overridden base-class print
60 private:
61   double wage;          // wage per hour
62   double hours;         // hours worked for week
63 };
64
65 #endif
66 // Fig. 9.5: hourly.cpp
67 // Member function definitions for class HourlyWorker
68 #include <iostream.h>
69 #include <iomanip.h>
70 #include "hourly.h"
71
72 // Constructor for class HourlyWorker
73 HourlyWorker::HourlyWorker(constchar*first,
74                       const char *last,
75                       double initHours, double initwage )
76   : Employee( first, last )  // call base-class constructor
77 {
70   hours = initHours;  // should validate
79   wage = initWage;   // should validate
80 }
81
82 // Get the HourlyWorker's pay
83 double HourlyWorker::getPay() const { return wage * hours; }
84
85 // Print the HourlyWorker's name and pay
86 void HourlyWorker::print() const
87 {
88   cout << "HourlyWorker::print() is executing\n\n";
89   Employee::print();  // call base-class print function
90
91   cout <<" is an hourly worker with pay of $"
92        << setiosflags( ios::fixed | ios::showpoint )
93       << setprecision( 2 ) << getPay() << endl;
94 }
95 // Fig. 9.5: fig.09_05.cpp
96 // Overriding a base-class member function in a
97 // derived class.
98 #include <iostream.h>
99 #include "hourly.h"
100
101 int main()
102 {
103   HourlyWorker h( "Bob", "Smith", 40.0, 10.00 );
104   h.print();
105   return 0;
106 }

输出结果:
HourlyWorker::print() is executing
Bob Smith is an hourly worker with pay of $400.00

                                图 9.5 在派生类中重新定义基类的成员函数

    类Employee的定义由两个private char*类型的数据成员(fisttName和lastName)和三个成员函数(构造函数、析构函数和print函数)组成。构造函数接收两个字符串,并动态分配存储字符串的字符数组。宏assert(见第18章)用来确定是否为firstName和lastName分配了内存。如果没有,程序终止并返回一条出错信息,该信息指出了被测试的条件以及条件所在的行号和文件。由于Employee的数据是private类型,所以只能用成员函数print访问数据,函数print非常简单,仅仅输出雇员的姓和名。析构函数将动态分配的内存交还给系统(防止内存泄漏)。
    类HoudyWorker对类Employee的继承是public继承。类定义的第一行指定了这种继承方式:
    class HourlyWorker:public EmPloyee
HourlyWorker的public接口包括Employee的函数print和HourlyWorker的成员函数getPay和print。注意,类HourlyWorker定义了其自身的print函数(使用同样的函数原型Employee:print()),所以类HourlyWorker有权访问两个print函数。类HourlyWorker还包含用来计算雇员的每周薪水的private数据成员wage和hours。
    HourlyWorker的构造函数用成员初始化值语法将字符串first和last传递给Employee的构造函数,从而初始化了基类的成员,然后再初始化成员wage和hours。成员函数getPay用来计算HourlyWorker的工资。
    类HourlyWorker的成员函数print重新定义Employee的print成员函数。为提供更多的功能而在派生类中重新定义基类的成员函数是常有的事。被重新定义的函数有时候为执行一些新任务而要调用基类中的函数版本。在本例中,派生类函数print调用基类Employee的print函数输出了雇员的名字(基类print函数是惟一能访问该类private数据的函数),派生类的print函数输出了雇员的工资。
调用基类print函数的方法如下:
    Employee::print();
因为基类函数和派生类函数的名字相同,所以必须在基类函数前使用基类名和作用域运算符,否则将调用派生类的函数版本(即类HourlyWorker的print函数调用其自身),从而导致无穷递归。

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
    建立成员对象的顺序是对象在类定义中的声明顺序。成员初始化值的顺序不影响建立对象的顺序。

⌨️ 快捷键说明

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