🎯 本章你会学到:指针的本质、指针与数组的关系、malloc/free 的正确用法与常见错误。建议边读边敲,每节练习先自己做,再展开答案对照。
指针是 C 的核心,也是最容易出错的地方。一句话理解:指针是一个存地址的变量,*p 就是访问那个地址上的东西。
先看一个必须用指针才能完成的任务
用指针交换两个变量
#include <stdio.h>
static void swap(int *a, int *b) {
int t = *a; /* 取出 a 指向的值 */
*a = *b; /* 把 b 指向的值写进 a 指向的位置 */
*b = t;
}
int main(void) {
int x = 1, y = 2;
swap(&x, &y); /* 传地址,函数里才能改到 x、y */
printf("x=%d y=%d\n", x, y);
return 0;
}运行结果
x=2 y=1
输出 x=2 y=1。因为 C 是值传递,swap(x, y) 只会交换副本;只有传地址 &x,函数才能改到调用方的变量。
动态内存:malloc 与 free
动态数组
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 0;
if (scanf("%d", &n) != 1 || n <= 0) {
return 1;
}
int *a = malloc((size_t)n * sizeof(int)); /* 申请 n 个 int */
if (a == NULL) { /* 一定要检查是否成功 */
return 1;
}
for (int i = 0; i < n; ++i) {
a[i] = i * i; /* a[i] 等价于 *(a + i) */
}
long long sum = 0;
for (int i = 0; i < n; ++i) {
sum += a[i];
}
printf("sum=%lld\n", sum);
free(a); /* 用完必须释放,且只能释放一次 */
a = NULL; /* 置空,避免变成野指针 */
return 0;
}输入
4运行结果
sum=14
输入 4 输出 sum=14(0+1+4+9)。malloc 只管要空间、不管清零;calloc 会清零,realloc 用来扩容。
malloc失败返回NULL——不检查就直接用,等于把崩溃留给运行期。free之后那块内存不能再用(悬垂指针),重复free也是未定义行为。sizeof(a)在指针上得到的是指针大小(8),不是数组长度。- 数组名在多数场合会“退化”成指向首元素的指针,所以
a[i]和*(a + i)完全等价。
⚠️ 易错点 忘记
free 会内存泄漏;忘记检查 malloc 返回值会崩溃。这两个是 C 语言作业里最常被扣分的点。⚠️ 易错点 返回局部变量的地址是错的:函数一返回,那块栈内存就失效了,调用方拿到的是悬垂指针。
✍️ 本节练习
6.1必做用指针输出最值
写函数 void minmax(const int *a, int n, int *out_min, int *out_max),把最小值和最大值写到两个输出参数里。
输入 第一行 n;第二行 n 个整数。
输出 两行:min=最小值、max=最大值。
样例输入
6
5 3 9 1 7 4样例输出
min=1
max=9💡 提示 要写回调用方的变量,参数就得是指针;const int *a 表示只读数组。
✅ 查看参考答案与解析
#include <stdio.h>
#define MAXN 1000
static void minmax(const int *a, int n, int *out_min, int *out_max) {
int lo = a[0], hi = a[0];
for (int i = 1; i < n; ++i) {
if (a[i] < lo) { lo = a[i]; }
if (a[i] > hi) { hi = a[i]; }
}
*out_min = lo; /* 通过指针写回调用方 */
*out_max = hi;
}
int main(void) {
int n = 0;
if (scanf("%d", &n) != 1 || n <= 0 || n > MAXN) {
return 1;
}
int a[MAXN];
for (int i = 0; i < n; ++i) {
if (scanf("%d", &a[i]) != 1) {
return 1;
}
}
int lo = 0, hi = 0;
minmax(a, n, &lo, &hi);
printf("min=%d\n", lo);
printf("max=%d\n", hi);
return 0;
}解析 这就是 C 里“返回多个值”的标准做法:入参用 const 指针(只读),出参用普通指针(可写)。
6.2挑战动态数组原地反转
用 malloc 申请数组读入 n 个数,用两个指针就地反转,再输出。要求释放内存。
输入 第一行 n;第二行 n 个整数。
输出 一行:反转后的序列。
样例输入
5
1 2 3 4 5样例输出
5 4 3 2 1💡 提示 左右两个指针向中间靠拢,交换 *left 与 *right,直到相遇。
✅ 查看参考答案与解析
#include <stdio.h>
#include <stdlib.h>
static void reverse(int *first, int *last) { /* [first, last) 左闭右开 */
if (first == last) {
return;
}
--last;
while (first < last) {
int t = *first;
*first = *last;
*last = t;
++first;
--last;
}
}
int main(void) {
int n = 0;
if (scanf("%d", &n) != 1 || n <= 0) {
return 1;
}
int *a = malloc((size_t)n * sizeof(int));
if (a == NULL) {
return 1;
}
for (int i = 0; i < n; ++i) {
if (scanf("%d", &a[i]) != 1) {
free(a);
return 1;
}
}
reverse(a, a + n);
for (int i = 0; i < n; ++i) {
printf("%d%c", a[i], i + 1 == n ? '\n' : ' ');
}
free(a);
return 0;
}解析 区间用左闭右开 [first, last) 是 C/C++ 的通用约定(STL 也是这个约定);出错时要先 free 再返回,避免泄漏。