1.块的基本知识
#代码示例
def a_method(a, b)
a + yield(a, b)
end
a_method(1, 2){ |x, y|(x+y)*3 } #=>10
#总结:
1.只有调用一个方法时才可以定义一个块。
2.块会被直接传递给这个方法,然后该方法可以用yield关键词回调这个块。
3.块可以有自己的参数,当回调块时,可以像调用方法那样为块参数提供值,另外,像方法一样,块中最后一行代码执行的结果被作为返回值。
4.可以使用Kernel的block_given?方法来询问当前的方法调用是否包含块,代码如下所示:
#使用block_given?
def a_method
return yield if block_given?
'no block'
end
#总结:定义和调用代码块的两种方式
#方式1,命名代码块
def hi &block
block.call
end
hi{9} #=>9
#方式2,匿名代码块
def hello
yield #yield就是一个关键字,会调用外部代码块
end
hello{9} #=>9
#传递代码块参数
def hi name
yield name
end
puts hi("jayzen"){|x| "hello #{x}"}
#对比两种方式的性能
require 'benchmark'
Benchmark.realtime{ 1_000_000.times { hi {9} } } #=>0.6748529999999846
Benchmark.realtime{ 1_000_000.times { hello {9} } } #=>0.09540099999958329,效率更高
2.闭包
#绑定(banding)
局部变量、实例变量、self...这些东西都是绑定到对象上的,这些东西统一被成为绑定
#闭包获取局部变量
当创建块时会获取绑定,然后把块连同它自己的绑定传给一个方法
def my_method
x = 'goodbye'
yield("cruel")
end
x = "Hello" #块中绑定的是这里的x,而不是方法中的x,方法中的x对外部是不可见的
my_method { |y| "#{x}, #{y} world "} #=>"Hello,cruel world"
#闭包获取self以及self变量
class Demo
attr_accessor :name
def initialize(name)
@name = name
end
def one
yield
end
def two
#one{ name }
one { self.name } #等价,获取self的变量
end
end
obj = Demo.new("jayzen")
p obj.two #"jayzen"
#块作用域
块可以改变绑定的值,但是块中定义的变量是块外部是不能访问的
def my_method
yield
end
top_level_variable = 1
my_method do
top_level_variable += 1 #块绑定的局部变量,可以在块中改变局部变量的值
local_to_block = 1
end
top_level_variable #=>2 块获得局部绑定,并改变其值
local_to_block #=> Error! 块局部变量在块外部不能被调用
3.作用域和作用域门
#作用域门中做了介绍
4.全局变量、顶级实例变量和局部变量
#变量中已经说明
5.扁平作用域和共享作用域
#扁平作用域:
使用方法来代替作用域门,可以让一个作用域看到另外一个作用域里的变量,即让一个变量穿越作用域。
1、Class.new代替class,如果是继承自Array的类,可以使用Class.new(Array)代替class
2、define_method代替def
my_var = "Success"
MyClass = Class.new do
puts "#{my_var} in the class definition!"
define_method :my_method do
puts "#{my_var} in the method!"
end
end
MyClass.new.my_method
# Success in the class definition
# Success in the method
#共享作用域
在一组方法之间共享一个变量,但是又不希望其他方法也能访问这个变量。
def define_methods
shared = 0
Kernel.send :define_method, :counter do
shared
end
Kernel.send :define_method, :inc do |x|
shared += x
end
end
define_methods
puts counter #=>0
inc(4)
puts counter #=>4
#counter和inc都是Kernel的内核方法,这是Kernel内核方法定义的一种形式,另外一种形式如下:
module Kernel
def counter
#do something
end
end
7.proc对象
#ruby的使用块机制:“先打包代码,以后调用”,下面介绍5种方法
1.Proc.new
2.proc
3.lambda
4.->
5.&
#使用Proc.new
inc = Proc.new{ |x| x+1 }
puts inc.call(2)
puts inc.class #=>Proc
#使用proc
inc = proc { |x| x+1 }
puts inc.call(2)
puts inc.class #>Proc
#使用lambda
inc = lambda { |x| x+1 }
puts inc.call(2)
puts inc.class #>Proc
#使用->
p = ->(x){ x+1 }
puts p.class #>Proc
puts p.call(1)
#总结
上面的三种技术叫做延迟技术,就是通过Proc方法将代码打包成块对象,以备后来用call方法调用.
他们都是Kernel方法,分别是proc, Proc.new, lambda,其中"->"是lambda的特殊表达形式
#块参数使用的第二种方式
demo = Proc.new{ |first, last| first+ " " + last}
demo["hello", "world"] #"hello world"
#&操作符
&操作符的真正含义是,这是一个Proc对象,我想把它当做一个块来使用,简单地去掉&操作符,就能再次得到一个Proc对象,作用如下:
1.把块传递给另外一个方法
2.块与Proc对象之间的相互转换,其中带&是块,&后面的是Proc对象
#代码示例,把块传递给另外一个方法:
def math(a, b)
yield(a, b)
end
def teach_math(a, b, &operation)
puts "let's do the math"
puts math(a, b, &operation)
end
#在调用teach_math()方法时如果没有附加一个块,则&operation参数将被赋值为nil,这样在math()方法中的yield操作会失败
teach_math(2, 3){|x, y| x*y } #teach_math方法将块传递给math方法,&operation是块参数。
#代码示例,将一个块参数转化为块对象
def my_method(&the_proc)
the_proc
end
p = my_method{ |name| "Hello, #{name}"}
puts p.class #>Proc
puts p.call("Bill") #>Hello, Bill
#代码示例,将块对象转换为块
def my_method(greeting)
puts "#{greeting}, #{yield}"
end
my_proc = proc{ "Bill" } #当调用my_method()方法时,&操作符会把my_proc转换为块,再把这个块传递给这个方法
my_method("Hello", &my_proc) #> Hello, Bill
8.proc和lambda对象的区别
#两点区别:
1.return关键词的处理
2.参数检验有关
#总结:
1.相比proc, lambda更特殊,lambda中return从lambda中返回,而proc从定义proc的作用域放回
2.lambda参数严格
#return关键词的处理
1.在lambda中,return仅仅表示从这个lambda中返回(lambda类似于普通函数)
2.而在proc中,return的行为则有所不同,它不是从proc中返回,而是从定义proc的作用域中(就是整个方法体)返回,代码如下所示:
#代码示例,lambda的return从lambda返回
def double(callable_object)
puts callable_object.call*2
end
l = lambda{ return 10 }
double(l) #=>20, #如上的代码表示return从这个lambda中返回。
#代码示例,proc的return从定义proc的作用域中返回
def another_double
p = Proc.new{ return 10 }
resule = p.call
return result*2
end
puts another_double #>10,上面的代码结果返回的是10,这个结果是从下面这段代码进行返回
p = Proc.new{ return 10 }
#而下面的代码其实不会被执行。
resule = p.call
return result*2
#参数检验有关
总结:
1.proc和lambda相比,lambda对参数要求更严格,如果规定lambda只能接受两个参数,那么参数减少或者增加都会报错
2.pro则会自己进行调整
#代码示例,proc形式
p = Proc.new{ |a, b| [a, b]}
puts p.arity
puts p.call(1,2,3) #=> [1,2]
puts p.call(1) #=>[1, nil]
#代码形式,lambda的形式:
p = lambda { |a, b| [a, b]}
puts p.arity
puts p.call(1,2,3) #=> [1,2] #报错,wrong number of arguments
9.方法对象
#代码示例:
class MyClass
def initialize(value)
@x = value
end
def my_method
puts @x
end
end
object = MyClass.new(1)
m = object.method :my_method #object#method方法可以获得一个用Method对象表示的方法
m.call #=>1 #通过Method的实例方法call调用
查看原书:Method对象类似于lambda,但是有一个重要的区别:lambda在定义它的作用域中执行,
而Method对象会在它自身所在对象的作用域中执行
#如上的代码中,m可以绑定到其他对象上,但是局限同一个类的对象
class MyClass
def initialize(value)
@x = value
end
def my_method
puts @x
end
end
object = MyClass.new(1)
m = object.method :my_method
m.call #=>1
unbound = m.unbind #通过unbind解绑
another_object = MyClass.new(2)
m = unbound.bind(another_object) #通过bind进行重新绑定
m.call #=>2
case1:计数器
def counter(n)
#代码块会保留当前作用域中的变量
proc { n +=1 }
end
a = counter(10)
a.call #=>11
a.call #=>12
case2:
#实现的功能代码
enu = EnuTest.new do |x|
x << 1
x << 3
x << proc { 'hello' }
end
enu.next # => 1
enu.next # => 3
enu.next # => 'hello'
enu.next # => raise error 'EOF'
#result
class EnuTest
def initialize &block
@eb = EnuBlock.new
yield @eb
end
def next
@eb.next
end
end
class EnuBlock
def initialize
@blocks = []
end
def << obj
if obj.is_a? Proc
@blocks << obj
else
@blocks << proc { obj }
end
end
def next
if @blocks.empty?
raise "EOF"
else
@blocks.shift.call
end
end
end