如何检查空结构?

How to check for an empty struct?

我定义了一个结构...

type Session struct {
    playerId string
    beehive string
    timestamp time.Time
}

有时我会为它分配一个空会话(因为 nil 是不可能的)

session = Session{};

然后我要检查,如果它是空的:

if session == Session{} {
     // do stuff...
}

显然这是行不通的。怎么写?

您可以使用 == 与零值复合文字进行比较,因为所有字段都是 comparable:

if (Session{}) == session  {
    fmt.Println("is zero value")
}

playground example

由于 parsing ambiguity,if 条件中的复合文字周围需要括号。

上面==的使用适用于所有字段都是comparable的结构。如果结构包含不可比较的字段(切片、映射或函数),则必须将这些字段与它们的零值一一比较。

比较整个值的另一种方法是比较必须在有效会话中设置为非零值的字段。例如,如果玩家 ID 在有效会话中必须为 != "",则使用

if session.playerId == "" {
    fmt.Println("is zero value")
}

使用reflect.deepEqual also works,尤其是当你在结构中有地图时

package main

import "fmt"
import "time"
import "reflect"

type Session struct {
    playerId string
    beehive string
    timestamp time.Time
}

func (s Session) IsEmpty() bool {
  return reflect.DeepEqual(s,Session{})
}

func main() {
  x := Session{}
  if x.IsEmpty() {
    fmt.Print("is empty")
  } 
}

这里还有 3 个建议或技巧:

有附加字段

您可以添加一个附加字段来判断该结构是已填充还是为空。我故意将它命名为 ready 而不是 empty 因为 bool 的零值是 false,所以如果你创建一个像 Session{} 这样的新结构它的 ready 字段将自动 false 并且它会告诉你真相:结构尚未准备好(它是空的)。

type Session struct {
    ready bool

    playerId string
    beehive string
    timestamp time.Time
}

初始化结构时,必须将 ready 设置为 true。不再需要您的 isEmpty() 方法(尽管您可以根据需要创建一个),因为您可以只测试 ready 字段本身。

var s Session

if !s.ready {
    // do stuff (populate s)
}

这个额外的 bool 字段的重要性随着结构变大或包含不可比较的字段(例如切片、map 和函数值)而增加。

使用现有字段的零值

这与之前的建议类似,但它使用现有字段的零值,当结构不为空时,该字段被视为 无效。这个的可用性取决于实现。

例如,如果在您的示例中您的 playerId 不能为空 string "",您可以使用它来测试您的结构是否为空,如下所示:

var s Session

if s.playerId == "" {
    // do stuff (populate s, give proper value to playerId)
}

在这种情况下,值得将此检查合并到 isEmpty() 方法中,因为此检查依赖于实现:

func (s Session) isEmpty() bool {
    return s.playerId == ""
}

并使用它:

if s.isEmpty() {
    // do stuff (populate s, give proper value to playerId)
}

使用指向你的结构的指针

第二个建议是使用指向您的结构的指针:*Session。指针可以有 nil 个值,所以你可以测试它:

var s *Session

if s == nil {
    s = new(Session)
    // do stuff (populate s)
}

请记住,对于指向结构的指针,您必须取消对变量的引用,而不是将其与指向空结构的指针进行比较:

session := &Session{}
if (Session{}) == *session {
    fmt.Println("session is empty")
}

检查此 playground

同样在这里你可以看到一个包含 属性 的结构,它是一个指针片段,不能以相同的方式进行比较...

作为其他答案的替代方案,如果您通过 case 语句而不是 if 来执行此操作,则可以使用与您最初预期的方式类似的语法来执行此操作:

session := Session{}
switch {
case Session{} == session:
    fmt.Println("zero")
default:
    fmt.Println("not zero")
}

playground example

只是快速补充一下,因为我今天解决了同样的问题:

在 Go 1.13 中可以使用新的 isZero() 方法:

if reflect.ValueOf(session).IsZero() {
     // do stuff...
}

我没有测试它的性能,但我想这应该比通过 reflect.DeepEqual() 进行比较更快。

@cerise-limón 的 是最好的。但是,如果您收到如下 struct containing ... cannot be compared 错误。

type Friends struct {
    nice []string
    mute []string
}

type Session struct {
    playerId  string
    beehive   string
    timestamp time.Time
    friends   Friends
}

func main() {
    session := Session{}
    if (Session{}) == session  {
        fmt.Println("zero")
    }
}

// Output: ./prog.go:23:17: invalid operation: Session{} == session (struct containing Friends cannot be compared)

以上 Friends' 对象导致错误。

修复它的简单方法是定义为字段中的指针。

type Friends struct {
    nice []string
    mute []string
    
}

type Session struct {
    playerId  string
    beehive   string
    timestamp time.Time
    friends   *Friends // <--- here
}

func main() {
    session := Session{}
    if (Session{}) == session  {
        fmt.Println("zero")
    }
}

// Output: zero

使用 fmt.Sprintf 可以帮助检测 struct 是否为空。当它为空时,输出为 { }.

此建议对于使用 JSON 以及 struct 没有特定名称的情况很有用。

if fmt.Sprintf("%v", session) == "{ }" {
    // session is empty
}