亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

Verzeichnis suchen
Ruby用戶指南 3、開始 4、簡單的例子 5、字符串 6、正則表達式 7、數(shù)組 8、回到那些簡單的例子 9、流程控制 10、迭代器 11、面向對象思維 12、方法 13、類 14、繼承 15、重載方法 16、訪問控制 17、單態(tài)方法 18、模塊 19、過程對象 20、變量 21、全局變量 22、實變量 23、局部變量 24、類常量 25、異常處理:rescue 26、異常處理:ensure 27、存取器 28、對象的初始化 29、雜項 RGSS入門教程 1、什么是RGSS 2、開始:最簡單的腳本 3、數(shù)據(jù)類型:數(shù)字 4、數(shù)據(jù)類型:常量與變量 5、數(shù)據(jù)類型:字符串 6、控制語句:條件分歧語句 7、控制語句:循環(huán) 8、函數(shù) 9、對象與類 10、顯示圖片 11、數(shù)組 12、哈希表(關聯(lián)數(shù)組) 13、類 14、數(shù)據(jù)庫 15、游戲對象 16、精靈的管理 17、窗口的管理 18、活動指令 19、場景類 Programming Ruby的翻譯 Programming Ruby: The Pragmatic Programmer's Guide 前言 Roadmap Ruby.new 類,對象和變量 容器Containers,塊Blocks和迭代Iterators 標準類型 深入方法 表達式Expressions 異常,捕捉和拋出(已經(jīng)開始,by jellen) 模塊 基本輸入輸出 線程和進程 當遭遇挫折 Ruby和它的世界 Ruby和Web開發(fā) Ruby Tk Ruby 和微軟的 Windows 擴展Ruby Ruby語言 (by jellen) 類和對象 (by jellen) Ruby安全 反射Reflection 內(nèi)建類和方法 標準庫 OO設計 網(wǎng)絡和Web庫 Windows支持 內(nèi)嵌文檔 交互式Ruby Shell 支持 Ruby參考手冊 Ruby首頁 卷首語 Ruby的啟動 環(huán)境變量 對象 執(zhí)行 結束時的相關處理 線程 安全模型 正則表達式 字句構造 程序 變量和常數(shù) 字面值 操作符表達式 控制結構 方法調(diào)用 類/方法的定義 內(nèi)部函數(shù) 內(nèi)部變量 內(nèi)部常數(shù) 內(nèi)部類/模塊/異常類 附加庫 Ruby變更記錄 ruby 1.6 特性 ruby 1.7 特性 Ruby術語集 Ruby的運行平臺 pack模板字符串 sprintf格式 Marshal格式 Ruby FAQ Ruby的陷阱
Figuren

標準類型



到現(xiàn)在我們已經(jīng)實現(xiàn)了我們那點唱機的一部分代碼,我們看到了數(shù)組,哈希,方法,但我們還沒有涉及到Ruby中其他的數(shù)據(jù)類型:數(shù)字,字符串,范圍(ranges),正則表達式。下面我們就要花些時間來看看這些類型。

數(shù)字型

??????? Ruby 支持整型和浮點型兩種數(shù)字類型。整型可以是任意長度(最大值由你機器的內(nèi)存大小決定)。在一定范圍內(nèi)(通常是-230 to 230-1 or -262 to 262-1)在內(nèi)部由二進制方式表示,內(nèi)部類為Fixnum。大小超過這個范圍的整數(shù)由Bignum表示,如果Fixnum計算之后結果超出范圍,自動轉換為Bignum。Ruby在兩者之間自動轉換,對用戶來說是透明的。

num?=?8
7.times?do
??print?num.type,?"?",?num,?"\n"
??num?*=?num
end
produces:
Fixnum?8
Fixnum?64
Fixnum?4096
Fixnum?16777216
Bignum?281474976710656
Bignum?79228162514264337593543950336
Bignum?6277101735386680763835789423207666416102355444464034512896

 

你也可以在使用整型的時候在前面使用進制標示符,比如0表示八進制,0x表示十六進制,0b表示二進制等。而且,如果一個整型數(shù)字中有一個下劃線,這個下劃線將被忽略。

123456????????????????????#?Fixnum
123_456???????????????????#?Fixnum?(underscore?ignored)
-543??????????????????????#?Negative?Fixnum
123_456_789_123_345_789???#?Bignum
0xaabb????????????????????#?Hexadecimal
0377??????????????????????#?Octal
-0b101_010????????????????#?Binary?(negated)

你也可以得到一個ASCII字符或者一個轉意字符的數(shù)字值通過在它前面加一個問號。Control和Meta鍵的組合也可以用?\C-x, ?\M-x 和 ?\M-\C-x表達。字符value的加Control鍵的版本和"value & 0x9f"的值是一樣的;字符value的加Meta鍵的版本和"value & 0x80"的值是一樣的。最后,序列 ?\C-? 產(chǎn)生一個ASCII碼的刪除,0177。

?a????????????????????????#?字符的數(shù)字值
?\n???????????????????????#?換行符的值?(0x0a)
?\C-a?????????????????????#?control?a?=??A?&?0x9f?=?0x01
?\M-a?????????????????????#?meta?sets?bit?7
?\M-\C-a??????????????????#?meta?和?control?a
?\C-??????????????????????#?刪除字符

