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

@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对象将错误添加到update_lanugages方法中的lang_errorsvariables中。 当我在@user对象上执行保存时,我失去了最初存储在lang_errorsvariables中的错误。

虽然我试图做的更多的是一个黑客(这似乎并没有工作)。 我想了解为什么variables值被淘汰。 我明白通过参考,所以我想知道如何在这个variables的价值可以被保留,而不会被淘汰。

在传统的术语中, Ruby是严格按照价值传递的 。 但是这不是你在这里问的。

Ruby没有任何纯粹的非引用值的概念,所以你当然不能把它传给一个方法。 variables始终是对象的引用。 为了得到一个不会从你下面改变的对象,你需要复制或克隆你传递的对象,从而给一个没有其他人参考的对象。 (尽pipe这并不是简洁的说法 – 两种标准的克隆方法都是做一个浅拷贝,所以克隆的实例variables仍然指向与原始文件相同的对象,如果ivars引用的对象发生变异,仍然显示在副本中,因为它引用了相同的对象。)

其他回答者都是正确的,但是一个朋友问我要向他解释这个问题,以及它是如何处理variables的,所以我想我会分享一些我为他写的简单的图片/解释(道歉的长度可能还有些简单化):


Q1:当你将一个新的variablesstr赋值为'foo'什么?

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

在这里输入图像描述

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


Q2:当你使用=来把现有的variablesstr分配给一个新的对象时会发生什么?

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

在这里输入图像描述

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


Q3:当你分配一个新variables= str时会发生什么?

 str2 = str str2.object_id # => 2002 

在这里输入图像描述

答:创build一个名为str2的新标签,指向与str 相同的对象


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

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

在这里输入图像描述

答:这两个标签仍然指向相同的对象,但该对象本身已经发生了变化(其内容已经变成了别的东西)。


这与原来的问题有什么关系?

这与第三季度/第四季度基本相同; 该方法获取它自己的私有副本的variables/标签( str2 )被传递给它( str )。 它不能改变标签str 指向哪个对象,但是它可以改变它们所引用的对象的内容包含else:

 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 

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

Ruby是传值的。 总是。 没有例外。 没有ifs。 没有但是。

这是一个简单的程序,它展示了这个事实:

 def foo(bar) bar = 'reference' end baz = 'value' foo(baz) puts "Ruby is pass-by-#{baz}" # Ruby is pass-by-value 

Ruby使用“通过对象引用”

(使用Python的术语。)

要说Ruby使用“按价值传递”或“通过引用传递”并不足以说明问题是有帮助的。 我想现在大多数人都知道,这个术语(“价值”vs“参考”)来自C ++。

在C ++中,“按值传递”意味着函数获取variables的副本,对副本的任何更改都不会更改原始内容。 对于对象也是如此。 如果按值传递一个对象variables,那么整个对象(包括其所有成员)将被复制,而对成员的任何更改都不会更改原始对象上的那些成员。 (根据值传递一个指针是不同的,但是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; ax = 5; inc(a); printf("in main: %d\n", ax); // => 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 ++中,“通过引用传递”意味着函数可以访问原始variables。 它可以分配一个全新的文字整数,原来的variables也会有这个值。

 void replace(A &arg) { A newA; newA.x = 10; arg = newA; printf("in replace: %d\n", arg.x); } int main() { A a; ax = 5; replace(a); printf("in main: %d\n", ax); return 0; } 

输出:

 in replace: 10 in main: 10 

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

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

  • 在函数内部,任何对象的成员都可以赋值给它们,并且在函数返回后这些变化将会持续。
  • 在函数内部,将一个全新的对象赋值给variables会导致variables停止引用旧的对象。 但函数返回后,原来的variables仍然会引用旧的对象。

因此,Ruby不使用C ++意义上的“通过引用”。 如果是这样,那么将一个新的对象分配给一个函数内的variables会导致旧的对象在函数返回后被遗忘。

 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 ax = 1 puts ax # 1 inc a # 2 puts ax # 2 replace a # 3 puts ax # 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中,如果你想修改一个函数内部的对象,但是当函数返回时忘记这些修改,那么你必须在临时修改副本之前明确地创build一个对象的副本。

现在已经有了一些很好的答案,但是我想发表一些关于这个主题的权威的定义,但是也希望有人能够解释当局Matz(Ruby的创造者)和David Flanagan在他们出色的O'Reilly书中的意思, Ruby编程语言

[来自3.8.1:对象引用]

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

因为对象引用被传递给方法,所以方法可以使用这些引用来修改底层对象。 这些修改在方法返回时可见。

这一切对我来说都是有道理的,直到最后一段, 特别是最后一句。 这是最好的误导,更糟的是混乱。 无论如何,对传值引用的修改如何改变底层对象?

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

Ruby是通过引用。 总是。 没有例外。 没有ifs。 没有但是。

这是一个简单的程序,它展示了这个事实:

 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因为object_id的(内存地址)总是相同,所以Ruby是通过引用传递的2279146940;)

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

