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


248
@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方法中的变量添加错误。当我在@user对象上执行保存时,我丢失了最初存储在lang_errors变量中的错误。

虽然我尝试做的事更多的是破解(似乎没有用)。我想了解为什么变量值会被冲掉。我了解按引用传递,所以我想知道如何将值保留在该变量中而不会被淘汰。


我还注意到,我能够保留克隆对象的值
希德

1
您应该看一下Abe Voelker的答案。但是,在围绕这个问题进行讨论之后,我会这样说。当将对象Foo传递给过程时,该对象的引用的副本将被传递,即按值传递。您不能更改Foo指向的对象,但是可以更改它指向的对象的内容。因此,如果您传递数组,则可以更改数组的内容,但不能更改所引用的数组。很高兴能够使用Foo的方法而不必担心搞乱对Foo的其他依赖关系。
bobbdelsol 2014年

Answers:


244

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

Ruby没有关于纯非引用值的任何概念,因此您当然不能将一个传递给方法。变量始终是对对象的引用。为了获得一个不会从您身下变出来的对象,您需要复制或克隆您传递的对象,从而为其他对象提供参考。(不过,即使这不是防弹技术-两种标准克隆方法都进行浅表复制,因此克隆的实例变量仍指向原始对象所指向的相同对象。如果ivars引用的对象发生突变,那将仍然显示在副本中,因为它引用的是相同的对象。)


88
Ruby是按值传递的。不,如果。没关系 没有例外。如果您想知道Ruby(或任何其他语言)是按引用传递还是按值传递,请尝试一下:def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"
约尔格W¯¯米塔格

95
@JörgWMittag:是的,但OP的困惑实际上不是严格意义上的CS含义的按值传递或按引用传递。他所缺少的是您传递的“值” 引用。我觉得只说“这是按价值传递”会显得很古怪,对OP不利,因为这实际上并不是他的意思。但是感谢您的澄清,因为它对将来的读者很重要,我应该将其包括在内。(我总是在提供更多信息和避免让人困惑之间感到困惑。)
Chuck

16
与@Jorg不同意。Ruby通过引用传递,他只是更改引用。改试试这个:def foo(bar)bar.replace'reference'end; baz ='值'; foo(巴兹); 提出“ Ruby is pass-by-#{baz}”
pguardiario 2012年

15
@pguardiario:我认为这实际上只是定义的问题。您使用的是您亲自提出的“通过引用”的定义,而Jörg使用的是传统的计算机科学定义。当然,这是我的事,告诉你如何使用的话-我只是在想解释一下术语是很重要的通常手段。在传统术语中,Ruby是按值传递的,但是值本身是引用。我完全理解您和OP为何喜欢将此视为通过引用-这不是该术语的传统含义。
Chuck 2012年

7
Ruby中的所有内容都是一个对象,因此Ruby既不按值传递也不按引用传递,至少在C ++中使用这些术语的意义上。“按对象引用传递”可能是描述Ruby功能的更好方法。最后,最好的选择是不要对这些术语中的任何一个使用过多的含义,而只是对实际发生的行为有一个很好的了解。
David Winiecki 2014年

424

其他回答者都是正确的,但是一个朋友要我向他解释这个问题,真正归结为Ruby如何处理变量,所以我想我会分享一些我为他写的简单图片/解释(抱歉并可能有些简化):


问题1:将新变量分配给str值会'foo'怎样?

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

在此处输入图片说明

答:str创建了一个称为的标签,该标签指向该对象'foo',对于该Ruby解释器,该标签恰好位于内存位置2000


问题2:使用将现有变量分配给str新对象时会发生什么=

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

在此处输入图片说明

答:标签str现在指向另一个对象。


Q3:当你将一个新的变量会发生什么=str

str2 = str
str2.object_id # => 2002

在此处输入图片说明

答:所谓的新标签str2被创建在点相同的对象str


问题4:如果引用strstr2更改的对象发生了什么情况?

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

在此处输入图片说明

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


这与原始问题有何关系?

基本上与第三季/第四季相同;该方法将获得自己的变量/标签(str2)的私有副本,该副本将传递给它(str)。它不能更改标签所str 指向的对象,但是可以更改它们都引用以包含其他对象的对象的内容

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

