114
程程程程 cs.sjtu 2011.9 程程程程 - 1 12 12 第 第第第第第 第 第第第第第 第第 第第 第第第第第第第 第第第第第第第第 第第第 第第第第第第第第

第 12 章 组合与继承

  • Upload
    daisy

  • View
    117

  • Download
    9

Embed Size (px)

DESCRIPTION

第 12 章 组合与继承. 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例. 派生类的概念. 继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类 基类、父类 派生类、导出类或子类 继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。. 派生类. 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作  重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象. 派生类的定义. 一般格式: - PowerPoint PPT Presentation

Citation preview

Page 1: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 1

第第 1212 章 组合与继承章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

Page 2: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 2

派生类的概念派生类的概念 继承是面向对象程序设计的一个重要特征,它

允许在已有类的基础上创建新的类 基类、父类 派生类、导出类或子类 继承可以让程序员在已有类的基础上通过增加

或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。

Page 3: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 3

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 4: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 4

派生类的定义派生类的定义 一般格式: class 派生类名:派生方法 基类名 {// 派生类新增的数据成员和成员函数 } ; 派生方法:1. 公有派生 : public2. 私有派生: private3. 保护派生: protected

Page 5: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 5

派生实例派生实例class base { int x; public: void setx(int k);}class derived1:public base { int y; public: void sety(int k);}

Derived1 有两个数据成员: x , y 。有两个成员函数:setx 和 sety

Page 6: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 6

派生类对基类成员的访问派生类对基类成员的访问 派生类的成员函数不能访问基类的私有数据成员 protected 访问特性

protected 成员是一类特殊的私有成员,它不可以被全局函数或其他类的成员函数访问,但能被派生类的成员函数访问 protected 成员破坏了类的封装,基类的

protected 成员改变时,所有派生类都要修改

Page 7: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 7

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 8: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 8

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

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

                    可以直接由任何非 static  成员函数、友元函数访问private         在派生类中隐藏 在派生类中隐藏 在派生类中隐藏 可以通过基类的 public  或 protected 成员函数或非 static 成员函数和友元函数访问

                                 

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

Page 9: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 9

  Derived1

不可访问 Int x

private Int y

public Setx()Sety()

class base { int x; public: void setx(int k);}class derived1:public base { int y; public: void sety(int k);}

Page 10: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 10

继承实例继承实例 定义一个二维平面上的点类型,可以

设置点的位置,获取点的位置。在此基础上,扩展出一个三维空间上的点类型。

Page 11: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 11

point_2dpoint_2d 的定义的定义class point_2d

{private: int x,y;

public:

void setpoint2(int a, int b) {x = a; y = b;}

int getx() {return x;}

int gety() {return y;}

};

Page 12: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 12

point_3dpoint_3d 的定义的定义class point_3d:public point_2d{int z; public: void setpoint3(int a,int b,int c)

{setpoint2(a,b); z=c;} int getz() {return z;}}

Page 13: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 13

point_3dpoint_3d 的讨论的讨论 point_3d 的组成:有三个数据成员: x , y 和 z

,有五个公有的成员函数: setpoint2, setpoint3, getx, gety 和 getz 。

point_3d 的成员函数无法直接访问基类的 x 和 y,因此在 setpoint3 函数中必须调用在 point_2d的公有成员函数 setpoint2 实现。

point_3d 类的使用和普通类完全一样,用户不用去管这个类是用继承方式从另外一个类扩展而来,还是完全直接定义的。

Page 14: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 14

Point_3dPoint_3d 的使用的使用int main(){point_2d p1; point_3d p2;

p1.setpoint2(1, 2); cout << "p1: (" << p1.getx() << ", " << p1.gety() << ")" << endl;

p2.setpoint3(1, 2, 3); cout << "p2: (" << p2.getx() << ", " << p2.gety() << ", " << p2.getz() << ")" << endl;

return 0;}

P1: (1, 2)

P2: (1, 2, 3)

Page 15: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 15

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 16: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 16

派生类的构造函数和析构函数 派生类的构造函数和析构函数 由于派生类继承了其基类的成员,所以在

建立派生类的实例对象时,必须初始化基类继承的数据成员。派生类对象析构时,也必须析构基类对象。

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

Page 17: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 17

派生类的构造函数派生类的构造函数 基类成员的初始化由基类的构造函数完成。派

生类的构造函数调用基类的构造函数完成基类成员的初始化。

派生类构造函数可以隐式调用基类缺省的构造函数,也可以在派生类的构造函数的初始化列表显式地调用基类的构造函数。

Page 18: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 18

构造函数的格式构造函数的格式 派生类构造函数的格式:

派生类构造函数名(参数表): 基类构造函数名(参数表){ 。。。 }

基类构造函数中的参数表通常来源于派生类构造函数的参数表,也可以用常数值。

如果构造派生类对象时调用的是基类的缺省构造函数,则可以不要初始化列表。

如果派生类新增的数据成员中含有对象成员,则在创建对象时,先执行基类的构造函数,再执行成员对象的构造函数,最后执行自己的构造函数体。

Page 19: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 19

派生类对象的析构派生类对象的析构 派生类的析构函数值析构自己新增的数据成员,基类成员的析构由基类的析构函数析构 派生类析构函数会自动调用基类的析构函数 派生类对象析构时,先执行派生类的析构函数,再执行基类的析构函数

Page 20: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 20

派生类构造实例派生类构造实例 定义一个二维平面上的点类,并从它派生出一个圆类

Page 21: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 21

point2.hpoint2.h// Definition of class Point#ifndef POINT2_H#define POINT2_Hclass Point {

public:    Point( int = 0, int = 0 );  // default constructor   ~Point();   // destructorprotected:     // accessible by derived classes   int x, y;   // x and y coordinates of Point};

#endif

Page 22: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 22

point2.cpppoint2.cpp#include "point2.h"

// Constructor for class PointPoint::Point( int a, int b ) { x = a; y = b;

   cout << "Point constructor:"      << '[' << x << ", "<< y << ']' << endl;

}

