多维数组的基于范围的for循环

21

我的嵌入式系统获得了一个支持C++11的g++版本,所以我一直在清理代码。

for( uint16_t* p = array; p < (&array)[1]; ++p ) {
    *p = fill_value;
}

for( uint16_t& r : array ) {
    r = fill_value;
}

这使得文章更易读。

是否有一个基于范围的for循环可以操作array2[m][n]中的所有元素?

旧版本为:

for( int16_t* p = array2[0]; p < (&array2)[1][0]; ++p ) {
    *p = fill_value;
}

我不想使用嵌套循环,除非可以保证编译器会将它们展开。

(顺便说一句,编译器是GNU 4.7.4 Linaro g ++ ARM交叉编译器,随TI Code Composer Studio 6.0.0一起提供)


1
@πάνταῥεῖ: 使用std::array, 我可以写成 for(p = array2.front().begin(); p < array2.back().end(); ++p),这是一个很大的改进,但仍然不如基于范围的 for 循环好。 - Ben Voigt
@DavidSchwartz:我有专业知识,多维数组是连续存储的,可以按照这种方式进行迭代。但这并不意味着编译器供应商会费力地为此制作一个窥视优化。(尽管我很想看到有关任何人的证据) - Ben Voigt
@BenVoigt 我不确定这是否定义良好?也许需要一个语言律师的问题。 - M.M
@BenVoigt:这只是一条关于代码清理的一般性评论,与实际功能无关。 - Kerrek SB
我在回应“使用std :: array,我可以编写....front() .begin() ....back.end()”,我假设您指的是std :: array <std :: array <T,N>,M> - M.M
显示剩余17条评论
7个回答

16
作为一个例子,有多种方法可以打印和操作多维数组的值。
int arr[2][3] = { { 2, 3, 4 }, { 5, 6, 7} };  

第一种方法,

size_t count = 0 ; 

for( auto &row : arr)
    for(auto &col : row)
         col = count ++; 

在第一个for循环中,我们引用了两个数组。然后在第二个数组中,我们单独引用了这些子数组的3个元素。我们还将计数分配给col。因此,它会迭代到子数组的下一个元素。

第二种方法:

for( auto &row : arr)
     for( auto col : row)
          cout << col << endl; 

我们在第一个循环中使用引用,以避免数组转换为指针。
如果没有这样做(错误情况:第一个for循环不是引用),
for( auto row : arr)          // program won't compile
     for( auto col : row)

这里我们有int* in row。当到达第二个for循环时,因为row现在是int*而不是列表,程序将无法编译。你需要创建一个列表,然后才能将其传递给范围for循环并用它来迭代该列表。

vector<int> list = { *(row+0) , *(row+1) , *(row+ 2) } ;

现在我们可以使用列表进行迭代。
for ( ----- : list)

row和col的实际数据类型将是什么?(声明为auto) - Mah35h
@Mah35h 外部循环迭代 arr(int[2][3])。每个内部数组(int[3])将会以复制的方式赋值给row。以复制的方式意味着int[3]被转换成其第一个元素的指针,所以rowint*类型的。由于仅使用 auto,我们无法确定col的类型因为代码不能通过编译。 - rosshjb

8
for ( auto &a : array )
{
   for ( int &x : a ) x = fill_value;
}

编辑:您可以尝试以下操作:

const size_t n = 2;
const size_t m = 3;

int a[n][m] = { { 1, 2, 3 }, { 4, 5, 6 } };

for ( auto &x : reinterpret_cast<int ( & )[n * m]>( a ) )  x = 10;
for ( auto x : reinterpret_cast<int ( & )[n * m]>( a ) )  std::cout << x << ' ';
std::cout << std::endl;;

输出结果为:
10 10 10 10 10 10 

这种方法的优点是您可以重新解释任何多维数组,不仅限于二维数组。例如:
int a[n][m][k] = { /* some initializers */ };

for ( auto x : reinterpret_cast<int ( & )[sizeof( a ) / sizeof( ***a )]>( a ) )
{
    std::cout << x << ' ';
}
std::cout << std::endl;;

