运算符分析C和Go是一样的:&运算符提取变量所在内存地址*运算符提取指针变量指向的内存地址中的值,也叫“解引用”C语言版本示例:#includeintmain(){intbar=1;//声明一个指向int类型值的指针int*ptr;//通过&获取bar变量的内存地址并赋值totheptrpointerptr=&bar;//打印ptr的值(fortheaddress),*prt表示取出指针变量指向的内存地址中的值printf("%p%d\n",ptr,*ptr);返回(0);}//输出结果://0x7ffd5471ee541Go语言版本示例:packagemainimport"fmt"funcmain(){bar:=1//声明一个指向int类型值的指针varptr*int//通过获取bar变量的内存地址&并为ptr指针赋值ptr=&bar//打印ptr变量中存放的指针地址,*prt表示取出指针变量指向的内存地址中的值fmt.Printf("%p%d\n",ptr,*ptr)}//输出结果://0xc0000860201Go也可以使用new关键字分配内存,创建指定类型的指针。//声明一个指向int类型值的指针//varptr*intptr:=new(int)//通过&获取bar变量所在的内存地址并赋值给ptr指针ptr=&bar数组名和数组firstaddressForanarray//Cintarr[5]={1,2,3,4,5};//去//需要指定长度,否则类型为slicearr:=[5]int{1,2,3,4,5}在C中,数组名arr表示数组首元素的地址,相当于&arr[0],&arr表示整个数组的首地址arr//C//arr数组名表示数组首元素的地址printf("arr->%p\n",arr);//&arr[0]表示数组首元素的地址printf("&arr[0]->%p\n",&arr[0]);//&arr表示整个数组的首地址printf("&arr->%p\n",&arr);//输出结果://arr->0061FF0C//&arr[0]->0061FF0C//&arr->0061FF0C运行程序可以发现arr&arr的输出值是一样的,但是它们的含义完全不同。首先,数组名arr作为标识符,它是arr[0]的地址,从&arr[0]的角度看,它是一个指向int类型值的指针。&arr是一个指向int[5]类型值的指针。可以通过指针偏移量//C//指针偏移量进一步验证printf("arr+1->%p\n",arr+1);printf("&arr+1->%p\n",&arr+1);//输出结果://arr+1->0061FF10//&arr+1->0061FF20这里涉及到offset的知识:一个T类型的指针的移动是基于移动单元的sizeof(T)。arr+1:arr是一个指向int类型值的指针,所以偏移量是1*sizeof(int)&arr+1:&arr是一个指向int[5]的指针,它的偏移量是1*sizeof(int)*5相信大家应该能理解C语言中arr和&arr的区别。接下来我们来看Go语言。//尝试输出数组名arr作为地址fmt.Printf("arr->%p\n",arr)fmt.Printf("&arr[0]->%p\n",&arr[0])fmt.Printf("&arr->%p\n",&arr)//输出结果://arr->%!p([5]int=[12345])//&arr[0]->0xc00000c300//&arr->0xc00000c300&arr[0]和&arr与C语言一致。但是数组名arr在Go中不再是数组首元素的地址,它代表的是整个数组的值,所以会提示%!p([5]int=[12345])指针算术指针本质上面是一个无符号整数,代表一个内存地址。指针和整型值可以进行加减运算,比如上面的指针偏移例子:添加n:一个T类型的指针,以n*sizeof(T)为单位移动到高位。Subtractn:T类型指针,以n*sizeof(T)为单位向低位移动。其中sizeof(T)表示该数据类型占用的字节数,例如int在32位环境下为4字节,64位环境下为8字节C语言示例:#includeintmain(){intarr[]={1,2,3,4,5};//ptr是一个指针,arr数组第一个元素的地址int*ptr=arr;printf("%p%d\n",ptr,*ptr);//ptr指针向高位移动一个单位,移动到arr数组的第二个元素地址ptr++;printf("%p%d\n",ptr,*ptr);return(0);}//输出结果://0061FF081//0061FF0C2这里ptr++将sizeof(int)=4字节从0061FF08移动到0061FF0C,指向下一个数组元素的地址Go语言例子:packagemainimport"fmt"funcmain(){arr:=[5]uint32{1,2,3,4,5}//ptr是一个指针,是arr数组ptr:=&arr[0]fmt的第一个元素的地址。Println(ptr,*ptr)//ptr指针向高位移动一个单位,移动到arr数组的第二个元素地址ptr++fmt.Println(ptr,*ptr)}//输出结果://比较ileerror://.\main.go:13:5:invalidoperation:ptr++(non-numerictype*uint32)编译错误*uint32non-numerictype,doesnotsupportoperations,说明Go不支持指针操作。GoWiki[1]中的GofromC++transitiondocument中其实提到了这一点:Gohaspointersbutnotpointerarithmetic。Go有指针,但不支持指针运算。另辟蹊径,还有别的办法吗?答案当然是肯定的。Go标准库中提供了一个unsafe包,用于在编译阶段绕过Go语言的类型系统,直接操作内存。我们可以使用unsafe包来实现指针运算。funcAlignof(xArbitraryType)uintptrfuncOffsetof(xArbitraryType)uintptrfuncSizeof(xArbitraryType)uintptrtypeArbitraryTypefuncSlice(ptr*ArbitraryType,lenIntegerType)[]ArbitraryTypetypeIntegerTypetypePointerfuncAdd(ptrPointer,lenIntegerType)指针核心介绍:uintptr的内置类型:Go。它是一个无符号整数,用于存储地址和支持数学运算。常与unsafe.Pointer一起使用做指针运算unsafe.Pointer:表示指向任意类型的指针,可以转换为任意类型的指针(类似于C语言中的void*类型指针),也可以转换为uintptrunsafe。sizeof:返回操作数在内存中的字节大小,参数可以是任意类型的表达式,比如fmt.Println(unsafe.Sizeof(uint32(0)))的结果为4unsafe.Offsetof:的参数该函数必须是一个字段x.f,然后返回f字段相对于x起始地址的偏移量,用于计算结构体成员的偏移量原理:Go的uintptr类型存储地址,支持数学运算*T(任何指针类型)不能用unsafe.Pointer操作,但是unsafe.Pointer可以转为*T和uintptr。所以,先把*T转成unsafe.Pointer再转成uintptr,运算后再把uintptr转成unsafe.Pointer=>*T。代码实现:packagemainimport("fmt""unsafe")funcmain(){arr:=[5]uint32{1,2,3,4,5}ptr:=&arr[0]//ptr(*uint32type)=>one(unsafe.Pointertype)one:=unsafe.Pointer(ptr)//one(unsafe.Pointertype)=>*uint32fmt.Println(one,*(*uint32)(one))//one(unsafe.pointertype)=>one(uintptrtype)并移动到高位unsafe.Sizeof(arr[0])=4bytes//twoUintptr:=uintptr(one)+unsafe.Sizeof(arr[0])//!!twoUintptrcannotbeasatemporaryvariable//uintptr类型的临时变量只是一个无符号整数,不知道是指针地址,可能会转换回unsafe.Pointer:two:=unsafe.Pointer(你intptr(one)+unsafe.Sizeof(arr[0]))fmt.Println(two,*(*uint32)(two))}//输出结果://0xc0000121501//0xc0000121542甚至可以改变私有属性结构成员://model/model.gopackagemodelimport("fmt")typeMstruct{foouint32baruint32}func(mM)Print(){fmt.Println(m.foo,m.bar)}//main.gopackagemainimport("example/model""不安全")funcmain(){m:=model.M{}m.Print()foo:=unsafe.Pointer(&m)*(*uint32)(foo)=1bar:=unsafe.Pointer(uintptr(foo)+4)*(*uint32)(bar)=2m.Print()}//输出结果://00//12小TipsGo的底层slice源码使用了unsafe包//slice的底层结构typeslicestruct{//的底层是一个数组指针arrayunsafe.Pointer//lengthlenint//capacitycapint}总结Go可以使用&运算符获取地址,或者使用new创建指针Go的数组名不是Go的指针首元素的地址不支持操作Go可以使用unsafe包来破解安全机制来操作指针,但是对于我们开发pers,是“不安全”不安全参考[1]GoWiki:https://github.com/golang/go/wiki/GoForCPPProgrammers这篇文章转载自微信公众号《寻找Gopher》,可以通过转载文章关注以下二维码,请联系歌斐公众号寻找。