(转)C++中虚函数功能的实现机制_weixin_30305735的博客-程序员秘密

技术标签: c/c++  

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

要理解C++中虚函数是如何工作的,需要回答四个问题。

1、  什么是虚函数。

虚函数由于必须是在类中声明的函数,因此又称为虚方法。所有以virtual修饰符开始的成员函数都成为虚方法。此时注意是virtual修饰的成员函数不是virtual修饰的成员函数名。

例如:基类中定义:

                            virtual void show();           //由于有virtual修饰因此是虚函数

                            voidshow(int);          //虽然和前面声明的show虚函数同名,但不是虚函数。

所有的虚函数地址都会放在所属类的虚函数表vtbl中。另外在基类中声明为虚函数的成员方法,到达子类时仍然是虚函数,即使子类中重新定义基类虚函数时未使用virtual修饰,该函数地址仍会放在子类的虚函数表vtbl中。

 

2、  正确区分重载、重写和隐藏。

注意三个概念的适用范围:处在同一个类中的函数才会出现重载。处在父类和子类中的函数才会出现重写和隐藏。

重载:同一类中,函数名相同,但参数列表不同。

重写:父子类中,函数名相同,参数列表相同,且有virtual修饰。

隐藏:父子类中,函数名相同,参数列表相同,但没有virtual修饰;

                    或:函数名相同,参数列表不同,无论有无virtual修饰都是隐藏。

例如:

                   基类中:(1)    virtual void show();           //是虚函数

                                     (2)    void show(int);          //不是虚函数

                   子类中:(3)    void show();                        //是虚函数

                                     (4)    void show(int);          //不是虚函数

1,2构成重载,3,4构成重载,1,3构成重写,2,4构成隐藏。另外2,3也会构成隐藏,子类对象无法访问基类的void show(int)成员方法,但是由于子类中4的存在导致了子类对象也可以直接调用void show(int)函数,不过此时调用的函数不在是基类中定义的void show(int)函数2,而是子类中的与3重载的4号函数。

 

3、  虚函数表是如何创建和继承的。

基类的虚函数表的创建:首先在基类声明中找到所有的虚函数,按照其声明顺序,编码0,1,2,3,4……,然后按照此声明顺序为基类创建一个虚函数表,其内容就是指向这些虚函数的函数指针,按照虚函数声明的顺序将这些虚函数的地址填入虚函数表中。例如若show放在虚函数声明的第二位,则在虚函数表中也放在第二位。

 

对于子类的虚函数表:首先将基类的虚函数表复制到该子类的虚函数表中。若子类重写了基类的虚函数show,则将子类的虚函数表中存放show的函数地址(未重写前存放的是子类的show虚函数的函数地址)更新为重写后函数的函数指针。若子类增加了一些虚函数的声明,则将这些虚函数的地址加到该类虚函数表的后面。

 

4、  虚函数表是如何访问的。

当执行pBase->show()时,要观察show在Base基类中声明的是虚函数还是非虚函数。若为虚函数将使用动态联编(使用虚函数表决定如何调用函数),若为非虚函数则使用静态联编(根据调用指针pBase的类型来确定调用哪个类的成员函数)。此处假设show为虚函数,首先:由于检查到pBase指针类型所指的类Base中show定义为虚函数,因此找到pBase所指的对象(有可能是Base类型也可能是Extend类型。),访问对象得到该对象所属类的虚函数表地址。其次:查找show在Base类中声明的位置在Base类中所有虚函数声明中的位序。然后到pBase所指对象的所属类(有可能是Extend哦,多态)的虚函数表中访问该位序的函数指针,从而得到要执行的函数。

         例如:

                   基类Base::virtualvoid show();                 (1)

                   子类Extend::virtualvoid show();             (2)

                   Externext;

                   Base*pBase=&ext;

                   pBase->show();

 

当执行pBase->show();时首先到Base中查看show(),发现其为虚函数,然后访问pBase指向的ext对象,在对象中得到Extend类的虚函数表,在Base类声明中找到show()声明的位序0,访问Extend类的虚函数表的位置0,得到show的函数地址。注意若只有基类定义了virtual void show();而子类未重写virtual void show();即上面的函数(2),则Extend虚函数表中的位序0中存放的地址仍然是Base类中定义的virtual void show()函数,而若Extend类中重写了Base类中的virtual void show()方法,则Extend的虚函数表中位序0的函数地址将被更新为Extend中新重写的函数地址。从而调用pBase->show()时将产生多态的现象。

 

总结:当调用pBase->show();时,执行的步骤:

1,  判断Base类中show是否为虚函数。

2,  若不是虚函数则找到pBase所指向的对象所属类Base。执行Base::show()。若是虚函数则执行步骤3.

3,  访问pBase所指对象的虚函数表指针得到pBase所指对象所在类的虚函数表。

