2006-07-13

第六章 深入方法

第六章 深入方法

一些其它语言有函数,过程,方法等,而Ruby中只有方法:一段表达式代码,返回一个值。


到目前为止,我们在这本书中只是基本的介绍了如何定义,使用方法,现在,我们会继续深入的探讨一些关于方法更深层的东西。

方法定义

如同在前面看到的一样,定义一个方法用关键字def。方法名应该以小写字母开头。[如果你用大写字母开头定义一个方法,你不会立即得到一个错误,但是当你调用这个方法时,Ruby首先认为你访问的是一个常量,所以可能会解析错误]被用来做为查询的方法通常名字后面带有一个问号"?"。例如,String提供了chopchop!两个方法,第一个方法返回一个修改过的字符串,而第二个方法直接就修改了被调本身。可以用来进行赋值的方法的名字后面带有等号(=)"?""!""="只允许做为方法名字的后缀。

现在,我们已经指定了我们新方法的名字,如果需要,我们可以定义一些参数。这些是在圆括号内的简单的一个局部变量列表。(包围方法的参数的圆括号是可选的;我们约定,方法有参数时使用它们,没有参数时省略它们。)

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 3 a b"

five(*(10..14).to_a) => "I was passed 10 11 12 13 14"

更加动态的块

我们已经看过了如何把一个方法和一个块联系起来。

list_bones("aardvark") do |bone|

# ...

end

通常,这已经足够好了,我们可以给一个方法提供一个合适的块,而不必再方法中使用很多的if或者while等语句。

但是有些时候,你需要更灵活一些,比如,下面的例子,如果选择times,即输入t,将会打印2468等等(代码不检查输入错误)

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