go语言和c语言在指针上有哪些区别
今天主机评测网小编给大家分享一下go语言和c语言在指针上有哪些区别的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
区别:1、go语言可以使用new关键字来分配内存创建指定类型的指针,而c语言不行。2、c语言中数组名arr代表的是数组首元素的地址,相当于“&arr[0]”;go语言中数组名arr不代表数组首元素的地址,代表的是整个数组的值。3、go语言不支持指针运算,而c语言支持指针运算。
运算符
C 和 Go 都相同:
&
运算符取出变量所在的内存地址
*
运算符取出指针变量所指向的内存地址里面的值,也叫 “ 解引用 ”
C 语言版示例:
#include<stdio.h>intmain(){intbar=1;//声明一个指向int类型的值的指针int*ptr;//通过&取出bar变量所在的内存地址并赋值给ptr指针ptr=&bar;//打印ptr的值(为地址),*prt表示取出指针变量所指向的内存地址里面的值printf("%p%d\n",ptr,*ptr);return(0);}//输出结果://0x7ffd5471ee541
Go 语言版示例:
Go 还可以使用 packagemainimport"fmt"funcmain(){bar:=1//声明一个指向int类型的值的指针varptr*int//通过&取出bar变量所在的内存地址并赋值给ptr指针ptr=&bar//打印ptr变量储存的指针地址,*prt表示取出指针变量所指向的内存地址里面的值fmt.Printf("%p%d\n",ptr,*ptr)}//输出结果://0xc0000860201
new
关键字来分配内存创建指定类型的指针。//声明一个指向int类型的值的指针//varptr*intptr:=new(int)//通过&取出bar变量所在的内存地址并赋值给ptr指针ptr=&bar
数组名和数组首地址
对于一个数组
//Cintarr[5]={1,2,3,4,5};//Go//需要指定长度,否则类型为切片arr:=[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代表整个数组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
这里涉及到偏移量的知识:一个类型为
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=[1 2 3 4 5])
指针运算
指针本质上就是一个无符号整数,代表了内存地址。
指针和整数值可以进行加减法运算,比如上文的指针偏移例子:
加n
: 一个类型为 T
的指针,以 n*sizeof(T)
为单位向高位移动。
减n
: 一个类型为 T
的指针,以 n*sizeof(T)
为单位向低位移动。
其中 sizeof(T)
代表的是数据类型占据的字节,比如 int
在 32 位环境下为 4 字节,64 位环境下为 8 字节
C 语言示例:
#include<stdio.h>intmain(){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++
从 0061FF08
移动了 sizeof(int) = 4
个字节到 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)}//输出结果://编译报错://.\main.go:13:5:invalidoperation:ptr++(non-numerictype*uint32)
编译报错
*uint32
非数字类型,不支持运算,说明 Go 是不支持指针运算的。
这个其实在 Go Wiki[1] 中的 Go 从 C++ 过渡文档中有提到过:Go has pointers but not pointer arithmetic.
Go 有指针但不支持指针运算。
另辟蹊径
那还有其他办法吗?答案当然是有的。
在 Go 标准库中提供了一个 unsafe
包用于编译阶段绕过 Go 语言的类型系统,直接操作内存。
我们可以利用 unsafe
包来实现指针运算。
funcAlignof(xArbitraryType)uintptrfuncOffsetof(xArbitraryType)uintptrfuncSizeof(xArbitraryType)uintptrtypeArbitraryTypefuncSlice(ptr*ArbitraryType,lenIntegerType)[]ArbitraryTypetypeIntegerTypetypePointerfuncAdd(ptrPointer,lenIntegerType)Pointer
核心介绍:
uintptr
: Go 的内置类型。是一个无符号整数,用来存储地址,支持数学运算。常与 unsafe.Pointer
配合做指针运算
unsafe.Pointer
: 表示指向任意类型的指针,可以和任何类型的指针互相转换(类似 C 语言中的 void*
类型的指针),也可以和 uintptr
互相转换
unsafe.Sizeof
: 返回操作数在内存中的字节大小,参数可以是任意类型的表达式,例如 fmt.Println(unsafe.Sizeof(uint32(0)))
的结果为 4
unsafe.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(*uint32类型)=>one(unsafe.Pointer类型)one:=unsafe.Pointer(ptr)//one(unsafe.Pointer类型)=>*uint32fmt.Println(one,*(*uint32)(one))//one(unsafe.Pointer类型)=>one(uintptr类型)后向高位移动unsafe.Sizeof(arr[0])=4字节//twoUintptr:=uintptr(one)+unsafe.Sizeof(arr[0])//!!twoUintptr不能作为临时变量//uintptr类型的临时变量只是一个无符号整数,并不知道它是一个指针地址,可能被GC//运算完成后应该直接转换回unsafe.Pointer:two:=unsafe.Pointer(uintptr(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""unsafe")funcmain(){m:=model.M{}m.Print()foo:=unsafe.Pointer(&m)*(*uint32)(foo)=1bar:=unsafe.Pointer(uintptr(foo)+4)*(*uint32)(bar)=2m.Print()}//输出结果://00//12
小 Tips
Go 的底层 slice
切片源码就使用了 unsafe
包
//slice切片的底层结构typeslicestruct{//底层是一个数组指针arrayunsafe.Pointer//长度lenint//容量capint}
以上就是“go语言和c语言在指针上有哪些区别”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注主机评测网行业资讯频道。
上一篇:基于Python+Vue.js怎么实现域名SSL证书监测平台
下一篇:GS Admin限流功能怎么使用