改善C++ 程序的150个建议学习之建议26:用引用代替指针

来源:互联网 发布:mac 终端 路径 编辑:程序博客网 时间:2024/06/05 10:26
建议26:用引用代替指针
            指针,可以通向内存世界,让我们具备了对硬件直接操作的超级能力。C++意识到了强大指针所带来的安全隐患,所以它适时地引入了一个新概念:引用。引用,从逻辑上理解就是“别名”,通俗地讲就是“外号”。在建立引用时,要用一个具有类型的实体去初始化这个引用,建立这个“外号”与实体之间的对应关系。
对于引用的理解与使用,主要存在两个的问题:它与指针之间的区别。未被充分利用。引用并非指针。引用只是其对应实体的别名,能对引用做的唯一操作就是将其初始化,而且必须是在定义时就初始化。对引用初始化的必须是一个内存实体,否则,引用便成为了无根之草。一旦初始化结束,引用就是其对应实体的另一种叫法了。与指针不同,引用与地址没有关联,甚至不占任何存储空间。代码如下所示:
int iNum = 12;
int &rNum = iNum;
int *pNum = &rNum; // 等同于int *pNum = & iNum;
iNum = 2011;             // rNum的值也为2011
由于引用没有地址,因此就不存在引用的引用、指向引用的指针或引用的数组这样的定义。据说尽管C++标准委员会已经在讨论,认为应在某些上下文环境里允许引用的引用。但那都是将来的事,至少现在不可以,将来的事谁又说得准呢?因为是别名,与实体所对应,所以引用不可能带有常量性和可挥发性。所以,下面的代码在编译时会出现问题:
int r = 10;
int & volatile s = r;
int & const m = r;
volatile int& t = r;
const int& n = r;
之所以说是出现问题,而不是错误,最主要的原因是各厂商编译器对于上述语法的容忍程度不同,有的会直接抛出错误并且编译失败,有的却只给出一个警告,比如:gcc 4.3给出错误
error: volatile/const限定符不能应用到'int&'上
VC++ 2010给出警告
warning C4227: 使用了记时错误: 忽略引用上的限定符
对于加在引用类型前面的const或volatile修饰词,它们是符合编译器规则的,或者说被编译器选择性忽略了,没有什么问题(无error或warning)。而对于指针上述使用绝对不存在任何的问题。C阵营中那帮“顽固派”习惯在C++工程里使用指针,并且以此为傲,现在该是为引用翻身的时候了。先看一个简单的示例:
void SwapData1(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
void SwapData2(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void SwapData3(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
上述代码要实现的功能极为简单,就是交换两个数据的值。SwapData1()是不能实现所设定的功能的,主要原因是函数内交换的只是实参的副本;而SwapData2()和SwapData3()则正确地实现了作者意图,其汇编代码如下:
SwapData1(a,b);
00F431EC mov eax,dword ptr [b]
00F431EF push eax
00F431F0 mov ecx,dword ptr [a]
00F431F3 push ecx
00F431F4 call SwapData1 (0F4114Fh)
00F431F9 add esp,8
SwapData2(&a,&b);
00F431FC lea eax,[b]
00F431FF push eax
00F43200 lea ecx,[a]
00F43203 push ecx
00F43204 call SwapData2 (0F411EFh)
00F43209 add esp,8
SwapData3(a,b);
00F4320C lea eax,[b]
00F4320F push eax
00F43210 lea ecx,[a]
00F43213 push ecx
00F43214 call SwapData3 (0F4101Eh)
00F43219 add esp,8
正如汇编代码中所示的那样,SwapData2()和SwapData3()其实是一样的,都是对源数据进行操作。但是相较于SwapData2()而言,SwapData3()的实现代码更加简洁清晰。这就是引用在传递函数参数时所具有的巨大优势。再来看函数返回值方面,如果其返回值是引用类型,那么就意味着可以对该函数的返回值重新赋值。就像下面代码所示的数组索引函数一样:
template <typename T, int n>
class Array
{
public:
T &operator [](int i)

return a_[i];

}
// ...
private:
T a_[n];
};
Array<int, 10> iArray;
for(int i=0; i<10; i++)
iArray[i] = i*2;
当然,上述代码可以使用指针重新实现,并且可以保证实现相同的功能,但是相比指针实现版,引用返回值使对数组索引函数的操作在语法上颇为自然,更容易让人接受。也许有人认为,指针功能更强大,因为还有指向数组的指针、指向函数的指针,这里我要说的是,引用同样可以。引用在指向数组时还能够保留数组的大小信息,关于这方面的内容,在此就不多讲了。
请记住:从编码实践角度来看,指针和引用并无太多不同。在大多情况下,指针可由索引类型完美代替,并且其实现代码更简洁清晰,更加易于理解。