directory search
Ruby用户指南 3、开始 4、简单的例子 5、字符串 6、正则表达式 7、数组 8、回到那些简单的例子 9、流程控制 10、迭代器 11、面向对象思维 12、方法 13、类 14、继承 15、重载方法 16、访问控制 17、单态方法 18、模块 19、过程对象 20、变量 21、全局变量 22、实变量 23、局部变量 24、类常量 25、异常处理:rescue 26、异常处理:ensure 27、存取器 28、对象的初始化 29、杂项 RGSS入门教程 1、什么是RGSS 2、开始:最简单的脚本 3、数据类型:数字 4、数据类型:常量与变量 5、数据类型:字符串 6、控制语句:条件分歧语句 7、控制语句:循环 8、函数 9、对象与类 10、显示图片 11、数组 12、哈希表(关联数组) 13、类 14、数据库 15、游戏对象 16、精灵的管理 17、窗口的管理 18、活动指令 19、场景类 Programming Ruby的翻译 Programming Ruby: The Pragmatic Programmer's Guide 前言 Roadmap Ruby.new 类,对象和变量 容器Containers,块Blocks和迭代Iterators 标准类型 深入方法 表达式Expressions 异常,捕捉和抛出(已经开始,by jellen) 模块 基本输入输出 线程和进程 当遭遇挫折 Ruby和它的世界 Ruby和Web开发 Ruby Tk Ruby 和微软的 Windows 扩展Ruby Ruby语言 (by jellen) 类和对象 (by jellen) Ruby安全 反射Reflection 内建类和方法 标准库 OO设计 网络和Web库 Windows支持 内嵌文档 交互式Ruby Shell 支持 Ruby参考手册 Ruby首页 卷首语 Ruby的启动 环境变量 对象 执行 结束时的相关处理 线程 安全模型 正则表达式 字句构造 程序 变量和常数 字面值 操作符表达式 控制结构 方法调用 类/方法的定义 内部函数 内部变量 内部常数 内部类/模块/异常类 附加库 Ruby变更记录 ruby 1.6 特性 ruby 1.7 特性 Ruby术语集 Ruby的运行平台 pack模板字符串 sprintf格式 Marshal格式 Ruby FAQ Ruby的陷阱
characters

深入方法


一些其它语言有函数,过程,方法等,而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
                   )


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Previous article: Next article: