STL算法移动范围:简单、高效的集合移动方法

一、简介

已经介绍过使用STL算法在范围(range)上实现复杂操作的各种方法。本文将重点介绍如何在STL中移动集合中的元素,以及一些常用的移动算法的实现和用法。包括批量移动元素、子范围变换和交换元素等。

移动集合中的元素在实际应用中非常常见,对于提高程序效率和减少资源占用都具有重要意义。通过合理选择移动算法,可以更加高效地操作集合中的数据,实现更灵活的功能。

二、批量移动集合中的多个元素

基本上有三种允许批量移动集合中的多个元素的STL算法:std::copystd::movestd::swap_ranges

2.1、std::copy

std::copy可能是STL列表中最简单的算法。它接受一个输入范围(以两个迭代器的形式,和现在的STL接口一样)和一个输出迭代器:

template<typename InputIterator, typename OutputIterator >
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator out);

它只是简单地将输入范围的每个元素复制到输出迭代器中,每一步增加一个元素。
在这里插入图片描述
当它的一个输入或输出没有绑定到容器时,它会变得更加微妙。例如,将输出迭代器绑定到流的情况:

std::vector<int> v = {1, 2, 3, 4, 5};

std::copy(begin(v), end(v), std::ostream_iterator<int>(std::cout));

输出:

12345

std::copy的另一个微妙之处是,如果范围的元素类型的复制构造函数满足某些条件(更准确地说,如果它是std::is_trivially_copyable), std::copy可以调用std::memmove来批量获取内存块,而不是对每个元素调用复制构造函数。

但总的来说,这不是一个非常微妙的算法。

注意,std::copy有一个对应的"_n ":std::copy_n。它以begin迭代器和大小的形式接受输入范围,而不是beginend

template<typename InputIterator, typename Size, typename OutputIterator>
OutputIterator copy_n(InputIterator first, Size count, OutputIterator out);

此外,要将范围复制到STL容器中,不仅仅只有std::move请,还有其他方法可以有效地将多个元素插入STL容器中,会有专门的文章介绍。

2.2、std::move

std::move是C++ 11带来的最基本的标准函数之一,如果还不知道,那你需要好好的重温以下C++ 11的知识了。

如果了解std::move,是否也知道std::move对范围也有重载呢?和std::copy一样,它接受两个输入迭代器和一个输出迭代器:

template<typename InputIterator, typename OutputIterator>
OutputIterator move(InputIterator first, InputIterator last, OutputIterator out);

它将输入范围的每个元素都移动到输出迭代器中:
在这里插入图片描述
这是一种不同于将迭代器移动来让STL移动元素的方法。

2.3、std::swap_ranges

顾名思义,std::swap_ranges将第一个范围中的每个元素与第二个范围中的对应元素交换:
在这里插入图片描述
注意,这两个范围不允许重叠。
有点奇怪的是,std::swap_rangestd::move 的名称不对称,也许使用 std::move_ranges 或者对 std::swap 重载会更一致。算了,尊重标准吧。

还要注意std::swap_ranges是一个“1.5 range”,也就是说它不接受第二个范围的末尾:

template<typename ForwardIterator1, typename ForwardIterator2>
ForwardIterator2 swap_ranges(ForwardIterator1 first1, ForwardIterator1 last1,
                             ForwardIterator2 first2);

它假设第二个范围至少和第一个范围一样大,所以在调用std::swap_ranges之前,需要确保这个假设是正确的。

三、在一个范围内变换子范围

上述三种算法可以将数据从一个范围传输到另一个范围。但如果这两个范围实际上是更大范围的两个子范围呢?如果这些子范围重叠呢?

3.1、std::copy_backward 向前复制

假如想要复制一个范围的子部分到该范围某个的位置,这个新位置可能位于第一个子范围的末尾之前。

例如,一个1到10的范围:
在这里插入图片描述

想将1到5的子范围向后移动3个位置:
在这里插入图片描述
第一反应可能是使用std::copy

std::copy(begin(v), begin(v) + 5, begin(v) + 3);

或者,std::copy_n

std::copy_n(begin(v), 5, begin(v) + 3);

但是,这不是这个操作的正确算法,原因是:

  • 第一个原因是,它不会得到我们期望的结果。就像下面这样:
    在这里插入图片描述
    哦~~,第一步就失去了4的值。
  • 第二个原因是标准要求输出迭代器不在[begin, end]之内。因此,std::copy实际上具有未定义行为。这有一个奇怪的含义,即禁止在自身上复制一个范围。

因此,要在一个范围内向前复制值,需要一个与std::copy相同的算法,但是向后(这听起来有点奇怪)。

这就是为什么有…std::copy_backwardstd::copy_backward类似于std::copy,不同之处在于它首先将输入范围的最后一个元素复制到输出范围的最后一个元素:
在这里插入图片描述
然后它从这里开始工作,一直到输入范围的开始:
在这里插入图片描述
这意味着指向输出范围的输出迭代器必须是它的end

template<typename BidirectionalIterator1, typename BidirectionalIterator2>
BidirectionalIterator2 copy_backward(BidirectionalIterator1 first, BidirectionalIterator1 last, 
									 BidirectionalIterator2 outLast);

所以,例子中的代码应该这样:

std::copy_backward(begin(v), begin(v) + 5, begin(v) + 8);

注意,还有std::move_backward,它移动一个范围的元素,从它的末尾开始,一直移动到它的开始。

3.2、元素倒退

与上面类似的推理,要向后移动,可以使用std::copy(或std::move)。实际上,如果std::copy_backward的输出迭代器位于输入范围的(begin, end)内,则是未定义的行为。

3.3、交换子范围

可以使用std::swap_ranges在一个范围内交换两个子范围,只要它们不重叠即可。

四、这一切太复杂了

使用std::copy_backward向前移动元素,确保所有的开始和结束迭代器都是正确的,以避免超出范围……这一切看起来都很复杂。

因此,有学者提议在C++ 20标准中增加一个std::shift_left和一个std::shift_right函数。建议的函数原型为:

template<typename ForwardIterator>
ForwardIterator shift_left(ForwardIterator first, ForwardIterator last,
                           typename std::iterator_traits<ForwardIterator>::difference_type n);

template<class ForwardIterator>
ForwardIterator shift_right(ForwardIterator first, ForwardIterator last,
                            typename std::iterator_traits<ForwardIterator>::difference_type n);

最后一个参数表示移动元素的步数,因此:

std::shift_right(begin(v), begin(v) + 5, 3);

会把范围3的前5个元素的位置移到范围下面。注意:这两个函数会移动,而不会复制元素。然而,C++ 20并没有对应的函数,但是有人已经实现了该函数,并上传在github上

五、总结

本文详细介绍了在STL中批量移动集合中的多个元素的几种常见算法,包括std::copystd::movestd::swap_ranges。通过这些算法,我们可以在不同的情况下灵活地对集合进行移动操作,实现数据的复制、移动和交换。

另外,还介绍了在一个范围内变换子范围的方法,以及如何向前复制元素、元素倒退和交换子范围。这些技巧可以帮助我们更好地处理集合中元素的移动和交换操作,提高代码的效率和可读性。

最后,虽然C++ 20标准没有引入std::shift_leftstd::shift_right函数,但可以通过自定义实现或使用第三方库来实现这些功能,以满足特定需求和提高编程效率。

在这里插入图片描述

  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lion Long

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

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

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

打赏作者

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

抵扣说明:

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

余额充值