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

开发 后端
Go 语言实际上没有直接支持枚举的关键字。一般我们都是通过 const + iota 实现枚举的能力。

[[395811]]

本文转载自微信公众号「吴亲强的深夜食堂」,作者吴亲库里。转载本文请联系吴亲强的深夜食堂公众号。  

介绍

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开始)。

比如,大家都或多或少了解电商系统。其中的订单模块一定会涉及到订单状态的流转。那么这时候,我们一般可以这样定义:

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type OrderStatus int 
  6.  
  7. const ( 
  8.   Cancelled OrderStatus = iota //订单已取消 0 
  9.   NoPay OrderStatus = iota //未支付 1 
  10.   PendIng OrderStatus = iota // 未发货 2 
  11.   Delivered OrderStatus = iota // 已发货 3 
  12.   Received OrderStatus = iota // 已收货 4 
  13.  
  14. func main() { 
  15.   fmt.Println(Cancelled, NoPay) // 打印:0,1 

当然,这样看着好麻烦。其实,其他常量可以重复上一行 iota 表达式,我们可以改成这样。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type OrderStatus int 
  6.  
  7. const ( 
  8.   Cancelled OrderStatus = iota //订单已取消 0 
  9.   NoPay //未支付 1 
  10.   PendIng // 未发货 2 
  11.   Delivered // 已发货 3 
  12.   Received // 已收货 4 
  13.  
  14. func main() { 
  15.   fmt.Println(Cancelled, NoPay) // 打印:0,1 

有人会用 0 的值来表示状态吗?一般都不会,我们想以1开头,那么可以这样。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type OrderStatus int 
  6.  
  7. const ( 
  8.   Cancelled OrderStatus = iota+1 //订单已取消 1 
  9.   NoPay //未支付 2 
  10.   PendIng // 未发货 3 
  11.   Delivered // 已发货 4 
  12.   Received // 已收货 5 
  13.  
  14. func main() { 
  15.   fmt.Println(Cancelled, NoPay) // 打印:1,2 

我们还想在 Delivered 后跳过一个数字,才是 Received 的值,也就是 Received=6,那么可以借助 _ 符号。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type OrderStatus int 
  6.  
  7. const ( 
  8.   Cancelled OrderStatus = iota+1 //订单已取消 1 
  9.   NoPay //未支付 2 
  10.   PendIng // 未发货 3 
  11.   Delivered // 已发货 4 
  12.   _ 
  13.   Received // 已收货 6 
  14.  
  15. func main() { 
  16.   fmt.Println(Received) // 打印:6 

顺着来可以,倒着当然也行。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. type OrderStatus int 
  6.  
  7. const ( 
  8.   Max = 5 
  9.  
  10. const ( 
  11.   Received OrderStatus = Max - iota // 已收货 5 
  12.   Delivered // 已发货 4 
  13.   PendIng // 未发货 3 
  14. NoPay //未支付 2 
  15.   Cancelled //订单已取消 1 
  16.  
  17. func main() { 
  18.   fmt.Println(Received,Delivered) // 打印:5,4 

还可以使用位运算,比如在 go 源码中的包 sync 中的锁上面有这么一段定义代码。

  1. const ( 
  2.     mutexLocked = 1 << iota  //1<<0 
  3.     mutexWoken //1<<1 
  4.     mutexStarving //1<<2 
  5.     mutexWaiterShift = iota  //3 
  6.  
  7.  
  8. func main() { 
  9.     fmt.Println("mutexLocked的值",mutexLocked) //打印:1 
  10.     fmt.Println("mutexWoken的值",mutexWoken) //打印:2 
  11.     fmt.Println("mutexStarving的值",mutexStarving) //打印:4 
  12.     fmt.Println("mutexWaiterShift的值",mutexWaiterShift) // 打印:3 

也许有人平常是直接定义常量值或者用字符串来表示的。

比如,上面这些我完全可以用 string 来表示,我还真见过用字符串来表示订单状态的。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. const ( 
  6.   Cancelled = "cancelled" 
  7.   NoPay = "noPay" 
  8.   PendIng = "pendIng" 
  9.   Delivered = "delivered" 
  10.   Received = "received" 
  11.  
  12. var OrderStatusMsg = map[string]string{ 
  13.   Cancelled: "订单已取消"
  14.   NoPay: "未付款"
  15.   PendIng: "未发货"
  16.   Delivered: "已发货"
  17.   Received: "已收货"
  18.  
  19. func main() { 
  20.   fmt.Println(OrderStatusMsg[Cancelled]) 

或者直接定义整形常量值。

  1. package main 
  2.  
  3. import "fmt" 
  4.  
  5. const ( 
  6.   Cancelled = 1 
  7.   NoPay = 2 
  8.   PendIng = 3 
  9.   Delivered = 4 
  10.   Received = 5 
  11.  
  12. var OrderStatusMsg = map[int]string{ 
  13.   Cancelled: "订单已取消"
  14.   NoPay: "未付款"
  15.   PendIng: "未发货"
  16.   Delivered: "已发货"
  17.   Received: "已收货"
  18.  
  19. func main() { 
  20.   fmt.Println(OrderStatusMsg[Cancelled]) 

其实上述两种都可以,但是相比之下使用 iota 更有优势。

  • 能保证一组常量的唯一性,人工定义的不能保证。
  • 可以为一组动作分享同一种行为。
  • 避免无效值。
  • 提高代码阅读性以及维护。

延伸

按照上面我们所演示的,最后我们可以这样操作。

  1. package main 
  2.  
  3. import ( 
  4.   "fmt" 
  5.  
  6. type OrderStatus int 
  7.  
  8. const ( 
  9.   Cancelled OrderStatus = iota + 1 //订单已取消 1 
  10.   NoPay //未支付 2 
  11.   PendIng // 未发货 3 
  12.   Delivered // 已发货 4 
  13.   Received // 已收货 5 
  14.  
  15. //公共行为 赋予类型 String() 函数,方便打印值含义 
  16. func (order OrderStatus) String() string { 
  17.   return [...]string{"cancelled""noPay""pendIng""delivered""received"}[order-1] 
  18.  
  19. //创建公共行为 赋予类型 int 函数 EnumIndex() 
  20. func (order OrderStatus) EnumIndex() int { 
  21.   return int(order
  22.  
  23. func main() { 
  24.   var order OrderStatus = Received 
  25.   fmt.Println(order.String()) // 打印:received 
  26.   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

 

责任编辑:武晓燕 来源: 吴亲强的深夜食堂
相关推荐

2022-06-02 08:48:39

Go枚举器Iota

2023-11-30 09:00:00

TypeScript开发

2023-04-13 11:05:10

5G网络无线技术

2020-04-06 14:45:22

云计算边缘计算网络

2022-12-01 14:43:56

物联网智慧城市

2022-08-26 08:00:19

企业架构IT

2019-08-05 08:42:37

物联网IOT技术

2018-09-14 18:00:29

无损网络

2023-09-05 09:49:03

2016-09-27 21:25:08

Go语言Ken Thompso

2012-08-13 09:15:54

Go开发语言编程语言

2016-01-20 09:54:51

微服务架构设计SOA

2021-05-24 11:30:49

智能建筑IOT物联网

2015-11-11 13:35:15

2015-08-03 10:40:45

动效设计优势

2022-01-03 08:06:15

函数Go数据

2022-07-21 08:00:00

人工智能开发机器学习

2021-04-09 09:55:55

DockerGoLinux

2011-06-08 10:30:08

MongoDB

2022-08-31 15:40:13

云原生数据
点赞
收藏

51CTO技术栈公众号