问题Is Ruby pass by reference or by value?吸引了很多有用的答案,也引起了很多分歧。到目前为止,我在任何答案中都没有看到的是解释以下内容的任何内容:
ruby -e "def f(x) x=7 end; a=3; f(a); print a"
打印3。
ruby -e "def f(x) x[0]=7 end; a=[3]; f(a); print a[0]"
打印7.
根据经验,我认为标量对象和更复杂的对象(如哈希和数组)之间存在某种区别,标量通过引用传递给值和复杂对象。这与C的语义类似。
我的理解是,ruby中的所有内容都是一个对象,而早期问题的答案都没有提到标量和复杂类型之间的区别。那么我的描述是错误的,如果是,那么更好的描述是什么?
答案 0 :(得分:5)
这里术语的问题是Ruby是“通过对象引用传递”,这是一种在其他语言中使用“指向对象的指针”的方式。 Ruby中指针和引用之间的界限是模糊的,因为没有实际的指针,加上对象本身通过引用计数保存在内存中,其中指针最终成为引用。所以它们是指向对象的指针,而不是传统意义上与同一个变量硬链接的引用。
根据定义,每个变量总是代表一个对象,即使它没有被定义:Phalcon DevTools (3.0.4)
Available commands:
info (alias of: i)
commands (alias of: list, enumerate)
controller (alias of: create-controller)
module (alias of: create-module)
model (alias of: create-model)
all-models (alias of: create-all-models)
project (alias of: create-project)
scaffold (alias of: create-scaffold)
migration (alias of: create-migration)
webtools (alias of: create-webtools)
本身也是一个对象以及数字,甚至是浮点数。这使得术语“标量”几乎无关紧要,Ruby中没有基本类型,就像你在其他语言中一样,布尔值,数字,字符串和类实例之间的区别很大。
一般规则是,您永远无法将更改反向传播到变量,但通过方法进行的更改会传播。要了解原因,Ruby就是如何看待您的代码的:
nil
这只是重新定义了def f(x)
# Change value of local variable x to 7
x = 7
end
所指向的对象,因为即使7是一个对象。
其他代码与Ruby的感知方式截然不同:
x
这会向def f(x)
# Send the []= method call to x with the argument 7
x.send(:[]=, 7)
end
发送一条消息(方法调用)以触发x
方法。该方法可以使用该值执行任何操作,但对于具有特定含义的数组,哈希和复数。它更新了对象[]=
引用的内部状态。
您可以在其他情况下看到这一点:
x
这扩展为def f(x)
x += 'y'
end
,它使用中间结果进行变量重新分配。原始x = x + y
值未修改。
x
在这种情况下,def f(x)
x << 'y'
end
会对x.send(:<<, 'y')
进行就地修改,因此会修改原文。
在编写和理解Ruby代码时,能够识别方法调用是一件很重要的事情。有时它们甚至都不那么明显。您认为x
的存在意味着“变量赋值”,但情况并非如此:
=
这看起来像是分配给def f(x)
x.y = 'z'
end
的{{1}}属性,但它不是,它只是调用y
方法,它相当于x
这是y=
1}}可以用许多方式解释。这可能会修改价值,或者它可能会做一些完全不同的事情。如果不更了解x.send(:y=, 'z')
,就无法知道。
答案 1 :(得分:3)
根据经验,我认为标量对象和更复杂的对象(如哈希和数组)之间存在某种区别,标量通过引用传递给值和复杂对象。
没有&#34;标量对象&#34;或者一个复杂的对象&#34;在Ruby中。一切都是对象。期。一切都是价值传递,永远,没有例外。永远不会有任何传递参考。
更确切地说,Ruby通常称为对象共享,共享调用或逐个调用。这是pass-by-value的特殊情况,其中传递的值始终是指向对象的指针。
闭包中的自由变量是通过引用捕获的,但这是一个不同的问题,与此无关。
这类似于C的语义。
不,实际上,它不会。在C中没有pass-by-reference,C总是按值传递,就像Ruby一样。
在C中,所有内容都按值传递。 int
按值传递。 char
按值传递。指针按值传递。 Ruby就像C,除了只有指针; 传递的每个值都是指向对象的指针。
def f(x) x = 7 end a = 3 f(a) a #=> 3 def f(x) x[0] = 7 end a = [3] f(a) a[0] #=> 7
这两种情况从根本上不同:在第一种情况下,你绑定一个 new 值到参数x
里面方法。此重新绑定仅在方法体内可见。方法参数本质上类似于局部变量。 (事实上,如果您反映方法体的局部变量,您将看到参数显示出来。)
在第二种情况下,您调用一个改变接收器的方法。没有任务正在进行中。是的,有一个等号,但这只是Ruby的索引方法分配语法糖的一部分。你真正做什么,是调用方法[]=
,传递0
和7
作为参数。它完全等同于调用x.[]=(0, 7)
;事实上,如果你愿意,你可以这样写。 (试试吧!)也许,如果你使用insert
method而不是[]=
,或者其他名称更明显地尖叫的方法,那么你就不会那么困惑了#34;我正在改变数组&#34; ,例如clear
或replace
?
数组仍然是传递给方法的完全相同的数组。该参考文献未被修改。只有阵列了。如果我们无法在其中插入内容,那么阵列将毫无用处,然后那些内容就会留在那里!
因此,两种情况之间的区别在于,在第一种情况下,您分配了一个新值,即您突变了引用,这不是工作,因为Ruby是按值传递的。在第二种情况下,你改变了值, 工作,因为Ruby不是纯函数式语言,只有纯不可变对象。 Ruby是不纯的,它确实有可变对象,如果你改变了一个对象,那么该对象会发生变异。
我的妈妈和我的理发师用不同的名字来称呼我,但如果我的理发师剪了头发,妈妈也会注意到这一点。
注意:有些对象没有改变它们的方法。这些对象是不可变的。 Integer
是不可变的对象,所以你永远不能用Integer
来演示上面的内容,但这纯粹是Integer
不具备的事实的结果变异的方法,它与它们是标量&#34;无关。你可以拥有复杂的复合对象,如果你想要的话,不要有任何变异方法:here is a question about implementing a linked list in Ruby,这两个答案包含链接列表的三个实现,所有这些都是不可变的。 (免责声明:有两个实现的答案来自我。)
答案 2 :(得分:0)
Ruby通过指针传递给对象。&#34;
那么现在有什么区别?
def f(x)
x = 7
end
为局部变量x
指定一个新值 - 由于您重新分配了局部变量,因此此更改是本地的。
def f(x)
x[0] = 7
end
通过x
为数组引用的第一个元素指定一个新值 - 这个更改是全局的,因为您修改了一个对象。
传递值和传递引用之间的区别不适用于Ruby,即来自另一种编程语言,并且在Ruby的上下文中没有意义。