4-1 指针
⏱ 本节预计用时:30分钟
在讲数组那一节我们对指针做了简单的介绍,数组传参传的不是复制品,而是存首元素地址的变量,也就是指针。指针就是存地址的变量,就是这么简单。
指针的语法比较绕,但我相信学完这一讲,你能够看懂大部分使用指针的代码,另外的就是熟练度问题了。
1 两个符号:& 和 *
1.1 & ------ 取地址
每个变量在内存里都有个"门牌号"(之前我们把变量比作格子,现在比作房子),也就是地址。用 &变量名,就能查到它的数字编号是多少:
c
#include <stdio.h>
int main() {
int x = 42;
printf("x 的值:%d\n", x); // 42
printf("x 的地址:%p\n", &x);
// 0x7ffd...(0x开头表示十六进制数)
// %p 是专门打印地址的格式符。
return 0;
}
运行情况:

每次运行,输出地址可能不一样,这是因为运行结束之后,房子被系统回收了,下次它再分配的可能不一样。
1.2 声明指针变量
声明时在变量名前加个 *:
c
int x = 42;
int *p = &x; // p 是一张小纸条,记下了 x 的门牌号
第一次看基本必懵逼,别怕,继续用"房子"来想:
x是一栋房子,里面住着42;&x是这栋房子的门牌号(一串地址数字);int *p = &x;的意思是:再造一个格子p,这个格子专门用来装门牌号 ,现在把x的门牌号塞进去。
所以 p 里装的数字,和 &x 完全一样------p 就是"存着 x 门牌号"的那个格子。打个比方:x 是房子,&x 是门牌号,p 就是那张记着门牌号的小纸条。
在1.1的示例里加入int *p = &x;,printf("x 的地址:%p\n", p);
#include <stdio.h>
int main() {
int x = 42;
int *p = &x;
printf("x 的值:%d\n", x);
printf("x 的地址:%p\n", &x);
printf("x 的地址:%p\n", p);
return 0;
}
运行情况:

1.3 * ------ 解引用
*p 的意思是:顺着纸条找到 x 的房子,看里面的住户。不光能看,还能改:
c
int x = 42;
int *p = &x;
printf("%d\n", *p); // 42 ------ 看房子的住户
*p = 100; // 把 x 房的值改成 100
printf("%d\n", x); // 100 ------ x 真被改了!
所以指针的全部就两件事:
&取地址:问门牌号*解引用:顺着门牌号找到房子,看或改里面的值
这里有一个容易乱的点:* 在1.2是声明用的,意思是"我要造一个装地址的指针变量",和这里的顺着地址去查变量不一样。
在1.2的示例里加入 printf("x 的值:%d\n", *p);
#include <stdio.h>
int main() {
int x = 42;
int *p = &x;
printf("x 的值:%d\n", x);
printf("x 的地址:%p\n", &x);
printf("x 的地址:%p\n", p);
printf("x 的值:%d\n", *p);
return 0;
}
运行情况:

1.4 野指针 和 NULL
野指针,就是声明了、却没给它一个明确地址的指针。它里面装着的是一个随机的"垃圾编号",你根本不知道它指向哪:
c
int *p; // p 里是垃圾值,野指针!
*p = 42; // 顺着一个随机编号去写 42,程序很可能崩溃
就像你手里有张空白纸条,却硬要按上面的"门牌号"去找房子------大概率走错门,甚至闯进别人家。
解决办法:声明时先把它指向"空"。 C 里用 NULL 表示"这个指针现在谁都不指":
c
int *p = NULL; // 明确告诉编译器:p 现在谁都不指
if (p != NULL) { // 安全习惯:用它之前先问一句"你指着没"
*p = 42;
}
NULL 就是一个特殊值(等于 0),意思是"空、没有指向"。
- 声明指针时赋
NULL:不给它留下垃圾编号的机会 - 解引用前先判断
NULL:确认它真的指着东西再动手
2 一张图弄清楚
地址: 0x100 0x104 0x108
┌─────────┬─────────┬─────────┐
│ 42 │ │ 0x100 │
└─────────┴─────────┴─────────┘
x p
(住户42) (记着0x100)
x住在0x100,里面是42p住在0x108,里面是0x100(就是 x 的门牌号)*p= 看地址0x100那房子的住户 =42*p = 100= 把0x100那房子改写成100→ x 变成 100
3 传地址,函数就能改外面的变量
回忆函数那章的"值传递":传进去的是复印件的值,函数里改了,外面纹丝不动。
想把外面的变量也改掉,就把门牌号传进去,让函数顺着地址改原件:
c
#include <stdio.h>
void add10(int *p) {
*p += 10; // 顺着 p 改外面那个变量
}
int main() {
int n = 5;
printf("%d\n", n); // 5
add10(&n);
printf("%d\n", n); // 15
return 0;
}
传地址 → 就能通过地址改外面的变量。 这也是数组传参能改外面的原因------数组名本身就是首元素地址,传数组本质就是传指针。
运行情况:

4 数组名就是指针
数组名是存放着首元素地址的指针常量。
c
#include <stdio.h>
int main() {
int a[5] = {10, 20, 30, 40, 50};
printf("%p\n", a); // 数组名 = 首元素地址
printf("%p\n", &a[0]); // 和 &a[0] 打印出来一样
int *p = a; // 数组名可以直接塞进指针
printf("%d\n", a[2]); // 30
printf("%d\n", *(a+2)); // 30 ------ a[2] 就是 *(a+2)
printf("%d\n", p[2]); // 30 ------ 指针也能用 []
return 0;
}
运行情况:

记住三点:
- 数组名是首元素的地址 :
a和&a[0]是同一个数字,所以int *p = a;合法。 a[i]本质就是*(a+i):先首地址加偏移量,再解引用。[]只是语法糖------所以只要手里有地址,p[2]和a[2]都好使。- 数组名是指针常量,改不了 :它相当于是
const int *a,不能a++、不能a = p(门牌号改不了);p是变量,可以p++往后挪。
看到数组名,就看成"首元素的门牌号"。 以后传数组、遍历数组,还有学生系统里
scanf("%31s", stu.name)不用写&,都是因为这个。
5 总结
| 概念 | 含义 | 代码 |
|---|---|---|
| 地址 | 变量的门牌号 | &x → 0x7fff... |
| 指针 | 存地址的变量 | int *p = &x; |
*p |
顺着地址看/改房子里的值 | *p → 42 |
| 传地址 | 函数能改外面 | add10(&n); |
| 数组名 | 首元素的地址,能当指针用 | int *p = a; |
| 野指针 | 声明后没赋地址,装着垃圾值 | int *p; 别乱用 |
NULL |
空指针,谁都不指 | int *p = NULL; 用前先判断 |
- 指针 = 存地址的变量,本身没魔法
&取地址,*解引用------全部就这两个操作- 传地址能改外面------数组传参能改外面的本质
- 数组名 = 首元素地址 :
a[i]就是*(a+i),传数组就是传指针 - 声明指针先赋
NULL,用前先判断,躲开野指针的坑
到这里,你已经会用指针指一个变量、一个数组了。可真实的数据往往不是"一个数",而是"一坨"------比如一个学生,就有学号、姓名、三门成绩。
下一讲我们专门解决这件事:用指针指一整个结构体,看懂它在内存里怎么摆。学完那一讲,我们就正式动手做学生成绩管理系统。