# **第 5 章 條件判斷**
本章我們將詳細討論一下控制結構之一的條件判斷,主要包括以下內容。
-
**什么是條件判斷。**
-
**條件判斷中不可或缺的比較運算符、真假值 1、邏輯運算符。**
-
**條件判斷的種類及其寫法和使用方法。**
1也稱布爾值。——譯者注
### **5.1 什么是條件判斷**
接下來,我們來考慮一下如何將公歷轉換為平成紀年 2。首先,我們將輸入的字符串轉換為數值后減去 1988,最后輸出運算結果,結束程序。程序如代碼清單 5.1 所示。
2日本的紀年方法。1989 年為平成元年,2014 年是平成 26 年。——譯者注
**代碼清單 5.1 ad2heisei.rb**
~~~
# 將公歷轉換為平成紀年
ad = ARGV[0].to_i
heisei = ad - 1988
puts heisei
~~~
執行結果如下:
> **執行示例**
~~~
> ruby ad2heisei.rb 2013
25
~~~
但是,這個程序有點小問題。如果我們輸入 1989 年以前的年份,返回值會變成 0 或者負數。
> **執行示例**
~~~
-8
~~~
按道理,1989 年以前的年份是不能轉換為平成 XX 年的,因此程序本不應允許輸入示例中那樣的年份。我們將程序稍微改進一下,若輸入 1989 年以前的年份,程序則返回“無法轉換”的提示。
在這樣的情況下,為了實現程序在“某個條件時執行○○處理,否則執行 ×× 處理”,Ruby 為我們準備了條件判斷語句。
條件判斷語句主要有以下三種。
-
**`if` 語句**
-
**`unless` 語句**
-
**`case` 語句**
接下來,我們將會介紹這些條件判斷語句及其寫法。
### **5.2 Ruby 中的條件**
在說明條件語句之前,我們首先來看看在 Ruby 中是如何寫條件的。
### **條件與真假值**
我們在之前的章節已經介紹過了在條件判斷中常用到的比較運算符。等號`==`,不等號`>`、`<` 等都是比較運算符。
比較的結果分為 `true` 和 `false` 兩種。顧名思義,比較結果正確時為 `true`,錯誤時為 `false`。
除了比較運算符外,Ruby 中還有很多可以作為條件判斷的方法。例如,字符串的 `empty?` 方法,該字符串的長度為 0 時返回 `true`,否則返回 `false`。
~~~
p "".empty? #=> true
p "AAA".empty? #=> false
~~~
另外,除了 `true` 和 `false` 外,還有其他值可作為條件判斷的值。例如,用正則表達式進行匹配時,匹配成功返回該字符串的位置,匹配失敗返回 `nil`。
~~~
p /Ruby/ =~ "Ruby" #=> 0
p /Ruby/ =~ "Diamond" #=> nil
~~~
關于 Ruby 中的真假值的定義,可參考表 5.1。
**表 5.1 Ruby 的真假值**
| **真** | `false` 、 `nil` 以外的所有對象 |
|-----|-----|
| **假** | `false` 、 `nil` |
也就是說,Ruby 會認為 `false` 與 `nil` 代表假,除此以外的所有值都代表真。因此,Ruby 中的真 / 假并非絕對等同于 `true/false`。`true` 代表真,`false` 代表假,同時,不返回 `true` 或 `false` 的方法只要能返回 `nil`,也可作為條件判斷的表達式來使用。另外,在 Ruby 中還有個約定俗成的規則,為了使程序更容易理解,返回真假值的方法都要以 `?` 結尾。建議大家在寫程序時也遵守這個規則。
### **5.3 邏輯運算符**
在判斷多個條件表達式時,我們會用到邏輯運算符 `&&` 和 `||`。
**條件 `1 &&` 條件 `2`**
表示條件 `1` 為真,并且條件 `2` 也為真時,則整體的表達式返回真。兩者中只要一個返回假時,則整體的表達式返回假。
相對地,
**條件 `1 ||` 條件 `2`**
表示條件 `1` 為真,或者條件 `2` 為真時,整體的表達式返回真。兩者同時為假時,則整體的表達式返回假。
還有表示否定的邏輯運算符:
**`!`條件**
表示相反的條件。也就是,條件為假時,表達式返回真;條件為真時,表達式返回假。例如,我們想判斷整數 `x` 是否在 1 到 10 之間,`if` 語句可以這么寫:
~~~
if x >= 1 && x <= 10
┊
end
~~~
與上面的條件相反,表示“1 到 10 以外”時使用`!`,表達式可以寫成 `!(x >= 1 && x <= 10)`。不過,像下面寫成“小于 1,或者大于 10”可能更加直接,更便于理解。
~~~
if x < 1 || x > 10
┊
end
~~~
條件判斷對于控制程序的行為非常重要。過于復雜、難以理解的條件,會使程序的目的也會變得難以琢磨。建議大家在寫程序時,注意盡量寫便于理解的條件。
在 Ruby 中,還有與 `&&`、`||`、`!` 意思相同,但優先級略低的邏輯運算符 `and`、`or`、`not`。關于運算符的優先級,我們將在第 9 章 9.5 節討論。
### **5.4 if 語句**
接下來,我們就來看看條件判斷語句到底如何使用。`if` 語句是最基本的條件判斷語句,用法如下:
**`if` 條件 `then`
處理
`end`**
※ 可以省略 `then`

