C++11提供了多种迭代容器的方法。例如:
基于范围的循环
for(auto c : container) fun(c)
std::for_each
for_each(container.begin(),container.end(),fun)
然而,迭代两个(或更多)大小相同的容器以完成类似以下操作的推荐方法是什么:
for(unsigned i = 0; i < containerA.size(); ++i) {
containerA[i] = containerB[i];
}
C++11提供了多种迭代容器的方法。例如:
for(auto c : container) fun(c)
for_each(container.begin(),container.end(),fun)
然而,迭代两个(或更多)大小相同的容器以完成类似以下操作的推荐方法是什么:
for(unsigned i = 0; i < containerA.size(); ++i) {
containerA[i] = containerB[i];
}
来晚了。但是:我会对索引进行迭代。但不使用传统的for
循环,而是使用基于范围的for
循环迭代索引:
for(unsigned i : indices(containerA)) {
containerA[i] = containerB[i];
}
indices
是一个简单的包装函数,它返回一组索引的(惰性求值)范围。虽然实现很简单,但代码有点长,无法在此处展示。您可以在 GitHub 上找到这个实现。
这段代码的效率与手动编写的经典for循环一样高。
如果您的数据中经常出现这种模式,请考虑使用另一种模式,即zip
两个序列并产生一组元组范围,对应于成对的元素:
for (auto& [a, b] : zip(containerA, containerB)) {
a = b;
}
zip
的实现留给读者作为练习,但它很容易从 indices
的实现中得出。
(在 C++17 之前,你需要写以下代码来代替上面的代码:)
for (auto&& items : zip(containerA, containerB))
get<0>(items) = get<1>(items);
boost::counting_range(size_t(0), containerA.size())
。 - SebastianKcont
的值没有保存在任何地方,并且在循环内部也没有使用。 - Konrad Rudolphzip
的实现留给读者作为练习”。 - Konrad Rudolph我不明白为什么没有人提到这件事:
auto itA = vectorA.begin();
auto itB = vectorB.begin();
while(itA != vectorA.end() || itB != vectorB.end())
{
if(itA != vectorA.end())
{
++itA;
}
if(itB != vectorB.end())
{
++itB;
}
}
提示:如果容器尺寸不匹配,则您可能需要将每个容器的特定代码放入其对应的 if 块中。
auto itA = vectorA.begin();
auto itB = vectorB.begin();
for (; (itA != vectorA.end()) && (itB != vectorB.end()); (++itA, ++itB))
此外,个人认为如果它们的大小不同,最好添加一个前提条件/错误提示。 - Timwhile(itA != vectorA.end() && itB != vectorB.end())
? - undefinedstd::copy_n(contB.begin(), contA.size(), contA.begin())
对于更一般的情况,您可以使用Boost.Iterator的zip_iterator
,并使用一个小函数使其可用于基于范围的for循环。对于大多数情况,这将起作用:
template<class... Conts>
auto zip_range(Conts&... conts)
-> decltype(boost::make_iterator_range(
boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}
// ...
for(auto&& t : zip_range(contA, contB))
std::cout << t.get<0>() << " : " << t.get<1>() << "\n";
点击此处查看实时示例。
然而,为了完全实现通用性,您可能需要类似这个的东西,它将正确处理数组和用户定义类型,这些类型没有成员begin()
/end()
,但在其命名空间中具有begin
/end
函数。此外,这将允许用户通过zip_c...
函数明确获得const
访问权限。
如果您像我一样支持友好的错误提示,则可能需要这个,它会检查是否向任何zip_...
函数传递了任何临时容器,并在出现这种情况时打印友好的错误消息。
zip_range
生成的元素。 - Xeoauto&
是一个左值引用——它根本无法绑定到临时对象。另一方面,auto&&
可以绑定到临时对象或引用(左值)。 - Xeo#include <algorithm>
#include <forward_list>
#include <ranges>
#include <array>
#include <iostream>
int main()
{
auto foos = std::to_array({ 1, 2, 3, 4, 5 });
auto woos = std::to_array({ 6, 7, 8, 9, 10 });
auto fooswoos = std::views::zip(foos,woos);
for(auto [foo, woo] : fooswoos) {
woo += foo;
}
std::ranges::for_each(woos, [](const auto& e) { std::cout << e << '\n'; });
return 0;
}
我们正在构建一个特殊的“视图”。这个视图允许我们查看容器,就好像它们是其他结构一样,而不需要进行任何复制或类似的操作。使用结构化绑定,我们能够在迭代中为每个对齐元素获取一个引用,并且可以对其进行任何我们想要的操作(并且是安全的)。
有许多使用algorithm
头文件提供的多个容器执行特定操作的方法。例如,在您给出的示例中,您可以使用std::copy
而不是显式的for循环。
另一方面,除了普通的for循环外,没有内置的方法可以通用地迭代多个容器。这并不奇怪,因为有很多迭代方式。想一想:您可以使用一个步骤迭代一个容器,使用另一个步骤迭代另一个容器;或在通过到达另一个容器的末尾之前遍历一个容器时开始插入;或者每次完全通过另一个容器后使用第一个容器的一步,然后重新开始;或其他某种模式;或同时处理两个以上的容器;等等…
但是,如果您想创建自己的“for_each”样式函数,仅迭代两个容器直到最短容器的长度,您可以像这样做:
template <typename Container1, typename Container2>
void custom_for_each(
Container1 &c1,
Container2 &c2,
std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
Container1::iterator begin1 = c1.begin();
Container2::iterator begin2 = c2.begin();
Container1::iterator end1 = c1.end();
Container2::iterator end2 = c2.end();
Container1::iterator i1;
Container2::iterator i2;
for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
f(i1, i2);
}
}
显然,您可以以类似的方式制定任何类型的迭代策略。
当然,您可能会认为直接执行内部for循环比编写这样的自定义函数更容易...如果您只打算执行一两次,那么您是正确的。但好处在于这非常可重复使用。 =)
for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)
但编译器报错了。有人能解释一下为什么这是无效的吗? - David Doriafor (int x = 0, y = 0; ...)
可以工作,但是for (int x = 0, double y = 0; ...)
不行。 - wjltypename...
可以轻松地将其变成可变参数。 - wjl另一个解决方案可能是在 lambda 中捕获另一个容器的迭代器引用,并对其使用后增量运算符。例如,简单的复制操作如下:
vector<double> a{1, 2, 3};
vector<double> b(3);
auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })
ita
,然后将其递增。这很容易扩展到多个容器的情况。如果您需要同时迭代2个容器,boost range库中有一个扩展版本的标准for_each算法,例如:
#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>
void foo(int a, int& b)
{
b = a + 1;
}
int main()
{
std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
std::vector<int> contB(contA.size(), 0);
boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
// contB will be now 5,4,6,3
//...
return 0;
}
当你需要在一个算法中处理超过2个容器时,那么你就需要使用 zip 函数。
如果可能的话,我个人更喜欢使用已经在STL(即 <algorithm>
头文件中)中存在的内容。 std::transform
具有可以接受两个输入迭代器的签名。 因此,至少对于两个输入容器的情况,您可以执行以下操作:
std::transform(containerA.begin(), containerA.end(), containerB.begin(), outputContainer.begin(), [&](const auto& first, const auto& second){
return do_operation(first, second);
});
outputContainer
也可以是输入容器之一。但是有一个限制,如果您正在就地修改其中一个容器,则无法执行后更新操作。std::back_inserter(outputContainer)
作为第三个参数使用会让生活更轻松。 - marsl一个区间库提供了这个以及其他非常有用的功能。以下示例使用 Boost.Range。 Eric Niebler的rangev3 应该是一个不错的替代方案。
#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& i: boost::combine(v, l))
{
int ti;
char tc;
boost::tie(ti,tc) = i;
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
C++17将使用结构化绑定使其变得更好:
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& [ti, tc]: boost::combine(v, l))
{
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i;
^ - syam19.13.26132.0
和Windows SDK版本10.0.16299.0
)出现以下编译错误:
“错误C2679:二进制'<<':没有找到接受右操作数类型为'const boost :: tuples :: cons < const char&,boost :: fusion :: detail :: build_tuple_cons < boost :: fusion :: single_view_iterator < Sequence,boost :: mpl :: int_ <1 >>,Last,true> :: type>'的运算符(或没有可接受的转换)”。 - pooya13boost::combine
一起使用:https://dev59.com/k1MI5IYBdhLWcg3wsNm9 - Dev Null我来晚了,但是你可以使用这个(C语言风格的可变函数):
template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
std::vector<T> v = va_arg(args, std::vector<T>);
std::for_each(v.begin(), v.end(), callback);
}
va_end(args);
}
foreach<int>([](const int &i) {
// do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);
template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
std::for_each(v.begin(), v.end(), callback);
}
template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
std::for_each(v.begin(), v.end(), callback);
return foreach(callback, args...);
}
foreach([](const int &i){
// do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);
template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
for (auto &vec : list) {
std::for_each(vec.begin(), vec.end(), callback);
}
}
foreach([](const int &i){
// do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});
或者你可以像这里一样连接向量: 什么是连接两个向量的最佳方法?,然后遍历大向量。
#include <algorithm>
中的transform
怎么样? - Ankit AcharyacontainerA = containerB;
代替循环。 - emlai