参考的文章
Golang-100-Days-切片的使用/day07_Slice%E7%9A%84%E4%BD%BF%E7%94%A8.md)
1.1 什么是切片
Go语言切片是对数组的抽象。Go语言中数组的长度是不可改变的,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型-切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时候可能使切片的容量增大。
切片与数组相比,不需要设定长度,在[]中不用设定值,他们只是对现有数组的引用。
从概念上面说slice像一个结构提,这个结构提包含了三个元素:
- 指针,指向数组中slice指定的开始位置
- 长度,即slice的长度
- 最大长度,也就是slice开始位置到最后位置的长度
1.2 切片的语法
切片不需要说明长度,使用make()函数分配内存创建切片: ```go s := make([]int32, 10) s[0] = 1 s[1] = 2 s[4] = 5 fmt.Println(s)
// [1 2 0 0 5 0 0 0 0 0]
也可以直接声明
```go
s1 := [5]int32{1, 2, 3}
fmt.Println(s1) // [1 2 3 0 0]
s2 := []int32{1, 2, 3}
fmt.Println(s2) // [1 2 3]
// 注意2者区别,上面是定义数组,下面是定义切片
也可以通过数组索引创建切片(前闭后开)
s1 := [5]int32{1, 2, 3, 4, 5}
fmt.Println(s1) // [1 2 3 4 5]
s2 := s1[2:4]
fmt.Println(s2) // [3 4]
s3 := s1[:3]
fmt.Println(s3) // [1 2 3]
s4 := s1[2:]
fmt.Println(s4) // [3 4 5]
1.3 修改切片
slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。
s1 := [5]int32{1, 2, 3, 4, 5}
fmt.Println("s1 value:", s1)
s2 := s1[3:]
fmt.Println("s2 value:", s2)
s2[0] = 44
s2[1] = 55
fmt.Println("updated s2 value: ", s2)
fmt.Println("update s1 value: ", s1)
/*
s1 value: [1 2 3 4 5]
s2 value: [4 5]
updated s2 value: [44 55]
update s1 value: [1 2 3 44 55]
*/
1.4 len()函数和cap()函数
切片的长度是切片中元素的数量。切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片是可索引的,并且可以由len()方法获取长度,通过cap()测量切片最长可以达到最少
s := make([]int, 3, 5)
arr := [10]int{2: 4, 5: 10}
fmt.Printf("%v\n", arr)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
s = arr[3:]
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
/*
[0 0 4 0 0 10 0 0 0 0]
len=3 cap=5 slice=[0 0 0]
len=7 cap=7 slice=[0 0 10 0 0 0 0]
*/
1.5 空切片
一个切片在未初始化之前默认为nil,长度为0
var s []int
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
if (s == nil) {
fmt.Println("切片是空的")
}
s1 := [3]int{1, 2, 4}
fmt.Printf("%v\n", s1)
s = s1[:] // 将数组转为slice
fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
/*
len=0 cap=0 slice=[]
切片是空的
[1 2 4]
len=3 cap=3 slice=[1 2 4]
*/
1.6 append()和copy()函数
append():向slice里追加一个或多个元素,然后返回一个和slice一样类型的slice
copy():从源slice的src中复制元素到目标dst,并且返回复制的元素个数
tips:appand函数会改变slice所引用的数组的内容,从而影响到引用同意数组的其他slice。但当slice中没有剩余空间(cap-len==0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其他引用此数组的slice不受影响。
var s1 []int
fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
// 允许追加空切片
s1 = append(s1, 0)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
// 向切片添加一个元素
s1 = append(s1, 1)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
// 同时添加多个元素
s1 = append(s1, 2, 3, 4)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
// 创建切片,容量是之前切片的两倍
s2 := make([]int, len(s1), (cap(s1))*2)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s2), cap(s2), s2)
// 拷贝s1内容到s2
copy(s2, s1)
fmt.Printf("len=%d cap=%d slice=%v\n", len(s2), cap(s2), s2)
/*
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 0 0 0 0]
len=5 cap=12 slice=[0 1 2 3 4]
*/
s2与s1两者不存在联系,s1发生变化时,s2是不会随着变化的,也就是说copy方法是不会建立两个切片之间的联系。
2.1 字符串、数组和切片的应用
2.1.1 从字符串生成切片
func main() {
strToSlice1()
}
func strToSlice1() {
s := "hello "
c := []byte(s)
c = append(c, 'w', 'o', 'r', 'l', 'd') // 追加英文
fmt.Println(string(c))
d := "你好 "
result := []rune(d)
arr := []rune("世界")
for _, v := range arr {
result = append(result, v) // 追加中文,遍历获取
}
fmt.Println(string(result))
}
/*
hello world
你好 世界
*/
2.1.2 字符串和切片的内存结构
在内存中,一个字符串实际上是一个双字结构,即一个指向实际数组的指针和记录字符串长度的整数(见下图)
字符串 string s = “hello” 和子字符串 t = s[2:3] 在内存中的结构可以用下图表示:
2.1.3 修改字符串中的某个字符
Go中字符串默认是不可修改的,只能通过转换为切片,然后才能进行索引赋值,示例:
func main() {
updateStr()
}
func updateStr() {
s1 := "hello"
fmt.Println(s1)
s2 := []byte(s1)
s2[0] = 'H'
fmt.Println(string(s2))
s3 := "你好"
fmt.Println(s3)
s4 := strings.Split(s3, "")
s4[0] = "您"
fmt.Println(strings.Join(s4,""))
}
/*
hello
Hello
你好
您好
*/
2.1.4 用切片进行排序
有时候普通的排序并不能起到很好的作用,因此我们需要自己定义排序的规则,切片可以很好的用来进行排序,一般稍微复杂的结构都是通过结构体和切片进行排序的,具体的例子:
通过结构体与切片结合对map进行排序
借助sort.Interface接口进行排序