当前位置: 首页 > news >正文

仓颉编程语言一

目录

  • 初识仓颉语言
  • 基本概念
    • 标识符
    • 程序结构
      • 变量
        • 值类型和引用类型变量
      • 作用域

在华为官方网站复制出来的,只是为了记录一下学习

初识仓颉语言

仓颉编程语言是一种面向全场景应用开发的通用编程语言,可以兼顾开发效率和运行性能,并提供良好的编程体验,主要具有如下特点:

  • 语法简明高效:仓颉编程语言提供了一系列简明高效的语法,旨在减少冗余书写、提升开发效率,例如插值字符串、主构造函数、Flow 表达式、match、if-let、while-let 和重导出等语法,让开发者可以用较少编码表达相关逻辑。

  • 多范式编程:仓颉编程语言支持函数式、命令式和面向对象等多范式编程,融合了高阶函数、代数数据类型、模式匹配、泛型等函数式语言的先进特性,还有封装、接口、继承、子类型多态等支持模块化开发的面向对象语言特性,以及值类型、全局函数等简洁高效的命令式语言特性。开发者可以根据开发偏好或应用场景,选用不同的编程范式。

  • 类型安全:仓颉编程语言是静态强类型语言,通过编译时类型检查尽早识别程序错误,降低运行时风险,也便于代码维护。同时,仓颉编译器提供了强大的类型推断能力,可以减少类型标注工作,提高开发效率。

  • 内存安全:仓颉编程语言支持自动内存管理,并在运行时进行数组下标越界检查、溢出检查等,确保运行时内存安全。

  • 高效并发:仓颉编程语言提供了用户态轻量化线程(原生协程),以及简单易用的并发编程机制,保证并发场景的高效开发和运行。

  • 兼容语言生态:仓颉编程语言支持和 C 等主流编程语言的互操作,并采用便捷的声明式编程范式,可实现对其他语言库的高效复用和生态兼容。

  • 领域易扩展:仓颉编程语言提供了基于词法宏的元编程能力,支持在编译时变换代码,此外,还提供了尾随 lambda、属性、操作符重载、部分关键字可省略等特性,开发者可由此深度定制程序的语法和语义,有利于内嵌式领域专用语言(Embedded Domain Specific Languages,EDSL)的构建。

  • 助力 UI 开发:UI 开发是构建端侧应用的重要环节,基于仓颉编程语言的元编程和尾随 lambda 等特性,可以搭建声明式 UI 开发框架,提升 UI 开发效率和体验。

  • 内置库功能丰富:仓颉编程语言提供了功能丰富的内置库,涉及数据结构、常用算法、数学计算、正则匹配、系统交互、文件操作、网络通信、数据库访问、日志打印、解压缩、编解码、加解密和序列化等功能。

基本概念

标识符

在仓颉编程语言中,开发者可以给一些程序元素命名,这些名字也被称为“标识符”,标识符分为普通标识符和原始标识符两类,它们分别遵从不同的命名规则。

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

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

其中,XID_Start、XID_Continue 定义见Unicode 标准。仓颉使用 Unicode 标准 15.0.0。

仓颉把所有标识符识别为 Normalization Form C (NFC) 后的形式。两个标识符如果在 NFC 后相等,则认为是相同的标识符。

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

abc
_abc
abc_
a1b2c3
a_b_c
a1_b2_c3
仓颉
__こんにちは

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

ab&c  // 使用了非法字符 “&”
3abc  // 数字不能出现在头部
while // 不能使用仓颉关键字

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

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

`abc`
`_abc`
`a1b2c3`
`if`
`while`
`à֮̅̕b`

以下每行字符串,由于反引号内的部分是不合法的普通标识符,所以它们整体也是不合法的原始标识符:

`ab&c`
`3abc`

程序结构

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

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

注意

定义 main 函数时,不需要写 func 修饰符。此外,如果需要获取程序启动时的命令行参数,可以声明和使用 Array 类型参数。

例如在以下程序中,我们在顶层作用域定义了全局变量 a 和全局函数 b,还有自定义类型 C、D 和 E,以及作为程序入口的 main 函数。

// example.cj
let a = 2023
func b() {}
struct C {}
class D {}
enum E { F | G }main() {println(a)
}

在非顶层作用域中不能定义上述自定义类型,但可以定义变量和函数,称之为局部变量局部函数。特别地,对于定义在自定义类型中的变量和函数,称之为成员变量成员函数

