Você está na página 1de 83

Escuela Politcnica Superior

Manual prctico programacin en C


Mtodos Informticos Ingeniera Industrialalidad

Curso 2009/2010

ndice
1. Introduccin............................................................................................................... 3 2. Entrada / salida bsica. .............................................................................................. 4 3. Sentencias condicionales. ........................................................................................ 12 4. Bucles ...................................................................................................................... 20 5. Funciones................................................................................................................. 26 6. Punteros (paso por referencia)................................................................................. 30 Paso por referencia ..................................................................................................... 31 7. Ficheros ................................................................................................................... 37 8. Arrays ...................................................................................................................... 41 Ordenacin.................................................................................................................. 44 9. Cadenas de caracteres.............................................................................................. 49 10. Matrices ................................................................................................................... 55 11. Reserva dinmica de memoria................................................................................. 60 12. Estructuras ............................................................................................................... 66 Apndice A. Normas de estilo de programacin............................................................ 70 Identificadores significativos...................................................................................... 70 Constantes simblicas................................................................................................. 71 Comentarios................................................................................................................ 72 Estructura del programa.............................................................................................. 73 Indentacin o sangrado............................................................................................... 76 13. Apndice B .............................................................................................................. 81

Antonio Becerra Permuy

1.

Introduccin.

El objetivo de este documento es proporcionar una orientacin prctica al alumno de primer curso de programacin en ingeniera. En ningn caso estos apuntes pretenden ser el nico material necesario para aprender a programar en C, y siempre debern ir apoyados los apuntes o transparencias que se proporcionan en clase de teora o bien de uno de los libros incluidos en la bibliografa. El alumno encontrar en este documento aquellos aspectos de la programacin en C que, a lo largo de los aos, hemos encontrado que se deben recalcar ms a menudo al alumno, bien por su dificultad o bien por su importancia. De esta forma, los programas que se aparecen resueltos en este manual incluyen una descripcin desde el punto de vista conceptual, pero no inciden en cmo elaborar los algoritmos. Todos los ejemplos aqu mostrados estn realizados con el entorno de desarrollo que se utiliza este ao en las clases de prcticas: el Codeblocks. Este programa est disponible en su pgina web (http://www.codeblocks.org/) para las plataformas Windows, Linux y Mac OSX.

2.

Entrada / salida bsica.

La inmensa mayora de los programas necesitan conocer para su ejecucin uno o ms datos (que denominaremos datos de entrada) y proporcionan, como resultado, uno o ms datos (datos de salida). En caso contrario, estaramos ante un programa que siempre realiza la misma funcin y que no tiene ningn tipo de relacin con el exterior, lo cual no es muy comn. Los datos de entrada pueden ser introducidos en el programa por medio de un fichero o por algn dispositivo de entrada como un teclado. Los datos de salida pueden almacenarse tambin en un fichero o pueden mostrarse por algn dispositivo de salida, como una pantalla o una impresora. A continuacin veremos varios ejemplos de programas en C en el que los datos de entrada se van a adquirir por teclado y los datos de salida se van a mostrar por pantalla. Para poder desarrollar estos programas, utilizaremos el entorno de desarrollo integrado (IDE, en ingls) Codeblocks que incluye, adems de un compilador de lenguaje C, un depurador y un editor de texto. Una vez que se ejecuta el Codeblocks nos encontramos con una pantalla similar a la de la Figura 1 (el aspecto concreto entorno grfico depender del sistema operativo):

Figura 1: Aspecto del Codeblocks

Para crear una nueva aplicacin es necesario pinchar sobre la opcin de la ventana cental Create a new project o bien acceder al men File, ah seleccionar New y despus Project. Toda aplicacin es, para Codeblocks, un proyecto. Si bien los ejemplos que veremos en este curso son muy sencillos y todo el cdigo fuente estar nicamente en un fichero, una aplicacin real suele tener su cdigo fuente dividido en varios ficheros, lo que facilita su desarrollo y posterior mantenimiento, ya sea por una persona o por un equipo de personas. Un proyecto es, por tanto, un conjunto de ficheros a partir de los cuales se generar una aplicacin.

Tras seleccionar la opcin para crear un nuevo proyecto nos aparece el cuadro de dilogo de la Figura 2.

Figura 2: Seleccin del tipo de proyecto

Codeblocks tiene plantillas para distintos tipos de proyectos. Nosotros escogeremos siempre Console Application, que se corresponde con el perfil de nuestra aplicacin, sin ningn tipo de interfaz grfica. Al pinchar sobre el botn Go nos aparece un asistente, en la primera ventana marcamos Next y en la siguiente escogemos la opcin C y volvemos a marcar Next. Es muy importante que en este paso no seleccionemos la opcin C++ porque es otro lenguaje diferente de C y no es el objeto de esta asignatura. En la siguiente ventana debemos dar un nombre al proyecto (Project Title) y escoger un directorio donde guardar sus ficheros (Folder to create project in). Es conveniente que el directorio sea distinto para cada proyecto, ya que de lo contrario, ficheros de un proyecto sobrescribirn ficheros de otro proyecto (por ejemplo, el fichero main.c o las carpetas obj y bin). Adems, el proyecto debera tener un nombre significativo del programa que almacena de cara a una identificacin posterior simple y nicamente debera contener letras y nmeros separados, si es necesario, con guin bajo. Otros caracteres como espacios, puntos, guiones, acentos, etc y la letra nunca se deben poner en el nombre del proyecto. Al escoger el nombre del proyecto y su ubicacin, las dos casillas Project Filename y Resulting filename se rellenan automticamente. Una vez finalizados estos pasos, pinchamos en Next y nos aparece la ltima ventana del asistente, donde nos pregunta el compilador y el tipo de configuracin (Debug o Release) que deseamos crear. Por defecto, dejaremos estas opciones tal cual aparecen y simplemente seleccionamos Finish. Codeblocks nos crea un esqueleto para nuestra aplicacin que puede verse en la Figura 3, donde en la parte izquierda de la ventana

aparece el explorador del proyecto, que contiene una carpeta source que almacena el archivo main.c inicial que nos crea Codeblocks. Las dos primeras lneas son directivas del compilador que incluyen sendos ficheros en el de vuestra aplicacin. Estos ficheros, stdio.h y stdlib.h, contienen definiciones de constantes y funciones tiles en cualquier aplicacin de consola, por ejemplo para leer de teclado y escribir por pantalla. A continuacin tenemos la funcin main. Todo programa en C se divide en funciones. Una funcin es un conjunto de sentencias que realiza una determinada tarea y que, opcionalmente, puede tener una serie de parmetros de entrada (argumentos) y, tambin opcionalmente, puede devolver un resultado. Todo programa en C debe tener al menos la funcin main, que es la funcin por la cual se empieza a ejecutar un programa. A la hora de definir una funcin, la primera lnea indica el tipo de dato del valor que devuelve, si es que devuelve alguno, el nombre de la funcin y los argumentos. En principio, main no tiene porque devolver ningn dato, pero por convenio se suele hacer que devuelva el nmero 0 si el programa finaliza correctamente y otro nmero distinto de 0, dependiente de la aplicacin, en caso contrario. Esto es particularmente til cuando se invoca un programa (hijo) desde otro (padre) ya que, en ese caso, el sistema operativo permite al programa padre consultar el valor devuelto por el programa hijo con lo que el primero puede comprobar si el segundo produjo algn error. Como se puede observar, el cdigo fuente que crea por defecto el Codeblocks hace uso de ese convenio y devuelve un 0 cuando finaliza correctamente (sentencia return 0;).

Figura 3: Console Application generada por defecto

La instruccin printf que aparece en la lnea 6 del programa muestra por pantalla el mensaje Hola mundo en ingls. Este tipo de instruccin forma parte del contenido de este tema y se ver en detalle a continuacin. Decir, por ltimo, que para compilar y ejecutar este programa de prueba bastara con ir al men Build y seleccionar Build and Run (o bien seleccionar la tecla F9). Los detalles sobre el funcionamiento del resto de

mens y opciones del Codeblocks sern explicadas en clase y adems se pueden consultar en la documentacin on-line: http://www.codeblocks.org/user-manual. A continuacin vamos a hacer un primer programa en C que, tras pedir al usuario el valor del radio, nos d el rea del crculo con dicho radio:
#include <stdio.h> #include <stdlib.h> int main() { float radio; printf("Introduce el radio del circulo: "); scanf("%f", &radio); printf("El area del circulo es %f\n", 3.14159 * radio * radio); return 0; }

El programa slo usa una variable: radio. Esta variable est definida dentro de la funcin main, por tanto es una variable local. Las variables locales slo son accesibles dentro de la funcin en la que son definidas. Si estuviese definida fuera de la funcin, despus de los #include, por ejemplo, sera una variable global. Las variables globales son accesibles desde cualquier funcin de un programa. Como regla general, siempre que sea posible se deben utilizar variables locales, ya que el mantenimiento de un programa que utiliza variables globales es ms complicado por el mero hecho de que una variable global puede ser modificada en cualquier parte del programa y encontrar un fallo relacionado con esa variable es ms difcil. En la normas de estilo que aparecen en el Apndice A se muestran ejemplos concretos que apoyan esta afirmacin. Uno de los errores ms frecuentes al empezar a programar en C consiste en un paso de parmetros a las funciones printf y scanf incorrecto. printf escribe en la pantalla, mientras que scanf lee de teclado y almacena lo ledo en variables. Lo que printf escribe por pantalla puede ser texto predeterminado o algn dato proveniente de una constante, una variable, una llamada a una funcin o una expresin aritmtica. Sin embargo, scanf no necesita el valor de una variable, si no la direccin de memoria donde se guarda el contenido de esa variable, ya que va a sobrescribir dicho contenido con lo que se lea por teclado. Es por eso que las variables en scanf van precedidas por un signo & que se puede interpretar como direccin de memoria de, mientras que eso no sucede con printf. Otro aspecto a mencionar en este primer programa es que la sintaxis de C fue elaborada por anglosajones, con lo que: El separador de decimales es el punto, no la coma. No se pueden utilizar caracteres acentuados y ni la letra en los nombres de las variables o las funciones.

Finalmente, resaltar la importancia de una correcta indentacin en el cdigo fuente. En C, el elemento sintctico que determina el final de una sentencia es el ;. Un programa en C podra escribirse, si as se desease, en una sola lnea. Obviamente, esto no es deseable. Y no lo es porque dificulta el entendimiento del funcionamiento del programa y, por tanto, su mantenimiento. Un programa no slo debe funcionar correctamente y

eficientemente, si no que debe estar programado de tal forma que realizar modificaciones en l en el futuro sea lo ms fcil posible. Uno de los elementos que ayudan a esto es la correcta indentacin del cdigo fuente: Al empezar a escribir un programa, todas las sentencias se escriben de tal forma que disten la misma distancia del margen izquierdo. Sin embargo, cuando se empieza a escribir el cdigo de una funcin, de una sentencia condicional, o de una sentencia iterativa, el cdigo se separa x caracteres adicionales del margen izquierdo, donde x es un nmero constante para todo el programa. Ninguna lnea debe de pasar de 80 caracteres para facilitar su impresin. Si una sentencia excede de esa longitud, se contina en la lnea siguiente dejando nuevamente x caracteres adicionales en el margen izquierdo.

A lo largo de los ejemplos presentes en este manual, se podr observar como se aplican estas reglas de indentacin. En el Apndice A de este manual, mostramos unas normas bsicas de estilo de programacin en C que sern aplicables a cualquier programa realizado a partir de ahora. A continuacin, modificamos el ejercicio anterior de forma que el programa no slo nos d el rea del crculo para un radio determinado, sino tambin la longitud de la circunferencia para ese mismo radio:
#include <stdio.h> #include <stdlib.h> int main() { float radio, pi=3.14159; printf("Introduce el radio del circulo: "); scanf("%f", &radio); printf("El area del circulo es %.3f\n", pi * radio * radio); printf("La longitud de la circunferencia es %.3f\n", 2 * pi * radio); system("PAUSE"); return 0; }

A diferencia del ejercicio anterior, aqu hemos utilizado una variable pi para almacenar en ella el valor del nmero en vez de usar una constante. En general, siempre es mejor definir las constantes como tales o almacenarlas en variables que no introducirlas directamente en las sentencias que las usen, ya que lo primero facilita el mantenimiento del cdigo (si queremos cambiar la constante slo es necesario cambiarla en un sitio) y es menos propenso a cometer errores (cuntas ms veces escribamos un nmero, ms probabilidades hay de que nos equivoquemos hacindolo). La expresin 2 * pi * radio produce como resultado un nmero en punto flotante. Cuando en una expresin aparecen valores en punto flotante y valores enteros, los enteros son convertidos a punto flotante antes de realizar la operacin. Por tanto, 2 es convertido a 2.0 antes de multiplicarse por pi. Este tipo de conversiones automticas deben de tenerse en cuenta. Por ejemplo, ambas expresiones producen resultados distintos: 3*5/2

3 * 5 / 2.0 La primera da como resultado 7, ya que tanto la multiplicacin como la divisin son con nmeros enteros. Sin embargo, la segunda da como resultado 7.5, ya que al ser 2.0 un nmero en punto flotante el resultado de la multiplicacin (15) es convertido a otro nmero en punto flotante y la divisin es una divisin de nmeros en punto flotante. En los printf de este ejercicio podemos ver tambin cmo limitar el nmero de decimales de un nmero en punto flotante a la hora de imprimirlo por pantalla: %.3f provoca que slo se impriman 3 decimales. Como se ha comentado con anterioridad, las constantes es mejor definirlas como tales o introducirlas en variables antes que explicitarlas cada vez que se requieren. Lo primero, en C, se realiza mediante la directiva #include:
#include <stdio.h> #include <stdlib.h> #define PI 3.14159 int main() { float radio; printf("Introduce el radio del circulo:\t\t"); scanf("%f", &radio); printf("El area del circulo es\t\t\t%.3f\n", PI * radio * radio); printf("La longitud de la circunferencia es\t%.3f\n", 2 * PI * radio); return 0; }

En programacin, una constante se puede pensar como una variable que no es tal, es decir, a la que no se le puede cambiar el valor inicial una vez se le ha asignado este. Sin embargo, en C es un poco distinto. Lo que realmente hace #define es sustituir literalmente en el cdigo fuente, desde el momento en el que aparece el #define, todas las instancias de lo definido por su valor antes de realizar la compilacin. Es decir, en el ejemplo anterior las apariciones de PI se cambiaran por 3.14159 antes de compilar. Esto tiene las siguientes implicaciones: No se puede cambiar el valor de una constante. Esto es muy fcil de ver. Si tenemos un
#define NUMERO 2

y, posteriormente, hacemos
NUMERO = 3;

eso sera lo mismo que poner


2 = 3;

lo cual, obviamente, no tiene sentido. Un #define no tiene porque restringirse a un valor numrico, si no que puede ser cualquier cosa y puede tener incluso argumentos como si fuese una funcin. Ej:

#include <stdio.h> #include <stdlib.h> #define CUADRADO(x) (x*x) int main() { int x; printf("Dime un numero: "); scanf("%d", &x); printf("El cuadrado de %d es %d\n", x, CUADRADO(x)); return 0; }

Hay que tener cuidado de que no se produzca ningn error sintctico o semntico en el cdigo fuente tras sustituir el valor. Por ejemplo, esto sera errneo (aunque fcilmente corregible poniendo parntesis alrededor de x+y en el #define):
#include <stdio.h> #include <stdlib.h> #define SUMA(x,y) x+y int main() { int x,y; printf("Dime un numero: "); scanf("%d", &x); printf("Dime otro numero: "); scanf("%d", &y); printf("La mitad de la suma entera es %d\n", SUMA(x,y)/2); return 0; }

A la hora de definir constantes, stas se suelen poner en maysculas, reservando las minsculas para las variables, para que, en cualquier lado del cdigo fuente, se pueda saber de forma inmediata cuando algo es una constante / definicin o una variable / funcin. En los printf de la ltima versin mostrada del programa que calcula el rea de un crculo, aparece el carcter especial \t, que representa el carcter tabulador y, por tanto, a la hora de imprimirlo por pantalla se substituye por espacios en blanco. Para finalizar con el apartado de entrada / salida bsica, veremos una versin ms del programa que calcula el rea de un crculo:
#include <stdio.h> #include <stdlib.h> #include <math.h> int main() { float radio, pi_radio; printf("Introduce el radio del circulo:\t\t"); scanf("%f", &radio); pi_radio = M_PI * radio; printf("El area del circulo es\t\t\t%.3f\n", pi_radio * radio); printf("La longitud de la circunferencia es\t%.3f\n", 2 * pi_radio);

return 0; }

La utilizacin de nmero es muy habitual, por lo que en realidad no es necesario definirlo en nuestros programas. Podemos hacer un #include del fichero math.h que incluye definiciones de nmeros habituales en Matemticas y de las funciones implementadas en la librera matemtica estndar de C (estas funciones nos permiten, por ejemplo, calcular races cuadradas, logaritmos, potencias, etc.). Estas son las constantes definidas en math.h:
#define #define #define #define #define #define #define #define #define #define #define #define #define M_E M_LOG2E M_LOG10E M_LN2 M_LN10 M_PI M_PI_2 M_PI_4 M_1_PI M_2_PI M_2_SQRTPI M_SQRT2 M_SQRT1_2 2.7182818284590452354 1.4426950408889634074 0.43429448190325182765 0.69314718055994530942 2.30258509299404568402 3.14159265358979323846 1.57079632679489661923 0.78539816339744830962 0.31830988618379067154 0.63661977236758134308 1.12837916709551257390 1.41421356237309504880 0.70710678118654752440

Como se puede ver, el nmero est aqu definido bajo la constante M_PI y con mucha mayor precisin que la que nosotros empleamos en los ejemplos anteriores. Anteriormente hemos comentado que un programa no slo debe de funcionar, si no que debe de hacerlo de forma eficiente. Por tanto, para qu repetir dos veces la misma operacin? En esta ltima versin del programa, hemos creado una variable temporal pi_radio en la que guardamos el valor resultante de multiplicar pi por radio. De esta forma, en los dos printf usamos el valor de esta variable en vez de repetir la operacin. Notar que la asignacin de un valor a una variable es siempre de la forma variable = expresin, es decir, la variable se pone a la izquierda del signo igual y el valor a almacenar en la variable, o la expresin que producir un valor a almacenar en la variable, se pone a la derecha. En la siguiente captura de pantalla podemos ver el resultado de ejecutar el programa.

Figura 4: Aplicacin que calcula la circunferencia y el rea del crculo a partir del radio.

3.

Sentencias condicionales.

Para ilustrar la utilizacin de las sentencias condicionales utilizaremos como ejemplo un programa que calcular las races reales de una ecuacin de segundo grado. Una posible primera implementacin sera esta:
#include <stdio.h> #include <stdlib.h> #include <math.h> int main() { double a, b, c, raiz, dos_a; printf("Introduce el valor de \"a\":\t"); scanf("%lf",&a); printf("Introduce el valor de \"b\":\t"); scanf("%lf",&b); printf("Introduce el valor de \"c\":\t"); scanf("%lf",&c); raiz = sqrt(b * b - 4 * a * c); dos_a = 2 * a; printf("Una solucion es:\t\t%lf\n", (-b + raiz) / dos_a); printf("La otra solucion es:\t\t%lf\n", (-b - raiz) / dos_a); return 0; }

En este programa vemos varias cosas nuevas: Las variables de tipo double. Este tipo de dato se usa para almacenar nmeros en punto flotante que necesitan de una precisin o rango de representacin mayor que el que proporciona el tipo float. Aunque el nmero de bytes usado para representar un float o un double depende de la arquitectura de la mquina y del sistema operativo en ella instalado, en las mquinas usadas en clase de prcticas se usan 4 bytes para los float y 8 bytes para los double. Como las comillas se usan para empezar y finalizar las cadenas de caracteres, cuando se quiere introducir ese carcter en la propia cadena de caracteres es necesario anteponer el carcter \. Para imprimir o leer un double se usa el indicador de formato %lf. Para calcular la raz cuadrada de un nmero usamos la funcin sqrt, definida en math.h.

Adems, es importante volver a recalcar la importancia de dos cosas: Los programas deben ser eficientes y evitar repetir clculos de forma innecesaria: usamos las variables raiz y dos_a para almacenar resultados temporales. Los operadores * y / tienen ms prioridad que + y -, por eso es necesario agrupar (b+raiz) y (-b-raiz) entre parntesis, ya que en caso contrario slo raiz estara siendo dividida por dos_a.

Ahora bien, este programa es incorrecto porque no contempla determinados casos particulares de a, b y c para los cuales no es posible realizar alguna de las operaciones aritmticas y el programa abortar con un error. Estos casos son los siguientes:

(b * b 4 * a * c) es negativo => no se puede hacer la raz cuadrada de un nmero negativo ya que la funcin sqrt slo trabaja con nmeros reales. Si esta situacin se produce lo que debemos hacer es avisar al usuario de que no existen races reales para la ecuacin planteada. a es 0 => no se puede hacer una divisin por 0. En este caso, nos encontramos con que la ecuacin slo tiene una solucin (-c/b).

Antes de proseguir, es necesario recalcar una cosa: no es suficiente con probar un programa una vez para asegurar su correcto funcionamiento! Es muy habitual la frase pues a mi me funciona cuando se muestra un fallo en un programa a un alumno. Aunque la verificacin formal no entra dentro del programa de esta asignatura, es importante tener claro que probar el correcto funcionamiento de un programa no es trivial. Algunos consejos prcticos son: Comprobar que no se pueden dar divisiones por cero. Comprobar que no se pueden dar races cuadradas de nmeros negativos. Comprobar que no se pueden dar situaciones de desbordamiento. Por ejemplo, si multiplicamos dos nmeros muy grandes es posible que excedamos el rango de representacin del tipo de datos y obtengamos un resultado incorrecto.

Vemos, por tanto, que en este programa es necesario ejecutar unas instrucciones u otras en funcin del valor de unas variables. Para ello utilizaremos la instruccin condicional if () else, explicada en clase, y que permite precisamente eso, ejecutar un bloque de instrucciones cuando se cumple una condicin y ejecutar otro bloque de instrucciones distinto en caso contrario. El programa quedara as:
#include <stdio.h> #include <stdlib.h> #include <math.h> int main() { double a, b, c, raiz, dos_a; printf("Introduce el valor de \"a\":\t"); scanf("%lf",&a); printf("Introduce el valor de \"b\":\t"); scanf("%lf",&b); Comparacin printf("Introduce el valor de \"c\":\t"); scanf("%lf",&c); if (a == 0) if (b != 0) printf("La solucion es:\t\t\t%lf\n", -c / b); else printf("Esto no es una ecuacion.\n"); Asignacin else { raiz = b * b - 4 * a * c; if (raiz >= 0) { raiz = sqrt(raiz); dos_a = 2 * a; printf("Una solucion es:\t\t%lf\n", (-b + raiz) / dos_a); printf("La otra solucion es:\t\t%lf\n", (-b - raiz) / dos_a); } else printf("La ecuacion no tiene raices reales.\n"); }

return 0; }

