如果在 Ruby 中一切都是 Object,为什么这不起作用?

Why doesn't this work if in Ruby everything is an Object?

提问人:jlstr 提问时间:10/7/2011 最后编辑:ROMANIA_engineerjlstr 更新时间:10/17/2017 访问量:10969

问:

考虑到在 Ruby 编程语言中,一切都被称为对象,我安全地假设将参数传递给方法是通过引用完成的。然而,下面的这个小例子让我感到困惑:

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil

正如你所看到的,原始对象没有被修改,我想知道为什么,以及我怎样才能完成所需的行为,即。获取方法以实际更改其参数引用的对象。

Ruby 参数传递 引用

评论

0赞 mliebelt 10/7/2011
我不认为参数传递与面向对象有任何关系。它与此正交。如果您想讨论它,inout 和 in 参数之间的区别(例如在 Corba 中)更合适。

答:

1赞 wkl 10/7/2011 #1

Ruby 将值传递给函数,这些值是对对象的引用。在函数中,您将重新分配给另一个值,在本例中是对 的引用。它不会修改原始对象。s1

你的方法不是在改变传入的对象,而是在改变所指的内容。s

评论

0赞 jlstr 10/7/2011
“Ruby 没有任何传递值的概念”在这里说: stackoverflow.com/questions/1872110/....我开始相信 Ruby 是按值传递的,您能说明一下如何从方法中完成引用对象修改吗?
0赞 Jörg W Mittag 4/26/2012
@user766388:是的,Ruby 是按值传递的。没有如果。没有但是。没有例外。如果你想知道 Ruby(或任何其他语言)是按引用传递还是按值传递,只需尝试一下: .def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"
2赞 Lindydancer 10/7/2011 #2

因为两者都是对同一对象的引用,所以字符串“String”。但是,当您赋值给 时,您不会更改对象“String”,而是让它引用一个新对象。$stringss1

评论

0赞 jlstr 10/7/2011
好吧,你描述的听起来很像传递价值,我说得对吗?另一方面,我怎样才能让该方法真正修改原始对象(“String”)?
0赞 mb14 10/7/2011
@user没有。按 Value 传递将复制字符串。这里的引用是重复的,所以它按值传递引用......
1赞 Lindydancer 10/7/2011
您可以使用破坏性操作修改字符串,但您永远不能将其转换为其他类型的对象。
24赞 Simone Carletti 10/7/2011 #3

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 中的即时对象与 其余的(例如,与 Java 中的 POD 不同)和 Ruby 从语言角度来看,您看不到任何区别(除了 性能也许)。这也是 Ruby 如此优雅的原因之一。

当您将参数传递到方法中时,您正在传递一个 指向引用的变量。在某种程度上,它是 按值传递和按引用传递。我的意思是,你通过 变量的值,但是 variable 始终是对对象的引用。

两者之间的区别:

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!这会更改对象 (a) 到位。由于 'str' 变量(在方法范围之外)和 “a”变量(在方法作用域内)都有一个“值”,即 对同一对象的引用,则反映对该对象的更改 在调用方法后的“str”变量中。在 #your_method 中,你 调用不修改原始对象的 #gsub。取而代之的是 创建包含修改的 String 的新实例。什么时候 将该对象分配给“a”变量,则要更改值 的 'a' 作为对该新 String 实例的引用。但是, “str”的值仍然包含对原始(未修改)的引用 string 对象。

方法是更改引用还是引用对象取决于类类型和方法实现。

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"

字符串是一种常见情况,其中大部分操作都适用于克隆,而不是在自身实例上。 出于这个原因,一些方法都有一个 bang 版本,可以就地执行相同的操作。

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

评论

0赞 jlstr 10/7/2011
很好的解释。很长,但绝对非常值得。感谢您提供包装器的代码示例,既然您已经提供了它,我将授予您的答案作为接受的答案。干得好,非常感谢你Simone。很棒的一课。
2赞 molf 10/7/2011
绝对没有按值传递和按引用传递的组合,Ruby 使用按值传递(就像 Java、C 等一样)。术语“传递引用”与对象引用无关。另请参阅此 SO 问题
17赞 Logan Capaldo 10/7/2011 #4

赋值不会将值绑定到对象,而是将对象引用绑定到标识符。参数传递的工作方式相同。

当您进入函数的主体时,世界如下所示:

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

代码

 s = 1

让世界看起来像

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

赋值语法操作变量,而不是对象。

像许多类似的语言(Java、C#、Python)一样,ruby 是按值传递的,其中值通常是引用。

若要操作字符串对象,可以在字符串上使用方法,例如 .这种事情将反映在方法之外,因为它操纵对象本身。s.upcase!

评论

0赞 jlstr 10/7/2011
谢谢。视觉辅助工具总是很棒,我想我现在明白了这部分。但为了争论......我怎样才能让方法修改引用的对象(“String”)?
1赞 AhHatem 10/7/2011 #5

实际上,大多数托管编程语言,如 java、c#......几乎所有的都不通过引用......

它们都通过值传递引用......这意味着他们做了另一个指向同一对象的引用......为它分配一个新值不会改变原始引用的值......只是 s 指向......

此外,字符串在大多数语言中是不可变的,这意味着您在创建后无法更改值。它们必须被重新创建为新的......因此,您永远不会看到实际字符串的任何变化......

评论

0赞 AhHatem 10/7/2011
关于如何修改引用的对象,您不能在大多数托管编程语言中修改字符串...但是,您可以通过将其包装在其他东西中来修改它,例如类,列表,映射..或任何内容,以便更改引用对象内属性的值,而不是引用本身
0赞 jlstr 10/7/2011
我不认为在这里谈论字符串不变性是恰当的,主要是因为我试图对我自己的类做同样的事情,即。将方法中的“Cat”对象更改为“Dog”对象,结果是相同的(方法不会更改它)。
0赞 AhHatem 10/7/2011
我的意思是改变猫物体内部的东西......没有完全把猫变成别的东西......由于上述原因,这行不通......如果要将 cat 传递给函数并将其更改为 dog..您将不得不将其包裹在其他对象中。
0赞 AhHatem 10/7/2011
我对字符串不变性的意思是,在大多数语言中,直接更改字符串总是会创建一个新字符串,例如,您不能就地修改第二个字符的值。您必须使用新值创建一个新字符串...我之所以这么说,是因为你问如何更改实际的字符串......所以答案是字符串不会改变。
0赞 newacct 10/8/2011
实际上,字符串在 Ruby 中是可变的