Author Topic: Arreglos bidimensionales (matrices) mediante asignación dinámica de memoria  (Read 3453 times)

yoel

  • Newbie
  • *
  • Posts: 7
  • Karma: 0
    • View Profile
Hola a todos, esta vez quiero compartir con ustedes un tópico de C que en su momento me dió muchos dolores de cabeza, y me llevó horas corregir hasta hacerlo bien. Se trata de crear matrices bidimensionales de números, pero no estáticas declaradas por ejemplo como int A[3][4] sino dinámicas. Es decir, que no tienen un tamaño fijo conocido sino que son creadas dinámicamente en tiempo de ejecución, usando malloc por supuesto. Les voy a explicar la forma correcta de hacerlo, este programa no es extenso pero lo complicado aquí es el correcto manejo de los punteros y la función malloc. Cualquier descuido en estos asuntos puede llevarnos a errores catastróficos y frustrantes.

Lo primero que hay que saber es que si bien un arreglo unidimensional de enteros por ejemplo es del tipo apuntador a entero, un arreglo bidimensional será un apuntador a apuntador a entero, o algo sí como un arreglo unidimensional donde cada elemento en sí en otro arreglo unidimensional. Por ejemplo, una matriz A de 2x3:

Code: [Select]
a b c
d e f

será básicamente un arreglo de dos elementos, donde el primer elemento será la primera fila de la matriz [a d c] y el segundo elemento será la segunda fila [d e f]. Ustedes me díran: Ah, pero en ese caso lo declaro como int A[2][3] y listo. Pero, nosotros queremos declarar una matriz que pueda tener cualquier cantidad de filas y cualquiera de columnas, las cuales no se conozcan de antemano.
Entonces comenzamos con

Code: [Select]
int ** A;

con lo que A será apuntador a apuntador a int. Supóngamos que M y N serán respectivamente la cantidad de filas y de columnas de A, y podrán un valor que será asignado en alguna parte del programa, incluso por ejemplo pedirlo al usuario a través del teclado. Una vez hayamos adquirido los valores de M y N, vamos a establecer dinámicamente el espacio de almacenamiento (memoria) para la matriz A. Primero, establecemos la dimensión M:

Code: (cpp) [Select]
if ( ( A = (int **) malloc ( M * sizeof(int *) ) ) == NULL )
    return -1;

y nótese que hemos tomado la previsión de que si no se puede asignar memoria, sale del programa. Ahora, en cada elemento de A vamos a poner un arreglo de enteros de longitud N, o sea, una fila de la matriz:

Code: [Select]
for ( i = 0; i < M; i++ ) {
if ( ( A[i] = (int *) malloc ( N * sizeof(int) ) ) == NULL )
return -1;
}

y si todo anda bien, ya podemos asignar o consultar elementos de la matriz con órdenes tan simples como A[1][2] = -1 u otras por el estilo.

Ok, ahora vamos a construir un programa completo que pida al usuario las dimensiones de la matriz a través de la consola, a continuación cree la matriz y pida valores para rellenarla, luego imprima su contenido. Aquí doy el código completo, listo para compilar y ejecutar:

Code: [Select]
#include <stdlib.h>
#include <stdio.h>

int ** createMatrix ( int, int );
void destroyMatrix ( int, int, int ** );

int main( ) {

int i, j, M, N;
int ** A;

/* Pide al usuario dimensiones de la matriz */
printf( "No de filas de la matriz: ");
scanf ( "%d", &M );
printf( "No de columnas de la matriz: ");
scanf( "%d", &N );

/* creamos la matriz */
if ( ( A = createMatrix( M, N ) ) == NULL ) return -1;

/* y la llenamos con elementos aleatorios entre 0 y 9,
* e imprimimos estos valores */
printf( "A = \n");
for ( i = 0; i < M; i++ ) {
printf( "\t" );
for ( j = 0; j < N; j++ ) {
A[i][j] = (int) (10 * (double) rand() / (double) RAND_MAX);
printf( "%d\t", A[i][j]);
}
printf( "\n" );
}

/* liberamos la memoria asignada a la matriz, y salimos */
destroyMatrix( M, N, A);
return 0;
}

/* Crea una matriz de enteros con las dimensiones M y N.
 * Devuelve la matriz creada en caso de éxito, y NULL
 * en caso de error.
 */
int ** createMatrix ( int M, int N ) {

int i, j;
int ** A;

/* Revisa consistencia de los argumentos */
if ( M < 0 || N < 0 ) return NULL;

/* Ahora ya tenemos que M >= 0, N >= 0 . Creamos la matriz */
if ( ( A = (int **) malloc ( M * sizeof(int *) ) ) == NULL )
    return NULL;

for ( i = 0; i < M; i++ ) {
if ( ( A[i] = (int *) malloc ( N * sizeof(int) ) ) == NULL )
return NULL;
/* inicializamos elementos a cero */
for ( j = 0; j < N; j++)
A[i][j] = 0;
}

return A;
}

/* Destruye la matriz creada, liberando la memoria 
 * dinámicamente asignada.
 */
void destroyMatrix ( int M, int N, int ** A ) {

int i;

/* liberamos cada fila de A */
for ( i = 0; i < M; i++ ) {
free( A[i] );
A[i] = NULL;
}
/* y luego liberamos A */
free( A );
A = NULL;
}

Espero haberlo explicado muy sencillamente, que lo disfruten, y bienvenidas su réplicas, comentarios, etc. Les prometo en un próximo tópico algo más completo como matrices que puedan cambiar su tamaño dinámicamente, y una clase completamente desarrollada sobre matrices de este tipo.
« Last Edit: Febrero 06, 2014, 08:56:07 am by yoel »

yoel

  • Newbie
  • *
  • Posts: 7
  • Karma: 0
    • View Profile
Por cierto, una alternativa al código anterior es modificar createMatriz para que la matriz A creada no sea devuelta como retorno de la función, sino pasada como uno de sus argumentos de entrada. Por supuesto, como el valor de A (que es un apuntador) queda establecido por malloc en tiempo de ejecución, y no hay manera de conocerlo de antemano, es necesario pasar A como una variable "por referencia". En este caso, su tipo será puntero a puntero a puntero a int (complicadillo, jeje).
La modificación quedaría así:

Code: [Select]
#include <stdlib.h>
#include <stdio.h>

int createMatrix ( int, int, int *** );
void destroyMatrix ( int, int, int ** );

int main( ) {

int i, j, M, N;
int ** A;

/* Pide al usuario dimensiones de la matriz */
printf( "No de filas de la matriz: ");
scanf ( "%d", &M );
printf( "No de columnas de la matriz: ");
scanf( "%d", &N );

/* creamos la matriz */
if ( createMatrix( M, N, &A ) == -1 ) return -1;

/* y la llenamos con elementos aleatorios entre 0 y 9,
* e imprimimos estos valores */
printf( "A = \n");
for ( i = 0; i < M; i++ ) {
printf( "\t" );
for ( j = 0; j < N; j++ ) {
A[i][j] = (int) (10 * (double) rand() / (double) RAND_MAX);
printf( "%d\t", A[i][j]);
}
printf( "\n" );
}

/* liberamos la memoria asignada a la matriz, y salimos */
destroyMatrix( M, N, A);
return 0;
}

/* Crea una matriz de enteros con las dimensiones M y N.
 * Devuelve 0 en caso de éxito, y -1 en caso de error.
 */
int createMatrix ( int M, int N, int *** A_ptr ) {

int i, j;
int ** A;

/* Revisa consistencia de los argumentos */
if ( M < 0 || N < 0 ) return -1;

/* Ahora ya tenemos que M >= 0, N >= 0 . Creamos la matriz */
if ( ( A = (int **) malloc ( M * sizeof(int *) ) ) == NULL )
    return -1;

for ( i = 0; i < M; i++ ) {
if ( ( A[i] = (int *) malloc ( N * sizeof(int) ) ) == NULL )
return -1;
/* inicializamos elementos a cero */
for ( j = 0; j < N; j++)
A[i][j] = 0;
}

/* asignamos el valor de A al apuntado por A_ptr */
*A_ptr = A;

return 0;
}

/* Destruye la matriz creada, liberando la memoria 
 * dinámicamente asignada.
 */
void destroyMatrix ( int M, int N, int ** A ) {

int i;

/* liberamos cada fila de A */
for ( i = 0; i < M; i++ ) {
free( A[i] );
A[i] = NULL;
}
/* y luego liberamos A */
free( A );
A = NULL;
}

Véanse las modificaciones importantes. En primer lugar, el prototipo de createMatrix ha cambiado, ahora devuelve un tipo entero indicado 0 si tuvo éxito y -1 si error. Además es importante la línea:

Code: [Select]
*A_ptr = A

donde se asigna el valor devuelto por malloc al argumento A_ptr pasado por referencia. En la invocación de esta función desde el main() es necesario poner:

Code: [Select]
createMatrix( M, N, &A )

es decir, pasando un puntero a la matriz A como argumento.

Se esperan comentarios ... Enjoy it .... :D

 

ey