导入
按值传递
学习值传递的内部机制和工作原理。
引言
利用我们对栈和函数调用机制的理解,现在来介绍“传值调用”的概念。
我们的目标是实现一个 swap 函数,以便在整个程序中用于交换两个变量的值。
1#include <stdio.h> 2 3void swap(int a, int b) 4{ 5 int aux = a; 6 a = b; 7 b = aux; 8} 9 10int main() 11{ 12 int a = 5, b = 6; 13 14 printf("交换前: a = %d, b = %d\n", a, b); 15 swap(a, b); 16 printf("交换后: a = %d, b = %d\n", a, b); 17 18 return 0; 19}
让我们运行这段代码并查看输出:
1交换前: a = 5, b = 6 2交换后: a = 5, b = 6
很遗憾!并没有生效。a 和 b 的值都没有改变,但我们确实看到在 swap 函数内部完成了交换操作。
分析代码
我们需要运用新掌握的内存图绘制技巧来理解原因。必须画出 main 函数的栈帧,并展示调用 swap 前所需的准备工作。为了简化图形并聚焦关键点,我们忽略 printf 调用。
swap 函数遵循调用约定:
- 在其栈帧上方,可以找到返回地址。
- 在返回地址之上,可以找到它的参数。
当 swap 尝试修改 a 和 b 时,它实际上修改的是紫色部分的变量——这些是为函数调用压入栈中的副本。
swap 函数看不到(也无法修改)main 中的 a 和 b 变量(图中蓝色变量)。
这种方式传递参数被称为 传值调用。
我们传递的是 main 中变量的 值,而不是变量本身。
被调用的函数接收到的是与原始变量具有相同值的副本,它们存储在不同的内存位置,是两个独立的变量。
这里有一个简单的类比:
1int arg = 7; 2int x = arg; 3 4// x 是 arg 的一份拷贝 5// x 是一个不同的变量,拥有自己的内存地址,但值与 arg 相同
传值调用的结论
从传值调用的概念中可以得出以下几点结论:
- 我们传递的是变量的副本。
- 当我们希望被调用函数读取变量内容时非常有用。
- 如果希望在函数内修改变量的值,则毫无用处,因为实际修改的是副本,而非原变量。
为了更清楚地说明最后一点,来看一个简单例子:
1#include <stdio.h> 2 3void change(int x) 4{ 5 x = 11; 6 // x 在 change 内部变为 11 7} 8 9int main() 10{ 11 int x = 5; 12 13 printf("x 之前: %d\n", x); 14 change(x); 15 // 这个 x 没有变化,它是 change 函数内部的那个 x 的不同版本 16 printf("x 之后: %d\n", x); 17 18 return 0; 19}
change 函数中对 x 的修改在 main 中不可见,因为 change 修改的是 x 的一份副本。在 change 函数内部,x 的值是 11,但在 main 中,它的值仍然是 5。
在 第10行 声明的 x 和 change 函数的参数 x 是两个完全不同的变量。
从功能上看,change 函数的函数头写成 void change(int arg) 也完全一样,因为 main 中的 x 与 change 中的参数 x 之间没有任何关联。