// Destructor for class PointPoint::~Point() { cout << "Point destructor:  "

     << '[' << x << ", "<< y << ']' << endl; }

Page 23: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 23

circle2.hcircle2.h#ifndef CIRCLE2_H#define CIRCLE2_H

#include "point2.h"

class Circle : public Point { public:

  // default constructor   Circle( double r = 0.0, int x = 0, int y = 0 );

   ~Circle(); private:

   double radius; };

#endif

Page 24: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 24

circle2.cppcircle2.cpp#include "circle2.h"

// Constructor for Circle calls constructor for PointCircle::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 CircleCircle::~Circle() {    cout << "Circle destructor: radius is "

        << radius << " [ " << x << ", "<< y << ']'  << endl; }

Page 25: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 25

CircleCircle 类的应用类的应用#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 ]

Page 26: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 26

派生类构造函数的构造规则派生类构造函数的构造规则 若基类使用缺省或不带参数的构造函数,则在

派生类定义构造函数是可略去:基类构造函数名(参数表)。此时若派生类也不需要构造函数,则可不定义构造函数。

当基类构造函数需要参数,而派生类本身并不需要构造函数时,派生类还必须定义构造函数。该函数只是起了一个参数传递作用。

如果省略了派生类的构造函数,那么就由派生类的默认构造函数调用基类的默认构造函数。

Page 27: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 27

派生类实例派生类实例 定义一个图书馆系统中的读者类,每个

读者的信息包括:卡号、姓名、单位、允许借书的数量以及已借书记录。学生最多允许借 5 本书,教师最多允许借 10本书。

Page 28: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 28

设计过程设计过程 系统中有两类读者:学生读者和教师读者。 这两类读者有一部分内容是相同的:卡号、姓名和单位。 可将两类读者的共同部分内容设计成一个基类。 学生读者和教师读者从基类派生,增加已借书的数量以及已借书记录两个数据成员,并将允许借书的数量定义为整个类共享的常量

Page 29: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 29

基类的设计基类的设计class reader{

int no;char name[10];char dept[20];

public:reader(int n, char *nm, char *d) { no = n; strcpy(name, nm); strcpy(dept, d); }

};

Page 30: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 30

教师读者类的设计教师读者类的设计class readerTeacher :public reader{

enum {MAX = 10}; int borrowed;int record[MAX];

public:readerTeacher(int n, char *nm, char *d):reader(n, nm, d)

{ borrowed = 0;}bool bookBorrow(int bookNo);bool bookReturn(int bookNo); void show(); // 显示已借书信息

};

Page 31: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 31

学生读者类的设计学生读者类的设计class readerStudent :public reader {

enum { MAX = 5};int borrowed;int record[MAX];

public:readerStudent(int n, char *nm, char *d):reader(n, nm, d)

{ borrowed = 0; }bool bookBorrow(int bookNo);bool bookReturn(int bookNo); void show(); // 显示已借书信息

};

Page 32: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 32

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 33: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 33

