二维数组元素移动逻辑错误排查:元素越界至非法位置
问题定位与修复:元素移动越界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分析
同一元素单次调用中被多次移动:
MovePeople采用顺序遍历+实时修改数组的逻辑,当元素被移动到后续遍历的位置时,会被再次处理并移动。比如元素从(y,x)移到(y+1,x),后续循环到y+1行x列时,该元素会被再次随机移动,多次移动叠加后就可能超出数组边界(如从(9,2)多次向下移动会得到row=10,超出0-9的合法范围)。CheckValid传值导致状态不一致:
CheckValid的第一个参数是传值的SRI Sim,每次调用都会拷贝原数组。当MovePeople已经修改了原数组的元素位置时,CheckValid检查的是修改前的拷贝数组,而非当前最新状态,可能导致错误判断目标位置是否为空。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
相关产品推荐
相关产品推荐

