1 概述
1.1 案例介绍
仓颉编程语言作为一款面向全场景应用开发的现代编程语言,通过现代语言特性的集成、全方位的编译优化和运行时实现、以及开箱即用的 IDE工具链支持,为开发者打造友好开发体验和卓越程序性能。
本案例将介绍仓颉编程语言入门知识,并结合简单的编程代码进行知识体验,仓颉开发环境我们使用了开发者空间提供的云主机环境,环境已经预装了仓颉工具链和CodeArts IDE for Cangjie,即开即用,非常便捷。
案例结合代码体验,让大家更直观的了解仓颉语言标识符、程序结构和表达式知识。
1.2 适用对象
- 个人开发者
- 高校学生
1.3 案例时间
本案例总时长预计30分钟。
1.4 案例流程
{{{width="55%" height="auto"}}}
说明:
- 进入华为开发者空间,登录云主机;
- 使用CodeArts IDE for Cangjie编程和运行仓颉代码。
1.5 资源总览
| 资源名称 | 规格 | 单价(元) | 时长(分钟) |
|---|---|---|---|
| 开发者空间——云主机 | 2vCPUs | 4GB | X86 | Ubuntu 或 4vCPUs | 8GB | ARM | Ubuntu | 免费 | 30 |
2 仓颉编程语言初体验
2.1 初识仓颉语言
仓颉编程语言是一款面向全场景智能的新一代编程语言,主打原生智能化、天生全场景、高性能、强安全。主要应用于鸿蒙原生应用及服务应用等场景中,为开发者提供良好的编程体验。
仓颉编程语言具有以下优势:
- 智能化:内嵌AgentDSL的编程框架,自然语言&编程语言有机融合;多Agent协同,简化符号表达,模式自由组合,支持各类智能应用开发。
- 全场景:轻量化可缩放运行时,模块化分层设计,内存再小也能装得下;全场景领域扩展,元编程和eDSL技术,支持面向领域声明式开发。
- 高性能:终端场景首款全并发 GC ,应用线程更流畅,响应更快。轻量化线程,并发性能更好,开销更少。
-
强安全:安全DNA融入语言设计,帮助开发者专注于业务逻辑,免于将太多精力投入到防御性编程中,编码即安全,漏洞无处藏。
在开发仓颉程序时,必用的工具之一是仓颉编译器,它可以将仓颉源代码编译为可运行的二进制文件,但现代编程语言的配套工具并不止于此,实际上,仓颉为开发者提供了编译器、调试器、包管理器、静态检查工具、格式化工具和覆盖率统计工具等一整套仓颉开发工具链,同时提供了友好的安装和使用方式,基本能做到“开箱即用”。
仓颉编程语言支持 CJNative 和 CJVM 两种后端。其中 CJNative 后端将代码编译为原生二进制代码,直接在操作系统层面上运行;CJVM 后端将代码编译为字节码,基于 VM(虚拟机)进行运行。本文档适配 CJNative 后端。本地环境安装可以参考仓颉工具链安装步骤。
本案例并不需要手动再安装仓颉工具链,而是直接使用华为开发者空间环境,空间提供了免费的云主机,且已经预装了仓颉工具链和编译器CodeArts IDE for Cangjie。

2.2 运行第一个仓颉程序
万事俱备,我们开始编写和运行第一个仓颉程序吧!
首先,进入华为开发者空间云主机桌面。

点击桌面CodeArts IDE for Cangjie,打开编辑器,点击新建工程,保持默认配置,点击创建。
产物类型说明:
- executable,可执行文件;
- static,静态库,是一组预先编译好的目标文件的集合;
- dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。

