Вопрос по c++ – Как создать непрерывный 2d массив в C ++?

6

Я хочу создать функцию, которая возвращает непрерывный 2D-массив в C ++.

Это не проблема для создания массива с помощью команды:

 int (*v)[cols] = new (int[rows][cols]);

Тем не менее, я не уверен, как вернуть этот массив в качестве общего типа для функции. Функция:

  NOT_SURE_WHAT_TYPE create_array(int rows, int cols)
  {
        int (*v)[cols] = new (int[rows][cols]);
        return v;
  }

Я пробовал двойной * [] и двойной **, и оба не работают. Я не хотел бы использовать double *, так как я хочу получить доступ к этому массиву извне как 2D-массив.

Связанный вопрос:Как мне объявить 2d массив в C ++, используя new?

«NOT_SURE_WHAT_TYPE» в C ++ это будетvector<vector<int> > ;-) dasblinkenlight
В общем, если ваша функция возвращает указатель на массивN Tс, это выглядит так:T (*func())[N] { /* ... */ }, Однако вы не можете сделать это в этой ситуации, потому что размер массива зависит от аргумента функции. Joseph Mansfield
Массив не совпадает с динамически выделяемой памятью yizzlez
Единственный способ создать непрерывный «2d» массив - это массив массивов. Some programmer dude
@dasblinkenlight Это то, что я бы тоже использовал, но, увы, он не отвечает требованиям вопроса и возвращает фон памяти структуры каксмежный. WhozCraig

Ваш Ответ

5   ответов
5

Если размер двух измерений не известен во время компиляции, у вас не будет большого выбора: выделить одинrows*cols массивints, и сверните свою собственную двумерную индексацию с целочисленным умножением и сложением Обтекание этого в классе может создать приятный синтаксис для доступа к элементам массива с помощью оператора квадратной скобки. Поскольку ваш массив 2D, вам нужно будет использовать прокси-объекты (AKA «суррогат») для первого уровня доступа к данным.

Вот небольшой пример кода, который используетstd::vector<T> для поддержания непрерывной области памяти в динамической памяти:

template<class T>
class Array2D {
    vector<T> data;
    size_t cols;
public:
    // This is the surrogate object for the second-level indexing
    template <class U>
    class Array2DIndexer {
        size_t offset;
        vector<U> &data;
    public:
        Array2DIndexer(size_t o, vector<U> &dt) : offset(o), data(dt) {}
        // Second-level indexing is done in this function
        T& operator[](size_t index) {
            return data[offset+index];
        }
    };
    Array2D(size_t r, size_t c) : data (r*c), cols(c) {}
    // First-level indexing is done in this function.
    Array2DIndexer<T> operator[](size_t index) {
        return Array2DIndexer<T>(index*cols, data);
    }
};

Теперь вы можете использоватьArray2D<int> как если бы это был встроенный массив C ++:

Array2D<int> a2d(10, 20);
for (int r = 0 ; r != 10 ; r++) {
    for (int c = 0 ; c != 20 ; c++) {
        a2d[r][c] = r+2*c+1;
    }
}

Запуск демо на Ideone.

@dasblinkenlight Почему вы настаиваете на «классической семантике» - здесь нет никакой выгоды, кроме громоздкой косвенности. Dieter Lücking
деление / по модулю не требуется. Вы передаете i, j через operator () и линеаризуете индекс. Joel Falcou
@dasblinkenlight well массив - это объект, который вы кормите двумя целыми числами, чтобы получить значение. Следовательно, ti можно рассматривать как (int x int) -> int. Приятный трюк с () заключается в том, что он правильно масштабируется с произвольным номером измерения и не требует лишней обертки. Joel Falcou
+1 Я иногда задумываюсь над тем, сколько инженеров в тот или иной день в конце концов думают об этой проблеме, пытаются решить ее, а затем приходят к пониманию, что это ответ. WhozCraig
6

обработка необработанных ресурсов памяти часто нелегка. Лучший снимок - это простая обёртка:

struct array2D : private std::vector<int>
{
  typedef  std::vector<int> base_type;

  array2D() : base_type(), height_(0), width_(0) {}
  array2D(std::size_t h, std::size_t w) : base_type(h*w), height_(h), width_(w);

  int operator()(std::size_t i, std::size_t j) const 
  { 
     return base_type::operator[](i+j*height_); 
  }

  int& operator()(std::size_t i, std::size_t j) 
  { 
     return base_type::operator[](i+j*height_); 
  }

  std::size_t rows() const { return height_; }
  std::size_t cols() const { return width_; }

  private:
  std::size_t height_, width_;
}

