从我的第一堂CS课开始,我想对递归内存分配进行更多练习,因此我决定制作一款名为“递归地牢”的小游戏。它只是允许用户在每次玩家进入一个空(NULL)房间时通过递归生成一个新的“房间”来漫游无限的地牢。
然后保存新房间,可以通过...访问它。
*该程序不使用循环(至少,我认为它不使用循环)。我不熟悉循环的概念以及如何使用它。
我遇到的问题是,当我尝试清理所有递归分配的内存(“房间”)时,收到了经典错误“分段错误:核心已转储”。
下面是我的结构“房间”:
struct room
{//begin struct
room* backward;
room* left;
room* forward;
room* right;
string desc;
room() { //begin
backward = NULL;
left = NULL;
forward = NULL;
right = NULL;
/*End*/}
/*End struct*/};
每个“房间”都有其他与之相连的房间(左/右/前进/后退)。用户从一个空房间指针“ startingpoint”开始,并且可以沿上述任何一个方向前进。尝试进入一个空(NULL)房间时,会随机生成一个新房间供用户输入。
一旦玩家满意浏览,我将尝试在结束程序之前使用存储所有房间的数组清理分配的内存。而是,它导致分段错误。这是代码:
void ClearAllocatedMemory(room* aRoom, room** roomArray, int& raIndex) {
for(short i=0; i<raIndex; i++) {//begin for
delete roomArray[i];
/*End for*/}
delete[] roomArray;
/*End func*/}
以下是构成我的数组并定义其第一个(第0个)索引的代码:
room** roomArray;
int raIndex = 0;
room* startingpoint = new room();
roomArray[0] = startingpoint;
下面是将新房间添加到roomArray
索引中的代码:
room* GenRoom(room** roomArray, int& raIndex) {
room* newroom = new room();
newroom->desc = GenRoomDesc( rand()%12 + 1 );
raIndex++;
roomArray[raIndex] = newroom;
return newroom;
}
答案 0 :(得分:3)
即使可以进行两次删除,您的算法也不起作用。 如果您有两个相互连接的房间,它们将递归地尝试删除彼此,从而导致堆栈溢出。即您无法通过简单的递归删除循环。
您有一个设计问题-谁拥有每个房间?如果用垃圾收集的语言编写这些内容,那么您将不会在乎,而各个房间仅由于它们自身的存在而相互拥有。在C ++中,您必须关心并且设计应该反映这一点。
shared_ptr
和std::weak_ptr
在这种情况下会很混乱。即使您可以建立树状层次结构并因此使用unique_ptr
,也可能仅由于嵌套深层树的析构函数而导致堆栈溢出。
最好和最简单的解决方案是创建一个std::vector<Room>
,它是所有房间的明确所有者。邻居然后可以使用该向量的索引。一个警告是,在向量中心处分配空间会使较高的索引无效。可以通过与最后一个元素交换并仅修复其连接来解决。也会从中间的O(1)中删除。
如果您的情况下地图确实是动态的,我可以为std::list<Room>
辩护-为邻居使用迭代器或指针-或std::vector<std::unique_ptr<Room>>
-使用原始的 non-owning < / strong>指针。
这些仅适用于邻居的解决方案只是一个警告-当玩家循环探索房间时,您没有工具来确定该房间是否已经存在。例如。向上2,向右2,向下2,向左2应该使玩家回到初始房间。您可能要考虑实际使用2D网格(以后再担心内存/性能)。
答案 1 :(得分:2)
如果您的房间图中有一棵普通树(不返回),您的算法将起作用。由于可以返回,因此需要注意不要再次处理已经在处理的房间。
我将展示两种方法,一种对任意图形都有用,另一种仅对带有反向链接的树有用。
方法1.房间类应具有布尔visited
标记(游戏者未访问过,但耗尽顺序访问过)。初始值为false
。然后,您的删除功能应进行如下修改:
ClearAllocatedMemory(room* aRoom) {
if (aRoom == nullptr || aRoom->visited) return;
aRoom->visited = true;
ClearAllocatedMemory(aRoom->backward);
ClearAllocatedMemory(aRoom->left);
ClearAllocatedMemory(aRoom->forward);
ClearAllocatedMemory(aRoom->right);
delete room;
}
这实际上将删除过程变成了Depth-first search。
方法2.房间删除例程应知道从到达的房间,而不要触摸那个房间。
ClearAllocatedMemory(room* aRoom, room* parent) {
if (aRoom == nullptr) return;
if (aRoom->backward != parent) ClearAllocatedMemory(aRoom->backward, aRoom);
if (aRoom->left != parent) ClearAllocatedMemory(aRoom->left, aRoom);
if (aRoom->forward != parent) ClearAllocatedMemory(aRoom->forward, aRoom);
if (aRoom->right != parent) ClearAllocatedMemory(aRoom->right, aRoom);
delete room;
}
这只是正常的从上到下的树遍历,并添加了阻止上升的检查。
第一种方法可能是更可取的,因为它是通用的,您可以将删除例程设为 destructor ,就像在C ++中一样。但这是另一个麻烦。
还请注意,此检查
if ( (aRoom->backward == NULL) && (aRoom->left == NULL)\
&& (aRoom->forward == NULL) && (aRoom->right == NULL) ) {
没有任何意义。每个房间都需要删除,而不仅仅是那些没有传出路径的房间。
答案 2 :(得分:1)
进行更多研究,回顾 stack 与 heap 内存,并通读此article-我想我可能已经在上面找到了问题代码。
更改了清理内存的方式后,从使用递归更改为使用@Quimby建议的数组,我实现了一个room **数组来容纳所有房间。我遇到的问题是我试图删除不应该删除的房间**:不应该将房间**分配给堆栈,而不是堆,因为我没有使用关键字new
来创建它。
因此,要修复我的代码,我只需要从函数中取出delete[] roomArray;
。