Go语言数组的内部实现和基础功能--切片的内部实现和基础功能

发布时间:2020-06-20编辑:lianpenglin阅读(1905)

    切片是一种数据结构,这种数据结构便于使用和管理数据集合。切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数append来实现的。这个函数可以快速且高效的增长切片。还可以通过对切片再次切片来缩小一个切片的大小。因为切片的底层内存也是在连续块中分配的,所以切片还能获得索引、迭代以及为垃圾回收优化的好处。

    内部实现

    切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。切片有3个字段的数据结构,这些数据结构包含Go语言需要操作底层数组的元数据。

    这3个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。后面会一进步说明容量和长途的区别。

    如下图所示

    1.png


    创建和初始化

    Go语言中有几种方法可以创建和初始化切片。是否能提前知道切片需要的容量通常会决定要如何创建切片。

    1、make和切片字面量

    一种创建切片的方法是使用内置的make函数。当使用make时,需要传入一个参数,指定切片的长度,如下代码所示:

    //创建一个字符串切片
        //其长度和容量都是5个元素
        
        slice := make([]string,5)

    如果只指定长度,那么切片的容量和长度相等。也可以分别指定长度和容量,如下所示:

    //创建一个整型切片
        //长度为3个元素,容量为5个元素
        slice := make([]int,3,5)

    分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能访问所有的元素数组。上图描述了上面代码声明的整型切片在初始化并存入一些值后的样子。

    上述代码的切片可以访问3个元素,而底层数组拥有5个元素。剩余的2个元素可以在后期操作中合并到切片,可以通过切片访问这些元素。如果基于这个切片创建新的切片,新切片会和原有的切片共享底层数组,也能通过后期操作来访问多余容量的元素。

    不允许创建容量小于长度的切片:

    //创建一个整型切片
        //使其长度大于容量
        slice := make([]int,5,3)
        
        compiler error:
        len larger than cap in make([]int)

    另一种常用的创建切片的方法是使用切片字面量,如下代码所示。这种方法和创建数组类似,只是不需要指定[]运算符里的值。初始的长度和容量会基于初始化时提供的元素的个数确定。

    //创建字符串切片
        //其长度和容量都是5个元素
        slice := []string{"Red","Blue","Green","Yellow","Pink"}
        //创建一个整型切片
        //其长度和容量都是3个元素
        slice := []int{10,20,30}

    当使用切片字面量时,可以设置初始长度和容量。要做的就是在初始化时给出所需的长度和容量作为索引。下面代码的语法展示了如何创建长度和容量都是100个元素的切片。

    //创建字符串切片
        //使用空字符串初始化第100个元素
        slice := []string{99:""}

    记住,如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。只有不指定值的时候,才会创建切片,如下代码所示:

    //创建有3个元素的整型数组
        array := [3]int{10,20,30}
        
        //创建长度和容量都是3的整型切片
        slice := []int{10,20,30}

    2、nil和空切片

    有时,程序可能需要声明一个值为nil的切片(也称nil切片)。只要在声明时不做任何初始化,就会创建一个nil切片,如下代码所示:

    //创建nil切片
        var slice []int

    在Go豫园里,nil切片是很常见的创建切片的方法。nil切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil切片会很好用。例如,函数要求返回一个切片但是发生异常的时候。

    利用初始化,通过生命一个切片可以创建一个空切片,如下代码所示:

    //使用make创建空的整型切片
        slice := make([]int,0)
        //使用切片字面量创建空的整型切片
        slice := []int{}

    空切片在底层数组包含0个元素,也没有分配任何存储空间。想表示空集合时空切片很有用,例如,数据库查询返回0个查询结果。

    不管是使用nil切片还是空切片,对其调用内置函数append、len和cap的效果都是一样的。

    3、使用切片

    现在知道了什么是切片,也知道如何创建切片,来看看如何在程序里使用切片。

    3.1、赋值和切片

    对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。使用[]操作符就可以改变某个元素的值,如下代码所示:

    //创建一个整型切片
        //其容量和长度都是5个元素
        slice := []int{10,20,30,40,50}
        //改变索引为1的元素的值
        slice[1] = 25

    切片之所以被称为切片,是因为创建一个新的切片就是把底层数组切除一部分,如下代码所示:

    //创建一个整型切片
        //其长度和容量都是5个元素
        slice := []int{10,20,30,40,50}
        //创建一个新切片
        //其长度为2个元素,容量为4个元素
        newSlice := slice[1:3]

    执行完上诉代码的切片动作后,我们有了两个切片,它们共享同一段底层数组,但通过不同的切片会看到底层数组的不同部分。

    2.png

    第一个切片slice能够看到底层数组全部5个元素的容量,不过之后的newSlice就看不到。对于newSlice,底层数组的容量只有4个元素。newSlice无法访问到它所指向的底层数组的第一个元素之前的部分。所以,对于newSlice来说,之前的那些元素就是不存在的。

    对底层数组容量时k的切片slice[i:j]来说:

        长度:j-i

        容量:k-i

    对newSlice应用这个公司就能得到如下所示的数字:

    对底层数组容量时5的切片slice[1:3]来说

    长度:3-1=2

    容量:5-1=4

    可以用另外一种方法来描述这几个值。第一个值表示新切片开始的元素的索引位置,这个例子中是1.第二个值表示开始的索引位置(1),加上希望包含的元素的个数(2),1+2的结果是3,所以第二个值就是3。容量是该与切片相关的所有元素的数量。

    需要记住的是,现在两个切片共享同一个底层数组。如果一个切片修改了该底层数组的共享部分,另一个切片也能感知到,如下代码所示:

    //创建一个整型切片
        //其长度和容量都是5个元素
        slice := []int{10,20,30,40,50}
        //创建一个新切片
        //其长度是2个元素,容量是4个元素
        newSlice := slice[1:3]
        //修改newSlice索引为1的元素
        //同时也修改了原来的slice的索引为2的元素
        newSlice[1] = 35

    把35赋值给newSlice的第二个元素(索引为1的元素)的同时也是在修改原来的slice的第3个元素(索引为2的元素)。

    切片只能访问到其长度内的元素。试图访问超出其长度的元素将会导致语言运行时异常,如下代码所示。与切片的容量相关联的元素只能用于增长切片。在使用这部分元素前,必须将其合并到切片的长度里。

标签golang

如果对你有用打赏一下吧!