爲什麼我們需要在 Go 中使用 iota
圖片拍攝於 2020 年 10 月 25 日,杭州大明山。
介紹
Go 語言實際上沒有直接支持枚舉的關鍵字。一般我們都是通過 const
+ iota
實現枚舉的能力。
有人要問了,爲什麼一定要使用枚舉呢?stackoverflow[1]
上有一個高讚的回答,如下:
You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: "permanent", "temp", "apprentice"), or flags ("execute now", "defer execution").
If you use enums instead of integers (or String codes), you increase compile-time checking and avoid errors from passing in invalid constants, and you document which values are legal to use.
簡單翻譯一下, 兩點很重要。
-
當一個變量 (尤其是方法參數) 只能從一小部分可能的值中取出一個時,理應使用枚舉。例如類型常量 (合同狀態:永久、臨時工、學徒), 或者在做任務程序時,是立即執行還是延遲執行的標記。
-
如果使用枚舉而不是整形,則會增加編譯時的檢查,避免錯誤無效值的傳入,記錄哪些值是合法使用的。
如何實現枚舉
iota 是 Go 中預聲明的一個特殊常量。它會被預聲明爲 0,但是它的值在編譯階段並非是固定的,當預聲明的 iota 出現在一個常量聲明中,它的值在第 n 個常量描述中的值爲 n(從 0 開始)。
比如,大家都或多或少了解電商系統。其中的訂單模塊一定會涉及到訂單狀態的流轉。那麼這時候,我們一般可以這樣定義:
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota //訂單已取消 0
NoPay OrderStatus = iota //未支付 1
PendIng OrderStatus = iota // 未發貨 2
Delivered OrderStatus = iota // 已發貨 3
Received OrderStatus = iota // 已收貨 4
)
func main() {
fmt.Println(Cancelled, NoPay) // 打印:0,1
}
當然,這樣看着好麻煩。其實,其他常量可以重複上一行 iota
表達式,我們可以改成這樣。
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota //訂單已取消 0
NoPay //未支付 1
PendIng // 未發貨 2
Delivered // 已發貨 3
Received // 已收貨 4
)
func main() {
fmt.Println(Cancelled, NoPay) // 打印:0,1
}
有人會用 0 的值來表示狀態嗎?一般都不會,我們想以 1 開頭,那麼可以這樣。
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota+1 //訂單已取消 1
NoPay //未支付 2
PendIng // 未發貨 3
Delivered // 已發貨 4
Received // 已收貨 5
)
func main() {
fmt.Println(Cancelled, NoPay) // 打印:1,2
}
我們還想在 Delivered
後跳過一個數字,纔是 Received
的值, 也就是 Received=6
,那麼可以藉助 _
符號。
package main
import "fmt"
type OrderStatus int
const (
Cancelled OrderStatus = iota+1 //訂單已取消 1
NoPay //未支付 2
PendIng // 未發貨 3
Delivered // 已發貨 4
_
Received // 已收貨 6
)
func main() {
fmt.Println(Received) // 打印:6
}
順着來可以,倒着當然也行。
package main
import "fmt"
type OrderStatus int
const (
Max = 5
)
const (
Received OrderStatus = Max - iota // 已收貨 5
Delivered // 已發貨 4
PendIng // 未發貨 3
NoPay //未支付 2
Cancelled //訂單已取消 1
)
func main() {
fmt.Println(Received,Delivered) // 打印:5,4
}
還可以使用位運算,比如在 go 源碼中的包 sync
中的鎖上面有這麼一段定義代碼。
const (
mutexLocked = 1 << iota //1<<0
mutexWoken //1<<1
mutexStarving //1<<2
mutexWaiterShift = iota //3
)
func main() {
fmt.Println("mutexLocked的值",mutexLocked) //打印:1
fmt.Println("mutexWoken的值",mutexWoken) //打印:2
fmt.Println("mutexStarving的值",mutexStarving) //打印:4
fmt.Println("mutexWaiterShift的值",mutexWaiterShift) // 打印:3
}
也許有人平常是直接定義常量值或者用字符串來表示的。
比如,上面這些我完全可以用 string
來表示,我還真見過用字符串來表示訂單狀態的。
package main
import "fmt"
const (
Cancelled = "cancelled"
NoPay = "noPay"
PendIng = "pendIng"
Delivered = "delivered"
Received = "received"
)
var OrderStatusMsg = map[string]string{
Cancelled: "訂單已取消",
NoPay: "未付款",
PendIng: "未發貨",
Delivered: "已發貨",
Received: "已收貨",
}
func main() {
fmt.Println(OrderStatusMsg[Cancelled])
}
或者直接定義整形常量值。
package main
import "fmt"
const (
Cancelled = 1
NoPay = 2
PendIng = 3
Delivered = 4
Received = 5
)
var OrderStatusMsg = map[int]string{
Cancelled: "訂單已取消",
NoPay: "未付款",
PendIng: "未發貨",
Delivered: "已發貨",
Received: "已收貨",
}
func main() {
fmt.Println(OrderStatusMsg[Cancelled])
}
其實上述兩種都可以,但是相比之下使用 iota
更有優勢。
-
能保證一組常量的唯一性,人工定義的不能保證。
-
可以爲一組動作分享同一種行爲。
-
避免無效值。
-
提高代碼閱讀性以及維護。
延伸
按照上面我們所演示的,最後我們可以這樣操作。
package main
import (
"fmt"
)
type OrderStatus int
const (
Cancelled OrderStatus = iota + 1 //訂單已取消 1
NoPay //未支付 2
PendIng // 未發貨 3
Delivered // 已發貨 4
Received // 已收貨 5
)
//公共行爲 賦予類型 String() 函數,方便打印值含義
func (order OrderStatus) String() string {
return [...]string{"cancelled", "noPay", "pendIng", "delivered", "received"}[order-1]
}
//創建公共行爲 賦予類型 int 函數 EnumIndex()
func (order OrderStatus) EnumIndex() int {
return int(order)
}
func main() {
var order OrderStatus = Received
fmt.Println(order.String()) // 打印:received
fmt.Println(order.EnumIndex()) // 打印:5
}
總結
這篇文章主要介紹了 Golang
中對 iota
的使用介紹,以及我們爲什麼要使用它。
不知道大家平常對於此類場景用的什麼招數,歡迎下方留言交流。
附錄
[1]https://stackoverflow.com/questions/4709175/what-are-enums-and-why-are-they-useful
https://levelup.gitconnected.com/implementing-enums-in-golang-9537c433d6e2
https://medium.com/qvault/how-and-why-to-write-enums-in-go-9c1a25649df0
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HOkquS6bA1I18TyT1lR8zQ