Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
第六章
1. 第六章
条件语句
计算机编程就和生活一样,总是有诸多困难的决定等着我们来做。就像:如果我继续待在床
上,那么我将睡得更多,否则我将去工作;如果我去工作,我将挣到钱,否则我将丢掉我的
工作,等等。
在先前的程序中,我们已经做过了很多次的 if 判断了。举一个简单的例子,这是第一章税率
计算器中的一段代码:
if (subtotal < 0.0) then
subtotal = 0.0
end
在这个程序中,用户将会被提示输入一个数值 subtotal,这个值将会被用于计算税值。如果
用户无意中输入一个负值,此时 if 判断成立,因为判断(subtotal < 0.0)值为 true,这将使
得在 if 判断和 then 之间的代码运行;即将 subtotal 值设为 0。
= 还 是 ==?
和大多数其他编程语言一样, Ruby 使用单等号用来进行赋值,而使用双等号来判断值是否相等
IF..THEN..ELSE
if_else.rb
像这个程序一样简单的判断只有两种可能的结果。代码的执行与否完全取决于判断是否为
true。通常你会有多个可能的结果。假设,你的程序在工作日的时候执行一种操作,而在周
末执行另一种操作。你可以在 if 判断之后加上一个 else 部分,如下:
if aDay == 'Saturday' or aDay == 'Sunday'
daytype = 'weekend'
else
daytype = 'weekday'
end
这里的 if 条件语句非常简单直接。它判断两种可能的条件:1)如果 aDay 的值
为'Saturday'或者 2)aDay 值为'Sunday'。如果这两个判断中的任何一个为 true,那么将
2. 执行下一行代码:daytype = 'weekend';其他的所有情况,都将执行 else 后的代码:daytype
= 'weekday'。
if_then.rb
当我们将 if 判断和将要执行的代码分别写在单独行的时候, then 关键字可以省略。当判
断和代码写在同一行的时候, then 关键字 ( 或者你想要更简洁一点的代码,只需一个冒
号 ) 是必须的。
if x == 1 then puts( 'ok' ) end # with 'then'
if x == 1 : puts( 'ok' ) end # with colon
if x == 1 puts( 'ok' ) end # syntax error!
一个 if 判断不一定严格地用于判断两个条件。假设,你的代码需要判断某一天是一个从昨日
还是节假日。所有的工作日(周一至周五)都是工作日,所有的周六都是节假日,而周日只有
在你不加班的时候才是节假日(这可能是作者所在国家英国的一些情况)。这是我第一次尝试
些一个判断来判断所有的条件:
and_or_wrong.rb
working_overtime = true
if aDay == 'Saturday' or aDay == 'Sunday' and not working_overtime
daytype = 'holiday'
puts( "Hurrah!" )
else
daytype = 'working day'
end
不幸的是,这个程序并没有达到真正的效果。记住周六一定是节假日。但是这段代码中周六
被当作一个工作日。这是因为 Ruby 将这个判断理解为:”如果今天是周六并且我不加班,
或者今天是周日并且我不加班”。然而我实际上想表达的意思是:”如果今天是周六,或者
今日是周日并且我不加班”。最简单的解决这个语法歧义的方法是将任何一个将要作为一个
单元进行判断的代码使用括号括起来,如下:
and_or.rb
if aDay == 'Saturday' or (aDay == 'Sunday' and not working_overtime)
3. AND..OR..NOT
另外,Ruby 中还有两种不同的语法用于判断布尔(true/false)条件。在上面的例子中,我使
用的是纯自然英语风格操作符:and,or 和 not。如果你想使用其他的和别的编程语言类似的
风格的话,那你可以使用&&(and),||(or)和!(not)。
注意,虽然这两种操作符是完全可互换的。但是需要注意的一点是,他们拥有不同的优先级,
这就意味着当在一个判断中使用符合的操作符,这些操作符的优先级将取决于你使用的方式。
我们来看下面的这个判断:
days.rb
if aDay == 'Saturday' or aDay == 'Sunday' and not working_overtime
daytype = 'holiday'
end
假设布尔变量 working_overtime 值为 true,那么当 aDay 初始化为'Saturday'时,这个判
断会是 true 吗?也就是说,当 aDay 是'Saturday'时,daytype 会被赋值为'holiday'吗?答
案是:No。这个判断只会在 aDay 既不是'Saturday'也不是'Sunday'并且
working_overtime 也不是 true 的时候才会为 true。
现在让我们来看这个判断:
if aDay == 'Saturday' || aDay == 'Sunday' && !working_overtime
daytype = 'holiday'
end
表面上看来,这个判断和上一个判断没有什么区别;唯一的区别就是这次我们使用了另一种
语法的操作符。然而,改变不只是在代码的外观上,如果 aDay 是'Saturday'这个判断将为
true,datatype 将被赋值为'holiday'。这是因为||操作符比 or 操作符拥有更高的优先级。
所以这个判断在 aDay 为'Saturday'或者 aDay 是'Sunday'并且 working_overtime 是 false
的时候将为通过。
关于这个,请参考本章末尾处的深入探讨一节。一个基本原则就是,选择你最喜欢的操作符,
并坚持使用它们,在使用过程中一定要使用括号来避免歧义。
IF..ElSIF
毫无疑问你可能会碰到很多二选一的条件需要你针对不同的情况作出不同的响应。有一种方
法可以做到,那就是在一个 if 之后的 elsif 关键字后接连使用一连串的条件判断。整个条件
判断将在 end 关键字处终止。
4. 例如,我在一个 while 循环中重复地进行输入操作,并且 if 条件判断用于判断用户是否输
入'q'(我使用了 chomp()方法来删除输入的回车符);如果一直没有输入'q'那第一个 elsif 条
件判断输入值的整型值(input.to_i)是否大于 800;如果这个判断为 false,那么下一个 elsif
将判断输入值是否小于或等于 800:
if_elsif.rb
while input != 'q' do
puts("Enter a number between 1 and 1000 (or 'q' to quit)")
print("?- ")
input = gets().chomp()
if input == 'q'
puts( "Bye" )
elsif input.to_i > 800
puts( "That's a high rate of pay!" )
elsif input.to_i <= 800
puts( "We can afford that" )
end
end
但是问题是这个程序即使会提示用户输入一个在 1 和 1000 之间的值,但是它仍然会接受一
个小于 1 的值(如果你真的想要一份负的薪水的话,我将很乐意给你一份工作!)和大于
1000 的值(现在别想着我能给你职位了!)。
我们可以通过重写这两个 elsif 条件并添加一个 else 部分来解决这个问题,else 部分在前面
所有判断不同的时候执行:
if_elsif2.rb
if input == 'q'
puts( "Bye" )
elsif input.to_i > 800 && input.to_i <= 1000
puts( "That's a high rate of pay!" )
elsif input.to_i <= 800 && input.to_i > 0
5. puts( "We can afford that" )
else
puts( "I said: Enter a number between 1 and 1000!" )
end
if_elsif_alt.rb
Ruby 还有一个 if..then..else 的缩写,在缩写方式中,用一个 ? 替代 if..then 部分,用 : 和
else... 的作用一样
<Test Condition>?<if true do this>:<else do this>
例如:
x == 10 ? puts("it's 10") : puts( "it's some other number" )
当条件判断特别复杂的时候 ( 如果判断使用 and 和 or) 你应该使用括号将其括起来。如果判
断语句跨越多行,那么 ? 必须和前面的条件放在同一行,而 : 必须和紧接 ? 的代码放在同一
行。也就是说,如果你在 ? 和 : 之前新起一行就会引发语法错误。这是一个正确的多行代码
块:
(aDay == 'Saturday' or aDay == 'Sunday') ?
daytype = 'weekend' :
daytype = 'weekday'
days2.rb
这是一另一个更长的 if..elsif 的例子,后接一个 else 用于处理前面所有判断均不通过的情况。
这次开关变量 i 是一个整型数:
def showDay( i )
if i == 1 then puts("It's Monday" )
elsif i == 2 then puts("It's Tuesday" )
elsif i == 3 then puts("It's Wednesday" )
elsif i == 4 then puts("It's Thursday" )
elsif i == 5 then puts("It's Friday" )
elsif (6..7) === i then puts( "Yippee! It's the weekend! " )
6. else puts( "That's not a real day!" )
end
end
注意我使用了一个区间(6..7)来匹配周六和周日这两个整型值。===方法(三个=字符)判断
一个变量值 i 是否包含于该区间。在上面的例子中,
(6..7) === i
可以这样写
(6..7).include?(i)
===方法在 Object 类中就定义了,在其派生类中对该方法进行了重载。它的行为根据类的
不同而变化。我们很快就会看到它的一个很基础的用法,那就是为 case 语句提供有意义的
判断。
UNLESS
unless.rb
Ruby 还可以执行 unless 判断,这个判读和 if 完全相反:
unless aDay == 'Saturday' or aDay == 'Sunday'
daytype = 'weekday'
else
daytype = 'weekend'
end
我们可以把 unless 想象成为'if not'的另一种表达方式。下面的代码和上面的代码是等价的:
if !(aDay == 'Saturday' or aDay == 'Sunday')
daytype = 'weekday'
else
daytype = 'weekend'
end
If 和 UNLESS 变体
你可能记起我们在第五章讲到的 while 语法替代方式。不再这样写:
7. while tired do sleep end
我们可以这样写:
sleep while tired
在这种替换语法中,while 关键字放在执行代码和条件判断之间,成为'while
modifier'(while 变体)。同样 Ruby 也有 if 和 unless 的变体。这里有些例子:
if_unless_mod.rb
sleep if tired
begin
sleep
snore
end if tired
sleep unless not tired
begin
sleep
snore
end unless not tired
当你需要在 if 判断通过时重复地执行一些定义良好的行为时,简洁的语法将会非常有效。例
如你可以在常量 DEBUG 为 true 时通过调试输出来点缀你的代码:
puts( "somevar = #{somevar}" ) if DEBUG
constants.rb
常量
在 ruby 中常量是以大写字母开头的。类名就是常量。你可以使用 constants 方法来获取类中
定义的所有常量:
Object.constants
Ruby 提供了 const_get 和 const_set 方法来获取和设置指定名称的常量值 ( 名称前加一个冒
号,如 :RUBY_VERSION) 。
注意,和其他编程语言中常量不同的是, Ruby 的常量可以被重新赋值:
RUBY_VERSION = "1.8.7"
RUBY_VERSION = "2.5.6"
上面的代码就给 RUBY_VERSION 重新赋值了,会引发一个 ' 已经初始化的常量 ' 警告,但是
不是错误!
8. CASE 语句
当你需要基于一个变量的值进行很多不同的操作时,太多的 if..elsif 实在是太烦所重复了。
Case 语句给我们提供了一个代替 if..elsif 的更为简洁的方式。它以 case 开头,后接需要进
行判断的变量名。随后是一连串的 when 语句段,在每一个开关变量后面都有一段执行代码。
只有当开关变量为 true 时,后面的代码才会执行:
case.rb
case( i )
when 1 : puts("It's Monday" )
when 2 : puts("It's Tuesday" )
when 3 : puts("It's Wednesday" )
when 4 : puts("It's Thursday" )
when 5 : puts("It's Friday" )
when (6..7) : puts( "Yippee! It's the weekend! " )
else puts( "That's not a real day!" )
end
在上面的例子中,我使用冒号来隔开 when 语句和执行代码。其实你也可以使用 then 关键
字:
when 1 then puts(“It's Monday”)
如果条件判断和执行代码不在同一行的话,冒号或者 then 可以省略。和 C 风格的程序设计
语言中 case 语言不同的是,在 Ruby 中没有必要输入一个 break 来防止代码执行一个
when 语句块之后继续执行剩下的语句。在 Ruby 中,一旦有一个值与 case 语句匹配,
case 语句就将终止:
case( i )
when 5 : puts("It's Friday" )
9. puts("...nearly the weekend!")
when 6 : puts("It's Saturday!" )
# the following never executes
when 5 : puts( "It's Friday all over again!" )
end
你可以在每个 when 条件之间包含多行代码,你还可以在开关变量中使用多个值,之间使用
逗号分割,如下:
when 6, 7 : puts( "Yippee! It's the weekend! " )
case2.rb
在 case 语句中的条件并不要求一定是一个简单变量,也可以是一个表达式:
case(i+1)
你也可以使用非整型的其他类型变量,如字符串。如果在一个 when 子句中声明了多个开关
值,它们也可以是不同的类型,例如字符串和整型数:
when 1,'Monday','Mon' : puts(“Yup,'#{i}' is Monday”)
这里有一个比较长的例子,举例说明了上面提及到的一些语法形式:
case3.rb
case( i )
when 1 : puts("It's Monday" )
when 2 : puts("It's Tuesday" )
when 3 : puts("It's Wednesday" )
when 4 : puts("It's Thursday" )
when 5 then puts("It's Friday" )
puts("...nearly the weekend!")
when 6, 7
puts("It's Saturday!" ) if i == 6
puts("It's Sunday!" ) if i == 7
puts( "Yippee! It's the weekend! " )
10. # the following never executes
when 5 : puts( "It's Friday all over again!" )
else puts( "That's not a real day!" )
end
===方法
正如我们前面提及的,在 case 语句中的 when 判断是通过===方法来进行的。例如,我们
在 when 判断中使用一个整型区间作为开关,只有当变量值处于这个整型区间时 when 判断
才返回 true:
when (6..7) : puts( "Yippee! It's the weekend! " )
如果你对 Object 类的===方法的作用仍怀有疑问的话,可以参考 Ruby 的关于 Object 类
的文档。
Case 语法的替代
有一种 case 语法的替代形式,它看起来跟一连串的 if..then..else 的简写形式一样。每一个
when 子句可以执行一些随机的判断并执行一行或多行代码。而不需要任何 case 变量。每
一个 when 子句返回一个值,就像一个方法,返回的值为最后的部分代码的值。这个值可以
赋值给 case 语句前面的变量:
case4.rb
salary = 2000000
season = 'summer'
happy = case
when salary > 10000 && season == 'summer':
puts( "Yes, I really am happy!" )
'Very happy' #=> This value is „returned‟
when salary > 500000 && season == 'spring' : 'Pretty happy'
else puts( 'miserable' )
end
11. puts( happy ) #=> “Very happy”
深入探讨
逻辑条件
and &&
这两个操作符是向左结合的,只有左边的值为 true,才会计算右边的值,and 的优先级比&
&要低。
or ||
这两个操作符也是向左结合的,如果左边的值为 false,才会继续计算右边的值,or 的优先级
比||要低
not !
这是对一个布尔值取反,当布尔值为 true 时返回 false,为 false 时返回 true。
当你在选择这些逻辑操作符的时候一定要小心。由于优先级的不同,会导致计算的顺序不同,
结果也会不同。
看看下面的例子:
boolean_ops.rb
# Example 1
if ( 1==3 ) and (2==1) || (3==3) then
puts('true')
else
puts('false')
end
# Example 2
if ( 1==3 ) and (2==1) or (3==3) then
puts('true')
else
puts('false')
12. end
一眼看上去这两个例子非常相似。但事实上,在示例 1 当中会打印出结果'false'而在示例 2
当中结果会是'true'。这完全取决于 or 的优先级比||要低。结果就是,示例 1 当中判断”如
果 1 等于 3[false]并且(2 等于 1 或者 3 等于 3)[true]”。这两个必要条件中的一个为 false,所
以整个判断返回 false。
现在我们来看示例 2。示例 2 的判断是”如果 1 等于 3 并且 2 等于 1[false]或者 3 等于
3[true]”。这一次,我们只需要两个判断中的一个为 true,第二个判断为 true,所以整个
判断为 true。
这些操作符的优先级的副作用在这些判断中将导致非常隐蔽的 bug。你可以通过使用括号加
强代码清晰度来防止其发生。这里,我重写了上面的两个示例;在两个例子中都使用了一对
括号来使得其返回值与上面的例子中恰恰相反:
# Example 1 (b) – now returns true
if (( 1==3 ) and (2==1)) || (3==3) then
puts('true')
else
puts('false')
end
# Example 2 (b) – now returns false
if ( 1==3 ) and ((2==1) or (3==3)) then
puts('true')
else
puts('false')
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,所以整个判断返回为 true。
在这种判断中,运算符的优先级的副作用将会引发一些非常不明显的 bug。你可以通过使用
13. 括号来加强语义清晰度来防止其发生。我还重写了上面的示例 1 和示例 2,在每一个例子中
都多使用了一对括号,这个时候它们的返回值与之前的返回值完全相反:
# Example 1 (b) – now returns true
if (( 1==3 ) and (2==1)) || (3==3) then
puts('true')
else
puts('false')
end
# Example 2 (b) – now returns false
if ( 1==3 ) and ((2==1) or (3==3)) then
puts('true')
else
puts('false')
end
取反
取反运算符一般在一个表达式前使用,也可以在一个表达式中间使用!=(不相等)。
!(1==1) #=> false
1!=1 #=> false
你还可以使用 not 来替代!
not(1==1)
逻辑运算符的古怪
一定要注意 Ruby 的逻辑运算符有点时候会以一种出人意料的方式执行。例如...
puts( (not( 1==1 )) ) # This is ok
puts( not( 1==1 ) ) # This is a syntax error
puts( true && true && !(true) ) # This is ok
puts( true && true and !(true) ) # This is a syntax error
14. puts( ((true) and (true)) ) # This is ok
puts( true && true ) # This is ok
puts( true and true ) # This is a syntax error
在很多情况中,这些问题可以通过坚持一种风格的逻辑运算符(要么使用 and,or,not 或者使
用&&,||,!)而不是将两种风格的运算符混搭着来使用。另外再次强调使用括号的重要性。
Catch 和 Throw
Ruby 提供了一对方法,catch 和 throw,这两个方法可以用于当符合某条件时从执行代码块
中调出来。这是 Ruby 提供的和其他语言中 goto 很相似的一种机制。该代码块必须由
catch 开头,后接一个符号(也就是使用冒号作为前缀的一个唯一标识符),例如:done 或
者:finished。代码块可以使用花括号或者 do 和 end 来界定,如下:
# think of this as a block called :done
catch( :done ){
# some code here
}
# and this is a block called :finished
catch( :finished ) do
# some code here
end
在代码块中,你可以像参数一般使用一个符号来调用 throw。通常,你将在某指定条件吻合
的时候调用 throw 方法,这将跳过该块中其他的所有代码。例如,我们假设代码块中有一
些代码用于提示用户输入一个数字,针对输入的数字区分多种情况分别作其他的较为复杂的
运算。显然,如果用户输入 0 那么之后的所有计算将不会进行,所以你不想运行下面的代码,
想立刻从代码块中跳出继续执行下一个代码段而不是任它按顺序继续执行下去。下面是一种
实现方式:
catch_throw.rb
catch( :finished) do
print( 'Enter a number: ' )
num = gets().chomp.to_i
15. if num == 0 then
throw :finished # if num is 0, jump out of the block
end
# Here there may be hundreds of lines of
# calculations based on the value of num
# if num is 0 this code will be skipped
end
# the throw method causes execution to
# jump to here – outside of the block
puts( "Finished" )
你还可以在 catch 代码块中嵌入其他的 catch 代码块,如下:
catch( :finished) do
print( 'Enter a number: ' )
num = gets().chomp.to_i
if num == 0 then throw :finished end
puts( 100 / num )
catch( :go_for_tea ){
dothings(5)
}
puts( "Things have all been done. Time for tea!" )
end
如同其他语言中的 goto 语句,catch 和 throw 语句在 Ruby 中的使用场景也会极大程度地
破坏你的代码的逻辑,将会潜在地引发一些很难发现的 bug。