切片的引入
- 切片(slice)是golang中一种特有的数据类型
- 数组有特定的用处,但是却有一些呆板(数组长度固定不可变当然可以使用函数添加),所以在Go语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
- 切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
ackage main
import "fmt"
func main() {
//首先定义数组
arr := [...]int{1,2,3,4,5,6}
//切片:够建在数组之上
var slice1, slice2 []int //切片长度可变
slice1 = arr[1:3] //切出一个片段,索引从1开始,3位终止索引,不包含
slice3 := arr[1:4]
slice2 = arr[:] //整个数组
fmt.Println(slice1)
fmt.Println(slice2)
fmt.Println(slice3)
//类型
fmt.Printf("arr类型:%T\n",arr)
fmt.Printf("slice1类型:%T\n",slice1)
fmt.Printf("slice2类型:%T\n",slice2)
fmt.Printf("slice3类型:%T\n",slice3)
//获取切片容量——可以动态变化
fmt.Println("slice1容量:",cap(slice1))
fmt.Println("slice2容量:",cap(slice2))
fmt.Println("slice3容量:",cap(slice3))
}
[2 3]
[1 2 3 4 5 6]
[2 3 4]
arr类型:[6]int
slice1类型:[]int
slice2类型:[]int
slice3类型:[]int
slice1容量: 5
slice2容量: 6
slice3容量: 5
- 首先对切片的赋值是建立在数组上的,通过指定索引,如果不指定默人开头和结尾(包含最后元素),但不管是整个数组还是片段,都需要使用
[:]
格式 - 通过输出我门可以看见,数组类型为
[长度]数据类型
,切片为[]数据类型
,所以不指定长度 - 切片的容量大小是动态变化的,且不一定等于元素的大小。
内存分析
切片再底层由3个字段的数据结构:指向底层数组的指针、切片长度、切片容量
//地址关系
fmt.Printf("数组1的地址:%p\n",&arr[1])
fmt.Printf("切片0位置的地址:%p\n",&slice1[0])
fmt.Printf("切片变量位置的地址:%p\n",&slice1)
//切片改变数组值
arr[1] = 11
slice1[1] = 10
fmt.Println(arr)
fmt.Println(slice1)
数组1的地址:0x14000118008
切片0位置的地址:0x14000118008
切片0位置的地址:0x1400011a000
[1 11 10 4 5 6]
[11 10]
由程序可以看出:
- 切片变量结构体实际上是一个单独的变量,该结构体变量由上面的3个部分组成
- 切片的指针指向引用数组的元素地址上
- 切片这个结构体中指针指向的是引用的数组地址,但不是说切片就是一个指针,他是一个结构题
- 切片为引用数据类型,通过改变切片元素的值,可以改变数组的值,当然,作为底层数据,改变数组的值,切片引用的是数组空间存储的值,那么切片值也会变。
切片的定义
- 定义一个切片,然后让切片去引用一个已经创建好的数组。
//方式1:通过引用数组定义
arr := [...]int {1,2,3,4,5,6,7}
//引用全部
slice1 := arr[:]
//引用片段
slice2 := arr[1:3]
fmt.Println(slice1)
fmt.Println(slice2)
[1 2 3 4 5 6 7]
[2 3]
- 通过make内置函数来创建切片。
make()
底层创建一个数组,对外不可见,所以不能直接操作这个数组,需要使用slice去间接访问各元素。基本语法:make(数据类型, 长度 , [容量])
//方式2:通过make内置函数创建-可不指定容量(只会默认长度容量)
slice3 := make([]int, 4)
slice4 := make([]int, 6, 8)
fmt.Println(cap(slice3))
fmt.Println(cap(slice4))
fmt.Println(slice3)
fmt.Println(slice4)
4
8
[0 0 0 0]
[0 0 0 0 0 0]
- 定一个切片,直接就指定具体数组,使用原理类似make的方式。
//方式3:定义一个切片,直接就指定一个具体的数组——和方式2一样,不能直接操作着这个数组
slice5 := []int {1,2,3}
fmt.Println(slice5 )
fmt.Println(cap(slice5))
[1 2 3]
3
所以,不管是什么方式,都是对数组的引用,使用make是创建一个数组空间,方式3是直接创建一个具体的数组。后两种方式只能使用slice去维护/操作数组,第一种方式可以通过数组直接操作。
切片相较于数组,主要区别在于亮点:1.数组需要固定长度,切片则不需要指定。2.数组为值类型,切片为引用类型
切片的遍历
- 普通for循环
- for-range循环
package main
import "fmt"
func main() {
//slice traceral
//slice definition
slice := []int{1,2,3,4,5}
//方式1:for
for i := 0; i < len(slice); i++ {
fmt.Printf("alice[%v]=%v\t",i,slice[i])
}
//方式2:for-range
fmt.Println("\n-------------------for-range-------------------")
for i, v := range slice {
fmt.Printf("alice[%v]=%v\t",i,v)
}
}
alice[0]=1 alice[1]=2 alice[2]=3 alice[3]=4 alice[4]=5
-------------------for-range-------------------
alice[0]=1 alice[1]=2 alice[2]=3 alice[3]=4 alice[4]=5
切片注意事项
- 切片在定义之后不能直接使用,需要让其引用到一个数组
var slice []int
fmt.Println(slice)
[]
- 切片使用不能越界
var slice []int
slice = []int{1,2,3,4}
fmt.Println(slice[3])
fmt.Println(slice[4]) //越界:index out of range [4] with length 4
4
panic: runtime error: index out of range [4] with length 4
goroutine 1 [running]:
main.main()
/Users/dearfriend/golang/code/chapter8_slice/demo4/main.go:8 +0x88
exit status 2
- 切片简写使用:
arr[:]
索引省略 - 切片可以继续切片(对切片进行切片)切片都是指向数组,所以改变切片的值,其指向空间的值改变,对应的,指向该空间的所有切片都会改变
var slice []int
slice = []int{1,2,3,4}
//对切片进行切片
slice2 := slice[1:2]
fmt.Println(slice)
fmt.Println(slice2)
//改变二次切片的值,所有值都变
slice2[0] = 9
fmt.Println(slice)
fmt.Println(slice2)
[1 2 3 4]
[2]
[1 9 3 4]
[9]
- 切片可以动态增长:使用
append
函数进行增加
append
函数在底层实际上是创建了一个新的数组,是否创建新数组,是否改变原数组取决于切片容量,见PSappend(slice1,1,2,3)
表示创建一个新的数组,将原有元素放入,在将添加的元素加在后面 ,所以,原来的slice所指向的空间还是不变,不会指向新的数组。可以使用slice = append(slice,1,2)
.append函数原数据智能传入切片——老数组扩容成新数组(切片指向新数组,不再指向原数组,原数组不变)
那么定义的新数组,和前面说的make定义一样,没有数组名,只能间接使用切片进行数组的维护
//切片动态增长
fmt.Println("================切片动态增长===============")
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("原切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
//slice追加元素
slice = append(slice,9,8,10,11)
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("新切片指向的地址:%p\n",&slice[0])
================切片动态增长===============
slice的长度为:3 slice的容量为:5
原切片指向的地址:0x14000016098
-----------------------------------------
slice的长度为:7 slice的容量为:10
新切片指向的地址:0x1400001a050
还可以添加切片
//切片动态增长
fmt.Println("================切片动态增长===============")
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("原切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
//slice追加元素
slice = append(slice,9,8,10,11)
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("新切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
//slice追加切片
slice1 := []int{1,2,3}
slice = append(slice,slice1...)
fmt.Printf("追加slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("新切片指向的地址:%p\n",&slice[0])
================切片动态增长===============
slice的长度为:3 slice的容量为:5
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:7 slice的容量为:10
新切片指向的地址:0x140000b6000
-----------------------------------------
追加slice的长度为:10 slice的容量为:10
新切片指向的地址:0x140000b6000
PS:如果添加的元素超过原切片的容量,那么会创建新数组,且切片指向新的数组;如果没有超过原有切片的容量,那么,则会在原切片上增加元素,并且所引用的数组也会改变
package main
import "fmt"
func main () {
arr := [...]int{1,2,3,4,5,6}
var slice []int
slice = arr[1:4]
//对切片进行切片
slice2 := slice[1:2]
fmt.Println(slice)
fmt.Println(slice2)
//改变二次切片的值,所有值都变
slice2[0] = 9
fmt.Println(slice)
fmt.Println(slice2)
//切片动态增长
fmt.Println("================切片动态增长===============")
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("原切片为:%v\n",slice)
fmt.Printf("原数组为:%v\n",arr)
fmt.Printf("原切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
//slice追加元素
slice = append(slice,9,8)
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("新切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
fmt.Printf("新切片为:%v\n",slice)
fmt.Printf("新数组为:%v\n",arr)
}
================切片动态增长===============
slice的长度为:3 slice的容量为:5
原切片为:[2 9 4]
原数组为:[1 2 9 4 5 6]
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:5 slice的容量为:5
新切片指向的地址:0x140000b0008
-----------------------------------------
新切片为:[2 9 4 9 8]
新数组为:[1 2 9 4 9 8]
//切片动态增长
fmt.Println("================切片动态增长(超过切片容量)===============")
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("原切片为:%v\n",slice)
fmt.Printf("原数组为:%v\n",arr)
fmt.Printf("原切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
//slice追加元素
slice = append(slice,9,8,10)
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
fmt.Printf("新切片指向的地址:%p\n",&slice[0])
fmt.Println("-----------------------------------------")
fmt.Printf("新切片为:%v\n",slice)
fmt.Printf("新数组为:%v\n",arr)
================切片动态增长(超过切片容量)===============
slice的长度为:3 slice的容量为:5
原切片为:[2 9 4]
原数组为:[1 2 9 4 5 6]
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:6 slice的容量为:10
新切片指向的地址:0x140000b6050
-----------------------------------------
新切片为:[2 9 4 9 8 10]
新数组为:[1 2 9 4 5 6]
切片初始容量实际上是通过数组长度决定的,如果一个数组长度为6,切片从1开始切,则切片会留下剩下的数组长度作为容量
arr := [...]int{1,2,3,4,5,6,7}
var slice []int
slice = arr[1:4]
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
slice的长度为:3 slice的容量为:6
arr := [...]int{1,2,3,4,5,6} //减少一个数组长度
var slice []int
slice = arr[1:4]
fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
slice的长度为:3 slice的容量为:6
所以着也能解释为什么使用slice := []int{1,2,3,4}
这样定义slice,其长度为4,因为数组的长度只有4,且此处,引用的整个数组。
- 切片的拷贝——
copy
函数
//切片拷贝
fmt.Println("================切片拷贝===============")
//创建新的切片空间
slice3 := make([]int,10)
//拷贝slice
copy(slice3,slice)
fmt.Println(slice3)
================切片拷贝===============
[2 9 4 9 8 10 0 0 0 0]