重定义基类的函数 重定义基类的函数 派生类是基类的扩展,可以是保存的数据内容的

扩展,也可以是功能的扩展。 当派生类对基类的某个功能进行扩展时,他定义

的成员函数名可能会和基类的成员函数名重复。 如果只是函数名相同,而原型不同时,系统认为

派生类中有两个重载函数。如果原型完全相同,则派生类的函数会覆盖基类的函数。这称为重定义基类的成员函数。

Page 34: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 34

实例实例 定义一个圆类型,用于保存圆以及输

出圆的面积和周长。在此类型的基础上派生出一个球类型,可以计算球的表面积和体积。

Page 35: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 35

圆类的设计圆类的设计 数据成员:圆的半径 成员函数:由于需要提供圆的面积和周长,需要提供两个公有的成员函数。除了这些之外,还需要一个构造函数

Page 36: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 36

圆类的定义圆类的定义class circle {protected: double radius;public:

circle(double r = 0) {radius = r;}double getr() {return radius;}double area() { return 3.14 * radius * radius; }double circum() { return 2 * 3.14 * radius;}

};

Page 37: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 37

球类的定义球类的定义class ball:public circle {public:

ball(double r = 0):circle(r) {}double area()

{ return 4 * 3.14 * radius * radius; }double volumn()

{ return 4 * 3.14 * radius * radius * radius / 3; }};

Page 38: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 38

BallBall 类的构造函数类的构造函数 Ball 类没有新增加数据成员,因而不需

要构造函数。但基类的构造函数需要参数,所以 ball 类必须写构造函数

Ball 类构造函数的作用是为 circle 类的构造函数传递参数

Page 39: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 39

BallBall 类的类的 areaarea 函数函数 Ball 类包含了两个原型完全一样的 area

函数。一个是自己定义的,一个是从circle 类继承来的

当对 ball 类的对象调用 area 函数时,调用的是 ball 类自己定义的 area 函数

Page 40: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 40

派生类引用基类的同名函数派生类引用基类的同名函数 派生类中重新定义基类的成员函数时,它的功能往往是基类功能的扩展。为完成扩展的工作,派生类版本通常要调用基类中的该函数版本。 引用基类的同名函数必须使用作用域运算符,否则会由于派生类成员函数实际上调用了自身而引起无穷递归。这样会使系统用光内存,是致命的运行时错误。

Page 41: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 41

实例实例 在 circle 类的基础上定义一个 cylinder 类。

可以计算圆柱体的表面积和体积 设计考虑:

存储圆柱体可以在圆的基础上增加一个高度。圆柱体的表面积是上下两个圆的面积加上它的侧面积

圆柱体的体积是底面积乘上高度。而求圆的面积的函数在 circle 类中已经存在。

Page 42: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 42

CylinderCylinder 类的定义类的定义class cylinder:public circle {

double height;

public:

cylinder(double r = 0, double h = 0):circle(r)

{height = h;}

double geth() {return height;}

double area()

{ return 2 * circle::area() + circum() * height; }

double volumn() { return circle::area() * height ; }

};

Page 43: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 43

BallBall 和和 cylindercylinder 类的使用类的使用int main(){circle c(3); ball b(2); cylinder cy(1,2);

cout << "circle: r=" << c.getr() << endl; cout << "area=" << c.area() << "\tcircum=" << c.circum() << endl;

cout << "ball: r=" << b.getr() << endl; cout << "area=" << b.area() << "\tvolumn=" << b.volumn() << endl;

cout << "cylinder: r=" << cy.getr() << "\th = " << cy.geth() << endl; cout << "area=" << cy.area() << "\tvolumn=" << cy.volumn() << endl; return 0;}

Page 44: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 44

执行结果执行结果circle: r=3area=28.26 circum=18.84ball: r=2area=50.24 volumn=33.4933cylinder: r=1area=18.84 volumn=6.28

Page 45: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 45

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 46: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 46

派生类作为基类派生类作为基类 基类本身可以是一个派生类,如:

class base { … }class d1:public base {…}class d2:public d1 {…}

每个派生类继承他的直接基类的所有成员。 如果派生类的基类是一个派生类,则每个派生类只负责他的直接基类的构造,依次上溯。 当构造 d2 类的对象时,会先调用 d1 的构造函数,而 d1的构造函数执行时又会先调用 base 的构造函数。因此,构造 d2 类的对象时,最先初始化的是 base 的数据成员,再初始化 d1 新增的成员,最后初始化 d2 新增的成员。 析构的过程正好相反。

Page 47: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 47

实例实例#include <iostream>using namespace std;

