C++中的三维整数数组

7
我希望了解在C++中实现三维整数数组的安全方法,可以使用指针算术/动态内存分配,或者使用STL技术,例如向量。基本上,我想让我的整数数组维度看起来像:
[ x ][ y ][ z ]

x和y的范围在20-6000之间,而z已知且等于4。


挖掘旧骨头,我知道...但为什么人们在其他高级语言中免费获得诸如多维数组之类的“基本”东西时还要使用C++?这是因为你必须使用C++以便双手被绑在背后吗?性能? - MikeMurko
嗨,MikeMurko,目前来看,手头被绑定的原因可能是最正确的。我同样乐意在Java或C#中做这样的事情。 - AndyUK
7个回答

11

请看一下 Boost 多维数组 库。以下是一个示例(改编自 Boost 文档):

#include "boost/multi_array.hpp"

int main() {
  // Create a 3D array that is 20 x 30 x 4
  int x = 20;
  int y = 30;
  int z = 4;

  typedef boost::multi_array<int, 3> array_type;
  typedef array_type::index index;
  array_type my_array(boost::extents[x][y][z]);

  // Assign values to the elements
  int values = 0;
  for (index i = 0; i != x; ++i) {
    for (index j = 0; j != y; ++j) {
      for (index k = 0; k != z; ++k) {
        my_array[i][j][k] = values++;
      }
    }
  }
}

5
以下是一种直接使用C或C ++在每个数组的一个块中创建3D数组的简单方法。无需使用BOOST(即使它很好),也不需要将分配分割为具有多个间接性的行(这通常会在访问数据时产生大的性能惩罚并且会导致内存碎片化)。
唯一要理解的事情是,不存在多维数组,只有数组的数组(的数组)。最内部的索引距离内存最远。
#include <stdio.h>
#include <stdlib.h>

int main(){

    {
        // C Style Static 3D Arrays
        int a[10][20][30];
        a[9][19][29] = 10;
        printf("a[9][19][29]=%d\n", a[9][19][29]);
    }

    {
        // C Style dynamic 3D Arrays
        int (*a)[20][30];
        a = (int (*)[20][30])malloc(10*20*30*sizeof(int));
        a[9][19][29] = 10;
        printf("a[9][19][29]=%d\n", a[9][19][29]);
        free(a);
    }

    {
        // C++ Style dynamic 3D Arrays
        int (*a)[20][30];
        a = new int[10][20][30];
        a[9][19][29] = 10;
        printf("a[9][19][29]=%d\n", a[9][19][29]);
        delete [] a;
    }

}

针对您的实际问题,由于可能存在两个未知维度,所以我的建议存在问题,因为它只允许一个未知维度。有几种方法可以解决这个问题。

好消息是,现在使用变量在C语言中已经可行,这被称为可变长度数组。您可以在这里查看详细信息。

    int x = 100;
    int y = 200;
    int z = 30;

    {
        // C Style Static 3D Arrays 
        int a[x][y][z];
        a[99][199][29] = 10;
        printf("a[99][199][29]=%d\n", a[99][199][29]);
    }

    {
        // C Style dynamic 3D Arrays
        int (*a)[y][z];
        a = (int (*)[y][z])malloc(x*y*z*sizeof(int));
        a[99][199][29] = 10;
        printf("a[99][199][29]=%d\n", a[99][199][29]);
        free(a);
    }

如果使用C ++,最简单的方法可能是使用运算符重载来保持数组语法:

    {
        class ThreeDArray {
            class InnerTwoDArray {
                int * data;
                size_t y;
                size_t z;
                public:
                InnerTwoDArray(int * data, size_t y, size_t z)
                    : data(data), y(y), z(z) {}

                public:
                int * operator [](size_t y){ return data + y*z; }
            };

            int * data;
            size_t x;
            size_t y;
            size_t z;
            public:
            ThreeDArray(size_t x, size_t y, size_t z) : x(x), y(y), z(z) {
                data = (int*)malloc(x*y*z*sizeof data);
            }

            ~ThreeDArray(){ free(data); }

            InnerTwoDArray operator [](size_t x){
                return InnerTwoDArray(data + x*y*z, y, z);
            }
        };

        ThreeDArray a(x, y, z);
        a[99][199][29] = 10;
        printf("a[99][199][29]=%d\n", a[99][199][29]);
    }

上述代码访问InnerTwoDArray存在一些间接成本(但是好的编译器可能可以优化掉),但仅使用一个内存块分配在堆上的数组。这通常是最有效的选择。
显然,即使上述代码仍然简单明了,STL或BOOST也可以很好地完成它,因此没有必要重复发明轮子。我仍然相信知道它可以很容易地完成是有趣的。

5

每一对方括号都是一次解引用操作(当应用于指针时)。例如,下面这两行代码是等价的:

x = myArray[4];
x = *(myArray+4);

 

x = myArray[2][7];
x = *((*(myArray+2))+7);

