第六章 深入方法
第六章 深入方法
一些其它语言有函数,过程,方法等,而Ruby中只有方法:一段表达式代码,返回一个值。
到目前为止,我们在这本书中只是基本的介绍了如何定义,使用方法,现在,我们会继续深入的探讨一些关于方法更深层的东西。
如同在前面看到的一样,定义一个方法用关键字def。方法名应该以小写字母开头。[如果你用大写字母开头定义一个方法,你不会立即得到一个错误,但是当你调用这个方法时,Ruby首先认为你访问的是一个常量,所以可能会解析错误]被用来做为查询的方法通常名字后面带有一个问号"?"。例如,String提供了chop和chop!两个方法,第一个方法返回一个修改过的字符串,而第二个方法直接就修改了被调本身。可以用来进行赋值的方法的名字后面带有等号(=)。"?","!"和"="只允许做为方法名字的后缀。
现在,我们已经指定了我们新方法的名字,如果需要,我们可以定义一些参数。这些是在圆括号内的简单的一个局部变量列表。(包围方法的参数的圆括号是可选的;我们约定,方法有参数时使用它们,没有参数时省略它们。)
def my_new_method(arg1, arg2, arg3) # 3个参数
# Code for the method would go here
end
def my_other_new_method #无参数
# Code for the method would go here
end
Ruby允许为方法的参数设置默认值:如果调用者没有显示的为这些参数提供值,将使用这些默认值。通过"=",就可以为这些参数设定默认值。
def cool_dude(arg1="Miles", arg2="Coltrane", arg3="Roach")
"#{arg1}, #{arg2}, #{arg3}."
end
cool_dude => "Miles, Coltrane, Roach."
cool_dude("Bart") => "Bart, Coltrane, Roach."
cool_dude("Bart", "Elwood") => "Bart, Elwood, Roach."
cool_dude("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的结构,包括了从第二个开始的所有参数。
在46页讨论块和迭代的那章时,我们知道,当一个方法被调用时候,可以接收一个块。通常,你只是简单地在方法内使用yield来调用这个块。
def take_block(p1)
if block_given?
yield(p1)
else
p1
end
end
take_block("no block") => "no block"
take_block("no block") {|s| s.sub(/no /, '') } => "block"
但是,当方法接受参数中最后一个参数以"&"开始的时候,任何与之关联的块都会转换为Proc对象,并且这个Proc对象将会赋值给这个参数(下例中block指向一个Proc对象)。
class TaxCalculator
def initialize(name, &block)
@name, @block = name, block
end
def get_tax(amount)
"#@name on #{amount} = #{ @block.call(amount) }"
end
end
tc = TaxCalculator.new("Sales tax") {|amt| amt * 0.075 }
tc.get_tax(100) => "Sales tax on 100 = 7.5"
tc.get_tax(250) => "Sales tax on 250 = 18.75"
通常,调用一个方法需要指定一个被调,方法名子,还有一些可选参数或者可选的块。
connection.download_MP3("jitterbug") {|p| show_progress(p) }
在这个例子里,connection是被调,downloadMP3是方法名,"jitterbug"是一个参数,{ |p| showProgress(p) }是传递给这个方法的块。
对于类或者模块方法来说,被调是类或模块名:
File.size("testfile") => 66
Math.sin(Math::PI/4) => 0.707106781186548
如果你省略了被调,那么默认为self是被调,即当前对象:
self.class => Object
self.frozen? => false
frozen? => false
self.id => 967900
id => 967900
这种缺省机制也是Ruby实现private方法的体现,private方法不能用一个被调来直接调用,只能在当前对象中使用。
同样,在前个例子中,我们调用self.class,但我们没有使用一个被调来调用这个方法。这是因为class也是Ruby的一个关键字(它引入类定义),所以使用的话会产生一个语法错误。
方法名后面是可选的参数,如果不会出现歧义的话,调用方法时参数可以不加括号括起来[Ruby文档有时候也叫做这样的方法是命令(commands)],然而,除非是特别简单的情况,否则还是加上括号的好,要不可能容易出错。(特别地,当被调用的方法是另一个方法的参数时,你必须使用圆括号。除非它是最后一个参数)我们的规则很简单:如果有任何歧义,使用圆括号。
a = obj.hash # Same as
a = obj.hash() # this.
obj.some_method "Arg1", arg2, arg3 # Same thing as
obj.some_method("Arg1", arg2, arg3) # with parentheses.
方法返回的值
每个被调用的方法返回一个值(尽管没有规定说你必须使用这个值)。方法的值是在方法执行期间被计算的最后语句的值。Ruby有个return语句,它从当前运行的方法中退出。return的值是它的参数。如果不需要,则Ruby习惯上是省略这个参数。
def meth_one
"one"
end
meth_one => "one"
def meth_two(arg)
case
when arg > 0
"positive"
when arg <>
"negative"
else
"zero"
end
end
meth_two(23) => "positive"
meth_two(0) => "zero"
def meth_three
100.times do |num|
square = num*num
return num, square if square > 1000
end
end
meth_three => [32, 1024]
就像最后一个case显示的,如果你给出有多个参数的return语句,则方法将在一个数组中返回它们。你可以使用并行赋值来收集这个返回值。
num, square = meth_three
num => 32
square => 1024
在方法调用时展开数组
前面我们已经说过了,在一个方法定义内的参数前面可以加一个星号,这样所有后面的参数都被放到了一个数组中,反过来,Ruby也支持调用的时候指定一个数组代替若干个参数。
在调用方法的时候,你可以展开一个数组,它的每个元素都将作为一个单独的参数使用。使用的时候,需要在这个作为参数的数组(which must follow all the regular arguments)前面加一个星号。
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
five(*(10..14).to_a) => "I was passed 10 11 12 13 14"
我们已经看过了如何把一个方法和一个块联系起来。
list_bones("aardvark") do |bone|
# ...
end
通常,这已经足够好了,我们可以给一个方法提供一个合适的块,而不必再方法中使用很多的if或者while等语句。
但是有些时候,你需要更灵活一些,比如,下面的例子,如果选择times,即输入t,将会打印2,4,6,8等等(代码不检查输入错误):
print "(t)imes or (p)lus: "
times = gets
print "number: "
number = Integer(gets)
if times =~ /^t/
puts((1..10).collect {|n| n*number }.join(", "))
else
puts((1..10).collect {|n| n+number }.join(", "))
end
produces:
(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 = Integer(gets)
if times =~ /^t/
calc = lambda {|n| n*number }
else
calc = lambda {|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对象,它被从参数列表中移除,并被转换成Proc对象传入与这个方法相关联的块内。
哈希结构作为参数
一些语言支持关键字参数,即不按照参数的个数和位置来调用一个方法,而是以任意次序传递带有值的参数的名字。Ruby1.8没有关键字参数(谎言,本书的前一个版本说过它有,或许是在Ruby2.0中)。在这期间,人们使用哈希表来达到同样的效果。例如,我们可以考虑为我们的SongList添加更强大的名字搜索功能。
class SongList
def create_search(name, params)
# ...
end
end
list.create_search("short jazz songs",
{
'genre' => "jazz",
'duration_less_than' => 270
})
第一个参数是查找的名称,第二个参数是一个包含搜索参数的Hash字面值。使用hash意味着我们可以模仿关键字:音乐流派是jazz,时长小于4.5分钟。但是这个实现还不是太好,而且大括号中的内容很容易被误认为是与方法关联的块。所以,Ruby提供了一个快捷方式,你可以在方法的参数中指定键=>值的结构,像普通的参数那样。这些键值对会被收集到单个Hash中,并做为一个参数传递给方法。花括号则就不需要了。
list.create_search('short jazz songs',
'genre' => 'jazz',
'duration_less_than' => 270)
最后,Ruby习惯上你或许应使用Symbol而不是字符串,因为Symbol会使你引用某些名字变得更清晰
list.create_search('short jazz songs',
:genre => :jazz,
:duration_less_than => 270)
写作风格良好的Ruby程序典型地有很多方法,每个都相当小,所以当定义和使用Ruby方法时应熟悉有效的操作。


0 Comments:
发表评论
<< Home