指针是 C 语言最强大的特性,也是新手劝退率最高的知识点。其实它只做一件事:保存另一个变量的内存地址。把地址想成"门牌号",指针就是写着门牌号的小纸条——概念并不难,难的是习惯用它思考。
1. 内存与地址
程序运行时的每个变量都住在内存的某个"房间"里,房间有编号,就是地址。用取地址运算符 & 可以拿到变量的地址:
#include <stdio.h>
int main(void) {
int x = 42;
printf("x 的值: %d\n", x);
printf("x 的地址: %p\n", (void *)&x);
return 0;
}
%p 以十六进制打印地址,每次运行地址可能不同——这是正常的,地址由操作系统分配。
2. 指针的声明
声明指针用 类型 *名字,星号表示"这是一个指针",类型说明它指向什么类型的数据:
#include <stdio.h>
int main(void) {
int x = 42;
int *p = &x; // p 保存 x 的地址
printf("p 指向的地址: %p\n", (void *)p);
return 0;
}
建议写作 int *p(星号靠变量),含义是 *p 是一个 int。
3. 解引用:通过地址访问数据
有了地址,可以用 *指针名 取出或修改它指向的变量,这叫解引用:
#include <stdio.h>
int main(void) {
int x = 42;
int *p = &x;
printf("解引用 *p = %d\n", *p); // 42
*p = 100; // 通过指针修改 x
printf("修改后 x = %d\n", x); // 100
return 0;
}
注意区分:p 是地址,*p 是那个地址上的值,&x 是取地址——三者各司其职。
4. 指针与数组
数组名本身就是指向首元素的指针(常量指针),下标访问 arr[i] 等价于指针运算 *(arr + i):
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30};
int *p = arr; // 等价于 &arr[0]
printf("arr[1] = %d\n", arr[1]);
printf("*(p+1) = %d\n", *(p + 1)); // 也是 20
printf("p[2] = %d\n", p[2]); // 30,指针也能用下标
return 0;
}
指针加 1 不是"地址 + 1 字节",而是跳到下一个同类型元素——对 int 来说地址加 4。
5. 指针参数:传址调用
前面学过函数参数是值传递,想修改外部变量必须传地址。经典的 swap 函数:
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 1, y = 2;
swap(&x, &y);
printf("x=%d, y=%d\n", x, y); // x=2, y=1
return 0;
}
调用时传 &x、&y(地址副本),函数内通过地址修改了原来的变量。
6. 常见陷阱
| 陷阱 | 说明 | 对策 |
|---|---|---|
| 未初始化指针 | 指向随机地址,解引用必崩 | 声明时初始化为 NULL |
| 野指针 | 指向已释放的内存 | free 后置 NULL |
| 越界访问 | 指针超出数组范围 | 严格检查下标边界 |
| 类型不匹配 | int* 赋给 char* | 保持类型一致或显式转换 |
#include <stdio.h>
int main(void) {
int *p = NULL; // 好习惯:先置空
if (p != NULL) { // 使用前检查
printf("%d\n", *p);
} else {
printf("指针为空,安全跳过\n");
}
return 0;
}
💡 学习建议:用纸笔画出"变量框"和"地址箭头",把本文每个例子的内存图都画一遍。指针学的是"图",不是"代码"。