切片(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
}