?
This document uses PHP Chinese website manual Release
;-)
。
Ruby和其它語言的一個不同之處就是任何東西都能返回一個值,幾乎所有的東西都是表達式,在實際中,這有什么意義呢?
一些明顯得作用是可以實現(xiàn)鏈?zhǔn)秸Z句:
a?=?b?=?c?=?0 |
? |
0 |
[?3,?1,?7,?0?].sort.reverse |
? |
[7,?3,?1,?0] |
一些不太起眼的東西,比如C和JAVA中的語句,在Ruby中都是表達式,例如,if和case都返回一個值,這個值就是這些語句中最后執(zhí)行的那行。
songType?=?if?song.mp3Type?==?MP3::Jazz ?????????????if?song.written?<?Date.new(1935,?1,?1) ???????????????Song::TradJazz ?????????????else ???????????????Song::Jazz ?????????????end ???????????else ?????????????Song::Other ???????????end ?rating?=?case?votesCast ??????????when?0...10????then?Rating::SkipThisOne ??????????when?10...50???then?Rating::CouldDoBetter ??????????else????????????????Rating::Rave ??????????end |
Ruby提供了諸如加減乘除等一些操作符,完整的操作符列表和優(yōu)先級在第18章有列表。
在Ruby中,很多操作符就是對一些方法的調(diào)用。比如你執(zhí)行a*b+c,實際上就是調(diào)用對象a的乘方法,把b作為一個參數(shù)傳遞過去,然后在調(diào)用這個結(jié)果對象的加方法,把c作為參數(shù)傳遞,實際上等于:
(a.*(b)).+(c) |
因為你可以重新定義實例方法,所以你可以修改一些不能滿足你的需求的方法,讓它達到你需要的作用
class?Fixnum | ||
??alias?oldPlus?+ | ||
??def?+(other) | ||
????oldPlus(other).succ | ||
??end | ||
end | ||
| ||
1?+?2 |
? |
4 |
a?=?3 | ||
a?+=?4 |
? |
8 |
很有用的一個技巧是你自己寫的的類可以像內(nèi)建對象一樣參與操作符的操作,比如,我們想從一首歌中間某處開始提取一部分音樂,我們可以用操作符"[ ]"來完成:
class?Song ??def?[](fromTime,?toTime) ????result?=?Song.new(self.title?+?"?[extract]", ??????????????????????self.artist, ??????????????????????toTime?-?fromTime) ????result.setStartTime(fromTime) ????result ??end end |
這段代碼擴展了類Song,增加了[ ]方法,這個方法接收兩個參數(shù),一個開始時間,一個結(jié)束時間。這個方法返回一個新的Song對象,這個對象是歌曲的一部分。然后,我們就可以這樣播放這段音樂:
aSong[0,?0.15].play |
除了上面最普通的操作符表達式,或者不是很顯眼的語句表達式(比如if或case),Ruby還支持在表達式中使用更多的東西。
如果你用反引號(`)來括起來一個字符串,或者用%x{ 和 }括起來,那么這個表達式中的字符串默認得會作為底層的操組系統(tǒng)命令來執(zhí)行,并返回結(jié)果,這個結(jié)果就是這個命令在操作系統(tǒng)中執(zhí)行之后的結(jié)果。換行符將不會從結(jié)果中去掉,所以返回結(jié)果一般都會包含一個回車符。
`date` |
? |
"Sun?Jun??9?00:08:26?CDT?2002\n" |
`dir`.split[34] |
? |
"lib_singleton.tip" |
%x{echo?"Hello?there"} |
? |
"Hello?there\n" |
你也可以在命令中使用表達式展開和所有通常的轉(zhuǎn)義序列。
for?i?in?0..3 ??status?=?`dbmanager?status?id=#{i}` ??#?... end |
執(zhí)行的命令的返回狀態(tài)存放在全局變量$?
中。
上面我們說道,反引號之中的命令"默認"會作為操作系統(tǒng)命令來執(zhí)行,實際上,這個字符串是傳遞給了
Kernel::`
這個方法(一個反引號)來執(zhí)行。如果你愿意,可以重寫這個方法,比如如下:
alias?oldBackquote?` def?`(cmd) ??result?=?oldBackquote(cmd) ??if?$??!=?0 ????raise?"Command?#{cmd}?failed" ??end ??result end print?`date` print?`data` |
Sun?Jun??9?00:08:26?CDT?2002 prog.rb:3:?command?not?found:?data prog.rb:5:in?``':?Command?data?failed?(RuntimeError) from?prog.rb:10 |
我們前面的例子中都涉及到了賦值這一基本表達式,下面,我們來討論一些關(guān)于賦值語句的東西。
一個賦值語句給一個變量或者屬性設(shè)定一個指定的值,變量或?qū)傩栽谧筮叄翟谟疫?。然后這個值作為表達式的返回值返回。也就是說,我們可以用鏈?zhǔn)劫x值來給一些變量賦值:
a?=?b?=?1?+?2?+?3 | ||
a |
? |
6 |
b |
? |
6 |
a?=?(b?=?1?+?2)?+?3 | ||
a |
? |
6 |
b |
? |
3 |
File.open(name?=?gets.chomp) |
在Ruby中有兩種基本的賦值格式,第一種是指給一個引用某一參數(shù)或者常量的對象賦值,這種形式是緊密連接到語言中的。
instrument?=?"piano" MIDDLE_A???=?440 |
另一種是在賦值語句左邊使用對象的屬性或者元素的引用。
aSong.duration????=?234 instrument["ano"]?=?"ccolo" |
這種方法比較特殊,通過調(diào)用左值的方法來賦值,也就是說我們可以重寫這些方法。
我們已經(jīng)看過如何定義一個可以修改的屬性了,只需要簡單的在方法后面以等號結(jié)尾即可。這個方法把接收的參數(shù)作為賦值語句的右值。
class?Song ??def?duration=(newDuration) ????@duration?=?newDuration ??end end |
沒有理由要求這些給參數(shù)設(shè)置值得方法與內(nèi)部的實例變量一致,或者每個可以修改的屬性都要提供一個讀方法,反過來也是一樣。
class?Amplifier ??def?volume=(newVolume) ????self.leftChannel?=?self.rightChannel?=?newVolume ??end ??#?... end |
Sidebar:在類中使用訪問方法( Accessors) | ||||||||||||||||||||||||||||||||||||
上面的例子中為什么我們必須要寫 self.leftChannel 而不能省掉self呢?一般的,一個類中的方法可以直接調(diào)用同類或者父類中的其他方法(默認得接收者是self),但是,對于attribute writers來說這就不管用了,Ruby將把左面的名字作為一個本地變量,而不是一個對寫屬性方法的調(diào)用。
我們在 |
在學(xué)習(xí)了一段時間程序設(shè)計之后,我們可能會遇到要求將兩個變量的值互換:
int?a?=?1; int?b?=?2; int?temp;temp?=?a; a?=?b; b?=?temp; |
在Ruby中很簡單,只需要:
a,?b?=?b,?a |
Ruby可以有效的實現(xiàn)并行賦值,在右邊的值在被賦給左面的變量或?qū)傩灾鞍凑账鼈兊捻樣嵾M行求值,然后對應(yīng)的賦給左面的屬性或變量。一個例子如下,第二行給a,b,c的值分別是x,x+=1,x+=1計算之后的值。
x?=?0 |
? |
0 |
a,?b,?c???=???x,?(x?+=?1),?(x?+=?1) |
? |
[0,?1,?2] |
當(dāng)一個賦值語句左值多余一時,這個表達式的返回值是一個由右面的值組成的數(shù)組,如果一個賦值語句的左值多余右值,多余的左值被設(shè)為nil,反過來如果右值多余左值,那么多余的右值將被忽略。在Ruby1.6.2中,如果左值只有一個,右值有多個,那么這些右值將作為一個數(shù)組賦給左值。
你也可以在并行賦值語句中分解和擴展數(shù)組。如果最后的左值以星號作為前綴,那么所有對應(yīng)這個得值和以后的值將會組成一個數(shù)組,賦給這個左值(如下面第三行的c);類似的,如果最后一個右值是一個數(shù)組,你可以加一個星號作為前綴,Ruby將會把這個數(shù)組拆開按相應(yīng)的位置賦給左值(如下面第六行的c,而且,如果這個數(shù)組是唯一的右值,這個星號是可以省略的,作為右值得數(shù)組自動拆開,如第二行所示)。
a = [1, 2, 3, 4] | |||
b,??c?=?a | ? | b == 1, | c == 2 |
b,?*c?=?a | ? | b == 1, | c == [2, 3, 4] |
b,??c?=?99,??a | ? | b == 99, | c == [1, 2, 3, 4] |
b,?*c?=?99,??a | ? | b == 99, | c == [[1, 2, 3, 4]] |
b,??c?=?99,?*a | ? | b == 99, | c == 1 |
b,?*c?=?99,?*a | ? | b == 99, | c == [1, 2, 3, 4] |
并行賦值還有一個值得一提的特性,賦值語句左邊還可以包括用括號括起來的變量列表,Ruby中叫做嵌套賦值語句。Ruby首先摘出右值中相應(yīng)的項進行賦值,然后在進行高層的賦值操作。
b,?(c,?d),?e?=?1,2,3,4 | ? | b == 1, | c == 2, | d == nil, | e == 3 |
b,?(c,?d),?e?=?[1,2,3,4] | ? | b == 1, | c == 2, | d == nil, | e == 3 |
b,?(c,?d),?e?=?1,[2,3],4 | ? | b == 1, | c == 2, | d == 3, | e == 4 |
b,?(c,?d),?e?=?1,[2,3,4],5 | ? | b == 1, | c == 2, | d == 3, | e == 5 |
b,?(c,*d),?e?=?1,[2,3,4],5 | ? | b == 1, | c == 2, | d == [3, 4], | e == 5 |
像其它語言一樣,ruby也為a=a+2提供了類似a+=2的快捷方式。
第二種方式是第一種的深入,可以讓操作符當(dāng)成方法來工作。
class?Bowdlerize | ||
??def?initialize(aString) | ||
????@value?=?aString.gsub(/[aeiou]/,?'*') | ||
??end | ||
??def?+(other) | ||
????Bowdlerize.new(self.to_s?+?other.to_s) | ||
??end | ||
??def?to_s | ||
????@value | ||
??end | ||
end | ||
| ||
a?=?Bowdlerize.new("damn?") |
? |
d*mn |
a?+=?"shame" |
? |
d*mn?sh*m* |
Ruby有幾種不同的機制來實現(xiàn)條件執(zhí)行,大多數(shù)都感覺很類似,也有一些很靈巧,在深入討論之前,我們先來花點時間看看布爾表達式。
Ruby中的true定義很簡單,任何不是nil和false常量的東西都是true,你會發(fā)現(xiàn)系統(tǒng)的實現(xiàn)庫中很多這種用法。比如,
IO#gets
,用來返回一個文件的下一行,如果到了文件末尾,返回nil,所以,我們才可以這樣通過while來循環(huán)讀取數(shù)據(jù):
while?line?=?gets ??#?process?line end |
但是,這里對于c和perl程序員來說有一個誤區(qū),數(shù)字0和長度為0的字符串都不會被解釋成false值,需要注意。
Ruby支持所有標(biāo)準(zhǔn)的布爾操作,另外,還引入了新的操作符defined?
操作符``and
'' 和``&&
'' 只有當(dāng)兩面的值都為真才會返回真,第一個值為真,才會判斷第二個值,否則直接返回假。這兩個操作符的區(qū)別是優(yōu)先級不同(and低于 &&)
類似的 ``or
'' 和``||
''有一方為真就會返回真,如果第一個為真,則不會判斷后面的值,類似and,這兩個操作符只有優(yōu)先級的不同。
and和or有相同的優(yōu)先級,而&&的優(yōu)先級高于||。
?
``not
'' and ``!
'' 返回操作數(shù)的相反的值,如果操作數(shù)為true,則這個操作符返回false。并且and和!也只是優(yōu)先級不同。
所有的這些操作符和優(yōu)先級都在18章有詳細講述。
操作符defined?
將返回nil,如果操作數(shù)沒有定義的話。否則,將返回后面參數(shù)的描述信息。
?
defined??1 |
? |
"expression" |
defined??dummy |
? |
nil |
defined??printf |
? |
"method" |
defined??String |
? |
"constant" |
defined??$& |
? |
nil |
defined??$_ |
? |
"global-variable" |
defined??Math::PI |
? |
"constant" |
defined??(?c,d?=?1,2?) |
? |
"assignment" |
defined??42.abs |
? |
"method" |
除了這些布爾表達式,Ruby對象還支持使用 ==
, ===
, <=>
, =~
, eql?
, 和equal?
進行對象之間的比較。除了<=>之外這些操作符都在Object類中定義,但是經(jīng)常被子類重載。比如,類Array重定義了==方法,判斷兩個數(shù)組相同的條件事它們的個數(shù)相同,同一位置的元素也相同。
通用比較操作符
|
==和=~都有相反的操作符!=和!~,但是Ruby會將程序中的a!=b轉(zhuǎn)換為!(a==b),a!~b轉(zhuǎn)換成!(a=~b),如果你自己的類中重新寫了==和=~方法,那么你同時的到了!=和!~兩個方法;同時,你也不能離開了==和=~而孤立的定義!=和!~兩個方法。
你可以使用Ruby? range 作為一個布爾表達式,一個類似exp1..exp2
的range只有在遇到exp1為true,然后exp2又為true之后,才會返回true。下面循環(huán)部分有例子。
最后,你可以用正則表達式來當(dāng)作一個布爾表達式。Ruby expands it to $_=~/re/
.
Ruby中的if語句跟其他語言類似。
if?aSong.artist?==?"Gillespie"?then ??handle?=?"Dizzy" elsif?aSong.artist?==?"Parker"?then ??handle?=?"Bird" else ??handle?=?"unknown" end |
如果你的if語句寫在多行上,可以省略then關(guān)鍵字。
if?aSong.artist?==?"Gillespie" ??handle?=?"Dizzy" elsif?aSong.artist?==?"Parker" ??handle?=?"Bird" else ??handle?=?"unknown" end |
但是,如果你的語句都寫在一行上,then應(yīng)該寫上來分開布爾表達式和后面的語句。
if?aSong.artist?==?"Gillespie"?then??handle?=?"Dizzy" elsif?aSong.artist?==?"Parker"?then??handle?=?"Bird" else??handle?=?"unknown" end |
你可以使用0個或多個elsif語句,和一個可選的else語句。
就像我們前面說道的,if是一個表達式,不是一個statement,它可以返回一個值,你不必使用if表達式的返回值,但是它可能有些用處。
handle?=?if?aSong.artist?==?"Gillespie"?then ???????????"Dizzy" ?????????elsif?aSong.artist?==?"Parker"?then ???????????"Bird" ?????????else ???????????"unknown" ?????????end |
unless?aSong.duration?>?180?then ??cost?=?.25 else ??cost?=?.35 end |
最后,也為使用C語言的程序員準(zhǔn)備了條件表達式:
cost?=?aSong.duration?>?180???.35?:?.25 |
這個條件表達式在根據(jù)?前面的布爾值為true或false返回冒號前面或后面的值。在這個例子中,如果歌曲的時長大于3分鐘,將返回.35,否則返回.25,然后,將這個值賦給cost。
Ruby也借鑒了Perl的一些特點,語句修飾符(Statement modifiers)使我們可以在語句末尾加上條件語句。
mon,?day,?year?=?$1,?$2,?$3?if?/(\d\d)-(\d\d)-(\d\d)/ puts?"a?=?#{a}"?if?fDebug print?total?unless?total?==?0 |
對于if修飾符來說,只有當(dāng)if后面的條件為true,前面的語句才會執(zhí)行,unless正好和if相反。
while?gets ??next?if?/^#/????????????#?Skip?comments ??parseLine?unless?/^$/???#?Don't?parse?empty?lines end |
因為if本身也是表達式,所以下面的寫法將會使代碼變得難懂。
if?artist?==?"John?Coltrane" ??artist?=?"'Trane" end?unless?nicknames?==?"no" |
case
表達式非常強大,就像多個if的固化物一樣。
case?inputLine ??when?"debug" ????dumpDebugInfo ????dumpSymbols ??when?/p\s+(\w+)/ ????dumpVariable($1) ??when?"quit",?"exit" ????exit ??else ????print?"Illegal?command:?#{inputLine}" end |
像if一樣,case返回最后執(zhí)行的語句的結(jié)果,如果你的when和后面的語句都在一行,你也需要加一個then關(guān)鍵字。
kind?=?case?year ?????????when?1850..1889?then?"Blues" ?????????when?1890..1909?then?"Ragtime" ?????????when?1910..1929?then?"New?Orleans?Jazz" ?????????when?1930..1939?then?"Swing" ?????????when?1940..1950?then?"Bebop" ?????????else?????????????????"Jazz" ???????end |
case
操作符根據(jù)case后面目標(biāo)的值,跟每個when后面的值用===進行判斷,
operates by comparing the target (the expression after the keyword case
) with each of the comparison expressions after the when
keywords. This test is done using comparison?===
?target. As long as a class defines meaningful semantics for ===
(and all the built-in classes do), objects of that class can be used in case expressions.
===
as a simple pattern match.
case?line ??when?/title=(.*)/ ????puts?"Title?is?#$1" ??when?/track=(.*)/ ????puts?"Track?is?#$1" ??when?/artist=(.*)/ ????puts?"Artist?is?#$1" end |
Class
, which defines ===
as a test to see if the argument is an instance of the class or one of its superclasses. So (abandoning the benefits of polymorphism and bringing the gods of refactoring down around your ears), you can test the class of objects:
case?shape ??when?Square,?Rectangle ????#?... ??when?Circle ????#?... ??when?Triangle ????#?... ??else ????#?... end |
不要告訴他人,Ruby支持原始的靈巧的內(nèi)建循環(huán)結(jié)構(gòu)。
while循環(huán)根據(jù)它的條件的真假來執(zhí)行0次或者多次語句,比如,下面程序?qū)⒁恢边\行,直到輸入被打斷。
while?gets ??#?... end |
util也可以用來循環(huán),知道條件為真,才停止操作。
until?playList.duration?>?60 ??playList.add(songList.pop) end |
a?*=?2?while?a?<?100 a?-=?10?until?a?<?100 |
在前面的布爾表達式中,我們說過range也可以作為布爾表達式,這個機制多用于循環(huán)中,在下面的例子中,我們從一個包含從first到tenth的數(shù)字的文本文件中讀取數(shù)據(jù),但是只打印從以third開頭的行,直到遇到fifth開頭的行為止。
file?=?File.open("ordinal") while?file.gets ??print??if?/third/?..?/fifth/ end |
third fourth fifth |
$.
contains the current input line number to display line numbers one through three and those between a match of /eig/
and /nin/
.
file?=?File.open("ordinal") while?file.gets ??print?if?($.?==?1)?||?/eig/?..?($.?==?3)?||?/nin/ end |
first second third eighth ninth |
這里有一點需要注意,當(dāng)while和until用作語句修飾符的時候,如果它們修飾的語句以begin開頭,end結(jié)尾,這段代碼將總會執(zhí)行,而不管后面的條件。
print?"Hello\n"?while?false begin ??print?"Goodbye\n" end?while?false |
Goodbye |
上面我們知道了,Ruby支持簡單的循環(huán),比如,Ruby沒有for循環(huán),而c和JAV等都支持for循環(huán)的,但是Ruby提供了其他的機制,比如迭代,提供了類似的功能。
讓我們看看一個例子:
3.times?do ??print?"Ho!?" end |
Ho!?Ho!?Ho! |
這可以避免off-by-1 錯誤,這個循環(huán)將執(zhí)行3次。除了times,整數(shù)還可以接收一些方法來執(zhí)行循環(huán),比如downto,upto,和step等。比如,傳統(tǒng)的從0到9的循環(huán)(類似for( i=0; i < 10; i++)
) 類似下面的樣子:
0.upto(9)?do?|x| ??print?x,?"?" end |
0?1?2?3?4?5?6?7?8?9 |
一個從0到12,步長為3的循環(huán)如下:
0.step(12,?3)?{|x|?print?x,?"?"?} |
0?3?6?9?12 |
用于數(shù)組和其它容器的迭代的each方法也可以用來循環(huán)。
[?1,?1,?2,?3,?5?].each?{|val|?print?val,?"?"?} |
1?1?2?3?5 |
如果一個類支持了each方法,那么在模塊Enumerable
中的方法也可以直接使用。比如,F(xiàn)ile類提供了each方法,依次返回一個文件的每一行。使用Enumerable
中的grep方法,我們可以只迭代符合條件的行。
File.open("ordinal").grep?/d$/?do?|line| ??print?line end |
second third |
最后也是最簡單的,Ruby提供了一個內(nèi)建的最基本的迭代器loop。
loop?{ ??#?block?... } |
loop迭代器一直調(diào)用給定的block(或者你調(diào)用了break跳出循環(huán),后面會講到)。
前面我們說道Ruby支持的最基本循環(huán)視while和until,而for指的什么呢,可以看如下代碼:
for?aSong?in?songList ??aSong.play end |
Ruby將會把它翻譯為如下:
songList.each?do?|aSong| ??aSong.play end |
for和each的唯一區(qū)別是局部變量的作用域。
你可以在支持each的類上使用for方法,比如Array或者Range。for?i?in?['fee',?'fi',?'fo',?'fum'] ??print?i,?"?" end for?i?in?1..3 ??print?i,?"?" end for?i?in?File.open("ordinal").find_all?{?|l|?l?=~?/d$/} ??print?i.chomp,?"?" end |
fee?fi?fo?fum?1?2?3?second?third |
一旦你的類支持了each方法,你就可以使用for來進行遍歷。
class?Periods ??def?each ????yield?"Classical" ????yield?"Jazz" ????yield?"Rock" ??end end periods?=?Periods.new for?genre?in?periods ??print?genre,?"?" end |
Classical?Jazz?Rock |
循環(huán)控制結(jié)構(gòu) break
, redo
, 和 next
讓你可以控制循環(huán)或者迭代器的流程。
break
立即結(jié)束當(dāng)前循環(huán),然后跳出去執(zhí)行循環(huán)后面的語句。redo從這次循環(huán)體的頭開始重新執(zhí)行,但是不會在對條件進行運算或者從迭代中取下一個值。next跳到本次循環(huán)末尾,開始執(zhí)行下一次循環(huán)。
while?gets ??next?if?/^\s*#/???#?skip?comments ??break?if?/^END/???#?stop?at?end ????????????????????#?substitute?stuff?in?backticks?and?try?again ??redo?if?gsub!(/`(.*?)`/)?{?eval($1)?} ??#?process?line?... end |
這些關(guān)鍵字也可以用在基于迭代器的循環(huán)機制中。
i=0 loop?do ??i?+=?1 ??next?if?i?<?3 ??print?i ??break?if?i?>?4 end |
345 |
redo使一個循環(huán)從當(dāng)前迭代中重新執(zhí)行。有時候,你需要從新開始一個循環(huán),retry從新開始任何地迭代循環(huán)。
for?i?in?1..100 ??print?"Now?at?#{i}.?Restart??" ??retry?if?gets?=~?/^y/i end |
Now?at?1.?Restart??n Now?at?2.?Restart??y Now?at?1.?Restart??n ?.?.?. |
retry
將重新計算條件值,然后再開始循環(huán)。Ruby文檔有如下例子:
def?doUntil(cond) ??yield ??retry?unless?cond end i?=?0 doUntil(i?>?3)?{ ??print?i,?"?" ??i?+=?1 } |
0?1?2?3?4 |
while,until和for循環(huán)內(nèi)建于Ruby語言之中,沒有引入新的作用域,前面定義的局部變量可以在循環(huán)中使用,在循環(huán)中創(chuàng)建的變量在后面的代碼也可以使用。
而對loop或each使用block來說則不一樣了。在這個block中創(chuàng)建的變量在外面是不能訪問的。
[?1,?2,?3?].each?do?|x| ??y?=?x?+?1 end [?x,?y?] |
prog.rb:4: undefined local variable or method `x' for #<Object:0x401c2ce0> (NameError) |
然而,如果block中的變量和前面已經(jīng)定義的變量重名的話,已經(jīng)存在的變量將會在塊中使用,而在塊執(zhí)行完成后,這個變量的值也會改變。下面的例子,我們看到block執(zhí)行之后,兩個變量都改變了。
x?=?nil | ||
y?=?nil | ||
[?1,?2,?3?].each?do?|x| | ||
??y?=?x?+?1 | ||
end | ||
[?x,?y?] |
? |
[3,?4] |