Figura 5: Aplicacin que calcula las races reales de una ecuacin de segundo grado

Sobre este programa, recalcaremos varios aspectos que suelen ser fuente de errores al empezar a programar en C: Los operadores para realizar una comparacin de igualdad y para realizar una asignacin son distintos. Para lo primero se usan dos smbolos = adyacentes mientras que para lo segundo un nico smbolo =. Si se usa el operador incorrecto (asignacin en vez de comparacin o comparacin en vez de asignacin), no se producir ningn error de compilacin, si no que el programa no se comportar como cabra esperar. Esto es as porque ambos tipos de expresin tienen un significado completo como sentencia. Una comparacin es una expresin que devuelve 1 si se verifica y 0 en caso contrario. Ponerla en una sentencia como en el siguiente ejemplo, que siempre imprimir el valor de x, es sintcticamente correcto aunque no tenga sentido ya que no se utiliza el resultado para nada:
#include <stdio.h> #include <stdlib.h> int main() { int x, y, z; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); z = x; if (y > z) z == y; printf("El mayor es %d\n", z); return 0; }

Por otra parte, una asignacin toma el valor lgico de cierto si el valor almacenado en la variable es distinto de 0 y toma el valor lgico de falso cuando es 0. El siguiente programa funciona de forma totalmente incorrecta, diciendo que los nmeros son distintos cuando y vale 0 y que son iguales en cualquier otro caso:
#include <stdio.h> #include <stdlib.h> int main() { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); if (x = y) printf("Los numeros son iguales.\n"); else printf("Los numeros son distintos.\n"); return 0; }

No se pone el carcter ; despus de la condicin del if. El carcter ;, en C, sirve para finalizar sentencias. No tiene sentido ponerlo despus de la condicin porque, necesariamente, hay que indicar al menos una instruccin a ejecutar en el caso de que la condicin se cumpla. Nuevamente, si se pone el ; no siempre se produce un error (slo se producir si hay un else asociado al if), si no que el programa no se comporta como el alumno espera ya que poner un ; tiene el significado asociado de sentencia vaca. Supongamos estos dos programas, el primero dar error de compilacin, mientras que el segundo siempre imprimir el valor de y:
#include <stdio.h> #include <stdlib.h> int main() { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); if (x > y); printf(El mayor es %d\n, x); else printf(El mayor es %d\n, y); return 0; } #include <stdio.h> #include <stdlib.h> int main() { int x, y, z; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y);

z = x; if (y > z); z = y; printf(El mayor es %d\n, z); return 0; }

Se pueden anidar expresiones lgicas sencillas para construir otras ms complejas mediante el uso de los operadores lgicos &&, || y !. El siguiente programa, que pregunta un mes y nos dice cuntos das tiene, es un ejemplo de uso de estos operadores lgicos para construir expresiones lgicas complejas:

Figura 6: Aplicacin que muestra el nmero de das de un mes


#include <stdio.h> #include <stdlib.h> int main() { int mes; printf("Escribe el mes (1-12): "); scanf("%d", &mes); if ((mes >= 1) && (mes <= 12)) if (mes == 2) printf("El mes tiene 28 dias.\n"); else if ((mes == 4) || (mes == 6) || (mes == 9) || (mes == 11)) printf("El mes tiene 30 dias.\n"); else printf("El mes tiene 31 dias.\n"); else printf("Ese mes no existe!\n"); return 0; }

Nuevamente, dos importantes aspectos a tener en cuenta: Salvo que se tenga muy clara la prioridad de los distintos operadores del lenguaje C, es recomendable usar los parntesis para agrupar subexpresiones. En el ejemplo anterior, gran parte de los parntesis no son necesarios (los que rodean las comparaciones), pero incluso aunque se sea consciente de este hecho, ponerlos ayuda a dar mayor claridad al cdigo fuente. Al igual que pasaba con los operadores de comparacin de igualdad y asignacin, los operadores && y || pueden confundirse fcilmente con los operadores bit a bit & y | respectivamente y no se producir ningn error de compilacin si no que el programa puede no funcionar correctamente. El siguiente programa que calcula el and bit a bit de dos nmeros funciona incorrectamente (cuando el resultado no es el nmero 0 imprime siempre 1) por culpa de utilizar && en vez de &:
#include <stdio.h> #include <stdlib.h> int main() { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); printf("El \"and\" de los dos nmeros vale %d\n", x && y); return 0; }

if (..) else es la sentencia condicional ms usada y ms genrica en lenguaje C, pero existe otra switch () case que es interesante en determinadas circunstancias. Cuando tenemos que mirar el valor de una variable de tipo entero y realizar distintas acciones en funcin de dicho valor, usar switch () case reduce un poco el tamao del cdigo fuente y nos evita usar numerosos if (..) else encadenados. El siguiente ejemplo, que calcula el rea de una figura geomtrica u otra en funcin de lo seleccionado por el usuario, ilustra este comentario:
#include <stdio.h> #include <stdlib.h> #include <math.h> int main() { char opcion; float lado1, lado2, radio; printf("Selecciona una figura para calcular el area (a, b o c):\n"); printf("\ta) Cuadrado.\n"); printf("\tb) Rectangulo.\n"); printf("\tc) Circulo.\n"); scanf("%c", &opcion); switch (opcion) { case 'a': printf("Introduce el lado del cuadrado: "); scanf("%f", &lado1); printf("El area vale %.3f\n", lado1 * lado1);

break; case 'b': printf("Introduce un lado del rectangulo: "); scanf("%f", &lado1); printf("Introduce el otro lado del rectangulo: "); scanf("%f", &lado2); printf("El area vale %.3f\n", lado1 * lado2); break; case 'c': printf("Introduce el radio del circulo: "); scanf("%f", &radio); printf("El area vale %.3f\n", M_PI * radio * radio); break; default: printf("Opcion no valida!\n"); } } return 0;

Figura 7: Aplicacin que calcula el rea de un cuadrado, rectngulo o crculo

Observaciones: Para almacenar un carcter se usa una variable de tipo char, la cual se lee de teclado / imprime por pantalla usando el identificador de formato %c. Aunque char se usa para guardar caracteres, es en realidad un tipo de dato entero, como lo pueda ser int, ya que una letra se representa por un nmero (cdigo ASCII). Por eso opcion puede ser un char. Sin embargo, opcion nunca podra ser un nmero en punto flotante ni tampoco una cadena de caracteres (como se ver en el captulo correspondiente). Notar que para indicar que a, b y c son caracteres se rodean por comillas simples. Si no se usan comillas tendremos un error porque el compilador pensar que son variables, mientras que si usamos comillas dobles el error se deber a que el compilador pensar que son cadenas de caracteres.

break se usa para salir del switch. Si no se pusiese, la siguiente sentencia que se ejecutara sera la escrita en la lnea de abajo, aun perteneciendo a un case distinto. Es, en este sentido, un caso muy particular de C que no se repite para ningn otro tipo de sentencia. El uso de break en cualquier otra situacin, est totalmente desaconsejado y va en contra de los principios de la programacin estructurada, al romper la secuencia del cdigo y dificultar su comprensin.

4.

Bucles

En ocasiones es necesario realizar una misma operacin sobre un conjunto de datos, o repetir esa operacin hasta que se cumpla una condicin determinada. Un ejemplo de lo primero, que veremos en el tema en el que se traten los arrays, sera sumar dos vectores. Un ejemplo de lo segundo es el programa que veremos a continuacin: clculo del factorial de un nmero. Para calcular el factorial de un nmero natural se multiplica ste por ese mismo nmero menos 1, el resultado se vuelve a multiplicar por el nmero menos dos y as sucesivamente hasta llegar a multiplicar por 1. Si desconocemos el nmero del cual hay que calcular el factorial, desconocemos tambin cuntas multiplicaciones habr que hacer, con lo que es necesario utilizar una sentencia que nos permita realizar la multiplicacin mientras no lleguemos al 1. Este tipo de sentencias se conocen como iterativas y en C disponemos de tres: while, dowhile y for. A continuacin mostramos una posible implementacin de un programa que resuelve este problema. Por supuesto, existen muchas otras formas de implementarlo, algunas ms eficientes, pero esta nos permitir observar claramente las diferencias entre los tres tipos de sentencias iterativas de C.

Figura 8: Aplicacin que calcula el factorial de un nmero


#include <stdio.h> #include <stdlib.h> int main() { long int numero, factorial; printf("Introduce un numero: "); scanf("%ld", &numero); if (numero < 0) printf("No existe el factorial de un numero negativo!\n");

else { factorial = 1; while (numero > 1) { factorial = factorial * numero; numero = numero 1; } printf("El factorial vale %ld\n", factorial); } return 0; }

Consideraciones: Las variables son esta vez de tipo long int. El estndar dice que el rango de nmeros representable por un long int es igual o mayor que el de un int. En las mquinas del laboratorio, ambos tipos pueden representar un entero entre -2147483648 y 2147483647, pero otras arquitecturas o con otro compilador los rangos podran ser distintos. Puede parecer un rango suficiente, pero el programa, en realidad, slo funciona con nmeros menores o iguales a 12. Para nmeros mayores, no obtendremos ningn error, si no un resultado incorrecto ya que el cdigo generado por el compilador no detecta situaciones de desbordamiento. Las variables deberan ser, en realidad, double para tener un mayor rango de representacin aunque los nmeros a almacenar sean enteros. Y aun as, para nmeros no muy grandes, no tendremos un resultado preciso. Al igual que con el if, y por las mismas razones, no se debe poner punto y coma despus de la condicin del while. Cuando se realiza una operacin aritmtica con dos operadores donde uno de ellos es a su vez la propia variable en la cual se va a almacenar el resultado, la asignacin y la expresin aritmtica se pueden abreviar como muestra la siguiente tabla:
Sentencia x = x + y x = x y x = x * y x = x / y x = x + 1 x = x 1 Forma(s) abreviada(s) x += y x -= y x *= y x /= y x++ ++x x---x

En la tabla tambin se muestra que, en el caso particular de sumar uno o restar uno, existen otras dos formas simplificadas de expresar lo mismo. Es muy importante recalcar que hay una substancial diferencia semntica entre x++/x-- y ++x/--x. En el primer caso, primero se utiliza el valor actual de la variable en la sentencia en la que aparezca y luego se incrementa / decrementa ese valor. En el segundo, caso, primero se incrementa / decrementa el valor y luego se utiliza el resultado en la sentencia. Se entender mejor con el siguiente ejemplo:
#include <stdio.h> #include <stdlib.h> int main() { int x=5; printf("%d\n", x);

