C 值传递与传地址
更新时间:2026-08-26。本文是
languages/c/主题入门层第 19 篇。上一篇说"传的是值",这一篇把这句话的后果讲透——以及怎么绕过它。这是新手第一次接触指针的"动力来源":没指针,函数就真的只能"只进不出"了。
本文要回答的问题
- 为什么函数里改了参数,外面没变?
- 怎么写出能"修改外部变量"的函数?
swap交换函数为什么必须传指针?
一、值传递:复制一份
c
#include <stdio.h>
void add_one(int n) {
n = n + 1; // 改的是副本
printf("函数内: %d\n", n); // 函数内: 6
}
int main(void) {
int x = 5;
add_one(x); // 把 x 的"值"复制一份传进去
printf("函数外: %d\n", x); // 函数外: 5,没变!
return 0;
}C 的参数传递只有一个语义:按值传递。调用 add_one(x) 时,把 x 的值(5)复制给形参 n。函数里操作 n,跟 x 没有任何关系。x 还是 5。
用上一篇的栈帧模型理解:形参 n 是新栈帧里的独立变量,只是初值恰好等于实参的值。
二、想改实参:传地址(指针)
要让函数修改外部的 x,得把"x 的地址"传进去,函数通过地址去改那块内存:
c
#include <stdio.h>
void add_one(int *p) { // 形参是指针,存的是地址
(*p) = (*p) + 1; // 通过地址改 x
}
int main(void) {
int x = 5;
add_one(&x); // 传 x 的地址(&x)
printf("x = %d\n", x); // x = 6,真的变了!
return 0;
}| 写法 | 含义 |
|---|---|
int *p | p 是"指向 int 的指针",能存 int 变量的地址 |
&x | 取 x 的地址 |
*p | 解引用:访问 p 指向的那块内存 |
值传递 + 传地址,本质没变:传 &x 也是"按值传递"——只不过传的值是地址。函数拿到地址后能通过它读写实参。所以准确的说法是:C 只有值传递,但你可以传"地址值"来间接修改。
三、经典例题:swap
c
void swap_bad(int a, int b) { // 错:交换的是副本
int t = a;
a = b;
b = t;
// 外面两个变量纹丝不动
}
void swap(int *a, int *b) { // 对:交换的是指向的值
int t = *a;
*a = *b;
*b = t;
}
int main(void) {
int x = 3, y = 8;
swap(&x, &y); // 必须传地址
printf("x=%d y=%d\n", x, y); // x=8 y=3
return 0;
}为什么 swap 必须传指针?因为要改的不是形参的值,而是 main 里 x、y 本身。只能通过地址到达它们。
四、什么时候用指针改值,什么时候返回
两种"让函数产生结果"的方式:
| 方式 | 适用 |
|---|---|
return 返回值 | 只有一个结果,如 int add(int, int) |
| 指针参数 | 多个结果(如函数同时产出两个数)、或要改数组/结构体 |
c
void divmod(int a, int b, int *q, int *r) { // 一次返回商和余
*q = a / b;
*r = a % b;
}不追求"用指针改参数"——能 return 就 return,指针改参只在确实需要时用(多返回值、改大结构体、回调等)。
五、数组名传进去是什么
c
void print_arr(int arr[], int n) { // 等价的写法是 int *arr
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
}数组传参不是复制整个数组,而是传首元素地址(数组名衰减成指针)。所以函数里改 arr[i] 会真的改到外面的数组——数组天然是"传引用效果"。这跟基本类型完全不同,是新手理解错位的高发点。细节在指针与数组里展开。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 函数里改了形参 | 外面没变 | 需要改外面就传指针 |
忘了 & | 传值而非地址 | swap(&x, &y) |
| 指针忘了解引用 | 改的是地址不是值 | *p = ... |
int *p 当 int 用 | 编译警告/错误 | 类型要对齐 |
七、与本站主线衔接
- 指针的完整世界(运算、数组、函数指针),见指针基础起的指针专题;
- 传值在汇编里是
mov把寄存器/栈值复制给参数,见与汇编、性能剖析衔接; - 大结构体传值 vs 传指针的性能对比,见高手层结构体与性能。
一句话总结
C 只有值传递——传基本类型改不了外面、传地址(& + 指针参数 + *p 解引用)才能改外面;swap 必须传指针、数组传参自动"传引用",这两条记住,函数的数据进出就通了。