一、«CPP面试冲刺周刊» 产生背景

各位老师好

CPP面试冲刺周刊 (c++ weekly)第三期开始了

目标:不是成为C++专家,而是成为C++面试专家

本期内容:指针与引用区别

c++周刊目的陪你一起快速冲击大厂面试

小提示:不要把他看成一个出售给你产品,我只出售给自己 在公司做任何事情事情, 都必须清楚拆解需求功能,开发周期,最后得到什么结果, 同样面试准备也是如此,给自己一个期限 21 天,给自己大纲,然后给自己 21 天学习结果,这样自己才能安心准备下去。

第一周(换个角度看问题):

曾经有一个让我心跳加速的岗位放在我面前,
我没有珍惜。
等到别人拿到 offer 的那一刻,
我才追悔莫及!

人世间,最痛苦的事情,
不是没钱吃饭,
也不是没房没车,
而是——错过了那个能让我逆天改命的机会!

如果上天再给我一次机会,
我一定会对那个岗位说三个字:
“我要你!”

如果非要在这份“心动”上加一个期限,
一万年太久了……
我只想要——21天!

你可能面临两种选择

① 犹豫不前:准备到天荒地老

“这个岗位太难了,我先准备一下吧。”
于是你准备1天、1周、1个月、1年……
等再回头,3年就这样过去了

  • 每天忙着搬砖,没时间系统复习
  • 每次想起要准备,又感觉心里没底
  • 面试知识点更新太快,拿着旧地图找新机会 最后,错过了一次又一次心动的岗位。

② 盲目回答:机会就在眼前,却抓不住

终于等来一场面试,
你觉得问题很简单,张口就答,
结果用“几千元思维”回答“百万年薪岗位”。

  • 面试官问到C++底层实现,答不上来
  • 设计题说到高并发架构,没实战经验
  • 一紧张,连项目里真实做过的东西都讲不清

一次面试失利,也许就意味着和理想岗位失之交臂。

更残酷的是

在你犹豫的这几年里,
找工作的成本越来越高:

  • 一个部门、一个领导,可能坚持一年就被解散
  • 一个项目,可能在10年、20年后,
    曾经复杂的业务规则、先进的架构,早已被淘汰
  • 市场上新的技术和面试要求,每年都在不断升级

等你回过头来,发现不仅机会没了,
连准备的方向都变了

21天C++面试冲刺周刊 ​一万年太久,只争三周​

不是让你成为C++专家, 而是让你成为C++面试专家

不是让你疯狂学习新知识, 而是帮你重新整理已有知识
让你的能力与面试题精准对齐。

因为,21天就够了,
足够让我火力全开,

  • 一边补齐 C++ 知识点,
  • 一边刷爆经典面试题,
  • 一边撸穿开源项目,
  • 让自己变得不可替代!

核心方法论

让你学到每个 c++知识,都关联一个经典面试,并对对应开源项目实践

  • 系统备战
    每天 20~30 分钟,聚焦 C++ 核心知识,
    三周时间完成高效梳理。
  • 经典面试题
    每个知识点都关联一个高频面试题
    让你知道“为什么考”和“怎么答”。
  • 开源项目实践
    通过真实项目理解底层原理,
    不背答案,而是用实践打动面试官
  • 场景驱动学习
    还原真实面试场景,
    帮你学会“怎么说服面试官”。

21天,你会获得什么?

  • 一份完整的C++面试知识地图
  • 一套高频题+解析+项目实践组合拳
  • 一次全链路模拟面试体验
  • 三周后,面对面试官,你能自信说出:
    “问吧,准备好了。”

这也是我的面试方法:

  • 如果一开始就直接学某个知识点,我常常感觉不到它的实际价值。
  • 所以我会先尝试树立一个整体的大局观,就算过程中被现实“啪啪打脸”了又怎样?
  • 把每一次面试都当成一场陪练,用面试官的专业视角和真实项目来反推和校正自己的理解,不是更好吗?这种即时、高质量的反馈,是你看多少书、自己一个人闷头琢磨多久,都很难获得的。

二、从青铜 (小青)到王者(小王)回答:指针与引用区别

2.1 小青(青铜级别 工作 0-3 年)面试

1. 面试官:指针与引用区别

2. 小青回答(回答很棒了):

  • 我熟读《C++ Primer》,引用本质上是变量(变量也是内存地址别名)的别名,定义时必须初始化;

  • 引用不允许为 NULL,而指针可以为 NULL

  • 原文里说:“A reference is not an object. Instead, a reference is just another name for an already existing object.” 这是最经典的描述

  • 我 «CPU眼里的C/C++» «c++反汇编与逆向分析技术»从底层看,引用在汇编层面仍是用指针实现的,可以理解为“常量指针”,所以二者在本质上差别不大.

  • 但语法和使用上有明显区别,引用更加安全

来源:CPU眼里的C/C++ 变量

参数传递

3. 面试官视角反问

  • 这个是像素级别的模仿
  • 无论怎么回答,都是照抄课本(我自己不会忘了搜索呀)。哪怕换个角度,比如从汇编层面解释,最会回答相同,跑题呀
  • 这个时候,面试官沉默大约两分钟,显然在等候选人继续补充,看是否还能说出更多区别。
  • 如果没有新的观点,或者候选人只是反复重复原来的答案,那就很尴尬了。

于是小青继续补充了几点:

  • 指针的大小:在 64 位系统中,sizeof(pointer) 固定是 8 个字节;
  • 自增运算的区别
    • 指针 ptr++ → 偏移一个对象的地址;
    • 引用 ref++ → 直接让变量本身加 1

4. 小青总结(这样回答还不够)

  • 这个时候,其实不用太担心自己记不住所有指针的语法细节。
    面试时,能回答多少就说多少,别陷入死记硬背。
    更重要的是思路是否清晰,能不能结合项目经验去解释。

  • 面试官往往是项目经理,他每天忙于业务推进,真的会关心你能背出多少条“指针 vs 引用”的区别吗?

  • 这样会到还不够,为什么不能针针对区详细说明

小青疑惑:为什么我全部回答了,面试官还是不满意 ,我回答不高深吗?

2.2 小白(白银级别 工作 3-5 年)面试

1. 面试官:谈谈你对指针与引用区别理解

2. 小白回答(工作怎么用就怎么回答):

核心原则:

  • Use references when you can(能用引用就用引用 ✅)
  • pointers when you have to(必须使用指针场景,不用指针无法解决问题✅)

2.1 必须使用指针的场景:需要频繁更新/延长初始化的重要数据结构设计

需要频繁更新数据结构:

数据结构 为什么用指针 为什么不用引用
std::vector 1. 内部维护动态数组,存储区可扩容。2. 扩容后旧地址失效,必须更新新的堆指针。3. 需要支持空容器状态,用空指针表示。 - 引用必须绑定到对象,无法延迟绑定或置空。- 引用不能在扩容时重新绑定新内存。
std::map 1. 基于红黑树实现,节点动态创建在堆上。2. 插入/删除会频繁申请和释放节点。3. 需要通过指针把左右子树、父节点串联。 - 引用无法重新指向新节点。- 引用不能天然支持“空子树”场景。
std::list 1. 双向链表,节点分散在堆上,每个节点需指向前驱/后继。2. 插入删除常数复杂度,依赖指针重连。 - 引用无法为空,不能用来表示链表末端或空表。- 插入/删除时无法更新引用重新指向新节点。
B+ 树 / B 树 1. 内部节点、叶子节点动态分配。2. 叶子节点间通过指针快速遍历。3. 插入/分裂/合并时,节点间指针频繁更新。 - 引用不能在运行时变更绑定关系。- 无法表示“空孩子指针”。
其他容器(unordered_map / unordered_set 1. 基于哈希桶实现,桶和节点动态分配。2. 哈希冲突需要通过链表或指针串联节点。 - 引用无法处理哈希桶为空的场景。- 无法在 rehash 时更新绑定。

举例说明: 为什么 std::vector 内部必须用指针

源码(libstdc++ 实现,<bits/stl_vector.h>):

1
2
3
4
5
6
7
8
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class vector {
    _Tp* _M_start;    // 指向首元素
    _Tp* _M_finish;   // 指向最后一个元素后
    _Tp* _M_end_of_storage; // 指向容量的末尾
};


原因

  1. 动态扩容
    • vector 会在容量不足时 realloc,需要把所有元素搬迁到新内存区域。
    • 如果用引用(T&),原来的绑定会失效 → 语义崩溃。
    • 指针可以重新指向新的内存 → 动态调整 OK。
  2. 空容器支持
    • vector 初始化时 _M_start = nullptr
    • 引用不能是空的,所以指针是唯一选择。
  3. 泛型友好
    • vector<T> 支持任意类型 T,不要求 T 必须可引用。
    • 用指针实现不会对模板参数类型提出额外约束。

2.2 必须使用指针场景-延迟初始化场景:

“延长初始化”是指 对象的真正初始化推迟到需要使用它的那一刻, 而不是在对象声明的时候立刻初始化。

在数据库、存储系统、分布式架构中非常常见,比如:

  • Redis:数据量极大,不能一次性初始化所有数据结构,否则启动时间、内存占用都不可接受。
  • Ceph:对象缓存和元数据加载也是懒加载。
  • TiDB:索引、Region、DDL 相关信息按需初始化。

在这种情况下,指针引用 更适合,主要原因是引用一旦绑定,就必须立即指向一个已初始化的对象,而指针可以:

  • 一开始是 nullptr
  • 后续按需动态分配
  • 再次释放并重建
系统 延迟加载对象/数据 延迟加载元数据 指针/引用作用
Ceph ✅ ObjectCacher ✅ OMAP / Metadata 延迟分配 + 内存管理
TiKV ✅ Block / SST ✅ Region Info 指针/block handle 延迟访问
3FS ✅ 对象页 ✅ 元数据页 指针/智能指针管理内存

3FS offers an innovative caching mechanism known as KVCache.

Traditional DRAM-based caching can be both expensive and limited in capacity, but KVCache provides a cost-effective alternative that delivers high throughput and a larger cache capacity.

3FS 提供了一种名为 KVCache 的创新缓存机制。传统的基于 DRAM 的缓存不仅价格昂贵,容量也有限,而 KVCache 则提供了一种经济高效的替代方案,能够提供高吞吐量和更大的缓存容量

这不就是 stl map 结构,redis 吗?kvCache 也不是神秘面纱

2.3 必须使用指针场景-资源所有权:

  • C++ 内存管理:指针负责生死,引用只是别名

在 C++ 的世界里, ​​资源所有权​​的界限非常清晰:​**​new和 delete必须成对出现, 由指针全权负责;

比如你 new 一个对象,系统给你分配内存,这块内存的“所有权”属于你。

你需要用 delete 手动释放,否则就会内存泄漏。

引用本质上只是对象的别名,它不拥有资源,不负责释放。赋值引用不会复制对象,也不会影响对象生命周期。

而引用根本不参与资源的创建与释放​**​。

1
2
3
4
5
6
7
MyClass obj; 
MyClass& ref = obj; 
// 只是别名,不创建新对象 
// 无 release() 或 delete ref 的操作!

delete ref;//语法报错 从语法层面避免这个操作

所以:

**指针=拥有权,需要管理生命周期;

引用=别名,不管理生命周期

2.4 什么场景必须使用引用

核心原理:

【Modern Cpp】从万能引用到完美转发

万能引用(Universal Reference)由Effective C++系列的作者Scott Meyers提出, 其对万能引用的定义如下:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

void fun(int &&a) { // a为右值引用
  // do sth

}

int main() {

  int a = 1;

  fun(a); 
  // 编译器报错 没有函数重载 错误:无法将左值‘int’绑定到‘int&&’
  fun(1); // OK

}

template <typename T> 
fun(T &&a) 编译时​
场景 为什么用万能引用
vector::push_back 接收左值/右值都能正确构造对象
函数模板转发参数 避免不必要拷贝,保持原有值类别
泛型工厂函数 构造对象并返回,左值/右值都高效处理

你一定会很奇怪,为什么万能引用的形式明明是T&&, 却既可以代表左值又可以代表右值。这就要涉及到C++的引用折叠语法了。

1️⃣ 普通右值引用 vs 万能引用

  • 普通右值引用int&& x
    • 只能绑定右值(临时量、std::move结果)
  • 万能引用(Forwarding Reference)template<typename T> void f(T&& t)
    • 可以绑定左值或右值
    • 这是 模板类型推导机制 导致的,而不是“引用类型缺少”本身。

2️⃣ 模板推导规则

模板函数参数 T&& 会触发引用折叠规则

调用情况 T 推导结果 函数参数类型
传左值 x T = int& T&& = int& && → int&
传右值 std::move(x) T = int T&& = int&&

关键点:引用折叠使得原本的右值引用 T&& 变成了左值引用,从而可以绑定左值。
所以万能引用的能力来源于模板推导 + 引用折叠,而不是缺少引用类型。

3. 面试官视角反问

为什么 T&& 还要 std::forward

1️⃣ 背景

1
2
3
4
5
template<typename T>
void wrapper(T&& t) {
    func(t);              // ❌ t 是左值还是右值?可能失去右值语义
    func(std::forward<T>(t)); // ✅ 完美转发
}
  • T&& t 是万能引用(forwarding reference)
  • t 在函数体内总是左值,即便原始传入的是右值

2️⃣ 问题

  • 如果直接用 func(t)
    • 左值参数 → 正确,调用拷贝构造/左值版本
    • 右值参数 → 也被视作左值 → 调用拷贝构造,而非移动构造
  • 右值语义丢失,影响性能

3️⃣ 解决:std::forward<T>(t) 重载不同类型处理方法,很 easy

4. 小白总结(面试官知道,我也知道)

  • 我并没有新增 c++语法知识,深入 引用是变量别名 扩展到变量所有权管理,这个很大话题 **指针=拥有权,需要管理生命周期;引用=别名,不管理生命周期
  • 我并没有新增 c++语法知识,深入引用初始化必须绑定 扩展到变量重要数据结构设计 ,这个是和很大话题 b++tree,红黑树 这个都是高频题目
  • 在参数传递过程中,在过程过程中,右值引用类型丢失这个隐藏的内容我怎知道的
  • 但凡阅读过源码,就知道STL里面充斥着大量的T&&以及std::forward

  1. 引用 = 变量别名

    • 引用本身不是对象,它只是已经存在对象的另一个名字。
    • 编译器在底层可能用指针实现,但语义上它没有独立内存,也不能重新绑定
  2. 生命周期管理

    • 引用 不拥有资源,不负责分配或释放对象内存。
    • 引用的有效期 依赖于所引用对象的生命周期
    • 如果引用指向的对象被销毁,再访问就是悬挂引用(undefined behavior)。
  3. 架构哲学

    • 引用用于短期访问和接口传递,保证语法安全
    • 不能作为缓存或懒加载的资源管理工具
    • 资源控制仍然需要指针或智能指针来实现

2.3 小王(王者 工作 5-10 年)面试

1. 面试官:谈谈你对指针与引用区别理解

2. 小王回答(我根本不 关心这个什么指针语法问题):

架构师才 不关心基于指针语法, 关心可维护,上线后不出事故, 哪怕出了事故,必须可控制,而不是丢数据致命问题

区别1:语言层面–生命周期管理

特性 C++ 指针 C++ 引用 Rust 引用 & 生命周期
本质 存储地址 对象别名 对象借用,编译器跟踪生命周期
是否拥有资源 可拥有(需要 delete) 不拥有 不拥有,所有权由编译器追踪
初始化要求 可为 nullptr,随时赋值 必须初始化,不可为 nullptr 必须指向有效对象,借用检查保证安全
生命周期管理 手动(裸指针)或智能指针 不管理生命周期 编译器管理,自动释放
安全性 容易悬挂或泄漏 安全,但不可重新绑定 编译期保证安全,无悬挂
使用场景 缓存、大对象、懒加载、动态分配 函数参数、别名访问 所有引用/借用、函数参数、高安全要求
可变性控制 指针指向可变或不可变对象 与原对象一致 可借用(不可变)或可变借用,编译器保证互斥

举例:

1
2
3
4
5
fn safe() -> &i32 { // 编译错误!需要生命周期注解。
    let x = 5;
    &x // 错误!编译器拒绝:`x` 的生命周期不够长。
}

接口设计:

  • 公共接口层:优先引用
  • 内部实现层:优先指针

在对象内部或容器中存储时:

  1. 如果需要可选性(optional) → 用裸指针或 std::unique_ptr
  2. 如果需要共享所有权 → 用 std::shared_ptr
  3. 如果需要弱引用(避免循环引用) → 用 std::weak_ptr

三、历史题目:c++高频面试题

序号 知识地图 题目
1 新特性 一分钟讲透:c++新特性string_view
2 库的编译链接 如何给一个高速行驶的汽车换轮胎(实现一个可扩展c++服务)
3 STL Traits 技术
4 新特性 if constexpr
5 新特性 面试题:C++中shared_ptr是线程安全的吗?
6 模板 C++17 新特性 std::optional
7 class c++类的成员函数,能作为线程的参数吗
8 编译器 const 如何保证const不变
9 值语义 一道面试题看深拷贝构造函数问题
10 值语义 智能指针究竟在考什么
11 指针 使用 C++ 智能指针遇到的坑
12 指针 指针与引用区别

最动人的作品,为自己而写,刚刚好打动别人

 1️⃣ 如果有更多疑问,联系小王,一起交流,进步

个人联系方式

2️⃣  关注公众号:后端开发成长指南(回复"面经"获取)获取过去我全部面试录音和面试复盘。

抬头看天:走暗路、耕瘦田、进窄门、见微光

  • 不要给自己这样假设:别人完成就等着自己完成了,大家都在一个集团,一个公司,分工不同,不,这个懒惰表现,这个逃避问题表现。
  • 别人不这么假设,至少本月绩效上不会写成自己的,至少晋升不是你,裁员淘汰就是你。
  • 目标:在跨越最后一道坎,拿百万年薪,进大厂。