多重继承下的虚函数表

合集下载

虚函数的实现机制

虚函数的实现机制

虚函数的实现机制
虚函数(virtual function)是 C++ 中的一种重要机制,它允许子类重新定义从父类继承的方法,这样在调用子类的方法时,会根据实际的对象类型调用相应的方法。

这种机制被称为动态绑定或运行时多态。

虚函数的实现机制主要依赖于虚表(vtable)和虚指针(vptr)。

1.虚表(vtable):每个有虚函数的类(或者从有虚函数的类派生出来的类)都有一个虚表。

虚表是一个包含指向虚函数的指针的数组。

在这个数组中,每个从父类派生出来的子类都有一个条目,每个条目都包含一个指向该类实现的相应虚函数的指针。

2.虚指针(vptr):每个包含虚函数的类的对象都有一个虚指针。

这个指针指向该对象的类的虚表。

当调用一个虚函数时,程序首先会通过这个虚指针找到对应的虚表,然后再根据虚表的索引找到正确的函数进行调用。

当定义一个含有虚函数的类时,编译器会为这个类创建一个虚表,并在该类的每个对象中嵌入一个指向该虚表的虚指针。

当创建一个类的对象时,这些对象会根据其类型使用正确的虚表。

在运行时,当调用一个虚函数时,程序会根据对象的虚指针找到正确的虚表,然后根据函数在虚表中的索引来调用正确的函数。

这种实现机制允许我们在运行时动态地决定要调用哪个函数,从而实现了多态性。

虚函数原理

虚函数原理

虚函数原理虚函数是 C++ 中一个非常重要的特性,它为面向对象编程提供了很强的支持。

虚函数的实现原理是通过虚函数表实现的,本文将介绍虚函数的概念、使用方法以及实现原理。

一、虚函数概念虚函数是指在基类中使用 virtual 关键字声明的成员函数,它的作用是允许在子类中对该函数进行覆盖。

具体来说,虚函数允许在子类中定义一个与基类中同名的函数,当使用子类对象调用该函数时,程序会动态的选择调用子类中的函数。

虚函数的语法如下:```class Base {public:virtual void foo();};```虚函数可以被重写(覆盖),也可以被继承,但是不能被 static 和 friend 修饰。

二、虚函数的使用使用虚函数需要满足一下条件:1.虚函数必须在公有的类成员函数列表中声明,并在类声明的内部定义。

2.虚函数必须在基类和派生类中以相同的参数列表进行定义。