要使用您建议的语法,只需取消引用从第一次取消引用返回的值即可。
int*** myArray = (some allocation method, keep reading);
//
// All in one line:
int   value = myArray[x][y][z];
//
// Separated to multiple steps:
int** deref1 = myArray[x];
int*  deref2 = deref1[y];
int   value = deref2[z];

要分配这个数组,你只需要认识到你并没有一个三维整数数组。你有的是一个由数组组成的数组,这些数组又由数组组成,最后才是整数。

// Start by allocating an array for array of arrays
int*** myArray = new int**[X_MAXIMUM];

// Allocate an array for each element of the first array
for(int x = 0; x < X_MAXIMUM; ++x)
{
    myArray[x] = new int*[Y_MAXIMUM];

    // Allocate an array of integers for each element of this array
    for(int y = 0; y < Y_MAXIMUM; ++y)
    {
        myArray[x][y] = new int[Z_MAXIMUM];

        // Specify an initial value (if desired)
        for(int z = 0; z < Z_MAXIMUM; ++z)
        {
            myArray[x][y][z] = -1;
        }
    }
}

释放该数组的过程与分配它的过程类似:
for(int x = 0; x < X_MAXIMUM; ++x)
{
    for(int y = 0; y < Y_MAXIMUM; ++y)
    {
        delete[] myArray[x][y];
    }

    delete[] myArray[x];
}

delete[] myArray;

是的,你可以这样做,但你也可以在一个内存块中完成,并且只进行一次分配。通常使用一个内存块比许多小内存块更快,因为内存间接引用是有代价的。你展示的方法主要适用于稀疏矩阵,当不使用完整的内部数组并且可以完全避免分配它们时,这种方法非常有用。 - kriss
@kriss 我完全同意,我个人倾向于使用单个数组(为大型数据分配页面)。我在这里设置的原因是为了所请求的语法(如我所述)。访问的性能差异微不足道,但对于足够大的数组和已知的缓存行为,我肯定可以创建退化的用例(对于两者都是如此),并且初始化的性能无关紧要(如果它很重要,则应该在更早的地方进行初始化)。 - Zooba
你也可以轻松地使用海报所要求的语法在一个内存块中完成它。我发布了一个答案,展示了如何使用C++来做到这一点(有很多种方法),而可变长度数组现在是C99的一个特性(太糟糕了,它没有成为C++的一部分)。无论如何,我会给你的答案点赞,因为它运行良好。 - kriss

2

使用向量:

std::vector< std::vector< std::vector< int > > > array3d;

如果元素已经被添加(例如通过 push_back),则可以使用 array3d[x][y][z] 访问每个元素。


1

使用STL来管理内存比使用new/delete有许多优点。如何表示数据取决于您计划如何使用它。一个建议是创建一个类,隐藏实现决策,并为一维STL向量提供三维get/set方法。

如果您真的认为需要创建自定义的3D向量类型,请先调查Boost。

// a class that does something in 3 dimensions

class MySimpleClass
{
public:

  MySimpleClass(const size_t inWidth, const size_t inHeight, const size_t inDepth) :
   mWidth(inWidth), mHeight(inHeight), mDepth(inDepth)
   {
       mArray.resize(mWidth * mHeight * mDepth);
   }


  // inline for speed
  int Get(const size_t inX, const size_t inY, const size_t inZ) {
     return mArray[(inZ * mWidth * mHeight) + (mY * mWidth) + mX];
  }

  void Set(const size_t inX, const size_t inY, const size_t inZ, const int inVal) {
     return mArray[(inZ * mWidth * mHeight) + (mY * mWidth) + mX];
  }

  // doing something uniform with the data is easier if it's not a vector of vectors
  void DoSomething()
  {
     std::transform(mArray.begin(), mArray.end(), mArray.begin(), MyUnaryFunc);
  }

private:

  // dimensions of data
  size_t mWidth;
  size_t mHeight;
  size_t mDepth;

  // data buffer
  std::vector< int > mArray;
};

1

需要注意的是,就所有目的而言,您只处理一个二维数组,因为第三个(最不重要的)维度已知。

如果您事先不知道数组每个维度中将有多少条目,则使用STL或Boost是相当好的方法,因为它们将为您提供动态内存分配,并且如果数据集基本保持静态,或者仅接收新条目而不是许多删除,则我建议使用这两种方法之一。

但是,如果您事先了解有关数据集的某些信息,例如大致上将存储多少项,或者数组将被稀疏地填充,那么您最好使用某种哈希/桶函数,并将XYZ索引用作密钥。在这种情况下,假设每个维度最多有8192(13位)个条目,则可以使用40位(5字节)密钥。或者,假设始终存在4 x Z个条目,则只需使用26位XY密钥。这是速度,内存使用和动态分配之间更有效的权衡之一。


-2

Pieter的建议当然很好,但你必须记住一件事情,在构建大数组的情况下,可能会非常慢。每次向量容量更改时,所有数据都必须复制一遍('n'个向量的向量)。


你可以事先轻松地分配。 - Raindog

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