Golang之Slice&&Array深度解读
一,基本用法
数组
- 数组是具有同一类型的一组已编号且长度固定的数据项序列
- 声明方式
1)在Go语言中,数组的声明方式为:var array-name [len]type
2)声明时没有指定数组的初始化值,因此所有的元素都会被自动初始化为该类型的默认值,例如:var a1 [5]int ,数组中的五个值都为0
3)Go语言中的数组是值类型,因此还可以使用new来创建。例如:var a2 = new([5]int),new返回的是该类型的指针,因此a2的类型为*[5]int,a1的类型为[5]int。
- 初始化方法
1)我们通常在{}中填写初始化值来初始化数组,初始化值的个数不允许超过长度len。
2)也可以使用 {index1: a, index2: b} 的方式初始化数组,指明数组的索引和对应的下标值,未指明的下标所在位置的值等于默认值 0:
3)暗示数组的长度:初始化的时候可以不指名数组的长度,而是使用[...]代替,然后再{}中写明元素的值
1)
b := [5]int{1, 2}
for index, val := range b {
fmt.Printf("下标 = %d, 值 = %d\n", index, val)
}
2)
d := [5]int{0: 1, 2: 3}
for index, val := range d {
fmt.Printf("下标 = %d, 值 = %d\n", index, val)
}
3)
c := [...]int{1, 2, 3, 4, 5}
for index, val := range c {
fmt.Printf("下标 = %d, 值 = %d\n", index, val)
}
- 数组指针和指针数组
- 数组指针:指向数组的指针
- 指针数组:装满了指针的数组
1)数组指针:声明一个数组a,然后将他的地址赋值给arrayPointer。这样一来,arrayPointer就是一个指向数组a的指针,即数组指针,他的类型为*[5]int。
2)指针数组:初始化数组pointerArray,传入的初始值为整型m与n的内存地址(&m和&n),那么这样一来pointerArray就是一个装着int类型的指针数组,即指针数组,他的类型为[2]*int
1)
a := [5]int{1, 2, 3, 4, 5}
arrayPointer := &a
fmt.Println(arrayPointer)
2)
m := 1
n := 2
pointerArray := [2]*int{&m, &n}
fmt.Println(pointerArray)
切片
- 定义
1)切片是GO中的一种基本数据结构。切片的想法是由动态数组来的,但是切片本身并不是动态数组或者数组指针。切片常见的操作有reslice,append,copy。与此同时切片还有可索引,可迭代的优秀特性。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装。切片是对数组的一个连续片段的引用,切片提供了一个指向底层数组的动态窗口。和数组不同的是切片的长度可以在运行时修改,最小为0最大为相关数组的长度:切片是一个长度可变的数组。
2)slice 共有三个属性:
指针,指向底层数组;
长度,表示切片可用元素的个数,也就是说使用下标对 slice 的元素进行访问时,下标不能超过 slice 的长度;
容量,底层数组的元素个数,容量 >= 长度。在底层数组不进行扩容的情况下,容量也是 slice 可以扩张的最大限度
- struct
1)切片的结构是由3部分组成,Pointer是指向一个数组的指针,len代表了当前切片的长度,cap是当前切片的的容量。cap总是大于等于len 。
2)如果想从slice中得到一块内存地址,可以这样做:
3)反过来,从Go的内存地址中构造一个slice
4)当然有更加直接的方式,在go的反射中就存在一个与之对应的数据结构SliceHeader,我们可以用她来构造一个Slice
1)
type slice struct {
array unsafe.Pointer
len int
cap int
}
2)
s := make([]byte,200)
ptr := unsafe.Pointer(&s[0])
3)
var ptr unsafe.Pointer
var s1 = struct {
addr uintptr
len int
cap int
}{ptr,length,length}
s := *(*[]byte)(unsafe.Pointer(&s1))
构造一个虚拟的结构体,把slice的数据结构拼出来
4)
var o []byte
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&o)))
sliceHeader.Cap = length
sliceHeader.Len = length
sliceHeader.Data = uintptr(ptr)
- 创建切片
1)make函数允许程序运行期间指定数组的长度,绕开了数组类型必须使用编译期常量的限制。创建切片有两种形式:make创建切片,空切片。
2)图一是用make函数创建的len为4,cap为6的切片。内存空间申请了6个int类型的内存的大小,由于len=4,所以后面的两个暂时访问不到,但是容量还是在的这个时候数组里面的每个变量都是0
3)下面图二使用了字面量创建了一个len=6,cap=6的切片,这个时候数组里的每个元素都初始化完成了。
4)下图三的SliceA就创建出了一个len = 3,cap = 3的切片 。从原数组的第三位元素开始(0是第一位)开始切,一直切到第四位为止(不包括第五位)。(个人感觉第三张图的写法有误,应该是Slice := array[2:5],但是没办法解释SliceB的容量问题)
1)
func makeslice(et *_type, len, cap int) slice {
maxElements := maxSliceCap(et.size)
if len < 0 || uintptr(len) > maxElements {
panic(errorString("makeslice: len out of range"))
}
if cap < len || uintptr(cap) > maxElements {
panic(errorString("makeslice: cap out of range"))
}
p := mallocgc(et.size*uintptr(cap), et, true)
return slice{p, len, cap}
}
func makeslice64(et *_type, len64, cap64 int64) slice {
len := int(len64)
if int64(len) != len64 {
panic(errorString("makeslice: len out of range"))
}
cap := int(cap64)
if int64(cap) != cap64 {
panic(errorString("makeslice: cap out of range"))
}
return makeslice(et, len, cap)
}
- nil和空切片
1)nil切片:var slice []int。nil切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就会使用nil切片,比如函数发生异常的时候,返回的切片就是nil切片,nil切片的指针指向nil。
2)空切片就用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片(如图二)。空切片和nil切片的区别在于,空切片的指向的地址不是nil,指向的是内存中的一个内存地址。当时他没有分配任何内存空间,即底层元素为0个元素。
3)最后需要说明的一点是。不管是使用 nil 切片还是空切片,对其调用内置函数 append,len 和 cap 的效果都是一样的。
2)
silce := make( []int , 0 )
slice := []int{ }
- 切片扩容
当一个切片的容量满了的时候,就需要扩容。策略是什么?
func growslice(et *_type, old slice, cap int) slice {
if raceenabled {
callerpc := getcallerpc(unsafe.Pointer(&et))
racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
}
if msanenabled {
msanread(old.array, uintptr(old.len*int(et.size)))
}
if et.size == 0 {
if cap < old.cap {
panic(errorString("growslice: cap out of range"))
}
return slice{unsafe.Pointer(&zerobase), old.len, cap}
}
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for newcap < cap {
newcap += newcap / 4
}
}
}
var lenmem, newlenmem, capmem uintptr
const ptrSize = unsafe.Sizeof((*byte)(nil))
switch et.size {
case 1:
lenmem = uintptr(old.len)
newlenmem = uintptr(cap)
capmem = roundupsize(uintptr(newcap))
newcap = int(capmem)
case ptrSize:
lenmem = uintptr(old.len) * ptrSize
newlenmem = uintptr(cap) * ptrSize
capmem = roundupsize(uintptr(newcap) * ptrSize)
newcap = int(capmem / ptrSize)
default:
lenmem = uintptr(old.len) * et.size
newlenmem = uintptr(cap) * et.size
capmem = roundupsize(uintptr(newcap) * et.size)
newcap = int(capmem / et.size)
}
if cap < old.cap || uintptr(newcap) > maxSliceCap(et.size) {
panic(errorString("growslice: cap out of range"))
}
var p unsafe.Pointer
if et.kind&kindNoPointers != 0 {
p = mallocgc(capmem, nil, false)
memmove(p, old.array, lenmem)
memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
} else {
p = mallocgc(capmem, et, true)
if !writeBarrier.enabled {
memmove(p, old.array, lenmem)
} else {
for i := uintptr(0); i < lenmem; i += et.size {
typedmemmove(et, add(p, i), add(old.array, i))
}
}
}
return slice{p, old.len, newcap}
}
- 切片拷贝
...
二,中阶用法