精华内容
下载资源
问答
  • C++与C#关于虚函数调用的一点比较

    千次阅读 2006-05-24 11:20:00
    在C++FAQ上看到一个关于虚函数的重载说明,原贴内容如下:[23.3] 当基类构造函数调用虚函数时,为什么不调用派生类重写的该虚函数?当基类被构造时,对象还不是一个派生类的对象,所以如果 Base::Base()调用了虚函数 ...

    在C++FAQ上看到一个关于虚函数的重载说明,原贴内容如下:

    [23.3] 当基类构造函数调用虚函数时,为什么不调用派生类重写的该虚函数?

    当基类被构造时,对象还不是一个派生类的对象,所以如果 Base::Base()调用了虚函数 virt(),则 Base::virt() 将被调用,即使 Derived::virt()(译注:即派生类重写的虚函数)存在。

    同样,当基类被析构时,对象已经不再是一个派生类对象了,所以如果 Base::~Base()调用了virt(),则 Base::virt()得到控制权,而不是重写的 Derived::virt()

    当你可以想象到如果 Derived::virt() 涉及到派生类的某个成员对象将造成的灾难的时候,你很快就能看到这种方法的明智。详细来说,如果 Base::Base()调用了虚函数 virt(),这个规则使得 Base::virt()被调用。如果不按照这个规则,Derived::virt()将在派生对象的派生部分被构造之前被调用,此时属于派生对象的派生部分的某个成员对象还没有被构造,而 Derived::virt()却能够访问它。这将是灾难。

    意思是说在C++中,如果基类的构造函数中调用它定义的的虚函数,即使在派生类中对这个虚函数进行了重写,实际运行中调用的仍是基类的版本。具体原因可以参见上面的说明,大概意思是在基类的构造函数中,子类对象还未被构造出来,所以这个时候调用的仍是基类的。

    但是在C#中,同样的情况,基类中调用的却是派生类重写后的版本。我在本机是试过,运行结果是这样的,但是不是太明白为什么。

    展开全文
  • 虚函数被类的构造析构函数和成员函数调用虚函数的执行过程,需要的朋友可以参考下
  • 虚函数表和虚函数调用机制、同名非虚函数调用机制 http://baike.baidu.com/view/3750123.htm http://blog.csdn.net/haoel/article/details/1948051 C++ 中的虚函数的实现一般是通过虚函数表实现的,尽管 ...
    
    
    #include <iostream>
    using namespace std;
    
    class Base{
    public:
    	virtual void f(int a){ cout << "Base::f(int )" << endl; }
    	virtual void g(){ cout << "Base::g()" << endl; }
    	virtual void h(){ cout << "Base::h()" << endl; }
    	void m(){ cout << "Base::m() " << endl; }
    	void m(int a){ cout << "Base::m(int a) " << endl; }
    };
    
    class Derived:public Base{
    public:
    	void f(){ cout << "Derived::f()" << endl; }
    	void g(){ cout << "Derived::g()" << endl; }
    	void m(){ cout << "Derived::m() " << endl; }
    	void m(int a){ cout << "Derived::m(int a) " << endl; }
    	void n(){ cout << "Derived::n()" << endl; }
    };
    
    class dd:public Derived{
    public:
    	void f(){ cout << "dd::f()" << endl; }
    };
    
    int main(){
    	//针对虚函数覆盖的具体情况;虚函数调用机制和非虚函数重名函数调用机制实验
    	Base *p=new Derived;
    	p->f(1);
    	p->g();
    	p->h();
    	p->m(1);
    	p->m();
    	p->n();
    
    	typedef void (*Fun)(void);
    	/*//访问虚函数表
    	Base b;
    	Fun pFun=NULL;
    	cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;
    	cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;
    
    	pFun=(Fun)* ((int *)*(int *)(&b));	//dd::f()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
    	pFun();*/
    	
    	/*//因为Derived从Base继承,所以Derived中也有虚函数,因此Derived类也有自己的V-Table
    	//证明每个含有虚函数的类都会有虚函数表,且被这个类的所有对象共享,如果子类中有
    	//和父类虚函数相同,注意必须完全相同的则会覆盖(重写),而不是重载
    	Derived b;
    	Fun pFun=NULL;
    	cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;
    	cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;
    
    	pFun=(Fun)* ((int *)*(int *)(&b));	//Derived::f()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
    	pFun();*/
    
    	/*//这里和Derived类的原因相同,并且g在Derived已经把Base的覆盖了
    	dd b;
    	Fun pFun=NULL;
    	cout << "the address of the V-Table is:     "<< (int *)(&b)<< endl;
    	cout << "the first function in the V-Table: "<< (int *)*(int *)(&b) << endl;
    
    	pFun=(Fun)* ((int *)*(int *)(&b));	//dd::f()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
    	pFun();
    	pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
    	pFun();*/
    
    	return 0;
    }


    C++虚函数表和虚函数调用机制、同名非虚函数调用机制

    http://baike.baidu.com/view/3750123.htm

    http://blog.csdn.net/haoel/article/details/1948051

    C++中的虚函数的实现一般是通过虚函数表实现的,尽管C++规范并没有规定具体使用哪种方法来实现虚函数,但大部分的编译器厂商都选择此方法来实现。

    类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。

    注意:编译器会为每个虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员函数占据函数表中的一个单元,如果类中有N个虚函数,则虚函数表将有N*sizeofx86)字节的大小。

    子类中如果有和父类虚函数冲了函数体定义不同其他完全相同的函数则会覆盖父类的虚函数,而不是重载。

    另外如果是虚继承如:class B:public virtual A,则在B中还要保存一个vbptr_B_A虚类指针;每个含有虚函数的类都至少含有一个vPtr,如果子类中只是含有父类的虚函数(可以有virtual标识也可以没有),则和父类共享一个vTable即可,即持有一个vPtr;但是如果子类含有父类中没有的虚函数,则子类需要重新创建一个vTable,保存这个vPtr;同时还要持有父类的vPtr。如果并没有含有另外一个虚函数,但是这个时候B中有了构造函数或者虚函数或者都有,则这个时候也要另外持有一份vtable,即要有自己的一份vptr。这个在计算类占有空间大小的时候很有用。参考《程序员面试宝典》P131

     

    如果父类虚函数没有被覆盖,则这个虚函数是完全没有意义的。

    如有以下两个类的继承情况:

    class Base{

    public:

        virtual void f(){ cout<<"Base::f()" <<endl; }

        virtual void g(){ cout<<"Base::g()" <<endl; }

        virtual void h(){ cout<<"Base::h()" <<endl; }

        void m(){ cout << "Base::m()" << endl; }

    };

     

    class Derived:public Base{

    public:

        void f(){ cout << "Derived::f()"<< endl; }

        void g(){ cout << "Derived::g()"<< endl; }

        void m(){ cout << "Derived::m()" << endl; }

        void n(){ cout << "Derived::n()"<< endl; }

    };

    第一种情况:我们使用虚函数一般是

    Base*p=newDerived;

    p->f(); // Derived::f()

    p->g(); // Derived::g()

    p->h(); // Base::h()

    p->m(); // Base::m()

    p->n();// 编译报错

    这是因为:f和g都是虚函数,并且在Derived的对象中都已经将这个函数覆盖了(注意要想覆盖必须是除了函数体定义不同其他的完全相同,否则就不能覆盖而视为不同的函数了);h是虚函数但是没有被覆盖;而n只是Derived类对象的成员函数而不是Base类的成员函数。

    第一行代码的意义就是:声明一个对象,并把这个对象的指针强制转换成一个Base类型的,我们知道在创建Derived类对象的时候,其中必然包含了Base基类中的所有内容,在进行指针强制转换后,就只会在继承的Base区域内寻找需要执行的对象或者需要操作的数据。根据上面所描述的,f和g被覆盖了,所以调用的时候就会调用Derived的,h没有被覆盖所以调用Base的h,而n根本就不在这个区域内所以会编译报错:找不到这个成员。

     

    第二种情况:

    Derived*p=newDerived;

    p->f(); // Derived::f()

    p->g(); // Derived::g()

    p->h(); // Base::h()

    p->m(); // Derived::m()

    p->n();// Derived::n()

    这个时候并没有把Derived类对象的指针做强制转换,这个时候就会在Derived的区域内寻找数据和函数进行执行;这个时候实际上在Derived对象的体内,因为f和g被覆盖了,所以只含有一个f和g而且是Derived的f和g;h没有被覆盖但是h是Base的h;总之对于虚函数的调用是通过查虚函数表来实现的。m在Base和Derived中都有定义但是不是虚函数,所以没有覆盖在这里也不是重载,而是属于两个不同类的函数,在具体调用的时候首先是根据函数名称即标识符来查看调用哪个类中的m函数,这个查找过程是是又一定的名字查找规则的,尤其是在多重继承中体现更明显,在C++编程思想第二卷P374中有具体查找规则描述;确定类了之后再选择函数具体的重载形式,比如如果在Base中m还有一个参数为int的重载形式,而在Derived中没有,但是在调用的时候使用p->m(1)调用,在这个例子的情形下就会报错,因为根据名称查找规则确定是使用Derived类中的m函数,但是这个类中没有参数为int的重载形式,如果这在Derived中重载了这种形式则肯定没有问题;同样在Derived类中如果不定义m则也没有问题,因为这个时候在名称查找的时候就会确定的是使用Base类中的m函数,而在Base类中恰好有m的int参数的重载形式,所以就不会报错,只是这个时候就会调用Base类中的m函数了。

    综上两种情况描述了目前我所见到的类继承中的两大类情形:一种就是虚函数的调用机制;另一种就是同名函数的查找和调用机制。

     

    展开全文
  • 原本以为自己对虚函数掌握的还可以,结果前几天面试的时候被问了一个构造和析构函数里调用虚函数的问题,结果就给答错了。

    原本以为自己对虚函数掌握的还可以,结果前几天面试的时候被问了一个基类指针操作派生类对象的时候,构造和析构函数里调用虚函数的时候,调用的是基类的还是派生类的。结果就给答错了,我当时觉得一个类里面的虚函数表覆盖的函数已经只能指向派生类的。因为当时我觉得,面试官提醒在构造和析构函数调用的时候,派生类还不存在或者已经被析构了,还怎么能调用呢?
    于是我就打算再从底层理解一下虚函数问题

    1.构造/析构函数函数中调用虚函数

    一如上面面试官所说,在构造和析构函数调用的时候,派生类还不存在或者已经被析构了,是不能调用派生类的虚函数的。所以这道题的标准答案是:
    在构造/析构函数函数中调用虚函数,是不会出现多态的现象,而是调用其自身【基类】的虚函数
    但是我当时疑惑的事情在于,因为虚函数的调用需要用到虚函数表,而在派生类中,其虚函数表中派生类相应的虚函数地址已经覆盖了基类的对应的虚函数地址,那么如何才能够找到已经被覆盖的虚函数地址的呢?
    我查了一下各种资料,发现【1】在构造函数/析构函数中调用虚函数的例子能够很好的说明这个问题。
    代码可以去原帖中看最后一段代码
    因为类实例地址里面,虚函数表的指针时存在开头的位置,所以int* vt = (int*)*((int*)this);这一步是取得其虚函数表的指针。
    在生成派生类的对象时,是先调用了基类的构造函数,再调用派生类的构造函数,析构函数的调用跟构造函数的调用顺序是相反的,它从最派生类的析构函数开始的。也就是说当基类的析构函数执行时,派生类的析构函数已经执行。
    所以在基类和派生类的构造函数和析构函数都调用一个打印当前this指针地址【表示这个类实例的地址】,答应指向虚函数表的指针的指向位置。

    #include <iostream>
    
    class Base
    {
    public:
        Base() { PrintBase(); }
        virtual ~Base() { PrintBase(); }
        void PrintBase()
        {
            std::cout << "Address of Base: " << this << std::endl;
            // 虚表的地址存在对象内存空间里的头4个字节
            int* vt = (int*)*((int*)this);
            std::cout << "Address of Base Vtable: " << vt << std::endl;
            // 通过vt来调用Foo函数,以证明vt指向的确实是虚函数表
            std::cout << "Call Foo by vt -> ";
            void(*pFoo)(Base* const) = (void(*)(Base* const))vt[1];   ///< 注意这里索引变成 1 了,因为析构函数定义在Foo之前
            (*pFoo)(this);
            std::cout << std::endl;
        }
        virtual void  Foo() { std::cout << "Base" << std::endl; }
    };
    
    class Derive : public Base
    {
    public:
        Derive() : Base() { PrintDerive(); }
        virtual ~Derive() { PrintDerive(); }
    
        void PrintDerive()
        {
            std::cout << "Address of Derive: " << this << std::endl;
    
            // 虚表的地址存在对象内存空间里的头4个字节
            int* vt = (int*)*((int*)this);
            std::cout << "Address of Derive Vtable: " << vt << std::endl;
    
            // 通过vt来调用Foo函数,以证明vt指向的确实是虚函数表
            std::cout << "Call Foo by vt -> ";
            void(*pFoo)(Base* const) = (void(*)(Base* const))vt[1];   ///< 注意这里索引变成 1 了,因为析构函数定义在Foo之前
            (*pFoo)(this);
    
            std::cout << std::endl;
        }
    
        virtual void Foo() { std::cout << "Derive" << std::endl; }
    };
    
    int main()
    {
        Base* p = new Derive();
        delete p;
        system("pause");
        return 0;
    }

    输出结果如下

    Address of Base: 004A8868
    Address of Base Vtable: 00C18BE0
    Call Foo by vt -> Base
    
    Address of Derive: 004A8868
    Address of Derive Vtable: 00C18D54
    Call Foo by vt -> Derive
    
    Address of Derive: 004A8868
    Address of Derive Vtable: 00C18D54
    Call Foo by vt -> Derive
    
    Address of Base: 004A8868
    Address of Base Vtable: 00C18BE0
    Call Foo by vt -> Base
    

    可以看到在创建 new Derive();类的过程中,在调用不论是基类还是派生类的构造函数和析构函数时,this指针时不会发生变化的,也就是说这个类本身地址是没有变化的。但是其虚函数表指针指向的地址却发生了变化:
    在调用基类的构造函数和析构函数时,其虚函数表指针指向的是基类的虚函数表地址,如上面程序的00C18BE0
    而在调用派生类的构造函数和析构函数时,其虚函数表指针指向的是派生类的虚函数表地址,如上面程序的004A8868

    所以说在类生成和析构的过程中,其虚函数表的指针时不断在发生变化的,所以就有了如下的两个问题:

    • 虚函数表什么时候生成?
    • 类里的虚函数指针什么时候初始化?

    2.虚函数表什么时候生成?

    【2】深入C++虚表中的说法:
    拥有虚函数的类会有一个虚表,而且这个虚表存放在类定义模块的数据段中。模块的数据段通常存放定义在该模块的全局数据和静态数据,这样我们可以把虚表看作是模块的全局数据或者静态数据
    也就是说虚函数表是在程序一开始运行的时候聚初始化好了的,每个类里面虚函数表中都有哪些内容都是已经订好的。类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表。
    而具体类的实例中的虚表指针的时候,是在类生成过程中动态绑定的,详见下一条:

    3.类里的虚函数指针什么时候初始化?

    按照【2】【3】中的反汇编的得到的汇编代码我们可以看到,类实例中的虚表指针是在类调用构造函数的时候完成初始化的,只不过是在进入构造函数函数体之前就完成了初始化。

    这也就是为什么构造函数不能是虚函数的原因,因为虚函数的调用需要涉及到虚表指针,而在构造函数调用之前,虚表指针还没有完成初始化,就没法调用虚的构造函数。

    继续说我们的这块的初始化内容,在构造函数进入函数体之前,进行虚表的初始化,此时需要讲虚表指针初始化为当前类的虚表地址,即在基类调用构造函数的时候,会把基类的虚表地址赋值给虚表指针,而如果进行到子类的构造函数时,就把子类的虚表地址赋值给虚表指针。
    所以就有了上面那个程序中,一个派生类创建过程中,其虚表指针在不断变化的原因。

    而在析构函数中刚好相反,可以认为编译器在子类的析构函数的末尾将虚表指针指向了基类,然后紧接着插入了基类的析构函数。【这个部分存疑,我没有找到作证我的观点例子】

    参考资料

    1. 在构造函数/析构函数中调用虚函数
    2. 深入C++虚表
    3. 从汇编层面深度剖析C++虚函数
    展开全文
  • C++虚函数调用机制初探.pdf
  • 虚函数调用过程

    千次阅读 2016-03-15 15:54:33
    2.(mov edx,dword ptr [ecx])this指针指向该对象的首地址,而该处的前四个字节存放着该对象的虚函数表的首地址,将虚表指针放到edx中。 3.(call dword ptr [edx+4])由于edx中存放着虚表指针,则edx+4表示调用...

    1.(mov ecx,dword ptr [ebp-0Ch])将this指针压入ecx

    2.(mov edx,dword ptr [ecx])this指针指向该对象的首地址,而该处的前四个字节存放着该对象的虚函数表的首地址,将虚表指针放到edx中。

    3.(call dword ptr [edx+4])由于edx中存放着虚表指针,则edx+4表示调用该虚表中的第二个函数

    4.执行到上述操作后,执行该条指令(jmp B::say (00401320)),从而真正调用我们的虚函数!

     

       如果我们的程序是通过指向对象的指针或者是引用来调用该对象的虚函数,则在调用虚函数的过程需要查表(虚函数表)来调用真正的函数。

       调用的不是虚函数则不需要查表,在编译时即可确定调用的是那个函数。

       如果是通过对象来调用则对任何类型的函数都不需要查表。

       虚函数指针是在对象的构造函数中初始化的。

     

    关于虚表指针的测试程序:

    //virtual1.cpp 
    #include <iostream> 
     using namespace std; 
     class A 
     {      
     	private:          
     	int a_val;      
     	public:          
     	virtual void show(){cout<<"show"<<a_val<<endl;}          
     	virtual void say(){cout<<"say"<<endl;}        
     	inline void setVal(int val){a_val = val;} 
    };
    int main() 
     {     
     	A a;
    	a.setVal(123);
    
     	//受保护的虚函数是不能被直接调用的    
     	//而通过虚表指针的地址拷贝则可实现函数的调运     
     	//a.show();    
     	 //该指针存放虚表地址     
     	 int *des = new int;     
     	 //该指针存放虚表中的第一个函数     
     	 int *ptr = new int;     
     	 memcpy(des,&a,4);    
     	 memcpy(ptr,reinterpret_cast<int *>(*des),4);     
     	 void (*pshow)() = reinterpret_cast<void (*)()>(*ptr);     
     	 //依据__thiscall的调用约定将this指针传入ecx     
     	 //从而使虚函数能够正确地取出参数     
     	 int addre = reinterpret_cast<int>(&a);     
     	 __asm34     {        
     		mov ecx,addre
     	};
         
     	pshow();     
     	//获得私有的成员v_val
     	memcpy(des, reinterpret_cast<int *>(&a)+1, 4);
     	cout<<*des<<endl;
     	return 0;
    }
    
    
    
    //virtual2.cpp
    #include <iostream> 
    #include <string> 
    using namespace std; 
    class A 
    { 
        private: 
        int a_val; 
        
        public: 
        virtual void show(std::string str)
        {
    	    /*
    	    int addr = reinterpret_cast<int>(this);
    	    __asm14             {
    	    mov ecx,addr16             }
    	    */
    	    cout<<a_val<<endl;
    	    cout<<str<<endl;
        }
        virtual void say(){cout<<"say"<<endl;}
        inline void setVal(int val){a_val = val;}
    };
        /*
    class B: public A
    {
        private:
        int b_val;
        public:
        void say(){cout<<"B in say"<<endl;}
        virtual void hello(){cout<<"hello"<<endl;}
    };
        */
    int main()
    {
        A a;
        a.setVal(123);
        //受保护的虚函数是不能被直接调用的
        //而通过虚表指针的地址拷贝则可实现函数的调运
        //a.show();
        //该指针存放虚表地址
        int *des = new int;
        //该指针存放虚表中的第一个函数
        int *ptr = new int;
        memcpy(des,&a,4);
        memcpy(ptr,reinterpret_cast<int *>(*des),4);
        void (*pshow)(std::string) = reinterpret_cast<void (*)(std::string)>(*ptr);
        int addre = reinterpret_cast<int>(&a);
        __asm55     {
        mov ecx,addre57     };
        string str("hello world");
        pshow(str);
        __asm63     {
        sub esp,10h65     };
        //获得私有的成员v_val
        memcpy(des, reinterpret_cast<int *>(&a)+1, 4);
        cout<<*des<<endl;
        //cout<<*des<<endl;
        //cout<<*ptr<<endl;
        return 0;
    }
    
    
    
    //virtual3.cpp
    
    #include <iostream> 
    #include <string> 
    using namespace std; 
    class A 
    { 
        private: 
        char a_val;
        public: 
        virtual void show()
        {
        	cout<<"show"<<endl;
        }
        virtual void say(){cout<<"say"<<endl;}
        inline void seta_Val(char val){a_val = val;}
        inline char geta_Val()const{return a_val;}    
    };
    class B: public A19 {
    private:
    	int b_val;
    public:
    	void say(){cout<<"B in say"<<endl;}
    	virtual void hello(){cout<<"hello"<<endl;}
    	inline void setb_Val(int val){b_val = val;}
    	inline int getb_Val()const{return b_val;}
    };
    int main()
    {
    	B b;
    	b.setb_Val(123);
    	b.seta_Val('A');
        int *vfptr = new int;
        int *pf = new int;
        memcpy(vfptr, &b, 4);
        memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4);
        void (*pfun)() = reinterpret_cast<void (*)()>(*pf);
        pfun();
        char *pa_val = new char;
        int *pb_val = new int;
        memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char));
        memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int));
        cout<<*pa_val<<endl;
        cout<<*pb_val<<endl;
        cout<<"<<<<<<<<<<<<<<"<<endl;
        *pa_val = 'B';
        *pb_val = 999;
        memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char));
        memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4);
        cout<<b.geta_Val()<<endl;
        cout<<b.getb_Val()<<endl;
        return 0;
    }
    
    



    由以上测试程序可以得出以下结论:

    1.c++对象(基类对象)的内存布局是:对象的内存地址(&a)所指向的内存中的前四个字节中存放的是该对象的虚函数表的首地址(前提是该对象有虚函数),接下来的内存中依次存放该对象的数据成员(非静态的数据成员)。

     

    注意:对象的虚函数表中存放的实际上并不是虚函数的入口地址,而是一个跳转指令(jmp)的地址,该跳转指令,转向虚函数的入口,为了叙述方便,我这里作出约定:我们就认为虚函数表中就存放的是虚函数的入口地址。

    虚函数的存放顺序与函数的声明顺序是相同的。

     

    2.派生类的对象的内存布局是:前四个字节依然存放虚表指针,虚表中首先存放父类的虚函数地址,注意,由于派生类中也可能有①自己的虚函数,同时派生类也可以②重写父类的虚函数,虚函数表的分布如何:

    对于情况一而言,将派生类新增加的虚函数地址依次添加到虚表(虚表中已经有父类的虚函数地址)的后面。

    对于情况二而言,如果派生类重写了父类的虚函数,则将重写后的虚函数地址替换掉父类原来的虚函数地址,如果没有重写,则按照父类的虚表顺序存放虚函数地址

     

    接下来的内存中依次存放该对象的父类的数据成员(非静态的数据成员),然后再存放派生类自己的数据成员。(还有内存对齐的问题)

     
    #include <iostream> 
    #include <string> 
    using namespace std; 
    class A 
    { 
    private: 
    	char a_val;
    	int a_val2; 
    public:
    	virtual void show(){cout<<"show"<<endl;}
    	virtual void say(){cout<<"say"<<endl;}
    	inline void seta_Val(char val){a_val = val;}
    	inline void seta_Val2(int val2){a_val2 = val2;}
    	inline char geta_Val()const{return a_val;}
    	inline int geta_Val2()const{return a_val2;}
    };
    class B: public A21 {
    private:
    	int b_val;
    	char b_val2;
    public:
    	void say(){cout<<"B in say"<<endl;}
    	virtual void hello(){cout<<"hello"<<endl;}
    	inline void setb_Val(int val){b_val = val;}
    	inline void setb_Val2(char val2){b_val2 = val2;}
    	inline int getb_Val()const{return b_val;}
    	inline char getb_Val2()const{return b_val2;}
    	 };
    	 int main()
    	 {
    	 B b;
    	 b.seta_Val('A');
    	 b.seta_Val2(1);
    	 b.setb_Val(2);
    	 b.setb_Val2('B');
    	 int *vfptr = new int;
    	 int *pf = new int;
    	 memcpy(vfptr, &b, 4);
    	 memcpy(pf, reinterpret_cast<int *>(*vfptr)+2, 4);
    	 void (*pfun)() = reinterpret_cast<void (*)()>(*pf);
    	 pfun();
    	 char *pa_val = new char;
    	 int *pb_val = new int;
    	 memcpy(pa_val, reinterpret_cast<int *>(&b)+1, sizeof(char));
    	 memcpy(pb_val, reinterpret_cast<int *>(&b)+2, sizeof(int));
    	 cout<<*pa_val<<endl;59     cout<<*pb_val<<endl;
    	 memcpy(pb_val, reinterpret_cast<int *>(&b)+3, sizeof(int));
    	 //存在内存对齐的问题
    	 memcpy(pa_val, reinterpret_cast<int *>(&b)+4, sizeof(char));
    	 cout<<*pb_val<<endl;66     cout<<*pa_val<<endl;
    	 /*
    	 cout<<"<<<<<<<<<<<<<<"<<endl;
    	 *pa_val = 'B';
    	 *pb_val = 999;
    	 memcpy(reinterpret_cast<int *>(&b)+1, pa_val, sizeof(char));
    	 memcpy(reinterpret_cast<int *>(&b)+2, pb_val, 4);
    	 cout<<b.geta_Val()<<endl;
    	 cout<<b.getb_Val()<<endl;
    	 */    
    	 return 0;
    }

     

    展开全文
  • 子类继承父类的虚函数调用

    万次阅读 多人点赞 2016-04-17 15:00:11
    子类继承父类中,虚函数再构造函数中和不再构造函数中的调用
  • 子类虚函数调用无效

    2017-06-02 09:36:39
    主要表现为子类重载了父类的虚函数后,使用正常方法(即父类指针指向子类对象后调用虚函数),结果调用的仍为父类的函数。 一开始以为是指针指向有问题,或者虚函数的声明名称写错了这种小问题,但是检查后都是...
  • 本文给大家分享了避免析构函数调用虚函数
  • 虚函数调用

    千次阅读 2017-03-12 17:44:58
    虚函数怎么调用: (1)先找到虚函数表 (2)从虚表中找到要调用虚函数。单继承中派生类虚表的形成: 先看下述的代码:#include using namespace std;//基类中包含虚函数和普通函数 class Base { public: ...
  • 虚函数的实调用与虚调用

    千次阅读 2017-07-29 18:15:56
    在发生函数调用的时候,如果函数的入口地址是在编译阶段静态确定的,就是是实调用。反之,如果函数的入口地址要在运行时通过查询虚函数表的方式获得,就是虚调用。虚函数的实调用不通过指针或者引用调用虚函数虚调用...
  • C++虚函数调用的反汇编解析
  • 关于基类构造函数调用虚函数实际调用的不是派生类的问题的原因   我们知道,类的构造函数里面编译器插入了很多代码,比如异常安全,虚函数表指针的设置,基类构造,等等。 而且,关键是这些代码时在任何...
  • C++ - 继承虚函数调用 代码

    千次阅读 2014-09-12 10:41:08
    继承虚函数调用 代码本文地址:http://blog.csdn.net/caroline_wendy派生类继承基类时, 使用virtual时, 会进行动态绑定, 没有virtual关键字则会覆盖.使用基类指针(Base*)指向派生类时, 调用函数, 则动态绑定的函数会...
  • 派生类虚函数调用基类版本

    千次阅读 2012-12-04 10:27:02
    C++ primer 这本书上有这么两句话“派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。” 这句话应该这样...
  • 父类函数调用虚函数

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

    千次阅读 2017-07-21 10:51:40
    一,下面是一个在非虚函数调用虚函数的例子 #include using namespace std; class Base { protected: int i; public: Base(int j):i(j){} virtual ~Base(){} virtual void func2(){ i ++; } void fu
  • C++类中非虚函数虚函数相互调用

    千次阅读 2012-06-14 23:15:30
     2)在虚函数调用虚函数的时候, 调用的非虚函数是与虚函数所在的类是一致的  3) 在非虚函数调用虚函数的时候, 调用虚函数是与对像的实际类一致的。  4) 非虚函数虚函数调用与其存取权限无关。 ...
  • C++编译期间的虚函数调用机制

    千次阅读 2010-11-25 21:11:00
    C++编译期间的虚函数调用机制
  •  在讲解虚函数的时候,我们知道,如果类中有虚函数,则该类中存在一个虚函数表(V-Table),每个该类的对象都会有一个指针指向该虚函数表,存储这类中虚函数的函数指针,而虚函数表的地址就存在该类对象内存的开始处...
  • 构造函数调用虚函数

    千次阅读 2013-03-22 14:31:05
    构造函数可以调用虚函数吗?语法上通过吗?语义上可以通过吗? C++是不允许虚构造函数。 测试代码: class A { public: virtual A() { cout; } private: int x; }; int main() { A
  • 虽然可以对虚函数进行实调用,但程序员编写虚函数的本意应该是实现动态联编。在构造函数中调用虚函数,函数的入口地址是在编译时静态确定的,并未实现虚调用。但是为什么在构造函数中调用虚函数,实际上没有发生动态...
  • Author:Jeff 2005-12-7关键字:C++ 虚函数 注入环境:Window XP Professional + SP2, VC6.0 在用汇编语言调用class的private虚函数Outp()时,注意到在调用之前先PUSH 23,而在调用后并... 从网上摘录的关于函数调用
  • 今天去参加百度的面试,遇到一个关于虚函数的机制的问题,我一直认为就是为了让基类指针或引用(指向继承类)能够看到基类的虚函数(当基类的虚函数没有被继承类重写),但是继承类又怎么看到基类的普通成员函数呢?...
  • 用汇编解释虚函数调用

    千次阅读 2012-06-09 16:07:18
    做程序设计助教,刚刚有个同学来实验室找我。问了个问题,“为什么指向基类指针能指向派生类,而且能调用派生类的函数”。  我按我的理解,简单解释下  ... 但是如果函数是虚函数,那么C++编译器会把这个调用,借
  • 虚函数调用虚函数要注意的地方

    千次阅读 2017-03-12 17:51:08
    居然没有注意虚函数调用虚函数的问题 class base { public: base(){}; virtual void printa() { printf("base A\n"); } virtual void printb() { printf("base B\n"); printa(); } }; class son :...
  • 调用基类函数的时候,如果基类调用了一个虚函数,同样虚机制也会产生效果!看一个程序,没有virtual的情况: #include iostream>using namespace std;class Base ...{public: Base() ...{} virtual ~...
  • 下面小编就为大家带来一篇c++ 构造函数中调用虚函数的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 引出:写个类A,声明类A指针指向NULL,调用类A的方法会有什么后果,编译通过吗,运行会通过吗? #include #include using namespace std; class base{ int a; public: void fun(){

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 165,862
精华内容 66,344
关键字:

关于虚函数的调用