Ruby 是通过引用传递还是按值传递?

Is Ruby pass by reference or by value?

提问人:Sid 提问时间:12/9/2009 最后编辑:isomorphismesSid 更新时间:6/16/2023 访问量:140697

问:

@user.update_languages(params[:language][:language1], 
                       params[:language][:language2], 
                       params[:language][:language3])
lang_errors = @user.errors
logger.debug "--------------------LANG_ERRORS----------101-------------" 
                + lang_errors.full_messages.inspect

if params[:user]
  @user.state = params[:user][:state]
  success = success & @user.save
end
logger.debug "--------------------LANG_ERRORS-------------102----------" 
                + lang_errors.full_messages.inspect

if lang_errors.full_messages.empty?

@user对象将错误添加到方法中的变量中。 当我对对象执行保存时,我丢失了最初存储在变量中的错误。lang_errorsupdate_lanugages@userlang_errors

尽管我试图做的更像是一种黑客攻击(这似乎不起作用)。我想知道为什么变量值被洗掉了。我理解通过引用传递,所以我想知道如何在不被洗掉的情况下将值保存在该变量中。

Ruby-on-Rails Ruby 按引用传递

评论

0赞 Sid 12/9/2009
我还注意到,我能够在克隆的对象中保留该值
2赞 bobbdelsol 1/10/2014
你应该看看 Abe Voelker 的回答。但是在围绕这个街区跑了一圈之后,我会这样说。当您将对象 Foo 传递给过程时,将传递对该对象的引用的副本,bar,按值传递。您无法更改 Foo 指向的对象,但可以更改它指向的对象的内容。因此,如果传递数组,则可以更改数组的内容,但不能更改所引用的数组。很高兴能够使用 Foo 的方法,而不必担心弄乱 Foo 上的其他依赖项。

答:

264赞 Chuck 12/9/2009 #1

在传统术语中,Ruby 是严格按值传递的。然而,Ruby 中的所有内容都是一个对象,因此 Ruby 的行为可能看起来像是按引用传递的语言。

Ruby 打破了“按引用传递”或“按值传递”的传统定义,因为一切都是一个对象,当它传递事物时,它会传递对对象的引用。所以实际上,Ruby 可以归类为第三种类型的语言,我们可以称之为“通过对象引用传递”。在计算机科学术语的严格定义中,Ruby 是按值传递的。

Ruby 没有任何纯非引用值的概念,因此您不能将一个值传递给方法。变量始终是对对象的引用。为了获得一个不会从你下面改变的对象,你需要复制或克隆你传递的对象,从而给出一个没有其他人引用的对象。然而,即使这样也不是万无一失的:两种标准的克隆方法都执行浅拷贝,因此克隆的实例变量仍然指向与原始克隆相同的对象。如果 ivar 引用的对象发生突变,它仍将显示在副本中,因为它引用的是相同的对象。

评论

