咱们知道在golang中是存在指针这个概念的。对于指针不少人有点忌惮(多是由于以前学习过C语言),由于它会致使不少异常的问题。可是不少人学习以后发现,golang中的指针很简单,没有C那么复杂。因此今天就详细来讲说指针。golang
由于博客园发布markdown格式存在问题,请移步http://www.linkinstar.wiki/2019/06/06/golang/source-code/point-unsafe/数组
a := 1 p := &a fmt.Println(p)
输出:0xc42001c070安全
能够看到p就是一个指针,也能够说是a的地址。markdown
a := 1 var p *int p = &a fmt.Println(p)
或者也能够写成这样,由于我知道,在不少人看来,看到*号才是指针(手动滑稽)学习
a := 1 p := &a fmt.Println(*p)
输出:1ui
而后使用就直接经过*号就能去到对应的值了,就这么简单指针
Golang中指针之因此看起来很简单,是由于指针的功能很少。咱们能看到的功能就是指针的指向一个地址而已,而后对于这个地址也只能进行传递,或者经过这个的地址去访问值。code
若是只是单纯说go中指针的功能,上面就已经说完了,不必写博客,可是其实go中还有一个包叫unsafe,有了它,指针就能够像C同样想干吗干吗了。对象
其实指针有三种:
一种是咱们常见的*
,用*去表示的指针;
一种是unsafe.Pointer,Pointer是unsafe包下的一个类型;
最后一种是uintptr,uintptr就厉害了,这玩意是能够进行运算的也就是能够++--;内存
他们之间有这样的转换关系:
*
<=> unsafe.Pointer <=> uintptr
从这样的关系你大概就能够猜到,咱们使用的指针*p转换成Pointer而后转换uintptr进行运算以后再原路返回,理论上就能等同于进行了指针的运算。咱们下面就来实践一下。
func main() { s := make([]int, 10) s[1] = 2 p := &s[0] fmt.Println(*p) up := uintptr(unsafe.Pointer(p)) up += unsafe.Sizeof(int(0)) // 这里可不是up++哦 p2 := (*int)(unsafe.Pointer(up)) fmt.Println(*p2) }
输出:
0
2
从代码中咱们能够看到,咱们首先将指针指向切片的第一个位置,而后经过转换获得uintptr,操做uintptr + 上8位(注意这里不能++由于存放的是int,下一个元素位置相隔举例int个字节),最后转换回来获得指针,取值,就能取到切片的第二个位置了。
固然有人确定要说了,上面那个一顿操做猛如虎,不就是访问下一个位置嘛,我直接访问就好了。
那下面就是厉害的来了,咱们知道若是一个结构体里面定义的属性是私有的,那么这个属性是不能被外界访问到的。咱们来看看下面这个操做:
package basic type User struct { age int name string }
package main func main() { user := &basic.User{} fmt.Println(user) s := (*int)(unsafe.Pointer(user)) *s = 10 up := uintptr(unsafe.Pointer(user)) + unsafe.Sizeof(int(0)) namep := (*string)(unsafe.Pointer(up)) *namep = "xxx" fmt.Println(user) }
User是另一个basic包中的结构体,其中的age是小写开头的,理论上来讲,咱们在外部没有办法修改age的值,可是通过上面这波操做以后,输出信息是:
&{0 }
&{10 xxx}
也就是说成功操做到告终构体的私有属性。
顺便提一句:建立结构体会被分配一块连续的内存,结构体的地址也表明了第一个成员的地址。
下面咱们来验证一下你是否已经学会了unsafe的操做,尝试不看一个小结,本身尝试一下:如何完成字符串到[]byte的转换,而且不开辟新的空间?
咱们知道若是将字符串转换成[]byte很是方便
s := "123" a := []byte(s)
可是这样须要开辟额外的空间,那么如何实现原地的,不须要拷贝数据的转换呢?
咱们想一下,其实从底层的存储角度来讲,string的存储规则和[]byte是同样的,也就是说,其实指针都是从某个位置开始到一段空间,中间一格一格。因此利用unsafe就能够作到。
func main() { s := "123" a := []byte(s) print("s = " , &s, "\n") print("a = " , &a, "\n") a2 := (*[]byte)(unsafe.Pointer(&s)) print("a2 = " , a2, "\n") fmt.Println(*a2) }
输出结果:
s = 0xc420055f40
a = 0xc420055f60
a2 = 0xc420055f40
[49 50 51]
咱们能够看到s和a的地址是不同的,可是s和a2的地址是同样的,而且a2已是一个[]byte了。
嘿嘿嘿~你觉得这样就结束了???
其实这个转换是存在问题的,问题就在新的[]byte的Cap没有正确的初始化。
咱们打印一下cap看一下
fmt.Println("cap a =", cap(a))
fmt.Println("cap a2 =", cap(*a2))
结果是:
cap a = 32
cap a2 = 17418400
这么大的容量是要上天呢???
在src/reflect/value.go下看
type StringHeader struct { Data uintptr Len int } type SliceHeader struct { Data uintptr Len int Cap int }
看到其实string没有cap而[]byte有,因此致使问题出现,也容易理解,string是没有容量扩容这个说法的,因此新的[]byte没有赋值cap因此使用了默认值。
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: stringHeader.Data, Len: stringHeader.Len, Cap: stringHeader.Len, } return *(*[]byte)(unsafe.Pointer(&bh))
经过从新设置SliceHeader就能够完成
以上就是全部golang指针和unsafe的相关细节和使用。那么确定有人会问这个有什么用了?
如map中经过key获取value的时候:
v := add(unsafe.Pointer(b), dataOffset+bucketCnt * uintptr(t.keysize)+i * uintptr(t.valuesize))
经过桶的指针的偏移拿到值,具体我就很少介绍了。 总之对于你看golang源码的时候会有很大帮助的。可能必要的时候你也能用到它,仍是那句话,除非你知道它在干什么,不然不要用。