Golang 指针

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

Golang 指针

一、先明确:什么是「内存地址」?

计算机的内存像无数个「小盒子」,每个盒子有唯一编号(即内存地址),用于存放数据。

  • 变量本质是给某个「小盒子」起的名字,比如 a := 10 中,a 就是编号为 0xc00001a0a8(假设)的盒子的名字,盒子里装着 10
  • 「地址」就是这个盒子的编号(如 0xc00001a0a8),「指针」就是专门用来存储这个编号的变量。

二、&:取地址运算符——把变量的「盒子编号」取出来

& 的作用是获取变量对应的内存地址,返回值是一个「指针」(即地址本身)。

核心特性:
  • 操作对象:必须是「变量」(不能对常量、表达式用 &,比如 &10&(a+b) 是错误的)。
  • 返回结果:类型为「*T」(T 是原变量的类型),表示「指向 T 类型的指针」。
示例:
package main

import "fmt"

func main() {
    a := 10      // 变量a,存着10,地址假设为0xc00001a0a8
    ptr := &a    // 用&取a的地址,ptr就是指针(类型为*int)
    
    fmt.Println(a)   // 输出:10(a盒子里的内容)
    fmt.Println(&a)  // 输出:0xc00001a0a8(a的地址,即ptr的值)
    fmt.Printf("ptr的类型:%T\n", ptr)  // 输出:*int(指向int的指针)
}
类比理解:
  • 你家地址是「北京市朝阳区XX街100号」(对应 &a)。
  • 你本人是 a&a 就是你家的门牌号。

三、*:双重身份——定义指针类型 + 解引用指针

* 有两个完全不同的用法,需根据上下文区分:

1. 定义指针类型(声明变量时用)

在类型前加 *,表示「这个变量是指针,专门存另一个变量的地址」。

格式:
var 指针变量名 *目标类型  // 声明一个指向「目标类型」的指针
示例:
var p *int    // p是指针变量,只能存int类型变量的地址(或nil)
var s *string // s是指针变量,只能存string类型变量的地址(或nil)
注意:
  • 未初始化的指针默认值为 nil(表示不指向任何地址,类似「空门牌号」)。
  • 指针类型严格匹配:*int 不能存 string 变量的地址,就像「只能存北京地址的本子,不能写上海地址」。
2. 解引用指针(使用指针时用)

对指针变量用 *,表示「根据指针存的地址,找到对应的变量,获取/修改它的值」。

核心作用:

通过指针间接操作目标变量(不用直接用变量名)。

示例1:获取指针指向的值
a := 10
p := &a  // p存a的地址(0xc00001a0a8)

fmt.Println(*p)  // 输出:10(通过p的地址找到a,取出值)
示例2:通过指针修改目标变量的值
a := 10
p := &a

*p = 20  // 解引用p,找到a,把a的值改成20
fmt.Println(a)  // 输出:20(a被修改)
类比理解:
  • p 是一张纸条,上面写着你家地址(&a)。
  • *p 就是「根据纸条上的地址找到你家,然后敲门看看里面有什么(取值),或者把里面的东西换了(修改值)」。

四、&* 的「互逆关系」

对变量 x 来说:

  • &x 得到指针 pp = &x);
  • *p 得到原变量 x*p = x)。
验证示例:
x := 5
p := &x  // p = &x(p是x的地址)
fmt.Println(*p == x)  // 输出:true(*p 等于 x的值)
注意:

&*p 等价于 p(先解引用 p 得到 x,再取 x 的地址,结果还是 p);
*&x 等价于 x(先取 x 的地址得到 p,再解引用 p,结果还是 x)。

五、指针的实际用途:解决「值传递」的局限

Go 语言函数参数默认是「值传递」(传变量的副本),如果想在函数内修改外部变量,必须用指针。

反例:值传递无法修改外部变量
func modify(a int) {
    a = 100  // 这里修改的是a的副本,和外部变量无关
}

func main() {
    x := 10
    modify(x)
    fmt.Println(x)  // 输出:10(x没被修改)
}
正例:用指针传递实现修改
func modify(p *int) {
    *p = 100  // 解引用p,修改外部变量x的值
}

func main() {
    x := 10
    modify(&x)  // 传x的地址(指针)
    fmt.Println(x)  // 输出:100(x被成功修改)
}
结构体场景:高效传递大对象

结构体可能包含大量字段(比如100个字段),值传递会复制整个结构体,效率低;而指针传递只复制一个地址(8字节,64位系统),更高效。

type Person struct {
    name string
    age  int
    // ... 很多字段
}

// 指针传递:只传地址,高效
func updateName(p *Person) {
    p.name = "张三"  // 直接修改原结构体
}

func main() {
    p := Person{name: "李四", age: 20}
    updateName(&p)   // 传p的地址
    fmt.Println(p.name)  // 输出:张三(原结构体被修改)
}

六、new() 函数:另一种创建指针的方式

new(T) 会创建一个 T 类型的变量,初始化默认值(如 int 为0,string 为空),并返回该变量的指针(*T 类型)。

用法对比:
// 方法1:先定义变量,再取地址
var a int
p1 := &a  // p1是*int类型,指向a(a的默认值为0)

// 方法2:用new()直接创建指针
p2 := new(int)  // p2是*int类型,指向一个默认值为0的int变量
*p2 = 10        // 给指针指向的变量赋值

fmt.Println(*p1)  // 输出:0
fmt.Println(*p2)  // 输出:10
注意:

new() 只用于创建「基本类型」或「结构体」的指针,不能直接初始化复杂数据(如切片、映射,它们有专门的初始化函数)。

七、常见误区和注意事项

  1. 对 nil 指针解引用会崩溃
    nil 指针不指向任何地址,对它用 * 会触发运行时错误:

    var p *int  // p是nil
    *p = 10     // 报错:panic: runtime error: invalid memory address or nil pointer dereference
    
  2. 指针也有自己的地址
    指针本身也是变量,存储在内存中,所以 &p 是「指针的地址」(类型为 **int,即「指向指针的指针」):

    a := 10
    p := &a       // p是*int类型,值为&a
    pp := &p      // pp是**int类型,值为&p
    fmt.Println(*pp == p)  // 输出:true(解引用pp得到p)
    fmt.Println(** pp == a) // 输出:true(连续解引用得到a)
    
  3. 不要过度使用指针
    指针会增加代码复杂度,能不用就不用:

    • 基本类型(int、string等)传值效率很高,无需指针;
    • 函数内不需要修改外部变量时,用值传递更清晰。

总结:核心公式

运算符 作用场景 示例 类比
&x 取变量x的地址,得指针 p := &a 问「a住在哪」,得到地址纸条p
*p 解引用指针p,得目标值 fmt.Println(*p) 按纸条p的地址找过去,看里面有什么
*p = v 通过指针修改目标值 *p = 20 按纸条p的地址找过去,把里面的东西换成v
*T 定义指向T类型的指针类型 var p *int 声明一个「只能写int类型地址」的纸条本

理解指针的关键:指针是「地址的载体」,& 负责生成这个载体,* 负责通过载体操作目标。多写几个修改变量、传递结构体的例子,很快就能熟练掌握~


网站公告

今日签到

点亮在社区的每一天
去签到