【仓颉语言入门 · 第18课】枚举 enum、代数数据类型与 match 模式匹配

第 17 课结尾留了一个问题:一个值"要么是成功结果,要么是错误原因"“要么是圆,要么是矩形,要么是三角形”,这种封闭的多选一类型,用 class 继承写起来又重又不安全。本课的主角 enum(枚举) 正是答案——仓颉的枚举不是其他语言里"给整数起名字"的弱枚举,而是函数式语言里的代数数据类型(ADT):构造器可以携带不同类型、不同个数的数据,再配合 match 的穷尽匹配,让编译器帮你保证"每一种可能都被处理了"。

本文所有代码与报错文案均在仓颉 SDK 1.2.0 下逐行实测编译运行。


目录(系列导航)

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

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

一、为什么需要枚举:从"魔法数字"说起

初学编程时,表示"春夏秋冬"你可能会这样写:

// 约定:1=春 2=夏 3=秋 4=冬
let season = 2

问题在于这个约定只存在于注释里,编译器完全不知道 1、2、3、4 是合法值。于是 let season = 99 也能编译通过,传错了、拼错了都要等运行时才暴露。

枚举把"只能取这几个值"从注释变成编译器强制的约束:

enum Season {
    | Spring | Summer | Autumn | Winter
}

Season 类型的变量,永远只能是这四个值之一。花括号里列出的每一个可能取值叫做一个构造器(constructor),多个构造器之间用 | 分隔(第一个 | 可写可不写)。

注意:这里的"构造器"和 class 里的构造函数 init 不是一回事,它指的是枚举类型的一个可能取值。


二、定义和使用枚举

2.1 创建枚举值

用 类型名.构造器 创建一个枚举值;当没有同名变量/函数冲突时,也可以直接写构造器名:

enum Season {
    | Spring | Summer | Autumn | Winter
}

func describe(s: Season): String {
    match (s) {
        case Spring => "春"
        case Summer => "夏"
        case Autumn => "秋"
        case Winter => "冬"
    }
}

main(): Int64 {
    let s1 = Season.Summer   // 完整写法,最稳妥
    let s2: Season = Summer  // 简写:类型明确且无歧义时,可省略类型名
    println(describe(s1))    // 夏
    println(describe(s2))    // 夏——两种写法得到的是同一个枚举值
    return 0
}

注意:仓颉的枚举默认不支持 == 运算符(仓颉也没有 ===,连写三个等号会直接报语法错误),判断枚举值的标准方式是 match;确实需要 == 时请实现 Equatable 接口,见 6.4 节。

2.2 枚举里可以定义方法

枚举体里除了构造器,还能定义成员函数。方法内部用 match (this) 判断"我现在是哪个构造器":

enum RGBColor {
    | Red | Green
    | Blue(UInt8)            // 有参构造器:携带一个"亮度",下一节细讲

    public func describe(): String {
        match (this) {
            case Red => "红色"
            case Green => "绿色"
            case Blue(b) => "蓝色(亮度=${b})"
        }
    }
}

main(): Int64 {
    println(Red.describe())        // 红色
    println(Blue(150).describe())  // 蓝色(亮度=150)
    return 0
}

2.3 枚举只能定义在顶层

枚举只能写在源文件的顶层作用域,不能嵌套在函数、class 或其他枚举里。需要被多个文件共用时,和其他顶层声明一样放在包里即可。


三、match 与枚举:穷尽匹配

第 4 课已经用过 match 做分支,这一节把它和枚举配对的完整能力讲透。

3.1 match 是表达式,有返回值

和 if 一样,match 是表达式,可以直接算出一个值赋给变量:

main(): Int64 {
    let s = Season.Summer
    let text = match (s) {
        case Spring => "春"
        case Summer => "夏"
        case Autumn => "秋"
        case Winter => "冬"
    }
    println(text)   // 夏
    return 0
}

3.2 穷尽性:漏一个分支,编译就报错

match 最值钱的特性是穷尽检查(exhaustive):所有构造器必须都被覆盖。故意漏掉 Winter:

error: non-exhaustive patterns
  # note: Winter is not covered

