C++虚函数调用机制初探

合集下载

虚函数的实现机制

虚函数的实现机制

虚函数的实现机制
虚函数(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++虚函数作用及底层原理

c++虚函数作用及底层原理

c++虚函数作用及底层原理C++是一种面向对象的编程语言,并支持多态性的实现。

其中,虚函数是C++中实现多态性的主要机制之一。

虚函数是一种特殊的成员函数,可以在派生类中重写,并通过基类指针或引用的间接方式调用派生类的实现。

虚函数的作用:1. 实现动态绑定:实现多态性的关键是在运行时确定函数的具体实现。

虚函数通过将函数调用的确定延迟到运行时,而不是在编译时确定,从而实现动态绑定。

2. 多态性:允许派生类重写基类的函数,并使用基类指针或引用调用派生类的实现。

这种多态性的实现可以增强代码的灵活性和可重用性。

3. 实现抽象类:虚函数也可以用于实现抽象类,即只声明函数接口而没有实现。

这样,派生类必须实现虚函数才能被实例化。

虚函数的底层原理:虚函数的实现需要两个关键组件:虚函数表(vtable)和虚函数指针(vptr)。

1. 虚函数表:每个包含虚函数的类都有一个虚函数表,其中包含了类中所有虚函数的地址。

虚函数表是一个静态数据结构,只有一个虚函数表,且在编译时生成。

2. 虚函数指针:每个包含虚函数的类的对象都有一个虚函数指针,指向其所属类的虚函数表。

虚函数指针是一个指向虚函数表的指针,指针的值在程序运行时动态确定。

当调用虚函数时,程序首先通过对象的虚函数指针找到其所属类的虚函数表,然后查找相应的虚函数地址,最终调用正确的虚函数实现。

这样,虚函数的实现在运行时动态确定,实现了动态绑定和多态性。

总之,虚函数是C++中实现多态性的主要机制之一,通过虚函数表和虚函数指针的使用,实现了动态绑定和多态性的实现。

虚函数的应用可以增强代码的灵活性和可重用性。

虚函数的实现原理

虚函数的实现原理

虚函数的实现原理虚函数是C++中的一种特殊函数,它允许派生类重写基类的同名函数,并根据对象的实际类型调用相应的函数。

虚函数的实现原理涉及到虚函数表(vtable)和虚函数指针(vpointer)两个重要的概念。

在C++中,每个包含虚函数的类都会生成一个与之对应的虚函数表(vtable)。

虚函数表是一个以函数指针为元素的数组,用于存储类的虚函数地址。

虚函数表中的每个元素都对应着类的一个虚函数,其中存储着该虚函数的地址。

虚函数表通常位于类的内存布局最前面的位置。

当一个类被定义为包含虚函数时,编译器会自动生成一个隐藏的虚函数指针(vpointer)并将它添加到类的内存布局中。

这个虚函数指针被添加到每一个类的对象中,并指向该对象对应的虚函数表。

通过虚函数指针,程序能够在运行时根据对象的实际类型找到正确的虚函数表,并调用相应的虚函数。

当派生类重写基类的虚函数时,它会生成一个新的函数地址并将其存储到自己对应的虚函数表中。

在派生类的虚函数表中,只有被重写的虚函数所对应的表项会被替换为新的函数地址,其它虚函数的表项仍然指向基类的虚函数地址。

这样,当通过派生类的对象调用虚函数时,程序会根据对象的实际类型找到对应的虚函数表,并调用正确的虚函数。

虚函数的实现原理可以通过以下示例代码进行说明:cpp#include <iostream>class Base {public:virtual void print() {std::cout << "Base::print()" << std::endl;}};class Derived : public Base {public:void print() override {std::cout << "Derived::print()" << std::endl;}};int main() {Base* base = new Derived();base->print(); 输出"Derived::print()"delete base;return 0;}在上述代码中,Base类包含一个虚函数print(),而Derived类重写了这个虚函数。

C++中构造函数中调用虚函数的问题

C++中构造函数中调用虚函数的问题
cout << "<< C180.foo this: " << this << " vtadr: " << *(void**)this << end码,输出就会变成:
<< C180.foo this: 0012F7A4 vtadr: CCCCCCCC
3) 如果对象有父类就会先调用父类的构造函数(并依次递归),如果有多个父类(多重继承)会依次对父类的构造函数进行调用,并会适当的调整this指针的位置。在调用完所有的父类的构造函数后,再执行自己的代码。
照上面的分析构造C190时也会调用C180的构造函数,这时在C180构造函数中的第一个foo调用为静态绑定,会调用到C180::foo()函数。第二个foo调用是通过指针调用的,这时多态行为会发生,应该调用的是C190::foo()函数。
17 0042700E mov dword ptr [eax],45C400h
18 00427014 mov eax,dword ptr [ebp-8]
19 00427017 pop edi
20 00427018 pop esi
21 00427019 pop ebx
22 0042701A add esp,0CCh
为此我查了一下C++标准规范。在12.7.3条中有明确的规定。这是一种特例,在这种情况下,即在构造子类时调用父类的构造函数,而父类的构造函数中又调用了虚成员函数,这个虚成员函数即使被子类重写,也不允许发生多态的行为。即,这时必须要调用父类的虚函数,而不子类重写后的虚函数。
我想这样做的原因是因为在调用父类的构造函数时,对象中属于子类部分的成员变量是肯定还没有初始化的,因为子类构造函数中的代码还没有被执行。如果这时允许多态的行为,即通过父类的构造函数调用到了子类的虚函数,而这个虚函数要访问属于子类的数据成员时就有可能出错。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

