Hace unos
meses que entregué unas prácticas finales de PE, mi profesora me preguntó: ¿tuvo
problemas con alguna? –Sí, con las matrices, ya que no pude hacer una función
que las devolviera, le contesté. –¿Y cómo lo resolvió?, dijo ella….
Yo esperaba con mi respuesta que la profesora me explicara un método para hacerlo, o que me dijera que luego me enviaría la solución, pero muy por el contrario ella muy segura respondió dando a entender que yo podría resolverlo “solo”. Y fue una de las más grandes lecciones de carrera que me han dado en mi vida. Desde entonces he buscado un ¿por qué mi código compilaba pero no era correcto su resultado?, y hallé artículos que abordaban (si no mi tema en cuestión) si ejemplos parecidos y eso me fue acercando a la solución, que ayer en un chispazo de conexión entre mis dos neuronas he obtenido. Se las presento.
Yo esperaba con mi respuesta que la profesora me explicara un método para hacerlo, o que me dijera que luego me enviaría la solución, pero muy por el contrario ella muy segura respondió dando a entender que yo podría resolverlo “solo”. Y fue una de las más grandes lecciones de carrera que me han dado en mi vida. Desde entonces he buscado un ¿por qué mi código compilaba pero no era correcto su resultado?, y hallé artículos que abordaban (si no mi tema en cuestión) si ejemplos parecidos y eso me fue acercando a la solución, que ayer en un chispazo de conexión entre mis dos neuronas he obtenido. Se las presento.
¿Qué
necesito saber para comprender este tutorial?
Manejo
básico de punteros y referencias. Tener bien en claro lo que significa para el
compilador cada uno ya que no es mi propósito explicarlo en este capítulo.
Nivel de dificultad: Básico Alto.
¿Es posible
que una función devuelva una matriz?
Con datos
estáticos (no creados en tiempo de ejecución) la respuesta es NO.
¿Por qué
no, si es posible enviar un vector como parámetro a una función?
Enviar es
posible ya sea por valor o referencia, pero esto no obliga a que se pueda
devolver, es decir, la función es capaz de recibir un vector, operar con él y
si uno quiere imprimirlo en pantalla, y puede regresarlo a la función llamadora.
Pero no puede hacerlo igual con un arreglo de vectores, y eso es una matriz en realidad.
Mostraré
por qué.
Es
importante tener muy en claro que las variables creadas en una función
secundaria son de alcance solamente local y una vez que la operación del
programa sale de la función estos espacios de memoria son liberados.
¿Por qué el
siguiente código compila pero no funciona correctamente?
void main (void)
{
int **Asignamatriz(void);
int fil=4, col=4;
int **q;
//El puntero a puntero recibe lo devuelto por la función
q=Asignamatriz();
//Imprime la matriz que se recibe como argumento.
for (int i=0; i<fil; i++)
{
for(int j=0;j<col; j++)
cout<<q[i][j]<<" ";
cout <<endl;
}
}
int **Asignamatriz(void)
{
//Matriz 4x4
int matriz[4][4];
//Número de filas y columnas
int col=4, fil=4;
//Valor que asignaré a cada elemento de la matriz
int valor=0;
//Vector de punteros
int *p[4];
//Puntero a puntero
int **q;
//Lleno la matriz con valores
for (int i=0; i<fil; i++)
for(int j=0;j<col; j++)
matriz[i][j]=valor++; //int valor=0
//Cada elemento del vector "p" apunta a una fila de la matriz
for (int i=0; i<4;i++)
p[i]=matriz[i];
//El puntero "q" apunta al vector "p"
q=p;
return q;
}
Ya hemos dicho que las funciones
sólo pueden retornar un valor a la vez, o un vector (uno sólo), pero no más.
“q” apunta a un arreglo de vectores, por tanto “q” en su dirección de memoria
asignada apunta al primer elemento de éstos, y puede acceder a los subsecuentes
tres cambiándole de índice, lo que es válido para C++, ¿pero qué contienen en
sus direcciones de memoria estos elementos subsecuentes? La respuesta son
direcciones de memoria de los elementos de cada fila…. ¿ya notaste el “error”?
Cuando la función devuelve el puntero a un puntero (su tipo de retorno) lo hace correctamente, devuelve un puntero a punteros de las direcciones de memoria pertenecientes a las filas, ¡y la función destruye (libera memoria) las demás variables (un puntero lo es también)! , por lo que ha liberado los espacios de memoria que contenían los elementos de cada fila…. Y a lo que apunta ahora el valor devuelto de la función es a lagunas de memoria, o “basura” como se le conoce.
Cuando la función devuelve el puntero a un puntero (su tipo de retorno) lo hace correctamente, devuelve un puntero a punteros de las direcciones de memoria pertenecientes a las filas, ¡y la función destruye (libera memoria) las demás variables (un puntero lo es también)! , por lo que ha liberado los espacios de memoria que contenían los elementos de cada fila…. Y a lo que apunta ahora el valor devuelto de la función es a lagunas de memoria, o “basura” como se le conoce.
Concluimos que no podemos devolver
variables (estáticas) locales de una función, porque éstas se destruyen al
salir de ella, algo que seguramente ya sabíamos pero que habíamos perdido de
vista trabajando con matrices y punteros.
La memoria que es reservada dentro de una función secundaria (como la trabajada aquí) se libera pero no vuelve a ser usada, se “desperdicia”, por tanto tampoco es muy eficiente.
¿Has cachado ahora la solución? Reservar memoria en la función principal, y usar esos espacios de memoria para guardar y modificar la matriz.
La memoria que es reservada dentro de una función secundaria (como la trabajada aquí) se libera pero no vuelve a ser usada, se “desperdicia”, por tanto tampoco es muy eficiente.
¿Has cachado ahora la solución? Reservar memoria en la función principal, y usar esos espacios de memoria para guardar y modificar la matriz.
¿Cómo
hacerlo entonces?
Ahora sí,
¡hagámoslo!
-Tenemos
una matriz de elementos que es de ámbito de función principal.
int
matriz[4][4];
Dividamos
mentalmente esa matriz ya no en elementos sino en filas.
- Creamos
un arreglo de punteros
int
*p[4];
Cada
elemento del arreglo de punteros debe apuntar a cada fila de la matriz
for (int i=0; i<4;i++)
p[i]=matriz[i];
-Creamos un
puntero a puntero
int **q;
Este
puntero debe apuntar al puntero anterior.
q=p;
-Declaramos
la función prototipo que se encargará de operar la matriz, en este caso sumará
en uno cada elemento.
void
Asignamatriz(int **&matriz,int fil, int col);
La función
espera como primer argumento la referencia (dirección en memoria) de un puntero
a puntero, ¿por qué? Porque crea un alias a una matriz ¡que ya existe en
memoria!, en este caso asignada en la función principal (main). Entonces
actuará directamente sobre ella, modificando los valores con alcance en la
función llamadora. Además espera el número de filas y columnas de la matriz ya
que C++ no lo determina automáticamente.
Este es el
truco primordial. Analiza bien la sentencia.
-Defino la
función Asignamatriz
void Asignamatriz(int **&matriz, int fil, int col)
{
for (int i=0; i<fil; i++)
for(int j=0;j<col; j++)
matriz[i][j]++;
}
matriz es un alias de una matriz que ya existe, por tanto lo que yo haga
con sus elementos es de alcance no local sino de la función que la ha llamado,
es decir en donde fue declarada la matriz pasada como argumento.
-Ahora así
empleo la función Asignamatriz
Asignamatriz(q,fil,col);
¿Por qué paso “q” como argumento?
Porque es un puntero a un puntero,
tal como lo he dicho en la función prototipo, y éste apunta a un vector de
punteros, o dicho con otras palabras, “q” apunta a las filas de la matriz.
- Puedo crear otra función que
utiliza esa matriz, pero que no modifica sus valores, sino que recibe una copia
de ésta y solo ejecuta una operación como imprimirla en pantalla:
void Imprimematriz(int**matriz, int fil, int col)
{
//Imprime la matriz que se recibe como argumento.
for (int i=0; i<fil; i++)
{
for(int j=0;j<col; j++)
cout<<matriz[i][j]<<" ";
cout <<endl;
}
}
En este
caso recibe, también, un puntero a un puntero pero sus valore son pasados como
una “copia”, por tanto no son los originales y si yo modifico los elementos su
alcance es local y no se reflejarán en la función llamadora.
VENTAJAS DE ESTE MÉTODO
Al
emplearlo logramos que nuestras funciones estén diseñadas para realizar una
operación a la vez. Ya no estamos obligados a operar la matriz y a imprimirla
en pantalla o realizar una segunda operación en la misma función, ahora
podemos separar responsabilidades, punto básico en la Programación
Orientada a Objetos. Y si has comprendido la idea esencial de
la solución te será muy fácil adaptarlo a un código POO.
ACLARACIONES
No es ésta
la única forma de trabajar con funciones que realicen esta operación, ya que con
matrices dinámicas esto también es posible, o usando variables de tipo “static”
pero ya se los contaré en el siguiente capítulo.
Adjunto elcódigo .cpp (C++), y si tienen cualquier pregunta no duden en postearla en los
comentarios, con todo gusto les responderé a la brevedad ( o buscaremos juntos
la solución).
Muy buena explicación, gracias!
ResponderEliminarHola Karla =D
EliminarEstá en la parte final del post el código .cpp en el compilador Dev, por si te sirve.
Cualquier cosa estoy para ayudarte, por cierto bienvenida a mi pequueño blog =)
Muchas gracias carlos, estaba estancado en este problema desde hace varias semanas, y ya habia recorrido muchos foros en ingles y en español, pero en ninguno de estos explicaban paso a paso el por que de las cosas.
ResponderEliminarMuchas gracias amigo. Me sirvio mucho tu explicacion. Estaba buscando como ahcerlo para mi proyecto y aqui lo encontre. Muchas gracias.
ResponderEliminarYo uso Code Blocks, EXCELENTE EJEMPLO, me sirvió mucho! Muchisimas Gracias!
ResponderEliminarexcelente, muchas gracias. Estuve más de una hora tratando de aprender como usar el puntero que regreso con un return al terminar una función.
ResponderEliminary si quisiera sumar dos matrices y me devuelva una tercer matris ?
ResponderEliminarHola
EliminarAquí está la solución para retornar una matriz, el otro problema que tienes es para sumar dos matrices, bueno suponiendo que ambas las recibas como matrices. En vez de un solo alias matriz, necesitas dos. Y luego sumar, bueno eso es cosa de niños solo recorre las matrices y las sumas guardando este resultado en una tercera matriz que puedes retornar.
void Imprimematriz(int**matriz, int fil, int col)
{
//Imprime la matriz que se recibe como argumento.
for (int i=0; i<fil; i++)
{
for(int j=0;j<col; j++)
cout<<matriz[i][j]<<" ";
cout <<endl;
}
}