1、标识符
首先了解一下Unicode:
Unicode ,官方机构Unicode联盟所用的中文名称为统一码 ,也有人称为万国码、国际码、单一码 ,是一种在计算机上使用的字符集。它为每种语言中的每个字符设定了统一而且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。最新版本的Unicode是2023年9月推出的Unicode 15.1。
在 Unicode 标准中,XID_Start 和 XID_Continue 属性用于标记可以作为 Unicode 标识符(Identifier)的起始字符和后续字符,其详细定义请参见 Unicode 标准文档(https://www.unicode.org/reports/tr31/tr31-37.html)。其中, XID_Start 包含中文和英文等字符,XID_Continue 包含中文、英文和阿拉伯数字等字符。仓颉语言 使用 Unicode 标准 15.0.0。
标识符是什么?
标识符(identifier)是软件工程领域中用于标识实体的符号序列,通常由字母、数字及下划线组成。在计算机编程中,主要用于变量、函数等程序元素的命名,建立名称与使用关系。
仓颉语言中的标识符
仓颉编程语言的标识符分为普通标识符和原始标识符两类,它们遵从不同的命名规则。
普通标识符不能和仓颉关键字相同,其取自以下两类字符序列:
- 由 XID_Start 字符开头,后接任意长度的 XID_Continue 字符。
- 由一个_开头,后接至少一个 XID_Continue 字符。
XID_Start(可作标识符首字符):
- 所有字母类别
-
- Lu:大写字母(A-Z, Γ, А, Ω)
- Ll:小写字母(a-z, γ, а, ω)
- Lt:首字母大写(Dž, Lj)
- Lm:修饰字母(ʰ, ʷ, ˆ)
- Lo:其他字母(汉字, あ, 가, ا, א)
- 字母类数字
-
- Nl:罗马数字(Ⅰ, Ⅱ, Ⅹ)
- 主要连接符
-
- Pc:下划线
_(主要)
- Pc:下划线
- 特殊格式字符
-
- Cf:ZWNJ(U+200C)、ZWJ(U+200D)
XID_Continue(可作为标识符非首字符):
- 包含 XID_Start 所有字符
- 所有数字
-
- Nd:0-9、٠-٩、०-९、0-9(各语种数字)
- 所有组合标记
-
- Mn:非间距标记(◌́, ◌̂, ◌̃)
- Mc:间距组合标记(◌ा, ◌ி)
- 所有连接符
-
- Pc:全部连接符符号
- 更多格式字符
-
- Cf:额外格式控制符
例如,以下每行字符串都是合法的普通标识符:
abc
_abc
abc_
a1b2c3
a_b_c
a1_b2_c3
仓颉
__こんにちは
以下每行字符串都是不合法的普通标识符:
ab&c // & 不是 XID_Continue 字符
3abc // 阿拉伯数字不是 XID_Start 字符,因此,数字不能作为起始字符
_ // _ 后至少需要有一个 XID_Continue 字符
while // while 是仓颉关键字,普通标识符不能使用仓颉关键字
原始标识符是在普通标识符或仓颉关键字的首尾加上一对反引号,主要用于将仓颉关键字作为标识符的场景。
例如,以下每行字符串都是合法的原始标识符:
`abc`
`_abc`
`a1b2c3`
`if`
`while`
`à֮̅̕b`
以下每行字符串,由于反引号内的部分是不合法的普通标识符,所以它们整体也是不合法的原始标识符:
`ab&c`
`3abc`
2、关键字
关键字是不能作为标识符使用的特殊字符串
仓颉语言的关键字如下表所示:
as break Bool
case catch class
const continue Rune
do else enum
extend for from
func false finally
foreign Float16 Float32
Float64 if in
is init inout
import interface Int8
Int16 Int32 Int64
IntNative let mut
main macro match
Nothing operator prop
package quote return
spawn super static
struct synchronized try
this true type
throw This unsafe
Unit UInt8 UInt16
UInt32 UInt64 UIntNative
var VArray where
while
上下文关键字是可以作为标识符使用的特殊字符串,它们在部分语法中作为关键字存在,但也可以作为普通标识符使用。
abstract open override
private protected public
redef get set
sealed internal
3、变量
在仓颉编程语言中,一个变量由对应的变量名、数据(值)和若干属性构成,开发者通过变量名访问变量对应的数据,但访问操作需要遵从相关属性的约束(如数据类型、可变性和可见性等)。
变量定义的具体形式为:
修饰符 变量名: 变量类型 = 初始值
其中修饰符用于设置变量的各类属性,可以有一个或多个,常用的修饰符包括:
- 可变性修饰符:
let与var,分别对应不可变和可变属性,可变性决定了变量被初始化后其值还能否改变,仓颉变量也由此分为不可变变量和可变变量两类。 const修饰符:const是一种特殊的变量修饰符。它用于声明常量,要求在声明时必须初始化,一旦被赋值,其值就不能被改变。这与let修饰符类似,都具有不可变的特性,但const在使用上有更严格的限制。- 可见性修饰符:
private与public等,影响全局变量和成员变量的可引用范围,详见后续章节的相关介绍。 - 静态性修饰符:
static,影响成员变量的存储和引用方式。
let 修饰的变量只能被赋值一次,即初始化;var 修饰的变量可以被多次赋值。
在定义仓颉变量时,可变性修饰符是必要的,在此基础上,还可以根据需要添加其他修饰符。
-
变量名应是一个合法的仓颉标识符。
-
变量类型指定了变量所持有数据的类型。当初始值具有明确类型时,可以省略变量类型标注,此时编译器可以自动推断出变量类型。
-
初始值是一个仓颉表达式,用于初始化变量,如果标注了变量类型,需要保证初始值类型和变量类型一致。在定义全局变量或静态成员变量时,必须指定初始值。在定义局部变量或实例成员变量时,可以省略初始值,但需要标注变量类型,同时要在此变量被引用前完成初始化,否则编译会报错。
/*
- 主函数,程序入口点
*/
main() {
// 打印"hello world"到控制台
println("hello world")// 声明一个不可变变量a,类型为Int64 let a: Int64 // 声明一个可变变量b,类型为Int64并初始化为2025 var b: Int64 = 2025 // 声明一个常量c,类型为Int64并初始化为2026 const c: Int64 = 2026 // 将变量b的值修改为2030 b = 2030 // 将变量b的值赋给变量a a = b // 输出三个变量的值 println("输出:${a}, ${b}, ${c}")}

如果尝试修改不可变变量,编译时会报错
main() {
let pi: Float64 = 3.1415926
//不可变变量不能修改
pi = 3.14 //cannot assign to immutable value
}

当初始值具有明确类型时,可以省略变量类型标注:
main() {
let a: Int64 = 2023
let b = a
println("a - b = ${a - b}")
let c = 2025
println("输出: ${c}")
}

在定义局部变量时,可以不进行初始化,但一定要在变量被引用前赋予初值:
main() {
let text: String
text = "用仓颉语言改变世界!"
println(text)
}

在定义全局变量和静态成员变量时必须初始化,否则编译会报错:
let PI: Float64 // Error, variable in top-level scope must be initialized
main(): Unit{
}

对于 try-catch 场景,编译器会假设 try 块总是全部被执行,且总是抛异常,从而进行相关报错:
main() {
let a: String
try {
a = "1"
} catch (_) {
a = "2" // Error, cannot assign to immutable value
}
}
const 变量
const 变量是一种特殊的变量,它以关键字 const 修饰,定义在编译时完成求值,并且在运行时不可改变的变量。
const PI = 3.14
4、(条件/循环)表达式
在仓颉编程语言中,条件表达式是 if 表达式,其值与类型需要根据使用场景来确定。循环表达式有三种:for-in 表达式、while 表达式和 do-while 表达式,它们的类型都是 Unit,值为 ()。
在仓颉程序中,由一对大括号"{}"包围起来的一组表达式,被称为"代码块",它将作为程序的一个顺序执行流,其中的表达式将按编码顺序依次执行。如果代码块中有至少一个表达式,规定此代码块的值与类型等于其中最后一个表达式的值与类型,如果代码块中没有表达式,规定这种空代码块的类型为 Unit,值为 ()。
1.条件表达式
if 表达式
if (条件) {
分支 1
} else {
分支 2
}
示例:
import std.random.*
main() {
// 生成一个随机年龄(0-100岁之间)
let age: UInt8 = Random().nextUInt8() % 101
println("随机生成的年龄: ${age}" )
// 简单判断是否成年(模仿原程序的 if-else 结构)
if (age >= 18) {
println("成年人")
} else {
println("未成年人")
}
}

2.循环表达式
1.while表达式
while (条件) {
循环体
}
main() {
var num = 1
var sum = 0
// 计算1到100的和
while (num <= 100) {
sum += num
num = num + 1
}
println("1到100的和是:${sum}")
}

2.do-while 表达式
do {
循环体
} while (条件)
main() {
var password = ""
let correctPassword = "123456"
var attempts = 0
println("请输入密码:")
// 至少让用户输入一次密码
do {
password = readln()
attempts = attempts + 1
if (password != correctPassword) {
println("密码错误,请重新输入")
}
} while (password != correctPassword && attempts < 3)
if (password == correctPassword) {
println("登录成功!")
} else {
println("尝试次数过多,账户已锁定")
}
}

3.for-in 表达式
for-in 表达式可以遍历那些扩展了迭代器接口 Iterable<T> 的类型实例
for (迭代变量 in 序列) {
循环体
}
main() {
// 定义一个数组
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
println("遍历数组:")
// 使用 for-in 遍历数组
for (num in numbers) {
println("元素:" + num.toString())
}
}

遍历区间
main() {
var sum = 0
for (i in 1..=100) {
sum += i
}
println(sum)
}

遍历元组构成的序列
main() {
let array = [(1, 2), (3, 4), (5, 6)]
for ((x, y) in array) {
println("${x}, ${y}")
}
}

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

使用通配符 _ 代替迭代变量
main() {
var number = 2
for (_ in 0..5) {
number *= number
}
println(number)
}
- 初始:number = 2
- 第1次:2 * 2 = 4
- 第2次:4 * 4 = 16
- 第3次:16 * 16 = 256
- 第4次:256 * 256 = 65536
- 第5次:65536 * 65536 = 4294967296

where 条件
在部分循环遍历场景中,对于特定取值的迭代变量,可能需要直接跳过,进入下一轮循环。虽然可以使用 if 表达式和 continue 表达式在循环体中实现这一逻辑,但仓颉为此提供了更便捷的表达方式------可以在所遍历的"序列"之后用 where 关键字引导一个布尔表达式,这样在每次将进入循环体执行前,会先计算此表达式。如果值为 true 则执行循环体,反之直接进入下一轮循环。
main() {
for (i in 0..8 where i % 2 == 1) { // i 为奇数才会执行循环体
println(i)
}
}

3.break 与 continue 表达式
在循环结构的程序中,有时需要根据特定条件提前结束循环或跳过本轮循环,为此仓颉引入了 break 与 continue 表达式,它们可以出现在循环表达式的循环体中,break 用于终止当前循环表达式的执行、转去执行循环表达式之后的代码,continue 用于提前结束本轮循环、进入下一轮循环。break 与 continue 表达式的类型都是 Nothing。
break表达式
main() {
let numbers = [12, 18, 25, 36, 49, 55]
for (number in numbers) {
if (number % 5 == 0) {
println(number)
break
}
println(number)
}
}

continue 表达式
main() {
let numbers = [12, 18, 25, 36, 49, 55]
for (number in numbers) {
if (number % 2 == 0) {
continue
}
println(number)
}
}