=>有些人没有意识到这是参考,因为本地分配可以优先,但它显然是通过引用

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

这可以被称为“ 通过价值参考 ”。 这篇文章有我读过的最好的解释: http : //robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

按值传递参考可以简要地解释如下:

一个函数接收一个引用(并将访问)调用者使用的内存中的同一个对象。 但是,它不会收到调用方存储该对象的框; 如通过值的函数,函数提供了自己的方框,并为自己创build一个新的variables。

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

参数是原始参考的副本。 所以,您可以更改值,但不能更改原始参考。

Ruby被解释。 variables是对数据的引用,而不是数据本身。 这有利于为不同types的数据使用相同的variables。

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

所以对于函数调用,传递的variables,比如x,确实被复制到函数中的局部variables中,但是x是引用。 然后将有两个引用副本,都引用相同的数据。 一个将在呼叫者,一个在function。

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

相反,在x上使用.replace方法会导致ruby执行数据复制。 如果在任何新的分配之前使用replace,则调用者也将在其版本中看到数据改变。

类似地,只要原始引用是通过variables传递的,实例variables将与调用者所看到的相同。 在一个对象的框架内,实例variables总是具有最新的引用值,不pipe这些引用值是由调用者提供的,还是在类被传入的函数中设置的。

由于对'='的混淆,'按价值调用'或'按参考调用'在这里混淆。在编译语言中,'='是数据副本。 在这个解释性语言中,“=”是参考副本。 在这个例子中,你有引用的参考,然后是参考副本,虽然'='会破坏引用中传入的原始内容,然后像“=”这样的人谈论它是一个数据副本。

为了与定义一致,我们必须保留“.replace”,因为它是数据副本。 从'替代'的angular度来看,这确实是经过参照。 而且,如果我们在debugging器中通过,我们看到引用被传入,因为variables是引用。

但是,如果我们必须保留“=”作为参考框架,那么事实上我们确实会看到传递的数据,直到分配完毕,然后在调用者的数据保持不变的情况下,我们不会再看到它。 在行为层面上,只要我们不认为传入的价值是合成的,我们将无法保留其中的一部分,而在一个任务中更改其他部分(如分配改变参考和原来的范围)。 还有一个疣,在这种情况下,对象中的variables将是引用,所有variables也是如此。 因此,我们将被迫谈论通过“价值参考”,并必须使用相关的语言。

尝试这个: –

 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.所以,Rubyvariables包含object_id来引用值对象。

下面的做法也给错误: –

 c #=> error 

但是这样做不会给错误:

 5.object_id #=> 11 c = 5 #=> value object 5 provides return type for variable c and saves 5.object_id ie 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 

这里标识一个返回值对象11,其对象id是23,即object_id 23是标识符a,现在我们看一个使用方法的例子。

 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 

arg在foo中被赋值为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,在内部赋值给value对象12之后,它包含object_id 25.但是它不改变调用方法中使用的标识符x引用的值。

因此,Ruby是通过值传递,Rubyvariables不包含值,但包含对值对象的引用。

应该指出,你甚至不必使用“replace”方法来改变数值的原始值。 如果您为散列指定了其中一个散列值,则您正在更改原始值。

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