C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
一、背景
各位老师好
CPP面试冲刺周刊 (c++ weekly)第三期开始了
目标:不是成为C++专家,而是成为C++面试专家
本期内容:指针与引用区别

c++周刊目的陪你一起快速冲击大厂面试
小提示:不要把他看成一个出售给你产品,我只出售给自己
在公司做任何事情事情,
都必须清楚拆解需求功能,开发周期,最后得到什么结果,
同样面试准备也是如此,给自己一个期限 21 天,给自己大纲,然后给自己 21 天学习结果,这样自己才能安心准备下去。
第一周(换个角度看问题):

曾经有一个让我心跳加速的岗位放在我面前,
我没有珍惜。
等到别人拿到 offer 的那一刻,
我才追悔莫及!
人世间,最痛苦的事情,
不是没钱吃饭,
也不是没房没车,
而是——错过了那个能让我逆天改命的机会!
如果上天再给我一次机会,
我一定会对那个岗位说三个字:
“我要你!”
如果非要在这份“心动”上加一个期限,
一万年太久了……
我只想要——21天!
你可能面临两种选择
① 犹豫不前:准备到天荒地老
“这个岗位太难了,我先准备一下吧。”
于是你准备1天、1周、1个月、1年……
等再回头,3年就这样过去了。
最后,错过了一次又一次心动的岗位。
② 盲目回答:机会就在眼前,却抓不住
终于等来一场面试,
你觉得问题很简单,张口就答,
结果用“几千元思维”回答“百万年薪岗位”。
一次面试失利,也许就意味着和理想岗位失之交臂。
更残酷的是
在你犹豫的这几年里,
找工作的成本越来越高:
曾经复杂的业务规则、先进的架构,早已被淘汰•市场上新的技术和面试要求,每年都在不断升级
等你回过头来,发现不仅机会没了,
连准备的方向都变了。
21天C++面试冲刺周刊
不是让你成为C++专家, 而是让你成为C++面试专家。
不是让你疯狂学习新知识, 而是帮你重新整理已有知识,
让你的能力与面试题精准对齐。
因为,21天就够了,
足够让我火力全开,
核心方法论:
• 系统备战让你学到每个 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++反汇编与逆向分析技术>>从底层看,引用在汇编层面仍是用指针实现的,可以理解为“常量指针”,所以二者在本质上差别不大.
•但语法和使用上有明显区别,引用更加安全


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>):
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 手动释放,否则就会内存泄漏。
而 引用本质上只是对象的别名,它不拥有资源,不负责释放。赋值引用不会复制对象,也不会影响对象生命周期。
而引用根本不参与资源的创建与释放**。
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.
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️⃣ 背景
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

2.3 小王(王者 工作 5-10 年)面试
1. 面试官:谈谈你对指针与引用区别理解
2. 小王回答(我根本不 关心这个什么指针语法问题):
架构师才 不关心基于指针语法,
关心可维护,上线后不出事故,
哪怕出了事故,必须可控制,而不是丢数据致命问题
区别1:语言层面--生命周期管理

| 特性 | C++ 指针 | C++ 引用 | Rust 引用 & 生命周期 |
|---|---|---|---|
| 本质 | 存储地址 | 对象别名 | 对象借用,编译器跟踪生命周期 |
| 是否拥有资源 | 可拥有(需要 delete) | 不拥有 | 不拥有,所有权由编译器追踪 |
| 初始化要求 | 可为 nullptr,随时赋值 | 必须初始化,不可为 nullptr | 必须指向有效对象,借用检查保证安全 |
| 生命周期管理 | 手动(裸指针)或智能指针 | 不管理生命周期 | 编译器管理,自动释放 |
| 安全性 | 容易悬挂或泄漏 | 安全,但不可重新绑定 | 编译期保证安全,无悬挂 |
| 使用场景 | 缓存、大对象、懒加载、动态分配 | 函数参数、别名访问 | 所有引用/借用、函数参数、高安全要求 |
| 可变性控制 | 指针指向可变或不可变对象 | 与原对象一致 | 可借用(不可变)或可变借用,编译器保证互斥 |
举例:
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️⃣ 关注公众号:后端开发成长指南(回复"面经"获取)获取过去我全部面试录音和面试复盘。

浙公网安备 33010602011771号