变量

仓颉编程语言作为一种静态类型(statically typed)语言,要求每个变量的类型必须在编译时确定。

根据是否可进行修改,可将变量分为 3 类:不可变变量(一旦初始化,值不可改变)、可变变量(值可以改变)、const 变量(必须编译期初始化,不可修改)。

变量的定义

变量定义的语法定义为:

variableDeclaration
: variableModifier* ('let' | 'var' | 'const') patternsMaybeIrrefutable (((':'↪ type)? ('=' expression)) | (':' type))
;
patternsMaybeIrrefutable
: wildcardPattern
| varBindingPattern
| tuplePattern
| enumPattern
;

变量的定义均包括四个部分:修饰符、let/var/const 关键字、patternsMaybeIrrefutable 和变量类型。其中:

修饰符

top-level 变量的修饰符包括:public, protected, private, internal
• 局部变量不能用修饰符修饰
• class 类型的成员变量的修饰符包括:public, protected, private, internal, static• struct 类型的成员变量的修饰符包括:public, private, internal, static

关键字 let/var/const

let 用于定义不可变变量,let 变量一旦初始化就不能再改变。• var 用于定义可变变量。

• const 用于定义 const 变量。

patternsMaybeIrrefutable

let(或 var/const)之后只能是那些一定或可能为 irrefutable 的 pattern(见模式的分类)。在语义检查阶段,会检查 pattern 是否真的是 irrefutable,如果不是 irrefutable pattern,则编译报错。

let(或 var/const)之后的 pattern 中引入的新的变量,全部都是 let 修饰(或 var 修饰)的变量。

• 在 class 和 struct 中定义成员变量时,只能使用 binding pattern(见绑定模式)。

变量类型是可选的,不声明变量类型时需要给变量初始值,编译器将尝试根据初始值推断变量类型;

变量可以定义在 top-level, 表达式内部,class/struct 类型内部。

需要注意的是:

pattern 和变量类型之间需要使用冒号(:)分隔,pattern 的类型需要和冒号后的类型相匹配。(2)关键字 let/var/const 和 pattern 是必选的;
(3)局部变量除了使用上述语法定义之外,还有如下几种情形会引入局部变量:
• for-in 循环表达式中,for 和 in 中间的 pattern,详见for-in 表达式
• 函数、lambda 定义中的形参,详见参数
• trr-with-resource 表达式中 ResourceSpeciffcations,详见异常
• match 表达式中,case 后的 pattern,详见模式匹配表达式

可以使用一对反引号()将关键字变为合法的标识符(例如,openthrow`等)。下面给出变量定义的一些实例:

let b: Int32 // Define read-only variable b with type Int32.
let c: Int64 // Define read-only variable c with type Int64.
var bb: String // Define writeable variable bb with type String.
var (x, y): (Int8, Int16) // Define two writeable variable: x with type Int8, xwith type Int16.
var `open` = 1 // Define a variable named `open` with value 1.
var `throw` = "throw" // Define a variable named `throw` with value "throw".const d: Int64 = 0 // Define a const variable named d with value 0.

变量的初始化

不可变变量和可变变量

不可变变量和可变变量的初始化均有两种方式:定义时初始化和先定义后初始化。需要注意的是,每个变量在使用前必须进行初始化,否则会报编译错误。关于变量的初始化,举例如下:

func f() {
let a = 1 // Define and initialize immutable variable a.
let b: Int32 // Define immutable variable b without initialization.b = 10 // Initialize variable b.
var aa: Float32 = 3.14 // Define and initialize mutable variable aa.
}

使用 let 定义的不可变变量,只能被赋值一次(即初始化),如果被多次赋值,会报编译错误。使用 var定义的可变变量,支持多次赋值。

func f() {
let a = 1 // Define and initialize immutable variable a.
a = 2 // error: immutable variable a cannot be reassigned.
var b: Float32 = 3.14 // Define and initialize mutable b.
b = 3.1415 // ok: mutable variable b can be reassigned.
}
class C {
let m1: Int64
init(a: Int64, b: Int64) {
m1 = a
if (b > 0) {
m1 = a * b // OK: immutable variable can be reassigned in constructor.}
}
}

全局变量及静态变量初始化

全局变量指定义在 top-level 的变量,静态变量包含定义在 class 或 struct 中的静态变量。全局变量和静态变量的初始化必须满足以下规则:

全局变量在声明时必须立即对其进行初始化,否则报错。即,声明必须提供一个初始化表达式。

• 静态变量在声明时必须立即对其进行初始化,可以采用与全局变量的初始化相同的形式,也可以在静态初始化器中进行初始化(更多细节见静态初始化器部分)。

– 注意,静态变量不能在其他静态变量中初始化:

class Foo {
static let x: Int64
static let y = (x = 1) // it's forbidden
}

• 初始化表达式 e 不能依赖未初始化的全局变量或静态变量。编译器会进行保守的分析,如果 e 可能会访问到未初始化的全局变量或静态变量,则报错。详细的分析取决于编译器的实现,在规范中没有指定。

全局/静态变量的初始化时机和初始化顺序规则如下:

所有的全局/静态变量都在 main (程序入口) 之前完成初始化;

对于同一个文件中声明的全局/静态变量,初始化顺序根据变量的声明顺序从上到下进行;如果使用了静态初始化器,则根据静态初始化器的初始化顺序规则执行(详见静态初始化器部分)

同一个包里不同文件或不同包中声明的全局/静态变量的初始化顺序取决于文件或包之间的依赖关系。如果文件 B.cj 依赖文件 A.cj 且 A.cj 不依赖 B.cj,则 A.cj 中的全局/静态变量的初始化在 B.cj 中的全局/静态变量的初始化之前;

如果文件或包之间存在循环依赖或者不存在任何依赖,那么它们之间的初始化顺序不确定,由编译器实现决定。

/* The initialization of the global variable cannot depend on the global
variables defined in other files of the same package. */
// a.cj
let x = 2
let y = z // OK, b.cj does not depend on this file directly or indirectly.let a = x // OK.
let c = A()
/* c.f is an open function, the compiler cannot statically determine whether thefunction meets the initialization rules of global variables, and an error maybe reported. */
let d = c.f()
open class A {
// static var x = A.z // Error, A.z is used before its initialization.// static var y = B.f // Error, B.f is used before its initialization.static var z = 1
public open func f(): Int64 {
return 77
}
}
class B {
static var e = A.z // OK.
static var f = x // OK.
}
// b.cj
let z = 10
// let y = 10 // Error, y is already defined in a.cj.// main.cj
main(): Int64 {
print("${x}")
print("${y}")
print("${z}")
return 1
}

const 变量

详见 const 章节。

修饰符

仓颉提供了很多修饰符,主要分以下两类:

• 访问修饰符

• 非访问修饰符

修饰符通常放在定义处的最前端,用来表示该定义具备某些特性。

访问修饰符

详细内容请参考包和模块管理章节访问修饰符。

非访问修饰符

仓颉提供了许多非访问修饰符以支持其它丰富的功能

open 表示该实例成员可被子类覆盖,或者该类能被子类继承,详见类
• sealed 表示该 class 或 interrace 只能在当前包内被继承或实现,详见类• override 表示覆盖父类的成员,详见类
• redef 表示重新定义父类的静态成员,详见类
• static 表示该成员是静态成员,静态成员不能通过实例对象访问,详见类• abstract 表示该 class 是抽象类,详见类

foreign 表示该成员是外部成员,详见语言互操作
• unsafe 表示与 C 语言进行互操作的上下文,详见语言互操作
• sealed 表示该 class 或 interrace 只能在当前包内被继承或实现,详见类• mut 表示该成员是具有可变语义的,详见函数

这些修饰符的具体功能详见对应的章节。

表达式

表达式通常由一个或多个操作数(operand)构成,多个操作数之间由操作符(operator)连接,每个表达式都有一个类型,计算表达式值的过程称为对表达式的求值(evaluation)。

在仓颉编程语言中,表达式几乎无处不在,有表示各种计算的表达式(如算术表达式、逻辑表达式等),也有表示分支和循环的表达式(如 if 表达式、循环表达式等)。对于包含多个操作符的表达式,必须明确每个操作符的优先级、结合性以及操作数的求值顺序。优先级和结合性规定了操作数与操作符的结合方式,操作数的求值顺序规定了二元和三元操作符的操作数求值顺序,它们都会对表达式的值产生影响。

下面将依次介绍仓颉中的表达式。

注:本章中对于各操作符的操作数类型的规定,均建立在操作符没有被重载的前提下。

字面量

字面量是一种拥有固定语法的表达式。对于内部不包含其他表达式的字面量(参见 1.3 字面量),它的值就是字面量自身的值,它的类型可由其语法或所在的上下文决定。当无法确定字面量类型时,整数字面量具有 Int64 类型,浮点数字面量具有 Float64 类型。对于可在内部包含其他表达式的集合类字面量和元组字面量(参见 [值类型]),它的值等于对其内部所有表达式求值后得到的字面量的值,它的类型由其语法确定。

字面量举例:

main(): Int64 {
10u8 // UInt8 literal
10i16 // Int16 literal
1024u32 // UInt32 literal
1024 // Int64 literal
1024.512_f32 // Float32 literal
1024.512 // Float64 literal
'a' // Rune literal
true // Bool literal
"Cangjie" // String literal
() // Unit literal
[1, 2, 3] // Array<Int64> literal
(1, 2, 3) // (Int64, Int64, Int64) literalreturn 0
}
Logo

新一代开源开发者平台 GitCode,通过集成代码托管服务、代码仓库以及可信赖的开源组件库,让开发者可以在云端进行代码托管和开发。旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