# Go 指针

指针是编程语言中用于存储变量内存地址的特殊类型。Go 提供了一定程度的指针支持，既保留了直接操作内存地址的能力，又通过安全机制（如没有指针运算、nil 检查）避免了 C 语言中指针的常见陷阱。本篇将详细讲解 Go 指针的概念和使用方法。

![指针与内存](https://img.zhaojq.top/20260729161534062.png "指针与内存")

## 什么是指针

在计算机内存中，每个变量都存储在一个特定的地址上。**指针**就是存储这个地址的变量。

可以把内存想象成一排带编号的储物柜：
- 变量是柜子里存放的物品。
- 地址是柜子的编号。
- 指针就是一张写着柜子编号的纸条，通过它可以找到对应的物品。

```go
package main

import "fmt"

func main() {
    x := 42
    fmt.Println("x 的值:", x)          // 42
    fmt.Println("x 的地址:", &x)       // 0xc0000b4008（具体地址每次运行不同）
}
```

## & 取地址运算符

`&` 运算符用于获取变量的内存地址，结果是一个指针：

```go
package main

import "fmt"

func main() {
    name := "Go语言"
    age := 15
    pi := 3.14

    fmt.Printf("name 的地址: %p\n", &name)
    fmt.Printf("age 的地址:  %p\n", &age)
    fmt.Printf("pi 的地址:   %p\n", &pi)

    // 将地址保存到指针变量中
    ptr := &age
    fmt.Printf("ptr 的值（即 age 的地址）: %p\n", ptr)
    fmt.Printf("ptr 的类型: %T\n", ptr) // *int
}
```

## * 解引用运算符

`*` 运算符用于通过指针访问指针所指向的值，称为"解引用"或"间接引用"：

```go
package main

import "fmt"

func main() {
    x := 100
    ptr := &x // ptr 指向 x

    // 通过指针读取值
    fmt.Println("ptr 指向的值:", *ptr) // 100

    // 通过指针修改值
    *ptr = 200
    fmt.Println("修改后 x 的值:", x) // 200
}
```

## 指针类型

Go 中指针的类型用 `*T` 表示，其中 `T` 是指针指向的类型：

```go
package main

import "fmt"

func main() {
    var a int = 10
    var b float64 = 3.14
    var c string = "Hello"

    var pa *int = &a
    var pb *float64 = &b
    var pc *string = &c

    fmt.Printf("pa 类型: %T, 值: %v\n", pa, *pa) // *int, 10
    fmt.Printf("pb 类型: %T, 值: %v\n", pb, *pb) // *float64, 3.14
    fmt.Printf("pc 类型: %T, 值: %v\n", pc, *pc) // *string, Hello
}
```

> **注意**：不同类型的指针之间不能互相赋值，`*int` 和 `*float64` 是完全不同的类型。

## 值传递 vs 引用传递

### 值传递（默认行为）

Go 中函数参数默认是**值传递**，即传递的是值的副本，修改参数不会影响原始变量：

```go
package main

import "fmt"

func increment(n int) {
    n++ // 修改的是副本
    fmt.Println("函数内 n:", n)
}

func main() {
    x := 10
    increment(x)
    fmt.Println("main 中 x:", x) // 仍然是 10
}
```

### 使用指针实现"引用传递"

通过传递指针，函数可以修改原始变量：

```go
package main

import "fmt"

func increment(n *int) {
    *n++ // 修改指针指向的值
    fmt.Println("函数内 *n:", *n)
}

func main() {
    x := 10
    increment(&x)          // 传递 x 的地址
    fmt.Println("main 中 x:", x) // 11
}
```

### 对比示例

```go
package main

import "fmt"

// 值传递：交换失败
func swapByValue(a, b int) {
    a, b = b, a
    fmt.Printf("swapByValue 内: a=%d, b=%d\n", a, b)
}

// 指针传递：交换成功
func swapByPointer(a, b *int) {
    *a, *b = *b, *a
    fmt.Printf("swapByPointer 内: *a=%d, *b=%d\n", *a, *b)
}

func main() {
    x, y := 1, 2

    swapByValue(x, y)
    fmt.Printf("swapByValue 后: x=%d, y=%d\n", x, y) // 1, 2（未变）

    swapByPointer(&x, &y)
    fmt.Printf("swapByPointer 后: x=%d, y=%d\n", x, y) // 2, 1（已交换）
}
```

## 指针作为函数参数

指针参数在实际开发中非常常见，主要用于以下场景：

### 避免大结构体的拷贝

```go
package main

import "fmt"

type User struct {
    Name  string
    Age   int
    Email string
}

// 使用指针避免拷贝整个结构体
func printUser(u *User) {
    fmt.Printf("姓名: %s, 年龄: %d, 邮箱: %s\n", u.Name, u.Age, u.Email)
}

func main() {
    user := User{
        Name:  "张三",
        Age:   25,
        Email: "zhangsan@example.com",
    }
    printUser(&user)
}
```

### 修改函数外部的数据

```go
package main

import "fmt"

func setName(u *User, name string) {
    u.Name = name // 等价于 (*u).Name = name
}

func main() {
    user := User{Name: "张三", Age: 25}
    setName(&user, "李四")
    fmt.Println("修改后的名字:", user.Name) // 李四
}
```

## new 函数

`new(T)` 分配一个 `T` 类型的零值内存，并返回指向它的指针：

```go
package main

import "fmt"

func main() {
    // new(int) 分配一个 int 的内存，初始化为 0，返回 *int
    p := new(int)
    fmt.Printf("类型: %T, 值: %d\n", p, *p) // *int, 0

    *p = 42
    fmt.Println("赋值后:", *p) // 42

    // 等价于：
    // var temp int  // 零值 0
    // p := &temp
}
```

> **提示**：从 Go 1.18 开始，可以使用泛型辅助函数 `func Ptr[T any](v T) *T { return &v }` 来创建任意类型的指针。

## nil 指针

零值指针的值为 `nil`，对 nil 指针解引用会导致程序崩溃（panic）：

```go
package main

import "fmt"

func main() {
    var p *int // 零值为 nil
    fmt.Println("p == nil:", p == nil) // true

    // fmt.Println(*p) // panic: runtime error: invalid memory address

    // 正确做法：解引用前先检查
    if p != nil {
        fmt.Println(*p)
    } else {
        fmt.Println("指针为空，无法解引用")
    }
}
```

## 指针的常见使用场景

### 场景一：结构体方法接收者

当方法需要修改结构体的字段时，使用指针接收者：

```go
package main

import "fmt"

type Counter struct {
    count int
}

// 值接收者：不能修改原始结构体
func (c Counter) GetCount() int {
    return c.count
}

// 指针接收者：可以修改原始结构体
func (c *Counter) Increment() {
    c.count++
}

func (c *Counter) Reset() {
    c.count = 0
}

func main() {
    counter := Counter{}

    counter.Increment()
    counter.Increment()
    counter.Increment()

    fmt.Println("计数:", counter.GetCount()) // 3

    counter.Reset()
    fmt.Println("重置后:", counter.GetCount()) // 0
}
```

### 场景二：链表节点

```go
package main

import "fmt"

type Node struct {
    Value int
    Next  *Node
}

func main() {
    // 创建链表: 1 -> 2 -> 3
    head := &Node{Value: 1}
    head.Next = &Node{Value: 2}
    head.Next.Next = &Node{Value: 3}

    // 遍历链表
    current := head
    for current != nil {
        fmt.Printf("%d -> ", current.Value)
        current = current.Next
    }
    fmt.Println("nil")
}
```

### 场景三：可选字段

在结构体中，指针类型字段可以表示"有值"或"无值"（nil）：

```go
package main

import "fmt"

type Profile struct {
    Name    string
    Age     int
    Website *string // 可选字段，nil 表示未填写
}

func main() {
    // 没有网站
    p1 := Profile{Name: "张三", Age: 25}
    printProfile(p1)

    // 有网站
    site := "https://example.com"
    p2 := Profile{Name: "李四", Age: 30, Website: &site}
    printProfile(p2)
}

func printProfile(p Profile) {
    fmt.Printf("姓名: %s, 年龄: %d", p.Name, p.Age)
    if p.Website != nil {
        fmt.Printf(", 网站: %s", *p.Website)
    }
    fmt.Println()
}
```

## 总结

本篇详细讲解了 Go 指针的核心概念和使用方法。指针存储变量的内存地址，`&` 取地址、`*` 解引用是两个基本操作。Go 的值传递默认传递副本，通过指针可以实现"引用传递"效果。`new` 函数用于分配零值内存，nil 指针需要在解引用前检查。在实际开发中，指针常用于：避免大结构体拷贝、修改函数外部数据、结构体方法接收者、链表等数据结构、以及表示可选字段。Go 的指针设计在灵活性和安全性之间取得了良好的平衡。

