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

指针能加减整数、能互相相减、能比较大小。但"加 1"加的不是 1 个字节,而是 1 个"元素"——int *p 加 1,地址就往后跳 4 字节;double *p 加 1,跳 8 字节。这条设计的用意只有一个:让指针能在数组上"自然地一步一步走"(7.5.1 全靠它)。

📝 本节和前面几节的关系

7.4.2 让指针能"照着门牌号取值"。这一节让指针能"自己走到下一个门牌号"。新知识只有一个——步长(每次走多远)由类型决定。它把 7.1 那种"靠下标 a[i] 逐格访问"升级成了"靠指针 *(p+i) 逐格走",也正是 7.5.1 讲"数组与指针等价"的地基。

一、指针能做的四件事

运算 写法 含义
加整数 p + n 往后走 n 个元素
减整数 p - n 往前走 n 个元素
两个指针相减 p2 - p1 相隔几个元素(不是几个字节)
比较 p1 < p2、p1 == p2 谁在前、是不是同一个位置

不能做的:两个指针相加(p1 + p2)、指针乘除。这些没有意义——两个门牌号相加能表示什么?

二、核心:加 1 加的是"一格"

int    a[3];
int   *p  = a;        // 假设 p 里是 1000
cout << p + 1;        // 1004  ← 加了 4 字节(sizeof(int))
cout << p + 2;        // 1008

double b[3];
double *q = b;        // 假设 q 里是 2000
cout << q + 1;        // 2008  ← 加了 8 字节(sizeof(double))

规律:指针 + n 实际增加的地址 = n × sizeof(指针指向的类型)。

指针类型 +1 实际跳多少字节 为什么
char * 1 一格字符 = 1 字节
int * 4 一格整数 = 4 字节
double * 8 一格小数 = 8 字节
char * 的数组元素 1 同上

为什么这么设计?想想 7.1 的数组:int a[5]; 里的 5 格紧挨着排,每格 4 字节。如果指针 +1 只加 1 字节,那它就会落到第一格的中间——完全没有意义。只有按"一格多大"来跳,指针才能准确地从一个元素走到下一个元素。

一句话记忆:指针的加减,是按"元素"计数的,不是按字节。

三、指针相减:得到"相隔几格"

int  a[5] = {10, 20, 30, 40, 50};
int *p1 = &a[0];
int *p2 = &a[4];

cout << p2 - p1 << endl;                 // 4     ← 相隔 4 个元素
cout << (char*)p2 - (char*)p1 << endl;   // 16    ← 相隔 16 字节(4 × 4)

两个结果都是 4 和 16,但含义不同:p2 - p1 是元素个数(这才是有用的信息——"它们之间隔了 4 格");转成 char* 相减才是字节数。

前提:两个指针必须指向同一块数组,否则结果没有意义(标准里属于未定义行为)。

四、实例07:输出指针运算后的地址值

#include <iostream>
using namespace std;

int main() {
    int    a[3] = {10, 20, 30};
    double b[3] = {1.5, 2.5, 3.5};

    int    *p = a;
    double *q = b;

    cout << "---- int 指针(每格 4 字节)----" << endl;
    cout << "p   → " << p     << "  值 = " << *p     << endl;
    cout << "p+1 → " << p + 1 << "  值 = " << *(p+1) << endl;
    cout << "p+2 → " << p + 2 << "  值 = " << *(p+2) << endl;

    cout << "---- double 指针(每格 8 字节)----" << endl;
    cout << "q   → " << q     << "  值 = " << *q     << endl;
    cout << "q+1 → " << q + 1 << "  值 = " << *(q+1) << endl;

    cout << "---- 相减与字节 ----" << endl;
    cout << "&a[2] - &a[0]      = " << (&a[2] - &a[0]) << " 个元素" << endl;
    cout << "(char*)&a[2]-(char*)&a[0] = "
         << ((char*)&a[2] - (char*)&a[0]) << " 字节" << endl;
    return 0;
}

看到什么算成功(地址会变,但差值固定):

---- int 指针(每格 4 字节)----
p   → 0x61ff00  值 = 10
p+1 → 0x61ff04  值 = 20      ← 地址差 4
p+2 → 0x61ff08  值 = 30      ← 再差 4
---- double 指针(每格 8 字节)----
q   → 0x61fee0  值 = 1.5
q+1 → 0x61fee8  值 = 2.5     ← 地址差 8
---- 相减与字节 ----
&a[2] - &a[0]      = 2 个元素
(char*)&a[2]-(char*)&a[0] = 8 字节

三处要点:

30 秒实验:把 int a[3] 改成 char a[3] = {'A','B','C'},把 int *p 改成 char *p(后面几行同步改),重跑——p+1 的地址只比 p 大 1。同一段代码,步长跟着类型变。

五、常见误区

  1. "p + 1 就是地址加 1 字节"——本节最大的坑。加的是 1 个元素(sizeof(类型) 字节)。
  2. "两个指针可以相加"——不行。p1 + p2 没有意义(比如"两个门牌号相加"),编译器直接报错。
  3. "任意两个指针都能相减"——只有指向同一块数组的指针相减才有意义;跨数组相减属于未定义行为。
  4. "p++ 和 (*p)++ 一样"——完全不同!p++ 是指针往后走一格;(*p)++ 是把那一格的值加 1(因为 ++ 优先作用在 *p 上,3.2 的优先级)。这个差别在 7.5.1 的遍历里天天用到。
自测三问(点开对答案) 1. `int *p` 从地址 1000 变成 `p+3`,地址是多少?——`1012`(每格 4 字节,加 12)。 2. `double *q` 和 `double *r` 相隔 5 个元素,它们的**字节**距离是多少?——`40` 字节(5 × 8)。 3. `p++` 和 `(*p)++` 有什么不同?——`p++` 让**指针**走一格;`(*p)++` 让**那一格的值**加 1。

相关笔记