CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Ruby 语言入门/课时/控制流
RawBlame
课时目录12 节

Ruby 语言入门

  1. 01Ruby 环境
  2. 02数据类型
  3. 03语法基础
  4. 04控制流
  5. 05方法与 Blocks
  6. 06OOP 与 Mixin
  7. 07元编程
  8. 08IO 与 Enumerable
  9. 09并发
  10. 10Gems 与框架
  11. 11测试与质量
  12. 12综合练习

控制流

each/times/case。

30 分钟

控制流

这一课你将能做到

  1. 熟练使用 if/unless/case、while/until/for 处理分支与循环
  2. 用 each/times/map/select/inject 等迭代器写出地道的 Ruby 循环,而不是传统 for 循环
  3. 正确使用 break/next/redo/retry 控制迭代行为,并理解迭代器与循环的本质区别

为什么要学

控制流是任何语言的核心,但 Ruby 的控制流有自己的「味道」:除非必要,不用 for 循环;unless 后置让条件读起来像自然语言;迭代器(iterator)是 Ruby 的灵魂——Rails 的 find_each、Sidekiq 的批量处理,底层都是迭代器。控制流不熟,Ruby 代码能写,但写不地道。

人话:控制流是程序的「交通指挥」。传统的 if/while/for 是手动挡,Ruby 的迭代器是自动挡——你告诉它「对每个元素做什么」,而不是「怎么一步步做」。

先建立图像

控制流选择指南
┌──────────────────┬──────────────────────────┐
│ 场景              │ 推荐写法                  │
├──────────────────┼──────────────────────────┤
│ 条件分支(2-3 个)   │ if / unless / 三目          │
│ 条件分支(多个值)    │ case-when                │
│ 固定次数循环        │ 3.times { }              │
│ 遍历集合           │ each / map / select / inject │
│ 条件循环           │ while / until / loop { }    │
│ 提前退出迭代        │ break / next             │
└──────────────────┴──────────────────────────┘

核心心法:Ruby 程序员首选迭代器,其次才是循环。

跟着做

步骤 1:if / unless / case

score = 85

# if-elsif-else
if score >= 90
  puts "优秀"
elsif score >= 60
  puts "及格"
else
  puts "不及格"
end

# unless:if not 的语义化写法,否定条件更自然
unless score < 60
  puts "通过了"
end

# 后置写法:一行时最简洁
puts "高分" if score >= 90
puts "继续加油" unless score >= 90

# case-when:多值匹配,区间、类型、正则都能用
grade = case score
        when 90..100 then "A"
        when 80...90 then "B"
        when 60...80 then "C"
        else "D"
        end
puts "等级:#{grade}"

# case 还能按类型匹配
value = "hello"
desc = case value
       when Integer then "整数"
       when String then "字符串"
       when Array then "数组"
       else "其他"
       end
puts desc   # => 字符串

步骤 2:while / until / loop

# while:条件为真时循环
count = 0
while count < 3
  puts "while: #{count}"
  count += 1
end

# until:条件为假时循环(while not 的语义化)
until count >= 5
  puts "until: #{count}"
  count += 1
end

# loop:无限循环,配合 break 退出
number = 0
loop do
  number += 1
  puts number
  break if number >= 3
end

# 后置修饰符:简洁但注意——do-while 语义!
num = 0
begin
  num += 1
  puts num
end while num < 2   # 先执行一次,再判断条件

步骤 3:迭代器——Ruby 的灵魂

# each:最基础的遍历
[1, 2, 3].each do |n|
  puts "元素: #{n}"
end

# times:固定次数
3.times { |i| puts "第 #{i} 次" }   # i 从 0 开始

# map:变换,返回新数组
squares = [1, 2, 3].map { |n| n * n }
puts squares.inspect   # => [1, 4, 9]

# select / reject:过滤
evens = [1, 2, 3, 4, 5].select { |n| n.even? }
odds  = [1, 2, 3, 4, 5].reject { |n| n.even? }
puts evens.inspect   # => [2, 4]
puts odds.inspect    # => [1, 3, 5]

# inject / reduce:累积
sum = [1, 2, 3, 4].inject(0) { |acc, n| acc + n }
puts sum   # => 10
# 简写:传符号
puts [1, 2, 3, 4].inject(:+)   # => 10
puts [1, 2, 3, 4].sum          # => 10(Ruby 2.4+)

# each_with_index:带索引遍历
["a", "b", "c"].each_with_index do |item, idx|
  puts "#{idx}: #{item}"
end

# each_with_object:边遍历边构造新对象
hash = ["a", "b"].each_with_object({}) do |letter, h|
  h[letter] = letter.upcase
end
puts hash.inspect   # => {"a"=>"A", "b"=>"B"}

步骤 4:break / next / redo

# break:跳出整个迭代
[1, 2, 3, 4, 5].each do |n|
  break if n > 3
  puts n
end
# 输出 1, 2, 3

# next:跳过当前元素,继续下一个
[1, 2, 3, 4, 5].each do |n|
  next if n.even?
  puts n
end
# 输出 1, 3, 5

# redo:重新执行当前迭代(不推进到下一个元素)
attempts = 0
[1, 2, 3].each do |n|
  attempts += 1
  puts "尝试 #{n}(第 #{attempts} 次)"
  redo if attempts < 2   # 前两次都重来
end
# 输出:尝试 1(第 1 次),尝试 1(第 2 次),尝试 2(第 3 次)...

# each 的返回值是原数组本身,这很重要!
result = [1, 2].each { |n| puts n }
puts result.inspect   # => [1, 2],不是 nil!

讲清楚要点

  1. 迭代器 vs 循环:迭代器(each/map/select)是对象的方法,把「怎么做」委托给对象;循环(while/for)是语言结构,显式控制流程。Ruby 社区文化:能用迭代器就不用循环。
  2. for 循环在 Ruby 里几乎不用:for i in [1,2,3] 会创建局部变量泄露到外部作用域,且性能差。除非面试,生产代码里见到 for 可以提 PR 改掉。
  3. next 不是 continue:在 Ruby 迭代器里,next 跳过当前元素到下一个,语义和 Java 的 continue 一样;但 next 还能带返回值:next n * 2。
  4. redo 和 retry 的区别:redo 重新执行当前迭代(不重新取值);retry 重新进入整个迭代器(Ruby 2.2+ 在迭代器里禁用,只在 begin-rescue 里可用)。日常几乎只用 redo 做重试逻辑。
  5. while 后置是 do-while:begin ... end while cond 先执行一次再判断;普通 while cond ... end 先判断。这个区别和 C 语言一致,但 Ruby 中较少用。

你可能会懵的地方

  • each 和 map 都能遍历,什么时候用哪个? 要「副作用」(打印、写库、发请求)用 each;要「变换结果」(转成新数组)用 map。map 的返回值是新数组,each 返回原数组。

  • select 和 detect/find 的区别? select 返回所有匹配元素的数组;detect(别名 find)返回第一个匹配的元素或 nil。找单个用 find,过滤多个用 select。

  • inject 的括号参数是什么? inject(0) 里的 0 是初始累积值。不给初始值时,第一个元素当初始值:[1,2,3].inject(:+) 等价 1 + 2 + 3。给初始值更安全,空数组不会报错。

  • loop 和 while true 有什么区别? loop 是 Kernel 方法,配合 break 使用,还能捕获 StopIteration 异常;while true 是语言结构。loop 更 Ruby 风格,还能 loop do ... break value end 返回值。

  • 迭代器里能用 return 吗? 在方法里调用迭代器,return 会直接退出整个方法,不是退出迭代器。这是 block 的闭包特性,和 JavaScript 的 forEach 里 return 只跳过当前元素完全不同!

错法对照

错法问题正解
用 for 循环遍历数组变量泄露、不地道用 each/map/select
each 里想收集结果each 返回原数组用 map 或 inject
迭代器里 return 想跳过退出整个方法用 next
while 后置以为先判断do-while 语义,先执行一次先判断用 while ... end 前置
if score > 90 写成 if score > 90 thenthen 在单行 case 才常用if 里省略 then,保持简洁

卡点急救

  1. 迭代器里变量没生效:block 参数 |n| 和外部变量同名会遮蔽。改参数名或检查作用域。
  2. break 没退出循环:可能在嵌套迭代器里,break 只退当前层。用 catch/throw 或重构逻辑。
  3. 无限循环:loop do 里忘了 break 条件。Ctrl+C 终止,检查条件逻辑。

面试常问

  • Ruby 的迭代器和循环有什么区别?为什么推荐迭代器? 迭代器把遍历逻辑封装在对象内部,代码更声明式、更少出错;循环是命令式,暴露索引和边界。Ruby 社区认为迭代器更符合「告诉它做什么,而不是怎么做」的哲学。

  • each 的返回值是什么?为什么? 返回调用它的原数组。设计意图:each 用于副作用,返回值不重要;如果需要变换结果,应该用 map。这个设计让 each 链式调用时不会意外创建新数组。

  • break 和 next 在 block 里的行为? break 退出整个迭代器,可以带返回值:break "done";next 跳过当前元素到下一个,也可以带返回值:next n * 2。两者都是 block 的局部控制流。

自测三问

  1. each、map、select、inject 各自的返回值和用途?
  2. unless 和 if ! 等价吗?什么场景用 unless 更好?
  3. 在迭代器里想「跳过当前元素」和「退出整个迭代」分别用什么关键字?

小结一句话

Ruby 控制流的精髓是:分支用 if/unless/case,遍历用 each/map/select/inject,循环是最后的手段。

延伸

  • Ruby 迭代器官方文档
  • 下一课:方法与 Blocks——理解 Ruby 的代码块和 yield 机制。
加载本节练习…
← 上一课语法基础下一课 →方法与 Blocks