注意

enum 和 interface 中仅支持定义成员函数,不支持定义成员变量。

例如在以下程序中,我们在顶层作用域定义了全局函数 a 和自定义类型 A,在函数 a 中定义了局部变量 b 和局部函数 c,在自定义类型 A 中定义了成员变量 b 和成员函数 c。

// example.cj
func a() {let b = 2023func c() {println(b)}c()
}class A {let b = 2024public func c() {println(b)}
}main() {a()A().c()
}

在非顶层作用域中不能定义上述自定义类型,但可以定义变量和函数,称之为局部变量局部函数。特别地,对于定义在自定义类型中的变量和函数,称之为成员变量成员函数

注意

enum 和 interface 中仅支持定义成员函数,不支持定义成员变量。

例如在以下程序中,我们在顶层作用域定义了全局函数 a 和自定义类型 A,在函数 a 中定义了局部变量 b 和局部函数 c,在自定义类型 A 中定义了成员变量 b 和成员函数 c。

// example.cj
func a() {let b = 2023func c() {println(b)}c()
}class A {let b = 2024public func c() {println(b)}
}main() {a()A().c()
}

运行以上程序,将输出:

2023
2024

变量

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

变量定义的具体形式为:

修饰符 变量名: 变量类型 = 初始值

其中修饰符用于设置变量的各类属性,可以有一个或多个,常用的修饰符包括:

  • 可变性修饰符:let 与 var,分别对应不可变和可变属性,可变性决定了变量被初始化后其值还能否改变,仓颉变量也由此分为不可变变量和可变变量两类。
  • 可见性修饰符:private 与 public 等,影响全局变量和成员变量的可引用范围,详见后续章节的相关介绍。
  • 静态性修饰符:static,影响成员变量的存储和引用方式,详见后续章节的相关介绍。

在定义仓颉变量时,可变性修饰符是必要的,在此基础上,还可以根据需要添加其他修饰符。

  • 变量名应是一个合法的仓颉标识符。
  • 变量类型指定了变量所持有数据的类型。当初始值具有明确类型时,可以省略变量类型标注,此时编译器可以自动推断出变量类型。
  • 初始值是一个仓颉表达式,用于初始化变量,如果标注了变量类型,需要保证初始值类型和变量类型一致。在定义全局变量或静态成员变量时,必须指定初始值。在定义局部变量或实例成员变量时,可以省略初始值,但需要标注变量类型,同时要在此变量被引用前完成初始化,否则编译会报错。

例如,下列程序定义了两个 Int64 类型的不可变变量 a 和可变变量 b,随后修改了变量 b 的值,并调用 println 函数打印 a 与 b 的值。

main() {let a: Int64 = 20var b: Int64 = 12b = 23println("${a}${b}")
}

编译运行此程序,将输出:

2023

如果尝试修改不可变变量,编译时会报错,例如:

main() {let pi: Float64 = 3.14159pi = 2.71828 // Error, cannot assign to immutable value
}

当初始值具有明确类型时,可以省略变量类型标注,例如:

main() {let a: Int64 = 2023let b = aprintln("a - b = ${a - b}")
}

其中变量 b 的类型可以由其初值 a 的类型自动推断为 Int64,所以此程序也可以被正常编译和运行,将输出:

a - b = 0

在定义局部变量时,可以不进行初始化,但一定要在变量被引用前赋予初值,例如:

main() {let text: Stringtext = "仓颉造字"println(text)
}

编译运行此程序,将输出:

仓颉造字

在定义全局变量和静态成员变量时必须初始化,否则编译会报错,例如:

// example.cj
let global: Int64 // Error, variable in top-level scope must be initialized
// example.cj
class Player {static let score: Int32 // Error, static variable 'score' needs to be initialized when declaring
}
值类型和引用类型变量

程序在运行阶段,只有指令流转和数据变换,仓颉程序中的各种标识符已不复存在。由此可见,编译器使用了一些机制,将这些名字和编程所取用的数据实体/存储空间绑定起来。

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

从语言层面看,值类型变量对它所绑定的数据/存储空间是独占的,而引用类型变量所绑定的数据/存储空间可以和其他引用类型变量共享。

基于上述原理,在使用值类型变量和引用类型变量时,会存在一些行为差异,以下几点值得注意:

  1. 在给值类型变量赋值时,一般会产生拷贝操作,且原来绑定的数据/存储空间被覆写。在给引用类型变量赋值时,只是改变了引用关系,原来绑定的数据/存储空间不会被覆写。
  2. 用 let 定义的变量,要求变量被初始化后都不能再赋值。对于引用类型,这只是限定了引用关系不可改变,但是所引用的数据是可以被修改的。

在仓颉编程语言中,class 和 Array 等类型属于引用类型,其他基础数据类型和 struct 等类型属于值类型。

例如,以下程序演示了 struct 和 class 类型变量的行为差异:

struct Copy {var data = 2012
}class Share {var data = 2012
}main() {let c1 = Copy()var c2 = c1c2.data = 2023println("${c1.data}, ${c2.data}")let s1 = Share()let s2 = s1s2.data = 2023println("${s1.data}, ${s2.data}")
}

运行以上程序,将输出:

2012, 2023
2023, 2023

由此可以看出,对于值类型的 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 = c1c2.data = 2023 // Error, cannot assign to immutable value
}

作用域

在前文中,我们初步介绍了如何给仓颉程序元素命名,实际上,除了变量,我们还可以给函数和自定义类型等命名,在程序中将使用这些名字访问对应的程序元素。

但在实际应用中,需要考虑一些特殊情况:

  • 当程序规模较大时,那些简短的名字很容易重复,即产生命名冲突。
  • 结合运行时考虑,在有些代码片段中,另一些程序元素是无效的,对它们的引用会导致运行时错误。
  • 在某些逻辑构造中,为了表达元素之间的包含关系,不应通过名字直接访问子元素,而是要通过其父元素名间接访问。

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

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

在仓颉编程语言中,用一对大括号“{}”包围一段仓颉代码,即构造了一个新的作用域,其中可以继续使用大括号“{}”包围仓颉代码,由此产生了嵌套作用域,这些作用域均服从上述规则。特别的,在一个仓颉源文件中,不被任何大括号“{}”包围的代码,它们所属的作用域被称为“顶层作用域”,即当前文件中“最外层”的作用域,按上述规则,其作用域级别最低。

注意

用大括号“{}”包围代码构造作用域时,其中不限于使用表达式,还可以定义函数和自定义类型等。

例如在以下名为 test.cj 的仓颉源文件里,在顶层作用域中定义了名字 element,它和字符串“仓颉”绑定,而 main 和 if 引导的代码块中也定义了名字 element,分别对应整数 9 和整数 2023。由上述作用域规则,在第 4 行,element 的值为“仓颉”,在第 8 行,element 的值为 2023,在第 10 行,element 的值为 9。

// test.cj
let element = "仓颉"
main() {println(element)let element = 9if (element > 0) {let element = 2023println(element)}println(element)
}

运行以上程序,将输出:

仓颉
2023
9

http://www.mrgr.cn/news/60619.html

相关文章:

  • 多个立方体盒子组成
  • java学习技巧分享
  • 深入浅出神经网络:从基础原理到高级应用
  • FFmpeg 4.3 音视频-多路H265监控录放C++开发六,使用SDLVSQT显示yuv文件
  • 软考高级架构师-6.5-NoSQL数据库-超详细讲解+精简总结
  • 一文读懂高考志愿专业名词,让你的志愿填报不再迷茫
  • 011:软件卸载工具TotalUninstall安装教程
  • 重写(外壳不变)
  • CSS3新增长度单位
  • Python自动化测试中的Mock与单元测试实战
  • 基于vue、VantUI、django的程序设计
  • 【HeadFirst 设计模式】设计模式总结与C++案例
  • 外包干了30天,技术明显退步
  • 内网穿透之网络层ICMP隧道
  • 基于SSM的宠物猫狗商业系统设计与实现
  • Chrome异步编程
  • 关于群辉920+更新升级系统后SSD固态存储不受当前DSM版本支持的解决方法
  • python实战(四)——RAG预热实践
  • ssm智慧社区电子商务系统+vue
  • Radar Fields: Frequency-Space Neural Scene Representations for FMCW Radar 笔记
  • 容器化实践:优化DevOps环境下的容器交付流程
  • 【CSS/SCSS】@supports的介绍与用法
  • 【深度学习|地学应用】人工智能技术的发展历程与现状:探讨深度学习在遥感地学中的应用前景
  • arduino uno R3更换328pb-au芯片,烧录bootloader
  • “药品追溯到客户管理:数字化转型下的药企发展之路”
  • 基于LLaMA Factory对LLama 3指令微调的操作学习笔记