【仓颉语言入门 · 第7课】
【仓颉语言入门 · 第7课】数组 Array 与区间 Range
上节课我们把字符串彻底讲透了。其实第 6 课的
sentence.split(" ")切出来的东西就是数组——只是当时没展开。程序里除了"一段文本",还经常要处理一排同类型的数据:一个班 50 个同学的成绩、一周 7 天的气温、购物车里的多件商品。总不能声明 50 个变量吧?这节课正式请出 Array(数组):把同类型数据整整齐齐装进一个容器,按下标存取、换着花样遍历、切一段出来、排序查找,再把第 5 课的 Range(区间) 用到数组切片上。本文是系列第 7 课:正式学习仓颉的 Array(数组)——创建、下标、三种遍历、切片、排序与查找、二维数组,并实测两个初学者必踩的"反直觉"(定长与共享存储),最后用一个"本周气温分析"实战收尾。
目录(系列导航)
整套路线共 7 个模块、30 课,按每周 2~3 课的节奏,大约 2~3 个月可以走完一遍:
| 模块 | 课次 | 内容 |
|---|---|---|
| 一、环境与入门 | 01~05 | 环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环 |
| 二、常用类型与数据组织 | 06~10 | 字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理 |
| 三、函数与函数式 | 11~14 | 函数、Lambda 与高阶函数、闭包、迭代器与惰性序列 |
| 四、面向对象与类型系统 | 15~20 | struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展 |
| 五、工程化与标准库 | 21~25 | cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试 |
| 六、并发编程 | 26~28 | 线程、Channel 通道与同步原语、并发实战 |
| 七、项目实战 | 29~30 | 命令行小工具、GeoJSON 数据处理实战 |
- 环境搭建与第一个仓颉程序
- 变量与常量:let / var 与基本数据类型
- 运算符与标准输入输出
- 分支结构:if 与 match 表达式
- 循环结构:while / for / Range
- 字符串详解与字符串插值
- 数组 Array 与区间 Range(本文)
- 集合框架:ArrayList、HashMap、HashSet
- 可空类型
?与 Option - 错误处理:异常机制与 Result
- 函数定义、参数与返回值
- Lambda 与高阶函数
- 闭包、作用域与函数类型
- 迭代器 Iterator 与 Sequence
- 结构体 struct 与类 class
- 构造函数、属性与方法
- 接口 interface 与实现
- 枚举 enum、代数数据类型与 match 模式匹配
- 泛型编程
- 扩展、类型别名与可见性控制
- cjpm 包管理与多文件项目组织
- 文件与目录 IO
- JSON 处理(结合 stdx 扩展库)
- 网络编程入门
- 单元测试
- 并发基础:线程的创建与等待
- Channel 通道与同步原语
- 并发实战:多线程任务处理
- 实战一:带文件持久化的命令行小工具
- 实战二:GeoJSON 数据处理程序
一、认识 Array:一排同类型数据的容器
用方括号把同类型的数据逗号隔开,就是数组字面量,类型记作 Array<T>(T 是元素类型):
package helloCangjie
main(): Int64 {
let scores = [86, 92, 57, 73, 100] // Array<Int64>
let fruits = ["苹果", "香蕉", "橙子"] // Array<String>
let flags = [true, false, true] // Array<Bool>
// 直接打印就是 [元素, 元素, ...] 的样子
println(scores)
println(fruits)
println(flags)
// size 属性:元素个数
println("成绩数量:${scores.size}")
return 0
}
运行结果:
[86, 92, 57, 73, 100]
[苹果, 香蕉, 橙子]
成绩数量:5
三点先记住:
- 元素必须同类型:
[1, "two", 3]这种混装编译不过;想装"不一样的东西",要等后面学枚举/接口; - 下标从 0 开始:第 1 个元素是
scores[0],最后一个是scores[scores.size - 1]; size是属性不是方法:写scores.size,不要画蛇添足加括号。
还记得第 6 课的 split 吗?"a,b,c".split(",") 切出来的正是一个 Array<String>,所以当时才能用 fruits[1] 取第二段——数组你早就用过了。
二、创建数组的三种方式
2.1 字面量:最常用
let a = [1, 2, 3]
字面量里所有元素类型一致,编译器自动推导出 Array<Int64>。
2.2 空数组:类型标注不能省
一个元素都没有时,编译器无从推导类型,必须写明类型:
package helloCangjie
main(): Int64 {
let empty: Array<Int64> = []
println("空数组长度:${empty.size}")
return 0
}
运行结果:
空数组长度:0
2.3 构造器:批量生成有规律的数组
想造一个"5 个元素、按下标有规律"的数组,用构造器 Array<T>(长度) { 下标 => 该位置的初始值 }——第二个参数是一个接收下标、返回元素的小函数:
package helloCangjie
main(): Int64 {
// 4 个 0:下标用不到,写成 _
let zeros = Array<Int64>(4, {_ => 0})
println(zeros)
// 5 个平方数:下标 i 的位置放 i * i
let squares = Array<Int64>(5, {i => i * i})
println(squares)
return 0
}
运行结果:
[0, 0, 0, 0]
[0, 1, 4, 9, 16]
这个 {i => i * i} 叫 Lambda(匿名函数),第 12 课会专门学,这里只要会照着写:箭头左边是下标(用不到就写 _),右边是生成规则。
三、按下标存取:0 开始的一排抽屉
数组像一排编了号的抽屉,用 数组[下标] 读、用 数组[下标] = 新值 改:
package helloCangjie
main(): Int64 {
let a = [10, 20, 30]
println("第 1 个:${a[0]}")
println("最后一个:${a[a.size - 1]}")
a[0] = 99 // 修改元素
println("改完之后:${a}")
return 0
}
运行结果:
第 1 个:10
最后一个:30
改完之后:[99, 20, 30]
3.1 let / var 到底管住了什么?
上面的数组明明是 let,为什么元素还能改?这是初学者最容易绕进去的地方,一句话讲清:
let/var管的是"这个名字能不能换一个数组",而元素始终可以通过下标修改。
let a = [...]:不能让a再指向另一个数组(a = [4,5,6]编译报错cannot assign to immutable value),但a[0] = 99改抽屉里的东西没问题;var a = [...]:既能改元素,也能整体换成另一个数组。
写代码时默认用 let,只有确实需要"整体换一批数据"时才用 var。
3.2 越界:下标必须在 0 ~ size-1
下标写错(比如 3 个元素却访问 [99])会怎样?仓颉有两道防线:
第一道:编译器直接拦截。 当下标是字面量、编译器一眼能看出越界时,根本编译不过:
error: array index is out of bounds
==> main.cj:6:13
|
6 | println(a[99])
| ^^^^^ array index 99 is past the end of array (which contains 3 elements)
负数下标同样编译期拒绝(array index can not be negative)——仓颉没有某些语言"负数倒着数"的语法。
第二道:运行时抛异常。 下标来自变量(用户输入、计算结果),编译器无法预知,一旦运行时越界,程序会抛出异常并停在出错行:
An exception has occurred:
IndexOutOfBoundsException: The length of the array is 3, but the index is 99.
at default::main()(main.cj:8)
所以处理"来自外界的下标"(输入、查找结果)前,要先判断 i >= 0 && i < arr.size。 第 10 课学错误处理后还能优雅地接住它。
四、遍历数组的三种姿势
第 5 课已经用过最简单的 for-in,这里系统整理三种写法:
package helloCangjie
main(): Int64 {
let scores = [86, 92, 57, 73, 100]
// 姿势一:for-in,只关心元素
var total = 0
for (s in scores) {
total += s
}
println("总分:${total}")
// 姿势二:enumerate(),同时拿下标和元素(编号输出神器)
for ((i, s) in scores.enumerate()) {
println("第 ${i + 1} 名同学:${s} 分")
}
// 姿势三:下标 + Range,需要按位置灵活访问时用
for (i in 0..scores.size) {
print("${scores[i]} ")
}
println()
return 0
}
运行结果:
总分:408
第 1 名同学:86 分
第 2 名同学:92 分
第 3 名同学:57 分
第 4 名同学:73 分
第 5 名同学:100 分
86 92 57 73 100
怎么选:
- 只要元素 → for-in,最简洁;
- 要输出"第几项"或同时访问两个数组的同一位置 → enumerate(),注意双括号
((i, s)),外层是循环变量、内层是"下标+元素"的组合; - 要跳着取、倒着取、相邻元素比较 → 下标 +
0..size(左闭右开正好数遍所有合法下标,不会越界)。
五、Range 进阶:数组切片
第 5 课学的区间不只能数数,还能给数组"切一刀",得到一个子数组。两种区间的含义和数数时完全一致:
package helloCangjie
main(): Int64 {
let a = [10, 20, 30, 40, 50]
println(a[1..3]) // 左闭右开:下标 1、2
println(a[1..=3]) // 左闭右闭:下标 1、2、3
println(a[0..a.size]) // 从 0 切到末尾,就是整个数组
return 0
}
运行结果:
[20, 30]
[20, 30, 40]
[10, 20, 30, 40, 50]
记忆技巧不变:..= 多一横,终点稳稳包含。像"取前 3 名 top[0..3]""跳过第 1 个 data[1..data.size]"这种需求,切片一步到位。
⚠️ 切片与原数组共享同一段底层数据(下一节实测给你看),改切片元素会影响原数组对应位置。需要一份互不相干的副本,在切片后面接
.clone():let part = a[1..3].clone()。
六、数组的两个"脾气":定长与共享
数组高效,但它有两个和直觉不太一样的设计,都是用 cjc 1.1.3 实测出来的,提前知道就不会踩坑。
6.1 脾气一:长度天生固定,不能追加
Array 在创建时长度就定死了。它没有 append、insert、removeAt 这类增删元素的方法——硬写会被编译器拦下:
error: 'append' is not a member of struct 'Array<Int64>'
能做的是:改元素、切片、整体重新赋值(var)。如果业务里元素个数事先不知道(用户不断输入、逐条读取记录),那是下一课 **ArrayList(动态数组)**的舞台,这节课先用固定长度的数据把基础打牢。
6.2 脾气二:赋值、传参、切片默认共享存储
数组内部是一块连续内存。把数组赋值给另一个名字、传进函数、或者切一片出来,默认都不会复制元素,而是共享同一块数据:
package helloCangjie
func modifyFirst(arr: Array<Int64>) {
arr[0] = -1
}
main(): Int64 {
// ① 赋值共享
let a = [1, 2, 3]
let b = a
b[0] = 99
println("a=${a},b=${b}")
// ② 传参共享:函数里改元素,外面看得见
let d = [7, 8, 9]
modifyFirst(d)
println("函数返回后 d=${d}")
// ③ 切片共享
let c = [10, 20, 30, 40]
let part = c[1..3]
part[0] = 88
println("c=${c},part=${part}")
return 0
}
运行结果:
a=[99, 2, 3],b=[99, 2, 3]
函数返回后 d=[-1, 8, 9]
c=[10, 88, 30, 40],part=[88, 30]
三处修改全部"串"到了原数组上。好处是大数组传参零成本(不复制);风险是一不小心就改了别人还在用的数据。想要一份互不影响的独立副本,调用 clone():
package helloCangjie
main(): Int64 {
let a = [1, 2, 3]
let b = a.clone() // 完整复制一份
b[0] = 99
println("a=${a},b=${b}")
return 0
}
运行结果:
a=[1, 2, 3],b=[99, 2, 3]
经验法则:只想读,随便传;要在新地方改元素又不想影响原件,先 clone()。
七、高频操作:查找、比较、反转与排序
先跑一段全家桶(每个输出都已实测):
package helloCangjie
import std.sort.*
main(): Int64 {
let a = [10, 20, 30, 40, 50]
// ① 判断与查找
println("包含 30:${a.contains(30)}")
println("包含 99:${a.contains(99)}")
println("40 的下标:${a.indexOf(40)}")
// ② 首尾元素:返回 Option,空数组时是 None
println("first=${a.first},last=${a.last}")
// ③ 按内容比较
println("[1,2]==[1,2]:${[1, 2] == [1, 2]}")
println("[1,2]==[1,3]:${[1, 2] == [1, 3]}")
// ④ 反转(原地)与排序(原地)
var nums = [3, 1, 4, 1, 5, 9, 2]
nums.reverse()
println("反转:${nums}")
sort(nums)
println("升序:${nums}")
sort(nums, descending: true)
println("降序:${nums}")
return 0
}
运行结果:
包含 30:true
包含 99:false
40 的下标:Some(3)
first=Some(10),last=Some(50)
[1,2]==[1,2]:true
[1,2]==[1,3]:false
反转:[2, 9, 5, 1, 4, 1, 3]
升序:[1, 1, 2, 3, 4, 5, 9]
降序:[9, 5, 4, 3, 2, 1, 1]
逐个说明:
contains(元素):返回 Bool,判断"在不在",最适合放进 if;indexOf(元素):返回第 9 课要讲的 Option——找到是Some(下标),找不到是None,和字符串的indexOf长得一模一样;first/last是属性不是方法:返回Some(首元素)/Some(尾元素),空数组返回None,所以别加括号;==比较的是内容:长度相同且每个位置元素相等才为 true,不用自己写循环;reverse():原地反转,无返回值,调用后数组本身倒过来;- 排序用全局函数
sort(数组):在std.sort包里,文件开头必须import std.sort.*;降序写命名参数sort(nums, descending: true)。它是原地排序,想保留原顺序就先clone()再排。
⚠️ 你可能在旧教程里看到
nums.sort()这种"点方法"写法——在 SDK 1.1.3 上它已经被标记为弃用(编译能过但有 deprecated 警告),官方推荐全局函数sort(nums)。课程统一用新写法,警告也能少一条。
速查表:
| 需求 | 写法 | 备注 |
|---|---|---|
| 元素个数 | a.size | 属性 |
| 读 / 改元素 | a[0] / a[0] = 5 | 下标 0 开始 |
| 遍历元素 | for (x in a) | — |
| 带下标遍历 | for ((i, x) in a.enumerate()) | 双括号 |
| 切一段 | a[1..3] / a[1..=3] | 共享存储 |
| 在不在 | a.contains(x) | 返回 Bool |
| 找下标 | a.indexOf(x) | Some(i) / None |
| 首 / 尾 | a.first / a.last | 属性,Option |
| 内容相等 | a == b | 逐元素比较 |
| 反转 | a.reverse() | 原地修改 |
| 升 / 降序 | sort(a) / sort(a, descending: true) | import std.sort.*,原地 |
| 独立副本 | a.clone() | 与原件互不影响 |
| 追加 / 插入 / 删除 | ❌ 无 | 用第 8 课 ArrayList |
八、二维数组:数组的数组
数组里装数组,就是二维数组——字面量写成"一排行数组",访问用两对方括号 grid[行][列]:
package helloCangjie
main(): Int64 {
// 3 行 3 列:九宫格
let grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
println("第 2 行第 3 列:${grid[1][2]}")
// 外层遍历每一行,内层遍历行里的每个数
for (row in grid) {
var line = ""
for (x in row) {
line += "${x} "
}
println(line)
}
// 每一行的长度可以不同(不规则数组)
let triangle = [[1], [2, 3], [4, 5, 6]]
println("三角形有 ${triangle.size} 行,第 3 行 ${triangle[2].size} 个元素")
return 0
}
运行结果:
第 2 行第 3 列:6
1 2 3
4 5 6
7 8 9
三角形有 3 行,第 3 行 3 个元素
grid 的类型是 Array<Array<Int64>>;grid.size 是行数,grid[i].size 是第 i 行的列数。表格、矩阵、多科目成绩单这类"行列"数据,自然就落到二维数组上。
九、CIDE 实操:本周气温分析(综合实战)
综合本课知识:把一周 7 天的气温装进数组,完成逐日播报、平均值、找最高温及其出现日、排序、工作日/周末切片统计,最后用二维数组对比三个城市。
9.1 编写程序
继续使用 helloCangjie 项目,把 src/main.cj 替换为:
package helloCangjie
import std.sort.*
main(): Int64 {
// 本周每天白天气温(周一~周日,固定 7 个)
let temps = [23.5, 25.0, 22.3, 26.8, 28.1, 24.6, 21.9]
let weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
println("------ 本周气温分析 ------")
// ① enumerate:下标 + 元素一起拿,下标复用给星期表
for ((i, t) in temps.enumerate()) {
println("${weekdays[i]}:${t}℃")
}
// ② 平均气温:总和 / 天数(整数转浮点再除,第 2 课的 Float64() 转换)
var sum = 0.0
for (t in temps) {
sum += t
}
let avg = sum / Float64(temps.size)
println("平均气温:${avg}℃")
// ③ 自己用循环找最高温及其下标(复习第 4、5 课)
var maxTemp = temps[0]
var maxDay = 0
for (i in 1..temps.size) {
if (temps[i] > maxTemp) {
maxTemp = temps[i]
maxDay = i
}
}
println("最高气温:${maxTemp}℃,出现在${weekdays[maxDay]}")
// ④ 排序:先 clone 一份,排序不影响原始记录
let sorted = temps.clone()
sort(sorted)
println("从低到高:${sorted}")
// ⑤ 切片:工作日(周一~周五)与周末(周六、周日)
let workday = temps[0..5]
let weekend = temps[5..7]
var workSum = 0.0
for (t in workday) {
workSum += t
}
println("工作日平均:${workSum / Float64(workday.size)}℃")
print("周末气温:")
for ((i, t) in weekend.enumerate()) {
print("${weekdays[5 + i]} ${t}℃ ")
}
println()
// ⑥ 二维数组:3 个城市 × 7 天气温
let cities = [
[23.5, 25.0, 22.3, 26.8, 28.1, 24.6, 21.9],
[18.2, 19.0, 17.5, 20.1, 21.3, 19.8, 18.9],
[30.1, 31.2, 29.8, 32.5, 33.0, 30.7, 29.5]
]
let cityNames = ["本市", "哈尔滨", "三亚"]
println("------ 三城本周平均气温 ------")
for ((ci, week) in cities.enumerate()) {
var citySum = 0.0
for (t in week) {
citySum += t
}
println("${cityNames[ci]}:${citySum / Float64(week.size)}℃")
}
return 0
}
设计要点:
- 两张表用同一套下标对齐:
temps[i]是气温、weekdays[i]是星期,这是数组最常见的搭配套路; - 求平均先转浮点:
sum / temps.size若两边都是整数会被截成整数,所以用第 2 课的Float64(...)把天数转成小数再除; - 打擂台找最值:
maxTemp先放第 1 个元素,从下标 1 开始逐一比较,赢了就更新"值 + 位置"; - 排序前
clone():原始的星期顺序还要用来做切片统计,不能被排序打乱,这正是 6.2 节"要改先克隆"的实际应用; - 切片统计工作日/周末:
0..5是 5 个工作日、5..7是周末 2 天,区间开闭一念之间。
9.2 运行与验证
按 Ctrl + R 运行:
------ 本周气温分析 ------
周一:23.500000℃
周二:25.000000℃
周三:22.300000℃
周四:26.800000℃
周五:28.100000℃
周六:24.600000℃
周日:21.900000℃
平均气温:24.600000℃
最高气温:28.100000℃,出现在周五
从低到高:[21.900000, 22.300000, 23.500000, 24.600000, 25.000000, 26.800000, 28.100000]
工作日平均:25.140000℃
周末气温:周六 24.600000℃ 周日 21.900000℃
------ 三城本周平均气温 ------
本市:24.600000℃
哈尔滨:19.257143℃
三亚:30.971429℃
(Float64 插值默认显示 6 位小数,所以是 23.500000。想控制小数位数属于格式化输出,后续课程再讲,现阶段不影响计算。)
可以自己动手改:
- 把三亚的数据整体改高 2 度再运行,观察城市平均值变化;
- 仿照 ③ 再找一个"最低温及其出现日"(提示:初值同样取
temps[0],比较号反过来); - 把周末切片改成
temps[6..7],看看只剩周日,体会开闭区间的差别。
📸 配图建议:Result 面板完整显示气温分析报告,能看到最高温定位到周五和三城平均值。
9.3 用调试器看清"共享与克隆"
这节课最抽象的共享存储,在调试器里亲眼看一次就懂了:
- 在
let sorted = temps.clone()一行打断点; - Shift + F9 启动调试,断住后在 Variables 面板展开
temps,看到 7 个浮点元素; - 单步(F8)越过 clone,再越过
sort(sorted):观察sorted已变成升序,而temps仍然按周一到周日的原顺序排列——clone 出来的副本与原件互不影响; - 展开二维数组
cities,能看到 3 行、每行 7 个元素的层级结构,行号列号一目了然; - 对比实验:把代码临时改成
let sorted = temps(去掉 clone)重新调试,排序后会发现temps也被打乱——这就是 6.2 节的共享存储。记得改回来。
📸 配图建议:断点命中时 Variables 面板中 sorted 升序、temps 原顺序同时可见的对比截图。
调试结束点停止按钮。
十、常见问题 FAQ
Q1:为什么写 a.append(4) 编译报错 “not a member of struct ‘Array’”?
Array 是定长数组,创建后不能追加、插入、删除元素,标准库压根没提供这些方法。元素个数动态变化的场景,请用第 8 课的 ArrayList。
Q2:let 声明的数组为什么还能 a[0] = 99 改元素?
let 只保证"这个名字不会指向另一个数组",数组元素本身始终可改。只有 a = 另一个数组 这种整体换绑才要求 var(否则报 cannot assign to immutable value)。
Q3:下标越界会怎样?
两道防线:下标是字面量时,编译器直接报 array index is out of bounds 并指出数组长度;下标是运行时值(输入、计算结果)时,抛出 IndexOutOfBoundsException: The length of the array is N, but the index is X.。访问外部输入的下标前,先判断范围 0 <= i && i < a.size。
Q4:把数组赋值给另一个变量,改新数组为什么原数组也变了?
数组赋值、传参、切片默认共享底层存储,不复制元素(实测见 6.2 节)。这样大数组传参零开销,但需要独立副本时要显式调用 a.clone(),切片同理:a[1..3].clone()。
Q5:sort(nums) 和 nums.sort() 到底写哪个?
SDK 1.1.3 上推荐全局函数 sort(nums)(升序)、sort(nums, descending: true)(降序),需要 import std.sort.*;点方法形式 nums.sort() 已被标记弃用,会产生 deprecated 警告。两者都是原地排序。
Q6:indexOf 返回的 Some(3)、first 的 Some(10) 是什么?
它们是第 9 课要学的 Option 可选值:找到了包一层 Some(值),没找到(或空数组取首尾)就是 None。入门阶段只要记住"None 代表没有",判断在不在优先用返回 Bool 的 contains。
Q7:数组能用 + 拼在一起吗?负下标能倒着取吗?
都不能。数组没有定义 + 运算符([1,2] + [3] 编译报 invalid binary operator '+');负数下标编译期直接禁止。拼接两个数组的需求,等第 8 课 ArrayList 的批量添加;倒着访问用第 5 课的负步长循环 for (i in a.size - 1..=0 : -1)(注意闭区间 ..=,写 ..0 会把下标 0 漏掉)。
Q8:切片 a[0..3] 和 a[0..=3] 差在哪?
.. 左闭右开,取到下标 2 共 3 个元素;..= 左闭右闭,取到下标 3 共 4 个元素。和 Range 数数规则完全一致。另外切片与原数组共享存储,改切片会"透传"回原数组。
十一、课后练习
- 给定成绩数组
let scores = [78, 95, 62, 88, 90, 73, 81],输出:总分、平均分(保留浮点计算)、最高分、最低分(最高最低都用"打擂台"循环写,不要用sort偷懒),以及不及格(低于 60 分才叫不及格,本题应输出 0 人;可以自己改个 55 分进去验证计数逻辑)的人数。 - 用
enumerate()把上题成绩按"第 N 个成绩:xx 分"逐行打印,并在遍历时统计高于等于平均分的有几个人(需要先算出平均分再数第二遍)。 - 声明二维数组存 3 位同学 4 门课的成绩:
[[80, 90, 85, 70], [60, 75, 90, 88], [100, 92, 78, 84]],输出每位同学的平均分(保留 1 位小数的计算过程即可,显示 6 位小数没关系)和每门课的全班平均分(提示:列求和用sum += students[i][课号])。 - 共享与克隆实验:
let a = [1,2,3,4,5];先let b = a后执行b[0] = 0,打印 a、b 观察;再let c = a.clone()后执行c[1] = 0,再次打印三者。用一句话总结哪次改动影响了a、哪次没有。 - 打开 9.1 的程序 Shift + F9 调试,在 clone 一行打断点,单步观察
sort(sorted)后sorted升序而temps不变;然后把 clone 去掉再调试一次,确认共享存储会让temps一起被排序,最后改回正确写法。
下节预告
这节课的数组什么都好,就是长度不能变——想录入数量未知的一批数据就卡壳了。第 8 课学习集合框架三剑客:ArrayList(动态数组,随便增删)、HashMap(键值对字典,按名字查数据)、HashSet(不重复元素集合)。有了它们,“不断添加的购物车”“姓名→电话的通讯录”"自动去重的名单"都能手到擒来。我们下节课见!
系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入,以你本地版本为准,欢迎评论区交流。
� 遇到问题?扫码联系作者
跟着课程练习时,如果在 SDK 安装、环境变量配置、编译报错或调试上卡住,欢迎扫码加作者企业微信直接咨询(请备注"仓颉课程"):

离线环境下图片可能加载不出来,也可以在 CIDE 菜单 Help ▸ 联系作者 / Contact 中查看同一张二维码(应用内置兜底图,无需联网)。
�📥 工具下载
本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):
- GitCode 仓库 / 安装包下载:https://gitcode.com/wp_upala/cide
- 打开页面后进入 发行版(Releases),下载
CIDE-<版本>-x64-Setup.exe安装即可,也有免安装版CIDE-Beta-<版本>-x64-Portable.zip下载解压双击cide.exe即可使用 - 仓颉 SDK 请前往仓颉编程语言官网下载:https://cangjie-lang.cn
更多推荐




所有评论(0)