《面向对象程序设计(C).ppt》由会员分享,可在线阅读,更多相关《面向对象程序设计(C).ppt(46页珍藏版)》请在三一办公上搜索。
1、1,第八章 多态性,面向对象程序设计C+,2,本章主要内容,多态性运算符重载虚函数纯虚函数抽象类深度探索,3,多态性的概念,多态性是面向对象程序设计的重要特征之一。多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。多态的实现:函数重载运算符重载虚函数,4,问题举例复数的运算,class Complex/复数类声明public:Complex(double r=0.0,double i=0.0)real=r;imag=i;void display()const;/显示复数的值private:double real;double imag;,运算符重载,5,问题举例复数的运算
2、,用“+”、“-”能够实现复数的加减运算吗?实现复数加减运算的方法 重载“+”、“-”运算符,运算符重载,6,运算符重载的实质,运算符重载是对已有的运算符赋予多重含义必要性C+中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)实现机制将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。编译系统对重载运算符的选择,遵循函数重载的选择原则。,运算符重载,7,规则和限制,可以重载C+中除下列运算符外的所有运算符:.*:?:只能重载C+语言中已有的运算符,不可臆造新的。不改变原运算符的优先级和结合性。不能改变操作数个数。经重载的运算符,其操作数中至少
3、应该有一个是自定义类型。,运算符重载,8,两种形式,重载为类的非静态成员函数重载为非成员函数,运算符重载,9,运算符函数,声明形式函数类型 operator 运算符(形参).重载为类成员函数时 参数个数=原操作数个数-1(后置+、-除外)重载为非成员函数时 参数个数=原操作数个数,且至少应该有一个自定义类型的形参。,运算符重载,10,运算符成员函数的设计,双目运算符 B如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。经重载后,表达式 oprd1 B opr
4、d2 相当于 oprd1.operator B(oprd2),运算符重载,11,例8-1,将“+”、“-”运算重载为复数类的成员函数。规则:实部和虚部分别相加减。操作数:两个操作数都是复数类的对象。,运算符重载,#include using namespace std;class Complex/复数类定义public:/外部接口/构造函数Complex(double r=0.0,double i=0.0):real(r),imag(i)/运算符+重载成员函数Complex operator+(const Complex,12,/重载运算符函数实现Complex Complex:operato
5、r+(const Complex,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=(5,4)c2=(2
6、,10)c3=c1-c2=(3,-6)c3=c1+c2=(7,14),15,运算符成员函数的设计,前置单目运算符 U如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。经重载后,表达式 U oprd 相当于 oprd.operator U(),运算符重载,16,运算符成员函数的设计,后置单目运算符+和-如果要重载+或-为类成员函数,使之能够实现表达式 oprd+或 oprd-,其中 oprd 为A类对象,则+或-应被重载为 A 类的成员函数,且具有一个 int 类型形参。经重载后,表达式 oprd+相当于 o
7、prd.operator+(0),运算符重载,17,例8-2,运算符前置+和后置+重载为时钟类的成员函数。前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参。操作数是时钟类的对象。实现时间增加1秒钟。,运算符重载,#include using namespace std;class Clock/时钟类声明定义public:/外部接口Clock(int hour=0,int minute=0,int second=0);void showTime()const;Clock,18,/前置单目运算符重载函数Clock,19,/后置单目运算符重载Clock Clock:o
8、perator+(int)/注意形参表中的整型参数Clock old=*this;+(*this);/调用前置“+”运算符return old;,20,/其它成员函数的实现略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;,21,程序运行结果为:First time output:23:59:59Show m
9、yClock+:23:59:59Show+myClock:0:0:1,22,运算符非成员函数的设计,函数的形参代表依自左至右次序排列的各操作数。后置单目运算符+和-的重载函数,形参列表中要增加一个int,但不必写形参名。如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。,运算符重载,23,运算符非成员函数的设计,双目运算符 B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2)前置单目运算符 B重载后,表达式 B oprd 等同于operator B(oprd)后置单目运算符+和-重载后,表达式 oprd B 等同于op
10、erator B(oprd,0),运算符重载,24,例8-3,将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。将(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std:ostream引用,右操作数为复数类的常引用,返回std:ostream引用,用以支持下面形式的输出:cout a b;该输出调用的是:operator(operator(cout,a),b);,运算符重载,#include using namespace std;class Complex/复数类定义public:/外部接口/构造函数Complex(double r=
11、0.0,double i=0.0):real(r),imag(i)/运算符+重载friend Complex operator+(const Complex,25,Complex operator+(const Complex,26,27,静态绑定与动态绑定,绑定程序自身彼此关联的过程,确定程序中的操作调用与执行该操作的代码间的关系。静态绑定绑定过程出现在编译阶段,用对象名或者类名来限定要调用的函数。动态绑定绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数。,#includeusing namespace std;class Point public:Point(double x
12、,double y):x(x),y(y)double area()const return 0.0;private:double x,y;class Rectangle:public Point public:Rectangle(double x,double y,double w,double h);double area()const return w*h;private:double w,h;,静态绑定例,28,Rectangle:Rectangle(double x,double y,double w,double h):Point(x,y),w(w),h(h)void fun(con
13、st Point 运行结果:Area=0,29,#includeusing namespace std;class Point public:Point(double x,double y):x(x),y(y)virtual double area()const return 0.0;private:double x,y;class Rectangle:public Point public:Rectangle(double x,double y,double w,double h);virtual double area()const return w*h;private:double w,
14、h;/其他函数同上例,动态绑定例,30,void fun(const Point 运行结果:Area=375,31,32,虚函数,虚函数是动态绑定的基础。是非静态的成员函数。在类的声明中,在函数原型之前写virtual。virtual 只用来说明类声明中的原型,不能用在函数实现时。具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。本质:不是重载声明而是派生类覆盖基类的同名虚函数。调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数。,虚 函 数,33,例 8-4,#include using namespace std;class Bas
15、e1/基类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;,虚 函 数,/公有派生类Derived定义class Derived:public Base2 public:void display()co
16、nst;/覆盖基类的虚函数;void Derived:display()const cout display();/对象指针-成员名,34,int main()/主函数Base1 base1;/定义Base1类对象Base2 base2;/定义Base2类对象Derived derived;/定义Derived类对象fun(,运行结果:Base1:display()Base2:display()Derived:display(),35,36,虚析构函数,为什么需要虚析构函数?可能通过基类指针删除派生类对象;如果你打算允许其他人通过基类指针调用派生类对象的析构函数,就需要让基类的析构函数成为虚函
17、数。,虚 函 数,37,抽象类,带有纯虚函数的类称为抽象类:class 类名 virtual 类型 函数名(参数表)=0;/纯虚函数.,纯虚函数与抽象类,38,抽象类,作用抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。注意抽象类只能作为基类来使用。不能声明抽象类的对象。构造函数不能是虚函数,析构函数可以是虚函数。,纯虚函数与抽象类,39,例 8-5,#include using namespace std;class Base1/基类Base1定义public:virtual
18、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 display();/对象指针-成员名int main()/主函数Base2 base2;/定义Base2类对象Derived derived;/定义Derived类对
19、象fun(,运行结果:Base2:display()Derived:display(),40,多态类型与非多态类型,多态类型与非多态类型有虚函数的类类型称为多态类型其它类型皆为非多态类型二者的差异语言层面的差异多态类型支持运行时类型识别多态类型对象占用额外的空间设计原则上的差异,41,深 度 探 索,设计原则,多态类型多态类型的析构函数一般应为虚函数非多态类型非多态类型不宜作为公共基类由于没有利用动态多态性,一般可以用组合,而无需用共有继承;如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱。把不需被继承的类型设定为非多态类型由于成员函数都是静态绑定,调用速度较
20、快;对象占用空间较小。,42,深 度 探 索,运行时类型识别,运行时类型识别允许在运行时通过基类指针(或引用)辨别对象所属的具体派生类;只对多态类型适用;比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用。运行时类型识别的方式用dynamic_cast做类型转换的尝试;用typeid直接获取类型信息。,43,深 度 探 索,虚函数动态绑定的实现原理,动态选择被执行的函数函数的调用,需要通过函数代码的入口地址把函数入口地址作为变量,在不同情况下赋予不同的值,通过该变量调用函数,就可动态选择被执行的函数回顾:第6章介绍的函数指针、指向成员函数的指针虚表每个多态类有一个虚表(virtua
21、l table)虚表中有当前类的各个虚函数的入口地址每个对象有一个指向当前类的虚表的指针(虚指针vptr)动态绑定的实现构造函数中为对象的虚指针赋值通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址通过该入口地址调用虚函数,44,深 度 探 索,45,class Base public:virtual void f();virtual void g();private:int i;,class Derived:public Base public:virtual void f();/覆盖Base:fvirtual void h();/新增的虚函数private:int j;,深 度 探 索,46,小结与复习建议,主要内容多态性的概念、运算符重载、虚函数、纯虚函数、抽象类达到的目标理解多态的概念,学会运用多态机制。实验任务实验八作业:课后习题8-1、8-2,