Por Daniel Ortega (profe de IO) BREVE EXPLICACIÓN DE PUNTEROS Y FUNCIONES Supongamos que tenemos dos funciones llamadas convierte_en_impar y dime_si_es_par. La segunda función es muy sencilla (podemos escribirla en una sola expresión), pero aún así diseñaremos una función para aprender más cosas de punteros. Esta función tiene una entrada, un entero, y una salida que nos dice si el entero es par o no. La salida la representaremos también con un entero, que será verdadero si vale distinto de cero (por ejemplo 1, pero no necesariamente tiene que ser 1) y que representará falso si vale 0. La función en si es muy sencilla, veámosla int dime_si_es_par(int a) { if(a%2==0) return 1; else return 0; } Realmente no es necesario escribir el if. Dado que una expresión como a%2==0 nos dará verdadero (!0) si a es par y falso (0) si a es impar, podemos escribir la función también como sigue: int dime_si_es_par(int a) { return a%2==0; } El escribirla de una u otra manera dependerá de como te sientas más a gusto (es decir, qué te parece más legible). En un determinado programa si tenemos una variable entera b, y queremos saber si es par, podríamos llamar a esta función, por ejemplo así: if(dime_si_es_par(b)) /* esto es equivalente a if (dime_si_es_par(b)!=0) */ printf("La variable b que vale %d es par\n",b); else printf("La variable b que vale %d es impar\n",b); La otra función, convierte_en_impar, es una función que acepta un entero, y si este es par, lo modifica, convirtiendolo en impar. Si el entero es impar no lo modifica. En cualquier caso, retorna verdadero (distinto de cero) si lo ha modificado o falso (0) si no lo ha modificado. En el esquema de cajas negras explicado en clase, esta función sería como sigue: ENTRADA ------------------------ SALIDAS | |-------> entero modicado (si es el caso) entero ----->| convierte_en_impar | | |-------> ha sido modificado? ------------------------ Como podemos ver, esta función tiene una entrada y dos salidas, por lo que en C no podremos implementarla tal como está. Una de las salidas habremos de convertirla a una modificación mediante un puntero. Como precisamente la entrada y la salida (entero modificado) son la misma cosa, lo que tendremos que hacer es agrupar las dos mediante un parámetro que sea un puntero a un entero. La otra salida podemos devolverla por el mecanismo normal de retorno de valores en C. Al final la declaración de la función se nos quedará como sigue: int convierte_en_impar(int* a); Mediante a (que es un puntero a un entero) pasaremos la dirección del número que queremos modificar (si cabe) y mediante el valor de retorno de la función, retornaremos si lo hemos o no modificado. Por lo tanto el cuerpo de la función es como sigue (para ver si el valor es par, llamaremos a la función que hemos escrito antes): int convierte_en_impar(int* a) /* <--- nótese que ya no ponemos ; */ { if(dime_si_es_par(*a)) { *a++; return 1; } else return 0; } Vamos a ver la llamada a la función dime_si_es_par. Nosotros tenemos en esta función un puntero a un entero (llamado a) y queremos saber si el entero al que apunta nuestro puntero es par o no. Para ello podemos usar la función dime_si_es_par, que acepta un entero y nos dice si es par. Dado que nuestra variable a no es un entero, no podemos llamar a la función dime_si_es_par con a como parámetro, de la siguiente manera: if(dime_si_es_par(a)) /* ERROR en tiempo de compilación */ Esto es un error porque la función dime_si_es_par acepta un entero y nuestra variable es un puntero a un entero. Siempre que llamemos a una función, los parámetros que le pasemos han de ser del tipo que espera la función, sino el compilador nos avisará con un error. Ahora bien, si la función dime_si_es_par acepta un entero, porqué no declaramos una variable entera y se la pasamos? Es decir, porqué no hacemos esto: int convierte_en_impar(int* a) /* <--- nótese que ya no ponemos ; */ { int b; if(dime_si_es_par(b)) /* Ya no hay error de compilación, sino de ejecución */ { *a++; return 1; } else return 0; } Si hacemos esto, lo que estamos haciendo es pasarle a la función un entero no inicializado. Aunque el compilador no se quejará (porque al fin y al cabo le estamos pasando a la función una variable entera que es lo que espera), el programa no funcionará, porque a nosotros no nos interesa saber si es par la variable b (que por cierto no está inicializada) que no tiene nada que ver con la variable apuntada por a. Si lo que queremos saber es si aquello a lo que apunta a es par, tendremos que llamar a la función con "aquello a lo que apunta" a, es decir, con *a. Imaginemos ahora un programa que lee desde teclado un número y lo convierte en impar, usando nuestra recien creada función. Veamos como se escribiría dicho programa: void main() { int z; scanf("%d",&z); if(convierte_en_impar(&z)) printf("La variable ha sido convertida a un impar, %d\n",z); else printf("La variable ya era un impar, %d\n",z); } En este caso tenemos una variable, la leemos con el scanf y llamamos a la función convierte_en_impar para convertirla en impar. Como esta función recibe un puntero a un entero (es decir, la dirección del entero que queremos modificar) habrá que llamarla con la dirección de la variable z, es decir, con &z. De esta manera permitimos que la función convierta_en_impar modifique nuestra variable z (que es local en el main) dado que tiene su dirección. Un error muy típico es intentar pasar la variable z directamente a la función, de la siguiente manera: if(convierte_en_impar(z)) /* ERROR de compilación */ El compilador nos daría un error y nos diría que z no es del tipo adecuado, que la función espera un puntero a un entero. Mucha gente, para resolver este problema lo que hace es darle al compilador lo que este le pide: que el compilador pide un puntero a un entero, pues le dan un puntero a un entero. void main() { int z; int *p; /* puntero a un entero */ scanf("%d",&z); if(convierte_en_impar(p)) printf("La variable ha sido convertida a un impar, %d\n",z); else printf("La variable ya era un impar, %d\n",z); } Al hacer esto, le estamos pasando a la función en cuestión un puntero a un entero, pero que no apunta a ningún sitio. La función convierte_en_impar usará dicho puntero para primero leer y luego posiblemente modificar la variable a la que apunta, pero a donde apunta p? Como no lo hemos inicializado, la variable p apunta a un sitio indeterminado y por lo tanto se produce un error. Otra cosa sería si inicializásemos la variable p, para que apuntase a un entero nuestro, en concreto al entero que queremos modificar, en este caso z. Esto lo podríamos hacer si incluimos la siguiente línea antes del if: p=&z; /* p contendrá la dirección de z, es decir, p apunta a z */ if(convierte_en_impar(p)) Pero hacer esto es lo mismo que hacer esto: if(convierte_en_impar(&z)) que es lo que teníamos en primer lugar.