如下所示,我们定义了三个常量来表示状态语义的列举值
const (StatusSuccess = 0StatusFailed = 1StatusForbidden = 2)
在Golang项目开拓中我们就可以直策应用上面的办法来代表我们的自定义列举语义。 不雅观察上面的示例以及结合日常的开拓履历,可以得到我们的列举值常日便是一组连续的数字。
以是在Golang中增加了一个iota关键字来简化上面的写法,使我们的代码更加简洁,如下所示

const (StatusSuccess = iota // 0StatusFailed // 1StatusForbidden // 2)
上面的代码与我们之前直接定义常量是等效的,这里的iota相称于一个从0开始的累加器,每一个列举值都会按照顺序依次加1, 从而接管了我们手动管理列举值的事情,使代码更加简洁。
换一种更加随意马虎理解的说法,可以把这一组列举项当作一个数组凑集,这里的iota可以理解为每个列举项所在的索引值。 以是上述代码与下面的代码也是等效的。
const (StatusSuccess = iota // 0StatusFailed = iota // 1StatusForbidden = iota // 2)
所以为了保持代码的简洁,我们只须要保留第一个iota即可。
自定义列举值常日情形下,我们的列举值都是从0开始依次递增的,但是有少数情形下,我们也会有须要自定义列举值的需求。
比如,我须要列举值从1开始,而不是0,就可以像下面这么写
const (StatusSuccess = iota + 1 // 1StatusFailed // 2StatusForbidden // 3)
上面的代码可能会让你看着有点迷惑,那我们把代码补全再看,可能会更清晰些
const (StatusSuccess = iota + 1 // 1StatusFailed = iota + 1 // 2StatusForbidden = iota + 1 // 3)
这下是不是就很随意马虎理解了,上面两段代码是等效的,每一个没有被显式赋值的列举项,都会依次往上探求最近的一个显示定义的列举项, 利用它的列举值表达式作为当前列举项的值,也便是iota + 1。
由于iota等价于列举项的索引位置,以是上述示例代码的列举值会涌现从1开始逐步递增的效果。
那么举一反三一下,如果我们须要将每个列举值之间的增长步长由1改为2该怎么实现呢?
聪明的你肯定已经想到了,没错便是将第一个值设置为iota 2即可,如下所示
const (StatusSuccess = iota 2 // 0StatusFailed // 2StatusForbidden // 4)
那如果我们将第一个列举值设置为一个常量的话会涌现什么情形呢?
参考前文所说的,我们很随意马虎就能得出答案: 每一个没有被显式赋值的列举项,都会依次往上探求最近的一个显示定义的列举项,利用它的列举值表达式作为当前列举项的值。
没错,假设我们将第一个列举值设置为1,那么后续没有显式赋值的列举都会参照第一个列举的表达式,也便是字面量1,如下所示:
const (StatusSuccess = 1 // 1StatusFailed // 1StatusForbidden // 1)
至此,你该当对Golang的列举以及iota表达的含义有了比较清晰的认识了, 虽然一开始可能会有点含糊,但是如果你亲自考试测验了所有示例,所有迷惑都会轻易解开。
跳过某个列举值在实际开拓过程中,我们在定义列举的时候,可能须要为程序预留一些列举值,暂时还不会利用,此时我们该怎么办呢?
常日我们可能会先任意定义一批列举值,并加上一些注释,见告开拓者这是预留的,请暂时不要利用之类的。
显而易见,这是比较糟糕的做法,由于并不是所有人都会负责的去看注释并严格的去遵守相应的规则。
万幸的是,Golang的开拓者已经想到了这一点,并为我们供应了一个极其大略的办法来处理这个问题。
我们可以利用_作为一个列举项来表示一个占位,它会拥有自己的列举数值,但是任何人都不能通过它的名称来进行调用, 也便是说列举项_对任何人都是不可见的,以是它可以多次利用,如下所示:
const (StatusSuccess = iota // 0StatusFailed // 1StatusForbidden // 2_ // 3_ // 4)
妙哉,没有比这更大略的了吧?
列举的常用老例由于Golang的列举是用数字常量来实现的,以是它携带的信息非常少,我们很难明得某一个列举数值代表着什么含义, 并且我们也很难去限定列举的取值范围。
自定义列举类型所以为了使列举更加好用,能够携带更多的信息,我们常用的一个做法因此int作为根本类型来自定义一个列举类型, 从而为其扩展更多的功能,如下所示
type Status int
此时我们用自定义的类型Status来创建列举,如下所示
const (Success Status = iota // 0Failed // 1Forbidden // 2_ // 3_ // 4)
从上面可以看到,比拟之前的例子,唯一的差异便是为第一个列举指定了一个显式类型Status,如果未指定类型,则它的默认类型是int。
此时所有列举项的类型都变成Status了,由于Status的根本类型是int,以是之前的规则依旧有效,完备与之前所说的相兼容。
列举边界其余,在利用列举的过程中,我们常常会通过一些边界值来判断列举值的有效性, 比如判断用户传入的列举值是否小于列举定义的最小值或者大于列举定义的最大值,如果不符合条件,则传入的列举不合法。
亦或者在一批列举值中存在着更加细分语义的分组,比如有一批状态码表示不同的成功语义,另一批状态码表示不同的失落败语义等等。
一种常见的做法便是直策应用对应的边界值来进行判断,但是这样存在一个问题,由于随着列举值的增加或者减少,边界值可能发生变革。
比如我今后增加了一个列举,那么当前列举的最大值就变成新增的这个列举了,以是校验列举值有效性的方法就必须得修正了。
诸如此类,由于列举项的变革,很多校验逻辑都会随之变革,对代码来说是一种潜在的风险,我们很可能会忘却修正某个边界判断逻辑。
以是聪明的开拓者就想了一个办法,便是通过在列举中定义一些私有的列举项来作为列举的边界值, 也便是将列举项定义为以小写字母或者_开头的常量,这样外部包就无法看到和利用这些私有的边界值了。
在我们当前包的代码中,就可以通过这些边界值来掌握我们上述碰着的边界判断问题,而且当我们的列举项发生增减的时候, 我们的边界逻辑也不须要随之变更,完全示例如下所示
const (minStatus Status = iota // 0Success // 1Failed // 2Forbidden // 3_ // 4_ // 5maxStatus // 6)
fmt.Stringer接口
由于列举归根到底实在是一个整型数字,以是在实际运行的时候,比如在打印日志时,我们很难明得详细的数字代表着什么含义, 如果每次都要去查看相应的文档可真的太费劲了。
我们可以利用fmt.Printf来考试测验打印列举的信息,这里我们之前定义的列举值边界就派上用场了,可以用它来循环遍历所有的列举项
func main() {for status := minStatus; status <= maxStatus; status++ {fmt.Printf("%d -> %v\n", status, status)}}
输出结果如下,可以看到根据打印结果很难判断其内在的含义
0 -> 01 -> 12 -> 23 -> 34 -> 45 -> 56 -> 6
幸好Golang为我们供应了一个fmt.Stringer接口,我们只要实现这个接口,当我们利用fmt包进行格式化输出的时候, 就会调用对应的方法输出更加有用的自定义信息。
fmt.Stringer接口中只有一个String() string方法,我们只要实现该方法即可, 如下所示,我们通过当前的列举值直接返回其对应的描述信息
func (s Status) String() string {switch s {case Success:return "Success"case Failed:return "Failed"case Forbidden:return "Forbidden"default:return "Unknown"}}
末了我们再次运行我们的代码,可以看到从输出结果中很随意马虎就能知道每个列举值的详细含义了
0 -> Unknown1 -> Success 2 -> Failed 3 -> Forbidden4 -> Unknown 5 -> Unknown 6 -> Unknown
小结
通过本章的学习,我们理解了Golang中列举的用法,它不像常规的编程措辞一样拥有独立的列举类型,而是通过常量合营上iota 关键字来实现列举的功能。
经由上述一系列的优化,我们的列举功能已经越来越完善了,当然这都不是必须的, 开拓者可以根据实际的需求来定制自己的列举写法,可以利用最精简的写法来定义列举,也可以为其添加更加强大的功能, 所有的选择权都在开拓者自己手中。