HOWTO · C++
如何在 C++ 中删除向量中的元素
本文演示了如何在 C++ 中从向量中删除一个元素。
本页内容
要删除已知位置或范围中的元素,请调用 std::vector::erase。在 C++20 中,要删除所有与某个值相等的元素,请使用 std::erase;在更早的 C++ 标准中,可将 std::remove 与 erase 结合,使用 erase-remove 惯用法。示例展示了每种选择如何改变向量,以及如何保持迭代器有效。
使用 std::vector::erase 删除单个元素
std::vector::erase 接受指向单个元素的迭代器,或一个左闭右开的范围 [first, last)。该范围包含 first,但不包含 last。单元素重载要求传入指向某个元素的有效迭代器。它会返回指向被删除范围之后元素的迭代器;如果后面没有元素,则返回 end()。
下面的完整示例删除索引为 2 的元素,然后读取返回的迭代器:
运行此示例:
本节中的代码以及显示的标签或输出仍为英文。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{10, 20, 30, 40, 50};
auto next = numbers.erase(numbers.begin() + 2);
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << "\nNext value: " << *next << '\n';
}
10 20 40 50
Next value: 40
擦除操作会使位于被删除位置及其之后的迭代器和引用失效。再次使用这些句柄前,请更新它们或丢弃它们。要擦除一个范围,请传入指向范围起点的迭代器和指向末尾后一位置的迭代器;返回迭代器及失效规则相同。如果被删除范围延伸到末尾,返回的迭代器就等于 numbers.end(),不能解引用。
使用 std::erase 删除所有匹配值(C++20)
C++20 为 std::vector 增加了非成员重载 std::erase,该重载通过 <vector> 声明。它会删除所有与给定值相等的元素,并返回删除的元素数量。如果有多个匹配项,它们都会被删除,因此这种方式适用于按相等关系筛选元素。更早的语言标准不提供此重载;请使用下一节介绍的 erase-remove 惯用法。
此示例在以 C++20 编译时使用 std::erase,在使用更早标准时则改用 erase-remove。两种分支都会删除所有值为 2 的元素,并输出删除数量:
本节中的代码以及显示的标签或输出仍为英文。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 2, 4, 2, 5};
std::size_t removed;
#if __cplusplus >= 202002L
removed = std::erase(numbers, 2);
#else
const auto old_size = numbers.size();
const auto new_end = std::remove(numbers.begin(), numbers.end(), 2);
numbers.erase(new_end, numbers.end());
removed = old_size - numbers.size();
#endif
std::cout << "Removed: " << removed << "\nRemaining: ";
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
Removed: 3
Remaining: 1 3 4 5
适用于 std::vector 的 std::erase 重载从 C++20 起可用,并会返回删除数量。较早标准对应的分支使用 erase-remove 惯用法,下一节将对此进行说明。
使用 erase-remove 惯用法删除匹配值
在 C++20 之前,可将 <algorithm> 中的 std::remove 与 std::vector 的成员函数 erase 结合使用。std::remove 会把不匹配的值移到可写范围的前部,并返回指向新逻辑末尾的迭代器。它不会改变向量的大小:在擦除尾部范围之前,向量仍包含原来的元素数量。将返回的迭代器和向量原本的 end() 传给 erase,即可缩小容器。
下面的示例使用这两个步骤删除所有值为 2 的元素:
本节中的代码以及显示的标签或输出仍为英文。
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 2, 4, 5};
const auto new_end = std::remove(numbers.begin(), numbers.end(), 2);
numbers.erase(new_end, numbers.end());
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
传给 std::remove 的范围必须可写,因为该算法会重新排列要保留的值。容器通过常量迭代器提供只读访问,并不妨碍在有可写迭代器范围时使用此惯用法。
在迭代时安全擦除元素
如果由谓词或其他条件决定要删除哪些元素,请使用迭代器循环。删除元素后,将迭代器设为 erase 返回的迭代器;只有当前元素保留时才递增迭代器。被擦除的迭代器已经失效,因此继续递增或解引用旧迭代器都不安全。
下面的示例会删除偶数,同时保持迭代器有效:
本节中的代码以及显示的标签或输出仍为英文。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers{1, 2, 3, 4, 5, 6, 7};
for (auto it = numbers.begin(); it != numbers.end();) {
if (*it % 2 == 0) {
it = numbers.erase(it);
} else {
++it;
}
}
for (std::size_t i = 0; i < numbers.size(); ++i) {
if (i != 0) std::cout << ' ';
std::cout << numbers[i];
}
std::cout << '\n';
}
C++20 中按相等值删除可使用 std::erase;在更早的标准中,可使用 erase-remove 惯用法;如果删除条件需要逐项判断,则可在循环中使用 erase 并接收其返回的迭代器。选择哪种方式,取决于筛选依据是已知位置或范围、与某个值相等,还是在循环过程中求值的谓词。