前面几节课,咱们把函数的定义、调用、声明都聊透了。这节课咱们来说一个几乎所有学C语言的人都会困惑的问题:为啥我把变量传到函数里改了,出来之后它还是原来的值?
这是个经典困惑。先别急,咱们把这个问题掰开了揉碎了说,你听完就明白了。
咱们先看一段代码。大家猜猜输出结果是多少?
#include <stdio.h> void swap(int a, int b) { int temp = a; a = b; b = temp; printf("函数内部:a=%d, b=%d\n", a, b); } int main() { int x = 3, y = 5; swap(x, y); printf("函数外部:x=%d, y=%d\n", x, y); return 0; }
猜猜结果?
函数内部:a=5, b=3 函数外部:x=3, y=5
很多人第一次看到这个结果的时候懵了:“明明在函数里面已经交换了,为啥外面还是原来的值?”
这就是咱们今天要讲的核心——值传递。
C语言的函数参数传递方式叫“值传递”。什么叫值传递?说人话就是:你把数据传进函数的时候,函数是把你的数据复制了一份,它用的是复印件,原件还好好的在你手里。
咱们用上面那个swap的例子走一遍执行过程,你就明白了。
main里有变量x=3,y=5。当你调用swap(x, y)的时候,C语言干了这么一件事:
把x的值3复制了一份,给了a;把y的值5复制了一份,给了b。
也就是说,现在内存里有四个变量:main的x=3,main的y=5,swap的a=3,swap的b=5。a和b是x和y的“复印件”。
然后swap函数里交换的是谁?交换的是a和b——也就是那两份复印件。a从3变成了5,b从5变成了3。原件x和y动都没动。
所以当swap执行完,a和b被销毁了,回到main一打印,x还是3,y还是5。复印件被撕了,原件完好无损。
这就是值传递的真相:形参是实参的副本。你在函数里改的永远是副本,影响不了外面的变量。
那问题来了:我就想写一个交换两个变量的函数,难道C语言就实现不了?
能实现。但是你得换个思路——传地址,而不是传值。
你想想,如果你不把变量的值传进去,而是把变量的“门牌号”(地址)告诉函数呢?函数拿着地址,就能直接找到那个房间,直接进去改里面的东西。这就不存在“复印件”的问题了。
这就要用到指针了。代码得改成这样:
#include <stdio.h> void swap(int *a, int *b) { // 接收的是地址 int temp = *a; // 通过地址找到a指向的房间,取出里面的值 *a = *b; // 通过地址找到b指向的房间,把值赋给a指向的房间 *b = temp; // 同理 } int main() { int x = 3, y = 5; swap(&x, &y); // 传的是x和y的地址 printf("x=%d, y=%d\n", x, y); // x=5, y=3 return 0; }
这回对了。main里调用swap(&x, &y)的时候,传进去的是x和y的地址——也就是告诉函数:“x在某某房间,y在某某房间,你自己进去改。”
函数里的a和b现在存的是地址。*a就是“找到a指向的那个房间,进去看里面的值”。*a = *b就是“找到b指向的房间,取出值,放到a指向的房间里”。直接操作的是原件,所以外面打印的时候x和y确实交换了。
这就是“地址传递”,也叫“传址”。但严格来说,C语言只有值传递,没有其他传递方式。传址本质上也是值传递——只不过传的是地址的值而已。你可以理解为:以前传的是数字本身,现在传的是这个数字在内存里的坐标。
那到底什么时候用值传递,什么时候用地址传递呢?给你一个简单的判断标准:
如果函数只是想“读取”你的数据,算个结果返回给你,用值传递就够。
比如加法函数:
int add(int a, int b) { return a + b; }
你传两个数进去,它算完把结果通过return还给你,不需要改动你原来的数。用值传递,简单又安全。
如果函数想“修改”你的变量,想直接改掉实参的值,那就必须用地址传递。
比如上面那个交换函数、还有后面要学的链表里各种修改结构的函数,都得传地址。
还有一个重要场景:当你要传的数据很大时,比如一个很大的结构体或者数组,传值的话要把整个数据都复制一遍,效率低。传地址的话只传一个地址(8个字节),效率高得多。
讲到数组的时候,有一个问题经常把人搞晕。
在C语言里,数组名本身就是地址。所以数组作为函数参数传递的时候,它天然就是“传址”的。你在函数里改了数组元素,外面的数组确实跟着变了。
#include <stdio.h> void changeArray(int arr[], int n) { for (int i = 0; i < n; i++) { arr[i] = arr[i] * 2; } } int main() { int a[5] = {1, 2, 3, 4, 5}; changeArray(a, 5); for (int i = 0; i < 5; i++) { printf("%d ", a[i]); // 输出 2 4 6 8 10 } return 0; }
看,数组元素确实被改掉了,因为传进去的是数组的首地址,函数拿着地址直接改原件。
但是注意:数组作为参数时,int arr[]这个写法在函数参数里等同于int *arr。 它并不是把整个数组复制了一份,而是把数组的首地址传进去了。
总结一下这节课的核心:
1. C语言只有值传递。形参是实参的副本,修改形参不影响实参。 这就是为什么你写的交换函数交换不了外面变量的值。
2. 如果要修改实参的值,传入变量的地址(指针)。 通过地址去操作原件。
3. 数组名本身就是地址,传数组相当于传地址,所以函数里改数组元素会影响外面的数组。
4. 值传递适合读取数据然后返回结果,地址传递适合需要修改实参、或者数据量较大的情况。
好了,这节课的核心就是:分清“复印件”和“原件”。你传给函数的到底是这个变量的值(复印件),还是这个变量的地址(能直接找到原件的地址)。想明白了这个,参数传递这块你就彻底拿下了。
回去自己写一个交换两个整数的函数,先写成值传递试试,再改成地址传递,看看输出有什么不同。亲手写一遍,比看十遍都管用。