C++语言中的虚函数研究

C++语言中的虚函数研究

万方数据 万方数据 万方数据C++语言中的虚函数研究作者:徐启丰, 胡勇, 万玉成, XU Qifeng, HU Yong, WANG Yucheng作者单位:徐州空军学院,江苏,徐州,221000刊名:现代电子技术英文刊名:MODERN ELECTRONICS TECHNIQUE年,卷(期):2010,33(4)参考文献(14条)1.Stanley B Lippman;侯捷Inside the C++ Object Model 20012.蓝雯飞;陆际光C++面向对象程序设计中的多态性研究[期刊论文]-计算机工程与应用 2000(08)3.Bjarne Stroustrup;裘宗燕C++的设计与演化 20024.赵红超;方金云;唐志敏C++的动态多态和静态多态[期刊论文]-计算机工程 2005(20)5.蓝雯飞C++中的多态性及其应用 1998(07)6.袁亚丽;肖桂云C++中虚函数的实现技术研究[期刊论文]-河北北方学院学报(自然科学版) 2006(05)7.Scott Mayers More Effective C++ 19968.和力;吴丽贤关于C++虚函数底层实现机制的研究与分析[期刊论文]-计算机工程与设计 2008(10)9.亚鹏关于C++中虚函数的几个问题 2006(02)10.Terrence W Pratt;傅育熙程序设计语言:设计与实现 200111.张昀C++中的多态性研究 2009(02)12.Bjarne Stroustrup The C++ Programming Language 200113.夏承遗;董玉涛;赵德新C++中虚函数的实现机制[期刊论文]-天津理工学院学报 2004(03)14.蓝雯飞C++语言中的面向对象特征探讨[期刊论文]-计算机工程与应用 2000(09)本文链接:/Periodical_xddzjs201004048.aspx。

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形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。

c++ 调用纯虚函数

c++ 调用纯虚函数

c++ 调用纯虚函数在C++的面向对象编程中,纯虚函数是一种特殊的虚函数,它没有函数体,需要子类实现。

如果一个类有纯虚函数,那么这个类就是抽象类,不能被实例化。

在使用纯虚函数时,需要注意以下几点: 1. 纯虚函数的声明方式:在函数原型后面加上“= 0”,例如: ```virtual void func() = 0;```2. 子类必须实现纯虚函数:如果一个类继承了一个抽象类,那么它必须实现抽象类中的所有纯虚函数,否则它也是个抽象类。

3. 抽象类不能被实例化:抽象类只能用作父类,不能被实例化。

