第2章 C++相关知识(三)

2.21 虚函数与纯虚函数

在 C++ 中,虚函数纯虚函数是实现多态的关键。虚函数允许通过基类指针或引用调用派生类中重写的函数,纯虚函数则用于定义接口,强制派生类提供特定的实现。

虚函数(Virtual Function)

虚函数是基类中声明的函数,允许派生类重写该函数。通过基类的指针或引用调用时,实际执行的是派生类的版本。虚函数实现了动态多态,即在运行时,根据对象的实际类型调用对应的函数。

语法:

Shape 类定义了虚函数 draw()

Circle 和 Square 类重写了 draw() 函数。

基类指针 shape1 shape2 分别指向 Circle 和 Square 对象,并通过指针调用 draw() 函数,实际调用的是派生类的重写函数。

纯虚函数(Pure Virtual Function)

纯虚函数是没有函数体的虚函数,其声明中包含 = 0,表示该函数是纯虚函数。纯虚函数用于在基类中定义接口,要求派生类提供具体实现。

语法:

纯虚函数没有函数体,因此不能直接调用。在基类中声明纯虚函数后,基类变成抽象类,抽象类不能实例化对象,只能作为基类供派生类继承。

抽象类和派生类

  • 包含纯虚函数的类是抽象类,抽象类不能被实例化。
  • 派生类必须重写所有纯虚函数,才能实例化对象。如果派生类没有重写所有纯虚函数,派生类仍然是抽象类。

Shape 类是一个抽象类,因为它包含纯虚函数 draw()

Circle 和 Square 类重写了 draw() 函数。

不能直接实例化 Shape 类对象,因为它包含纯虚函数。

在 main() 中,创建了 Circle Square 类的对象,并通过基类指针调用重写的 draw() 函数。

总结:

  1. 虚函数允许基类指针调用派生类的函数,实现在运行时动态选择调用的函数,提供了多态性
  2. 纯虚函数用于定义接口,要求派生类实现,基类成为抽象类,不能实例化。
  3. 抽象类是包含至少一个纯虚函数的类,它不能实例化对象,只能作为基类供派生类继承。
  4. 纯虚函数的作用是为派生类提供统一的接口规范,强制派生类实现特定的行为。

2.22 虚函数表(VTable)与虚函数指针(VPtr)

在 C++ 中,为了支持运行时的多态性,编译器通过虚函数表(VTable)和虚函数指针(VPtr)来实现虚函数的调用。以下是这些概念的详细解释:

虚函数表(VTable)

虚函数表是一个类的内部结构,它存储了类中所有虚函数的地址。对于每个包含虚函数的类,编译器会为它生成一个虚函数表。虚函数表本质上是一个指针数组,每个元素是指向类中虚函数实现的指针。

每个类:对于每个包含虚函数的类,编译器会为其生成一个虚函数表。

存储函数地址:虚函数表中的每个元素指向相应虚函数的实现代码。

与对象类型相关:每个类的虚函数表是特定于该类的,因此不同类的虚函数表是不同的。

虚函数指针(VPtr)

每个对象(对于包含虚函数的类)会有一个额外的指针,称为虚函数指针(VPtr),它指向该对象所属的虚函数表。通过虚函数指针,程序能够动态查找和调用正确的虚函数。

VPtr存在于对象内部:VPtr存在于对象的内存布局中,指向该对象的虚函数表。

动态调用:当通过基类指针调用虚函数时,编译器会利用VPtr查找虚函数表,然后根据表中的函数地址来调用对应的虚函数。

如何工作:

编译时:编译器为每个类生成虚函数表,存储类中所有虚函数的地址。

运行时:当创建一个对象时,该对象的虚函数指针(VPtr)会指向类的虚函数表。

当通过基类指针或引用调用虚函数时,程序首先通过VPtr查找虚函数表,找到正确的函数地址,然后调用该虚函数。

虚函数表的创建:

基类 Animal 定义了一个虚函数 sound()

派生类 Dog Cat 重写了 sound() 函数

编译器为 AnimalDog Cat 类各自生成了一个虚函数表,分别存储对应类的 sound() 函数地址。

虚函数指针:

