1. 程式人生 > Ruby 程式語言入門 >20 Ruby 的塊

20 Ruby 的塊

塊是 Ruby 程式設計師最喜歡的東西之一。它是一項非常強大的功能,使我們能夠編寫非常靈活的程式碼,擁有極高的可讀性,並且可以在各處使用。本章中我們會詳細為您講解塊的使用。

1. 什麼是塊

Ruby 的塊(Block)的概念在其他語言中也被稱為閉包。本質上,塊與方法是相同的,除了它沒有名稱並且不屬於物件。塊是一段接受引數並返回值的程式碼並總是通過傳遞給方法來呼叫。

2. 如何建立一個塊

塊有兩種表達形式,一種是 do...end,用來包含多行程式碼,另外一種是 {},只包含1行程式碼。

例項:

5.times do
  puts "Hello, This is from inside of block."
end #---- 輸出結果 ---- Hello, This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block.

再看另一種例子:

例項:

5.times { puts "Hello, This is from inside of block." }

#---- 輸出結果 ----
Hello,
This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block. Hello, This is from inside of block.

解釋:這兩種語句完全相同,它們的含義均是向 5times 例項方法中傳遞了一個塊,而 times方法接收了塊,並執行了裡面的內容。由此可見,可以簡單的將整個塊理解成可以作為方法的一個特殊引數。

Tips:在 Ruby 社群中,慣例是塊只有單行的時候使用花括號 {}

,每當塊多於一行的時候,使用do..end。雖然花括號也可以接收多行引數。

3. 塊如何接收引數

塊經常與雜湊、陣列結合成迭代器Iterators)來使用。這裡我們給出了一個塊接收引數的例子。

例項:

[1, 2, 3, 4, 5].each do |number|
  puts "#{number} was passed to the block"
end

#---- 輸出結果 ----
1 was passed to the block
2 was passed to the block
3 was passed to the block
4 was passed to the block
5 was passed to the block
[1, 2, 3, 4, 5].each { |number| puts "#{number} was passed to the block" }
 
#---- 輸出結果 ----
1 was passed to the block
2 was passed to the block
3 was passed to the block
4 was passed to the block
5 was passed to the block

解釋:上面兩個例子是完全相同的,跟方法不同的是,number引數沒有使用括號(())括起來,而是使用豎線(|)的形式列出。

在迭代器(Iterators)中,我們可以通過操作塊來改變返回值。

例項:

[1, 2, 3, 4, 5].collect { |number| number + 1 }

#---- 輸出結果 ----
[2, 3, 4, 5, 6]

解釋:它呼叫原始陣列上的collect方法,該方法為每個元素呼叫給定的塊,並收集該塊返回的每個返回值。然後,通過方法collect返回一個新的陣列。

4. 建立一個帶有塊方法

4.1 隱形引數形式

這種形式我們會在方法後面傳入一個塊,用yield來呼叫塊內的方法:

例項:

def sayHello 
  yield                                  # 塊會在這裡被呼叫
end
sayHello {puts 'Hello, This is a block'} # 傳入塊

#---- 輸出結果 ----
Hello, This is a block

它實際上和下面這個程式碼是一致的:

def sayHello 
  puts 'Hello, This is a block'                      
end
sayHello

#---- 輸出結果 ----
Hello, This is a block

塊是可以多次呼叫的:

例項:

def sayHello 
  yield # 塊會在這裡被呼叫
  yield # 塊會在這裡第二次被呼叫
end
sayHello {puts 'Hello, This is a block'} # 傳入塊

#---- 輸出結果 ----
Hello, This is a block
Hello, This is a block

注意事項:當沒有塊傳入sayHello方法的時候,呼叫yield會丟擲異常:

例項:

def sayHello 
  yield                        # 塊會在這裡被呼叫
end
sayHello                       # 不傳入塊

#---- 輸出結果 ----
ruby.rb:2:in `sayHello': no block given (yield) (LocalJumpError)

所以,如果塊是一個可選項,我們要使用block_given?方法,僅當塊傳入的時候呼叫。

def sayHello 
  yield if block_given?        # 塊會在這裡被呼叫
end
sayHello                       # 不傳入塊

# 不會丟擲異常

4.2 顯式引數形式

當我們顯示宣告塊引數的時候,要使用&來標註哪個引數是塊(&後面的引數名稱是任意的。)

例項:

def sayHello &block
  block.call                             # 塊會在這裡通過call來呼叫
end
sayHello {puts 'Hello, This is a block'} # 傳入塊

#---- 輸出結果 ----
Hello, This is a block

注意事項:顯示宣告塊引數的時候,如果不傳入塊,block的值會成為nil,所以,這種時候如果塊是可選項,我們可以通過增加判斷block來忽略塊呼叫部分程式碼。

def sayHello &block
  block.call if block           # 塊未傳入時不會呼叫block.call
end
sayHello                        # 傳入塊

# 不會丟擲異常

注意事項:當方法有多個引數的時候,block的引數一定要放到最後。

def sayHello name, &block
  ...
end

4.3 如何呼叫含有引數的程式碼塊œ

當我們使用隱形引數的yield來呼叫含有引數的程式碼塊的時候我們直接將引數傳入yield,類似方法的呼叫形式。同樣,我們定義塊的時候,也要設定接收這個引數。

def sayHello
  yield('Andrew')
end
sayHello { |name| puts "Hello, #{name}, This is block" }

#---- 輸出結果 ----
Hello, Andrew, This is block

當我們使用顯式引數的時候,呼叫的形態基本一樣。

例項:

def sayHello &block
  block.call('Andrew')
end
sayHello { |name| puts "Hello, #{name}, This is block" }

#---- 輸出結果 ----
Hello, Andrew, This is block

Tips : block.arity 返回塊一共需要接收多少個引數,block.call 用來呼叫這個塊。

5. 小結

本章節中我們瞭解了塊,知道了可以通過{}建立單行的塊,也可以通過do...end建立多行的塊,我們在塊中可以使用|引數|的形式接收傳入引數。如果自己要定義帶有塊的方法的話,有隱形和顯式兩種執行塊的方式,我們同樣還可以向塊中傳入引數,形式和方法傳參是一致的。