为什么像String#这样的Ruby方法会替换变量的变异副本?

时间:2016-02-23 19:24:39

标签: ruby string variable-assignment object-identity

首先,我只是学习Ruby并且来自JavaScript背景。我有一个问题,我无法找到答案。我有这个例子:

a = 'red'
b = a
b.replace('blue')
b = 'green'
print a
  

蓝色

我的问题是:为什么这是一件事?我知道设置b = a使它们成为相同的object_id,因此技术上两个名称用于相同的变量字符串。但我没有看到使用这种递归价值变化的理由。如果我设置b = a因为我想操纵 a 的值而不更改它。

此外,有时似乎某个方法会修改 a ,但有时它会导致" b"成为一个新的对象。这似乎含糊不清,毫无意义。

我什么时候会用这个?有什么意义?这是否意味着我无法将 a 的值传递给另一个变量,而没有任何更改传播回 a

3 个答案:

答案 0 :(得分:3)

这里的问题不称为递归,Ruby变量不是递归的(对于单词的任何正常含义 - 即他们不引用自己,并且你不需要递归例程才能工作跟他们)。计算机编程中的递归是指代码直接或间接地调用自身,例如包含对自身调用的函数。

在Ruby中,所有变量都指向对象。这一点毫无例外 - 虽然有一些内部技巧可以使事情变得更快,但即使编写a=5也会创建一个名为a和"点"的变量。它到Fixnum对象5 - 仔细的语言设计意味着你几乎没有注意到这种情况发生。最重要的是,数字无法更改(您无法将5更改为6,它们始终是不同的对象),因此您可以认为以某种方式a& #34;含有"一个5即使在技术上a指向5,也可以使用它。

使用字符串,可以更改对象。您的示例代码的逐步说明可能如下所示:

a = 'red'

创建一个新的String对象,其中包含内容" red",并在其中指向变量a

b = a

将变量b指向与a相同的对象。

b.replace('blue')

replace指向的对象上调用b方法(并且也由a指向)该方法将字符串的内容更改为"蓝色&#34 ;

b = 'green'; 

使用内容" green"和点变量String创建一个新的b对象。变量ab现在指向不同的对象。

print a 

a指向的String对象包含内容" blue"。根据语言规范,这一切都正常工作。

  

我什么时候会用这个?

一直以来。在Ruby中,您可以使用变量临时指向对象,以便在它们上调用方法。对象是您要使用的对象,变量是您用于引用它们的代码中的名称。它们是分开的这一事实可能会不时地绊倒你(特别是在带有字符串的Ruby中,许多其他语言没有这种行为)

  

这是否意味着我无法超越" a"进入另一个变量,没有任何变化,重复回到" a"?

如果要复制String,有几种方法可以执行此操作。 E.g。

b = a.clone

b = "#{a}"

但是,在实践中,您很少只想直接复制字符串。您将需要执行与代码目标相关的其他操作。通常在Ruby中,会有一个方法执行你需要的操作并返回一个 new String,所以你会做这样的事情

b = a.something

在其他情况下,您实际上希望对原始对象进行更改。这一切都取决于您的代码的目的是什么。对String对象的就地更改很有用,因此Ruby支持它们。

  

此外,似乎有时一种方法会进入" a"有时它会导致" b"成为一个新的object_id。

事实并非如此。没有方法会改变对象的身份。但是,大多数方法将返回一个新对象。有些方法会改变对象的内容 - 由于在其他地方使用的数据发生变化的可能性,你需要更多地了解Ruby中的那些方法 - 在其他OO语言中也是如此,JavaScript对象也不例外在这里,他们表现得完全相同。

答案 1 :(得分:1)

在处理散列中的递归时,它可能很有用。

dup

如果您希望复制,可以使用> a = 'red' => "red" > b = a.dup => "red" > b.replace('orange') => "orange" > a => "red" > b => "orange"

dup

但是> a = {hello: {world: 1}} => {:hello=>{:world=>1}} > b = a.dup => {:hello=>{:world=>1}} > b[:hello][:world] = 4 => 4 > a => {:hello=>{:world=>4}} > b => {:hello=>{:world=>4}} 没有像评论中指出的那样执行deep_copy,参见示例

