【问题标题】:How come my vector array won't output anything after I erase an element?为什么我的向量数组在擦除元素后不会输出任何内容?
【发布时间】:2022-01-18 05:04:48
【问题描述】:

最近我开始学习 C++,每天我都会做一个 C++ 练习以进一步了解这门语言。今天我在学习向量数组,但遇到了障碍。

我正在尝试制作一个简单的程序,该程序接受一个数组,将其放入一个向量中,然后删除所有奇数。但是由于某种原因,当我从向量中删除一个元素并输出修改后的向量时,它不会输出任何内容。

如果有人能在我做错的事情上指引我正确的方向,那就太好了!

remove.cpp

#include <iostream>
#include <vector>

using namespace std;

class removeOddIntegers {
    public:

        void removeOdd(int numbs[]) {

            vector<int> removedOdds;

            for(int i = 0; i < 10; ++i) {
                removedOdds.push_back(numbs[i]);
            }

            for(auto i = removedOdds.begin(); i != removedOdds.end(); ++i) {
                if(*i % 2 == 1) {
                    removedOdds.erase(removedOdds.begin() + *i);
                    std::cout << "Removed: " << *i << endl;
                }
            }

            for(auto i = removedOdds.begin(); i != removedOdds.end(); ++i) {
                std::cout << *i << endl; //doesn't output anything.
            }
 
        }

};

ma​​in.cpp

#include <iostream>
#include "remove.cpp"

using namespace std;

int main() {

    removeOddIntegers r;
    int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    r.removeOdd(numbers);

    return 0;
}

现在,我知道我可以只过滤数组,然后只将偶数推回向量,坦率地说,这就像一个魅力。但我想了解为什么我的方法不起作用。为什么当我从向量中删除一个元素时,它只是无法输出任何东西?

提前致谢!

【问题讨论】:

标签: c++ arrays vector


【解决方案1】:

存在一些问题,但它们大多归结为同一个基本问题。您违反了std::vector::erase 的迭代器保证:

在擦除点或之后使迭代器和引用无效,包括end() 迭代器。

在取消引用已删除的迭代器以显示“已删除”消息时,以及为循环调用 ++i 时,您都会这样做。

此外,您的调用 removedOdds.erase(removedOdds.begin() + *i); 是错误的,因为它使用向量中的实际 作为从一开始的偏移量。这种假设是完全错误的。

在迭代器中擦除元素并保留有效迭代器的正确方法是:

i = removedOdds.erase(i);

这是您的循环,修复它所需的更改最少:

for (auto i = removedOdds.begin(); i != removedOdds.end(); ) {
    if (*i % 2 == 1) {
        std::cout << "Removed: " << *i << endl;
        i = removedOdds.erase(i);
    } else {
        ++i;
    }
}

注意迭代器现在是如何在循环体中前进的。你可以做一个思想实验来思考为什么。或者您可以尝试以错误的方式进行操作,并使用{ 1, 3, 5, 7, 9 } 之类的输入来演示问题。

这仍然不是从向量中删除元素的惯用方式。正如您所提到的,元素应该交换到向量的末尾。这样做的原因是std::vector::erase 是一个线性运算,它必须对向量的整个剩余部分进行洗牌。如果你多次这样做,你基本上有 O(N^2) 的时间复杂度。

推荐的方法是使用std::remove_if:

removedOdds.erase(removedOdds.begin(),
                  std::remove_if(removeOdds.begin(), removeOdds.end(),
                                 [](int n) { return n % 2 == 1; }));

【讨论】:

【解决方案2】:

通过一个更简单的示例更容易观察到所示算法中的缺陷:

        for(int i = 0; i < 2; ++i) {
            removedOdds.push_back(numbs[i]);
        }

这仅使用两个值初始化向量:01。当您在脑海中执行显示的代码时,这足够小,可以在您的脑海中跟随:

        for(auto i = removedOdds.begin(); i != removedOdds.end(); ++i) {

在这里迭代的第一个值0 不会发生任何有趣的事情。 ++i 递增迭代器以指向值1,然后:

            if(*i % 2 == 1) {
                removedOdds.erase(removedOdds.begin() + *i);
                std::cout << "Removed: " << *i << endl;
            }

这一次erase() 从向量中删除1。当然,这也是i 所指的。然后,如果您查看您的 C++ 参考资料,您将看到 discover that std::vector::erase:

在擦除点或擦除点之后使迭代器和引用无效, 包括 end() 迭代器。

i 现在是“擦除点”,因此,i 不再是有效的迭代器。任何后续使用 i 都会变成未定义的行为。

而且,i 立即被使用,即在for 循环迭代表达式中递增。那是你未定义的行为。

原始向量包含值09:如果您使用调试器,它将显示各种有趣的未定义行为。您可以使用调试器查看显示的代码在遇到更高的奇数值时是否能够存活,例如79。如果确实如此,那么此时vector 显然会小得多,但removedOdds.erase(removedOdds.begin() + *i); 现在将尝试删除现在大约一半大小的向量中的第 7 个或第 9 个值,一个完全不存在的值在向量中,随之而来的是欢闹。

总结一下:您的“方法不起作用”是因为该算法在多个方面存在根本缺陷,而您得到“无输出”的原因是程序崩溃。

【讨论】:

    猜你喜欢
    • 2019-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    相关资源
    最近更新 更多