Ruby 語句與控制結(jié)構(gòu)

迭代器和可枚舉對象

迭代器的描述并不準(zhǔn)確酗宋,像”期待一個(gè)關(guān)聯(lián)代碼塊的方法“這樣的描述更加準(zhǔn)確一些。迭代器是 Ruby 的重要特性之一疆拘。當(dāng)程序執(zhí)行時(shí)蜕猫,遇到迭代器總的 yield 語句時(shí),程序控制流會(huì)從迭代器轉(zhuǎn)移到那個(gè)與迭代器想關(guān)聯(lián)的代碼塊中哎迄,程序執(zhí)行完代碼塊之后回右,迭代器方法重新獲得控制權(quán)并從 yield 語句之后的第一條語句開始執(zhí)行。

yield 語句像一個(gè)方法調(diào)用漱挚,后邊可以接零個(gè)或多個(gè)參數(shù)翔烁,這些值將會(huì)賦給對應(yīng)的代碼塊的形參。

block_given?(同義詞 iterator? )方法可以判斷是否在調(diào)用該方法時(shí)帶有一個(gè)代碼塊旨涝,它們都是 Kernel 模塊定義的蹬屹,所以表現(xiàn)的像全局函數(shù)一樣。

可枚舉對象

Array白华、Hash慨默、Range 和許多其他的類都定義了 each 迭代器。大多數(shù)定義了 each 迭代器的類都包含了 Enumerable 模塊弧腥,它定義了許多更加特殊的迭代器厦取,而它們都是基于 each 方法來實(shí)現(xiàn)的。其中包括 each_with_index管搪、collect(也被稱為 map )蒜胖、select、reject 和 inject 等等抛蚤。

枚舉器

枚舉器是 Enumerable::Enumerator 的實(shí)例,其目的在于枚舉其他對象寻狂。雖然可以通過 new 操作符直接實(shí)例化這個(gè)類岁经,但是通常情況下,我們并不會(huì)通過這種方式來創(chuàng)建枚舉器蛇券,而是使用 Object 類的 to_enum 或其同義詞 enum_for缀壤。

如果調(diào)用的時(shí)候沒有提供參數(shù),那么這個(gè)枚舉器的 each 方法只是簡單的調(diào)用目標(biāo)對象的 each 方法纠亚。例如塘慕,你有一個(gè)數(shù)組和一個(gè)方法,該方法期望一個(gè)可枚舉對象蒂胞。因?yàn)閿?shù)組可變图呢,而且你不確定該方法是否會(huì)修改該數(shù)組,所以不想直接將數(shù)組傳遞給該方法。為了達(dá)到這個(gè)目的蛤织,與其創(chuàng)建一個(gè)該數(shù)組的深度防御拷貝赴叹,還不如直接調(diào)用它的 to_enum 方法。

process(data.to_enum)

你也可以給 to_enum 或 enum_for (顯得更自然一些)方法傳遞參數(shù)指蚜,第一個(gè)參數(shù)應(yīng)該是一個(gè)符號乞巧,表示了一個(gè)迭代器方法(來自原先的對象)。這個(gè)返回的迭代器的 each 方法會(huì)調(diào)用那個(gè)迭代器方法摊鸡。例如绽媒,在 Ruby1.9 中,String 類不是 Enumerable 的免猾,但是它具有3個(gè)迭代器方法: each_char(同名 chars )是辕,each_byte 和 each_line 。但如果我們想使用一個(gè) Enumerable 方法掸刊,比如 map免糕,而且基于 each_char 迭代器。我們可以這樣創(chuàng)建一個(gè)迭代器:

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

在 Ruby1.9 中忧侧,通常都不用顯式的使用 to_enmu 和 enum_for石窑,因?yàn)橐圆粠Тa塊的方式調(diào)用內(nèi)建的迭代器方法時(shí)(包括數(shù)值迭代器、each 和 Enumerable 相關(guān)方法時(shí))蚓炬,它們都會(huì)自動(dòng)的返回一個(gè)枚舉器松逊。因此上邊的連個(gè)例子可以修改為:

 process(data.each)

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

當(dāng)以不帶代碼塊的方式調(diào)用自己的迭代器時(shí),可以通過返回 self.to_enum 的方法來實(shí)現(xiàn)上述行為肯夏。

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

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

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

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