下面是一个使用虚函数的简单例子:class Square: public Shape {public:Square(double s) : side(s) {}double getArea() { return side * side; }Shape 是一个基类,Square 是它的一个派生类,Square 中重写了 getArea() 函数,计算正方形的面积。

虚函数的实现原理是通过虚函数表实现的。

虚函数表是一个指针数组,存储了每个类中的虚函数指针。

当对象被创建时,会在其内存空间中创建一个指向虚函数表的指针,这个指针通常称为虚函数表指针(vptr),虚函数的调用就是通过这个指针完成的。

每个含有虚函数的类都有一个独立的虚函数表,虚函数表智能在类的第一个对象中存储,它包含了该类中所有虚函数的地址。

在派生类中,虚函数表通常继承自它的直接基类,并在此基础上添加或修改虚函数的地址。

这样如果在派生类对象中调用虚函数时,程序会先获得对象的虚函数表指针,然后通过该指针找到对应的虚函数地址来执行函数。

C--程序设计--第10章-多态性及虚函数

C--程序设计--第10章-多态性及虚函数

使用重载函数注意:
不要使用重载函数描述不相干的函数 在类中,构造函数和普通成员函数均可以
重载 避免与函数的默认参数产生二义性
二、运算符重载
运算符重载(operate overloading)就是 赋予已有的运算符多重含义。
运算符重载实质是函数重载,运算符重载 的选择与函数重载类似,会根据运算符的 操作数的类型、个数和顺序来进行运算符 函数的选择。
#include<iostream.h> str#iinngc:l:usdter<isntgr(icnhga.rh>*s) v{}ossccsssc{s{{ittohtttolsstlsssls*drruarrrueptrepttepsi1trii3tc{pn=rin=rrn=pmn.<nn.<lprgncngncign=agp<*ggp<auitepgtepnte'irssrssbv\hwy:hwyghwnsit1ssitsla0=(:=(:=(tnr=ttnrit'scssscs:sc)rt1"rrt3scesss~ivci;thpt1hpsih1(.T23(.t::tttsnohn}ra,r.a,tza()gh(()grrrrttiatlrsilrsrer";eass;eiiiirdre[)ne[1i;[Ttt1ttnnnniglnl;gnl.nlhl)rlggggnep*e(e}(gesgeiei;2e(((gtrsnsnstnp(nsns)ncsi(lipg)gthg)ig(;(htn)en;t;tr;t;nti)a)artnthhih}ths<<ri{((;+n++<p<snd))}1g1s1aere*ige;]]i]nonszl{{;&;z;ddgd)&eercseelrl;s=teo1)m;a;/18etu)om/)0ut..;)构sr<""/;pn<造);//;s;/复}lp函构e<制n<数造ge构tn函hd造;l数};重} 载

C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容

C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容

C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执⾏顺序和执⾏内容⼀、本⽂⽬的与说明1. 本⽂⽬的:理清在各种继承时,构造函数、复制构造函数、赋值操作符、析构函数的执⾏顺序和执⾏内容。

2. 说明:虽然复制构造函数属于构造函数的⼀种,有共同的地⽅,但是也具有⼀定的特殊性,所以在总结它的性质时将它单独列出来了。

3. 单继承、多继承、虚继承,既然都属于继承,那么虽然有⼀定的区别,但还是相同点⽐较多。

如果放在⼀块讲,但为了将内容制作成递进的,就分开了,对相同点进⾏重复,(⼤量的复制粘贴哈),但在不同点进⾏了标注。

注意:三块内容是逐步递进的如果你懂虚函数,那么单继承和多继承那块你就可以不看;如果你懂多继承,那单继承你就不要看了,⾄于虚继承就等你懂虚继承再回来看吧;如果你只懂单继承,那你就只看单继承就好。

⼆、基本知识1. 对于⼀个空类,例如:class EmptyClass{};虽然你没有声明任何函数,但是编译器会⾃动为你提供上⾯这四个⽅法。

class EmptyClass {public:EmptyClass(); // 默认构造函数EmptyClass(const EmptyClass &rhs); // 复制构造函数~EmptyClass(); // 析构函数EmptyClass& operator=(const EmptyClass &rhs); // 赋值运算符}对于这四个⽅法的任何⼀个,你的类如果没有声明,那么编译器就会⾃动为你对应的提供⼀个默认的(注意合成默认构造函数是⽤于没有编写构造函数编译器才会合成默认构造函数,其中复制构造函数也是构造函数)。

(在《C++ primer》中,这个编译器⾃动提供的版本叫做“合成的***”,例如合成的复制构造函数)当然如果你显式声明了,编译器就不会再提供相应的⽅法。

2. 合成的默认构造函数执⾏内容:如果有⽗类,就先调⽤⽗类的默认构造函数。

C++虚函数及虚函数表解析

C++虚函数及虚函数表解析

C++虚函数及虚函数表解析虚函数的定义: 虚函数必须是类的⾮静态成员函数(且⾮构造函数),其访问权限是public(可以定义为private or proteceted,但是对于多态来说,没有意义。

),在基类的类定义中定义虚函数的⼀般形式: virtual 函数返回值类型虚函数名(形参表) { 函数体 } 虚函数的作⽤是实现动态联编,也就是在程序的运⾏阶段动态地选择合适的成员函数,在定义了虚函数后, 可以在基类的派⽣类中对虚函数重新定义(形式也是:virtual 函数返回值类型虚函数名(形参表){ 函数体 }),在派⽣类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。

以实现统⼀的接⼝,不同定义过程。

如果在派⽣类中没有对虚函数重新定义,则它继承其基类的虚函数。

当程序发现虚函数名前的关键字virtual后,会⾃动将其作为动态联编处理,即在程序运⾏时动态地选择合适的成员函数。

实现动态联编需要三个条件: 1、必须把需要动态联编的⾏为定义为类的公共属性的虚函数。

2、类之间存在⼦类型关系,⼀般表现为⼀个类从另⼀个类公有派⽣⽽来。

3、必须先使⽤基类指针指向⼦类型的对象,然后直接或者间接使⽤基类指针调⽤虚函数。

定义虚函数的限制: (1)⾮类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。

实际上,优秀的程序员常常把基类的析构函数定义为虚函数。

因为,将基类的析构函数定义为虚函数后,当利⽤delete删除⼀个指向派⽣类定义的对象指针时,系统会调⽤相应的类的析构函数。

⽽不将析构函数定义为虚函数时,只调⽤基类的析构函数。

(2)只需要在声明函数的类体中使⽤关键字“virtual”将函数声明为虚函数,⽽定义函数时不需要使⽤关键字“virtual”。

(3)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、参数类型都相同的⾮虚函数。

C++14

C++14

14.1什么是虚函数
静态绑定是指绑定的是对象的静态类型,某 特性(比如函数)依赖于对象的静态类型, 发生在编译期。动态绑定:绑定的是对象的 动态类型,某特性(比如函数)依赖于对象 的动态类型,发生在运行期。
14.2 抽象类与纯虚函数
在C++中,在许多情况下,在基类中不能对 虚函数给出有意义有实现,而把它说明为纯 虚函数,它的实现留给该基类的派生类去做 。带有纯虚函数的类称为抽象类。下面,详 细介绍下纯虚函数和抽象类。 class <类名> { virtual <类型><函数名>(<参数表>)=0; };
14.4 虚函数表
虚函数(Virtual Function)是通过一张虚 函数表(Virtual Table)来实现的。简称为 V-Table。 在这个表中,主是要一个类的虚 函数的地址表,这张表解决了继承、覆盖的 问题,保证其容真实反应实际的函数。这样 ,在有虚函数的类的实例中这个表被分配在 了 这个实例的内存中,所以,当用父类的 指针来操作一个子类的时候,这张虚函数表 就显得由为重要了,它就像一个地图一样, 指明了实际所应该调用的函数。
14.2 抽象类与纯虚函数
抽象类是一种特殊的类,它是为了抽象和设 计的目的而建立的,它处于继承层次结构的 较上层。抽象类是不能定义对象的,在实际 中为了强调一个类是抽象类,可将该类的构 造函数说明为保护的访问控制权限。
14.3多重继承时抽象类的应用
在多重继承中,以抽象类作为基类,不实现 抽象类中的方法。比较在上例中,先定义汽 车和船的抽象类,在定义汽陆两用船时机可 以多重继承,然后具体实现各个抽象类的方 法。
第14章 虚函数和抽象类
ቤተ መጻሕፍቲ ባይዱ

C++中虚函数工作原理和(虚)继承类的内存占用大小计算

C++中虚函数工作原理和(虚)继承类的内存占用大小计算

C++中虚函数工作原理和(虚)继承类的内存占用大小计算一、虚函数的工作原理虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。

典型情况下,这一信息具有一种被称为vptr(virtual table pointer,虚函数表指针)的指针的形式。

vptr 指向一个被称为vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到vtbl。

当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的vptr 指向的vtbl,然后在vtbl 中寻找合适的函数指针。

虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。

如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。

而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。

所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。

故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。

虚函数需要注意的大概就是这些个地方了,之前在More effective C++上好像也有见过,不过这次在Visual C++权威剖析这本书中有了更直白的认识,这本书名字很牛逼,看看内容也就那么回事,感觉名不副实,不过说起来也是有其独到之处的,否则也没必要出这种书了。

每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。

虚函数的具体实现方式

虚函数的具体实现方式

虚函数的具体实现方式{"title":"如何实现虚函数","content":"深入探究C++虚函数的实现方式"}在C++中,虚函数是一种非常重要的特性,它允许子类使用自己的方法重载父类的方法,实现多态性。

那么,虚函数的实现方式是什么呢?在C++中,虚函数的实现方式主要有两种:虚函数表和虚函数指针。

1. 虚函数表虚函数表是一种数据结构,它存储了类中所有虚函数的地址。

每个含有虚函数的类都有一个虚函数表,这个表只有当类被实例化时才创建。

虚函数表通常是一个数组,数组中每个元素都是一个指向虚函数的指针。

当一个对象被实例化时,它会包含一个指向虚函数表的指针,这个指针被称为虚函数表指针。

虚函数表指针和对象的数据成员一起存储在对象的内存空间中。

当调用一个虚函数时,程序首先查找虚函数表指针,然后根据虚函数的位置在虚函数表中查找函数的地址,最终调用该函数。

2. 虚函数指针另一种实现虚函数的方式是使用虚函数指针,这种方式与虚函数表类似。

在含有虚函数的类中,每个对象包含一个指向虚函数表的指针,这个指针被称为虚函数指针。

不同的是,虚函数指针不是存储在类的内存空间中,而是存储在对象的内存空间中。

当调用虚函数时,程序首先查找虚函数指针,然后根据虚函数的位置在虚函数表中查找函数的地址,最终调用该函数。

总结:虚函数是一种非常重要的特性,它实现了多态性。

C++中有两种主要的实现虚函数的方式:虚函数表和虚函数指针。

虚函数表是一种数据结构,它存储了类中所有虚函数的地址。

虚函数指针也是一种指向虚函数表的指针。

虚函数表和虚函数指针都是实现多态性的重要手段。

c++虚继承原理

c++虚继承原理

c++虚继承原理小伙伴,今天咱们来唠唠C++里的虚继承这个超有趣的东西。

你知道吗,在C++的世界里啊,继承就像是家族传承一样。

普通的继承呢,就好比是简单的家族传承,没太多复杂的弯弯绕绕。

但是虚继承啊,那可就是家族传承里的特殊情况啦。

想象一下,我们有这么一个类的家族树。

当没有虚继承的时候,如果有个类从多个父类继承,而这些父类又有共同的祖先类,那就可能会出现问题哦。

比如说,这个共同的祖先类里有个成员变量,那在子类里就可能会有多个副本。

这就好像家族里的传家宝,结果因为不同的传承线路,最后在一个子孙那里有好几个一模一样的传家宝,这多奇怪呀。

这时候,虚继承就闪亮登场啦。

虚继承就像是家族里的一个特殊约定,它说:“咱们得共享这个祖先的东西,可不能弄出好几个副本。

”从实现原理上来说呢,虚继承在对象的内存布局上就做了特殊的安排。

当一个类虚继承另一个类的时候,编译器会在虚继承的子类对象里添加一个指针,这个指针可神奇啦,它就像是一个指向秘密宝藏的小箭头。

这个宝藏就是虚基类子对象的位置。

通过这个指针,不管有多少条继承线路最终汇聚到这个虚基类,大家都能准确地找到那个唯一的虚基类对象。

就好比在一个超级复杂的家族聚会里,大家都知道有个特别重要的老祖宗的东西放在一个神秘的地方。

不管是从爸爸这边的家族线,还是妈妈那边的家族线过来的亲戚,只要按照这个特殊的指针指示的方向,就能找到那个独一无二的老祖宗的传家宝。

而且哦,虚继承在构造函数的调用顺序上也有它的独特之处。

它得先把虚基类的构造函数给调用了,就像家族里先得把老祖宗的事情安排好一样。

这样才能保证在整个对象构建的过程中,虚基类部分是最先被妥善处理的。

再说说这个虚继承在多态里的表现吧。

当我们有虚函数在虚基类里的时候,虚继承就像是一个忠诚的助手,确保在不同的继承路径下,通过指针或者引用调用虚函数的时候,都能准确地找到正确的函数实现。

这就像家族里不管怎么传承,大家都知道按照某种规则去对待家族的传统习俗一样。

关于虚函数的应用(10个例子)

关于虚函数的应用(10个例子)

VbtSon(Father) Farther~Num VbtSon(Uncle) Uncle~NumSon~NumOffset(这个地⽅??) Vftable(GrandFather) GrandFather~Num 关于虚函数的应⽤(10个例⼦)虚函数是C++中⾮常重要的⼀个概念,它最⼤的好处是能够触发动态绑定。

C++中的函数默认不使⽤动态绑定,要触发动态绑定,必须满⾜两个条件:第⼀,只有指定为虚函数的成员函数才能进⾏动态绑定,成员函数默认为⾮虚函数,⾮虚函数不进⾏动态绑定;第⼆,必须通过基类类型的指针或引⽤进⾏函数的调⽤。

具体理解指针或引⽤在使⽤继承层次中某⼀类型的对象时会发⽣什么,本⽂不展开讨论,这两天主要研习了虚函数的具体应⽤这⼀块,⽽它的应⽤⼜⾮常⼴泛,学MFC的应该能够感受到它的强⼤,要说是总结也不⼀定能够总结全,本⼈⽬前也处在studying中,所以⽤10个具体的例⼦来说明。

例⼦是从难到易,看到的朋友如果懂前⾯的可以不⽤看后⾯的。

每⼀个例⼦就是⼀个类,通过类在内存中的布局来形象地分析虚函数究竟是如何运作的。

图表⽰可能抽象⼀点,⼀般带有V开头的表⽰⼀个虚函数表,如果是学过编译原理这门课就很容易看懂,没学过的只要懂虚函数的⼀些机制,耐着性⼦也是没问题的。

每个图⽰都配有相应的代码。

可以对照着代码来看。

1、虚函数继承的复杂例⼦2、菱形继承⽆虚拟继承的情况3、虚拟继承的简单情况4、单⼀普通继承(⽆虚函数)5、单⼀继承(含虚函数)(虚函数表只有⼀个)6、多重继承(不含虚函数)7、多重继承(⼀个含虚函数,⼀个不含虚函数)8、多重继承(两个都含有虚函数)9、纯虚汗继承10、 private 的虚函数1、虚函数继承的复杂例⼦,见下图:见图:左边是这个类的内存布局,右边是继承图⽰。

farther类和Uncle类都是虚拟继承,其内部也都有偏移表,但我觉得这两个表只是内部隐藏的,不在Son的内存布局中表⽰出来,本题Son的内存只有32个字节,如果表⽰出来就不⽌32个了,但是下⾯这个地⽅在内存中显⽰是00 00 00 00,我猜想是不是GrandFather的偏移地址。

第六讲 继承

第六讲  继承

在单个类中,protected和private没有什么区别。但在 继承关系中,基类的private成员不但对应用程序隐藏,甚 至对派生类也隐藏。而基类的保护成员则只对应用程序隐 藏,而对派生类则毫不隐瞒。
一个私有的或保护的派生类不是子类,因 为非公共的派生类不能做基类能做的所有的事
多态性例


单继承派生类的构造例

若派生类没有声明构造函数,根据类的实现机制,派生类对 象创建时,将执行其默认的构造函数。该默认构造函数会首 先调用基类的默认构造函数, 派生类的构造总是由基类的初始化开始的。首先调用基类的 构造函数完成其基类部分的构造,派生类中只需对本类新增 成员进行初始化。类与类之间,你做你的,我做我的,以 接口作沟通。 派生类的构造函数需要给基类的构造函数传递参数(基类中 声明有默认形式的构造函数或未声明构造函数时除外)。 派生类的析构函数以构造函数相反的顺序被调用。
class A { public: void f( ); }; class B { public: void f( ); void g( ); }; class C: public A, piblic B { public: void g( ); void h( ); }; 如果声明:C c1; 则 c1.f( ); 具有二义性 而 c1.g( ); 无二义性(同名隐藏) 解决方法二:同名隐藏 在C 中声明一个同名成员函数f( ) ,f( )再根据需要调用 A::f( ) 或 B::f( )
虚拟继承


同一个基类被间接继承多次的时候,将在派生类 中产生多个基类的副本。为了解决同一基类被多 次继承产生的二义性,在C++语言中,引入了虚 拟继承机制。 虚拟继承和非虚拟继承的定义格式基本相同,区 别在于:在虚拟继承的派生表中增加一个关键字 virtual,表明基类和派生类之间是虚拟继承关系, 格式为: <继承方式> virtual <基类名>, 其中,继承方式和virtual的顺序不重要,可以互换。 虚拟继承中的基类被称为虚基类。

虚函数表和虚基类表

虚函数表和虚基类表

虚函数表和虚基类表任何类型的指针变量都是占⽤4个字节。

实现虚函数需要对象附带⼀些额外信息,以使对象在运⾏时可以确定该调⽤哪个虚函数。

对⼤多数编译器来说,这个额外信息的具体形式是⼀个称为vptr(虚函数表指针)的指针。

vptr指向的是⼀个称为vtbl(虚函数表)的函数指针数组。

每个有虚函数的类都附带有⼀个vtbl。

当对⼀个对象的某个虚函数进⾏请求调⽤时,实际被调⽤的函数是根据指向vtbl的vptr在vtbl⾥找到相应的函数指针来确定的。

编译器会为每个有虚函数的类创建⼀个虚函数表,该虚函数表将被该类的所有对象共享。

类的每个虚成员占据虚函数表中的⼀⾏。

如果类中有N个虚函数,那么其虚函数表将有N*4字节的⼤⼩。

虚函数(Virtual Function)是通过⼀张虚函数表(Virtual Table)来实现的。

简称为V-Table。

在这个表中,主要是⼀个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。

这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当⽤⽗类的指针来操作⼀个⼦类的时候,这张虚函数表就显得由为重要了,它就像⼀个地图⼀样,指明了实际所应该调⽤的函数。

编译器应该是保证虚函数表的指针存在于对象实例中最前⾯的位置(这是为了保证取到虚函数表的有最⾼的性能——如果有多层继承或是多重继承的情况下)。

这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调⽤相应的函数。

在⽅法定义时加上virtual,表⽰此⽅法是虚拟⽅法,可供⼦类覆盖,修改⽗类的执⾏。

所以有虚函数的类,⽣成对象时,对象占⽤的内存要多⼀个指针(4字节),来存放虚函数表指针。

虚基类指针

虚基类指针

虚基类指针虚基类指针是C++中的一个重要概念,用于处理多重继承中的问题。

在多重继承中,如果从不同的基类中派生出的类都需要访问相同的基类成员,则需要使用虚基类来解决菱形继承问题。

虚基类指针的作用是指向虚基类的对象,以便访问虚基类的成员。

虚基类的概念在C++中,多重继承可以使用不同的方式组合基类。

当不同基类中有相同的成员时,编译器会自动合并这些成员,从而产生菱形继承问题。

如果不采取任何措施处理这一问题,会导致程序中出现重复的成员,从而引发undefined behavior虚基类的出现就是为了解决这一问题。

虚基类是被派生类所共享的基类,即被继承多次但在继承体系中仅出现一次的基类。

将虚基类声明为虚拟继承可以保证继承体系只有一个基类的实例,从而避免了重复继承的问题。

虚基类指针的定义虚基类指针是一个指针,它指向继承体系中的虚基类对象。

虚基类指针的定义如下:class Class_name : virtual publicVirtual_base_class_name{ }其中,Virtual_base_class_name为虚基类的名称。

在类的定义中,必须同时使用virtual关键字和public访问修饰符来声明虚基类。

这将告诉编译器,该类从基类中继承的虚函数表是共享的,并且必须被所有派生自该基类的派生类所共享。

虚基类指针的作用虚基类指针是访问虚基类成员的关键。

在派生类中,虚基类指针会自动被添加到对象的开头。

虚基类指针的值为基类对象在派生类中的偏移量。

在使用虚基类指针访问虚基类成员时,编译器会自动调整指针的偏移量,以便访问正确的地址。

例如,假设一个派生类有两个基类A和B,其中A是虚基类,B从A中继承了一些成员。

如果派生类需要访问A 中的成员,则需要使用A的虚基类指针。

在访问虚基类成员时,编译器会查找虚基类指针并自动调整偏移量,以便访问正确的虚基类成员。

否则,可能会出现重复的虚基类成员,从而导致undefined behavior。

虚 函 数

虚 函 数
(5)虽然使用对象名和点运算符的方式也可以调用虚函 数 , 例 如 语 句 “ dl.print();” 可 以 调 用 虚 函 数 derived1∷print(),但是这种调用是在编译时进行的 是静态联编,它没有充分利用虚函数的特性。只有通 过基类指针访问虚函数时才能获得运行时的多态性。
(6)一个虚函数无论被公有继承多少次,它仍然保持其 虚函数的特性。
my_base--------
10 20
从程序运行的结果可以看出,虽然执行语句mp=&mc;后, 指针mp已经指向了对象mc,但是它所调用的函数(mp>show()),仍然是其基类对象的show(),显然这不是我 们所期望的。出现这个问题的原因以及解决方法,我 们将在下一节介绍。在此先说明引入派生类后,使用 对象指针应注意的几个问题:
derive op2; //定义派生类derive的对象op2
ptr=&op1;
//使指针ptr指向对象op1
ptr=&op2;
//错误,不允许将base类指针ptr指
向它的私有派生类//对象op2
//…
}
(2)允许将一个声明为指向基类的指针指向公有派生类
的对象,但是不能将一个声明为指向派生类对象的指 针指向其基类的对象。
(3)声明为指向基类的指针,当其指向公有派生的
对象时,只能用它来直接访问派生类中从基类继承来
的成员,而不能直接访问公有派生类中定义的成员,
例如:
class A { //... public: void print1(); }; class B:public A{ //... public: print2(); };
可见,虚函数同派生类的结合和使C++支持运行时的多 态性,而多态性对面向对象的程序设计是非常重要的, 实现了在基类定义派生类所拥有的通用接口,而在派 生类定义具体的实现方法,即常说的“同一接口,多 种方法”。

详解Virtual table

详解Virtual table

详解Virtual tableC++中的虚函数的作用主要是实现了多态的机制。

关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。

这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。

所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。

比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

关于虚函数的使用方法,我在这里不做过多的阐述。

大家可以看看相关的C++的书籍。

在这篇文章中,我只想从虚函数的实现机制上面为大家一个清晰的剖析。

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。

不利于学习和阅读,所以这是我想写下这篇文章的原因。

也希望大家多给我提意见。

言归正传,让我们一起进入虚函数的世界。

虚函数表对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。

简称为V-Table。

在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。

这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。

在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。

没关系,下面就是实际的例子,相信聪明的你一看就明白了。

假设我们有这样的一个类:class Base {public:virtual void f() { cout << "Base::f" << endl; }virtual void g() { cout << "Base::g" << endl; }virtual void h() { cout << "Base::h" << endl; }};按照上面的说法,我们可以通过Base的实例来得到虚函数表。

关于C++中菱形继承和虚继承的问题总结

关于C++中菱形继承和虚继承的问题总结

关于C++中菱形继承和虚继承的问题总结前⾔菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接⼝。

C++中虽然没有明确说明接⼝这种东西,但是只有纯虚函数的类可以看作Java中的接⼝。

在多重继承中建议使⽤“接⼝”,来避免多重继承中可能出现的各种问题。

本⽂将给⼤家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供⼤家参考学习,话不多说了,来⼀起看看详细的介绍吧。

继承:1. 单继承–⼀个⼦类只有⼀个直接⽗类时称这个继承关系为单继承2. 多继承–⼀个⼦类有两个或以上直接⽗类时称这个继承关系为多继承例如下⾯这两个例⼦:例⼀(单继承):class A{public:int _a;};class B : public A // B是⼦类/派⽣类,公有继承⽗类/基类 A{public:int _b;};class C : public B //C是⼦类/派⽣类,公有继承⽗类/基类 B{public:int _c;};例⼆(多继承):class A{public:int _a;};class B{public:int _b;};class C : public A , public B // ⼦类C同时公有继承⽗类A和⽗类B{public:int _c;};⽤图很形象的表⽰⼀下:但是在使⽤过程中,很容易出现⼀种继承关系叫菱形继承。

就好⽐下⾯这种继承⽅式。

class A{public:int _a;};class B : public A{public:int _b;};class C : public A{public:int _c;};class D : public B, public C{public:int _d;};继承的⽅式简单画出来就是下⾯这样:我们在使⽤过程中会发现以下缺点:1、当我们⽤D类创建出对象d时,可以访问到_a,但是⼀旦编译就会出现错误。

错误说明为: C2385: 对“_a”的访问不明确。

虚函数继承,多态,虚继承(详细讲解)

虚函数继承,多态,虚继承(详细讲解)

虚函数的定义要遵循以下重要规则:1.如果虚函数在基类与派生类中出现,仅仅是名字相同,而形式参数不同,或者是返回类型不同,那么即使加上了virtual关键字,也是不会进行滞后联编的。

2.只有类的成员函数才能说明为虚函数,因为虚函数仅适合用与有继承关系的类对象,所以普通函数不能说明为虚函数。

3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。

4.内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。

即使虚函数在类的内部定义定义,但是在编译的时候系统仍然将它看做是非内联的。

5.构造函数不能是虚函数,因为构造的时候,对象还是一片位定型的空间,只有构造完成后,对象才是具体类的实例。

6.析构函数可以是虚函数,而且通常声名为虚函数。

在编译时就能够确定哪个重载的成员函数被调用的情况被称做先期联编(earlybinding),而在系统能够在运行时,能够根据其类型确定调用哪个重载的成员函数的能力,称为多态性,或叫滞后联编(late binding)//例程3#include <iostream>usingnamespacestd;classVehicle{public:Vehicle(floatspeed,inttotal){Vehicle::speed=speed;Vehicle::total=total;}virtualvoidShowMember()//虚函数{cout<<speed<<"|"<<total<<endl;}protected:floatspeed;inttotal;};classCar:publicVehicle{public:Car(intaird,floatspeed,inttotal):Vehicle(speed,total){Car::aird=aird;}virtualvoidShowMember()//虚函数,在派生类中,由于继承的关系,这里的virtual也可以不加{cout<<speed<<"|"<<total<<"|"<<aird<<endl; }public:intaird;};voidtest(Vehicle &temp){temp.ShowMember();}intmain(){Vehicle a(120,4);Car b(180,110,4);test(a);test(b);cin.get();}运行结果:120|4110|4|180//例程2#include <iostream>usingnamespacestd;classVehicle{public:Vehicle(floatspeed,inttotal){Vehicle::speed=speed;Vehicle::total=total;}voidShowMember(){cout<<speed<<"|"<<total<<endl;}protected:floatspeed;inttotal;};classCar:publicVehicle{public:Car(intaird,floatspeed,inttotal):Vehicle(speed,total)Car::aird=aird;}voidShowMember(){cout<<speed<<"|"<<total<<"|"<<aird<<endl; }protected:intaird;};voidtest(Vehicle &temp){temp.ShowMember();}voidmain(){Vehicle a(120,4);Car b(180,110,4);test(a);test(b);cin.get();运行结果:120|4110|4虚继承是为了在多继承的时候避免引发歧义,比如类A有个就是a,B继承了A,C也继承了A,当D多继承B,C时,就会有歧义产生了,所以要使用虚拟继承避免重复拷贝。

C++继承,虚函数与多态性专题

C++继承,虚函数与多态性专题

本文作者:黄邦勇帅学习本文首先你应熟悉C++中的构造函数,基本的类的声明及怎样初始化类,关于这些问题,请参看本人所作的《C++构造函数,复制构造函数和析构函数》一文,在这篇文章中作了详细的介绍。

本文分两部分即继承和虚函数与多态性,本文第一部分详细讲解了继承时的构造函数和析构函数的问题,父类与子类的同名变量和函数问题,最后介绍了多重继承与虚基类。

本文第二部分重点介绍了虚函数与多态性的问题,因此学习虚函数的基础是继承,因此在学习虚函数前应学好继承。

本文详细易懂,内容全面,是学习C++的不错的资料。

本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。

声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。

主要参考文献:1、C++.Primer.Plus.第五版.中文版[美]Stephen Prata著孙建春韦强译人民邮电出版社2005年5月2、C++.Primer.Plus.第四版.中文版Stanley B.Lippman、Barbara E.Moo著李师贤等译人民邮电出版社2006年3月3、C++.Primer.Plus.第三版.中文版Stanley B.Lippman等著潘爱民张丽译中国电力出版社2002年5月4、C++入门经典第三版[美]Ivor Horton著李予敏译清华大学出版社2006年1月5、C++参考大全第四版[美]Herbert Schidt著周志荣朱德芳于秀山等译电子工业出版社2003年9月6、21天学通第四版C++ [美]Jesse Liberty著康博创作室译人民邮电出版社2002年3月14 继承(基类,父类,超类),派生类,子类一:继承中的访问权限关系。

1.基类,父类,超类是指被继承的类,派生类,子类是指继承于基类的类.2.在C++中使用:冒号表示继承,如class A:public B;表示派生类A从基类B继承而来3.派生类包含基类的所有成员,而且还包括自已特有的成员,派生类和派生类对象访问基类中的成员就像访问自已的成员一样,可以直接使用,不需加任何操作符,但派生类仍然无法访问基类中的私有成员.4.在C++中派生类可以同时从多个基类继承,Java不充许这种多重继承,当继承多个基类时,使用逗号将基类隔开.5.基类访问控制符,class A:public B基类以公有方式被继承,A:private B基类以私有方式被继承,A:protected B基类以受保护方式被继承,如果没有访问控制符则默认为私有继承。

iat和虚函数表

iat和虚函数表

iat和虚函数表
虚函数表(Virtual Function Table,简称vtable)和IAT(Import Address Table)是两种不同的数据结构,但它们在计算机科学中都起着非常重要的作用。

虚函数表是多态实现的基础,当基类与派生类中有同名成员函数时,如果使用基类指针或基类引用操作派生类对象,只能调用基类的同名函数。

如果想要使用基类指针或基类引用派生类中的成员函数,就需要虚函数解决。

IAT是一个表,存储着程序中所有外部函数的地址。

当程序需要调用外部函数时,它可以通过查找IAT来获取正确的函数地址,从而正确地执行函数。

所以,虚函数表和IAT在功能和应用场景上存在显著差异。

如需了解更多信息,建议请教计算机专业人士。

虚函数表

虚函数表

C++ 虚函数表解析:虚函数表对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。

简称为V-Table。

在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其能真实反应实际的函数。

这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。

C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。

这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。

没关系,下面就是实际的例子,相信聪明的你一看就明白了。

假设我们有这样的一个类:class Base {public:virtual void f() { cout << "Base::f" << endl; }virtual void g() { cout << "Base::g" << endl; }virtual void h() { cout << "Base::h" << endl; }};按照上面的说法,我们可以通过Base的实例来得到虚函数表。

下面是实际例程:typedef void(*Fun)(void);Base b;Fun pFun = NULL;cout << "虚函数表地址:" << (int*)(&b) << endl;cout << "虚函数表—第一个函数地址:" << (int*)*(int*)(&b) << endl;// Invoke the first virtual functionpFun = (Fun)*((int*)*(int*)(&b));pFun();实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)虚函数表地址:0012FED4虚函数表—第一个函数地址:0044F148Base::f通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

多重继承下的虚函数表
多态是C++语言中的一项重要的机制,虚函数就是为实现多态而设计的。

多态就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。

这种技术可以让父类的指针有“多种形态”。

而虚函数表在这种多态机制中起了核心调度的作用。

由于是编译器在后台操作,所以它被蒙上了一层神秘的面纱。

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。

简称为VFTable。

在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。

这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,VFTable就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

在C++的标准中提到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置。

这意味着我们通过对象实例的地址得到它的VFTable,然后就可以遍历其中函数指针,并调用相应的函数。

Code 1:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
我们可以通过Base的实例来得到虚函数表。

Code 2:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" <<(int *)*(int*)(&b) << endl;
cout << "虚函数表—第一个函数地址:" << (int*)*((int*)*(int*)(&b)) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
实际运行经果如下:(Windows XP+VC6.0)
虚函数表地址:0x00425030
虚函数表—第一个函数地址:0040100F
Base::f
通过Code 2看到,可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。

如此就可以知道如果要调用Base::g()和Base::h(),其代码如下:
Code 3:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
通过类实例的偏移地址得到函数表地址,具体位置示意图如下:
注意:在上面这个图中,虚函数表的最后多加了一个结点,它是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。

这个结束标志的值在不同的编译器下是不同的。

在WinXP+VC6.0下,这个值是NULL。

下面,将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。

没有覆盖父类的虚函数是毫无意义的。

无虚函数覆盖的一般继承如下所示的一个继承关系:
注意,在这个继承关系中,子类没有重载任何父类的函数。

那么,在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d; 的虚函数表如下:
通过上图可以总结以下几点:
1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

有虚函数覆盖的一般继承如下图所示:
在这个类的设计中,只覆盖了父类的一个函数:f()。

那么,对于派生类的实例,其虚函数表会是下面的一个样子:
通过上图总结以下几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

Code 4:
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。

这样就实现了多态性。

无虚函数覆盖的多重继承,假设有下面这样一个类的继承关系。

注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,如下图所示:
通过上面2图可以总结:
1)每个父类都有自己的虚表。

2)子类的成员函数被放到了第一个父类的表中。

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

有虚函数覆盖的多重继承
下图中,在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:
可以看出,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。

这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。

如下:
Code 5:
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()。

相关文档
最新文档