1
罗伯特·希顿(Robert Heaton)最近也就此发表了博客:robertheaton.com/2014/07/22/…–
Michael Renner

48

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中,如果您想修改函数内部的对象,但是在函数返回时忘记了这些更改,那么您必须在对副本进行临时更改之前显式创建该对象的副本。


您的答案是最好的。我也想发布一个简单的例子 def ch(str) str.reverse! end; str="abc"; ch(str); puts str #=> "cba"
方兴

这是正确的答案!此处对此也有很好的解释:robertheaton.com/2014/07/22/…。但是我仍然不明白的是:def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}"。这将显示“ Ruby是按值传递”。但是里面的变量foo被重新分配了。如果bar是数组,则重新分配不会生效baz。为什么?
haffla

我不明白你的问题。我认为您应该提出一个全新的问题,而不是在此处提出评论。
David Winiecki

42

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

Ruby是按价值传递的。总是。没有例外。不,如果。没关系

这是一个演示该事实的简单程序:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

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

15
@DavidJ .:“这里的错误是重新分配了本地参数(指向内存中的新位置)” –这不是一个错误,这是传递值定义。如果Ruby是按引用传递的,那么在被调用方中重新分配给本地方法参数绑定也将在调用方中重新分配给本地变量。没有。因此,Ruby是按价值传递的。如果更改可变值,则更改值是完全不相关的,这就是可变状态的工作方式。Ruby不是纯函数式语言。
约尔格W¯¯米塔格

5
感谢约尔格捍卫“价值传递”的真正定义。当价值实际上是参考时,这显然正在使我们的大脑融化,尽管红宝石总是通过价值传递。
道格拉斯

9
这是诡辩。“按价值传递”和“按引用传递”之间的实际区别是一种语义,而不是句法。您会说C数组是按值传递的吗?当然不是,即使当您将数组的名称传递给函数时,您传递的是不可变的指针,并且只有该指针所指向的数据才能被更改。显然,Ruby中的值类型是按值传递的,而引用类型是按引用传递的。
dodgethesteamroller 2013年

3
@dodgethesteamroller:Ruby和C都是按值传递的。总是。没有例外,不是没有。传递值和引用传递之间的区别在于,是将引用指向的值传递给参考值,还是传递引用。C 总是传递值,而不传递引用。该值可能会或可能不会是一个指针,但什么值无关,无论是在第一时间被传递。Ruby也总是传递值,而不传递引用。该值始终是一个指针,但同样,这是无关紧要的。
约尔格W¯¯米塔格

44
这个答案虽然严格说来是正确的,但却不是很有。传递的值始终是指针这一事实并不重要。对于试图学习的人来说,这是一个混乱的根源,而您的答案对解决这种混乱毫无帮助。

20

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

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

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

函数接收对调用者使用的内存中相同对象的引用(并将访问)。但是,它没有收到呼叫者在其中存储该对象的框;就像按值传递值一样,该函数提供了自己的框并为其创建了一个新变量。

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


“按值传递引用”与我用来描述Ruby的参数传递的短语相同。我认为这是最准确,最简洁的词组。
韦恩·康拉德

16

已经有了一些不错的答案,但是我想发布关于该主题的一对权威的定义,但也希望有人能解释权威Matz(Ruby的创建者)和David Flanagan在其出色的O'Reilly书中所说的含义,Ruby编程语言

[来自3.8.1:对象引用]

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

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

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


1
因为参考没有被修改;基础对象是。
dodgethesteamroller 2013年

1
因为对象是可变的。Ruby不是纯粹的功能语言。这与按引用传递与按值传递完全正交(除了在纯功能语言中,按值传递和按引用传递总是产生相同结果的事实,因此该语言可以在您不知情的情况下使用其中一个或两个)。
约尔格W¯¯米塔格

一个很好的例子是,如果您查看将散列传递给函数并进行合并的情况,而不是函数中的变量赋值!在传递的哈希值上。原始哈希最终被修改。
elc

16

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

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


