精华内容
下载资源
问答
  • 这个问题的答案是: 多态的三个必要条件: 1.存在继承2.虚方法重写3.父类(指针或者引用)指向子类对象。...因此,调用虚函数总能产生多态,无论是通过引用,指针还是对象。 我的问题是:上面的分析正确吗?
  • 对象引用指针调用虚函数源程序实例,帮助初学者理解虚函数的用法
  • 本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章。问题描述大家都知道在面向对象...问题分析在最初接触到这个问题的时候,也很茫然,好像都知道不能在父类的构造函数里通过调用虚函数...

    本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列文章。

    问题描述

    大家都知道在面向对象语言中,多态实际上就是接口的多种不同的实现方式,而虚函数实现多态的机制就是通过指向派生类的基类指针或引用,访问派生类中同名覆盖的成员函数。那么有人知道为什么在父类的构造函数中调用了虚函数却不能实现多态吗?

    问题分析

    在最初接触到这个问题的时候,也很茫然,好像都知道不能在父类的构造函数里通过调用虚函数来实现多态,但是至于为什么还真的没有想过。但是遇到了这个问题不能不解决呀,所以又要开始发扬打破砂锅问到底的“好品质”了。

    首先在查看虚函数实现多态的方法描述里就能知道,虚函数之所以能实现多态完全得益于vptr指针和虚函数表,那么vptr指针到底是什么?虚函数表又是什么?他们之间又有什么关系呢?

    解决方案

    vptr指针:当类中声明虚函数时,编译器就会在类中自动生成一个虚函数表,同时编译器会在类实例化对象时在对象中加入vptr指针,且指向这个虚函数表。

    虚函数表:其实虚函数就是是通过一张虚函数表来实现的,简称为V-Table。在这个表中,主要是一个类的虚函数的地址,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

    因为vptr指针变量是在构造函数中进行初始化的,且初始化过程为:首先对象在创建的时,由编译器对VPTR指针进行初始化 ,只有当对象的构造完全结束后VPTR的指向才最终确定。

    所以当构造函数被调用时,编译器生成的VPTR指针只能产生通向它自己的虚函数表的调用,而不是指向最后派生的虚函数表,因为所有构造函数只有被调用后才会有最后派生的虚函数表,且不能知道有哪些子类继承自该父类。

    所以这就是为什么在父类构造函数中调用虚函数不能实现多态的原因。

    实习编辑:邱胜

    稿件来源:深度学习与文旅应用实验室(DLETA)

    展开全文
  • C++利用对象、引用、指针调用虚函数 虚函数实现原理说明: 每个类的大小比起所有成员数据多4个字节,表明有虚函数的类的大小还要加上一个紧缩的空指针类型的大小。这说明了该在包含虚函数的类中,编译系统自动加入了...

    C++利用对象、引用、指针调用虚函数

    虚函数实现原理说明:

    每个类的大小比起所有成员数据多4个字节,表明有虚函数的类的大小还要加上一个紧缩的空指针类型的大小。这说明了该在包含虚函数的类中,编译系统自动加入了一些表明类型的信息。

    当一个类中拥有虚函数时,编译系统将为该类创建一个数组VTABLE。VTABLE的元素是虚函数的地址,且同一虚函数的地址在基类和派生类的VTABLE中相对首位置的偏移是一样的。同时,编译系统还加入了相应的调用虚函数的代码。所有这些都是不需要程序员作的工作,由系统自动完成。在初始化该类对象时,将加入一个指向VTABLE的指针,这个指针一般称为VPTR。一般来说,VPTR位于该类对象的存储单元的最开始部位。

    这样,当VPTR被正确的初始化之后,便指向了该对象的VTABLE,从而在对象及其特定的虚函数定义间建立了联系。从虚函数调用的意义上来说,VPTR表明了类型信息,因为它使得调用与类型相符合。

    本例(见下文)可以看到,不管类层次进行了多少扩充,对于已有类对象的操作都不必作出改动。从这一点来看,虚函数所体现的运行时多态,大大提高了软件的可扩充性。我们知道,系统的设计者在初期集中于整个系统框架的合理构建,而在后期进行具体问题的分析,并逐渐扩充该框架。运行时多态保证了分析、设计、实现和扩充各个阶段的统一,使得系统的设计者在各阶段可以集中于眼前的工作,而不必为了以后不可预见的变化付出代价。

    虚函数总结:

    • 虚函数只能是类成员函数,它在基类体内部说明,目的是提供一种接口界面;

    • 虚函数不能是友元函数(即非成员函数),也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。虚函数可以在另一个类中被声明为友元函数;

    • 一旦一个函数定义为虚函数,那么无论它传下多少层,都将保持为虚函数,而不必每次都加关键字virtual;

    • 基类的虚函数可在一个或多个派生类中被重新定义,但其原型与基类必须完全相同(即返回类型、函数名、参数个数、类型及顺序一样),否则系统将认为派生类中的函数是重载的,而非虚函数;如果仅有返回类型不同,那么编译将出错;

    • 要虚函数发挥作用,必须用基类的指针(或引用)指向派生类的对象,并用指针(或引用)调用虚函数。也就是说,只有用地址才能体现运行多态性。因为不论是指向基类还是指向派生类的指针(引用),大小都是一样的,这样才能用基类指针指向派生类对象。这时,指针提供的信息是不完全的,在编译阶段不知道应该调用虚函数的哪个版本。而如果用对象调用虚函数,由于类型已经确定了,因此编译系统很可能采用预绑定;

    • 由于包含虚函数的基类指针可以指向其不同的派生类,并可执行不同版本的虚函数,提供了实现程序运行的多态性方法,因而将包含虚函数的类称为多态类。

    虚函数与一般重载函数的区别:

    • 重载函数在类型和参数数量上一定不相同,而重定义的虚函数则要求参数的类型和个数、函数返回类型相同;
    • 虚函数必须是类的成员函数,重载的函数则不一定是这样;
    • 构造函数可以重载,但不能是虚函数,析构函数可以是虚函数。

    指针的转换规则:

    • 指向基类的指针,可以指向它的公有派生的对象,但不能指向私有派生的对象;

    • 只能利用它直接访问派生类从基类继承来的成员,不能直接访问公有派生类中特定的成员;

    • 不能将指向派生类对象的指针指向其基类的一个对象。

    • 当虚函数在操作中引用的基类数据成员无法被派生类直接引用时(例如被隐藏的成员),便会出现错误。为了使用虚函数达到最好的动态联编效果,一般应以该虚函数第一次出现的类的引用体或指针作为参数,避免不确定因素。

    代码如下:

    /************************************************************************
    * 利用对象、引用、指针调用虚函数
    ************************************************************************/
    #include <IOSTREAM.H>
    //基类
    class CBase
    {
       int x;
    public:
       CBase(int n) {x=n;}
       virtual void PrintX() {cout<<"CBase::PrintX : "<<x<<endl;}
    };
    
    //派生类
    class CDerive : public CBase
    {
       int x;
    public:
       CDerive(int n1,int n2):CBase(n1) 
    {    x=n2;    }
       void PrintX() 
       {    cout<<"CDerive::PrintX : "<<x<<endl;
           CBase::PrintX();
       }
    };
    
    //子派生类
    class CSubDerive : public CDerive
    {
       int x;
    public:
       CSubDerive(int n1,int n2,int n3):CDerive(n1,n2)
       {    x=n3;    }
       void PrintX()
       {    cout<<"CSubDerive::PrintX : "<<x<<endl;
           CDerive::PrintX();
       }
    };
    
    void main()
    {   cout<<"CBase size = "<<sizeof(CBase)<<endl;    
       cout<<"CDerive size = "<<sizeof(CDerive)<<endl;
       cout<<"CSubDerive size = "<<sizeof(CSubDerive)<<endl;
       cout<<endl;
    
       CBase obj1(1);    
    CDerive obj2(2,3);
    CSubDerive obj3(4,5,6);
       obj1.PrintX();
       obj2.PrintX();
       obj3.PrintX();
       cout<<endl;
    
       CBase *pObj1=&obj1;    
       CDerive *pObj2=&obj2;    
       CSubDerive *pObj3=&obj3;
       pObj1->PrintX();
       pObj2->PrintX();
       pObj3->PrintX();
       cout<<endl;
    
       CBase *pObj[]={pObj1,pObj2,pObj3};
       pObj[0]->PrintX();
       pObj[1]->PrintX();
       pObj[2]->PrintX();
       cout<<endl;
    
       CBase &yobj1=obj1;
       CBase &yobj2=obj2;
       CBase &yobj3=obj3;
       yobj1.PrintX();
       yobj2.PrintX();
       yobj3.PrintX();
    }
    
    
    1. 运行结果如下(4组打印语句的打印结果完全一样,在此只给出前两组打印语句的画面):
      在这里插入图片描述

    2. 对象的内容:
      在这里插入图片描述

    3. 三个对象的地址及内容:

            ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190704212705392.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvdzUyNTI=,size_16,color_FFFFFF,t_70)
      
    4. 基类指针指向三个对象时,指针的详细内容:

    在这里插入图片描述

    1. 基类对象引用三个对象:

               ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190704212717625.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xvdzUyNTI=,size_16,color_FFFFFF,t_70)
      

    转自:https://blog.csdn.net/livelylittlefish/article/details/2171504
    更多C++相关知识体系,请移步C++知识目录

    展开全文
  • - 当在用父类的引用或者指针调用虚函数时,会先从该对象的头部取到虚函数的地址(C++标准规定虚函数表地址必须放最前),再从虚函数表中取到实际要调用的函数的地址,最终调用该函数 - 调用虚函数,相比普通函数...

    为什么C++里调用虚函数比调用普通函数慢?

    原因:
    - 运行时需要得到对应类的函数的地址
    - 当在用父类的引用或者指针调用虚函数时,会先从该对象的头部取到虚函数的地址(C++标准规定虚函数表地址必须放最前),再从虚函数表中取到实际要调用的函数的地址,最终调用该函数
    - 调用虚函数,相比普通函数,实际上多了三条指令:取虚表,取函数地址,call调用
    - 影响cpu流水线
    - 编译器不能是内联函数
    - 仅当用父类引用或者指针调用时,不能内联
    - 因为内联函数是指在编译期间用被调用的函数体本身来代替函数调用的指令,但是虚函数是直到运行时才能知道要调用的是哪一个函数,所以没法在编译时进行内联函数展开。
    - 但是当子类直接调用虚函数,是可以内联优化的。
    - 单继承时性能差不多,多继承的时候会慢

    展开全文
  • 关于c++虚函数多态的内部机制,有些经典的书籍已经给出了详细的阐述,例如侯捷先生的《深度探索C++对象模型》。 在此做些小总结。 >类的存储空间: 1) 含有虚函数的类有一个虚函数表,存储各个虚函数的地址。...

     

     


    关于c++虚函数多态的内部机制,有些经典的书籍已经给出了详细的阐述,例如侯捷先生的《深度探索C++对象模型》。

    在此做些小总结。

    >类的存储空间:
     1) 含有虚函数的类有一个虚函数表,存储各个虚函数的地址。
     2) 每个对象有一个虚函数指针,指向该其所属类的虚函数表;

    编译期间, 编译器会为每一个含有虚函数的类生成一个虚函数表, 这个虚函数表的大小必须在编译阶段确定下来,这也导致了模板函数不能为虚函数(https://blog.csdn.net/qq_35865125/article/details/109234801)。

    编译期间的另一项重要工作是,编译器会替换所有通过指针(引用)访问虚函数的地方,替换成 虚表[idx]。例如,p->func(), func函数在虚表中的位置是已经确定了的。 嗯,也就是说,基类的func函数在其虚函数表中的位置索引 = 各个子类的func函数在其虚函数表中的位置索引 , 这样才能保证通过一个固定的编译期间写死的索引值来定位到func函数。
     
    >执行:
    Base* p = new subClass;

    p->func();

    在编译期间,p->func()已经被替换为p->vptr[3], (假设func函数在虚表中的索引为3)。程序执行到这里时,会去p指向的地址空间取出虚函数表指针,另外,这个时候p指向的对象是哪个子类已经确定了,只需要到这个地址取它的虚指针就没错了,如果p指向的subClass A的对象地址,则,取出的是subClass A的虚指针。(虽然是父类的指针,但是它指向的还是子类的空间呀呀).。然后,就会顺利成章地调用到正确的虚函数了。

     

    虚表实现多态是支持面向对象语言的最大特点吧(另外,C++的模板技术可实现静态多态)。 在c++以后,c#,Java都相继扩展了虚表的使用范围,这些语言背后的设计这都是高手,他们在权衡了多态的利与弊之后还是做出了扩展虚表的决定。


    yes, 这种非多态的函数调用,编译阶段即可定位出要调用的是哪个具体的函数,跟普通的c语言函数调用一样哦。

     


    Ref:

    《深度探索C++对象模型》

    https://www.cnblogs.com/ViCross/archive/2008/09/28/1301767.html

     

    展开全文
  • 父类函数调用虚函数

    千次阅读 2015-04-19 09:57:08
    1.如果在父类函数中调用父类的虚函数,且该虚函数在子类中被改写了,那么在把子类指针/引用交给父类指针/引用的时候,在调用父类中的函数,该函数内的虚函数实际上是子类中改写过的。 #include class base { ...
  • 虚函数的实调用与虚调用

    千次阅读 2017-07-29 18:15:56
    虚调用是相对于实调用而言,它的本质是动态联编。...虚函数的实调用不通过指针或者引用调用虚函数虚调用不能简单的理解成“对虚函数的调用”,因为对虚函数的调用很有可能是实调用。#include using namespace std;cl
  • #include < iostream > using namespace std; class Box ...Box::Box(int vlength, int vbreadth, int vlongth) : length(vlength), breadth...错误 2 IntelliSense: 对象包含与成员函数不兼容的类型限定符
  • 利用对象、引用、指针调用虚函数

    千次阅读 2010-03-09 11:39:00
    利用对象、引用、指针调用虚函数 虚函数实现原理说明: 在“C++学习笔记(12)——多态与虚函数 ”中,每个类的大小比起所有成员数据多4个字节,表明有虚函数的类的大小还要加上一个紧缩的空指针类型的大小。...
  • 文章目录08:别让异常逃离析构函数09不在构造函数和析构函数中调用虚函数10重载=运算符,返回一个引用 08:别让异常逃离析构函数 09不在构造函数和析构函数中调用虚函数 10重载=运算符,返回一个引用 ...
  • 在构造函数中调用虚函数,并不会产生多态的效果,就跟普通函数一样。 c++primer第四版中497页15.4.5构造函数和析构中的虚函数讲到,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型...
  • 在一个类的成员函数前面加virtual 关键字,在另一个类中不加virtual关键字,除此之外两类的成员完全相同,然后用sizeof()判断两类的大小。... virtual void func()//加virtual关键字声明为虚函数 { ...
  •  虚函数不能是友元函数(即非成员函数),也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数。虚函数可以在另一个类中被声明为友元函数;  一旦一个函数定义为虚函数,那么无论它传...
  • 虚函数调用

    2013-02-27 11:44:17
    如果子类覆盖的父类的虚函数 则父类的指针或引用实际指向子类的对象 那通过该指针或引用调用虚函数时,则将调用子类的(虚)函数 如果不是虚函数,则将调用父类的函数
  • C++调用虚函数的方法

    2017-03-20 23:21:54
    调用虚函数的方法: 只有使用指针和引用才能实现多态性(如果在虚函数没有采用指针或者引用,那么就无法实现动态联编) class father { public:  virtual void run()const { cout "父亲可以跑万米...
  • #include &lt;iostream&gt;#include &.../*--------------------------------- 13-9 三种调用虚函数的方式比较---------------------------------*/class father{public: virtual void run()co...
  • 本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一、小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!...利用对象、引用、指针调用虚函数      虚函数实现原理说明:
  • 虚函数实现原理说明:    每个类的大小比起所有成员数据多4个字节,表明有虚函数的类的大小还要加上一个紧缩的空指针类型的大小。这说明了该在包含虚函数的类中,编译系统自动加入了一些表明类型的信息。 ...
  • 如果一个类有虚函数,那么这个类的虚函数会被放在一个虚函数表里面, 使用这个类声明的对象中,会有一个指向虚函数表的指针,当使用指向 这个对象的指针或者这个对象的引用调用一个虚函数的时候,就会从虚函数表中去...
  • 《Visual C++2012入门经典(第6版)》实例,使用指向基类对象的指针调用虚函数,使用引用处理虚函数
  • c++中基类指针指向派生类时,调用的虚函数是派生类的。那为何不直接用派生类的对象来调用就行了,设计虚函数的真正意图是什么?...其好处是:通过这个基类指针或者引用调用虚函数的时候,实际执行的是派生类对象...
  • 虚函数

    2020-05-18 11:00:00
    虚函数的作用就是实现多态性,多态性时将接口与实现分离,用形象的语言解释就是...当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才会有可能与静态类型不同 当我
  • 我是一个马上要考二级的学生,这几天对继承和派生中指针或引用虚函数和非虚函数调用规则和用delete删除指针对象时应该调用哪个析构函数还不知道。请知道的讲讲,但是我问其他人他们都用面向对象中的多态性来讲,...
  • 引用或指针的静态类型与对象本身的动态类型的不同,才是C++支持多态的根本所在。...只有通过指针或引用调用虚函数才会发生动态绑定,因为只有这种情况,引用或指针的静态类型与对象本身的动态类型才会不同。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,987
精华内容 794
关键字:

引用调用虚函数