golang 空切片和nil切片 有区别吗?
你看到的这个文章来自于http://www.cnblogs.com/ayanmw
判空
我们写程序都会有进行判空
的操作:
对于map
对于golang的map
,它为nil,你是无法直接使用的 如var oneMap map[uint32]uint32
, 不赋值使用直接panic,必定要判空; 给它赋值为一个初始值var oneMap = map[uint32]uint32{}
使用的时候
if oneMap!=nil{//
oneMap[1]=1
}
对于slice
初始化:
func Test_EmptySlice1(t *testing.T) {
var emptySlice1 []int
var emptySlice2 = []int{}
if emptySlice1 == nil {
t.Logf("emptySlice1==nil")
}
if emptySlice2 == nil {
t.Logf("emptySlice2==nil")
}
}
您会得到答案:
emptySlice1==nil
emptySlice2 就这么被忽略了, 这显然是不对的!
所以探索一下 slice 底层 空slice 和 nil slice的区别到底是什么?
探索runtime/slice.go 源码
slice的runtime源码为:
type slice struct {
array unsafe.Pointer
len int
cap int
}
这个结构体包含三个字段:
array:指向底层数组的指针。
len:切片的长度。
cap:切片的容量。
下面我们来详细探讨空切片和 nil 切片在源码层面的不同。
1. 空切片
空切片是指长度为 0 的切片,它可以通过 make 函数创建,也可以使用字面量创建。例如:
// 使用 make 函数创建
emptySlice1 := make([]int, 0)
// 使用字面量创建
emptySlice2 := []int{}
对于空切片,slice 结构体的各个字段值如下:
- array:作为一个指针指向一个地址,但是这个地址是不存数据的。
- len:值为 0,表示切片的长度为 0。
- cap:值为 0,表示切片的容量为 0。
2. nil 切片
nil 切片是指未初始化的切片,它没有指向任何底层数组。例如:
var nilSlice []int
对于 nil 切片,slice 结构体的各个字段值如下:
- array:值为 nil,表示没有指向任何底层数组。
- len:值为 0,表示切片的长度为 0。
- cap:值为 0,表示切片的容量为 0。
3. 源码层面的区别
在 Go 语言的源码中,空切片和 nil 切片的主要区别在于 array 字段的值。空切片的 array 字段指向一个空的底层数组,而 nil 切片的 array 字段为 nil。.
以下是一个简单的示例,展示了空切片和 nil 切片在内存中的不同:
func Test_EmptySlice(t *testing.T) {
type UserShip struct {
Id int
}
type Slice struct {
Array unsafe.Pointer
Len int
Cap int
}
emptySlice := make([]*UserShip, 0)
// 打印切片的长度、容量和底层数组的地址
theVal := (*Slice)(unsafe.Pointer(&emptySlice))
t.Logf("Length: %d, Capacity: %d, Array address: %p theVal=%#v , ptrIntVal=%v\n",
len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal, *(*int)(theVal.Array))
// 尝试向空切片添加元素
newShip := &UserShip{}
emptySlice = append(emptySlice, newShip)
theVal = (*Slice)(unsafe.Pointer(&emptySlice))
t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
emptySlice = append(emptySlice, newShip)
theVal = (*Slice)(unsafe.Pointer(&emptySlice))
t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
emptySlice = append(emptySlice, newShip)
theVal = (*Slice)(unsafe.Pointer(&emptySlice))
t.Logf("After append: Length: %d, Capacity: %d, Array address: %p theVal=%#v\n",
len(emptySlice), cap(emptySlice), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice)), theVal)
{
emptySlice2 := []uint32{}
t.Logf("Length: %d, Capacity: %d, Array address: %p",
len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
}
{
emptySlice2 := []uint32{}
t.Logf("Length: %d, Capacity: %d, Array address: %p",
len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
}
{
emptySlice2 := []uint32{}
t.Logf("Length: %d, Capacity: %d, Array address: %p",
len(emptySlice2), cap(emptySlice2), *(*unsafe.Pointer)(unsafe.Pointer(&emptySlice2)))
}
}
输出结果:
=== RUN Test_EmptySlice
deepcopy_test.go:37: Length: 0, Capacity: 0, Array address: 0x16cb8c0 theVal=&common.Slice{Array:(unsafe.Pointer)(0x16cb8c0), Len:0, Cap:0} , ptrIntVal=0
deepcopy_test.go:44: After append: Length: 1, Capacity: 1, Array address: 0xc0000a0000 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0000a0000), Len:1, Cap:1}
deepcopy_test.go:48: After append: Length: 2, Capacity: 2, Array address: 0xc000052050 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc000052050), Len:2, Cap:2}
deepcopy_test.go:52: After append: Length: 3, Capacity: 4, Array address: 0xc0001206a0 theVal=&common.Slice{Array:(unsafe.Pointer)(0xc0001206a0), Len:3, Cap:4}
deepcopy_test.go:56: Length: 0, Capacity: 0, Array address: 0x16cb8c0
deepcopy_test.go:61: Length: 0, Capacity: 0, Array address: 0x16cb8c0
deepcopy_test.go:66: Length: 0, Capacity: 0, Array address: 0x16cb8c0
--- PASS: Test_EmptySlice (0.00s)
可以看到 无论nil还是 empty 的slice , 在赋值后,len长度发生变化后, 它的 array值都会发生变化.
且 多个 empty slice 它们的 array值 都是: 0x16cb8c0
,这个值打印出来ptrIntVal=0
,这个值大概率就是一个全局变量,值为0.
这一定是golang的优化, 一个 实例化的emptySlice ,大小为0, 应该是一个0数组的地址,简化就是一个 0值指针的变量地址. 虽然emptySlice array 有值, 但是 它 不包含任何元素。
实际影响
虽然空切片和 nil 切片在长度和容量上都是 0,但它们在某些情况下的行为是不同的。例如,在 JSON 编码时,空切片会被编码为 [],而 nil 切片会被编码为 null。
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 创建空切片
emptySlice := make([]int, 0)
// 创建 nil 切片
var nilSlice []int
// 对空切片进行 JSON 编码
emptyJSON, _ := json.Marshal(emptySlice)
// 对 nil 切片进行 JSON 编码
nilJSON, _ := json.Marshal(nilSlice)
fmt.Printf("Empty slice JSON: %s\n", string(emptyJSON))
fmt.Printf("Nil slice JSON: %s\n", string(nilJSON))
}
输出结果
Empty slice JSON: []
Nil slice JSON: null
综上所述,空切片和 nil 切片在源码层面的主要区别在于 array 字段的值,这也导致了它们在某些情况下的行为不同。
转载请注明出处:http://www.cnblogs.com/ayanmw 我会很高兴的!
------------------------------------------------------------------------------------------------
一定要专业!本博客定位于 ,C语言,C++语言,Java语言,Android开发和少量的Web开发,之前是做Web开发的,其实就是ASP维护,发现EasyASP这个好框架,对前端后端数据库 都很感觉亲切啊。. linux,总之后台开发多一点。以后也愿意学习 cocos2d-x 游戏客户端的开发。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2012-02-28 C语言零碎记录之extern
2012-02-28 Linux零碎记录之ulimit【堆栈大小、stack size、进程数限制、文件句柄限制、linux用户空间限制】