94赞 Jörg W Mittag 4/26/2012
Ruby 是按值传递的。没有如果。没有但是。没有例外。如果你想知道 Ruby(或任何其他语言)是按引用传递还是按值传递,只需尝试一下: .def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"
113赞 Chuck 4/26/2012
@JörgWMittag:是的,但 OP 的混淆实际上不是严格意义上的按值传递或按引用传递。他缺少的是,你传递的“值”引用。我觉得仅仅说“这是按价值传递的”是迂腐的,并且对 OP 有害,因为这实际上不是他的意思。但感谢您的澄清,因为它对未来的读者很重要,我应该包括它。(我总是在包含更多信息和不让人们感到困惑之间左右为难。
19赞 pguardiario 8/18/2012
不同意@Jorg。Ruby 是通过引用传递的,他只是更改了引用。试试这个: def foo(bar) bar.replace 'reference' end;baz = '值';foo(baz);把 “Ruby is pass-by-#{baz}”
18赞 Chuck 8/21/2012
@pguardiario:我认为这实际上只是一个定义问题。你使用的是你个人提出的“引用传递”的定义,而 Jörg 使用的是传统的计算机科学定义。当然,告诉你如何使用单词与我无关——我只是认为解释这个词通常的含义很重要。在传统术语中,Ruby 是按值传递的,但值本身就是引用。我完全理解为什么你和 OP 喜欢将其视为传递引用——这不是该术语的传统含义。
7赞 David Winiecki 5/2/2014
Ruby 中的所有内容都是一个对象,因此 Ruby 既不按值传递,也不按引用传递,至少在 C++ 中使用这些术语的意义上是这样。“通过对象引用传递”可能是描述 Ruby 功能的更好方式。不过,最终,最好的选择可能是不要对这些术语中的任何一个赋予太多意义,而只是对真正发生的行为有一个很好的理解。
40赞 Jörg W Mittag 4/26/2012 #2

Ruby 是通过引用传递还是按值传递?

Ruby 是按值传递的。总是。没有例外。没有如果。没有但是。

这里有一个简单的程序来演示这一事实:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value

评论

15赞 Jörg W Mittag 5/22/2012
@DavidJ.: “这里的错误是重新分配了本地参数(指向内存中的新位置)”——这不是一个错误,这就是按值传递的定义。如果 Ruby 是按引用传递的,那么重新分配给被调用方中的局部方法参数绑定也会重新分配调用方中的局部变量绑定。但事实并非如此。因此,Ruby 是按值传递的。事实上,如果你改变一个可变值,这个值就会改变,这是完全无关紧要的,这就是可变状态的工作原理。Ruby 不是一种纯粹的函数式语言。
7赞 Douglas 2/19/2013
感谢 Jörg 捍卫了“按价值传递”的真正定义。当值实际上是一个参考时,它显然正在融化我们的大脑,尽管 ruby 总是传递副值。
10赞 dodgethesteamroller 10/29/2013
这是诡辩。“按值传递”和“按引用传递”之间的实际区别是语义上的,而不是句法上的。你会说 C 数组是按值传递的吗?当然不是,即使当你将数组的名称传递给一个函数时,你传递的是一个不可变的指针,并且只有指针所引用的数据才能被改变。显然,Ruby 中的值类型是按值传递的,而引用类型是按引用传递的。
3赞 Jörg W Mittag 10/29/2013
@dodgethesteamroller:Ruby 和 C 都是按值传递的。总是。没有例外,不是如果,没有,而是。按值传递和按引用传递之间的区别在于是传递引用指向的值还是传递引用。C 始终传递值,从不传递引用。该值可能是指针,也可能不是指针,但值是什么与它是否首先被传递无关。Ruby 也总是传递值,而不是引用。该值始终是一个指针,但同样,这是无关紧要的。
53赞 12/4/2013
这个答案虽然严格来说是正确的,但不是很有。传递的值始终是指针这一事实并非无关紧要。对于试图学习的人来说,这是一个困惑的根源,你的回答绝对无助于解决这种困惑。
478赞 Abe Voelker 6/11/2012 #3

其他回答者都是正确的,但一个朋友让我向他解释这一点,它真正归结为 Ruby 如何处理变量,所以我想我会分享一些我为他写的简单图片/解释(很抱歉篇幅太长了,可能有些过于简单化):


问题 1:将新变量赋值为 ?str'foo'

str = 'foo'
str.object_id # => 2000

在此处输入图像描述

答:创建一个名为 的标签,指向对象,对于这个 Ruby 解释器的状态,它恰好位于内存位置。str'foo'2000


问题 2:当您使用 ?str=

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002

在此处输入图像描述

答:标签现在指向不同的对象。str


问题 3:将新变量分配给 ?=str

str2 = str
str2.object_id # => 2002

在此处输入图像描述

答:将创建一个名为 的新标签,该标签指向与 相同的对象str2str


问题 4:如果对象被引用并被更改,会发生什么情况?strstr2

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002

在此处输入图像描述

答:两个标签仍然指向同一个对象,但该对象本身已经变异(其内容已更改为其他内容)。


这与最初的问题有什么关系?

这与第三季度/第四季度的情况基本相同;该方法获取传递给它 () 的变量 / 标签 () 的私有副本。它不能更改标签指向的对象,但可以更改它们都引用的对象的内容,否则:str2strstr

str = 'foo'

def mutate(str2)
  puts "str2: #{str2.object_id}"
  str2.replace 'bar'
  str2 = 'baz'
  puts "str2: #{str2.object_id}"
end

str.object_id # => 2004
mutate(str) # str2: 2004, str2: 2006
str # => "bar"
str.object_id # => 2004

评论

3赞 Michael Renner 12/11/2014
罗伯特·希顿(Robert Heaton)最近也在博客上谈到了这一点:robertheaton.com/2014/07/22/......
9赞 Rael Gugelmin Cunha 6/22/2013 #4

参数是原始引用的副本。因此,您可以更改值,但不能更改原始引用。

17赞 Dominick 8/6/2013 #5

已经有一些很好的答案了,但我想发布关于这个主题的两位权威的定义,但也希望有人可以解释一下 Matz(Ruby 的创建者)和 David Flanagan 在他们优秀的 O'Reilly 著作《The Ruby Programming Language》中的意思。

[从 3.8.1 开始:对象引用]

在 Ruby 中将对象传递给方法时,它是传递给该方法的对象引用。它不是对象本身,也不是对对象的引用。另一种说法是,方法参数是按值而不是通过引用传递的,但传递的值是对象引用

由于对象引用传递给方法,因此方法可以使用这些引用来修改基础对象。然后,当方法返回时,这些修改是可见的。

直到最后一段,尤其是最后一句话,这对我来说都是有道理的。这充其量是误导性的,更糟糕的是令人困惑。以任何方式,对按值传递引用的修改如何更改基础对象?

评论

1赞 dodgethesteamroller 10/29/2013
因为引用没有被修改;基础对象是。
1赞 Jörg W Mittag 9/19/2014
因为对象是可变的。Ruby 不是一种纯粹的函数式语言。这与按引用传递与按值传递是完全正交的(除了在纯函数式语言中,按值传递和按引用传递总是产生相同的结果,因此该语言可以在您不知情的情况下使用其中之一或两者)。
2赞 elc 7/21/2015
一个很好的例子是,如果你看的是将哈希传递给函数并执行合并的情况,而不是函数中的变量赋值!在传递的哈希上。原始哈希值最终被修改。
15赞 Brett Allred 9/18/2013 #6

Ruby 是通过引用传递还是按值传递?

Ruby 是按引用传递的。总是。没有例外。没有如果。没有但是。

这里有一个简单的程序来演示这一事实:

def foo(bar)
  bar.object_id
end

baz = 'value'

puts "#{baz.object_id} Ruby is pass-by-reference #{foo(baz)} because object_id's (memory addresses) are always the same ;)"

=> 2279146940 Ruby 是按引用传递的2279146940,因为 object_id(内存地址)始终是相同的;)