下面是一个例子,演示如何在子类中实现纯虚函数的调用:```c++#include <iostream>using namespace std;class Shape {public:virtual void draw() = 0; // 纯虚函数};class Rectangle : public Shape {public:void draw() {cout << 'Rectangle::draw()' << endl;}};int main() {Shape *shape = new Rectangle();shape->draw();delete shape;return 0;}```这个例子中,我们定义了一个抽象类 Shape 和一个子类Rectangle,Rectangle 继承了 Shape 并实现了其中的纯虚函数draw。

在主函数中,我们通过 Shape 类型的指针指向 Rectangle 对象并调用 draw() 函数,输出了“Rectangle::draw()”。

注意,我们不能直接实例化 Shape 对象,因为它是抽象类。

总之,在 C++ 中调用纯虚函数需要在子类中实现,否则子类也会变成抽象类。

通过抽象类可以实现多态性和接口隔离等面向对象编程的重要概念。

c语言虚函数

c语言虚函数

c语言虚函数一、什么是虚函数在C++中,虚函数是指在基类中被声明为虚函数的成员函数,在派生类中重新定义后,会根据对象的实际类型来选择调用哪个版本的函数。

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

二、C语言中是否支持虚函数C语言并不直接支持虚函数,因为它没有面向对象的特性。

但是,我们可以通过结构体和函数指针模拟出类和虚函数的概念。

三、如何实现虚函数1. 定义一个基类结构体,并在其中定义一个指向成员函数的指针作为虚函数。

struct Base {int (*fun)(struct Base *self);};2. 定义一个派生类结构体,并在其中定义一个指向成员函数的指针作为重写后的虚函数。

struct Derived {struct Base base;int (*fun)(struct Derived *self);};3. 实现基类和派生类各自对应的成员函数。

int base_fun(struct Base *self) {printf("Base fun\n");return 0;}int derived_fun(struct Derived *self) {printf("Derived fun\n");return 0;}4. 在程序中创建基类对象和派生类对象,并分别调用它们各自对应的成员函数。

int main() {struct Base base = {base_fun};struct Derived derived = {{base_fun}, derived_fun};base.fun(&base);derived.base.fun(&derived);derived.fun(&derived);return 0;}四、虚函数的优缺点1. 优点虚函数可以实现多态,使得不同类型的对象在调用同一个函数时,可以根据实际类型来选择调用哪个版本的函数。

c++虚函数和动态束定总结

c++虚函数和动态束定总结

c++虚函数和动态束定总结
C++中的虚函数和动态绑定是面向对象编程中非常重要的概念,它们在实现多态性和继承性方面起着关键作用。

让我来从多个角度全面总结一下这两个概念。

首先,我们来看虚函数。

在C++中,通过在基类的成员函数声明前加上关键字`virtual`来定义虚函数。

当派生类继承并重写这个虚函数时,可以实现多态性,即同一个函数调用可以根据对象的实际类型来执行不同的函数体。

这样的设计使得程序更加灵活和可扩展,能够更好地适应变化和复杂的需求。

虚函数的存在使得基类指针可以在运行时指向派生类对象,并调用相应的函数,这就引出了动态绑定的概念。

动态绑定是指在运行时确定调用的函数版本。

当通过基类指针或引用调用虚函数时,程序会在运行时根据对象的实际类型来确定调用的函数版本,而不是在编译时就确定。

这种动态绑定的特性使得程序能够更加灵活地处理对象的多态性,从而实现更加复杂的行为和逻辑。

另外,虚函数和动态绑定也涉及到虚函数表的概念。

在包含虚
函数的类的对象中,会有一个指向虚函数表的指针,虚函数表中存
储了该类的虚函数地址。

当调用虚函数时,程序会根据对象的实际
类型找到相应的虚函数表,并调用正确的函数。

这种机制保证了动
态绑定的实现。

总的来说,虚函数和动态绑定是C++中实现多态性的关键机制,它们使得程序能够更加灵活地处理对象的多态性,实现更加复杂的
行为和逻辑。

同时,理解和正确运用虚函数和动态绑定也是面向对
象编程中的重要技能之一。

希望以上总结能够帮助你更好地理解这
两个概念。

C语言 函数调用原理

C语言 函数调用原理

C语言函数调用原理
函数调用原理是指在C语言程序中,通过函数的调用来实现
代码的重用和模块化的编程方式。

函数调用原理主要涉及栈、函数调用过程和参数传递等方面。

在C语言中,当需要调用一个函数时,首先需要将函数的信
息压入栈中。

栈是一种后进先出(LIFO)的数据结构,用于
存储函数调用时产生的临时数据和函数调用的返回地址。

栈顶指针指向栈中当前可用的位置,当调用函数时,栈顶指针会向下移动,为函数的局部变量和参数分配空间。

当调用函数时,程序会将调用函数的返回地址压入栈中,并跳转到被调用函数的入口地址开始执行。

被调用函数执行完毕后,会通过返回指令将控制权和返回值返回到调用函数。

在函数调用过程中,还涉及参数的传递。

C语言中的参数传递
方式包括值传递、地址传递和指针传递。

对于简单类型的参数,如整型或字符型,一般采用值传递方式,即将参数的值复制一份传递给函数,不影响原始变量的值。

对于复杂类型参数,如数组或结构体,一般采用地址传递方式,即将参数的地址传递给函数,函数可以通过指针访问和修改参数的值。

总结起来,C语言的函数调用原理主要涉及栈、函数调用过程
和参数传递等方面。

通过函数的调用,可以实现代码的重用和模块化,提高程序的可读性和可维护性。

c语言虚函数

c语言虚函数

C语言虚函数中的特定函数简介C语言是一种面向过程的编程语言,并不直接支持面向对象的概念,其中包括了“类”、“对象”、“继承”等概念。

然而,通过使用一些技巧和设计模式,我们可以在C语言中实现类似于面向对象的功能,其中一个重要的概念就是虚函数。

虚函数是一种特殊的函数,它可以在派生类中被重写,从而实现多态。

虚函数的定义、用途和工作方式是C语言中面向对象编程的重要部分,本文将详细介绍这些内容。

虚函数的定义在C语言中,虚函数的定义需要使用函数指针和结构体实现。

我们可以使用函数指针将一个函数地址赋值给一个结构体中的成员变量,从而形成一个具有特定功能的“方法”。

这样,我们就可以通过这个函数指针来调用结构体中的函数,实现类似于面向对象中对象的方法调用的功能。

下面是一个虚函数的定义示例:typedef struct {void (*function_ptr)(void);} VTable;void function1(void) {printf("This is function1\n");}void function2(void) {printf("This is function2\n");}VTable vtable = {.function_ptr = function1};在上述示例中,我们使用typedef定义了一个VTable结构体,其中有一个function_ptr成员变量,它是一个指向函数的指针。

我们定义了两个函数function1和function2,并分别赋值给了vtable中的function_ptr成员变量。

虚函数的用途虚函数的主要用途是实现多态,使不同类型的对象可以调用相同的接口名称,但执行不同的操作。

通过使用虚函数,我们可以在C语言中实现类似于面向对象的继承和多态的功能。

在面向对象的编程中,我们可以定义一个基类(或接口),然后派生出不同的子类,每个子类都可以重写基类的虚函数,以实现它们自己的特定行为。

c++ 纯虚函数定义和使用方法

c++ 纯虚函数定义和使用方法
注意,纯虚函数不能直接实例化,因此基类是一个抽象类,不能创建其对象。
下面是一个完整的示例,展示了纯虚函数的定义和使用方法:
cpp复制代码
#include<iostream>
classBase{
public:
virtualvoidmyFunction()=0;//纯虚函数
};
classDerived:publicBase {
public:
voidmyFunction()override{
std::cout <<"Derived class implementation"<< std::endl;
}
};
intmain(){
Base* ptr =newDerived();
ptr->myFunction();//调用派生类的函数
deleteptr;
return0;
}
在上面unction()函数是纯虚函数。Derived类继承自Base类并实现了myFunction()函数。在main()函数中,通过基类指针ptr调用了派生类的函数,实现了多态性。输出结果为"Derived class implementation"。
cpp复制代码
voidDerived::myFunction(){
//具体的实现代码
}
3.使用纯虚函数:通过基类的指针或引用,可以调用派生类中实现的纯虚函数。这样可以实现多态性,即在运行时确定调用的是哪个派生类的函数。例如:
cpp复制代码
Base* ptr =newDerived();
ptr->myFunction();//调用派生类的函数

C#的虚函数解析机制

C#的虚函数解析机制

C#的虚函数解析机制前⾔ 这篇⽂章出⾃我个⼈对C#虚函数特性的研究和理解,未参考、查阅第三⽅资料,因此很可能存在谬误之处。

我在这⾥只是为了将我的理解呈现给⼤家,也希望⼤家在看到我犯了错误后告诉我。

⽤词约定“⽅法的签名”包括返回类型、⽅法名、参数列表,这三者共同标识了⼀个⽅法。

“声明⽅法”,即指出该⽅法的签名。

“定义⽅法”,则是指定调⽤⽅法时执⾏的代码。

“同名⽅法”是指⽅法的签名相同的两个⽅法。

“重写”⼀个⽅法,意味着⼦类想继承⽗类对⽅法的声明,却想重新定义该⽅法。

单独使⽤“使⽤”⼀词时,包括“显式”或“隐式”两种使⽤⽅式:前者是指在代码中指明,后者是根据语句的上下⽂推断。

某个类的⽅法,包括了在该类中定义的⽅法,以及由继承得到的直接⽗类的⽅法。

注意这条规则的递归性质。

理论部分 在⽗类与⼦类⾥,除了类之间的继承链,还存在⽅法之间的继承链。

C#⾥,在⼀个类中声明⼀个⽅法时,有四个和⽅法的继承性有关的关键字:new、virtual、sealed、override。

virtual表⽰允许⼦类的同名⽅法与其①建⽴继承链。

override表⽰其①与⽗类的同名⽅法之间建⽴了继承链,并隐式使⽤virtual关键字。

new表⽰其切断了其①与⽗类的同名⽅法之间的继承链。

sealed表⽰将其①与⽗类的同名⽅法建⽴继承链(注意这个就是override关键字的特性),并且不允许⼦类的同名⽅法与其建⽴继承链。

在使⽤sealed关键字时,必须同时显式使⽤override关键字。

以及:在定义⽅法时,若不使⽤以上关键字,⽅法就会具有new关键字的特性。

对于这⼀点,如果⽗类中没有同名⽅法,则没有任何影响;如果⽗类中存在⼀个同名⽅法,编译器会给出⼀个警告,询问你是否是想隐藏⽗类的同名⽅法,并推荐你显式地为其指定new关键字。

①其:指代正在进⾏声明的⽅法。

依照上述的说明,在调⽤类上的某个⽅法时,可以为该⽅法构建出⼀个或多个“⽅法继承链”。

虚函数调用顺序

虚函数调用顺序

虚函数调用顺序
在C++中,虚函数是一种非常重要的概念,它使得程序员能够在派生类中重新定义基类的函数。

然而,在使用虚函数时,我们需要了解虚函数的调用顺序,这样才能正确地设计和理解程序的逻辑。

首先,让我们来了解一下虚函数的概念。

虚函数是在基类中使用关键字“virtual”来声明的函数,在派生类中可以重新定义。

在调用虚函数时,程序会根据实际对象的类型来确定调用哪个版本的函数,这被称为动态绑定。

接下来,让我们来看一下虚函数的调用顺序。

当一个对象调用一个虚函数时,程序首先会查看对象的虚函数表(vtable),然后根据对象的类型来确定调用哪个版本的函数。

如果对象是派生类的实例,而且派生类重新定义了虚函数,那么程序会调用派生类的版本;否则,程序会调用基类的版本。

这种机制保证了程序能够正确地调用对象的特定版本的虚函数。

另外,如果一个虚函数在派生类中被重新定义为非虚函数,那么在使用指向派生类对象的基类指针调用该函数时,程序会调用基类的版本,而不是派生类的版本。

因此,在设计程序时,需要格外小心地处理虚函数的定义和调用,以避免出现意外的行为。

总的来说,虚函数的调用顺序是根据对象的类型来确定调用哪个版本的函数。

这种动态绑定的特性使得程序能够正确地调用对象的特定版本的函数,而不需要在编写代码时就确定函数的具体版本。