Imprime 5

x++; printf("%d\n", ++x; printf("%d\n", printf("%d\n", printf("%d\n", return 0; }

x); x); x++); ++x);

Imprime 6 Imprime 7 Imprime 7

Imprime 9 Como consideracin final al respecto de estas formas abreviadas de expresar algunas operaciones aritmticas en C, decir que slo se deben de usar si se est absolutamente seguro de cmo funcionan, ya que se pueden crear sentencias poco claras, especialmente utilizando ++ y -- en la misma sentencia conjuntamente con otros operadores. Ej:
#include <stdio.h> #include <stdlib.h> int main() { int x=5; printf("%d\n", (x++) + (--x)); printf("%d\n", x); return 0; }

Imprime 8 Imprime 5

En este ltimo ejemplo, el primer printf podra escribirse como printf("%d\n", x+++--x); pero eso sera todava mucho menos claro: aunque no sean necesarios, deben utilizarse parntesis siempre que se considere adecuado con el objetivo de facilitar la compresin del cdigo fuente. Teniendo en cuenta todo esto, el ejemplo anterior del factorial puede escribirse de la siguiente forma:
#include <stdio.h> #include <stdlib.h> int main() { double numero, factorial; printf("Introduce un numero: "); scanf("%lf", &numero); if (numero < 0) printf("No existe el factorial de un numero negativo!\n"); else { factorial = 1; while (numero > 1) factorial *= numero--; printf("El factorial vale %.0lf\n", factorial); } return 0; }

Notar como se ha utilizado %.0lf para imprimir un double sin decimales.

A continuacin veremos cmo realizar el mismo programa utilizando las otras dos sentencias iterativas de C. En este caso no se ha utilizado ningn tipo de abreviatura en las operaciones como en el caso anterior:
#include <stdio.h> #include <stdlib.h> int main() { double numero, factorial; printf("Introduce un numero: "); scanf("%lf", &numero); if (numero < 0) printf("No existe el factorial de un numero negativo!\n"); else { factorial = 1; if (numero > 1) do { factorial = factorial * numero; numero = numero - 1; } while (numero > 1); printf("El factorial vale %.0lf\n", factorial); } return 0; } #include <stdio.h> #include <stdlib.h> int main() { double numero, factorial;

Inicializacin

Condicin

printf("Introduce un numero: "); scanf("%lf", &numero); Actualizacin if (numero < 0) printf("No existe el factorial de un numero negativo!\n"); else { for (factorial = 1; numero > 1; numero=numero-1) factorial = factorial * numero; printf("El factorial vale %.0lf\n", factorial); } return 0; }

Consideraciones al respecto de la versin con dowhile(): Ahora s hay un punto y coma despus de la condicin, ya que en este caso la condicin es la ltima parte de una sentencia dowhile(), a diferencia del while() en el cual al menos una sentencia tiene que seguir a la condicin. Las sentencias dentro de un dowhile() se ejecutan al menos una vez. Esto es una diferencia con el while(), en el cual puede darse el caso de que nunca se ejecuten. Esto es as porque en el while() la condicin se evala antes de ejecutar el contenido del bucle, mientras que en el dowhile() se evala despus. Por esto, y para que el programa funcione correctamente en el caso de que el nmero introducido por el usuario sea el 0, hemos tenido que aadir un if adicional y poner el dowhile()

dentro de ese if. Por lo general, slo se usa dowhile() cuando sabemos a ciencia cierta que el contenido del bucle debe ejecutarse siempre al menos una vez. Consideraciones al respecto de la versin con for(): for() permite escribir de forma ms compacta los bucles while(). De hecho, se puede establecer la siguiente equiparacin entre for() y while():
A while(B) { C D }

for (A; B; D) C

Las partes de inicializacin y actualizacin pueden ser mucho ms complejas e incluir varias sentencias separadas por comas, pero hay que tener cuidado de no abusar de esa caracterstica, ya que puede producir cdigo fuente muy confuso.

Cualquier bucle puede implementarse con cualquiera de las tres sentencias iterativas, pero while() tiene la sintaxis ms clara y fcil de entender, con lo que, si no se est seguro de la sintaxis de las otras sentencias, recomiendo optar por el uso de while(). Aunque no se ha comentado explcitamente, un programa puede contener cualquier tipo de sentencia dentro de una sentencia condicional o iterativa. Es decir, una sentencia condicional puede contener en su interior otras sentencias condicionales o sentencias iterativas. Igualmente, una sentencia iterativa puede contener sentencias condicionales o ms sentencias iterativas. El siguiente ejemplo corresponde a un programa en el que se pide al usuario realizar un programa que lea un nmero positivo, n, por teclado y muestre por pantalla todos los nmeros perfectos entre 1 y n: NOTA: Un nmero es perfecto cuando es igual a la suma de sus divisores excepto l mismo. Ejemplo: Es el nmero 6 perfecto? Los divisores de 6 son: 3, 2 y 1. 3+2+1=6 por tanto 6 es un nmero perfecto:
#include <stdio.h> #include <stdlib.h> int main() { int numero, contador, suma, candidato; printf("Introduce un numero: "); scanf("%d", &numero); if (numero < 1) printf("El numero ha de ser un entero positivo\n"); else { printf("Los numeros perfectos entre 1 y %d son: ", numero); for (candidato = 1; candidato <= numero; candidato++) { suma = 0; for (contador = 1; contador <= candidato; contador++) if ((candidato % contador) == 0)

suma += contador; if (suma == candidato) printf("%d ", candidato); } printf("\n"); } return 0; }

Como se puede ver en el cdigo anterior, se ha utilizado una sentencia iterativa for que recorre los nmeros entre 1 y el nmero mximo que ha introducido el usuario, dentro de la cual hay otra sentencia iterativa for (a este procedimiento se le denomina anidar bucles) que recorre los nmeros entre 1 y el nmero mximo que marca, en este caso, el bucle ms exterior. Es importante hacer notar que la variable suma debe ser puesta a cero en cada iteracin del bucle ms exterior (esto es un error comn a la hora de trabajar con variables que acumulan resultados parciales). A continuacin mostramos una versin ms optimizada del programa anterior, donde se ha reducido el nmero de iteraciones que deben hacer ambos bucles for teniendo en cuenta que el caso en el que las variables valen 1 puede ser incluido directamente en la variable suma y, por otro lado, que en el bucle interior no es necesario recorrer los nmeros hasta el valor de la variable candidato ya que a partir de candidato/2 ya no pueden ser mltiplos:
#include <stdio.h> #include <stdlib.h> int main() { int numero, contador, suma, candidato, mitad_candidato; printf("Introduce un numero: "); scanf("%d", &numero); if (numero < 1) printf("El numero ha de ser un entero positivo\n"); else { printf("Los numeros perfectos entre 1 y %d son: ", numero); for (candidato = 2; candidato <= numero; candidato++) { mitad_candidato = candidato / 2; for (contador = 2, suma = 1; contador <= mitad_candidato; contador++) if ((candidato % contador) == 0) suma += contador; if (suma == candidato) printf("%d ", candidato); } printf("\n"); } return 0; }

Es altamente recomendable que, tras realizar una versin inicial que funcione de un programa, se dedique un tiempo a tratar de optimizar el cdigo reduciendo el nmero de operaciones que se llevan a cabo (en sentencias iterativas normalmente). Este proceso depende del algoritmo que se deba implementar en cada caso.

5.

Funciones

A medida que los problemas a resolver mediante un programa en C se hacen ms complicados, el cdigo se convierte cada vez en ms difcil de estructurar y, sobre todo, de comprender dado el gran nmero de lneas que lo componen. Una solucin consiste en descomponer los problemas en otros ms sencillos que se puedan analizar y programar de forma independiente. A cada uno de estos problemas ms sencillos se les denomina mdulo, y a este tipo de programacin, programacin modular. En lenguaje C, los mdulos se denominan funciones y es posible establecer la similitud con las funciones matemticas que disponen de variables sobre las que operan para obtener un resultado. Para dividir un programa en funciones, hay que distinguir las partes que tienen alguna independencia. Despus se intenta dividir estas partes en otras ms pequeas y as sucesivamente, hasta llegar a fragmentos lo suficientemente simples. Esta forma de proceder se conoce como diseo Top-Down. Un programa diseado mediante la tcnica de programacin modular est formado por: El programa principal, que describe la solucin completa del problema y consta fundamentalmente de llamadas a funciones. El programa principal debe constar de pocas lneas y en l deben estar claros los diferentes pasos del proceso que se seguir para obtener el resultado. Las funciones, que tienen una estructura similar a la de un programa (declaracin de variables, programacin estructurada y resultado). El objetivo de cada funcin es resolver de modo independiente una parte del problema y slo sern ejecutadas cuando sean llamadas por el programa principal o por otras funciones. Las funciones, para preservar sus fundamentos, deben ser pequeas, claras y sencillas. A cada funcin de un programa en C se le debe asignar un nombre significativo con la tarea que lleva a cabo. Por ejemplo, si necesitamos una funcin que calcule la suma de dos nmeros, un nombre adecuado sera calculaSuma. Una buena metodologa a la hora de asignar nombres a las funciones consiste en que empiece por minscula y, en caso de constar de varias palabras, stas se escriban pegadas unas a las otras y con su primera letra mayscula. Cuando en un punto de un programa se llama a una funcin, se ejecutarn todas las instrucciones de la misma, y una vez finalizada, se vuelve al punto del programa desde donde se llam dicha funcin. Las funciones pueden (y suelen) recibir parmetros que se declaran a la derecha del nombre de la funcin y entre parntesis. Adems, suelen devolver un resultado que deber ser tratado en el punto del programa desde el que se llam a la funcin. La forma ms clara de entender la utilidad de la programacin modular y el envo y recepcin de parmetros es mediante un ejemplo. A continuacin mostramos un programa en C que calcula todas las posibles combinaciones (sin repeticin) de N elementos tomados de k en k segn la frmula:

! ( )= k!( NN k )!
N k

Por ejemplo, las combinaciones de 4 elementos tomados de 3 en 3 se calcularan:

! ( )= 3!(44 =4 3)!
4 3

NOTA: tanto N como k deben ser mayores que 0

#include <stdio.h> #include <stdlib.h> long int calcularFactorial(int numero) { long int factorial; int i; if (numero<2) factorial=1; else { factorial=numero; for (i = factorial - 1; i > 1; i--) factorial *= i; } return factorial; } int main() { int n,k; long int combinaciones; printf("Introduzca el valor de N: "); scanf("%d", &n);

if (n<=0) printf(N no puede ser negativo ni nulo \n); else //n > 0 { printf("Introduzca el valor de k: "); scanf("%d", &k); if (k<=0) printf("k no puede ser negativo ni nulo\n); else //(k>0) { if (n-k<0) printf("N tiene que ser mayor o igual que k \n"); else { combinaciones=calcularFactorial(n)/ (calcularFactorial(k)*calcularFactorial(n-k)); printf("\nEl numero de combinaciones de %i elementos " "tomados de %i en %i es: %li\n",n,k,k,combinaciones); } }//fin else k>0 }//fin else n>0 return 0; }//fin del main

La frmula utilizada para resolver el ejercicio implica realizar 3 veces el clculo del factorial. Sin programacin modular, el cdigo necesario en este caso repetira 3 veces las mismas instrucciones y esto es, obviamente, muy ineficiente. Sobre la base de la programacin modular, hemos creado una funcin (denominada calcularFactorial) que incluye las lneas de cdigo que calculan dicho factorial una nica vez en la totalidad del programa. Consideraciones sobre el programa anterior:

La estructura de la funcin calcularFactorial es idntica a la del main(), con una cabecera que incluye el nombre (significativo de lo que hace la funcin) y el cdigo entre llaves. En el main(), tras la comprobacin de errores en los valores de N y k, se ejecuta la instruccin: combinaciones=calcularFactorial(n)/
(calcularFactorial(k)*calcularFactorial(n-k));

donde se llama a la funcin calcularFactorial tres veces (una en el numerador y dos en el denominador). En cada una de estas llamadas, entre parntesis aparece un parmetro (n, k y n-k). Cada uno de estos parmetros se pasa a la funcin, que deber realizar sus operaciones a partir de estos valores. En el caso de sea necesario pasar ms de un parmetro a una funcin, debern estar separados por comas y dentro de los parntesis. La funcin calcularFactorial devuelve, tras su ejecucin, el factorial del nmero que se le pasa como parmetro. Es necesario, por tanto, almacenar en una variable o crear una expresin que utilice el dato devuelto (en este ejemplo, primeramente se resuelve la expresin matemtica y el resultado se almacena en la variable combinaciones). La declaracin de la funcin tiene la siguiente sintaxis:
long int calcularFactorial(int numero)

Entre parntesis aparece la nica variable que la funcin recibe como parmetro, en este caso almacenada en int numero. Como vemos, los parmetros que recibe una

funcin se declaran como una variable ms, indicando el tipo de variable y su nombre. Dicho nombre no tiene por qu coincidir con el nombre utilizado por el parmetro en el punto en que se llama a la funcin. El tipo s debe coincidir con el tipo de dato que se pasa. Obsrvese que en las llamadas a la funcin calcularFactorial del programa anterior, las dos primeras veces se le pasa una variable de tipo int (n y k ) y la ultima se le pasa una expresin, cuyo resultado tambin es de tipo int (n-k). Si una funcin recibe varios parmetros, stos debern estar separados por comas. Por otro lado, previo al nombre de la funcin aparece de nuevo long int, que indica el tipo de dato que devuelve (en este caso) la funcin. Los tipos de datos que puede devolver una funcin son todos los existente en C y en el que caso de que no devuelva nada, se utiliza el tipo especial void. Para devolver un parmetro se utiliza la sentencia return. Por este motivo, en el programa anterior se ha declarado la variable factorial de tipo long int dentro de la funcin y es la variable que se devuelve en el return. Vemos que la funcin calcularFactorial se sita antes del main(). Esto no tiene por qu ser siempre as, lo importante es que en el punto del main() desde donde se llame, dicha funcin debe ser conocida. Es decir, si est declarada antes del main(), ser conocida en cualquier punto del mismo, pero si se declara debajo del main(), es necesario situar el prototipo de la funcin (en este caso long int calcularFactorial(int numero) seguido de punto y coma) antes del main().

6.

Punteros (paso por referencia)

Un puntero no es ms que un tipo de variable que almacena direcciones de memoria. La direccin de memoria almacenada normalmente indica la posicin en la que est situada otra variable. As, decimos que la primera variable apunta a la segunda. Realmente, una variable de tipo puntero puede apuntar a todo tipo de objetos que residan en memoria, por ejemplo, constantes, funciones, otros punteros, etc. Los punteros se declaran como cualquier otro tipo de variable, eso s, con un identificador especial *. Por ejemplo, un puntero de nombre punt y que apunta a una variable de tipo double, se declara double *punt; Esta declaracin implica la reserva de una zona de memoria donde se guardar la direccin de memoria de una variable de tipo double, es decir, punt apuntar al primer byte de una zona de 8 bytes. Para trabajar con punteros tenemos dos operadores bsicos: El operador & que devuelve la direccin de memoria en la que se encuentra almacenada la variable sobre la que se aplica. Por ejemplo, para que punt apunte a una variable a, de tipo double, tendramos que incluir la sentencia: punt = &a; de tal forma que punt guardara la direccin de memoria en que se encuentra a, pero no el valor numrico de a. El otro operador bsico es *, que es complementario a &, ya que devuelve el valor contenido en la direccin que almacena la variable sobre la que se aplica *. Es decir, despus de la asignacin anterior, podramos acceder al valor numrico de a de dos formas: printf(%lf,a); que es la forma habitual que ya conocemos printf(%lf,*punt); que devolvera el mismo resultado utilizando el contenido de la variable a la que apunta punt. El siguiente programa es un buen ejemplo del manejo bsico de punteros:
#include <stdio.h> #include <stdlib.h> int main() { double a; double *punt;

a=15.6; punt=&a; printf("\nValor de punt=%p\n",punt); printf("\nDireccion de memoria de la variable a=%p\n",&a); printf("\nValor de a=%lf\n",a); printf("\nValor al que apunta punt=%lf\n",*punt); a=20.1; printf("\nValor de a=%lf\n",a); *punt=36.5; printf("\nValor de a=%lf\n\n",a); return 0; }

Cuya ejecucin proporciona la siguiente salida por pantalla:

Vemos cmo la direccin de memoria que almacena punt es la de a, de modo que los cambios que se realizan sobre *punt afectan al contenido de a.

Paso por referencia


Una de las principales aplicaciones de los punteros en C viene de la necesidad de que una funcin pueda modificar varias variables (sin que sean globales). El ejemplo que vimos en el apartado anterior en el que se calculaba las posibles combinaciones (sin repeticin) de N elementos tomados de k en k y que requera de tres llamadas a la funcin que calcula el factorial, es un ejemplo tpico de paso de parmetros por valor. En aquel caso, la funcin reciba un nico parmetro (int numero) del que se realizaba una copia local en la funcin, es decir, la variable n del main() no se vea modificada. Adems, en dicha funcin, nicamente se devolva un parmetro.

En cambio, muchas veces es necesario que una funcin devuelva varios parmetros y, en este caso, es necesario el uso de punteros. La idea bsica consiste en enviar como parmetro desde el main() la direccin de memoria de las variables que deben ser modificadas, y la funcin las almacena en punteros. A esta forma de pasar parmetros se le denomina paso por referencia e implica que no se realizan copias de los parmetros locales a la funcin. Por ejemplo, en el siguiente programa, dados tres nmeros se pide crear una funcin que calcule el menor, el mayor y la media de dichos nmeros:

#include "stdio.h" #include "stdlib.h /* Funcion que dados tres numeros, devuelve el menor, mayor y su media */ void calcularMedia(float *num1, float *num2, float *num3) { float min,max; if (*num1<*num2) { //Se comprueba cual es menor if (*num1<*num3) min=*num1; else min=*num3; //Se comprueba cual es mayor if (*num3<*num2) max=*num2; else max=*num3; } else //num2 < num1 { //Se comprueba cual es menor if (*num2<*num3) min=*num2; else min=*num3;

//Se comprueba cual es mayor if (*num3<*num1) max=*num1; else max=*num3; } *num3=(*num1+*num2+*num3)/3; *num1=min; *num2=max; } int main() { float num1,num2,num3; printf("Introduzca el primer numero"); scanf("%f",&num1); printf("Introduzca el segundo numero "); scanf("%f",&num2); printf("Introduzca el tercer numero "); scanf("%f",&num3); calcularMedia(&num1,&num2,&num3); printf(El menor es: %6.3f\nEl mayor es: %6.3f\n Y la media es: %6.3f\n, num1,num2,num3); return 0; }

Queda claro en este ejemplo la necesidad del uso de punteros, ya que as la funcin calcularMedia trabaja sobre las variables num1, num2 y num3 del main() y no sobre copias. De esta forma, las modificaciones que se hacen sobre *num1, *num2 y *num3 afectan a las variables originales (como se puede ver en el ltimo printf del main() que muestra el contenido de num1, num2 y num3 modificadas en la funcin). Es importante resaltar el uso ya explicado del operador & a la hora de mandar las direcciones de las variables num1, num2 y num3 a la funcin, que son recogidas, obviamente, por tres punteros *num1, *num2 y *num3. El siguiente cdigo es otro ejemplo de paso de variables por referencia y consiste en un programa que debe sumar dos fracciones (positivas y distintas de cero) introducidas por el usuario. Cada vez que se introduce una fraccin se debe simplificar (si no lo est) y el resultado de la suma tambin debe estar simplificado. Para ello, se debe calcular el mximo comn divisor mediante la siguiente funcin (algoritmo de Euclides):
int mcd(int a, int b) { while(a!=b) { if (a<b) b=b-a; else a=a-b; } return a; }

A continuacin mostramos el resultado de la ejecucin del programa y su cdigo:

#include <stdio.h> #include <stdlib.h> /*Funcion que calcula el maximo comun divisor de dos numeros usando el algoritmo de Euclides*/ int mcd(int a, int b) { while(a!=b) { if (a<b) b=b-a; else a=a-b; } return a; } /* Devuelve 1 en el caso de que no se pueda simplificar la fraccion */ int simplificarFraccion(int *numerador, int *denominador) { int maximo; maximo=mcd(*numerador,*denominador); *numerador=*numerador/maximo; *denominador=*denominador/maximo; return maximo; //el mcd es uno si no hay divisor comun } /*Suma dos fracciones y sobreescribe las variables suma*/ void sumarFracciones(int n1, int d1, int n2, int d2, *den_suma) { //numerador *num_suma=n1*d2+n2*d1; //denominador int *num_suma, int

*den_suma=d1*d2; } int main() { int num1,num2,num_suma,den1,den2,den_suma,entero; do { printf("Introduzca el numerador de la primera fraccion: "); scanf("%i",&num1); if (num1 <= 0) printf("El numerador debe ser positivo y mayor que cero\n"); } while (num1 <= 0); do { printf("Introduzca el denominador de la primera fraccion: "); scanf("%i",&den1); if (den1 <= 0) printf("El denominador debe ser positivo y mayor que cero\n"); } while (den1 <= 0); entero = simplificarFraccion(&num1,&den1); if (entero != 1) //si se pudo simplificar printf("La primera fraccion simplificada es %i/%i\n",num1,den1); do { printf("Introduzca el numerador de la segunda fraccion: "); scanf("%i",&num2); if (num2 <= 0) printf("El numerador debe ser positivo y mayor que cero\n"); } while (num2 <= 0); do { printf("Introduzca el denominador de la segunda fraccion: "); scanf("%i",&den2); if (den2 <= 0) printf("El denominador debe ser positivo y mayor que cero\n"); } while (den2 <= 0); entero = simplificarFraccion(&num2,&den2); if (entero != 1) //si se pudo simplificar printf("La segunda fraccion simplificada es: %i/%i\n",num2,den2); sumarFracciones(num1,den1,num2,den2,&num_suma,&den_suma); //Simplificacion de la matriz resultante simplificarFraccion(&num_suma,&den_suma); if (den_suma == 1) //si no es una fraccion printf("El resultado de sumar %i/%i + %i/%i es: %i\n" ,num1,den1,num2,den2,num_suma); else printf("El resultado de sumar %i/%i + %i/%i es %i/%i\n" ,num1,den1,num2,den2,num_suma,den_suma); return 0; }

En este caso, la funcin simplificarFraccion requiere dos parmetros de entrada que deben ser sobrescritos y dicha funcin debe ser aplicada 3 veces en el programa. Por otro lado, la funcin sumarFracciones mezcla paso por referencia y paso por valor, ya que requiere el paso de 4 parmetros por valor (num1, num2, den1 y den2) de los que

realiza copia local y no debe modificar, y adems requiere las variables num_suma y den_suma por referencia, ya que deben ser sobrescritas con el resultado de la suma.

7.

Ficheros

Hasta este momento, los datos que hemos utilizado en nuestros programas, se almacenaban en la memoria principal y dejaban de estar accesibles cuando el programa finalizaba. Los ficheros permiten almacenar informacin de manera permanente en la memoria secundaria (disco duro, cd, dvd, ) de tal forma que puedan ser accedidos con posterioridad por el propio programa o por otros. Asimismo, un programa puede leer datos por teclado (como hemos hecho hasta el momento) pero tambin los puede leer de un fichero. De hecho, esta es una opcin muy comn cuando deben ser ledas grandes cantidades de datos, ya que evita que el usuario tenga que teclear demasiado. Los ficheros de texto pueden ser de dos tipos: de texto o binarios. Los ficheros de texto almacenan informacin legible, es decir, basada en el cdigo ASCII. Cualquier fichero que no sea de texto, es binario. Para explicar el funcionamiento prctico de la lectura y escritura de ficheros en C, utilizaremos un programa que realiza lo siguiente: 1- Pedir al usuario un nmero N de datos (mximo 100) que desea guardar en un archivo de nombre datos.txt 2- Crear N datos aleatorios entre 0 y 1 3- Almacenar los datos en el archivo anterior
#include <stdio.h> #include <stdlib.h> #define MAX 100 int main() { int i,j,k; int cantidad; FILE *fichero; /* abrimos el fichero para escritura */ fichero = fopen(datos.txt,"w"); if (fichero == NULL) printf(\n\nERROR..... No se puede crear el fichero en la ruta especificada\n\n); else { do { printf("\nIntroduzca Numero de datos a guardar (maximo %d): ",MAX); scanf("%d",&cantidad); } while (cantidad>=MAX || cantidad<=0); /* almaceno en el fichero los numeros aleatorios */ srand(time(NULL)); for(i=0; i<cantidad; i++) fprintf(fichero,"\n%f", (float)rand()/RAND_MAX); fclose(fichero); } return 0; }

La comprobacin de que este ejercicio funciona correctamente se realiza abriendo el archivo de texto datos.txt creado y viendo que guarda el nmero de datos entre 0 y 1 que el usuario ha especificado y que estn en desorden (es decir, que son realmente aleatorios). La estructura del programa anterior es muy simple y se basa en la creacin de nmeros aleatorios que se almacenan en un fichero. En cuanto a la utilizacin de ficheros, vemos como mediante la instruccin
FILE *fichero;

declaramos un puntero que ser utilizado para acceder a los ficheros (realmente es un puntero a la estructura FILE, declarada en stdio.h). Antes de usar un fichero es necesario realizar una operacin de apertura del mismo y, posteriormente, si se desea almacenar datos en l hay que realizar una operacin de escritura. Cuando ya no se quiera utilizar el fichero se realiza una operacin de cierre. En este sentido, en el programa anterior se lleva a cabo inicialmente la apertura de un fichero para escribir datos de l. El nombre del fichero se podra solicitar al usuario (lo veremos ms adelante), pero en este ejemplo se ha puesto de forma explcita datos.txt. As, la apertura del fichero se realiza mediante la instruccin fopen:
fichero = fopen(datos.txt, "w");

La funcin fopen requiere como primer parmetro una cadena de caracteres que contenga el nombre del fichero que se quiere tratar (y en su caso la ruta de acceso). Como segundo parmetro es necesario especificar el modo de apertura de dicho fichero, que es una cadena de caracteres que indica el tipo del fichero (texto o binario) y el uso que se va ha hacer de l (lectura, escritura, aadir datos al final, etc). En este caso, el fichero datos.txt almacenar una serie de nmeros aleatorios, y se abre como escritura. A partir de esta instruccin, en nuestro programa accederemos al fichero mediante la variable fichero. Si existe algn tipo de error al realizar la operacin de apertura del fichero (por ejemplo, porque el disco est lleno, porque el usuario no tiene permisos de escritura en el disco, porque la ruta especificada no existe, etc), fopen devuelve el valor NULL. Esto nos permite controlar un error la apertura del fichero mediante una instruccin condicional, como la utilizada en este caso:
if (fichero == NULL)

Lo siguiente que realiza el programa anterior es pedir al usuario el nmero de datos que quiere guardar en el fichero y despus genera dicho nmero de datos mediante el uso de la funcin rand(), que devuelve un nmero entero aleatorio entre 0 y RAND_MAX (el mayor nmero entero que puede generar la funcin rand). Para crear nmeros decimales entre 0 y 1 basta con normalizar y cambiar el tipo de dato a float tal y como se hace en el cdigo anterior:
(float)rand()/RAND_MAX

La funcin srand(time(NULL)) que se utiliza en este programa inicializa la semilla para la generacin de nmeros aleatorios con rand. Si no se utiliza una semilla distinta cada

vez (esto se consigue mediante la funcin time), siempre obtendramos los mismos aleatorios en distintas ejecuciones del programa. Los nmeros aleatorios que se generan con la funcin rand deben ser guardados en el fichero. Para ello es necesario realizar una operacin de escritura. En este caso utilizaremos dentro de un bucle la funcin fprintf, anloga al printf que hemos usado hasta ahora pero especificando como primer argumento el puntero al fichero donde queremos escribir:
fprintf(fichero,"%f\n", (float)rand()/RAND_MAX);

Por ltimo, tras terminar de usar cada fichero, debemos cerrarlo utilizando la funcin fclose. Como continuacin del ejercicio anterior, y de cara a mostrar la lectura de datos de un archivo, a continuacin mostramos el cdigo del siguiente programa en C: 1. Abrir el archivo anterior datos.txt para lectura 2. Buscar el mayor y el menor de los nmeros almacenados 3. Guardar dichos nmeros en el archivo mayor_menor.txt
#include <stdio.h> #include <stdlib.h> int main() { int i; float numero; float mayor = 0.0, menor = 1.0; FILE *fichero;

//as nos aseguramos la primera comparacin

/* Abro el archivo anterior como lectura */ fichero = fopen(datos.txt,"r"); if (fichero == NULL) printf("\n\n ERROR..... No se puede leer el fichero\n\n"); else { /* leo los datos aleatorios del archivo */ while (! feof(fichero)) { fscanf(fichero,"%f",&numero); if (numero>mayor) mayor = numero; if (numero<menor) menor = numero; } fclose(fichero); //tras leer los datos se cierra el fichero if ((fichero = fopen(mayor_menor.txt,"w")) == NULL) printf(\n\nERROR..... No se puede crear el fichero en la ruta especificada\n\n); else { fprintf(fichero,"El mayor es %f y el menor %f\n",mayor,menor); fclose(fichero); } } return 0; }

El resultado de este programa ser correcto si el archivo mayor_menor.txt contiene el mayor y el menor de los nmeros almancenados en datos.txt. En este caso, para obtener los datos originales hay que efectuar una operacin de lectura de fichero. Una de las posibles instrucciones de lectura es fscanf, que es la utilizada en el programa anterior. Su estructura es similar al scanf que ya hemos utilizado en los captulos anteriores, con la diferencia de que hay que especificar como primer argumento el fichero en el que leemos:
fscanf(fichero,"%f",&numero);

En ciertos casos, como el del ejemplo anterior, no conoceremos el nmero de datos que almacena el fichero, de modo que es til la utilizacin de la funcin feof() que se muestra en el ejemplo anterior. Esta funcin nos devuelve un valor distinto de 0 si se alcanza el final de fichero. Por esta razn, la instruccin:
while (! feof(fichero)) { fscanf(fichero,"%f",&numero); }

lee los datos hasta que se alcanza el final del archivo sin necesidad de que el usuario introduzca cuntos hay, ni de que exista una cabecera en el fichero. A continuacin se realiza la comprobacin de si el nmero introducido es el mayor o el menor de los ledos (queda como ejercicio la modificacin del ejemplo anterior utilizando una estructura if-else) y, por ltimo, se cierra el fichero de lectura. Esto es necesario siempre que se deja de utilizar un fichero y, adems, permite reutilizar la variable fichero como es el caso del ejercicio anterior. Volveremos a mostrar ejemplos del uso de ficheros en posteriores apartados.

8.

Arrays

En ocasiones es interesante almacenar en memoria varios datos del mismo tipo sobre los que se realizar algn tipo de operacin comn. Por ejemplo, los elementos de un vector: si nuestro programa necesita almacenar en memoria un vector, casi seguro que las operaciones que haga con l afectarn a todos sus elementos (ya sea leerlos, imprimirlos por pantalla, compararlos, etc.). Es para esta necesidad de almacenar en memoria un conjunto de datos del mismo tipo para lo que se usan los arrays. Un array es, por tanto, una variable que contiene mltiples datos del mismo tipo y que podremos tratar, bien de forma conjunta, bien elemento a elemento, segn las necesidades del programa. A continuacin veremos un ejemplo de programa que emplea un array. Este programa debe leer N nmeros por teclado (donde N le ser preguntado al usuario), almacenarlos en un array, sumar los nmeros positivos almacenados en las posiciones pares del array y mostrar el resultado por pantalla con dos decimales:

#include <stdio.h> #include <stdlib.h> #define SIZE 256 int main() { int n, i; float array[SIZE], suma; printf("Cuantos numeros vas a introducir?: "); scanf("%d", &n); if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE); else

{ printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"); suma = 0; for (i = 0; i < n; i++) { scanf("%f", &array[i]); if (((i % 2) == 0) && (array[i] > 0)) suma += array[i]; } printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %.2f\n", suma); } return 0; }

