有没有一种方法可以遍历作为枚举类型使用的常量?

36

我正在尝试在Go语言中使用枚举,以下是我的代码。我无法找到一种简单的方法来遍历常量值列表。在golang中,遍历用作枚举的常量值的常见做法是什么?谢谢!

type DayOfWeek int
const(
       Monday DayOfWeek = iota
       Tuesday
       Wednesday
       Thursday
       Friday
       Saturday
       Sunday
      )
在Java中,我们可以按如下方式进行迭代。
public enum DayOfWeek {
    MONDAY, 
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

for (DayOfWeek day: DayOfWeek.values()) {
   // code logic
}

8
首先,所有Go标识符通常采用[MixedCaps](https://github.com/golang/go/wiki/CodeReviewComments#mixed-caps)格式,包括常量。您可以使用简单的循环,例如 for d := Monday; d <= Sunday; d++ {} ,其中您明确知道起始/结束值,或者您可以为其添加别名(例如,像 firstDay = Monday; lastDay = Sunday 这样)。尽管可能会更进一步制作出使用range的东西,但在我看来这绝对不值得。 - Dave C
顺便提一下,已经有一个 time.Weekday 可以从 time.Sundaytime.Saturday。当然,你只是恰好将一周的天数作为任意示例,这并不重要。 - Dave C
Dave,感谢您的回复。我已经更正了常量的大小写。我想了解枚举类型,不仅仅是星期几。firstDay和lastDay对我来说很有趣。它们应该放在常量列表中还是作为外部变量?谢谢! - Jack_125
1
@DaveC:你的评论可以作为一个很好的答案。 - Vitor De Mario
2
对于我需要可迭代的枚举,通常会添加一个虚拟的结束标记。以一周中的某日为例,我的代码会有像for i := Monday; i < LastDay; i++ {这样的循环。这样,如果万一需要在一周中添加第八天,只需将其插入到虚拟结束标记LastDay之前即可。这是在不支持枚举迭代内置支持的语言(如C、Go等)中常见的模式。 - wldsvc
显示剩余2条评论
5个回答

28

在运行时,无法直接枚举命名类型的值/实例,无论是变量还是常量,除非您明确定义一个列出它们的切片。这取决于枚举类型的定义者或用户。

package main

import (
    "fmt"
    "time"
)

var Weekdays = []time.Weekday{
    time.Sunday,
    time.Monday,
    time.Tuesday,
    time.Wednesday,
    time.Thursday,
    time.Friday,
    time.Saturday,
}

func main() {
    for _, day := range Weekdays {
            fmt.Println(day)
    }
}

为了在运行时动态生成此列表,例如通过反射,链接器必须保留所有包中定义的符号,就像Java一样。 golang-nuts小组讨论了这个问题,涉及从包导出的名称和函数,是包常量定义的超集。https://groups.google.com/forum/#!topic/golang-nuts/M0ORoEU115o 如果程序引用了它,语言可以包括用于在编译时生成此列表的语法糖。但是迭代顺序应该是什么?如果你的一周从星期一开始,我定义的列表就没什么用了;你将不得不定义自己的切片来遍历从星期一到星期日的天数。

1

使用 stringer 更可取,它可以帮助你使用代码生成器保持你的代码库最新。不幸的是,stringer 并不总是生成映射表。

对于任何有兴趣继续使用 go 生成器的人,我编写了一个名为 enumall 的小代码生成器。它会为每个提供的类型生成一个文件,变量中包含给定类型的所有值。

通过像这样向你的代码添加代码生成器注释来使用它:

//go:generate go run github.com/tomaspavlic/enumall@latest -type=Season

type Season uint8

const (
    Spring Season = 1 << iota
    Summer
    Autumn
    Winter
)

您可以在此处找到更多信息:https://github.com/tomaspavlic/enumall


1
的评论非常好。当您具有按一递增的计数器时,这非常完美。
You could either do a simple loop such as for d := Monday; d <= Sunday; d++ {}

我遇到了一个不断跳动的常量(1、2、4、8、16等):

const (
    Approved = 1 << iota
    AlreadyApproved
    NotApproved
    OldTicket
    Unknown
)

我避免使用`range`函数,而是使用左移一位的方法来遍历我的常量:
var score Bits
score = Set(score, AlreadyApproved)

for i := Approved; i < Unknown; i = i << 1 {
    fmt.Println(i)
}

输出:

1
2
4
8
16

1
你可以不使用反射来实现。
首先,在编译时使用Go工具Stringer,通过go generate执行。这将创建一个文件[filename]_string.go,其中包含一个映射_[structname]_map,该映射引用枚举值并将其作为字符串表示。该映射是私有的,因此只需在包初始化时将其分配给公共映射即可。
var EnumMap map[Enum]string

func init() {
    EnumMap = _Enum_map
}

type Enum uint

//go:generate go run golang.org/x/tools/cmd/stringer -type=Enum

const (
    One Enum = iota
    Two
)

然后,您只需循环遍历该映射的键即可。

很好的想法,但可悲的是,当我尝试这样做时,生成的代码无法构建地图。显然,只有当存在10个或更多的“运行”(连续值集)时才会发生这种情况。 - Jeff Learman

0
for i := 0; i <= int(Sunday); i++ {
    fmt.Println(DayOfWeek(i))
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接