精华内容
下载资源
问答
  • 虚函数实现机制

    2011-02-08 16:45:00
    虚函数实现机制(2007-04-24 09:07:26)转载标签:虚函数实现机制内存分析反汇编   注:本文系原创,如需转载,请注明出处和作者.   C++实现多态靠两种方案,静态的重载和动态的虚函数机制.下面通过VC...

    虚函数实现机制

    (2007-04-24 09:07:26)
     

     注:本文系原创,如需转载,请注明出处和作者.
     
    C++实现多态靠两种方案,静态的重载和动态的虚函数机制.下面通过VC6环境分析下虚函数的实现机制.
     (其实在VC6中,重载的实现方式是名字粉碎,即给函数通过某种方式起个别名,实现"多态",个人认为并不是真正的多态)
    代码经VC6+WINXP SP2编译通过.
    #include <iostream.h>
    class CFather 
    {
    public:
     virtual void virFunc();{cout<<"CFather::virFunc()"<<endl;}
     CFather();
     virtual ~CFather(); //虚函数定义
    };
    class CSon1 : public CFather 
    {
    public:
     void virFunc();{cout<<"CSon1::virFunc()"<<endl;} //重写
     CSon1();
     virtual ~CSon1();
    };
    class CSon2 : public CFather 
    {
    public:
     void virFunc();{cout<< "CSon2::virFunc()" <<endl;} //重写
     CSon2();
     virtual ~CSon2();

    };
    在父类中定义了虚函数virFunc,两个子类分别重写,默认也为virtual类型.main 函数中调用如下:
    int main(int argc, char* argv[])
    {
     CFather father;
     CSon1 son1;
     CSon2 son2;

     CFather* pObj = &father; //父类指针

     pObj->virFunc();

     pObj = &son1; //子类指针

     pObj->virFunc();

     pObj = &son2; //子类指针

     pObj->virFunc();

     return 0;
    }

    根据虚函数机制,结果应该为:
    CFather::virFunc()
    CSon1::virFunc()
    CSon2::virFunc()

    这里解释下,在C++中,基类指针可以指向其派生类,是实现动态多态的必要前提.

    那么,在VC6中是如何实现虚函数呢?答案是虚函数表。看下编译时的内存情况。
    当执行至CFather father 后,观察。(不同机器地址可能有所不同)
    &father 的地址为:0x0012ff70,打开memory,该地址内容为:
    0012FF70  1C 80 42 00 B0 FF 12 00 3B 6A 41 00 00 00 00 00 
    首地址的4字节内容为:0042801c,这就是虚表的入口,查看memory,该地址内容为:
    0042801C  46 10 40 00 64 10 40 00 00 00 00 00 43 46 61 74 
    由于虚表中存放的都是函数指针,所以,每4个字节为一个地址,可以从这个表中找到两个地址:0x00401046,0x00401064。分别对应virFunc和析构函数。

    同理,看一下son2的情况,换个观察方式。
    当执行至 CSon2时,变量监视窗口可以看到son2的__vfptr的值0x00428086,其下分别为__vfptr[0],__vfptr[1],值分别为0x00401014,0x00401023。有点眼熟,对,这就是son2类的虚表,只是在从CFather继承来的时候,重写的函数地址被覆盖了。

    以上为编译时候内存的情况,通过分析我们可以得知,在实例化类的同时,实例的首地址存储其虚表的地址。当子类重写时,将其重写的地址覆盖从父类继承来的虚表。当不同指针调用时,根据其虚表选取不同的函数,实现多态性.也就是说,这样的调用是在运行时才绑定的,所以称为动态的多态性.

    然后我们看下反汇编的情况。

    编译时单步进入00401690   call    @ILT+55(CFather::CFather) (0040103c)

    004010E9   pop         ecx
    004010EA   mov         dword ptr [ebp-4],ecx
    004010ED   mov         eax,dword ptr [ebp-4]
    004010F0   mov         dword ptr [eax],offset CFather::`vftable' (0042801c)

    我们来分析这四行代码。
    首先,pop ecx,
          mov dword ptr [ebp-4],ecx
    将ecx出栈,并将其值按两个字(4个字节)传给ebp-4中的地址。通过观察registers(寄存器窗口),ecx=0012ff70。这里
    的ecx其实就是实例对象的this指针。
    然后,mov eax,dword ptr [ebp-4]
    将this指针传值给寄存器eax。因为汇编中不允许两个内存地址的操作,所以必须

    采用eax做中转.
    最后,mov dword ptr [eax],offset CFather::`vftable' (0042801c)
    使用offset,将虚表的地址按两个字传给eax中的地址,即this指针指向了虚表地址。这就可以解释为什
    么对象的首地址都是虚表的地址了。

    子类实现方式同上,只是在call本身的构造之前,首先要进行父类的构造。

    另外,在编译的时候,根据函数定义的先后顺序,在虚拟表中安排函数的位置,其实就是定义了一个函数指针的数组。当采用相应的对象调用函数时,在数组中相应的偏移寻找函数。

    还要注意,一般在类定义的时候,构造函数不可以采用虚函数,而析构函数则最好采用虚拟函数

    有关虚拟函数的定义,概念等以及汇编知识等请参考相应资料。本文不再讨论。

     

    展开全文
  • C++虚函数实现机制

    2019-03-02 19:58:42
    C++虚函数实现机制 C++程序的内存格局通常分为四个区:全局数据区,代码区,栈区,和堆区(即自由存储区)。 全局数据区存放全局变量,静态数据和常量;所有类成员函数和非成员函数代码存放在代码区;为运行函数而...

    C++虚函数实现机制

    C++程序的内存格局通常分为四个区:全局数据区,代码区,栈区,和堆区(即自由存储区)。
    全局数据区存放全局变量,静态数据和常量;所有类成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量,函数参数,返回数据,返回地址等存放在栈区,余下的空间都被成为堆区。
    类成员函数是放在代码区,而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对相关生成一个拷贝,所以它是属于对象的。
    在这里插入图片描述

    类的静态成员函数和非静态成员函数的区别

    静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,因而可以说它们都是属于类的。区别在于类的非静态类成员函数其实都内含了一个指向类对象的指针参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
    所有说类的成员函数其实是有大小的,其大小为void的两倍。在64位的机器下,void的大小为8个字节,类成员函数的大小为16个字节,猜想是因为里面还存了一根this指针。

    #include <iostream>
    class IType {
    public:
    	void Show1() {
     
    	}
    };
    class IType2 {
    public:
    	void Show2() {
     
    	}
    };
    class MyClass : IType, IType2
    {
    public:
    	void Show3() {}
    };
    
    int main()
    {
    	int a = sizeof(&MyClass::Show3);
    	std::cout << sizeof(void *) << "," << a << std::endl;  //vs 下输出4,8。g++ 下输出4,8
    	return 0;
    }
    

    C++对象的内存布局

    要想知道C++对象的内存布局, 可以有多种方式, 比如:

    1. 输出成员变量的偏移, 通过offsetof宏来得到
    2. 通过调试器查看, 比如常用的VS
    class Base1
    {
    public:
        int base1_1;
        int base1_2;
     
        virtual void base1_fun1() {}
    };
    
    sizeof(Base1) 16
    offsetof(Base1, base1_1 8
    offsetof(Base1, base1_2) 12

    在这里插入图片描述
    base1_1前面多了一个变量__vfptr(常说的虚函数表vtable指针),其类型为void**,这说明它是一个void*指针,它是一个指向指针数组的指针。
    虚函数指针__vfptr位于所有的成员变量之前定义。

    class Base1
    {
    public:
    	int base1_1;
    	int base2_2;
    
    	virtual void base1_fun1() {}
    	virtual void base1_fun2() {}
    };
    

    我们再加上一个虚函数。
    在这里插入图片描述
    我们发现类的大小没变,这更加坚定了我们认为__vfptr是一个指向数组的指针。
    而__vfptr所指向的函数指针数组里出现了第2个元素,其值为Base2类的第2个虚函数base1_fun2()函数的地址。

    void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun2 };
    const void** __vfptr = &__fun[0];
    
    1. 更加坚定我们前面描述的:__vfptr只是一个指向函数指针数组的指针。
    2. 增加一个虚函数,只是简单地向该类所对应的虚函数表里增加一项而已,并不会影响到类对象的大小以及布局情况。
      同一个类的不同实例共用一份虚函数表,它们都通过一个所谓的虚函数指针__vfptr指向该虚函数表。
      虚函数表再编译时期为我们创建好的,只存在一份。定义类对象时,编译器自动将类对象的__vfptr指向这个虚函数表。

    先研究单继承

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Derive1 : public Base1
    {
    public:
        int derive1_1;
        int derive1_2;
    };
    

    在这里插入图片描述

    class Base1
    {
    public:
        int base1_1;
        int base1_2;
    
        virtual void base1_fun1() {}
        virtual void base1_fun2() {}
    };
    
    class Derive1 : public Base1
    {
    public:
        int derive1_1;
        int derive1_2;
    
        // 覆盖基类函数
        virtual void base1_fun1() {}
    };
    

    在这里插入图片描述
    在这里插入图片描述

    再研究下多继承

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    class Base1
    {
    public:
    	int base1_1;
    	int base1_2;
    
    	virtual void base1_fun1() {}
    	virtual void base1_fun2() {}
    };
    
    class Base2
    {
    public:
    	int base2_1;
    	int base2_2;
    
    	virtual void base2_fun1() {}
    	virtual void base2_fun2() {}
    };
    
    // 多继承
    class Derive1 : public Base1, public Base2
    {
    public:
    	int derive1_1;
    	int derive1_2;
    
    	// 基类虚函数覆盖
    	virtual void base1_fun1() {}
    	virtual void base2_fun2() {}
    
    	// 自身定义的虚函数
    	virtual void derive1_fun1() {}
    	virtual void derive1_fun2() {}
    };
    
    using namespace std;
    
    int main()
    {
    	Derive1 d;
    	cout<<sizeof(d)<<endl;
    	Base1 *base = new Derive1();
    	cout << sizeof(Base1) << endl;
    	cout << offsetof(Base1, base1_1) << endl;
    	cout << offsetof(Base1, base1_2) << endl;
    }
    

    在这里插入图片描述
    在这里插入图片描述
    可见Derive1里面既有Base1类的虚函数指针,又有Base2类的虚函数指针,所以sizeof才会是40.
    而Derive1自身的虚函数其实在Base1类的虚函数表里(VS debug模式里看不到,从汇编中可以看到),如果Base1类里没有虚函数,那么就在Base2类里,且Base2类的位置更前。
    在这里插入图片描述

    展开全文
  • 虚函数实现机制 静态绑定和动态绑定 静态绑定和动态绑定实际上是针对C++动多态机制而言的。所谓的绑定指的是函数的绑定,对于静态绑定,其函数的地址是在编译时期就已经确定了,而动态绑定是在运行时期才能确定,...

    目录

    静态绑定和动态绑定

    虚函数实现机制


    静态绑定和动态绑定

    静态绑定和动态绑定实际上是针对C++动多态机制而言的。所谓的绑定指的是函数的绑定,对于静态绑定,其函数的地址是在编译时期就已经确定了,而动态绑定是在运行时期才能确定,在汇编层次实际上是call一个寄存器。静态绑定也是对非虚函数的绑定,动态绑定是对虚函数的绑定。

    虚函数实现机制

    什么是虚函数?对于类的成员方法在其前面加上virtul关键字进行修饰,那么该成员函数就变成了虚函数。先看一下代码:

    class Base {
    public:
    	Base(int data) :_data(data) {}
    	void show() {cout << "Base show()" << endl;}
    private:
    	int _data;
    };
    
    class Derive :public Base {
    public:
    	Derive(int data) :Base(data), _datab(data) {};
    	void show() { cout << "Derive show()" << endl; }
    private:
    	int _datab;
    };
    int main() {
    	Derive d(10);
    	Base* p = &d;
    	p->show();
        /*
        没有虚函数,因此是静态绑定
    	p->show();
        mov   ecx,dword ptr [p]  
        call  Base::show (033325Ch)
        可以看出来在编译时期就知道了函数的地址033325Ch
        */
    	cout << sizeof(Base) << endl;
    	cout << sizeof(Derive) << endl;
    	cout << typeid(p).name() << endl;
    	cout << typeid(*p).name() << endl;
    	return 0;
    }

    其输出结果为:

    再看下面代码:

    class Base {
    public:
    	 Base(int data) :_data(data) {}
    	 virtual void show() {cout << "Base show()" << endl;}
    private:
    	int _data;
    };
    
    class Derive :public Base {
    public:
    	Derive(int data) :Base(data), _datab(data) {};
    	void show() { cout << "Derive show()" << endl; }
    private:
    	int _datab;
    };
    int main() {
    	Derive d(10);
    	Base* p = &d;
    	p->show();
        /*
        这里由于show是虚函数,所以是静态绑定
        mov    ecx,dword ptr [p]  
        mov    eax,dword ptr [edx]  
        call   eax 
        这里call的是eax,是一个寄存器,具体的值需要在运行时期才能确定
        */
    	cout << sizeof(Base) << endl;
    	cout << sizeof(Derive) << endl;
    	cout << typeid(p).name() << endl;
    	cout << typeid(*p).name() << endl;
    	return 0;
    }
    

    其输出结果为:

     

    上面两段程序唯一的不同之处就在于将基类的成员方法show加上了virtual关键字使其变成了虚函数。但其结果大为不同,到底是为什么呢?我们来一个一个分析,针对第一个程序,基类的大小是4,而基类只有一个int型的变量,这是符合逻辑的,派生类从基类继承来了一个变量加上自己的私有变量其大小为8,这也是符合逻辑的。p是基类的指针,所以其类型为class Base*,对其解引用得到的类型为class Base,自己调用自己的成员方法那肯定是没问题的。那对于第二个程序呢?为啥就不一样了,注意这里有虚函数,首先通过VS所提供的工具查看一下类的内存布局是什么。

    其命令为:cl 文件名/d1reportSingleClassLayout类名称

    基类的内存布局:

    派生类的内存布局:

    看到这里就很清晰了基类的大小之所以是8不是4,是因为多了一个vfptr,那么派生类从基类多继承了一个vfptr,加上变量的大小,自然而然就是12了。那下面的vftable呢?让我用一张图来说明一下vfptr和vftable的关系:

    这里画出的派生类的对应关系,实际上基类一样如此,只是虚函数表中虚函数地址和RTTI信息不同罢了。虚函数指针(vfptr)指向的是虚函数表中函数的首地址,RTTI(run_time type information)指的是运行时的类型信息,0表示的是虚函数指针的一个偏移量。而此时调用成员方法之所以是派生类的成员方法是因为,基类的成员方法被定义是虚函数,那么此时该函数的绑定就发生就运行时期,即动态绑定。基类的指针指向派生类对象,那么派生类的虚函数指针就要访问自己的虚函数表看有没有相同的成员函数,如果有就访问自己的成员函数,如果没有那么依然还是访问的基类的成员方法。至于此时*p的类型是什么,由于派生类指针访问的使自己的虚函数表,那么RTTI信息就是自己的类类型,即class Derive。

    做出总结:

    1、一个类里面定义了虚函数,那么在编译阶段,编译器给这个类产生了一个唯一的虚函数表,虚函数表里主要存储了RTTI信息和虚函数的地址,每一张虚函数表都存在内存的.rodata区。

    2、一个类里面定义了虚函数,那么这个类定义的对象,在运行时,内存的起始部分,多存储了一个虚函数指针,指向对应类型的虚函数表,一个类定义n个对象,虚函表只有一个,n个对象均指向这一张虚函数表。

    3、一个类里面虚函数的个数,不改变对象的大小,改变的是虚函数表的大小;

    4、派生类里面有和基类函数名相同、返回值、参数列表均相同的函数,如果基类中该函数是虚函数,那么派生类中该函数自动转化为虚函数。

    展开全文
  • C++ 虚函数实现机制

    千次阅读 2019-02-07 19:40:18
    转 C++面试题之虚函数(表)实现机制 前言大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时决定该调用基类的...

    C++面试题之虚函数(表)实现机制

    前言

    大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时决定该调用基类的函数还是继承类的函数. 虚函数是实现多态(动态绑定)/接口函数的基础. 可以说: 没有虚函数, C++将变得一无是处!

    既然是C++的精髓, 那么我们有必要了解一下她的实现方式吗? 有必要! 既然C++是从C语言的基础上发展而来的, 那么我们可以尝试用C语言来模拟实现吗? 有可能! 接下来, 就是我一步一步地来解析C++的虚函数的实现方式, 以及用C语言对其进行的模拟.

    C++对象的内存布局

    要想知道C++对象的内存布局, 可以有多种方式, 比如:

    1. 输出成员变量的偏移, 通过offsetof宏来得到
    2. 通过调试器查看, 比如常用的VS
    1. 只有数据成员的对象

      类实现如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. };

      对象大小及偏移:

      sizeof(Base1)8
      offsetof(Base1, base1_1)0
      offsetof(Base1, base1_2)4

      可知对象布局:

      可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
      类对象的大小就是所有成员变量大小之和.

    2. 没有虚函数的对象

      类实现如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. void foo(){}
      7. };

      结果如下:

      sizeof(Base1)8
      offsetof(Base1, base1_1)0
      offsetof(Base1, base1_2)4

      和前面的结果是一样的? 不需要有什么疑问对吧?
      因为如果一个函数不是虚函数,那么他就不可能会发生动态绑定,也就不会对对象的布局造成任何影响.
      当调用一个非虚函数时, 那么调用的一定就是当前指针类型拥有的那个成员函数. 这种调用机制在编译时期就确定下来了.

    3. 拥有仅一个虚函数的类对象

      类实现如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. };

      结果如下:

      sizeof(Base1)12
      offsetof(Base1, base1_1)4
      offsetof(Base1, base1_2)8

      咦? 多了4个字节? 且 base1_1 和 base1_2 的偏移都各自向后多了4个字节!
      说明类对象的最前面被多加了4个字节的"东东", what's it?
      现在, 我们通过VS2013来瞧瞧类Base1的变量b1的内存布局情况:
      (由于我没有写构造函数, 所以变量的数据没有根据, 但虚函数是编译器为我们构造的, 数据正确!)
      (Debug模式下, 未初始化的变量值为0xCCCCCCCC, 即:-858983460)

      看到没? base1_1前面多了一个变量 __vfptr(常说的虚函数表vtable指针), 其类型为void**, 这说明它是一个void*指针(注意:不是数组).

      再看看[0]元素, 其类型为void*, 其值为 ConsoleApplication2.exe!Base1::base1_fun1(void), 这是什么意思呢? 如果对WinDbg比较熟悉, 那么应该知道这是一种惯用表示手法, 她就是指 Base1::base1_fun1() 函数的地址.

      可得, __vfptr的定义伪代码大概如下:

      1. void* __fun[1] = { &Base1::base1_fun1 };
      2. const void** __vfptr = &__fun[0];

      值得注意的是:

      1. 上面只是一种伪代码方式, 语法不一定能通过
      2. 该类的对象大小为12个字节, 大小及偏移信息如下:
        sizeof(Base1)12
        offsetof(__vfptr)0
        offsetof(base1_1)4
        offsetof(base1_2)8
      3. 大家有没有留意这个__vfptr? 为什么它被定义成一个指向指针数组的指针, 而不是直接定义成一个指针数组呢?

        我为什么要提这样一个问题? 因为如果仅是一个指针的情况, 您就无法轻易地修改那个数组里面的内容, 因为她并不属于类对象的一部分.
        属于类对象的, 仅是一个指向虚函数表的一个指针__vfptr而已, 下一节我们将继续讨论这个问题.

      4. 注意到__vfptr前面的const修饰. 她修饰的是那个虚函数表, 而不是__vfptr.

      现在的对象布局如下:

      虚函数指针__vfptr位于所有的成员变量之前定义.

      注意到: 我并未在此说明__vfptr的具体指向, 只是说明了现在类对象的布局情况.
      接下来看一个稍微复杂一点的情况, 我将清楚地描述虚函数表的构成.

    4. 拥有多个虚函数的类对象

      和前面一个例子差不多, 只是再加了一个虚函数. 定义如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };

      大小以及偏移信息如下:

      有情况!? 多了一个虚函数, 类对象大小却依然是12个字节!

      再来看看VS形象的表现:

      呀, __vfptr所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.

      现在, 虚函数指针以及虚函数表的伪定义大概如下:

      1. void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun2 };
      2. const void** __vfptr = &__fun[0];

      通过上面两张图表, 我们可以得到如下结论:

      1. 更加肯定前面我们所描述的: __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
      2. 增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况

      前面已经提到过: __vfptr只是一个指针, 她指向一个数组, 并且: 这个数组没有包含到类定义内部, 那么她们之间是怎样一个关系呢?
      不妨, 我们再定义一个类的变量b2, 现在再来看看__vfptr的指向:

      通过Watch 1窗口我们看到:

      1. b1和b2是类的两个变量, 理所当然, 她们的地址是不同的(见 &b1 和 &b2)
      2. 虽然b1和b2是类的两个变量, 但是: 她们的__vfptr的指向却是同一个虚函数表

      由此我们可以总结出:

      同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

      是时候该展示一下类对象的内存布局情况了:

      不出意外, 很清晰明了地展示出来了吧? :-) hoho~~

      那么问题就来了! 这个虚函数表保存在哪里呢? 其实, 我们无需过分追究她位于哪里, 重点是:

      1. 她是编译器在编译时期为我们创建好的, 只存在一份
      2. 定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表
    5. 单继承且本身不存在虚函数的继承类的内存布局

      前面研究了那么多啦, 终于该到研究继承类了! 先研究单继承!

      依然, 简单地定义一个继承类, 如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };
      9. class Derive1 : public Base1
      10. {
      11. public:
      12. int derive1_1;
      13. int derive1_2;
      14. };

      我们再来看看现在的内存布局(定义为Derive1 d1):

      没错! 基类在上边, 继承类的成员在下边依次定义! 展开来看看:

      经展开后来看, 前面部分完全就是Base1的东西: 虚函数表指针+成员变量定义.
      并且, Base1的虚函数表的[0][1]两项还是其本身就拥有的函数: base1_fun1() 和 base1_fun2().

      现在类的布局情况应该是下面这样:

    6. 本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局

      标题`本身不存在虚函数`的说法有些不严谨, 我的意思是说: 除经过继承而得来的基类虚函数以外, 自身没有再定义其它的虚函数.

      Ok, 既然存在基类虚函数覆盖, 那么来看看接下来的代码会产生何种影响:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };
      9. class Derive1 : public Base1
      10. {
      11. public:
      12. int derive1_1;
      13. int derive1_2;
      14. // 覆盖基类函数
      15. virtual void base1_fun1() {}
      16. };

      可以看到, Derive1类 重写了Base1类的base1_fun1()函数, 也就是常说的虚函数覆盖. 现在是怎样布局的呢?

      特别注意我高亮的那一行: 原本是Base1::base1_fun1(), 但由于继承类重写了基类Base1的此方法, 所以现在变成了Derive1::base1_fun1()!

      那么, 无论是通过Derive1的指针还是Base1的指针来调用此方法, 调用的都将是被继承类重写后的那个方法(函数), 多态发生鸟!!!

      那么新的布局图:

    7. 定义了基类没有的虚函数的单继承的类对象布局

      说明一下: 由于前面一种情况只会造成覆盖基类虚函数表的指针, 所以接下来我不再同时讨论虚函数覆盖的情况.

      继续贴代码:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };
      9. class Derive1 : public Base1
      10. {
      11. public:
      12. int derive1_1;
      13. int derive1_2;
      14. virtual void derive1_fun1() {}
      15. };

      和第5类不同的是多了一个自身定义的虚函数. 和第6类不同的是没有基类虚函数的覆盖.

      咦, 有没有发现问题? 表面上看来几乎和第5种情况完全一样? 为嘛呢?
      现在继承类明明定义了自身的虚函数, 但不见了??
      那么, 来看看类对象的大小, 以及成员偏移情况吧:

      居然没有变化!!! 前面12个字节是Base1的, 有没有觉得很奇怪?

      好吧, 既然表面上没办法了, 我们就只能从汇编入手了, 来看看调用derive1_fun1()时的代码:

      1. Derive1 d1;
      2. Derive1* pd1 = &d1;
      3. pd1->derive1_fun1();

      要注意: 我为什么使用指针的方式调用? 说明一下: 因为如果不使用指针调用, 虚函数调用是不会发生动态绑定的哦! 你若直接d1.derive1_fun1(); , 是不可能会发生动态绑定的, 但如果使用指针: pd1->derive1_fun1(); , 那么 pd1就无从知道她所指向的对象到底是Derive1 还是继承于Derive1的对象, 虽然这里我们并没有对象继承于Derive1, 但是她不得不这样做, 毕竟继承类不管你如何继承, 都不会影响到基类, 对吧?

      ; pd1->derive1_fun1();
      00825466  mov         eax,dword ptr [pd1]  
      00825469  mov         edx,dword ptr [eax]  
      0082546B  mov         esi,esp  
      0082546D  mov         ecx,dword ptr [pd1]  
      00825470  mov         eax,dword ptr [edx+8]  
      00825473  call        eax

      汇编代码解释:

      第2行: 由于pd1是指向d1的指针, 所以执行此句后 eax 就是d1的地址
      第3行: 又因为Base1::__vfptr是Base1的第1个成员, 同时也是Derive1的第1个成员, 那么: &__vfptr == &d1, clear? 所以当执行完 mov edx, dword ptr[eax] 后, edx就得到了__vfptr的值, 也就是虚函数表的地址.
      第5行: 由于是__thiscall调用, 所以把this保存到ecx中.
      第6行: 一定要注意到那个 edx+8, 由于edx是虚函数表的地址, 那么 edx+8将是虚函数表的第3个元素, 也就是__vftable[2]!!!
      第7行: 调用虚函数.

      结果:

      1. 现在我们应该知道内幕了! 继承类Derive1的虚函数表被加在基类的后面! 事实的确就是这样!
      2. 由于Base1只知道自己的两个虚函数索引[0][1], 所以就算在后面加上了[2], Base1根本不知情, 不会对她造成任何影响.
      3. 如果基类没有虚函数呢? 这个问题我们留到第9小节再来讨论!

      最新的类对象布局表示:

    8. 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局

      真快, 该看看多继承了, 多继承很常见, 特别是接口类中!

      依然写点小类玩玩:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };
      9. class Base2
      10. {
      11. public:
      12. int base2_1;
      13. int base2_2;
      14. virtual void base2_fun1() {}
      15. virtual void base2_fun2() {}
      16. };
      17. // 多继承
      18. class Derive1 : public Base1, public Base2
      19. {
      20. public:
      21. int derive1_1;
      22. int derive1_2;
      23. // 基类虚函数覆盖
      24. virtual void base1_fun1() {}
      25. virtual void base2_fun2() {}
      26. // 自身定义的虚函数
      27. virtual void derive1_fun1() {}
      28. virtual void derive1_fun2() {}
      29. };

      代码变得越来越长啦! 为了代码结构清晰, 我尽量简化定义.

      初步了解一下对象大小及偏移信息:

      貌似, 若有所思? 不管, 来看看VS再想:

      哇, 不摆了! 一丝不挂啊! :-)

      结论:

      1. 按照基类的声明顺序, 基类的成员依次分布在继承中.
      2. 注意被我高亮的那两行, 已经发生了虚函数覆盖!
      3. 我们自己定义的虚函数呢? 怎么还是看不见?!

      好吧, 继承反汇编, 这次的调用代码如下:

      1. Derive1 d1;
      2. Derive1* pd1 = &d1;
      3. pd1->derive1_fun2();

      反汇编代码如下:

      ; pd1->derive1_fun2();
      00995306  mov         eax,dword ptr [pd1]  
      00995309  mov         edx,dword ptr [eax]  
      0099530B  mov         esi,esp  
      0099530D  mov         ecx,dword ptr [pd1]  
      00995310  mov         eax,dword ptr [edx+0Ch]  
      00995313  call        eax

      解释下, 其实差不多:

      第2行: 取d1的地址
      第3行: 取Base1::__vfptr的值!!
      第6行: 0x0C, 也就是第4个元素(下标为[3])

      结论:

      Derive1的虚函数表依然是保存到第1个拥有虚函数表的那个基类的后面的.

      看看现在的类对象布局图:

      如果第1个基类没有虚函数表呢? 进入第9节!

    9. 如果第1个直接基类没有虚函数(表)

      这次的代码应该比上一个要稍微简单一些, 因为把第1个类的虚函数给去掉鸟!

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. };
      7. class Base2
      8. {
      9. public:
      10. int base2_1;
      11. int base2_2;
      12. virtual void base2_fun1() {}
      13. virtual void base2_fun2() {}
      14. };
      15. // 多继承
      16. class Derive1 : public Base1, public Base2
      17. {
      18. public:
      19. int derive1_1;
      20. int derive1_2;
      21. // 自身定义的虚函数
      22. virtual void derive1_fun1() {}
      23. virtual void derive1_fun2() {}
      24. };

      来看看VS的布局:

      这次相对前面一次的图来说还要简单啦! Base1已经没有虚函数表了! (真实情况并非完全这样, 请继续往下看!)

      现在的大小及偏移情况: 注意: sizeof(Base1) == 8;

      重点是看虚函数的位置, 进入函数调用(和前一次是一样的):

      1. Derive1 d1;
      2. Derive1* pd1 = &d1;
      3. pd1->derive1_fun2();

      反汇编调用代码:

      ; pd1->derive1_fun2();
      012E4BA6  mov         eax,dword ptr [pd1]  
      012E4BA9  mov         edx,dword ptr [eax]  
      012E4BAB  mov         esi,esp  
      012E4BAD  mov         ecx,dword ptr [pd1]  
      012E4BB0  mov         eax,dword ptr [edx+0Ch]  
      012E4BB3  call        eax

      这段汇编代码和前面一个完全一样!, 那么问题就来了! Base1 已经没有虚函数表了, 为什么还是把b1的第1个元素当作__vfptr呢?
      不难猜测: 当前的布局已经发生了变化, 有虚函数表的基类放在对象内存前面!? , 不过事实是否属实? 需要仔细斟酌.

      我们可以通过对基类成员变量求偏移来观察:

      可以看到:

      &d1==0x~d4
      &d1.Base1::__vfptr==0x~d4
      &d1.base2_1==0x~d8
      &d1.base2_2==0x~dc
      &d1.base1_1==0x~e0
      &d1.base1_2==0x~e4

      所以不难验证: 我们前面的推断是正确的, 谁有虚函数表, 谁就放在前面!

      现在类的布局情况:

      那么, 如果两个基类都没有虚函数表呢?

    10. What if 两个基类都没有虚函数表

      代码如下:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. };
      7. class Base2
      8. {
      9. public:
      10. int base2_1;
      11. int base2_2;
      12. };
      13. // 多继承
      14. class Derive1 : public Base1, public Base2
      15. {
      16. public:
      17. int derive1_1;
      18. int derive1_2;
      19. // 自身定义的虚函数
      20. virtual void derive1_fun1() {}
      21. virtual void derive1_fun2() {}
      22. };

      前面吃了个亏, 现在先来看看VS的基本布局:

      可以看到, 现在__vfptr已经独立出来了, 不再属于Base1和Base2!

      看看求偏移情况:

      Ok, 问题解决! 注意高亮的那两行, &d1==&d1.__vfptr, 说明虚函数始终在最前面!

      不用再废话, 相信大家对这种情况已经有底了.

      对象布局:

    11. 如果有三个基类: 虚函数表分别是有, 没有, 有!

      这种情况其实已经无需再讨论了, 作为一个完结篇....

      上代码:

      1. class Base1
      2. {
      3. public:
      4. int base1_1;
      5. int base1_2;
      6. virtual void base1_fun1() {}
      7. virtual void base1_fun2() {}
      8. };
      9. class Base2
      10. {
      11. public:
      12. int base2_1;
      13. int base2_2;
      14. };
      15. class Base3
      16. {
      17. public:
      18. int base3_1;
      19. int base3_2;
      20. virtual void base3_fun1() {}
      21. virtual void base3_fun2() {}
      22. };
      23. // 多继承
      24. class Derive1 : public Base1, public Base2, public Base3
      25. {
      26. public:
      27. int derive1_1;
      28. int derive1_2;
      29. // 自身定义的虚函数
      30. virtual void derive1_fun1() {}
      31. virtual void derive1_fun2() {}
      32. };

      只需要看看偏移就行了:

      只需知道: 谁有虚函数表, 谁就往前靠!

    C++中父子对象指针间的转换与函数调用

    讲了那么多布局方面的东东, 终于到了尾声, 好累呀!!!

    通过前面的讲解内容, 大家至少应该明白了各类情况下类对象的内存布局了. 如果还不会.....呃..... !@#$%^&*

    进入正题~

    由于继承完全拥有父类的所有, 包括数据成员与虚函数表, 所以:把一个继承类强制转换为一个基类是完全可行的.

    如果有一个Derive1的指针, 那么:

    • 得到Base1的指针: Base1* pb1 = pd1;
    • 得到Base2的指针: Base2* pb2 = pd1;
    • 得到Base3的指针: Base3* pb3 = pd1;

    非常值得注意的是:

    这是在基类与继承类之间的转换, 这种转换会自动计算偏移! 按照前面的布局方式!
    也就是说: 在这里极有可能: pb1 != pb2 != pb3 ~~, 不要以为她们都等于 pd1!

    至于函数调用, 我想, 不用说大家应该知道了:

    1. 如果不是虚函数, 直接调用指针对应的基本类的那个函数
    2. 如果是虚函数, 则查找虚函数表, 并进行后续的调用. 虚函数表在定义一个时, 编译器就为我们创建好了的. 所有的, 同一个类, 共用同一份虚函数表.

    用C语言完全模拟C++虚函数表的实现与运作方式

    如果对前面两大节的描述仔细了解了的话, 想用C语言来模拟C++的虚函数以及多态, 想必是轻而易举的事情鸟!

    前提

    但是, 话得说在前面, C++的编译器在生成类及对象的时候, 帮助我们完成了很多事件, 比如生成虚函数表!
    但是, C语言编译器却没有, 因此, 很多事件我们必须手动来完成, 包括但不限于:

    1. 手动构造父子关系
    2. 手动创建虚函数表
    3. 手动设置__vfptr并指向虚函数表
    4. 手动填充虚函数表
    5. 若有虚函数覆盖, 还需手动修改函数指针
    6. 若要取得基类指针, 还需手动强制转换
    7. ......

    总之, 要想用C语言来实现, 要写的代码绝对有点复杂.

    C++原版调用

    接下来, 我们都将以最后那个, 最繁杂的那个3个基类的实例来讲解, 但作了一些简化与改动:

    1. 用构造函数初始化成员变量
    2. 减少成员变量的个数
    3. 减少虚函数的个数
    4. 调用函数时产生相关输出
    5. Derive1增加一个基类虚函数覆盖

    以下是对类的改动, 很少:

    1. class Base1
    2. {
    3. public:
    4. Base1() : base1_1(11) {}
    5. int base1_1;
    6. virtual void base1_fun1() {
    7. std::cout << "Base1::base1_fun1()" << std::endl;
    8. }
    9. };
    10. class Base2
    11. {
    12. public:
    13. Base2() : base2_1(21) {}
    14. int base2_1;
    15. };
    16. class Base3
    17. {
    18. public:
    19. Base3() : base3_1(31) {}
    20. int base3_1;
    21. virtual void base3_fun1() {
    22. std::cout << "Base3::base3_fun1()" << std::endl;
    23. }
    24. };
    25. class Derive1 : public Base1, public Base2, public Base3
    26. {
    27. public:
    28. Derive1() : derive1_1(11) {}
    29. int derive1_1;
    30. virtual void base3_fun1() {
    31. std::cout << "Derive1::base3_fun1()" << std::endl;
    32. }
    33. virtual void derive1_fun1() {
    34. std::cout << "Derive1::derive1_fun1()" << std::endl;
    35. }
    36. };

    为了看到多态的效果, 我们还需要定义一个函数来看效果:

    1. void foo(Base1* pb1, Base2* pb2, Base3* pb3, Derive1* pd1)
    2. {
    3. std::cout << "Base1::\n"
    4. << " pb1->base1_1 = " << pb1->base1_1 << "\n"
    5. << " pb1->base1_fun1(): ";
    6. pb1->base1_fun1();
    7. std::cout << "Base2::\n"
    8. << " pb2->base2_1 = " << pb2->base2_1
    9. << std::endl;
    10. std::cout << "Base3::\n"
    11. << " pb3->base3_1 = " << pb3->base3_1 << "\n"
    12. <<" pb3->base3_fun1(): ";
    13. pb3->base3_fun1();
    14. std::cout << "Derive1::\n"
    15. << " pd1->derive1_1 = " << pd1->derive1_1<< "\n"
    16. <<" pd1->derive1_fun1(): ";
    17. pd1->derive1_fun1();
    18. std::cout<< " pd1->base3_fun1(): ";
    19. pd1->base3_fun1();
    20. std::cout << std::endl;
    21. }

    调用方式如下:

    1. Derive1 d1;
    2. foo(&d1, &d1, &d1, &d1);

    输出结果:

    可以看到输出结果全部正确(当然了! :-), 哈哈~
    同时注意到 pb3->base3_fun1() 的多态效果哦!

    用C语言来模拟

    必须要把前面的理解了, 才能看懂下面的代码!

    为了有别于已经完成的C++的类, 我们分别在类前面加一个大写的C以示区分(平常大家都是习惯在C++写的类前面加C, 今天恰好反过来, 哈哈).

    C语言无法实现的部分

    C/C++是两个语言, 有些语言特性是C++专有的, 我们无法实现! 不过, 这里我是指调用约定, 我们应该把她排除在外.

    对于类的成员函数, C++默认使用__thiscall, 也即this指针通过ecx传递, 这在C语言无法实现, 所以我们必须手动声明调用约定为:

    1. __stdcall, 就像微软的组件对象模型那样
    2. __cdecl, 本身就C语言的调用约定, 当然能使用了.

    上面那种调用约定, 使用哪一种无关紧要, 反正不能使用__thiscall就行了.

    因为使用了非__thiscall调用约定, 我们就必须手动传入this指针, 通过成员函数的第1个参数!

    从最简单的开始: 实现 Base2

    由于没有虚函数, 仅有成员变量, 这个当然是最好模拟的咯!

    1. struct CBase2
    2. {
    3. int base2_1;
    4. };
    有了虚函数表的Base1, 但没被覆盖

    下面是Base1的定义, 要复杂一点了, 多一个__vfptr:

    1. struct CBase1
    2. {
    3. void** __vfptr;
    4. int base1_1;
    5. };

    因为有虚函数表, 所以还得单独为虚函数表创建一个结构体的哦!
    但是, 为了更能清楚起见, 我并未定义前面所说的指针数组, 而是用一个包含一个或多个函数指针的结构体来表示!
    因为数组能保存的是同一类的函数指针, 不太很友好! 
    但他们的效果是完全一样的, 希望读者能够理解明白!

    1. struct CBase1_VFTable
    2. {
    3. void(__stdcall* base1_fun1)(CBase1* that);
    4. };

    注意: base1_fun1 在这里是一个指针变量!
    注意: base1_fun1 有一个CBase1的指针, 因为我们不再使用__thiscall, 我们必须手动传入! Got it?

    Base1的成员函数base1_fun1()我们也需要自己定义, 而且是定义成全局的:

    1. void __stdcall base1_fun1(CBase1* that)
    2. {
    3. std::cout << "base1_fun1()" << std::endl;
    4. }
    有虚函数覆盖的Base3

    虚函数覆盖在这里并不能体现出来, 要在构造对象初始化的时候才会体现, 所以: base3其实和Base1是一样的.

    1. struct CBase3
    2. {
    3. void** __vfptr;
    4. int base3_1;
    5. };
    6. struct CBase3_VFTable
    7. {
    8. void(__stdcall* base3_fun1)(CBase3* that);
    9. };

    Base3的成员函数:

    1. void __stdcall base3_fun1(CBase3* that)
    2. {
    3. std::cout << "base3_fun1()" << std::endl;
    4. }
    定义继承类CDerive1

    相对前面几个类来说, 这个类要显得稍微复杂一些了, 因为包含了前面几个类的内容:

    1. struct CDerive1
    2. {
    3. CBase1 base1;
    4. CBase3 base3;
    5. CBase2 base2;
    6. int derive1_1;
    7. };

    特别注意: CBase123的顺序不能错!

    另外: 由于Derive1本身还有虚函数表, 而且所以项是加到第一个虚函数表(CBase1)的后面的, 所以此时的CBase1::__vfptr不应该单单指向CBase1_VFTable, 而应该指向下面这个包含Derive1类虚函数表的结构体才行:

    1. struct CBase1_CDerive1_VFTable
    2. {
    3. void (__stdcall* base1_fun1)(CBase1* that);
    4. void(__stdcall* derive1_fun1)(CDerive1* that);
    5. };

    因为CDerive1覆盖了CBase3的base3_fun1()函数, 所以不能直接用Base3的那个表:

    1. struct CBase3_CDerive1_VFTable
    2. {
    3. void(__stdcall* base3_fun1)(CDerive1* that);
    4. };

    Derive1覆盖Base3::base3_fun1()的函数以及自身定义的derive1_fun1()函数:

    1. void __stdcall base3_derive1_fun1(CDerive1* that)
    2. {
    3. std::cout << "base3_derive1_fun1()" << std::endl;
    4. }
    5. void __stdcall derive1_fun1(CDerive1* that)
    6. {
    7. std::cout << "derive1_fun1()" << std::endl;
    8. }
    构造各类的全局虚函数表

    由于没有了编译器的帮忙, 在定义一个类对象时, 所有的初始化工作都只能由我们自己来完成了!

    首先构造全局的, 被同一个类共同使用的虚函数表!

    1. // CBase1 的虚函数表
    2. CBase1_VFTable __vftable_base1;
    3. __vftable_base1.base1_fun1 = base1_fun1;
    4. // CBase3 的虚函数表
    5. CBase3_VFTable __vftable_base3;
    6. __vftable_base3.base3_fun1 = base3_fun1;

    然后构造CDerive1和CBase1共同使用的虚函数表:

    1. // CDerive1 和 CBase1 共用的虚函数表
    2. CBase1_CDerive1_VFTable __vftable_base1_derive1;
    3. __vftable_base1_derive1.base1_fun1 = base1_fun1;
    4. __vftable_base1_derive1.derive1_fun1 = derive1_fun1;

    再构造CDerive1覆盖CBase3后的虚函数表: 注意: 数覆盖会替换原来的函数指针

    1. CBase3_CDerive1_VFTable __vftable_base3_derive1;
    2. __vftable_base3_derive1.base3_fun1 = base3_derive1_fun1;
    开始! 从CDerive1构造一个完整的Derive1类

    先初始化成员变量与__vfptr的指向: 注意不是指错了!

    1. CDerive1 d1;
    2. d1.derive1 = 1;
    3. d1.base1.base1_1 = 11;
    4. d1.base1.__vfptr = reinterpret_cast<void**>(&__vftable_base1_derive1);
    5. d1.base2.base2_1 = 21;
    6. d1.base3.base3_1 = 31;
    7. d1.base3.__vfptr = reinterpret_cast<void**>(&__vftable_base3_derive1);

    由于目前的CDerive1是我们手动构造的, 不存在真正语法上的继承关系, 如要得到各基类指针, 我们就不能直接来取, 必须手动根据偏移计算:

    1. char* p = reinterpret_cast<char*>(&d1);
    2. Base1* pb1 = reinterpret_cast<Base1*>(p + 0);
    3. Base2* pb2 = reinterpret_cast<Base2*>(p + sizeof(CBase1) + sizeof(CBase3));
    4. Base3* pb3 = reinterpret_cast<Base3*>(p + sizeof(CBase1));
    5. Derive1* pd1 = reinterpret_cast<Derive1*>(p);

    真正调用:

    foo(pb1, pb2, pb3, pd1);

    调用结果:

    结果相当正确!!!

    源代码

    我以为我把源代码搞丢了,结果过了一年多发现其实并没有。--- 2015-12-24(每个圣诞我都在写代码)

    有两个,忘了区别了:Source1.cppSource2.cpp.


    原文链接:https://blog.twofei.com/496/

    展开全文
  • 虚函数是C++中用于实现多态的机制。核心理念就是通过基类指针访问派生类中定义的函数。虚函数允许子类重新定义成员函数,继承时不重新定义虚函数也是可以的。而子类重新定义父类的做法称为覆盖(override),或者称为...
  • 1. Java虚函数虚函数的存在是为了多态。C++中普通成员函数加上virtual关键字就成为虚函数Java中其实没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑定是Java的默认行为。如果Java中不希望某个函数具有...
  • c++虚函数实现机制

    2016-08-09 18:36:06
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • 多态性与将实现多态的函数的访问限定符没有任何关系,private 函数仍然可以实现多态,它的指针仍然位于vtbl中,只不过该函数的多态一般只能在基类的内部由其他非虚函数调用该函数的时候反映出来,访问限定符仅仅限制...
  • 虚函数实现多态(动态绑定)/接口函数的基础. 可以说: 没有虚函数, C++将变得一无是处!既然是C++的精髓, 那么我们有必要了解一下她的实现方式吗? 有必要! 既然C++是从C语言的基础上发展而来...
  • c++为了兼容c保留了struct类型,但是c++中的struct和c有明显的区别,c++中的struct可以继承,可以有成员函数,但是在c中却不行,在c++中struc和class更相似(还是有一些区别的,这里不再叙述),c中struct的内存分布...
  • c++ 虚函数实现机制

    2012-02-19 10:15:23
    C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。...
  • c++虚函数实现机制及内存模型

    千次阅读 2017-03-30 16:35:18
    前言 大家都应该知道C++的精髓是... 虚函数实现多态(动态绑定)/接口函数的基础. 可以说: 没有虚函数, C++将变得一无是处! 既然是C++的精髓, 那么我们有必要了解一下她的实现方式吗? 有必要! 既然C++是从C语言

空空如也

空空如也

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

虚函数实现机制