C 语言和 C++ 中的"pass by reference"究竟有什么区别?
What exactly is the difference between "pass by reference" in C and in C++?
C和C++开发人员都使用短语"pass-by-reference",但它们的意思似乎不同。每种语言中这个模棱两可的短语到底有什么区别?
有些问题已经处理了通过引用传递和通过值传递之间的区别。从本质上讲,将参数按值传递给函数意味着该函数将有自己的参数副本-其值将被复制。修改该副本不会修改原始对象。但是,当通过引用传递时,函数内部的参数引用了传递的同一对象-函数内部的任何更改都将在外部看到。
不幸的是,短语"按值传递"answers"按引用传递"有两种使用方式,这可能会引起混淆。我相信这就是为什么新的C++程序员很难采用指针和引用的部分原因,尤其是当它们来自C.背景时
C
在C语言中,一切都是通过技术意义上的值传递的。也就是说,无论你给一个函数什么参数,它都会被复制到那个函数中。例如,用foo(x)
调用函数void foo(int)
会复制x
的值作为foo
的参数。这可以在一个简单的例子中看到:
void foo(int param) { param++; }
int main()
{
int x = 5;
foo(x);
printf("%dn",x); // x == 5
}
x
的值被复制到foo
中,并且该副本被递增。main
中的x
继续具有其原始值。
我相信您已经知道,对象可以是指针类型的。例如,int* p
将p
定义为指向int
的指针。需要注意的是,以下代码引入了两个对象:
int x = 5;
int* p = &x;
第一个是类型int
,并且具有值5
。第二个是类型int*
,其值是第一个对象的地址。
当向函数传递指针时,仍然是按值传递指针。它包含的地址被复制到函数中。在函数内修改指针不会更改函数外的指针,但是,修改指向的对象将更改函数外对象。但为什么呢?
由于具有相同值的两个指针总是指向同一对象(它们包含相同的地址),因此可以通过这两个指针来访问和修改所指向的对象。这给出了通过引用传递指向对象的语义,尽管实际上从未存在过引用——C中根本没有引用
void foo(int* param) { (*param)++; }
int main()
{
int x = 5;
foo(&x);
printf("%dn",x); // x == 6
}
我们可以说,当将int*
传递到函数中时,它所指向的int
是"通过引用传递的",但事实上,int
实际上根本没有传递到任何地方——只有指针被复制到函数中。这给了我们"按值传递"answers"按引用传递"的口语含义。
此术语的使用由标准中的术语支持。当您有一个指针类型时,它所指向的类型称为其引用类型。也就是说,int*
的被引用类型是int
。
指针类型可以从函数类型、对象类型或不完整类型,称为引用类型。
虽然一元*
运算符(如*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中调用此函数,并传入一些类型为Bar
的对象,则对param.something()
的调用将在传入的同一对象上调用。这是因为您传入了对对象的引用。然而,即使新的Bar
被分配给param
,函数之外的对象仍然是同一个旧对象。从外面永远看不到新的。这是因为foo
内部的引用正在被重新分配给一个新对象。对于C++引用,这种重新分配引用是不可能的。
1通过"口语化",我并不是说"通过引用传递"的C含义比C++含义更不真实,只是C++确实有引用类型,所以你真的通过了引用。C的含义是对真正传递价值的东西的抽象。
2当然,这些都是左值引用,我们现在在C++11中也有右值引用。
- 传递给std::function template的template参数究竟代表什么
- C++关于指针和使用函数将它们启动到堆的行为究竟是什么?
- 当使用对象名称后带有 [] 的类对象时,您究竟会传入什么.C++
- 哪种方式更快?究竟发生了什么,我们没有看到什么?
- 当我使用 void 函数的返回值(通过强制转换函数指针)时,究竟会发生什么?
- DrawShadow 中的 Elevation 究竟有什么作用?
- 究竟是什么设置了_WIN32或_WIN64预处理器?
- GCC 的 -Wpsabi 选项究竟有什么作用?压制它意味着什么?
- 这个编译器错误究竟希望我执行什么?
- 在陈述"Implementation-defined"时,什么样的软件是"Implementation"的一部分?究竟什么是"Implementation"?
- 究竟什么是"位填充"或"填充位"?
- 究竟发生了什么,我们需要在 c++ 中双重调度/访客
- 根据标准,究竟什么是"declaration of an object"
- 为了对象替换的目的,究竟什么构成了对象的"name"?
- 被调用的函数究竟什么时候结束
- 究竟什么是"trailing parameter pack"
- 究竟什么是取消点
- c++中究竟什么时候调用析构函数?
- 视觉 究竟什么是 CLI/C++?它与"普通"c++有何不同?
- 就解析而言,究竟什么是令牌?