将来你给枚举新增一个构造器,所有没处理它的 match 会集体编译报错——这相当于编译器帮你把"所有需要同步修改的地方"一个个揪出来,杜绝"加了新状态却忘了处理"的线上事故。

3.3 通配符 _:我不关心具体是谁

不想逐一列出时,最后一个分支用 _ 兜底,它能匹配任何值:

match (s) {
    case Summer => "放暑假"
    case _ => "正常上学"       // 春/秋/冬 都走这里
}

_ 还能用在有参构造器上,表示"这个参数我不要":case Blue(_) => ...。

3.4 一条 case 合并多个模式

相邻分支行为相同时,用 | 在一条 case 里合并:

enum LogLevel {
    | Debug | Info | Warn | Error | Fatal
}

func needAlert(level: LogLevel): Bool {
    match (level) {
        case Error | Fatal => true   // 错误和致命错误都要告警
        case _ => false
    }
}

小提示:构造器名 Error 恰好与标准库的异常基类 Error 同名。在 case 里有明确的类型上下文,不受影响;但在普通表达式中直接写 Error(如 needAlert(Error))可能被解析成标准库类型而报错,这时写全名 LogLevel.Error 即可。

3.5 没有匹配值的 match:更清爽的 if-else 链

match 后面可以不跟值,此时每个 case 后面直接写一个 Bool 表达式,相当于自上而下的 if-else 链:

let score = 85
let grade = match {
    case score >= 90 => "优"
    case score >= 60 => "及格"
    case _ => "不及格"
}
println("等级:${grade}")   // 等级:及格

3.6 只关心一种情况:if-let

满屏分支有时只是为了取一个值。if-let 在"只关心某一种构造器"时比 match 简洁,注意箭头是 <-:

enum MyOpt {
    | SomeS(String)
    | NoneS
}

main(): Int64 {
    let o: MyOpt = SomeS("hello")
    if (let SomeS(text) <- o) {
        println("拿到字符串:${text}")   // 拿到字符串:hello
    } else {
        println("是空的")
    }
    return 0
}

标准库 Option(第 9 课)同样可以这样写:if (let Some(v) <- opt) { ... }。第 9 课我们用的是 isSome() + getOrThrow() 的姿势,if-let 是它更简洁的替代——实测在 SDK 1.2.0 下两种写法都可用。


四、有参构造器:关联值(ADT 的核心)

枚举真正强大的地方,是不同构造器可以携带不同类型、不同个数的数据,这叫关联值(associated value)。

4.1 同一个枚举,每种形态带不同的数据

用一个 Shape 枚举表示三种图形:圆只需要半径,矩形需要宽和高,点什么都不需要:

enum Shape {
    | Circle(Float64)              // 半径
    | Rectangle(Float64, Float64)  // 宽、高
    | Point
}

func area(s: Shape): Float64 {
    match (s) {
        case Circle(r) => 3.14159265 * r * r
        case Rectangle(w, h) => w * h
        case Point => 0.0
    }
}

main(): Int64 {
    println("${area(Circle(2.0))}")           // 12.566371
    println("${area(Rectangle(3.0, 4.0))}")   // 12.000000
    println("${area(Point)}")                 // 0.000000
    return 0
}

case Circle(r) 中的 r 是绑定模式:匹配成功时,构造器携带的数据被绑定到变量 r,分支体内直接使用。

对比用 class 实现:得写 Shape 父类 + Circle/Rectangle/Point 三个子类 + 三个文件的仪式感。而枚举把"一组封闭的形态"压缩进一个类型,数据和分支都集中可见。

4.2 同名构造器:靠参数个数区分

仓颉允许一个枚举里出现同名构造器,只要参数个数不同(无参算 0 个):

enum MyColor {
    | White | Black
    | White(UInt8) | Black(UInt8)
}

match (c) {
    case White => println("无参 White")
    case White(v) => println("带亮度 ${v} 的 White")
    case _ => println("其他")
}

case White 匹配无参构造器,case White(v) 匹配有参构造器,编译器靠参数个数区分。

4.3 守卫 where:匹配成功后再加条件

