菜鸟系列Golang学习 — 切片

切片简介

切片也是一种数据类型,在Golang中,切片底层基于数组实现的。
我们定义切片如下

var slice []int

切片之所以出现,是为了更好的利用资源,管理数据,如果使用数组,则我们一开始就要定义数组的长度,而使用切片,则可以不需要定义数组长度。

切片数据结构如下,假设初始化分配容量为6,长度为4的切片。

1. 切片的初始化

在初始化切片阶段,会调用下列源码:

// NewSlice returns the slice Type with element type elem.
func NewSlice(elem *Type) *Type {
    if t := elem.Cache.slice; t != nil {
        if t.Elem() != elem {
            Fatalf("elem mismatch")
        }
        return t
    }

    t := New(TSLICE)
    t.Extra = Slice{Elem: elem}
    elem.Cache.slice = t
    return t
}

从代码可知,上述方法返回的结构体 TSLICE 中的 Extra 字段是一个只包含切片内元素类型的 Slice{Elem: elem} 结构,也就是说切片内元素的类型是在编译期间确定的。

在源码编译期间的切片是 Slice 类型的,但是在运行时切片由如下的 SliceHeader 结构体表示

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Data 字段是指向数组的指针,Len 表示当前切片的长度,而 Cap 表示当前切片的容量。其实Cap也是底层数组的大小。正如图片所示,切片只是在数组上面进行了抽象而成的。

2. 切片的访问

访问切片中元素使用的 OINDEX 操作也会在中间代码生成期间转换成对地址的直接访问:

func (s *state) expr(n *Node) *ssa.Value {
    switch n.Op {
    case OINDEX:
        switch {
        case n.Left.Type.IsSlice():
            p := s.addr(n, false)
            return s.load(n.Left.Type.Elem(), p)
        ...
        }
    ...
    }
}

切片的访问操作基本都是在编译期间完成的.

3. 追加元素

往切片追加元素是我们经常的操作。在 Go 语言中我们会使用 append 关键字向切片追加元素。在追加元素过程中,先对切片结构体进行解构获取它的数组指针、大小和容量,如果在追加元素后切片的大小大于容量。在Go语言中,通过append追加元素,但是并不会主动赋值给原切片,需要手动赋值。

    var slice []int
    slice2 := append(slice, 0)
    fmt.Println(slice)
    fmt.Println(slice2)

输出:

扫描二维码关注公众号,回复: 9638808 查看本文章
[]
[0]

并且,Go语言考虑到追加元素超过原切片容量时会进行扩容操作。此处到扩容操作会重新申请一段内存,并将原切片元素拷贝过去。

func growslice(et *_type, old slice, cap int) slice {
    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.len < 1024 {
            newcap = doublecap
        } else {
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

在分配内存空间之前需要先确定新的切片容量,Go 语言根据切片的当前容量选择不同的策略进行扩容:

  1. 如果期望容量大于当前容量的两倍就会使用期望容量;
  2. 如果当前切片容量小于 1024 就会将容量翻倍;
  3. 如果当前切片容量大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;

加一道题目,你觉得输出的会是什么:

func main() {
    s := []int{1, 2, 3}                          
    ss := s[1:]                                        
    ss = append(ss, 4)

    for _, v := range ss {
        v += 10
    }
    for i := range ss {
        ss[i] += 10
    }
    fmt.Println(s)
}

猜你喜欢

转载自www.cnblogs.com/jiliguo/p/12431317.html
今日推荐