Ruby.new
當我們開始要寫這本書的時候,有一個偉大重要的計劃,因為我們那時候還很年輕。我們決定從最高到低層次來記錄ruby語言,從類和對象開始,然后以詳細的核心語法結束。那時候,這看來是個不錯的選擇,幾乎Ruby中很多東西都是對象,感覺也也應該先說說對象。
我們是這么想的。
但是,那樣來描述一門語言顯得比較困難:如果我們不曾涉及到string,if語句,賦值語句,或其他詳細的東西,很難寫一個類的例子。貫穿于我們從高到低的計劃,我們不得不穿插一些低層次的細節(jié)來使得例子能被讀者看懂。
所以,我們開始了另一個計劃,雖然我們仍然從對象開始,但是在這之前,先用一章來描述ruby比較普通的特點,這都會在將來的例子里出現(xiàn)。就讓我們從一小段指南開始,然后再進入剩余的部分。
Ruby 是面向對象的語言
重申一遍的是,ruby是純的面向對象的語言,你操作的一切都是對象,并且他們產生的結果也是對象。盡管很多其他語言也說自己是面向對象的,但是他們總是用不同的解釋來闡述什么是面向對象,用不同的術語描述他們的使用的觀念。
所以,在進一步深入到詳細內容之前,先來簡要看看我們要用到的術語和符號。
當你編寫面向對象的代碼時,總會將現(xiàn)實世界中的事物模型化到你的代碼之中,在建模的過程中,你會發(fā)現(xiàn)事物能分成不同的對象(類)在代碼中實現(xiàn),比如在自動點唱機(jukebox)中,歌曲就是這樣的例子。在ruby里,我們用class來表示一個對象類。一個類是狀態(tài)(比如歌曲名稱等)和方法(比如播放,停止)等地組合。
當你寫完了類,就可以為每個類建立若干個實例,比如對于jukebox來說,它包含一個類song,我們可以建立諸如``Ruby Tuesday,'' ``Enveloped in Python,'' ``String of Pearls,'' ``Small talk,''等實例。對象這個詞經常和類的實例交換使用,一般的時候我們還是樂意叫它對象。
在 Ruby中,要建立這些對象,可以調用類的構造函數。這個函數是類中的特殊的函數標準名稱是new
.
song1?=?Song.new("Ruby?Tuesday")
song2?=?Song.new("Enveloped?in?Python")
#?and?so?on
|
這兩個實例都來源于一個類,但他們有不同的特征。首先,每個對象都有一個唯一的對象標識符(object identifier)也就是對象id。第二,你可以定義實例變量,每個實例的實例變量都是不同的。實例變量保存著實例的不同狀態(tài)。上面的例子中,兩個實例是不同的因為他們有著不同的歌曲名稱。
在每一個類里面,還可以定義實例方法(instance methods),每個方法都是都是一塊代碼的集合,根據訪問限制的不同,可以在內部或者外部訪問這些方法。這些方法可以訪問對象的實例變量,也就是對象的狀態(tài)。
要想調用一個方法,可以向這個對象發(fā)送一個消息。這個消息包括方法名和一些參數。[
這種用發(fā)消息的方式調用方法來自smalltalk]。當一個對象收到消息,它會檢查自己所屬的類是否有對應的方法,如果有,那么就執(zhí)行這個方法,如果沒有,下面會講到該會如何。
方法和消息的關系聽起來可能有些復雜,但是實際上這樣很自然。我們先來看看一些方法調用(記住}}在這里表示的是對應的表達式返回的結果):
"gin?joint".length |
}} |
9 |
"Rick".index("c") |
}} |
2 |
-1942.abs |
}} |
1942 |
sam.play(aSong) |
}} |
"duh?dum,?da?dum?de?dum?..." |
上面的例子中,點(.)前面的是消息的接收者,點后面的是要調用的方法名。第一個例子中,調用一個字符串的方法,得到它的長度,第二個得到一個字符在字符串中的位置,第三個取得了一個數的絕對值,第四個調用sam對象的play方法。
It's worth noting here a major difference between Ruby and most other languages. 在java里,要想得到一個數的絕對值,要調用額外的方法,并且把那個數字傳過去,比如這樣:
number?=?Math.abs(number)?????//?Java?code
|
在Ruby中, 得到一個數的絕對值的功能在數字對象里是內建的,所有細節(jié)都在內部,我們只需要調用就行了。
同樣,對其他ruby中的對象來說也是這樣的。比如在c語言中要寫
strlen(name)
,在ruby中,只要
name.length
就行了。這也是為什么我們說ruby是一個純的oo語言。
Ruby基礎
一個人在學習一門新的語言的時候,都不大愿意去看長篇大論的繁瑣的語法,所以,這一節(jié)我們打算涉及到一些顯著的特點,如果你打算寫ruby程序的話,這些東西你都應該知道。在第18章,我們將會更深入的解釋類和對象。
讓我們從一個簡單的程序開始。這里有一個方法,它返回一個string類型的值,簡單的把一個字符串加到用戶名后面。然后調用這個函數2次。
def?sayGoodnight(name)
??result?=?"Goodnight,?"?+?name
??return?result
end
#?Time?for?bed...
puts?sayGoodnight("John-Boy")
puts?sayGoodnight("Mary-Ellen")
|
首先,從大體上我們看到,ruby的語法還是比較簡單的,首先,你不必每行都寫個分號,Ruby注釋以#開頭,直到行尾。 Code layout is pretty much up to you; indentation is not significant.
Methods定義以關鍵字def開始,接著是方法名(這里是sayGoodnight)和用兩個小括號括起來的方法參數,ruby不需要用{ }來界定程序主體,只需要關鍵字end就行了。這個程序也相當簡單,第一行把``Goodnight, ''加上參數name,并把它賦給了局部變量result,第二行把結果返回給調用者。注意我們不需要定義變量result,當給它賦值的時候,系統(tǒng)會自動創(chuàng)建的。
最后我們調用了2次這個方法,并把結果傳給puts函數,這個函數簡單的再新行上打印傳給它的參數而已,最后結果像這樣:
Goodnight,?John-Boy
Goodnight,?Mary-Ellen
|
其實 ``puts sayGoodnight("John-Boy")'' 包括了2個函數調用,一個調用
sayGoodnight
,另一個調用
puts
,但是為什么puts調用沒有用括號呢?實際上,下面的調用都是等價的:
puts?sayGoodnight?"John-Boy"
puts?sayGoodnight("John-Boy")
puts(sayGoodnight?"John-Boy")
puts(sayGoodnight("John-Boy"))
|
但是事實上,這樣很難看清楚哪個參數是給哪個方法的,所以還是建議在方法后面加上括號,除非是非常簡單的場合。
這個方法還展示了string對象,有很多種辦法可以創(chuàng)建string對象,但最普通的要算用string literals了:單引號或雙引號包起來的一組字符。它們的區(qū)別是ruby構建這兩種字符串時要做的操作。對單引號引起來的字符串來說,ruby做的工作會很少,單引號引起來得部分就是它的值。如果是雙引號引起來得,則要做多一些工作了。首先,它檢查是否包含反斜線,也就是轉義符,然后用適當的二進制值代替,最常見的就是"\n"了,它將會被換行替換。
比如:
puts?"And?Goodnight,\nGrandma"
|
?
產生結果如下:
?
處理雙引號括起來的字符串的時候如果做的第二件事情就是表達式處理(expression interpolation)。#{ expression }將被expression的值代替,例如,下面的方法和剛才的例子是一樣的結果:
def?sayGoodnight(name)
??result?=?"Goodnight,?#{name}"
??return?result
end
|
當ruby構造這個字符串時,先檢查name的值,然后用這個值替換到字符串里面去。表達式要放到
#{...}
結構里面去。As a shortcut, you don't need to supply the braces when the expression is simply a global, instance, or class variable. For more information on strings, as well as on the other Ruby standard types, see Chapter 5, which begins on page 47.
最后,我們還可以把剛才的函數簡化成這樣的。一個ruby函數返回的結果就是最后一行的值,所以這個函數也可以寫成如下:
def?sayGoodnight(name)
?? "Goodnight,?#{name}"
end
|
We promised that this section would be brief. We've got just one more topic to cover: Ruby names. For brevity, we'll be using some terms (such as class variable) that we aren't going to define here. However, by talking about the rules now, you'll be ahead of the game when we actually come to discuss instance variables and the like later.
Ruby uses a convention to help it distinguish the usage of a name: the first characters of a name indicate how the name is used. Local variables, method parameters, and method names should all start with a lowercase letter or with an underscore. Global variables are prefixed with a dollar sign ($), while instance variables begin with an ``at'' sign (@). Class variables start with two ``at'' signs (@@). Finally, class names, module names, and constants should start with an uppercase letter. Samples of different names are given in Table 2.1 on page 10.
Following this initial character, a name can be any combination of letters, digits, and underscores (with the proviso that the character following an @ sign may not be a digit).
Example variable and class names
Variables |
Constants and |
Local |
Global |
Instance |
Class |
Class Names |
name |
$debug |
@name |
@@total |
PI |
fishAndChips |
$CUSTOMER |
@point_1 |
@@symtab |
FeetPerMile |
x_axis |
$_ |
@X |
@@N |
String |
thx1138 |
$plan9 |
@_ |
@@x_pos |
MyClass |
_26 |
$Global |
@plan9 |
@@SINGLE |
Jazz_Song |
 |
