NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约,已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时,不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
基础数据类型
元组类型
元组(
Tuple)可以将多个不同的类型组合在一起,成为一个新的类型元组类型使用 (
T1, T2, ..., TN) 表示,其中T1到TN可以是任意类型,不同类型间使用逗号(,)连接元组至少是
二元,例如,(Int64, Float64)表示一个二元组类型,(Int64, Float64, String)表示一个三元组类型元组的长度是固定的,即一旦定义了一个元组类型的实例,它的长度不能再被更改
元组类型是不可变类型,即一旦定义了一个元组类型的实例,它的内容(即单个元素)不能再被更新
但整个元组可被覆盖替换,例如:
let tuple1 = (8, false)var tuple2 = (true, 9, 20)tuple2 = tuple1 // Error, mismatched typestuple2[0] = false // Error, 'tuple element' can not be assignedvar tuple3 = (9, true)tuple3 = tuple1println(tuple3[0]) // 8println(tuple3[1]) // false
元组是一种类型, 元组类型的语法格式为: (T1, T2, T3, ..., TN)
T可以是任意类型, 不同的T或不同顺序, 都是一种具体的元组类型, 类型与类型之间也不相同
即, (Int64, Int32)与(Int32, Int64)就不是相同的类型
且, 元组是不可变类型, 定义了实例之后, 元素就无法再改变了
元组实例的元素, 可以通过下标索引进行访问
元组至少是二元的, 即 元组元素至少有两个, 但没有上限
元组类型的字面量
元组类型的字面量使用
(e1, e2, ..., eN)表示,其中e1到eN是表达式,多个表达式之间使用逗号分隔下面的例子中,分别定义了一个
(Int64, Float64)类型的变量x,以及一个(Int64, Float64, String)类型的变量y,并且使用元组类型的字面量为它们定义了初值:let x: (Int64, Float64) = (3, 3.141592)let y: (Int64, Float64, String) = (3, 3.141592, "PI")元组支持通过
t[index]的方式访问某个具体位置的元素,其中t是一个元组,index是下标,并且index只能是从0开始且小于元组元素个数的整数类型字面量,否则编译报错下面的例子中,使用
pi[0]和pi[1]可以分别访问二元组pi的第一个元素和第二个元素main() {var pi = (3.14, "PI")println(pi[0])println(pi[1])}编译并执行上述代码,输出结果为:
3.140000PI在赋值表达式中,可使用元组进行多赋值,参见 赋值操作符 章节
元组的类型是(T1, T2, T3, ..., TN), 元组字面量也很简单: (expr1, expr2, expr3, ..., exprN)
元组, 实际就是多个元素组成的一个元素组
可以通过下标访问具体的元素, 但不能修改元素值
使用元组字面量创建元组变量实例, 可以不用声明元组类型, 可以由编译器推导
let person = ("humid1ch", 10, 50, 100)print("${person[0]} ")print("${person[1]} ")print("${person[2]} ")print("${person[3]} ")println()元组类型的类型参数
可以为元组类型标记显式的类型参数名,下面例子中的
name和price就是 类型参数名func getFruitPrice (): (name: String, price: Int64) {return ("banana", 10)}对于一个元组类型,只允许统一写类型参数名,或者统一不写类型参数名,不允许交替存在,并且参数名本身不能作为变量使用或用于访问元组中元素
let a: (name: String, Int64) = ("banana", 5) // Errorlet b: (name: String, price: Int64) = ("banana", 5) // OKb.name // Error
元组类型在声明时, 可以像函数参数一样, 添加类型参数名, 比如: (name: String, age: Int64)
但类型参数名实际上只用作说明, 不能像变量名一样访问目标元素
且, 如果要声明类型参数名, 那么所有类型都要声明, 否则所有类型都不能声明类型参数名
数组类型
仓颉中的数组有两种: Array引用类型数组 和 VArray值类型数组
Array
可以使用
Array类型来构造单一元素类型,有序序列的数据仓颉使用
Array<T>来表示Array类型
T表示Array的元素类型,T可以是任意类型var a: Array<Int64> = [0, 0, 0, 0] // 元素类型为 Int64 的数组var b: Array<String> = ["a1", "a2", "a3"] // 元素类型为 String 的数组元素类型不相同的
Array是不相同的类型,所以它们之间不可以互相赋值因此以下例子是不合法的
b = a // 类型不匹配
数组Array, 可以构造单一元素类型的有序序列
数组的字面量语法为: [elem1, elem2, elem3, ..., elemN], 字面量中所有elem类型需要为同一类型
Array的元素类型不同, 则数组类型也不同, Array数组类型只与元素类型有关
不同的数组类型变量之间禁止相互赋值
数组, 也是一个各编程语言中几乎都存在的概念
数组元素可以通过下标索引访问
可以轻松使用字面量来初始化一个
Array,只需要使用方括号将逗号分隔的值列表括起来即可编译器会根据上下文自动推断
Array字面量的类型let a: Array<String> = [] // 创建一个元素类型为 String 的 空Array数组let b = [1, 2, 3, 3, 2, 1] // 创建一个元素类型为 Int64 的 Array数组,包含元素 1、2、3、3、2、1也可以使用构造函数的方式构造一个指定元素类型的
Array其中,
repeat属于Array构造函数中的一个命名参数需要注意的是,当通过
repeat指定的初始值初始化Array时,该构造函数不会拷贝repeat,如果repeat是一个引用类型,构造后数组的每一个元素都将指向相同的引用let a = Array<Int64>() // 创建一个元素类型为 Int64 的 空Array数组let b = Array<Int64>(3, repeat: 0) // 创建一个元素类型为 Int64、长度为 3 的 Array数组,所有元素均初始化为 0let c = Array<Int64>(3, {i => i + 1}) // 创建一个元素类型为 Int64、长度为 3 的 Array数组,所有元素都由初始化函数初始化示例中
let c = Array<Int64>(3, {i => i + 1})使用了lambda表达式作为初始化函数来初始化数组中的每一个元素,即{i => i + 1}
除了使用Array字面量直接创建Array实例
Array提供有构造函数, 最常用的就是这三个构造函数:
-
init()无参, 构造一个空的
Array数组let a = Array<Int64>()println(a)执行结果为:
[] -
init(size: Int64, repeat!: T)第一个参数:
size, 表示构造的Array数组的长度第二个参数:
repeat!, 表示元素初始值, 如果是此参数传入引用类型, 则数组中所有元素指向同一引用let b = Array<Int64>(3, repeat: 1)println(b)执行结果为:
[1, 1, 1] -
init(size: Int64, initElement: (Int64) -> T)第一个参数:
size, 表示构造的Array数组的长度第二个参数:
initElement, 各索引元素的初始化函数, 即 各位置会将索引作为参数, 传入此函数 返回值作为元素值let c = Array<Int64>(3, {i => i + 1})println(c)执行结果为:
[1, 2, 3]
Array还有其他构造函数, 具体之后再分析
访问Array成员
当需要对
Array的所有元素进行访问时,可以使用for-in循环遍历Array的所有元素
Array是按元素插入顺序排列的,因此对Array遍历的顺序总是恒定的main() {let arr = [0, 1, 2]for (i in arr) {println("The element is ${i}")}}编译并执行上面的代码,会输出:
The element is 0The element is 1The element is 2当需要知道某个
Array包含的元素个数时,可以使用size属性获得对应信息main() {let arr = [0, 1, 2]if (arr.size == 0) {println("This is an empty array")} else {println("The size of array is ${arr.size}")}}编译并执行上面的代码,会输出:
The size of array is 3当想访问单个指定位置的元素时,可以使用下标语法访问(下标的类型必须是
Int64)非空
Array的第一个元素总是从位置0开始的可以从
0开始访问Array的任意一个元素,直到最后一个位置(Array的size - 1)索引值不能使用负数或者大于等于
size,当编译器能检查出索引值非法时,会在编译时报错,否则会在运行时抛异常main() {let arr = [0, 1, 2]let a = arr[0] // a == 0let b = arr[1] // b == 1let c = arr[-1] // 数组大小为 3,但访问索引为 -1,这将导致溢出}
仓颉中要访问Array的元素, 有很多种方法:
-
for-in按顺序, 迭代遍历访问Array的元素for-in访问Array的元素, 是不可修改的 -
[index], 通过下标索引访问Array的元素通过
[index]访问Array的元素, 是可以修改索引位置的元素值的访问单个元素时,
index索引只能是Int64类型, 甚至不能是Int32等其他整数类型且, 仓颉
Array的索引下标的范围也是:[0, size - 1]或[0, size)
仓颉的Array提供有size属性, 可以可以直接访问获取Array的元素个数
如果想获取某一段
Array的元素,可以在下标中传入Range类型的值,就可以一次性取得Range对应范围的一段Arraylet arr1 = [0, 1, 2, 3, 4, 5, 6]let arr2 = arr1[0..5] // arr2 包含元素 0, 1, 2, 3, 4当
Range字面量在下标语法中使用时,可以省略start或end当省略
start时,Range会从0开始;当省略end时,Range的end会延续到最后一位let arr1 = [0, 1, 2, 3, 4, 5, 6]let arr2 = arr1[..3] // arr2 包含元素 0, 1, 2let arr3 = arr1[2..] // arr3 包含元素 2, 3, 4, 5, 6
仓颉的Array可以 以[Range]的语法, 获取原Array的一段元素的引用
具体的使用方式为: array[start..(=)end]
其中, Range字面量的start和end是可以省略的:
-
end省略, 但start不省略:array[start..]此时, 表示从索引
start开始截取(包括索引start的元素), 到Array的结尾当
Range字面量的=省略时,end不能省略 -
start省略, 但end不省略:array[..(=)end]此时, 表示从
Array首位开始截取, 到索引end结束如果
Range字面量的=不省略, 则包括索引end的元素如果
=省略, 则不包括索引end的元素其实只要理解了
Range语法, 就很简单 -
start和end都省略:array[..]此时, 表示截取整个
Array, 其实就等价于array
通过[Range]截取的Array, 是原Array数组片段的引用, 通过截取的片段修改元素, 是 **会影响原Array**的
修改Array
Array是一种长度不变的Collection类型,因此Array没有提供添加和删除元素的成员函数但是
Array允许对其中的元素进行修改,同样使用下标语法main() {let arr = [0, 1, 2, 3, 4, 5]arr[0] = 3println("The first element is ${arr[0]}")}编译并执行上面的代码,会输出:
The first element is 3
Array虽然是struct类型,但其内部持有的只是元素的引用,因此在作为表达式使用时不会拷贝副本,同一个Array实例的所有引用都会共享同样的元素数据因此对
Array元素的修改会影响到该实例的所有引用let arr1 = [0, 1, 2]let arr2 = arr1arr2[0] = 3// arr1 contains elements 3, 1, 2// arr2 contains elements 3, 1, 2
仓颉中Array实际是struct类型的, 但内部持有元素的引用, 所以Array是引用类型
通过下标索引访问Array的元素时, 可以直接修改元素值
VArray<T, $N>类型
除了引用类型的数组
Array,仓颉还引入了值类型数组VArray<T, $N>,其中T表示该值类型数组的元素类型,$N是一个固定的语法通过
$加上一个Int64类型的数值字面量表示这个值类型数组的长度需要注意的是,
VArray<T, $N>不能省略<T, $N>,且使用类型别名时,不允许拆分VArray关键字与其泛型参数与频繁使用引用类型
Array相比,使用值类型VArray可以减少堆上内存分配和垃圾回收的压力但是需要注意的是,由于值类型本身在传递和赋值时的拷贝,会产生额外的性能开销,因此建议不要在性能敏感场景使用较大长度的
VArray值类型和引用类型的特点请参见 值类型和引用类型变量
type varr1 = VArray<Int64, $3> // Oktype varr2 = VArray // Error注意:
由于运行时后端限制,当前
VArray<T, $N>的元素类型T或T的成员不能包含引用类型、枚举类型、Lambda表达式(CFunc除外)以及未实例化的泛型类型
仓颉中除了 引用类型的数组Array 之外, 还有一个 值类型数组VArray<T, $N>
如果要声明一个VArray类型变量, 必须明确VArray<T, $N>中的T和N
T表示类型, N表示数组长度
T和$N一起影响VArray的实际类型, T相同N不同, 实际的VArray类型也不同, T不同就更不用说了
VArray<T, $N>中, T不能包含引用类型、枚举类型、Lambda表达式等为实例化泛型
VArray是值类型的, 在对其进行传值或复制时, 会发生拷贝
VArray可以由一个数组的字面量来进行初始化,左值a必须标识出VArray的实例化类型:var a: VArray<Int64, $3> = [1, 2, 3]同时,它拥有两个构造函数:
// VArray<T, $N>(initElement: (Int64) -> T)let b = VArray<Int64, $5>({ i => i }) // [0, 1, 2, 3, 4]// VArray<T, $N>(repeat!: T)let c = VArray<Int64, $5>(repeat: 0) // [0, 0, 0, 0, 0]除此之外,
VArray<T, $N>类型提供了两个成员方法:
用于下标访问和修改的
[]操作符方法:var a: VArray<Int64, $3> = [1, 2, 3]let i = a[1] // i is 2a[2] = 4 // a is [1, 2, 4]下标访问的下标类型必须为
Int64用于获取
VArray长度的size成员:var a: VArray<Int64, $3> = [1, 2, 3]let s = a.size // s is 3
size属性的类型为Int64此外,
VArray还支持仓颉与C语言互操作场景使用,相关内容请参见数组
VArray实例可以直接使用 Array数组字面量创建, 但是此时必须声明实例类型为VArray<T, $N>, 否则会创建Array实例
并且, 声明的类型的$N必须与字面量中的元素个数一致, 否则类型不匹配
除了使用Array数组字面量, 还可以使用VArray<T, $N>构造函数
-
init(repeat!: T)命名参数
repeat, 类型为数组元素类型, 用于设置VArray数组每个元素的初始值 -
init(initElement: (Int64) -> T)参数
initElement, 各索引元素的初始化函数, 即 各位置会将索引作为参数, 传入此函数 返回值作为元素值
VArray未实现ToString接口, 所以不能直接使用print或println
但可以使用[]通过下标索引访问数组元素