<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>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # **第 21 章 Proc 類** 在本章中,我們將介紹 `Proc` 類相關的內容。 - **Proc 類是什么** 介紹 `Proc` 類是什么、以及創建 `Proc` 對象的幾種方法。 - **Proc 對象的特征** `Proc` 對象具有部分程序的特質,并不是普通的數據,這里將會介紹 `Proc` 類的相關特質。 - **Proc 類的實例方法** 介紹 `Proc` 類的實例方法。 ### **21.1 Proc 類是什么** 所謂 `Proc`,就是使塊對象化的類。`Proc` 與塊的關系非常密切,在第 11 章中我們也介紹過 `Proc` 類。請大家結合第 11 章的內容,一起學習本章。 下面,我們來看看如何創建與執行 `Proc` 對象。 - **`Proc.new`(...) `proc`{...}** 創建 `Proc` 對象的典型方法是通過 `Proc.new` 方法,或者對 `proc` 方法指定塊。 ~~~ hello1 = Proc.new do |name| puts "Hello, #{name}." end   hello2 = proc do |name| puts "Hello, #{name}." end   hello1.call("World") #=> Hello, World. hello2.call("Ruby") #=> Hello, Ruby. ~~~ 利用 `Proc.new` 方法,或者對 `proc` 方法指定塊,都可以創建代表塊的 `Proc` 對象。 通過調用 `Proc#call` 方法執行塊。調用 `Proc#call` 方法時的參數會作為塊變量,塊中最后一個表達式的值則為 `Proc#call` 的返回值。`Proc#call` 還有一個名稱叫 `Proc#[]`。 ~~~ # 判斷西歷的年是否為閏年的處理 leap = Proc.new do |year| year % 4 == 0 && year % 100 != 0 || year % 400 ==0 end   p leap.call(2000) #=> true p leap[2013] #=> false p leap[2016] #=> true ~~~ 將塊變量設置為 |* 數組 | 的形式后,就可以像方法參數一樣,以數組的形式接收可變數量的參數。 ~~~ double = Proc.new do |*args| args.map{|i| i * 2 } # 所有元素乘兩倍 end   p double.call(1, 2, 3) #=> [2, 3, 4] p double[2, 3, 4] #=> [4, 6, 8] ~~~ 除此以外,定義普通方法時可使用的參數形式,如默認參數、關鍵字參數等,幾乎都可以被用于塊變量的定義,并被指定給 `Proc#call` 方法。關于方法定義的參數指定,請參考第 7 章。 ### **21.1.1 lambda** `Proc.new`、`proc` 等有另外一種寫法叫 `lambda`。與 `Proc.new`、`proc` 一樣,`lambda` 也可以創建 `Proc` 對象,但通過 `lambda` 創建的 `Proc` 的行為會更接近方法。 第一個不同點是,`lambda` 的參數數量的檢查更加嚴密。對用 `Proc.new` 創建的 `Proc` 對象調用 `call` 方法時,`call` 方法的參數數量與塊變量的數量可以不同。但通過 `lambda` 創建 `Proc` 對象時,如果參數數量不正確,程序就會產生錯誤。 ~~~ prc1 = Proc.new do |a, b, c| p [a, b, c] end prc1.call(1, 2) #=> [1, 2, nil] prc2 = lambda do |a, b, c| p [a, b, c] end prc2.call(1, 2) #=> 錯誤(ArgumentError) ~~~ 第二個不同點是,`lambda` 可以使用 `return` 將值從塊中返回。請看代碼清單 21.1。`power_of` 方法會利用參數 `n` 返回“計算 x 的 n 次冪的 Proc 對象”。請注意,返回值并不是數值,而是進行運算的 `Proc` 對象。調用 `power_of(3)` 后,結果就會得到 `call` 方法參數值的 3 次冪的 `Proc` 對象。從 `lambda` 中返回值時使用了 `return`,這里的 `return` 會將 `lambda` 中的值返回。 **代碼清單 21.1 power_of.rb** ~~~ def power_of(n) lambda do |x| return x ** n end end cube = power_of(3) p cube.call(5) #=> 125 ~~~ 接下來,我們嘗試用 `Proc.new` 方法改寫代碼清單 21.1。使用 `Proc.new` 方法時,在塊中使用 `return` 后,程序就會跳過當前執行塊,直接從創建這個塊的方法返回。在本例中,即雖然塊內的 `return` 應該從 `power_of` 方法返回,但由于程序運行時 `power_of` 方法的上下文會消失,因此程序就會出現錯誤。 ~~~ def power_of(n) Proc.new do |x| return x ** n end end cube = power_of(3) p cube.call(5) #=> 錯誤(LocalJumpError) ~~~ 不是 `lambda` 的普通塊中的 `return`,會從正在執行循環的方法返回。代碼清單 21.2 中的 `prefix` 方法會比較參數 `ary` 中的元素是否與 `obj` 相等,相等就返回在此之前的所有元素,不相等則返回空數組。第 6 行中的 `return` 并不會從塊返回,而是跳過塊,并作為 `prefix` 方法整體的返回值返回。 **代碼清單 21.2 prefix.rb** ~~~ 1: def prefix(ary, obj) 2: result = [] # 初始化結果數組 3: ary.each do |item| # 逐個檢查元素 4: result << item # 將元素追加到結果數組中 5: if item == obj # 如果元素與條件一致 6: return result # 返回結果數組 7: end 8: end 9: return result # 所有元素檢查完畢的時候 10: end 11: 12: prefix([1, 2, 3, 4, 5], 3) #=> [1, 2, 3] ~~~ `break` 被用于控制迭代器的行為。這個命令會向接收塊的方法的調用者返回結果值。如下所示,`break []` 會馬上終止 `Array#collect` 方法,并將空數組作為 `collent` 方法的整體的返回值返回。 ~~~ [:a, :b, :c].collect do |item| break [] end ~~~ > **注** 用 `Proc.new` 方法或者 `proc` 方法創建的 `Proc` 對象的情況下,由于這些方法都接收塊,在調用 `Proc#call` 方法的時候并沒有適當的返回對象,因此就會發生錯誤。而 `lambda` 的情況下則與 `return` 一樣,將值返回給 `Proc#call` 方法。另一方面,由于 `next` 方法的作用在于中斷 1 次塊的執行,因此無論如何創建 `Proc` 對象,都可以將值返回給 `call` 方法。 `lambda` 有另外一種寫法——“`->( 塊變量 ){ 處理 }`”。塊變量在 `{ ~ }` 之前,看上去有點像函數。使用 `->` 的時候,我們一般會使用 `{ ~ }` 而不是 `do ~ end`。 ~~~ square = ->(n){ return n ** 2} p square[5] #=> 25 ~~~ ### **21.1.2 通過 Proc 參數接收塊** 在調用帶塊的方法時,通過 `Proc` 參數的形式指定塊后,該塊就會作為 `Proc` 對象被方法接收。代碼清單 21.3 是我們在第 11 章中介紹過的例子。在 `total2` 方法中,調用 `total2` 方法時指定的塊,可以作為 `Proc` 對象從變量 `block` 中獲取。 **代碼清單 21.3 total2.rb** ~~~ def total2(from, to, &block) result = 0 # 合計值 from.upto(to) do |num| # 處理從 from 到 to 的值 if block # 如果有塊的話 result += # 累加經過塊處理的值 block.call(num) else # 如果沒有塊的話 result += num # 直接累加 end end return result # 返回方法的結果 end p total2(1, 10) # 從 1 到 10 的和 => 55 p total2(1, 10){|num| num ** 2 } # 從 1 到 10 的 2 次冥的和 => 385 ~~~ ### **21.1.3 to_proc 方法** 有些對象有 `to_proc` 方法。在方法中指定塊時,如果以 & 對象的形式傳遞參數,對象 `.to_proc` 就會被自動調用,進而生成 `Proc` 對象。 其中,`Symbol#to_proc` 方法是比較典型的,并且經常被用到。例如,對符號 `:to_i` 使用 `Symbol#to_proc` 方法,就會生成下面那樣的 `Proc` 對象。 ~~~ Proc.new{|arg| arg.to_i } ~~~ 這個對象在什么時候使用呢?例如,把數組的所有元素轉換為數值類型時,一般的做法如下: > **執行示例** ~~~ >> %w(42 39 56).map{|i| i.to_i } => [42, 39, 56] ~~~ 上述代碼還可以像下面這樣寫: > **執行示例** ~~~ >> %w(42 39 56).map(&:to_i) => [42, 39, 56] ~~~ 按照類名排序的程序,也可以寫成: > **執行示例** ~~~ >> [Integer, String, Array, Hash, File, IO].sort_by(&:name) => [Array, File, Hash, IO, Integer, String] ~~~ 熟悉這樣的寫法可能需要一定的時間,但這種寫法不僅干凈利索,而且意圖明確。 ### **21.2 Proc 的特征** 雖然 `Proc` 對象可以作為匿名函數或方法使用,但它并不只是單純的對象化。請看代碼清單 21.4。 **代碼清單 21.4 counter_proc.rb** ~~~ 1: def counter 2: c = 0 # 初始化計數器 3: Proc.new do # 每調用 1 次 call 方法,計數器加1 4: c += 1 # 返回加 1 后的 Proc 對象 5: end 6: end 7: 8: # 創建計數器 c1 并計數 9: c1 = counter 10: p c1.call #=> 1 11: p c1.call #=> 2 12: p c1.call #=> 3 13: 14: # 創建計數器 c2 并計數 15: c2 = counter # 創建計數器c2 16: p c2.call #=> 1 17: p c2.call #=> 2 18: 19: # 再次用 c1 計數 20: p c1.call #=> 4 ~~~ 第 1 行到第 6 行為 `counter` 方法的定義。該方法首先把作為計數器的本地變量 `c` 初始化為 0。然后每調用 1 次 `Proc#call` 方法,就將計數器加 1,并返回該 `Proc` 對象。在第 9 行中,調用 `counter` 方法,將 `Proc` 對象賦值給 `c1`。可以看到,`c1` 調用 `call` 方法后,`proc` 對象引用的本地變量 `c` 開始計數了。在第 15 行中,以同樣的方法創建新的計數器,之后計數器被重置。在最后的第 20 行中,再次調用最初創建的 `c1` 的 `call` 方法,計數器開始接著之前的結果計數。 通過這個例子我們可以看出,變量 `c1` 與變量 `c2` 引用的 `Proc` 對象,是分別保存、處理調用 `counter` 方法時初始化的本地變量的。與此同時,`Proc` 對象也會將處理內容、本地變量的作用域等定義塊時的狀態一起保存。 像 `Proc` 對象這樣,將處理內容、變量等環境同時進行保存的對象,在編程語言中稱為閉包(closure)。使用閉包后,程序就可以將處理內容和數據作為對象來操作。這和在類中描述處理本身、在實例中保存數據本質上是一樣的,只是從寫程序的角度來看,使用類的話當然也就意味著可以使用更多的功能。 就像剛才的計數器的例子那樣,`Proc` 對象可被用來對少量代碼實現的功能做對象化處理。另外,由于 Ruby 中大量使用了塊,因此在有一定規模的程序開發中,我們就難免會使用到 `Proc` 對象。特別是像調用和傳遞帶塊的方法時的方法、通過閉包保存數據等功能,我們都需要透徹理解才行。 ### **21.3 Proc 類的實例方法** - ***prc*.`call`(*args, ...*) *prc*[*args, ...*] *prc*.`yield`(*args, ...*) *prc*.(*args, ...*) *prc* === *arg*** 上述方法都執行 `Proc` 對象 *prc*。 ~~~ prc = Proc.new{|a, b| a + b} p prc.call(1, 2) #=> 3 p prc[3, 4] #=> 7 p prc.yield(5, 6) #=> 11 p prc.(7, 8) #=> 15 p prc === [9, 10] #=> 19 ~~~ 由于受到語法的限制,通過 `===` 指定的參數只能為 1 個。大家一定要牢記這個方法會在 `Proc` 對象作為 `case` 語句的條件時使用。因此,在創建這樣的 `Proc` 對象時,比較恰當的做法是,只接收一個參數,并返回 `true` 或者 `false`。 下面的例子實現的是,從 1 到 100 的整數中,當值為 3 的倍數時輸出 `Fizz`,5 的倍數時輸出 `Buzz`,15 的倍數時輸出 `Fizz Buzz`,除此以外的情況下則輸出該值本身。 ~~~ fizz = proc{|n| n % 3 == 0 } buzz = proc{|n| n % 5 == 0 } fizzbuzz = proc{|n| n % 3 == 0 && n % 5 == 0} (1..100).each do |i| case i when fizzbuzz then puts "Fizz Buzz" when fizz then puts "Fizz" when buzz then puts "Buzz" else puts i end end ~~~ - ***prc*.`arity`** 返回作為 `call` 方法的參數的塊變量的個數。以 `|*args|` 的形式指定塊變量時,返回 -1。 ~~~ prc0 = Proc.new{ nil } prc1 = Proc.new{|a| a } prc2 = Proc.new{|a, b| a + b } prc3 = Proc.new{|a, b, c| a + b +c } prcn = Proc.new{|*args| args }   p prc0.arity #=> 0 p prc1.arity #=> 1 p prc2.arity #=> 2 p prc3.arity #=> 3 p prcn.arity #=> -1 ~~~ - ***prc*.`parameters`** 返回關于塊變量的詳細信息。返回值為 [ 種類 , 變量名 ] 形式的數組的列表。表 21.1 為表示種類的符號。 **表 21.1 Proc#parameters 返回的變量種類** | 符號 | 意義 | |-----|-----| | `:opt` | 可省略的變量 | | `:req` | 必需的變量 | | `:rest` | 以 \*_args_ 形式表示的變量 | | `:key` | 關鍵字參數形式的變量 | | `:keyrest` | 以 \*\*_args_ 形式表示的變量 | | `:block` | 塊 | ~~~ prc0 = proc{ nil } prc1 = proc{|a| a } prc2 = lambda{|a, b| [a, b] } prc3 = lambda{|a, b=1, *c| [a, b, c] } prc4 = lambda{|a, &block| [a, block] } prc5 = lambda{|a: 1, **b| [a, b] }   p prc0.parameters #=> [] p prc1.parameters #=> [[:opt, :a]] p prc2.parameters #=> [[:req, :a], [:req, :b]] p prc3.parameters #=> [[:req, :a], [:opt, :b], [:rest, :c]] p prc4.parameters #=> [[:req, :a], [:block, :block]] p prc5.parameters #=> [[:key, :a], [:keyrest, :b]] ~~~ - ***prc*.`lambda?`** 判斷 *prc* 是否為通過 `lambda` 定義的方法。 ~~~ prc1 = Proc.new{|a, b| a + b} p prc1.lambda? #=> false   prc2 = lambda{|a, b| a + b} p prc2.lambda? #=> true ~~~ - ***prc*.`source_location`** 返回定義 *prc* 的程序代碼的位置。返回值為 [ 代碼文件名 , 行編號 ] 形式的數組。*prc* 由擴展庫等生成,當 Ruby 腳本不存在時返回 `nil`。 **代碼清單 21.5 proc_source_location.rb** ~~~ 1: prc0 = Proc.new{ nil } 2: prc1 = Proc.new{|a| a } 3: 4: p prc0.source_location 5: p prc1.source_location ~~~ > **執行示例** ~~~ > ruby proc_source_location.rb ["proc_source_location.rb", 1] ["proc_source_location.rb", 2] ~~~ ### **練習題** 1. 仿照 `Array#collect` 方法,定義 `my_collect` 方法。參數為擁有 `each` 方法的對象,并在塊中對各元素進行處理。 ~~~ def my_collect(obj, &block) (??) end ary = my_collect([1, 2, 3, 4, 5]) do |i| i * 2 end p ary #=> [2, 4, 6, 8, 10] ~~~ 2. 確認使用了下述 `Symbol#to_proc` 方法的例子的執行結果。 ~~~ to_class = :class.to_proc p to_class.call("test") #=> ?? p to_class.call(123) #=> ?? p to_class.call(2 ** 1000) #=> ?? ~~~ 3. 修改計數器的例子,計算 `call` 方法的參數的合計值。請補充下面 (??) 部分的代碼。 ~~~ def accumlator total = 0 Proc.new do (??) end end acc = accumlator p acc.call(1) #=> 1 p acc.call(2) #=> 3 p acc.call(3) #=> 6 p acc.call(4) #=> 10 ~~~ > 參考答案:請到圖靈社區本書的“隨書下載”處下載([http://www.ituring.com.cn/book/1237](http://www.ituring.com.cn/book/1237))。
                  <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>

                              哎呀哎呀视频在线观看