Go语言基础 – 切片

  • A+
所属分类:go语言编程 学编程

本文主要介绍Go语言中切片(slice)及它的基本使用。

一、说明

1.1、切片说明

1.切片(Slice)是一个拥有数组相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

2.切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。

3.将数组赋值给切片后,切片里的指针就指向了这个数组

4.切片遍历方式和数组一样,可以用len()求长度

5.cap可以求出slice的最大容量,0<=len(slice)<=cap(array),其中array是slice引用的数组

二、切片使用

2.1、切片定义与初始化

1.切片定义

var 变量名 []类型

2.切片初始化

1.使用数组初始化切片

var arr [5]int = [...]int{1,2,3,4,5}      // 先定义数组

var slice []int = arr[start:end]   // 包含arr数组start到end之间的的元素,但不包含end

var slice []int = arr[0:end]    //可以简写为:var slice []int = arr[:]

slice = slice[:len(slice)-1]    //如果要去掉切片最后一个元素可以这么写

2.使用make初始化切片
a := make([]int, len, cap)      // make([]int:切片元素的类型,len:切片长度,cap:切片的容量)

示例:

func testSlice() {
    var slice []int       // 定义int类型的切片(空切片不可以直接赋值)
    var arr [5]int = [...]int{1,2,3,4,5}      // 定义数组
    slice = arr[2:5]      // 将数组下标2到5的值(不包含5)赋值给切片(也支持make的方式给切片赋值)
    fmt.Println(slice)      // 打印切片的结果,结果为:[3 4 5]
}

2.2、切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

打印切片的长度和容量示例:

func testSlice2() {

    var slice []int       // 定义intl类型的切片(空切片不可以直接赋值)
    var arr [5]int = [...]int{1,2,3,4,5}      // 定义数组
    slice = arr[2:5]      // 讲数组下标2到3的值(不包含3)赋值给切片(也支持make的方式给切片复制)
    fmt.Println(slice)      // 打印切片的结果

    // 长度和容量一样示例
    fmt.Println("1 slice len:",len(slice))        // 长度,结果为:1 slice len: 3
    fmt.Println("1 slice cap:",cap(slice))        // 容量,结果为:1 slice cap: 3

    // 长度和容量不一样示例
    slice = slice[0:1]       // 切片支持自己给自己赋值
    fmt.Println("2 slice len:",len(slice))        // 长度,结果为:2 slice len: 1
    fmt.Println("2 slice cap:",cap(slice))        // 容量,结果为:2 slice cap: 3
}

2.3、切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

Go语言基础 - 切片

2.4、切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil,例如下面的示例:

var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

2.4、切片拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
    s1 := make([]int, 3) //[0 0 0]
    s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
    s2[0] = 100
    fmt.Println(s1) //[100 0 0]
    fmt.Println(s2) //[100 0 0]
}

2.5、切片遍历

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

func main() {
    s := []int{1, 3, 5}

    for i := 0; i < len(s); i++ {
        fmt.Println(i, s[i])
    }

    for index, value := range s {
        fmt.Println(index, value)
    }
}

2.6、append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时。 举个例子:

func main() {
    //append()添加元素和切片扩容
    var numSlice []int
    for i := 0; i < 10; i++ {
        numSlice = append(numSlice, i)
        fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
    }
}

输出:

[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

从上面的结果可以看出:

1.append()函数将元素追加到切片的最后并返回该切片。

2.切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

3.append()函数还支持一次性追加多个元素。 例如:

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

2.7、使用copy()函数赋值切片

首先我们来看一个问题:

func main() {
    a := []int{1, 2, 3, 4, 5}
    b := a
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(b) //[1 2 3 4 5]
    b[0] = 1000
    fmt.Println(a) //[1000 2 3 4 5]
    fmt.Println(b) //[1000 2 3 4 5]
}

由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)

其中:

srcSlice: 数据来源切片
destSlice: 目标切片
举个例子:

func main() {
    // copy()复制切片
    a := []int{1, 2, 3, 4, 5}
    c := make([]int, 5, 5)
    copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(c) //[1 2 3 4 5]
    c[0] = 1000
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(c) //[1000 2 3 4 5]
}

2.8、切片排序和查找操作

1.对切片得排序操作需要导入sort模块:import "sort"。

2.对切片得查找操作的前提时切片必须是有序的。

示例:

// 对int类型切片排序与查找(使用:sort.Ints()方法排序)
func testIntSort() {
    var a = [...]int{1,34,45,7,8,3}       // 定义数组(数组是值类型,要改变数组里面的东西要通过切片来修改)
    sort.Ints(a[:])     // 只能对切片排序,所以要传切片
    fmt.Println(a)      // 结果为:[1 3 7 8 34 45]

    index := sort.SearchInts(a[:], 8)     // 在a(切片)里查找8的下标,切片必须是有序的否则查找的结果不准
    fmt.Println("Int Search index :",index)
}

// 对字符串排序(使用:sort.Strings()对字符串类型切片排序,根据字母顺序排序,大写字母优先小写字母)
func testStrSort() {
    var a = [...]string{"abc","dsvc","rsg","fdv","AB"}
    sort.Strings(a[:])
    fmt.Println(a)
    index := sort.SearchStrings(a[:], "rsg")
    fmt.Println("String Search index :",index)
}

// 对浮点型进行排序(使用:sort.Floatxxs()方法对浮点类型切片排序)
func testFloatSort() {
    var a = [...]float64{1.1,3.5,5.8,9.9}
    sort.Float64s(a[:])
    fmt.Println(a)
    index := sort.SearchFloat64s(a[:],5.8)
    fmt.Println("Float Search index :",index)
}

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: