本篇博客会讲解C语言函数调用的2种方式,分别是:传值调用和传址调用。这2种函数调用方式有什么区别呢?为什么会有不同的效果呢?分别有哪些用途呢?下面我会一一展开。
区别
- 传值调用,即通过传递变量的值来调用函数。
- 传址调用,即通过传递变量的地址来调用函数。
比如,假设有2个变量a和b,对于变量a和b来说,test(a, b)
就是传值调用,test(&a, &b)
就是传址调用。
原理
这时你可能很好奇:这2种调用方式的原理是什么呢?其实非常简单。
先举个传值调用的例子:
// 函数定义
int Add(int x, int y)
{
return x + y;
}
// 调用
int a = 3, b = 5;
int sum = Add(a, b);
在上面的代码中,我们分别把a和b的值传递给了Add函数中的x和y。此时,我们称:a和b是“实际参数”,简称实参;x和y是“形式参数”,简称形参。传值调用的方式,会把实参的值传递给形参,也就是说,把a中的3传递给x,此时x就是3,把b中的5传递给y,此时y就是5。在Add函数内部,把x+y的值带回来,也就返回3+5的值,即返回8,再把返回值赋值给sum,sum就是8。
再举个传址调用的例子:
// 函数定义
int Add(int* p1, int* p2)
{
return *p1 + *p2;
}
// 调用
int a = 3, b = 5;
int sum = Add(&a, &b);
以上就是典型的传址调用,但是很显然在这个场景下,我们只想“求和”,使用传址调用有点多此一举,但是还是分析一下原理:我们把a和b的地址传给了p1和p2,此时p1存储了a的地址,p2存储了b的地址,p1就指向了a,p2就指向了b。我们想在Add函数内部求和,就要先对p1解引用,拿到a的值,再对p2解引用,拿到b的值,再把拿到的a和b的值加起来返回,此时sum就被赋值为函数的返回值,即8。
以上只是非常粗略的带大家了解了传值调用和传址调用的区别。下面用一个经典的例子进行更加深入的讲解。这个例子就是:写一个函数,交换2个整数的值。
使用传值调用的方式,写出来的函数如下:
// 定义
void Swap(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
// 调用
int a = 3, b = 5;
Swap(a, b);
下面我通过调试的方式,来带大家看一下这个程序会如何执行。
代码即将执行Swap(a, b);
,此时a的值是3,b的值是5。接下来执行这条语句:
代码来到第17行,此时a和b的值并没有交换?到底发生了啥?
重新开始调试,这次我进入到Swap函数内部看一眼。
按照前面的分析,此时x和y拿到了a和b的值,接下来进行交换:
代码执行到第10行,此时可以发现,x和y其实已经交换了,但是a和b并没有变化?这又是为什么呢?
此时再回到main函数,发现a和b的值并没有被交换。
发现这个问题后,我们可以干一件事,使用下面的代码,把a、b、x、y的地址打印出来:
#include <stdio.h>
void Swap(int x, int y)
{
printf("&x = %p, &y = %p\n", &x, &y);
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 3, b = 5;
printf("&a = %p, &b = %p\n", &a, &b);
Swap(a, b);
return 0;
}
输出结果如下:
可以发现,当把a的值传递给x,把b的值传递给y时,x和y,a和b已经是不同的空间了,此时相当于,内存中有4个变量,分别是a、b、x、y,由于值传递,x的值和a相同,y的值和b相同,此时交换了x和y,对a和b的值并没有影响!所以函数调用结束后,a和b的值并没有交换。
这时,我们就可以总结:当我们使用传值调用,实参的值传递给形参后,形参只是实参的一份临时拷贝,改变形参的值并不影响实参的值!
那Swap函数的正确实现形式是怎样的呢?相信聪明的你已经想到了,使用传址调用就行了嘛!
// 定义
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 调用
int a = 3, b = 5;
Swap(&a, &b);
为什么以上的代码就能实现“交换”的效果呢?这就是传址调用的神奇之处!
分析一下:把a的地址传递给p1,把b的地址传递给p2,此时p1就指向了a,p2就指向了b,这时再对p1和p2解引用,就能把a和b的值给修改了!
我们还是通过调试来观察一下细节:
进入到函数内部:
再回到main函数:
成功交换了a和b的值!
用途
根据以上的讲解,可以总结一下传值调用和传址调用的用途:
- 传值调用适用于不需要修改函数外部变量的场景。
- 传址调用适用于需要修改函数外部变量的场景。
这是因为,传值调用时,形参是实参的一份临时拷贝,改变形参并不影响实参,所以在函数内部没有能力改变外面的变量的值;传址调用就不一样了,形参保存了函数外部变量的地址,就可以通过解引用的方式,修改函数外部变量的值了。
总结
- 传值调用适用于不需要修改函数外部变量的场景,因为函数内部变量和外部变量并没有建立联系,是独立的空间。
- 传址调用适用于需要修改函数外部变量的场景,因为函数内部存储了指向函数外部变量的指针,建立了联系,可以通过解引用的方式改变函数外部的变量。
感谢大家的阅读!