def bar(babar)
  babar.replace("reference")
end

bar(baz)

puts "some people don't realize it's reference because local assignment can take precedence, but it's clearly pass-by-#{baz}"

=> 有些人没有意识到它是引用,因为本地分配可以优先,但它显然是按引用传递的

评论

0赞 Martijn 2/10/2014
这是唯一的正确答案,并提出了一些不错的问题: 尝试 a = 'foobar' ;b = 一个 ;b[5] = 'z',则 a 和 b 都会被修改。
2赞 Lukas_Skywalker 5/28/2014
@Martijn:你的论点并不完全有效。让我们逐条查看您的代码语句。a = 'foobar' 创建一个指向 'foobar' 的新引用。b[5] = 'z' 将 b 引用的值的第六个字符更改为 'z'(巧合的是,a 引用的值也发生了变化)。这就是为什么用你的术语来说“两者都被修改”,或者更准确地说,为什么“两个变量引用的值都被修改”。
2赞 Jörg W Mittag 2/16/2015
您没有对方法中的引用执行任何操作。您只是在修改引用指向的对象,而不是引用本身。在 Ruby 中修改引用的唯一方法是赋值。您不能通过调用 Ruby 中的方法来修改引用,因为方法只能在对象上调用,而引用在 Ruby 中不是对象。您的代码示例演示了 Ruby 具有共享的可变状态(这里不讨论),但是它没有说明按值传递和按引用传递之间的区别。bar
2赞 Toby 1 Kenobi 12/9/2015
当有人问一种语言是否是“通过引用传递”时,他们通常想知道当你将某些东西传递给一个函数并且函数修改它时,它是否会在函数外部被修改。对于 Ruby 来说,答案是肯定的。这个答案有助于证明,@JörgWMittag的答案是极其无益的。
0赞 Jörg W Mittag 12/9/2015
@Toby1Kenobi:当然,您可以自由地使用自己对“按价值传递”一词的个人定义,这与常见的、广泛使用的定义不同。然而,如果你这样做,你应该准备好让人们感到困惑,特别是如果你忽略了这样一个事实,即你正在谈论一个非常不同的事实,在某些方面甚至与其他人相反。特别是,“引用传递”并不关心传递的“某物”是否可以修改,而是关心该“某物”是什么,特别是它是否是引用......
1赞 user3446498 3/21/2014 #7

