Ruby 的字面構造器
Ruby 大部分的內建類可以使用 new 來執行個體化:
str = String.new
arr = Array.new
有些類不能使用 new 來執行個體化,比如 Integer 類。有些內建類可以使用字面構造器,就是不需要使用 new ,可以使用一種特別的形式來建立類的執行個體。
內建類的字面構造器
String:使用引號, —— "new string",'new string'
Symbol:冒號在前,—— :symbol,:"symbol with spaces"
Array:方括弧,—— [1,2,3]
Hash:大括弧,—— {"New York" => "NY", "Oregon" => "OR"}
Range:兩個或三個點,—— 0..9 或者 0...10
Regexp:斜線,—— /([a-z]+)/
Proc(Lambda):橫線,箭頭,括弧,大括弧 —— ->(x,y){x * y}
糖衣文法
文法糖(Syntactic Sugar)。方法調用是這個形式:object.method(args),但 Ruby 提供了一些糖衣文法,可以讓方法調用更漂亮:
x = 1 + 2
上面相當於是:
x = 1.+(2)
通過定義方法來定義操作符
如果在類裡定義了 a + 方法,你的類的對象就可以使用加法的糖衣文法。注意,操作符也是一種方法,這種方法只是作為操作符來用,讓它更好看點。
使用 + 是一種慣例,Ruby 本身不知道 + 的意思是加法。你可以定義自己的不是加法的 + 方法:
obj = Object.new
def obj.+(other_obj)
"想加點東西嗎?"
end
puts obj + 100
puts 後面的 + 是調用 obj 上面的 + 方法,整數 100 是它的唯一參數。實際上 obj 上的 + 方法不是加法的意思,而只是簡單的輸出一個字串。
在操作符糖衣之上的是快捷糖衣:x += 1 就是 x = x + 1。下面的銀行帳號類裡有 + 與 - 方法:
class Account
attr_accessor :balance
def initialize(amount=0)
self.balance = amount
end
def +(x)
self.balance += x
end
def -(x)
self.balance -= x
end
def to_s
balance.to_s
end
end
acc = Account.new(20)
acc -= 5
puts acc
上面輸出的結果是 15。定義了 - 這個執行個體方法以後,我們就可以使用 -= 這個捷徑了。
自訂一元操作符
+ 與 - 是一元操作符,在自己的類或對象裡你可以指定 +obj 與 -obj 這些運算式的行為,可以定義方法 +@ 與 -@ 。
比如你想在一個叫 Banner 的類裡面重新定義 + 與 - 的行為,讓它們意思是把字串變成大寫字母或小寫字母:
class Banner
def initialize(text)
@text = text
end
def to_s
@text
end
def +@
@text.upcase
end
def -@
@text.downcase
end
end
然後再實驗一下:
banner = Banner.new("Eat at David's!")
puts banner # 輸出:Eat at David's!
puts +banner # 輸出:EAT AT DAVID'S!
puts -banner # 輸出:eat at david's!
下午 5:30 ***
下午7:22 **
你也可以定義 ! 操作符,定義一個 ! 方法就行了,完成以後你會有一個一元操作符 ! ,另外還有一個 not :
class Banner
def !
reverse
end
end
再實驗一下:
puts !banner # 輸出:!s'divaD ta taE
puts (not banner) # 輸出:!s'divaD ta taE
! 方法
2016年9月11日 上午9:41 ***
Ruby 的方法可以使用 ! 號結尾,表示 “危險”。比如兩個方法做的事情差不多,其中的一個會有點副作用,也可以說它有點危險,那麼你可以讓這兩個方法叫一個名字,但是那個有副作用的或者有點危險的方法的名字的最後可以加上一個 ! 號結尾,比如(upcase 與 upcase!)。
這是 Ruby 的習俗,或者叫慣例,! 號本身在 Ruby 內部並沒有特別的意思。但是按照慣例,有歎號的方法就表示它是一個危險的方法。什麼是危險,其實是寫這個方法的人定義的。比如在 Ruby 內建的類裡面,帶 ! 號的方法相比它的不帶歎號的版本,可能會改變它的接收者。不過也不一直是這樣,像:exit/exit!,sub/sub! 。
你也可以把危險想成是一種警告,或者注意! 大部分帶歎號的方法都會有一個不帶吧號的版本,它們是成對出現的。
破壞性
Ruby 核心帶的一些帶 ! 號結尾的方法有破壞性,就是它們會改變調用它的那個對象。比如 upcase 這個方法,它會返回一個新的大寫字母的字母串,但是 upcase! 方法會直接改變原始的字串。
做個實驗:
>> str = 'hello'
=> "hello"
>> str.upcase
=> "HELLO"
>> str
=> "hello"
>> str.upcase!
=> "HELLO"
>> str
=> "HELLO"
Ruby 核心裡的有很多這樣的方法,比如 sort/sort!,strip/strip!,reverse/reverse! 。不帶歎號的方法返回新的對象,帶歎號的方法直接改變原始對象。你得注意調用的方法是不是會改變它的接收者。返回新的對象的方法比修改原始對象的方法更消耗資源。
另一面,你也要注意一下,修改原始對象可能會影響到程式裡的其它的地方,比如可能還有些地方要使用原始的沒有被修改的對象。
破壞與危險
破壞與危險並不是一回事,有些帶破壞性的方法可能不帶 ! 號。Ruby 本身並不在乎方法裡是不是有 ! 號,只要是方法它都會被執行。這個 ! 號是方法的作者與使用者之間的一種溝通的方式。這是一種慣例,有經驗的人一看就知道它的意思。
想使用帶 ! 號的方法,方法一定得有一個跟它對應的不帶 ! 號的版本。這兩個方法做的事情基本一樣,就是帶 ! 號的方法可能有點副作用,比如返回一個不同的值,或者有一些其它的跟不帶 ! 號的方法不同的行為。
不要你覺得方法在某些方面有點危險就在方法的名字裡使用 ! 號。因為任何的方法它自己本身都不是危險的。只有在一些相對的情況下,一個方法有兩個名字,一個帶歎號,一個不帶歎號,帶歎號的方法的意思是這個方法可能會有一些副作用。
比如一個儲存檔案的方法,別因為它能把檔案儲存到磁碟上,你就叫它 save! ,就叫它 save 就行了。除非你需要另一個方法,儲存檔案的時候不備份原始檔案(假設 save 方法能備份原始檔案),你可以叫這個方法 save! 。
注意並不是所有的有破壞性的方法都是用 ! 號結尾的。沒人強制在方法的名字裡使用 ! 號,這隻是一種慣例。
to_*
上午11:54 ***
Ruby 裡面有一些用 to_ 開頭的方法,它們可以轉換對象。比如 to_s(轉換成字串),to_sym(轉換成符號),to_a(轉換數組),to_i(轉換整數),to_f(轉換浮點小數)。
to_s:轉換字串
做個實驗:
>> "hello".to_s
=> "hello"
再試一下:
>> ["one", "two", "three", 4, 5, 6].to_s
=> "[\"one\", \"two\", \"three\", 4, 5, 6]"
再試試:
>> Object.new.to_s
=> "#<Object:0x007fbde88267e0>"
再做個實驗:
>> obj = Object.new
=> #<Object:0x007fbde882d9a0>
>> puts obj
#<Object:0x007fbde882d9a0>
=> nil
>> def obj.to_s
>> "I'm an object!"
>> end
=> :to_s
>> puts obj
I'm an object!
=> nil
字串插值也會用到 to_s:
>> "My Object says: #{obj}"
=> "My Object says: I'm an object!"
看一下 inspect 方法:
>> Object.new.inspect
=> "#<Object:0x007fbde8963e78>"
irb 在輸出的值上面會自動調用 inspect 方法:
>> Object.new
=> #<Object:0x007fbde8960ed0>
>> 'abc'
=> "abc"
>> [1,2,3]
=> [1, 2, 3]
>> /a regular expression/
=> /a regular expression/
自己可以在類裡定義 inspect:
class Person
def initialize(name)
@name = name
end
def inspect
@name
end
end
david = Person.new('David')
puts david.inspect
輸出的結果是 David 。
再用一下 display:
>> 'hello'.display
hello=> nil
to_a:轉換數組
[1,2,3,4,5]
方括弧裡的東西是一個列表,而不是一個數組,加上方括弧以後它才會是一個數組。
>> array = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> [*array]
=> [1, 2, 3, 4, 5]
如果不用 * 號:
>> [array]
=> [[1, 2, 3, 4, 5]]
參數:
def combine_names(first_name, last_name)
first_name + " " + last_name
end
names = ["David", "Black"]
puts combine_names(*names)
輸出的是:David Black 。如果不用 * 號,你的參數的值就會是一個數組。
to_i 與 to_f
Ruby 不會自動把字串轉換成數字,或者把數字轉換成字串。
試一下:
>> 1 + "2"
會提示:
TypeError: String can't be coerced into Fixnum
from (irb):25:in `+'
from (irb):25
from /usr/local/bin/irb:11:in `<main>'
因為 Ruby 不知道把數字跟字串加到一塊兒是什麼意思。
再做個實驗:
print '輸入一個數字:'
n = gets.chomp
puts n * 100
結果就是你輸入的東西會輸出 100 次。把輸入的東西當成數字來處理,你得:
n = gets.to_i
再試一下:
>> 'hello'.to_i
=> 0
保留字元串裡的數字部分:
>> '123hello'.to_i
=> 123
被保留的數字部分要在前面,在後面是不行的:
>> 'hello123'.to_i
=> 0
to_f 方法的行為跟 to_i 類似。
嚴格轉換
用 Integer 與 Float。
>> '123abc'.to_i
=> 123
>> Integer('123abc')
ArgumentError: invalid value for Integer(): "123abc"
from (irb):34:in `Integer'
from (irb):34
from /usr/local/bin/irb:11:in `<main>'
>> Float('3')
=> 3.0
>> Float('-3')
=> -3.0
>> Float('-3xyz')
ArgumentError: invalid value for Float(): "-3xyz"
from (irb):37:in `Float'
from (irb):37
from /usr/local/bin/irb:11:in `<main>'
角色扮演 to_*
>> 'hello ' + 'there'
=> "hello there"
>> 'hello' + 10
TypeError: no implicit conversion of Fixnum into String
from (irb):40:in `+'
from (irb):40
from /usr/local/bin/irb:11:in `<main>'
class Person
attr_accessor :name
def to_str
name
end
end
如果建立一個 Person 對象,讓它去加一個字串,to_str
>> david = Person.new
=> #<Person:0x007fbde8a498d8>
>> david.name = 'David'
=> "David"
>> puts 'david is named ' + david + '.'
david is named David.
=> nil
to_ary
class Person
attr_accessor :name, :age, "email"
def to_ary
[name, age, email]
end
end
試下:
>> david = Person.new
=> #<Person:0x007fbde89fa2b0>
>> david.name = "David"
=> "David"
>> david.age = 55
=> 55
>> david.email = "david@wherever"
=> "david@wherever"
>> array = []
=> []
>> array.concat(david)
=> ["David", 55, "david@wherever"]
>> p array
["David", 55, "david@wherever"]
=> ["David", 55, "david@wherever"]
布爾狀態,布爾對象,nil
Ruby 的每個運算式都會計算出一個對象,每個對象裡面都有一個布爾值,true 或 false 。true 與 false 本身也是對象。在很多情況下,在你想得到真或假值的地方,比如 if 聲明,或比較兩個數字,你不用直接處理這些特別的對象,在這種情況下,你可以把真假想成是狀態,而不是對象。
作為狀態的 true 與 false
Ruby 裡的運算式要麼是 true 要麼就是 false。使用 if 語句你可以去驗證運算式到底是 true 還是 false 。
做些實驗:
if (class MyClass; end)
puts '空白的對象是 true'
else
puts '空白的對象是 false'
end
if (class MyClass; 1; end)
puts '類定義裡有個數字 1 是 true'
else
puts '類定義裡有個數字 1 是 false'
end
if (def m; return false; end)
puts '方法定義是 true'
else
puts '方法定義是 false'
end
if 'string'
puts '字串是 true'
else
puts '字串是 false'
end
if 100 > 50
puts '100 大於 50'
else
puts '100 不大於 50'
end
結果會是:
空白的對象是 false
類定義裡有個數字 1 是 true
方法定義是 true
字串是 true
100 大於 50
再來試一下,看看錶達式到底返回的是什麼東西:
>> class MyClass; end
=> nil
>> class MyClass; 1; end
=> 1
>> def m; return flase; end
=> :m
>> 'string'
=> "string"
>> 100 > 50
=> true
100 > 50 返回的是 true 這個對象。
作為對象的 true 與 false
true 與 false 是特別的對象,它們是 TrueClass 與 FalseClass 類的唯一的執行個體。
做個實驗:
>> puts true.class
TrueClass
=> nil
>> puts false.class
FalseClass
=> nil
true 與 false 是關鍵詞,所以你不能把它們用在變數或方法的名字裡。
>> a = true
=> true
>> a = 1 unless a
=> nil
>> a
=> true
>> b = a
=> true
有時候你會看到把 true 或 false 作為方法的參數使用。比如你想讓一個類顯示它所有的執行個體方法,但不想顯示在祖先類裡的執行個體方法,可以這樣做:
>> String.instance_methods(false)
nil
nil 是 NilClass 的唯一執行個體。nil 的布爾值是 false。nil 表示的是沒有任何東西。
做個實驗:
puts @x
puts 一個不存在的執行個體變數,輸出的是 nil 。nil 也是不存在的元素的預設值,比如你建立了一個數組,裡面有三個項目,當你訪問數組裡的第九個項目的時候,得到的值就是 nil 。試一下:
>> ['one', 'two', 'three'][9]
=> nil
nil 表示的就是沒有還有不存在。不過 nil 本身是存在的,它也可以像其它對象一樣回應程式法地調用:
>> nil.to_s
=> ""
>> nil.to_i
=> 0
>> nil.object_id
=> 8
nil 與 false 是 Ruby 裡面唯一的兩個布爾值是 false 的對象。
下午2:53 ***
比較兩個對象
下午3:28 ***
Ruby 的對象可以比較它們自己是否跟其它的對象相等。
相等測試
做些實驗:
>> a = Object.new
=> #<Object:0x007fbde89294a8>
>> b = Object.new
=> #<Object:0x007fbde89099c8>
>> a == a
=> true
>> a == b
=> false
>> a != b
=> true
>> a.eql?(a)
=> true
>> a.eql?(b)
=> false
>> a.equal?(a)
=> true
>> a.equal?(b)
=> false
在上面的實驗裡,我們用了三種相等測試的方法,==,eql?,equal? ,它們給出的結果是一樣的。a 跟 a 是相等的,a 跟 b 不相等。
在自己的類裡你可以重新定義 == 與 eql? 方法。一般會留著 equal? 方法,你可以用它比較兩個對象表示的是不是同一個對象。比如在 String 類裡就重新定義了 == 與 eql? 方法:
>> string1 = 'text'
=> "text"
>> string2 = 'text'
=> "text"
>> string1 == string2
=> true
>> string1.eql?(string2)
=> true
>> string1.equal?(string2)
=> false
Comparable 模組
最常見的重定義的相等測試方法是 == 。它是相等測試方法大家族裡的一員,也是比較方法家族的成員,==,!==,>,<,>=,<= 。
如果你需要讓 MyClass 這個類的對象擁有所有的這些比較方法,你只需要:
把 Comparable 模組混合到 MyClass 裡面。
在 MyClass 裡使用 <=> 名字定義一個比較方法。
<=> 比較方法有時候也叫飛船操作符或者飛船方法。在這個方法裡你可以定義小於,等於,大於到底是什麼意思。完成以後,Ruby 就會給你提供對應的比較方法。
例如,你有一個投標類,名字是 Bid,可以跟蹤進來的投標,你希望有一些比較的功能可以比較兩個 Bid 對象,基於 estimate 屬性。比較的時候可以使用 > ,< 這些簡單的操作符。
class Bid
include Comparable
attr_accessor :estimate
def <=>(other_bid)
if self.estimate < other_bid.estimate
-1
elsif self.estimate > other_bid.estimate
1
else
0
end
end
end
簡單點,可以這樣;
def <=>(other_bid)
self.estimate <=> other_bid.estimate
end
實驗一下:
>> bid1 = Bid.new
=> #<Bid:0x007fbde8960778>
>> bid2 = Bid.new
=> #<Bid:0x007fbde8959928>
>> bid1.estimate = 100
=> 100
>> bid2.estimate = 105
=> 105
>> bid1 < bid2
=> true
檢查對象的能力
inspection 與 reflection,就是讓 Ruby 對象在它的生命週期裡關於它自己的一些事兒。
列出對象的方法
你想知道一個對象能懂什麼資訊,也就是能在它上面調用什麼方法,試一下:
>> '我是個字串對象'.methods
你會看到大量的方法,排下順序可以:
>> '我是個字串對象'.methods.sort
methods 方法也可以在模組對象與類對象上用:
>> String.methods.sort
列表裡的方法是 String 這個類對象的方法。
在一個字串對象上定義一個獨立方法,再查看這個對象的方法列表,你會找到在這個對象上定義的這個獨立方法:
>> str = 'A plain old string'
=> "A plain old string"
>> def str.shout
>> self.upcase + "!!!"
>> end
=> :shout
>> str.shout
=> "A PLAIN OLD STRING!!!"
>> str.methods.sort
你也可以單獨顯示對象的獨立方法:
>> str.singleton_methods
=> [:shout]
在一個類裡混合了模組,在模組裡的方法,類的執行個體對象也可以調用,再做個實驗:
>> str = 'Another plain old string.'
=> "Another plain old string."
>> module StringExtras
>> def shout
>> self.upcase + '!!!'
>> end
>> end
=> :shout
>> class String
>> include StringExtras
>> end
=> String
>> str.methods.include?(:shout)
=> true
先建立了一個字串對象,又建立了一個模組,又把這個模組混合到了 String 類裡面,再查看最開始建立的字串對象是否包含在模組裡定義的 :shout 方法的時候,會返回 true 。
查詢類與模組對象
在 irb 裡執行 String.methods.sort 的時候,你會找到一個 instance_methods 方法,它會告訴我們 String 類的執行個體能使用的執行個體方法:
>> String.instance_methods.sort
也可以問一下模組:
>> Enumerable.instance_methods.sort
過濾與選擇的方法列表
有時候你想知道某個特定類上面定義的執行個體方法,不想包含這個類的祖先上面定義的方法,可以這樣做:
String.instance_methods(false).sort
你看到的就只是在 String 類上定義的執行個體方法。還有幾種列出方法的方法:
obj.private_methods
obj.public_methods
obj.protected_methods
obj.singleton_methods
類與模組上的執行個體方法:
MyClass.private_instance_methods
MyClass.protected_instance_methods
MyClass.public_instance_methods
public_instance_methods 跟 instance_methods 是一個意思。