嵌入式软件工程师面经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 引用传递参数?

回答

  • 避免大对象拷贝,提高效率
  • 防止函数内部修改原始数据
  • 可以接收临时对象

七、总结表格

本质

存储地址的变量

变量的别名

写着地址的纸条

房子的昵称

内存

有自己的内存空间

无自己的内存空间

纸条本身占空间

昵称不占空间

初始化

可初始化,可

nullptr

,可未初始化

必须

在定义时初始化

纸条可空白、可写 “无”

必须先有房子才能起名

更改指向

可以

不可以

可改纸条上的地址

昵称不能换对象

空值

可以

nullptr

不可以

为空

纸条上写 “无地址”

昵称不能指向虚无

解引用

需要使用 

*

无需解引用

按地址找房子

直接叫昵称

sizeof

返回指针本身的大小

返回被引用对象的大小

量纸条大小

量房子大小

自增

++

地址增加一个步长

所指变量的值 + 1

纸条地址往后移

房子里的人数 + 1​

#嵌入式软件开发面经##嵌入式软件##嵌入式笔面经分享##秋招白月光#
嵌入式软件工程师面经 文章被收录于专栏

嵌入式岗位热门但面试难,考点繁杂。本专栏聚焦实战,助求职者突破壁垒。​ 内容覆盖面试全流程:从简历优化突出项目能力,到拆解多方向(物联网、汽车电子等)高频题,含 C 语言、RTOS、驱动开发等核心考点解析与拓展。另有面试模拟、薪资谈判、大厂流程揭秘等实用内容。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务