当通过基类指针(animal1 animal2)调用 sound() 时,程序会使用虚函数指针(VPtr)来查找对象的虚函数表。

对于 animal1,虚函数指针会指向 Dog 类的虚函数表,因此调用的是 Dog 类中的 sound() 函数。

对于 animal2,虚函数指针会指向 Cat 类的虚函数表,因此调用的是 Cat 类中的 sound() 函数。

多态:

通过基类指针调用虚函数,实现了动态多态。具体调用哪个版本的虚函数(Dog 或 Cat)取决于对象的实际类型。

总结:

虚函数表(VTable):每个包含虚函数的类都有一个虚函数表,存储类中所有虚函数的地址。

虚函数指针(VPtr):每个对象会有一个虚函数指针,指向该对象所属类的虚函数表。

运行时多态:通过基类指针或引用调用虚函数时,程序使用虚函数指针(VPtr)查找虚函数表,从而动态调用正确的虚函数。

虚函数表和虚函数指针的作用:使得通过基类指针可以实现对派生类重写函数的调用,从而实现了多态性,提升了程序的灵活性和扩展性。

2.23 虚函数可以内联吗?

在 C++ 中,虚函数和内联函数是两种不同的优化技术,它们有不同的工作原理。

虚函数的工作原理

虚函数在运行时通过虚函数表(VTable)进行动态绑定。每个包含虚函数的类会有一个虚函数表,该表存储了虚函数的地址。通过基类指针或引用调用虚函数时,编译器会使用虚函数表来查找实际的函数地址,并在运行时调用正确的虚函数。

  • 虚函数表(VTable):每个类有一个虚函数表,表中存储虚函数的地址。
  • 虚函数指针(VPtr):每个对象有一个指向虚函数表的指针(VPtr),用来访问虚函数表。

由于虚函数的调用依赖于运行时的虚函数表,编译器无法在编译时确定调用哪个具体的函数。这就导致虚函数调用是在运行时动态决定的。

内联函数的工作原理

内联函数(Inline Function)是由编译器在编译阶段将函数调用的代码直接插入到调用处的函数。这种机制可以减少函数调用的开销,提高性能。内联函数要求编译器能够在编译时确定函数的实现,因此只能在编译期间进行替换。

  • 内联函数:通常用inline关键字声明,编译器在编译时将函数体直接插入到调用点,从而消除了函数调用的开销。

虚函数和内联函数的冲突

  1. 动态绑定:虚函数调用的决定是由运行时的虚函数表来执行的,而内联函数要求在编译时知道函数的实现。由于虚函数的调用在编译期无法确定具体的函数,因此无法进行内联优化。
  2. 运行时调用 vs. 编译时替换:虚函数的调用发生在运行时,通过虚函数表查找函数地址。而内联函数是通过在编译时替换函数调用来消除调用开销,这两者的机制是不兼容的。

因此,虚函数不能直接被标记为内联函数,因为编译器无法在编译时确定具体调用哪个版本的虚函数,而内联要求在编译时已经确定函数体。

结论:虚函数不能是内联函数

由于虚函数的调用需要动态绑定,编译器无法在编译时确定函数体,因此无法在编译时进行内联优化。因此,虚函

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

作者简介:仅用大半年时间0基础天坑急转嵌入式开发,逆袭成功拿下华为、vivo、小米等15个offer,面试经验60+,收藏20+面经,分享自己的求职历程与学习心得。 专栏内容:最新求职与学习经验,详细讲解了嵌入式开发的学习路径、项目经验分享、简历优化技巧、面试心得及实习经验,从测评,笔试,技术面,HR面,AI面,主管面,谈薪一站式服务,助你突破技术瓶颈、打破信息差,争取更多大厂offer。

全部评论
点赞 回复 分享
发布于 昨天 15:03 浙江
浙大爷牛的
点赞 回复 分享
发布于 昨天 15:44 浙江
VTable是如何生成的
点赞 回复 分享
发布于 昨天 15:51 浙江
构造函数能是虚函数吗
点赞 回复 分享
发布于 昨天 15:51 浙江

相关推荐

评论
6
3
分享

创作者周榜

更多
牛客网
牛客企业服务