`
BlogDown
  • 浏览: 223955 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

C++的多态性实现机制剖析

C++ 
阅读更多

1、多态性和虚函数

我们先看一个例子:

 

  1. #include<iostream.h>  
  2. class animal  
  3. {  
  4. public:  
  5.     void sleep()  
  6.     {  
  7.         cout<<"animal sleep"<<endl;  
  8.     }  
  9.     void breath()  
  10.     {  
  11.         cout<<"animal breath"<<endl;  
  12.     }  
  13. };  
  14. class fish:public animal  
  15. {  
  16. public:  
  17.     void breath()  
  18.     {  
  19.         cout<<"fish bubble"<<endl;  
  20.     }  
  21. };  
  22. void main()  
  23. {  
  24.     fish fh;  
  25.     animal* pAn=&fh;  
  26.     pAn->breath();  
  27. }  
 

 

考虑一下这段程序的输出结果是什么?答案是输出:animal breath

我们在main函数中首先定义一个fish类的对象fh,接着定义了一个指向animal类的指针pAn,将fn的地址赋给了指针变量pAn,然后利用该变量调用pAn->breath()。许多人往往将这种情况和C++的多态性搞混淆,认为fh实际上是fish类的对象,应该调用fish类的breath函数,输出“fish bubble”。然而结果却不是这样,下面我们从2个角度来讲述原因。

1)编译的角度

我们构造fish类的对象时,首先要调用animal类的构造函数去构造animal类的对象,然后才调用fish类的构造函数完成自身部分的构造,从而拼接出一个完整的fish对象。当我们将fish类的对象转换为animal类型时,fish对象就被认为是原对象整个内存模型的上半部分,也就是animal对象所占内存。那么当我们利用类型转换后的对象指针去调用它的方法时,当然也就是调用它所在的内存中的方法。因此,输出animal breath,也就顺理成章了。

正如很多人所想,我们知道pAn实际指向的是fish类的对象。我们希望的输出结果是fish的breath方法,这个时候就该轮到虚函数登场了。前面的输出结果是因为编译器在编译的时候,就已经确定了对象调用 的函数 的地址。要解决这个问题要使用迟绑定(late binding)技术。当编译器使用迟绑定时,就会在运行时再去确定对象的类型以及正确的调用函数。而要让编译器使用迟绑定,就要在基类中声明函数时使用virtual关键字(注意:这是必须的,很多人就是因为没有使用虚函数而写出很多错误的例子),这样的函数我们成为虚函数。一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式的声明为virtual。

那么当我们将breath声明为virtual时,在背后发生了什么呢?

编译器在编译的时候,发现animal类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即vtable),该表是一个一维数组,在这个数组中存放每个虚函数的地址,对于上面的程序,animal和fish类都包含一个虚函数breath(),因此编译器会为这两个类都建立一个虚表。

那么如何定位虚表呢?编译器另外还为每个类的 对象 提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。对于上面的程序,由于pAn实际指向的对象类型是fish,因此vptr指向的是fish类的vtable,当调用pAn->breath()时,根据虚表中的函数地址找到的就是fish类的breath()函数。

正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的。换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针在什么时候,或者说在什么地方初始化呢?

答案是在构造函数中进行 虚表的创建 和 虚表指针的初始化 。还记得构造函数的调用顺序吗?在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类。并不知道后面还有没有继承者,它初始化父类对象的虚表指针,该指针指向父类的虚表。当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。对于上例的程序来说,当fish类的fh对象构造完毕后,其内部的虚表指针也就被初始化为指向fish的虚表。在类型转换后,调用pAn->breath(),由于pAn实际指向的是fish类的对象,该对象内部的虚表指针指向的是fish类虚表,因此最终调用的是fish类的breath()函数。

要注意:对于虚函数来说,每一个对象内部都有一个虚表指针,该虚表指针被初始化为本类的虚表,所有在程序中,不管你的对象类型如何转换,但该对象内部的虚表指针是固定的,所以呢,才能实现动态的对象函数调用,这就是C++多态性实现的原理。

总结:

1、每一个类都有虚表

2、虚表可以继承,如果子类没有重新虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有3项(虚函数的地址),派生类也会有虚表,至少有3项,如果重写了相应的虚函数,那么虚表的中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚函数表中就会添加该项。

3、派生类的虚表中虚函数的地址排列顺序和基类的虚表中虚函数地址排列顺序相同。


分享到:
评论

相关推荐

    C++实验报告多态性.doc

    本实验报告主要探讨了如何在C++中实现静态多态性和动态多态性。 一、多态性概述 1. 静态多态性(编译时多态性):通过函数重载和运算符重载实现。在编译期间,编译器就能确定调用哪个特定的函数或运算符实现。例如...

    c++多态性与虚函数练习题

    ### C++多态性与虚函数知识点解析 #### 一、多态性的概念 多态性是面向对象编程的一个核心特性,它允许我们通过基类的接口来操作派生类的对象。这种特性使得代码更加灵活且易于扩展。在C++中,多态性主要通过虚函数...

    C++中的多态性的分析与作用

    《C++中的多态性分析及其作用》 多态性是C++编程中一个核心的概念,它赋予了程序更高的灵活性和可扩展性。在本文中,我们将深入探讨多态性的定义、类型、实现方式以及运算符重载等相关知识点。 1. 多态性的定义 多...

    多态性实现机制在C 与JAVA中的比较分析.pdf

    本文主要对比分析了C++和Java两种语言中多态性的实现机制。 C++中的多态性主要通过虚函数(virtual functions)来实现。虚函数在基类中声明,子类可以覆盖这个函数,提供自己的实现。当通过基类指针或引用调用虚...

    C++之多态实现机制剖析

    本文将深入剖析C++中的多态实现机制。 1. 虚函数(Virtual Functions) 虚函数是C++中实现动态多态的基础。通过在基类中声明虚函数,子类可以重写这些函数,使得在使用基类指针或引用调用该函数时,实际执行的是...

    多态性实验报告-实验七

    通过本次实验,不仅巩固了面向对象程序设计的基础知识,还深入学习了多态性的实现机制及其在实际编程中的应用。特别是通过抽象类与纯虚函数的设计,体验到了多态带来的灵活性和扩展性,这将对未来的学习和开发工作...

    多态多态多态多态多态多态多态多态

    #### 二、多态性的实现机制 在C++中,多态性的实现主要依赖于虚函数机制。当基类中的成员函数被声明为`virtual`时,该函数在派生类中可以被重写,并且通过基类的指针或引用调用时将自动调用相应类型的版本。这种...

    C++中虚函数的实现机制

    在C++编程语言中,虚函数是实现多态性的关键机制之一。多态性是指同一个操作作用于不同的对象,可以有不同的解释,进而触发不同的行为。在面向对象编程中,这种特性允许我们编写更加灵活和可扩展的代码。 #### 二、...

    C++继承与多态性实验报告

    ### C++继承与多态性实验报告知识点解析 #### 实验背景及目的 本实验旨在通过实际操作加深学生对C++面向对象编程的理解,特别是针对继承与多态性的概念进行深入学习。实验要求学生能够利用C++语言特性,设计并实现...

    实验4 多态性的应用.doc

    多态性是一种面向对象程序设计的机制,它允许我们在编程时使用同一个函数名来调用不同的函数,以便实现不同的功能。这种机制能够帮助我们编写更加灵活和可维护的代码。 多态性在C++中的应用: 在C++中,多态性可以...

    C++ 实验多态性实验报告.pdf

    C++ 实验多态性实验报告.pdf 本实验报告的目的是让学生通过实验,深入了解C++语言中的多态性,并熟悉其使用方法。实验要求学生编写四个重载函数、一个抽象类和两个派生类,并对类Point重载 ++ 和 -- 运算符。 1. ...

    C++程序设计与实践:10-虚函数和多态性.ppt

    5. **内存布局与多态**:在继承体系中,派生类对象包含基类的部分,当基类指针指向派生类对象时,可以通过虚函数表(vtable)找到正确的函数实现,这是C++实现动态多态性的机制。 总的来说,多态性极大地增强了C++...

    C++继承,虚函数与多态性专题

    ### C++继承、虚函数与多态性专题 #### 继承的概念与访问权限 **继承**是面向对象编程中的一个核心概念,它允许我们创建一个新的类(派生类),该类继承另一个类(基类)的属性和方法。继承提高了代码的复用性和...

    C++虚函数表测试源码

    在C++编程语言中,虚函数表(Virtual Function Table,简称vtable)是实现多态性的一个关键机制。本文将深入探讨C++虚函数表的相关知识点,并通过标题中提到的测试源码进行详细讲解。 首先,理解虚函数的定义:在...

    C++制作的简单工厂模式计算器

    通过这个程序,我们可以学习到如何在C++中实现一个基本的四则运算计算器,并理解简单工厂模式在实际编程中的应用。 **四则运算的实现** 这个计算器程序包含了加、减、乘、除四种基本的数学运算。在C++中,我们可以...

    C++重载机制剖析(三篇)

    C++是一种强大的面向对象编程语言,它允许程序员通过重载机制来实现多态性,这是C++的一个核心特性。在本篇文章中,我们将深入探讨C++的重载机制,包括其概念、工作原理以及在实际编程中的应用。 一、重载的概念 ...

    现代多线程 JAVA和c++多线程实现 测试和调试

    本资源主要探讨了如何在JAVA和C++中实现多线程,以及相关的测试和调试技术。 在JAVA中,多线程的实现主要依赖于`Thread`类和`Runnable`接口。开发者可以通过直接继承`Thread`类或实现`Runnable`接口来创建新的线程...

Global site tag (gtag.js) - Google Analytics