零值
golang在声明一个变量时,每个类型都会有一个默认零值,例如:
- 对于值类型:布尔类型为 false, 数值类型为 0,字符串为"",数组和结构会递归初始化其元素或字段,即其初始值取决于元素或字段。
- 对于引用类型: 均为 nil,包括指针 pointer,函数 function,接口 interface,切片 slice,管道 channel,映射 map。
因此在声明一个变量绑定数据类型后,没有设置其初始值时,该变量都会有一个默认值,为该类型的零值
空值 nil
nil 是 Golang 中预先声明的标识符(非关键字保留字),其主要用来表示引用类型的零值(指针,接口,函数,映射,切片和通道),表示它们未初始化的值fagn
// [src/builtin/builtin.go](https://golang.org/src/builtin/builtin.go#L98)
//
// 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
nil 是 Golang 中唯一没有默认类型的非类型化的值,它不是一个未定义的状态。所以你不能像这样使用它:
a := nil // cannot declare variable as untyped nil: a
零值切片var zeroSlice = make([]int, 0) fmt.Println(zeroSlice) // [] fmt.Println(zeroSlice == nil) // false fmt.Println(len(zeroSlice)) // 0 fmt.Println(cap(zeroSlice)) // 0
nil切片var nilSlice []int fmt.Println(nilSlice) // [] fmt.Println(nilSlice == nil) // true fmt.Println(len(nilSlice)) // 0 fmt.Println(cap(nilSlice)) // 0
nil切片是一个未初始化的切片变量,而零值切片已经初始化了,只是内部没有任何元素
空结构体
空结构是没有任何字段的结构类型,例如:
type Q struct{}
var q struct{}
iface和eface
iface 和 eface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}。
iface
iface的源码如下
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}
iface内部维护了两个指针:
tab指向一个itab实例,它表示接口的类型以及赋给这个接口的实体类型
data 则指向接口具体的值,一般而言是一个指向堆内存的指针
在itab结构体中:
_type 字段描述了实体的类型,包括内存对齐方式,大小等
inter 字段则描述了接口的类型
fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。fun的数组大小为1,这里存储的是第一个方法的函数指针,一个接口一般会有多个方法,会在它之后的内存空间里面存储
interfacetype 类型,它描述的是接口的类型:
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
_type :实际上是描述 Go 语言中各种数据类型的结构体
mhdr 字段:表示接口所定义的函数列表
pkgpath: 记录定义了接口的包名
eface
eface的源码如下:
type eface struct {
_type *_type
data unsafe.Pointer
}
相比 iface,eface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。
结构体omitempty的应用
定义结构体 CAPIBaseResponse
type CAPIBaseResponse struct { Code int `json:"Code"` Status string `json:"Status"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` }
给该结构体赋一个空值func main() { data := "" capi := new(CAPIBaseResponse) json.Unmarshal([]byte(data), &capi) addressBytes, _ := json.MarshalIndent(capi, "", " ") fmt.Printf("%s\n", string(addressBytes)) }
打印结果:{ "Code": 0, "Status": "", "Msg": "", "RequestId": "" }
修改结构体,给字段加上omitemptytype CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` }
打印结果:{ "Msg": "", "RequestId": "" }
当添加上omitempty属性后,如果赋的是字段类型的默认空值,会隐藏该字段
修改结构体:
type CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` Coordinate coordinate `json:"coordinate,omitempty"` } type coordinate struct { Lat float64 `json:"latitude"` Lng float64 `json:"longitude"` }
继续赋空值,打印结果{ "Msg": "", "RequestId": "", "coordinate": { "latitude": 0, "longitude": 0 } }
可以发现,给coordinate添加上omitempty,但是其结构体字段没添加,依旧会显示。
继续修改结构体
type CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` Coordinate coordinate `json:"coordinate,omitempty"` } type coordinate struct { Lat float64 `json:"latitude,omitempty"` Lng float64 `json:"longitude,omitempty"` }
打印结果:{ "Msg": "", "RequestId": "", "coordinate": {} }
给其结构体的每个字段都加上omitempty,依旧会显示"coordinate":{},因为golang不知道这个结构体的“空值”
继续修改结构体
type CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` Coordinate *coordinate `json:"coordinate,omitempty"` } type coordinate struct { Lat float64 `json:"latitude"` Lng float64 `json:"longitude"` }
打印结果:{ "Msg": "", "RequestId": "" }
将Coordinate定义为指针类型后,golang就知道一个指针的“空值”是多少,因此隐藏了Coordinate,否则golang是不知道我们定义的结构体的”空值“。
将结构体修改为
type CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` Coordinate *coordinate `json:"coordinate,omitempty"` } type coordinate struct { Lat float64 `json:"latitude,omitempty"` Lng float64 `json:"longitude,omitempty"` }
修改赋值代码,如果给field赋的值正好是该类型的默认空值时func main() { data := `{ "coordinate": { "latitude": 0.0, "longitude": 0.0 } }` capi := new(CAPIBaseResponse) json.Unmarshal([]byte(data), &capi) addressBytes, _ := json.MarshalIndent(capi, "", " ") fmt.Printf("%s\n", string(addressBytes)) }
打印结果:{ "Msg": "", "RequestId": "", "coordinate": {} }
结构体的字段被隐藏了,因为赋的值刚好为float64类型的默认空值,但是依旧会显示"coordinate": {},因为不是nil。
继续修改结构体
type CAPIBaseResponse struct { Code int `json:"Code,omitempty"` Status string `json:"Status,omitempty"` Msg string `json:"Msg"` RequestId string `json:"RequestId"` Coordinate *coordinate `json:"coordinate,omitempty"` } type coordinate struct { Lat *float64 `json:"latitude,omitempty"` Lng *float64 `json:"longitude,omitempty"` }
打印结果:{ "Msg": "", "RequestId": "", "coordinate": { "latitude": 0, "longitude": 0 } }
将Lat和Lng改为指针类型后,即使赋的是float64的默认空值,也会正常显示,因为*float64的默认空值为nil
修改赋值
func main() { data := "" capi := new(CAPIBaseResponse) json.Unmarshal([]byte(data), &capi) addressBytes, _ := json.MarshalIndent(capi, "", " ") fmt.Printf("%s\n", string(addressBytes)) }
打印结果:{ "Msg": "", "RequestId": "" }
因为赋的是nil,是指针的空值,所以被隐藏了。
总结
- 在判断变量是否为nil时,需要先弄清变量的类型,对于值类型,声明后都会有一个默认零值,因此不会为nil,对于引用类型,需要先判断其是否初始化,只有当类型和值同时为空时,才为nil(比如 var nilSlice []int 就是一个nil切片,而 var zeroSlice = make([]int, 0) 就不是nil切片)
- iface和eface都是 Go 中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface描述的接口不包含方法;
- 在结构体的json中使用omitempty时,一定要清楚定义的类型的默认空值,只有当其为默认空值时才会省略,比如int类型,当值为0时就会省略,如果改成*int,当值为nil时才会省略,为0依旧会正常显示;对于子结构体,非引用类型类型,golang并不知道其“空值”,即使没有赋值也会显示空结构体,修改为引用类型后,其空值就为nil,在没有赋值的时候就不会显示了。