避坑指南:Go 语言中 append 与结构体指针的那些事儿
好的,这是为你精简后的博客文章,只保留核心概念和解决方案:
Go 语言避坑:append 结构体后数据“丢失”的秘密
在 Go 语言中,处理数据分页和聚合时,你可能会遇到一个困惑:为什么我明明往结构体里加了数据,但 append 到切片后,数据却没了?
问题本质:append 的“副本”行为
我的代码大致是这样:
// main 函数
for {
// ... 获取分页数据 result
currentPageResultList := &QueryResultList{ Page: page, PageSize: pageSize } // 创建空结构体指针
queryResult.Data = append(queryResult.Data, *currentPageResultList) // 【问题点】这里就追加了空的副本!
doQueryList(currentPageResultList, result) // 后面才填充数据
// ...
}
好的,这是为你精简后的博客文章,只保留核心概念和解决方案:
Go 语言避坑:append 结构体后数据“丢失”的秘密
在 Go 语言中,处理数据分页和聚合时,你可能会遇到一个困惑:为什么我明明往结构体里加了数据,但 append 到切片后,数据却没了?
问题本质:append 的“副本”行为
我的代码大致是这样:
// main 函数
for {
// ... 获取分页数据 result
currentPageResultList := &QueryResultList{ Page: page, PageSize: pageSize } // 创建空结构体指针
queryResult.Data = append(queryResult.Data, *currentPageResultList) // 【问题点】这里就追加了空的副本!
doQueryList(currentPageResultList, result) // 后面才填充数据
// ...
}
当你执行 queryResult.Data = append(queryResult.Data, *currentPageResultList) 时,append 会将 *currentPageResultList(即你解引用后的 QueryResultList 值)的副本存储到 queryResult.Data 切片中。此时,这个副本的 Items 字段还是空的。
之后,doQueryList(currentPageResultList, result) 虽然修改了 原始的 currentPageResultList 指向的结构体,但它无法影响到已经存在于 queryResult.Data 中的那个副本。
精简方案:先填充,后追加
解决之道很简单:确保在将结构体追加到切片之前,它已经包含了所有需要的数据。
这里提供两种 Go 语言中常用的、更整洁的解决方案:
- 结构体方法封装 (推荐)
这是最 Go 风格的方式。让 QueryResultList 自己知道如何填充其 Items 字段。
// 1. 定义 QueryResultList(Items 仍为值切片)
type QueryResultList struct {
Page int
PageSize int
Items []QueryResultItem // 保持为值切片
}
// 2. 为 QueryResultList 添加填充方法
func (qrl *QueryResultList) FillItemsFromProjectListV2Res(result *internal.ProjectListV2Res) {
// 遍历 result.Data.Result,创建 QueryResultItem 并 append 到 qrl.Items
// 逻辑和原来的 doQueryList 类似
for _, project := range result.Data.Result {
item := QueryResultItem{ /* ... */ } // 创建值
qrl.Items = append(qrl.Items, item)
}
}
// 3. main 函数这样调用
func main() {
// ...
for {
// ... 获取 result
currentPageResultList := &QueryResultList{
Page: page,
PageSize: pageSize,
Items: []QueryResultItem{}, // 确保初始化
}
// 核心:先调用方法填充数据
currentPageResultList.FillItemsFromProjectListV2Res(result)
// 填充完毕,再追加这个完整的结构体副本
queryResult.Data = append(queryResult.Data, *currentPageResultList)
// ...
}
// ...
}
优点: 代码清晰,职责明确,main 函数简洁,符合 Go 的高内聚设计。
- 函数返回填充好的结构体
让数据填充函数直接创建并返回一个完整的结构体实例。
// 1. doQueryList 函数返回填充好的 QueryResultList 值
func doQueryList(page, pageSize int, result *internal.ProjectListV2Res) QueryResultList {
qrl := QueryResultList{ // 创建并初始化
Page: page,
PageSize: pageSize,
Items: []QueryResultItem{},
}
// ... 遍历 result.Data.Result 填充 qrl.Items ...
return qrl // 返回填充好的结构体值
}
// 2. main 函数这样调用
func main() {
// ...
for {
// ... 获取 result
// 核心:直接获取填充好的 QueryResultList 值
currentPageResultList := doQueryList(page, pageSize, result)
// 直接追加这个完整实例(因为 doQueryList 返回的是值)
queryResult.Data = append(queryResult.Data, currentPageResultList)
// ...
}
// ...
}
优点: 函数职责独立,更像一个“工厂”,生产完整的数据对象。
总结
问题在于 append 结构体时会创建副本。解决之道是在追加之前,确保结构体已经包含所有数据。推荐使用结构体方法或函数返回填充好的结构体这两种方式,让代码更清晰、更符合 Go 习惯。尽量避免切片内存储过多指针,除非有明确的共享数据需求。

浙公网安备 33010602011771号