深入探索

引用或值传参

搜索互联网,你很快就会发现 Ruby 程序员经常会讨论 Ruby 是通过’值’(by value)还是’引用’(by reference)传递参数。

在诸如 Pascal 和 C 等许多面向过程编程语言及其衍生物中,通过值或通过引用传递参数之间存在明显的区别。

‘值’(by value)参数是原始变量的副本;你可以将它传递给一个程序,修改它但是原始变量的值保持不变。

另一方面,‘引用’(by reference)是一个指向原始变量的指针。当它传递给例程(procedure)时,你传递的不是新的副本,而是原始数据在内存中存储的地址引用。因此,在例程(procedure)中进行的任何更改都将对原始数据进行,并且必然会影响原始变量的值。

arg_passing.rb

实际上很容易解决这个问题。如果 Ruby 通过值传递,那么它会生成原始变量的副本,因此该副本将具有不同的 object_id。事实上,情况并非如此。尝试运行 arg_passing.rb 程序来证明这一点。

现在,很可能在某些情况下,参数的传递可以(“在幕后”)说是“按值”(by value)实现的。但是,这些实现细节应该是 Ruby 解释器和编译器的编写者而不是 Ruby 程序员的义务。事实很明显,如果你以’纯’ OOP 方式编程 - 通过将参数传递给方法但只是随后使用这些方法返回的值 - 实现细节(通过值或通过引用)将不会给你带来任何意外后果。

然而,由于 Ruby 有时可以修改参数(例如使用如前所述的 !<< 方法),一些程序员已经习惯使用参数本身的修改值(相当于在 C 中使用引用(By Reference)参数)而不是使用返回的值。在我看来,这是一种不好的做法。它使你的程序依赖于方法的实现细节,因此应该避免这么做。

赋值是拷贝还是引用?

我之前说过,当某个表达式产生一个值时会创建一个新对象。因此,例如,如果为名为 x 的变量分配新值,则赋值后的对象将与赋值之前的对象不同(即,它将具有不同的 object_id):

  1. x = 10 # this x has one object_id
  2. x +=1 # and this x has a different one

但它不是因为赋值创建了新对象。而是新生成的值导致新对象被创建。在上面的示例中,+=1 是一个生成了新值的表达式(x+=1 等同于 x=x+1)。

简单的将一个变量赋值给另一个变量不会创建新对象。因此,假设你有一个名为 num 和另一个名为 num2 的变量。如果将 num2 赋值给 num,则两个变量都将引用同一个对象。你可以用 Object 类的 equals? 方法测试验证:

assign_ref.rb
  1. num = 11.5
  2. num2 = 11.5
  3. # num and num 2 are not equal
  4. puts( "num.equal?(num2) #{num.equal?(num2)}" )
  5. num = num2
  6. # but now they are equal
  7. puts( "num.equal?(num2) #{num.equal?(num2)}" )
equal_tests.rb

相等测试:==equal?

默认情况下(如 Ruby 的 Kernel 模块中所定义),当测试的两个对象都是同一个对象时,使用 == 的测试返回 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

什么时候两个对象是相同的?

作为一般规则,如果使用十个值初始化十个变量,则每个变量将引用一个不同的对象。例如,如果你创建两个这样的字符串…

identical.rb
  1. s1 = "hello"
  2. s2 = "hello"

…然后 s1s2 将引用独立的对象。两个浮点数(float)也一样…

  1. f1 = 10.00
  2. f2 = 10.00

但是,如前所述,整数(integer)是不同的。创建具有相同值的两个整数,它们最终将引用相同的对象:

  1. i1 = 10
  2. i2 = 10

对于普通整数值而言就是 true。如果有疑问?请使用 equal? 方法测试两个变量或值是否引用完全相同的对象:

  1. 10.0.equal?(10.0) # compare floats – returns false
  2. 10.equal?(10) # compare integers (Fixnums) – returns true

括号避免歧义

方法可以与局部变量共享相同的名称。例如,你可能有一个名为 name 的变量和一个名为 name 的方法。如果你习惯于在没有括号的情况下调用方法,那么这里是指方法还是变量是不明确的。括号再次避免含糊不清…

parentheses.rb
  1. greet = "Hello"
  2. name = "Fred"
  3. def greet
  4. return "Good morning"
  5. end
  6. def name
  7. return "Mary"
  8. end
  9. def sayHi( aName )
  10. return "Hi, #{aName}"
  11. end
  12. puts( greet ) #<= Hello
  13. puts greet #<= Hello
  14. puts( sayHi( name ) ) #<= Hi, Fred
  15. puts( sayHi( name() ) ) #<= Hi, Mary