87 / 103 · C++11 · 约 12 分钟
过滤序列:算法重排与容器删除分开理解
算法只看到迭代器范围,不负责改变容器大小。以订单状态过滤为例,区分复制到新序列、稳定划分和删除尾区间,并观察空输入、全部淘汰以及原顺序保留的契约。
Accelerated C++: Practical Programming by Example
连续读完第0—16章与附录A、B的全部可用正文、代码、Details与练习。另直接读完PDF中24幅实质图示,覆盖中位数、删除/划分、指针、复制、引用计数和图片继承图;非把提取完成算作阅读。完整范围指此公开第二印副本,不包括未附独立习题解答;练习未执行。个别转录与早期印次错误保留勘误说明。
查看版本、实际阅读范围与原文入口 →需求先于容器技巧
要从一串任务中去掉取消项,先问是否要保留原输入、是否保留幸存项的顺序、谁拥有结果。书中通过学生分类不断改变实现,展示同一个业务规则可以用不同容器和算法表达。接口契约没有变,成本和失效规则却会变。
本例把任务缩成整数,非负值为保留项。copy_if 写入另一个 vector,原序列完全不动;就地 remove_if 则把保留项稳定地搬到前部,适合允许修改原序列的场景。它们都使用同一谓词含义,不让存储细节决定业务规则。
逻辑终点不等于容器终点
remove_if 返回的是保留前缀的终点。调用后 size 尚未缩小,尾部元素仍然存在,但不能把其具体值解释成被删除项清单。随后调用容器 erase,才销毁尾部元素并更新大小;这是算法与容器各自承担一半职责的结果。
示例不检查尾部碰巧留下什么,而是检查 erase 之后的完整序列。空输入自然形成空区间,全部淘汰时逻辑终点等于 begin。不要先保留指向某个业务对象的迭代器,再假设重排和删除后它仍表示同一个任务:即便某个位置仍可访问,那个位置的值也可能已经换了。
现代简写不取代理解契约
C++20 的 std::erase_if(vector, predicate) 可以直接表达就地过滤;这里保留两步写法,是为了说明 remove 算法不拥有容器的结构。若要同时得到两组并保留组内顺序,可以复制到两个目标,或用 stable_partition 得到分界,再自行决定是否拆成两个容器。
原书是 C++98 时代作品,函数对象和迭代器协议仍有教学价值,但旧式适配器不应直接移植。lambda 让局部判断更直观,标准 copy_if 也已经提供;这并不意味着算法会替你检查输出范围,裸 begin 指向空目标时仍不能写入,示例通过 back_inserter 建立新元素。
容易答错的地方
- remove_if 不改变 vector::size;省略 erase 会把无意义尾部当成有效记录继续处理。
- copy_if 输出到空 vector 的 begin 是越界写入;用 back_inserter,或先创建足够多的目标元素。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <algorithm>
#include <cassert>
#include <iterator>
#include <vector>
void discard_negative(std::vector<int>& tasks) {
const auto end = std::remove_if(tasks.begin(), tasks.end(),
[](int value) { return value < 0; });
tasks.erase(end, tasks.end());
}
int main() {
const std::vector<int> original{3, -1, 0, -2, 3};
std::vector<int> selected;
std::copy_if(original.begin(), original.end(), std::back_inserter(selected),
[](int value) { return value >= 0; });
const std::vector<int> expected{3, 0, 3};
assert(selected == expected);
auto modified = original;
discard_negative(modified);
assert(modified == expected && original[1] == -1);
std::vector<int> empty;
discard_negative(empty);
assert(empty.empty());
std::vector<int> rejected{-1, -2};
discard_negative(rejected);
assert(rejected.empty());
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-accelerated-cpp.cpp -o example && ./example预期结果
预期:正常退出、无输出;所有 assert 通过。
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
改成删除偶数,保留奇数原有顺序,负奇数也应保留。
查看参考答案
谓词改成 value % 2 == 0,不能用 value % 2 == 1 判断所有奇数,因为负奇数的余数可以为-1。对 {-3,-2,0,1,4,5} 执行两步删除,结果应完整等于 {-3,1,5};原有先后关系不变,证明的是稳定保留而非排序。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。