ChatGPT解决这个技术问题 Extra ChatGPT

按引用传递与按值传递有什么区别?

和有什么区别

引用传递的参数 值传递的参数?

请给我一些例子好吗?

如果您不知道 addressvalue 是什么,请参阅 here

1
13 revs, 4 users 53%

首先,CS 理论中定义的“按值传递与按引用传递”区别现在已经过时,因为最初定义为“按引用传递”的技术已经失宠,现在很少使用。 1

较新的语言 2 倾向于使用不同(但相似)的技术对来实现相同的效果(见下文),这是造成混淆的主要原因。

混淆的第二个来源是,在“通过引用”中,“引用”的含义比一般术语“引用”更窄(因为该短语早于它)。

现在,真实的定义是:

当参数通过引用传递时,调用者和被调用者使用相同的变量作为参数。如果被调用者修改了参数变量,效果对调用者的变量是可见的。

当参数传值时,调用者和被调用者有两个具有相同值的自变量。如果被调用者修改了参数变量,则调用者看不到效果。

在这个定义中需要注意的是:

这里的“变量”是指调用者的(局部或全局)变量本身——即,如果我通过引用传递一个局部变量并分配给它,我将更改调用者的变量本身,而不是它所指向的任何东西,如果它是一个指针.这现在被认为是不好的做法(作为隐式依赖)。因此,几乎所有较新的语言都完全或几乎完全是按值传递的。引用传递现在主要以“输出/输入参数”的形式用于函数不能返回多个值的语言中。

这现在被认为是不好的做法(作为隐式依赖)。因此,几乎所有较新的语言都完全或几乎完全是按值传递的。引用传递现在主要以“输出/输入参数”的形式用于函数不能返回多个值的语言中。

“引用传递”中“引用”的含义。与一般的“参考”术语不同的是,这个“参考”是暂时的和隐含的。被调用者基本上得到的是一个“变量”,它在某种程度上与原始变量“相同”。实现这种效果的具体方式无关紧要(例如,语言还可能公开一些实现细节——地址、指针、取消引用——这都是无关紧要的;如果最终效果是这样,它就是通过引用传递)。

现在,在现代语言中,变量往往是“引用类型”(另一个概念晚于“引用传递”并受其启发),即实际对象数据单独存储在某个地方(通常在堆上),并且只有对它的“引用”才会保存在变量中并作为参数传递。3

传递这样的引用属于按值传递,因为变量的值在技术上是引用本身,而不是被引用的对象。但是,对程序的净影响可能与按值传递或按引用传递相同:

如果引用只是从调用者的变量中获取并作为参数传递,这与传递引用具有相同的效果:如果被引用的对象在被调用者中发生了变异,调用者将看到更改。然而,如果一个持有这个引用的变量被重新分配,它将停止指向那个对象,所以对这个变量的任何进一步的操作都会影响它现在指向的任何东西。

然而,如果一个持有这个引用的变量被重新分配,它将停止指向那个对象,所以对这个变量的任何进一步的操作都会影响它现在指向的任何东西。

为了具有与按值传递相同的效果,在某个时间点制作对象的副本。选项包括:调用者可以在调用之前制作一个私人副本,并给被调用者一个参考。在某些语言中,某些对象类型是“不可变的”:对它们的任何似乎改变值的操作实际上都会创建一个全新的对象,而不会影响原始对象。因此,将这种类型的对象作为参数传递总是具有按值传递的效果:如果需要更改,将自动为被调用者制作副本,并且调用者的对象永远不会受到影响。在函数式语言中,所有对象都是不可变的。

调用者可以在调用之前制作一个私人副本,然后给被调用者一个参考。

在某些语言中,某些对象类型是“不可变的”:对它们的任何似乎改变值的操作实际上都会创建一个全新的对象,而不会影响原始对象。因此,将这种类型的对象作为参数传递总是具有按值传递的效果:如果需要更改,将自动为被调用者制作副本,并且调用者的对象永远不会受到影响。在函数式语言中,所有对象都是不可变的。

在函数式语言中,所有对象都是不可变的。

