控制表達式¶ ↑
Ruby 有多種方式來控制執行。這裡描述的所有表達式都會傳回一個值。
對於這些控制表達式中的測試,nil
和 false
是 false 值,而 true
和任何其他物件都是 true 值。在此文件中,「true」表示「true 值」,而「false」表示「false 值」。
if
表達式¶ ↑
最簡單的 if
表達式有兩個部分,一個「測試」表達式和一個「then」表達式。如果「測試」表達式的評估結果為真,則會評估「then」表達式。
以下是一個簡單的 if 陳述式
if true then puts "the test resulted in a true-value" end
這會列印「測試結果為真值」。
then
是可選的
if true puts "the test resulted in a true-value" end
這份文件會省略所有表達式的可選 then
,因為這是 if
最常見的用法。
您也可以新增一個 else
表達式。如果測試評估結果不為真,則會執行 else
表達式
if false puts "the test resulted in a true-value" else puts "the test resulted in a false-value" end
這會列印「測試結果為假值」。
您可以使用 elsif
為 if 表達式新增任意數量的額外測試。當 elsif
上方的所有測試都為假時,elsif
會執行。
a = 1 if a == 0 puts "a is zero" elsif a == 1 puts "a is one" else puts "a is some other value" end
這會列印「a 為一」,因為 1
不等於 0
。由於 else
僅在沒有符合條件時執行。
一旦條件符合,無論是 if
條件或任何 elsif
條件,if
表達式即完成,且不會執行進一步的測試。
與 if
一樣,elsif
條件後面可以接 then
。
在此範例中,只會列印「a 為一」
a = 1 if a == 0 puts "a is zero" elsif a == 1 puts "a is one" elsif a >= 1 puts "a is greater than or equal to one" else puts "a is some other value" end
if
和 elsif
的測試可能會有副作用。最常見的副作用是用法是將值快取到區域變數中
if a = object.some_value # do something to a end
if
表達式的結果值是表達式中執行的最後一個值。
三元運算子 if¶ ↑
您也可以使用 ?
和 :
來撰寫 if-then-else 表達式。此三元運算子 if
input_type = gets =~ /hello/i ? "greeting" : "other"
與此 if
表達式相同
input_type = if gets =~ /hello/i "greeting" else "other" end
雖然三元運算子 if 比更冗長的格式短得多,但為了可讀性,建議僅將三元運算子 if 用於簡單的條件式。此外,避免在同一個表達式中使用多個三元運算子條件,因為這可能會令人困惑。
unless
表達式¶ ↑
unless
表達式與 if
表達式相反。如果值為假,則會執行「then」表達式
unless true puts "the value is a false-value" end
這不會印出任何東西,因為 true 並非 false 值。
你可以使用一個選用的 then
與 unless
,就像 if
一樣。
請注意,以上的 unless
表達式與以下相同
if not true puts "the value is a false-value" end
就像 if
表達式,你可以使用一個 else
條件與 unless
unless true puts "the value is false" else puts "the value is true" end
這會從 else
條件印出「值為 true」。
你不能在 unless
表達式中使用 elsif
。
unless
表達式的結果值是表達式中執行的最後一個值。
修改器 if
和 unless
¶ ↑
if
和 unless
也能用於修改表達式。當用作修改器時,左手邊是「then」敘述,右手邊是「測試」表達式
a = 0 a += 1 if a.zero? p a
這會印出 1。
a = 0 a += 1 unless a.zero? p a
這會印出 0。
雖然修改器和標準版本都有「測試」表達式和「then」敘述,但由於解析順序,它們並非彼此的完全轉換。以下是一個顯示差異的範例
p a if a = 0.zero?
這會引發 NameError
「未定義的局部變數或方法『a』」。
當 Ruby 解析此表達式時,它首先在「then」表達式中將 a
視為方法呼叫,然後它在「測試」表達式中看到對 a
的指派,並將 a
標記為局部變數。
執行此行時,它首先執行「測試」表達式 a = 0.zero?
。
由於測試為 true,因此它執行「then」表達式 p a
。由於主體中的 a
被記錄為不存在的方法,因此引發 NameError
。
unless
也是如此。
case
表達式¶ ↑
case
表達式可以用兩種方式。
最常見的方式是將物件與多個模式進行比較。模式使用 +===+ 方法進行比對,該方法在 Object
上別名為 +==+。其他類別必須覆寫它才能提供有意義的行為。請參閱 Module#===
和 Regexp#===
以取得範例。
以下是使用 case
將 String
與模式進行比較的一個範例
case "12345" when /^1/ puts "the string starts with one" else puts "I don't know what the string starts with" end
在此,字串 "12345"
與 /^1/
進行比較,方法是呼叫 /^1/ === "12345"
,它會傳回 true
。就像 if
敘述一樣,第一個符合的 when
會執行,而其他所有符合的則會忽略。
如果找不到符合的,則會執行 else
。
else
和 then
是選用的,這個 case
敘述會產生與上面相同的結果
case "12345" when /^1/ puts "the string starts with one" end
您可以在同一個 when
上放置多個條件
case "2" when /^1/, "2" puts "the string starts with one or is '2'" end
Ruby 會輪流嘗試每個條件,所以首先 /^1/ === "2"
會傳回 false
,然後 "2" === "2"
會傳回 true
,因此會印出「字串以 1 開頭或為『2』」。
您可以在 when
條件後使用 then
。這最常被用於將 when
的主體放在單一行上。
case a when 1, 2 then puts "a is one or two" when 3 then puts "a is three" else puts "I don't know what a is" end
使用 case
敘述的另一種方法就像 if-elsif 敘述
a = 2 case when a == 1, a == 2 puts "a is one or two" when a == 3 puts "a is three" else puts "I don't know what a is" end
同樣地,then
和 else
是選用的。
case
敘述的結果值是敘述中執行的最後一個值。
自 Ruby 2.7 起,case
敘述也透過 in
關鍵字提供更強大的實驗模式比對功能
case {a: 1, b: 2, c: 3} in a: Integer => m "matched: #{m}" else "not matched" end # => "matched: 1"
模式比對語法說明在 其專屬頁面 上。
while
迴圈¶ ↑
while
迴圈會在條件為真時執行
a = 0 while a < 10 do p a a += 1 end p a
印出數字 0 到 10。條件 a < 10
會在進入迴圈前檢查,然後主體執行,然後再次檢查條件。當條件結果為假時,迴圈會終止。
do
關鍵字是選用的。下列迴圈等同於上面的迴圈
while a < 10 p a a += 1 end
while
迴圈的結果為 nil
,除非使用 break
來提供值。
until
迴圈¶ ↑
until
迴圈會在條件為假時執行
a = 0 until a > 10 do p a a += 1 end p a
這會印出數字 0 到 11。就像 while 迴圈一樣,條件 a > 10
會在進入迴圈時檢查,以及每次迴圈主體執行時檢查。如果條件為假,迴圈將繼續執行。
就像 while
迴圈一樣,do
是選用的。
就像 while
迴圈一樣,until
迴圈的結果為 nil,除非使用 break
。
for
迴圈¶ ↑
for
迴圈包含 for
,後接一個變數以包含反覆運算引數,接著是 in
和使用 each 反覆運算的值。do
是選用的
for value in [1, 2, 3] do puts value end
印出 1、2 和 3。
如同 while
和 until
,do
是選用的。
for
迴圈類似於使用 each,但不會建立新的變數範圍。
for
迴圈的結果值是反覆運算的值,除非使用 break
。
for
迴圈很少用於現代的 ruby 程式。
修改器 while
和 until
¶ ↑
如同 if
和 unless
,while
和 until
可用作修改器
a = 0 a += 1 while a < 10 p a # prints 10
until
用作修改器
a = 0 a += 1 until a > 10 p a # prints 11
可以使用 begin
和 end
建立一個 while
迴圈,在條件之前執行一次主體
a = 0 begin a += 1 end while a < 10 p a # prints 10
如果您不使用 rescue
或 ensure
,Ruby 會最佳化任何例外處理的開銷。
break
陳述式¶ ↑
使用 break
提早離開區塊。如果 values
中的其中一個是偶數,這將停止反覆運算這些項目
values.each do |value| break if value.even? # ... end
您也可以使用 break
終止 while
迴圈
a = 0 while true do p a a += 1 break if a < 10 end p a
這會印出數字 0 和 1。
break
接受一個值,提供它「中斷」的表達式的結果
result = [1, 2, 3].each do |value| break value * 2 if value.even? end p result # prints 4
next
陳述式¶ ↑
使用 next
跳過目前反覆運算的其餘部分
result = [1, 2, 3].map do |value| next if value.even? value * 2 end p result # prints [2, nil, 6]
next
接受一個引數,可用作目前區塊反覆運算的結果
result = [1, 2, 3].map do |value| next value if value.even? value * 2 end p result # prints [2, 2, 6]
redo
陳述式¶ ↑
使用 redo
重新執行目前的反覆運算
result = [] while result.length < 10 do result << result.length redo if result.last.even? result << result.length + 1 end p result
這會印出 [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11]
在 Ruby 1.8 中,您也可以在使用 redo
的地方使用 retry
。這不再成立,現在您會在 rescue
區塊外使用 retry
時收到 SyntaxError
。請參閱 例外 以了解 retry
的正確用法。
修改器陳述式¶ ↑
Ruby 的語法區分陳述式和表達式。所有表達式都是陳述式(表達式是一種陳述式),但並非所有陳述式都是表達式。語法的某些部分接受表達式,而不接受其他類型的陳述式,這會導致看起來相似的程式碼以不同的方式進行剖析。
例如,當未用作修改器時,if
、else
、while
、until
和 begin
是表達式(也是陳述式)。但是,當用作修改器時,if
、else
、while
、until
和 rescue
是陳述式,但不是表達式。
if true; 1 end # expression (and therefore statement) 1 if true # statement (not expression)
無法在預期表達式的環境中使用非表達式的陳述,例如方法引數。
puts( 1 if true ) #=> SyntaxError
你可以用括弧包住陳述來建立表達式。
puts((1 if true)) #=> 1
如果你在方法名稱與開啟括弧之間加上空白,你就不需要兩組括弧。
puts (1 if true) #=> 1, because of optional parentheses for method
這是因為這會被解析成類似沒有括弧的方法呼叫。它等同於下列程式碼,但沒有建立區域變數
x = (1 if true) p x
在修改器陳述中,左手邊必須是陳述,右手邊必須是表達式。
因此在 a if b rescue c
中,因為 b rescue c
是非表達式的陳述,因此不能作為 if
修改器陳述的右手邊,程式碼勢必會被解析成 (a if b) rescue c
。
這會與運算子優先順序互動,例如
stmt if v = expr rescue x stmt if v = expr unless x
會被解析成
stmt if v = (expr rescue x) (stmt if v = expr) unless x
這是因為修改器 rescue
的優先順序高於 =
,而修改器 if
的優先順序低於 =
。
反轉¶ ↑
反轉是一種有點特別的條件式表達式。它的一個典型用途是處理使用 ruby -n
或 ruby -p
的 ruby 單行程式中的文字。
反轉的形式是一個表達式,表示反轉開啟的時間,..
(或 ...
),然後是一個表達式,表示反轉關閉的時間。當反轉開啟時,它會持續評估為 true
,而關閉時則為 false
。
以下是一個範例
selected = [] 0.upto 10 do |value| selected << value if value==2..value==8 end p selected # prints [2, 3, 4, 5, 6, 7, 8]
在上面的範例中,「開啟」條件是 n==2
。反轉最初對 0 和 1 為「關閉」(false),但對 2 變成「開啟」(true)並持續到 8。「開啟」後,它會關閉並對 9 和 10 保持「關閉」狀態。
反轉必須用在條件式中,例如 !
、? :
、not
、if
、while
、unless
、until
等,包括修改器形式。
當你使用包含範圍(..
)時,「關閉」條件會在「開啟」條件改變時評估
selected = [] 0.upto 5 do |value| selected << value if value==2..value==2 end p selected # prints [2]
在此,對 flip-flop 的兩側進行評估,因此只有當 value
等於 2 時,flip-flop 才會開啟和關閉。由於 flip-flop 在反覆運算中開啟,因此傳回 true。
當您使用獨佔範圍 (...
) 時,會在下列反覆運算中評估「關閉」條件
selected = [] 0.upto 5 do |value| selected << value if value==2...value==2 end p selected # prints [2, 3, 4, 5]
在此,當 value
等於 2 時,flip-flop 會開啟,但不會在同一個反覆運算中關閉。「關閉」條件不會評估到下一個反覆運算,而 value
也不會再變成 2。
throw/catch¶ ↑
throw
和 catch
用於在 Ruby 中實作非區域控制流程。它們的操作方式類似於例外,允許控制直接從呼叫 throw
的位置傳遞到呼叫配對 catch
的位置。throw
/catch
和使用例外的主要差別在於,throw
/catch
是設計用於預期的非區域控制流程,而例外則是設計用於例外控制流程情況,例如處理意外錯誤。
當使用 throw
時,您會提供 1-2 個引數。第一個引數是配對 catch
的值。第二個引數是選用的 (預設為 nil
),如果在 catch
區塊內有配對的 throw
,它將會是 catch
傳回的值。如果在 catch
區塊內沒有呼叫配對的 throw
方法,catch
方法會傳回傳遞給它的區塊的傳回值。
def a(n) throw :d, :a if n == 0 b(n) end def b(n) throw :d, :b if n == 1 c(n) end def c(n) throw :d if n == 2 end 4.times.map do |i| catch(:d) do a(i) :default end end # => [:a, :b, nil, :default]
如果您傳遞給 throw
的第一個引數未由配對的 catch
處理,將會引發 UncaughtThrowError
例外。這是因為 throw
/catch
應該只用於預期的控制流程變更,因此使用尚未預期的值會造成錯誤。
throw
/catch
是以 Kernel
方法 (Kernel#throw
和 Kernel#catch
) 實作,而不是關鍵字。因此,如果您在 BasicObject
環境中,它們無法直接使用。在此情況下,您可以使用 Kernel.throw
和 Kernel.catch
BasicObject.new.instance_exec do def a b end def b c end def c ::Kernel.throw :d, :e end result = ::Kernel.catch(:d) do a end result # => :e end