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

C++: Remove element from dynamic struct array and shift other elements

本文关键字:元素 删除 移动 其他 数组 动态 结构 C++      更新时间:2023-10-16

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

#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("%dn",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("%dn",array[i]->n);
i++;
}
}

我用这段代码让它工作。但是我将按照你们许多人的建议使用std::vector。

您在表达式未返回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],已删除指针的副本仍保留在数组中,而某些指针将被覆盖,因此它们的内存将被泄漏。算法的简单修复:首先删除索引处的指针,删除后移动元素。

甚至更多:如果你的循环按预期工作,前 2 次迭代将分别删除数组的一个元素,从而将numofelements减少到 3。然后,您将删除在索引 0..2 中具有有效指针的数组的索引 4 处的元素。大概必须对要删除的索引进行排序;在这种情况下,可以通过删除索引removelist[i] - i来计算班次来解决此问题。另一个聪明的策略是按照保罗的建议,从高到低删除指数。


其他需要考虑的事项:

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

除了内存管理中的明显错误之外,如果您首先对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';
}

现场示例

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

这一行:

delete [] array[j+1];

删除由"数组[j+1]"指向的元素数组。但是'array[j+1]'是由这一行初始化的:

array[i] = new element;

它只分配单个元素,而不是元素数组,因此删除也应该只删除单个元素。例如:

delete array[j+1];

然而,主要问题是删除了错误的元素。为了了解原因,让我们假设初始化"array"的循环将其分配给五个"元素"结构,我们将这些结构称为A,B,C,D和E。

在调用 removeelements() 之前,'array' 包含以下指针:

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

"numofelements"是5。

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

for(j=1;j<3;j++){
array[j] = array[j+1];
}
这将导致"array[2]"的内容被复制到"array[1]"中,"array[3]">

被复制到"array[2]"中。在该"数组"之后包含以下内容:

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

此时 'j' 包含 3,因此 'delete array[j+1]' 将删除 'array[4]' 指向的元素,即 'E'。

然后"元素数"递减为 4。

要删除的第二个元素是 3。因为 'numofelements' 现在是 4,所以内部循环将如下所示:

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

"j"将初始化为 3。这大于 2,因此循环的主体不会执行,"数组"将保持不变。

由于"j"是3,"删除数组[j+1]"将再次删除"array[4]",它仍然指向E。因此,E 被第二次删除,导致您收到错误。

如果程序继续,"numofelements"将减少到3,我们将继续删除第三个元素,即4。这将给出一个内部循环,如下所示:

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

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

正如其他人所建议的,处理此问题的最佳方法是使用std::vector。但是,你的代码结构方式甚至std::vector都无法给你你想要的结果,因为一旦你从"数组"中删除一个元素,它后面的所有元素的索引都会改变,这意味着"removelist"中的其余索引将不再正确。

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