<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # **第 11 章 塊** Ruby 中大量使用了塊(block)。塊原本只是為了循環而產生的語法結構,但現在程序中許多地方也都使用了塊。因此,如何靈活地使用塊,也是 Ruby 的重點之一。 下面就讓我們來討論一下塊的作用及其用途。 ### **11.1 塊是什么** 塊 1 就是在調用方法時,能與參數一起傳遞的多個處理的集合。之前在介紹 `each` 方法、`time` 方法等與循環有關的部分時,我們就已經接觸過塊。接收塊的方法會執行必要次數的塊。塊的執行次數由方法本身決定,因此不需事前指定,甚至有可能一次都不執行。 1有時也稱代碼塊。——譯者注 在下面的例子中,我們使用 `each` 方法,把保存在 `Array` 對象中的各個整數依次取 2 次冪后輸出。`do` 和 `end` 之間的部分就是所謂的塊。在本例中,塊總共被執行了 5 次。 ~~~ [1, 2, 3, 4, 5].each do |i| puts i ** 2 end ~~~ 正如在第 7 章中所介紹的那樣,我們把這樣的方法調用稱為“調用帶塊的方法”或者“調用塊”。塊的調用方法一般采用以下形式。 **對象. 方法名( 參數列表) `do` | 塊變量 |  希望循環的處理 `end`** 或者 **對象. 方法名( 參數列表) { | 塊變量 |  希望循環的處理 }** 塊的開頭是塊變量。塊變量就是在執行塊的時候,從方法傳進來的參數。不同方法的塊變量個數也不相同。例如,在 `Array#each` 方法中,數組的元素會作為塊變量被逐個傳遞到塊中。而在 `Array#each_with_index` 方法中,則是 [ 元素 , 索引 ] 兩個值被傳遞到塊中。 > **執行示例** ![{%}](https://box.kancloud.cn/2015-10-26_562e01ec04111.png) 而在第 6 章中介紹的 `loop` 方法則不需要傳遞塊變量。 ### **11.2 塊的使用方法** ### **11.2.1 循環** 在 Ruby 中,我們常常使用塊來實現循環。在接收塊的方法中,實現了循環處理的方法稱為迭代器(iterator)。`each` 方法就是一個典型的迭代器。 在下面的例子中,我們把數組的各個元素轉換為大寫后輸出。 ~~~ alphabet = ["a", "b", "c", "d", "e"] alphabet.each do |i| puts i.upcase end ~~~ 和數組一樣,散列也能將元素一個個拿出來,但與數組不同的是,散列會將 `[key, value]` 的組合作為數組來提取元素。如代碼清單 11.1 所示,可以成對地提取散列的全部鍵、值。本例中使用 `pair[1]` 提取并合計了散列的值,提取散列的鍵時則可以使用 `pair[0]`。 **代碼清單 11.1 hash_each.rb** ~~~ sum = 0 outcome = {"參加費"=>1000, "掛件費用"=>1000, "聯歡會費用"=>4000} outcome.each do |pair| sum += pair[1] # 指定值 end puts "合計:#{sum}" ~~~ 在接收塊變量時,多重賦值規則也是同樣適用的。我們稍微把代碼清單 11.1 的程序修改一下,使之變成代碼清單 11.2 那樣,這樣一來,鍵、值就可以被分別賦值給不同的變量了。 **代碼清單 11.2 hash_each2.rb** ~~~ sum = 0 outcome = {"參加費"=>1000, "掛件費用"=>1000, "聯歡會費用"=>4000} outcome.each do |item, price| sum += price end puts "合計:#{sum}" ~~~ `File` 對象被用于讀寫文件的內容。使用 `File` 對象可將文件數據從頭到尾讀取出來。 根據文件內容的不同,我們需要考慮是以字符為單位,還是以行為單位來做讀取處理。代碼清單 11.3 是使用了 `File` 類的 `each` 方法的一個程序示例,它會把 sample.txt 文件的內容按順序逐行讀取出來并輸出。 **代碼清單 11.3 file_each.rb** ~~~ file = File.open("sample.txt") file.each_line do |line| print line end file.close ~~~ 除了 `each_line` 方法外,`File` 對象中還有以字符為單位來循環讀取數據的 `each_char` 方法、以及以字節為單位進行循環讀取的 `each_byte` 方法等等。而其他對象也有很多以 `each_XX` 命名的循環讀取數據的方法。 ### **11.2.2 隱藏常規處理** 上文中我們介紹了將塊用于循環的迭代器的例子。但正如本章開頭所介紹的那樣,除了迭代器以外,塊還被廣泛使用在其他地方。其中一個用法就是確保后處理被執行。下面我們來看一個典型的例子——`File.open` 方法。`File.open` 方法在接收塊后,會將 `File` 對象作為塊變量,并執行一次塊。這里,我們可以使用塊把代碼清單 11.3 改寫為代碼清單 11.4 那樣。 **代碼清單 11.4 file_open.rb** ~~~ File.open("sample.txt") do |file| file.each_line do |line| print line end end ~~~ 與改寫之前的程序相比,`File` 對象讀取數據的部分一樣,不同點在于沒有了最后的 `close` 方法的調用。如果使用完打開的文件后沒有將文件關閉的話,有可能會產生其他程序無法打開該文件,或者到達一次性可打開的文件數的上限時無法再打開新文件等問題。而在代碼清單 11.4 的程序中,即使遇到無法打開文件等錯誤也可以正常關閉文件,因為塊內部進行了程序清單 11.5 那樣的處理。 **代碼清單 11.5 file_open_no_block.rb** ~~~ file = File.open("sample.txt") begin file.each_line do |line| print line end ensure file.close end ~~~ `File.open` 方法使用塊時,塊內部的處理完畢并跳出方法前,文件會被自動關閉,因此就不需要像代碼清單 11.3 那樣使用 `File#close` 方法。 文件使用完畢后,由方法執行關閉操作,而我們只需將必要的處理記述在塊中即可。這樣一來可以減少程序的代碼量,二來可以防止忘記關閉文件等錯誤的發生。 ### **11.2.3 替換部分算法** 下面我們再來介紹一個塊的常見用法。這一次我們以數組排序為例,來了解一下指定處理順序時塊的使用方法。 - **自定義排列順序** `Array` 類的 `sort` 方法是對數組內元素進行排序的方法。對數組元素進行排序,可以采取多種方法。 - **按數字的大小順序** - **按字母順序** - **按字符串的長度順序** - **按數組元素的合計值的大小順序** 如果按照這樣的條件分別定義相應的排序方法,就會使方法的數量過多,不便于記憶。因此,在 `Array#sort` 方法中,元素的排序步驟由方法決定,用戶只能指定元素間關系的比較邏輯。 `Array#sort` 方法沒有指定塊時,會使用 `<=>` 運算符對各個元素進行比較,并根據比較后的結果進行排序。`<=>` 運算符的返回值為`-1`、`0`、`1` 中的一個。 **表 11.1 a <=> b 的結果** | `a <>` 時 | -1(比 0 小) | |-----|-----| | `a == b` 時 | 0 | | `a > b` 時 | 1(比 0 大) | 使用 `<=>` 運算符比較字符串時,會按照字符編碼的順序進行比較。比較字母時,會按先大寫字母后小寫字母的順序排列。 ~~~ array = ["ruby", "Perl", "PHP", "Python"] sorted = array.sort p sorted #=> ["PHP", "Perl", "Python", "ruby"] ~~~ 我們可以通過調用塊來指定排列順序。下面的例子與不使用塊時的執行結果是一樣的。 ~~~ array = ["ruby", "Perl", "PHP", "Python"] sorted = array.sort{ |a, b| a <=> b } p sorted #=> ["PHP", "Perl", "Python", "ruby"] ~~~ 在 `sort` 方法的末尾添加了塊 `{ |a, b| a <=> b }`,sort 方法會根據塊的執行結果判斷元素的大小關系。當需要比較元素的大小關系時,塊中需要比較的兩個對象就會被作為塊變量調用。對塊變量 `a` 和 `b` 進行比較后,數組整體就會按該順序排列。 在這里,我們需要注意塊中最后一個表達式的值就是塊的執行結果,因此 `<=>` 運算符必須在最后一行使用。 > **備注** 塊的最后一個表達式不是指塊的最后一行表達式,而是指在塊中最后執行的表達式。 按字符串的長度排序時,可以采用如下方法。 ~~~ array = ["ruby", "Perl", "PHP", "Python"] sorted = array.sort{ |a, b| a.length <=> b.length } p sorted #=> ["PHP", "ruby", "Perl", "Python"] ~~~ 在之前的例子中,我們只是單純地比較了字符串 `a`、`b`,這里我們使用 `String#length` 方法,來比較字符串的長度。用 `<=>` 運算符比較數值時,得到的是由小到大的排列順序,因此,比較字符串長度時,結果就是按照由短到長的順序進行排列。 像這樣,塊經常被用來在 `sort` 方法中實現自定義排列順序。 - **預先取出排序所需的信息** 我們再來詳細看看 `sort` 方法的塊。每次比較元素時,`sort` 方法都會調用一次將兩個元素作為塊變量的塊。這里,我們仍以剛才介紹的按字符串長度排序的程序為例,來看看程序調用了 `length` 方法多少次。 **代碼清單 11.6 sort_comp_count.rb** ~~~ ary = %w( Ruby is a open source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write )   call_num = 0 # 塊的調用次數 sorted = ary.sort do |a, b| call_num += 1 # 累加塊的調用次數 a.length &lt;=> b.length end   puts "排序結果 #{sorted}" puts "數組的元素數量 #{ary.length}" puts "調用塊的次數 #{call_num}" ~~~ > **執行示例** ~~~ > ruby sort_comp_count.rb 排序結果 ["a", "a", "on", "to", "It", "to", "is", "an", ......] 數組的元素數量 28 調用塊的次數 91 ~~~ 可以看出,在這個例子中,我們對 28 個元素進行了排序,塊總共被調用了 91 次。由于每調用 1 次塊,`length` 方法就會被調用 2 次,因此最終就會被調用 182 次。而實際上,我們只需對所有的字符串都調用 1 次 `length` 方法,然后再用得出的結果進行排序就可以了。像這樣,在能夠通過 `< = >` 運算符對轉換后的結果進行比較的情況下,使用 `sort_by` 方法會使排序更加有效率。 ~~~ ary = %w( Ruby is a open source programming language with a focus on simplicity and productivity. It has an elegant syntax that is natural to read and easy to write ) sorted = ary.sort_by{ |item| item.length } p sorted ~~~ `sort_by` 方法會將每個元素在塊中各調用一次,然后再根據這些結果做排序處理。這種情況下,雖然比較的次數不變,但獲取排序所需要的信息的次數(本例中為 28 次)只需與元素個數一樣就可以了。 總結一下,元素排序算法中公共的部分由方法本身提供,我們則可以用塊來替換方法中元素排列的順序(或者取得用于比較的信息),或者根據不同的目的來替換需要更改的部分。 ### **11.3 定義帶塊的方法** 在第 7 章中我們簡單地介紹了如何定義帶塊的方法,接下來我們就來詳細地討論一下。 ### **11.3.1 執行塊** 首先讓我們重溫一下第 7 章中的 `myloop` 方法(代碼清單 11.7)。 **代碼清單 11.7 myloop.rb** ~~~ def myloop while true yield # 執行塊 end end num = 1 # 初始化num myloop do puts "num is #{num}" # 輸出num break if num > 100 # num 超過100 后跳出循環 num *= 2 # num 乘2 end ~~~ `myloop` 方法在執行 `while` 循環的同時執行了 `yield` 關鍵字,`yield` 關鍵字的作用就是執行方法的塊。因為這個 `while` 循環的條件固定為 `true`,所以會無限循環地執行下去,但只要在塊里調用 `break`,就可以隨時中斷 `myloop` 方法,來執行后面的處理。 ### **11.3.2 傳遞塊參數,獲取塊的值** 在剛才的例子中,塊參數以及塊的執行結果都沒有被使用。接下來,我們會定義一個方法,該方法接收兩個整數參數,并對這兩個整數之間的整數做某種處理后進行合計處理,而“某種處理”則由塊指定(代碼清單 11.8)。 **代碼清單 11.8 total.rb** ~~~ 1: def total(from, to) 2: result = 0 # 合計值 3: from.upto(to) do |num| # 處理從from 到to 的值 4: if block_given? # 如果有塊的話 5: result += yield(num) # 累加經過塊處理的值 6: else # 如果沒有塊的話 7: result += num # 直接累加 8: end 9: end 10: return result # 返回方法的結果 11: end 12: 13: p total(1, 10) # 從1 到10 的和 => 55 14: p total(1, 10){|num| num ** 2 } # 從1 到10 的2 次冪的和 => 385 ~~~ `total` 方法會先使用 `Integer#upto` 方法把 `from` 到 `to` 之間的整數值按照從小到大的順序取出來,然后交給塊處理,最后再將塊處理后的值累加到變量 `result`。程序第 5 行中,對 `yield` 傳遞參數后,參數值就會作為塊變量傳遞到塊中。同時,塊的運行結果也會作為 `yield` 的結果返回。 程序第 4 行的 `block_given?` 方法被用來判斷調用該方法時是否有塊被傳遞給方法,如果有則返回 `true`,反之返回 `false`。如果方法沒有塊,則在程序第 7 行中直接把 `num` 相加。 在本例中,對 `yield` 傳遞 1 個參數,就有 1 個塊變量接收。下面我們來看看對 `yield` 傳遞 0 個、1 個、3 個等多個參數時,對應的塊變量是如何進行接收的(代碼清單 11.9)。 **代碼清單 11.9 block_args_test.rb** ~~~ def block_args_test yield() # 0 個塊變量 yield(1) # 1 個塊變量 yield(1, 2, 3) # 3 個塊變量 end puts "通過|a| 接收塊變量" block_args_test do |a| p [a] end puts puts "通過|a, b, c| 接收塊變量" block_args_test do |a, b, c| p [a, b, c] end puts puts "通過|*a| 接收塊變量" block_args_test do |*a| p [a] end puts ~~~ > **執行示例** ~~~ > ruby block_args_test.rb 通過|a| 接收塊變量 [nil] [1] [1] 通過|a, b, c| 接收塊變量 [nil, nil, nil] [1, nil, nil] [1, 2, 3] 通過|*a| 接收塊變量 [[]] [[1]] [[1, 2, 3]] ~~~ 首先我們注意到,`yield` 參數的個數與塊變量的個數是不一樣的。從 `|a|` 和 `|a, b, c|` 的例子中可以看出,塊變量比較多時,多出來的塊變量值為 `nil`,而塊變量不足時,則不能接收參數值。 最后的通過 `|*a|` 接收的情況是將所有塊變量整合為一個數組來接收。這與定義方法時接收可變參數的情況非常相似。 另外,在第 4 章中介紹的抽取嵌套數組的元素的規則,同樣也適用于塊變量。例如,`Hash#each_with_index` 方法的塊變量有 2 個,并以 `yield([ 鍵 , 值 ], 索引 )` 的形式傳遞。像代碼清單 11.10 那樣,在接收塊變量后,我們就可以把 `[ 鍵 , 值 ]` 部分分別賦值給不同的變量。 **代碼清單 11.10 param_grouping.rb** ~~~ hash = {a: 100, b: 200, c: 300} hash.each_with_index do |(key, value), index| p [key, value, index] end ~~~ > **執行示例** ~~~ > ruby param_grouping.rb [:a, 100, 0] [:b, 200, 1] [:c, 300, 2] ~~~ ### **11.3.3 控制塊的執行** 在調用代碼清單 11.8 的 `total` 方法時,如果像下面那樣在中途使用 `break`,`total` 方法的結果會變成什么樣子呢? ~~~ n = total(1, 10) do |num| if num == 5 break end num end p n #=> ?? ~~~ 答案是 `nil`。在塊中使用 `break`,程序會馬上返回到調用塊的地方,因此 `total` 方法中返回計算結果的處理等都會被忽略掉。但作為方法的結果,當我們希望返回某個值的時候,就可以像 `break 0` 這樣指定 `break` 方法的參數,這樣該值就會成為方法的返回值。 此外,如果在塊中使用 `next`,程序就會中斷當前處理,并繼續執行下面的處理。使用 `next` 后,執行塊的 `yield` 會返回,如果 `next` 沒有指定任何參數則返回 `nil`,而如果像 `next 0` 這樣指定了參數,那么該參數值就是返回值。 ~~~ n = total(1, 10) do |num| if num % 2 != 0 next 0 end num end p n #=> 30 ~~~ 最后,如果在塊中使用 `redo`,程序就會返回到塊的開頭,并按照相同的塊變量再次執行處理。這種情況下,塊的處理結果不會返回給外部,因此需要十分小心 `redo` 的用法,注意不要使程序陷入死循環。 ### **11.3.4 將塊封裝為對象** 如前所述,在接收塊的方法中執行塊時,可以使用 `yield` 關鍵字。 而 Ruby 還能把塊當作對象處理。把塊當作對象處理后,就可以在接收塊的方法之外的其他地方執行塊,或者把塊交給其他方法執行。 這種情況下需要用到 `Proc` 對象。`Proc` 對象是能讓塊作為對象在程序中使用的類。定義 `Proc` 對象的典型的方法是,調用 `Proc.new` 方法這個帶塊的方法。在調用 `Proc` 對象的 `call` 方法之前,塊中定義的程序不會被執行。 在代碼清單 11.11 的例子中,定義一個輸出信息的 `Proc` 對象,并調用兩次。這時,程序就會把 `call` 方法的參數作為塊參數來執行塊。 **代碼清單 11.11 proc1.rb** ~~~ hello = Proc.new do |name| puts "Hello, #{name}." end hello.call("World") hello.call("Ruby") ~~~ > **執行示例** ~~~ > ruby proc1.rb Hello, World. Hello, Ruby. ~~~ 把塊從一個方法傳給另一個方法時,首先會通過變量將塊作為 `Proc` 對象接收,然后再傳給另一個方法。在方法定義時,如果末尾的參數使用“& 參數名”的形式,Ruby 就會自動把調用方法時傳進來的塊封裝為 `Proc` 對象。 下面,我們將代碼清單 11.8 中塊的接收方法加以改寫,如下所示: **代碼清單 11.12 total2.rb** ~~~ 1: def total2(from, to, &block) 2: result = 0 # 合計值 3: from.upto(to) do |num| # 處理從from 到to 的值 4: if block # 如果有塊的話 5: result += # 累加經過塊處理的值 6: block.call(num) 7: else # 如果沒有塊的話 8: result += num # 直接累加 9: end 10: end 11: return result # 返回方法的結果 12: end 13: 14: p total2(1, 10) # 從1 到10 的和 => 55 15: p total2(1, 10){|num| num ** 2 } # 從1 到10 的2 次冪的和 => 385 ~~~ 我們在首行的方法定義中定義了 `&block` 參數。像這樣,在變量名前添加 `&` 的參數被稱為 Proc 參數。如果在調用方法時沒有傳遞塊,`Proc` 參數的值就為 `nil`,因此通過這個值就可以判斷出是否有塊被傳入方法中。另外,執行塊的語句不是 `yield`,而是 `block.call(num)`,這一點與之前的例子也不一樣。 在第 7 章中我們提到過方法可以有多個參數,而且定義參數的默認值等時都需要按照一定的順序。而 `Proc` 參數則一定要在所有參數之后,也就是方法中最后一個參數。 將塊封裝為 `Proc` 對象后,我們就可以根據需要隨時調用塊。甚至還可以將其賦值給實例變量,讓別的實例方法去任意調用。 此外,我們也能將 `Proc` 對象作為塊傳給其他方法處理。這時,只需在調用方法時,用“`&Proc` 對象”的形式定義參數就可以了。例如,向 `Array#each` 方法傳遞塊時,可以像代碼清單 11.13 那樣定義。 **代碼清單 11.13 call_each.rb** ~~~ def call_each(ary, &block) ary.each(&block) end call_each [1, 2, 3] do |item| p item end ~~~ 這樣一來,我們就可以非常方便地把調用 `call_each` 方法時接收到的塊,原封不動地傳給 `ary.each` 方法。 > **執行示例** ~~~ > ruby call_each.rb 1 2 3 ~~~ ### **11.4 局部變量與塊變量** 塊內部的命名空間與塊外部是共享的。在塊外部定義的局部變量,在塊中也可以繼續使用。而被作為塊變量使用的變量,即使與塊外部的變量同名,Ruby 也會認為它們是兩個不同的變量。請看代碼清單 11.14。 **代碼清單 11.14 local_and_block.rb** ~~~ x = 1 # 初始化x y = 1 # 初始化y ary = [1, 2, 3] ary.each do |x| # 將x 作為塊變量使用 y = x # 將x 賦值給y end p [x, y] # 確認x 與y 的值 ~~~ > **執行示例** ~~~ > ruby local_and_block.rb [1, 3] ~~~ 在 `ary.each` 方法的塊中,`x` 的值被賦值給了局部變量 `y`。因此,`y` 保留了最后一次調用塊時塊變量 `x` 的值 3。而變量 `x` 的值在調用 `ary.each` 前后并沒有發生改變。 相反,在塊內部定義的變量不能被外部訪問。在剛才的例子中,如果把第 2 行的代碼刪掉,程序就會出錯。 ~~~ x = 1 # 初始化 x #y = 1 # 初始化 y ary = [1, 2, 3] ary.each do |x| # 將 x 作為塊變量使用 y = x # 將 x 賦值給y end p [x, y] # 引用 y 時會出錯誤(NameError) ~~~ 塊中變量的作用域之所以這么設計,是為了通過與塊外部共享局部變量,從而擴展變量的有效范圍。在塊內部給局部變量賦值的時候,要時刻注意它與塊外部的同名變量的關系。大家一定要小心 Ruby 中的這個小陷阱。 塊變量是只能在塊內部使用的變量(塊局部變量),它不能覆蓋外部的局部變量,但 Ruby 為我們提供了定義塊變量以外的塊局部變量的語法。使用在塊變量后使用 `;` 加yiqufen以區分的方式,來定義塊局部變量。這里我們再稍微修改一下剛才的例子,如下所示。可以看出,塊執行后 `x` 和 `y` 的值并沒有變化。 **代碼清單 11.15 local_and_block2.rb** ~~~ x = y = z = 0 # 初始化x、y、z ary = [1, 2, 3] ary.each do |x; y| # 使用塊變量x,塊局部變量y y = x # 代入塊局部變量y z = x # 代入不是塊局部變量的變量z p [x, y, z] # 確認塊內的 x、y、z 的值 end puts p [x, y, z] # 確認x、y、z 的值 ~~~ > **執行示例** ~~~ > ruby local_and_block2.rb [1, 1, 1] [2, 2, 2] [3, 3, 3] [0, 0, 3] ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看