Hello Cangjie!华为仓颉编程语言:开启鸿蒙原生开发新纪元
一门编程语言的诞生,不仅是技术栈的扩充,更是一个庞大生态系统的奠基。华为仓颉,正是为万物互联的智能时代而生。
项目简介
仓颉是华为自主研发、面向全场景智能应用开发的新一代编程语言,主打原生智能化、天生全场景、高性能、强安全,旨在为鸿蒙生态提供高效、可靠的开发利器。语言诞生背景:为何需要“仓颉”?
仓颉的诞生,源于两大核心驱动力:技术自主权与生态构建。在当前的国际技术格局下,主流编程语言如Java、Go等均由国外厂商主导,底层技术由国外垄断。自研则实现编程语言底层技术上的突破,助力减少对国外技术的依赖,保障关键行业供应链安全同时,仓颉是华为构建“纯血鸿蒙”完整技术生态的关键一环。与鸿蒙操作系统内核、编译器、开发环境等全栈自研技术协同,仓颉将为鸿蒙原生应用开发提供强大的底层支持,繁荣开发者生态。技术基础:自研全栈,性能卓越
仓颉并非基于现有虚拟机,而是采用了全方位的自研编译优化和运行时实现。自研编译器与中间码
仓颉编译器前端基于自研的CHIR(Cangjie High-Level IR)进行高层优化,后端则进行了一系列深度优化,以充分发挥硬件性能。先进的运行时与内存管理
其运行时采用模块化分层设计,极为轻量。最大的亮点之一是采用了全并发(Fully Concurrent)的垃圾回收(GC)算法,摒弃了传统的“Stop-The-World”暂停,使得应用线程同步耗时极短(典型情况数十微秒),确保了应用的流畅响应。强大的跨平台支持
仓颉天生支持全场景。通过静态编译技术,它能将代码编译为不同操作系统平台的机器码,目前已支持HarmonyOS、Windows、Linux、macOS等环境,实现了真正的跨OS平台代码共享。应用场景
仓颉的定位是“面向全场景应用开发的现代编程语言”,其应用场景广泛而聚焦。鸿蒙原生应用开发
作为鸿蒙生态的“官方语言”之一,仓颉与ArkTS共同承担起鸿蒙应用开发的重任,尤其适用于对性能和安全有更高要求的复杂应用场景。高性能业务系统
凭借其静态类型、卓越的编译优化和轻量并发模型,仓颉适合开发服务器后端、高性能计算等业务系统。原生AI应用开发
这是仓颉的一大特色。它内嵌了Agent DSL(领域特定语言)编程框架,允许开发者用接近自然语言的方式声明和调用AI智能体(Agent),极大简化了AI应用的开发流程,实现了自然语言与编程语言的有机融合。服务端/云化场景
在云化场景中,仓颉编程语言提供了微服务应用开发框架,提供基于编译前处理模式的IOC容器、AOP切面编程、 ORM和事务管理、端到端的异步请求以及与其他组件快速对接等能力,帮助开发者快速构建微服务应用,并且通过仓颉轻量级用户态线程以及高性能网络协议库,能够充分利用系统资源,高效开发出高性能云服务应用。同时,仓颉社区协同合作伙伴以及开发者,共同构建了丰富的三方库及中间件生态,方便开发者构建仓颉程序。开发工具与环境:开箱即用
华为为仓颉提供了开箱即用的IDE工具链支持,致力于打造友好的开发体验。下载地址:https://cangjie-lang.cn/download仓颉编程语言提供三个版本(LTS、STS 和 Nightly Builds),每个通道均提供可以在Linux、Windows以及Mac上安装使用的软件包与帮助开发者在VScode平台上搭建开发环境的插件。每个版本通道更新/维护计划有所差异,具体计划根据各版本通道描述为准。提供仓颉编程语言的各类配套开发环境与工具,方便开发者更高效的学习与使用仓颉关键字概览
仓颉是一门多范式(支持面向对象、函数式、命令式)、静态类型并支持类型推断的现代语言,语法设计追求简洁高效。https://developer.huawei.com/consumer/cn/doc/cangjie-guides-V5/89_u7f16_u7a0b_u8bed_u8a00_u5f00_u53d1_u6307_u5357-V5
关键字:是不能作为标识符使用的特殊字符串,仓颉语言的关键字如下表所示:
操作符
下表列出了仓颉支持的所有操作符的优先级及结合性,其中优先级一栏数值越小,对应操作符的优先级越高。
| | | | |
|---|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | expr?.id, expr?[expr], expr?(expr), expr?{expr} | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
程序结构
来源:https://developer.huawei.com/consumer/cn/doc/cangjie-guides-V5/program_structure-V5
通常,开发者都会在扩展名为 .cj 的文本文件中编写仓颉程序,这些程序和文件也被称为源代码和源文件,在程序开发的最后阶段,这些源代码将被编译为特定格式的二进制文件。
在仓颉程序的顶层作用域中,可以定义一系列的变量、函数和自定义类型(如 struct、class、enum 和 interface 等),其中的变量和函数分别被称为全局变量和全局函数。如果要将仓颉程序编译为可执行文件,需要在顶层作用域中定义一个 main 函数作为程序入口,它可以有 Array<String> 类型的参数,也可以没有参数,它的返回值类型可以是整数类型或 Unit 类型。
注意
定义 main 函数时,不需要写 func 修饰符。此外,如果需要获取程序启动时的命令行参数,可以声明和使用 Array<String> 类型参数。
例如在以下程序中,在顶层作用域定义了全局变量 a 和全局函数 b,还有自定义类型 C、D 和 E,以及作为程序入口的 main 函数。
// example.cjlet a = 2023func b() {}struct C {}class D {}enum E { F | G }main() { println(a)}
在非顶层作用域中不能定义上述自定义类型,但可以定义变量和函数,称之为局部变量和局部函数。特别地,对于定义在自定义类型中的变量和函数,称之为成员变量和成员函数。
注意
enum 和 interface 中仅支持定义成员函数,不支持定义成员变量。
例如在以下程序中,在顶层作用域定义了全局函数 a 和自定义类型 A,在函数 a 中定义了局部变量 b 和局部函数 c,在自定义类型 A 中定义了成员变量 b 和成员函数 c。
// example.cjfunc a() { let b = 2023 func c() { println(b) } c()}class A { let b = 2024 public func c() { println(b) }}main() { a() A().c()}
运行以上程序,将输出:
变量
在仓颉编程语言中,一个变量由对应的变量名、数据(值)和若干属性构成,开发者通过变量名访问变量对应的数据,但访问操作需要遵从相关属性的约束(如数据类型、可变性和可见性等)。
变量定义的具体形式为:
其中修饰符用于设置变量的各类属性,可以有一个或多个,常用的修饰符包括:
- 可变性修饰符:let 与 var,分别对应不可变和可变属性,可变性决定了变量被初始化后其值还能否改变,仓颉变量也由此分为不可变变量和可变变量两类。
- 可见性修饰符:private 与 public 等,影响全局变量和成员变量的可引用范围,详见后续章节的相关介绍。
- 静态性修饰符:static,影响成员变量的存储和引用方式,详见后续章节的相关介绍。
在定义仓颉变量时,可变性修饰符是必要的,在此基础上,还可以根据需要添加其他修饰符。
- 变量名
- 变量类型指定了变量所持有数据的类型。当初始值具有明确类型时,可以省略变量类型标注,此时编译器可以自动推断出变量类型。
- 初始值是一个仓颉表达式,用于初始化变量,如果标注了变量类型,需要保证初始值类型和变量类型一致。在定义全局变量或静态成员变量时,必须指定初始值。在定义局部变量或实例成员变量时,可以省略初始值,但需要标注变量类型,同时要在此变量被引用前完成初始化,否则编译会报错。
例如,下列程序定义了两个 Int64 类型的不可变变量 a 和可变变量 b,随后修改了变量 b 的值,并调用 println 函数打印 a 与 b 的值。
main() { let a: Int64 = 20 var b: Int64 = 12 b = 23 println("${a}${b}")}
编译运行此程序,将输出:
如果尝试修改不可变变量,编译时会报错,例如:
main() { let pi: Float64 = 3.14159 pi = 2.71828 // Error, cannot assign to immutable value}
当初始值具有明确类型时,可以省略变量类型标注,例如:
main() { let a: Int64 = 2023 let b = a println("a - b = ${a - b}")}
其中变量 b 的类型可以由其初值 a 的类型自动推断为 Int64,所以此程序也可以被正常编译和运行,将输出:
在定义局部变量时,可以不进行初始化,但一定要在变量被引用前赋予初值,例如:
main() { let text: String text = "仓颉造字" println(text)}
编译运行此程序,将输出:
在定义全局变量和静态成员变量时必须初始化,否则编译会报错,例如:
// example.cjlet global: Int64 // Error, variable in top-level scope must be initialized
// example.cjclassPlayer{ static let score: Int32 // Error, static variable 'score' needs to be initialized when declaring}
需要注意的是,当编译器无法判断某些场景是否一定会被初始化或无法判断是否重复初始化了不可变变量时,会倾向于保守策略进行编译报错,见如下示例:
func calc(a: Int32){ println(a) return a*a}main() { let a: String if(calc(32) == 0){ a = "1" } a = "2" // Error, cannot assign to immutable value}
此外,对于 try-catch 场景,编译器会假设 try 块总是全部被执行、且总是抛异常,从而进行相关报错,见如下示例:
main() { let a: String try { a = "1" } catch (_) { a = "2" // Error, cannot assign to immutable value }}
值类型和引用类型变量
从编译器实现层面看,任何变量总会关联一个值(一般是通过内存地址/寄存器关联),只是在使用时,对有些变量,将直接取用这个值本身,这被称为值类型变量,而对另一些变量,将这个值作为索引、取用这个索引指示的数据,这被称为引用类型变量。值类型变量通常在线程栈上分配,每个变量都有自己的数据副本;引用类型变量通常在进程堆中分配,多个变量可引用同一数据对象,对一个变量执行的操作可能会影响其他变量。
从语言层面看,值类型变量对它所绑定的数据/存储空间是独占的,而引用类型变量所绑定的数据/存储空间可以和其他引用类型变量共享。
基于上述原理,在使用值类型变量和引用类型变量时,会存在一些行为差异,以下几点值得注意:
- 在给值类型变量赋值时,一般会产生拷贝操作,且原来绑定的数据/存储空间被覆写。在给引用类型变量赋值时,只是改变了引用关系,原来绑定的数据/存储空间不会被覆写。
- 用 let 定义的变量,要求变量被初始化后都不能再赋值。对于引用类型,这只是限定了引用关系不可改变,但是所引用的数据是可以被修改的。
在仓颉编程语言中,class 和 Array 等类型属于引用类型,其他基础数据类型和 struct 等类型属于值类型。
例如,以下程序演示了 struct 和 class 类型变量的行为差异:
struct Copy { var data = 2012}class Share { var data = 2012}main() { let c1 = Copy() var c2 = c1 c2.data = 2023 println("${c1.data}, ${c2.data}") let s1 = Share() let s2 = s1 s2.data = 2023 println("${s1.data}, ${s2.data}")}
运行以上程序,将输出:
由此可以看出,对于值类型的 Copy 类型变量,在赋值时总是获取 Copy 实例的拷贝,如 c2 = c1,随后对 c2 成员的修改并不影响 c1。对于引用类型的 Share 类型变量,在赋值时将建立变量和实例之间的引用关系,如 s2 = s1,随后对 s2 成员的修改会影响 s1。
如果将以上程序中的 var c2 = c1 改成 let c2 = c1,则编译会报错,例如:
struct Copy { var data = 2012}main() { let c1 = Copy() let c2 = c1 c2.data = 2023 // Error, cannot assign to immutable value}
作用域
在前文中,初步介绍了如何给仓颉程序元素命名,实际上,除了变量,还可以给函数和自定义类型等命名,在程序中将使用这些名字访问对应的程序元素。
但在实际应用中,需要考虑一些特殊情况:
- 当程序规模较大时,那些简短的名字很容易重复,即产生命名冲突。
- 结合运行时考虑,在有些代码片段中,另一些程序元素是无效的,对它们的引用会导致运行时错误。
- 在某些逻辑构造中,为了表达元素之间的包含关系,不应通过名字直接访问子元素,而是要通过其父元素名间接访问。
为了应对这些问题,现代编程语言引入了“作用域”的概念及设计,将名字和程序元素的绑定关系限制在一定范围里。不同作用域之间可以是并列或无关的,也可以是嵌套或包含关系。一个作用域将明确开发者能用哪些名字访问哪些程序元素,具体规则是:
- 当前作用域中定义的程序元素与名字的绑定关系,在当前作用域和其内层作用域中是有效的,可以通过此名字直接访问对应的程序元素。
- 内层作用域中定义的程序元素与名字的绑定关系,在外层作用域中无效。
- 内层作用域可以使用外层作用域中的名字重新定义绑定关系,根据规则 1,此时内层作用域中的命名相当于遮盖了外层作用域中的同名定义,对此可以称内层作用域的级别比外层作用域的级别高。
在仓颉编程语言中,用一对大括号“{}”包围一段仓颉代码,即构造了一个新的作用域,其中可以继续使用大括号“{}”包围仓颉代码,由此产生了嵌套作用域,这些作用域均服从上述规则。特别的,在一个仓颉源文件中,不被任何大括号“{}”包围的代码,它们所属的作用域被称为“顶层作用域”,即当前文件中“最外层”的作用域,按上述规则,其作用域级别最低。
注意
仓颉不允许使用单独的大括号“{}”,大括号必须依赖 if、match、函数体、类体、结构体等其他语法结构存在。
例如在以下名为 test.cj 的仓颉源文件里,在顶层作用域中定义了名字 element,它和字符串“仓颉”绑定,而 main 和 if 引导的代码块中也定义了名字 element,分别对应整数 9 和整数 2023。由上述作用域规则,在第 4 行,element 的值为“仓颉”,在第 8 行,element 的值为 2023,在第 10 行,element 的值为 9。
// test.cjlet element = "仓颉"main() { println(element) let element = 9 if (element > 0) { let element = 2023 println(element) } println(element)}
运行以上程序,将输出:
表达式
if 表达式
if 表达式的基本形式为:
if (条件) { 分支 1} else { 分支 2}
其中“条件”是布尔类型表达式,“分支 1”和“分支 2”是两个代码块。if 表达式将按如下规则执行:
- 计算“条件”表达式,如果值为 true 则转到第 2 步,值为 false 则转到第 3 步。
在一些场景中,可能只关注条件成立时该做些什么,所以 else 和对应的代码块是允许省略的。
如下程序演示了 if 表达式的基本用法:
import std.random.*main() { let number: Int8 = Random().nextInt8() println(number) if (number % 2 == 0) { println("偶数") } else { println("奇数") }}
在这段程序中,使用仓颉标准库的 random 包生成了一个随机整数,然后使用 if 表达式判断这个整数是否能被 2 整除,并在不同的条件分支中打印“偶数”或“奇数”。
仓颉编程语言是强类型的,if 表达式的条件只能是布尔类型,不能使用整数或浮点数等类型,和 C 语言等不同,仓颉不以条件取值是否为 0 作为分支选择依据,例如以下程序将编译报错:
main() { let number = 1 if (number) { // Error, mismatched types println("非零数") }}
在许多场景中,当一个条件不成立时,可能还要判断另一个或多个条件、再执行对应的动作,仓颉允许在 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 表达式的值与类型,需要根据使用形式与场景来确定:
当含 else 分支的 if 表达式被求值时,需要根据求值上下文确定 if 表达式的类型:
如果编译通过,则 if 表达式的值就是所执行分支代码块的值。
- 如果上下文明确要求值类型为 T,则 if 表达式各分支代码块的类型必须是 T 的子类型,这时 if 表达式的类型被确定为 T,如果不满足子类型约束,编译会报错。
- 如果上下文没有明确的类型要求,则 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。
while 表达式
while 表达式的基本形式为:
其中“条件”是布尔类型表达式,“循环体”是一个代码块。while 表达式将按如下规则执行:
- 计算“条件”表达式,如果值为 true 则转第 2 步,值为 false 转第 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}")}
运行以上程序,将输出:
do-while 表达式
do-while 表达式的基本形式为:
其中“条件”是布尔类型表达式,“循环体”是一个代码块。do-while 表达式将按如下规则执行:
- 计算“条件”表达式,如果值为 true 则转第 1 步,值为 false 转第 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.14。
for-in 表达式
for-in 表达式可以遍历那些扩展了迭代器接口 Iterable<T> 的类型实例。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 - 1 println("农历 2024 年各月干支:") for (noumenon in noumenonArray) { print("${metaArray[index]}${noumenon} ") index = (index + 1) % 10 }}
其中 r 开头的字符表示字符类型字面量。运行以上程序,将输出:
- 丙寅 丁卯 戊辰 己巳 庚午 辛未 壬申 癸酉 甲戌 乙亥 丙子 丁丑
遍历区间
for-in 表达式可以遍历区间类型实例,例如:
main() { var sum = 0 for (i in 1..=100) { sum += i } println(sum)}
运行以上程序,将输出:
关于区间类型的详细内容,请参阅基本数据类型区间类型章节。
遍历元组构成的序列
如果一个序列的元素是元组类型,则使用 for-in 表达式遍历时,“迭代变量”可以写成元组形式,以此实现对序列元素的解构,例如:
main() { let array = [(1, 2), (3, 4), (5, 6)] for ((x, y) in array) { println("${x}, ${y}") }}
运行以上程序,将输出:
迭代变量不可修改
在 for-in 表达式的循环体中,不能修改迭代变量,例如以下程序在编译时会报错:
main() { for (i in 0..5) { i = i * 10 // Error, cannot assign to value which is an initialized 'let' constant println(i) }}
使用通配符 _ 代替迭代变量
在一些应用场景中,只需要循环执行某些操作,但并不使用迭代变量,这时可以使用通配符 _ 代替迭代变量,例如:
main() { var number = 2 for (_ in 0..5) { number *= number } println(number)}
运行以上程序,将输出:
注意
在这种场景下,如果使用普通的标识符定义迭代变量,编译会输出“unused variable”告警,使用通配符 _ 则可以避免这一告警。
where 条件
在部分循环遍历场景中,对于特定取值的迭代变量,可能需要直接跳过、进入下一轮循环,虽然可以使用 if 表达式和 continue 表达式在循环体中实现这一逻辑,但仓颉为此提供了更便捷的表达方式——可以在所遍历的“序列”之后用 where 关键字引导一个布尔表达式,这样在每次将进入循环体执行前,会先计算此表达式,如果值为 true 则执行循环体,反之直接进入下一轮循环。例如:
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 整除的数字:
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中的后续数字不会被遍历到,因此运行以上程序,将输出:
以下程序使用 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 不会被执行,因此运行以上程序,将输出:
学习资源与社区
对于初学者和希望深入研究的开发者,华为及社区已提供了丰富的资源:官方文档与白皮书
访问华为开发者联盟仓颉主页,获取最权威的语言指南、技术白皮书和API文档。系统教材与课程
已有《仓颉编程快速上手》、《图解仓颉编程》等系列书籍出版,覆盖从基础到进阶的知识点,配套源代码和视频,适合系统学习。开源教程与社区
仓颉项目地址:https://gitcode.com/Cangjie仓颉社区优质开源项目:
https://gitcode.com/Cangjie/CangjieCommunity/blob/main/%E7%A4%BE%E5%8C%BA%E4%BC%98%E8%B4%A8%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE.md
示例代码运行流程
一个简单的“智能体”代码片段,感受仓颉在AI原生开发上的便捷性:// 1. 编写文件:例如 Planner.cj// 定义一个旅行规划智能体@agentclassPlanner {@prompt[pattern=APE] (action: "帮助用户制定旅行路线",purpose: "让用户在计划时间内多参观景点并得到充分休息",expectation: "生成一条合理的旅游景点路线,包括时间、景点、通勤等信息")fn chat(query: String) -> String {// 智能体处理逻辑...return"为您规划的路线是:..."}}// 主程序fn main() {// 2. 创建并使用智能体let agent = Planner()let result = agent.chat("我想前往上海")// 3. 运行输出结果println(result) // 输出:为您规划的路线是:...}
结语
仓颉编程语言的亮相,不仅仅是华为在核心技术领域的一次重大突破,更是为中国乃至全球开发者打开了一扇通往全场景智能应用开发的新大门。它融合了现代语言的优雅、卓越的性能与前瞻的AI原生支持,背靠日益壮大的鸿蒙生态,其未来充满无限可能。