先抛出几个问题

声明一个 slice 并赋值为 nil, 如 var slice []int = nil,此时 len(slice) 的运行结果是什么?

func(arr []int) 和 func(arr [10]int) 两个函数内部都对 arr 进行修改, 对外面的值(作为参数的数据)是否造成影响?
创建一个 slice := make([]int, 5, 10), 然后 slice[8] 和 slice[:8] 的运行结果是什么?
下面两段代码(示例1)的输出结果是什么
 

package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5}
	slice2 := append(slice[:3], 6, 7)
	fmt.Println("slice:",slice)
	fmt.Println("slice2:",slice2)
}

输出:

package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5}
	slice2 := append(slice[:3], 6, 7, 8) // 多追加一个数字 8, 这是唯一的不同
	fmt.Println(slice)
	fmt.Println(slice2)
}

输出:

以上两个示例,同样是截取slice的元素赋值给slice2,只是截取的长度不一样,对slice造成的影响完全不同。

如果你对上面你的几个问题, 无法给出确定的答案, 说明你并没有真正理解 Go 的 slice 和 array. 就更不要提精通二字了.

为了方便, 下面的描述均以 int 作为元素类型说明

 

数组 Array

先说一下数组, 的确在 Go 语言中, 因为 slice 的存在, 使得 array 的出场率不高。但想要很好的理解 slice, 还是要先要了解 array.

数组的声明

Go 语言的数组和其他语言一样, 没有什么特别的地方, 就是一段以元素类型(如int)为单位的连续内存空间。数组创建时, 被初始化为元素类型的零值.

声明举例:

var arr [10]int  // 长度为 10 的数组, 默认所有元素是 0
arr := [...]int{1, 2, 3} // 长度由初始化元素个数指定, 这里长度是 3
arr := [...]int{11: 3} // 长度为 11 的数组, arr[11] 初始化为 3, 其他为 0
arr := [5]int{1,2} // 长度为 5 的数组, 前两位初始化为 1, 2
arr := [5]int{1,2} // 长度为 5 的数组, 前两位初始化为 1, 2
arr := [...]int{1: 23, 2, 3: 22} // 长度为 4 的数组, 初始化为 [0 23 2 22]
[]...
{1:23, 2:8, 3:12}{23, 8, 12}
{5: 10, 11, 12, 6: 100}{5: 10, 6: 11, 7: 12, 6: 100}duplicate index in array literal: 6

长度为 0 的数组

长度为 0 的数组

[0]intstruct{}[10]struct{}

看一个例子:

var (
    a [0]int
    b struct{}
    c [0]struct {
        Value int64
    }
    d [10]struct{}
    e = new([10]struct{}) // new 返回的就是指针
    f byte
)
fmt.Printf("%p, %p, %p, %p, %p, %p", &a, &b, &c, &d, e, &f)
// 0x1127a88, 0x1127a88, 0x1127a88, 0x1127a88, 0x1127a88, 0xc42000e280
f[0]intstruct{}
mapmap[string]struct{}map[string]bool

数组作为函数参数

func(arr [3]int)arr

因为一个数组作为参数时, 会拷贝一份副本作为参数, 函数内部操作的数组与外界数组, 在内存中根本就不是同一个地方. 是值传递不是引用传递, 这点可能和某些语言不同.

看下面代码:

array := [3]int{1, 2, 3}
func(innerArray [3]int) {
    innerArray[0] = 8
    fmt.Printf("%p: %v\n", &innerArray, innerArray)
}(array)
fmt.Printf("%p: %v\n", &array, array)
// 0xc42000a2e0: [8 2 3]
// 0xc42000a2c0: [1 2 3]

函数内外, 数组的内存地址都不一样, 自然不会有影响.

func(arr *[3]int)

切片 Slice

slice 通常用来表示一个变长序列, 也是基于数组实现的。看下图:

                                           切片结构图

 

Q2summermonths

Q2的cap:13-4=9(length(months)-截取的前一个下标);summer的cap:13-6=7

再看一下 slice 在 Go 内部的定义.

type slice struct {
    array unsafe.Pointer // 被引用的数组中的起始元素地址
    len   int            // 长度
    cap   int            // 最大长度
}

我们对 slice 的读写, 实际上操作的都是它所指向的数组.

看到了上面的 slice 数据结构, 自然就知道了以下两点:

nillencapslice.arrayslice.lenslice.cap
func(arr []int)arr

slice 越界

slice 是可伸缩变长的, 导致很多人误以为 slice 是不会越界的, 下面我们来阐述下几种越界情况.

summersummer[4] = "hello"index out of rangecap(summer) = 7summer[4]len(summer) = 3

再看下面这个例子:

arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(arr[:3:5][:4]) // [1 2 3 4]
fmt.Println(arr[:3:5][:8]) // panic: runtime error: slice bounds out of range
arr[:3:5]arrlen = 4len = 8cap = 5

对 slice 的操作记住两点:

slice[index]len(slice)slice[start:end]cap(slice)
slice[8]slice[:8]

append 函数

appendappend
append(slice, v1, v2)appendcap(slice)
makecopy
len(slice)len(slice)cap(slice)
appendappend2 * cap(slice)
make([]int, 0, capValue)

现在回头来看示例1,示例1的第一段代码,由于append的时候没有超越slice的cap,所以slice会在下标3开始追加数据,slice2                                           指向追加之后的slice;

make

再来看看以下代码,就很容易明白输出内容了。

package main

import "fmt"

func main() {
	slice := []int{1, 2, 3, 4, 5,888}
	slice2 := append(slice[:3], 6, 7) 
	fmt.Println("slice:",slice)
	fmt.Println("slice2:",slice2)
}

输出:

更多阅读

$GOROOT/src/runtime/slice.go




参考链接:https://www.jianshu.com/p/ae8a413fc33f