Ruby 被解释。变量是对数据的引用,但不是数据本身的引用。这有助于对不同类型的数据使用相同的变量。

然后,lhs = rhs 的赋值将复制 rhs 上的引用,而不是数据。这在其他语言(例如 C)中有所不同,其中赋值将数据从 rhs 复制到 lhs。

因此,对于函数调用,传递的变量(例如 x)确实被复制到函数中的局部变量中,但 x 是引用。然后,将有两个引用的副本,这两个副本都引用了相同的数据。一个在调用方中,一个在函数中。

然后,函数中的赋值会将新引用复制到函数的 x 版本。在此之后,调用方的 x 版本保持不变。它仍然是对原始数据的引用。

相反,在 x 上使用 .replace 方法将导致 ruby 执行数据复制。如果在任何新赋值之前使用 replace,那么调用方实际上也会看到其版本中的数据更改。

同样,只要原始引用对传入的变量处于有利地位,实例变量就会与调用者看到的相同。在对象的框架内,实例变量始终具有最新的引用值,无论这些引用值是由调用方提供的,还是在类传入的函数中设置的。

由于混淆了“=”,此处的“按值调用”或“按引用调用”被混淆了,在编译语言中,“=”是数据副本。在这种解释语言中,“=”是一个参考副本。在这个例子中,你让引用传入,然后通过“=”来引用副本,它破坏了原始传入的引用,然后人们谈论它,就好像“=”是一个数据副本一样。

为了与定义保持一致,我们必须保留“.replace”,因为它是数据副本。从“.replace”的角度来看,我们看到这确实是通过引用传递的。此外,如果我们在调试器中遍历,我们会看到引用被传入,因为变量是引用。

但是,如果我们必须将“=”作为参考系,那么在赋值之前,我们确实可以看到传入的数据,然后在赋值后我们再也看不到它,而调用者的数据保持不变。在行为层面上,只要我们不认为传入的值是复合值,这就是按值传递 - 因为我们将无法在单个赋值中更改另一部分时保留其中的一部分(因为该赋值更改了引用,而原始赋值超出了范围)。还会有一个疣,因为对象中的实例变量将是引用,所有变量也是如此。因此,我们将被迫谈论“按值传递引用”,并且必须使用相关的语言。

3赞 Alok Anand 3/25/2014 #8

试试这个:--

1.object_id
#=> 3

2.object_id
#=> 5

a = 1
#=> 1
a.object_id
#=> 3

b = 2
#=> 2
b.object_id
#=> 5

标识符 A 包含值对象 1 的object_id 3,标识符 B 包含值对象 2 的object_id 5。

现在这样做:--

a.object_id = 5
#=> error

a = b
#value(object_id) at b copies itself as value(object_id) at a. value object 2 has object_id 5
#=> 2

a.object_id 
#=> 5

现在,a 和 b 都包含相同的object_id 5,它指的是值对象 2。 因此,Ruby 变量包含引用值对象object_ids。

执行以下操作也会产生错误:--

c
#=> error

但这样做不会产生错误:--

5.object_id
#=> 11

c = 5
#=> value object 5 provides return type for variable c and saves 5.object_id i.e. 11 at c
#=> 5
c.object_id
#=> 11 

a = c.object_id
#=> object_id of c as a value object changes value at a
#=> 11
11.object_id
#=> 23
a.object_id == 11.object_id
#=> true

a
#=> Value at a
#=> 11

这里标识符 a 返回对象 11 的值,其对象 ID 为 23,即 object_id 23 位于标识符 a,现在我们看到一个使用 method 的例子。

def foo(arg)
  p arg
  p arg.object_id
end
#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23

foo 中的 arg 被赋值为 x。 它清楚地表明参数是由值 11 传递的,而值 11 本身就是一个具有唯一对象 ID 23 的对象。

现在也看到这个:--

