C++ 多态与虚函数

什么是多态?

一个 “操作” 随着所 ”传递或捆绑的对象类型的不同 “ 能够做出 ”不同的反应“,其行为模式称为多态。

即,对这个操作,能接受不同类型的参数,而且在处理参数时,会根绝其类型做出不同的反应。

为什么要引入多态?

针对由继承得到一类对象,在处理这类对象时,能够以同一操作处理完所有对象。

遇到的问题:

  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:  
  7.     void Show()  
  8.     {  
  9.         cout<<"Base"<<endl;  
  10.     }  
  11. };  
  12.   
  13. class DeriveOne : public Base  
  14. {  
  15. public:  
  16.     void Show()  
  17.     {  
  18.         cout<<"DeriveOne"<<endl;  
  19.     }  
  20. };  
  21.   
  22.   
  23. void AAA(Base base)  
  24. {  
  25.     base.Show();  
  26. }  
  27.   
  28.   
  29. int main()  
  30. {  
  31.     Base base;  
  32.     DeriveOne deriveOne;  
  33.       
  34.     AAA(base);  
  35.     AAA(deriveOne);  
  36.   
  37.     system("pause");  
  38.     return 1;  
  39. }  
输出


说明:

在父类Base和派生类DeriveOne中都定义了Show操作,它们分别输出对应类的信息,Base的show操作显示Base的信息,DeriveOne的show操作显示DeriveOne的信息。在函数AAA中,由于参数是base类型,在函数AAA中使用时,无论形参是父类Base还是子类DeriveOne,在函数中,其仅仅表示父类或者子类中的父类的那部分信息。若想显示完全,就必须写两个函数,它们分别接受两种参数,如:

  1. void show(Base base);  
  2. void show(DeriveOne Derive);  
问题又来了,由于派生类可以转化为基类,因此在调用Show函数时,还需要判断来的类是基类还是派生类。此时,用的时候就很不方便了。

此时,我们想要的是,函数AAA的获得实参可以是继承类集合中的任意类对象,而且其函数体也能根据其实参的改变调用不同的函数,即自动分别实参。这就是我们要求的多态。

举例说明要实现的目标:

  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class Base  
  5. {  
  6. public:  
  7.     virtual void Show()  
  8.     {  
  9.         cout<<"Base"<<endl;  
  10.     }  
  11. };  
  12.   
  13. class DeriveOne : public Base  
  14. {  
  15. public:  
  16.      void Show()  
  17.     {  
  18.         cout<<"DeriveOne"<<endl;  
  19.     }  
  20. };  
  21.   
  22. class DeriveTwo : public Base  
  23. {  
  24. public:  
  25.     void Show()  
  26.     {  
  27.         cout<<"DeriveTwo"<<endl;  
  28.     }  
  29. };  
  30.   
  31. //定义一个操作,对多个不同的继承类对象做统一操作  
  32. void AAA(Base& base)    
  33. {  
  34.     base.Show();  
  35. }  
  36.   
  37. int main()  
  38. {  
  39.     Base base;  
  40.     DeriveOne deriveOne;  
  41.     DeriveTwo deriveTwo;  
  42.   
  43.     AAA(base);  
  44.     AAA(deriveOne);  
  45.     AAA(deriveTwo);  
  46.     system("pause");  
  47.     return 1;  
  48. }  

说明,这里还是给出一个函数AAA,其调用AAA的实参是有三个,base、deriveOne、deriveTwo,函数AAA能够根据传进来的实参来自定义判断调用哪个类的Show函数。这是我们想要的结果,即函数AAA能够接受整个派生类集合,并能区分不同的类对象。

怎么实现多态?

虚函数 + 基类指针引用调用。

什么是虚函数?

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。

虚函数的原理:

虚函数的作用是实现类的继承所体现的多态性,具体点是实现动态联编。

从程序的角度上来说,在定义了虚函数后,可以在派生类中对虚函数重新定义,以实现统一的接口,不同定义过程,在程序的运行阶段动态地选择合适的成员函数。

一旦标记基类的函数为虚函数,便有连锁反应,后面继承的类中一切同名成员函数都变成了虚函数,(即使后面的这些同名函数没有声明该函数为虚函数)。

C++实现运行时多态性的关键途径:在公有派生情况下,一个指向基类的指针可用来访问从基类继承的任何对象。

虚函数语法:

对函数的设置:普通函数的前面加上virtual

  1. virtual 函数返回值类型 虚函数名(形参表)  
  2. {   
  3.     //函数体  
  4. }  

虚函数的调用方式:只能通过指向基类的指针或基类对象的引用来调用虚函数

调用语法:

  1. 指向基类的指针变量名->虚函数名(实参表)  
  2. 基类对象的引用名. 虚函数名(实参表)  

注意事项一:正常情况下,如果不把函数声明为虚函数,指向基类的指针的访问情况如下:

1)基类指针指向基类对象:基类指针可以直接访问基类对象中的成员

2)基类指针指向派生类对象:基类指针只能访问派生类中的从基类中继承的成员,派生类有同名的函数或成员,也只能调用基类的成员。

如果定义成虚函数时:定义一个基类指针,把不同的派生类对象付给它,会调用对应派生类的函数,而非基类函数。

