Go语言入门-常用数据结构之切片slice
定义
概述
A slice is a descriptor for a contiguous segment of an underlying array and provides access to a numbered sequence of elements from that array.The number of elements is called the length of the slice and is never negative. The value of an uninitialized slice is nil. --切片是对底层数组连续的个段的描述符,并提供对该数组中元素的顺序(下标)访问。 元素的数量称为切片的长度,绝不为负。 未初始化的片的值为nil。
这句话体现数组的几个特点
- 切片是对底层数组的一个引用,因此切片是引用类型
- 切片中元素是被编号的(具备下标)
- 切片有长度
切片(Slice)是通过引用底层数组,设定相关的属性,把数据读写操作些安定在指定区域中,切片本是是个只读对象。
切片定义语法
var Slice []Type // 声明切片类型,nil,不会分配空间
array[low:high:max] //切片表达式获取切片
//以下语句等价
make([]Type, len, cap) // 使用make定义切片,分配内存,并初始化
new([cap]Type)[0 : len] // 使用new定义切片,分配内存,并初始化。
切片定义示例
- 示例1
普通切片的声明,类型是[]int 值是nil代表没有引用到具体的底层数组。
func main() {
var slc1 []int //声明一个int切片
//slic1 类型是[]int 但是值是nil, 并为初始化。
fmt.Printf("%#v", slc1)
}
/**
output:
[]int(nil) //没有引用到底层数组
*/
- 示例2
普通切片后声明,并且通过初始化表达式进行初始化。
func main() {
var slc1 []int //声明一个int切片 没有初始化
//slic1 类型是[]int 但是值是nil
fmt.Printf("%#v\n", slc1)
var slc2 = []int{} // 定义一个整型切片并初始化。切片引用runtime.zerobase。
fmt.Printf("%#v\n", slc2)
//切片之间不支持判等
//fmt.Println(slc1 == slc2) //Invalid operation: slc1 == slc2 (operator == not defined on []int)
fmt.Println(slc1 == nil) //没有初始化所以 nil == nil
fmt.Println(slc2 == nil) //初始化以后 slc2引用runtime.zerobase(某一地址),而不是0x00所以为false
}
/**
[]int(nil)
[]int{}
true
false
*/
- 示例3
通过make定义切片,表达式make([]Type, len, cap) cap省略以后默认容量等于len。
func main() {
//make 定义slc1变量。长度2,
var slc1 = make([]int, 2) //没有指定cap,cap等于len
fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
var slc2 = make([]int, 2, 5)
fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
}
/**
output:
slc1 len=[2] cap=[2] value=[[]int{0, 0}]
slc2 len=[2] cap=[5] value=[[]int{0, 0}]
*/
- 示例4
通过new来定义切片( 本质也是切片表达式)
func main() {
//new定义slc1变量。长度2,
var slc1 []int = new([2]int)[:] //实际上是定义一个长度为2的int数组,然后通过切片表达式定义切片
fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
var slc2 = new([2]int)[0:2]
fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
}
/**
slc1 len=[2] cap=[2] value=[[]int{0, 0}]
slc2 len=[2] cap=[2] value=[[]int{0, 0}]
*/
- 示例5
通过切片表达式构造切片
func main() {
// 切片表达式 array[low:high:max]
//完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。
//low省略从0开始,high省略则为len(array),cap省略则为 cap(array) - low
//切片为右半开区间
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
//省略了low high cap 则引用整个底层数组
slc1 := array[:]
fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
//从array数组下标2,到下标4-1(不包括4),默认最大容量为 10-2(cap(array) - low)
slc2 := array[2:4]
fmt.Printf("slc2 len=[%d] cap=[%d] value=[%#v]\n", len(slc2), cap(slc2), slc2)
//从array数组下标2,到下标4-1(不包括4),最大容量为 5-2(high - low)
slc3 := array[2:4:5]
fmt.Printf("slc3 len=[%d] cap=[%d] value=[%#v]\n", len(slc3), cap(slc3), slc3)
//从array数组下标2,到下标4-1(不包括4),最大容量为 10-2(high - low)
slc4 := array[2:4:10]
fmt.Printf("slc4 len=[%d] cap=[%d] value=[%#v]\n", len(slc4), cap(slc4), slc4)
//省略high和max,从array数组下标2,到array[len(array) -1 ],最大容量为 10-2(high - low)
slc5 := array[2:]
fmt.Printf("slc5 len=[%d] cap=[%d] value=[%#v]\n", len(slc5), cap(slc5), slc5)
//省略low和max,从array数组下标0,到array[4 -1 ],最大容量为 10-2(high - low)
slc6 := array[:4]
fmt.Printf("slc6 len=[%d] cap=[%d] value=[%#v]\n", len(slc6), cap(slc6), slc6)
//省略low,从array数组下标0,到array[4 -1],最大容量为 4-0(high - low)
slc7 := array[:4:4]
fmt.Printf("slc7 len=[%d] cap=[%d] value=[%#v]\n", len(slc7), cap(slc7), slc7)
}
/**
slc1 len=[10] cap=[10] value=[[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}]
slc2 len=[2] cap=[8] value=[[]int{3, 4}]
slc3 len=[2] cap=[3] value=[[]int{3, 4}]
slc4 len=[2] cap=[8] value=[[]int{3, 4}]
slc5 len=[8] cap=[8] value=[[]int{3, 4, 5, 6, 7, 8, 9, 10}]
slc6 len=[4] cap=[10] value=[[]int{1, 2, 3, 4}]
slc7 len=[4] cap=[4] value=[[]int{1, 2, 3, 4}]
*/
- 示例6
通过切片表达式构造切片-根据字符串
func main() {
//切片表达式 string[low:high]
s := "erwrwer"
//不能使用cap
slc1 := s[:]
fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc1), slc1)
slc2 := s[4:]
fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc2), slc2)
slc3 := s[:2]
fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc3), slc3)
slc4 := s[1:4]
fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc4), slc4)
//slc1 := s[1:3:4] //Invalid operation s[1:3:4] (3-index slice of string)
//fmt.Printf("slc1 len=[%d] value=[%#v]\n", len(slc1), slc1)
}
/**
output:
slc1 len=[7] value=["erwrwer"]
slc1 len=[3] value=["wer"]
slc1 len=[2] value=["er"]
slc1 len=[3] value=["rwr"]
*/
切片表达式用在string上 cap会失效
切片表达式为 string[low:high]—实际上来说,应用在string上算是一种通过下标范围求取子串。并不是切片。因为通过表达式得到不是切片而是字符串。
因此,切片表达式用在string不会产生切片,只是产生字符串的子串。
- 示例7
通过切片表达式构造切片-根据切片
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
slice := array[1:] //把slice当做数组即可
//对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样。
slc1 := slice[:4]
fmt.Printf("slc1 len=[%d] cap=[%d] value=[%#v]\n", len(slc1), cap(slc1), slc1)
}
/**
output:
slc1 len=[4] cap=[9] value=[[]int{2, 3, 4, 5}]
*/
对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样
切片的使用
赋值与传递
- 示例1
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(array)
slc1 := array[1:]
slc2 := array[3:]
//底层数组使用下标修改元素值,会修改底层数组的元素值
array[4] = 9999
//切片使用下标修改元素值,会修改底层数组的元素值
slc1[6] = 88888
slc2[6] = 77
fmt.Println(slc1)
fmt.Println(slc2)
fmt.Println(array)
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
[2 3 4 9999 6 7 88888 9 77]
[4 9999 6 7 88888 9 77]
[1 2 3 4 9999 6 7 88888 9 77]
*/
数组关联的切片和数组本身都是共同使用一个底层数组的,因此切片类型是值传递
切片遍历
切片的遍历和数组一致,支持fori 和 for range
- 示例1
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(array)
slc1 := array[1:]
//for i 遍历1
for i := 0; i < len(slc1); i++ {
fmt.Printf("%#v ", slc1[i])
}
fmt.Println()
//for range 遍历
for _, v := range slc1 {
fmt.Printf("%#v ", v)
}
fmt.Println()
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10
*/
切片增加元素
go语言中使用内置函数append()追加元素。
追加单个元素
- 示例1
追加单个元素到切片中
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(array)
slc1 := array[1:]
fmt.Println(slc1)
//切片slc1追加一个元素
slc1 = append(slc1, 1000)
fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
slc1 = append(slc1, 1000)
fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
slc1 = append(slc1, 1000)
fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
}
/**
[1 2 3 4 5 6 7 8 9 10]
[2 3 4 5 6 7 8 9 10]
address 0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000]
address 0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000 1000]
address 0xc0000044c0 [2 3 4 5 6 7 8 9 10 1000 1000 1000]
*/
切片追加切片
语法:slice1 = append(slice1, slice2…)
使用…省略号来把添加的切片当成多个元素。并且两个切片的元素类型必须一致。
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(array)
slc1 := array[1:]
slc2 := array[3:]
fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
slc1 = append(slc1, slc2...) //append的第二个参数,送切片变量,然后增加...标识批量增加切片数据
fmt.Println("address ", unsafe.Pointer(&slc1), slc1)
}
/**
output:
[1 2 3 4 5 6 7 8 9 10]
address 0xc0000044c0 [2 3 4 5 6 7 8 9 10]
address 0xc0000044c0 [2 3 4 5 6 7 8 9 10 4 5 6 7 8 9 10]
*/
切片扩容
可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
- 如果新申请容量大于2倍的旧容量,最终容量是新申请的容量。
- 如果旧切片的长度小于1024,则最终容量是老容量的2倍、
-----TODO
切片删除
1.切片头部的删除使用切片表达式
2.切片尾部的删除也是用切片变道时
3. 切边中间的删除比较特殊,使用append。使用的方法
slice = apend(slice[:要删除元素的起始索引], slice[:要删除的截止索引 + 1]…)
- 示例1
func main() {
array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(array)
slc := array[:]
fmt.Println("address ", unsafe.Pointer(&slc), slc)
//从开头删除连续元素
slc = slc[2:] //从开头删除数据
fmt.Println("address ", unsafe.Pointer(&slc), slc)
//从尾部删除连续元素
slc = slc[:7] //从开头删除数据
fmt.Println("address ", unsafe.Pointer(&slc), slc)
//从中间删除元素,使用append
//删掉下标是2,3的数值5和6
//后续的789分别想前移动两位,赋值。
slc = append(slc[0:2], slc[4:]...)
fmt.Println("address ", unsafe.Pointer(&slc), slc)
//底层数组元素值也会变化
fmt.Println("address ", unsafe.Pointer(&array), array)
}
/**
[1 2 3 4 5 6 7 8 9 10]
address 0xc0000044c0 [1 2 3 4 5 6 7 8 9 10]
address 0xc0000044c0 [3 4 5 6 7 8 9 10]
address 0xc0000044c0 [3 4 5 6 7 8 9]
address 0xc0000044c0 [3 4 7 8 9]
address 0xc000016230 [1 2 3 4 7 8 9 8 9 10]
*/
切片拷贝
内建函数copy可以用来赋值前片数据,允许指向同一底层数组,允许目标取现重叠,最后复制长度以较短的切片长度为准。使用copy可以避免切片是引用类型导致修改切片后底层数据也发生变化。
不是同一底层数据的复制,所复制元素的个数以目标切片的长度为准,当源长度小于等于目标长度,全部复制,否则截断源切片多余的长度
- 示例1-截断源长度
func main() {
s := []int{45, 56, 7, 423, 4}
t := [...]int{0, 0}
d := t[:]
fmt.Printf("sour = %#v dest=%#v\n", s, d)
//截断源长度
copy(d, s)
fmt.Println("after copy")
fmt.Printf("sour = %#v dest=%#v\n", s, d)
}
/**
output:
sour = []int{45, 56, 7, 423, 4} dest=[]int{0, 0}
after copy
sour = []int{45, 56, 7, 423, 4} dest=[]int{45, 56}
*/
- 示例2-复制所有源数据
func main() {
s := []int{45, 56, 7, 423, 4}
t := [10]int{0, 0, 9:10}
d := t[:]
fmt.Printf("sour = %#v dest=%#v\n", s, d)
//源切片所有都被赋值。
copy(d, s)
fmt.Println("after copy")
fmt.Printf("sour = %#v dest=%#v\n", s, d)
}
/**
output:
sour = []int{45, 56, 7, 423, 4} dest=[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 10}
after copy
sour = []int{45, 56, 7, 423, 4} dest=[]int{45, 56, 7, 423, 4, 0, 0, 0, 0, 10}
*/
切片排序
go语言标准库中的sort提供了对整型、浮点型、字符串类型进行排序的函数
- 示例1 -排序int切片
func main() {
s := []int{45, 56, 7, 423, 4}
fmt.Println(s)
sort.Ints(s)
fmt.Println("after sort")
fmt.Println(s)
}
/**output
[45 56 7 423 4]
after sort
[4 7 45 56 423]
*/
具体sort的使用后续整理。
总结
关键点:
- 切片表达式用在string不会产生切片,只是产生字符串的子串。
- 对于切片求切片本质和切片对数组一样,只不过 切片的切片的底层数组和切片的底层数组一样
- 数组关联的切片和数组本身都是共同使用一个底层数组的,因此切片类型是值传递
- 不是同一底层数据的复制,所复制元素的个数以目标切片的长度为准,当源长度小于等于目标长度,全部复制,否则截断源切片多余的长度