You are on page 1of 83

Escuela Politécnica Superior

Manual práctico programación en C
Métodos Informáticos Ingeniería Industrialalidad

Curso 2009/2010

Índice
1.   Introducción............................................................................................................... 3   2.   Entrada / salida básica. .............................................................................................. 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   Ordenación.................................................................................................................. 44   9.   Cadenas de caracteres.............................................................................................. 49   10.   Matrices ................................................................................................................... 55   11.   Reserva dinámica de memoria................................................................................. 60   12.   Estructuras ............................................................................................................... 66   Apéndice A. Normas de estilo de programación............................................................ 70   Identificadores significativos...................................................................................... 70   Constantes simbólicas................................................................................................. 71   Comentarios................................................................................................................ 72   Estructura del programa.............................................................................................. 73   Indentación o sangrado............................................................................................... 76   13.   Apéndice B .............................................................................................................. 81  

Antonio Becerra Permuy

1.

Introducción.

El objetivo de este documento es proporcionar una orientación práctica al alumno de primer curso de programación en ingeniería. En ningún caso estos apuntes pretenden ser el único material necesario para aprender a programar en C, y siempre deberán ir apoyados los apuntes o transparencias que se proporcionan en clase de teoría o bien de uno de los libros incluidos en la bibliografía. El alumno encontrará en este documento aquellos aspectos de la programación en C que, a lo largo de los años, hemos encontrado que se deben recalcar más 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 descripción desde el punto de vista conceptual, pero no inciden en cómo elaborar los algoritmos. Todos los ejemplos aquí mostrados están realizados con el entorno de desarrollo que se utiliza este año en las clases de prácticas: el Codeblocks. Este programa está disponible en su página web (http://www.codeblocks.org/) para las plataformas Windows, Linux y Mac OSX.

2.

Entrada / salida básica.

La inmensa mayoría de los programas necesitan conocer para su ejecución uno o más datos (que denominaremos datos de entrada) y proporcionan, como resultado, uno o más datos (datos de salida). En caso contrario, estaríamos ante un programa que siempre realiza la misma función y que no tiene ningún tipo de relación con el exterior, lo cual no es muy común. • Los datos de entrada pueden ser introducidos en el programa por medio de un fichero o por algún dispositivo de entrada como un teclado. • Los datos de salida pueden almacenarse también en un fichero o pueden mostrarse por algún dispositivo de salida, como una pantalla o una impresora. A continuación 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 inglés) Codeblocks que incluye, además 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 gráfico dependerá del sistema operativo):

Figura 1: Aspecto del Codeblocks

Para crear una nueva aplicación es necesario pinchar sobre la opción de la ventana cental Create a new project o bien acceder al menú File, ahí seleccionar New y después Project. Toda aplicación es, para Codeblocks, un proyecto. Si bien los ejemplos que veremos en este curso son muy sencillos y todo el código fuente estará únicamente en un fichero, una aplicación real suele tener su código 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 aplicación.

Es muy importante que en este paso no seleccionemos la opción C++ porque es otro lenguaje diferente de C y no es el objeto de esta asignatura. donde en la parte izquierda de la ventana . donde nos pregunta el compilador y el tipo de configuración (Debug o Release) que deseamos crear. guiones. 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). en la primera ventana marcamos Next y en la siguiente escogemos la opción C y volvemos a marcar Next. Al escoger el nombre del proyecto y su ubicación. el proyecto debería tener un nombre significativo del programa que almacena de cara a una identificación posterior simple y únicamente debería contener letras y números separados. Figura 2: Selección del tipo de proyecto Codeblocks tiene plantillas para distintos tipos de proyectos. etc y la letra ñ nunca se deben poner en el nombre del proyecto. sin ningún tipo de interfaz gráfica. ficheros de un proyecto sobrescribirán ficheros de otro proyecto (por ejemplo. Al pinchar sobre el botón Go nos aparece un asistente.Tras seleccionar la opción para crear un nuevo proyecto nos aparece el cuadro de diálogo de la Figura 2.c o las carpetas obj y bin). Además. puntos. que se corresponde con el perfil de nuestra aplicación. Nosotros escogeremos siempre Console Application. ya que de lo contrario. pinchamos en Next y nos aparece la última ventana del asistente. dejaremos estas opciones tal cual aparecen y simplemente seleccionamos Finish. con “guión bajo”. el fichero main. las dos casillas Project Filename y Resulting filename se rellenan automáticamente. acentos. Una vez finalizados estos pasos. Otros caracteres como espacios. Por defecto. Es conveniente que el directorio sea distinto para cada proyecto. Codeblocks nos crea un esqueleto para nuestra aplicación que puede verse en la Figura 3. si es necesario.

Este tipo de instrucción forma parte del contenido de este tema y se verá en detalle a continuación. A continuación tenemos la función main.c inicial que nos crea Codeblocks. Todo programa en C debe tener al menos la función main. opcionalmente. puede tener una serie de parámetros de entrada (argumentos) y. stdio. que para compilar y ejecutar este programa de prueba bastaría con ir al menú Build y seleccionar Build and Run (o bien seleccionar la tecla F9). Estos ficheros. la primera línea indica el tipo de dato del valor que devuelve. en caso contrario. el nombre de la función y los argumentos. por ejemplo para leer de teclado y escribir por pantalla. Esto es particularmente útil cuando se invoca un programa (“hijo”) desde otro (“padre”) ya que. main no tiene porque devolver ningún dato. Todo programa en C se divide en funciones. si es que devuelve alguno. Figura 3: Console Application generada por defecto La instrucción printf que aparece en la línea 6 del programa muestra por pantalla el mensaje “Hola mundo” en inglés.”). A la hora de definir una función. que contiene una carpeta source que almacena el archivo main.h y stdlib. En principio. Como se puede observar. Los detalles sobre el funcionamiento del resto de .aparece el explorador del proyecto. contienen definiciones de constantes y funciones útiles en cualquier aplicación de consola. el código fuente que crea por defecto el Codeblocks hace uso de ese convenio y devuelve un 0 cuando finaliza correctamente (sentencia “return 0.h. Una función es un conjunto de sentencias que realiza una determinada tarea y que. por último. 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 algún error. pero por convenio se suele hacer que devuelva el número 0 si el programa finaliza correctamente y otro número distinto de 0. dependiente de la aplicación. también opcionalmente. Las dos primeras líneas son directivas del compilador que incluyen sendos ficheros en el de vuestra aplicación. Decir. que es la función por la cual se empieza a ejecutar un programa. en ese caso. puede devolver un resultado.

Es por eso que las variables en scanf van precedidas por un signo & que se puede interpretar como “dirección de memoria de”. esto no es deseable. Y no lo es porque dificulta el entendimiento del funcionamiento del programa y.menús y opciones del Codeblocks serán explicadas en clase y además se pueden consultar en la documentación on-line: http://www. Finalmente. nos dé el área del círculo con dicho radio: #include <stdio. tras pedir al usuario el valor del radio. no la coma. una llamada a una función o una expresión aritmética. ya que el mantenimiento de un programa que utiliza variables globales es más 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 más difícil. siempre que sea posible se deben utilizar variables locales. printf("Introduce el radio del circulo: ").org/user-manual. scanf no necesita el valor de una variable. Otro aspecto a mencionar en este primer programa es que la sintaxis de C fue elaborada por anglosajones. A continuación vamos a hacer un primer programa en C que. Uno de los errores más frecuentes al empezar a programar en C consiste en un paso de parámetros a las funciones printf y scanf incorrecto. por ejemplo. Como regla general. ya que va a sobrescribir dicho contenido con lo que se lea por teclado.”. Un programa en C podría escribirse. No se pueden utilizar caracteres acentuados y ni la letra “ñ” en los nombres de las variables o las funciones. scanf("%f". Un programa no sólo debe funcionar correctamente y . por tanto. por tanto es una variable local. En la normas de estilo que aparecen en el Apéndice A se muestran ejemplos concretos que apoyan esta afirmación. si no la dirección de memoria donde se guarda el contenido de esa variable. En C. el elemento sintáctico que determina el final de una sentencia es el “. Sin embargo. mientras que scanf lee de teclado y almacena lo leído en variables. Si estuviese definida fuera de la función. return 0.codeblocks. Las variables globales son accesibles desde cualquier función de un programa. } El programa sólo usa una variable: radio. Esta variable está definida dentro de la función main. si así se desease. printf escribe en la pantalla. printf("El area del circulo es %f\n". Lo que printf escribe por pantalla puede ser texto predeterminado o algún dato proveniente de una constante. resaltar la importancia de una correcta indentación en el código fuente. Obviamente.h> int main() { float radio.14159 * radio * radio). sería una variable global. su mantenimiento. Las variables locales sólo son accesibles dentro de la función en la que son definidas.h> #include <stdlib. 3. &radio). mientras que eso no sucede con printf. después de los #include. en una sola línea. una variable. con lo que: El separador de decimales es el punto.

modificamos el ejercicio anterior de forma que el programa no sólo nos dé el área del círculo para un radio determinado. siempre es mejor definir las constantes como tales o almacenarlas en variables que no introducirlas directamente en las sentencias que las usen.eficientemente. aquí hemos utilizado una variable pi para almacenar en ella el valor del número π en vez de usar una constante. todas las sentencias se escriben de tal forma que disten la misma distancia del margen izquierdo. sino también la longitud de la circunferencia para ese mismo radio: #include <stdio. A continuación.0 antes de multiplicarse por pi. el código se separa x caracteres adicionales del margen izquierdo. printf("La longitud de la circunferencia es %.h> int main() { float radio. ya que lo primero facilita el mantenimiento del código (si queremos cambiar la constante sólo es necesario cambiarla en un sitio) y es menos propenso a cometer errores (cuántas más veces escribamos un número. o de una sentencia iterativa. En el Apéndice A de este manual. 2 * pi * radio). printf("Introduce el radio del circulo: "). Por ejemplo. scanf("%f". si no que debe estar programado de tal forma que realizar modificaciones en él en el futuro sea lo más fácil posible.3f\n". La expresión 2 * pi * radio produce como resultado un número en punto flotante. } A diferencia del ejercicio anterior. Ninguna línea debe de pasar de 80 caracteres para facilitar su impresión. ambas expresiones producen resultados distintos: 3*5/2 . Si una sentencia excede de esa longitud. donde x es un número constante para todo el programa. • A lo largo de los ejemplos presentes en este manual. system("PAUSE"). 2 es convertido a 2. Cuando en una expresión aparecen valores en punto flotante y valores enteros. return 0. pi=3. En general. &radio). cuando se empieza a escribir el código de una función.3f\n". los enteros son convertidos a punto flotante antes de realizar la operación. más probabilidades hay de que nos equivoquemos haciéndolo). Sin embargo.h> #include <stdlib. de una sentencia condicional. printf("El area del circulo es %. se podrá observar como se aplican estas reglas de indentación. pi * radio * radio). Uno de los elementos que ayudan a esto es la correcta indentación del código fuente: • • Al empezar a escribir un programa. Este tipo de conversiones automáticas deben de tenerse en cuenta. Por tanto. se continúa en la línea siguiente dejando nuevamente x caracteres adicionales en el margen izquierdo. mostramos unas normas básicas de estilo de programación en C que serán aplicables a cualquier programa realizado a partir de ahora.14159.

todas las instancias de lo definido por su valor antes de realizar la compilación.14159 int main() { float radio. ya que tanto la multiplicación como la división son con números enteros. printf("La longitud de la circunferencia es\t%.3f\n". &radio). return 0.h> #define PI 3. obviamente. eso sería lo mismo que poner 2 = 3. se realiza mediante la directiva #include: #include <stdio. Ej: . } En programación. PI * radio * radio). Esto tiene las siguientes implicaciones: • No se puede cambiar el valor de una constante. la segunda da como resultado 7. a la que no se le puede cambiar el valor inicial una vez se le ha asignado este. no tiene sentido.5. posteriormente. desde el momento en el que aparece el #define. las constantes es mejor definirlas como tales o introducirlas en variables antes que explicitarlas cada vez que se requieren. Esto es muy fácil de ver. Sin embargo.0 La primera da como resultado 7. es decir.3 * 5 / 2. printf("Introduce el radio del circulo:\t\t"). En los printf de este ejercicio podemos ver también cómo limitar el número de decimales de un número en punto flotante a la hora de imprimirlo por pantalla: %. lo cual. scanf("%f". ya que al ser 2.3f provoca que sólo se impriman 3 decimales. hacemos NUMERO = 3. en el ejemplo anterior las apariciones de PI se cambiarían por 3. una constante se puede pensar como una variable que no es tal. Como se ha comentado con anterioridad.0 un número en punto flotante el resultado de la multiplicación (15) es convertido a otro número en punto flotante y la división es una división de números en punto flotante. printf("El area del circulo es\t\t\t%. Es decir. Lo que realmente hace #define es sustituir literalmente en el código fuente.14159 antes de compilar. Si tenemos un #define NUMERO 2 y. Lo primero. en C es un poco distinto. si no que puede ser cualquier cosa y puede tener incluso argumentos como si fuese una función. Sin embargo.h> #include <stdlib. 2 * PI * radio). • Un #define no tiene porque restringirse a un valor numérico.3f\n". en C.

en cualquier lado del código fuente. 2 * pi_radio). reservando las minúsculas para las variables.h> int main() { float radio. CUADRADO(x)). scanf("%d". Por ejemplo.3f\n". &x). pi_radio = M_PI * radio. éstas se suelen poner en mayúsculas. &x). printf("Dime un numero: "). En los printf de la última versión mostrada del programa que calcula el área de un círculo. &radio). &y). que representa el carácter tabulador y.h> #include <math. .y. a la hora de imprimirlo por pantalla se substituye por espacios en blanco.h> #include <stdlib. scanf("%d". printf("El cuadrado de %d es %d\n".3f\n". } A la hora de definir constantes. printf("La longitud de la circunferencia es\t%. scanf("%d". Para finalizar con el apartado de entrada / salida básica. aparece el carácter especial “\t”.y)/2). se pueda saber de forma inmediata cuando algo es una constante / definición o una variable / función. printf("Dime otro numero: ").#include <stdio. x. esto sería erróneo (aunque fácilmente corregible poniendo paréntesis alrededor de x+y en el #define): #include <stdio. return 0. printf("Introduce el radio del circulo:\t\t"). por tanto. veremos una versión más del programa que calcula el área de un círculo: #include <stdio. pi_radio. pi_radio * radio).h> #include <stdlib.h> #define SUMA(x. printf("El area del circulo es\t\t\t%. scanf("%f".h> #include <stdlib. } Hay que tener cuidado de que no se produzca ningún error sintáctico o semántico en el código fuente tras sustituir el valor.y) x+y int main() { int x.h> #define CUADRADO(x) (x*x) int main() { int x. printf("La mitad de la suma entera es %d\n". return 0. para que. printf("Dime un numero: "). SUMA(x.

el número π está aquí definido bajo la constante M_PI y con mucha mayor precisión que la que nosotros empleamos en los ejemplos anteriores.14159265358979323846 1.12837916709551257390 1. se pone a la derecha. si no que debe de hacerlo de forma eficiente. Notar que la asignación de un valor a una variable es siempre de la forma variable = expresión. por ejemplo. Figura 4: Aplicación que calcula la circunferencia y el área del círculo a partir del radio. Anteriormente hemos comentado que un programa no sólo debe de funcionar. es decir. En la siguiente captura de pantalla podemos ver el resultado de ejecutar el programa. por lo que en realidad no es necesario definirlo en nuestros programas.7182818284590452354 1. } La utilización de número π es muy habitual.78539816339744830962 0. Podemos hacer un #include del fichero math.63661977236758134308 1. hemos creado una variable temporal pi_radio en la que guardamos el valor resultante de multiplicar pi por radio.70710678118654752440 Como se puede ver. De esta forma. logaritmos. etc. o la expresión que producirá un valor a almacenar en la variable. calcular raíces cuadradas.43429448190325182765 0.31830988618379067154 0. Estas son las constantes definidas en math. Por tanto. . la variable se pone a la izquierda del signo igual y el valor a almacenar en la variable.return 0.41421356237309504880 0.h que incluye definiciones de números habituales en Matemáticas y de las funciones implementadas en la librería matemática estándar de C (estas funciones nos permiten.).57079632679489661923 0. ¿para qué repetir dos veces la misma operación? En esta última versión del programa.30258509299404568402 3.69314718055994530942 2. en los dos printf usamos el valor de esta variable en vez de repetir la operación.4426950408889634074 0.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. potencias.

&c). es importante volver a recalcar la importancia de dos cosas: • • Los programas deben ser eficientes y evitar repetir cálculos de forma innecesaria: usamos las variables raiz y dos_a para almacenar resultados temporales. Aunque el número de bytes usado para representar un float o un double depende de la arquitectura de la máquina y del sistema operativo en ella instalado. (-b + raiz) / dos_a). Una posible primera implementación sería esta: #include <stdio. scanf("%lf". ya que en caso contrario sólo raiz estaría siendo dividida por dos_a.4 * a * c). definida en math.&b). (-b . scanf("%lf".h> #include <math. raiz. scanf("%lf". printf("Introduce el valor de \"c\":\t"). b y c para los cuales no es posible realizar alguna de las operaciones aritméticas y el programa abortará con un error.&a). cuando se quiere introducir ese carácter en la propia cadena de caracteres es necesario anteponer el carácter ‘\’.h> int main() { double a. printf("Una solucion es:\t\t%lf\n". Para ilustrar la utilización de las sentencias condicionales utilizaremos como ejemplo un programa que calculará las raíces reales de una ecuación de segundo grado. dos_a = 2 * a. b. Como las comillas se usan para empezar y finalizar las cadenas de caracteres. en las máquinas usadas en clase de prácticas se usan 4 bytes para los float y 8 bytes para los double.h.h> #include <stdlib.3. Para imprimir o leer un double se usa el indicador de formato %lf. Este tipo de dato se usa para almacenar números en punto flotante que necesitan de una precisión o rango de representación mayor que el que proporciona el tipo float. return 0. } En este programa vemos varias cosas nuevas: • Las variables de tipo double. Sentencias condicionales. Estos casos son los siguientes: .raiz) / dos_a). Ahora bien. raiz = sqrt(b * b . printf("Introduce el valor de \"a\":\t"). • • • Además. por eso es necesario agrupar (b+raiz) y (-b-raiz) entre paréntesis. Los operadores * y / tienen más prioridad que + y -. Para calcular la raíz cuadrada de un número usamos la función sqrt. dos_a. este programa es incorrecto porque no contempla determinados casos particulares de a. c. printf("La otra solucion es:\t\t%lf\n". printf("Introduce el valor de \"b\":\t").

