iota 在 Go 中的使用

Remember發表於2021-04-28

文章持續更新,微信搜一搜「 吳親強的深夜食堂

介紹

Go 語言實際上沒有直接支援列舉的關鍵字。一般我們都是通過 const + iota 實現列舉的能力。

有人要問了,為什麼一定要使用列舉呢?stackoverflow 上有一個高讚的回答,如下:

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.BTW, overuse of enums might mean that your methods do too much (it’s often better to have several separate methods, rather than one method that takes several flags which modify what it does), but if you have to use flags or type codes, enums are the way to go.

簡單翻譯一下, 兩點很重要。

  • 當一個變數(尤其是方法引數) 只能從一小部分可能的值中取出一個時,理應使用列舉。
    例如型別常量(合同狀態:永久、臨時工、學徒), 或者在做任務程式時,是立即執行還是延遲執行的標記。
  • 如果使用列舉而不是整形,則會增加編譯時的檢查,避免錯誤無效值的傳入,記錄哪些值是合法使用的。

如何實現列舉

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 的使用介紹,以及我們為什麼要使用它。

不知道大家平常對於此類場景是用的什麼招數,歡迎下方留言交流。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
吳親庫裡

相關文章