class base{ int x; public: base(int xx) {x=xx; cout<<"constructing base\n";} ~base() {cout<<"destructint base\n";} };class derive1:public base{ int y;public:

derive1(int xx, int yy): base(xx) {y = yy; cout<<"constructing derive1\n";} ~derive1() {cout<<"destructing derive1\n";} };

Page 48: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 48

class derive2:public derive1{ int z;public:

derive2(int xx, int yy, int zz):derive1(xx, yy) {z = zz;cout<<"constructing derive2\n";} ~derive2() {cout<<"destructing derive2\n";} };main(){derive2 op(1, 2, 3); return 0;}

constructing baseconstructing derive1constructing derive2 destructing derive2destructing derive1destructint base

Page 49: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 49

派生类派生类 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象

Page 50: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 50

将派生类对象隐式转换为基类对象将派生类对象隐式转换为基类对象 将派生类对象赋给基类对象 基类指针指向派生类对象 基类的对象引用派生类的对象

Page 51: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 51

将派生类对象赋给基类对象将派生类对象赋给基类对象 派生类中的基类部分赋给此基类对象,派生类

新增加的成员就舍弃了。赋值后,基类对象和派生类对象再无任何关系。

class base { public: int a; };class d1:public base{ public: int b; };

d1 d;d.a = 1; d.b = 2;base bb = d;cout << bb.a; // 输出 1bb.a = 3;cout << d.a; // 输出 1

Page 52: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 52

基类指针指向派生类对象基类指针指向派生类对象 尽管该指针指向的对象是一个派生类对象,但由

于它本身是一个基类的指针,它只能解释基类的成员,而不能解释派生类新增的成员。因此,只能访问派生类中的基类部分。

通过指针修改基类对象时,派生类对象也被修改。 d1 d;

d.a = 1; d.b = 2;base *bp = &d;cout << bp->a; // 输出 1Bp->a = 3;cout << d.a; // 输出 3

Page 53: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 53

基类的对象引用派生类的对象基类的对象引用派生类的对象 给派生类中的基类部分取个别名。 基类对象改变时,派生类对象也被修改。

d1 d;d.a = 1; d.b = 2;base &bb = d;cout << bb.a; // 输出 1bb.a = 3;cout << d.a; // 输出 3

Page 54: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 54

class Shape {public:

void printShapeName() {cout<<“Shape”<<endl;} };

class Point : public Shape {public:

void printShapeName() {cout<<“Point”<<endl;}}

class Circle : public Point {public:

void printShapeName() {cout<<“Circle”<<endl;}}

class Cylinder : public Circle { public:

void printShapeName() {cout<<“Cylinder”<<endl;}}

实例

Page 55: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 55

将派生类对象赋给基类对象将派生类对象赋给基类对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape shapes[3]= {aPoint, aCircle, aCylinder};

for (i=0;i<3;i++) shapes[i].printShapeName(); ShapeShapeShape

Page 56: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 56

基类指针指向派生类对象基类指针指向派生类对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape *pShape[3]= { &aPoint, &aCircle, &aCylinder };

for (i=0;i<3;i++) pShape[i]->printShapeName();

ShapeShapeShape

Page 57: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 57

基类的对象引用派生类的对象基类的对象引用派生类的对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;

Shape &shape1= aPoint;

shape1.printShapeName();

shape

Page 58: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 58

注意注意 不能将基类对象赋给派生类对象,除非在基类中定义了向派生类的类型转换函数 不能将基类对象地址赋给指向派生类对象的指针 也不能将指向基类对象的指针赋给指向派生类对象的指针。如果程序员能确保这个基类指针指向的是一个派生类的对象,则可以用

reinterpret_cast 类型的强制类型转换。表示程序员知道这个风险

Page 59: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 59

第第 1212 章 组合与继承章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

Page 60: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 60

虚函数与多态性虚函数与多态性 多态性 虚函数 虚析构函数

Page 61: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 61

多态性多态性 多态性:不同对象收到相同的消息时产

生不同的动作。 多态性的作用:便于系统功能的扩展

Page 62: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 62

多态性的实现多态性的实现 静态联编:编译时已决定用哪一个函数

实现某一动作。 动态联编:直到运行时才决定用哪一个

函数来实现动作

Page 63: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 63

静态联编静态联编 函数重载:用同一名字实现访问一组相关的函数 运算符重载 重载函数是通过“名字压延”方法来实现。即在编译时将函数名和参数结合起来创造一个新的函数名,用新的名字替换原有名字。

Page 64: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 64