4,  查找Base中show()在声明时的位序为x,到步骤3得到的虚函数表中找到位序x,从而得到要执行的show的函数地址。

5,  根据函数地址和Base中声明的show的函数类型(形参和返回值)访问地址所指向的函数。

 

 

以上为虚函数的工作机制。

注意只有用virtual修饰的成员方法才会放到虚函数表中去。

子类对父类函数的隐藏将导致无法通过子类对象访问基类的成员方法。

因此给出以下建议:

1、  若要在子类中重新定义父类的方法(有virtual为重写,无virtual为隐藏),则应确保子类中的函数声明和父类函数声明中的形参完全一样。但返回值类型是基类引用/指针的成员函数在重新定义时可以返回子类的引用/指针(返回值协变),这是由于子类的对象可以赋给基类引用/指针。

2、  若基类中声明了函数的重载版本,则在派生类中重新定义时应该重新定义所有基类的重载版本。这是因为,重新定义一个函数,其他的基类重载版本将被隐藏,导致子类无法使用这些基类的成员方法。所以需要每个都重新定义。若一些父类的重载版本,子类确实不需要修改,则由于重新定义了一个重载版本,即使有些重载版本不需要修改也要重新定义,在定义体中直接调用基类的成员方法(使用作用于限定符访问)。

3、  从虚函数的实现机制可以看到要想在子类中实现多态需要满足三个重要的条件。(1)在基类中函数声明为虚函数。(2)在子类中,对基类的虚函数进行了重写。(3)基类的指针指向了子类的对象。

 

上述转载自:here

隐藏测试: 子类中只定义了show(),那么子类对象将不能访问父类的show(int)

#include<iostream>
using namespace std;
class Parent{
public:
    void show()
    {
        cout<<"Parent::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Parent::show()"<<t<<endl;
    }
};
class Child: public Parent{
public:
    void show()
    {
        cout<<"Child::show()"<<endl;
    }
};
int main()
{
    Child c;
    c.show();

    getchar();
    return 0;
}
Child::show()

多态 父类中没有定义虚函数show,则使用静态联编,根据指针p的类型来决定调用哪个类的函数
#include<iostream>
using namespace std;
class Parent{
public:
    void show()
    {
        cout<<"Parent::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Parent::show()"<<t<<endl;
    }
};
class Child: public Parent{
public:
    void show()
    {
        cout<<"Child::show()"<<endl;
    }
};
int main()
{
    Child c;
    Parent *p=&c;
    p->show();
    p->show(2);

    getchar();
    return 0;
}

Parent::show()

Parent::show()2

 

多态 父类中定义了虚函数show,查找show在Base类中声明的位置在父类中所有虚函数声明中的位序。然后找到p所指对象的所属类的虚函数表中访问该位序的函数指针,从而得到要执行的函数。

#include<iostream>
using namespace std;
class Parent{
public:
    virtual void show()
    {
        cout<<"Parent::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Parent::show()"<<t<<endl;
    }
};
class Child: public Parent{
public:
    void show()
    {
        cout<<"Child::show()"<<endl;
    }
};
int main()
{
    Child c;
    Parent *p=&c;
    p->show();
    p->show(2);

    getchar();
    return 0;
}

Child::show()

Parent::show()2

 

多态 如果子类中没有重新父类方法,那么复制父类的虚函数表

#include<iostream>
using namespace std;
class Parent{
public:
    virtual void show()
    {
        cout<<"Parent::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Parent::show()"<<t<<endl;
    }
};
class Child: public Parent{
public:
    void show()
    {
        cout<<"Child::show()"<<endl;
    }
};
class GrandChild:public Child{

};
int main()
{
    GrandChild c;
    Parent *p=&c;
    p->show();
    p->show(2);

    getchar();
    return 0;
}

Child::show()

Parent::show()2

 

多态 如果子类重写了父类的虚函数,那么通过p指针所在类的该虚函数的位序访问子类中的对应的函数,如果子类重写的是非虚函数,那么通过指针p只能访问p所在类的该函数

#include<iostream>
using namespace std;
class Parent{
public:
    virtual void show()
    {
        cout<<"Parent::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Parent::show()"<<t<<endl;
    }
};
class Child: public Parent{
public:
    void show()
    {
        cout<<"Child::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"Child::show()"<<t<<endl;
    }
};
class GrandChild:public Child{
    void show()
    {
        cout<<"GrandChild::show()"<<endl;
    }
    void show(int t)
    {
        cout<<"GrandChild::show()"<<t<<endl;
    }
};
int main()
{
    GrandChild c;
    Parent *p=&c;
    p->show();
    p->show(2);

    getchar();
    return 0;
}

GrandChild::show()

Parent::show()2

 

转载于:https://www.cnblogs.com/kylehz/p/4663468.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30305735/article/details/97280207

