06.Ruby學習分享之閉包
上次的作業
對於arr = [1,2,3],寫一個singleton method,將自己變成[2,4,6]
這道題的核心代碼就是遍歷。如果使用each遍歷,它不會對元素做改變。如果使用map,它會返回新的實例化對象,不合題意。在迭代器不行的情況下,我們可以使用以前提到的forin。讓我們看下參考代碼
arr = [1,2,3]
def arr.double!()
#我們使用forin對數組遍歷,不取數組長度的末尾,因為數組的下標是從0開始。每個遍歷出來的對象,讓它進行乘等運算。
for i in 0...self.length
self[i] *= 2
end
end
作業就到這裡吧,看看今天的內容——閉包
閉包其實分為三個地方學習,第一個是{},第二個是Proc類的proc實例化對象,第三個也是隸屬於Proc類lambda.
我們先看下{}
blcok寫法有兩種形式,一種為do end,一種為{}。我們看下下面的例子
像這樣的當你的block代碼有多行的時候,我們就使用do end
arr.each do |elem|
foo(elem)
bar(elem)
baz(elem)
end
而假如你的代碼只有一行的話,我們就使用{}
arr.each {|e| p e}
當然有的時候,為了不發生誤會,我們必須使用{},因為它的優先級很高
scope :foo,lambda {|elem|
foo(elem)
bar(elem)
baz(elem)
}
上面的代碼是指有一個方法叫做scope,需要傳入兩個參數,第一個參數為一個方法名,第二個參數為一個帶block的參數。
這裡我們使用了{}就可以讓其和lambda綁定在一起。
block方法的兩種方式
第一種傳入式,為了區分參數和block參數,我們需要在其前面加一個&,然後調用call方法,將它傳給方法調用時後面的block
def foo(&block)
a = 2
block.call(a)
end
call就把a傳給了後面的{}
foo {|a|p a}
第二種方式就是使用yield
def foo2
a = 2
yield a
end
foo2 {|a| p a}
這裡我們把a,通過yield方法傳給了{},後面調用時,我們就可以在{}輸入代碼進行操作
block是可以拿到外界定義的變量的
比如
b = 3
foo2 {|a|p a + b}
很明顯,a是我們yield出來的a,b是我們剛剛定義的3,所以terminal打印了5
接著說下block的return問題。
看下面的例子。猜一下它的打印結果
是打印(foo3.start,bar.end)還是(foo3.start)?
def bar
x = 3
yield x
p 'bar.end'
end
def foo3
p 'foo3.start'
bar {|x| return if x > 0}
p 'foo3.end'
end
foo3
正確結果是foo3.start
因為首先看下foo3這個方法,我們的x=3,所以它會被return,也就造成了不會走下面的p 'foo3.end'。但是為什麼沒走bar.end了?這是因為我們調用bar的時候,x滿足了>0的條件,所以跳出了所有的代碼塊
所以也就不管p 'bar.end'的事情。
如果在foo3中調用bar這樣寫bar {p '123'},那麼結果就會是("foo3.start","123","bar.end","foo3.end")
因為p '123'被傳入了bar方法體內,先執行foo3方法內部的p 'foo3.start',接著調用bar,bar中的yield會接收 p '123',最後回到foo3中,調用p 'foo3.end'
除了return,大家也可以試下next、break看看其他效果
Proc
我們不得不說下Proc這個東西,它是一個類,而proc和lambda是它的兩個實例化
剛剛我們說block方法的第一種,其實那個&block就是個proc,不然怎麼可能可以調用call方法。
這個東西可以和block互換,比如下面的例子
arr = %w(a b c)
p arr.map(&:to_sym)
p arr.map {|m|m.to_sym}
你會發現兩種結果是一樣的,to_sym方法被當做了block傳入了進去
當然proc也支持直接定義
proc = Proc.new {|x,y|p x,y}
p proc.call 3
定義以後,我們就可以直接使用call方法,傳入的3可以被proc使用
跟proc相似的還有個lambda
lambda是這樣定義的
l = lambda {|x,y|p x,y}
但是proc和lambda有很大的區別,一個是block,另外一個更像一個方法
前面的proc使用call方法參數可以有任意多,可多可少,會自動nil或者忽略
但是lambda就不行,你定義的參數有多少個,一個不能多也不能少
l.call 1,l.call 1,2,3#這就是錯誤的
l.call 1,2#這個就是對的
因為更像方法,所以定義的時候,我們也可以給它來個缺省值
l = lambda {|x,y = 3|p x,y}
l.call 1
我們再來多參數轉數組
l = lambda {|x,*y|p x,y}
l.call 1,2,3,4,5
是不是這樣就可以可多可少的參數了?
由於兩者不同,所以proc中要是直接出現return會報錯,因為必須包含在方法體內部,而lambda則不會。
因為它更像一個方法
今天的作業
創建一個文件名為start_with.rb,給它添加一個名為each_starts_with的方法。
功能如下:
第一個參數為字符串數組,第二個為字符串,要求利用block打印從字符串數組中以第二個參數開頭的字符串。
參考調用如下:
each_starts_with(%w(abcd axyz able xyzab qrst),'ab'){|s|p s}
它會打印出:"abcd","able"