Трехмерные массивы целых чисел в C++
Я хотел бы узнать безопасные способы реализации трехмерных массивов целых чисел в C++, используя арифметику указателя / динамическое выделение памяти, или, альтернативно, используя STL
методы, такие как векторы.
по сути, я хочу, чтобы мои целочисленные размеры массива выглядели так:
[ x ][ y ][ z ]
X и y находятся в диапазоне 20-6000 z известен и равен 4.
7 ответов
посмотрите на 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++;
}
}
}
}
каждая пара квадратных скобок является операцией разыменования (при применении к указателю). Например, следующие пары строк кода эквивалентны:
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;
Ниже приведен простой способ создания 3D-массивов с использованием C или C++ в одном куске памяти для каждого массива. Нет необходимости использовать 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 делают это хорошо, поэтому нет необходимости изобретать колесо. Я по-прежнему считаю интересным знать, что это можно легко сделать.
с векторами:
std::vector< std::vector< std::vector< int > > > array3d;
каждый элемент доступен с array3d[x] [y] [z], если элемент уже добавлен. (например, через push_back)
следует отметить, что во всех отношениях вы имеете дело только с 2D-массивом, потому что известно третье (и наименее значимое) измерение.
использование STL или Boost - довольно хорошие подходы, если вы заранее не знаете, сколько записей у вас будет в каждом измерении массива, потому что они дадут вам динамическое распределение памяти, и я рекомендую любой из этих подходов, если ваш набор данных должен оставаться в основном статическим, или если он в основном только получает новые записи и не так много удалений.
однако, если вы знаете что-то о своем наборе данных заранее, например, примерно, сколько элементов в общей сложности будет сохранено, или если массивы должны быть редко заполнены, вам может быть лучше использовать какую-то функцию хэша/ведра и использовать индексы XYZ в качестве ключа. В этом случае, предполагая не более 8192 (13 бит) записей на измерение, вы можете обойтись 40-битным (5-байтовым) ключом. Или, предполагая, что всегда есть записи 4 x Z, вы бы просто используйте 26-битный ключ XY. Это один из наиболее эффективных компромиссов между скоростью, использованием памяти и динамическим распределением.
есть много преимуществ в использовании STL для управления памятью по сравнению с использованием new / delete. Выбор способа представления данных зависит от того, насколько вы планируете ее использовать. Одним из предложений будет класс, который скрывает решение о реализации и предоставляет трехмерные методы get/set одномерному вектору STL.
Если вы действительно считаете, что вам нужно создать пользовательский тип 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;
};
предложение Питера, конечно, хорошо, но одна вещь, которую вы должны иметь в виду, заключается в том, что в случае создания больших массивов это может быть довольно медленно. Каждый раз, когда векторная емкость изменяется, все данные должны быть скопированы вокруг ("n" векторов векторов).