Разница между += для целых чисел/строк и ‹‹ для массивов?

Меня смущают разные результаты, которые я получаю при выполнении простого сложения/объединения целых чисел, строк и массивов в Ruby. У меня сложилось впечатление, что при присвоении переменной b переменной a (см. ниже) и последующем изменении значения a это b останется прежним. И это происходит в первых двух примерах. Но когда я изменяю массив 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$ 

Мой инструктор по программированию дал мне следующее объяснение:

Присвоение чего-либо новой переменной — это просто присвоение ей дополнительной метки, а не копирование.

Похоже, что += — это такой же метод, как и ‹‹, поэтому вы ожидаете, что он будет вести себя аналогично. Но на самом деле это «синтаксический сахар», что-то добавленное в язык для облегчения работы разработчиков.

Когда вы запускаете += 1, Ruby преобразует это в a = a + 1.

В этом случае мы не изменяем Fixnum в файле a. Вместо этого мы на самом деле переназначаем его поверх него, эффективно удаляя предыдущее значение a.

С другой стороны, когда вы запускаете b ‹‹ "c", вы изменяете базовый массив, добавляя к нему строку "c".

Мои вопросы таковы:

1) Он упоминает синтаксический сахар, но разве это не то же самое, что и ‹‹, то есть синтаксический сахар для метода .push?

2) Какая разница, является ли += синтаксическим сахаром или более формальным методом? Если между ними есть какая-то разница, то не означает ли это, что мое ранее понятое понятие синтаксического сахара («синтаксис в языке программирования, предназначенном для облегчения чтения или выражения») неполно, поскольку это не так. t его единственная цель?

3) Если присваивание b переменной a не создает копию a, то почему стирание старого значения a не означает, что старое значение b также стирается во всех трех случаях (целое число, строка и массив)?

Как вы можете видеть, я совсем запутался в том, что, как мне казалось, я понял до сих пор. Любая помощь высоко ценится!


person Richie Thomas    schedule 05.04.2013    source источник
comment
Вы хотите, чтобы мы объяснили, почему два разных результата?   -  person Arup Rakshit    schedule 05.04.2013
comment
@iAmRubuuu- да, и я вижу, что вы уже сделали это ниже. Объяснение object_id было полезным, спасибо за это.   -  person Richie Thomas    schedule 07.04.2013


Ответы (7)


Видите ли, имена (имена переменных, такие как 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

Здесь нет присваивания, метод << модифицирует существующий массив, не заменяя его. Поскольку замены нет, a и b по-прежнему указывают на один и тот же массив, и можно увидеть изменения, внесенные в другой.

person Sergio Tulentsev    schedule 05.04.2013
comment
Поэтому, когда я говорю, что a = 5 и b = a, я на самом деле не указываю b на a, а скорее указываю на то, на что также указывает a. Но если я изменю то, на что указывает a, то теперь будут указываться разные «вещи» — новый элемент a и старый элемент b. Это правильно? - person Richie Thomas; 06.04.2013

Ответ на 1) и 2) вашего вопроса:

Причина, по которой += является синтаксическим сахаром, а << нет, довольно проста: += абстрагирует некоторые синтаксические выражение: a += 1 — это просто короткая версия a = a + 1. << сам по себе является методом и не является псевдонимом для push: << может принимать только один аргумент, тогда как push может принимать произвольное количество аргументов: я демонстрирую это с помощью отправки здесь, поскольку [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, не принимая во внимание различия, показанные выше, поскольку он делает большинство операций, связанных с присоединением элементов к массиву, более простыми и синтаксически более узнаваемыми.

Если мы углубимся и посмотрим на различные варианты использования << в рубине:

  • Вставьте элемент в массив:

    [1,2] << 5 
    
  • объединить строку с другой, здесь << фактически является псевдонимом для concat

    "hello " << "world"
    
  • Откройте класс singleton и определите метод в классе:

    class Foo
      class << self
        def bar
          puts 'baz'
        end
      end
    end
    
  • И последнее, но не менее важное: добавьте self к self в целых числах:

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

Мы видим, что << на самом деле означает append во всем Ruby, поскольку оно всегда появляется в контексте, где что-то добавляется к чему-то уже существующему. Поэтому я бы предпочел утверждать, что << является важной частью синтаксиса 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) просто присваивается переменной, поэтому сама переменная не является объектом, она просто указывает на него.

