Go中的零检测


165

我在Go中看到很多检测nil的代码,如下所示:

if err != nil { 
    // handle the error    
}

但是,我有一个这样的结构:

type Config struct {
    host string  
    port float64
}

而config是Config的实例,当我这样做时:

if config == nil {
}

有编译错误,说:无法将nil转换为Config类型


3
我不明白为什么port是float64类型的?
阿拉明'18

2
不应该这样 Go的JSON api将JSON中的任意数字导入float64,我必须将float64转换为int。
钱琛

Answers:


179

编译器将错误指向您,您正在比较结构实例和nil。它们的类型不同,因此将其视为无效的比较并大吼大叫。

您要在此处执行的操作是将指向您的配置实例的指针与nil进行比较,这是有效的比较。为此,您可以使用new内置的golang 或初始化指向它的指针:

config := new(Config) // not nil

要么

config := &Config{
                  host: "myhost.com", 
                  port: 22,
                 } // not nil

要么

var config *Config // nil

然后,您将能够检查

if config == nil {
    // then
}

5
我猜var config &Config // nil应该是:var config *Config
Tomasz Plonka

var config *Config崩溃invalid memory address or nil pointer dereference。也许我们需要var config Config
kachar

我明白这种选择背后的原因可能不是你的,但它使没有给我感觉,“如果!(配置!=无)”是有效的,但是,“如果配置==无”不是。两者都在相同结构和非结构之间进行比较。
反击

@retorquere它们都无效,请参见play.golang.org/p/k2EmRcels6。是'!='还是'=='都没有区别;确实有所不同的是config是结构还是结构指针。
stewbasic

我认为这是错误的,因为它总是错误的:play.golang.org/p/g-MdbEbnyNx
Madeo

61

除了Oleiade之外,请参见零值规范

当通过声明或调用make或new分配内存以存储值时,没有提供任何显式初始化,则为该内存提供默认初始化。此值的每个元素的类型均设置为零:布尔值为false,整数值为0,浮点数为0.0,字符串为“”,指针,函数,接口,切片,通道和映射为nil。该初始化是递归完成的,因此,例如,如果未指定任何值,则结构数组的每个元素的字段都将为零。

如您所见,nil不是每种类型的零值,而是指针,函数,接口,片,通道和映射的零值。这就是为什么config == nil不是错误 的原因&config == nil

要检查你的结构是未初始化你必须检查每一个成员提供其相应的零值(例如host == ""port == 0等)或具有通过内部初始化方法设置一个私有字段。例:

type Config struct {
    Host string  
    Port float64
    setup bool
}

func NewConfig(host string, port float64) *Config {
    return &Config{host, port, true}
}

func (c *Config) Initialized() bool { return c != nil && c.setup }

4
除此之外,这就是为什么time.Time要有一种IsZero()方法的原因。但是你也可以这样做var t1 time.Time; if t1 == time.Time{},你可以同时做if config == Config{}检查所有的现场为你(结构平等是顺利的定义)。但是,如果您有很多字段,效率就不高。而且,零值也许是一个合理且可用的值,所以传入一个并没有什么特别的。
Dave C

1
如果访问了Config作为指针,则初始化功能将失败。可以将其更改为func (c *Config) Initialized() bool { return !(c == nil) }
Sundar,

在这种情况下,@ Sundar可能很方便,因此我应用了更改。但是,通常我不希望方法调用的接收端检查自身是否为nil,因为这应该是调用者的工作。
nemo

16

我创建了一些示例代码,这些代码使用我能想到的各种方式来创建新变量。看起来前三种创建值的方式,后两种创建引用。

package main

import "fmt"

type Config struct {
    host string
    port float64
}

func main() {
    //value
    var c1 Config
    c2 := Config{}
    c3 := *new(Config)

    //reference
    c4 := &Config{}
    c5 := new(Config)

    fmt.Println(&c1 == nil)
    fmt.Println(&c2 == nil)
    fmt.Println(&c3 == nil)
    fmt.Println(c4 == nil)
    fmt.Println(c5 == nil)

    fmt.Println(c1, c2, c3, c4, c5)
}

输出:

false
false
false
false
false
{ 0} { 0} { 0} &{ 0} &{ 0}

6

您也可以检查struct_var == (struct{})。这不允许您与nil进行比较,但是会检查它是否已初始化。使用此方法时要小心。如果您的结构的所有字段的都可以为零,那么您将不会花费很多时间。

package main

import "fmt"

type A struct {
    Name string
}

func main() {
    a := A{"Hello"}
    var b A

    if a == (A{}) {
        fmt.Println("A is empty") // Does not print
    } 

    if b == (A{}) {
        fmt.Println("B is empty") // Prints
    } 
}

http://play.golang.org/p/RXcE06chxE


3

语言规范中提到的比较操作符的行为:

比较运算符

在任何比较中,第一个操作数必须可分配给第二个操作数的类型,反之亦然。


可分配性

在以下任何一种情况下,值x都可以分配给类型T的变量(“ x可以分配给T”):

  • x的类型与T相同。
  • x的类型V和T具有相同的基础类型,并且V或T中的至少一个不是命名类型。
  • T是接口类型,并且x实现T。
  • x是双向通道值,T是通道类型,x的类型V和T具有相同的元素类型,并且V或T中的至少一个不是命名类型。
  • x是预声明的标识符nil,T是指针,函数,切片,映射,通道或接口类型。
  • x是可由类型T的值表示的无类型常量。

0

在Go 1.13和更高版本中,您可以使用包中Value.IsZero提供的方法reflect

if reflect.ValueOf(v).IsZero() {
    // v is zero, do something
}

除基本类型外,它还适用于Array,Chan,Func,Interface,Map,Ptr,Slice,UnsafePointer和Struct。请参阅作为参考。

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.