Ruby 語句與控制結構

迭代器和可枚舉對象

迭代器的描述并不準確,像”期待一個關聯代碼塊的方法“這樣的描述更加準確一些。迭代器是 Ruby 的重要特性之一。當程序執行時,遇到迭代器總的 yield 語句時,程序控制流會從迭代器轉移到那個與迭代器想關聯的代碼塊中,程序執行完代碼塊之后,迭代器方法重新獲得控制權并從 yield 語句之后的第一條語句開始執行。

yield 語句像一個方法調用,后邊可以接零個或多個參數,這些值將會賦給對應的代碼塊的形參。

block_given?(同義詞 iterator? )方法可以判斷是否在調用該方法時帶有一個代碼塊,它們都是 Kernel 模塊定義的,所以表現的像全局函數一樣。

可枚舉對象

Array、Hash、Range 和許多其他的類都定義了 each 迭代器。大多數定義了 each 迭代器的類都包含了 Enumerable 模塊,它定義了許多更加特殊的迭代器,而它們都是基于 each 方法來實現的。其中包括 each_with_index、collect(也被稱為 map )、select、reject 和 inject 等等。

枚舉器

枚舉器是 Enumerable::Enumerator 的實例,其目的在于枚舉其他對象。雖然可以通過 new 操作符直接實例化這個類,但是通常情況下,我們并不會通過這種方式來創建枚舉器,而是使用 Object 類的 to_enum 或其同義詞 enum_for。

如果調用的時候沒有提供參數,那么這個枚舉器的 each 方法只是簡單的調用目標對象的 each 方法。例如,你有一個數組和一個方法,該方法期望一個可枚舉對象。因為數組可變,而且你不確定該方法是否會修改該數組,所以不想直接將數組傳遞給該方法。為了達到這個目的,與其創建一個該數組的深度防御拷貝,還不如直接調用它的 to_enum 方法。

process(data.to_enum)

你也可以給 to_enum 或 enum_for (顯得更自然一些)方法傳遞參數,第一個參數應該是一個符號,表示了一個迭代器方法(來自原先的對象)。這個返回的迭代器的 each 方法會調用那個迭代器方法。例如,在 Ruby1.9 中,String 類不是 Enumerable 的,但是它具有3個迭代器方法: each_char(同名 chars ),each_byte 和 each_line 。但如果我們想使用一個 Enumerable 方法,比如 map,而且基于 each_char 迭代器。我們可以這樣創建一個迭代器:

s = "hello"
s.enum_for(:each_char).map { |c| c.succ }    # ["i", "f", "m", "m", "p"]

在 Ruby1.9 中,通常都不用顯式的使用 to_enmu 和 enum_for,因為以不帶代碼塊的方式調用內建的迭代器方法時(包括數值迭代器、each 和 Enumerable 相關方法時),它們都會自動的返回一個枚舉器。因此上邊的連個例子可以修改為:

 process(data.each)

s="hello"
s.chars.map{ |c| c.succ }

當以不帶代碼塊的方式調用自己的迭代器時,可以通過返回 self.to_enum 的方法來實現上述行為。

def twice
  if block_given?
    yield
    yield
  else
    self.to_enum(:twice)
  end
end

Ruby1.9 中還定義了 with_index 方法,它只是返回一個新的枚舉器,為迭代添加索引形參。

s = "hello"
enumerator = s.each_char.with_index

enumerator.each do |char, index|
  puts index.to_s + " " + char
end

外部迭代器

在 Ruby1.9 中迭代器還有一個重要作用就是外部迭代器: 外部迭代器。你可以通過反復調用一個枚舉器的 next 方法來遍歷一個集合的元素。

iterator = 9.downto(1)
begin
  print iterator.next while true
rescue StopIteration
  puts "...blastoff!"
end

外部迭代器的使用很簡單,每次需要另一個元素時調用 next 方法即可,遍歷完元素之后,next 拋出一個 StopIteration 異常。

Kernel.loop 方法包含了一個隱式的 rescue 從句,而且在 StopIteration 拋出時干凈利落的退出循環。前邊例子可以改寫如下:

iterator = 9.downto(1)
loop do
  print iterator.next
end
puts "...blastoff!"

使用 rewind 方法可以是許多外部迭代器重新開始迭代,但是如果一個迭代器像 File 這樣從文件中順序讀入行的對象,那么調用 remind 方法并不能使其重新開始迭代??偟膩碚f,如果調用底層 Enumeralbe 對象的 each 方法并不能使其重新開始迭代,那么調用rewind 的方法也不會有效。

一個外部迭代器一旦啟動(第一次調用 next 方法之后),就不能在克隆和賦值該迭代器??梢钥寺∫粋€迭代器的典型時機是:next 被調用之前、StopIteration 被拋出之后,或者在 rewind 被調用之后。