外部迭代器

在 Ruby1.9 中迭代器還有一個(gè)重要作用就是外部迭代器: 外部迭代器烁兰。你可以通過反復(fù)調(diào)用一個(gè)枚舉器的 next 方法來遍歷一個(gè)集合的元素。

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

外部迭代器的使用很簡單徊都,每次需要另一個(gè)元素時(shí)調(diào)用 next 方法即可沪斟,遍歷完元素之后,next 拋出一個(gè) StopIteration 異常暇矫。

Kernel.loop 方法包含了一個(gè)隱式的 rescue 從句主之,而且在 StopIteration 拋出時(shí)干凈利落的退出循環(huán)。前邊例子可以改寫如下:

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

使用 rewind 方法可以是許多外部迭代器重新開始迭代李根,但是如果一個(gè)迭代器像 File 這樣從文件中順序讀入行的對象槽奕,那么調(diào)用 remind 方法并不能使其重新開始迭代》拷危總的來說粤攒,如果調(diào)用底層 Enumeralbe 對象的 each 方法并不能使其重新開始迭代所森,那么調(diào)用rewind 的方法也不會(huì)有效。

一個(gè)外部迭代器一旦啟動(dòng)(第一次調(diào)用 next 方法之后)琼讽,就不能在克隆和賦值該迭代器必峰。可以克隆一個(gè)迭代器的典型時(shí)機(jī)是:next 被調(diào)用之前钻蹬、StopIteration 被拋出之后吼蚁,或者在 rewind 被調(diào)用之后。

外部迭代器比內(nèi)部迭代器更加靈活问欠,它們可以解決兩個(gè)迭代器的并行迭代的問題肝匆。

代碼塊

代碼塊的值

一個(gè)代碼塊的“返回值”就是它最后邊執(zhí)行那個(gè)表達(dá)式的值。一般來說顺献,你不應(yīng)該將使用 return 關(guān)鍵字來從代碼塊中返回旗国。一個(gè)位于代碼塊中的 return 將會(huì)導(dǎo)致包含該代碼塊的那個(gè)方法返回。如果你希望指定一個(gè)代碼塊的返回值應(yīng)該使用 next注整。

變量作用域

代碼塊定義了一個(gè)新的變量作用域能曾,但是在一個(gè)作用域中定義的局部變量,在該作用域中所有的代碼塊中都可見肿轨。

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

從 Ruby1.9 開始寿冕,代碼塊的形參作用域范圍始終都在代碼塊內(nèi)。如果使用 -w 選項(xiàng)椒袍,那么當(dāng)一個(gè)代碼塊形參和一個(gè)已經(jīng)存在的變量重名時(shí)驼唱,它就會(huì)發(fā)出警告。另外驹暑,你也可以聲明塊級局部變量玫恳,如下:

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

傳遞實(shí)參

Ruby1.9 使代碼塊形參作用域范圍嚴(yán)格的局部于代碼塊本省,這就意味著优俘,全局或?qū)嵗兞坎辉偈呛侠淼拇a塊形參了京办。

與方法調(diào)用比起來,yield 關(guān)鍵字后邊的實(shí)參值傳遞給代碼塊形參的給類似于并行賦值規(guī)則帆焕,但是也部完全一樣惭婿。如果一個(gè)迭代器將兩個(gè)值傳遞給它的代碼塊,但是代碼塊只接受一個(gè)參數(shù)视搏,Ruby 并不會(huì)像并行賦值一樣將兩個(gè)參數(shù)合并成為一個(gè)數(shù)組。

def two
  yield 1, 2
end

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

和并行賦值一樣县袱,1.9中浑娜,無論代碼塊形參在參數(shù)列表的什么位置,都可以具有一個(gè) * 前綴式散。

和方法調(diào)用一樣筋遭,yield 也允許不帶花括號的哈希作為其最后一個(gè)參數(shù)。

1.9中,最后一個(gè)代碼塊形參可以具有一個(gè) & 前綴漓滔,表示它將接受與該代碼塊相關(guān)的任何代碼塊编饺。

代碼塊形參和方法形參有一個(gè)重要的區(qū)別就是,代碼塊形參不允許有默認(rèn)值响驴。一種創(chuàng)建 proc 對象的字面量語法才允許有默認(rèn)值透且。

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

改變控制流

return

當(dāng) return 語句位于一個(gè)代碼塊的時(shí)候(無論嵌套多深),它總會(huì)使得外圍的方法返回豁鲤,即它不僅會(huì)使得代碼塊返回秽誊,還會(huì)使得調(diào)用代碼塊的那個(gè)迭代器返回,而且它還會(huì)使得外圍方法返回琳骡。

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

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

break

當(dāng)被用在一個(gè)代碼塊中時(shí)楣号,break 不僅將控制權(quán)傳遞出代碼塊最易,而且傳遞到調(diào)用代碼塊的迭代器之外。和 return 不一樣炫狱,break 并不會(huì)使外圍方法返回藻懒。

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

break 只能出現(xiàn)在一個(gè)詞法上外圍循環(huán)或代碼塊里,其他任何上下問使用 break 都會(huì)導(dǎo)致一個(gè) LocalJumpError毕荐。

break 可以為他所跳出的循環(huán)或迭代器指定一個(gè)值束析。如果 break 表達(dá)式后邊沒有表達(dá)式,那么循環(huán)表達(dá)式和迭代器的返回值就是 nil憎亚。

next

next 語句使一個(gè)循環(huán)或迭代器結(jié)束當(dāng)前的迭代员寇,開始下一輪迭代。當(dāng)用在一個(gè)代碼塊里的時(shí)候第美,next 使代碼塊立即結(jié)束蝶锋,將控制權(quán)返回給迭代器的方法。

next 后邊也可以接一個(gè)表達(dá)式什往,當(dāng)用在一個(gè)循環(huán)當(dāng)中扳缕,next 之后的任何值都會(huì)被忽略。當(dāng)用在代碼快中時(shí)别威,next 之后的值會(huì)被當(dāng)作 yield 語句的返回值躯舔。

redo

redo 將控制權(quán)傳遞到循環(huán)或代碼塊的開頭,重新開始當(dāng)前迭代省古。它不會(huì)重新測試循環(huán)條件粥庄,也不會(huì)獲取迭代器的下一個(gè)元素。redo 語句并不是一個(gè)常用語句豺妓,它一種用法是從用戶輸入錯(cuò)誤中恢復(fù)過來惜互。

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 不僅可以跳出當(dāng)前循環(huán)或代碼塊,而且可以向外跳出任意數(shù)量級训堆,使與 catch 一同定義的代碼塊退出描验。

下面展示了如何“跳出”嵌套循環(huán):

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坑鱼,隨后出現(xiàn)的幾起案子膘流,更是在濱河造成了極大的恐慌,老刑警劉巖姑躲,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睡扬,死亡現(xiàn)場離奇詭異,居然都是意外死亡黍析,警方通過查閱死者的電腦和手機(jī)卖怜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阐枣,“玉大人马靠,你說我怎么就攤上這事“剑” “怎么了甩鳄?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長额划。 經(jīng)常有香客問我妙啃,道長,這世上最難降的妖魔是什么俊戳? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任揖赴,我火速辦了婚禮,結(jié)果婚禮上抑胎,老公的妹妹穿的比我還像新娘燥滑。我一直安慰自己,他們只是感情好阿逃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布铭拧。 她就那樣靜靜地躺著,像睡著了一般恃锉。 火紅的嫁衣襯著肌膚如雪搀菩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天破托,我揣著相機(jī)與錄音肪跋,去河邊找鬼。 笑死炼团,一個(gè)胖子當(dāng)著我的面吹牛澎嚣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瘟芝,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼易桃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锌俱?” 一聲冷哼從身側(cè)響起晤郑,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贸宏,沒想到半個(gè)月后造寝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吭练,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年诫龙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲫咽。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡签赃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出分尸,到底是詐尸還是另有隱情锦聊,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布箩绍,位于F島的核電站孔庭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏材蛛。R本人自食惡果不足惜圆到,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仰税。 院中可真熱鬧构资,春花似錦、人聲如沸陨簇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽河绽。三九已至己单,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耙饰,已是汗流浹背纹笼。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留苟跪,地道東北人廷痘。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓蔓涧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笋额。 傳聞我的和親對象是個(gè)殘疾皇子元暴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容