C++面试11——指针与引用

在C++面试中,深刻理解指针与引用是考察候选人语言功底的关键。下面我将从核心区别、应用场景到面试真题,为你提供一个全面的解析。

为了让你快速建立起知识框架,下表总结了指针与引用最核心的区别。

特性 指针 (Pointer) 引用 (Reference)
本质 是一个实体变量,存储的是另一个变量的内存地址 是原变量的一个别名,与原变量是同一个东西
初始化 可以不初始化(但极度不推荐) 必须初始化
可空性 可以为NULLnullptr 不能为空,必须总是指向一个有效的对象
重定向 初始化后,可以改变其指向的地址 初始化后,不能再改变其绑定的对象
内存操作 sizeof(指针) 得到的是指针本身的大小(如32位系统为4字节) sizeof(引用) 得到的是所绑定对象的大小
自增(++)操作 指向下一个相邻同类型的内存地址 使所绑定对象的值增加1
内存分配 程序会为指针变量本身分配内存 引用不需要分配内存区域,它是已存在对象的别名
多级概念 支持多级指针,如int **pp(指向指针的指针) 只能是一级,int &&r是无效的(注:C++11中的&&表示右值引用,含义不同)

💡 核心机制与使用场景

理解表格中的区别后,我们来看看这些特性在实际编码中意味着什么。

  • 初始化和安全性 :引用必须初始化的特性,使其在定义时就与一个合法对象绑定,减少了出现"野指针"那样的未定义行为风险。使用引用作为函数参数时,你不需要像使用指针那样检查它是否为NULL,代码更简洁安全。

  • 重定向与语义:指针可以重新指向的特性,使其在需要动态切换操作目标时非常有用(如遍历链表、动态数组)。而引用的不可变性则表达了"从一而终"的语义,常用于表示函数参数或返回值与某个已存在对象是同一实体。

  • 操作符重载 :在重载下标操作符[]时,必须返回引用。这是因为像v[5] = 10这样的语句,期望修改的是v[5]这个实际元素,如果返回的是指针,语句就要写成*v[5] = 10,这不符合直觉。

🔄 函数参数传递的差异

指针和引用都可用于按引用传递函数参数,但形式和效果不同。

  • 指针传递 :传递的是实参地址的副本 (值传递)。在函数内部,你可以通过解引用操作符*来修改实参指向的值。但如果你修改指针本身的值(让它指向别处),这个改变不会反映到函数外部的实参指针上。

    cpp 复制代码
    void modifyByPointer(int *p) {
        *p = 100; // 修改了外部变量的值
        p = nullptr; // 只修改了函数内部指针副本的指向,外部指针不变
    }
  • 引用传递 :传递的是实参的别名。对引用的任何操作都直接作用于原始实参本身。

    cpp 复制代码
    void modifyByReference(int &r) {
        r = 100; // 直接修改了外部变量的值
        // 无法让引用r重新绑定到另一个变量
    }

⚠️ 常见陷阱与最佳实践

  1. 悬垂引用/指针

    永远不要返回局部变量的指针或引用。因为函数结束后,局部变量会被销毁,其内存空间失效,返回的指针或引用就变成了"悬垂"的,使用它们会导致未定义行为。

    cpp 复制代码
    int* badReturnPointer() {
        int x = 10;
        return &x; // 错误!返回了局部变量的地址
    }
    int& badReturnReference() {
        int x = 10;
        return x; // 错误!返回了局部变量的引用
    }
  2. 引用和指针的const限定

    • 指向常量的指针:不能通过该指针修改对象的值。
    • 指针常量:指针本身的值(指向的地址)不能改变。
    • 指向常量的指针常量:两者都不能改变。
    • 常量引用:常用于传递不希望被修改的函数参数,可以提高效率并避免不必要的拷贝。需要注意的是,虽然通过常量引用不能修改所绑定的对象,但该对象本身可能通过其他途径被修改。
  3. 现代C++的智能指针

    对于动态分配的内存,应优先使用std::unique_ptrstd::shared_ptrstd::shared_ptr等智能指针。它们基于RAII(资源获取即初始化)机制,能自动管理内存释放,极大地减少了内存泄漏和悬垂指针的风险。

📝 某大厂面试真题解析

真题1:基础概念辨析

题目:写出下面代码的输出结果。

