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?

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

虽然我正在尝试做更多的黑客(似乎不工作)。我想知道为什么变量值被冲掉了。我理解通过引用传递,所以我想知道如何保持该值在该变量中,而不被洗掉。

132942 次浏览

在传统术语中,Ruby是严格的值传递。但这不是你真正想要的。

Ruby没有任何纯的、非引用值的概念,所以您当然不能将一个值传递给方法。变量总是对象的引用。为了获得一个不会从您下面更改的对象,您需要dup或克隆您传递的对象,从而给出一个其他人没有引用的对象。(不过,这也不是无懈无击的——两种标准克隆方法都只做浅拷贝,因此克隆的实例变量仍然指向与原始克隆相同的对象。如果ivars引用的对象发生了变化,它仍然会显示在副本中,因为它引用的是相同的对象。)

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

Ruby是值传递。总是这样。没有例外。没有如果。少啰嗦

下面是一个简单的程序,说明了这一事实:

def foo(bar)
bar = 'reference'
end


baz = 'value'


foo(baz)


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

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


Q1:当你将一个新变量str赋值给一个值'foo'时,会发生什么?

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

enter image description here

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


Q2:使用=将现有变量str赋值给新对象时会发生什么?

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

enter image description here

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


Q3:当你将一个新变量=赋值给str时会发生什么?

str2 = str
str2.object_id # => 2002

enter image description here

答:一个名为str2的新标签被创建,它指向相同的对象作为str


Q4:如果strstr2引用的对象被改变了,会发生什么?

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

enter image description here

答:两个标签仍然指向同一个对象,但是对象本身已经发生了变化(它的内容已经改变为其他东西)。


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

这与第三季度和第四季度的情况基本相同;该方法获得自己的变量/ label (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

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

已经有一些很好的答案,但我想发布关于这个主题的一对权威的定义,但也希望有人能解释说权威Matz (Ruby的创造者)和David Flanagan在他们优秀的O'Reilly书Ruby编程语言中的意思。

[来自3.8.1:对象引用]

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

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

这一切对我来说都是有意义的,直到最后一段,特别是最后一句。往好了说是误导,往坏了说是混淆。对值传递引用的修改如何以任何方式改变底层对象?

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

=>有些人没有意识到它是引用,因为局部赋值可以优先,但它显然是引用传递

Ruby是解释性的。变量是对数据的引用,而不是数据本身。这便于对不同类型的数据使用相同的变量。

赋值lhs = rhs然后复制rhs上的引用,而不是数据。这与其他语言不同,比如C语言,赋值是从rhs复制数据到lhs。

对于函数调用,传递的变量,比如x,确实被复制到函数中的局部变量中,但x是一个引用。然后会有两个引用副本,它们都引用相同的数据。一个在调用者中,一个在函数中。

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

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

类似地,只要传入变量的原始引用是恰当的,则调用者看到的实例变量将是相同的。在对象的框架内,实例变量总是具有最新的引用值,无论这些引用值是由调用者提供的,还是在类传入的函数中设置的。

“按值调用”或“按引用调用”在这里被混淆了,因为在编译语言中“=”是一个数据副本。在这种解释语言中,'='是一个参考副本。在这个例子中,你有一个传入的引用,后面跟着一个引用副本,尽管'='破坏了传入的原始引用,然后人们谈论它就好像'='是一个数据副本。

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

然而,如果我们必须保留'='作为参照系,那么在赋值之前,我们确实可以看到传入的数据,赋值之后,当调用者的数据保持不变时,我们就再也看不到它了。在行为层面上,只要我们不认为传入的值是复合的,这就是按值传递的——因为我们不能在一次赋值中保留它的一部分而改变另一部分(因为赋值改变了引用,原始值超出了范围)。还有一个问题,在实例中,对象中的变量将被引用,就像所有变量一样。因此,我们将被迫谈论“按值传递引用”,并不得不使用相关的语句。

试试这个:——

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,它指向值object 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。< / p >

现在再看这个:——

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变量不包含值,但包含对值对象的引用。

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

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


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

Ruby使用“对象引用传递”

(使用Python术语。)

说Ruby使用“按值传递”或“按引用传递”并不是真正的描述性足够有帮助。我想现在大多数人都知道,这个术语(“值”vs“引用”)来自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中,如果你想在函数中修改一个对象,但在函数返回时忘记这些更改,那么你必须在对副本进行临时更改之前显式地创建一个对象的副本。

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

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

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

函数接收(并将访问)调用者在内存中使用的同一对象的引用。但是,它不接收调用方存储该对象的盒子;与pass-value-by-value一样,函数提供自己的方框,并为自己创建一个新变量。

结果行为实际上是引用传递和值传递的经典定义的组合。

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

是的,但是....

Ruby将引用传递给一个对象,因为Ruby中的所有东西都是对象,那么你可以说它是通过引用传递的。

我不同意这里的帖子声称它是通过价值,这对我来说似乎是迂腐的赛门铁克游戏。

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

> astringobject = "lowercase"


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


> puts astringobject
lowercase


> puts bstringobject
LOWERCASE

这意味着大多数时候,原始对象保持不变,给人一种ruby是“传递值”的感觉。

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

关于Ruby的“pass-reference-by-value"如何工作的理论,有很多很棒的答案。但以身作则,我能更好地学习和理解一切。希望这对你们有帮助。

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”。但随后我们将一个字符串对象“reference"赋值给酒吧,它有一个新的object_id。在这种情况下,foo内部的酒吧有一个不同的范围,无论我们在方法中传递什么,当我们重新分配它并将它指向内存中包含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对象的我们修改了内容,该变量所指向的。酒吧的作用域在方法内部仍然不同。

所以要注意如何处理传递给方法的变量。如果修改传入的变量-in-place (gsub!, replace, etc),然后在方法的名称中加上,如so "def foo!

注:

重要的是要记住foo的内部和外部的“bar”是“different" " 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