这样做的好处在于:(1)保证在运算中,c 与 d 的值不会意外被 修改;(2)d 对应的实际参数可以为常量也可以为变量。
在本文中,主要讨论函数(尤其是与类相关的函数——成员函数或友元函数)返回值类型何时用引用的问题。
一、函数返回值为引用的典型案例
在做输入输出重载时,重载函数返回流对象,如:
在例程1的第11和12行,运算符重载函数的返回值类类被声明为引用;在第22行和30行,这两个函数的实现中,分别返回了输入流对象input和输出流对象output(要注意到这两个对象是作为形式参数出现的,且都为引用)。实际上,<<和>>运算符对其他类型重载时也是这样处理的。这样处理的好处在于,函数返回的输入/出流还可以继续用于其他数据的输入/出,使我们能用cin>>i,j;和cout<<i<<","<<j<<endl;的形式输入/出。
以例程1中的第69行(cout<<"c1="<<c1<<endl; )为例。
cout<<"c1="<<c1<<endl; 中首先执行的是oprate<<(cout,"c1=")。实际参数——输出流对象cout(本身为引用)传递给形式参数output,在完成输出字符串"c1="的任务后,cout这个引用对象作为返回值返回到被调用处,于是余下的未执行的部分相当于:cout<<c1<<endl;。这是例程1中定义的重载发挥作用的时候了,输出c1后,cout被返回,再次用于输出换行符endl。
这里值得总结的是:(1)返回值为引用时,返回的变量仍然要继续完成相关的工作;(2)返回的引用值本身也必须是引用,一般是在调用函数中存在的,以引用型形式参数的方式传递到函数中的变量(例程1中的input和output为引用)。
二、一个令人惊讶的程序:给函数的返回值赋值
这个例子来自《c++ primer(第四版)》。
正如注释中所讲,返回值为引用,函数调用get_val(s,0)居然可以作为赋值表达式的左值,就这样,其引用的空间(s[0])中所存储的字符被赋值为‘a’,“引用是被返回元素的同义词”,此处完全等同于s[0]='a'。
程序从语法上讲没有问题,运行结果也达到了举例的目的。本例仅在于展示这种用法,理解引用作为函数返回值。在工程中,这种风格的程序当然不推荐使用,当不希望引用返回值被修改时,将返回值声明为const,即:
const char &get_val(string &str, string::size_type ix)
三、加法运算的重载结果也定义为引用,如何?
对于例程1,将其中的加法重载函数的返回值也定义为引用(当然,这样做纯属撞错),结果会怎样?先给出这样的程序,请注意在第14、15、44和52行中增加的&。
这个程序运行结果可能与例程1的结果是一样的。的确,在我运行中,结果没有出现过异常。这个程序在vs2008下编译时会有两个警告:
1>d:\c++\vs2008 project\example\example\example.cpp(49) : warning c4172: 返回局部变量或临时变量的地址
1>d:\c++\vs2008 project\example\example\example.cpp(57) : warning c4172: 返回局部变量或临时变量的地址
这两个警告道出了危险所在:第49行和57行返回临时变量c之后,c 的空间将被释放,也就意味着可以由系统进行再分配,作其他用途使用了。而返回值类型为引用时,返回的值仍然在使用着这一块内存区域。结果只能是“可能”正确,毫无保障。好比付款买了房(对方收条都没有开),房产证却放在房管局大厅中,哪一天你被赶出家门,那是活该的。这个简单的例子没有出问题纯属意外,因为操作简单,那片空间还没有被重新分配。
但是,最值得警惕的还是那些出问题可能性更小的时候,“不以恶小而为之”,一贯正常,只有很小几率出的错的情况更可怕。
最重要的,理解了上述道理,要做到:千万不要返回对局部变量的引用。
还有一条类似的:千万不要返回局部变量的指针。
四、钻个牛角尖:operate+就要返回引用
重温本文第一部分最后的加粗字:(2)返回的引用值本身也必须是引用,一般是在调用函数中存在的,以引用型形式参数的方式传递到函数中的变量(例程1中的input和output为引用)。要应付这种胡搅蛮缠式的要求,我们也只能围绕这个要求想办法。
给出的一种解决办法是:
这种实现中,c1在调用operate+()前已经存在,是作为引用进行参数传递的。这样的变量能够保证程序不会出现意外。但是,会出的代价是,c1的值在参与加法运算时被改变了。在第40行执行了c3=c1+c2后,第41行显示的c1的值同c3的值相同,是相加后的结果。
这种安排也只能接受这种结局。事实上,学习计算机的同学也要接受这种风格,在有些语言(例如,汇编以及被冠以高雅称号的函数式语言)中,运算就是这么完成的。c1+c2怎么完成?add c1, c2; 其结果如何取出?结果就保存到第一个运算量中。
牛角尖再钻深些,不能这样做!我只能为返回引用再使一招了:提前定义保存结果的变量(例c),并将之作为参数传递到函数中。付出的代价是,加运算的运算量成了3个,operate+()形式是不能用了(运算符重载不能改变其目数)。实际上,返回的那个引用也没有什么意思了,结果已经由引用 c 带回来了,返回值甚至可以为void。这种设计太差了,程序依然贴在下面,读者不看也罢。
<本文完>