Ruby是按值传递还是按引用传递?

4

我基本上是一名Java开发者。我在Ruby上工作了大约一年时间。与Java不同,Ruby是一种纯面向对象的编程语言。这里出现了一个疑问。它是按值传递还是按引用传递?Java按值传递工作:“当传递原始类型时,我看到值被复制并传递给方法。但是在对象的情况下,引用被复制并传递给方法。引用包含堆中对象的位置。在方法调用期间,只传递对象的位置。因此不会创建重复的对象。同一个对象被修改。”

但是当我尝试下面的Ruby代码片段时,我得到了与Java相同的结果:“数字在方法调用期间像原始类型(就像在Java中)一样工作,而数组则像在Java中完美地引用”。现在,我感到困惑。如果Ruby中的所有内容都是对象,那么为什么数字对象在方法调用期间会被复制?

class A
  def meth1(a)
    a = a+5
    puts "a inside meth1---#{a}"
  end

  def meth2(array)
    array.pop
    puts "array inside meth2---#{array}"
  end
end


obj1 = A.new
aa=5
obj1.meth1(aa)
puts "aa-----#{aa}"

arr = [3,4,5]
obj1.meth2(arr)
puts "arr---#{arr}"

结果:

meth1内部的a---10

aa-----5

meth2内部的数组---34

arr---34


在此处和这里重复,并且在这里也有重复。 - phuclv
4个回答

19

Ruby使用传值调用(pass-by-value),更确切地说,是传值调用的一种特殊情况:被传递的值 总是 一个指针。这种特殊情况有时也称为共享调用(call-by-sharing)、共享对象调用(call-by-object-sharing)或对象调用(call-by-object)。

它是Java(用于对象)、C#(默认情况下用于引用类型)、Smalltalk、Python、ECMAScript/JavaScript以及几乎所有已创建的面向对象语言所使用的相同约定。

注意:在所有现有的Ruby实现中,SymbolFixnumFloat实际上是通过直接传值而不是通过中间指针传递的。但是,由于这三个都是不可变的,因此在这种情况下,传值调用和共享对象调用之间没有可观察到的行为差异,因此您可以将一切都视为共享对象调用。只需将这三个特殊情况解释为您无需担心的内部编译器优化即可大大简化您的思维模型。

下面是一个简单的示例,您可以运行它来确定Ruby(或任何其他语言,在您翻译后)的参数传递约定:

def is_ruby_pass_by_value?(foo)
  foo.replace('More precisely, it is call-by-object-sharing!')
  foo = 'No, Ruby is pass-by-reference.'
  return nil
end

bar = 'Yes, of course, Ruby *is* pass-by-value!'

is_ruby_pass_by_value?(bar)

p bar
# 'More precisely, it is call-by-object-sharing!'

1
在所有现有的Ruby实现中,符号(Symbols)、Fixnums和Floats实际上是直接按值传递的,而不是通过中介指针传递。我非常喜欢它,我认为你应该包括它。但你已经做到了,看起来很好。 :) 但我认为你也应该把truenilfalse也加入到你的列表中。 - Arup Rakshit
为了进一步说明,可以在 foo = 'No....' 后面添加一行代码,写上 "foo.replace('some text')"。 - Mr Mikkél

6
请看下面,Object_id将回答您所有的问题:
class A
 def meth1(a)
  p a.object_id #=> 11
  a = a+5 # you passed a reference to the object `5`,but by `+` you created a new object `10`.
  p a.object_id #=> 21
 end

 def meth2(array)
  p array.object_id #=> 6919920
  array.pop
  p array.object_id #=> 6919920
 end
end


obj1 = A.new
aa=5
obj1.meth1(aa)
p aa.object_id #=> 11

arr = [3,4,5]
obj1.meth2(arr)
p arr.object_id #=> 6919920

在你的代码中,确实传递了对象引用按值传递。注意,+会创建一个新的对象,因此引用指向10在方法本地被更改。


6
在这两种情况下,它都是按值传递的,就像Java一样。不同之处在于,在你的测试中,这两个项目都是对象,而在Java中,其中一个将是基本类型,另一个将是对象。但是,无论某些东西是基本类型还是对象,都与按值传递与按引用传递无关。按值传递与按引用传递与被调用方法对其传入的变量所做的事情有关,在“调用”上下文中。

让我们忽略两种语言和对象,并只查看按值传递与按引用传递实际意义。我将使用伪代码,采用模糊的B / Java / C / C ++ / C#/D语法:

Function Foo(arg) {
  arg = 6
}

declare variable a
a = 5
Foo(a)
output a

如果按值传递参数a,输出结果为5。如果通过引用(将变量a的引用传递给Foo),则输出结果为6,因为Foo是通过对变量的引用来处理a的。

请注意,你的两个测试之间存在显著差异。

在第一个测试中,你正在为a分配一个全新的值:

a = a + 5

你并没有修改传入方法的a版本,而是使用该值来为a分配一个值。

在你的第二个测试中,你只是修改了array

array.pop

例如,不是:

array = ...put something entirely new in `array`...

在你的测试中,由于你只是修改了对象引用指向的东西,而没有改变引用本身,所以你当然可以看到这种修改。但如果你实际上将一个新数组分配给 array,那么这种改变在调用上下文中将不会显现出来。

实际上,它们都是引用。在Java或Ruby中,“对象”不是值。 - newacct
@newacct:不,测试中的两个项目都是对象(一个数字对象和一个数组对象)。是的,当它们被传递到函数中时,传递的是对该对象的引用(而不是对象本身),正如上面所清楚地表明的那样。测试是整个内容,而不仅仅是将对象引用传递到函数中的狭窄部分。 - T.J. Crowder
@T.J.Crowder:不是这样的。 "传递"没有什么特别之处。语言中任何表达式的值都是一个引用。当你将一个表达式赋值给一个变量时,你正在分配一个引用。 - newacct
@newacct:你分配给变量的并不是这个东西的“本质”。测试使用对象,我们用引用来引用它们。 - T.J. Crowder
1
@T.J.Crowder:你说“一个是原始类型,另一个是对象”,好像“原始类型”和“对象”是可比较的东西。但在Java中,原始类型是值,而对象不是值。你让人们误以为有一些东西既是原始类型又是对象,但实际上并没有这样的东西。在Java中,一个值要么是原始类型,要么是引用。一个“对象”只是一个引用可以指向的东西。 - newacct
@newacct:你还在谈论变量中的内容。看看我的最后一条评论。那不是我要谈论的。祝你好运。 - T.J. Crowder

4
Ruby和Java一样,都是传值调用的...但有个小细节:在传递对象时,传递的实际上是指针引用。因此,如果在方法内部对对象值进行修改,则会修改与调用上下文中相同的对象。
现在看看你的例子,需要知道FixNum是“immediate”对象,只有一个值——在这种情况下,引用不是指针而是对象本身(它仍然是一个对象,具有方法等,因此这不像Java中的原始类型)。
在你的示例中,实际上是将一个新对象重新分配给了“a”指针,这在任何情况下都不会反映在任何地方,就像:
my_name = "John"
my_name = "Robert"

实际上是将一个新指针分配给引用。由于在Ruby中无法更改FixNum的值,因此这不是可以工作的情况。

数组的情况可能是您预期的:您有一个对象,对对象状态进行修改,然后获取修改后的状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接