1
第八章 多态性
清华大学计算机与信息管理中心
郑 莉
C++语言程序设计
前一页 休息 2
本章主要内容
? 多态性
? 运算符重载
? 虚函数
? 纯虚函数
? 抽象类
前一页 休息 3
多态性的概念
? 多态性是面向对象程序设计的重要特
征之一。
? 多态性是指发出同样的消息被不同类
型的对象接收时导致完全不同的行为。
? 多态的实现:
– 函数重载
– 运算符重载
– 虚函数
前一页 休息 4
问题举例 —— 复数的运算
class complex //复数类声明
{
public:
complex(double r=0.0,double i=0.0){real=r;imag=i;}
//构造函数
void display(); //显示复数的值
private:
double real;
double imag;
};





前一页 休息 5
问题举例 —— 复数的运算
? 用,+”、,-”能够实现复数的加减运
算吗?
? 实现复数加减运算的方法
—— 重载, +”、,-”运算符





前一页 休息 6
运算符重载的实质
? 运算符重载是对已有的运算符赋予多重含义
? 必要性
– C++中预定义的运算符其运算对象只能是基本数
据类型,而不适用于用户自定义类型(如类)
? 实现机制
– 将指定的运算表达式转化为对运算符函数的调
用,运算对象转化为运算符函数的实参。
– 编译系统对重载运算符的选择,遵循函数重载
的选择原则。





前一页 休息 7





规则和限制
? 可以重载 C++中除下列运算符外的所
有运算符:
.,*,,?:
? 只能重载 C++语言中已有的运算符,
不可臆造新的。
? 不改变原运算符的优先级和结合性。
? 不能改变操作数个数。
? 经重载的运算符,其操作数中至少应
该有一个是自定义类型。
前一页 休息 8
两种形式
? 重载为类成员函数。
? 重载为友元函数。





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





前一页 休息 10
运算符成员函数的设计
? 双目运算符 B
– 如果要重载 B 为类成员函数,使之能够实
现表达式 oprd1 B oprd2,其中 oprd1 为 A
类对象,则 B 应被重载为 A 类的成员函数,
形参类型应该是 oprd2 所属的类型。
– 经重载后,表达式 oprd1 B oprd2 相当于
oprd1.operator B(oprd2)





前一页 休息 11





例 8.1
将, +”,,-” 运算重载为复数类
的成员函数。
? 规则,
– 实部和虚部分别相加减。
? 操作数,
– 两个操作数都是复数类的对象。
#include<iostream.h>
class complex //复数类声明
{
public,//外部接口
complex(double r=0.0,double i=0.0){real=r;imag=i;}
//构造函数
complex operator + (complex c2); //+重载为成员函数
complex operator - (complex c2); //-重载为成员函数
void display(); //输出复数
private,//私有数据成员
double real; //复数实部
double imag; //复数虚部
};
complex complex::
operator +(complex c2) //重载函数实现
{
complex c;
c.real=c2.real+real;
c.imag=c2.imag+imag;
return complex(c.real,c.imag);
}
complex complex::
operator -(complex c2) //重载函数实现
{
complex c;
c.real=real-c2.real;
c.imag=imag-c2.imag;
return complex(c.real,c.imag);
}
void complex::display()
{ cout<<"("<<real<<","<<imag<<")"<<endl; }
void 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();
}
程序输出的结果为:
c1=(5,4)
c2=(2,10)
c3=c1-c2=(3,-6)
c3=c1+c2=(7,14)
前一页 休息 17
运算符成员函数的设计
? 前置单目运算符 U
– 如果要重载 U 为类成员函数,使之能够
实现表达式 U oprd,其中 oprd 为 A类对
象,则 U 应被重载为 A 类的成员函数,
无形参。
– 经重载后,
表达式 U oprd 相当于 oprd.operator U()





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





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





#include<iostream.h>
class Clock //时钟类声明
{
public,//外部接口
Clock(int NewH=0,int NewM=0,int NewS=0);
void ShowTime();
void operator ++(); //前置单目运算符重载
void operator ++(int); //后置单目运算符重载
private,//私有数据成员
int Hour,Minute,Second;
};
void Clock::operator ++() //前置单目运算符重载
{ Second++;
if(Second>=60)
{ Second=Second-60;
Minute++;
if(Minute>=60)
{ Minute=Minute-60;
Hour++;
Hour=Hour%24;
}
}
cout<<"++Clock,";
}
void Clock::operator ++(int) //后置单目运算符重载
{ Second++;
if(Second>=60)
{ Second=Second-60;
Minute++;
if(Minute>=60)
{ Minute=Minute-60;
Hour++;
Hour=Hour%24;
}
}
cout<<"Clock++,";
}
//其它成员函数的实现略
void main()
{
Clock myClock(23,59,59);
cout<<"First time output:";
myClock.ShowTime();
myClock++;
myClock.ShowTime();
++myClock;
myClock.ShowTime();
}
程序运行结果为:
First time output:23:59:59
Clock++,0:0:0
++Clock,0:0:1
前一页 休息 25
运算符友元函数的设计
? 如果需要重载一个运算符,使之能够
用于操作某类对象的私有成员,可以
此将运算符重载为该类的友元函数。
? 函数的形参代表依自左至右次序排列
的各操作数。
? 后置单目运算符 ++和 --的重载函数,
形参列表中要增加一个 int,但不必写
形参名。





前一页 休息 26
运算符友元函数的设计
? 双目运算符 B重载后,
表达式 oprd1 B oprd2
等同于 operator B(oprd1,oprd2 )
? 前置单目运算符 B重载后,
表达式 B oprd
等同于 operator B(oprd )
? 后置单目运算符 ++和 --重载后,
表达式 oprd B
等同于 operator B(oprd,0 )





