版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第七章多态性(polymorphism)7.1、编译时的多态性与运行时的多态性7.2、多态的思考方式7.3、函数重载7.4、运算符重载7.5、虚函数综合训练17.1、编译时的多态性与运行时的多态性1、多态性的概念多态性是面向对象程序设计的关键技术之一,若程序设计语言不支持多态性,不能称为面向对象的语言多态性就是不同(成员)函数使用同一个函数名,即同样的接口实现不同的操作扩展:多态性指不同类的对象对同一消息作出不同响应有继承关系运算符重载7.1、编译时的多态性与运行时的多态性2、联编多态性的实现与联编这一个概念有关一个源程序经过编译,连接,成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程
也就是一个程序自身彼此关联的过程7.1、编译时的多态性与运行时的多态性举例#include<iostream.h>
classPoint
{
public:
Point(doublei,doublej){x=i;y=j;}
doubleArea()const{return0.0;}
private:
doublex,y;
};
classRectangle:publicPoint
{
public:
Rectangle(doublei,doublej,doublek,doublel);
doubleArea()const{returnw*h;}
private:
doublew,h;
};7.1、编译时的多态性与运行时的多态性Rectangle::Rectangle(doublei,doublej,doublek,doublel)
:Point(i,j)
{
w=k;h=l;
}
voidfun(Point&s)//外部函数
{
cout<<s.Area()<<endl;
}
voidmain()
{
Rectanglerec(3.0,5.2,15.0,25.0);
fun(rec);
}
调用哪个Area函数?上述联编过程存在问题,没有按我们的期望进行联编体会联编的含义不同对象point、rectangle具有相同接口Area()体会多态性的含义参数改为:
Rectangle&s如果Point派生:
Circle7.1、编译时的多态性与运行时的多态性3、静态联编和动态联编根据联编所执行的阶段分为:静态联编:在编译阶段进行联编,速度快、效率高,但不灵活(含义?)动态联编:在程序运行阶段进行联编,即直到程序运行时在确定调用哪个函数,灵活性、可维护性在C++中,编译时的多态性是通过函数重载和运算符重载实现的,运行时的多态性是通过继承和虚函数来实现的7.2、多态的思考方式多态性允许程序员通过向一个对象发送消息来完成一系列动作,无需涉及软件系统如何实现这些动作Area()Area()Rectangle对象Point对象发送消息(调用)Area()继承7.2、多态的思考方式举例实现一个计算机绘图的程序:
假设有一个Cricle类,一个Square类。每个类都是一个Shape,并且都有一个成员函数Draw(),该函数负责将不同的图形显示在屏幕上7.2、多态的思考方式程序的伪代码如下:
#include<iostream.h>
enum
ShapeType{SHAPE,CIRCLE,SQUARE};//将图形形状定
//义为枚举类型
classShape
{
public:
Shape()
{
type=SHAPE;
}
ShapeTypetype;
voiddraw(){}//Shape不是具体的图形,draw()什么也不做
};7.2、多态的思考方式classCircle:publicShape//定义Circle类
{
public:
Circle()
{
type=CIRCLE;//覆盖刚赋值的type
}
voiddraw()
{
cout<<“drawcircle”<<endl;//……完成Circle图形的绘制
}
};
7.2、多态的思考方式classSquare:publicShape//定义Square类
{
public:
Square()
{
type=SQUARE
}
voiddraw()
{
cout<<"drawSquare"<<endl;//完成Square的绘制工作
}
//….
};
7.2、多态的思考方式voiddrawShape(Shape&s)//图形绘制的外部函数
{
//…
switch(s.type)//判断对象的type,决定调用哪个draw()函数
{
caseCIRCLE:
s.Circle::draw();break;
caseSQUARE
s.Square::draw();break;
//…
}
}
7.2、多态的思考方式分析在一个绘图程序中,类似的图形成员不止两个,都要进行类似的处理。而且还可能会增加新的图形类,例如要增加一个Traingle类,我们需要对drawType函数进行修改,使它能够支持新增加的类型,这样应用程序的维护量很大,面向对象的优越性被遏制,又回到面向过程程序设计的老路上去了。我们可以使用多态机制来必免使用上述的代码。7.3、函数重载函数重载:用同一个名字访问一组相关的函数。也就是说,能使用户为某一类操作取一个通用的名字,而由编译程序来选择具体由哪个函数来执行;意义:因而有助于解决程序的复杂性问题。7.3、函数重载#include<iostream.h>
voidprint(intn)//定义函数print
{cout<<n<<endl;}
voidprint(constchar*str)//重载函数print
{cout<<str<<endl;}
intmain()
{
print(4);//调用voidprint(intn)
print("helloworld");//调用voidprint(constchar*)
return0;
}7.3、函数重载对于重载函数,编译程序能够通过参数选择具体由哪个函数来执行。在类中,普通成员函数和构造函数都可以重载,特别是构造函数的重载(它提供了多种初始化方式)给用户以更大的灵活性在基类和派生类中的函数重载有两种情况:一种是函数参数有所差别的重载。另一种是函数所带的参数完全相同,只是它们属于不同的类(基类和派生类)。可以使用对象名加以区分和使用”类名::”加以区分7.4、运算符重载1、运算符重载的基本概念2、成员运算符重载3、友元运算符重载4、值返回和引用返回5、增量运算符的重载6、转换运算符重载7、赋值运算符重载7.4、运算符重载1、运算符重载的基本概念定义:赋予已有的运算符多重含义。
C++中通过重新定义运算符,使它能够用于特定类的对象执行特定的功能。这样就增强了C++语言的扩充能力用途:运算符重载所定义的操作通常作用在一个类上,来执行对这个对象的运算目的:使C++代码更直观,易读。
例如:4+5就比add(4,5)更加直观7.4、运算符重载语法:
类型[类名::]operator重载的运算符(参数)
{
函数相关操作
}
[]表示可选的;在运算符重载时如果有类名,就是成员运算符函数,否则就是友元运算符函数。7.4、运算符重载#include<iostream.h>
classInteger//大写
{
public:
Integer(inti=0)
{
val=i;
}
Integeroperator+(constInteger&a)//重载运算符+
{
returnInteger(val+a.val);
}
voidprint()const
{
cout<<“value=”<<val<<endl;
}
private:
intval;
};生成、返回匿名对象谁的val相加?7.4、运算符重载voidmain()
{
Integera(5),b(3),c;
cout<<"objecta";
a.print();
cout<<"objectb";
b.print();
c=a+b;
cout<<"objectc";
c.print();
}
程序运行结果:objectavalue=5objectbvalue=3objectcvalue=8相当于”a.+”,调用a的成员函数+因为此处的+本质上是单目运算符,形式上为双目,与之前保持不变也是静态联编7.4、运算符重载分析:
上例中为Integer重载了运算符“+”,使“+”能够使用于Integer类的对象。运算符重载是通过编写函数定义实现的。函数定义包括函数首部和函数体,但是函数名是由关键字operator和其后的要重载的运算符号组成7.4、运算符重载在C++中:几乎所有的运算符都可用作重载,包括:
算术运算符:+,-,*,/,%,++,――
位操作运算符:&,|,~,^,<<,>>
逻辑运算符:!,&&,||
比较运算符:>,<,>=,<=,==,!=
赋值运算符:=,+=,-=,*=,/=,%=,&=,|=,<<=,>>=
其他运算符:[],(),->,new,delete,new[],delete[],->*,甚至int等注意:1、运算符是在C++系统内部定义的,它们的运算顺序、优先级、目数在重载过程中都不改变2、有一些运算符是不能重载的,例如:
.,.*,::,?:,#,##
7.4、运算符重载唯一的三目运算符7.4、运算符重载2、成员运算符重载运算符重载包括两种形式:成员函数重载和友元运
算符重载成员函数重载:将运算符重载函数定义为类中的成员函数友元运算符重载:将运算符重载函数定义为类中的友元函数7.4、运算符重载成员运算符函数声明和定义的格式如下:
声明的格式:
classX{
…
typeoperator@(参数表);
};
定义的格式:
TypeX::operator@(参数表)
{
函数体
}
其中type是函数的返回类型,@是要重载的运算符也可以写成内联形式7.4、运算符重载#include<iostream.h>
classthree_d
{
private:
intx; //x坐标
inty; //y坐标
intz; //z坐标
public:
three_d(inti,intj,intk); //构造函数
three_d(); //默认构造函数
three_doperator+(three_dc);//成员方式重载+运算符
voidshow(); //显示结果
};
7.4、运算符重载three_d::three_d(inti,intj,intk)//构造函数
{
x=i; //给x赋值
y=j; //给y赋值
z=k; //给z赋值
}
three_d::three_d()//默认构造函数
{
x=0; //初始化x
y=0; //初始化y
z=0; //初始化z
}
7.4、运算符重载//重载+三维坐标的相加使用成员运算符重载
three_dthree_d::operator+(three_dc)
{
three_dtmp; //定义临时变量
tmp.x=x+c.x; //取得x坐标
tmp.y=y+c.y; //取得y坐标
tmp.z=z+c.z; //取得z坐标
returntmp; //返回
}
//显示坐标
voidthree_d::show()
{
cout<<“x=”<<x<<“y=”<<y<<“z=”<<z<<endl;
//显示x,y,z坐标
}如何实现局部对象的返回?访问权限?7.4、运算符重载//主函数
intmain()
{
three_dob1,ob2(10,11,12),ob3(11,21,31);//定义3个对象
cout<<"-------ob1,ob2.ob3-------\n";
ob1.show(); //显示第一个对象
ob2.show(); //显示第二个对象
ob3.show(); //显示第三个对象
ob1=ob2+ob3; //利用重载+
cout<<"-----ob1=ob2+ob3,--------\n";
ob1.show(); //显示第一个对象
return0;
}
7.4、运算符重载3、友元运算符重载运算符声明的格式:
classX{
…
friendtypeoperator@(参数表);
};定义的格式:
Typeoperator@(参数表)
{
函数体
}其中type是函数的返回类型,@是要重载的运算符7.4、运算符重载成员运算符函数与友元运算符函数的区别友元运算符函数:(1)不属于任何类对象;(2)它没有this指针;(3)若重载的是双目运算符,则参数表中有两个操作数,如果重载的是单目运算符,则参数表中有一个操作数。成员运算符函数:(1)属于一个类对象;(2)它有this指针;(3)若重载的是双目运算符,则参数表中有一个操作数,如果重载的是单目运算符,则参数表中没有操作数(隐含this)为什么成员运算符函数少一个参数?7.4、运算符重载#include<iostream.h>
classthree_d
//定义类
{
private:
intx;
//x坐标
inty;
//y坐标
intz; //z坐标
public:
three_d(inti,intj,intk); //构造函数
three_d(); //默认构造函数
friendthree_doperator+(three_dc,three_dd);
//重载+使用友元
voidshow(); //显示结果
};
7.4、运算符重载three_d::three_d(inti,intj,intk)//构造函数
{
x=i; //给x赋值
y=j; //给y赋值
z=k; //给z赋值
}
three_d::three_d()//默认构造函数
{
x=0; //初始化x
y=0; //初始化y
z=0; //初始化z
}
7.4、运算符重载//重载+三维坐标的相加使用友元
three_doperator+(three_dc,three_dd)
{
three_dtmp; //定义临时变量
tmp.x=c.x+d.x; //取得x坐标
tmp.y=c.y+d.y; //取得y坐标
tmp.z=c.z+d.z; //取得z坐标
returntmp; //返回
}
//显示坐标
voidthree_d::show()
{
cout<<"x="<<x<<"y="<<y<<"z="<<z<<endl;
//显示x,y,z坐标
}
7.4、运算符重载//主函数
intmain()
{
three_dob1,ob2(10,11,12),ob3(11,21,31);//定义3个对象
cout<<"-------ob1,ob2.ob3-------\n";
ob1.show(); //显示第一个对象
ob2.show(); //显示第二个对象
ob3.show(); //显示第三个对象
ob1=ob2+ob3; //利用重载+
cout<<"-----ob1=ob2+ob3,--------\n";
ob1.show(); //显示第一个对象
return0;
}
7.4、运算符重载4、值返回和引用返回运算符重载过程中有两种情况:
(1)返回值的类型是值类型;
(2)返回值的类型是引用;
这两者有什么区别呢?看下面的例子:把对象的(数据成员)值返回把对象本身返回7.4、运算符重载#include<iostream.h>
classRMB{
public:
RMB(unsignedintd,unsignedintc);//构造函数
friendRMBoperator+(RMB&,RMB&);//重载+
friendRMB&operator++(RMB&);//重载++
voiddisplay()
{
cout<<(yuan+f/100.0)<<endl;
}
protected:
unsignedintyuan;
unsignedintf;
};
能否定义为成员运算符函数:RMBoperator+(RMB&,RMB&);7.4、运算符重载RMB::RMB(unsignedintd,unsignedintc)//构造函数
{
yuan=d;
f=c;
while(f>=100)//规整人民币单位,分不超过100
{
yuan++;
f-=100;
}
}
RMB
operator+(RMB&s1,RMB&s2)//两个不同数目的人民币相加
{
unsignedintf=s1.f+s2.f;
unsignedintyuan=s1.yuan+s2.yuan;
RMBresult(yuan,f);//使用构造函数规整相加后的结果
returnresult;//返回值
}7.4、运算符重载RMB&operator++(RMB&s)//定义重载运算符++
{
s.f++;
if(s.f>=100)
{
s.f-=100;
s.yuan++;
}
returns;//返回引用
}
voidmain(){//主函数
RMBd1(1,60);
RMBd2(2,50);
RMBd3(0,0);
d3=d1+d2;
++d3;
d3.display();
}程序运行结果:4.117.4、运算符重载7.4、运算符重载7.4、运算符重载采用值返回或引用返回的原则:
1、是否改变自身(或者实参是否改变)
2、局部对象不能返回引用
3、返回引用的函数名可以做左值
作左值的例子:#include<iostream.h>
classCA
{
public:
CA(intx=0,inty=0):a(x),b(y){};
inta,b;
CA&operator+=(constCA&rhs)
{
this->a+=rhs.a;
this->b+=rhs.b;
return*this;
}
CAoperator+(constCA&rhs)
{
intx=a+rhs.a;
inty=a+rhs.b;
CAret(x,y);
returnret;
}This->可省略吗?作左值的例子:
voiddisp()
{
cout<<a<<“”<<b<<endl;
}
};
voidmain()
{
CAc_one(1,1),c_two(2,2),c_three;
c_three=c_one+c_two;
c_three+=c_two;//!!
c_three.disp();
(c_three+=c_two)=c_one; //把c_one的值赋给c_three
c_three.disp();
}
作左值的例子:理解(1)两个函数没有返回值行不行?(2)返回对象和返回引用存在区别运算符重载函数一般需要返回对象或引用根据需要决定根据实现决定7.4、运算符重载运算符重载的注意事项(比较成员、友元运算符函数)(1)对于二元运算符,成员运算符函数带一个参数,而友元运算符函数带两个参数;对于一元运算符,成员运算符函数不带参数,而友元运算符带一个参数(2)C++的大部分运算符既可以声明为成员运算符函数,又可以声明为友元运算符函数,具体选择哪一种情况,主要取决于实际情况和程序员的习惯7.4、运算符重载(3)当运算符函数是一个成员函数时,最左边的操作数(或者只有左边的操作数)必须是运算符类的一个对象(或者是该类的一个引用)。如果左边的操作数必须是一个不同类的对象或者是一个内部类的对象,该运算符函数必须作为一个友元函数实现(4)在重载运算符()、[]、->或者任何赋值运算符时,运算符重载函数必须声明为类的一个成员(5)编译程序对运算符重载选择,遵循着函数重载的选择。当遇到不很明显的运算符时,编译程序将去寻找参数匹配的运算符函数系统已定义的类7.4、运算符重载(6)重新定义运算符,不改变原运算符的优先级和结合性。并且运算符重载后,也不改变运算符的语法结构,即单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符(7)不可臆造新的运算符。必须把重载运算符限制在C++语言中已经有的运算符范围允许重载的运算符中(8)重载运算符的含义必须清楚;重载运算符不能有二义性7.4、运算符重载5、增量运算符的重载(减量运算法类似)#include<iostream.h>
classInteger{//Integer类声明
intvalue;
public:
Integer(intx)
{
value=x;
}
Integer&operator++();//前置增量运算符
Integeroperator++(int);//后置增量运算符
friend
ostream&operator<<(ostream&,constInteger&);//重载<<运算符
};原因:对对象进行++内部类新功能:输出对象双目运算符两个:前缀、后缀要不要参数:?要不要返回:?返回引用和对象的区别:?可以是成员,也可是友元7.4、运算符重载Integer&Integer::operator++()//定义前置增量运算符重载函数
{
value++;//先增量
return*this;//返回原有对象
}
IntegerInteger::operator++(int)//定义后置增量运算符重载函数
{
Integertemp(value);//临时对象保存原有对象值
value++;//原有对象增量修改
returntemp;
}
有this指针Int
aaa也行7.4、运算符重载ostream&operator<<(ostream&output,constInteger&a)//定义<<运算符
{
output<<"Thevalueis"<<a.value;
returnoutput;
}
intmain()
{
Integera(20);
cout<<a<<endl;
cout<<(a++)<<endl;
cout<<a<<endl;
cout<<(++a)<<endl;
return0;
}Cout<<“Thevalueis”<<a.value;程序输出结果:Thevalueis20Thevalueis20Thevalueis21Thevalueis22要不要返回:?能不能定义为成员函数?7.4、运算符重载在C++中,前置增量运算符和后置增量运算符的意义是不同的:
使用前置增量时,对对象进行增量修改,然后再返回该对象。所以前置增量运算符操作时,参数与返回的是同一对象。
使用后置增量时,必须在增量返回之前返
回原有的对象值。后置增量操作返回的是原有对象值,不是原有对象,原有对象已经被增量修改。7.4、运算符重载6、转换运算符重载赋值、计算、给函数传值以及从函数返回值都可能会进行类型转换对于内部数据类型,编译器知道如何转换类型。程序员可以用强制类型转换运算符实现内部类型之间的转换对于用户自定义数据类型,可以使用以下两种途径实现类型转换:
(1)通过构造函数进行类型转换
(2)通过类型转换函数进行类型转换7.4、运算符重载#include<iostream.h>
classInteger{//Integer
public:
Integer(inti);//类型转换构造函数
friendostream&operator<<(ostream&,constInteger&);//重载<<运算符
private:
intvalue;
};
通过构造函数进行类型转换:
实现整型数
对象的转换7.4、运算符重载Integer::Integer(inti)//定义类型转换函数
{
cout<<"Typeconvertconstructor"<<endl;
value=i;
}
ostream&operator<<(ostream&output,constInteger&a)
{
output<<"Integervalue="<<a.value;
returnoutput;
}
7.4、运算符重载voidmain()
{
Integera=Integer(3);//显式调用类型转换构造函数
cout<<a<<endl;
Integerb=6;//隐式调用类型转换构造函数
cout<<b<<endl;
}
程序运行结果:TypeconvertconstructorIntegervalue=3TypeconvertconstructorIntegervalue=67.4、运算符重载#include<iostream.h>
classInteger{//Integer类声明
public:
Integer(inti=0);//类型转换构造函数
friendostream&operator<<(ostream&,constInteger&);
//重载<<运算符
operatorint();//重载类型转换运算符
private:
intvalue;
};类型转换函数进行类型转换含义:它原来就是类型转换运算符现在是新含义的类型转换运算符没有返回类型、参数7.4、运算符重载Integer::Integer(intx)
{
cout<<"Integerconstructor"<<endl;
value=x;
}
Integer::operator
int()
{
cout<<"Typechangedtoint"<<endl;
returnvalue;
}
7.4、运算符重载ostream&operator<<(ostream&output,constInteger&a)
{
output<<“Integervalue=”<<a.value;
returnoutput;
}
voidmain()
{
Integera(5),b(3),c;
cout<<a<<endl;
cout<<int(a)*2<<endl;//显式转换
c=a+b;
//隐式转换
cout<<c<<endl;
}7.4、运算符重载程序运行结果:
Integerconstructor
Integerconstructor
Integerconstructor
Integervalue=5
Typechangedtoint
10
Typechangedtoint
Typechangedtoint
Integerconstructor
Integervalue=8
构造了什么对象?7.4、运算符重载类型转换函数声明的一般格式为:
operator类型名();
(1)类型转换运算符将对象转换成类型名规定的类型。转换形式就像强制类型转换一样。
(2)如果没有重载类型转换运算符,直接使用强制转换是不行的。因为强制转换运算符只能对基本数据类型进行操作,对自定义类型的操作没有定义
(3)没有返回类型,因为类型名就代表了它的返回类型,故返回类型显得多余。7.4、运算符重载重载类型转换运算符的注意事项:(1)类型转换运算符重载函数只能定义为类的成员函数,不能定义为类的友元函数(2)类型转换运算符重载函数既没有参数,也没有返回类型(3)类型转换运算符重载函数中必须把目的类型的数据做为函数的返回值(4)一个类可以定义多个类型的类型转换函数,C++编译器根据操作数的类型自动选择一个合适的类型转函数与之匹配,在可能出现歧义的情况下,必须显式地使用相应类型的类型转换函数进行转换7.4、运算符重载7、赋值运算符重载一般情况,用于类的对象的运算符必须重载但是赋值运算符(=)无需重载就可以用于每一个类。在不提供重载的赋值运算符时,赋值运算符的默认行为是复制对象的数据成员,比如前例通常情况下,默认的赋值运算符操作是能胜任工作但有些应用中,仅使用默认的赋值运算符的操作不够,还需要根据用户的情况对赋值运算符进行重载。
有时使用默认的赋值运算符会出现不能正常工作的情况,此时,程序员必须自己实现赋值运算符重载。7.4、运算符重载#include<iostream.h>
#include<string.h>
#include<assert.h>
classString//大写
{
public:
String(constchar*s)
{
ptr=newchar[strlen(s)+1];
assert(ptr!=0);//确保内存分配成功
strcpy(ptr,s);
len=strlen(s);
}
7.4、运算符重载~String()
{
delete[]ptr;
}
friendostream&operator<<(ostream&output,constString&s);//重载<<运算符
private:
char*ptr;
int
len;
};
ostream&operator<<(ostream&output,constString&s)//重载<<运算符
{
output<<s.ptr;
returnoutput;
}7.4、运算符重载voidmain()
{
Strings1("test1");
{
Strings2("test2");
s2=s1;
cout<<"s2:"<<s2<<endl;
}
cout<<"s1:"<<s1<<endl;
}
赋的什么值?7.4、运算符重载上述程序虽然能够正确编译,但是该程序运行时会发生指针错误。
因为我们没有为String类重载赋值运算符,当程序执行到语句s2=s1时,使用默认的赋值运算符操作,将对象s1的数据成员逐个拷贝到对象s2中。此时s2和s1中的指针成员ptr指向同一块内存空间。当s2的生存期(main()函数内层的一对花括号间)结束时,编译器调用析构函数将这一内存空间回收。此时,尽管对象s1的成员ptr存在,但其指向的空间却无法访问了7.4、运算符重载#include<iostream.h>
#include<assert.h>
#include<string.h>
classString//大写
{
public:
String(constchar*s)
{
ptr=newchar[strlen(s)+1];
assert(ptr!=0);//确保内存分配成功
strcpy(ptr,s);
len=strlen(s);
}7.4、运算符重载~String()
{
delete[]ptr;
}
voiddisp(){cout<<ptr<<endl;};
String&operator=(constString&s)
{
cout<<"oveload"<<endl;
if(ptr)delete[]ptr;
ptr=newchar[strlen(s.ptr)+1];
strcpy(ptr,s.ptr);
len=strlen(s.ptr);
return*this;
}
7.4、运算符重载private:
char*ptr;
int
len;
};
voidmain()
{
Strings1("test1");
Strings2=s1;
s2.disp();
s2=s1;
s2.disp();
}
修改主函数:voidmain()
{
Strings1("test1");
Strings2("test2");
s2.disp();
s2=s1;
s2.disp();
}7.4、运算符重载赋值运算符重载的注意事项:(1)类的赋值运算符"="只能定义为类的成员函数,不能定义为类的友元函数(2)C++编译器默认为每个类重载了赋值运算符"=",其默认行为是复制对象的数据成员。但有时程序员必须自己实现赋值运算符重载,否则会出现错误(3)重载赋值运算符"="时,赋值运算符函数的返回类型应是类的引用,这与赋值的语义相匹配。因为C++中要求赋值表达式左边的表达式是左值(4)赋值运算符可以被重载,但重载了的运算符函数operator
=()不能被继承,所有都不能继承7.5、虚函数1、引入派生类后的对象指针2、虚函数定义3、虚函数和重载函数的关系4、纯虚函数和抽象类动态联编实现的多态不同类的对象对同一消息作出不同响应便于程序扩充7.5、虚函数1、引入派生类后的对象指针公有派生类的对象可以作为其基类的对象处理比如:基类
Point,派生类Rectangle、Circle
函数voidfun(Point&s)但是,反过来是不行的,基类的对象不能自动成为派生类的对象总结:派生类对象也是基类对象,可以实现一般性处理(在公有派生时)7.5、虚函数通常通过将派生类对象指针转换为基类指针来实现一般性处理举例:例7.15通俗地讲:可以用基类指针指向一个派生类的对象(在公有派生时)隐式转换7.5、虚函数引入派生类指针后,对象指针的注意事项:1、对于public继承,总是可以将派生类指针赋给基类指针1,因为派生类对象也是基类对象。基类指针只"看到"派生类对象的基类部分。编译器进行派生类指针向基类指针的隐式转换见上例2、反之,因为把基类指针直接赋给派生类指针蕴含着危险性,所以编译器不允许这么做,也不执行隐式类型转换
即:不能用派生类指针指向基类对象7.5、虚函数3、声明为指向基类的指针,当它指向公有派生类对象时,只能用它来访问派生类中从基类继承来的成员,不能访问派生类中定义的成员。因为编译器认为该指针指向的是基类对象4、对于private继承,不允许将声明为指向基类对象的指针指向它的派生类的对象7.5、虚函数2、虚函数定义虚函数就是在基类中被关键字virtual说明,并在派生类中重新定义的函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数与参数类型的顺序,都必须与基类中的原型完全相同7.5、虚函数#include<iostream.h>
classparent{
protected:
charversion;
public:
parent()
{
version='A'; }
virtualvoidprint()//定义虚函数print()
{
cout<<"\nTheparent.version"<<version;
}
};
7.5、虚函数classderived1:publicparent{
private:
intinfo;
public:
derived1(intnumber)
{
info=number;
version='1';
}
voidprint()//重新定义虚函数print()
{
cout<<"\nThederived1info:"<<info
<<"version"<<version;
}
};省略“:parent()”7.5、虚函数intmain()
{
parentob,*op;//声明了基类的对象和指针
op=&ob;
op->print();//通过基类指针调用基类的parent的print()
derived1d1(3);
op=&d1;
op->print();//通过基类指针调用派生类的derived1的print()——多态
return0;
}程序中语句op->print();出现了两次,由于op指向的对象不同,每次出现都执行了print()的不同的版本。程序运行的结果为:
Theparent.VersionA
Thederived1info:3version17.5、虚函数虚函数定义与使用的注意事项:1、只有类的成员函数才能声明为虚函数,因为虚函数仅适用于有继承关系的类对象,普通函数不能声明为虚函数2、在派生类中对基类声明的虚函数进行重定义时,关键字virtual可以写也可以不写3、不能把静态成员函数声明为虚函数,因为静态成员函数不属于某个对象,而虚函数是通过对象指针或引用来调用的7.5、虚函数4、构造函数不能是虚函数,因为执行构造函数时,对象还没有实例化。但析构函数可以是虚函数5、在派生类中重定义虚函数时,函数的原型必须与基类中的函数原型完全相同6、定义了虚函数后,我们通过使用基类指针或引用指明派生类对象并使用该指针或引用来调用虚函数的方式来实现动态关联。
虽然可以使用对象名和点运算符的方式来调用虚函数,但是这种调用是在编译时进行的静态关联,它没有充分利用虚函数的特性7.5、虚函数3、虚函数和重载函数的关系在派生类中重新定义基类的虚函数是函数重载另一种形式,但它不同于一般的函数重载(1)当普通的函数重载时,其函数的参数或参数类型必须有所不同,函数的返回类型也可以不同;
但是,当重载一个虚函数时,也就是说在派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同(2)多态的实现不同7.5、虚函数4、纯虚函数和抽象类纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,但要求在它的派生类中定义自己的版本,或重新说明为纯虚函数纯虚函数的一般形式如下:
virtualtypefunc_name(参数表)=0;
这里,type是函数的返回类型,func_name是函数名,此形式与一般的虚函数的形式相同,只是在后面多了“=0”
7.5、虚函数#include<iostream.h>
classcircle{//声明类circle
protected:
intr;//圆的半径
public:
voidsetr(intx)//设定圆的半径
{r=x; }
virtualvoidshow()=0;//定义纯虚函数show()
};
//声明派生类area——从circle类派生
classarea:publiccircle{
public:
voidshow()//重新定义虚函数show()
{cout<<"areais"<<3.14*r*r<<endl; }
};7.5、虚函数//声明派生类perimeter——从circle类派生
classperimeter:publiccircle{//周长
public:
voidshow() //重新定义虚函数show()
{
cout<<"perimeteris"<<2*3.14*r<<endl;
}
};
voidmain()
{
circle*ptr;//声明基类circle指针
areaob1;//声明派生类area对象
perimeterob2;//声明派生类perimeter对象7.5、虚函数
ob1.setr(10);//调用基类的方法设定对象的属性
ob2.setr(10);//调用基类的方法设定对象的属性
ptr=&ob1;//初始化基类指针
ptr->show();//多态:指向基类的指针调用area类的成员函数
ptr=&ob2;
ptr->show();//多态:指向基类的指针调用perimeter类的成员函数
}7.5、虚函数
在以上的例子中,circle是一个基类,它表示一个圆。从它可以派生出面积类area和周长类perimeter。
显然,基类中定义的show()函数是没有任何意义的,它只是用来提供派生类使用的公共接口,所以程序中将其定义为纯虚函数,但在派生类中,则根据它们自己的需要,具体地重新定义虚函数。编程过程中常常有这样的情况,设计的基类是一个很抽象的概念,如形状。其中计算面积的成员函数不能够完全实现,只能声明为纯虚函数,而不必要定义函数体。7.5、虚函数含有纯虚函数的被称为抽象类:
classshape{//抽象类
virtualfloatarea()=0;//纯虚函数
};设计抽象类的目的是为了多态地使用它的成员函数;
对于抽象类是不能实例化的。必须通过继承得到派生类后,在派生类中对纯虚函数进行了定义,再获得派生类的对象7.5、虚函数抽象类和纯虚函数的使用总结:1、抽象类只能用作其他类的基类,不能实例化,必须通过继承得到派生类后,在派生类中对纯虚函数进行了定义,再获得派生类的对象2、抽象类中至少包含一个未定义功能的纯虚函数设计抽象类的目的是为了多态地使用它的成员函数3、因为抽象类不能表示具体的类型,因此,抽象类不能用作参数类型、函数返回类型或进行显示类型转换的类型。一般声明为抽象类的指针或引用,该指针指向它的派生类,从而实现多态性7.5、虚函数4、纯虚函数是在基类中声明的虚函数,并不在基类中进行定义。如果在派生类中,并没有对其抽象基类中的纯虚函数进行定义,则该虚函数在派生类中仍为纯虚函数,而该派生类也是一个抽象类5、在抽象基类中除了声明纯虚函数以外,还可以定义普通成员函数和虚函数。可以通过派生类的对象来访问它们本章小结本章介绍了C++中关于多态性机制的相关知识。多态性使得程序内需设计和实现易于扩展和维护,程序可以对层次中所有现有类的对象进行一般性处理。多态性是通过虚函数机制来实现的,我们对虚函数的定义,使用进行了详细的介绍。同时介绍了抽象类和纯虚函数在实际设计中的作用,并结合具体的例子介绍了抽象类和纯虚函数的定义和使用。
运算符重载在一定程度上提高了程序的可读性,本章我们对运算符重载的定义和使用进行了介绍,并结合例子说明了几种常用运算符重载的实现。正确的使用运算符可以增加程序的可读性,但不合理的或过度的使用运算符重载会使程序语义不清且难以阅读。综合训练1题目:
利用虚函数实现的多态性来求四种几何图形的面积之和。这四种几何图形是:三角形、矩形、正方形和圆。
几何图形的类型可以通过构造函数或通过成员函数来设置综合训练1分析:1、定义一个包含两个虚函数的类:
class
Shape{
public:
virtual
float
Area(
void)
=0;//求面积
virtual
void
Setdata(float
,float
=0)
=0;//设置图形数据
};因为Shape的形状是抽象的,无法计算面积和设置形状数据综合训练12、定义一个计算面积之和的类,该类中定义一个指向基类Shape的指针数组,其元素分别指向由基类Shape派生出的不同的几何图形类,并完成求出所有几何图形面积之和,以及设置参数的函数。为什么定义指针数组?综合训练1classShape{
public:
virtualfloatArea(void)=0; //虚函数
virtualvoidSetdata(float,float=0)=0;//虚函数
};
classTriangle:publicShape{
floatW,H; //三角形边长为W,高为H
public:
Triangle(floatw=0,floath=0)
{W=w;H=h;}
floatArea(void) //定义虚函数
{returnW*H/2;}
voidSetdata(float
w,floath=0)//定义虚函数
{
W=w;H=h;
}
};综合训练1classRectangle:publicShape{
floatW,H; //矩形边长为W,高为H
public:
Rectangle(floatw=0,floath=0){W=w;H=h;}
floatArea(void)//定义虚函数
{
returnW*H;
}
voidSetdata(float
w,floath=0)//定义虚函数
{
W=w;H=h;
}
};
综合训练1classSquare:publicShape{
floatS;//正方形边长S
public:
Square(floata=0)
{
S=a;
}
floatArea(void)//定义虚函数
{
returnS*S;
}
voidSetdata(floatw,floath=0)//定义虚函数
{
S=w;
}
};综合训练1classCircle:publicShape{
floatR;//圆的半径为R
public:
Circle(floatr=0)
{
R=r;
}
floatArea(void)//定义虚函数
{
return3.1415926*R*R;
}
voidSetdata(floatw,floath=0)//定义虚函数
{
R=w;
}
};综合训练1classCompute{
Shape**s;
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 辐射安全管理题库(含答案)
- 民房房屋购买合同范例
- 数据结构与算法复习题
- 特约顾问聘用合同范例
- 工程咨询合同范例内容
- 员工宿舍合租合同范例
- 合同范例生效
- Unit1《Greetings》(教学实录)-2024-2025学年沪教版(五四制)(2024)英语一年级上册
- 市场开发咨询合同范例
- 律师委托协议合同范例
- 妇产科运用PDCA降低产后乳房胀痛发生率品管圈成果报告书
- 山东省建筑面积计算规则
- 福费廷业务流程
- 初中毕业证书怎么查询电子版
- 垃圾焚烧发电厂消防系统安装施工方案
- 加油站安全生产例会制度安全生产
- 中心小学综合楼建设项目可行性研究报告
- 工艺管廊架施工方案
- 《可爱的中国读》书分享会PPT课件(带内容)
- 2023行政执法人员考试题库及答案
- GB/T 6581-2007玻璃在100℃耐盐酸浸蚀性的火焰发射或原子吸收光谱测定方法
评论
0/150
提交评论