运行时多态性运行时多态性 运行时多态性是指必须等到程序动态运行时才可确定的多态性,主要通过继承结合动态绑定获得。这与类的继承密切相关。因为存在类型的兼容性,所以有些函数只有在运行时才能确定是调用父类的还是子类的函数。在 C++中,使用虚函数

( Virtual Functions)来实现。

Page 65: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 65

虚函数与多态性虚函数与多态性 多态性 虚函数 虚析构函数

Page 66: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 66

虚函数虚函数 虚函数提供动态重载方式,允许函数调用与函数体之间的联系在运行时才建立。 虚函数的定义:在基类中用关键词 virtual 说明,并在派生类中重新定义的函数称为虚函数。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数与参数类型的顺序都必须与基类中的原型完全相同。 当把一个函数定义为虚函数时,等于告诉编译器,这个成员函数在派生类中可能有不同的实现。必须在执行时根据传递的参数来决定调用哪一个函数

Page 67: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 67

虚函数的使用虚函数的使用 虚函数是与基类指针指向派生类对象,或基类

对象引用派生类对象结合起来实现多态性。 当基类指针指向派生类对象或基类对象引用派

生类对象时,对基类指针或对象调用基类的虚函数,系统会到相应的派生类中寻找此虚函数的重定义。如找到,则执行派生类中的函数。如没有找到,则执行基类的虚函数。

Page 68: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 68

虚函数虚函数class Shape {public:

virtual void printShapeName() {cout<<“Shape”<<endl;} };

class Point:public Shape {public:

virtual void printShapeName() {cout<<“Point”<<endl;}}

class Circle:public Point {public:

virtual void printShapeName() {cout<<“Circle”<<endl;}}

class Cylinder:public Circle { public:

virtual void printShapeName() {cout<<“Cylinder”<<endl;}}

Page 69: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 69

将派生类对象赋给基类对象将派生类对象赋给基类对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape shapes[3]= {aPoint, aCircle, aCylinder};

for (i=0;i<3;i++) shapes[i].printShapeName(); ShapeShapeShape

Page 70: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 70

基类指针指向派生类对象基类指针指向派生类对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;Shape *pShape[3]= { &aPoint, &aCircle, &aCylinder };

for (i=0;i<3;i++) pShape[i]->printShapeName(); PointCircleCylinder

Page 71: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 71

基类的对象引用派生类的对象基类的对象引用派生类的对象int i;Point aPoint;Circle aCircle;Cylinder aCylinder;//Shape *pShape[3]= {&aPoint, &aCircle, &aCylinder};Shape &shape1= aPoint;

//for (i=0;i<3;i++) pShape[i]->printShapeName(); shape1.printShapeName();

Point

Page 72: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 72

使用虚函数的注意事项 使用虚函数的注意事项 在派生类中重新定义虚函数时,它的原型必须

与基类中的虚函数完全相同。否则编译器会把它认为是重载函数,而不是虚函数的重定义。

派生类在对基类的虚函数重定义时,关键字virtual 可以写也可以不写。不管 virtual写或者不写,该函数都被认为是虚函数。但最好是在重定义时写上 virtual 。

Page 73: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 73

例例 正方形是一类特殊的矩形,因此,可以

从 rectangle 类派生一个 square 类。在这两个类中,都有一个显示形状的函数。

Page 74: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 74

class rectangle { int w, h;public: rectangle(int ww, int hh): w(ww), h(hh) {} virtual void display() {cout << “this is a rectangle\n”;}};

