深入探索
引用或值传参
搜索互联网,你很快就会发现 Ruby 程序员经常会讨论 Ruby 是通过’值’(by value)还是’引用’(by reference)传递参数。
在诸如 Pascal 和 C 等许多面向过程编程语言及其衍生物中,通过值或通过引用传递参数之间存在明显的区别。
‘值’(by value)参数是原始变量的副本;你可以将它传递给一个程序,修改它但是原始变量的值保持不变。
另一方面,‘引用’(by reference)是一个指向原始变量的指针。当它传递给例程(procedure)时,你传递的不是新的副本,而是原始数据在内存中存储的地址引用。因此,在例程(procedure)中进行的任何更改都将对原始数据进行,并且必然会影响原始变量的值。
实际上很容易解决这个问题。如果 Ruby 通过值传递,那么它会生成原始变量的副本,因此该副本将具有不同的 object_id
。事实上,情况并非如此。尝试运行 arg_passing.rb 程序来证明这一点。
现在,很可能在某些情况下,参数的传递可以(“在幕后”)说是“按值”(by value)实现的。但是,这些实现细节应该是 Ruby 解释器和编译器的编写者而不是 Ruby 程序员的义务。事实很明显,如果你以’纯’ OOP 方式编程 - 通过将参数传递给方法但只是随后使用这些方法返回的值 - 实现细节(通过值或通过引用)将不会给你带来任何意外后果。
然而,由于 Ruby 有时可以修改参数(例如使用如前所述的 !
或 <<
方法),一些程序员已经习惯使用参数本身的修改值(相当于在 C 中使用引用(By Reference)参数)而不是使用返回的值。在我看来,这是一种不好的做法。它使你的程序依赖于方法的实现细节,因此应该避免这么做。
赋值是拷贝还是引用?
我之前说过,当某个表达式产生一个值时会创建一个新对象。因此,例如,如果为名为 x
的变量分配新值,则赋值后的对象将与赋值之前的对象不同(即,它将具有不同的 object_id
):
x = 10 # this x has one object_id
x +=1 # and this x has a different one
但它不是因为赋值创建了新对象。而是新生成的值导致新对象被创建。在上面的示例中,+=1
是一个生成了新值的表达式(x+=1
等同于 x=x+1
)。
简单的将一个变量赋值给另一个变量不会创建新对象。因此,假设你有一个名为 num
和另一个名为 num2
的变量。如果将 num2
赋值给 num
,则两个变量都将引用同一个对象。你可以用 Object 类的 equals?
方法测试验证:
num = 11.5
num2 = 11.5
# num and num 2 are not equal
puts( "num.equal?(num2) #{num.equal?(num2)}" )
num = num2
# but now they are equal
puts( "num.equal?(num2) #{num.equal?(num2)}" )
相等测试:==
或 equal?
==
的测试返回 true
。因此,如果值相同但对象不同,它将返回 false
: ob1 = Object.new ob2 = Object.new puts( ob1==ob2 ) #<= false事实上,==
经常被诸如 String 之类的类重写,然后当值相同但对象不同时会返回 true
: s1 = “hello” s2 = “hello” puts( s1==s2 ) #<= true出于这个原因,当你想确定两个变量是否引用同一个对象时,最好使用 equal?
方法: puts( s1.equal?(s2) ) #<= false什么时候两个对象是相同的?
作为一般规则,如果使用十个值初始化十个变量,则每个变量将引用一个不同的对象。例如,如果你创建两个这样的字符串…
s1 = "hello"
s2 = "hello"
…然后 s1
和 s2
将引用独立的对象。两个浮点数(float)也一样…
f1 = 10.00
f2 = 10.00
但是,如前所述,整数(integer)是不同的。创建具有相同值的两个整数,它们最终将引用相同的对象:
i1 = 10
i2 = 10
对于普通整数值而言就是 true。如果有疑问?请使用 equal?
方法测试两个变量或值是否引用完全相同的对象:
10.0.equal?(10.0) # compare floats – returns false
10.equal?(10) # compare integers (Fixnums) – returns true
括号避免歧义
方法可以与局部变量共享相同的名称。例如,你可能有一个名为 name
的变量和一个名为 name
的方法。如果你习惯于在没有括号的情况下调用方法,那么这里是指方法还是变量是不明确的。括号再次避免含糊不清…
greet = "Hello"
name = "Fred"
def greet
return "Good morning"
end
def name
return "Mary"
end
def sayHi( aName )
return "Hi, #{aName}"
end
puts( greet ) #<= Hello
puts greet #<= Hello
puts( sayHi( name ) ) #<= Hi, Fred
puts( sayHi( name() ) ) #<= Hi, Mary