一個帶小數(shù)點或者帶指數(shù)的數(shù)字字符串會轉換成一個Float對象,對應于本機操作系統(tǒng)構架的double數(shù)據(jù)類型。你必須在小數(shù)點后面加一個數(shù)字,因為像 1.e3會認為是調(diào)用了Fixnum類的e3這個方法。

所有數(shù)字都是對象,會響應一些消息(在290, 313, 315, 323和349頁會完整的描述)。所以不像(比如說) C++,你會發(fā)現(xiàn)求一個數(shù)字的絕對值是這樣寫的 aNumber.abs,而不是abs(aNumber)。

整數(shù)也支持一些有用的迭代器(iterators)。我們已經(jīng)看到過一個了--- 7.times在47頁的代碼例子中。還有其他的比如 upto 和 downto,用來在兩個整數(shù)之間向上和向下迭代,還有 step,用于傳統(tǒng)的 for 循環(huán)語句。

3.times????????{?print?"X?"?}
1.upto(5)??????{?|i|?print?i,?"?"?}
99.downto(95)??{?|i|?print?i,?"?"?}
50.step(80,?5)?{?|i|?print?i,?"?"?}
會產(chǎn)生:
X?X?X?1?2?3?4?5?99?98?97?96?95?50?55?60?65?70?75?80

最后,給Perl使用者提供一個警告。含有數(shù)字字符的字符串在表達式中使用時不會自動轉換成數(shù)字。這最有可能在從文件里讀取數(shù)字時引發(fā)錯誤。下面的代碼沒有做我們 想要的。

DATA.each?do?|line|
??vals?=?line.split????#分割一行,把值存在vals里面
??print?vals[0]?+?vals[1],?"?"
end

給它一個文件,內(nèi)容是:

3?4
5?6
7?8

你會得到結果: ``34 56 78.'' 到底發(fā)生了什么呢?

問題就出在程序把輸入當成字符串,而不是數(shù)字的。加號運算符把兩個字符串連接,這就是我們看到這個結果的原因。要解決這個問題,我們可以使用String#to_i這個方法來把字符串轉換成整數(shù)。

DATA.each?do?|line|
??vals?=?line.split
??print?vals[0].to_i?+?vals[1].to_i,?"?"
end
得到結果:
7?11?15

字符串 Strings

Ruby的字符串是簡單的8位字節(jié)(8-bit bytes)序列。它們通常保存可打印字符序列,但是這不是必須的;一個字符串也可以保存二進制數(shù)據(jù)。字符串是String類的對象。

字符串通常用字符常量建立---包括在分隔符里面的字符序列。因為二進制數(shù)據(jù)很難在程序代碼里面表達,你可以在一個字符串常量里面使用各種轉義字符。每個轉義字符都會在程序編譯的時候轉換成相應的二進制數(shù)值。分隔符的種類表明了取代作用的程度。用單引號刮住的字符串里,兩個連續(xù)的反斜杠會被一個反斜杠取代,一個反斜杠后面跟一個單引號變成一個單引號。

'escape?using?"\\"' }} escape?using?"\"
'That\'s?right' }} That's?right

用雙引號刮住的字符串支持更多的轉義字符。最常見的轉義字符可能是"\n"了,代表一個換行符。第203頁的表18.2列出了完整的轉義字符。另外,你可以用 #{ expr } 來把任何的Ruby表達式的值插入到字符串中。如果那個表達式是全局變量,類變量或者實例變量,你可以省略大括號。

"Seconds/day:?#{24*60*60}" }} Seconds/day:?86400
"#{'Ho!?'*3}Merry?Christmas" }} Ho!?Ho!?Ho!?Merry?Christmas
"This?is?line?#$." }} This?is?line?3

還有三種方法來構建字符串常量:%q, %Q和“here documents.”

%q 和 %Q用來界定單引號和雙引號的范圍。

%q/general?single-quoted?string/ }} general?single-quoted?string
%Q!general?double-quoted?string! }} general?double-quoted?string
%Q{Seconds/day:?#{24*60*60}} }} Seconds/day:?86400

跟在'q'或者'Q'后面的字符是分隔符,如果那個字符是括號,大括號,圓括號或者小于等于符號,那么程序會一直向下讀直到遇見最近的停止符號,或者到匹配到相應的符號才停止,然后把讀入的字符作為一個字符串整體。

最后,你可以用"here document"構建字符串。

aString?=?<<END_OF_STRING
????The?body?of?the?string
????is?the?input?lines?up?to
????one?ending?with?the?same
????text?that?followed?the?'<<'
END_OF_STRING

一個 here document 由包含在開始到一個由你在'<<'后面指定的結束符之間的(但是不包括結束符)多行字符串組成。一般的,這個結束符必須在第一列開始,但是如果你在'<<'后面加一個減號,你就可以縮進結束符了。

print?<<-STRING1,?<<-STRING2
???Concat
???STRING1
??????enate
??????STRING2
產(chǎn)生結果:
?????Concat
????????enate

運用字符串

String可能是Ruby最大的內(nèi)建類了,它有超過75個標準方法。我們不會在這里挨個介紹它們;函數(shù)庫參考有它們完整的介紹。讓我們先來看看幾個常用的字符串用法---在日常編程中我們經(jīng)常會用到的。

讓我們回頭看看我們的點唱機。盡管它是被設計成聯(lián)接到互聯(lián)網(wǎng)上的,但是也保存了一些很流行的歌曲在本地硬盤上。這樣,即使我們的網(wǎng)絡連接出問題了,我們?nèi)匀荒軌蚍瘴覀兊念櫩汀?/p>

