2.7.1 什么是数组?
数组(Array)是一种有序的、元素类型相同的集合。说白了,就是把一堆同一类型的东西排成一排,编上号。
你可以把数组想象成一个鸡蛋托——每个格子只能放一个鸡蛋,所有格子放的东西类型相同(都是鸡蛋),而且每个格子有固定位置(第 1 个、第 2 个……)。
let scores = [95, 87, 92, 78, 88] // 5 个 Int64 分数
let names = ["张三", "李四", "王五"] // 3 个 String 名字
与元组(Tuple)不同的是,数组的所有元素必须是同一种类型,而元组的元素可以不同类型。
2.7.2 创建数组
仓颉中用 Array<T> 来表示数组类型,T 是元素的类型,T 可以是任意类型。
方式一:用字面量创建(最常用)
用方括号 [] 把值括起来,逗号分隔:
let a: Array<Int64> = [1, 2, 3, 4] // 明确标注类型
let b = [1, 2, 3, 3, 2, 1] // 让编译器自动推断为 Array<Int64>
let c: Array<String> = [] // 创建一个空数组(必须标注类型)
当你不写类型时,编译器会根据里面的值自动推断。比如 [1, 2, 3] 会自动推断为 Array<Int64>。
方式二:用构造函数创建
let a = Array<Int64>() // 创建空数组
let b = Array<Int64>(3, repeat: 0) // 创建长度为 3 的数组,全部初始化成 0
// 结果: [0, 0, 0]
repeat: 0 表示"用 0 重复填充"。如果 repeat 的值是引用类型(比如对象),所有元素会指向同一个对象,而不是复制多份。
方式三:用初始化函数创建
let c = Array<Int64>(3, { i => i + 1 }) // 结果: [1, 2, 3]
这里的 { i => i + 1 } 是一个 lambda 表达式(也叫匿名函数),你可以把它理解成一个"小公式":
给一个数字 i(下标,从 0 开始),计算出对应的值。下标 i |
公式 i + 1 |
结果 |
|---|---|---|
| 0 | 0 + 1 | 1 |
| 1 | 1 + 1 | 2 |
| 2 | 2 + 1 | 3 |
Lambda 表达式是仓颉中非常常用的语法,后面会专门讲解。目前你只需要知道 { i => 表达式 } 的意思是"对每个下标 i,计算出对应的元素值"。
2.7.3 访问数组元素
获取数组长度
用 .size 属性获取数组中有多少个元素:
main() {
let arr = [0, 1, 2]
println("数组长度是 ${arr.size}") // 输出:数组长度是 3
}
用下标访问单个元素
用 数组名[下标] 的方式获取指定位置的元素。下标从 0 开始,即第一个元素是 arr[0],最后一个元素是 arr[arr.size - 1]。
main() {
let arr = [0, 1, 2]
let a = arr[0] // a == 0(第一个元素)
let b = arr[1] // b == 1(第二个元素)
let c = arr[2] // c == 2(第三个元素)
}
下标不能是负数,也不能大于等于数组长度。比如长度为 3 的数组,只能访问 arr[0]、arr[1]、arr[2]。访问 arr[3] 或 arr[-1] 会导致错误。
let c = arr[-1] // ❌ 错误:下标 -1 超出范围
用 Range 访问一段元素
将在下一章详细介绍
如果想一次性取出数组中连续的一段,可以用 .. 范围语法:
let arr1 = [0, 1, 2, 3, 4, 5, 6]
let arr2 = arr1[0..5] // 从下标 0 到 4(不包含 5),结果是 [0, 1, 2, 3, 4]
范围语法的省略写法:
| 写法 | 含义 | 示例 | 结果 |
|---|---|---|---|
start..end |
从 start 到 end-1 | [0,1,2,3,4][1..3] |
[1, 2] |
..end |
从 0 到 end-1 | [0,1,2,3,4][..3] |
[0, 1, 2] |
start.. |
从 start 到结尾 | [0,1,2,3,4][2..] |
[2, 3, 4] |
let arr1 = [0, 1, 2, 3, 4, 5, 6]
let arr2 = arr1[..3] // 取前 3 个: [0, 1, 2]
let arr3 = arr1[2..] // 从第 3 个取到末尾: [2, 3, 4, 5, 6]
arr[n..] 和 arr[..n] 这类省略起始或结束的切片写法仅限数组等下标访问类型使用。其中 arr[n..] 等价于 arr[n..arr.size],arr[..n] 等价于 arr[0..n]。
记住口诀——左闭右开:start..end 包含 start,不包含 end。比如 [0..5] 取的是 0、1、2、3、4,没有 5。
用 for-in 遍历所有元素
此处暂先死记硬背,后续文章会讲解
main() {
let arr = [0, 1, 2]
for (i in arr) {
println("元素是 ${i}")
}
}
运行结果:
元素是 0
元素是 1
元素是 2
数组会按照元素插入的顺序(即你写进去的顺序)依次遍历,顺序是固定的。
2.7.4 修改数组元素
数组创建后长度不能改变(不能增删元素),但可以修改已有元素的值:
main() {
let arr = [0, 1, 2, 3, 4, 5]
arr[0] = 3 // 把第一个元素从 0 改成 3
println("第一个元素是 ${arr[0]}") // 输出:第一个元素是 3
}
虽然这里 arr 是用 let 声明的(不可变变量),但数组内部存储的元素可以被修改。这是因为数组内部只是"引用"着这些数据,修改元素并不改变数组本身的引用。
2.7.5 数组的"共享引用"特性
数组在赋值时不会复制数据,而是让两个变量指向同一份数据:
let arr1 = [0, 1, 2]
let arr2 = arr1 // arr2 和 arr1 指向同一份数据
arr2[0] = 3 // 修改 arr2 的第一个元素
// 结果:arr1 和 arr2 都被影响了!
// arr1 变成了 [3, 1, 2]
// arr2 变成了 [3, 1, 2]
可以把数组想象成一个共享的笔记本——小明(arr1)和小红(arr2)共用一个笔记本,谁在上面改了内容,另一个人看到的也会变。
这个特性在很多编程场景下很有用,但也要小心——如果你不希望共享数据,需要显式地复制一份。
2.7.6 值类型数组 VArray
除了上面介绍的 Array(引用类型),仓颉还提供了一个值类型数组 VArray。
和普通 Array 有什么区别?
| 对比 | Array |
VArray |
|---|---|---|
| 类型 | 引用类型 | 值类型 |
| 赋值行为 | 共享同一份数据 | 复制一份新数据 |
| 长度定义 | 运行时确定 | 编译时固定 |
| 内存分配 | 堆上分配 | 栈上分配(更快) |
VArray 语法
VArray<T, $N> 中的 $N 表示数组长度,编译时必须确定:
var a: VArray<Int64, $3> = [1, 2, 3] // 长度为 3 的 VArray
构造函数
// 用初始化函数:下标 i 从 0 到 4,值 = i
let b = VArray<Int64, $5>({i => i}) // [0, 1, 2, 3, 4]
// 用 repeat 填充
let c = VArray<Int64, $5>(repeat: 0) // [0, 0, 0, 0, 0]
访问和修改
和普通 Array 一样使用下标:
var a: VArray<Int64, $3> = [1, 2, 3]
let i = a[1] // i == 2
a[2] = 4 // a 变为 [1, 2, 4]
let s = a.size // s == 3
VArray 是值类型,赋值时会复制数据,不会像 Array 那样共享引用:
var a: VArray<Int64, $3> = [1, 2, 3]
var b = a
b[0] = 99
// a 仍然是 [1, 2, 3],不受影响
// b 是 [99, 2, 3]
由于运行时后端限制,VArray 的元素类型不能包含引用类型、枚举类型、Lambda 表达式等。这在入门阶段几乎不会遇到,了解一下有这个限制就行。
2.7.7 动手试一试 ✏️
- 创建一个包含你一周每天学习时长的数组(比如
[2, 1.5, 0, 3, 2, 4, 1]),然后计算一周的总学习时长。
main() {
let study_hours = [2.0, 1.5, 0.0, 3.0, 2.0, 4.0, 1.0]
var total = 0.0
for (h in study_hours) {
total = total + h
}
println("一周总学习时长:${total} 小时")
}
- 创建一个数组
[10, 20, 30, 40, 50],尝试用不同的下标范围语法取出[20, 30, 40]这一段。
提示:[20, 30, 40] 在原数组中的下标范围是从 1 到 3(不包含 4),所以是 arr[1..4]。试试用 arr[1..] 或 arr[..4] 看看结果有什么不同?
2.7.8 小结
| 概念 | 要点 |
|---|---|
| 什么是数组 | 相同类型元素的有序集合 |
| 类型写法 | Array<T>,T 是元素类型 |
| 创建方式 | 字面量 [1, 2, 3]、构造函数 Array<Int64>(3, repeat: 0)、初始化函数 |
| 访问元素 | arr[i],下标从 0 开始 |
| 范围访问 | arr[start..end],左闭右开 |
| 数组长度 | arr.size |
| 修改元素 | arr[i] = 新值,但不能增删 |
| 共享引用 | 数组赋值不复制数据,两个变量指向同一份数据 |
| 值类型数组 | VArray<T, $N>,长度编译时固定,赋值时复制数据 |
2.7.9 常见问题
Q1:`arr[arr.size]` 会怎样?
会报错!因为下标从 0 开始,最后一个元素的下标是 arr.size - 1。访问 arr[arr.size] 就是越界了。
Q2:能创建一个包含不同类型元素的数组吗?
不能!数组要求所有元素类型相同。如果你需要不同类型的数据,可以使用元组(Tuple)。
Q3:`Array` 和 `VArray` 我应该用哪个?
- 日常开发:用
Array就足够了,灵活方便 - 性能敏感场景:如果数组长度固定且很小(比如几百以内),可以考虑
VArray - 入门阶段:先用
Array,等对值类型和引用类型有更深理解后再了解VArray
Q4:数组下标为什么不从 1 开始?
这是一个历史悠久的编程惯例。从 0 开始有很多好处,比如 arr[0] 就表示第一个元素,偏移量为 0。你熟悉了就会觉得自然。
