【C语言-函数-参数传递】函数的参数传递——你以为你传过去了,其实传了个“复印件”

开发技术 作者: 精灵星空 SKY315.COM 更新于: 浏览: 242
码字发文不易,转载请注明出处【精灵星空 SKY315.COM】

前面几节课,咱们把函数的定义、调用、声明都聊透了。这节课咱们来说一个几乎所有学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. 值传递适合读取数据然后返回结果,地址传递适合需要修改实参、或者数据量较大的情况。

好了,这节课的核心就是:分清“复印件”和“原件”。你传给函数的到底是这个变量的值(复印件),还是这个变量的地址(能直接找到原件的地址)。想明白了这个,参数传递这块你就彻底拿下了。

回去自己写一个交换两个整数的函数,先写成值传递试试,再改成地址传递,看看输出有什么不同。亲手写一遍,比看十遍都管用。