# **第 2 章 便利的對象**
在第 1 章里,我們介紹了 Ruby 的基本數據對象“字符串”以及“數值”。當然,Ruby 能使用的數據對象不可能只有這兩種。一般 Ruby 程序里數據對象的結構都比它們復雜得多。
假設現在我們用 Ruby 做一個通訊錄,通訊錄一般有以下項目:
-
**名字**
-
**拼音**
-
**郵政編碼**
-
**都道府縣**1
-
**地址**
-
**電話號碼**
-
**郵箱地址**
-
**SNS 的 URL**
-
**登記日期**
1日本的行政區域單位——譯者注
在這些項目里,郵政編碼用 7 位的數字表示,除此以外的項目都是用字符串表示(一般來說,登記日期這個項目應該用 `Date` 對象來表示。關于 `Date` 對象我們將會在第 20 章介紹)。
這樣一來,一組的項目集合起來后就可以表示一個人的基本信息(圖 2.1),再把親朋好友的基本信息都收集后就成為一本通訊錄。

**圖 2.1 收集、匯總各項目**
不同數據間的組合無法用字符串或數值這樣簡單的對象來表示,因此我們需要一個可以用以表示數據集合的數據結構。
本章我們將介紹數組和散列這兩 種新的數據結構。另外,我們還會介紹處理字符串時常用的工具——正則表達式。
> **備注** 像數組、散列這樣保存對象的對象,我們稱為容器(container)。
數組、散列、正則表達式的應用十分廣泛,關于它們的詳細用法我們會在后面的章節介紹,在這里我們只會簡略地說明一下,讓大家對它們有個初步印象。
### **2.1 數組**
數組(array)是一個按順序保存多個對象的對象,它是基本的容器之一。我們一般稱為數組對象或者 Array 對象。
### **2.1.1 數組的創建**
要創建數組,我們需要把各數組的元素用逗號隔開,然后再用 `[]` 把它們括起來即可。首先,讓我們創建一個簡單的數組。
~~~
names = [" 小林 ", " 林 ", " 高野 ", " 森岡 "]
~~~
在這個例子里,我們創建了一個叫 `names` 的數組對象,分別保存了 4 個數組元素:小林、林、高野、森岡。如圖 2.2 所示。

