zoukankan      html  css  js  c++  java
  • 菜鸟系列Golang学习 — 切片

    有兴趣的关注IT程序员客栈哦

    切片简介

    切片也是一种数据类型,在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)
    

    输出:

    []
    [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.itkezhan.top或者关注公众号IT程序员客栈
  • 相关阅读:
    adb常用命令和工具
    playwright学习记录
    vue,element-ui表格,多个单元格值可修改(点击聚焦后变成input,失去焦点请求保存)
    vue,element-ui表格,合并单元格,如果需要合并的数据隔行,需要重新排列数组
    cas-5.3.x接入REST登录认证,移动端登录解决方案
    企业级cas5.3登录页面修改
    cas实现单点登录mysql,oracle双版本
    Mycat实现MySQL主从复制和读写分离(双主双从)
    IDEA安装插件后默认存放的位置
    值得推荐的Idea十几大优秀插件
  • 原文地址:https://www.cnblogs.com/i-dandan/p/12431317.html
Copyright © 2011-2022 走看看