執行方式有以下幾種:
I.交談模式 (有些版本不支援此)
ex. hello例:
1. 打ruby進入
2. 打 puts "Hello, world!" # 註:puts 可簡化成 p
3. 按^D
II.ruby <script檔名>
ex. ruby hello.rb
III. use the Unix ``shebang'' notation as the first line (記得檔名加x屬性)
第一行常用有:
如:#!/usr/bin/ruby -w
或如:#!/usr/bin/env ruby 用此行可避免像上行在不同系統而可能需改path等情形
常量亦為物件,故有其method可用,如:
"gin joint".length 為 9
"Rick".index("c") 為 2
-1942.abs 為 1942
若類別或模組內含constant, 平時可用<類別名或模組名>::<CONST名>來讀取
(注意!constant名的開頭字元必須大寫! 例如class名 屬constant, 故第一字元須大寫)
像諸如 number = number.abs 對ruby是合法的,故使用時須小心
true or false:
只有false和nil表示false, 其他皆表為true, 故在Ruby中,0亦為true.
字串例:
str = "Tom" + " Huse" # -> "Tom Huse"
str[-4,1] + str[1,2] + str[-1,1] # -> "Home"
Control Structures:
如 if ...elsif....else....end
如 while......end
當if...end 中間僅一行時用 ... if ... 亦可(後不含end)
同理 while...end僅一行時可用 ... while ...
Array (containers之一) 建立方式例:
I 法ex. empty1 = []
II法ex. empty2 = Array.new
III法ex.empty3 = Array.new(0) # 0表長度為0, 故empty3亦為一empty array
當puts 一array時,自動地一元素列一行; 若需陣列印於一行,則陣列後須加.inspect
ex.
a = [5, 6, 7, 8, 9]
puts a.inspect # 印出 [5, 6, 7, 8, 9]
for i in 0...a.length # 因a只五個元素,故此例a.length為5,注意此行三點... 會不含上限,故i只執行到4 (i.e.5-1);若是二點..才會包含上限至i=5
puts a[i] # 陣列a元素一行一個依次印出
end
a.length.times do |i| # 此三行與上for迴圈效果相同 (執行5 次迴圈,預設從0開始,故只執行到4)
puts a[i]
end
Regular Expressions: ( =~ 用法同 Perl )
ex1. /P(erl|ython)/ 相當於 /Perl|Python/
ex2. /\d\d:\d\d:\d\d/ # a time such as 12:34:56
ex3. /Perl\s+Python/ # Perl, one or more spaces, then Python
ex4. /^Ruby (Perl|Python)/ # 開頭依次為為 Ruby, a space, and either Perl or Python
ex5. if line =~ /Perl|Python/
line.sub(/Perl/, 'Ruby') # replace first 'Perl' with 'Ruby'
line.gsub(/Python/, 'Ruby') # replace every 'Python' with 'Ruby'
end
定義方法:(當定義時,方法內引數若有設值則其值為default)
ex1. 底下 def ... end 之間亦可簡化成一行: return "Test, aug: #{m}" 或 "Test, aug: #{m}" (因 Ruby可省略 return)
def testFn(m='d-0')
result = "Test, aug: " + m # <=> result = "Test, aug: #{m}"
return result # <=> result (因return 可省略)
end
以下為使用上面testFn方法之同例等價敘述:(皆印出Test, aug: a-1 若不加後面引數則預設'd-0', 那麼將為Test, aug: d-0)
puts testFn "a-1"
puts testFn("a-1")
puts(testFn "a-1")
puts(testFn("a-1"))
由上可知呼叫方法時常可省略括號()
上例之result為一區域變數(變數名開頭亦可底線_)
ex2.
def tstr(_arg1, *rest) # 除第一引數外,其餘引數在array中,此例將這array命名為rest
if rest == [] # 判斷rest是否為空
_arg1 # 當呼叫方法時,只給一引數時的情況會執行至此,此例只是將第一引數return
else
"#{rest.join(', ')} and #{_arg1}" # 多引數時,rest不為空,會執行此行,陣列rest之元素間會以', '來合併, 其後return此合併字串
end
end
puts tstr("me") # 印出 me
puts tstr("me", "alice") # 印出 alice and me
puts tstr("me", "joe", "tom", "bob") # 印出 joe, tom, bob and me
puts tstr(*["me", "joe", "tom", "bob"]) # *[]表呼叫方法時將陣列中各元素展開後傳至各引數, 結果同上行
puts tstr(*(0..9).to_a) # 類似上行,將0至9展開至各引數,故印出 1, 2, 3, 4, 5, 6, 7, 8, 9 and 0
# (0..9) 為一Enumerable例,用其方法to_a => [0 1 2 3 4 5 6 7 8 9],故展開需前頭加*
# 此例亦可看出 * 的優先權 比 . 低
當變數前加$者,為全域變數 (ex. $CUSTOMER)
當變數前加@者,為Instance Variables (ex. @X, @_, @pt_3), 其通常用於儲存物件 (如後面OOP中之例)
當變數前加@@者,為Class Variables (ex. @@SN, @@tl)
然而當最前頭為大寫字母時,其為類別名,module名,或常數名(<自>實際上三名皆屬constant) (ex. String, MyClass, PI)
常數於方法內不可指定也不可更動,一般情況下更動會產生警告
若成常數參考,reference更動性同上,只要指向同一個referent,referent內容可經由其他參考隨意改變 (例如下面ex.2 中之常數參考C)
ex1. called-by-value 與 called-by-reference
# from: <ref.1>
def addOne(n)
n += 1
end
a = 1
puts addOne(a) # -> 2 # called-by-value
puts a # -> 1
def downer(string)
string.downcase! # 原字串string全轉成小寫,傳回值為nil;若不加'!'則傳回轉小寫之字串內容,但對原字串string並未作任何改變
end
a = "HELLO"
puts downer(a) # -> "hello" # called-by-reference
puts a # -> "hello"
ex2. <ref.1>與上例後面同理,變數自動以reference來處理string,故下面不同的變數都參考同一個string
a = b = C = "wxy" # 注意 C 為常數reference
b.concat("z") # => a, b, C 印出結果都為 wxyz (因三者都指向同一個referent)
puts a
puts b
puts C
# 上面三行可用 puts a, b, C 取代,結果相同
blocks:
{...} 或 do ... end 間為 code block. code blocks 可用來實作 callbacks
ex. (而yield處如同替換成其{...}之block,且一yield一block) (下行的方法threeTimes為 Iterator, 詳看下段)
def threeTimes # 此例無引數
yield # 可用yield召喚block
yield
yield
end
threeTimes { puts "The block's here" } # block 的括號 { .. } 可用 do .. end 取代
Iterators:
A Ruby iterator is simply a method that can invoke a block of code repeatedly.
An iterator is a method which accepts a block or a Proc object. (ref.1)
Iterators are used to produce user-defined control structures--especially loops.
註:Ruby的iterator與C++的有些不同,C++的iterator是物件,用來存取或操縱container中元素;而Ruby的iterator是方法,其內部可視以yield來實作(如 threeTimes例中的threeTimes)
對C++而言,An iterator is an object that "walks through" a collection....(C++ How To Prog. p.568);就應用上,OOP的iterator都有相近之處
<自> 注意! iterator引數 與 block引數 不見得相同 (依己設而定):
def 方法後若有(),則其內接的是iterator引數(預設其最後為block, 可用block_given?來判斷實際呼叫時是否有給block)
而 yield後()內接的是block引數
OOP:
ex. (from ref.1)以類別Person之成員age為鍵值,由小到大來排序(設已有一檔名為 ages 之文件檔,每行格式為 人名: 年齡 e.g. bert: 8)
class Person
attr_accessor :name, :age # 因底下def <=>...裡只斟酌age, 故此例就key而言實只需 attr_accessor :age 即可
def initialize(name, age)
@name = name
@age = age.to_i
end
def <=>(other)
@age <=> other.age
end
end
people = Array.new # 此行使 people 成一空array (值於下面給定) (注意對Ruby而言Array亦視為一物件)
File.foreach("ages") { |l| # <自> 此處 File.foreach 為 iterator , 其引數"ages"為文件檔檔名; 而 |l| 為block引數
people << Person.new($1, $2) if l =~ /(.*):\s+(\d+)/
}
# 上面執行完後,陣列people中每一元素皆為一Person物件
pa = people.sort # pa 為經排序後之新Array
# 注意! 上行並未改變原陣列people;若是要更動原陣列則將其換成 people.sort! 那麼將無另一新Array, pa就只是people的reference
# 由於 sort! 須更改原陣列,故在排序完成前people是frozen的;然而此例sort因建新Array而無需frozen。(此分別對後面的threads很重要)
以receiver來呼叫方法(method), receiver可以是物件,類別,或模組。
比如此例的people.sort的 物件people是receiver, 而 sort 為被reciver呼叫的方法 (the method to be invoked)
呼叫方法省略receiver名時,receiver會預設成self,即當前物件(類似C++的this)。
ex. block_given? 例; <ref.3>
def takeBlock(p1) # 此例 p1 為 "no block"
if block_given? # 偵測呼叫方法時是否有給 block 若有則至下行,否則到else
yield(p1) # 最後一行的iterator呼叫方法會執行到此 => s成為p1 亦即 "no block", 故s.sub(...) 使"no "去掉
else # <自> 注意上行相當於 return s.sub(...) 故return "block" 然而s實際上未變更 (類似 無! 的string.downcase)
p1
end
end
puts takeBlock("no block") # iterator引數 與 block引數 相同例; 沒block => 走else => 印出 no block
puts takeBlock("no block") { |s| s.sub(/no /, '') } # |s|表使s成為block引數
可將block化為 Proc物件,而當於 Proc物件 前頭加'&' 即為block
ex. (例中dbl 與 square 為 Proc物件;&dbl 與 &square 即成 blocks) <ref.1>
dbl = proc { |i| i+i }
square = proc { |i| i*i }
def mth1(&b) # mth1方法 亦為一iterator (用來invoke block &b)
print b.call(9), "\n" # 同理,&b為block 而 b為Proc物件; 故此行 以b為reciever 來呼叫方法call
end # &b 為block ,故mth1方法內若用 block_given?判斷 則會為true
mth1(&dbl) # -> 18 # 不加()亦可,但會有warning訊息
mth1(&square) # -> 81
def mth2
print yield(9), "\n" # yield()作用與方法mth1中的 b.call()的作用相同,由於此處引數亦為9,故底下mth2()結果亦相同
end
mth2(&dbl)
mth2(&square)
puts( (1..10).collect(&square).join(", ") ) # (1..10)為一Enumerable例,用其方法collect(block)可疊代block回傳結果成一array,
# 故後可用方法join()
# 而印出 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 (注意:collect 亦為 iterator)
一些現成的iterators:
ex. 6.upto(9) { |i| print i } # 印出6789 (upto() 為iterator)
ex. 3.times { |i| print i } # 印出012 (i.e. 執行三次block) (times 為iterator)
ex. ('w'..'z').each { |j| print j } # 印出wxyz # each is an iterator--a method that invokes a block of code repeatedly
ex. ('w'..'z').each { |j, k| print j } # k為欲傳入的第二引數, 由於此例只傳入第一引數j, 故k為 nil => 仍是印出wxyz
ex. <ref.3>
class TaxCalculator
def initialize(name, &block)
@name, @blocktst = name, block # 平行賦值(parallel assignment), 注意此處將 Instance變數@blocktst 指定為一 Proc物件(其物件名為block)
end
def getTax(amount,intr) # <自> 此成員方法內之accept同為物件成員的 @blocktst內之Proc物件
"#@name on #{amount} and interest #{intr} = #{ @blocktst.call(amount,intr) }"
end # 注意 Proc物件 於其物件建立時即載入而不是經由getTax呼引而來,故用if block_given? 判別會為false
end
tc = TaxCalculator.new("Sales tax") { |amt, i| amt * i }
puts tc.getTax(100, 0.075) # 印出 Sales tax on 100 and interest 0.075 = 7.5
puts tc.getTax(250, 0.08) # 印出 Sales tax on 250 and interest 0.08 = 20.0
上例中的getTax為方法,其receiver為物件tc。
省略方法的括號時,有些Ruby文件會稱其為命令(commands),其本質上仍為方法。比如上例第一個puts行亦可寫成 puts tc.getTax 100, 0.075
<ref.6>
You might be better off thinking of the block and the method as coroutines, which transfer control back and forth between themselves.
(<自> coroutines 字典中無此字,co- 有共同,協同之意, 此處應是表明應視block與method為相互協同合作之常式)
雖然Ruby號稱 "Everything is an object". 但精確地說只是 "Almost everything is an object in Ruby".
比如上例的方法getTax()不算物件,但Ruby有Method類別可使上例getTex()封裝成 方法物件(be wrapped in an object)。
如上例之後再接一行 mtd = tc.method(:getTax) 那麼 mtd 才是 方法物件(由上例tc的getTax()封裝而成)。此即為objectify(物件化)
ex. 接上例,下面兩行與上例最後一行結果相同
mtd = tc.method(:getTax)
puts mtd.call 250, 0.08
<自> 雖依照Ruby而言,例中 方法getTax 應亦為iterator (其accept同為物件成員的 @blocktst內之Proc物件)
然依其他OOP(如C++) 而言, mtd 方為iterator [因 方法物件mtd 才是物件, 而就C++等 iterator是物件(或變數)]
ex. <ref.5>
class A
def a
p "blah"
end
end
class Method # <自> 注意Ruby內建Method類別, 故這裡只算加一成員方法aa於一已存在類別Method
def aa
p "hi"
end
end
f = A.new
f.a # 印出 "blah"
f.method(:a).aa # <自> f.method(:a) 為方法物件,屬於Method類別,故能呼叫上面加入的方法aa, 因而印出"hi"
輸入:
gets為標準輸入,且亦會把輸入值存入$_
ARGF為Ruby提供之已存在物件,表input stream
ex. 印出標準輸入中包含"Test"之行
ARGF.each { |line| print line if line=~/Test/ }
General delimited input : %q :Single-quoted string; %Q, % :Double-quoted string; %w :Array of tokens %x :Shell command
%r :Regular expression pattern
ex. 利用 %w 建立一陣列後,其元素為字串,
下下行會輸出元素 |...|間之animal相當於yield之引數,each為iterator
a = %w{ ant bee cat dog elk } # 使a成Array, 如 a[2] 為cat
a.each { |animal| puts animal } # iterate over the contents
ex. (上例全皆印於同一行而以 -- 分隔)
[ 'ant', 'bee', 'cat', 'dog', 'elk' ].each do |animal|
print animal, " -- "
end
ex. Fibonacci <ref.3> <ref.4>
def fibUpTo(max) # 此亦為 iterator 的實作
i1, i2 = 1, 1
while i1 <= max
yield i1 # 由此處 i1 知 block 有一引數
i1, i2 = i2, i1+i2 # 注意!當平行賦值(parallel assignment),等號右邊一律為舊值
end
end
fibUpTo(100) { |f| print f, " " } # 印出 1 1 2 3 5 8 13 21 34 55 89
puts
b=[]
fibUpTo(30) { |val| b << val }
puts b.inspect # 印出 [1, 1, 2, 3, 5, 8, 13, 21]
# 底下六行效果相當於上面 b 三行code; (由此可看出 Proc物件 不只能當引數,亦可當return結果, 此與一般物件相同)
def into(anArray)
return proc { |val| anArray << val }
end
fibUpTo 30, &into(a = []) # 注意 1.陣列變數a 在此處首次出現,故不是區域變數 2. into方法 return一Proc物件, 故into前加&,以適用於 fibUpTo
puts a.inspect # 結果與b同,印出 [1, 1, 2, 3, 5, 8, 13, 21]
ex. (改自 <ref.4>)
class Song
def initialize(title, artist, serialnumber)
@title, @artistname, @sn = title, artist, serialnumber
end
def name
@title
end
end
class SongList
def initialize
@songs = Array.new
end
def append(aSong)
@songs.push(aSong)
self # 回傳當前物件指標; i.e. <=> return self <=> 相當於 C++ 的 return this;
end
def deleteFirst
@songs.shift
end
def deleteLast
@songs.pop
end
end
slist = SongList.new # slist 為一 SongList 物件, 此例其亦為一container
slist.
append(Song.new('title1', 'artist1', 1)). # 同一物件連續呼叫方法,可用.來依序連用方法 (好處為物件名只需打一次,看起來較簡潔)
append(Song.new('title3', 'artist3', 3)). # <自> 因於def 成員方法 內最後有用self回傳當前物件指標,故可如此作
append(Song.new('title2', 'artist2', 2)).
append(Song.new('title4', 'artist4', 4))
puts slist.inspect
puts slist.deleteLast.inspect # 'title4'... 之 Song 物件 從陣列中刪除,此行亦用 puts 印出這個被刪除的物件
class SongList
def [](key) # 對SongList類別加建一形式為 [key] 之方法
return @songs[key] if key.kind_of?(Integer) # key 為整數時處理,由於此行下面才能用如 slist[1] 來表其第二元素
return @songs.find { |aSong| key == aSong.name } # find 亦為 iterator,由於此行,下面方可用如 slist['title3'] 方式來存取元素
end
end
puts slist[1].inspect # 印出 'title3'... 這個 Song 物件
p slist[0].inspect # 印出 'title1'... 這 Song 物件
p slist['title3'].inspect # 此處與 puts slist[1].inspect 結果相同
lambda:
用法幾乎與proc同,甚有文件視兩者同義 (<ref.10> lambda .... Synonym for Kernel::proc)
然兩者還是有些許差異: lambda與proc在處理return時有些許不同,詳例可參考<ref.11>
自己習慣上return通常用於method中而 不在 block/proc/lambda 的裡面使用, 以避免混淆
ex.hello例
testh = lambda { puts "hello" }
testh.call
testa = testh
testa.call
Hash: (containers之一)
ex1. 定義hash例
instSection = {
'cello' => 'string',
'clarinet' => 'woodwind',
'trumpet' => 'brass',
'drum' => 'percussion',
'oboe' => 'woodwind',
}
p instSection['oboe'] # 印出 woodwind
p instSection['cello'] # 印出 string
# 上兩行 <=> p instSection['oboe']; p instSection['cello'] # ';'作用類似C, 但於Ruby中非必須
p instSection.length # => 5 因上面有5對
p instSection.inspect # 印出 {"oboe"=>"woodwind", ... } i.e.印出不見得會未按照上面宣告次序(依不同Ruby版本而定)
p instSection # 由於只顯示一般字元,不加inspect亦可
# 下行増一element (其為Element Assignment---Associates the value given by aValueObject with the key given by aKeyObject. <ref.7>)
instSection['violin'] = 'string'
p instSection.length # => 6 因増一elment故成6對
p instSection # 印出hash亦證明"violin"=>"string" 増入
p instSection.keys # hash中所有的keys所組成之array, 故顯示 ["cello", "clarinet", "trumpet", "drum", "oboe", "violin"]
p instSection.values # hash中所有的values所組成之array, 故顯示 ["string", "woodwind", "brass", "percussion", "woodwind", "string"]
# 下行減一element
p instSection.delete('oboe') # 刪一element, 回傳所刪除之值, 故此行會印出 "woodwind"
p instSection.length; p instSection # 顯示 長度成5; hash中變成少了 "oboe"=>"woodwind" 元素
p instSection['oboe'] # 印出 nil (表無此元素)
p instSection.sort # 以key排序後的結果以陣列顯示:[["cello", "string"],......, ["trumpet", "brass"], ["violin", "string"]]
p instSection # 顯示仍為未排序的hash, 表上行的sort並未影響原來的hash
p instSection.sort {|a,b| a[1]<=>b[1]} # 以value排序(hash中元素:[0]-key; [1]-value), 故顯示如下:
# [["trumpet", "brass"], ["drum", "percussion"], ["cello", "string"], ["violin", "string"], ["clarinet", "woodwind"]
# 由上故知 instSection.sort {|a,b| a[0]<=>b[0]} 與不加block的instSection.sort 兩者結果相同
# 下行淸除hash中所有elements
instSection.clear
p instSection.length; p instSection # 其亦顯示 長度成0; hash成空{}
p "empty hash" if instSection.empty? # 亦可用.empty?來判斷hash是否為空
ex.2 定義一hash, 其名為testh3, 當指定之key未定義時,將傳回預設值9
testh3 = Hash.new(9) # 9為我們指定的預設值
testh3['testkey1'] = 3
testh3['testkey5'] = 8
p testh3['testkey5'] # 印出 8
testh3.replace({ "testkey1" => 300, "testkey5" => 700 }) # 以replace來更改hash中已存在元素
p testh3['testkey5'] # 印出 700
p testh3 # 顯示 {"testkey1"=>300, "testkey5"=>700}
testh = { 'testk3' => 60, 'testkey5' => 900 }
testh3.update(testh) # 以上行所建立的hash資料來更新testh3
p testh3 # 顯示 {"testkey1"=>300, "testkey5"=>900, "testk3"=>60}
p testh3.has_key?('testkey1') # 可用 .has_key? 來判斷hash中是否有此key, 因上行之故,此會印出 true
p testh3['tstkey'] # 由於tstkey未定義,故印出9
p testh3.has_key?('tstkey') # 雖有預設值,然實未設定,故印出false
# 當方法的引數當中有hash時,允許省略大括號 { }, 故ex22程式中的兩行程式碼:
# testh = { 'testk3' => 60, 'testkey5' => 900 }
# testh3.update(testh)
# 可以用 testh3.update( 'testk3' => 60, 'testkey5' => 900 ) 來替代
fork 與 thread 的不同:
fork是利用OS去建立子程序,而 thread 是由ruby解譯器實作出來的
故fork較慢,且彼此不分享記憶體空間,其優點在於能暫停等待I/O完成再繼續進行,而thread不行
[ thread 亦會分享global, instance, (thread外部已有的)local var. ] ......(**)
當thread遇deadlock時整個程序都將停止;但thread較快又不會導致thrashing <自註>,且因是由解譯器實作,故即使在DOS下亦能用thread
注意: 每一thread在thread's block內定義的local variables範圍只限於thread自身
ex.
tds = []
for name in ['one', 'two' ] do # 此迴圈建立兩個threads, 分別命名為one與two
# tds << Thread.new { # 下行註解觀點來自於 <ref.8> :
# localName = name # 在thread裡面用到name的習慣不好,由於(**)之故, 存取於內可能因其他thread使變數意外被更動 故最好改成下行
tds << Thread.new(name) { |localName| # 陣列tds中的元素相當於thread物件,thread中內容為此block, 此例結果陣列tds會有二元素
a = 0
3.times do |i|
Thread.pass # Invokes the thread scheduler to pass execution to another thread.
a += i
print localName, ": ", a, "\n"
end
}
end
tds.each {|t| t.join } # join 方法確保thread完成前不會結束程式
# 印出結果如下: ( 與<ref.1>略有不同,thread特性使然 )
# two: 0 # i 一開始為0, 故 a+=i 後 a仍然為0
# one: 0
# one: 1 # i 為差分; a 為和分
# one: 3
# two: 1
# two: 3
#
# <ref.1>所顯示的結果:
# onetwo: : 00
#
# onetwo: : 11
#
# onetwo: : 33
# # <自> 此空行亦為thread中之'\n'所造成
一些thread方法:
Thread.current # 傳回當前正在跑的thread
Thread.list # 傳回所有的threads之list
(thead個例).status # 傳回此thread的狀態: sleep或run ; false表此thread已正常結束 ; nil 表此thread已意外結束(terminated with an exception)
<自註> thrashing 於此應可翻為顛簸
ref.
ref.1 file:///usr/share/doc/ruby*/FAQ.html
ref.2 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/index.html
ref.3 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/tut_methods.html
ref.4 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/tut_containers.html
ref.5 http://stackoverflow.com/questions/529315/how-do-i-get-the-method-object-for-a-method
ref.6 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/intro.html
ref.7 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/ref_c_hash.html
ref.8 file:///usr/share/doc/ruby*/ProgrammingRuby-0.4/tut_threads.html
ref.9 https://ruby-china.org/topics/19930
ref.10 file:/usr/share/doc/ruby-*/ProgrammingRuby-0.4/builtins.html
ref.11 http://railsfun.tw/t/method-block-yield-proc-lambda/110