指针 #
一、指针概念 #
1.1 什么是指针 #
指针是一个变量,存储另一个变量的内存地址。
text
变量 a: 值 10,地址 0x1000
指针 p: 值 0x1000(指向a的地址)
1.2 为什么需要指针 #
- 函数间共享数据
- 修改函数外部变量
- 减少大数据复制的开销
二、指针基础 #
2.1 声明指针 #
使用*声明指针类型:
go
var p *int // 指向int的指针
var s *string // 指向string的指针
2.2 取地址 #
使用&获取变量地址:
go
a := 10
p := &a // p是指向a的指针
fmt.Println(a) // 10
fmt.Println(&a) // 0xc0000b2008(地址)
fmt.Println(p) // 0xc0000b2008(相同地址)
2.3 解引用 #
使用*获取指针指向的值:
go
a := 10
p := &a
fmt.Println(*p) // 10(解引用)
fmt.Println(*&a) // 10(等同于a)
2.4 通过指针修改值 #
go
a := 10
p := &a
*p = 20 // 通过指针修改a的值
fmt.Println(a) // 20
三、指针与零值 #
3.1 nil指针 #
未初始化的指针值为nil:
go
var p *int
fmt.Println(p) // <nil>
3.2 空指针检查 #
go
var p *int
if p == nil {
fmt.Println("指针为空")
}
3.3 解引用nil指针会panic #
go
var p *int
fmt.Println(*p) // panic: nil pointer dereference
四、指针与函数 #
4.1 值传递 #
go
func modifyValue(x int) {
x = 20 // 修改的是副本
}
func main() {
a := 10
modifyValue(a)
fmt.Println(a) // 10,未改变
}
4.2 指针传递 #
go
func modifyPointer(x *int) {
*x = 20 // 修改原变量
}
func main() {
a := 10
modifyPointer(&a)
fmt.Println(a) // 20,已改变
}
4.3 返回指针 #
go
func newInt() *int {
x := 10
return &x // Go允许返回局部变量的指针
}
func main() {
p := newInt()
fmt.Println(*p) // 10
}
4.4 new函数 #
go
p := new(int) // 分配内存,返回指针
fmt.Println(*p) // 0(零值)
*p = 10
fmt.Println(*p) // 10
五、指针与结构体 #
5.1 结构体指针 #
go
type Person struct {
Name string
Age int
}
p := &Person{Name: "Tom", Age: 20}
fmt.Println(p.Name) // Tom(自动解引用)
fmt.Println((*p).Name) // Tom(显式解引用)
5.2 方法接收者 #
go
func (p *Person) SetAge(age int) {
p.Age = age
}
func main() {
p := &Person{Name: "Tom"}
p.SetAge(25)
fmt.Println(p.Age) // 25
}
六、指针与切片/映射 #
6.1 切片本身是引用类型 #
go
func modifySlice(s []int) {
s[0] = 100
}
func main() {
s := []int{1, 2, 3}
modifySlice(s)
fmt.Println(s) // [100 2 3]
}
6.2 映射也是引用类型 #
go
func modifyMap(m map[string]int) {
m["a"] = 100
}
func main() {
m := map[string]int{"a": 1}
modifyMap(m)
fmt.Println(m) // map[a:100]
}
6.3 何时使用指针 #
切片/映射本身是引用类型,通常不需要指针。但如果需要修改切片本身(如append),需要传递指针:
go
func appendSlice(s *[]int, v int) {
*s = append(*s, v)
}
func main() {
s := []int{1, 2, 3}
appendSlice(&s, 4)
fmt.Println(s) // [1 2 3 4]
}
七、指针数组与数组指针 #
7.1 指针数组 #
元素为指针的数组:
go
a, b, c := 1, 2, 3
arr := [3]*int{&a, &b, &c}
fmt.Println(*arr[0]) // 1
7.2 数组指针 #
指向数组的指针:
go
arr := [3]int{1, 2, 3}
p := &arr
fmt.Println((*p)[0]) // 1
fmt.Println(p[0]) // 1(自动解引用)
八、指针与接口 #
8.1 接口与指针 #
go
type Reader interface {
Read()
}
type MyReader struct{}
func (r *MyReader) Read() {
fmt.Println("Reading...")
}
func main() {
var r Reader = &MyReader{} // 必须使用指针
r.Read()
}
8.2 指针接收者 #
如果方法使用指针接收者,接口变量必须存储指针:
go
var r Reader
r = MyReader{} // 错误:MyReader没有实现Read
r = &MyReader{} // 正确
九、Go指针与C指针的区别 #
9.1 Go指针特点 #
- 不支持指针运算
- 不支持->操作符
- 自动解引用结构体指针
- 有垃圾回收,无需手动释放
9.2 不允许的指针运算 #
go
a := [3]int{1, 2, 3}
p := &a[0]
p++ // 错误:不支持指针运算
9.3 unsafe包 #
如需指针运算,使用unsafe包:
go
import "unsafe"
a := [3]int{1, 2, 3}
p := &a[0]
p2 := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Sizeof(int(0))))
fmt.Println(*p2) // 2
十、最佳实践 #
10.1 何时使用指针 #
- 需要修改函数外部变量
- 结构体较大,避免复制
- 方法需要修改接收者
- 表示可选参数(nil表示无值)
10.2 何时避免指针 #
- 小型数据类型
- 切片、映射、通道本身是引用类型
- 增加代码复杂性
10.3 指针命名 #
go
type Person struct {
Name string
}
func NewPerson(name string) *Person {
return &Person{Name: name}
}
10.4 检查nil #
go
func process(p *Person) error {
if p == nil {
return errors.New("person is nil")
}
// 处理逻辑
return nil
}
十一、常见错误 #
11.1 解引用nil指针 #
go
var p *int
*p = 10 // panic
11.2 返回局部变量地址 #
Go允许这样做,编译器会处理:
go
func getInt() *int {
x := 10
return &x // OK,Go会将其分配到堆上
}
11.3 接口与指针混淆 #
go
type Reader interface {
Read()
}
type MyReader struct{}
func (r MyReader) Read() {} // 值接收者
func main() {
var r Reader = MyReader{} // OK
var r2 Reader = &MyReader{} // 也OK
}
十二、总结 #
指针要点:
| 操作 | 语法 |
|---|---|
| 声明 | var p *T |
| 取地址 | &变量 |
| 解引用 | *指针 |
| 分配 | new(T) |
关键点:
- 指针存储地址:指向变量的内存位置
- &取地址:获取变量地址
- *解引用:获取指针指向的值
- nil检查:使用前检查是否为nil
- 无指针运算:Go不支持指针算术
准备好学习运算符了吗?让我们进入下一章!
最后更新:2026-03-26