仓颉编程语言青少年基础教程:enum(枚举)类型和Option类型
enum 和 Option 各自解决一类“语义级”问题:
enum 让“取值只在有限集合内”的约束从注释变成编译器强制;
Option 让“值可能不存在”的语义显式化。
enum类型
enum 类型提供了通过列举一个类型的所有可能取值来定义此类型的方式。
定义 enum 时需要把它所有可能的取值一一列出,称这些值为 enum 的构造器(constructor)。
仓颉的枚举(enum)类型,简单说就是:通过把一个类型所有可能的 “选项” 列出来,来定义这个类型。
枚举只能定义在文件的最顶层(不能嵌套在函数或其他类型里)。
枚举的定义
基本语法:
enum <类型名> {
| <构造器1> [参数]
| <构造器2> [参数]
......
| ... // 非穷举(non-exhaustive)构造器,只能有一个且在最后
}
说明:用enum关键字开头,后面跟类型名,大括号里列出所有可能的 “选项”(官方叫 “构造器”),选项之间用|隔开。
• 构造器分无参(如 Red)和有参(如 Red(UInt8))
• 支持同名构造器(需参数个数不同)
• 可包含递归定义(构造器参数使用自身类型)
• 可添加 ... 作为最后一个构造器(non-exhaustive enum,不可直接匹配)
注意,构造器(constructor)的含义,枚举的 “构造器” = 枚举类型的 “所有可能取值”,和类里的 “构造器(构造函数)” 不是一回事。
基本示例源码:
// 1. 顶层 enum 定义
enum RGBColor {| Red | Green // 无参构造器| Blue(UInt8) // 有参构造器(亮度值)// 成员函数:打印颜色信息public func describe(): Unit { // ① 必须显式写出返回类型 Unitmatch (this) { // ② match 后面要加括号case Red => println("Red")case Green => println("Green")case Blue(brightness) => println("Blue with brightness: ${brightness}") // ③ 字符串插号语法}}
}// 2. 主函数入口
main(): Int64 { let red = RGBColor.Red // 全名访问,稳妥let green = RGBColor.Green // 为避免歧义,统一加类型名let blue = RGBColor.Blue(150)red.describe() // 输出:Redgreen.describe() // 输出:Greenblue.describe() // 输出:Blue with brightness: 150return 0
}
编译运行截图:
使用(创建枚举的实例的)规则
• 通过 类型名.构造器 或直接用构造器创建实例(无歧义时)
• 名字冲突时必须加类型名(如与变量 / 函数同名)
例如:
let c1 = Color.Red // 完整写法
let c2 = Red // 简写,前提是不跟别的 Red 冲突
如果当前作用域里已经有一个变量叫 Red,那就必须写全名 Color.Red,否则编译器会以为你在用那个变量。
完整示例:
enum Color {| Red| Yellow| Green
}// 模拟变量名冲突
let Red = "字符串变量Red"func message(light: Color): String {match (light) {case Red => "停!" case Yellow => "注意!"case Green => "通行!"}
}main() {let light = Yellow // 可以用 Yellow 或Color.Yellowprintln(message(light)) // 输出:注意!let light2 = Green // 可以用 Green 或Color.Greenprintln(message(light2)) // 输出:通行!let light3 = Color.Red // 只能用Color.Red ,不能用Redprintln(message(light3)) // 输出:停! println(Red) // 输出:字符串变量Red
}
编译运行输出:
注意!
通行!
停!
字符串变量Red
重要事项说明
1.构造器比较灵活:
(1)可以带参数(带附加信息)
比如想表示 “红色的亮度”,可以给Red加个参数(比如 0-255 的数值):
enum RGBColor {
| Red(UInt8) | Green(UInt8) | Blue(UInt8)
}
// 比如 Red(255) 就表示“亮度为255的红色”
(2)可以有同名选项,但参数个数必须不同
比如既可以有 “单纯的红色”(不带参数),也可以有 “带亮度的红色”(带参数):
plaintext
enum RGBColor {
| Red | Green | Blue // 不带参数的选项
| Red(UInt8) | Green(UInt8) | Blue(UInt8) // 带参数的同名选项(参数个数不同)
}
(3)可以有一个 “省略号选项”...
加在最后,表示 “还有其他没列出来的选项”(这种枚举叫 non-exhaustive enum)。用的时候不能直接匹配这个...,得用通配符_(比如 “其他所有情况”):
plaintext
enum T {
| Red | Green | Blue | ... // 最后一个是省略号,表示还有其他可能
}
2.枚举可以 “自己包含自己”(递归定义)
枚举的选项参数可以是它自己的类型,这在定义复杂结构时很有用。
比如定义一个 “数学表达式” 类型:
• 可以是一个数字(Num)
• 可以是两个表达式相加(Add)
• 可以是两个表达式相减(Sub)
可以这样写:
enum Expr {
| Num(Int64) // 数字,参数是具体数值(比如Num(5))
| Add(Expr, Expr) // 加法,参数是两个表达式(比如Add(Num(2), Num(3))表示2+3)
| Sub(Expr, Expr) // 减法,同理
}
这样你就能表达 1 + (2 - 3) 这样的嵌套结构。
完整示例源码如下:
// 定义递归枚举:表示数学表达式
enum Expr {| Num(Int64) // 数字| Add(Expr, Expr) // 加法| Sub(Expr, Expr) // 减法
}// 定义一个递归函数:计算Expr表达式的值
func eval(expr: Expr): Int64 {match (expr) {case Num(n) => n // 如果是数字,直接返回数值case Add(a, b) => eval(a) + eval(b) // 加法:递归计算左右两边再相加case Sub(a, b) => eval(a) - eval(b) // 减法:递归计算左右两边再相减}
}// 构造表达式:1 + (2 - 3)
let nestedExpr = Add(Num(1), // 左边:1Sub(Num(2), Num(3)) // 右边:2 - 3
)main(): Unit {let result = eval(nestedExpr)println(result) // 输出:0,因为1 + (2-3) = 1 + (-1) = 0
}
3.枚举里可以加 “功能”(函数 / 属性)
可以在枚举里定义函数或属性,但名字不能和选项(构造器)重复。完整示例源码如下:
// 带亮度参数的RGB颜色枚举
enum RGBColor {| Red(UInt8) // 红色(亮度0-255)| Green(UInt8) // 绿色| Blue(UInt8) // 蓝色// 成员函数:打印颜色和亮度信息func printInfo() {// 使用this指代当前实例match (this) {case Red(b) => println("红色,亮度:${b}")case Green(b) => println("绿色,亮度:${b}")case Blue(b) => println("蓝色,亮度:${b}")}}// 成员函数:判断是否为高亮度(亮度>127)func checkHighBright(): Bool {match (this) {case Red(b) => return b > 127case Green(b) => return b > 127case Blue(b) => return b > 127}}
}// 程序入口(无需func修饰)
main() {let red = RGBColor.Red(200)let green = RGBColor.Green(50)// 调用枚举成员函数red.printInfo() // 输出:红色,亮度:200println("红色是否高亮度:${red.checkHighBright()}") // 输出:红色是否高亮度:truegreen.printInfo() // 输出:绿色,亮度:50println("绿色是否高亮度:${green.checkHighBright()}")// 输出:绿色是否高亮度:false
}
输出:
红色,亮度:200
红色是否高亮度:true
绿色,亮度:50
绿色是否高亮度:false
Option类型
Option 是 enum 的泛型应用,专门处理 “可能为空” 的场景,通过 Some 和 None 避免空指针错误,是仓颉中处理不确定性的常用方式。
Option<T> 类型 —— 一种安全处理“可能无值”情况的机制。Option 类型通过Some(有值)和None(无值)两种状态,让 "值是否存在" 变得显式可控,配合模式匹配、??操作符、?操作符等工具,能安全、简洁地处理各种可能无值的场景,是仓颉中避免空引用错误的重要机制。
Option 类型使用 enum 定义,它包含两个构造器(constructor):Some 和 None。其中,Some 会携带一个参数,表示有值;None 不带参数,表示无值。当需要表示某个类型可能有值,也可能没有值时,可以选择使用 Option 类型。
Option 是仓颉标准库里内置的泛型枚举,声明如下:
enum Option<T> {
Some(T) // 表示“有值”,并携带一个 T 类型的数据
None // 表示“无值”
}
其中,Some() 是仓颉语言中 Option<T> 类型的一个“构造器”(constructor),用来表示“有一个值”。用法示例:
写法 含义
Some(100) 表示“有一个整数 100”
Some("Hello") 表示“有一个字符串 "Hello"”
Option 类型简洁的写法:
?T 等价于 Option<T> (官方写为:?Ty 等价于 Option<Ty>。T和Ty都是Type 的缩写,都代表“某个具体的类型”):
?Int64 等价于 Option<Int64>
?String 等价于 Option<String>
?Bool 等价于 Option<Bool>
例如:
// 完整写法
let a: Option<Int64> = Some(100) // 有值:100
let b: Option<String> = Some("Hello") // 有值:"Hello"
let c: Option<Int64> = None // 无值
// 简写形式(?Ty)
let d: ?Int64 = Some(200) // 等价于Option<Int64>
let e: ?String = None // 等价于Option<String>
Option 类型的解构使用方式
Option 是一种非常常用的类型,所以仓颉为其提供了多种解构【注】方式,以方便 Option 类型的使用,具体包括:模式匹配、getOrThrow 函数、coalescing 操作符(??),以及问号操作符(?)。
【“解构” 在这里的核心含义是:打破 Option 类型的 “包装”,获取 Some 中包含的具体值,或对 None 进行处理】
1.模式匹配(match 语句)
通过match匹配Some和None,显式处理两种状态。 示例:
//将Option<Int64>转换为字符串(有值则返回值的字符串,无值返回"none")
func getString(p: ?Int64): String {match (p) {case Some(x) => "数字:${x}" // 匹配有值状态,x绑定具体值case None => "没有提供数字" // 匹配无值状态}
}main() {let a = Some(10)let b: ?Int64 = Noneprintln(getString(a)) // 输出:数字:10println(getString(b)) // 输出:没有提供数字
}
2. coalescing 操作符(??)
用于为None提供默认值,语法:e1 ?? e2
• 若e1是Some(v),返回v
• 若e1是None,返回e2(e2需与v同类型)
示例:
main() {let a: ?Int64 = Some(5)let b: ?Int64 = Nonelet r1 = a ?? 0 // a是Some(5),返回5let r2 = b ?? 0 // b是None,返回默认值0println(r1) // 输出:5println(r2) // 输出:0
}
3. 问号操作符(?)
与.、()、[]、{}结合使用,实现对 Option 内部成员的安全访问:
• 若 Option 是Some(v),则访问v的成员,结果用Some包装
• 若 Option 是None,则直接返回None(避免空访问错误)
示例:
a.访问结构体 / 类的成员(. 操作):
// 定义一个结构体
struct R {public var a: Int64public init(a: Int64) {this.a = a}
}main() {let r = R(100)let x: ?R = Some(r) // 有值的Optionlet y: ?R = None // 无值的Optionlet r1 = x?.a // x是Some(r),访问r.a,结果为Some(100)let r2 = y?.a // y是None,直接返回Noneprintln(r1) // 输出:Some(100)println(r2) // 输出:None
}
b.多层访问
问号操作符支持链式调用,任意一层为None则整体返回None:
class A {public var b: B = B() // A包含B类型的b
}class B {public var c: ?C = C() // B包含Option<C>类型的c(有值)public var c1: ?C = None // B包含Option<C>类型的c1(无值)
}class C {public var d: Int64 = 100 // C包含Int64类型的d
}main() {let a: ?A = Some(A()) // 有值的Option<A>// 多层访问:a?.b.c?.d// a是Some,b存在;c是Some,d存在 → 结果为Some(100)let r1 = a?.b.c?.d// a?.b.c1?.d:c1是None → 结果为Nonelet r2 = a?.b.c1?.dprintln(r1) // 输出:Some(100)println(r2) // 输出:None
}
4. getOrThrow 函数
用于直接获取Some中的值,若为None则抛出异常(需配合try-catch处理):
main() {let a: ?Int64 = Some(8)let b: ?Int64 = None// 获取a的值(成功)let r1 = a.getOrThrow()println(r1) // 输出:8// 获取b的值(失败,抛出异常)try {let r2 = b.getOrThrow()} catch (e: NoneValueException) {println("捕获异常:b是None") // 输出:捕获异常:b是None}
}
Option 类型主要用于以下场景:
1. 处理可能为空的返回值:如函数可能返回有效结果或无结果(避免返回 null)
2. 安全的成员访问:通过?操作符避免访问空对象的成员
3. 简化错误处理:用None表示 "无结果" 类错误,替代复杂的错误判断