C 和 C++ 开发人员都使用“按引用传递”这个短语,但它们似乎用于表示不同的东西。每种语言中这个模棱两可的短语之间到底有什么区别?
1 回答
有些问题已经处理了按引用传递和按值传递之间的区别。本质上,将参数按值传递给函数意味着该函数将拥有自己的参数副本 - 它的值被复制。修改该副本不会修改原始对象。但是,当通过引用传递时,函数内部的参数指的是传入的同一个对象 - 函数内部的任何更改都会在外部看到。
不幸的是,有两种方式使用“按值传递”和“按引用传递”这两个短语,这可能会引起混淆。我相信这就是为什么新的 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
该副本中并且该副本递增。x
inmain
继续具有其原始价值。
我相信您知道,对象可以是指针类型。例如,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 中也有右值引用。