-1

我昨天遇到了这个问题,想知道按值传递、按引用传递和按值传递引用的关系是什么。他们的行为如何?哪种编程语言使用哪种?

另外:我也偶尔听到过其他术语,如 pass-by-pointer、pass-by-copy、pass-by-object ......它们是如何适应的?

4

2 回答 2

2

这是一个虚构的术语,指的是实际上是按值传递的东西,但人们总是会感到困惑,因为语言中唯一的值是引用(指向事物的指针)。许多现代语言都以这种方式工作,包括 Java(用于非原始语言)、JavaScript、Python、Ruby、Scheme、Smalltalk 等。

初学者总是在 StackOverflow 上问为什么它不是 pass-by-reference,因为他们不明白 pass-by-reference 到底是什么。因此,为了让他们满意,一些人编造了一个他们也不理解的新术语,但让人觉得他们学到了一些东西,听起来有点像 pass-by-value 和 pass-by-reference 。

不同语言社区之间的术语使用差异很大。例如,Java 社区几乎总是将此语义描述为按值传递,而 Python 和 Ruby 社区很少将其描述为按值传递,即使它们在语义上是相同的。

于 2013-06-05T05:58:11.850 回答
1

让我们从基础开始。你可能已经知道这一点,或者至少认为你知道。令人惊讶的是,许多人实际上都在滥用这些术语(这当然无助于混淆)。

什么是按值传递和按引用传递?

我现在不打算告诉你答案,而是用例子来解释它。稍后你会明白为什么。

想象一下,您正在尝试学习一种编程语言,而一位朋友告诉您他知道一本关于该主题的好书。你有两个选择:

  1. 向他借书或
  2. 买你自己的副本。

案例 1:如果您借他的书并在页边空白处做笔记(并添加一些书签、参考资料、图纸……),您的朋友以后也可以从这些笔记和书签中受益。分享是关怀。世界真好!

案例 2:但是,另一方面,如果您的朋友有点爱整洁,或者非常珍惜他的书,他可能不会欣赏您的笔记,更不用说偶尔的咖啡渍或您如何折叠角落以添加书签。你真的应该得到你自己的书,每个人都可以愉快地以他喜欢的方式对待他的书。世界真好!

这两种情况——你猜对了——就像传递引用和传递值一样。 区别在于函数的调用者是否可以看到被调用者所做的更改。

让我们看一个代码示例。假设您有以下代码片段:

function largePrint(text):
  text = makeUpperCase(text)
  print(text)

myText = "Hello"
largePrint(myText)
print(myText)

并且您以按值传递的语言运行它,输出将是

HELLO
Hello

而在传递参考语言中,输出将是

HELLO
HELLO

如果您通过引用传递,则变量“myText”会被函数更改。如果您只是传递变量的值,则函数无法更改它。

如果你需要另一个例子,这个使用网站的例子得到了很多支持。

好的,现在我们已经掌握了基础知识,让我们继续...

什么是按值传递引用?

让我们再次使用我们的书籍示例。假设您决定向您的朋友借这本书,尽管您知道他是多么喜欢他的书。让我们也假设你当时——作为一个完全的笨蛋——把一加仑咖啡洒在上面。哦,我的……别担心,你的大脑会启动并意识到你可以给你的朋友买一本新书。状态如此之好,他甚至都不会注意到!

这个故事与按值传递引用有什么关系?

好吧,另一方面,想象一下你的朋友非常喜欢他的书,他不想借给你。然而,他确实同意把这本书带来给你看。问题是,你仍然可以将一加仑咖啡洒在上面,但现在你不能再给他买一本新书了。他会注意到的!

虽然这听起来很可怕(并且可能让你像喝了那加仑咖啡一样出汗),但它实际上很常见并且是共享书籍调用功能的完美方式。它有时被称为按值传递引用。

代码示例的表现如何?

在按值传递引用的函数中,我们的代码片段的输出是

HELLO
Hello

就像在按值传递的情况下一样。但是如果我们稍微修改一下代码:

function largePrint(text):
  text.toUpperCase()
  print(text)

myText = "Hello"
largePrint(myText)
print(myText)

输出变为:

HELLO
HELLO

就像在传递引用的情况下一样。原因与书籍示例中的相同:我们可以通过调用 text.toUpperCase() (或将咖啡洒在上面)来更改变量(或书籍)。但是我们不能再改变变量引用的对象(text = makeUppercase(text) 没有效果);换句话说,我们不能使用不同的书。

总结一下,我们得到:

传值:

你得到了你自己的书,你自己的变量副本,以及你用它做的任何事情,原始所有者永远不会知道。

参考传递:

您使用与您的朋友相同的书或与呼叫者相同的变量。如果您更改变量,它会在您的函数之外更改。

按值传递引用

你和你的朋友使用同一本书,但他不会让它离开他的视线。你可以换书,但不能换书。在变量方面,这意味着您无法更改变量引用的内容,但您可以更改变量引用的内容。

因此,现在当我向您展示以下 Python 代码时:

def appendSeven(list):
    list.append(7)

def replaceWithNewList(list):
    list = ["a", "b", "c"]

firstList = [1, 2, 3, 4, 5, 6]
print(firstList)
appendSeven(firstList)
print(firstList)

secondList = ["x", "y", "z"]
print(secondList)
replaceWithNewList(secondList)
print(secondList)

并告诉你输出是这样的

[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
['x', 'y', 'z']
['x', 'y', 'z']

你说什么?

确实,你是对的!Python 使用按值传递引用!(Java、Ruby 和 Scheme 也是如此……)

结论:

这非常困难吗?不,那为什么这么多人对此感到困惑?(我不会链接到关于“如何在 Python 中通过引用传递变量”或“Java 是否通过引用传递?”等所有其他问题......)

好吧,我看到几个原因:

  1. 说按引用传递值真的很长(无论如何它的行为与按引用传递几乎相同,所以退后并停止分裂头发!)
  2. 有一些令人讨厌的特殊情况使问题复杂化
    • 像原始类型(在按引用传递值的语言中通常是按值传递)
    • 不可变类型(无法更改,因此通过引用传递它们不会让你到任何地方)
  3. 有些人只是喜欢使用不同的名称:
    • pass-by-copy for pass-by-value因为如果你注意了,你需要自己的书,所以必须制作一个副本。(但这是一个实现细节,其他技巧如写时复制可能仅用于有时制作副本。)
    • pass-by-reference用于pass-reference-by-value因为毕竟你确实得到了一个引用,这意味着你可以改变调用者拥有的对象(即使你不能交换它)
    • pass-by-reference用于pass-reference-by-value因为几乎没有语言实际上具有 pass-by-reference (值得注意的例外(我知道):C++,C#,但你必须使用特殊的语法)
    • pass-by-sharing用于pass-reference-by-value(还记得我们书中的例子吗?这个名字实际上更好,每个人都应该使用它!)
    • pass-by-object用于pass-reference-by-value因为据称 Barbara Liskov 首先命名它并使用它,所以这是它的出生名)
    • pass-by-pointer用于传递引用,因为 C 没有传递引用,这就是它允许您完成相同事情的方式。

如果您认为我应该添加任何内容,请告诉我。如果我犯了错误,请告诉我!

对于那些还不够的人:http ://en.wikipedia.org/wiki/Evaluation_strategy

于 2013-06-04T23:20:06.700 回答