Menu
CoddyTech

指针作为映射值

本课属于 Coddy GO 学习旅程的“逻辑与流程”单元。第 27 课,共 68 课。

虽然在映射中直接存储结构体很有用,但存储指向结构体的指针还能带来一个强大的功能:通过映射直接修改原始结构体数据。当你需要在存储数据后更新数据时,这种方法非常重要。

当你将结构体指针存储为映射的值时,你存储的是结构体的内存地址,而不是它的副本。下面介绍如何声明和使用这样的映射:

type Product struct {
    Price    float64
    Quantity int
}

products := map[string]*Product{
    "laptop": &Product{Price: 999.99, Quantity: 5},
    "mouse":  &Product{Price: 25.50, Quantity: 20},
}

当你需要修改数据时,这个主要优势就很明显了。由于你使用的是指针,通过映射所做的更改会影响原始结构体:

// 直接通过映射更新笔记本电脑的价格
products["laptop"].Price = 899.99
fmt.Println(products["laptop"].Price) // 输出:899.99

这种模式在需要频繁更新存储数据的应用中尤其有用,例如库存系统、用户档案,或地图作为主要数据存储且修改需要持久保存的任何场景。

challenge icon

挑战

简单

构建一个简单的库存管理系统,展示将结构体指针用作映射值的优势。这个挑战会说明,基于指针的映射如何支持直接修改存储的数据,让库存系统能够高效、及时地响应更新。

你会收到 3 个输入:

  • 一个表示产品数量的字符串(例如,"4")
  • 一个包含产品数据的字符串,格式为:"name1:price1:quantity1,name2:price2:quantity2,name3:price3:quantity3"(例如,"laptop:999.99:5,mouse:25.50:20,keyboard:75.00:15,monitor:299.99:8")
  • 一个包含更新操作的字符串,格式为:"operation1:product1:value1,operation2:product2:value2",其中操作可以是 "price" 或 "quantity"(例如,"price:laptop:899.99,quantity:mouse:25,price:keyboard:65.00")

你的任务是:

  1. 定义一个 Product 结构体,包含以下字段:
    • Price(float64),表示产品价格
    • Quantity(int),表示产品库存数量
  2. 创建一个映射,键为产品名称(字符串),值为指向 Product 结构体的指针
  3. 解析第二个输入中的产品数据:
    • 按逗号拆分输入,获取各个产品条目
    • 对每个条目按冒号拆分,分离名称、价格和数量
    • 将价格字符串转换为 float64,将数量字符串转换为整数
    • 创建一个 Product 结构体,并将指向它的指针存入映射
  4. 解析时,将产品名称按它们在输入中出现的顺序存入单独的切片。在 Go 中遍历映射时,键的访问顺序是随机的,因此需要用切片来保持所有产品的输入顺序。
  5. 显示初始库存,并使用标题 "Initial Inventory:"
  6. 按以下格式显示每个产品:"[name]: $[price] (Stock: [quantity])"
  7. 解析并应用第三个输入中的更新操作:
    • 按逗号拆分输入,获取各个操作
    • 对每个操作按冒号拆分,获取操作类型、产品名称和新值
    • 通过映射中的指针直接更新相应字段
    • 按以下格式打印每次更新:"Updated [product]: [field] changed to [new_value]"
  8. 显示更新后的库存,并使用标题 "Updated Inventory:"
  9. 按与初始显示相同的格式再次显示每个产品
  10. 计算并显示库存总价值:"Total Inventory Value: $[total_value]"

使用 strconv 包将价格和数量字符串转换为相应的数值类型。显示价格时,将所有价格格式化为 2 位小数。这个挑战展示了将指针存入映射后,如何直接修改原始结构体数据,从而立即持久地更新库存,而不需要将值重新赋给映射。

亲手试试

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	// 读取输入
	var numProductsStr string
	var productDataStr string
	var operationsStr string
	
	fmt.Scanln(&numProductsStr)
	fmt.Scanln(&productDataStr)
	fmt.Scanln(&operationsStr)
	
	// TODO:在这里定义 Product 结构体
	
	// TODO:创建 map 来存储产品指针
	
	// TODO:创建切片来维护产品顺序(确保输出顺序一致)
	
	// TODO:解析产品数据并填充 map
	// 注意:
	// 用逗号拆分 productDataStr,得到各个条目
	// 对每个条目,用冒号拆分出名称、价格和数量
	// 将价格和数量的字符串转换为合适的数据类型
	// 在 map 中存储指向 Product 结构体的指针
	// 将产品名称添加到 order 切片
	
	// TODO:显示初始库存
	// 使用顺序切片按固定顺序遍历商品
	// 格式:"[name]: $[price] (Stock: [quantity])"
	
	// TODO:解析并执行更新操作
	// 注意:
	// 用逗号拆分 operationsStr,得到每个操作
	// 对每个操作,用冒号拆分,得到类型、名称和值
	// 通过 map 中的指针直接更新对应字段
	// 为每个操作打印更新消息
	
	// TODO:显示更新后的库存
	// 同样使用顺序切片,确保输出顺序一致
	// 显示时计算总价值
	
	// TODO:计算并显示库存总价值
	// 格式:"Total Inventory Value: $[total_value]"
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

逻辑与流程中的所有课

自己练习: Go 在线编译器