Wednesday, October 22, 2008

(6)Ruby中的块Blocks

在我们前面介绍迭代器的时候已经用到了块,块类似于方法调用,但是还是有一些区别的,本节将详细介绍块。
块的语法结构
块是不能单独存在的,它必须出现在方法调用之后,也就是说如果块不在一个迭代器或者方法调用中没有yield那么这个块会被忽略。块以{}定义,或者使用do end 关键字,块的一个花括号{ 或者do 关键字必须与迭代器或者方法调用在同一行,否则Ruby解析器无法把它当作迭代器或者方法调用的一部分:
# 打印1到10的数字
1.upto(10) {|x| puts x }   # 块与方法调用位于同一行
1.upto(10) do |x|          # 使用 do/end关键字
  puts x
end
1.upto(10)                 # 没有指定块
{|x| puts x }             # 语法错误:块没有位于方法调用之后
一个通常的做法是,如果块只有一行接单的逻辑则使用{},如果是多行的逻辑,就使用do/end关键字方式。值得一提的是如果你的方法存在参数,那么使用{的时候要给参数加上括号(),如果你的参数没有括号,那么你应该使用do/end方式:
1.upto(3) {|x| puts x }    # 加括号的参数使用{}
1.upto 3 do |x| puts x end # 没有括号参数的方法使用,do/end
1.upto 3 {|x| puts x }     # 语法错误!
块可以有参数,不过它的参数要放在||之间并用逗号分隔,除此之外它和方法参数非常相似:
# 字典对象的each方法有两个参数
hash.each do |key, value|   # 循环调用字典对象的(key,value) 
  puts "#{key}: #{value}"   # 打印key和value
end                         # 块结束
块的返回值
我们在前面提到的块调用中一再说到yield值,但是没有说到返回值,实际上块是有返回值的,返回值就是块的最后一个表达式的值,注意,不要在块中使用return关键字,这个关键字意味着强制结束你的块逻辑执行,有些时候这是不好的,如果你希望块能够返回更多的值你应该使用next关键字:
array.collect do |x|
  next 0 if x == nil  # 如果x==nil返回0
  next x, x*x         # 返回两个值
end
块的变量范围
块内可以有自己的局部变量,这些变量只在块的范围你有效,但是,如果块内的变量名和块外的变量名相同,这时Ruby不会在块内重新声明变量,而是使用块外部的变量。有时候这对我们来说是很有用的,比如:
total = 0   
data.each {|x| total += x }  # 计算数组内所有元素的合计值
puts total                   # 打印合计值
但是有的时候这样不好,比如,如果一个块的参数和一个已经存在的变量重名,那么Ruby不会创建一个新的变量而是使用那个已经存在的变量。比如:
1.upto(10) do |i|         # 十条记录
  1.upto(10) do |i|       # 每条记录有十列
    print "#{i} "         # 打印每一列
end
  print " ==> Row #{i}\n" # 这里试图打印行号,但是实际得到是列号
end
在Ruby1.9中没有这种情况,块中的变量只在当前块中有效,而且不共享块以为定义的变量,如果你打开了-w选项,Ruby会警告你块变量与已经存在的变量重复。Ruby1.9中从语法定义上根本的解决了这个问题,块参数之间使用分号;分隔来表示这是一个块的参数:
x = y = 0            # 局部变量
1.upto(4) do |x;y|   # x和y现在是块的局部变量
                     
  y = x + 1          # 使用块局部变量
puts y*y           # 打印4, 9, 16, 25
end
[x,y]                # => [0,0]:块没有修改外部的局部变量
块可以有多个参数和多个变量下面是一个例子:
hash.each {|key,value; i,j,k| ... }

No comments: