您的位置 首页 golang

Golang 复合数据类型:切片


切片(slice)

切片的底层是数组实现的,可以按需自动增长和缩小。切片是数组的引用,因此是引用类型,不支持直接比较,只能和nil比较。切片的动态增长是通过内建函数append()来实现的,这个函数可以快速且高效地增长切片,也可以通过切片再次切割,缩小每一个切片的大小。

  • 切片不存值,底层数组存值
  • 切片指向一个底层数组
  • 底层数组是占用一块连续的内存空间

创建数组切片

创建两个类型分别为 int 型和 string 型的切片,并初始化

func main(){    var slice1 []int    var slice2 []string    fmt.Println(slice1,slice2)    fmt.Println(slice1 == nil) //true,没有开辟内存空间    fmt.Println(slice2 == nil) //true    //初始化    slice1 = []int{1,3,5}    slice2 = []string{"小","马","锅"}    fmt.Println(slice1,slice2)    fmt.Println(slice1 == nil) //false,初始化已分配内存空间    fmt.Println(slice2 == nil) //false}/*[] []truetrue[1 3 5] [小 马 锅]falsefalse*/

可以看到,未初始化时的切片为 [ ],即切片的零值为 [ ]。由于切片是引用类型,切片与切片之间不能直接比较,只能与nil比较。未初始化的 slice1 和 slice2 由于没有分配到内存空间,因此与nil比较的值为 true ,初始化的 slice1 和 slice2 已经分配了内存空间,因此与 nil 比较的值为 false (分配的内存空间不同)。

  • make()函数创建切片
func main(){    //创建长度5,容量10的切片,未被初始化    s1 := make([]int, 5,10)    fmt.Println(s1)    fmt.Printf("s1长度:%d s1容量:%d\n",len(s1),cap(s1))    //创建长度0,容量10的切片,空切片    s2 := make([]int, 0 , 10)    fmt.Println(s2)    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))}/*[0 0 0 0 0]s1长度:5 s1容量:10[]s2长度:0 s2容量:10*/
  • 使用切片字面量创建切片
func main(){    s1 := []int{1,2,3,5,4,20}    fmt.Println(s1)    fmt.Printf("s1长度:%d s1容量:%d\n",len(s1),cap(s1))    s2 := []string{"字","面","量"}    fmt.Println(s2)    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))}/*[1 2 3 5 4 20]s1长度:6 s1容量:6[字 面 量]s2长度:3 s2容量:3*/

nil 切片和空切片

有时候需要声明一个值为 nil 的切片,也叫空切片;nil 切片在底层数组中包含 0 个元素,也没有分配任何的存储空间。nil 切片还可以用来表示空集合。一个 nil 切片没有底层数组,它的长度和容量都是 0 以下是创建空切片的三种方式:声明未初始化的切片使用make函数使用切片字面量

