8036 字
40 分钟
仓颉文档阅读-开发指南II: 基本概念(II)
NOTE

阅读文档版本:

语言规约 Cangjie-0.53.18-Spec

具体开发指南 Cangjie-LTS-1.0.3

在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证

有条件当然可以直接 配置Canjie-SDK

WARNING

博主在此之前, 基本只接触过C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与C/C++中的相似概念作类比, 见谅

且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验

WARNING

在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解

所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释

此样式内容, 表示文档原文内容

基本概念#

表达式#

在一些传统编程语言中, 一个表达式由一个或多个操作数(operand)通过零个或多个操作符(operator)组合而成

表达式总是隐含着一个计算过程, 因此每个表达式都会有一个计算结果

对于只有操作数而没有操作符的表达式, 其计算结果就是操作数自身

对于包含操作符的表达式, 计算结果是对操作数执行操作符定义的计算而得到的值

在这种定义下的表达式也被称为算术运算表达式

操作符优先级请参见操作符章节

在仓颉编程语言中, 简化并延伸了表达式的传统定义——凡是可求值的语言元素都是表达式

因此, 仓颉不仅有传统的算术运算表达式, 还有条件表达式循环表达式try表达式等, 它们都可以被求值, 并作为值去使用, 如作为变量定义的初值和函数实参等

此外, 因为仓颉是强类型的编程语言, 所以仓颉表达式不仅可求值, 还有确定的类型

注意:

为了清晰地划分不同的程序语句或表达式, 仓颉采用分号(;)进行分隔

如果一条语句独占一行, 该分号可以省略, 但一行存在多条语句, 这些语句必须用分号进行分隔

仓颉语言中, 表达式不再仅仅是算术表达式, 而是一切可以求值的语言元素都是表达式

仓颉中, 条件表达式、循环表达式、try表达式等, 都拥有类型且都可以求值, 都可以作为变量的初始化值 以及 函数的实参

仓颉中使用;划分单个语句或表达式, 但如果单句单行, ;可以省略

仓颉编程语言的各种表达式将在后续章节中逐一介绍, 本节介绍最常用的条件表达式循环表达式以及部分控制转移表达式(breakcontinue)

任何一段程序的执行流程, 只会涉及三种基本结构——顺序结构、分支结构和循环结构

实际上, 分支结构和循环结构, 是由某些指令控制当前顺序执行流产生跳转而得到的, 它们让程序能够表达更复杂的逻辑

在仓颉中, 这种用来控制执行流的语言元素就是条件表达式和循环表达式

在仓颉编程语言中, 条件表达式是if表达式, 其值与类型需要根据使用场景来确定

循环表达式有三种:for-in表达式、while表达式和do-while表达式, 它们的类型都是Unit, 值为()

在仓颉程序中, 由一对大括号"{}"包围起来的一组表达式, 被称为”代码块”, 它将作为程序的一个顺序执行流, 其中的表达式将按编码顺序依次执行

如果代码块中有至少一个表达式, 规定此代码块的值与类型等于其中最后一个表达式的值与类型, 如果代码块中没有表达式, 规定这种空代码块的类型为Unit, 值为()

注意:

代码块本身不是一个表达式, 不能被单独使用, 它将依附于函数、条件表达式和循环表达式等执行和求值

本篇文章只介绍仓颉的, 条件表达式(if)、循环表达式(for-inwhiledo-while)、部分控制转义表达式(breakcontinue)

仓颉中, if表达式的类型和, 根据具体的使用场景 动态确定

而循环表达式的类型和值, 恒为Unit()

{}包围的一组表达式, 被称为代码块, 其中表达式是按照编码顺序执行的

代码块, 也拥有类型和值:

  1. 如果代码块中存在表达式, 那么最后一个表达式的类型和值 就是此代码块的类型和值

  2. 如果代码块中不存在表达式, 即 是一个空代码块, 那么此代码块的类型和值, 恒为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表达式将按如下规则执行:

  1. 计算”条件”表达式, 如果值为true则转到第2步, 值为false则转到第3

  2. 执行”分支 1”, 并转到第4

  3. 执行”分支 2”, 并转到第4

  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 = 1
if (number) { // 编译错误, 类型不匹配
println("非零数")
}
}

仓颉是强类型语言, 仓颉的基础数据类型之间, 不允许类型转换, 即 不存在0当作false, 非0当作true使用的情况, 就如上面的这个例子中所示

甚至, 如果都是整数类型, 不同大小范围的整数类型之间也禁止出现类型转换, 甚至禁止相互赋值, 比如:

main() {
var int8: Int8 = 0
var int16: Int16 = 0
int8 = int16
int16 = int8
}

