首页 > 编程知识 正文

c中虚函数的作用,c++和c语言有什么区别

时间:2023-05-03 13:54:42 阅读:35134 作者:4167

在理解虚函数之前,请了解以下对象模型:

对象模型: c具有两种类型的数据成员:静态和非静态,以及三种类型的成员函数: classmemberfunctions3360static、nonstatic和virtual:

说明:使用未继承的c对象模型。

非静态数据成员位于每个类对象上,静态数据成员位于类对象之外。 静态函数和非静态函数也位于类对象之外,但对于virtual函数,虚函数表中的虚指针支持,如下所示:

每个类都生成一个称为虚拟表(virtual table,简称vtbl )的表。 虚拟表包含指向该类的所有虚拟函数的一堆指针。 虚拟表中的函数地址按声明的顺序排列,但子类中有多个重载函数时例外。

每个类对象都有一个虚拟表指针(vptr ),由编译器生成。 所有虚拟表指针的设置和重置都由类的复制控件完成,即构造函数、析构函数和赋值操作符。 vptr的位置由编译器决定,以前放在要显示声明的所有成员之后,但现在许多编译器都将vptr放在一个类对象的开头。

此外,虚函数表前面还有一个指向type_info的指针,支持运行时类型识别(rtti )。 RTI是为多态性生成的信息,仅生成具有虚函数的对象,如对象继承关系、对象自身的描述等。

圆表示类中每个对象可以共享的部分,矩形表示每个对象自己拥有的部分。

个人资料

虚函数(virtual )是c实现多态性所采用的方法,使用的是动态绑定技术虚函数表。 包括虚函数在内的所有类都有虚表。 如果基类包含虚函数,则派生类也需要自己的虚表。 通过使用这些虚函数表,即使使用基类指针调用函数,也可以正确调用正在运行的实际对象的虚函数。

例如,a类包含虚函数vfunc1、vfunc2,a类包含虚函数,因此a类具有虚表。

类a {

公共:

虚拟语音功能1 (;

虚拟语音功能2 (;

void func1(;

void func2(;

隐私:

int m_data1,m_data2;

(;

然后,a类的虚表(虚表只包含虚函数)如下:

虚表其实是指针数组,每个元素都是指向虚函数的指针。 普通函数是非虚函数,不需要通过虚表。 虚表中条目虚函数指针的赋值发生在编译器的编辑阶段,在代码编译时已经构建。

2 .虚拟表指针

虚拟表属于类,不属于特定对象。 一个班有一个虚设表就可以了。 同一类的对象使用同一虚拟表。 为了为对象指定虚拟表,对象内部包含指向您正在使用的虚拟表的指针。 为了使类(包括虚拟表)中的所有对象都具有虚拟表指针,编译器向类中添加了指针*__vptr。 当创建指向虚拟表的对象时,指针将自动设置为指向类的虚拟表。

说明:如果继承类的基类包含虚函数,则继承类也有自己的虚表,因此继承类的对象也包含指向虚表的虚表指针。

4 .动态绑定

类a {

公共:

虚拟语音功能1 (;

虚拟语音功能2 (;

void func1(;

void func2(;

隐私:

int m_data1,m_data2;

(;

class b :公共a {

公共:

虚拟语音功能1 (;

void func1(;

隐私:

int m_data3;

(;

class c :公共b {

公共:

虚拟语音功能2 (;

void func2(;

隐私:

int m_data1,m_data4;

(;

对象模型图:

说明: a是基类,b继承a,c又继承b。 因为三个类都有虚函数,所以编译器为每个类创建一个虚表。 每个类中的每个对象都有一个虚拟表指针,指向其所属类的虚拟表。

因为a类包含两个虚函数,所以A vtbl包含两个指针,分别指向A:vfunc1()和A:vfunc2)。

b类是

类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。

类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。

法则:对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向器继承的最近一个类的虚函数

5.父类通过虚表指针访问子类的虚函数

int main()

{

B bObject;

A *p = & bObject;

}

说明:bObject是类B的一个对象,故bObject包含一个虚表指针。声明一个类A的指针指向对象bObject。

以上代码执行步骤:

根据虚表指针p->__vptr来访问对象bObject对应的虚表。虽然指针p是基类A*类型,但是*__vptr也是基类的一部分,所以可以通过p->__vptr可以访问到对象对应的虚表。

在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目。

根据虚表中找到的函数指针,调用函数。从图3可以看到,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。

  《-------》    指针p是基类A*类型,但是*__vptr也是基类的一部分的理解。

6.静态绑定和动态绑定,运行时多态,编译时多态

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

通过指针来调用函数

指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast,可以参考本文的参考资料)

调用的是虚函数

符合三个条件,编译器就会把该函数调用编译为动态绑定,调用时走虚表的机制。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。