数组以及切片

[TOC]

1. Arrays

golang数组与基本静态语言的数组一致,由于golang是强类型的语言,所以数组元素必须类型相同。数组的基本声明方式如下:

1.1 数组声明

  • 定长数组

    使用var arr [n]T 进行声明长度为n, 类型为T的数组对象,采用这种方式进行声明,每个元素根据数组类型赋予相应的初始值,例如int初值为0

    也可以在声明时向数组赋予初始值,a := [3]int{1, 3, 6} ,可以不全赋值,其余位置会被默认值填充

    // 初始值测试
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var a [3]int //int array with length 3
    
        var (
            b [3]float32
            c [3]float64
            d [3]string
            e [3]bool
            f [3]byte
            g [3]rune
            h [3]complex64
            i [3]int8
        )
        fmt.Println(reflect.ValueOf(a[0]).Kind(), a,
        reflect.ValueOf(b[0]).Kind(), b, 
        reflect.ValueOf(c[0]).Kind(), c, 
        reflect.ValueOf(d[0]).Kind(), d, 
        reflect.ValueOf(e[0]).Kind(), e, 
        reflect.ValueOf(f[0]).Kind(), f, 
        reflect.ValueOf(g[0]).Kind(), g, 
        reflect.ValueOf(h[0]).Kind(), h,
        reflect.ValueOf(i[0]).Kind(), i)
    }
    int [0 0 0] float32 [0 0 0] float64 [0 0 0] string [  ] bool [false false false] uint8 [0 0 0] int32 [0 0 0] complex64 [(0+0i) (0+0i) (0+0i)] int8 [0 0 0]
  • 自定长度数组

    可以使用[...]T{.., .., } 进行数组的声明,编译器会自动根据给定元素给数组确定长度

    但是后面必须跟上数组元素的声明,不可以不指定元素

    func main() {
        a := [...]int{1, 2, 1e3, 2e4} // ... makes the compiler determine the length
        fmt.Println(a)
    }

    注意数组长度也是数组类型的一部分,对于强类型语言来说,不同长度的数组不属于同一类型,所以将不同长度的数组进行赋值是不被允许的

1.2. 数组以值传递

golang中的数组是value type而不是reference type ,也就是说,进行数组间的相互赋值时,使用的不是同一个内存空间,开辟了另一个内存空间用以存放新的数组,所以改变新的数组中元素并不会改变原数组的值,也就是说,使用的是深复制。

所以数组作为参数进行传递时,也是按值传递,不会改变原数组的元素。

关于数组的长度可以使用len()函数求取

2. 可迭代对象的遍历

对于数组等可迭代对象,可以使用基本的for循环进行,也可以使用range 函数,进行迭代,每次迭代包含index, value两个值进行使用,如果需要忽略index可以使用空白操作符,忽略value可以只进行index的赋值

3. 切片

数组虽然已经足够灵活,但是必须是确定长度,不支持变长操作。切片类型是基于数组的一个顶层wrapper ,切片本身并不存放任何数据,他们只是某些已经存在的数组的引用

3.1 切片声明

切片的类型用不需要长度的[]int表示,对于数组的[:] 操作,与python用法一致,但是不允许负值操作,而且不允许倒序操作,而且最多只接受两个参数,同样,包含下界不包含上界。

使用make() 函数声明切片

用于生成一个切片,传入的是类型,容量以及长度,值采用默认值填充。

3.2 切片机制

切片不拥有任何数据,只是作为底层数组的引用,所以基于切片的数据修改,会直接反映在底层数组对象上。所以公用同一个底层数组的切片,如果对其进行更改,会映射到每一个切片上去。

长度与容量:

切片的长度使用len()函数,表示的是切片可表示的元素的数量;切片的容量是从切片开始位置到数组最后一个元素的长度。

一个切片可以被重新切片,并且增长到其容量,如果超出范围会抛出运行时错误。

结构特征

slices可以被表示为一个结构体

一个切片包含了长度,容量,以及指向数组第一个元素的指针,但一个切片传作为参数传递时,虽然是按值传递,但是指针变量仍然会指向同一个底层数组。所以使用切片进行参数传递时,内部的改变会影响到函数外部.

3.3 基本操作

  • Append()

    append不仅可以追加单个或者多个元素,而且可以追加另外一个slice, 使用...操作符即可

  • copy

    将切片的容量扩充2倍,可以使用

3.4 空值nil与多维slice

一个slice 的length , capacity均为0,则该切片为空nil ,对于一个控切片,可以使用append() 进行扩充。

使用多维slice只需要在声明是将其指定为多维即可

注意使用多维数组时,只允许最高纬度使用...运算符,表示可以不确定其维度,但是其他维度必须标识,如果不标识则内部存放的是slice

4. 切片的优化

正如前面所说,切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据。

例如, FindDigits 函数加载整个文件到内存,然后搜索第一个连续的数字,最后结果以切片方式返回。

因为切片引用了原始的数组, 导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里。

改进方法是将该切片复制到一个新的切片中,释放原内存。

Last updated

Was this helpful?