这个例子, 也会报错: 类型不匹配, Int8类型的变量 和Int16类型的变量之间是无法赋值的

即使范围不会溢出, 但 仓颉是强类型语言, 认为Int8Int16是两个完全不同的类型

这在C/C++这种弱类型语言上 根本无法想象

在许多场景中, 当一个条件不成立时, 可能还要判断另一个或多个条件, 再执行对应的动作

仓颉允许在else之后跟随新的if表达式, 由此支持多级条件判断和分支执行, 例如:

import std.random.*
main() {
let speed = Random().nextFloat64() * 20.0
println("${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 = 0
let one: Int8 = 1
let voltage = 5.0
let bit = if (voltage < 2.5) {
zero
} else {
one
}
}

在以上程序中, if表达式作为变量定义的初值使用, 由于变量bit没有被标注类型、需要从初值中推导, 所以if表达式的类型取为两个分支代码块类型的最小公共父类型

根据前文对”代码块”的介绍, 可知两个分支代码块类型都是Int8, 所以if表达式的类型被确定为Int8, 其值为所执行分支即else分支代码块的值, 所以变量bit的类型为Int8、值为1

if表达式的类型和值的方式, 根据下面这两种情况 再具体分析

  1. 是否被求值, 即 是否需要使用if表达式的值, 比如 是否要将if表达式的值 赋值给某个变量 或 当作实参传参等

  2. 是否含else分支

具体是这样的:

  1. 如果不含else分支, 无论被不被求值, 因为if分支可能并不被执行, 所以此时, if表达式的类型和值 恒为Unit()

  2. 如果不被求值, 则表示 不在意if表达式的类型和值, 此时, if表达式类型和值 恒为Unit()

  3. 如果被求值, 则根据上下文的目标类型, 来对if表达式做类型检查

    上下文的目标类型, 举例子就是: 要赋值的变量的类型 或 函数调用形参的类型等

    此时, 又分两种情况:

    1. 上下文指定目标类型, 比如:let res: Boolfunc cal(param: Int64)

      此时, if表达式的所有分支的代码块, 都必须要为目标类型的子类型, if表达式的最终类型会被转换为目标类型, 值还是最终执行的代码块的值

      如果存在分支的代码块, 不为目标类型的子类型, 则编译报错

    2. 上下文没有指定目标类型

      此时, 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 = 3
if (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且存在绑定变量, 条件的表达式之间就不能使用||连接

原因也可以猜测出:

  1. ||的连接遵循短路的原则, 这表示 如果模式匹配没有匹配成功 变量也没有被绑定有效数据, 但之后的条件也可能使此分支的整个条件成立

  2. 此时, 分支将会被执行, 也就意味着 模式匹配的绑定变量 在此分支内是可以被访问的, 但 此变量可能并没有绑定有效数据, 此时就可能出现安全问题

错误的表达式示例#

此处介绍错误的”条件”示例

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`

表达式的条件里, 必须严格遵循:

  1. 必须是Bool类型的表达式 和let pattern, 不允许出现类型转换

  2. 如果出现let pattern, 则表达式之间不能使用||连接

  3. 模式匹配要遵循模式匹配的规则, 这个之后分析

while表达式#

while表达式的基本形式为:

while (条件) {
循环体
}

其中”条件”同if表达式的”条件”, “循环体”是一个代码块

while表达式将按如下规则执行:

  1. 计算”条件”表达式, 如果值为true则转第2步, 值为false转第3

  2. 执行”循环体”, 并转第1

  3. 结束循环, 继续执行while表达式后面的代码

例如, 以下程序使用while表达式, 基于二分法, 近似计算数字2的平方根:

main() {
var root = 0.0
var min = 1.0
var max = 2.0
var error = 1.0
let tolerance = 0.1 ** 10
while (error ** 2 > tolerance) {
root = (min + max) / 2.0
error = root ** 2 - 2.0
if (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表达式将按如下规则执行:

  1. 执行”循环体”, 转第2步.

  2. 计算”条件”表达式, 如果值为true则转第1步, 值为false转第3

  3. 结束循环, 继续执行do-while表达式后面的代码

例如, 以下程序使用do-while表达式, 基于蒙特卡洛算法, 近似计算圆周率的值:

import std.random.*
main() {
let random = Random()
var totalPoints = 0
var hitPoints = 0
do {
// 在 ((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-whilewhile不同, do-while是先执行一遍代码块之后, 在判断条件是否成立 是否继续进行下一次循环

也是因为此, do-while的条件中不允许存在匹配模式, 只能是Bool类型的表达式

for-in表达式#

for-in表达式可以遍历那些扩展了迭代器接口Iterable<T>的类型实例

for-in表达式的基本形式为:

for (迭代变量 in 序列) {
循环体
}

其中”循环体”是一个代码块

“迭代变量”是单个标识符或由多个标识符构成的元组, 用于绑定每轮遍历中由迭代器指向的数据, 可以作为”循环体”中的局部变量使用

“序列”是一个表达式, 它只会被计算一次, 遍历是针对此表达式的值进行的, 其类型必须扩展了迭代器接口Iterable<T>

for-in表达式将按如下规则执行:

  1. 计算”序列”表达式, 将其值作为遍历对象, 并初始化遍历对象的迭代器

  2. 更新迭代器, 如果迭代器终止, 转第4步, 否则转第3

  3. 将当前迭代器指向的数据与”迭代变量”绑定, 并执行”循环体”, 转第2

  4. 结束循环, 继续执行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 - 1
println("农历 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 = 0
for (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, 2
3, 4
5, 6

当序列元素是元组类型时, 可以将迭代变量也写成元组的形式

此时, 在迭代的同时就进行模式匹配, 会对迭代到的元素进行解构, 并绑定到目标变量上

然后就可以在代码块中, 使用绑定的变量访问元素值

迭代变量不可修改**#

for-in表达式的循环体中, 不能修改迭代变量, 例如以下程序在编译时会报错:

main() {
for (i in 0..5) {
i = i * 10 // 错误, 不能对已初始化的`let`常量赋值
println(i)
}
}

for-in的迭代变量是被let修饰的, 所以无法在代码块内修改迭代变量

但, 如果元素的实际类型是引用类型, 那么实际是可以通过迭代变量尝试修改元素的成员的

迭代变量是序列元素的拷贝是确定的, 但如果元素是引用类型, 则 迭代变量实际是元素的引用, 而不是值的深拷贝

使用通配符_代替迭代变量#

在一些应用场景中, 只需要循环执行某些操作, 但并不使用迭代变量, 这时可以使用通配符_代替迭代变量, 例如:

main() {
var number = 2
for (_ 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)
}
}

运行以上程序, 将输出:

1
3
5
7

仓颉的for-in表达式, 允许在序列之后通过where关键字 后接一个Bool类型的表达式, 进行条件判断, 只有满足条件才执行循环体

所以, where之后的条件判断一般是针对迭代变量的布尔表达式

当然, 如果序列是在定义循环是生成的Range, 如果可以的话 更高效的方式是直接生成 满足目的的Range, 因为Range是可以按间隔等规则创建的

比如:

main() {
for (elem in 0..=10:2) {
println(elem)
}
}

这段代码的执行结果是:

直接生成只携带有偶数的Range或许执行更高效, 但where并不是可代替的, 应该是互补的

breakcontinue表达式#

在循环结构的程序中, 有时需要根据特定条件提前结束循环或跳过本轮循环, 为此仓颉引入了breakcontinue表达式, 它们可以出现在循环表达式的循环体中

break用于终止当前循环表达式的执行、转去执行循环表达式之后的代码, continue用于提前结束本轮循环、进入下一轮循环

breakcontinue表达式的类型都是 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分支中的printlnbreak, 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不会被执行. 因此运行以上程序, 将输出:

25
49
55

如果你已经接触过其他的编程语言, 那么这仓颉中的两个表达式 大概率不会存在什么问题

breakcontinue都可以出现在任意循环体中:

break被执行时, 会直接结束循环

continue被执行时, 会跳过本趟循环, 直接进入下一趟循环

breakcontinue的类型恒是Nothing

函数#

仓颉使用关键字func来表示函数定义的开始, func之后依次是函数名、参数列表、可选的函数返回值类型、函数体

其中, 函数名可以是任意的合法标识符, 参数列表定义在一对圆括号内(多个参数间使用逗号分隔), 参数列表和函数返回值类型(如果存在)之间使用冒号分隔, 函数体定义在一对花括号内

函数定义举例:

func add(a: Int64, b: Int64): Int64 {
return a + b
}

上例中定义了一个名为add的函数, 其参数列表由两个Int64类型的参数ab组成, 函数返回值类型为Int64, 函数体中将ab相加并返回

详细介绍可参见定义函数模块介绍

熟悉C/C++的博主, 对函数当然在熟悉不过了

仓颉中函数的定义长这样:

func 标识符(参数列表): 返回值类型 {
// 函数体
}

定义了函数之后, 此函数就可以通过函数名进行调用

使用文档的例子:

func add(a: Int64, b: Int64): Int64 {
return a + b
}
main() {
let result = add(1, 2)
println(result)
}

这段代码的执行结果为:

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

作者
Humid1ch
发布于
2025-10-23
许可协议
CC BY-NC-SA 4.0