外部迭代器比內部迭代器更加靈活,它們可以解決兩個迭代器的并行迭代的問題。

代碼塊

代碼塊的值

一個代碼塊的“返回值”就是它最后邊執行那個表達式的值。一般來說,你不應該將使用 return 關鍵字來從代碼塊中返回。一個位于代碼塊中的 return 將會導致包含該代碼塊的那個方法返回。如果你希望指定一個代碼塊的返回值應該使用 next。

變量作用域

代碼塊定義了一個新的變量作用域,但是在一個作用域中定義的局部變量,在該作用域中所有的代碼塊中都可見。

total = 0
data = [1, 2, 3]
data.each { |x| total += x }
puts total

從 Ruby1.9 開始,代碼塊的形參作用域范圍始終都在代碼塊內。如果使用 -w 選項,那么當一個代碼塊形參和一個已經存在的變量重名時,它就會發出警告。另外,你也可以聲明塊級局部變量,如下:

x = y = 0
1.upto(4) do |x;y|
  y = x + 1
  puts y*y
end
[x, y]    # [0, 0]

傳遞實參

Ruby1.9 使代碼塊形參作用域范圍嚴格的局部于代碼塊本省,這就意味著,全局或實例變量不再是合理的代碼塊形參了。

與方法調用比起來,yield 關鍵字后邊的實參值傳遞給代碼塊形參的給類似于并行賦值規則,但是也部完全一樣。如果一個迭代器將兩個值傳遞給它的代碼塊,但是代碼塊只接受一個參數,Ruby 并不會像并行賦值一樣將兩個參數合并成為一個數組。

def two
  yield 1, 2
end

two{ |x| p x }    # 1
two{ |*x| p x }    # [1, 2]
two{ |x,| p x }    # 1

和并行賦值一樣,1.9中,無論代碼塊形參在參數列表的什么位置,都可以具有一個 * 前綴。

和方法調用一樣,yield 也允許不帶花括號的哈希作為其最后一個參數。

1.9中,最后一個代碼塊形參可以具有一個 & 前綴,表示它將接受與該代碼塊相關的任何代碼塊。

代碼塊形參和方法形參有一個重要的區別就是,代碼塊形參不允許有默認值。一種創建 proc 對象的字面量語法才允許有默認值。

[1, 2, 3].each &->(x, y=10) { print x*y }

改變控制流

return

當 return 語句位于一個代碼塊的時候(無論嵌套多深),它總會使得外圍的方法返回,即它不僅會使得代碼塊返回,還會使得調用代碼塊的那個迭代器返回,而且它還會使得外圍方法返回。

def find(array, target)
  array.each_with_index do |element, index|
    puts "haha"
    return index if (element == target)
  end
  nil
end

值得注意的是,普通代碼塊和 lambda 表達式中的 return 行為并不一致。

break

當被用在一個代碼塊中時,break 不僅將控制權傳遞出代碼塊,而且傳遞到調用代碼塊的迭代器之外。和 return 不一樣,break 并不會使外圍方法返回。

arr = [1, 2, 3, 4, 5]
arr.each do |i|
  break if i == 4
  puts i
end

break 只能出現在一個詞法上外圍循環或代碼塊里,其他任何上下問使用 break 都會導致一個 LocalJumpError。

break 可以為他所跳出的循環或迭代器指定一個值。如果 break 表達式后邊沒有表達式,那么循環表達式和迭代器的返回值就是 nil。

next

next 語句使一個循環或迭代器結束當前的迭代,開始下一輪迭代。當用在一個代碼塊里的時候,next 使代碼塊立即結束,將控制權返回給迭代器的方法。

next 后邊也可以接一個表達式,當用在一個循環當中,next 之后的任何值都會被忽略。當用在代碼快中時,next 之后的值會被當作 yield 語句的返回值。

redo

redo 將控制權傳遞到循環或代碼塊的開頭,重新開始當前迭代。它不會重新測試循環條件,也不會獲取迭代器的下一個元素。redo 語句并不是一個常用語句,它一種用法是從用戶輸入錯誤中恢復過來。

puts "Please enter the first word you think of"
words = %w(apple banana cherry)
response = words.collect do |word|
  print word + "> "
  response = gets.chop
  if response.size == 0
    word.upcase!
    redo
  end
  response
end

throw 和 catch

throw 和 catch 是 Kernel 模塊的方法。throw 不僅可以跳出當前循環或代碼塊,而且可以向外跳出任意數量級,使與 catch 一同定義的代碼塊退出。

下面展示了如何“跳出”嵌套循環:

for matrix in data do
  catch :missing_data do
    for row in matrix do
      for value in row do
        throw :missing_data unless value
        puts "#{value}"
      end
    end
  end
end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容