Golang学习之路— 一文带你了解切片(slice)

切片

  • 切片的基本介绍
  • 切片的快速入门
  • 切片在内存中形式
  • nil切片和空切片
    • nil切片
    • 空切片
  • 切片的使用
    • 方式1
    • 方式2
    • 方式3
    • 方式1与方式2的区别(面试)
  • 切片的遍历
  • append内置函数对切片进行动态追加
    • 案例演示
    • 底层分析
  • 切片的拷贝操作
  • 切片使用的注意事项

切片的基本介绍

切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。

切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个与指向数组的动态窗口。

给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个长度可变的数组。

切片定义的基本语法:

var 切片名[]类型
如:var a[]int

切片的快速入门

package main

import(
	"fmt"
)

func main(){
     

	var arr [5]int = [...]int{
     5,4,3,2,1}

	//定义一个切片
	//引用arr数组的起始下标为1,最后的下标为3(但不包括3)
	slice := arr[1:3]
	fmt.Println("arr=",arr)
	fmt.Println("slice 的元素是 =",slice)
	fmt.Println("slice 的元素个数 =",len(slice))
	fmt.Println("slice 的容量 =",cap(slice))//切片的容量是可以动态变化的

}

运行结果:
在这里插入图片描述

切片在内存中形式

切片的内存布局:
Golang学习之路— 一文带你了解切片(slice)_第1张图片
说明:

  • slice是一个引用类型
  • slice从底层来说,其实就是一个数据结构(struct结构体)
type slice struct{
     
   ptr *[2] int
   len int
   cap int
}

nil切片和空切片

nil切片和空切片也是常用的。

nil切片

var slice []int

Golang学习之路— 一文带你了解切片(slice)_第2张图片
nil 切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就需要用到 nil 切片。比如函数在发生异常的时候,返回的切片就是 nil 切片。nil 切片的指针指向 nil。

空切片

空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。

slice := make([]int, 0)
slice := []int{
     }

Golang学习之路— 一文带你了解切片(slice)_第3张图片
空切片和 nil 切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。

最后需要说明的一点是。不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。

切片的使用

方式1

定义一个切片,然后让切片去引用一个已经创建好的数组,比如前面的案例。

func main(){
     

	var arr [5]int = [...]int{
     5,4,3,2,1}

	//定义一个切片
	//引用arr数组的起始下标为1,最后的下标为3(但不包括3)
	slice := arr[1:3]
	fmt.Println("arr=",arr)
	fmt.Println("slice 的元素是 =",slice)
	fmt.Println("slice 的元素个数 =",len(slice))
	fmt.Println("slice 的容量 =",cap(slice))//切片的容量是可以动态变化的
}

方式2

通过make来创建切片。

基本语法:

var 切片名 []type = make([]type,len,[cap])

参数说明:

  • type就是数据类型
  • len:大小
  • cap:指定切片容量,可选,如果分配了,则要求cap>=len

案例演示

package main

import(
	"fmt"
)

func main(){
     

   var slice []int = make([]int ,5,10)
   slice[1] = 10
   slice[2] = 20
   fmt.Println(slice)
   fmt.Println("slice的size =",len(slice))
   fmt.Println("slice的cap =",cap(slice))
}

运行结果:
在这里插入图片描述
说明:

  • 通过make方式创建切片可指定切片的大小和容量
  • 如果没有给切片的各个元素赋值,那么就是使用默认值。
  • 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素。

方式3

定义一个切片,直接就指定具体数组,使用原理类似make的方式。

案例:

package main

import(
	"fmt"
)

func main(){
     

   var slice []string = []string{
     "Casey","Lily","Sally"}
   fmt.Println(slice)
   fmt.Println("slice的size =",len(slice))
   fmt.Println("slice的cap =",cap(slice))
}

运行结果:
在这里插入图片描述

方式1与方式2的区别(面试)

  • 方式1是直接引用数组,这个数组是事先存在的,程序员可见的。
  • 方式2是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员是看不见的。
    make创建切片的示意图:
    Golang学习之路— 一文带你了解切片(slice)_第4张图片

切片的遍历

切片的遍历和数组一样,也是有两种方式,for循环常规方法遍历,for-range结构遍历切片。
案例演示:

package main

import(
	"fmt"
)

func main(){
     

  var slice []string = []string{
     "Casey","Lily","Sally"}
  //常规for循环遍历切片
  for i := 0; i < len(slice); i++{
     
	  fmt.Printf("slice[%d] = %v\n", i, slice[i])
  }

  //使用for-range结构遍历切片

  for index, value := range slice{
     
	  fmt.Printf("i = %v, v = %v\n", index, value)
  }

}

运行结果:
Golang学习之路— 一文带你了解切片(slice)_第5张图片

append内置函数对切片进行动态追加

案例演示

package main

import(
	"fmt"
)

func main(){
     

  var slice []string = []string{
     "Casey","Lily","Sally"}
  //通过append内置函数,可以对切片进行动态追加 
  slice = append(slice,"Jerry","Tom")
  fmt.Println("slice =",slice)

  //通过append将切片slice追加给slice
  slice = append(slice,slice...)
  fmt.Println("slice =",slice)

}

运行结果:
在这里插入图片描述

底层分析

示意图:
Golang学习之路— 一文带你了解切片(slice)_第6张图片
说明:

  • 切片append操作的本质就是对数组扩容
  • go底层会创建一个新的数组newArr(安装扩容后大小)
  • 将slice原来包含的元素拷贝到新的数组newArr中,slice重新引用到newArr。

切片的拷贝操作

切片使用copy内置函数完成拷贝。
案例:

package main

import(
	"fmt"
)

func main(){
     

  var slice []string = []string{
     "Casey","Lily","Sally"}
  var slice1 = make([]string,5)
  copy(slice1,slice)
  fmt.Printf("slice = %v\n",slice)
  fmt.Printf("slice1 = %v\n", slice1)

  var slice2 []int = []int{
     10,20,30}
  var slice3 = make([]int,5)
  copy(slice3,slice2)
  fmt.Printf("slice2 = %v\n",slice2)
  fmt.Printf("slice3 = %v\n", slice3)

}

运行结果:
在这里插入图片描述
说明:

  • copy(para1,para2)参数的数据类型是切片。
  • 按照上面代码:slice和slice,slice2和slice3的数据空间是独立的,互不影响。

切片使用的注意事项

  • 切片初始化时 var slice = arr[startIndex:endIndex]
    说明:从arr数组下标为startIndex,取到小标为endIndex的元素(不含endIndex)
  • 切片初始化时,仍然不能越界,范围在0 ~ len(arr)-1,但是可以动态增长。
var slice = arr[0:end] 可简写 var slice = arr[:end]
var slice = arr[start:len(arr)]可简写var slice = arr[start:]
var slice = arr[0:len(str)] 可简写 var slice = arr[:]
  • cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
  • 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者make一个空间供切片来使用。
  • 切片可以继续切片。
    案例:
package main

import(
	"fmt"
)

func main(){
     

  var slice []string = []string{
     "Casey","Lily","Sally"}
  slice1 := slice[0:2]
  //slice 和 slice1指向的数据空间是同一个,因此slice[1] = "abc"
  slice1[1] = "abc"

  fmt.Println("slice =",slice)
  fmt.Println("slice1 =",slice1)

}

运行结果:
在这里插入图片描述

  • 切片是引用类型,在传递时,遵守引用传递机制。

你可能感兴趣的