2

我对在 Ruby 中对整数、字符串和数组执行简单的加法/连接时得到的不同结果感到困惑。我的印象是,将变量 b 分配给 a(见下文),然后更改 a 的值时,b 将保持不变。在前两个示例中也是如此。但是当我在第三个示例中修改 Array a 时,a 和 b 都被修改了。

a = 100
b = a
a+= 5
puts a
puts b

a = 'abcd'
b = a
a += 'e'
puts a
puts b

a = [1,2,3,4]
b = a
a << 5
puts a.inspect
puts b.inspect

以下是上述代码在终端中返回的内容:

Ricks-MacBook-Pro:programs rickthomas$ ruby variablework.rb
105
100
abcde
abcd
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
Ricks-MacBook-Pro:programs rickthomas$ 

我的编程教练给了我以下解释:

将某些内容分配给新变量只是给它一个额外的标签,它不会复制。

看起来 += 是一种方法,就像 << 一样,因此您希望它的行为相似。但实际上,它是“语法糖”,添加到语言中以使开发人员更轻松。

当您运行 a += 1 时,Ruby 会将其转换为 a = a + 1。

在这种情况下,我们不会修改 a.Fixnum 中的 Fixnum。相反,我们实际上是在它之上重新分配,有效地消除了 a 的先前值。

另一方面,当您运行 b << "c" 时,您正在通过将字符串 "c" 附加到它来修改底层数组。

我的问题是:

1)他提到了语法糖,但这不也是 << 是什么,即 .push 方法的语法糖吗?

2)如果 += 是语法糖或更正式的方法,为什么这很重要?如果两者之间存在一些差异,那么这是否意味着我之前对语法糖的理解(“编程语言中旨在使事情更容易阅读或表达的语法”)是不完整的,因为这不是它的唯一目的是什么?

3)如果将b分配给a不会复制a,那么为什么不擦除a的旧值意味着对于所有3种情况(整数,字符串和数组)也擦除b的旧值?

正如你所看到的,我对一些我认为我直到现在都理解的事情很感兴趣。任何帮助深表感谢!

4

7 回答 7

4

您会看到,名称(变量名称,例如ab)本身不包含任何值。它们只是指向一个值。当你做作业时

a = 5

thena现在指向值 5,不管它之前指向什么。这个很重要。

a = 'abcd'
b = a

这里两者都a指向b同一个字符串。但是,当你这样做时

a += 'e'

它实际上被翻译成

a = a + 'e'
# a = 'abcd' + 'e'

因此,namea现在绑定到一个新值,同时b一直指向“abcd”。

a = [1,2,3,4]
b = a
a << 5

这里没有赋值,方法<<修改现有数组而不替换它。因为没有替代品,所以两者都a指向b同一个数组,并且可以看到对另一个数组所做的更改。

于 2013-04-05T12:28:35.720 回答
3

您问题的1)和2)的答案:

+=is语法糖<<is not的原因相当简单:+=抽象了一些语法表达式:a += 1只是a = a + 1. <<本身就是一个方法,而不是push:的别名,<<只能接受一个参数,而push可以接受任意数量的参数:我在这里用 send 演示了这一点,因为[1,2]<<(1,2)在语法上不正确:

[1,2].send(:<<, 4, 5) #=> ArgumentError: wrong number of arguments (2 for 1)

push将所有参数附加到数组:

[1,2].push(4,5,6) #=> [1,2,4,5,6]

因此,<<是 ruby​​ 数组中不可替代的部分,因为没有等效的方法。有人可能会争辩说,它是 的某种语法糖push,而忽略了上面显示的差异,因为它使大多数涉及将元素附加到数组的操作更简单,并且在语法上更易于识别。

如果我们更深入地了解<<整个 ruby​​ 的不同用途:

  • 将元素推送到数组:

    [1,2] << 5 
    
  • 将一个字符串连接到另一个,这里<<实际上是一个别名concat

    "hello " << "world"
    
  • 打开单例类并在类上定义一个方法:

    class Foo
      class << self
        def bar
          puts 'baz'
        end
      end
    end
    
  • 最后但并非最不重要的附加selfself整数:

    1 << 2 #translates to ((1 + 1) + (1 + 1))
    

我们可以看到它<<实际上代表整个 ruby​​ 中的append,因为它总是出现在某些内容被附加到已经存在的内容的上下文中。因此,我宁愿认为这<<是 ruby​​ 语法的重要部分,而不是语法糖。

以及3)的答案

b如果您使用运算符,则 ' 的分配不会被修改(或如您所说的那样删除其旧值)的原因+=只是a += 1,作为 的缩写a = a + 1,重新分配a的值,因此随之分配一个新对象。<<正在修改原始对象。您可以使用以下命令轻松查看object_id

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

a += 1
b.object_id == a.object_id #=> false

a = [1,2]
b = a
b.object_id == a.object_id #=> true
a << 3
b.object_id == a.object_id #=> true

Integer实例(100, )等也有一些警告101:相同的数字始终是相同的对象,因为拥有多个实例没有任何意义,例如100

a = 100
b = a
b.object_id == a.object_id #=> true
a += 1
b.object_id == a.object_id #=> false
a -= 1
b.object_id == a.object_id #=> true

这也说明值,或者说Integer实例(100)只是赋值给变量,所以变量本身并不是一个对象,它只是指向它。

于 2013-04-05T14:57:11.727 回答
1

String#+:: str + other_str → new_strConcatenation - 返回一个包含other_str连接到的新字符串str

String#<<:: str << integer → str: Append - 将给定对象连接到str.

<<不创建新对象,就像+那样。

样品1:

