📄 一句话概括
指针能加减整数、能互相相减、能比较大小。但"加 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 字节
三处要点:
p+1的地址比p大 4,而q+1比q大 8——同样写+1,跳的距离由类型决定;*(p+1)取到的是 20——说明"跳一格"跳到的正好是下一个元素(这就是数组"连续存放"的用处);- 最后两行一个说"2 个元素"、一个说"8 字节"——同一个距离的两种说法。
30 秒实验:把 int a[3] 改成 char a[3] = {'A','B','C'},把 int *p 改成 char *p(后面几行同步改),重跑——p+1 的地址只比 p 大 1。同一段代码,步长跟着类型变。
五、常见误区
- "
p + 1就是地址加 1 字节"——本节最大的坑。加的是 1 个元素(sizeof(类型)字节)。 - "两个指针可以相加"——不行。
p1 + p2没有意义(比如"两个门牌号相加"),编译器直接报错。 - "任意两个指针都能相减"——只有指向同一块数组的指针相减才有意义;跨数组相减属于未定义行为。
- "
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。- ☐ 7.4.4 指向空的指针与空类型指针(下一节)
相关笔记
- 指针运算符和取地址运算符 —— 先会取值,再学走动
- 指针与一维数组 —— 指针运算的主战场
- 结合性和优先级 ——
*p++到底怎么算 - 整型变量 —— 各种类型占几字节
- 🔷 C++基础 · 索引 —— 全课路线图