C++基于虚函数的多态分析

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

本文只是讨论运行时多态(虚函数)

关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,(怎么个多种形态,就是父类型的指针,有时候可以解引用而调父类方法(当此方法不是虚函数),有时候可以调到子类方法(虚函数价值体现)),这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。


我们先来讨论一下类有无虚函数对于的实例大小有什么影响来引出虚函数表


#include<iostream>
using namespace std;


class Empty{
};


class NoVirtualBase
{
public:
    NoVirtualBase() {};
    ~NoVirtualBase() { cout << "Output from the destructor of class NoVirtualBase!" << endl; };
    void DoSomething() { cout << "no virtual Do something in class NoVirtualBase!" << endl; };
private:
    int a;
};


class VirtualBase
{
public:
    VirtualBase() {};
    virtual ~VirtualBase() { cout << "Output from the destructor of class VirtualBase" << endl; };
    virtual void DoSomething() { cout << "  virtual Dosomething in class VirtualBase" << endl; };
private:
    int a;
};


class PurlBase
{
public:
    PurlBase() {};
    virtual void DoSomething()=0;


private:
    int a;
};


class Derive : public VirtualBase
{
public:
    Derive() {};
    ~Derive() { cout << "Output from the destructor of class Derive!" << endl; };
    virtual void DoSomething() { cout << "Do something in class Derive!" << endl; };//加不加上virtual关键字都没问题,不加也是一个对>基类doSomething的覆盖,只是加上代码更清晰明了。
};


int main()
{
    //check sizeof each class 
    cout << "sizeof(Empty)=" << sizeof(Empty) <<endl;
    cout << "sizeof(NoVirtualBase)=" << sizeof(NoVirtualBase) <<endl;
    cout << "sizeof(VirtualBase)=" << sizeof(VirtualBase) <<endl;
    cout << "sizeof(PurlBase)=" << sizeof(PurlBase) <<endl;
    return 0;
}


1,为什么sizeof(Empty)=1?
类的实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在 内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了 独一无二的地址了。所以空类的sizeof为1,对于不同的编译器也可能会是4。


2,为什么siozeof(NoVirtualBase)=4?
成员函数也是函数,函数都是有地址的,所以函数是占用内存空间的,但函数由于是多对象共享的,所以函数不占用对象的内存地址,是放在代码段的;
那么static 数据成员呢?具有相似性,他也属于类的,所以sizeof时也要去掉这个的计算,最后只有普通数据成员才能计算在内,等于4


3,sizeof(VirtualBase) =8
为什么等于8,一个int 4字节,加一个指向虚表(V-TABLE)的指针VPTR,不管是一个虚函数还是多个,只有一个vptr,只指向一个v-table,所以算作一个指针4字节。


4,sizeof(PurlBase)=8
纯虚函数的类也有一个虚表,但是这个类不能实例化。此为抽象类。


编译器对每个包含虚函数的类创建一个表或多个(多继承),称为V TA B L E。每个对象都是都有一个或多个VPTR,指向这个虚表。在V TA B L E中,编译器放置特定类的虚函数地址。通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使动态联编发生。为每个类设置V TA B L E、初始化V P T R、为虚函数调用插入代码,所有这些都是自动发生的(VTPR的初始化是发生在构造函数时,destroy在析构。所以万不用到malloc一个含有虚函数的对象,而要用到new,其实有很多说法是:vtpr的初始化是编译器所关心的,实现的,不一定放在构造函数里面。大部分的编译器实现是放在构造函数后面,其它代码之前,众说纷纭,C++也没有规范。),所以我们不必担心这些。利用虚函数,这个对象的合适的函数就能被调用,哪怕在编译器还不知道这个对象 的特定类型的情况下。(《C++编程思想》)

在任何类中不存在显示的类型信息,可对象中必须存放类信息,否则类型不可能在运行时建立。那这个类信息是什么呢?我们来看下面几个类:

class no_virtual
{
public:
void fun1() const{}
int fun2() const { return a; }
private:
int a;
}

class one_virtual
{
public:
virtual void fun1() const{}
int fun2() const { return a; }
private:
int a;
}

class two_virtual
{
public:
virtual void fun1() const{}
virtual int fun2() const { return a; }
private:
int a;
}


