嵌入式软件工程师面经C/C++篇—指针与引用:从本质到应用
一、核心概念:一句话区分
- 指针 (Pointer):一个存储内存地址的变量。可以把它想象成一张写着房子地址的纸条。这张纸条本身是一个实体,你可以拿着它去查地址,也可以把它撕掉换成另一张写着别的房子地址的纸条。
- 引用 (Reference):一个已存在变量的别名。可以把它想象成房子的 **“昵称” 或 “别名”**。比如 “我的家”、“XX 小区 B 栋 302” 指的都是同一套房子。“昵称” 本身不是一个实体,它和房子是绑定在一起的。
二、核心区别详解(附代码示例)
下面我们从 7 个维度,用 “房子” 的比喻和代码示例来详细对比。
1. 本质与内存
- 指针:是一个独立的变量,它有自己的内存空间,里面存放的是另一个变量的地址。 比喻:“纸条” 本身是一个实体,它占用一定的空间(比如一张小纸片)。
- 引用:不是一个独立的变量,它没有自己的内存空间,它只是一个已存在变量的别名。编译器在底层可能会用指针来实现引用,但这对程序员是透明的。 比喻:“昵称” 本身不占用物理空间,它只是一个称呼。
2. 初始化
- 指针:可以不初始化(会成为野指针,非常危险),也可以初始化为
nullptr
(空指针),或者指向一个已存在的变量。 比喻:你可以有一张空白的纸条(野指针),一张写着 “无” 的纸条(空指针),或者一张写着具体地址的纸条。 - 引用:必须在定义时初始化,并且必须绑定到一个已存在的变量上。不能有 “空引用” 或 “野引用”。 比喻:你不能凭空给一个不存在的东西起昵称。必须先有 “房子”,才能给它起 “我的家” 这个昵称。
3. 可变性(是否能更改指向)
- 指针:可以更改指向,让它指向另一个变量。 比喻:你可以擦掉纸条上的旧地址,写上一个新的地址,这样它就指向另一套房子了。
- 引用:一旦初始化,就不能更改绑定,它会一生一世地绑定在最初的那个变量上。 比喻:“我的家” 这个昵称一旦给定,就不能再被用来指代邻居家的房子。如果你给它赋值,那是修改房子里的内容,而不是更改昵称的指向。
4. 空值
- 指针:可以被赋值为
nullptr
,表示它不指向任何有效的内存地址。 比喻:纸条上写着 “无地址”。 - 引用:不能为
nullptr
。它必须始终指向一个有效的对象。 比喻:昵称必须指代一个真实存在的东西。
5. 解引用
- 指针:访问所指向变量的值时,必须使用解引用运算符
*
。 比喻:你需要根据纸条上的地址,去找到那套房子,才能看到里面的东西。 - 引用:使用时无需解引用,直接使用引用的名字就相当于访问了原变量。 比喻:你说 “去我的家”,大家都知道是去哪,不需要先查地址再去。
6. sizeof
运算符
- 指针:
sizeof(指针)
返回的是指针变量本身的大小(在 32 位系统上通常是 4 字节,在 64 位系统上是 8 字节)。 比喻:测量的是 “纸条” 本身的大小。 - 引用:
sizeof(引用)
返回的是被引用变量的大小。 比喻:通过 “昵称” 去测量,得到的是 “房子” 的大小。
7. 自增 / 自减运算
- 指针:
指针++
表示指针地址增加一个类型的大小,即指针移动到下一个相邻的元素。 比喻:从 “XX 路 1 号” 的地址,移动到 “XX 路 3 号”(假设是整数类型,步长为 2)。 - 引用:
引用++
表示被引用的变量的值增加 1。 比喻:“我的家” 里的人数增加了 1。
三、代码示例:直观感受区别
#include <iostream> int main() { int house_A_value = 100; // 房子A,价值100 int house_B_value = 200; // 房子B,价值200 // --- 1. 定义与初始化 --- int* ptr = &house_A_value; // 指针ptr,指向房子A的地址 int& ref = house_A_value; // 引用ref,是房子A的别名 // --- 2. 访问与修改 --- std::cout << "通过指针访问 house_A: " << *ptr << std::endl; // 需要解引用* std::cout << "通过引用访问 house_A: " << ref << std::endl; // 直接访问 *ptr = 150; // 通过指针修改房子A的价值 std::cout << "修改后 house_A 的价值: " << house_A_value << std::endl; // 150 ref = 200; // 通过引用修改房子A的价值 std::cout << "再次修改后 house_A 的价值: " << house_A_value << std::endl; // 200 // --- 3. 更改指向 (指针可以,引用不行) --- ptr = &house_B_value; // 指针ptr现在指向房子B std::cout << "指针现在指向 house_B 的价值: " << *ptr << std::endl; // 200 // ref = house_B_value; // 这不是更改引用的指向! // 这等价于 house_A_value = house_B_value; // std::cout << "引用现在指向 house_B 的价值: " << ref << std::endl; // 200 (但ref仍然是house_A的别名) // std::cout << "house_A 的价值被意外修改: " << house_A_value << std::endl; // 200 // --- 4. sizeof 对比 --- std::cout << "sizeof(ptr) (指针大小): " << sizeof(ptr) << std::endl; // 8 (在64位系统) std::cout << "sizeof(ref) (被引用对象大小): " << sizeof(ref) << std::endl; // 4 (int的大小) return 0; }
四、如何相互转换?
指针和引用之间可以进行转换,但需要遵循一定的规则。
1. 指针 -> 引用
方法:对指针进行解引用 (*
),就可以得到它所指向的对象,这个对象可以直接用来初始化一个引用。
注意:转换的前提是指针不能为 nullptr
,否则会导致未定义行为(通常是程序崩溃)。
#include <iostream> void print_value(int& val_ref) { std::cout << "Received value: " << val_ref << std::endl; } int main() { int a = 10; int* p = &a; // 指针p指向a // 指针转引用 int& r = *p; // 对指针p解引用,得到对象a,用a初始化引用r r = 20; // 通过引用r修改a的值 std::cout << "a is now: " << a << std::endl; // 输出 20 // 在函数调用中非常常见 print_value(*p); // 直接将解引用后的指针作为引用参数传递 }
2. 引用 -> 指针
方法:对引用进行取地址 (&
),就可以得到它所绑定变量的地址,这个地址可以赋给一个指针。
#include <iostream> int main() { int a = 10; int& r = a; // 引用r是a的别名 // 引用转指针 int* p = &r; // 对引用r取地址,得到a的地址,赋给指针p *p = 20; // 通过指针p修改a的值 std::cout << "a is now: " << a << std::endl; // 输出 20 std::cout << "r is now: " << r << std::endl; // 输出 20 }
五、何时使用指针?何时使用引用?
- 优先使用引用的场景:作为函数参数: 当你需要修改实参,并且希望函数调用语法更简洁时。当你需要传递大型对象(如 std::string, std::vector)以避免昂贵的拷贝开销,同时又不希望修改它时,使用常量引用 const &。这是最常见、最安全的用法。作为函数返回值: 当函数返回一个容器内的元素或类的成员,且你希望调用者可以直接修改它时。警告:绝对不要返回一个函数内部局部变量的引用,因为函数结束后该变量的内存会被销毁,引用会变成 “悬挂引用”,导致未定义行为。
- 必须使用指针的场景:需要动态内存分配:使用 new 分配的内存,必须用指针来接收地址,并且最终需要用 delete 手动释放。(现代 C++ 推荐使用智能指针来管理动态内存)。需要表示 “空” 或 “无”:当一个变量在某些情况下可能不指向任何有效对象时,必须使用指针并将其设为 nullptr。引用无法做到这一点。需要更改指向:当你需要一个变量能在不同时间指向不同对象时(例如,遍历链表或树结构),必须使用指针。实现数据结构:像链表、树、图等复杂数据结构,其节点之间的连接关系必须通过指针来实现。
六、常见面试题解析
问题:指针和引用有什么区别?
回答:
- 指针是实体,存储地址;引用是别名,不占内存
- 指针可空可换指向;引用必须初始化且不可更改
- 指针需显式解引用;引用自动解引用
- sizeof (指针) 是地址大小;sizeof (引用) 是对象大小
问题:能返回函数内局部变量的引用吗?
回答:不能。函数返回后局部变量销毁,引用会悬空,访问会导致未定义行为。
问题:为什么有时用 const 引用传递参数?
回答:
- 避免大对象拷贝,提高效率
- 防止函数内部修改原始数据
- 可以接收临时对象
七、总结表格
本质 | 存储地址的变量 | 变量的别名 | 写着地址的纸条 | 房子的昵称 |
内存 | 有自己的内存空间 | 无自己的内存空间 | 纸条本身占空间 | 昵称不占空间 |
初始化 | 可初始化,可
,可未初始化 | 必须 在定义时初始化 | 纸条可空白、可写 “无” | 必须先有房子才能起名 |
更改指向 | 可以 | 不可以 | 可改纸条上的地址 | 昵称不能换对象 |
空值 | 可以 为
| 不可以 为空 | 纸条上写 “无地址” | 昵称不能指向虚无 |
解引用 | 需要使用
| 无需解引用 | 按地址找房子 | 直接叫昵称 |
| 返回指针本身的大小 | 返回被引用对象的大小 | 量纸条大小 | 量房子大小 |
自增
| 地址增加一个步长 | 所指变量的值 + 1 | 纸条地址往后移 | 房子里的人数 + 1 |
嵌入式岗位热门但面试难,考点繁杂。本专栏聚焦实战,助求职者突破壁垒。 内容覆盖面试全流程:从简历优化突出项目能力,到拆解多方向(物联网、汽车电子等)高频题,含 C 语言、RTOS、驱动开发等核心考点解析与拓展。另有面试模拟、薪资谈判、大厂流程揭秘等实用内容。