3
除非编译器可以保证展开嵌套循环,否则我不希望有嵌套循环。- 你有这个他正在使用的编译器的保证吗? - Praetorian
1
我不喜欢重复尺寸,但看起来这种方法至少是安全的,请参见 https://dev59.com/uWUp5IYBdhLWcg3wbXJf#15284276。 - Ben Voigt
@Ben Voigt 这种方法相比于模板类有一个优势,就是你可以重新解释任意维度的数组。 :) - Vlad from Moscow
@Vlad:我同意这是一个不错的特性。哦,n * m可以被替换为sizeof a / sizeof **a - Ben Voigt
@Ben Voigt 或者例如 sizeof a / sizeof ***a 等等。 - Vlad from Moscow
显示剩余6条评论

3

以下是填充任意数组(静态已知大小)的代码:

#include <algorithm>
#include <iterator>
#include <type_traits>

template <typename T>
void fill_all(T & a, typename std::remove_all_extents<T>::type v);

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::false_type);

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::true_type)
{
  for (auto & x : a)
    fill_all(x, v);
}

template <typename T>
void fill_all_impl(T & a, typename std::remove_all_extents<T>::type v, std::false_type)
{
  std::fill(std::begin(a), std::end(a), v);
}

template <typename T>
void fill_all(T & a, typename std::remove_all_extents<T>::type v)
{
  fill_all_impl(a, v, std::is_array<typename std::remove_extent<T>::type>());
}

使用示例:

int a[3][4][2];
fill_all(a, 10);

1

结合Vlad和Praetorian的答案,我决定使用:

template<typename T, size_t N, size_t M>
auto flatten(T (&a)[M][N]) -> T (&)[M*N] { return reinterpret_cast<T (&)[M*N]>(a); }

for( int16_t& r : flatten(array2) ) {
    r = fill_value;
}

1
这是一个更一般化的版本(针对n维数组)链接 - Constructor
我能把这个通用变量(请参见上一条评论中的参考)添加为答案吗? - Constructor
@构造函数:当然,你可以将其作为一个答案添加进去。 - Ben Voigt

1

这是@Ben Voigt答案的更一般的变体(适用于n维数组):

template
    <
        typename Array,
        typename Element = typename std::remove_all_extents<Array>::type,
        std::size_t Size = sizeof(Array) / sizeof(Element),
        typename FlattenedArray = Element (&)[Size]
    >
constexpr FlattenedArray Flatten(Array &a)
{
    return reinterpret_cast<FlattenedArray>(a);
}

template
    <
        typename Array,
        typename Element = typename std::remove_all_extents<Array>::type
    >
void FillArray(Array& a, Element v)
{
    for (Element& e : Flatten(a))
    {
        e = v;
    }
}

// ...

int a[2][3][5];
int d = 42;

FillArray(a, d);

实时示例.


0
一个比@Kerrek SBone更简单(且可能不那么有效)的解决方案:

#include <type_traits>

template <typename Type>
void FillArray(Type& e, Type v)
{
    e = v;
}

template <typename Type, std::size_t N>
void FillArray(Type (&a)[N], typename std::remove_all_extents<Type>::type v)
{
    for (Type& e : a)
    {
        FillArray(e, v);
    }
}

使用示例:

int a[2][3][5];

FillArray(a, 42);

一个更为一般的解决方案,允许将一个函数应用于多维数组的所有元素:

template <typename Type, typename Functor>
void ForEachElement(Type& e, Functor f)
{
    f(e);
}

template <typename Type, std::size_t N, typename Functor>
void ForEachElement(Type (&a)[N], Functor f)
{
    for (Type& e : a)
    {
        ForEachElement(e, f);
    }
}

使用示例:

int a[2][3][5];

ForEachElement(a, [](int& e){e = 42;});

-1
#include <iostream>
using namespace std;

int main() {
    int arr[3][4];
    
    // Initialising the 2D array
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 4; j++){
            arr[i][j] = i * j + 10;
        }
    }
    
    // Accessing elements of 2D array using range based for loop
    for(auto &row: arr){
        for(auto &col: row){
            cout << col << " ";
        }
        cout << endl;
    }
    
    return 0;
}

问题中说:“我不想要嵌套循环”。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接