举例:

  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     virtual void show()  
  7.     {  
  8.         cout<<"A"<<endl;  
  9.     }  
  10. };  
  11. class B:public A  
  12. {  
  13. public:  
  14.     void show()  
  15.     {  
  16.         cout<<"B"<<endl;  
  17.     }  
  18. };  
  19. class C:public A  
  20. {  
  21. public:  
  22.     void show()  
  23.     {  
  24.         cout<<"C"<<endl;  
  25.     }  
  26. };  
  27. void main()  
  28. {  
  29.     A*a;  
  30.     B b;  
  31.     C c;  
  32.     a=&b;  
  33.     a->show();  
  34.     a=&c;  
  35.     a->show();  
  36.     system("pause");  
  37. }  

运行结果:B(换行)C(换行)--指向不同的派生类,调用不同的函数

如果不加基类A中的Virtual,则输出结果:A(换行)A(换行)--基类指针,调用派生类中继承的基类成分

注意事项二:如果不是使用基类指针或引用,而仅仅是类之间的复制,那么子类对象就完全转换为父类对象,就不会有多态了。

  1. Student s; //基类  
  2. GraduateStudent gs; //派生类  
  3.   
  4. void fn (Student a)//形参传值--而非(Student & a)  
  5. {  
  6.     a.Show();  
  7. };  
  8.   
  9. fn(gs); //无多态,调用基类的show函数,而不是派生类的show函数。  

原因,在参数传递的过程中,已经将实参的性质做了肯定的转变,而对于确定的对象,是没有选择操作可言的。

因此,仅仅对于对象的指针和引用的间接访问,才会发生多态现象

定义虚函数,实现动态联编需要三个条件:

1)必须把动态联编的行为定义为类的虚函数---定义虚函数

2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来---类之间是公有继承

3)基类指针指向派生类的对象,然后使用基类指针调用虚函数

把那些函数设置为虚函数?

如果一个函数在子类和父类中都有,而且子类使用这函数时,仅仅使用自己就ok,那么可以把这个函数设置为虚函数。

虚函数是怎么实现动态捆绑的?

编译器在编译的时候,看到一个基类函数前面有关键字”virtual“,编译器就知道需要对该函数进行晚捆绑,即不能立即断定虚函数的确切位置,此时编译器将此函数调用转化为一个指针。该指针在该虚函数被调用的时候,根据实际对象来给指针赋值,即指向实际对象的成员函数。这样一来,若实际对象是基类,则调用的是基类成员函数。若实际对象是派生类,则调用的是子类的成员函数。

为了给这个指针赋值,必须在每个实际的对象中额外占有一个指针空间,以指向自己类中的虚函数表。

如:


注意,具有虚函数的类,其对象空间比没有虚函数的类多了一个指针空间,而且在使用时需要间接访问虚函数,即采用虚函数,会影响一些程序运行效率。

关于虚函数的详细说明,请见另一篇文章:

虚函数的

注意:

1、使用时,虚函数可以在基类中声明,提供界面。可以在给派生类中定义具体的实现方法,而得到多种方法。

2、多态的实现必须是公有派生。

原因:虚函数为了实现多态,虚函数建立在赋值兼容原则上,而赋值兼容原则成立的前提条件是派生类从基类那里公有派生。

3、派生类对基类中虚函数重新定义时,关键字可以写也可以不写,系统可以自动判断。

因为,虚函数会自动地从基类往下传播,可以虚到底。在函数调用时,总是先碰见基类的函数,只要把该函数设置为虚函数,系统就认为其他派生类函数就为虚的啦。

4、虚函数必须是类的成员函数,不能是友元函数,也不能是静态成员函数。

因为虚函数仅仅适用于有继承关系的类对象,所有普通函数不行。由于静态函数不收对象的捆绑,即使在形象上的捆绑,实际上也没有任何对象的信息,只有类的信息。但是虚函数可以是另一个类的友元函数

5、内敛函数不能是虚函数。

因为内敛函数不能在运行时动态确定位置。所以虚函数都是非内敛的。即使虚函数在类内部定义,在编译时,仍被看做为非内敛的。

6、构造函数不是虚函数。因为构造时,对象还不存在。

7、析构函数是可以虚函数,那么派生而来的所有派生类的析构函数也是虚函数,不管是否使用了关键字virtual

8、一旦一个函数被说明为虚函数,不管经历多少层派生,都将保持其虚特性,即虚特性具有继承性

9、如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。

10、虚函数在派生类中重新定义时,可以函数原型必须完全相同,如具有相同的形参个数和形参类型,但返回值可以不同。

 

纯虚函数:如果基类的函数没有必要或者无法实现,完全要依赖子类去实现的话,可以使用纯虚函数

简单点说,基类提供接口,而不提供定义,派生类提供实现

语法:

  1. virtual 函数返回值类型 虚函数名(形参表)=0;  

注意:从基类继承来的纯虚函数,在派生类中仍是纯虚函数,除非派生类按原型一致地超载该纯虚函数,变成类的纯虚函数

 

抽象类:包含了纯虚函数的类

注意:抽象类只能用作其他类的基类,不能建立对象,不能作为参数类型、返回类型或显式转换类型,可声明指针和引用。可以定义返回引用。

posted @ 2015-03-30 22:47  _tham  阅读(143)  评论(0编辑  收藏  举报