完整教程:C++ 抽象类与多态原理深度解析:从纯虚函数到虚表机制(附高频面试题)

news/2025/12/8 11:50:32/文章来源:https://www.cnblogs.com/tlnshuju/p/19320891

完整教程:C++ 抽象类与多态原理深度解析:从纯虚函数到虚表机制(附高频面试题)

2025-12-08 11:45  tlnshuju  阅读(0)  评论(0)    收藏  举报

在这里插入图片描述

草莓熊Lotso:个人主页

❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》

✨生活是默默的坚持,毅力是永久的享受!

博主简介:

在这里插入图片描述


文章目录

  • 前言:
  • 一. 纯虚函数与抽象类:强制接口规范的“契约”
    • 1.1 纯虚函数:没有实现的 “接口声明”
    • 1.2 抽象类:包含纯虚函数的 “不可实例化类”
  • 二. 多态的底层原理:虚表指针与虚函数表
    • 2.1 虚表指针(vfptr):对象中的 “导航器”
    • 2.2 多态的实现原理
    • 2.3 虚函数表(vtable):存储虚函数地址的 “数组”
    • 2.4 动态绑定与静态绑定
  • 三. 关键问题辨析与总结
    • 3.1 虚函数存在哪里?虚表又存在哪里?
    • 3.2 实战注意事项:
  • 四. 多态考察的一些常见问题(重点,面试高频题)
  • 结尾:


前言:

在 C++ 多态体系中,纯虚函数与抽象类是实现 “接口规范” 的核心工具,而虚函数表(vtable)与虚表指针(vfptr)则是多态底层实现的关键。本文将基于实战代码,从纯虚函数与抽象类的定义、使用场景入手,逐步拆解多态的底层原理 —— 包括虚表指针的存在性、虚函数表的结构与存储位置,最终帮你打通 “多态怎么用” 到 “多态如何实现” 的认知链路。


一. 纯虚函数与抽象类:强制接口规范的“契约”

在实际开发中,我们经常需要定义一个“只规定行为,不提供具体实现”的类。C++ 通过纯虚函数抽象类实现这种 “接口契约”。

本文代码示例所需头文件

#include<iostream>using namespace std;

代码仓库:多态收尾- Gitee.com

1.1 纯虚函数:没有实现的 “接口声明”

在虚函数的声明后加=0,该函数即为纯虚函数。纯虚函数无需在基类中实现(语法上允许实现,但无实际意义,因为会被派生类重写),其核心作用是 “强制派生类必须重写该函数”

1.2 抽象类:包含纯虚函数的 “不可实例化类”

包含纯虚函数的类称为抽象类,它有两个关键特性:

有了上面的知识储备,我们来看下代码示例吧:

// 抽象类:包含纯虚函数Drive()
class Car {
public:
// 纯虚函数:只声明接口,不提供实现
virtual void Drive() = 0;
};
// 派生类Benz:重写纯虚函数,成为“具体类”
class Benz :public Car {
public:
// 必须重写Drive(),否则Benz也是抽象类
virtual void Drive() {
cout << "Benz-舒适" << endl;
}
};
// 派生类BMW:重写纯虚函数,成为“具体类”
class BMW :public Car {
public:
virtual void Drive() {
cout << "BMW-操控" << endl;
}
};
int main() {
// 抽象类无法实例化对象
// Car car; 
// 用抽象类指针指向派生类对象(多态核心用法)
Car* pBenz = new Benz;
pBenz->Drive();  // 多态调用:输出“Benz-舒适”
Car* pBMW = new BMW;
pBMW->Drive();   // 多态调用:输出“BMW-操控”
return 0;
}

二. 多态的底层原理:虚表指针与虚函数表

当我们用基类指针调用派生类的虚函数时,编译器如何 “知道” 该调用哪个类的函数?答案藏在虚表指针(vfptr)虚函数表(vtable) 中 —— 这是 C++ 实现动态绑定(运行时多态)的核心机制。

2.1 虚表指针(vfptr):对象中的 “导航器”