\n"). scanf("%lf". es importante tener claro que probar el correcto funcionamiento de un programa no es trivial. Antes de proseguir. En este caso. si multiplicamos dos números muy grandes es posible que excedamos el rango de representación del tipo de datos y obtengamos un resultado incorrecto.&b). raiz. Vemos. dos_a = 2 * a. dos_a.&c). nos encontramos con que la ecuación sólo tiene una solución (-c/b).h> #include <stdlib. printf("La otra solucion es:\t\t%lf\n". Comprobar que no se pueden dar situaciones de desbordamiento. scanf("%lf". (-b . } . Algunos consejos prácticos son: • • • Comprobar que no se pueden dar divisiones por cero. a es 0 => no se puede hacer una división por 0. else printf("Esto no es una ecuacion.h> #include <math. ejecutar un bloque de instrucciones cuando se cumple una condición y ejecutar otro bloque de instrucciones distinto en caso contrario. Por ejemplo. explicada en clase.h> int main() { double a. printf("Una solucion es:\t\t%lf\n". Para ello utilizaremos la instrucción condicional if (…) … else…. por tanto. -c / b).\n"). Aunque la verificación formal no entra dentro del programa de esta asignatura. b. c. que en este programa es necesario ejecutar unas instrucciones u otras en función del valor de unas variables. printf("Introduce el valor de \"b\":\t"). Asignación else { raiz = b * b .• • (b * b – 4 * a * c) es negativo => no se puede hacer la raíz cuadrada de un número negativo ya que la función sqrt sólo trabaja con números reales. y que permite precisamente eso. 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. printf("Introduce el valor de \"a\":\t"). Comparación printf("Introduce el valor de \"c\":\t"). El programa quedaría así: #include <stdio.raiz) / dos_a). Comprobar que no se pueden dar raíces cuadradas de números negativos. scanf("%lf".&a).4 * a * c. if (a == 0) if (b != 0) printf("La solucion es:\t\t\t%lf\n". Si esta situación se produce lo que debemos hacer es avisar al usuario de que no existen raíces reales para la ecuación planteada. if (raiz >= 0) { raiz = sqrt(raiz). } else printf("La ecuacion no tiene raices reales. (-b + raiz) / dos_a).

recalcaremos varios aspectos que suelen ser fuente de errores al empezar a programar en C: • Los operadores para realizar una comparación de igualdad y para realizar una asignación son distintos. } . Si se usa el operador incorrecto (asignación en vez de comparación o comparación en vez de asignación). z = x. printf("Introduce un numero: "). que siempre imprimirá el valor de x. printf("Introduce otro numero: "). es sintácticamente correcto aunque no tenga sentido ya que no se utiliza el resultado para nada: #include <stdio. scanf("%d". return 0. if (y > z) z == y. Una comparación es una expresión que devuelve “1” si se verifica y “0” en caso contrario. printf("El mayor es %d\n". z.&x). Para lo primero se usan dos símbolos “=” adyacentes mientras que para lo segundo un único símbolo “=”. y.h> #include <stdlib. si no que el programa no se comportará como cabría esperar. Ponerla en una sentencia como en el siguiente ejemplo. z). Esto es así porque ambos tipos de expresión tienen un significado completo como sentencia. no se producirá ningún error de compilación.&y). } Figura 5: Aplicación que calcula las raíces reales de una ecuación de segundo grado Sobre este programa.return 0. scanf("%d".h> int main() { int x.

” después de la condición del if. y. Nuevamente.• Por otra parte. No tiene sentido ponerlo después de la condición porque. y).&x). y. } • No se pone el carácter “. x).h> #include <stdlib. scanf("%d". si no que el programa no se comporta como el alumno espera ya que poner un “. scanf("%d". Supongamos estos dos programas. else printf(“El mayor es %d\n”.\n").” no siempre se produce un error (sólo se producirá si hay un else asociado al if). . scanf("%d". z.h> int main() { int x.h> int main() { int x.\n"). El carácter “. return 0. diciendo que los números son distintos cuando “y” vale 0 y que son iguales en cualquier otro caso: #include <stdio. printf(“El mayor es %d\n”.h> #include <stdlib. if (x = y) printf("Los numeros son iguales. printf("Introduce otro numero: "). si se pone el “. else printf("Los numeros son distintos. una asignación toma el valor lógico de cierto si el valor almacenado en la variable es distinto de “0” y toma el valor lógico de falso cuando es “0”. scanf("%d". if (x > y). printf("Introduce un numero: "). printf("Introduce un numero: "). sirve para finalizar sentencias. y.h> int main() { int x. El siguiente programa funciona de forma totalmente incorrecta. hay que indicar al menos una instrucción a ejecutar en el caso de que la condición se cumpla. necesariamente.&x). printf("Introduce un numero: "). return 0. mientras que el segundo siempre imprimirá el valor de y: #include <stdio. el primero dará error de compilación.&y).&y).&y). printf("Introduce otro numero: "). scanf("%d". en C.” tiene el significado asociado de “sentencia vacía”.h> #include <stdlib.&x).”. scanf("%d". } #include <stdio. printf("Introduce otro numero: ").

es un ejemplo de uso de estos operadores lógicos para construir expresiones lógicas complejas: Figura 6: Aplicación que muestra el número de días de un mes #include <stdio. printf(“El mayor es %d\n”. z = y. printf("Escribe el mes (1-12): "). else printf("El mes tiene 31 dias.h> int main() { int mes. El siguiente programa.\n"). else printf("Ese mes no existe!\n"). scanf("%d". return 0. return 0. z). if (y > z). if ((mes >= 1) && (mes <= 12)) if (mes == 2) printf("El mes tiene 28 dias.\n").\n"). } Se pueden anidar expresiones lógicas sencillas para construir otras más complejas mediante el uso de los operadores lógicos “&&”.h> #include <stdlib. que pregunta un mes y nos dice cuántos días tiene.z = x. “||” y “!”. } . else if ((mes == 4) || (mes == 6) || (mes == 9) || (mes == 11)) printf("El mes tiene 30 dias. &mes).

