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

jls*_*str 12 ruby parameter-passing pass-by-reference

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

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,原始对象未被修改,我希望知道为什么,以及如何实现所需的行为,即.获取方法实际更改其参数引用的对象.

Sim*_*tti 24

Ruby的工作方式是传递值和传递引用的组合.实际上,Ruby使用带引用的pass by值.

您可以在以下主题中阅读更多内容:

一些值得注意的引用:

绝对正确:Ruby使用pass by value - 带引用.

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>
Run Code Online (Sandbox Code Playgroud)

没有标准的方法(即除了涉及eval和元编程魔法之外)使调用范围中的变量指向另一个对象.而且,顺便说一句,这与变量引用的对象无关.Ruby中的直接对象与其他对象无缝集成(例如,不同于Java中的POD),从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'
Run Code Online (Sandbox Code Playgroud)

和:

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

str = 'foo is awesome'
my_method( str )            #=> 'ruby is awesome'
str                                    #=> 'foo is awesome'
Run Code Online (Sandbox Code Playgroud)

就是在#my_method中,你正在调用#gsub!它改变了对象(a)的位置.由于'str'变量(在方法范围之外)和'a'变量(在方法范围内)都有一个"值",它是对同一个对象的引用,对该对象的更改反映在'str'中'调用方法后的变量.在#your_method中,调用#gsub,它不会修改原始对象.相反,它会创建一个包含修改的String新实例.将该对象分配给'a'变量时,您将'a'的值更改为对该新String实例的引用.但是,'str'的值仍包含对原始(未修改)字符串对象的引用.

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

string = "hello"

def changer(str)
  str = "hi"
end

changer(string)
puts string
# => "hello"
Run Code Online (Sandbox Code Playgroud)

string不会更改,因为字符串上的赋值会替换引用,而不是引用的值.我想要修改字符串,你需要使用String#replace.

string = "hello"

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

changer(string)
puts string
# => "hi"
Run Code Online (Sandbox Code Playgroud)

字符串是一种常见情况,其中大部分操作适用于克隆,而不是自身实例.出于这个原因,有几种方法具有在适当位置执行相同操作的爆炸版本.

str1 = "hello"
str2 = "hello"

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

puts str1
# => "hello"
puts str2
# => "Hello"
Run Code Online (Sandbox Code Playgroud)

最后,要回答原始问题,您无法更改字符串.您只能为其分配新值或将字符串包装到不同的可变对象中并替换内部引用.

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

def changer(w)
  w.string = 1
end

changer($wrapper)

puts $wrapper.string
# => 1
Run Code Online (Sandbox Code Playgroud)

  • 绝对没有pass-by-value和pass-by-reference的组合,Ruby**只使用pass-by-value(就像Java,C等).术语"传递引用"与对象引用没有任何关系*.另见[此SO问题](http://stackoverflow.com/questions/2027/pass-by-reference-or-pass-by-value) (2认同)

Log*_*ldo 17

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

当您输入函数的主体时,世界看起来像这样:

 +---+                  +----------+
 | s |----------------->| "String" |
 +---+                  +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+
Run Code Online (Sandbox Code Playgroud)

代码

 s = 1
Run Code Online (Sandbox Code Playgroud)

让世界看起来像

 +---+       +---+      +----------+
 | s |------>| 1 |      | "String" |
 +---+       +---+      +----------+
                              ^
 +-------+                    |
 |$string|--------------------+
 +-------+
Run Code Online (Sandbox Code Playgroud)

赋值语法操纵变量,而不是对象.

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

要操纵字符串对象,可以在字符串上使用方法,例如s.upcase!.当它操纵对象本身时,这种事物将反映在方法之外.