实用经验 9 引用难道只是别人的替身?

引用是我们多次讨论的概念了,引用只是初始化物的别名。对应用唯一的操作就是将其初始化。一旦引用初始化结束,引用就是其初始化物的另一种写法罢了。引用变量没有地址,甚至他们可能不占用任何存储空间。

注意事项:

  1. 声明引用的引用、指向引用的指针、指向引用的数组都是非法的。
  2. 引用不可能带有常量性和易变性,因为别名不能带有常量性和易变性。用关键字const和volatile修饰引用会造成编译错误。
  3. const或volatile修饰引用类型,不会造成编译错误,但是编译器会默认忽略这些修饰。
int  a = 12;
int &ra = a;
int &p = &ra;       // p 指向a的地址
a = 42;             // ra 和a的值都变成42了
int &rri = ra;      // 错误
int &*pri;          // 错误
int &ar[3];         // 错误

我们一直在说引用就是别名,既然是别名,总的是“某个已存在东西”的别名。并且这个东西必须真实存在才行。其实引用不只是简单变量名的别名,其实任何任何可作为左值的复杂表达式都可以作为引用的初始化物。只要类型确定,有明确的内存地址,可作左值,就可初始化引用。引用和函数相结合时,有这么几种功能:

如果一个函数返回一个引用,这说明此函数的返回值可重新赋值。我们经常使用的STL库中所有的下表操作基本上都是这样返回引用的形式。例如vector数据类型的下表操作声明:

template <class Tclass Alloc = alloc> // vector STL模版类声明
class vector
{
public:
    typedef T  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);
}

引用的另一个用途,即是让函数在其返回值之外多传递几个值。例如:

typedef  int  failure;
char *FindStr(const char *pszMainStr, failure &reason);

另外一个需要特别注意的地方时,指向数组的引用保留了数组的长度信息。而指针不会保留数组的长度信息。例如下面的代码,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] = {24};
    Array_Test1(n3); //   错误“Array_Test1”: 不能将参数 1 从“int [2]”转换为“int (&)[3]”
    Array_Test2(n3);//   可正常编译通过
}

最后,我们讨论一下常量引用(const reference)。为了阐述常量引用的特殊之处,我们看下面的代码:

int &rInt = 12;       // 错误
const int &rInt = 12;  // 正常编译通过

可以看出常量值不能给普通引用初始化,但是常量值可以给const引用初始化。

小心地雷

  • 如果初始化值是一个左值(可以取得地址),则可以初始化引用,没有任何问题;
  • 如果初始化值不是一个左值,则只能对const T&(常量引用)赋值。且赋值过程包括三个步驟:
    (1)将值隐式转换到类型T;
    (2)将这个转换结果存放在一个临时对象里;
    (3)用这个临时对象来初始化这个引用变量。
    在这种情况下,const T&(常量引用)中使用的临时对象会和const T&(常量引用)共存亡。

上述代码,引用rInt指向编译器隐式分配内存并创建的匿名int类型临时对象。对rInt引用的任何操作都会影响匿名临时变量,而不会影响常量12。同时编译器也会确保这样的匿名临时对象会将生命期扩展到初始化后的引用存在的全部时域。这无形之中也开启了临时生命期问题的万劫不复之门。我们考察看下面这段普通代码:

short s = 123;
const int &rIntegrate = s;
s = 321;
const int *ip = &rIntegrate;
printf(“rIntegrate = %d, s = %d\r\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)
{
    return ((a > b)?(a): (b));
}

咋一看,此函数完全无害:函数功能很简单,就是返回两个参数中的一个罢了。引发问题的是那个return语句。因为a,b都是const引用,在函数参数实参传值时,函数首先会生成两个临时对象将实参的值拷贝到临时对象中,然后用这两个临时对象初始化a,b。现在问题也许你明白了。函数返回临时变量的引用了。

请谨记

  • 若非必要请不要使用const引用。因为const引用有时会伴随着临时对象的产生。
  • 在函数声明时,请尽量避免const引用形参声明,使用非const引用形参替代之,以防因返回const引用生成的临时变量,而导致程序执行错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值