0
点赞
收藏
分享

微信扫一扫

华为云CodeArts Check代码检查插件(CodeArts IDE本地版本)使用指南

爱我中华8898 2023-10-08 阅读 84
c++

虚函数表

先来看这个问题:

class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};

sizeof(Base)是多少? 

答案是:8

因为Base中除了成员变量_b,还有一个虚函数表_vfptr(当类中有虚函数就会生成),虚函数表的本质是函数指针数组,用来存储虚函数的指针


class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}

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

void Func3()
{
cout << "Base::Func3()" << endl;
}
private:
int _a = 1;
};

class Derive :public Base
{
public:
void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _b = 2;
};

void test1()
{
Base bs;
Derive de;
}

 

基类虚函数表

 

派生类基函数表

派生类虚函数表是这样生成的:先将基类的虚函数表内容拷贝过来,在把派生类中重写的虚函数的地址覆盖到被重写的虚函数地址上,最后将派生类自己增加的虚函数依次增加到虚表的末尾。

上面这个例子中,派生类和基类的虚函数表都存储有两个函数地址,即Func1和Func2,但是派生类只对Func1重写,没有对Func2重写,编译器会将派生类中的Func1的地址进行覆盖,所以Func1的地址不同,Func2的地址相同(0x009f1370)

我的编译器是将虚函数表存储在类对象的开始位置,使用32位系统虚表大小4byte

void test()
{
int i = 0;
printf("栈区:%p\n",

char* ch = new char;
printf("堆区:%p\n", ch);

static int ci = 1;
printf("数据段:%p\n",

const char* s = "hello";
printf("代码段:%p\n", s);

Derive de;
printf("虚表:%p\n", *((int*)

}

虚表地址和代码段地址相差48byte,基本可以认为虚表存在代码段。大家可以参考这个方法,根据自己的编译器和环境来测试


多态原理

说了这么多,虚函数表在多态中有什么用?

当我们有基类的指针或引用调用派生类的虚函数时,第一步是将派生类赋值给基类,这时派生类只会将属于基类的那部分交给基类的指针或引用,而属于基类的那部分中包含了派生类的虚函数表。这样造成的结果就是,调用虚函数时调用了派生类中重写过的虚函数,实现了多态。

为什么直接将派生类赋值给基类就不能实现多态呢?因为这种写法会在编译器编译时,直接从符号表确定函数地址,程序运行时直接调用。而多态调用在编译期间不会确定,只在程序运行时到对象中寻找函数。其实编译器并不知道这个对象是基类还是派生类,只是无脑从虚函数表中找。


下面这个测试程序,通过观察汇编代码,就可以直观看出普通调用和多态调用的区别:

void test()
{
Base bs;
Derive de;
bs = de;
Base de;

bs.Func1();

b.Func1();
}

多继承的虚函数表

前面都是以单继承为例讲虚函数表和多态原理,下面我们看多继承的虚函数表

class Base1
{
public:
virtual void Func1()
{
cout << "Base1::Func1()" << endl;
}
virtual void Func2()
{
cout << "Base1::Func2()" << endl;
}
private:
int _a = 1;
};

class Base2
{
public:
virtual void Func3()
{
cout << "Base2::Func3()" << endl;
}

virtual void Func4()
{
cout << "Base2::Func4()" << endl;
}
};
class Derive :public Base1 ,public Base2
{
public:
void Func1()
{
cout << "Derive::Func1()" << endl;
}
void Func3()
{
cout << "Derive::Func3()" << endl;
}
virtual void Func5()
{
cout << "Derive::Func5()" << endl;
}
private:
int _b = 2;
};

void test()
{
Base1 b1;
Base2 b2;
Derive d;
}

调试: 

 

 

多继承后的派生类有两个虚函数表,Func2和Func4没有重写,与基类函数地址相同。Func1和Func3重写后进行覆盖。派生类新增加的虚函数地址存到了最先继承的基类Base1的虚表中(第一张图的监视窗口未显示,有bug,在内存窗口可以看到) 

重写是对虚函数函数体部分的重写

看下面程序的运行结果是什么:

 class A
 {
 public:
   virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
   virtual void test(){ func();}
 };
 class B : public A
 {
 public:
   void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
 };
 int main(int argc ,char* argv[])
 
{
   B*p = new B;
   p->test();
   return 0;
 }

test函数没有重写,直接调用A::test();func函数被重写,虚表中是B中func的函数地址,又因为重写只是对函数体的重写,val缺省值是0,结果是B->0.

举报

相关推荐

0 条评论