1. 什么是块?

块是一段匿名的代码,通常与方法调用一起使用,类似其他语言中的匿名函数或闭包。块有两种形式:

  • do ... end 包裹(多行)。
  • { ... } 包裹(单行)。

块常用于:

  • 遍历集合(如 each)。
  • 执行回调。
  • 自定义方法行为。

2. 块的基本语法

do ... end

3.times do
  puts "你好"
end

输出:

你好
你好
你好

{ ... }

3.times { puts "你好" }

输出同上。

带参数

块可以接受参数,用 | 包裹:

[1, 2, 3].each do |number|
  puts "数字:#{number}"
end

输出:

数字:1
数字:2
数字:3

3. 块与方法的关系

方法可以通过 yield 调用传入的块,或者显式接收块参数(用 &)。

使用 yield

def say_hello
  puts "开始"
  yield
  puts "结束"
end

say_hello do
  puts "你好,世界!"
end

输出:

开始
你好,世界!
结束

带参数的 yield

def greet(name)
  yield(name)
end

greet("张三") do |n|
  puts "你好,#{n}!"
end

输出:

你好,张三!

检查块是否存在

block_given? 判断是否传入了块:

def try_block
  if block_given?
    yield
  else
    puts "没有传入块"
  end
end

try_block { puts "这是一个块" }  # 这是一个块
try_block                       # 没有传入块

4. 显式块参数

方法可以用 &block 显式接收块,并将其作为对象处理。

示例

def run_block(&block)
  puts "准备执行块"
  block.call
  puts "块执行完毕"
end

run_block { puts "我在块中" }

输出:

准备执行块
我在块中
块执行完毕

带参数

def pass_value(&block)
  block.call("李四")
end

pass_value { |name| puts "你好,#{name}!" }

输出:

你好,李四!

5. 常见块用法

遍历集合

  • 数组
  水果 = ["苹果", "香蕉", "橙子"]
  水果.each_with_index do |fruit, index|
    puts "#{index + 1}. #{fruit}"
  end

输出:

  1. 苹果
  2. 香蕉
  3. 橙子
  • 哈希
  学生 = { 姓名: "王五", 年龄: 20 }
  学生.each do |键, 值|
    puts "#{键}:#{值}"
  end

输出:

  姓名:王五
  年龄:20

转换数据

map

numbers = [1, 2, 3]
doubled = numbers.map do |n|
  n * 2
end
puts doubled  # [2, 4, 6]

过滤数据

select

numbers = [1, 2, 3, 4, 5]
even = numbers.select { |n| n % 2 == 0 }
puts even  # [2, 4]

6. 块的返回值

块的最后一行是返回值,常用于方法处理:

def calculate
  result = yield(5, 3)
  puts "结果:#{result}"
end

calculate { |a, b| a + b }  # 结果:8
calculate { |a, b| a * b }  # 结果:15

7. 中文支持示例

块支持中文变量和输出:

任务 = ["写代码", "开会", "休息"]
任务.each do |task|
  puts "今天要 #{task}"
end

输出:

今天要 写代码
今天要 开会
今天要 休息

8. 实践案例

案例 1:计时器

def timer
  start = Time.now
  yield
  end_time = Time.now
  puts "耗时:#{end_time - start} 秒"
end

timer do
  5.times { puts "执行中..." }
end

输出(示例):

执行中...
执行中...
执行中...
执行中...
执行中...
耗时:0.00234 秒

案例 2:自定义迭代

def repeat(n)
  n.times { |i| yield(i + 1) }
end

repeat(3) do |count|
  puts "第 #{count} 次问候:你好!"
end

输出:

第 1 次问候:你好!
第 2 次问候:你好!
第 3 次问候:你好!

案例 3:任务处理器

class TaskProcessor
  def process(tasks)
    tasks.each do |task|
      yield(task) if block_given?
    end
  end
end

processor = TaskProcessor.new
任务 = ["买菜", "做饭", "吃饭"]
processor.process(任务) do |task|
  puts "正在处理:#{task}"
end

输出:

正在处理:买菜
正在处理:做饭
正在处理:吃饭

9. 块 vs Proc vs Lambda

简单对比

  • :匿名代码,直接传递给方法。
  • Proc:块的对象化形式,可存储和传递。
  • Lambda:类似 Proc,但更严格(参数检查,返回行为)。

示例

# Proc
my_proc = Proc.new { |x| puts "Proc: #{x}" }
my_proc.call(5)  # Proc: 5

# Lambda
my_lambda = lambda { |x| puts "Lambda: #{x}" }
my_lambda.call(10)  # Lambda: 10

方法接收 Proc

def run_proc(&block)
  block.call
end

proc = Proc.new { puts "你好" }
run_proc(&proc)  # 你好

10. 注意事项

  • 性能:块是轻量级的,但大量嵌套可能影响可读性。
  • 作用域:块可以访问外部变量(闭包特性):
  x = 0
  3.times { x += 1 }
  puts x  # 3
  • 参数匹配:块参数数量应与 yield 匹配,否则可能报错。

下一步

  • 练习:告诉我你想用块做什么(遍历、自定义方法等),我可以设计一个例子。
  • 问题解答:对块用法有疑问吗?直接问我!
  • 深入学习:想了解 Proc、Lambda 或块在框架中的应用吗?我可以继续讲解。

你现在想做什么?写代码、问问题,还是其他?