以上三个类中:
no_virtual没有虚函数,sizeof(no_virtual)=4,类no_virtual的长度就是其成员变量整型a的长度;
one_virtual有一个虚函数,sizeof(one_virtual)=8;
two_virtual 有两个虚函数,sizeof(two_virtual)=8; 有一个虚函数和两个虚函数的类的长度没有区别,其实它们的长度就是no_virtual的长度加一个void指针的长度,它反映出,如果有一个或多个虚函 数,编译器在这个结构中插入一个指针( V P T R)。在one_virtual 和two_virtual之间没有区别。这是因为V P T R指向一个存放地址的表,只需要一个指针,因为所有虚函数地址都包含在这个表中。

这个VPTR就可以看作类的类型信息。

那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的。先看下面两个类:
class base
{
public:
void bfun(){}
    virtual void vfun1(){}
virtual int vfun2(){}

private:
int a;
}


class derived : public base
{
public:
void dfun(){}
    virtual void vfun1(){}
virtual int vfun3(){}

private:
int b;
}


两个类VPTR指向的虚函数表(VTABLE)分别如下:
base类
——————
VPTR——> |&base::vfun1 |
——————
|&base::vfun2 |
——————

derived类
———————
VPTR——> |&derived::vfun1 |
———————
|&base::vfun2    |
———————
|&derived::vfun3 |
———————
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个VTABLE,如上图所示。在这个表中,编译器放置了在这个类中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义,编译器就使用基类的这个虚函数地址。(在derived的VTABLE中,vfun2的入口就是这种情况。)然后编译器在这个类中放置VPTR。当使用简单继承时,对于每个对象只有一个VPTR。VPTR必须被初始化为指向相应的VTABLE,这在构造函数中发生。
一旦VPTR被初始化为指向相应的VTABLE,对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。

VPTR常常位于对象的开头,编译器能很容易地取到VPTR的值,从而确定VTABLE的位置。VPTR总指向VTABLE的开始地址,所有基类和它的子类的虚函数地址(子类自己定义的虚函数除外)在VTABLE中存储的位置总是相同的,如上面base类和derived类的VTABLE中 vfun1和vfun2的地址总是按相同的顺序存储。编译器知道vfun1位于VPTR处,vfun2位于VPTR+1处,因此在用基类指针调用虚函数时,编译器首先获取指针指向对象的类型信息(VPTR),然后就去调用虚函数。如一个base类指针pBase指向了一个derived对象,那 pBase->vfun2()被编译器翻译为 VPTR+1 的调用,因为虚函数vfun2的地址在VTABLE中位于索引为1的位置上。同理,pBase->vfun3()被编译器翻译为VPTR+2的调用。这就是所谓的晚绑定。

我们来看一下虚函数调用的汇编代码,以加深理解。
void test(base* pBase)
{
pBase->vfun2();
}

int main(int argc, char* argv[])
{
derived td;
test(&td);
return 0;
}
 