person Beat Richartz    schedule 05.04.2013

String#+ :: str + other_str → new_str Конкатенация — возвращает новый Строка, содержащая other_str, объединена в str.

String#<< :: str << integer → str : Добавить — Сцепляет данный объект с 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
person Arup Rakshit    schedule 05.04.2013

Ваш пример:

a = 100
b = a
a+= 5

эквивалентно:

a = 100
b = a
a = 100 + 5

После этого a содержит ссылку на 105, а b по-прежнему содержит ссылку на 100. Вот как работает назначение в Ruby.

Вы ожидали, что += изменит экземпляр объекта 100. Однако в Ruby (цитируя документы):

Фактически существует только один экземпляр объекта 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 << "e" объединяет "e" с a, тем самым меняя получателя: a по-прежнему ссылается на тот же экземпляр объекта.

Другие методы, такие как a += "e", возвращают (и присваивают) новый String: a впоследствии будут ссылаться на этот новый экземпляр.

документация довольно ясна:

str + other_str → new_str

Конкатенация — возвращает новый String, содержащий other_str, соединенный с str.

улица ‹‹ объект → улица

Append — объединяет данный объект с str.

person Stefan    schedule 05.04.2013

Я могу ответить на ваши вопросы.

1) Нет, метод << не является синтаксическим сахаром для push. Это оба метода с разными именами. У вас могут быть объекты в Ruby, которые определяют одно, но не другое (например, String ).

2) Для обычного метода, такого как <<, единственное, что может произойти в результате a << x, — это изменение объекта, на который указывает a. Операторы a << x или a.push(x) не могут создать новый объект и изменить переменную a, чтобы она указывала на него. Именно так работает Руби. Такие вещи называются "вызовом метода".

Причина, по которой += является синтаксическим сахаром, заключается в том, что его можно использовать для изменения переменной без изменения старого объекта, на который раньше указывала переменная. Рассмотрим 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), то это изменение повлияет на все переменные, указывающие на объект. Вы не можете изменять символы и целые числа.

person David Grayson    schedule 05.04.2013

Я думаю, приведенные выше ответы объясняют причину. Также обратите внимание, что если вы хотите убедиться, что b не является указателем, вы можете использовать b = a.dup вместо b=a (дублировать для дубликата)

person aherve    schedule 05.04.2013

Я постараюсь ответить на ваш вопрос в меру своих возможностей.

Да, оба являются «сахарами», но они работают по-разному, и, как сказал Серджио Туленцев, << на самом деле это не сахар, а псевдоним.

И это работает как псевдоним в 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.

So

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

Переменные в Ruby не содержат значений, на которые они указывают!

person Henrik Andersson    schedule 05.04.2013
comment
вы на самом деле копируете значение - нет, это не так. Нет копии. Если, конечно, вы не пытались сказать, что копируемое значение является указателем. В таком случае вы плохо объяснили. - person Sergio Tulentsev; 05.04.2013
comment
Спасибо! :) Я немедленно перефразирую свой ответ! Я пытался слишком объяснить, но теперь я понимаю, что вы имеете в виду, извините! - person Henrik Andersson; 05.04.2013
comment
@СергиоТуленцев лучше? Если нет, я просто удалю свой ответ! - person Henrik Andersson; 05.04.2013
comment
В чем разница между передачей по ссылке и вашим следующим предложением? :) - person Sergio Tulentsev; 05.04.2013
comment
@SergioTulentsev, я надеюсь, что это так! :) Преклоняюсь перед превосходством, если вы думаете, что я больше путаю, чем помогаю! :) - person Henrik Andersson; 05.04.2013
comment
Итак, ruby ​​передается по значению или нет? Вы сами себе противоречите :) - person Sergio Tulentsev; 05.04.2013
comment
Аргх, я думал, что я его удалил! Это был мой худший ответ за все время. Прошу прощения! - person Henrik Andersson; 05.04.2013
comment
@SergioTulentsev ты классный чувак, спасибо за помощь! и извините за беспокойство! :) - person Henrik Andersson; 05.04.2013