nbhkdz.com冰点文库

C++清华大学出版社


C++语言程序设计

第八章 多态性
东华理工大学信息工程学院

1

C++语言程序设计

ECIT CoIE

本章主要内容
? ?

多态性 运算符重载

?
?

虚函数

纯虚函数

?
?

抽象类
深度探索
2

C++语言程序设计

ECIT CoIE

多态性的概念
多态性是面向对象程序设计的重要特 征之一。 ? 多态性是指发出同样的消息被不同类 型的对象接收时有可能导致完全不同 的行为。 ? 多态的实现:
?

– 函数重载 – 运算符重载 – 虚函数
3

C++语言程序设计

ECIT CoIE

问题举例——复数的运算
运 算 符 重 载
class Complex { //复数类声明 public: Complex(double r = 0.0,double i = 0.0) { real = r; imag=i; } void display() const; //显示复数的值 private: double real; double imag; };
4

C++语言程序设计

ECIT CoIE

问题举例——复数的运算
运 算 符 重 载
?

用“+”、“-”能够实现复数的加减运 算吗?

?

实现复数加减运算的方法
——重载“+”、“-”运算符

5

C++语言程序设计

ECIT CoIE

运算符重载的实质
运 算 符 重 载
运算符重载是对已有的运算符赋予多重含义 ? 必要性
?

– C++中预定义的运算符其运算对象只能是基本数 据类型,而不适用于用户自定义类型(如类)
?

实现机制
– 将指定的运算表达式转化为对运算符函数的调 用,运算对象转化为运算符函数的实参。 – 编译系统对重载运算符的选择,遵循函数重载 的选择原则。

6

C++语言程序设计

ECIT CoIE

规则和限制
运 算 符 重 载
可以重载C++中除下列运算符外的所 有运算符: . .* :: ?: ? 只能重载C++语言中已有的运算符, 不可臆造新的。 ? 不改变原运算符的优先级和结合性。 ? 不能改变操作数个数。 ? 经重载的运算符,其操作数中至少应 该有一个是自定义类型。
?
7

C++语言程序设计

ECIT CoIE

两种形式
运 算 符 重 载
? ?

重载为类的非静态成员函数 重载为非成员函数

8

C++语言程序设计

ECIT CoIE

运算符函数
运 算 符 重 载
声明形式 函数类型 operator 运算符(形参) { ...... } ? 重载为类成员函数时 参数个数=原操作数个数-1(后置++、--除外) ? 重载为非成员函数时 参数个数=原操作数个数, 且至少应该有一个自定义类型的形参。
?
9

C++语言程序设计

ECIT CoIE

运算符成员函数的设计
运 算 符 重 载
?

双目运算符 B
– 如果要重载 B 为类成员函数,使之能够实

现表达式 oprd1 B oprd2,其中 oprd1 为A
类对象,则 B 应被重载为 A 类的成员函数, 形参类型应该是 oprd2 所属的类型。 – 经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)
10

C++语言程序设计

ECIT CoIE

例 8-1
运 算 将“+”、“-”运算重载为复数类 符 的成员函数。 重 ? 规则: 载
– 实部和虚部分别相加减。
?

操作数:
– 两个操作数都是复数类的对象。

11

#include <iostream> using namespace std; class Complex { //复数类定义 public: //外部接口 Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { } //构造函数 Complex operator + (const Complex &c2) const; //运算符+重载成员函数 Complex operator - (const Complex &c2) const; //运算符-重载成员函数 void display() const; //输出复数 private: //私有数据成员 double real; //复数实部 double imag; //复数虚部 };
12