如您所见,这对技术与定义中的技术几乎相同,只是有一定程度的间接性:只需将“变量”替换为“引用对象”即可。

它们没有商定的名称,这导致了扭曲的解释,例如“在值是引用的情况下按值调用”。 1975 年,Barbara Liskov 提出了术语“call-by-object-sharing”(或有时只是“共享呼叫”),尽管它从未完全流行起来。此外,这些短语都没有与原始对平行。难怪旧术语最终在没有更好的东西的情况下被重用,导致混乱。4

(对于新技术,我会使用术语“新”或“间接”传递值/传递引用。)

注意:很长一段时间,这个答案曾经说:

假设我想与你分享一个网页。如果我告诉你 URL,我是通过引用传递的。您可以使用该 URL 来查看我可以看到的同一网页。如果该页面发生更改,我们都会看到更改。如果您删除 URL,您所做的只是破坏您对该页面的引用 - 您并没有删除实际页面本身。如果我打印出页面并给您打印输出,我就是按价值传递。您的页面是原始的断开连接的副本。您不会看到任何后续更改,并且您所做的任何更改(例如在打印输出上涂鸦)都不会显示在原始页面上。如果您销毁打印输出,您实际上已经销毁了您的对象副本 - 但原始网页保持不变。

这几乎是正确的,除了“引用”的更狭义的含义——它既是临时的又是隐含的(它不是必须的,但是显式和/或持久是附加功能,而不是传递引用语义的一部分,如上所述)。一个更接近的类比是给你一份文件的副本,而不是邀请你处理原件。

1除非您使用 Fortran 或 Visual Basic 进行编程,否则这不是默认行为,并且在现代使用的大多数语言中,甚至不可能实现真正的按引用调用。

2相当多的老年人也支持它

3在几种现代语言中,所有类型都是引用类型。这种方法是由语言 CLU 在 1975 年开创的,此后被许多其他语言采用,包括 Python 和 Ruby。还有更多的语言使用混合方法,其中一些类型是“值类型”,而另一些是“引用类型”——其中包括 C#、Java 和 JavaScript。

4回收一个合适的旧术语本身并没有什么不好,但必须以某种方式明确每次使用的含义。不这样做正是导致混乱的原因。


您提供的“真实”定义并不是几乎所有入门编程课程中给出的定义。谷歌什么是通过引用传递的,你不会得到那个答案。您提供的真实定义是对引用一词的滥用,因为当您遵循该定义时,您使用的是别名而不是引用:您有两个变量实际上是同一个变量,即别名而不是引用。您的真实定义无缘无故地引起了巨大的混乱。只是说通过引用传递意味着传递地址。这是有道理的,并且会避免这种毫无意义的混乱。
@YungGun 1)请提供“几乎所有入门编程课程中给出的定义”的链接。另请注意,这旨在在当今的现实中明确,而不是在十年或三年前编写某些 CS 课程时的现实。 2)“地址”不能在定义中使用,因为它故意从可能的实现中抽象出来。例如,某些语言(Fortran)没有指针;它们在是否向用户公开原始地址方面也有所不同(VB 没有);它也不必是原始内存地址,任何允许链接到变量的东西都可以。
@YungGun“太长了,没看”。一目了然地显示了答案中概述的混淆。通过引用传递是一种与实现无关的抽象技术。究竟在幕后传递了什么并不重要,重要的是对程序的影响。
“对程序的影响可以与按值传递或按引用传递相同”:我不同意效果与旧的“按引用传递”相同,因为调用者变量不能从被调用者内部重新分配
'Barbara Liskov 建议使用“call-by-object-sharing”这个术语——如果这个名称指的是第一种或第二种技术,这将是一个很好的澄清。目前的文字没有说清楚
1
11 revs, 3 users 95%

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

Java 仅支持按值传递。始终复制参数,即使在复制对对象的引用时,被调用函数中的参数将指向同一个对象,并且对该对象的更改将在调用者中看到。由于这可能会令人困惑,因此 Jon Skeet 对此有什么看法。

