c++ 虚函数,虚表相关总结

面向对象,从单一的类开始说起。

class A
{
private:
    int m_a;
    int m_b;
}; 

这个类中有两个成员变量,都是int类型,所以这个类在内存中占用多大的内存空间呢?

sizeof(A), 8个字节,一个int占用四个字节。下图验证:

这两个数据在内存中是怎样排列的呢?

原来是这样,我们根据debug出来的地址画出a对象在内存的结构图

如果 class A 中包含成员函数呢? A 的大小又是多少?

class A
{
public:
    void func1() {}    
private:
    int m_a;
    int m_b;
}; 

直接告诉你答案,类的成员函数多大? 没人能回答你,并且不是本文的重点,类的成员函数是放在代码区的,不算在类的大小内。

类的对象共享这一段代码,试想,如果每一个对象都有一段代码,光是存储这些代码得占用多少空间?所以同一个类的对象共用一段代码。

共用同一段代码怎么区分不同的对象呢?

实际上,你在调用成员函数时,a.func1() 会被编译器翻译为 A::func1(&a),也就是A* const this, this 就是 a 对象的地址。

所以根据this指针就能找到对应的数据,通过这同一段代码来处理不同的数据。

接下来我们讨论一下继承,子类继承父类,将会继承父类的数据,以及父类函数的调用权。

以下的测试可以验证这个情况。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func2() { cout << "B func2" << endl; }
private:
    int m_c;
};

int main(int argc, char const* argv[])
{
    B b;
    b.func1();
    b.func2();
    return 0;
} 

输出:

// A func1
// B func2 

那么对象b在内存中的结构是什么样的呢?

继承关系,先把a中的数据继承过来,再有一份自己的数据。

每个包含虚函数的类都有一个虚表,虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。

为了指定对象的虚表,对象内部包含指向一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
}; 

cout << sizeof(A);, 输出12,A中包括两个int型的成员变量,一个虚指针,指针占4个字节。

a的内存结构如下:

虚表是一个函数指针数组,数组里存放的都是函数指针,指向虚函数所在的位置。

对象调用虚函数时,会根据虚指针找到虚表的位置,再根据虚函数声明的顺序找到虚函数在数组的哪个位置,找到虚函数的地址,从而调用虚函数。

调用普通函数则不像这样,普通函数在编译阶段就指定好了函数位置,直接调用即可。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc2() { cout << "B vfunc2" << endl; }
private:
    int m_a;
}; 

像这样,B类继承自A类,B中又定义了一个虚函数vfunc2, 它的虚表又是怎么样的呢?

给出结论,虚表如下图所示:

我们来验证一下:

A a;
B b;
void(*avfunc1)() = (void(*)()) *(int*) (*(int*)&a);
void (*bvfunc1)() = (void(*)()) *(int*) *((int*)&b);
void (*bvfunc2)() = (void(*)()) * (int*)(*((int*)&b) + 4);
avfunc1();
bvfunc1();
bvfunc2();

来解释一下代码: void(*avfunc1)() 声明一个返回值为void, 无参数的函数指针 avfunc1, 变量名代表我们想要取A类的vfunc1这个虚函数。

右半部分的第一部分,(void(*)()) 代表我们最后要转换成对应上述类型的指针,右边需要给一个地址。

我们看 (*int(*)&a), 把a的地址强转成int*, 再解引用得到 虚指针的地址。

*(int*) (*(int*)&a) 再强转解引用得到虚表的地址,最后强转成函数指针。

同理得到 bvfunc1, bvfunc2, +4是因为一个指针占4个字节,+4得到虚表的第二项。

覆盖

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc1() { cout << "B vfunc1" << endl; }
private:
    int m_a;
}; 

子类重写父类的虚函数,需要函数签名保持一致,该种情况在内存中的结构为:

多态

父类指针指向子类对象的情况下,如果指针调用的是虚函数,则编译器会将会从虚指针所指的虚函数表中找到对应的地址执行相应的函数。

子类很多的话,每个子类都覆盖了对应的虚函数,则通过虚表找到的虚函数执行后不就执行了不同的代码嘛,表现出多态了嘛。

我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。

那么,什么时候会执行函数的动态绑定?这需要符合以下三个条件。

  • 通过指针来调用函数
  • 指针 upcast 向上转型(继承类向基类的转换称为 upcast)
  • 调用的是虚函数

为什么父类指针可以指向子类?

子类继承自父类,子类也属于A的类型。

最后通过一个例子来体会一下吧:

class Shape
{
public:
    virtual void draw() = 0;
};

class Rectangle : public Shape
{
    void draw() { cout << "rectangle" << endl; }
};

class Circle : public Shape
{
    void draw() { cout << "circle" << endl; }
};

class Triangle : public Shape
{
    void draw() { cout << "triangle" << endl; }
};

int main(int argc, char const *argv[])
{
    vector<Shape*> v;
    v.push_back(new Rectangle());
    v.push_back(new Circle());
    v.push_back(new Triangle());
    for (Shape* p : v) {
        p->draw();
    }
    return 0;
} 

