nil is a predefined identifier in the Go language, representing a null or zero value. In the Go language, the zero value (initial value) of the Boolean type is false, the zero value of the numeric type is 0, the zero value of the string type is the empty string "''", and the pointer, slice, map, channel, function The zero value of the interface is nil. nil is not a keyword or reserved word. Pointers to nil of different types are the same. Nil of different types cannot be compared, and nil values of the same type may not be compared.
The operating environment of this tutorial: Windows 7 system, GO version 1.18, Dell G3 computer.
In the Go language, the zero value (initial value) of the Boolean type is false, the zero value of the numeric type is 0, and the zero value of the string type The zero value is the empty string "", and the zero value for pointers, slices, maps, channels, functions, and interfaces is nil.
nil is a predefined identifier in the Go language. Developers who have experience in developing other programming languages may regard nil as null (NULL) in other languages. In fact, this is not completely correct. Yes, because there are many differences between nil in Go language and null in other languages.
Go language nil feature
nil identifiers cannot be compared
nil is not critical Words or reserved words
The pointers of different types of nil are the same
The pointers of different types of nil cannot be compared
Two nil values of the same type may not be compared
nil is the zero value of a common reference type
I believe it Programmers who have written Golang are very familiar with the following piece of code:
if err != nil { // do something.... }
When it is not equal to nil
, it means that some error has occurred, and we need to correct this error Perform some processing, and if it is equal to nil
, it means the operation is normal. So what is nil
? Check the dictionary and you will know that nil
means nothing, or a zero value. Zero value, zero value, does it sound familiar? In the Go language, if you declare a variable but do not assign a value to it, the variable will have a default value of zero. This is the corresponding zero value for each type:
bool -> false numbers -> 0 string -> "" pointers -> nil slices -> nil maps -> nil channels -> nil functions -> nil interfaces -> nil
For example, when you define a struct:
type Person struct { AgeYears int Name string Friends []Person } var p Person // Person{0, "", nil}
The variablep
is only declared but not assigned, so All fields of p have corresponding zero values. So, what exactly is this nil
? Go's documentation says that nil is a predefined identifier that represents the zero value of a pointer, channel, function, interface, mapping or slice , which is a predefined variable:
type Type int var nil Type
Are you a little surprised? nil
is not one of the keywords of Go. You can even change the value of nil
yourself:
var nil = errors.New("hi")
This is completely compilable, but in the end It's better not to do it like this.
After understanding what nil
is, let’s talk about the use of nil
.
pointers
var p *int p == nil // true *p // panic: invalid memory address or nil pointer dereference
The pointer represents the address pointing to the memory. If a nil pointer is dereferenced, panic will occur. So what is the use of a pointer that is nil
? Let’s first look at an example of calculating the sum of binary trees:
type tree struct { v int l *tree r *tree } // first solution func (t *tree) Sum() int { sum := t.v if t.l != nil { sum += t.l.Sum() } if t.r != nil { sum += t.r.Sum() } return sum }
The above code has two problems, one is code duplication:
if v != nil { v.m() }
and the other is when t
is nil
will panic:
var t *tree sum := t.Sum() // panic: invalid memory address or nil pointer dereference
How to solve the above problem? Let's first look at an example of a pointer receiver:
type person struct {} func sayHi(p *person) { fmt.Println("hi") } func (p *person) sayHi() { fmt.Println("hi") } var p *person p.sayHi() // hi
For the method of the pointer object, it can be called even if the value of the pointer is nil
. Based on this, we can Let’s modify the example of calculating the sum of binary trees just now:
func(t *tree) Sum() int { if t == nil { return 0 } return t.v + t.l.Sum() + t.r.Sum() }
Is it much simpler compared to the code just now? For nil
pointers, you only need to make a judgment before the method and it will be ok. There is no need to make repeated judgments. It is the same when printing the value of a binary tree or searching for a certain value in a binary tree:
func(t *tree) String() string { if t == nil { return "" } return fmt.Sprint(t.l, t.v, t.r) } // nil receivers are useful: Find func (t *tree) Find(v int) bool { if t == nil { return false } return t.v == v || t.l.Find(v) || t.r.Find(v) }
So if it is not necessary, do not use NewX() to initialize the values, but use their default values.
slices
// nil slices var s []slice len(s) // 0 cap(s) // 0 for range s // iterates zero times s[i] // panic: index out of range
A slice of nil
, except that it cannot be indexed, other operations are possible , when you need to fill in the value, you can use the append
function, and the slice will automatically expand. So what is the underlying structure of the slice that is nil
? According to the official documentation, slice has three elements, namely length, capacity, and pointer to the array:
When there are elements:
所以我们并不需要担心slice的大小,使用append的话slice会自动扩容。(视频中说slice自动扩容速度很快,不必担心性能问题,这个值得商榷,在确定slice大小的情况只进行一次内存分配总是好的)
map
对于Go来说,map,function,channel都是特殊的指针,指向各自特定的实现,这个我们暂时可以不用管。
// nil maps var m map[t]u len(m) // 0 for range m // iterates zero times v, ok := m[i] // zero(u), false m[i] = x // panic: assignment to entry in nil map
对于nil
的map,我们可以简单把它看成是一个只读的map,不能进行写操作,否则就会panic。那么nil
的map有什么用呢?看一下这个例子:
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 }
对于NewGet
来说,我们需要传入一个类型为map的参数,并且这个函数只是对这个参数进行读取,我们可以传入一个非空的值:
NewGet("http://google.com", map[string]string{ "USER_AGENT": "golang/gopher", },)
或者这样传:
NewGet("http://google.com", map[string]string{})
但是前面也说了,map的零值是nil
,所以当header
为空的时候,我们也可以直接传入一个nil
:
NewGet("http://google.com", nil)
是不是简洁很多?所以,把nil
map作为一个只读的空的map进行读取吧。
channel
// nil channels var c chan t <- c // blocks forever c <- x // blocks forever close(c) // panic: close of nil channel
关闭一个nil
的channel会导致程序panic
(如何关闭channel可以看这篇文章:如何优雅地关闭Go channel)举个例子,假如现在有两个channel负责输入,一个channel负责汇总,简单的实现代码:
func merge(out chan<- int, a, b <-chan int) { for { select { case v := <-a: out <- v case v := <- b: out <- v } } }
如果在外部调用中关闭了a或者b,那么就会不断地从a或者b中读出0,这和我们想要的不一样,我们想关闭a和b后就停止汇总了,修改一下代码:
func merge(out chan<- int, a, b <-chan int) { for a != nil || b != nil { select { case v, ok := <-a: if !ok { a = nil fmt.Println("a is nil") continue } out <- v case v, ok := <-b: if !ok { b = nil fmt.Println("b is nil") continue } out <- v } } fmt.Println("close out") close(out) }
在知道channel关闭后,将channel的值设为nil,这样子就相当于将这个select case子句停用了,因为nil
的channel是永远阻塞的。
interface
interface并不是一个指针,它的底层实现由两部分组成,一个是类型,一个值,也就是类似于:(Type, Value)。只有当类型和值都是nil
的时候,才等于nil
。看看下面的代码:
func do() error { // error(*doError, nil) var err *doError return err // nil of type *doError } func main() { err := do() fmt.Println(err == nil) }
输出结果是false
。do
函数声明了一个*doErro
的变量err
,然后返回,返回值是error
接口,但是这个时候的Type已经变成了:(*doError,nil),所以和nil
肯定是不会相等的。所以我们在写函数的时候,不要声明具体的error变量,而是应该直接返回nil
:
func do() error { return nil }
再来看看这个例子:
func do() *doError { // nil of type *doError return nil } func wrapDo() error { // error (*doError, nil) return do() // nil of type *doError } func main() { err := wrapDo() // error (*doError, nil) fmt.Println(err == nil) // false }
这里最终的输出结果也是false
。为什么呢?尽管wrapDo
函数返回的是error
类型,但是do
返回的却是*doError
类型,也就是变成了(*doError,nil),自然也就和nil
不相等了。因此,不要返回具体的错误类型。遵从这两条建议,才可以放心地使用if x != nil
。
看完了那个视频,发现nil
还有这么多用处,真是意外之喜。
油管上面还有很多干货满满的视频,可以多学习学习咯。
The above is the detailed content of What is nil in go language. For more information, please follow other related articles on the PHP Chinese website!