C# 支持按值传递和按引用传递(在调用方和被调用函数处使用关键字 ref)。 Jon Skeet 在这里也有一个很好的解释。

C++ 支持按值传递和按引用传递(在被调用函数中使用的引用参数类型)。您将在下面找到对此的解释。

代码

由于我的语言是 C++,我将在这里使用它

// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
    p = NULL;
}

// passes an integer
void call_by_value(int p) { // :2
    p = 42;
}

// passes an integer by reference
void call_by_reference(int & p) { // :3
    p = 42;
}

// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
    *p = 10; // changes what p points to ("what p references" in java)
    // only changes the value of the parameter, but *not* of 
    // the argument passed by the caller. thus it's pass-by-value:
    p = NULL;
}

int main() {
    int value = 10;
    int * pointer = &value;

    call_by_value(pointer); // :1
    assert(pointer == &value); // pointer was copied

    call_by_value(value); // :2
    assert(value == 10); // value was copied

    call_by_reference(value); // :3
    assert(value == 42); // value was passed by reference

    call_by_value_special(pointer); // :4
    // pointer was copied but what pointer references was changed.
    assert(value == 10 && pointer == &value);
}

Java中的一个例子不会有什么坏处:

class Example {
    int value = 0;

    // similar to :4 case in the c++ example
    static void accept_reference(Example e) { // :1
        e.value++; // will change the referenced object
        e = null; // will only change the parameter
    }

    // similar to the :2 case in the c++ example
    static void accept_primitive(int v) { // :2
        v++; // will only change the parameter
    }        

    public static void main(String... args) {
        int value = 0;
        Example ref = new Example(); // reference

        // note what we pass is the reference, not the object. we can't 
        // pass objects. The reference is copied (pass-by-value).
        accept_reference(ref); // :1
        assert ref != null && ref.value == 1;

        // the primitive int variable is copied
        accept_primitive(value); // :2
        assert value == 0;
    }
}

维基百科

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value

http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference

这家伙几乎钉了它:

http://javadude.com/articles/passbyvalue.htm


您将指针传递给函数的地方。指针不是简单地允许您修改它指向的值,并且它会反映在指针指向的值上。如果使用指针修改形式参数..也应该更改参数?还是我错过了什么? ..那些不应该是参考传递..那么?
D
Daniel Pryden

这里的许多答案(尤其是最受好评的答案)实际上是不正确的,因为他们误解了“通过引用调用”的真正含义。这是我试图解决问题的尝试。

TL;博士

用最简单的话来说:

按值调用意味着您将值作为函数参数传递

通过引用调用意味着您将变量作为函数参数传递

用比喻的方式:

价值调用是我在一张纸上写下一些东西并交给你的地方。也许它是一个 URL,也许它是战争与和平的完整副本。不管它是什么,它都写在我给你的一张纸上,所以现在它实际上是你的一张纸。你现在可以自由地在那张纸上涂鸦,或者用那张纸在其他地方找到一些东西并摆弄它,无论如何。

参考电话是当我给你我的笔记本时,上面写着一些东西。你可以在我的笔记本上涂鸦(也许我希望你这样做,也许我不想),然后我会保留我的笔记本,无论你在哪里涂鸦。另外,如果你或我写的有关于如何在其他地方找到东西的信息,你或我可以去那里摆弄这些信息。

“按值调用”和“按引用调用”不是什么意思

请注意,这两个概念都与 引用类型 的概念完全独立且正交(在 Java 中,所有类型都是 Object 的子类型,而在 C# 中,所有class 类型),或类似于 C 中的 指针类型 的概念(在语义上等同于 Java 的“引用类型”,只是语法不同)。

引用类型的概念对应于 URL:它本身既是一条信息,又是对其他信息的引用(如果你愿意的话,是一个指针)。您可以在不同的地方拥有多个 URL 副本,并且它们不会更改它们都链接到的网站;如果网站更新了,那么每个 URL 副本仍然会导致更新的信息。相反,在任何地方更改 URL 都不会影响该 URL 的任何其他书面副本。

请注意,C++ 有一个“引用”的概念(例如 int&),它 像 Java 和 C# 的“引用类型”,但 is 像“引用调用” . Java 和C# 的“引用类型”,以及Python 中的所有 类型,就像C 和C++ 所说的“指针类型”(例如int*)。

好的,这是更长更正式的解释。

术语

首先,我想强调一些重要的术语,以帮助澄清我的答案,并确保我们在使用单词时都指的是相同的想法。 (在实践中,我认为对此类主题的绝大多数混淆源于以无法完全传达预期含义的方式使用词语。)

首先,这里有一个使用类似 C 语言的函数声明示例:

void foo(int param) {  // line 1
  param += 1;
}

这是调用此函数的示例:

void bar() {
  int arg = 1;  // line 2
  foo(arg);     // line 3
}

使用这个例子,我想定义一些重要的术语:

foo 是在第 1 行声明的函数(Java 坚持让所有函数都成为方法,但概念是一样的,但不失一般性;C 和 C++ 对声明和定义进行了区分,这里不再赘述)

param 是 foo 的形式参数,也在第 1 行声明

arg 是一个变量,具体来说是函数 bar 的局部变量,在第 2 行声明和初始化

arg 也是第 3 行特定调用 foo 的参数

这里有两组非常重要的概念需要区分。第一个是值与变量:

值是对语言中的表达式求值的结果。例如,在上面的 bar 函数中,在 int arg = 1; 行之后,表达式 arg 的值为 1。

变量是值的容器。变量可以是可变的(这是大多数类 C 语言的默认设置)、只读的(例如使用 Java 的 final 或 C# 的只读声明)或深度不可变的(例如使用 C++ 的 const)。

要区分的另一对重要概念是参数与参数:

参数(也称为形参)是调用者在调用函数时必须提供的变量。

参数是由函数调用者提供的值,以满足该函数的特定形式参数

按值调用

在按值调用中,函数的形式参数是为函数调用新创建的变量,并使用其参数的值进行初始化。

这与使用值初始化任何其他类型的变量的方式完全相同。例如:

int arg = 1;
int another_variable = arg;

这里 arganother_variable 是完全独立的变量——它们的值可以相互独立地改变。但是,在声明 another_variable 时,它被初始化为保持与 arg 相同的值——即 1

由于它们是自变量,因此对 another_variable 的更改不会影响 arg

int arg = 1;
int another_variable = arg;
another_variable = 2;

assert arg == 1; // true
assert another_variable == 2; // true

这与上面示例中 argparam 之间的关系完全相同,为了对称,我将在此重复:

void foo(int param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

就像我们这样编写代码一样:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here

也就是说,按值调用的定义特征是被调用者(在本例中为 foo)接收 values 作为参数,但有自己独立的 variables 来自调用者变量的那些值(在本例中为 bar)。

回到我上面的比喻,如果我是 bar 而你是 foo,当我给你打电话时,我会给你一张纸,上面写着 。你称那张纸为 param。该值是我在笔记本中写入的值(我的局部变量)的副本,位于我称为 arg 的变量中。

(顺便说一句:取决于硬件和操作系统,关于如何从另一个函数调用一个函数有各种调用约定。调用约定就像我们决定是否将值写在纸上然后交给你,或者如果你有一张我写的纸,或者如果我写在我们俩面前的墙上。这也是一个有趣的主题,但远远超出了这个已经很长的答案的范围。)

通过引用调用

在引用调用中,函数的形式参数只是调用者作为参数提供的相同变量的新名称。

回到我们上面的例子,它相当于:

// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here

由于 param 只是 arg 的另一个名称——也就是说,它们是同一个变量,对 param 的更改会反映在 arg 中。这是引用调用与值调用不同的根本方式。

很少有语言支持引用调用,但 C++ 可以这样做:

void foo(int& param) {
  param += 1;
}

void bar() {
  int arg = 1;
  foo(arg);
}

在这种情况下,param 不仅具有与 arg 相同的 ,它实际上 arg(只是名称不同),所以 { 4} 可以观察到 arg 已经增加。

请注意,这不是 Java、JavaScript、C、Objective-C、Python 或当今几乎任何其他流行语言的工作方式。这意味着这些语言不是按引用调用的,而是按值调用的。

附录:对象共享调用

如果你所拥有的是按值调用,但实际值是引用类型或指针类型,那么“值”本身就不是很有趣(例如,在 C 中它只是平台特定大小的整数)——什么是有趣的是该值指向的内容。

如果该引用类型(即指针)指向的内容是可变的,那么可能会产生一个有趣的效果:您可以修改指向的值,并且调用者可以观察到指向的值的变化,即使调用者无法观察到指针本身的变化。

再次借用 URL 的类比,如果我们都关心的是网站而不是 URL,那么我给你一个网站的 URL 副本这一事实并不是特别有趣。您在 URL 副本上乱涂乱画的事实不会影响我的 URL 副本,这不是我们关心的事情(事实上,在 Java 和 Python 等语言中,“URL”或引用类型值可以'根本不能修改,只有它指向的东西可以)。

Barbara Liskov 在发明 CLU 编程语言(具有这些语义)时,意识到现有术语“按值调用”和“按引用调用”对于描述这种新语言的语义并不是特别有用。于是她发明了一个新名词:call by object sharing

在讨论技术上按值调用的语言时,但使用的常见类型是引用或指针类型(即:几乎所有现代命令式、面向对象或多范式编程语言),我发现它不那么令人困惑只需避免谈论按值调用或按引用调用即可。坚持通过对象共享调用(或简单地通过对象调用),没有人会感到困惑。 :-)


更好地解释:这里有两组非常重要的概念需要区分。 The first is value versus variable. The other important pair of concepts to distinguish is parameter versus argument:
优秀的答案。我想我会补充一点,不需要通过引用创建新的存储。参数名称引用原始存储(内存)。谢谢
最佳答案 海事组织
m
mfaani

在理解这两个术语之前,您必须了解以下内容。每个对象都有 2 个可以区分它的东西。

它的价值。

它的地址。

所以如果你说employee.name = "John"

知道关于 name 有两件事。它的值是"John",它在内存中的位置是一些十六进制数,可能像这样:0x7fd5d258dd00

根据语言的体系结构或对象的 类型(类、结构等),您将转移 "John"0x7fd5d258dd00

传递 "John" 称为按值传递。传递 0x7fd5d258dd00 称为通过引用传递。任何指向此内存位置的人都可以访问 "John" 的值。

有关这方面的更多信息,我建议您阅读 dereferencing a pointerwhy choose struct (value type) over class (reference type)


那就是我一直在寻找的,实际上应该寻找概念而不仅仅是解释,竖起大拇指兄弟。
Java 总是按值传递。在java中传递对对象的引用被认为是按值传递。这与您的陈述“传递 0x7fd5d258dd00 被称为通过引用传递”相矛盾。
仅区分 valueaddress 是不够的。问题是新内存是否用于您传递的任何内容。您可以基于 pass-by-value (被调用者内地址的新存储)传递 address,以便在被调用者内更改此地址不会影响调用者的原始变量(旧存储) 仍然保留原始地址。
D
Deduplicator

这是一个例子:

#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
}

我认为有一个问题,因为最后一行应该打印 0 而不是 2。如果我遗漏了什么,请告诉我。
@TaimoorChangaiz;哪个“最后一行”?顺便说一句,如果你会使用 IRC,请来##programming on Freenode。在那里解释事情会容易得多。我的昵称是“pyon”。
@EduardoLeón by_val(y); std::cout << y << std::endl; // 打印 2
@TaimoorChangaiz:为什么不打印 2? y 已被前一行设置为 2。为什么会回到0?
@EduardoLeón 我的错。你是对的。感谢指正
T
Than Skourtan

最简单的方法是使用 Excel 文件。例如,假设您在单元格 A1 和 B1 中有两个数字 5 和 2,并且您想在第三个单元格中找到它们的总和,例如 A2。您可以通过两种方式做到这一点。

通过在此单元格中键入 = 5 + 2 将它们的值传递给单元格 A2。在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和保持不变。

或者通过键入 = A1 + B1 将单元格 A1 和 B1 的“引用”传递给单元格 A2。在这种情况下,如果单元格 A1 或 B1 的值发生变化,则 A2 中的总和也会发生变化。


这是所有其他答案中最简单和最好的例子。
C
Craig

通过 ref 传递时,您基本上是在传递一个指向变量的指针。按值传递,您正在传递变量的副本。在基本用法中,这通常意味着通过引用传递对变量的更改将被视为调用方法,而通过值传递它们不会。


M
MetaGuru

值传递发送存储在您指定的变量中的数据的副本,传递引用发送指向变量本身的直接链接。因此,如果您通过引用传递一个变量,然后在您传递它的块内更改该变量,则原始变量将被更改。如果您只是按值传递,则原始变量将无法被您传递到的块更改,但您将获得调用时它包含的任何内容的副本。


H
Hamed Naeemaei

看看这张照片:

在第一种情况下(通过引用传递),当在函数内部设置或更改变量时,外部变量也会发生变化。

但是在第二种情况下(按值传递),更改函数内部的变量对外部变量没有影响。

如需阅读文章,请参阅此link

https://blog.penjee.com/wp-content/uploads/2015/02/pass-by-reference-vs-pass-by-value-animation.gif


u
user326964

按值传递 - 函数复制变量并使用副本(因此它不会更改原始变量中的任何内容)

通过引用传递 - 该函数使用原始变量,如果您在另一个函数中更改变量,它也会更改原始变量。

示例(复制并使用/自己尝试并查看):

#include <iostream>

using namespace std;

void funct1(int a){ //pass-by-value
    a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
    a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}

int main()
{
    int a = 5;

    funct1(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
    funct2(a);
    cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7

    return 0;
}

保持简单,偷看。文字墙可能是一个坏习惯。


这对于了解参数值是否更改非常有帮助,谢谢!
T
Tina Endresen

它们之间的主要区别在于值类型变量存储值,因此在方法调用中指定值类型变量会将该变量值的副本传递给方法。引用类型变量存储对对象的引用,因此将引用类型变量指定为参数会向该方法传递引用该对象的实际引用的副本。即使引用本身是按值传递的,该方法仍然可以使用它接收到的引用来与原始对象进行交互,并可能对其进行修改。类似地,当通过 return 语句从方法返回信息时,该方法返回存储在值类型变量中的值的副本或存储在引用类型变量中的引用的副本。返回引用时,调用方法可以使用该引用与被引用对象进行交互。因此,实际上,对象总是通过引用传递。

在 C# 中,要通过引用传递变量以便被调用的方法可以修改变量的,C# 提供了关键字 ref 和 out。将 ref 关键字应用于参数声明允许您通过引用将变量传递给方法——被调用的方法将能够修改调用者中的原始变量。 ref 关键字用于已经在调用方法中初始化的变量。通常,当方法调用包含未初始化的变量作为参数时,编译器会生成错误。在参数前面加上关键字 out 会创建一个输出参数。这向编译器表明参数将通过引用传递给被调用的方法,并且被调用的方法将为调用者中的原始变量分配一个值。如果该方法没有为每个可能的执行路径中的输出参数分配值,则编译器会生成错误。这还可以防止编译器为作为参数传递给方法的未初始化变量生成错误消息。一个方法只能通过 return 语句向其调用者返回一个值,但可以通过指定多个输出(ref 和/或 out)参数来返回多个值。

在此处查看 c# 讨论和示例link text


D
Deduplicator

例子:

class Dog 
{ 
public:
    barkAt( const std::string& pOtherDog ); // const reference
    barkAt( std::string pOtherDog ); // value
};

const & 通常是最好的。您不会受到建造和破坏的惩罚。如果引用不是 const,则您的界面暗示它将更改传入的数据。


P
Pang

如果您不想在将原始变量传递给函数后更改其值,则应使用“按值传递”参数构造函数。

然后该函数将只有值,但没有传入变量的地址。如果没有变量的地址,函数内部的代码就无法改变从函数外部看到的变量值。

但是如果你想让函数能够改变从外部看到的变量的值,你需要使用按引用传递。因为值和地址(引用)都被传入并且在函数内部可用。


M
Monster

简而言之,按值传递就是它是什么,按引用传递就是它在哪里。

如果你的值是 VAR1 = "Happy Guy!",你只会看到 "Happy Guy!"。如果 VAR1 更改为“Happy Gal!”,您将不会知道。如果它是通过引用传递的,并且 VAR1 发生了变化,你会的。


a
abhinisha thakur

按值传递是指如何通过使用参数将值传递给函数。在按值传递中,我们复制存储在我们指定的变量中的数据,它比通过引用传递要慢,因为数据被复制。我们对复制的数据进行更改,原始数据不受影响。通过引用传递或通过地址传递,我们将直接链接发送到变量本身。或将指针传递给变量。它更快 bcse 消耗的时间更少


B
BugShotGG

这是一个示例,演示了按值传递 - 指针值 - 引用之间的区别:

void swap_by_value(int a, int b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}   
void swap_by_pointer(int *a, int *b){
    int temp;

    temp = *a;
    *a = *b;
    *b = temp;
}    
void swap_by_reference(int &a, int &b){
    int temp;

    temp = a;
    a = b;
    b = temp;
}

int main(void){
    int arg1 = 1, arg2 = 2;

    swap_by_value(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 1 2

    swap_by_pointer(&arg1, &arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1

    arg1 = 1;                               //reset values
    arg2 = 2;
    swap_by_reference(arg1, arg2);
    cout << arg1 << " " << arg2 << endl;    //prints 2 1
}

“通过引用传递”方法有一个重要的限制。如果一个参数被声明为通过引用传递(所以它前面有 & 符号)它对应的实际参数必须是一个变量。

引用“按值传递”形式参数的实际参数通常可以是表达式,因此它不仅可以使用变量,还可以使用文字甚至函数调用的结果。

该函数不能将值放在变量以外的东西中。它不能为文字分配新值或强制表达式更改其结果。

PS:您也可以在当前线程中查看 Dylan Beattie 的答案,该答案用简单的文字解释。


您声明“如果将参数声明为 [作为参考],则其对应的实际参数必须是变量”,但通常情况并非如此。如果一个引用被绑定到一个临时的(例如函数的返回值),它的生命周期会被延长以匹配这个引用。有关详细信息,请参阅 here
M
Misaal D'souza

1.按值传递/按值调用

   void printvalue(int x) 
   {
       x = x + 1 ;
       cout << x ;  // 6
   }

   int x = 5;
   printvalue(x);
   cout << x;    // 5

在按值调用中,当您将值传递给 printvalue(x) 即参数 5 时,它会被复制到 void printvalue(int x)。现在,我们有两个不同的值 5 和复制的值 5,这两个值存储在不同的内存位置。因此,如果您在 void printvalue(int x) 内进行任何更改,它不会反映回参数。

2. 引用传递/引用调用

   void printvalue(int &x) 
   {
      x = x + 1 ;
      cout << x ; // 6
   }

   int x = 5;
   printvalue(x);
   cout << x;   // 6

在引用调用中,只有一个区别。我们使用&,即地址运算符。通过执行
void printvalue(int &x),我们指的是 x 的地址,这告诉我们它都指的是同一个位置。因此,函数内部所做的任何更改都将反映在外部。

既然来了,你也应该知道...

3. 按指针传递/按地址调用

   void printvalue(int* x) 
   {
      *x = *x + 1 ;
      cout << *x ; // 6
   }

   int x = 5;
   printvalue(&x);
   cout << x;   // 6

在按地址传递中,指针 int* x 保存传递给它的地址 printvalue(&x)。因此,函数内部所做的任何更改都将反映在外部。


s
sifr_dot_in

问题是“vs”。并且没有人指出重要的一点。在传递值时,会占用额外的内存来存储传递的变量值。在传递引用时,不会为这些值占用额外的内存。 (在某些情况下内存有效)。