这是唯一正确的答案,并提出了一些不错的陷阱:试试a ='foobar'; b = a; b [5] ='z',a和b都会被修改。
马丁2014年

2
@Martijn:您的论点并不完全有效。让我们逐条检查代码。a ='foobar'创建一个指向'foobar'的新引用。b = a创建与a相同数据的第二个引用。b [5] ='z'将b引用的值的第六个字符更改为'z'(巧合的是,a也引用了该值)。这就是为什么用您的术语“都被修改”,或更确切地说,就是“两个变量所引用的值都被修改”的原因。
Lukas_Skywalker 2014年

2
您没有使用bar方法中的引用做任何事情。您只是在修改引用所指向的对象,而不是引用本身。它们在Ruby中修改引用的唯一方法是分配。您不能通过在Ruby中调用方法来修改引用,因为方法只能在对象上调用,而引用不是Ruby中的对象。您的代码示例演示了Ruby具有共享的可变状态(此处不再讨论),但是它并不能说明按值传递和按引用传递之间的区别。
约尔格W¯¯米塔格

1
当有人问一种语言是否为“按引用传递”时,他们通常想知道何时将某些内容传递给函数,并且函数对其进行了修改,是否会在函数外部对其进行修改。对于Ruby,答案是“是”。该答案有助于证明@JörgWMittag的答案非常无用。
Toby 1 Kenobi 2015年

@ Toby1Kenobi:当然,您可以自由使用自己的“传递价值”一词的个人定义,该定义不同于普遍使用的定义。但是,如果您这样做了,就应该为人们感到困惑做好准备,尤其是如果您忽略了您所谈论的事实,即在某些方面与其他人截然不同的事实,那就很不一样了。特别是“传址引用”是关心是否传递可以修改的“东西”,而是以什么是“东西”是,特别是,无论是参考...
约尔格w ^ Mittag 2015年


2

试试这个: -

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都包含引用值对象2的相同object_id5。因此,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处。现在我们来看一个使用方法的示例。

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,并且在内部分配了值object 12之后,它包含object_id25。但是它不会更改调用方法中使用的标识符x引用的值。

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


1

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

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

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

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

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

同样,只要传入变量的原始引用完好无损,实例变量将与调用者看到的相同。在对象的框架内,实例变量始终具有最新的参考值,无论这些参考值是由调用方提供的还是在传递给类的函数中设置的。

由于对'='的混淆,此处混淆了“按值调用”或“按引用调用”。在已编译语言中,“ =”是数据副本。在这里,这种解释语言中的'='是参考副本。在该示例中,您有一个传入的引用,后跟一个通过“ =”引用的引用副本,它掩盖了传入的原始引用,然后人们在谈论它时,就好像“ =”是数据副本一样。

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

但是,如果我们必须保留“ =”作为参考框架,那么实际上我们确实可以看到传入的数据直到分配为止,然后在分配调用者的数据保持不变的情况下,再也无法看到传递的数据了。在行为层面上,这是按值传递的,只要我们不认为传递的值是复合值即可-因为我们将无法在单个分配中更改另一部分时保留其中的一部分(因为该分配更改参考,并且原始参考超出范围)。还有一个疣,在这种情况下,对象中的变量和所有变量一样都是引用。因此,我们将不得不谈论传递“按值引用”,并且不得不使用相关功能。


1

应该注意的是,您甚至不必使用“替换”方法来更改原始值。如果您为哈希分配哈希值之一,则您正在更改原始值。

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

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

我发现了另一件事。如果要传递数字类型,则所有数字类型都是不可变的,因此将按值传递。使用上述字符串的替换功能不适用于任何数字类型。
Don Carr 2014年

1
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

对,但是 ....

Ruby将引用传递给对象,并且由于ruby中的所有内容都是对象,因此您可以说它是通过引用传递的。

我不同意这里所说的按价值传递的贴子,对我来说这似乎像是书呆子般的游戏。

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

> astringobject = "lowercase"

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

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

这意味着在很多时候,原始对象保持不变,从而使红宝石看起来像“按值传递”。

当然,在设计自己的类时,了解此行为的细节对于功能行为,内存效率和性能都非常重要。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.