为什么程序员说 "pass by reference" 真的是 "passing references by value?" 为什么这很重要?

Why do programmers say that "pass by reference" is really "passing references by value?" Why is that important?

我知道 C 和 C++ 中按引用传递的整个概念,以及 Java 中仅按值传递的类似概念。 但是从某种角度来看,一切都是按价值传递的,不是吗? 在 C 中,我们将变量的指针传递给函数。所以我们只是将引用的值传递给函数。这就是我们说 Java 不支持按引用传递的原因,因为我们只是将引用变量的值传递给函数。所以我们按值传递引用。虽然在 C++ 中有一种通过引用传递的方法,因为我们可以传递参数,并且该函数将使用这种格式在相同的内存位置工作

void swap(int &x, int &y)

但是在 C 中通过指针的引用传递只是通过值传递指针。

void swap(int* x, int* y)

我知道这个问题可能看起来有点愚蠢,但我觉得我的整个概念中存在一个很大的漏洞。那么按引用调用的实际定义是什么,这只是另一个上下文中按值调用的伪名称吗?

两个要点:

  • C 中没有引用调用。
  • 按值传递和按引用传递是不同的。他们不一样。

按值传递:被调用函数在堆栈中创建一组新变量,并将参数的值复制到其中。

通过引用传递:不是将值传递给被调用的函数,而是references/pointers传递给原始变量。

Why do programmers say that “pass by reference” is really “passing references by value?”

在将references/pointers传递给原始变量时,实际上objects/addresses是按值传递的。因此,您可以说按引用传递是 按值传递引用 但这并不意味着 按引用传递 是 [=21 的伪名称=]按值传递。两者的区别在this answer中有很好的解释。我正在复制摘录:

If I tell you the URL, I'm passing by reference. You can use that URL to see the same web page I can see. If that page is changed, we both see the changes. If you delete the URL, all you're doing is destroying your reference to that page - you're not deleting the actual page itself.

If I print out the page and give you the printout, I'm passing by value. Your page is a disconnected copy of the original. You won't see any subsequent changes, and any changes that you make (e.g. scribbling on your printout) will not show up on the original page. If you destroy the printout, you have actually destroyed your copy of the object - but the original web page remains intact.

通过引用传递意味着被调用函数的参数将与调用者传递的参数相同(不是值,而是标识 - 变量本身)。按值传递意味着被调用函数的参数将是调用者传递的参数的副本。值将相同,但标识(变量)不同。因此,在一种情况下,被调用函数对参数所做的更改会更改传递的参数,而在另一种情况下,只会更改被调用函数中参数的值(这只是一个副本)。

C++ 中的简单示例

#include <iostream>

void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }

int main()
{
    int x = 0;
    by_val(x); std::cout << x << std::endl;  // prints 0
    by_ref(x); std::cout << x << std::endl;  // prints 2

    int y = 0;
    by_ref(y); std::cout << y << std::endl;  // prints 2
    by_val(y); std::cout << y << std::endl;  // prints 2
}

C++ 由标准的模数错误定义。 C++ 标准没有指定引用是如何实现的,也没有指定它们是如何传递给函数的。它也没有定义一般的调用约定,也没有定义指针布局、堆栈、堆或无数其他实现细节。

相反,它试图定义 C++ 代码的含义。

引用最终成为其他值的别名。实现它们的最常见方法是作为引擎盖下的指针:但是由于无法访问该指针值,也没有通过它访问的副作用,这使得编译器很容易完全消除引用的存在, 直接使用引用值即可。

如果编译器不能这样做,它通常会传递一个指针或等价物。

尽管经常被实现为一个指针,但它仍然是不同的,因为与它交互的语义是不同的:它们不能被重新安置,它们不能被占用它们的地址,它们不能被初始化(null 或等价物)。

当然,这些规则可以通过未定义的行为来打破。

如果不给出 "reference" 的定义,就不能说 "pass by reference" 真的是 "passing references by value"。

Java 中,该语句是正确的(起初大约),因为在函数内部您可以更改作为引用的形式参数的值,而无需更改调用中传递的参数值:

void f(Object param) {
  param = null;
}

void g() {
  Object o = new Object();
  System.out.println(o);
  f(o);
  System.out.printn(o);
}

每个人都知道这两个打印语句会给出完全相同的结果。实际上在Java中没有引用传递,只有引用可以传递;并且只有一种传递参数的方法:按值。

C++中,这真的很不一样。当你在函数内部更改引用参数的值时,调用中传递的参数被修改:

void f(int &param) {
  param = 0;
}

void g() {
  int i=12;
  cout << i << endl;
  f(i);
  cout << i << endl;
}

每个 C++ 程序员都知道第二次打印将显示 0(而第一次打印为 12)。

所以在 C++ 中,你不能说 "pass by reference" 是 "passing references by value"。在 C++ 中,引用没有价值,它们只是与内存块关联的名称(标准部分 8.3.2 参考:[注意:引用可以被认为是对象的名称。 - 尾注]). f() 里面只有一个变量有两个名字。当然,你可能会反对大多数 C++ 引用实现都是隐藏指针,所以这个说法可能是正确的;但你应该知道,有很多情况可以避免使用隐藏指针来实现引用(标准部分 8.3.2/7 引用。未指定引用是否需要存储 )... 所以在 C++ 中,该语句无效。 C++ 实际上有两种传递参数的方法:按值和按引用。