Golang 中的指针保存的是变量的内存地址。判断两个指针是否相等,核心并不是比较它们所指向的数据内容,而是比较它们是否指向同一个对象。对于同类型指针,语言本身提供了直接比较能力;对于空指针、不同类型指针,或者需要绕过类型系统查看地址的场景,则需要采用不同策略。理解这些差异,有助于避免编译错误、逻辑误判以及不安全的底层操作。

普通指针的直接比较规则
普通指针可以直接使用 == 和 != 进行比较。这里的相等含义非常明确:两个指针变量中保存的地址是否相同。若两个指针指向同一个变量,即使通过不同变量名访问,比较结果也为真;若分别指向两个独立变量,即使变量值相同,指针比较结果仍为假。
直接比较还有一个前提:两个指针的类型必须一致。例如 *int 与 *int 可以比较,而 *int 与 *string 不能直接比较。Golang 的类型系统会在编译阶段阻止这种跨类型指针比较,从而避免开发者把两个语义不同的指针误认为可以互换。
指针比较并不会访问指针指向的数据。因此,即使指向的对象非常大,比较操作本身仍然只处理地址值。这也是指针比较高效且语义明确的原因之一。不过,正因为不访问对象内容,开发者不能把指针相等误当成内容相等。
以下示例展示同类型指针的直接比较。代码中先取得同一变量的两个指针,再取得另一个变量的指针,通过输出结果可以清楚看到指针相等的判断依据。
package main
import "fmt"
func main() {
a := 10
b := 10
p1 := &a
p2 := &a
p3 := &b
// 指向同一个变量,地址相同
fmt.Println(p1 == p2)
// 指向不同变量,地址不同
fmt.Println(p1 == p3)
// 指针不相等,但指向的值可能相同
fmt.Println(*p1 == *p3)
// 不同类型指针不能直接比较
// s := "10"
// ps := &s
// fmt.Println(p1 == ps)
}
从结果看,指向同一地址时返回 true,指向不同地址时返回 false。这说明指针比较关注的是引用关系,而不是数据本身。对于普通业务代码,这是最安全、最直观的判断方式。
空指针与不同类型指针的判断方式
空指针是常见且必须正确处理的场景。一个指针如果没有指向任何对象,其零值通常为 nil。两个同类型的空指针都保存零值地址,因此可以直接比较,结果为真;空指针与有效指针比较,结果为假。这类判断在初始化检查、可选对象比较以及资源释放前的状态判断中经常出现。
在空指针判断中,常见写法是将指针与 nil 比较。这种写法直观且可读性好。对于函数返回指针的场景,调用方通常需要先判断返回值是否为空,再决定是否继续访问对象字段或方法。
package main
import "fmt"
func main() {
var p1 *int
var p2 *int
// 两个同类型空指针比较
fmt.Println(p1 == p2)
fmt.Println(p1 == nil)
a := 10
p3 := &a
// 空指针与非空指针比较
fmt.Println(p1 == p3)
}
与空指针相关的判断通常不需要借助复杂工具,因为语言允许指针与 nil 进行比较,也允许同类型空指针之间比较。开发者需要注意的是,判断空指针只是确认指针当前没有指向对象,并不代表后续可以安全解引用。若指针可能为空,解引用前必须先检查。
当两个指针类型不同,但又需要判断它们是否指向同一块内存时,普通比较无法通过编译。此时可以借助 unsafe 包将指针转换为通用指针,再转换为整数形式的地址进行比较。unsafe.Pointer 可以在特定规则下连接不同指针类型,而 uintptr 则用于保存地址数值。
unsafe.Pointer 与 uintptr 的分工也不同。前者仍然是指针语义,可以参与指针转换;后者是整数语义,适合做地址值比较或记录。进行这类转换时,应尽量在同一个表达式链路中完成,避免中间状态造成理解困难。
下面的示例将 *int 转换为 *byte,再比较二者的地址值。这样可以演示不同类型指针在绕过类型系统后如何进行地址层面的相等判断。
package main
import (
"fmt"
"unsafe"
)
func main() {
a := 10
pInt := &a
pByte := (*byte)(unsafe.Pointer(pInt))
// 不同类型指针不能直接用 == 比较
// fmt.Println(pInt == pByte)
addrInt := uintptr(unsafe.Pointer(pInt))
addrByte := uintptr(unsafe.Pointer(pByte))
// 转换为地址值后比较
fmt.Println(addrInt == addrByte)
}
这种方式虽然能完成地址层面的比较,但它绕过了 Golang 的类型安全检查。将一种类型指针转换成另一种类型指针,可能导致访问宽度、对齐方式或语义不匹配。因此,只有在明确理解底层内存布局,并且确实需要跨类型判断地址时才应使用。常规业务逻辑中,优先使用类型一致指针的直接比较更稳妥。
指针相等与值相等的边界及工程建议
指针相等与值相等是两个完全不同的概念。指针相等表示两个指针变量保存同一个地址,也就是引用同一个对象;值相等表示两个指针解引用之后得到的数据相同。两个变量可以拥有相同的值,却位于不同内存地址,此时指针不相等而值相等。若混淆二者,程序逻辑很容易出现偏差。
| 比较目标 | 判断方式 | 含义 |
|---|---|---|
| 指针相等 | p1 == p2 | 两个指针是否指向同一内存地址 |
| 值相等 | *p1 == *p2 | 两个指针指向的数据是否相同 |
例如两个独立变量分别赋值为相同数字,它们的指针并不相等,因为地址不同;但解引用后比较数值,会得到相等结果。反过来,如果一个指针被复制给另一个指针,它们指向同一对象,那么指针相等,解引用后的值也相等。下面示例同时展示这两种判断。
package main
import "fmt"
func main() {
a := 10
b := 10
p1 := &a
p2 := &b
p3 := p1
// 指针比较:是否指向同一地址
fmt.Println(p1 == p2)
fmt.Println(p1 == p3)
// 值比较:解引用后判断数据是否相同
fmt.Println(*p1 == *p2)
fmt.Println(*p1 == *p3)
}
如果指针指向结构体,值相等还可能受到结构体字段类型影响。若结构体所有字段均可比较,解引用后可以直接使用相等运算符;若包含切片、映射或函数等不可比较字段,则不能直接用相等运算符比较整个结构体值。这时需要根据业务选择逐字段比较或其他判断方式。
在工程实践中,选择哪种判断方式取决于业务目标。如果需要确认两个引用是否指向同一个对象,例如判断缓存条目、共享结构或同一资源句柄,应比较指针本身;如果需要确认内容是否一致,例如比较配置、数值或状态字段,应解引用后比较值。
综合来看,Golang 中判断指针相等应优先使用同类型指针的直接比较;空指针判断保持直接、明确;跨类型地址比较只在必要且可控的场景中使用 unsafe。同时,始终区分地址相同与内容相同,才能让指针逻辑既符合语言规则,也符合业务语义。
Golangpointer_equality指针判断unsafe修改时间:2026-06-29 12:09:25