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

时间:2011-10-07 14:06:04

标签: ruby parameter-passing pass-by-reference

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

$string = "String"

def changer(s)
  s = 1
end

changer($string)

puts $string.class
String
 => nil

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

5 个答案:

答案 0 :(得分:24)

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

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

一些值得注意的引语:

  

绝对正确:Ruby使用pass-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>
     

没有标准方式(即除了涉及eval和   metaprogramming magic)使调用范围内的变量指向   另一个对象。而且,顺便说一句,这是独立于对象的   变量指的是。 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'
     

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 = "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"

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

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

答案 1 :(得分:17)

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

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

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

代码

 s = 1

使世界看起来像

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

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

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

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

答案 2 :(得分:2)

因为$strings都是对同一对象的引用,所以字符串“String”。但是,当您将s分配给1时,不会更改对象“String”,而是让它引用一个新对象。

答案 3 :(得分:1)

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

您的方法不会更改传入的对象,而是更改s引用的内容。

答案 4 :(得分:1)

实际上,大多数托管编程语言如java,c#... almsot都没有通过引用传递...

它们都通过值传递引用...这意味着它们创建另一个指向同一对象的引用...用新值赋值它不会改变原始引用的值...只是什么s指向......

此外,字符串在大多数语言中是不可变的,这意味着你不能在创建后更改它们......它们必须重新创建为新的...所以你永远不会看到实际字符串中的任何变化... < / p>