由于歷史的原因(還有其他原因嗎?),歌曲列表是成行的保存在一個文件里面的。文件中的每一行內(nèi)容包含了歌曲,歌曲長度,演唱者和曲名。所有的字段都是用小豎線分隔開的。一個典型的文件如下:

/jazz/j00132.mp3??|?3:45?|?Fats?????Waller?????|?Ain't?Misbehavin'
/jazz/j00319.mp3??|?2:58?|?Louis????Armstrong??|?Wonderful?World
/bgrass/bg0732.mp3|?4:09?|?Strength?in?Numbers?|?Texas?Red
?????????:??????????????????:???????????:???????????????????:

觀察一下數(shù)據(jù),很明顯在我們建立一些基于這個文件的Song對象之前,我們需要用String類的一些方法來展開和清理這些字段。至少,我們需要:

  • 把一行分成一個字段,
  • 把MM:SS格式的時間轉換成秒, 然后
  • 把演唱者名字中額外的空格去掉.

我們的第一個任務是要把每行分成各個字段,? String#split 方法最適合最這個。在這里,我們給 split 傳遞一個正則表達式,/\s*|\s*/,它會用小豎線和空格來分隔一行文本,把文本分成各個字段。還有,因為因為用文件里讀取的這行文本后面跟了一個換行符,在我們用split函數(shù)之前,我們可以使用 String#chomp 來把換行符去掉。

songs?=?SongList.new


songFile.each?do?|line|  
??file,?length,?name,?title?=?line.chomp.split(/\s*\|\s*/)  
??songs.append?Song.new(title,?name,?length)  
end  
puts?songs[1]
產(chǎn)生結果:
Song:?Wonderful?World--Louis????Armstrong?(2:58)

不幸的是,那個創(chuàng)建原始文件的人在敲演唱者名字的時候是按列敲的,有些名字里面可能包含額外的空格。這樣那些名字在我們極好的,高科技的,24小時運行的顯示板上顯示會很難看。我們必須去掉那些額外的空格才能繼續(xù)我們的工作。有很多方法來做這個工作,但是最簡單的可能是用 String#squeeze ,它會修飾重復的字符。我們在這里使用這個方法的 squeeze! 格式,它會改變適當位置的字符串。

songs?=?SongList.new

songFile.each?do?|line|   
??file,?length,?name,?title?=?line.chomp.split(/\s*\|\s*/)   
??name.squeeze!("?")   
??songs.append?Song.new(title,?name,?length)   
end   
puts?songs[1]
產(chǎn)生結果:
Song:?Wonderful?World--Louis?Armstrong?(2:58)

最后,還有一個小問題---時間的格式問題:文件說 2:58,我們想要用秒數(shù)來表示,178秒。我們可以再次使用 split 函數(shù),把用冒號分隔的兩個字段分開。

mins,?secs?=?length.split(/:/)

不過,我們這里使用另一個相關的函數(shù)。 String#scan 和 split有點相象,都可以通過一個模式匹配把一個字符串變成幾部分。 但是和split不同的是,scan允許你指定用來匹配字段的模式串。在咱們這個例子中,我們想為分字段和秒字段匹配一個或多個數(shù)字。一個或多個數(shù)字的正則式是 /\d+/。

songs?=?SongList.new
songFile.each?do?|line|
??file,?length,?name,?title?=?line.chomp.split(/\s*\|\s*/)
??name.squeeze!("?")
??mins,?secs?=?length.scan(/\d+/)
??songs.append?Song.new(title,?name,?mins.to_i*60+secs.to_i)
end
puts?songs[1]
產(chǎn)生結果:
Song:?Wonderful?World--Louis?Armstrong?(178)

我們的點唱機有關鍵字搜索的能力。給一個歌曲名或者演唱者姓名中的單詞,它能夠列出所有匹配的歌曲。比如,敲進"fats",它可能列出 Fats Domino, Fats Navarro, 和Fats Waller的歌曲。我們通過建立一個索引類來實現(xiàn)這個功能。給它一個對象和一些字符串,它會索引出那個對象里所有包含在字符串里的單詞(有兩個或者多個字符的)。這會用到String類里面另一些其他方法。

class?WordIndex
??def?initialize
????@index?=?Hash.new(nil)
??end
??def?index(anObject,?*phrases)
????phrases.each?do?|aPhrase|
??????aPhrase.scan?/\w[-\w']+/?do?|aWord|???#?extract?each?word
????????aWord.downcase!
????????@index[aWord]?=?[]?if?@index[aWord].nil?
????????@index[aWord].push(anObject)
??????end
????end
??end
??def?lookup(aWord)
????@index[aWord.downcase]
??end
end

String#scan 方法能從一個字符串里面提取出符合一個正則表達式 的元素。在這個例子里面模式串“\w[-\w']+”匹配任何能出現(xiàn)在單詞里的字符,后面跟一個或多個在方括號里指定的東東(一個小橫線,另一個單詞符號或者一個單引號)。我們會在56頁開始部分更詳細地介紹正則表達式。要讓我們的搜索大小寫不敏感,我們把剛才我們提取出來的字段和傳進來的關鍵字轉化成小寫。注意在第一個 downcase! 方法后面的感嘆號。就像以前我們使用的 squeeze! 方法一樣,"!"是一個標識來表明方法會在某個地方改變接受者,在這里表示把字符串變成小寫。[這段代碼里面有一個Bug, 歌曲"Gone, Gone, Gone"會被索引三次。你能想一個方法修正它嗎?]

我們來擴展一下我們的 SongList 類,讓它能夠在歌曲加進來的時候索引它們,然后加一個用一個字符串查找歌曲的方法。

class?SongList
??def?initialize
????@songs?=?Array.new
????@index?=?WordIndex.new
??end
??def?append(aSong)
????@songs.push(aSong)
????@index.index(aSong,?aSong.name,?aSong.artist)
????self
??end
??def?lookup(aWord)
????@index.lookup(aWord)
??end
end

最后,我們來測試一下:

songs?=?SongList.new
songFile.each?do?|line|
??file,?length,?name,?title?=?line.chomp.split(/\s*\|\s*/)
??name.squeeze!("?")
??mins,?secs?=?length.scan(/\d+/)
??songs.append?Song.new(title,?name,?mins.to_i*60+secs.to_i)
end
puts?songs.lookup("Fats")
puts?songs.lookup("ain't")
puts?songs.lookup("RED")
puts?songs.lookup("WoRlD")
產(chǎn)生結果:
Song:?Ain't?Misbehavin'--Fats?Waller?(225)
Song:?Ain't?Misbehavin'--Fats?Waller?(225)
Song:?Texas?Red--Strength?in?Numbers?(249)
Song:?Wonderful?World--Louis?Armstrong?(178)

我們可以再花50頁的篇幅來介紹 String 類里面的所有方法。但是,現(xiàn)在還是讓我們繼續(xù)來學習一個簡單的數(shù)據(jù)類型:范圍(ranges)。

 

(范圍)Ranges

范圍無處不在:從一月到十二月,0到9,半熟到完全煮熟,從第50行到第67行等等。如果ruby想要幫助我們很好的根據(jù)現(xiàn)實世界來建模,那么它也應該支持這些范圍。實際上正是如此,ruby支持的ranges有三種用途:序列,條件,和間隔(sequences, conditions, and intervals)。

作為序列

ruby的range最常用的用處是表示一個順序的序列,序列有一個開始點,和一個結束點,和產(chǎn)生序列中下一個值的方法。ruby中,定義學列使用".."和"..."操作符。".."創(chuàng)建的序列包括兩邊的邊界值,而"..."創(chuàng)建的序列將不包括最大的那個邊界值。

1..10
'a'..'z'
0...anArray.length

ruby不像其他一些地早期perl那樣,把序列保存在一個內(nèi)部列表中,比如,1..100000在ruby中只是一個Range對象,包括兩個指向Fixnum對象的引用。如果需要,你可以把一個Range用to_a轉換成一個數(shù)組。

(1..10).to_a }} [1,?2,?3,?4,?5,?6,?7,?8,?9,?10]
('bar'..'bat').to_a }} ["bar",?"bas",?"bat"]

Ranges實現(xiàn)了一些可以讓你對其進行迭代,測試是否包含某個值的方法。

digits?=?0..9
digits.include?(5) }} true
digits.min }} 0
digits.max }} 9
digits.reject?{|i|?i?<?5?} }} [5,?6,?7,?8,?9]
digits.each?do?|digit|
??dial(digit)
end

到現(xiàn)在我們的ranges表示的都是數(shù)字和字符串型,作為一個面向對象的語言,ranges也可以用于我們自己創(chuàng)建的對象,但是這個對象必須實現(xiàn)一個succ方法,以返回下一個值,而且這個對象也必須支持<=>來對其進行比較。調(diào)用<=>時,它們比較前后兩個對象的大小關系,根據(jù)兩個對象是小于,等于,還是大于而返回-1, 0, 或者 +1

下面看一個簡單的例子,這個類表示由若干個"#"符號組成的一行,我們可以用它來對我們點唱機音量做基于文本的測試。

class?VU

 
??include?Comparable
 
??attr?:volume
 
??def?initialize(volume)??#?0..9      
????@volume?=?volume      
??end
 
??def?inspect      
????'#'?*?@volume      
??end
 
??#?Support?for?ranges
 
??def?<=>(other)      
????self.volume?<=>?other.volume      
??end
 
??def?succ      
????raise(IndexError,?"Volume?too?big")?if?@volume?>=?9      
????VU.new(@volume.succ)      
??end      
end

我們可以創(chuàng)建一個VU的range來測試一下:

medium?=?VU.new(4)..VU.new(7)
medium.to_a }} [####,?#####,?######,?#######]
medium.include?(VU.new(3)) }} false

條件范圍( Ranges as Conditions )

除了表示一系列連續(xù)值之外,range還能作為條件表達式。比如,下面代碼將接收標準輸入,打印出來那些以start開頭和以end結尾的代碼行。

while?gets
??print?if?/start/../end/
end

Ranges表示間隔

rang最后一個用處是測試一些值是否在這個間隔之內(nèi),這要用到操作符===

(1..10)????===?5 }} true
(1..10)????===?15 }} false
(1..10)????===?3.14159 }} true
('a'..'j')?===?'c' }} true
('a'..'j')?===?'z' }} false

正則表達式

