Presentation is loading. Please wait.

Presentation is loading. Please wait.

第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院.

Similar presentations


Presentation on theme: "第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院."— Presentation transcript:

1 第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院

2 什么是多态性 多态是指同样的消息被不同类型的对象接收时导致完全不同的行为。 多态可以分为: 编译时的多态,如函数重载、运算符重载
运行时的多态,虚函数 2019/1/17

3 运算符重载 当在使用一种程序设计语言编写程序时,我们不仅要设计新的数据类型,同时还要为新类型设计运算。一般地,用户定义类型的运算都是用函数的方式实现的。而在一般情况下,基本类型的运算都是用运算符表达的,这很直观,语义也简单。 如果直接将运算符作用在用户定义类型之上,那么编译器将不能识别运算符的语义。因此,在这种情况下,我们需要一种特别的机制来重新定义作用在用户定义类型上的普通运算符的含义。这就是运算符重载的简单概念。 2019/1/17

4 在C编译器里早就存在简单的运算符重载的概念。考虑整型和浮点型两种加法运算:
int a,b,c; c=a+b; mov eax,dword ptr [ebp-4] add eax,dword ptr [ebp-8] mov dword ptr [ebp-0Ch],eax float a,b,c; c=a+b; fld dword ptr [ebp-4] fadd dword ptr [ebp-8] fstp dword ptr [ebp-0Ch] 2019/1/17

5 什么是运算符重载 在原来预定义的运算符含义的基础上,再定义对于某个用户定义类型的对象进行操作的新的含义。这就是运算符重载。
运算符重载后,其优先级和结合性不变,所需的操作数也不能变。 2019/1/17

6 重载运算符 大多数系统预定义的运算符可以重载,但少数的C++运算符不能重载:
:: 、#、 ?:、 .、.*、* 另外,不是运算符的符号,如“;”等也不能重载。C++还不允许重载不存在的运算符,如“$”、“**”等。 2019/1/17

7 例:复数的加操作 c3 = c1.add(c2);这种使用方式不太直观。 我们更希望是如下方式: c3=c1+c2;
class Complex{ double re, im; public: Complex(double r=0.0, double i=0.0): re(r), im(i){ } Complex add(Complex c){ Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2), c2(3, 4); Complex c3 = c1.add(c2); } c3 = c1.add(c2);这种使用方式不太直观。 我们更希望是如下方式: c3=c1+c2; 2019/1/17

