传值参数与传引用参数
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。
传值参数
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
// return the greatest common divisor
int gcd(int v1, int v2)
{
while (v2) {
int temp = v2;
v2 = v1 % v2;
v1 = temp;
}
return v1;
}
while 循环体虽然修改了 v1 与 v2 的值,但这些变化仅限于局部参数,而对调用 gcd 函数使用的实参没有任何影响。于是,如果有函数调用:
gcd(i, j)
则 i 与 j 的值不受 gcd 内执行的赋值操作的影响。
非引用形参表示对应实参的局部副本。对这类形参的修改仅仅改变了局部副本的值。一旦函数执行结束,这些局部变量的值也就没有了。
指针形参
函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
事实上被复制的指针只影响对指针的赋值。如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值:
void reset(int *ip)
{
*ip = 0; // changes the value of the object to which ip points
ip = 0; // changes only the local value of ip; the argument
is unchanged
}
调用 reset 后,实参依然保持原来的值,但它所指向的对象的值将变为 0。
如果保护指针指向的值,则形参需定义为指向 const 对象的指针:
void use_ptr(const int *p)
{
// use_ptr may read but not write to *p
}
指针形参是指向 const 类型还是非 const 类型,将影响函数调用所使用的实参。我们既可以用 int* 也可以用 const int* 类型的实参调用 use_ptr 函数;但仅能将 int* 类型的实参传递给 reset 函数。这个差别来源于指针的初始化规则。可以将指向 const 对象的指针初始化为指向非 const对象,但不可以让指向非 const 对象的指针向 const 对象。
值传递的局限性
值传递并不是在所有的情况下都适合,不适宜值传递的情况包括:
当需要在函数中修改实参的值时。
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
当没有办法实现对象的值传递时。
传引用参数
// incorrect version of swap: The arguments are not changed!
void swap(int v1, int v2)
{
int tmp = v2;
v2 = v1; // assigns new value to local copy of the argument
v1 = tmp;
} // local objects v1 and v2 no longer exist
这个例子期望改变实参本身的值。但对于上述的函数定义,swap 无法影响实参本身。执行 swap 时,只交换了其实参的局部副本,而传递 swap 的实参并没有修改。
为了使 swap 函数以期望的方式工作,交换实参的值,需要将形参定义为引用类型:
// ok: swap acts on references to its arguments
void swap(int &v1, int &v2)
{
int tmp = v2;
v2 = v1;
v1 = tmp;
}
与所有引用一样,引用形参直接关联到其所绑定的对象,而并非这些对象的副本。定义引用时,必须用与该引用绑定的对象初始化该引用。引用形参完全以相同的方式工作。每次调用函数,引用形参被创建并与相应实参关联。
使用引用形参返回额外的信息
引用形参的另一种用法是向主调函数返回额外的结果。函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。
利用引用避免复制
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;使用引用形参,函数可以直接访问实参对象,而无须复制它。
编写一个比较两个 string 对象长度的函数作为例子。这个函数需要访问每个 string 对象的 size,但不必修改这些对象。由于 string 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:
// compare the length of two strings
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。