如果你从其它的高级语言转到Golang,你可能会对Golang中的零值、空值感到疑惑。很多面向对象的语言中,如果一个对象为空,那么这个对象的值就为Null或者None,但是在Golang中并不如此。
零值是指当你声明变量(分配内存)并未显式初始化时,始终为你的变量自动设置一个默认初始值的策略。
一、值类型与引用类型的零值
Golang官网对零值有如下定义:
When storage is allocated for a variable, either through a declaration or a call of
new
, or when a new value is created, either through a composite literal or a call ofmake
, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type:false
for booleans,0
for numeric types,""
for strings, andnil
for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.
可以得出:
对于值类型:布尔类型为 false
, 数值类型为 0
,字符串为 ""
,数组和结构会递归初始化其元素或字段,即其初始值取决于元素或字段。
对于引用类型: 均为 nil
,包括指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map。
值类型的空值我们很好理解(注意,字符串string类型在Golang中为值类型),引用类型的空值nil,则和其它语言的空值(例如Java中null,Python中的None)就有很大的不同之处。
nil的特殊含义
在Golang中,nil是一个预声明的标识符,它代表了指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map没有被初始化是的默认值。nil在builtin.go中的定义如下
// nil is a predeclared identifier representing the zero value for a// pointer, channel, func, interface, map, or slice type.
var nil Type// Type must be a pointer, channel, func, interface, map, or slice 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甚至不是Golang的关键词,只是一个变量名,这和其它语言中的null,None有本质的不同,我们甚至可以自己声明一个nil,覆盖预定义的nil(当然肯定不推荐这样做)。
nil与类型转换的比较
上面提到过,引用类型的零值为nil,但是引用类型这么多种,只用一个nil怎么可以代表这么多类型,事实上,一个nil也代表不了这么多类型。
例如下面的例子:
type A interface{}
type B struct{}
var a *B
print(a == nil) //true
print(a == (*B)(nil)) //true
print((A)(a) == (*B)(nil)) //true
print((A)(a) == nil) //false
print(a == nil) // true
这里的 a
是一个 *B
类型的指针。由于 a
被声明为 var a *B
,它的初始值是 nil
。因此,a == nil
的结果是 true
,因为 a
确实是 nil
。
print(a == (*B)(nil)) // true
(*B)(nil)
是将 nil
转换为 *B
类型的指针。由于 a
是 *B
类型的指针,(*B)(nil)
和 a
都是 *B
类型的 nil
指针,因此它们相等。结果是 true
。
print((A)(a) == (*B)(nil)) // true
这里,(A)(a)
是将 a
转换为 interface{}
类型。由于 a
是 *B
类型的 nil
指针,这个转换后的 interface{}
值也是 nil
(因为一个 nil
指针赋值给 interface{}
时,interface{}
的内部类型和值都会是 nil
)。所以 (A)(a)
和 (*B)(nil)
都表示 nil
值,因此它们相等。结果是 true
。
print((A)(a) == nil) // false
这里 (A)(a)
是将 a
转换为 interface{}
类型。在 Go 语言中,当 nil
的指针被转换为 interface{}
时,interface{}
的值是 nil
,但类型部分不为 nil
。因此,虽然 (A)(a)
的值是 nil
,但它的类型部分是 *B
,这使得 (A)(a)
不等于 nil
(nil
在这里仅指值部分,而 (A)(a)
包含一个具体的类型 *B
)。因此,结果是 false
。
对于第三点,可以在这里明确一下:
print((A)(a) == (*B)(nil)) // true
这行代码涉及到类型转换和 interface{}
类型的行为。在 Go 语言中,interface{}
类型可以持有任何类型的值,但它有两个部分:值部分和类型部分。让我们一步步拆解:
a
的类型和值
你有一个 *B
类型的指针变量 a
,并且 a
是 nil
。这意味着 a
的值部分是 nil
,而它的类型是 *B
。
(A)(a)
的转换
(A)(a)
是将 a
转换为 interface{}
类型。转换后的 interface{}
值包含两个部分:
- 值部分:
a
的值,即nil
。 - 类型部分:
a
的类型,即*B
。
因此,(A)(a)
是一个 interface{}
类型的值,其中的值部分是 nil
,而类型部分是 *B
。
(*B)(nil)
的意义
(*B)(nil)
是将 nil
转换为 *B
类型的指针。它表示一个 *B
类型的 nil
指针。
- 比较
(A)(a)
和(*B)(nil)
当你比较 (A)(a)
和 (*B)(nil)
时,你实际上是在比较 interface{}
类型的值 (A)(a)
和 *B
类型的 nil
指针。
(A)(a)
表示一个interface{}
值,其值部分是nil
,类型部分是*B
。(*B)(nil)
表示一个*B
类型的nil
指针。
在 Go 中,interface{}
类型的值可以持有任何类型的值,并且它会将 nil
的指针和 interface{}
的 nil
处理成等价的情况。所以当比较 (A)(a)
和 (*B)(nil)
时,它们的值部分都为 nil
,虽然 interface{}
内部包含了类型信息(*B
),但是由于值部分是 nil
,所以它们被认为是相等的。
因此,(A)(a) == (*B)(nil)
返回 true
。
方法接收者为nil时的行为
当方法接收者为nil时,仍然可以访问对应的方法。偶尔可以帮忙减少代码量。虽然根据方法的写法,是有可能panic的。 比如
type A []int
func (a A) Get() int {
return a[0]
}
func main() {
var a Aa.Get()
}
这个代码是可以编译通过的,但是运行时会panic, runtime error: index out of range [0] with length 0。
结语
在Golang中,理解零值和nil的概念对于编写健壮的代码至关重要。虽然Golang的零值处理与其它语言有很大不同,但掌握了这些基础概念后,你将能够更有效地处理未初始化变量和避免常见的编程错误。通过深入理解值类型和引用类型的零值行为,你可以确保代码在各种情况下都能稳定运行,减少潜在的运行时问题。希望本文对你理解Golang的零值机制有所帮助。
延展阅读:
淘宝京东电商客服如何更好地催单、跟单提升询单转化率和顾客体验?
如何使用Ollama与AnythingLLM零成本搭建本地知识库?
咨询方案 获取更多方案详情