前面我们一直通过变量名读取或修改数据,例如把 data 从 3 改成 5。程序运行时,这些数据并不是凭空存在的,而是保存在内存中。理解指针,可以先从“一个变量的值存放在哪里”开始。
变量的值与存放位置
可以把内存想象成一排带编号的储物格。格子里放的是数据,编号告诉我们到哪里找它。对于一个变量,需要区分三个概念:
- 变量名:代码里使用的名字,例如
data。 - 值:变量里保存的内容,例如整数
3。 - 地址:这个变量在内存中的位置。
变量的值改变了,并不意味着它搬到了另一个位置。例如把 data 从 3 改成 5,只是同一个位置里的内容变了。
用地址找到变量
知道变量名可以找到它;知道它的地址,也可以找到它。在代码中,取地址运算符 & 用来获取一个变量的地址:&data 表示“data 存放在哪里”,而不是“data 的值是多少”。
内存地址通常写成十六进制,例如 0x1000。本节图中的地址都是便于观察的示意编号;实际地址由运行环境决定,不需要记住这些数字。
让一个变量保存地址
如果把一个地址保存下来,之后就能沿着这个地址找到原来的变量。专门用来保存地址的变量,就是指针变量(pointer variable),简称指针。
int *p = &data; 创建一个名为 p 的指针,并把 data 的地址保存在里面。int * 表示这个指针用来指向整数变量。我们把这种关系叫作“p 指向 data”。
注意,p 保存的是地址,不是整数 3 的副本。p 自己也是一个变量,所以它也有自己的存放位置:&p 是指针变量自身的地址,与它保存的 &data 是两回事。
沿地址读取与修改
知道 p 指向哪里后,就可以用 *p 访问那个位置里的变量,这个操作叫作解引用(dereference):
- 读取
p,得到它保存的地址。 - 读取
*p,得到被指向变量的值。 - 执行
*p = 5;,修改被指向变量的值,不改变指针的指向。
声明中的 * 用来说明“这是一个指针”;表达式中的 *p 则表示“沿着 p 保存的地址访问变量”。下面把这些操作放进一个完整程序:
#include <stdio.h>
int main(void) {
int data = 3;
int *p = &data;
printf("data = %d\n", data);
printf("&data = %p\n", (void *)&data);
printf("p = %p\n", (void *)p);
printf("*p = %d\n", *p);
*p = 5;
printf("data = %d\n", data);
return 0;
}地址与解引用内存图
解锁完整互动实验、进阶解析与精选参考答案。
知识测验
知识测验
空指针与安全检查
指针也可能暂时不指向任何变量,这时应明确把它初始化为空指针。空指针可以用来判断“当前有没有目标”,但不能对它解引用。
未初始化的指针并不自动为空,它的值是不确定的。声明指针后,应立即让它指向有效对象,或者设为空;读取或写入目标前先检查指针是否为空。
int *q = NULL;
if (q != NULL) {
*q = 5;
}知识测验
把地址传给函数
普通参数只复制值,直接交换函数里的两个整数副本不会改变调用者的变量。要修改原变量,可以把它们的地址传给函数。
调用 swap(&x, &y) 后,形参 a 保存 x 的地址,b 保存 y 的地址。函数通过 *a、*b 访问的仍是 main 中的 x、y。交换过程中,改变的是两个整数的值,两个指针的指向不变。
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void) {
int x = 3;
int y = 5;
swap(&x, &y);
printf("%d %d\n", x, y); // 5 3
return 0;
}分三步交换:沿着 a、b 找到 x、y
解锁完整互动实验、进阶解析与精选参考答案。
通过指针清零
编写 set_to_zero(int *value),把调用者的整数改成 0。在 main 中用 score = 42 测试并输出结果。
- 定义接收整数指针的 clear_value 函数。
- 检查指针后通过 *value 写入 0。
- 在 main 中传入变量地址,并输出调用前后的值。
把一对整数按升序排列
编写 order_pair(int *a, int *b)。只有 *a 大于 *b 时才交换,使用 9 和 4 测试,输出应为 4 9。
- 函数接收两个整数地址。
- 仅当第一个值大于第二个值时交换解引用后的值。
- 在 main 中用两组输入验证已排序和未排序情况。
安全递增
编写 safe_increment,空指针时返回 false;否则把目标值加一并返回 true。分别用有效地址和 NULL 调用。
- 函数接收整数指针并先检查空指针。
- 有效时执行 (*value)++。
- 返回成功或失败状态,并在 main 中测试两种情况。