深入探索

布尔(Boolean)测试

  1. and &&

这些运算符只有在判断左侧结果为 true 时,会继续判断右侧,and 的优先级比 && 低。

  1. or ||

这些运算符只有在判断左侧结果为 false 时,会继续判断右侧,or 的优先级比 || 低。

  1. not !

布尔值的否操作,即值为 false 时返回 true,值为 true 时返回 false。

使用两种不同的布尔运算符时要小心。由于优先级的差异,测试将以不同的顺序进行判断,并可能产生不同的结果。

思考以下代码:

boolean_ops.rb
  1. # Example 1
  2. if (1==3) and (2==1) || (3==3) then
  3. puts('true')
  4. else
  5. puts('false')
  6. end
  7. # Example 2
  8. if (1==3) and (2==1) or (3==3) then
  9. puts('true')
  10. else
  11. puts('false')
  12. end

这些看起来可能是一样的。实际上,示例 1 将打印 ‘false’ ,而示例 2 将打印 true。这完全是因为 or|| 优先级低的事实。因此,示例 1 中的测试是:如果 1 等于 3 [false] 并且(要么 2 等于 1 ,要么 3 等于 3)[true]。由于这两个必要的条件中有一个是 false,所以整个测试返回 false。

现在来看示例 2,其测试是:(如果 1 等于 3 ,并且 2 等于 1)[false],或者 3 等于 3 [true]。这次,我们仅需要两个测试中一个成功即可;第二个测试判断为 true,所以整个测试返回 true 。

在这样的测试中,运算符优先级的副作用可能会导致非常模糊的错误。你可以通过使用括号来清楚的表达测试的含义来避免这些错误。在这里,我重写了上面的示例 1 和 2;在每种情况下,添加一对括号都会反转测试返回的布尔值:

  1. # Example 1 (b) – now returns true
  2. if ((1==3) and (2==1)) || (3==3) then
  3. puts('true')
  4. else
  5. puts('false')
  6. end
  7. # Example 2 (b) – now returns false
  8. if (1==3) and ((2==1) or (3==3)) then
  9. puts('true')
  10. else
  11. puts('false')
  12. end

否定

否定运算符 ! 可以在表达式的开头使用,或者你可以在一个表达的左侧和右侧中间使用 !=(不等于)运算符:

  1. !(1==1) #=> false
  2. 1 != 1 #=> false

或者,你可以用 not 代替 !

  1. not(1==1)

布尔运算中的怪象

eccentricities.rb

请注意,Ruby 的布尔(boolean)运算符有时会以一种奇怪且不可预测的方式运行。例如:

  1. puts( (not( 1==1 )) ) # This is ok
  2. puts( not( 1==1 ) ) # This is a syntax error
  3. puts( true && true && !(true) ) # This is ok
  4. puts( true && true and !(true) ) # This is a syntax error
  5. puts( ((true) and (true)) ) # This is ok
  6. puts( true && true ) # This is ok
  7. puts( true and true ) # This is a syntax error

在多数情况下,可以通过统一使用同一类型的运算符(要么用 andornot,要么用 &&||!)来避免这些问题,而不是混合地使用两者。另外,推荐经常使用括号。

Catch 与 Throw

Ruby 提供了一对方法 catchthrow,可用于在满足某些条件时中断(break)代码块的执行。这是 Ruby 中与其它一些编程语言中的 goto 最接近的等价语法。该代码块必须以 catch 后跟一个符号(symbol)(即以冒号开头的唯一标识符)开头,例如 :done:finished。代码块本身可以用大括号限定,也可以用关键字 doend 限定,如下所示:

  1. # think of this as a block called :done
  2. catch(:done){
  3. # some code here
  4. }
  5. # and this is a block called :finished
  6. catch(:finished) do
  7. # some code here
  8. end

在块内,你可以使用一个符号(symbol)作为参数调用 throw。通常,当满足某些特定条件时,你将可以调用 throw 来跳过块中的所有剩余的未执行代码。例如,让我们假设该块包含这样一些代码,提示用户输入一个数字,用某个值来除以该数字,然后继续对结果进行大量其它的复杂计算。显然,如果用户输入 0,则后面的计算都不能完成,因此你可以通过跳出块来跳过这些计算,并继续执行块后的任何代码。这是这样做的一种方式:

catch_throw.rb
  1. catch(:finished) do
  2. print('Enter a number: ')
  3. num = gets().chomp.to_i
  4. if num == 0 then
  5. throw :finished # if num is 0, jump out of the block
  6. end
  7. # Here there may be hundreds of lines of
  8. # calculations based on the value of num
  9. # if num is 0 this code will be skipped
  10. end
  11. # the throw method causes execution to
  12. # jump to here – outside of the block
  13. puts("Finished")

实际上,你可以在块外面调用 throw,像这样:

  1. def dothings( aNum )
  2. i = 0
  3. while true
  4. puts("I'm doing things...")
  5. i += 1
  6. throw(:go_for_tea) if (i == aNum)
  7. # throws to end of go_to_tea block
  8. end
  9. end
  10. catch(:go_for_tea) { # this is the :go_to_tea block
  11. dothings(5)
  12. }

并且你可以将 catch 块嵌套在其它的 catch 块中,像这样:

  1. catch(:finished) do
  2. print('Enter a number: ')
  3. num = gets().chomp.to_i
  4. if num == 0 then throw :finished end
  5. puts( 100 / num )
  6. catch(:go_for_tea) {
  7. dothings(5)
  8. }
  9. puts("Things have all been done. Time for tea!")
  10. end

与其它编程语言中的 gotos 和 jumps 一样,在 Ruby 应该非常谨慎地使用 catchthrow,因为它们会破坏代码的逻辑,并且可能会引入难以发现的错误。