第12章 组合与继承 欢迎辞 第14次见面!.

Slides:



Advertisements
Similar presentations
移动应用软件开发技术 第二讲:C++编程基础
Advertisements

课程 要求 参考 书目 课程 内容 课程 练习.
面向对象的C++程序设计基础 第 4 章 继承与派生.
第四章 继承与派生 Chapter 4 Inheritance and Derivation
第九讲 类与对象 (I)面向对象基础.
第10章 虚函数与多态性.
第6章 多态性与虚函数.
7.2 访问控制 —— 公有继承 公有继承练习 //Point.h #ifndef _POINT_H #define _POINT_H class Point { //基类Point类的定义 public: //公有函数成员 void initPoint(float x = 0, float.
内容提要 代码重用 类的继承 多态 抽象类 多重继承 虚拟继承. 常宝宝 北京大学计算机科学与技术系
C++面向对象程序设计 第八章 继承和派生.
面向对象程序设计 第三章 C++面向对象程序设计 武汉大学 赵小红.
第四章 继承和派生类 汽车 专用汽车 运输汽车 货车 客车 消防车 洒水车 最普遍、最一般,可以自行驱动 含有汽车的特性,同时与汽车有不同
第14章 c++中的代码重用.
C++语言程序设计 第七章 继承与派生 清华大学 郑 莉.
C++ 面对对象程序设计 授课老师:.
第11章 类的继承和派生 继承是面向对象程序设计方法的四个基本特征之一,是程序代码可重用性的具体体现。
第6章 多态性与虚函数.
Using C++ The Weird Way Something about c++11 & OOP tricks
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
第11章 运算符重载 什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例.
第3章 继承和派生.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Object-Oriented Programming in C++ 第一章 C++的初步知识
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
辅导课程六.
第9章 类和对象(一) 9.1 面向对象的基本概念 9.2 类与对象的声明和定义 9.3 成员函数 9.4 对象的访问 9.5 对象的存储.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
面向对象程序设计 QQ群: Object-Oriented Programming 汽车学院.
第八章 继承与派生 丘志杰 电子科技大学 计算机学院 软件学院.
第十一章 继承和派生. 主讲教师:全红艳 第十一章 继承和派生.
用event class 从input的root文件中,由DmpDataBuffer::ReadObject读取数据的问题
第七章 操作符重载 胡昊 南京大学计算机系软件所.
第16章 虛擬與多形 16-1 虛擬函數 16-2 純虛擬函數與抽象類別 16-3 多形 16-4 虛擬繼承與虛擬解構子.
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
C++大学基础教程 第9章 继承与派生 北京科技大学.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
第11讲 类的继承 1. 类的继承的概念 2. 类的单继承机制 3. 单继承中的构造函数和析构函数.
第12讲 多继承与虚基类 多继承 虚基类.
C++复习3 ----类的继承与派生.
C#面向对象程序设计 $6 深入理解类.
第13讲 多态 友员函数 多态性与虚函数 纯虚函数和抽象类.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
补课.
C++程序设计— 多态与虚函数 主讲:资讯系张玉宏.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
本节内容 类成员的访问控制 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
面向对象技术 练习 ffh.
C++程序设计基础 主讲人:谢昕 华东交通大学信息工程学院 第十~十二讲 多态性和虚函数 2005年春季学期.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计教程 第8章 继承与派生 第8章 继承与派生 制作人:杨进才.
辅导课程十五.
#include <iostream.h>
第7章 模板 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
谭浩强编著 C++面向对象程序设计 授课教师:姬广永 学习网站:
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
《数据结构与算法设计》第一部分 面向对象的C++程序设计基础.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第十二讲 继承 与 派生.
C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
Presentation transcript:

第12章 组合与继承 欢迎辞 第14次见面!

学习新内容了,准备好了吗? 记住: 学习是快乐的~

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

组合 组合就是把用户定义类的对象作为新类的数据成员 组合表示一种聚集关系,是一种部分和整体(is a part of)的关系 必须用初始化列表去初始化对象成员

组合实例 定义一个复数类,而复数的虚部和实部都用有理数表示

