【问题标题】:How do I best handle dynamic multi-dimensional arrays in C/C++?如何最好地处理 C/C++ 中的动态多维数组?
【发布时间】:2010-09-26 19:38:16
【问题描述】:

在 C 和/或 C++ 中操作动态(直到运行时才知道所有维度)多维数组的公认/最常用方法是什么。

我正在尝试找到最简洁的方式来完成这段 Java 代码的工作:

public static void main(String[] args){
 Scanner sc=new Scanner(System.in);
 int rows=sc.nextInt();
 int cols=sc.nextInt();
 int[][] data=new int[rows][cols];
 manipulate(data);
}

public static void manipulate(int[][] data){
   for(int i=0;i<data.length;i++)
   for(int j=0;j<data[0].length.j++){
         System.out.print(data[i][j]);       
   }    
}

(从 std_in 读取只是为了澄清直到运行时才知道尺寸)。

编辑:我注意到这个问题很受欢迎,尽管它已经很老了。我实际上不同意最高投票的答案。我认为 C 的最佳选择是使用一维数组,正如 Guge 下面所说的“您可以分配行colssizeof(int) 并通过 table[row*cols+col] 访问它。” .

C++ 有多种选择,如果您真的喜欢 boost 或 stl,那么下面的答案可能更可取,但最简单且可能最快的选择是使用 C 中的一维数组。

如果您想要 [][] 语法,C 和 C++ 中的另一个可行选择是 lillq 在底部的答案是手动构建包含大量 malloc 的数组。

