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

results matching ""

    No results matching ""