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

举报
yd_287944033 发表于 2026/09/04 16:10:46 2026/09/04
【摘要】 读懂 C 语言函数传值与传址,彻底告别参数修改失败很多刚学 C 语言的人都会遇到一个让人抓狂的问题:我在函数里明明改了参数,为什么回到 main 里一点变化都没有?更离谱的是,有时候改了又能生效。于是你开始怀疑人生,甚至觉得 C 语言“看心情运行”。其实这不是玄学,而是 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;   // p 是 q 的一份拷贝,值都是 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 *p
f(&a)
传变量地址
改指针变量
int **p
f(&p)
传指针的地址
改数组内容
int arr[]int *arr
f(a)
数组退化为指针
只读取参数
int x
f(a)
拷贝值即可

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

你可能会问:
为什么 C 语言不直接支持“传引用”?这样不就简单了吗?
这其实和 C 语言的设计哲学有关。
C 语言追求的是:
  • 简单
  • 高效
  • 可预测
  • 贴近硬件
如果引入“引用”或“自动传址”,编译器需要额外规则来判断什么时候拷贝、什么时候传址。
而 C 语言只认一条规则:
函数参数就是一份拷贝。
这让行为非常明确,也让程序更高效、更容易推理。

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

以后写 C 语言函数时,只要记住下面三句话,基本不会再出错:
  1. 函数里改不动外面的变量?
    • 你大概率传了值,而不是地址。
  2. 想改谁,就传谁的地址。
    • 改普通变量 → &x
    • 改指针 → &p
  3. 函数参数永远只是拷贝。
    • 拷贝的是值
    • 指针也不例外,拷贝的是地址

总结

C 语言函数传参这件事,表面上坑很多,实际上只有一条规则:
C 语言只有传值。
所谓“传址”,只是你传了一个“地址的值”。
只要你始终问自己两个问题:
  1. 我现在传的是什么?
  2. 函数里改的是拷贝,还是拷贝指向的东西?
参数修改失败的问题,就再也不会出现了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。