回到第50頁當我們從一個文件里創(chuàng)建歌曲列表的時候,我們用了一個正則表達式去匹配文件里的字段。我們聲明了正則表達式 line.split(/\s*|\s*)來匹配一個小豎線被(不是必須的)空格環(huán)繞的情況。讓我們來仔細研究一下正則表達式來證明為什么我們的聲明是正確的。

正則表達式用來匹配字符串的模式。Ruby提供了對模式匹配和替換內(nèi)建的支持,使我們使用時很方便簡練。在這部分,我們會介紹大部分主要的正則表達式特征。有些細節(jié)我們不會涉及:你可以參考第205頁取得更多信息。

正則表達式是類型Regexp的對象。它們可以用顯式的構造函數(shù)建立或者直接用 /pattern/ 和 %r/pattern/這種格式的字符常量構造。

a?=?Regexp.new('^\s*[a-z]') }} /^\s*[a-z]/
b?=?/^\s*[a-z]/ }} /^\s*[a-z]/
c?=?%r{^\s*[a-z]} }} /^\s*[a-z]/

一當你有了一個正則表達式對象,你可以用它和一個字符串比較,通過使用 Regexp#match(aString) 或者用匹配操作符 =~(確定匹配)和 !~(否定匹配)。字符串和Regexp對象都可以使用匹配操作符。如果匹配操作符的兩個操作數(shù)都是字符串的話,右邊那個會轉化成正則表達式。

a?=?"Fats?Waller"
a?=~?/a/ }} 1
a?=~?/z/ }} nil
a?=~?"ll" }} 7

匹配操作符返回模式匹配成功的字符位置。 它們還有一個設置所有Ruby變量的額外作用。$&接受模式匹配成功的那部分字符,$`(鍵盤1左邊那個鍵)接受模式匹配成功前面那一部分字符,$'接受模式匹配成功后面那部分字符。我們可以用這個來編寫一個函數(shù),showRE,它闡明了一個特殊的模式匹配例子:
def?showRE(a,re)
??if?a?=~?re
????"#{$`}<<#{$&}>>#{$'}"
??else
????"no?match"
??end
end
showRE('very?interesting',?/t/) }} very?in<<t>>eresting
showRE('Fats?Waller',?/ll/) }} Fats?Wa<<ll>>er

這個匹配也設置了Ruby的全局線程(thread-global)變量 $~ 和 $1 到 $9。變量 $~ 是一個MatchData對象(在336頁開始部分有描述),它保存了所有關于這個匹配的信息。$1和其他$*保存了這個匹配的部分,我們呆會兒還會討論它們。如果有人看見這些像Perl語言的變量名感到害怕,不要著急,這章后面還有好消息。

模式(Patterns)

每個正則表達式都有一個模式,用來和字符串做匹配。

在一個模式中,除了., |, (, ), [, {, +, \, ^, $, *和 ? 之外的字符都是和它本身匹配。

showRE('kangaroo',?/angar/) }} k<<angar>>oo
showRE('!@%&-_=+',?/%&/) }} !@<<%&>>-_=+

如果你想在字面上匹配一個上面的特殊字符,在它前面加一個'\'。這解釋了我們用在分離那個歌曲列表文件時用的一個正則表達式的一部分,/\s*|\s*/。'\|'表示匹配一個'|',沒有那個反斜杠,'|'代表交換(我們會在后面描述)。

showRE('yes?|?no',?/\|/) }} yes?<<|>>?no
showRE('yes?(no)',?/\(no\)/) }} yes?<<(no)>>
showRE('are?you?sure?',?/e\?/) }} are?you?sur<<e?>>

反斜杠后跟一個數(shù)字字符用來引進一個特殊的匹配構造,我們會在后面介紹它。另外,一個正則表達式可以包含#{...}表達式取代。

Anchors

一個正則表達式默認會找到字符串中第一個匹配的情況。要在字符串"Mississippi"中匹配 /iss/ ,它會找到那個靠近開始位置的哪個子串"iss"。但是當你想自己指定從頭部或者末尾開始匹配時要怎么設置呢?

模式 ^ 和 $ 分別匹配一行字符的開始和結束。它們經(jīng)常用來指定一個模式匹配的方向:比如,/^option/和在一行文本開始處出現(xiàn)的字符串'option'匹配。字符序列 \A 和一個字符串的開始匹配,\z 和 \Z 和一個字符串的結束匹配。(事實上,\Z 和一個以"\n"結尾的字符串的結束匹配,這種情況下,它從'\n'前面開始匹配)。

showRE("this?is\nthe?time",?/^the/) }} this?is\n<<the>>?time
showRE("this?is\nthe?time",?/is$/) }} this?<<is>>\nthe?time
showRE("this?is\nthe?time",?/\Athis/) }} <<this>>?is\nthe?time
showRE("this?is\nthe?time",?/\Athe/) }} no?match

相似的,模式 \b 和 \B 分別和單詞界限(word boundaries)和非單詞界限(nonword boundaries)匹配。構成單詞的字符有字母,數(shù)字和下劃線。

showRE("this?is\nthe?time",?/\bis/) }} this?<<is>>\nthe?time
showRE("this?is\nthe?time",?/\Bis/) }} th<<is>>?is\nthe?time

字符類(character class)