Complex Complex::operator + (const Complex &c2) const { //重载运算符函数实现 return Complex(real + c2.real, imag + c2.imag); //创建一个临时无名对象作为返回值 } Complex Complex::operator - (const Complex &c2) const { //重载运算符函数实现 return Complex(real - c2.real, imag c2.imag); //创建一个临时无名对象作为返回值 }

13

void Complex::display() const { cout << "(" << real << ", " << imag << ")" << endl; } int main() { //主函数 Complex c1(5, 4), c2(2, 10), c3; //定义复 数类的对象 cout << "c1 = "; c1.display(); cout << "c2 = "; c2.display(); c3 = c1 - c2; //使用重载运算符完成复数减法 cout << "c3 = c1 - c2 = "; c3.display(); c3 = c1 + c2; //使用重载运算符完成复数加法 cout << "c3 = c1 + c2 = "; c3.display(); return 0; }

14

程序输出的结果为: c1 c2 c3 c3 = = = = (5, 4) (2, 10) c1 - c2 = (3, -6) c1 + c2 = (7, 14)

15

C++语言程序设计

ECIT CoIE

运算符成员函数的设计
运 算 符 重 载
?

前置单目运算符 U
– 如果要重载 U 为类成员函数,使之能够 实现表达式 U oprd,其中 oprd 为A类对 象,则 U 应被重载为 A 类的成员函数, 无形参。

– 经重载后,
表达式 U oprd 相当于 oprd.operator U()
16

C++语言程序设计

ECIT CoIE

运算符成员函数的设计
运 算 符 重 载
?

后置单目运算符 ++和-– 如果要重载 ++或--为类成员函数,使之 能够实现表达式 oprd++ 或 oprd-- ,其 中 oprd 为A类对象,则 ++或-- 应被重 载为 A 类的成员函数,且具有一个 int 类型形参。 – 经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)
17

C++语言程序设计

ECIT CoIE

例8-2
运 算 符 重 载
运算符前置++和后置++重载为时钟类 的成员函数。 ? 前置单目运算符,重载函数没有形参, 对于后置单目运算符,重载函数需要 有一个整型形参。 ? 操作数是时钟类的对象。 ? 实现时间增加1秒钟。
?
18

#include <iostream> using namespace std; class Clock { //时钟类声明定义 public: //外部接口 Clock(int hour = 0, int minute = 0, int second = 0); void showTime() const; Clock& operator ++ (); //前置单目运算符 重载 Clock operator ++ (int);//后置单目运算符 重载 private: //私有数据成员 int hour, minute, second; };
19

//前置单目运算符重载函数 Clock & Clock::operator ++ () { second++; if (second >= 60) { second -= 60; minute++; if (minute >= 60) { minute -= 60; hour = (hour + 1) % 24; } } return *this; }
20

//后置单目运算符重载 Clock Clock::operator ++ (int) { //注意形参表中的整型参数 Clock old = *this; ++(*this); //调用前置“++”运算符 return old; }

21

//其它成员函数的实现略 int main() { Clock myClock(23, 59, 59); cout << "First time output: "; myClock.showTime(); cout << "Show myClock++: "; (myClock++).showTime(); cout << "Show ++myClock: "; (++myClock).showTime(); return 0; }
22

程序运行结果为:

First time output: 23:59:59 Show myClock++: 23:59:59 Show ++myClock: 0:0:1

23

C++语言程序设计

ECIT CoIE

运算符非成员函数的设计
运 算 符 重 载
函数的形参代表依自左至右次序排列 的各操作数。 ? 后置单目运算符 ++和--的重载函数, 形参列表中要增加一个int,但不必写 形参名。 ? 如果在运算符的重载函数中需要操作 某类对象的私有成员,可以将此函数 声明为该类的友元。
?
24

C++语言程序设计

ECIT CoIE

运算符非成员函数的设计
运 算 符 重 载
双目运算符 B重载后, 表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2 ) ? 前置单目运算符 B重载后, 表达式 B oprd 等同于operator B(oprd ) ? 后置单目运算符 ++和--重载后, 表达式 oprd B 等同于operator B(oprd,0 )
?
25

C++语言程序设计

ECIT CoIE

例8-3
运 算 符 重 载
? ?

将+、-(双目)重载为非成员函数,并将其声明为复 数类的友元,两个操作数都是复数类的常引用。 将<<(双目)重载为非成员函数,并将其声明为复数 类的友元,它的左操作数是std::ostream引用,右操作 数为复数类的常引用,返回std::ostream引用,用以支 持下面形式的输出: cout << a << b; 该输出调用的是: operator << (operator << (cout, a), b);

26

#include <iostream> using namespace std; class Complex { //复数类定义 public: //外部接口 Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { } //构造函数 friend Complex operator + (const Complex &c1, const Complex &c2); //运算符+重载 friend Complex operator - (const Complex &c1, const Complex &c2); //运算符-重载 friend ostream & operator << (ostream &out, const Complex &c); //运算符<<重载 private: //私有数据成员 double real; //复数实部 double imag; //复数虚部 };
27

Complex operator + (const Complex &c1, const Complex &c2) { return Complex(c1.real + c2.real, c1.imag + c2.imag); } Complex operator - (const Complex &c1, const Complex &c2) { return Complex(c1.real - c2.real, c1.imag - c2.imag); }

