C++ 竞赛教程

九、指针与引用

指针是 C++ 的核心特性之一,理解指针需要先了解计算机内存的工作方式。引用则是更安全、语法更简洁的变量别名机制。

内存与地址

计算机的内存就像一排有编号的格子,每个格子都有一个唯一的编号,称为地址。每个变量都占据内存中的一段连续格子。使用 & 取址运算符可以获取变量的首地址:

C++ · 取址运算符
1int a = 10;
2cout << a; // 输出变量的值:10
3cout << &a; // 输出变量的地址(十六进制,如 0x61fe1c)

程序内存分区

内存区域说明
Stack(栈区)重点存放局部变量、函数参数、返回地址等,由编译器自动管理,出作用域自动释放
Heap(堆区)手动管理动态内存分配区域,由程序员手动申请(new)和释放(delete),不释放会内存泄漏
BSS 段存放未显式初始化或初始化为 0 的全局变量和静态变量
Data 段存放已显式初始化的全局变量和静态变量
Text 段(代码区)存储程序的机器指令,只读
📌
初学者重点:先关注栈区堆区即可。栈区存放局部变量(自动管理),堆区用于动态分配(手动管理),其余区域了解即可。

指针变量

指针变量存储的是另一个变量的内存地址。通过指针,可以间接访问和修改它所指向的变量。

int a = 10; int *p = &a; // p 中存储 a 的地址
···
···
0x61fe14
p
0x61fe1c
0x61fe18
a
10
0x61fe1c
···
···
0x61fe20
变量 a — 存放值 10
指针 p — 存放 a 的地址(0x61fe1c)
其他内存区域

三个关键符号

符号出现场景作用
& 变量前(如 &a 取址运算符:获取变量的内存地址
* 声明语句中(如 int *p 指针类型声明:声明一个指针变量
* 使用指针时(如 *p 解引用运算符:访问指针所指向的值
C++ · 指针基本用法
1int *p; // 声明:p 是一个指向 int 类型的指针
2int a = 10;
3p = &a; // 让 p 指向变量 a(存储 a 的地址)
4
5cout << p; // 输出 a 的地址(如 0x61fe1c)
6cout << *p; // 解引用:输出 p 所指向的值,即 10
7*p = 20; // 通过指针修改 a 的值
8cout << a; // 输出 20(a 被间接修改了)
☠️
野指针!指针使用前必须初始化(指向一个有效地址)。未初始化的指针是野指针,使用它会导致未定义行为(程序崩溃或数据错乱)。
不确定指针应指向哪里时,先初始化为 nullptrint *p = nullptr;。这样意外解引用时会立刻崩溃,便于调试,比野指针安全得多。

指针与数组

数组名本身就是一个指向首元素地址的指针常量,可以像指针一样使用。

int arr[] = {1, 2, 3, 4, 5}; int *p = arr; // p 指向 arr[0]
p
1
2
3
4
5
索引
[0]
[1]
[2]
[3]
[4]
*p → 1 (arr[0]) *(p+2) → 3 (arr[2]) p[3] → 4 (arr[3])
C++ · 指针访问数组
1int arr[] = {1,2,3,4,5};
2int *p = arr; // 等同于 p = &arr[0]
3
4cout << *p; // 输出 1(arr[0])
5cout << *(p+2); // 输出 3(arr[2])
6cout << p[3]; // 输出 4(arr[3],指针可用下标)
7
8// 用指针遍历数组
9for (int i = 0; i < 5; i++)
10 cout << *(p + i) << " ";

数组名 vs 指针

特性数组名 arr指针 p
是否可改变指向❌ 常量,不能修改✅ 变量,可重新赋值
+ 偏移运算✅ arr + 1✅ p + 1
[ ] 下标访问✅ arr[2]✅ p[2]
⚠️
指针偏移注意:p + 1 并不是将地址加 1,而是偏移一个元素的大小(如 int 指针偏移 4 字节)。解引用前务必确保指针指向有效的数组范围,越界访问是未定义行为。

引用

引用是变量的别名,对引用的所有操作等同于直接操作原变量。引用比指针更安全、语法更简洁,声明时必须立即初始化,且绑定后不能再改变。

int a = 10; int &ref = a; // ref 是 a 的别名
a = 10
ref = 10
同一块内存,两个名字
ref = 20; // 等同于 a = 20 cout << a; // 输出 20(a 也变了)
C++ · 引用用法
1int a = 10;
2int &ref = a; // ref 是 a 的引用(别名),必须在声明时初始化
3cout << ref; // 输出 10(和 a 完全一样)
4ref = 20; // 修改 ref 等同于修改 a
5cout << a; // 输出 20
6
7// 引用最常用于函数参数(实现引用传递)
8void swap(int &x, int &y)
9{
10 int temp = x;
11 x = y;
12 y = temp;
13}

指针 vs 引用

🎯指针(Pointer)
存储另一个变量的地址,通过 * 解引用访问值。
灵活但危险:可以不初始化(野指针),可以改变指向,可以为 nullptr
语法相对复杂,需要 *& 操作。
🔗引用(Reference)
变量的别名,操作引用就是操作原变量。
安全且简洁:必须在声明时初始化,绑定后不能改变,不能为空。
语法像普通变量,无需 * 解引用。
特性指针引用
是否必须初始化❌ 可不初始化(危险)✅ 必须在声明时初始化
能否改变指向✅ 可以随时指向别处❌ 一旦绑定,终生不变
是否可以为空✅ 可以为 nullptr❌ 不能为空
语法复杂度需要 *& 操作简洁,像普通变量
适用场景需要灵活改变指向或可为空函数传参、修改原变量
💡
选择建议:
① 函数参数需要修改原变量时,优先使用引用&),更安全、代码更简洁。
② 需要表示"可能不存在"的对象,或需要动态改变指向时,使用指针
③ 传递大对象(如 string、结构体)时,用 const 引用 避免拷贝开销。