核心要点速览

  • 指针:存储内存地址的变量,支持空值(nullptr)、动态指向、多级访问,存在野指针风险
  • 引用:变量的别名,必须初始化、不可改指向、无空引用,语法简洁安全
  • 访问效率:两者基本一致
  • 适用场景:需空值 / 动态指向 / 多级访问用指针;需简洁语法 / 确保有效 / 避免误操作引用

一、指针

指针本质是 “存储内存地址的变量”,支持对内存的间接访问。

1. 类型与特性

(1)野指针

  • 定义:未初始化、已释放或越界访问的指针(指向随机 / 无效内存)。
  • 风险:访问野指针会导致内存错误(崩溃、数据错乱),是常见 bug 来源。
  • 示例(错误):
1
2
int* p; // 未初始化,野指针
*p = 10; // 未定义行为(访问随机内存)
  • 规避:指针定义时立即初始化(如int* p = nullptr;),释放后及时置空(p = nullptr;)。

(2)空指针(nullptr)

  • 定义:C++11 标准的空指针常量,替代旧版NULLNULL本质是0,易引发歧义)。
  • 用途:表示指针 “无指向”,避免野指针,可通过条件判断安全使用。
  • 示例:
1
2
3
4
int* p = nullptr;
if (p == nullptr) { // 安全判断空指针
// 避免无效访问
}

(3)函数指针

  • 定义:指向函数的指针,语法:返回类型 (*指针名)(参数类型列表)
  • 用途:作为函数参数 / 返回值,实现回调函数(如排序算法中的比较器)。
  • 示例:
1
2
3
4
5
6
7
8
// 目标函数:两数相加
int add(int a, int b) { return a + b; }

int main() {
int (*func_ptr)(int, int) = add; // 函数指针指向add
int res = func_ptr(3, 5); // 调用:res=8(等价于(*func_ptr)(3,5))
return 0;
}

(4)指针数组与数组指针

原则:[]优先级高于*()可提升*的优先级,需通过语法结构判断本质。

类型 语法格式 逻辑(优先级分析) 示例
指针数组 类型* 数组名[数组长度]; []优先级高,先构成数组,元素是指针 int* arr[3];(3 个 int * 指针的数组)
数组指针 类型 (*指针名)[数组长度]; ()提升*优先级,先是指针,指向 “固定长度的数组” int (*p)[3];(指向含 3 个 int 的数组)
  • 示例解析:
1
2
3
int a[3] = {1,2,3};
int* arr[3]; // 指针数组:每个元素是int*,可指向不同int变量
int (*p)[3] = &a; // 数组指针:p指向整个数组a,*p等价于a(数组首地址)

2. 指针的操作

  • 解引用:*p,访问指针指向的变量值。
  • 取地址:&变量,获取变量的内存地址,赋值给指针。
  • 算术运算:仅对 “指向数组的指针” 有意义,步长 = 指向类型的大小(如int* pp++移动 4 字节)。

二、引用

引用本质是 “变量的别名”,语法上无独立内存(编译器通常用指针实现,但对外隐藏细节),核心优势是简洁安全。

1. 特性

  1. 必须初始化:定义时必须绑定已存在的变量(无空引用),否则编译报错。

    1
    2
    3
    4
    int a = 10;
    int& r = a; // 正确:绑定变量a
    // int& r; // 错误:未初始化
    // int& r = nullptr; // 错误:无空引用
  2. 不可改指向:绑定后无法切换到其他变量,赋值操作是修改 “绑定变量的值”。

    1
    2
    int b = 20;
    r = b; // 不是更改绑定,而是将a的值改为20(a=20,r仍绑定a)
  3. 内存特性:sizeof(r) = sizeof(原变量)(无额外内存开销)。

    1
    cout << sizeof(r); // 4字节(与int类型大小一致)

2. 应用

(1)函数参数(避免拷贝 + 修改实参)

  • 替代指针,语法更简洁,无野指针风险,适合大对象传递(无拷贝开销)。
  • 示例:
    1
    2
    3
    void update(int& x) { x *= 2; }
    int a = 5;
    update(a); // a=10(直接修改实参)

