5. 指针与函数:指针作为函数参数、指针作为函数返回值、用指针实现函数间数据交换
好,我们继续往下走。这一章要聊的是指针和函数之间的“联姻”。
很多初学者觉得函数就是传传值、返返值,挺简单的。但一旦你开始接触稍微复杂一点的项目——比如写个驱动、做个协议栈、或者搞个内存池——你就会发现,没有指针的函数,就像没有轮子的车,根本跑不起来。
我个人习惯把指针和函数的关系分成三个层次来看:指针做参数、指针做返回值、以及用指针实现真正的数据交换。咱们一个一个来拆。
5.1 指针作为函数参数
先问一个问题:C语言里,函数参数是怎么传递的?
答案是:值传递。也就是说,你把一个变量传给函数,函数拿到的是这个变量的“复印件”,而不是原件。你在函数里怎么改这个复印件,原件纹丝不动。
那如果我想让函数修改调用者的变量呢?
嗯,这时候就需要指针出场了。
你把变量的地址传给函数,函数通过这个地址,就能直接操作原件。说白了,就是你把家里的钥匙给了别人,别人就能进你家帮你修水管。
核心理解:指针作为参数,本质上是把“变量的地址”传进去,而不是把“变量的值”传进去。函数内部通过解引用操作,直接读写原始内存。
来看个最简单的例子:
#include <stdio.h>
void set_to_zero(int *p) {
*p = 0; // 通过指针修改原始变量
}
int main() {
int x = 100;
printf("修改前: %d\n", x);
set_to_zero(&x);
printf("修改后: %d\n", x);
return 0;
}
输出结果:
修改前: 100
修改后: 0
你看,set_to_zero 函数接收的是 x 的地址,然后通过 *p = 0 直接修改了 x 本身。这就是指针作为参数最基础、最核心的用法。
我的经验:我在项目中遇到过很多新手,明明传了指针进去,但函数里却写 p = 0 而不是 *p = 0。结果发现外面的变量根本没变,排查半天。记住:传指针是为了解引用,不是改指针本身。
5.2 指针作为函数返回值
函数不仅能接收指针,还能返回指针。这有什么用?
最常见的场景是:函数内部动态分配了一块内存,然后把这块内存的地址返回给调用者。调用者拿着这个地址,就能使用这块内存了。
举个例子:
#include <stdio.h>
#include <stdlib.h>
int* create_array(int size) {
int *arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
return NULL; // 分配失败,返回空指针
}
// 初始化数组
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr; // 返回动态数组的地址
}
int main() {
int *my_array = create_array(5);
if (my_array != NULL) {
for (int i = 0; i < 5; i++) {
printf("%d ", my_array[i]);
}
printf("\n");
free(my_array); // 别忘了释放!
}
return 0;
}
输出:
0 10 20 30 40
警告:千万不要返回局部变量的地址!局部变量在函数返回后就被销毁了,返回它的地址会导致“悬空指针”。我曾经见过一个同事,返回了栈上数组的地址,结果程序跑起来数据全是乱的,查了一下午才发现是这个问题。
正确的做法是:要么返回静态变量的地址,要么返回动态分配的内存地址(如 malloc),要么返回传入的指针参数本身。
5.3 用指针实现函数间数据交换
这是指针作为参数最经典的应用——交换两个变量的值。
你想想看,如果不用指针,能不能实现交换?
void swap_bad(int a, int b) {
int temp = a;
a = b;
b = temp;
// 这里改的是副本,外面的 a、b 纹丝不动
}
不行。因为这是值传递,函数内部交换的是复印件。
用指针就对了:
void swap_good(int *pa, int *pb) {
int temp = *pa;
*pa = *pb;
*pb = temp;
}
int main() {
int x = 10, y = 20;
printf("交换前: x=%d, y=%d\n", x, y);
swap_good(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y);
return 0;
}
输出:
交换前: x=10, y=20
交换后: x=20, y=10
这个模式太常见了。排序算法里、状态机切换里、甚至一些回调函数里,你都会看到类似的写法。
避坑指南:我曾经在写一个链表反转函数时,忘了传二级指针,结果头指针一直没更新,链表越反转越乱。后来才意识到:如果要在函数内部修改指针本身(比如让指针指向别处),就得传二级指针。这个后面会专门讲,但你现在先有个印象。
5.4 知识体系总览
为了让你更直观地理解这一章的内容,我画了一张图:
5.5 总结一下
这一章的内容其实就三句话:
- 指针做参数:把地址传进去,函数就能改原件。这是最常用的方式。
- 指针做返回值:把动态分配的内存地址传出来,调用者负责使用和释放。注意别返回局部变量地址。
- 数据交换:用指针实现两个变量的交换,这是指针作为参数的经典案例。记住
swap(&a, &b)这个模式。
嗯,说白了,指针和函数的结合,就是让函数不再“闭门造车”,而是能够直接操作调用者的数据。这在嵌入式开发里尤其重要——比如你要写一个驱动函数去修改某个寄存器的值,不传指针进去,你怎么改?
好,这一章就到这里。下一章我们会聊一个更进阶的话题:指针与数组的纠缠。到时候你会发现,数组名和指针之间,其实藏着不少“猫腻”。
公众号:蓝海资料掘金营,微信deep3321