**圖 2.2 數組對象**
### **2.1.2 數組對象**
在數組元素對象還未確定的情況下,我們可以用 `[]` 表示一個空數組對象。
~~~
names = []
~~~
除此以外,還有其他方法創建數組,我們將會在第 13 章再詳細說明。
### **2.1.3 從數組中抽取對象**
保存在數組里的每個對象,都各自有一個表示其位置的編號,我們稱之為索引(index)。利用索引,我們可以進行把對象保存到數組、從數組中抽取對象等操作。
要從數組中抽取元素(對象),我們可以使用以下方法。
**數組名 [ 索引 ]**
如下所示,有一個名為 `names` 的數組對象。
~~~
names = [" 小林 ", " 林 ", " 高野 ", " 森岡 "]
~~~
把 `names` 數組里第一個元素拿出來,我們可以這么做
~~~
names[0]
~~~
因此,若執行以下代碼,
~~~
print "第一個名字為:", names[0], "。\n"
~~~
得到的結果為,
~~~
第一個名字為小林。
~~~
同樣地,`names[1]` 表示林,`names[2]` 表示高野。
> **執行示例**
~~~
> irb --simple-prompt
=> names = ["小林", "林", "高野", "森岡"]
=> ["小林", "林", "高野", "森岡"]
>> names[0]
=> "小林"
>> names[1]
=> "林"
>> names[2]
=> "高野"
>> names[3]
=> "森岡"
~~~
> **備注** 數組的索引值是從 0 開始,并非 1。因此,`a[1]`返回的并不是數組第一個元素,而是第二個元素。剛接觸編程時,大家比較容易弄錯(即便是熟悉以后也有可能會犯這樣的錯誤)。大家在使用數組時,務必注意索引值這個特點。
> **注** 在 Windows 命令行中,用 Alt + Shift 鍵切換中英文輸入法模式。
### **2.1.4 將對象保存到數組中**
我們可以將新的對象保存到已經創建的數組中。
將數組里的某個元素置換為其他對象,我們可以這樣做:
**數組名 [ 索引 ] = 希望保存的對象**
我們試著置換一下剛才的 `names` 數組的內容,將 " 野尻 " 放在數組首位。
~~~
names [0] = "野尻"
~~~
執行下面的程序,我們可以知道 " 野尻 " 的確已經被置換為首位的數組元素。
> **執行示例**
~~~
> irb --simple-prompt
>> names = ["小林", "林", "高野", "森岡"]
=> ["小林", "林", "高野", "森岡"]
>> names[0] = "野尻"
=> "野尻"
>> names
=> ["野尻", "林", "高野", "森岡"]
~~~
在保存對象時,如果指定了數組中不存在的索引值時,則數組的大小會隨之而改變。Ruby 數組的大小是按實際情況自動調整的 2。
2即動態數組。——譯者注
> **執行示例**
~~~
> irb --simple-prompt
>> names = ["小林", "林", "高野", "森岡"]
=> ["小林", "林", "高野", "森岡"]
>> names[4] = "野尻"
=> "野尻"
>> names
=> ["小林", "林", "高野", "森岡", "野尻"]
~~~
### **2.1.5 數組的元素**
任何對象都可以作為數組元素保存到數組中。例如,我們除了可以創建字符數組,還可以創建數值數組。
~~~
num = [3, 1, 4, 1, 5, 9, 2, 6, 5]
~~~
Ruby 數組還支持多種不同對象的混合保存。
~~~
mixed = [1, " 歌 ", 2, " 風 ", 3]
~~~
這里,我們不再舉其他例子了,像時間、文件等對象也都可以作為數組元素。
### **2.1.6 數組的大小**
我們可以用 `size` 方法獲知數組的大小。例如,若想獲知數組 `array` 的大小,程序可以這么寫:
~~~
array.size
~~~
我們現在就用 `size` 方法,查看一下剛才的 `names` 數組的大小。
> **執行示例**
~~~
> irb --simple-prompt
>> names = ["小林", "林", "高野", "森岡"]
=> ["小林", "林", "高野", "森岡"]
>> names.size
=> 4
~~~
size 方法的返回值就是數組的大小。
### **2.1.7 數組的循環**
有時,我們希望輸出所有數組元素,或者對在數組中符合某條件的元素執行 xx 方法,不符合條件的執行 yy 方法。為實現這些目的,我們需要一種方法遍歷所有數組元素。
為此,Ruby 提供了 `each` 方法。我們在第 1 章介紹迭代器時,已經稍微接觸了一下 `each` 方法。
`each` 方法的語法如下:
**數組 .`each do` | 變量 |
希望循環的處理
`end`**
`each` 后面在 `do ~ end` 之間的部分稱為塊(block)3。因此,`each` 這樣的方法也可以稱為帶塊的方法。我們可以把多個需要處理的內容合并后寫到塊里面。
3也稱為代碼塊。——譯者注
塊的開始部分為 | 變量 |。`each` 方法會把數組元素逐個拿出來,賦值給指定的 | 變量 |,那么塊里面的方法就可以通過訪問該變量,實現循環遍歷數組的操作。
接下來,我們實際操作一下,按順序輸出數組 `names` 的元素。
> **執行示例**

每循環一次,就會把當前的數組元素賦值給變量 `|n|`(圖 2.3)。

**圖 2.3 循環時 n 的變化**
除了 `each` 方法外,數組還提供了許多帶塊的方法,我們在實際的數組操作中會經常使用到,詳細的內容會在 13.6 節介紹。
### **2.2 散列**
散列(hash)也是一個程序里常用到的容器。散列是鍵值對(key-value pair)的一種數據結構。在 Ruby 中,一般是以字符串或者符號(Symbol)作為鍵,來保存對應的對象(圖 2.4)。