derived td;编译生成的汇编代码如下:
mov DWORD PTR _td$[esp+24], OFFSET FLAT:??_7derived@@6B@ ; derived::`vftable'
由编译器的注释可知,此时PTR _td$[esp+24]中存储的就是derived类的VTABLE地址。

test(&td);编译生成的汇编代码如下:
lea eax, DWORD PTR _td$[esp+24]   
mov DWORD PTR __$EHRec$[esp+32], 0
push eax
call ?test@@YAXPAVbase@@@Z   ; test 
调用test函数时完成了如下工作:取对象td的地址,将其压栈,然后调用test。
pBase->vfun2();编译生成的汇编代码如下:
mov ecx, DWORD PTR _pBase$[esp-4]
mov eax, DWORD PTR [ecx]
jmp DWORD PTR [eax+4]

首先从栈中取出pBase指针指向的对象地址赋给ecx,然后取对象开头的指针变量中的地址赋给eax,此时eax的值即为VPTR的值,也就是 VTABLE的地址。最后就是调用虚函数了,由于vfun2位于VTABLE的第二个位置,相当于 VPTR+1,每个函数指针是4个字节长,所以最后的调用被编译器翻译为 jmp DWORD PTR [eax+4]。如果是调用pBase->vfun1(),这句就该被编译为jmp DWORD PTR [eax]。

虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。编译器对每个包含虚函数的类创建一个表(称为V TA B L E), 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。一个类的虚函数表是静态的,也就是说对这个类的每个实例,他的虚函数表的是固定的,不会为每个实例生成一个相应的虚函数表。虚函数表就像我们上面讨论的,它在代码段,归属于类,而非对象,指向虚表的指针才在实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。在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 function

pFun = (Fun)*((int*)*(int*)(&b));//这个才是第一个虚函数的地址

pFun();

实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

虚函数表地址:0012FED4

虚函数表 — 第一个函数地址:0044F148

Base::f

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

(Fun)*((int*)*(int*)(&b)+0); // Base::f()

(Fun)*((int*)*(int*)(&b)+1); // Base::g()

(Fun)*((int*)*(int*)(&b)+2); // Base::h()

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

对于实例:Derive d; 的虚函数表如下:

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

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

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

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

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

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

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

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()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

Base1 *b1 = new Derive();

b1->f1(); //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

如:

class Base {

private:

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

};

class Derive : public Base{

};

typedef void(*Fun)(void);

void main() {

Derive d;

Fun pFun = (Fun)*((int*)*(int*)(&d)+0);

pFun();

}

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。


参考资料:

http://hi.baidu.com/marktian/blog/item/9f05591397505ad9f6039e0f.html

http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html


C++分析——多态虚函数 virtual C 虚函数的继承关系 基类Test_virtual_A 派生类Test_virtual_B 派生类Test_virtual_C 实现函数 Test_virtual_A类自己调用 Test_virtual_A类派生Test_virtual_B类 Test_virtual_A类派生Test_virtual_C类 Test_virtual_B类派生Test_virtual_C类 析构虚函数虚函数 带有 阅读详情

相关推荐

C/C++—— 在构造函数中调用虚函数能实现多态吗(Vptr指针初始化的过程分析

问题引入:比如:如果我们想在父类的构造函数中调用虚函数,当定义子类对象的时候,父类的构造函数中的虚函数执行的是子类中的函数。

Be The Best! 2866

详解C++虚函数虚函数

虚函数表:存储虚函数指针。虚函数表指针:每个对象通过vptr访问虚函数表。动态绑定:运行时通过vptr和虚函数表确定调用哪个函数。这种机制使得C++能够在运行时根据对象类型调用正确的函数,实现多态

qq_56921730的博客 434

C++之关于虚函数多态分析和补充

一 二 三 四

小勇博客 363

C++虚函数机制

1.1      简介编译器对每个包含虚函数的类创建一个表(称为VTABLE)。在VTABLE中,编译器放置特定类的虚函数地址。在每个带有虚函数的类中,编译器秘密地置一指针,称为vpointer(缩写为VPTR),指向这个对象的VTABLE。通过基类指针做虚函数调用时(也就是做多态调用时),编译器静态地插入取得这个VPTR,并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑

starryheavens的专栏 1406

C++逆向分析--虚函数多态的前置)

真正调用的其实是call eax这句汇编代码,我们看到他的硬编码是FF D0。说明他是个间接调用。将this指针的第一项放入eax,又将eax的第一项复制给edx,此时将this指针给ecx(如果是linux平台g++编译this指针是赋值给rdi的 _x64情况下)最后将edx的第一项赋值给eax。我们先来看一个例子,这次试验用的是windows平台下的VS编译器不同编译器的细节是不一样的原理大差不差。现在我们知道虚函数,如果是通过对象去调用,和普通成员方法没什么区别,但是如果通过指针或者引用去调用。

qq_30528603的博客 2676

C++多态实现的核心基石:纯虚函数详解(含内存布局分析

掌握C++多态设计的关键,本文深入解析C++虚函数的实现机制,涵盖抽象类定义、动态绑定原理与内存布局分析,揭示接口与实现分离的核心优势。适用于框架设计与系统解耦,值得收藏。

PixelIsle的博客 437

c++多态,继承,抽象类,虚函数表,虚函数等题目+分析

c++多态,继承,抽象类,虚函数表,虚函数等题目+分析

m0_74206992的博客 1251

C++ 多态虚函数实现原理分析

C++ 多态 多态C++中表现为,通过基类指针指向其派生类对象,用该指针调用基类中声明的虚函数编译器可以自动根据该对象类别来确定实际调用的函数。可以通过以下例子说明: //demo1 #include using namespace std; class A { public: A(){} void say(){cout<<"A\n";} }; class B:pub

Coffee 488

C++深度解析 C++对象模型分析(下) --- 继承对象模型,多态对象模型,虚函数表,用C语言实现多态(50)

C++深度解析 C++对象模型分析(下) --- 继承对象模型,多态对象模型,虚函数表,用C语言实现多态(50)       继承对象模型 在C++编译器的内部类可以理解为结构体。 子类是由 父类成员 叠加 子类新成员 得到的。 代码如下:继承对象模型初探 #include &lt;iostream&gt; #include &lt;string&gt; using names...

Tim的嵌入式之旅 436

C++多态分析和与Linux内核中的多态性实现的共性和差异比较

其实,在Inside The C++ Object Model(中文版名称是深度探索C++对象模型)的4.2节,已经明确给出:“在C++中,virtual funcdtions(可经由其class object被调用)可以在编译时期获得,此外,这一组地址是固定不变的,执行期不可能新增或替换之,由于程序执行时,表格的大小和内容不会改变,所以其建构和存取皆可以由编译器完全掌握,不需要执行期的任何介入”拜编译器所赐,CPP实现多态机制几乎是透明的,程序员几乎不用做什么。

tugouxp的专栏 2714

c++虚函数多态实例分析

1 #include <iostream> 2 #include <complex> 3 using namespace std; 4 5 class Base 6 { 7 public: 8 Base() {cout<<"Base-ctor"<<endl;}...

weixin_34149796的博客 110

C++多态&&虚函数原理及应用详细讲解与案例分析

本文系统介绍了C++多态的实现机制,重点阐述了虚函数的工作原理和应用场景。主要内容包括:1. 多态的基本概念,区分静态多态(函数重载)和动态多态虚函数);2. 虚函数表的实现原理,通过虚表指针实现运行时动态绑定;3. 虚函数重写与覆盖的区别,以及基类中虚函数和非虚函数的调用规则;4. 纯虚函数和抽象类的特性,虚析构函数的重要性;5. 多重继承中的虚函数处理方式。文章还通过计算器案例展示了多态的实际应用,并详细分析虚函数的优缺点及使用注意事项,为深入理解C++面向对象编程提供了全面指导。

zxy_ZXY123的博客 1284

java基类shape中虚函数area_详细分析C++ 多态虚函数

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

weixin_28884811的博客 1242

C++虚函数多态性的实现与分析+虚继承的实现与分析

C++在继承中虚函数多态性的实现与分析

cjc211322的专栏 1563

C++继承与多态五:继承多态虚函数案例深入分析

我们这里来看几个问题来更加深入了解继承多态虚函数。 题目一:猫狗叫声问题 //动物基类 泛指 类-》抽象一个实体的类型 class Animal { public: Animal(string name):_name(name){} //纯虚函数 virtual void bark() = 0; protected: string _name; }; //动物实体类 class C...

Rain的博客 497

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

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。 下面的实例中,基类 Shape 被派生为两个类,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

122

C++多态分析虚函数

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

holmeswu的专栏 343

C++中的多态

前言 我认为多态是因为有了虚函数,通过继承,派生类可以重写虚函数,才有了C++多态。所以要了解多态的原理,需要重点了解虚函数和虚基表。 一.多态的概念

weixin_57023347的博客 1万+

C/C++逆向:虚函数逆向分析

Virtual Function)是C++中实现的一种机制,它允许在运行时通过基类的指针或引用调用派生类中的函数,而不是基类中的版本。虚函数通常与和结合使用。通过在基类中使用virtual关键字声明函数,允许派生类重写该函数。当通过基类指针或引用调用时,根据对象的实际类型决定调用哪个版本的函数;它依赖虚函数表(vtable)实现,用于动态绑定,常用于多态场景下的接口设计和扩展。每个包含虚函数的类都有一张虚函数表:如果类没有派生类,则虚函数表存储类自身的虚函数地址。

WolvenSec的博客 1697
上一篇: IPTV时移实现
下一篇: VC6 Activex控件开发
cap_77
博客等级 码龄15年 2粉丝 10原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值