承上启下
我们在前面的文章中,首先介绍了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 := 10modifyValue(&value)fmt.Println(value) // 输出 30
}
在这个例子中,modifyValue
函数通过指针参数修改了外部变量value
的值。
指针的nil值
一个未初始化的指针有一个nil值,表示它不指向任何地址:
var pointer *int
if pointer == nil {fmt.Println("Pointer is nil")
}
指针和结构体
指针常用于结构体,可以创建结构体的指针,并通过指针访问或修改结构体的字段:
type Person struct {Name stringAge 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 mainimport ("fmt""unsafe"
)func main() {i := 42ip := &i // *intptr := unsafe.Pointer(ip) // 转换为 unsafe.Pointeruptr := uintptr(ptr) // 转换为 uintptr// 反向转换ptr = unsafe.Pointer(uptr) // 转换回 unsafe.Pointerip2 := (*int)(ptr) // 转换回 *int*ip2 = 84fmt.Println(i) // 输出 84
}
访问任意内存地址
通过 unsafe.Pointer
,你可以访问任意内存地址,这在Go的常规操作中是不被允许的,因为它绕过了Go的类型系统和内存安全检查。
ptr := unsafe.Pointer(uintptr(0x12345678))
上述代码试图访问一个特定的内存地址,这在实际的程序中是非常危险的,因为它可能导致未定义行为,包括程序崩溃。
计算结构体大小
unsafe.Sizeof
函数可以返回一个值的大小,单位是字节。这个函数通常与 unsafe.Pointer
一起使用来计算结构体的大小。
type MyStruct struct {a intb 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 mainimport ("fmt""unsafe"
)func main() {i := 42ptr := &i // *intuptr := uintptr(unsafe.Pointer(ptr)) // 转换为 uintptr// 使用 uintptr 进行指针算术newPtr := unsafe.Pointer(uptr + unsafe.Sizeof(i))// 反向转换回指针类型newIntPtr := (*int)(newPtr)*newIntPtr = 84fmt.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
用于更底层的内存操作,它们提供了更大的灵活性和能力,但同时也带来了更高的风险,因为它们不是类型安全的,并且需要程序员更加小心地使用。