《《多态性和虚函数》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《多态性和虚函数》PPT课件.ppt(53页珍藏版)》请在金锄头文库上搜索。
1、C+程序设计程序设计 第第9章运算符重载与虚函数章运算符重载与虚函数 C+C+程序设计程序设计CC的输入输出流的输入输出流2主要内容主要内容C+C+程序设计程序设计CC的输入输出流的输入输出流3多态性的概念及其表现形式顾名思义,多态的意思是一个事物有多种形态。从程序设计的顾名思义,多态的意思是一个事物有多种形态。从程序设计的角度看,通常指对于同一个消息、同一种调用,在不同的场合,不角度看,通常指对于同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为。例如运算符的重载和虚函数。同的情况下,执行不同的行为。例如运算符的重载和虚函数。 多态性是指发出同样的消息被不同类型的对象接收时导
2、致完全多态性是指发出同样的消息被不同类型的对象接收时导致完全不同的行为。不同的行为。 消息消息主要指对类的成员函数的调用。主要指对类的成员函数的调用。 C+ 系统支持二种多态性:系统支持二种多态性:1.静态多态性静态多态性编译时的多态性,其具体实现主要有:函数重编译时的多态性,其具体实现主要有:函数重载(详见载(详见5.5.2,本章不再介绍)和运算符重载,本章不再介绍)和运算符重载2.动态多态性动态多态性运行时的多态性,其具体实现主要有:虚函数运行时的多态性,其具体实现主要有:虚函数C+C+程序设计程序设计CC的输入输出流的输入输出流4运算符重载的规则与方式运算符重载的规则与方式 我们为什么要
3、运算符重载呢?我们为什么要运算符重载呢? 这是因为这是因为C+预定义的运算符只是对基本数据类型进行操作,预定义的运算符只是对基本数据类型进行操作,而对于自定义的数据类型,比如类,却没有类似的操作。为了实现而对于自定义的数据类型,比如类,却没有类似的操作。为了实现对自定义类型的操作,就必须自己编写程序来说明某个运算符作用对自定义类型的操作,就必须自己编写程序来说明某个运算符作用在这些数据类型上时,应该完成怎样的操作,这就要引入运算符重在这些数据类型上时,应该完成怎样的操作,这就要引入运算符重载的概念。载的概念。运算符重载:运算符重载: 就是对已有的运算符赋予多重含义。就是对已有的运算符赋予多重含
4、义。必要性:必要性:C+中预定义的运算符其运算对象只能是基本数据类型,而不适用中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)。实现机制:将指定的运算表达式转化于用户自定义类型(如类)。实现机制:将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。编译为对运算符函数的调用,运算对象转化为运算符函数的实参。编译系统对重载运算符的选择,遵循函数重载的选择原则,也就是说,系统对重载运算符的选择,遵循函数重载的选择原则,也就是说,运算符的重载实际上就是函数重载的一种特例。运算符的重载实际上就是函数重载的一种特例。 C+C+程序设计程序设计CC的输入
5、输出流的输入输出流5运算符重载的规则与方式运算符重载的规则与方式-重载运算符的规则重载运算符的规则 1.规则和限制规则和限制(1)可以重载)可以重载C+中除下列中除下列5个运算符外的所有运算符:个运算符外的所有运算符:、:、?:、:、?:、sizeof()(2)只能重载)只能重载C+语言中已有的运算符,不可臆造新的。语言中已有的运算符,不可臆造新的。(3)不能改变原运算符的优先级和结合性。)不能改变原运算符的优先级和结合性。(4)运算符重载以后的功能应与原有功能类似,含义必须清楚,)运算符重载以后的功能应与原有功能类似,含义必须清楚,不能有二义性。不能有二义性。2. 运算符重载函数的两种形式运
6、算符重载函数的两种形式(1)重载为类成员函数。)重载为类成员函数。(2)重载为友元函数。)重载为友元函数。C+C+程序设计程序设计CC的输入输出流的输入输出流6运算符重载的规则与方式运算符重载的规则与方式-重载运算符的规则重载运算符的规则 3. 重载运算符的语法形式重载运算符的语法形式运算符重载的方法,就是要编写一个以运算符重载的方法,就是要编写一个以“operator运算符号运算符号”为函数名的运算符函数,该函数定义了重载的运算符将要执行的操为函数名的运算符函数,该函数定义了重载的运算符将要执行的操作,函数的形参类型必须是自定义的类型。当使用该运算符对形参作,函数的形参类型必须是自定义的类型
7、。当使用该运算符对形参规定的数据类型进行运算时,就执行函数体中的操作,而不再是原规定的数据类型进行运算时,就执行函数体中的操作,而不再是原运算符的操作了。运算符的操作了。其具体的语法形式为:其具体的语法形式为:函数类型函数类型 类名类名:operator运算符运算符() 运算符函数体运算符函数体 C+C+程序设计程序设计CC的输入输出流的输入输出流7运算符重载的规则与方式运算符重载的规则与方式-重载运算符的规则重载运算符的规则 其中,其中,operator是定义运算符重载函数的关键字;运算符是要是定义运算符重载函数的关键字;运算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。
8、重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。重载为类成员函数时,参数个数重载为类成员函数时,参数个数=原操作数个数原操作数个数-1(后置(后置+、-除外)。除外)。重载为友元函数时重载为友元函数时 ,参数个数,参数个数=原操作数个数,且至少应该有原操作数个数,且至少应该有一个自定义类型的形参。一个自定义类型的形参。C+C+程序设计程序设计CC的输入输出流的输入输出流8运算符重载的规则与方式运算符重载的规则与方式-用成员函数重载运算符用成员函数重载运算符 1.双目运算符双目运算符 如果要重载双目运算符(假设为如果要重载双目运算符(假设为B)为类成员函数,使)为类成员函数,使之能够
9、实现表达式之能够实现表达式 oprd1 B oprd2,其中,其中 oprd1 为为A 类对类对象,则象,则 B 应被重载为应被重载为 A 类的成员函数,形参类型应该是类的成员函数,形参类型应该是 oprd2 所属的类型。所属的类型。相应的调用形式为:相应的调用形式为: oprd1.operator B(oprd2)C+C+程序设计程序设计CC的输入输出流的输入输出流9例例9.1将将“+”、“-”运算重载为复数类的成员函数运算重载为复数类的成员函数 规则规则:实部和虚部分别相加减。实部和虚部分别相加减。操作数操作数:两个操作数都是复数类的对象。两个操作数都是复数类的对象。#include cl
10、ass Complex/复数类声明复数类声明private:/私有数据成员私有数据成员double real,image; /复数实部复数实部 ,复数虚部复数虚部public:Complex(double r=0,double i=0); /构造函数构造函数void print(); /输出复数输出复数Complex operator+(const Complex&other); /重载重载+为成员函数为成员函数Complex operator-(const Complex&other); /重载重载-为成员函数为成员函数;C+C+程序设计程序设计CC的输入输出流的输入输出流10例例9.1将将
11、“+”、“-”运算重载为复数类的成员函数运算重载为复数类的成员函数 Complex:Complex(double r,double i) /构造函数的实现构造函数的实现real=r;image=i;void Complex:print()/输出复数的实现输出复数的实现cout0)cout+imagein;else if(image0)coutimagein;C+C+程序设计程序设计CC的输入输出流的输入输出流11例例9.1将将“+”、“-”运算重载为复数类的成员函数运算重载为复数类的成员函数 Complex Complex:operator+(const Complex&other)/重载函数
12、的实现重载函数的实现Complex temp;temp.real=real+other.real;temp.image=image+other.image;return temp;Complex Complex:operator-(const Complex&other)/重载函数的实现重载函数的实现Complex temp;temp.real=real-other.real;temp.image=image-other.image;return temp;C+C+程序设计程序设计CC的输入输出流的输入输出流12例例9.1将将“+”、“-”运算重载为复数类的成员函数运算重载为复数类的成员函数
13、void main()/主函数主函数Complex c1(5,4),c2(2,10),c3; /声明复数类的对象声明复数类的对象couttc1=; c1.print();couttc2=; c2.print();c3=c1-c2;/使用重载运算符完成复数减法使用重载运算符完成复数减法couttc3=c1-c2=;c3.print();c3=c1+c2;/使用重载运算符完成复数加法使用重载运算符完成复数加法couttc3=c1+c2=;c3.print();结果:结果:c1=5+4i c2=2+10i c3=c1-c2=3-6i c3=c1+c2=7+14iC+C+程序设计程序设计CC的输入输出
14、流的输入输出流13运算符重载的规则与方式运算符重载的规则与方式-用成员函数重载运算符用成员函数重载运算符 上述是把运算符加(减)法作为一函数的形式,即上述是把运算符加(减)法作为一函数的形式,即operator+(-)来进行重载,使之能够对复数可以直接进行加(减)法的运)来进行重载,使之能够对复数可以直接进行加(减)法的运算。算。 2.前置单目运算符前置单目运算符+和和-如果要重载前置单目运算符(假设为如果要重载前置单目运算符(假设为U)为类成员函数,使之)为类成员函数,使之能够实现表达式能够实现表达式 U oprd,其中,其中 oprd 为为A类对象,则类对象,则 U 应被重载应被重载为为
15、A 类的成员函数,无形参。类的成员函数,无形参。相应的调用形式为:相应的调用形式为: oprd.operator U()C+C+程序设计程序设计CC的输入输出流的输入输出流14运算符重载的规则与方式运算符重载的规则与方式-用成员函数重载运算符用成员函数重载运算符 3.后置单目运算符后置单目运算符 +和和如果要重载如果要重载 +或或-为类成员函数,使之能够实现表为类成员函数,使之能够实现表达式达式 oprd+ 或或 oprd- ,其中,其中 oprd 为为A类对象,则类对象,则 +或或- 应被重载为应被重载为 A 类的成员函数,且作为双目运算符重类的成员函数,且作为双目运算符重载为:载为: op
16、rd+ 0或或 oprd- 0,具有一个,具有一个 int 类型形参。类型形参。相应的调用形式为:相应的调用形式为: oprd.operator +(0)C+C+程序设计程序设计CC的输入输出流的输入输出流15运算符重载的规则与方式运算符重载的规则与方式-用友员函数重载运算符用友员函数重载运算符 如果需要重载一个运算符,使之能够用于操作某类对象的私如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。有成员,可以此将运算符重载为该类的友元函数。将重载的运算符函数定义为类的友元函数,称为友元运算符将重载的运算符函数定义为类的友元函数,称为友元运算符函数。
17、友元运算符函数不是类的成员,它在类内声明原型,在类函数。友元运算符函数不是类的成员,它在类内声明原型,在类外定义函数本身。由于它不是类的成员函数,不属于任何一个类外定义函数本身。由于它不是类的成员函数,不属于任何一个类对象,所以没有对象,所以没有this指针,因此,重载双目运算符时要有两个参数,指针,因此,重载双目运算符时要有两个参数,重载单目运算符时只要一个参数就可以了。重载单目运算符时只要一个参数就可以了。1.运算符重载为友员函数的格式运算符重载为友员函数的格式 在类内声明的一般形式为:在类内声明的一般形式为: friend operator(参数表参数表); 在类外定义的一般形式为:在类
18、外定义的一般形式为: operator(参数表参数表) C+C+程序设计程序设计CC的输入输出流的输入输出流16运算符重载的规则与方式运算符重载的规则与方式-用友员函数重载运算符用友员函数重载运算符 函数体函数体 其中,其中,operator是定义运算符重载函数的关键字;运算符是要是定义运算符重载函数的关键字;运算符是要重载的运算符的名称;参数表给出重载运算符所需要的参数和类重载的运算符的名称;参数表给出重载运算符所需要的参数和类型。型。2.双目运算符的重载双目运算符的重载 双目运算符重载为友元函数时,由于没有双目运算符重载为友元函数时,由于没有this指针,所以两个指针,所以两个操作数都要通
19、过友元运算符函数的参数指出。函数的形参依自左操作数都要通过友元运算符函数的参数指出。函数的形参依自左至右次序排列各操作数。至右次序排列各操作数。双目运算符(假设为双目运算符(假设为B)重载为友元函数后,其表达式)重载为友元函数后,其表达式oprd1 B oprd2,等同于,等同于operator B(oprd1,oprd2 )。C+C+程序设计程序设计CC的输入输出流的输入输出流17例例9.3将将“+”、“-”运算重载为复数类的友元函数运算重载为复数类的友元函数 #include class Complex/复数类声明复数类声明private:double real,image; /复数实部复
20、数实部 ,复数虚部复数虚部public:Complex(double r=0,double i=0); /构造函数构造函数void print(); /输出复数输出复数friend Complex operator+(Complex c1,Complex c2); /运算符运算符+重载为友元函数重载为友元函数friend Complex operator-(Complex c1,Complex c2); /运算符运算符-重重载为友元函数载为友元函数 ;C+C+程序设计程序设计CC的输入输出流的输入输出流18例例9.3将将“+”、“-”运算重载为复数类的友元函数运算重载为复数类的友元函数 Com
21、plex:Complex(double r,double i) /构造函数的实现构造函数的实现real=r;image=i;void Complex:print() /输出复数的实现输出复数的实现cout0)cout+imagein;else if(image0)coutimagein;C+C+程序设计程序设计CC的输入输出流的输入输出流19例例9.3将将“+”、“-”运算重载为复数类的友元函数运算重载为复数类的友元函数 Complex operator +(Complex c1,Complex c2)/运算符重载友元函数实现运算符重载友元函数实现 return Complex(c2.real
22、+c1.real, c2.image+c1.image); Complex operator -(Complex c1,Complex c2)/运算符重载友元函数实现运算符重载友元函数实现 return Complex(c1.real-c2.real, c1.image-c2.image); void main()Complex c1(5,4),c2(2,10),c3;/声明复数类的对象声明复数类的对象couttc1=; C+C+程序设计程序设计CC的输入输出流的输入输出流20例例9.3将将“+”、“-”运算重载为复数类的友元函数运算重载为复数类的友元函数 c1.print();couttc2
23、=; c2.print();c3=c1-c2; /使用重载运算符完成复数减法使用重载运算符完成复数减法couttc3=c1-c2=;c3.print();c3=c1+c2; /使用重载运算符完成复数加法使用重载运算符完成复数加法couttc3=c1+c2=;c3.print();结果:结果:c1=5+4i c2=2+10i c3=c1-c2=3-6i c3=c1+c2=7+14iC+C+程序设计程序设计CC的输入输出流的输入输出流21运算符重载的规则与方式运算符重载的规则与方式-用友员函数重载运算符用友员函数重载运算符 3.单目运算符的重载单目运算符的重载单目运算符重载为友元函数时,由于没有单
24、目运算符重载为友元函数时,由于没有this指针,所以操作指针,所以操作数要通过友元运算符函数的参数指出。数要通过友元运算符函数的参数指出。单目运算符重载为友元函数后:单目运算符重载为友元函数后:1)重载前置单目运算符)重载前置单目运算符 B:表达式表达式 B oprd 等同于等同于operator B(oprd ) 2)重载后置单目运算符)重载后置单目运算符 +和和-:表达式表达式 oprd B 等同于等同于operator B(oprd,0 )C+C+程序设计程序设计CC的输入输出流的输入输出流22运算符重载的规则与方式运算符重载的规则与方式-成员运算符函数与友元运算符函数的比较成员运算符函
25、数与友元运算符函数的比较 对双目运算符:成员运算符函数是类的成员,带有对双目运算符:成员运算符函数是类的成员,带有this指指针,只需一个参数,而友元运算符函数不是类的成员,不带针,只需一个参数,而友元运算符函数不是类的成员,不带this指指针,参数必须是两个。对单目运算符:成员运算符函数不带参数,针,参数必须是两个。对单目运算符:成员运算符函数不带参数,而友元运算符必须带一参数。而友元运算符必须带一参数。 双目运算符可被重载为友元函数,也可被重载为成员函数,双目运算符可被重载为友元函数,也可被重载为成员函数,但在运算符的左操作数是一个标准数据类型而右操作数是对象的但在运算符的左操作数是一个标
26、准数据类型而右操作数是对象的情况下,必须将它重载为友元函数(详见例情况下,必须将它重载为友元函数(详见例9.5),原因是标准数),原因是标准数据类型的数据不能产生对重载运算符的调用。据类型的数据不能产生对重载运算符的调用。例如,考虑下面的表达式:例如,考虑下面的表达式:5.6+c其中,其中,c 是是Complex类的一个对象。类的一个对象。将该加法操作重载为成员函数时,该表达式将被解释为将该加法操作重载为成员函数时,该表达式将被解释为5.6.operator+(c)C+C+程序设计程序设计CC的输入输出流的输入输出流23运算符重载的规则与方式运算符重载的规则与方式-成员运算符函数与友元运算符函
27、数的比较成员运算符函数与友元运算符函数的比较 显然,这是错误的(详见显然,这是错误的(详见9.5),说明在上述表达式的情况下,),说明在上述表达式的情况下,不能将加法操作重载为成员函数。不能将加法操作重载为成员函数。而该加法操作重载为友元函数时,该表达式将被解释为而该加法操作重载为友元函数时,该表达式将被解释为operator+(Complex(5.6),c) 成员运算符函数与友元运算符函数都可用习惯和专用方式成员运算符函数与友元运算符函数都可用习惯和专用方式调用。调用。 成员运算符函数与友元运算符函数选择那种方式比较好,成员运算符函数与友元运算符函数选择那种方式比较好,要根据实际情况和使用习
28、惯决定,一般而言,对于双目运算符重要根据实际情况和使用习惯决定,一般而言,对于双目运算符重载为友元运算符函数较好,若运算符的操作数特别是左操作数需载为友元运算符函数较好,若运算符的操作数特别是左操作数需要进行类型转换,必须重载为友元运算符函数。若一个运算符需要进行类型转换,必须重载为友元运算符函数。若一个运算符需要修改对象的状态,则选择成员运算符较好。要修改对象的状态,则选择成员运算符较好。C+C+程序设计程序设计CC的输入输出流的输入输出流24几种典型运算符的重载几种典型运算符的重载 -赋值运算符赋值运算符“=”的重载的重载 对于任何一个类,如果没有用户自定义的赋值运算符函数,对于任何一个类
29、,如果没有用户自定义的赋值运算符函数,系统会自动地为其生成一个缺省的赋值运算符函数,以完成数据成员系统会自动地为其生成一个缺省的赋值运算符函数,以完成数据成员之间的逐域拷贝。如下面的程序段:之间的逐域拷贝。如下面的程序段:X & X:operator=(const X &source) /类对象成员之间的赋值语句类对象成员之间的赋值语句 当类当类X的两个对象的两个对象oprd 1和和oprd 2已创建,就可用已创建,就可用oprd 1= oprd 2进行赋值了。进行赋值了。 通常情况下,缺省的赋值运算符函数就可完成赋值任务,但通常情况下,缺省的赋值运算符函数就可完成赋值任务,但在某些特殊情况下
30、,比如类中有指针类形式,使用缺省的赋值运算符在某些特殊情况下,比如类中有指针类形式,使用缺省的赋值运算符函数就会产生指针悬挂的错误,必须显式地定义一个赋值运算符重载函数就会产生指针悬挂的错误,必须显式地定义一个赋值运算符重载函数,使参与赋值的两个对象有各自的存储空间,以解决这个问题。函数,使参与赋值的两个对象有各自的存储空间,以解决这个问题。 几个注意事项几个注意事项赋值运算符只能重载为成员运算符函数,不能重载为友元运算赋值运算符只能重载为成员运算符函数,不能重载为友元运算符函数。符函数。 赋值运算符重载后不能被继承。赋值运算符重载后不能被继承。C+C+程序设计程序设计CC的输入输出流的输入输
31、出流25几种典型运算符的重载几种典型运算符的重载 -函数调用运算符函数调用运算符“()()”的重的重载载 在创建对象时,可利用构造函数给数据成员赋值,其形式是:在创建对象时,可利用构造函数给数据成员赋值,其形式是: 类名类名 对象名(参数);对象名(参数); 但对象创建完成以后,就不能用但对象创建完成以后,就不能用“对象名(参数)对象名(参数)”的形式重的形式重新赋值了。为解决这个问题,需要重载运算符新赋值了。为解决这个问题,需要重载运算符“()()”,重载之后,重载之后,就可以用就可以用“对象名(参数)对象名(参数)”的形式为对象重新赋值。的形式为对象重新赋值。 该运算符可以看成是双目运算符
32、,由左操作数来调用重载后的该运算符可以看成是双目运算符,由左操作数来调用重载后的运算符。运算符。该运算符重载时的声明和定义与其他的运算符相同,但必须重载该运算符重载时的声明和定义与其他的运算符相同,但必须重载为成员运算符函数。为成员运算符函数。C+C+程序设计程序设计CC的输入输出流的输入输出流26类型转换类型转换 类类型与标准类型之间的转换有三种方法:类类型与标准类型之间的转换有三种方法: 通过构造函数转换。通过构造函数转换。通过构造函数能将标准数据类型向类类型转换,但不能将类类型通过构造函数能将标准数据类型向类类型转换,但不能将类类型转换为标准类型。转换为标准类型。 通过类类型转换函数转换
33、通过类类型转换函数转换要将类类型转换为标准数据类型时,需要采用显式类型转换机制,要将类类型转换为标准数据类型时,需要采用显式类型转换机制,定义类类型转换函数。定义类类型转换函数。 定义一个类的类型转换函数的一般形式为:定义一个类的类型转换函数的一般形式为: :operator type() return type类型的数据类型的数据 /返回返回type类型的对象类型的对象 其中,其中,type通常是标准类型。通常是标准类型。C+C+程序设计程序设计CC的输入输出流的输入输出流27类型转换类型转换 要注意的是:要注意的是: 此函数的功能是将类的对象转换为类型为此函数的功能是将类的对象转换为类型为
34、type的数据,它的数据,它既没有参数,也没有返回类型,但在函数体中必须返回具有既没有参数,也没有返回类型,但在函数体中必须返回具有type类型类型的一个数据。的一个数据。 类类型转换函数只能定义为类的成员函数,可在类内声明类类类型转换函数只能定义为类的成员函数,可在类内声明类外定义,也可在类内定义。外定义,也可在类内定义。 一个类内可定义多个类类型转换函数,编译器会根据操作数一个类内可定义多个类类型转换函数,编译器会根据操作数的类型自动选择一个合适的类型转换函数与之匹配。但在可能出现二的类型自动选择一个合适的类型转换函数与之匹配。但在可能出现二义性时,应显式地使用类类型转换函数进行转换。义性
35、时,应显式地使用类类型转换函数进行转换。 通过运算符重载实现类型转换通过运算符重载实现类型转换这种方式的转换,可以实现标准类型的数据与类对象之间的运算。这种方式的转换,可以实现标准类型的数据与类对象之间的运算。C+C+程序设计程序设计CC的输入输出流的输入输出流28联编与虚函数联编与虚函数 联编也称绑定,是指源程序在编译后生成的可执行代码联编也称绑定,是指源程序在编译后生成的可执行代码经过连接装配在一起的过程,即是指计算机程序自身彼此经过连接装配在一起的过程,即是指计算机程序自身彼此关联的过程,关联的过程, 按联编工作来分:按联编工作来分:1. 静态联编:联编工作在编译连接阶段完成。静态联编:
36、联编工作在编译连接阶段完成。2. 动态联编:联编在程序运行阶段完成。动态联编:联编在程序运行阶段完成。C+C+程序设计程序设计CC的输入输出流的输入输出流29联编与虚函数联编与虚函数 -静态联编静态联编 在运行前就完成的联编,又称前期联编。这种联编工作出在运行前就完成的联编,又称前期联编。这种联编工作出现在编译阶段,在编译时就决定如何实现某一动作,用对象名现在编译阶段,在编译时就决定如何实现某一动作,用对象名或者类名来限定要调用的函数,因此要求在程序编译时就知道或者类名来限定要调用的函数,因此要求在程序编译时就知道调用函数的全部信息。这种联编类型的函数调用速度很快,效调用函数的全部信息。这种联
37、编类型的函数调用速度很快,效率也很高;但灵活性较差。率也很高;但灵活性较差。由静态联编支持的多态性称为编译时的多态性或静态多态由静态联编支持的多态性称为编译时的多态性或静态多态性,也就是说,确定同名操作的具体操作对象的过程是在编译性,也就是说,确定同名操作的具体操作对象的过程是在编译过程中完成的。过程中完成的。C+用函数重载和运算符重载来实现编译时的用函数重载和运算符重载来实现编译时的多态性。多态性。函数重载的二种方式:函数重载的二种方式: 在同一类中重载:同名的成员函数,根据参数等不同,在同一类中重载:同名的成员函数,根据参数等不同,自动予以区别自动予以区别基类成员在派生类中的重载:基类成员
38、在派生类中的重载:C+C+程序设计程序设计CC的输入输出流的输入输出流30联编与虚函数联编与虚函数 -动态联编动态联编 在运行时动态地决定实现某一动作,又成后期联在运行时动态地决定实现某一动作,又成后期联编。这种联编要到程序运行时才能确定调用哪个函数,编。这种联编要到程序运行时才能确定调用哪个函数,提供了更好的灵活性和程序的易维护性。提供了更好的灵活性和程序的易维护性。由动态联编支持的多态性称为运行时的多态性活由动态联编支持的多态性称为运行时的多态性活动太多态性,也就是说,确定同名操作的具体操作对动太多态性,也就是说,确定同名操作的具体操作对象的过程是在运行过程中完成的。象的过程是在运行过程中
39、完成的。C+用继承和虚函用继承和虚函数来实现运行时的多态性。数来实现运行时的多态性。C+C+程序设计程序设计CC的输入输出流的输入输出流31联编与虚函数联编与虚函数 -虚函数虚函数 虚函数是重载的另一种形式,实现的是动态的重虚函数是重载的另一种形式,实现的是动态的重载,即函数调用与函数体之间的联系是在运行时才建载,即函数调用与函数体之间的联系是在运行时才建立,也就是动态联编。立,也就是动态联编。9.5.3.1为什么要引入虚函数 一般对象的指针之间没有联系,彼此独立,不一般对象的指针之间没有联系,彼此独立,不能混用。但派生类是由基类派生出来的,它们之间有能混用。但派生类是由基类派生出来的,它们之
40、间有继承关系,因此,指向基类和派生类的指针之间也有继承关系,因此,指向基类和派生类的指针之间也有一定的联系,如果使用不当,将会出现一些问题。一定的联系,如果使用不当,将会出现一些问题。请看下面的例子:请看下面的例子:C+C+程序设计程序设计CC的输入输出流的输入输出流32例例9.9静态联编的例子静态联编的例子 #include class Aprivate:int x,y;public:A(int xx=0,int yy=0) x=xx; y=yy; void disp() couttA:xtyendl; ; class B:public Aprivate:int z;C+C+程序设计程序设计
41、CC的输入输出流的输入输出流33例例9.9静态联编的例子静态联编的例子 public:B(int xx,int yy,int zz):A(xx,yy) z=zz;void disp()couttB:zdisp();objp=&obj1;objp-disp();结果:结果: A:3 4 A:1 2C+C+程序设计程序设计CC的输入输出流的输入输出流34例例9.9静态联编的例子静态联编的例子 很很显显然然,在在执执行行语语句句objp=&obj1之之后后,指指针针已已经经指指向向了了对对象象obj1,但但它它调调用用的的函函数数仍仍然然是是基基类类对对象象的的函函数数而而不不是是派派生生类类对对象
42、象的的函函数数,原原本本想想通通过过使使用用对对象象指指针针来来达达到到动动态态调调用用即即当当指指针针指指向向不不同同的的对对象象时时执执行行不同的操作的目的没有达到。不同的操作的目的没有达到。 出出现现这这种种现现象象的的原原因因是是调调用用的的成成员员函函数数在在编编译译时时静静态态联联编编了了,也也就就是是说说,disp()的的操操作作已已静静态态束束定定到到基基类的函数上,因此,导致程序输出了不希望的结果。类的函数上,因此,导致程序输出了不希望的结果。 为解决这个问题,就要引入虚函数的概念。为解决这个问题,就要引入虚函数的概念。C+C+程序设计程序设计CC的输入输出流的输入输出流35
43、联编与虚函数联编与虚函数 -虚函数虚函数 9.5.3.2虚函数的定义和使用1.虚函数的定义虚函数的定义虚函数的定义是在基类中进行的,即把基类中需要定义为虚函数的成员函数声明为virtual。当基类中的某个成员函数被声明为虚函数后,它就可以在派生类中被重新定义。在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数和类型、参数的顺序都必须与基类中的原型完全一致。 虚函数定义的一般形式为: virtual(参数表参数表) 函数体函数体 C+C+程序设计程序设计CC的输入输出流的输入输出流36联编与虚函数联编与虚函数 -虚函数虚函数 2.虚函数与重载函数的关系虚函数与重载函数的关系在派生类
44、中被重新定义的基类中的虚函数,是函数重载的在派生类中被重新定义的基类中的虚函数,是函数重载的另一种形式,但它与函数重载又有区别:一般的函数重载,要另一种形式,但它与函数重载又有区别:一般的函数重载,要求其函数的参数或参数类型必须有所不同,函数的返回类型也求其函数的参数或参数类型必须有所不同,函数的返回类型也可以不同,但重载一个虚函数时,要求函数名、返回类型、参可以不同,但重载一个虚函数时,要求函数名、返回类型、参数个数、参数的类型和参数的顺序必须与基类中的虚函数的原数个数、参数的类型和参数的顺序必须与基类中的虚函数的原型完全相同。如果仅返回类型不同,其余相同,则系统会给出型完全相同。如果仅返回
45、类型不同,其余相同,则系统会给出错误信息;如果函数名相同,而参数个数、参数的类型或参数错误信息;如果函数名相同,而参数个数、参数的类型或参数的顺序不同,系统认为是普通的函数重载,虚函数的特性将丢的顺序不同,系统认为是普通的函数重载,虚函数的特性将丢失。失。使用虚函数改写例使用虚函数改写例9.9。C+C+程序设计程序设计CC的输入输出流的输入输出流37例例9.10增加虚函数修改例增加虚函数修改例9.9 #include class Aprivate:int x,y;public:A(int xx=0,int yy=0) x=xx; y=yy; void virtual disp()couttA:
46、xtyendl; ; class B:public Aprivate:int z;C+C+程序设计程序设计CC的输入输出流的输入输出流38例例9.10增加虚函数修改例增加虚函数修改例9.9 public:B(int xx,int yy,int zz):A(xx,yy) z=zz; void disp()couttB:zdisp();objp=&obj1;objp-disp();结果:结果: A:3 4 B:3C+C+程序设计程序设计CC的输入输出流的输入输出流39联编与虚函数联编与虚函数 -虚函数虚函数 3.使用虚函数的一些注意事项:使用虚函数的一些注意事项:(1)虚函数是引入继承机制后,用于
47、表现基类和派生类)虚函数是引入继承机制后,用于表现基类和派生类之间的成员函数重载的一种关系,是特殊的函数重载。之间的成员函数重载的一种关系,是特殊的函数重载。(2)关键字)关键字virtual只能对基类中的非静态的保护成员或公只能对基类中的非静态的保护成员或公有成员函数进行声明。虚函数的声明只能出现在类声明的函数有成员函数进行声明。虚函数的声明只能出现在类声明的函数原型的声明中,不能出现在函数体实现的时候,原型的声明中,不能出现在函数体实现的时候,(3)虚函数的声明在基类中进行,虚函数具有传递性)虚函数的声明在基类中进行,虚函数具有传递性(可以隔代传递),即具有继承性,基类中定义了虚函数,派(
48、可以隔代传递),即具有继承性,基类中定义了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数,且派生类生类中无论是否说明,同原型函数都自动为虚函数,且派生类中可以省略关键字中可以省略关键字virtual。基类中的某个成员函数被声明成虚函数后,此虚函数可以基类中的某个成员函数被声明成虚函数后,此虚函数可以在其派生类中被重载,并可以被重新定义,但是函数原型必须在其派生类中被重载,并可以被重新定义,但是函数原型必须相同(包括函数的返回类型),否则系统把其作为一般的函数相同(包括函数的返回类型),否则系统把其作为一般的函数重载而失去虚特性。重载而失去虚特性。C+C+程序设计程序设计CC的输入输出流
49、的输入输出流40联编与虚函数联编与虚函数 -虚函数虚函数 (4)动态联编只能通过成员函数来调用或通过指针、引)动态联编只能通过成员函数来调用或通过指针、引用来访问虚函数,如果用对象名的形式来访问虚函数,将采用用来访问虚函数,如果用对象名的形式来访问虚函数,将采用静态联编。静态联编。(5)虚函数必须是所在类的成员函数,不能是友元函数)虚函数必须是所在类的成员函数,不能是友元函数或静态成员函数。但可以在另一个类中被声明为友元函数。或静态成员函数。但可以在另一个类中被声明为友元函数。(6)构造函数不能声明为虚函数,析构函数可以声明为)构造函数不能声明为虚函数,析构函数可以声明为虚函数。虚函数。(7)
50、由于内联函数不能在运行中动态确定其位置,所以)由于内联函数不能在运行中动态确定其位置,所以它不能声明为虚函数。它不能声明为虚函数。C+C+程序设计程序设计CC的输入输出流的输入输出流41例例9.12自动为虚函数的例子自动为虚函数的例子 #include class A/基类基类A声明声明public:virtual void display()cout“tA:display()”endl;/虚成员函数虚成员函数;class B: public A/公有派生公有派生public: void display() cout“tB:display()”endl; /自动成为虚函数自动成为虚函数; C+
51、C+程序设计程序设计CC的输入输出流的输入输出流42例例9.12自动为虚函数的例子自动为虚函数的例子 class C: public B/公有派生公有派生public: void display() cout“tC:display()”display(); /通过指针调用虚函数通过指针调用虚函数 void main() C+C+程序设计程序设计CC的输入输出流的输入输出流43例例9.12自动为虚函数的例子自动为虚函数的例子 A a,*p; /声明基类对象和指针声明基类对象和指针B b; /声明派生类对象声明派生类对象C c; /声明派生类对象声明派生类对象p=&a;fun(p); /调用基类调
52、用基类A函数成员函数成员p=&b;fun(p); /调用派生类调用派生类B函数成员函数成员p=&c; fun(p); /调用派生类调用派生类C函数成员函数成员 结果:结果: A:display() B:display() C:display() 参见例参见例8.16C+C+程序设计程序设计CC的输入输出流的输入输出流44纯虚函数和抽象类纯虚函数和抽象类 1.问题的提出:问题的提出: 有时设计一个基类是为了被继承,并不需要为其有时设计一个基类是为了被继承,并不需要为其提供成员函数,但是因为虚函数调用要实现动态联编提供成员函数,但是因为虚函数调用要实现动态联编方式,对派生类虚函数进行定义时,也要在
53、基类中为方式,对派生类虚函数进行定义时,也要在基类中为其进行定义。所以若派生类中需要很多虚函数,就要其进行定义。所以若派生类中需要很多虚函数,就要在基类中也定义同样多且无用的虚函数。在基类中也定义同样多且无用的虚函数。2.解决方案:解决方案: 在类中定义纯虚函数,不需提供任何实际操作定在类中定义纯虚函数,不需提供任何实际操作定义。即定义一种特殊的类,我们称之为抽象类,它为义。即定义一种特殊的类,我们称之为抽象类,它为一族类提供统一的操作界面,建立抽象类就是为了通一族类提供统一的操作界面,建立抽象类就是为了通过它多态地使用其中的成员函数。抽象类是带有纯虚过它多态地使用其中的成员函数。抽象类是带有
54、纯虚函数的类。函数的类。 C+C+程序设计程序设计CC的输入输出流的输入输出流45纯虚函数和抽象类纯虚函数和抽象类 -纯虚函数纯虚函数 纯虚函数:基类中的永远不被调用执行的虚函数称为纯虚纯虚函数:基类中的永远不被调用执行的虚函数称为纯虚函数。函数。纯虚函数的作用:利用其虚函数的传递特性,达到继承的纯虚函数的作用:利用其虚函数的传递特性,达到继承的动态多态性目的。动态多态性目的。一个抽象类带有至少一个纯虚函数。纯虚函数是一个在基一个抽象类带有至少一个纯虚函数。纯虚函数是一个在基类中说明的虚函数,在基类中没有定义具体的操作内容,要求类中说明的虚函数,在基类中没有定义具体的操作内容,要求派生类根据需
55、要定义自己的版本。派生类根据需要定义自己的版本。纯虚函数的声明格式:纯虚函数的声明格式:virtual 函数类型函数类型 函数名函数名(参数表参数表) = 0 ;声明为纯虚函数后,基类中就不再给出函数的实现部分,声明为纯虚函数后,基类中就不再给出函数的实现部分,纯虚函数的函数体由各派生类自己给出。纯虚函数的函数体由各派生类自己给出。注意:注意:区分函数体为空的虚函数与纯虚函数的区别。区分函数体为空的虚函数与纯虚函数的区别。 C+C+程序设计程序设计CC的输入输出流的输入输出流46纯虚函数和抽象类纯虚函数和抽象类 -抽象类抽象类 1.抽象类抽象类如果一个类中至少有一个纯虚函数,这个类就成为抽象类
56、,如果一个类中至少有一个纯虚函数,这个类就成为抽象类,即带有纯虚函数的类是抽象类。即带有纯虚函数的类是抽象类。抽象类是一种特殊的类,为抽象和设计的目的而建立,处抽象类是一种特殊的类,为抽象和设计的目的而建立,处于继承层次结构的较上层。其作用是将有关的数据和行为组织于继承层次结构的较上层。其作用是将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。在一个继承层次结构中,保证派生类具有要求的行为。抽象类描述了一组子类共同的操作接口,而完整的实现留抽象类描述了一组子类共同的操作接口,而完整的实现留给了子类。给了子类。抽象类只能作为基类来使用,不能实例化。但是可以声明抽象类只能作为基
57、类来使用,不能实例化。但是可以声明抽象类的指针和引用,指向并访问派生类的对象。抽象类的指针和引用,指向并访问派生类的对象。对于暂时无法实现的函数,可以声明为纯虚函数,留给派对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。生类去实现。 抽象类使用时应注意以下问题:抽象类使用时应注意以下问题:C+C+程序设计程序设计CC的输入输出流的输入输出流47纯虚函数和抽象类纯虚函数和抽象类 -抽象类抽象类 抽象类只能用作其它类的基类,不能建立抽象类的对象。因抽象类只能用作其它类的基类,不能建立抽象类的对象。因为它的纯虚函数没有定义功能。为它的纯虚函数没有定义功能。 抽象类不能用作参数类型、函数
58、的返回类型或显式转换的类型。抽象类不能用作参数类型、函数的返回类型或显式转换的类型。可以声明抽象类的指针和引用,通过它们,可以志向并访问派可以声明抽象类的指针和引用,通过它们,可以志向并访问派生类对象,从而访问派生类的成员。生类对象,从而访问派生类的成员。 若抽象类的派生类中没有给出所有纯虚函数的函数体,这个派若抽象类的派生类中没有给出所有纯虚函数的函数体,这个派生类仍是一个抽象类。若抽象类的派生类中给出了所有纯虚函数的函生类仍是一个抽象类。若抽象类的派生类中给出了所有纯虚函数的函数体,这个派生类不再是一个抽象类,可以声明自己的对象。数体,这个派生类不再是一个抽象类,可以声明自己的对象。2.抽
59、象类的一般形式抽象类的一般形式class 类名类名 virtual 类型类型 函数名函数名(参数表参数表)=0; /纯虚函数纯虚函数 .C+C+程序设计程序设计CC的输入输出流的输入输出流48例例9.14抽象类的例子抽象类的例子 #include class A /抽象基类抽象基类A声明声明public: virtual void display( )=0; /纯虚函数成员纯虚函数成员;class B: public Apublic: void display()cout“ttB:display()”endl;/虚成员函数虚成员函数 ; C+C+程序设计程序设计CC的输入输出流的输入输出流49
60、例例9.14抽象类的例子抽象类的例子 class C: public Bpublic: void display()cout“ttC:display()”display(); C+C+程序设计程序设计CC的输入输出流的输入输出流50例例9.14抽象类的例子抽象类的例子 void main()A *p; /声明抽象基类指针声明抽象基类指针B b; /声明派生类对象声明派生类对象C c; /声明派生类对象声明派生类对象p=&b;fun(p); /调用派生类调用派生类B函数成员函数成员p=&c;fun(p); /调用派生类调用派生类C函数成员函数成员 结果:结果: B:display() C:display() C+C+程序设计程序设计CC的输入输出流的输入输出流51作业1:C+C+程序设计程序设计CC的输入输出流的输入输出流52作业2:C+C+程序设计程序设计CC的输入输出流的输入输出流53再见!