ostream & operator << (ostream &out, const Complex &c) { out << "(" << c.real << ", " << c.imag << ")"; return out; }

28

C++语言程序设计

ECIT CoIE

静态绑定与动态绑定
?

绑定
– 程序自身彼此关联的过程,确定程序中的 操作调用与执行该操作的代码间的关系。

?

静态绑定
– 绑定过程出现在编译阶段,用对象名或者 类名来限定要调用的函数。

?

动态绑定
– 绑定过程工作在程序运行时执行,在程序 运行时才确定将要调用的函数。
29

C++语言程序设计

ECIT CoIE

虚函数
?

虚 函 数

? ?

?
? ? ?

虚函数是动态绑定的基础。 是非静态的成员函数。 在类的声明中,在函数原型之前写virtual。 virtual 只用来说明类声明中的原型,不能用在 函数实现时。 具有继承性,基类中声明了虚函数,派生类中 无论是否说明,同原型函数都自动为虚函数。 本质:不是重载声明而是覆盖。 调用方式:通过基类指针或引用,执行时会 根据指针指向的对象的类,决定调用哪个函数。
34

C++语言程序设计

ECIT CoIE

例 8-4
#include <iostream> using namespace std;

虚 函 数

class Base1 { //基类Base1定义 public: virtual void display() const; //虚函数 }; void Base1::display() const { cout << "Base1::display()" << endl; } class Base2: public Base1 { // 公有派生类Base2定义 public: void display() const; //覆盖基类的虚函数 }; void Base2::display() const { cout << "Base2::display()" << endl; }
35

//公有派生类Derived定义 class Derived: public Base2 { public: void display() const; //覆盖基类的虚函数 }; void Derived::display() const { cout << "Derived::display()" << endl; } //参数为指向基类对象的指针 void fun(Base1 *ptr) { ptr->display(); //"对象指针->成员名" }
36

int main() { //主函数 Base1 base1; //定义Base1类对象 Base2 base2; //定义Base2类对象 Derived derived; //定义Derived类对象 fun(&base1); //用Base1对象的指针调用fun函数 fun(&base2); //用Base2对象的指针调用fun函数 fun(&derived); //用Derived对象的指针调用fun函数 return 0; 运行结果: }

Base1::display() Base2::display() Derived::display()
37

C++语言程序设计

ECIT CoIE

虚析构函数
虚 函 数
为什么需要虚析构函数? ? 可能通过基类指针删除派生类对象; ? 如果你打算允许其他人通过基类指针 调用对象的析构函数(通过delete这 样做是正常的),就需要让基类的析 构函数成为虚函数,否则执行delete 的结果是不确定的。

38

C++语言程序设计

ECIT CoIE

抽象类
纯 虚 带有纯虚函数的类称为抽象类: 函 class 类名 数 { 与 virtual 类型 函数名(参数表)=0; 抽 //纯虚函数 象 ... 类
}
39

C++语言程序设计

ECIT CoIE

抽象类
纯 虚 函 数 与 抽 象 类
?

作用
– 抽象类为抽象和设计的目的而声明,将有关的数 据和行为组织在一个继承层次结构中,保证派生 类具有要求的行为。 – 对于暂时无法实现的函数,可以声明为纯虚函数, 留给派生类去实现。

?

注意
– 抽象类只能作为基类来使用。

– 不能声明抽象类的对象。
– 构造函数不能是虚函数,析构函数可以是虚函数。
40

C++语言程序设计

ECIT CoIE

例 8-5
纯 虚 函 数 与 抽 象 类
#include <iostream> using namespace std; class Base1 { //基类Base1定义 public: virtual void display() const = 0; //纯虚函数 }; class Base2: public Base1 { //公有派生类Base2定义 public: void display() const { //覆盖基类的虚函数 cout << "Base2::display()" << endl; } }; class Derived: public Base2 { //公有派生类Derived定义 public: void display() const { //覆盖基类的虚函数 cout << "Derived::display()" << endl; } };
41

void fun(Base1 *ptr) { ptr->display(); //"对象指针->成员名" } int main() { //主函数 Base2 base2; //定义Base2类对象 Derived derived; //定义Derived类对象 fun(&base2); //用Base2对象的指针调用fun函数 fun(&derived);// 用 Derived 对象的指针调用 fun 函 数 return 0; 运行结果: Base2::display() }
Derived::display()
42

C++语言程序设计

ECIT CoIE

多态类型与非多态类型
深 度 探 索
?

多态类型与非多态类型
– 有虚函数的类类型称为多态类型 – 其它类型皆为非多态类型

?

二者的差异
– 语言层面的差异
? 多态类型支持运行时类型识别
? 多态类型对象占用额外的空间

– 设计原则上的差异
43

C++语言程序设计

ECIT CoIE

设计原则
?

多态类型
– 多态类型的析构函数一般应为虚函数

深 度 探 索

?

非多态类型
– 非多态类型不宜作为公共基类
由于没有利用动态多态性,一般可以用组合,而 无需用共有继承; ? 如果继承,则由于析构函数不是虚函数,删除对 象时所执行操作与指针类型有关,易引起混乱。
?

– 把不需被继承的类型设定为非多态类型
由于成员函数都是静态绑定,调用速度较快; ? 对象占用空间较小。
?

44

C++语言程序设计

ECIT CoIE

运行时类型识别
?

运行时类型识别
– 允许在运行时通过基类指针(或引用)辨别 对象所属的具体派生类; – 只对多态类型适用; – 比虚函数动态绑定的开销更大,因此应仅对 虚函数无法解决的问题使用。

深 度 探 索

?

运行时类型识别的方式
– 用dynamic_cast做类型转换的尝试; – 用typeid直接获取类型信息。
45

C++语言程序设计

ECIT CoIE

dynamic_cast的使用
?

语法形式
– dynamic_cast<目的类型>(表达式)

深 度 探 索

?

功能
– 将基类指针转换为派生类指针,将基类引用 转换为派生类引用; – 转换是有条件的
如果指针(或引用)所指对象的实际类型与转换 的目的类型兼容,则转换成功进行; ? 否则如执行的是指针类型的转换,则得到空指针; 如执行的是引用类型的转换,则抛出异常。
?
46

C++语言程序设计

ECIT CoIE

例8-9 dynamic_cast示例
深 度 探 索
#include <iostream> using namespace std; class Base { public: virtual void fun1() virtual ~Base() { } }; class Derived1: public public: virtual void fun1() virtual void fun2() }; class Derived2: public public: virtual void fun1() virtual void fun2() }; { cout << "Base::fun1()" << endl; }

Base { { cout << "Derived1::fun1()" << endl; } { cout << "Derived1::fun2()" << endl; } Derived1 { { cout << "Derived2::fun1()" << endl; } { cout << "Derived2::fun2()" << endl; }

47

void fun(Base *b) { b->fun1(); //尝试将b转换为Derived1指针 Derived1 *d = dynamic_cast<Derived1 *>(b); //判断转换是否成功 if (d != 0) d->fun2(); } int main() { Base b; 运行结果: fun(&b); Base::fun1() Derived1 d1; fun(&d1); Derived1::fun1() Derived2 d2; Derived1::fun2() fun(&d2); Derived2::fun1() return 0; } Derived2::fun2()
48

C++语言程序设计

ECIT CoIE

typeid的使用
?

语法形式
– typeid ( 表达式 ) – typeid ( 类型说明符 )

深 度 探 索

?

功能
– 获得表达式或类型说明符的类型信息
? ?

表达式有多态类型时,会被求值,并得到动态类型信息; 否则,表达式不被求值,只能得到静态的类型信息。

– 类型信息用type_info对象表示
?
? ? ?

type_info是typeinfo头文件中声明的类; typeid的结果是type_info类型的常引用; 可以用type_info的重载的“==”、“!=”操作符比较两 类型的异同; type_info的name成员函数返回类型名称,类型为const char *。
49

C++语言程序设计

ECIT CoIE

例8-10 typeid示例
深 度 探 索
#include <iostream> #include <typeinfo> using namespace std; class Base { public: virtual ~Base() { } }; class Derived: public Base { };

50

void fun(Base *b) { //得到表示b和*b类型信息的对象 const type_info &info1 = typeid(b); const type_info &info2 = typeid(*b); cout << "typeid(b): " << info1.name() << endl; cout << "typeid(*b): " << info2.name() << endl; if (info2 == typeid(Base)) //判断*b是否为Base类型 cout << "A base class!" << endl; } int main() { Base b; fun(&b); Derived d; fun(&d); return 0; }

运行结果:
typeid(b): class Base * typeid(*b): class Base A base class! typeid(b): class Base * typeid(*b): class Derived
51

C++语言程序设计

ECIT CoIE

虚函数动态绑定的实现原理
?

动态选择被执行的函数
– 函数的调用,需要通过函数代码的入口地址 – 把函数入口地址作为变量,在不同情况下赋予不同的值, 通过该变量调用函数,就可动态选择被执行的函数
?

深 度 探 索

回顾:第6章介绍的函数指针、指向成员函数的指针

?

虚表
– 每个多态类有一个虚表(virtual table) – 虚表中有当前类的各个虚函数的入口地址 – 每个对象有一个指向当前类的虚表的指针(虚指针vptr)

?

动态绑定的实现
– 构造函数中为对象的虚指针赋值 – 通过多态类型的指针或引用调用成员函数时,通过虚指 针找到虚表,进而找到所调用的虚函数的入口地址 – 通过该入口地址调用虚函数
52

class Base { public: virtual void f(); virtual void g(); private: int i; };

class Derived: public Base { public: virtual void f(); //覆盖Base::f virtual void h(); //新增的虚函数 private: int j; (Base::f的代码) };
指向f()的指针 指向g()的指针 Base的虚表 push %ebp mov %esp,%ebp …… (Base::g的代码) push %ebp mov %esp,%ebp …… (Derived::f的代码) push %ebp mov %esp,%ebp …… (Derived::h的代码) push %ebp mov %esp,%ebp ……

深 度 探 索

vptr
i

Base类型对象

vptr

指向f()的指针
指向g()的指针 指向h()的指针 Derived的虚表

i
j

Derived类型对象

53

C++语言程序设计

ECIT CoIE

小结与复习建议
?

主要内容
– 多态性的概念、运算符重载、虚函数、 纯虚函数、抽象类

?

达到的目标
– 理解多态的概念,学会运用多态机制。

?

实验任务
– 实验八

54


C++复习题__清华大学出版社

一二章 A)# 1.下列 C++标点符号中表示行注释开始的是 C B); C)// D)} A 。。 。 A)return 语句中的表达式类型所决定的 调用函数类型所决定的 C)...

C++复习题及答案__清华大学出版社

C++复习题及答案__清华大学出版社 隐藏>> 第一.二章 1.下列 C++标点符号中表示行注释开始的是 ( C )。 A)# B); C)// D)} 2.下列字符串中可以用作...

VisualC++面向对象编程教程第2版(王育坚)清华大学出版社课后答案

VisualC++面向对象编程教程第2版(王育坚)清华大学出版社课后答案_理学_高等教育_教育专区。王育坚Vc++第二版课后答案2-45 编写一个程序,输入三角形的三条边的边长...

清华大学出版C++教程书上经典例题

游泳池改造预算,circle 类 #include<iostream> using namespace std; const float pi=(float)3.1415926; const float fenceprice=35.; const float concreteprice...

Visual C++面向对象编程教程 第2版 (王育坚) 清华大学出版社 课后答案

Visual C++面向对象编程教程 第2版 (王育坚) 清华大学出版社 课后答案_理学_高等教育_教育专区。Visual C++面向对象编程教程 第2版 (王育坚) 清华大学出版社 课后...

C++程序语言设计(清华大学出版社)复习要点

C++程序语言设计(清华大学出版社)复习要点 隐藏>> 二、填空题 20 1. 下列程序实现了输入两个整数,比较两个数的大小的功能,请在空格内填写相应的语句 使程序正确...

从入门到精通C++需要学的10本书

C++程序设计教程》 (钱能-清华版) 【作者】钱能董灵平张敏霞 【丛书名】 C++程序设计系列教材 【出版社】清华大学出版社 国人所著, 国人水平实在有限, 深度...

清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案6章白永利)

清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案第6章清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案第6章隐藏>> 例6.1 通过指针变量访问整型变量...

Visual_C++面向对象程序设计教程与实验(第二版)清华大学出版社1—8章答案

Visual_C++面向对象程序设计教程与实验(第二版)清华大学出版社1—8章答案_理学_高等教育_教育专区。第一章 1. 什么是面向对象程序设计?它与传统的结构化程序设计...

清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案9章(白永利)

清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案第9章清华大学出版社出版 谭浩强主编 C++程序设计课后相接答案第9章隐藏>> 例9.1 在例 8.3 基础上定义构造...