类定义 class Complex{ friend Complex operator+(Complex x, Complex y); friend istream& operator>>(istream &is, Complex &obj); friend ostream& operator<<(ostream &os, const Complex &obj); Rational real; //实部 Rational imag; //虚部 public: Complex(int r1 = 0, int r2 = 1, int i1= 0, int i2 = 1): real(r1, r2), imag(i1, i2) {} };

成员函数的实现 Complex operator+(Complex x, Complex y) { Complex tmp; //利用Rational类的加法重载函数完成实部和虚部的相加 tmp.real = x.real + y.real; tmp.imag = x.imag + y.imag; return tmp; }

istream& operator>>(istream &is, Complex &obj) { cout << "请输入实部:"; is >> obj.real; //利用Rational类的输入重载实现实部的输入 cout << "请输入虚部:"; is >> obj.imag; //利用Rational类的输入重载实现虚部的输入 return is; } ostream& operator<<(ostream &os, const Complex &obj) { //利用Rational类的输出重载实现实部和虚部的输出 os << '(' << obj.real << " + " << obj.imag << "i" << ')'; return os;

复数类的使用 int main() {Complex x1,x2,x3; cout << "请输入x1:\n"; cin >> x1; cout << "请输入x2: \n"; cin >> x2; x3 = x1 + x2; cout << x1 << " + " << x2 << " =   " << x3 << endl; return 0; }

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

派生类的概念 继承是面向对象程序设计的一个重要特征,它允许在已有类的基础上创建新的类 基类、父类 派生类、导出类或子类 继承可以让程序员在已有类的基础上通过增加或修改少量代码的方法得到新的类,从而较好地解决代码重用的问题。

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

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

派生实例 class base { int x; Derived1有两个数据成员:x,y。 public: void setx(int k); } class derived1:public base { int y; void sety(int k); Derived1有两个数据成员:x,y。 有两个成员函数:setx和sety

派生类对基类成员的访问 派生类的成员函数不能访问基类的私有数据成员 protected访问特性 protected成员破坏了类的封装,基类的protected成员改变时,所有派生类都要修改

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

派生类对基类成员的访问性 基类成员的 访问说明符 继承类型 访问说明符                             继承类型                                    public继承            protected继承                private继承 public          在派生类中为public    在派生类中为protected        在派生类中为private                     可以由任何非static     可以直接由任何非static       可以直接由任何非static                     成员函数、友元函数和   成员函数、友元函数     成员函数、友元函数                     非成员函数访问 访问 访问 protecetd      在派生类中为proteced  在派生类中为protected        在派生类中private                     可以直接由任何非static 成员函数、友元函数访问 private         在派生类中隐藏         在派生类中隐藏                在派生类中隐藏                    可以通过基类的public 或protected成员函数或非static成员函数和友元函数访问                                  

class base { int x; public: void setx(int k); } class derived1:public base { int y; void sety(int k);   Derived1 不可访问 Int x private Int y public Setx() Sety()

我的派生类 #include <iostream> using namespace std; class yu{ protected: int age; int eng; public: yu(int a,int b){age=a;eng=b;} void show(){cout<<age<<" "<<eng<<endl;} };

class yu1:public yu{ int hige; public: yu1(int a,int b,int c):yu(a,b) { hige=c; } void show_all(){ //cout<<age<<" "<<eng<<" "; cout<<hige<<endl; };

主函数 int main() { yu1 qqq(1,2,3); qqq.show_all(); yu aaa(1,2); aaa.show(); return 0; }

继承实例 定义一个二维平面上的点类型,可以设置点的位置,获取点的位置。在此基础上,扩展出一个三维空间上的点类型。

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; 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)

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

派生类的构造函数和析构函数 由于派生类继承了其基类的成员,所以在建立派生类的实例对象时,必须初始化基类继承的数据成员。派生类对象析构时,也必须析构基类对象。 派生类不继承基类的构造函数、析构函数和赋值运算符,但是派生类的构造函数、析构函数和赋值运算符能调用基类的构造函数、析构函数和赋值运算符。

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

构造函数的格式 派生类构造函数的格式: 基类构造函数中的参数表通常来源于派生类构造函数的参数表,也可以用常数值。 派生类构造函数名(参数表): 基类构造函数名(参数表) { 。。。} 基类构造函数中的参数表通常来源于派生类构造函数的参数表,也可以用常数值。 如果构造派生类对象时调用的是基类的缺省构造函数,则可以不要初始化列表。 如果派生类新增的数据成员中含有对象成员,则在创建对象时,先执行基类的构造函数,再执行成员对象的构造函数,最后执行自己的构造函数体。

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

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

point2.h // Definition of class Point #ifndef POINT2_H #define POINT2_H class Point { public:    Point( int = 0, int = 0 );  // default constructor    ~Point();   // destructor protected:     // 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 validate cout << "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(); //显示已借书信息 };

Bool readerTeacher::bookBorrow(int bookNo) { if (borrowed==MAX) return false; else record[borrowed++]=bookNo; return true; } Bool readerTeacher::bookReturn(int bookNo) {int I; for(i=0;i<borrowed;++i) if (record[i]==bookNo) break; If (i==borrowed) return false; //无该书号 While(++I <borrowed) record[i-1]=record[i]; --borrowed; Return true; }

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

重定义基类的函数 派生类是基类的扩展,可以是保存的数据内容的扩展,也可以是功能的扩展。 当派生类对基类的某个功能进行扩展时,他定义的成员函数名可能会和基类的成员函数名重复。 如果只是函数名相同,而原型不同时,系统认为派生类中有两个重载函数。如果原型完全相同,则派生类的函数会覆盖基类的函数。这称为重定义基类的成员函数。

实例 定义一个圆类型,用于保存圆以及输出圆的面积和周长。在此类型的基础上派生出一个球类型,可以计算球的表面积和体积。

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

圆类的定义 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类继承来的

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

实例 在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; derive1(int xx, int yy): base(xx) {y = yy; cout<<"constructing derive1\n";} ~derive1() {cout<<"destructing derive1\n";}

class derive2:public derive1{ int z; 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 derive2 destructing derive2 destructing derive1 destructint base

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

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

将派生类对象赋给基类对象 派生类中的基类部分赋给此基类对象,派生类新增加的成员就舍弃了。赋值后,基类对象和派生类对象再无任何关系。 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; //输出1 bb.a = 3; cout << d.a; //输出1

基类指针指向派生类对象 尽管该指针指向的对象是一个派生类对象,但由于它本身是一个基类的指针,它只能解释基类的成员,而不能解释派生类新增的成员。因此,只能访问派生类中的基类部分。 通过指针修改基类对象时,派生类对象也被修改。 d1 d; d.a = 1; d.b = 2; base *bp = &d; cout << bp->a; //输出1 Bp->a = 3; cout << d.a; //输出3

基类的对象引用派生类的对象 给派生类中的基类部分取个别名。 基类对象改变时,派生类对象也被修改。 d1 d; d.a = 1; d.b = 2; base &bb = d; cout << bb.a; //输出1 bb.a = 3; cout << d.a; //输出3

实例 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;}

将派生类对象赋给基类对象 int i; Point aPoint; Circle aCircle; Cylinder aCylinder; Shape shapes[3]= {aPoint, aCircle, aCylinder}; for (i=0;i<3;i++) shapes[i].printShapeName(); Shape

基类指针指向派生类对象 Shape int i; Point aPoint; Circle aCircle; Cylinder aCylinder; Shape *pShape[3]= { &aPoint, &aCircle, &aCylinder }; for (i=0;i<3;i++) pShape[i]->printShapeName();

基类的对象引用派生类的对象 int i; Point aPoint; Circle aCircle; Cylinder aCylinder; Shape &shape1= aPoint; shape1.printShapeName(); shape

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

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

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

多态性 多态性:不同对象收到相同的消息时产生不同的动作。 多态性的作用:便于系统功能的扩展

多态性的实现 静态联编:编译时已决定用哪一个函数实现某一动作。 动态联编:直到运行时才决定用哪一个函数来实现动作

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

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

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

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

虚函数的使用 虚函数是与基类指针指向派生类对象,或基类对象引用派生类对象结合起来实现多态性。 当基类指针指向派生类对象或基类对象引用派生类对象时,对基类指针或对象调用基类的虚函数,系统会到相应的派生类中寻找此虚函数的重定义。如找到,则执行派生类中的函数。如没有找到,则执行基类的虚函数。

虚函数 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;}

将派生类对象赋给基类对象 int i; Point aPoint; Circle aCircle; Cylinder aCylinder; Shape shapes[3]= {aPoint, aCircle, aCylinder}; for (i=0;i<3;i++) shapes[i].printShapeName(); Shape

基类指针指向派生类对象 int i; Point aPoint; Circle aCircle; Cylinder aCylinder; Shape *pShape[3]= { &aPoint, &aCircle, &aCylinder }; for (i=0;i<3;i++) pShape[i]->printShapeName(); Point Circle Cylinder

基类的对象引用派生类的对象 Point 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

使用虚函数的注意事项 在派生类中重新定义虚函数时,它的原型必须与基类中的虚函数完全相同。否则编译器会把它认为是重载函数,而不是虚函数的重定义。 派生类在对基类的虚函数重定义时,关键字virtual可以写也可以不写。不管virtual写或者不写,该函数都被认为是虚函数。但最好是在重定义时写上virtual。

例 正方形是一类特殊的矩形,因此,可以从rectangle类派生一个square类。在这两个类中,都有一个显示形状的函数。

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 { square(int ss): rectangle(ss, ss) {} void display() //虚函数 {cout << “this is a square\n”;}

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

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

解决方案 将基类的析构函数定义为虚函数。 当析构基类指向的派生类的对象时,找到基类的析构函数。由于基类的析构函数是虚函数,又会找到派生类的析构函数,执行派生类的析构函数。

虚析构函数的继承性 和其他的虚函数一样,析构函数的虚函数的性质将被继承。 如果继承层次树中的根类的析构函数是虚函数的话,所有派生类的析构函数都将是虚函数。

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

纯虚函数 纯虚函数:是一个在基类中说明的虚函数,它在该基类中没有定义,但要在它的派生类里定义自己的版本,或重新说明为纯虚函数 纯虚函数的一般形式 virtual 类型 函数名(参数表)=0

纯虚函数实例 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";} };

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

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

抽象类实例 下面程序用于计算各类形状的总面积 #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();}

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

多重继承 一个派生类有多个基类时称为多重继承 多继承时,派生类包含所有基类的成员 A B C D

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

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

多重继承的访问特性 类z有三个数据成员和六个成员函数 与单继承规则相同,例: #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有三个数据成员和六个成员函数

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

多重继承实例 #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; 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; 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();}

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

多重继承的主要问题 二义性 多个基类有相同的的成员名 有两个以上的基类有共同的基类

二义性 如果多个基类中有相同的成员名,则派生类在引用时就具有二义性。例: 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 () 有二义性。

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();}

二义性---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

二义性实例 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 B1,public B2 {}; void main() { D obj; //obj.fun1(); //不可以执行各有fun1()函数) obj.B1::fun1(); obj.B2::fun1(); //obj.fun(); //不可以执行 //obj.A::fun(); //不可以执行:二义性 obj.B1::fun(); //无二义性:可以执行 }

虚基类 用途:当一个派生类是从多个基类派生,而这些基类又有一个公共的基类。则在这个派生类中访问公共基类中的成员时会有二义性问题。如上例中的B, B1, B2和C,他们的派生关系是: B B1 B2 C 如果B只有一个拷贝的话,那么在C中对B成员的访问就不会有二义性。

虚基类的概念 使公共的基类只产生一个拷贝。 虚基类的定义用关键词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

虚基类的初始化 保证虚基类对象只被初始化一次。 虚基类的构造由最终的类的构造函数负责。 例如,在构造C的对象时,由C的构造函数负责调用B的构造函数,而不是由B1、B2来调用。 构造次序:先执行B的构造函数,再执行B1、B2的构造函数,最后执行C的构造函数。 析构次序与构造次序相反。

虚基类的初始化实例 #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;}

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

Welcome to HDOJ Thank You ~

课后一定要练习之!