版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第五讲多态性多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例多态是指操作接口具有表现多种形态的能力,即能根据操作环境的不同采用不同的处理方式。多态性是面向对象系统的主要特性之一,在这样的系统中,一组具有相同基本语义的方法能在同一接口下为不同的对象服务。C++语言支持的多态性可以按其实现的时机分为编译时多态和运行时多态两类。多态性概述多态的类型多态的类型重载多态:函数重载及运算符重载强制多态:隐式类型转换及强制类型转换包含多态:基类及子类中同名成员函数的多态行为参数多态:函数模板及类模板C++不但提供了固有的多态性,还提供了实现自定义多态性的手段。多态性概述多态的实现多态的种类编译时的多态运行时的多态绑定是指把一个标识符名和一个存储地址联系在一起的过程编译时的多态绑定工作在编译连接阶段完成的情况称为静态绑定运行时的多态绑定工作在程序运行阶段完成的情况称为动态绑定多态性概述多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例用“+”、“-”能够实现复数的加减运算吗?实现复数加减运算的方法
——重载“+”、“-”运算符运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。运算符重载运算符重载的规则C++几乎可以重载全部的运算符,而且只能够重载C++中已经有的运算符重载之后运算符的优先级和结合性都不会改变,操作数个数也不会改变运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造两种重载方式:重载为类的非静态成员函数和重载为非成员函数重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数运算符重载运算符重载的规则运算符重载C++中可以被重载的操作符:C++中不能被重载的操作符:
“.”、“.*”、“::”、“?:”C++要求,重载时,赋值”=“、下标”[]”、调用”()”和成员访问箭头”->”操作符必须被重载为类的成员函数运算符重载为成员函数声明形式函数类型类名::operator运算符(形参){......}重载为类成员函数时
参数个数=原操作数个数-1
(后置++、--除外)重载为非成员函数时参数个数=原操作数个数,且至少应该有一个自定义类型的形参运算符重载运算符前后可以有空格,也可以没有空格运算符重载为成员函数双目运算符B如果要重载B为类成员函数,使之能够实现表达式oprd1Boprd2,其中
oprd1为A类对象,则B应被重载为A类的成员函数,形参类型应该是oprd2
所属的类型。经重载后,表达式
oprd1Boprd2
相当于oprd1.operatorB(oprd2)运算符重载这是理解和应用函数重载的关键复数类加减法运算重载运算符重载
将“+”、“-”运算重载为复数类的成员函数。规则:实部和虚部分别相加减。
操作数:两个操作数都是复数类的对象。复数类加减法运算重载运算符重载#include<iostream>usingnamespacestd;classComplex //复数类定义{ public: //外部接口
Complex(doubler=0.0,doublei=0.0):m_real(r),m_imag(i){} //构造函数
Complexoperator+(constComplex&c2)const; //运算符+重载成员函数
Complexoperator-(constComplex&c2)const; //运算符-重载成员函数
voiddisplay()const; //输出复数private: //私有数据成员
doublem_real; //复数实部
doublem_imag; //复数虚部};复数类加减法运算重载运算符重载//重载运算符函数实现ComplexComplex::operator+(constComplex&c2)const{
returnComplex(m_real+c2.m_real,m_imag+c2.m_imag);//创建一个临时无名对象作为返回值}//重载运算符函数实现ComplexComplex::operator-(constComplex&c2)const{
returnComplex(m_real-c2.m_real,m_imag-c2.m_imag);//创建一个临时无名对象作为返回值}voidComplex::display()const{ cout<<"("<<m_real<<","<<m_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;}程序输出的结果为:c1=(5,4)c2=(2,10)c3=c1-c2=(3,-6)c3=c1+c2=(7,14)运算符成员函数的设计运算符重载前置单目运算符U如果要重载U为类成员函数,使之能够实现表达式Uoprd,其中
oprd为A类对象,则U应被重载为A类的成员函数,无形参经重载后,
表达式
Uoprd
相当于oprd.operatorU()运算符成员函数的设计运算符重载后置单目运算符++和--如果要重载++或--为类成员函数,使之能够实现表达式
oprd++
或oprd--
,其中
oprd为A类对象,则++或--应被重载为A类的成员函数,且具有一个int类型形参经重载后,表达式
oprd++
相当于oprd.operator++(0)示例:运算符重载为成员函数运算符重载运算符前置++和后置++重载为时钟类的成员函数前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参操作数是时钟类的对象实现时间增加1秒钟运算符重载为成员函数运算符重载#include<iostream>usingnamespacestd;classClock //时钟类定义{ public: //外部接口
Clock(inthour=0,intminute=0,intsecond=0); voidshowTime()const;
Clock&operator++(); //前置单目运算符重载
Clockoperator++(int); //后置单目运算符重载private: //私有数据成员
intm_hour,m_minute,m_second;};Clock::Clock(inthour/*=0*/,intminute/*=0*/,intsecond/*=0*/){
if(0<=hour&&hour<24&&0<=minute&&minute<60 &&0<=second&&second<60) { this->m_hour=hour; this->m_minute=minute; this->m_second=second; }else cout<<"Timeerror!"<<endl;}运算符重载为成员函数运算符重载voidClock::showTime()const //显示时间函数{
cout<<m_hour<<":"<<m_minute<<":"<<m_second<<endl;}Clock&Clock::operator++() //前置单目运算符重载函数{
m_second++; if(m_second>=60) { m_second-=60; m_minute++; if(m_minute>=60) { m_minute-=60; m_hour=(m_hour+1)%24; } } return*this;}//后置单目运算符重载(注意形参表中的整型参数)ClockClock::operator++(int){
Clockold=*this; ++(*this); //调用前置“++”运算符
returnold;}这个参数仅起到标识的作用,不传递任何参数值返回的是值,而不是引用运算符重载为成员函数运算符重载intmain(){ ClockmyClock(23,59,59); cout<<"Firsttimeoutput:"; myClock.showTime(); cout<<"ShowmyClock++:";
(myClock++).showTime(); cout<<"Show++myClock:";
(++myClock).showTime(); return0;}运行结果:Firsttimeoutput:23:59:59ShowmyClock++:23:59:59Show++myClock:0:0:1运算符重载为非成员函数运算符重载函数的形参代表依自左至右次序排列的各操作数后置单目运算符++和--的重载函数,形参列表中要增加一个int,但不必写形参名如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元运算符重载为非成员函数运算符重载双目运算符B重载后,
表达式oprd1Boprd2
等同于operatorB(oprd1,oprd2)前置单目运算符B重载后,
表达式Boprd
等同于operatorB(oprd)后置单目运算符++和--重载后,
表达式oprdB
等同于operatorB(oprd,0)以非成员函数形式重载Complex的加减法运算和“<<”运算符运算符重载将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用,用以支持下面形式的输出:
cout<<a<<b;
该输出调用的是:
operator<<(operator<<(cout,a),b);运算符重载为非成员函数运算符重载#include<iostream>usingnamespacestd;classComplex //复数类定义{ public: //外部接口
Complex(doubler=0.0,doublei=0.0):m_real(r),m_imag(i){} //构造函数
friendComplexoperator+(constComplex&c1,constComplex&c2); //运算符+重载
friendComplexoperator-(constComplex&c1,constComplex&c2); //运算符-重载
friendostream&operator<<(ostream&out,constComplex&c);//运算符<<重载private: //私有数据成员
doublem_real; //复数实部
doublem_imag; //复数虚部};//重载运算符函数实现Complexoperator+(constComplex&c1,constComplex&c2){
returnComplex(c1.m_real+c2.m_real,c1.m_imag+c2.m_imag);}运算符重载为非成员函数运算符重载//重载运算符函数实现Complexoperator-(constComplex&c1,constComplex&c2){
returnComplex(c1.m_real-c2.m_real,c1.m_imag-c2.m_imag);}//重载运算符函数实现ostream&operator<<(ostream&out,constComplex&c){
out<<"("<<c.m_real<<","<<c.m_imag<<")"; returnout;}intmain() //主函数{
Complexc1(5,4),c2(2,10),c3; //定义复数类的对象
cout<<"c1="<<c1<<endl; cout<<"c2="<<c2<<endl;
c3=c1-c2; //使用重载运算符完成复数减法
cout<<"c3=c1-c2="<<c3<<endl;
c3=c1+c2; //使用重载运算符完成复数加法
cout<<"c3=c1+c2="<<c3<<endl; return0;}多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例问题的提出虚函数与多态回顾前面个人银行账户的例子,其中遗留了一个问题:如何利用一个循环结构依次处理同一类族中不同类的对象?问题的提出虚函数与多态#include"account.h"#include<iostream>usingnamespacestd;intmain(){ Datedate(2008,11,1); //起始日期
//建立几个账户
SavingsAccountsa1(date,"S3755217",0.015); SavingsAccountsa2(date,"02342342",0.015); CreditAccountca(date,"C5392394",10000,0.0005,50);
Account*accounts[]={&sa1,&sa2,&ca}; constintn=sizeof(accounts)/sizeof(Account*); for(inti=0;i<n;i++) {
accounts[i]->show(); cout<<endl; } return0;}问题的提出虚函数与多态从show函数的输出结果可以看出,无论accounts[i]指向的是哪种类型的实例,通过accounts[i]->show()调用的函数都是Account类中定义的show函数而我们希望根据accounts[i]所指向的实例类型决定哪个show函数被调用,当i==2时被调用的应当是CreditAccount类中定义的show函数要解决这一问题,就要应用虚函数来实现多态性虚函数的概念虚函数与多态注意区分“指针类型”与“指针所指实例类型”,后者可以是前者的子类型“静态绑定”是指根据“指针类型”在编译时决定要调用的函数;“动态绑定”是指根据“指针所指实例类型”在运行时决定要调用的函数根据类型兼容规则,基类指针(或引用)可以指向其派生类的实例,但通过基类指针(或引用)却只能调用基类的函数成员,无法调用其所指实例(派生类)的函数成员解决这一问题的办法是:如果希望通过基类指针(或引用)访问其所指派生类实例的函数成员,则应在基类中声明同名的虚拟函数成员,表示对相应的函数进行动态绑定,而不是静态绑定一般虚函数成员虚函数与多态C++中引入了虚函数的机制在派生类中可以对基类中的成员函数进行覆盖(重定义)虚函数必须是非静态成员函数虚函数的声明
virtual函数类型函数名(形参表) {
函数体 }virtual关键字只能出现在类定义中的函数原型声明中,不能出现在类体外的函数定义中。注意,不同于“隐藏”的概念虚函数成员虚函数与多态#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;}注意,派生类中的虚函数可以不用增加virtual关键字。对于基类中的虚函数,派生类中的同名同参数函数自动成为虚函数。虚函数成员虚函数与多态classDerived:publicBase2 //公有派生类{public:
voiddisplay()const; //覆盖基类的虚函数};voidDerived::display()const{ cout<<"Derived::display()"<<endl;}voidfun(Base1*ptr) //参数为指向基类对象的指针{
ptr->display(); //"对象指针->成员名"}intmain() //主函数{
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()注意,实际调用的函数不是由指针类型决定的,而是由指针所指的派生类实例决定的虚函数注意事项虚函数与多态同名隐藏规则对于虚函数来说仍然有效,并且规则不变当基类构造函数和析构函数调用虚函数时,不会调用派生类的虚函数。这是因为当基类被构造时,对象还不是一个派生类对象;当基类被析构时,对象已经不是一个派生类对象一般不要重写继承而来的非虚函数(虽然语法对此没有强行限制),因为那会导致通过基类指针和派生类的指针或对象调用同名函数时,产生不同的结果,从而引起混乱虚函数注意事项虚函数与多态在重写继承来的虚函数时,如果函数有默认形参值,不要重新定义不同的值。原因是,虽然虚函数是动态绑定的,但默认形参值却是静态绑定的。也就是说,默认形参值只能来自基类的定义。只有通过基类的指针或引用调用虚函数时,才会发生动态绑定;通过基类的对象调用虚函数时,不会发生动态绑定。这是因为,基类的指针和引用可以指向派生类的对象,但基类的对象却不能表示派生类的对象。例如:Derivedd; //定义派生类对象Base*ptr=&d; //基类指针ptr可以指向派生类对象Base&ref=d; //基类引用ref可以作为派生类对象的别名Baseb=d; //调用Base的复制构造函数用d构造b,b的类型是Base而非Derived对象切片:由于执行的是Base的复制构造函数,只有Base类的数据成员会被复制,Derived类中新增的数据成员既不会被复制,也没有空间去存储。虚函数成员虚函数与多态#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;}虚函数成员虚函数与多态classDerived:publicBase2 //公有派生类{public:
voiddisplay()const; //覆盖基类的虚函数};voidDerived::display()const{ cout<<"Derived::display()"<<endl;}voidfun(Base1ptr) //参数为指向基类对象的指针{
ptr.display(); //"对象指针->成员名"}intmain() //主函数{
Base1base1; //定义Base1类对象
Base2base2; //定义Base2类对象
Derivedderived; //定义Derived类对象
fun(base1);//用Base1对象的指针调用fun函数
fun(base2);//用Base2对象的指针调用fun函数
fun(derived);//用Derived对象的指针调用fun函数
return0;}运行结果:Base1::display()Base1::display()Base1::display()注意,用基类对象无法形成动态绑定相关概念小结虚函数与多态重载、覆盖与隐藏成员函数被重载的特征:相同的范围(在同一个类中)函数名相同函数参数个数或类型不同virtual关键字可有可无覆盖是指派生类的虚函数覆盖基类的虚函数,特征是:不同的范围(分别位于派生类和基类)函数名字相同函数参数相同基类的被覆盖函数必须有virtual关键字相关概念小结虚函数与多态成员函数被隐藏的特征是:不同的范围(分别位于派生类和基类)函数名字相同函数参数可以相同,也可以不同virtual关键字可有可无基类中的同名函数在派生类中不可见(即被隐藏,除非用域操作符指定基类类型)从内向外寻找函数名多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例虚析构函数为什么需要虚析构函数?可能通过基类指针删除派生类对象如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的虚析构函数举例虚析构函数#include<iostream>usingnamespacestd;classBase{public: ~Base();};Base::~Base(){ cout<<"Basedestructor"<<endl;}classDerived:publicBase{public: Derived(); ~Derived();private: int*p;};Derived::Derived(){
p=newint(0);}Derived::~Derived(){ cout<<"Deriveddestructor"<<endl;
deletep;}voidfun(Base*b){
deleteb;}intmain(){ Base*b=newDerived(); fun(b); return0;}虚析构函数运行时结果:Basedestructor避免上述错误的有效方法就是将析构函数声明为虚函数,运行结果变为:DeriveddestructorBasedestructor虚析构函数举例虚析构函数举例虚析构函数#include<iostream>usingnamespacestd;classBase{public:
virtual~Base();};Base::~Base(){ cout<<"Basedestructor"<<endl;}classDerived:publicBase{public: Derived(); ~Derived();private: int*p;};Derived::Derived(){
p=newint(0);}Derived::~Derived(){ cout<<"Deriveddestructor"<<endl;
deletep;}voidfun(Base*b){
deleteb;}intmain(){ Base*b=newDerived(); fun(b); return0;}多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例纯虚函数与抽象类纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:virtual函数类型函数名(参数表)=0;带有纯虚函数的类称为抽象类,这样的类不可实例化:class类名{virtual类型函数名(参数表)=0;//纯虚函数
...}纯虚函数纯虚函数的作用:1)表示相应的类为抽象类,不可实例化;子类必须给出实现才能实例化;2)支持通过抽象类指针实现对应函数的动态绑定(即多态)。纯虚函数与抽象类作用抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于抽象类中无法实现的函数,可以声明为纯虚函数,留给派生类去实现。注意抽象类只能作为基类来使用不能声明抽象类的对象析构函数可以是虚函数,但构造函数不能是虚函数抽象类抽象类相当于一个“半成品”,它为派生类的实现提供基础,但自己不可以用于产品实现。抽象类举例纯虚函数与抽象类#include<iostream>usingnamespacestd;classBase1 //基类Base1定义{public:
virtualvoiddisplay()const=0; //纯虚函数};classBase2:publicBase1
//公有派生类Base2定义{public: voiddisplay()const; //覆盖基类的虚函数};voidBase2::display()const{ cout<<"Base2::display()"<<endl;}抽象类举例纯虚函数与抽象类classDerived:publicBase2
//公有派生类Derived定义{public: voiddisplay()const; //覆盖基类的虚函数};voidDerived::display()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()多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例程序实例—变步长梯形积分算法求解函数的定积分我们只考虑最简单的情况,设被积函数是一个一元函数,定积分表达式为:积分表示的意义是一元函数f(x)在区间a到b之间与x轴所夹的面积算法基本原理y
xabxkxk-1hIkf(x)程序实例—变步长梯形积分算法求解函数的定积分在每个小区间上都用小的梯形面积来近似原函数的积分,当小区间足够小时,我们就可以得到原来积分的近似值。每个小区间的面积值公式:实际计算中步长h逐次减半,反复利用上述求积公式进行计算,直到所求得的积分结果满足要求的精度为止。并得到递推公式:算法基本原理程序实例—变步长梯形积分算法求解函数的定积分算法基本原理MyFunction<<const>>+operator()(x:double):doubleIntegration<<const,abstract>>+operator()(a:double,b:double,eps:double):doubleFunction<<const,abstract>>+operator()(x:double):doubleTrapz+Trapz(pf:constF&)<<const>>+operator()(a:double,b:double,eps:double):double-f程序实例—变步长梯形积分算法求解函数的定积分我们求一个测试函数在某给定区间的积分值,对整个程序进行了测试,误差为10-7。测试函数:整个程序分为三个独立的文件,Trapzint.h文件包括类的定义,Trapzint.cpp文件包括类的成员函数实现。文件intmain.cpp是程序的主函数,主函数中定义了函数类Fun和梯形积分类Trapz的对象源程序及说明程序实例—变步长梯形积分算法求解函数的定积分源程序//Trapzint.h文件一,类定义classFunction //抽象类Function的定义{ public:
virtualdoubleoperator()(doublex)const=0; //纯虚函数重载运算符() virtual~Function(){}};classMyFunction:publicFunction //公有派生类MyFunction定义{ public:
virtualdoubleoperator()(doublex)const; //覆盖虚函数};classIntegration //抽象类Integration定义{ public:
virtualdoubleoperator()(doublea,doubleb,doubleeps)const=0; virtual~Integration(){}};程序实例—变步长梯形积分算法求解函数的定积分源程序classTrapz:publicIntegration //公有派生类Trapz定义{ public: Trapz(constFunction&f):f(f){} //构造函数
virtualdoubleoperator()(doublea,doubleb,doubleeps)const;private: constFunction&f; //私有成员,Function类对象的引用};//Trapzint.cpp文件二,类实现#include"Trapzint.h" //包含类的定义头文件#include<cmath>doubleMyFunction::operator()(doublex)const//被积函数{
returnlog(1.0+x)/(1.0+x*x);}//积分运算过程,重载为运算符()doubleTrapz::operator()(doublea,doubleb,doubleeps)const{ booldone=false; //是Trapz类的虚函数成员
intn=1; doubleh=b-a; doubletn=h*(f(a)+f(b))/2; //计算n=1时的积分值
doublet2n;
引用成员只能在初始化列表中初始化程序实例—变步长梯形积分算法求解函数的定积分源程序 do{ doublesum=0; for(intk=0;k<n;k++) { doublex=a+(k+0.5)*h; sum+=f(x); }
t2n=(tn+h*sum)/2.0; //变步长梯形法计算
if(fabs(t2n-tn)<eps) done=true; //判断积分误差
else { //进行下一步计算
tn=t2n; n*=2; h/=2; } }while(!done); returnt2n;}程序实例—变步长梯形积分算法求解函数的定积分源程序#include"Trapzint.h" //类定义头文件#include<iostream>#include<iomanip>usingnamespacestd;intmain() //主函数{
MyFunctionf; //定义MyFunction类的对象
Trapztrapz(f); //定义Trapz类的对象
//计算并输出积分结果
cout<<"TRAPZInt:"<<setprecision(7)<<trapz(0,2,1e-7)<<endl; return0;}运行结果:TRAPZInt:0.5548952多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例多态类型小结多态类型与非多态类型有虚函数的类类型称为多态类型其它类型皆为非多态类型二者的差异语言层面的差异多态类型支持运行时类型识别多态类型对象占用额外的空间设计原则上的差异多态类型与非多态类型多态类型小结多态类型多态类型的析构函数一般应为虚函数非多态类型非多态类型不宜作为公共基类由于没有利用动态多态性,一般可以用组合,而无需用公有继承如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱把不需被继承的类型设定为非多态类型由于成员函数都是静态绑定,调用速度较快对象占用空间较小设计原则多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例运行时类型识别运行时类型识别允许在运行时通过基类指针(或引用)辨别对象所属的具体派生类只对多态类型适用比虚函数动态绑定的开销更大,因此应仅对虚函数无法解决的问题使用运行时类型识别的方式用dynamic_cast做类型转换的尝试用typeid直接获取类型信息运行时类型识别语法形式dynamic_cast<目的类型>(表达式)功能将基类指针转换为派生类指针,将基类引用转换为派生类引用;转换是有条件的如果指针(或引用)所指对象的实际类型与转换的目的类型兼容,则转换成功进行否则如执行的是指针类型的转换,则得到空指针;如执行的是引用类型的转换,则抛出异常使用dynamic_castdynamic_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;}};dynamic_cast用法示例运行时类型识别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()运行时类型识别语法形式typeid(表达式)typeid(类型说明符)功能获得表达式或类型说明符的类型信息表达式有多态类型时,会被求值,并得到动态类型信息否则,表达式不被求值,只能得到静态的类型信息类型信息用type_info对象表示type_info是typeinfo头文件中声明的类typeid的结果是type_info类型的常引用可以用type_info的重载的“==”、“!=”操作符比较两类型的异同type_info的name成员函数返回类型名称,类型为constchar*用typeid获取运行时类型信息typeid用法示例运行时类型识别#include<iostream>#include<typeinfo>usingnamespacestd;classBase{public: virtual~Base(){}};classDerived:publicBase{};voidfun(Base*b){ //得到表示b和*b类型信息的对象
consttype_info&info1=typeid(b); consttype_info&info2=typeid(*b);
cout<<"typeid(b):"<<()<<endl;cout<<"typeid(*b):"<<()<<endl;//判断*b是否为Base类型if(info2==typeid(Base)) cout<<"Abaseclass!"<<endl;}intmain(){Baseb;fun(&b);Derivedd;fun(&d);return0;}结果:typeid(b):classBase*typeid(*b):classBaseAbaseclass!typeid(b):classBase*typeid(*b):classDerived多态性多态性概述运算符重载虚函数与多态虚析构函数纯虚函数与抽象类程序实例多态类型小结运行时类型识别综合实例综合实例
——对个人银行账户管理程序的改进本例在第七章例7-10的基础上,对Account类做了如下改进:(1)将show函数声明为虚函数,因此通过指向CreditAccount类实例的Accout类型的指针来调用show函数时,被实际调用的将是为CreditAccount类定义的show函数,这样,如果创建一个Account指针类型的数组,使各个元素分别指向各个账户对象,就可以通过一个循环来调用它们的show函数;(2)在Account类中添加deposit、withdraw、settle这3个函数的声明,且将它们都声明为纯虚函数,这使得通过基类的指针可以调用派生类的相应函数,而且无需给出它们在基类中的实现。经过这一改动之后,Account类就变成了抽象类。- acc:Accumulator- rate:doubleSavingsAccount+ SavingsAccount(date:Date,id:int,rate:double)<<const>>+ getRate():double+ deposit(date:Date,amount:double,desc:string)+ withdraw(date:Date,amount:double,desc:string)+ settle(date:Date)11- acc:Accumulator- credit:double- rate:double- fee:doubleCreditAccount<<const>>- getDebt():double+ CreditAccount(date:Date,id:int,credit:double,rate:double,fee:double)<<const>>+ getCredit():double<<const>>+ getRate():double<<const>>+ getFee():double<<const>>+ getAvailableCredit():double+ deposit(date:Date,amount:double,desc:string)+ withdraw(date:Date,amount:double,desc:string)+ settle(date:Date)<<const>>+ show()- year:int- month:int- day:int- totalDays:int+ Date(year:int,month:int,day:int)<<const>>+ getYear():int<<const>>+ getMonth():int<<const>>+ getDay():int<<const>>+getMaxDay():int<<const>>+isLeapYear():bool<<const>>+show()<<const>>+operator-(date:Date):intDate- lastDate:Date- value:double- sum:double+ Accumulator(date:Date,value:double)<<const>>+ getSum(date:Date):double+ change(date:Date,value:double)+ reset(date:Date,value:double)Accumulator# Account(date:Date,id:int)# record(date:Date,amount:double,desc:string)<<const>>#error(msg:string) <<const>>+ getId():int<<const>>+ getBalance():double<<abstract>>+deposit(date:Date,amount:double,desc:string)<<abstract>>+withdraw(date:Date,amount:double,desc:string)<<abstract>>+settle(date:Date)<<virtual>><<const>>+show()<<static>>+ getTotal():doubleAccount- id:string- balance:double- total:double1综合实例——对个人银行账户管理程序的改进//date.h#ifndef__DATE_H__#define__DATE_H__classDate //日期类{ private: intm_year; //年
intm_month; //月
intm_day; //日
intm_totalDays; //该日期是从公元元年1月1日开始的第几天public: Date(intyear,intmonth,intday); //用年、月、日构造日期
intgetYear()const{returnm_year;} intgetMonth()const{returnm_month;} intgetDay()const{returnm_day;} intgetMaxDay()const; //获得当月有多少天
boolisLeapYear()const //判断当年是否为闰年 {
returnm_year%4==0&&m_year%100!=0||m_year%400==0; } voidshow()const; //输出当前日期 //计算两个日期之间差多少天
intoperator-(constDate&date)const {
returnm_totalDays-date.m_totalDays; }};#endif//__DATE_H__综合实例——对个人银行账户管理程序的改进//accumulator.h#ifndef__ACCUMULATOR_H__#define__ACCUMULATOR_H__#include"date.h"classAccumulator //将某个数值按日累加{ private: Datem_lastDate; //上次变更数值的时期
doublem_value; //数值的当前值
doublem_sum; //数值按日累加之和public: doublegetSum(constDate&date)const { returnm_sum+m_value*(date-m_lastDate); } //该类其它成员函数的原型和实现与第四讲中例子完全相同,不再重复给出};#endif//__ACCUMULATOR_H__综合实例——对个人银行账户管理程序的改进//account.h#ifndef__ACCOUNT_H__#define__ACCOUNT_H__#include"date.h"#include"accumulator.h"#include<string>classAccount //账户类{private: std::stringm_id; //帐号
doublem_balance; //余额
staticdoubles_total;//所有账户的总金额protected: //供派生类调用的构造函数,id为账户
Account(constDate&date,conststd::string&id); //记录一笔帐,date为日期,amount为金额,desc为说明
voidrecord(constDate&date,doubleamount,conststd::string&desc); //报告错误信息
voiderror(conststd::string&msg)const;综合实例——对个人银行账户管理程序的改进public: conststd::string&getId()const{returnm_id;} doublegetBalance()const{returnm_balance;}
staticdoublegetTotal(){returns_total;} //存入现金,date为日期,amount为金额,desc为款项说明
virtualvoiddeposit(constDate&date,doubleamount,conststd::string&desc)=0; //取出现金,date为日期,amount为金额,desc为款项说明
virtualvoidwithdraw(constDate&date,doubleamount,conststd::string&desc)=0; //结算(计算利息、年费等),每月结算一次,date为结算日期
virtualvoidsettle(constDate&date)=0; //显示账户信息
virtualvoidshow()const;};//SavingsAccount和CreditAccount两个类的定义与第四讲中的例子完全相同,不再重复给出#endif//__ACCOUNT_H__综合实例——对个人银行账户管理程序的改进//account.cpp//与第四讲中相同,不再重复给出
//main.cpp#include"account.h"#include<iostream>usingnamespacestd;intmain(){ Datedate(2008,11,1); //起始日期
//建立几个账户
SavingsAccountsa1(date,"S3755217",0.015); SavingsAccountsa2(date,"02342342",0.015); CreditAccountca(date,"C5392394",10000,0.0005,50);
Account*accounts[]={&sa1,&sa2,&ca}; constintn=sizeof(accounts)/sizeof(Account*);//账户总数
cout<<"(d)deposit(w)withdraw(s)show(c)changeday(n)nextmonth(e)exit"<<endl; charcmd;综合实例——对个人银行账户管理程序的改进 do{ //显示日期和总金额
date.show(); cout<<"\tTotal:"<<Account::getTotal()<<"\tcommand>"; intindex,day
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年南靖县教师招聘笔试备考试题及答案解析
- 2026雅安职业技术学院附属医院第二批见习人员招募笔试备考试题及答案解析
- 2026珠海市妇幼保健院、华南理工大学附属珠海妇儿医院第三季度招聘劳动合同制工作人员12名笔试参考题库及答案解析
- 四川省第三人民医院2027年招聘笔试参考题库及答案解析
- 2026广东惠州仲恺高新区招聘区应急救援大队(森林消防大队)队员16人考试备考试题及答案解析
- 2027中国地震局事业单位公开招聘(188人!新疆有岗)笔试备考试题及答案解析
- 2026广东汕头大学精神卫生中心第四批招聘31人笔试模拟试题及答案解析
- 2026哈尔滨“丁香人才周”(秋季)事业单位引才招聘829人笔试备考试题及答案解析
- 2026年西安市人才协会招聘(5人)笔试模拟试题及答案解析
- 2026浙江杭州西湖大学生命科学学院何灵娟实验室科研助理招聘考试模拟试题及答案解析
- 2026年新闻记者职业资格证考试全真模拟试卷及答案
- 造船厂安全制度
- 26秋教科版三年级上册科学全册必背知识点新教材含答案
- 2026-2027学年高二语文上册第一次月考试卷原卷解析
- 成年人幽门螺杆菌感染诊断、治疗与预防临床实践指南(2026版)
- 4.RBT 089-2022绿色供应链管理手册
- LNG加气站人员培训方案
- 2026年中小学诗词竞赛诗词大会竞赛题库(含答案)
- 2026-2030中国矿山生态修复行业前景规划及投资潜力分析报告
- 《人工智能伦理》教学课件-2025-2026学年浙教版(新教材)初中信息技术九年级全册
- 《中药学·固精缩尿止带药》研究型教学设计(本科中医学专业三年级)
评论
0/150
提交评论