Matriz dinámica en C++

Un problema muy común de programación en C++ es la creación de una matriz (arreglo bidimensional) que sea de un tamaño que se determine en tiempo de ejecución. Los arreglos en memoria fija de C++ deben tener dimensiones que se puedan determinar durante la compilación, por lo que no es posible utilizar una declaración como la siguiente para lograrlo.

int m, n;
cin >> m >> n;
int arreglo[m][n]; // ¡no funciona!

El compilador no puede conocer de antemano el valor de las variables m y n, por lo que asume un valor de cero y no es el resultado esperado. Para solucionar esta situación es necesario hacer uso de memoria dinámica.

Los arreglos en C++ son equivalentes a un puntero al tipo de los elementos en el arreglo, esto es una característica heredada del lenguaje C. Esto se aprovecha para poder crear arreglos de un tamaño que se determina en tiempo de ejecución.

Por ejemplo, para crear un arreglo de enteros de una dimensión y con un tamaño que se reciba de la consola durante la ejecución, puede hacerse de la siguiente manera.

int n;
cin >> n;
int *arreglo = new int[n];

Pero para crear un arreglo bidimensional el asunto no es tan simple como lo fue para el arreglo de una dimensión, ya que lo siguiente no es permitido.

int m, n;
cin >> m >> n;
int *arreglo = new int[m][n]; // ¡tampoco funciona!

Para lograr crear la matriz bidimensional en memoria dinámica es necesario comprender que la misma va a estar formada por dos partes: primero, un arreglo de punteros y segundo, una serie de arreglos de elementos. Cada uno de los punteros del primer arreglo va a apuntar a un arreglo de elementos. El nombre del arreglo bidimensional es un puntero que apunta hacia el primer arreglo.


Por lo tanto, el nombre de la matriz no va a ser simplemente un puntero hacia elementos, si no que es un puntero hacia un arreglo de punteros, lo que significa que hay que declararlo como un puntero hacia un puntero. Esta lógica puede extenderse a cualquier cantidad de dimensiones.

La matriz del diagrama anterior la podemos ver como una matriz de 5 filas por 7 columnas. El asunto es que tanto los arreglos con los elementos como el arreglo de punteros van a existir en memoria dinámica, por lo que hay que solicitar la memoria respectiva.

int **arreglo = new int*[5];
for (int i = 0; i < 5; i++) {
    arreglo[i] = new int[7];
}

Primero se solicita memoria para el arreglo de punteros y luego se solicita la memoria para cada una de las filas de la matriz. En el ejemplo anterior, las dimensiones están fijas en el código, pero estos valores pueden ser variables dado que se está declarando todo en memoria dinámica. Entonces, volviendo a nuestra intención original, puede hacerse algo como lo siguiente.

int m, n;
cin >> m >> n;
int **arreglo = new int*[m];
for (int i = 0; i < m; i++) {
    arreglo[i] = new int[n];
}

Es importante recalcar que, cuando se solicita memoria dinámica, ésta no se encuentra inicializada, por lo que los valores almacenados son indeterminados y podríamos obtener cualquier valor.

Después de esa declaración es posible acceder a los elementos de la matriz mediante dos índices, uno para seleccionar la "fila" y otro para la "columna". Si quisiéramos asignar un valor inicial de cero a todos los elementos de la matriz anterior, debe hacerse algo así:

for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
        arreglo[i][j] = 0;
    }
}

A continuación se encuentra el código de una clase genérica que encapsula el funcionamiento de una matriz bidimensional. El constructor recibe como parámetro las dimensiones y solicita la memoria necesaria. El destructor se encarga de liberar la memoria dinámica solicitada. Se incluyen métodos para asignar un valor a una celda, obtener el valor de una celda, y para obtener la cantidad de filas y columnas. Posteriormente se muestra un ejemplo de utilización de la clase.

template <typename E>
class DynamicMatrix
{
private:
    int rows;
    int columns;
    E** matrix;

public:
    DynamicMatrix(int pRows, int pColumns) throw(runtime_error) {
        if (pRows <= 0 || pColumns <= 0) {
            throw runtime_error("Number of rows and columns must be greater than zero.");
        }
        rows = pRows;
        columns = pColumns;
        matrix = new E*[rows];
        for (int i = 0; i < rows; i++) {
            matrix[i] = new E[columns];
        }
    }

    ~DynamicMatrix() {
        for (int i = 0; i < rows; i++) {
            delete [] matrix[i];
        }
        delete [] matrix;
    }

    E getValue(int pRow, int pColumn) throw(runtime_error) {
        if (pRow < 0 || pRow >= rows) {
            throw runtime_error("Invalid row.");
        }
        if (pColumn < 0 || pColumn >= columns) {
            throw runtime_error("Invalid column.");
        }
        return matrix[pRow][pColumn];
    }

    void setValue(int pRow, int pColumn, E value) throw(runtime_error) {
        if (pRow < 0 || pRow >= rows) {
            throw runtime_error("Invalid row.");
        }
        if (pColumn < 0 || pColumn >= columns) {
            throw runtime_error("Invalid column.");
        }
        matrix[pRow][pColumn] = value;
    }
    int getRows() {
        return rows;
    }
    int getColumns() {
        return columns;
    }
};


Ejemplo de uso de la clase. El primer ciclo llena la matriz con valores crecientes y el segundo lo imprime en consola.

int main(){
    DynamicMatrix<int> matriz(10, 15);
    for (int i = 0; i < matriz.getRows(); i++) {
        for (int j = 0; j < matriz.getColumns(); j++) {
            matriz.setValue(i, j, i+j);
        }
    }
    for (int i = 0; i < matriz.getRows(); i++) {
        for (int j = 0; j < matriz.getColumns(); j++) {
            cout << matriz.getValue(i, j) << "\t";
        }
        cout << endl;
    }
    return 0;
}

Comments

  1. Muchas Gracias, rápido y sencillo.

    ReplyDelete
  2. Excelente explicacion,pero en el ejemplo sin clases,en el primero,para eliminar la memoria solicitada como se hace??? ??para liberar la memoria solicitada se puede hacer "delete arreglo"?

    ReplyDelete
    Replies
    1. Se haría basicamente de la misma forma que se está haciendo en el destructor de la clase. Hay que recorrer el arreglo de punteros para eliminar cada una de las filas, y luego se elimina la memoria correspondiente al arreglo de punteros.

      Delete
  3. Si tengo una instancia de DynamicMatrix
    dentro de una clase la cual es una ventana,

    ¿ el destructor se ejecutaría automáticamente al cerrar la ventana ?

    ReplyDelete
    Replies
    1. Hola. Eso depende de cómo haya sido declarada la instancia de DynamicMatrix. Si la hizo en memoria estática (no es un puntero) entonces el destructor se ejecuta automáticamente cuando el ambiente local deja de existir. Esto aplica si, por ejemplo, la declaró como un atributo de la ventana. En ese caso, se ejecutaría el destructor cuando se ejecuta el destructor de la ventana.
      Pero si la declaró como un puntero hacia DynamicMatrix, entonces sí es necesario que ejecute el comando delete correspondiente para liberar la memoria y forzar la ejecución del destructor.

      Delete
  4. Muy buena explicación, me percate que hay que corregir el 4° código ya que estamos recorriendo demás al puntero con 7 iteraciones y deberían ser solo 5

    ReplyDelete
    Replies
    1. Tiene absolutamente toda la razón. Debo corregirlo, no sé como pudo pasar desapercibido ese error tanto tiempo. ¡Gracias por señalarlo!

      Delete
  5. Gracias!!! Fue muy útil la explicación que diste con respecto a este tema, además de ser muy clara

    ReplyDelete
    Replies
    1. Gracias por visitar el blog. Me alegra haberte ayudado.

      Delete
  6. De las mejores explicaciones que he visto por internet y además
    con ejemplos, de verdad gracias

    ReplyDelete
  7. ¡Excelente explicación! ¿Solo tengo una duda, al momento de reservar el espacio de memoria para el arreglo de punteros porque se debe poner doble asterisco?

    ReplyDelete
    Replies
    1. ¡Hola! Dado que tenemos un arreglo de punteros y deseamos apuntarlo con otro puntero, debemos declararlo con doble *, porque en realidad tenemos un puntero que apunta a otro puntero.

      Delete

Post a Comment

Popular Posts