3

好的,所以 Ruby 是“按值传递”。但是您如何在 Ruby 中准确定义“按引用传递”和“按值传递”?我用过这个答案引用传递和值传递有什么区别?根据它,Ruby 似乎是一个混合体……

从技术上讲,Ruby 似乎是“按值传递”,不同之处在于,当您将值传递给方法时,它不会被复制。如果我们定义“value” = 对象,而“reference” = 指向该对象的引用变量,那么“通过引用传递”是否等同于“传递指向特定对象的引用变量”?然后,一旦“引用”被传递,该方法不会复制对象,而是实际上拥有可以直接操作的 ORIGINAL 对象本身(由变量引用)。如我错了请纠正我。

编辑:我知道这个问题Ruby 是按引用传递还是按值传递?但是不同的人似乎对通过引用/值传递有不同的定义。

4

3 回答 3

5

Ruby 是按值传递的,就像 C、Java、Python、Smalltalk、ECMAScript 和许多其他语言一样。默认情况下,C++ 和 C# 也是按值传递的,您必须使用特殊注释(&在 C++ 中,ref在 C# 中)才能使用按引用传递。

区别其实很简单:如果传递了引用,那么被调用者可以修改它,否则不能。在 Ruby 中,被调用者不能修改引用,因此它是按值传递的:

def is_ruby_pass_by_value?(foo)
  foo = 'No, Ruby is pass-by-reference.'
  return nil
end

bar = 'Yes, of course, Ruby *is* pass-by-value!'

is_ruby_pass_by_value?(bar)

p bar
# 'Yes, of course, Ruby *is* pass-by-value!'

如您所见,在方法is_ruby_pass_by_value?中,引用bar/foo没有被传递,否则修改后将可见。bar正在按值传递,即(其中包含的值)的内容正在传递,而不是引用本身。bar

现在,传递的值是什么不是String对象。相反,它是指向String对象的指针。更准确地说:该指针的副本

现在,有两个指向该String对象的指针。而且那个String对象是可变的!所以,如果我跟随一个指针 ( foo) 并告诉该String对象改变自己,然后我跟随另一个指针 ( bar) 并询问它的内容,那么我显然会看到改变的内容。这只是共享可变状态的本质,Ruby 不是一种纯粹的功能性、引用透明的语言:

def is_ruby_pass_by_value?(foo)
  foo.replace('More precisely, it is call-by-object-sharing!')
  foo = 'No, Ruby is pass-by-reference.'
  return nil
end

bar = 'Yes, of course, Ruby *is* pass-by-value!'

is_ruby_pass_by_value?(bar)

p bar
# 'More precisely, it is call-by-object-sharing!'

事实上,在 Ruby 中,由变量保存并作为参数传递的值始终是一个指针。这就是几乎所有面向对象语言的工作方式。Barbara Liskov 将这种按值传递的特殊情况称为“按对象共享”,有时也称为“按共享调用”或“按对象调用”。

但是请注意,传递的值是指针这一事实完全无关紧要。Pass-by-value vs. pass-by-reference 是关于如何传递参数,而不是参数是什么。无论您传递的int是指针还是指针,C 始终是按值传递的。指针仍在按值传递。同样在 Ruby 中,指针是按值传递的。Ruby 和 C 之间的区别是 a) 您只能在 Ruby 中传递指针,并且 b) 没有特殊语法表明您正在传递指针。

[注意:大多数 Ruby 实现实际上都会对传递小于指针的对象进行优化,而不是直接传递指向该对象的指针。但是,它们只对语言规范保证深度不可变的对象这样做,因此无法观察到传递指向值的指针和直接传递值之间的区别。例如,对于Fixnums、Symbols、Floats niltruefalse.]

这是 C# 中的一个示例,它演示了按值传递(即使该值是引用)和按引用传递之间的区别:

class Program
{
    static void IsCSharpPassByValue(string[] foo, ref string baz)
    {
        foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
        foo = new string[] { "C# is not pass-by-reference." };

        baz = "It also supports pass-by-reference if explicitly requested.";
    }

    static void Main(string[] args)
    {
        var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };

        var grault = "This string will vanish because of pass-by-reference.";

        IsCSharpPassByValue(quux, ref grault);

        Console.WriteLine(quux[0]);
        // More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.

        Console.WriteLine(grault);
        // It also supports pass-by-reference if explicitly requested.
    }
}
于 2013-11-10T12:51:08.307 回答
2

您可以对此表示纯粹,并说 ruby​​ 是“通过参考值传递”的特例,但它没有抓住重点。试想 ruby​​ 中的一切都是一个对象

foo(14) 传递一个对值为 14 的整数对象的引用。有些东西在幕后进行,所以你最终不会得到 14 个对象中的 100 个,但从意图的角度来看,大多数时间你可以忘记这个概念。

于 2013-11-10T12:00:38.603 回答
2

Ruby 是“通过引用传递”。区别如下:如果通过引用传递,可以对原始对象做坏事:

x = [ "virgin" ]

def do_bad_things_to( arg )
  arg.clear << "bad things"
end

do_bad_things_to( x )

如果按值传递,则得到原始对象的值并可以使用它,但不能对原始对象做坏事。您消耗更多内存,因为原始对象值的副本也占用内存:

def pass_by_value( arg )
  arg.dup
end

y = [ "virgin" ]

do_bad_things_to( pass_by_value( y ) )

p x #=> [ "bad things" ]
p y #=> [ "virgin" ]

对于不可变的对象(数字、符号、true... falsenil...),由于它们的不变性,我们不能做坏事。人们常说,在 Ruby 中,它们是按值传递的,但实际上,这种区别对它们来说意义不大,就像在内存中保留许多内部结构的副本一样意义不大。

更新:关于“参考”的含义似乎存在术语争议。在 Ruby 中,Jörg Mittag 的“按引用传递”通过关闭局部变量的闭包显式实现:

baz = "Jörg"
define_method :pass_by_Jorgs_reference_to_baz do baz = "Boris" end
pass_by_Jorgs_reference_to_baz
baz #=> "Boris"
于 2013-11-10T12:56:27.253 回答