|
數組和哈希表
ruby的數組和哈希是有索引的集合,它們存放著各種對象,并且用一個key來訪問。對數組來說這個key是一個整數,而哈希則支持任何對象作為key。兩者都可以動態(tài)增長,如果他們需要的話。訪問數組是一件很方便的事情,但是訪問哈希就要復雜一些了。
最簡單的創(chuàng)建和初始化一個數組的方法是用一對中括號包含一組值,這組值用逗號分割。然后用一個索引可以訪問單獨的任何一個元素。如下:
a?=?[?1,?'cat',?3.14?]???#?array?with?three?elements |
#?access?the?first?element |
a[0] |
}} |
1 |
#?set?the?third?element |
a[2]?=?nil |
#?dump?out?the?array |
a |
}} |
[1,?"cat",?nil] |
也可以這樣創(chuàng)建新的空的數組:用空元素構造或者調用Array的構造函數
Array.new
.
empty1?=?[]
empty2?=?Array.new
|
當然,像第一種方法那樣寫很多引號和逗號可能很麻煩,但是ruby提供了一個好東西,如下面那樣%w可真管用:
a?=?%w{?ant?bee?cat?dog?elk?} |
a[0] |
}} |
"ant" |
a[3] |
}}
|
|
"dog" |
哈希和數組類似,不過它不用[]來包括,而是用{},哈希里的每條記錄要兩個要素來表示,一個是關鍵字,另一個是對應這個key的值。如:
instSection?=?{
??'cello'?????=>?'string',
??'clarinet'??=>?'woodwind',
??'drum'??????=>?'percussion',
??'oboe'??????=>?'woodwind',
??'trumpet'???=>?'brass',
??'violin'????=>?'string'
}
|
哈希的引用也是用[],像數組一樣,但是它的[]里面是key,可以是字符串不像數組,只能是整數比如:
instSection['oboe'] |
}} |
"woodwind" |
instSection['cello'] |
}} |
"string" |
instSection['bassoon'] |
}} |
nil |
從上面最后一個例子看出,如果哈希里不包含一個key值,則默認會返回nil,這也是一個關鍵字(保留字),在條件運算中,nil跟false是一樣的。如果你想改變這個默認值,則可以在創(chuàng)建一個哈希的時候提供這個默認值就行了,如下面的
?Hash.new(0)
中,0就是默認值:
histogram?=?Hash.new(0) |
histogram['key1'] |
}} |
0 |
histogram['key1']?=?histogram['key1']?+?1 |
histogram['key1'] |
}} |
1 |
數組和哈希又很多其他有用的用法,可以參看書末附錄中的參考手冊。
條件控制
Ruby包括通常的一些條件控制語句,比如if,then,while等。 Java, C, 和 Perl 程序員可能會被打括號{}弄得很煩亂,與此不同Ruby用關鍵字end來結束一塊代碼 ,比如:
if?count?>?10
??puts?"Try?again"
elsif?tries?==?3
??puts?"You?lose"
else
??puts?"Enter?a?number"
end
|
同樣,while語句看起來都是這樣的。
while?weight?<?100?and?numPallets?<=?30
??pallet?=?nextPallet()
??weight?+=?pallet.weight
??numPallets?+=?1
end
|
statement modifiers 是ruby語法中的一點特別之處,如果你的if或while條件下的語句特別簡單,可以把他們寫在一行上:先寫表達式,然后跟著寫if或者while語句,比如下面的例子:
if?radiation?>?3000
??puts?"Danger,?Will?Robinson"
end
|
可以改寫成如下:
puts?"Danger,?Will?Robinson"?if?radiation?>?3000
|
同樣,
while循環(huán)如下:
while?square?<?1000
???square?=?square*square
end
|
更簡潔的形式:
square?=?square*square??while?square?<?1000
|
statement modifiers對perl程序員來說應該比較熟悉。
正則表達式Regular Expressions
大多數語言都有字符串,整型,浮點型,數組等數據類型,所以ruby中的大多數內建類型對大家來說一定不算陌生。所謂的腳本語言比如perl,python,awk中都支持正則表達式,
However, until Ruby came along, regular expression support was generally built into only the so-called scripting languages, such as Perl, Python, and awk. This is a shame: regular expressions, although cryptic, are a powerful tool for working with text.
本書整個都包括正則表達式的內容,所以這里沒必要用一節(jié)涵蓋所有內容。所以,我們只會涉及到幾個正則表達式的例子。
正則表達式就是在一個字符串中查找一個匹配的模式,在Ruby中,要建立一個正在表達式,只要把要匹配的模式放到兩個斜線中就行了(/
pattern/),而且,在Ruby中,正則表達式也是對象,可以像對象一樣被操作。
例如,你寫了一個模式,匹配一個字符串中的Perl或者Python,模式如下:
The forward slashes delimit the pattern, which consists of the two things we're matching, separated by a pipe character (``
|
''). You can use parentheses within patterns, just as you can in arithmetic expressions, so you could also have written this pattern as
在模式里,還可以指定重復的字符,比如
/ab+c/
匹配一個字符串包括一個a,一個或多個b,然后跟一個c。把加號改成星號,即
/ab*c/
將匹配一個a,0個或多個b,然后是一個c。
在一個模式中,也可以匹配一類字符,比如"\s"匹配一個空格,"\d"匹配數字,"\w"匹配在典型單詞出現(xiàn)的字符,"."匹配任何字符。
我們可以把這些規(guī)則寫道一塊,生成一些比較有用的表達式。
/\d\d:\d\d:\d\d/?????#?a?time?such?as?12:34:56
/Perl.*Python/???????#?Perl,?zero?or?more?other?chars,?then?Python
/Perl\s+Python/??????#?Perl,?one?or?more?spaces,?then?Python
/Ruby?(Perl|Python)/?#?Ruby,?a?space,?and?either?Perl?or?Python
|
創(chuàng)建了一個模式,當然要使用它。匹配操作符"=~
"可以用來在一個字符串中匹配一個模式,如果在字符串中找到匹配的模式,=~
返回它的開始位置,否則返回nil。所以,你也可以將正則表達式放到if或者while語句中作為判斷條件。 比如,下面代碼判斷到如果一個字符串包括指定的模式(包含Perl或者Python),則輸出一條消息。
if?line?=~?/Perl|Python/
??puts?"Scripting?language?mentioned:?#{line}"
end
|
一個字符串中被匹配到的部分也可以用ruby自帶的替換函數替換成不同的值。
line.sub(/Perl/,?'Ruby')????#?replace?first?'Perl'?with?'Ruby'
line.gsub(/Python/,?'Ruby')?#?replace?every?'Python'?with?'Ruby'
|
在本書以后 章節(jié)中,還有很多關于正則表達式的東西要講述。
塊和迭代
這節(jié)簡要介紹ruby特別的一點長處。 一大塊代碼,可以像方法一樣被調用,好像是方法的參數一樣。你可以用塊來實現(xiàn)方法回調(比java的內類要簡單多了),給一段代碼傳遞另一段代碼(比c的方法指針要靈活),并且可以實現(xiàn)迭代。塊用{}或者do…end包含起來:
{?puts?"Hello"?}???????#?this?is?a?block
do?????????????????????#
??club.enroll(person)??#?and?so?is?this
??person.socialize?????#
end????????????????????#
|
當你完成了一個塊,下一步可以用一個方法來調用這個塊,這個方法可以用yield關鍵字來調用一次或者多次塊。如下面這個例子,我們定義了一個方法,調用了兩次yield,然后調用這個方法,并且在同一行放了一個塊:
def?callBlock
??yield
??yield
end
callBlock?{?puts?"In?the?block"?}
|
結果如下:
In?the?block
In?the?block
|
可以看到代買(puts "In the block"
)執(zhí)行了兩次,每次yield調用都會執(zhí)行一次。
而且,我們還可以給yield傳參數,這個參數也會被傳到塊中。在塊中,列出參數的名字,用豎線 "|" 格開
def callBlock
yield ,
end
callBlock { |, | ... }
|
塊代碼貫穿于Ruby庫中迭代的實現(xiàn)之中:某種集合返回下一個值得方法,比如數組
a?=?%w(?ant?bee?cat?dog?elk?)????#?create?an?array
a.each?{?|animal|?puts?animal?}??#?iterate?over?the?contents
|
產生:
讓我們看看上面的例子是如何來實現(xiàn)數組的
each
迭代的,each迭代循環(huán)整個數組,從第一個到最后一個元素,對每一個元素,調用yield。偽代碼表示如下:
#?within?class?Array...
def?each
??for?each?element
????yield(element)
??end
end
|
你可以循環(huán)迭代數組中的每個元素,并且提供一個塊來處理。
[?'cat',?'dog',?'horse'?].each?do?|animal|
??print?animal,?"?--?"
end
|
結果:
ruby也提供了類似c和java的其他一些循環(huán)結構,而且非常容易使用,每個方法可以運行0次和多次提供的塊代碼。
5.times?{??print?"*"?}
3.upto(6)?{|i|??print?i?}
('a'..'e').each?{|char|?print?char?}
|
產生:
這里,我們讓數字5調用塊5次,然后從3到6調用另一個塊,每次給塊傳遞下一個循環(huán)值,最后,在從a到e的范圍里用each方法循環(huán)。
讀寫操作
Ruby自帶了豐富的I/O操作庫,在本書的例子中,我們只會用到很少的一些方法。目前我們已經接觸到了2個輸出方法,puts和print。puts依次輸出所有的參數,每個參數之后都會加一個換行;print也類似, 不過不會在每個參數后面加入新行,比如(譯者測試的例子,原書無):
irb(main):011:0> puts "puts 1","puts 2" puts 1 puts 2 => nil irb(main):012:0> printf " puts %s puts %s",1,2 puts 1 puts 2=> nil #nil出現(xiàn)在此處 irb(main):013:0> |
這兩個方法都可以輸出到I/O對象,默認都輸出到控制臺。另一個經常用到的是printf,類似c里的,是格式化輸出語句:
printf?"Number:?%5.2f,?String:?%s",?1.23,?"hello"
|
produces:
Number:??1.23,?String:?hello
|
printf的格式如下:pringf(格式控制,輸出列表)這個例子里,格式化字符串"Number: %5.2f, String: %s"
中有兩個參數,第一個是一個浮點型(%5.2f),5表示一共有5個數字,2表示小數點后邊有兩位;第二個表示是一個字符串。
讀入輸入字符有幾種方法,按傳統(tǒng)慣例來說最簡單的是用gets。它返回你的程序標準輸入的下一行。(下例為譯者修改過的)
irb(main):016:0> line=gets ruby => "ruby\n" irb(main):017:0> puts line ruby => nil irb(main):018:0> |
不過gets也有一個副作用
:用它得到一行的時候,它還會將這個值存到一個全局變量$_里,這個變量很特殊,在很多場合,他都會作為一些方法的默認值。比如你光運行print而不帶任何參數,就會輸出$_的值,如果你的if或者while沒有條件語句,而只有表達式,它們也會判斷當前$_值的真假來做判斷。這些縮寫可能會使你能夠寫出很簡練的程序。比如下面的程序從輸入流接收數據,打印出所有包含"Ruby"的行。
while?gets???????????#?assigns?line?to?$_
??if?/Ruby/??????????#?matches?against?$_
????print????????????#?prints?$_
??end
end
#以下代碼為譯者添加
irb(main):004:0> line=gets hehe # 此時$_=line="hehe" => "hehe\n" irb(main):006:0> print # print $_ hehe => nil irb(main):007:0> puts # 無效
=> nil irb(main):008:0> gets # 存到$_ xixi => "xixi\n" irb(main):009:0> print # print $_ xixi => nil irb(main):010:0>
|
Ruby特色的寫法應該用一個迭代如下:
ARGF.each?{?|line|??print?line??if?line?=~?/Ruby/?}
|
這里用到了一個預先定義好的對象ARGF,它代表了一個可以被程序使用的輸入流。
Onward and Upward
到這里,我們快速的瀏覽了ruby的一些基本特點,我們知道了對象,方法字符串,容器,正則表達式,一些簡單的條件控制,迭代等。希望這一章能幫助你很好的掌握本書的其他章節(jié)。
Time to move on, and up---up to a higher level. Next, we'll be looking at classes and objects, things that are at the same time both the highest-level constructs in Ruby and the essential underpinnings of the entire language.
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.