首先通过下面这个题目来验证一下虚表指针的存在
下面编译为32位程序的运行结果是什么(D
A. 编译报错 B. 运行报错 C. 8 D. 12

class Base
{
public:
// 虚函数:触发编译器生成虚表指针
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{
cout << "Func2()" << endl;
}
// 普通函数
void Func3()
{
cout << "Func3()" << endl;
}
protected:
int _b = 1;
char _ch = 'x';
};
int main()
{
Base b;
//除了我们能看到的_b和_ch,其实有虚函数的类就会有一个虚函数表指针(32位下4字节,64位下8字节)
//因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表。
cout << sizeof(b) << endl;//32位:4+4+1->12// 输出结果:32位环境下为12字节,64位环境下为16字节return 0;}

在这里插入图片描述
关键结论

  • 只要类中包含虚函数(或继承自含虚函数的类),该类的对象就会额外存储一个虚表指针
  • 虚表指针通常位于对象内存的最前端(不同编译器可能有差异),其作用是 “指向该类的虚函数表”;
  • 同类型的对象共用一张虚函数表,但每个对象都有独立的虚表指针(指向同一张虚表)。

2.2 多态的实现原理

从底层的角度Func函数中ptr->BuyTicket(),是如何作为ptr指向Person对象调用Person::BuyTicket,ptr指向Student对象调用Student::BuyTicket的呢?通过下图我们可以看到,满足多态条件后,底层不再是编译时通过调用对象确定函数的地址,而是运行时到指向的对象的虚表中确定对应的虚函数的地址,这样就实现了指针或引用指向基类就调用基类的虚函数,指向派生类就调用派生类对应的虚函数。第⼀张图,ptr指向的Person对象,调用的是Person的虚函数;第二张图,ptr指向的Student对象,调用的是Student的虚函数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
private:
string _name;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-打折" << endl; }
private:
string _id;
};
void Func(Person ptr)
{
// 这里可以看到虽然都是Person指针Ptr在调用BuyTicket
// 但是跟ptr没关系,而是由ptr指向的对象决定的。
ptr.BuyTicket();
}
int main()
{
// 其次多态不仅仅发⽣在派⽣类对象之间,多个派⽣类继承基类,重写虚函数后
// 多态也会发⽣在多个派⽣类之间。
Person ps;
Student st;
Func(ps);
Func(st);
////这三个的虚函数表是一样的,同类型的对象共用一虚表
//Person p1;
//Person p2;
//Person p3;
return 0;
}

2.3 虚函数表(vtable):存储虚函数地址的 “数组”

虚函数表(简称 “虚表”)是编译器为每个含虚函数的类生成的一张 “虚函数指针数组”,数组中存储的是该类所有虚函数的地址。其结构与生成规则如下:

  • 基类虚表:存储基类所有虚函数的地址(如Base类的虚表存储Func1Func2的地址);

  • 派生类虚表

    • 首先继承基类虚表的所有内容;
    • 若派生类重写了基类的虚函数,会用派生类自身的虚函数地址 “覆盖” 基表中 对应的位置;
    • 派生类新增的虚函数,其地址会追加到虚表的末尾;
  • 虚表结尾标记:部分编译器(如 VS)会在虚表末尾添加0x00000000作为结束标记(g++ 无此标记,C++ 标准未强制规定)。

  • 注意:同类型的对象共用同一张虚表,不同类型的对象都有各自独立的虚表。

在这里插入图片描述

class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
// 普通函数:不存入虚表
void func5() { cout << "Base::func5" << endl; }
protected:
int a = 1;
};
class Derive : public Base
{
public:
// // 重写基类的func1:会覆盖虚表中func1的地址
virtual void func1() { cout << "Derive::func1" << endl; }
// 派生类新增虚函数:会追加到虚表末尾
virtual void func3() { cout << "Derive::func1" << endl; }
// 普通函数:不存入虚表
void func4() { cout << "Derive::func4" << endl; }
protected:
int b = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}

在这里插入图片描述
在这里插入图片描述

2.4 动态绑定与静态绑定

  • 对不满足多态条件(指针或者引用+调用虚函数)的函数调用在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。
  • 满足多态条件的函数调用是在运行时绑定,也就是运行时到指定对象的虚函数表中找到调用函数的地址,也叫做动态绑定。

当用基类指针调用虚函数时,编译器会按以下步骤完成 “动态绑定”(运行时确定调用的函数):

  • 获取虚表指针:从基类指针指向的对象中,取出虚表指针(vfptr);
  • 查找虚表:通过虚表指针找到该对象所属类的虚函数表(vtable);
  • 定位函数地址:在虚表中找到目标虚函数对应的地址(若派生类重写过,此处就是派生类函数地址);
  • 调用函数:通过找到的函数地址,调用对应的虚函数。

以之前的 “买票” 场景为例,流程如下:

// 基类指针指向派生类对象
Person* ptr = new Student;
// 动态绑定流程:
1. 从ptr指向的Student对象中,取出vfptr;
2. 通过vfptr找到Student类的虚表;
3. 在虚表中找到BuyTicket对应的地址(Student::BuyTicket的地址);
4. 调用该地址对应的函数,输出“买票-打折”。

三. 关键问题辨析与总结

3.1 虚函数存在哪里?虚表又存在哪里?

int main()
{
int i = 0;
static int j = 1;
int* p1 = new int;
const char* p2 = "xxxxxxxx";
printf("栈:%p\n", &i);
printf("静态区:%p\n", &j);
printf("堆:%p\n", p1);
printf("常量区:%p\n", p2);
Base b;
Derive d;
printf("Base虚函数表地址:%p\n", *((int*)&b));
printf("Derive虚函数表地址:%p\n", *((int*)&d));
printf("虚函数地址:%p\n", &Base::func1);
printf("普通函数地址:%p\n", &Base::func5);
}

运行结果
栈:010FF954
静态区:0071D000
堆:0126D740
常量区:0071ABA4
Person虚表地址:0071AB44
Student虚表地址:0071AB84
虚函数地址:00711488
普通函数地址:007114BF

3.2 实战注意事项:

  • 抽象类只能作为基类指针 / 引用使用,不可直接实例化;
  • 抽象类无法实例化,派生类必须重写所有纯虚函数才能成为 “具体类”;
  • 虚表指针会增加对象的内存开销(32 位下 4 字节,64 位下 8 字节);
  • 虚函数调用比普通函数多一次 “地址查找”,存在微小性能损耗,但通常可忽略(多态带来的灵活性远大于性能损失)。

四. 多态考察的一些常见问题(重点,面试高频题)

1. 面向对象的三大特性(这里重点讲讲什么是多态)

2. 什么是重载,重写(覆盖),重定义(隐藏)?

特性定义示例
重载同一类中,方法名相同,参数列表(参数类型、个数、顺序)不同,与返回值类型无关类中add(int a, int b)add(double a, double b)
重写(覆盖)子类继承父类后,对父类的虚函数进行重新实现,方法名、参数列表、返回值类型(协变情况除外)完全相同父类Animal的虚函数makeSound(),子类Dog重写为void makeSound() { cout << "汪汪" << endl; }
重定义(隐藏)子类中定义了与父类同名的非虚函数,隐藏父类的该函数父类有func(),子类也定义func(),子类对象调用func()时执行子类的,父类对象调用执行父类的

3. 多态的实现原理?
答: 多态通过 虚函数表(vtable)和虚函数指针(vptr) 实现。每个包含虚函数的类都有一个虚函数表,表中存储着该类所有虚函数的地址。每个对象都有一个虚函数指针,指向所属类的虚函数表(相同类型的对象指向同一张虚函数表)。当通过父类指针或者引用调用虚函数时,程序会根据指向的实际对象类型,通过其虚函数指针找到虚函数表,再找到对应的虚函数地址并调用,从而实现运行时的多态。
4. inline函数可以是虚函数吗?inline属性和虚函数属性能同时存在吗?
:可以是虚函数,从语法上看,inline函数可以声明为虚函数,但实际上编译器会忽略inline属性(inline一般展开是不需要地址的),将其当作普通虚函数处理。因为虚函数要放在虚表中去,两者机制冲突,也就是说inline属性和虚函数属性是不同时存在的。
5. 静态成员可以是虚函数吗?
:不能,静态成员函数属于类,不属于某个对象,没有this指针,而虚函数的调用需要通过对象的虚函数指针来实现,所以静态成员函数不能是虚函数。
6. 构造函数可以是虚函数吗?
:不可以。因为对象中的虚函数指针是在构造函数初始化列表阶段才初始化的,所以构造函数不能是虚函数。
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

8. 对象访问普通函数快还是虚函数更快?
:首先如果是普通对象调用的话两者是一样快的,但如果是基类的指针或者引用去调用,且构成了多态调用,则调用的普通函数更快,运行时调用虚函数需要到虚函数表中去查找,有一定开销。

调用场景普通函数调用机制虚函数调用机制性能差异根源
普通对象调用编译时直接绑定函数地址,直接跳转执行编译时直接绑定函数地址,直接跳转执行无差异
基类指针/引用多态调用编译时直接绑定函数地址,直接跳转执行运行时通过vptr找vtable,再找函数地址执行虚函数多了查表的运行时开销

9. 虚函数表是在什么阶段生成的,存在哪里的?
:虚函数表是在编译阶段生成的,一般情况下是存在代码段(常量区)的。
10. C++菱形继承的问题?虚继承的原理?
:菱形继承会导致
数据冗余和二义性
的问题,虚继承则是通过虚基类指针和虚基表(不要把虚函数表,虚函数指针和虚基表,虚基类指针搞混了) 实现的中间基类在继承时顶层基类时声明为虚继承,这样可以保证顶层基类的成员只会有一份,解决了数据冗余和二义性的问题。
11. 什么是抽象类?抽象类的作用?
: 包含纯虚函数(形如virtual void func() = 0;)的类,无法实例化对象。抽象类的作用是 作为接口规范,强制子类必须重写实现纯虚函数


结尾:

 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:通过抽象类,我们能定义清晰的接口契约;通过虚表机制,C++ 实现了 “运行时动态绑定” 的多态能力。理解这两者,不仅能正确使用多态,更能在复杂场景中(如框架设计、模块扩展)写出更灵活、可维护的 C++ 代码。

✨把这些内容吃透超牛的!放松下吧✨
ʕ˘ᴥ˘ʔ
づきらど

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/992542.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

富通天下:打造数字化私域平台,福州奇富网络小额贷客服服务赋能中国外贸品牌出海!

树立行业标杆,讲好中国故事,传递中国声音,充分展现腾飞的中国经济、崛起的民族品牌和向上的企业家精神。近日,“崛起的民族品牌”专题系列节目对话宁波富通天下信息技术有限公司(简称:富通天下)及其创始人兼总经理周文…

2025聚合氯化铝厂家推荐:技术与性价比双优清单

随着环保政策持续收紧,水处理药剂的品质稳定性、技术适配性成为企业采购的核心考量。聚合氯化铝(PAC)、聚丙烯酰胺(PAM)等作为污水净化的关键材料,其供应商的选择直接影响处理效果与运营成本。基于技术实力、产品…

改进遗传算法求解VRP问题的局部搜索能力优化方案

一、技术实现 1. 混合邻域搜索算子设计 % 扫描-节约操作(Scan-Save) function new_route = scan_save(route, dist_matrix)n = length(route);best_save = inf;new_route = route;% 扫描阶段:寻找删除点for i = 2:n…

2025英国好的留学中介

2025英国好的留学中介一、2025年如何选择英国留学中介?作为从业15年的英国留学申请规划导师,我经常被学生和家长询问:“2025年申请英国留学,哪家中介更靠谱?”这个问题的答案并非单一,但根据《2025留学服务机构评…

花豆分期客服服务/翼展宏图,换芯上市!江西五十铃翼放新EC 9.98万起

11月29日,江西五十铃翼放新EC轻卡上市发布会在长沙成功举办。这款基于五十铃全球技术标准打造的全新力作正式上市,旨在以全面进阶的实力,满足全领域、全场景的现代物流运输需求。伴随城市物流高效化、专业化发展的趋势…

2025年长沙记账报税公司权威推荐榜单:代理记账‌/工商注册‌/财税咨询‌‌源头公司精选

在“大众创业、万众创新”的时代浪潮下,长沙作为中部地区重要的经济增长极,每年新设市场主体数量持续攀升。与此同时,规范、高效、专业的财税服务已成为企业稳健经营的刚需。据市场观察,一个高效的代理记账服务能帮…

2025 年除沫器厂家最新推荐榜,技术实力与市场口碑深度解析,助力企业精准采购优质设备聚四氟乙烯丝网/PP 丝网/钛丝网/不锈钢/PTFE 丝网/聚丙烯丝网/折流板/波浪形抽屉除沫器公司推荐

引言 在石油、化工、环保等关键工业领域,除沫器作为保障工艺纯度与生产安全的核心设备,其品质优劣直接关系到企业生产效率与环保达标情况。当前市场上除沫器厂家数量众多,但产品性能差异显著,给企业采购带来极大挑…

2025年装车鹤管厂家权威推荐榜单:装卸鹤管/石油鹤管/天然气鹤管源头厂家精选

装车鹤管作为石化、化工、油气储运领域的关键流体装卸设备,其安全性、密封性与耐用性直接关系到整个装卸作业的效率与环保合规。随着国家对危化品运输监管的日益严格以及行业自动化、智能化水平的提升,具备核心材料技…

2025英国留学中介机构排名北京

2025英国留学中介机构排名北京一、如何选择2025年北京地区的英国留学中介作为从业15年的国际教育规划师,我经常被北京学生和家长问及:2025年申请英国留学,如何筛选可靠的中介机构?根据《2025中国留学白皮书》数据,…

2025 年 12 月食堂承包服务商权威推荐榜:专业运营与高效供餐,大型饭堂/食堂承包解决方案深度解析

2025 年 12 月食堂承包服务商权威推荐榜:专业运营与高效供餐,大型饭堂/食堂承包解决方案深度解析 随着企事业单位后勤社会化改革的深入,以及员工对工作餐品质需求的不断提升,食堂承包服务已从简单的“做饭”演变为…

2025 年分布器厂家最新推荐榜,聚焦企业技术研发实力与市场应用口碑深度解析槽式液体/管式液体/塔内件/管式/液体再/全连通液体/二级槽式/液体收集再分布器公司推荐

引言 在石油、化工、环保等工业领域,分布器作为塔内件核心组件,其性能直接决定设备运行效率与工艺稳定性。为精准筛选优质企业,本次推荐榜联合中国化工装备协会、中国环保产业协会开展测评,参照《塔内件设备质量评…

2025英国留学中介哪家好一点

2025英国留学中介哪家好一点一、2025英国留学中介哪家好一点作为从事15年英国留学申请规划的导师,本人经常被学生和家长问及:2025年申请英国高校,选择哪家留学中介更合适?根据2025年11月27日的最新行业数据,英国留…

2025年云南住宅修缮服务推荐:昆明曲靖玉溪靠谱的房屋修缮公

TOP1 推荐:云南鼎钻建筑加固工程有限公司 推荐指数:★★★★★ 口碑评分:云南住宅修缮领域首推品牌 专业能力:作为云南本土聚焦建筑加固与住宅修缮的全链条服务企业,云南鼎钻建筑加固工程有限公司整合加固施工、设…

2025年度重庆电力总包资质代办和转让五大推荐,看哪家公司实

建筑行业中,电力总包资质是企业承接光伏、风电、输变电等项目的入场券。2024年数据显示,重庆地区电力项目招标量同比增长38%,但超60%的企业因资质问题错失投标机会:35%的企业自行办理因材料不符被驳回,22%的企业误…

2025英国留学中介排行榜

2025英国留学中介排行榜一、2025英国留学中介选择指南随着2025年英国留学申请季的临近,许多学生和家长在搜索引擎上频繁查询“2025年英国留学中介如何挑选”、“英国留学中介排名靠谱吗”等问题。作为从业12年的英国留…

2025年建筑木模板直销厂家权威推荐榜单:建筑施工模板‌/建筑模板‌/胶合板‌‌源头厂家精选

在建筑行业向高质量、高效率发展的背景下,建筑木模板作为直接影响混凝土成型质量、施工安全与综合成本的核心周转材料,其重要性日益凸显。当前市场厂家繁多,但部分企业存在原料以次充好、工艺落后等问题,导致模板周…

2025年低灰分机油制造商五大推荐企业榜单发布,看看哪家技术

本榜单依托行业权威认证数据、真实用户反馈及技术实力评估,深度筛选低灰分机油领域标杆企业,为商用车队、乘用车车主及工程设备运营商提供客观选型依据,助力精准匹配兼具技术优势、优质售后与高性价比的合作伙伴。 …

vue vxe-gantt table 甘特图实现任务可拖拽自动调整日期

vue vxe-gantt table 甘特图实现任务可拖拽自动调整日期,通过设置 task-bar-config.drag 启用任务条拖拽功能 效果代码 设置 task-bar-config.drag 启用任务条拖拽功能 <template><div><vxe-gantt v-b…

2025源头地道肠厂家TOP5权威推荐:甄选诚信商家,原味地

当烤肠成为家庭餐桌、街头小吃、露营野餐的国民单品,消费者对原味地道肠的需求愈发苛刻:既要肉香纯粹、无过多添加,又需原料透明、制作卫生。但市场上淀粉肠冒充地道肠、代加工品质参差不齐、防腐剂超标等乱象频发,…

2025年度设备安全锁推荐厂商TOP5:专业供应商实力解析与

在工业生产安全管理体系中,设备安全锁(Lockout/Tagout锁具)是能量隔离作业的后一道防线,直接关系到检修维护人员的生命安全。面对市场上良莠不齐的锁具品牌,如何选择资质齐全、性能可靠的供应商?以下结合技术实力…