C++:参数传递“按引用传递”

24

我了解,与任何其他变量一样,参数的类型确定了参数与其参数的交互。我的问题是,为什么你会引用一个参数,而不引用另一个参数?为什么有些函数的参数是引用,而有些不是?我很难理解这样做的优势,有人可以解释一下吗?


创建一个包含2GB数据的向量。现在将其传递给一个函数...一百万次。我赞扬这个问题。通常它以“引用和指针不是一样的吗?”开始。 - WhozCraig
那么总是引用参数有益吗?还是这样做不太明智? - Tyler
有时候传值是有明确目的的,这非常情境化。其中一些情况可能非常微妙。 - WhozCraig
这个问题不是每个新接触C++的程序员都问过无数遍了吗?为什么不直接谷歌一下呢? - Siyuan Ren
11
非常抱歉,我以为我在正确使用这个资源。我认为向知识渊博的人询问并由他们解释比多次阅读材料(特别是《C++ Primer Fifth Ed.》)而不完全理解问题“为什么”要好得多。 - Tyler
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 引用的相关知识,当使用它时,函数内的参数将不能被修改。


24

这篇文章帮了我很多。

现在先不考虑指针,但也请谨慎对待此处内容。

引用就是对象本身。当您通过引用传递时,您传递的是该对象。

当您按值传递时,您传递的是对象的副本;另一个对象。它可能具有相同的状态,但它是不同的实例;一个克隆体。

因此,如果您需要在函数内修改对象,则通过引用传递可能是有意义的。

  • 需要在函数内修改对象
  • 不需要(或不想)修改对象,但希望避免复制该对象只为将其传递给函数。这将是一个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

注意,按引用调用的地址与原始 Foo的地址相同(都是0x7fff65f77a0f)。而按值调用的地址则不同(它是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调用起作用。

也许这会有所帮助。


1
使用引用传递的优点: 您不必创建数据的副本,只需在内存中传递指向它的指针即可。(巨大的性能优势,想象一下如果您传递了一个庞大的对象)。 您可以“返回”多个值。我知道C / C ++中的某些函数返回一个数字,其中一个参数是指向被操作的数据的指针。
使用引用传递的缺点: 修改传递的数据时必须小心,因为它可能会导致您可能或可能不希望的副作用。

1

按引用传递变量与手动通过指针传递变量相同,但按引用传递变量不会让用户处理“容易混淆”的指针。


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