函数的引用返回

时间:2022-09-30 15:33:37
引用是给变量取一个别名,所以引用传递会直接进行变量本身的传递。它的最大好处是可以把别处对变量的改变保留下来,第二好处是它提高了性能:如果函数的返回值是一个引用,那么,如上文所说,它会节约一组构造、赋值和析构过程。但是,函数返回引用往往会带来一些意想不到的错误:比如返回临时变量的引用。
  //一个错误的函数
  int &Max(int i, int j)
  {
    return i>j ? i : j;
  }
    以上函数的错误在于,i和j在函数结束后会被释放。对它们的引和也将失效。如果用这个返回值给别的变量赋值,将会获得一个垃圾。VC++.Net会对以上return语句显示警告。
    那么,如果返回一个全局变的引用呢?这当然是可以的,但是,一来程序设计中不建议使用过多的全局变量,二来全局变量即使不返回也可以访问。这样做的唯一用途就是把函数做右值来给其它变量赋值。
  int m;//全局变量
  int &MaxByGlobal(int i, int j)
  {
    return m = i>j ? i : j;
  }
  int a, b, c;
  c = MaxByGlobal(a, b);//用法一、用返回值赋值
  MaxByGlobal(a, b); c = m;//用法二、不用返回值赋值
    当然,以上这个MaxByGlobal函数也不是一无是处,能用返回值来进行赋值会给程序带来更好的可读性。只是这样的函数设计本身不被建议。
    那么,函数返回引用用得最多的就是返回形参了。因为形参可以用引用传递,引用的形参不是函数内部的局部变量,这样做是可取的:
  int &MaxByRef(int &i, int &j)
  {
    return i>j ? i : j;
  }
    上面这个函数和上文中的“int Max(int i, int j)”函数如此相似,但是它省去了三次构造、赋值和析构。
    另外一种用法就是在类的成员函数中返回类对象自身了,典型的是“operator +=”函数之类。
  MyClass &MyClass::operator +=(const MyClass &other)
  {
    //某些语句
    return *this;
  }
    以上函数返回的是自身的引用。因为类的成员函数也可以写成全局函数“MyClass &operator +=(MyClass &Left, const MyClass &right)”,而且在类成员函数的调用中实际存在着this指针的传递。所以,以上这个函数依然可以看作返回了形参的引用。
    对于返回引用的函数,还有一个好玩的现像。即返回值还可能可以被赋值。如“(a += b) = c;”这样的形式。这种写法明显不伦不类,但是如果函数返回了非const的引用,这个表达式的确是合理的。所以,上面的“operator +=”函数还要修改一下,将返回值由“MyClass&”改为“const MyClass&”。
    返回引用并不是处处可用的,正如《引用传递的应用范围》中提到的一样:不能用引用来传递临时值。有时候我们的确要产生一个临时对象并返回它,那就不能返回引用。典型的有“operator +”函数:
  const MyClass MyClass::operator +(const MyClass &other) const
  {
    MyClass Temp;
    //某些语句

    return Temp;//这里只能返回对象,因为Temp必须是局部变量


函数返回引用类型和非引用类型的区别

函数返回引用类型 和 非引用类型究竟有什么不同呢?

先来个总结:非引用类型返回值的函数,返回的是一个新的临时对象。

 

分析见下图:

#include <iostream>
using namespace std;
class CText
{
public:
 ~CText()
 {
  cout<<"析构了"<<this<<endl;
 }
};

CText& fRefer(CText &a)
{
 return a;
}

CText fNoRefer(CText &a)
{
 return a;
}


int main(int arge,char*argv[ ])
{
 CText a;
 cout<<"a的地址:"<<&a<<endl;

 cout<<endl<<"------引用类型返回值------"<<endl;
    a=fRefer(a);

 cout<<endl<<"------非引用类型的返回值------"<<endl;
    a=fNoRefer(a);

 cout<<endl<<"------main结束------"<<endl;
 return 0;
}

 

运行结果:

函数的引用返回 图1
如图,在调用非引用类型返回值函数的时候会多出了一个临时的对象。

多出的临时对象究竟有什么用呢?

 

我们用反汇编再深入看看内部运行过程。

如果对反汇编调试不太熟的朋友,请先看:《反汇编学习》

在看引用类型返回值函数之前先看看返回引用类型值的函数调用过程,如图:

函数的引用返回图2

解读:[ebp-10h]存放的是a对象的地址,把a作为参数压栈,然后调用fRefer。

fRefer内部:(只看红色部分)

函数的引用返回     图3
解读:[ebp+8]就是fRefer的参数a,直接将a返回。

接下来就看非引用类型返回值函数如图:

函数的引用返回图4

解读:将a对象的地址作为参数压入栈,然后调用fNoRefer。咦? 怎么压2个参数到栈里?一个是a对象的地址,另一个是?

这里,细心的读者可以发现另外一个参数正是图1里冒出来的临时对象。

fNoRefer函数的内部:(重点看红色框内部)

 函数的引用返回图5

解读:[ebp+0Ch]是第一个参数即a对象的地址,[ebp+8]是第二个参数即临时产生的对象。将a赋值给临时对象,然后将临时对象返回。
回到图4,最后将返回值(即临时对象)赋值给a,然后销毁临时对象。

函数的引用返回

结论:非引用类型返回的是临时对象。

可见非引用类型的不但要多建一个临时对象,还会多出了不少的语句从而多消耗CPU性能。

 

如果还觉得晕晕的,我再举个例子:

#include <iostream>
using namespace std;
class CText
{
public:
 void say()
 {
  cout<<"我是"<<this<<endl;
 }
 ~CText()
 {
  cout<<"析构了"<<this<<endl;
 }
};

CText& fRefer(CText& a)
{
 return a;
}

CText fNoRefer(CText& a)
{
 return a;
}

int main(int arge,char*argv[ ])
{
 CText a;
 cout<<"a的地址:"<<&a<<endl;

 cout<<endl<<"------引用类型返回值------"<<endl;
    fRefer(a).say();
 cout<<endl<<"------非引用类型的返回值------"<<endl;
    fNoRefer(a).say();
 cout<<endl<<"------main结束------"<<endl;
 return 0;
}

结果:

函数的引用返回


  }