创建完成后,打开src/main.cj,参考下面代码简单修改后,点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。
package demo
// 第一个仓颉程序
main(): Int64 {
println("hello world")
println("你好,仓颉!")
return 0
}
(* 注意:后续替换main.cj文件代码时,package demo保留,如果创建项目不为demo,同样保留第一行package xxx即可)
(* 仓颉注释语法:// 符号之后写单行注释,也可以在一对 /* 和 */ 符号之间写多行注释)

到这里,我们第一个仓颉程序就运行成功啦,后面案例中的示例代码都可以放到main.cj文件中进行执行,接下来我们继续探索仓颉语言。
2.3 标识符
标识符是程序中的命名元素,用于表示变量、函数、类/结构体、模块、常量、类型别名、命名空间。标识符分为普通标识符和原始标识符两类。
2.3.1 普通标识符
组成规则:
(1)首字符:Unicode字母或下划线(_)
(2)后续字符:Unicode字母、数字或下划线
(3)支持多语言字符(包括中文、日文等)
(4)普通标识符不能和仓颉关键字相同
- 合法的普通标识符示例:
abc
_abc
abc_
a1b2c3
a_b_c
a1_b2_c3
仓颉
__こんにちは
- 不合法的普通标识符示例:
ab&c // 使用了非法字符 “&”
3abc // 数字不能出现在第一个字符
while // 不能使用仓颉关键字
2.3.2 原始标识符
在仓颉关键字或普通标识符的首尾加上一对反引号,主要用于将仓颉关键字作为标识符的场景。
- 合法的原始标识符示例:
`abc`
`_abc`
`a1b2c3`
`if`
`while`
`à֮̅̕b`
- 不合法的原始标识符示例:
`ab&c`
`3abc`
2.4 程序结构
仓颉程序所在扩展名为.cj的文本文称为源文件,仓颉程序成为源代码,在程序开发的最后阶段,这些源代码将被编译为特定格式的二进制文件。
在仓颉程序的顶层作用域中,可以定义一系列的变量、函数和自定义类型(如 struct、class、enum 和 interface 等),其中的变量和函数分别被称为全局变量和全局函数。如果要将仓颉程序编译为可执行文件,需要在顶层作用域中定义一个 main 函数作为程序入口,它可以有 Array\<String> 类型的参数,也可以没有参数,它的返回值类型可以是整数类型或 Unit 类型。
例如在以下程序中,在顶层作用域定义了全局变量 a 和全局函数 b,还有自定义类型 C、D 和 E,以及作为程序入口的 main 函数。
let a = 2023
func b() {}
struct C {}
class D {}
enum E { F | G }
main() {
println(a)
}
在非顶层作用域中不能定义上述自定义类型,但可以定义变量和函数,称之为局部变量和局部函数。特别地,对于定义在自定义类型中的变量和函数,称之为成员变量和成员函数。
(* 注意:enum 和 interface 中仅支持定义成员函数,不支持定义成员变量)
2.4.1 变量
在仓颉编程语言中,一个变量由对应的变量名、数据(值)和若干属性构成,开发者通过变量名访问变量对应的数据,但访问操作需要遵从相关属性的约束(如数据类型、可变性和可见性等)。
变量定义的具体形式为:
修饰符 变量名: 变量类型 = 初始值
其中修饰符用于设置变量的各类属性,可以有一个或多个,常用的修饰符包括:
(1)可变性修饰符:let 与 var,分别对应不可变和可变属性,可变性决定了变量被初始化后其值还能否改变,仓颉变量也由此分为不可变变量和可变变量两类。
(2)可见性修饰符:private 与 public 等,影响全局变量和成员变量的可引用范围。
(3)静态性修饰符:static,影响成员变量的存储和引用方式。
在定义仓颉变量时,可变性修饰符是必要的,在此基础上,还可以根据需要添加其他修饰符。
(1)变量名应是一个合法的仓颉标识符。
(2)变量类型指定了变量所持有数据的类型。当初始值具有明确类型时,可以省略变量类型标注,此时编译器可以自动推断出变量类型。
(3)初始值是一个仓颉表达式,用于初始化变量,如果标注了变量类型,需要保证初始值类型和变量类型一致。在定义全局变量或静态成员变量时,必须指定初始值。在定义局部变量或实例成员变量时,可以省略初始值,但需要标注变量类型,同时要在此变量被引用前完成初始化,否则编译会报错。
例如,下列程序定义了两个 Int64 类型的不可变变量 a 和可变变量 b,随后修改了变量 b 的值,并调用 println 函数打印 a 与 b 的值。
main() {
let a: Int64 = 20
var b: Int64 = 12
b = 23
println("${a}${b}")
}
运行以上程序,将输出:

2.4.2 值类型和引用类型
从编译器实现层面看:
- 任何变量总会关联一个值(一般是通过内存地址/寄存器关联),只是在使用时,对有些变量,将直接取用这个值本身,这被称为值类型变量。值类型变量通常在线程栈上分配,每个变量都有自己的数据副本。
- 而对另一些变量,将这个值作为索引、取用这个索引指示的数据,这被称为引用类型变量。引用类型变量通常在进程堆中分配,多个变量可引用同一数据对象,对一个变量执行的操作可能会影响其他变量。
从存储空间层面看:
- 值类型变量对它所绑定的数据/存储空间是独占的。
- 引用类型变量所绑定的数据/存储空间可以和其他引用类型变量共享。
基于上述原理,在使用值类型变量和引用类型变量时,会存在一些行为差异,注意:
(1)在给值类型变量赋值时,一般会产生拷贝操作,且原来绑定的数据/存储空间被覆写。在给引用类型变量赋值时,只是改变了引用关系,原来绑定的数据/存储空间不会被覆写。
(2)用 let 定义的变量,要求变量被初始化后都不能再赋值。对于引用类型,这只是限定了引用关系不可改变,但是所引用的数据是可以被修改的。
(* 注意:在仓颉编程语言中,class 和 Array 等类型属于引用类型,其他基础数据类型和 struct 等类型属于值类型。)
例如,以下程序演示了 struct 和 class 类型变量的行为差异:
//值类型
struct Copy {
var data = 2012
}
//引用类型
class Share {
var data = 2012
}
main() {
//值类型,每个变量都有自己的副本
let c1 = Copy()
var c2 = c1
//修改变量c2,c1变量不会受影响
c2.data = 2023
//所以,应输出2012,2023
println("${c1.data}, ${c2.data}")
//引用类型,多变量引用统一数据对象
let s1 = Share()
let s2 = s1
//修改变量s2,s1变量也会受影响
s2.data = 2023
//所以,应输出两个变量都被修改为了2023
println("${s1.data}, ${s2.data}")
}
运行以上程序,将输出:

2.4.3 作用域
在仓颉编程语言中,用一对大括号“{}”包围一段仓颉代码,即构造了一个新的作用域,其中可以继续使用大括号“{}”包围仓颉代码,由此产生了嵌套作用域,这些作用域均服从上述规则。特别的,在一个仓颉源文件中,不被任何大括号“{}”包围的代码,它们所属的作用域被称为“顶层作用域”,即当前文件中“最外层”的作用域,按上述规则,其作用域级别最低。
(* 注意:仓颉不允许使用单独的大括号“{}”,大括号必须依赖 if、match、函数体、类体、结构体等其他语法结构存在)
例如,在以下仓颉源文件里,在顶层作用域中定义了名字 element,它和字符串“仓颉”绑定,而 main 和 if 引导的代码块中也定义了名字 element,分别对应整数 9 和整数 2023。由上述作用域规则,在第 4 行,element 的值为“仓颉”,在第 8 行,element 的值为 2023,在第 10 行,element 的值为 9。
let element = "仓颉"
main() {
println(element)
let element = 9
if (element > 0) {
let element = 2023
println(element)
}
println(element)
}
运行以上程序,将输出:

2.5 表达式
任何一段程序的执行流程,只会涉及三种基本结构——顺序结构、分支结构和循环结构。实际上,分支结构和循环结构,是由某些指令控制当前顺序执行流产生跳转而得到的,它们让程序能够表达更复杂的逻辑,在仓颉中,这种用来控制执行流的语言元素就是条件表达式和循环表达式。
- 条件表达式分为 if 表达式和 if-let 表达式两种,它们的值与类型需要根据使用场景来确定。
- 循环表达式有四种:for-in 表达式、while 表达式、do-while 表达式和 while-let 表达式,它们的类型都是 Unit、值为 ()。
在仓颉程序中,由一对大括号“{}”包围起来的一组表达式,被称为“代码块”,它将作为程序的一个顺序执行流,其中的表达式将按编码顺序依次执行。
(* 注意:代码块本身不是一个表达式,不能被单独使用,需要依附于函数、条件表达式和循环表达式等执行和求值。)
2.5.1 if表达式
if 表达式的基本形式为:
if (条件) {
分支 1
} else {
分支 2
}
其中“条件”是布尔类型表达式,“分支 1”和“分支 2”是两个代码块。
当一个条件不成立时,可能还要判断另一个或多个条件、再执行对应的动作,仓颉允许在 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("脚踏实地,仰望星空")
}
}
运行以上程序,将输出:

