Почему это не работает, если в Ruby все является объектом? - PullRequest
12 голосов
/ 07 октября 2011

Учитывая, что в языке программирования Ruby все называется Object, я смело предположил, что передача аргументов в методы выполняется по ссылке . Однако этот маленький пример ниже озадачивает меня:

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil

Как вы видите, исходный Объект не был изменен, я хотел бы знать , почему , а также, как я мог выполнить желаемое поведение, т.е. Получение метода для фактического изменения объекта, на который ссылается его аргумент.

Ответы [ 5 ]

24 голосов
/ 07 октября 2011

Работа Ruby - это комбинация передачи по значению и передачи по ссылке.Фактически, Ruby использует передачу по значению со ссылками.

Вы можете прочитать больше в следующих темах:

Некоторые заметные цитаты:

Абсолютно верно: Ruby использует передачу по значению - со ссылками.

irb(main):004:0> def foo(x) x = 10 end
=> nil
irb(main):005:0> def bar; x = 20; foo(x); x end
=> nil
irb(main):006:0> bar
=> 20
irb(main):007:0>

Не существует стандартного способа (т.е. кроме использования eval и магии метапрограммирования) сделать переменную в вызывающей области видимости указывающей на другой объект.И, между прочим, это не зависит от объекта, на который ссылается переменная.Непосредственные объекты в Ruby легко интегрируются с остальными (например, в отличие от POD в Java), и с точки зрения языка Ruby вы не видите никакой разницы (возможно, кроме производительности).Это одна из причин, почему Ruby такой элегантный.

и

Когда вы передаете аргумент в метод, вы передаете переменную, которая указывает на ссылку,В некотором смысле, это комбинация передачи по значению и передачи по ссылке.Я имею в виду, что вы передаете значение переменной в метод, однако значение переменной составляет всегда ссылка на объект.

Разница между:

def my_method( a )
  a.gsub!( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'ruby is awesome'

и:

def your_method( a )
  a = a.gsub( /foo/, 'ruby' )
end

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'foo is awesome'

в #my_method вы вызываете #gsub!который меняет объект (а) на месте.Поскольку переменная 'str' (вне области действия метода) и переменная 'a' (внутри области действия метода) оба имеют "значение", которое является ссылкой на один и тот же объект, изменение этого объекта отражается в "strпеременная после вызова метода.В #your_method вы вызываете #gsub, который не изменяет исходный объект.Вместо этого он создает новый экземпляр String, содержащий изменения.Когда вы назначаете этот объект переменной «a», вы меняете значение «a», чтобы оно стало ссылкой на этот новый экземпляр String.Однако значение 'str' по-прежнему содержит ссылку на исходный (неизмененный) строковый объект.

Изменяет ли метод ссылку или объект, на который ссылаются, зависит от типа класса и реализации метода.

string = "hello"

def changer(str)
  str = "hi"
end

changer(string)
puts string
# => "hello"

string не изменяется, поскольку присваивание в строках заменяет ссылку, а не ссылочное значение.Если вы хотите изменить строку на месте, вам нужно использовать String#replace.

string = "hello"

def changer(str)
  str.replace "hi"
end

changer(string)
puts string
# => "hi"

Строка является распространенным случаем, когда большая часть операций работает с клонами, а не с экземпляром self.По этой причине у нескольких методов есть версия взрыва, которая выполняет одну и ту же операцию на месте.

str1 = "hello"
str2 = "hello"

str1.gsub("h", "H")
str2.gsub!("h", "H")

puts str1
# => "hello"
puts str2
# => "Hello"

Наконец, чтобы ответить на исходный вопрос, вы не можете изменить строку.Вы можете только присвоить ему новое значение или обернуть строку в другой изменяемый объект и заменить внутреннюю ссылку.

$wrapper = Struct.new(:string).new
$wrapper.string = "String"

def changer(w)
  w.string = 1
end

changer($wrapper)

puts $wrapper.string
# => 1
17 голосов
/ 07 октября 2011

Назначение не привязывает значения к объектам, оно привязывает ссылки на объекты к идентификаторам. Передача аргумента работает так же.

Когда вы вводите тело функции, мир выглядит так:

 +---+                  +----------+
 | s |----------------->| "String" |
 +---+                  +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+

код

 s = 1

делает мир похожим на

 +---+       +---+      +----------+
 | s |------>| 1 |      | "String" |
 +---+       +---+      +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+

Синтаксис присваивания управляет переменными, а не объектами.

Как и во многих похожих языках (Java, C #, Python), ruby ​​является передачей по значению, где значения чаще всего являются ссылками.

Чтобы манипулировать строковым объектом, вы можете использовать метод для строки, такой как s.upcase!. Подобные вещи будут отражены вне метода, поскольку они манипулируют самим объектом.

2 голосов
/ 07 октября 2011

Поскольку и $string, и s являются ссылками на один и тот же объект, строка "String".Однако, когда вы присваиваете s 1, вы не изменяете объект «String», вы делаете его ссылкой на новый объект.

1 голос
/ 07 октября 2011

На самом деле, большинство управляемых языков программирования, таких как java, c # ... almsot, все не передаются по ссылке ...

Они все передают ссылку по значению ... это означает, что они делают другую ссылку, которая указывает на тот же объект ... присвоение ему нового значения не изменит значение исходной ссылки ... только что указывает на ...

Кроме того, строки являются неизменяемыми в большинстве языков, что означает, что вы не можете изменить значение после создания ... они должны быть воссозданы как новые ... так что вы никогда не увидите никаких изменений в фактической строке ...

1 голос
/ 07 октября 2011

Ruby передает значения функциям, и эти значения являются ссылками на объекты. В вашей функции вы переназначаете s на другое значение, в данном случае ссылка на 1. Не изменяет исходный объект.

Ваш метод не изменяет переданный объект, вы меняете то, к чему относится s.

...