class square:public rectangle {public: square(int ss): rectangle(ss, ss) {} void display() // 虚函数 {cout << “this is a square\n”;}};

Page 75: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 75

虚函数与多态性虚函数与多态性 多态性 虚函数 虚析构函数

Page 76: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 76

为什么需要虚析构函数为什么需要虚析构函数 构造函数不能是虚函数,但析构函数可以是

虚函数,而且最好是虚函数 如果派生类新增加的数据成员中含有指针,指向动态申请的内存,那么派生类必须定义析构函数释放这部分空间。但如果派生类的对象是通过基类的指针操作的,则 delete 基类指针指向的对象就会造成内存泄漏。

Page 77: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 77

解决方案解决方案 将基类的析构函数定义为虚函数。 当析构基类指向的派生类的对象时,找

到基类的析构函数。由于基类的析构函数是虚函数,又会找到派生类的析构函数,执行派生类的析构函数。

Page 78: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 78

虚析构函数的继承性虚析构函数的继承性 和其他的虚函数一样,析构函数的虚函

数的性质将被继承。 如果继承层次树中的根类的析构函数是

虚函数的话,所有派生类的析构函数都将是虚函数。

Page 79: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 79

第第 1212 章 组合与继承章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

Page 80: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 80

纯虚函数纯虚函数 纯虚函数:是一个在基类中说明的虚函

数,它在该基类中没有定义,但要在它的派生类里定义自己的版本,或重新说明为纯虚函数

纯虚函数的一般形式 virtual 类型 函数名(参数表) =0

Page 81: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 81

纯虚函数实例纯虚函数实例class shape{ protected:

double x, y; public:

shape(double xx, double yy) {x=xx; y=yy;} virtual double area() = 0 ;

virtual void display()     {cout << "This is a shape. The position is ("       << x << ", " << y << ")\n";}};

Page 82: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 82

抽象类抽象类 抽象类:如果一个类中至少有一个纯虚函数,则该类被称为抽象类 抽象类使用说明: ※ 抽象类只能作为其他类的基类,不能建立抽象类的对象。 ※ 可以声明指向抽象类的指针或引用,此指针可指向它的派生类,进而实现多态性 ※ 抽象类不能用作参数类型、函数返回类型或显式转换类型 ※ 如果派生类中给除了基类所有纯虚函数的实现,则该派生类不再是抽象类,否则仍为抽象类

Page 83: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 83

抽象类的意义抽象类的意义 保证进入继承层次的每个类都具有纯虚函数所要求的行为,这保证了围绕这个继承层次所建立起来的软件系统能正常运行,避免了这个继承层次的用户由于偶尔的失误(忘了为它所建立的派生类提供继承层次所要求的行为)而影响系统正常运行

Page 84: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 84

抽象类实例抽象类实例 下面程序用于计算各类形状的总面积#include <iostream.h>class shape{ public: virtual void area()=0;};class rectangle:public shape{float w,h; public:rectangle(float ww, float hh){w=ww; h=hh;}   void area() {cout<<"\narea is:"<<w*h;}};class circle:public shape{float r; public:circle(float rr) {r=rr;}

  void area(){cout<<"\narea is:"<<3.14*r*r;}};void main(){shape *ptr; rectangle ob1(5,10); circle ob2(10); ptr=&ob1; ptr->area(); ptr=&ob2; ptr->area();}

Page 85: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 85

第第 1212 章 组合与继承章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

Page 86: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 86

多重继承多重继承 一个派生类有多个基类时称为多重继承

多继承时,派生类包含所有基类的成员

A B C

D

Page 87: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 87

多重继承的定义格式多重继承的定义格式class 派生类名: 基类表 { 新增派生类的数据成员和成员函数 } ; 基类表为:派生方法 1 基类名 1 , 派生方法 2 基类名 2 ,……,派生方法 n 基类名 n

Page 88: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 88

多继承的访问特性多继承的访问特性 与单继承相同 取决于每个基类的派生方法,和成员在

基类中的访问特性

Page 89: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 89

多重继承的访问特性多重继承的访问特性 与单继承规则相同,例: #include <iostream.h> class X{int a; public: void setX(int x) {a=x;} void showX() {cout<<a; } }; class Y{int b; public: void setY(int x) {b=x;} void showY() {cout<<b;} }; class Z: public X, private Y{ int c; public: void setZ(int x, int y) {c=x; setY(y);} void showZ() {cout<<c;} }; main() {Z obj; obj.setX(3); obj.showX(); obj.setY(4); obj.showY(); // 非法 obj.setZ(5,6); obj.showZ(); }

类 z 有三个数据成员和六个成员函数

Page 90: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 90

多重继承的构造函数和析构函数多重继承的构造函数和析构函数 构造函数的定义形式 派生类构造函数名(参数表):基类 1构造函数名(参数表),基类 2 构造函数名(参数表),。。。基类 n 构造函数名(参数表) { } 执行顺序:先执行基类(按照继承的次序,而不是构造函数的初始化列表的次序),再执行派生类。 析构的次序和构造相反

Page 91: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 91

多重继承实例多重继承实例#include<iostream.h>class A{ int i; public:

A(int ii=0){ i=ii; cout<<"A... i="<<i<<endl; } void show() { cout<<"A::show() i="<<i<<endl;} };class B{ int i; public:

B(int ii=0){ i=ii; cout<<"B... i="<<i<<endl; } void show() { cout<<"B::show() i="<<i<<endl; } };class C: public A, public B { int i; public: C(int i1=0, int i2=0, int i3=0) :A(i1), B(i2) { i=i3; cout<<"C... i="<<i<<endl;} void show() {cout<<"C::show() i="<<i<<endl;} };void main(){ C c(1,2,3); c.show();}

