【仓颉语言入门 · 第4课】
【仓颉语言入门 · 第4课】分支结构:if 与 when 表达式
上节课结尾我们留了一个"钩子":三元运算符
? :只能处理简单的二选一,真实程序里常有"大于 85 优秀、大于 60 及格、否则不及格"这种多分支逻辑。本文是系列第 4 课:正式学习仓颉的两大分支结构——if 条件分支和 when 匹配分支。学完之后,程序就能"看情况办事":根据数据的不同走不同的路,这几乎是所有业务逻辑的骨架。
目录(系列导航)
整套路线共 7 个模块、30 课,按每周 2~3 课的节奏,大约 2~3 个月可以走完一遍:
| 模块 | 课次 | 内容 |
|---|---|---|
| 一、环境与入门 | 01~05 | 环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环 |
| 二、常用类型与数据组织 | 06~10 | 字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理 |
| 三、函数与函数式 | 11~14 | 函数、Lambda 与高阶函数、闭包、迭代器与惰性序列 |
| 四、面向对象与类型系统 | 15~20 | struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展 |
| 五、工程化与标准库 | 21~25 | cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试 |
| 六、并发编程 | 26~28 | 线程、Channel 通道与同步原语、并发实战 |
| 七、项目实战 | 29~30 | 命令行小工具、GeoJSON 数据处理实战 |
- 环境搭建与第一个仓颉程序
- 变量与常量:let / var 与基本数据类型
- 运算符与标准输入输出
- 分支结构:if 与 match 表达式(本文)
- 循环结构:while / for / Range
- 字符串详解与字符串插值
- 数组 Array 与区间 Range
- 集合框架:ArrayList、HashMap、HashSet
- 可空类型
?与 Option - 错误处理:异常机制与 Result
- 函数定义、参数与返回值
- Lambda 表达式与高阶函数
- 闭包、作用域与函数类型
- 迭代器 Iterator 与 Sequence
- 结构体 struct 与类 class
- 构造函数、属性与方法
- 接口 interface 与实现
- 枚举 enum、代数数据类型与 match 模式匹配
- 泛型编程
- 扩展、类型别名与可见性控制
- cjpm 包管理与多文件项目组织
- 文件与目录 IO
- JSON 处理(结合 stdx 扩展库)
- 网络编程入门
- 单元测试
- 并发基础:线程的创建与等待
- Channel 通道与同步原语
- 并发实战:多线程任务处理
- 实战一:带文件持久化的命令行小工具
- 实战二:GeoJSON 数据处理程序
一、if 分支:让程序学会"看情况办事"
1.1 先跑一段整体感受一下
package helloCangjie
import std.env.*
import std.convert.*
main(): Int64 {
print("请输入你的年龄:")
let age = Int64.parse(getStdIn().readln().getOrThrow())
if (age >= 18) {
println("已成年,可以独立签署协议。")
} else {
println("未成年,请在监护人陪同下操作。")
}
return 0
}
运行两次,分别输入 20 和 12,程序会给出不同的回应——这就是分支:条件成立走这条路,不成立走那条路。
请输入你的年龄:20
已成年,可以独立签署协议。
请输入你的年龄:12
未成年,请在监护人陪同下操作。
1.2 语法要点:条件、大括号、缩进
把 if 的语法拆开看:
if (条件) {
条件为 true 时执行的语句块
} else {
条件为 false 时执行的语句块
}
三个初学者必守的规矩:
- 条件必须写在小括号里,且结果必须是
Bool类型(第 3 课学过,比较和逻辑运算的结果就是Bool); - 语句块必须用大括号包住,哪怕里面只有一条语句也不能省——这是仓颉的强制规定,不给自己和别人留"悬空 else"的歧义;
- 块内语句用 4 个空格缩进,写完可以按 Ctrl + Alt + L 让 CIDE 自动排版。
else 块是可选的:只想在条件成立时做点事,不成立时什么都不做,就省略 else。
1.3 条件必须是 Bool:仓颉不玩"默认转换"
很多语言允许写 if (n),让数字"非零即真"。仓颉不允许:
let n: Int64 = 5
// if (n) { } // 编译错误:条件必须是 Bool
if (n != 0) { // 正确:明确写出比较
println("n 不是 0")
}
这是一道防呆设计:条件到底是什么含义,代码里必须写得明明白白。同理,if (name)、if (list) 这类写法统统不合法,必须显式写成 name != ""、list.size > 0 等。
💡 记住这条铁律:if 的括号里只认
Bool。看到"条件"就先想"这个表达式算出来是不是 true / false"。
1.4 else if 链:多档条件逐级筛选
现实中的判断往往不止两档。用 else if 可以把多个条件串成一条链,从上往下逐个检查,命中一个就执行并跳出整条链:
if (score >= 85) {
println("优秀")
} else if (score >= 60) {
println("及格")
} else {
println("不及格")
}
两个关键点:
- 顺序决定语义:程序先看
score >= 85,不成立才看score >= 60。所以走到第二个分支时,score < 85已经是隐含前提,不用写成score >= 60 && score < 85; - else 兜底必在最后:它承接前面所有条件都不成立的情况。
⚠️ 如果把条件顺序写反(先判 >= 60 再判 >= 85),90 分会在第二个分支被"截胡"输出"及格"——这是 if 链最经典的逻辑 bug,动手写时务必从最严的条件排到最松的。
1.5 条件组合:&&、|| 与分支嵌套
第 3 课的逻辑运算符在这里派上大用场:
// 一个条件组合多个判断
if (score >= 0 && score <= 100) {
println("分数合法")
}
// 嵌套:条件里还有条件
if (score >= 60) {
if (score < 85) {
println("及格,继续加油")
}
}
嵌套能不用尽量不用——两层嵌套还看得懂,四五层就成"俄罗斯套娃"了。能用 && 合并的条件就合并:score >= 60 && score < 85 一眼看懂,两段嵌套读起来费劲。
二、if 是表达式:分支也能"算出值"
第 3 课介绍三元运算符时提过:仓颉的 if 本身是表达式——整条 if 语句算完会有一个值(命中分支里最后一个表达式的值),可以直接赋给变量:
let a = 12
let b = 30
let max = if (a > b) { a } else { b }
println("较大值是 ${max}") // 较大值是 30
两个使用限制:
- 两个分支的"结果类型"必须一致:一个分支给
Int64、另一个给String,编译器直接报错; - 必须有 else:条件不成立时总得有个值可取,没有 else 的 if 没有确定的值。
💡 很多语言(C、Java、JS)还有一个"三元运算符"
条件 ? 值1 : 值2用来二选一取值——仓颉没有这个运算符,?在仓颉里另有用途(第 9 课的可空类型)。想挑值就用上面的 if 表达式,能力完全覆盖,语义也更清楚。
有了 if 表达式,第 3 课计算器里"除数为 0 给提示"的判断现在可以升级成更自然的写法(第 3 课用的是提前 return 的 if 语句,这里用 if 表达式直接算出要显示的内容):
let x = 17
let y = 5
let divResult = if (y == 0) {
"除数不能为 0"
} else {
"${x / y}"
}
println(divResult) // 3
三、match 表达式:多分支的利器
条件一多,if 链就开始显得啰嗦。仓颉提供了专门的匹配分支 match,语法更清爽、表达力更强。
3.1 基本形式:一个值,若干个"case"
let level = 2
match (level) {
case 1 => println("新手")
case 2 => println("进阶")
case 3 => println("高手")
case _ => println("神秘玩家")
}
读法:拿 level 的值,从上到下逐个和 case 后面的模式比对,命中哪个就执行哪个分支,一个都匹配不上就走 case _(_ 是通配符,匹配任何值)。
和 if 链相比,match 有三个讨喜的特性:
| 特性 | 说明 |
|---|---|
| 无穿透 | 命中一个分支后自动结束,不需要写 break(C / Java 的 switch 老用户狂喜) |
| 多值合并 | `case 1 |
| 必须处理"其余情况" | 用 case _ 显式接住漏网的值,编译器会盯着 |
💡 小提示:像上面这样用字面量写示例,编译器可能给出
unreachable pattern警告——那是因为编译器静态分析发现level恒为 2,某些分支永远走不到。真实程序里值来自运行时(输入、计算结果),不会有这个警告,学习阶段见到不必慌张。
3.2 多值合并与"范围"匹配
match 的 case 后面不只是常量,还支持丰富的模式。多值合并用 | 把几个值连起来:
let month = 8
match (month) {
case 3 | 4 | 5 => println("春季") // 多值合并:一网打尽
case 6 | 7 | 8 => println("夏季")
case 9 | 10 | 11 => println("秋季")
case 12 | 1 | 2 => println("冬季")
case _ => println("月份不合法")
}
连续范围(分数段、年龄段)没有直接的"区间模式",但配合下一节的 where 守卫可以轻松表达,而且比一长串 | 更干净:
let score = 92
match (score) {
case s where s >= 85 => println("优秀") // 85 及以上
case s where s >= 60 => println("及格,得分 ${s}") // 走到这里说明 < 85
case _ => println("不及格")
}
注意守卫分支是从上往下逐条筛选的,和 if 链一样要"从严到松"排列——第二个分支能走到,说明 s < 85 已经是隐含前提,不用重复写。
3.3 绑定与守卫:匹配中还能加条件
case s where 条件 叫做绑定 + 守卫:先把被匹配的值绑定到新名字 s,再用 where 后面的布尔条件决定是否命中——分支里还能直接用 s 参与计算:
let score = 95
let remark = match (score) {
case s where s == 100 => "满分!"
case s where s >= 85 => "优秀,离满分只差 ${100 - s} 分"
case _ => "继续努力"
}
println(remark) // 优秀,离满分只差 5 分
如果只是想对"一组互斥条件"逐条筛选(不是对同一个值做模式匹配),直接用 if / else if 链就好,二者表达力等价,if 链更直白。
3.4 穷尽性:编译器逼你想清楚"其他情况"
match 有一个 if 链没有的严格约束:当它的值被使用时(比如赋给变量),所有可能的取值都必须有归宿。也就是说,除非每个可能值都被 case 覆盖,否则必须写 case _ 兜底,否则编译报错。
// 用 match 表达式给变量赋值:必须有 case _,否则编译错误
let remark = match (score) {
case s where s >= 85 => "优秀"
case s where s >= 60 => "及格"
case _ => "不及格"
}
这看似麻烦,实则是保命符:以后给数据新增类别时,所有没跟着更新的 match 会当场全部编译报错,一个都漏不掉。if 链就做不到这一点——漏了个 else 分支,编译器不吭声,bug 悄悄上线。
四、if 链还是 match?怎么选
| 场景 | 推荐 |
|---|---|
条件是范围判断(>=、<),且各条件彼此独立 | if / else if 链 |
| 判断的是同一个值落在哪些离散类别(枚举值、固定选项) | match + 多值/守卫模式 |
| 需要"算出一个值"并且要求覆盖所有情况 | match 表达式(穷尽性兜底) |
| 只是简单的二选一挑值 | if 表达式 |
一句话总结:对"一个值分类"用 match,对"多个条件各自为政"用 if。两者不冲突,实战里经常配合出现——先用 if 排除非法输入,再用 match 给合法数据分类,下面实操就是这么写的。
五、CIDE 实操:成绩等级评定程序(综合实战)
综合本课知识:读入一个分数,先做合法性检查(if),再用 match 划分等级,最后给出一句评语(match 表达式)。
5.1 编写程序
继续使用 helloCangjie 项目(也可 Ctrl + N 新建),把 src/main.cj 替换为:
package helloCangjie
import std.env.*
import std.convert.*
main(): Int64 {
print("请输入你的分数(0~100):")
let score = Int64.parse(getStdIn().readln().getOrThrow())
// 第一步:if 排除非法输入
if (score < 0 || score > 100) {
println("${score} 不是合法分数,请输入 0~100 之间的整数。")
return 0
}
// 第二步:match 守卫划分等级(条件从严到松排列)
match (score) {
case s where s >= 90 => println("等级:优秀 🎉")
case s where s >= 85 => println("等级:优秀,但离满分档还差 ${90 - s} 分")
case s where s >= 60 => println("等级:及格")
case _ => println("等级:不及格")
}
// 第三步:match 表达式生成评语(穷尽性要求必须有 case _)
let remark = match (score) {
case s where s >= 90 => "非常出色,保持这个状态!"
case s where s >= 75 => "基础扎实,还有上升空间"
case s where s >= 60 => "刚刚过线,要加把劲了"
case _ => "这次先欠着,下次翻身"
}
println("评语:${remark}")
return 0
}
写完按 Ctrl + Alt + L 格式化。
5.2 运行与验证
按 Ctrl + R 运行,多测几组输入覆盖所有分支:
请输入你的分数(0~100):92
等级:优秀 🎉
评语:非常出色,保持这个状态!
请输入你的分数(0~100):85
等级:优秀,但离满分档还差 5 分
评语:基础扎实,还有上升空间
请输入你的分数(0~100):59
等级:不及格
评语:这次先欠着,下次翻身
请输入你的分数(0~100):150
150 不是合法分数,请输入 0~100 之间的整数。
留意两个细节:
- 输入
85时,第一个 match 里没有命中s >= 90,落到s >= 85分支;而评语的 match 分段不同(85 落在s >= 75档)——两处 match 各管各的分段,互不干扰; - 输入
150时程序提前return 0退出,后面两段根本没执行——"先挡非法,再处理正常"是分支设计的常用套路。
📸 配图建议:Result 面板显示多组输入(高分 / 临界分 / 低分 / 非法值)的完整输出截图。
5.3 用调试器观察"分支走向"
上次我们用调试器看过变量,这次观察程序在分支岔路口如何选路:
- 在
if (score < 0 || score > 100)这一行左侧行号沟槽点击打断点; - 按 Shift + F9 启动调试,在调试控制台输入
70回车; - 按 F8 单步,会看到程序跳过了
println("不是合法分数")直接落到第一个 match——这就是"条件不成立,分支不走"的直观呈现; - 继续单步,程序命中第一个 match 的
case s where s >= 60一行,前后两个 case 一晃而过; - 打开 Variables(变量)面板确认
score = 70;把鼠标悬停在代码里的remark上,悬浮框会显示它被 match 表达式赋的值"刚刚过线,要加把劲了"; - 小实验:重新调试一次,输入
150,单步观察程序怎么走进if的真分支并在return 0处提前离场。
📸 配图建议:调试停在 match 命中行时,编辑器高亮 + Variables 面板 score 值 + 悬停 remark 弹框的截图。
调试结束点停止按钮,结束调试会话。
六、常见问题 FAQ
Q1:if (score) 这样的写法为什么报错?
仓颉的条件必须是 Bool 类型,不做"非零即真"之类的隐式转换。请显式写出完整条件,如 if (score != 0)。这能避免大量语义不清的条件判断。
Q2:match 的分支需要 break 吗?
不需要。match 命中一个 case 后自动结束整个匹配,没有"穿透"行为,也就没有 break 的必要。想匹配多个值请写成 case 1 | 2 =>,而不是让分支故意穿透。
Q3:为什么 match 最后总被要求写 case _?
当 match 的结果被使用(赋值、作为返回值)时,编译器要求穷尽所有可能取值;case _ 就是显式的"其余情况"兜底。仅作为语句执行、不使用结果时可以省略,但建议永远写上——日后数据新增类别时,漏更新的 match 会直接编译报错,而不是埋下运行时 bug。
Q4:if 表达式两个分支类型不一样会怎样?
编译错误。if (x > 0) { 1 } else { "一" } 无法确定整体类型。分支结果是"二选一的值"时,两边的类型必须一致。
Q5:if / match 的分支只有一条语句,能省略大括号吗?
if 的分支不能省,必须用 { } 包住,这是仓颉的强制语法。match 的每个 case 是 => 表达式 的形式,本来就不涉及大括号歧义。
Q6:仓颉为什么没有三元运算符 ? :?
设计取舍:cond ? a : b 能做的,if (cond) { a } else { b } 同样能做且可读性更好,还能顺带支持分支内多条语句。少一个符号化的运算符,代码意图反而更清楚。需要"一行挑一个值"时用 if 表达式即可。
Q7:match 里能直接写 case 85..=100 这样的区间吗?
不能。仓颉的 case 模式不直接支持区间,连续范围用守卫表达:case s where s >= 85 =>(配合"从严到松"的排列顺序,隐含了上界)。离散的多个值才用 case 1 | 2 | 3 => 一网打尽。
Q8:字面量测试时编译器报 unreachable pattern 警告,是代码错了吗?
不是。这是编译器静态分析发现某个分支按当前字面量值永远走不到(比如 let level = 2 时 case 1 永远不命中),属于善意的提醒。真实程序里值来自运行时输入或计算,不会有这个警告;学习阶段看到可以放心忽略。
七、课后练习
- 输入一个整数,用 if / else 判断它是奇数还是偶数并输出(提示:
n % 2 == 0)。 - 输入体重(kg)和身高(m),计算 BMI(
体重 / 身高 ** 2,注意把体重转成Float64),用 if 链输出档位:< 18.5偏瘦、18.5 ~ 23.9正常、24.0 ~ 27.9偏胖、>= 28.0肥胖。 - 输入年份,判断是否为闰年并输出
true/false:能被 4 整除但不能被 100 整除,或能被 400 整除(提示:%、&&、||组合)。 - 把 5.1 的成绩程序改造成"菜单计算器":输入两个数后再输入运算符(
+-*/四个字符串之一),用match (op)匹配运算符并输出结果,输入其他符号时提示"不支持的运算"(字符串常量可以直接当模式:case "+" =>);最后 Shift + F9 调试,输入12、5、+,F8 单步观察 match 如何命中case "+",并在 Variables 面板确认op的值。
下节预告
有了分支,程序已经能"看情况办事"了。但如果要打印 1 到 100、或者把全班每个人的分数都评一遍,难道写一百行 println?第 5 课我们学习循环结构:while / for / Range,让程序自动重复干活——分支管"选路",循环管"赶路",两者合体,程序才算真正长大。我们下节课见!
系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入,以你本地版本为准,欢迎评论区交流。
📥 工具下载
本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):
- GitCode 仓库 / 安装包下载:https://gitcode.com/wp_upala/cide
- 打开页面后进入 发行版(Releases),下载
CIDE-<版本>-x64-Setup.exe安装即可 - 仓颉 SDK 请前往仓颉编程语言官网下载:https://cangjie-lang.cn
更多推荐




所有评论(0)