c++语言程序设计郑莉第四版课件8_第1页
c++语言程序设计郑莉第四版课件8_第2页
c++语言程序设计郑莉第四版课件8_第3页
c++语言程序设计郑莉第四版课件8_第4页
c++语言程序设计郑莉第四版课件8_第5页
已阅读5页,还剩52页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1第八章多态性清华大学郑莉C++语言程序设计2本章主要内容多态性运算符重载虚函数纯虚函数抽象类深度探索3多态性的概念多态性是面向对象程序设计的重要特征之一。多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。多态的实现:函数重载运算符重载虚函数4重载函数的声明函数重载:两个以上的具有相同函数名的函数,但是形参的个数不同或类型不同,编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。例:形参类型不同int

add(intx,inty);floatadd(floatx,floaty);形参个数不同int

add(intx,inty);int

add(intx,inty,intz);

函数重载5注意事项int

add(int

x,int

y);int

add(int

a,int

b);编译器不以形参名来区分int

add(int

x,inty);void

add(int

x,inty);编译器不以返回值来区分

函数重载重载函数的形参必须不同:个数不同或类型不同。编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。6问题举例——复数的运算classComplex{ //复数类声明public:

Complex(doubler=0.0,doublei=0.0){real=r;imag=i;} voiddisplay()const; //显示复数的值private: doublereal; doubleimag;}; 运算符重载7问题举例——复数的运算用“+”、“-”能够实现复数的加减运算吗?实现复数加减运算的方法

——重载“+”、“-”运算符运算符重载8运算符重载的实质运算符重载是对已有的运算符赋予多重含义必要性C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)。实现机制运算符重载的实质是函数重载。将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,根据实参的类型来确定需要调用的函数。编译系统对重载运算符的选择,遵循函数重载的选择原则。主要优点可以改变现有运算符的操作方式,用于自定义的类类型使得程序看起来更加直观。运算符重载9运算符重载规则和限制可以重载C++中除下列运算符外的所有运算符:

类运算符.、成员指针运算符.*、作用域运算符::和三目运算符?:只能重载C++语言中已有的运算符,不可臆造新的。不改变原运算符的优先级和结合性。不能改变操作数个数。经重载的运算符,其操作数中至少应该有一个是自定义类型。10两种形式重载为类的非静态成员函数重载为非成员函数运算符重载11运算符函数声明形式函数类型operator运算符(形参){......}重载为类成员函数时

参数个数=原操作数个数-1 (后置++、--除外)重载为非成员函数时参数个数=原操作数个数,且至少应该有一个自定义类型的形参。运算符重载12运算符成员函数的设计双目运算符B如果要重载B为类成员函数,使之能够实现表达式oprd1Boprd2,其中

oprd1为A类对象,则B应被重载为A类的成员函数,形参类型应该是oprd2

所属的类型。经重载后,表达式

oprd1Boprd2

相当于oprd1.operatorB(oprd2)运算符重载13运算符重载

例8-1

将“+”、“-”运算重载为复数类的成员函数。规则:实部和虚部分别相加减。操作数:两个操作数都是复数类的对象。#include<iostream>usingnamespacestd;classComplex{ //复数类定义public: //外部接口

Complex(doubler=0.0,doublei=0.0):real(r),imag(i){} //构造函数

Complexoperator+(constComplex&c2)const; //运算符+重载成员函数

Complexoperator-(constComplex&c2)const; //运算符-重载成员函数

voiddisplay()const; //输出复数private: //私有数据成员

doublereal; //复数实部

doubleimag; //复数虚部};14ComplexComplex::operator+(constComplex&c2)const{ //重载运算符函数实现

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

returnComplex(real-c2.real,imag-c2.imag);//创建一个临时无名对象作为返回值}15voidComplex::display()const{

