Ruby通过引用或值传递吗?

时间:2009-12-09 07:05:06

标签: ruby-on-rails ruby pass-by-reference

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

虽然我试图做的事情更多的是黑客攻击(似乎没有用)。我想了解为什么变量值被淘汰了。我理解通过引用传递,所以我想知道如何在不被淘汰的情况下将该值保存在该变量中。

14 个答案:

答案 0 :(得分:411)

其他的回答者都是正确的,但是一位朋友让我向他解释这个问题以及它真正归结为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现在指向另一个对象。


问题3:将新变量=分配给str时会发生什么?

str2 = str
str2.object_id # => 2002

enter image description here

答:创建了一个名为str2的新标签,该标签指向同一对象str


问题4:如果strstr2引用的对象发生变化会怎样?

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

enter image description here

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


这与原始问题有什么关系?

它与Q3 / Q4中的情况基本相同;该方法获取自己传递给它的变量/标签(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 :(得分:231)

在传统术语中,Ruby is strictly pass-by-value。但那不是你在这里问的那个。

Ruby没有任何纯粹的非引用值的概念,所以你当然不能将一个传递给一个方法。变量始终是对象的引用。为了获得一个不会从你下面改变的对象,你需要复制或克隆你传递的对象,从而给出一个没有其他人可以引用的对象。 (尽管这不是防弹的 - 但两种标准克隆方法都做了浅拷贝,因此克隆的实例变量仍然指向与原件相同的对象。如果ivars引用的对象发生变异,那么仍然显示在副本中,因为它引用了相同的对象。)

答案 2 :(得分:45)

  

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

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

这是一个简单的程序,它证明了这一事实:

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

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

答案 3 :(得分:41)

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使用pass by value(在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中,如果要修改函数内部的对象但在函数返回时忘记这些更改,则必须在对副本进行临时更改之前显式创建对象的副本。 / p>

答案 4 :(得分:17)

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

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

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

  

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

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

答案 5 :(得分:16)

已经有一些很好的答案了,但我想发布关于这个主题的一对权威的定义,但也希望有人可以解释说Matz(Ruby的创造者)和David Flanagan在他们出色的O&#中的含义39; Reilly书, Ruby编程语言

  

[来自3.8.1:对象引用]

     

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

     

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

这对我来说都是有意义的,直到最后一段,尤其最后一句。这充其量是误导性的,更糟糕​​的是混淆。无论如何,对该值传递值引用的修改如何改变底层对象?

答案 6 :(得分:15)

  

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

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

答案 7 :(得分:8)

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

答案 8 :(得分:1)

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

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

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

然后

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

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

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

按价值呼叫'或者通过引用来调用'由于对' ='的混淆而在这里混乱在编译语言中' ='是一个数据副本。这里用这种解释语言' ='是参考副本。在示例中,您传入的引用后跟引用副本,但是' ='原来通过参考的那些破坏者,然后人们谈论它,好像' ='是一个数据副本。

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

但是,如果我们必须保持' ='作为一个参考框架,那么我们确实可以看到传入的数据直到作业,然后我们在分配后不再看到它而呼叫者的数据保持不变。在行为层面,只要我们不认为传入的值是复合的,这就是价值传递 - 因为我们在单个任务中更改其他部分时无法保留部分值(因为该分配会更改引用,而原始内容超出范围)。还会有一个疣,在这种情况下,对象中的变量将是引用,所有变量也是如此。因此,我们将被迫谈论通过价值传递参考文献'并且必须使用相关的位置。

答案 9 :(得分:1)

试试这个: -

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,现在我们通过使用方法看到一个例子。

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

答案 10 :(得分:1)

应该注意的是,您甚至不必使用"替换"更改值原始值的方法。如果为散列指定其中一个散列值,则表示您正在更改原始值。

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

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

答案 11 :(得分: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"}}

答案 12 :(得分:0)

是,但是....

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

我不同意这里的帖子,声称它是按价值传递的,对我来说这似乎像是书呆子般的,对等的游戏。

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

> astringobject = "lowercase"

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

> puts astringobject
lowercase

> puts bstringobject
LOWERCASE

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

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

答案 13 :(得分:0)

很多很好的答案都深入探讨了 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 # <-----

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

现在考虑这个相同的方法。唯一的区别是方法内部的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 等),则在方法名称中用 bang ! 表示如此,例如“def foo!”< /p>

附注:

重要的是要记住,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