返回引用是什么意思?

12

我理解C++中引用的概念,也知道它们在函数参数中使用时的作用,但是我仍然非常困惑它们如何与返回类型一起使用。

例如,在参数中使用时,下面的代码:

int main (void) {
  int foo = 42;
  doit(foo);
}

void doit (int& value) {
  value = 24;
}

类似于以下代码:

int main (void) {
  int foo = 42;
  doit(&foo);
}

void doit (int* value) {
  *value = 24;
}

(知道编译器会在每次在doit的第一个代码示例中使用value时自动在其前面放置一个星号,但在后者中,您必须自己在每次尝试使用value时放置星号)

那么,当用作引用时,下一个代码(在返回类型中使用引用)的翻译是什么?它会返回一个指向int的指针吗?还是只返回一个int?

int main (void) {
  int* foo = /*insert useful place in memory*/;
  foo = doit(foo);
}

int& doit (int* value) {
  //insert useful code
}

它将返回一个int的引用,你可以把引用看作是一个命名的指针,但没有指针的所有原始功能。所有这些中的大问题是,返回对可能在堆栈上创建的对象的引用时,当函数返回并清除函数堆栈时,您将遇到一些令人讨厌的内存问题。因此,请注意,仅因为它是引用,并不意味着您不会遇到内存问题,而且使用引用更糟糕,因为内存问题是隐藏的。 - johnathan
思考引用的最佳方式是将其视为对象的另一个名称,或者如Luchian所说,一个别名 - Peter - Reinstate Monica
5个回答

25

这意味着您通过引用返回值,而在这种情况下,这可能不是所需的。它基本上意味着返回的值是对从函数返回的任何内容的别名。除非它是一个持久化对象,否则会导致错误。

例如:

int& foo () {
    static int x = 0;
    return x;
}

//...
int main()
{
    foo() = 2;
    cout << foo();
}

如果执行foo() = 2这样的语句,会合法并打印出2,因为它修改了foo函数实际返回的值。

然而:

int& doit () {
    int x = 0;
    return x;
}

这样做可能是非法的(只是访问返回值是非法的),因为x在方法退出时被销毁,所以你将得到一个悬空引用。

对于自由函数来说,按引用返回并不常见,但是对于返回成员的方法来说却很常见。例如,在std中,常见容器的operator []就是按引用返回的。例如,使用[i]访问向量元素会返回该元素的实际引用,因此v[i] = x实际上会更改该元素。

另外,我希望“与此代码基本相等”表示它们在语义上有点类似(但并不完全相同)。不要超出这个范围。


谢谢Luchian,那帮了很大的忙!是的,我想说“类似”。我会在以后的参考中进行编辑。 - crodriguez
1
只是为了帮助澄清一下,按引用返回会返回你尝试返回的对象的地址。显然,你肯定不想返回具有局部作用域的对象的地址,因为当调用该函数完成时,该对象将被销毁,这会导致悬空引用(可能引发使用后释放错误)。因此,按引用返回的一个很好的用途,如Luchian所说,是在返回成员时使用。 - crodriguez
1
@MrMeganFox 通过引用返回不是返回地址,而是返回别名。 - Luchian Grigore
@LuchianGrigore:不,它返回一个引用而不是别名。 引用通常通过在底层使用机器地址来实现。 - Chris Dodd
@ChrisDodd 引用是指所引用的对象的“别名” - 同一对象的不同名称。 - Luchian Grigore
对象不需要是持久的,对象的生命周期需要大于函数作用域,可以通过生命周期延长来实现。 - 1stCLord

4
这意味着你返回一个指向相应数据所在内存地址的指针,而不是数据本身。

1
假设这段代码(为了与第一个示例进行比较):
int main (void) {
  int* foo = /*insert useful place in memory*/;
  *foo = doit(foo);
}

int& doit (int* value) {
  *value = 24;
  return *value;
}

int&在这种情况下并不是一个有用的返回类型,因为它提供了对内存中变量的访问(你将指向该函数的指针)。

它会返回一个指向int的指针吗?还是只会返回一个int?

不,它返回一个int的引用。如果你愿意,你可以把它看作是一个不能为nullptr的指针。


0

嗯,知道答案的最好方法是尝试一下...

你的代码将无法通过类型检查,因为doit将返回一个int的引用,而你接受返回值时却将其视为int指针。

你可以看看这个:

#include<iostream>
using namespace std;
int& doit (int* value) {
    value[0] = 3;
    return value[4];
}
int main (void) {
  int* foo = new int[10];
  for (int i=0; i<10; i++)
    foo[i] = i;
  int& bar = doit(foo);
  cout<<bar<<endl;
  for (int i=0; i<10; i++)
      cout<<foo[i]<<" ";
  cout<<endl;
  bar = 12;
  for (int i=0; i<10; i++)
      cout<<foo[i]<<" ";
  cout<<endl;
  return 0;
}

变量“bar”将接受返回值,并且可以用于更改“foo”的内容。正如Luchian所提到的,从函数返回引用可能是危险的,因为后续代码可能会修改堆栈中的值。

0

另一个有用的例子是运算符重载。

class vec2 {
public:
     vec2 operator+(const vec2& right_hand_side){
          return vec2(*this) += right_hand_side;
     }
     vec2& operator+=(const vec2& right_hand_side){
          x += right_hand_side.x;
          y += right_hand_side.y;
          return *this;
     }
 public:
 int x;
 int y;
}

在这个例子中,我们有一个代表二维向量的vec2类。现在我们为这个类定义了两个运算符,+和+=。我们希望+运算符返回vec2的副本,以避免添加两个操作数导致操作数值改变的情况。
vec2 a(1, 1);
vec2 b(5, 5);
vec2 c;
c = a + b;

在这里,我们想要将c赋值为(a+b),而不影响ab的值。然而,在+=运算符的情况下,我们希望该操作影响调用此运算符的对象,该对象是操作数之一。
vec2 c(2, 2);
c += a; // c = (3, 3) a = (1, 1)

所以,我们可以返回一个引用来影响我们想要操作的对象。如果我们没有返回引用,那么两个值都将被复制到原地。如果我们让两个运算符都返回引用,那么任何两个vec2之间的+操作都会影响操作数。
例如,如果我们改变了
vec2& operator+(const vec2 right_hand_side){
     return *this += right_hand_side;
}

执行了相同的操作,

c = a + b; // c = (6, 6), a = (6, 6)

c将被设置为(6, 6),但a也会,因为我们在+运算符中返回了一个引用。

如果有点困惑,这里稍作澄清。使用运算符重载时,调用运算符的对象是左操作数,因此当我们执行a + b时,a是我们所指的*this,以及我们返回的vec2引用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接