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

二维数组元素移动逻辑错误排查:元素越界至非法位置

问题定位与修复:元素移动越界bug

我定义了结构体SRI,其中包含初始化值为常量Empty(=0)的二维数组SimArr。通过GenRanPlace函数按指定数量(4个1、5个2、2个3、1个4)在10×10的数组中随机放置数值,这部分功能正常。但在随机移动元素(上下左右或对角线相邻位置,要求不越界、不触碰其他元素)时,编号为4的元素从(8,3)依次移动到(9,2)、(8,2)、(7,1)后,突然跳至非法位置(10,0)。

相关代码片段:

void GenRanPlace(SRI Sim, int numperson, int TypePerson, int rows, int cols)
{
    for(int x = 0;  x < numperson; x++)
    {
        int row = Random(0, rows - 1);
        int col = Random(0, cols - 1);
        
        while(Sim.SimArr[row][col] != Empty)
        {
            row = Random(0, rows - 1) ;
            col = Random(0, cols - 1);
        }
        
        Sim.SimArr[row][col] = TypePerson;
    }
}

bool CheckValid(SRI Sim, int rows, int cols, int destrow, int destcol, int row, int col)
{
    if(destrow >= 0 && destrow < rows  && destcol >= 0 && destcol <cols)
    {
        if((abs(row - destrow) <= 1) && (abs(col - destcol) <= 1))
        {
            if(Sim.SimArr[destrow][destcol] == Empty)
            {
                return true;
            }
        }
    }
    return false;  
}

void MovePeople(SRI &Sim, int rows, int cols)
{
    for(int y = 0; y < rows; y++) 
    {
        for(int x = 0; x < cols; x++)
        {
            int DestinationRow = y;
            int DestinationCol = x;
            
            int value = Random(0,7);
            
            switch(value)
            {
                case 0: DestinationRow--; break; // 上
                case 1: DestinationRow++; break; // 下
                case 2: DestinationCol--; break; // 左
                case 3: DestinationCol++; break; // 右
                case 4: DestinationRow--; DestinationCol--; break; // 左上
                case 5: DestinationRow--; DestinationCol++; break; // 右上
                case 6: DestinationRow++; DestinationCol--; break; // 左下
                case 7: DestinationRow++; DestinationCol++; break; // 右下
            }
            
            if(CheckValid(Sim, rows, cols, DestinationRow, DestinationCol, y, x))
            {
                Sim.SimArr[DestinationRow][DestinationCol] =  Sim.SimArr[y][x];
                Sim.SimArr[y][x] = Empty;
            }
        }
    }
}

// main函数内循环
do
{
    PrintGame(Rows, Cols, Sim);
    cin >> chEntry;
    
    switch(chEntry)
    {
        case 'A':
        case 'a': 
            MovePeople(Sim, Rows, Cols);
            break;
        case 'Q':
        case 'q':
            blnRun = false;
            break;
    }
}while(blnRun);

核心bug分析

  1. 同一元素单次调用中被多次移动:
    MovePeople采用顺序遍历+实时修改数组的逻辑,当元素被移动到后续遍历的位置时,会被再次处理并移动。比如元素从(y,x)移到(y+1,x),后续循环到y+1行x列时,该元素会被再次随机移动,多次移动叠加后就可能超出数组边界(如从(9,2)多次向下移动会得到row=10,超出0-9的合法范围)。

  2. CheckValid传值导致状态不一致:
    CheckValid的第一个参数是传值的SRI Sim,每次调用都会拷贝原数组。当MovePeople已经修改了原数组的元素位置时,CheckValid检查的是修改前的拷贝数组,而非当前最新状态,可能导致错误判断目标位置是否为空。

  3. GenRanPlace传值的潜在问题:
    当前GenRanPlace参数是传值的SRI Sim,修改的是结构体拷贝而非原结构体,这会导致原数组无法被正确初始化。你提到这部分功能正常,大概率是代码书写失误,实际应该使用传引用。


修复代码

1. 修改GenRanPlace为传引用

void GenRanPlace(SRI &Sim, int numperson, int TypePerson, int rows, int cols)
{
    for(int x = 0;  x < numperson; x++)
    {
        int row = Random(0, rows - 1);
        int col = Random(0, cols - 1);
        
        while(Sim.SimArr[row][col] != Empty)
        {
            row = Random(0, rows - 1) ;
            col = Random(0, cols - 1);
        }
        
        Sim.SimArr[row][col] = TypePerson;
    }
}

2. 修改CheckValid为传引用

bool CheckValid(SRI &Sim, int rows, int cols, int destrow, int destcol, int row, int col)
{
    if(destrow >= 0 && destrow < rows && destcol >= 0 && destcol < cols)
    {
        if((abs(row - destrow) <= 1) && (abs(col - destcol) <= 1))
        {
            if(Sim.SimArr[destrow][destcol] == Empty)
            {
                return true;
            }
        }
    }
    return false;  
}

3. 重写MovePeople,先记录移动计划再统一执行

避免同一元素被多次移动:

#include <vector>
#include <utility>

void MovePeople(SRI &Sim, int rows, int cols)
{
    // 记录所有合法移动:原位置 -> 目标位置
    std::vector<std::pair<std::pair<int, int>, std::pair<int, int>>> moves;
    
    // 第一步:遍历数组,确定所有合法移动
    for(int y = 0; y < rows; y++) 
    {
        for(int x = 0; x < cols; x++)
        {
            if(Sim.SimArr[y][x] == Empty)
                continue; // 空位置跳过
            
            int destRow = y;
            int destCol = x;
            
            int value = Random(0,7);
            
            switch(value)
            {
                case 0: destRow--; break; // 上
                case 1: destRow++; break; // 下
                case 2: destCol--; break; // 左
                case 3: destCol++; break; // 右
                case 4: destRow--; destCol--; break; // 左上
                case 5: destRow--; destCol++; break; // 右上
                case 6: destRow++; destCol--; break; // 左下
                case 7: destRow++; destCol++; break; // 右下
            }
            
            // 检查移动合法性,合法则记录
            if(CheckValid(Sim, rows, cols, destRow, destCol, y, x))
            {
                moves.push_back({{y, x}, {destRow, destCol}});
            }
        }
    }
    
    // 第二步:统一执行所有合法移动
    for(auto &move : moves)
    {
        auto srcPos = move.first;
        auto destPos = move.second;
        Sim.SimArr[destPos.first][destPos.second] = Sim.SimArr[srcPos.first][srcPos.second];
        Sim.SimArr[srcPos.first][srcPos.second] = Empty;
    }
}

修复说明

  • 先记录移动计划再统一执行,确保每个元素在单次MovePeople调用中最多移动一次,避免多次移动叠加导致越界。
  • 所有涉及修改或检查结构体的函数改用传引用,避免拷贝导致的状态不一致问题。
  • 跳过空位置的遍历,减少不必要的计算。

内容的提问来源于stack exchange,提问作者Lauri

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:37:33