📄 一句话概括
让指针指向数组的第一个元素(int *p = a;),然后就有四种完全等价的取元素方式:a[i]、*(a+i)、p[i]、*(p+i)。更妙的是——指针自己就能一步步往前走:*p++ 先取值、再把指针挪一格,一趟循环走完整排柜子。但千万记住 sizeof 的区别:数组名是全排,指针是一张纸条。
📝 本节和前面几节的关系
7.5 讲了"数组名就是首地址、a[i] ≡ *(a+i)"。这一节把它落到实处:写成可运行的遍历。用到的零件:7.4.3 的"指针加 1 跳一格"、3.2 的优先级(*p++ 谁先算)、7.1.2 的越界坑(指针走过头同样越界)。这是本章第一个"指针真正好用"的地方。
一、让指针指向数组
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // 指向 a[0](等价于 &a[0])
此时 p 和 a 指的是同一处——都可以用来访问这排柜子。
二、四种等价写法
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
a[2] // 30
*(a + 2) // 30 ← 数组名当指针用
p[2] // 30 ← 指针当数组用
*(p + 2) // 30
为什么能互相替代?因为三者都是同一个算式:首地址 + 偏移 再解引用。
| 写法 | 拆解 | 说明 |
|---|---|---|
a[i] |
编译器自动翻译成 *(a + i) |
下标法是"语法糖" |
*(a + i) |
首地址 + i 格,再取值 | 本质写法 |
p[i] |
编译器自动翻译成 *(p + i) |
指针也能用下标 |
*(p + i) |
同 ② | p 和 a 此时指向相同 |
记住这一句就够了:[] 只是 *( + ) 的简写。
三、用指针遍历:两种经典写法
写法 1:指针自增(最"指针"的写法)
int a[5] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
cout << *p << " ";
p++; // 指针往后走一格
}
更紧凑的写法是 *p++——它在一行里干两件事:
for (int i = 0; i < 5; i++)
cout << *p++ << " ";
*p++ 到底怎么算?(3.2 的优先级在这里上岗)
| 写法 | 拆解 | 结果 |
|---|---|---|
*p++ |
*(p++):先取值,再把 p 挪一格 |
打印当前格的值 |
(*p)++ |
先取 *p,再把那个值加 1 |
把当前格的值 +1 |
++*p |
++(*p):把当前格的值 +1 |
同上一行 |
注意 *p++ 里 ++ 优先于 *,所以它是 *(p++) 而不是 (*p)++。这两个写法结果完全不同,是本节最需要动手验证的一处。
写法 2:首尾指针夹逼(更清晰)
int *begin = a;
int *end = a + 5; // 指向"最后元素之后"的位置
for (int *q = begin; q < end; q++)
cout << *q << " ";
这种写法不用数 i,靠"指针有没有走到尾"来判断。a + 5 是尾后位置(第 5 格之后,也就是第 6 格)——它本身不能解引用,只用来当"终点线"。
四、实例09:通过指针变量获取数组中的元素
#include <iostream>
using namespace std;
int main() {
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // 指向第一个元素
cout << "---- ① 四种写法对照 ----" << endl;
for (int i = 0; i < 5; i++) {
cout << a[i] << " " << *(a+i) << " " << p[i] << " " << *(p+i) << endl;
}
cout << "---- ② 指针自增遍历 ----" << endl;
p = a; // 别忘了让 p 回到开头!
for (int i = 0; i < 5; i++) cout << *p++ << " ";
cout << endl;
cout << "---- ③ 首尾夹逼遍历 ----" << endl;
for (int *q = a; q < a + 5; q++) cout << *q << " ";
cout << endl;
cout << "---- ④ 指针相减求元素个数 ----" << endl;
int *first = a;
int *last = &a[4];
cout << "元素个数 = " << (last - first) + 1 << endl; // 5
return 0;
}
看到什么算成功:
---- ① 四种写法对照 ----
10 10 10 10
20 20 20 20
30 30 30 30
40 40 40 40
50 50 50 50
---- ② 指针自增遍历 ----
10 20 30 40 50
---- ③ 首尾夹逼遍历 ----
10 20 30 40 50
---- ④ 指针相减求元素个数 ----
元素个数 = 5
三处千万注意:
- ② 之前必须
p = a;——上一段循环里p已经被推到末尾了(这就是*p++的副作用)。忘了复位,第二段会打印垃圾。 - ③ 的终止条件是
q < a + 5(不是<=)——a + 5是尾后位置,解引用它就越界。 - ④ 用
last - first + 1算元素个数:指针相减得到"相隔几格"(7.4.3),加 1 才是总数。
30 秒实验:把 ② 里的 *p++ 改成 (*p)++,重跑——输出会变成 11 21 31 41 51,而且打印的是原值、格子里已经加过 1 了。再把数组打印一遍,会看到 a 真的被改成 {11,21,31,41,51}。一行之差,一个只是"走过",一个是"改掉"。
五、常见误区
- "数组名和指针完全一样"——
sizeof不一样(7.5):sizeof(a)是 20、sizeof(p)是 4。数组名也不能a++。 - "
*p++是先把值加 1"——反了。是"先取值,再把指针挪一格"。要改值得写(*p)++。 - "指针遍历完还能接着用"——循环结束时
p已经指向末尾之后,再用它就越界。要么中途复位,要么另开一个指针。 - "
a + 5就是最后一个元素"——a + 5是尾后位置(第 5 格之后),不能解引用;最后一个元素是a[4](a + 4)。 - "指针访问数组比下标快很多,所以都该用指针"——现代编译器会把
a[i]优化成同样的代码,性能差别可以忽略。选哪种看可读性:a[i]更适合日常,指针遍历更适合"边走边改地址"的场景。
自测三问(点开对答案)
1. `p[3]` 和 `*(p + 3)` 有关系吗?——**完全等价**,编译器把 `p[3]` 翻译成 `*(p + 3)`。 2. `*p++` 和 `(*p)++` 有什么不同?——前者"取值 + 指针挪一格",后者"把当前格的值加 1"。 3. 指针遍历结束后,为什么不能直接再遍历一遍?——因为指针已经跑到末尾之后了,**要复位到开头**(`p = a;`)。- ☐ 7.5.2 指针与二维数组(下一节)
相关笔记
- 指针与数组 —— 数组名就是首地址
- 指针运算 —— 加 1 跳一格
- 结合性和优先级 ——
*p++谁先算 - 一维数组的引用 —— 越界不报错
- 🔷 C++基础 · 索引 —— 全课路线图