func main(){    //声明未初始化的切片是空切片,值为 nil    var slice []int    fmt.Println(slice)    //使用make函数创建空的字符型切片    s1 := make([]string, 0)    fmt.Println(s1)    //使用字面量创建空的布尔型切片    s2 := []bool{}    fmt.Println(s2)}/*[][][]*/

长度和容量都为 0 的切片不一定都是 nil 切片(空切片),判断一个切片是否为空,应该使用 len(slice) == 0,不能使用 slice == nil 判断。

切片的长度和容量

切片拥有自己的长度和容量,可以通过使用内建函数 len() 求长度,cap() 求容量。

func main(){    var slice1 []int    var slice2 []string    //初始化    slice1 = []int{1,3,5,9,65,3}    slice2 = []string{"小","马","锅"}    //长度和容量    fmt.Printf("slice1长度:%d slice1容量:%d\n",len(slice1),cap(slice1))    fmt.Printf("slice2长度:%d slice2容量:%d\n",len(slice2),cap(slice2))}/*slice1长度:6 slice1容量:6slice2长度:3 slice2容量:3*/

由数组得到切片

func main(){    array := [...]int{2,3,5,1,8,13,31,9}    //切片是基于底层数组的切片    s := array[0:4]  //从第0个到第4个的切片(不包含最后一个) -> [2 3 5 1]    s1 := array[1:6] //从第1个到第6个的切片(不包含最后一个) ->[3 5 1 8 13]    s2 := array[:]  //全切(包含所有元素) -> [2 3 5 1 8 13 31 9]    s3 := array[:5] //从第0个到第5个(不包含最后一个) -> [2 3 5 1 8]    s4 := array[4:] //从第4个到结束(包括最后一个) -> [8 13 31 9]    fmt.Printf("array:%d\n",array)    fmt.Printf("s:%d\ns1:%d\ns2:%d\ns3:%d\ns4:%d\n",s,s1,s2,s3,s4)}/*array:[2 3 5 1 8 13 31 9]s:[2 3 5 1]s1:[3 5 1 8 13]s2:[2 3 5 1 8 13 31 9]s3:[2 3 5 1 8]s4:[8 13 31 9]*/

切片的长度和容量

func main(){    array := [...]int{2,3,5,1,8,13,31,9}//    s := array[0:4]  //从第0个到第4个的切片(不包含最后一个) -> [2 3 5 1]    s1 := array[1:6] //从第1个到第6个的切片(不包含最后一个) ->[3 5 1 8 13]    s2 := array[:]  //全切(包含所有元素) -> [2 3 5 1 8 13 31 9]    s3 := array[:5] //从第0个到第5个(不包含最后一个) -> [2 3 5 1 8]    s4 := array[3:6] //从第3个到第6个(不包含最后一个)-> [1 8 13]        //切片长度和容量与数组的关系    fmt.Printf("array:%d\n",array)    fmt.Printf("array长度:%d array容量:%d\n",len(array),cap(array))    fmt.Printf("s1长度:%d s1容量:%d\n",len(s1),cap(s1))    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))    fmt.Printf("s3长度:%d s3容量:%d\n",len(s3),cap(s3))    fmt.Printf("s4长度:%d s4容量:%d\n",len(s4),cap(s4))}/*array:[2 3 5 1 8 13 31 9]array长度:8 array容量:8s1长度:5 s1容量:7s2长度:8 s2容量:8s3长度:5 s3容量:8s4长度:3 s4容量:5*/
  • 切片是引用类型,实际就是指向底层数组的指针
  • 切片的长度就是它的元素个数
  • 切片容量就是底层数组从切片第一个元素到最后一个元素的长度

s4

由切片得到切片

func main(){    array := [...]int{2,3,5,1,8,13,31,9}//    s := array[0:4]  //从第0个到第4个的切片(不包含最后一个) -> [2 3 5 1]    s1 := array[1:6] //从第1个到第6个的切片(不包含最后一个) ->[3 5 1 8 13]    s2 := s1[1:3]    //从s1切片第1个到第3个元素(不包含最后一个) -> [5 1]    //切片长度和容量与数组的关系    fmt.Printf("array:%d\n",array)    fmt.Printf("array长度:%d array容量:%d\n",len(array),cap(array))    fmt.Printf("s1长度:%d s1容量:%d\n",len(s1),cap(s1))    //由切片组成切片    fmt.Printf("s2:%d\n",s2)    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))    //切片是引用类型,如果改变底层数组,切片也会改变    array[3] = 8888    fmt.Printf("array修改第3个元素:%d\n",array)    fmt.Printf("s1修改:%d\ns2修改:%d\n",s1,s2)}/*array:[2 3 5 1 8 13 31 9]array长度:8 array容量:8s1长度:5 s1容量:7s2:[5 1]s2长度:2 s2容量:6array修改第3个元素:[2 3 5 8888 8 13 31 9]s1修改:[3 5 8888 8 13]s2修改:[5 8888]*/

可以看到,切片 s2 是由切片 s1 从第1个元素切到第3个元素(不包括最后一个)而形成的切片,为 [5 1] 。因为切片是对底层数组的引用,又称引用类型;因此,改变底层数组的值,切片也会改变。但是这里可以分为两种情况:

例如,使用 [ ] 修改底层数组 array 索引值3的元素为 8888 ,又因为 8888 分别是切片 s1 和 s2 对应索引值 2 和 1 的值,因此切片会发生改变;如果修改底层数组 array 索引值0的元素为 8888,又因为 8888 不在切片 s1 和 s2 的范围内,所以切片不会受到底层数组的改变而影响。

  • 若改变底层数组的索引值对应的元素,不在对应切片内的元素,则切片不会发生改变(情况一)
//情况一 · 修改底层数组元素不在切片范围内func main(){    array := [...]int{2,3,5,1,8,13,31,9}    s1 := array[1:6] //从第1个到第6个的切片(不包含最后一个) ->[3 5 1 8 13]    s2 := s1[1:3]    //从s1切片第1个到第3个元素(不包含最后一个) -> [5 1]    //由切片组成切片    fmt.Printf("s2:%d\n",s2)    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))    //切片是引用类型,如果改变底层数组,切片也会改变    array[0] = 8888    fmt.Printf("array修改第0个元素:%d\n",array)    fmt.Printf("s1:%d\ns2:%d\n",s1,s2)}/*s2:[5 1]s2长度:2 s2容量:6array修改第0个元素:[8888 3 5 1 8 13 31 9]s1:[3 5 1 8 13]s2:[5 1]*/
  • 若改变底层数组的索引值对应的元素,是在对应切片内的元素,则切片也会发生改变(情况二)
////情况二 · 修改底层数组元素在切片范围内func main(){    array := [...]int{2,3,5,1,8,13,31,9}    s1 := array[1:6] //从第1个到第6个的切片(不包含最后一个) ->[3 5 1 8 13]    s2 := s1[1:3]    //从s1切片第1个到第3个元素(不包含最后一个) -> [5 1]    //由切片组成切片    fmt.Printf("s2:%d\n",s2)    fmt.Printf("s2长度:%d s2容量:%d\n",len(s2),cap(s2))    //切片是引用类型,如果改变底层数组,切片也会改变    //情况二    array[3] = 8888    fmt.Printf("array修改第3个元素:%d\n",array)    fmt.Printf("s1:%d\ns2:%d\n",s1,s2)}/*s2:[5 1]s2长度:2 s2容量:6array修改第3个元素:[2 3 5 8888 8 13 31 9]s1:[3 5 8888 8 13]s2:[5 8888]*/

切片的赋值

func main() {    s1 := []string{"red", "yellow", "blue", "green"}    fmt.Printf("s1:%s\n",s1)    //修改第0个元素的值    s1[0] = "black"    s2 := s1 //s2和s1都指向同一个底层数组    fmt.Printf("s1:%s\ns2:%s\n",s1,s2)}/*s1:[red yellow blue green]s1:[black yellow blue green]s2:[black yellow blue green]*/

切片遍历

  • 索引遍历
  • range遍历,总是从切片的头部(索引值为0的元素)开始的
func main(){    array := []int{1,2,3}    //按索引遍历    for i:=0; i<len(array) ; i++ {        fmt.Println(array[i])    }    //range遍历    for i,v := range array {        fmt.Println(i,v)    }}/*1230 11 22 3*/

切片扩容

  • 使用append()函数进行切片扩容

    • 调用append函数必须用原来的切片变量接收返回值
    • 必须用变量接收函数返回值
    • 若底层数组不够,append函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值
    • 切片扩容会根据切片类型不同而做不同的处理,比如 int 和 string 类型的处理方式是不一样的
func main(){    //原始切片    slice := []int{1,2,3}    fmt.Printf("slice:%d len(slice):%d cap(slice):%d\n",slice,len(slice),cap(slice))    //第一次切片扩容,观察长度和容量    s1 := append(slice,4,5)    fmt.Printf("s1:%d len(s1):%d cap(s1):%d\n",s1,len(s1),cap(s1))    //第二次切片扩容,观察长度和容量    s1 = append(s1,6,7,8)    fmt.Printf("s1:%d len(s1):%d cap(s1):%d\n",s1,len(s1),cap(s1))    //第三次切片扩容,观察长度和容量    s1 = append(s1,9,10,11,12,15)    fmt.Printf("s1:%d len(s1):%d cap(s1):%d\n",s1,len(s1),cap(s1))    //追加原始切片slice元素 ... 代表拆分切片内元素,比如[1][2][3]    s1 = append(s1, slice...)    fmt.Printf("s1:%d len(s1):%d cap(s1):%d\n",s1,len(s1),cap(s1))}/*slice:[1 2 3] len(slice):3 cap(slice):3s1:[1 2 3 4 5] len(s1):5 cap(s1):6s1:[1 2 3 4 5 6 7 8] len(s1):8 cap(s1):12s1:[1 2 3 4 5 6 7 8 9 10 11 12 15] len(s1):13 cap(s1):24s1:[1 2 3 4 5 6 7 8 9 10 11 12 15 1 2 3] len(s1):16 cap(s1):24*/

函数append()会智能地处理底层数组的容量。可以看出,在创建新的底层数组时,与原始底层数组相比较,新数组容量是原始数组容量的两倍。在切片容量小于 1024 个元素时,总是会成倍地增长容量。一旦元素超过 1024 个,容量的增长因子将会设置为 1.25 ,也就是每次会增加 25% 的容量。【注】:关于切片容量增长的源代码在 $GOROOT/src/runtime/slice.go

切片复制

使用copy()函数进行切片复制

func main(){    a1 := []int{1,3,5}    a2 := a1    var a3 = make([]int,3,3)     copy(a3,a1)  //把切片a1复制给a3(副本)    fmt.Println(a1,a2,a3)    a1[0] = 100  //修改a1索引值0的元素    fmt.Println(a1,a2,a3) //a2共用a1的底层数组会改变,副本a3不受影响}/*[1 3 5] [1 3 5] [1 3 5][100 3 5] [100 3 5] [1 3 5]*/

切片删除

Go语言中没有删除切片的专用方法,但是可以利用切片的特性进行删除操作。

func main(){    array := [...]int{1,2,3,4,5,6,7}  //底层数组    slice := array[:]  //全切,把底层数组转化成切片类型    //删除索引值2的元素,即删除3    fmt.Printf("原始array容量:%d 原始slice容量:%d\n",cap(array),cap(slice))    //先把[1 2]和[4 5 6 7]切开,再拼接    slice = append(slice[:2],slice[3:]...)    fmt.Println(slice)    fmt.Printf("修改slice容量:%d\n",cap(slice)) //切片不存值,底层数组存,删除切片元素等价于元素向左移,内存空间没被删,容量也就不变}/*原始array容量:7 原始slice容量:7[1 2 4 5 6 7]修改slice容量:7*/

切片不存值,底层数组存值,删除切片元素可以看作是切片元素范围移动,而数组本身所占用的内存空间没有被删,因此原始切片容量和删除切片后容量是一致的,都是7

func main(){    array := []int{1,2,3,4,6,8,5,13,25}    slice := array[:]    fmt.Printf("原始数组array:%d\n",array)    //删除索引值为4的元素6    slice = append(slice[:4],slice[5:]...)    fmt.Printf("修改切片slice:%d\n",slice)    fmt.Printf("修改数组array:%d\n",array)}/*原始数组array:[1 2 3 4 6 8 5 13 25]修改切片slice:[1 2 3 4 8 5 13 25]修改数组array:[1 2 3 4 8 5 13 25 25]*/

这里原始数组和修改数组不同的原因是:当删除索引值为4的元素6时,就相当于把 slice [ 5 : ] 这部分的切片元素都追加到 slice [ : 4 ] 后面,此时索引值是4的切片元素6已经被删除,而底层数组本身的内存空间是不改变的,因此在使用 append() 函数追加以后,底层数组最后一位元素是原始数组的最后一位元素,即 25 。

练习(demo)

一道考察切片基础的面试题

func main(){    //未初始化时的切片是零值    var slice = make([]int,5,10)    fmt.Println(slice)     //在int型切片零值的基础上追加元素    for i:=0 ; i<10 ; i++ {        slice = append(slice,i)    }    fmt.Println(slice)}/*[0 0 0 0 0][0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]*/

文章来源:智云一二三科技

文章标题:Golang 复合数据类型:切片

文章地址:https://www.zhihuclub.com/3455.shtml

关于作者: 智云科技

热门文章

网站地图