有些话是大白话,哈哈,如果这篇文章写的不错,解决了你的疑惑的话,点个赞再走吧!

不对的地方也请指出来,大家一起学习进步。

以上就是c++ 虚函数,虚表相关总结的详细内容,更多关于c++ 虚函数,虚表的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++ 虚函数和纯虚函数的区别分析

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数. 简介 假设我们有下面的类层次: class A { public: virtual void foo() { cout<<"A::foo() is called"<<endl; } }; cl

  • C++ 基础教程之虚函数实例代码详解

    虚函数的定义 虚函数:就是在基类的成员函数前加关键字virtual(即被virtual关键字修饰的成员函数),并在一个或多个派生类中被重新定义的成员函数:虚函数:就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数.它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了,也就是用基类的指针来调用子类的这个函数:虚函数的作用:在于用专业术语来解释就是实现多态性,多态性是将接口与实现进行分离,通过指向派生类的基类指针或引用,访问派生类中同名

  • 详细分析C++ 多态和虚函数

    多态按字面的意思就是多种形态.当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态. C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数. 下面的实例中,基类 Shape 被派生为两个类,如下所示: #include <iostream> using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a;

  • 如何获取C++类成员虚函数地址的示例代码

    本文主要给大家介绍了关于如何获取C++类成员虚函数地址的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍: 1.GCC平台 GCC平台获取C++成员虚函数地址可使用如下方法[1]: class Base{ int i; public: virtual void f1(){ cout<<"Base's f1()"<<endl; } }; Base b; void (Base::*mfp)() = &Base::f1; printf(&qu

  • 浅谈C++ 虚函数

    缘起 在上一篇文章中,测试代码2 中的 pBaseA->AA(); 输出的内容很"奇怪".其实,完全在情理之中.本文将简单探究一下 c++ 中的虚函数实现机制.本文主要基于 vs2013 生成的 32 位代码进行研究,相信其它编译器(比如,gcc)的实现大同小异. 先从对象大小开始 假设我们有如下代码,假设 int 占 4 字节,指针占 4 字节. #include "stdafx.h" #include "stdlib.h" #inclu

  • C++ 中const修饰虚函数实例详解

    C++ 中const修饰虚函数实例详解 [1]程序1 #include <iostream> using namespace std; class Base { public: virtual void print() const = 0; }; class Test : public Base { public: void print(); }; void Test::print() { cout << "Test::print()" << end

  • C++ 虚函数专题

    虚函数 基类中使用virtual关键字声明的函数,称为虚函数. 虚函数的实现,通过虚函数表来实现的.即V-table 这个表中有一个类,用于储存虚函数的地址.解决其继承,覆盖的问题,用于保证其真实反映的函数.这样有虚函数的实例,将会储存在这个实例的内存中.即用父类的指针,操作子类的时候,通过虚函数表来实现找寻到父类. 定义下方的一个类 class Base{ public: virtual void f(){ cout << "Base::f" << endl;

  • 虚函数表-C++多态的实现原理解析

    参考:http://c.biancheng.net/view/267.html 1.说明 我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表 在 关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态 2.虚函数表 我们看一下下面的代码 class A { public: int i; virtual void func() { cout << "A func" <<

  • 详解C++虚函数的工作原理

    静态绑定与动态绑定 讨论静态绑定与动态绑定,首先需要理解的是绑定,何为绑定?函数调用与函数本身的关联,以及成员访问与变量内存地址间的关系,称为绑定. 理解了绑定后再理解静态与动态. 静态绑定:指在程序编译过程中,把函数调用与响应调用所需的代码结合的过程,称为静态绑定.发生在编译期. 动态绑定:指在执行期间判断所引用对象的实际类型,根据实际的类型调用其相应的方法.程序运行过程中,把函数调用与响应调用所需的代码相结合的过程称为动态绑定.发生于运行期. C++中动态绑定 在C++中动态绑定是通过虚函数

  • 详解C++纯虚函数与抽象类

    1.虚函数 1.1虚函数简介 虚函数可以毫不夸张的说是C++最重要的特性之一,我们先来看一看虚函数的概念. 在基类的定义中,定义虚函数的一般形式为: virtual 函数返回值类型 虚函数名(形参表) { 函数体 } 为什么说虚函数是C++最重要的特性之一呢,因为虚函数承载着C++中动态联编的作用,也即多态,可以让程序在运行时选择合适的成员函数.虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public.那么:  (1)为什么类的静态成员函数不能为虚函数?  如果定义为虚函数,那么

  • C++ 中virtual 虚函数用法深入了解

    一.virtual修饰基类中的函数,派生类重写该函数: #include using namespace std; class A{ public: virtual void display(){ cout<<"A"<<ENDL; } }; class B : public A{ public: void display(){ cout<<"B"<<ENDL; } }; void doDisplay(A *p) { p

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

随机推荐