《Rubu基础教程第五版》第十一章笔记 块

块(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]

 

posted @ 2020-06-02 16:52  就是想学习  阅读(183)  评论(0编辑  收藏  举报