Как получить доступ к одномерным массивам с несколькими скобками для удобства чтения?

У меня огромный код, использующий трехмерный массив, управляемый указателями. Что-то вроде:

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(),

Другие вопросы по тегам