Ruby 入门指南 / 第 05 章:控制流程
第 05 章:控制流程
“程序 = 数据结构 + 算法。” —— Niklaus Wirth
5.1 条件判断
5.1.1 if / elsif / else
# if 语法
score = 85
if score >= 90
puts "优秀"
elsif score >= 80
puts "良好"
elsif score >= 60
puts "及格"
else
puts "不及格"
end
# if 作为表达式(返回值)
grade = if score >= 90
"A"
elsif score >= 80
"B"
elsif score >= 60
"C"
else
"F"
end
puts grade # => "B"
5.1.2 三元运算符
age = 20
status = age >= 18 ? "成年人" : "未成年"
puts status # => "成年人"
# 嵌套(不推荐,影响可读性)
grade = score >= 90 ? "A" : score >= 80 ? "B" : "C"
5.1.3 unless(除非)
# unless 是 if 的反义
unless user.admin?
puts "你没有权限"
end
# 等价于
if !user.admin?
puts "你没有权限"
end
# unless + else(不推荐,影响可读性)
unless success?
puts "失败"
else
puts "成功"
end
# unless 作为后置条件
puts "没有权限" unless user.admin?
5.1.4 case / when
# 基本 case 语句
day = "Monday"
case day
when "Monday"
puts "星期一,加油!"
when "Tuesday", "Wednesday", "Thursday"
puts "工作日"
when "Friday"
puts "快到周末了!"
when "Saturday", "Sunday"
puts "周末愉快!"
else
puts "无效日期"
end
# case 返回值
result = case day
when "Monday" then "周一"
when "Tuesday" then "周二"
when "Wednesday" then "周三"
else "其他"
end
# case 与范围
score = 85
grade = case score
when 90..100 then "A"
when 80...90 then "B"
when 70...80 then "C"
when 60...70 then "D"
else "F"
end
# case 与正则表达式
url = "https://example.com"
case url
when /https/
puts "安全连接"
when /http/
puts "普通连接"
when /ftp/
puts "FTP 连接"
end
# case 与类型
value = 42
case value
when Integer then puts "整数: #{value}"
when String then puts "字符串: #{value}"
when Array then puts "数组: #{value}"
when Float then puts "浮点数: #{value}"
end
# 自定义 case 匹配(实现 === 方法)
class Temperature
def initialize(value)
@value = value
end
def ===(other)
case other
when Range
other.cover?(@value)
when Numeric
@value == other
else
false
end
end
end
temp = Temperature.new(37.5)
case temp
when 36..37 then puts "正常体温"
when 37..38 then puts "低热"
when 38..39 then puts "中热"
else puts "需要就医"
end
5.1.5 条件修饰符(后置条件)
# 单行条件
puts "正数" if x > 0
puts "偶数" if n.even?
return unless valid?
# 优雅的守卫条件
def process(data)
return nil if data.nil?
return [] if data.empty?
# 处理数据
data.map(&:upcase)
end
5.1.6 条件判断最佳实践
| 场景 | 推荐 | 避免 |
|---|---|---|
| 简单二选一 | if/else 或 ? : | 嵌套三元 |
| 多分支 | case/when | 多层 elsif |
| 守卫条件 | 后置 if/unless | 多层嵌套 |
| 空值检查 | value.nil? | value == nil |
| 布尔判断 | if value | if value == true |
5.2 循环结构
5.2.1 while 循环
# 基本 while
count = 0
while count < 5
puts "Count: #{count}"
count += 1
end
# 后置 while(至少执行一次)
count = 0
begin
puts "Count: #{count}"
count += 1
end while count < 5
# while 作为表达式
count = 0
result = while count < 3
count += 1
end
puts result # => nil
5.2.2 until 循环
# until 是 while 的反义
count = 0
until count >= 5
puts "Count: #{count}"
count += 1
end
# 后置 until
count = 0
begin
puts "Count: #{count}"
count += 1
end until count >= 5
5.2.3 for 循环
# for...in(不常用,推荐使用迭代器)
for i in 1..5
puts i
end
# 带索引
for i, v in ["a", "b", "c"].each_with_index
puts "#{i}: #{v}"
end
# ⚠️ 注意:for 循环不会创建新的作用域
x = 10
for x in 1..3
puts x
end
puts x # => 3(x 被修改了!)
5.2.4 loop 循环
# loop 是无限循环,需要 break 退出
count = 0
loop do
puts "Count: #{count}"
count += 1
break if count >= 5
end
# loop 返回值
result = loop do
break "done" if condition
end
puts result # => "done"
5.2.5 循环控制
# break - 退出循环
loop do
input = gets.chomp
break if input == "quit"
puts "You said: #{input}"
end
# next - 跳过当前迭代
(1..10).each do |i|
next if i.even?
puts i # 只打印奇数
end
# redo - 重试当前迭代
count = 0
[1, 2, 3].each do |i|
count += 1
redo if count < 3 && i == 1
puts "#{i} (count: #{count})"
end
# retry - 重试整个迭代器(谨慎使用)
begin
[1, 2, 3].each do |i|
raise "error" if i == 2
puts i
end
rescue
puts "caught error"
end
5.3 迭代器模式
5.3.1 Ruby 的迭代器哲学
# Ruby 不鼓励使用传统的 for 循环
# 而是使用迭代器(Iterator)
# ❌ 不推荐
for i in 0...array.length
puts array[i]
end
# ✅ 推荐
array.each { |item| puts item }
# ❌ 不推荐
i = 0
while i < array.length
puts array[i]
i += 1
end
# ✅ 推荐
array.each { |item| puts item }
5.3.2 核心迭代器
# each - 最基本的迭代器
[1, 2, 3].each { |n| puts n }
# each_with_index - 带索引
["a", "b", "c"].each_with_index do |item, index|
puts "#{index}: #{item}"
end
# map / collect - 转换
[1, 2, 3].map { |n| n * 2 } # => [2, 4, 6]
[1, 2, 3].map(&:to_s) # => ["1", "2", "3"]
# select / find_all - 过滤
[1, 2, 3, 4, 5].select(&:even?) # => [2, 4]
# reject - 反向过滤
[1, 2, 3, 4, 5].reject(&:even?) # => [1, 3, 5]
# find / detect - 查找第一个
[1, 2, 3, 4, 5].find { |n| n > 3 } # => 4
# reduce / inject - 聚合
[1, 2, 3, 4, 5].reduce(0) { |sum, n| sum + n } # => 15
[1, 2, 3, 4, 5].reduce(:+) # => 15
# all? / any? / none? / one? - 谓词
[2, 4, 6].all?(&:even?) # => true
[1, 2, 3].any?(&:even?) # => true
[1, 3, 5].none?(&:even?) # => true
[1, 2, 3].one? { |n| n == 2 } # => true
# count - 计数
[1, 2, 3, 4, 5].count(&:even?) # => 2
# min / max / minmax
[3, 1, 4, 1, 5].min # => 1
[3, 1, 4, 1, 5].max # => 5
[3, 1, 4, 1, 5].minmax # => [1, 5]
# sort / sort_by
[3, 1, 4, 1, 5].sort # => [1, 1, 3, 4, 5]
["banana", "apple"].sort_by(&:length) # => ["apple", "banana"]
# flat_map - 嵌套展平
[[1, 2], [3, 4]].flat_map { |a| a.map { |n| n * 2 } }
# => [2, 4, 6, 8]
# group_by - 分组
[1, 2, 3, 4, 5].group_by { |n| n.even? ? "even" : "odd" }
# => {"odd"=>[1, 3, 5], "even"=>[2, 4]}
# tally - 计数(Ruby 2.7+)
["a", "b", "a", "c", "b", "a"].tally
# => {"a"=>3, "b"=>2, "c"=>1}
5.3.3 链式迭代
# 链式调用让代码更简洁
result = (1..100)
.select(&:odd?) # 过滤奇数
.map { |n| n ** 2 } # 求平方
.select { |n| n > 100 } # 大于 100
.first(5) # 取前 5 个
.reduce(:+) # 求和
puts result
# 业务场景:处理用户数据
users = [
{ name: "Alice", age: 25, active: true },
{ name: "Bob", age: 17, active: true },
{ name: "Charlie", age: 30, active: false },
{ name: "David", age: 22, active: true }
]
adult_active_names = users
.select { |u| u[:active] }
.select { |u| u[:age] >= 18 }
.map { |u| u[:name] }
.sort
puts adult_active_names # => ["Alice", "David"]
5.4 块(Block)
5.4.1 块的定义
# 块是 Ruby 最独特的特性之一
# 块可以是 { } 或 do...end
# 单行块用 { }
[1, 2, 3].each { |n| puts n }
# 多行块用 do...end
[1, 2, 3].each do |n|
result = n * 2
puts "#{n} * 2 = #{result}"
end
# 块不是对象,不能单独存在
# { puts "hello" } # SyntaxError
# 块是方法的隐式参数
def greet
puts "Before block"
yield # 调用块
puts "After block"
end
greet { puts "Inside block" }
# 输出:
# Before block
# Inside block
# After block
5.4.2 yield 机制
# yield 调用传入的块
def say_hello
puts "Hello!"
yield
puts "Goodbye!"
end
say_hello { puts "Block called!" }
# Hello!
# Block called!
# Goodbye!
# yield 传递参数给块
def repeat(times)
times.times do |i|
yield i
end
end
repeat(3) { |i| puts "Iteration #{i}" }
# yield 返回块的返回值
def calculate
result = yield(10, 20)
puts "Result: #{result}"
end
calculate { |a, b| a + b } # => Result: 30
# 检查是否有块传入
def optional_block
if block_given?
yield
else
"No block given"
end
end
optional_block # => "No block given"
optional_block { "Block!" } # => "Block!"
5.4.3 块作为闭包
# 块捕获定义时的上下文(闭包)
def create_multiplier(factor)
-> (n) { n * factor } # factor 被捕获
end
double = create_multiplier(2)
triple = create_multiplier(3)
double.call(5) # => 10
triple.call(5) # => 15
# 块捕获局部变量
x = 10
[1, 2, 3].each do |n|
x += n # 块可以修改外部变量
end
puts x # => 16
5.5 高级迭代技巧
5.5.1 each_with_object
# each_with_object - 传递可变对象
result = [1, 2, 3, 4, 5].each_with_object({}) do |n, hash|
hash[n] = n ** 2
end
puts result # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# 业务场景:统计词频
words = %w[hello world hello ruby world hello]
freq = words.each_with_object(Hash.new(0)) do |word, hash|
hash[word] += 1
end
puts freq # => {"hello"=>3, "world"=>2, "ruby"=>1}
5.5.2 slice_when / chunk
# slice_when - 按条件切片
data = [1, 2, 4, 5, 7, 8, 10]
slices = data.slice_when { |a, b| b - a > 1 }.to_a
puts slices.inspect # => [[1, 2], [4, 5], [7, 8], [10]]
# chunk - 按条件分块
data = [1, 1, 2, 2, 3, 3, 1, 1]
chunks = data.chunk { |n| n }.to_a
puts chunks.inspect # => [[1, [1, 1]], [2, [2, 2]], [3, [3, 3]], [1, [1, 1]]]
5.5.3 each_cons / each_slice
# each_cons - 滑动窗口
[1, 2, 3, 4, 5].each_cons(2) do |pair|
puts pair.inspect
end
# [1, 2]
# [2, 3]
# [3, 4]
# [4, 5]
# each_slice - 分批处理
(1..10).each_slice(3) do |batch|
puts batch.inspect
end
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10]
# 业务场景:分页处理
def process_in_batches(records, batch_size = 100)
records.each_slice(batch_size).with_index do |batch, index|
puts "Processing batch #{index + 1} (#{batch.size} records)"
yield batch
end
end
5.5.4 Enumerator 和 Lazy
# Enumerator - 惰性枚举
enum = [1, 2, 3].each
puts enum.next # => 1
puts enum.next # => 2
puts enum.next # => 3
# Lazy - 惰性求值(处理大序列时有用)
# 不使用 Lazy(会创建中间数组)
(1..Float::INFINITY).select(&:even?).first(5) # 死循环!
# 使用 Lazy
(1..Float::INFINITY).lazy.select(&:even?).first(5) # => [2, 4, 6, 8, 10]
# Lazy 链式操作
result = (1..Float::INFINITY)
.lazy
.select(&:odd?)
.map { |n| n ** 2 }
.select { |n| n > 100 }
.first(5)
puts result.inspect # => [121, 169, 225, 289, 361]
5.6 异常处理中的控制流
5.6.1 begin / rescue / retry
# 重试机制
attempts = 0
begin
attempts += 1
puts "Attempt #{attempts}"
# 模拟可能失败的操作
raise "Network error" if attempts < 3
puts "Success!"
rescue => e
puts "Error: #{e.message}"
retry if attempts < 3
puts "Failed after #{attempts} attempts"
end
5.7 实际业务场景
5.7.1 数据验证
def validate_user(params)
errors = []
unless params[:name]&.length&.between?(2, 50)
errors << "名字长度必须在 2-50 之间"
end
unless params[:email]&.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
errors << "邮箱格式无效"
end
unless params[:age].is_a?(Integer) && params[:age].between?(0, 150)
errors << "年龄必须是 0-150 之间的整数"
end
if errors.empty?
{ success: true }
else
{ success: false, errors: errors }
end
end
5.7.2 状态机
class Order
STATES = %i[pending paid shipped delivered cancelled].freeze
def initialize
@state = :pending
end
def transition_to(new_state)
case [@state, new_state]
when [:pending, :paid] then proceed(new_state)
when [:paid, :shipped] then proceed(new_state)
when [:shipped, :delivered] then proceed(new_state)
when [:pending, :cancelled] then proceed(new_state)
when [:paid, :cancelled] then proceed(new_state)
else
raise "Invalid transition: #{@state} -> #{new_state}"
end
end
private
def proceed(new_state)
@state = new_state
puts "Order transitioned to: #{new_state}"
end
end
5.8 动手练习
- FizzBuzz:经典练习
# 打印 1-100,3 的倍数打印 Fizz,5 的倍数打印 Buzz,15 的倍数打印 FizzBuzz
(1..100).each do |n|
# 你的代码...
end
- 嵌套迭代:乘法表
# 打印 9x9 乘法表
# 你的代码...
- 数据转换:处理嵌套数组
data = [[1, 2], [3, 4], [5, 6]]
# 将嵌套数组展平并求偶数之和
# 你的代码...
5.9 本章小结
| 要点 | 说明 |
|---|---|
| 条件 | if/elsif/else、unless、case/when、三元运算符 |
| 循环 | while、until、loop、for...in(少用) |
| 迭代器 | each、map、select、reduce 等链式操作 |
| 块 | { } 和 do...end,通过 yield 调用 |
| 闭包 | 块捕获定义时的上下文变量 |
| 惰性 | Lazy 枚举器处理无限序列 |
📖 扩展阅读
- Ruby 迭代器文档
- Ruby 控制表达式
- 《The Well-Grounded Rubyist》—— David A. Black 著
上一章:← 第 04 章:变量与数据类型 下一章:第 06 章:方法 →