nil是什么
在Go语言中,布尔类型的"0"(初始值)为false,数值类型的"0"为0,字符串类型的"0"为空字符串"",而指针/切片/映射/通道/函数和接口的"0"即为nil
nil不是关键字
func main() {
var _ map[string]int = nil
nil := 123
fmt.Println(nil)
var coverNil map[string]int = map[string]int{"str": nil}
fmt.Println(coverNil)
}
var nil = new(int)
func main() {
var p *int
if p == nil {
fmt.Println("p is nil")
} else {
fmt.Println("p is not nil")
}
}
类似的还有
func main() {
true := false
fmt.Println(true)
}
nil的意义是什么
- 当变量的类型为指针时,nil表示该指针不指向任何值,即不能从指针中取值。
- 当变量的类型为切片时,nil表示该切片没有backing array(不知道怎么翻译好),即切片的长度和容量都为0,系统没有为该切片分配存储空间。
- 当变量的类型为映射/通道/函数时,nil表示该变量未初始化。未初始化的映射只能读不能写。未初始化的通道只能读不能写,且读会造正阻塞。未初始化的函数不能被调用。
- 当变量的类型为接口时,nil表示该变量没有值,也不能是一个空指针。接口由两部分组成,一部分是类型,另一部分是值,只有当类型和值都为nil时,该变量才等于nil。
func main() {
x := interface{}(nil)
y := (*int)(nil)
a := y == x
b := y == nil
_, c := x.(interface{})
println(a, b, c)
}
参考答案及解析:false true false 类型断言语法:i.(Type),其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
这里的 x 的动态类型是 *int
,所以 x 不为 nil
nil有什么用
nil指针
和其他语言稍微不同的是,Go语言的函数接收器(receiver)允许nil的存在,即下面的代码可以编译通过:
func (p *Person) SayHi(){
fmt.Println("Hi")
}
var p *Person
p.SayHi() // print "Hi"
这个特性让我们无需在每次调用方法前判断指针是否为nil,如:
type node struct {
value int
next *node
}
func (n *node) Sum() int {
s := 0
if n.next != nil {
s = n.next.Sum()
}
return n.value + s
}
func main() {
var n *node
if n != nil {
n.Sum()
}
}
这个特性让我们无需在每次调用方法前判断指针是否为nil,如:
type node struct {
value int
next *node
}
func (n *node) Sum() int {
if n == nil {
return 0
}
return n.value + n.next.Sum()
}
func main() {
var n *node
n.Sum()
}
nil切片
nil切片是长度和容量都为0的切片,在使用中如果没有必要,我们完全可以不初始化nil切片,因为nil切片也有切片的功能,如:
var ss []string // nil切片
len(ss) // 0
cap(ss) // 0
for s := range ss // 迭代0次
ss[i] // panic:index out of range
nil切片还可以直接append数据,如:
func main() {
var ss []string
ss = append(ss, "hello world") // ss ["hello world"]
}
在使用切片时可以放心地使用nil切片而不需要担心其容量问题,因为它的每次重分配容量都是倍增的。即nil切片的第一次append,会重分配一个容量为1的切片。而后会分配容量为2/4/8/16这样倍增的数值,所以长度为1000的切片也只是重分配了10次。如果切片的内存重分配确实影响了应用的性能,那可以考虑声明一个具有一定容量的切片,否则,使用nil切片,因为他们通常足够快。
Empty切片
var emptySlice []int
emptySlice = []int{}
len(emptySlice) // 0
cap(emptySlice) // 0
for s := range emptySlice // 迭代0次
nil映射(map)
nil映射是指未初始化的映射,其长度为0,可读但不可写,如:
func main() {
var m map[string]string
fmt.Println("len:", len(m)) // 0
for key, value := range m { // 迭代0次
fmt.Println("aa", key, value)
}
val, ok := m["key"] // "",false
fmt.Println(ok, val)
m["key"] = "value" // panic: assignment to entry in nil map
}
nil映射用在只读的地方非常方便,假设有一个创建Get请求的函数
func NewGet(url string, headers map[string]string) (*http.Request, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
return req, nil
}
如果你不想设置该请求的header,那你只需要传入nil:
NewGet("http://google.com", nil)
nil通道
nil通道是未初始化的通道,当尝试写入或者读取时,会永久阻塞,且无法被close。
nil函数
由于在Go语言中,函数可以作为结构体的域(Field)存在,所以必须为其设置一个初始值,那就是nil:
type Foo struct {
f func() error // 初始值为nil
}
nil函数可以用于懒加载或者执行默认操作,如:
func NewServer(logger func(string, ...interface{})) {
if logger == nil {
logger = log.Printf // 使用默认logger
}
logger("init ... ")
}
nil接口
nil接口最为常用的场景是作为一个信号,想必写过很多Go代码的已经见过无数次了,如:
if err != nil {
...
}