因此,在使用虚函数时,程序员需要了解虚函数的调用顺序,以确保程序能够正确地调用和处理对象的虚函数。

c++虚函数的调用过程

c++虚函数的调用过程

c++虚函数的调用过程
虚函数的调用过程如下:
1、在编译时,编译器将非虚函数的调用准备好,并且不绑定到具体的函数实现。

2、如果有一个虚函数被执行,编译器会构建一个调用函数,该调用函数将检查对象的虚函数表,以查找相应的函数实现。

这个调用函数会被加载到对象的调用图表中,在调用的时候,执行的是这个调用表的函数。

3、如果在调用的时候,基类的虚函数被实现成派生类的函数,那么编译器会生成一个指向派生类函数的指针,并将这个指针放入到虚函数表里面。

4、一旦这个调用函数被构建好,它会在执行期间调用具体的函数实现。

如果对象是一个基类,那么调用的函数就是基类的实现;如果对象是一个派生类,那么调用的函数就是派生类的实现。

pure virtual function call 原理

pure virtual function call 原理

"Pure virtual function call"(纯虚函数调用)是指在C++中使用纯虚函数(Pure Virtual Function)时可能遇到的错误。

这通常发生在尝试调用一个未被实现的纯虚函数的情况下。

让我们来了解一下这个错误的原理:
在C++中,一个类可以包含纯虚函数。

纯虚函数是在基类中声明的虚函数,但没有提供实际的实现。

这样的函数用关键字`virtual` 修饰,并在声明中用 `= 0` 指定。

例如:
```cpp
class Base {
public:
virtual void pureVirtualFunction() = 0;
};
```
任何从这个基类继承的派生类都必须提供对这个纯虚函数的实际实现,否则派生类也会成为抽象类。

如果在没有提供实现的情况下尝试调用这个纯虚函数,编译器就会产生"pure virtual function call"错误。

这是因为纯虚函数没有具体的实现,无法被直接调用。

这个错误的出现通常表明在
继承层次中有一个类没有提供纯虚函数的实现,导致对象无法被实例化。

解决这个错误的方法是确保所有的纯虚函数在继承链中都有适当的实现。

如果你希望一个类只包含接口而没有实现,可以考虑将这个类设计为纯虚基类,并由派生类提供实际实现。

虚函数的具体实现方式

虚函数的具体实现方式

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

虚函数的实现原理

虚函数的实现原理

虚函数的实现原理
1 虚函数的实现原理
虚函数是C++ 程序设计中的重要特性,可以实现多态、多继承的功能。

虚函数的实现主要依赖虚函数表,通过在编译期计算,动态地建立虚函数表,在运行时利用表中的信息,实现函数的调用。

1.1 程序链接
当程序编译完成和链接之后,每个类就可以创建一个虚函数表,并把与类相关的虚函数地址写入这个表中。

链接器会计算每个类中虚函数地址,并把它们依次存放到虚函数表中,这个表存放在代码段(Code Segment)中。

1.2 表的建立
每一个类的虚函数表的结构定义如下:
struct __vftable
{
void* _vftable;
void* func1;
void* func2;
...
void* funcN;
};
每一个虚函数表都以一个指向自身的指针作为第一个函数,其余
是每个虚函数的地址。

1.3 运行时表的引用
当我们调用虚函数时,编译器会根据类的定义在运行时找到虚函
数表,里面的函数的地址会被调用,从而实现多态的功能。

类的每一个对象除了内存中存储其成员变量,还会存在一个指针
成员变量,这个指针就是指向虚函数表的,该指针叫做“虚函数指针”或“虚表指针”。

当虚函数被调用时,操作系统就会从虚表地址读取相应函数的实
际地址,并把这个函数地址交给CPU执行。

因此,虚函数表的形式使得程序在运行时可以确定函数的真正执
行地址,实现了C++ 所需的多态功能。

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

C++虚函数调用机制初探Introduction to the Mechanism of InvokingVirtual Function in C++电子系00级陈硕chenshuo@ 学习C++语言的最终目标是polymorphism。

——侯捷只用一样东西,不明白它的道理,实在不高明——林语堂排版的同学请注意:请尽量不要修改本文正文(尤其是程序代码)的字型、字号、行距、段距等,特别请不要分栏,另请删除这句话☺。

本文将简要介绍单继承下C++的对象模型和虚函数调用机制。

关于C++对象模型的深入探讨,请看华中科技大学出版的《深度探索C++对象模型》一书。

所谓C++对象模型,指C++类所生成的实体(即对象)在内存中的长相,亦即每个数据成员的位置等等。

请注意,不同的C++ 编译器采用的对象模型是不同的,以下的讨论仅适用于Borland C++ Builder 5.5 Free。

本文所列之代码,适宜在命令行下编译,具体操作步骤请参考侯捷先生的文章:《在console mode中使用C/C++编译器》/article99-10.htm学习C++一个很重要的内容,就是了解类的继承与虚函数的使用,请看下面这个使用虚函数的简单例子。

#0001 //exam_1.cpp#0002 //bcc32 exam_1.cpp 命令行编译,使用Borland C++ Builder 5.5 Free#0003 #include <iostream> //注意,这里没有.h,这样写符合符合C++ Standard#0004 using std::cout; //此为C++ Standard所要求#0005 using std::endl;#0006#0007 class Base {#0008 public:#0009 virtual void foo() { cout << "Base::foo " << ", a = " << a << endl; } #0010 Base():a(123) { } // constructor#0011 protected:#0012 int a;#0013 };#0014#0015 class Derived : public Base#0016 {#0017 public:#0018 virtual void foo() {#0019 cout << "Derived::foo " << ", a = "#0020 << a << ", b = " << b << endl;#0021 }#0022 Derived():b(456) { }; // constructor#0023 private:#0024 int b;#0025 };#0026#0027 int main()#0028 {#0029 Base *pb = new Base;#0030 cout << "Point to a Base object: \n";#0031 pb->foo();#0032 delete pb;#0033#0034 pb = new Derived; //注意这里pb的型别(Type)为Base*#0035 cout << "\nPoint to a Derived object: \n";#0036 pb->foo();#0037 delete pb;#0038 }运行结果:Point to a Base object:Base::foo , a = 123Point to a Derived object:Derived::foo , a = 123, b = 456运行结果符合预期,如果你问:为什么31行和36行两处的pb->foo();会有不同的执行结果,老师会告诉你,因为它们实际指向不同的对象,前一次pb指向一个Base对象,后一次pb指向一个Dericed对象,这是后期联编(或者叫“迟绑定”)的结果;如果你还要问:后期联编是怎么实现的,效率如何,请继续阅读本文。

为了探个究竟,我们研究一下两处pb->foo();所生成的汇编代码。

在命令行执行bcc32 –S exam_1.cpp将exam_1.cpp文件编译为exam_1.asm,打开这个文件,两处pb->foo();生成的汇编代码跃入眼帘:; 这里 ebx == pb; 第一处; pb->foo();; 生成的汇编指令push ebxmov eax,dword ptr [ebx]call dword ptr [eax]pop ecx//此处省略若干行; 这里 ebx == pb; 第二处; pb->foo();; 生成的汇编指令push ebxmov eax,dword ptr [ebx]call dword ptr [eax]pop ecx两处pb->foo();生成的汇编代码代码并无二致,但因两处pb(即EBX)的值不同,以致有不同的输出。

还可以看出,pb被当作函数的第一个参数传入(push ebx),也就是说每个成员函数的第一个参数都是this指针。

我把以上汇编代码翻译为C++ 伪代码,如下://请注意,以下并非合法之C++代码ppf为指向“函数指针”的指针(Pointer to Pointer to Function)ppf = *((int*)pb); //按某种方式提领(dereferencing)指针pb,获得ppf的值pf = *ppf; //pf为函数指针pf(pb); //调用pf指向的函数, 自动增加pb为函数的第一个参数可见Base对象的第一个数据成员是一个指向“函数指针”的指针,我们称之为vptr;vptr(即ppf)所指之处,有一个表,称vtbl(虚函数表);这个表的每个元素,都是函数指针,表中第一个(或者说第0个)函数指针指向Base::foo()。

我以图形来说明vptr与vtbl的关系,箭头表示指针的指向。

在更实际的例子中,vtbl应不止一项。

