【Go系列】Go的指针

承上启下

        我们在前面的文章中,首先介绍了GO的基础语法,然后介绍了Goroutine和channel这个最具有特色的东西,同时介绍了Sync和context,以及在上篇文章中详细距离说明了Go里面用于高并发的多种写法。基础的使用方法也告一段落了,我们要进入新的篇章,就是Go的指针,这边的指针类型不仅是一个地址,还有Unsafe.Point,还有intptr,让我们详细看看。

开始学习

普通指针

在Go语言中,指针是一种特殊类型的变量,它存储了另一个变量的内存地址。指针在Go中虽然不像C或C++那样普遍使用,但它们在某些情况下仍然非常有用,尤其是在需要修改函数内部变量的值、避免大对象复制、实现数据结构(如链表、树等)时。

以下是关于Go语言中指针的详细介绍:

指针的基本概念

  • 内存地址:每个变量在内存中都有一个地址,指针变量存储的就是这个地址。
  • 解引用:通过指针访问它所指向的变量的值称为解引用。

声明指针

在Go中,指针的声明方式是在变量类型前加上*

var pointer *int

这里,pointer是一个指向int类型变量的指针。

初始化指针

指针必须在使用前进行初始化。你可以使用&操作符来获取一个变量的地址,并将其赋值给指针:

value := 10
pointer := &value

在这个例子中,pointer存储了变量value的内存地址。

解引用指针

使用*操作符可以解引用指针,访问或修改它所指向的值:

*pointer = 20

这将把变量value的值修改为20。

函数中的指针

在函数中传递指针允许你修改函数外部的变量:

func modifyValue(ptr *int) {
    *ptr = 30
}

func main() {
    value := 10
    modifyValue(&value)
    fmt.Println(value) // 输出 30
}

在这个例子中,modifyValue函数通过指针参数修改了外部变量value的值。

指针的nil值

一个未初始化的指针有一个nil值,表示它不指向任何地址:

var pointer *int
if pointer == nil {
    fmt.Println("Pointer is nil")
}

指针和结构体

指针常用于结构体,可以创建结构体的指针,并通过指针访问或修改结构体的字段:

type Person struct {
    Name string
    Age  int
}

func main() {
    person := &Person{Name: "Alice", Age: 30}
    person.Age = 31 // 通过指针修改结构体的字段
}

指针数组与数组指针

  • 指针数组:一个数组,其元素是指针。

    var ptrArray [3]*int
    
  • 数组指针:一个指向数组的指针。

    var array [3]int
    var ptrToArray *[3]int = &array
    

指针的指针

虽然不常见,但你可以在Go中创建指向指针的指针:

var value int = 100
var ptr *int = &value
var ptrToPtr **int = &ptr

这里,ptrToPtr是一个指向ptr指针的指针。

注意事项

  • Go不支持指针算术,即你不能对指针进行加减操作。
  • Go的垃圾回收机制会自动管理内存,因此通常不需要手动释放指针指向的内存。

Unsafe.Point

在Go语言中,unsafe.Pointer 是一个特殊类型的指针,它可以指向任意类型的值。unsafe 包提供了一些绕过Go类型系统的功能,允许程序进行一些原本不被允许的操作,比如在不同指针类型之间进行转换,或者计算一个对象的实际内存大小等。

以下是关于 unsafe.Pointer 的一些关键点:

类型转换

unsafe.Pointer 可以用于在任意指针类型之间进行转换。例如,如果你有一个 *int 类型的指针,你可以将其转换为 unsafe.Pointer,然后再转换回其他类型的指针。

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    i := 42
    ip := &i         // *int
    ptr := unsafe.Pointer(ip) // 转换为 unsafe.Pointer
    uptr := uintptr(ptr)     // 转换为 uintptr

    // 反向转换
    ptr = unsafe.Pointer(uptr) // 转换回 unsafe.Pointer
    ip2 := (*int)(ptr)        // 转换回 *int
    *ip2 = 84

    fmt.Println(i) // 输出 84
}

访问任意内存地址

通过 unsafe.Pointer,你可以访问任意内存地址,这在Go的常规操作中是不被允许的,因为它绕过了Go的类型系统和内存安全检查。

