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 实现实际上都会对传递小于指针的对象进行优化,而不是直接传递指向该对象的指针。但是,它们只对语言规范保证深度不可变的对象这样做,因此无法观察到传递指向值的指针和直接传递值之间的区别。例如,对于Fixnum
s、Symbol
s、Float
s nil
、true
和false
.]
这是 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.
}
}