深入方法
一些其它语言有函数,过程,方法等,而Ruby中只有方法:一段表达式代码,返回一个值。
到目前为止,我们在这本书中只是基本的介绍了如何定义,使用方法,现在,我们会继续深入的探讨一些关于方法更深层的东西。
方法定义
如同在前面看到的一样,定义一个方法用关键字def开头,方法名应该以小写字母开头[如果你用大写字母开头定义一个方法,你不会立即得到一个错误,但是当你调用这个方法时,Ruby首先认为你访问的是一个常量,所以可能会解析错误],如果一个方法主要用来完成一些查询操作(不专指数据库查询),通常以一个问号"?"结束,作为函数名的最后一个字母,比如instance_of?等。如果一个方法有一定危险,或者可能修改方法的接受者,通常以"!"结尾,比如String类提供了chop和chop!两个方法,第一个方法返回一个修改过的字符串,而第二个方法直接就修改了接收者本身。"?"和"!"是唯一两个能作为方法名后缀的特殊字符。
我们已经指定了方法名,如果需要,我们可以定义一些参数,这些参数用双括号括起来,作用域范围都是局部变量,一些例子如下:
def myNewMethod(arg1, arg2, arg3) # 3 arguments # Code for the method would go here end def myOtherNewMethod # No arguments # Code for the method would go here end
Ruby允许为方法的参数设置默认值:如果调用者没有显示的为这些参数提供值,将使用这些默认值。通过"=",就可以为这些参数设定默认值。
def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
"#{arg1}, #{arg2}, #{arg3}."
end
coolDude
?"Miles, Coltrane, Roach."
coolDude("Bart")
?"Bart, Coltrane, Roach."
coolDude("Bart", "Elwood")
?"Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus")
?"Bart, Elwood, Linus."
方法体中包含了一般的Ruby表达式,但是你不能在方法里面定义实例方法,类或者模块。方法的返回值是方法体最后一行执行后的结果,或者你显示的用一个return语句。
可变长度的参数列表
如果我们想给方法传入一个数目不定的参数,或者把所有参数放到一个参数中进行传递的话,该怎么办呢?我们可以在普通的参数后面加入一个特殊的参数,这个参数以"*"开头,就可以达到这个目的了。
def varargs(arg1, *rest)
"Got #{arg1} and #{rest.join(', ')}"
end
varargs("one")
?"Got one and "
varargs("one", "two")
?"Got one and two"
varargs "one", "two", "three"
?"Got one and two, three"
在这个例子中,第一个参数很普通,直接作为第一个参数变量,而后面以"*"开头的参数,将会包括调用时候后面的所有参数,是一个Array的结构,包括了从第二个开始的所有参数。
方法和块
在讨论块和迭代的那章时,我们知道,当一个方法被调用时候,可以接收一个block,而我们在方法中可以用yield来执行这个block。
def takeBlock(p1) if block_given? yield(p1) else p1 end end
takeBlock("no block")
?"no block"
takeBlock("no block") { |s| s.sub(/no /, '') }
?"block"
但是,当方法接受参数中最后一个参数以"&"开始的时候,任何给定的block都会转换为Proc对象,并且这个Proc对象将会赋值给这个参数(下例中block指向一个Proc对象)。
class TaxCalculator
def initialize(name, &block)
@name, @block = name, block
end
def getTax(amount)
"#@name on #{amount} = #{ @block.call(amount) }"
end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100)
?"Sales tax on 100 = 7.5"
tc.getTax(250)
?"Sales tax on 250 = 18.75"
调用方法
通常,调用一个方法需要指定一个接收者,方法名,还有一些参数或者block。
connection.downloadMP3("jitterbug") { |p| showProgress(p) }
在这个例子里,connection是接收者,downloadMP3是方法名,"jitterbug"是一个参数,{ |p| showProgress(p) }是传递给这个方法的块。
对于类或者模块方法来说,接收者是类或模块名:
File.size("testfile") Math.sin(Math::PI/4)
如果你省略了接收者,那么默认为self是接收者,即当前对象:
self.id
?537794160
id
?537794160
self.type
?Object
type
?Object
这种机制也是Ruby实现private方法的体现,private方法不能用一个接收者来直接调用,只能在当前对象中使用。
方法名后面是可选的参数,如果不会出现歧义的话,调用方法时参数可以不加括号括起来[Ruby文档有时候也叫做这样的方法是命令(commands)],然而,除非特别简单的方法,否则还是加上括号的好,要不可能容易出错,比如,你的方法嵌套在另一个方法调用之中。
a = obj.hash # Same as a = obj.hash() # this. obj.someMethod "Arg1", arg2, arg3 # Same thing as obj.someMethod("Arg1", arg2, arg3) # with parentheses.
在方法调用时使用数组
前面我们已经说过了,在一个方法的参数前面可以加一个星号,这样所有后面的参数都被放到了一个数组中,反过来,Ruby也支持调用的时候指定一个数组代替若干个参数。
在调用方法的时候,你可以使用一个数组作为一个参数,它的每个元素都将作为一个单独的参数使用。使用的时候,需要在这个作为参数的数组前面加一个星号。
def five(a, b, c, d, e)
"I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 )
?"I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b'])
?"I was passed 1 2 3 a b"
five(*(10..14).to_a)
?"I was passed 10 11 12 13 14"
更加动态的block
我们已经看过了如何把一个方法和一个块联系起来。
listBones("aardvark") do |aBone| # ... end
通常,这已经足够好了,我们可以给一个方法提供一个机构良好的块,而不必再方法中使用很多的if或者while等语句。
但是有些时候,你需要更灵活一些,比如,下面的例子,如果选择times,即输入t,将会打印2,4,6,8等等:
print "(t)imes or (p)lus: " times = gets print "number: " number = gets.to_i if times =~ /^t/ puts((1..10).collect { |n| n*number }.join(", ")) else puts((1..10).collect { |n| n+number }.join(", ")) end结果:
(t)imes or (p)lus: t number: 2 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
虽然这样可以工作,但是不是很完美,我们可以把负责计算的部分抽出来组成一个block。
print "(t)imes or (p)lus: " times = gets print "number: " number = gets.to_i if times =~ /^t/ calc = proc { |n| n*number } else calc = proc { |n| n+number } end puts((1..10).collect(&calc).join(", "))produces:
(t)imes or (p)lus: t number: 2 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
如果最后一个方法的最后一个参数以"&"开头,Ruby把它最为一个Proc来处理,传到相应的block。
这种技术也有另外的用处,比如我们使用迭代器处理一些数据,把每个步骤地结果存储到一个数组中,我们下面将用到前面的Fibonacci 例子来产生一组数据:a = []
fibUpTo(20) { |val| a << val }
?nil
a.inspect
?"[1, 1, 2, 3, 5, 8, 13]"
尽管这样已经可以工作了,但是这显示出来的意图不像我们想象的那么明晰,所以,我们取而代之的是另外定义了一个方法into,它将返回一个完成填充array功能的block。(注意返回的block是一个闭包closure ,即使into返回了,它还指向参数anArray)
def into(anArray)
return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect
?"[1, 1, 2, 3, 5, 8, 13]"
哈希结构作为参数
一些语言支持基于键的参数,即hash结构的参数。不按照参数的个数和位置来调用一个方法,而是用一个hash结构的键-值结构来设定参数,而不是按位置。Ruby1。6不支持这种特性,1。8支持。[本书写的是基于1.6,而目前最新的Ruby是1.8]
同时,人们可以用hash结构来实现这一功能,比如,我们要为我们的SongList实现一个按名字查找的功能。
class SongList def createSearch(name, params) # ... end end aList.createSearch("short jazz songs", { 'genre' => "jazz", 'durationLessThan' => 270 } )
第一个参数是查找的名称,第二个参数是一个hash结构,包含了各种查找的参数。使用hash结构,我们可以是有一些键-值特性:音乐流派是jazz,时长小于4.5分钟。但是这段代码不是太好,而且大括号中的内容很容易被误认为块。所以,Ruby提供了一个快捷方式,你可以在方法的参数中指定键=>值的结构,像普通的参数那样。这样的结构都将作为一个hash结构传给方法,而不需要大扩号了。
aList.createSearch("short jazz songs", 'genre' => "jazz", 'durationLessThan' => 270 )