本問答的目标讀者是不了解ruby語言、但有别的程式設計語言經驗的人。
ruby語言的代碼可讀性是很強的。本問答隻把一些文法特點、以及别的語言中可能沒有或不同的東西展現出來,目的在于讓有别的程式設計語言經驗的人能快速讀懂ruby代碼。
注意本問答講的是ruby語言本身(基于版本1.9),而不是ruby on rails,後者是ruby的一種dsl,語言面貌上和ruby有一定差異。
q:ruby最有特點的文法形式是什麼?
或許是方法後接代碼塊的大量使用,以下面這行代碼為例:
file.each_line("x") { |line| print line }
表示在file對象上調用each_line方法(以"x"為行的分隔符),該方法的功能是依次得到每一行,傳遞給後面的代碼塊,代碼塊把傳來的行指派給 line變量,然後在代碼塊裡對line進行處理,處理完畢則從代碼塊傳回each_line方法,再由它得到下一行,再一次傳遞給代碼塊。——像 each_line這樣的方法,ruby中稱之為疊代器方法(iterator)。
又比如這個例子:
open('test.txt') { |f| line_array = f.readlines }
用open方法打開test.txt檔案,生成了一個file類的執行個體對象,并把這個對象傳遞給後面的代碼塊,指派給變量f,然後代碼塊裡對f進行操作,操作完畢後傳回open方法,open方法再把f關閉,是以這一行代碼相當于如下三行:
f = open('test.txt')
line_array = f.readlines
f.close
ruby風格寫法的好處:一行完成,邏輯緊湊;自動關閉檔案,防止忘了f.close;
目前scope少建立一個變量名f,代碼塊關閉後,f就消失了
一個ruby風格的完整指令就是由對象、方法(包含參數)、代碼塊(包含參數)構成的。有的方法可以不接代碼塊。
q:我看到有些代碼和上面提到的寫法不太像,是怎麼回事?
有些dsl看起來和ruby語言本身不大像,但其實文法格局是一樣的,隻是通過一些設定僞裝成别的風格。
大緻有四點導緻這種情況:
1、隐性地調用方法,讓方法看起來像函數或關鍵詞;
ruby中沒有函數,全都是方法。方法就得在某個對象上調用,但是這個對象可以隐藏
方法不在某個對象上顯式調用,那它就一定是在self所指的對象上調用
如open(file)實際是self.open(file),不過open是私有方法,不能顯式寫出對象
2、省略了括起參數的括号;
如open('test.txt','w')可以寫成open 'test.txt', 'w'
3、代碼塊的{...}改成do...end;
open 'test.txt' do |line|
end
就相當于open('test.txt') {|line| }
這是很常見的,{...}和do...end隻在優先級上有一些不同,一般都可互換
通常的風格是:代碼塊裡的代碼若隻有一行,則用{},若有多行,則用do...end
這隻是風格管理,實際上即使是多行代碼,你也可以用{}括起來
4、省略作為方法參數的哈希(散列)字面量的花括号。
很多方法喜歡拿一個哈希做參數,如果哈希是方法調用的最後一個參數,則花括号可省略
task :name => :test 相當于 task({:name => :test})
如下一段代碼:
htmlform.generate(stdout) do
comment "this is a simple html form"
form :name => "registration",
:action => "http://www.example.com/register.cgi" do
content "name:"
input :name => "name"
content "address:"
textarea :name => "address", :rows=>6, :cols=>40 do
"please enter your mailing address here"
如果寫“全”來,就相當于這樣:
htmlform.generate(stdout) {
self.comment("this is a simple html form")
self.form({:name => "registration",
:action => "http://www.example.com/register.cgi"}) {
self.content("name:")
self.input({:name => "name"})
self.content("address:")
self.textarea({:name => "address", :rows=>6, :cols=>40}) {
}
q:我聽說ruby分1.8和1.9兩個版本,二者的文法有什麼不同?
目前ruby流行1.8.x和1.9.x兩個主要版本。1.9.x版使用新的解釋器yarv,比1.8.x速度快;重寫了string類,增加了encoding類,從此可以完善處理多位元組字元;殺手應用ror也一早支援了1.9.x版;還有一些文法上的改進。
本問答以1.9版文法為準,兩個版本有一些文法差别,略提幾條差別的線索:
§ 如果有require 'rubygems'的,為1.8版;
§ 如果看見$kcode的,為1.8版;
§ 哈希的鍵值對之間可以用逗号(而非=>)分隔的,為1.8版;
§ if condition:這種和python一樣的寫法(條件之後用冒号),為1.8版
§ {|a,b;x,y| }的寫法(用分号隔開兩類參數),一定是1.9版
q:有些寫法感覺很奇怪,比如5.times { puts "ruby! " },怎麼了解?
這種寫法其實很酷。ruby中一切值都是對象,包括整數。integer類有執行個體方法times,依次傳遞0到n-1給後面的代碼塊,相當于運作n次後接的代碼塊。
這一代碼就是在5上調用方法times
q:ruby代碼中很少看見for...in/foreach的寫法,為什麼?
相比for i in xx的循環方式,ruby的風格是更喜歡用xx.each {|i| }這種調用疊代器方法的方式。
對于數組for elem in array,疊代器方法寫作array.each { |elem| }
對于讀檔案的每行for line in file,疊代器方法寫作file.each { |line| }
相比for...in方式,疊代器方法更快,更靈活,更強大,比如對于一個file對象
file.each_line { |line| } # 每次處理一行
file.each(' ') { |para| } # 每次處理一段
file.each_char { |char| } # 每次處理一個字元
file.each_byte { |byte| } # 每次處理一個位元組
file.each_line.with_index(1) { |line, lineno| }
# 傳遞行時,還把索引值(在這裡就是行号)也傳遞給代碼塊
這些都不是for...in擅長的
至于for(i=0; i<10; i++)這種寫法,ruby當然是寫成9.times {|i| }這種形式了
q:benchmark::measure、benchmark.measure兩種寫法有什麼差別?
表示方法調用,用::還是用.,完全是一樣的,指向的是同一個方法,差別隻在于作者怎麼看待measure這個方法。
符号::一般是用來分隔嵌套的子產品、類、常量的,寫成benchmark::measure,像是表明measure是在benchmark這個子產品中定義的函數,benchmark隻是它的容器;而寫成benchmark.measure,像是在說measure是對benchmark這個對象進行操作。
從内部實作上說,ruby中隻有方法,沒有函數;但從内涵上說,benchmark::measure的意義更确切,是以有人願意這樣寫。
q:array#each是什麼意思?
array#each的寫法并不用在實際代碼中,而是文檔中約定俗成的一種寫法,表示array類中定義的執行個體方法:
array = array.new
array.each {} # array#each指的就是這裡的each,是array類的執行個體所用
q:::foobar是什麼意思?
其中的::是分隔嵌套子產品、類、一般常量的分隔符,::前面沒有東西,表示到global scope去找這個常量。
q:經常聽到ruby“一切皆對象”的說法,怎麼了解?
嚴格來說,應該是ruby中一切可獨立的合法語言片段都是表達式,表達式都要傳回一個值,而一切值在ruby中都是對象。
比如true false nil也是對象,分别是trueclass、falseclass、nilclass的執行個體
比如if結構可獨立,是以是表達式,是以要傳回值,這個值總是一個對象,是以if結構可以指派給一個變量:
a = if x > y
x + 4
else
y * 2
比如子產品、類也是對象,string、array等類是class類的執行個體對象,class作為對象也是class這個類的執行個體
q:$foo、@bar和@@baz裡的$、@、@@是什麼意思?
ruby沒有global、local之類關鍵詞設定變量可見範圍,而是采用變量自帶标記的方式
§ 以小寫字母或_開頭的變量是局部變量
§以$開頭的是全局變量
§以@開頭的是每個對象自身的執行個體變量
§以@@開頭的是同類對象都可通路的類變量
class a
def initialize(var)
@s=var
@@ss=var
def to_s
"s=#@s,ss=#@@ss"
a=a.new("1")
puts a.to_s
b=a.new("2")
puts b.to_s
$ ruby a.rb
s=1,ss=1
s=2,ss=2
s=1,ss=2
@@變量在一個執行個體裡變了,所有執行個體都會變
q:大寫字母開頭的名稱代表什麼?
大寫字母開頭的是常量,包括子產品名、類名都以大寫字母開頭,如array、enumerable都是常量。常量的意思是這個名稱和某個對象的聯系是固定了的,但不表示那個對象不可更改,如:
foobar = [ 1, 2, 3 ]
foobar[2] = 99
print foobar # [1, 2, 99]
要想常量所指的對象不可修改,那應該 foobar = [ 1, 2, 3 ].freeze
q:stdin、stdout、stderr和$stdin、$stdout、$stderr有什麼差別?
stdin這一類以大寫字母開頭,是常量;$stdin這一類以$開頭,是全局變量。
常量不可變,stdout總指向螢幕顯示(除非運作ruby時在指令行設定>out 2>err之類),變量可變,是以$stdin可以替換成别的io/file對象。
全局的輸出方法,如print puts等,總是向$stdout輸出,而非向stdout輸出,如:
print 1 # 這時$stdout和stdout是一緻的,輸出到螢幕
$stdout = open('output_file','w')
print 2 # 這時輸出到output_file了
$stdout = stdout
print 3 # 又輸出到螢幕了
q:argv = ["a","b","c"]的寫法為什麼會報錯?
perl裡寫@argv = qw(a b c)和python裡寫sys.argv = ["a","b","c"]都是ok的
ruby這麼寫報錯的原因其實也很簡單,因為argv以大寫字母開頭,是以它是個常量,ruby解析器一啟動,argv常量就設定好了,再用等号指派的方式,表示你想改變這個常量跟某個對象之間的聯系,對常量來說這是不行的
是以在ruby裡得寫成argv.replace ["a","b","c"],replace是array類的一個執行個體方法,表示不改變對象,隻替換内容
q:表示"什麼都沒有",用什麼?null undef nil?
用nil。perl裡用undef表示什麼也沒有,但在ruby裡,undef是取消方法定義的關鍵詞。
q:在條件判斷中,哪些算是真值,哪些算是假值?
在ruby裡false、nil表示假,其他所有對象都為真,包括0、""、[]等
q:有些方法名稱裡有?和!,是什麼意思?比如nil?和strip!
方法名的最後可以有一個?或!,這隻是一種命名習慣,讓方法的涵義看起來更好懂
加?的方法,通常都是傳回true/false的
像nil?的功能是檢測它的對象是否是nil,obj.nil?感覺就是在問obj是nil嗎?
又如file.exist?("test.txt")感覺就是在問"test.txt"存在嗎?
加!的方法,總有一個對應的不加!的方法,通常不加!的生成新對象,而加!的是對本對象進行修改,如string類的strip和strip!:
str = " abc "
new_str = str.strip # 不改動原str對象,而是新生成一個字元串,删去了前後空白符
str.strip! # 直接在原str對象上改動,删去str的前後空白符
?和!的使用并沒有強制性的規定,你要定義一個傳回true/false的方法,不加?也可以,或者某個以?結尾的方法,不傳回true/false也可,!也是。總之?和!就是一般字元,不具有限定功能,隻是增強可讀性的
q:我看到有def []=(name, value)這樣的寫法,什麼意思?難道定義了"[]="這個方法?
bingo![]=确實是一個方法。
ruby語言中很多(但不是全部)操作符實際上都是方法,比如像+ - * / % << == ** 等都是。既然是方法,就可以在自己的類裡定義。
str[2..4] = "xyz"其實相當于str.[]=(2..4,"xyz"),也就是在str對象上調用[]=方法,傳遞兩個參數2..4和"xyz"
q:我看到[1,2,3,4].from(2)的寫法,但是在官方api裡沒有看到from這個方法啊?
說明from這個方法是第三方子產品加到array類裡去的。
ruby的類是開放的,即使是核心的類,你也可以随意添加方法、undef方法、增加别名等等
比如對于核心的string類:
class string
def to_file
file.open(self)
然後我就可以"filename.txt".to_file得到一個file對象了
q:string#length方法和string#size方法有沒有差別?
沒有差別,這兩個方法完全一樣,是同義詞。
ruby的标準api裡有不少方法的用法是完全相同的,作者的考慮可能是讓不同來源的程式員都有親近感,或者在不同的上下文使用,更接近自然語言;我是覺得這種備援不太必要,但對常見的同義詞方法,還是應知道一點。
如 string類的length和size同義,each_line和lines同義,each_char和chars同義,each_byte和 bytes同義;file類的each和each_line以及lines同義;hash類的each和each_pair同義
q:file#gets方法和file#readline方法有沒有差別?
有差別,這兩個方法都是讀取檔案下一行,但到檔案末尾eof時,再gets會傳回nil,而再readline會觸發eoferror異常。
ruby标準api裡也有一些這種大體相同,但有細微差别的方法。
哪些方法是同義詞,完全一樣,哪些是近義,類似但有差別,确實給學習造成了一定的困難,隻能是多查。
q::encoding :xyz是什麼意思?
這是symbol類執行個體的字面量表示法,用個冒号放在字元之前,初學ruby者可能容易把這個誤認為是變量名。也可以寫作:"encoding"這樣,看起來就像個特殊的字元串,而不是變量名了,但通常是省略引号的。
q:symbol類執行個體有什麼用途?
ruby中的字元串是可變的,symbol對象是不可變的,可以把symbol對象了解為一種名稱,一種标簽。因為symbol對象不可變,它用在哈希裡當鍵比用字元串更有效率:
person = { :name => 'joey', :age => 21, :rank => 5 } # 就比
person = { 'name' => 'joey', 'age' => 21, 'rank' => 5 } # 更加ruby
另外,在一些方法中,經常用symbol做參數,指代方法等的名稱,如:
str = "abc|def|ghi"
array = str.send(:split, "|") # 向str發送消息,相當于str.split("|")
q:哈希字面量的寫法是怎樣的?
用花括号,鍵和值用=>分隔開,如:
hash = { :key1 => "val1", :key2 => "val2", :key3 => "val3" }
perl衆注意,這個=>是從perl來的,但perl裡=>跟逗号完全一樣,但在ruby裡,=>跟逗号是不同的
q:哈希的鍵是有序的?
1.9版本的哈希,鍵确實是有序的,你{:a => 1, :b => 2, :c => 3}用each疊代時,總是首先出:a,其次出:b,然後出:c
但沒看到官方保證後續版本一定也是這樣,是以這就像雜牌充電器,你照樣用來充電也沒問題,但官方不給保修
q:不帶花括号的寫法,比如:encoding => 'gbk'是什麼意思?
還是一個hash,隻是省略了花括号,這種寫法常用在充當方法調用的最後一個參數時:
file = file.open('test.txt', :encoding = > 'gbk') # 相當于
file = file.open('test.txt', {:encoding = > 'gbk'}) # 第二個參數是個哈希
open方法内部接了這個哈希,opt = {:encoding = > 'gbk'},就可通過opt[:encoding]獲得檔案編碼值,進行下一步處理
一些dsl很喜歡用這種方式來傳遞參數,比如:
class htmlform < xmlgrammar
element :form, :action => req,
:method => "get",
:enctype => "application/x-www-form-urlencoded",
:name => opt
element :input, :type => "text", :name => opt, :value => opt,
:maxlength => opt, :size => opt, :src => opt,
:checked => bool, :disabled => bool, :readonly => bool
element :textarea, :rows => req, :cols => req, :name => opt,
:disabled => bool, :readonly => bool
element :button, :name => opt, :value => opt,
:type => "submit", :disabled => opt
看起來一個element帶了好多參數,實際上呢,給它的隻是兩個參數
相當于:
element(:button,{:name=>opt, :value=>opt, :type=>"submit", :disabled=>opt})
參數就是一個:button(symbol),一個hash
q:{a:1,b:2,c:3}也是哈希字面量麼?是不是和python的涵義一樣?
不一樣。python要這樣寫,a、b、c是三個變量,而在ruby中(隻限1.9版),這其實是
{ :a => 1, :b => 2, :c => 3 }的另一種寫法,a、b、c是三個symbol
為什麼要引進這種寫法呢?也是為了哈希做方法參數時好看
file.open('test.txt', :encoding = > 'gbk') # 就可以寫成
file.open('test.txt', encoding: 'gbk')
上面的例子,寫成這樣也可以:
element :button, name: opt, value: opt, type: "submit", disabled: opt
q:1..5、"a"..."z"是什麼意思?
是一個range對象的字面量表示法。1..5表示從1到5的範圍,包含5(2個點包含尾端);
"a"..."z"表示從"a"到"z"的範圍,不含"z"(3個點不含尾端)
這種寫法是從perl繼承的,但是在perl裡1..5是一個清單,要寫成1..得記憶體爆炸了,但在ruby裡,一個range對象隻記錄首端的1和尾端的,這麼寫沒問題
range對象可以疊代操作:(1..6).each {|i| print i}
又如str[1..5]就是以一個range對象1..5做參數,表示第2個到第6個字元
q:=>還有什麼用途?
除了在hash裡分隔鍵和值外,還用在異常處理文法裡:
begin # 異常處理文法
# blah blah
rescue argumenterror => e # 若上面代碼觸發argumenterror,則指派給e
還可以寫成:rescue => e # 任何出現的異常都指派給e
q:這一句什麼意思?m = a / b rescue 0
這是一種快捷的異常處理文法,a rescue b,若表達式a觸發異常,則對b表達式求值并傳回
m = a / b rescue 0 # 假如b是0,出現除0錯誤,那麼右邊的0作為傳回值
$stdout = open(output_file,'w') rescue stdout
# 若output_file沒有寫權限,出錯,則傳回stdout給$stdout
q:puts、p、print有什麼差別?似乎ruby衆不喜歡用print?
puts列印一個字元串,如果字元串末尾沒有"\n"則添加換行,如果有則不添加
puts "abc" # 實際列印的是"abc\n"
puts "abc\n" # 實際列印的還是"abc\n",而非"abc\n\n"
ruby中用puts的情況應該比print多吧
p 則是列印供程式員調試的字元串,會把不在ascii範圍的字元轉義
print "上下" # 列印出來:上下
p "上下" # 列印出來:"\上\下" 引号也是列印出來的内容
實際上 p obj相當于print obj.inspect,而obj.inspect相當于python裡的repr(obj)
q:字元串裡的#{}是什麼意思?比如"a + b = #{ a + b }"
雙引号内的表達式内插,如
a = b = 3
puts "a + b = #{ a + b }" # "a + b = 6"
q:"%s = %f" % ["pi", math::pi]是什麼意思?
string類的%方法,調用在一個格式字元串之上,相當于printf出來新的字元串
q:string << "a"、string << 65,array << "a",file << "a"中的<<各代表什麼意思?
str << "a"表示将字元"a"加到str字元串尾端
str << 65表示将碼點65所代表的字元(這裡也是"a")加到str字元串尾端
ruby中的字元串是可變的,用str << "a"的方式,是在str這個對象上直接修改,比str = str + "a"快,邏輯也清晰
array << "a"表示将"a"追加到array末尾,作為最後一個元素
file << ""表示列印到file對象,相當于file.print "a"
對于整數來說,<<則是位移方法。對象不同,<<的涵義也不同,很好的duck typing例證
q:<<eof是什麼意思?
這叫做here document,perl衆懂的。
<<後面緊跟一個标記,從下一行開始到出現标記的行為止,其中字元串都存入這個here document,例如:
str1 = <<hd1.upcase + <<hd2.downcase
aaaaaaa
bbbbbbb
hd1
xxxxxxx
yyyyyyy
hd2
p str1 # "aaaaaaa\nbbbbbbb\nxxxxxxx\nyyyyyyy\n"
這種代碼相當于下面:
str2 = "aaaaaaa
".upcase + "xxxxxxx
".downcase
又如:
eval_r(<<cmds)
print a + b
cmds # 上面黃色的字不是代碼,而是字元串
q:`ls`是什麼意思?
在作業系統中運作``裡的指令,如在windows下運作dir指令,傳回dir出現的資訊
`dir`.each_line.select { |line| line.start_with? '2011/09/08' }
# dir傳回的資訊,挑選每一行以"2011/09/08"開頭的
q:/[rr]uby/是什麼意思?
正規表達式的字面量表示法,和perl的正規表達式簡寫形式一樣。
q:%w %q %q %r是什麼意思?
從perl繼承并加以變化的文法糖。
%w後接分界符(可以是%w{} %w() %w[] %w//等等),裡面的字元串以空白符分開,這些字元串各自作為數組的元素
%w( abc 123 def 456) # 相當于 [ 'abc', '123', 'def', '456']
%q相當于單引号,隻是中間出現\'不轉義,主要用在字元串内有很多'和"時
%q{abc'def'} # 相當于 'abc\'def\''
%q相當于雙引号,主要也是用在字元串裡有很多'和",隻是裡面可以内插表達式
bar = "foo"
%q/foo"#{bar}"/ # => "foo\"foo\""
單獨的%//也代表雙引号,是%q//的簡寫
%r相當于//,用于建立正規表達式
q:$` $& $' $1 $2是什麼意思?
當一個字元串和正規表達式比對時,字元串中比對正規表達式的那部分存入$&,之前的部分存入$`,之後的部分存入$'
如果正規表達式裡有捕獲括号,則第一個捕獲的子串存入$1,第二個存入$2,依次類推
這種标點符号式的變量是直接從perl中繼承過來的,确實很醜陋,很影響代碼可讀性,現在ruby對這些符号變量的使用是depreciated的
要想涵義清楚點,要麼可以導入english.rb子產品
require 'english'
$match # 相當于 $&
$prematch # 相當于 $`
$postmatch # 相當于 $'
或者動用regexp.last_match
regexp.last_match.to_s # 相當于 $&
regexp.last_match.pre_match # 相當于 $`
regexp.last_match.post_match # 相當于 $'
類似的變量還有一些如$/ $* $.等,具體涵義可查相應的文檔,自己寫最好是不要用了
q:=~是什麼意思?
從perl繼承的,拿一個字元串和一個正規表達式進行比對,傳回第一次比對的位置
和perl不同的是,在ruby中string =~ regexp和regexp =~ string兩種寫法都可以
q:<=>是什麼意思?
a <=> b傳回-1 / 0 / 1或nil,左小右大則傳回-1,左大右小則傳回1,左右相等則傳回0
比較沒意義(不是同類對象比較)則傳回nil,如123 <=> "abc"
q:===是什麼意思?
很多類定義了自己的===方法,涵義各不相同,例如:
§ range類的===是測試參數是某個range的成員,如(1..10) === 5傳回真
§ string類的===和==意義相同,都是測試兩個字元串的值是否相等
§ regexp類的===和=~意義相同,測試是否比對
§ class類的===是測試參數是否是類的成員
string、array、integer這些類本身也是對象,是class類的執行個體,是以下面都傳回真
string === "abc"
array === [1,2,3]
integer === 123
有的語言成分依賴===,但沒有顯式地使用===,最主要的是case...when結構(見下一問)
另外arra#grep方法也依賴===
a = [1, "abc", :sss, 4.6, "def", :bar ]
p a.grep(string) # ["abc", "def"]
array#grep方法,是對數組的每個元素elem,用方法參數arg === elem為真的則保留
這裡就表示挑出string === elem為真的elem,也就是類為string的對象
q:case...when結構的用法是什麼?
最常見的case...when結構的用法如下:
generation = case birthyear
when 1946..1963 then "baby boomer"
when 1964..1976 then "generation x"
when 1978..2000 then "generation y"
else nil
case後面的表達式隻求值一次,得到的值依次去被when後的對象用===比較,哪一次為真,則傳回相應的值,此例中就是以1946..196、1964..1976、1978..2000三個range對象去===birthyear
q:指派操作、方法定義和方法調用裡的*是什麼意思?
§ 指派操作比如:
x, *y = 1, 2, 3 # x == 1; y == [2,3]
*x, y = 1, 2, 3 # x == [1,2]; y == 3
x, *y, z = 1, 2 # x == 1; y == []; z == 2
*這标記的作用好像是在說“你們先拿,剩下全歸我”
在平行指派中,左邊隻可以有一個*,但是位置可以任意(1.8版本隻能在最後)
别的變量得到各自的值以後,剩下的全歸*,變成一個數組(數組有可能為空)
在方法定義中的情況一樣,對于多參數而言,也是“你們先拿,剩下全歸我”
def foo(a,b,*x)
# 表示調用foo時,至少要兩個參數,指派給a和b,剩下全給x,x是一個數組
def bar(*args) # 表示可以有任意數量的參數
方法調用中*的作用和定義相反,是放在一個數組之前,把其元素拆成參數
args = [1,2,3]
bar(args) # 傳遞給bar的是一個參數,數組[1,2,3]
bar(*args) # 傳遞給bar的是3個參數,1,2,3
q:代碼塊是對象嗎?
不是。代碼塊不能獨立存在,單獨寫{|n| n * 2 },是會報錯的。
但是代碼塊可以對象化,對象化後的代碼塊是proc類的執行個體。
将代碼塊對象化的寫法主要有兩種:
proc1 = proc.new {|n| n * 2 }
proc2 = lambda {|n| n *2 }
兩種寫法生成的proc對象有細微差别,break和return等的行為有異。
q:為什麼這樣寫不行:foo = lambda {|n| n * 2 }; foo(5)
python類似的寫法foo = lambda n: n * 2可行,但在ruby中,foo得到的是一個對象,而非函數,不能在對象上加參數,當成方法用。
是以得寫成foo.call(5),表示在foo對象上調用call方法,傳遞參數5
q:代碼塊{ |a; x| }裡設定參數的部分,分号後面的變量是什麼意思?
(1.8版本不可用)分号前面的a,用來接受方法傳遞過來的參數,自然是block-local的
分号後面的x,則是設定别的block-local變量,在代碼塊中修改x,不會影響代碼塊外可能存在的x,如:
x = a = 9
3.times do |a; x|
x = a * 2
print [ a, x ] # 依次列印[0, 0][1, 2][2, 4]
print [ a, x ] # 仍然是[9, 9]
3.times do |a|
print [ a, x ]
print [ a, x ] # 變成[9, 4]了
q:->是什麼意思?比如 ->(x,y=10) { print x*y }
是1.9版本新加的lambda文法,把原本在代碼塊中的參數移到前面去了
->(x,y) { print x * y } # 相當于 lambda { |x,y| print x * y }
有一個好處是參數可以設定預設值,->(x,y=10) {}
有争議的地方是和别的語言中的->的涵義完全不同
q:不接代碼塊的each方法是什麼意思?比如e = [ 1, 2, 3, 4, 5 ].each
很多方法會根據是否後接block而運作不同的功能,傳回不同的值。
比如這個each方法,如果後接代碼塊,則會把數組中的每個元素依次傳遞給代碼塊,讓它運作某些指令,而如果each方法未後接代碼塊,則傳回一個enumerator執行個體
很多一般後接代碼塊的疊代方法若不加block,都傳回enumerator執行個體,如file類和string類的each_line、each_char等(這個不是文法規定,而是方法内部就這麼處理的,具體參見官方api文檔)
q:enumerator類的作用是什麼?
可以說把疊代操作這個動作抽象化為對象。一般的用途包括:
1、多個對象同時并行疊代,如:
e1 = [ 1, 2, 3, 4, 5].each
e2 = [ 99, 98, 97, 96 ,95].each
new_array = []
loop {
new_array << e1.next
new_array << e2.next
p new_array # [1, 99, 2, 98, 3, 97, 4, 96, 5, 95]
2、給原來的疊代方法增加新的功能,如enumerator類有一個方法with_index:
e1 = string.each_char
e2 = array.each
e1.with_index(1) {|char,index| } # 參數1表示從1開始計數,無參數則從0開始
e2.with_index {|elem,index| }
這樣傳遞給後面block的,就不僅包括原來的每個字元、每個元素,連帶把對應的索引數也傳了
3、無限循環。可以定義一個帶yield的方法,轉換為enumerator對象,實作無限循環
def foo
i = 0; loop { i = i + 3; yield i }
#foo {|i| print i} # 别運作,這是死循環
e = to_enum(:foo)
# to_enum的作用是把:foo這個symbol所指代的foo方法轉為enumerator對象
1234.times { e.next } # 讓它疊代1234次,可以無限疊代
p e.next # 3705
q:yield是幹什麼用的?
方法定義中把控制權交給代碼塊,是用來實作each這一類疊代方法的直接途徑:
def from_to_by(from, to, by)
x = from
while x <= to
yield x
x += by
from_to_by(3,26,4) {|x| print x, " " } # 3 7 11 15 19 23
自己的疊代方法就這樣定義好了
q:iterator?是什麼意思?
現在一般寫成block_given?,這就好了解一點了吧。
在方法定義中用來判斷這個方法在調用時是否後接代碼塊
if block_given?
這樣一個方法就可以根據是否後接block而做不同的事了
iterator? 是block_given?的同義詞,字面意思是問目前方法是否用作iterator,用作iterator意味着必接block,像each這樣的方法可以說是iterator方法,但不是所有後接代碼塊的都是iterator,如file.open(file) {|f| },這個時候說open是iterator就不太妥當,而說block_given?總是恰當的
q:方法定義和方法調用裡的&是什麼意思?比如def foo(a,b,&blk)
在方法定義中,&連帶後面的變量名必須是最後一個,表示把方法調用時的代碼塊轉換為proc執行個體
def foo(a,b,&blk)
foo(x,y) {|n| n + 1}
# blk的值就相當于proc.new {|n| n + 1}了
如果沒帶代碼塊,不會報錯,隻是blk的值為nil了
用這種方式最大的好處是:blk是一個對象,可以傳遞給别的方法
而 blk.call(x,y) 相當于 yield x,y,blk.nil? 也可以達到和 block_given? 同樣的目的,檢測是否接了代碼塊
q:array.map(&:upcase)是什麼意思?
這種寫法有點晦澀。上面已經說了&的涵義,&要求它後面的對象是個proc執行個體,假如不是,則調用它的to_proc方法生成一個proc
而symbol類正好有一個執行個體方法to_proc
:a_method.to_proc 變成的代碼塊相當于:
proc.new {|obj, *args| obj.send(:a_method,*args) }
array.map(&:upcase)的了解過程是:
一變:array.map {|obj, *args| obj.send(:upcase, *args) }
二變:array.map {|obj, *args| obj.upcase(*args)
三變:array.map {|obj| obj.upcase } # upcase這個方法不需參數
q:class foo < bar是什麼意思?
表示建立新的類foo,是bar的子類,ruby用<形象地表示foo和bar之間的關系
<也可以用來快速檢測兩個類或子產品之間的關系,如string類是object類的子類,則
string < object # true
object > string # true
q:class << obj是什麼意思?
打開obj的singleton類,通常用來定義singleton方法
比如str是個字元串,也就是個string類的執行個體,string類的執行個體方法str都可以調用
我們又可以定義隻有str這個對象才能調用的方法,這樣的方法就是str的singleton方法
class << str
def foo # 這個foo方法隻能被str調用,不能被string類的其他執行個體調用
q:def obj.method是什麼意思?
也是定義obj的singleton方法,直接定義,沒有打開singleton class
定義所謂類方法,也是這種方式:
def string.foo
實際上類也是對象,所謂類方法,也就是類對象的singleton方法
q:定義方法為什麼不用self作為第一個參數?
ruby是純oop語言,沒有函數,全是方法,是以省了傳遞self
def foo(x,y,z)
str = "abc"
str.foo(1,2,3)
# 方法定義時的參數,和方法調用時的參數,看起來就一緻了
q:ruby中的self是變量麼?
不是變量,而是個關鍵詞。在任何環境self都指向一個對象
module foo
p self # self 為 foo
class bar
p self # self 為 foo::bar
def baz
p self # self 為調用此方法的對象
q:sub、gsub方法是什麼作用?
string類的sub方法作用是替換子串并生成新字元串,gsub是替換所有比對子串
對應的sub!和gsub!是在原字元串上進行修改,不生成新對象
string#replace并不是替換子串的作用,而是把字元串整個替換成别的值,但本身對象不變
str.replace "def"
# str有相同的object_id,但内容由"abc"替換為"def"了
q:string類的scan方法是什麼作用?
從一個字元串中抽取出所有比對的子串
str = "a1b2c3d4e5"
p str.scan(/\d/) # ["1", "2", "3", "4", "5"]
可以後接代碼塊依次處理每個比對子串
str.scan(/\d/) {|c| print c}
q:array類的&和|表示什麼意思?
&表示傳回兩個數組的交集,去除重複元素
[ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
|表示傳回兩個數組的并集,去除重複元素
[ "a", "b", "c" ] | [ "c", "d", "a" ] #=> [ "a", "b", "c", "d" ]