为什么我们需要在 Go 中使用 iota

简介: 为什么我们需要在 Go 中使用 iota

介绍


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-usefulhttps://levelup.gitconnected.com/implementing-enums-in-golang-9537c433d6e2

https://medium.com/qvault/how-and-why-to-write-enums-in-go-9c1a25649df0

相关文章
|
15天前
|
安全 Go
Go语言的iota关键字有什么用途?
**Go语言中的`iota`是常量生成器,用于在`const`声明中创建递增的常量。`iota`在每个新的`const`块重置为0,然后逐行递增,简化了枚举类型或常量序列的定义。例如,定义星期枚举:** ```markdown ```go type Weekday int const ( Sunday Weekday = iota // 0 Monday // 1 Tuesday // 2 ... ) ``` 同样,`iota`可用于定义不同组的常量,如状态码和标志位,保持各自组内的递增,提高代码可读性。
|
29天前
|
存储 安全 Go
【Go语言精进之路】构建高效Go程序:掌握变量、常量声明法则与iota在枚举中的奥秘
【Go语言精进之路】构建高效Go程序:掌握变量、常量声明法则与iota在枚举中的奥秘
23 2
|
21天前
|
Go
go常量显示定义、隐式定义、iota
go常量显示定义、隐式定义、iota
10 0
|
2月前
|
Go
go语言iota详解
go语言iota详解
32 0
|
9月前
|
Go
这才是模拟枚举的最佳方式 Go语言const+iota实现简明优雅
这才是模拟枚举的最佳方式 Go语言const+iota实现简明优雅
60 0
|
12月前
|
Go 索引
Go 语言 iota 的神奇力量
在本文中,我将带着大家深入探讨 iota 的神奇力量,包括 iota 的介绍和应用场景以及使用技巧和注意事项。
89 0
|
Go
Go中的iota
Go中的iota
52 0
|
程序员 Go PHP
Go语言关于const和iota进阶实战
你能做对这几道面试题吗?Go语言关于const和iota进阶实战
161 0
Go语言关于const和iota进阶实战
|
Go
go语言常量以及iota
go语言常量以及iota
65 0
go语言常量以及iota
|
算法 Go 索引
Go语言 iota 实现原理
iota 表示 const 声明块的行索引(下标从0开始)
94 0
Go语言 iota 实现原理