C 和 C++ 中的“通过引用传递”到底有啥区别?

Posted

技术标签:

【中文标题】C 和 C++ 中的“通过引用传递”到底有啥区别?【英文标题】:What exactly is the difference between "pass by reference" in C and in C++?C 和 C++ 中的“通过引用传递”到底有什么区别? 【发布时间】:2012-11-19 04:48:32 【问题描述】:

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

【问题讨论】:

尽管有一些类似的问题和答案,但我很惊讶没有找到真正涵盖这个问题的答案(特别是对 C 和 C++ 之间差异的深入讨论)。这是一个常见的误解,尤其是对于许多人来说,由于某种原因同时学习 C 和 C++ 或从一个转移到另一个。我认为新手 C 和 C++ 程序员的许多问题源于对每种语言到底发生了什么的混淆。 你有没有读过Pass by Reference / Value in C++最受好评的答案? @JesseGood 我做到了,但我试图针对两种语言之间的差异以及何时应该在两种上下文中使用这些短语。尽管答案包含一些重复的内容,但我觉得我的问题相当不同。 @stfrabbit:请注意链接的答案如何区分绑定的临时对象,这与您的答案略有不同。 【参考方案1】:

有些问题已经涉及the difference between passing by reference and passing by value。本质上,将参数按值传递给函数意味着该函数将拥有自己的参数副本 - 它的 value 被复制。修改该副本不会修改原始对象。但是,当通过引用传递时,函数内的参数引用传入的同一对象 - 函数内部的任何更改都会在外部看到。

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

C

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

void foo(int param)  param++; 

int main()

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

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

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

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

第一个是int 类型,其值为5。第二个是int* 类型,其值为第一个对象的地址。

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

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

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

int main()

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

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

该术语的使用由标准中的术语支持。当你有一个指针类型时,它所指向的类型被称为它的引用类型。即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*&amp;)。但是,在使用指针时,您可能会遇到“按引用传递”的用法,但现在至少您知道实际发生了什么。


其他语言

其他编程语言使事情变得更加复杂。在某些(例如 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++ 确实有引用类型所以你真的是通过reference。 C 的含义是对真正按值传递的内容的抽象。

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

【讨论】:

"C 中根本没有真正的通过引用传递" -- 这是错误的。您对通过引用传递的定义偏向于 C++。术语“通过引用传递”早于 C++。它以不同的方式用不同的语言完成。在 C 中,传递指向对象的指针实际上就是通过引用传递该对象。实际上,它也在 C++ 中。恰巧C++有两种通过引用传递的方式。 应该说:在某些语言中,比如Java,你拥有的每个变量都是一个指向对象的指针(称为引用,但绝对不是C++中引用的意思)。这与在 C++ 中传递指向对象的指针完全相同。 “传入一些 Bar 类型的对象”您在这里感到困惑。对象不能被传递,也不是 Java 中的值。 所以基本上c:按值传递指针,c++:按引用传递 也就是说,传递引用不会复制可能是错误的,因为大多数编译器在内部将引用实现为自动取消引用的常量指针,因此当您传递对函数的引用时,它会复制一份。

以上是关于C 和 C++ 中的“通过引用传递”到底有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

通过引用传递 C++ 迭代器有啥问题?

Object C和C++有啥区别

c 和 c++ 中的指针之间有啥大的区别吗? [关闭]

c和c ++中的动态内存分配和堆有啥区别

C++和VisualC++有啥区别?

c++中scanf和cout有啥区别