【仓颉语言入门 · 第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~20struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展
五、工程化与标准库21~25cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试
六、并发编程26~28线程、Channel 通道与同步原语、并发实战
七、项目实战29~30命令行小工具、GeoJSON 数据处理实战
  1. 环境搭建与第一个仓颉程序
  2. 变量与常量:let / var 与基本数据类型
  3. 运算符与标准输入输出
  4. 分支结构:if 与 match 表达式
  5. 循环结构:while / for / Range
  6. 字符串详解与字符串插值
  7. 数组 Array 与区间 Range(本文)
  8. 集合框架:ArrayList、HashMap、HashSet
  9. 可空类型 ? 与 Option
  10. 错误处理:异常机制与 Result
  11. 函数定义、参数与返回值
  12. Lambda 与高阶函数
  13. 闭包、作用域与函数类型
  14. 迭代器 Iterator 与 Sequence
  15. 结构体 struct 与类 class
  16. 构造函数、属性与方法
  17. 接口 interface 与实现
  18. 枚举 enum、代数数据类型与 match 模式匹配
  19. 泛型编程
  20. 扩展、类型别名与可见性控制
  21. cjpm 包管理与多文件项目组织
  22. 文件与目录 IO
  23. JSON 处理(结合 stdx 扩展库)
  24. 网络编程入门
  25. 单元测试
  26. 并发基础:线程的创建与等待
  27. Channel 通道与同步原语
  28. 并发实战:多线程任务处理
  29. 实战一:带文件持久化的命令行小工具
  30. 实战二: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 在创建时长度就定死了。它没有 appendinsertremoveAt 这类增删元素的方法——硬写会被编译器拦下:

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 用调试器看清"共享与克隆"

这节课最抽象的共享存储,在调试器里亲眼看一次就懂了:

  1. let sorted = temps.clone() 一行打断点;
  2. Shift + F9 启动调试,断住后在 Variables 面板展开 temps,看到 7 个浮点元素;
  3. 单步(F8)越过 clone,再越过 sort(sorted):观察 sorted 已变成升序,而 temps 仍然按周一到周日的原顺序排列——clone 出来的副本与原件互不影响;
  4. 展开二维数组 cities,能看到 3 行、每行 7 个元素的层级结构,行号列号一目了然;
  5. 对比实验:把代码临时改成 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)firstSome(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 数数规则完全一致。另外切片与原数组共享存储,改切片会"透传"回原数组。


十一、课后练习

  1. 给定成绩数组 let scores = [78, 95, 62, 88, 90, 73, 81],输出:总分、平均分(保留浮点计算)、最高分、最低分(最高最低都用"打擂台"循环写,不要用 sort 偷懒),以及不及格(低于 60 分才叫不及格,本题应输出 0 人;可以自己改个 55 分进去验证计数逻辑)的人数。
  2. enumerate() 把上题成绩按"第 N 个成绩:xx 分"逐行打印,并在遍历时统计高于等于平均分的有几个人(需要先算出平均分再数第二遍)。
  3. 声明二维数组存 3 位同学 4 门课的成绩:[[80, 90, 85, 70], [60, 75, 90, 88], [100, 92, 78, 84]],输出每位同学的平均分(保留 1 位小数的计算过程即可,显示 6 位小数没关系)和每门课的全班平均分(提示:列求和用 sum += students[i][课号])。
  4. 共享与克隆实验:let a = [1,2,3,4,5];先 let b = a 后执行 b[0] = 0,打印 a、b 观察;再 let c = a.clone() 后执行 c[1] = 0,再次打印三者。用一句话总结哪次改动影响了 a、哪次没有。
  5. 打开 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
Logo

一站式 AI 云服务平台

更多推荐