go指针

84 次阅读

一、指针基础概念

1. 什么是指针

  • 变量存储的是;指针变量存储的是另一个变量的内存地址
  • 通过地址可以直接操作原始变量,避免值拷贝开销、修改原值。

2. 两个核心操作符

  1. &:取地址符,放在变量前,获取该变量内存地址
  2. *:解引用符,放在指针变量前,取出地址对应的值

3. 指针类型格式

var p *int

*int 代表:指向 int 类型变量的指针。

二、指针基础使用示例

package main

import "fmt"

func main() {
	// 普通变量
	var num int = 10
	fmt.Printf("num的值:%d\n", num)
	fmt.Printf("num的内存地址:%p\n", &num)

	// 定义int类型指针,保存num地址
	var p *int
	p = &num
	fmt.Printf("指针p存储的地址:%p\n", p)
	fmt.Printf("指针p解引用的值:%d\n", *p)

	// 通过指针修改原始变量
	*p = 66
	fmt.Printf("修改后num的值:%d\n", num)
}

输出说明:修改 *p 等价直接修改 num

三、指针零值

指针未赋值时默认零值为 nil,代表不指向任何内存地址。

func main() {
	var p *int
	fmt.Println(p) // <nil>
	// *p // 报错:解引用nil指针,程序崩溃
}

使用指针前必须判断是否为 nil,避免运行时panic。

四、new 函数创建指针

new(T) 会:

  1. 分配一块 T 类型内存
  2. 内存填充类型零值
  3. 返回该内存地址指针
func main() {
	// 创建int指针,初始值0
	p := new(int)
	fmt.Println(*p) // 0
	*p = 99
	fmt.Println(*p) // 99

	// 结构体指针
	type User struct{ name string }
	u := new(User)
	u.name = "张三"
	fmt.Println(u.name)
}

五、值类型与指针(核心场景)

Go 值类型:int、float、bool、数组、struct

值类型传参/赋值会完整拷贝,副本修改不影响原变量;传指针可操作原值。

示例:不传指针,无法修改外部变量

func changeAge(age int) {
	age = 20
}

func main() {
	a := 10
	changeAge(a)
	fmt.Println(a) // 10,原值不变
}

示例:传入指针,修改原始数据

func changeAge(age *int) {
	*age = 20
}

func main() {
	a := 10
	changeAge(&a)
	fmt.Println(a) // 20,原值被修改
}

六、数组指针

数组是值类型,大数组拷贝成本高,可用数组指针传递:

func modifyArr(arr *[3]int) {
	(*arr)[0] = 99
}

func main() {
	a := [3]int{1,2,3}
	modifyArr(&a)
	fmt.Println(a) // [99 2 3]
}

七、结构体指针

结构体指针访问字段时,Go 支持简写,无需手动解引用 (*stu).name,直接 stu.name

package main
import "fmt"

type Student struct {
	name string
	age  int
}

func main() {
	s := Student{"小明", 16}
	p := &s
	// 两种写法等价
	fmt.Println((*p).name)
	fmt.Println(p.name) // 推荐简写

	// 通过指针修改结构体字段
	p.age = 17
	fmt.Println(s.age)
}

结构体指针作为函数参数

func setName(s *Student, newName string) {
	s.name = newName
}

func main() {
	s := Student{}
	setName(&s, "小红")
	fmt.Println(s.name)
}

八、多级指针(*T)

指针也可以保存指针地址,格式 **int,日常开发极少使用。

func main() {
	num := 10
	p1 := &num   // *int
	p2 := &p1    // **int

	fmt.Println(*p2)  // 等价p1,num的地址
	fmt.Println(**p2) // 等价num的值
}

九、指针与切片/map/channel(引用类型)

切片、map、channel 本身底层就是指针封装,不需要再传指针

func modifySlice(s []int) {
	s[0] = 99
}

func main() {
	sl := []int{1,2,3}
	modifySlice(sl)
	fmt.Println(sl) // [99 2 3] 直接修改原切片数据
}

只有需要修改切片本身(长度、容量、底层数组地址)时才使用 *[]int

十、指针方法(结构体接收者)

  1. 值接收者:方法接收结构体副本,无法修改原结构体
  2. 指针接收者:接收结构体指针,可修改原始结构体字段
type User struct {
	name string
}

// 值接收者
func (u User) show() {
	fmt.Println(u.name)
}

// 指针接收者
func (u *User) rename(newName string) {
	u.name = newName
}

func main() {
	u := User{"老王"}
	u.rename("老李")
	u.show() // 老李
}

十一、指针使用规范与注意事项

  1. nil 指针禁止解引用,使用前先判空
var p *int
if p != nil {
	fmt.Println(*p)
}
  1. 大值类型(数组、大结构体)传参优先指针,减少内存拷贝
  2. 引用类型 slice/map/chan 一般无需额外套指针
  3. 不要返回函数内局部变量的指针(Go 逃逸分析可自动优化,但尽量避免)
  4. 多级指针可读性差,业务代码尽量不用

十二、指针核心总结

  1. &变量:取地址;*指针:解引用取值
  2. 指针零值 nil,nil 不可解引用
  3. new(T) 快速生成类型指针
  4. 值类型传参拷贝,指针可修改外部原始数据
  5. 结构体指针访问字段可省略解引用
  6. 切片、map、channel 属于引用类型,自带地址特性