You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言中如何正确处理大切片子集的修改与删除操作?

瓦片地图游戏中单位删除与指针失效问题的解决方案

场景与问题描述

在一款拥有大量单位的瓦片地图游戏中,分属不同帝国的单位会互相战斗。为了在战斗胜利后让进攻单位占据防守单位的位置,使用了指向Unit的指针切片Army。但从game.Map.Units中删除战死单位时遇到以下问题:

  • remove1:因切片共享底层数组,删除元素后原数组元素移位,导致Army中的指针指向错误的单位,产生副作用。
  • remove2:循环中直接修改TileMap.Units的长度,后续迭代会触发数组越界错误。
  • remove3:创建新切片解决了删除问题,但setX/setY方法失效——因为Army中的指针指向的是旧切片的底层数组元素,新切片的元素是值拷贝,修改不会同步到全局列表。

核心误区

问题根源在于TileMap.Units是值类型切片,而Army是指向这些值的指针切片:

  1. 当删除值切片中的元素时,底层数组的后续元素会向前移位,导致原指针指向的位置现在存储的是另一个单位。
  2. 当创建新切片时,新切片的元素是原元素的拷贝,原指针依然指向旧数组中的值,修改不会同步到新切片。

优化方案

方案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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 18:07:34