仓颉之循环语句(for/while),一文搞懂!
大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~
本文目录:
1. 设计视角:为什么仓颉把循环也当“表达式”看待?
很多传统语言把 for/while 归类为“语句(statement)”。仓颉的不同之处在于:凡是可求值的语言元素都是表达式,包括条件与循环,这些表达式都有明确的类型和值(循环表达式的类型为 Unit、值为 ())——这让控制流与求值规则可以自然融入类型系统,既便于组合,也便于推导与检查。这样带来的直接好处是:控制流更“函数式”,可与其它表达式自由拼接,减少“只能写语句、不能嵌入表达式”的割裂感。(docs.cangjie-lang.cn)
更进一步,仓颉把循环细分为四种:for-in、while、do-while 与 while-let。其中 for-in 专注可迭代数据,while / do-while 面向基于条件的过程控制,而 while-let 则把模式匹配与循环结合,在处理可能失败/可选值(如 Option<T>)的连续拉取等场景里极具表达力。(docs.cangjie-lang.cn)
2. 语法与要点总览
2.1 while:以条件为中心的迭代
- 语义:先判定条件,再执行循环体;条件为
false时结束。 - 适用:不确定迭代次数、需要明确的收敛/退出条件(容忍度、上界、状态变化)等数值/过程型任务。
- 关键点:条件必须是布尔表达式;循环体是代码块。(docs.cangjie-lang.cn)
示例(数值逼近)
(见“实战 A”中的牛顿法/二分法片段)
2.2 do-while:至少执行一次
- 语义:先执行循环体,再判定是否继续。
- 适用:至少执行一次的流程,如“先读取一次、再决定是否继续读取”或“先显示菜单、再判断是否退出”。(docs.cangjie-lang.cn)
示例(蒙特卡洛 π)
(见“实战 A”中的 π 估算片段)
2.3 for-in:遍历一切可迭代对象
- 语义:遍历实现了
Iterable<T>接口的序列(数组、区间等);支持元组解构、占位符_与 where 条件过滤(进入循环体前即判断)。 - 适用:集合/序列处理的首选;
where可在进入循环体前进行布尔裁剪,减少分支与continue。(docs.cangjie-lang.cn)
要点:
- “序列”表达式只求值一次;
- 迭代变量不可修改;
- 可对元组元素直接解构;
where让过滤逻辑更直观。(docs.cangjie-lang.cn)
示例(区间、解构、where)
(见“实战 B”与“实战 C”)
2.4 while-let:把“取值+匹配”折叠进循环条件
- 语义:对
<-右侧表达式求值,与<-左侧的模式匹配;匹配成功则执行循环体并继续,否则结束。 - 适用:流式拉取、可能失败的读取、迭代器返回
Option<T>/枚举 的场景,避免“先读—判空—再继续”的样板代码。(docs.cangjie-lang.cn)
3. 选择指南:谁更适合你的问题?
- 遍历容器/区间 →
for-in:带来更清晰的意图表达与更少的错误面。where在进入循环体前就裁剪数据,避免无谓分支。(docs.cangjie-lang.cn) - 数值逼近/过程控制 →
while:与你的收敛条件同构,关注不变量、单调性与误差上界。(docs.cangjie-lang.cn) - 至少执行一次 →
do-while:典型如“先做一次 I/O 或 UI 交互,再判断是否继续”。(docs.cangjie-lang.cn) - 可选值/枚举驱动的拉取式循环 →
while-let:将“取值 + 解构/匹配 + 判断”合一,显著降噪,也更安全(不易遗漏失败分支)。(docs.cangjie-lang.cn)
4. 实战 A:数值计算中的循环策略(精度、收敛与鲁棒性)
目标:实现两个经典计算——(1)二分法/牛顿法求平方根;(2)蒙特卡洛估算 π。我们关注循环退出条件、数值稳定性与可读性。
4.1 二分法与 while:把“误差小于阈值”写进条件
优势在于:停止条件即代码意图,循环内只做收缩区间的纯逻辑。仓颉官方文档中给出了以 while 表达式近似计算 √2 的示例,核心是把误差平方与阈值放进条件,让退出条件可见且可调。(docs.cangjie-lang.cn)
main() {
var min = 1.0
var max = 2.0
var root = 0.0
var error = 1.0
let tol = 1e-10 // 容忍度
while (error * error > tol) {
root = (min + max) / 2.0
error = root * root - 2.0
if (error > 0.0) { max = root } else { min = root }
}
println("sqrt(2) ≈ ${root}")
}
工程思考
- 容忍度与溢出:用
error * error > tol而非abs(error) > tol可以避免引入额外库,且对称;若担心数值上下溢,可在外部包一层abs。 - 单调性保证:二分法依赖有界、单峰/单根与连续性;对更复杂函数,需在循环外预先定位区间,否则会“卡死”或过早收敛。
- 不可变 vs 可变:这里选择可变变量以减少中间对象;若偏函数式,可把状态封装到小记录体里,返回新状态。
4.2 do-while 与蒙特卡洛:至少跑一次采样
官方文档示例展示了用 do-while 进行 π 的蒙特卡洛近似,先采样、后判断,直观表达“先做一次”的业务语义。(docs.cangjie-lang.cn)
import std.random.*
main() {
let rnd = Random()
var total = 0
var hit = 0
do {
let x = rnd.nextFloat64()
let y = rnd.nextFloat64()
if ((x - 0.5) ** 2 + (y - 0.5) ** 2 < 0.25) { hit++ }
total++
} while (total < 1_000_000)
let pi = 4.0 * Float64(hit) / Float64(total)
println("π ≈ ${pi}")
}
工程思考
- 采样独立性:将
Random实例提到循环外,避免每轮重新构造影响分布。 - 退出上界:Monte Carlo 无法以“误差达到阈值”确定退出,通常按批次/上界退出;如果要动态估计方差,则可在固定批次后评估置信区间再决定是否继续。

