8.2.2 将引用用作函数参数
引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量别名(就是说,此时函数的形参和实参是同一个东西)。这种传递参数的方式称为 按引用传递 。按引用传递 允许被调用的函数访问调用函数中的变量(在函数的形参中去操作实参的别名(引用),就等于直接操作实参)。C++新增的这项特性是对C语言的超越,C语言除了指针传递的方式,只能按值传递。按值传递导致被调用函数使用调用程序的值的拷贝。
#include <iostream>
using namespace std;
void swapr(int &a,int &b);
void swapp(int *p,int *q);
void swapv(int a,int b);
int main()
{
int wallet1 = 300;
int wallet2 = 350;
cout << "wallet1 = " << wallet1 << endl;
cout << "wallet2 = " << wallet2 << endl;
cout << "Using references to swap contents:\n";
swapr(wallet1,wallet2);
cout << "wallet1 = " << wallet1 << endl;
cout << "wallet2 = " << wallet2 << endl;
cout << "Using pointers to swap contents again:\n";
swapp(&wallet1,&wallet2);
cout << "wallet1 = " << wallet1 << endl;
cout << "wallet2 = " << wallet2 << endl;
cout << "Tring to use passing by value:\n";
swapv(wallet1,wallet2);
cout << "wallet1 = " << wallet1 << endl;
cout << "wallet2 = " << wallet2 << endl;
return 0;
}
void swapr(int &a,int &b)
{
int temp;
temp = a;
a = b;
b= temp;
}
void swapp(int *p ,int *q)
{
int temp;
temp = *p;
*p = *q;
*q = temp;
}
void swapv(int a,int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
可以看到使用引用和指针的方式,wallet1和wallet2正确交换。
按引用传递(swapr()),按值传递(swapv()),按地址传递(sawpp())。
swapr(wallet1,wallet2);
swapv(wallet1,wallet2);
其中swapr()和swapv()在使用的时候看起来相同。只能通过函数原型或者函数定义才能知道swapr()是按引用传递的。而地址运算符(&)使得按地址传递swapp(&wallet1,&wallet2)一目了然(类型声明int *p表明了,p是一个int指针,所以与p对应的参数应为地址)。
swapr()和swapv()的代码唯一的外在区别是声明函数参数的方式不同:
void swapr(int &a,int &b);
void swapv(int a,int b);
swapr()和swapv()内在区别:
在swapr()中,变量a和b是wallet1和wallet2的别名,所以交换a和b等于交换 wallet1和wallet2;但是在swapv()中,变量a和b是复制了wallet1和wallet2的值的新变量,因此交换了a和b的值并不会影响wallet1和wallet2的值。
比较函数swapr()(传递引用)和swapp()(传递指针)。第一个区别是声明函数参数的方式不同:
void swapr(int & a,int & b);
void swapp(int * p,int & q);
另一个区别是指针版本需要再函数使用p和q的整个过程中使用解引用运算符*。
应该在定义应用变量时对其进行初始化。函数调用使用实参初始化形参,因此函数的引用参数被初始化为函数调用传递的实参。也就是说,下面的函数调用将形参a和b分别初始化为wallet1和wallet2:
swapr(wallet1,wallet2);
8.2.3 引用的属性和特别之处
使用引用参数时,需要了解一些特点。
#include <iostream>
double cube(double a);
double refcube(double &ra);
using namespace std;
int main()
{
using namespace std;
double x = 3.0;
cout << cube(x);
cout << " = cube of " << x << endl;
cout << refcube(x);
cout << " = cube of " << x << endl;
return 0;
}
double cube (double a)
{
a *= a*a;
return a;
}
double refcube (double &ra)
{
ra *= ra*ra;
return ra;
}
使用两个函数来计算参数的立方,其中一个函数接受double类型的参数,另一个接受double引用。
refcube()函数修改了main()中的x值,而cube()没有,这提醒我们为何通常按值传递。变量a位于cube()中,它被初始化为x的值,但是修改a并不会影响x。但由于refcube()使用了引用参数,因此修改ra实际上就是x。如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应该使用常量引用。例如,在这个例子中,在函数原型和函数头中使用const:
double refcube(const double &ra);
如果这样写代码,那么但编译器发现代码修改了ra的值的时候,将生成错误消息。
如果要编写类似上述的函数(即使用基本数值类型),应该采用按值传递的方法,而不要采用按引用传递的方式。当数据比较大(如结构和类)时,引用参数将很有用。
按值传递的函数如上述的cube(),可以使用多种类型的实参。例如:
double z = cube(x + 2.0);
z = cube(8.0);
int k = 10;
z = cube(k);
double yo[3] = {2.2,3.3,4.4};
z = cube(yo[2]);
如果将上面类似的参数传递给接受引用参数的函数,将会发现,传递引用的限制更严格,毕竟,如果ra是一个变量的别名,则实参应该是该变量。下面的代码会不合理,因为表达式x + 3.0不是变量:
double z = refcube(x + 3.0);
例如,不能将值赋给表达式:
x + 3.0 = 5.0;
如果试图使用像refcube(x + 3.0)这样的函数调用,在现代C++中,是错误的。大多数编译器都会指出这一点,然而有些老的编译器会发出这样的警告:
Warning:Temporary used for parameter 'ra' in call to refcube(double &)
之所以做出这种比较温和的反应是由于早期C++确实允许将表达式传递给引用变量。有些情况下,还是这么做的。
其结果如下:由于x+3.0不是double类型的变量,因此程序将创建一个临时的无名变量,并将其初始化为表达式x+3.0的值。然后,ra将成为该临时变量的引用。
临时变量,引用参数和const
如果实参与引用参数不匹配,C++将生成临时变量。当前,只有参数为const引用时,C++才允许这样做,但是以前不是这样的。
如果引用参数是const,则编译器将在下面两种情况下生成临时变量:
1.实参的类型正确,但不是左值。
2.实参的类型不正确,但是可以转换成正确的类型。
左值(左值参数):可以被引用的数据对象,例如,变量,数组元素,结构成员,引用和接触引用用的指针都是左值。
非左值:包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
在C语音中,左值最初指的是可以出现在赋值语句中左边的实体,但这是引入关键字const之前的情况。现在,常规变量和const变量都可以视为左值,因为可以通过地址访问它们。但常规变量属于可修改的左值,而const变量属于不可修改的左值。
Tips:如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。
尽可能使用const
将引用参数声明为常量数据的引用的理由有三个:
1.使用const可以避免无意中的修改数据的编程错误;
2.使用const使函数能够处理const和非const实参,否则将只能接受非const数据;
3.使用const引用使函数能够正确生成并使用临时变量。
因此,尽可能地将引用形参声明为const。