Page 92: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 92

执行结果执行结果 A... i=1 B... i=2 C... i=3 C::show() i=3

Page 93: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 93

多重继承的主要问题多重继承的主要问题 二义性

多个基类有相同的的成员名有两个以上的基类有共同的基类

Page 94: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 94

二义性二义性 如果多个基类中有相同的成员名,则派生类在引用时就具有二义性。例: class A{ public: void f();}; class B{ public: void f(); void g();}; class C: public A, public B { public: void g(); void h();}; 如有: C x; 则 x.f () 有二义性。

Page 95: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 95

x.f()x.f() 的解决方法的解决方法 方法一: C 类的成员在引用 f 时说明是 A 的

f 还是 B 的 f 。如: x.A::f(); 或 x.B::f();

其缺陷是对 C 的用户不利。 C 用户必须知道自己是从哪些基类派生出来的。

方法二:在 C 类声明中指出基类名。如在 C中可声明: void ha() {A::f(); }

void hb() { B::f();}

Page 96: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 96

二义性二义性 ---cont.---cont.

如果一个派生类从多个基类派生,而这些基类又有公共的基类,则对该基类中声明的名字进行访问时,可能会产生二义性。这个问题由虚基类来解决。

Page 97: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 97

二义性二义性 ---cont.---cont. 如:Class B{ public: int b;};Class B1: public B{ private: int b1;};Class B2: public B{ private: int b2;};Class C: public B1, public B2 {public: int f(); private: int d;};

定义: C c;

下面对 b 的访问是有二义性的:c.b

c.B::b

下面对 b 的访问是正确的:c.B1::b

b.B2::b

Page 98: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 98

二义性实例二义性实例class A{public: void fun(){cout<<"A:fun()"<<endl; } };class B1:public A {public: void fun1(){cout<<"B1:fun1()"<<endl; } };class B2:public A {public: void fun1(){cout<<"B2:fun1()"<<endl; } };class D:public B 1,public B 2 {};void main(){ D obj; //obj.fun1(); // 不可以执行各有 fun1() 函数 ) obj.B1::fun1(); obj.B2::fun1(); //obj.fun(); // 不可以执行 //obj.A::fun(); // 不可以执行:二义性 obj.B1::fun(); // 无二义性:可以执行 }

Page 99: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 99

虚基类虚基类 用途:当一个派生类是从多个基类派生,而这些基类又有一个公共的基类。则在这个派生类中访问公共基类中的成员时会有二义性问题。如上例中的 B, B1,

B2 和 C ,他们的派生关系是:B B

B1 B2

C

如果 B 只有一个拷贝的话,那么在 C 中对 B 成员的访问就不会有二义性。

Page 100: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 100

虚基类的概念虚基类的概念 使公共的基类只产生一个拷贝。 虚基类的定义用关键词 virtual 。如: Class B{ public: int b;}; Class B1: virtual public B{ private: int b1;}; Class B2: virtual public B{ private: int b2;}; Class C: public B1, public B2 {public: int f(); private: int d;}; 这样 B1, B2 公用了一个 B 的拷贝, 对 B 成员的引用就不会产生二义性。

B

B1 B2

C

Page 101: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 101

虚基类的初始化虚基类的初始化 保证虚基类对象只被初始化一次。 虚基类的构造由最终的类的构造函数负责。 例如,在构造 C 的对象时,由 C 的构造函数负责

调用 B 的构造函数,而不是由 B1 、 B2 来调用。 构造次序:先执行 B 的构造函数,再执行

B1 、 B2 的构造函数,最后执行 C 的构造函数。 析构次序与构造次序相反。

Page 102: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 102

虚基类的初始化实例虚基类的初始化实例#include <iostream.h>class B{ int a; public: B(int sa) {a=sa; cout<<"constructing B\n";}};class B1:virtual public B{ int b;

public: B1(int sa, int sb):B(sa) {b=sb; cout<<"constructing B1\n";}};

class B2:virtual public B{ int c;public: B2(int sa, int sb):B(sa) {c=sb; cout<<"constructing B2\n";}};

class C: public B1, public B2{ int d;public: C(int sa, int sb, int sc, int sd):

B(sa), B1(sa, sb), B2(sa,sc) {d=sd; cout<<"constructing C\n";}};main() {C obj(2,4,6,8); return 0;}

Page 103: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 103