cpp 复制代码
#include <iostream>
using namespace std;
int main() {
    char b = 30;
    char* p = &b;
    char& ra = b;
    cout << sizeof(p) << endl;
    cout << sizeof(ra) << endl;
    return 0;
}

详解

  • sizeof(p)p是一个指针,sizeof(指针)返回的是存储地址所需的内存大小。在64位系统 上,结果通常是8字节;在32位系统上是4字节。
  • sizeof(ra)rachar型变量b的引用,sizeof(引用)返回的是其所引用对象类型的大小。bchar类型,所以结果是1字节
    答案 :在64位系统下,输出为 81
真题2:指针运算与数组

题目:分析以下代码的输出。

cpp 复制代码
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d\n", *(a + 1), *(ptr - 1));

详解

  • a是数组名,在大多数情况下会退化为指向首元素的指针(类型为int*)。
  • &a表示的是整个数组的地址 ,其类型是int (*)[5]
  • &a + 1:这里的1是加上整个数组的大小(5 * sizeof(int)),所以ptr指向数组a末尾之后的下一个位置。
  • *(a + 1)a退化为int*a + 1是第二个元素的地址,解引用得到a[1],即2
  • *(ptr - 1)ptrint*类型,ptr - 1向前移动一个int的大小,指向a的最后一个元素a[4],解引用得到5
    答案 :输出 2, 5。此题考察对数组指针和普通指针运算的理解深度。
真题3:内存管理陷阱

题目:下面代码有何问题?

cpp 复制代码
char *getMemory() {
    char p[] = "hello";
    return p;
}
void Test() {
    char *str = NULL;
    str = getMemory();
    printf(str);
}

详解 :函数getMemory中,数组p是一个局部变量,存储在栈上。字符串字面量"hello"被初始化到该数组空间。当函数返回时,栈内存被释放,数组p不再有效。返回其地址给str后,str成了一个悬垂指针 。后续printf试图通过str访问已释放的内存,行为是未定义的 ,通常输出乱码或导致程序崩溃。
修正 :若需要返回字符串,可将数组改为动态分配(用new/malloc,但需调用者释放)或使用智能指针。更佳做法是直接返回std::string,避免手动管理内存。

💎 总结与面试技巧

在面试中回答此类问题时,可以遵循以下思路:

  1. 结构化回答:先抛出核心区别,如"指针是实体,引用是别名",然后分点阐述初始化、可空性、重定向等关键差异。
  2. 结合场景:不仅说出区别,更要说明不同特性所适用的场景,例如"正因为引用不能为空,所以做函数参数时通常更安全简洁"。
  3. 体现深度 :在适当的时候提及const的正确用法、现代C++的智能指针最佳实践,这能展示你的知识广度和对代码质量的重视。
  4. 谨慎分析:面对代码题,先慢下来,一步步分析每个表达式的类型和含义,特别是遇到指针和数组混合运算时。

希望这份详细的解析能帮助你在面试中游刃有余。如果对const的深入用法或智能指针的具体细节感兴趣,我们可以继续深入探讨。

相关推荐
杨小码不BUG2 小时前
CSP-J/S初赛知识点精讲-图论
c++·算法·图论··编码·csp-j/s初赛
初圣魔门首席弟子3 小时前
flag使用错误出现bug
c++·bug
ThreeAu.3 小时前
2025年Web自动化测试与Selenium面试题收集:从基础到进阶的全方位解析
自动化测试·软件测试·selenium·测试工具·面试·web测试·测试开发工程师
Mr_WangAndy3 小时前
C++设计模式_创建型模式_原型模式Prototype
c++·设计模式·原型模式
奔跑吧邓邓子3 小时前
【C++实战㊷】C++ 原型模式实战:从概念到高效应用
c++·实战·原型模式
在未来等你3 小时前
Elasticsearch面试精讲 Day 20:集群监控与性能评估
大数据·分布式·elasticsearch·搜索引擎·面试
奔跑吧邓邓子4 小时前
【C++实战㊶】C++建造者模式:复杂对象构建的秘密武器
c++·实战·建造者模式
奔跑吧邓邓子4 小时前
【C++实战㊵】C++抽象工厂模式:解锁高效对象创建的密钥
c++·实战·抽象工厂模式
jf加菲猫5 小时前
条款11:优先选用删除函数,而非private未定义函数
开发语言·c++