模式后面可以跟 where 条件(注意不是 if),模式匹配上且条件为 true 时分支才命中。case 自上而下,第一个命中的胜出:

func describe(s: Shape): String {
    match (s) {
        case Circle(r) where r > 10.0 => "大圆"
        case Circle(_) => "小圆"                 // 半径 <=10 的圆落到这里
        case Rectangle(w, h) where w == h => "正方形"
        case Rectangle(_, _) => "长方形"
        case Point => "一个点"
    }
}

实测输出:describe(Circle(20.0)) 是 大圆,describe(Rectangle(5.0, 5.0)) 是 正方形。

4.4 嵌套枚举:一层套一层地解构

构造器的参数类型可以是另一个枚举,match 时可以一层套一层地解构:

enum PayInfo {
    | Unpaid
    | Paid(Int64)          // 支付金额(分)
}

enum OrderStatus {
    | Created
    | Pay(PayInfo)         // 支付中,里面再带一个支付信息
    | Closed
}

func statusText(s: OrderStatus): String {
    match (s) {
        case Created => "待支付"
        case Pay(Unpaid) => "支付处理中"
        case Pay(Paid(amount)) => "已支付 ${amount} 分"   // 两层一次性拆开
        case Closed => "已关闭"
    }
}

Pay(Paid(9900)) 直接在一条 case 里拆出最里层的 amount,输出 已支付 9900 分。


五、递归枚举:表达式求值器

构造器的参数类型还可以是枚举自己,这叫递归枚举。经典例子是算术表达式:一个表达式要么是数字,要么是两个子表达式相加/相减/相乘:

enum Expr {
    | Num(Int64)
    | Add(Expr, Expr)
    | Sub(Expr, Expr)
    | Mul(Expr, Expr)
}

func eval(e: Expr): Int64 {
    match (e) {
        case Num(n) => n
        case Add(a, b) => eval(a) + eval(b)
        case Sub(a, b) => eval(a) - eval(b)
        case Mul(a, b) => eval(a) * eval(b)
    }
}

(1 + 2) * (10 - 3) 用嵌套构造器写出来,再交给递归的 eval:

main(): Int64 {
    let e = Mul(Add(Num(1), Num(2)), Sub(Num(10), Num(3)))
    println("(1+2)*(10-3) = ${eval(e)}")   // (1+2)*(10-3) = 21
    return 0
}

这就是 JSON 解析器、AST 抽象语法树、状态机等数据结构的标准写法——数据用递归枚举描述,处理用递归 match 完成,而且编译器保证每一种节点都被处理。


六、泛型枚举:你其实早就认识两个

6.1 亲手写一个 Option

第 9 课的 Option<T>,本质上就是一个带泛型参数的枚举:

enum MyOption<T> {
    | MySome(T)
    | MyNone

    func unwrapOr(def: T): T {
        match (this) {
            case MySome(v) => v
            case MyNone => def
        }
    }
}

main(): Int64 {
    let a: MyOption<Int64> = MySome(42)
    let n: MyOption<Int64> = MyNone
    println("${a.unwrapOr(0)}, ${n.unwrapOr(0)}")   // 42, 0
    return 0
}

标准库的 enum Option<T> { Some(T) | None } 与此一模一样,只是多了 ??、?.、getOrThrow 等语法糖(第 9 课)。

6.2 亲手写一个 Result

第 10 课我们实测过:当前版本(截至 SDK 1.2.0)的标准库还没有内置 Result,直接写 Result<...> 会报 undeclared type name 'Result',当时我们用一个普通枚举手写了"结果"类型。有了泛型,可以把它写得更通用——成功装一种数据,失败装另一种,这也是将来官方 Result<T, E> 的形态:

enum MyResult<T, E> {
    | Ok(T)
    | Err(E)
}

func divide(a: Int64, b: Int64): MyResult<Int64, String> {
    if (b == 0) {
        return Err("除数不能为 0")
    }
    return Ok(a / b)
}

main(): Int64 {
    match (divide(10, 3)) {
        case Ok(v) => println("成功:${v}")       // 成功:3
        case Err(msg) => println("失败:${msg}")
    }
    match (divide(10, 0)) {
        case Ok(v) => println("成功:${v}")
        case Err(msg) => println("失败:${msg}")  // 失败:除数不能为 0
    }
    return 0
}