虚基类的初始化实例虚基类的初始化实例 执行结果: constructing B constructing B1 constructing B2 constructing C

Page 104: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 104

第第 1212 章 组合与继承章 组合与继承 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例

Page 105: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 105

龟兔赛跑问题龟兔赛跑问题 确定对象:乌龟、兔子和裁判员 类的确定:乌龟类、兔子类和裁判员类 乌龟类没有数据成员,只有一个成员函

数 move

兔子类没有数据成员,只有一个成员函数 move

Page 106: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 106

乌龟类的定义乌龟类的定义class Tortoise {public:

int move(){ int probability = rand() * 10 / (RAND_MAX + 1);

if (probability < 5) return 3; //快走 else if (probability < 7) return -6; // 后滑 else return 1; //慢走

}};

Page 107: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 107

兔子类的定义兔子类的定义class Hare {public:

int move(){ int probability = rand() * 10 / (RAND_MAX + 1);

if (probability < 2) return 0; //睡觉 else if (probability < 4) return -9; //大后滑 else if (probability < 5) return 14; //快走 else if (probability < 8) return 3;//小步跳 else return -2; //慢后滑 }};

Page 108: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 108

裁判员类的设计裁判员类的设计 裁判员的功能:

记录乌龟和兔子在比赛中的位置输出它们的位置判断比赛是否结束并给出比赛的结果

裁判类:两个数据成员,分别为乌龟和兔子的位置 5 个成员函数:

更新乌龟和兔子的位子 update输出乌龟壳兔子的当前位置 print判断比赛是否结束 isFinish并给出胜者输出报表头部的函数重新开始比赛。

Page 109: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 109

裁判员类的定义裁判员类的定义class Judger {

int hare, tortoise; enum { RACE_END = 100 }; // 赛道长度

public:Judger() {

hare = tortoise = 0;srand(time(NULL));

}void restart() { hare = tortoise = 0;} // 重新开始比赛void print_title() const { // 显示输出信息的头部

cout << " hare\ttortoise\n";cout << " ======= ========\n";

}

Page 110: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 110

void update(int h, int t) { // 更新兔子和乌龟的当前位置hare += h;tortoise += t;

}void print() const { // 输出乌龟和兔子的当前位置

cout << hare << '\t' << tortoise << endl;}bool isFinish(int &winner) const {//0:兔子赢, 1:乌龟赢 , 2 :平局 if (hare >= RACE_END || tortoise >= RACE_END) {

if (hare > tortoise) winner = 0;else if (hare < tortoise) winner = 1; else winner = 2; return true;}else return false;

}};

Page 111: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 111

龟兔赛跑过程模拟 龟兔赛跑过程模拟 int main(){ Tortoise tort; Hare hare;

Judger judger;int winner;judger.print_title();while (!judger.isFinish(winner)) {

judger.update(hare.move(), tort.move());judger.print();

}switch (winner) { // 输出比赛结果 case 0:cout << "兔子赢了! "; break; case 1:cout << "乌龟赢了! "; break; default:cout << " 平局 ";}return 0;

}

Page 112: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 112

统计统计 nn 次龟兔赛跑的结果 次龟兔赛跑的结果 int main(){ Tortoise tort; Hare hare; Judger judger; int winner, num, win_cnt_hare = 0, win_cnt_tort = 0, tie_cnt = 0; cout << " 需要模拟多少次比赛? "; cin >> num; for (int i = 0; i < num; ++i) { // 模拟 num次比赛

judger.restart(); while (!judger.isFinish(winner)) // 模拟一场比赛

judger.update(hare.move(), tort.move());switch (winner) { // 记录比赛结果 case 0: ++win_cnt_hare; break; case 1: ++win_cnt_tort; break; default: ++tie_cnt;}

} cout << "乌龟赢 兔子赢 平局 \n"; cout << win_cnt_tort << '\t' << win_cnt_hare << '\t' << tie_cnt << endl; return 0;}

Page 113: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 113

问题问题 如果要统计若干个兔子的若干场比赛的结果该怎么办?

Page 114: 第 12 章 组合与继承

《程序设计 》 cs.sjtu 2011.9

程序设计 - 114

小结 小结 面向对象程序设计的一个重要的目标是代码重用。 代码重用的两种方法:组合和继承。

组合是将某一个已定义类的对象作为当前类的数据成员,则对此数据成员操作的代码得到了重用。继承是在已有类(基类)的基础上加以扩展,形成一个新类,称为派生类。在派生类定义时,只需要实现扩展功能,而基类有的功能得到了重用。