(2)函数返回值(避免拷贝)

  • 可返回 “全局变量、静态变量、类成员变量” 的引用(生命周期长于函数),避免返回对象的拷贝开销。

  • 禁忌:禁止返回局部变量的引用(函数结束后局部变量销毁,引用变为悬垂引用)。

  • 示例(正确):

    1
    2
    int g_val = 10;
    int& get_global() { return g_val; } // 返回全局变量引用,安全
  • 示例(错误):

    1
    2
    3
    4
    int& get_local() {
    int x = 20;
    return x; // 错误:x是局部变量,函数结束后销毁
    }

三、本质区别

  • 指针:独立的变量,存储目标变量的内存地址(占用 4/8 字节内存,与系统位数相关)。
  • 引用:非独立变量,是目标变量的 “别名”(语法层面无独立内存,编译器通常用指针实现,但对外隐藏指针操作)。

四、特性对比表

对比特性 指针(Pointer) 引用(Reference)
初始化 可延迟初始化(但未初始化是野指针) 必须定义时初始化,且绑定已存在变量
指向可修改性 可重新指向其他变量(p = &b; 合法) 绑定后不可更改指向(r = b; 是修改原变量值)
空值支持 支持(int* p = nullptr; 无空引用(必须绑定有效变量)
解引用操作 需显式用 **p = 10; 无需解引用(r = 10; 直接修改绑定变量)
算术运算 支持(步长 = 指向类型大小) 不支持(非独立对象,无地址可运算)
多级嵌套 支持(int** p 指向指针的指针) 不支持(int&& r 是右值引用,非 “引用的引用”)
语法复杂度 较高(需关注*/&/ 空指针 / 野指针) 较低(简洁直观,无指针操作风险)

五、补充

1. 作为函数参数的传递机制

(1)指针传参

  • 本质:“值传递地址”—— 函数接收的是实参地址的拷贝,修改指针本身(如p = &b;)不影响实参指针;但修改指针指向的内容(*p = 10;)会影响实参变量。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    void func(int* p) {
    *p = 20; // 影响实参变量(修改指向的内容)
    p = nullptr; // 不影响实参指针(仅修改拷贝)
    }
    int a = 10;
    int* ptr = &a;
    func(ptr); // a=20,ptr仍指向a(未变)

(2)引用传参

  • 本质:传递 “变量别名”—— 函数内修改引用(r = 20;)直接修改实参;且无法修改引用的指向(语法禁止)。

2. 返回值的 “悬垂问题”

  • 指针与引用的共同禁忌:禁止返回局部变量(生命周期随函数结束而销毁)。
    • 返回局部变量的指针:得到野指针(指向已释放内存),访问即未定义行为。
    • 返回局部变量的引用:得到悬垂引用(绑定已销毁变量),访问即未定义行为。

3. const 修饰

(1)const 修饰指针(左定值,右定向)

  • 口诀:const 靠近谁,就限制谁 —— 左定值(限制指向的内容不可改),右定向(限制指针本身不可改)。
    1
    2
    3
    const int* p; // 常量指针(左定值):指向的内容不可改(*p不能赋值),指针可改指向
    int* const p; // 指针常量(右定向):指针本身不可改指向,指向的内容可改(*p可赋值)
    const int* const p; // 指向常量的指针常量:内容和指向均不可改

(2)const 引用(const T&

  • 特性:不可通过引用修改绑定变量,但变量本身可被其他方式修改。
  • 用途:可绑定临时对象(字面量、表达式结果),常用于函数参数(接收任意类型参数,避免拷贝)。
  • 示例:
    1
    2
    3
    4
    5
    6
    const int& r1 = 10; // 合法:const引用绑定字面量(临时对象)
    const int& r2 = 3 + 4; // 合法:绑定表达式结果
    int a = 5;
    const int& r3 = a;
    // r3 = 10; // 错误:const引用不可修改绑定变量
    a = 10; // 合法:变量本身可通过其他方式修改(r3的值同步变为10)