**圖 2.4 散列**
### **2.2.1 什么是符號**
在 Ruby 中,符號(symbol)與字符串對象很相似 4,符號也是對象,一般作為名稱標簽來使用,用來表示方法等的對象的名稱。
4可以將符號簡單理解為輕量級的字符串。——譯者注
創建符號,只需在標識符的開頭加上`:` 就可以了。
~~~
sym = :foo # 表示符號“:foo”
sym2 = :"foo" # 意思同上
~~~
符號能實現的功能,大部分字符串也能實現。但像散列鍵這樣只是單純判斷“是否相等”的處理中,使用符號會比字符串比較更加有效率,因此在實際編程中我們也會時常用到符號。
另外,符號與字符串可以互相任意轉換。對符號使用 `to_s` 方法,則可以得到對應的字符串。反之,對字符串使用 `to_sym` 方法,則可以得到對應的符號。
> **執行示例**
~~~
> irb --simple-prompt
>> sym = :foo
=> :foo
>> sym.to_s # 將符號轉換為字符串
=> "foo"
>> "foo".to_sym # 將字符串轉換為符號
=> :foo
~~~
### **2.2.2 散列的創建**
創建散列的方法與創建數組的差不多,不同的是,不使用 `[]`,而是使用 `{}` 把創建的內容括起來。散列用`=>`來定義獲取對象時所需的鍵(key),以及鍵相對應的對象(value)。
~~~
address = {:name => "高橋", :pinyin => "gaoqiao", :postal => "1234567"}
~~~
將符號當作鍵來使用時,程序還可以像下面這么寫:
~~~
address = {name: "高橋", pinyin: "gaoqiao", postal: "1234567"}
~~~
### **2.2.3 散列的使用**
從散列取出對象、將對象保存到散列的使用方法也都和數組非常相似。我們使用以下方法從散列里取出對象。
**散列名 [ 鍵 ]**
保存對象時使用以下方法。
**散列名[ 鍵 ] = 希望保存的對象**
> **執行示例**
~~~
> irb --simple-prompt
>> address = {name: "高橋", pinyin: "gaoqiao"}
=> {:name=>"高橋", :pinyin=>"gaoqiao"}
>> address[:name]
=> "高橋"
>> address[:pinyin]
=> "gaoqiao"
>> address[:tel] = "000-1234-5678"
=> "000-1234-5678"
>> address
=> {:name=>"高橋", :pinyin=>"gaoqiao", :tel=>"000-1234-5678"}
~~~
### **2.2.4 散列的循環**
使用 `each` 方法,我們可以遍歷散列里的所有元素,逐個取出其元素的鍵和對應的值。循環數組時是按索引順序遍歷元素,循環散列時按照鍵值組合遍歷元素。
散列的 `each` 語法如下。
**散列 .`each do` | 鍵變量 , 值變量 |
希望循環的處理
`end`**
事不宜遲,我們馬上來看看怎么用。
> **執行示例**
~~~
> irb --simple-prompt
>> address = {name: "高橋", pinyin: "gaoqiao"}
=> {:name=>"高橋", :pinyin=>"gaoqiao"}
>> address.each do |key, value|
?> puts "#{key}: #{value}"
>> end
name: 高橋
pinyin: gaoqiao
=> {:name=>"高橋", :pinyin=>"gaoqiao"}
~~~
顯而易見,程序循環執行了輸出散列 `address` 的鍵和值的 `puts` 方法 5。
5原文是 print 方法。——譯者注
### **2.3 正則表達式**
在 Ruby 中處理字符串時,我們常常會用到正則表達式(regular expression)。使用正則表達式,可以非常簡單地實現以下功能:
-
**將字符串與模式(pattern)相匹配**
-
**使用模式分割字符串**
Ruby 的前輩——Perl、Python 等腳本語言至今還在使用正則表達式。Ruby 繼承了這一點,把正則表達式的使用嵌入到語法中,大大簡化了正則表達式的調用方式。正是在正則表達式的幫助下,字符串處理變成了一個 Ruby 非常擅長的領域。
### **模式與匹配**
我們有時會有按照特定模式進行字符串處理的需求,比如“找出包含○○字符串的行”或者“抽取○○和 ×× 之間的字符串”。判斷字符串是否適用于某模式的過程稱為匹配,如果字符串適用于該模式則稱為匹配成功(圖 2.5)。

**圖 2.5 匹配的例子**
像這樣的字符串模式就是所謂的正則表達式。
乍一看,“正則表達式”這個詞可能會給人一種深奧、難理解的印象。的確,正則表達式非常復雜,但如果只是使用單純的匹配功能,也并不怎么難。所以大家也無需感到如臨大敵,我們暫時只需要知道有個工具叫“正則表達式”就足夠了。
創建正則表達式對象的語法如下所示。
**/ 模式 /**
例如,我們希望匹配 `Ruby` 字符串的正則表達式為:
~~~
/Ruby/
~~~
把希望匹配的內容直接寫出來,就這么簡單。匹配字母、數字時,模式按字符串原樣寫就可以了。6
6漢字也可以通過同樣的方法做匹配。——譯者注
我們用運算符`=~` 來匹配正則表達式和字符串。它與判斷是否為同一個對象時用到的 `==` 有點像。
匹配正則表達式與字符串的方法是:
**/ 模式 / =~ 希望匹配的字符串**
若匹配成功則返回匹配部分的位置。字符的位置和數組的索引一樣,是從 0 開始計數的。也就是說,字符串的首個字符位置為 0。反之,若匹配失敗,則返回 `nil`。
> **執行示例**
~~~
> irb --simple-prompt
>> /Ruby/ =~ "Ruby"
=> 0
>> /Ruby/ =~ "Diamond"
=> nil
~~~
之前曾提到過,使用單純的字母、數字、漢字模式時,如果字符串里存在該模式則匹配成功,否則匹配失敗。
> **執行示例**
~~~
> irb --simple-prompt
>> /Ruby/ =~ "Yet Another Ruby Hacker,"
=> 12
>> /Yet Another Ruby Hacker,/ =~ "Ruby"
=> nil
~~~
正則表達式右邊的 / 后面加上 i 表示不區分大小寫匹配。
> **執行示例**
~~~
> irb --simple-prompt
>> /Ruby/ =~ "ruby"
=> nil
>> /Ruby/ =~ "RUBY"
=> nil
>> /Ruby/i =~ "ruby"
=> 0
>> /Ruby/i =~ "RUBY"
=> 0
>> /Ruby/i =~ "rUbY"
=> 0
~~~
除此以外,正則表達式還有很多寫法和用法,更詳細內容將會在第 16 章介紹。
> **專欄**
> **nil 是什么**
> `nil` 是一個特殊的值,表示對象不存在。像在正則表達式中表示無法匹配成功一樣,方法不能返回有意義的值時就會返回 `nil`。另外,從數組或者散列里獲取對象時,若指定不存在的索引或者鍵,則得到的返回值也是 `nil`。
> > **執行示例**
~~~
> irb --simple-prompt
>> hash = {"a"=>1, "b"=>2}
=> {"a"=>1, "b"=>2}
>> hash["c"]
=> nil
~~~
> `if` 語句和 `while` 語句在判斷條件時,如果碰到 `false` 和 `nil`,則會認為是“假”,除此以外的都認為是“真”。因此,除了可以使用返回 `true` 或者 `false` 的方法,也可以使用“返回某個值”或者返回“`nil`”的方法作為判斷條件表達式。
> **代碼清單 print_hayasi.rb**
~~~
names = ["小林", "林", "高野", "森岡"]
["小林", "林", "高野", "森岡"]
names.each do |name|
if / 林/ =~ name
puts name
end
end
~~~
> > **執行示例**
~~~
> ruby print_hayasi.rb
小林
林
~~~
- 推薦序
- 譯者序
- 前言
- 本書的讀者對象
- 第 1 部分 Ruby 初體驗
- 第 1 章 Ruby 初探
- 第 2 章 便利的對象
- 第 3 章 創建命令
- 第 2 部分 Ruby 的基礎
- 第 4 章 對象、變量和常量
- 第 5 章 條件判斷
- 第 6 章 循環
- 第 7 章 方法
- 第 8 章 類和模塊
- 第 9 章 運算符
- 第 10 章 錯誤處理與異常
- 第 11 章 塊
- 第 3 部分 Ruby 的類
- 第 12 章 數值類
- 第 13 章 數組類
- 第 14 章 字符串類
- 第 15 章 散列類
- 第 16 章 正則表達式類
- 第 17 章 IO 類
- 第 18 章 File 類與 Dir 類
- 第 19 章 Encoding 類
- 第 20 章 Time 類與 Date 類
- 第 21 章 Proc 類
- 第 4 部分 動手制作工具
- 第 22 章 文本處理
- 第 23 章 檢索郵政編碼
- 附錄
- 附錄 A Ruby 運行環境的構建
- 附錄 B Ruby 參考集
- 后記
- 謝辭