智能推荐

Morgan Rothschild Academy job_iteye_5113的博客-程序员秘密

1成功Morgan Rothschild Academy Year End Farewell fromNursery News 2012School’s Over Already!I am Morgan Rothschild Academy so sad to see the end of the year here at Morgan Rothschild Academy...

华为交换机查光衰_华为交换机硬件信息查看命令_我投三分的博客-程序员秘密

查看主控板、业务板信息display version[HUAWEI]dis verHuawei Versatile Routing Platform SoftwareVRP (R) software, Version 5.130 (S7700 V200R003C00SPC500)Copyright (C) 2000-2013 HUAWEI TECH CO., LTDQuidway S7703 Te...

FreeType 2的设计——《The design of FreeType 2》中译版_关于freetype的书_lost7788的博客-程序员秘密

介绍     这份文档提供了FreeType 2函数库设计与实现的细节。本文档的目标是让开发人员更好的理解FreeType 2是如何组织的,并让他们扩充、定制和调试它。      首先,我们先了解这个库的目的,也就是说,为什么会写这个库:* 它让客户应用程序方便的访问字体文件,无论字体文件存储在哪里,并且与字体格式无关。* 方便的提取全局字体数据,这些数据在

BZOJ 1718: [Usaco2006 Jan] Redundant Paths 分离的路径( tarjan )_weixin_30361753的博客-程序员秘密

tarjan求边双连通分量, 然后就是一棵树了, 可以各种乱搞...-------------------------------------------------------------------------------#include&lt;cstdio&gt;#include&lt;cstring&gt;#include&lt;algorithm&gt;using namespace...

docker registry 查询,上传或获取私有仓库中的镜像_orange_bug的博客-程序员秘密

查询指令docker 查询私有仓库(registry)中的镜像,使用 docker search 192.168.1.8:5000命令经测试不好使用。更好用的如下:注意是http协议不是https[[email protected] docker]# curl -XGET http://10.10.0.233:5000/v2/_catalog上传镜像到私有仓库1、标记镜像为私有仓库的镜像 docker tag jdk1.8 宿主机IP:5000/jdk1.82、再次启动私有仓库容器 doc

随便推点

Java中的内置注解、元注解以及自定义注解_CSDN蔡茂的博客-程序员秘密

Java中的注解注解(Annotation),也叫元数据,标签,注释。注解叫注释的时候,容易和java的代码注释混淆,一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。注解一般来说都会配合反射使用,通过反射技术获取到注解的相关信息才能在看似简介的代码中实现其...

语音合成论文优选:自动打分系统MBNet: MOS Prediction for Synthesized Speech with Mean-Bias Network_语音mos打分数据库_我叫永强的博客-程序员秘密

声明:语音合成论文优选系列主要分享论文,分享论文不做直接翻译,所写的内容主要是我对论文内容的概括和个人看法。如有转载,请标注来源。欢迎关注微信公众号:低调奋进MBNet: MOS Prediction for SynthesizedSpeechwith Mean-Bias Network本文是中国科学技术大学在2021.02.27更新的文章,主要工作是对合成的音频进行MOS值得预测,从而减少大量劳动力,具体的文章链接https://arxiv.org/pdf/2103.00110.pdf..

eureka.client.service to java.util.Map_没忄没肺的博客-程序员秘密

在对springboot与springclould进行集成的时候,application.yml文件需要配置eureka的信息,内容如下:eureka: client: service-url: defaultZone:http://localhost:8761/eureka/配置之后报错,内容如下:2019-02-24 17:09:04.988 WARN 825...

手把手使用Python语音识别,进行语音转文字_DataBaker标贝科技的博客-程序员秘密

Python调用标贝科技语音识别接口,实现语音转文字环境准备:Python 3登录点击产品地址进行登录,支持短信、密码、微信三种方式登录。创建新应用登录后进入【首页概览】,各位开发者可以进行创建多个应用。包括一句话识别、长语音识别、录音文件识别;在线合成、离线合成、长文本合成,并可以根据自己需求进行用量、音色、并发等接入试用。值得注意的是,每个应用都是独立的(量不共享),其使用数据也为单独统计,更加方便开发者测算自家产品调用服务形式。未认证商户只能创建1个应用以供试用企业

js,css压缩打包_zgmzyr的博客-程序员秘密

net.alchim31.maven yuicompressor-maven-plugin 1.3.0 process-resources compress **/*.min.js **/*-min.js

iview 时间控件 日期处理格式解决方法_ivew日期控件输出时间格式_咖啡有点酷的博客-程序员秘密

&lt;FormItem label="年月日" label-position="top" prop="check_ym"&gt; &lt;DatePicker v-model="formData.check_ym" type="date" :disabled="isSee || isOperate" :format="dateFormat" placeholder="请...

推荐文章

热门文章

相关标签