【问题讨论】:

    标签: c++ c arrays multidimensional-array


    【解决方案1】:

    使用boost::multi_array

    在您的示例中,您在编译时唯一需要知道的是维数。这是文档中的第一个示例:

    #include "boost/multi_array.hpp"
    #include <cassert>
    
    int 
    main () {
      // Create a 3D array that is 3 x 4 x 2
      typedef boost::multi_array<double, 3> array_type;
      typedef array_type::index index;
      array_type A(boost::extents[3][4][2]);
    
      // Assign values to the elements
      int values = 0;
      for(index i = 0; i != 3; ++i) 
        for(index j = 0; j != 4; ++j)
          for(index k = 0; k != 2; ++k)
            A[i][j][k] = values++;
    
      // Verify values
      int verify = 0;
      for(index i = 0; i != 3; ++i) 
        for(index j = 0; j != 4; ++j)
          for(index k = 0; k != 2; ++k)
            assert(A[i][j][k] == verify++);
    
      return 0;
    }
    

    编辑:正如 cmets 中所建议的,here is a "simple" example 应用程序允许您在运行时定义多维数组大小,从控制台输入询问。 这是此示例应用程序的示例输出(使用常量编译,表示它是 3 维):

    Multi-Array test!
    Please enter the size of the dimension 0 : 4
    
    Please enter the size of the dimension 1 : 6
    
    Please enter the size of the dimension 2 : 2
    
    Text matrix with 3 dimensions of size (4,6,2) have been created.
    
    Ready!
    Type 'help' for the command list.
    
    >read 0.0.0
    Text at (0,0,0) :
      ""
    
    >write 0.0.0 "This is a nice test!"
    Text "This is a nice test!" written at position (0,0,0)
    
    >read 0.0.0
    Text at (0,0,0) :
      "This is a nice test!"
    
    >write 0,0,1 "What a nice day!"
    Text "What a nice day!" written at position (0,0,1)
    
    >read 0.0.0
    Text at (0,0,0) :
      "This is a nice test!"
    
    >read 0.0.1
    Text at (0,0,1) :
      "What a nice day!"
    
    >write 3,5,1 "This is the last text!"
    Text "This is the last text!" written at position (3,5,1)
    
    >read 3,5,1
    Text at (3,5,1) :
      "This is the last text!"
    
    >exit
    

    代码中的重要部分是我们从用户那里获取尺寸并创建数组的主要功能:

    const unsigned int DIMENSION_COUNT = 3; // dimension count for this test application, change it at will :)
    
    // here is the type of the multi-dimensional (DIMENSION_COUNT dimensions here) array we want to use
    // for this example, it own texts
    typedef boost::multi_array< std::string , DIMENSION_COUNT > TextMatrix;
    
    // this provide size/index based position for a TextMatrix entry.
    typedef std::tr1::array<TextMatrix::index, DIMENSION_COUNT> Position; // note that it can be a boost::array or a simple array
    
    /*  This function will allow the user to manipulate the created array
        by managing it's commands.
        Returns true if the exit command have been called.
    */
    bool process_command( const std::string& entry, TextMatrix& text_matrix );
    
    /* Print the position values in the standard output. */
    void display_position( const Position& position );
    
    int main()
    {
        std::cout << "Multi-Array test!" << std::endl;
    
        // get the dimension informations from the user
        Position dimensions; // this array will hold the size of each dimension 
    
        for( int dimension_idx = 0; dimension_idx < DIMENSION_COUNT; ++dimension_idx )
        {
            std::cout << "Please enter the size of the dimension "<< dimension_idx <<" : ";
            // note that here we should check the type of the entry, but it's a simple example so lets assume we take good numbers
            std::cin >> dimensions[dimension_idx]; 
            std::cout << std::endl;
    
        }
    
        // now create the multi-dimensional array with the previously collected informations
        TextMatrix text_matrix( dimensions );
    
        std::cout << "Text matrix with " << DIMENSION_COUNT << " dimensions of size ";
        display_position( dimensions );
        std::cout << " have been created."<< std::endl;
        std::cout << std::endl;
        std::cout << "Ready!" << std::endl;
        std::cout << "Type 'help' for the command list." << std::endl;
        std::cin.sync();
    
    
        // we can now play with it as long as we want
        bool wants_to_exit = false;
        while( !wants_to_exit )
        {
            std::cout << std::endl << ">" ;
            std::tr1::array< char, 256 > entry_buffer; 
            std::cin.getline(entry_buffer.data(), entry_buffer.size());
    
            const std::string entry( entry_buffer.data() );
            wants_to_exit = process_command( entry, text_matrix );
        }
    
        return 0;
    }
    

    您可以看到,要加入数组中的一个元素,这真的很简单:您只需使用 operator(),如下所示:

    void write_in_text_matrix( TextMatrix& text_matrix, const Position& position, const std::string& text )
    {
        text_matrix( position ) = text;
        std::cout << "Text \"" << text << "\" written at position ";
        display_position( position );
        std::cout << std::endl;
    }
    
    void read_from_text_matrix( const TextMatrix& text_matrix, const Position& position )
    {
        const std::string& text = text_matrix( position );
        std::cout << "Text at ";
        display_position(position);
        std::cout << " : "<< std::endl;
        std::cout << "  \"" << text << "\"" << std::endl;
    }
    

    注意:我在 VC9 + SP1 中编译了这个应用程序 - 得到了一些容易忘记的警告。

    【讨论】:

    • 应该是用于 C++ 的
    • 您还应该展示如何从 std::cin 读取尺寸(根据问题的要求)
    • 你是对的!我添加了一个示例应用程序。如果还不够清楚,请告诉我:)
    • 可以只使用int类型而不是使用“typedef array_type::index index”作为索引吗?
    【解决方案2】:

    在 C++ 中有两种表示二维数组的方法。一个比另一个更灵活。

    数组数组

    首先创建一个指针数组,然后用另一个数组初始化每个指针。

    // First dimension
    int** array = new int*[3];
    for( int i = 0; i < 3; ++i )
    {
        // Second dimension
        array[i] = new int[4];
    }
    
    // You can then access your array data with
    for( int i = 0; i < 3; ++i )
    {
        for( int j = 0; j < 4; ++j )
        {
            std::cout << array[i][j];
        }
    }
    

    这种方法的问题是你的第二维被分配了许多数组,所以不会减轻内存分配器的工作。您的内存可能会碎片化,从而导致性能下降。但它提供了更大的灵活性,因为第二维中的每个数组都可以有不同的大小。

    保存所有值的大数组

    这里的诀窍是创建一个庞大的数组来保存您需要的所有数据。困难的部分是,如果您希望能够使用 array[i][j] 语法访问数据,您仍然需要第一个指针数组。

    int* buffer = new int[3*4];   
    int** array = new int*[3];
    
    for( int i = 0; i < 3; ++i )
    {
        array[i] = array + i * 4;
    }
    

    int* 数组不是强制性的,因为您可以通过从值的二维坐标计算缓冲区中的索引来直接访问缓冲区中的数据。

    // You can then access your array data with
    for( int i = 0; i < 3; ++i )
    {
        for( int j = 0; j < 4; ++j )
        {
            const int index = i * 4 + j;
            std::cout << buffer[index];
        }
    }
    

    要记住的规则

    计算机内存是线性的,并且在很长一段时间内仍然会如此。请记住,计算机本身不支持二维数组,因此唯一的方法是将数组“线性化”为一维数组。

    【讨论】:

      【解决方案3】:

      您可以分配行colssizeof(int) 并通过 table[row*cols+col] 访问它。

      【讨论】:

        【解决方案4】:

        不使用 boost 的标准方法是使用 std::vector :

        std::vector< std::vector<int> > v;
        v.resize(rows, std::vector<int>(cols, 42)); // init value is 42
        v[row][col] = ...;
        

        这将自动处理新/删除您需要的内存。但它相当慢,因为std::vector 并不是主要设计用于这样使用它(将std::vector 相互嵌套)。例如,并非所有内存都分配在一个块中,而是为每一列单独分配。此外,行不必都是相同的宽度。 Faster 是使用一个法线向量,然后像col_count * row + col 这样进行索引计算以获取特定的行和列:

        std::vector<int> v(col_count * row_count, 42);
        v[col_count * row + col) = ...;
        

        但这将失去使用[x][y] 索引向量的能力。您还必须在某处存储行数和列数,而使用嵌套解决方案时,您可以使用v.size() 获取行数,使用v[0].size() 获取列数。

        使用 boost,您可以使用 boost::multi_array,这正是您想要的(请参阅其他答案)。


        还有使用原生 C++ 数组的原始方式。这涉及相当多的工作,并且绝不比嵌套向量解决方案更好:

        int ** rows = new int*[row_count];
        for(std::size_t i = 0; i < row_count; i++) {
            rows[i] = new int[cols_count];
            std::fill(rows[i], rows[i] + cols_count, 42);
        }
        
        // use it... rows[row][col] then free it...
        
        for(std::size_t i = 0; i < row_count; i++) {
            delete[] rows[i];
        }
        
        delete[] rows;
        

        您必须在某处存储您创建的列和行的数量,因为您无法从指针接收它们。

        【讨论】:

        • 嵌套 是实现自动管理的 Ileffe 向量的标准方法。对于锯齿状数组,嵌套并不是最简单的,也是最不头疼的方法。
        【解决方案5】:

        这是在 C 中执行此操作的简单方法:

        void manipulate(int rows, int cols, int (*data)[cols]) {
            for(int i=0; i < rows; i++) {
                for(int j=0; j < cols; j++) {
                    printf("%d ", data[i][j]);       
                }
                printf("\n");
            }
        }
        
        int main() {
            int rows = ...;
            int cols = ...;
            int (*data)[cols] = malloc(rows*sizeof(*data));
            manipulate(rows, cols, data);
            free(data);
        }
        

        这从 C99 开始完全有效,但它不是任何标准的 C++:C++ 要求数组类型的大小是编译时间常量。在这方面,C++ 现在比 C 落后了 15 年。而且这种情况不会很快改变(C++17 的可变长度数组提案并未接近 C99 可变长度数组的功能)。

        【讨论】:

          【解决方案6】:

          C 和 C++ 中的二维 C 样式数组是一块大小为 rows * columns * sizeof(datatype) 字节的内存。

          实际的 [row][column] 维度仅在编译时静态存在。运行时没有任何动态!

          因此,正如其他人所提到的,您可以实施

            int array [ rows ] [ columns ];
          

          作为:

           int  array [ rows * columns ]
          

          或如:

           int * array = malloc ( rows * columns * sizeof(int) );
          

          下一步:声明一个可变大小的数组。 在 C 中这是可能的:

          int main( int argc, char ** argv )
          {
            assert( argc > 2 );
          
            int rows    = atoi( argv[1] );
            int columns = atoi( argv[2] );
          
            assert(rows > 0 && columns > 0);
            int data [ rows ] [ columns ];  // Yes, legal!
          
            memset( &data, 0, sizeof(data) );
          
            print( rows, columns, data );
            manipulate( rows, columns, data );
            print( rows, columns, data );
          }
          

          在 C 语言中,您可以将可变大小的数组与非可变大小的数组一样传递:

          void manipulate( int theRows, int theColumns, int theData[theRows][theColumns] )
          {
            for (   int r = 0; r < theRows;    r ++ )
              for ( int c = 0; c < theColumns; c ++  )
                theData[r][c] = r*10 + c;
          }
          

          但是,在 C++ 中这是不可能的。您需要使用动态分配来分配数组,例如:

          int *array = new int[rows * cols]();
          

          或者最好(使用自动内存管理)

          std::vector<int> array(rows * cols);
          

          然后必须修改函数以接受一维数据:

          void manipulate( int theRows, int theColumns, int *theData )
          {
            for (   int r = 0; r < theRows;    r ++ )
              for ( int c = 0; c < theColumns; c ++  )
                theData[r * theColumns + c] = r*10 + c;
          }
          

          【讨论】:

            【解决方案7】:

            如果您使用 C 而不是 C++,您可能需要查看 Dave Hanson 的库 C Interfaces and Implementations 中的 Array_T 抽象。它非常干净且设计精良。我让我的学生做一个二维版本作为练习。您可以这样做,也可以简单地编写一个执行索引映射的附加函数,例如,

            void *Array_get_2d(Array_T a, int width, int height, int i, int j) {
                return Array_get(a, j * width, i, j);
            }
            

            有一个单独的结构来存储宽度、高度和指向元素的指针会更简洁一些。

            【讨论】:

              【解决方案8】:

              我最近遇到了类似的问题。我没有可用的 Boost。与普通数组相比,向量的向量变得非常慢。拥有一个指针数组会使初始化更加费力,因为您必须遍历每个维度并初始化指针,在此过程中可能有一些非常笨拙的级联类型,可能还有很多 typedef。

              免责声明:我不确定是否应该将其发布为答案,因为它只回答了您的部分问题。对于以下内容,我深表歉意:

              • 正如其他评论员所说,我没有介绍如何从标准输入读取尺寸。
              • 这主要用于 C++。
              • 我只为二维编码了这个解决方案。

              我还是决定发布这个,因为我看到在回答有关 C++ 中的多维数组的问题时经常提到向量的向量,而没有人提到它的性能方面(如果你关心的话)。

              我还将这个问题的核心问题解释为关于如何获得动态多维数组,这些数组可以像问题中的 Java 示例一样轻松使用,即无需计算索引的麻烦一个伪多维一维数组。

              我没有看到其他答案中提到的编译器扩展,例如 GCC/G++ 提供的那些用于声明具有动态边界的多维数组的方式与使用静态边界的方式相同。据我了解,这个问题并不限制标准 C/C++ 的答案。 ISO C99 显然支持它们,但在 C++ 和 C 的早期版本中,它们似乎是特定于编译器的扩展。看到这个问题:Dynamic arrays in C without malloc?

              我想出了一种人们可能会喜欢 C++ 的方法,因为它的代码少,具有内置静态多维数组的易用性,而且速度一样快。

              template <typename T> 
              class Array2D {
              private:
                  std::unique_ptr<T> managed_array_;
                  T* array_;
                  size_t x_, y_;
              
              public:
                  Array2D(size_t x, size_t y) {
                      managed_array_.reset(new T[x * y]);
                      array_ = managed_array_.get();
                      y_ = y;
                  }
                  T* operator[](size_t x) const {
                      return &array_[x * y_];
                  }
              };
              

              你可以这样使用它。尺寸不

              auto a = Array2D<int>(x, y);
              a[xi][yi] = 42;
              

              您可以添加一个断言,至少除了最后一个维度之外的所有维度,并将想法扩展到两个以上的维度。我在我的博客上发表了一篇关于获取多维数组的替代方法的文章。我对那里的相对性能和编码工作也更加具体。

              Performance of Dynamic Multi-Dimensional Arrays in C++

              【讨论】:

              • 这是否按行对齐元素?这对于缓存优化访问可能很重要,大多数程序员会假设 x 在空间上很接近。另一种方法是在代码中的所有位置切换 x,y,包括用法“a[yi][xi]”,这对程序员来说不直观,但对数学家来说正好;)
              • 非常感谢您指出这一点!内存通常应该以这样的方式布局,即对其的访问是顺序的,因此现代硬件可以预测。在这种情况下,与性能相关的 SO 上投票最多的问题之一就是:stackoverflow.com/questions/11227809/…
              【解决方案9】:

              您可以使用 malloc 来完成此操作,并且仍然可以通过普通的 array[][] 方法访问它,而不是 array[rows * cols + cols] 方法。

              main()
              {
                 int i;
                 int rows;
                 int cols;
                 int **array = NULL;
              
                 array = malloc(sizeof(int*) * rows);
                 if (array == NULL)
                     return 0;  // check for malloc fail
              
                 for (i = 0; i < rows; i++)
                 {
                     array[i] = malloc(sizeof(int) * cols)
                     if (array[i] == NULL)
                         return 0;  // check for malloc fail
                 }
              
                 // and now you have a dynamically sized array
              }
              

              【讨论】:

                【解决方案10】:

                在 C++ 中无法确定给定数组的长度。最好的方法可能是传入数组每个维度的长度,并使用它而不是数组本身的 .length 属性。

                【讨论】:

                  猜你喜欢
                  • 2010-10-11
                  • 1970-01-01
                  • 2012-02-02
                  • 1970-01-01
                  • 2018-05-16
                  • 2011-02-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多