3

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

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

蓝色的

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

此外,有时一个方法似乎会修改a,但有时它会导致 "b" 成为一个新对象。这似乎模棱两可,毫无意义。

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

4

3 回答 3

4

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

在 Ruby 中,所有变量都指向对象。这一点也不例外——尽管有一些内部技巧可以让事情变得更快,但即使是编写也会a=5创建一个名为的变量a并将其“指向”Fixnum对象5——仔细的语言设计意味着你几乎不会注意到这种情况的发生。最重要的是,数字不能改变(您不能将 a 更改5为 a 6,它们始终是不同的对象),因此您可以认为它以某种方式a“包含” a5并逃脱它,即使技术上a指向5

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

a = 'red'

创建一个String内容为“red”的新对象,并将变量指向a它。

b = a

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

b.replace('blue')

调用replace指向b(也指向a)的对象的方法。该方法将字符串的内容更改为“蓝色”。

b = 'green'; 

创建一个String内容为“green”的新对象,并将变量指向b它。变量ab现在指向不同的对象。

print a 

指向的 String 对象的a内容为“blue”。因此,根据语言规范,一切正常。

我什么时候会用这个?

每时每刻。在 Ruby 中,您使用变量来临时指向对象,以便在对象上调用方法。对象是你想要使用的东西,变量是你代码中用来引用它们的名称。它们是分开的这一事实可能会不时让你感到困惑(尤其是在带有字符串的 Ruby 中,许多其他语言没有这种行为)

这是否意味着我不能将“a”的值传递给另一个变量,而没有任何更改递归回“a”?

如果你想复制一个字符串,有几种方法可以做到。例如

b = a.clone

或者

b = "#{a}"

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

b = a.something

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

此外,有时一个方法似乎会递归到“a”,有时它会导致“b”成为一个新的object_id。

从来都不是这样。没有任何方法会改变对象的身份。但是,大多数方法将返回一个新对象。一些方法会改变对象的内容——在 Ruby 中你需要更加注意这些方法,因为可能会改变在其他地方使用的数据——在其他 OO 语言中也是如此,JavaScript 对象在这里也不例外,它们的行为以完全相同的方式。

于 2016-02-23T20:06:15.407 回答
1

TL;博士

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

泛化方法的能力是在 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正在向ab指向的同一对象发送消息。由于两个变量都包含同一个对象,因此无论您调用方法 asa.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 真正的作用

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

replace(other_str) → str
将 str 的内容和污点替换为 other_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 的新对象。

怎么做:分配新对象

如果你想让ab指向不同的对象,你可以使用像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返回一个的String 对象,然后将其分配给变量b。这导致将不同的对象分配给每个变量,这是您最初期望的行为。

于 2016-02-23T21:08:47.170 回答
1

在处理哈希中的递归时,它在场景中很有用。

obj = {}
ary = [1,2,3]

temp_obj = obj

ary.each do |entry|
  temp_obj[entry] = {}
  temp_obj = temp_obj[entry]
end

> obj
=> {1=>{2=>{3=>{}}}}

如果你想复制你可以使用dup

> a = 'red'
=> "red"
> b = a.dup
=> "red"
> b.replace('orange')
=> "orange"
> a
=> "red"
> b
=> "orange"

但是dup没有像评论中指出的那样做 deep_copy,见例子

> 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}}
于 2016-02-23T19:46:44.927 回答