a = 100
p a.object_id
b = a
p b.object_id
a+= 5
p a.object_id
p b.object_id
puts a
puts b

输出:

201
201
211
201
105
100
于 2013-04-05T12:30:01.917 回答
1

你的例子:

a = 100
b = a
a+= 5

相当于:

a = 100
b = a
a = 100 + 5

之后a持有对 的引用,105并且b仍然持有对 的引用100。这就是分配在 Ruby 中的工作方式。

您希望+=更改对象实例100。然而,在 Ruby 中(引用文档):

对于任何给定的整数值,实际上只有一个Fixnum对象实例

所以只有一个对象实例 for100和另一个(但始终相同)一个 for 105。更改100105所有 100更改为105。因此,无法在 Ruby 中修改这些实例,它们是固定的。

String另一方面,可以修改一个实例,并且与Integer同一字节序列可以有多个实例不同:

a = "abcd"
b = "abcd"
a.equal? b # returns true only if a and b are the same object
# => false

a << "e"连接"e"a,从而改变接收者:a仍然引用相同的对象实例。

其他方法,如a += "e"返回(和分配)一个 String的:a之后会引用这个新实例。

文档很清楚:

str + other_str → new_str

连接 - 返回连接到的新String包含。other_strstr

str << obj → str

追加 - 将给定对象连接到str.

于 2013-04-05T13:41:28.633 回答
1

我可以回答你的问题。

1)不,该<<方法不是push. 它们都是具有不同名称的方法。您可以在 Ruby 中拥有定义一个但不定义另一个的对象(例如String)。

2)对于像这样的普通方法<<唯一可能发生的事情是指向a << x的对象a被修改。语句a << xora.push(x)不能创建新对象并将变量更改a为指向它。这就是 Ruby 的工作原理。这种事情称为“调用方法”。

作为语法糖很重要的原因在于+=,它可以用来修改变量,而不会改变变量曾经指向的旧对象。考虑a += x。该语句可以修改 a 指向的对象,因为它是实际分配给的语法糖a

a = a + x

上面发生了两件事。首先,使用 的一个参数+调用该方法。然后方法的返回值,无论它是什么,都分配给变量。ax+a

3)您的 Array 案例不同的原因是您选择改变数组而不是创建新数组。你可以用来+=避免改变数组。我认为这六个示例将为您理清思路并向您展示 Ruby 中的可能性:

没有突变的字符串

a = "xy"
b = a
a += "z"
p a  # => "xyz"
p b  # => "xy"

带有突变的字符串

a = "xy"
b = a
a << "z"
p a  # => "xyz"
p b  # => "xyz"

没有突变的数组

a = [1, 2, 3]
b = a
a += [4]
p a  # => [1, 2, 3, 4]
p b  # => [1, 2, 3]

带有突变的数组

a = [1, 2, 3]
b = a
a.concat [4]
p a  # => [1, 2, 3, 4]
p b  # => [1, 2, 3, 4]

没有突变的整数

a = 100
b = a
a += 5
puts a   # => 105
puts b   # => 100

有突变的整数

在 Ruby 中,改变整数实际上是不可能的。实际上,写作a = b = 89确实创建了数字 89 的两个副本,并且这个数字永远不会发生变异。只有少数特殊类型的对象具有这样的行为。

结论

您应该将变量视为名称,将对象视为无名的数据。

Ruby 中的所有对象都可以以不可变的方式使用,您永远不会实际修改对象的内容。如果您这样做,那么您不必担心b我们示例中的变量会自行更改;b将始终指向同一个对象,并且该对象永远不会改变。只有b当您执行某种形式的b = x.

Ruby 中的大多数对象都可以变异。如果您有多个变量引用同一个对象并且您选择改变对象(例如通过调用push),那么该更改将影响指向该对象的所有变量。你不能改变符号和整数。

于 2013-04-05T15:41:49.150 回答
0

我想上面的答案解释了原因。另请注意,如果要确保b没有指针,可以使用b = a.dup代替b=a (dup for duplicate )

于 2013-04-05T12:34:33.603 回答
0

我会尽力回答你的问题。

是的,两者都是“糖”,但它们的作用不同,正如 Sergio Tulentsev 所说,<<它不是真正的糖,而是别名。

这些在类 Unix 语言中用作别名,它是根据您的喜好命名的东西的缩写。

所以对于第一种情况:+=基本上发生的事情是你在说:

for the value 100 assign label 'a'.
for label 'b' assign the value of label 'a'.
for label 'a' take the value of label 'a' and add 5 to label 'a's value and return a new value
print label 'a' #this now holds the value 105
print label 'b' #this now holds the value 100

在 Ruby 的引擎盖下,这与+=发生这种情况时返回一个新字符串有关。

对于第二种情况:<<它说:

for value [1,2,3,4] assign label 'a'  
for label 'b' assign the value of label 'a'
for label 'a' do the '<<' thing on the value of label 'a'.
print label 'a'
print label 'b'

如果您将 应用于<<字符串,它将修改现有对象并附加到它。

那么有什么不同。不同的是,<<糖的作用不是这样的:

a is the new value of a + 5

它的行为是这样的:

5 into the value of 'a'

2) 因为在这种情况下您使用语法糖的方式使开发人员更容易阅读和理解代码。这是一个简写。

好吧,速记,如果你这样称呼它们,确实有不同的用途。语法糖不是同质的,即。对于所有“糖”,它的作用方式不同。

3)关于擦除值:

就像这样。

put value 100 into the label 'a'
put the value of label 'a' into label 'b'
remove label 'a' from the value.

所以

a = 100
b = a
a = nil
puts a
puts b
=> 100

Ruby 中的变量不保存它们指向的值!

于 2013-04-05T12:41:57.503 回答