文档章节

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

梦朝思夕
 梦朝思夕
发布于 2017/10/30 11:37
字数 1363
阅读 583
收藏 7

golang中的nil,很多人都误以为与Java、PHP等编程语言中的null一样。但是实际上Golang的niu复杂得多了,如果不信,那我们继续往下阅读。

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 type
var 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的零值与其属性有关

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 main

import "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的内存地址是一样的。

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标识符可能具有不同的大小。

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位体系结构, 打印的大小将是一半。

对于正式 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类型,希望各位同学细细品味其中区别。

© 著作权归作者所有

梦朝思夕

梦朝思夕

粉丝 103
博文 28
码字总数 56485
作品 2
朝阳
高级程序员
私信 提问
golang: 类型转换和类型断言

类型转换在程序设计中都是不可避免的问题。当然有一些语言将这个过程给模糊了,大多数时候开发者并不需要去关注这方面的问题。但是golang中的类型匹配是很严格的,不同的类型之间通常需要手动...

陈亦
2014/01/20
0
8
golang: 详解interface和nil

golang的nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。nil是预先说明的标识符,也即通常意义上的关键字。在golang中,nil只能赋值给指针、channel、func、interfa...

陈亦
2014/01/19
0
29
golang: Martini之inject源码分析

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。在传统的程序设计过程中,调用者是自己来决定使用哪些被调用者实现的。但是在依赖注入模式中,创建被调用者的...

陈亦
2014/01/22
0
21
Golang Channel用法简编

在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面,国内go程序员与爱好者们可以到"Golang中 国",即g...

nop4ss
2015/07/23
177
0
TODO:Golang UDP连接简单测试慎用Deadline

TODO:Golang UDP连接简单测试慎用Deadline UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的...

OneTODO
2016/11/10
77
0

没有更多内容

加载失败,请刷新页面

加载更多

约瑟夫环(报数游戏)java实现

开端 公司组织考试,一拿到考题,就是算法里说的约瑟夫环,仔细想想 以前老师将的都忘了,还是自己琢磨把~ package basic.gzy;import java.util.Iterator;import java.util.LinkedList;...

无极之岚
25分钟前
0
0
Kernel字符设备驱动框架

Linux设备分为三大类:字符设备,块设备和网络设备,这三种设备基于不同的设备框架。相较于块设备和网络设备,字符设备在kernel中是最简单的,也是唯一没有基于设备基础框架(device结构)的...

yepanl
今天
3
0
Jenkins 中文本地化的重大进展

本文首发于:Jenkins 中文社区 我从2017年开始,参与 Jenkins 社区贡献。作为一名新成员,翻译可能是帮助社区项目最简单的方法。 本地化的优化通常是较小的改动,你无需了解项目完整的上下文...

Jenkins中文社区
昨天
4
0
Spring中如何使用设计模式

关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,工厂方法模式以及Builder模式。 1. 策略模式 关于策略模式的使用方式,在S...

爱宝贝丶
昨天
4
0
前后端分离-前端搭建(vue)

前端使用vue,那么怎么搭建vue呢 先安装nodejs以及npm 现在基本的nodejs都包含有npm,下载安装后, 可以在cmd命令里输入 node -v 和npm -v 分别查看安装的版本 两个都显示了版本就是安装ok ...

咸鱼-李y
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部