一個字符類是一系列在方括號("[...]")之間的字符,用來匹配方括號里面的單個字符。比如,[aeiou]會和元音字符匹配,[,.:;!?]和標點符號匹配,等等。那些重要的特殊字符(.|()[{+^$*?)在方括號里面會失去匹配作用。但是普通的轉義字符仍然起作用,所以,\b代表退格鍵,\n是換行符(見203頁表18.2)。另外,你可以用59頁的表5.1的縮寫,所以 \s 表示空白符,不僅僅是一個字面上的空格。

showRE('It?costs?$12.',?/[aeiou]/) }} It?c<<o>>sts?$12.
showRE('It?costs?$12.',?/[\s]/) }} It<<?>>costs?$12.

在方括號里面,序列 c1-c2 表示包括在c1到c2之間的字符。

如果你想在字符類(方括號)里面包含 ] 和 - 的話,它們必須出現(xiàn)在開始。?

a?=?'Gamma?[Design?Patterns-page?123]'
showRE(a,?/[]]/) }} Gamma?[Design?Patterns-page?123<<]>>
showRE(a,?/[B-F]/) }} Gamma?[<<D>>esign?Patterns-page?123]
showRE(a,?/[-]/) }} Gamma?[Design?Patterns<<->>page?123]
showRE(a,?/[0-9]/) }} Gamma?[Design?Patterns-page?<<1>>23]

在'['后面緊跟一個 ^ 代表字符類相反的含義: [^a-z]和不是小寫字母的字符匹配。

一些字符類特別常用,所以Ruby提供了它們的縮寫形式。這些縮寫列在59頁的表5.1上,它們可以用在方括號和模式串里面。

showRE('It?costs?$12.',?/\s/) }} It<<?>>costs?$12.
showRE('It?costs?$12.',?/\d/) }} It?costs?$<<1>>2.

字符類縮寫
字符序列 ?[ ... ] 意思
\d [0-9] 數(shù)字字符
\D [^0-9] 非數(shù)字
\s [\s\t\r\n\f] 空格字符
\S [^\s\t\r\n\f] 非空格字符
\w [A-Za-z0-9_] 單詞符號
\W [^A-Za-z0-9_] 非單詞符號

最后,一個在放括號外面出現(xiàn)的句點"."表示除了換行符以外的任何字符(在多行模式下它也表示一個換行符)。

a?=?'It?costs?$12.'
showRE(a,?/c.s/) }} It?<<cos>>ts?$12.
showRE(a,?/./) }} <<I>>t?costs?$12.
showRE(a,?/\./) }} It?costs?$12<<.>>

重復(Repetition)

在我們講述那個分隔歌曲文件的正則模式(/\s*\|\s*/)的時候, 我們說想匹配在一個'|'兩邊環(huán)繞任意的空格的情況?,F(xiàn)在我們知道了 \s 匹配一個空白符,所以看來星號'*'代表任意數(shù)的大小。事實上,星號是允許你匹配模式多次出現(xiàn)的修飾符中的一個。

如果 r 代表一個模式里面的前置字符,那么:
r * 匹配0個或多個 r.
r + 匹配1個或多個 r.
r ? 匹配0個或1個 r.
r {m,n} 匹配最少m個,最多n個 r.
r {m,} 匹配最少m個 r.

這些重復修飾符有很高的優(yōu)先權---在正則模式串里它們僅僅和它們的緊密前綴綁定。/ab+/匹配一個"a"后面跟一個或多個"b"而不是一個"ab"組成的序列。你也必須小心使用'*'修飾符---正則模式串/a*/會匹配任何字符串;任何有0個或者多個"a"的字符串。

這些模式串被稱為“貪婪地”,因為它們默認會匹配盡量多的字符。你可以改變這種行為,讓它們匹配最少的,只要加一個問號后綴就可以了。

a?=?"The?moon?is?made?of?cheese"
showRE(a,?/\w+/) }} <<The>>?moon?is?made?of?cheese
showRE(a,?/\s.*\s/) }} The<<?moon?is?made?of?>>cheese
showRE(a,?/\s.*?\s/) }} The<<?moon?>>is?made?of?cheese
showRE(a,?/[aeiou]{2,99}/) }} The?m<<oo>>n?is?made?of?cheese
showRE(a,?/mo?o/) }} The?<<moo>>n?is?made?of?cheese

間隔(Alternation)

我們知道‘|’是特殊的,因為我們的在行分隔模式中必須用一個反斜杠使之轉義。因為一個沒有反斜杠的‘|’匹配正則表達式中它左右兩邊模式中的一個。

a?=?"red?ball?blue?sky"
showRE(a,?/d|e/) }} r<<e>>d?ball?blue?sky
showRE(a,?/al|lu/) }} red?b<<al>>l?blue?sky
showRE(a,?/red?ball|angry?sky/) }} <<red?ball>>?blue?sky

要是我們一粗心,這里會有一個陷阱,因為‘|’的優(yōu)先級很低。在上面最后一個例子中,我們的正則式匹配“red ball”或者“angry sky”,而不是“red ball sky”或“red angry sky”。為了匹配“red ball sky”或“red angry sky”,我們用grouping重載默認的優(yōu)先級。

成組技術(Grouping)

你可以在正則式中用圓括號來成組字符集。在這個組里面的所有字符會被認為是一個正則表達式。

showRE('banana',?/an*/) }} b<<an>>ana
showRE('banana',?/(an)*/) }} <<>>banana
showRE('banana',?/(an)+/) }} b<<anan>>a

