题目

编写程序,阐述现代 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