必备技能6.4:引用参数
前面我们看到可以通过使用指针的方式来手动地实现传引用,但是这种方式显得有些笨拙。首先,这种方式强制我们所有的操作都是基于指针的。其次,这种方式要求我们必须铭记在传参数的时候需要传入变量的地址。然而,C++中有一种方式可以自动地告诉编译器在调用某些函数的时候使用的是传引用而不是缺省的传值。这是通过引用参数来实现的。当我们使用引用参数的时候,参数的地址而不是值自动地被传入到函数中。在被调用的函数中,对引用参数的操作会被C++编译器自动感知为对地址的操作,因此我们不需要使用和指针相关的那些运算符。
声明函数的参数为引用参数的时候,只需要在参数的前面加上&即可。对于引用参数的操作会影响实参的值,而不是影响引用参数。
为了更好地理解引用参数,我们可以看看下面简单的程序。在下面的程序中,函数f()使用一个int类型的引用参数。
// 使用引用参数
#include <iostream>
using namespace std;
void f ( int &i ); //这里i是引用参数
int main()
{
int val = 1;
cout << "Old value for val: " << val << '\n';
f(val);
cout << "New value for val: " << val << '\n';
return 0;
}
void f(int &i)
{
i = 10; //这句将导致传入的参数的值被修改
}
程序的输出如下:
Old value for val: 1
New value for val: 10
我们需要特别注意函数f()的定义:
void f(int &i)
{
i = 10; //这句将导致传入的参数的值被修改
}
注意上面对i的声明。变量i的前面多了一个&,它就会使得变量i变为一个引用参数。(在函数的声明中也需要这样写。)在函数的内部,语句
i = 10;
不是给i赋值10,而是给被i引用的变量(实际上就是val)赋值为10。还需要注意的是,这条语句中没有使用与指针相关的*运算符。当我们使用引用参数的时候,C++编译器能自动地知道它是一个地址。实际上,在代码中写上*会导致错误。
由于i被声明为是一个引用参数,编译器会在调用f()函数的时候把实参的地址传递给f()。因此main()函数中是把val的地址,而不是值传递给了f()。这里我们没有必要在val的前面增加&运算符了(实际上这样做会导致编译错误)。既然f()函数接收到的是以引用的方式接收到val的地址,它就可以修改变量val的值。
为了充分展示实际中引用参数的用法以及带来的好处,我们可以采用引用参数的形式重写一下swap()函数。请特别留意下面的swap()函数的声明和调用。
//使用引用参数来实现swap()函数
#include <iostream>
using namespace std;
//声明swap()函数使用引用参数
void swap(int &x, int &y);
int main()
{
int i,j;
i = 10;
j = 20;
cout << "Initial values of i and j : ";
cout << i << ' ' << j << '\n';
swap(j, i);
cout << "Swapped values of i and j : ";
cout << i << ' ' << j << '\n';
return 0;
}
/*
下面采用引用参数的方式,而不是传值的方式来实现swap()函数。
因此,swap()函数能够完成对传入参数的值的交换
*/
void swap(int &x, int &y)
{
int temp ;
//使用引用来完成参数值的交换。
temp = x;
x = y;
y = temp;
}
这个程序的输出和上一个版本的输出结果是一样的。再次提醒,因为x,y都是引用参数,所以我们在交换值的时候不需要使用*运算符。请记住:在调用函数swap()的时候,编译器会自动地传入实参的地址。
我们复习一下前面的内容。当创建了引用参数的时候,参数会自动地表示对传入实参的引用。进一步来说,调用该函数的时候没有必要使用&运算符。同样,在该函数的内部,我们直接使用引用参数,而不用使用*运算符。在函数中所有对于引用参数的操作都会自动转换为对传入实参的操作。最后,当我们在给引用参数赋值的时候,我们实际上是给它指向的对象进行赋值。
最后一点,C语言是不支持引用的。因此,在C语言中使用指针是实现传引用的唯一方式,就像我们在swap()函数的第一个版本的那样。当需要把C代码转换为C++代码的时候,我们可能需要把这种代码转换为引用参数的方式。
专家答疑
问:
在一些C++的代码中,我看到一些变量在声明的时候,&是和变量的类型结合在一起的,而不是和变量自身结合在一起的,如下所示:
int& i;
而不是:
int &i;
这两种方式有区别吗?
答:
简而言之,是没有区别的。比如,我们也可以采用如下的方式来声明swap()函数的原型:
void swap( int& x, int& y);
正如我们所看到的那样,&是紧跟在int的后面,而不是紧密地写在了x的前面。更有甚者,一些程序员也会把声明指针的*紧密跟在类型的后面,而不是写在变量名称的前面,如下:
float* p;
这种写法反映出了C++程序员试图创建一个单独的引用或者指针类型的良好愿望。然而这种写法存在一个问题。那就是,根据C++的语法,&和*是不能作用于一系列变量的。例如下面的声明方式声明的是一个int类型的指针,而不是两个:
int* a, b;
这里,b是一个int类型的变量,而不是一个int类型的指针变量。这是因为,根据C++语法,当在声明中使用*或者&得时候,它们是和其后的单个变量相关联的,而不是和前面的类型相关联的。
需要明确的是,就C++编译器而言,我们到底是写int *p还是int* p并不重要。因此,如果你个人喜欢把*和&紧密地和类型写在一起,而不是和变量的名称写在一起的话,你完全可以这样做的。然而,为了避免混淆,本书中我们将始终把*和&写在它们所修饰的变量名称的前面,而不是紧跟在类型的后面。
练习:
1. 如何声明引用参数?
2. 当调用使用引用参数函数的时候,我们必须在传入参数的前面使用&吗?