简介:这份C++面试八股文深度总结源自作者备战22届秋招并斩获华为、荣耀、联发科、海康威视等offer的实战积累,面向准备C++方向校招与社招的求职者,系统梳理高频考点,帮助读者在有限时间内建立完整知识框架、少走弯路。内容覆盖C++基础、操作系统、计算机网络、数据库等模块,具体包括C++三大特性、C与C++的区别、struct与class差异、头文件引用规则、extern "C"、从源码到可执行文件的四步流程、static关键字、数组与指针等常见面试题。资源为1个PDF文件,压缩包约5.28MB,便于打印或移动端随时翻阅。目前已有40089人学习下载,适合需要集中突击八股、查漏补缺的开发者,可作为面试前的速查清单与知识复盘材料。
1. 从一道“送分题”翻车说起:C++ 面试到底在考什么
“说说 C 和 C++ 的区别。”这题够基础吧?可我在模拟面试里见过不止一个候选人,张口就是“C++ 是面向对象,C 是面向过程”,然后……就没有然后了。面试官等的是你把封装、继承、多态、模板、STL、const、引用、智能指针、四种 cast 这些点串起来,顺带提一句 C 是 C++ 的子集、C++ 兼容 C。只答一句话,在面试官眼里就是“背过,但没真懂”。
C++ 面试八股文从来不是考你背了多少冷门语法,而是考三件事:基础概念能不能说清、内存模型能不能画明白、面向对象机制能不能讲透。这份总结把 C++ 基础、内存管理、面向对象三大块拆成高频考点,每个考点配真题和答题思路。适合正在备战校招或社招 C++ 岗的人——你不需要从零学 C++,你需要的是把已经会的东西,用面试官想听的方式讲出来。
2. C++ 基础高频考点:从语言特性到编译链接的必答清单
2.1 语言特性类:C vs C++、struct vs class、const vs define
这三组对比几乎是每场 C++ 面试的开场白。答不好不一定会挂,但答得漂亮能迅速建立“基础扎实”的印象。
C 和 C++ 的区别,标准答法分四层:第一,C 是 C++ 的子集,C++ 兼容 C,但 C++ 多了引用、智能指针、auto 等新特性;第二,C 面向过程,C++ 面向对象;第三,C 的指针、强制转换、内存管理不够安全,C++ 用 const、引用、四种 cast、智能指针、try-catch 来改善;第四,C++ 有模板和 STL,复用性更高。注意别只说“面向对象”就停,面试官会追着问“还有呢”。
struct 和 class 的区别,在 C++ 里只有两点:默认访问权限(struct 是 public,class 是 private)和默认继承方式(struct 是公有继承,class 是私有继承)。另外 class 可以用于定义模板参数,struct 不能。至于“struct 用于数据结构、class 用于对象封装”,那是使用习惯,不是语言强制。
const 和 define 的区别,核心是三点:const 在编译阶段生效,define 在预处理阶段生效;const 带类型,define 不带类型,所以 define 不利于类型检查;const 定义的常量在 C 语言中占内存,define 是直接操作数不占内存。面试官常追问“const 一定不占内存吗”,你要知道 C++ 中 const 常量可能被编译器优化掉,但取地址时仍会分配空间。
提示:这三组对比题,面试官最想听的是“分点 + 每点一句话解释”,不要展开成小作文。
2.2 编译链接类:从源码到可执行文件、extern "C"、头文件查找
C++ 从代码到可执行二进制文件的过程,四个阶段必须背熟:预编译、编译、汇编、链接。预编译处理 #define 展开、#if 条件编译、#include 插入、删注释、加行号;编译做词法分析、语法分析、语义分析、代码优化、生成汇编;汇编把汇编代码转成机器指令;链接把多个目标文件合并,分静态链接和动态链接。静态链接在链接时就把函数代码嵌入可执行文件,Windows 下 .lib、Linux 下 .a;动态链接运行时才找函数,Windows 下 .dll、Linux 下 .so,删掉动态库程序就跑不起来。
extern "C" 的作用,是让 C++ 代码调用 C 语言代码时,按 C 的规则编译。因为 C++ 支持函数重载,编译时会把参数类型加到函数名里(name mangling),而 C 不会。不加 extern "C",链接时会找不到 C 函数的符号。
头文件双引号和尖括号的区别,查找路径不同:双引号先查当前头文件目录,再查编译器设置路径,最后查系统变量;尖括号直接查编译器设置路径和系统变量。所以自定义头文件用双引号,系统头文件用尖括号。
2.3 真题演练:static 关键字、数组与指针、函数指针
真题 1:说说 static 关键字的作用。
答题思路分五个层面:全局静态变量和局部静态变量(初始化在数据段,未初始化在 BSS 段,程序结束才释放);静态函数(只能在本源文件使用);静态变量(只能在本源文件使用);类中静态成员变量(所有对象共享一块存储区,不属于某个对象);类中静态成员函数(没有 this 指针,不能访问非静态成员)。面试官最想听的是“静态成员函数没有 this 指针”这一句,很多人漏掉。
真题 2:数组和指针的区别。
从三个角度答:赋值(同类型指针可相互赋值,数组只能逐个元素赋值或拷贝);存储方式(数组连续存放,在静态区或栈上;指针本身是变量,存放地址,存储空间不确定);sizeof(数组是总大小除以元素类型大小,32 位平台指针 sizeof 都是 4,64 位都是 8)。注意“数组名是首元素地址”这句话要说,但别把它和“数组就是指针”混为一谈。
真题 3:什么是函数指针,怎么定义,用在哪?
函数指针就是指向函数的指针变量,函数名本身就是入口地址。定义形式:
int func(int a); int (*f)(int a); // 定义函数指针 f f = &func; // 取函数地址赋给 f f(10); // 通过函数指针调用使用场景主要是回调:你调用别人的 API 叫 Call,别人库里面调用你的函数叫 Callback。面试官可能追问“函数指针和指针函数区别”,指针函数是返回指针的函数,写法是int *fun(int x, int y),函数指针是int (*fun)(int x, int y),括号位置决定一切。
2.4 易错点:nullptr 调用成员函数、野指针、静态变量初始化时机
nullptr 调用成员函数可以吗?能。因为编译时对象就绑定了函数地址,和指针空不空没关系。但注意:如果成员函数里访问了非静态成员变量,就会崩。这题考的是“静态绑定”的理解。
野指针怎么产生、怎么避免?产生原因:释放内存后指针没置空,依然指向那块内存。避免办法:初始化置 NULL、申请内存后判空、释放后置 NULL、使用智能指针。面试官常追问“智能指针为什么能避免野指针”,答“RAII 机制,析构时自动释放并置空”。
静态变量什么时候初始化?C 语言的全局和静态变量在编译期初始化,任何代码执行之前。C++ 标准规定全局或静态对象首次用到时才构造。这题容易和“静态局部变量只初始化一次”混淆,注意区分。
3. 内存管理必考题:堆栈、内存分区、内存对齐与泄漏排查
3.1 堆和栈的区别、malloc 和 new 的区别
堆和栈的区别,从三个维度答:分配方式(栈由操作系统自动分配释放,堆由程序员手动分配释放);缓存方式(栈用一级缓存,调用完立即释放;堆用二级缓存,速度慢些);数据结构(堆类似数组,栈是先进后出)。
malloc 和 new 的区别,这是高频中的高频。标准答法:new 是操作符,malloc 是函数;new 先分配内存再调构造函数,释放时调析构函数,malloc 没有构造和析构;malloc 需要指定大小且返回指针要强转,new 不用;new 可以被重载,malloc 不行;new 分配失败抛异常,malloc 返回 NULL。
底层实现也要知道:malloc 小于 128K 调 brk(),大于 128K 调 mmap(),采用内存池管理,用隐式链表记录空闲块。new 底层先分配内存,再调构造函数,最后返回对象指针。
真题:malloc 和局部变量分配在堆还是栈?malloc 在堆上,需要手动回收;局部变量在栈上,超过作用域自动回收。这题考的是“谁管理生命周期”。
3.2 内存分区、程序启动过程、内存对齐
C++ 内存分区,五个区:栈、堆、自由存储区、全局/静态存储区、常量存储区。栈存放局部变量和函数参数,函数结束自动释放;堆是 new 分配的内存块,一般一个 new 对应一个 delete;自由存储区是 malloc 分配的,用 free 释放;全局/静态存储区放全局变量和静态变量;常量存储区放常量,不允许修改。
程序有哪些 section?从低地址到高地址:代码段(只读,存执行代码)、数据段(已初始化的全局和静态变量)、BSS 段(未初始化或初始化为 0 的全局和静态变量,程序执行前自动清 0)。运行时多出堆区和栈区,堆从低地址向高地址增长,栈从高地址向低地址增长,中间还有文件映射区。
程序启动过程,五步:操作系统创建进程并分配私有空间,加载器把可执行文件的数据段和代码段映射到虚拟内存;加载器读入导入符号表,查找依赖的动态链接库;对每个动态库调用 LoadLibrary,确定基地址、匹配导入导出符号、递归加载依赖库、调用初始化函数;初始化全局变量,全局对象自动调构造函数;进入入口点函数开始执行。
内存对齐,四个原则:数据成员对齐规则(第一个成员 offset 为 0,后面每个成员从自身大小或子成员大小的整数倍开始);结构体作为成员(从内部最宽基本类型成员的整数倍地址开始);收尾工作(结构体总大小必须是内部最宽基本类型成员的整数倍,不足补齐);union 的 sizeof 以最大元素为准。为什么要对齐?为了快速准确访问,不对齐会导致多次访问浪费时间。
3.3 真题演练:内存泄漏的检测与避免
真题:什么是内存泄漏?怎么检测?怎么避免?
内存泄漏就是申请了内存,使用完没释放。常见原因:new/malloc 后没用 delete/free;子类继承父类时父类析构函数不是虚函数;句柄资源没释放。
检测方法:良好的编码习惯,分配和释放配对;将分配的内存指针用链表管理,程序结束检查链表;使用智能指针;用工具如 ccmalloc、Dmalloc、Leaky、Valgrind。
避免办法:定义指针初始化为 NULL;申请内存后立即判空;数组和动态内存赋初值;避免下标越界;申请和释放配对;free/delete 后立即置 NULL;使用智能指针。
注意:面试官问“内存泄漏”时,最想听的是“父类析构函数不是虚函数会导致子类对象析构不完全”这一条,很多人只答 new/delete 不配对。
3.4 易错点:初始化为 0 的全局变量在 BSS 还是 data
初始化为 0 的全局变量在 BSS 段,不是 data 段。BSS 段存放未初始化或初始化为 0 的全局和静态变量,特点是可读写,程序执行前自动清 0。data 段存放已初始化的全局和静态变量。这题考的是“BSS 段自动清 0”这个特性,答错说明没理解程序启动时的内存布局。
4. 面向对象核心考点:封装继承多态与构造析构顺序
4.1 三大特征、重载与重写、虚函数机制
面向对象三大特征:封装、继承、多态。封装是把数据和操作数据的方法结合,隐藏实现细节,只暴露接口;继承是用现有类的功能扩展新类,三种继承方式(public、protected、private)影响基类成员在派生类中的可见性;多态是用父类指针指向子类实例,通过父类指针调用实际子类的成员函数,实现方式有重写和重载。
重载和重写的区别:重写是派生类重新定义基类的虚函数,函数名、参数列表、返回值都必须一致,基类函数必须有 virtual 修饰;重载是同一作用域内同名函数参数列表不同(类型、个数、顺序),不关心返回值。
重载和重写的实现:重载用命名倾轧(name mangling),编译阶段把参数类型加入函数名区分;重写用虚函数和动态绑定,运行时根据对象实际类型调用。虚函数机制:存在虚函数的类有一维虚函数表(虚表),对象有一个指向虚表开始的虚指针,虚表和类对应,虚指针和对象对应。
真题:为什么要虚析构?为什么不能虚构造?
虚析构:将可能被继承的父类析构函数设为虚函数,保证 new 子类后用基类指针指向子类对象时,delete 能正确调用子类析构函数,避免内存泄漏。不能虚构造:虚函数依赖虚表,而虚表指针在构造函数中初始化,构造时虚表还没建立,所以构造函数不能是虚函数。
4.2 构造函数、拷贝构造、深拷贝浅拷贝
构造函数有几种?四种:默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数。默认构造函数和初始化构造函数完成对象初始化;有了有参构造,编译器就不提供默认构造。拷贝构造函数用于复制本类对象,默认实现是浅拷贝。移动构造函数用于将其他类型变量隐式转换为本类对象。
只定义析构函数,会自动生成哪些构造函数?编译器自动生成拷贝构造函数和默认构造函数。注意:如果一个类定义了析构函数,那么它要定义自己的拷贝构造函数和默认构造函数,否则可能出现重复释放同一块内存的错误。
深拷贝和浅拷贝:浅拷贝是值拷贝,源对象和目标对象共用一份实体,地址相同;深拷贝先开辟和源对象一样大的空间,再把内容拷贝过去,两个指针指向不同内存位置。深拷贝不会出现重复释放同一块内存的错误。
真题:一个空类默认生成哪些函数?无参构造函数、拷贝构造函数、赋值运算符、析构函数(非虚)。
4.3 类对象初始化顺序、向上向下转型
类对象初始化顺序:创建派生类对象时,基类构造函数优先调用(也优先于派生类成员类);成员类构造函数优先于该类本身构造函数;多个基类按派生表出现顺序调用,不是成员初始化表顺序;多个成员类对象按声明顺序调用,不是初始化表顺序。综上:父类构造 -> 成员类对象构造 -> 自身构造。析构顺序和构造顺序相反。
向上转型和向下转型:子类转父类用 dynamic_cast,相对安全,不会数据丢失;父类转子类用强制转换,不安全,可能数据丢失,因为父类指针或引用的内存中可能不包含子类成员的内存。
4.4 易错点:多态条件、虚函数表、纯虚函数
多态的实现条件:必须通过基类类型的指针或引用调用虚函数;基类中必须有虚函数;派生类中必须重写虚函数。三个条件缺一不可,很多人只答“虚函数”就停了。
虚函数表:每个有虚函数的类有一张虚表,存放虚函数地址;每个对象有一个虚指针,指向虚表。虚表是类级别的,虚指针是对象级别的。面试官常追问“虚表在编译期还是运行期建立”,答“编译期确定虚表结构,运行期根据对象类型查表调用”。
纯虚函数和抽象类:纯虚函数是虚函数加= 0,抽象类是包含至少一个纯虚函数的类,必须在子类实现纯虚函数才能实例化。
5. 答题技巧与押题思路:把八股文变成自己的话
5.1 答题模板:分点 + 一句话解释 + 一个例子
C++ 面试八股文最大的坑是“背了但不会说”。面试官问“说说 const 和 define 的区别”,你如果背出五条,面试官会觉得你在背书;如果你分三点,每点一句话解释,再补一句“比如 const 有类型检查,define 没有,所以 define 容易出玄学 bug”,面试官会觉得你真懂。
我自己的习惯是:任何对比题,先答“核心区别是什么”,再分点展开,最后补一个实际场景。比如“new 和 malloc 的区别”,核心区别是“new 是操作符,malloc 是函数”,然后分五点展开,最后补一句“所以 C++ 里优先用 new,除非你要和 C 代码兼容”。
5.2 押题思路:高频考点串讲
根据这份总结和近年面试趋势,C++ 岗高频考点集中在:
| 模块 | 高频题 | 出现频率 |
|---|---|---|
| C++ 基础 | C vs C++、struct vs class、const vs define | 极高 |
| 编译链接 | 源码到可执行文件过程、extern "C" | 高 |
| 内存管理 | 堆栈区别、malloc vs new、内存泄漏 | 极高 |
| 面向对象 | 三大特征、重载 vs 重写、虚析构 | 极高 |
| 指针引用 | 数组 vs 指针、函数指针、野指针 | 高 |
| 构造析构 | 初始化顺序、深拷贝浅拷贝 | 高 |
押题思路:优先把“极高”频率的题练到能脱口而出,再补“高”频率的题。每道题准备一个“一句话核心 + 三点展开 + 一个例子”的答题框架。
5.3 一个具体方法:用“黑匣子”思维答内存管理题
内存管理题最容易翻车,因为看不见摸不着。我的方法是把内存想象成一个黑匣子,面试官问“堆和栈的区别”,你就想“栈是自动售货机,投币出饮料,用完就走;堆是仓库,你自己租格子,自己还钥匙”。面试官问“内存泄漏”,你就想“租了仓库不还钥匙,仓库越租越多,最后没仓库可租”。
具体答题时,先画内存分区图(代码段、数据段、BSS 段、堆、栈),再标出每个区放什么、谁管理、生命周期多长。面试官问“程序启动过程”,你就按“创建进程 -> 映射代码和数据 -> 加载动态库 -> 初始化全局变量 -> 进入入口函数”五步走。这套方法我每次面试前都强制走一遍,从没在内存题上翻过车。
提示:面试官问内存题时,最想听的是“你能把抽象概念具象化”,不是背定义。
从那以后我每次准备 C++ 面试,都强制走一遍“分点 + 一句话解释 + 一个例子”的答题框架,把每道八股文题都用自己的话讲一遍。希望帮到你。
本文还有配套的精品资源,点击获取