частное наследование позволяет вам получить все вкусности от вектора, просто добавьте ваш 2D конструктор. Управление ресурсами свободно, так как вектор ctor / dtor сделает свое дело. Очевидно, что i + h * j можно изменить на любой порядок хранения.

vector <vector <int>> является 2D, но не будет непрерывным в памяти.

Ваша функция тогда станет:

array2D create_array(int rows, int cols)
{
  return array2D(cols,rows);
}

РЕДАКТИРОВАТЬ:

Вы также можете получить другие части векторного интерфейса, такие как начало / конец или размер, с помощью предложения usign, чтобы сделать частные унаследованные функции-члены снова открытыми.

У меня просто есть привычка привыкать к ресурсам такого класса. Но да, член так же хорош. Поскольку для строк / столбцов начинается изменение, я могу добавить простую функцию-член изменения размера (h, w). Joel Falcou
Помимо частного наследования (array2D может иметь член std :: vector), это правильно, когда строки и / или столбцы не меняются +1 Dieter Lücking
5

Поскольку вы используете C ++, а не C, я бы порекомендовал использовать один вектор вместо возни с new / delete.

Вы можете определить один непрерывный блок памяти следующим образом:

std::vector<int> my_matrix(rows*cols);

И теперь вы получаете доступ к этому вектору в виде двумерного массива по формуле i * n + j, где i - индекс строки, j - индекс столбца, а n - длина строки:

my_matrix[i*n + j];

Это то же самое, что получить доступ к 2d массиву с помощью array [i] [j]. Но теперь у вас есть преимущество одного непрерывного блока памяти, вам не нужно беспокоиться о новом / удалении, и вы можете легко делиться и возвращать этот векторный объект с помощью функций.

4

На мой взгляд, ни один из способов определения 2D-динамического массива в стандарте C ++ не является полностью удовлетворительным.

Вы заканчиваете тем, что катили свои собственные решения. К счастью, в Boost уже есть решение.повышение :: multi_array:

#include "boost/multi_array.hpp"

template<typename T>
boost::multi_array<T, 2> create_array(int rows, int cols) {
  auto dims = boost::extents[rows][cols];
  return boost::multi_array<T, 2>(dims);
}

int main() {
  auto array = create_array<int>(4, 3);
  array[3][2] = 0;
}

Live демо.

22

Если вы хотите создать массив, в котором данные являются смежными, и вам не нужен одномерный массив (т. Е. Вы хотите использовать[][] синтаксис), то должно работать следующее. Он создает массив указателей, и каждый указатель указывает на позицию в пуле памяти.

#include <iostream>

template <typename T>
T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T())
{
   T** ptr = nullptr;
   T* pool = nullptr;
   try
   {
       ptr = new T*[nrows];  // allocate pointers (can throw here)
       pool = new T[nrows*ncols]{val};  // allocate pool (can throw here)

       // now point the row pointers to the appropriate positions in
       // the memory pool
       for (unsigned i = 0; i < nrows; ++i, pool += ncols )
           ptr[i] = pool;

       // Done.
       return ptr;
   }
   catch (std::bad_alloc& ex)
   {
       delete [] ptr; // either this is nullptr or it was allocated
       throw ex;  // memory allocation error
   }
}

template <typename T>
void delete2DArray(T** arr)
{
   delete [] arr[0];  // remove the pool
   delete [] arr;     // remove the pointers
}

int main()
{
   try 
   { 
      double **dPtr = create2DArray<double>(10,10);
      dPtr[0][0] = 10;  // for example
      delete2DArray(dPtr);  // free the memory
   }
   catch(std::bad_alloc& ex)
   {
      std::cout << "Could not allocate array";
   }
}

Обратите внимание, что только 2 распределения сделаны. Также обратите внимание, как вы освобождаете память. Вы можете улучшить дизайн, сделав его истинным классом, вместо того, чтобы выделять / освобождать его как две отдельные функции.

Редактировать: класс не RAII-как, как говорится в комментарии. Я оставляю это как упражнение для читателя. В коде выше отсутствует одна вещь - проверка того, что nRows и nCols> 0 при создании такого массива.

Изменить 2: Добавленоtry-catch чтобы обеспечить надлежащий откат выделения памяти, еслиstd::bad_alloc выдается исключение при попытке выделить память.

Изменить: для примера 3-мерного массива кода, аналогичного приведенному вышеувидеть этот ответ, Включен код для отката распределения в случае сбоя распределения.

+1 за использование хранилища NRC :) Joel Falcou
+1: изобретательно. Не очень RAII-как, но делает работу. Lightness Races in Orbit

Похожие вопросы