x && y). printf("Introduce otro numero: "). . &opcion). &lado1).h> int main() { char opcion. printf("\tc) Circulo.) … else …” encadenados. ponerlos ayuda a dar mayor claridad al código fuente. que calcula el área de una figura geométrica u otra en función de lo seleccionado por el usuario.h> int main() { int x. gran parte de los paréntesis no son necesarios (los que rodean las comparaciones).&y). ilustra este comentario: #include <stdio. printf("Selecciona una figura para calcular el area (a. printf("\tb) Rectangulo. los operadores “&&” y “||” pueden confundirse fácilmente con los operadores bit a bit “&” y “|” respectivamente y no se producirá ningún error de compilación si no que el programa puede no funcionar correctamente. Al igual que pasaba con los operadores de comparación de igualdad y asignación. lado1 * lado1). dos importantes aspectos a tener en cuenta: • Salvo que se tenga muy clara la prioridad de los distintos operadores del lenguaje C. b o c):\n"). printf("El \"and\" de los dos números vale %d\n". return 0. printf("\ta) Cuadrado.Nuevamente. lado2.h> #include <math.. y. El siguiente ejemplo. scanf("%c". scanf("%f"..3f\n".h> #include <stdlib.\n"). scanf("%d". usar “switch (…) case…” reduce un poco el tamaño del código fuente y nos evita usar numerosos “if (. printf("El area vale %.h> #include <stdlib.&x). El siguiente programa que calcula el and bit a bit de dos números funciona incorrectamente (cuando el resultado no es el número 0 imprime siempre 1) por culpa de utilizar “&&” en vez de “&”: #include <stdio.\n"). float lado1. printf("Introduce un numero: "). es recomendable usar los paréntesis para agrupar subexpresiones. En el ejemplo anterior. scanf("%d". } • “if (. pero incluso aunque se sea consciente de este hecho.) … else …” es la sentencia condicional más usada y más genérica en lenguaje C. pero existe otra “switch (…) case…” que es interesante en determinadas circunstancias.\n"). Cuando tenemos que mirar el valor de una variable de tipo entero y realizar distintas acciones en función de dicho valor. radio. switch (opcion) { case 'a': printf("Introduce el lado del cuadrado: ").

rectángulo o círculo Observaciones: • • Para almacenar un carácter se usa una variable de tipo char. case 'b': printf("Introduce un lado del rectangulo: "). printf("Introduce el otro lado del rectangulo: ").3f\n".3f\n". case 'c': printf("Introduce el radio del circulo: ").break. printf("El area vale %. opcion nunca podría ser un número en punto flotante ni tampoco una cadena de caracteres (como se verá en el capítulo correspondiente). la cual se lee de teclado / imprime por pantalla usando el identificador de formato %c. break. lado1 * lado2). Aunque char se usa para guardar caracteres. default: printf("Opcion no valida!\n"). &lado1). scanf("%f". &radio). como lo pueda ser int. Notar que para indicar que a. break. • . ya que una letra se representa por un número (código ASCII). es en realidad un tipo de dato entero. Por eso opcion puede ser un char. mientras que si usamos comillas dobles el error se deberá a que el compilador pensará que son cadenas de caracteres. scanf("%f". &lado2). Figura 7: Aplicación que calcula el área de un cuadrado. Si no se usan comillas tendremos un error porque el compilador pensará que son variables. scanf("%f". Sin embargo. printf("El area vale %. M_PI * radio * radio). } } return 0. b y c son caracteres se rodean por comillas simples.

la siguiente sentencia que se ejecutaría sería la escrita en la línea de abajo. El uso de break en cualquier otra situación.• break se usa para salir del switch. Si no se pusiese. al romper la secuencia del código y dificultar su comprensión. en este sentido. aun perteneciendo a un case distinto. un caso muy particular de C que no se repite para ningún otro tipo de sentencia. Es. está totalmente desaconsejado y va en contra de los principios de la programación estructurada. .

Si desconocemos el número del cual hay que calcular el factorial. factorial. Un ejemplo de lo primero. Para calcular el factorial de un número natural se multiplica éste por ese mismo número menos 1. Por supuesto. &numero).4.h> int main() { long int numero. printf("Introduce un numero: ").h> #include <stdlib. do…while y for. Un ejemplo de lo segundo es el programa que veremos a continuación: cálculo del factorial de un número. el resultado se vuelve a multiplicar por el número menos dos y así sucesivamente hasta llegar a multiplicar por 1. o repetir esa operación hasta que se cumpla una condición determinada. Bucles En ocasiones es necesario realizar una misma operación sobre un conjunto de datos. scanf("%ld". algunas más eficientes. A continuación mostramos una posible implementación de un programa que resuelve este problema. Figura 8: Aplicación que calcula el factorial de un número #include <stdio. sería sumar dos vectores. existen muchas otras formas de implementarlo. pero esta nos permitirá observar claramente las diferencias entre los tres tipos de sentencias iterativas de C. desconocemos también cuántas multiplicaciones habrá que hacer. . if (numero < 0) printf("No existe el factorial de un numero negativo!\n"). que veremos en el tema en el que se traten los arrays. Este tipo de sentencias se conocen como iterativas y en C disponemos de tres: while. con lo que es necesario utilizar una sentencia que nos permita realizar la multiplicación mientras no lleguemos al 1.

en realidad. caso. no obtendremos ningún error. En las máquinas del laboratorio. Las variables deberían ser. double para tener un mayor rango de representación aunque los números a almacenar sean enteros.h> #include <stdlib.h> int main() { int x=5. primero se utiliza el valor actual de la variable en la sentencia en la que aparezca y luego se incrementa / decrementa ese valor.else { factorial = 1. no tendremos un resultado preciso. pero el programa. ambos tipos pueden representar un entero entre -2147483648 y 2147483647. factorial). Y aun así. while (numero > 1) { factorial = factorial * numero. Se entenderá mejor con el siguiente ejemplo: #include <stdio. no se debe poner punto y coma después de la condición del while. la asignación y la expresión aritmética 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 también se muestra que. Imprime 5 . • Cuando se realiza una operación aritmética con dos operadores donde uno de ellos es a su vez la propia variable en la cual se va a almacenar el resultado. En el primer caso. primero se incrementa / decrementa el valor y luego se utiliza el resultado en la sentencia. printf("%d\n". El estándar dice que el rango de números representable por un long int es igual o mayor que el de un int. y por las mismas razones. } return 0. • Al igual que con el if. } printf("El factorial vale %ld\n". En el segundo. } Consideraciones: • Las variables son esta vez de tipo long int. pero otras arquitecturas o con otro compilador los rangos podrían ser distintos. sólo funciona con números menores o iguales a 12.y ++x/--x. x). Puede parecer un rango suficiente. Para números mayores. existen otras dos formas simplificadas de expresar lo mismo. en el caso particular de sumar uno o restar uno. para números no muy grandes. si no un resultado incorrecto ya que el código generado por el compilador no detecta situaciones de desbordamiento. Es muy importante recalcar que hay una substancial diferencia semántica entre x++/x-. en realidad. numero = numero – 1.

factorial.h> int main() { int x=5. return 0.h> #include <stdlib. else { factorial = 1. } x). &numero). x). el ejemplo anterior del factorial puede escribirse de la siguiente forma: #include <stdio. ya que se pueden crear sentencias poco claras. } Notar como se ha utilizado %. ++x.h> #include <stdlib. x). printf("%d\n". deben utilizarse paréntesis siempre que se considere adecuado con el objetivo de facilitar la compresión del código fuente. printf("El factorial vale %. return 0.0lf\n". Ej: #include <stdio. x++). (x++) + (--x)). pero eso sería todavía mucho menos claro: aunque no sean necesarios. factorial). Imprime 6 Imprime 7 Imprime 7 Imprime 9 Como consideración final al respecto de estas formas abreviadas de expresar algunas operaciones aritméticas en C. while (numero > 1) factorial *= numero--. Teniendo en cuenta todo esto. } return 0.en la misma sentencia conjuntamente con otros operadores.h> int main() { double numero. } Imprime 8 Imprime 5 En este último ejemplo. printf("%d\n". decir que sólo se deben de usar si se está absolutamente seguro de cómo funcionan. printf("%d\n". printf("Introduce un numero: "). printf("%d\n". printf("%d\n".0lf para imprimir un double sin decimales. scanf("%lf". ++x). if (numero < 0) printf("No existe el factorial de un numero negativo!\n"). el primer printf podría escribirse como printf("%d\n".x++. x+++--x). especialmente utilizando ++ y -. printf("%d\n". .

factorial. Las sentencias dentro de un do…while() se ejecutan al menos una vez. y para que el programa funcione correctamente en el caso de que el número introducido por el usuario sea el 0.A continuación veremos cómo realizar el mismo programa utilizando las otras dos sentencias iterativas de C. mientras que en el do…while() se evalúa después. en el cual puede darse el caso de que nunca se ejecuten.0lf\n". &numero).h> int main() { double numero. ya que en este caso la condición es la última parte de una sentencia do…while(). } #include <stdio.h> #include <stdlib. } while (numero > 1). hemos tenido que añadir un if adicional y poner el do…while() . Actualización if (numero < 0) printf("No existe el factorial de un numero negativo!\n"). else { factorial = 1. printf("El factorial vale %. printf("Introduce un numero: "). numero=numero-1) factorial = factorial * numero. Inicialización Condición printf("Introduce un numero: "). if (numero > 1) do { factorial = factorial * numero.h> #include <stdlib. else { for (factorial = 1. scanf("%lf". a diferencia del while() en el cual al menos una sentencia tiene que seguir a la condición. &numero).h> int main() { double numero. numero > 1.1. if (numero < 0) printf("No existe el factorial de un numero negativo!\n"). factorial. En este caso no se ha utilizado ningún tipo de abreviatura en las operaciones como en el caso anterior: #include <stdio. factorial). scanf("%lf". } return 0. numero = numero . Esto es una diferencia con el while(). Esto es así porque en el while() la condición se evalúa antes de ejecutar el contenido del bucle. } return 0.0lf\n". factorial). } Consideraciones al respecto de la versión con do…while(): • • Ahora sí hay un punto y coma después de la condición. printf("El factorial vale %. Por esto.

Por lo general. si no se está seguro de la sintaxis de las otras sentencias. Igualmente. printf("Introduce un numero: "). Aunque no se ha comentado explícitamente. D) C • Las partes de inicialización y actualización pueden ser mucho más complejas e incluir varias sentencias separadas por comas. recomiendo optar por el uso de while(). una sentencia iterativa puede contener sentencias condicionales o más sentencias iterativas. sólo se usa do…while() cuando sabemos a ciencia cierta que el contenido del bucle debe ejecutarse siempre al menos una vez. else { printf("Los numeros perfectos entre 1 y %d son: ". contador <= candidato.h> #include <stdlib. El siguiente ejemplo corresponde a un programa en el que se pide al usuario realizar un programa que lea un número positivo. se puede establecer la siguiente equiparación entre for() y while(): A while(B) { C D } for (A. Cualquier bucle puede implementarse con cualquiera de las tres sentencias iterativas. n. contador++) if ((candidato % contador) == 0) . candidato. candidato <= numero. ya que puede producir código fuente muy confuso.dentro de ese if. candidato++) { suma = 0. pero hay que tener cuidado de no abusar de esa característica. if (numero < 1) printf("El numero ha de ser un entero positivo\n"). por teclado y muestre por pantalla todos los números perfectos entre 1 y n: NOTA: Un número es perfecto cuando es igual a la suma de sus divisores excepto él mismo. De hecho. scanf("%d". B.h> int main() { int numero. 3+2+1=6 por tanto 6 es un número perfecto: #include <stdio. &numero). for (contador = 1. 2 y 1. Es decir. un programa puede contener cualquier tipo de sentencia dentro de una sentencia condicional o iterativa. Consideraciones al respecto de la versión con for(): • for() permite escribir de forma más compacta los bucles while(). Ejemplo: ¿Es el número 6 perfecto? Los divisores de 6 son: 3. for (candidato = 1. con lo que. suma. pero while() tiene la sintaxis más clara y fácil de entender. contador. numero). una sentencia condicional puede contener en su interior otras sentencias condicionales o sentencias iterativas.

candidato <= numero. scanf("%d". Este proceso depende del algoritmo que se deba implementar en cada caso. se ha utilizado una sentencia iterativa for que recorre los números entre 1 y el número máximo que ha introducido el usuario. if (suma == candidato) printf("%d ". candidato). } printf("\n"). se dedique un tiempo a tratar de optimizar el código reduciendo el número de operaciones que se llevan a cabo (en sentencias iterativas normalmente). tras realizar una versión inicial que funcione de un programa. } printf("\n"). donde se ha reducido el número 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.h> int main() { int numero. printf("Introduce un numero: "). . if (suma == candidato) printf("%d ". suma. A continuación mostramos una versión más optimizada del programa anterior. for (contador = 2. suma = 1. candidato++) { mitad_candidato = candidato / 2. que en el bucle interior no es necesario recorrer los números hasta el valor de la variable candidato ya que a partir de candidato/2 ya no pueden ser múltiplos: #include <stdio. el bucle más exterior. } return 0. por otro lado. &numero). else { printf("Los numeros perfectos entre 1 y %d son: ". candidato. numero). en este caso. if (numero < 1) printf("El numero ha de ser un entero positivo\n"). mitad_candidato. } Como se puede ver en el código anterior. candidato).h> #include <stdlib. Es importante hacer notar que la variable suma debe ser puesta a cero en cada iteración del bucle más exterior (esto es un error común a la hora de trabajar con variables que acumulan resultados parciales). contador++) if ((candidato % contador) == 0) suma += contador. } return 0. for (candidato = 2.suma += contador. contador. dentro de la cual hay otra sentencia iterativa for (a este procedimiento se le denomina anidar bucles) que recorre los números entre 1 y el número máximo que marca. } Es altamente recomendable que. contador <= mitad_candidato.

que tienen una estructura similar a la de un programa (declaración de variables. que describe la solución completa del problema y consta fundamentalmente de llamadas a funciones. A cada uno de estos problemas más sencillos se les denomina módulo. se ejecutarán todas las instrucciones de la misma. Un programa diseñado mediante la técnica de programación modular está formado por: • El programa principal. los módulos se denominan funciones y es posible establecer la similitud con las funciones matemáticas que disponen de variables sobre las que operan para obtener un resultado. Además. programación modular. el código se convierte cada vez en más difícil de estructurar y. A cada función de un programa en C se le debe asignar un nombre significativo con la tarea que lleva a cabo. programación estructurada y resultado). se vuelve al punto del programa desde donde se llamó dicha función. de comprender dado el gran número de líneas que lo componen. si necesitamos una función que calcule la suma de dos números. y una vez finalizada. claras y sencillas. suelen devolver un resultado que deberá ser tratado en el punto del programa desde el que se llamó a la función. Las funciones pueden (y suelen) recibir parámetros que se declaran a la derecha del nombre de la función y entre paréntesis. y a este tipo de programación. Para dividir un programa en funciones. Esta forma de proceder se conoce como diseño Top-Down. Las funciones. un nombre adecuado sería calculaSuma. El programa principal debe constar de pocas líneas y en él deben estar claros los diferentes pasos del proceso que se seguirá para obtener el resultado. El objetivo de cada función es resolver de modo independiente una parte del problema y sólo serán ejecutadas cuando sean llamadas por el programa principal o por otras funciones. para preservar sus fundamentos. La forma más clara de entender la utilidad de la programación modular y el envío y recepción de parámetros es mediante un ejemplo. A continuación mostramos un programa en C que calcula todas las posibles combinaciones (sin repetición) de N elementos tomados de k en k según la fórmula: . En lenguaje C. hasta llegar a fragmentos lo suficientemente simples. sobre todo. Funciones A medida que los problemas a resolver mediante un programa en C se hacen más complicados. deben ser pequeñas. en caso de constar de varias palabras. Después se intenta dividir estas partes en otras más pequeñas y así sucesivamente. Una buena metodología a la hora de asignar nombres a las funciones consiste en que empiece por minúscula y. Una solución consiste en descomponer los problemas en otros más sencillos que se puedan analizar y programar de forma independiente. éstas se escriban pegadas unas a las otras y con su primera letra mayúscula. Por ejemplo.5. • Las funciones. hay que distinguir las partes que tienen alguna independencia. Cuando en un punto de un programa se llama a una función.

. scanf("%d". printf("Introduzca el valor de N: ").h> long int calcularFactorial(int numero) { long int factorial.! ( )= k!( NN− k )! N k Por ejemplo. } return factorial. if (numero<2) factorial=1. i--) factorial *= i.k.1. long int combinaciones. } int main() { int n. for (i = factorial .h> #include <stdlib. i > 1. las combinaciones de 4 elementos tomados de 3 en 3 se calcularían: ! ( )= 3!(44− =4 3)! 4 3 NOTA: tanto N como k deben ser mayores que 0 #include <stdio. &n). else { factorial=numero. int i.

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 fórmula utilizada para resolver el ejercicio implica realizar 3 veces el cálculo del factorial. Sin programación modular, el código necesario en este caso repetiría 3 veces las mismas instrucciones y esto es, obviamente, muy ineficiente. Sobre la base de la programación modular, hemos creado una función (denominada calcularFactorial) que incluye las líneas de código que calculan dicho factorial una única vez en la totalidad del programa. Consideraciones sobre el programa anterior:

La estructura de la función calcularFactorial es idéntica a la del main(), con una cabecera que incluye el nombre (significativo de lo que hace la función) y el código entre llaves. En el main(), tras la comprobación de errores en los valores de N y k, se ejecuta la instrucción: combinaciones=calcularFactorial(n)/
(calcularFactorial(k)*calcularFactorial(n-k));

donde se llama a la función calcularFactorial tres veces (una en el numerador y dos en el denominador). En cada una de estas llamadas, entre paréntesis aparece un parámetro (n, k y n-k). Cada uno de estos parámetros se pasa a la función, que deberá realizar sus operaciones a partir de estos valores. En el caso de sea necesario pasar más de un parámetro a una función, deberán estar separados por comas y dentro de los paréntesis. La función calcularFactorial devuelve, tras su ejecución, el factorial del número que se le pasa como parámetro. Es necesario, por tanto, almacenar en una variable o crear una expresión que utilice el dato devuelto (en este ejemplo, primeramente se resuelve la expresión matemática y el resultado se almacena en la variable combinaciones). • La declaración de la función tiene la siguiente sintaxis:
long int calcularFactorial(int numero)

Entre paréntesis aparece la única variable que la función recibe como parámetro, en este caso almacenada en int numero. Como vemos, los parámetros que recibe una

función se declaran como una variable más, indicando el tipo de variable y su nombre. Dicho nombre no tiene por qué coincidir con el nombre utilizado por el parámetro en el punto en que se llama a la función. El tipo sí debe coincidir con el tipo de dato que se pasa. Obsérvese que en las llamadas a la función 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 expresión, cuyo resultado también es de tipo int (n-k). Si una función recibe varios parámetros, éstos deberán estar separados por comas. Por otro lado, previo al nombre de la función aparece de nuevo long int, que indica el tipo de dato que devuelve (en este caso) la función. Los tipos de datos que puede devolver una función 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 parámetro 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 función y es la variable que se devuelve en el return. • Vemos que la función calcularFactorial se sitúa 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 función 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 función (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 más que un tipo de variable que almacena direcciones de memoria. La dirección de memoria almacenada normalmente indica la posición 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 declaración implica la reserva de una zona de memoria donde se guardará la dirección 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 básicos: • El operador & que devuelve la dirección 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, tendríamos que incluir la sentencia: punt = &a; de tal forma que punt guardaría la dirección de memoria en que se encuentra a, pero no el valor numérico de a. • El otro operador básico es *, que es complementario a &, ya que devuelve el valor contenido en la dirección que almacena la variable sobre la que se aplica *. Es decir, después de la asignación anterior, podríamos acceder al valor numérico de a de dos formas: printf(“%lf”,a); que es la forma habitual que ya conocemos printf(“%lf”,*punt); que devolvería el mismo resultado utilizando el contenido de la variable a la que apunta punt. El siguiente programa es un buen ejemplo del manejo básico 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 ejecución proporciona la siguiente salida por pantalla:

Vemos cómo la dirección 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 función 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 repetición) de N elementos tomados de k en k y que requería de tres llamadas a la función que calcula el factorial, es un ejemplo típico de paso de parámetros por valor. En aquel caso, la función recibía un único parámetro (int numero) del que se realizaba una copia local en la función, es decir, la variable n del main() no se veía modificada. Además, en dicha función, únicamente se devolvía un parámetro.

Por ejemplo. } else //num2 < num1 { //Se comprueba cual es menor if (*num2<*num3) min=*num2. if (*num1<*num2) { //Se comprueba cual es menor if (*num1<*num3) min=*num1. La idea básica consiste en enviar como parámetro desde el main() la dirección de memoria de las variables que deben ser modificadas. y la función las almacena en punteros.max. A esta forma de pasar parámetros se le denomina paso por referencia e implica que no se realizan copias de los parámetros locales a la función.h“ /* Funcion que dados tres numeros. en el siguiente programa. el mayor y la media de dichos números: #include "stdio. es necesario el uso de punteros. en este caso. float *num3) { float min. mayor y su media */ void calcularMedia(float *num1. else max=*num3. float *num2. //Se comprueba cual es mayor if (*num3<*num2) max=*num2. else min=*num3.En cambio.h" #include "stdlib. dados tres números se pide crear una función que calcule el menor. else min=*num3. . muchas veces es necesario que una función devuelva varios parámetros y. devuelve el menor.

*num1=min. printf(“El menor es: %6. De esta forma.num2. calcularMedia(&num1. *num2 y *num3.3f\n” “Y la media es: %6. } *num3=(*num1+*num2+*num3)/3. se debe calcular el máximo común divisor mediante la siguiente función (algoritmo de Euclides): int mcd(int a.3f\n”. por tres punteros *num1. printf("Introduzca el primer numero").num2. printf("Introduzca el segundo numero "). } int main() { float num1. } Queda claro en este ejemplo la necesidad del uso de punteros. *num2=max. } return a. else a=a-b. return 0.num3. que son recogidas. scanf("%f". Cada vez que se introduce una fracción se debe simplificar (si no lo está) y el resultado de la suma también debe estar simplificado. printf("Introduzca el tercer numero ").num3). scanf("%f". } . num1. num2 y num3 modificadas en la función).&num2. ya que así la función calcularMedia trabaja sobre las variables num1. Es importante resaltar el uso ya explicado del operador & a la hora de mandar las direcciones de las variables num1.&num3). num2 y num3 del main() y no sobre copias. las modificaciones que se hacen sobre *num1. El siguiente código 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. Para ello. scanf("%f".&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.&num3). num2 y num3 a la función. else max=*num3.&num2). obviamente.3f\nEl mayor es: %6.//Se comprueba cual es mayor if (*num3<*num1) max=*num1. int b) { while(a!=b) { if (a<b) b=b-a.

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

den2). do { printf("Introduzca el denominador de la segunda fraccion: "). num2.den1.&num2).den1.num1. } int main() { int num1.num_suma. if (den1 <= 0) printf("El denominador debe ser positivo y mayor que cero\n"). } while (num2 <= 0). return 0. Por otro lado. do { printf("Introduzca el denominador de la primera fraccion: "). } En este caso. if (den_suma == 1) //si no es una fraccion printf("El resultado de sumar %i/%i + %i/%i es: %i\n" . if (entero != 1) //si se pudo simplificar printf("La segunda fraccion simplificada es: %i/%i\n". do { printf("Introduzca el numerador de la segunda fraccion: "). if (num1 <= 0) printf("El numerador debe ser positivo y mayor que cero\n"). else printf("El resultado de sumar %i/%i + %i/%i es %i/%i\n" .&den_suma). if (den2 <= 0) printf("El denominador debe ser positivo y mayor que cero\n").den2. la función simplificarFraccion requiere dos parámetros de entrada que deben ser sobrescritos y dicha función debe ser aplicada 3 veces en el programa. } while (den1 <= 0).&den1). scanf("%i".&den2).num1.&den1). sumarFracciones(num1. scanf("%i".num2. den1 y den2) de los que .&num1).num2.den1.&den2). entero = simplificarFraccion(&num1. if (num2 <= 0) printf("El numerador debe ser positivo y mayor que cero\n").entero. scanf("%i". do { printf("Introduzca el numerador de la primera fraccion: ").den_suma.num2.num1. } while (den2 <= 0).num2.num_suma).num_suma. //Simplificacion de la matriz resultante simplificarFraccion(&num_suma. scanf("%i". } while (num1 <= 0).den2. entero = simplificarFraccion(&num2.den_suma). if (entero != 1) //si se pudo simplificar printf("La primera fraccion simplificada es %i/%i\n".num2.&num_suma. ya que requiere el paso de 4 parámetros por valor (num1.den1).den1. la función sumarFracciones mezcla paso por referencia y paso por valor.den2.*den_suma=d1*d2.den2.&den_suma).

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

scanf("%d".h> #define MAX 100 int main() { int i. es binario. /* almaceno en el fichero los numeros aleatorios */ srand(time(NULL)).. Los ficheros permiten almacenar información de manera permanente en la memoria secundaria (disco duro. Los ficheros de texto almacenan información “legible”.h> #include <stdlib. i<cantidad.7. } . ya que evita que el usuario tenga que teclear demasiado.j. No se puede crear el fichero en” “ la ruta especificada\n\n”). esta es una opción muy común cuando deben ser leídas grandes cantidades de datos.MAX). fclose(fichero).k."\n%f".. Los ficheros de texto pueden ser de dos tipos: de texto o binarios.Pedir al usuario un número N de datos (máximo 100) que desea guardar en un archivo de nombre “datos. FILE *fichero. es decir. Para explicar el funcionamiento práctico de la lectura y escritura de ficheros en C.&cantidad). De hecho. Ficheros Hasta este momento.. cd.. se almacenaban en la memoria principal y dejaban de estar accesibles cuando el programa finalizaba. (float)rand()/RAND_MAX). basada en el código ASCII. for(i=0. } return 0.Almacenar los datos en el archivo anterior #include <stdio.txt” 2. Cualquier fichero que no sea de texto. } while (cantidad>=MAX || cantidad<=0). if (fichero == NULL) printf(“\n\nERROR. dvd.txt”."w"). utilizaremos un programa que realiza lo siguiente: 1. /* abrimos el fichero para escritura */ fichero = fopen(“datos. i++) fprintf(fichero.Crear N datos aleatorios entre 0 y 1 3. int cantidad. los datos que hemos utilizado en nuestros programas. else { do { printf("\nIntroduzca Numero de datos a guardar (maximo %d): ". …) 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 también los puede leer de un fichero.

porque el disco está lleno. Así. porque la ruta especificada no existe. La estructura del programa anterior es muy simple y se basa en la creación de números aleatorios que se almacenan en un fichero.txt”. Antes de usar un fichero es necesario realizar una operación de apertura del mismo y. 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. pero en este ejemplo se ha puesto de forma explícita “datos. Si no se utiliza una semilla distinta cada . En cuanto a la utilización de ficheros. Como segundo parámetro es necesario especificar el modo de apertura de dicho fichero. posteriormente. en nuestro programa accederemos al fichero mediante la variable fichero. que son realmente aleatorios). si se desea almacenar datos en él hay que realizar una operación de escritura.La comprobación de que este ejercicio funciona correctamente se realiza abriendo el archivo de texto “datos. el fichero datos. como la utilizada en este caso: if (fichero == NULL) Lo siguiente que realiza el programa anterior es pedir al usuario el número de datos que quiere guardar en el fichero y después genera dicho número de datos mediante el uso de la función rand(). declarada en stdio. la apertura del fichero se realiza mediante la instrucción fopen: fichero = fopen(“datos. etc). etc). El nombre del fichero se podría solicitar al usuario (lo veremos más adelante). A partir de esta instrucción. Si existe algún tipo de error al realizar la operación de apertura del fichero (por ejemplo.txt”. "w"). porque el usuario no tiene permisos de escritura en el disco.txt” creado y viendo que guarda el número de datos entre 0 y 1 que el usuario ha especificado y que están en desorden (es decir. En este caso. En este sentido. declaramos un puntero que será utilizado para acceder a los ficheros (realmente es un puntero a la estructura FILE. y se abre como escritura. Esto nos permite controlar un error la apertura del fichero mediante una instrucción condicional. fopen devuelve el valor NULL. Para crear números decimales entre 0 y 1 basta con normalizar y cambiar el tipo de dato a float tal y como se hace en el código anterior: (float)rand()/RAND_MAX La función srand(time(NULL)) que se utiliza en este programa inicializa la semilla para la generación de números aleatorios con rand. vemos como mediante la instrucción FILE *fichero. añadir datos al final.h). Cuando ya no se quiera utilizar el fichero se realiza una operación de cierre. La función fopen requiere como primer parámetro una cadena de caracteres que contenga el nombre del fichero que se quiere tratar (y en su caso la ruta de acceso).txt almacenará una serie de números aleatorios. en el programa anterior se lleva a cabo inicialmente la apertura de un fichero para escribir datos de él. que devuelve un número entero aleatorio entre 0 y RAND_MAX (el mayor número entero que puede generar la función rand).

&numero). FILE *fichero. //tras leer los datos se cierra el fichero if ((fichero = fopen(“mayor_menor.vez (esto se consigue mediante la función time). Para ello es necesario realizar una operación de escritura. if (numero>mayor) mayor = numero. } } return 0. if (fichero == NULL) printf("\n\n ERROR.txt” #include <stdio. tras terminar de usar cada fichero.. análoga al printf que hemos usado hasta ahora pero especificando como primer argumento el puntero al fichero donde queremos escribir: fprintf(fichero. fclose(fichero).0.txt”. y de cara a mostrar la lectura de datos de un archivo.h> #include <stdlib.txt” para lectura 2..h> int main() { int i. En este caso utilizaremos dentro de un bucle la función fprintf. debemos cerrarlo utilizando la función fclose."%f". } fclose(fichero). Como continuación del ejercicio anterior.. Por último. float mayor = 0. float numero. Buscar el mayor y el menor de los números almacenados 3."%f\n". No se puede leer el fichero\n\n"). else { /* leo los datos aleatorios del archivo */ while (! feof(fichero)) { fscanf(fichero. menor = 1. (float)rand()/RAND_MAX).."El mayor es %f y el menor %f\n". if (numero<menor) menor = numero. Los números aleatorios que se generan con la función rand deben ser guardados en el fichero. else { fprintf(fichero.."r")."w")) == NULL) printf(“\n\nERROR.txt”. No se puede crear el fichero en” “ la ruta especificada\n\n”). } . siempre obtendríamos los mismos aleatorios en distintas ejecuciones del programa. Abrir el archivo anterior “datos..0.. Guardar dichos números en el archivo “mayor_menor.mayor. //así nos aseguramos la primera comparación /* Abro el archivo anterior como lectura */ fichero = fopen(“datos..menor). a continuación mostramos el código del siguiente programa en C: 1.

"%f". ni de que exista una cabecera en el fichero. para obtener los datos originales hay que efectuar una operación de lectura de fichero. no conoceremos el número de datos que almacena el fichero. } lee los datos hasta que se alcanza el final del archivo sin necesidad de que el usuario introduzca cuántos hay. se cierra el fichero de lectura. En ciertos casos. permite reutilizar la variable fichero como es el caso del ejercicio anterior. además. Su estructura es similar al scanf que ya hemos utilizado en los capítulos anteriores."%f". Una de las posibles instrucciones de lectura es fscanf.&numero). como el del ejemplo anterior. con la diferencia de que hay que especificar como primer argumento el fichero en el que leemos: fscanf(fichero.El resultado de este programa será correcto si el archivo “mayor_menor. Por esta razón. que es la utilizada en el programa anterior. de modo que es útil la utilización de la función feof() que se muestra en el ejemplo anterior. . por último.txt” contiene el mayor y el menor de los números almancenados en “datos. Esta función nos devuelve un valor distinto de 0 si se alcanza el final de fichero. Volveremos a mostrar ejemplos del uso de ficheros en posteriores apartados. A continuación se realiza la comprobación de si el número introducido es el mayor o el menor de los leídos (queda como ejercicio la modificación del ejemplo anterior utilizando una estructura if-else) y. Esto es necesario siempre que se deja de utilizar un fichero y. En este caso.&numero).txt”. la instrucción: while (! feof(fichero)) { fscanf(fichero.

bien elemento a elemento. Es para esta necesidad de almacenar en memoria un conjunto de datos del mismo tipo para lo que se usan los arrays. else . según las necesidades del programa. A continuación veremos un ejemplo de programa que emplea un array. compararlos. almacenarlos en un array. imprimirlos por pantalla. suma. SIZE). bien de forma conjunta.h> #define SIZE 256 int main() { int n. etc. if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d. Este programa debe leer N números por teclado (donde N le será preguntado al usuario).h> #include <stdlib. float array[SIZE].). Un array es. casi seguro que las operaciones que haga con él afectarán a todos sus elementos (ya sea leerlos.\n". Arrays En ocasiones es interesante almacenar en memoria varios datos del mismo tipo sobre los que se realizará algún tipo de operación común. los elementos de un vector: si nuestro programa necesita almacenar en memoria un vector. Por ejemplo. scanf("%d". printf("Cuantos numeros vas a introducir?: "). i. por tanto. &n). una variable que contiene múltiples datos del mismo tipo y que podremos tratar. sumar los números positivos almacenados en las posiciones pares del array y mostrar el resultado por pantalla con dos decimales: #include <stdio.8.

por tanto. que será un número entero entre 0 y N-1 donde N es el número de elementos del array. arrays con una instrucción. sumar. • Salvo el caso particular de los arrays de caracteres (strings) que veremos más adelante. • Recuerda que para acceder a un elemento concreto del array se pone el nombre de éste y. • El usuario debe especificar con cuántos números desea trabajar. aunque estén en líneas distintas. • Cuando se leen datos con scanf se debe poner un & delante de cada variable en la que se vaya a guardar un dato. se suelen declarar en mayúsculas. for (i = 0.2f\n". entre corchetes. no podemos leer. etc. como cualquier otra variable. • No confundas el operador de asignación “=” con el operador de comparación de igualdad “==”. || significa “o” y && significa “y”. • Un array. . sentencias iterativas y arrays. Recuerda que: • Las constantes. i. i < n. que va tomando todos los valores desde 0 hasta n-1 porque nos interesa recorrer todos los elementos del array. suma). suma = 0. • En una expresión lógica. escribir. si no que tenemos que hacerlo elemento a elemento. • El operador % significa “módulo” y se usa. es necesario declararlo. una variable o incluso una expresión. no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar los arrays con una sola instrucción. constantes. a la derecha del nombre. sentencias condicionales. } return 0. El índice puede ser una constante. i++) { scanf("%f". En el programa que acabamos de ver se usa una variable. pero dado que no hemos visto asignación dinámica de memoria y tenemos que declarar el array antes de usarlo. siempre y cuando el resultado final de evaluarla sea un número entero. condiciones compuestas.{ printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"). En este caso. Una vez se ha declarado. para saber cuál es el resto de una división entre dos números enteros. &array[i]). } printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %. C no permite cambiar el número de elementos del array. aunque no es obligatorio. porque scanf necesita saber la dirección de memoria de esas variables. Es decir. debemos crear un array sobredimensionado y controlar que el usuario no especifica un número de elementos mayor que el tamaño del array. if (((i % 2) == 0) && (array[i] > 0)) suma += array[i]. el índice del elemento al que queremos acceder. } Consideraciones: • En este ejercicio tenemos ejemplo de todo lo visto hasta ahora: E/S. • Se pueden concatenar cadenas de caracteres simplemente cerrando una con comillas y abriendo la siguiente con otras comillas. Lo que no se puede hacer es crear una cadena de caracteres en una línea y continuarla en la siguiente. se pone entre corchetes el número de elementos que tiene el array.

} return sum.En cuanto al uso de arrays y funciones. al ser un paso por referencia se está trabajando siempre sobre la matriz del . printf("Cuantos numeros vas a introducir?: "). i < n. siempre se realiza por referencia. } int main() { int n. } Como vemos. i++) scanf("%f". por lo que no es necesario devolver dicho array desde la función al estar modificando directamente el array original (no es necesario el return).h> #include <stdlib. for (i = 0. A continuación mostramos el código del ejemplo anterior realizado ahora usando una función par_positivo que calcula la suma: #include <stdio. i. scanf("%d". el uso de punteros es necesario en C cuando una función debe modificar varias variables. printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %. else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"). i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]. suma = par_positivo(array. consecuentemente. suma). float array[SIZE]. int i. SIZE). el paso por referencia y. for (i = 0.\n". } return 0. Por este motivo. suma. int numeros) { float sum = 0. pero como hemos dicho anteriormente. if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d. Este es el motivo por el cual en este ejemplo no es necesario un return de matriz. como vimos en anteriormente.h> #define SIZE 256 float par_positivo (float array[SIZE]. en el caso de pasar un array a una función.2f\n". El nombre de un array es realmente un puntero a la dirección de memoria donde empieza el array. &n). el paso del array a la función es simple y podría parecer que se está haciendo paso por valor ya que no hay distinción respecto a cómo se envía la variable n. el paso de arrays es siempre por referencia. n). i < numeros. &array[i]).

scanf("%d". suma. La función par_positivo no copia localmente el contenido del array. if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d. else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"). Ordenación En cualquier lenguaje de programación es muy común encontrarse con la necesidad de ordenar un conjunto de datos en función de un criterio de orden o alfabético. suma = par_positivo(array. SIZE). En concreto.main(). sino que trabaja con un puntero que apunta al array creado desde el main(). } } return sum. que consiste en que ahora el array se recibe como puntero. int i. . printf("Cuantos numeros vas a introducir?: "). for (i = 0.h> #define SIZE 256 float par_positivo (float *array. } return 0. float array[SIZE]. es importante conocer el funcionamiento básico de los algoritmos de ordenación ascendente o descendente de datos numéricos almacenados en un array.\n". &n).h> #include <stdlib. &array[i]). n). for (i = 0. i. suma). } Hemos resaltado la única diferencia respecto a la versión anterior. La declaración de los arrays unidimensionales como punteros dentro de las funciones es la ideal en este curso.2f\n". printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %. De hecho. int main() { int n. i++) scanf("%f". i < n. i < numeros. en el siguiente programa mostramos la forma más común de pasar arrays a funciones utilizando un puntero como argumento: #include <stdio. i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]. int numeros) { float sum = 0.

j++) if (vector[j] < vector[i]) { aux = vector[j].h> #define MAX 100 int main() { int numelementos.Existen distintas formas de ordenar dichos datos. i++) { printf("\nIntroduzca el elemento %i: ". vector[i] = aux. i). que se diferencian mucho en cuanto a su rapidez y eficiencia derivadas del número de operaciones que realizan. printf("\nNumero de elementos del vector:"). i. i<numelementos. for (i=0. j. uno de los más ineficientes: #include <stdio. i<numelementos. &vector[i]). if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos. en este caso. de menor a mayor un array de números enteros. vector[MAX]. MAX). a la vez. j<numelementos. Este método es uno de los más sencillos y. } for(i=0. i++) printf("%i ". i<numelementos. Maximo = %i". &numelementos). } . else { for (i=0. i++) { for (j=i+1. scanf("%i". scanf("%i". } } printf("\nVector ordenado = "). De cara a ilustrar el funcionamiento básico de un algoritmo de ordenación. a continuación mostramos el denominado método de la burbuja para ordenar. vector[i]). vector[j] = vector[i].h> #include <stdlib.

} if (k != i) { vector[k] = vector[i]. .h> #include <stdlib. De hecho. i). se lleva a cabo un único intercambio. cada vez que se encuentra un número menor que el de referencia señalado por el índice i. else { for (i=0. &vector[i]). tras finalizar el bucle en j. de nuevo de menor a mayor: #include <stdio. k = j. una vez que un elemento señalado por j es menor que el de referencia señalado por i. scanf("%i". for (j=i+1. j<numelementos. j. simplemente se intercambian. printf("\nNumero de elementos del vector:"). el método de la burbuja se engloba dentro de los algoritmos de intercambio. se almacena dicho número y su posición en un par de variables y. i++) { aux = vector[i]. scanf("%i". } Como vemos. Maximo = %i". i. j++) if (vector[j] < aux) { aux = vector[j]. De esta forma. ya que su filosofía de funcionamiento se basa en la realización de sucesivos intercambios de valores. es el denominado método de selección directa donde. que se obtiene como una variación simple del anterior. i<numelementos. k = i. El siguiente código muestra este método aplicado a la ordenación de un array. en este algoritmo se utiliza un índice i para señalar el elemento que se toma como referencia en la comparación (bucle exterior) y otro índice j cuyo valor inicial es i+1 para señalar el elemento que se compara cada vez (bucle interior). vector[MAX]. &numelementos). si el array que proporciona el usuario estuviese ordenado de mayor a menor (caso opuesto a lo que queremos obtener). aux. i++) { printf("\nIntroduzca el elemento %i: ". Un método de ordenación más eficiente.return 0. Por ejemplo. } for(i=0.h> #define MAX 100 int main() { int numelementos. MAX). Por este motivo. el mayor defecto de este algoritmo es el elevado número de intercambios innecesarios que se llevan a cabo. el programa anterior realizaría continuos intercambios ya que cualquier número es siempre menor que el que se toma como referencia. i<numelementos. k. if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos.

j<elem. i<numelementos.vector[i] = aux. j++) { if (tipo == 0) { if (vector[j] < aux) { aux = vector[j]. k = j. De hecho. i<elem.h> #define MAX 100 void ordena (int tipo. Para finalizar este apartado dedicado a los algoritmos de ordenación simples. k.h> #include <stdlib. como dijimos antes. . 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 método y en otro. decir simplemente que para ordenar de mayor a menor en los ejemplos anteriores. realizaría únicamente tantos intercambios como elementos tenga el array. k = i. for (j=i+1. } } else { if (vector[j] > aux) { aux = vector[j]. vector[i]). i++) printf("%i ". a continuación mostramos un programa que implementa el algoritmo de selección directa mediante el uso de una función que permite al usuario escoger entre una ordenación ascendente o descendente: #include <stdio. } return 0. for (i=0. } } printf("\nVector ordenado = "). int elem) { int aux. i++) { aux = vector[i]. únicamente sería necesario cambiar el signo de comparación del primer if de < a >. for(i=0. i. k = j. respecto al método de la burbuja se han añadido únicamente dos variables:   aux: que almacena temporalmente el menor valor en cada iteración del bucle i k: que almacena temporalmente la posición del menor valor en cada iteración del bucle i Con esta simple modificación. j. en el caso extremo de que el array inicial esté ordenado de mayor a menor. int *vector. } Por tanto. el método de selección directa se vuelve mucho más eficiente que el de la burbuja y.

numelementos). } return 0. scanf("%i". i++) printf("%i ". Maximo = %i". vector[i] = aux. for (i=0. } do { printf("\nIntroduzaca el tipo de ordenacion:\n"). vector[i]). printf("1. scanf("%i". else { for (i=0. if (numelementos <= 0 || numelementos>MAX) printf("\nNumero de elementos incorrectos. printf("\nNumero de elementos del vector:"). i). i. } while ((tipo != 0) && (tipo != 1)). i++) { printf("\nIntroduzca el elemento %i: ". &numelementos).Descendente\n").Ascendente\n"). i<numelementos.} } } if (k != i) { vector[k] = vector[i]. printf("Tipo: "). ya que la función realmente está modificando el array declarado en el main mediante el uso del puntero. &vector[i]). vector[MAX]. printf("0. ordena(tipo. tipo. scanf("%i". MAX). } } } int main() { int numelementos. printf("\nVector ordenado = "). ya que dentro de la función ordenacion se modifica pero no es necesario un return para actualizar su valor en el main. . &tipo). vector. } En este ejemplo se ve claramente el efecto del paso por referencia del array vector. i<numelementos.

no para arrays numéricos. Cadenas de caracteres Las cadenas de caracteres son simplemente arrays que almacenan variables de tipo char. que consiste en averiguar si una frase es o no palíndromo (es decir.9. El uso de variables de tipo carácter cobra una gran importancia práctica a la hora de trabajar con cadenas de caracteres. por ejemplo para: – Leer o escribir una cadena entera (array completo) (gets y puts) – Averiguar la longitud de una cadena (strlen) – Buscar un carácter o grupo dentro de una cadena (strchr) – Unir dos cadenas o extraer parte de una cadena (strcat. A continuación mostramos un ejemplo de manejo de cadenas de caracteres. La única característica propia de este tipo de array es que se utiliza el carácter 0 (NULL o carácter de código ASCII 0) para indicar el final de la cadena (esto no es necesario en arrays numéricos). si se lee igual de izquierda a derecha y de derecha a izquierda): #include<stdio.h> #include <string. Existen en C una serie de funciones específicas para operar con estas cadenas. ya que desde el punto de vista del usuario.h> #include <stdlib. strtok) Es importante recalcar que estas funciones sólo sirven para cadenas de caracteres. esto le permite utilizar palabras y frases de forma cómoda.h> #define MAX 40 .

frase[i]!=0. } void eliminarBlancos(char frase[]) { int i. i<longitud/2 && es_palindromo == 1. . Esto implica. i++) if (palabra[i] != palabra[longitud-1-i]) es_palindromo = 0. la declaración de una cadena de caracteres se realiza declarando un array de 40 caracteres. } La idea básica para la resolución de este ejemplo (función esPalindromo) es que se debe comparar el primer carácter del array con el último.j++) frase[j] = frase[j+1]. strlwr(frase). for(i=0. scanf("%39[^\n]s". En el caso de que una de estas comparaciones detecte que dos caracteres simétricos no son iguales. return es_palindromo. printf("\nIntroduzca una frase: ").longitud. es_palindromo=1. if (es_palindromo) printf("\nLa frase es un palindromo\n"). a continuación. etc. es_palindromo=esPalindromo(frase). longitud = strlen(palabra).frase). debemos pasar todos los caracteres a minúsculas (la comparación simple de una letra mayúscula con una minúscula devolvería que son distintas y la frase sería palíndromo igualmente) y. de acuerdo con lo que comentamos antes. for (i=0. ya que el último carácter es el nulo. return 0.int esPalindromo(char palabra[]) { int i. debemos tener en cuenta las siguientes consideraciones relacionadas con la utilización de cadenas de caracteres: • En el ejemplo anterior. Además. frase[j]!=0. eliminar los espacios en blanco que contenga la frase para que la comparación tenga sentido (función eliminarBlancos). Previamente a esta comparación.j. la frase ya no será palíndromo. else printf("\nLa frase no es un palindromo\n"). int es_palindromo. que la frase más grande que podríamos utilizar en este caso es de 39 caracteres. } } int main() { char frase[MAX]. el segundo con el penúltimo. eliminarBlancos(frase).i++) { if (frase[i] == ' ') for(j=i.

La función strlen() devuelve una variable de tipo int indicando el número de elementos que tiene la cadena hasta el carácter nulo. Un opción que evita el uso de gets() es usar scanf() con indicador de longitud (%). Como consecuencia.h> #define TAM 40 int main() { char frase[TAM]. int i. desborda la cadena que traerá como consecuencia fallos impredecibles en la ejecución del programa (acceso a dirección de memoria inválida.• • • • • • • Las cadenas de caracteres se pueden leer completas usando las funciones gets() y scanf() con %s. sobrescribir variables del propio programa). longitud. printf("\nIntroduzca una frase: "). aunque sólo funciona con palabras.j=0. j. es_palindromo=1. La eliminación de los espacios en blanco en el ejemplo anterior se ha llevado a cabo sobre el mismo array. Por último. lo que indicaría que no es palíndromo. Las funciones strlwr() y strlen() son ejemplos de funciones específicas de las cadenas de caracteres que no pueden ser utilizadas con arrays numéricos. 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".h> #include <stdlib. tabulador. La función gets() que hemos utilizado en estos dos ejemplos lee todos los caracteres hasta retorno de carro que lo sustituye por un carácter nulo. scanf("%39[^\n]s". no con frases ya que el scanf() para de leer al encontrar un espacio en blanco. el bucle for se termina (ya que es_palindromo también controla la condición de realización del bucle) y no se realizan más comprobaciones innecesarias. por lo cual su utilización está desaconsejada y de ahora en adelante no volverá a aparecer. longitud = strlen(frase). Esta forma de lectura será la empleada a partir de ahora en lugar del gets. donde se utiliza un segundo array (nueva_frase) al que se copian las palabras de la frase original (queda como ejercicio pasar esta versión a funciones): #include <stdio. o retorno de carro. el programa anterior utiliza una variable que únicamente toma dos valores (cero o uno) para detectar si la cadena es palíndromo. en este caso. longitud. frase). Debemos almacenar dicha variable en otra conocida de nuestro programa. for(i=0. /* convierte a minúsculas la frase */ strlwr(frase). no es necesario leer los caracteres uno a uno en un bucle como se hacía en el caso de arrays numéricos. desplazando los elementos de derecha a izquierda cada vez que se detecta un espacio. Otra posible solución de este ejercicio es la que se muestra a continuación. Es decir. Dicha variable (es_palindromo) se pone a cero en caso de que la condición dentro del bucle se cumpla. nueva_frase[TAM]. Pero si se leen más datos de los que caben en la cadena.h> #include <string. cadena).i++) if (frase[i] != ' ') .i<=longitud.

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

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

o sea. si en el buffer ya existen valores entonces la función de lectura lee esos valores sin detenerse. aunque sea un carácter de control. saltándose algunos caracteres de control (si se los encuentra). se suele utilizar la instrucción flush(stdin) que vacía dicho buffer. Sin embargo. la cual es otro carácter (el carácter '\n'). Pero n osotros lo desaconsejamos totalmente porque fflush solo está definido en el estándar para flujos de salida y stdin es un flujo de entrada.Como vemos. Para evitar la lectura de caracteres de control y también para evitar la lectura de otros caracteres anteriores que puede haber en el buffer del teclado. no lo hará en todos. la ejecución del programa se detiene hasta que se introducen estos caracteres en el buffer de teclado. abrir el fichero ahora en modo escritura y guardar en él los datos del array vector. Usualmente. Continuando con el programa anterior. Si no hay ninguno. sea cual sea. Aunque en algunos compiladores funciona. como el carácter '\n'. Cuando estamos leyendo números la función intenta leer números. entonces. hasta que se pulsa la tecla RETURN. cuando estamos leyendo un carácter (o varios) la función de lectura leerá el siguiente carácter que haya en el buffer de teclado. Sin embargo. Antes de añadir estos datos ordenados a un fichero de destino debemos vaciar previamente el buffer del teclado mediante la función borra_buffer_teclado que ejecuta el siguiente código: while((c = getchar()) != '\n' && c != EOF). Pero si en el buffer existe algún carácter éste será leído. una vez que se tienen los datos en el array vector simplemente ejecutamos el algoritmo de ordenación visto en el tema anterior. la función se detiene a la espera de poder leer. cuando se usan funciones de lectura como scanf() o getchar(). por lo cual nosotros recomendamos la utilización de la expresión anterior como equivalente para el fflush(stdin). solo nos resta pedir al usuario un nombre para el fichero destino. El buffer de teclado es una memoria temporal donde se introducen los caracteres tecleados antes de pulsar la tecla RETURN (Intro o Enter). . y por ello no podemos estar seguros de cómo se comportará en un caso general. por ejemplo.

int matriz1[MAXFIL][MAXCOL]. scanf("%i". j. numcol. i=i+1) for (j=0. Matrices En las secciones anteriores hemos trabajado con arrays de una única dimensión (vectores). Estas matrices podrán ser de dos dimensiones o en general n-dimensionales para cualquier número n entero positivo. Numero de columnas incorrectas (MAX = %i)". j<numcol. i. MAXFIL).h> #include <stdlib.10. } printf("\nMatriz 2:\n"). else if (numcol <= 0 || numcol > MAXCOL) printf("\nError. else { printf("\nMatriz 1:\n"). #include <stdio. &numcol). i<numfil. j). numfil. for (i=0. j=j+1) { printf("Introduzca el elemento [%i][%i]: ".h> #define MAXFIL 20 #define MAXCOL 20 int main() { int i. if (numfil <= 0 || numfil > MAXFIL) printf("\nError. scanf("%i". suma[MAXFIL][MAXCOL]. &numfil). printf("\nIntroduzca el numero de filas de las matrices: "). A continuación veremos el ejemplo de un programa que lee dos matrices por teclado y calcula la suma de ambas matrices. MAXCOL). scanf("%i". printf("\nIntroduzca el numero de columnas de las matrices: "). . &matriz1[i][j]). sin embargo en muchas aplicaciones y problemas reales es necesario el uso de matrices. Numero de filas incorrectas (MAX = %i)". matriz2[MAXFIL][MAXCOL].

se deben especificar todas las dimensiones menos la primera. i=i+1) { for (j=0. matrices con una instrucción. El índice puede ser una constante. no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar las matrices con una sola instrucción. } } return 0. Al igual que sucedía con los arrays. Es decir. a la derecha del nombre de la variable. el índice del elemento al que queremos acceder. entre corchetes. j=j+1) { suma[i][j] = matriz1[i][j] + matriz2[i][j]. j++) { printf("Introduzca el elemento [%i][%i]: ". } printf("\nSuma de las matrices:\n"). } printf("\n"). i<numfil. sumar. es necesario declararla. Puesto que dicho tamaño no puede ser sobrepasado. siempre y cuando el resultado final de evaluarla sea un número entero. Recuerda que para acceder a un elemento concreto de la matriz se pone el nombre de éste y. etc. i. hay que tener en cuenta que cuando se recibe una array bidimensional (matriz) en una función. una variable o incluso una expresión. 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. En el programa que acabamos de ver se usan una variable. j). En este caso. En el ejemplo anterior se especificaron el número de filas y de columnas de la matriz. printf("%i\t". &matriz2[i][j]). j<numcol. no podemos leer. escribir. El siguiente ejemplo muestra el uso de matrices en funciones. que es opcional. será necesario controlar que el usuario no especifica un número de elementos mayor que el tamaño de la matriz. suma[i][j]). i. Hay que tener en cuenta que para cada fila de la matriz se deberán 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. que será un número entero entre 0 y N-1 donde N es el número de elementos de esa dimensión de la matriz. El programador debe especificar el tamaño (números de elementos) de cada dimensión. i<numfil. como cualquier otra variable. al ser matrices bidimensionales. for (i=0. si no que tenemos que hacerlo elemento a elemento. se usaron dos pares de corchetes para indicar en el primero de ellos el número de filas y en el segundo el número de columnas de la matriz. En el ejemplo anterior. j<numcol.for (i=0. donde el objetivo consiste en realizar un programa que ponga a 0 . se ponen tantos corchetes como dimensiones tenga la matriz. una vez se ha declarada la matriz no se permite cambiar el número de elementos de la misma. } En este programa hay que tener en cuenta las siguientes consideraciones: • Una matriz. scanf("%i". i++) for (j=0. • • • En cuanto al uso de matrices y funciones.

matriz[MAX][MAX].j.h> #define MAX 10 void modificarDiagonalesMatriz(int matriz[][MAX]. j<dimension. } modificarDiagonalesMatriz(matriz. printf("Introduzca la dimension de la matriz cuadrada: ").j). i. for (i=0. if (dimension>MAX || dimension<=0) printf("Error. matriz[i][dim-i-1]=0.(mediante una función) los elementos de la diagonal principal y secundaria de una matriz cuadrada: #include <stdio. scanf("%i".h> #include <stdlib. i<dimension.int dim) { int i.&matriz[i][j]). El numero de elementos es incorrecto (Maximo= %i)\n". i<dim.dimension. i++) { matriz[i][i]=0. %i: ". for (i=0. scanf("%d". j++) { printf("Introduzca el elemento %i. i++) for (j=0. i++) .&dimension). else { for (i=0. } } int main() { int i.dimension). i<dimension.MAX).

De cara a clarificar el hecho de que no se realiza una copia de la matriz local a la función. la declaración de la matriz en la cabecera de la función no incluye la primera dimensión. En cambio. pero sí la segunda tal y como habíamos comentado anteriormente. Por otro lado.txt” que contiene los valores de una matriz de tamaño 10 x 10. Al terminar la ejecución de la función. al final de este manual. printf("\n"). el primero de ellos indica el número de fila. j<dimension. en la declaración de la misma hemos mantenido el nombre que tenía en el main(). El contenido del fichero de texto se muestra en el Apéndice B. La ejecución del programa proporciona la siguiente salida por pantalla: . la matriz ha quedado modificada también en el main(). en el main() únicamente se llama a la función y se le pasan los parámetros correspondientes. El objetivo es realizar un programa en C que lea este fichero “numeros. pero de forma matricial. Por este motivo. matriz[i][j]). j++) printf("%i\t ". dim (las modificaciones que se hiciesen sobre dim no afectarían a la variable dimension). el segundo el número de columna y el último el valor correspondiente.txt” y muestre por pantalla y guarde en otro fichero el contenido del fichero original.{ for (j=0. } }//fin else return 0. Así cada línea del fichero contiene 3 números. A continuación mostramos otro ejemplo del uso de matrices y archivos en C que lee datos de un fichero “numeros. } La función modificarDiagonalesMatriz recibe como parámetros la matriz y la variable dimension y no devuelve nada (void). la variable dimension se almacena localmente en la función en una variable con otro nombre.

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

A continuación se introducen los elementos uno a uno y se calcula la media: #include <stdio. Hablamos. éstas se pueden declarar para un tamaño máximo de 100x100 (float matriz[100][100]). La memoria reservada mediante malloc() puede (y debe) ser liberada una vez utilizada mediante la función free() que requiere como parámetro el puntero asignado en malloc(). si realizamos un programa en C que calcula el producto de dos matrices. pero en el caso de que el producto sea por ejemplo de tamaño 3x3. la memoria reservada para esa matriz corresponderá al tamaño máximo de 100x100. La función que permite crear o asignar un espacio de memoria interna durante la ejecución de un programa es malloc(). Como ejemplo de aplicación simple de la reserva dinámica de memoria. el siguiente programa crea un array de elementos float dinámicamente en función del tamaño que introduce el usuario. Por ejemplo.h> #include <stdlib. que requiere como parámetro el número de bytes de memoria que se van a reservar y devuelve un puntero al comienzo de la zona de memoria reservada. asignando normalmente un tamaño por exceso y dejando el resto sin usar. en este caso. que resulta en una utilización ineficiente de la memoria.11. según el tamaño del caso concreto que se vaya a resolver. de manera que en dicho programa se podrá calcular cualquier producto de un tamaño igual o inferior a 100x100. de reserva dinámica de memoria. es muy útil el poder reservar más o menos memoria en tiempo de ejecución.h> . Por este motivo. o NULL si no es posible reservar la cantidad de memoria pedida. Reserva dinámica de memoria En los programas que hemos visto hasta ahora. la reserva o asignación de memoria para los vectores y matrices se realiza cuando se declaran dichas variables.

n. es la posibilidad de que se realice reserva dinámica de memoria dentro de una función. scanf("%d". en este caso con la sentencia free(numeros). printf("\nLa media es: %f\n\n". se finaliza la ejecución del programa.i<n. Por ejemplo. a continuación. si la memoria no se pudo asignar correctamente. if (numeros == NULL) printf("Memoria insuficiente\n"). media=0. que al multiplicarlo por n nos dará el número exacto de bytes necesario para almacenar en memoria los datos que introducirá el usuario. media += numeros[i]. Un aspecto que debe ser tratado cuando se trabaja con funciones. printf("Introduzca el numero de datos: "). numeros =(float *)malloc(n*sizeof(float)). } return 0. } media = media/n. un puntero genérico) por lo que es necesario hacer una conversión de tipo de puntero (en este caso float *) de acuerdo con el tipo de dato que se desea almacenar. En este caso se han utilizado funciones para realizar las operaciones básicas de reserva de memoria y de cálculo de la media: . } Lo primero que hay que notar del código anterior es que el array está definido como un puntero a un float en lugar de utilizar la notación para declarar los arrays de manera estática.&numeros[i]). La función malloc() devuelve un puntero de tipo void (es decir. free(numeros). Una vez que el usuario introduce el número de elementos que tendrá el array (variable n). Por último. El siguiente código corresponde a un programa que crea un array de elementos dinámicamente en función del tamaño que introduce el usuario y. scanf("%f". pide los elementos del array uno a uno y calcula la media.i). en este caso sizeof(float) devolvería un 4. la variable numero guardará la dirección de comienzo del array en memoria y a partir de este momento la utilización del array es la común. mediante la función malloc() se realiza la reserva de un número de bytes que permita almacenar n elementos de tipo float. &n). else { for (i=0.i++) { printf("Introduce el elemento %d: ". int i.int main() { float *numeros. debemos liberar siempre la memoria cuando ya no sea necesaria su utilización.media). Para ello. A partir de este momento. utilizamos la función sizeof() que devuelve el número de bytes del tipo de dato o de la variable que se le pasa como argumento. En el resto del ejemplo.

Esto es posible porque cuando desde el main() se llama a la función calcula_media() lo que se pasa como primer argumento es el puntero numeros. la dirección de memoria donde comienza el array.&numeros[i]). int tam) { array = (float *) calloc(tam*sizeof(float)).i<num. media. int i. la función reserva_local() recibe como parámetro el puntero definido en el main() y el número de elementos que va a introducir el usuario. Por este motivo.n.n). int). } float *reserva_local(float *array. scanf("%f". numeros = reserva_local(numeros. } media = calcula_media(numeros. return (media/num).h> float *reserva_local(float *. float calcula_media(float *. } return 0. y por tanto. float media=0. for(i=0. else { for (i=0. la función devuelve un puntero que apunta a la primera dirección de memoria asignada a array.i). } float calcula_media(float *array.n). return(array). printf("Introduzca el numero de datos: ").i<n.h> #include <stdlib.i++) media += array[i]. printf("\nLa media es: %f\n\n". la función es de tipo float *. Vemos también que en la función calcula_media() el array de números se recibe como primer parámetro en forma de puntero y no con el formato de matriz estática que hemos usado hasta el momento. scanf("%d".#include <stdio. &n). int).i++) { printf("Introduce el elemento %d: ". free(numeros). } Como podemos observar en el código. int main() { float *numeros. . int num) { int i.media). Tras realizar la llamada a malloc(). es decir. if (numeros == NULL) printf("Memoria insuficiente\n"). una variable tipo float * que es donde se almacena.

scanf("%d". printf("Introduce el numero de filas: "). /* operaciones sobre los elementos de la matriz */ for (i=0. /* liberación de memoria */ for (i=0. i++) { principal *= m[i][i]. pero debemos tener en cuenta que no podemos gestionar la matriz como una sucesión de elementos contiguos. debemos notar que en este ejemplo se ha usado la función de reserva dinámica calloc(). secundaria = 1. } printf("Diagonal principal: %f y secundaria: %f\n". secundaria). i++) m[i] = malloc(columnas * sizeof(float)). return 0. float principal = 1. sino como un “vector dinámico de vectores dinámicos”. como un puntero a un puntero. &columnas). A continuación se muestra el código fuente de un programa que solicita al usuario el número de filas y columnas de una matriz. int filas.0. scanf("%d". m = NULL. free(m). i<filas. es decir. /* reserva de memoria */ m = malloc(filas * sizeof(float *)). secundaria *= m[i][filas-1-i].principal. En la instrucción m = malloc(filas * sizeof(float *)). i<filas. columnas.h> int main() { float **m = NULL. i<filas. que es idéntica a malloc() pero que inicializa a cero las posiciones reservadas. Por ejemplo. supongamos que el usuario introduce un valor 4 para filas.Para finalizar este ejemplo. reserva memoria dinámicamente y calcula el producto de los elementos de la diagonal principal y el de los elementos de la diagonal secundaria: #define <stdio. printf("Introduce el numero de columnas: "). &filas). } Ahora la matriz se declara como un doble puntero float **m . La asignación de memoria para m sería: . Podemos extender la idea de los vectores dinámicos a matrices dinámicas. for (i=0. i++) free(m[i]). Esto es así porque realmente estamos definiendo un vector dinámico de vectores dinámicos.0. se reserva memoria para un número filas de punteros a float.

i++) m[i] = malloc(columnas * sizeof(float)). Por último. tras ejecutar estas 3 instrucciones de reserva de memoria. es decir. m = NULL. que es de tipo float *. perderemos el puntero que los referencia y. i<filas. al acceder a m[1][2]. Como vemos. estaremos accediendo inicialmente a m. se libera la memoria: for (i=0. por ejemplo. free(m). al acceder a m[1].El vector m es un vector dinámico cuyos elementos son punteros (del tipo float *) pero esos punteros no apuntan a ninguna zona de memoria reservada. i++) free(m[i]). que representa la fila 1 de la matriz global. . aunque esté estructurado como un array de arrays. Este bucle proporciona un bloque de memoria para m[0]. . cuando accedemos. i<filas. en las líneas siguientes. Así. estaremos accediendo a la dirección de memoria de comienzo del array m[1]. es equivalente a trabajar directamente con una matriz. al elemento m[1][2]. que sí contiene un dato float. Esta operación se realiza con la instrucción siguiente: for (i=0. por los de la forma m[i] (si liberamos m antes que todos los m[i]. En el programa anterior. m[2]. tendremos una matriz exactamente igual que en el caso de reserva estática. Hemos de liberar cada uno de los bloques reservados y hemos de empezar a hacerlo por los de “segundo nivel”. que es de tipo float **. Finalmente. m[1]. y que almacena la dirección de comienzo del array de punteros m. . que es de tipo float. estamos accediendo a la posición 2 del array m[1]. a nivel puramente práctico. en consecuencia no podremos liberarlos). obteniendo algo así: De este modo.

pero ambas expresiones son equivalentes a “vector de vectores a entero”.El paso de matrices dinámicas a funciones tiene varias formas posibles. si una función recibe una matriz de enteros para mostrar su contenido por pantalla. En cambio. la cabecera: void muestra_matriz ( int m[][] ) no es correcta en C. Por ejemplo. la cabecera ideal sería: void muestra_matriz ( int ** m ) El parámetro 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 sería un “vector de punteros a entero”. . ya que se entiende que queremos pasar una matriz estática y que hemos omitido el número de columnas.

tipoN CampoN. . int Anio. CampoN). char *Mes. . Declara una variable del tipo "struct Nombre". ya que permiten tratar como una unidad a un conjunto de variables relacionadas. Se puede inicializar una estructura externa o estática añadiendo a su definición la lista de inicializadotes. struct Fecha Hoy = {8. Un campo de una estructura se utiliza como una variable más.Dia = 24. esto es. Hoy. Una estructura se define en C a través de la siguiente sintaxis: struct Nombre { tipo1 Campo1. Hoy. el compilador reserva la cantidad de memoria sufuciente para mantener la estructura íntegra (es decir espacio para almacenar Campo1. La asignación VarFecha = Hoy copia la estructura integra Hoy en VarFecha. Cuando se hace referencia a la variable Var. VarFecha.. Campo2.Mes = "Agosto".12. }. }.. Para referenciar un campo de una estructura se emplea el operador .."Mayo". VarFecha = Hoy. 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 aplicación. . en lugar de tratarlas como entidades independientes. por ejemplo: struct Fecha { int Dia.. . se esta haciendo referencia a la estructura íntegra. Cuando dentro de los campos de una estructura aparecen punteros y uno realiza este tipo de asignación.Anio = 1991. particularmente en programas grandes. tipo2 Campo2. La instrucción: struct Nombre Var1. Hoy. Las estructuras ayudan a organizar datos complicados.2008}. 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. se esta copiando también los valores de los punteros....

De cara a ilustrar esta posibilidad. &radio). paso de argumentos por referencia: #include <stdio. printf(”Introduce el radio: "). ya que lo que se devuelve es la estructura. printf(”Introduce el radio: ").h> #include <math. circulo(radio. float *p_circunferencia) { *p_area = M_PI * radio * radio.Anio%4 == 0 && Fecha.Ejemplo para verificar año bisiesto: Bisiesto = Fecha. printf("Area: %. struct s_circulo f_circulo(float radio) { struct s_circulo circulo. printf("Area: %. circulo. scanf("%f".h> #include <stdlib. return circulo. area. &circunferencia).Anio%100 != 0 || Fecha. } int main() { float radio.area = M_PI * radio * radio. return 0. struct s_circulo circulo. circunferencia). circunferencia.2f Circunferencia: %.0 * M_PI * radio. El uso de estructuras permite que una función modifique y devuelva más de un tipo de dato.h> #include <stdlib. &area. &radio).h> struct s_circulo { float area.circunferencia). circulo. inicialmente.circunferencia = 2.2f\n". }.2f Circunferencia: %.0 * M_PI * radio. *p_circunferencia = 2. } int main() { float radio.h> void circulo(float radio. area. } El mismo programa usando una estructura sería: #include <stdio.Anio%400 == 0.h> #include <math. circulo. float circunferencia. circulo.2f\n". . circulo = f_circulo(radio).area. a continuación mostramos un programa que calcula el área y la longitud de un círculo a partir del radio del mismo que es pedido al usuario por teclado usando. scanf("%f". float *p_area.

/* funcion estandar que borra el buffer del teclado */ void borra_buffer_teclado() { int c. pos_mayor. int longitud.longitud.h> #include <string. Por ejemplo.longitud > valor_mayor) { pos_mayor = i. mediante una funcion. ya que permite realizar programas muy complejos a nivel de variables con una organización simple.i++) if (pais[i]. } . El fichero origen deberá incluir en la primera línea el número de países que hay en él. } while((c = getchar()) != '\n' && c != EOF). } return pos_mayor.return 0. Un posible archivo origen contendría: 5 Suecia Alemania Francia Polonia Rusia Y el archivo de salida guardaría: “El pais de mayor longitud es Alemania con 8 letras” #include <stdio. /* funcion que devuelve la palabra de mayor longitud */ int mayor_palabra(struct datos_pais *pais. }. es decir una cabecera.i<num.h> #define MAXNOMBRE 50 struct datos_pais { char nombre[MAXNOMBRE]. valor_mayor = 0. el de mayor longitud y guarde su nombre en otro fichero. 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. } En el programa anterior hemos utilizado una función que devuelve un dato de tipo estructura struct s_circulo. //valor inicial for (i=0.h> #include <stdlib. valor_mayor. El uso de arrays de estructuras es realmente útil en C. valor_mayor = pais[i]. int num) { int i.

} while (fichero == NULL). Este array se declara como: struct datos_pais *pais. se realiza la reserva de memoria: pais = (struct datos_pais *) malloc(numpaises*sizeof(struct datos_pais)).nombreFichero).numpaises.nombre). //leo la cabecera /* reservamos memoria para el array de estructuras */ pais = (struct datos_pais *) malloc(numpaises*sizeof(struct datos_pais)).i++) { fscanf(fichero.longitud = strlen(pais[i]. pais[i]. free (pais).pos_mayor."\nEl pais de mayor longitud es %s con %d letras\n". que tiene el formato habitual. //array de estructuras FILE *fichero.numpaises). Este nos ha permitido utilizar un array donde cada elemento no es un dato de un tipo simple. } pos_mayor = mayor_palabra(pais. Es decir. } En este caso. } while (fichero == NULL).nombre). do { printf("\nIntroduzca el nombre del fichero destino: "). fclose(fichero). scanf("%s". A continuación. el fichero no existe\n").&numpaises).longitud).int main() { int i. scanf("%s". do { printf("\nIntroduzca el nombre del fichero origen: ").pais[pos_mayor]. se ha utilizado un array de estructuras reservado dinámicamente. "wt"). fichero = fopen(nombreFichero. struct datos_pais *pais. return 0. pais[pos_mayor]. el fichero no existe\n"). con la única complejidad añadida de que el tipo de dato es ahora struct datos_pais. sino un dato compuesto a su vez de varios tipos. tras haber leído la cabecera del fichero. if (fichero == NULL) printf("\nError. fichero = fopen(nombreFichero.nombre. fscanf(fichero. if (fichero == NULL) printf("\nError.i<numpaises. fclose(fichero). . fprintf(fichero. for (i=0."%d"."%s\n".pais[i].nombreFichero). "rt"). char nombreFichero[MAXNOMBRE]. definimos un puntero a una estructura de tipo datos_pais.

son tan importantes que todas las empresas dedicadas a programación imponen a sus empleados una mínima uniformidad.es/fundinfo/matdoc/normasC. Además. constante.. Por eso. de tal forma que. ficheros.Apéndice A. sino que pueden ser aplicados a cualquier otro lenguaje. El nombre de cada identificador debe identificar lo más claramente posible al objeto que identifica (valga la redundancia). Normalmente los identificadores deben empezar por una letra. para ahorrarnos tiempo. primero hay que comprender su funcionamiento. en mayor o menor medida.ieev. La legibilidad de un programa es demasiado importante como para prestarle la atención que merece por parte del programador.uma. aunque consigamos mayor eficiencia su comprensión sea todo un reto. es decir. cada programa debe ir acompañado de una documentación adicional. pero que no debería superar los 10-20 caracteres para evitar lecturas muy pesadas. Han sido ligeramente modificadas y adaptadas para el contexto de la actual asignatura y conforman una serie de “buenos hábitos” de programación. Para poder modificarlo. y para facilitar esta tarea el programa debe estar escrito siguiendo unas normas básicas.. Aquí intentaremos resumir estas reglas: Identificadores significativos Un identificador es un nombre asociado a un objeto de programa. No existen un conjunto de reglas fijas para programar con legibilidad. . no pueden contener espacios (ni símbolos raros) y suelen tener una longitud máxima que puede variar. cuando surgen los problemas de legibilidad de un programa. En C se pueden encapsular órdenes y operadores.. función. Unas normas de estilo en programación. tipo de datos. Normas de estilo de programación Estas normas de estilo de programación. objetivos. Independientemente de los algoritmos usados. que puede ser una variable. se consiguen programas bastante legibles. Es entonces. que aplicándolas. que aclare detalladamente cada módulo del programa. el C es un lenguaje que se presta a hacer programas complejos y difíciles de comprender.. Lo que sí existen son un conjunto de reglas generales. están basada en las que pueden ser consultadas en la página web: http://www. al programar debemos intentar que nuestros programas sean lo más expresivos posibles. dinero y quebraderos de cabeza a la hora de modificarlos. una serie de indicaciones sobre el formato de los programas que el alumno debería seguir siempre. ya que cada programador tiene su modo y sus manías y le gusta escribir de una forma determinada.html correspondientes a la asignatura de Fundamentos de Informática de la Universidad de Málaga. hay muchas formas y estilos de programar. La tarea de mantenimiento del software (corregir y ampliar los programas) es una de las tareas más laboriosas del ciclo de vida del software. 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. Por supuesto. algoritmos usados. para facilitar el intercambio de programas y la modificación por cualquier empleado. Estos consejos de estilo no son propios del lenguaje C. sea o no el programador inicial.

las constantes simbólicas se suelen poner usando una orden al preprocesador de C.16) + gastos_transporte(destino). cadenas. Si estas constantes las usamos directamente en el programa. para que sea más expresivo usar la función en algunas expresiones. una función podría llamarse escribir_opciones. dentro de una función o programa pequeño. Un identificador debe indicar lo más breve y claramente posible el objeto al que referencia. El carácter '_' es muy usado para separar palabras en los identificadores...). para indicar claramente lo que hace. como: precio_total = precio_total + IVA(precio_total.141592 que sería más costoso y podemos olvidarnos alguna. pero esto no es muy recomendable.. Así.14 en su lugar: volumen_esfera = 4/3. y no tenemos que cambiar todas las ocurrencias de 3.Se recomienda encarecidamente la utilización de letras minúsculas para definir los nombres de las variables y las funciones y de mayúsculas para las constantes y los define. conseguimos principalmente dos ventajas: 1. j o k para nombres de índices de bucles (for. Por ejemplo.3). Por ejemplo. 2. Observe que no ponemos los acentos. En C. Es muy normal usar variables como i. se pueden usar este tipo de variables. * PI * pow(radio. while. quedando definidas desde el lugar en que se definen hasta el final del fichero (o hasta que expresamente se indique). Constantes simbólicas En un programa es muy normal usar constantes (numéricas. Los programas serán más fáciles de modificar: Si en un momento dado necesitamos usar PI con más decimales (3. su nombre debe hacer referencia a este valor..14 por 3. la variable se puede llamar nota_informatica. y sería más comprensible que si le hubiéramos llamado escribir o escrOpc. los cuales pueden dar problemas de compatibilidad en algunos sistemas. si una variable contiene la nota de un alumno de informática.). lo cual es aceptable siempre que la variable sirva sólo para el bucle y no tenga un significado especial. Si la función devuelve un valor.141592) sólo tenemos que cambiar la definición. pero es más recomendable usar constantes simbólicas. si no crean problemas de comprensión. Para los identificadores de función se suelen usar las formas de los verbos en infinitivo. Su formato general es: #define CONSTANTE valor . el programa funcionará. Los programas se hacen más legibles: Es más legible usar la constante simbólica PI como el valor de π que usar 3. de forma que las definimos al principio del programa y luego las usamos cuando haga falta. En determinados casos. seguido de algún sustantivo.

ya que esto puede causar una larga y tediosa lectura del programa.que se encarga de cambiar todas las ocurrencias de CONSTANTE por el valor indicado en la segunda palabra (valor). • Al principio de cada función cuyo nombre no explique suficientemente su cometido. Los comentarios sirven para aumentar la claridad de un programa. . switch.) que revista cierta complejidad. Este cambio lo realiza el preprocesador de C.. por ejemplo. sobre todo si se va a usar en más de una ocasión en nuestro programa.. principalmente cuando existe mucho anidamiento de sentencias y/o los bloques contienen muchas líneas de código. No se debe abusar de comentarista. if. autor o autores. Comentarios El uso de comentarios en un programa escrito en un lenguaje de alto nivel es una de las ventajas más importantes con respecto a los lenguajes máquina. que aunque el valor de π es constante. de forma que el comentario indique qué se realiza o cómo funciona. ya que ese valor es absolutamente inalterable. módulos que lo componen. fecha de finalización. Se debe poner no sólo lo que hace sino la utilidad de cada parámetro.. el valor que devuelve (si lo hubiera) y. Por ejemplo. últimas modificaciones realizadas y sus fechas. si fuera oportuno. • En cada sentencia o bloque (bucle. por lo que es recomendable usar una constante simbólica en este caso. • En los cierres de bloques con '}'. En general. además de otras más obvias. los requisitos necesarios para que dicha función opere correctamente. y cualquier otra eventualidad que el programador quiera dejar constancia. indicando: Nombre del programa.. Puede no resultar muy útil dedicar una constante para el número de meses del año. objetivo. las constantes se suelen poner completamente en mayúsculas y las variables no. y sobre todo: • Al principio del programa o de cada fichero del programa que permita seguir un poco la historia de cada programa.141592 Como hemos dicho. parámetros (si los tiene). se deben usar constantes simbólicas en constantes que aparezcan más de una vez en el programa referidas a un mismo ente que pueda variar ocasionalmente. condiciones de ejecución. es absurdo poner: Nota = 10. • En la declaración de variables y constantes cuyo identificador no sea suficiente para comprender su utilidad. pero en caso de duda es mejor poner comentarios de más. de forma que leyendo el programa podamos saber rápidamente qué es cada cosa. ayudan para la documentación y bien utilizados nos pueden ahorrar mucho tiempo. para indicar a qué sentencias de control de flujo pertenecen. Por ejemplo: #define PI 3. Se deben poner comentarios cuando se crean necesarios. /* Asignamos 10 a la variable Nota */ Los comentarios deben ser breves y evitando divagaciones. podemos variar su precisión. Obsérvese. antes de empezar la compilación.

por lo que debemos cuidar el estilo. acentos y signos de puntuación. y podemos organizar el código del programa como más nos interese. en general. dividir el bloque en varias llamadas a otras funciones más simples. aunque para ello recomendamos usar los comandos break. los ficheros . De todas formas. la inclusión de bibliotecas del sistema. El uso de la sentencia GOTO está totalmente desaconsejado.. Casi todos los lenguajes de programación son de formato libre. de forma que el programa principal llame a las funciones más generales. entre cada función se añaden una linea de asteriscos o guiones.. Esto sigue el principio de divide y vencerás. una programación descuidada y un análisis del problema poco estudiado. Bibliotecas propias de la aplicación. Como dicen Kerninghan y Ritchie en su libro de C. Quizás la más típica sea: #include <stdio.h> 3. y estas vayan llamando a otras. Se deberá. Estructura del programa Un programa debe ser claro. A veces. Para aumentar la claridad no se deben escribir líneas muy largas que se salgan de la pantalla y funciones con muchas líneas de código (especialmente la función principal). aunque no sean necesarios. Una función demasiado grande demuestra. return o la función exit(). Normalmente. se suelen realizar varias librerías con funciones. para destacar que empieza la implementación de otra función (aparte de los comentarios explicativos de dicha función). A veces.>) el nombre del fichero. como salir de una estructura profundamente anidada. Primero los comentarios de presentación. como comentario. Cada bloque de especial importancia o significación. es conveniente usar paréntesis en las expresiones. Si no queda clara su finalidad. En general se debe modularizar siempre que se pueda. y cada función debe separarse de la siguiente con una línea en blanco. Normalmente. estar bien organizado y que sea fácil de leer y entender. será mejor que nos planteemos de nuevo el problema y estudiemos otra posible solución que seguro que la hay. Después. mediante el cual es más fácil solucionar un problema dividiéndolo en subproblemas (funciones) más simples.h con el #include y entre ángulos (<. para salir de bucles anidados deben usarse lo menos posible y sólo en casos donde quede bien clara su finalidad. esos recursos. para aumentar la claridad. 2. su utilización sólo estaría justificada en casos muy especiales.No olvidemos que los comentarios son textos literarios. como ya hemos indicado. Los nombres de fichero se ponen entre comillas (para que no las busque en el . continue. un programa en C se suele estructurar de la siguiente forma: 1. de manera que los espacios no importan. en tal caso. para que su lectura sea más agradable. hasta llegar a las funciones primitivas más simples. separadas por su semántica. en grandes aplicaciones.

Por ejemplo. 9. 5. Constantes simbólicas y definiciones de macros. a las que se le pasan unos determinados datos y nos devuelve otros. Variables globales. con la palabra reservada extern. por supuesto. En general es mejor no usar nunca variables globales. la palabra extern. Lo mejor es no usar nunca variables globales y pasar su valor por parámetros a las funciones que estrictamente lo necesiten. usadas en el módulo y declaradas en otro módulo distinto. El uso de estas variables puede dar lugar a los llamados efectos laterales. De esta forma. y que si se usan en otro módulo deberán llevar. Es una buena medida. ya que así puede ser más fácil localizarlas. Declaración de funciones del módulo: Se escribirá sólo el prototipo de la función. que aparezcan en el mismo orden que sus prototipos. aunque a veces se pone al final y nunca en medio. 8.h" /*Primitivas para el manejo de una cola*/ 4.directorio de las bibliotecas o librerías estándar) y se puede incluir un comentario aclarativo: #include "raton. . y usar el mismo orden en todos los módulos. ésta se pone la primera. no su implementación. Normalmente. perfectamente conocidos y expresados en sus parámetros. este orden no es estricto y pueden cambiarse algunos puntos por otros. El resto de funciones. salvo que sean variables que se usen en gran parte de las funciones (y módulos) y esté bien definida y controlada su utilidad. si el módulo incluye la función principal. es frecuente saltarse la declaración de los prototipos de las funciones poniendo en su lugar la implementación y. no se deben usar pasos de parámetros por referencia (o por variable). Naturalmente. 7. el programa (y el programador) sabrá el número y el tipo de cada parámetro y cada función. pero debemos ser coherentes. Otro punto muy importante es el referente a variables globales. 6. cuando no sea necesario.h> #include <stdlib.h> double f_factorial(double numero) { double factorial = 1. dejando para el final la función main(). con typedef. la función main().h" /*Rutinas para control del ratón*/ #include "cola. incluida la función principal. que provienen de la modificación indebida de una de estas variables en algún módulo desconocido. se suelen ordenar por orden de aparición en la función principal y poner juntas las funciones que son llamadas desde otras. Declaración de variables globales del módulo: Se trata de las variables globales declaradas aquí. Definición de tipos. con #define. while (numero > 1) factorial *= numero--. Implementación de funciones: Aquí se programarán las acciones de cada función. Por la misma razón que no debemos usar variables globales. A continuación mostramos un programa que calcula el factorial de un número de un número y que utiliza variables locales: #include <stdio. en el otro módulo. viendo así las funciones como cajas negras.

} } return 0. &numero). el principal problema derivado del uso de variables globales reside en encontrar dónde se produce la modificación de una variable que provoca que el programa no funcione correctamente. Este problema es más grave cuanto más complejo . } De hecho. } El mismo programa.0lf\n".return factorial. no funciona (se deja como ejercicio al alumno averiguar por qué). factorial = 1. Aunque la programación se simplifica al evitar el envío y devolución de parámetros a la función. void f_factorial() { while (numero > 1) factorial *= numero--. scanf("%lf". if (numero == 0) printf("El factorial vale 1\n"). else { if (numero > 1) f_factorial().h> #include <stdlib. if (numero == 0) printf("El factorial vale 1\n"). printf("El factorial vale %. printf("El factorial vale %. factorial). } int main() { while (numero >= 0) { printf("Introduce un numero positivo (en caso contrario el programa finaliza): "). simplemente realizado con variables globales. factorial = 1.0lf\n". scanf("%lf".h> double numero = 0. factorial). } } return 0. al final el esfuerzo deberá ser realizado en la etapa de depuración tratando de encontrar dónde está el fallo: #include <stdio. &numero). } int main() { double numero = 0. while (numero >= 0) { printf("Introduce un numero positivo (en caso contrario el programa finaliza): "). else { if (numero > 1) factorial = f_factorial(numero).

Eso suele hacerse así.pero algunos programadores prefieren poner la llave { en la misma columna que la llave }.media/num_datos).contador++) datos[contador] = 0.num_datos. Algunos estudios indican que el indentado debe hacerse con 2.sea el programa y mayor número de funciones utilice. contador++.contador<num_datos. for(contador=0. media_acumulada(). Existe un fallo en este código que el alumno deberá encontrar y que viene derivado del uso de una variable global en varios puntos y funciones del programa: #include <stdio. for (contador=0. contador++) media += datos[contador].contador.h> #include <stdlib. float media. void media_acumulada() { media = 0. contador=0. de forma que se vea rápidamente cuales pertenecen al bloque y cuales no. mostramos a continuación un programa que simplemente pide números al usuario y va calculando y mostrando por pantalla la media acumulada. return 0. } int main() { printf("\nCalculo del valor medio\n"). quedando una encima de otra. Como ejemplo. Usar más espacios no aumenta la claridad y puede originar que las líneas se salgan de la pantalla. } Indentación o sangrado La indentación o sangrado consiste en marginar hacia la derecha todas las sentencias de una misma función o bloque. printf("La media acumulada es: %f\n". La indentación es muy importante para que el lector/programador no pierda la estructura del programa debido a los posibles anidamientos.MAX). do { printf("Introduce un numero entero: "). 3 ó 4 espacios. scanf("%d".&num_datos). contador < num_datos. scanf("%d".h> #define MAX 20 int datos[MAX]. complicando su lectura. Normalmente.&datos[contador]). } while (contador < num_datos). la llave de comienzo de una estructura de control ({) se pone al final de la línea y la que lo cierra (}) justo debajo de donde comienza -como veremos más adelante. en la . printf("Introduce el numero de datos a tratar (MAX %d): ".

sentencia2. case default2: sentencia1. . } Segunda opción: if (condición) { sentencia1. . .. } else { sentencia1. Sentencia swith Primera opción: switch (expresión) { case expresión1:sentencia1.exp2. case expresión2:sentencia1...exp2. sentencia2. sentencia2. . a continuación. las indentaciones típicas en las estructuras de control: Sentencia if-else Primera opción: if (condición) { sentencia1. } .implementación de funciones. sentencia2. } Segunda opción switch (expresión) { case expresión1:sentencia1. } Sentencia for Primera opción: for (exp1. . Veamos. sentencia2.exp3) { sentencia1... .. . .exp3) { sentencia1. ... sentencia2. sentencia2. . sentencia2. break. sentencia2. sentencia2... ... ... case expresión2:sentencia1. case default2: sentencia1. . break. ... . .... break.. } else { sentencia1. .. sentencia2. sentencia2. } Segunda opción: for (exp1... .. donde la llave de apertura de la función se suele poner en la primera columna. break.

.. . sentencia2. sentencia2. Aunque estos formatos no son en absoluto fijos. se recomienda añadir un comentario al final de cada llave de cierre del bloque. . o lo que es lo mismo. } while (condición).. sentencia2.exp2. ..... } /*while (condición_2)*/ } /*if*/ else { sentencia1. sentencia2. if (condición) { sentencia1.exp3) { sentencia1. lo que es muy importante es que quede bien claro las sentencias que pertenecen a cada bloque. sentencia2. . } do-while Sentencia Primera opción: do { sentencia1. Por ejemplo: for (exp1.... sentencia2.. sentencia2... . . while (condición_1) { sentencia1.. . sentencia2.... } while (condición).Sentencia while Primera opción: while (condición) { sentencia1.. En bloques con muchas líneas de código y/o con muchos anidamientos.. . if (condición) { sentencia1. . donde empieza y termina cada bloque. sentencia2. . while (condición_2) { sentencia1. Segunda opción: do { sentencia1.. sentencia2. indicando a qué sentencia cierra.. } Segunda opción: while (condición) { sentencia1.

sentencia2. es complicado averiguar donde se encuentra. por tanto. } } return 0. } int main() { double numero = 0. printf("El factorial vale %. factorial). factorial = 1. &numero). while (numero > 1) factorial *= numero--. .. El siguiente programa muestra un ejemplo claro de programa que tiene un fallo y que. else { if (numero > 1) factorial = f_factorial(numero). Recomendamos al alumno que trate de encontrar dicho fallo arreglando previamente la indentación: #include <stdio. while (numero >= 0) { printf("Introduce un numero positivo: "). de arreglar en caso de errores. if (numero == 0) printf("El factorial vale 1\n"). } Un código mal indentado es difícil de leer y.h> #include <stdlib.. las indentaciones para las funciones deben llevar siempre las llaves de apertura y cierre siempre se ponen a la en la primera columna.0lf\n".h> #define SIZE 25 int main() { .} else { sentencia1. return factorial. como se muestra en la función factorial() y en el main() del siguiente ejemplo: double factorial(double numero) { double factorial = 1. } } /*else*/ } /*while (condición_1)*/ } /*for*/ Por otro lado. debido a la mala indentación del mismo. scanf("%lf".

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

13. Apéndice B Contenido del fichero de texto necesario para la solución 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 .