Ruby通过引用或值传递吗?

Sid*_*Sid 245 ruby ruby-on-rails 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?
Run Code Online (Sandbox Code Playgroud)

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

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

Abe*_*ker 417

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


Q1:将新变量str赋值为'foo'?时会发生什么?

str = 'foo'
str.object_id # => 2000
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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


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

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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


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

str2 = str
str2.object_id # => 2002
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

答:所谓的新标签str2被创建在指向同一个对象str.


Q4:如果对象通过引用会发生什么事str,并str2得到改变?

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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


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

它与Q3/Q4中的情况基本相同; 该方法获取自己的变量/ label(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
Run Code Online (Sandbox Code Playgroud)

  • Robert Heaton 最近也在博客中谈到了这一点:http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/ (3认同)

Chu*_*uck 238

在传统术语中,Ruby是严格按值传递的.但那不是你在这里问的那个.

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

  • @JörgWMittag:是的,但OP的混淆实际上并不是严格的CS意义上的值传递或传递.他缺少的是你传递的"价值"*是*参考.我觉得只是说"这是价值传递"会是迂腐的,并且OP是一种伤害,因为这实际上并不是他的意思.但感谢澄清,因为它对未来的读者很重要,我应该把它包括在内.(我总是在包括更多信息而不是让人混淆之间徘徊.) (94认同)
  • Ruby是*pass-by-value*.不,如果.没有但是.没有例外.如果你想知道Ruby(或任何其他语言)是*by-by-reference*还是*pass-by-value*,那就试试吧:`def foo(bar)bar ='reference'end; baz ='价值'; FOO(巴兹); 把"Ruby传递 - #{baz}"`. (85认同)
  • 不同意@Jorg.Ruby是通过引用传递的,他只是更改了引用.试试这个:def foo(bar)bar.replace'reference'end; baz ='价值'; FOO(巴兹); 把"Ruby传递 - #{baz}" (16认同)
  • @pguardiario:我认为这只是一个定义问题.你正在使用你个人提出的"传递参考"的定义,而Jörg正在使用传统的计算机科学定义.当然,告诉你如何使用单词并不是我的事 - 我只是认为解释*通常*的含义是很重要的.在传统术语中,Ruby是按值传递,但值本身是引用.我完全理解为什么你和OP喜欢将其视为传递参考 - 这不是该术语的传统含义. (15认同)
  • Ruby中的所有东西都是一个对象,所以Ruby既不是通过值传递也不是通过引用传递,至少在C++中使用这些术语."通过对象引用传递"可能是描述Ruby所做事情的更好方法.最后,最好的选择可能不是对这些术语中的任何一个都赋予太多意义,而只是对真正发生的行为有一个很好的理解. (7认同)
  • 是的,克隆是一个完全独立的对象.至于传值,它与纯OO并不真正兼容,其中"值"除了作为对象状态之外不存在.你可以得到的最接近的是Objective-C的`bycopy`类型修饰符,它告诉运行时在幕后制作副本.这听起来很有用. (3认同)
  • @pguardiario:您的代码与通过引用传递或传递值完全没有任何关系.无论Ruby是通过引用传递还是按值传递,它都会打印完全相同的内容.你的代码唯一证明的是Ruby不是一种纯粹的函数式语言,我认为无论如何都不会有任何争议. (3认同)
  • Ruby,Python,Java,JavaScript和C#使用pass by value(在C++意义上)_if参数不是object_.如果参数是一个对象,则使用"通过对象引用传递"(使用Python的术语):任何对象的成员都可以为其分配新值,原始对象将记住这些更改,但是分配一个全新的对象变量导致变量停止引用原始变量.因此,它不是C++意义上的"通过引用传递". (3认同)
  • @pguardiario:关于"pass-by-reference"与"pass-by-value"的关系是,pass-by-reference意味着你直接引用调用者的内存空间,这样重新分配变量就会改变变量在来电者中.这不会发生.`foo`的变量`bar`是一个独立变量,它与调用者的`baz`具有相同的*值*.恰好在这种情况下,该值是对象引用.但就调用约定而言,它被视为按值传递. (2认同)
  • @Chuck,我同意这是一个定义问题,但我没有编造出来。按值传递意味着一个副本被发送到该方法,很明显这不是正在发生的事情。 (2认同)
  • 在 C++ 中,“按值传递”意味着函数获取变量的副本,并且对副本的任何更改都不会更改原始变量。对于对象也是如此。如果按值传递对象变量,则整个对象(包括其所有成员)都会被复制,并且对成员的任何更改都不会更改原始对象上的这些成员。(如果您按值传递指针则不同,但 Ruby 无论如何都没有指针,AFAIK。) (2认同)
  • 在 C++ 中,“通过引用传递”意味着函数可以访问原始变量。它可以分配一个全新的文字整数,然后原始变量也将具有该值。 (2认同)

Dav*_*cki 47

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

in inc: 6
in main: 5
in inc: 1
in main: 1
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

in replace: 10
in main: 10
Run Code Online (Sandbox Code Playgroud)

如果参数不是对象,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
Run Code Online (Sandbox Code Playgroud)

输出:

1
2
2
3
2

1
2
1
Run Code Online (Sandbox Code Playgroud)

*这就是为什么在Ruby中,如果要修改函数内部的对象但在函数返回时忘记这些更改,则必须在对副本进行临时更改之前显式创建对象的副本.


Jör*_*tag 45

Ruby通过引用或值传递吗?

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

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

def foo(bar)
  bar = 'reference'
end

baz = 'value'

foo(baz)

puts "Ruby is pass-by-#{baz}"
# Ruby is pass-by-value
Run Code Online (Sandbox Code Playgroud)

  • 这个答案虽然严格来说是*true*,但并不是非常有用*.传递的值总是一个指针是*不*无关紧要的事实.对于那些正在努力学习的人来说,这是一个混乱的根源,而你的答案绝对无助于解决这种困惑. (40认同)
  • @DavidJ.:"这里的错误是本地参数被重新分配(指向内存中的新位置)" - 这不是错误,这是*pass-by-value*的*定义*.如果Ruby是pass-by-reference,那么重新分配给被调用者中的本地方法参数绑定也会在调用者中重新分配本地变量绑定.它没有.因此,Ruby是值得传递的.事实上,如果你改变一个可变值,那么值的变化是完全无关紧要的,这就是可变状态的工作原理.Ruby不是一种纯函数式语言. (15认同)
  • 这是诡辩."按值传递"和"按引用传递"之间的实际区别是语义,而不是语法.你会说C数组是按值传递的吗?当然不是,即使您将数组的名称传递给函数,您传递的是不可变指针,并且只有指针引用的数据才会被突变.显然,Ruby中的值类型按值传递,引用类型通过引用传递. (8认同)
  • 感谢Jörg捍卫"传值"的真正定义.当值实际上是一个参考时,它显然正在融化我们的大脑,尽管红宝石总是通过值. (5认同)
  • @dodgethesteamroller:Ruby和C都是pass-by-value.总是.没有例外,不是没有但是没有.pass-by-value和pass-by-reference之间的区别在于你是否传递了引用指向的值或传递了引用.C*始终*传递值,*never*参考.该值可能是也可能不是指针,但*该值与该值是否正在首先传递无关.Ruby也*总是*传递值,*never*引用.该值始终是*指针,但同样,这是无关紧要的. (3认同)
  • 如果 Ruby 是按值传递的,因为您总是传递指针的值,那么像 `baz = 'reference'` 这样的行就没有多大意义。指针不是字符串。如果您正在操作引用的值,则只能将其分配给其他指针。在这种情况下,`=` 不是指针的赋值,而是说实例化一个新的字符串并将指向该字符串的指针存储在 `bar` 中。基本上,上面的例子说明我们正在使用指针,然后使用字符串操作来证明它。通过引用传递给 foo 的 String _is_。指针不是。 (2认同)
  • @karmajunkie:是的,这使得Ruby传递值.无关*传递什么*,重要的是*它是如何传递的,正如你自己说的那样,指针通过值*传递*.因此,Ruby是值得传递的.无论是传递`int`还是`int*`,C也总是按值传递.仅仅因为传递指针不会改变调用约定.你可以想象Ruby就像C一样,除了所有方法都接受并返回一个不透明的`object_t*`指针.这仍然是价值传递. (2认同)

Ari*_*Ari 19

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

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

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

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

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


Dom*_*ick 16

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

[来自3.8.1:对象引用]

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

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

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

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

Bre*_*red 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 ;)"
Run Code Online (Sandbox Code Playgroud)

=> 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}"
Run Code Online (Sandbox Code Playgroud)

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

  • @Martijn:你的论点并不完全有效.让我们逐个声明您的代码声明.a ='foobar'创建一个指向'foobar'的新引用.b = a创建与a相同的数据的第二个引用.b [5] ='z'将b引用的值的第六个字符更改为'z'(同时也由a引用的值更改).这就是为什么"在你的术语中都被修改",或者更准确地说,为什么"两个变量引用的值被修改"的原因. (2认同)
  • 你在`bar`方法中没有对引用做任何事情.您只是修改引用*指向*的对象,而不是引用本身.他们只能通过赋值来修改Ruby中的引用.您不能通过在Ruby中调用方法来修改引用,因为方法只能在对象上调用,而引用不是Ruby中的对象.您的代码示例演示了Ruby已经共享可变状态(这里没有讨论),但它没有说明传值和传递引用之间的区别. (2认同)
  • 当有人问一种语言是否是“按引用传递”时,他们通常想知道当您将某些内容传递给函数并且函数对其进行修改时,它是否会在函数外部进行修改。对于 Ruby 来说,答案是“是”。这个答案有助于证明@JörgWMittag 的答案极其无益。 (2认同)

Rae*_*nha 8

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