程序从此有了"看情况办事"的能力——if 家族一次讲透。
前面七章写的程序都是"一条道走到黑":从第一行执行到最后一行。真实程序处处是岔路——成绩够不够及格?用户输入合不合法?var 里装的到底是数字还是文本?本章学习 xlang 的分支结构:if / then / elseif / else / end。
xlang 的分支语法讲究"显式收尾":每个 if 块必须以 end 结束,多路分支用 elseif 一个词(不是两个词),条件表达式必须严格是 bool——没有 C 家族"非零即真"的潜规则。这些纪律初学时略显啰嗦,换来的好处是:分支边界永远肉眼可见,条件永远不存在歧义。
掌握 if/then/elseif/else/end 全形态;树立"条件必须 bool"的纪律;会用嵌套分支与块级作用域;会把第 7 章的 is 用作条件;懂得三元表达式与 if 语句的选型;会用 if + return 写守卫式代码。
最简单的分支:条件成立就执行一段代码。
int temperature = 38 if temperature >= 37 then println("发热,请休息") end
三个要点:if 后面是条件表达式;then 标志条件部分结束、分支体开始;end 关闭整个 if 语句。分支体必须另起一行——then 后面同一行直接写语句是语法错误(见坑 4)。
两路分支加 else:
int age = 17 if age >= 18 then println("成年人") else println("未成年人") end
结构上:else 没有自己的条件,它收编"前面所有条件都不成立"的剩余情况。一个 if 语句里 else 最多出现一次,且必须在 end 之前。
三路及以上用 elseif——注意是一个词,写成 else if 是语法错误(见坑 1):
int score = 86 if score >= 90 then println("优秀") elseif score >= 75 then println("良好") elseif score >= 60 then println("及格") else println("不及格") end
求值规则:从上往下逐个检查,命中第一个真条件就执行对应分支,然后整条语句结束——后面的条件不再求值。上例 86 分命中第二个条件(>= 75),"及格"分支的条件根本没被检查。
这条"短路"规则带来一个实用推论:条件顺序就是优先级。把最严格、最特殊的条件放前面,把兜底的放后面。上例若把 score >= 60 排到最前,86 分会被误判为"及格"——条件本身没错,顺序错了。
C 语言里 if (n) 合法,0 为假、非零为真。xlang 明确拒绝这套"真值(truthy)"潜规则:if/elseif 的条件表达式的类型必须是 bool,否则编译期 [sem] 报错:
数字、字符串、null 都不能直接当条件。想表达"非零""非空""有值",就写出来:
int count = 3 if count != 0 then // 显式比较,而不是 if count println("有库存") end var name = "xlang" if name != null then // 显式判空,而不是 if name println(name as string) end
复杂条件用第 6 章的 and / or / not 组合,它们同样要求两边都是 bool,且短路求值:
int score = 86 if score >= 80 and score % 2 == 0 then println("80+ 且为偶数") end
初看繁琐,实则受益:if count != 0 把意图写在明处,读者不用猜"这个 if 到底在判断什么"。这也是 xlang"显式优于隐式"设计哲学的又一体现(第 3、7 章的转换规则同源)。
分支体里可以再写 if,层数不限:
int a = 3 int b = 7 if a > 0 then if b > 5 then println("both") else println("only a") end end
每个分支体都是一个块,遵守第 4 章确立的块级作用域规则:块内声明的变量只属于这个块,出了 end 立即失效:
这条规则常常塑造代码结构:想在分支之后还用某个值,就要在 if 之前声明好,分支内只负责赋值:
string level // 先声明(第 4 章:声明可不带初始化) int score = 86 if score >= 60 then level = "通过" // 块内只赋值 else level = "未通过" end println(level) // 块外可用
语法允许无限嵌套,工程上建议最多两层。超过两层的嵌套通常有两种解药:用 elseif 拍平(条件互斥时),或用 if + return 提前退出(8.7 节)。嵌套每深一层,读者脑子里的"条件栈"就压一层。
第 7 章的 is 放进 if 条件里,就是处理 var 值的标准姿势——先判断、后取出:
var v = 42 if v is int then int n = v as int // is 确认过,as 才安全 println("int: " + string(n)) end var s = "hi" if s is string then println("string: " + (s as string)) end
回忆第 7 章坑 3:as 失败是 fatal,进程直接终止且无法捕获。所以 is + as 必须成对出现——is 是安检门,as 是取货窗。注意 xlang 当前不做"控制流类型收窄":进入 if v is int 的分支后,v 的类型仍是 var,不会自动变成 int,该写的 as 一次不能少。
第 6 章学过三元表达式 cond ? a : b。它与 if 语句的分工很清晰:
| 场景 | 选择 | 理由 |
|---|---|---|
| 根据条件取一个值 | 三元 | 表达式直接嵌进赋值/拼接/传参 |
| 根据条件做一件事(或多件事) | if 语句 | 语句块能装任意多条语句 |
| 三路及以上 | if/elseif | 嵌套三元可读性急剧恶化 |
int x = 7 string tag = x % 2 == 0 ? "even" : "odd" // 取值:三元一步到位 println(tag) if x > 100 then // 做事:用 if 语句 println("big") println("really big") end
一句话:值用三元,动作用 if;两个分支以内考虑三元,三个以上必须 if/elseif。
函数体内,if 常与 return 搭档写成"守卫子句(guard clause)":异常情况先检查、先返回,主逻辑保持在最浅缩进:
func grade(int s) as string if s >= 90 then return "A" elseif s >= 60 then return "B" else return "C" end end println(grade(92)) println(grade(70)) println(grade(30))
函数将在第 11 章系统讲解,这里只需记住模式:func 名(参数) as 返回类型 ... end,分支内 return 立即结束函数。当每个分支都 return 时,整条 if 语句就是函数的"分拣器"。
笔者实测发现:if x > 3 后面不写 then、直接换行写分支体,编译器同样接受。但本书所有示例都写 then,理由有三:官方文档示例全部带 then,它是语言的"标准仪态";then 让条件与分支体的分界一目了然;团队代码里少一个"可选项"就少一种风格分歧。把 then 当作必须写,是最省心的纪律。
很多语言有 switch(C/Java)或 match(Rust/Python 3.10+)。xlang 当前版本只有 if/elseif 链。功能清单中模式匹配相关的枚举、联合类型(union/derive)已列入语言设计,但以值匹配为核心的多路分支,现状就是 elseif。好在 elseif 链表达能力不弱:条件可以是任意 bool 表达式,比只能匹配常量值的 switch 更自由。真要按值分派且分支很多,第 16 章的字典查表法是惯用替代。
Rust 里 let x = if cond { 1 } else { 2 }; 合法——if 本身有值。xlang 的 if 是纯语句,不能出现在赋值号右边,要"按条件取值"请用三元表达式。这与第 2 章"语句与表达式泾渭分明"的设计一致:语句做事,表达式算值,谁也不越界。想看 if 的实现成本?用 xl cgen 观察生成的 C 代码会发现:xlang 的 if/else 几乎一对一映射为 C 的 if/else,分支本身零开销。
以下报错全部来自真实编译器输出(文件路径已缩短)。
原因:xlang 的多路分支关键字是 elseif 一个词。else 后面只能直接换行跟分支体,不能再接 if。报错位置指向那个多出来的 if。
原因:xlang 没有 truthy 规则,整数、字符串、null 都不能当条件。正确做法:写出显式比较——if n != 0、if s != ""、if v != null。这条错误信息本身就是最好的老师。
原因:条件位置不允许出现赋值语句,= 在此直接被解析器拒绝。C 家族"if 里误写赋值却悄悄通过"的经典灾难在 xlang 里从语法层面被消灭——这正是不提供 truthy 规则的深层收益之一。
原因:then 之后必须换行,分支语句从下一行开始。xlang 的分支体是"行导向"的块,没有 C 的单语句简写。乖乖换行即可。有趣的是,反向操作合法:最后一条语句结束后用分号把 end 接在同一行(println("x"); end)——但本书不推荐,独立一行最清晰。
| 知识点 | 写法/规则 | 要点 |
|---|---|---|
| 单分支 | if cond then ... end | then 后换行,end 收尾 |
| 两分支 | else | 最多一次,无自身条件 |
| 多分支 | elseif cond then | 一个词;命中即短路;顺序即优先级 |
| 条件类型 | 必须是 bool | 无 truthy;[sem] 编译期拦截 |
| 作用域 | 分支体是块 | 块内声明块外失效;跨块值提前声明 |
| is 条件 | if v is T then ... v as T ... | is/as 成对;无自动类型收窄 |
| 三元选型 | cond ? a : b | 取值用三元,动作用 if |
if n 这种写法?坑 3 与此有什么内在联系?end 之后使用?想让分支结果带出块外该怎么做?if v is int then int n = v + 1 end 能通过编译吗?为什么?正确写法是什么?>= 60 排在 >= 90 前,95 分会被判为"及格"而非"优秀"。if x = 5 这种赋值误写才必然被解析器拦下——bool 纪律与坑 3 的防护是同一条设计链。end 销毁。办法是在 if 之前声明变量,分支内只赋值。v + 1 的结果类型是 var,赋给 int 变量报 [sem] 类型不匹配;且没有控制流收窄,分支内 v 仍是 var。应写 int n = (v as int) + 1——先 as 取出,再运算。(顺带一提:println(v + 1) 打印 43 是合法的,var 参与运算没问题,卡住的是"var 值进静态类型变量"这一步。)编写 bmi.xl:给定 float weight = 68.0、float height = 1.75,计算 BMI = weight / (height * height),按 <18.5 偏瘦、<24 正常、<28 超重、否则肥胖 四档打印结论,并打印 BMI 数值。
参考思路:elseif 链按从低到高的阈值顺序排列;注意第 6 章的坑——** 幂运算符不接受浮点操作数,平方请写 height * height。
编写 sort_var.xl:对一组 var 值(至少含 int、float、string、bool、null 各一个),逐个用 if/elseif 的 is 链判断类型,int 打印其两倍,string 与其自身拼接打印,其余各打印一句类型说明。
参考思路:复用 8.5 节 is + as 模式;null 分支用 v is null。体会"先安检后取货"的节奏。
编写 max3.xl:函数 max3(int a, int b, int c) as int 返回三者最大值。先用嵌套 if 写一版,再用"if + return 提前返回"的扁平风格重写,对比两种写法的可读性。
参考思路:扁平版形如 if a >= b and a >= c then return a end ...。体会 8.7 节"守卫子句让主逻辑保持最浅缩进"的收益。