通过在 C++ 中单独直接访问容器的迭代器来删除容器的元素

Deleting an element of a container by directly accessing its iterator alone in C++

本文关键字:迭代器 元素 删除 访问 C++ 单独直      更新时间:2023-10-16

我已经在我的主函数中声明了一个std::vector<int>,并希望从中删除所有偶数元素,但只能将其迭代器传递给名为 remove_even 的函数,该函数接受容器的开始和结束迭代器。

#include <iostream>
#include <algorithm>
#include <vector>
void remove_even(auto start, auto end) {
    while(start != end) {
        if(*start % 2 == 0)
        // Remove element from container
    }
}
int main() {
    std::vector<int> vec = {2, 4, 5, 6, 7};
    remove_even(vec.begin(), vec.end());
}

有没有办法在C++中做到这一点,或者我必须直接将我的向量传递给函数?

std::vector本身具有允许擦除向量中所需元素的方法erase

使用迭代器所能做的只是调用标准算法std::remove_if然后在调用方法 erase 时使用返回的迭代器。

例如

#include <iostream>
#include <vector>
#include <algorithm>
std::vector<int>::iterator remove_even( std::vector<int>::iterator first,
                                        std::vector<int>::iterator last )
{
    return std::remove_if( first, last, []( int x ) { return x % 2 == 0; } );
}
int main()
{
    std::vector<int> vec = { 2, 4, 5, 6, 7 };
    for ( int x : vec ) std::cout << x << ' ';
    std::cout << std::endl;
    vec.erase( remove_even( vec.begin(), vec.end() ), vec.end() );
    for ( int x : vec ) std::cout << x << ' ';
    std::cout << std::endl;
}    

程序输出为

2 4 5 6 7 
5 7 

这是IMO的正确方式:

template <typename T>
T remove_even(T start, T end) {
   return std::remove_if(start,end,[](const auto& item){return item%2==0;};
}
int main() {
    std::vector<int> vec = {2, 4, 5, 6, 7};
    vec.erase(remove_even(vec.begin(), vec.end()),vec.end());
}

从 cplusplus.com:

迭代器是指向元素范围(如数组或容器)中的某个元素的任何对象,它能够使用一组运算符(至少具有增量 (++) 和取消引用 (*) 运算符)遍历该范围的元素。

如引文中所述,它指向一系列元素中的一个元素。它不需要提供有关其所在范围的任何信息,即有关存储元素的容器的信息。

如注释中所述,从向量(或任何其他容器)中删除元素是影响容器的操作,而不仅仅是对象。因此,您将始终必须在容器上调用erase()或类似函数。

您询问的内容(有点,不完全)与此类似:

void remove_or_not(int& i){
    //do something with i to remove it from a container
    //but we dont have a container here
}
int main(){
    std::vector<int> vec;
    //fill vec and generate some int n
    remove_or_not(vec[n]);
}

在上面的例子中调用remove_or_not()时,我们只是传递对 int 的引用 - 我们完全丢失了它在容器内的信息,所以很明显我们不能从任何容器中删除它。

当尝试使用迭代器进行相同的操作时,我们仍然拥有元素在容器内的信息 - 但我们可能已经丢失了哪个容器中的信息,因为迭代器不需要保留此信息。
例如,C 样式数组上的迭代器可以只是一个指针。我们可以递增和递减它,并将其与指向第一个元素的指针和最后一个元素后面的指针进行比较。但是根本不需要知道数组的大小或数组的任何信息。

PS:有关如何正确实现所需内容的方法,请参阅已经发布的答案,我认为没有必要重复这些。

没有

办法通过仅将迭代器传递给函数来做到这一点。您必须将整个向量传递给函数,如下所示:

void remove_even(std::vector<int> v)
{
    for (auto it = v.begin(); it != v.end();) {
        if ((*it) % 2 == 0) {
            it = v.erase(it);
        } else {
            ++it;
        }
    }
}

您可能需要考虑通过 x 值引用传递容器然后返回复制/移动版本的习惯用法。

这提供了效率以及呼叫站点的可读性的优势。

下面是一个扩展示例。

请注意,编译器 (RVO) 将省略(优化)向量的所有"明显"复制。

另请注意,由于Container&&是在推导的上下文中评估的,因此它将自动成为const Container&Container&&,以调用站点上下文中最合适的为准。这意味着相同的功能既可以用来改变现有的容器(x = sorted(x)),也可以用于制作一个变异的副本(x = sorted(y)

#include <iostream>
#include <vector>
#include <algorithm>
template<class Container, class Predicate>
auto all_except(Container&& c, Predicate&& pred)
{
    auto result = std::forward<Container>(c);
    result.erase(std::remove_if(std::begin(result),
                                std::end(result),
                                std::forward<Predicate>(pred)),
                 std::end(result));
    return result;
}
template<class Container>
auto sorted(Container&& c)
{
    auto result = std::forward<Container>(c);
    std::sort(std::begin(result), std::end(result));
    return result;
}
auto even_items = [](auto const& item) { return item % 2 == 0; };
auto odd_items = [](auto const& item) { return item % 2 != 0; };

template<class Vec>
void emit(const Vec& v)
{
    std::cout << "[";
    auto sep = " ";
    for (auto const& e : v) {
        std::cout << sep << e;
        sep = ", ";
    }
    std::cout << " ]" << std::endl;
}
int main()
{
    std::vector<int> vec = {65, 2, 32, 63, 9, 13, 88, 22, 4, 5, 6, 7};
    emit(all_except(vec, even_items));
    emit(all_except(vec, odd_items));
    emit(all_except(sorted(vec), odd_items));
    vec = sorted(all_except(std::move(vec), even_items));
    emit(vec);
    return 0;
}

预期成果:

[ 65, 63, 9, 13, 5, 7 ]
[ 2, 32, 88, 22, 4, 6 ]
[ 2, 4, 6, 22, 32, 88 ]
[ 5, 7, 9, 13, 63, 65 ]