C 语言和 C++ 中的"pass by reference"究竟有什么区别?

What exactly is the difference between "pass by reference" in C and in C++?

本文关键字:究竟 什么 区别 reference pass 语言 C++ 中的 by      更新时间:2023-10-16

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* pp定义为指向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指的是完全相同的对象。递增paramx的值有明显的变化,现在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中也有右值引用。