不得不知道的golang知识点之nil

golang中的nil,不少人都误觉得与Java、PHP等编程语言中的null同样。可是实际上Golang的niu复杂得多了,若是不信,那咱们继续往下阅读。golang

nil 为预声明的标示符,定义在builtin/builtin.go编程

// nil is a predeclared identifier representing the zero value for a// pointer, channel, func, interface, map, or slice type.// Type must be a pointer, channel, func, interface, map, or slice typevar nil Type 

// Type is here for the purposes of documentation only. It is a stand-in// for any Go type, but represents the same type for any given function// invocation.type Type int

nil的零值

按照Go语言规范,任何类型在未初始化时都对应一个零值:布尔类型是false,整型是0,字符串是"",而指针、函数、interface、slice、channel和map的零值都是nil。编程语言

PS:这里没有说结构体struct的零值为nil,由于struct的零值与其属性有关ide

nil没有默认的类型,尽管它是多个类型的零值,必须显式或隐式指定每一个nil用法的明确类型。函数

package main

func main() {

	// 明确.
	_ = (*struct{})(nil)
	_ = []int(nil)
	_ = map[int]bool(nil)
	_ = chan string(nil)
	_ = (func())(nil)
	_ = interface{}(nil)

	// 隐式.
	var _ *struct{} = nil
	var _ []int = nil
	var _ map[int]bool = nil
	var _ chan string = nil
	var _ func() = nil
	var _ interface{} = nil
}

若是关注过golang关键字的同窗就会发现,里面并无nil,也就是说nil并非关键字,那么就能够在代码中定义nil,那么nil就会被隐藏。布局

package mainimport "fmt"func main() {	
        nil := 123
	fmt.Println(nil) // 123
	var _ map[string]int = nil //cannot use nil (type int) as type map[string]int in assignment
}

nil类型的地址和值大小

nil类型的全部值的内存布局始终相同,换一句话说就是:不一样类型nil的内存地址是同样的。ui

package main
import (
	"fmt"
)
func main() {
	var m map[int]string
	var ptr *int
	var sl []int
	fmt.Printf("%p\n", m)       //0x0
	fmt.Printf("%p\n", ptr )    //0x0
	fmt.Printf("%p\n", sl )     //0x0
}

业务中通常将nil值表示为异常。nil值的大小始终与其类型与nil值相同的non-nil值大小相同。所以, 表示不一样零值的nil标识符可能具备不一样的大小。lua

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var p *struct{} = nil
	fmt.Println( unsafe.Sizeof( p ) ) // 8

	var s []int = nil
	fmt.Println( unsafe.Sizeof( s ) ) // 24

	var m map[int]bool = nil
	fmt.Println( unsafe.Sizeof( m ) ) // 8

	var c chan string = nil
	fmt.Println( unsafe.Sizeof( c ) ) // 8

	var f func() = nil
	fmt.Println( unsafe.Sizeof( f ) ) // 8

	var i interface{} = nil
	fmt.Println( unsafe.Sizeof( i ) ) // 16
}

大小是编译器和体系结构所依赖的。以上打印结果为64位体系结构和正式 Go 编译器。对于32位体系结构, 打印的大小将是一半。spa

对于正式 Go 编译器, 同一种类的不一样类型的两个nil值的大小始终相同。例如, 两个不一样的切片类型 ( []int和[]string) 的两个nil值始终相同。指针

nil值比较

1.不一样类型的nil是不能比较的。

package main
import (
	"fmt"
)
func main() {
	var m map[int]string
	var ptr *int
	fmt.Printf(m == ptr) //invalid operation: m == ptr (mismatched types map[int]string and *int)
}

在 Go 中, 两个不一样可比较类型的两个值只能在一个值能够隐式转换为另外一种类型的状况下进行比较。具体来讲, 有两个案例两个不一样的值能够比较:

  • 两个值之一的类型是另外一个的基础类型。

  • 两个值之一的类型实现了另外一个值的类型 (必须是接口类型)。

nil值比较并无脱离上述规则。

package main
import (
	"fmt"
)
func main() {
	type IntPtr *int
	fmt.Println(IntPtr(nil) == (*int)(nil))			//true
	fmt.Println((interface{})(nil) == (*int)(nil))	//false
}

2.同一类型的两个nil值可能没法比较 由于golang中存在map、slice和函数类型是不可比较类型,它们有一个别称为不可比拟的类型,因此比较它们的nil亦是非法的。

package main
import (
	"fmt"
)
func main() {
	var v1 []int = nil
	var v2 []int = nil
	fmt.Println(v1 == v2)
	fmt.Println((map[string]int)(nil) == (map[string]int)(nil))
	fmt.Println((func())(nil) == (func())(nil))
}

不可比拟的类型的值缺是能够与“纯nil”进行比较。

package main
import (
	"fmt"
)
func main() {
	fmt.Println((map[string]int)(nil) == nil)  //true
	fmt.Println((func())(nil) == nil)		   //true
}

3.两nil值可能不相等

若是两个比较的nil值之一是一个接口值, 而另外一个不是, 假设它们是可比较的, 则比较结果老是 false。缘由是在进行比较以前, 接口值将转换为接口值的类型。转换后的接口值具备具体的动态类型, 但其余接口值没有。这就是为何比较结果老是错误的。

package main
import (
	"fmt"
)
func main() {
	fmt.Println( (interface{})(nil) == (*int)(nil) ) // false
}

常见问题

1.函数返回

func nilReturn() (string,error)  {

	return nil,nil  //cannot use nil as type string in return argument
}

由于error是接口类型因此error类型没有报错。

2.map的nil key map的key为指针、函数、interface、slice、channel和map,则key能够为nil。

package main
import (
	"fmt"
)
func main() {
	mmap := make(map[*string]int,4)
	a:="a"
	mmap[&a] = 1
	mmap[nil] = 99
	fmt.Println(mmap)   //map[0xc042008220:1 <nil>:99]
}

总结

nil之因此比较难以理解由于咱们常常混淆了nil值和nil类型,但愿各位同窗细细品味其中区别。

相关文章
相关标签/搜索