$(".menu .menu-item-has-children .dropdown-arrow").click(function() {

  $(this).parent().toggleClass('have-submenu-opened');

  $('.menu').find('.have-submenu-opened').find('.sub-menu.display-on').removeClass('display-on');

        $(this).parent().find(".sub-menu").toggleClass("display-on");
    });

答案 2 :(得分:1)

TL; DR

在你原来的问题中,现在编辑了,你会混淆递归与变异和传播。在正确的情况下以及预期的行为时,所有这三个概念都是有用的工具。您可能会发现您发布的特定示例令人困惑,因为您不希望字符串在适当位置发生变异,或者更改会在指向该对象的所有指针之间传播。

通用方法的能力使得能够在像Ruby这样的动态语言中进行鸭子打字。主要的概念障碍是理解变量指向对象,只有使用核心和标准库的经验才能让您理解对象如何响应特定的消息。

Ruby中的字符串是完全成熟的对象,它们响应消息,而不仅仅是语言原语。在下面的部分中,我试图解释为什么这很少是一个问题,以及为什么该功能在像Ruby这样的动态语言中很有用。我还介绍了一种产生您最初期望的行为的相关方法。

它的所有关于对象分配

  

我的问题是为什么这是一件事。我明白设置" b = a"使它们成为相同的object_id,因此技术上有两个相同变量字符串的名称。

这在日常编程中很少出现问题。请考虑以下事项:

a = 'foo' # assign string to a
b = a     # b now points to the same object as a
b = 'bar' # assign a different string object to to b

[a, b]
#=> ["foo", "bar"]

这可以按照您期望的方式工作,因为该变量只是对象的占位符。只要您将对象分配给变量,Ruby就会做出您可能直观的期望。

对象接收消息

在您发布的示例中,您正在遇到此行为,因为您真正在做的是:

a = 'foo'       # assign a string to a
b = a           # assign the object held in a to b as well
b.replace 'bar' # send the :replace message to the string object

在这种情况下,String#replace正在向 a b 指向的同一对象发送消息。由于两个变量都包含相同的对象,因此无论您是以a.replace还是b.replace调用该方法,都会替换该字符串。

这可能不直观,但在实践中很少成为问题。在许多情况下,这种行为实际上是可取的,因此您可以传递对象而无需关心方法如何在内部标记对象。这对于概括方法或自我记录方法的签名很有用。例如:

def replace_house str
  str.sub! 'house', 'guard'
end

def replace_cat str
  str.sub! 'cat', 'dog'
end

critter = 'house cat'    
replace_house critter; replace_cat critter
#=> "guard dog"

在此示例中,每个方法都需要一个String对象。它并不关心字符串在别处被标记为 critter ;在内部,该方法使用标签 str 来引用同一个对象。

只要您知道方法何时改变接收器以及何时传回新对象,您就不会对结果感到惊讶。稍等一下。

什么字符串#替换真的

在您的具体示例中,我可以看到String#replace的文档可能会令人困惑。文档说:

  

替换(other_str)→str
  用other_str中的相应值替换str的内容和污点。

这个真正意味着b.replace实际上是在改变对象("替换内容"),而不是返回一个新对象来赋值给变量。例如:

# Assign the same String object to a pair of variables.
a = 'foo'; b = a;

a.object_id
#=> 70281327639900

b.object_id
#=> 70281327639900

b.replace 'bar'
#=> "bar"

b.object_id
#=> 70281327639900

a.object_id == b.object_id
#=> true

请注意,object_id永远不会更改。您使用的特定方法重复使用同一个对象;它只是改变了它的内容。将此与String#sub之类的方法进行对比,后者返回对象的副本,这意味着您将获得具有不同object_id的新对象。

该怎么做:分配新对象

如果您希望 a b 指向不同的对象,则可以使用非变异方法,例如String#sub

a = 'foo'; b = a;
b = b.sub 'oo', 'um'
#=> "fum"

[a.object_id, b.object_id]
#=> [70189329491000, 70189329442400]

[a, b]
#=> ["foo", "fum"]

在这个相当人为的示例中,b.sub返回 new String对象,然后将其分配给变量 b 。这导致为每个变量分配不同的对象,这是您最初期望的行为。