cout<<"("<<real<<","<<imag<<")"<<endl;}intmain(){ //主函数

Complexc1(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(); return0;}16程序输出的结果为:c1=(5,4)c2=(2,10)c3=c1-c2=(3,-6)c3=c1+c2=(7,14)1718运算符成员函数的设计前置单目运算符U如果要重载U为类成员函数,使之能够实现表达式Uoprd,其中

oprd

为A类对象,则U应被重载为A类的成员函数,无形参。经重载后,

表达式

Uoprd

相当于oprd.operatorU()(不带参数)运算符重载19运算符成员函数的设计后置单目运算符++和--如果要重载++或--为类成员函数,使之能够实现表达式

oprd++

或oprd--

,其中

oprd

为A类对象,则++或--应被重载为A类的成员函数,且具有一个int

类型形参。经重载后,表达式

oprd++

相当于oprd.operator++(0)运算符重载20例8-2运算符前置++和后置++重载为时钟类的成员函数。前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参。操作数是时钟类的对象。实现时间增加1秒钟。运算符重载#include<iostream>usingnamespacestd;classClock{ //时钟类声明定义public: //外部接口

Clock(inthour=0,intminute=0,intsecond=0); voidshowTime()const;

Clock&operator++();//前置单目运算符重载

Clockoperator++(int);//后置单目运算符重载private: //私有数据成员

inthour,minute,second;};21//构造函数

Clock::Clock(int

hour,int

minute,int

second){ if(0<=hour&&hour<24&&0<=minute&&minute<60&&0<=second&&second<60) {this->hour=hour;

this->minute=minute;

this->second=second; } else

cout<<"Timeerror!"<<endl;}22//前置单目运算符重载函数ClockClock::operator++(){

second++; if(second>=60){ second-=60; minute++; if(minute>=60){ minute-=60; hour=(hour+1)%24; } } return*this;}23//后置单目运算符重载ClockClock::operator++(int){

//注意形参表中的整型参数

Clockold=*this; ++(*this); //调用前置“++”运算符

returnold;}24//其它成员函数的实现略intmain(){ ClockmyClock(23,59,59);

cout<<"Firsttimeoutput:";

myClock.showTime();

cout<<"ShowmyClock++:"; (myClock++).showTime();

cout<<"Show++myClock:"; (++myClock).showTime(); return0;}25程序运行结果为:Firsttimeoutput:23:59:59ShowmyClock++:23:59:59Show++myClock:0:0:12627运算符非成员函数的设计函数的形参代表依从左至右次序排列的各操作数。后置单目运算符++和--的重载函数,形参列表中要增加一个int,但不必写形参名。如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。运算符重载28运算符非成员函数的设计双目运算符B重载后,

表达式oprd1Boprd2

等同于operatorB(oprd1,oprd2)前置单目运算符B重载后,

表达式Boprd

等同于operatorB(oprd)后置单目运算符++和--重载后,

表达式oprdB

等同于operatorB(oprd,0)运算符重载29例8-3将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用,用以支持下面形式的输出:

cout<<a<<b;

该输出调用的是:

operator<<(operator<<(cout,a),b);运算符重载#include<iostream>usingnamespacestd;classComplex{ //复数类定义public: //外部接口

Complex(doubler=0.0,doublei=0.0):real(r),imag(i){} //构造函数

friendComplexoperator+(constComplex&c1,constComplex&c2); //运算符+重载

friendComplexoperator-(Complexc1,Complexc2); //运算符-重载

friendostream&operator<<(ostream&out,constComplex&c);//运算符<<重载private: //私有数据成员

doublereal; //复数实部

doubleimag; //复数虚部};30Complexoperator+(constComplex&c1,constComplex&c2){

returnComplex(c1.real+c2.real,c1.imag+c2.imag);}Complexoperator-(Complexc1,Complexc2){

returnComplex(c1.real-c2.real,c1.imag-c2.imag);}ostream&operator<<(ostream&out,constComplex&c){

out<<"("<<c.real<<","<<c.imag<<")"; returnout;}3132静态绑定与动态绑定绑定程序自身彼此关联的过程,确定程序中的操作调用与执行该操作的代码间的关系。静态绑定绑定过程出现在编译阶段,用对象名或者类名来限定要调用的函数。动态绑定绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数。#include<iostream>usingnamespacestd;classPoint{public:

Point(doublex,doubley):x(x),y(y){} doublearea()const{return0.0;}private: doublex,y;};classRectangle:publicPoint{public:

Rectangle(doublex,doubley,doublew,doubleh); doublearea()const{returnw*h;}private: doublew,h;};静态绑定例33Rectangle::Rectangle(doublex,doubley,doublew,doubleh):Point(x,y),w(w),h(h){}voidfun(const

Point&s){

cout<<"Area="<<s.area()<<endl;}intmain(){

Rectanglerec(3.0,5.2,15.0,25.0);

fun(rec); return0;}运行结果:Area=034#include<iostream>usingnamespacestd;classPoint{public:

Point(doublex,doubley):x(x),y(y){}

virtualdoublearea()const{return0.0;}private: doublex,y;};classRectangle:publicPoint{public:

Rectangle(doublex,doubley,doublew,doubleh);

virtualdoublearea()const{returnw*h;}private: doublew,h;};//其他函数同上例动态绑定例35voidfun(const

Point&s){

cout<<"Area="<<s.area()<<endl;}intmain(){

Rectanglerec(3.0,5.2,15.0,25.0);

fun(rec); return0;}运行结果:Area=3753637虚函数虚函数是动态绑定的基础。是非静态的成员函数。在类的声明中,在函数原型之前写virtual。virtual

只用来说明类声明中的原型,不能用在函数实现时。具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。本质:不是重载声明而是覆盖。调用方式:通过基类指针或引用,执行时会

根据指针指向的对象的类,决定调用哪个函数。虚函数38例8-4#include<iostream>usingnamespacestd;classBase1{//基类Base1定义public:

virtualvoiddisplay()const; //虚函数};voidBase1::display()const{

cout<<"Base1::display()"<<endl;}classBase2:publicBase1{//公有派生类Base2定义public: voiddisplay()const; //覆盖基类的虚函数};voidBase2::display()const{

cout<<"Base2::display()"<<endl;}虚函数//公有派生类Derived定义classDerived:publicBase2{public: voiddisplay()const;//覆盖基类的虚函数};voidDerived::display()const{

cout<<"Derived::display()"<<endl;}//参数为指向基类对象的指针voidfun(Base1*ptr){

ptr->display(); //"对象指针->成员名"}39intmain(){ //主函数

Base1base1; //定义Base1类对象

Base2base2; //定义Base2类对象

Derivedderived; //定义Derived类对象

fun(&base1); //用Base1对象的指针调用fun函数

fun(&base2); //用Base2对象的指针调用fun函数

fun(&derived); //用Derived对象的指针调用fun函数

return0;}运行结果:Base1::display()Base2::display()Derived::display()4041虚析构函数为什么需要虚析构函数?可能通过基类指针删除派生类对象;如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。虚函数42抽象类带有纯虚函数的类称为抽象类:class类名

{

virtual

类型函数名(参数表)=0;//纯虚函数

...}纯虚函数与抽象类43抽象类纯虚函数与抽象类作用抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。注意抽象类只能作为基类来使用。不能声明抽象类的对象。构造函数不能是虚函数,析构函数可以是虚函数。44例8-5纯虚函数与抽象类#include<iostream>usingnamespacestd;classBase1{//基类Base1定义public:

virtualvoiddisplay()const=0; //纯虚函数};classBase2:publicBase1{//公有派生类Base2定义public: voiddisplay()const{//覆盖基类的虚函数

cout<<"Base2::display()"<<endl; }};classDerived:publicBase2{//公有派生类Derived定义public: voiddisplay()const{//覆盖基类的虚函数

cout<<"Derived::display()"<<endl; }};voidfun(Base1*ptr){

ptr->display(); //"对象指针->成员名"}intmain(){ //主函数

Base2base2; //定义Base2类对象

Derivedderived; //定义Derived类对象

fun(&base2); //用Base2对象的指针调用fun函数

fun(&derived);//用Derived对象的指针调用fun函数

return0;}运行结果:Base2::display()Derived::display()4546类型兼容规则一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:派生类的对象可以隐含转换为基类对象。派生类的对象可以初始化基类的引用。派生类的指针可以隐含转换为基类的指针。通过基类对象名、指针只能使用从基类继承的成员类型兼容47例7-4类型兼容规则举例#include<iostream>usingnamespacestd;classBase1{//基类Base1定义public: voiddisplay()const{

