倉頡編程語言青少年基礎教程: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表示 "無結果" 類錯誤,替代復雜的錯誤判斷