【Go语言】Go语言中的指针

Go语言中的指针

变量的本质是对一块内存空间的命名,我们可以通过引用变量名来使用这块内存空间存储的值,而指针则是用来指向这些变量值所在内存地址的值。

注:变量值所在内存地址的值不等于该内存地址存储的变量值。

Go语言中,如果一个变量是指针类型的,可以用这个变量来存储指针类型的值。

以下是Go语言中,指针的简单使用:

Go 复制代码
a := 100
var ptr *int  // 声明指针类型
ptr = &a      // 初始化指针类型值为变量 a 
fmt.Println(ptr)
fmt.Println(*ptr)

如上代码中,变量 ptr 就是一个指针类型,表示指向存储 int 类型值的指针,ptr本身是一个内存地址,因此需要通过内存地址进行赋值(通过 &a 获取变量 a 所在的内存地址),赋值之后,可以通过 *ptr 获取指针指向内存地址所存储的变量值,这种操作称为"间接引用"。

1 指针类型的声明和初始化

指针变量在传值时之所以可以节省内存空间,是因为指针指向的内存地址的大小是固定的,在32位机器上占4个字节,在64位机器上占8个字节,这与指针指向内存地址存储的值类型无关。

Go 复制代码
var ptr *int
fmt.Println(ptr)
fmt.Println(*ptr)
​
a := 100
var ptr *int
ptr = &a
fmt.Println(ptr)
fmt.Println(*ptr)

指针被声明后,没有指向任何的内存空间,此时指针的值是零值nil,可以通过&变量名的方式获取变量对应的内存地址,再将其赋值给指针,这样就完成指针的初始化操作。

也能够通过 := 实现指针类型的初始化,代码如下所示:

Go 复制代码
b := 100
ptr2 := &b
fmt.Printf("%p\n", ptr2)
fmt.Printf("%d\n", *ptr2)

通过 := 进行指针的初始化,无需声明指针类型,底层会自动判断。

此外,也可以通过内置函数 new 声明指针:

Go 复制代码
ptr3 := new(int)
*ptr3 = 100

通过 new 初始化的指针,已经指向的内存地址,此时内存地址中存储的值是该指针类型的零值。

2 通过指针传值

通过指针传值能够节省内存空间,此外还能够在调用函数中实现对变量值的修改,因为直接修改了内存地址上存储的值,而不是值拷贝。

Go 复制代码
func swap(a, b int) {
    a, b = b, a
    fmt.Println(a, b)
}
func pointerSwap(a, b *int) {
    *a, *b = *b, *a
    fmt.Println(*a, *b)
}
​
// 值拷贝
func PointerValueCopyExample() {
    a := 10
    b := 20
    fmt.Println("直接进行值拷贝")
    swap(a, b)
    fmt.Println(a, b)
    fmt.Println("通过指针进行值交换")
    pointerSwap(&a, &b)
    fmt.Println(a, b)
}

如上运行结果,可以发现通过指针进行值交换,变量的值也会发生变化,这里是因为指针的交换是直接修改内存地址上存储的值,调用完交换函数后,对应的内存空间值也进行了交换,因此外部的指针指向变量地址存储的值也发生了变化。

3 unsafe.Pointer

unsafe.Pointer 是特别定义的一种指针类型,能够包含任意类型变量的地址,以下是Go语言官方的定义:

  • 任何类型的指针都可以被转化为 unsafe.Pointer;

  • unsafe.Pointer 可以被转化位任何类型的指针;

  • unintpr 可以被转化为 unsafe.Pointer;

  • unsafe.Pointer 可以被转化为 uintptr。

因此,unsafe.Pointer 可以在不同的指针类型之间做转化,从而可以表示任意可寻址的指针类型:

Go 复制代码
i := 10
var p *int = &i
var fp *float32 = (*float32)(unsafe.Pointer(p))
*fp = *fp * 10
fmt.Println(i)

如上代码中,首先是声明了一个int类型的指针 p 指向变量 i ,然后int类型的指针转化为unsafe.Pointer再转化为float32类型的指针,最终对p指向内存地址的变量进行修改,打印出来i的地址发生了变化。

unsafe.Pointer 是一个万能指针,可以在任何指针类型之间进行转化,绕过了Go语言的类型安全机制,因此是一个不安全的操作。

unsafe.Pointer 还可以与 uintptr 类型之间相互转化,uintptr 是 Go语言内置的可以用于存储指针的整型,而整型是可以进行运算的,因此将 unsafe.Pointer 转化为 uintptr 类型后,就可以让本不具备运算能力的指针具备了指针运算能力:

Go 复制代码
arr := [3]int{1, 2, 3}
ap := &arr
// unsafe.Sizeof 数组元素偏移量
// ap由unsafe.Pointer -> uintptr -> unsafe.Pointer
sp := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(ap)) + unsafe.Sizeof(arr[0])))
*sp += 3

这里,将arr数组的内存地址赋值给指针ap,通过unsafe.Pointer转化为uintptr类型,再加上数组中第一个元素的偏移量,就可以得到该数组中第二个元素的内存地址,最后通过unsafe.Pointer将其转化为int类型指针赋值给sp,修改sp指针指向内存地址的变量值。

通过如上操作,能够绕过Go语言中指针的安全限制,实现对指针的动态偏移和计算,但这样操作,如果数组发生了越界也不会报错,而是返回下一个内存地址的值,破坏了内存的安全限制,因此这个操作也是不安全的操作,尽量避免unsafe.Pointer的相关使用,必须使用时需要非常谨慎。

相关推荐
weixin_472339463 小时前
高效处理大体积Excel文件的Java技术方案解析
java·开发语言·excel
枯萎穿心攻击4 小时前
响应式编程入门教程第二节:构建 ObservableProperty<T> — 封装 ReactiveProperty 的高级用法
开发语言·unity·c#·游戏引擎
Eiceblue5 小时前
【免费.NET方案】CSV到PDF与DataTable的快速转换
开发语言·pdf·c#·.net
tan180°6 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
m0_555762906 小时前
Matlab 频谱分析 (Spectral Analysis)
开发语言·matlab
浪裡遊7 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
优创学社27 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理7 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
lzb_kkk7 小时前
【C++】C++四种类型转换操作符详解
开发语言·c++·windows·1024程序员节