24

我理解与任何其他变量一样,参数的类型决定了参数与其参数之间的交互。我的问题是,为什么要引用参数而不是为什么不引用参数的原因是什么?为什么有些函数参数是引用而有些不是?无法理解这样做的好处,有人可以解释一下吗?

4

4 回答 4

52

存在通过引用传递的能力有两个原因:

  1. 修改函数参数的值
  2. 为了避免出于性能原因复制对象

修改参数的示例

void get5and6(int *f, int *s)  // using pointers
{
    *f = 5;
    *s = 6;
}

这可以用作:

int f = 0, s = 0;
get5and6(&f,&s);     // f & s will now be 5 & 6

或者

void get5and6(int &f, int &s)  // using references
{
    f = 5;
    s = 6;
}

这可以用作:

int f = 0, s = 0;
get5and6(f,s);     // f & s will now be 5 & 6

当我们通过引用传递时,我们传递变量的地址。通过引用传递类似于传递指针——在这两种情况下都只传递地址。

例如:

void SaveGame(GameState& gameState)
{
    gameState.update();
    gameState.saveToFile("save.sav");
}

GameState gs;
SaveGame(gs)

或者

void SaveGame(GameState* gameState)
{
    gameState->update();
    gameState->saveToFile("save.sav");
}

GameState gs;
SaveGame(&gs);


由于只传递了地址,因此不需要复制变量的值(对于大型对象来说可能非常大) 。因此,通过引用传递可以提高性能,尤其是在以下情况下:

  1. 传递给函数的对象很大(我会在这里使用指针变体,以便调用者知道函数可能会修改变量的值)
  2. 该函数可以被多次调用(例如在循环中)

另外,请阅读const参考资料。使用时,不能在函数中修改参数。

于 2013-11-07T03:07:09.890 回答
24

这篇文章对我帮助很大。

请暂时忘记指针。把这个和一粒盐一起吃。

引用对象。当你通过引用传递时,你传递对象。

当你通过值传递时,你传递了一个对象的副本另一个对象。它可能具有相同的state,但它是不同的实例;一个克隆。

因此,如果您:

  • 需要修改函数里面的对象
  • 不需要(或不想)修改对象,但想避免复制它只是为了将它传递给函数。这将是一个const参考。

如果您:

  • 想从同卵 双胞胎开始,不打扰原来的双胞胎
  • 不关心复制对象的成本(例如,除非我想修改它,否则我不会通过int引用传递)。

在这里,看看这段代码:

#include<iostream>

struct Foo {
  Foo() { }
  void describe() const {
    std::cout<<"Foo at address "<<this<<std::endl;
  }  
};

void byvalue(Foo foo) {
  std::cout<<"called byvalue"<<std::endl;
  foo.describe();
}

void byreference(Foo& foo) {
  std::cout<<"called byreference"<<std::endl;  
  foo.describe();
}

int main() {
  Foo foo;
  std::cout<<"Original Foo"<<std::endl;
  foo.describe();
  byreference(foo);
  byvalue(foo);
}

并像这样编译它:g++ example.cpp

运行:./a.out

并检查输出(您的计算机中的实际地址可能不同,但要点仍然存在):

Original Foo
Foo at address 0x7fff65f77a0f
called byreference
Foo at address 0x7fff65f77a0f
called byvalue
Foo at address 0x7fff65f779f0

请注意called byreference地址与Original Foo地址的相同之处(两者都是0x7fff65f77a0f)。并注意called byvalue地址有何不同(它是0x7fff65f779f0)。

把它提升一个档次。修改代码如下所示:

#include<iostream>
#include<unistd.h> // for sleeping

struct Foo {
  Foo() { }
  Foo(const Foo&) {
    sleep(10); // assume that building from a copy takes TEN seconds!
  }
  void describe() const {
    std::cout<<"Foo at address "<<this<<std::endl;
  }  
};

void byvalue(Foo foo) {
  std::cout<<"called byvalue"<<std::endl;
  foo.describe();
}

void byreference(Foo& foo) {
  std::cout<<"called byreference"<<std::endl;  
  foo.describe();
}

int main() {
  Foo foo;
  std::cout<<"Original Foo"<<std::endl;
  foo.describe();
  byreference(foo);
  byvalue(foo);  
}

以相同的方式编译它,并注意输出(输出中没有注释;为清楚起见包括在内):

Original Foo
Foo at address 0x7fff64d64a0e
called byreference
Foo at address 0x7fff64d64a0e # this point is reached "immediately"
called byvalue # this point is reached TEN SECONDS later
Foo at address 0x7fff64d64a0f

因此,该代码旨在夸大副本的成本:当您通过引用调用时,不会产生此成本。当您按值调用时,您必须等待十秒钟。

注意:我的代码是在 OS X 10.7.4 中使用 GCC 4.8.1 编译的。如果您在 Windows 中,您可能需要一些不同的东西unitsd.h才能使sleep呼叫正常工作。

也许这有帮助。

于 2013-11-07T03:16:09.673 回答
1

使用按引用传递的优点:您不必创建数据的副本,您只需在内存中传递指向它的指针。(想想如果你有一个巨大的物体,你会获得巨大的性能胜利)。您可以“返回”多个值 我知道 c/c++ 中的某些函数返回一个数字,其中一个参数是指向被操作数据的指针。

使用按引用传递的缺点:您必须小心修改传递的数据,因为它可能会导致您可能想要或可能不想要的副作用。

于 2013-11-07T02:56:48.687 回答
1

通过引用同样是通过指针手动传递变量,但通过引用不会让用户处理“容易搞砸”的指针。

于 2013-11-07T03:01:02.387 回答