指针 #

一、指针概念 #

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)

关键点:

  1. 指针存储地址:指向变量的内存位置
  2. &取地址:获取变量地址
  3. *解引用:获取指针指向的值
  4. nil检查:使用前检查是否为nil
  5. 无指针运算:Go不支持指针算术

准备好学习运算符了吗?让我们进入下一章!

最后更新:2026-03-26