引用是我们多次讨论的概念了,引用只是默认值的别名。对引用唯一的操作就是将其初始化。一旦引用初始化结束,引用就只是其默认值的另一种写法罢了。引用变量没有地址,甚至它们可能不占用任何存储空间。
注意事项
(1) 声明引用的引用、指向引用的指针、指向引用的数组都是非法的。
(2) 引用不可能带有常量性和挥发性,因为别名不能带有常量性和挥发性。用关键字const和volatile修饰引用会造成编译错误。
(3) 用const或volatile修饰引用类型,不会造成编译错误,但是编译器会默认忽略这些修饰。
inta=12;
int &ra =a;
int &p =&ra; //p指向a的地址
a=42; //ra和a的值都变成了42
int &&rri=ra; //错误
int&*pri; //错误
int &ar[3]; //错误
我们一直在说引用就是别名,既然是别名,就要是“某个已存在变量”的别名,并且这个变量必须真实存在才行。其实引用不只是简单变量名的别名,其任何可作为左值的复杂表达式都可以作为引用的默认值。只要类型确定,有明确的内存地址,可做左值,就可初始化引用。引用和函数相结合时,有如下几种功能:
(1) 如果一个函数返回一个引用,这说明此函数的返回值可重新赋值。我们经常使用的 STL库中所有的下标操作基本上都是这样返回引用的形式。例如 vector 数据类型的下标操作声明:
template <class T, class Alloc =alloc> // vector STL模板类声明
class vector
{
public:
typedefT
value_type; // 数据类型
typedef value_type reference;
typedef size_t size_type;
typedef value_type*iterator;
...
protected:
iterator start;
public:
iterator begin() { return start;}
reference operator[] (size_type n) { return *(begin() + n);
}
(2) 引用的另一个用途即让函数在其返回值之外多传递几个值。例如:
typedef int failure;
char *FindStr(const char *pszMainStr, failure &reason);
(3) 另外一个需要特别注意的地方是,指向数组的引用保留了数组的长度信息,而指针不会保留数组的长度信息。例如下面的代码,Array_Test1 函数可记住实参必须为长度为 3 的数组,而Array_Test2 却无法记录,导致长度为 2 的数组也可作为实参传给函数。
//引用形式数组测试函数
void Array_Test1(int (&array)[3])
{
array[2]=3;
}
//非引用形式数组测试函数
void Array Test2(int array[3])
{
array[2]=3;
int _tmain(int argc, char* argv[])
{
int n3[2]={2, 4};
Array_Test1(n3); //错误“Array_Test1”:不能将参数1 从“int [2]”转换为“int(&)[3]”
Array_Test2(n3);//可正常编译通过
}
(4) 最后,我们讨论一下常量引用(const reference)。为了阐述常量引用的特殊之处,我们看下面的代码:
int &rInt=12; //错误
constint&rInt=12; //正常编译通过
可以看出常量值不能给普通引用初始化,但是可以给const引用初始化。
小心陷阱
● 如果初始化值是一个左值(可以取得地址),则可以初始化引用,没有任何问题。
● 如果初始化值不是一个左值,则只能对constT&(常量引用)赋值,且赋值过程包括3个阶段:首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量。
● 在这种情况下,constT&(常量引用)过程中使用的临时对象会和constT&(常量引用)共“存亡”。
上述代码中,引用 rInt指向编译器隐式分配内存并创建的匿名int类型临时对象。对 rInt引用的任何操作都会影响匿名临时变量,而不会影响常量 12。同时编译器也会确保这样的匿名临时对象会将生命期扩展到初始化后的引用存在的全部时域。这无形之中也开启了临时生命期问题的万劫不复之门。我们来看下面这段普通代码:
shorts=123;
const int &rIntegrate = s;
s=321;
const int *ip = &rlntegrate;
printf("rIntegrate =%d, s = %dr\n", rIntegrate, s);
printf("ip = %d, &s = %d", ip, &s);
输出结果为:
rIntegrate = 123, s =321
ip=2030760,&s =2030784
可看出 rIntegrate引用的默认值并不是 s,而是常量引用初始化过程中隐式使用的匿名对象。接着,我们看一下const引用作为函数形参存在的问题。来看下面这段代码:
const int&GetMax(const int &a, const int &b)
returm ((a>b)?(a): (b));
乍一看,此函数完全无害:函数功能很简单,就是返回两个参数中的一个。引发问题的是那个 retun 语句。因为 a、b都是const引用,在函数参数实参传值时,函数首先会生成两个临时对象将实参的值复制到临时对象中,然后用这两个临时对象初始化a、b。现在你也许明白了:函数返回了临时变量的引用。
请谨记
● 若非必要请不要使用const引用,因为const引用有时会伴随着临时对象的产生。
● 在函数声明时,请尽量避免const引用形参声明,使用非const引用形参替代,以防因返回const引用生成的临时变量而导致程序执行错误。