一,指针基础概念
-
Go编程语言中的指针是一个变量 ,用于存储另一个变量的内存地址。
-
Golang中的指针也称为特殊变量。
-
变量被用于一些数据在系统中的一个特定存储器地址存储 。始终以十六进制格式找到内存地址(以0x开头,如0xFFAAF等)。

二,基本操作
2.1 基本声明与初始化
go
var a int = 42
var p *int = &a // p 是指向 a 的指针
&
操作符:取变量地址(&a
返回a
的内存地址)。*
操作符:声明指针类型(*int
)或解引用指针(*p
访问指向的值)。
2.2 指针的解引用
go
fmt.Println(*p) // 输出 42(通过指针访问值)
*p = 100 // 通过指针修改值
fmt.Println(a) // 输出 100(原变量被修改)
- 解引用指针(
*p
)直接操作其指向的内存数据。
2.3 指针的零值
go
var p *int
fmt.Println(p) // 输出 <nil>(指针零值)
- 未初始化的指针值为
nil
,解引用nil
指针会导致运行时panic
。
2.4 new 函数动态分配内存
go
ptr := new(int) // 分配 int 类型零值内存,返回指针
*ptr = 30 // 安全赋值
new(T)
分配类型T
的零值内存,并返回*T
类型指针。
三、指针与结构体
3.1 隐式解引用
go
type User struct {
Name string
Age int
}
u := User{"Alice", 25}
p := &u
fmt.Println(p.Name) // 隐式解引用:输出 "Alice"
fmt.Println((*p).Age) // 显式解引用:输出 25
- 结构体指针可直接访问字段(语法糖),无需显式写
(*p).Field
。
3.2 方法接收者
指针接收者允许方法修改原结构体:
go
func (p *Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}
四,指针与函数
4.1 修改外部变量
go
func increment(n *int) {
*n++ // 修改外部变量
}
num := 10
increment(&num)
fmt.Println(num) // 输出 11
4.2 避免大结构体复制
go
type LargeStruct struct { data [1024]byte }
func process(s *LargeStruct) { /* 避免复制开销 */ }
4.3 函数返回指针
go
//Go 函数返回指针
package main
import "fmt"
func main() {
//调用函数
n := rpf()
//显示值
fmt.Println("n的值: ", *n)
}
//定义具有整数的函数
//指针作为返回类型
func rpf() *int {
//局部变量
//函数内部使用简短运算符声明
lv := 100
// 返回lv的地址
return &lv
}
五,二级指针
5.1 基本概念
在Go中:
- 一级指针 :存储变量的内存地址(如
var p *int
) - 二级指针 :存储指针变量的内存地址(如
var pp **int
)
go
package main
import "fmt"
func main() {
a := 10
p := &a // 一级指针,指向a
pp := &p // 二级指针,指向p
fmt.Println("a:", a) // 10
fmt.Println("*p:", *p) // 10
fmt.Println("**pp:", **pp) // 10
}
内存关系:
css
pp → p → a
5.2 应用场景
5.2.1 在函数中修改指针指向
当需要修改指针本身的指向时(而不仅仅是修改指针指向的值),需要使用二级指针:
go
func modifyPointer(pp **int) {
b := 20
*pp = &b // 修改外部指针的指向
// 注意:b是局部变量,返回后会失效
// 实际应用中应使用堆分配
}
func safeModifyPointer(pp **int) {
// 更安全的做法:使用堆分配
b := new(int)
*b = 30
*pp = b
}
func main() {
a := 10
p := &a
// 错误示例:无法修改指针指向
// unsafeModifyPointer(&p)
// fmt.Println(*p) // 结果不确定,可能崩溃
safeModifyPointer(&p)
fmt.Println(*p) // 30
}
5.2.2 动态数据结构操作
在链表、树等数据结构中,二级指针可以简化头节点/根节点的操作:
go
type Node struct {
Value int
Next *Node
}
// 使用二级指针在链表头部插入节点
func insertAtBeginning(pp **Node, value int) {
newNode := &Node{Value: value}
newNode.Next = *pp
*pp = newNode
}
func main() {
var head *Node // 空链表
insertAtBeginning(&head, 3)
insertAtBeginning(&head, 2)
insertAtBeginning(&head, 1)
// 链表:1 -> 2 -> 3
}
5.2.3 接口指针的修改
当需要修改接口变量时(接口本身包含类型和值指针),可以使用指向接口的指针:
go
type Writer interface {
Write([]byte) (int, error)
}
func replaceWriter(wp *Writer) {
*wp = new(bytes.Buffer)
}
func main() {
var w Writer
replaceWriter(&w)
// w现在指向bytes.Buffer
}
5.3 使用注意事项
-
避免返回局部变量地址:
gofunc badExample() **int { var p *int pp := &p return pp // 安全,但p是nil }
-
nil指针处理:
govar pp **int // **pp // 会导致panic if pp != nil && *pp != nil { fmt.Println(**pp) }
-
在反射中的使用:
gofunc setValue(ptr interface{}) { v := reflect.ValueOf(ptr).Elem() v.SetInt(42) } func main() { var x int setValue(&x) // 实际上使用了"指针的指针"概念 fmt.Println(x) // 42 }
六,指针与数组
在Go中,数组是值类型,当数组作为参数传递时会发生拷贝。使用指针可以避免这种拷贝。
go
package main
import "fmt"
func main() {
// 定义数组
arr := [3]int{1, 2, 3}
// 获取数组指针
pArr := &arr
// 通过指针访问数组元素
fmt.Println((*pArr)[0]) // 1
fmt.Println(pArr[0]) // 1 (Go语法糖,等价于上一行)
// 通过指针修改数组元素
pArr[1] = 20
fmt.Println(arr) // [1 20 3]
// 数组指针作为函数参数
modifyArray(pArr)
fmt.Println(arr) // [100 20 3]
}
func modifyArray(p *[3]int) {
(*p)[0] = 100
// 或者 p[0] = 100 (语法糖)
}
注意事项
- 数组指针的类型是
*[n]T
,其中n是数组长度 - Go提供了语法糖,可以直接用指针变量名访问元素,无需显式解引用
- 数组长度是指针类型的一部分,
*[3]int
和*[4]int
是不同的类型
七,指针的比较
7.1 基本指针比较
相等性比较
在Go中,可以使用 ==
和 !=
运算符来比较两个指针是否指向同一个内存地址:
go
package main
import "fmt"
func main() {
a := 10
b := 10
p1 := &a
p2 := &a
p3 := &b
fmt.Println(p1 == p2) // true,指向同一个变量
fmt.Println(p1 == p3) // false,指向不同变量
fmt.Println(p1 != p3) // true
}
nil比较
指针可以与nil比较,检查是否为空指针:
go
var p *int
fmt.Println(p == nil) // true
p = new(int)
fmt.Println(p == nil) // false
7.2 不同类型指针的比较
Go是强类型语言,不同类型的指针不能直接比较:
go
var x int = 10
var y float64 = 10.0
px := &x
py := &y
// fmt.Println(px == py) // 编译错误:类型不匹配
特殊情况:unsafe.Pointer
使用unsafe.Pointer
可以绕过类型系统进行比较:
go
import "unsafe"
pxUnsafe := unsafe.Pointer(px)
pyUnsafe := unsafe.Pointer(py)
fmt.Println(pxUnsafe == pyUnsafe) // 可以比较,但通常为false
7.3 切片、映射和函数的指针比较
这些引用类型的比较有特殊规则:
7.3.1 切片比较
切片只能与nil比较,不能直接比较两个切片:
go
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
fmt.Println(s1 == nil) // false
// fmt.Println(s1 == s2) // 编译错误:切片只能与nil比较
7.3.2 映射比较
映射同样只能与nil比较:
go
m1 := map[string]int{"a": 1}
m2 := map[string]int{"a": 1}
fmt.Println(m1 == nil) // false
// fmt.Println(m1 == m2) // 编译错误:映射只能与nil比较
7.3.3 函数比较
函数也只能与nil比较:
go
f1 := func() {}
f2 := func() {}
fmt.Println(f1 == nil) // false
// fmt.Println(f1 == f2) // 编译错误:函数只能与nil比较
7.4 结构体指针的比较
结构体指针的比较遵循基本指针比较规则:
go
type Person struct {
Name string
Age int
}
p1 := &Person{"Alice", 30}
p2 := p1
p3 := &Person{"Alice", 30}
fmt.Println(p1 == p2) // true,相同指针
fmt.Println(p1 == p3) // false,不同指针(即使内容相同)
八,其他
指针的限制
- 不支持指针算术 (如
ptr++
): Go 禁止直接操作内存地址,避免越界错误。 - 内存安全: 垃圾回收机制自动管理内存,避免野指针。
指针 vs 值
场景 | 值传递 | 指针传递 |
---|---|---|
修改变量 | 无法修改原值 | 可修改原值 |
大对象性能 | 复制开销大 | 避免复制(仅传地址) |
并发安全 | 天然安全(副本隔离) | 需同步机制(如互斥锁) |
零值可用性 | 直接使用 | 需检查 nil |
使用事项
- 必要场景使用指针 :
- 需要修改接收者状态时(方法接收者)
- 传递大结构体/数组
- 实现接口时动态修改对象
- 避免过度使用 :
- 小对象(如基本类型)直接传值更高效
- 减少指针链(如
**int
)以保持代码清晰
- 始终检查
nil
指针: 避免运行时 panic。