Go Array & Slice


数组

几乎所有计算机语言,数组的实现都是相似的:一段连续的内存,Go 语言也一样,Go 语言的数组底层实现就是一段连续的内存空间。每个元素有唯一一个索引(或者叫下标)来访问。

如下图所示,下图是 [5]int{1:10, 2:20} 数组的内部实现逻辑图:

image

由于内存连续,CPU 很容易计算索引(即数组的下标),可以快速迭代数组里的所有元素。

slice

切片是一个很小的对象,是对数组进行了抽象,并提供相关的操作方法。切片有三个属性字段:长度、容量和指向数组的指针。严格地讲,切片有容量(capacity)和长度(length)两个属性。

切片有两种定义方式,一种是先声明一个变量是切片,然后使用内置函数 make 去初始化这个切片。另外一种是通过取数组切片来赋值。

package main
    
import (
    "fmt"
)
    
func main() {
    var x = make([]float64, 5)
    fmt.Println("Capcity:", cap(x), "Length:", len(x))
    var y = make([]float64, 5, 10)
    fmt.Println("Capcity:", cap(y), "Length:", len(y))
    for i := 0; i < len(x); i++ {
        x[i] = float64(i)
    }
    fmt.Println(x)
    for i := 0; i < len(y); i++ {
        y[i] = float64(i)
    }
    fmt.Println(y)
}

输出结果为

Capcity: 5 Length: 5
Capcity: 10 Length: 5
[0 1 2 3 4]
[0 1 2 3 4]

上面我们首先用 make 函数定义切片 x,这个时候 x 的容量是 5,长度也是 5。然后使用 make 函数定义了切片 y,这个时候 y 的容量是 10,长度是 5。然后我们再分别为切片 x 和 y 的元素赋值,最后输出。

所以使用 make 函数定义切片的时候,有两种方式,一种只指定长度,这个时候切片的长度和容量是相同的。另外一种是同时指定切片长度和容量。虽然切片的容量可以大于长度,但是赋值的时候要注意最大的索引仍然是 len(x)-1。否则会报索引超出边界错误。

另外一种是通过数组切片赋值,采用 [low_index:high_index] 的方式获取数值切片,其中切片元素包括 low_index 的元素,但是不包括 high_index 的元素。

package main
    
import (
    "fmt"
)
    
func main() {
    var arr1 = [5]int{1, 2, 3, 4, 5}
    var s1 = arr1[2:3]
    var s2 = arr1[:3]
    var s3 = arr1[2:]
    var s4 = arr1[:]
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
    fmt.Println(s4)
}

输出结果为

[3]
[1 2 3]
[3 4 5]
[1 2 3 4 5]

在上面的例子中,我们还省略了 low_index 或 high_index。

如果省略了 low_index,那么等价于从索引 0 开始;如果省略了 high_index,则默认 high_index 等于 len(arr1),即切片长度。

这里为了体现切片的长度可以变化,我们看一下下面的例子:

package main
    
import (
    "fmt"
)
    
func main() {
    var arr1 = make([]int, 5, 10)
    for i := 0; i < len(arr1); i++ {
        arr1[i] = i
    }
    fmt.Println(arr1)
    arr1 = append(arr1, 5, 6, 7, 8)
    fmt.Println("Capacity:", cap(arr1), "Length:", len(arr1))
    fmt.Println(arr1)
}

输出结果为

[0 1 2 3 4]
Capacity: 10 Length: 9
[0 1 2 3 4 5 6 7 8]

image

上图中,ptr 指的是指向 array 的 pointer,len 是指切片的长度, cap 指的是切片的容量。

对于s := make([]byte, 5) 和 s := []byte{...} 的方式

image

对于 s = s[2:4] 的方式

image

对于 nil 的切片即 var s []byte 对应的逻辑图是

image

在此有一个说明:nil 切片和空切片是不太一样的,空切片即 s := make([]byte, 0) 或者 s := []byte{} 出来的切片
空切片的逻辑图为:

image

空切片指针不为 nil,而 nil 切片指针为 nil。但是,不管是空切片还是 nil 切片,对其调用内置函数 append()、len 和 cap 的效果都是一样的,感受不到任何区别。

扩容

这里我们初始化 arr1 为容量 10,长度为 5 的切片,然后为前面的 5 个元素赋值。然后输出结果。然后我们再使用 Go 内置方法 append 来为 arr1 追加四个元素,这个时候再看一下 arr1 的容量和长度以及切片元素,我们发现切片的长度确实变了。

另外我们再用 append 方法给 arr1 多追加几个元素,试图超过 arr1 原来定义的容量大小。

package main
    
import (
    "fmt"
)
    
func main() {
    var arr1 = make([]int, 5, 10)
    for i := 0; i < len(arr1); i++ {
        arr1[i] = i
    }
    arr1 = append(arr1, 5, 6, 7, 8, 9, 10)
    fmt.Println("Capacity:", cap(arr1), "Length:", len(arr1))
    fmt.Println(arr1)
}

输出结果为

Capacity: 20 Length: 11
[0 1 2 3 4 5 6 7 8 9 10]

我们发现 arr1 的长度变为 11,因为元素个数现在为 11 个。另外我们发现 arr1 的容量也变了,变为原来的两倍。这是因为 Go 在默认的情况下,如果追加的元素超过了容量大小,Go 会自动地重新为切片分配容量,容量大小为原来的两倍。

上面我们介绍了,可以使用 append 函数给切片增加元素,现在我们再来介绍一个 copy 函数用来从一个切片拷贝元素到另一个切片。

package main
    
import (
    "fmt"
)
    
func main() {
    slice1 := []int{1, 2, 3, 4, 5, 6}
    slice2 := make([]int, 5, 10)
    copy(slice2, slice1)
    fmt.Println(slice1)
    fmt.Println(slice2)
}

输出结果

[1 2 3 4 5 6]
[1 2 3 4 5]

在上面的例子中,我们将 slice1 的元素拷贝到 slice2,因为 slice2 的长度为 5,所以最多拷贝 5 个元素。

应用

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // index
    arrayA := [...]int{0: 1, 2: 1, 3: 4}
    fmt.Println(arrayA)

    // type
    arrayB := [...]int{1, 2, 3}
    arrayC := [...]int{1, 2, 3, 4}
    fmt.Println(reflect.TypeOf(arrayB))
    fmt.Println(reflect.TypeOf(arrayC))
    fmt.Println(reflect.TypeOf(arrayB) == reflect.TypeOf(arrayC))

    array := [4]int{10, 20, 30, 40}
    slice := array[0:2]
    fmt.Println(slice)

    newSlice := append(append(append(slice, 50), 100), 150)
    fmt.Println(newSlice)

    newSlice[1] = newSlice[1] + 1
    fmt.Println(newSlice)

    sliceInt := []int{1, 2, 3, 4, 5, 6}
    newSliceInt := append(sliceInt, 1)
    fmt.Println(newSliceInt)
}
r := [...]int{99:-1}

定义了一个含有 100 个元素的数组 r,最后一个元素输出化为 -1,其他的元素都是用 0 初始化。

分享:

评论