Golang nil的妙用

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 {
  ...
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值