Ruby通过引用或值传递吗?

问题描述 投票:239回答:12
@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变量中的错误。

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

ruby-on-rails ruby pass-by-reference
12个回答
231
投票

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

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


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

这里标识符是返回值对象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变量不包含值,但包含对value对象的引用。


1
投票

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

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

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

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

411
投票

其他的答案都是正确的,但是一位朋友让我向他解释这个问题以及它真正归结为Ruby如何处理变量,所以我想我会分享一些我为他写的简单图片/解释(道歉的长度)并且可能有些过于简单化了):


Q1: What happens when you assign a new variable str to a value of 'foo'?

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

A: A label called str is created that points at the object 'foo', which for the state of this Ruby interpreter happens to be at memory location 2000.


Q2: What happens when you assign the existing variable str to a new object using =?

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

A: The label str now points to a different object.


Q3: What happens when you assign a new variable = to str?

str2 = str
str2.object_id # => 2002

A: A new label called str2 is created that points at the same object as str.


Q4: What happens if the object referenced by str and str2 gets changed?

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

A: Both labels still point at the same object, but that object itself has mutated (its contents have changed to be something else).


How does this relate to the original question?

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

45
投票

Ruby通过引用或值传递吗?

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

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

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

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

41
投票

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


17
投票

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

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

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

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

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


16
投票

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

[来自3.8.1:对象引用]

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

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

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


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

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


8
投票

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


1
投票

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

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

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

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

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

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.