即使是像C这样的具有显式指针操作的语言,也总是按值传递(您可以按引用传递它们,但这不是默认行为)。
这有什么好处,为什么这么多的语言通过值传递,为什么其他的语言通过引用传递?(我不确定Haskell是通过引用传递的,尽管我不确定)。
temp := a; a := b; b := temp;
当它返回的值a
,并b
会被交换。用C语言无法做到这一点。你必须通过指针a
和b
和swap
例程有作用于它们指向的值。
即使是像C这样的具有显式指针操作的语言,也总是按值传递(您可以按引用传递它们,但这不是默认行为)。
这有什么好处,为什么这么多的语言通过值传递,为什么其他的语言通过引用传递?(我不确定Haskell是通过引用传递的,尽管我不确定)。
temp := a; a := b; b := temp;
当它返回的值a
,并b
会被交换。用C语言无法做到这一点。你必须通过指针a
和b
和swap
例程有作用于它们指向的值。
Answers:
值传递通常比引用传递更安全,因为您不会意外地修改方法/函数的参数。这使语言更易于使用,因为您不必担心为函数提供的变量。您知道它们不会被更改,而这通常是您期望的。
但是,如果要修改参数,则需要进行一些显式操作以使其清楚(传递指针)。这将迫使您的所有调用者进行稍有不同的调用(&variable
在C中为),这明确表明可以更改变量参数。
因此,现在您可以假定一个函数不会更改您的变量参数,除非它被明确标记为这样做(通过要求您传递一个指针)。这是比替代方案更安全,更清洁的解决方案:假定所有内容都可以更改您的参数,除非他们明确声明不能更改。
按值调用和按引用调用是很久以前就误认为参数传递模式的实现技术。
最初,有FORTRAN。FORTRAN仅具有按引用调用的功能,因为子例程必须能够修改其参数,并且计算周期过于昂贵,以至于无法使用多个参数传递模式,而且首次定义FORTRAN时对编程的了解还不够。
ALGOL提出了按名称致电和按值致电。按值调用用于不应该更改的事物(输入参数)。名称呼叫用于输出参数。事实证明按姓名呼叫是一个主要的障碍,ALGOL 68放弃了它。
PASCAL提供了按值调用和按引用调用。它没有提供任何方式让程序员告诉编译器他正在通过引用传递一个大对象(通常是数组),以避免破坏参数堆栈,但是不应更改该对象。
PASCAL添加了指向语言设计词典的指针。
C通过定义kludge运算符以返回指向内存中任意对象的指针,提供了按值调用和模拟的按引用调用。
后来的语言复制了C语言,主要是因为设计人员从未见过其他东西。这可能就是按值致电如此受欢迎的原因。
C ++在C代码的顶部添加了一个代码,以提供按引用调用。
现在,作为按值调用,按引用调用与按指针调用的混合的直接结果,C和C ++(程序员)对const指针和指向const的指针(只读)感到头痛不已对象。
艾达设法避免了整个噩梦。
Ada没有显式的按值调用与按引用调用。相反,Ada具有in参数(可以读取但不能写入),out参数(必须在可以读取之前写入)和in out参数,可以按任意顺序读取和写入。编译器决定是通过值还是通过引用传递特定参数:它对程序员是透明的。
0
前缀与其他所有非十进制的前缀不一致。如果八进制是引入时八进制是唯一的替代基础,那么在C语言(和大多数与源兼容的语言,例如C ++,Objective C)中,这可能是可以原谅的,但是当更现代的语言同时使用0x
并且0
从一开始就使用它们时,这只会使它们看起来很差深思熟虑。
通过引用传递会产生非常细微的意外副作用,当它们开始引起意外行为时,很难将其几乎追踪到。
按值传递,尤其是final
,static
或者const
输入参数使得这整个类的错误消失。
不可变的语言更具确定性,并且更容易推理和理解函数的内容和预期结果。
Swap
,不使用临时变量的hack)虽然本身不太可能成为问题,但它们显示了调试更复杂的示例的难度。
为什么有那么多语言按价值传递?
将大型程序分解为较小的子例程的目的是,您可以独立地推理子例程。引用传递会破坏此属性。(共享的可变状态也是如此。)
即使是像C这样的具有显式指针操作的语言,也总是按值传递(您可以按引用传递它们,但这不是默认行为)。
实际上,C 始终是按值传递,而不是按引用传递。您可以接受某物的地址并传递该地址,但是该地址仍将按值传递。
这有什么好处,为什么这么多的语言通过值传递,为什么其他的语言通过引用传递?
使用通过引用的主要原因有两个:
我个人认为#1是虚假的:它几乎始终是不良API和/或语言设计的借口:
您也可以通过将多个返回值打包到某些轻量级数据结构(例如元组)中来模拟它们。如果该语言支持模式匹配或解构绑定,则效果特别好。例如Ruby:
def foo
# This is actually just a single return value, an array: [1, 2, 3]
return 1, 2, 3
end
# Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
one, two, three = foo
通常,您甚至不需要多个返回值。例如,一种流行的模式是子例程返回错误代码,并且实际结果通过引用写回。相反,如果错误是意外错误,则仅应引发异常;如果预期错误,则应返回Either<Exception, T>
。另一种模式是返回一个布尔值,该布尔值指示操作是否成功,并通过引用返回实际结果。同样,如果失败是意外的,则应该引发异常,如果期望失败,例如,在字典中查找值时,则应返回a Maybe<T>
。
引用传递可能比值传递更有效,因为您不必复制值。
(我不确定Haskell是通过引用传递的,尽管我不确定)。
不,Haskell不是传递引用。它也不是按值传递的。引用传递和值传递都是严格的评估策略,但是Haskell是非严格的。
实际上,Haskell规范并未指定任何特定的评估策略。大多数Hakell实现都使用按名称调用和按需调用(带备注的按名称调用的变体)的混合体,但是该标准并未强制要求这样做。
请注意,即使对于严格的语言,也无法区分功能语言的按引用传递或按值传递,因为仅当您对引用进行了更改时,才能观察到它们之间的差异。因此,实现者可以在两者之间自由选择,而不会破坏语言的语义。
根据语言的调用模型,参数的类型和语言的存储模型,会有不同的行为。
对于简单的本机类型,按值传递允许您通过寄存器传递值。这可能非常快,因为不需要从内存中加载值,也无需保存回去。一旦被调用方完成了参数的重用,也可以简单地通过重用自变量使用的内存来进行类似的优化,而不必担心会弄乱对象的调用方副本。如果参数是一个临时对象,那么您可能会保存一个完整副本(C ++ 11使用新的右引用及其移动语义使这种优化更加明显)。
在许多OO语言中(在这种情况下C ++例外),您不能按值传递对象。您被迫通过引用传递它。这使代码默认情况下是多态的,并且更适合面向对象的实例的概念。另外,如果您要按价值传递,则必须自己制作副本,并确认产生此类操作的性能成本。在这种情况下,该语言会为您选择最有可能为您提供最佳性能的方法。
对于功能语言,我想按值或引用传递只是一个优化问题。由于这种语言的函数是纯函数,因此没有副作用,因此除了速度之外,实际上没有任何理由复制该值。我什至可以肯定,这种语言经常共享具有相同值的对象的相同副本,只有当您使用传递(常量)引用语义时,这种可能性才可用。Python还将此技巧用于整数和通用字符串(如方法和类名),解释了为什么整数和字符串是Python中的常量对象。通过允许使用指针比较而不是内容比较,并且对某些内部数据进行延迟评估,这也有助于再次优化代码。
您可以按值传递表达式,这很自然。通过(临时)引用传递表达式是...很奇怪。
void acceptEntireProgrammingLanguageByValue(C++);