前一页 休息 27
例 8-3
? 将 +,-(双目)重载为复数类的友元
函数。
? 两个操作数都是复数类的对象。





#include<iostream.h>
class complex //复数类声明
{
public,//外部接口
complex(double r=0.0,double i=0.0)
{ real=r; imag=i; } //构造函数
friend complex operator + (complex c1,complex c2);
//运算符 +重载为友元函数
friend complex operator - (complex c1,complex c2);
//运算符 -重载为友元函数
void display(); //显示复数的值
private,//私有数据成员
double real;
double imag;
};
complex operator +(complex c1,complex c2)
//运算符重载友元函数实现
{ return
complex(c2.real+c1.real,c2.imag+c1.imag);
}
complex operator -(complex c1,complex c2)
//运算符重载友元函数实现
{ return
complex(c1.real-c2.real,c1.imag-c2.imag);
}
// 其它函数和主函数同例 8.1
前一页 休息 30
静态联编与动态联编
? 联编:
– 程序自身彼此关联的过程,确定程序中的
操作调用与执行该操作的代码间的关系。
? 静态联编(静态束定)
– 联编工作出现在编译阶段,用对象名或者
类名来限定要调用的函数。
? 动态联编
– 联编工作在程序运行时执行,在程序运行
时才确定将要调用的函数。


前一页 休息 35
虚函数
? 虚函数是动态联编的基础。
? 是非静态的成员函数。
? 在类的声明中,在函数原型之前写 virtual。
? virtual 只用来说明类声明中的原型,不能用在
函数实现时。
? 具有继承性,基类中声明了虚函数,派生类中
无论是否说明,同原型函数都自动为虚函数。
? 本质:不是重载声明而是覆盖。
? 调用方式:通过基类指针或引用,执行时会
根据 指针指向的对象的类,决定调用哪个函数。
前一页 休息 36
例 8.4
#include <iostream.h>
class B0 //基类 B0声明
{
public,//外部接口
virtual void display()
{cout<<"B0::display()"<<endl;}
//虚成员函数
};
class B1,public B0 //公有派生
{ public:
void display()
{ cout<<"B1::display()"<<endl; }
};
class D1,public B1 //公有派生
{ public:
void display()
{ cout<<"D1::display()"<<endl; }
};
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
void main() //主函数
{ B0 b0,*p; //声明基类对象和指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b0;
fun(p); //调用基类 B0函数成员
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
程序的运行结果为:
B0::display()
B1::display()
D1::display()
前一页 休息 40
抽象类的一般形式
带有纯虚函数的类称为抽象类,
class 类名
{
virtual 类型 函数名 (参数表 )=0;
//纯虚函数
...
}








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








前一页 休息 42
注意
? 抽象类只能作为 基类 来使用。
? 不能声明抽象类的对象。
? 构造函数不能是虚函数,析构函数可
以是虚函数。








前一页 休息 43
例 8.5
#include <iostream.h>
class B0 //抽象基类 B0声明
{
public,//外部接口
virtual void display( )=0;
//纯虚函数成员
};








class B1,public B0 //公有派生
{
public:
void display(){cout<<"B1::display()"<<endl;}
//虚成员函数
};
class D1,public B1 //公有派生
{
public:
void display(){cout<<"D1::display()"<<endl;}
//虚成员函数
};
void fun(B0 *ptr) //普通函数
{ ptr->display(); }
void main() //主函数
{ B0 *p; //声明抽象基类指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p=&b1;
fun(p); //调用派生类 B1函数成员
p=&d1;
fun(p); //调用派生类 D1函数成员
}
程序的运行结果为:
B1::display()
D1::display()
前一页 休息 47
本章小结
? 多态:
– 同样的消息被不同类型的对象接收时导致
完全不同的行为,是对类的特定成员函数
的再抽象。
? 运算符重载
– 对已有的运算符赋予多重含义,使用已有
运算符对用户自定义类型 (比如类 )进行运
算操作。
前一页 休息 48
本章小结
? 联编
– 程序自身彼此关联的过程称为联编,联
编确定程序中的操作调用与执行该操作
的代码间的关系。
– 静态联编工作出现在编译阶段。
– 动态联编工作在程序运行时执行。
– 虚函数是动态联编的基础。
前一页 休息 49
本章小结
? 纯虚函数
– 在基类中说明的虚函数,它在该基类中
可以不给出数体,要求各派生类根据实
际需要编写自己的函数体。
? 抽象类
– 带有纯虚函数的类是抽象类。
– 抽象类的主要作用是通过它为一个类族
建立一个公共的接口,使它们能够更有
效地发挥多态特性。
前一页 休息 50
作业
? 复习第八章,预习第九章
? 8-5~8-9
? 实验八
前一页 休息 51
,C++语言程序设计, 更正
? P67,18行中,mC++”改为,m++”。
? P68,倒 9行,nC++”改为,n++”。
? P103,倒 6行,Hour=H;”改为,Hour=NewH;”。
? P103,倒 5行,Minute=M;”改为,Minute=NewM;”。
? P103,倒 4行,Second=S;”改为,Second=NewS;”。
? P151,倒 5行,intAy[5];”改为,int Ay[5];”。
? P211,19行,Member of D1”改为,Member of B0”。
? P211,20行,Member of B1”改为,Member of B0”。
? P211,21行 整行删除。
? P257,19行,delete name;”改为,delete [] name;”。