日常使用中,var a []int
和 a := make([]int, 0)
的用法是比较频繁的,多数情况下两者可以互换,一般来说如果接下来的操作是append的话,两者几乎没有区别。
日常使用时的影响
- 从日常实践角度来说,两者在使用时影响最大的区别是在json序列化时,前者会序列化为
null
,这个在前端解析中会异常;后者解析为[]
,如果涉及到前端交互时,两种用法还是得区分下 - 判断问题,首先两者是不等的;其次在判断是否空时,建议统一使用len(a)==0做判断
不同的原因
下面对这两种情况为什么会有如此区别做解释,我们都知道slice在golang中内存结构如下
type slice struct {
array unsafe.Pointer // 指向具体的数据地址
len int // 切片数量
cap int // 切片容量
}
那么我们就看看两种情况下对应的字段值是啥。编写测试代码(代码见文末)打印两种情况下相应内存字段值如下图所示
可以看到第二种情况下array字段指向一个地址,这个地址比较特殊,凡是所有的空slice都是指向这个地址,凡是所有的空结构体指针也是指向这个地址,这个地址其实是一个全局变量zerobase
,在runtime.malloc.go
文件中定义的
// runtime/malloc.go
var zerobase uintptr
...
// 零内存空间分配时代码
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zerobase)
}
...
其存在于地址中0xc000092e88
中
测试代码
type slice struct {
array unsafe.Pointer
len int
cap int
}
type empty struct{}
func main() {
var a []int // a=nil
p := (*slice)(unsafe.Pointer(&a))
fmt.Printf("a.array %x\n", uintptr(p.array))
fmt.Printf("a.len %v\n", uintptr(p.len))
fmt.Printf("a.cap %v\n", uintptr(p.cap))
b := make([]int, 0)
pb := (*slice)(unsafe.Pointer(&b))
fmt.Printf("b.array %x\n", uintptr(pb.array))
fmt.Printf("b.len %v\n", uintptr(pb.len))
fmt.Printf("b.cap %v\n", uintptr(pb.cap))
c := make([]string, 0)
pc := (*slice)(unsafe.Pointer(&c))
fmt.Printf("c.array %x\n", uintptr(pc.array))
fmt.Printf("c.len %v\n", uintptr(pc.len))
fmt.Printf("c.cap %v\n", uintptr(pc.cap))
d := new(empty)
fmt.Printf("empty addr %x\n", uintptr(unsafe.Pointer(d)))
}
测试结果
a.array 0
a.len 0
a.cap 0
b.array c000092e88
b.len 0
b.cap 0
c.array c000092e88
c.len 0
c.cap 0
empty addr c000092e88