Golang iota
7.3 用于枚举的 iota
许多编程语言中都提供了枚举类型(Enum),用以表示一组固定的值。Go 语言中没有内置的枚举类型,但提供了 iota,它允许为一组常量赋予递增的整数值。
iota 的最佳用法是首先定义一个基于 int 的新类型,该类型将表示所有有效值。例如:
type MailCategory int // 基于 int 类型,声明 MailCategory 类型
然后,用 const 关键字为新类型定义一组值:
const (
Uncategorized MailCategory = iota // 第一个常量,显式指定类型并赋值为 iota
Personal // 第二个常量,自动继承类型和递增的 iota 值
Spam // 第三个常量,以此类推
Social
Advertisements
)
在 Go 的 const 块中,第一个常量会显式指定类型并赋值为 iota,而后续的常量如果未指定类型和值,编译器会自动复用第一个常量的类型,并依次赋予递增的 iota 值(从 0 开始)。这意味着第一个常量(Uncategorized)被赋值为 0,第二个常量(Personal)被赋值为 1,以此类推。每当遇到一个新的 const 块时,iota 会重置为 0。
在 const 块中,iota 的值会为每一个常量自动递增,无论该常量是否直接使用了 iota(如下所示):
const (
Field1 = 0 // 显式赋值为 0,此时 iota = 0(但未使用)
Field2 = 1 + iota // 显式赋值,此时 iota = 1
Field3 = 20 // 显式赋值为20,此时 iota = 2(但未使用)
Field4 // 未赋值,继承上一行的 20,此时 iota = 3
Field5 = iota // 显式使用 iota,此时 iota = 4
)
func main() {
fmt.Println(Field1, Field2, Field3, Field4, Field5)
}
输出结果(出乎意料)为:
0 2 20 20 4
Field2 = 2的原因:iota在每个const块中从 0 开始计数,在第二行(Field2)时,iota值为 1,表达式1 + iota即为1+1=2。Field4 = 20的原因:const块中,如果某行(Field4)没有显式赋值,会完全复制上一行(Field3)的值和类型。所以,Field4 自动继承了 Field3 的值和类型,即 20。Field5 = 4的原因:在const块中,iota计数会为每个常量自动递增(不受中间显式赋值的影响)。即第一行iota=0(Field1)、iota=1(Field2),以此类推。当遇到 Field5 时,iota已递增到 4。
在
const块中,iota的行为遵循以下规则:
iota的计数是基于行号的,从 0 开始- 即使中间有显式赋值(如
Field3=20),iota仍然会继续递增- 未赋值的常量会自动继承前一个常量的值和类型
iota 的最佳实践
当常量值由外部规范明确规定时,应避免使用
iota,直接赋值即可。例如:实现协议、标准或第三方接口时,应直接赋值(如:const StatusOK = 200)。
iota应仅用于内部场景——即常量通过名称(而非具体数值)被引用的场合。如此,你既能充分享受iota的便利,又能随时在列表中插入新常量,而不用担心破坏现有代码。——Danny van Heumen
在 Go 语言中,没有任何机制能阻止你(或其他人)为已定义的类型创建额外值。这意味着,如果定义了一个类型,并且类型中用 iota 枚举了一组常量,则你或其他人仍然可以创建这个类型的其他值,即使这些值不在枚举列表中。如果你在一个使用 iota 枚举的常量列表中插入一个新的标识符,所有后续的标识符都会被重新编号。这是因为 iota 枚举是基于常量的位置来生成值的,而不是基于常量的名称。这种重新编号可能会导致问题,特别是当这些常量代表了另一个系统或数据库中的值时。例如,如果你有一个与数据库表中的记录相对应的枚举,插入一个新标识符可能会导致枚举值与数据库中的值不一致,从而破坏应用程序。
鉴于上述两个限制,iota 枚举主要适用于当你只需要区分一组值,而不关心这些值的具体数值的情况。例如,如果你有一组状态值(如“开始”、“进行中”、“完成”),并且你只关心这些状态之间的区别,而不关心它们的具体数值,那么使用 iota 枚举是合适的。如果实际值很重要,你应该明确指定这些值,而不是使用 iota 枚举。例如,如果你有一组与外部系统或数据库中的特定值相对应的常量,你应该直接指定这些值,以确保它们与外部系统或数据库中的值保持一致。
由于可以将字面量表达式赋值给常量,将看到许多示例代码建议在如下这种情况下使用
iota:type BitField int const ( Field1 BitField = 1 << iota // assigned 1 Field2 // assigned 2 Field3 // assigned 4 Field4 // assigned 8 )虽然这很巧妙,但在使用这种编程模式时要小心。如果使用这种模式,请添加足够的注释以描述代码逻辑。如前所述,当关心常量的具体值时,使用
iota是脆弱的。例如,未来的维护者在列表中间插入一个新的常量,将会破坏应用程序。
请注意,iota 从 0 开始编号。如果使用一组常量来表示不同的配置状态,则零值(Zero Value)可能很有用。例如,前文中的 MailCategory 类型。当邮件首次到达时,它是未分类的,所以零值是有意义的。如果常量没有合理的默认值,一个常见的编程模式是将常量代码块中的第一个 iota 值赋给 _,或者赋给一个表示“值无效”的常量。这样可以轻松检测变量何时未正确初始化。
评论