NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置Canjie-SDK
WARNING博主在此之前, 基本只接触过C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
基本概念
表达式
在一些传统编程语言中, 一个表达式由一个或多个操作数(
operand)通过零个或多个操作符(operator)组合而成表达式总是隐含着一个计算过程, 因此每个表达式都会有一个计算结果
对于只有操作数而没有操作符的表达式, 其计算结果就是操作数自身
对于包含操作符的表达式, 计算结果是对操作数执行操作符定义的计算而得到的值
在这种定义下的表达式也被称为算术运算表达式
操作符优先级请参见操作符章节
在仓颉编程语言中, 简化并延伸了表达式的传统定义——凡是可求值的语言元素都是表达式
因此, 仓颉不仅有传统的算术运算表达式, 还有条件表达式、循环表达式和
try表达式等, 它们都可以被求值, 并作为值去使用, 如作为变量定义的初值和函数实参等此外, 因为仓颉是强类型的编程语言, 所以仓颉表达式不仅可求值, 还有确定的类型
注意:
为了清晰地划分不同的程序语句或表达式, 仓颉采用分号
(;)进行分隔如果一条语句独占一行, 该分号可以省略, 但一行存在多条语句, 这些语句必须用分号进行分隔
仓颉语言中, 表达式不再仅仅是算术表达式, 而是一切可以求值的语言元素都是表达式
仓颉中, 条件表达式、循环表达式、try表达式等, 都拥有类型且都可以求值, 都可以作为变量的初始化值 以及 函数的实参
仓颉中使用;划分单个语句或表达式, 但如果单句单行, ;可以省略
仓颉编程语言的各种表达式将在后续章节中逐一介绍, 本节介绍最常用的条件表达式、循环表达式以及部分控制转移表达式(
break、continue)任何一段程序的执行流程, 只会涉及三种基本结构——顺序结构、分支结构和循环结构
实际上, 分支结构和循环结构, 是由某些指令控制当前顺序执行流产生跳转而得到的, 它们让程序能够表达更复杂的逻辑
在仓颉中, 这种用来控制执行流的语言元素就是条件表达式和循环表达式
在仓颉编程语言中, 条件表达式是
if表达式, 其值与类型需要根据使用场景来确定循环表达式有三种:
for-in表达式、while表达式和do-while表达式, 它们的类型都是Unit, 值为()在仓颉程序中, 由一对大括号
"{}"包围起来的一组表达式, 被称为”代码块”, 它将作为程序的一个顺序执行流, 其中的表达式将按编码顺序依次执行如果代码块中有至少一个表达式, 规定此代码块的值与类型等于其中最后一个表达式的值与类型, 如果代码块中没有表达式, 规定这种空代码块的类型为
Unit, 值为()注意:
代码块本身不是一个表达式, 不能被单独使用, 它将依附于函数、条件表达式和循环表达式等执行和求值
本篇文章只介绍仓颉的, 条件表达式(if)、循环表达式(for-in、while、do-while)、部分控制转义表达式(break、continue)
仓颉中, if表达式的类型和, 根据具体的使用场景 动态确定
而循环表达式的类型和值, 恒为Unit和()
被{}包围的一组表达式, 被称为代码块, 其中表达式是按照编码顺序执行的
代码块, 也拥有类型和值:
-
如果代码块中存在表达式, 那么最后一个表达式的类型和值 就是此代码块的类型和值
-
如果代码块中不存在表达式, 即 是一个空代码块, 那么此代码块的类型和值, 恒为
Unit和()
但, 代码块并不是表达式, 因为他在仓颉中不能单独使用, 只能依附于其他语言元素 如: 函数、if表达式、循环表达式等
if表达式的类型和值, 与各分支代码块的类型和值有关
而循环表达式的类型和值, 抛弃了代码块, 恒为Unit和()
if表达式
if表达式的基本形式为:if (条件) {分支 1} else {分支 2}其中”条件”可以是一个布尔类型的表达式, 或者一个 “
let pattern” (语法糖), 或者多个 “let pattern” 和布尔类型的表达式之间 通过 逻辑与 或 逻辑或 直接连接形成的表达式涉及 “
let pattern” 的介绍和示例, 参照涉及 “let pattern” 的”条件”示例
仓颉中的if表达式, 与C/C++中的if从结构上看, 长得一样
但仓颉的if表达式的条件, 必须是Bool类型的表达式 或let pattern(模式匹配的一种语法糖), 当然也可以通过&&和||连接
当表达式和模式匹配成功时, 该模式匹配的值为
true, 此时执行if分支对应的代码块; 反之, 为false, 执行else分支代码块,else分支可以不存在“分支 1”和”分支 2”是两个代码块
if表达式将按如下规则执行:
计算”条件”表达式, 如果值为
true则转到第2步, 值为false则转到第3步执行”分支 1”, 并转到第
4步执行”分支 2”, 并转到第
4步继续执行
if表达式后面的代码在一些场景中, 可能只关注条件成立时该做些什么, 所以
else和对应的代码块是允许省略的如下程序演示了
if表达式的基本用法:import std.random.*main() {let number: Int8 = Random().nextInt8()println(number)if (number % 2 == 0) {println("偶数")} else {println("奇数")}}在这段程序中, 使用仓颉标准库的
random包生成了一个随机整数, 然后使用if表达式判断这个整数是否能被2整除, 并在不同的条件分支中打印”偶数”或”奇数”
if表达式的条件, 最终值类型一定要是Bool, 执行的规则与C/C++一致, if(条件)条件是true就执行if的代码块, 否则执行之后else代码块
仓颉的else分支也可以省略, 可以只存在if分支
仓颉编程语言是强类型的,
if表达式的条件只能是布尔类型, 不能使用整数或浮点数等类型和 C 语言等不同, 仓颉不以条件取值是否为 0 作为分支选择依据, 例如以下程序将编译报错(此外, 后文的错误的表达式示例补充了更多错误的表达式用例场景, 可对比参照):
main() {let number = 1if (number) { // 编译错误, 类型不匹配println("非零数")}}
仓颉是强类型语言, 仓颉的基础数据类型之间, 不允许类型转换, 即 不存在0当作false, 非0当作true使用的情况, 就如上面的这个例子中所示
甚至, 如果都是整数类型, 不同大小范围的整数类型之间也禁止出现类型转换, 甚至禁止相互赋值, 比如:
main() { var int8: Int8 = 0 var int16: Int16 = 0
int8 = int16 int16 = int8}这个例子, 也会报错: 类型不匹配, Int8类型的变量 和Int16类型的变量之间是无法赋值的
即使范围不会溢出, 但 仓颉是强类型语言, 认为Int8和Int16是两个完全不同的类型
这在C/C++这种弱类型语言上 根本无法想象
在许多场景中, 当一个条件不成立时, 可能还要判断另一个或多个条件, 再执行对应的动作
仓颉允许在
else之后跟随新的if表达式, 由此支持多级条件判断和分支执行, 例如:import std.random.*main() {let speed = Random().nextFloat64() * 20.0println("${speed} km/s")if (speed > 16.7) {println("第三宇宙速度, 鹊桥相会")} else if (speed > 11.2) {println("第二宇宙速度, 嫦娥奔月")} else if (speed > 7.9) {println("第一宇宙速度, 腾云驾雾")} else {println("脚踏实地, 仰望星空")}}
仓颉也存在else if, 用来在首个if条件不满足时, 另外判断多个条件 并执行相应的代码块
if表达式的值与类型, 需要根据使用形式与场景来确定:
当含
else分支的if表达式被求值时, 需要根据求值上下文确定if表达式的类型:
如果上下文明确要求值类型为
T, 则if表达式各分支代码块的类型必须是T的子类型, 这时if表达式的类型被确定为T, 如果不满足子类型约束, 编译会报错如果上下文没有明确的类型要求, 则
if表达式的类型是其各分支代码块类型的最小公共父类型, 如果最小公共父类型不存在, 编译会报错如果编译通过, 则
if表达式的值就是所执行分支代码块的值如果含
else分支的if表达式没有被求值, 在这种场景里, 开发者一般只想在不同分支里做不同操作, 不会关注各分支最后一个表达式的值与类型, 为了不让上述类型检查规则影响这一思维习惯, 仓颉规定这种场景下的if表达式类型为Unit、值为(), 且各分支不参与上述类型检查对于不含
else分支的if表达式, 由于if分支也可能不被执行, 所以规定这类if表达式的类型为Unit、值为()例如, 以下程序基于
if表达式求值, 模拟一次简单的模数转换过程:main() {let zero: Int8 = 0let one: Int8 = 1let voltage = 5.0let bit = if (voltage < 2.5) {zero} else {one}}在以上程序中,
if表达式作为变量定义的初值使用, 由于变量bit没有被标注类型、需要从初值中推导, 所以if表达式的类型取为两个分支代码块类型的最小公共父类型根据前文对”代码块”的介绍, 可知两个分支代码块类型都是
Int8, 所以if表达式的类型被确定为Int8, 其值为所执行分支即else分支代码块的值, 所以变量bit的类型为Int8、值为1
求if表达式的类型和值的方式, 根据下面这两种情况 再具体分析
-
是否被求值, 即 是否需要使用
if表达式的值, 比如 是否要将if表达式的值 赋值给某个变量 或 当作实参传参等 -
是否含
else分支
具体是这样的:
-
如果不含
else分支, 无论被不被求值, 因为if分支可能并不被执行, 所以此时,if表达式的类型和值 恒为Unit和() -
如果不被求值, 则表示 不在意
if表达式的类型和值, 此时,if表达式类型和值 恒为Unit和() -
如果被求值, 则根据上下文的目标类型, 来对
if表达式做类型检查上下文的目标类型, 举例子就是: 要赋值的变量的类型 或 函数调用形参的类型等
此时, 又分两种情况:
-
上下文指定目标类型, 比如:
let res: Bool或func cal(param: Int64)等此时,
if表达式的所有分支的代码块, 都必须要为目标类型的子类型,if表达式的最终类型会被转换为目标类型, 值还是最终执行的代码块的值如果存在分支的代码块, 不为目标类型的子类型, 则编译报错
-
上下文没有指定目标类型
此时,
if表达式的所有分支的代码块, 需要存在一个最小公共父类型, 即 存在一个共同祖先类型,if表达式的最终类型会被转换为这个最小共父类型, 值还是最终执行的代码块的值 如果,if表达式的所有分支的代码块不存在最小公共夫类型, 则编译报错
-
其实也并不复杂, 各分支代码块的类型和值, 满足要求就可以
涉及let pattern的条件示例
let pattern属于语法糖一个
let pattern的构成为let pattern <- expression, 其中各字段含义为:
pattern: 模式, 用于匹配expression的值类型和内容
<-: 模式匹配操作符
expression: 表达式, 该表达式求值后, 再和模式进行匹配
expression表达式的优先级不能低于..运算符, 但是可以用()改变优先级运算符优先级请参见操作符
此处介绍”条件”是两个
let pattern进行 逻辑与 或 逻辑或 操作以及let pattern与其他表达式进行 逻辑与 或 逻辑或 操作的示例:main() {let a = Some(3)let c = if (let Some(b) <- a) {1 // 模式匹配成功, c = 1} else {2}let d = Some(1)if (let Some(e) <- a && let Some(f) <- d) { // 两种模式都匹配, 条件的值为真println("${e} ${f}") // print 3 1}if (let Some(f) <- d && f > 3) { // 模式匹配; f = 1, f > 3 检查失败, 跳转到 else 分支println("${f}")} else {println("d is None or value of d is less or equal to 3") // 打印该行}if (let Some(_) <- a || let Some(_) <- d) { // 枚举模式通过||连接, 没有变量绑定, 正确println("at least one of a and d is Some") // 打印该行} else {println("both a and d are None")}let g = 3if (let Some(_) <- a || g > 1) {println("this") // 打印该行} else {println("that")}}
let pattern中表达式部分运算符优先级不能低于..运算符, 此处介绍对应的错误和正确示例其中,
Option类型的相关介绍在后文给出if (let Some(a) <- fun() as Option<Int64>) {} // 解析错误,`as`的优先级低于 `..`if (let Some(a) <- (fun() as Option<Int64>)) {} // 正确if (let Some(a) <- b && a + b > 3) {} // 正确, 解析为 (let Some(a) <- b) && (a + b > 3)if (let m <- 0..generateSomeInt()) {} // 正确
仓颉中为模式匹配提供了一种语法糖let pattern <- expression, 用于 在可以拥有条件的表达式中 当作条件使用
匹配成功即为true, 匹配失败即为false
expression部分的运算符优先级要比..高, ..是左闭右开区间操作符, 优先级是8, 比他高的有:
| 操作符 | 优先级 | 含义 | 示例 | 结合方向 |
|---|---|---|---|---|
@ | 0 | 宏调用 | @id | 右结合 |
. | 1 | 成员访问 | expr.id | 左结合 |
[] | 1 | 索引 | expr[expr] | 左结合 |
() | 1 | 函数调用 | expr(expr) | 左结合 |
++ | 2 | 自增 | var++ | 无 |
-- | 2 | 自减 | var-- | 无 |
? | 2 | 问号 | expr?.id, expr?[expr], expr?(expr), expr?{expr} | 无 |
! | 3 | 按位求反、逻辑非 | !expr | 右结合 |
- | 3 | 一元负号 | -expr | 右结合 |
** | 4 | 幂运算 | expr ** expr | 右结合 |
*, / | 5 | 乘法, 除法 | expr * expr, expr / expr | 左结合 |
% | 5 | 取模 | expr % expr | 左结合 |
+, - | 6 | 加法, 减法 | expr + expr, expr - expr | 左结合 |
<< | 7 | 按位左移 | expr << expr | 左结合 |
>> | 7 | 按位右移 | expr >> expr | 左结合 |
即, 如果expression不用()提升优先级, 那么 表达式中就只能存在上表中的操作符, 否则会解析出错
这里的语法糖, 就表示expression尝试匹配pattern, 匹配的上就是true, 匹配不上就是false
模式匹配的话, 之后的文档内容中会介绍, 暂时可以简单的认为是C/C++中的switch
let pattern <- expression就是模式匹配的一种语法糖
不过, 从文档提供的代码注释来看, if表达式中如果出现let pattern <- expression且存在绑定变量, 条件的表达式之间就不能使用||连接
原因也可以猜测出:
-
||的连接遵循短路的原则, 这表示 如果模式匹配没有匹配成功 变量也没有被绑定有效数据, 但之后的条件也可能使此分支的整个条件成立 -
此时, 分支将会被执行, 也就意味着 模式匹配的绑定变量 在此分支内是可以被访问的, 但 此变量可能并没有绑定有效数据, 此时就可能出现安全问题
错误的表达式示例
此处介绍错误的”条件”示例
if (let Some(a) <- b || a > 1) {} // 由`||`连接的条件不能使用 会绑定变量的 enum 模式if (let Some(a) <- b && a + 1) {} //`&&`右侧既不是 let pattern, 也不是类型为 Bool 的普通表达式if (a > 3 && let Some(a) <- b) {} // a 由 Some(a) pattern 绑定, 不能在绑定它的 pattern 左侧使用if (let Some(a) <- b && a > 3) {println("${a} > 3")} else {println("${a} < 3") // a 只能在 if 分支使用, 不能在 else 分支使用}if (let Some(a) <- b where a > 3) {} // 使用`&&`表示条件检查, 而不是`where`
表达式的条件里, 必须严格遵循:
-
必须是
Bool类型的表达式 和let pattern, 不允许出现类型转换 -
如果出现
let pattern, 则表达式之间不能使用||连接 -
模式匹配要遵循模式匹配的规则, 这个之后分析
while表达式
while表达式的基本形式为:while (条件) {循环体}其中”条件”同
if表达式的”条件”, “循环体”是一个代码块
while表达式将按如下规则执行:
计算”条件”表达式, 如果值为
true则转第2步, 值为false转第3步执行”循环体”, 并转第
1步结束循环, 继续执行
while表达式后面的代码例如, 以下程序使用
while表达式, 基于二分法, 近似计算数字2的平方根:main() {var root = 0.0var min = 1.0var max = 2.0var error = 1.0let tolerance = 0.1 ** 10while (error ** 2 > tolerance) {root = (min + max) / 2.0error = root ** 2 - 2.0if (error > 0.0) {max = root} else {min = root}}println("2 的平方根约等于: ${root}")}运行以上程序, 将输出:
2 的平方根约等于: 1.414215
while(条件)表达式, 条件与if(条件)表达式的条件规则保持一致
while表达式需要先对条件进行判断
而且, while表达式也是条件成立时, 才执行代码块的内容, 条件成立即为true
但不同的是, while是循环判断条件是否成立, 如果成立也就是循环执行代码块
do-while表达式
do-while表达式的基本形式为:do {循环体} while (条件)其中”条件”是布尔类型表达式, “循环体”是一个代码块
do-while表达式将按如下规则执行:
执行”循环体”, 转第
2步.计算”条件”表达式, 如果值为
true则转第1步, 值为false转第3步结束循环, 继续执行
do-while表达式后面的代码例如, 以下程序使用
do-while表达式, 基于蒙特卡洛算法, 近似计算圆周率的值:import std.random.*main() {let random = Random()var totalPoints = 0var hitPoints = 0do {// 在 ((0, 0), (1, 1)) 这个正方形中随机取点let x = random.nextFloat64()let y = random.nextFloat64()// 判断是否落在正方形内接圆里if ((x - 0.5) ** 2 + (y - 0.5) ** 2 < 0.25) {hitPoints++}totalPoints++} while (totalPoints < 1000000)let pi = 4.0 * Float64(hitPoints) / Float64(totalPoints)println("圆周率近似值为: ${pi}")}运行以上程序, 将输出:
圆周率近似值为: 3.141872说明:
由于算法涉及随机数, 所以每次运行程序输出的数值可能都不同, 但都会约等于
3.14
do-while表达式也是一个循环表达式
但do-while与while不同, do-while是先执行一遍代码块之后, 在判断条件是否成立 是否继续进行下一次循环
也是因为此, do-while的条件中不允许存在匹配模式, 只能是Bool类型的表达式
for-in表达式
for-in表达式可以遍历那些扩展了迭代器接口Iterable<T>的类型实例
for-in表达式的基本形式为:for (迭代变量 in 序列) {循环体}其中”循环体”是一个代码块
“迭代变量”是单个标识符或由多个标识符构成的元组, 用于绑定每轮遍历中由迭代器指向的数据, 可以作为”循环体”中的局部变量使用
“序列”是一个表达式, 它只会被计算一次, 遍历是针对此表达式的值进行的, 其类型必须扩展了迭代器接口
Iterable<T>
for-in表达式将按如下规则执行:
计算”序列”表达式, 将其值作为遍历对象, 并初始化遍历对象的迭代器
更新迭代器, 如果迭代器终止, 转第
4步, 否则转第3步将当前迭代器指向的数据与”迭代变量”绑定, 并执行”循环体”, 转第
2步结束循环, 继续执行
for-in表达式后面的代码说明:
仓颉内置的区间和数组等类型已经扩展了
Iterable<T>接口例如, 以下程序使用
for-in表达式, 遍历中国地支字符构成的数组noumenonArray, 输出农历 2024 年各月的干支纪法:main() {let metaArray = [r'甲', r'乙', r'丙', r'丁', r'戊', r'己', r'庚', r'辛', r'壬', r'癸']let noumenonArray = [r'寅', r'卯', r'辰', r'巳', r'午', r'未', r'申', r'酉', r'戌', r'亥', r'子', r'丑']let year = 2024// 年份对应的天干索引let metaOfYear = ((year % 10) + 10 - 4) % 10// 此年首月对应的天干索引var index = (2 * metaOfYear + 3) % 10 - 1println("农历 2024 年各月干支: ")for (noumenon in noumenonArray) {print("${metaArray[index]}${noumenon} ")index = (index + 1) % 10}}其中
r开头的字符表示字符类型字面量运行以上程序, 将输出:
农历 2024 年各月干支:丙寅 丁卯 戊辰 己巳 庚午 辛未 壬申 癸酉 甲戌 乙亥 丙子 丁丑
仓颉for-in是遍历/迭代循环, 完整为for (迭代变量 in 序列) {}, 序列即为迭代目标
for-in的功能, 是从序列的头开始, 逐个元素迭代/遍历, 到序列的尾结束
每次迭代, 序列的当前元素都会被拷贝/引用到迭代变量, 通过迭代变量就可以访问到当前迭代到的元素值
拷贝或引用, 当然取决于元素的实际类型
举个更简单一些的例子:
main() { for (elem in 0..=20) { print("${elem} ") } println()}
从一个序列的头0开始, 一直迭代到序列的尾20结束
for-in迭代的序列, 必须要实现Iterable<T>接口, 此接口从名字上来看, 就是可迭代的意思
for-in类似C++中的范围for, 均为迭代器迭代有限的序列
遍历区间
for-in表达式可以遍历区间类型实例, 例如:main() {var sum = 0for (i in 1..=100) {sum += i}println(sum)}运行以上程序, 将输出:
5050关于区间类型的详细内容, 请参见基本数据类型区间类型
文档中的此例子, 与上面我举的例子例子是一样的
遍历元组构成的序列
如果一个序列的元素是元组类型, 则使用
for-in表达式遍历时, “迭代变量”可以写成元组形式, 以此实现对序列元素的解构, 例如:main() {let array = [(1, 2), (3, 4), (5, 6)]for ((x, y) in array) {println("${x}, ${y}")}}运行以上程序, 将输出:
1, 23, 45, 6
当序列元素是元组类型时, 可以将迭代变量也写成元组的形式
此时, 在迭代的同时就进行模式匹配, 会对迭代到的元素进行解构, 并绑定到目标变量上
然后就可以在代码块中, 使用绑定的变量访问元素值
迭代变量不可修改**
在
for-in表达式的循环体中, 不能修改迭代变量, 例如以下程序在编译时会报错:main() {for (i in 0..5) {i = i * 10 // 错误, 不能对已初始化的`let`常量赋值println(i)}}
for-in的迭代变量是被let修饰的, 所以无法在代码块内修改迭代变量
但, 如果元素的实际类型是引用类型, 那么实际是可以通过迭代变量尝试修改元素的成员的
迭代变量是序列元素的拷贝是确定的, 但如果元素是引用类型, 则 迭代变量实际是元素的引用, 而不是值的深拷贝
使用通配符_代替迭代变量
在一些应用场景中, 只需要循环执行某些操作, 但并不使用迭代变量, 这时可以使用通配符
_代替迭代变量, 例如:main() {var number = 2for (_ in 0..5) {number *= number}println(number)}运行以上程序, 将输出:
4294967296注意:
在这种场景下, 如果使用普通的标识符定义迭代变量, 编译会输出”
unused variable”告警, 使用通配符_则可以避免这一告警
for-in表达式中, 可以使用通配符_作为迭代变量, 从而可以无警告不使用迭代变量
以实现更优雅的循环
where条件
在部分循环遍历场景中, 对于特定取值的迭代变量, 可能需要直接跳过, 进入下一轮循环
虽然可以使用
if表达式和continue表达式在循环体中实现这一逻辑, 但仓颉为此提供了更便捷的表达方式——可以在所遍历的”序列”之后用where关键字引导一个布尔表达式, 这样在每次将进入循环体执行前, 会先计算此表达式如果值为
true则执行循环体, 反之直接进入下一轮循环例如:
main() {for (i in 0..8 where i % 2 == 1) { // i 为奇数才会执行循环体println(i)}}运行以上程序, 将输出:
1357
仓颉的for-in表达式, 允许在序列之后通过where关键字 后接一个Bool类型的表达式, 进行条件判断, 只有满足条件才执行循环体
所以, where之后的条件判断一般是针对迭代变量的布尔表达式
当然, 如果序列是在定义循环是生成的Range, 如果可以的话 更高效的方式是直接生成 满足目的的Range, 因为Range是可以按间隔等规则创建的
比如:
main() { for (elem in 0..=10:2) { println(elem) }}这段代码的执行结果是:

直接生成只携带有偶数的Range或许执行更高效, 但where并不是可代替的, 应该是互补的
break和continue表达式
在循环结构的程序中, 有时需要根据特定条件提前结束循环或跳过本轮循环, 为此仓颉引入了
break与continue表达式, 它们可以出现在循环表达式的循环体中
break用于终止当前循环表达式的执行、转去执行循环表达式之后的代码,continue用于提前结束本轮循环、进入下一轮循环
break与continue表达式的类型都是Nothing例如, 以下程序使用
for-in表达式和break表达式, 在给定的整数数组中, 找到第一个能被5整除的数字:main() {let numbers = [12, 18, 25, 36, 49, 55]for (number in numbers) {if (number % 5 == 0) {println(number)break}}}当
for-in迭代至numbers数组的第三个数25时, 由于25可以被5整除, 所以将执行if分支中的println和break,break将终止for-in循环,numbers中的后续数字不会被遍历到因此运行以上程序, 将输出:
25以下程序使用
for-in表达式和continue表达式, 将给定整数数组中的奇数打印出来:main() {let numbers = [12, 18, 25, 36, 49, 55]for (number in numbers) {if (number % 2 == 0) {continue}println(number)}}在循环迭代中, 当
number是偶数时,continue将被执行, 这会提前结束本轮循环, 进入下一轮循环,println不会被执行. 因此运行以上程序, 将输出:254955
如果你已经接触过其他的编程语言, 那么这仓颉中的两个表达式 大概率不会存在什么问题
break和continue都可以出现在任意循环体中:
break被执行时, 会直接结束循环
continue被执行时, 会跳过本趟循环, 直接进入下一趟循环
break和continue的类型恒是Nothing
函数
仓颉使用关键字
func来表示函数定义的开始,func之后依次是函数名、参数列表、可选的函数返回值类型、函数体其中, 函数名可以是任意的合法标识符, 参数列表定义在一对圆括号内(多个参数间使用逗号分隔), 参数列表和函数返回值类型(如果存在)之间使用冒号分隔, 函数体定义在一对花括号内
函数定义举例:
func add(a: Int64, b: Int64): Int64 {return a + b}上例中定义了一个名为
add的函数, 其参数列表由两个Int64类型的参数a和b组成, 函数返回值类型为Int64, 函数体中将a和b相加并返回详细介绍可参见定义函数模块介绍
熟悉C/C++的博主, 对函数当然在熟悉不过了
仓颉中函数的定义长这样:
func 标识符(参数列表): 返回值类型 { // 函数体}定义了函数之后, 此函数就可以通过函数名进行调用
使用文档的例子:
func add(a: Int64, b: Int64): Int64 { return a + b}
main() { let result = add(1, 2) println(result)}这段代码的执行结果为:

函数可以简化复杂代码的复用