引入
Go 里函数传参是值传递,参数会被复制一份。想修改原始变量或在函数间共享数据时,就需要用到指针。指针保存的是另一个变量的内存地址,通过指针可以间接修改数据,是 Go 里非常核心的概念。
正文
定义
定义
指针是一个变量,它的值不是数据本身,而是另一个变量的内存地址。通过
&取地址可以获得变量的指针,通过*解引用可以访问或修改指针指向的数据。Go 的指针不支持指针运算,比 C 语言安全得多。
语法
// 声明指针变量
var p *int
// 取地址
x := 10
p = &x
// 解引用(访问/修改指针指向的值)
fmt.Println(*p) // 读取
*p = 20 // 修改例子
取地址和解引用
x := 42
p := &x
fmt.Println(x) // 42
fmt.Println(p) // 0xc000018000(内存地址)
fmt.Println(*p) // 42(通过指针读取值)
*p = 100
fmt.Println(x) // 100(通过指针修改了原始值)&x 拿到 x 的地址,*p 通过地址访问或修改 x 的值。
函数传指针
func double(n *int) {
*n *= 2
}
func main() {
x := 5
double(&x)
fmt.Println(x) // 10
}传指针进函数,函数内修改指针指向的值,原始变量跟着变。值传递做不到这一点。
值传递 vs 指针传递
func byValue(n int) {
n = 100 // 修改的是副本
}
func byPointer(n *int) {
*n = 100 // 修改的是原始值
}
func main() {
x := 1
byValue(x)
fmt.Println(x) // 1(没变)
byPointer(&x)
fmt.Println(x) // 100(变了)
}值传递修改副本,指针传递修改原始值。
new() 函数
p := new(int)
fmt.Println(*p) // 0(零值)
*p = 42
fmt.Println(*p) // 42new(T) 分配一个 T 类型的内存空间,返回指向它的指针。初始值是该类型的零值。
new() 创建结构体指针
type Person struct {
Name string
Age int
}
func main() {
p := new(Person)
p.Name = "Alice"
p.Age = 25
fmt.Println(p.Name, p.Age)
}new() 创建结构体时返回指针,访问字段用 . 就行,Go 自动处理了解引用。
指针的零值是 nil
var p *int
fmt.Println(p) // nil
fmt.Println(p == nil) // true指针不初始化就是 nil,解引用 nil 指针会触发 panic。
多级指针
x := 10
p1 := &x // *int
p2 := &p1 // **int
fmt.Println(**p2) // 10Go 支持多级指针,但实际开发中很少用到两级以上。
常见写法
| 写法 | 说明 |
|---|---|
p := &x | 取地址,获得指针 |
*p | 解引用,读取或修改值 |
new(int) | 分配内存,返回指针 |
var p *int | 声明指针变量(零值 nil) |
func f(p *int) | 函数接收指针参数 |
p.Name | 结构体指针访问字段(自动解引用) |
特点
- Go 指针不支持指针运算(不能
p++),比 C 安全 - 值传递是副本,指针传递可以修改原始数据
new()分配内存并返回指针,初始值为零值- 指针的零值是
nil,解引用 nil 指针会 panic - 结构体指针访问字段用
.,Go 自动解引用 - Go 没有引用传递,只有值传递,指针本身也是按值传递的
&取地址,*解引用,两个操作互为反向
理解
指针就是”变量家的门牌号”。拿到门牌号(&),就能找到变量在哪;通过门牌号进门(*),就能读取或修改里面的东西。Go 的指针设计得很克制,不能做运算,不能随便乱指,大大降低了出错的可能。理解指针是理解 Go 方法、接口和并发编程的基础。
引出
有了指针,就能给类型”绑定”行为。接下来看 Go 方法,学习怎么用值接收者和指针接收者给结构体定义方法。