C ++:从动态struct数组中删除元素并移动其他元素

时间:2017-12-31 03:03:24

标签: c++ malloc free delete-operator

我有一系列结构。我试图从该数组中删除元素列表,并将其他元素移到左侧。移动元素后,我试图删除/释放数组末尾的内存,我们不再需要了。我有以下代码:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
void removeelement(int*);
void displayelements();

typedef struct {
   int n;
}element;
element** array;

int numofelements=5;

int main() {
   array = (element**)malloc(5*sizeof(element*));
   for(int i=0;i<5;i++){
       array[i] = new element;
       array[i]->n=i;
   }
   int removelist[3] = {1,3,4};
   removeelement(removelist);
   displayelements();
   return 0;
}
void removeelement(int* removelist){
    for(int i=0;i<3;i++){
        int index = removelist[i];
        int j;
        for(j=index;j<numofelements-2;j++){
            array[j] = array[j+1];
        }
        delete [] array[j+1];
        numofelements--;
    }
}
void displayelements(){
    int i=0;
    while(i<numofelements){
        printf("%d\n",array[i]->n);
        i++;
    }
}

delete [] array[j+1];导致异常:

*** Error in `main': double free or corruption (fasttop): 0x0000000001861cb0 ***

我不明白是什么造成了这种情况。正如许多人在其他论坛中建议的那样,我使用'new'运算符来创建一个新的动态元素。

修改

我做了以下更改:

我将for(j=index;j<numofelements-2;j++){更改为for(j=index;j<numofelements-1;j++){

int index = removelist[i]int index = removelist[i]-i

我删除了delete [] array[j+1]delete array[numofelements+1]置于for循环之外。 虽然我只在一个元素上使用了删除,但它也为其他冗余元素释放了内存,这很有趣。 这是最终的代码:

#include <iostream>
#include<stdio.h>
#include<stdlib.h>
void removeelement(int*);
void displayelements();

typedef struct {
   int n;
}element;
element** array;

int numofelements=5;

int main() {
   array = (element**)malloc(5*sizeof(element*));
   for(int i=0;i<5;i++){
       array[i] = new element;
       array[i]->n=i;
   }
   int removelist[3] = {1,3,4};
   removeelement(removelist);
   displayelements();
   return 0;
}
void removeelement(int* removelist){
    for(int i=0;i<3;i++){
        int index = removelist[i]-i;
        int j=index;
        for(;j<numofelements-1;j++){
            array[j] = array[j+1];
        }
        numofelements--;
    }
    delete array[numofelements+1];
}
void displayelements(){
    int i=0;
    while(i<5){
        printf("%d\n",array[i]->n);
        i++;
    }
}

我使用此代码了解它。但是我会像许多人建议的那样使用std :: vector。

3 个答案:

答案 0 :(得分:3)

您在delete[]表达式未返回的指针上使用了new[]表达式。因此,程序的行为是不确定的。

使用new分配的任何内容都必须使用delete取消分配。 delete[]不会这样做。

即使您使用了正确的表达式,还有另一个错误:

int numofelements=5;

//...
for(int i=0;i<3;i++){
    int index = removelist[i];
    int j;
    for(j=index;j<numofelements-2;j++){
        array[j] = array[j+1];
    }
    delete [] array[j+1];
    numofelements--;
}

在外循环的第一次迭代之后,array[4]已被删除。请注意,自removelist[i] == 1起,我怀疑array[4]不应该首先被删除。

在第二次迭代期间,array[4]将再次被删除。由于这指向已经删除的对象,因此行为未定义。

此外,删除指针的副本在内部循环中保留在数组中array[j] = array[j+1],而某些指针将被覆盖,因此它们的内存将被泄露。对算法的简单修复:首先删除索引处的指针,然后删除删除后的元素。

更多:如果您的循环按预期工作,前两次迭代将删除数组的一个元素,从而将numofelements减少为3.然后,您将删除索引4处的元素一个在索引0..2中有有效指针的数组。据推测,要删除的索引必须进行排序;在这种情况下,可以通过删除索引removelist[i] - i来确定转移的数量。另一个聪明的策略是将指数从高点移到低点,正如保罗所建议的那样。

需要考虑的其他事项:

  • 程序泄漏为array分配的内存。对于这个微不足道的程序来说这可能不是问题,但是有一个习惯是释放所有已分配的内存,以免在重要时忘记这样做。
  • 使用malloc是个坏主意,除非有具体和合理的理由这样做。人们通常没有合理的理由使用malloc
  • 在不使用RAII容器的情况下完全分配动态内存是个坏主意。如果使用std::vector,则可以避免使用此程序中的错误。

答案 1 :(得分:1)

这一行:

delete [] array[j+1];

删除&#39; array [j + 1]&#39;指向的元素数组。但是&#39;数组[j + 1]&#39;由这条线初始化:

array[i] = new element;

仅分配单个元素,而不是元素数组,因此删除也应仅删除单个元素。 E.g:

delete array[j+1];

然而,主要问题是正在删除错误的元素。为了了解原因,我们假设初始化&#39;数组&#39;将它指向五个元素&#39;我们称之为A,B,C,D和E的结构。

在调用removeelements()之前,&#39; array&#39;包含以下指针:

array[0] -> A
array[1] -> B
array[2] -> C
array[3] -> D
array[4] -> E

&#39; numofelements&#39;是5。

在removeelements()中,要删除的第一个元素是1,内部循环如下所示:

for(j=1;j<3;j++){
    array[j] = array[j+1];
}

这将导致&#39; array [2]&#39;的内容被复制到&#39;数组[1]&#39;和&#39;阵列[3]&#39;被复制到&#39;数组[2]。之后&#39;阵列&#39;包含以下内容:

array[0] -> A
array[1] -> C
array[2] -> D
array[3] -> D
array[4] -> E

此时&#39; j&#39;包含3所以&#39;删除数组[j + 1]&#39;将删除&#39; array [4]&#39;指向的元素,即&#39; E&#39;。

&#39; numofelements&#39;然后递减到4。

要删除的第二个元素是3.因为&#39; numofelements&#39;现在是4,内循环将如下所示:

for(j=3;j<2;j++){
    array[j] = array[j+1];
}

&#39; J&#39;将被初始化为3.大于2,因此循环的主体不会执行,并且&#39; array&#39;将保持不变。

因为&#39; j&#39;是3&#39;删除数组[j + 1]&#39;将再次删除&#39; array [4]&#39;,仍然指向E.所以E第二次被删除,导致你得到的错误。

程序是否继续,&#39; numofelements&#39;将被递减为3并且我们将继续移动到要移除的第三个元素,这将是4.这将产生这样的内部循环:

for(j=4;j<1;j++){
    array[j] = array[j+1];
}

&#39; J&#39;将被初始化为4,并且循环的主体将不再被执行。 &#39;删除数组[j + 1]&#39;会尝试删除&#39; array [5]&#39;指向的元素,这超出了&#39;数组的范围。并会导致例外。

正如其他人所建议的,处理此问题的最佳方法是使用std :: vector。但是,你的代码构造方式甚至std :: vector都无法提供你想要的结果,因为只要你从&#39; array&#39;中删除一个元素。所有跟随它的人的指数都会发生变化,这意味着其余的指数将会改变。将不再是正确的。

我建议无论你做了什么改变,你都可以手动单步执行代码,如上所述,跟踪数组的内容和相关变量,这样你就可以准确理解你的代码在做什么。

答案 2 :(得分:1)

除了内存管理中的明显错误之外,如果您首先对removelist数组进行排序,然后在该数组中从最后一个条目开始向第一个条目开始向后工作,则该方法通常可以更简单

执行此操作会改变调整array的方式,因为您将对不再受影响的条目进行大小调整(转换元素)。在当前代码中,您正在移动条目,并且在循环的后续迭代中,您需要使用现在“无效”removelist索引集来重新访问这些移位条目以进行删除。

请参阅mayaknife和user2079303的答案,以说明删除每个项目后的无效条目问题(从removelist数组中的最低条目到最高条目)。正如所指出的,即使使用std::vector也不会对你有所帮助,因为这个问题指出了用于删除元素的基本逻辑中的缺陷。

如果您要在removelist数组中向后工作,我们可能会在您当前的代码中解决这个问题(我说“可能已经解决了”,因为这没有经过全面测试,但它说明了更多或更少的要点):

void removeelement(int* removelist)
{
    for(int i = 2; i >= 0 ; --i)
    {
        int index = removelist[i];
        array* elementToDelete = array[index];
        for(j=index; j < numofelements -2; j++)
        {
            array[j] = array[j+1];
        }
        delete [] elementToDelete;
        numofelements--;
    }
}

因此,在每次迭代中,removelist索引仍然有效,因为您要从要删除的条目中的最高条目到最低条目。在纸上解决这个问题,看看你是否改变了迭代removelist数组的方式,你应该看看它是如何工作的,而不是通过removelist数组。

您还有其他问题,例如将mallocdelete[]混合。这样做是未定义的行为 - 永远不要在C ++程序中混合使用这样的分配/释放方法。

话虽如此,这是您程序的另一个版本,但不使用手动内存管理:

#include <vector>
#include <algorithm>
#include <iostream>
#include <array>

struct element {
   int n;
};

int main() 
{
    std::vector<element> arr(5);

    for (int i = 0; i < 5; ++i)
       arr[i].n = i;

    std::array<int, 3> removelist = {1,3,4};
    // sort the list 
    std::sort(removelist.begin(), removelist.end());

    // work backwards, erasing each element
    std::for_each(removelist.rbegin(), removelist.rend(),[&](int n){arr.erase(arr.begin() + n);});

    // output results
    for( auto& v : arr)
       std::cout << v.n << '\n';
}

Live Example

请注意反向迭代器rbegin()rend()的用法,从而模仿removelist容器的向后遍历。