这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
我们之前学习过golang中的数组,数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。从概念上讲所有语言种的数组基本都是一样的。注意数组一旦定义后,大小不能更改。大家可以回顾一下:跟我一起来学golang之《数组》。本篇文章我们学一下数组的升级『切片』。
什么是切片
Go 语言切片是对数组的抽象。 Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片是一种方便、灵活且强大的包装器。切片本身没有任何数据。它们只是对现有数组的引用。
切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由。
从概念上面来说切片像一个结构体,这个结构体包含了三个元素:
- 指针,指向数组中的开始位置
- 长度,即切片的长度
- 最大长度,也就是切片开始位置到数组的最后位置的长度
提示:切片只是引用数组,所以效率非常高,例如在函数传参的时候,使用切片传递数组参数,不会复制数组。
我们怎么区分是数组还是切片呢?只要看在定义语法中是否有初始大小。有定义初始大小也就是[]中有值的就是数据,否则是切片。
定义切片
var identifier []type
切片不需要说明长度。 或使用make()函数来创建切片:
var slice0 []type = make([]type, len)
也可以简写为
slice0 := make([]type, len)
make([]T, length, capacity)
初始化
s[0] = 1
s[1] = 2
s[2] = 3
s :=[] int {1,2,3 }
s := arr[startIndex:endIndex]
将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片(前闭后开),长度为endIndex-startIndex
s := arr[startIndex:] //缺省endIndex时将表示忽略最后一个元素,也就是一直到最后
s := arr[:endIndex] //缺省startIndex时将表示忽略第一个元素开始,也就是从头开始
package main
import (
"fmt"
)
func main() {
a := [5]int{1, 2, 3, 4, 5}
var b []int = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b)
}
修改切片
slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。
示例代码:
package main
import (
"fmt"
)
func main() {
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59} //定义数组
dslice := darr[2:5] //变成切片
fmt.Println("array before",darr)
for i := range dslice {
dslice[i]++
}
fmt.Println("array after",darr)
}
运行结果:
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]
当多个片共享相同的底层数组时,每个元素所做的更改将在数组中反映出来。
示例代码:
package main
import (
"fmt"
)
func main() {
numa := [3]int{78, 79 ,80}
nums1 := numa[:] //将数组变成切片
nums2 := numa[:]
fmt.Println("array before change 1",numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
}
运行结果:
array before change 1 [78 79 80]
array after modification to slice nums1 [100 79 80]
array after modification to slice nums2 [100 101 80]
len() 和 cap() 函数
切片的长度是切片中元素的数量。切片的容量是从创建切片的索引开始的底层数组中元素的数量。
切片是可索引的,并且可以由 len() 方法获取长度,切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少
package main
import "fmt"
func main() {
var numbers = make([]int,3,5)
printSlice(numbers)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果
len=3 cap=5 slice=[0 0 0]
空切片
一个切片在未初始化之前默认为 nil,长度为 0,nil 切片不能用来存数据。
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
if(numbers == nil){
fmt.Printf("切片是空的")
}
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果
len=0 cap=0 slice=[]
切片是空的
package main
import "fmt"
func main() {
/* 创建切片 */
numbers := []int{0,1,2,3,4,5,6,7,8}
printSlice(numbers)
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
numbers1 := make([]int,0,5)
printSlice(numbers1)
/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)
/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]
append() 和 copy() 函数
append 向切片里面追加一个或者多个元素,然后返回一个和切片一样类型的切片。与java中的list类的add方法类似。
copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数。
append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。这句话需要好好进行理解。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
运行结果
len=0 cap=0 slice=[]
len=1 cap=2 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=8 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
numbers1与numbers两者不存在联系,numbers发生变化时,numbers1是不会随着变化的。也就是说copy方法是不会建立两个切片的联系的
package main
import "fmt"
func main() {
/*
append(),向切片的末尾追加数据
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
*/
s1 :=[] int{1,2,3,4}
s2 :=[] int{5,6,7,8}
//方式一:
//for i:=0;i<len(s2);i++{
// s1 = append(s1,s2[i])
//}
// 方法二:
s1 = append(s1,s2...)
fmt.Println(s1)
fmt.Println("---------------")
//删除切片中的内容
s3 := []int{1,2,3,4,5,6,7,8,9}
del:=3//要删除的元素的下标
//fmt.Println(s3[:del])
//fmt.Println(s3[del+1:])
s3 = append(s3[:del],s3[del+1:]...)
fmt.Println(s3)
}
copy()方法的使用:
package main
import "fmt"
func main() {
/*
copy(),
切片是引用类型,传递的是地址
深拷贝和浅拷贝
深拷贝:拷贝的是数据
值类型都是深拷贝,基本类型,数组
浅拷贝:拷贝的是地址
引用类型默认都是浅拷贝,切片,map
*/
s1 := []int{1,2,3,4,5}
s2 := s1
fmt.Println(s1,s2)
s2[0] =100
fmt.Println(s1,s2)
a := 100
b := a
fmt.Println(a,b)
b = 200
fmt.Println(a,b)
fmt.Println("-------------")
m:=[]int{1,2,3,4,5}
n:=[]int{7,8,9}
fmt.Println(m)
fmt.Println(n)
//copy(m,n) //将n中的数据,拷贝到m里
//copy(n,m)//将m中的数据,拷贝到n里
//copy(n,m[1:4])//将m中的下标1到3的数据,拷贝到n里
copy(n[1:],m[3:]) //将m中的下标3到最后的数据,拷贝到n的下标1之后
fmt.Println(m)
fmt.Println(n)
s3 :=[]int{1,2,3,4}
s4 := s3 //浅拷贝
s5:=make([]int,4,4)
copy(s5,s3)//深拷贝
fmt.Println(s4)
fmt.Println(s5)
s3[0] = 100
fmt.Println(s4)
fmt.Println(s5)
}
切片的扩容
当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增长)。一旦出现扩容则会返回新的数组空间,原数组空间不变,也就是对新的扩容后的数组空间进行操作不会体现在原数组空间上。
package main
import (
"fmt"
)
func main() {
/*
切片叫做变长数组,长度不固定
len(),cap()
当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增长)
3-->6-->12-->24-->48
4-->8-->16-->32
*/
arr := [10]int{1,2,3,4,5,6,7,8,9,10}
fmt.Println(arr)
fmt.Printf("%p\n",&arr) //打印输出数组自己的地址
s1 := arr[:5]
fmt.Println(s1)
fmt.Printf("%p,长度:%d,容量:%d\n",s1,len(s1),cap(s1)) //长度:5,容量:10
s2 := arr[2:7]
fmt.Println(s2)
fmt.Printf("%p,长度:%d,容量:%d\n",s2,len(s2),cap(s2)) //长度:5,容量:8
//修改切片的数据,其实是修改该切片所指向的底层数组的数据。
//导致所有指向该数组的切片的数据,都会改变。
s1[3] = 100
fmt.Println(arr)
fmt.Println(s1)
fmt.Println(s2)
//append(),追加数据
s1 = append(s1,1,1,1,1) //s1的长度:5,容量:10
fmt.Println(arr) //[1 2 3 100 5 1 1 1 1 10]
fmt.Println(s1) //[1 2 3 100 5 1 1 1 1]
fmt.Println(s2) //[3 100 5 1 1]
//追加的时候,涉及到了扩容,会更改切片指向的底层数组
//s2:len5,cap8
s2= append(s2, 2,2,2,2,2)
//更改s2指向的底层数组
fmt.Println(arr)//[1 2 3 100 5 1 1 1 1 10]
fmt.Println(s1) //[1 2 3 100 5 1 1 1 1]
fmt.Println(s2) //[3 100 5 1 1 2 2 2 2 2]
}
切片是引用类型
package main
import "fmt"
func main() {
/*
值类型:数组,int,float,string,bool
传递的是数据副本
引用类型:
传递的地址,多个变量指向了同一块内存地址,
*/
arr1 :=[4]int{1,2,3,4}
arr2 := arr1//传递的是数据
fmt.Println(arr1,arr2)
arr2[0]=100
fmt.Println(arr1,arr2)
s1 :=[]int{1,2,3,4}
fmt.Println(s1)
s2 := s1 //传递的地址
fmt.Println(s2)
s2[0]= 100
fmt.Println(s1,s2)
}
运行结果:
[1 2 3 4] [1 2 3 4]
[1 2 3 4] [100 2 3 4]
[1 2 3 4]
[1 2 3 4]
[100 2 3 4] [100 2 3 4]