GO语言基础教程(98)Go指针的使用方法之获取指针指向的变量的值:别怕,Go指针不是刺儿头!带你轻松玩转“寻宝游戏”

大家好呀!我是你们的技术小伙伴,今天咱们来聊一个让很多Go语言新手“闻风丧胆”的话题——指针!别急着关页面,我保证这次不会像教科书一样念经,而是带大家像玩寻宝游戏一样轻松搞定它。

想象一下:变量就像一个个藏着宝贝的盒子,而指针就是那张写着“宝贝在哪儿”的藏宝图。今天我们的任务特别明确——学会怎么根据这张藏宝图,把真正的宝贝(变量的值)给挖出来!

一、指针是个啥?先来点前戏

刚开始学Go的时候,我听到“指针”俩字脑子里就自动弹出“复杂”、“容易出错”、“内存泄漏”这些吓人的标签。但实际接触后发现,Go的指针其实特别乖巧,比C语言的指针友好多了!

简单来说:

  • 普通变量:就像你家里的储物柜,直接放着你的东西(数据)
  • 指针变量:就像储物柜的钥匙,它不直接放东西,但能带你找到东西

更专业点说,指针就是一个存储了内存地址的变量。这个地址指向另一个变量在内存中的位置。

来,看个最简单的例子:

package main

import "fmt"

func main() {
    // 定义一个普通变量,就像买个新柜子放东西
    myTreasure := "一块金光闪闪的金条"
    
    // 定义一个指针变量,就像配一把柜子钥匙
    var myPointer *string
    
    // 让指针指向这个变量,就像把钥匙对应到柜子
    myPointer = &myTreasure
    
    fmt.Println("宝贝本身:", myTreasure)
    fmt.Println("藏宝图(内存地址):", myPointer)
}

运行结果:

宝贝本身: 一块金光闪闪的金条
藏宝图(内存地址): 0xc000010200

看到那个0xc000010200了吗?这就是金条在内存中的“住址”,每次运行都会不一样哦!

二、重头戏:怎么获取指针指向的值?

好了,现在我们知道指针存储的是地址了。但光知道地址没用啊,我们得把里面的宝贝拿出来才对!这就是今天的重头戏——使用*操作符来获取指针指向的值。

2.1 那个神奇的星号(*)

在Go语言里,*有两个身份:

  1. 在类型前面,表示这是一个指针类型,比如*string表示指向string的指针
  2. 在指针变量前面,表示“获取这个指针指向的值”

来,直接上代码最直观:

package main

import "fmt"

func main() {
    // 场景1:基本的取值操作
    amount := 100
    pointer := &amount
    
    fmt.Println("原始值:", amount)
    fmt.Println("指针的值(地址):", pointer) 
    fmt.Println("通过指针获取的值:", *pointer) // 看这里!关键的*
}

运行结果:

原始值: 100
指针的值(地址): 0xc000018068
通过指针获取的值: 100

看到了吗?*pointer就像是在对指针说:“别给我看地图了,直接带我去找宝贝!”

2.2 完整示例:购物车实战

光说不练假把式,我们来个实际点的例子。假设我们在写一个电商网站,要处理用户的购物车:

package main

import "fmt"

type ShoppingCart struct {
    UserID    int
    Items     []string
    TotalCost float64
}

func main() {
    // 创建一个小明的购物车
    xiaomingCart := ShoppingCart{
        UserID:    12345,
        Items:     []string{"Go语言编程", "机械键盘", "咖啡"},
        TotalCost: 299.97,
    }
    
    // 获取购物车指针
    cartPointer := &xiaomingCart
    
    // 各种取值方式展示
    fmt.Println("=== 直接访问 ===")
    fmt.Printf("用户%d的购物车里有%d件商品,总价¥%.2f\n", 
        xiaomingCart.UserID, len(xiaomingCart.Items), xiaomingCart.TotalCost)
    
    fmt.Println("\n=== 通过指针访问 ===")
    fmt.Printf("用户%d的购物车里有%d件商品,总价¥%.2f\n", 
        (*cartPointer).UserID, len((*cartPointer).Items), (*cartPointer).TotalCost)
    
    // Go有个语法糖,可以直接用指针访问字段
    fmt.Println("\n=== 指针的语法糖访问 ===")
    fmt.Printf("用户%d的购物车里有%d件商品,总价¥%.2f\n", 
        cartPointer.UserID, len(cartPointer.Items), cartPointer.TotalCost)
}

运行结果:

=== 直接访问 ===
用户12345的购物车里有3件商品,总价¥299.97

=== 通过指针访问 ===
用户12345的购物车里有3件商品,总价¥299.97

=== 指针的语法糖访问 ===
用户12345的购物车里有3件商品,总价¥299.97

三种方式结果一模一样!这说明Go真的很贴心,为了让你们不讨厌指针,它允许直接用指针访问字段,不用每次都写那个丑丑的(*pointer).Field

