您的位置:首页 > 其它

多态的实现原理

2017-07-17 21:58 253 查看
一、虚表

在C++语言中,每个有虚函数的类或者虚继承的子类,编译器都会为它生成一个虚拟函数表(简称:虚表),表中的每一个元素都指向一个虚函数的地址。(注意:虚表是从属于类的)

此外,编译器会为包含虚函数的类加上一个成员变量,是一个指向该虚函数表的指针(常被称为vptr),每一个由此类别派生出来的类,都有这么一个vptr。虚表指针是从属于对象的。也就是说,如果一个类含有虚表,则该类的所有对象都会含有一个虚表指针,并且该虚表指针指向同一个虚表。

虚表的内容是依据类中的虚函数声明次序--填入函数指针。派生类别会继承基础类别的虚表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚表就受了影响;表中的元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。

 

二、多态实现的原理(摘自网上)http://blog.csdn.net/haoel/article/details/1948051/#comments

下面,我将分别说明"无覆盖"和"有覆盖"时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

  

一般继承(无虚函数覆盖)

  

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

  



  

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

  

对于实例:Derive d; 的虚函数表如下:



  

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

  

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

 

 

 

一般继承(有虚函数覆盖)
  

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

  

 


  

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

  



  

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

  

这样,我们就可以看到对于下面这样的程序,

  

            Base *b = new Derive();

  

            b->f();

  

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

  

  

  多重继承(无虚函数覆盖)

多重继承(无虚函数覆盖)

  

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

  


 

  

对于子类实例中的虚函数表,是下面这个样子:



  

我们可以看到:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

  

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

  

  

  

多重继承(含虚函数的覆盖)  

多重继承(有虚函数覆盖)

  

下面我们再来看看,如果发生虚函数覆盖的情况。

  

下图中,我们在子类中覆盖了父类的f()函数。

  


 

  

下面是对于子类实例中的虚函数表的图:

  



  

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。

 

三、重载、隐藏、覆盖区别

重载:针对同一类,函数名字相同,参数列表不同,和返回值无关即返回值不能作为函数重载的判断条件。

函数的两个要素:参数与返回值。

如果同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。

如果同名函数仅仅是返回值类型不同,有时可以区分,有时却不能。例如:

void Function(void);

int Function (void);

上述两个函数,第一个没有返回值,第二个的返回值是int 类型。如果这样调用函数:

int x = Function ();

则可以判断出Function 是第二个函数。问题是在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。

所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符。

 

隐藏:基于继承,通过子类的对象 就无法调动父类的相同名字的函数(只要名字相同)

 

覆盖:1子类继承基类 2.父类有虚函数3.通过父类指针或者引用-----》通过接口实现多态。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息