【问题标题】:range-based for on multi-dimensional array基于范围的多维数组
【发布时间】:2014-08-10 14:50:30
【问题描述】:

我的嵌入式系统有一个支持 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;
}

我不想要嵌套循环,除非它保证编译器会将它们展平。

(FWIW,编译器是 TI Code Composer Studio 6.0.0 附带的 GNU 4.7.4 Linaro g++ ARM 交叉编译器)

【问题讨论】:

  • @πάνταῥεῖ:使用 std::array,我可以编写 for( p = array2.front().begin(); p &lt; array2.back().end(); ++p ),这是一个很大的改进,但仍然不如基于范围的 for
  • "我不想要嵌套循环,除非它保证编译器会将它们展平。" 如果我​​问为什么,介意吗?如果有好处,编译器将它们展平还不够吗?
  • 如果您可以使用 Boost,并且可以用 Boost.MultiArray 替换这些数组,我很确定有一种方法可以获得多维的一维 视图维数组。然后,您可以使用基于范围的for 对视图进行迭代。
  • @David:这是一个嵌入式系统。我尽量不做无端增加指令数的事情。
  • @BenVoigt 然后,大概你的编译器也是如此。否则,您选择了一个糟糕的编译器。您应该首先按照您的意思编写代码,并且只有在您有证据表明编译器出错时才担心这些细节。

标签: c++ arrays c++11 for-loop foreach


【解决方案1】:

例如,有多种方法可以打印和操作多维数组的值。

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 * 行。当我们到达第二个 for 循环时。因为 row 现在是 int * 而不是列表,所以程序将无法编译。您必须创建一个列表,然后我们才能将它传递给 ranged for 循环并使用它来迭代该列表。

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

现在我们可以使用列表进行迭代

for ( ----- : list)

【讨论】:

  • 行和列的实际数据类型是什么? (声明为自动)
  • @Mah35h 外部循环迭代arr(int[2][3])。每个内部数组(int[3])将通过复制分配给row。复制意味着int[3] 被转换为指向其第一个元素的指针。所以rowint*。仅使用auto,我们无法确定col 的类型,因为它无法编译。
【解决方案2】:
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;;

【讨论】:

  • 我不想要嵌套循环,除非它保证编译器会将它们展平。 - 对他使用的编译器有任何保证吗?
  • 我不喜欢重复大小,但看起来这种方法至少是安全的,见stackoverflow.com/a/15284276/103167
  • @Ben Voigt 与模板类相比,这种方法的一个优点是可以重新解释蚂蚁维度数组。:)
  • @Vlad:我同意这是一个不错的功能。哦,n * m可以换成sizeof a / sizeof **a
  • @Ben Voigt 或者例如 sizeof a / sizeof ***a 等等。
【解决方案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);

【讨论】:

    【解决方案4】:

    结合 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;
    }
    

    【讨论】:

    • 一点more general variant(用于n维数组)。
    • 我可以添加这个通用变体(参见上一条评论中的参考)作为答案吗?
    • @Constructor:当然你可以添加它作为答案。
    【解决方案5】:

    @Ben Voigtanswer 的更一般的变体(可应用于 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);
    

    Live example.

    【讨论】:

      【解决方案6】:

      @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;});
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-06-29
        • 1970-01-01
        • 2022-11-14
        • 1970-01-01
        • 2023-03-13
        • 2020-04-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多