看懂这两段,你就彻底理解了第 9、10 课:"可空"和"错误"都不是什么特殊语法——Option 是标准库提供的枚举,而 Result 形态的结果类型,我们自己用一个枚举也能写出来,配合 match 强制调用方处理"没有值/出错了"的分支。等将来 SDK 内置 Result,把 MyResult/Ok/Err 换个名字即可完成迁移(第 10 课已预告过这一点)。

6.3 枚举也能实现接口

和 class 一样,枚举用 <: 实现接口,照样能多态:

interface Drawable {
    func draw(): Unit
}

enum TrafficLight <: Drawable {
    | Red | Yellow | Green

    public func draw(): Unit {
        match (this) {
            case Red => println("画红灯")
            case Yellow => println("画黄灯")
            case Green => println("画绿灯")
        }
    }
}

let lights: Array<Drawable> = [Red, Yellow, Green] 后统一调 l.draw(),走的就是第 17 课的接口多态。

6.4 想打印、想 == 比较:实现 ToString / Equatable

枚举默认不能直接 println,也不能用 == 比较,编译器会分别报 mismatched types 和 invalid binary operator '=='。需要这两个能力时,实现对应接口:

enum Coin <: Equatable<Coin> & ToString {
    | Penny | Nickel | Dime | Quarter

    // Equatable 要求:实现 ==(!= 会自动获得)
    public operator func ==(other: Coin): Bool {
        match ((this, other)) {     // 元组模式:同时匹配两个值
            case (Penny, Penny) => true
            case (Nickel, Nickel) => true
            case (Dime, Dime) => true
            case (Quarter, Quarter) => true
            case _ => false
        }
    }

    // ToString 要求:实现 toString 后即可 println / 字符串插值
    public func toString(): String {
        match (this) {
            case Penny => "1美分"
            case Nickel => "5美分"
            case Dime => "10美分"
            case Quarter => "25美分"
        }
    }
}

实测:Penny == Penny 为 true、Penny != Dime 为 true(两者确实不同,!= 由接口自动提供)、println(Nickel) 输出 5美分。

6.5 Comparable 与 Ordering:让枚举能排序

实现 Comparable<T> 需要写 compare 方法,它的返回值是标准库枚举 Ordering,只有三个构造器:LT(小于)、EQ(等于)、GT(大于)。实现后自动获得 <、>、== 全套比较:

enum Priority <: Comparable<Priority> {
    | Low | Medium | High

    func rank(): Int64 {
        match (this) {
            case Low => 1
            case Medium => 2
            case High => 3
        }
    }

    public func compare(other: Priority): Ordering {
        let d = this.rank() - other.rank()
        if (d < 0) {
            return Ordering.LT
        } else if (d > 0) {
            return Ordering.GT
        } else {
            return Ordering.EQ
        }
    }
}

排序沿用第 7、12 课的全局函数 sort(数组, by: { ... })(import std.sort.*),比较器直接委托给 compare:

sort(tasks, by: { a: Task, b: Task => b.priority.compare(a.priority) })

七、实战:待办事项状态机

把本课知识点串起来:一个待办清单,任务有优先级(可排序的枚举)和状态(关联值携带进度、完成时间的枚举),所有状态流转都靠 match 保证合法。

package todo

import std.sort.*

// 优先级:实现 Comparable,自带 compare 产出 Ordering
enum Priority <: Comparable<Priority> {
    | Low | Medium | High

    func rank(): Int64 {
        match (this) {
            case Low => 1
            case Medium => 2
            case High => 3
        }
    }

    public func compare(other: Priority): Ordering {
        let d = this.rank() - other.rank()
        if (d < 0) {
            return Ordering.LT
        } else if (d > 0) {
            return Ordering.GT
        } else {
            return Ordering.EQ
        }
    }

    func text(): String {
        match (this) {
            case Low => "低"
            case Medium => "中"
            case High => "高"
        }
    }
}