为了理解C++的对象模型,我们可以模拟C++的虚函数调用机制,自己手工调用虚函数,请看例子:#0001 //exam_2.cpp#0002 //bcc32 exam_1.cpp 命令行编译,使用Borland C++ Builder Free 5.5#0003 #include <iostream>#0004 using std::cout;#0005 using std::endl;#0006#0007 class Base {#0008 public:#0009 virtual void zuu() { cout << "Base::zuu" << endl; }#0010 virtual void foo() { cout << "Base::foo, a = "#0011 << a << endl; }#0012 virtual void gaa(int c) {#0013 cout << "Base::gaa, a = " << a#0014 << ", c = " << c << endl;#0015 }#0016 Base():a(123) { };#0017 protected:#0018 int a;#0019 };#0020#0021 class Derived : public Base#0022 {#0023 public: //这里重载了基类三个虚函数中的两个#0024 virtual void zuu(){ cout << "Derived::zuu" << endl; }#0025 virtual void foo() {#0026 cout << "Derived::foo, a = " << a#0027 << ", b = " << b << endl;#0028 }#0029 Derived():b(456) { };#0030 private:#0031 int b;#0032 };#0033#0034 void mycall_noPara(void *_this, int n);#0035 void mycall_int(void *_this, int n, int para);#0036#0037 int main()#0038 {#0039 Base *pb = new Derived;#0040 cout << "Derived object: \n";#0041 pb->zuu(); //实际调用 Derived::zuu()#0042 pb->foo(); //实际调用 Derived::foo()#0043 pb->gaa(100); //实际调用 Base::gaa()#0044 cout << "\nManually invoke the virtual functions : \n" ;#0045 mycall_noPara(pb, 0); //调用第0个虚函数, 实际为Derived::zuu() #0046 mycall_noPara(pb, 1); //调用第1个虚函数, 实际为Derived::foo() #0047 mycall_int(pb, 2, 200); //调用第2个虚函数,注意函数参数#0048 //以上这行实际调用Base::gaa(int)#0049 delete pb;#0050#0051 pb = new Base;#0052 cout << "\n\nBase object: \n";#0053 pb->zuu(); //实际调用 Base::zuu()#0054 pb->foo(); //实际调用 Base::foo()#0055 pb->gaa(300); //实际调用 Base::gaa()#0056 cout << "\nManually invoke the virtual functions : \n" ;#0057 mycall_noPara(pb, 0); //调用第0个虚函数, 实际为Base::zuu()#0058 mycall_noPara(pb, 1); //调用第1个虚函数, 实际为Base::foo()#0059 mycall_int(pb, 2, 400); //调用第2个虚函数,注意函数参数#0060 //以上这行实际调用Base::gaa(int)#0061 delete pb;#0062 }#0063#0064 void mycall_noPara(void *_this, int n)#0065 {#0066 typedef void (*VTBL)(); //函数指针#0067 typedef VTBL* VPTR; //指向“函数指针”的指针#0068 VPTR vptr = *(VPTR*)_this; // 由_this取得vptr的值#0069 VTBL vtbl_n = vptr[n]; // 取得第n个虚函数的入口地址#0070#0071 typedef void (*PFV)(void*);#0072 // 指向“形参为void*的函数”的指针#0073 PFV pf = (PFV)vtbl_n; // 转换函数原型#0074 pf(_this); // 调用该函数,参数为this指针#0075 }#0076#0077 void mycall_int(void *_this, int n, int para)#0078 {#0079 typedef void (*VTBL)();#0080 typedef VTBL* VPTR;#0081 VPTR vptr = *(VPTR*)_this; // 由_this取得vptr的值#0082 VTBL vtbl_n = vptr[n]; // 取得第n个虚函数的入口地址#0083#0084 typedef void (*PFVI)(void*, int);#0085 // 指向“形参为(void*, int)的函数”的指针#0086 PFVI pf = (PFVI)vtbl_n; // 转换函数原型#0087 pf(_this, para); // 调用该函数,参数为this指针与para#0088 }64到88行的代码是前述伪代码的“合法化”版本,其中运用了大量型别转换技巧,请不要太专注于此。

相关文档
最新文档