文章目录
- 前言
- 什么是指针?
- 指针的基本操作
- [指针 vs 值传递(重点🔥)](#指针 vs 值传递(重点🔥))
- 指针的零值(非常重要)
-
- [⚠️ 空指针错误](#⚠️ 空指针错误)
- 正确写法
- [new vs &(面试常问🔥)](#new vs &(面试常问🔥))
- 指针与结构体
- 指针常见坑(必看🔥)
-
- [for 循环变量指针坑](#for 循环变量指针坑)
- 什么时候用指针?
-
- 推荐使用场景:
-
- [✅ 需要修改原数据](#✅ 需要修改原数据)
- [✅ 结构体较大(性能优化)](#✅ 结构体较大(性能优化))
- [✅ 避免拷贝(高性能场景)](#✅ 避免拷贝(高性能场景))
- [❌ 不推荐:](#❌ 不推荐:)
- 总结(面试速记版🔥)
- 结语
前言
在很多编程语言中,"指针"是一个既强大又容易让人困惑的概念。而在 Go 语言中,指针被设计得相对简单,但依然非常重要。
👉 本文将带你了解:
- 指针的本质
- Go 中如何使用指针
- 指针与函数、结构体的关系
- 常见坑位解析(面试高频🔥)
什么是指针?
指针的本质
👉 指针:存储变量内存地址的变量
换句话说:指针是一个变量,它存储的是另一个变量在内存中的地址
text
变量 = 存数据
指针 = 存变量的地址
示例理解
go
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println("x 的值:", x)
fmt.Println("x 的地址:", &x)
fmt.Println("p 的值:", p)
}
👉 输出:
bash
x 的值: 10
x 的地址: 0xc000010120
p 的值: 0xc000010120
📌 说明:
&x:获取变量地址p:就是指针变量,保存地址
指针的基本操作
声明指针
go
var p *int
📌 含义:
- p 是一个指针
- 指向 int 类型
解引用(取值)
go
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println(*p) // 取值
}
👉 输出:
bash
10
📌 说明:
*p:访问指针指向的值
修改变量值
go
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println("x 的值:", x)
fmt.Println("x 的地址:", &x)
fmt.Println("p 的值:", p)
fmt.Println("p 内存的解值:", *p)
*p = 20
fmt.Println("x 的值:", x)
fmt.Println("x 的地址:", &x)
fmt.Println("p 的值:", p)
fmt.Println("p 内存的解值:", *p)
}
输出:
bath
x 的值: 10
x 的地址: 0xc000098040
p 的值: 0xc000098040
p 内存的解值: 10
x 的值: 20
x 的地址: 0xc000098040
p 的值: 0xc000098040
p 内存的解值: 20
📌 核心:
0xc000098040这个内存的地址没有变化
👉 通过指针可以修改原变量
指针 vs 值传递(重点🔥)
值传递
go
package main
import "fmt"
func change(x int) {
fmt.Println("x1 = ", x)
fmt.Println("&x1 = ", &x)
x = 100
fmt.Println("x2 = ", x)
fmt.Println("&x2 = ", &x)
}
func main() {
a := 10
fmt.Println("a1 = ", a)
fmt.Println("&a1 = ", &a)
change(a)
fmt.Println("a2 = ", a)
fmt.Println("&a2 = ", &a)
}
输出:
bath
a1 = 10
&a1 = 0xc000010120
x1 = 10
&x1 = 0xc000010140
x2 = 100
&x2 = 0xc000010140
a2 = 10
&a2 = 0xc000010120
📌 原因:
👉 传的是"副本"
指针传递
go
package main
import "fmt"
func change(p *int) {
fmt.Println("p1 = ", *p)
fmt.Println("&p1 = ", p)
*p = 100
fmt.Println("p2 = ", *p)
fmt.Println("&p2 = ", p)
}
func main() {
a := 10
fmt.Println("a1 = ", a)
fmt.Println("&a1 = ", &a)
change(&a)
fmt.Println("a2 = ", a)
fmt.Println("&a2 = ", &a)
}
输出:
bath
a1 = 10
&a1 = 0xc000010120
p1 = 10
&p1 = 0xc000010120
p2 = 100
&p2 = 0xc000010120
a2 = 100
&a2 = 0xc000010120
📌 原因:
👉 传的是"地址",直接修改原值
指针的零值(非常重要)
go
package main
import "fmt"
func main() {
var p *int
fmt.Println(p)
}
输出:
bath
<nil>
📌 结论:
- 指针默认值是
nil nil指针不能解引用
⚠️ 空指针错误
go
var p *int
*p = 10 // panic!
👉 报错:
bash
panic: runtime error: invalid memory address or nil pointer dereference
翻译:panic:运行时错误:无效内存地址或空指针解引用
正确写法
go
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println(*p) // 输出 10
fmt.Println(p) // 输出 0xc000110040
}
或:
go
package main
import "fmt"
func main() {
p := new(int)
*p = 10
fmt.Println(*p) // 输出 10
fmt.Println(p) // 输出 0xc000010120
}
new vs &(面试常问🔥)
new
go
package main
import "fmt"
func main() {
p := new(int)
fmt.Println(*p) // 输出 0
fmt.Println(p) // 输出 0xc000098040
}
📌 特点:
- 分配内存
- 返回指针
- 默认值为 0
&
go
package main
import "fmt"
func main() {
x := 10
p := &x
fmt.Println("*p:", *p) // 输出 10
fmt.Println("p:", p) // 输出地址
fmt.Println("x:", x) // 输出10
fmt.Println("&x", &x) // 输出地址
}
输出:
bath
*p: 10
p: 0xc000098040
x: 10
&x 0xc000098040
📌 特点:
- 获取已有变量地址
对比总结
| 方式 | 用途 |
|---|---|
| new | 创建变量并返回指针 |
| & | 获取已有变量地址 |
指针与结构体
普通结构体
go
package main
import "fmt"
type User struct {
Name string
}
func main() {
u := User{Name: "Tom"}
fmt.Println(u)
fmt.Println(u.Name)
}
输出:
bath
{Tom}
Tom
指针结构体
go
package main
import "fmt"
type User struct {
Name string
}
func main() {
u := &User{Name: "Tom"}
fmt.Println("u:", u)
fmt.Println("*u:", *u)
fmt.Println("u.Name:", u.Name)
fmt.Println("(*u).Name:", (*u).Name)
}
输出:
bath
u: &{Tom}
*u: {Tom}
u.Name: Tom
(*u).Name: Tom
📌 Go 特性:
👉 自动解引用(语法糖)
修改结构体
go
package main
import "fmt"
type User struct {
Name string
}
func update(u *User) {
fmt.Println("u.name 3:", u.Name) // Tom
fmt.Println("&u 3:", &u)
u.Name = "Jerry"
fmt.Println("u.name 4:", u.Name) // Jerry
fmt.Println("&u 4:", &u)
}
func main() {
u := User{Name: "Tom"}
fmt.Println("u.name 1:", u.Name) // Tom
fmt.Println("&u 1:", &u)
update(&u)
fmt.Println("&u 2:", &u)
fmt.Println("u.name 2:", u.Name) // Jerry
}
输出:
bath
u.name 1: Tom
&u 1: &{Tom}
u.name 3: Tom
&u 3: 0xc000102000
u.name 4: Jerry
&u 4: 0xc000102000
&u 2: &{Jerry}
u.name 2: Jerry
指针常见坑(必看🔥)
for 循环变量指针坑
go
package main
import "fmt"
func main() {
arr := []int{1, 2, 3}
var ptrs []*int
for _, v := range arr {
ptrs = append(ptrs, &v)
}
for _, p := range ptrs {
fmt.Println(*p)
}
}
👉 输出:
bash
1
2
3
| 场景 | Go 1.21 以前 | Go 1.22+ |
|---|---|---|
| for range 取地址 | ❌ 有坑(3 3 3) | ✅ 已修复 |
❗原因
👉 v 是同一个变量复用
✅ 正确写法老版本
go
for _, v := range arr {
tmp := v
ptrs = append(ptrs, &tmp)
}
什么时候用指针?
推荐使用场景:
✅ 需要修改原数据
go
func update(p *int)
✅ 结构体较大(性能优化)
go
func handle(u *User)
✅ 避免拷贝(高性能场景)
❌ 不推荐:
- 小数据类型(int、bool)
- 不需要修改的场景
总结(面试速记版🔥)
👉 指针核心三点:
&取地址*解引用- 指针可以修改原变量
👉 高频考点:
- nil 指针会 panic
- new vs &
- 值传递 vs 指针传递
- for range 指针坑
- 结构体指针自动解引用
结语
Go 的指针相比 C 语言简化了很多:
- ❌ 没有指针运算
- ❌ 没有复杂语法
- ✅ 更安全
- ✅ 更易用
但它依然是:
👉 写出高性能 Go 程序的关键能力