**块(block)**就是在调用方法时能与参数一起传递的多个处理的集合。
带块的方法调用:
对象.方法名(参数列表) do |快变量|
希望循环的处理
end
# 或者
对象.方法名(参数类别){ |块变量|
希望循环的处理
}
块的开头块变量。快变量就是在执行块时,从方法传进来的参数。不同方法的块变量个数也不相同。
ary = ["a","b","c"]
ary.each{|obj|
p obj
}
ary.each_with_index{|obj,idx| # |元素,索引|
p [obj,idx]
}
在Ruby中,我们常常使用块来实现循环。在接收块的方法中,实现了循环处理的方法称为迭代器(iterator)。each方法就是一个典型的迭代器。
alphabet = ["a","b","c","d","e"]
alphabet.each do |i|
puts i.upcase
end
与数列不同的是,散列会将[key,value]的组合作为数组来提取元素
sum = 0
score = {"eva"=>100,"alex"=>88,"hopkins"=>94}
score.each do |pair|
sum += pair[1]
end
p sum
也可以将key和value分别取出来
sum = 0
score = {"eva"=>100,"alex"=>88,"hopkins"=>94}
score.each do |name,score|
sum += score
end
p sum
也可以对文件使用each方法。除了each_line方法还有以字符为单位来循环读取的each_char方法,以及以字节为单位循环的each_byte方法等
file = File.open("TheNewColossus.txt")
file.each_line do |line|
puts line
end
file.close
File.open("TheNewColossus.txt") do |file|
file.each_line do |line|
puts line
end
end
与之前的不同点在于没有了最后的close方法调用。在当前这个程序中,即使遇到无法打开文件等错误也可以正常关闭文件,以为块内相当于进行了一下的处理
file = File.open("TheNewColossus.txt")
begin
file.each_line do |line|
print line
end
ensure
file.close
end
通过使用块的方法,一来可以减少程序的代码量,二来可以防止忘记关闭文件等错误的发生。
以数组排序为例
Array类的sort方法是对数组元素进行排列的方法。对数组元素进行排序,可以采取多重方法
- 按数字的大小排序
- 按字母顺序
- 按字符串的长度顺序
- 按数组元素的合计值的大小顺序
如果按照这样的条件分别定义相应的排序方法,就会是方法的数量过多,不便于记忆。因此在Array#sort方法中,元素的排序步骤由方法决定,用户只能指定元素间前后关系的比较逻辑。
Array#sort方法没有指定块时,会使用<=>运算符对各个元素进行比较,并根据比较后的结果进行排序
条件 | 结果 |
---|---|
a<b | -1 |
a==b | 0 |
a> | 1 |
array =["Ruby","Perl","PHP","Python"]
#先大写字母后小写字母的顺序排列
p array.sort
# 指定块排序
p array.sort{|a,b| b.length<=>a.length}
p array.sort{|a,b| a.length<=>b.length}
#%w(...)是一个用于生成以各单词为元素的数组的字面量
ary = %w(
You cannot improve your past, but you can improve your future. Once time is wasted, life is wasted.
Knowlegde can change your fate and English can accomplish your future.
Don't aim for success if you want it; just do what you love and believe in, and it will come naturally.
Jack of all trades and master of none.
)
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}"
调用块246次则调用length方法为492次。但是只需要将每个单词调用Length方法一次,在按结果排序即可
#%w(...)是一个用于生成以各单词为元素的数组的字面量
ary = %w(
You cannot improve your past, but you can improve your future. Once time is wasted, life is wasted.
Knowlegde can change your fate and English can accomplish your future.
Don't aim for success if you want it; just do what you love and believe in, and it will come naturally.
Jack of all trades and master of none.
)
call_num = 0 # 块次数初始化
sorted = ary.sort do |a,b|
call_num +=1 #累加块次数
a.length <=>b.length
end
print ary.sort_by{|item| item.length}
def myloop
while 1
yield
end
end
num = 1
myloop do
p num
break if num>100
num *= 2
end
myloop 方法在执行while循环的同时执行了yield关键字,yield关键字的作用就是执行方法的块。因为这个while的循环条件,所以会一直循环,但只要在块中调用break,就可以终止循环,执行之后的处理。
def total(from, to)
result = 0 # 结果
from.upto(to) do |num| # 处理从from到to的num
if block_given? # 判断是否存在块
result += yield(num)
else
result += num
end
end
return result
end
p total(1,10)
p total(1,10){|num| num**2}
block_given? 判断调用该方法时是否有块被传递给方法
def block_args_test
yield()
yield(1)
yield(1,2,3)
end
puts "通过|a|接收块变量"
block_args_test do |a|
p [a]
end
puts "通过|a,b,c|接受块变量"
block_args_test do |a,b,c|
p [a,b,c]
end
puts "通过|*a|接收块变量"
block_args_test do |*a|
p [a]
end
def total(from, to)
result = 0
from.upto(to) do |num|
if block_given?
result += yield(num)
else
result += num
end
end
return result
end
n = total(1,10) do |num|
if num == 5
break
end
num
end
p n
答案是nil。在块中使用break,程序会马上返回到调用块的地方,因此total方法红返回计算结果的处理等都会被忽略掉。但作为方法的结果,当我们希望返回某个值时,就可以像break 0
这样指定break方法的参数,这样该值就会成为方法的返回值。
def total(from, to)
result = 0
from.upto(to) do |num|
if block_given?
result += yield(num)
else
result += num
end
end
return result
end
n = total(1,10) do |num|
if num%2 != 0
next 0
end
num
end
p n
如果在块中使用了next,程序就会中断当前处理,并继续执行下面的处理,使用next后,执行块的yield会返回,如果next没有指定任何参数则返回nil,如果像next 0
这样制定了参数,那么该参数值就是返回值
如果在块中使用了redo,程序就会返回到块的开头,并以相同的块变量再次执行处理。这种情况下,块的处理结果不会返回给外部,因此需要十分小心redo的用法,注意不要使程序陷入死循环
在接收块的方法中执行块时,可以使用yield关键字。
Ruby还能把块当作对象处理。把块当作对象处理后,就可以在接收块的方法之外的其他地方执行块,或者把块交给其他方法执行。
把块当作对象操作时,我们需要用到Proc对象。定义Proc对象的典型方法是,调用Proc.new方法这个带块的方法。在调用Proc对象的call方法之前,块中定义的程序不会被执行。
hello = Proc.new do |name|
puts "Hello,#{name}"
end
hello.call("Harry")
hello.call("Eva")
把块从一个方法传给另一个方法时,首先会通过变量将块作为Proc对象接收,然后再传给另一个方法。在方法定义时,如果末尾的参数要使用“&参数名”的形式,Ruby就会自动把调用方法时传进来的块封装为Proc对象。
def total2 (from,to,&block)
result = 0
from.upto(to) do |num|
if block
result +=
block.call(num)
else
result += num
end
end
return result
end
p total2(1,10)
p total2(1,10){|num| num**2}
在首行的方法定义中定义&block参数。像这样,在变量名前添加&的参数称为Proc参数。如果在调用方法时没有传递块,Proc参数的值为nil,因此通过这个值就可以判断出是否有块被传入方法中。另外,执行块的语句不是yield,而是block.call(num),这一点与之前的例子也不一样。
将块封装为Proc对象后,我们就可以根据需要随时调用块,甚至还可以将其赋值给实例变量,让别的实例方法去任意调用。
def call_each(ary,&block)
ary.each(&block)
end
call_each [1,2,3] do |item|
p item
end
也可以将Proc对象作为块传给其他方法处理,例如Array#each方法。
x = 1
y = 1
ary = [1,2,3]
ary.each do |x|
y = x
end
p [x,y]
在块外部定义的局部变量,在块中也可以继续使用。,而被作为块变量使用的变量,即使与块外部的变量同名,Ruby也会认为是两个不同的变量。但是块内部定义的变量不能被外部访问
x = y = z = 0 # 初始化x,y,z
ary = [1,2,3]
ary.each do |x;y| # 使用块变量x,块局部变量y
y = x # 对块局部变量y赋值
z = x # 对不是块局部变量的z赋值
p [x,y,z] # 确认块内的x,y,z的值
end
puts
p [x,y,z] # x,y没变化
用 " 变量名; ” 的形式,定义块变量以外的块变量,为不能覆盖外部的局部变量