在Go中表示枚举的惯用方式是什么?


522

我试图代表一个简化的染色体,该染色体由N个碱基组成,每个碱基只能是的一个{A, C, T, G}

我想用一个枚举来形式化约束,但是我想知道在Go中最惯用的枚举方式是什么。


4
在go标准包中,它们表示为常量。见golang.org/pkg/os/#pkg-constants
丹尼斯·塞居勒


相关的Golang语言
icza

7
@icza在此之前3年提出了这个问题。假设时间箭头处于正常工作状态,则这不可能是该副本的副本。
carbocation '18年

Answers:


658

引用语言规范:Iota

在常量声明中,预声明的标识符iota表示连续的无类型整数常量。每当保留字const出现在源中时,它将重置为0,并在每个ConstSpec之后递增。它可以用来构造一组相关的常量:

const (  // iota is reset to 0
        c0 = iota  // c0 == 0
        c1 = iota  // c1 == 1
        c2 = iota  // c2 == 2
)

const (
        a = 1 << iota  // a == 1 (iota has been reset)
        b = 1 << iota  // b == 2
        c = 1 << iota  // c == 4
)

const (
        u         = iota * 42  // u == 0     (untyped integer constant)
        v float64 = iota * 42  // v == 42.0  (float64 constant)
        w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

在ExpressionList中,每个iota的值都是相同的,因为它仅在每个ConstSpec之后才递增:

const (
        bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
        bit1, mask1                           // bit1 == 2, mask1 == 1
        _, _                                  // skips iota == 2
        bit3, mask3                           // bit3 == 8, mask3 == 7
)

最后一个示例利用了最后一个非空表达式列表的隐式重复。


所以你的代码可能像

const (
        A = iota
        C
        T
        G
)

要么

type Base int

const (
        A Base = iota
        C
        T
        G
)

如果您希望基数是与int分开的类型。


16
很好的例子(我从规格中并未记得确切的iota行为-当它递增时)。我个人喜欢给枚举类型,所以可以将其用作参数,字段等时进行类型检查
。– mna 2013年

16
非常有趣的@jnml。但令我有些
Deleplace

4
Go没有数字子范围类型的概念,例如Pascal的概念,因此Ord(Base)不限0..3于其下限数字类型,但具有相同的限制。这是一种语言设计选择,是安全性和性能之间的折衷。每次触摸Base键入的值时,请考虑“安全”运行时界限检查。还是应该Base为算术和for ++和for 定义值的“溢出”行为--?等等
zzzz

7
为了在jnml上进行补充,甚至在语义上,该语言中没有任何内容表示定义为Base的const表示有效Base的整个范围,而只是说这些特定的const是Base类型的。也可以在其他地方将更多常量定义为Base,并且它甚至不是互斥的(例如const Z Base = 0可以定义并且将是有效的)。
2013年

10
您可以使用iota + 1不从0开始。
MarçalJuan

87

参考jnml的答案,您可以通过根本不导出基本类型(即将其写为小写)来防止基本类型的新实例。如果需要,您可以制作一个具有可返回基本类型的方法的可导出接口。该接口可以在外部处理Base的函数中使用,即

package a

type base int

const (
    A base = iota
    C
    T
    G
)


type Baser interface {
    Base() base
}

// every base must fulfill the Baser interface
func(b base) Base() base {
    return b
}


func(b base) OtherMethod()  {
}

package main

import "a"

// func from the outside that handles a.base via a.Baser
// since a.base is not exported, only exported bases that are created within package a may be used, like a.A, a.C, a.T. and a.G
func HandleBasers(b a.Baser) {
    base := b.Base()
    base.OtherMethod()
}


// func from the outside that returns a.A or a.C, depending of condition
func AorC(condition bool) a.Baser {
    if condition {
       return a.A
    }
    return a.C
}

在主程序包内部a.Baser实际上就像是一个枚举。只有在包中,您才能定义新实例。


10
对于base仅用作方法接收者的情况,您的方法似乎是完美的。如果您的a程序包要公开一个带有type参数的函数base,那么它将很危险。确实,用户可以使用文字值42调用它,函数可以接受base它,因为它可以转换为整数。为了防止这种情况,做出base一个structtype base struct{value:int}。问题:您不能再将基声明为常量,只能将模块变量声明为常量。但是42绝不会强制转换base为该类型。
Niriel

6
@metakeule我试图理解您的示例,但是您对变量名的选择使其变得异常困难。
anon58192932

1
这是我在示例中遇到的麻烦之一。FGS,我知道这很诱人,但不要将变量的名称与类型的名称相同!
Graham Nicholls

26

您可以这样:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

使用此代码,编译器应检查枚举的类型


5
常量通常用普通的驼峰字母写,而不是全部用大写字母写。最初的大写字母表示已导出变量,它可能是您想要的,也可能不是您想要的。
425nesp

1
我注意到在Go源代码中存在一种混合,其中常量有时全部为大写,而有时它们均为驼峰。您有规格参考吗?
杰里米·盖洛

@JeremyGailor我认为425nesp只是指出,正常的偏好是开发人员将它们用作未导出的常量,因此请使用camelcase。如果开发人员确定应将其导出,则可以使用所有大写或大写字母,因为没有确定的首选项。见Golang代码审查的建议,并 在常量有效围棋节
waynethec

有一个偏好。就像变量,函数,类型和其他变量一样,常量名称应该是blendCaps或MixedCaps,而不是ALLCAPS。资料来源:Go Code评论评论
罗道夫·卡瓦略

22

的确,上面使用const和的示例iota是表示Go中原始枚举的最惯用的方式。但是,如果您正在寻找一种创建功能更全面的枚举(类似于您在另一种语言(如Java或Python)中看到的类型)的方法,该怎么办?

创建看起来像Python中的字符串枚举的对象的一种非常简单的方法是:

package main

import (
    "fmt"
)

var Colors = newColorRegistry()

func newColorRegistry() *colorRegistry {
    return &colorRegistry{
        Red:   "red",
        Green: "green",
        Blue:  "blue",
    }
}

type colorRegistry struct {
    Red   string
    Green string
    Blue  string
}

func main() {
    fmt.Println(Colors.Red)
}

假设您还需要一些实用方法,例如Colors.List()Colors.Parse("red")。而且您的颜色更加复杂,需要成为一种结构。然后,您可能会执行以下操作:

package main

import (
    "errors"
    "fmt"
)

var Colors = newColorRegistry()

type Color struct {
    StringRepresentation string
    Hex                  string
}

func (c *Color) String() string {
    return c.StringRepresentation
}

func newColorRegistry() *colorRegistry {

    red := &Color{"red", "F00"}
    green := &Color{"green", "0F0"}
    blue := &Color{"blue", "00F"}

    return &colorRegistry{
        Red:    red,
        Green:  green,
        Blue:   blue,
        colors: []*Color{red, green, blue},
    }
}

type colorRegistry struct {
    Red   *Color
    Green *Color
    Blue  *Color

    colors []*Color
}

func (c *colorRegistry) List() []*Color {
    return c.colors
}

func (c *colorRegistry) Parse(s string) (*Color, error) {
    for _, color := range c.List() {
        if color.String() == s {
            return color, nil
        }
    }
    return nil, errors.New("couldn't find it")
}

func main() {
    fmt.Printf("%s\n", Colors.List())
}

在这一点上,确保它可以正常工作,但是您可能不喜欢必须重复定义颜色的方式。如果此时您想消除这种情况,可以在结构上使用标签并做一些花哨的反射来设置它,但是希望这足以覆盖大多数人。


19

从Go 1.4开始,该go generate工具已与stringer命令一起引入,该命令使您的枚举易于调试和打印。


您是否知道相反的解决方案。我的意思是字符串-> MyType。由于单向解决方案远非理想。是执行我想要的工作的人-但手工书写很容易出错。
SR

11

我相信我们在这里有很多好的答案。但是,我只是想增加使用枚举类型的方式

package main

import "fmt"

type Enum interface {
    name() string
    ordinal() int
    values() *[]string
}

type GenderType uint

const (
    MALE = iota
    FEMALE
)

var genderTypeStrings = []string{
    "MALE",
    "FEMALE",
}

func (gt GenderType) name() string {
    return genderTypeStrings[gt]
}

func (gt GenderType) ordinal() int {
    return int(gt)
}

func (gt GenderType) values() *[]string {
    return &genderTypeStrings
}

func main() {
    var ds GenderType = MALE
    fmt.Printf("The Gender is %s\n", ds.name())
}

到目前为止,这是我们可以创建枚举类型并在Go中使用的惯用方法之一。

编辑:

添加使用常量枚举的另一种方法

package main

import (
    "fmt"
)

const (
    // UNSPECIFIED logs nothing
    UNSPECIFIED Level = iota // 0 :
    // TRACE logs everything
    TRACE // 1
    // INFO logs Info, Warnings and Errors
    INFO // 2
    // WARNING logs Warning and Errors
    WARNING // 3
    // ERROR just logs Errors
    ERROR // 4
)

// Level holds the log level.
type Level int

func SetLogLevel(level Level) {
    switch level {
    case TRACE:
        fmt.Println("trace")
        return

    case INFO:
        fmt.Println("info")
        return

    case WARNING:
        fmt.Println("warning")
        return
    case ERROR:
        fmt.Println("error")
        return

    default:
        fmt.Println("default")
        return

    }
}

func main() {

    SetLogLevel(INFO)

}

2
您可以使用字符串值声明常量。如果您打算显示它们并且实际上不需要数字值,则IMO会更容易做到这一点。
cbednarski '19

4

这是一个有许多枚举的示例。它使用Golang中的结构,并利用面向对象的原理将它们整齐地捆绑在一起。添加或删除新枚举时,基础代码都不会更改。该过程是:

  • 定义一个枚举结构 enumeration items以下对象EnumItem。它具有整数和字符串类型。
  • 定义 enumeration为以下列表enumeration items枚举
  • 枚举的生成方法。其中包括一些:
    • enum.Name(index int):返回给定索引的名称。
    • enum.Index(name string):返回给定索引的名称。
    • enum.Last():返回上一个枚举的索引和名称
  • 添加您的枚举定义。

这是一些代码:

type EnumItem struct {
    index int
    name  string
}

type Enum struct {
    items []EnumItem
}

func (enum Enum) Name(findIndex int) string {
    for _, item := range enum.items {
        if item.index == findIndex {
            return item.name
        }
    }
    return "ID not found"
}

func (enum Enum) Index(findName string) int {
    for idx, item := range enum.items {
        if findName == item.name {
            return idx
        }
    }
    return -1
}

func (enum Enum) Last() (int, string) {
    n := len(enum.items)
    return n - 1, enum.items[n-1].name
}

var AgentTypes = Enum{[]EnumItem{{0, "StaffMember"}, {1, "Organization"}, {1, "Automated"}}}
var AccountTypes = Enum{[]EnumItem{{0, "Basic"}, {1, "Advanced"}}}
var FlagTypes = Enum{[]EnumItem{{0, "Custom"}, {1, "System"}}}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.