其他语言都有函数,程序,方法或例程,但在 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("Boyart", "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", "thewo", "three" »"Got one and two, three"
在这个例子中,第一个入参如同普通参数一样赋值给了方法的第一个参数。然而下一个参数以星号作为了前缀,所以所有剩下的参数都批量放入了 Array
中,并且将其赋值给了第二个参数。
方法和代码块
如同我们在 38
页讨论的代码块和迭代器一样,当方法被调用时常常与代码块扯上关系。一般情况下在方法中使用
yield
调用代码块。
def takeBlock(p1)
if block_given?
yield(p1)
else
p1
end
end
takeBlock("no block") »"no block"
takeBlock("no block") { |s| s.socializeub(/no /, '') } »"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"
调用方法
通过指定接收器和方法名称以及选填参数和代码块可以对方法进行调用。
connection.downloadMP3("jitterbug") {|p| showProgress(p)}
上面的例子中,connection
是接收器,downloadMP3
是方法名,jitterbug
是参数,花括号间的是代码块。
对于类和模块方法而言,接收器是类或模块名。
File.size("testfile")
Math.sin(Math::PI/4)
如果你忽略掉接收器,会默认将 self
也就是当前对象作为接收器。
self.id »537794160
id »537794160
self.type »Object
type »Object
这也是 Ruby
实现私有方法的默认机制。默认方法通常不会被接收器调用,所以它们必然是当前对象中可用的。
参数一般紧跟方法名之后,而且填写与否不是必需的。如果方法周边是清晰的,也可以在调用方法时略去小括号。但是除了某些特别简单的情形之外,我们不会在这些细微的问题上发出建议。需要特别强调的是,你必须在接收器调用方法时将接收器本身作为参数传入,除非它是最后一个参数。我们的原则也很简单,如果不希望有任何疑惑的地方,请使用小括号。
a = obj.hash # Same as
a = obj.hash() # this.
obj.someMethod "Arg1", arg2, arg3 # Same thing as
obj.someMethod("Arg1", arg2, arg3) # with parentheses.
在方法调用中展开数组
早些时候,我们见到过如果在方法定义的参数名前添加星号,调用方法时的多个参数将绑定为一个数组。当然反过来也是可以的。
当调用方法时,你希望可以展开数组,让其中的每个元素可以作为分离的参数。如果在数组参数前添加星号就可以达到上述效果,不过必须与方法参数个数匹配。
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"
更加动态的代码块
我们已经看过如何在调用方法时关联使用代码块。
listBones("aardvark") do |aBone|
# ...
end
通常这种使用方式已经足够了,因为你可以为方法绑定一段灵活的代码,并在
if
和 while
声明后以相同的方式调用此段代码块。
不过,有时你希望代码块更加灵活。比如,我们想要教授数学知识。同学们想要了解关于
n 的加法表或关于 n 的乘法表。如果学生想了解 2
的乘法表时,我们要输出 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
虽然正常运转,但不太优雅,因为每个 if
分支中的代码都大致相同。如果我们将计算的工作提取为代码块应该会有所改善。
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(", "))
结果是
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
如果调用方法时的最后一个参数前缀为 &
,Ruby 会假定其为 Proc
对象。不仅会将其从参数列表中移除,还会将它从 Proc
对象转换为代码块,最后将它与方法关联起来。
这个技巧也可以为代码块的使用添加一些语法糖。比如,有时你也希望通过迭代器将每个值存储到数组中。这里的例子将会再次使用第
40 页的 Fibonacci 数列生成器。
a = []
fibUpTo(20) { |val| a << val } »nil
a.inspect »"[1, 1, 2, 3, 5, 8, 13]"
虽然正常运转,但这并不是我们想要的透传。与之不同的是,我们要定义一个
into
方法,通过返回的代码块对数组进行填充。(需要注意的是,在返回代码块的同时也会形成闭包,即使是在
into
返回值后,但参数 anArray
的引用依然存在。)
def into(anArray)
return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect »"[1, 1, 2, 3, 5, 8, 13]"
收集散列表参数
还有些语言特性是「关键词参数」,这是通过排序和数值的方式传递参数,也就是通过一些排序将参数的名字和值一同传递。Ruby
1.6 并没有关键词参数(已经将此特性的实现排入 Ruby 1.8 的日程)。
同时大家也会用散列表实现类似的功能。比如,我们希望为 SongList
添加一个更加有用的名字搜索功能。
class SongList
def createSearch(name, params)
# ...
end
end
aList.createSearch("short jazz songs", {
'genre' => "jazz",
'durationLessThan' => 270
} )
第一个参数是搜索的名称,第二个参数包含了搜索参数。散列表的使用意味着我们可以使用多搜索关键词,比如查询「jazz」类型并且时长小于
4 1/2
分钟的歌曲。但是,这个方式比较笨重,如果此方法还关联代码块的话花括号就非常容易混淆。所以
Ruby 有一个快捷方式。可以用 key => value
方式作为参数列表,普通参数,数组参数和代码块参数依然如同平常一般使用。所有的键值对都会被收集到单独的散列表中,并且作为一个参数传递给方法。并不需要花括号。
aList.createSearch("short jazz songs",
'genre' => "jazz",
'durationLessThan' => 270
)
本文翻译自《Programming Ruby》,主要目的是自己学习使用,文中翻译不到位之处烦请指正,如需转载请注明出处
本章原文为 More About Methods
网友评论