// 任务状态:关联值携带进度和完成时间
enum Status {
    | Pending            // 未开始
    | Doing(UInt8)       // 进行中,0~100
    | Done(Int64)        // 已完成,携带完成时间戳

    func text(): String {
        match (this) {
            case Pending => "未开始"
            case Doing(p) => "进行中 ${p}%"
            case Done(ts) => "已完成(时间戳=${ts})"
        }
    }
}

class Task {
    public let id: Int64
    public let title: String
    public let priority: Priority
    public var status: Status

    public init(id: Int64, title: String, priority: Priority) {
        this.id = id
        this.title = title
        this.priority = priority
        this.status = Pending
    }
}

// 开始任务:只有"未开始"能开始
func start(t: Task): Bool {
    match (t.status) {
        case Pending =>
            t.status = Doing(0)
            return true
        case _ =>
            return false
    }
}

// 更新进度:只有进行中能更新,且取值合法
func updateProgress(t: Task, p: UInt8): Bool {
    match (t.status) {
        case Doing(_) where p <= 100 =>
            t.status = Doing(p)
            return true
        case _ =>
            return false
    }
}

// 完成任务:已完成的不能重复完成,其他状态都可完成
func finish(t: Task, ts: Int64): Bool {
    match (t.status) {
        case Done(_) => false
        case _ =>
            t.status = Done(ts)
            return true
    }
}

// 紧急任务:高优先级且尚未完成
func isUrgent(t: Task): Bool {
    match (t.status) {
        case Done(_) => false
        case _ => t.priority == High
    }
}

func oneLine(t: Task): String {
    return "#${t.id} [${t.priority.text()}] ${t.title} —— ${t.status.text()}"
}

main(): Int64 {
    let tasks = [
        Task(1, "写第18课教程", High),
        Task(2, "回复评论区", Low),
        Task(3, "修复 LSP 崩溃", High),
        Task(4, "整理发布说明", Medium)
    ]

    // 模拟操作
    start(tasks[0])
    updateProgress(tasks[0], 60)
    start(tasks[2])
    finish(tasks[2], 1727000000)
    finish(tasks[3], 1727000100)

    println("===== 全部任务 =====")
    for (t in tasks) {
        println(oneLine(t))
    }

    println("\n===== 紧急任务(高优先级且未完成)=====")
    for (t in tasks) {
        if (isUrgent(t)) {
            println(oneLine(t))
        }
    }

    // 按优先级从高到低排序(比较器委托给 Priority.compare)
    sort(tasks, by: { a: Task, b: Task => b.priority.compare(a.priority) })
    println("\n===== 按优先级从高到低 =====")
    for (t in tasks) {
        println(oneLine(t))
    }
    return 0
}

运行结果:

===== 全部任务 =====
#1 [高] 写第18课教程 —— 进行中 60%
#2 [低] 回复评论区 —— 未开始
#3 [高] 修复 LSP 崩溃 —— 已完成(时间戳=1727000000)
#4 [中] 整理发布说明 —— 已完成(时间戳=1727000100)

===== 紧急任务(高优先级且未完成)=====
#1 [高] 写第18课教程 —— 进行中 60%

===== 按优先级从高到低 =====
#1 [高] 写第18课教程 —— 进行中 60%
#3 [高] 修复 LSP 崩溃 —— 已完成(时间戳=1727000000)
#4 [中] 整理发布说明 —— 已完成(时间戳=1727000100)
#2 [低] 回复评论区 —— 未开始

这个例子值得体会的设计:每个操作函数返回 Bool 表示"这次状态流转是否合法",非法流转在编译期就被 match 的分支结构框死了——比如对 Done 的任务再 finish,不用任何 if 判空,天然返回 false。这就是用类型表达业务规则的威力。


八、常见问题 FAQ

Q1:仓颉枚举为什么没有 Mon = 1 这种原始值?
仓颉枚举是代数数据类型,不是其他语言里"整数的别名",构造器不自动对应整数(写 Mon = 1 会直接语法报错)。想要数字编号,自己写一个 match 方法转换(如本课的 rank());想让构造器携带数据,用有参构造器 Blue(UInt8)。这反而比原始值强大得多——关联值可以是任意类型、任意个数。

