C++ 多态性

来源:互联网 发布:java正则表达式实例 \ 编辑:程序博客网 时间:2024/06/04 19:49


多态概念介绍


      进来心血来潮,打算重新开始学习C++,因为我一直信奉小甲鱼老师说的“编程改变世界”,呵呵。在学习的过程中知道了C++确实比C晦涩难懂一点,不过没有关系,我依旧相信自己能够学好(加油!!!),下面整理了一些大神们关于多态的见解,方面自己学习。

     我个人觉得----类的使用使C++编程变的简单,多态使C++变得强大。

     所谓的多态即用父类型的指针指向子类对象,然后通过父类的指针调用实际之类的成员函数,因此父类的指针具有多种形态。多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
     从系统的实现来看,多态性分为两类:静态多态性和动态多态性。

     <静态多态性>:以前学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序的编译时系统就能决定调用的是哪个函数,因此静态多态性又称为编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载也属于函数重载)。

      <动态多态性>:C++动态多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(个人观点:重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性

     然而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题-------《但这并没有体现多态性》。

      多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

关于早绑定和晚绑定可参考博文http://blog.sina.com.cn/u/1736689713

      那么多态的作用是什么呢,封装可以使得代码模块化继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有。

 /*******************ege:*********************/

class A{

};

class B : public A{

};

由以上可知:A是基类,B是派生类

[情景归纳]:

(1).     A *a1 =new A();

   此时指针a1是A类型,指向A类型的对象;若要调用a1对象的函数,直接调用。

(2).   B *a2 = new B();

   此时指针a2是B类型,指向B类型的对象;如若调用a2对象的函数(除构造函数外),直接调用。

   对于构造函数需要先调用A的构造函数,再调用B的构造函数。

(3).     A*a3 = new B();

   此时a3是A类型,指向A类型对象和B类型对象;如若调用a3对象的函数(除构造函数外),会先调用基类(A类)的该函数,如果基类里的该函数是virtual类型的,则会调用B类的该函数,否则只调用基类的该函数;对于构造函数来说需要先调用基类的构造函数,再调用B类的构造函数。

         内存中a3指向的区域:

a3 ->  | A对象 (基类)|

   |B对象  (子类)|

      利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。


  多态性的条件:
    1:基类的虚函数。
    2:派生类的虚函数必须和基类的虚函数声明一致(包括参数类型,返回值类)
    3:类的成员函数才可以说明成虚函数(一般函数不行)。静态成员函数不受制于某个对象,不能说明成虚函数。内联函数不能在运行中动态确定。构造函数因为负责构造对象,所以也不能是虚函数。而析构函数一般是虚函数。
    4:指针,或者引用才能实现多态

例题:

复制代码
 1 #include <iostream> 2  3 using namespace std; 4  5 class A 6 { 7 public: 8     void foo() 9     {10     cout<<"A::function foo"<<endl;11     }12 virtual void fuu()13     {14         cout<<"A::function fuu"<<endl;15     }16 };17 class B:public A18 {19     public:20     void foo()21     {22        cout<<"B::function foo"<<endl;23     }24     void fuu()25     {26         cout<<"B::function fuu"<<endl;27     }28 };29 int main()30 {31     A a;32     B b;33 34     A *p = &a;35     p->foo();36     p->fuu();37     p = &b;38     p->foo();39     p->fuu();40     return 0;41 }
复制代码

      第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数。

      第二个p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了。而p->fuu()指针是基类指针,指向的fuu是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fuu()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fuu()函数的地址。


!!!注意::

1):对子类对象进行析构时,先调用子类的析构函数,再调用基类的析构函数;这个顺序是和构造函数恰恰相反的。

2):构造函数是不能声明为虚类型的。

3):子类实例化的时候,相应的基类的构造函数同样会被调用。

4):当基类的成员函数是虚类型时,子类的该函数也会自动的为虚类型。

原创粉丝点击