对于具有多个维度的数组,我们通常需要为每个维度编写for
循环。例如:
vector< vector< vector<int> > > A;
for (int k=0; k<A.size(); k++)
{
for (int i=0; i<A[k].size(); i++)
{
for (int j=0; j<A[k][i].size(); j++)
{
do_something_on_A(A[k][i][j]);
}
}
}
double B[10][8][5];
for (int k=0; k<10; k++)
{
for (int i=0; i<8; i++)
{
for (int j=0; j<5; j++)
{
do_something_on_B(B[k][i][j]);
}
}
}
您经常在我们的代码中看到这种for-for-for
循环。如何使用宏来定义for-for-for
循环,这样我每次都不需要重写这种代码?有更好的方法吗?
答案 0 :(得分:280)
首先,您不要使用这样的数据结构。如果 你需要一个三维矩阵,你定义一个:
class Matrix3D
{
int x;
int y;
int z;
std::vector<int> myData;
public:
// ...
int& operator()( int i, int j, int k )
{
return myData[ ((i * y) + j) * z + k ];
}
};
或者,如果您想使用[][][]
进行索引,则需要operator[]
它返回一个代理。
一旦你完成了这个,如果你发现你经常这样做 如你所呈现的那样迭代,你会暴露一个迭代器 支持它:
class Matrix3D
{
// as above...
typedef std::vector<int>::iterator iterator;
iterator begin() { return myData.begin(); }
iterator end() { return myData.end(); }
};
然后你写下:
for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
// ...
}
(或只是:
for ( auto& elem: m ) {
}
如果你有C ++ 11。)
如果在这样的迭代过程中需要三个索引,那就是 可以创建一个暴露它们的迭代器:
class Matrix3D
{
// ...
class iterator : private std::vector<int>::iterator
{
Matrix3D const* owner;
public:
iterator( Matrix3D const* owner,
std::vector<int>::iterator iter )
: std::vector<int>::iterator( iter )
, owner( owner )
{
}
using std::vector<int>::iterator::operator++;
// and so on for all of the iterator operations...
int i() const
{
((*this) - owner->myData.begin()) / (owner->y * owner->z);
}
// ...
};
};
答案 1 :(得分:43)
使用宏隐藏for
循环可能会让人感到困惑,只是为了节省很少的字符。我改用range-for循环:
for (auto& k : A)
for (auto& i : k)
for (auto& j : i)
do_something_on_A(j);
如果您实际上没有修改数据,那么您可以将auto&
替换为const auto&
。
答案 2 :(得分:21)
这样的事情可以提供帮助:
template <typename Container, typename Function>
void for_each3d(const Container &container, Function function)
{
for (const auto &i: container)
for (const auto &j: i)
for (const auto &k: j)
function(k);
}
int main()
{
vector< vector< vector<int> > > A;
for_each3d(A, [](int i){ std::cout << i << std::endl; });
double B[10][8][5] = { /* ... */ };
for_each3d(B, [](double i){ std::cout << i << std::endl; });
}
为了使它成为N-ary,我们需要一些模板魔法。首先我们应该创建SFINAE结构来区分这个值还是容器。值的默认实现,以及数组和每个容器类型的特化。 @Zeta如何注意,我们可以通过嵌套iterator
类型确定标准容器(理想情况下,我们应该检查该类型是否可以与范围基础for
一起使用)。
template <typename T>
struct has_iterator
{
template <typename C>
constexpr static std::true_type test(typename C::iterator *);
template <typename>
constexpr static std::false_type test(...);
constexpr static bool value = std::is_same<
std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
>::value;
};
template <typename T>
struct is_container : has_iterator<T> {};
template <typename T>
struct is_container<T[]> : std::true_type {};
template <typename T, std::size_t N>
struct is_container<T[N]> : std::true_type {};
template <class... Args>
struct is_container<std::vector<Args...>> : std::true_type {};
for_each
的实施非常简单。默认函数将调用function
:
template <typename Value, typename Function>
typename std::enable_if<!is_container<Value>::value, void>::type
rfor_each(const Value &value, Function function)
{
function(value);
}
专业化将递归地调用自己:
template <typename Container, typename Function>
typename std::enable_if<is_container<Container>::value, void>::type
rfor_each(const Container &container, Function function)
{
for (const auto &i: container)
rfor_each(i, function);
}
瞧:
int main()
{
using namespace std;
vector< vector< vector<int> > > A;
A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
rfor_each(A, [](int i){ std::cout << i << ", "; });
// 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
std::cout << std::endl;
double B[3][3] = { { 1. } };
rfor_each(B, [](double i){ std::cout << i << ", "; });
// 1, 0, 0, 0, 0, 0, 0, 0, 0,
}
这也不适用于指针(在堆中分配的数组)。
答案 3 :(得分:17)
大多数答案只是演示了如何将C ++扭曲成难以理解的句法扩展,恕我直言。
通过定义任何模板或宏,您只需强制其他程序员理解一些混淆代码,这些代码旨在隐藏其他混淆代码。
您将强制每个读取您代码的人都具有模板专业知识,以避免您使用明确的语义来定义对象。
如果您决定使用三维数组等原始数据,只需使用它,或者定义一个为您的数据提供一些可理解含义的类。
for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
do_something_on_A(current_A);
与int矢量向量的神秘定义一致,没有明确的语义。
答案 4 :(得分:10)
#include "stdio.h"
#define FOR(i, from, to) for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to) FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)
int main()
{
TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
{
printf("i: %d, j: %d, k: %d\n", i, j, k);
}
return 0;
}
更新:我知道,你要求它,但最好不要使用它:)
答案 5 :(得分:5)
一个想法是编写一个可迭代的伪容器类,它“包含”你要索引的所有多索引元组的集合。这里没有实现,因为它需要太长时间,但想法是你应该能够写...
multi_index mi (10, 8, 5);
// The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...
for (auto i : mi)
{
// In here, use i[0], i[1] and i[2] to access the three index values.
}
答案 6 :(得分:3)
我用以下声明警告这个答案:这只有在你使用实际数组时才有效 - 它不适用于使用std::vector
的示例。
如果您对多维数组的每个元素执行相同的操作,而不关心每个项的位置,那么您可以利用数组放置在连续内存位置的事实,并对待整个作为一个大的一维数组的东西。例如,如果我们想在第二个例子中将每个元素乘以2.0:
double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0]; // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
(*begin) *= 2.0;
}
请注意,使用上述方法还允许使用一些“适当的”C ++技术:
double do_something(double d) {
return d * 2.0;
}
...
double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0]; // get a pointer to the first element
double* end = &B[3][0][0]; // get a pointer past the last element
std::transform(begin, end, begin, do_something);
我通常不会建议这种方法(更像是Jefffrey的回答),因为它依赖于为数组定义的大小,但在某些情况下它可能很有用。
答案 7 :(得分:3)
我在这里看到许多以递归方式工作的答案,检测输入是否是容器。相反,为什么不检测当前图层是否与函数采用的类型相同?它更简单,并允许更强大的功能:
//This is roughly what we want for values
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ func(input);}
//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ for(auto&& i : input) rfor_each(i, func);}
然而,这(显然)给了我们歧义错误。因此,我们使用SFINAE来检测当前输入是否适合函数
//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input))
{ return func(input);}
//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func)
{ for(auto&& i : input) rfor_each(i, func);}
现在可以正确处理容器,但编译器仍然认为这可以传递给函数的input_types不明确。所以我们使用标准的C ++ 03技巧,使它更喜欢第一个函数而不是第二个函数,也传递零,并使我们更喜欢接受和int,另一个需要...
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input))
{ return func(input);}
//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...)
{ for(auto&& i : input) rfor_each(i, func, 0);}
就是这样。六,相对简单的代码行,您可以迭代值,行或任何其他子单元,与所有其他答案不同。
#include <iostream>
int main()
{
std::cout << std::endl;
double B[3][3] = { { 1.2 } };
rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
auto write = [](double (&i)[3]) //iterate over rows
{
std::cout << "{";
for(double d : i)
std::cout << d << ", ";
std::cout << "}\n";
};
rfor_each(B, write );
};
如果你想在C ++ 11中使用更方便的语法,你可以添加一个宏。 (以下未经测试)
template<class container>
struct container_unroller {
container& c;
container_unroller(container& c_) :c(c_) {}
template<class lambda>
void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index)
//note that this can't handle functions, function pointers, raw arrays, or other complex bits
int main() {
double B[3][3] = { { 1.2 } };
FOR_NESTED(double, v, B) {
std::cout << v << ", ";
}
}
答案 8 :(得分:2)
我有点震惊的是,没有人提出一些基于算术魔法的循环来完成这项工作。 由于C. Wang正在寻找一个没有嵌套循环的解决方案,我建议一个:
double B[10][8][5];
int index = 0;
while (index < (10 * 8 * 5))
{
const int x = index % 10,
y = (index / 10) % 10,
z = index / 100;
do_something_on_B(B[x][y][z]);
++index;
}
嗯,这种方法不够优雅和灵活,因此我们可以将所有流程打包成模板函数:
template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
const int limit = X * Y * Z;
int index = 0;
while (index < limit)
{
const int x = index % X,
y = (index / X) % Y,
z = index / (X * Y);
func(xyz[x][y][z]);
++index;
}
}
此模板函数也可以以嵌套循环的形式表示:
template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
for (auto &yz : xyz)
{
for (auto &z : yz)
{
for (auto &v : z)
{
func(v);
}
}
}
}
可以使用提供任意大小的3D数组加上函数名称,让参数推导做了计算每个维度大小的艰苦工作:
int main()
{
int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
iterate_all(A, do_something_on_A);
iterate_all(B, do_something_on_B);
return 0;
}
但又一次,它缺乏灵活性,因为它只适用于3D阵列,但使用SFINAE我们可以为任意维度的数组做工作,首先我们需要一个迭代{{{ 3}} 1:
template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
for (auto &v : xyz)
{
func(v);
}
}
另一个迭代任何等级的数组,进行递归:
template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
for (auto &v : xyz)
{
iterate_all(v, func);
}
}
这允许我们迭代任意维度任意大小数组的所有维度中的所有元素。
std::vector
对于多个嵌套向量,解决方案重新采用任意维度任意大小的数组,但没有SFINAE:首先我们需要一个迭代std::vector
并调用所需函数的模板函数:
template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
for (auto &v : xyz)
{
func(v);
}
}
另一个模板函数,它迭代任何类型的向量向量并调用自己:
template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
for (auto &v : xyz)
{
iterate_all(v, func);
}
}
无论嵌套级别如何,iterate_all
都会调用矢量矢量版本,除非值矢量版本更好匹配,从而结束递归。
int main()
{
using V0 = std::vector< std::vector< std::vector<int> > >;
using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;
V0 A0 = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};
iterate_all(A0, do_something_on_A);
iterate_all(A1, do_something_on_A);
return 0;
}
我认为函数体非常简单直接......我想知道编译器是否可以展开这个循环(我几乎可以肯定大多数编译器都可以展开第一个例子)。
请参阅rank。
希望它有所帮助。
答案 9 :(得分:1)
沿着这些线使用某些东西(它的伪代码,但想法保持不变)。您将模式提取为循环一次,并且每次都应用不同的函数。
doOn( structure A, operator o)
{
for (int k=0; k<A.size(); k++)
{
for (int i=0; i<A[k].size(); i++)
{
for (int j=0; j<A[k][i].size(); j++)
{
o.actOn(A[k][i][j]);
}
}
}
}
doOn(a, function12)
doOn(a, function13)
答案 10 :(得分:1)
坚持使用嵌套的for循环!
这里提出的所有方法在可读性或灵活性方面都有缺点。
如果需要在外循环中使用内循环的结果进行处理,会发生什么?如果你需要内循环中外循环的值,会发生什么?大多数“封装”方法都失败了。
相信我,我已经看到了几次“清理”嵌套for循环的尝试,最后证明嵌套循环实际上是最干净,最灵活的解决方案。
答案 11 :(得分:0)
我使用的一种技术是模板。 E.g:
template<typename T> void do_something_on_A(std::vector<T> &vec) {
for (auto& i : vec) { // can use a simple for loop in C++03
do_something_on_A(i);
}
}
void do_something_on_A(int &val) {
// this is where your `do_something_on_A` method goes
}
然后,您只需在主代码中调用do_something_on_A(A)
即可。每个维度都会创建一次模板函数,第一次使用T = std::vector<std::vector<int>>
,第二次使用T = std::vector<int>
。
如果你想要的话,你可以使用std::function
(或C ++ 03中类似函数的对象)作为第二个参数使其更通用:
template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
for (auto& i : vec) { // can use a simple for loop in C++03
do_something_on_vec(i, func);
}
}
template<typename T> void do_something_on_vec(T &val, std::function &func) {
func(val);
}
然后称之为:
do_something_on_vec(A, std::function(do_something_on_A));
即使函数具有相同的签名,这仍然有效,因为第一个函数更适合类型中std::vector
的任何内容。
答案 12 :(得分:0)
这是一个C ++ 11实现,可以处理迭代的所有内容。其他解决方案将自己局限于具有::iterator
typedef或数组的容器:但for_each
是关于迭代的,而不是容器。
我还将SFINAE分离到is_iterable
特征中的单个点。调度(元素和迭代之间)通过标签调度完成,我发现这是一个更清晰的解决方案。
应用于元素的容器和函数都是完美转发的,允许const
和非const
访问范围和仿函数。
#include <utility>
#include <iterator>
我正在实施的模板功能。其他所有东西都可以进入细节命名空间:
template<typename C, typename F>
void for_each_flat( C&& c, F&& f );
标签调度比SFINAE更清晰。这两个分别用于可迭代对象和非可迭代对象。第一次迭代可以使用完美转发,但我很懒:
template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
for( auto&& x : std::forward<C>(c) )
for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
std::forward<F>(f)(std::forward<D>(data));
}
这是编写is_iterable
所需的一些样板文件。我在详细命名空间中对begin
和end
进行参数依赖查找。这模拟了for( auto x : y )
循环的合理性:
namespace adl_aux {
using std::begin; using std::end;
template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;
TypeSink
对于测试代码是否有效非常有用。您执行TypeSink< decltype(
代码) >
,如果code
有效,则表达式为void
。如果代码无效,SFINAE将启动并阻止专业化:
template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;
template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};
我只测试begin
。也可以进行adl_end
测试。
for_each_flat
的最终实现最终非常简单:
template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}
这是在底部的方式:随意挖掘最佳答案,这是坚实的。我只是想要一些更好的技术来使用!
答案 13 :(得分:0)
你可以像这样在一个循环中生成索引(A,B,C是维度):
int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
int a = i/(B*C);
int b = (i-((B*C)*(i/(B*C))))/C;
int c = i%C;
}
答案 14 :(得分:0)
如果你只在最内层的循环中有语句,那么你可能想要尝试的一件事 - 你关心的更多是关于代码的过于冗长的性质 - 就是使用不同的空格方案。只有当你能够足够紧凑地陈述你的for循环以便它们全部适合一行时,这才有效。
对于您的第一个示例,我将其重写为:
vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
do_something_on_A(A[k][i][j]);
}
这有点推动它,因为你在外部循环中调用函数,这相当于将语句放入其中。我删除了所有不必要的空白区域,它可能是可以通过的。
第二个例子好多了:
double B[10][8][5];
int i,j,k;
for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
do_something_on_B(B[k][i][j]);
}
这可能是你想要使用的空白约定不同,但它实现了一个紧凑的结果,但不需要除C / C ++之外的任何知识(例如宏约定),也不需要像宏这样的任何技巧。
如果您真的想要一个宏,那么您可以通过以下方式更进一步:
#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)
这会将第二个例子改为:
double B[10][8][5];
int i,j,k;
FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
do_something_on_B(B[k][i][j]);
}
并且第一个示例也更好:
vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
do_something_on_A(A[k][i][j]);
}
希望你可以很容易地告诉哪些陈述与哪些陈述有关。另外,请注意逗号,现在您不能在任何for
的单个子句中使用它们。
答案 15 :(得分:-2)
首先,你不应该使用向量向量的向量。保证每个向量具有连续的存储器,但向量向量的“全局”存储器不是(也可能不是)。您应该使用标准库类型数组,而不是C样式数组。
using std::array;
array<array<array<double, 5>, 8>, 10> B;
for (int k=0; k<10; k++)
for (int i=0; i<8; i++)
for (int j=0; j<5; j++)
do_something_on_B(B[k][i][j]);
// or, if you really don't like that, at least do this:
for (int k=0; k<10; k++) {
for (int i=0; i<8; i++) {
for (int j=0; j<5; j++) {
do_something_on_B(B[k][i][j]);
}
}
}
但更好的是,您可以定义一个简单的3D矩阵类:
#include <stdexcept>
#include <array>
using std::size_t;
template <size_t M, size_t N, size_t P>
class matrix3d {
static_assert(M > 0 && N > 0 && P > 0,
"Dimensions must be greater than 0.");
std::array<std::array<std::array<double, P>, N>, M> contents;
public:
double& at(size_t i, size_t j, size_t k)
{
if (i >= M || j >= N || k >= P)
throw out_of_range("Index out of range.");
return contents[i][j][k];
}
double& operator(size_t i, size_t j, size_t k)
{
return contents[i][j][k];
}
};
int main()
{
matrix3d<10, 8, 5> B;
for (int k=0; k<10; k++)
for (int i=0; i<8; i++)
for (int j=0; j<5; j++)
do_something_on_B(B(i,j,k));
return 0;
}
你可以更进一步,使其完全恒定,添加矩阵乘法(正确和元素),乘以矢量等。你甚至可以将它推广到不同的类型(如果你主要是我做的模板使用双打)。
您还可以添加代理对象,以便执行B [i]或B [i] [j]。它们可以返回向量(在数学意义上)和充满双重和可能的矩阵