类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,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 }
但是在实际工作中,很可能会碰到对象所属类不清的情况,下面我们来看一下派生类成员作为函数参数传递的例子,代码如下:
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 }
为了要解决上述不能正确分辨对象类型的问题,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 }
多态特性让程序员省去了细节的考虑,提高了开发效率,使代码大大的简化,当然虚函数的定义也是有缺陷的,因为多态特性增加了一些数据存储和执行指令的开销,所以能不用多态最好不用。
虚函数的定义要遵循以下重要规则:
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 }
1 class A 2 { 3 ...... 4 } 5 class B : public A 6 { 7 ..... 8 } 9 (1)A *pA = new B 10 (2)B b; A &rb=b;
基类的指针或者引用指向派生类的实例,这在面向对象编程中使用极其普遍。
A *pA = new B;这是一个基类指针指向一个派生类的实例。
B b; A &rb=b;这是一个基类引用指向(引用)派生类的实例。
至于这个指针pA和引用rb的访问范围,完全由pA和rb定义所在的范围决定,跟它们所指向的目标无关。 通过基类指针或者引用来访问派生类实例的意义在于,这种指针和引用可以通用于访问这个基类之下的所有派生类的对象,这一方面可以使用面向对象的“多态”特性,通过这个基类指针或者引用来调用虚函数的时候,实际执行的是派生类对象的函数,使用这个指针或者引用的一方的代码不必随着派生类的不同而改变,却可以达到执行最适合这个派生类的函数(也就是这个派生类自己的成员函数)的目的;另一方面可以使程序模块具有很好的可替换性,用一个派生类替换另一个派生类,程序的其它部分不需要做任何改动就可以正常运行而且发挥出新的派生类的特性。 PS:基类指针和引用可以用来访问派生类对象是把派生类对象看成基类对象。理论基础是:一个派生类对象一定也是一个基类对象