golang枚举的实践、原理与拓展
今天因为需求,好好研究了一下golang
的枚举体系。网上的文章缺乏系统性,走了一些弯路。因此我自己整理一下。
golang存在枚举吗
如果你希望找到c++
那样的强枚举,那么你会非常失望。因为go
不仅没有强枚举,连弱枚举也没有。只有通过自增长常量实现的伪弱枚举。
通常会写成如下形式。
//ColumnType 列类型
type ColumnType int
const (
CtNone ColumnType = iota //无效
CtInt //整数
CtString //文本
CtFloat //小数
CtEnum //枚举
CtBitEnum //位枚举
CtVecDataPKey //主枚举列
CtVecDataCKey //子枚举列
CtVecDataValue //数据列
CtRealEnd //真实类型结束
CtLogicBegin = 100 //逻辑类型 为了避免增减枚举导致的版本不对应绕过此处,因此特殊处理一下
CtData = iota + CtLogicBegin - CtRealEnd - 1 // 压缩过后的逻辑数据
CtAttrs // 属性集
)
一般如下使用
for i, col := range logic.Columns {
// 非复杂类型
switch col.ExcelType {
case CtVecDataPKey, CtVecDataCKey:
continue
case CtVecDataValue:
if i == len(logic.Columns)-1 ||
(logic.Columns[i+1].ExcelType != CtVecDataPKey &&
logic.Columns[i+1].ExcelType != CtVecDataCKey) {
excel.ColumnTypes = append(excel.ColumnTypes, CtData)
}
case CtBitEnum:
excel.ColumnTypes = append(excel.ColumnTypes, CtAttrs)
default:
excel.ColumnTypes = append(excel.ColumnTypes, col.ExcelType)
}
}
和弱枚举的功能和效果一样,同时也具备弱枚举的缺点,你无法避免用户传入不合理的值例如 CtEnumType(1111)
。但是通过合理的case
和if
的判断是可以避免的。下文也会提到。
如何使用枚举
容错
必须要将默认值设为无效,如果你使用int
作为枚举,那么应该手动制定0为无效值。避免忘记对枚举赋值,而产生意外的结果。
CtNone ColumnType = iota //无效
需要指定有效区域,对区域外的枚举过滤掉。
CtInt //整数
CtString //文本
CtFloat //小数
CtEnum //枚举
CtBitEnum //位枚举
CtVecDataPKey //主枚举列
CtVecDataCKey //子枚举列
CtVecDataValue //数据列
CtRealEnd //真实类型结束
if cType == CtNone || cType >= CtRealEnd {
return
}
使用iota
iota 是go提供的枚举语法糖,可以在const
定义块中提供以某种规律连续分布的值。
最常见的使用方法是
const (
a = iota
b
c
d
)
此时,abcd的值依次为0、1、2、3。
也可以将iota
放在公式里面,值也会顺延
a = iota * 2
b
c
d
此时abcd的值依次为0、2、4、6
通过这个方式我们可以实现位枚举
a = 1 << iota
b
c
d
值依次为 1、2、4、8。
itoa
可以在过程中被重新指定公式,但是值还是连续分布的
a = iota
b
c
d
e = 1 << iota
值依次为 0、1、2、3、16
有些人可能会想,我们能不能和c的枚举一样跳过某些值或者指定某一个枚举后,枚举又从另外一个值开始呢。于是写出了以下c风格代码。
a = iota
b
c
d = 100 // 以下是另外一组
e
f
实际上值依次为0、1、2、100、100、100。是的,后面三个枚举的值都是100,同时goland
也无法识别出值对应的枚举。于是有人尝试了以后写成了
a = ioat
b
c
d = 100 + ioat // 以下是另外一组
e
f
这个代码是可以正常运作的,但是无法以你想象的方式运行。实际上值依次为0、1、2、103、104、105。本质上来看itoa
的值没有重新指定。此时goland
也无法将103识别为d,不过我个人认为这个是goland
的bug。
正确的写法应该是
a = ioat
b
c
normalEnd
otherBegin = 100 + ioat - normalEnd // 以下是另外一组
d
e
此时的逻辑是正确的
abcde的值依次为0、1、2、101、102,就是有那么一点扭曲。
同时go
还提供了一个小语法糖 _
a = ioat
_
_
b
此时ab的值依次为0、3。
代码规范
我个人建议为了避免造成误解,枚举变量以e开头。枚举值以枚举类型的缩写开头例如。
var eType ColumnType = CtInt
这和指针需要以p
开头、接口以i
开头一样,看上去是过时的匈牙利命名法。但是在go
这个自由且泛用的环境下可以避免很多问题。