🔷 第七章 数组、指针和引用
📄 一句话概括

指针数组是一个数组,每个格子里装的是指针: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)

判断口诀:看 * 和名字之间有没有括号。

记忆画面:括号像一件外套,套住谁,谁就先"定性"。

三、最常用场景:装一排字符串

字符串本质是 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 倍。

五、常见误区

  1. "int *p[5] 是 5 个 int 的指针…不对,是数组指针"——是指针数组(5 个指针组成的数组)。写起来像,含义完全不同,认括号。
  2. "指针数组和二维数组差不多"——不一样。指针数组各行长度可不同、内容不可改(若指向字面量);二维字符数组各行等长、内容可改。
  3. "交换字符串要逐字符搬"——用指针数组时,只交换指针就行(换纸条,不搬字)。
  4. "char *names[3] = {"Tom",...}; 可以改 names[0][0] = 'X';"——会崩。指向的是字符串常量区(7.5.3)。要改内容就用二维字符数组。
  5. "指针数组只能装字符串"——不限于此。可以装任何类型的指针,包括 7.6.2 的函数指针(函数表就是指针数组)。
自测三问(点开对答案) 1. `int *p[5]` 和 `int (*p)[5]` 分别是什么?——前者是**指针数组**(5 个 `int*`);后者是**数组指针**(1 个指针,指向含 5 个 int 的数组)。 2. 怎么快速判断?——看 `*` 和名字之间有没有括号;`[]` 优先级高于 `*`,**有括号是指针,没括号是数组**。 3. 用指针数组装名字,交换两个名字要做什么?——**只交换两个指针**(换纸条),不用搬动字符。

相关笔记