CPP-基础:关于多态

    类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visual BASIC 6.0 是典型的非面向对象的开发语言,但是它的确是支持类,支持类并不能说明就是支持面向对象,能够解决多态问题的语言,才是真正支持面向对象的开发的语言,所以务必提醒有过其它非面向对象语言基础的读者注意!


  多态的这个概念稍微有点模糊,如果想在一开始就想用清晰用语言描述它,让读者能够明白,似乎不太现实,所以我们先看如下代码:

 1 //例程1  
 2 #include <iostream>      
 3 using namespace std;    
 4     
 5 class Vehicle  
 6 {    
 7 public:    
 8     Vehicle(float speed,int total)  
 9     {  
10         Vehicle::speed=speed;  
11         Vehicle::total=total;  
12     }  
13     void ShowMember()  
14     {  
15         cout<<speed<<"|"<<total<<endl;  
16     }  
17 protected:    
18     float speed;  
19     int total;  
20 };    
21 class Car:public Vehicle    
22 {    
23 public:    
24     Car(int aird,float speed,int total):Vehicle(speed,total)    
25     {    
26         Car::aird=aird;    
27     }  
28     void ShowMember()  
29     {  
30         cout<<speed<<"|"<<total<<"|"<<aird<<endl;  
31     }  
32 protected:    
33     int aird;  
34 };    
35   
36 void main()    
37 {    
38     Vehicle a(120,4);  
39     a.ShowMember();  
40     Car b(180,110,4);  
41     b.ShowMember();  
42     cin.get();  
43 }

 

  在c++中是允许派生类重载基类成员函数的,对于类的重载来说,明确的,不同类的对象,调用其类的成员函数的时候,系统是知道如何找到其类的同名成员,上面代码中的a.ShowMember();,即调用的是Vehicle::ShowMember(),b.ShowMember();,即调用的是Car::ShowMemeber();。

 但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:

 1 //例程2  
 2 #include <iostream>      
 3 using namespace std;    
 4     
 5 class Vehicle  
 6 {    
 7 public:    
 8     Vehicle(float speed,int total)  
 9     {  
10         Vehicle::speed=speed;  
11         Vehicle::total=total;  
12     }  
13     void ShowMember()  
14     {  
15         cout<<speed<<"|"<<total<<endl;  
16     }  
17 protected:    
18     float speed;  
19     int total;  
20 };    
21 class Car:public Vehicle    
22 {    
23 public:    
24     Car(int aird,float speed,int total):Vehicle(speed,total)    
25     {    
26         Car::aird=aird;    
27     }  
28     void ShowMember()  
29     {  
30         cout<<speed<<"|"<<total<<"|"<<aird<<endl;  
31     }  
32 protected:    
33     int aird;  
34 };    
35   
36 void test(Vehicle &temp)  
37 {  
38     temp.ShowMember();  
39 }  
40   
41 void main()    
42 {  
43     Vehicle a(120,4);  
44     Car b(180,110,4);  
45     test(a);  
46     test(b);  
47     cin.get();  
48 }

 

  例子中,对象a与b分别是基类和派生类的对象,而函数test的形参却只是Vehicle类的引用,按照类继承的特点,系统把Car类对象看做是一个Vehicle类对象,因为Car类的覆盖范围包含Vehicle类,所以test函数的定义并没有错误,我们想利用test函数达到的目的是,传递不同类对象的引用,分别调用不同类的,重载了的,ShowMember成员函数,但是程序的运行结果却出乎人们的意料,系统分不清楚传递过来的基类对象还是派生类对象,无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。

为了要解决上述不能正确分辨对象类型的问题,c++提供了一种叫做多态性(polymorphism)的技术来解决问题,对于例程序1,这种能够在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(early binding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding),下面我们要看的例程3,就是滞后联编,滞后联编正是解决多态问题的方法。

代码如下:

 1 //例程3  
 2 #include <iostream>      
 3 using namespace std;    
 4     
 5 class Vehicle  
 6 {    
 7 public:    
 8     Vehicle(float speed,int total)  
 9     {  
10         Vehicle::speed = speed;  
11         Vehicle::total = total;  
12     }  
13     virtual void ShowMember()//虚函数  
14     {  
15         cout<<speed<<"|"<<total<<endl;  
16     }  
17 protected:    
18     float speed;  
19     int total;  
20 };    
21 class Car:public Vehicle    
22 {    
23 public:    
24     Car(int aird,float speed,int total):Vehicle(speed,total)    
25     {    
26         Car::aird = aird;    
27     }  
28     virtual void ShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加  
29     {  
30         cout<<speed<<"|"<<total<<"|"<<aird<<endl;  
31     }  
32 public:    
33     int aird;  
34 };  
35   
36 void test(Vehicle &temp)  
37 {  
38     temp.ShowMember();  
39 }  
40   
41 int main()    
42 {    
43     Vehicle a(120,4);  
44     Car b(180,110,4);  
45     test(a);  
46     test(b);  
47     cin.get();  
48 }

 

  多态特性的工作依赖虚函数的定义,在需要解决多态问题的重载成员函数前,加上 virtual关键字,那么该成员函数就变成了虚函数,从上例代码运行的结果看,系统成功的分辨出了对象的真实类型,成功的调用了各自的重载成员函数。

  多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。

