go语言切片是对数组的抽象。
go数组的长度不可改变,在特定场景中这样的集合就不太适用,go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片是一种方便、灵活且强大的包装器。切片本身没有任何数据。它们只是对现有数组的引用。
切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由。
从概念上面来说slice像一个结构体,这个结构体包含了三个元素:
var identifier []type
例:
var s1 []int
fmt.Println(s1)
s2 := []int{1,2,3,4}
fmt.Println(s2)
切片不需要说明长度。
可使用make()函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
make为slice, map, channel分配内存,并返回一个初始化的值
func make(t Type, size ...IntegerType) Type
第一个参数:类型
slice,map,chan
第二个参数:长度len
实际存储元素的数量
第三个参数:容量cap
最多能够存储的元素的数量
例:创建slice切片
s3 := make([]int,3,8)
fmt.Println(s3)
fmt.Printf("容量:%d,长度:%d\n",cap(s3),len(s3)) //容量:8,长度:3
例:操作切片(重新赋值元素)
s3[0] = 1
s3[1] = 2
s3[2] = 3
fmt.Println(s3)
fmt.Println(s3[3]) //index out of range [3] with length 3
专门用于向切片的尾部追加元素
语法:func append(slice []Type, elems ...Type) []Type
两种用法:
// 向一个切片的末尾追加元素
slice = append(slice, elem1, elem2)
//向一个切片的末尾追加另外一个切片的所有元素
slice = append(slice, anotherSlice...)
例:操作切片(向一个切片的末尾追加元素)
//虽然容量为5,但长度为0,实际一个元素都没有
s4 := make([]int,0,5)
fmt.Println(s4) // 打印:[]
s4 = append(s4,1,2)
fmt.Println(s4) //打印:[1 2]
继续添加元素:
s4 = append(s4,3,4,5,6,7)
fmt.Println(s4) //打印:[1 2 3 4 5 6 7]
注意:虽然已经超出了开始设置的容量,但是slice具有自动扩容的特点,所以可以继续添加元素
例:操作切片(向一个切片的末尾追加另外一个切片的所有元素)
s4 = append(s4,s3...)
fmt.Println(s4) //打印:[1 2 3 4 5 6 7 1 2 3]
遍历某个切片的元素
方法1:
for i := 0;i < len(s4);i++{
fmt.Println(i)
}
方法2:
for i,v := range s4{
fmt.Printf("s4切片下标%d对应的元素为:%d\n",i,v)
}
package main
import (
"fmt"
)
func main() {
s5 := []int{1,2,3} //创建切片
fmt.Println(s5) //打印切片元素内容
fmt.Printf("长度:%d,容量:%d\n",len(s5),cap(s5)) //打印切片长度及容量
fmt.Printf("%p\n",s5) //打印切片内存地址
返回值:
[1 2 3]
长度:3,容量:3
0xc00009e140
s5 = append(s5,4,5) //扩容切片,追加元素
fmt.Println(s5) //打印扩容后的切片元素内容
fmt.Printf("长度:%d,容量:%d\n",len(s5),cap(s5)) ///打印扩容后的切片长度及容量
fmt.Printf("%p\n",s5) //打印扩容后的切片内存地址
返回值:
[1 2 3 4 5]
长度:5,容量:6
0xc0000c8030
}
注:切片append扩容以后,发生了以下变化:
1、扩容前的容量为3,扩容后的容量成倍增长,变化为6
2、扩容后的长度发生了变化,由3变为5
3、切片一旦扩容,就改变了底层数组,所以底层数组的内存地址发生了改变
继续对上面的切片进行扩容:
s5 = append(s5,6,7,8)
fmt.Println(s5)
fmt.Printf("长度:%d,容量:%d\n",len(s5),cap(s5))
fmt.Printf("%p\n",s5)
返回值:
[1 2 3 4 5 6 7 8]
长度:8,容量:12
0xc000086060
注:切片再次发生的变化:
1、扩容前的容量为6,扩容后的容量成倍增长,变化为12
2、扩容后的长度发生了变化,由5变为8
3、内存地址发生了改变,所以每当扩容时,都改变了切片使用的底层数组
继续对上面的切片进行添加元素:
s5 = append(s5,9,10)
fmt.Println(s5)
fmt.Printf("长度:%d,容量:%d\n",len(s5),cap(s5))
fmt.Printf("%p\n",s5)
返回值:
[1 2 3 4 5 6 7 8 9 10]
长度:10,容量:12
0xc000086060
注:切片再次添加元素后的变化
1、长度发生了变化
2、容量没有发送变化,因为你再次加了两个元素,也没有超过它的容量,则不需要扩容
3、因为切片本次不需要扩容,所以切片使用的底层数组地址也没有发生改变
s5 = append(s5,11,12,13,14,15)
fmt.Println(s5)
fmt.Printf("长度:%d,容量:%d\n",len(s5),cap(s5))
fmt.Printf("%p\n",s5)
返回值:
[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
长度:15,容量:24
0xc000102000
注:切片再次发生的变化:
1、扩容前的容量为12,扩容后的容量成倍增长,变化为24
2、扩容后的长度发生了变化,由10变为15
3、因为切片再次发生了扩容,所以切片使用的底层数组也发送了变化
slice := arr[start:end]
切片中的数据:[start:end]
arr[:end],从头到end
arr[start:]从start到末尾
a := [10]int{1,2,3,4,5,6,7,8,9,10}
s1 := a[:5] //1-5
s2 := a[3:8] //4-8
s3 := a[5:] //6-10
s4 := a[:] //1-10
fmt.Println("a",a) //0xc0000a40a0
fmt.Println("s1",s1) //0xc0000a40a0
fmt.Println("s2",s2) //0xc0000a40b8
fmt.Println("s3",s3) //0xc0000a40c8
fmt.Println("s4",s4) //0xc0000a40a0
注:因为容量从start到数组的末尾,所以切片s2,s3的容量发生了变化,底层数组也进行了改变
fmt.Printf("数组a的长度:%d,容量%d\n",len(a),cap(a)) // 数组a的长度:10,容量10
fmt.Printf("切片s1的长度:%d,容量%d\n",len(s1),cap(s1)) //切片s1的长度:5,容量10
fmt.Printf("切片s2的长度:%d,容量%d\n",len(s2),cap(s2)) //切片s2的长度:5,容量7
fmt.Printf("切片s3的长度:%d,容量%d\n",len(s3),cap(s3)) //切片s3的长度:5,容量5
fmt.Printf("切片s4的长度:%d,容量%d\n",len(s4),cap(s4)) //切片s4的长度:10,容量10
注:
因为:长度是从start到end切割的数据量
所以:切片s1的长度:5,容量10
因为:容量从start到数组的末尾
所以:
切片s2的长度:5,容量7
切片s3的长度:5,容量5
a[5] = 100
fmt.Println(a) //[1 2 3 4 5 100 7 8 9 10]
fmt.Println(s1) //[1 2 3 4 5]
fmt.Println(s2) //[4 5 100 7 8]
fmt.Println(s3) //[100 7 8 9 10]
fmt.Println(s4) //[1 2 3 4 5 100 7 8 9 10]
注:
因为s1、s2、s3、s4都指向了同一个底层数组,修改底层数组的元素后,只要被引用的切片中包含了该元素,那么也会改变
s1因为未包含数组a[5]的元素,所以值不变
s2[4] = 200
fmt.Println(a) //[1 2 3 4 5 100 7 200 9 10]
fmt.Println(s1) //[1 2 3 4 5]
fmt.Println(s2) //[4 5 100 7 200]
fmt.Println(s3) //[100 7 200 9 10]
fmt.Println(s4) //[1 2 3 4 5 100 7 200 9 10]
注:
在操作切片时,其实就是在操作切片对应的底层数组,所以底层数组及使用该底层数组的切片中的元素都会改变
fmt.Println(len(s1),cap(s1)) //5 10
s1 = append(s1,1,1,1,1,1,2,2,2)
fmt.Println(len(s1),cap(s1)) //13 20
fmt.Println(a) //[1 2 3 4 5 100 7 200 9 10]
fmt.Println(s1) //[1 2 3 4 5 1 1 1 1 1 2 2 2]
fmt.Println(s2) //[4 5 100 7 200]
fmt.Println(s3) //[100 7 200 9 10]
fmt.Printf("%p\n",&a) //0xc000014190
fmt.Printf("%p\n",&s1) //0xc0000044a0
注:
当s1所指向的底层数组容量不足时,则会重新创建一个底层数组(先将原数组内容全部拷贝,再进行元素的添加)
后面再去操作s1切片时,则会指向新的底层数组,与数组a、切片s2、s3、s4没有任何关系了
按照数据类型来分:
按照数据特点来分:
值类型:int、float、string、bool、array ...
传递的是数据副本
引用类型:Slice
传递的地址,多个变量指向了同一块内存地址
所以:切片是引用类型的数据,存储了底层数组的引用
a1 := [4]int{1,2,3,4}
a2 := a1
fmt.Println(a1,a2) //[1 2 3 4] [1 2 3 4]
fmt.Printf("a1内存地址:%p\n",&a1) //a1内存地址:0xc00009e140
fmt.Printf("a2内存地址:%p\n",&a2) //a2内存地址:0xc00009e160
a1[0] = 100
fmt.Println(a1,a2) //[100 2 3 4] [1 2 3 4]
注:
a1赋值给a2,是一种值传递,传递的是数据
所以a1 与 a2的内存地址是不一样的。修改a1中的元素内容,与a2是没有任何关系的
s1 := []int{1,2,3,4}
s2 := s1
fmt.Println(s1,s2) //[1 2 3 4] [1 2 3 4]
fmt.Printf("s1的内存地址:%p\n",s1) //s1的内存地址:0xc00009e220
fmt.Printf("s2的内存地址:%p\n",s2) //s2的内存地址:0xc00009e220
s1[0] = 100
fmt.Println(s1,s2) //[100 2 3 4] [100 2 3 4]
注:
s1赋值给s2,是一种引用传递,传递的底层数组
所以s1 与 s2的内存地址是一样的。修改s1中的元素内容,s2也会跟着改变
深拷贝:拷贝的是数据本身。
值类型的数据,默认都是深拷贝:array,int,float,string、bool、struct
浅拷贝:拷贝的数据地址。
导致多个变量指向同一块内存,引用类型的数据,默认都是浅拷贝:slice、map
因为切片是引用类型的数据,直接拷贝的是地址
s1 := []int{1,2,3,4}
s2 := make([]int,0) //len:0,cap:0
for i:=0;i<len(s1);i++ {
s2 = append(s2,s1[i])
}
fmt.Println(s1) //[1 2 3 4]
fmt.Println(s2) //[1 2 3 4]
s1[0] = 100
fmt.Println(s1) //[100 2 3 4]
fmt.Println(s2) //[1 2 3 4]
注:
以上方式通过make方法创建了s2切片,指向了一个新的空数组
以上方式通过for循环对s2切片不断的添加元素,导致s2不断的进行扩容,每当扩容,都是去重新创建了一个数组,直到元素添加完毕;
这个时候再去改变s1的第一个下标元素,对s2是没有任何影响的
go语言当中,内置的copy函数专用于给copy进行深拷贝的
func copy(dst, src []Type) int
cppy函数一共有两个参数:
内置函数 copy 将元素从源切片复制到目标切片。(一种特殊情况是,它还会将字符串字节复制到字节切片中。) 源切片和目标切片可能会存在重叠。 copy 的返回值是复制的元素个数,该数为 len(src) 和 len(dst) 中的最小值。
package main
import "fmt"
func main() {
s1 := []int{1,2,3,4}
s2 := []int{7,8,9}
copy(s1,s2) //将s2中的元素,拷贝到s1中
fmt.Println(s1) //[7 8 9 4]
fmt.Println(s2) //[7 8 9]
copy(s2,s1) //将s1中的元素,拷贝到s2中
fmt.Println(s1) //[1 2 3 4]
fmt.Println(s2) //[1 2 3]
copy(s2,s1[2:]) //将s1中的下标2元素至末尾拷贝至s2(s2从下标0开始添加)
fmt.Println(s1) //[1 2 3 4]
fmt.Println(s2) //[3 4 9]
copy(s2[1:],s1[2:]) //将s1中的下标2元素至末尾拷贝至s2(s2从下标1开始添加)
fmt.Println(s1) //[1 2 3 4]
fmt.Println(s2) //[7 3 4]
}
原文:https://www.cnblogs.com/jemooner/p/14794575.html