virtual成员函数

一,虚函数一般实现模型:
    每一个类只要含有虚函数,就会创建一个虚函数表,里面包含的虚函数的地址,每个类对象里面包含一个指针(vptr)指向这个虚函数表。(ps基于主流编译器的,标准并未规定)
下面我们来测试一下:

#include<iostream>

using namespace std;

class A
{
    private:
        int va;     //4
        int vb;     //4
};

class B
{
    public:
        virtual void foo(){};
    private:
        int va;     //4
        int vb;     //4
};

class C
{
    public:
        virtual void foo(){};
        virtual void g(){};
    private:
        int va;     //4
        int vb;     //4
};

int main()
{
    A a;
    B b;
    C c;
    cout<<"a:"<<sizeof(a)<<endl;
    cout<<"b:"<<sizeof(b)<<endl;
    cout<<"c:"<<sizeof(b)<<endl;
    return 0;
}

上述例子可以看出,首先没有虚函数的类的对象里面没有额外花销;其次添加了了虚函数之后类对象内部就添加了一个我们上述说道的指针;最后继续添加虚函数并不会继续改变类对象的大小。

二.多态(简单提一下重点不在它):
    多态表示一下基类的指针或者引用指向一个派生类的对象,然后通过基类指针或引用调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。
    识别一个类是否支持多态,唯一适当的方法就是看看他是否有任何虚函数:只要类拥有一个虚函数,他就需要这份额外的执行期信息。
    如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。

三.单一继承下的虚函数:
    一个类只会有一个虚函数表,每个表内含其对应的类对象中所有虚函数的地址。
    首先我们先学一下取虚函数表地址的过程:
        1,一般指向虚函数表的指针(vptr)都是放在对象的首部,所以我们对类对象取地址则是&vptr
        2,我们对取得地址转换为指针类型,然后解引用,得到vptr也就是虚函数表的地址。
    取得了虚函数表的地址我们就可以对虚函数进行操作了,因为虚函数表里面存的都是虚函数的地址。

#include<iostream>

using namespace std;

class A
{
public:
    virtual void foo(){ cout << "virtual foo function" << endl; };
    virtual void goo(){ cout << "virtual goo function" << endl; };
};
typedef  void(*F)(void);     //结合右左法则很好理解 ,F是一个函数指针。
int main()
{
    A a;
    int **p = (int**)&a;
    cout << p << endl;         //取vptr的地址及虚函数指针的地址
    cout << p[0] << endl;      //虚函数指针,由于虚函数指针指向虚函数表,所以它是虚函数表的地址
    cout << p[0][0] << endl;   //虚函数表里面存的虚函数的地址
    F f1 = (F)(p[0][0]);   //f1指向虚函数A::foo
    f1();
    F f2 = (F)(p[0][1]);//f2指向虚函数A::goo
    f2();
    return 0;
}

  

  下面我们对继承里面虚函数地址的变化进行研究:

#include<iostream>

using namespace std;

class A
{
    public:
        virtual void foo(){  cout<<"virtual foo function"<<endl; };
        virtual void goo(){ cout<<"virtual goo function"<<endl; };
};

class B:public A
{
    public:
        void goo(){ cout<<"1"<<endl;}    //对于基类的虚函数进行修改
        virtual void hoo(){ cout<<"virtual hoo function"<<endl;}
};

int main()
{
    A a;
    B b;
    int **p = (int**)&a;
    int **q = (int**)&b;
    cout<<"A的虚函数表地址"<<p[0]<<endl;
    cout<<"A::foo地址"<<p[0][0]<<endl;
    cout<<"A::goo地址"<<p[0][1]<<endl;
    cout<<"B的虚函数表地址"<<q[0]<<endl;
    cout<<"B::foo地址"<<q[0][0]<<endl;
    cout<<"B::goo地址"<<q[0][1]<<endl;
    cout<<"B::hoo地址"<<q[0][2]<<endl;
    return 0;
}

对于一个派生类来说它的虚函数包含的地址有三种可能性:
        1,派生类直接继承基类的虚函数而不修改,则该函数的地址会从基类的虚表直接拷贝到派生类的虚表里面;
        2,派生类从基类继承的虚函数并且进行了修改,这部分虚函数在派生类虚表里面有了新的地址,和基类的不同;
        3,派生类自己声明的虚函数,这些函数都是新的函数地址。

四,多重继承的虚函数:

class A
{
    public:
        virtual void foo(){  cout<<"virtual foo function"<<endl; };
    private:
        int va;
};

class B
{
    public:
        virtual void hoo(){ cout<<"virtual hoo function"<<endl;}
    private:
        int vb;
};
class C:public A,public B
{
    public:
        virtual void goo(){ cout<<"virtual goo function"<<endl; }
    private:
        int vc;
};

多重继承有多个虚函数表,一般多了n-1个虚函数表针对每一个虚函数表都有一个指针指向它,当派生类对基类的虚函数进行重写时,派生类的函数覆盖基类的函数在对应的虚函数位置,当派生类有新的虚函数时,这些虚函数被加在第一个虚函数表的后面。

时间: 2024-10-15 16:45:34