Q2:枚举为什么不能直接 println、不能用 ==?
枚举默认不实现任何接口。打印需要 <: ToString 并写 toString();比较需要 <: Equatable<T> 并写 operator func ==(实现后 != 自动可用);想排序就实现 Comparable<T> 的 compare。日常最简单的判断方式其实是 match,多数场景根本不需要 ==。

Q3:枚举能写在函数里面吗?
不能,枚举只能定义在源文件顶层。函数内部需要"临时的多选一"时,直接用顶层枚举或 match 字面分支即可。

Q4:case 的书写顺序重要吗?
重要。match 自上而下匹配,第一个命中的分支胜出后立刻跳出。所以带 where 守卫的"小范围"分支必须写在"大范围"分支前面(Circle(r) where r > 10 要在 Circle(_) 之前),否则永远轮不到它。

Q5:报 non-exhaustive patterns 怎么办?
说明有构造器没覆盖,报错信息的 note 会直接告诉你漏了谁(如 Winter is not covered)。要么补全分支,要么在最后加 case _ => 兜底。不要无脑加 _——穷尽检查是编译器送的安全网,显式列全每个构造器通常更安全。

Q6:match 和 if-let 什么时候用哪个?
需要覆盖所有/多种构造器、各分支行为不同时用 match;只关心某一种构造器、其他情况统一处理时用 if-let。处理 Option(第 9 课)或手写的结果枚举(第 10 课)时这两者最常用。

Q7:枚举最后的 ... 是什么?
那是非穷举枚举声明,表示"未来可能追加新构造器":

enum ExtendedColor {
    | Red | Green | Blue
    | ...
}

对它做 match 时必须写 case _ 兜底(编译器不认为三个构造器就是全部),这样将来新增构造器时旧代码仍能编译。自己的业务枚举形态固定时不需要它。

Q8:枚举和第 17 课的 sealed 类怎么选?
形态封闭、只装数据、几乎没有各自的行为时用枚举(订单状态、表达式树、Option/Result);各形态需要独立的字段、复杂方法或被外部继承时用 sealed 类。经验法则:先问"这是不是一组数据形态"——是,就用枚举。


九、课后练习

  1. 定义枚举 enum Weekday { | Mon | Tue | Wed | Thu | Fri | Sat | Sun },写函数 isWeekend(d: Weekday): Bool(用多模式合并),再写一个 text(d) 返回中文星期。
  2. 给本课的 Shape 枚举增加 Triangle(Float64, Float64, Float64)(三边,用海伦公式 √(p(p-a)(p-b)(p-c)),p=(a+b+c)/2 求面积),在 area 和 describe 中补全分支;故意删掉一个分支,观察 non-exhaustive patterns 报错。
  3. 给第五节的 Expr 增加 Div(Expr, Expr) 除法构造器:除数求值为 0 时让 eval 返回 Option<Int64>(None 表示除零错误),调用方用 match 处理两种结果。
  4. 定义 enum HttpStatus { | Ok(Int64) | Redirect(Int64, String) | Error(Int64, String) },实现 ToString,用嵌套绑定/多模式让 200/204 都显示"成功"。
  5. 扩展实战项目:给 Status 增加 Paused(UInt8)(暂停时保留进度),新增 pause(t)(只有 Doing 能暂停)和 resume(t)(只有 Paused 能恢复为 Doing)。编译后按编译器的 not covered 提示,把所有需要补分支的函数逐个补全——亲手感受一遍穷尽检查的价值。

下节预告

本课的 MyOption<T>、MyResult<T, E> 已经提前用了泛型:类型本身留一个"占位符 T",使用时再决定装什么类型。第 19 课 泛型编程将系统讲解泛型函数、泛型类/结构体、泛型约束(where T <: Comparable<T>),让你写出"一份逻辑,任意类型复用"的容器与算法——第 8 课的 ArrayList<T>、第 14 课的 Iterator<T> 背后都是它。


系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.2.0)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入,以你本地版本为准,欢迎评论区交流。


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

跟着课程练习时,如果在 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
Logo

一站式 AI 云服务平台

更多推荐