8 例:重载复数类的加运算符 class Complex{ …… public: Complex operator+(Complex c){
Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2), c2(3, 4); Complex c3 = c1 + c2; //相当于c3 = c1.operator+(c2); } operator+(…)称为运算符重载函数 c3 = c1 + c2称为operator+(…)函数的隐式调用 c3 = c1.operator+(c2)称为operator+(…)函数的显示调用 2019/1/17

9 例:计数器重载运算符 class counter{ unsigned int value; public:
counter( ){value=0;} void operator++( ); void operator--( ); unsigned int operator( )( ); }; void counter:: operator++( ){if (value<1000) value++;} void counter:: operator--( ){if (value>0) value--;} unsigned int counter:: operator( )( ){return value;} 2019/1/17

10 for(int i=0;i<10;i++){ ++my_counter;
void main( ) { counter my_counter; for(int i=0;i<10;i++){ ++my_counter; cout<<“my_counter=“<<my_counter( )<<endl; } --my_counter; 2019/1/17

11 运算符重载的语法形式 运算符是通过运算符重载函数来完成重载的。 运算符重载函数是成员函数时,语法形式为:
type list); type是返回类型 className是重载该运算符的类的类名 重载运算符时使用operator关键字, 当运算符函数不属于某个类时,一般将此函数声明为类的友元函数,语法形式为: type list); 2019/1/17

12 几点注意 当运算符被重载时,它是被绑定在一个特定的类型之上的。当此运算符不作用在特定类型上时,它将保持原有的含义。
当重载运算符时,不能创造新的运算符符号,例如不能用“**”来表示求幂运算符。 另外,我们应当尽可能保持重载运算符原有的语义。试想,如果在某个程序中用“+”表示减,“*”表示除,那么这个程序读起来将会非常别扭。 2019/1/17

13 用友元函数重载运算符 用成员函数重载运算符会碰到麻烦: class Complex{ …… public:
Complex operator+(Complex c){ Complex t; t.re = re + c.re; t.im = im + c.im; return t; } }; void main( ) { Complex c1(1, 2); c1 = c1 + 27; //正确,相当于c1=c1.operator+(Complex(27)) c1 =27+c1;//错误,被解释为无意义的c1=27.operator+(c1) } 2019/1/17

14 可以用友元函数实现运算符重载,此时能够很容易地将27隐式转换为Complex类型
class Complex{ …… public: friend Complex operator+(Complex c1, Complex c2){ Complex t; t.re = c1.re + c2.re; t.im = c1.im + c2.im; return t; } }; 请思考,能不能同时存在以下两个函数: Complex operator+(Complex c){…} friend Complex operator+(Complex c1, Complex c2){…} void main( ) { Complex c1(1, 2); c1 = c1 + 27; //正确,相当于c1=operator+(c1,Complex(27)) c1 =27+c1;//正确,相当于c1=operator+(Complex(27), c1) } 2019/1/17

15 用友元或成员函数重载运算符的一般原则 没有强制要求程序员必须用那种方式来重载运算符。
如果运算符的操作需要修改类对象的状态,则应该把它实现为成员函数。 对于诸如=、+=、++等需要左值操作数的运算符最好重载为成员函数。 如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,那么应该实现为友元函数。 =、()、[]、->不能重载为友元函数。 2019/1/17

16 例子:用友元重载运算符 class counter{ int value; public:
counter operator=(int num);//表示 对象=整数 friend counter operator+(counter obj,int num); //表示 对象+整数 friend counter operator+(int num,counter obj); //表示 整数+对象 }; counter counter::operator=(int num){ value=num; return *this; } counter operator+(counter obj,int num){ counter tmp; tmp.value=obj.value+num; return tmp; counter operator +(int num,counter obj){ counter tmp; tmp.value=obj.value+num; return tmp; 2019/1/17

17 counter obj2=obj1+5;//使用重载运算符函数operator+(counter,int)
void main( ) { counter obj1; obj1=10;//使用重载的=运算符 counter obj2=obj1+5;//使用重载运算符函数operator+(counter,int) counter obj3=3+obj2; //使用重载运算符函数operator+(int, counter) } 2019/1/17

18 例子:用友元重载++运算符 class Point{ int x, y; public:
Point(int a=0,int b=0):x(a),y(b){ } Point operator++( ){ x++; y++; return *this; } };//用成员函数重载 class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point obj); }; Point operator++(Point obj){ obj.x++; obj.y++; return obj; } //用友元函数和重载 void main( ) { Point point1(1,3); Point point2=++point1; } 2019/1/17

19 Point(int a=0,int b=0):x(a),y(b){ }
class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj); }; Point operator++(Point &obj){ obj.x++; obj.y++; return obj; } void main( ) { Point point1(1,3); Point point2=++point1; } //通过传递引用,可以改变对象的内部状态 2019/1/17

20 一元和二元运算符 一元运算符: 对任意一元运算符@,将其作为成员函数重载时的定义及调用方式为:
type className obj; @obj 或 2019/1/17

21 如果将一元运算符@作为友元函数重载时的定义及调用方式为:
type className obj; @obj 或 2019/1/17

22 二元运算符 对任意二元运算符@,将其作为成员函数重载时的定义及调用方式为:
type param); className obj1,obj2; 2019/1/17

23 如果将二元运算符@作为友元函数重载时的定义及调用方式为:
type className obj1,obj2; obj2; 2019/1/17

24 重载++和-- 运算符++和--有两种使用方式:前缀和后缀。因此在重载这两个运算符时,应该考虑到这两种方式的差别。
二者的差别主要在于函数的参数不同。 为了方便起见,我们仅以++为例。 2019/1/17

25 这里的int类型参数叫做占位参数,用来区别前缀和后缀方式,一般在函数体里不会引用它。
对于前缀方式++obj 成员函数方式 type className::operator++(); 友元函数方式 type operator++(className&); 对于后缀方式obj++ type className::operator++( int ); type operator++(className&, int); 这里的int类型参数叫做占位参数,用来区别前缀和后缀方式,一般在函数体里不会引用它。 2019/1/17

26 Point(int a=0,int b=0):x(a),y(b){ }
class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj); friend Point operator++( Point &obj,int); }; Point operator++(Point &obj){ obj.x++; obj.y++; return obj; } Point operator++(Point &obj , int){ Point tmp=obj;//请注意该语句 return tmp; //请注意该语句 void main( ) { Point point1(3,4); Point point3=++point1; Point point2=point1++; } 2019/1/17

27 对于占位参数,缺省为0,但也可以设置非0值,例如:
obj.operator++(3)//成员函数形式 operator++(obj,3)//友元函数形式 相当于:obj++=3或obj++(3) class Point{ int x, y; public: Point(int a=0,int b=0):x(a),y(b){ } friend Point operator++( Point &obj, int v); }; Point operator++(Point &obj,int v){ Point tmp=obj; obj.x+=v; obj.y+=v; return tmp; } 2019/1/17

28 重载运算符 [ ] [ ]不能用友元函数重载,只能用成员函数重载。 重载下标运算符[ ] operator[](参数表)
obj[index] => obj.operator[](index) 2019/1/17

29 例子:整型数组类IntArray class IntArray{ int *p; int size;
void expand(int offset); public: IntArray(int num){ size=(num>6)?num:6; p=new int[size]; } ~ IntArray( ){delete [ ]p;} int &operator[ ](int index); }; void IntArray::expand(int offset){ int *pi=new int[size+offset]; for(int i=0;i<size;i++) pi[i]=p[i]; delete [ ]p; p=pi; size+=offset; } int &IntArray:: operator[ ](int index){ if(index<size) return p[index]; else{ expand(index-size+1); return p[index]; 2019/1/17

30 for(int i=0;i<8;i++) array[i]=i; }
void main( ) { IntArray array(5); for(int i=0;i<8;i++) array[i]=i; } 2019/1/17

31 重载赋值运算符 当用户自定义类要重载赋值运算符时,必须重载为成员函数,格式一般如下:
X X::operator=(X &from) { //复制X的成员 } 如果用户没有在类中重载赋值运算符,那么编译器会生成一个缺省的赋值运算符,该缺省的赋值运算符的作用就是通过位拷贝的方式将源对象复制到目的对象。 2019/1/17

32 举例(一) class Complex{ double re, im; public:
Complex(double r=0.0, double i=0.0): re(r), im(i){ } Complex(Complex &obj){ re=obj.re; im=obj.im; } Complex operator=(Complex &obj){ re=obj.re; im=obj.im; return *this; …… }; void main( ){ Complex c1(1, 2), c2(3, 4),c3; Complex c4=c1+c2;//调用的是拷贝构造函数 c3= c1 + c2;//调用的是重载的赋值运算符 2019/1/17

33 赋值运算符与拷贝构造函数的异同 相同点:它们的目的都是为了将一个对象的数据成员复制到另一个对象中。
不同点:拷贝构造函数是要创建一个新对象,而赋值运算符则是要改变一个已经存在的对象的值。 2019/1/17

34 举例(二) class A{ private: int len; char *buf; A::A(const A&obj){ public:
A(int n); A(const A&obj); ~A( ); void copy(char *src); }; A::A( int n){ len=n; buf=new char[n]; } void A::copy(char *src){ strcpy(buf,src); A::~A( ){ delete [ ]buf; A::A(const A&obj){ len = obj.len; buf=new char[ len ]; strcpy( buf , obj.buf ); } void func(){ A obj1( 64 ), obj2(32); obj1.copy(“helloworld”); A obj3=obj1; obj2=obj3;//将会出错 void main(){ func( ); obj2中buf成员指向的32字节的内存区将会丢失 2019/1/17

35 改进办法:重载赋值运算符 class A{ …… public: A(int n); A operator=(const A&from){
if(this==&from) return *this;//避免自己给自己赋值 delete [ ]buf; len = from.len; buf=new char[ len ]; strcpy( buf , from.buf ); return *this; } }; 2019/1/17

36 重载输入输出运算符 在iostream.h中,有两个标准的类类型istream和ostream。istream将“>>”重载为输入运算符。ostream将“<<”重载为输出运算符。 用户可以很方便地使用“>>”和 “<<”对系统预定义类型进行输入和输出。 对于类类型,用户也可以将“>>”和 “<<”两个运算符进行重载后以满足自身的需要。 2019/1/17

37 >>运算符第一个操作数是cin,它是istream的对象的引用;<<运算符第一个操作数是cout,它是ostream的对象的引用。
void main( ) { int num; istream &my_cin=cin; ostream &my_cout=cout; my_cin>>num; my_cout<<num<<endl; } 2019/1/17

38 重载<<运算符 当用户自定义的className类要重载<<运算符时,重载函数为operator<<,并且只能使用友元函数进行重载。原因在于: 隐式调用的方式为“cout<<obj”。 显示调用方式为“cout.operator<<(obj),而cout本身不是className类的对象。 为了保证”<<“的连用性,重载函数的返回应该为ostream &。 重载函数有两个参数,类型为ostream和className,分别对应于实参cout和obj。 2019/1/17

39 重载>>运算符 当用户自定义的className类要重载>>运算符时,重载函数为operator>> ,并且只能使用友元函数进行重载。 为了保证“>>”的连用性,重载函数的返回应该为istream &。 注意:重载函数的第一个参数的类型为istream,而第二个参数必须是className的对象的引用。 2019/1/17

40 举例 class Complex{ double re, im; public:
Complex(double r=0.0, double i=0.0): re(r), im(i){ } friend istream &operator>>(istream &scin, Complex &obj); friend ostream &operator<<(ostream &scout, Complex obj); …… }; istream &operator>>(istream &scin, Complex &obj) { scin>>obj.re; scin>>obj.im; return scin; } ostream &operator<<(ostream &scout, Complex obj) scout<<obj.re<<“+”<<obj.im<<“i”; return scout; 2019/1/17

41 cin>>c1>>c2; Complex c3=c1+c2; cout<<c3; }
void main( ) { Complex c1, c2; cin>>c1>>c2; Complex c3=c1+c2; cout<<c3; } 2019/1/17

42 new和delete的特殊用途 C++多用new和delete来动态申请和释放内存。
因此需要很好地设计类的复制构造函数、析构函数,以及赋值运算符的重载。 2019/1/17

43 重载new和delete C++将new和delete都看作运算符。重载这两个运算符的格式如下:
void *operator new (size_t size){ ……//完成存储分配工作 return pToMemory; } void operator delete(void *p){ ……//释放p所指向的存储空间 2019/1/17

44 类型size_t由C++定义为能容纳可分配的单一的内存块的最大值。 参数size给出待创建的对象的大小。
重载new函数返回的是指向新分配空间的指针,如果分配不成功,返回NULL。 注意:虽然new函数有参数要求,但是调用该函数时不必给出实参,系统会自动计算出要分配空间的大小。 有两种重载方式:局部重载和全局重载。 2019/1/17

45 局部重载new和delete 将new和delete重载为与类相关的成员函数,即局部重载。
使用new分配某个重载了new的类的对象时,先调用new的重载函数,再调用构造函数。当构造函数有参数时,还必须给出相应的实参。 使用delete释放某个重载了delete的类的对象空间时,先调用类的析构函数,再调用delete的重载函数。 2019/1/17

46 举例 class Complex{ double re, im; public:
Complex(double r=0.0, double i=0.0): re(r), im(i){ cout<<“Complex Constructing\n”; } ~Complex( ){cout<<“Complex Destructing\n”;} void *operator new(size_t size){ cout<< “new Complex obj\n”; return malloc(size); void operator delete( void *p){ cout<<“delte Complex obj\n”; free(p); }; 2019/1/17

47 if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”;
void main( ){ Complex *p1, *p2; p1=new Complex(3,4); p2=new Complex(5,6); if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; return; } int *pnum=new int(78); delete p1; delete p2; delete pnum; //输出: new Complex obj Complex Constructing Complex Destructing delte Complex obj 2019/1/17

48 全局重载new和delete 在任何类说明之外重载new和delete时,称为全局重载new和delete。
全局重载时,C++原来的new和delete被忽略,而重载后的new和delete运算符则用于所有的分配和释放请求。 全局重载有时候会产生一些意想不到的问题,建议尽量少使用全局重载。 2019/1/17

49 举例 class Complex{ double re, im; public:
Complex(double r=0.0, double i=0.0): re(r), im(i){ cout<<“Complex Constructing\n”; } ~Complex( ){cout<<“Complex Destructing\n”;} }; void *operator new(size_t size){ cout<<“begin new obj\n”; return malloc(size); void operator delete( void *p){ cout<<“begin delte obj\n”; free(p); 2019/1/17

50 if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”;
void main( ){ Complex *p1, *p2; p1=new Complex(3,4); p2=new Complex(5,6); if((p1==NULL)||(p2==NULL)){ cout<<“Allocation Failsure\n”; return; } int *pnum=new int(78); delete p1; delete p2; delete pnum; //输出如下,但是当程序快运行完时,会报告错误 begin new obj Complex Constructing Complex Destructing begin delte obj 2019/1/17

51 运算符重载函数返回值类型的选择 当进行运算符重载的时候,需要根据该运算符的语义来确定运算符重载函数的返回值类型。合适的返回类型将会使得:
重载的“=”运算符可以级联:a=b=c。 能够进行诸如a+b+c+d这样复杂的运算。 能够进行scout<<a<<b或scin>>a>>b这样的级联。 2019/1/17

52 类型转换 C++允许的类型转换有四种: 标准类型-〉标准类型 标准类型-〉类类型 类类型-〉标准类型 类类型-〉类类型 2019/1/17

53 标准类型转换到类类型 要实现标准类型到类类型的转换,可以通过重载“=“运算符和定义构造函数来完成。 class counter{
int value; public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; }; void main( ) { counter obj1=6;//使用构造函数 counter obj2; //使用构造函数 obj2=10;//使用重载的=运算符 } 2019/1/17

54 类型转换函数 将类类型转换到简单类型的时候,需要一个特殊的成员函数,即类型转换函数来完成。语法如下:
className::operator type() { return type类型的实例; } 该函数的功能是将className类型的对象转换为type类型的实例。type可以是基本类型,也可以是用户自定义类型。 这个转换函数没有参数,没有返回类型,但函数体内必须有一条返回type类型实例的返回语句。 只能将其定义为类的成员函数。 2019/1/17

55 举例:为counter添加类型转换函数 class counter{ void main( ) int value; {
public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; operator int( ){ return value; }; void main( ) { counter obj1=6; counter obj2; obj2=10; int v1=obj1; int v2=(int)obj2; int v3=int(obj2); } //对于最后3条赋值语句,都调用了类型转换函数。 2019/1/17

56 请思考 依据编译器而定! 对于“counter objx=obj1+v1”语句,编译器该按怎样的方式进行解释? class counter{
int value; public: counter(int v=0){ value=v; } counter operator=(int num){ value=num; return *this; operator int( ){ return value; }; void main( ) { counter obj1=6; int v1=obj1+1; counter objx=obj1+v1; } 依据编译器而定! 2019/1/17

57 虚函数和多态性 重载普通的成员函数,存在着两种方式: 以上两种重载的匹配都是在编译的时候静态完成的。 在同一个类中重载
重载函数是以参数特征区分的。 派生类重载基类的成员函数 由于重载函数处在不同的类当中,因此它们的原型可以完全相同。在调用时,使用类名::函数名的方式加以区分。 以上两种重载的匹配都是在编译的时候静态完成的。 2019/1/17

58 重载是一种简单形式的多态。C++提供另一种更加灵活的多态机制:虚函数。虚函数允许函数调用与函数体的匹配在运行时才确定。
虚函数提供的是一种动态绑定的机制。 2019/1/17

59 基类对象的指针指向派生类对象 我们已经学习过,在单继承或多继承的公有派生方式下,基类对象的指针可以指向派生类对象。设B是基类,D是派生类:
B *p; D Dobj; p = &Dobj; 利用p可以访问从基类B中继承的成员,但是不能用p访问派生类自己定义的成员。 2019/1/17

60 简单的例子 class Base{ public: void Print(){ cout<<"I am Base\n"; };
class Derived:public Base{ cout<<"I am Derived\n"; void main() { Derived derived; Base base, *p; Base &ref1 = base; Base &ref2 = derived; //调用的是基类的Print p=&base; ref1.Print(); p->Print(); p=&derived; ref2.Print(); } 2019/1/17

61 我们的目的是试图通过基类引用或指针来访问派生类的成员。但这是行不通的,因为通过基类引用或指针所能看到的是一个基类对象,派生类中的成员对于基类引用或指针来说是“不可见的”。
Other members &Base::Print &Derived::Print p 对于p是不可见的 2019/1/17

62 为了解决上面的问题,我们可以利用C++的虚函数机制,将基类的Print说明为虚函数形式。这样就可以通过基类引用或指针来访问派生类中的Print。
2019/1/17

63 简单的例子(续):使用虚函数 class Base{ public: virtual void Print(){
cout<<"I am Base\n"; }; class Derived:public Base{ void Print(){ cout<<"I am Derived\n"; void main() { Derived derived; Base base, *p; Base &ref1 = base; Base &ref2 = derived; //调用的是基类的Print p=&base; ref1.Print(); p->Print(); //调用的是派生类的Print p=&derived; ref2.Print(); } 2019/1/17

64 虚函数 虚函数的概念 在基类中用virtual关键字声明的成员函数即为虚函数。
虚函数可以在一个或多个派生类中被重新定义,但要求在重定义时虚函数的原型(包括返回值类型、函数名、参数列表)必须完全相同。 2019/1/17

65 例子:虚函数 class Base{ public: virtual void Print(){
cout<<"I am Base\n"; }; class Derived1:public Base{ void Print(){ cout<<"I am Derived1\n"; class Derived2:public Base{ cout<<"I am Derived2\n"; void main() { Base *p, obj; Derived1 obj1; Derived2 obj2; //调用的是Base的Print p=&obj; p->Print(); //调用的是Derived1的Print p=&obj1; //调用的是Derived2的Print p=&obj2; } 2019/1/17

66 基类中的函数具有虚特性的条件 在基类中用virtual将函数说明为虚函数。 在公有派生类中原型一致地重载该虚函数。
定义基类引用或指针,使其引用或指向派生类对象。当通过该引用或指针调用虚函数时,该函数将体现出虚特性来。 2019/1/17

67 注意 在派生类中重载虚函数时必须与基类中的函数原型相同,否则该函数将丢失虚特性。 仅返回类型不同,其他相同。C++认为这种情况是不允许的。
2019/1/17

68 虚函数的实现机制 函数指针回顾 int add(int a, int b) { return a+b; }
int sub(int a, int b) return a-b; void main() { int (*pfunction)(int ,int); int ret; pfunction=add; ret=pfunction(5,3); pfunction=sub; ret=pfunction(6,3); } 2019/1/17

69 虚函数表和虚指针 在编译时,为每个有虚函数的类建立一张虚函数表VTABLE(即函数指针数组),表中存放的是每一个虚函数的指针;同时用一个虚指针VPTR指向这张表的入口。 VPTR &fun1 &fun2 &funN VTABLE 访问某个虚函数时,不是直接找到那个函数的地址,而是通过VPTR间接查到它的地址。 2019/1/17

70 因此,每个对象的内存空间除了保存数据成员外,还保存了VPTR。而VPTR也是由构造函数初始化的。
&Print VTABLE Other members 基类Base Derived d; Base* bp = &d; VPTR &Print VTABLE Other members 子类Derived bp 2019/1/17

71 对虚函数的要求 虚函数必须是类的成员函数。 不能将虚函数说明为全局函数,也不能说明为静态成员函数。 不能将友元说明为虚函数。
2019/1/17

72 在成员函数中调用虚函数 在一个基类或派生类的成员函数中,可以直接调用类等级中的虚函数。此时需要根据成员函数中this指针所指向的对象来判断调用的是哪一个函数。 2019/1/17

73 析构函数可以定义为虚函数 构造函数不能定义为虚函数(为什么?) 而析构函数可以定义为虚函数。
若析构函数为虚函数,那么当使用delete释放基类指针所指向的派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。 2019/1/17

74 例子 class Base{ public: virtual ~Base(){ cout<<“Base destroy\n”;
} }; class Derived:public Base{ ~Derived (){ cout<<“Derived destroy\n”; void main() { Base *p1, *p2; p1=new Base(); p2=new Derived(); delete p1; delete p2; } 输出: Base destroy Derived destroy 2019/1/17

75 提供虚函数的意义 虚函数的特性使得它成为许多C++程序设计的关键,因为基类可以使用虚函数提供一个接口,这使该类的所有公有派生类都具有相同的接口,但派生类可以定义自己的实现版本,而且虚函数调用的解释依赖于它的对象类型。这实现了“一个接口,多种实现/语义”的概念,为软件的充分可重用提供了坚实的基础。 2019/1/17

76 纯虚函数及抽象类 基类往往表示一种抽象的概念。在很多时候,基类仅仅提供一些公共的接口,表示这类对象拥有的共同操作。而这些操作又是依赖于不同的派生类对象的,因此,在基类的定义中,这些公共接口只需要有说明而不需要有实现。这就是纯虚函数的概念。 纯虚函数刻画了一系列派生类应该遵循的协议,这些协议的具体实现由派生类来决定。 2019/1/17

77 例子 class Shape{ virtual float Circumference()=0;
Virtual float Area()=0; }; class Circle:public Shape{ float r; …… public: float Circumference(){ return 2*3.14*r; } float Area(){ return 3.14*r*r; class Squre:public Shape{ float l, h; …… public: float Circumference(){ return 2*l*h; } float Area(){ return l*h; }; 2019/1/17

78 将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。
定义纯虚函数的语法形式: virtual type functionName(parameters)=0; 将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。 拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为其它类的基类。抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。 特别地,当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类。 2019/1/17


Download ppt "第九章 多态性 丘志杰 电子科技大学 计算机学院 软件学院."

Similar presentations


Ads by Google