def foo(arg)
  p arg
  p arg.object_id
  arg = 12
  p arg
  p arg.object_id
end

#=> nil
11.object_id
#=> 23
x = 11
#=> 11
x.object_id
#=> 23
foo(x)
#=> 11
#=> 23
#=> 12
#=> 25
x
#=> 11
x.object_id
#=> 23

在这里,标识符 arg 首先包含object_id 23 来引用 11,在使用值对象 12 进行内部赋值后,它包含object_id 25。但它不会更改调用方法中使用的标识符 x 引用的值。

因此,Ruby 是按值传递的,而 Ruby 变量不包含值,但包含对值对象的引用。

3赞 Don Carr 3/27/2014 #9

需要注意的是,您甚至不必使用“replace”方法来更改原始值。如果为哈希分配其中一个哈希值,则将更改原始值。

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"

评论

0赞 Don Carr 3/29/2014
我发现的另一件事。如果要传递数值类型,则所有数值类型都是不可变的,因此 ARE 按值传递。使用上述字符串的 replace 函数不适用于任何数值类型。
64赞 David Winiecki 5/2/2014 #10

Ruby 使用“按对象引用传递”

(使用 Python 的术语。

说 Ruby 使用“按值传递”或“按引用传递”并不足以描述性,不足以提供帮助。我认为现在大多数人都知道,这个术语(“值”与“引用”)来自 C++。

在 C++ 中,“按值传递”意味着函数获取变量的副本,对副本的任何更改都不会更改原始副本。对象也是如此。如果按值传递对象变量,则整个对象(包括其所有成员)将被复制,并且对成员的任何更改都不会更改原始对象上的这些成员。(如果你按值传递指针,但 Ruby 无论如何都没有指针,那就不同了,AFAIK。

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

输出:

in inc: 6
in main: 5
in inc: 1
in main: 1

在 C++ 中,“通过引用传递”意味着函数可以访问原始变量。它可以分配一个全新的文字整数,然后原始变量也将具有该值。

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

输出:

in replace: 10
in main: 10

如果参数不是对象,Ruby 使用按值传递(在 C++ 意义上)。但是在 Ruby 中,一切都是一个对象,所以在 Ruby 中实际上没有 C++ 意义上的传递值。

在 Ruby 中,使用“通过对象引用传递”(使用 Python 的术语):

  • 在函数内部,对象的任何成员都可以为其分配新值,这些更改将在函数返回后保留。
  • 在函数内部,将一个全新的对象分配给变量会导致变量停止引用旧对象。但是在函数返回后,原始变量仍将引用旧对象。

因此,Ruby 不使用 C++ 意义上的“引用传递”。如果是这样,那么将新对象分配给函数内部的变量将导致在函数返回后忘记旧对象。

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

输出:

1
2
2
3
2

1
2
1

* 这就是为什么,在 Ruby 中,如果你想修改函数中的对象,但在函数返回时忘记了这些更改,那么你必须在对副本进行临时更改之前显式创建对象的副本。

评论

1赞 Fangxing 7/6/2017
你的答案是最好的。我还想发布一个简单的例子def ch(str) str.reverse! end; str="abc"; ch(str); puts str #=> "cba"
0赞 haffla 10/6/2017
这是正确答案!这里也很好地解释了这一点:robertheaton.com/2014/07/22/....但我仍然不明白的是:.这将打印“Ruby is pass-by-value”。但是里面的变量被重新分配了。如果是一个数组,则重新赋值不会影响 。为什么?def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"foobarbaz
0赞 David Winiecki 10/12/2017
我不明白你的问题。我认为你应该问一个全新的问题,而不是在这里发表评论。
0赞 aka 7/1/2021
@haffla 正在将 bar 重新分配给字符串对象“reference”;因此,bar 和 baz 不再引用同一个 String 对象。添加几个打印object_id语句以仔细查看。 重新分配前的柱线:7864800 重新分配后的柱线:7864620 Ruby 是按值传递的 baz:7864800def foo(bar) bar = 'reference' end;def foo(bar) puts "bar before reassign: #{bar.object_id}"; bar = 'reference'; puts "bar after reassign: #{bar.object_id}"; end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"; puts "baz: #{baz.object_id}";
26赞 Ari 3/1/2015 #11

Ruby 在严格意义上是按值传递的,但值是引用。

这可以称为“按值传递引用”。这篇文章有我读过的最好的解释:http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

按值传递引用可以简要解释如下:

函数接收对调用方使用的内存中同一对象的引用(并将访问)。但是,它不会接收调用方存储此对象的框;与逐个值传递一样,该函数提供自己的框并为自己创建一个新变量。

由此产生的行为实际上是按引用传递和按值传递的经典定义的组合。

评论

0赞 Wayne Conrad 8/23/2016
“按值传递引用”是我用来描述 Ruby 参数传递的短语。我认为这是最准确、最简洁的短语。
0赞 Pablo 10/9/2020
这篇文章帮助我理解了 Ruby 是通过引用的值传递的:launchschool.com/blog/object-passing-in-ruby
2赞 Ayman Hussain 12/21/2017 #12
Two references refer to same object as long as there is no reassignment. 

同一对象中的任何更新都不会引用新内存,因为它仍位于同一内存中。 以下是几个例子:

    a = "first string"
    b = a



    b.upcase! 
    => FIRST STRING
    a
    => FIRST STRING

    b = "second string"


a
    => FIRST STRING
    hash = {first_sub_hash: {first_key: "first_value"}}
first_sub_hash = hash[:first_sub_hash]
first_sub_hash[:second_key] = "second_value"

    hash
    => {first_sub_hash: {first_key: "first_value", second_key: "second_value"}}

    def change(first_sub_hash)
    first_sub_hash[:third_key] = "third_value"
    end

    change(first_sub_hash)

    hash
    =>  {first_sub_hash: {first_key: "first_value", second_key: "second_value", third_key: "third_value"}}
0赞 Tomm P 4/15/2020 #13

是的,但是....

Ruby 传递对对象的引用,因为 ruby 中的所有内容都是对象,那么你可以说它是通过引用传递的。

我不同意这里的帖子声称它是按价值传递的,这对我来说似乎是迂腐的、对称的游戏。

然而,实际上它“隐藏”了行为,因为大多数操作 ruby 都提供了“开箱即用”——例如字符串操作,生成对象的副本:

> astringobject = "lowercase"

> bstringobject = astringobject.upcase
> # bstringobject is a new object created by String.upcase

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

这意味着在很多时候,原始对象保持不变,给人一种 ruby 是“按值传递”的外观。

当然,在设计自己的类时,了解这种行为的细节对于功能行为、记忆效率和性能都很重要。

2赞 konung 1/18/2021 #14

很多很好的答案深入探讨了 Ruby 的“按值传递引用”如何工作的理论。但我通过榜样更好地学习和理解一切。希望这对您有所帮助。

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar =  "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 80 # <-----
bar (value) after foo with object_id 60 # <-----

正如你所看到的,当我们输入方法时,我们的栏仍然指向字符串“value”。但随后我们给 bar 分配了一个字符串对象“引用”,它有一个新object_id。在这种情况下,foo 内部的 bar 具有不同的作用域,并且无论我们在方法中传递什么,都不再由 bar 访问,因为我们重新分配它并将其指向内存中保存 String“reference”的新位置。

现在考虑同样的方法。唯一的区别是方法内部的 do

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar.replace "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 60 # <-----
bar (reference) after foo with object_id 60 # <-----

注意到区别了吗?我们在这里所做的是:我们修改了变量指向的 String 对象的内容。方法内部的 bar 范围仍然不同。

因此,要小心如何处理传递给方法的变量。如果你修改了传入的变量(gsub!、replace等),那么在方法的名称中用砰!表示,就像“def foo!”

附言:

重要的是要记住,foo 内部和外部的“bar”是“不同的”“bar”。它们的范围是不同的。在该方法中,您可以将“bar”重命名为“club”,结果将是相同的。

我经常看到变量在方法内部和外部重用,虽然这很好,但它会降低代码的可读性,并且恕我直言,这是一种代码味道。我强烈建议不要做我在上面的例子中所做的:)而是这样做

def foo(fiz)
  puts "fiz (#{fiz}) entering foo with object_id #{fiz.object_id}"
  fiz =  "reference"
  puts "fiz (#{fiz}) leaving foo with object_id #{fiz.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
fiz (value) entering foo with object_id 60
fiz (reference) leaving foo with object_id 80
bar (value) after foo with object_id 60