关于 G o Go Go语言的切片类型此处不进行展开,这里更多探讨切片在底层的基本原理。简单来讲,切片的类型字面量如下
[]T
具体而言,例如
[]rune
[]struct{
name,department,string}
[]string{
"Cauchy","Gauss","Riemann","Euler","Bayes"}
对于切片类型,其零值是nil
。
一个切片类型值(后文均简称为切片值)总会持有一个对某个数组值的引用。一个切片值一旦被初始化,就会与一个包含了其中元素值的数组值相关联,这个数组值被称为引用它的切片值的底层数组。多个切片值可能会共用一个底层数组。例如,将一个切片值复制成多个或针对其中的某个连续片段再进行切片形成新的切片值,这些切片值所引用的都是同一个底层数组。对切片值中的某一个元素值进行修改实质上就是对其对应的底层数组上的对应元素进行修改。反之也是成立的。
对此,切片的一个重要属性——容量,便与该切片引用的底层数组有关。获取一个切片的容量可以使用 G o Go Go语言的内置函数cap()
。如下
cap([]String{
8:"Cauchy",2:"Gauss","Riemann","Euler","Bayes"})
>>>
9
在此案例中,切片值的容量就等于它的长度。但多数情况下切片的容量与长度是不一致的。这与切片值的底层数据结构有关。
一个切片值的底层数据结构包含了一个指向底层数组的指针类型值、一个代表了切片长度的int
类型值和一个代表了切片容量的int
类型值。
type slice struct {
array unsafe.Pointer
len int
cap int
}
在切片值中存储着指向其底层数组的指针。这个引用体现了它们之间的引用关系。在使用复合字面量初始化一个切片值的时候,首先创建的是这个切片值所引用的底层数组。这个底层数组与这个切片值有着相同的元素类型、元素值及排列顺序和长度。因此,此时的切片值的长度和容量一定是相同的。
利用切片表达式得到切片值时,如下
// Array
array = [...]string{
"Cauchy","Gauss","Riemann","Euler","Bayes"}
//Slice
slice = array[:4]
该切片值slice
的底层数组就是array
。切片表达式的作用并不是复制数组值中某个连续片段所包含的元素值,而是创建一个新的切片值。在这个切片值中包含了指向这个连续片段中首个元素值的指针。此案例中len(slice)=4,cap(slice)=5
,但需要注意的是一个切片值的容量并不是其底层数组的长度。为了验证这个说法,再新建一个切片值
slice = array[2:]
容易知道新切片值的底层数组同样是array
。
package main
import "fmt"
func main(){
Arr := [...]string{
"Cauchy","Gauss","Riemann","Euler","Bayes"}
Sli := Arr[2:]
fmt.Print(cap(Sli))
}
>>>
3
此时,len(slice)=3,cap=3
。可见slice
的容量此时并不等于array
的长度。实际上,切片值容量的定义是
一个切片值的容量是,从其中的指针指向的那个元素值到底层数组最后一个元素值的计数值。
如果想从切片值中得到一块内存地址,可以这样做:
s := make([]byte, 200)
ptr := unsafe.Pointer(&s[0])
反过来,可以从 G o Go Go 的内存地址中构造一个切片:
var ptr unsafe.Pointer
var s1 = struct {
addr uintptr
len int
cap int
}{
ptr, length, length}
s := *(*[]byte)(unsafe.Pointer(&s1))
构造一个虚拟的结构体,把切片类型值的数据结构拼出来。
当然还有更加直接的方法,在 G o Go Go的反射中就存在一个与之对应的数据结构 SliceHeader
,可以用它来构造一个切片:
var ptr unsafe.Pointer
var s1 = struct {
addr uintptr
len int
cap int
}{
ptr, length, length}
s := *(*[]byte)(unsafe.Pointer(&s1))
未完待续