Go语言中如何正确处理大切片子集的修改与删除操作?
瓦片地图游戏中单位删除与指针失效问题的解决方案
场景与问题描述
在一款拥有大量单位的瓦片地图游戏中,分属不同帝国的单位会互相战斗。为了在战斗胜利后让进攻单位占据防守单位的位置,使用了指向Unit的指针切片Army。但从game.Map.Units中删除战死单位时遇到以下问题:
- remove1:因切片共享底层数组,删除元素后原数组元素移位,导致
Army中的指针指向错误的单位,产生副作用。 - remove2:循环中直接修改
TileMap.Units的长度,后续迭代会触发数组越界错误。 - remove3:创建新切片解决了删除问题,但
setX/setY方法失效——因为Army中的指针指向的是旧切片的底层数组元素,新切片的元素是值拷贝,修改不会同步到全局列表。
核心误区
问题根源在于TileMap.Units是值类型切片,而Army是指向这些值的指针切片:
- 当删除值切片中的元素时,底层数组的后续元素会向前移位,导致原指针指向的位置现在存储的是另一个单位。
- 当创建新切片时,新切片的元素是原元素的拷贝,原指针依然指向旧数组中的值,修改不会同步到新切片。
优化方案
方案1:将全局单位列表改为指针切片(推荐)
直接把TileMap.Units定义为[]*Unit,让所有Army中的指针直接指向唯一的Unit实例。这样无论切片如何修改,指针始终指向正确的单位,修改操作会直接同步到全局列表。
修改关键代码
type TileMap struct { Units []*Unit // 从值切片改为指针切片 } // 获取指定帝国的单位,直接返回指针 func (tilemap *TileMap) getUnitsByEmpire(empire int) []*Unit { units := []*Unit{} for _, unit := range tilemap.Units { if unit.Empire == empire { units = append(units, unit) } } return units } // 简化的删除方法 func (tilemap *TileMap) removeById(id int) { if idx := slices.IndexFunc(tilemap.Units, func(u *Unit) bool { return u.Id == id }); idx != -1 { tilemap.Units = slices.Delete(tilemap.Units, idx, idx+1) } }
方案优势
- 指针始终指向同一个
Unit实例,setX/setY修改会直接同步到全局列表。 - 删除操作无副作用,底层数组共享问题不复存在。
- 代码逻辑更简洁,避免值拷贝带来的指针失效问题。
方案2:用ID映射维护单位(适合超大量单位场景)
如果游戏中单位数量极大,使用map[int]*Unit存储所有单位,同时保留切片用于遍历。查找和删除操作的时间复杂度为O(1),指针始终有效。
修改关键代码
type TileMap struct { Units []*Unit UnitMap map[int]*Unit // 新增ID到Unit的映射 } // 初始化单位时同时添加到映射 func NewTileMap() *TileMap { return &TileMap{ UnitMap: make(map[int]*Unit), } } // 删除单位时同步更新映射和切片 func (tilemap *TileMap) removeById(id int) { delete(tilemap.UnitMap, id) tilemap.Units = slices.DeleteFunc(tilemap.Units, func(u *Unit) bool { return u.Id == id }) }
方案优势
- 查找、删除单位效率更高,适合单位数量极多的场景。
- 指针无需担心失效,始终指向唯一的
Unit实例。
修正后的完整示例代码(方案1)
package main import ( "fmt" "slices" ) type Game struct { Map *TileMap } type TileMap struct { Units []*Unit // 改为指针切片 } // 按ID删除单位 func (tilemap *TileMap) removeById(id int) { if idx := slices.IndexFunc(tilemap.Units, func(u *Unit) bool { return u.Id == id }); idx != -1 { tilemap.Units = slices.Delete(tilemap.Units, idx, idx+1) } } // 获取指定帝国的单位 func (tilemap *TileMap) getUnitsByEmpire(empire int) []*Unit { units := []*Unit{} for _, unit := range tilemap.Units { if unit.Empire == empire { units = append(units, unit) } } return units } type Unit struct { Id, X, Y, Empire int } type Army []*Unit // 删除Army中的单位 func (army Army) remove(i int) Army { return slices.Delete(army, i, i+1) } func (army Army) X() int { return army[0].X } func (army Army) setX(x int) { for _, unit := range army { unit.X = x } } func (army Army) Y() int { return army[0].Y } func (army Army) setY(y int) { for _, unit := range army { unit.Y = y } } func (army Army) print(name string) { fmt.Println() fmt.Println(name, "len:", len(army), "cap", cap(army), ":") for _, unit := range army { fmt.Printf("%+v\n", unit) } } func main() { game := Game{ Map: &TileMap{}, } // 初始化单位,直接创建指针 game.Map.Units = append(game.Map.Units, &Unit{Id: 0, X: 0, Y: 0, Empire: 0}, &Unit{Id: 1, X: 0, Y: 0, Empire: 0}, &Unit{Id: 2, X: 0, Y: 0, Empire: 0}, &Unit{Id: 10, X: 1, Y: 1, Empire: 1}, &Unit{Id: 11, X: 1, Y: 1, Empire: 1}, &Unit{Id: 12, X: 1, Y: 1, Empire: 1}, &Unit{Id: 20, X: 2, Y: 2, Empire: 2}, &Unit{Id: 21, X: 2, Y: 2, Empire: 2}, &Unit{Id: 30, X: 3, Y: 3, Empire: 3}, &Unit{Id: 31, X: 3, Y: 3, Empire: 3}, &Unit{Id: 40, X: 4, Y: 4, Empire: 4}, &Unit{Id: 41, X: 4, Y: 4, Empire: 4}, &Unit{Id: 42, X: 4, Y: 4, Empire: 4}, ) fmt.Println("game.Map.Units:", len(game.Map.Units), cap(game.Map.Units)) for _, unit := range game.Map.Units { fmt.Printf("%+v\n", unit) } attackers := Army(game.Map.getUnitsByEmpire(1)) attackers.print("attackers") defenders := Army(game.Map.getUnitsByEmpire(3)) defenders.print("defenders") x := defenders.X() y := defenders.Y() round := 0 attackerRandomValues := []int{9, 0, 0, 9} defenderRandomValues := []int{0, 9, 9, 0} for len(attackers) > 0 && len(defenders) > 0 { round++ id := -1 r1 := 0 r2 := 0 for r1 == r2 { r1, attackerRandomValues = attackerRandomValues[0], attackerRandomValues[1:] r2, defenderRandomValues = defenderRandomValues[0], defenderRandomValues[1:] } if r1 > r2 { id = defenders[0].Id defenders = defenders.remove(0) } else if r1 < r2 { id = attackers[0].Id attackers = attackers.remove(0) } fmt.Println("\nround:", round) attackers.print("attackers") defenders.print("defenders") game.Map.removeById(id) fmt.Println("\ngame.Map.Units:", len(game.Map.Units), cap(game.Map.Units)) for _, unit := range game.Map.Units { fmt.Printf("%+v\n", unit) } } if len(attackers) > 0 { attackers.setX(x) attackers.setY(y) fmt.Println("\nAttackers won!") attackers.print("attackers") } if len(defenders) > 0 { fmt.Println("\nDefenders won!") defenders.print("defenders") } fmt.Println("\ngame.Map.Units:", len(game.Map.Units), cap(game.Map.Units)) for _, unit := range game.Map.Units { fmt.Printf("%+v\n", unit) } }
内容的提问来源于stack exchange,提问作者scratch
相关产品推荐
相关产品推荐

