资源预览内容
第1页 / 共58页
第2页 / 共58页
第3页 / 共58页
第4页 / 共58页
第5页 / 共58页
第6页 / 共58页
第7页 / 共58页
第8页 / 共58页
第9页 / 共58页
第10页 / 共58页
亲,该文档总共58页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
目目 录录F第第1章章 C+概述概述F第第2章章 数据类型、运算符和表达式数据类型、运算符和表达式F第第3章章 简单的输入简单的输入/输出输出F第第4章章 C+的流程控制的流程控制F第第5章章 函数函数F第第6章章 编译预处理编译预处理F第第7章章 数组数组F第第8章章 结构体、共同体和枚举类型结构体、共同体和枚举类型F第第9章章 指针和引用指针和引用F第第10章章 类和对象类和对象F第第11章章 类和对象的其他特性类和对象的其他特性F第第12章章 继承和派生继承和派生F第第13章章 多态性多态性F第第14章章 输入输入/输出流输出流F第第15章章 模板模板v第一部分第一部分 面向过程的程序设计面向过程的程序设计v第二部分第二部分 面向对象的程序设计面向对象的程序设计第第13章章 多态性多态性 13.1 函数重载函数重载 13.2 运算符重载运算符重载 13.3 静态联编静态联编 13.4 动态联编和虚函数动态联编和虚函数 13.5 纯虚函数和抽象类纯虚函数和抽象类 第第 13 章章 多态性多态性 多态性是实现多态性是实现 OOP 的关键技术之一。的关键技术之一。 在在C+中,多态性分为两种:中,多态性分为两种: 静态多态静态多态 动态多态动态多态 函数重载和运算符重载属于静态多态。函数重载和运算符重载属于静态多态。函数重载:相同函数名可以完成不同功能。函数重载:相同函数名可以完成不同功能。运算符重载:相同运算符完成不同功能。运算符重载:相同运算符完成不同功能。 动态多态动态多态是指:程序执行过程中确定的关系,是指:程序执行过程中确定的关系, 如动态确定函数的调用关系。如动态确定函数的调用关系。 运行时的多态(动态多态)运行时的多态(动态多态) 是通过类的继承和虚函数来实现的。是通过类的继承和虚函数来实现的。13.1 函数重载函数重载 参见参见5.6节(略)节(略) 13.2 重载运算符重载运算符 C+中所有的运算符都已预先定义了用法及意义。中所有的运算符都已预先定义了用法及意义。 如:如:+ * / = 等,适用于已有的数据类型。等,适用于已有的数据类型。 已定义的已定义的用法及意义是不允许用户改变的。用法及意义是不允许用户改变的。 如果用户定义了新的数据类型,希望已定义的运算符如果用户定义了新的数据类型,希望已定义的运算符 能适应新的数据类型,能适应新的数据类型, 如如: 前述前述 Complex 类的加法运算类的加法运算Complex c1, c2, c3;c3 = add(c1, c2); /以前通过函数实现加法运算以前通过函数实现加法运算c2.add(c3); / 或或通过成员函数实现通过成员函数实现c3 = c1 + c2; /能否直观地写成这样?能否直观地写成这样?能能 ! 运算符的重载可以达到此目的。运算符的重载可以达到此目的。 允许重载允许重载 和和 不允许重载不允许重载的运算符见的运算符见13-1和和13-2表。表。13.2.1 运算符重载的几点说明运算符重载的几点说明重载运算符的限制重载运算符的限制(1)只能对已有运算符重载,不可臆造新的运算符。)只能对已有运算符重载,不可臆造新的运算符。(2)不允许改变运算符的优先级和结合性。)不允许改变运算符的优先级和结合性。(3)不允许改变运算符的语法结构,)不允许改变运算符的语法结构, 如二元运算符只能重载成二元运算符,如二元运算符只能重载成二元运算符, 一元运算符只能重载成一元运算符。一元运算符只能重载成一元运算符。1.重载为类的成员函数重载为类的成员函数 在类内定义运算符重载函数的格式为:在类内定义运算符重载函数的格式为: operator ( ) 以以 operator 为关键字,编译器可以很容易将为关键字,编译器可以很容易将运算符重载函数与其他成员函数区别开来。运算符重载函数与其他成员函数区别开来。例例13.2 实现复数类的实现复数类的“+”,“-”等重载运算等重载运算 关键部分见下页关键部分见下页13.2.2 运算符重载的两种方式运算符重载的两种方式在类外定义运算符重载函数的格式为:在类外定义运算符重载函数的格式为: :operator ( )特殊的成员函数名特殊的成员函数名class Complex float Real, Image;public: . Complex operator +(const Complex &c); Complex operator +(double); Complex operator-(const Complex &c); Complex operator-(double); Complex operator-(void); . ;Complex Complex:operator +(const Complex &c) return Complex (Real+c.Real, Image+c.Image); Complex Complex:operator +(double r) return Complex(Real+r, Image); Complex Complex:operator-(void) return Complex(-Real, -Image); void main( ) Complex c1(2, 3), c2(4, -2), c3; c3 = c1+c2 ; c3 = c1+5 ; c3 = - c1; 编译器将编译器将 c1+c2 解释为:解释为:c1.operator+(c2)将将 c1+5 解释为:解释为:c1.operator+(5)第第1个运算量是对象,第个运算量是对象,第2个运算量是参数。个运算量是参数。阅读教材上程序全文阅读教材上程序全文(讲解略)讲解略)将将 - c1 解释为:解释为:c1.operator( ) 当用成员函数实现运算符的重载时,重载函数的当用成员函数实现运算符的重载时,重载函数的 参数个数只能是参数个数只能是 0 个或个或 1 个。分别实现:一元、二元个。分别实现:一元、二元 运算符的重载。运算符的重载。2.重载为友元函数重载为友元函数 例例13.3 用友元函数实现复数类的用友元函数实现复数类的“+”和和“” 重载运算重载运算 关键部分见下页关键部分见下页在在类内类内定义友元重载函数的格式为:定义友元重载函数的格式为:friend operator ( )在在类外类外定义友元重载函数的格式为:定义友元重载函数的格式为: operator ( )class Complex float Real, Image;public: . friend Complex operator +(const Complex &c1, const Complex &c2); friend Complex operator(const Complex &c); . ;Complex operator +(const Complex &c1, const Complex &c2)/二元运算二元运算 Complex t; t.Real=c1.Real+c2.Real; t.Image=c1.Image+c2.Image; return t;Complex operator(const Complex c) /一元运算一元运算 return Complex(c.Real, c.Image) ;不是类的成员函数不是类的成员函数在在 main( )函数中函数中,若有若有 Complex c1,c2; 则编译器将则编译器将 c1+c2 解释为:解释为:operator+(c1, c2)将将 c1 解释为:解释为:operator(c1)阅读教材上程序全文阅读教材上程序全文(讲解略)讲解略) 当用友元函数实现运算符的重载时,重载函数的当用友元函数实现运算符的重载时,重载函数的 参数个数只能是参数个数只能是1 个或个或 2 个。个。 分别实现:分别实现:一元一元运算符重载、运算符重载、二元二元运算符重载运算符重载3.两种重载方式的比较两种重载方式的比较 成员实现:成员实现:将将 c1+c2 解释为:解释为:c1.operator +(c2)友元实现:友元实现:将将 c1+c2 解释为:解释为:operator +(c1, c2)成员实现:成员实现:将将 c1+5.6 解释为:解释为:c1.operator +(5.6)友元实现:友元实现:将将 c1+5.6 解释为:解释为:operator +(c1, 5.6)成员实现:成员实现:将将 5.6 + c1解释为:解释为: 5.6.operator + (c1) 友元实现:友元实现:将将 5.6 + c1解释为:解释为:operator + ( 5.6, c1)因此:因此:对一般的二元运算符重载为友元函数比重载为成员函对一般的二元运算符重载为友元函数比重载为成员函数更优越。数更优越。但是对于赋值运算符,将其重载为成员函数较好,因但是对于赋值运算符,将其重载为成员函数较好,因为赋值运算符是一个二元运算符,为赋值运算符是一个二元运算符,其语法格式为其语法格式为 =,第一个运算量必须是对象(变量也是对象),通过对象第一个运算量必须是对象(变量也是对象),通过对象调用成员函数比较自然。调用成员函数比较自然。若重载为友元,则可能会出现若重载为友元,则可能会出现5.6=c这样的表达式,与赋这样的表达式,与赋值表达式的语义不一致。值表达式的语义不一致。 定义友元的目的是在友元函数中直接访问类的私有成员,定义友元的目的是在友元函数中直接访问类的私有成员,实际上,也可以通过公有函数接口访问类的私有成员,实际上,也可以通过公有函数接口访问类的私有成员,所以所以实现运算符重载,可以即不用成员函数,实现运算符重载,可以即不用成员函数,也不用友元函数。也不用友元函数。class Complex float Real, Image;public: Complex(double r=0, double i=0) Real=r; Image=i; void SetReal(double Real) Complex:Real = Real; void SetImage(double Image) Complex:Image = Image; double GetReal( ) return(Real); double GetImage( ) return(Image); ; 4.使用非成员、非友元实现运算符的重载使用非成员、非友元实现运算符的重载例例13.4:Complex operator +(Complex &c1, Complex &c2) /二元运算二元运算 Complex t; t.SetReal( c1.GetReal( )+c2.GetReal( ) ); t.SetImage( c1.GetImage( )+c2.GetImage( ) ); return t; void main( )Complex c1(2, 3), c2(4, 8), c3;c3 = c1+c2;c3.Show( );编译器将编译器将 c1+c2 解释为:解释为:operator +(c1, c2)对于对于Complex类,如有类,如有Complex c1(2, 3), c2; 则自动将则自动将 c2=c1; 处理成:处理成: c2.Real = c1.Real; c2.Image = c1. Image; 一般不会出现问题。一般不会出现问题。 5. 何时必须重载何时必须重载 = 和和 += 运算符?运算符? 相同类型的对象之间是可以直接赋值的,相同类型的对象之间是可以直接赋值的,C+将赋将赋值处理成两个对象的各个成员直接赋值。两个对象的值处理成两个对象的各个成员直接赋值。两个对象的对应数据成员逐一赋值。对应数据成员逐一赋值。例例13.5 在类中,用字符数组实现字符串。在类中,用字符数组实现字符串。 见见 “第第13章章 多态性多态性(例子例子).doc”Num数组数组Name数组数组Score整数整数stud1存储空间存储空间Num数组数组Name数组数组Score整数整数stud2存储空间存储空间程序中程序中A行使用赋值运算符进行对象整体赋值,行使用赋值运算符进行对象整体赋值, C+将其处理成各个成员逐一赋值将其处理成各个成员逐一赋值,如下图所示:如下图所示: :C+默认的处理是:默认的处理是:strcpy(stud2.Num, stud1.Num); strcpy(stud2.Name, stud1.Name); stud2.Score = stud1.Score; 。 但是如果对象的成员中有成员指向动态分配的但是如果对象的成员中有成员指向动态分配的 数据空间就会出现问题。数据空间就会出现问题。 例例13.6 在类中,用指针实现字符串,在类中,用指针实现字符串, 即字符串的空间是动态分配的。即字符串的空间是动态分配的。 class Studentchar *Nump; /学号,注意:用指针实现学号,注意:用指针实现char *Namep; /姓名,注意:用指针实现姓名,注意:用指针实现int Score; /成绩成绩public: Student(char *nump=NULL, char *namep=NULL, int score=0) if(nump) /构造函数构造函数 Nump = new charstrlen(nump)+1;strcpy(Nump, nump); /动态分配存储空间动态分配存储空间 else Nump=NULL; if(namep) Score=score;Student( ) / 析构函数,释放指针指向的空间析构函数,释放指针指向的空间 if(Nump)delete Nump;if(Namep)delete Namep;void Show( ) if(Nump & Namep) coutNum=NumptName=NameptScore=Scoreendl;void main( )Student stud1(01201, Mary, 88), stud2;stud2=stud1; /Astud1.Show( );stud2.Show( );cout.flush( ); /BNump指针指针Namep指针指针Score整数整数stud2存储空间存储空间Nump指针指针Namep指针指针Score整数整数stud1存储空间存储空间01201Mary首先撤消对象首先撤消对象stud2,然后撤消对象然后撤消对象stud1 ,出问题!出问题!同一对象被撤销两次。同一对象被撤销两次。 编译器将编译器将A行处理成:行处理成:解决办法,在类中增加赋值解决办法,在类中增加赋值 = 重载函数:重载函数:Student & operator=(const Student &stud) / 返回值为对象自身的引用返回值为对象自身的引用 if(Nump)delete Nump; / C 释放对象自身的原串空间释放对象自身的原串空间 if(Namep)delete Namep; / D 释放对象自身的原串空间释放对象自身的原串空间 if(stud.Nump) / 根据赋值对象的空间大小给被赋值对象分配空间根据赋值对象的空间大小给被赋值对象分配空间 Nump = new charstrlen(stud.Nump)+1;strcpy(Nump, stud.Nump); else Nump=NULL; if(stud.Namep) / 根据赋值对象的空间大小给被赋值对象分配空间根据赋值对象的空间大小给被赋值对象分配空间 Namep = new charstrlen(stud.Namep)+1;strcpy(Namep, stud.Namep); else Namep=NULL; Score=stud.Score; return *this; / *this是对象自身是对象自身 Nump指针指针Namep指针指针Score整数整数stud2存储空间存储空间Nump指针指针Namep指针指针Score整数整数stud1存储空间存储空间01201Mary01201Mary在赋值时,为目的对象的指针重新分配指向的字符在赋值时,为目的对象的指针重新分配指向的字符串空间。串空间。增加赋值增加赋值 = 重载函数后,对象赋值后的存储空间如重载函数后,对象赋值后的存储空间如下下:这样,程序结束时,分别撤销两个对象,程序正确这样,程序结束时,分别撤销两个对象,程序正确运行!运行!6对于对于+=(或(或=)运算符,重载函数的返回值为)运算符,重载函数的返回值为void类型或本类类型对象的区别类型或本类类型对象的区别 例例13.7 见见 “第第13章章 多态性多态性(例子例子).doc”若若重载为返回重载为返回void类型,则本类对象不可连续赋值。类型,则本类对象不可连续赋值。若重载为返回本类类型,则本类对象可以连续赋值。若重载为返回本类类型,则本类对象可以连续赋值。7对于对于+=(或(或=)运算符,返回本类对象与返回)运算符,返回本类对象与返回本类对象的引用的区别本类对象的引用的区别 比较下面两例,先看比较下面两例,先看第一个例子:第一个例子: Complex Complex :operator+=(const Complex &c) Real+=c.Real; Image+=c.Image; return *this; 此函数的返回值为本类对象,此函数的返回值为本类对象,C+的处理是:用返回的处理是:用返回的对象值的对象值*this初始化内存临时对象(调用拷贝构造初始化内存临时对象(调用拷贝构造函数),从本函数返回后,函数),从本函数返回后,用内存临时对象作为调用用内存临时对象作为调用函数的结果。函数的结果。 再看第二个例子:再看第二个例子:Complex Complex :operator+=(const Complex &c) Real+=c.Real; Image+=c.Image; Complex temp = *this ;return temp;C+的的处处理理是是:调调用用拷拷贝贝构构造造函函数数,用用返返回回的的对对象象值值temp初初始始化化内内存存临临时时对对象象,内内存存临临时时对对象象作作为为调调用用函数的结果。函数的结果。 从上面两个小例子可以看出,从上面两个小例子可以看出,若重载函数返回对象值,若重载函数返回对象值,则返回则返回*this和返回和返回temp均可。均可。但要注意,但要注意,因为返回对象值时需要调用拷贝构造函数因为返回对象值时需要调用拷贝构造函数初始化内存临时对象,因此若对象有动态分配的存储初始化内存临时对象,因此若对象有动态分配的存储空间,就必须定义拷贝构造函数。空间,就必须定义拷贝构造函数。 第三个例子:第三个例子: Complex & Complex :operator+=(const Complex &c) Real+=c.Real; Image+=c.Image;return *this; 本例的返回值为本类对象的引用,本例的返回值为本类对象的引用,不需初始化内存临时对不需初始化内存临时对象,象,返回值是对象自身,即返回值是对象自身,即*this。因为不需要初始化内因为不需要初始化内存临时对象,效率较高,所以像存临时对象,效率较高,所以像 +=、= 等改变对象值的等改变对象值的重载函数最好返回对象的引用。重载函数最好返回对象的引用。 第四个例子:第四个例子:Complex & Complex :operator+=(const Complex &c) Real+=c.Real; Image += c.Image; Complex temp = *this ;return temp;此此时时,出出现现问问题题。因因为为返返回回的的是是对对象象的的引引用用(即即对对象象自自身身,不不借借助助于于内内存存临临时时对对象象),系系统统要要求求在在执执行行流流程程返返回回到到调调用用处处时时,返返回回值值是是存存在在的的。但但是是本本例例返返回回值值是是函函数数内内部部的的局局部部对对象象temp,而而局局部部对对象象在在函函数数返返回回前前,其其空空间间是是被被撤撤消的。消的。结论:结论: 返返回回本本类类对对象象时时,可可以以用用对对象象自自身身和和局局部部对对象象做做为为返返回回值(有时需要定义拷贝构造函数)。值(有时需要定义拷贝构造函数)。 返回对象的引用时,不能用局部对象做为返回值。返回对象的引用时,不能用局部对象做为返回值。例例13.8对于字符串类,重载对于字符串类,重载 = 运算符,返回对象自身运算符,返回对象自身的引用。就本例而言,可以不定义拷贝构造函数,程序的引用。就本例而言,可以不定义拷贝构造函数,程序能正确运行。能正确运行。 见见 “第第13章章 多态性多态性(例子例子).doc”8.调用调用拷贝构造函数拷贝构造函数 和和 调用调用赋值运算符重载函数赋值运算符重载函数的时机的时机 class Complexdouble Real, Image;public:Complex(double r=0, double i=0) / 构造函数构造函数 Real=r; Image=i; Complex(Complex &c) / 拷贝构造函数拷贝构造函数 Real=c.Real; Image=c.Image; coutCall copy Real Imagen; ;void main( )Complex c1(2, 3), c2(4, -2);Complex c3=c1; c1=c2; 程序的运行结果是?程序的运行结果是?调用了拷贝构造函数调用了拷贝构造函数未调用拷贝构造函数未调用拷贝构造函数Call copy 2 3v只有在产生新对象时,调用构造函数。只有在产生新对象时,调用构造函数。v用已有对象初始化新产生的对象时,用已有对象初始化新产生的对象时, 调用拷贝构造函数。调用拷贝构造函数。v赋值运算赋值运算 = , 不调用拷贝构造函数。不调用拷贝构造函数。13.2.3 类型转换函数将本类对象转换成其他类对象类型转换函数将本类对象转换成其他类对象例例: Complex c(3, 2); double x=6.2;如果有如果有: c=x; /类型不一致类型不一致 或或: x=c; /类型不一致类型不一致则则系统自动处理为系统自动处理为: c = Complex(x); /需作类型转换需作类型转换 x = double(c); /需需作类型转换作类型转换对于对于,通过以前所学的,通过以前所学的构造函数构造函数实现类型转换,实现类型转换, 将将其他类型的数据转换成本类数据其他类型的数据转换成本类数据。对于对于,使用本节将介绍的类型转换,使用本节将介绍的类型转换运算符重载函数运算符重载函数, 将本类数据转换成将本类数据转换成其他类型的数据。其他类型的数据。构造函数:构造函数:Complex:Complex(double r) real = r ; image = 0; 定义类型转换函数定义类型转换函数: 功能:功能:将将 类对象自动转换成类对象自动转换成 类型对象类型对象类型转换函数只能用成员函数实现类型转换函数只能用成员函数实现, 不能用友元函数实现。不能用友元函数实现。在类内定义类型转换函数的一般格式为:在类内定义类型转换函数的一般格式为:operator ( ) . 在类外定义类型转换函数的一般格式为:在类外定义类型转换函数的一般格式为: : operator ( ) . 该函数不能有参数,该函数不能有参数,不能写返回值类型,不能写返回值类型, 因返回值类型已确定。因返回值类型已确定。例例13.10 类型转换函数的定义和使用类型转换函数的定义和使用 #include class Complexdouble Real, Image;public:Complex(double r=0, double i=0) Real=r; Image=i; operator double ( ) / A 类型转换函数,类型转换函数, / 将将 Complex 类转换成类转换成 double 类类 return Real; ;void main( ) Complex c(3,2);double x;x = c; / B cout x= x endl;/ 即即 x = double (c) ;系统自动处理为系统自动处理为 x = c.operator double( );例例13.11 成员函数和类型转换函数的比较成员函数和类型转换函数的比较见见 “第第13章章 多态性多态性(例子例子).doc”其中主函数为:其中主函数为:void main(void)RMB r(23, 8, 6);int r1, r2, r3;r1= r ; /处理成处理成 r1 = r.operator int( );r2= int(r); /处理成处理成 r2 = r.operator int( ) ;r3= r.GetFen( ); coutr1=r1endl;coutr2=r2endl;coutr3=r3endl; 使用使用类型自动转换类型自动转换 较方便、自然。较方便、自然。13.2.4 其他运算符的重载其他运算符的重载1重载重载+、-运算符运算符 重载重载前置前置 + 运算符的运算符的成员成员函数的一般格式为:函数的一般格式为: :operator+ ( ) . 重载重载后置后置 + 运算符的运算符的成员成员函数的一般格式为:函数的一般格式为: :operator+ (int) . int 用于区分,没有实际意义,用于区分,没有实际意义,可给出实参,也可以不给出实参。可给出实参,也可以不给出实参。重载重载前置前置 + 运算符的运算符的友元友元函数的一般格式为:函数的一般格式为:friend operator+ ( &obj ) . 重载重载后置后置+运算符的运算符的友元友元函数的一般格式为:函数的一般格式为:friend operator+ ( &obj, int) . int 用于区分,没有实际意义,用于区分,没有实际意义,可给出实参,也可以不给出实参。可给出实参,也可以不给出实参。例例13.12 实现实现+和和 - 的前置和后置运算符重载,的前置和后置运算符重载, 程序见程序见 “第第13章章 多态性多态性(例子例子).doc”13.2.5 字符串类字符串类C+ 提供的字符串处理能力较弱,如不能用运算符直提供的字符串处理能力较弱,如不能用运算符直接对字符串对象进行加、减等操作,接对字符串对象进行加、减等操作,而必须通过字符串处理函数来实现拷贝、连接等操作。而必须通过字符串处理函数来实现拷贝、连接等操作。如:如:char s110=abc, s210=123; strcpy(s1, s2); /不能写成不能写成 s1= s2; strcat(s1, s2); /不能写成不能写成 s1= s1+s2;能否定义一个字符串类:能否定义一个字符串类:String 实现:实现: String s1(abc), s2(123), s3; s1 = s2; s3 = s1 + s2;能能! 可以利用可以利用C+提供的运算符重载实现。提供的运算符重载实现。例例13.18 定义字符串类定义字符串类String,并测试重载的运算符并测试重载的运算符以及成员函数以及成员函数 程序见程序见 “第第13章章 多态性多态性(例子例子).doc”,或,或阅读教材阅读教材上的程序。上的程序。重点讲解:重点讲解:(1) 说明:函数名后的说明:函数名后的 const(2) (拷贝拷贝)构造函数,在主函数中如何使用?构造函数,在主函数中如何使用?(3) 重载赋值重载赋值 = 运算符运算符(4) 重载重载 + 运算符运算符(5) 类型转换函数类型转换函数 operator const char *(6) 删除子串图示见下页删除子串图示见下页删除子串:删除子串:String operator - (const String &s1, const char *s2 ) lenp1p2s1.Strps2t.Strp13.3 静态联编静态联编联编是指一个计算机程序彼此关联的过程。联编是指一个计算机程序彼此关联的过程。 在本章中指在本章中指函数间调用关系的确定函数间调用关系的确定。按照联编所确定的时刻不同,可分为两种:按照联编所确定的时刻不同,可分为两种: 静态联编静态联编和和动态联编动态联编。 静态联编是指联编出现在编译连接阶段,静态联编是指联编出现在编译连接阶段, 即函数调用关系的确定是在程序执行之前。即函数调用关系的确定是在程序执行之前。这种联编又称早期联编,这种联编又称早期联编, 通过这种联编可实现静态多态。通过这种联编可实现静态多态。 例例13.20 普通函数的静态联编普通函数的静态联编#include int add(int a, int b) /重载函数重载函数1 return(a+b); double add(double a, double b) /重载函数重载函数2 return(a+b); void main( ) coutadd(1, 2)t; /编译时确定调用重载函数编译时确定调用重载函数1 coutadd(1.1, 2.2)n; /编译时确定调用重载函数编译时确定调用重载函数2在在编编译译连连接接阶阶段段,就就能能根根据据参参数数的的个个数数和和类类型型确确定定调调用用的的是哪一个函数。是哪一个函数。 例例13.21 读书上程序读书上程序 重点讲解:重点讲解:double CalcArea( Point &p ) return(p.Area( ); /A 编译连接时确定调用函数编译连接时确定调用函数 1void main( ) Rectangle r(0, 0, 1, 1); Circle c(0, 0, 1); coutCalcArea( r )t; coutCalcArea( c )n;Point Rectangle Circle能否找到一种机制,能否找到一种机制,让让CalcArea( )函数变成一个函数变成一个通用的求面积的函数。通用的求面积的函数。这就是这就是C+提供的动态联编和提供的动态联编和虚函数应完成的工作。虚函数应完成的工作。 用派生类实参初始化基类型参,用派生类实参初始化基类型参,p只能引用基类的成员。只能引用基类的成员。13.4 动态联编和虚函数动态联编和虚函数运行阶段才能确定函数的调用关系,这就是动态联编运行阶段才能确定函数的调用关系,这就是动态联编 动态联编又称滞后联编、晚期联编,动态联编又称滞后联编、晚期联编, 动态联编技术能实现动态多态。动态联编技术能实现动态多态。 必须将类的成员函数定义成虚函数,必须将类的成员函数定义成虚函数, 才可以实现动态联编。才可以实现动态联编。 将成员函数定义成虚函数的格式为:将成员函数定义成虚函数的格式为:virtual ( ) 13.4.1 虚函数虚函数重点讲解:重点讲解:double CalcArea(Point &p) return( p.Area( ) ); /A Area( )是虚函数是虚函数void main( ) Point p(1, 2); Rectangle r(0, 0, 1, 1); Circle c(0, 0, 1); coutCalcArea( p )t CalcArea( r )t CalcArea( c )n;例例13.22 阅读书上程序阅读书上程序Area( )是虚函数是虚函数 ,C+ 规定,规定,在在 A 行保留相关的行保留相关的三个虚函数入口地址三个虚函数入口地址 。在程序的运行阶段,在程序的运行阶段,根据实参的类型来确定根据实参的类型来确定调用哪一个虚函数。调用哪一个虚函数。通用的通用的求求面积函数面积函数(1)派生类的虚函数必须与基类虚函数同名,)派生类的虚函数必须与基类虚函数同名, 且参数的类型、个数、顺序必须一致,且参数的类型、个数、顺序必须一致, 否则,属于函数重载,而不是虚函数。否则,属于函数重载,而不是虚函数。有关虚函数的说明:有关虚函数的说明: (2)基类中虚函数前的关键字)基类中虚函数前的关键字virtual不能缺省。不能缺省。 (3)必须通过基类对象的)必须通过基类对象的指针或引用指针或引用调用虚函数,调用虚函数, 才能实现动态多态。才能实现动态多态。 (4)虚函数必须是类的成员函数,不能是友元函数,)虚函数必须是类的成员函数,不能是友元函数, 也不能是静态成员函数。也不能是静态成员函数。 (5)不能将构造函数定义为虚函数,)不能将构造函数定义为虚函数, 但可将析构函数定义为虚函数。但可将析构函数定义为虚函数。 (6)调用虚函数速度较慢。因为,要实现动态多态,)调用虚函数速度较慢。因为,要实现动态多态, 在函数调用处必须保留多个虚函数的入口地址。在函数调用处必须保留多个虚函数的入口地址。例例13.23 在成员函数中调用在成员函数中调用虚函数虚函数程序见程序见 “第第13章章 多态性多态性(例子例子).doc”,或阅读教材上的程序。或阅读教材上的程序。我们注意,我们注意,在成员函数中调用成员函数时,在成员函数中调用成员函数时,系统都是通过对象自身的指针系统都是通过对象自身的指针this调用的,调用的,A类中的类中的fun2( )的实际被处理成如下形式:的实际被处理成如下形式:void fun2( ) cout A:fun2 fun3( ); /EB b;b.fun1( ); / 调用调用A类的类的fun1( )和和fun2( ),在,在A类的类的fun2( )函数中,函数中, / 在在E行行, this 是指向是指向b的指针的指针, 所以调用所以调用B的的fun3( )函数函数class Apublic: A( ) fun( ); virtual void fun( ) cout A:fun t; ;class B: public A public: B( ) fun( ); void fun( ) cout B:fun t; void g( ) fun( ); / 在成员函数中调用在成员函数中调用虚函数虚函数; A B C 例例13.24 在构造函数中调用在构造函数中调用虚函数虚函数class C: public B public:C( ) fun( ); void fun( ) cout C:fun n; ;void main( ) C c; /依次调用依次调用A、B、C三类的缺省构造函数三类的缺省构造函数 c.g( );运行结果:运行结果:A:funB:funC:funC:fun构造函数调用虚函数,调用的是类本身的虚函数。构造函数调用虚函数,调用的是类本身的虚函数。成员函数调用成员函数调用虚函数虚函数,遵循动态多态性原则。遵循动态多态性原则。ABC13.4.2 虚析构函数虚析构函数如果类的构造函数中有如果类的构造函数中有动态申请动态申请的存储空间,的存储空间,在析构函数中在析构函数中应释放该空间应释放该空间。此时,建议将析构函数定义为虚函数,此时,建议将析构函数定义为虚函数,以便实现以便实现通过基类的指针或引用通过基类的指针或引用撤消派生类对象撤消派生类对象时的多态性。时的多态性。 #include class Achar *Aptr;public:A( ) Aptr = new char100; A( ) /析构函数不是虚函数析构函数不是虚函数 delete Aptr; coutDelete Aptrendl; ;例例: 析构函数不是虚函数的情况析构函数不是虚函数的情况class B : public A char *Bptr; public: B( ) Bptr=new char100; B( ) delete Bptr; coutDelete Bptrendl; ;void main(void)B b; 输出结果:输出结果:Delete BptrDelete Aptr系统自动撤销派生类对象,系统自动撤销派生类对象,不需要将析构函数定义为虚函数。不需要将析构函数定义为虚函数。void main(void)A *p=new B; delete p;A &p1=*(new B); delete &p1;输出结果:输出结果:Delete AptrDelete Aptr如果将析构函数定义成虚析构函数(如下),如果将析构函数定义成虚析构函数(如下),则运行结果为:则运行结果为:Delete BptrDelete AptrDelete BptrDelete Aptrvirtual A( ) B类的析构函数自然类的析构函数自然也是虚析构函数。也是虚析构函数。如果如果通过基类的指针或引用通过基类的指针或引用指向或引用派生类对象。指向或引用派生类对象。则输出是?则输出是?结论:结论:对于虚函数,对于虚函数,若用派生类的指针或对象若用派生类的指针或对象初始化基类的指针或引用,初始化基类的指针或引用,则通过该指针或引用调用则通过该指针或引用调用虚函数,遵循派生类规则。虚函数,遵循派生类规则。 例例13.26 虚析构函数虚析构函数 程序见程序见 “第第13章章 多态性多态性(例子例子).doc”,或阅读教材上的程序。或阅读教材上的程序。BaseAB13.5 纯虚函数和抽象类纯虚函数和抽象类 在定义基类时,会遇到这样的情况:无法定义基类中在定义基类时,会遇到这样的情况:无法定义基类中 虚函数的具体实现,其实现依赖于其不同的派生类。虚函数的具体实现,其实现依赖于其不同的派生类。考虑例考虑例13.22有关有关“形状形状”的类,从日常抽象思维的角的类,从日常抽象思维的角度考虑,能否定义一个度考虑,能否定义一个“形状形状”类类Shape,对形状的通对形状的通用操作可能有:求这个形状的面积、绘制这个形状的用操作可能有:求这个形状的面积、绘制这个形状的图形等。图形等。当然它是一个抽象的类,我们无法真正定义当然它是一个抽象的类,我们无法真正定义对这个形状操作的具体实现。可以由对这个形状操作的具体实现。可以由Shape类派生出具类派生出具体的体的“点点”类类Point、“长方形长方形”类类Rectangle、“圆圆”类类Circle等,在派生类中实现具体的求不同形状的面积等,在派生类中实现具体的求不同形状的面积和绘制不同形状的图形的操作。和绘制不同形状的图形的操作。 可以把基类中的虚函数定义为可以把基类中的虚函数定义为纯虚函数纯虚函数。 把至少包含一个纯虚函数的类称为把至少包含一个纯虚函数的类称为抽象类抽象类。 在抽象类的派生类中,应写出纯虚函数的实现,在抽象类的派生类中,应写出纯虚函数的实现, 否则,派生类依然是抽象类。否则,派生类依然是抽象类。 抽象类只能作基类,不能定义抽象类的对象。抽象类只能作基类,不能定义抽象类的对象。定义纯虚函数的一般格式为:定义纯虚函数的一般格式为:virtual ( ) = 0 ; 没有函数体。函数参数列表圆括号后面的没有函数体。函数参数列表圆括号后面的“= 0”,表示将函数名的值赋予,表示将函数名的值赋予0。 例例13.27 定义抽象类,派生出若干类,定义抽象类,派生出若干类, 在派生类中实现纯虚函数。在派生类中实现纯虚函数。程序见程序见 “第第13章章 多态性多态性(例子例子).doc”,或阅读教材上的程序。或阅读教材上的程序。 Point RectangleCircle Shape 重点讲解,见下页:重点讲解,见下页:double CalcArea(Shape &s) return(s.Area( ); /通通过过基基类类对对象象的的引引用用实实现现动动态态多多态态void DrawShape(Shape *sp) sp-Draw( ); / 通过基类对象的指针实现动态多态通过基类对象的指针实现动态多态 void main( )Point p(1, 2);Rectangle r(0, 0, 1, 1);Circle c(0, 0, 1);coutCalcArea(p)tCalcArea(r)t CalcArea(c)n;DrawShape(&p);DrawShape(&r);DrawShape(&c);运行结果是?运行结果是?本章完本章完
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号