深入方法

优质
小牛编辑
121浏览
2023-12-01

一些其它语言有函数,过程,方法等,而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}."endcoolDude ?"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(', ')}"endvarargs("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 TaxCalculatordef initialize(name, &block)@name, @block = name, blockenddef getTax(amount)"#@name on #{amount} = #{ @block.call(amount) }"endendtc = 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 ?537794160id ?537794160self.type ?Objecttype ?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}"endfive(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 } ?nila.inspect ?"[1, 1, 2, 3, 5, 8, 13]"

尽管这样已经可以工作了,但是这显示出来的意图不像我们想象的那么明晰,所以,我们取而代之的是另外定义了一个方法into,它将返回一个完成填充array功能的block。(注意返回的block是一个闭包closure ,即使into返回了,它还指向参数anArray

def into(anArray)return proc { |val| anArray << val }endfibUpTo 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
                   )