1.14k likes | 1.33k Views
第 12 章 组合与继承. 组合 继承 虚函数与多态性 纯虚函数与抽象类 多继承 面向对象设计范例. 派生类的概念. 继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类 基类、父类 派生类、导出类或子类 继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。. 派生类. 单继承的格式 基类成员在派生类中的访问特性 派生类对象的构造、析构与赋值操作 重定义基类的函数 派生类作为基类 将派生类对象隐式转换为基类对象. 派生类的定义. 一般格式:
E N D
第12章 组合与继承 • 组合 • 继承 • 虚函数与多态性 • 纯虚函数与抽象类 • 多继承 • 面向对象设计范例
派生类的概念 • 继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类 • 基类、父类 • 派生类、导出类或子类 • 继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
派生类的定义 • 一般格式: class 派生类名:派生方法 基类名 {//派生类新增的数据成员和成员函数 }; • 派生方法: • 公有派生: public • 私有派生:private • 保护派生:protected
派生实例 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
派生类对基类成员的访问 • 派生类的成员函数不能访问基类的私有数据成员 • protected访问特性 • protected成员是一类特殊的私有成员,它不可以被全局函数或其他类的成员函数访问,但能被派生类的成员函数访问 • protected成员破坏了类的封装,基类的protected成员改变时,所有派生类都要修改
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
class base { int x; public: void setx(int k); } class derived1:public base { int y; public: void sety(int k); }
继承实例 • 定义一个二维平面上的点类型,可以设置点的位置,获取点的位置。在此基础上,扩展出一个三维空间上的点类型。
point_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;} };
point_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;} }
point_3d的讨论 • point_3d的组成:有三个数据成员:x,y和z,有五个公有的成员函数:setpoint2, setpoint3, getx, gety和getz。 • point_3d的成员函数无法直接访问基类的x和y,因此在setpoint3函数中必须调用在point_2d的公有成员函数setpoint2实现。 • point_3d类的使用和普通类完全一样,用户不用去管这个类是用继承方式从另外一个类扩展而来,还是完全直接定义的。
Point_3d的使用 P1: (1, 2) P2: (1, 2, 3) 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; }
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
派生类的构造函数和析构函数 • 由于派生类继承了其基类的成员,所以在建立派生类的实例对象时,必须初始化基类继承的数据成员。派生类对象析构时,也必须析构基类对象。 • 派生类不继承基类的构造函数、析构函数和赋值运算符,但是派生类的构造函数、析构函数和赋值运算符能调用基类的构造函数、析构函数和赋值运算符。
派生类的构造函数 • 基类成员的初始化由基类的构造函数完成。派生类的构造函数调用基类的构造函数完成基类成员的初始化。 • 派生类构造函数可以隐式调用基类缺省的构造函数,也可以在派生类的构造函数的初始化列表显式地调用基类的构造函数。
构造函数的格式 • 派生类构造函数的格式: 派生类构造函数名(参数表): 基类构造函数名(参数表) { 。。。} • 基类构造函数中的参数表通常来源于派生类构造函数的参数表,也可以用常数值。 • 如果构造派生类对象时调用的是基类的缺省构造函数,则可以不要初始化列表。 • 如果派生类新增的数据成员中含有对象成员,则在创建对象时,先执行基类的构造函数,再执行成员对象的构造函数,最后执行自己的构造函数体。
派生类对象的析构 • 派生类的析构函数值析构自己新增的数据成员,基类成员的析构由基类的析构函数析构 • 派生类析构函数会自动调用基类的析构函数 • 派生类对象析构时,先执行派生类的析构函数,再执行基类的析构函数
派生类构造实例 • 定义一个二维平面上的点类,并从它派生出一个圆类
point2.h // Definition of class Point #ifndef POINT2_H #define POINT2_H class 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
point2.cpp #include "point2.h" // Constructor for class Point Point::Point( int a, int b ) { x = a; y = b; cout << "Point constructor:" << '[' << x << ", "<< y << ']' << endl; } // Destructor for class Point Point::~Point() { cout << "Point destructor: " << '[' << x << ", "<< y << ']' << endl; }
circle2.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
circle2.cpp #include "circle2.h" // Constructor for Circle calls constructor for Point Circle::Circle( double r, int a, int b ) : Point( a, b ) // call base-class Constructor { radius = r; // should validatecout << "Circle constructor: radius is" << radius << "[" << x << ", "<< y << ']' << endl; } // Destructor roi class Circle Circle::~Circle() { cout << "Circle destructor: radius is " << radius << " [ " << x << ", "<< y << ']' << endl; }
Circle类的应用 #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 ]
派生类构造函数的构造规则 • 若基类使用缺省或不带参数的构造函数,则在派生类定义构造函数是可略去:基类构造函数名(参数表)。此时若派生类也不需要构造函数,则可不定义构造函数。 • 当基类构造函数需要参数,而派生类本身并不需要构造函数时,派生类还必须定义构造函数。该函数只是起了一个参数传递作用。 • 如果省略了派生类的构造函数,那么就由派生类的默认构造函数调用基类的默认构造函数。
派生类实例 • 定义一个图书馆系统中的读者类,每个读者的信息包括:卡号、姓名、单位、允许借书的数量以及已借书记录。学生最多允许借5本书,教师最多允许借10本书。
设计过程 • 系统中有两类读者:学生读者和教师读者。 • 这两类读者有一部分内容是相同的:卡号、姓名和单位。 • 可将两类读者的共同部分内容设计成一个基类。 • 学生读者和教师读者从基类派生,增加已借书的数量以及已借书记录两个数据成员,并将允许借书的数量定义为整个类共享的常量
基类的设计 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); } };
教师读者类的设计 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(); //显示已借书信息 };
学生读者类的设计 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(); //显示已借书信息 };
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
重定义基类的函数 • 派生类是基类的扩展,可以是保存的数据内容的扩展,也可以是功能的扩展。 • 当派生类对基类的某个功能进行扩展时,他定义的成员函数名可能会和基类的成员函数名重复。 • 如果只是函数名相同,而原型不同时,系统认为派生类中有两个重载函数。如果原型完全相同,则派生类的函数会覆盖基类的函数。这称为重定义基类的成员函数。
实例 • 定义一个圆类型,用于保存圆以及输出圆的面积和周长。在此类型的基础上派生出一个球类型,可以计算球的表面积和体积。
圆类的设计 • 数据成员:圆的半径 • 成员函数:由于需要提供圆的面积和周长,需要提供两个公有的成员函数。除了这些之外,还需要一个构造函数
圆类的定义 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;} };
球类的定义 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; } };
Ball类的构造函数 • Ball类没有新增加数据成员,因而不需要构造函数。但基类的构造函数需要参数,所以ball类必须写构造函数 • Ball类构造函数的作用是为circle类的构造函数传递参数
Ball类的area函数 • Ball类包含了两个原型完全一样的area函数。一个是自己定义的,一个是从circle类继承来的 • 当对ball类的对象调用area函数时,调用的是ball类自己定义的area函数
派生类引用基类的同名函数 • 派生类中重新定义基类的成员函数时,它的功能往往是基类功能的扩展。为完成扩展的工作,派生类版本通常要调用基类中的该函数版本。 • 引用基类的同名函数必须使用作用域运算符,否则会由于派生类成员函数实际上调用了自身而引起无穷递归。这样会使系统用光内存,是致命的运行时错误。
实例 • 在circle类的基础上定义一个cylinder类。可以计算圆柱体的表面积和体积 • 设计考虑: • 存储圆柱体可以在圆的基础上增加一个高度。 • 圆柱体的表面积是上下两个圆的面积加上它的侧面积 • 圆柱体的体积是底面积乘上高度。而求圆的面积的函数在circle类中已经存在。
Cylinder类的定义 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 ; } };
Ball和cylinder类的使用 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; }
执行结果 circle: r=3 area=28.26 circum=18.84 ball: r=2 area=50.24 volumn=33.4933 cylinder: r=1 area=18.84 volumn=6.28
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
派生类作为基类 • 基类本身可以是一个派生类,如: class base { … } class d1:public base {…} class d2:public d1 {…} • 每个派生类继承他的直接基类的所有成员。 • 如果派生类的基类是一个派生类,则每个派生类只负责他的直接基类的构造,依次上溯。 • 当构造d2类的对象时,会先调用d1的构造函数,而d1的构造函数执行时又会先调用base的构造函数。因此,构造d2类的对象时,最先初始化的是base的数据成员,再初始化d1新增的成员,最后初始化d2新增的成员。 • 析构的过程正好相反。
实例 #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";} };
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 base constructing derive1 constructing derive2destructing derive2 destructing derive1 destructint base
派生类 • 单继承的格式 • 基类成员在派生类中的访问特性 • 派生类对象的构造、析构与赋值操作 • 重定义基类的函数 • 派生类作为基类 • 将派生类对象隐式转换为基类对象
将派生类对象隐式转换为基类对象 • 将派生类对象赋给基类对象 • 基类指针指向派生类对象 • 基类的对象引用派生类的对象