a?=?'red?ball?blue?sky'
showRE(a,?/blue|red/) }} <<red>>?ball?blue?sky
showRE(a,?/(blue|red)?\w+/) }} <<red?ball>>?blue?sky
showRE(a,?/(red|blue)?\w+/) }} <<red?ball>>?blue?sky
showRE(a,?/red|blue?\w+/) }} <<red>>?ball?blue?sky

showRE(a,?/red?(ball|angry)?sky/) }} no?match
a?=?'the?red?angry?sky'
showRE(a,?/red?(ball|angry)?sky/) }} the?<<red?angry?sky>>

圓括號也用來收集模式匹配的結果。Ruby對左括號記數(shù),對每個左括號,它保存了已經(jīng)匹配的部分結果和相應的右括號。你可以在剩下的模式串中或者你的Ruby程序里使用這個匹配。在模式匹配中,\1代表第1個組,\2代表第2個組,其他依次類推。在模式串外面,特殊變量 $1, $2和其他$*和這個作用一樣。

"12:50am"?=~?/(\d\d):(\d\d)(..)/ }} 0
"Hour?is?#$1,?minute?#$2" }} "Hour?is?12,?minute?50"
"12:50am"?=~?/((\d\d):(\d\d))(..)/ }} 0
"Time?is?#$1" }} "Time?is?12:50"
"Hour?is?#$2,?minute?#$3" }} "Hour?is?12,?minute?50"
"AM/PM?is?#$4" }} "AM/PM?is?am"

能夠利用目前的部分匹配允許你尋找各種形式的循環(huán)。

#?匹配重復的字母
showRE('He?said?"Hello"',?/(\w)\1/) }} He?said?"He<<ll>>o"
#?匹配重復的子串
showRE('Mississippi',?/(\w+)\1/) }} M<<ississ>>ippi

你也可以使用后置引用來匹配分隔符。

showRE('He?said?"Hello"',?/(["']).*?\1/) }} He?said?<<"Hello">>
showRE("He?said?'Hello'",?/(["']).*?\1/) }} He?said?<<'Hello'>>

基于模式的子串技術

有時候在一個字符串里面尋找一個模式已經(jīng)滿足要求了。如果一個朋友刁難你,要你找出一個順序包含a, b, c, d 和 e 的單詞,你可以用模式串 /a.*b.*c.*d.*e/來尋找然后可以找到"absconded"和"ambuscade" 。這毫無疑問是挺有用的。

然后,有時候我們需要改變一個模式匹配的內(nèi)容。讓我們回到我們的歌曲列表文件。創(chuàng)建文件的人用小寫字母敲進了演唱者的名字。當我們把名字顯示在點唱機上時,我們想讓它大小寫混寫。那么我們怎么樣才能把每個單詞的首字母變成大寫呢?

String#sub 和 String#gsub 方法 尋找字符串中匹配它們第一個參數(shù)的那部分,然后把那部分用它們的第二個參數(shù)代替。String#sub 只替換一次,String#gsub 則替換所有在字符串里出現(xiàn)的匹配。兩個方法都返回一個已經(jīng)替換過的新字符串的拷貝。另外一個版本的方法String#sub!String#gsub! 會修改原始字符串。

a?=?"the?quick?brown?fox"
a.sub(/[aeiou]/,??'*') }} "th*?quick?brown?fox"
a.gsub(/[aeiou]/,?'*') }} "th*?q**ck?br*wn?f*x"
a.sub(/\s\S+/,??'') }} "the?brown?fox"
a.gsub(/\s\S+/,?'') }} "the"

第二個參數(shù)可以是一個字符串或者一個程序塊(block)。如果用了程序塊,那個程序塊的值被替換進了字符串。

a?=?"the?quick?brown?fox"
a.sub(/^./)?{?$&.upcase?} }} "The?quick?brown?fox"
a.gsub(/[aeiou]/)?{?$&.upcase?} }} "thE?qUIck?brOwn?fOx"

所以,這看起來是我們轉變演唱者名字的正確方法。匹配一個單詞的首字母的模式串是 \b\w---尋找一個單詞邊界然后跟一個字母。結合 gsub 使用,我們可以來修改演唱者的名字了。?

def?mixedCase(aName)
??aName.gsub(/\b\w/)?{?$&.upcase?}
end
mixedCase("fats?waller") }} "Fats?Waller"
mixedCase("louis?armstrong") }} "Louis?Armstrong"
mixedCase("strength?in?numbers") }} "Strength?In?Numbers"

子串函數(shù)中的轉義字符

前面我們講過\1, \2和類似的字符序列可以在模式串中使用,代表到現(xiàn)在為止已經(jīng)匹配的第n組數(shù)據(jù)。相同的字符序列也可以在 sub 和 gsub函數(shù)的第二個參數(shù)中使用。

"fred:smith".sub(/(\w+):(\w+)/,?'\2,?\1') }} "smith,?fred"
"nercpyitno".gsub(/(.)(.)/,?'\2\1') }} "encryption"

