?
This document uses PHP Chinese website manual Release
一些其它語言有函數(shù),過程,方法等,而Ruby中只有方法:一段表達(dá)式代碼,返回一個(gè)值。
到目前為止,我們?cè)谶@本書中只是基本的介紹了如何定義,使用方法,現(xiàn)在,我們會(huì)繼續(xù)深入的探討一些關(guān)于方法更深層的東西。
如同在前面看到的一樣,定義一個(gè)方法用關(guān)鍵字def開頭,方法名應(yīng)該以小寫字母開頭[如果你用大寫字母開頭定義一個(gè)方法,你不會(huì)立即得到一個(gè)錯(cuò)誤,但是當(dāng)你調(diào)用這個(gè)方法時(shí),Ruby首先認(rèn)為你訪問的是一個(gè)常量,所以可能會(huì)解析錯(cuò)誤],如果一個(gè)方法主要用來完成一些查詢操作(不專指數(shù)據(jù)庫查詢),通常以一個(gè)問號(hào)"?"結(jié)束,作為函數(shù)名的最后一個(gè)字母,比如instance_of?等。如果一個(gè)方法有一定危險(xiǎn),或者可能修改方法的接受者,通常以"!"結(jié)尾,比如String類提供了chop和chop!兩個(gè)方法,第一個(gè)方法返回一個(gè)修改過的字符串,而第二個(gè)方法直接就修改了接收者本身。"?"和"!"是唯一兩個(gè)能作為方法名后綴的特殊字符。
我們已經(jīng)指定了方法名,如果需要,我們可以定義一些參數(shù),這些參數(shù)用雙括號(hào)括起來,作用域范圍都是局部變量,一些例子如下:
def?myNewMethod(arg1,?arg2,?arg3)?????#?3?arguments ??#?Code?for?the?method?would?go?here end def?myOtherNewMethod??????????????????#?No?arguments ??#?Code?for?the?method?would?go?here end |
Ruby允許為方法的參數(shù)設(shè)置默認(rèn)值:如果調(diào)用者沒有顯示的為這些參數(shù)提供值,將使用這些默認(rèn)值。通過"=",就可以為這些參數(shù)設(shè)定默認(rèn)值。
def?coolDude(arg1="Miles",?arg2="Coltrane",?arg3="Roach") | ||
??"#{arg1},?#{arg2},?#{arg3}." | ||
end | ||
| ||
coolDude |
? |
"Miles,?Coltrane,?Roach." |
coolDude("Bart") |
? |
"Bart,?Coltrane,?Roach." |
coolDude("Bart",?"Elwood") |
? |
"Bart,?Elwood,?Roach." |
coolDude("Bart",?"Elwood",?"Linus") |
? |
"Bart,?Elwood,?Linus." |
方法體中包含了一般的Ruby表達(dá)式,但是你不能在方法里面定義實(shí)例方法,類或者模塊。方法的返回值是方法體最后一行執(zhí)行后的結(jié)果,或者你顯示的用一個(gè)return語句。
如果我們想給方法傳入一個(gè)數(shù)目不定的參數(shù),或者把所有參數(shù)放到一個(gè)參數(shù)中進(jìn)行傳遞的話,該怎么辦呢?我們可以在普通的參數(shù)后面加入一個(gè)特殊的參數(shù),這個(gè)參數(shù)以"*"開頭,就可以達(dá)到這個(gè)目的了。
def?varargs(arg1,?*rest) | ||
??"Got?#{arg1}?and?#{rest.join(',?')}" | ||
end | ||
| ||
varargs("one") |
? |
"Got?one?and?" |
varargs("one",?"two") |
? |
"Got?one?and?two" |
varargs?"one",?"two",?"three" |
? |
"Got?one?and?two,?three" |
在這個(gè)例子中,第一個(gè)參數(shù)很普通,直接作為第一個(gè)參數(shù)變量,而后面以"*"開頭的參數(shù),將會(huì)包括調(diào)用時(shí)候后面的所有參數(shù),是一個(gè)Array的結(jié)構(gòu),包括了從第二個(gè)開始的所有參數(shù)。
在討論塊和迭代的那章時(shí),我們知道,當(dāng)一個(gè)方法被調(diào)用時(shí)候,可以接收一個(gè)block,而我們?cè)诜椒ㄖ锌梢杂脃ield來執(zhí)行這個(gè)block。
def?takeBlock(p1) ??if?block_given? ????yield(p1) ??else ????p1 ??end end |
takeBlock("no?block") |
? |
"no?block" |
takeBlock("no?block")?{?|s|?s.sub(/no?/,?'')?} |
? |
"block" |
但是,當(dāng)方法接受參數(shù)中最后一個(gè)參數(shù)以"&"開始的時(shí)候,任何給定的block都會(huì)轉(zhuǎn)換為Proc對(duì)象,并且這個(gè)Proc對(duì)象將會(huì)賦值給這個(gè)參數(shù)(下例中block指向一個(gè)Proc對(duì)象)。
class?TaxCalculator | ||
??def?initialize(name,?&block) | ||
????@name,?@block?=?name,?block | ||
??end | ||
??def?getTax(amount) | ||
????"#@name?on?#{amount}?=?#{?@block.call(amount)?}" | ||
??end | ||
end | ||
| ||
tc?=?TaxCalculator.new("Sales?tax")?{?|amt|?amt?*?0.075?} | ||
| ||
tc.getTax(100) |
? |
"Sales?tax?on?100?=?7.5" |
tc.getTax(250) |
? |
"Sales?tax?on?250?=?18.75" |
通常,調(diào)用一個(gè)方法需要指定一個(gè)接收者,方法名,還有一些參數(shù)或者block。
connection.downloadMP3("jitterbug")?{?|p|?showProgress(p)?} |
在這個(gè)例子里,connection是接收者,downloadMP3是方法名,"jitterbug"是一個(gè)參數(shù),{?|p|?showProgress(p)?}是傳遞給這個(gè)方法的塊。
對(duì)于類或者模塊方法來說,接收者是類或模塊名:
File.size("testfile") Math.sin(Math::PI/4) |
如果你省略了接收者,那么默認(rèn)為self是接收者,即當(dāng)前對(duì)象:
self.id |
? |
537794160 |
id |
? |
537794160 |
self.type |
? |
Object |
type |
? |
Object |
這種機(jī)制也是Ruby實(shí)現(xiàn)private方法的體現(xiàn),private方法不能用一個(gè)接收者來直接調(diào)用,只能在當(dāng)前對(duì)象中使用。
?方法名后面是可選的參數(shù),如果不會(huì)出現(xiàn)歧義的話,調(diào)用方法時(shí)參數(shù)可以不加括號(hào)括起來[Ruby文檔有時(shí)候也叫做這樣的方法是命令(commands)],然而,除非特別簡(jiǎn)單的方法,否則還是加上括號(hào)的好,要不可能容易出錯(cuò),比如,你的方法嵌套在另一個(gè)方法調(diào)用之中。
a?=?obj.hash????#?Same?as a?=?obj.hash()??#?this. obj.someMethod?"Arg1",?arg2,?arg3???#?Same?thing?as obj.someMethod("Arg1",?arg2,?arg3)??#?with?parentheses. |
前面我們已經(jīng)說過了,在一個(gè)方法的參數(shù)前面可以加一個(gè)星號(hào),這樣所有后面的參數(shù)都被放到了一個(gè)數(shù)組中,反過來,Ruby也支持調(diào)用的時(shí)候指定一個(gè)數(shù)組代替若干個(gè)參數(shù)。
在調(diào)用方法的時(shí)候,你可以使用一個(gè)數(shù)組作為一個(gè)參數(shù),它的每個(gè)元素都將作為一個(gè)單獨(dú)的參數(shù)使用。使用的時(shí)候,需要在這個(gè)作為參數(shù)的數(shù)組前面加一個(gè)星號(hào)。
def?five(a,?b,?c,?d,?e) | ||
??"I?was?passed?#{a}?#?#{c}?#377j5v51b?#{e}" | ||
end | ||
| ||
five(1,?2,?3,?4,?5?) |
? |
"I?was?passed?1?2?3?4?5" |
five(1,?2,?3,?*['a',?'b']) |
? |
"I?was?passed?1?2?3?a?b" |
five(*(10..14).to_a) |
? |
"I?was?passed?10?11?12?13?14" |
我們已經(jīng)看過了如何把一個(gè)方法和一個(gè)塊聯(lián)系起來。
listBones("aardvark")?do?|aBone| ??#?... end |
通常,這已經(jīng)足夠好了,我們可以給一個(gè)方法提供一個(gè)機(jī)構(gòu)良好的塊,而不必再方法中使用很多的if或者while等語句。
?但是有些時(shí)候,你需要更靈活一些,比如,下面的例子,如果選擇times,即輸入t,將會(huì)打印2,4,6,8等等:
print?"(t)imes?or?(p)lus:?" times?=?gets print?"number:?" number?=?gets.to_i if?times?=~?/^t/ ??puts((1..10).collect?{?|n|?n*number?}.join(",?")) else ??puts((1..10).collect?{?|n|?n+number?}.join(",?")) end |
(t)imes?or?(p)lus:?t number:?2 2,?4,?6,?8,?10,?12,?14,?16,?18,?20 |
雖然這樣可以工作,但是不是很完美,我們可以把負(fù)責(zé)計(jì)算的部分抽出來組成一個(gè)block。
?
print?"(t)imes?or?(p)lus:?" times?=?gets print?"number:?" number?=?gets.to_i if?times?=~?/^t/ ??calc?=?proc?{?|n|?n*number?} else ??calc?=?proc?{?|n|?n+number?} end puts((1..10).collect(&calc).join(",?")) |
(t)imes?or?(p)lus:?t number:?2 2,?4,?6,?8,?10,?12,?14,?16,?18,?20 |
如果最后一個(gè)方法的最后一個(gè)參數(shù)以"&"開頭,Ruby把它最為一個(gè)Proc來處理,傳到相應(yīng)的block。
這種技術(shù)也有另外的用處,比如我們使用迭代器處理一些數(shù)據(jù),把每個(gè)步驟地結(jié)果存儲(chǔ)到一個(gè)數(shù)組中,我們下面將用到前面的Fibonacci 例子來產(chǎn)生一組數(shù)據(jù):
a?=?[] | ||
fibUpTo(20)?{?|val|?a?<<?val?} |
? |
nil |
a.inspect |
? |
"[1,?1,?2,?3,?5,?8,?13]" |
盡管這樣已經(jīng)可以工作了,但是這顯示出來的意圖不像我們想象的那么明晰,所以,我們?nèi)《氖橇硗舛x了一個(gè)方法into,它將返回一個(gè)完成填充array功能的block。(注意返回的block是一個(gè)閉包closure ,即使into返回了,它還指向參數(shù)anArray)
def?into(anArray) | ||
??return?proc?{?|val|?anArray?<<?val?} | ||
end | ||
| ||
fibUpTo?20,?&into(a?=?[]) | ||
a.inspect |
? |
"[1,?1,?2,?3,?5,?8,?13]" |
一些語言支持基于鍵的參數(shù),即hash結(jié)構(gòu)的參數(shù)。不按照參數(shù)的個(gè)數(shù)和位置來調(diào)用一個(gè)方法,而是用一個(gè)hash結(jié)構(gòu)的鍵-值結(jié)構(gòu)來設(shè)定參數(shù),而不是按位置。Ruby1。6不支持這種特性,1。8支持。[本書寫的是基于1.6,而目前最新的Ruby是1.8]
同時(shí),人們可以用hash結(jié)構(gòu)來實(shí)現(xiàn)這一功能,比如,我們要為我們的SongList實(shí)現(xiàn)一個(gè)按名字查找的功能。
class?SongList ??def?createSearch(name,?params) ????#?... ??end end aList.createSearch("short?jazz?songs",?{ ???????????????????'genre'????????????=>?"jazz", ???????????????????'durationLessThan'?=>?270 ???????????????????}?) |
第一個(gè)參數(shù)是查找的名稱,第二個(gè)參數(shù)是一個(gè)hash結(jié)構(gòu),包含了各種查找的參數(shù)。使用hash結(jié)構(gòu),我們可以是有一些鍵-值特性:音樂流派是jazz,時(shí)長(zhǎng)小于4.5分鐘。但是這段代碼不是太好,而且大括號(hào)中的內(nèi)容很容易被誤認(rèn)為塊。所以,Ruby提供了一個(gè)快捷方式,你可以在方法的參數(shù)中指定鍵=>值的結(jié)構(gòu),像普通的參數(shù)那樣。這樣的結(jié)構(gòu)都將作為一個(gè)hash結(jié)構(gòu)傳給方法,而不需要大擴(kuò)號(hào)了。
aList.createSearch("short?jazz?songs", ???????????????????'genre'????????????=>?"jazz", ???????????????????'durationLessThan'?=>?270 ???????????????????) |