5. 实战 B:for-in 的“可读性增强包”——区间、解构与 where
5.1 遍历区间与累加
for-in 可直接遍历区间(内置类型已扩展 Iterable<T>),表达“从 1 到 100 的和”几乎等同自然语言。(docs.cangjie-lang.cn)
main() {
var sum = 0
for (i in 1..=100) { sum += i }
println(sum) // 5050
}
5.2 元组解构:一次性拆出多值
当集合元素为元组时,可在循环头部直接解构,避免在循环体里反复下标访问。(docs.cangjie-lang.cn)
main() {
let points = [(1, 2), (3, 4), (5, 6)]
for ((x, y) in points) {
println("${x}, ${y}")
}
}
5.3 where 条件:进入循环体前就过滤
当只对“满足条件的元素”做处理,where 比先 if 再 continue 更清爽,且编译器能更好地做局部分析与告警控制(未使用变量等)。(docs.cangjie-lang.cn)
main() {
for (i in 0..8 where i % 2 == 1) {
println(i) // 1 3 5 7
}
}
工程思考
- 迭代变量不可修改是语言规则,有助于减少循环内部的副作用(见官方文档对应说明)。如需索引变换,请引入新变量而非直接赋值。(docs.cangjie-lang.cn)
- 序列只求值一次意味着:若“序列”表达式昂贵,放在
for头部也安全;若需要动态生成序列,则考虑迭代器/生成器式设计。

6. 实战 C:while-let 驱动的拉取式处理(I/O、解析、异步队列)
目标:展示如何把“尝试取一个元素,如果有就处理并继续,否则停止”的惯用模式写得既简洁又安全。
6.1 while-let 的语义回顾
while (let PATTERN <- EXPR) { ... }:对 EXPR 求值,与 PATTERN 进行匹配;若成功,进入循环体;失败则退出。常用于 Option<T>、枚举、迭代器的“下一项”。这在官方指南中被系统性介绍,尤其适合“接收数据直到失败/结束”的情景。(docs.cangjie-lang.cn)
6.2 案例:Option 拉取与解析
假设我们有一个可能返回 Some(T) 或 None 的 next() 风格函数(如网络接收、流式读取、解析器窥探等),while-let 能将读取 + 判空 + 解构折叠为一行。
// 模拟:从数据源尝试拉取一字节,成功返回 Some(b),失败返回 None
import std.random.*
func recv(): Option<UInt8> {
let n = Random().nextUInt8()
if (n < 200) { return Some(n) }
return None
}
main() {
// 只要还能收到数据,就持续处理;失败则退出
while (let Some(data) <- recv()) {
// 在此做校验/聚合/统计/写入等处理
if (data % 16 == 0) { println("block boundary: ${data}") }
}
println("receive finished.")
}
这个模式与官方示例精神一致:条件中直接匹配,让控制流变得“声明式”,同时避免漏判 None。(docs.cangjie-lang.cn)
工程思考
- 背压与节流:若数据来得太快、处理跟不上,循环内应在合适位置引入批量提交或节流策略(例如计数到 N 再 flush)。
- 错误与重试:把“失败”与“致命错误”区分开:
None代表“结束/暂不可用”,而真正的 I/O 错误应通过Result<T, E>(或抛异常)显式上抛。 - 可测试性:将
recv()作为依赖注入(传入函数指针/接口实现),让循环逻辑可单测(注入固定序列,验证聚合结果)。
7. 可维护性的 10 条实践建议(含性能与安全)
- 优先
for-in:遍历集合/区间时,for-in的语义密度更高;用where对进入循环体的元素“预筛”。(docs.cangjie-lang.cn) - 把“停止条件”写清楚:
while的条件要体现收敛,比如“误差平方 < 阈值”“计数达到上限”;避免在循环体内“隐式 break”。(docs.cangjie-lang.cn) - 首轮即有副作用 →
do-while:当业务语义是“先做一次再说”,用do-while封装第一步。(docs.cangjie-lang.cn) - 可空/可失败数据 →
while-let:与Option/枚举天然匹配,降噪且避免漏判。(docs.cangjie-lang.cn) - 避免修改迭代变量:仓颉明确禁止,这有助于减少副作用;用新变量承载变换后的值。(docs.cangjie-lang.cn)
- 序列只求值一次:在
for-in里把昂贵表达式放到“序列”位置是安全的,但也因此要留意共享状态是否被多次迭代使用。(docs.cangjie-lang.cn) - 把“过滤”提前:能用
where就别把大量if { continue }放在循环体首行。(docs.cangjie-lang.cn) - 控制复杂度:循环体一旦超过 20 行,考虑提炼为函数;通过早返回与守卫式条件降低嵌套。
- 幂等与副作用边界:I/O 写入、网络调用等副作用应集中在少数点位,便于重试与容错。
- 可观测性:长循环要有进度日志/指标;数值迭代要在达不到收敛时暴露保护性上限并告警。
8. 常见坑与规避
- 把非布尔作为条件:仓颉的条件必须是布尔;不能像 C 一样用
0/非0代替,否则编译报错。(docs.cangjie-lang.cn) - 在
for-in内修改迭代变量:属于语义违背;应使用新的可变变量存放变换结果。(docs.cangjie-lang.cn) - 把“过滤”放在循环体里:不仅冗长,还可能引入未使用变量的告警;
_与where是更好的表达手段。(docs.cangjie-lang.cn) - 遗漏
None/失败分支:拉取式处理应使用while-let,天然避免漏判与空指针式错误。(docs.cangjie-lang.cn)
9. 进阶话题(给想“抠细节”的同学)
- 与模式匹配的融合:
while-let左侧支持多种模式(如常量、通配符、绑定、元组、枚举),可以构建“只对某几种枚举分支持续处理”的精细逻辑。(humid1ch.cn) - 与类型推导相处:由于循环表达式本身是
Unit,把循环纳入更大表达式时不用担心“混型”;但若循环体最后一个表达式需要求值并参与推导,应将其提取到循环外。(docs.cangjie-lang.cn) - 可迭代协议与自定义容器:
for-in依赖Iterable<T>;当你实现自己的容器/生成器时,按协议暴露迭代器,即可与for-in无缝协作(数组、区间已内置实现)。(docs.cangjie-lang.cn)
10. 小结
在仓颉里,“循环是一等公民的表达式”。这看似一个“术语偏好”,其实在工程层面落地为三件事:
- 意图可见:
for-in表达集合遍历,where表达预过滤;while把收敛写进条件;do-while贴合“先做一次”;while-let把可选/枚举处理写进循环头。 - 类型友好:循环作为表达式,与类型系统自然融合;配合
Option/枚举/模式匹配,减少空值与分支遗漏。 - 可维护:规则清晰、语法增益(解构、占位符、where)与明确限制(不可修改迭代变量)共同引导写出更稳、更干净的控制流。
当你下一次需要“处理一批数据、迭代逼近某个数值、从流里持续拉取直到结束”时,先想想“用哪个循环让意图最清楚”,再让仓颉的类型与语法为你兜底吧。🚀
参考与延伸阅读
- 仓颉开发指南《表达式》:统一把
for-in/while/do-while/while-let作为循环表达式(类型为Unit),并给出for-in的区间遍历、解构、where、迭代变量不可修改等要点与示例。(docs.cangjie-lang.cn) while-let:将取值与模式匹配折叠到循环条件中的官方说明与示例。(docs.cangjie-lang.cn)
如果你有具体业务场景(比如:日志清洗、协议解析、批处理 ETL、图算法遍历等),我也可以据此把上面的模式“落地成一段可直接跑的模块化代码”,顺便加上性能对比与基准测量建议。要不要来一发?🙂
如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~
更多推荐


所有评论(0)