为什么我们可以修改复杂参数但不修改ruby函数中的标量?

时间:2017-01-24 17:09:47

标签: ruby pass-by-reference

问题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中的所有内容都是一个对象,而早期问题的答案都没有提到标量和复杂类型之间的区别。那么我的描述是错误的,如果是,那么更好的描述是什么?

3 个答案:

答案 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的索引方法分配语法糖的一部分。你真正做什么,是调用方法[]=,传递07作为参数。它完全等同于调用x.[]=(0, 7);事实上,如果你愿意,你可以这样写。 (试试吧!)也许,如果你使用insert method而不是[]=,或者其他名称更明显地尖叫的方法,那么你就不会那么困惑了#34;我正在改变数组&#34; ,例如clearreplace

数组仍然是传递给方法的完全相同的数组。该参考文献未被修改。只有阵列了。如果我们无法在其中插入内容,那么阵列将毫无用处,然后那些内容就会留在那里!

因此,两种情况之间的区别在于,在第一种情况下,您分配了一个新值,即您突变了引用,这不是工作,因为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的上下文中没有意义。