ptr := unsafe.Pointer(uintptr(0x12345678))

上述代码试图访问一个特定的内存地址,这在实际的程序中是非常危险的,因为它可能导致未定义行为,包括程序崩溃。

计算结构体大小

unsafe.Sizeof 函数可以返回一个值的大小,单位是字节。这个函数通常与 unsafe.Pointer 一起使用来计算结构体的大小。

type MyStruct struct {
    a int
    b string
}

s := MyStruct{a: 1, b: "hello"}
size := unsafe.Sizeof(s)
fmt.Println(size) // 输出结构体 MyStruct 的大小

使用 uintptr 进行指针算术

虽然Go不支持 unsafe.Pointer 的算术操作,但你可以将 unsafe.Pointer 转换为 uintptr,然后在 uintptr 上执行算术操作,最后再转换回 unsafe.Pointer

ptr := unsafe.Pointer(&s)
uptr := uintptr(ptr)
newPtr := unsafe.Pointer(uptr + unsafe.Offsetof(s.b)) // 访问结构体中的 b 字段

注意事项

  • 使用 unsafe 包绕过Go的类型系统和内存安全机制,需要非常小心,因为错误的使用可能会导致程序崩溃或者安全漏洞。
  • unsafe.Pointer 的使用应该限制在必要的范围内,并且要确保操作的安全性。
  • unsafe 包的内容可能会在不同的Go版本之间发生变化,因此在使用时应保持谨慎。

由于 unsafe 包的功能非常强大,Go官方建议开发者只有在没有其他选择的情况下才使用它,并且要确保代码的稳定性和安全性。

UintPtr

在Go语言中,intptr 并不是一个内置的类型。你可能在提到 uintptr 时出现了误解,或者是在引用其他语言中的类型。在Go语言中,与指针操作相关的类型是 uintptr

uintptr 类型

uintptr 是 unsafe 包中的一个类型,它足够大,可以存储任何类型的指针的位模式(即内存地址)。uintptr 类型主要用于低级编程,比如与操作系统接口、内存操作等。

以下是一些关于 uintptr 的关键点:

  • uintptr 是一个无符号整数类型,其大小足以容纳任何指针的位模式。
  • 它可以用于将指针转换为整数,反之亦然。
  • uintptr 可以用于执行指针算术,但这样做需要非常小心,因为它可能会绕过Go的内存安全保证。

示例

以下是如何使用 uintptr 的示例:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    i := 42
    ptr := &i         // *int
    uptr := uintptr(unsafe.Pointer(ptr)) // 转换为 uintptr

    // 使用 uintptr 进行指针算术
    newPtr := unsafe.Pointer(uptr + unsafe.Sizeof(i))

    // 反向转换回指针类型
    newIntPtr := (*int)(newPtr)
    *newIntPtr = 84

    fmt.Println(i) // 输出 84
}

在这个例子中,我们首先将一个 *int 类型的指针转换为 uintptr,然后执行了指针算术操作(虽然在这个特定的例子中这样做没有意义,因为它只是在一个整数大小的范围内移动),最后将结果转换回 *int 类型的指针。

注意事项

  • 使用 uintptr 需要非常小心,因为不正确的使用可能会导致内存安全问题,比如访问未分配的内存、越界访问等。
  • uintptr 类型的值不应该被存储或以任何方式保留,因为它们可能会在垃圾回收期间变得无效。
  • 通常情况下,Go程序员不需要直接使用 uintptr,除非他们正在编写需要直接与操作系统或硬件交互的底层代码。

三种指针类型对比

在Go语言中,指针、unsafe.Pointer 和 uintptr 是三种不同的概念,它们在内存操作和类型转换中扮演着不同的角色。下面是它们的区别:

指针(如 *int

  • 定义:指针是一种变量,它存储了另一个变量的内存地址。
  • 用途:用于引用和修改变量,或者在函数调用中传递变量的地址以修改其值。
  • 类型安全:指针是类型安全的,它们只能指向特定类型的变量。
  • 示例
    var a int = 42
    var ptr *int = &a
    *ptr = 100 // 修改a的值
    

unsafe.Pointer

  • 定义unsafe.Pointer 是 unsafe 包中的一个特殊类型,它可以指向任意类型的变量。
  • 用途:用于在不同指针类型之间进行转换,或者在需要时进行底层的内存操作。
  • 类型安全unsafe.Pointer 本身不是类型安全的,因为它可以指向任何类型的变量,但它需要与其他类型安全的指针一起使用。
  • 示例
    var a int = 42
    var ptr unsafe.Pointer = unsafe.Pointer(&a)
    

uintptr

  • 定义uintptr 是 unsafe 包中的一个无符号整数类型,其大小足以存储任何类型的指针的位模式。
  • 用途:用于执行指针算术操作,或者将指针转换为整数以便进行低级内存操作。
  • 类型安全uintptr 不是类型安全的,因为它可以存储任何指针的位模式,并且可以用于执行指针算术,这可能会绕过Go的内存安全保证。
  • 示例
    var a int = 42
    var ptr uintptr = uintptr(unsafe.Pointer(&a))
    

区别

  • 类型安全

    • 指针是类型安全的,只能用于指向特定类型的变量。
    • unsafe.Pointer 不是类型安全的,可以指向任何类型的变量,但它需要与其他类型安全的指针一起使用。
    • uintptr 也不是类型安全的,它可以存储任何指针的位模式,并且可以用于执行指针算术。
  • 用途

    • 指针主要用于变量引用和修改变量的值。
    • unsafe.Pointer 用于在不同指针类型之间进行转换,或者在需要时进行底层的内存操作。
    • uintptr 用于执行指针算术操作,或者将指针转换为整数以便进行低级内存操作。
  • 内存安全

    • 使用指针时,Go的垃圾回收器会确保指向的变量在需要时不会被回收。
    • 使用 unsafe.Pointer 和 uintptr 时,程序员需要确保操作不会导致内存安全问题,比如越界访问或访问未分配的内存。

总结来说,指针是Go中用于日常变量引用和修改变量的类型安全工具,而 unsafe.Pointer 和 uintptr 用于更底层的内存操作,它们提供了更大的灵活性和能力,但同时也带来了更高的风险,因为它们不是类型安全的,并且需要程序员更加小心地使用。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Go语言中,指针变量和指针地址的概念与其他编程语言类似。指针变量是存储内存地址的变量,而指针地址是指针变量所指向的内存地址。 以下是关于Go语言指针变量和指针地址的介绍和演示: 1. 声明指针变量和指针地址: ```go var var_name *var-type ``` 其中,`var_name`是指针变量名,`*var-type`是指针类型,`*`号用于指定变量是作为一个指针。 2. 指针变量的使用: ```go package main import "fmt" func main() { var a int = 10 var ptr *int // 声明指针变量 ptr = &a // 将变量a的地址赋值给指针变量ptr fmt.Printf("a的值:%d\n", a) // 输出:10 fmt.Printf("a的地址:%d\n", &a) // 输出:变量a的地址 fmt.Printf("ptr的值:%d\n", *ptr) // 输出:指针变量ptr所指向的值 fmt.Printf("ptr的地址:%d\n", &ptr) // 输出:指针变量ptr的地址 } ``` 运行结果为: ``` a的值:10 a的地址:变量a的地址 ptr的值:10 ptr的地址:指针变量ptr的地址 ``` 3. 指针数组和指向指针指针: ```go package main import "fmt" func main() { var ptr [3]*int // 声明指针数组 a := [...]int{10, 100, 200} // 实际数组 for i := 0; i < len(a); i++ { ptr[i] = &a[i] // 地址赋值给指针数组 fmt.Printf("第%d个元素的指针地址:%d\n", i, &a[i]) } for j := 0; j < len(a); j++ { fmt.Printf("a[%d] = %d\n", j, *ptr[j]) // 使用指针变量值指向值进行遍历 } } ``` 运行结果为: ``` 第0个元素的指针地址:变量a[0]的地址 第1个元素的指针地址:变量a[1]的地址 第2个元素的指针地址:变量a[2]的地址 a[0] = 10 a[1] = 100 a[2] = 200 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值