不懂Ruby的程序員,如何快速讀懂Ruby代碼


本問答的目標讀者是不了解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"

  end

  end

  end

  如果寫“全”來,就相當於這樣:

  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}) {

  "Please enter your mailing address here"

  }

  }

  }

  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

  end

  比如模塊、類也是對象,String、Array等類是Class類的實例對象,Class作為對象也是Class這個類的實例

  Q:$foo、@bar和@@baz里的$、@、@@是什么意思?

  Ruby沒有global、local之類關鍵詞設定變量可見范圍,而是采用變量自帶標記的方式

  § 以小寫字母或_開頭的變量是局部變量

  §以$開頭的是全局變量

  §以@開頭的是每個對象自身的實例變量

  §以@@開頭的是同類對象都可訪問的類變量

  class A

  def initialize(var)

  @s=var

  @@ss=var

  end

  def to_s

  "s=#@s,ss=#@@ss"

  end

  end

  a=A.new("1")

  puts a.to_s

  b=A.new("2")

  puts b.to_s

  puts a.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)

  end

  end

  然后我就可以"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

  end

  看起來一個element帶了好多參數,實際上呢,給它的只是兩個參數

  element :button, :name => OPT, :value => OPT,

  :type => "submit", :disabled => OPT

  相當於:

  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

  # blah blah

  end

  還可以寫成: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

  bbbbbbb

  ".upcase + "XXXXXXX

  YYYYYYY

  ".downcase

  又如:

  eval_r(<<cmds)

  a = b = 3

  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

  end

  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]

  end

  print [ a, x ] # 仍然是[9, 9]

  x = a = 9

  3.times do |a|

  x = a * 2

  print [ a, x ]

  end

  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 }

  end

  #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

  end

  end

  from_to_by(3,26,4) {|x| print x, " " } # 3 7 11 15 19 23

  自己的迭代方法就這樣定義好了

  Q:iterator?是什么意思?

  現在一般寫成block_given?,這就好理解一點了吧。

  在方法定義中用來判斷這個方法在調用時是否后接代碼塊

  def foo

  if block_given?

  # blah blah

  else

  # blah blah

  end

  end

  這樣一個方法就可以根據是否后接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)

  # blah blah

  end

  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類的其他實例調用

  # blah blah

  end

  end

  Q:def obj.method是什么意思?

  也是定義obj的singleton方法,直接定義,沒有打開singleton class

  定義所謂類方法,也是這種方式:

  def String.foo

  # blah blah

  end

  實際上類也是對象,所謂類方法,也就是類對象的singleton方法

  Q:定義方法為什么不用self作為第一個參數?

  Ruby是純OOP語言,沒有函數,全是方法,所以省了傳遞self

  class String

  def foo(x,y,z)

  # blah blah

  end

  end

  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 為調用此方法的對象

  end

  end

  end

  Q:sub、gsub方法是什么作用?

  String類的sub方法作用是替換子串並生成新字符串,gsub是替換所有匹配子串

  對應的sub!和gsub!是在原字符串上進行修改,不生成新對象

  String#replace並不是替換子串的作用,而是把字符串整個替換成別的值,但本身對象不變

  str = "abc"

  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" ]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM