¿Cómo crear una matriz 2D contigua en C++?

15 minutos de lectura

¿Como crear una matriz 2D contigua en C
kloop

Quiero crear una función que devuelva una matriz 2D contigua en C++.

No es un problema crear la matriz usando el comando:

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

Sin embargo, no estoy seguro de cómo devolver esta matriz como un tipo general para una función. La función es:

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

Probé el doble*[] y doble** y ambos no funcionan. No me gustaría usar double*, ya que quiero acceder a esta matriz desde el exterior como una matriz 2D.

Pregunta relacionada: ¿Cómo declaro una matriz 2d en C++ usando new?

  • “NOT_SURE_WHAT_TYPE” en C++ eso sería vector<vector<int> > 😉

    – Serguéi Kalinichenko

    21 de febrero de 2014 a las 19:37

  • Una matriz no es lo mismo que una memoria asignada dinámicamente

    – yizzlez

    21 de febrero de 2014 a las 19:37

  • La única forma de crear una matriz “2d” contigua es como una matriz de matrices.

    – Un tipo programador

    21 de febrero de 2014 a las 19:38

  • @dasblinkenlight Es lo que yo usaría también, pero lamentablemente no cumple con los requisitos de la pregunta y devuelve un fondo de memoria de la estructura como contiguo.

    – WhozCraig

    21 de febrero de 2014 a las 19:43


  • En general, si su función devuelve un puntero a una matriz de N TS, parece: T (*func())[N] { /* ... */ }. Sin embargo, no puede hacer eso en esta situación, porque el tamaño de la matriz depende de un argumento para la función.

    –Joseph Mansfield

    21 de febrero de 2014 a las 19:46

1647560109 190 ¿Como crear una matriz 2D contigua en C
pablomckenzie

Si desea crear una matriz en la que los datos sean contiguos y no desea una matriz unidimensional (es decir, desea utilizar el [][] sintaxis), entonces lo siguiente debería funcionar. Crea una matriz de punteros, y cada puntero apunta a una posición en un grupo de memoria.

#include <iostream>
#include <exception>

template <typename T>
T** create2DArray(unsigned nrows, unsigned ncols, const T& val = T())
{
   if (nrows == 0)
        throw std::invalid_argument("number of rows is 0");
   if (ncols == 0)
        throw std::invalid_argument("number of columns is 0");
   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";
   }
}

Tenga en cuenta que solo se realizan 2 asignaciones. Esto no solo es más eficiente debido a la menor cantidad de asignaciones realizadas, sino que ahora tenemos una mejor oportunidad de hacer una reversión de la memoria asignada si falla una asignación de memoria, a diferencia de la forma “tradicional” de asignar una matriz 2D en no contiguos memoria:

// The "traditional" non-contiguous allocation of a 2D array (assume N x M)
T** ptr;
ptr = new T*[N];
for (int i = 0; i < N; ++i)
   ptr[i] = new T [M]; // <<-- What happens if new[] throws at some iteration?

Si new[] lanza una excepción en algún lugar durante la operación del for bucle, debe revertir todas las llamadas exitosas a new[] eso sucedió anteriormente, eso requiere más código y agrega complejidad.

Tenga en cuenta cómo desasigna la memoria en la versión contigua: solo dos llamadas a delete[] cuando se asigna de forma contigua en lugar de una llamada de bucle delete[] por cada fila.


Además, dado que los datos están en la memoria contigua, los algoritmos, funciones, etc. que asumen que los datos están en la memoria contigua, al igual que una matriz unidimensional, ahora se pueden usar especificando el rango inicial y final para el M*N matriz:

[&array[0][0], &array[M-1][N])

Por ejemplo:

std::sort(&myArray[0][0], &myArray[M-1][N]);

ordenará toda la matriz en orden ascendente, comenzando desde el índice [0][0] hasta el último índice [M-1][N-1].

Puede mejorar el diseño haciendo de esta una verdadera clase en lugar de tener asignación/desasignación como 2 funciones separadas.


Editar: la clase no es similar a RAII, tal como dice el comentario. Lo dejo como ejercicio para el lector. Una cosa que falta en el código anterior es la comprobación de que nRows y nCols son > 0 al crear una matriz de este tipo.

Edición 2: Añadido un try-catch para garantizar que se realice una reversión adecuada de la asignación de memoria si std::bad_alloc se lanza una excepción al intentar asignar memoria.


Editar: para ver un ejemplo de matriz tridimensional de código similar al anterior, consulte esta respuesta. Se incluye un código para revertir las asignaciones si la asignación falla.


Editar: clase RAII rudimentaria agregada:

template <typename T>
class Array2D
{
    T** data_ptr;
    unsigned m_rows;
    unsigned m_cols;

    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
        }
    }

public:
    typedef T value_type;
    T** data() {
        return data_ptr;
    }

    unsigned get_rows() const {
        return m_rows;
    }

    unsigned get_cols() const {
        return m_cols;
    }

    Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {}
    Array2D(unsigned rows, unsigned cols, const T& val = T())
    {
        if (rows == 0)
            throw std::invalid_argument("number of rows is 0");
        if (cols == 0)
            throw std::invalid_argument("number of columns is 0");
        data_ptr = create2DArray(rows, cols, val);
        m_rows = rows;
        m_cols = cols;
    }

    ~Array2D()
    {
        if (data_ptr)
        {
            delete[] data_ptr[0];  // remove the pool
            delete[] data_ptr;     // remove the pointers
        }
    }

    Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols)
    {
        data_ptr = create2DArray(m_rows, m_cols);
        std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]);
    }

    Array2D(Array2D&& rhs) noexcept
    {
        data_ptr = rhs.data_ptr;
        m_rows = rhs.m_rows;
        m_cols = rhs.m_cols;
        rhs.data_ptr = nullptr;
    }

    Array2D& operator=(Array2D&& rhs) noexcept
    {
        if (&rhs != this)
        {
            swap(rhs, *this);
            rhs.data_ptr = nullptr;
        }
        return *this;
    }

    void swap(Array2D& left, Array2D& right)
    {
        std::swap(left.data_ptr, right.data_ptr);
        std::swap(left.m_cols, right.m_cols);
        std::swap(left.m_rows, right.m_rows);
    }

    Array2D& operator = (const Array2D& rhs)
    {
        if (&rhs != this)
        {
            Array2D temp(rhs);
            swap(*this, temp);
        }
        return *this;
    }

    T* operator[](unsigned row)
    {
        return data_ptr[row];
    }

    const T* operator[](unsigned row) const
    {
        return data_ptr[row];
    }

    void create(unsigned rows, unsigned cols, const T& val = T())
    {
        *this = Array2D(rows, cols, val);
    }
};

int main()
{
    try
    {
        Array2D<double> dPtr(10, 10);
        std::cout << dPtr[0][0] << " " << dPtr[1][1] << "\n";
    }
    catch (std::exception& ex)
    {
        std::cout << ex.what();
    }
}
 

  • +1: Inventivo. No es muy parecido a RAII, pero hace el trabajo.

    – Carreras de ligereza en órbita

    23 de febrero de 2014 a las 18:07

  • ¿Es esto lo que std::vector ¿Detrás de escena o sería esto mucho más rápido?

    usuario11383585

    9 de mayo de 2021 a las 9:57

  • Además, me pregunto si sería una mejor solución ser T* arr = new T[row*col] y luego, en lugar de hacer que cada fila apunte a un bloque de memoria, haga que el [] operador hacer algunos cálculos para determinar qué ubicación en la memoria es? (Sé que esto es más o menos lo que se hace, pero hacer esto solo requeriría 1 asignación y 1 eliminación, excepto que el acceso a los elementos será más lento)

    usuario11383585

    9 de mayo de 2021 a las 10:53

  • La respuesta dada aquí es proporcionar una mejor manera de comenzar con un T** y creando una matriz 2D. Usando un T* no es el objetivo de la respuesta dada. por supuesto, un T* o un std::vector<T> Sería mucho mejor, pero de nuevo, a veces cuando un T** tiene que ser utilizado, hay formas mucho mejores de construir una matriz 2D a partir de ella que el enfoque tradicional de asignar n buffers en un bucle.

    – Paul McKenzie

    9 mayo 2021 a las 15:00


1647560109 339 ¿Como crear una matriz 2D contigua en C
Serguéi Kalinichenko

A menos que se conozca el tamaño de las dos dimensiones en el momento de la compilación, no tiene muchas opciones: asignar una única rows*cols gama de ints, y haga rodar su propia indexación 2D con multiplicación y suma de enteros. Envolver esto en una clase puede producir una sintaxis atractiva para acceder a los elementos de la matriz con el operador de corchetes. Dado que su matriz es 2D, deberá usar objetos proxy (también conocido como “sustituto”) para el primer nivel de acceso a los datos.

Aquí hay un pequeño código de muestra que usa std::vector<T> para mantener una región de memoria contigua en memoria dinámica:

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);
    }
};

Ahora puedes usar Array2D<int> como si fuera una matriz de C++ incorporada:

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;
    }
}

Ejecutando demostración en ideone.

  • +1 A veces reflexiono sobre cuántos ingenieros en un día determinado finalmente piensan en este problema, intentan resolverlo y luego se dan cuenta de que es esta respuesta.

    – WhozCraig

    21 de febrero de 2014 a las 19:46


  • división/módulo es innecesaria. Pasas i,j a través del operador() y linealizas al índice.

    – Joel Falcou

    21 de febrero de 2014 a las 19:48

  • @DieterLücking Porque es la sintaxis de C++ para acceder a una matriz, a diferencia de la sintaxis de C++ para llamar a una función.

    – Serguéi Kalinichenko

    21 de febrero de 2014 a las 20:01


  • operator() es la semántica clásica para acceso multiíndice. Una matriz es una función de alguna manera de todos modos.

    – Joel Falcou

    21 de febrero de 2014 a las 20:08

  • @dasblinkenlight bueno, una matriz es un objeto al que alimenta dos enteros para obtener un valor. Por lo tanto, ti puede verse como (int x int) -> función int. Lo bueno con () es que se escala correctamente con un número de dimensión arbitrario y no requiere un envoltorio falso.

    – Joel Falcou

    21 de febrero de 2014 a las 20:20

1647560110 94 ¿Como crear una matriz 2D contigua en C
eulelie

Ya que está usando C++ y no C, recomendaría usar un vector en lugar de perder el tiempo con new/delete.

Puede definir un bloque contiguo de memoria como este:

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

Y ahora accede a este vector en forma de matriz 2d con la fórmula i*n + j, siendo i el índice de fila, j el índice de columna y n la longitud de una fila:

my_matrix[i*n + j];

Eso es lo mismo que acceder a una matriz 2d con matriz[i][j]. Pero ahora tiene la ventaja de un bloque contiguo de memoria, no necesita preocuparse por la creación/eliminación y puede compartir y devolver fácilmente este objeto vectorial con funciones.

manejar recursos de memoria sin procesar suele ser complicado. Best shot es un envoltorio simple como:

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_;
}

la herencia privada le permite tomar todas las ventajas del vector, solo agregue su constructor 2D. La gestión de recursos es gratuita ya que vector ctor/dtor hará su magia. Obviamente, el i+h*j se puede cambiar al orden de almacenamiento que desee.

vector< vector< int > > es 2D pero no será contiguo en la memoria.

Su función entonces se convierte en:

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

EDITAR:

También puede recuperar otras partes de la interfaz vectorial como comienzo/fin o tamaño con la cláusula usign para hacer que las funciones de miembro privadas heredadas vuelvan a ser públicas.

¿Como crear una matriz 2D contigua en C
chris dibujó

En mi opinión, ninguna de las formas de definir una matriz dinámica 2D en C++ estándar es completamente satisfactoria.

Terminas teniendo que lanzar tus propias soluciones. Por suerte ya hay una solución en Boost. impulso::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;
}

Demo en vivo.

1647560111 616 ¿Como crear una matriz 2D contigua en C
huserben

La clase “Rudimentary RAll” proporcionada por PaulMcKenzie es una solución excelente. Al usarlo, encontré una pérdida de memoria que se solucionó en la versión que se muestra a continuación.

La pérdida de memoria se debió a un problema con
Array2D& operator=(Array2D&& rhs) noexcept.

La declaración rhs.m_dataPtr = nullPtr necesitaba eliminarse para permitir que el destructor rhs elimine los datos originales (grupo y punteros) intercambiados de lhs.

Aquí está el código corregido para la clase “Rudimentary RAll” proporcionada por PaulMcKenzie

template <typename T>
class Array2D
{
    T** data_ptr;
    unsigned m_rows;
    unsigned m_cols;

    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
        }
    }

public:
    typedef T value_type;
    T** data() {
        return data_ptr;
    }

    unsigned get_rows() const {
        return m_rows;
    }

    unsigned get_cols() const {
        return m_cols;
    }

    Array2D() : data_ptr(nullptr), m_rows(0), m_cols(0) {}
    Array2D(unsigned rows, unsigned cols, const T& val = T())
    {
        if (rows == 0)
            throw std::invalid_argument("number of rows is 0");
        if (cols == 0)
            throw std::invalid_argument("number of columns is 0");
        data_ptr = create2DArray(rows, cols, val);
        m_rows = rows;
        m_cols = cols;
    }

    ~Array2D()
    {
        if (data_ptr)
        {
            delete[] data_ptr[0];  // remove the pool
            delete[] data_ptr;     // remove the pointers
        }
    }

    Array2D(const Array2D& rhs) : m_rows(rhs.m_rows), m_cols(rhs.m_cols)
    {
        data_ptr = create2DArray(m_rows, m_cols);
        std::copy(&rhs.data_ptr[0][0], &rhs.data_ptr[m_rows-1][m_cols], &data_ptr[0][0]);
    }

    Array2D(Array2D&& rhs) noexcept
    {
        data_ptr = rhs.data_ptr;
        m_rows = rhs.m_rows;
        m_cols = rhs.m_cols;
        rhs.data_ptr = nullptr;
    }

    Array2D& operator=(Array2D&& rhs) noexcept
    {
        if (&rhs != this)
        {
            swap(rhs, *this);
        }
        return *this;
    }

    void swap(Array2D& left, Array2D& right)
    {
        std::swap(left.data_ptr, right.data_ptr);
        std::swap(left.m_cols, right.m_cols);
        std::swap(left.m_rows, right.m_rows);
    }

    Array2D& operator = (const Array2D& rhs)
    {
        if (&rhs != this)
        {
            Array2D temp(rhs);
            swap(*this, temp);
        }
        return *this;
    }

    T* operator[](unsigned row)
    {
        return data_ptr[row];
    }

    const T* operator[](unsigned row) const
    {
        return data_ptr[row];
    }

    void create(unsigned rows, unsigned cols, const T& val = T())
    {
        *this = Array2D(rows, cols, val);
    }
};

int main()
{
    try
    {
        Array2D<double> dPtr(10, 10);
        std::cout << dPtr[0][0] << " " << a2[0][0] << "\n";
    }
    catch (std::exception& ex)
    {
        std::cout << ex.what();
    }
}

1647560111 552 ¿Como crear una matriz 2D contigua en C
dali zhang

Creo que debería escribir una clase simple para envolver una matriz de 1 dimensión. Luego, puede implementar una matriz de 2 dim con la sobrecarga del operador () para obtener valores y deconstruir la función para liberar la memoria. Código de la siguiente manera:

#include <assert.h>

template <typename T>
class Array_2D
{
private:
    T *data_inside;
public:
    int size[2];
    Array_2D(int row, int column);
    ~Array_2D();

    // 
    T operator()(int index1, int index2){
        return data_inside[get_index(index1, index2)];
    }

    int get_index(int index1, int index2){
        if(index1>=0 and index1<size[0] and index2>=0 and index2<=size[1]){
            return index1*size[0] + index2;
        }else{
            assert("wrong index for array!" == "True");
        }
    }

};

template <typename T>
Array_2D<T>::Array_2D(int row, int column)
{
    size[0] = row;
    size[1] = column;
    data_inside = new T[row*column];
}

template <typename T>
Array_2D<T>::~Array_2D()
{
    // 使用析构函数,自动释放资源
    delete[] data_inside;
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad