在Golang开发中,指针和引用是两个容易混淆的概念,很多开发者刚接触时会分不清两者的差异,也不清楚什么时候该用指针什么时候该用引用。实际上Golang中没有传统意义上的引用类型,通常所说的引用更多是指切片、map、通道这类底层包含指针的复合类型,而指针是显式存储变量内存地址的类型。

指针的基础概念
指针是存储变量内存地址的变量,通过*符号声明指针类型,通过&符号获取变量的内存地址。指针的核心作用是直接操作内存中的数据,修改指针指向的内容会影响原变量。
下面是指针的基础使用示例:
package main
import "fmt"
func main() {
var num int = 10
// 声明指针变量,存储num的内存地址
var ptr *int = &num
fmt.Println("num的原始值:", num)
fmt.Println("ptr存储的地址:", ptr)
// 通过指针修改指向的值
*ptr = 20
fmt.Println("修改后num的值:", num)
}
Golang中“引用”的真实含义
Golang官方并没有定义“引用类型”这个概念,通常大家说的引用指的是切片、map、通道、函数这类复合类型。这些类型的变量本身不存储全部数据,底层包含指向实际数据的指针,赋值或传参时只是复制了底层的指针部分,所以修改内容会影响原变量。
以切片为例,它的底层结构包含指向底层数组的指针、长度和容量,赋值操作只是复制了这个结构,不会复制底层数组:
package main
import "fmt"
func main() {
// 创建切片
slice1 := []int{1, 2, 3}
// 赋值操作,复制切片的结构
slice2 := slice1
fmt.Println("修改前slice1:", slice1)
// 修改slice2的元素
slice2[0] = 100
fmt.Println("修改后slice1:", slice1)
fmt.Println("修改后slice2:", slice2)
}
指针和引用的核心区别
可以从以下几个维度区分指针和Golang中常说的引用:
- 定义方式不同:指针是显式声明的类型,需要用
*和&操作;引用类类型是直接声明的复合类型,不需要额外的符号。 - 赋值行为不同:指针赋值复制的是内存地址;引用类类型赋值复制的是底层结构(包含指针),但底层数据通常是共享的。
- 空值不同:指针的空值是
nil;引用类类型的空值也是nil,但切片、map等类型可以通过字面量初始化,指针需要显式分配内存。 - 函数传参表现不同:指针传参传递的是地址,修改指针指向的内容会影响原变量;引用类类型传参传递的是底层结构,修改内容也会影响原变量,但如果给引用类类型变量重新赋值,不会影响原变量。
下面通过函数传参的示例对比两者的差异:
package main
import "fmt"
// 指针传参函数
func modifyByPtr(ptr *int) {
*ptr = 100
// 修改指针指向的地址,不会影响原指针变量
var newNum int = 200
ptr = &newNum
}
// 切片传参函数
func modifyBySlice(slice []int) {
slice[0] = 100
// 重新给切片赋值,不会影响原切片变量
slice = []int{4, 5, 6}
}
func main() {
num := 10
ptr := &num
fmt.Println("指针传参前num:", num)
modifyByPtr(ptr)
fmt.Println("指针传参后num:", num)
slice := []int{1, 2, 3}
fmt.Println("切片传参前slice:", slice)
modifyBySlice(slice)
fmt.Println("切片传参后slice:", slice)
}
常见使用场景
实际开发中可以根据需求选择使用指针还是引用类类型:
- 如果需要传递大型结构体,避免值拷贝的开销,优先使用指针。
- 如果需要修改函数外部的变量值,使用指针传参。
- 如果需要存储一组动态变化的数据,优先使用切片这类引用类类型。
- 如果需要键值对存储,优先使用map这类引用类类型。
注意事项
使用指针时需要注意空指针问题,对nil指针解引用会导致程序崩溃,使用前需要判断指针是否为空。使用引用类类型时,需要注意多个变量共享底层数据的问题,避免意外的修改影响其他变量。如果需要对引用类类型做独立的拷贝,需要手动复制底层数据,比如切片的深拷贝需要遍历元素重新赋值,或者使用copy函数。
下面是指针判空的示例:
package main
import "fmt"
func main() {
var ptr *int = nil
// 判断指针是否为空
if ptr != nil {
fmt.Println(*ptr)
} else {
fmt.Println("指针为空,无法解引用")
}
}