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

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

標(biāo)準(zhǔn)類型



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

數(shù)字型

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

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

 

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

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)

你也可以得到一個(gè)ASCII字符或者一個(gè)轉(zhuǎn)意字符的數(shù)字值通過在它前面加一個(gè)問號(hào)。Control和Meta鍵的組合也可以用?\C-x, ?\M-x 和 ?\M-\C-x表達(dá)。字符value的加Control鍵的版本和"value & 0x9f"的值是一樣的;字符value的加Meta鍵的版本和"value & 0x80"的值是一樣的。最后,序列 ?\C-? 產(chǎn)生一個(gè)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-??????????????????????#?刪除字符

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

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

整數(shù)也支持一些有用的迭代器(iterators)。我們已經(jīng)看到過一個(gè)了--- 7.times在47頁(yè)的代碼例子中。還有其他的比如 upto 和 downto,用來在兩個(gè)整數(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,?"?"?}
會(huì)產(chǎn)生:
X?X?X?1?2?3?4?5?99?98?97?96?95?50?55?60?65?70?75?80

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

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

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

3?4
5?6
7?8

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

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

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

字符串 Strings

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

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

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

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

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

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

%q 和 %Q用來界定單引號(hào)和雙引號(hào)的范圍。

%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'后面的字符是分隔符,如果那個(gè)字符是括號(hào),大括號(hào),圓括號(hào)或者小于等于符號(hào),那么程序會(huì)一直向下讀直到遇見最近的停止符號(hào),或者到匹配到相應(yīng)的符號(hào)才停止,然后把讀入的字符作為一個(gè)字符串整體。

最后,你可以用"here document"構(gòu)建字符串。

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

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

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

運(yùn)用字符串

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

讓我們回頭看看我們的點(diǎn)唱機(jī)。盡管它是被設(shè)計(jì)成聯(lián)接到互聯(lián)網(wǎng)上的,但是也保存了一些很流行的歌曲在本地硬盤上。這樣,即使我們的網(wǎng)絡(luò)連接出問題了,我們?nèi)匀荒軌蚍?wù)我們的顧客。

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

/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ù),很明顯在我們建立一些基于這個(gè)文件的Song對(duì)象之前,我們需要用String類的一些方法來展開和清理這些字段。至少,我們需要:

  • 把一行分成一個(gè)字段,
  • 把MM:SS格式的時(shí)間轉(zhuǎn)換成秒, 然后
  • 把演唱者名字中額外的空格去掉.

我們的第一個(gè)任務(wù)是要把每行分成各個(gè)字段,? String#split 方法最適合最這個(gè)。在這里,我們給 split 傳遞一個(gè)正則表達(dá)式,/\s*|\s*/,它會(huì)用小豎線和空格來分隔一行文本,把文本分成各個(gè)字段。還有,因?yàn)橐驗(yàn)橛梦募镒x取的這行文本后面跟了一個(gè)換行符,在我們用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)生結(jié)果:
Song:?Wonderful?World--Louis????Armstrong?(2:58)

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

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)生結(jié)果:
Song:?Wonderful?World--Louis?Armstrong?(2:58)

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

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

不過,我們這里使用另一個(gè)相關(guān)的函數(shù)。 String#scan 和 split有點(diǎn)相象,都可以通過一個(gè)模式匹配把一個(gè)字符串變成幾部分。 但是和split不同的是,scan允許你指定用來匹配字段的模式串。在咱們這個(gè)例子中,我們想為分字段和秒字段匹配一個(gè)或多個(gè)數(shù)字。一個(gè)或多個(gè)數(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)生結(jié)果:
Song:?Wonderful?World--Louis?Armstrong?(178)

