Как получить доступ к одномерным массивам с несколькими скобками для удобства чтения?
У меня огромный код, использующий трехмерный массив, управляемый указателями. Что-то вроде:
int *** tab;
tab = malloc(m*sizeof(int**));
for(i= 1..n) tab[i] = malloc(n*sizeof(int*));
... etc...
и позже к элементам обращаются с помощью:
tab[i][j][k] = ...
Но из-за специфических проблем с этой структурой я хотел бы объявить tab как непрерывный массив, но все же использовать синтаксис с 3 скобками в коде. Компилятор внутренне заменит их следующим образом:
tab[i][j][k] = ... => tab[i*m*n+j*m+k] = ...
Таким образом, доступ к массиву осуществляется только с помощью разыменования указателя. Я бы не хотел менять исходный код (без sed).
Например, я мог бы сделать это, объявив вкладку в стеке:
int tab[n][m][l];
но, к сожалению, это не работает, если m
а также n
переменные времени выполнения.
6 ответов
В С (С99 или С11) tab
массив с переменными измерениями может быть передан в качестве параметра функции, если его размеры также переданы в предыдущих параметрах. Вот пример, чтобы показать, что я имею в виду:
#include <stdio.h>
#include <stdlib.h>
int sum3d(unsigned int dim_n, unsigned int dim_m, unsigned int dim_l,
int tab[dim_n][dim_m][dim_l])
{
int total = 0;
int n, m, l;
for (n = 0; n < dim_n; n++)
{
for (m = 0; m < dim_m; m++)
{
for (l = 0; l < dim_l; l++)
{
total += tab[n][m][l];
}
}
}
return total;
}
int main(void)
{
unsigned int dim_n, dim_m, dim_l;
unsigned int n, m, l;
int tot;
dim_n = 10;
dim_m = 5;
dim_l = 4;
int (*tab)[dim_m][dim_l] = calloc(dim_n, sizeof(*tab));
if (!tab)
{
fprintf(stderr, "Memory allocation failure!\n");
exit(1);
}
for (n = 0; n < dim_n; n++)
{
for (m = 0; m < dim_m; m++)
{
for (l = 0; l < dim_l; l++)
{
tab[n][m][l] = 1;
}
}
}
printf("total = %d\n", sum3d(dim_n, dim_m, dim_l, tab));
return 0;
}
В функции sum3d
, tab
мог быть объявлен как int tab[][dim_m][dim_l]
или как int (*tab)[dim_m][dim_l]
, опуская крайнее левое измерение в обоих случаях.
C++ способ заключить 3D-массив в класс, чтобы иметь естественный метод доступа:
struct Arr3D
{
int *arr;
const int n, m, p; //size of the tab in 3 dimensions
public:
Arr3D(int n, int m, int l): n(n), m(m), p(l) {
arr = new int[n * m * p];
}
~Arr3D() {
delete[] arr;
}
int& val(int i, int j, int k) { // read-write accessor
// optionaly test for 0<=i<n...
return arr[k + p * (j + i * m)];
}
};
Вы создаете и используете массив просто с:
Arr3D * parr = new Arr3D(3,4,5); // dynamic allocation
Arr3D arr(3, 4, 5); // automatic allocation
...
arr(1,2,3) = 5;
int i = arr(2,0,1);
В качестве альтернативы, если вы хотите сохранить синтаксис tab[i][j][k]
Вы можете, если вы используете вспомогательный класс Arr2D, способный предоставить представление для двумерного массива:
struct Arr2D
{
int *arr;
const int n, m; //size of the tab in 2 dimensions
const bool is_view;
public:
Arr2D(int n, int m): n(n), m(m), is_view(false) {
arr = new int[n * m];
}
Arr2D(int *arr, int n, int m): arr(arr), n(n), m(m), is_view(true) {}
~Arr2D() {
if (! is_view) delete[] arr;
}
int * operator[] (int i) {
return arr + i * m;
}
};
struct Arr3D
{
int *arr;
const int n, m, p; //size of the tab in 3 dimensions
public:
Arr3D(int n, int m, int l): n(n), m(m), p(l) {
arr = new int[n * m * p];
}
~Arr3D() {
delete[] arr;
}
Arr2D operator[](int i) {
return Arr2D(arr + i * p * m, m, p);
}
};
Теперь вы можете просто использовать arr[i][j][k]
...
Для достижения этого вы можете сначала выделить полную "вкладку"
int* tab = malloc(sizeof(int)* i * j * k);
Который даст вам ваши данные. Этот указатель будет служить владельцем массива.
После этого вы можете создать свой "3d" массив, создав массив указателей доступа к переменной tab. Что приведет к тому, что у вас будет int*** access;
который имеет все свои внутренние указатели, указывающие на правильные части tab
позволяя вам получить доступ к tab
с помощью access
,
это предполагает, что вы придерживаетесь кода стиля C.
Если вы хотите сделать это, используя стиль C++:
Смотрите: Какой самый эффективный способ инициализации трехмерного вектора?
Этот пост является хорошей отправной точкой для того же std::vector
Вы пометили это как C и C++, так что возможны разные решения.
Решение переменного тока будет иметь форму
#include <stdlib.h> /* assumed */
int* tab = malloc(sizeof(int)*n*m*l);
tab[i*m*n+j*m+k] = 42;
free(tab); /* when done */
Это решение C технически может быть использовано в C++ (хотя и с (int *)
преобразование типа в результат malloc()
). Однако в C++ это не рекомендуется в пользу
int* tab = new int[n*m*l];
tab[i*m*n+j*m+k] = 42;
delete [] tab; // when done
Еще лучший подход C++ - использовать стандартную библиотеку
#include <vector>
std::vector<int> tab(n*m*l);
tab[i*m*n+j*m+k] = 42;
// tab will be automatically released when it passes from scope.
Конечно, все эти элементы будут иметь доступ к элементам с одним отношением. Но вычисление индексов включает в себя ряд умножений, которые также не являются особенно недорогими операциями. Было бы необходимо проверить, чтобы определить, что является более эффективным / действенным.
#define SUB(x,y,z) ((x)*m + (y))*n + (z)
. . . . .
tab[SUB(i,j,k)] = ....
Для двумерного int
массив с размерами n
Икс m
, вы можете сделать это (в C):
size_t n;
size_t m;
...
// base all sizeof() calls directly on array so it's
// trivial to change the type from int to something else
int **array = malloc( n * sizeof( *array ) );
array[ 0 ] = malloc( n * m * sizeof( **array ) );
size_t rowSize = m * sizeof( **array );
for ( size_t i = 1; ii < m; ii++ )
{
array[ i ] = array[ i - 1 ] + rowSize;
}
Расширить это до трех измерений довольно просто (как добавление проверки ошибок...)
Настоящий трюк делает все с помощью одного звонка malloc()
,