【问题标题】:Difference between += for Integers/Strings and << For Arrays?整数/字符串的 += 和数组的 << 之间的区别?
【发布时间】:2013-03-27 20:37:40
【问题描述】:

我对在 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。相反,我们实际上是在它之上重新赋值,有效地消除了 a 的先前值。

另一方面,当您运行 b

我的问题是:

1) 他提到了语法糖,但这不也是

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

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

如您所见,我对一些我认为我直到现在都理解的事情非常感兴趣。非常感谢任何帮助!

【问题讨论】:

  • 你想让我们解释一下为什么会有两个不同的结果吗?
  • @iAmRubuuu- 是的,我看到你已经在下面这样做了。 object_id 的解释很有帮助,谢谢。

标签: ruby syntactic-sugar


【解决方案1】:

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

a = 5

然后a 现在指向值 5,不管它之前指向什么。这很重要。

a = 'abcd'
b = a

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

a += 'e'

其实是翻译成

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

所以,名称a 现在绑定到一个新值,而b 一直指向“abcd”。

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

这里没有赋值,方法&lt;&lt; 修改现有数组而不替换它。因为没有替换,a 和 b 仍然指向同一个数组,并且可以看到对另一个数组所做的更改。

【讨论】:

  • 所以当我说 a = 5 和 b = a 时,我实际上并不是将 b 指向 a,而是将它指向 a 也指向的任何东西。但是,如果我修改 a 指向的内容,那么现在指向不同的“事物”——a 的新项目和 b 的旧项目。对吗?
【解决方案2】:

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

+= 是syntactic sugar 而&lt;&lt; 不是的原因相当简单:+= 抽象了一些语法表达式:a += 1 只是a = a + 1 的一个简短版本。 &lt;&lt; 本身就是一个方法,不是push 的别名:&lt;&lt; 只能接受一个参数,而push 可以接受任意数量的参数:我是在这里发送演示这一点,因为 [1,2]&lt;&lt;(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]

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

如果我们更深入地了解&lt;&lt; 在整个 ruby​​ 中的不同用途:

  • 将元素推入数组:

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

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

    class Foo
      class << self
        def bar
          puts 'baz'
        end
      end
    end
    
  • 最后但并非最不重要的一点是将self 附加到整数中的self:

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

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

以及 3) 的答案

如果您使用+= 运算符,b 的赋值不会被修改(或如您所说的那样删除其旧值)的原因就是 a += 1,作为a = a + 1 的缩写, 重新分配 a 的值,因此分配一个新对象。 &lt;&lt; 正在修改原始对象。您可以使用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)只是赋值给变量,所以变量本身不是一个对象,它只是指向它。

【讨论】:

    【解决方案3】:

    String#+ :: str + other_str → new_str 串联—返回一个新字符串,其中包含与str 串联的other_str。

    String#&lt;&lt; :: str &lt;&lt; integer → str : Append - 将给定对象连接到 str。

    &lt;&lt; 不会创建新对象,而 + 会。

    样品 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
    

    【讨论】:

      【解决方案4】:

      你的例子:

      a = 100
      b = a
      a+= 5
      

      相当于:

      a = 100
      b = a
      a = 100 + 5
      

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

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

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

      因此,100 只有一个对象实例,105 则只有一个(但始终相同)。将100 更改为105 会将所有 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 &lt;&lt; "e" 将"e" 连接到a,从而改变接收者:a 仍然引用同一个对象实例。

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

      documentation 很清楚:

      str + other_str → new_str

      连接 - 返回一个新的 String,其中包含连接到 str 的 other_str。

      str

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

      【讨论】:

        【解决方案5】:

        我可以回答你的问题。

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

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

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

        a = a + x
        

        上面发生了两件事。首先在a 上调用+ 方法,其中一个参数为x。然后+方法的返回值,不管是什么,都赋值给变量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),那么该更改将影响指向该对象的所有变量。你不能改变符号和整数。

        【讨论】:

          【解决方案6】:

          我猜上面的答案解释了原因。另请注意,如果您想确保 b 不是指针,您可以使用 b = a.dup 而不是 b=a (重复复制)

          【讨论】:

            【解决方案7】:

            我会尽力回答你的问题。

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

            这些在类 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 的内部,这与 += 在发生这种情况时返回一个新字符串有关。

            对于第二种情况:&lt;&lt; 它的意思是:

            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'
            

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

            那么有什么不同。不同的是&lt;&lt; 糖不会像这样:

            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 中的变量不保存它们指向的值!

            【讨论】:

            • “你实际上是在复制值”——不,你不是。没有副本。当然,除非您试图说被复制的“值”是一个指针。在这种情况下,您没有做好解释工作。
            • 谢谢! :) 我会立即改写我的答案!我试图过度解释,但我现在明白你的意思了,对不起!
            • @SergioTulentsev 更好吗?如果不是,我将删除我的答案!
            • “通过引用传递”和你的下一句话有什么区别? :)
            • @SergioTulentsev 我希望这样做! :) 如果您认为我混淆的不仅仅是帮助,我会向优越感低头! :)
            猜你喜欢
            • 1970-01-01
            • 2017-01-29
            • 2015-08-08
            • 2021-09-11
            • 2014-05-20
            • 1970-01-01
            • 1970-01-01
            • 2013-11-06
            • 1970-01-01
            相关资源
            最近更新 更多