题目
编写程序,阐述现代 C++ 极其著名的“Erase-Remove Idiom”(擦除-移除惯用法)。演示单纯调用 std::remove 算法并不能改变 vector.size(),它只是将不被删除的有效元素向前压实平移,并返回新逻辑尾部的迭代器;必须紧接着调用 vec.erase(newEnd, vec.end()) 才能真正释放尾部多余的残余元素。最后演示 C++20 引入的一步到位全局函数 std::erase 与 std::erase_if。
输入格式:无需外部输入。
输出格式:输出 remove 之后与 erase 之后的元素序列、size 与 capacity 变化。
数据范围与约定:STL 算法与容器接口解耦分离设计原则。
思路与算法
1. 为什么 std::remove 不能真正删除元素
STL 算法的设计哲学是“算法与容器解耦”。std::remove 仅接收泛型迭代器,它根本不知道底层是 vector、list 还是原生数组,因此它无权也无法调用容器自身的 erase 调整容量。
2. 平移与残余值remove 执行后,从返回的 newEnd 迭代器到 vec.end() 之间的元素处于残留状态(仍然占据内存空间且计数在 size() 中)。
3. 标准写法vec.erase(std::remove(vec.begin(), vec.end(), target), vec.end()); 在单次 O(N) 线性遍历中完成平移与尾部批量析构,是删除 vector 元素的标准教科书范式。
完整程序
#include <iostream>
#include <vector>
#include <algorithm>
void printVec(const std::vector<int>& v, const std::string& label) {
std::cout << label << " [size=" << v.size() << ", cap=" << v.capacity() << "]: ";
for (int x : v) std::cout << x << " ";
std::cout << "\n";
}
int main() {
std::vector<int> v = {1, 2, 99, 3, 99, 4, 99, 5};
printVec(v, "初始数组");
// 1. 第一步:仅执行 std::remove
auto newEnd = std::remove(v.begin(), v.end(), 99);
printVec(v, "仅调用 std::remove(99) 之后 (注意 size 未发生改变!)");
// 2. 第二步:调用容器的 erase 彻底截断丢弃尾部
v.erase(newEnd, v.end());
printVec(v, "调用 v.erase(newEnd, v.end()) 之后 (完成真正彻底删除)");
// 3. 基于条件谓词删除:删除所有奇数
v.erase(std::remove_if(v.begin(), v.end(), [](int n) {
return n % 2 != 0;
}), v.end());
printVec(v, "Erase-Remove-if 删除所有奇数后");
return 0;
}运行示例
输出:
初始数组 [size=8, cap=8]: 1 2 99 3 99 4 99 5
仅调用 std::remove(99) 之后 (注意 size 未发生改变!) [size=8, cap=8]: 1 2 3 4 5 4 99 5
调用 v.erase(newEnd, v.end()) 之后 (完成真正彻底删除) [size=5, cap=8]: 1 2 3 4 5
Erase-Remove-if 删除所有奇数后 [size=2, cap=8]: 2 4