程序结构
通常会在扩展名为 .cj 的文本文件中编写仓颉程序,这些程序和文件也被称为源代码和源文件,在程序开发的最后阶段,这些源代码将被编译为特定格式的二进制文件。
在仓颉程序的顶层作用域中,可以定义一系列的变量、函数和自定义类型(如 struct、class、enum 和 interface 等),其中的变量和函数分别被称为全局变量和全局函数。如果要将仓颉程序编译为可执行文件,您需要在顶层作用域中定义一个 main 函数作为程序入口,它可以有 Array 类型的参数,也可以没有参数,它的返回值类型可以是整数类型或 Unit 类型。
ps : 定义 main 函数时,不需要写 func 修饰符。此外,如果需要获取程序启动时的命令行参数,可以声明和使用 Array 类型参数
main(): Int64 {
return 0
}
除此之外我们还可以在main函数之上 , 也就是顶级作用于定义相关变量
let a = 2023
func b() {}
struct C {}
class D {}
enum E { F | G }
main() {
println(a)
}
在非顶层作用域中不能定义上述自定义类型,但可以定义变量和函数,称之为局部变量和局部函数。特别地,对于定义在自定义类型中的变量和函数,称之为成员变量和成员函数。
我们在定义方法的时候也可以进行方法的嵌套 , 同时一个.cj文件中也可以定义多个类
// example.cj
func a() {
let b = 2023
func c() {
println(b)
}
c()
}
class A {
let b = 2024
public func c() {
println(b)
}
}
main() {
a()
A().c()
}
值类型和引用类型变量
在使用值类型变量和引用类型变量时,会存在一些行为差异,以下几点值得注意:
- 在给值类型变量赋值时,一般会产生拷贝操作,且原来绑定的数据/存储空间被覆写。在给引用类型变量赋值时,只是改变了引用关系,原来绑定的数据/存储空间不会被覆写。
- 用 let 定义的变量,要求变量被初始化后都不能再赋值。对于引用类型,这只是限定了引用关系不可改变,但是所引用的数据是可以被修改的。
在仓颉编程语言中,基础数据类型和 struct 等类型属于值类型,而 class 和 Array 等类型属于引用类型。
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,则编译会报错
作用域
在前文中,我们初步介绍了如何给仓颉程序元素命名,实际上,除了变量,我们还可以给函数和自定义类型等命名,在程序中将使用这些名字访问对应的程序元素。
但在实际应用中,需要考虑一些特殊情况:
- 当程序规模较大时,那些简短的名字很容易重复,即产生命名冲突。
- 结合运行时考虑,在有些代码片段中,另一些程序元素是无效的,对它们的引用会导致运行时错误。
- 在某些逻辑构造中,为了表达元素之间的包含关系,不应通过名字直接访问子元素,而是要通过其父元素名间接访问。
为了应对这些问题,现代编程语言引入了“作用域”的概念及设计,将名字和程序元素的绑定关系限制在一定范围里。不同作用域之间可以是并列或无关的,也可以是嵌套或包含关系。一个作用域将明确我们能用哪些名字访问哪些程序元素,具体规则是:
- 当前作用域中定义的程序元素与名字的绑定关系,在当前作用域和其内层作用域中是有效的,可以通过此名字直接访问对应的程序元素。
- 内层作用域中定义的程序元素与名字的绑定关系,在外层作用域中无效。
- 内层作用域可以使用外层作用域中的名字重新定义绑定关系,根据规则 1,此时内层作用域中的命名相当于遮盖了外层作用域中的同名定义,对此我们称内层作用域的级别比外层作用域的级别高。
在仓颉编程语言中,用一对大括号“{}”包围一段仓颉代码,即构造了一个新的作用域,其中可以继续使用大括号“{}”包围仓颉代码,由此产生了嵌套作用域,这些作用域均服从上述规则。特别的,在一个仓颉源文件中,不被任何大括号“{}”包围的代码,它们所属的作用域被称为“顶层作用域”,即当前文件中“最外层”的作用域,按上述规则,其作用域级别最低。
// test.cj
let element = "仓颉"
main() {
println(element)
let element = 9
if (element > 0) {
let element = 2023
println(element)
}
println(element)
}