通过引用传递或通过复制传递 - Ruby 模块 [重复]

Posted

技术标签:

【中文标题】通过引用传递或通过复制传递 - Ruby 模块 [重复]【英文标题】:Pass by reference or pass by copy - Ruby Modules [duplicate] 【发布时间】:2013-11-21 10:40:42 【问题描述】:

我不确定将对象传递给模块方法时会发生什么。对象是通过引用还是通过副本传递的?就像在这个例子中一样:

module SampleModule
  def self.testing(o)
    o.test
  end
end

class SampleClass
  def initialize(a)
    @a = a
  end

  def test
    @a = @a + 1
  end
end

sample_object = SampleClass.new(2)
3.times do
  SampleModule.testing(sample_object)
end
p sample_object # => #<SampleClass:somehexvalue @a=5>

似乎是通过引用。这个真的不清楚。

【问题讨论】:

【参考方案1】: Ruby 中的

所有 变量都是对对象的引用。您不能像在 C、C++ 或 Perl 中那样选择“按值传递”与“按引用传递”。 Ruby 实际上强制按值传递,否则没有其他选择。但是,发送的值始终是对对象的引用。这有点像使用 C 或 C++,其中所有成员变量都是指针,或者使用 Perl,您必须始终使用引用,即使使用简单的标量也是如此。

我认为正是这种变量与对象数据的分离让您感到困惑。

几点:

变量分配从不覆盖可能指向同一对象的其他变量。这几乎就是按值传递的定义。但是,这并不能满足您对对象内容也受到保护的期望。

实例变量和容器中的项目(例如在Arrays 和Strings 中)是单独的变量,如果您发送一个容器,您可以直接更改其内容,因为您发送了对容器的引用,其中包含 same 变量作为其内容。我想这就是你所说的“似乎是传递引用”的意思

一些类 - 包括那些代表数字的类和Symbol - 是不可变的,即没有数字4的就地更改方法。但从概念上讲,您仍在将单个对象 4 的引用传递给例程(在底层,为了提高效率,Ruby 会将值 4 简单地编码在变量的内存分配中,但这是一个实现细节 - 值在这种情况下也是“指针”)。

使用SampleModule 使接近 到您似乎正在寻找的“按值传递”语义的最简单方法是clone 例程开始处的参数.请注意,这实际上并不会导致 Ruby 更改调用语义,只是在这种情况下,您可以从方法外部获得您似乎想要的安全假设(方法内的参数发生的任何事情都保留在方法内):


module SampleModule
  def self.testing(o)
    o = o.clone
    o.test
  end
end
从技术上讲,这应该是一个通用的深层克隆,但并不需要让您的示例接近按值传递。您可以调用SampleModule.testing( any_var_or_expression ) 并知道无论any_var_or_expression 在您的代码的其余部分中,关联的对象都不会被更改。

【讨论】:

也许你可以澄清o.clone不是按值传递,而是创建一个复制克隆对象实例变量的新对象并返回它 ;这意味着新对象的实例变量引用了克隆对象的各个实例变量,这是与“按值传递”概念的关键区别 @ProgNOMmers - 是的,我想我的意思是使用“深度克隆”,而我的示例仅因为属性是数字而有效。使用深层克隆,原则上您更接近“按值传递”的行为(就像您对方法内的参数所做的任何事情对调用者都不重要),尽管实际上当您拥有深层引用树时,它不是首先是这样一个干净的概念。 这是我在 Ruby 中怀念的一件事:没有一种明确的方法可以对对象进行深度复制;在大多数情况下,您必须自己实现它。 Ruby 是按值传递的。无需“引用”它。总是。没有例外。没有如果。没有但是。如果你不相信我,你可以自己检查一下:def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#baz" # Ruby is pass-by-value 是的。一个重要的补充是 assignment 运算符 = 所做的。它是 Ruby 中为数不多的改变变量的东西之一。因此,当您添加foo = 'No, Ruby is pass-by-reference.' 时,您会创建一个新的String 对象并将foo 指向它——此时您有两个引用,每个引用指向一个单独的对象。 但是将该行更改为 foo.replace('No, Ruby is pass-by-reference.') 更改对象 - 而不是变量 - 你会得到与你的 OP 匹配的相反行为。【参考方案2】:

如果你真的想了解词汇,Ruby 通过值传递对(可变)对象的引用:

def pass_it(obj)
  obj = 'by reference'
end

def mutate_it(obj)
  obj << ' mutated'
end

str = 'by value'

pass_it(str)
mutate_it(str)

puts str # by value mutated

您可以使用dupclone(请注意,两者都执行浅拷贝)和freeze 来解决可能由此产生的问题。

【讨论】:

【参考方案3】:

Ruby 中的一切都是通过引用传递的:

class Test
  attr_reader :a
  def initialize(a)
    @a = a
  end
end

s = "foo"
o = Test.new(s)
o.a << "bar"
o.a          #=> "foobar"
s            #=> "foobar"
o.a.equal? s #=> true

在您的代码中,将对象传递给模块方法这一事实并没有改变任何东西; sample_object 已经是对新对象 SampleClass.new(2) 的引用

【讨论】:

-1。这完全是错误的。 Ruby 是按值传递的。总是。没有例外。没有如果。没有但是。如果你不相信我,你可以自己检查一下:def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#baz" # Ruby is pass-by-value @JörgWMittag ,我开了个玩笑,但否决了答案不好...

以上是关于通过引用传递或通过复制传递 - Ruby 模块 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个 Ruby 方法通过引用传递它的参数

Ruby 是按值传递还是按引用传递? [复制]

通过引用传递 v. 通过指针传递——优点? [复制]

Python:变量何时通过引用传递,何时通过值传递? [复制]

JavaScript 是不是通过引用传递? [复制]

通过副本或参考传递?