##第一章 最低限度的ruby知識
為了方便第一部分的解說,在這里簡單介紹一下ruby的基本知識。這里不會系統介紹編程的技巧方面的東西,讀完這章節也不會讓你掌握ruby的編程方法。如果讀者已經有ruby的經驗,那就可以直接跳過本章節。
另外我們會在第二部分不厭其煩地講解語法,于是在這一章節我們盡量不涉及語法相關內容。關于hash,literal之類的表示方法我們會采用經常使用的方式。可以省略的東西原則上我們不會省略。這樣才會使語法看起來簡單,但是我們不會重復提醒"這里可以省略"。
###對象
####字符串
ruby程序造作的所有東西都是對象。在ruby里面沒有如java里面的 int 或者 long一樣的基本數據類型。比如,如下面的例子一樣書寫的話,就會生成一個內容是"content"的字符串(String)對象。
```
"content"
```
剛才說這個只是個字符串的對象,其實正確來講這個是生成字符串對象的表達式。所以每寫一次,都會有新的字符串對象生成。
```
"content"
"content"
"content"
```
這里就生成了三個內容是"content"的對象。
但是單純有對象程序是看不到的。下面教你如何在終端顯示對象。
```
p("content") #顯示"content"
```
"#"之后的東西是注釋。接下這個都表示注釋。
"p(……)"是調用了函數p。它能夠較逼真的表示對象的狀態,基本上是一個debug用的函數。
雖然嚴格意義上講,ruby里面并不存在函數這個概念。但是現在請先把它當作函數來理解。函數無論在哪里都可以使用。
####各種各樣的序列
接下來我們來說明一下直接生成對象的序列(literal)。先從一般的整數和小數說起。
```
# 整數
1
2
100
9999999999999999999999999 # 無論多大的數都可以使用
# 小數
1.0
99.999
1.3e4 # 1.3×10^4
```
請記住,這些也全部是生成對象的表達式。在這里我們不厭其煩得重復強調,ruby里面是沒有基本類型的。
下面是生成數組對象的表達式。
```
[1,2,3]
```
這個程序會按順序生成包含1 2 3 三個元素的數組。數組的元素可以是任何對象。于是也可以有這種表達式。
```
[1, "string", 2, ["nested", "array"]]
```
甚至,下面的用法可以來生成哈希表。
```
{"key"=>"value", "key2"=>"value2", "key3"=>"value3"}
```
所謂哈希表,是指任何的所有對象之間的一對一的數據結構。按照上述寫法會構造出包含下面所示關系的一張表。
```
"key" → "value"
"key2" → "value2"
"key3" → "value3"
```
這樣生成了哈希表之后,當我們向此對象詢問"key對應的值是什么"的時候,它就會告訴我們結果是"value"。那該怎么詢問呢?那就要用到方法了。
####方法的調用
對象可以調用方法。用c++的話說就是調用成員函數。至于什么是方法,我覺得沒有必要說明,還是看一下下面這個簡單的例子吧。
```
"content".upcase()
```
這里調用了字符串(字符串的內容為"content")的upcase方法。upcase是返回一個將所有小寫字母全部變成大寫的字符串。于是會有下面的效果。
```
p("content".upcase()) # 輸出"CONTENT"
```
方法可以連續調用
```
"content".upcase().downcase()
```
這個時候調用的是"content".upcase()的返回值對象的downcase方法。
另外,ruby里面沒有像Java和C++一樣的全局概念。于是所有對象的接口都是通過方法來實現。
###程序
####頂層
在ruby里面寫上一個式子就已經算是一個程序了。沒有像C++或者Java一樣需要定義main()。
```
p("content")
```
僅僅就這樣已經算是一個完整的ruby程序。把這個東西復制到first.rb這個文件中然后在命令行執行
```
% ruby first.rb
"content"
```
使用選項-e可以不需要創建文件就可以執行代碼。
```
% ruby -e 'p("content")'
"content"
```
然而要注意到的是,上面p的位置是程序中最外層的東西,也就是說在程序上屬于最上層,于是被稱作頂層。有層頂是ruby腳本語言的最大特征。
ruby基本上一行就是一句。不需要在句尾加上分號。所以下面的內容實際上是三個語句。
```
p("content")
p("content".upcase())
p("CONTENT".downcase())
```
執行結果如下
```
% ruby second.rb
"content"
"CONTENT"
"content"
```
####局部變量
ruby中所有的變量和常量都僅僅是對對象的引用(reference)。所以僅僅是帶入其他變量的話并不會發生復制之類的行為。
這個可以聯想到Java中的對象型變量,以及C++中的指向對象的指針。但是指針的值無法改變。
ruby僅看變量的首字母就可以分別出變量的種類。小寫阿拉伯字母或者下劃線開始的變量屬于局部變量。使用等于號=代入賦值。
```
str = "content"
arr = [1,2,3]
```
一開始代入的時候不需要聲明變量,另外無論變量是什么類型,代入方法都沒有區別。下面的寫法都是合法的。
```
lvar = "content"
lvar = [1,2,3]
lvar = 1
```
當然,雖然可以這么寫但是完全沒有必要故意寫成這樣。把種類各樣的對象放在一個變量中會使得代碼變得晦澀難懂。現實中很少有如此的寫法,在這里我們僅僅是舉個例子。
變量內容查詢也是常用的。
```
str = "content"
p(str) # 結果顯示"content"
```
下面我們從變量持有對象的引用的觀點來看下面的例子。
```
a = "content"
b = a
c = b
```
執行這個程序之后,變量a,b,c三個局部變量指向的都是同一個對象,也就是第一行生成的"content"這個字符串對象。
[](img/ch_minimum_reference.jpg)
[](img/ch_minimum_reference.jpg)
圖1: Ruby的變量擁有對對象的引用
這里我們注意到,一直在說的局部變量,那么這個局部必然是針對某個范圍的局部。但是請稍等片刻我們再做解釋。總之我們先可以說頂層也是一個局部的作用域。
####常量
名稱首字母是大寫的稱作常量。所謂常量就是只能代入賦值一次。
```
Const = "content"
PI = 3.1415926535
p(Const) # 輸出"content"
```
帶入兩次的話會發生錯誤。雖然按道理是這樣,但是實際運行的時候卻不會有錯誤例外發生。這個是為了保證執行ruby程序的應用程序,比如說在同一個開發環境下,讀入兩個同樣的文件的時候不至于產生錯誤。也就是說是為了實用性而不得不做出的犧牲,萬不得已才取消了錯誤提示。實際上在Ruby1.1之前是會報錯的。
```
C = 1
C = 2 # 實際上只會給出警告,最理想的還是要做成會顯示錯誤
```
接下來,被常量這個稱呼欺騙的人肯定有很多。常量指的是"一旦保存了所要指向的對象就不再會改變"這個意思。而常量指向的對象并不是不會發生變化。如果用英語來說,比起constant這個意思,還是read only來的比較貼切。(圖2)。順帶一提,如果要使對象自身不發生變化可以用freeze這個方法來實現。
[](img/ch_minimum_const.jpg)
[](img/ch_minimum_const.jpg)
圖2: 常量是read only的意思
另外這里還沒有提到常量的作用域。我們會在下一節的類的話題中來說明。
####流程控制
Ruby的流程控制結構很多要列舉的話舉不勝舉。總之就介紹一下基本的if和while。
```
if i < 10 then
# 內容
end
while i < 10 do
# 內容
end
```
在條件語句中只有false和nil兩個對象是假,其他所有對象的都屬于真。當然0和空字符串也是真。
順帶一提,只有false的話感覺不怎么美觀,于是當然也有true,當然true是屬于真。
最純粹的面向對象的系統中,方法都是對象的附屬物。但是那畢竟只是一個理想。在普通的程序中會有大量的擁有相同方法集合的對象。如果還傻傻的以對象為單位來調用方法那就會造成內存的大大的浪費。于是一般的方法是使用類或者multi-method來避免重復定義。
ruby采用了傳統上來連接方法和對象的結構,類。也就是所有的對象都必須屬于唯一的一個類,這個對象能夠調用的方法也友這個類來決定。這個時候對象通常被叫做“某某類的實例”。
例如"str"就是類String的實例。另外,String這個類還定義了upcase,downcase,strip等一系列其他方法,仿佛就是在說所有的字符串對象都可以利用這些方法一樣。
```
# 大家都屬于同一個String類于是擁有相同的方法。
"content".upcase()
"This is a pen.".upcase()
"chapter II".upcase()
"content".length()
"This is a pen.".length()
"chapter II".length()
```
那么,如果調用的方法沒有事前定義會發生什么呢?在靜態的語言中,編譯器會報錯,而在ruby中,執行的時候會拋出例外。我們來實際嘗試一下。這點長度的話直接在命令行用-e就好了。
```
% ruby -e '"str".bad_method()'
-e:1: undefined method `bad_method' for "str":String (NoMethodError)
```
找不到方法的時候會發生NomethodError這個錯誤。
另外最后,每次都說一遍類似“String的upcase方法”太煩了,于是我們下面就用“String#upcase”來表示“在String的類中定義好的upcase方法”。
順帶一提,如果寫成“String.upcase”在ruby中就又是另一個意思了。
#### 類的定義
到目前為止都是針對已經定義好的類。當然我們也可以自己來定義類。定義類的時候使用class關鍵字。
```
class C
end
```
這樣就定義了C這個類。定義好之后可以有以下的使用。
```
class C
end
c = C.new() # 生成類C的實例帶入c中。
```
注意生成實例的寫法不是`new C`。恩,好像`C.new`這個寫法在調用方法一樣呢。如果你這樣想,那說明你很聰明。Ruby生成對象的時候僅僅是調用了方法而已。
首先在Ruby里面類名和常量名是一個概念。那么類名和同名的常量的內容到底是什么呢?實際上里面是類。在Ruby中一切都是對象,那么當然類也是對象了。我們姑且將其稱作類對象。所有的類對象都是Class這個類的實例。
也就是說class這個寫法,完成的是生成新的類對象的實例,并且將類名帶入和它同名的常量中這一系列操作。同時,生成實例,實際上是參照常量名,對該類對象調用方法(通常是new方法)的操作。看完下面的例子你就應該會明白生成實例和普通的調用方法根本就是一回事。
```
S = "content"
class C
end
S.upcase() # 獲取常量S所指的對象并調用upcase方法。
C.new() # 獲取常量C所指的對象并調用new方法。
```
因此,在Ruby里面new并不是保留詞。
另外我們也可以用p來顯示剛剛生成的類的實例。
```
class C
end
c = C.new()
p(c) # #<C:0x2acbd7e4>
```
當然不可能像字符串和整數那樣漂亮地顯示出來,顯示出來的是類內部的ID。順帶一提這個ID其實是指向對象的指針的值。
對了。差點忘了說明一下方法名的寫法。`Object.new`是類對象Object調用自己的方法new的意思。`Object#new`和`Object.new`完全是兩碼事情,必須嚴格區分。
```
obj = Object.new() # Object.new
obj.new() # Object#new
```
在剛剛的例子中因為還沒定義`Object#new`方法所以第二行會報錯。我們就當是一個寫法的例子來理解就好了。
###方法定義
就算定義了類如果不定義方法的話一般沒有什么太大意義。我們繼續定義類C的方法。
```
class C
def myupcase( str )
return str.upcase()
end
end
```
定義類用的是def。這個例子中定義了myupcase這個方法。這個方法帶一個參數str。和變量同樣,沒有必要寫明返回值的類型和參數的類型。另外參數的個數是沒有限制的。
我們來使用一下定義好的方法。方法默認可以從外部調用。
```
c = C.new()
result = c.myupcase("content")
p(result) # 輸出"CONTENT"
```
當然習慣了之后也沒必要每次都帶入。下面的寫法也是同樣的效果。
```
p(C.new().myupcase("content")) # 同樣輸出"CONTENT"
```
####self
在執行方法的過程中經常要保存自己是誰(調用方法的實例)這個信息。self可以取得這個信息。在C++或者Java中就是this。我們來確認一下。
```
class C
def get_self()
return self
end
end
c = C.new()
p(c) # #<C:0x40274e44>
p(c.get_self()) # #<C:0x40274e44>
```
如上所示,兩個語句返回的是用一個對象。也就是說對實例c調用方法的時候slef就是c自身。
那么要如何才能對自身的方法進行調用呢?首先可以考慮通過self。
```
class C
def my_p( obj )
self.real_my_p(obj) # 調用自身的方法。
end
def real_my_p( obj )
p(obj)
end
end
C.new().my_p(1) # 輸出1
```
但是調用自身的方法每次都要這么表示一下實在是太麻煩。于是可以省略掉self,調用自身的方法的時候直接就可以省略掉調用方法的對象(receiver)。
```
class C
def my_p( obj )
real_my_p(obj) # 無需指定receiver
end
def real_my_p( obj )
p(obj)
end
end
C.new().my_p(1) # 輸出1
```
####實例變量
對象的實質就是數據+代碼。這個說法表明,僅僅定義了方法還是不夠的。我們需要以對象為單位來保存數據。也就是說我們需要實例變量。在C++里面就是成員變量。
Ruby的變量命名規則很簡單,是由第一個字符決定的。實例變量以@開頭。
```
class C
def set_i(value)
@i = value
end
def get_i()
return @i
end
end
c = C.new()
c.set_i("ok")
p(c.get_i()) # 顯示"ok"
```
實例變量和之前的所介紹的變量稍微有點區別,不用代入(也無需定義)也可以可以引用。這個時候會是什么情形呢……我們在之前代碼的基礎上來試試看。
```
c = C.new()
p(c.get_i()) # 顯示nil
```
在沒有set的情況下調用get,會顯示nil。nil是表示什么都沒有的對象。明明自身是個對象卻表示什么都沒有這個的確有點奇怪,但是它就是這么一個玩意兒。
nil也可以用序列來表示。
```
p(nil) # 顯示nil
```
#### 初始化
到目前為止,正如所見,就算是剛定義好的類只要調用new方法就可以生成實例。的確是這樣,但是有時候類也需要特殊的初始化吧。這個時候我們就不是去改變new,而是應該定義initialize這個方法。這樣的話,在new中就會調用這個方法。
```
class C
def initialize()
@i = "ok"
end
def get_i()
return @i
end
end
c = C.new()
p(c.get_i()) # 顯示"ok"
```
嚴格意義上來說這個初始化只是new這個方法的特殊設計而不是語言層次上的特殊設計。
繼承
類可以繼承其他類。比如說String類就是繼承的Object這個類。在本書中將用下圖的箭頭來表示。
[](img/ch_minimum_supersub.jpg)
[](img/ch_minimum_supersub.jpg)
上圖的情況下,被繼承的類(Object)叫做父類或者上層類,繼承的類(String)叫做下層類或者子類。注意這個叫法和C++有所區別。但和Java是一樣的喊法。
總之我們來嘗試一下,我們來定義一個繼承別的類的類。要定義一個繼承其他類的類的時候有以下寫法。
```
class C < SuperClassName
end
```
到目前為止,我們凡是沒有標明父類的類的定義,其實繼承的都是Object這個父類。
接下來我們考慮一下為什么要繼承,當然繼承是為了繼承方法了。所謂繼承,仿佛就是再次重復了一下在父類中第一的方法。
```
class C
def hello()
return "hello"
end
end
class Sub < C
end
sub = Sub.new()
p(sub.hello()) # 輸出"hello"
```
雖然hello方法是在類C中定義的,但是Sub這個類的實例可以調用這個方法。當然這次不需要帶入變量。下面的寫法也是同樣的效果。
```
p(Sub.new().hello())
```
只要在子類中定義相同名字的方法就可以重載。在C++,Object Pascal(Delphi)使用virtual之類的保留字明示除了指定的方法之外無法重載,而在Ruby中所有的方法都可以無條件地重載。
```
class C
def hello()
return "Hello"
end
end
class Sub < C
def hello()
return "Hello from Sub"
end
end
p(Sub.new().hello()) # 顯示"Hello from Sub"
p(C.new().hello()) # 顯示"Hello"
```
另外類可以多層次繼承。如圖4所示。這個時候Fixnum繼承了Object和Numeric以及Integer的所有方法。如果有同名的方法則以最近的類的方法為優先。不存在因類型不同而發生的重載所以使用條件非常簡單。
[](img/ch_minimum_multiinherit.jpg)
[](img/ch_minimum_multiinherit.jpg)
另外C++中可以定義沒有繼承任何類的類,但是Ruby的類必然是直接或者間接繼承Object類的。也就是說繼承關系是以Object在最頂端的一棵樹。比如說,基本庫中的重要的類的繼承關系樹如圖5所示。
[](img/ch_minimum_classtree.jpg)
[](img/ch_minimum_classtree.jpg)
父類被定義之后絕對不會被改變,也就是說在類樹中添加新的類的時候,類的位置不會發生變化也不會被刪除。
####變量的繼承……?
Ruby中不繼承變量(實例變量)。因為就算想繼承,也無法獲取類中使用的變量的情報。
但是只要是繼承了方法那么調用繼承的方法的時候(以子類的實例)會發生實例變量的帶入。也就是說會被定義。這樣的話實例變來那個的命名空間在各個實例中是完全平坦的,無論是哪個類的方法都可以獲取。
```
class A
def initialize() # 在new的時候被調用
@i = "ok"
end
end
class B < A
def print_i()
p(@i)
end
end
B.new().print_i() # 顯示"ok"
```
如果無法理解這個行為那么干脆別去考慮類和繼承。就想象一下如果類C存在實例obj,那么C的父類的所有方法都在C中有定義。當然也要考慮到重載的罪責。然后現在把C的方法和obj銜接在一起。(圖6)
這種強烈的真實感正是Ruby的面向對象的特征。
[](img/ch_minimum_objimage.jpg)
[](img/ch_minimum_objimage.jpg)
####模塊
父類只能指定一個,也就是說Ruby表面上是單一繼承的。實際上因為存在模塊所以Ruby擁有多重繼承的能力。下面我們就來介紹一下模塊。
```
module M
end
```
這樣就定義了模塊。在模塊里定義方法和類中定義完全一樣。
```
module M
def myupcase( str )
return str.upcase()
end
end
```
但是因為模塊無法生成對象所以是無法直接調用模塊里面定義的方法的。那么該怎么辦?恩將模塊包含進其他的類里面就是了。這樣的話模塊就仿佛繼承了類一樣可以被操作了。
```
module M
def myupcase( str )
return str.upcase()
end
end
class C
include M
end
p(C.new().myupcase("content")) # 顯示"CONTENT"
```
雖然類C根本沒有定義任何方法但是可以調用myupcase這個方法。也就是說它繼承了模塊M的方法。包含的機能和繼承完全是一樣的,無論是定義方法還是獲取實例變量都不受限制。
模塊無法指定父類。但是卻可以包含其它模塊。
```
module M
end
module M2
include M
end
```
也就是說這個機能去其實和指定父類是一樣的。但是模塊上邊是不會有類的,模塊可以包含的只能是模塊。
下面的例子包含了方法繼承。
```
module OneMore
def method_OneMore()
p("OneMore")
end
end
module M
include OneMore
def method_M()
p("M")
end
end
class C
include M
end
C.new().method_M() # 輸出"M"
C.new().method_OneMore() # 輸出"OneMore"
```
Module也可以和類一樣的繼承圖。
[](img/ch_minimum_modinherit.jpg)
[](img/ch_minimum_modinherit.jpg)
話說類C如果本身已經存在父類,那么這下子和模塊的關系會變成什么樣呢?請試著考慮下面的例子。
```
# modcls.rb
class Cls
def test()
return "class"
end
end
module Mod
def test()
return "module"
end
end
class C < Cls
include Mod
end
p(B.new().test()) # "class"? "module"?
```
類C繼承了類Cls,并且引入了Mod模塊。這個時候應該表示"class"呢還是表示"module"呢?換句話說,模塊和類哪一個距離更近呢?Ruby的一切都可以向Ruby詢問。于是我們來執行看一下結果。
```
% ruby modcls.rb
"module"
```
比起父類模塊的優先度更高呢!
一般來說,在Ruby中引入模塊的話,會在類和父類之間夾帶產生一個繼承關系,如下圖所示。
[](img/ch_minimum_modclass.jpg)
[](img/ch_minimum_modclass.jpg)
在模塊中又引入模塊的話會有以下的關系。
[](img/ch_minimum_modclass2.jpg)
[](img/ch_minimum_modclass2.jpg)
###程序(2)
注意,在本節中出現的內容非常重要,并且主要講的是一些習慣靜態語言思維的人很難習慣的部分。其他的內容可以一眼掃過,而這里請格外注意。我會比較詳細地說明。
####常量的嵌套
首先復習一下常量。以大寫字母開頭的是常量。可以以如下方法定義。
```
Const = 3
```
要調用這個參數的時候可以如下。
```
p(Const) # 輸出3
```
其實寫成這樣也可以。
```
p(::Const) # 同樣輸出3
```
在開頭添加`::`表示是在頂層定義的常量。你可以類比一下文件系統的路徑的表示方法。在root目錄下有vmunix這個文件。如果是在"/"目錄下的話直接輸入vmunix就可以獲取文件。當然也可以用完整的路徑"/vmunix"來表示。`Const`和`::Const`也是同樣道理。如果是在頂層的話直接用`Const`就ok,當然使用完整路徑版本的`::Const`也是可以的。
那么類比文件系統目錄的東西,在Ruby中是什么呢?那就是類的定義和模塊的定義了。因為每次都這么說一遍的話太煩了于是下面統一就用類的定義語句來代替。在類的定義語句中常量的等級會隨之上升。(類比進入文件系統的目錄)
```
class SomeClass
Const = 3
end
p(::SomeClass::Const) # 輸出3
p( SomeClass::Const) # 同樣輸出3
```
SomeClass是在頂層定義的類,也是常量。于是寫成`SomeClass`和`::SomeClass`都可以。在其中簽到的常量`Const`的路徑就變成了`::SomeClass::Const`
就像在目錄中可以繼續創建目錄一樣,在類中也可以繼續定義類。例如
```
class C # ::C
class C2 # ::C::C2
class C3 # ::C::C2::C3
end
end
end
```
那么問題是,在類定義語句中定義的常量就必須要用完整的路徑表示嗎?當然不是了。就像文件系統一樣,只要在同一層的類定義語句中,就不需要`::`來獲取。如下所示。
```
class SomeClass
Const = 3
p(Const) # 輸出3
end
```
也許你會感到奇怪。居然在類的定義語句中可以直接書寫執行語句。這個也是習慣動態語言的人相當不習慣的部分了。作者當初也是大吃一驚。
姑且再補充說明一點,在方法中也可以獲取常量。獲取的規則和在類的定義語句中(方法之外)是一樣的。
```
class C
Const = "ok"
def test()
p(Const)
end
end
C.new().test() # 輸出"ok"
```
####全部執行
這里從整體出發來寫一段代碼吧。Ruby中程序的大部分都是會被執行的。常量定義,類定義語句,方法定義語句以及其他幾乎所有的東西都會以你看到的順序依次執行。
例如,請看下面的代碼。這段代碼使用了目前為止說到的很多結構。
```
1: p("first")
2:
3: class C < Object
4: Const = "in C"
5:
6: p(Const)
7:
8: def myupcase(str)
9: return str.upcase()
10: end
11: end
12:
13: p(C.new().myupcase("content"))
```
這個代碼將會按照以下的順序執行。
1: p("first") 輸出"first"
3: < Object 通過常量Object獲取Object類對象。
3: class C 生成以Object為父類的新的類對象,并將其代入C
4: Const = "in C" 定義::C::Const。值為"in C"
6: p(Const) 輸出::C::Const的值。輸出結果為"in C"。
8: def myupcase(...)...end 定義方法C#myupcase。
13: C.new().myupcase(...) 通過常量C調用其new方法,并且調用其結果的myupcase方法。
9: return str.upcase() 返回"CONTENT"。
13: p(...) 輸出"CONTENT"。
####局部常量的作用域
終于說道了局部變量的作用域。
頂層,類定義語句內部,模塊定義語句內部,方法自身都各自擁有完全獨立的局部變量作用域。也就是說在下面的程序中Ivar這個變量都不一樣,也沒有相互往來。
```
lvar = 'toplevel'
class C
lvar = 'in C'
def method()
lvar = 'in C#method'
end
end
p(lvar) # 輸出"toplevel"
module M
lvar = 'in M'
end
p(lvar) # 輸出"toplevel"
```
####表示上下文的self
以前執行方法的時候自己自身(調用方法的對象)會成為self。雖然這個是正確的說法,但是話只說了一半。實際上不管是在Ruby程序執行到哪里,self總會被具體賦值。也就是說在頂層和在類定義語句中都會有self存在。
頂層是存在self的,頂層的self就是main。沒有什么奇怪的規則,main就是Object的實例。其實main也僅僅是為了self而存在的,并沒有什么深入的含義。
也就是說頂層的self是main,main則是Object的實例,從頂層也可以直接調用Object的方法。另外在Object中引入了Kernel這個模塊,里面存在著諸如`p`和`puts`一樣函數風格的方法。于是在頂層也可以調用`p`和`puts`。
[](img/ch_minimum_Kernel.jpg)
[](img/ch_minimum_Kernel.jpg)
當然`p`本來就不是函數而是方法。只是因為其定義在Kernel中,無論在哪里,換句話說無論self的類是什么,都可以被當作"自己的"方法來像函數一樣被調用。所以Ruby在真正意義上不存在函數。存在的只有方法。
順帶一提,出了`p`和`puts`之外,帶有函數風格的方法還有`print puts printf
sprintf gets forks exec`等等等等。大多數都是仿佛曾經在哪里見過的名字。從這個命名中大概也可以想象得出Ruby的性格了吧。
接下來,既然self在那里都會被設定那么在類的定義語句里面也是一樣的。在類的定義中self就是類自身(類對象)。于是就會變成這樣。
```
class C
p(self) # C
end
```
這樣設計有什么好處?實際上有個使其好處突出得十分明顯的例子。請看。
```
module M
end
class C
include M
end
```
實際上這個include是針對類對象C的調用。雖然我們還沒有提及,但是很明顯Ruby可以省略調用方法的括號。由于到目前還沒有結束類定義語句的內容,所以作者為了盡量使其看起來不像方法的調用,而把括號給去掉了。
####載入
Ruby中載入庫的過程也是在執行中進行。通常這樣寫。
```
require("library_name")
```
為了不被看到的假象所欺騙這里再說一句,require其實是方法,甚至都不是保留語句。這樣寫的話在寫的地方被執行,庫里面的代碼得以被執行。因為Ruby中不存在像Java里面的包的概念,如果想要將庫的命名空間分開來的話,可以建立一個文件夾然后將庫放到文件夾里面。
```
require("somelib/file1")
require("somelib/file2")
```
于是在庫中也可以定義普通的類和模塊。頂層的常量作用域和文件并沒有多大關聯,而是平坦的,于是從一開始就可以獲取在其他文件中定義的類。如果想要用命名空間來區分類的名字,可以用下面的方法來顯式嵌入模塊。
```
# net 庫的例子來使用模塊命名空間來區分類
module Net
class SMTP
# ...
end
class POP
# ...
end
class HTTP
# ...
end
end
```
###就類的話題繼續深入
####還是關于常量
到目前為止用文件系統的例子來類比了常量的作用域。現在請你完全忘掉剛才的例子。
常量里面還有各種各樣的陷阱。首先,我們可以獲取"外層"類的常量。
Const = "ok"
class C
p(Const) # 輸出"ok"
end
為什么要這樣?因為可以方便使用命名空間來調用模塊。到底怎么回事?我們來用之前Net庫的類來做進一步說明。
```
module Net
class SMTP
# 在方法中可以使用Net::SMTPHelper
end
class SMTPHelper # 輔助Net::SMTP的類
end
end
```
在這種場合,在SMTP類中只需要寫上`Net::SMTPHelper`就可以進行調用。于是就有了"外層類如果可以調用的話就很方便了"這個結論。
不管外層的類進行了多少層嵌套都可以調用。在不同的嵌套層次中如果定義了同名的常量,會調用從內而外最先找到的變量。
```
Const = "far"
class C
Const = "near" # 這里的Const比最外層的Const更近一些
class C2
class C3
p(Const) # 輸出"near"
end
end
end
```
真是太特么復雜了。
我們來總結一下吧。在探索常量的時候,首先探索的是外層的類,然后探索父類。例如看下面這個故意寫成這么扭曲的例子。
```
class A1
end
class A2 < A1
end
class A3 < A2
class B1
end
class B2 < B1
end
class B3 < B2
class C1
end
class C2 < C1
end
class C3 < C2
p(Const)
end
end
end
```
在C3內部想要獲取Const的話,會按照下圖的順序進行探索。
[](img/ch_minimum_constref.jpg)
[](img/ch_minimum_constref.jpg)
注意一點,外層的類的父類,例如A1和B2是不會被探索的。探索的時候所謂的外層僅僅是向外層,父類的話也僅僅是朝著父類的方向。如果不這樣的話,會造成探索的類過多,而無法正確預測這個復雜的行為。
####meta類
我們說過對象可以調用方法。調用的方法則是對象的類所決定的。那么類對象也一定存在自己所屬的類。
[](img/ch_minimum_classclass.jpg)
[](img/ch_minimum_classclass.jpg)
這個時候直接和Ruby確認是最好的方法。返回自己所屬的類的方法是`Object#class`。
```
p("string".class()) # 輸出String
p(String.class()) # 輸出Class
p(Object.class()) # 輸出Class
```
String貌似屬術語Class這個類的。那么進一步Class所屬的類是什么呢?
```
p(Class.class()) # 輸出Class
```
又是Class。也就是說不管是什么對象, 沿著`.class().class().class()……`總會到達Class,然后陷入循環以致最后。
[](img/ch_minimum_ccc.jpg)
[](img/ch_minimum_ccc.jpg)
Class是類的類。我們稱擁有"xx的xx"的這種遞歸構造的的東西"meta xx"。所以Class也被叫做"meta class(類)"。
####meta對象
接下來我們改變目標,來考察一下模塊。模塊也是對象,當然會有其所屬的類了。我們來調查一下。
```
module M
end
p(M.class()) # 輸出Module
```
模塊對象的類貌似是Module。那么Module類對象類是什么呢?
```
p(Module.class()) # Class
```
答案又是Class。
接下來還個方向繼續調查其中的繼承關系。Class和Module的父類到底是什么?Ruby中可以通過`Class#superclass`來進行查看。
```
p(Class.superclass()) # Module
p(Module.superclass()) # Object
p(Object.superclass()) # nil
```
Class居然是Module下層的類。根據以上事實可以得到Ruby中重要類的關系圖。
[](img/ch_minimum_metaobjects.jpg)
[](img/ch_minimum_metaobjects.jpg)
到目前為止我們沒有做任何說明就是用了new和include這些東西。現在終于可以解釋清楚他們的真實面貌了。new實際上是Class類定義的方法。所以無論是什么類(因為都是Class類的實例)都可以使用new。但是Module里面沒有定義new所以無法生成實例。同樣,include被定義在Module類中,所以不管是類還是模塊都可以調用include。
####奇異方法
對象可以調用方法,能調用的方法由對象所屬的類來決定,到目前為止我們都這么說。但是作為設計理念我們還是希望方法都是屬于對象的。說到底只是因為同樣的類定義同樣的方法可以省去不少麻煩。
于是實際上在Ruby中是存在不通過類而直接給對象定義的方法的機制的。可以這樣寫。
```
obj = Object.new()
def obj.my_first()
puts("My first singleton method")
end
obj.my_first() # My first singleton method
```
正如你已了解到,Object是所有類的根目錄。這么重要的類里面是不會輕易讓你定義my_first這種奇怪名字的方法的。另外obj是Object的實例。但是卻可以通過obj這個實例來調用my_first這個方法。也就是說,我們定義了和所屬類完全無關的方法。這種給對象定義的方法我們稱為奇異方法(singleton method)。
那么什么時候使用奇異方法呢?首先是像Java和C++一樣定義靜態方法的時候。也就是說不需要生成實例就可以使用的方法。這種方法在Ruby里面作為Class類對象的奇異方法而存在。
例如UNIX中存在unlink這種系統調用來刪除文件的別名。Ruby中這個方法作為File類的奇異方法可以直接被使用。我們來試一試。
```
File.unlink("core") # 消去core名稱的dump。
```
每次提到都說一遍"這個是File對象的奇異方法unlink"實在是太麻煩了,下面用"File.unlink"代替。注意不要寫成"File#unlink"或者是將"在File類中定義的方法write"錯寫成"File.write"。
下面是寫法的總結
|寫法|調用的對象|調用的實例|
|:-:|:-:|:-:|
|File.unlink|File類自身|File.unlink("core")|
|File#write|File的實例|f.write("str")|
####類變量
類變量是Ruby1.6新增的內容。類變量和變量屬于同一個類,可以被類以及類的實例代入,引用。來看一下例子。開頭是"@@"就是類變量。
```
class C
@@cvar = "ok"
p(@@cvar) # 輸出"ok"
def print_cvar()
p(@@cvar)
end
end
C.new().print_cvar() # 輸出"ok"
```
類變量也是由最初的定義來賦值代入的,所以在代入之前引用出錯。仿佛多加了個"@"就和一般的實例變量不一樣了。
```
% ruby -e '
class C
@@cvar
end
'
-e:3: uninitialized class variable @@cvar in C (NameError)
```
在這里直接用"-e"命令執行了程序。"'"之間三行是程序正文。
另外類變量是可以繼承的。換句話說,子類可以代入,引用父類的類變量。
```
class A
@@cvar = "ok"
end
class B < A
p(@@cvar) # "ok"
def print_cvar()
p(@@cvar)
end
end
B.new().print_cvar() # "ok"
```
####全局變量
最后姑且說一句,全局變量也是存在的。無論在程序的哪里都可以代入,引用。在變量前加上"$"就成了全局變量。
```
$gvar = "global variable"
p($gvar) # "global variable"
```
全局變量和實例變量一樣,我們可以看作所有的名稱都是在代入之前就被定義好的。所以就算是代入前我們引用這個變量,只會返回nil而不會發生錯誤。
(第一章完)