面试官:指针和应用区别
文章目录
一、«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++反汇编与逆向分析技术»从底层看,引用在汇编层面仍是用指针实现的,可以理解为“常量指针”,所以二者在本质上差别不大.
-
但语法和使用上有明显区别,引用更加安全
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>
):
|
|
原因:
- 动态扩容
vector
会在容量不足时realloc
,需要把所有元素搬迁到新内存区域。- 如果用引用(
T&
),原来的绑定会失效 → 语义崩溃。 - 指针可以重新指向新的内存 → 动态调整 OK。
- 空容器支持
- 空
vector
初始化时_M_start = nullptr
。 - 引用不能是空的,所以指针是唯一选择。
- 空
- 泛型友好
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
手动释放,否则就会内存泄漏。
而 引用本质上只是对象的别名,它不拥有资源,不负责释放。赋值引用不会复制对象,也不会影响对象生命周期。
而引用根本不参与资源的创建与释放**。
|
|
所以:
**指针=拥有权,需要管理生命周期;
引用=别名,不管理生命周期
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.
|
|
场景 | 为什么用万能引用 |
---|---|
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️⃣ 背景
|
|
T&& t
是万能引用(forwarding reference)t
在函数体内总是左值,即便原始传入的是右值
2️⃣ 问题
- 如果直接用
func(t)
:- 左值参数 → 正确,调用拷贝构造/左值版本
- 右值参数 → 也被视作左值 → 调用拷贝构造,而非移动构造
- 右值语义丢失,影响性能
3️⃣ 解决:std::forward<T>(t)
重载不同类型处理方法,很 easy
4. 小白总结(面试官知道,我也知道)
- 我并没有新增 c++语法知识,深入 引用是变量别名 扩展到变量所有权管理,这个很大话题 **指针=拥有权,需要管理生命周期;引用=别名,不管理生命周期
- 我并没有新增 c++语法知识,深入引用初始化必须绑定 扩展到变量重要数据结构设计 ,这个是和很大话题 b++tree,红黑树 这个都是高频题目
- 在参数传递过程中,在过程过程中,右值引用类型丢失这个隐藏的内容我怎知道的
- 但凡阅读过源码,就知道STL里面充斥着大量的T&&以及std::forward
-
引用 = 变量别名
- 引用本身不是对象,它只是已经存在对象的另一个名字。
- 编译器在底层可能用指针实现,但语义上它没有独立内存,也不能重新绑定
-
生命周期管理
- 引用 不拥有资源,不负责分配或释放对象内存。
- 引用的有效期 依赖于所引用对象的生命周期。
- 如果引用指向的对象被销毁,再访问就是悬挂引用(undefined behavior)。
-
架构哲学
- 引用用于短期访问和接口传递,保证语法安全
- 不能作为缓存或懒加载的资源管理工具
- 资源控制仍然需要指针或智能指针来实现
2.3 小王(王者 工作 5-10 年)面试
1. 面试官:谈谈你对指针与引用区别理解
2. 小王回答(我根本不 关心这个什么指针语法问题):
架构师才 不关心基于指针语法, 关心可维护,上线后不出事故, 哪怕出了事故,必须可控制,而不是丢数据致命问题
区别1:语言层面–生命周期管理
特性 | C++ 指针 | C++ 引用 | Rust 引用 & 生命周期 |
---|---|---|---|
本质 | 存储地址 | 对象别名 | 对象借用,编译器跟踪生命周期 |
是否拥有资源 | 可拥有(需要 delete) | 不拥有 | 不拥有,所有权由编译器追踪 |
初始化要求 | 可为 nullptr,随时赋值 | 必须初始化,不可为 nullptr | 必须指向有效对象,借用检查保证安全 |
生命周期管理 | 手动(裸指针)或智能指针 | 不管理生命周期 | 编译器管理,自动释放 |
安全性 | 容易悬挂或泄漏 | 安全,但不可重新绑定 | 编译期保证安全,无悬挂 |
使用场景 | 缓存、大对象、懒加载、动态分配 | 函数参数、别名访问 | 所有引用/借用、函数参数、高安全要求 |
可变性控制 | 指针指向可变或不可变对象 | 与原对象一致 | 可借用(不可变)或可变借用,编译器保证互斥 |
举例:
|
|
接口设计:
- 公共接口层:优先引用
-
内部实现层:优先指针
在对象内部或容器中存储时:
- 如果需要可选性(optional) → 用裸指针或
std::unique_ptr
- 如果需要共享所有权 → 用
std::shared_ptr
- 如果需要弱引用(避免循环引用) → 用
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️⃣ 关注公众号:后端开发成长指南(回复"面经"获取)获取过去我全部面试录音和面试复盘。
抬头看天:走暗路、耕瘦田、进窄门、见微光
- 不要给自己这样假设:别人完成就等着自己完成了,大家都在一个集团,一个公司,分工不同,不,这个懒惰表现,这个逃避问题表现。
- 别人不这么假设,至少本月绩效上不会写成自己的,至少晋升不是你,裁员淘汰就是你。
- 目标:在跨越最后一道坎,拿百万年薪,进大厂。