先抛出几个问题
声明一个 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