块(block)就是在调用方法时能与参数一期传递的多个处理的集合。
对象.方法名(参数列表) do |块变量|
希望循环的处理
end
或者
对象.方法名(参数列表) do { |块变量| 希望循环的处理}
块的开头是块变量,块变量就是在执行块时,从方法传进来的参数
>> ary = ("a".."c").to_a => ["a", "b", "c"] >> ary.each{|obj| p obj} "a" "b" "c" => ["a", "b", "c"] >> ary.each_with_index{|obj, index| p [obj, index]} ["a", 0] ["b", 1] ["c", 2] => ["a", "b", "c"] >>
块的使用方法
在ruby中,我们常常使用块来实现循环。在接收块的方法中,实现了循环处理的方法称为迭代器(iterator)。each方法就是一个典型的迭代器,相当于Python中的iter
>> hash.each {|name| p name} [:a, "b"] => {:a=>"b"} >>
下面演示了each对hash的操作
sum = 0 outcome = {"参加费"=>1000, "挂号费"=>1000, "联欢费"=>4000} outcome.each do |pair| sum += pair[1] end puts "合计: #{sum}"
sum = 0 outcome = {"参加费"=>1000, "挂号费"=>1000, "联欢费"=>4000} outcome.each do |item, price| sum += price end puts "合计: #{sum}"
逐行读取文件
file = File.open("file_each.rb") file.each_line do |line| print line end file.close
隐藏常规处理
这个有点像Python中的 with open as
File.open("file_open.rb") do |file| # 类型Python中的with open file.each_line do |line| print line end end
这个代码普通写法如下
file = File.open("file_open_no_block.rb") begin file.each_line do |line| print line end ensure file.close end
替换部分算法
通过块的设置,指定sort规则
Array#sort方法没有指定块时,会使用<=>运算符对各个元素进行比较,并根据比较后的结果进行排序。<=>运算符的返回值为-1,0,1中的一个
a <=> b 当a < b -1; a == b 0; a > b 1,排序的时候,a < b 排前面
这个跟Python中的sorted(key差不多)
ary = %w( Ruby is a open source programming language with a focus on simplicity and productivity.It has an elegant syntax that is natural to read and easy to write ) sorted = ary.sort_by{|item| item.length } p sorted
call_num = 0 # 块的调用次数 sorted = ary.sort do |a, b| call_num += 1 a.length <=> b.length # 长度进行排序 end puts "排序结果 #{sorted}" puts "数组的元素数量 #{ary.length}" puts "调用块的次数 #{call_num}"
从执行可以看出调用长度的方法调用了很多次,可以改成sort_by进行排序,在上面
定义块的方法
块的方法跟Python中的生成器比较像,生成器也使迭代器
def myloop while true yield end end num = 1 myloop do puts "num is #{num}" break if num > 100 num *= 2 end
传递块参数,获取块的值
有意思,有点生产者与消费者的关系
def total(from, to) result = 0 from .upto(to) do |num| if block_given? # 如果有块的话 result += yield(num) # 累加经过块处理的值 else result += num #没有块处理直接累加 end end result end p total(1, 10) p total(1, 10){|num| num **2 } # 这个后面的块处理就像前面的num是yield传过来的参数,后面的num ** 2将传递给result
测试yield返回多个值的情况
通过|a|接收块变量 [nil] [1] [1] 通过|a, b, c|接收块变量 [nil, nil, nil] [1, nil, nil] [1, 2, 3] 通过|*a|接收块变量 [[]] [[1]] [[1, 2, 3]] shijianzhongdeMacBook-Pro:chapter_11 shijianzhong$
下面使代码,分别用多值或者单值或者不定长参数接收参数
shijianzhongdeMacBook-Pro:chapter_11 shijianzhong$ cat block_args_test.rb def block_args_test yield() # 0个块变量 yield(1) # 1个块变量 yield(1, 2, 3) # 3个块变量 end puts "通过|a|接收块变量" block_args_test do |a| p [a] end puts puts "通过|a, b, c|接收块变量" block_args_test do |a, b, c| p [a, b, c] end puts puts "通过|*a|接收块变量" block_args_test do |*a| p [a] end puts
控制块的执行
def total(from, to) result = 0 from .upto(to) do |num| if block_given? # 如果有块的话 result += yield(num) # 累加经过块处理的值 else result += num #没有块处理直接累加 end end result end p total(1, 10) p total(1, 10){|num| num **2 } # 这个后面的块处理就像前面的num是yield传过来的参数,后面的num ** 2将传递给result n = total(1, 10) do |num| if num == 5 break # 默认这里就停了,返回nil end num end p n m = total(1, 10) do |num| if num % 2 != 0 next 0 # 当不等于1,3等的时候返回返回0 end num end p m
将块封装为对象
把块当做对象操作时,我们需要用到Proc对象。定义Proc对象的典型的方法是,调用Proc.new方法这个带块的方法。在调用Proc对象的call方法之前,块中定义的程序不会执行
这个有点像Python中的匿名函数 lambda
hello = Proc.new { |name| puts "hello #{name}" } hello.call("sidian") hello.call("laji huawei")
把块的一个方法传给另一个方法时,首先会通过变量将块作为Proc对象接收,然后再传给另一个方法。在方法定义时,如果末尾的参数使用"&参数名"的形式,Ruby就会自动把调用方法时传进来的块封装成Proc对象
shijianzhongdeMacBook-Pro:chapter_11 shijianzhong$ cat total2.rb def total(from, to, &block) result = 0 from .upto(to) do |num| if block # 如果有块的话 result += block.call(num) # 累加经过块处理的值 else result += num #没有块处理直接累加 end end result end p total(1, 10) p total(1, 10){|num| num **2 } # 这个后面的块处理就像前面的num是yield传过来的参数,后面的num ** 2将传递给result
在自定义方法的时候,定义了&block参数,像这样在变量名前添加&的参数称为Proc参数。如果没有传参就是nil,如果传参了可以通过block.call调用
Proc参数一定要在最后一个位置
proc.call的调用像执行一个匿名函数,下面这种是直接通过方法后面执行的逻辑,参数&block,到函数里面不用变
将块封装为Proc对象后,我们就可以根据需要随时调用块,甚至还可以将其赋值给实例变量,让别的实例方法取任意调用。
def call_each(ary, &block) ary.each(&block) end call_each [1, 2, 3] do |item| # 调用这个方法,传入两个参数 p item end
局部变量与块变量
块外部定义的局部变量,在块中可以继续使用。而被作为块变量使用的变量,即使与块外部的变量同名,Ruby也会认为它们是两个不同的变量
x = 1 y = 1 ary = [1, 2, 3] ary.each do |x| # 块变量与外部的局部变量重名,但不会影响局部变量 y = x # 将x 赋值给y,符修改局部变量y的值 end p [x, y]
局部变量中没有的变量名,在块中进行赋值,后续在块的外面无法读取到该变量
x = 1 # y = 1 # 屏蔽外部变量局部变量,在块内部进行变量赋值,外部无法读取 ary = [1, 2, 3] ary.each do |x| # 块变量与外部的局部变量重名,但不会影响局部变量 y = x # 将x 赋值给y,符修改局部变量y的值 end p [x, y]
当不想修改局部变量的时候,可以定义块局部变量
x = y = z = 0 ary = [1, 2, 3] ary.each do |a; y| # 这个分号与逗号都可以用,y就变成了块局部变量,这样y的值修改,不会影响块外面y的值 x = a y = a z = a p [x, y, z] end puts p [x, y, z]