在這基礎上可再加上 `elsif`、`else` :
**`if` 條件 `1 then`
處理 `1`
`elsif` 條件 `2 then`
處理 `2`
`elsif` 條件 `3 then`
處理 `3`
`else`
處理 `4`
`end`**
※ 可以省略 `then`

Ruby 會按照從上到下的順序進行判斷。首先,條件 `1` 為真時程序執行處理 `1`。條件 `1` 為假時,程序再判斷條件 `2`,若為真時執行處理 `2`。同樣地,條件 `2` 為假時,程序再判斷條件 `3……`本例中雖然只有 `4` 個條件分支,但根據實際需要可以添加無限個的分支。最后,如果前面所有條件都為假時則執行處理 4。
我們來看看使用 `elsif` 的例子(代碼清單 5.2)。
**代碼清單 5.2 if_elsif.rb**
~~~
a = 10
b = 20
if a > b
puts "a 比b 大"
elsif a < b
puts "a 比b 小"
else
puts "a 與b 相等"
end
~~~
這是一個比較 a、b 大小的程序。比較結果分為 a 比 b 大、a 比 b 小或者 a 與 b 相等三種情況。這種情況下,我們可以使用 `if ~ elsif ~ else` 結構。
### **5.5 unless 語句**
`unless` 語句的用法剛好與 `if` 語句相反。`unless` 語句的用法如下:
**`unless` 條件 `then`
處理
`end`**
※ 可以省略 `then`

`unless` 語句的形式和 if 語句一樣。但 `if` 語句是條件為真時執行處理,`unless` 語句則剛好相反,條件為假時執行處理。
下面是使用 `unless` 的例子(代碼清單 5.3)。
**代碼清單 5.3 unless.rb**
~~~
a = 10
b = 20
unless a > b
puts "a 不比b 大"
en
~~~
這個程序執行后輸出“`a` 不比 `b` 大”。`unless` 語句的條件 `a > b` 為假,所以程序執行了 `puts` 方法。
`unless` 語句也可以使用 `else`。
**`unless` 條件
處理 `1`
`else`
處理 `2`
`end`**
這個與下面的 if 語句是等價的。
**`if` 條件
處理 `2`
`else`
處理 `1`
`end`**
對比以上兩種寫法,我們可以知道處理 1 和處理 2 的位置互換了,`if`語句通過這樣的互換,能達到與使用 `unless` 語句時同樣的效果。
### **5.6 case 語句**
條件有多個時,使用 `if` 與 `elsif` 的組合雖然也能達到判斷多個條件的效果,但是如果需要比較的對象只有一個,根據這個對象值的不同,執行不同的處理時,使用 `case` 語句會使程序更簡單,更便于理解。
`case` 語句的用法如下:
**`case` 比較對象
`when` 值 `1 then`
處理 `1`
`when` 值 `2 then`
處理 `2`
`when` 值 `3 then`
處理 `3`
`else`
處理 `4`
`end`**
※ 可以省略 `then`

本例的比較對象的值有 3 個,但根據實際情況可以無限增加下去。
還有,`when` 可以一次指定多個值。下面的示例(代碼清單 5.4)從數組 `tags` 的開頭依次取出元素,判斷元素值,輸出相應的結果。
**代碼清單 5.4 case.rb**
~~~
tags = [ "A", "IMG", "PRE" ]
tags.each do |tagname|
case tagname
when "P","A","I","B","BLOCKQUOTE"
puts "#{tagname} has child."
when "IMG", "BR"
puts "#{tagname} has no child."
else
puts "#{tagname} cannot be used."
end
end
~~~
> **執行示例**
~~~
> ruby case.rb
A has child.
IMG has no child.
PRE cannot be used.
~~~
我們再來看看其他例子。
**代碼清單 5.5 case_class.rb**
~~~
array = [ "a", 1, nil ]
array.each do |item|
case item
when String
puts "item is a String."
when Numeric
puts "item is a Numeric."
else
puts "item is something."
end
end
~~~
> **執行示例**
~~~
> ruby case_class.rb
item is a String.
item is a Numeric.
item is something.
~~~
在本例中,程序判斷傳過來的對象類型是字符串(`String` 類)還是數值(`Numeric` 類),或者均不是以上兩者,然后再輸出相應的結果。
在這里,我們同樣是使用 `case` 語句,不過判斷的主體與之前的例子有點區別。本例中的 `when` 實際并不是直接判斷傳過來的字符串,而是先查找該對象屬于哪個類,然后再根據這個類的信息來進行條件判斷。
我們還可以根據正則表達式的匹配結果進行不同處理。下面是使用正則表達式做判斷的 `case` 語句的例子。
~~~
text.each_line do |line|
case line
when /^From:/i
puts "發現寄信人信息"
when /^To:/i
puts "發現收信人信息"
when /^Subject:/i
puts "發現主題信息"
when /^$/
puts "頭部解析完畢"
exit
else
## 跳出處理
end
end
~~~
這是一個解析電子郵件頭部的程序。為了簡化程序,我們并沒有考慮有多個頭部的情況,而且電子郵件里的內容我們也沒取出來。在這里,大家掌握程序的大概的處理流程就可以了。
`each_line` 方法逐行讀取電子郵件正文數據 `text`,并將每行的內容賦值給變量 `line`。這個是處理文件、文本數據時的典型的寫法。
接著 `case` 語句判斷得到的字符串的內容,執行不同的處理。以 `From:` 開頭時輸出“發現寄信人信息”,以 `To:` 開頭時輸出“發現收信人信息”,以 `Subject:` 開頭時輸出“發現主題信息”。
最后的 `when` 判斷的 `/^$/`,表示行的開頭后馬上就接著是行尾的意思 3,也就是說,這是表示空行的正則表達式。電子郵件的頭部和正文間一定會以空行作間隔,因此根據這個規則我們就可以把空行作為頭部結束的標志。當 `when` 遇到空行,輸出“頭部解析完畢”的信息后調用 `exit` 方法,結束程序。
3在正則表達式中,`^` 表示匹配字符串的開始,`$` 表示匹配字符串的結束。——譯者注
> **專欄**
> === 與 case 語句
> 在 `case` 語句中,`when` 判斷值是否相等時,實際是使用 `===` 運算符來判斷的。左邊是數值或者字符串時,`===` 與`==` 的意義是一樣的,除此以外,`===` 還可以與`=~` 一樣用來判斷正則表達式是否匹配,或者判斷右邊的對象是否屬于左邊的類,等等。對比單純的判斷兩邊的值是否相等,`===` 能表達更加廣義的“相等”。
~~~
p (/zz/ === "xyzzy") #=> true
p (String === "xyzzy") #=> true
p ((1..3) === 2) #=> true
~~~
> 用 `if` 語句改寫 `case` 語句的程序如下所示。請注意 `when` 指定的對象在`===`h 的左邊。
> 
### **5.7 if 修飾符與 unless 修飾符**
`if` 與 `unless` 可以寫在希望執行的代碼的后面。像下面這樣:
~~~
puts "a 比b 大" if a > b
~~~
這與下面的寫法是等價的。
~~~
if a > b
puts "a 比b 大"
end
~~~
使用修飾符的寫法會使程序更加緊湊。通常,我們在希望強調代碼執行的內容時會使用修飾符寫法。同樣地,在使用修飾符寫法時,請大家注意程序的易讀性。
### **5.8 總結**
本章介紹了以下內容。
-
**真假值**
真假值是條件表達式的返回值。
-
**`nil` 或者 `false` 時為假**
-
**除此以外的值為真**
-
**條件判斷語句**
條件判斷語句有:
-
**`if` 語句**
-
**`unless` 語句**
-
**`case` 語句**
-
**比較**
用 `if` 語句、`unless` 語句做比較時,會用到比較運算符(==,!=,<,> 等)、以 ? 結尾的方法、邏輯運算符等。
-
**if 語句、unless 語句**
兩者皆為條件判斷的基本語句。
-
**case 語句**
在遇到像“根據對象的不同狀態,采取不同的處理”那樣的分情況處理時,我們會用到 `case` 語句。
分情況處理時,不同的對象所采取的判斷方法也不一樣。具體來說是根據 === 運算符的比較特性,實現分情況處理。更詳細的說明請參考專欄“`===` 與 `case` 語句”。
大部分的編程語言都有條件判斷。本書也在很多地方使用了條件判斷。大家可以參考這些內容,逐漸熟練掌握什么時候該用哪種條件判斷語句。
> **專欄**
> **對象的同一性**
> 所有的對象都有標識和值。
> 標識(ID)用來表示對象同一性。Ruby 中所有對象都是唯一的,對象的 ID 可以通過 `object_id`(或者 `__id__`)方法取得。
~~~
ary1 = []
ary2 = []
p ary1.object_id #=> 67653636
p ary2.object_id #=> 67650432
~~~
> 我們用 `equal?` 方法判斷兩個對象是否同一個對象(ID 是否相同)。
~~~
str1 = "foo"
str2 = str1
str3 = "f" + "o" + "o"
p str1.equal?(str2) #=> true
p str1.equal?(str3) #=> false
~~~
> 對象的“值”就是對象擁有的信息。例如,只要對象的字符串內容相等,Ruby 就會認為對象的值相等。Ruby 使用 == 來判斷對象的值是否相等。
~~~
str1 = "foo"
str2 = "f" + "o" + "o"
p str1 == str2 #=> true
~~~
> 除了 == 以外,Ruby 還提供 `eql?` 方法用來判斷對象的值是否相等。`==` 與 `eql?` 都是 `Object` 類定義的方法,大部分情況下它們的執行結果都是一樣的。但也有例外,數值類會重定義 `eql?` 方法,因此執行后有不一樣結果。
~~~
p 1.0 == 1 #=> true
p 1.0.eql?(1) #=> false
~~~
> 憑直覺來講,把 `1.0` 與 `1` 判斷為相同的值會更加方便。在一般情況進行值的比較時使用 `==`,但是在一些需要進行更嚴謹的比較的程序中,就需要用到 `eql?` 方法。例如,`0` 與 `0.0` 作為散列的鍵時,會判斷為不同的鍵,這是由于散列對象內部的鍵比較使用了 `eql?` 方法來判斷。
~~~
hash = { 0 => "0"}
p hash[0.0] #=> nil
p hash[0] #=> "0"
~~~
- 推薦序
- 譯者序
- 前言
- 本書的讀者對象
- 第 1 部分 Ruby 初體驗
- 第 1 章 Ruby 初探
- 第 2 章 便利的對象
- 第 3 章 創建命令
- 第 2 部分 Ruby 的基礎
- 第 4 章 對象、變量和常量
- 第 5 章 條件判斷
- 第 6 章 循環
- 第 7 章 方法
- 第 8 章 類和模塊
- 第 9 章 運算符
- 第 10 章 錯誤處理與異常
- 第 11 章 塊
- 第 3 部分 Ruby 的類
- 第 12 章 數值類
- 第 13 章 數組類
- 第 14 章 字符串類
- 第 15 章 散列類
- 第 16 章 正則表達式類
- 第 17 章 IO 類
- 第 18 章 File 類與 Dir 類
- 第 19 章 Encoding 類
- 第 20 章 Time 類與 Date 類
- 第 21 章 Proc 類
- 第 4 部分 動手制作工具
- 第 22 章 文本處理
- 第 23 章 檢索郵政編碼
- 附錄
- 附錄 A Ruby 運行環境的構建
- 附錄 B Ruby 參考集
- 后記
- 謝辭