C++的std::transform算法函数,一行代码解决数据转换!

std::transform是C++中强大的数据转换工具,本文介绍了其基本用法、在单个范围和两个范围上的操作,以及原地转换和transform_if的概念。通过实例演示了如何利用std::transform进行元素转换和筛选。
摘要由CSDN通过智能技术生成

一、简介

std::transform 是 C++ 标准库中的一个算法函数,位于 <algorithm> 头文件中。它允许对一个范围内的元素进行转换操作,并将结果存储到另一个范围中。

std::transform 的输入范围和输出范围的大小必须相等,否则行为是未定义的。同时,输出范围必须具有足够的空间来存储转换后的元素。

std::transform 的时间复杂度取决于输入范围的大小,通常为线性时间复杂度 O(N),其中 N 是输入范围的元素数量。但具体的性能表现还受到 unary_op 函数的执行时间和容器的特性影响。

二、在一个范围上 std:: transform

本质上,std::transform对范围中的每个元素应用一个函数:
在这里插入图片描述

std::transform 的函数原型:

template <class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op);

参数说明:

  • first1last1:输入范围的起始和结束迭代器。
  • d_first:输出范围的起始迭代器,用于存储转换后的结果。
  • unary_op:一元操作函数,用于对输入范围内的每个元素进行转换操作。

std::transform 对输入范围 [first1, last1) 中的每个元素调用 unary_op 函数,并将结果存储到从 d_first 开始的输出范围中。输出范围必须具有足够的空间来存储转换后的元素。

只要使用了STL,就会有std::transform的需求。例如,要获取map包含的键,可以使用std::transform

map<int, string> m = { {1,"foo"}, {42, "bar"}, {7, "baz"} };
vector<int> keys;
std::transform(m.begin(), m.end(), std::back_inserter(keys), getFirst);

其中getFirst是需要用户实现的一个(非标准)函数,它接受一个pair并返回它的第一个元素。上面使用的std::back_inserter是一个输出迭代器,每次赋值给它时,它都会向传递给它的容器执行push_back操作。这使程序员不必考虑输出的大小。

std::transform的概念非常有用,因此它有一个来自函数式编程的名字:Map(与std::map无关)。实际上,可以反过来看这个问题:STL源于函数式编程,因此函数式编程中的一个核心概念在STL中占据核心地位也就不足为奇了。

三、在两个范围上 std:: transform

std::transform 还有一个重载版本,它接受两个范围作为输入,并对输入范围中的每个元素应用一个需要两个参数的函数。换句话说,它让你可以同时处理两个容器中相同位置的元素。
在这里插入图片描述
函数原型:

template<typename InputIterator1, typename InputIterator2, typename OutputIterator, typename BinaryOperation>
OutputIterator transform(InputIterator1 first1, 
						 InputIterator1 last1,
                         InputIterator2 first2,
                         OutputIterator result,
                         BinaryOperation op);

但是,在使用此重载时需要小心,因为第二个范围至少需要与第一个范围一样长

事实上,如图和原型所示,std::transform 遍历第一个范围,并从第二个范围读取对应值。但是它没有办法知道第二个范围实际上停止在哪里。这个重载使用了所谓的“1.5-Ranges”,因为第一个范围是完全提供的,但第二个范围没有结束部分。

示例,将两个整数范围的元素相加:

vector<int> numbers1 = {1, 5, 42, 7, 8};
vector<int> numbers2 = {10, 7, 4, 2, 2};
vector<int> results;
std::transform(numbers1.begin(), numbers1.end(),
               numbers2.begin(),
               std::back_inserter(results),
               [](int i, int j) {return i+j;});

在两个范围上应用一个函数的概念也有一个来自函数式编程的名称:zip

三、原地 std::transform

输出范围可以是2个输入范围中的任何一个。在这种情况下,范围被“就地”转换。

原地std::transform在一个范围上与std::for_each有什么不同?其实,两者都对每个元素应用一个函数。

实际上有两个主要的区别,一个是技术上 的,在实践中相对不重要,另一个更重要:

  • 不重要的技术问题:从标准的角度来看,for_each提供了比transform更多的保证,即:
    • 从第一个元素到最后一个元素按顺序遍历该范围。
    • 在遍历过程中不拷贝函数(或函数对象)。
    • 因此,理论上可以通过for_each函数来控制函数对象的状态。但是一般来说,并不希望在函数对象中引入状态。
  • 重要的一点是:for_eachtransform在处理元素时有不同的行为。即:
    • for_each会在每个元素上应用一个函数,但不会改变元素本身。
    • transform则会在每个元素上应用函数,并将结果赋值给相应的元素。
    • 简单来说,for_each只是对元素执行某个操作,而transform不仅执行操作,还将操作的结果保存回原来的位置。

所以在有些情况下for_each更合适。例如,在更一般的意义上(IO输出,日志记录等),for_each应该优先考虑具有副作用,因为transform只是说…它转换您的元素。

因此,有些情况下更适合使用for_each。例如,在更广义的层面上(如IO输出、日志记录等)需要产生副作用时,应首选for_each,因为transform只是表示……它会转换你的元素。

四、transform_if 概述

如果使用 std::transform 函数很多,有可能会遇到需要对某个范围内元素的特定部分应用变换的情况。这些元素将由一个谓词来标识。

因此,基于 std::copy_if 算法的模型,该算法只复制满足谓词的元素,首先想到的是应该有一个名为transform_if的算法。但是,STL 中没有这样的算法,Boost 中也没有,其他任何地方也没有。

这本身就暗示了这种算法可能不是上述需求的最佳解决方案,而且这种解决方案确实存在一些问题:

  • 它将是一个做两件事的函数:过滤谓词和应用函数。

  • 应该以什么顺序传递谓词和函数?在某些情况下(特别是在boolint相互隐式转换的情况下),以错误的顺序传递它们可以编译通过,但不会达到想要的目的。虽然这可以通过强类型来解决。

  • 应该如何处理现有的转换?如何处理不满足谓词的元素?它们应该被保留吗?

因此,transform_if算法并不是解决这种需求的正确方法(否则是合法的)。一个优雅而强大的解决方案是使用范围:

因此,这种(合法的)需求并不适合使用 transform_if 算法来解决。一种优雅而强大的解决方案是使用范围(range):

v | filter(myPredicate) | transform(f)

range可以做transform_if想要做的事情,甚至更多。

五、总结

C++的std::transform算法函数是一个强大的工具,它能够以极简的方式解决数据转换的问题。通过这个函数,可以在一行代码中完成各种数据转换操作,无论是简单的数值运算还是复杂的自定义逻辑。本文从介绍函数的基本用法开始,逐步展示了如何灵活运用std::transform函数来实现不同类型的数据转换。

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值