仓颉-基础语法

标识符

仓颉编程语言的标识符分为普通标识符原始标识符两类,它们遵从不同的命名规则。

普通标识符不能和仓颉关键字相同,其取自以下两类字符序列:

  • 由 XID_Start 字符开头,后接任意长度的 XID_Continue 字符。
  • 由一个_开头,后接至少一个 XID_Continue 字符。

以下每行字符串都是不合法的普通标识符:

1
2
3
4
ab&c  // & 不是 XID_Continue 字符
3abc // 阿拉伯数字不是 XID_Start 字符,因此,数字不能作为起始字符
_ // _ 后至少需要有一个 XID_Continue 字符
while // while 是仓颉关键字,普通标识符不能使用仓颉关键字

原始标识符是在普通标识符或仓颉关键字的首尾加上一对反引号,主要用于将仓颉关键字作为标识符的场景。

例如,以下每行字符串都是合法的原始标识符:

1
2
3
4
5
6
`abc`
`_abc`
`a1b2c3`
`if`
`while`
`à֮̅̕b`

我看和其他语言基本保持一致。简单理解为慎用已存在的关键字作为标识符;不要用中文标识符、数字开头即可。

程序结构

通常,开发者会在扩展名为 .cj 的文本文件中编写仓颉程序,这些程序和文件也被称为源代码和源文件。在程序开发的最后阶段,这些源代码将被编译为特定格式的二进制文件。

在仓颉程序的顶层作用域中,可以定义一系列的变量、函数和自定义类型(如 struct、class、enum 和 interface 等),其中的变量和函数分别被称为全局变量和全局函数。如果要将仓颉程序编译为可执行文件,需要在顶层作用域中定义一个 main 函数作为程序入口,它可以有 Array 类型的参数,也可以没有参数,它的返回值类型可以是整数类型或 Unit 类型。

变量

在仓颉编程语言中,一个变量由对应的变量名、数据(值)和若干属性构成,开发者通过变量名访问变量对应的数据,但访问操作需要遵从相关属性的约束(如数据类型、可变性和可见性等)。

变量定义的具体形式为:修饰符 变量名: 变量类型 = 初始值

const 变量

是一种特殊的变量,它以关键字 const 修饰,定义在编译时完成求值,并且在运行时不可改变的变量。

值类型和引用类型变量

从编译器实现层面看,任何变量总会关联一个值(一般是通过内存地址/寄存器关联),只是在使用时,对有些变量,将直接取用这个值本身,这被称为值类型变量;而对另一些变量,将这个值作为索引、取用这个索引指示的数据,这被称为引用类型变量。值类型变量通常在线程栈上分配,每个变量都有自己的数据副本;引用类型变量通常在进程堆中分配,多个变量可引用同一数据对象,对一个变量执行的操作可能会影响其他变量。

作用域

现代编程语言引入了“作用域”的概念及设计,将名字和程序元素的绑定关系限制在一定范围里。不同作用域之间可以是并列或无关的,也可以是嵌套或包含关系。一个作用域将明确开发者能用哪些名字访问哪些程序元素,具体规则是:

  1. 当前作用域中定义的程序元素与名字的绑定关系,在当前作用域和其内层作用域中是有效的,可以通过此名字直接访问对应的程序元素。
  2. 内层作用域中定义的程序元素与名字的绑定关系,在外层作用域中无效。
  3. 内层作用域可以使用外层作用域中的名字重新定义绑定关系,根据规则 1,此时内层作用域中的命名相当于遮盖了外层作用域中的同名定义,对此称内层作用域的级别比外层作用域的级别高。

表达式

if 表达式

if 表达式基本形式为:

1
2
3
4
5
if (条件) {
分支 1
} else {
分支 2
}

while 表达式

while 表达式的基本形式为:

1
2
3
while (条件) {
循环体
}

do-while 表达式

do-while 表达式的基本形式为:

1
2
3
do {
循环体
} while (条件)

for-in 表达式

for-in 表达式可以遍历那些扩展了迭代器接口 Iterable 的类型实例。for-in 表达式的基本形式为:

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

如果一个序列的元素是元组类型,则使用 for-in 表达式遍历时,“迭代变量”可以写成元组形式,以此实现对序列元素的解构,例如:

1
2
3
4
5
6
main() {
let array = [(1, 2), (3, 4), (5, 6)]
for ((x, y) in array) {
println("${x}, ${y}")
}
}

在 for-in 表达式的循环体中,不能修改迭代变量。

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

1
2
3
4
5
6
7
main() {
var number = 2
for (_ in 0..5) {
number *= number
}
println(number)
}

在部分循环遍历场景中,对于特定取值的迭代变量,可能需要直接跳过,进入下一轮循环。虽然可以使用 if 表达式和 continue 表达式在循环体中实现这一逻辑,但仓颉为此提供了更便捷的表达方式——可以在所遍历的“序列”之后用 where 关键字引导一个布尔表达式,这样在每次将进入循环体执行前,会先计算此表达式。如果值为 true 则执行循环体,反之直接进入下一轮循环。例如:

1
2
3
4
5
main() {
for (i in 0..8 where i % 2 == 1) { // i 为奇数才会执行循环体
println(i)
}
}

break 与 continue 表达式

在循环结构的程序中,有时需要根据特定条件提前结束循环或跳过本轮循环,为此仓颉引入了 break 与 continue 表达式,它们可以出现在循环表达式的循环体中,break 用于终止当前循环表达式的执行、转去执行循环表达式之后的代码,continue 用于提前结束本轮循环、进入下一轮循环。break 与 continue 表达式的类型都是 Nothing。

例如,以下程序使用 for-in 表达式和 break 表达式,在给定的整数数组中,找到第一个能被 5 整除的数字:

1
2
3
4
5
6
7
8
9
main() {
let numbers = [12, 18, 25, 36, 49, 55]
for (number in numbers) {
if (number % 5 == 0) {
println(number)
break
}
}
}

函数

仓颉使用关键字 func 来表示函数定义的开始,func 之后依次是函数名、参数列表、可选的函数返回值类型、函数体。其中,函数名可以是任意的合法标识符,参数列表定义在一对圆括号内(多个参数间使用逗号分隔),参数列表和函数返回值类型(如果存在)之间使用冒号分隔,函数体定义在一对花括号内。

函数定义举例:

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