在掌握了 Scala 的基础语法和数据类型之后,我们接下来将深入学习如何控制程序的执行流程。本节将详细探讨 Scala 中的分支和循环结构,你将领略到 Scala 在流程控制方面独特的、富有表现力的设计。
思维导图
一、流程控制结构
顺序结构: 代码默认按照从上到下、从左到右的顺序执行。
这是最基本的执行模式,无需特殊语法。
分支结构 (if
): 根据条件决定代码执行路径。
if
语句用于基于一个布尔表达式的结果来选择性地执行代码块。
分支类型 | 语法 | 示例 |
---|---|---|
单分支 | if (condition) { ... } |
if (age >= 18) { println("Adult") } |
双分支 | if (condition) { ... } else { ... } |
if (score >= 60) { println("Pass") } else { println("Fail") } |
多分支 | if ... else if ... else ... |
if (grade == 'A') { ... } else if (grade == 'B') { ... } else { ... } |
嵌套分支 | 在一个 if 结构中嵌入另一个 |
if (isLogin) { if (isAdmin) { ... } } |
if
表达式: Scala 中的 if
语句是表达式,具有返回值。这个特性可以用来替代 Java 中的三元运算符。
if
表达式的返回值是被执行分支中最后一个表达式的值。
代码案例:
// 单分支
val age = 20
if (age >= 18) {
println("此人是成年人 (Adult)")
}
// 双分支
val score = 55
if (score >= 60) {
println("考试通过 (Pass)")
} else {
println("考试未通过 (Fail)")
}
// 多分支
val grade = 'B'
if (grade == 'A') {
println("优秀 (Excellent)")
} else if (grade == 'B') {
println("良好 (Good)")
} else if (grade == 'C') {
println("及格 (Average)")
} else {
println("需要努力 (Needs Improvement)")
}
// 嵌套分支
val isLogin = true
val isAdmin = false
if (isLogin) {
println("用户已登录。")
if (isAdmin) {
println("欢迎管理员!拥有所有权限。")
} else {
println("欢迎普通用户!拥有受限权限。")
}
} else {
println("用户未登录,请先登录。")
}
// if表达式返回值
val temperature = 25
val weatherDescription = if (temperature > 30) {
"炎热"
} else if (temperature < 10) {
"寒冷"
} else {
"温和"
}
println(s"今天天气: $weatherDescription")
}
}
val x = 10
// 使用 if 表达式给常量赋值
val result = if (x > 0) "positive" else "non-positive"
println(result) // 输出: positive
val score = 85
val grade = if (score >= 90) 'A' else if (score >= 80) 'B' else 'C'
println(s"Grade is: $grade") // 输出: Grade is: B
块表达式 ({}
):
- 使用花括号
{}
包围的多行代码构成一个块表达式。 - 块表达式的值是其最后一个语句的值。
代码案例:
val a = 5
val b = 10
val blockResult = {
println("Calculating...")
val sum = a + b
sum // 块表达式的最后一个语句是 sum, 所以它的值被赋给 blockResult
}
println(s"The result from the block is: $blockResult") // 输出 15
二、循环结构
for循环
Scala 的 for
循环远比传统命令式语言的 for
循环更灵活,它更像一个“生成器”。
for 循环特性 |
语法/示例 | 说明 |
---|---|---|
遍历范围 (Range) | for (i <- 1 to 10) for (i <- 1 until 10) |
to 包含上界 (1-10)。until 不包含上界 (1-9)。 |
循环守卫 (Guard) | for (i <- 1 to 10 if i % 2 == 0) |
在遍历过程中加入 if 条件进行过滤,只有满足条件的元素才会被处理。 |
嵌套循环 | for (i <- 1 to 3; j <- 1 to 3) |
可以将多个生成器用分号隔开写在同一行,代码更紧凑。 |
for 推导式 (Comprehension) |
val squares = for (i <- 1 to 5) yield i * i |
使用 yield 关键字,将 for 循环的每次迭代结果收集起来,构建成一个新的集合。 |
代码案例:
// 遍历范围并使用循环守卫
println("偶数 in 1 to 10:")
for (i <- 1 to 10 if i % 2 == 0) {
print(s"$i ")
}
println()
// 嵌套循环打印坐标
println("坐标:")
for (i <- 1 to 2; j <- 'a' to 'b') {
println(s"($i, $j)")
}
// for 推导式生成一个 List[Int]
val squares = for (i <- 1 to 5) yield i * i
println(s"Squares: $squares") // 输出: Squares: List(1, 4, 9, 16, 25)
while循环
格式: while (condition) { ... }
var i = 5
while (i > 0) {
println(i)
i -= 1
}
do-while循环
语法 (Scala 2):
// do {
// // 循环体
// } while (condition)
重要说明: do-while
语句在 Scala 3 中已被移除。 由于它在实践中使用较少且不符合函数式编程的表达式风格,Scala 3 将其废弃。任何 do-while
逻辑都可以用 while
循环或其他结构等价实现。
代码案例 (Scala 2):
// 以下代码仅在 Scala 2.x 环境中有效
var j = 5
do {
println(j)
j -= 1
} while (j > 0)
循环中断
Scala 本身没有内置的 break
和 continue
关键字,因为这鼓励使用更函数式的编程风格 (如使用过滤器、递归等)。但如果确实需要,可以通过标准库模拟。
标准做法: 导入
scala.util.control.Breaks
,并使用breakable
和break()
方法。
实现break
: 将整个循环用breakable
代码块包裹。
实现continue
: 将循环体内部需要跳过的部分用breakable
包裹。
代码案例 (模拟 break):
import scala.util.control.Breaks._
println("寻找第一个大于5的偶数:")
breakable {
for (i <- 1 to 10) {
if (i % 2 == 0 && i > 5) {
println(s"找到了: $i")
break // 中断 breakable 代码块,即跳出循环
}
}
}
代码案例 (模拟 continue):
import scala.util.control.Breaks._
println("\n打印 1到10 之间的所有奇数:")
for (i <- 1 to 10) {
breakable { // 这个 breakable 块放在循环内部,用于模拟 continue
if (i % 2 == 0) {
break // 当 i 是偶数时,跳出内部的 breakable 块
}
println(s"奇数: $i") // 这行代码只在 i 是奇数时执行
}
}
三、综合案例:打印九九乘法表
这个经典案例很好地展示了嵌套循环和字符串格式化的应用。
方法一:传统嵌套 for
循环
println("--- 九九乘法表 (传统嵌套 for) ---")
for (i <- 1 to 9) {
for (j <- 1 to i) {
print(s"$j * $i = ${i * j}\t")
}
println() // 每行结束后换行
}
方法二:使用分号的紧凑型嵌套 for
循环
println("\n--- 九九乘法表 (紧凑型 for) ---")
for (i <- 1 to 9; j <- 1 to i) {
print(s"$j * $i = ${i * j}\t")
if (j == i) println() // 当内层循环结束时换行
}
解析:这种紧凑写法将两个生成器放在同一行。if (j == i)
这个判断是关键,它确保了只在每一行的最后一个表达式打印完毕后才执行换行操作。
练习题
题目一:if
表达式
编写一段代码,使用 if
表达式判断一个整数 num
是正数、负数还是零,并将结果 (“positive”, “negative”, “zero”) 赋值给一个 val
变量 sign
。
题目二:for
循环与 to
使用 for
循环打印出从 5 到 1 (递减) 的所有整数。
题目三:for
循环与 until
使用 for
循环打印出 100 以内的所有 7 的倍数 (不包含100)。
题目四:循环守卫
使用 for
循环和循环守卫,找出 1 到 50 之间所有既能被 3 整除又能被 5 整除的数。
题目五:嵌套 for
循环
使用嵌套的 for
循环打印一个 4x4 的星号 *
矩阵。
题目六:for
推导式 (生成新集合)
使用 for
推导式,将一个 List("apple", "banana", "cherry")
转换为一个新的 List
,其中每个单词都变成大写。
题目七:while
循环
使用 while
循环计算 1 到 100 的所有整数之和。
题目八:模拟 break
给定一个 List(1, 4, 9, 16, 25, 36, 49)
,使用 breakable
和 break
找到并打印出第一个大于 30 的数后立即停止循环。
题目九:块表达式返回值
编写一个块表达式,它接收两个变量 x
和 y
,在内部计算它们的平方和 (x*x + y*y
),并将这个结果作为块表达式的值赋给一个 val
变量 result
。
题目十:if
表达式的类型
分析以下代码,result
的类型会被推断为什么?并解释原因。
val condition = true
val result = if (condition) 100 else "Error"
题目十一:for
循环步长
使用 for
循环和 by
关键字,打印出从 0 到 20,步长为 5 的所有数字 (0, 5, 10, 15, 20)。
题目十二:for
推导式与守卫
给定一个 List(1, 2, 3, 4, 5, 6)
,使用 for
推导式和循环守卫,生成一个新的 List
,其中只包含原列表中偶数的平方。
题目十三:if
表达式与 Unit
类型
分析以下代码,result
的值和类型是什么?
val result = if (false) "Success"
题目十四:模拟 continue
使用 breakable
和 break
模拟 continue
的效果:打印 1 到 10 中所有的奇数。
题目十五:综合应用 (FizzBuzz)
编写一个程序,使用 for
循环遍历 1 到 100。对于每个数字,使用 if-else if-else
结构判断:
- 如果数字能被 15 整除,打印 “FizzBuzz”。
- 如果只能被 3 整除,打印 “Fizz”。
- 如果只能被 5 整除,打印 “Buzz”。
- 否则,打印数字本身。
答案与解析
答案一:
val num = -5
val sign = if (num > 0) "positive" else if (num < 0) "negative" else "zero"
println(s"The sign of $num is: $sign")
解析:
if-else if-else
结构作为表达式,其结果可以直接赋值给sign
变量。
答案二:
for (i <- 5 to 1 by -1) {
println(i)
}
解析:
for
循环可以通过by
关键字指定步长,步长为负数时即为递减。
答案三:
for (i <- 1 until 100 if i % 7 == 0) {
println(i)
}
解析:
until
创建了一个不包含上界 (100) 的范围,循环守卫if i % 7 == 0
过滤出了 7 的倍数。
答案四:
for (i <- 1 to 50 if i % 3 == 0 && i % 5 == 0) {
println(i)
}
解析: 循环守卫可以包含复杂的逻辑条件,如使用
&&
(与)。
答案五:
for (i <- 1 to 4) {
for (j <- 1 to 4) {
print("* ")
}
println() // 每行结束后换行
}
解析: 外层循环控制行数,内层循环控制每行打印的星号数量。
答案六:
val words = List("apple", "banana", "cherry")
val upperWords = for (word <- words) yield word.toUpperCase
println(upperWords) // 输出: List(APPLE, BANANA, CHERRY)
解析:
for-yield
结构遍历words
列表,对每个元素word
应用.toUpperCase
方法,并将结果收集到一个新的List
中。
答案七:
var sum = 0
var i = 1
while (i <= 100) {
sum += i
i += 1
}
println(s"The sum is: $sum")
解析: 经典的
while
循环用法,需要一个可变的循环控制变量i
和累加变量sum
。
答案八:
import scala.util.control.Breaks._
val numbers = List(1, 4, 9, 16, 25, 36, 49)
breakable {
for (num <- numbers) {
if (num > 30) {
println(s"Found number greater than 30: $num")
break
}
}
}
解析:
breakable
包裹了整个循环,当num > 30
条件满足时,break
会中断breakable
块的执行,从而跳出循环。
答案九:
val x = 3
val y = 4
val result = {
println("Performing calculation...")
val xSquare = x * x
val ySquare = y * y
xSquare + ySquare // 这是块的最后一个表达式,它的值将赋给 result
}
println(result) // 输出 25
解析: 块表达式的值由其最后一个表达式
xSquare + ySquare
决定。
答案十:
result
的类型会被推断为 Any
。
解析: Scala 的
if-else
表达式的返回类型是两个分支返回类型的最近公共父类型。100
的类型是Int
,"Error"
的类型是String
。Int
和String
的最近公共父类型是Any
。
答案十一:
for (i <- 0 to 20 by 5) {
println(i)
}
解析:
by
关键字用于指定循环的步长。
答案十二:
val numbers = List(1, 2, 3, 4, 5, 6)
val evenSquares = for (n <- numbers if n % 2 == 0) yield n * n
println(evenSquares) // 输出: List(4, 16, 36)
解析:
for-yield
结合循环守卫,先通过if n % 2 == 0
过滤出偶数,然后yield
出它们的平方。
答案十三:
result
的值是 ()
(Unit 的实例),类型是 Any
。
解析: 这个
if
语句没有else
分支。当条件为false
时,if
语句没有返回值。在 Scala 中,这种情况的“无返回值”由Unit
类型表示。因此,整个if
表达式的类型是String
和Unit
的最近公共父类型,即Any
。
答案十四:
import scala.util.control.Breaks._
for (i <- 1 to 10) {
breakable {
if (i % 2 == 0) {
break // 如果是偶数,中断 breakable 块,效果类似 continue
}
println(i) // 这行代码只在 i 是奇数时执行
}
}
解析: 将
breakable
块放在循环体内部。当满足某个条件时调用break
,只会跳出当前的breakable
块,然后继续下一次循环,从而模拟了continue
的效果。
答案十五:
for (i <- 1 to 100) {
if (i % 15 == 0) {
println("FizzBuzz")
} else if (i % 3 == 0) {
println("Fizz")
} else if (i % 5 == 0) {
println("Buzz")
} else {
println(i)
}
}
解析: 这是一个经典的编程问题。关键在于首先检查能被 15 (3和5的最小公倍数) 整除的条件,因为如果先检查3或5,那么15的倍数就会被错误地归类。
日期:2025年9月3日
专栏:Scala教程