還有一些外加的轉義字符用在字符串替換中:\&(最后那個匹配),\+(最后匹配的組),\`(匹配串前面的字符串),\'(匹配后面的字符串),\\(反斜杠)。如果你在替換中使用反斜杠那么會引起混亂。最明顯的例子是:

str.gsub(/\\/,?'\\\\')

很清楚,這句代碼想把 str 里面的一個反斜杠變成兩個。程序員用了兩個反斜杠在替換文本里面,希望它們在語法分析時變成兩個反斜杠。但是當替換發(fā)生時,正則表達式引擎又讀了一遍字符串,把"\\"變成了"\",所以上面代碼的作用是用一個反斜杠替換另外一個反斜杠。你需要這樣寫 gsub(/\\/, '\\\\\\\\')!

str?=?'a\b\c' }} "a\b\c"
str.gsub(/\\/,?'\\\\\\\\') }} "a\\b\\c"

因為 \& 會被匹配的字符串替換,所以你也可以這樣寫:

str?=?'a\b\c' }} "a\b\c"
str.gsub(/\\/,?'\&\&') }} "a\\b\\c"

如果你使用 gsub 的程序塊形式,用來替換的字符串僅僅被分析一次(在語法分析階段),所以結果是你想要的:

str?=?'a\b\c' }} "a\b\c"
str.gsub(/\\/)?{?'\\\\'?} }} "a\\b\\c"

最后,作為正則表達式和程序塊結合起來的強大表現(xiàn)力的例子,我們來看看這段CGI函數(shù)庫模塊里的代碼,是Wakou Aoyama編寫的。代碼接受一段包含HTML文本的字符串然后把它轉化成普通的ASCII文本。因為這是為日本的用戶編寫的,它在正則式用了"n"修飾符來使寬字符失效。代碼也演示了Ruby的 case 語句,我們在81頁討論它。

def?unescapeHTML(string)
??str?=?string.dup
??str.gsub!(/&(.*?);/n)?{
????match?=?$1.dup
????case?match
????when?/\Aamp\z/ni???????????then?'&'
????when?/\Aquot\z/ni??????????then?'"'
????when?/\Agt\z/ni????????????then?'>'
????when?/\Alt\z/ni????????????then?'<'
????when?/\A#(\d+)\z/n?????????then?Integer($1).chr
????when?/\A#x([0-9a-f]+)\z/ni?then?$1.hex.chr
????end
??}
??str
end

 
puts?unescapeHTML("1&lt;2?&amp;&amp;?4&gt;3")       
puts?unescapeHTML("&quot;A&quot;?=?&#65;?=?&#x41;")
產(chǎn)生結果:
1<2?&&?4>3
"A"?=?A?=?A

面向對象的正則表達式

我們必須承認雖然這些關怪的表達式很好用,但是它們不是面向對象的,而且相當晦澀難懂。你們不是說過Ruby里面任何東西都是對象嗎?為什么這里是這樣的呢?

這無關緊要,真的。因為Matz在設計Ruby的時候,他構建了一個完全面向對的正則表達式處理系統(tǒng)。但是為了讓Perl程序員感到熟悉一些,他把這些 $* 包裝在這系統(tǒng)之上。這些對象和類還在那里,在外觀里面,現(xiàn)在讓我們花點時間把它們挖出來。

我們已經(jīng)遇到過這樣一個類了:字面正則表達式的產(chǎn)生類 Regexp(在361頁有具體描述)。

re?=?/cat/
re.type }} Regexp

方法 Regexp#match 把一個正則表達式和一個字符串進行匹配。如果不成功,方法返回 nil。在成功的情況下,它返回類 Matchdata 的一個實例,在336頁有詳細描述。然后那個 MatchData 對象給你訪問這個匹配的各種信息的方法。所有能從 $-*變量里得到的好東東都可以在咱們手邊這個小對象里得到。

re?=?/(\d+):(\d+)/?????#?匹配一個時間?hh:mm
md?=?re.match("Time:?12:34am")
md.type }} MatchData
md[0]?????????#?==?$& }} "12:34"
md[1]?????????#?==?$1 }} "12"
md[2]?????????#?==?$2 }} "34"
md.pre_match??#?==?$` }} "Time:?"
md.post_match?#?==?$' }} "am"

因為匹配信息是保存在它自己對象里的,你可以在同一時間保存兩個或者多個匹配的結果,這用$-*你可能不能實現(xiàn)。在下面一個例子里,我們用同一個 Regexp 對象去匹配兩個不同的字符串。每個匹配都返回一個唯一的 MatchData 對象,我們通過它們的兩個子模式字段來區(qū)別它們。

re?=?/(\d+):(\d+)/?????#?匹配一個時間?hh:mm
md1?=?re.match("Time:?12:34am")
md2?=?re.match("Time:?10:30pm")
md1[1,?2] }} ["12",?"34"]
md2[1,?2] }} ["10",?"30"]

但是那些 $-*是怎么包裝起來的呢?每個模式匹配結束以后,Ruby在一個局部線程(thread-local)變量中保存了一個指向結果的引用(nil 或者 是一個 MatchData 對象)。所有其他的正則表達式變量都是從這個對象中繼承而來的。雖然我們不能真正這樣使用以下代碼,但是它證明了所有其他的和MatchData有關的 $-*變量都是 $~里面的值。

re?=?/(\d+):(\d+)/
md1?=?re.match("Time:?12:34am")
md2?=?re.match("Time:?10:30pm")
[?$1,?$2?]???#?最后匹配成功的情況 }} ["10",?"30"]
$~?=?md1
[?$1,?$2?]???#?上一個匹配成功情況 }} ["12",?"34"]

說完了所有這些,我們必須說點實話:) Andy和Dave平常都只是使用$-*變量而不去擔心 MatchData對象。在日常應用中,我們只要用起來舒服就行。有時候我們情不自禁地使自己變得更務實。


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright ? 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Vorheriger Artikel: N?chster Artikel: