目录

3.5 Slice 切片

优质
小牛编辑
111浏览
2023-12-01

Slice切片

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

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

定义

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

//基本方法
var s1 []string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
//数组引用
array:=[...]string{"vb","vc++","Python","java","C lang","D lang","Go lang"}
s2 := array[:]

//make或者new定义。
//make的调用会创建一个隐含的数组,Slice就是引用的这个数组。
make([]string, 5, 10)  //这两种方法定义的Slice是相等的
new([10]string)[0:5]

pro03_5_1.go

append

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

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

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

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

pro03_5_2.go

长度计算

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

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

struct slice{
    ptr *Elem  //指针
    len int    //长度
    cap int    //容量
 }

  先看一个小例子:pro03_5_3.go

package main

import "fmt"

func main() {
    slice := []int{5}
    slice = append(slice, 7)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    slice = append(slice, 9)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    s1 := append(slice, 11)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
    s2 := append(slice, 12)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])

}

运算结果

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

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

package main

import "fmt"

func main() {
    slice := []int{5}
    slice = append(slice, 7)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    slice = append(slice, 9)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    s1 := append(slice, 11)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
    s2 := append(slice, 12)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
    fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
}

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

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

cap(slice) = 4 len(slice) = 3 ptr(slice) = 0xc082002760 ptr(s1) = 0xc082002760
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

package main

import "fmt"

func main() {
    slice := []int{5}
    slice = append(slice, 7)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    slice = append(slice, 9)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0])
    s1 := make([]int, len(slice))
    copy(s1, slice)
    s1 = append(s1, 11)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s1) =", &s1[0])
    s2 := make([]int, len(slice))
    copy(s2, slice)
    s2 = append(s2, 12)
    fmt.Println("cap(slice) =", cap(slice), "len(slice) =", len(slice), "ptr(slice) =", &slice[0], "ptr(s2) =", &s2[0])
    fmt.Println("slice:", slice, "s1:", s1, "s2:", s2)
}

运算结果

运算的结果: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

package main

import (
    "fmt"
    "sort"
)

func main() {
    array := [...]string{"vb", "vc++", "Python", "java", "C lang", "D lang", "Go lang"}
    slice := array[:]
    fmt.Println(slice, "ptr(slice) =", &slice[0])
    sort.Strings(slice)
    fmt.Println(slice, "ptr(slice) =", &slice[0])
}

运算结果

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

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

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

其他类型的如下:

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

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

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

type Element interface{}
// 类似python的list.index()方法,找出slice里面是否包含查询的参数
// 返回值是-1表示没有搜索到。
func SliceIndex(a Element, i interface{}) (result int) {
    result = -1
    if b, ok := a.([]int); ok {
        if c, ok1 := i.(int); ok1 {
            for indexC, v := range b {
                if v == c {
                    result = indexC
                    break
                }
            }
        }
    }
    if b, ok := a.([]string); ok {
        if c, ok1 := i.(string); ok1 {
            for indexC, v := range b {
                if v == c {
                    result = indexC
                    break
                }
            }
        }
    }
    if b, ok := a.([]float64); ok {
        if c, ok1 := i.(float64); ok1 {
            for indexC, v := range b {
                if v == c {
                    result = indexC
                    break
                }
            }
        }
    }
    return
}
}

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

一件趣事引发的程序

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

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

funny

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

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

运行结果如下:

联系方式: 18013820100