std::ranges::remove
C++20 引入的算法,用于从范围中移除满足特定条件的元素。它是标准库算法 std::remove
的范围化版本,结合了范围库的便利性。以下是详细说明和示例:
功能
- 作用:将范围中不满足条件的元素移动到范围前部,并返回新的逻辑结尾迭代器。
- 特点:
- 不真正删除元素,仅通过移动元素实现“逻辑移除”。
- 实际需配合容器的
erase
方法完成物理删除。
- 复杂度:线性复杂度 O(n),n 为范围大小。
原型
template< std::permutable I, std::sentinel_for<I> S, class T, class Proj = std::identity > | (since C++20) (until C++26) | |
template< std::permutable I, std::sentinel_for<I> S, class Proj = std::identity, | (since C++26) | |
(2) | ||
template< ranges::forward_range R, class T, class Proj = std::identity > | (since C++20) (until C++26) | |
template< ranges::forward_range R, class Proj = std::identity, |
参数
first, last
:要处理的范围迭代器。r
:要处理的范围(可直接传递容器)。value
:要移除的元素值。
返回值
- 返回
subrange
,表示移除后的新逻辑范围(从原开始到新结尾)。
示例 1
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
int main() {
std::vector<int> vec{1, 2, 3, 2, 5, 2, 6};
// 使用 ranges::remove 移除所有值为 2 的元素
auto [new_begin, new_end] = std::ranges::remove(vec, 2);
// 物理删除
vec.erase(new_begin, new_end);
// 输出结果
for (int num : vec) {
std::cout << num << " "; // 输出:1 3 5 6
}
}
说明
- 逻辑移除:
ranges::remove
将非2
的元素移动到前面,返回新逻辑结尾。 - 物理删除:通过
vec.erase()
截断容器,实际删除多余元素。
应用场景
- 需要从容器中移除特定元素时。
- 结合范围库简化代码。
注意:若需自定义移除条件,可使用 ranges::remove_if
。
std::ranges::remove_if
template< std::permutable I, std::sentinel_for<I> S, class Proj = std::identity, | (3) | (since C++20) |
template< ranges::forward_range R, class Proj = std::identity, |
C++20 引入的算法,用于从范围中移除满足特定条件的元素。它是 std::remove_if
的范围化版本,结合了范围库的简洁性,允许直接操作容器或视图。以下是详细说明和示例:
功能
- 作用:将范围中不满足条件的元素移动到范围前部,返回新的逻辑结尾迭代器。
- 特点:
- 不实际删除元素,仅通过移动元素实现“逻辑移除”。
- 需配合容器的
erase
方法完成物理删除。
- 复杂度:线性复杂度 O(n),n 为范围大小。
参数
first, last
或r
:要处理的范围(迭代器对或直接传递容器)。pred
:谓词函数,返回true
的元素会被标记为“待移除”。proj
(可选):投影函数,用于在应用pred
前对元素进行转换。
返回值
- 返回
subrange
,表示移除后的新逻辑范围(从原开始到新结尾)。
示例 1 移除所有偶数:
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
int main() {
std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8};
// 使用 ranges::remove_if 移除所有偶数
auto [new_begin, new_end] = std::ranges::remove_if(vec,
[](int x) { return x % 2 == 0; } // 谓词:判断是否为偶数
);
// 物理删除
vec.erase(new_begin, new_end);
// 输出结果
for (int num : vec) {
std::cout << num << " "; // 输出:1 3 5 7
}
}
输出:
1 3 5 7
示例 2 投影的使用
投影 (proj
) 对元素的某个属性进行条件判断(如结构体的字段)。
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
// 定义结构体
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> people = {
{"Alice", 30},
{"Bob", 15},
{"Charlie", 20},
{"David", 17},
{"Eve", 19}
};
// 使用 ranges::remove_if + 投影
auto result = std::ranges::remove_if(people,
[](int age) {
return age < 18; // 谓词:判断年龄是否小于 18
},
&Person::age // 投影:将 Person 映射到其 age 字段
);
// 物理删除
people.erase(result.begin(), result.end());
// 输出结果
for (const auto& person : people) {
std::cout << person.name << " (" << person.age << ")\n";
}
}
输出
Alice (30) Charlie (20) Eve (19)
关键解释
-
投影的作用:
&Person::age
将每个Person
对象映射到其age
字段。- 谓词
[](int age) { ... }
实际接收的是投影后的age
值,而非整个Person
对象。
-
执行流程:
remove_if
遍历people
中的每个Person
对象。- 对每个对象,先应用投影(提取
age
),再应用谓词判断age < 18
。 - 移除所有满足条件(年龄 <18)的对象。
更复杂的投影示例
若需要根据字符串长度移除姓名长度超过 5 的人:
auto result = std::ranges::remove_if(people,
[](size_t len) { return len > 5; }, // 谓词:判断长度是否 >5
[](const Person& p) { // 投影:将 Person 映射到其 name 的长度
return p.name.size();
}
);
// 结果:移除 "Charlie"(长度 7)
std::ranges::remove_copy
C++20 引入的算法,用于将范围中不满足移除条件的元素复制到目标范围。与 std::ranges::remove
不同,它不会修改原始范围,而是直接生成过滤后的副本。以下是详细说明和示例:
功能
- 作用:复制原始范围中不等于指定值的元素到目标范围。
- 特点:
- 不修改原始范围,纯复制操作。
- 需要提前保证目标范围有足够空间,或使用插入迭代器(如
back_inserter
)。
- 复杂度:线性复杂度 O(n),n 为原始范围大小。
原型
Defined in header | ||
Call signature | ||
(1) | ||
template< std::input_iterator I, std::sentinel_for<I> S, std::weakly_incrementable O, class T, class Proj = std::identity > | (since C++20) (until C++26) | |
template< std::input_iterator I, std::sentinel_for<I> S, std::weakly_incrementable O, class Proj = std::identity, | (since C++26) | |
(2) | ||
template< ranges::input_range R, std::weakly_incrementable O, class T, class Proj = std::identity > | (since C++20) (until C++26) | |
template< ranges::input_range R, std::weakly_incrementable O, class Proj = std::identity, |
参数
参数 说明
first, last
或 r
: 输入范围(要处理的原始数据)
result
:目标范围的起始迭代器(复制结果的输出位置)
value:
要移除的元素值(所有等于此值的元素不会被复制)
proj
: 投影函数(可选,用于在比较前转换元素)
返回值
返回一个 remove_copy_result
结构体,包含:
.in
:输入范围的结束迭代器(即last
).out
:目标范围的结束迭代器(最后一个复制元素的下一个位置)
示例 1:基础用法
移除所有 2
并复制到新容器:
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <iterator> // 用于 back_inserter
int main() {
std::vector<int> src{1, 2, 3, 2, 5, 2, 6};
std::vector<int> dst;
// 复制所有不等于 2 的元素到 dst
std::ranges::remove_copy(src, std::back_inserter(dst), 2);
// 输出结果
for (int num : dst) {
std::cout << num << " "; // 输出:1 3 5 6
}
}
输出:
1 3 5 7
示例 2:使用投影(Projection)
复制所有非空字符串到新容器
#include <vector>
#include <string>
#include <algorithm>
#include <ranges>
#include <iterator>
int main() {
std::vector<std::string> src{"Hello", "", "World", "", "C++"};
std::vector<std::string> dst;
// 投影:将字符串转换为长度,移除长度为 0 的元素
std::ranges::remove_copy(
src,
std::back_inserter(dst),
0, // 要移除的投影值(长度=0)
[](const std::string& s) { // 投影函数:返回字符串长度
return s.size();
}
);
}
此时,dst 内容:Hello, World, C++
示例 3:结构体过滤
复制所有年龄 ≥18 的 Person
对象:
#include <vector>
#include <algorithm>
#include <ranges>
#include <iterator>
struct Person {
std::string name;
int age;
};
int main() {
std::vector<Person> src{
{"Alice", 30}, {"Bob", 15}, {"Charlie", 20}
};
std::vector<Person> dst;
// 移除年龄 <18 的人(保留年龄 >=18 的)
std::ranges::remove_copy(
src,
std::back_inserter(dst),
18, // 要比较的阈值
[](const Person& p) { // 投影:提取年龄
return p.age;
},
std::ranges::less{} // 比较方式:若 age < 18 则移除
);
// dst 内容:Alice (30), Charlie (20)
}
应用场景
- 数据备份:保留原始数据,生成过滤后的副本。
- 流水线处理:与其他范围适配器(如
views::filter
)结合使用。 - 条件导出:将满足条件的数据导出到文件、网络或另一个容器。
注意事项
- 若目标范围空间不足,需使用
std::back_inserter
或提前reserve()
。 - 投影函数应轻量,避免副作用(如修改元素或外部状态)。
- 若需要自定义条件(非简单值比较),改用
std::ranges::remove_copy_if
。
std::ranges::remove_copy_if
C++20 引入的算法,用于将输入范围中不满足条件的元素复制到目标范围,同时跳过满足条件的元素。它是 std::remove_copy_if
的范围化版本,更适配现代 C++ 的范围操作。
template< std::input_iterator I, std::sentinel_for<I> S, std::weakly_incrementable O, class Proj = std::identity, | (3) | (since C++20) |
template< ranges::input_range R, std::weakly_incrementable O, class Proj = std::identity, |
-
参数:
first, last
或r
:输入范围。result
:目标范围的起始迭代器。pred
:一元谓词,若元素满足条件(返回true
),则跳过该元素。proj
:投影函数(可选),用于对元素进行转换后再应用谓词。
-
返回值:
- 包含两个迭代器的结构体:
in
:输入范围的结束迭代器。out
:目标范围最后一个复制元素的下一个位置。
- 包含两个迭代器的结构体:
功能说明
- 遍历输入范围,对每个元素应用投影函数
proj
。 - 检查谓词
pred
:- 若
pred
返回false
,则将该元素复制到result
。 - 若
pred
返回true
,则跳过该元素。
- 若
- 返回结果迭代器,方便后续操作。
示例代码
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator> // 用于 std::ostream_iterator
int main() {
std::vector<int> src = {1, 2, 3, 4, 5, 6};
std::vector<int> dst;
// 移除所有偶数(即保留奇数)
auto is_even = [](int x) { return x % 2 == 0; };
std::ranges::remove_copy_if(src, std::back_inserter(dst), is_even);
// 输出结果
std::copy(dst.begin(), dst.end(), std::ostream_iterator<int>(std::cout, " "));
// 输出:1 3 5
std::cout << std::endl;
std::vector<int> src2 = {1, 2, 3, 4, 5, 6};
std::vector<int> dst2;
//dst2.reserve(src2.size()); std::vector<int> dst2(src2.size())之前有区别
//std::cout << dst2.size() << ", " << dst2.capacity() << std::endl;
// std::copy_if(src2.begin(), src2.end(), std::back_inserter(dst2), is_even);
dst2.reserve(src2.size());
std::ranges::copy_if(src2, std::back_inserter(dst2), is_even);
// 输出结果
std::copy(dst2.begin(), dst2.end(), std::ostream_iterator<int>(std::cout, " "));
}
关键点
- 不修改原范围:仅复制符合条件的元素到目标范围,原数据保持不变。
- 目标空间需足够:需确保
result
有足够空间(或使用std::back_inserter
动态扩展)。 - 谓词逻辑:注意谓词是“跳过满足条件的元素”,与
std::ranges::copy_if(复制满足条件的元素)正好相反。