--- tags: [go, golang, go基础语法, 指针] create time: 2026-06-07 15:00 --- # Go 语言指针 ## 概述 指针是存放内存地址的变量。Go 保留了这一底层能力,但做了简化——去掉了指针运算,只保留 `&`(取地址)和 `*`(解引用)。本文解释指针的工作原理、`new()` vs `make()`,以及何时该用指针。 ## 正文 ### 指针到底是什么? 简单说:**指针 = 内存地址**。它本身不存储数据,而是指向存储数据的位置。 ```go b := 42 // b 在内存某处存了值 42 ptr := &b // ptr 存的是 b 的地址(如 0x14000018000) fmt.Println(*ptr) // 通过地址读取 → 42 *ptr = 100 // 通过地址修改 → b 变成了 100 ``` | 操作符 | 含义 | 示例 | |--------|------|------| | `&T` | 取变量的地址 | `&b` → `*int` | | `*T` | 解引用指针 | `*ptr` → 读取/写入指向的值 | > [!question] ❓ 思考 > 如果 `a := &b`,那么 `*a = 100` 会影响 `b` 吗?为什么? ### 创建指针的两种方式 #### 方式一:`&` 取地址 ```go var ptr *int b := 3 ptr = &b // ptr 指向 b 的地址 fmt.Println(*ptr) // 3 ``` 未初始化的指针值为 `nil`: ```go var p *int fmt.Println(p == nil) // true ``` #### 方式二:`new()` 分配零值内存 ```go p := new(int) // 分配 int 大小的内存,归零,返回 *int *p = 42 fmt.Println(*p) // 42 ``` > [!note] 📝 new(T) 做了什么? > 1. 分配一块大小为 `sizeof(T)` 的内存 > 2. 将内存清零(零值) > 3. 返回 `*T` 指向这块内存 > > 等价于 `&T{}`,但后者更常用且可内联初始化。 ### new() vs make():最常见的混淆 这是 Go 面试必问的问题: | | `new(T)` | `make(T, ...)` | |--|----------|---------------| | 适用类型 | 所有类型 | **仅限** slice、map、chan | | 返回值 | `*T`(指针) | `T`(值本身) | | 用途 | 分配零值内存 | 分配+初始化 | ```go // new:只分配,不归零以外的初始化 p := new(int) // *int,指向 0 s := new([]int) // *[]int,指向 nil slice(不能直接用!) // make:分配 + 初始化 sl := make([]int, 3, 5) // len=3, cap=5 的切片 mp := make(map[string]int) // 可用的空 map ch := make(chan int, 10) // 缓冲为 10 的 channel ``` > [!warning] ⚠️ new([]int) 是陷阱 > `new([]int)` 返回的是 `*[]int`,里面的 slice 头是 nil。对它做 `append` 会 panic。应该用 `make`。 ### 结构体与指针 ```go type Point struct { X, Y int } p := Point{X: 1, Y: 2} ptr := &p ptr.X = 10 // Go 自动解引用,等价于 (*ptr).X = 10 fmt.Println(p.X) // 10 — 原值被修改了 ``` > [!tip] 💡 什么时候传指针? > - **需要修改原值**:函数参数传 `*T` > - **结构体较大**:避免拷贝开销(通常 > 64 字节就值得考虑) > - **slice/map/chan**:它们本身就是引用语义,不需要额外加指针 ### 常见误区 > [!warning] ⚠️ 不要对 map 元素取地址 > ```go > m := map[string]int{"a": 1} > p := &m["a"] // ❌ 编译错误:cannot take address of map element > ``` > map 的元素不是稳定的内存位置——扩容时整个数据结构会迁移。 > [!info] ℹ️ Go 没有指针运算 > Go 去掉了 C 语言的 `ptr++`、`ptr + n` 等指针算术。你只能: > - 取地址 `&v` > - 解引用 `*p` > - 比较 `p == nil` > > 这消除了缓冲区溢出等安全漏洞。