三、实际开发中我们怎么用?

我知道你们在想什么:“学这些有啥用?我直接访问变量不香吗?” 来来来,看看指针在真实场景下的威力。

3.1 场景一:函数内修改外部变量

这是指针最经典的用法了!先看一个反面教材:

func badAddOne(num int) {
    num = num + 1  // 只修改了副本,外面的变量没变
}

func main() {
    count := 10
    badAddOne(count)
    fmt.Println(count) // 还是10,没变!
}

再看正确做法:

func goodAddOne(num *int) {
    *num = *num + 1  // 修改指针指向的实际值
}

func main() {
    count := 10
    goodAddOne(&count)
    fmt.Println(count) // 变成11了!
}
3.2 场景二:避免大结构体的复制开销

当你的结构体很大时,传值会导致整个结构体被复制一份,既慢又耗内存。这时候指针就派上用场了:

type BigStruct struct {
    Data [1000000]int // 超大的数组
    Name string
}

// 这样传参很慢,因为要复制整个大数组
func processSlow(big BigStruct) {
    // 处理...
}

// 这样传参很快,只传个地址
func processFast(big *BigStruct) {
    // 通过指针访问和修改
    big.Name = "处理后的" + big.Name
    // 注意:这里直接 big.Name 而不是 (*big).Name
}

四、避坑指南:新手常犯的错误

我知道你们肯定会踩这些坑,因为我当年也踩过!提前给你们打个预防针:

4.1 空指针取值(Null Pointer)
var p *int
fmt.Println(*p) // 报错:panic: runtime error: invalid memory address or nil pointer dereference

解决办法:

var p *int
if p != nil {
    fmt.Println(*p)
} else {
    fmt.Println("指针是空的,别取值!")
}
4.2 指针未初始化就使用
var pointer *int
*pointer = 100 // 报错!

正确做法:

value := 100
pointer := &value  // 先让指针指向实际的变量
*pointer = 200     // 现在可以安全使用了

五、再来个完整实战示例

让我们用今天学到的所有知识,写个稍微复杂点的例子:

package main

import "fmt"

type Player struct {
    Name   string
    Level  int
    Health int
}

// 使用指针修改玩家状态
func levelUp(player *Player) {
    player.Level++
    player.Health = 100 // 升级回满血
    
    fmt.Printf("恭喜%s升级到%d级!\n", player.Name, player.Level)
}

// 使用指针检查玩家状态
func checkStatus(player *Player) {
    if player.Health < 20 {
        fmt.Printf("警告:%s血量过低,只剩%d点!\n", player.Name, player.Health)
    } else {
        fmt.Printf("%s状态良好,血量%d/100\n", player.Name, player.Health)
    }
}

func main() {
    // 创建玩家
    hero := Player{Name: "Go大侠", Level: 1, Health: 80}
    
    fmt.Println("=== 游戏开始 ===")
    checkStatus(&hero)
    
    // 玩家受到攻击
    hero.Health -= 70
    checkStatus(&hero)
    
    // 玩家升级
    levelUp(&hero)
    checkStatus(&hero)
    
    // 看看指针的各种玩法
    playerPtr := &hero
    fmt.Printf("\n=== 指针玩法展示 ===\n")
    fmt.Printf("直接访问:%s,等级%d\n", hero.Name, hero.Level)
    fmt.Printf("指针取值:%s,等级%d\n", (*playerPtr).Name, (*playerPtr).Level) 
    fmt.Printf("语法糖访问:%s,等级%d\n", playerPtr.Name, playerPtr.Level)
    
    // 证明它们真的是同一个东西
    fmt.Printf("内存地址:%p\n", playerPtr)
}

运行结果:

=== 游戏开始 ===
Go大侠状态良好,血量80/100
警告:Go大侠血量过低,只剩10点!
恭喜Go大侠升级到2级!
Go大侠状态良好,血量100/100

=== 指针玩法展示 ===
直接访问:Go大侠,等级2
指针取值:Go大侠,等级2
语法糖访问:Go大侠,等级2
内存地址:0xc000008078

六、总结

好了,今天的指针寻宝之旅就到这儿!我们再来回顾一下重点:

  1. 指针的本质:存储内存地址的变量,像藏宝图一样指向真正的数据
  2. 获取指针的值:使用*操作符,就像对着藏宝图说“我要真的宝贝”
  3. 实际用途:在函数内修改外部变量、避免大结构体复制
  4. 避坑技巧:记得检查空指针,确保指针已初始化

指针其实没那么可怕,对吧?Go语言已经把它包装得很友好了。记住,指针就是你的寻宝工具,用好了能让代码更高效、更优雅。

下次当你看到*&时,别再头疼了——想想今天的寻宝游戏,你现在已经知道怎么根据地图找到宝藏了!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

值引力

持续创作,多谢支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值