被调函数的返回类型决定返回的是左值还是右值,当被调函数的返回类型是 引用 时,返回的是左值,其余情况下返回的是右值,被调函数返回的左值有着与其他左值类型同样的运算特性,需要特别指出的是, 我们能为返回类型是非常量引用的函数的结果赋值 ,但是需要注意的是,进行运算的前提是确保返回的引用是有效的, 返回与局部对象绑定的引用是无效的 ,因为被调函数运行结束时,局部对象的存储空间已经释放,返回与局部对象绑定的引用将会产生错误的结果,要想确保返回值的安全,我们不妨自问:引用绑定的是被调函数运行前的哪一个对象?然而,在某些时候,也是容易让人产生混淆的,譬如下面的代码,在很多人看来是错误的,但事实并非如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <cstddef>
#include <iterator>
using std::cout;
using std::endl;
using std::begin;
using std::end;

int &get(int *arry, size_t index)
{
return arry[index];
}

int main()
{
int ia[10] = { 2, 4, 6, 8, 1, 3, 5, 7, 9, 10 };
for (size_t i = 0; i != 10; ++i) // 将 ia 元素的值改为对应的下标
get(ia, i) = i;

for (auto ibeg = begin(ia), iend = end(ia); ibeg != iend; ++ibeg)
cout << *ibeg << " ";
cout << endl;

return 0;
}

在 VS2013 中编译并运行以上的程序,得到了理想的结果,有人会说,不对呀,arry[index] 明明是一个局部的对象,返回一个与局部对象绑定的引用不是会产生错误的结果么?然而,arry 是一个局部对象就意味着arry[index] 也是一个局部对象吗?我们来分析一下。

main 函数中传递给 arry 的参数是 ia,即数组 ia 首元素的地址,arry[index] 实际上是对指向 ia 首元素的指针进行下标运算,相当于以下两步操作:

1
2
int *p = arry;
*(p + index);

也就是说,arry[index] 的值就是 *(p + index) ,请注意,不是 *(p + index) 的副本,而是本身就是 *(p + index) ,这二者是同一个对象,而 *(p + index) 本质上就是 ia[index],即说 arry[index] 本身就是 ia[index] 而非 ia[index] 的副本,所以,arry[index] 并不是一个局部对象,它是一个在被调函数运行之前就已经存在的对象,虽然 arry 是一个局部对象,但是,由它进行下标运算而最后得到的值却不是一个局部对象。

经过以上分析,我们可以得到以下的结论:

虽然某个对象是局部对象,但是由它经过下标运算后得到的对象却并不一定是一个局部对象,应该具体问题具体分析。