GO语言基础教程(99)Go指针的使用方法之修改指针指向的变量的值:来来来,聊聊Go指针:如何“偷天换日”修改变量值!

一、开篇:从一个“值拷贝”的尴尬说起

刚开始学Go的时候,你肯定写过这样的代码:

func changeValue(num int) {
    num = 100
}

func main() {
    x := 10
    changeValue(x)
    fmt.Println(x) // 输出啥?还是10!
}

是不是很崩溃?明明在函数里把num改成了100,出来一看x还是那个倔强的10!

这就是Go的值传递在作祟——函数接收到的是变量的副本,你对副本的任何修改,都影响不了原始数据。

这时候,指针就像一把万能钥匙,能直接打开变量的“房间门”,进去随意改造!

二、指针是啥?打个比方就懂了!

想象一下,变量就像一栋房子,里面住着数据。普通操作就像是:

  • 访问变量:你站在房子外喊话,里面的人把数据递出来
  • 值传递:你完全复制了一栋新房子,但改造副本不影响原房

指针呢?它就是这栋房子的地址纸条!拿着这个纸条,你可以:

  1. 找到原始房子
  2. 直接进去重新装修(修改变量值)
  3. 甚至把纸条复制给更多人,让大家都能找到这里

在Go中:

  • &变量 → 获取变量的地址(拿到地址纸条)
  • *指针 → 根据地址访问实际房子(拿着纸条找上门)
三、实战开始:让指针“大显神通”
3.1 基础操作三步走
package main

import "fmt"

func main() {
    // 第1步:有个普通变量
    original := "我是原始字符串"
    
    // 第2步:获取它的地址(拿到地址纸条)
    pointer := &original
    
    // 第3步:通过指针修改值(拿着纸条上门装修)
    *pointer = "我被指针修改了!"
    
    fmt.Println(original) // 输出:我被指针修改了!
}

看,这就是指针的魔力!我们没直接碰original变量,但通过它的地址,实现了“隔空改造”。

3.2 函数中的指针改造

回到开头的尴尬场景,现在用指针来解决:

func realChange(num *int) { // 接收指针类型
    *num = 100 // 解引用并修改
}

func main() {
    x := 10
    realChange(&x) // 传入地址
    fmt.Println(x) // 输出100!成功了!
}

关键区别

  • 之前:changeValue(x) → 传递值的副本
  • 现在:realChange(&x) → 传递原始地址

这就好比:

  • 值传递:我复制一份你的照片,在照片上涂鸦,你的脸还是干净的
  • 指针传递:我直接拿到你家的钥匙,进去在你脸上画画(有点暴力,但很形象!)
四、深入场景:指针在复杂数据中的运用
4.1 结构体指针——让代码更高效
type User struct {
    Name string
    Age  int
}

// 值传递版本——产生结构体拷贝
func updateUserV1(user User) {
    user.Name = "张三"
    user.Age = 20
}

// 指针传递版本——直接操作原结构体
func updateUserV2(user *User) {
    user.Name = "李四" // 自动解引用,等价于 (*user).Name
    user.Age = 25
}

func main() {
    user := User{Name: "初始名字", Age: 18}
    
    updateUserV1(user)
    fmt.Printf("值传递后:%s, %d\n", user.Name, user.Age) 
    // 输出:初始名字, 18 → 没变!
    
    updateUserV2(&user)
    fmt.Printf("指针传递后:%s, %d\n", user.Name, user.Age) 
    // 输出:李四, 25 → 成功修改!
}

性能提示:大型结构体传指针能避免内存拷贝,提升效率。小结构体(比如几个字段)用值传递反而可能更快,因为指针需要间接寻址。

4.2 切片与指针——小心这个陷阱!

很多人以为切片传进函数就能直接修改,其实不然:

func modifySlice(s []int) {
    s[0] = 999 // 这个能修改成功!
    s = append(s, 1000) // 但这个可能无效!
}

func main() {
    nums := []int{1, 2, 3}
    modifySlice(nums)
    fmt.Println(nums) // 输出:[999 2 3]
}

为什么append可能无效?因为切片本质是个结构体,包含指向底层数组的指针、长度、容量。append可能触发重新分配内存,让切片指向新数组。

解决方案——如果需要修改切片本身(比如扩容后),请用指针:

func realModifySlice(s *[]int) {
    *s = append(*s, 1000)
}

func main() {
    nums := []int{1, 2, 3}
    realModifySlice(&nums)
    fmt.Println(nums) // 输出:[1 2 3 1000]
}
五、指针的“规矩”与最佳实践
5.1 空指针——指针的“薛定谔状态”
var p *int
fmt.Println(*p) // 报错:空指针解引用!

指针声明后默认是nil,就像一张空头支票,不能直接使用。安全的做法:

if p != nil {
    fmt.Println(*p) // 确保指针有效才使用
}
5.2 多重指针——指针的“套娃游戏”
value := "hello"
p := &value     // 一级指针
pp := &p        // 二级指针

fmt.Println(**pp) // 输出:hello
**pp = "world"    // 修改原始值

实际开发中很少需要二级指针,但在某些CGO交互或复杂数据结构中可能会遇到。

5.3 什么时候该用指针?

推荐用指针

  • 需要在函数内修改原始数据
  • 结构体很大,避免拷贝开销
  • 实现链表、树等数据结构

不必要用指针

  • 基本类型(int、bool等),除非确实需要修改
  • 小结构体,值传递更简单安全
  • 并发场景下,指针可能引发数据竞争
六、完整示例:综合运用指针技巧

来看一个实际案例——用户管理系统:

package main

import "fmt"

type Profile struct {
    Email string
    Level int
}

type User struct {
    Name    string
    Age     int
    Profile *Profile // 嵌套指针
}

// 修改用户基本信息
func updateBasicInfo(user *User, newName string, newAge int) {
    user.Name = newName
    user.Age = newAge
}

// 修改用户资料(通过指针的指针)
func updateProfile(profile **Profile, newEmail string) {
    if *profile == nil {
        *profile = &Profile{} // 创建新的Profile
    }
    (*profile).Email = newEmail
    (*profile).Level = 999
}

// 批量升级用户等级
func upgradeUsers(users []*User) { // 切片里存储指针
    for i := range users {
        if users[i].Profile != nil {
            users[i].Profile.Level++
        }
    }
}

func main() {
    // 创建用户
    user1 := &User{Name: "小明", Age: 20}
    user2 := &User{
        Name: "小红", 
        Age: 22,
        Profile: &Profile{Email: "xiaohong@example.com", Level: 1},
    }
    
    users := []*User{user1, user2}
    
    fmt.Println("修改前:")
    printUsers(users)
    
    // 各种指针操作
    updateBasicInfo(user1, "大明", 25)
    updateProfile(&user1.Profile, "daming@example.com")
    upgradeUsers(users)
    
    fmt.Println("\n修改后:")
    printUsers(users)
}

func printUsers(users []*User) {
    for _, user := range users {
        fmt.Printf("姓名:%s, 年龄:%d", user.Name, user.Age)
        if user.Profile != nil {
            fmt.Printf(", 邮箱:%s, 等级:%d", user.Profile.Email, user.Profile.Level)
        }
        fmt.Println()
    }
}

输出结果:

修改前:
姓名:小明, 年龄:20
姓名:小红, 年龄:22, 邮箱:xiaohong@example.com, 等级:1

修改后:
姓名:大明, 年龄:25, 邮箱:daming@example.com, 等级:1000
姓名:小红, 年龄:22, 邮箱:xiaohong@example.com, 等级:2

这个示例展示了:

  • 结构体指针作为函数参数
  • 指针切片的高效遍历
  • 多级指针的实际应用
  • 空指针的安全处理
七、总结

指针就像是Go语言中的“任意门”,让你能够:

  • 🚀 直接修改变量——不再受值拷贝的限制
  • 💾 节省内存——避免大数据结构的复制
  • 🛠 实现复杂数据结构——链表、树等必备

记住几个关键点

  1. &取地址,*解引用
  2. 函数内修改外部变量必须传指针
  3. 切片、map本身是引用类型,但扩容时需要指针
  4. 结构体较大时用指针提升性能
  5. 始终注意空指针问题

指针刚开始可能觉得绕,但多用几次就会发现:它不过是变量世界的“导航系统”。掌握了指针,你就掌握了Go语言直接操作内存的超能力!

现在,就去你的代码里试试指针的魔力吧!保证让你有种“从外围观看到直接入场改造”的爽快感!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值