内容纲要
举例
len
和cap
的关系我们举个恰当的例子来看就是cap
比作一个水瓶,len
比作这个水瓶中此时所装载水的容量,而当瓶子大小装不下水的时候,也就是这个slice的扩容append
我们暂时不讨论。
我们直接先从一道关于slice的题来开始:
func main() {
var array [20]int
fmt.Println(array)
slice1 := array[10:11]
fmt.Println(slice1)
fmt.Println(len(slice1))
fmt.Println(cap(slice1))
}
大家可以先在不看答案的情况下来计算一下输出结果,下面是结果
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0]
1
10
不知大家看出上面的结果没,为何是上面这几个输出结果呢?这里让我们尝试从源码的角度来看看输出这个结果的原因。
说明
原码路径:$GOROOT/src/reflect/value.go
// Slice returns v[i:j].
// It panics if v's Kind is not Array, Slice or String, or if v is an unaddressable array,
// or if the indexes are out of bounds.
func (v Value) Slice(i, j int) Value {
var (
cap int
typ *sliceType
base unsafe.Pointer
)
switch kind := v.kind(); kind {
// ... 省略
case Array:
// ... 省略
tt := (*arrayType)(unsafe.Pointer(v.typ))
cap = int(tt.len) // 把数组的长度当做cap
typ = (*sliceType)(unsafe.Pointer(tt.slice))
base = v.ptr
case Slice:
typ = (*sliceType)(unsafe.Pointer(v.typ))
s := (*unsafeheader.Slice)(v.ptr)
base = s.Data
cap = s.Cap // 如果为slice,则直接取其中结构体中的cap
case String:
// 这个里面的大致思路就是直接取了字符串中的一段数据拷贝后返回回去
// ... 省略
}
// Declare slice so that gc can see the base pointer in it.
var x []unsafe.Pointer
// Reinterpret as *unsafeheader.Slice to edit.
s := (*unsafeheader.Slice)(unsafe.Pointer(&x))
s.Len = j - i // 统一的,不管是array还是slice,len = j - i
s.Cap = cap - i // 由上面计算出来的cap - i 就是新的cap大小
if cap-i > 0 {
s.Data = arrayAt(base, i, typ.elem.Size(), "i < cap")
} else {
// do not advance pointer, to avoid pointing beyond end of slice
s.Data = base
}
fl := v.flag.ro() | flagIndir | flag(Slice)
return Value{typ.common(), unsafe.Pointer(&x), fl}
}
针对上面代码的一些说明就是:
- 通过reflect映射Value的类型,然后计算对应的cap值
- 如果映射为Array类型,则cap大小就等于数组的长度
- 如果映射为Slice类型,则cap大小就等于切片结构体中的cap
- 如果为映射String类型,则直接取这个string中指定长度赋值给一个新的Value对象后返回
- 经过上面的操作后,原本的Array或者是Slice都会被统一转换为slice,新的slice的len就是参数的
j-i
,cap就是参数cap-i
,而指向的数据都是原本的内存空间。
经过上面原码的简单说明后,我们再回到上面举例里面的打印输出:
func main() {
var array [20]int
fmt.Println(array) // Output: 这里输出20个0,相信大家问题不大
slice1 := array[10:11]
fmt.Println(slice1) // Output: 经过上面的原码来看,slice1的len=11-10=1,cap=20-10=10,所以输出为[0]
fmt.Println(len(slice1)) // Output: 上面给出了解释,输出为 len=11-10=1
fmt.Println(cap(slice1)) // Output: 上面给出了解释,输出为 cap=20-10=10
}
至此,我们对上面的例子说明有了一定的原码支撑,但是,我们有时也会遇到这种用法:array[10:11:20]
,那后面那个20又怎么解释呢?其实这个的原码也在上面提到的那个文件中,在上面提到的文件直接搜索func (v Value) Slice3(i, j, k int) Value
,我这里就不列举原码了,这里面i
和j
的解释说明同Slice,k
的含义也就是我们给他指定了一个cap,这个k的大小不能大于它原始的cap值,超过的话会直接报panic,然后剩下的流程几乎同Slice
的原理(不支持String类型,否则也会panic)。
文章评论