Consideraciones: En este ejercicio tenemos ejemplo de todo lo visto hasta ahora: E/S, constantes, sentencias condicionales, condiciones compuestas, sentencias iterativas y arrays. Recuerda que: Las constantes, aunque no es obligatorio, se suelen declarar en maysculas. Cuando se leen datos con scanf se debe poner un & delante de cada variable en la que se vaya a guardar un dato, porque scanf necesita saber la direccin de memoria de esas variables. En una expresin lgica, || significa o y && significa y. El operador % significa mdulo y se usa, por tanto, para saber cul es el resto de una divisin entre dos nmeros enteros. No confundas el operador de asignacin = con el operador de comparacin de igualdad ==. Se pueden concatenar cadenas de caracteres simplemente cerrando una con comillas y abriendo la siguiente con otras comillas, aunque estn en lneas distintas. Lo que no se puede hacer es crear una cadena de caracteres en una lnea y continuarla en la siguiente. Un array, como cualquier otra variable, es necesario declararlo. En este caso, a la derecha del nombre, se pone entre corchetes el nmero de elementos que tiene el array. Una vez se ha declarado, C no permite cambiar el nmero de elementos del array. El usuario debe especificar con cuntos nmeros desea trabajar, pero dado que no hemos visto asignacin dinmica de memoria y tenemos que declarar el array antes de usarlo, debemos crear un array sobredimensionado y controlar que el usuario no especifica un nmero de elementos mayor que el tamao del array. Recuerda que para acceder a un elemento concreto del array se pone el nombre de ste y, entre corchetes, el ndice del elemento al que queremos acceder, que ser un nmero entero entre 0 y N-1 donde N es el nmero de elementos del array. El ndice puede ser una constante, una variable o incluso una expresin, siempre y cuando el resultado final de evaluarla sea un nmero entero. En el programa que acabamos de ver se usa una variable, i, que va tomando todos los valores desde 0 hasta n-1 porque nos interesa recorrer todos los elementos del array. Salvo el caso particular de los arrays de caracteres (strings) que veremos ms adelante, no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar los arrays con una sola instruccin. Es decir, no podemos leer, escribir, sumar, etc. arrays con una instruccin, si no que tenemos que hacerlo elemento a elemento.

En cuanto al uso de arrays y funciones, como vimos en anteriormente, el paso por referencia y, consecuentemente, el uso de punteros es necesario en C cuando una funcin debe modificar varias variables. Por este motivo, en el caso de pasar un array a una funcin, siempre se realiza por referencia, por lo que no es necesario devolver dicho array desde la funcin al estar modificando directamente el array original (no es necesario el return). A continuacin mostramos el cdigo del ejemplo anterior realizado ahora usando una funcin par_positivo que calcula la suma:
#include <stdio.h> #include <stdlib.h> #define SIZE 256 float par_positivo (float array[SIZE], int numeros) { float sum = 0; int i; for (i = 0; i < numeros; i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]; } return sum; } int main() { int n, i; float array[SIZE], suma; printf("Cuantos numeros vas a introducir?: "); scanf("%d", &n); if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE); else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"); for (i = 0; i < n; i++) scanf("%f", &array[i]); suma = par_positivo(array, n); printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %.2f\n", suma); } return 0; }

Como vemos, el paso del array a la funcin es simple y podra parecer que se est haciendo paso por valor ya que no hay distincin respecto a cmo se enva la variable n, pero como hemos dicho anteriormente, el paso de arrays es siempre por referencia. El nombre de un array es realmente un puntero a la direccin de memoria donde empieza el array. Este es el motivo por el cual en este ejemplo no es necesario un return de matriz, al ser un paso por referencia se est trabajando siempre sobre la matriz del

main(). De hecho, en el siguiente programa mostramos la forma ms comn de pasar arrays a funciones utilizando un puntero como argumento:
#include <stdio.h> #include <stdlib.h> #define SIZE 256 float par_positivo (float *array, int numeros) { float sum = 0; int i; for (i = 0; i < numeros; i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]; } } return sum;

int main() { int n, i; float array[SIZE], suma; printf("Cuantos numeros vas a introducir?: "); scanf("%d", &n); if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE); else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"); for (i = 0; i < n; i++) scanf("%f", &array[i]); suma = par_positivo(array, n); printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %.2f\n", suma); } return 0; }

Hemos resaltado la nica diferencia respecto a la versin anterior, que consiste en que ahora el array se recibe como puntero. La funcin par_positivo no copia localmente el contenido del array, sino que trabaja con un puntero que apunta al array creado desde el main(). La declaracin de los arrays unidimensionales como punteros dentro de las funciones es la ideal en este curso.

Ordenacin
En cualquier lenguaje de programacin es muy comn encontrarse con la necesidad de ordenar un conjunto de datos en funcin de un criterio de orden o alfabtico. En concreto, es importante conocer el funcionamiento bsico de los algoritmos de ordenacin ascendente o descendente de datos numricos almacenados en un array.

Existen distintas formas de ordenar dichos datos, que se diferencian mucho en cuanto a su rapidez y eficiencia derivadas del nmero de operaciones que realizan. De cara a ilustrar el funcionamiento bsico de un algoritmo de ordenacin, a continuacin mostramos el denominado mtodo de la burbuja para ordenar, en este caso, de menor a mayor un array de nmeros enteros. Este mtodo es uno de los ms sencillos y, a la vez, uno de los ms ineficientes:

#include <stdio.h> #include <stdlib.h> #define MAX 100 int main() { int numelementos, i, j, vector[MAX]; printf("\nNumero de elementos del vector:"); scanf("%i", &numelementos); if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos. Maximo = %i", MAX); else { for (i=0; i<numelementos; i++) { printf("\nIntroduzca el elemento %i: ", i); scanf("%i", &vector[i]); } for(i=0; i<numelementos; i++) { for (j=i+1; j<numelementos; j++) if (vector[j] < vector[i]) { aux = vector[j]; vector[j] = vector[i]; vector[i] = aux; } } printf("\nVector ordenado = "); for (i=0; i<numelementos; i++) printf("%i ", vector[i]); }

return 0; }

Como vemos, en este algoritmo se utiliza un ndice i para sealar el elemento que se toma como referencia en la comparacin (bucle exterior) y otro ndice j cuyo valor inicial es i+1 para sealar el elemento que se compara cada vez (bucle interior). De esta forma, una vez que un elemento sealado por j es menor que el de referencia sealado por i, simplemente se intercambian. Por este motivo, el mtodo de la burbuja se engloba dentro de los algoritmos de intercambio, ya que su filosofa de funcionamiento se basa en la realizacin de sucesivos intercambios de valores. De hecho, el mayor defecto de este algoritmo es el elevado nmero de intercambios innecesarios que se llevan a cabo. Por ejemplo, si el array que proporciona el usuario estuviese ordenado de mayor a menor (caso opuesto a lo que queremos obtener), el programa anterior realizara continuos intercambios ya que cualquier nmero es siempre menor que el que se toma como referencia. Un mtodo de ordenacin ms eficiente, que se obtiene como una variacin simple del anterior, es el denominado mtodo de seleccin directa donde, cada vez que se encuentra un nmero menor que el de referencia sealado por el ndice i, se almacena dicho nmero y su posicin en un par de variables y, tras finalizar el bucle en j, se lleva a cabo un nico intercambio. El siguiente cdigo muestra este mtodo aplicado a la ordenacin de un array, de nuevo de menor a mayor:
#include <stdio.h> #include <stdlib.h> #define MAX 100 int main() { int numelementos, aux, i, j, k, vector[MAX]; printf("\nNumero de elementos del vector:"); scanf("%i", &numelementos); if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos. Maximo = %i", MAX); else { for (i=0; i<numelementos; i++) { printf("\nIntroduzca el elemento %i: ", i); scanf("%i", &vector[i]); } for(i=0; i<numelementos; i++) { aux = vector[i]; k = i; for (j=i+1; j<numelementos; j++) if (vector[j] < aux) { aux = vector[j]; k = j; } if (k != i) { vector[k] = vector[i];

vector[i] = aux; } } printf("\nVector ordenado = "); for (i=0; i<numelementos; i++) printf("%i ", vector[i]); } return 0; }

Por tanto, respecto al mtodo de la burbuja se han aadido nicamente dos variables:

aux: que almacena temporalmente el menor valor en cada iteracin del bucle i k: que almacena temporalmente la posicin del menor valor en cada iteracin del bucle i

Con esta simple modificacin, el mtodo de seleccin directa se vuelve mucho ms eficiente que el de la burbuja y, como dijimos antes, en el caso extremo de que el array inicial est ordenado de mayor a menor, realizara nicamente tantos intercambios como elementos tenga el array. Un ejercicio interesante es comprobar este incremento de eficiencia guardando e imprimiendo una variable contador que aumente su valor en una unidad cada vez que se lleva a cambio un intercambio en un mtodo y en otro. Para finalizar este apartado dedicado a los algoritmos de ordenacin simples, decir simplemente que para ordenar de mayor a menor en los ejemplos anteriores, nicamente sera necesario cambiar el signo de comparacin del primer if de < a >. De hecho, a continuacin mostramos un programa que implementa el algoritmo de seleccin directa mediante el uso de una funcin que permite al usuario escoger entre una ordenacin ascendente o descendente:
#include <stdio.h> #include <stdlib.h> #define MAX 100 void ordena (int tipo, int *vector, int elem) { int aux, i, j, k; for(i=0; i<elem; i++) { aux = vector[i]; k = i; for (j=i+1; j<elem; j++) { if (tipo == 0) { if (vector[j] < aux) { aux = vector[j]; k = j; } } else { if (vector[j] > aux) { aux = vector[j]; k = j;

} } } if (k != i) { vector[k] = vector[i]; vector[i] = aux; } } } int main() { int numelementos, i, tipo, vector[MAX]; printf("\nNumero de elementos del vector:"); scanf("%i", &numelementos); if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos. Maximo = %i", MAX); else { for (i=0; i<numelementos; i++) { printf("\nIntroduzca el elemento %i: ", i); scanf("%i", &vector[i]); } do { printf("\nIntroduzaca el tipo de ordenacion:\n"); printf("0- Ascendente\n"); printf("1- Descendente\n"); printf("Tipo: "); scanf("%i", &tipo); } while ((tipo != 0) && (tipo != 1)); ordena(tipo, vector, numelementos); printf("\nVector ordenado = "); for (i=0; i<numelementos; i++) printf("%i ", vector[i]); } return 0; }

En este ejemplo se ve claramente el efecto del paso por referencia del array vector, ya que dentro de la funcin ordenacion se modifica pero no es necesario un return para actualizar su valor en el main, ya que la funcin realmente est modificando el array declarado en el main mediante el uso del puntero.

9.

Cadenas de caracteres

Las cadenas de caracteres son simplemente arrays que almacenan variables de tipo char. La nica caracterstica propia de este tipo de array es que se utiliza el carcter 0 (NULL o carcter de cdigo ASCII 0) para indicar el final de la cadena (esto no es necesario en arrays numricos). El uso de variables de tipo carcter cobra una gran importancia prctica a la hora de trabajar con cadenas de caracteres, ya que desde el punto de vista del usuario, esto le permite utilizar palabras y frases de forma cmoda. Existen en C una serie de funciones especficas para operar con estas cadenas, por ejemplo para: Leer o escribir una cadena entera (array completo) (gets y puts) Averiguar la longitud de una cadena (strlen) Buscar un carcter o grupo dentro de una cadena (strchr) Unir dos cadenas o extraer parte de una cadena (strcat, strtok) Es importante recalcar que estas funciones slo sirven para cadenas de caracteres, no para arrays numricos. A continuacin mostramos un ejemplo de manejo de cadenas de caracteres, que consiste en averiguar si una frase es o no palndromo (es decir, si se lee igual de izquierda a derecha y de derecha a izquierda):

#include<stdio.h> #include <stdlib.h> #include <string.h> #define MAX 40

int esPalindromo(char palabra[]) { int i,longitud, es_palindromo=1; longitud = strlen(palabra); for (i=0; i<longitud/2 && es_palindromo == 1; i++) if (palabra[i] != palabra[longitud-1-i]) es_palindromo = 0; return es_palindromo; } void eliminarBlancos(char frase[]) { int i,j; for(i=0; frase[i]!=0;i++) { if (frase[i] == ' ') for(j=i; frase[j]!=0;j++) frase[j] = frase[j+1]; } } int main() { char frase[MAX]; int es_palindromo; printf("\nIntroduzca una frase: "); scanf("%39[^\n]s",frase); strlwr(frase); eliminarBlancos(frase); es_palindromo=esPalindromo(frase); if (es_palindromo) printf("\nLa frase es un palindromo\n"); else printf("\nLa frase no es un palindromo\n"); return 0; }

La idea bsica para la resolucin de este ejemplo (funcin esPalindromo) es que se debe comparar el primer carcter del array con el ltimo, el segundo con el penltimo, etc. En el caso de que una de estas comparaciones detecte que dos caracteres simtricos no son iguales, la frase ya no ser palndromo. Previamente a esta comparacin, debemos pasar todos los caracteres a minsculas (la comparacin simple de una letra mayscula con una minscula devolvera que son distintas y la frase sera palndromo igualmente) y, a continuacin, eliminar los espacios en blanco que contenga la frase para que la comparacin tenga sentido (funcin eliminarBlancos). Adems, debemos tener en cuenta las siguientes consideraciones relacionadas con la utilizacin de cadenas de caracteres: En el ejemplo anterior, la declaracin de una cadena de caracteres se realiza declarando un array de 40 caracteres. Esto implica, de acuerdo con lo que comentamos antes, que la frase ms grande que podramos utilizar en este caso es de 39 caracteres, ya que el ltimo carcter es el nulo.

Las cadenas de caracteres se pueden leer completas usando las funciones gets() y scanf() con %s. Es decir, no es necesario leer los caracteres uno a uno en un bucle como se haca en el caso de arrays numricos. La funcin gets() que hemos utilizado en estos dos ejemplos lee todos los caracteres hasta retorno de carro que lo sustituye por un carcter nulo. Pero si se leen ms datos de los que caben en la cadena, desborda la cadena que traer como consecuencia fallos impredecibles en la ejecucin del programa (acceso a direccin de memoria invlida, sobrescribir variables del propio programa), por lo cual su utilizacin est desaconsejada y de ahora en adelante no volver a aparecer. Un opcin que evita el uso de gets() es usar scanf() con indicador de longitud (%), aunque slo funciona con palabras, no con frases ya que el scanf() para de leer al encontrar un espacio en blanco, tabulador, o retorno de carro. Para solucionar este problema existe la posibilidad de utilizar scanf con indicador de longitud (%) y con un indicador de caracteres permitidos de lectura (entre corchetes) que le puede indicar que pare de leer nicamente al detectar un retorno de carro. Por ejemplo: scanf("%16[^\n]s", cadena); Esta forma de lectura ser la empleada a partir de ahora en lugar del gets. Las funciones strlwr() y strlen() son ejemplos de funciones especficas de las cadenas de caracteres que no pueden ser utilizadas con arrays numricos. La funcin strlen() devuelve una variable de tipo int indicando el nmero de elementos que tiene la cadena hasta el carcter nulo. Debemos almacenar dicha variable en otra conocida de nuestro programa, en este caso, longitud. Por ltimo, el programa anterior utiliza una variable que nicamente toma dos valores (cero o uno) para detectar si la cadena es palndromo. Dicha variable (es_palindromo) se pone a cero en caso de que la condicin dentro del bucle se cumpla, lo que indicara que no es palndromo. Como consecuencia, el bucle for se termina (ya que es_palindromo tambin controla la condicin de realizacin del bucle) y no se realizan ms comprobaciones innecesarias.

La eliminacin de los espacios en blanco en el ejemplo anterior se ha llevado a cabo sobre el mismo array, desplazando los elementos de derecha a izquierda cada vez que se detecta un espacio. Otra posible solucin de este ejercicio es la que se muestra a continuacin, donde se utiliza un segundo array (nueva_frase) al que se copian las palabras de la frase original (queda como ejercicio pasar esta versin a funciones):
#include <stdio.h> #include <stdlib.h> #include <string.h> #define TAM 40 int main() { char frase[TAM], nueva_frase[TAM]; int i, j, longitud, es_palindromo=1; printf("\nIntroduzca una frase: "); scanf("%39[^\n]s", frase); /* convierte a minsculas la frase */ strlwr(frase); longitud = strlen(frase); for(i=0,j=0;i<=longitud;i++) if (frase[i] != ' ')

{ nueva_frase[j] = frase[i]; j++; } longitud = strlen(nueva_frase); for (i=0; i<=longitud/2 && es_palindromo == 1; i=i+1) if (nueva_frase[i] != nueva_frase[longitud-1-i]) es_palindromo = 0; if (es_palindromo == 1) printf("\nLa frase es un palindromo\n"); else printf("\nLa frase no es un palindromo\n"); return 0;

El uso de arrays de caracteres y funciones es equivalente al resto de arrays explicado en el tema anterior. Una de las principales utilidades que tendrn las cadenas de caracteres en este curso ser a la hora de almacenar el nombre de un fichero, que se le pedir al usuario. Por ejemplo, en el siguiente programa: 1- Pedir al usuario el nombre de un archivo donde se encuentran, como mximo, 100 nmeros en punto flotante entre 0 y 1. 2- Leer los nmeros del archivo y ordenarlos de menor a mayor 3- Guardar los datos ordenados en otro archivo cuyo nombre se pedir de nuevo al usuario
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 /* funcion estandar que borra el buffer del teclado */ void borra_buffer_teclado() { int c; while((c = getchar()) != '\n' && c != EOF); } void ordena(float vector[],int num_datos) { int i,j,k; float aux; for(i=0; i<num_datos; i++) { aux = vector[i]; k = i; for (j=i+1; j<num_datos; j++) if (vector[j] < aux) { aux = vector[j]; k = j; }

if (k != i) { vector[k] = vector[i]; vector[i] = aux; } } /* Fin del proceso de ordenacion */ } int main() { int i; int num_datos=0; float vector[MAX}; char nombre[35]; FILE *fichero; printf("\n\nIntroduce el nombre del fichero origen sin extension: "); scanf("%34[^\n]s",nombre); strcat(nombre,".txt"); /* Abro el archivo anterior como lectura */ fichero = fopen(nombre,"r"); if (fichero == NULL) printf("\n\n ERROR..... No se puede leer el fichero %s \n\n",nombre); else { /* leo los datos aleatorios del archivo */ while (! feof(fichero)) { fscanf(fichero,"%f",&vector[num_datos]); num_datos++; } fclose(fichero); /* Ordeno de menor a mayor */ ordena(vector,num_datos); /* Esta instruccin borra el buffer del teclado */ borra_buffer_teclado(); /* Guardo los datos en un archivo */ printf(\nIntroduce el nombre del fichero destino para los datos ordenados (sin extension): ); scanf("%34[^\n]s",nombre); strcat(nombre,".txt"); if ((fichero = fopen(nombre,"w")) == NULL) printf(\n\nERROR..... No se puede crear el fichero %s en la ruta especificada\n\n,nombre); else { for(i=0; i<num_datos; i++) fprintf(fichero,"%f\n",vector[i]); fclose(fichero); } } return 0; }

Como vemos, una vez que se tienen los datos en el array vector simplemente ejecutamos el algoritmo de ordenacin visto en el tema anterior. Antes de aadir estos datos ordenados a un fichero de destino debemos vaciar previamente el buffer del teclado mediante la funcin borra_buffer_teclado que ejecuta el siguiente cdigo:
while((c = getchar()) != '\n' && c != EOF);

El buffer de teclado es una memoria temporal donde se introducen los caracteres tecleados antes de pulsar la tecla RETURN (Intro o Enter), la cual es otro carcter (el carcter '\n'). Usualmente, cuando se usan funciones de lectura como scanf() o getchar(), por ejemplo, la ejecucin del programa se detiene hasta que se introducen estos caracteres en el buffer de teclado, o sea, hasta que se pulsa la tecla RETURN. Sin embargo, si en el buffer ya existen valores entonces la funcin de lectura lee esos valores sin detenerse. Cuando estamos leyendo nmeros la funcin intenta leer nmeros, saltndose algunos caracteres de control (si se los encuentra), como el carcter '\n'. Sin embargo, cuando estamos leyendo un carcter (o varios) la funcin de lectura leer el siguiente carcter que haya en el buffer de teclado, sea cual sea. Si no hay ninguno, entonces, la funcin se detiene a la espera de poder leer. Pero si en el buffer existe algn carcter ste ser ledo, aunque sea un carcter de control. Para evitar la lectura de caracteres de control y tambin para evitar la lectura de otros caracteres anteriores que puede haber en el buffer del teclado, se suele utilizar la instruccin flush(stdin) que vaca dicho buffer. Pero n osotros lo desaconsejamos totalmente porque fflush solo est definido en el estndar para flujos de salida y stdin es un flujo de entrada, y por ello no podemos estar seguros de cmo se comportar en un caso general. Aunque en algunos compiladores funciona, no lo har en todos, por lo cual nosotros recomendamos la utilizacin de la expresin anterior como equivalente para el fflush(stdin). Continuando con el programa anterior, solo nos resta pedir al usuario un nombre para el fichero destino, abrir el fichero ahora en modo escritura y guardar en l los datos del array vector.

10. Matrices
En las secciones anteriores hemos trabajado con arrays de una nica dimensin (vectores), sin embargo en muchas aplicaciones y problemas reales es necesario el uso de matrices. Estas matrices podrn ser de dos dimensiones o en general n-dimensionales para cualquier nmero n entero positivo. A continuacin veremos el ejemplo de un programa que lee dos matrices por teclado y calcula la suma de ambas matrices.

#include <stdio.h> #include <stdlib.h> #define MAXFIL 20 #define MAXCOL 20 int main() { int i, j, numfil, numcol; int matriz1[MAXFIL][MAXCOL], matriz2[MAXFIL][MAXCOL], suma[MAXFIL][MAXCOL]; printf("\nIntroduzca el numero de filas de las matrices: "); scanf("%i", &numfil); printf("\nIntroduzca el numero de columnas de las matrices: "); scanf("%i", &numcol); if (numfil <= 0 || numfil > MAXFIL) printf("\nError. Numero de filas incorrectas (MAX = %i)", MAXFIL); else if (numcol <= 0 || numcol > MAXCOL) printf("\nError. Numero de columnas incorrectas (MAX = %i)", MAXCOL); else { printf("\nMatriz 1:\n"); for (i=0; i<numfil; i=i+1) for (j=0; j<numcol; j=j+1) { printf("Introduzca el elemento [%i][%i]: ", i, j); scanf("%i", &matriz1[i][j]); } printf("\nMatriz 2:\n");

for (i=0; i<numfil; i++) for (j=0; j<numcol; j++) { printf("Introduzca el elemento [%i][%i]: ", i, j); scanf("%i", &matriz2[i][j]); } printf("\nSuma de las matrices:\n"); for (i=0; i<numfil; i=i+1) { for (j=0; j<numcol; j=j+1) { suma[i][j] = matriz1[i][j] + matriz2[i][j]; printf("%i\t", suma[i][j]); } printf("\n"); } } return 0; }

En este programa hay que tener en cuenta las siguientes consideraciones: Una matriz, como cualquier otra variable, es necesario declararla. En este caso, a la derecha del nombre de la variable, se ponen tantos corchetes como dimensiones tenga la matriz. En el ejemplo anterior, al ser matrices bidimensionales, se usaron dos pares de corchetes para indicar en el primero de ellos el nmero de filas y en el segundo el nmero de columnas de la matriz. Al igual que suceda con los arrays, una vez se ha declarada la matriz no se permite cambiar el nmero de elementos de la misma. El programador debe especificar el tamao (nmeros de elementos) de cada dimensin. En el ejemplo anterior se especificaron el nmero de filas y de columnas de la matriz. Puesto que dicho tamao no puede ser sobrepasado, ser necesario controlar que el usuario no especifica un nmero de elementos mayor que el tamao de la matriz. Recuerda que para acceder a un elemento concreto de la matriz se pone el nombre de ste y, entre corchetes, el ndice del elemento al que queremos acceder, que ser un nmero entero entre 0 y N-1 donde N es el nmero de elementos de esa dimensin de la matriz. El ndice puede ser una constante, una variable o incluso una expresin, siempre y cuando el resultado final de evaluarla sea un nmero entero. En el programa que acabamos de ver se usan una variable, i, que va tomando todos los valores desde 0 hasta numfil porque nos interesa recorrer todos las filas de la matriz y otra variable numcol para ir recorriendo todas las columnas de la matriz. Hay que tener en cuenta que para cada fila de la matriz se debern recorrer todas sus columnas por ello en el programa anterior el bucle de la variable j (las columnas) es un bucle anidado dentro del bucle de la variable i (filas de la matriz). Al igual que en el caso de los arrays, no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar las matrices con una sola instruccin. Es decir, no podemos leer, escribir, sumar, etc. matrices con una instruccin, si no que tenemos que hacerlo elemento a elemento.

En cuanto al uso de matrices y funciones, hay que tener en cuenta que cuando se recibe una array bidimensional (matriz) en una funcin, se deben especificar todas las dimensiones menos la primera, que es opcional. El siguiente ejemplo muestra el uso de matrices en funciones, donde el objetivo consiste en realizar un programa que ponga a 0

(mediante una funcin) los elementos de la diagonal principal y secundaria de una matriz cuadrada:

#include <stdio.h> #include <stdlib.h> #define MAX 10 void modificarDiagonalesMatriz(int matriz[][MAX],int dim) { int i; for (i=0; i<dim; i++) { matriz[i][i]=0; matriz[i][dim-i-1]=0; }

int main() { int i,j,dimension,matriz[MAX][MAX]; printf("Introduzca la dimension de la matriz cuadrada: "); scanf("%d",&dimension); if (dimension>MAX || dimension<=0) printf("Error. El numero de elementos es incorrecto (Maximo= %i)\n",MAX); else { for (i=0; i<dimension; i++) for (j=0; j<dimension; j++) { printf("Introduzca el elemento %i, %i: ", i,j); scanf("%i",&matriz[i][j]); } modificarDiagonalesMatriz(matriz,dimension); for (i=0; i<dimension; i++)

{ for (j=0; j<dimension; j++) printf("%i\t ", matriz[i][j]); printf("\n"); } }//fin else return 0; }

La funcin modificarDiagonalesMatriz recibe como parmetros la matriz y la variable dimension y no devuelve nada (void). Por este motivo, en el main() nicamente se llama a la funcin y se le pasan los parmetros correspondientes. Al terminar la ejecucin de la funcin, la matriz ha quedado modificada tambin en el main(). De cara a clarificar el hecho de que no se realiza una copia de la matriz local a la funcin, en la declaracin de la misma hemos mantenido el nombre que tena en el main(). En cambio, la variable dimension se almacena localmente en la funcin en una variable con otro nombre, dim (las modificaciones que se hiciesen sobre dim no afectaran a la variable dimension). Por otro lado, la declaracin de la matriz en la cabecera de la funcin no incluye la primera dimensin, pero s la segunda tal y como habamos comentado anteriormente. A continuacin mostramos otro ejemplo del uso de matrices y archivos en C que lee datos de un fichero numeros.txt que contiene los valores de una matriz de tamao 10 x 10. As cada lnea del fichero contiene 3 nmeros, el primero de ellos indica el nmero de fila, el segundo el nmero de columna y el ltimo el valor correspondiente. El objetivo es realizar un programa en C que lea este fichero numeros.txt y muestre por pantalla y guarde en otro fichero el contenido del fichero original, pero de forma matricial. El contenido del fichero de texto se muestra en el Apndice B, al final de este manual. La ejecucin del programa proporciona la siguiente salida por pantalla:

#include <stdio.h> #include <stdlib.h> #define MAX 10 int main() { int matriz[MAX][MAX]; int i,j,valor; char nombreFichero[40]; FILE *fichero1,*fichero2; printf("\nIntroduzca el nombre del fichero origen: "); scanf(%s39[^\n]s,nombreFichero); fichero1 = fopen(nombreFichero, "r"); if (fichero1 != NULL) { printf("\nIntroduzca el nombre del fichero destino: "); scanf(%s39[^\n]s,nombreFichero); fichero2 = fopen(nombreFichero, "wb"); if (fichero2!=NULL) { while (! feof(fichero1)) { fscanf(fichero1, "%i", &i); fscanf(fichero1, "%i", &j); fscanf(fichero1, "%i", &valor); matriz[i][j]=valor; } for (i=0;i<MAX;i++) { for (j=0;j<MAX;j++) { printf("%4i",matriz[i][j]); fprintf(fichero2,"%4i",matriz[i][j]); } printf("\n"); fprintf(fichero2,"\n"); } fclose(fichero2); } else printf("\nError al crear el fichero\n"); fclose(fichero1); } else printf("\nError al abrir el fichero\n"); return 0; }

Como vemos, la lectura de matrices de datos de ficheros permite realizar operaciones con gran cantidad de datos.

11. Reserva dinmica de memoria


En los programas que hemos visto hasta ahora, la reserva o asignacin de memoria para los vectores y matrices se realiza cuando se declaran dichas variables, asignando normalmente un tamao por exceso y dejando el resto sin usar. Por ejemplo, si realizamos un programa en C que calcula el producto de dos matrices, stas se pueden declarar para un tamao mximo de 100x100 (float matriz[100][100]), de manera que en dicho programa se podr calcular cualquier producto de un tamao igual o inferior a 100x100, pero en el caso de que el producto sea por ejemplo de tamao 3x3, la memoria reservada para esa matriz corresponder al tamao mximo de 100x100, que resulta en una utilizacin ineficiente de la memoria. Por este motivo, es muy til el poder reservar ms o menos memoria en tiempo de ejecucin, segn el tamao del caso concreto que se vaya a resolver. Hablamos, en este caso, de reserva dinmica de memoria. La funcin que permite crear o asignar un espacio de memoria interna durante la ejecucin de un programa es malloc(), que requiere como parmetro el nmero de bytes de memoria que se van a reservar y devuelve un puntero al comienzo de la zona de memoria reservada, o NULL si no es posible reservar la cantidad de memoria pedida. La memoria reservada mediante malloc() puede (y debe) ser liberada una vez utilizada mediante la funcin free() que requiere como parmetro el puntero asignado en malloc(). Como ejemplo de aplicacin simple de la reserva dinmica de memoria, el siguiente programa crea un array de elementos float dinmicamente en funcin del tamao que introduce el usuario. A continuacin se introducen los elementos uno a uno y se calcula la media:

#include <stdio.h> #include <stdlib.h>

int main() { float *numeros, media=0; int i,n; printf("Introduzca el numero de datos: "); scanf("%d", &n); numeros =(float *)malloc(n*sizeof(float)); if (numeros == NULL) printf("Memoria insuficiente\n"); else { for (i=0;i<n;i++) { printf("Introduce el elemento %d: ",i); scanf("%f",&numeros[i]); media += numeros[i]; } media = media/n; printf("\nLa media es: %f\n\n",media); free(numeros); } return 0;

Lo primero que hay que notar del cdigo anterior es que el array est definido como un puntero a un float en lugar de utilizar la notacin para declarar los arrays de manera esttica. Una vez que el usuario introduce el nmero de elementos que tendr el array (variable n), mediante la funcin malloc() se realiza la reserva de un nmero de bytes que permita almacenar n elementos de tipo float. Para ello, utilizamos la funcin sizeof() que devuelve el nmero de bytes del tipo de dato o de la variable que se le pasa como argumento. Por ejemplo, en este caso sizeof(float) devolvera un 4, que al multiplicarlo por n nos dar el nmero exacto de bytes necesario para almacenar en memoria los datos que introducir el usuario. La funcin malloc() devuelve un puntero de tipo void (es decir, un puntero genrico) por lo que es necesario hacer una conversin de tipo de puntero (en este caso float *) de acuerdo con el tipo de dato que se desea almacenar. A partir de este momento, la variable numero guardar la direccin de comienzo del array en memoria y a partir de este momento la utilizacin del array es la comn. En el resto del ejemplo, si la memoria no se pudo asignar correctamente, se finaliza la ejecucin del programa. Por ltimo, debemos liberar siempre la memoria cuando ya no sea necesaria su utilizacin, en este caso con la sentencia free(numeros). Un aspecto que debe ser tratado cuando se trabaja con funciones, es la posibilidad de que se realice reserva dinmica de memoria dentro de una funcin. El siguiente cdigo corresponde a un programa que crea un array de elementos dinmicamente en funcin del tamao que introduce el usuario y, a continuacin, pide los elementos del array uno a uno y calcula la media. En este caso se han utilizado funciones para realizar las operaciones bsicas de reserva de memoria y de clculo de la media:

#include <stdio.h> #include <stdlib.h> float *reserva_local(float *, int); float calcula_media(float *, int); int main() { float *numeros, media; int i,n; printf("Introduzca el numero de datos: "); scanf("%d", &n); numeros = reserva_local(numeros,n); if (numeros == NULL) printf("Memoria insuficiente\n"); else { for (i=0;i<n;i++) { printf("Introduce el elemento %d: ",i); scanf("%f",&numeros[i]); } media = calcula_media(numeros,n); printf("\nLa media es: %f\n\n",media); free(numeros); } return 0; } float *reserva_local(float *array, int tam) { array = (float *) calloc(tam*sizeof(float)); return(array); } float calcula_media(float *array, int num) { int i; float media=0; for(i=0;i<num;i++) media += array[i]; return (media/num); }

Como podemos observar en el cdigo, la funcin reserva_local() recibe como parmetro el puntero definido en el main() y el nmero de elementos que va a introducir el usuario. Tras realizar la llamada a malloc(), la funcin devuelve un puntero que apunta a la primera direccin de memoria asignada a array. Por este motivo, la funcin es de tipo float *. Vemos tambin que en la funcin calcula_media() el array de nmeros se recibe como primer parmetro en forma de puntero y no con el formato de matriz esttica que hemos usado hasta el momento. Esto es posible porque cuando desde el main() se llama a la funcin calcula_media() lo que se pasa como primer argumento es el puntero numeros, es decir, la direccin de memoria donde comienza el array, y por tanto, una variable tipo float * que es donde se almacena.

Para finalizar este ejemplo, debemos notar que en este ejemplo se ha usado la funcin de reserva dinmica calloc(), que es idntica a malloc() pero que inicializa a cero las posiciones reservadas. Podemos extender la idea de los vectores dinmicos a matrices dinmicas, pero debemos tener en cuenta que no podemos gestionar la matriz como una sucesin de elementos contiguos, sino como un vector dinmico de vectores dinmicos. A continuacin se muestra el cdigo fuente de un programa que solicita al usuario el nmero de filas y columnas de una matriz, reserva memoria dinmicamente y calcula el producto de los elementos de la diagonal principal y el de los elementos de la diagonal secundaria:
#define <stdio.h> int main() { float **m = NULL; int filas, columnas; float principal = 1.0, secundaria = 1.0; printf("Introduce el numero de filas: "); scanf("%d", &filas); printf("Introduce el numero de columnas: "); scanf("%d", &columnas); /* reserva de memoria */ m = malloc(filas * sizeof(float *)); for (i=0; i<filas; i++) m[i] = malloc(columnas * sizeof(float)); /* operaciones sobre los elementos de la matriz */ for (i=0; i<filas; i++) { principal *= m[i][i]; secundaria *= m[i][filas-1-i]; } printf("Diagonal principal: %f y secundaria: %f\n",principal, secundaria); /* liberacin de memoria */ for (i=0; i<filas; i++) free(m[i]); free(m); m = NULL; return 0; }

Ahora la matriz se declara como un doble puntero float **m , es decir, como un puntero a un puntero. Esto es as porque realmente estamos definiendo un vector dinmico de vectores dinmicos. En la instruccin
m = malloc(filas * sizeof(float *));

se reserva memoria para un nmero filas de punteros a float. Por ejemplo, supongamos que el usuario introduce un valor 4 para filas. La asignacin de memoria para m sera:

El vector m es un vector dinmico cuyos elementos son punteros (del tipo oat *) pero esos punteros no apuntan a ninguna zona de memoria reservada. Esta operacin se realiza con la instruccin siguiente:
for (i=0; i<filas; i++) m[i] = malloc(columnas * sizeof(float));

Este bucle proporciona un bloque de memoria para m[0], m[1], m[2], . . obteniendo algo as:

De este modo, cuando accedemos, por ejemplo, al elemento m[1][2], estaremos accediendo inicialmente a m, que es de tipo oat **, y que almacena la direccin de comienzo del array de punteros m. As, al acceder a m[1], que es de tipo oat *, estaremos accediendo a la direccin de memoria de comienzo del array m[1], que representa la fila 1 de la matriz global. Por ltimo, al acceder a m[1][2], que es de tipo oat, estamos accediendo a la posicin 2 del array m[1], que s contiene un dato float. Como vemos, es equivalente a trabajar directamente con una matriz, aunque est estructurado como un array de arrays. En el programa anterior, a nivel puramente prctico, tras ejecutar estas 3 instrucciones de reserva de memoria, tendremos una matriz exactamente igual que en el caso de reserva esttica. Finalmente, en las lneas siguientes, se libera la memoria:
for (i=0; i<filas; i++) free(m[i]); free(m); m = NULL;

Hemos de liberar cada uno de los bloques reservados y hemos de empezar a hacerlo por los de segundo nivel, es decir, por los de la forma m[i] (si liberamos m antes que todos los m[i], perderemos el puntero que los referencia y, en consecuencia no podremos liberarlos).

El paso de matrices dinmicas a funciones tiene varias formas posibles. Por ejemplo, si una funcin recibe una matriz de enteros para mostrar su contenido por pantalla, la cabecera ideal sera: void muestra_matriz ( int ** m ) El parmetro indica que es de tipo puntero a punteros a enteros. Una forma alternativa de decir lo mismo es esta: void muestra_matriz ( int * m[] ) Esto sera un vector de punteros a entero, pero ambas expresiones son equivalentes a vector de vectores a entero. En cambio, la cabecera: void muestra_matriz ( int m[][] ) no es correcta en C, ya que se entiende que queremos pasar una matriz esttica y que hemos omitido el nmero de columnas.

12. Estructuras
Una estructura es un tipo de dato definido por el usuario y que est compuesto por datos de tipos diferentes agrupados bajo un mismo nombre. Las estructuras ayudan a organizar datos complicados, particularmente en programas grandes, ya que permiten tratar como una unidad a un conjunto de variables relacionadas, en lugar de tratarlas como entidades independientes. Una estructura se define en C a travs de la siguiente sintaxis:
struct Nombre { tipo1 Campo1; tipo2 Campo2; ... tipoN CampoN; };

La instruccin:
struct Nombre Var1;

Declara una variable del tipo "struct Nombre", esto es, el compilador reserva la cantidad de memoria sufuciente para mantener la estructura ntegra (es decir espacio para almacenar Campo1, Campo2, ..., CampoN). Cuando se hace referencia a la variable Var, se esta haciendo referencia a la estructura ntegra. Se puede inicializar una estructura externa o esttica aadiendo a su definicin la lista de inicializadotes, por ejemplo:
struct Fecha { int Dia; char *Mes; int Anio; }; struct Fecha Hoy = {8,"Mayo",2008}, VarFecha; ... VarFecha = Hoy;

La asignacin VarFecha = Hoy copia la estructura integra Hoy en VarFecha. Cuando dentro de los campos de una estructura aparecen punteros y uno realiza este tipo de asignacin, se esta copiando tambin los valores de los punteros, de tal manera que se puede estar haciendo referencia a un dato desde dos puntos diferentes lo que puede causar efectos no deseados y un potencial peligro para la aplicacin. Un campo de una estructura se utiliza como una variable ms. Para referenciar un campo de una estructura se emplea el operador .
Hoy.Dia = 24; Hoy.Mes = "Agosto"; Hoy.Anio = 1991;

Ejemplo para verificar ao bisiesto:


Bisiesto = Fecha.Anio%4 == 0 && Fecha.Anio%100 != 0 || Fecha.Anio%400 == 0;

El uso de estructuras permite que una funcin modifique y devuelva ms de un tipo de dato, ya que lo que se devuelve es la estructura. De cara a ilustrar esta posibilidad, a continuacin mostramos un programa que calcula el rea y la longitud de un crculo a partir del radio del mismo que es pedido al usuario por teclado usando, inicialmente, paso de argumentos por referencia:
#include <stdio.h> #include <stdlib.h> #include <math.h> void circulo(float radio, float *p_area, float *p_circunferencia) { *p_area = M_PI * radio * radio; *p_circunferencia = 2.0 * M_PI * radio; } int main() { float radio, area, circunferencia; printf(Introduce el radio: "); scanf("%f", &radio); circulo(radio, &area, &circunferencia); printf("Area: %.2f Circunferencia: %.2f\n", area, circunferencia); return 0; }

El mismo programa usando una estructura sera:


#include <stdio.h> #include <stdlib.h> #include <math.h> struct s_circulo { float area; float circunferencia; }; struct s_circulo f_circulo(float radio) { struct s_circulo circulo; circulo.area = M_PI * radio * radio; circulo.circunferencia = 2.0 * M_PI * radio; return circulo; } int main() { float radio; struct s_circulo circulo; printf(Introduce el radio: "); scanf("%f", &radio); circulo = f_circulo(radio); printf("Area: %.2f Circunferencia: %.2f\n", circulo.area, circulo.circunferencia);

return 0; }

En el programa anterior hemos utilizado una funcin que devuelve un dato de tipo estructura struct s_circulo. El uso de arrays de estructuras es realmente til en C, ya que permite realizar programas muy complejos a nivel de variables con una organizacin simple. Por ejemplo, el siguiente programa pide desarrollar un programa en C que permita al usuario introducir el nombre de un fichero que contenga los nombres de una serie de paises y calcule, mediante una funcion, el de mayor longitud y guarde su nombre en otro fichero. El fichero origen deber incluir en la primera lnea el nmero de pases que hay en l, es decir una cabecera. Un posible archivo origen contendra: 5 Suecia Alemania Francia Polonia Rusia Y el archivo de salida guardara: El pais de mayor longitud es Alemania con 8 letras
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXNOMBRE 50 struct datos_pais { char nombre[MAXNOMBRE]; int longitud; }; /* funcion estandar que borra el buffer del teclado */ void borra_buffer_teclado() { int c; } while((c = getchar()) != '\n' && c != EOF);

/* funcion que devuelve la palabra de mayor longitud */ int mayor_palabra(struct datos_pais *pais, int num) { int i, pos_mayor, valor_mayor; valor_mayor = 0; //valor inicial for (i=0;i<num;i++) if (pais[i].longitud > valor_mayor) { pos_mayor = i; valor_mayor = pais[i].longitud; } return pos_mayor; }

int main() { int i,numpaises,pos_mayor; struct datos_pais *pais; //array de estructuras FILE *fichero; char nombreFichero[MAXNOMBRE]; do { printf("\nIntroduzca el nombre del fichero origen: "); scanf("%s",nombreFichero); fichero = fopen(nombreFichero, "rt"); if (fichero == NULL) printf("\nError, el fichero no existe\n"); } while (fichero == NULL); fscanf(fichero,"%d",&numpaises); //leo la cabecera /* reservamos memoria para el array de estructuras */ pais = (struct datos_pais *) malloc(numpaises*sizeof(struct datos_pais)); for (i=0;i<numpaises;i++) { fscanf(fichero,"%s\n",pais[i].nombre); pais[i].longitud = strlen(pais[i].nombre); } pos_mayor = mayor_palabra(pais,numpaises); fclose(fichero); do { printf("\nIntroduzca el nombre del fichero destino: "); scanf("%s",nombreFichero); fichero = fopen(nombreFichero, "wt"); if (fichero == NULL) printf("\nError, el fichero no existe\n"); } while (fichero == NULL); fprintf(fichero,"\nEl pais de mayor longitud es %s con %d letras\n", pais[pos_mayor].nombre,pais[pos_mayor].longitud); fclose(fichero); free (pais); return 0; }

En este caso, se ha utilizado un array de estructuras reservado dinmicamente. Este array se declara como:
struct datos_pais *pais;

Es decir, definimos un puntero a una estructura de tipo datos_pais. A continuacin, tras haber ledo la cabecera del fichero, se realiza la reserva de memoria:
pais = (struct datos_pais *) malloc(numpaises*sizeof(struct datos_pais));

que tiene el formato habitual, con la nica complejidad aadida de que el tipo de dato es ahora struct datos_pais. Este nos ha permitido utilizar un array donde cada elemento no es un dato de un tipo simple, sino un dato compuesto a su vez de varios tipos.

Apndice A. Normas de estilo de programacin


Estas normas de estilo de programacin, estn basada en las que pueden ser consultadas en la pgina web: http://www.ieev.uma.es/fundinfo/matdoc/normasC.html correspondientes a la asignatura de Fundamentos de Informtica de la Universidad de Mlaga. Han sido ligeramente modificadas y adaptadas para el contexto de la actual asignatura y conforman una serie de buenos hbitos de programacin, es decir, una serie de indicaciones sobre el formato de los programas que el alumno debera seguir siempre. Estos consejos de estilo no son propios del lenguaje C, sino que pueden ser aplicados a cualquier otro lenguaje. Independientemente de los algoritmos usados, hay muchas formas y estilos de programar. La legibilidad de un programa es demasiado importante como para prestarle la atencin que merece por parte del programador. Es frecuente que uno deba modificar un programa escrito por otro programador o bien que deba actualizar un programa escrito por uno mismo en el pasado. Es entonces, cuando surgen los problemas de legibilidad de un programa. Para poder modificarlo, primero hay que comprender su funcionamiento, y para facilitar esta tarea el programa debe estar escrito siguiendo unas normas bsicas. La tarea de mantenimiento del software (corregir y ampliar los programas) es una de las tareas ms laboriosas del ciclo de vida del software. Por eso, al programar debemos intentar que nuestros programas sean lo ms expresivos posibles, para ahorrarnos tiempo, dinero y quebraderos de cabeza a la hora de modificarlos. Adems, el C es un lenguaje que se presta a hacer programas complejos y difciles de comprender. En C se pueden encapsular rdenes y operadores, de tal forma que, aunque consigamos mayor eficiencia su comprensin sea todo un reto. Unas normas de estilo en programacin, son tan importantes que todas las empresas dedicadas a programacin imponen a sus empleados una mnima uniformidad, para facilitar el intercambio de programas y la modificacin por cualquier empleado, sea o no el programador inicial. Por supuesto, cada programa debe ir acompaado de una documentacin adicional, que aclare detalladamente cada mdulo del programa, objetivos, algoritmos usados, ficheros... No existen un conjunto de reglas fijas para programar con legibilidad, ya que cada programador tiene su modo y sus manas y le gusta escribir de una forma determinada. Lo que s existen son un conjunto de reglas generales, que aplicndolas, en mayor o menor medida, se consiguen programas bastante legibles. Aqu intentaremos resumir estas reglas:

Identificadores significativos
Un identificador es un nombre asociado a un objeto de programa, que puede ser una variable, funcin, constante, tipo de datos... El nombre de cada identificador debe identificar lo ms claramente posible al objeto que identifica (valga la redundancia). Normalmente los identificadores deben empezar por una letra, no pueden contener espacios (ni smbolos raros) y suelen tener una longitud mxima que puede variar, pero que no debera superar los 10-20 caracteres para evitar lecturas muy pesadas.

Se recomienda encarecidamente la utilizacin de letras minsculas para definir los nombres de las variables y las funciones y de maysculas para las constantes y los define. Un identificador debe indicar lo ms breve y claramente posible el objeto al que referencia. Por ejemplo, si una variable contiene la nota de un alumno de informtica, la variable se puede llamar nota_informatica. Observe que no ponemos los acentos, los cuales pueden dar problemas de compatibilidad en algunos sistemas. El carcter '_' es muy usado para separar palabras en los identificadores. Es muy normal usar variables como i, j o k para nombres de ndices de bucles (for, while...), lo cual es aceptable siempre que la variable sirva slo para el bucle y no tenga un significado especial. En determinados casos, dentro de una funcin o programa pequeo, se pueden usar este tipo de variables, si no crean problemas de comprensin, pero esto no es muy recomendable. Para los identificadores de funcin se suelen usar las formas de los verbos en infinitivo, seguido de algn sustantivo, para indicar claramente lo que hace. Por ejemplo, una funcin podra llamarse escribir_opciones, y sera ms comprensible que si le hubiramos llamado escribir o escrOpc. Si la funcin devuelve un valor, su nombre debe hacer referencia a este valor, para que sea ms expresivo usar la funcin en algunas expresiones, como:
precio_total = precio_total + IVA(precio_total,16) + gastos_transporte(destino);

Constantes simblicas
En un programa es muy normal usar constantes (numricas, cadenas...). Si estas constantes las usamos directamente en el programa, el programa funcionar, pero es ms recomendable usar constantes simblicas, de forma que las definimos al principio del programa y luego las usamos cuando haga falta. As, conseguimos principalmente dos ventajas: 1. Los programas se hacen ms legibles: Es ms legible usar la constante simblica PI como el valor de que usar 3.14 en su lugar:
volumen_esfera = 4/3. * PI * pow(radio,3);

2. Los programas sern ms fciles de modificar: Si en un momento dado necesitamos usar PI con ms decimales (3.141592) slo tenemos que cambiar la definicin, y no tenemos que cambiar todas las ocurrencias de 3.14 por 3.141592 que sera ms costoso y podemos olvidarnos alguna. En C, las constantes simblicas se suelen poner usando una orden al preprocesador de C, quedando definidas desde el lugar en que se definen hasta el final del fichero (o hasta que expresamente se indique). Su formato general es:
#define CONSTANTE valor

que se encarga de cambiar todas las ocurrencias de CONSTANTE por el valor indicado en la segunda palabra (valor). Este cambio lo realiza el preprocesador de C, antes de empezar la compilacin. Por ejemplo:
#define PI 3.141592

Como hemos dicho, las constantes se suelen poner completamente en maysculas y las variables no, de forma que leyendo el programa podamos saber rpidamente qu es cada cosa. En general, se deben usar constantes simblicas en constantes que aparezcan ms de una vez en el programa referidas a un mismo ente que pueda variar ocasionalmente. Obsrvese, que aunque el valor de es constante, podemos variar su precisin, por lo que es recomendable usar una constante simblica en este caso, sobre todo si se va a usar en ms de una ocasin en nuestro programa. Puede no resultar muy til dedicar una constante para el nmero de meses del ao, por ejemplo, ya que ese valor es absolutamente inalterable.

Comentarios
El uso de comentarios en un programa escrito en un lenguaje de alto nivel es una de las ventajas ms importantes con respecto a los lenguajes mquina, adems de otras ms obvias. Los comentarios sirven para aumentar la claridad de un programa, ayudan para la documentacin y bien utilizados nos pueden ahorrar mucho tiempo. No se debe abusar de comentarista, ya que esto puede causar una larga y tediosa lectura del programa, pero en caso de duda es mejor poner comentarios de ms. Por ejemplo, es absurdo poner:
Nota = 10; /* Asignamos 10 a la variable Nota */

Los comentarios deben ser breves y evitando divagaciones. Se deben poner comentarios cuando se crean necesarios, y sobre todo: Al principio del programa o de cada fichero del programa que permita seguir un poco la historia de cada programa, indicando: Nombre del programa, objetivo, parmetros (si los tiene), condiciones de ejecucin, mdulos que lo componen, autor o autores, fecha de finalizacin, ltimas modificaciones realizadas y sus fechas... y cualquier otra eventualidad que el programador quiera dejar constancia. En cada sentencia o bloque (bucle, if, switch...) que revista cierta complejidad, de forma que el comentario indique qu se realiza o cmo funciona. Al principio de cada funcin cuyo nombre no explique suficientemente su cometido. Se debe poner no slo lo que hace sino la utilidad de cada parmetro, el valor que devuelve (si lo hubiera) y, si fuera oportuno, los requisitos necesarios para que dicha funcin opere correctamente. En la declaracin de variables y constantes cuyo identificador no sea suficiente para comprender su utilidad. En los cierres de bloques con '}', para indicar a qu sentencias de control de flujo pertenecen, principalmente cuando existe mucho anidamiento de sentencias y/o los bloques contienen muchas lneas de cdigo.

No olvidemos que los comentarios son textos literarios, por lo que debemos cuidar el estilo, acentos y signos de puntuacin.

Estructura del programa


Un programa debe ser claro, estar bien organizado y que sea fcil de leer y entender. Casi todos los lenguajes de programacin son de formato libre, de manera que los espacios no importan, y podemos organizar el cdigo del programa como ms nos interese. Para aumentar la claridad no se deben escribir lneas muy largas que se salgan de la pantalla y funciones con muchas lneas de cdigo (especialmente la funcin principal). Una funcin demasiado grande demuestra, en general, una programacin descuidada y un anlisis del problema poco estudiado. Se deber, en tal caso, dividir el bloque en varias llamadas a otras funciones ms simples, para que su lectura sea ms agradable. En general se debe modularizar siempre que se pueda, de forma que el programa principal llame a las funciones ms generales, y estas vayan llamando a otras, hasta llegar a las funciones primitivas ms simples. Esto sigue el principio de divide y vencers, mediante el cual es ms fcil solucionar un problema dividindolo en subproblemas (funciones) ms simples. A veces, es conveniente usar parntesis en las expresiones, aunque no sean necesarios, para aumentar la claridad. Cada bloque de especial importancia o significacin, y cada funcin debe separarse de la siguiente con una lnea en blanco. A veces, entre cada funcin se aaden una linea de asteriscos o guiones, como comentario, para destacar que empieza la implementacin de otra funcin (aparte de los comentarios explicativos de dicha funcin). El uso de la sentencia GOTO est totalmente desaconsejado. Como dicen Kerninghan y Ritchie en su libro de C, su utilizacin slo estara justificada en casos muy especiales, como salir de una estructura profundamente anidada, aunque para ello recomendamos usar los comandos break, continue, return o la funcin exit(). De todas formas, esos recursos, para salir de bucles anidados deben usarse lo menos posible y slo en casos donde quede bien clara su finalidad. Si no queda clara su finalidad, ser mejor que nos planteemos de nuevo el problema y estudiemos otra posible solucin que seguro que la hay. Normalmente, un programa en C se suele estructurar de la siguiente forma: 1. Primero los comentarios de presentacin, como ya hemos indicado. 2. Despus, la inclusin de bibliotecas del sistema, los ficheros .h con el #include y entre ngulos (<...>) el nombre del fichero. Quizs la ms tpica sea:
#include <stdio.h>

3. Bibliotecas propias de la aplicacin. Normalmente, en grandes aplicaciones, se suelen realizar varias libreras con funciones, separadas por su semntica. Los nombres de fichero se ponen entre comillas (para que no las busque en el

directorio de las bibliotecas o libreras estndar) y se puede incluir un comentario aclarativo:


#include "raton.h" /*Rutinas para control del ratn*/ #include "cola.h" /*Primitivas para el manejo de una cola*/

4. Variables globales, usadas en el mdulo y declaradas en otro mdulo distinto, con la palabra reservada extern. 5. Constantes simblicas y definiciones de macros, con #define. 6. Definicin de tipos, con typedef. 7. Declaracin de funciones del mdulo: Se escribir slo el prototipo de la funcin, no su implementacin. De esta forma, el programa (y el programador) sabr el nmero y el tipo de cada parmetro y cada funcin. 8. Declaracin de variables globales del mdulo: Se trata de las variables globales declaradas aqu, y que si se usan en otro mdulo debern llevar, en el otro mdulo, la palabra extern. 9. Implementacin de funciones: Aqu se programarn las acciones de cada funcin, incluida la funcin principal. Normalmente, si el mdulo incluye la funcin principal, la funcin main(), sta se pone la primera, aunque a veces se pone al final y nunca en medio. El resto de funciones, se suelen ordenar por orden de aparicin en la funcin principal y poner juntas las funciones que son llamadas desde otras. Es una buena medida, que aparezcan en el mismo orden que sus prototipos, ya que as puede ser ms fcil localizarlas. Naturalmente, este orden no es estricto y pueden cambiarse algunos puntos por otros, pero debemos ser coherentes, y usar el mismo orden en todos los mdulos. Por ejemplo, es frecuente saltarse la declaracin de los prototipos de las funciones poniendo en su lugar la implementacin y, por supuesto, dejando para el final la funcin main(). Otro punto muy importante es el referente a variables globales. En general es mejor no usar nunca variables globales, salvo que sean variables que se usen en gran parte de las funciones (y mdulos) y est bien definida y controlada su utilidad. El uso de estas variables puede dar lugar a los llamados efectos laterales, que provienen de la modificacin indebida de una de estas variables en algn mdulo desconocido. Lo mejor es no usar nunca variables globales y pasar su valor por parmetros a las funciones que estrictamente lo necesiten, viendo as las funciones como cajas negras, a las que se le pasan unos determinados datos y nos devuelve otros, perfectamente conocidos y expresados en sus parmetros. Por la misma razn que no debemos usar variables globales, no se deben usar pasos de parmetros por referencia (o por variable), cuando no sea necesario. A continuacin mostramos un programa que calcula el factorial de un nmero de un nmero y que utiliza variables locales:
#include <stdio.h> #include <stdlib.h> double f_factorial(double numero) { double factorial = 1; while (numero > 1) factorial *= numero--;

return factorial; } int main() { double numero = 0, factorial = 1; while (numero >= 0) { printf("Introduce un numero positivo (en caso contrario el programa finaliza): "); scanf("%lf", &numero); if (numero == 0) printf("El factorial vale 1\n"); else { if (numero > 1) factorial = f_factorial(numero); printf("El factorial vale %.0lf\n", factorial); } } return 0; }

El mismo programa, simplemente realizado con variables globales, no funciona (se deja como ejercicio al alumno averiguar por qu). Aunque la programacin se simplifica al evitar el envo y devolucin de parmetros a la funcin, al final el esfuerzo deber ser realizado en la etapa de depuracin tratando de encontrar dnde est el fallo:
#include <stdio.h> #include <stdlib.h> double numero = 0, factorial = 1; void f_factorial() { while (numero > 1) factorial *= numero--; } int main() { while (numero >= 0) { printf("Introduce un numero positivo (en caso contrario el programa finaliza): "); scanf("%lf", &numero); if (numero == 0) printf("El factorial vale 1\n"); else { if (numero > 1) f_factorial(); printf("El factorial vale %.0lf\n", factorial); } } return 0; }

De hecho, el principal problema derivado del uso de variables globales reside en encontrar dnde se produce la modificacin de una variable que provoca que el programa no funcione correctamente. Este problema es ms grave cuanto ms complejo

sea el programa y mayor nmero de funciones utilice. Como ejemplo, mostramos a continuacin un programa que simplemente pide nmeros al usuario y va calculando y mostrando por pantalla la media acumulada. Existe un fallo en este cdigo que el alumno deber encontrar y que viene derivado del uso de una variable global en varios puntos y funciones del programa:
#include <stdio.h> #include <stdlib.h> #define MAX 20 int datos[MAX],contador,num_datos; float media; void media_acumulada() { media = 0; for (contador=0; contador < num_datos; contador++) media += datos[contador]; printf("La media acumulada es: %f\n",media/num_datos); } int main() { printf("\nCalculo del valor medio\n"); printf("Introduce el numero de datos a tratar (MAX %d): ",MAX); scanf("%d",&num_datos); for(contador=0;contador<num_datos;contador++) datos[contador] = 0; contador=0; do { printf("Introduce un numero entero: "); scanf("%d",&datos[contador]); contador++; media_acumulada(); } while (contador < num_datos); return 0; }

Indentacin o sangrado
La indentacin o sangrado consiste en marginar hacia la derecha todas las sentencias de una misma funcin o bloque, de forma que se vea rpidamente cuales pertenecen al bloque y cuales no. Algunos estudios indican que el indentado debe hacerse con 2, 3 4 espacios. Usar ms espacios no aumenta la claridad y puede originar que las lneas se salgan de la pantalla, complicando su lectura. La indentacin es muy importante para que el lector/programador no pierda la estructura del programa debido a los posibles anidamientos. Normalmente, la llave de comienzo de una estructura de control ({) se pone al final de la lnea y la que lo cierra (}) justo debajo de donde comienza -como veremos ms adelante- pero algunos programadores prefieren poner la llave { en la misma columna que la llave }, quedando una encima de otra. Eso suele hacerse as, en la

implementacin de funciones, donde la llave de apertura de la funcin se suele poner en la primera columna. Veamos, a continuacin, las indentaciones tpicas en las estructuras de control: Sentencia if-else Primera opcin:
if (condicin) { sentencia1; sentencia2; ... } else { sentencia1; sentencia2; ... }

Segunda opcin:
if (condicin) { sentencia1; sentencia2; ... } else { sentencia1; sentencia2; ...

Sentencia swith Primera opcin:


switch (expresin) { case expresin1:sentencia1; sentencia2; ... break; case expresin2:sentencia1; sentencia2; ... break; . . . case default2: sentencia1; sentencia2; ... }

Segunda opcin
switch (expresin) { case expresin1:sentencia1; sentencia2; ... break; case expresin2:sentencia1; sentencia2; ... break; . . . case default2: sentencia1; sentencia2; ... }

Sentencia for Primera opcin:


for (exp1;exp2;exp3) { sentencia1; sentencia2; ... }

Segunda opcin:
for (exp1;exp2;exp3) { sentencia1; sentencia2; ... }

Sentencia while Primera opcin:


while (condicin) { sentencia1; sentencia2; ... }

Segunda opcin:
while (condicin) { sentencia1; sentencia2; ... } do-while

Sentencia Primera opcin:


do { sentencia1; sentencia2; ... } while (condicin);

Segunda opcin:
do { sentencia1; sentencia2; ... } while (condicin);

Aunque estos formatos no son en absoluto fijos, lo que es muy importante es que quede bien claro las sentencias que pertenecen a cada bloque, o lo que es lo mismo, donde empieza y termina cada bloque. En bloques con muchas lneas de cdigo y/o con muchos anidamientos, se recomienda aadir un comentario al final de cada llave de cierre del bloque, indicando a qu sentencia cierra. Por ejemplo:
for (exp1;exp2;exp3) { sentencia1; sentencia2; ... while (condicin_1) { sentencia1; sentencia2; ... if (condicin) { sentencia1; sentencia2; ... while (condicin_2) { sentencia1; sentencia2; ... } /*while (condicin_2)*/ } /*if*/ else { sentencia1; sentencia2; ... if (condicin) { sentencia1; sentencia2; ...

} else { sentencia1; sentencia2; ... } } /*else*/ } /*while (condicin_1)*/ } /*for*/

Por otro lado, las indentaciones para las funciones deben llevar siempre las llaves de apertura y cierre siempre se ponen a la en la primera columna, como se muestra en la funcin factorial() y en el main() del siguiente ejemplo:
double factorial(double numero) { double factorial = 1; while (numero > 1) factorial *= numero--; return factorial; } int main() { double numero = 0, factorial = 1; while (numero >= 0) { printf("Introduce un numero positivo: "); scanf("%lf", &numero); if (numero == 0) printf("El factorial vale 1\n"); else { if (numero > 1) factorial = f_factorial(numero); printf("El factorial vale %.0lf\n", factorial); } } return 0; }

Un cdigo mal indentado es difcil de leer y, por tanto, de arreglar en caso de errores. El siguiente programa muestra un ejemplo claro de programa que tiene un fallo y que, debido a la mala indentacin del mismo, es complicado averiguar donde se encuentra. Recomendamos al alumno que trate de encontrar dicho fallo arreglando previamente la indentacin:
#include <stdio.h> #include <stdlib.h> #define SIZE 25 int main() {

int f1, f2, c1, c2, i, j, k; int matriz1[SIZE][SIZE], matriz2[SIZE][SIZE], matrizr[SIZE][SIZE]; printf("Introd el scanf("%d", &f1); printf("Introd el scanf("%d", &c1); printf("Introd el scanf("%d", &f2); printf("Introd el scanf("%d", &c2); num de filas de la primera matriz (max.%d): ", SIZE); num de columnas de la primera matriz (max.%d): ", SIZE); num de filas de la segunda matriz (max.%d): ", SIZE); num de columnas de la segunda matriz (max.%d): ", SIZE);

if ((f1 < 1) || (f1 > SIZE) || (c1 < 1) || (c1 > SIZE) || (f2 < 1) || (f2 > SIZE) || (c2 < 1) || (c2 > SIZE) || (c1 != f2)) { printf("Error!"); } else { printf("Introduce los elementos de la primera matriz:\n"); for (i = 0; i < f1; i++) for (j = 0; j < c1; j++) scanf("%d", &(matriz1[i][j])); printf("Introduce los elementos de la segunda matriz:\n"); for (i = 0; i < f2; i++) for (j = 0; j < c2; j++) scanf("%d", &(matriz2[i][j])); for (i = 0; i < f1; i++) for (j = 0; j < c2; j++) matrizr[i][j] = 0; for (k = 0; k < c1; k++) matrizr[i][j] += matriz1[i][k] * matriz2[k][j]; printf("La matriz resultante es:\n"); for (i = 0; i < f1; i++) { for (j = 0; j < c2; j++) printf("%d ", matrizr[i][j]); printf("\n"); } printf("\n"); } return 0; }

13. Apndice B
Contenido del fichero de texto necesario para la solucin del segundo ejemplo de la parte de ficheros: 0 7 2 5 0 0 4 8 4 5 9 5 8 1 3 4 9 0 2 5 7 5 2 7 2 3 6 4 9 5 6 4 3 2 1 4 6 4 0 0 1 0 6 9 6 3 4 5 5 7 6 3 0 6 9 0 7 6 6 5 6 9 3 9 6 7 5 5 0 9 7 7 3 3 8 9 8 1 4 1 1 3 0 3 2 2 4 1 7 74 25 56 6 8 47 84 41 57 100 51 88 17 37 46 97 10 24 60 77 58 26 76 21 40 68 48 94 54 69 50 39 22 15 42 62 44 1 4 13 3 65 92

3 0 1 9 7 6 6 8 1 6 3 1 8 8 0 3 2 9 9 8 5 3 8 9 6 6 7 9 5 6 2 7 4 4 1 7 1 8 0 8 7 2 1 1 3 3 9 7 7 5

1 1 3 2 9 9 0 9 9 3 7 7 6 1 4 4 9 7 5 5 8 2 4 8 2 5 8 0 2 6 2 4 2 8 8 2 1 8 8 0 7 7 0 5 0 5 4 0 1 4

32 2 14 93 80 70 61 90 20 64 38 18 87 82 5 35 30 98 96 86 59 33 85 99 63 66 79 91 53 67 23 75 43 49 19 73 12 89 9 81 78 28 11 16 31 36 95 71 72 55

8 2 5 4 2 3

2 6 1 4 8 3

83 27 52 45 29 34

Você também pode gostar