参考的文章

Golang-100-Days-切片的使用/day07_Slice%E7%9A%84%E4%BD%BF%E7%94%A8.md)

1.1 什么是切片

Go语言切片是对数组的抽象。Go语言中数组的长度是不可改变的,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型-切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时候可能使切片的容量增大。
切片与数组相比,不需要设定长度,在[]中不用设定值,他们只是对现有数组的引用。
从概念上面说slice像一个结构提,这个结构提包含了三个元素:

  1. 指针,指向数组中slice指定的开始位置
  2. 长度,即slice的长度
  3. 最大长度,也就是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]

  1. 也可以直接声明
  2. ```go
  3. s1 := [5]int32{1, 2, 3}
  4. fmt.Println(s1) // [1 2 3 0 0]
  5. s2 := []int32{1, 2, 3}
  6. fmt.Println(s2) // [1 2 3]
  7. // 注意2者区别,上面是定义数组,下面是定义切片

也可以通过数组索引创建切片(前闭后开)

  1. s1 := [5]int32{1, 2, 3, 4, 5}
  2. fmt.Println(s1) // [1 2 3 4 5]
  3. s2 := s1[2:4]
  4. fmt.Println(s2) // [3 4]
  5. s3 := s1[:3]
  6. fmt.Println(s3) // [1 2 3]
  7. s4 := s1[2:]
  8. fmt.Println(s4) // [3 4 5]

1.3 修改切片

slice没有自己的任何数据。它只是底层数组的一个表示。对slice所做的任何修改都将反映在底层数组中。

  1. s1 := [5]int32{1, 2, 3, 4, 5}
  2. fmt.Println("s1 value:", s1)
  3. s2 := s1[3:]
  4. fmt.Println("s2 value:", s2)
  5. s2[0] = 44
  6. s2[1] = 55
  7. fmt.Println("updated s2 value: ", s2)
  8. fmt.Println("update s1 value: ", s1)
  9. /*
  10. s1 value: [1 2 3 4 5]
  11. s2 value: [4 5]
  12. updated s2 value: [44 55]
  13. update s1 value: [1 2 3 44 55]
  14. */

1.4 len()函数和cap()函数

切片的长度是切片中元素的数量。切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
切片是可索引的,并且可以由len()方法获取长度,通过cap()测量切片最长可以达到最少

  1. s := make([]int, 3, 5)
  2. arr := [10]int{2: 4, 5: 10}
  3. fmt.Printf("%v\n", arr)
  4. fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
  5. s = arr[3:]
  6. fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
  7. /*
  8. [0 0 4 0 0 10 0 0 0 0]
  9. len=3 cap=5 slice=[0 0 0]
  10. len=7 cap=7 slice=[0 0 10 0 0 0 0]
  11. */

1.5 空切片

一个切片在未初始化之前默认为nil,长度为0

  1. var s []int
  2. fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
  3. if (s == nil) {
  4. fmt.Println("切片是空的")
  5. }
  6. s1 := [3]int{1, 2, 4}
  7. fmt.Printf("%v\n", s1)
  8. s = s1[:] // 将数组转为slice
  9. fmt.Printf("len=%d cap=%d slice=%v\n", len(s), cap(s), s)
  10. /*
  11. len=0 cap=0 slice=[]
  12. 切片是空的
  13. [1 2 4]
  14. len=3 cap=3 slice=[1 2 4]
  15. */

1.6 append()和copy()函数

append():向slice里追加一个或多个元素,然后返回一个和slice一样类型的slice
copy():从源slice的src中复制元素到目标dst,并且返回复制的元素个数
tips:appand函数会改变slice所引用的数组的内容,从而影响到引用同意数组的其他slice。但当slice中没有剩余空间(cap-len==0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其他引用此数组的slice不受影响。

  1. var s1 []int
  2. fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
  3. // 允许追加空切片
  4. s1 = append(s1, 0)
  5. fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
  6. // 向切片添加一个元素
  7. s1 = append(s1, 1)
  8. fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
  9. // 同时添加多个元素
  10. s1 = append(s1, 2, 3, 4)
  11. fmt.Printf("len=%d cap=%d slice=%v\n", len(s1), cap(s1), s1)
  12. // 创建切片,容量是之前切片的两倍
  13. s2 := make([]int, len(s1), (cap(s1))*2)
  14. fmt.Printf("len=%d cap=%d slice=%v\n", len(s2), cap(s2), s2)
  15. // 拷贝s1内容到s2
  16. copy(s2, s1)
  17. fmt.Printf("len=%d cap=%d slice=%v\n", len(s2), cap(s2), s2)
  18. /*
  19. len=0 cap=0 slice=[]
  20. len=1 cap=1 slice=[0]
  21. len=2 cap=2 slice=[0 1]
  22. len=5 cap=6 slice=[0 1 2 3 4]
  23. len=5 cap=12 slice=[0 0 0 0 0]
  24. len=5 cap=12 slice=[0 1 2 3 4]
  25. */

s2与s1两者不存在联系,s1发生变化时,s2是不会随着变化的,也就是说copy方法是不会建立两个切片之间的联系。

2.1 字符串、数组和切片的应用

2.1.1 从字符串生成切片

  1. func main() {
  2. strToSlice1()
  3. }
  4. func strToSlice1() {
  5. s := "hello "
  6. c := []byte(s)
  7. c = append(c, 'w', 'o', 'r', 'l', 'd') // 追加英文
  8. fmt.Println(string(c))
  9. d := "你好 "
  10. result := []rune(d)
  11. arr := []rune("世界")
  12. for _, v := range arr {
  13. result = append(result, v) // 追加中文,遍历获取
  14. }
  15. fmt.Println(string(result))
  16. }
  17. /*
  18. hello world
  19. 你好 世界
  20. */

2.1.2 字符串和切片的内存结构

在内存中,一个字符串实际上是一个双字结构,即一个指向实际数组的指针和记录字符串长度的整数(见下图)
字符串 string s = “hello” 和子字符串 t = s[2:3] 在内存中的结构可以用下图表示:
image.png

2.1.3 修改字符串中的某个字符

Go中字符串默认是不可修改的,只能通过转换为切片,然后才能进行索引赋值,示例:

  1. func main() {
  2. updateStr()
  3. }
  4. func updateStr() {
  5. s1 := "hello"
  6. fmt.Println(s1)
  7. s2 := []byte(s1)
  8. s2[0] = 'H'
  9. fmt.Println(string(s2))
  10. s3 := "你好"
  11. fmt.Println(s3)
  12. s4 := strings.Split(s3, "")
  13. s4[0] = "您"
  14. fmt.Println(strings.Join(s4,""))
  15. }
  16. /*
  17. hello
  18. Hello
  19. 你好
  20. 您好
  21. */

2.1.4 用切片进行排序

有时候普通的排序并不能起到很好的作用,因此我们需要自己定义排序的规则,切片可以很好的用来进行排序,一般稍微复杂的结构都是通过结构体和切片进行排序的,具体的例子:
通过结构体与切片结合对map进行排序
借助sort.Interface接口进行排序