vault backup: 2026-06-07 12:14:39

This commit is contained in:
2026-06-07 12:14:39 +08:00
parent 1a72bc4d82
commit 7ce9b83218
61 changed files with 8409 additions and 14429 deletions
+102 -62
View File
@@ -1,83 +1,123 @@
---
tags:
- Go
- golang
- go基础语法
- 指针
tags: [go, golang, go基础语法, 指针]
create time: 2026-06-07 15:00
---
# Go语言指针
# Go 语言指针
## 指针是什么
## 概述
像c语言一样,Go语言也有指针的概念。简单理解,指针就是地址,指针变量就是存放地址的变量。在一个变量前加上*,那么这个变量就是指针变量,指针变量只能存放地址。
1个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在32和64位机器上分别占用4或8个字节,占用字节的大小与所指向的值的大小无关。
指针是存放内存地址的变量。Go 保留了这一底层能力,但做了简化——去掉了指针运算,只保留 `&`(取地址)和 `*`(解引用)。本文解释指针的工作原理、`new()` vs `make()`,以及何时该用指针。
## 创建指针
## 正文
### 指针到底是什么?
简单说:**指针 = 内存地址**。它本身不存储数据,而是指向存储数据的位置。
### 声明指针
我们可以像声明其它类型的变量一样,声明一个指针变量
```go
var a *int
b := 42 // b 在内存某处存了值 42
ptr := &b // ptr 存的是 b 的地址(如 0x14000018000)
fmt.Println(*ptr) // 通过地址读取 → 42
*ptr = 100 // 通过地址修改 → b 变成了 100
```
上述方法声明了一个整形指针变量a,但是该变量还没有存放任何地址,操作系统并未为其指向的内容分配内存,即该指针没有指向任何内容,所以它是一个空指针。
| 操作符 | 含义 | 示例 |
|--------|------|------|
| `&T` | 取变量的地址 | `&b` → `*int` |
| `*T` | 解引用指针 | `*ptr` → 读取/写入指向的值 |
> [!question] ❓ 思考
> 如果 `a := &b`,那么 `*a = 100` 会影响 `b` 吗?为什么?
### 创建指针的两种方式
#### 方式一:`&` 取地址
```go
var b int = 3
a = &b
var ptr *int
b := 3
ptr = &b // ptr 指向 b 的地址
fmt.Println(*ptr) // 3
```
此时a不再是一个空指针,a指向一个整形变量,该变量的值是3。
要获取指针变量所指向的内容,很简单,在变量前加*即可
未初始化的指针值为 `nil`:
```go
package main
var p *int
fmt.Println(p == nil) // true
```
import "fmt"
#### 方式二:`new()` 分配零值内存
func main() {
var a *int
fmt.Printf("赋值前a: %v\n", a)
var b int = 3
a = &b // a存放的是b的地址
fmt.Printf("赋值后a: %v\n", a) // 打印出b的地址
fmt.Printf("a指向的内容是: %v\n", *a) // *a表示a存放的地址对应的内存存放的内容,这里为整数3
```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
}
```
运行结果:
```
赋值前a: <nil>
赋值后a: 0x14000018000
a指向的内容是: 3
```
### new()函数
Go语言还提供了另外一种方法来创建指针变量,使用`new`函数创建
```go
new(type)
p := Point{X: 1, Y: 2}
ptr := &p
ptr.X = 10 // Go 自动解引用,等价于 (*ptr).X = 10
fmt.Println(p.X) // 10 — 原值被修改了
```
代码展示:
```go
package main
import "fmt"
func main() {
s := new(string)
fmt.Printf("赋值前s: %v\n", s)
fmt.Printf("赋值前s的内容: %v\n", *s)
*s = "Golang学习之路"
fmt.Printf("赋值h后s: %v\n", s)
fmt.Printf("赋值后s的内容: %v\n", *s)
}
```
运行结果:
```
赋值前s: <nil>
赋值前s的内容:
赋值后s: 0x14000018000
赋值后s的内容: Golang学习之路
```
可以看到,通过new函数创建指针的时候,系统会为其分配内存,所以两次都能打印出地址,但是,在没有对其赋值的时候,系统会存放默认值,string类型的就是空字符串""。
> [!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`
>
> 这消除了缓冲区溢出等安全漏洞。