cout<<"Base1::display()"<<endl; }};类型兼容classBase2:publicBase1{//公有派生类Base2定义public: voiddisplay()const{

cout<<"Base2::display()"<<endl; }};classDerived:publicBase2{//公有派生类Derived定义public: voiddisplay()const{

cout<<"Derived::display()"<<endl; }};voidfun(Base1*ptr){//参数为指向基类对象的指针

ptr->display(); //"对象指针->成员名"}48intmain(){ //主函数

Base1base1; //声明Base1类对象

Base2base2; //声明Base2类对象

Derivedderived; //声明Derived类对象

//用Base1对象的指针调用fun函数 fun(&base1); //用Base2对象的指针调用fun函数

fun(&base2); //用Derived对象的指针调用fun函数fun(&derived);

return0;}运行结果:Base1::display()Base1::display()Base1::display()49多态类型与非多态类型多态类型与非多态类型有虚函数的类类型称为多态类型其它类型皆为非多态类型二者的差异语言层面的差异多态类型支持运行时类型识别多态类型对象占用额外的空间设计原则上的差异50深度探索设计原则多态类型多态类型的析构函数一般应为虚函数非多态类型非多态类型不宜作为公共基类由于没有利用动态多态性,一般可以用组合,而无需用共有继承;如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱。把不需被继承的类型设定为非多态类型由于成员函数都是静态绑定,调用速度较快;对象占用空间较小。51深度探索运行时类型识别运行时类型识别允许在运行时通过基类指针(或引用)辨别对象所属的具体派生类;只对多态类型适用;比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用。运行时类型识别的方式用dynamic_cast做类型转换的尝试;用typeid直接获取类型信息。52深度探索dynamic_cast的使用语法形式dynamic_cast<目的类型>(表达式)功能将基类指针转换为派生类指针,将基类引用转换为派生类引用;转换是有条件的如果指针(或引用)所指对象的实际类型与转换的目的类型兼容,则转换成功进行;否则如执行的是指针类型的转换,则得到空指针;如执行的是引用类型的转换,则抛出异常。53深度探索例8-9dynamic_cast示例#include<iostream>usingnamespacestd;classBase{public: virtualvoidfun1(){cout<<"Base::fun1()"<<endl;} virtual~Base(){}};classDerived1:publicBase{public: virtualvoidfun1(){cout<<"Derived1::fun1()"<<endl;} virtualvoidfun2(){cout<<"Derived1::fun2()"<<endl;}};classDerived2:publicDerived1{public: virtualvoidfun1(){cout<<"Derived2::fun1()"<<endl;} virtualvoidfun2(){cout<<"Derived2::fun2()"<<endl;}};54深度探索voidfun(Base*b){ b->fun1(); //尝试将b转换为Derived1指针 Derived1*d=dynamic_cast<Derived1*>(b); //判断转换是否成功

if(d!=0)d->fun2();}intmain(){ Baseb;

fun(&b); Derived1d1; fun(&d1); Derived2d2; fun(&d2); return0;}运行结果:Base::fun1()Derived1::fun1()Derived1::fun2()Derived2::fun1()Derived2::fun2()55typeid的使用语法形式typeid(表达式)typeid(类型说明符)功能获得表达式或类型说明符的类型信息表达式有多态类型时,会被求值,并得到动态类型信息;否则,表达式不被求值,只能得到静态的类型信息。类型信息用type_info对象表示type_info是typeinfo头文件中声明的类;typeid的结果是type_info类型的常引用;可以用type_info的重载的“==”、“!=”操作符比较两类型的异同;type_info的name成员函数返回类型名称,类型为constchar*。56深度探索例8-10typeid示例#include<iostream>#include<typeinfo>usingnamespacestd;classBase{public: virtual~Base(){}};classDerived:publicBase{

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论