GoLang Array VS. Slice

2017-10-18 language golang network

两者十分类似,很容易造成混淆。

简介

数组

数组会根据元素个数以及其中元素的类型分配内存空间,这也就意味着,在编译阶段必须包含这两个元素,要么显示指定,要么可以在编译阶段间接推断出来。

package main

import (
        "fmt"
)

func main() {
        array := [5]int{1, 2, 3, 4, 5}
        //array := [5]int{1, 2}
        //array := [...]int{1, 2, 3, 4, 5}
        //array := [5]int{4: 5}
        //array := [...]int{2: 3, 3: 4, 4: 5}

        fmt.Printf("array %#v\n", array)
}

数组的序号与 C 语言类似,都是从 0 开始。

当数组创建完成之后,其大小是不能进行修改的,如果需要调整其大小,只能新建一个数组。

当作为参数传入时,会直接赋值一个数组,导致在某些场景下会比较浪费内存。

切片

相比数组来说,切片可以动态扩容,包括了 len 长度和 cap 容量两个属性,分别表示当前大小,以及最大容量。

package main

import (
        "fmt"
)

func main() {
        slice := []int{1, 2, 3, 4, 5}
        //array := [5]int{1, 2, 3, 4, 5}
        //slice := array[:]
        //slice := array[1:5]
        //slice := make([]int, 5, 20)

        fmt.Printf("slice %#v\n", slice)
}

切片是引用类型,当作为参数传入时类似于引用同一指针,修改值将会影响其它的对象。

源码

Golang 中关于 slice 的实现在 src/runtime/slice.go 文件中。

type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
}

其中,比较关键的是 growslice() 函数的实现,也就是在切片的容量不够的时候如何进行扩容。

如果通过 append() 添加元素超过了 cap 的值后,会进行扩容,而扩容之后同时需要将原有的数据复制到新的内存空间中,类似于 C 中的 realloc() 操作。

所以,尽量可以提前分配内存空间,避免不必要的内存申请。

Array VS. Slice

如上所述,当通过 Array 作为入参时会复制一份数据,而对于 Slice 来说,实际上也是复制一份,只是,两者指向的实际内存空间是相同的。

package main

import (
        "fmt"
)

func foobar(a [4]int, s []int) {
        fmt.Printf("array in func %p, %v\n", &a, a)
        fmt.Printf("slice in func %p, %v\n", &s, s)
        s[0] = 100
}

func main() {
        array := [4]int{10, 20, 30, 40}
        slice := array[:]

        fmt.Printf("array in main %p, %v\n", &array, array)
        fmt.Printf("slice in main %p, %v\n", &slice, slice)

        foobar(array, slice)
        fmt.Printf("slice after foobar %p, %v\n", &slice, slice)
}

最终会输出如下的内容,注意最后针对 Slice 元素的修改。

array in main 0xc4200121a0, [10 20 30 40]
slice in main 0xc42000a060, [10 20 30 40]
array in func 0xc420012200, [10 20 30 40]
slice in func 0xc42000a0a0, [10 20 30 40]
slice after foobar 0xc42000a060, [100 20 30 40]