Slice切片

  Go中的数组和PHP中的数组很不一样,没法排序,没法添加。不过Go中有一种数据类型叫做Slice切片,这个数据类型很类似于PHP中的数组。我个人感觉也是利用率比较高的数据类型。

  Slice切片实际上是对一个数组上的连续一段的引用,一个未初始化的分片的值是nil。一个切片,一旦初始化后它总是关联着一个容纳其元素的底层数组,所以一个分片和它的数组共享存储。

定义

slice := make([]type, start_length, capacity)//其中 start_length 作为切片初始长度而 capacity 作为相关数组的长度。

  1. //基本方法
  2. var s1 []string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
  3. //数组引用
  4. array:=[...]string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
  5. s2 := array[:]
  6. //make或者new定义。
  7. //make的调用会创建一个隐含的数组,Slice就是引用的这个数组。
  8. make([]string 5 10) //这两种方法定义的Slice是相等的
  9. new([10]string)[0:5]

pro03_5_1.go

append

  在切片后面追加数据,方法有两种

  1. slice = append(slice, elem1, elem2)//切片后面追加相同的type的元素
  2. slice = append(slice, anotherSlice…)//切片后面追加切片

  第一种用法中,第一个参数为slice,后面可以添加多个参数。

  如果是将两个slice拼接在一起,则需要使用第二种用法,在第二个slice的名称后面加三个点,而且这时候append只支持两个参数,不支持任意个数的参数。

pro03_5_2.go

长度计算

初次使用Go的时候,容易对len和cap混淆,感觉一维切片的长度和容量是一回事,其实不然。

切片是一种结构体(我们会在后面具体讲什么是结构体),大体结构像这样:

  1. struct slice{
  2. ptr *Elem //指针
  3. len int //长度
  4. cap int //容量
  5. }

  先看一个小例子:pro03_5_3.go

  1. package main
  2. import "fmt"
  3. func main() {
  4. slice := []int{5}
  5. slice = append(slice, 7)
  6. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  7. slice = append(slice, 9)
  8. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  9. s1 := append(slice, 11)
  10. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
  11. s2 := append(slice, 12)
  12. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
  13. }

运算结果

通过结果图,我们可以看到len和cap的长度是不一样的。但是他们所指向的地址是相同的。

通过上面的例子还引出一个有趣的问题。把程序稍微修改一下:pro03_5_4.go

  1. package main
  2. import "fmt"
  3. func main() {
  4. slice := []int{5}
  5. slice = append(slice, 7)
  6. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  7. slice = append(slice, 9)
  8. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  9. s1 := append(slice, 11)
  10. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
  11. s2 := append(slice, 12)
  12. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
  13. fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
  14. }

输出结果:slice: [5 7 9] s1: [5 7 9 12] s2 [5 7 9 12]

难道s1不应该是[5 7 9 11]吗?这个时候在看看上面的显示结果图的地址

  1. cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s1) = 0xc082002760
  2. cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s2) = 0xc082002760

地址一样,OMG,这个是怎么回事?我来解释一下,append的实现原理:本质上append是在原有空间中添加,若空间不足时,采用 newSlice := make([]int, len(slice), 2*len(slice)+1)的方式进行扩容。
cap(slice) = 2 len(slice) = 2 ptr(slice) = 0xc082006290 //原始地址
cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 //容量不够,扩容
cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s1) = 0xc082002760 //容量够了,直接追加
cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s2) = 0xc082002760 //容量够了,直接追加

由于s1和s2的地址指向是一样的,所以s1的内容和我们原来想象的不一样。这个就是使用指针需要注意的。
程序换一下:pro03_5_5.go

  1. package main
  2. import "fmt"
  3. func main() {
  4. slice := []int{5}
  5. slice = append(slice, 7)
  6. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  7. slice = append(slice, 9)
  8. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
  9. s1 := make([]int, len(slice))
  10. copy(s1, slice)
  11. s1 = append(s1, 11)
  12. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
  13. s2 := make([]int, len(slice))
  14. copy(s2, slice)
  15. s2 = append(s2, 12)
  16. fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
  17. fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
  18. }

运算结果

运算的结果:slice: [5 7 9] s1: [5 7 9 11] s2: [5 7 9 12]

这次正确了。从这个例子我们可以清晰的看出,切片就是数组的指针。

排序

  Go的切片排序是根据类型来排的, GO分别提供了 sort.Ints() 、 sort.Float64s() 和 sort.Strings() 函数, 默认都是从小到大排序。(没有 sort.Float32s() 函数。)
pro03_5_6.go

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main() {
  7. array := [...]string{"vb", "vc++", "Python", "java", "C lang", "D lang", "Go lang"}
  8. slice := array[:]
  9. fmt.Println(slice, "ptr(slice) =", &slice[0])
  10. sort.Strings(slice)
  11. fmt.Println(slice, "ptr(slice) =", &slice[0])
  12. }

运算结果

我们看到切片的地址没有变化,但是值发生了变化。 实际上在发生sort的时候,执行了三个方法 : Len() 求长度、 Less(i,j) 比较第 i 和 第 j 个元素大小的函数、 Swap(i,j) 交换第 i 和第 j 个元素的函数。

在Go默认的排序方式是从小到大,从大到小的方式稍微复杂一点。pro03_5_7.go

  1. array := [...]string{"vb", "vc++", "Python", "java", "C lang", "D lang", "Go lang"}
  2. slice := array[:]
  3. sort.Strings(slice) //正序排序
  4. sort.Sort(sort.Reverse(sort.StringSlice(slice))) //倒序排序

其他类型的如下:

  1. sort.Sort(sort.Reverse(sort.IntSlice(slice)))
  2. sort.Sort(sort.Reverse(sort.Float64Slice(slice)))

slice,类似python的list.index()方法

golang里面默认没有这个方法,这个方法就是为了查找slice里面是否包含所查询的参数,如果找到返回这个参数所在的index,未找到返回-1。
具体函数如下:

  1. type Element interface{}
  2. // 类似python的list.index()方法,找出slice里面是否包含查询的参数
  3. // 返回值是-1表示没有搜索到。
  4. func SliceIndex(a Element, i interface{}) (result int) {
  5. result = -1
  6. if b, ok := a.([]int); ok {
  7. if c, ok1 := i.(int); ok1 {
  8. for indexC, v := range b {
  9. if v == c {
  10. result = indexC
  11. break
  12. }
  13. }
  14. }
  15. }
  16. if b, ok := a.([]string); ok {
  17. if c, ok1 := i.(string); ok1 {
  18. for indexC, v := range b {
  19. if v == c {
  20. result = indexC
  21. break
  22. }
  23. }
  24. }
  25. }
  26. if b, ok := a.([]float64); ok {
  27. if c, ok1 := i.(float64); ok1 {
  28. for indexC, v := range b {
  29. if v == c {
  30. result = indexC
  31. break
  32. }
  33. }
  34. }
  35. }
  36. return
  37. }
  38. }

会有后续的更改,更改的功能去这里看

一件趣事引发的程序

无意间看到了这样一则新闻:

  1. 程序猿招租广告 手机号竟是一串代码
  2. 日前,有程序员在南京某小区张贴了一份合租广告,表示屋内均是IT行业人士,喜欢安静,所以要求来租者最好是同行或者刚毕业的年轻人。
  3. 而在联系方式部分,这条广告并没有提供明显的手机号,而是一段代码。。。

funny

这个一看就是第一的C语言练习题目,基本就是Go的slice俩习题呀,我们来做一下:

  1. func main() {
  2. var arr = []int{8, 2, 1, 0, 3} //手机号码只是由这5个数字组成
  3. var index = []int{2, 0, 3, 2, 4, 0, 1, 3, 2, 3, 3} //手机号码各位的顺序
  4. tel := ""
  5. for _, i := range index {
  6. tel += strconv.Itoa(arr[i])
  7. }
  8. fmt.Println("联系方式:", tel)
  9. }

运行结果如下:

  1. 联系方式: 18013820100