virtual成员函数的相关文章

(转)c++类的成员函数存储方式(是否属于类的对象)---一道面试题引发的思考

昨天去面试一家公司,面试题中有一个题,自己没弄清楚,先记录如下: class D { public: void printA() { cout<<"printA"<<endl; } virtual void printB() { cout<<"printB"<<endl; } }; main函数调用: D *d=NULL; d->printA(); d->printB(); 输出结果是? 当时想的是对象d直

这两个成员函数inline重新virtual种类

inlineType表示在编译时扩展功能,随着在函数调用的函数体而出替换函数调用:和vitual它是c++多态的必要条件,但为了表现出多态性,您将需要等到执行,要知道什么是真正的函数调用.从表面上看这两个keyword假定同时使用,将生成一个错误.但不会. 先简单说一下虚表的机制:多态的实现是由虚表加以支持的,凡是有虚函数的对象,都会在构造函数開始时构造一个虚表,虚表中的第一个元素通常是对象的类型信息.其它每一个元素存放的是真正函数的地址,假设子类覆盖了父类的虚函数,则相应的位置中的地址就会被改

成员函数既是inline又是virtual类型

inline类型表示在编译时期进行函数展开,用函数体在函数调用出代替函数的调用:而vitual是c++多态的必要条件,但是要想表现出多态,必须要等到运行时,才知道真正调用的是哪一个函数.表面上看这两个关键字如果同时使用,会产生错误,但不会的. 先简单说一下虚表的机制:多态的实现是由虚表加以支持的,凡是有虚函数的对象,都会在构造函数开始时构造一个虚表,虚表中的第一个元素一般是对象的类型信息,其他每个元素存放的是真正函数的地址,如果子类覆盖了父类的虚函数,则对应的位置中的地址就会被修改,但是同一个函

类的成员函数的指针

前面一篇文章<函数的指针 >介绍了全局函数的指针,现在我们再来介绍一下成员函数的指针. 成员函数指针的定义: 一般形式 Return_Type (Class_Name::* pointer_name) (Argument_List); 用typedef简化的形式 Typedef Return_Type (Class_Name::* FuncPtr_Type) (Argument_List); FuncPtr_Type pFunc = NULL; //pFunc为成员函数指针的指针变量 成员函数

C++成员函数指针错误用法警示(成员函数指针与高性能的C++委托,三篇),附好多评论

今天做一个成绩管理系统的并发引擎,用Qt做的,仿照QtConcurrent搞了个模板基类.这里为了隐藏细节,隔离变化,把并发的东西全部包含在模板基类中.子类只需注册需要并发执行的入口函数即可在单独线程中执行.最终目标是,继承的业务逻辑类外部调用时有两个接口可选,调用syncRun同步执行:调用由引擎自动生成的asyncRun就异步执行.最终自动生成asyncRun的模板基类没能实现,主要原因是mingw对this处理的太有问题了!!原本以为编译器问题,后来才知道成员函数指针和this指针如此特殊

【C++缺省函数】 空类默认产生的6个类成员函数

1.缺省构造函数. 2.缺省拷贝构造函数. 3. 缺省析构函数. 4.缺省赋值运算符. 5.缺省取址运算符. 6. 缺省取址运算符 const. <span style="font-size:18px;"> class A { public: A(){}//缺省构造函数 A(const A&){}//拷贝构造函数 ~A(){}//析构函数 A&operator=(const A&){}//赋值运算符 A*operator&(){}//取址运算

拷贝构造函数和const成员函数

实验原因 说明如何使用const描述保护类数据不会意外修改. 编译环境 vc6sp6 + win7x64 工程下载 copyConstruction_constMemberFunction.zip 使用非const成员函数,引起的拷贝构造函数报错 [cpp] view plain copy class CStudent { /// 常量定义 public: enum {NAME_SIZE_MAX = 64}; /// 构造, 拷贝构造, 析构函数 public: CStudent(); CStu

为什么 C++ 中成员函数指针是 16 字节?

当我们讨论指针时,通常假设它是一种可以用 void * 指针来表示的东西,在 x86_64 平台下是 8 个字节大小.例如,下面是来自 维基百科中关于 x86_64 的文章 的摘录: Pushes and pops on the stack are always in 8-byte strides, and pointers are 8 bytes wide. 从 CPU 的角度来看,指针无非就是内存的地址,所有的内存地址在 x86_64 平台下都是由 64 位来表示,所以假设它是 8 个字节是

boost在lambda表达式中调用占位符参数的成员函数的方法

boost中提供了lambda表达式的用法,但是lambda表达式的功能还不是很强大,在其中只能对lambda的占位符参数_1等使用最基本的操作符,如+-*/,可是很多时候如果传入的占位符参数是一个对象指针的话,我们可能想要调用这个类的成员函数. 我在开发中遇到了这个问题,需要在stl的算法中传入一个函数来调用对象的比较函数,因为感觉这样太麻烦,还需要重新定义一个函数,所以想起了lambda表达式,c++11的lambda表达式我倒是没试过,可是受项目开发环境所限,只能选择boost.但是我用的