读懂 C 语言函数传值与传址,彻底告别参数修改失败

读懂 C 语言函数传值与传址,彻底告别参数修改失败

很多刚学 C 语言的人都会遇到一个让人抓狂的问题:

我在函数里明明改了参数,为什么回到 main 里一点变化都没有?

更离谱的是,有时候改了又能生效。于是你开始怀疑人生,甚至觉得 C 语言“看心情运行”。

其实这不是玄学,而是 C 语言最基础、也最容易被误解的一个机制:函数调用时,永远只有“传值”

这篇文章把这件事彻底讲清楚,让你以后再也不会在参数修改上踩坑。


一、先说结论:C 语言只有“传值”

这是一个非常重要的前提,请先记住它:

C 语言的函数参数传递,永远都是传值(pass by value)。

所谓“传址”,并不是 C 语言有另一种传参方式,而是你传了一个“地址的值”

换句话说:

  • 传普通变量:传的是变量的值
  • 传指针:传的是指针的值(也就是地址)

本质都是传值,只是传的东西不同。


二、最经典的坑:为什么函数里改不动外面的变量

来看一个最常见、也最让人困惑的例子。

1. 普通变量传参

#include <stdio.h>

void change(int x) {
    x = 100;
}

int main() {
    int a = 10;
    change(a);
    printf("a = %d\n", a);  // 输出 a = 10
    return 0;
}

你期望 a 变成 100,但结果还是 10。

为什么会这样?

因为 change(a) 时,发生的是:

x = a;   // 把 a 的值拷贝给 x

也就是说:

  • xa 的一份拷贝
  • 函数里改的是 x
  • ax 是两个完全独立的变量

函数结束后,x 被销毁,a 当然不会有任何变化。

一个生活化的比喻

你把我书桌上的一本书拍了张照片,拿回去在照片上把书名涂掉。

请问:我书桌上的那本书,书名会变吗?

当然不会。


三、那为什么传数组好像“能改”?

这是第二个让人迷惑的地方。

数组传参的例子

#include <stdio.h>

void modify(int arr[]) {
    arr[0] = 99;
}

int main() {
    int a[3] = {1, 2, 3};
    modify(a);
    printf("a[0] = %d\n", a[0]);  // 输出 a[0] = 99
    return 0;
}

咦?函数里改了,外面也变了。

于是很多人得出结论:

数组是传址的。

错。

真相:数组参数会退化成指针

在 C 语言里,下面两种写法是等价的:

void modify(int arr[]);
void modify(int *arr);

当你写 modify(a) 时,实际发生的是:

arr = &a[0];  // 把数组首元素的地址传进去

你传的依然是“值”,只不过这个值是一个地址

函数里通过地址去访问内存,当然能改到原来的数据。


四、真正“能改外面变量”的方式:传指针

现在你明白了,要让函数修改外面的变量,必须让它知道变量在哪。

也就是:传地址

用指针修改普通变量

#include <stdio.h>

void change(int *p) {
    *p = 100;
}

int main() {
    int a = 10;
    change(&a);
    printf("a = %d\n", a);  // 输出 a = 100
    return 0;
}

这次发生了什么?

p = &a;   // 把 a 的地址拷贝给 p

注意:

  • p&a 的拷贝
  • p 里保存的是 a 的地址
  • *p = 100 意思是:去 a 的地址上写 100

所以 a 被改到了。

再强调一次本质

你并没有把 a 传给函数,而是把 a 的地址传给了函数。


五、为什么指针本身也可能“改不动”

这是第三个大坑,很多人以为自己已经懂了指针,结果在这里翻车。

想在函数里给指针分配内存

#include <stdio.h>
#include <stdlib.h>

void alloc(int *p) {
    p = malloc(sizeof(int));
    *p = 10;
}

int main() {
    int *q = NULL;
    alloc(q);
    // q 仍然是 NULL
    return 0;
}

你以为 q 会得到一块新内存,结果它还是 NULL

原因还是一样:传值

调用 alloc(q) 时,发生的是:

p = q;   // pq 的一份拷贝,值都是 NULL

函数里改的是 p,不是 q

函数结束后,p 被销毁,q 当然还是 NULL

正确写法:传指针的地址

void alloc(int **p) {
    *p = malloc(sizeof(int));
    **p = 10;
}

int main() {
    int *q = NULL;
    alloc(&q);  // 现在 q 指向新分配的内存
    free(q);
    return 0;
}

规则没变:

想改谁,就传谁的地址。


六、一张表彻底理清所有情况

你想做什么函数参数调用方式原因
改普通变量int *pf(&a)传变量地址
改指针变量int **pf(&p)传指针的地址
改数组内容int arr[]int *arrf(a)数组退化为指针
只读取参数int xf(a)拷贝值即可

七、为什么 C 语言要这样设计?

你可能会问:

为什么 C 语言不直接支持“传引用”?这样不就简单了吗?

这其实和 C 语言的设计哲学有关。

C 语言追求的是:

  • 简单
  • 高效
  • 可预测
  • 贴近硬件

如果引入“引用”或“自动传址”,编译器需要额外规则来判断什么时候拷贝、什么时候传址。

而 C 语言只认一条规则:

函数参数就是一份拷贝。

这让行为非常明确,也让程序更高效、更容易推理。


八、一个自检清单,避免以后再踩坑

以后写 C 语言函数时,只要记住下面三句话,基本不会再出错:

  1. 函数里改不动外面的变量?

    • 你大概率传了值,而不是地址。
  2. 想改谁,就传谁的地址。

    • 改普通变量 → &x
    • 改指针 → &p
  3. 函数参数永远只是拷贝。

    • 拷贝的是值
    • 指针也不例外,拷贝的是地址

总结

C 语言函数传参这件事,表面上坑很多,实际上只有一条规则:

C 语言只有传值。

所谓“传址”,只是你传了一个“地址的值”。

只要你始终问自己两个问题:

  1. 我现在传的是什么?
  2. 函数里改的是拷贝,还是拷贝指向的东西?

参数修改失败的问题,就再也不会出现了。

0
0
0
0
评论
未登录
暂无评论