版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、1第第3章章 多态性多态性n本章主要内容本章主要内容:n1、多态性的概念、多态性的概念n2、运算符重载、运算符重载n3、虚函数、虚函数n4、纯虚函数和抽象类、纯虚函数和抽象类21、 多态性的概念多态性的概念n多态性(Polymorphism)是面向对象程序设计的重要特性之一。n多态性主要体现在:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为。也就是说,每个对象可以用自己的方式去响应共同的消息。nC+支持两种形式的多态性,一种是编译时的多态性,称为静态联编。一种是运行时多态,称为动态联编。n多态的实现:n函数重载n运算符重载n虚函数32、运算符重载、运算符重载 在以前的学习中,C
2、+中预定义的运算符的操作对象只能是基本数据类型如int或float等。实际上,对于很多用户自定义的类型(如类),也需要有类似的运算操作。例如复数类例如复数类Complex。class Complexpublic: Complex () real=image=0; Complex (double r, double i)4运算符重载运算符重载 real = r, image = i; void Print();private: double real, image;void Complex:Print() if(image0) coutrealimagei; else coutreal+imag
3、ei;5运算符重载运算符重载n用用“+”、“-”能够实现复数的加减运算吗?能够实现复数的加减运算吗?n实现复数加减运算的方法实现复数加减运算的方法 重载重载“+”、“-”运算符运算符6运算符重载的实质运算符重载的实质n运算符重载是对已有的运算符赋予多重含义n必要性nC+中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)n实现机制n将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。n编译系统对重载运算符的选择,遵循函数重载的选择原则。7运算符重载运算符重载 C+中运算符的重载虽然给我们设计程序带来很多的方便,但对运算符的重载时,以下的几种情况
4、需要注意:(1)一般来说,不改变运算符原有含义,只让它能针对新类型数据的实际需要,对原有运算符进行适当的改造。例如,重载“+”运算符后,它的功能还是进行加法运算。(2)重载运算符时,不能改变运算符原有的优先级别,也不能改变运算符需要的操作数的数目。重载之后运算符的优先级和结合性都不会改变。8运算符重载运算符重载(3)不能创建新的运算符,只能重载c+中已有的运算符。(4)有些运算符不能进行重载。如:“.”类成员运算符、“*”类指向运算符、“:”类作用域运算符、“?:”条件运算符及“sizeof”求字节数运算符。n运算符重载不能滥用运算符重载不能滥用,只有当用户自定义类型上只有当用户自定义类型上的
5、操作与内置运算符之间存在逻辑对应关系时,的操作与内置运算符之间存在逻辑对应关系时,重载的运算符才能使程序显得更自然、更直观。重载的运算符才能使程序显得更自然、更直观。9运算符重载的实现运算符重载的实现运算符重载的本质就是函数重载。在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,这个过程是在编译过程中完成的。 运算符重载形式有两种:重载为类的成员函数和重载为类的友元函数。 10运算符重载的实现运算符重载的实现1. 1. 运算符重载为类的成员函数运算符重载为类的成员函数 语法形式如下: 函数类型 operator
6、 运算符(形参表) 函数体;11运算符重载的实现运算符重载的实现 2. 运算符重载为类的友元函数运算符重载为类的友元函数 运算符重载还可以为友元函数。当重载友元函数时,将没有隐含的参数this指针。语法形式如下: friend 函数类型 operator 运算符(形参表);n重载为类成员函数时 参数个数=原操作数个数-1 (后置+、-除外)n重载为友元函数时 参数个数=原操作数个数,且至少应该有一个自定义类型的形参12双目运算符重载双目运算符重载n双目运算符双目运算符 Bn如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被
7、重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。n经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)13双目运算符重载双目运算符重载将将”+”+”、”-”-”运算重载为复数类的成员函数。运算重载为复数类的成员函数。n 规则规则:n实部和虚部分别相加减。n 操作数操作数:n两个操作数都是复数类的对象。14#includeusing namespace std;class complex /复数类声明复数类声明public: /外部接口外部接口complex(double r=0.0,double i=0.0)real=r;ima
8、g=i; /构造函数构造函数complex operator + (complex c2); /+重载为成员函数重载为成员函数complex operator - (complex c2); /-重载为成员函数重载为成员函数void display();/输出复数输出复数private:double real;/复数实部复数实部double imag;/复数虚部复数虚部;15complex complex:operator +(complex c2) /重载函数实现重载函数实现complex c;c.real=c2.real+real;c.imag=c2.imag+imag;return co
9、mplex(c.real,c.imag);1、临时对象,返回值优化、临时对象,返回值优化 2、complex temp(c.real,c.imag);return temp;16complex complex:operator -(complex c2) /重载函数实现重载函数实现complex c;c.real=real-c2.real;c.imag=imag-c2.imag;return complex(c.real,c.imag);17void complex:display() cout(real,imag)endl; void main() /主函数主函数 complex c1(5,
10、4),c2(2,10),c3; /声明复数类的对象声明复数类的对象coutc1=; c1.display();coutc2=; c2.display();c3=c1-c2; /使用重载运算符完成复数减法使用重载运算符完成复数减法coutc3=c1-c2=;c3.display();c3=c1+c2;/使用重载运算符完成复数加法使用重载运算符完成复数加法coutc3=c1+c2=;c3.display();18程序输出的结果为:程序输出的结果为:c1=(5,4)c2=(2,10)c3=c1-c2=(3,-6)c3=c1+c2=(7,14)19例例 两字符串加法两字符串加法#include #in
11、clude class String char name256;public: String(char* str) strcpy(name,str); String() String() String operator+(const String&); void display() coutThe string is:nameendl;20static char* str;String String:operator+(const String& a)strcpy(str,name); strcat(str,); return String(str);void ma
12、in() str=new char256; String demo1(Visual c+); String demo2(6.0); demo1.display(); demo2.display();21 String demo3=demo1+demo2; demo3.display(); String demo4=demo3+Programming.; demo4.display(); delete str;此程序的运行结果为:此程序的运行结果为:The string is : Visual C+The string is : 6.0The string is : Visual C+ 6.0T
13、he string is : Visual C+ Programming.22运算符友元函数的设计运算符友元函数的设计n如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数。n双目运算符 B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2 )n如:将+、-(双目)重载为复数类的友元函数。两个操作数都是复数类的对象。23#includeusing namespace std;class complex/复数类声明复数类声明public:/外部接口外部接口complex(double r=0.0,double i
14、=0.0) real=r; imag=i; /构造函数构造函数friend complex operator + (complex c1,complex c2);/运算符运算符+重载为友元函数重载为友元函数friend complex operator - (complex c1,complex c2);/运算符运算符-重载为友元函数重载为友元函数void display();/显示复数的值显示复数的值private:/私有数据成员私有数据成员double real;double imag;24complex operator +(complex c1,complex c2)/运算符重载友元函
15、数实现运算符重载友元函数实现 return complex(c2.real+c1.real, c2.imag+c1.imag);complex operator -(complex c1,complex c2)/运算符重载友元函数实现运算符重载友元函数实现 return complex(c1.real-c2.real, c1.imag-c2.imag);25void complex:display() cout(real,imag)endl; void main() /主函数主函数complex c1(5,4),c2(2,10),c3; /声明复数类的对象声明复数类的对象coutc1=; c1
16、.display();coutc2=; c2.display();c3=c1-c2;/使用重载运算符完成复数减法使用重载运算符完成复数减法coutc3=c1-c2=;c3.display();c3=c1+c2;/使用重载运算符完成复数加法使用重载运算符完成复数加法cout mon_daydt.month-1) / 少一个闰年判断少一个闰年判断 dt.day = dt.day- mon_daydt.month-1; dt.month+; if(dt.month= =13) dt.month=1; dt.year+; return dt; 28单目运算符重载单目运算符重载 类的单目运算符可重载为一
17、个没有参数的非静态成员函数或者带有一个参数的非成员函数,参数必须是用户自定义类型的对象或者是对该对象的引用。一般情况下,单目运算符最好重载为成员函数。 在C+中,单目运算符有+和-,它们是变量自动增1和自动减1的运算符。在类中可以对这两个单目运算符进行重载。29单目运算符重载单目运算符重载 如同“+”运算符有前缀、后缀两种使用形式,“+”和“-”重载运算符也有前缀和后缀两种运算符重载形式,以“+”重载运算符为例,其语法格式如下: 函数类型 operator +(); /前缀运算 函数类型 operator +(int);/后缀运算 使用前缀运算符的语法格式如下: +对象; 使用后缀运算符的语法
18、格式如下: 对象+;30例:运算符前置例:运算符前置+和后置和后置+重载为时钟类的成员函数重载为时钟类的成员函数n前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参。n操作数是时钟类的对象。n实现时间增加1秒钟。31#includeusing namespace std;class Clock/时钟类声明时钟类声明 public:/外部接口外部接口 Clock(int NewH=0, int NewM=0, int NewS=0); void ShowTime(); Clock operator +(); /前置单目运算符重载前置单目运算符重载 Clock ope
19、rator +(int); /后置单目运算符重载后置单目运算符重载 private:/私有数据成员私有数据成员 int Hour,Minute,Second;32Clock Clock:operator +() /前置单目运算符重载函数前置单目运算符重载函数Second+;if(Second=60) Second=Second-60; Minute+; if(Minute=60) Minute=Minute-60; Hour+; Hour=Hour%24; return *this;33/后置单目运算符重载后置单目运算符重载Clock Clock:operator +(int)/注意形参表中的
20、整型参数注意形参表中的整型参数 Clock old=*this; +(*this); return old;34/其它成员函数的实现略其它成员函数的实现略void main()Clock myClock(23,59,59);coutFirst time output:;myClock.ShowTime(); coutShow myClock+:; (myClock+).ShowTime(); coutShow +myClock:;(+myClock).ShowTime();35程序运行结果为:程序运行结果为:First time output: 23:59:59Show myClock+: 2
21、3:59:59Show +myClock: 0:0:136#include class Increasepublic: Increase(int x):value(x) Increase operator+(); /前增量前增量 Increase operator+(int); /后增量后增量 void display() cout the value is value endl; private: int value;Increase Increase:operator+() value+; /先增量先增量 return *this; /再返回原对象再返回原对象例例 成员函数成员函数37In
22、crease Increase:operator+(int) Increase temp(*this); /临时对象存放原有对象值临时对象存放原有对象值 value+; /原有对象增量修改原有对象增量修改 return temp; /返回原有对象值返回原有对象值void main() Increase n(20); n.display(); (n+).display(); /显示临时对象值显示临时对象值 n.display(); /显示原有对象显示原有对象 +n; n.display(); +(+n); n.display();38(n+)+; /第二次增量操作对临时对象进行第二次增量操作对临
23、时对象进行 n.display();此程序的运行结果为:此程序的运行结果为:the value is 20the value is 20the value is 21the value is 22the value is 23the value is 2439例例 友元函数友元函数#include class Increasepublic: Increase(int x):value(x) friend Increase operator+(Increase & ); /前增量前增量 friend Increase operator+(Increase &,int); /后增量
24、后增量 void display() cout the value is value endl; private: int value;40Increase operator+(Increase & a) a.value+; /前增量前增量 return a; /再返回原对象再返回原对象Increase operator+(Increase& a, int) Increase temp(a); /通过拷贝构造函数保存原有对象值通过拷贝构造函数保存原有对象值 a.value+; /原有对象增量修改原有对象增量修改 return temp; /返回原有对象值返回原有对象值void
25、main() Increase n(20); n.display();41 (n+).display(); /显示临时对象值显示临时对象值 n.display(); /显示原有对象显示原有对象 +n; n.display(); +(+n); n.display(); (n+)+; /第二次增量操作对临时对象进行第二次增量操作对临时对象进行 n.display();此程序的运行结果为:此程序的运行结果为:the value is 20the value is 20the value is 21the value is 22the value is 23the value is 2442特殊运算符
26、重载特殊运算符重载赋值运算符重载赋值运算符重载在C+中有两种类型的赋值运算符:一类是“+=”和“-=”等先计算后赋值的运算符,另一类是“=”即直接赋值的运算符。下面分别进行讨论。1 1运算符运算符“+=”+=”和和“-=”-=”的重载的重载【例】【例】 实现复数类实现复数类“+=”+=”和和“-=”-=”的重载。的重载。#includeclass Complexpublic:43 Complex(double r,double i) real=r;image=i;Complex operator -=(Complex& t); Complex operator +=(Complex&a
27、mp; t); Print();private:double real,image;Complex Complex:operator -=(Complex& t) real- = t.real;image-=t.image; return *this;44Complex:Print() if(image0) coutrealimageiendl;else coutreal+imageIendl;void main()Complex c1(5.0,3.0),c2(2.1,1.8),c3(5.3,4.2);c1-=c2; coutc1=; c1.Print();c3+=c2; coutc3
28、=;c3.Print();45赋值运算符重载赋值运算符重载2运算符运算符“=”的重载的重载【例【例3.5】 实现实现“=”运算符重载的示例。运算符重载的示例。#include#includeclass CMessage public:CMessage( ) buffer = NULL; CMessage() delete buffer; 46 void Display( ) coutbufferendl; void Set(char *string) if(buffer != NULL) delete buffer; buffer= new charstrlen(string)+1; strc
29、py(buffer, string); void operator=(const CMessage& Message) if(buffer != NULL) delete buffer; buffer= new charstrlen(Message.buffer)+1; strcpy(buffer, Message.buffer); 47private: char *buffer; void main( ) CMessage c1; c1.Set(initial c1 message); c1.Display( ); CMessage c2; c2.Set(initial c2 mes
30、sage); c2.Display( ); c1=c2; c1.Display( );48特殊运算符重载特殊运算符重载下标运算符重载下标运算符重载n 下标运算符下标运算符“ ”通常用于在数组中标识数通常用于在数组中标识数组元素的位置,下标运算符重载可以实现数组组元素的位置,下标运算符重载可以实现数组数据的赋值和取值。下标运算符重载函数只能数据的赋值和取值。下标运算符重载函数只能作为类的成员函数,不能作为类的友元函数。作为类的成员函数,不能作为类的友元函数。n下标运算符下标运算符“ ”函数重载的一般形式为:函数重载的一般形式为:ntype class_name:operator (arg_);4
31、9#include class Demo int Vector5;public:Demo() ;int &operator (int i) (return Vectori; );void main() Demo v; for(int i=0;i5;i+)vi=i+1;for(i=0;i5;i+)coutvi ;coutendl;此程序的运行结果为:此程序的运行结果为:1 2 3 4 5 503、联编和虚函数、联编和虚函数 n绑定绑定n程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程。按照绑定进行的阶段不同,可以分为两种不同的绑定方法:静态绑定和动态绑定。n静态
32、绑定(静态联编)静态绑定(静态联编)n联编工作出现在编译阶段,用对象名或者类名来限定要调用的函数。n动态绑定(动态联编)动态绑定(动态联编)n联编工作在程序运行时执行,在程序运行时才确定将要调用的函数,或称动态束定,又叫晚期联编。51绑定绑定n把函数体和函数调用相联系称为绑定(binding,也译作捆绑或编联)。C+中,默认的函数调用绑定方式是早绑定。早绑定又称为静态绑定,即在程序运行之前,由编译器和连接器实现。class employee void salary() ;class manager : public employeepublic:void salary()/*经理工资的计算和发
33、放经理工资的计算和发放*/;class programmer : public employeepublic:void salary()/*程序员工资的计算和发放程序员工资的计算和发放*/;52class parttime : public employeepublic:void salary()/兼职人员工资的计算和发放兼职人员工资的计算和发放;void payroll(employee& re) /payroll函数函数 re.salary(); n在payroll()函数中,对salary()的调用实施早绑定。53n晚绑定将绑定推迟到程序运行时,在程序运行时,获知实际接收消息的对
34、象的类型,根据这时的类型信息绑定函数调用,又称动态绑定或运行时绑定。n/如果使用晚绑定,运行下面的代码之前re.salary()没有和任何函数体联系manager Harry;programmer Ron; /真正执行re.salary()时进行动态函数绑定/re指向Harry, 将re.salary()动态绑定manager:salary()payroll(Harry);/re指向Ron, 将re.salary()动态绑定programmer:salarypayroll(Ron);54n在C+语言中,是通过将一个函数定义成虚函数来实现运行时的多态的。虚函数是动态绑定的基础。n是非静态的成员函
35、数。n在类的声明中,在函数原型之前写virtual。virtual 只用来说明类声明中的原型,不能用在函数实现时。n具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。n本质:不是重载声明而是覆盖。n调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定调用哪个函数。虚函数虚函数55class employeepublic: virtual void salary() /基类中声明的虚函数基类中声明的虚函数;/下面的派生类中重写下面的派生类中重写salary()都是虚函数都是虚函数class manager : public employeepublic
36、:void salary() /*经理工资的计算和发放经理工资的计算和发放*/;例:对上面的例子,定义为虚函数,分析运行结果例:对上面的例子,定义为虚函数,分析运行结果56class programmer : public employeepublic:void salary() /*程序员工资的计算和发放程序员工资的计算和发放*/ ;class parttime : public employeepublic:void salary() /*兼职人员工资的计算和发放兼职人员工资的计算和发放*/;void payroll(employee& re) re.salary(); /通过基类
37、引用调用虚函数,动态绑定通过基类引用调用虚函数,动态绑定57int main()manager Albus;payroll(Albus);/根据实际类型调用根据实际类型调用manager:salary()programmer Ron; payroll(Ron);/根据实际类型调用根据实际类型调用programmer:salary()parttime Lily;payroll(Lily);/根据实际类型调用根据实际类型调用parttime:salary()58例例#include class CPersonpublic: virtual void PrintInfo() /基类中的虚函数基类中的
38、虚函数 coutPersonn; ;class CWorker: public CPersonprivate: int kindofwork;59public: void PrintInfo () /在派生类在派生类worker中重新定义中重新定义 coutWorkern; void PrintotherInfo () coutother information of Workern; ;class CTeacher: public CPersonprivate: int subject;public: void PrintInfo () /在派生类中重新定义在派生类中重新定义 coutTea
39、chern;60 void PrintotherInfo () coutPrintInfo(); p = &t; p-PrintInfo(); p = &d; p-PrintInfo(); t.PrintInfo();61动态联编的工作机制动态联编的工作机制 编译器在执行过程中遇到virtual关键字的时候,将自动安装动态联编需要的机制,首先为这些包含virtual函数的类(注意不是对象)建立一张虚拟函数表VTABLE。在这些虚拟函数表中,编译器将依次按照函数声明次序放置类的特定虚函数的地址。 同时在每个带有虚函数的类中放置一个称之为vpointer的指针,简称vptr,这个指
40、针指向这个类的VTABLE。62动态联编的工作机制动态联编的工作机制vptr一般置于对象的起始位置,在对象的构造函数中将vptr初始化为本类的VTABLE的地址。 C+ 编译程序时候按下面的步骤进行工作: 1.为各类建立虚拟函数表,如果没有虚函数则不建立。 2.暂时不连接虚函数,而是将各个虚函数的地址放入虚拟函数表中。 3.直接连接各静态函数。63class shape public: virtual double area() const return 0; virtual void draw() /;class rectangle: public shape public: double
41、area() const return height * width; void draw()protected: double height, width;例例64class square: public rectangle public:void draw();class circle: public shape public: double area() const return PI* radius * radius; void draw()private: double radius;shape* sa = new circle, new rectangle, new rectang
42、le, new square;65square对象VPTRrectangle对象VPTRrectangle对象VPTRcircle对象VPTR对象shape*数组sa&circile:area()&circle:draw()&rectangle:area()&rectangle:draw()&rectangle:area()&square:draw()66n当通过基类指针调用一个虚函数时,例如上面的 sa0,它指向circle 对象的起始地址。编译器从sa0指向的对象中取出VPTR,根据VPTR 找到相应的虚函数表 VTABLE。再根据函数在VT
43、ABLE中的偏移,找到到适当的函数。 因此不是根据指针的类型shape*决定调用shape:area(),而是调用“ VPTR+偏移量 ”处的函数。因为获取VPTR和确定实际的函数地址发生在运行时,所以就实现了晚绑定。 67n派生类如果重定义了基类中的虚函数,就在派生类的VTABLE中保存新版本虚函数的地址,没有重定义的仍使用基类虚函数的地址。同一个虚函数在派生类和基类的VTABLE中处于相同的位置。如果派生类增加了新的虚函数,则编译器先将基类的虚函数准确地映射到派生类的VTABLE中,再加入新增加的虚函数地址。只存在于派生类中的虚函数不能通过基类指针调用。 68#include using
44、namespace std;class B0/基类基类B0声明声明public: /外部接口外部接口virtual void display() /虚成员函数虚成员函数 coutB0:display()endl; ;class B1: public B0/公有派生公有派生 public: void display() coutB1:display()endl; ;class D1: public B1/公有派生公有派生 public: void display() coutD1:display()display(); void main()/主函数主函数 B0 b0, *p; /声明基类对象和
45、指针声明基类对象和指针B1 b1;/声明派生类对象声明派生类对象D1 d1;/声明派生类对象声明派生类对象p=&b0;fun(p);/调用基类调用基类B0函数成员函数成员p=&b1;fun(p);/调用派生类调用派生类B1函数成员函数成员p=&d1;fun(p);/调用派生类调用派生类D1函数成员函数成员运行结果:运行结果:B0:display()B1:display()D1:display()70关于虚函数有以下几点说明关于虚函数有以下几点说明(1)当基类中把成员函数定义为虚函数后,要达到动态联编的效果,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和
46、类型也必须相同。否则不能实现运行时多态。(2)基类中虚函数前的virtual不能省略,派生类中的虚函数的virtual关键字可以省略,缺省后仍为虚函数。(3)内联函数不能使虚函数。即使在类的内部定义,编译时仍将其看作非内联的。(4)虚函数必须是类的成员函数,不能是友员函数,也不能是静态成员函数。(5)不能将构造函数定义为虚函数,但可将析构函数定义为虚函数。71虚函数与重载函数的比较:虚函数与重载函数的比较:(1)重载函数要求函数有相同的函数名,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同;(2)重载函数可以是成员函数或友员函数,而虚函数只能是成员函数;(3)
47、重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;(4)虚函数在运行时表现出多态功能,这是C+的精髓;而重载函数则在编译时表现出多态性。72例例#include #include using namespace std;class Base public: virtual int f() const cout Base:f()n; return 1; virtual void f(string) const virtual void g() const ;73class Derived1 : public Base public:/覆盖虚
48、函数覆盖虚函数g(),继承了虚函数继承了虚函数f()和和f(string) void g() const ;class Derived2 : public Base public: /覆盖虚函数覆盖虚函数f(),隐藏了,隐藏了f(string),继承了,继承了g() int f() const cout Derived2:f()n; return 2;74class Derived3 : public Base public: / 编译错误:不能修改虚函数的返回类型编译错误:不能修改虚函数的返回类型: void f() const cout Derived3:f()n;class Derive
49、d4 : public Base public: / 改变虚函数的参数表,隐藏了改变虚函数的参数表,隐藏了f()和和f(string);继承了继承了g() int f(int) const cout Derived4:f()n; return 4; ;75int main() string s(hello); Derived1 d1; int x = d1.f();/ Base:f() d1.f(s);/ Base:f(string) Derived2 d2; x = d2.f();/Derived2:f() d2.f(s); / 错误错误: f(string)被隐藏被隐藏 Derived4
50、d4; x = d4.f(1);/Derived4:f(int) x = d4.f(); / 错误:错误:f() 被隐藏被隐藏 d4.f(s); /错误:错误: f(string)被隐藏被隐藏 Base& br = d4; / 向上类型转换向上类型转换 br.f(1); /错误:基类接口中没有错误:基类接口中没有f(int),基类指针不能调用派生,基类指针不能调用派生类中的成员类中的成员 br.f(); / Base: f() br.f(s); / Base:f(string) 76虚析构函数虚析构函数 在C+中,不能声明虚构造函数,因为在构造函数执行时,对象还没有完全构造好,不能按虚
51、函数方式进行调用。但是可以声明虚析构函数,如果用基类指针指向一个new生成的派生类对象,通过delete作用于基类指针删除派生类对象时,有以下两种情况:77虚析构函数虚析构函数(1)如果基类析构函数不为虚析构函数,则只会调用基类的析构函数,而派生类的析构函数不会被调用,因此派生类对象中派生的那部分内存空间无法析构释放。(2)如果基类析构函数为虚析构函数,则释放基类指针的时候会调用基类和派生类中的所有析构函数,派生类对象中所有的内存空间都将被释放,包括继承基类的部分。所以C+中的析构函数通常是虚析构函数。78例例 虚析构函数的用法和作用示例。虚析构函数的用法和作用示例。#include clas
52、s Base1 public: Base1() cout Base1()n; ;class Derived1 : public Base1 public: Derived1() cout Derived1()n; ;class Base2 public: virtual Base2() cout Base2()n; ;79class Derived2 : public Base2 public: Derived2() cout Derived2()n; ;void main() Base1* bp = new Derived1; delete bp; Base2* b2p = new Deri
53、ved2; delete b2p; 运行结果:运行结果:Base1() Derived2() Base2()80实现多态性的步骤实现多态性的步骤n使用虚函数实现多态性的一般步骤是:使用虚函数实现多态性的一般步骤是:n在基类中将需要多态调用的成员函数声明为virtual;n在派生类中覆盖基类的虚函数,实现各自需要的功能;n基类的指针或引用指向派生类对象,通过指针或引用调用虚函数。n这样,会根据基类的指针或引用实际指向的这样,会根据基类的指针或引用实际指向的(派生类)对象来调用派生类中的虚函数版本。(派生类)对象来调用派生类中的虚函数版本。如果派生类没有覆盖基类的虚函数,则基类中如果派生类没有覆盖
54、基类的虚函数,则基类中的虚函数被调用。的虚函数被调用。81n需要注意的是,需要注意的是,只有通过基类的指针或引用才只有通过基类的指针或引用才能实现虚函数的多态调用,能实现虚函数的多态调用,通过对象调用虚函通过对象调用虚函数不会引起多态调用。数不会引起多态调用。C+ 为了减少程序运为了减少程序运行时的开销,提高效率,编译时能确定的信息行时的开销,提高效率,编译时能确定的信息不会推迟到运行时处理。对虚函数的绑定是根不会推迟到运行时处理。对虚函数的绑定是根据实施调用的对象类型确定的,如果使用指针据实施调用的对象类型确定的,如果使用指针或引用调用虚函数,那么要到运行时才知道它或引用调用虚函数,那么要到
55、运行时才知道它们指向的实际对象类型,因而会进行晚绑定。们指向的实际对象类型,因而会进行晚绑定。如果直接使用对象调用虚函数,对象的类型在如果直接使用对象调用虚函数,对象的类型在编译时就知道了,不会进行晚绑定。编译时就知道了,不会进行晚绑定。82#include using namespace std;class Base public:virtual void vfunc()coutBase:vfunc()endl;class Derived1 : public Base public:void vfunc() coutDerived1:vfunc()endl;class Derived2 :
56、public Base public:void vfunc() coutDerived2:vfunc() vfunc();/运行时绑定到运行时绑定到pb这时指向这时指向b所属所属类型类型Base的的vfunc()pb = &d1; pb - vfunc();/运行时绑定到运行时绑定到pb这时指向这时指向d1所所属类型属类型Derived1的的vfunc()pb = &d2; pb - vfunc();/运行时绑定到运行时绑定到pb这时指向这时指向d2所所属类型属类型Derived2的的vfunc()85注意:注意:n基类指针(引用)即使在指向派生类对象时,基类指针(引用)即使在
57、指向派生类对象时,也只能调用基类接口中出现的成员函数,不能也只能调用基类接口中出现的成员函数,不能调用派生类中增加的成员函数,即使是虚函数。调用派生类中增加的成员函数,即使是虚函数。86class Base public:virtual void f();class Derived : public Base public:void f()virtual void g()/派生类增加的成员函数派生类增加的成员函数;int main()Base b;b.f(); /!b.g(); 这个错误我们容易发现这个错误我们容易发现,Base中没有中没有g()Derived d;d.f();/OKd.g()
58、;/OKBase* pb = &d;pb - f();/OK,多态调用,绑定到,多态调用,绑定到Derived:f()pb - g(); /错误错误 87动态绑定的实现动态绑定的实现n下面一段程序的运行结果是什么呢?下面一段程序的运行结果是什么呢? #include using namespace std;class B1int m;public: void f();class B2int m;public: virtual void f();class D1: public B1 int n; public:void f();88class D2: public B2 int n;
59、public:void f();class B3 int n;public: virtual void g() virtual void h();int main()coutsizeof(B1)=sizeof(B1)endl;coutsizeof(B2)=sizeof(B2)endl;coutsizeof(D1)=sizeof(D1)endl;coutsizeof(D2)=sizeof(D2)endl;coutsizeof(B3)=sizeof(B3)endl;89n程序的运行结果:程序的运行结果:sizeof(B1)=4sizeof(B2)=8sizeof(D1)=8sizeof(D2)=1
60、2sizeof(B3)=890对象的布局对象的布局mB1类对象布局VPTRmB2类对象布局&B2:f()B2的VTABLED1类对象布局B1:mnVPTRB2:mD2类对象布局&D2:f()D2的VTABLEn&B3:g()&B3:h()VPTRnB3类对象布局B3的VTABLE914 纯虚函数和抽象类纯虚函数和抽象类n面向对象设计中经常需要对一组具体类的共性进行抽面向对象设计中经常需要对一组具体类的共性进行抽象,自下而上形成更一般的基类,描述这组类的公共象,自下而上形成更一般的基类,描述这组类的公共接口。接口。n在这种向上抽象的过程中,我们发现,越上层的基类在这种向上抽象的过程中,我们发现,越上层的基类其抽象程度越高,有时甚至难以对它们的某些操作给其抽象程度越高,有时甚至难以对它们的某些操作给出具体描述。这些基类存在的目的已经不再是用来创出具体描述。这些基类存在的目的已经不再是用来创建实
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年F电力设施建设工程合同
- 机构综合课程设计
- 机床夹具与应用课程设计
- 机床维修课程设计
- 机床数控加工课程设计
- 机床变速器课程设计
- 机器人课程主题课程设计
- 绿建筑复合风管施工创新方案
- 地方国企职工安置与社会保障方案
- 2024年合作共建协议:工程项目的联建条款
- 万盛关于成立医疗设备公司组建方案(参考模板)
- 停线管理规定
- 《我和小姐姐克拉拉》阅读题及答案(一)
- 大型展会对城市会展业发展影响文献综述会展专业
- 乡镇结核病防治工作职责
- 机组启动试运行工作报告
- 礼仪队工作计划三篇
- 互补输出级介绍
- 中波广播发送系统概述
- (完整版)管道代号对照
- 市森林消防(防汛)专业队管理制度森林防火扑火队管理制度.doc
评论
0/150
提交评论