云计算百科
云计算领域专业知识百科平台

【仓颉语言入门 · 第14课】

【仓颉语言入门 · 第14课】迭代器 Iterator 与惰性序列

第 13 课用闭包做出了"能记住状态的函数",这节课解决一个更实际的问题:想逐个产出一系列值——大文件的逐行读取、传感器源源不断的读数、甚至斐波那契那样无穷无尽的数列——难道都要先一口气算完塞进数组?当然不是。仓颉的答案是迭代器(Iterator):用一个 next() 方法按需取值,用多少算多少。

本文所有代码与报错文案均在仓颉 SDK 1.1.3 下逐行实测编译运行。途中会澄清四个高频误区(每一个都是编译器亲自告诉我的):① 仓颉 1.1.3 没有 Python/JS 那种 yield / sequence { } 生成器语法糖,惰性序列靠手写迭代器类;② 数组上直接调 map / filter 是立即求值返回新数组,先 iterator() 再调才是惰性链;③ Iterator 和 Iterable 是两个并列接口,不是继承关系;④ 迭代器上没有 toArray()、drop()、chain()、sum(),正确的名字分别是"手动收集"、skip()、concat()、fold()。


目录(系列导航)

整套路线共 7 个模块、30 课:

| 模块 | 课次 | 内容 | | — | — | — — | | 一、环境与入门 | 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 数据处理程序

  • 一、先想清楚:为什么要"懒"

    前 8 课处理数据,套路都是"全部装进容器,再遍历处理":

    let nums = [1, 2, 3, 4, 5]
    for (n in nums) {
    println(n)
    }

    这在数据量小、数量有限时毫无问题。但设想三种场景:

  • 数据量巨大:一个 10 GB 的日志文件,你只想找出前 10 条错误。一次性读进内存?机器直接卡死。
  • 数据无限:斐波那契数列没有尽头,自然数 1、2、3……永远数不完,根本没法"先全部装进数组"。
  • 数据还没产生:传感器每秒上报一次读数,处理程序必须"来一个处理一个"。
  • 这三种场景的共同点是:值不应该提前算好,而应该在被需要的那一刻才算出来。这就是"惰性求值(lazy evaluation)“。迭代器就是承载惰性求值的对象——它像一个"游标”,你每叫一次 next(),它才往前走一步、吐出一个值。


    二、Iterator 接口:一个 next() 方法的协议

    仓颉标准库中,迭代器的定义极其简单(std.core 包,无需 import):

    public interface Iterator<T> {
    func next(): Option<T>
    }

    就一个方法:

    • 返回 Some(值):游标当前位置有值,同时游标前进一步;
    • 返回 None:序列到头了,以后再调也永远是 None。

    自己写一个迭代器,只要实现这个接口。注意仓颉的语法:接口实现用 <:,接口方法在实现类里要加 public:

    class Counter <: Iterator<Int64> {
    var current: Int64 = 1
    let limit: Int64

    init(limit: Int64) {
    this.limit = limit
    }

    public func next(): Option<Int64> {
    if (current <= limit) {
    let v = current
    current++
    return Some(v)
    }
    return None
    }
    }

    🚫 仓颉的类没有主构造函数简写。写 class Counter(var current: Int64, let limit: Int64)(Kotlin 风格)会直接报 expected '{', found '('。必须先在类体里声明字段,再写 init 构造函数。第 15 课会专门讲,这里先照抄格式。


    三、消费迭代器的三种姿势

    3.1 手动调 next():while + match

    迭代器最原始的消费方式。仓颉 1.1.3 没有 while let Some(v) = … 这类语法糖,标准写法是 while (true) 配合第 4、9 课的 match:

    main(): Int64 {
    let c = Counter(3)
    while (true) {
    match (c.next()) {
    case Some(v) => print("${v} ")
    case None => break
    }
    }
    println()
    return 0
    }

    输出:

    1 2 3

    None 一出现就 break,游标到此耗尽。

    3.2 for-in:最常用

    实现了 Iterator 的对象可以直接放进 for-in,编译器帮你重复调 next() 并在 None 时停止:

    for (v in Counter(5)) {
    print("${v} ")
    }

    1 2 3 4 5

    3.3 终端方法:函数式一把梭

    迭代器上挂了十几个现成方法(第六节细讲),forEach 是其中最直接的——对每个值执行一个 Lambda:

    Counter(3).forEach { v => print("${v} ") }


    四、Iterable 接口:让自定义容器支持 for-in

    Counter 那种"自己就是游标"的对象适合一次性遍历。但更多时候我们有的是容器(书架、成绩表、传感器一周的数据)——容器希望能被反复遍历,每次遍历都从头开始。

    这时用另一个接口 Iterable<T>,它同样只有一个方法:

    public interface Iterable<T> {
    func iterator(): Iterator<T>
    }

    注意它的方法签名:返回一个迭代器。这是一个"工厂方法"——每调用一次,就生产出一个游标在起点的全新迭代器。

    完整套路是"容器 + 专属游标类"两件套:

    // 游标:持有容器引用和当前下标
    class RangeIter <: Iterator<Int64> {
    var current: Int64
    let end: Int64

    init(start: Int64, end: Int64) {
    this.current = start
    this.end = end
    }

    public func next(): Option<Int64> {
    if (current <= end) {
    let v = current
    current++
    return Some(v)
    }
    return None
    }
    }

    // 容器:每次被遍历都 new 一个新游标
    class IntRange <: Iterable<Int64> {
    let start: Int64
    let end: Int64

    init(start: Int64, end: Int64) {
    this.start = start
    this.end = end
    }

    public func iterator(): Iterator<Int64> {
    return RangeIter(start, end)
    }
    }

    main(): Int64 {
    for (v in IntRange(1, 5)) {
    print("${v} ")
    }
    println()
    return 0
    }

    1 2 3 4 5

    标准库的 Array、ArrayList、Range、HashMap(遍历键值对)走的都是这套协议。第 6 课学的字符串字节遍历和 runes() 字符遍历,本质也是迭代器。

    🚫 如果一个类光写了 iterator() 方法却不声明 <: Iterable<T>,for-in 会报错:the type … does not implement Iterator。仓颉不看"方法长得像不像",只认接口声明。


    五、Iterator 与 Iterable:一次性游标 vs 可重复遍历

    这是本节课最容易混淆、也最关键的一点。两个接口是并列关系,不是继承(我试过把 Iterable 对象赋给 Iterator 变量,编译器直接 mismatched types)。分工如下:

    Iterator<T>Iterable<T>
    角色 游标(有状态、会前进) 容器(数据本身)
    要实现的方法 next(): Option<T> iterator(): Iterator<T>
    能否重复遍历 不能,耗尽即空 能,每次产生新游标
    典型代表 arr.iterator() 的返回值 数组、ArrayList、Range

    实测"迭代器一次性":

    main(): Int64 {
    let arr = [10, 20, 30]

    let it = arr.iterator()
    let n1 = it.fold(0) { acc, _ => acc + 1 }
    let n2 = it.fold(0) { acc, _ => acc + 1 }
    println("第一次:${n1},第二次:${n2}")

    // 直接遍历数组(Iterable),多少次都行
    let n3 = arr.iterator().fold(0) { acc, _ => acc + 1 }
    println("重新拿一个游标:${n3}")
    return 0
    }

    第一次:3,第二次:0
    重新拿一个游标:3

    同一个 it 遍历一遍后游标已到尽头,第二遍什么都没有(0 个);而向数组(Iterable)再要一个 iterator(),又是一条好汉。

    📌 经验法则:自己写数据容器,实现 Iterable;只想生成一串值(尤其是算出来的、无限的),实现 Iterator。


    六、惰性流水线:map / filter / take / skip

    迭代器之所以好用,是因为 Iterator 上挂了一串链式方法,写法和第 12 课的数组 Lambda 几乎一样,但语义完全不同:

    6.1 先看数组上的链:立即求值

    let nums = [1, 2, 3, 4, 5, 6, 7, 8]
    let r = nums.map { x => x * 2 }.filter { x => x > 10 }.take(2)
    println("${r}")

    [12, 14]

    这条链每一步都立刻执行并产出一个新数组:map 先造一个 8 元素数组,filter 再造一个,take 再造一个。数据量大时中间数组会浪费大量内存。

    6.2 再看迭代器上的链:惰性求值

    只要在开头加一个 .iterator(),整条链的性质就变了:

    let r2 = nums.iterator()
    .map { x => x * 2 }
    .filter { x => x > 10 }
    .take(2)
    .forEach { v => print("${v} ") }

    • map / filter / take / skip 称为中间操作:它们不立刻计算,只是返回一个"记住了规则"的新迭代器,像一串扣在一起的齿轮;
    • forEach / fold / any 等称为终端操作:只有调用它们,齿轮才真正转动,开始一个一个地 next() 拉取;
    • take(2) 拉够 2 个就停——上游不会多算一个值。

    空口无凭,看实测。在 map 里塞一行打印当"探针",只取 3 个值:

    main(): Int64 {
    let data = [10, 20, 30, 40, 50]
    let lazyMapped = data.iterator().map { x =>
    println(" …正在计算 ${x}")
    x * 2
    }
    println("只取前 3 个:")
    lazyMapped.take(3).forEach { v => println("得到 ${v}") }
    return 0
    }

    只取前 3 个:
    …正在计算 10
    得到 20
    …正在计算 20
    得到 40
    …正在计算 30
    得到 60

    5 个数据,只算了前 3 个——40 和 50 的 map 规则一次都没执行。这就是惰性:终端操作要多少,中间操作才算多少。如果这是 10 GB 日志,省下的就是巨量内存和时间。

    6.3 常用中间操作

    方法作用
    map { x => … } 逐个变换值的类型/内容
    filter { x => 条件 } 只保留满足条件的值
    take(n) 只取前 n 个,然后结束
    skip(n) 跳过前 n 个,从第 n+1 个开始
    flatMap { x => 迭代器 } 一对多展开,再拍平成一条流
    zip(另一个迭代器) 两条流配对,产出 (a, b) 元组,短的到头就结束
    concat(另一个迭代器) 先流完自己,再接上流另一个
    enumerate() 给每个值贴上从 0 开始的编号,产出 (下标, 值) 元组

    flatMap 示例(每个值展开成两个):

    [1, 2, 3].iterator()
    .flatMap { x => [x, x * 10].iterator() }
    .forEach { v => print("${v} ") }

    1 10 2 20 3 30

    ⚠️ 元组不能直接字符串插值。zip / enumerate 产出的元组写 "${p}" 会报 Tuple … should implement interface 'ToString',要用下标取分量:p[0]、p[1]。


    七、无限序列:有了 take,无穷也敢遍历

    惰性迭代器最震撼的用法:序列可以是无限的。因为反正你不主动拉,它一个值都不会算;再配合 take 设好"安全阀",无限流就变成了"想要多少取多少"。

    实测一个无限的斐波那契数列生成器:

    class Fib <: Iterator<Int64> {
    var a: Int64 = 0
    var b: Int64 = 1

    public func next(): Option<Int64> {
    let v = a
    a = b
    b = v + b
    return Some(v) // 永远返回 Some,序列没有尽头
    }
    }

    main(): Int64 {
    print("前 10 个斐波那契数:")
    Fib().take(10).forEach { x => print("${x} ") }
    println()
    return 0
    }

    前 10 个斐波那契数:0 1 1 2 3 5 8 13 21 34

    Fib() 本身是无限的,直接 for (v in Fib()) 会死循环;但 take(10) 拉满 10 个就发出结束信号,程序安然无恙。自然数、素数流、随机数流都是同一个套路。

    🚫 仓颉 1.1.3 没有生成器语法糖。 网上看到仓颉(或其他语言)教程里写 sequence<Int64> { yield(1); yield(2) }、或在普通函数里写 yield(x) 当生成器——本版本编译器会报 undeclared identifier 'sequence' / undeclared identifier 'yield'。仓颉的做法就是老老实实写一个实现 Iterator 的类,把"状态"放进字段(比如 Fib 的 a、b),在 next() 里推进状态。啰嗦一点,但逻辑完全透明。


    八、终端操作与 API 速查

    终端操作一调用,整条惰性链开始真正运算,并给出一个最终结果(而不是另一个迭代器)。均在 1.1.3 实测:

    方法返回说明
    forEach { x => … } Unit 逐值执行副作用(打印、写入文件等)
    fold(初值) { acc, x => … } 累加结果 最通用的聚合,求和/计数/拼接都靠它
    reduce { a, b => … } Option<R> 无初值聚合,空流返回 None,更安全
    count() Int64 统计剩余元素个数(不接受条件,要条件计数先 filter)
    any { x => 条件 } Bool 是否存在满足条件的值(找到即停)
    all { x => 条件 } Bool 是否全部满足(遇到反例即停)
    first() Option<T> 第一个值,空流为 None
    min() / max() Option<T> 最小/最大值,空流为 None

    几个实测要点:

    ① 条件计数用 filter(…).count(),因为 count() 不带参数:

    let n = [1, 2, 3, 4].iterator().filter { x => x > 2 }.count()
    println("${n}") // 2

    ② reduce / min / max / first 返回 Option,空流不抛异常,用 match 或 ?? 兜底:

    match (ArrayList<Int64>().iterator().min()) {
    case Some(v) => println("最小值 ${v}")
    case None => println("空的,没有最小值")
    }

    空的,没有最小值

    ③ 迭代器上没有 toArray() / toArrayList(),想把惰性链的结果收成集合,手动接进 ArrayList(forEach 里调用的是容器方法,不捕获外层 var,合法):

    let list = ArrayList<Int64>()
    Fib().take(5).forEach { x => list.add(x) }
    let arr = list.toArray()
    println("${arr}") // [0, 1, 1, 2, 3]

    ④ 一张"别写错名字"对照表(左列在 1.1.3 迭代器上不存在,右列才是对的):

    别写正确写法
    iter.drop(n) iter.skip(n)
    iter1.chain(iter2) iter1.concat(iter2)
    iter.sum() iter.fold(0) { a, b => a + b }
    iter.toArray() forEach 手动收集进 ArrayList,再 toArray()
    iter.count { x => … } iter.filter { x => … }.count()

    九、CIDE 实操:气温监测数据流

    9.1 编写程序

    综合运用本节课全部要素:自定义无限迭代器 + 自定义 Iterable 容器、惰性 filter/map/take、fold/reduce 聚合、any/all 判定、min/max 安全取值、enumerate 编号、concat 拼接。

    场景:气象站一周气温读数,用 -99 标记传感器缺失的数据。需要过滤缺失值并完成统计;另用一个无限自然数流演示"无限数据 + take 安全阀"。

    package WeatherIter

    import std.collection.*

    // ① 无限迭代器:从 1 开始的自然数流
    class Naturals <: Iterator<Int64> {
    var n: Int64 = 1
    public func next(): Option<Int64> {
    let v = n
    n++
    return Some(v)
    }
    }

    // ② 容器:一周气温读数
    class WeeklyTemps <: Iterable<Int64> {
    let data: Array<Int64>
    init(data: Array<Int64>) { this.data = data }
    public func iterator(): Iterator<Int64> {
    return TempIter(data)
    }
    }

    // 容器专属游标
    class TempIter <: Iterator<Int64> {
    let data: Array<Int64>
    var i: Int64 = 0
    init(data: Array<Int64>) { this.data = data }
    public func next(): Option<Int64> {
    if (i < data.size) {
    let v = data[i]
    i++
    return Some(v)
    }
    return None
    }
    }

    main(): Int64 {
    // —— 无限流也敢用,take 兜底 ——
    print("前 10 个平方数:")
    Naturals().map { x => x * x }.take(10).forEach { v => print("${v} ") }
    println()

    // —— 真实容器的惰性流水线 ——
    let week = WeeklyTemps([21, -99, 23, -99, 25, 22, 30])

    // 个数与总和(count 不接受条件,先 filter)
    let count = week.iterator().filter { t => t != -99 }.count()
    let total = week.iterator().filter { t => t != -99 }.fold(0) { a, b => a + b }
    println("有效天数:${count},平均气温:${total / count}℃")

    // reduce 无初值求和:空流天然得到 None
    let valid = week.iterator().filter { t => t != -99 }
    match (valid.reduce { a, b => a + b }) {
    case Some(s) => println("reduce 总和:${s}")
    case None => println("没有有效读数")
    }

    // 判定类终端操作
    println("是否全部高于 20℃:${week.iterator().filter { t => t != -99 }.all { t => t > 20 }}")
    println("是否出现过 30℃:${week.iterator().any { t => t == 30 }}")

    // 最高温:max() 返回 Option,用 ?? 兜底
    println("最高温:${week.iterator().filter { t => t != -99 }.max() ?? -1}℃")

    // 带编号展示前 3 个有效读数(元组用 p[0]/p[1] 取值)
    print("前 3 个有效读数:")
    week.iterator().filter { t => t != -99 }.take(3).enumerate().forEach { p =>
    print("第${p[0] + 1}天=${p[1]}℃ ")
    }
    println()

    // concat 拼接两周数据
    let week2 = WeeklyTemps([19, 20])
    print("两周连读前 4 个:")
    week.iterator().concat(week2.iterator()).take(4).forEach { v => print("${v} ") }
    println()

    return 0
    }

    9.2 运行与验证

    前 10 个平方数:1 4 9 16 25 36 49 64 81 100
    有效天数:5,平均气温:24℃
    reduce 总和:121
    是否全部高于 20℃:true
    是否出现过 30℃:true
    最高温:30℃
    前 3 个有效读数:第1天=21℃ 第2天=23℃ 第3天=25℃
    两周连读前 4 个:21 -99 23 -99

    关键点解读:

    • Naturals 永远返回 Some,是真正的无限流;take(10) 像闸门,拉够 10 个就关,map 不会被执行第 11 次。
    • WeeklyTemps 是容器(Iterable),持有原始数组;每次 iterator() 都 new 一个 TempIter,所以下面能对同一周数据反复做 5 次统计,互不干扰。
    • -99 的缺失值统一在 filter 阶段剔除,后续所有统计拿到的都是干净数据——这正是流水线的价值:过滤逻辑只写一次。
    • max() 返回 Option<Int64>,?? -1 在全空时兜底,绝不会抛异常。
    • enumerate() 产出元组,必须 p[0]、p[1] 取分量。

    可以自己动手改:

    • 把 take(10) 改成 take(20),确认平方数继续正确产出、且程序正常退出;
    • 把周数据改成全 -99,观察 reduce 走 None 分支、max() 打印兜底值 -1;
    • 用 fold 计算有效气温的平均值(提示:先求和、再除以 count,注意整数除法)。

    9.3 用调试器观察惰性求值

  • 在 Naturals 的 next() 里 let v = n 一行打断点,Shift + F9 启动调试;
  • 单步观察:take(10) 驱动下,next() 恰好被调用 10 次,第 11 次永远不会发生——亲眼确认"终端操作要多少才算多少";
  • 在 TempIter.next() 打断点,观察 filter 剔除 -99 时游标如何"多走一步"才吐出一个有效值;
  • 展开 Variables 面板里的 this,对比每次 iterator() 产生的 TempIter 的 i 都是独立从 0 开始的。
  • 调试结束点停止按钮。


    十、常见问题 FAQ

    Q1:迭代器和数组遍历,日常写代码该用哪个? 数据已经在数组里、且要多次使用,直接遍历数组(最简单)。迭代器的价值在三种情况:数据量很大/无限、数据是边产生边处理的、想把多个变换串成一条不产生中间集合的流水线。

    Q2:Iterator 和 Iterable 到底怎么选? 你在描述"一个数据集合"(能被反复遍历)→ 实现 Iterable,配一个游标类;你在描述"一串按规则生成的值"(尤其是算出来的、无限的、一次性的)→ 直接实现 Iterator。

    Q3:为什么我的类写了 iterator() 方法,for-in 还是报错? 光有方法不够,必须在类声明里写 <: Iterable<T>。仓颉的 for-in 只认接口,不认"结构上恰好长这样"的鸭子类型。报错文案是 does not implement Iterator。

    Q4:仓颉有 yield / 生成器函数吗? 1.1.3 没有。sequence { } 块和函数体里的 yield(x) 都会报"未声明标识符"。生成序列的唯一方式是手写实现 Iterator 的类,用字段保存状态、在 next() 里推进。

    Q5:为什么 arr.map { … } 和 arr.iterator().map { … } 写法差不多,要强调区别? 前者每一步立即产出新数组(中间结果全部驻留内存);后者只在终端操作时按需计算,不产生中间数组。数据量小看不出差别,处理大文件/无限流时是天壤之别。

    Q6:迭代器遍历一遍后还能再遍历吗? 同一个迭代器对象不能,游标耗尽后再调 next() 永远是 None。但 Iterable 容器每次 iterator() 都会给你一个新游标,可以反复遍历。需要"再来一遍"时,重新调用 iterator()。

    Q7:zip / enumerate 的结果为什么不能直接打印? 它们产出的是元组,标准库的元组没有实现 ToString,字符串插值会报错。用下标取分量:p[0]、p[1],或者解构后再打印。

    Q8:怎么把迭代器结果存回数组? 迭代器上没有 toArray()。先建一个 ArrayList<T>(),用 forEach { x => list.add(x) } 收集,再调 list.toArray()。

    Q9:for-in 无限迭代器会怎样? 会死循环(无限迭代器的 next() 永远返回 Some)。无限流必须用 take(n)、any { 条件 } 这类自带终止能力的操作消费,确保它迟早会停。

    Q10:reduce 和 fold 有什么区别?空流会崩吗? fold(初值) { … } 必须给初值,空流直接返回初值;reduce { … } 不给初值,返回 Option,空流得到 None。二者都不会因为空序列崩溃——这比很多语言抛异常的设计更安全。


    十一、课后练习

  • 写一个 Countdown <: Iterator<Int64>,从构造参数 n 开始倒着数数到 1,之后返回 None。用 for-in 打印 Countdown(5),应输出 5 4 3 2 1。
  • 给第 1 题的倒计时接上链式操作:只保留偶数、每个数平方、用 forEach 打印。预期输出 16 4(即 4²、2²)。
  • 写一个无限迭代器 MultiplesOf(k: Int64),依次产出 k 的所有倍数(k、2k、3k……)。用 take(5) 安全地取出 3 的前 5 个倍数并打印。
  • 用 fold 实现一个函数,接收一个 Iterator<Int64>,返回其中正数的平均值(提示:可以 fold 出一个"个数+总和"的二元组,元组字段用 [0]、[1] 访问;注意空流除零,用 match 处理)。
  • 综合:定义一个 Sentence 容器实现 Iterable<String>(内部用一个 Array<String> 存单词),写专属游标类。然后用迭代器链完成:过滤掉长度小于 3 的单词、enumerate 编号、只取前 3 个、按 #编号:单词 的格式打印。

  • 下节预告

    函数式模块(11~14 课)到此收官:从命名函数、匿名 Lambda、闭包捕获,到今天按需取值的迭代器,你已经能用"值 + 函数"表达相当复杂的逻辑了。但写大型程序还需要另一套组织代码的武器——把数据和操作数据的方法绑在一起。第 15 课 结构体 struct 与类 class 正式进入面向对象世界:值类型与引用类型的根本差异、什么时候该用谁、init 构造函数的完整规则。我们下节课见!


    系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入(例如新版本补充了 yield 生成器语法、为迭代器增加了 toArray() 等),以你本地版本为准,欢迎评论区交流。


    💬 遇到问题?扫码联系作者

    跟着课程练习时,如果在 SDK 安装、环境变量配置、编译报错或调试上卡住,欢迎扫码加作者企业微信直接咨询(请备注"仓颉课程"): 在这里插入图片描述

    离线环境下图片可能加载不出来,也可以在 CIDE 菜单 Help ▸ 联系作者 / Contact 中查看同一张二维码(应用内置兜底图,无需联网)。


    📥 工具下载

    本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):

    • GitCode 仓库 / 安装包下载:https://gitcode.com/wp_upala/cide
    • 打开页面后进入 发行版(Releases),两种包任选其一:
      • 安装版:下载 CIDE-<版本>-x64-Setup.exe,双击安装,适合日常长期使用;
      • 免安装版(Portable):下载 CIDE-<版本>-x64-Portable.zip,解压到任意目录即用,不写注册表、不留安装痕迹,拷到 U 盘也能在别的电脑直接运行(包内附《使用说明.txt》)。适合先试用、或在受限电脑上学习本系列课程。
    • 仓颉 SDK 请前往仓颉编程语言官网下载:https://cangjie-lang.cn
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【仓颉语言入门 · 第14课】
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!