更改Ruby变量/引用的值

6

我刚刚偶然发现一件我不太理解的事情。我知道Ruby中的变量是引用。因此那些很棒的东西是可能实现的。但是当我将一个变量传递给一个方法时,它的行为很奇怪:

my_var_a = "nothing happend to me"
my_var_b = "nothing happend to me"

def parse_set(my_var_set)
  my_var_set = "my value changed"
end

def parse_sub(my_var_sub)
  my_var_sub.sub! /(.*)/, "my value changed"
end

parse_set(my_var_a)
parse_sub(my_var_b)

my_var_a # => "nothing happend to me"
my_var_b # => "my value changed"

你能解释一下为什么使用 sub! 可以工作而使用 = 会使对象保持不变吗?我如何避免使用 sub! 但又能获得相同的结果?

3个回答

12
my_var_amy_var_set 是不同的引用,但它们指向同一个对象。如果你在 my_var_set 中修改了该对象,那么这个更改会在 my_var_a 中显示出来。但是,如果你将 my_var_set 指向一个新的对象,那么并不会改变 my_var_a 所指向的对象。

编辑:澄清一下...

Ruby 所做的是按值传递引用。当你说

my_var_a = "nothing happend to me"
Ruby将字符串"nothing happened to me"保存在内存位置(假设为1000),并在另一个内存位置(假设为2000)保存了my_var_a引用。当您的代码使用my_var_a时,解释器会查看位置2000,发现它指向1000,然后从1000获取实际的字符串值。
当您调用parse_set(my_var_a)时,Ruby实际上创建了一个新的引用,命名为my_var_set,并将其指向my_var_a指向的字符串(内存位置1000)。但是,my_var_set是my_var_a引用的副本 - 假设my_var_set创建在内存位置3000. my_var_a和my_var_set是内存中2个完全不同的引用,它们只是恰好指向相同的存储字符串值的内存位置。
在parse_set中,语句my_var_set = "my value changed"在内存中创建了一个新的字符串,并将my_var_set指向该新的内存位置。但是,这不会改变原始的my_var_a引用所指向的内容!现在my_var_set指向一个不同的内存位置,对该变量进行的任何操作都不会影响my_var_a.
对于parse_sub也是同样的副本引用。但是parse_sub更改字符串的原因是因为您直接在my_var_sub引用上调用方法。当您这样做时,解释器获取my_var_sub所指向的对象,然后修改它。因此,该更改将显示在my_var_a引用中,因为它仍然指向相同的字符串。

你能否以某种方式重新表达一下?我不太明白关于重定向的部分 :) (我还更改了变量名称以减少歧义)而且我怎么能在不使用荒谬的子程序和正则表达式的情况下更改值呢? - nocksock
1
我添加了更多信息,希望这样能让事情更清晰。按值传递引用很复杂,需要一段时间才能意识到发生了什么。此外,如果不使用子程序来改变值,只需让您的“解析”方法返回一个值,并将其赋值给原始变量:my_var_a = some_parse_method(my_var_a) - Kaleb Brasee
哇,这是一个非常好的和简单的解释。谢谢! - nocksock
“通过值传递引用”是描述 Ruby 工作方式最准确的方式。 - Wayne Conrad

6
irb(main):008:0* a = 'abc'
=> "abc"
irb(main):009:0> a.replace('def')
=> "def"
irb(main):010:0> a
=> "def"

在使用 Ruby 编程的多年里,我从未确切使用过 replace 方法。也许代码设计上还有更好的方案。

大多数改变字符串接收者的方法都以 ! 结尾(如 sub!、capitalize!、chomp! 等)。其中一些修改接收器的方法没有以 ! 结尾(比如 replace 方法)。如果您调用修改接收器的方法,则所有对该对象的引用都将看到更改。如果您调用不会更改接收器的方法,则该方法返回一个新字符串。

gsub 不会修改接收器,而是返回 String 的新实例:

a = "foo"
b = a
p a.sub(/o/, '#')     # "f##"
p a                   # => "foo"
p b                   # => "foo"

gsub!会修改接收者:

a = "foo"
b = a
p a.sub!(/o/, '#')    # => "f#o"
p a                   # => "f#o"
p b                   # => "f#o"

谢谢你指出了replace方法。但是Kaleb的答案向我解释了其内部机制,使我能够理解正在发生的事情,否则我会接受你的答案:D - nocksock

0
"

我如何避免使用 sub! 但仍然得到相同的结果?

"
def parse_set()
  "my value changed"
end

a = parse_set()

将其设置为对象的实例变量(虽然这只在您的主脚本中有效,但如果您想使用实例变量方法,我建议创建自己的类)。
@my_var_a = "nothing happend to me"

def parse_set()
  @my_var_a = "my value changed"
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接