一、区别
数组的长度是固定的,初始化后就不能修改长度,大家平时代码中比较少用。
slice是对数组的一个封装,可以动态扩容,slice是一个结构体,包含三个字段:底层数组、长度、容量
二、初始化方式
数组
var a = [4]int{1,2,3,4} var b = [...]int{1,2,3,4,5}
切片
var a []int //当前值为nil,未分配内存空间 var b = []int{1, 2, 3} var c = make([]int, 5) //初始化5个元素,默认值为0,容量为5,在后面追加元素就会触发扩容 var d = make([]int, 0, 5) //初始化0个元素,容量为5
注意:slice在追加元素时如果容量不够会触发扩容,原slice拷贝到新slice中,扩容规则网上的说法:原slice小于1024,新的slice容量变成原slice的2倍,大于1024时,新slice容量变成原slice的1.25倍。这里的说法只是一个大概不是很准确,感兴趣的朋友可以查看网络上相关文章或源码,其实这对于我们平时开发影响不是很大,想要最求性能可以在初始化时预估容量,减少扩容和拷贝的次数。
三、拷贝
第一种
var a = []int{1,2,3,4,5} var b = a[2:4] //拷贝a中部分数据到b中 b[0] = 12 //改变b第一个元素的值 log.Println(a,b) //输出:[1 2 12 4 5] [12 4] 两个数组的值都被改变了,说明数据是地址拷贝 b = append(b,6,7,8,9) //追加元素,容量不够引发扩容 b[0] = 13 //改变b第一个元素的值 log.Println(a,b) //输出:[1 2 12 4 5] [13 4 6 7 8 9] b中的数据改变了,a中的值并没有改变,扩容以后b和a中的元素已经没有了关系
第二种
var a = []int{1,2,3,4,5} var b = make([]int,5) copy(b,a) //值拷贝 b[0] = 12 log.Println(a,b) //输出:[1 2 3 4 5] [12 2 3 4 5]
四、平时使用需要注意的地方
1、作为函数参数传递(go语言的函数参数传递,只有值传递,没有引用传递),但有时候我们会产生一些误区,看看下面这个例子
func f(a []int){ a[0] = 12 a = append(a,6) } func main(){ var a = []int{1,2,3,4,5} f(a) log.Println(a) //输出:[12 2 3 4 5] 第一个元素改变了,但值6并没有追加到a中 }
2、range 中改变值
var a = []int{1, 2, 3, 4, 5} for _, v := range a { //不会改变a中元素的值(v只是一个临时变量) v = v + 2 } log.Println(a) //输出:[1 2 3 4 5] for i := range a { //会改变a中的值 a[i] = i + 2 } log.Println(a) //输出:[2 3 4 5 6]
五、扩展
1、slice的结构
func main(){ var a = []int{1, 2, 3, 4, 5} s := (*SliceHeader)(unsafe.Pointer(&a)) log.Println(s.Len,s.Cap) //输出:5 5 } type SliceHeader struct { Data uintptr //底层数组地址 Len int //长度 Cap int //容量 }
2、append方法必须要有接收者
var a = []int{1, 2, 3, 4, 5} append(a,6) //编译失败
3、数组的元素在内存中地址是连续的
var a = []int{1, 2, 3, 4, 5} s := (*reflect.SliceHeader)(unsafe.Pointer(&a)) //根据数组索引获取元素值 arrGet := func(n int)int{ if n >= s.Len{ panic("索引越界") } v := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s.Data))+unsafe.Sizeof(int(0))*uintptr(n))) return *v } log.Println(arrGet(0)) //输出:1 log.Println(arrGet(1)) //输出:2 log.Println(arrGet(5)) //输出:panic: 索引越界
以上内容为个人理解,如有问题欢迎指出。