1.切片的介绍
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
2.切片的定义:
你可以声明一个未指定大小的数组来定义切片:
var variable []type
切片不需要说明长度。
或使用make()函数来创建切片:
var s1 []type = make([]type, len) //也可以简写为 s1 := make([]type, len) //也可以指定容量,其中capacity为可选参数。 make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
3.使用切片
//方式1:定义一个切片,用切片去引用前面创建好的数组 var arr [5]int = [...]int{1, 2, 3, 4, 5} slice1 := arr[1:3] //方式2:通过make来创建切片 var slice2 []int = make([]int, 4, 10) //方式3:定义一个切片,直接指定一个数组 var slice []int = []int{1, 3, 5} var strSlice []string = []string{"tom", "jack", "mary"}
切片的注意事项
- 1、切片初始化时 var slice = arr[startIndex,endIndex]
- 说明:从arr数组下标为startIndex,取到下标为endIndex的元素(不含arr[endIndex])
- 2、切片初始化时,仍然不能越界
- var slice = arr[0:end] 可以简写 var slice = arr[:end]
- var slice = arr[start:len(arr)]可以简写var slice = arr[start:]
- var slice = arr[0:len(arr)] 可以简写 var slice = arr[:]
- 3、cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
- 4、切片定义完后,还不能使用,因为本身是一个空,需要让其引用一个数组或或者make一个空间供切片来使用。
- 5、切片可以继续切片
示例DEMO
package main import ( "fmt" ) func main() { //定义一个数组 var intArr [5]int = [...]int{1, 22, 33, 66, 99} //定义一个切片 //slice是一个切片 //引用了intArr数组的,起始下标为1,终止下标为3(但是不包含3) slice := intArr[1:3] fmt.Println("intArr数组元素是 ", intArr) fmt.Println("slice切片的元素是 ", slice) fmt.Println("slice切片的长度是 ", len(slice)) fmt.Println("slice切片的容量是 ", cap(slice)) //容量 cap //打印intArr[1]的内存中的地址 %p 格式化为0x开头的十六进制 fmt.Printf("intArr[1]的地址=%p\n", &intArr[1]) //打印slice的地址 fmt.Printf("slice[0]的地址=%p\n", &slice[0]) slice[1] = 34 fmt.Println() fmt.Println("intArr数组元素是 ", intArr) fmt.Println("slice切片的元素是 ", slice) }
输出结果:
intArr数组元素是 [1 22 33 66 99] slice切片的元素是 [22 33] slice切片的长度是 2 slice切片的容量是 4 intArr[1]的地址=0xc00006c068 slice[0]的地址=0xc00006c068 intArr数组元素是 [1 22 34 66 99] slice切片的元素是 [22 34]
由此可得出结论:切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用类型的传递机制。
4.遍历切片
for 和 for range两种方式
package main import ( "fmt" ) func main() { var arr [5]int = [...]int{10, 20, 30, 40, 50} slice := arr[1:4] //20,30.40 fmt.Println("--- for ---") for i := 0; i < len(slice); i++ { fmt.Printf("i=%v v=%v \n", i, slice[i]) } fmt.Println("--- for range ---") for i, v := range slice { fmt.Printf("i=%v v=%v \n", i, v) } } //Output //--- for --- //i=0 v=20 //i=1 v=30 //i=2 v=40 //--- for range --- //i=0 v=20 //i=1 v=30 //i=2 v=40
append增加元素或切片
var slice []int = []int{100, 200, 300} fmt.Println("slice=", slice) //输出:slice= [100 200 300] //通过append直接给slice追加具体的元素。元素类型要一致 slice = append(slice, 400, 500, 600) fmt.Println("slice=", slice) //输出:slice= [100 200 300 400 500 600] //切片追加切片 slice = append(slice, slice...) fmt.Println("slice=", slice) //输出:slice= [100 200 300 400 500 600 100 200 300 400 500 600]
append操作的底层原理分析:
- 切片append操作的本质就是对数组扩容
- go底层会创建一下新的数组newArr(按扩容后大小)
- 将slice原来包含的元素拷贝到新的数组newArr
- slice重新引用到newArr
- 注意newArr是在底层来维护的。程序员不可见。
切片的copy(拷贝)
var slice1 []int = []int{1, 2, 3, 4, 5} var slice2 = make([]int, 10) copy(slice2, slice1) ////将slice1的内容拷贝到slice2中 fmt.Println("slice1=", slice1) //slice1= [1 2 3 4 5] fmt.Println("slice2=", slice2) //slice2= [1 2 3 4 5 0 0 0 0 0] slice1[2] = 100 fmt.Println("修改后的slice1=", slice1) //slice1= [1 2 100 4 5] fmt.Println("最后slice2=", slice2) //最后slice2= [1 2 3 4 5 0 0 0 0 0] //copy的时候要求数据类型都是切片; //可以看出:slice1和slice2的数据空间是独立的,相互不影响。
string 和slice
1、string底层是一个byte数组,因此string也可以进行切片处理。
str := "hello@qq.com" //使用切片来获取数组 slice := str[6:] fmt.Println("slice=", slice) //slice= qq.com
2、string和切片在内存的形式,以’abcd’画出内存示意图

3、string是不可变的,也就说不能通过str[0] = ‘z’方式来修改字符串
4、如果需要修改字符串,可以先将string -> []byte /或者 []rune -> 修改 -> 重写转成string
str := "hello@qq.com" //将字符串转为byte数组 arr1 := []byte(str) arr1[0] = 'z' //将切片转为字符串 str = string(arr1) fmt.Println("str = ", str) //str = zello@qq.com //byte类型只能处理英文和数字,不能处理中文,原因是英文和数字占一个字节,而中文会占用3个字节,可以通过[]rune 来处理 arr2 := []rune(str) arr2[0] = '北' str = string(arr2) fmt.Println("str = ", str) //str = 北ello@qq.com