(* 注意:if-let 表达式和 while-let 表达式都与模式匹配相关,请参见if-let 表达式和while-let 表达式介绍)
2.5.2 while 表达式
while 表达式的基本形式为:
while (条件) {
循环体
}
其中“条件”是布尔类型表达式,“循环体”是一个代码块。只要布尔表达式为 true,循环就会一直执行下去。
2.5.3 do-while 表达式
do {
循环体
} while (条件)
其中“条件”是布尔类型表达式,“循环体”是一个代码块。对于 while 语句而言,如果不满足条件,则不能进入循环。但有时候我们需要即使不满足条件,也至少执行一次。
do…while 循环和 while 循环相似,不同的是,do…while 循环至少会执行一次。
(* 注意:布尔表达式在循环体的后面,所以语句块在检测布尔表达式之前已经执行了。 如果布尔表达式的值为 true,则语句块一直执行,直到布尔表达式的值为 false)
2.5.4 for-in 表达式
for-in 表达式可以遍历那些扩展了迭代器接口 Iterable\<T> 的类型实例。for-in 表达式的基本形式为:
for (迭代变量 in 序列) {
循环体
}
其中“循环体”是一个代码块。“迭代变量”是单个标识符或由多个标识符构成的元组,用于绑定每轮遍历中由迭代器指向的数据,可以作为“循环体”中的局部变量使用。“序列”是一个表达式,它只会被计算一次,遍历是针对此表达式的值进行的,其类型必须扩展了迭代器接口 Iterable\<T>。
例如,以下程序使用 for-in 表达式,遍历中国地支字符构成的数组 noumenonArray(仓颉语言中字符类型使用 Rune 表示,一个 Rune 字面量由字符 r 开头,后跟一个由一对单引号或双引号包含的字符),输出农历 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
}
}
运行以上程序,将输出:

(* 注意:在 for-in 表达式的循环体中,不能修改迭代变量)
在一些应用场景中,只需要循环执行某些操作,但并不使用迭代变量,这时可以使用通配符 _ 代替迭代变量,例如:
main() {
var number = 2
for (_ in 0..5) {
number *= number
}
println(number)
}
运行以上程序,将输出:

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

2.5.5 break 与 continue 表达式
仓颉引入了 break 与 continue 表达式,它们可以出现在循环表达式的循环体中,break 用于终止当前循环表达式的执行、转去执行循环表达式之后的代码,continue 用于提前结束本轮循环、进入下一轮循环。
break示例:找到第一个能被 5 整除的数字后打印并推出循环。
main() {
let numbers = [12, 18, 25, 36, 49, 55]
for (number in numbers) {
if (number % 5 == 0) {
println(number)
break
}
}
}
运行以上程序,将输出:

continue示例:将给定整数数组中的奇数打印出来,number 是偶数时,continue 将被执行,这会提前结束本轮循环、进入下一轮循环,println 不会被执行。
main() {
let numbers = [12, 18, 25, 36, 49, 55]
for (number in numbers) {
if (number % 2 == 0) {
continue
}
println(number)
}
}
运行以上程序,将输出:

至此,仓颉入门案例内容全部完成。
如果想了解更多仓颉编程语言知识可以访问: https://cangjie-lang.cn/
6万+

被折叠的 条评论
为什么被折叠?



