Go语言 - 切片

引子

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。 例如:

func arraySum(x [3]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}

这个求和函数只能接受[3]int类型,其他的都不支持。 再比如,

a := [3]int{1, 2, 3}

数组a中已经有三个元素了,我们不能再继续往数组a中添加新元素了。

切片

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

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

值类型与引用型的区别:

底层数组的修改会影响切片
切片的修改也会影响底层数组

 切片的定义

var name []T
// 切片拥有数组的所有特性
  • name:表示变量名
  • T:表示切片中的元素类型

切片的声明

package main

import "fmt"

func main()  {
    // 切片声明方式一  直接声明
    var s = []int{1,2,3}
    fmt.Println(s)  // [1 2 3]
    fmt.Printf("%T \n", s)  // []int

    // 切片声明方式二 从数组中得到切片
    var a = [3]int{1,2,3}
    c := a[0:3]
    fmt.Println(c)  // [1 2 3]
    fmt.Printf("%T \n", c)  // []int

}

切片的长度和容量

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

package main

import "fmt"

func main()  {
    var a1 = [...]string{"北京", "上海", "深圳", "成都", "广州", "青岛"}
    s1 := a1[1:4]
    // 切片的大小 (切片内目前元素的数量)
    fmt.Println(len(s1))  // 3
    // 切片的容量 (底层数组最大能放多少元素)
    fmt.Println(cap(s1))  // 5  

}

切片的本质

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

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

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

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

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

切片的扩容策略

newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
    newcap = cap
} else {
    if old.len < 1024 {
        newcap = doublecap
    } else {
        // Check 0 < newcap to detect overflow
        // and prevent an infinite loop.
        for 0 < newcap && newcap < cap {
            newcap += newcap / 4
        }
        // Set newcap to the requested cap when
        // the newcap calculation overflowed.
        if newcap <= 0 {
            newcap = cap
        }
    }
}
源码
  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样

copy()函数复制切片

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

copy(destSlice, srcSlice []T)
  • srcSlice: 数据来源切片
  • destSlice: 目标切片

使用

package main

import "fmt"

func main{
    test()
}
func test()  {
    a := []int{1,2,3}
    b := a  // 直接赋值

    var c []int  // 定义一个切片,还没有申请内存
    c = make([]int, 3, 3)  // 为c切片申请内存 make([]T, len, cap)
    copy(c, a)  // 参数2是被copy对象

    b[0] = 100
    fmt.Println(a)  // [100 2 3]
    fmt.Println(b)  // [100 2 3]
    fmt.Println(c)  // [1 2 3]

}
var c []int  // 只定义,没初始化,没有内存
var c = []int{} // 定义,初始化,有内存

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

package main

import "fmt"

func main{
    test1()
}
func test1()  {
    a := []int{1,2,3,4,5,6,7}
    a = append(a[:2], a[3:]...)
    fmt.Println(a)  // [1 2 4 5 6 7]
}

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

 题

package main

import "fmt"

func main{
    test2()
}
func test2()  {
    a := [...]int{1,2,3,4,5,6,7}
    b := a[:]
    b[0] = 100
    fmt.Println(a[0])  // 100  因为切片是引用类型

    c := a[2:5]
    d := c[:5]
    fmt.Println(c)  // [3 4 5]
    fmt.Println(len(c))  // 3
    fmt.Println(cap(c))  // 5
    fmt.Println(d)  // [3 4 5 6 7]
    fmt.Println(len(d))  // 5
    fmt.Println(cap(d))  // 5
}
c与d内存地址一样,因为c与d的第一个元素是同一个

 

 

 

 

摘自

posted @ 2019-11-24 19:09  waller  阅读(215)  评论(0编辑  收藏  举报