导学案7 · 指针:地址这门手艺¶
前置要求:导学案6(会写函数,理解"值传递"改不到外面的变量) 参考课程:浙江大学 翁恺《C 语言程序设计》第 9 章(指针) 预计用时:1 ~ 1.5 小时(按需自取) 本节目标:理解指针与地址的对应关系;会用取地址
&和解引用*;会用指针让函数改到外面的变量;知道数组名本质是指针先说清楚我们对指针的要求。 指针是 C 语言里名声最吓人的部分,但我们对它的要求不高:理解概念、会用即可。 本节不讲二级指针、多级指针,不讲
const与指针的各种组合,也不讲复杂的指针运算——那些东西很容易把初学者绕晕。 你只需要带走三样东西:指针存的是地址、*p能顺着地址摸到那个变量、把地址传给函数就能改到原件。
7.0 序¶
导学案2里我们说过一句话:变量 = 内存里一小片有名字的空间,地址是它的门牌号。 那时 &a 只是个"照做即可"的符号。这一节我们把它变成一件趁手的工具。
为什么绕不开它?因为单片机上的硬件,本质上就是一排排固定地址的格子(寄存器)。想让灯亮、让电机转,就是往某个地址写一个值。指针,就是 C 语言里"按地址操作内存"的唯一手段。
本节路线:地址与指针 → 指针在函数里怎么用 → 数组名本质是指针 → 练习
7.1 地址、指针、解引用¶
三个动作:
| 写法 | 名字 | 意思 |
|---|---|---|
&a |
取地址 | 问出变量 a 住在几号门牌 |
int *p = &a; |
定义指针 | 造一个专门存地址的变量 p,把 a 的门牌抄进去 |
*p |
解引用 | 顺着 p 里存的门牌找过去,访问那个变量本身 |
int *p 表示 p 是一个指针,它指向一个 int。
#include <stdio.h>
int main(void)
{
int a = 10;
int *p = &a; /* p 里存的是 a 的地址 */
printf("a 的值 = %d\n", a);
printf("a 的地址 = %p\n", (void *)&a);
printf("p 里存的 = %p\n", (void *)p);
printf("*p(解引用)= %d\n", *p);
*p = 20; /* 顺着地址找到那个盒子,把值改掉 */
printf("改完之后 a = %d\n", a);
return 0;
}
输出(地址每次运行、每台机器都不同,下面是其中一次):
几点说明:
%p是打印地址的占位符。输出格式因编译器而异(有的打0x0061FE1C,有的打0061FE1C),认得出"这是一串十六进制的门牌号"就够了,不要纠结长相。- 最后一行是关键:我们没有碰
a这个名字,只通过*p就把a改成了 20。*p和a是同一个盒子的两种叫法。 int *p里的*只在定义时表示"这是个指针";定义之后,*p才是解引用。同一个符号两个场合两种意思,和数组的[5]一样。
野指针警告:
这可能当场崩溃,也可能悄悄改掉别的变量。规矩只有一条:定义指针时就给它一个地址;暂时没有,就写 int *p = NULL;(NULL 表示"不指向任何东西")。
7.2 指针在函数里怎么用¶
7.2.1 终于能改到外面的变量了¶
回收导学案6 6.4 那个失败的 swap。这次传地址:
#include <stdio.h>
void swap(int *pa, int *pb) /* 形参是两个指针:收到的是地址 */
{
int t = *pa;
*pa = *pb;
*pb = t;
}
int main(void)
{
int x = 1, y = 2;
swap(&x, &y); /* 把 x、y 的地址传进去 */
printf("x = %d, y = %d\n", x, y);
return 0;
}
输出:
这次真的换过来了。道理一句话:传值只能改副本,传地址就能改原件。 导学案2练习 5 的"三杯水",到这里才算真正成功。
7.2.2 一个函数"带回"多个值¶
return 一次只能带回一个值。想带回两个怎么办?用指针把结果"写"到调用者准备好的盒子里:
#include <stdio.h>
void minMax(int a, int b, int *pmin, int *pmax)
{
if (a < b) {
*pmin = a;
*pmax = b;
} else {
*pmin = b;
*pmax = a;
}
}
int main(void)
{
int lo = 0, hi = 0;
minMax(7, 3, &lo, &hi);
printf("小的是 %d,大的是 %d\n", lo, hi);
return 0;
}
输出:
这个模式你在飞控代码里会反复见到:LZY_Scheduler(..., &ModeChangedFlag) 就是把变量的地址交出去,让别的模块往里写结果。
7.2.3 顺便解开一个旧谜¶
导学案2让你死记"scanf 里每个变量前面都要写 &"。现在你懂了:
scanf("%d", &a)就是把a的地址交给scanf,让它顺着地址把读到的值放进a。 如果不给地址,scanf就不知道往哪放。
7.3 数组名本质是指针¶
这是本节第二个必须带走的结论:
数组名就是"第一个元素的地址"。 即
a等价于&a[0]。 因此a[i]等价于*(a + i):从首地址往后挪 i 个格子,再取里面的值。
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
int *p = a; /* 数组名直接赋给指针,不用写 & */
printf("a[0] = %d, *p = %d\n", a[0], *p);
printf("a[2] = %d, *(p + 2) = %d, p[2] = %d\n", a[2], *(p + 2), p[2]);
printf("a == &a[0] 吗? %d\n", a == &a[0]);
return 0;
}
输出:
这个结论顺手解释了两件旧事:
- 为什么
scanf("%s", name)不写&:name本身就是地址,再写&反而不对。 - 为什么数组传给函数不用复制整个数组:传一个地址就够了,又快又省。
7.3.1 数组作函数参数¶
#include <stdio.h>
void printArr(int a[], int len) /* 也可以写成 int *a,两种写法完全等价 */
{
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
void doubleAll(int a[], int len)
{
for (int i = 0; i < len; i++) {
a[i] *= 2; /* 传进来的是地址,所以能改到原数组 */
}
}
int main(void)
{
int score[5] = {90, 85, 77, 68, 99};
int len = (int)(sizeof(score) / sizeof(score[0]));
printArr(score, len);
doubleAll(score, len);
printArr(score, len);
return 0;
}
输出:
注意第二行:doubleAll 里改的就是 main 里的那个数组。数组作参数时是"传地址",不是"值传递",这是它和普通 int 参数最大的区别。
一个必须记住的坑:长度要单独传。 函数里的 a 只是一个指针,sizeof(a) 得到的是指针本身的大小(4 或 8 字节),不是数组的大小。所以导学案5说"sizeof(a)/sizeof(a[0]) 这个套路只在数组定义的地方有效"——原因就在这里。
7.4 综合练习¶
本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。
- 写指针版
void swap(double *pa, double *pb),主函数读入两个小数,交换后输出。 - 写
void minMaxArr(int a[], int len, int *pmin, int *pmax),求数组的最小值和最大值。主函数定义一个 6 个元素的数组并打印结果。 - 写
void zeroEven(int a[], int len),把数组里所有偶数改成 0。主函数打印修改前后的数组,验证"函数里真的改到了原数组"。 4.(*)用指针遍历数组:int *p = a;,然后循环里printf("%d ", *p); p++;。体会p++的含义是"往后挪一个格子",挪的步长由指针的类型决定(int *一次挪 4 字节)。
7.5 本节小结与自查¶
学完本节,你应该能做到:
- 说清
&a、p、*p三者的关系,知道int *p怎么读 - 定义指针时就初始化,知道野指针为什么危险、
NULL表示什么 - 用指针让函数改到外面的变量(成功的
swap) - 知道函数要"带回"多个值时用指针参数
- 说清
a与&a[0]、a[i]与*(a + i)的关系 - 知道数组传给函数时传的是地址:必须同时传长度,且函数里能改到原数组
- 解释
scanf("%d", &a[i])要写&、而scanf("%s", name)不写的原因
下一节:导学案8 · 结构体。把不同类型的数据捆成一个"自定义类型",飞控里每个传感器数据包都长这样。
7.6 练习参考代码(做完再看)¶
练习 1 参考代码¶
#include <stdio.h>
void swap(double *pa, double *pb)
{
double t = *pa;
*pa = *pb;
*pb = t;
}
int main(void)
{
double x = 0.0, y = 0.0;
printf("请输入两个小数:");
scanf("%lf %lf", &x, &y);
swap(&x, &y);
printf("交换后:x = %.1f, y = %.1f\n", x, y);
return 0;
}
练习 2 参考代码¶
#include <stdio.h>
void minMaxArr(int a[], int len, int *pmin, int *pmax)
{
*pmin = a[0];
*pmax = a[0];
for (int i = 1; i < len; i++) {
if (a[i] < *pmin) {
*pmin = a[i];
}
if (a[i] > *pmax) {
*pmax = a[i];
}
}
}
int main(void)
{
int a[6] = {5, 2, 9, 1, 7, 3};
int len = (int)(sizeof(a) / sizeof(a[0]));
int lo = 0, hi = 0;
minMaxArr(a, len, &lo, &hi);
printf("最小 = %d,最大 = %d\n", lo, hi);
return 0;
}
输出:最小 = 1,最大 = 9
练习 3 参考代码¶
#include <stdio.h>
void printArr(int a[], int len)
{
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
void zeroEven(int a[], int len)
{
for (int i = 0; i < len; i++) {
if (a[i] % 2 == 0) {
a[i] = 0;
}
}
}
int main(void)
{
int a[6] = {4, 7, 2, 9, 6, 1};
int len = (int)(sizeof(a) / sizeof(a[0]));
printArr(a, len);
zeroEven(a, len);
printArr(a, len);
return 0;
}
输出:
指针不可怕,可怕的是"不知道指针里存的是什么"。
以后每写一行带
*的代码,先问自己:这个指针现在指向谁?作者:Mike-lin300
最后更新:2026-09-25
更新说明:无(新编写)