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 语言版示例:

    packagemainimport"fmt"funcmain(){bar:=1//声明一个指向int类型的值的指针varptr*int//通过&取出bar变量所在的内存地址并赋值给ptr指针ptr=&bar//打印ptr变量储存的指针地址,*prt表示取出指针变量所指向的内存地址里面的值fmt.Printf("%p%d\n",ptr,*ptr)}//输出结果://0xc0000860201

    Go 还可以使用 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 可以和 *Tuintptr 互相转换

    因此,将 *T 转换为 unsafe.Pointer 后再转换为 uintptruintptr 进行运算之后重新转换为 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限流功能怎么使用


    Copyright © 2002-2019 测速网 www.inhv.cn 皖ICP备2023010105号
    测速城市 测速地区 测速街道 网速测试城市 网速测试地区 网速测试街道
    温馨提示:部分文章图片数据来源与网络,仅供参考!版权归原作者所有,如有侵权请联系删除!

    热门搜索 城市网站建设 地区网站制作 街道网页设计 大写数字 热点城市 热点地区 热点街道 热点时间 房贷计算器