Array
声明一个 array的方式为:
var variable_name [SIZE] variable_type 例子: var arr[10] int
值得注意的是,这里的size 作为明确的参数,需要显示的传进来, arr[10] int 和 arr[12]int 是不能使用简单的方式互转的
Array,进行赋值或者传参都是拷贝值,不影响原始数组:
例子:
func enterAndLeaveCall(tag string) string { fmt.Printf("\nenter %v<<<<<\n", tag) return fmt.Sprintf("\nleave %v>>>>>>\n", tag) } func testArrayChange(bArr [6]int) { defer fmt.Printf(enterAndLeaveCall("testArrayChange")) fmt.Printf(" before change array: %v,address %p \n", bArr, &bArr) func(cArr [6]int) { cArr[0] = -100 fmt.Printf(" change array: %v, address %p \n", cArr, &cArr) }(bArr) fmt.Printf(" after change array: %v,address %p \n", bArr, &bArr) } func main() { var tmpArr = [...]int{1, 2, 3, 4, 5, 6} var tmpSlice = tmpArr[:] var noLengthBArr=[]int{1,2,3} testArrayChange(tmpArr) } |
输出的结果是
enter testArrayChange<<<<< before change array: [1 2 3 4 5 6],address 0xc0000ac090 change array: [-100 2 3 4 5 6], address 0xc0000ac0f0 after change array: [1 2 3 4 5 6],address 0xc0000ac090 leave testArrayChange>>>>>> |
可以看到,Array的原始值没有改变,说明函数传参的array 跟原始Array不是同一个Array ,是深拷贝出来的另一个array
有时候我们会避免传参的时候进行,太多的拷贝,这时候,我们会想直接通过传指针的方式来进行传参,这样的话,就不需要进行深拷贝了。
我们看看演示代码
func testArrayWithPoint(tmpArray *[]int) { defer fmt.Printf(enterAndLeaveCall("testArrayWithPoint")) fmt.Printf("%v , address %p \n ",*tmpArray,tmpArray) func (bArr *[]int){ (*bArr)[1]=30 fmt.Printf("%v , address %p \n ",*bArr,bArr) }(tmpArray) /*(*tmpArray)[10]=300; // out of bounds*/ fmt.Printf("%v , address %p \n ",*tmpArray,tmpArray) fmt.Printf("use len: %v \n",len(*tmpArray)) } func main() { var tmpArr = [...]int{1, 2, 3, 4, 5, 6} var tmpSlice = tmpArr[:] var noLengthBArr=[]int{1,2,3} // testArrayChange(tmpArr) testArrayWithPoint(&noLengthBArr) } |
输出的结果为:
enter testArrayWithPoint<<<<< [1 2 3] , address 0xc0000a6020 [1 30 3] , address 0xc0000a6020 [1 30 3] , address 0xc0000a6020 use len: 3 leave testArrayWithPoint>>>>>> |
可以看到,通过指针传参的方式,函数里面修改了数组内容,也能反映到外部地址,因为传参中,两者指向的地址是一致的
那么通过这种方式,是不是就能解决传参拷贝的问题了,是,算是能解决深拷贝效率不高问题,但指针传值略显复杂,还不能限制index视图。
Slice
Slice 也是类似于指针的方式传递数组的值,在值拷贝或者函数参数传递的时候不会引起数组内容的拷贝,slice 和原数组,底层使用的是同一个数组,所以slice的改动,会引起 原数组的改变
例子:
func testSliceChange(aSlice []int) { defer fmt.Printf(enterAndLeaveCall("testSliceChange")) fmt.Printf("slice value %v,address is %p\n", aSlice,aSlice) //fmt.Printf("unsafe Point %v \n", unsafe.Pointer(&aSlice)) func(tSlice []int) { tSlice[0] = 99 fmt.Printf("value is %v, slice address %p \n ",tSlice, tSlice) }(aSlice) fmt.Printf(" slice value %v,address is %p\n", aSlice, aSlice) //bSlice:=aSlice[1:2] //fmt.Printf("%v \n",bSlice[4]) } func main() { var tmpArr = [...]int{1, 2, 3, 4, 5, 6} var tmpSlice = tmpArr[:] var noLengthBArr=[]int{1,2,3} //testArrayChange(tmpArr) //testArrayWithPoint(&noLengthBArr) //noLengthBArr=append(noLengthBArr,30,3) testSliceChange(tmpSlice) } |
输出:
enter testSliceChange<<<<< slice value [1 2 3 4 5 6],address is 0xc0000ac060 value is [99 2 3 4 5 6], slice address 0xc0000ac060 slice value [99 2 3 4 5 6],address is 0xc0000ac060 leave testSliceChange>>>>>> |
可见 slice能改变原数组的值
Slice 的创建方式
序号 | 方式 | 代码示例 |
---|---|---|
1 | 直接声明 | var slice []int |
2 | new | slice := *new([]int) |
3 | 字面量 | slice := []int{1,2,3,4,5} |
4 | make | slice := make([]int, 5, 10) |
5 | 从切片或数组“截取” | slice := array[1:5] 或 slice := sourceSlice[1:5] |
Slice 的常见操作:
1)拼接两个切片
a = append(a, b...)
(2)复制一个切片
b = append([]T(nil), a...)
b = append(a[:0:0], a...)
(3)删除切片的第i~第j-1个元素([i,j)),如果切片的元素是指针或者具有指针成员的结构体,需要避免内存泄露问题,此时需要修改删除切片元素的代码如下:
for k, n := len(a)-j+i, len(a); k < n; k++ {
a[k] = nil // 或该类型的零值}
a = a[:len(a)-j+i]
(4)删除第i个元素a = append(a[:i], a[i+1:]...)
同样的,为了避免内存泄露
copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of Ta = a[:len(a)-1]
(5)弹出切片最后一个元素,即出队列尾(pop back)
x, a = a[len(a) - 1], a[:len(a)-1]
(6)弹出切片第一个元素,即出队列头(pop)
x, a = a[0], a[1:]
(7)在第i个元素前插入一个切片
ba = append(a[:i], append(b, a[i:]...)...)
(8)切片乱序(Go 1.10以上)
for i := len(a) - 1; i > 0; i-- { j := rand.Intn(i + 1) // 生成一个[0,i+1)区间内的随机数 a[i], a[j] = a[j], a[i] }
浅谈 Slice 的内存结构
s 为 一个slice
根据上图,我们可以通过代码来验证 slice的内存结构
一个常见的思路是,怎么从slice获取到 指针,通过指针来访问length, capacity, 和 底层的[]int ,而不是简单通过len(),cap()和索引方式取得slice的值
先上整体代码
func pointerCaclu(tmpSlice []int) { defer fmt.Printf(enterAndLeaveCall("pointerCaclu")) p1 := unsafe.Pointer(&tmpSlice) fmt.Printf("change to Slice Header %v \n",*(*reflect.SliceHeader)(p1)) fmt.Printf("slice addr is %p,slice[0] addr is %p ,and point is %v \n",&tmpSlice,&(tmpSlice[0]),p1) // **[]int ,指的是 *(*[]int) , **(**[]int) 就是 []int了 // unsafe Point 转成啥样的指针都行,但是看意义 var p2 = unsafe.Pointer(uintptr(p1) + uintptr(8)) var p3 = unsafe.Pointer(uintptr(p1) + uintptr(16)) fmt.Printf("len is %v,cap is %v \n", *((*int)(p2)), *((*int)(p3))) fmt.Printf("%v, %v,value is %v\n ", p1, uintptr(p1), **((**int)(p1))) //var forceGetArrayP = **(**[20]int)(p1) //for i := 0; i < 10; i++ { // fmt.Printf("%v ", forceGetArrayP[i]) //} } |
输出内容为:
enter pointerCaclu<<<<< change to Slice Header {824634466400 6 6} slice addr is 0xc0000b0160,slice[0] addr is 0xc0000b6060 ,and point is 0xc0000b0160 len is 6,cap is 6 0xc0000b0160, 824634442080,value is 99 leave pointerCaclu>>>>>> |
fmt.Printf("%p ",&v) 输出的是v 指针的表示的值
怎么获取, 获取一个value的指针值,并为了能对其进行加减操作,把其转为uintptr.
把 slice 的值 ,转为内部实现的SliceHeader 的指针值,可以看到,
slice 里面指针的值,和len,cap等信息。
其中struct 的地址和struct里面的第一个变量的地址相同
test:
func printStructPoint() { /*struct 的地址,和其第一个变量一致,跟数组一样的逻辑*/ defer fmt.Printf(enterAndLeaveCall("printStructPoint")) tP:=TestStrP{num: 10,name: "apple"} fmt.Printf("tp point value %p, tp inner element num address %v, another name address %v \n",&tP,&(tP.num),&(tP.name)) } |
输出
enter printStructPoint<<<<< leave printStructPoint>>>>>>
|
slice 内部运行时候实现是以SliceHeader实现的
// SliceHeader is the runtime representation of a slice. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type SliceHeader struct { Data uintptr Len int Cap int } |
p1 := unsafe.Pointer(&tmpSlice) 获取到slice 的指针值, 而从上图可以知道,这个地址获取到slice整体指针,这个地址和 Data的地址(就是底层数组指针的值是一致的),和 slice 里,而这个值 &(tmpSlice[0]) 是不一样的
var p2 = unsafe.Pointer(uintptr(p1) + uintptr(8))
var p3 = unsafe.Pointer(uintptr(p1) + uintptr(16))
fmt.Printf("len is %v,cap is %v \n", *((*int)(p2)), *((*int)(p3)))
其中 64位机器,地址总线也是64位的,也就是每个指针地址相隔8个字节,(把 指向底层数组的指针值叫 P, 由于 &slice 的值是 &P, 所以 uintptr(unsafe.Pointer(&slice)+8 为 &len, uintptr(unsafe.Pointer(&slice)+16 为 &cap
所以通过 unsafe.Point(&slice)指针,直接构造出slice 也是可以的
unc constructWithSliceHeader(tmpSlice []int) { defer fmt.Printf(enterAndLeaveCall("constructWithSliceHeader")) var bWithSliceHead = reflect.SliceHeader{Len: len(tmpSlice) + 10, Cap: len(tmpSlice) + 10, Data: uintptr(unsafe.Pointer(&tmpSlice))} var cSlice = **(**[]int)(unsafe.Pointer(&bWithSliceHead)) for i := 0; i < len(cSlice) /*what if put 11 */ ; i++ { fmt.Printf("%v ", cSlice[i]) } fmt.Printf("\n"); var sliHeadP=*(*reflect.SliceHeader)(unsafe.Pointer(&tmpSlice)) // 地址,长度,容量 fmt.Printf("%v \n",sliHeadP) fmt.Printf("%v \n",*((*[6]int)(unsafe.Pointer(sliHeadP.Data)))) } |
输出
enter constructWithSliceHeader<<<<< leave constructWithSliceHeader>>>>>> |
关于slice ,里面的常规方法, copy ,append,make 等方法,代码研读
直接看代码: go/src/runtime/slice.go
切片中有一个需要注意的问题。
就是循环,值拷贝问题。
func main() { slice := []int{10, 20, 30, 40} for index, value := range slice { fmt.Printf("value = %d , value-addr = %x , slice-addr = %x\n", value, &value, &slice[index]) |
输出:
|
从上面结果我们可以看到,如果用 range 的方式去遍历一个切片,拿到的 Value 其实是切片里面的值拷贝。所以每次打印 Value 的地址都不变。
所以在for 循环里面做闭包,参数传递是for 循环参数可能会有问题,需采用拷贝变量方式,不适合直接引用。
源码:
package main
import (
"fmt"
"reflect"
"unsafe"
)
func enterAndLeaveCall(tag string) string {
fmt.Printf("\nenter %v<<<<<\n", tag)
return fmt.Sprintf("\nleave %v>>>>>>\n", tag)
}
func testSliceChange(aSlice []int) {
defer fmt.Printf(enterAndLeaveCall("testSliceChange"))
fmt.Printf("slice value %v,address is %p\n", aSlice,aSlice)
//fmt.Printf("unsafe Point %v \n", unsafe.Pointer(&aSlice))
func(tSlice []int) {
tSlice[0] = 99
fmt.Printf("value is %v, slice address %p \n ",tSlice, tSlice)
}(aSlice)
fmt.Printf(" slice value %v,address is %p\n", aSlice, aSlice)
//bSlice:=aSlice[1:2]
//fmt.Printf("%v \n",bSlice[4])
}
func testArrayWithPoint(tmpArray *[]int) {
defer fmt.Printf(enterAndLeaveCall("testArrayWithPoint"))
fmt.Printf("%v , address %p \n ",*tmpArray,tmpArray)
func (bArr *[]int){
(*bArr)[1]=30
fmt.Printf("%v , address %p \n ",*bArr,bArr)
//var c2=bArr
//(*c2)[2]=39
}(tmpArray)
/*(*tmpArray)[10]=300; // out of bounds*/
fmt.Printf("%v , address %p \n ",*tmpArray,tmpArray)
fmt.Printf("use len: %v \n",len(*tmpArray))
}
func pointerCaclu(tmpSlice []int) {
defer fmt.Printf(enterAndLeaveCall("pointerCaclu"))
p1 := unsafe.Pointer(&tmpSlice)
fmt.Printf("change to Slice Header %v \n",*(*reflect.SliceHeader)(p1))
fmt.Printf("slice addr is %p,slice[0] addr is %p ,and point is %v \n",&tmpSlice,&(tmpSlice[0]),p1)
// **[]int ,指的是 *(*[]int) , **(**[]int) 就是 []int了
// unsafe Point 转成啥样的指针都行,但是看意义
var p2 = unsafe.Pointer(uintptr(p1) + uintptr(8))
var p3 = unsafe.Pointer(uintptr(p1) + uintptr(16))
fmt.Printf("len is %v,cap is %v \n", *((*int)(p2)), *((*int)(p3)))
var oneApple=*((**int)(p1))
var d1= unsafe.Pointer(oneApple)
//d2:= unsafe.Pointer(uintptr(d1)+8);
//d2:=(*[12]int)d1
var oneMe=(*[336]int)(d1)
fmt.Printf("%v, %v,value is %v\n ", p1, uintptr(p1), oneMe[233])
//var forceGetArrayP = **(**[20]int)(p1)
//for i := 0; i < 10; i++ {
// fmt.Printf("%v ", forceGetArrayP[i])
//}
}
func selfConstructSlice(tmpSlice []int) {
defer fmt.Printf(enterAndLeaveCall("selfConstructSlice"))
var s1 = struct {
addr uintptr
len int
cap int
//tmp2 int
}{addr: uintptr(unsafe.Pointer(&tmpSlice)) , len: 136, cap:336}
var cSlice = **(**[]int)( unsafe.Pointer(&s1)) // unsafe Point 转成啥样的指针都行,但是看意义
// **[]int ,指的是 *(*[]int) , **(**[]int) 就是 []int了
fmt.Printf("len of cSlice %v\n", len(cSlice))
for i := 0; i < len(tmpSlice) ; i++ {
fmt.Printf("%v ", cSlice[i])
}
}
func constructWithSliceHeader(tmpSlice []int) {
defer fmt.Printf(enterAndLeaveCall("constructWithSliceHeader"))
var bWithSliceHead = reflect.SliceHeader{Len: len(tmpSlice) + 10, Cap: len(tmpSlice) + 10,
Data: uintptr(unsafe.Pointer(&tmpSlice))}
var cSlice = **(**[]int)(unsafe.Pointer(&bWithSliceHead))
for i := 0; i < len(cSlice) /*what if put 11 */ ; i++ {
fmt.Printf("%v ", cSlice[i])
}
fmt.Printf("\n");
var sliHeadP=*(*reflect.SliceHeader)(unsafe.Pointer(&tmpSlice)) // 地址,长度,容量
var oneMe=(*[6]int)(unsafe.Pointer(sliHeadP.Data))
fmt.Printf("%v \n",sliHeadP)
fmt.Printf("%v \n",(*(oneMe))[2])
}
func testArrayChange(bArr [6]int) {
defer fmt.Printf(enterAndLeaveCall("testArrayChange"))
fmt.Printf(" before change array: %v,address %p \n", bArr, &bArr)
func(cArr [6]int) {
cArr[0] = -100
fmt.Printf(" change array: %v, address %p \n", cArr, &cArr)
}(bArr)
fmt.Printf(" after change array: %v,address %p \n", bArr, &bArr)
fmt.Printf("array addr is %p, and array 0 ele addr is %p \n",&bArr,&(bArr[0])) //the same
}
func testPrintAddressAndPoint() {
defer fmt.Printf(enterAndLeaveCall("testPrintAddressAndPoint"))
var num=9
var tmpP=#
fmt.Printf("num %s %p, and point %v, and tmpP %p", &num,&num,tmpP,tmpP)
}
type TestStrP struct {
num int
name string
}
func printStructPoint() {
/*struct 的地址,和其第一个变量一致,跟数组一样的逻辑*/
defer fmt.Printf(enterAndLeaveCall("printStructPoint"))
tP:=TestStrP{num: 10,name: "apple"}
fmt.Printf("tp point value %p, tp inner element num address %v, another name address %v \n",&tP,&(tP.num),&(tP.name))
}
func main() {
var tmpArr = [...]int{1, 2, 3, 4, 5, 6}
var tmpSlice = tmpArr[:]
var noLengthBArr=[]int{1,2,3}
testArrayChange(tmpArr)
testArrayWithPoint(&noLengthBArr)
//noLengthBArr=append(noLengthBArr,30,3)
testSliceChange(tmpSlice)
pointerCaclu(tmpSlice)
selfConstructSlice(tmpSlice)
constructWithSliceHeader(tmpSlice)
//testPrintAddressAndPoint()
//printStructPoint()
var ber=[3]int{1,23,3}
var cer=&ber
fmt.Print(cer[2])
}