指针数组是一个数组,每个格子里装的是指针:int *p[5]; 就是"5 个 int*"。它最实用的场景是装一排字符串——char *names[3] = {"Tom", "Jerry", "Spike"};,省空间又整齐。必须和"数组指针"分清:int *p[5](5 个指针)vs int (*p)[5](1 个指针,指向 5 个 int 的数组)——差别只在一对括号,靠"[] 优先级高于 *"判断。
这一节把两条线合在一起:7.1 的数组 + 7.4 的指针。它也是 7.5.2 埋下的那句话的答案——当时说 int (*row)[4] 的括号不能省,"省了就变成 7.7 要讲的指针数组"。另外,7.6.2 的"函数表"用的也是指针数组。
一、声明与读法
int *p[5]; // 一个数组:5 格,每格是一个 int*
double *q[3]; // 3 格,每格是 double*
char *names[4]; // 4 格,每格是 char*
怎么一眼看出它是"数组"而不是"指针"?用 3.2 的优先级:
[]的优先级高于*→ 所以p先和[5]结合 →p是数组 → 再把每格看成int *。
int * p [5]
↑ ↑
│ └── 先结合:p 是"5 个格子的数组"
└────── 每格是 int*
一句话:"指针数组"的重心落在"数组"两个字上——它是个数组,里面装指针。
二、和"数组指针"分清(本节最大的坑)
int *p[5]; // ① 指针数组:5 个 int*
int (*p)[5]; // ② 数组指针:1 个指针,指向"含 5 个 int 的数组"
| 对比 | int *p[5] |
int (*p)[5] |
|---|---|---|
| 是什么 | 数组(5 格) | 指针(1 个) |
sizeof(p) |
5 × 4 = 20(或 40) | 4(或 8) |
p + 1 跳多少 |
跳过一个指针 | 跳过 20 字节(一整排 5 个 int) |
| 典型用途 | 装一排字符串 | 指向二维数组的一整行(7.5.2) |
判断口诀:看 * 和名字之间有没有括号。
- 有括号
(*p)→*先跟名字结合 → p 是指针(数组指针); - 没括号
*p[5]→[]优先级高,先跟名字结合 → p 是数组(指针数组)。
记忆画面:括号像一件外套,套住谁,谁就先"定性"。
三、最常用场景:装一排字符串
字符串本质是 char*(7.5.3),所以"一排字符串"自然就是 char* 数组:
char *names[3] = {"Tom", "Jerry", "Spike"};
它在内存里是这样:
names[0] ──► "Tom"
names[1] ──► "Jerry"
names[2] ──► "Spike"
对比二维字符数组:
char names2[3][10] = {"Tom", "Jerry", "Spike"};
| 对比 | char *names[3] |
char names2[3][10] |
|---|---|---|
| 占用 | 3 个指针 + 各串实际长度 | 固定 30 字节(不管内容多短) |
| 长度是否可不同 | ✅ 各串可长短不一 | ❌ 每行都固定 10 格(短的浪费) |
| 内容能不能改 | ❌ 指向只读常量区(7.5.3) | ✅ 是数组,能改 |
| 适合 | 一批只读的名字表 | 需要修改内容的定长表 |
一句口诀:长短短不齐、只读不改 → 用指针数组;长度整齐、需要改 → 用二维字符数组。
四、动手 Demo:指针数组装名字表
#include <iostream>
using namespace std;
int main() {
char *names[4] = {"小明", "小红", "小刚", "小美"};
cout << "---- ① 遍历打印 ----" << endl;
for (int i = 0; i < 4; i++)
cout << i + 1 << ". " << names[i] << endl; // cout 对 char* 直接打印整串
cout << "---- ② 看它装的是什么 ----" << endl;
cout << "sizeof(names) = " << sizeof(names) << endl; // 16(4 个指针)
cout << "sizeof(names[0]) = " << sizeof(names[0]) << endl; // 4(一个指针)
cout << "元素个数 = " << sizeof(names)/sizeof(names[0]) << endl; // 4
cout << "---- ③ 每个格子的地址与内容 ----" << endl;
for (int i = 0; i < 4; i++)
cout << "names[" << i << "] 地址=" << (void*)names[i]
<< " 内容=" << names[i] << endl;
cout << "---- ④ 交换两个名字(7.6.1 的 swap 派上用场)----" << endl;
char *t = names[0]; names[0] = names[3]; names[3] = t;
for (int i = 0; i < 4; i++) cout << names[i] << " ";
cout << endl;
return 0;
}
看到什么算成功:① 依次打印 4 个名字;② 16、4、4;③ 四行"地址 + 内容"(交换前后,地址不变,内容按顺序打印);④ 交换后输出 小美 小红 小刚 小明。
三个要点:
| 现象 | 说明 |
|---|---|
sizeof(names) = 16 |
整个数组 = 4 个指针 × 4 字节。它装的是纸条,不是内容 |
打印 names[i] 得到整串名字 |
char* 的 cout 特权(7.5.3) |
| ④ 交换只换了指针 | 没有搬动任何字符,只是把两张纸条对调——效率极高 |
第四点是本节最值钱的地方:如果用的是 char names2[4][10],交换两个名字就得逐字符搬运(10 次赋值);而指针数组只换两个地址(1 次赋值)。这就是"用指针处理数据"的核心价值:搬地址,不搬内容。
30 秒实验:把 char *names[4] 改成 char *p[4] 并在后面写 int (*q)[4];,分别打印 sizeof(p) 和 sizeof(q)——前者是 16(4 个指针),后者是 4(1 个指针)。一对括号,数字差了 4 倍。
五、常见误区
- "
int *p[5]是 5 个 int 的指针…不对,是数组指针"——是指针数组(5 个指针组成的数组)。写起来像,含义完全不同,认括号。 - "指针数组和二维数组差不多"——不一样。指针数组各行长度可不同、内容不可改(若指向字面量);二维字符数组各行等长、内容可改。
- "交换字符串要逐字符搬"——用指针数组时,只交换指针就行(换纸条,不搬字)。
- "
char *names[3] = {"Tom",...};可以改names[0][0] = 'X';"——会崩。指向的是字符串常量区(7.5.3)。要改内容就用二维字符数组。 - "指针数组只能装字符串"——不限于此。可以装任何类型的指针,包括 7.6.2 的函数指针(函数表就是指针数组)。
自测三问(点开对答案)
1. `int *p[5]` 和 `int (*p)[5]` 分别是什么?——前者是**指针数组**(5 个 `int*`);后者是**数组指针**(1 个指针,指向含 5 个 int 的数组)。 2. 怎么快速判断?——看 `*` 和名字之间有没有括号;`[]` 优先级高于 `*`,**有括号是指针,没括号是数组**。 3. 用指针数组装名字,交换两个名字要做什么?——**只交换两个指针**(换纸条),不用搬动字符。- ☐ 7.8 引用(下一节)
相关笔记
- 指针与二维数组 —— 行指针
int (*p)[4]的出处 - 指针与字符数组 —— 字符串常量不可改
- 指向函数的指针 —— 函数表也是指针数组
- 结合性和优先级 —— 判断括号的根据
- 🔷 C++基础 · 索引 —— 全课路线图