Приведение указателя с неизвестным размером массива в C ++ - PullRequest
0 голосов
/ 28 мая 2020

как я могу применить указатель void к 2d-массиву (массив указателей на массивы int), если я не знаю размер массива во время компиляции? Это как-то возможно? (Я делаю это потому, что передаю 2-мерный массив неизвестного размера забавному c. Поэтому я привел 2-мерный массив к пустому указателю, а затем в этом забавном c я хочу, чтобы он был переделан обратно.)

int i = 5;

int tab1[i][i];

//cast to void pointer
void *p = (void *)tab1;

//and recast back
int (*tab2)[5] = (int (*)[5])p;   //this is working
int (*tab3)[i] = (int (*)[i])p;   // but this is not

Ответы [ 5 ]

1 голос
/ 28 мая 2020
  1. int i = 5; int tab1[i][i]; - это VLA . Это нестандартный C ++, и его следует избегать.

  2. Массив указателей на массивы (и вектор векторов) не будет таким эффективным, как настоящий 2D-массив поскольку он больше не является непрерывным (int tab1[5][5] является настоящим 2D-массивом и хранится в памяти непрерывно, но размеры должны быть известны во время компиляции).

  3. Вы можете легко создать пользовательский класс 2D-контейнера, который будет хранить данные в непрерывном 1D vector и применять простую математику (x + y*width) для доступа к элементам.

Пример:

class Matrix {
    std::vector<int> data;
public:
    const int width;
    const int height;

    Matrix(int width, int height) : width(width), height(height), data(width*height) {}

    int operator()(int x, int y) const {
        return data[y * width + x];
    }

    int& operator()(int x, int y) {
        return data[y * width + x];
    }

};

void print(Matrix const& mat) {
    for (int y = 0; y < mat.height; y++) {
        for (int x = 0; x < mat.width; x++)
            std::cout << mat(x, y) << " ";
        std::cout << std::endl;
    }
}

int main() {
    Matrix mat(5, 5);
    mat(1, 1) = 1;
    mat(2, 2) = 2;
    mat(3, 3) = 3;

    print(mat);
}

Для удобства это перегружает оператор (). Это все еще возможно с оператором [], но для этого потребуется прокси-класс для доступа к внутренним измерениям, а также установка y перед x, поскольку размеры фактически поменяны местами.

1 голос
/ 28 мая 2020

Сначала я предлагаю не использовать размер времени выполнения для массива в C / C ++, за исключением того, что вы используете вектор STL в качестве массива. поэтому вместо:

int i = 5;

вы должны использовать:

const int i = 5;

, за исключением того, что вы используете Vector, который безопасен и лучше, чем массивы intrinsi c.

как я могу привести указатель void на 2d-массив (массив указателей на массивы int), когда я не знаю размер массива во время компиляции? Это как-то возможно?

Если говорить о массиве C intrinsi c, это невозможно!

почему нельзя? потому что компилятор C / C ++ не знает вашего размера массива, границ и т. д., поэтому, если вы преобразуете свой 2d-массив в 1d-массив, это возможно. это причина того, что массив tab2 может получить доступ к первому 5-му элементу вашего массива. на самом деле компилятор C / C ++ не может отличить guish от

int a[3][3]

от

int a[3*3]

, поэтому вы должны знать хотя бы об одном измерении вашего массива:

int main() {
    const int i = 3,j = 4;

    int tab1[i][j] = {1,2,3,4,5,6,7,8,9,10,11};

//cast to void pointer
    void *p = (void *)tab1;

    auto a = (int (*)[i][12/i])p;
    return 0;
}

В приведенном выше примере мне известно о i и общем количестве ( 12 ), и я вычисляю второе измерение. Я использую ключевое слово auto, которое очень легко определяет тип данных.

0 голосов
/ 28 мая 2020

Проблема, с которой вы столкнулись, заключается в том, что размер массива должен быть определен во время компиляции.

В вашем случае у вас есть несколько вариантов:

  • make i a constexpr как constexpr int i = 5;
  • используйте int ** вместо этого:

    int i = 5;
    
    int tab1[i][i];
    
    //cast to void pointer
    void *p = (void *)tab1;
    // cast to int **
    auto tab1_p = (int **)p;
    // use it like it was an array
    tab1_p[1][3] = 5;
    
0 голосов
/ 28 мая 2020

как я могу привести указатель void на 2d-массив (массив указателей на массивы int), когда я не знаю размер массива во время компиляции?

Вы не можете. Вы можете привести только к типу, который известен во время компиляции.

Что вы можете сделать, так это преобразовать в указатель на первый элемент первой строки: int* p = static_cast<int*>(tab1);. Затем вы можете рассматривать массив как одномерный 1 . Преобразование двумерных индексов в одномерные требует некоторой тривиальной математики: x, y -> x + y * i.


1 Если вы не возражаете против технических особенностей, что арифметика указателя c пересекает границу подмассива технически может быть запрещено стандартом. Но это правило глупо. Если вас это беспокоит, вам следует в первую очередь создать одномерный массив.

0 голосов
/ 28 мая 2020

int tab1[i][i]; - нестандартное расширение компилятора для массивов переменной длины. Лучше избегать этого, потому что он непереносимый, и с ним трудно работать, как вы видите. Вам было бы лучше:

std::vector<std::vector<int>> tab1(i, std::vector<int>(i));

Тогда ваша функция может просто взять этот вектор:

void foo(const std::vector<std::vector<int>>& array) { ....
...