
Go语言递归构建树形结构时,子节点为空的常见问题及解决方案
在使用Go语言从数据库查询数据并构建树形结构时,经常会遇到子节点为空的情况,即使数据库中存在子节点数据。本文分析此问题,并提供解决方案。
问题描述:
假设要构建一个地区树形结构。数据库查询能正确获取数据,但递归函数返回的子节点始终无法正确赋值到父节点的children字段,导致最终树形结构所有节点的children字段都为空。
立即学习“go语言免费学习笔记(深入)”;
示例代码:
func getSysRegionTree(pid string, deep int) (error, []data.RegionTree) {
var regionTree []data.RegionTree
// 查询数据库
err := tx.Model(&model.SysRegion{}).Where("pid = ? and enabled = true", pid).Order("id asc").Find(®ionTree).Error
if err != nil {
panic(err)
}
for _, region := range regionTree {
children := findChildren(tx, region.ID, deep-1) // 假设ID是string类型
fmt.Println("children count =", len(children))
region.Children = children // 问题在此行,修改的是副本
}
return err, regionTree
}
func findChildren(tx *gorm.DB, pid string, left int) []data.RegionTree {
var regionTree []data.RegionTree
err := tx.Model(&model.SysRegion{}).Where("pid = ? and enabled = true", pid).Order("id desc").Find(®ionTree).Error
if err != nil {
panic(err)
}
if left > 1 {
for _, region := range regionTree {
region.Children = findChildren(tx, region.ID, left-1) // 同样修改的是副本
}
}
return regionTree
}
问题根源:
Go语言的for...range...循环会创建循环变量的副本。在getSysRegionTree函数中,for _, region := range regionTree 创建了region变量的副本,region.Children = children 操作修改的是副本,而非regionTree切片中的原始元素。
解决方案:
主要有两种解决方案:
-
使用指针: 将
data.RegionTree结构体的Children字段定义为*[]data.RegionTree,即指向切片的指针。修改指针指向的切片内容会直接影响regionTree中的元素。 这需要修改数据结构定义和相关代码。 -
使用索引遍历: 放弃
for...range...,使用基于索引的循环:
for i := range regionTree {
children := findChildren(tx, regionTree[i].ID, deep-1)
regionTree[i].Children = children
}
这种方法直接修改regionTree切片中的元素,避免了值拷贝问题。
选择哪种方法取决于代码结构和个人偏好。使用指针更符合Go语言的习惯,但需要修改数据结构;使用索引遍历则无需修改数据结构,但代码略显冗长。 建议优先考虑使用指针,以提高代码的可读性和效率。










