传值调用
C 语言使用传值调用(Call by Value)机制:函数参数传递的是实参的副本,函数内部对形参的修改不会影响实参。这是 C 语言函数调用的核心规则,理解它对于掌握指针参数(模拟传址)和避免意外副作用至关重要。
基本规则
当调用函数时,实参的值被复制给形参。形参和实参是两个独立的变量:
void increment(int x)
{
x++; /* 只修改形参 x */
printf("Inside: x = %d\n", x);
}
int main(void)
{
int a = 10;
increment(a); /* 传递 a 的值(10) */
printf("Outside: a = %d\n", a); /* a 仍然是 10 */
return 0;
}
/* 输出:
Inside: x = 11
Outside: a = 10
*/
increment(a) 调用时,a 的值 10 被复制给 x。x++ 只修改 x,不影响 a。
为什么传值
传值调用的优点:
- 安全性:函数内部不会意外修改调用者的变量
- 简单性:不需要考虑别名问题
- 可预测性:函数调用是纯本地操作
void process(int data)
{
/* 可以安全地修改 data,不用担心影响外部 */
data = data * 2 + 1;
/* ... */
}
int value = 10;
process(value); /* value 仍然是 10 */
传递指针实现"传址"
如果需要函数修改实参,传递指向该变量的指针:
void increment(int *p)
{
(*p)++; /* 通过指针修改指向的值 */
}
int main(void)
{
int a = 10;
increment(&a); /* 传递 a 的地址 */
printf("%d\n", a); /* 11 */
return 0;
}
这里传递的是指针的值(地址的副本),但通过这个地址可以访问和修改原始变量。
数组参数的退化
数组作为参数传递时,退化为指向首元素的指针:
void print_array(int arr[], int n) /* arr 实际是指针 */
{
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
/* 等价声明 */
void print_array(int *arr, int n)
int main(void)
{
int data[] = {1, 2, 3, 4, 5};
print_array(data, 5); /* 传递的是 data 的首地址 */
return 0;
}
由于传递的是地址,函数内部可以通过指针修改数组元素:
void double_values(int arr[], int n)
{
for (int i = 0; i < n; i++)
arr[i] *= 2; /* 修改原始数组 */
}
int data[] = {1, 2, 3};
double_values(data, 3);
/* data 现在是 {2, 4, 6} */
结构体传值
结构体作为参数时,整个结构体被复制:
struct Point { int x; int y; };
void move(struct Point p) /* 复制整个结构体 */
{
p.x += 10; /* 只修改副本 */
}
int main(void)
{
struct Point pt = {0, 0};
move(pt);
printf("(%d, %d)\n", pt.x, pt.y); /* (0, 0) */
return 0;
}
大型结构体传值有复制开销。传递指针更高效:
void move(struct Point *p)
{
p->x += 10; /* 修改原始结构体 */
}
move(&pt); /* 传递地址 */
/* pt 现在是 (10, 0) */
如果不需要修改,用 const 指针保护:
void print_point(const struct Point *p) /* 承诺不修改 */
{
printf("(%d, %d)\n", p->x, p->y);
}
常见错误
试图通过传值修改实参:
void swap(int a, int b) /* 错误:传值 */
{
int temp = a;
a = b;
b = temp;
}
int x = 3, y = 5;
swap(x, y); /* x 和 y 不变! */
/* 正确:传指针 */
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
swap(&x, &y); /* x = 5, y = 3 */
混淆指针和值:
void set_value(int *p, int value)
{
p = &value; /* 错误:修改的是指针副本 */
}
/* 正确 */
void set_value(int *p, int value)
{
*p = value; /* 修改指针指向的值 */
}
数组大小丢失:
void print(int arr[])
{
int n = sizeof(arr) / sizeof(arr[0]); /* 错误!sizeof(arr) = sizeof(int*) */
/* ... */
}
/* 正确:显式传递大小 */
void print(int arr[], int n)
最佳实践
- 基本类型和简单结构体:传值(安全、简单)
- 大型结构体:传
const指针(效率) - 需要修改的变量:传指针
- 数组:传指针 + 大小
- 用
const修饰不需要修改的指针参数