切片(slice)
数组长度是固定的并且数组长度属于数组类型的一部分,所以数组有很多的局限性。
package main
import "fmt"
func arrsum(x [3]int){
sum:=0
for _,v:=range x{
sum+=v
}
fmt.Println(sum)
}
func main(){
}
这个求和函数只能接受[3]int类型的其他都不支持。
a:=[3]int{1,2,3}
数组a已经有三个元素了,不能继续往a中添加新元素了。
切片
切片(slice)是一个拥有相同类型元素的可变长度序列。它基于数组类型做的一层封装。它非常灵活,支持自动扩容。
切片是一个引用类型,它的内部结构包含,地址,长度和容量。 切片一般用于快速操作一块数据集合。
切片的定义
声明切片类型的语法
var name []T
其中
1.name:表示变量名
2.T:表示切片中的元素类型
package main
import "fmt"
func main(){
//声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整形切片并初始化
var c = []bool{false , true} //声明一个bool类型切片并初始化
var d = []bool{false , true} //声明一个bool切片并初始化
var e []bool
var f []int
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c)
fmt.Println(d)
fmt.Println(a == nil) //true
fmt.Println(b == nil) //false
fmt.Println(c == nil) //false
fmt.Println(e == nil) //true
fmt.Println(f == nil) //true
}
切片的长度和容量
切片拥有自己的长度和容量,我们可以通过内置的len()函数来求长度,使用内置的cap()函数来求容量。
基于数组定义切片
由于切片底层是一个数组,所以我们可以根据数组定义切片。
package main
import "fmt"
func main(){
//由数组得到切片 切割
s1 := [...]int{1,3,5,7,9}
s2 := s1[0:4] //1,3,5,7 左包含右不包含(左闭右开)
s3 := s1[:4] // [0:4]
s4 := s1[3:] // [3:len(s1)]
s5 := s1[:] // [0:len(s1)]
fmt.Println(s2)
fmt.Printf("len(s2):%d cap(s2):%d" , len(s2) , cap(s4))
}
切片再切片
除了基于数组得到切片,我们还可以通过切片得到切片。
package main
import "fmt"
func main(){
//切片再切片
a := [...]string{"北京","上海","广州","深圳","成都","重庆"}
fmt.Printf("a:%v,type:%T,len(a):%d,cap(a):%d\n",a,a,len(a),cap(a))
//切片的容量是指底层数组的容量,底层数组从切片第一个元素到最后一个元素的数量
b :=a[1,3] //len 2 cap 5
fmt.Printf("b:%v,type:%T,len(b):%d,cap(b)%d\n",b,b,len(b),cap(b))
c :=b[1,5] //len 4 cap 5
fmt.Printf("c:%v,type:%T,len(c):%d,cap(b)%d\n",c,c,len(b),cap(b))
a[4] = "曹县" //切片是引用类型,他们都指向了一个底层数组
d := c[3:] //曹县
fmt.Println(d)
}
注意:
1.切片指向了一个底层数组,切片的长度就是它的元素个数。
2.切片的容量是底层数组从切片第一个元素到最后一个元素的数量。
3.对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。
使用make()函数构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置make()函数,格式如下:
make([]T , size ,cap)
其中
T:切片的元素类型
size:切片中元素的数量
cap:切片的容量
package main
import "fmt"
func main(){
a := make([]int , 2 ,10)//不写容量默认是和长度是一样的
b := make([]int , 0 ,10) // [] , 0 ,10
fmt.Println(a) //[0,0]
fmt.Println(len(a)) //2
fmt.Println(cap(10)) //10
}
上面代码中a 的内部存储空间已经分配了10个,但实际上只用了2个。容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)返回该切片的容量。
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针,切片的长度(len),和切片的容量(cap)。
切片框住的是一块连续的内存,只能存储相同的类型变量,并不像php等动态语言那像变量内存是散落的,属于引用类型,真正的数据都是保存在底层数组里面的。
切片是不能直接比较
1.切片是引用类型不能直接比较,只能和nil比较
2.一个nil值切片是没有底层数组的,一个nil值切片的长度和容量都是0
3.一个长度和容量是0的切片不一定都是nil
package main
import "fmt"
func main(){
var s []int //len 0 cap 0 s==nil
s1 := []int{}//len 0 cap 0 s1!=nil
s2 := make([]int , 0)//len 0 cap 0 s2 !=nil
}
注:
判断一个切片是否是空,要用len(a)0而不是 anil
切片的赋值
package main
import "fmt"
func main(){
s1 := make([]int , 3)
s2 := s1 //将s1直接赋值给s2,s1 和 s2共用一个底层数组,对一个切片进行修改会影响另一个切片
s1[0] = 100
fmt.Println(s1)
fmt.Println(s2)
}
切片的遍历
package main
import "fmt"
func main(){
s1 := []int{1,3,5,7,9}
for i:=0;i<len(s1);i++{
fmt.Println(s1[i])
}
for _,v:=range s{
fmt.Println(v)
}
}
append()方法为切片添加元素
Go语言的内建函数append()可以为切片动态添加元素。每个切片都会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会按照一定策略进行扩容,此时该切片指向的底层数组就会更换。"扩容"往往发生在append()函数调用时。
package main
import "fmt"
func main(){
var a []int
for i:=0;i<10;i++{
a = append(a , i) //调用append函数必须要用原来的切片接收返回值
fmt.Printf("%v len:%d cap:%d %p" , a ,len(a) ,cap(a) , a)
}
var b []int{100 , 1000 ,2000}
a = append(a , 10 ,20) //添加多个元素
a = append(a , b...) //添加整个切片 ...表示拆开
}
[0] len:1 cap:1 0xc000016090
[0 1] len:2 cap:2 0xc0000160c0
[0 1 2] len:3 cap:4 0xc00001c0a0
[0 1 2 3] len:4 cap:4 0xc00001c0a0
[0 1 2 3 4] len:5 cap:8 0xc000014100
[0 1 2 3 4 5] len:6 cap:8 0xc000014100
[0 1 2 3 4 5 6] len:7 cap:8 0xc000014100
[0 1 2 3 4 5 6 7] len:8 cap:8 0xc000014100
[0 1 2 3 4 5 6 7 8] len:9 cap:16 0xc000104000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 0xc000104000
切片的扩容原理
1.如果新申请的容量大于两倍的旧容量,最终容量就是新申请的容量。
2.如果旧切片的长度小于1024,最终容量就是旧容量的2倍。
3.如果旧切片的长度大于等于1024,最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于等于新申请的容量。
4.如果最终容量计算溢出,最终容量就是新申请的容量。
5.切片扩容还会根据切片中元素类型的不同而做不同的处理,比如int和string类型的处理方式就不一样。
copy函数复制切片
copy函数格式
copy(destSlice , srcSlice []T)
destSlice:目标切片
srcSlice:数据来源切片
package main
import "fmt"
func main(){
var a =[]int{1,3,5,7}
b:=make([]int , 5 ,5)
c:=a
copy(b , a)
b[0] = 100
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
从切片中删除元素
Go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。
package main
import "fmt"
func main(){
//从切片中删除元素
a:=[]int{1,3,5,7,9}
a =append(a[:2],a[3:]...)
fmt.Print(a)
}
注:
要从切片a中删除索引为index的元素,操作方法是 a=append(a[:index],a[index+1:]…)
append会修改底层数组
package main
import "fmt"
func main(){
a := [...]int{1,3,5}
b:=a[:]
b = append(b[:1],b[2:0]...)
fmt.Println(a) //[1,5,5]
fmt.Println(b) //[1,5]
}
思考:
package main
import "fmt"
func main(){
a := make([]int , 5 ,10)
for i:=0;i<10;i++{
a = append(a , i)
}
fmt.Println(a) // 0,0,0,0,0,1,2,3,4,5,6,7,8,9
}