C++之STL的algorithm(1)之遍历算法(for_each、transform)整理、函数对象与谓词回顾
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
C++ 的遍历算法整理
提示:本文为 C++ 中for_each、transform 的写法和举例
一、遍历算法
std::for_each 和 std::transform
是C++标准模板库(STL)中用于遍历容器
元素的两个非常有用的算法,这两个算法均会遍历每个元素并执行对每个元素执行一个函数,for_each仅调用,transform会将函数结果存入新容器,能够极大地简化对容器元素的遍历和转换操作。通过传入使用不同的【函数对象】或【lambda表达式】,可以实现各种复杂的操作。下面详细解释这两个算法的参数和语法。
1、for_each
std::for_each
用于遍历容器中的元素,并对每个元素均执行同一个操作。该操作由传入的函数决定。
语法格式:
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f);
参数:
first
:指向要遍历序列的起始位置的迭代器。
last
:指向要遍历序列结束位置之后的下一个位置的迭代器。
f
:一个函数或者函数对象(可以是函数、仿函数、谓词、lambda表达式),它对每个遍历到的元素执行操作。
返回值:
std::for_each 返回的是传入的函数对象 f,这使得可以方便地链式调用。
遍历示例:对一个vector每个元素执行打印操作
#include <iostream>
#include <vector>
#include <algorithm>
void print_element(int n) {
std::cout << n << " ";
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), print_element);
return 0;
}
在这个例子中,std::for_each
遍历 vec 容器中的每个元素,并对每个元素调用 print_element
函数,打印出每个元素的值。
2、transform
std::transform
用于将一个容器的元素转换为另一个容器序的元素。转换的方式由一个传入的函数决定,会对每个元素执行同一个变换,将变换后的数据存入新容器,且需要我们提前给目标容器分配好内存。
一元操作是对一个容器做变换得到数据,例如求每个元素的绝对值,二元操作是对两个容器的数据做变换得到数据,例如求二维坐标向量的模长距离
语法格式:
template<class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperation unary_op);
template<class InputIterator1, class InputIterator2, class OutputIterator, class BinaryOperation>
OutputIterator transform(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, BinaryOperation binary_op);
返回值:std::transform 返回的是指向目标序列中最后一个写入元素的下一个位置的迭代器。
参数:
对于一元操作版本:
first1
:指向源序列的起始位置的迭代器。
last1
:指向源序列结束位置之后的一个位置的迭代器。
result
:指向目标序列的起始位置的迭代器
,用于存储转换后的结果。
unary_op
:一个一元函数或者函数对象,它对每个遍历到的元素执行转换操作。
对于二元操作版本:
first1
:指向第一个源序列的起始位置的迭代器。
last1
:指向第一个源序列结束位置之后的一个位置的迭代器。
first2
:指向第二个源序列的起始位置的迭代器。
result
:指向目标序列的起始位置的迭代器,用于存储转换后的结果。
binary_op
:一个二元函数或者函数对象,它根据两个序列中对应的元素执行转换操作。
transform示例(一元操作版本):
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int double_value(int n) {
return n * 2;
}
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> destination(source.size());
std::transform(source.begin(), source.end(), destination.begin(), double_value);
for (int n : destination) {
std::cout << n << " ";
}
return 0;
}
在这个例子中,std::transform
遍历 source 容器中的每个元素,调用 double_value
函数将每个元素的值翻倍,并将变换结果存储到 destination
容器中。
注意:在使用 std::transform 时,需要确保预先非陪的目标容器 destination 足够大,以容纳转换后的所有元素。
transform示例(二元操作版本):
std::transform
的二元操作模式允许根据两个序列中对应元素的值来生成一个新序列。一个常见的应用是计算两点之间的距离(或模长),特别是当你有一系列点(例如二维或三维空间中的点)时。
假设有一个点的结构体 Point
,包含 x 和 y 坐标,你想要计算每个点的模长(即距离原点的距离)。这可以通过 std::transform 的二元操作版本来实现,尽管在这种情况下,实际上我们只需要一元操作,因为模长只依赖于点本身的坐标,不依赖于其他点。不过,为了展示二元操作的用法,我们可以假设我们有两个点序列,并想要计算每对点之间的距离。
首先,定义 Point 结构体和计算两点间距离的函数:
#include <iostream>
#include <vector>
#include <numeric> // for std::transform
#include <cmath> // for std::sqrt, std::pow
struct Point {
double x;
double y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
};
double distance(const Point& a, const Point& b) {
return std::sqrt(std::pow(a.x - b.x, 2) + std::pow(a.y - b.y, 2));
}
然后,使用 std::transform 的二元操作版本来计算两个 Point 序列中对应点之间的距离,并将结果存储在一个 double 类型的向量中:
int main() {
// 假设我们有两个点的序列
std::vector<Point> points1 = {{1, 2}, {3, 4}, {5, 6}};
std::vector<Point> points2 = {{6, 8}, {2, 3}, {0, 9}};
// 用于存储每对点之间距离的向量
std::vector<double> distances(points1.size());
// 使用std::transform计算每对点之间的距离
std::transform(points1.begin(), points1.end(), points2.begin(), distances.begin(), distance);
// 打印结果
for (double d : distances) {
std::cout << d << std::endl;
}
return 0;
}
在这个例子中,std::transform 遍历 points1 和 points2 中的每对点,调用 distance 函数计算它们之间的距离,并将结果存储在 distances 向量中。注意,std::transform 要求两个输入序列的大小相同,并且结果序列的大小也应该相同。如果大小不匹配,行为将是未定义的。
3、关联式容器使用遍历算法
在C++中,std::for_each
和 std::transform
算法是泛型算法,它们可以在任何满足迭代器要求的容器上使用。这意味着,只要容器提供了合适的迭代器类型(例如begin()
和 end()
成员函数),就可以使用这些算法。
对于 std::map
和 std::set
,它们都是关联式容器,提供了双向迭代器,因此可以使用 std::for_each
和 std::transform
。不过,对于 std::transform,由于 std::map 和 std::set 中的元素是键值对(std::pair<Key, Value>
),所以你需要确保你的转换函数能够正确处理这种类型。
for_each
下面是一个使用 std::for_each 遍历 std::map 的示例:
#include <iostream>
#include <map>
#include <algorithm>
#include <functional> // for std::ref
void print_pair(const std::pair<int, std::string>& p) {
std::cout << p.first << ": " << p.second << std::endl;
}
int main() {
std::map<int, std::string> my_map = {{1, "one"}, {2, "two"}, {3, "three"}};
std::for_each(my_map.begin(), my_map.end(), print_pair);
return 0;
}
在这个例子中,print_pair 函数接收一个 std::pair<int, std::string> 类型的参数,并打印出键和值。std::for_each 遍历 my_map 中的每个元素,并对每个元素调用 print_pair 函数。
transform
对于 std::transform
,由于 std::map 的元素是键值对,你可能需要编写一个特定的转换函数来处理这种类型。不过,更常见的做法是对 std::map 的键或值进行转换,然后将结果存储到另一个容器中。以下是一个示例,它将 std::map 中的值(字符串)转换为大写,并存储到另一个 std::vector<std::string>
中:
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <cctype> // for std::toupper
#include <iterator> // for std::back_inserter
std::string to_upper(const std::string& s) {
std::string upper_s;
for (char c : s) {
upper_s.push_back(std::toupper(c));
}
return upper_s;
}
int main() {
std::map<int, std::string> my_map = {{1, "one"}, {2, "two"}, {3, "three"}};
std::vector<std::string> upper_strings;
std::transform(my_map.begin(), my_map.end(), std::back_inserter(upper_strings),
[](const std::pair<int, std::string>& p) { return to_upper(p.second); });
for (const std::string& s : upper_strings) {
std::cout << s << std::endl;
}
return 0;
}
在这个例子中,我们定义了一个 to_upper 函数来将字符串转换为大写,并使用 std::transform 和一个lambda表达式来遍历 my_map,并将每个值的大写形式添加到 upper_strings 向量中。std::back_inserter
是一个迭代器适配器
,它会在每次插入时调用容器的 push_back 方法。