什么是指针?
指针是存储一个变量的内存地址的,声明一个指针变量的格式如下:
Go
// ptr是指针变量名,*表示是指针类型,*int表示存储的是一个int类型变量的内存地址
var ptr *int
1、关键操作符
|----|------|-------------------------------------------------|----------------------------------------------------------------|
| 符号 | 名称 | 作用 | 示例 |
| & | 取址符 | 获取变量在内存中的地址,返回指针类型 | ptr := &num (获取 num 的地址) |
| * | 解引用符 | 1. 声明时:表示指针类型(如 *int) 2. 使用时:获取或修改指针指向的具体值 | val := *ptr (读取ptr指向的int变量值) *ptr = 100 (修改ptr指向的int变量值为100) |
2、常见操作
Go
package main
import "fmt"
func main() {
// 1.定义普通变量
num := 100
fmt.Printf("普通变量值: %d, 地址: %p\n", num, &num)
// 2. 声明并初始化指针
var ptr *int = &num // ptr 存储的是 num 的内存地址
// 3. 解引用:读取和修改
fmt.Printf("指针指向的值: %d\n", *ptr) // 输出 100
*ptr = 200 // 通过指针修改原始变量的值
fmt.Printf("修改后原始变量值: %d\n", num) // 输出 200,证明原值被改变
}
3、空指针
当指针变量未初始化时,默认为空指针,nil。
go语言底层将nil视为地址为0的特殊变量。
所以,空指针 ptr == nil 为true,但是在打印ptr指向的内存地址时,打印出的是 0x0【就是0】
危险操作:对 nil 指针进行解引用(如 *ptr)会导致程序运行时崩溃(panic: invalid memory address or nil pointer dereference)。
Go
package main
import "fmt"
func main() {
var ptr *int
if ptr == nil {
fmt.Printf("%p", ptr)
}
}
4、指针的使用场景
4.1、在函数间共享数据并修改原值
Go 语言默认参数传递是值传递(拷贝副本)。如果希望在函数内部修改外部变量的值,必须传递指针。
Go
func increment(ptr *int) {
*ptr++ // 修改指针指向的原始值
}
func main() {
count := 10
increment(&count) // 传入地址
fmt.Println(count) // 输出 11
}
4.2、避免大型数据结构的拷贝开销
对于大型结构体(Struct)或数组,值传递会复制整个数据结构,消耗内存和 CPU。传递指针仅复制一个地址(64位系统占8字节),效率极高。
Go
type BigData struct {
Items int
}
// 使用指针接收,避免拷贝 10000 个 int
func process(data *BigData) {
// 处理逻辑
}
5、注意事项
1. 不可取地址的情况
你不能对以下对象使用 & 取地址,否则编译报错 cannot take the address of ...:
-
字面量/常量 :
&42、&"hello"是错误的。 -
Map 的值 :
&m["key"]是错误的。因为 Map 的值可能随时移动或不存在,Go 禁止直接取 Map 值的地址。如果需要,先将值赋给临时变量再取址。 -
函数返回值:通常情况下,不能直接对函数返回值取地址(除非返回的是指针或可寻址变量)。
2. 切片、Map 和 Channel 的特殊性
-
切片(Slice)、Map 和 Channel 在 Go 中本身就是引用类型(底层实现包含指针)。
-
无需传指针:在函数间传递切片或 Map 时,通常直接传递值即可。函数内部对元素内容的修改会反映到原变量上。
-
例外 :如果你需要在函数内改变切片的长度 或容量 (例如 append 导致底层数组重新分配),或者替换整个 Map 对象,则需要传递指向切片或 Map 的指针(如
*[]int),但这在日常开发中较少见。
3. 内存逃逸与性能
-
栈与堆:Go 编译器会自动进行"逃逸分析"。如果局部变量的地址被返回或传递给其他 goroutine,该变量会分配到堆上,增加垃圾回收(GC)压力。
-
适度使用:不要滥用指针。对于小的基本类型(int, bool, small struct),值传递往往比指针传递更快,因为指针涉及间接寻址和可能的堆分配。
4. 原子指针与 unsafe
-
sync/atomic.Pointer提供了无锁的原子指针操作,适用于高并发场景下的状态切换。 -
unsafe.Pointer允许绕过类型系统进行指针转换,极其危险,不参与 GC 生命周期检查,容易导致悬垂指针或内存破坏。除非编写底层库或驱动,否则严禁在业务代码中使用。