数组与切片

以前粗学时并没有留意 Golang 的数组,一直以为只有切片。

今天看《Go语言实践》时才发现 Golang 也有数组。与切片的区别是定义时指定长度(例如长度为4的 int 型数组var array [4]int)。

数组赋值时会拷贝整个数组,所以作为参数传递时也会完整拷贝,要考虑性能合理使用,必要时可以使用指针。

以下代码便于理解数组和切片:

package main

import "fmt"

func main() {
    // 定义数组 a1
    a1 := [4]int{1, 2, 3, 4}

    // 数组 a2 来自 a1 的拷贝
    a2 := a1

    var a3 [4]int
    a3 = a1 // 同样会完整拷贝

    // a2 修改自己的内存区域
    a2[0] = 0

    // 切片 s1 来自 a1,其空间仍由 a1 实现
    s1 := a1[1:2]
    // 修改切片 s1 会影响数组 a1
    s1 = append(s1, -1, -2)

    fmt.Println("a1:", a1)
    fmt.Println("a2:", a2)
    fmt.Println("a3:", a3)
    fmt.Println("s1:", s1)
}

输出:

a1: [1 2 -1 -2]
a2: [0 2 3 4]
a3: [1 2 3 4]
s1: [2 -1 -2]

Slice 保存的是长度、容量以及其底层数组的地址。

这里 s1 来自 a1 的切片,底层数组与 a1 同内存区域,所以对 s1 的 append 导致 a1 的数值被修改。

在使用中也要注意这种情况,合理使用。

使用三个索引创建切片

如果希望 append 时不影响原有底层数组,可以使用三个索引创建切片:

func main() {
    a1 := [4]int{1, 2, 3, 4}
    a2 := [4]int{1, 2, 3, 4}

    s1 := a1[1:2]
    s2 := a2[1:2:2]

    s1 = append(s1, -1)
    s2 = append(s2, -1)

    fmt.Println("a1:", a1)
    fmt.Println("a2:", a2)
    fmt.Println("s1:", s1)
    fmt.Println("s2:", s2)
}

输出:

a1: [1 2 -1 4]
a2: [1 2 3 4]
s1: [2 -1]
s2: [2 -1]

可以看到此时对 s2 的 append 并没有影响 a2 数组。

这种格式为:slice[i:j:k]的切片方式,前两个索引与原来相同,第三索引用于表示新切片的容量。但它不是直接表示容量大小,而是表示原切片或者数组的索引位置。当它越界时会出现运行时错误。

所以新切片的长度和容量应该这样计算:

长度:j - i
容量:k - i

else

使用slice[i:j:k]创建切片的时候,并没有立即创建新的底层数组,而是在 append 时才创建新数组的:

func main() {
    a1 := [4]int{1, 2, 3, 4}
    // s1来自a1
    s1 := a1[1:2:2]

    // 修改a[1]
    a1[1] = 0

    fmt.Println("a1:", a1)
    fmt.Println("s1:", s1)

    fmt.Println("-------------")

    // append s1
    s1 = append(s1, -1)

    fmt.Println("a1:", a1)
    fmt.Println("s1:", s1)
}

输出:

a1: [1 0 3 4]
s1: [0] // <-对`a[1]`的修改也影响了 s1
-------------
a1: [1 0 3 4]
s1: [0 -1]