分享

快速读懂Ruby代码问答

 icecity1306 2015-02-26
本问答的目标读者是不了解Ruby语言、但有别的编程语言经验的人。 
  
Ruby语言的代码可读性是很强的。本问答只把一些语法特点、以及别的语言中可能没有或不同的东西展现出来,目的在于让有别的编程语言经验的人能快速读懂Ruby代码。 
  
注意本问答讲的是Ruby语言本身(基于版本1.9),而不是Ruby on Rails,后者是Ruby的一种DSL,语言面貌上和Ruby有一定差异。 
  
Q:Ruby最有特点的语法形式是什么? 
  
或许是方法后接代码块的大量使用,以下面这行代码为例: 
  
   
file.each_line { |line| print line } 
  
表示在file对象上调用each_line方法,该方法的功能是依次得到每一行,传递给后面的代码块,代码块把传来的行赋值给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./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./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之类关键词设定变量可见范围,而是采用变量自带标记的方式 
  
§ 以小写字母或_开头的变量是局部变量 
  
§ 以$开头的是全局变量 
  
§ 以@开头的是每个对象自身的实例变量 
  
§ 以@@开头的是同类对象都可访问的类变量 
  
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..100000000得内存爆炸了,但在Ruby里,一个range对象只记录首端的1和尾端的100000000,这么写没问题 
  
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 "上下" # 打印出来:"\u4E0A\u4E0B" 引号也是打印出来的内容 
  
实际上 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(<<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,yblk.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" ] 
※ 修改:·sevk 于 Sep  1 10:11:18 2013 修改本文·[FROM: 122.225.48.*] 
※ 来源:·水木社区 newsmth.net·[FROM: 221.218.155.*] 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多