為什么我們需要在 Go 中使用 Iota
本文轉(zhuǎn)載自微信公眾號「吳親強(qiáng)的深夜食堂」,作者吳親庫里。轉(zhuǎn)載本文請聯(lián)系吳親強(qiáng)的深夜食堂公眾號。
介紹
Go 語言實(shí)際上沒有直接支持枚舉的關(guān)鍵字。一般我們都是通過 const + iota 實(shí)現(xiàn)枚舉的能力。
有人要問了,為什么一定要使用枚舉呢?stackoverflow[1] 上有一個(gè)高贊的回答,如下:
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.
簡單翻譯一下, 兩點(diǎn)很重要。
- 當(dāng)一個(gè)變量(尤其是方法參數(shù)) 只能從一小部分可能的值中取出一個(gè)時(shí),理應(yīng)使用枚舉。例如類型常量(合同狀態(tài):永久、臨時(shí)工、學(xué)徒), 或者在做任務(wù)程序時(shí),是立即執(zhí)行還是延遲執(zhí)行的標(biāo)記。
- 如果使用枚舉而不是整形,則會(huì)增加編譯時(shí)的檢查,避免錯(cuò)誤無效值的傳入,記錄哪些值是合法使用的。
如何實(shí)現(xiàn)枚舉
iota 是 Go 中預(yù)聲明的一個(gè)特殊常量。它會(huì)被預(yù)聲明為0,但是它的值在編譯階段并非是固定的,當(dāng)預(yù)聲明的 iota 出現(xiàn)在一個(gè)常量聲明中,它的值在第n個(gè)常量描述中的值為n(從0開始)。
比如,大家都或多或少了解電商系統(tǒng)。其中的訂單模塊一定會(huì)涉及到訂單狀態(tài)的流轉(zhuǎn)。那么這時(shí)候,我們一般可以這樣定義:
- package main
- import "fmt"
- type OrderStatus int
- const (
- Cancelled OrderStatus = iota //訂單已取消 0
- NoPay OrderStatus = iota //未支付 1
- PendIng OrderStatus = iota // 未發(fā)貨 2
- Delivered OrderStatus = iota // 已發(fā)貨 3
- Received OrderStatus = iota // 已收貨 4
- )
- func main() {
- fmt.Println(Cancelled, NoPay) // 打印:0,1
- }
當(dāng)然,這樣看著好麻煩。其實(shí),其他常量可以重復(fù)上一行 iota 表達(dá)式,我們可以改成這樣。
- package main
- import "fmt"
- type OrderStatus int
- const (
- Cancelled OrderStatus = iota //訂單已取消 0
- NoPay //未支付 1
- PendIng // 未發(fā)貨 2
- Delivered // 已發(fā)貨 3
- Received // 已收貨 4
- )
- func main() {
- fmt.Println(Cancelled, NoPay) // 打印:0,1
- }
有人會(huì)用 0 的值來表示狀態(tài)嗎?一般都不會(huì),我們想以1開頭,那么可以這樣。
- package main
- import "fmt"
- type OrderStatus int
- const (
- Cancelled OrderStatus = iota+1 //訂單已取消 1
- NoPay //未支付 2
- PendIng // 未發(fā)貨 3
- Delivered // 已發(fā)貨 4
- Received // 已收貨 5
- )
- func main() {
- fmt.Println(Cancelled, NoPay) // 打印:1,2
- }
我們還想在 Delivered 后跳過一個(gè)數(shù)字,才是 Received 的值,也就是 Received=6,那么可以借助 _ 符號。
- package main
- import "fmt"
- type OrderStatus int
- const (
- Cancelled OrderStatus = iota+1 //訂單已取消 1
- NoPay //未支付 2
- PendIng // 未發(fā)貨 3
- Delivered // 已發(fā)貨 4
- _
- Received // 已收貨 6
- )
- func main() {
- fmt.Println(Received) // 打印:6
- }
順著來可以,倒著當(dāng)然也行。
- package main
- import "fmt"
- type OrderStatus int
- const (
- Max = 5
- )
- const (
- Received OrderStatus = Max - iota // 已收貨 5
- Delivered // 已發(fā)貨 4
- PendIng // 未發(fā)貨 3
- NoPay //未支付 2
- Cancelled //訂單已取消 1
- )
- func main() {
- fmt.Println(Received,Delivered) // 打印:5,4
- }
還可以使用位運(yùn)算,比如在 go 源碼中的包 sync 中的鎖上面有這么一段定義代碼。
- const (
- mutexLocked = 1 << iota //1<<0
- mutexWoken //1<<1
- mutexStarving //1<<2
- mutexWaiterShift = iota //3
- )
- func main() {
- fmt.Println("mutexLocked的值",mutexLocked) //打?。?
- fmt.Println("mutexWoken的值",mutexWoken) //打?。?
- fmt.Println("mutexStarving的值",mutexStarving) //打?。?
- fmt.Println("mutexWaiterShift的值",mutexWaiterShift) // 打?。?
- }
也許有人平常是直接定義常量值或者用字符串來表示的。
比如,上面這些我完全可以用 string 來表示,我還真見過用字符串來表示訂單狀態(tài)的。
- package main
- import "fmt"
- const (
- Cancelled = "cancelled"
- NoPay = "noPay"
- PendIng = "pendIng"
- Delivered = "delivered"
- Received = "received"
- )
- var OrderStatusMsg = map[string]string{
- Cancelled: "訂單已取消",
- NoPay: "未付款",
- PendIng: "未發(fā)貨",
- Delivered: "已發(fā)貨",
- 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: "未發(fā)貨",
- Delivered: "已發(fā)貨",
- Received: "已收貨",
- }
- func main() {
- fmt.Println(OrderStatusMsg[Cancelled])
- }
其實(shí)上述兩種都可以,但是相比之下使用 iota 更有優(yōu)勢。
- 能保證一組常量的唯一性,人工定義的不能保證。
- 可以為一組動(dòng)作分享同一種行為。
- 避免無效值。
- 提高代碼閱讀性以及維護(hù)。
延伸
按照上面我們所演示的,最后我們可以這樣操作。
- package main
- import (
- "fmt"
- )
- type OrderStatus int
- const (
- Cancelled OrderStatus = iota + 1 //訂單已取消 1
- NoPay //未支付 2
- PendIng // 未發(fā)貨 3
- Delivered // 已發(fā)貨 4
- Received // 已收貨 5
- )
- //公共行為 賦予類型 String() 函數(shù),方便打印值含義
- func (order OrderStatus) String() string {
- return [...]string{"cancelled", "noPay", "pendIng", "delivered", "received"}[order-1]
- }
- //創(chuàng)建公共行為 賦予類型 int 函數(shù) 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
- }
總結(jié)
這篇文章主要介紹了 Golang 中對 iota 的使用介紹,以及我們?yōu)槭裁匆褂盟?/p>
不知道大家平常對于此類場景用的什么招數(shù),歡迎下方留言交流。
附錄
[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