33

C 和 C++ 开发人员都使用“按引用传递”这个短语,但它们似乎用于表示不同的东西。每种语言中这个模棱两可的短语之间到底有什么区别?

4

1 回答 1

72

有些问题已经处理了按引用传递和按值传递之间的区别。本质上,将参数按值传递给函数意味着该函数将拥有自己的参数副本 - 它的被复制。修改该副本不会修改原始对象。但是,当通过引用传递时,函数内部的参数的是传入的同一个对象 - 函数内部的任何更改都会在外部看到。

不幸的是,有两种方式使用“按值传递”和“按引用传递”这两个短语,这可能会引起混淆。我相信这就是为什么新的 C++ 程序员很难采用指针和引用的部分原因,尤其是当他们有 C 的背景时。

C

在 C 中,从技术意义上讲,一切都是按值传递的。也就是说,无论您作为函数的参数提供什么,它都会被复制到该函数中。例如,调用函数void foo(int)foo(x)将 的值复制x为 的参数foo。这可以从一个简单的例子中看出:

void foo(int param) { param++; }

int main()
{
  int x = 5;
  foo(x);
  printf("%d\n",x); // x == 5
}

的值x被复制到foo该副本中并且该副本递增。xinmain继续具有其原始价值。

我相信您知道,对象可以是指针类型。例如,int* p定义p为指向int. 需要注意的是,以下代码引入了两个对象:

int x = 5;
int* p = &x;

第一个是 typeint并具有 value 5。第二个是类型int*,它的值是第一个对象的地址。

当传递一个指向函数的指针时,你仍然是按值传递它。它包含的地址被复制到函数中。修改函数内部的指针不会改变函数外部的指针——但是,修改它指向的对象会改变函数外部的对象。但为什么?

由于具有相同值的两个指针总是指向同一个对象(它们包含相同的地址),因此可以通过两者访问和修改所指向的对象。这给出了通过引用传递指向对象的语义,尽管实际上不存在任何引用 - C 中根本没有引用。看看更改后的示例:

void foo(int* param) { (*param)++; }

int main()
{
  int x = 5;
  foo(&x);
  printf("%d\n",x); // x == 6
}

我们可以说,当将 the 传递int*给函数时,int它指向的是“通过引用传递”,但实际上它int根本没有真正传递到任何地方——只有指针被复制到函数中。这给了我们“按值传递”和“按引用传递”的口语1含义。

该术语的使用由标准中的术语支持。当你有一个指针类型时,它所指向的类型被称为它的引用类型。也就是说,被引用的类型int*int

指针类型可以派生自函数类型、对象类型或不完整类型,称为引用类型

虽然一元运算*符(如 in *p)在标准中称为间接,但通常也称为解引用指针。这进一步促进了 C 中“通过引用传递”的概念。

C++

C++ 采用了 C 的许多原始语言特性。其中包括指针,因此这种“通过引用传递”的口语形式仍然可以使用 -*p仍然是解引用p。但是,使用该术语会令人困惑,因为 C++ 引入了 C 没有的特性:真正传递引用的能力。

后跟 & 符号的类型是引用类型2。例如,int&是对int. 将参数传递给采用引用类型的函数时,对象实际上是通过引用传递的。不涉及指针,不复制对象,什么都没有。函数内部的名称实际上指的是与传入的对象完全相同的对象。与上面的示例相比:

void foo(int& param) { param++; }

int main()
{
  int x = 5;
  foo(x);
  std::cout << x << std::endl; // x == 6
}

现在该foo函数有一个参数是对int. 现在当传递x,param指的是完全相同的对象。递增param对 的值有明显的变化,x现在x的值为 6。

在此示例中,没有按值传递任何内容。没有任何东西被复制。与在 C 中按引用传递实际上只是按值传递指针不同,在 C++ 中我们可以真正按引用传递。

由于术语“按引用传递”中的这种潜在歧义,当您使用引用类型时,最好仅在 C++ 上下文中使用它。如果您传递的是指针,则不是通过引用传递,而是通过值传递指针(当然,除非您传递对指针的引用!例如int*&)。但是,当使用指针时,您可能会遇到“按引用传递”的用法,但现在至少您知道实际发生了什么。


其他语言

其他编程语言使事情进一步复杂化。在某些(例如 Java)中,您拥有的每个变量都称为对对象的引用(与 C++ 中的引用不同,更像是指针),但这些引用是按值传递的。因此,即使您似乎是通过引用传递给函数,但实际上您正在做的是将引用按值复制到函数中。当您将新对象分配给传入的引用时,会注意到 C++ 中通过引用传递的这种细微差别:

public void foo(Bar param) {
  param.something();
  param = new Bar();
}

如果您要在 Java 中调用此函数,并传入一些 type 的对象Bar,则调用param.something()将在您传入的同一对象上调用。这是因为您传入了对对象的引用。但是,即使将 newBar分配给param,函数外的对象仍然是同一个旧对象。从外面看不到新的。那是因为内部的引用foo被重新分配给一个新对象。对于 C++ 引用,这种重新分配引用是不可能的。


1通过“口语化”,我并不是说“通过引用传递”的 C 含义比 C++ 含义更不真实,只是 C++ 确实具有引用类型,因此您是真正通过引用传递。C 的含义是对真正按值传递的内容的抽象。

2当然,这些是左值引用,我们现在在 C++11 中也有右值引用。

于 2012-11-30T22:30:43.107 回答