虚函数的定义要遵循以下重要规则:

  1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

  2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

  3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

  4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义,但是在编译的时候系统仍然将它看做是非内联的。

  5.构造函数不能是虚函数,因为构造的时候,对象还是一片未定型的空间,只有构造完成后,对象才是具体类的实例。

  6.析构函数可以是虚函数,而且通常声名为虚函数。

  说明一下,虽然我们说使用虚函数会降低效率,但是在处理器速度越来越快的今天,将一个类中的所有成员函数都定义成为virtual总是有好处的,它除了会增加一些额外的开销是没有其它坏处的,对于保证类的封装特性是有好处的。

  对于上面虚函数使用的重要规则6,我们有必要用实例说明一下,为什么具备多态特性的类的析构函数,有必要声明为virtual

代码如下:

  1 #include "stdafx.h"
  2 
  3 #include <iostream>     
  4 
  5 using namespace std;    
  6 
  7     
  8 
  9 class Vehicle  
 10 
 11 {    
 12 
 13 public:   
 14 
 15     Vehicle(float speed,int total)  
 16 
 17     {  
 18 
 19         Vehicle::speed=speed;  
 20 
 21         Vehicle::total=total;  
 22 
 23     }  
 24 
 25     virtual void ShowMember()  
 26 
 27     {  
 28 
 29         cout<<speed<<"|"<<total<<endl;  
 30 
 31     }  
 32 
 33     virtual ~Vehicle()  
 34 
 35     {  
 36 
 37         cout<<"载入Vehicle基类析构函数"<<endl;  
 38 
 39         cin.get();  
 40 
 41     }  
 42 
 43 protected:    
 44 
 45     float speed;  
 46 
 47     int total;  
 48 
 49 };    
 50 
 51 class Car:public Vehicle    
 52 
 53 {    
 54 
 55 public:    
 56 
 57     Car(int aird,float speed,int total):Vehicle(speed,total)    
 58 
 59     {    
 60 
 61         Car::aird=aird;    
 62 
 63     }  
 64 
 65     virtual void ShowMember()  
 66 
 67     {  
 68 
 69         cout<<speed<<"|"<<total<<"|"<<aird<<endl;  
 70 
 71     }  
 72 
 73     virtual ~Car()  
 74 
 75     {  
 76 
 77         cout<<"载入Car派生类析构函数"<<endl;  
 78 
 79         cin.get();  
 80 
 81     }  
 82 
 83 protected:    
 84 
 85     int aird;  
 86 
 87 };    
 88 
 89   
 90 
 91 void test(Vehicle &temp)  
 92 
 93 {  
 94 
 95     temp.ShowMember();  
 96 
 97 }  
 98 
 99 void DelPN(Vehicle *temp)  
100 
101 {  
102 
103     delete temp;  
104 
105 }  
106 
107 void main()  
108 
109 {    
110 
111     Car *a=new Car(100,1,1);  
112 
113     a->ShowMember();  
114 
115     DelPN(a);  
116 
117     cin.get();  
118 
119 } 

 

从上例代码的运行结果来看,当调用DelPN(a);后,在析构的时候,系统成功的确定了先调用Car类的析构函数,而如果将析构函数的virtual修饰去掉,再观察结果,会发现析构的时候,始终只调用了基类的析构函数,由此我们发现,多态的特性的virtual修饰,不单单对基类和派生类的普通成员函数有必要,而且对于基类和派生类的析构函数同样重要。
 
附:
 1 class A
 2 {
 3        ......
 4 }
 5 class B : public A
 6 {
 7       .....
 8 }
 91)A *pA = new B
102)B b; A &rb=b;

基类的指针或者引用指向派生类的实例,这在面向对象编程中使用极其普遍。

A *pA = new B;这是一个基类指针指向一个派生类的实例。

B b; A &rb=b;这是一个基类引用指向(引用)派生类的实例。

至于这个指针pA和引用rb的访问范围,完全由pA和rb定义所在的范围决定,跟它们所指向的目标无关。 通过基类指针或者引用来访问派生类实例的意义在于,这种指针和引用可以通用于访问这个基类之下的所有派生类的对象,这一方面可以使用面向对象的“多态”特性,通过这个基类指针或者引用来调用虚函数的时候,实际执行的是派生类对象的函数,使用这个指针或者引用的一方的代码不必随着派生类的不同而改变,却可以达到执行最适合这个派生类的函数(也就是这个派生类自己的成员函数)的目的;另一方面可以使程序模块具有很好的可替换性,用一个派生类替换另一个派生类,程序的其它部分不需要做任何改动就可以正常运行而且发挥出新的派生类的特性。 PS:基类指针和引用可以用来访问派生类对象是把派生类对象看成基类对象。理论基础是:一个派生类对象一定也是一个基类对象

 

转载于:https://www.cnblogs.com/CPYER/p/3257671.html

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值