如何以及为什么在 Go 中编写枚举

dust347 · 2021-09-28 00:15:55 · 2414 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2021-09-28 00:15:55 的文章,其中的信息可能已经有所发展或是发生改变。

一个枚举(enum,enumerator 的缩写),是一组命名的常量值。枚举是一个强大 的工具,让开发者可以创建复杂的常量集,而这些常量集有着有用的名称和简单且唯一的取值。

在我们走远之前,我想提一下我最近启动了 Go Mastery,一门动手的 Golang 课程。如果想要了解更多关于 Go 的信息,请尝试下该课程,现在让我们回到枚举上面。

语法示例

在一个常量声明中,iota 关键字创建枚举作为连续的无类型整型常量。

type BodyPart int

const (
    Head BodyPart = iota // Head = 0
    Shoulder             // Shoulder = 1
    Knee                 // Knee = 2
    Toe                  // Toe = 3
)

为什么应该使用枚举?

来看一些关于枚举你可能会有的几个疑问。首先枚举也许看起来没那么有用,但是我向你保证枚举是有用的。

而且,如果想要一个整型常量,就不能用一个普通的 const 吗?比如,const head = 0

可以,可以这么做,但是枚举的强大之处在于将常量聚合在一起并且保证值唯一。通过使用枚举,编译器层面保证了你的常量(比如,HeadShoulderKnee,和 Toe)不会有相同的值。

为什么不直接使用字符串作为唯一值?比如说,const Head = "head" 以及 const Shoulder = "shoulder"

除了编译器无法保证唯一这个老生常谈的回答之外,一个字符串需要更多内存,并且在一些受限的情形下会导致性能问题。如果你有一组4个,10个,甚至100个唯一的值,你真的需要存储整个 string 吗?一个 int 型会占用更少的程序内存。

不仅仅与空间有关系,尤其是在现代硬件十分强大。假如你有类似下面这样的一些配置变量。

const (
    statusSuccess = iota
    statusFailed
    statusPending
    statusComplete
)

假装你需要将 statusFailed 变更为 statusCancelled,以便和其他代码库保持一致。如果你先前没有使用枚举而是使用 failed 这个(字符串类型的)值,而现如今这个值散布在不同数据库中,那变更会变的非常困难。如果你使用的是枚举,你可以修改名字而不需要触及底层的值,你的代码还能保持干净。

从 1 开始枚举

有的时候,如果你是受虐狂,或者如果你是一个 Lua 开发者,希望你的枚举列表从 1 开始而不是从默认的 0 开始,在 Go 中你可以很轻易地实现。

const (
    Head = iota + 1  // 1
    Shoulder         // 2
    Knee             // 3
    Toe              // 4
)

带有乘法的枚举

iota 关键字简单地代表一个自增的整型常量,即在同一个 const 块中每使用一次就会变大的一个数字。你可以对它使用任何你想要使用的数学运算。

const (
    Head = iota + 1      // 0 + 1 = 1
    Shoulder = iota + 2  // 1 + 2 = 3
    Knee = iota * 10     // 2 * 10 = 20
    Toe = iota * 100     // 3 * 100 = 300
)

考虑到这一点,请记住你可以做不代表你应该这样做。

跳过的值的枚举

如果你想要跳过某个值,可以使用 _ 字符,就如同忽略(函数)返回的变量一样。

const (
    Head = iota // Head = 0
    _
    Knee // Knee = 2
    Toe // Toe = 3
)

在 Go 中枚举的 String

Go 对于枚举并没有内置的 string 函数,但是可以很容易地通过实现 String() 。通过使用 String() 方法而非将常量设置为字符串类型,可以使枚举带有“可打印性”从而获得和使用字符串相同的好处。

type BodyPart int

const (
    Head BodyPart = iota // Head = 0
    Shoulder // Shoulder = 1
    Knee // Knee = 2
    Toe // Toe = 3
)

func (bp BodyPart) String() string {
    return [...]string{"Head", "Shoulder", "Knee", "Toe"}
    // 译注:这里应该是 return [...]string{"Head", "Shoulder", "Knee", "Toe"}[bp]
}

但是这个方法存在一些“陷阱”,需要注意。如果 const 块中声明的数量与 String() 方法中所创建的“常量切片”中条目数对不上,编译器并不会警告可能存在的“越界”错误。同样的,如果你曾更新了常量中的某个名称,不要忘记更新列表中对应的字符串。


via: https://qvault.io/golang/golang-enum/

作者:Lane Wagner  译者:dust347  校对:lxbwolf

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2414 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传