我們的點(diǎn)唱機(jī)有關(guān)鍵字搜索的能力。給一個(gè)歌曲名或者演唱者姓名中的單詞,它能夠列出所有匹配的歌曲。比如,敲進(jìn)"fats",它可能列出 Fats Domino, Fats Navarro, 和Fats Waller的歌曲。我們通過建立一個(gè)索引類來實(shí)現(xiàn)這個(gè)功能。給它一個(gè)對(duì)象和一些字符串,它會(huì)索引出那個(gè)對(duì)象里所有包含在字符串里的單詞(有兩個(gè)或者多個(gè)字符的)。這會(huì)用到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 方法能從一個(gè)字符串里面提取出符合一個(gè)正則表達(dá)式 的元素。在這個(gè)例子里面模式串“\w[-\w']+”匹配任何能出現(xiàn)在單詞里的字符,后面跟一個(gè)或多個(gè)在方括號(hào)里指定的東東(一個(gè)小橫線,另一個(gè)單詞符號(hào)或者一個(gè)單引號(hào))。我們會(huì)在56頁(yè)開始部分更詳細(xì)地介紹正則表達(dá)式。要讓我們的搜索大小寫不敏感,我們把剛才我們提取出來的字段和傳進(jìn)來的關(guān)鍵字轉(zhuǎn)化成小寫。注意在第一個(gè) downcase! 方法后面的感嘆號(hào)。就像以前我們使用的 squeeze! 方法一樣,"!"是一個(gè)標(biāo)識(shí)來表明方法會(huì)在某個(gè)地方改變接受者,在這里表示把字符串變成小寫。[這段代碼里面有一個(gè)Bug, 歌曲"Gone, Gone, Gone"會(huì)被索引三次。你能想一個(gè)方法修正它嗎?]

我們來擴(kuò)展一下我們的 SongList 類,讓它能夠在歌曲加進(jìn)來的時(shí)候索引它們,然后加一個(gè)用一個(gè)字符串查找歌曲的方法。

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

最后,我們來測(cè)試一下:

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)生結(jié)果:
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頁(yè)的篇幅來介紹 String 類里面的所有方法。但是,現(xiàn)在還是讓我們繼續(xù)來學(xué)習(xí)一個(gè)簡(jiǎn)單的數(shù)據(jù)類型:范圍(ranges)。

 

(范圍)Ranges

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

作為序列

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

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

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

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

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

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ù)字和字符串型,作為一個(gè)面向?qū)ο蟮恼Z言,ranges也可以用于我們自己創(chuàng)建的對(duì)象,但是這個(gè)對(duì)象必須實(shí)現(xiàn)一個(gè)succ方法,以返回下一個(gè)值,而且這個(gè)對(duì)象也必須支持<=>來對(duì)其進(jìn)行比較。調(diào)用<=>時(shí),它們比較前后兩個(gè)對(duì)象的大小關(guān)系,根據(jù)兩個(gè)對(duì)象是小于,等于,還是大于而返回-1, 0, 或者 +1

下面看一個(gè)簡(jiǎn)單的例子,這個(gè)類表示由若干個(gè)"#"符號(hào)組成的一行,我們可以用它來對(duì)我們點(diǎn)唱機(jī)音量做基于文本的測(cè)試。

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)建一個(gè)VU的range來測(cè)試一下:

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

條件范圍( Ranges as Conditions )

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

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

Ranges表示間隔

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

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

正則表達(dá)式

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

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

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

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

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

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

