在C语言中,函数参数的传递方式有值传递和指针传递两种,在C++中又多了一个引用传递。事实上值传递和指针传递都是传值,只不过对指针传递来说传的是指针的值。

传值调用:

当实参的的值被拷贝给形参时,形参和实参是两个相互独立的对象,这样的函数调用就是传值调用。传值调用执行的是值拷贝操作,初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会影响初始值,比如:

int n = 0;
int i = n;  // 此时,n = 0, i = 0
i = 42;  // i 的值改变为42,但 n 的值不变,仍然是0


再说一下指针传递。指针的行为和其他非引用类型一样,当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。比如:

#include <iostream>
using namespace std;

int main()
{
	int i = 11, j = 22;
	int *p = &i;    // p 指向i
	cout << "1:i = " << i << endl;	
	p = &j;    // 现在p指向了j,只是将j的地址传递给了p,但i本身的值仍然不变
	cout << "2:i = " << i << endl;	

	return 0;
}

运行结果是:

Android 函数指针传参 函数指针怎么传参数_参数传递

因为指针可以间接访问它所指的对象,所以通过指针可以修改它所指向对象的值,如:

#include <iostream>
using namespace std;

int main()
{
	int i = 11, j = 22;
	int *p = &i;    // p 指向i
	cout << "1:i = " << i << endl;	
	*p = j;    // 通过指针的解引用将p所指对象i的值改变为j的值22
	cout << "3:i = " << i << endl;	

	return 0;
}

运行结果是:

Android 函数指针传参 函数指针怎么传参数_参数_02


传引用调用:

当形参是引用类型时,这样的函数调用是传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名,也就是说,引用形参是它对应的实参的别名。对于引用的操作实际上是作用在引用所引的对象上的。比如:

#include <iostream>
using namespace std;

int main()
{
	int i = 11, j = 22;
	int &r = i;    // 将r绑定到i
	cout << "1:i = " << i << endl;
	r = j;         // 现在i和j的值相同都为22
	cout << "2:i = " << i << endl;

	return 0;
}


运行结果是:

Android 函数指针传参 函数指针怎么传参数_参数_03

使用引用有很大的好处,在C语言中常常使用指针类型的形参访问函数外部的对象,在C++中建议使用引用类型的形参来代替指针。

(1)使用引用可以避免拷贝:

由于传值调用是将实参的值拷贝后赋给形参,而拷贝大的类类型或者容器对象比较低效,甚至有的类类型根本就不支持拷贝操作,此时通过引用形参访问该类型的对象就能很好地解决这个问题。比如编写一个函数用来比较两个 string 对象的长度,因为 string 对象可能会比较长,所以应该尽量避免直接拷贝它们,所以选择使用引用形参。

(2)使用引用形参可返回额外信息:

一个函数只能返回一个值,然而有时函数需要同时返回多个值,此时引用形参就为我们一次返回多个结果提供了有效途径。比如对于两个整数的整除,我们想要同时返回它们的商和余数。当然我们可以想到定义一个新的数据类型,让它包含商和余数两个成员,但是给函数传入一个额外的引用实参令其保存余数更为简单。


#include <iostream>
using namespace std;

int divide(int a, int b, int &r)
{
	int c = a / b;  // 计算商
	r = a % b;      // 计算余数并用r保存
	return c;       // 返回商
}

int main()
{
	int a = 19, b = 7;
	int c, r;
	c = divide(a, b, r);
	cout << "商是: " << c << ", 余数是:" << r << endl;

	return 0;
}


运行结果是:

Android 函数指针传参 函数指针怎么传参数_参数_04


通过 swap 函数来分析函数参数的传递:

我们实现一个 swap 函数来实现两个整数的交换,先写出第一个函数 swap1():

#include <iostream>
using namespace std;

void swap1(int x, int y)
{
	int t = x;
	x = y;
	y = t;
}

int main()
{
	int a = 3, b = 6;
	cout << "swap前:a = " << a << ", b = " << b << endl;
	swap1(a, b);
	cout << "swap后:a = " << a << ", b = " << b << endl;

	return 0;
}


我们很容易误认为这个 swap1 函数可以实现 a 和 b 的交换,然而结果并没有交换成功:

Android 函数指针传参 函数指针怎么传参数_C++_05


这个错误的原因是误以为 main 函数中的 a、b 和 swap1 函数中的 x、y 是相同的了。根据上面传值调用的叙述很容易确定调用 swap1 函数时传值调用,调用 swap1 函数时将 main 函数中的实参 a、b 的值拷贝给 swap1 函数中的形参 x、y,形参 x、y 获得只是实参 a、b 的副本(记 a、b的副本为_a、_b),则 swap1 函数中 x、y 的交换操作只是 _a、_b在做交换,而实际的 main 中的实参 a、b 并没有收到任何影响,所以没有发生改变。因此这个 swap1 函数并不能实现两个整数的交换。

下面再看第二个函数 swap2():

#include <iostream>
using namespace std;

void swap2(int *x, int *y)
{
	int t = *x;
	*x = *y;
	*y = t;
}

int main()
{
	int a = 3, b = 6;
	cout << "swap前:a = " << a << ", b = " << b << endl;
	swap2(&a, &b);
	cout << "swap后:a = " << a << ", b = " << b << endl;

	return 0;
}

我们先看一下结果:

Android 函数指针传参 函数指针怎么传参数_参数_06


我们看出 swap2 函数实现了 a、b 的交换。这个也是传值调用,但传的是指针。调用 swap2 函数时将 main 中实参 a、b 的地址 pa、pb 的值拷贝给 swap2 中的形参 x、y,形参 x、y 获得是 pa、pb 的副本(记副本为 _pa、_pb)。如果要是改变 _pa、_pb 的值,那么 pa、pb 的值不会收到影响,自然 a、b 的值也不会改变。但 swap2 函数中的操作是通过 _pa、_pb 改变了它们所指对象的值,由于 pa 和 _pa 指向的是同一个位置,pb 和_pb 和指向的是同一个位置,因此 pa 和 pb 所指对象 a、b 的值也会发生改变。


最后看第三个函数 swap3() 函数:


#include <iostream>
using namespace std;

void swap3(int &x, int &y)
{
	int t = x;
	x = y;
	y = t;
}

int main()
{
	int a = 3, b = 6;
	cout << "swap前:a = " << a << ", b = " << b << endl;
	swap3(a, b);
	cout << "swap后:a = " << a << ", b = " << b << endl;

	return 0;
}

结果如下:

Android 函数指针传参 函数指针怎么传参数_参数_07


swap3 函数是传引用调用,根据上面传引用调用的叙述,调用 swap3 函数时形参 x、y 获得的是 main 中实参 a、b 的引用即别名,此时 x、y 是分别绑定在 a 、b 上的,对于引用的操作实际上是作用在引用所引的对象上的,因而通过使 a、b 的引用发生改变也会改变 a 、b 的值。


总结:

(1)传值调用执行的是值拷贝操作,传入实参时形参获得是实参的副本,所以形参的改变不会影响实参。

(2)传指针也是传值调用,执行的指针的值的拷贝,同样传入指针实参时形参获得时实参的副本,形参自身的改变不会影响指针实参,但可以实现指针所指对象值的改变。

(3)传引用调用的引用形参是它所对应实参对象的别名,形参的改变会使得所对应的实参发生相应的改变。