匹配操作符返回模式匹配成功的字符位置。 它們還有一個(gè)設(shè)置所有Ruby變量的額外作用。$&接受模式匹配成功的那部分字符,$`(鍵盤1左邊那個(gè)鍵)接受模式匹配成功前面那一部分字符,$'接受模式匹配成功后面那部分字符。我們可以用這個(gè)來編寫一個(gè)函數(shù),showRE,它闡明了一個(gè)特殊的模式匹配例子:
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

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

模式(Patterns)

每個(gè)正則表達(dá)式都有一個(gè)模式,用來和字符串做匹配。

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

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

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

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

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

Anchors

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

模式 ^ 和 $ 分別匹配一行字符的開始和結(jié)束。它們經(jīng)常用來指定一個(gè)模式匹配的方向:比如,/^option/和在一行文本開始處出現(xiàn)的字符串'option'匹配。字符序列 \A 和一個(gè)字符串的開始匹配,\z 和 \Z 和一個(gè)字符串的結(jié)束匹配。(事實(shí)上,\Z 和一個(gè)以"\n"結(jié)尾的字符串的結(jié)束匹配,這種情況下,它從'\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)匹配。構(gòu)成單詞的字符有字母,數(shù)字和下劃線。

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

字符類(character class)

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

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

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

如果你想在字符類(方括號(hào))里面包含 ] 和 - 的話,它們必須出現(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]

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

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

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_] 單詞符號(hào)
\W [^A-Za-z0-9_] 非單詞符號(hào)

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

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

重復(fù)(Repetition)

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

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

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

這些模式串被稱為“貪婪地”,因?yàn)樗鼈兡J(rèn)會(huì)匹配盡量多的字符。你可以改變這種行為,讓它們匹配最少的,只要加一個(gè)問號(hào)后綴就可以了。

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)

我們知道‘|’是特殊的,因?yàn)槲覀兊脑谛蟹指裟J街斜仨氂靡粋€(gè)反斜杠使之轉(zhuǎn)義。因?yàn)橐粋€(gè)沒有反斜杠的‘|’匹配正則表達(dá)式中它左右兩邊模式中的一個(gè)。

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

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

成組技術(shù)(Grouping)

你可以在正則式中用圓括號(hào)來成組字符集。在這個(gè)組里面的所有字符會(huì)被認(rèn)為是一個(gè)正則表達(dá)式。

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>>

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

"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)。

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

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

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

基于模式的子串技術(shù)

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

然后,有時(shí)候我們需要改變一個(gè)模式匹配的內(nèi)容。讓我們回到我們的歌曲列表文件。創(chuàng)建文件的人用小寫字母敲進(jìn)了演唱者的名字。當(dāng)我們把名字顯示在點(diǎn)唱機(jī)上時(shí),我們想讓它大小寫混寫。那么我們?cè)趺礃硬拍馨衙總€(gè)單詞的首字母變成大寫呢?

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

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"

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

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

所以,這看起來是我們轉(zhuǎn)變演唱者名字的正確方法。匹配一個(gè)單詞的首字母的模式串是 \b\w---尋找一個(gè)單詞邊界然后跟一個(gè)字母。結(jié)合 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ù)中的轉(zhuǎn)義字符

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

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

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

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

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

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

因?yàn)?\& 會(huì)被匹配的字符串替換,所以你也可以這樣寫:

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

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

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

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

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)生結(jié)果:
1<2?&&?4>3
"A"?=?A?=?A

面向?qū)ο蟮恼齽t表達(dá)式

我們必須承認(rèn)雖然這些關(guān)怪的表達(dá)式很好用,但是它們不是面向?qū)ο蟮?,而且相?dāng)晦澀難懂。你們不是說過Ruby里面任何東西都是對(duì)象嗎?為什么這里是這樣的呢?

這無關(guān)緊要,真的。因?yàn)镸atz在設(shè)計(jì)Ruby的時(shí)候,他構(gòu)建了一個(gè)完全面向?qū)Φ恼齽t表達(dá)式處理系統(tǒng)。但是為了讓Perl程序員感到熟悉一些,他把這些 $* 包裝在這系統(tǒng)之上。這些對(duì)象和類還在那里,在外觀里面,現(xiàn)在讓我們花點(diǎn)時(shí)間把它們挖出來。

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

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

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

re?=?/(\d+):(\d+)/?????#?匹配一個(gè)時(shí)間?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"

因?yàn)槠ヅ湫畔⑹潜4嬖谒约簩?duì)象里的,你可以在同一時(shí)間保存兩個(gè)或者多個(gè)匹配的結(jié)果,這用$-*你可能不能實(shí)現(xiàn)。在下面一個(gè)例子里,我們用同一個(gè) Regexp 對(duì)象去匹配兩個(gè)不同的字符串。每個(gè)匹配都返回一個(gè)唯一的 MatchData 對(duì)象,我們通過它們的兩個(gè)子模式字段來區(qū)別它們。

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

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

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

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


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.
上一篇: 下一篇: