manual lenguaje c

80
Universidade da Coruña Escuela Universitaria Politécnica Departamento de Computación Manual práctico programación en C Fundamentos de Informática Ingeniería Técnica Industrialalidad Curso 2008/2009

Upload: alfalm

Post on 27-Jun-2015

979 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Manual Lenguaje C

Universidade da Coruña

Escuela Universitaria Politécnica Departamento de Computación

Manual práctico programación en C

Fundamentos de Informática Ingeniería Técnica Industrialalidad

Curso 2008/2009

Page 2: Manual Lenguaje C

Índice 1. Introducción. .............................................................................................................. 32. Entrada / salida básica................................................................................................ 43. Sentencias condicionales. ........................................................................................ 124. Bucles....................................................................................................................... 205. Funciones ................................................................................................................. 266. Punteros (paso por referencia) ................................................................................. 30

Paso por referencia...................................................................................................... 317. Estructuras ............................................................................................................... 378. Arrays....................................................................................................................... 40

Ordenación.................................................................................................................. 439. Cadenas de caracteres .............................................................................................. 4810. Matrices ................................................................................................................... 5211. Ficheros.................................................................................................................... 5612. Reserva dinámica de memoria ................................................................................. 63Apéndice A. Normas de estilo de programación ............................................................ 67

Identificadores significativos ...................................................................................... 67Constantes simbólicas................................................................................................. 68Comentarios ................................................................................................................ 69Estructura del programa.............................................................................................. 70Indentación o sangrado ............................................................................................... 73

13. Apéndice B .............................................................................................................. 78

Antonio Becerra Permuy

Page 3: Manual Lenguaje C

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 que 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 encuentran 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 compilador que se utiliza este año en las clases de prácticas: el Codeblocks. Este programa está disponible en su página web para cualquier plataforma: http://www.codeblocks.org/

Page 4: Manual Lenguaje C

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 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.

Page 5: Manual Lenguaje C

Tras seleccionar la opción para crear un nuevo proyecto nos aparece el cuadro de diálogo de la Figura 2.

Figura 2: Selección del tipo de proyecto

Codeblocks tiene plantillas para distintos tipos de proyectos. Nosotros escogeremos siempre Console Application, que se corresponde con el perfil de nuestra aplicación, sin ningún tipo de interfaz gráfica. Al pinchar sobre el botón Go nos aparece un asistente, en la primera ventana marcamos Next y en la siguiente escogemos la opción C y volvemos a marcar Next. 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. En la siguiente ventana debemos dar un nombre al proyecto (Project Title) y escoger un directorio donde guardar sus ficheros (Folder to create project in). Es conveniente que el directorio sea distinto para cada proyecto, ya que de lo contrario ficheros de un proyecto sobrescribirán ficheros de otro proyecto (por ejemplo, el fichero main.c o las carpetas obj y bin). Además, 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, si es necesario, con “guión bajo”. Otros caracteres como espacios, puntos, guiones, acentos, etc y la letra ñ nunca se deben poner en el nombre del proyecto. Al escoger el nombre del proyecto y su ubicación, las dos casillas Project Filename y Resulting filename se rellenan automáticamente.

Una vez finalizados estos pasos, pinchamos en Next y nos aparece la última ventana del asistente, donde nos pregunta el compilador y el tipo de configuración (Debug o Release) que deseamos crear. Por defecto, dejaremos estas opciones tal cual aparecen y simplemente seleccionamos Finish. Codeblocks nos crea un esqueleto para nuestra aplicación que puede verse en la Figura 3, donde en la parte izquierda de la ventana

Page 6: Manual Lenguaje C

aparece el explorador del proyecto, donde tendremos una carpeta source que contiene el archivo main.c inicial que nos crea Codeblocks. Las dos primeras líneas son directivas del compilador que incluyen sendos ficheros en el de vuestra aplicación. Estos ficheros, stdio.h y stdlib.h, contienen definiciones de constantes y funciones útiles en cualquier aplicación de consola, por ejemplo para leer de teclado y escribir por pantalla. A continuación tenemos la función main. Todo programa en C se divide en funciones. Una función es un conjunto de sentencias que realiza una determinada tarea y que, opcionalmente, puede tener una serie de parámetros de entrada (argumentos) y, también opcionalmente, puede devolver un resultado. Todo programa en C debe tener al menos la función main, que es la función por la cual se empieza a ejecutar un programa.

A la hora de definir una función, la primera línea indica el tipo de dato del valor que devuelve, si es que devuelve alguno, el nombre de la función y los argumentos. En principio, main no tiene porque devolver ningún dato, 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, en caso contrario. Esto es particularmente útil cuando se invoca un programa (“hijo”) desde otro (“padre”) ya que, en ese caso, el sistema operativo permite al programa “padre” consultar el valor devuelto por el programa “hijo” con lo que el primero puede comprobar si el segundo produjo algún error. Como se puede observar, 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;”).

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. Este tipo de instrucción forma parte del contenido de este tema y se verá en detalle a continuación. Decir, por último, 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). Los detalles sobre el funcionamiento del resto de

Page 7: Manual Lenguaje C

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.codeblocks.org/user-manual. A continuación vamos a hacer un primer programa en C que, tras pedir al usuario el valor del radio, nos dé el área del círculo con dicho radio: #include <stdio.h> #include <stdlib.h> int main() { float radio; printf("Introduce el radio del circulo: "); scanf("%f", &radio); printf("El area del circulo es %f\n", 3.14159 * radio * radio); return 0; }

El programa sólo usa una variable: radio. Esta variable está definida dentro de la función main, por tanto es una variable local. Las variables locales sólo son accesibles dentro de la función en la que son definidas. Si estuviese definida fuera de la función, después de los #include, por ejemplo, sería una variable global. Las variables globales son accesibles desde cualquier función de un programa. Como regla general, siempre que sea posible se deben utilizar variables locales, ya que el mantenimiento de un programa que utiliza variables globales es 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. En la normas de estilo que aparecen en el Apéndice A se muestran ejemplos concretos que apoyan esta afirmación. 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. printf escribe en la pantalla, mientras que scanf lee de teclado y almacena lo leído en variables. Lo que printf escribe por pantalla puede ser texto predeterminado o algún dato proveniente de una constante, una variable, una llamada a una función o una expresión aritmética. Sin embargo, scanf no necesita el valor de una variable, si no la dirección de memoria donde se guarda el contenido de esa variable, ya que va a sobrescribir dicho contenido con lo que se lea por teclado. Es por eso que las variables en scanf van precedidas por un signo & que se puede interpretar como “dirección de memoria de”, mientras que eso no sucede con printf. Otro aspecto a mencionar en este primer programa es que la sintaxis de C fue elaborada por anglosajones, con lo que:

- El separador de decimales es el punto, no la coma. - No se pueden utilizar caracteres acentuados y ni la letra “ñ” en los nombres de

las variables o las funciones. Finalmente, resaltar la importancia de una correcta indentación en el código fuente. En C, el elemento sintáctico que determina el final de una sentencia es el “;”. Un programa en C podría escribirse, si así se desease, en una sola línea. Obviamente, esto no es deseable. Y no lo es porque dificulta el entendimiento del funcionamiento del programa y, por tanto, su mantenimiento. Un programa no sólo debe funcionar correctamente y

Page 8: Manual Lenguaje C

eficientemente, si no que debe estar programado de tal forma que realizar modificaciones en él en el futuro sea lo más fácil posible. Uno de los elementos que ayudan a esto es la correcta indentación del código fuente: • Al empezar a escribir un programa, todas las sentencias se escriben de tal forma que

disten la misma distancia del margen izquierdo. • Sin embargo, cuando se empieza a escribir el código de una función, de una

sentencia condicional, o de una sentencia iterativa, el código se separa x caracteres adicionales del margen izquierdo, donde x es un número constante para todo el programa.

• Ninguna línea debe de pasar de 80 caracteres para facilitar su impresión. Si una sentencia excede de esa longitud, se continúa en la línea siguiente dejando nuevamente x caracteres adicionales en el margen izquierdo.

A lo largo de los ejemplos presentes en este manual, se podrá observar como se aplican estas reglas de indentación. En el Apéndice A de este manual, mostramos unas normas básicas de estilo de programación en C que serán aplicables a cualquier programa realizado a partir de ahora. A continuación, modificamos el ejercicio anterior de forma que el programa no sólo nos dé el área del círculo para un radio determinado, sino también la longitud de la circunferencia para ese mismo radio: #include <stdio.h> #include <stdlib.h> int main() { float radio, pi=3.14159; printf("Introduce el radio del circulo: "); scanf("%f", &radio); printf("El area del circulo es %.3f\n", pi * radio * radio); printf("La longitud de la circunferencia es %.3f\n", 2 * pi * radio); system("PAUSE"); return 0; }

A diferencia del ejercicio anterior, aquí hemos utilizado una variable pi para almacenar en ella el valor del número π en vez de usar una constante. En general, siempre es mejor definir las constantes como tales o almacenarlas en variables que no introducirlas directamente en las sentencias que las usen, ya que lo primero facilita el mantenimiento del 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, más probabilidades hay de que nos equivoquemos haciéndolo). La expresión 2 * pi * radio produce como resultado un número en punto flotante. Cuando en una expresión aparecen valores en punto flotante y valores enteros, los enteros son convertidos a punto flotante antes de realizar la operación. Por tanto, 2 es convertido a 2.0 antes de multiplicarse por pi. Este tipo de conversiones automáticas deben de tenerse en cuenta. Por ejemplo, ambas expresiones producen resultados distintos: 3 * 5 / 2

Page 9: Manual Lenguaje C

3 * 5 / 2.0 La primera da como resultado 7, ya que tanto la multiplicación como la división son con números enteros. Sin embargo, la segunda da como resultado 7.5, ya que al ser 2.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. 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: %.3f provoca que sólo se impriman 3 decimales. Como se ha comentado con anterioridad, las constantes es mejor definirlas como tales o introducirlas en variables antes que explicitarlas cada vez que se requieren. Lo primero, en C, se realiza mediante la directiva #include: #include <stdio.h> #include <stdlib.h> #define PI 3.14159 int main() { float radio; printf("Introduce el radio del circulo:\t\t"); scanf("%f", &radio); printf("El area del circulo es\t\t\t%.3f\n", PI * radio * radio); printf("La longitud de la circunferencia es\t%.3f\n", 2 * PI * radio); return 0; }

En programación, una constante se puede pensar como una variable que no es tal, es decir, a la que no se le puede cambiar el valor inicial una vez se le ha asignado este. Sin embargo, en C es un poco distinto. Lo que realmente hace #define es sustituir literalmente en el código fuente, desde el momento en el que aparece el #define, todas las instancias de lo definido por su valor antes de realizar la compilación. Es decir, en el ejemplo anterior las apariciones de PI se cambiarían por 3.14159 antes de compilar. Esto tiene las siguientes implicaciones: • No se puede cambiar el valor de una constante. Esto es muy fácil de ver. Si tenemos

un #define NUMERO 2

y, posteriormente, hacemos NUMERO = 3;

eso sería lo mismo que poner 2 = 3;

lo cual, obviamente, no tiene sentido. • Un #define no tiene porque restringirse a un valor numérico, si no que puede ser

cualquier cosa y puede tener incluso argumentos como si fuese una función. Ej:

Page 10: Manual Lenguaje C

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

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. Por ejemplo, esto sería erróneo (aunque fácilmente corregible poniendo paréntesis alrededor de x+y en el #define): #include <stdio.h> #include <stdlib.h> #define SUMA(x,y) x+y int main() { int x,y; printf("Dime un numero: "); scanf("%d", &x); printf("Dime otro numero: "); scanf("%d", &y); printf("La mitad de la suma entera es %d\n", SUMA(x,y)/2); system("PAUSE"); return 0; }

A la hora de definir constantes, éstas se suelen poner en mayúsculas, reservando las minúsculas para las variables, para que, en cualquier lado del código fuente, se pueda saber de forma inmediata cuando algo es una constante / definición o una variable / función. En los printf de la última versión mostrada del programa que calcula el área de un círculo, aparece el carácter especial “\t”, que representa el carácter tabulador y, por tanto, a la hora de imprimirlo por pantalla se substituye por espacios en blanco. Para finalizar con el apartado de entrada / salida básica, veremos una versión más del programa que calcula el área de un círculo: #include <stdio.h> #include <stdlib.h> #include <math.h> int main() { float radio, pi_radio; printf("Introduce el radio del circulo:\t\t"); scanf("%f", &radio); pi_radio = M_PI * radio; printf("El area del circulo es\t\t\t%.3f\n", pi_radio * radio); printf("La longitud de la circunferencia es\t%.3f\n", 2 * pi_radio);

Page 11: Manual Lenguaje C

return 0; }

La utilización de número π es muy habitual, por lo que en realidad no es necesario definirlo en nuestros programas. Podemos hacer un #include del fichero math.h que incluye definiciones de 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, por ejemplo, calcular raíces cuadradas, logaritmos, potencias, etc.). Estas son las constantes definidas en math.h: #define M_E 2.7182818284590452354 #define M_LOG2E 1.4426950408889634074 #define M_LOG10E 0.43429448190325182765 #define M_LN2 0.69314718055994530942 #define M_LN10 2.30258509299404568402 #define M_PI 3.14159265358979323846 #define M_PI_2 1.57079632679489661923 #define M_PI_4 0.78539816339744830962 #define M_1_PI 0.31830988618379067154 #define M_2_PI 0.63661977236758134308 #define M_2_SQRTPI 1.12837916709551257390 #define M_SQRT2 1.41421356237309504880 #define M_SQRT1_2 0.70710678118654752440

Como se puede ver, 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. Anteriormente hemos comentado que un programa no sólo debe de funcionar, si no que debe de hacerlo de forma eficiente. Por tanto, ¿para qué repetir dos veces la misma operación? En esta última versión del programa, hemos creado una variable temporal pi_radio en la que guardamos el valor resultante de multiplicar pi por radio. De esta forma, en los dos printf usamos el valor de esta variable en vez de repetir la operación. Notar que la asignación de un valor a una variable es siempre de la forma variable = expresión, es decir, la variable se pone a la izquierda del signo igual y el valor a almacenar en la variable, o la expresión que producirá un valor a almacenar en la variable, se pone a la derecha. En la siguiente captura de pantalla podemos ver el resultado de ejecutar el programa.

Figura 4: Aplicación que calcula la circunferencia y el área del círculo a partir del radio.

Page 12: Manual Lenguaje C

3. Sentencias condicionales. 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. Una posible primera implementación sería esta: #include <stdio.h> #include <stdlib.h> #include <math.h> int main(int argc, char *argv[]) { double a, b, c, raiz, dos_a; printf("Introduce el valor de \"a\":\t"); scanf("%lf",&a); printf("Introduce el valor de \"b\":\t"); scanf("%lf",&b); printf("Introduce el valor de \"c\":\t"); scanf("%lf",&c); raiz = sqrt(b * b - 4 * a * c); dos_a = 2 * a; printf("Una solucion es:\t\t%lf\n", (-b + raiz) / dos_a); printf("La otra solucion es:\t\t%lf\n", (-b - raiz) / dos_a); system("PAUSE"); return 0; }

En este programa vemos varias cosas nuevas: • Las variables de tipo double. 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. 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, en las máquinas usadas en clase de prácticas se usan 4 bytes para los float y 8 bytes para los double.

• Como las comillas se usan para empezar y finalizar las cadenas de caracteres, cuando se quiere introducir ese carácter en la propia cadena de caracteres es necesario anteponer el carácter ‘\’.

• Para imprimir o leer un double se usa el indicador de formato %lf. • Para calcular la raíz cuadrada de un número usamos la función sqrt, definida en

math.h. Además, 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. • Los operadores * y / tienen más prioridad que + y -, por eso es necesario agrupar (-

b+raiz) y (-b-raiz) entre paréntesis, ya que en caso contrario sólo raiz estaría siendo dividida por dos_a.

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

Page 13: Manual Lenguaje C

• (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. 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.

• a es 0 => no se puede hacer una división por 0. En este caso, nos encontramos con que la ecuación sólo tiene una solución (-c/b).

Antes de proseguir, es necesario recalcar una cosa: ¡no es suficiente con probar un programa una vez para asegurar su correcto funcionamiento! Es muy habitual la frase “pues a mi me funciona” cuando se muestra un fallo en un programa a un alumno. Aunque la verificación formal no entra dentro del programa de esta asignatura, es importante tener claro que probar el correcto funcionamiento de un programa no es trivial. Algunos consejos prácticos son: • Comprobar que no se pueden dar divisiones por cero. • Comprobar que no se pueden dar raíces cuadradas de números negativos. • Comprobar que no se pueden dar situaciones de desbordamiento. Por ejemplo, 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.

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

Comparación

Asignación

Page 14: Manual Lenguaje C

return 0; }

Figura 5: Aplicación que calcula las raíces reales de una ecuación de segundo grado

Sobre este programa, recalcaremos varios aspectos que suelen ser fuente de errores al empezar a programar en C: • Los operadores para realizar una comparación de igualdad y para realizar una

asignación son distintos. Para lo primero se usan dos símbolos “=” adyacentes mientras que para lo segundo un único símbolo “=”. Si se usa el operador incorrecto (asignación en vez de comparación o comparación en vez de asignación), no se producirá ningún error de compilación, si no que el programa no se comportará como cabría esperar. Esto es así porque ambos tipos de expresión tienen un significado completo como sentencia. Una comparación es una expresión que devuelve “1” si se verifica y “0” en caso contrario. Ponerla en una sentencia como en el siguiente ejemplo, que siempre imprimirá el valor de x, es sintácticamente correcto aunque no tenga sentido ya que no se utiliza el resultado para nada: #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x, y, z; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); z = x; if (y > z) z == y; printf("El mayor es %d\n", z); system("PAUSE"); return 0; }

Page 15: Manual Lenguaje C

• Por otra parte, 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”. El siguiente programa funciona de forma totalmente incorrecta, diciendo que los números son distintos cuando “y” vale 0 y que son iguales en cualquier otro caso: #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); if (x = y) printf("Los numeros son iguales.\n"); else printf("Los numeros son distintos.\n"); system("PAUSE"); return 0; }

• No se pone el carácter “;” después de la condición del if. El carácter “;”, en C, sirve para finalizar sentencias. No tiene sentido ponerlo después de la condición porque, necesariamente, hay que indicar al menos una instrucción a ejecutar en el caso de que la condición se cumpla. Nuevamente, si se pone el “;” no siempre se produce un error (sólo se producirá si hay un else asociado al if), si no que el programa no se comporta como el alumno espera ya que poner un “;” tiene el significado asociado de “sentencia vacía”. Supongamos estos dos programas, el primero dará error de compilación, mientras que el segundo siempre imprimirá el valor de y:

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); if (x > y); printf(“El mayor es %d\n”, x); else printf(“El mayor es %d\n”, y); system("PAUSE"); return 0; } #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x, y, z; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y);

Page 16: Manual Lenguaje C

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

Se pueden anidar expresiones lógicas sencillas para construir otras más complejas mediante el uso de los operadores lógicos “&&”, “||” y “!”. El siguiente programa, que pregunta un mes y nos dice cuántos días tiene, 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.h> #include <stdlib.h> int main(int argc, char *argv[]) { int mes; printf("Escribe el mes (1-12): "); scanf("%d", &mes); if ((mes >= 1) && (mes <= 12)) if (mes == 2) printf("El mes tiene 28 dias.\n"); else if ((mes == 4) || (mes == 6) || (mes == 9) || (mes == 11)) printf("El mes tiene 30 dias.\n"); else printf("El mes tiene 31 dias.\n"); else printf("Ese mes no existe!\n"); system("PAUSE"); return 0; }

Page 17: Manual Lenguaje C

Nuevamente, dos importantes aspectos a tener en cuenta: • Salvo que se tenga muy clara la prioridad de los distintos operadores del lenguaje C,

es recomendable usar los paréntesis para agrupar subexpresiones. En el ejemplo anterior, gran parte de los paréntesis no son necesarios (los que rodean las comparaciones), pero incluso aunque se sea consciente de este hecho, ponerlos ayuda a dar mayor claridad al código fuente.

• Al igual que pasaba con los operadores de comparación de igualdad y asignación, 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. 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.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x, y; printf("Introduce un numero: "); scanf("%d",&x); printf("Introduce otro numero: "); scanf("%d",&y); printf("El \"and\" de los dos números vale %d\n", x && y); system("PAUSE"); return 0; }

“if (..) … 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. Cuando tenemos que mirar el valor de una variable de tipo entero y realizar distintas acciones en función de dicho valor, usar “switch (…) case…” reduce un poco el tamaño del código fuente y nos evita usar numerosos “if (..) … else …” encadenados. El siguiente ejemplo, que calcula el área de una figura geométrica u otra en función de lo seleccionado por el usuario, ilustra este comentario: #include <stdio.h> #include <stdlib.h> #include <math.h> int main(int argc, char *argv[]) { char opcion; float lado1, lado2, radio; printf("Selecciona una figura para calcular el area (a, b o c):\n"); printf("\ta) Cuadrado.\n"); printf("\tb) Rectangulo.\n"); printf("\tc) Circulo.\n"); scanf("%c", &opcion); switch (opcion) { case 'a': printf("Introduce el lado del cuadrado: "); scanf("%f", &lado1); printf("El area vale %.3f\n", lado1 * lado1);

Page 18: Manual Lenguaje C

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

Figura 7: Aplicación que calcula el área de un cuadrado, rectángulo o círculo

Observaciones: • Para almacenar un carácter se usa una variable de tipo char, la cual se lee de teclado

/ imprime por pantalla usando el identificador de formato %c. • Aunque char se usa para guardar caracteres, es en realidad un tipo de dato entero,

como lo pueda ser int, ya que una letra se representa por un número (código ASCII). Por eso opcion puede ser un char. Sin embargo, 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).

• Notar que para indicar que a, b y c son caracteres se rodean por comillas simples. Si no se usan comillas tendremos un error porque el compilador pensará que son variables, mientras que si usamos comillas dobles el error se deberá a que el compilador pensará que son cadenas de caracteres.

Page 19: Manual Lenguaje C

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

Page 20: Manual Lenguaje C

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

Figura 8: Aplicación que calcula el factorial de un número

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

Page 21: Manual Lenguaje C

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

Consideraciones: • Las variables son esta vez de tipo long int. El estándar dice que el rango de números

representable por un long int es igual o mayor que el de un int. En las máquinas del laboratorio, ambos tipos pueden representar un entero entre -2147483648 y 2147483647, pero otras arquitecturas o con otro compilador los rangos podrían ser distintos. Puede parecer un rango suficiente, pero el programa, en realidad, sólo funciona con números menores o iguales a 12. Para números mayores, no obtendremos ningún error, si no un resultado incorrecto ya que el código generado por el compilador no detecta situaciones de desbordamiento. Las variables deberían ser, en realidad, double para tener un mayor rango de representación aunque los números a almacenar sean enteros. Y aun así, para números no muy grandes, no tendremos un resultado preciso.

• Al igual que con el if, y por las mismas razones, no se debe poner punto y coma después de la condición del while.

• 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, la asignación y la expresión aritmética se pueden abreviar como muestra la siguiente tabla:

Sentencia Forma(s) abreviada(s)

x = x + y x += y

x = x – y x -= y

x = x * y x *= y

x = x / y x /= y

x = x + 1 x++ ++x x = x – 1 x-- --x

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

Imprime 5

Page 22: Manual Lenguaje C

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

Como consideración final al respecto de estas formas abreviadas de expresar algunas operaciones aritméticas en C, decir que sólo se deben de usar si se está absolutamente seguro de cómo funcionan, ya que se pueden crear sentencias poco claras, especialmente utilizando ++ y -- en la misma sentencia conjuntamente con otros operadores. Ej:

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int x=5; printf("%d\n", (x++) + (--x)); printf("%d\n", x); system("PAUSE"); return 0; }

En este último ejemplo, el primer printf podría escribirse como printf("%d\n", x+++--x); pero eso sería todavía mucho menos claro: aunque no sean necesarios, deben utilizarse paréntesis siempre que se considere adecuado con el objetivo de facilitar la compresión del código fuente.

Teniendo en cuenta todo esto, el ejemplo anterior del factorial puede escribirse de la siguiente forma: #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { double numero, factorial; printf("Introduce un numero: "); scanf("%lf", &numero); if (numero < 0) printf("No existe el factorial de un numero negativo!\n"); else { factorial = 1; while (numero > 1) factorial *= numero--; printf("El factorial vale %.0lf\n", factorial); } system("PAUSE"); return 0; }

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

Imprime 6

Imprime 7

Imprime 7

Imprime 9

Imprime 8

Imprime 5

Page 23: Manual Lenguaje C

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

Consideraciones al respecto de la versión con do…while(): • Ahora sí hay un punto y coma después de la condición, ya que en este caso la

condición es la última parte de una sentencia do…while(), a diferencia del while() en el cual al menos una sentencia tiene que seguir a la condición.

• Las sentencias dentro de un do…while() se ejecutan al menos una vez. Esto es una diferencia con el while(), en el cual puede darse el caso de que nunca se ejecuten. Esto es así porque en el while() la condición se evalúa antes de ejecutar el contenido del bucle, mientras que en el do…while() se evalúa después. Por esto, y para que el programa funcione correctamente en el caso de que el número introducido por el usuario sea el 0, hemos tenido que añadir un if adicional y poner el do…while()

Inicialización

Condición

Actualización

Page 24: Manual Lenguaje C

dentro de ese if. Por lo general, sólo se usa do…while() cuando sabemos a ciencia cierta que el contenido del bucle debe ejecutarse siempre al menos una vez.

Consideraciones al respecto de la versión con for(): • for() permite escribir de forma más compacta los bucles while(). De hecho, se puede

establecer la siguiente equiparación entre for() y while(): A while(B) { C D }

for (A; B; D) C

• Las partes de inicialización y actualización pueden ser mucho más complejas e incluir varias sentencias separadas por comas, pero hay que tener cuidado de no abusar de esa característica, ya que puede producir código fuente muy confuso.

Cualquier bucle puede implementarse con cualquiera de las tres sentencias iterativas, pero while() tiene la sintaxis más clara y fácil de entender, con lo que, si no se está seguro de la sintaxis de las otras sentencias, recomiendo optar por el uso de while(). Aunque no se ha comentado explícitamente, un programa puede contener cualquier tipo de sentencia dentro de una sentencia condicional o iterativa. Es decir, una sentencia condicional puede contener en su interior otras sentencias condicionales o sentencias iterativas. Igualmente, una sentencia iterativa puede contener sentencias condicionales o más sentencias iterativas. El siguiente ejemplo corresponde a un programa en el que se pide al usuario realizar un programa que lea un número 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.

Ejemplo: ¿Es el número 6 perfecto? Los divisores de 6 son: 3, 2 y 1. 3+2+1=6 por tanto 6 es un número perfecto: #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int numero, contador, suma, candidato; printf("Introduce un numero: "); scanf("%d", &numero); if (numero < 1) printf("El numero ha de ser un entero positivo\n"); else { printf("Los numeros perfectos entre 1 y %d son: ", numero); for (candidato = 1; candidato <= numero; candidato++) { suma = 0; for (contador = 1; contador <= candidato; contador++) if ((candidato % contador) == 0)

Page 25: Manual Lenguaje C

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

Como se puede ver en el código anterior, 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, 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, en este caso, el bucle más exterior. 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). A continuación mostramos una versión más optimizada del programa anterior, 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, por otro lado, 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.h> #include <stdlib.h> int main(int argc, char *argv[]) { int numero, contador, suma, candidato, mitad_candidato; printf("Introduce un numero: "); scanf("%d", &numero); if (numero < 1) printf("El numero ha de ser un entero positivo\n"); else { printf("Los numeros perfectos entre 1 y %d son: ", numero); for (candidato = 2; candidato <= numero; candidato++) { mitad_candidato = candidato / 2; for (contador = 2, suma = 1; contador <= mitad_candidato; contador++) if ((candidato % contador) == 0) suma += contador; if (suma == candidato) printf("%d ", candidato); } printf("\n"); } system("PAUSE"); return 0; }

Es altamente recomendable que, tras realizar una versión inicial que funcione de un programa, 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). Este proceso depende del algoritmo que se deba implementar en cada caso.

Page 26: Manual Lenguaje C

5. Funciones A medida que los problemas a resolver mediante un programa en C se hacen más complicados, el código se convierte cada vez en más difícil de estructurar y, sobre todo, de comprender dado el gran número de líneas que lo componen. Una solución consiste en descomponer los problemas en otros más sencillos que se puedan analizar y programar de forma independiente. A cada uno de estos problemas más sencillos se les denomina módulo, y a este tipo de programación, programación modular. En lenguaje C, 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. Para dividir un programa en funciones, hay que distinguir las partes que tienen alguna independencia. Después se intenta dividir estas partes en otras más pequeñas y así sucesivamente, hasta llegar a fragmentos lo suficientemente simples. Esta forma de proceder se conoce como diseño Top-Down. Un programa diseñado mediante la técnica de programación modular está formado por: • El programa principal, que describe la solución completa del problema y consta

fundamentalmente de llamadas a funciones. 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.

• Las funciones, que tienen una estructura similar a la de un programa (declaración de variables, programación estructurada y 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. Las funciones, para preservar sus fundamentos, deben ser pequeñas, claras y sencillas.

A cada función de un programa en C se le debe asignar un nombre significativo con la tarea que lleva a cabo. Por ejemplo, si necesitamos una función que calcule la suma de dos números, un nombre adecuado sería calculaSuma. Una buena metodología a la hora de asignar nombres a las funciones consiste en que empiece por minúscula y, en caso de constar de varias palabras, éstas se escriban pegadas unas a las otras y con su primera letra mayúscula. Cuando en un punto de un programa se llama a una función, se ejecutarán todas las instrucciones de la misma, y una vez finalizada, se vuelve al punto del programa desde donde se llamó dicha 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. Además, suelen devolver un resultado que deberá ser tratado en el punto del programa desde el que se llamó a la función. 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:

Page 27: Manual Lenguaje C

( ))!(!

!kNk

NNk −=

Por ejemplo, las combinaciones de 4 elementos tomados de 3 en 3 se calcularían:

( ) 4)!34(!3

!443 =

−=

NOTA: tanto N como k deben ser mayores que 0

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

Page 28: Manual Lenguaje C

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 system("PAUSE"); 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

Page 29: Manual Lenguaje C

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().

Page 30: Manual Lenguaje C

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(int argc, char *argv[]) { double a; double *punt;

Page 31: Manual Lenguaje C

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); system("PAUSE"); 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

La principal razón para la utilización de los punteros en C, viene de la necesidad de que una función pueda modificar varios parámetros. 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.

Page 32: Manual Lenguaje C

En cambio, muchas veces es necesario que una función devuelva varios parámetros y, en este caso, es necesario el uso de punteros. 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. 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. Por ejemplo, en el siguiente programa, dados tres números se pide crear una función que calcule el menor, el mayor y la media de dichos números:

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

Page 33: Manual Lenguaje C

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

Queda claro en este ejemplo la necesidad del uso de punteros, ya que así la función calcularMedia trabaja sobre las variables num1, num2 y num3 del main() y no sobre copias. De esta forma, las modificaciones que se hacen sobre *num1, *num2 y *num3 afectan a las variables originales (como se puede ver en el último printf del main() que muestra el contenido de num1, num2 y num3 modificadas en la función). Es importante resaltar el uso ya explicado del operador & a la hora de mandar las direcciones de las variables num1, num2 y num3 a la función, que son recogidas, obviamente, por tres punteros *num1, *num2 y *num3. 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. 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. Para ello, se debe calcular el máximo común divisor mediante la siguiente función (algoritmo de Euclides): int mcd(int a, int b) { while(a!=b) { if (a<b) b=b-a; else a=a-b; } return a; }

Page 34: Manual Lenguaje C

A continuación mostramos el resultado de la ejecución del programa y su código:

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

Page 35: Manual Lenguaje C

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

,num1,den1,num2,den2,num_suma); else printf("El resultado de sumar %i/%i + %i/%i es %i/%i\n"

,num1,den1,num2,den2,num_suma,den_suma); system("PAUSE"); return 0; }

En este caso, 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. Por otro lado, la función sumarFracciones mezcla paso por referencia y paso por valor, ya que requiere el paso de 4 parámetros por valor (num1, num2, den1 y den2) de los que

Page 36: Manual Lenguaje C

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

Page 37: Manual Lenguaje C

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

La instrucción: struct Nombre Var1;

Declara una variable del tipo "struct Nombre", esto es, el compilador reserva la cantidad de memoria sufuciente para mantener la estructura íntegra (es decir espacio para almacenar Campo1, Campo2, ..., CampoN). Cuando se hace referencia a la variable Var, se esta haciendo referencia a la estructura íntegra. Se puede inicializar una estructura externa o estática añadiendo a su definición la lista de inicializadotes, por ejemplo: struct Fecha { int Dia; char *Mes; int Anio; }; struct Fecha Hoy = {8,"Mayo",2008}, VarFecha; ... VarFecha = Hoy; La asignación VarFecha = Hoy copia la estructura integra Hoy en VarFecha. Cuando dentro de los campos de una estructura aparecen punteros y uno realiza este tipo de asignación, se esta copiando también los valores de los punteros, de tal manera que se puede estar haciendo referencia a un dato desde dos puntos diferentes lo que puede causar efectos no deseados y un potencial peligro para la aplicación. Un campo de una estructura se utiliza como una variable más. Para referenciar un campo de una estructura se emplea el operador . Hoy.Dia = 24; Hoy.Mes = "Agosto"; Hoy.Anio = 1991;

Page 38: Manual Lenguaje C

Ejemplo para verificar año bisiesto: Bisiesto = Fecha.Anio%4 == 0 && Fecha.Anio%100 != 0 || Fecha.Anio%400 == 0; El uso de estructuras permite que una función modifique y devuelva más de un tipo de dato, ya que lo que se devuelve es la estructura. De cara a ilustrar esta posibilidad, 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, inicialmente, paso de argumentos por referencia: #include <stdio.h> #include <stdlib.h> #include <math.h> void circulo(float radio, float *p_area, float *p_circunferencia) { *p_area = M_PI * radio * radio; *p_circunferencia = 2.0 * M_PI * radio; } int main(int argc, char *argv[]) { float radio, area, circunferencia; printf(”Introduce el radio: "); scanf("%f", &radio); circulo(radio, &area, &circunferencia); printf("Area: %.2f Circunferencia: %.2f\n", area, circunferencia); system("PAUSE"); return 0; }

El mismo programa usando una estructura sería: #include <stdio.h> #include <stdlib.h> #include <math.h> struct s_circulo { float area; float circunferencia; }; struct s_circulo f_circulo(float radio) { struct s_circulo circulo; circulo.area = M_PI * radio * radio; circulo.circunferencia = 2.0 * M_PI * radio; return circulo; } int main(int argc, char *argv[]) { float radio; struct s_circulo circulo; printf(”Introduce el radio: "); scanf("%f", &radio); circulo = f_circulo(radio); printf("Area: %.2f Circunferencia: %.2f\n", circulo.area, circulo.circunferencia);

Page 39: Manual Lenguaje C

system("PAUSE"); return 0; } En el programa anterior hemos utilizado una función que devuelve un dato de tipo estructura struct s_circulo.

Page 40: Manual Lenguaje C

8. 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. Por ejemplo, los elementos de un vector: si nuestro programa necesita almacenar en memoria un vector, casi seguro que las operaciones que haga con él afectarán a todos sus elementos (ya sea leerlos, imprimirlos por pantalla, compararlos, etc.). Es para esta necesidad de almacenar en memoria un conjunto de datos del mismo tipo para lo que se usan los arrays. Un array es, por tanto, una variable que contiene múltiples datos del mismo tipo y que podremos tratar, bien de forma conjunta, bien elemento a elemento, según las necesidades del programa. A continuación veremos un ejemplo de programa que emplea un array. Este programa debe leer N números por teclado (donde N le será preguntado al usuario), almacenarlos en un array, sumar los números positivos almacenados en las posiciones pares del array y mostrar el resultado por pantalla con dos decimales:

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

Page 41: Manual Lenguaje C

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

Consideraciones: • En este ejercicio tenemos ejemplo de todo lo visto hasta ahora: E/S, constantes,

sentencias condicionales, condiciones compuestas, sentencias iterativas y arrays. Recuerda que: • Las constantes, aunque no es obligatorio, se suelen declarar en mayúsculas. • Cuando se leen datos con scanf se debe poner un & delante de cada variable en

la que se vaya a guardar un dato, porque scanf necesita saber la dirección de memoria de esas variables.

• En una expresión lógica, || significa “o” y && significa “y”. • El operador % significa “módulo” y se usa, por tanto, para saber cuál es el resto

de una división entre dos números enteros. • No confundas el operador de asignación “=” con el operador de comparación de

igualdad “==”. • Se pueden concatenar cadenas de caracteres simplemente cerrando una con

comillas y abriendo la siguiente con otras comillas, aunque estén en líneas distintas. Lo que no se puede hacer es crear una cadena de caracteres en una línea y continuarla en la siguiente.

• Un array, como cualquier otra variable, es necesario declararlo. En este caso, a la derecha del nombre, se pone entre corchetes el número de elementos que tiene el array. Una vez se ha declarado, C no permite cambiar el número de elementos del array.

• El usuario debe especificar con cuántos números desea trabajar, pero dado que no hemos visto asignación dinámica de memoria y tenemos que declarar el array antes de usarlo, debemos crear un array sobredimensionado y controlar que el usuario no especifica un número de elementos mayor que el tamaño del array.

• Recuerda que para acceder a un elemento concreto del array se pone el nombre de éste y, entre corchetes, el índice del elemento al que queremos acceder, que será un número entero entre 0 y N-1 donde N es el número de elementos del array. El índice puede ser una constante, una variable o incluso una expresión, siempre y cuando el resultado final de evaluarla sea un número entero. En el programa que acabamos de ver se usa una variable, i, que va tomando todos los valores desde 0 hasta n-1 porque nos interesa recorrer todos los elementos del array.

• Salvo el caso particular de los arrays de caracteres (strings) que veremos más adelante, no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar los arrays con una sola instrucción. Es decir, no podemos leer, escribir, sumar, etc. arrays con una instrucción, si no que tenemos que hacerlo elemento a elemento.

Page 42: Manual Lenguaje C

En cuanto al uso de arrays y funciones, como vimos en el tema anterior, el paso por referencia y, consecuentemente, el uso de punteros es necesario en C cuando una función debe modificar varias variables. Por este motivo, en el caso de pasar un array a una función, siempre se realiza por referencia, 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). A continuación mostramos el código del ejemplo anterior realizado ahora usando una función par_positivo que calcula la suma: #include <stdio.h> #include <stdlib.h> #define SIZE 256 float par_positivo (float array[SIZE], int numeros) { float sum = 0; int i; for (i = 0; i < numeros; i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]; } return sum; } int main(int argc, char *argv[]) { int n, i; float array[SIZE], suma; printf("Cuantos numeros vas a introducir?: "); scanf("%d", &n); if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE); else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"); for (i = 0; i < n; i++) scanf("%f", &array[i]); suma = par_positivo(array, n); printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %.2f\n", suma); } system("PAUSE"); return 0; }

Como vemos, 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, pero como hemos dicho anteriormente, el paso de arrays es siempre por referencia. El nombre de un array es realmente un puntero a la dirección de memoria donde empieza el array. Este es el motivo por el cual en este ejemplo no es necesario un return de matriz, al ser un paso por referencia se está trabajando siempre sobre la matriz del

Page 43: Manual Lenguaje C

main(). De hecho, en el siguiente programa mostramos la forma más común de pasar arrays a funciones utilizando un puntero como argumento: #include <stdio.h> #include <stdlib.h> #define SIZE 256 float par_positivo (float *array, int numeros) { float sum = 0; int i; for (i = 0; i < numeros; i++) { if (((i % 2) == 0) && (array[i] > 0)) sum += array[i]; } return sum; } int main(int argc, char *argv[]) { int n, i; float array[SIZE], suma; printf("Cuantos numeros vas a introducir?: "); scanf("%d", &n); if ((n < 1) || (n > SIZE)) printf("Tienes que indicar un numero positivo y menor que %d.\n", SIZE); else { printf("A continuacion introduce los numeros separados por espacios en\n" "blanco o por retornos de carro:\n"); for (i = 0; i < n; i++) scanf("%f", &array[i]); suma = par_positivo(array, n); printf("El resultado de la suma de los numeros positivos\n" "almacenados en posicion par es: %.2f\n", suma); } system("PAUSE"); return 0; }

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

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. En concreto, es importante conocer el funcionamiento básico de los algoritmos de ordenación ascendente o descendente de datos numéricos almacenados en un array.

Page 44: Manual Lenguaje C

Existen distintas formas de ordenar dichos datos, que se diferencian mucho en cuanto a su rapidez y eficiencia derivadas del número de operaciones que realizan. 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, en este caso, de menor a mayor un array de números enteros. Este método es uno de los más sencillos y, a la vez, uno de los más ineficientes:

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

Page 45: Manual Lenguaje C

system("PAUSE"); return 0; }

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

Page 46: Manual Lenguaje C

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

Por tanto, 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, el método de selección directa se vuelve mucho más eficiente que el de la burbuja y, como dijimos antes, en el caso extremo de que el array inicial esté ordenado de mayor a menor, realizaría únicamente tantos intercambios como elementos tenga el array. Un ejercicio interesante es comprobar este incremento de eficiencia guardando e imprimiendo una variable contador que aumente su valor en una unidad cada vez que se lleva a cambio un intercambio en un método y en otro. Para finalizar este apartado dedicado a los algoritmos de ordenación simples, decir simplemente que para ordenar de mayor a menor en los ejemplos anteriores, únicamente sería necesario cambiar el signo de comparación del primer if de < a >. De hecho, 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.h> #include <stdlib.h> #define MAX 100 void ordena (int tipo, int *vector, int elem) { int aux, i, j, k; for(i=0; i<elem; i++) { aux = vector[i]; k = i; for (j=i+1; j<elem; j++) { if (tipo == 0) { if (vector[j] < aux) { aux = vector[j]; k = j; } } else { if (vector[j] > aux) { aux = vector[j]; k = j;

Page 47: Manual Lenguaje C

} }

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

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

Page 48: Manual Lenguaje C

9. Cadenas de caracteres Las cadenas de caracteres son simplemente arrays que almacenan variables de tipo char. 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). El uso de variables de tipo carácter cobra una gran importancia práctica a la hora de trabajar con cadenas de caracteres, ya que desde el punto de vista del usuario, esto le permite utilizar palabras y frases de forma cómoda. Existen en C una serie de funciones específicas para operar con estas cadenas, por ejemplo para: – Leer o escribir una cadena entera (array completo) (gets y puts) – Averiguar la longitud de una cadena (strlen) – Buscar un carácter o grupo dentro de una cadena (strchr) – Unir dos cadenas o extraer parte de una cadena (strcat, strtok) Es importante recalcar que estas funciones sólo sirven para cadenas de caracteres, no para arrays numéricos. A continuación mostramos un ejemplo de manejo de cadenas de caracteres, que consiste en averiguar si una frase es o no palíndromo (es decir, si se lee igual de izquierda a derecha y de derecha a izquierda):

#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[])

Page 49: Manual Lenguaje C

{ char frase[40]; int i, j, longitud, es_palindromo=1; printf("\nIntroduzca una frase: "); gets(frase); /* convierte a minúsculas la frase */ strlwr(frase); /* elimina los espacios en blanco */ for(i=0; frase[i]!=0;i++) { if (frase[i] == ' ') for(j=i;frase[j]!=0;j++) frase[j] = frase[j+1]; } longitud = strlen(frase); for (i=0; i<=longitud/2 && es_palindromo == 1; i=i+1) if (frase[i] != frase[longitud-1-i]) es_palindromo = 0; if (es_palindromo == 1) printf("\nLa frase es un palindromo\n"); else printf("\nLa frase no es un palindromo\n"); system("PAUSE"); return 0; }

La idea básica para la resolución de este ejemplo es que se debe comparar el primer carácter del array con el último, el segundo con el penúltimo, etc. En el caso de que una de estas comparaciones detecte que dos caracteres simétricos no son iguales, la frase ya no será palíndromo. Previamente a esta comparación, 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, a continuación, eliminar los espacios en blanco que contenga la frase para que la comparación tenga sentido. Además, debemos tener en cuenta las siguientes consideraciones relacionadas con la utilización de cadenas de caracteres: • En el ejemplo anterior, la declaración de una cadena de caracteres se realiza

declarando un array de 40 caracteres. Esto implica, de acuerdo con lo que comentamos antes, que la frase más grande que podríamos utilizar en este caso es de 39 caracteres, ya que el último carácter es el nulo.

• Las cadenas de caracteres se pueden leer completas usando las funciones gets() y scanf() con %s. Es decir, no es necesario leer los caracteres uno a uno en un bucle como se hacía en el caso de arrays numéricos.

• 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. Pero si se leen más datos de los que caben en la cadena, desborda la cadena que traerá como consecuencia fallos impredecibles en la ejecución del programa (acceso a dirección de memoria inválida, sobrescribir variables del propio programa), por lo cual su utilización está desaconsejada y de ahora en adelante no volverá a aparecer.

Page 50: Manual Lenguaje C

• Un opción que evita el uso de gets() es usar scanf() con indicador de longitud (%), aunque sólo funciona con palabras, no con frases ya que el scanf() para de leer al encontrar un espacio en blanco, tabulador, o retorno de carro.

• Para solucionar este problema existe la posibilidad de utilizar scanf con indicador de longitud (%) y con un indicador de caracteres permitidos de lectura (entre corchetes) que le puede indicar que pare de leer únicamente al detectar un retorno de carro. Por ejemplo: scanf("%16[^\n]s", cadena); Esta forma de lectura será la empleada a partir de ahora en lugar del gets.

• Las funciones strlwr() y strlen() son ejemplos de funciones específicas de las cadenas de caracteres que no pueden ser utilizadas con arrays numéricos.

• 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. Debemos almacenar dicha variable en otra conocida de nuestro programa, en este caso, longitud.

• Por último, el programa anterior utiliza una variable que únicamente toma dos valores (cero o uno) para detectar si la cadena es palíndromo. Dicha variable (es_palindromo) se pone a cero en caso de que la condición dentro del bucle se cumpla, lo que indicaría que no es palíndromo. Como consecuencia, 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.

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

Page 51: Manual Lenguaje C

printf("\nLa frase es un palindromo\n"); else printf("\nLa frase no es un palindromo\n"); system("PAUSE"); return 0; } El uso de arrays de caracteres y funciones es equivalente al resto de arrays explicado en el tema anterior.

Page 52: Manual Lenguaje C

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

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

Page 53: Manual Lenguaje C

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

En este programa hay que tener en cuenta las siguientes consideraciones: • Una matriz, como cualquier otra variable, es necesario declararla. En este caso, a la

derecha del nombre de la variable, se ponen tantos corchetes como dimensiones tenga la matriz. En el ejemplo anterior, al ser matrices bidimensionales, se usaron dos pares de corchetes para indicar en el primero de ellos el número de filas y en el segundo el número de columnas de la matriz. Al igual que sucedía con los arrays, una vez se ha declarada la matriz no se permite cambiar el número de elementos de la misma.

• El programador debe especificar el tamaño (números de elementos) de cada dimensión. En el ejemplo anterior se especificaron el número de filas y de columnas de la matriz. Puesto que dicho tamaño no puede ser sobrepasado, será necesario controlar que el usuario no especifica un número de elementos mayor que el tamaño de la matriz.

• Recuerda que para acceder a un elemento concreto de la matriz se pone el nombre de éste y, entre corchetes, el índice del elemento al que queremos acceder, que será un número entero entre 0 y N-1 donde N es el número de elementos de esa dimensión de la matriz. El índice puede ser una constante, una variable o incluso una expresión, siempre y cuando el resultado final de evaluarla sea un número entero. En el programa que acabamos de ver se usan una variable, i, que va tomando todos los valores desde 0 hasta numfil porque nos interesa recorrer todos las filas de la matriz y otra variable numcol para ir recorriendo todas las columnas de la matriz. Hay que tener en cuenta que para cada fila de la matriz se 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, no existen elementos en el lenguaje C ni tampoco funciones que nos permitan tratar las matrices con una sola instrucción. Es decir, no podemos leer, escribir, sumar, etc. matrices con una instrucción, si no que tenemos que hacerlo elemento a elemento.

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

Page 54: Manual Lenguaje C

(mediante una función) los elementos de la diagonal principal y secundaria de una matriz cuadrada:

#include <stdio.h> #include <stdlib.h> #define MAX 10 void modificarDiagonalesMatriz(int matriz[][MAX],int dim) { int i; for (i=0; i<dim; i++) { matriz[i][i]=0; matriz[i][dim-i-1]=0; } } int main(int argc, char *argv[]) { int i,j,dimension,matriz[MAX][MAX]; printf("Introduzca la dimension de la matriz cuadrada: "); scanf("%d",&dimension); if (dimension>MAX || dimension<=0) printf("Error. El numero de elementos es incorrecto (Maximo= %i)\n",MAX); else { for (i=0; i<dimension; i++) for (j=0; j<dimension; j++) { printf("Introduzca el elemento %i, %i: ", i,j); scanf("%i",&matriz[i][j]); } modificarDiagonalesMatriz(matriz,dimension);

Page 55: Manual Lenguaje C

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

La función modificarDiagonalesMatriz recibe como parámetros la matriz y la variable dimension y no devuelve nada (void). Por este motivo, en el main() únicamente se llama a la función y se le pasan los parámetros correspondientes. Al terminar la ejecución de la función, la matriz ha quedado modificada también en el main(). De cara a clarificar el hecho de que no se realiza una copia de la matriz local a la función, en la declaración de la misma hemos mantenido el nombre que tenía en el main(). En cambio, la variable dimension se almacena localmente en la función en una variable con otro nombre, dim (las modificaciones que se hiciesen sobre dim no afectarían a la variable dimension). Por otro lado, la declaración de la matriz en la cabecera de la función no incluye la primera dimensión, pero sí la segunda tal y como habíamos comentado anteriormente.

Page 56: Manual Lenguaje C

11. Ficheros Hasta este momento, los datos que hemos utilizado en nuestros programas, se almacenaban en la memoria principal y dejaban de estar accesibles cuando el programa finalizaba. Los ficheros permiten almacenar información de manera permanente en la memoria secundaria (disco duro, cd, dvd, …) de tal forma que puedan ser accedidos con posterioridad por el propio programa o por otros. Asimismo, un programa puede leer datos por teclado (como hemos hecho hasta el momento) pero también los puede leer de un fichero. De hecho, esta es una opción muy común cuando deben ser leídas grandes cantidades de datos, ya que evita que el usuario tenga que teclear demasiado. Los ficheros de texto pueden ser de dos tipos: de texto o binarios. Los ficheros de texto almacenan información “legible”, es decir, basada en el código ASCII. Cualquier fichero que no sea de texto, es binario. Para explicar el funcionamiento práctico de la lectura y escritura de ficheros en C, utilizaremos un programa que realiza lo siguiente programa: 1- Pedir al usuario el nombre de un archivo 2- Pedir al usuario el número de datos N que desea guardar en el archivo (máximo 100) 3- Crear N datos aleatorios entre 0 y 1 4- Almacenar los datos en el archivo anterior NOTA: si el programa se ejecuta desde el diskette, es necesario que el nombre del fichero incluya la ruta completa donde se encuentra el archivo de texto, por ejemplo: a:\programas\ficheros\palabras.txt #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 int main() { int i,j,k; int cantidad; char nombre[35]; FILE *fichero; printf("\n\nIntroduce el nombre del fichero destino sin extension: "); scanf("%30[^\n]s",nombre); strcat(nombre,".txt"); /* lo abro como lectura para ver si ya existe */ fichero = fopen(nombre,"w"); if (fichero == NULL) printf(“\n\nERROR..... No se puede crear el fichero %s en” “ la ruta especificada\n\n”,nombre); else { do { printf("\nIntroduzca Numero de datos a guardar (maximo %d): ",MAX); scanf("%d",&cantidad); } while (cantidad>=MAX || cantidad<=0);

Page 57: Manual Lenguaje C

/* almaceno en el fichero los numeros aleatorios */ srand(time(NULL)); for(i=0; i<cantidad; i++) fprintf(fichero,"\n%f", (float)rand()/RAND_MAX); fclose(fichero); } system("PAUSE"); return 0; }

La comprobación de que este ejercicio funciona correctamente se realiza abriendo el archivo de texto 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, que son realmente aleatorios). 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. En cuanto a la utilización de ficheros, vemos como mediante la instrucción

FILE *fichero;

declaramos un puntero que será utilizado para acceder a los ficheros (realmente es un puntero a la estructura FILE, declarada en stdio.h). Antes de usar un fichero es necesario realizar una operación de apertura del mismo y, posteriormente, si se desea almacenar datos en él hay que realizar una operación de escritura. Cuando ya no se quiera utilizar el fichero se realiza una operación de cierre. En este sentido, en el programa anterior se lleva a cabo la apertura de un fichero para escribir datos de él. El nombre del fichero se solicita al usuario sin extensión, se le concatena la extensión .txt mediante la función strcat y se almacena en la cadena de caracteres nombre, mediante la instrucción:

fichero = fopen(nombre, "w");

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). Como segundo parámetro es necesario especificar el modo de apertura de dicho fichero, que es una cadena de caracteres que indica el tipo del fichero (texto o binario) y el uso que se va ha hacer de él (lectura, escritura, añadir datos al final, etc). En este caso, el fichero nombre almacenará una serie de números aleatorios, y se abre como escritura. A partir de esta instrucción, en nuestro programa accederemos al fichero mediante la variable fichero. Si existe algún tipo de error al realizar la operación de apertura del fichero (por ejemplo, porque el disco está lleno, porque el usuario no tiene permisos de escritura en el disco, porque la ruta especificada no existe, etc), fopen devuelve el valor NULL. Esto nos permite controlar un error la apertura del fichero mediante una instrucción condicional, como la utilizada en este caso:

if (fichero == NULL)

Page 58: Manual Lenguaje C

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(), que devuelve un número entero aleatorio entre 0 y RAND_MAX (el mayor número entero que puede generar la función rand). 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. Si no se utiliza una semilla distinta cada vez (esto se consigue mediante la función time), siempre obtendríamos los mismos aleatorios en distintas ejecuciones del programa. Los números aleatorios que se generan con la función rand deben ser guardados en el fichero. Para ello es necesario realizar una operación de escritura. En este caso utilizaremos dentro de un bucle la función fprintf, análoga al printf que hemos usado hasta ahora pero especificando como primer argumento el puntero al fichero donde queremos escribir:

fprintf(fichero,"%f\n", (float)rand()/RAND_MAX); Por último, tras terminar de usar cada fichero, debemos cerrarlo utilizando la función fclose. Como continuación del ejercicio anterior, y de cara a mostrar la lectura de datos de un archivo, a continuación mostramos el código del siguiente programa en C:

1- Pedir al usuario el nombre de un archivo donde se encuentran, como máximo, 100 números en punto flotante entre 0 y 1.

2- Leer los números del archivo y ordenarlos de menor a mayor 3- Guardar los datos ordenados en otro archivo cuyo nombre se pedirá de nuevo al

usuario #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX 100 int main() { int i,j,k; int c; /* variable que utilizaremos para borrar el buffer del teclado */ int num_datos=0; float vector[MAX], aux; char nombre[35]; FILE *fichero; printf("\n\nIntroduce el nombre del fichero origen sin extension: "); scanf("%30[^\n]s",nombre); strcat(nombre,".txt"); /* Abro el archivo anterior como lectura */ fichero = fopen(nombre,"r"); if (fichero == NULL) printf("\n\n ERROR..... No se puede leer el fichero %s \n\n",nombre);

Page 59: Manual Lenguaje C

else { /* leo los datos aleatorios del archivo */ while (! feof(fichero)) { fscanf(fichero,"%f",&vector[num_datos]); num_datos++; } fclose(fichero); /* Ordeno de menor a mayor */ for(i=0; i<num_datos; i++) { aux = vector[i]; k = i; for (j=i+1; j<num_datos; j++) if (vector[j] < aux) { aux = vector[j]; k = j; } if (k != i) { vector[k] = vector[i]; vector[i] = aux; } } /* Fin del proceso de ordenacion */ /* esta instrucción borra el buffer del teclado */ while((c = getchar()) != '\n' && c != EOF); /* Guardo los datos en un archivo */ printf(“\nIntroduce el nombre del fichero destino para”

“ los datos ordenados (sin extension): “); scanf("%30[^\n]s",nombre); strcat(nombre,".txt"); if ((fichero = fopen(nombre,"w")) == NULL) printf(“\n\nERROR..... No se puede crear el fichero %s en”

“ la ruta especificada\n\n”,nombre); else { for(i=0; i<num_datos; i++) fprintf(fichero,"%f\n",vector[i]); fclose(fichero); } } system("PAUSE"); return 0; } El resultado de este programa será correcto si el archivo creado contiene los mismos datos que el archivo original, pero ordenados de menor a mayor. En este caso, para obtener los datos hay que efectuar una operación de lectura de fichero. Una de las posibles instrucciones de lectura es fscanf, que es la utilizada en el programa anterior. Su estructura es similar al scanf que ya hemos utilizado en los

Page 60: Manual Lenguaje C

capítulos anterior, con la diferencia de que hay que especificar como primer argumento el fichero en el que leemos:

fscanf(fichero,"%f",&vector[num_datos]);

En ciertos casos no conoceremos el número de datos que almacena el fichero, de modo que es útil la utilización de la función feof() que se muestra en el ejemplo anterior. Esta función nos devuelve un valor distinto de 0 si se alcanza el final de fichero. Por esta razón, las instrucciones: while (! feof(fichero)) { fscanf(fichero,"%f",&vector[num_datos]); num_datos++; }

leen los datos (y los cuentan mediante la variable num_datos) hasta que se alcanza el final del archivo sin necesidad de que el usuario introduzca cuántos hay. Una vez que se tienen los datos en el array vector simplemente ejecutamos el algoritmo de ordenación visto en el tema 5. Antes de añadir estos datos ordenados a un fichero de destino debemos vaciar previamente el buffer del teclado mediante la expresión:

while((c = getchar()) != '\n' && c != EOF);

El buffer de teclado es una memoria temporal donde se introducen los caracteres tecleados antes de pulsar la tecla RETURN (Intro o Enter), la cual es otro carácter (el carácter '\n'). Usualmente, cuando se usan funciones de lectura como scanf() o getchar(), por ejemplo, la ejecución del programa se detiene hasta que se introducen estos caracteres en el buffer de teclado, o sea, hasta que se pulsa la tecla RETURN. Sin embargo, si en el buffer ya existen valores entonces la función de lectura lee esos valores sin detenerse. Cuando estamos leyendo números la función intenta leer números, saltándose algunos caracteres de control (si se los encuentra), como el carácter '\n'. Sin embargo, 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, sea cual sea. Si no hay ninguno, entonces, la función se detiene a la espera de poder leer. Pero si en el buffer existe algún carácter éste será leído, aunque sea un carácter de control. 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, se suele utilizar la instrucción flush(stdin) que vacía dicho buffer. 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, y por ello no podemos estar seguros de cómo se comportará en un caso general. Aunque en algunos compiladores funciona, no lo hará en todos, por lo cual nosotros recomendamos la utilización de la expresión anterior como equivalente para el fflush(stdin). Continuando con el programa anterior, solo nos resta pedir al usuario un nombre para el fichero destino, abrir el fichero ahora en modo escritura y guardar en él los datos del array vector.

Page 61: Manual Lenguaje C

A continuación mostramos otro ejemplo del uso de archivos en C que lee datos de un fichero “numeros.txt” que contiene los valores de una matriz de tamaño 10 x 10. Así cada línea del fichero contiene 3 números, el primero de ellos indica el número de fila, el segundo el número de columna y el último el valor correspondiente. El objetivo es realizar un programa en C que lea este fichero “numeros.txt” y muestre por pantalla y guarde en otro fichero el contenido del fichero original, pero de forma matricial. El contenido del fichero de texto se muestra en el Apéndice B, al final de este manual. La ejecución del programa proporciona la siguiente salida por pantalla:

#include <stdio.h> #include <stdlib.h> #define MAX 10 int main(int argc, char *argv[]) { int matriz[MAX][MAX]; int i,j,valor; char nombreFichero[40]; FILE *fichero1,*fichero2; printf("\nIntroduzca el nombre del fichero origen: "); scanf(“%s39[^\n]s,nombreFichero); fichero1 = fopen(nombreFichero, "rb"); if (fichero1 != NULL) { printf("\nIntroduzca el nombre del fichero destino: "); scanf(“%s39[^\n]s,nombreFichero); fichero2 = fopen(nombreFichero, "wb"); if (fichero2!=NULL) {

Page 62: Manual Lenguaje C

while (! feof(fichero1)) { fscanf(fichero1, "%i", &i); fscanf(fichero1, "%i", &j); fscanf(fichero1, "%i", &valor); matriz[i][j]=valor; } for (i=0;i<MAX;i++) { for (j=0;j<MAX;j++) { printf("%4i",matriz[i][j]); fprintf(fichero2,"%4i",matriz[i][j]); } printf("\n"); fprintf(fichero2,"\n"); } fclose(fichero2); } else printf("\nError al crear el fichero\n"); fclose(fichero1); } else printf("\nError al abrir el fichero\n"); system("PAUSE"); return 0; }

Como vemos, la utilización de las funciones de acceso a ficheros es muy similar al ejercicio anterior.

Page 63: Manual Lenguaje C

12. 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, asignando normalmente un tamaño por exceso y dejando el resto sin usar. Por ejemplo, si realizamos un programa en C que calcula el producto de dos matrices, éstas se pueden declarar para un tamaño máximo de 100x100 (float matriz[100][100]), de manera que en dicho programa se podrá calcular cualquier producto de un tamaño igual o inferior a 100x100, 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, que resulta en una utilización ineficiente de la memoria. Por este motivo, es muy útil el poder reservar más o menos memoria en tiempo de ejecución, según el tamaño del caso concreto que se vaya a resolver. Hablamos, en este caso, de reserva dinámica de memoria. La función que permite crear o asignar un espacio de memoria interna durante la ejecución de un programa es malloc(), 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, o NULL si no es posible reservar la cantidad de memoria pedida. La memoria reservada mediante malloc() puede (y debe) ser liberada una vez utilizada mediante la función free() que requiere como parámetro el puntero asignado en 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. A continuación se introducen los elementos uno a uno y se calcula la media:

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

Page 64: Manual Lenguaje C

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

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. Una vez que el usuario introduce el número de elementos que tendrá el array (variable n), mediante la función malloc() se realiza la reserva de un número de bytes que permita almacenar n elementos de tipo float. Para ello, 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. Por ejemplo, en este caso sizeof(float) devolvería un 4, que al multiplicarlo por n nos dará el número exacto de bytes necesario para almacenar en memoria los datos que introducirá el usuario. La función malloc() devuelve un puntero de tipo void (es decir, 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. A partir de este momento, 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. En el resto del ejemplo, si la memoria no se pudo asignar correctamente, se finaliza la ejecución del programa. Por último, debemos liberar siempre la memoria cuando ya no sea necesaria su utilización, en este caso con la sentencia free(numeros). Un aspecto que debe ser tratado cuando se trabaja con funciones, es la posibilidad de que se realice reserva dinámica de memoria dentro de una función. 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, a continuación, pide los elementos del array uno a uno y calcula la media. En este caso se han utilizado funciones para realizar las operaciones básicas de reserva de memoria y de cálculo de la media:

Page 65: Manual Lenguaje C

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

Como podemos observar en el código, 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. Tras realizar la llamada a malloc(), la función devuelve un puntero que apunta a la primera dirección de memoria asignada a array. Por este motivo, 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. 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, es decir, la dirección de memoria donde comienza el array, y por tanto, una variable tipo float * que es donde se almacena.

Page 66: Manual Lenguaje C

Para finalizar, debemos notar que en este ejemplo se ha usado la función de reserva dinámica calloc(), que es idéntica a malloc() pero que inicializa a cero las posiciones reservadas.

Page 67: Manual Lenguaje C

Apéndice A. Normas de estilo de programación

Estas normas de estilo de programación, están basada en las que pueden ser consultadas en la página web: http://www.ieev.uma.es/fundinfo/matdoc/normasC.html correspondientes a la asignatura de Fundamentos de Informática de la Universidad de Málaga. Han sido ligeramente modificadas y adaptadas para el contexto de la actual asignatura y conforman una serie de “buenos hábitos” de programación, es decir, una serie de indicaciones sobre el formato de los programas que el alumno debería seguir siempre. Estos consejos de estilo no son propios del lenguaje C, sino que pueden ser aplicados a cualquier otro lenguaje.

Independientemente de los algoritmos usados, hay muchas formas y estilos de programar. La legibilidad de un programa es demasiado importante como para prestarle la atención que merece por parte del programador. Es frecuente que uno deba modificar un programa escrito por otro programador o bien que deba actualizar un programa escrito por uno mismo en el pasado. Es entonces, cuando surgen los problemas de legibilidad de un programa. Para poder modificarlo, primero hay que comprender su funcionamiento, y para facilitar esta tarea el programa debe estar escrito siguiendo unas normas básicas. 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. Por eso, al programar debemos intentar que nuestros programas sean lo más expresivos posibles, para ahorrarnos tiempo, dinero y quebraderos de cabeza a la hora de modificarlos.

Además, el C es un lenguaje que se presta a hacer programas complejos y difíciles de comprender. En C se pueden encapsular órdenes y operadores, de tal forma que, aunque consigamos mayor eficiencia su comprensión sea todo un reto.

Unas normas de estilo en programación, son tan importantes que todas las empresas dedicadas a programación imponen a sus empleados una mínima uniformidad, para facilitar el intercambio de programas y la modificación por cualquier empleado, sea o no el programador inicial. Por supuesto, cada programa debe ir acompañado de una documentación adicional, que aclare detalladamente cada módulo del programa, objetivos, algoritmos usados, ficheros...

No existen un conjunto de reglas fijas para programar con legibilidad, ya que cada programador tiene su modo y sus manías y le gusta escribir de una forma determinada. Lo que sí existen son un conjunto de reglas generales, que aplicándolas, en mayor o menor medida, se consiguen programas bastante legibles. Aquí intentaremos resumir estas reglas:

Identificadores significativos

Un identificador es un nombre asociado a un objeto de programa, que puede ser una variable, función, constante, tipo de datos... 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, no pueden contener espacios (ni símbolos raros) y suelen tener una longitud máxima que puede variar, pero que no debería superar los 10-20 caracteres para evitar lecturas muy pesadas.

Page 68: Manual Lenguaje C

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.

Un identificador debe indicar lo más breve y claramente posible el objeto al que referencia. Por ejemplo, si una variable contiene la nota de un alumno de informática, la variable se puede llamar nota_informatica. Observe que no ponemos los acentos, los cuales pueden dar problemas de compatibilidad en algunos sistemas. El carácter '_' es muy usado para separar palabras en los identificadores.

Es muy normal usar variables como i, j o k para nombres de índices de bucles (for, while...), lo cual es aceptable siempre que la variable sirva sólo para el bucle y no tenga un significado especial. En determinados casos, dentro de una función o programa pequeño, se pueden usar este tipo de variables, si no crean problemas de comprensión, pero esto no es muy recomendable.

Para los identificadores de función se suelen usar las formas de los verbos en infinitivo, seguido de algún sustantivo, para indicar claramente lo que hace. Por ejemplo, una función podría llamarse escribir_opciones, y sería más comprensible que si le hubiéramos llamado escribir o escrOpc. Si la función devuelve un valor, su nombre debe hacer referencia a este valor, para que sea más expresivo usar la función en algunas expresiones, como:

precio_total = precio_total + IVA(precio_total,16) + gastos_transporte(destino);

Constantes simbólicas

En un programa es muy normal usar constantes (numéricas, cadenas...). Si estas constantes las usamos directamente en el programa, el programa funcionará, pero es más recomendable usar constantes simbólicas, de forma que las definimos al principio del programa y luego las usamos cuando haga falta. Así, conseguimos principalmente dos ventajas:

1. Los programas se hacen más legibles: Es más legible usar la constante simbólica PI como el valor de π que usar 3.14 en su lugar:

volumen_esfera = 4/3. * PI * pow(radio,3);

2. Los programas serán más fáciles de modificar: Si en un momento dado necesitamos usar PI con más decimales (3.141592) sólo tenemos que cambiar la definición, y no tenemos que cambiar todas las ocurrencias de 3.14 por 3.141592 que sería más costoso y podemos olvidarnos alguna.

En C, las constantes simbólicas se suelen poner usando una orden al preprocesador de C, quedando definidas desde el lugar en que se definen hasta el final del fichero (o hasta que expresamente se indique). Su formato general es:

#define CONSTANTE valor

Page 69: Manual Lenguaje C

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

#define PI 3.141592

Como hemos dicho, las constantes se suelen poner completamente en mayúsculas y las variables no, de forma que leyendo el programa podamos saber rápidamente qué es cada cosa. En general, 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. Obsérvese, que aunque el valor de π es constante, podemos variar su precisión, por lo que es recomendable usar una constante simbólica en este caso, sobre todo si se va a usar en más de una ocasión en nuestro programa. Puede no resultar muy útil dedicar una constante para el número de meses del año, por ejemplo, ya que ese valor es absolutamente inalterable.

Comentarios

El uso de comentarios en un programa escrito en un lenguaje de alto nivel es una de las ventajas más importantes con respecto a los lenguajes máquina, además de otras más obvias. Los comentarios sirven para aumentar la claridad de un programa, ayudan para la documentación y bien utilizados nos pueden ahorrar mucho tiempo.

No se debe abusar de comentarista, ya que esto puede causar una larga y tediosa lectura del programa, pero en caso de duda es mejor poner comentarios de más. Por ejemplo, es absurdo poner:

Nota = 10; /* Asignamos 10 a la variable Nota */

Los comentarios deben ser breves y evitando divagaciones. Se deben poner comentarios cuando se crean necesarios, y sobre todo:

• Al principio del programa o de cada fichero del programa que permita seguir un poco la historia de cada programa, indicando: Nombre del programa, objetivo, parámetros (si los tiene), condiciones de ejecución, módulos que lo componen, autor o autores, fecha de finalización, últimas modificaciones realizadas y sus fechas... y cualquier otra eventualidad que el programador quiera dejar constancia.

• En cada sentencia o bloque (bucle, if, switch...) que revista cierta complejidad, de forma que el comentario indique qué se realiza o cómo funciona.

• Al principio de cada función cuyo nombre no explique suficientemente su cometido. Se debe poner no sólo lo que hace sino la utilidad de cada parámetro, el valor que devuelve (si lo hubiera) y, si fuera oportuno, los requisitos necesarios para que dicha función opere correctamente.

• En la declaración de variables y constantes cuyo identificador no sea suficiente para comprender su utilidad.

• En los cierres de bloques con '}', para indicar a qué sentencias de control de flujo pertenecen, principalmente cuando existe mucho anidamiento de sentencias y/o los bloques contienen muchas líneas de código.

Page 70: Manual Lenguaje C

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

Estructura del programa

Un programa debe ser claro, estar bien organizado y que sea fácil de leer y entender. Casi todos los lenguajes de programación son de formato libre, de manera que los espacios no importan, y podemos organizar el código del programa como más nos interese.

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). Una función demasiado grande demuestra, en general, una programación descuidada y un análisis del problema poco estudiado. Se deberá, en tal caso, dividir el bloque en varias llamadas a otras funciones más simples, para que su lectura sea más agradable. En general se debe modularizar siempre que se pueda, de forma que el programa principal llame a las funciones más generales, y estas vayan llamando a otras, hasta llegar a las funciones primitivas más simples. Esto sigue el principio de divide y vencerás, mediante el cual es más fácil solucionar un problema dividiéndolo en subproblemas (funciones) más simples.

A veces, es conveniente usar paréntesis en las expresiones, aunque no sean necesarios, para aumentar la claridad.

Cada bloque de especial importancia o significación, y cada función debe separarse de la siguiente con una línea en blanco. A veces, entre cada función se añaden una linea de asteriscos o guiones, como comentario, para destacar que empieza la implementación de otra función (aparte de los comentarios explicativos de dicha función).

El uso de la sentencia GOTO está totalmente desaconsejado. Como dicen Kerninghan y Ritchie en su libro de C, su utilización sólo estaría justificada en casos muy especiales, como salir de una estructura profundamente anidada, aunque para ello recomendamos usar los comandos break, continue, return o la función exit(). De todas formas, esos recursos, para salir de bucles anidados deben usarse lo menos posible y sólo en casos donde quede bien clara su finalidad. Si no queda clara su finalidad, será mejor que nos planteemos de nuevo el problema y estudiemos otra posible solución que seguro que la hay.

Normalmente, un programa en C se suele estructurar de la siguiente forma:

1. Primero los comentarios de presentación, como ya hemos indicado. 2. Después, la inclusión de bibliotecas del sistema, los ficheros .h con el

#include y entre ángulos (<...>) el nombre del fichero. Quizás la más típica sea:

#include <stdio.h>

3. Bibliotecas propias de la aplicación. Normalmente, en grandes aplicaciones, se suelen realizar varias librerías con funciones, separadas por su semántica. Los nombres de fichero se ponen entre comillas (para que no las busque en el

Page 71: Manual Lenguaje C

directorio de las bibliotecas o librerías estándar) y se puede incluir un comentario aclarativo:

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

4. Variables globales, usadas en el módulo y declaradas en otro módulo distinto, con la palabra reservada extern.

5. Constantes simbólicas y definiciones de macros, con #define. 6. Definición de tipos, con typedef. 7. Declaración de funciones del módulo: Se escribirá sólo el prototipo de la

función, no su implementación. De esta forma, el programa (y el programador) sabrá el número y el tipo de cada parámetro y cada función.

8. Declaración de variables globales del módulo: Se trata de las variables globales declaradas aquí, y que si se usan en otro módulo deberán llevar, en el otro módulo, la palabra extern.

9. Implementación de funciones: Aquí se programarán las acciones de cada función, incluida la función principal. Normalmente, si el módulo incluye la función principal, la función main(), ésta se pone la primera, aunque a veces se pone al final y nunca en medio. El resto de funciones, se suelen ordenar por orden de aparición en la función principal y poner juntas las funciones que son llamadas desde otras. Es una buena medida, que aparezcan en el mismo orden que sus prototipos, ya que así puede ser más fácil localizarlas.

Naturalmente, este orden no es estricto y pueden cambiarse algunos puntos por otros, pero debemos ser coherentes, y usar el mismo orden en todos los módulos. Por ejemplo, es frecuente saltarse la declaración de los prototipos de las funciones poniendo en su lugar la implementación y, por supuesto, dejando para el final la función main().

Otro punto muy importante es el referente a variables globales. En general es mejor no usar nunca variables globales, salvo que sean variables que se usen en gran parte de las funciones (y módulos) y esté bien definida y controlada su utilidad. El uso de estas variables puede dar lugar a los llamados efectos laterales, que provienen de la modificación indebida de una de estas variables en algún módulo desconocido. Lo mejor es no usar nunca variables globales y pasar su valor por parámetros a las funciones que estrictamente lo necesiten, viendo así las funciones como cajas negras, a las que se le pasan unos determinados datos y nos devuelve otros, perfectamente conocidos y expresados en sus parámetros. Por la misma razón que no debemos usar variables globales, no se deben usar pasos de parámetros por referencia (o por variable), cuando no sea necesario.

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.h> #include <stdlib.h> double f_factorial(double numero) { double factorial = 1; while (numero > 1) factorial *= numero--;

Page 72: Manual Lenguaje C

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

El mismo programa, simplemente realizado con variables globales, no funciona (se deja como ejercicio al alumno averiguar por qué). Aunque la programación se simplifica al evitar el envío y devolución de parámetros a la función, al final el esfuerzo deberá ser realizado en la etapa de depuración tratando de encontrar dónde está el fallo:

#include <stdio.h> #include <stdlib.h> double numero = 0, factorial = 1; void f_factorial() { while (numero > 1) factorial *= numero--; } int main(int argc, char *argv[]) { while (numero >= 0) { printf("Introduce un numero positivo (en caso contrario el programa finaliza): "); scanf("%lf", &numero); if (numero == 0) printf("El factorial vale 1\n"); else { if (numero > 1) f_factorial(); printf("El factorial vale %.0lf\n", factorial); } } system("PAUSE"); return 0; }

De hecho, 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

Page 73: Manual Lenguaje C

sea el programa y mayor número de funciones utilice. Como ejemplo, mostramos a continuación un programa que simplemente pide números al usuario y va calculando y mostrando por pantalla la 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.h> #include <stdlib.h> #define MAX 20 int datos[MAX],contador,num_datos; float media; void media_acumulada() { media = 0; for (contador=0; contador < num_datos; contador++) media += datos[contador]; printf("La media acumulada es: %f\n",media/num_datos); } int main(int argc, char *argv[]) { printf("\nCalculo del valor medio\n"); printf("Introduce el numero de datos a tratar (MAX %d): ",MAX); scanf("%d",&num_datos); for(contador=0;contador<num_datos;contador++) datos[contador] = 0; contador=0; do { printf("Introduce un numero entero: "); scanf("%d",&datos[contador]); contador++; media_acumulada(); } while (contador < num_datos); system("PAUSE"); return 0; }

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, de forma que se vea rápidamente cuales pertenecen al bloque y cuales no. Algunos estudios indican que el indentado debe hacerse con 2, 3 ó 4 espacios. Usar más espacios no aumenta la claridad y puede originar que las líneas se salgan de la pantalla, complicando su lectura.

La indentación es muy importante para que el lector/programador no pierda la estructura del programa debido a los posibles anidamientos.

Normalmente, la llave de comienzo de una estructura de control ({) se pone al final de la línea y la que lo cierra (}) justo debajo de donde comienza -como veremos más adelante- pero algunos programadores prefieren poner la llave { en la misma columna que la llave }, quedando una encima de otra. Eso suele hacerse así, en la

Page 74: Manual Lenguaje C

implementación de funciones, donde la llave de apertura de la función se suele poner en la primera columna.

Veamos, a continuación, las indentaciones típicas en las estructuras de control:

Sentencia if-else

Primera opción:

if (condición) { sentencia1; sentencia2; ... } else { sentencia1; sentencia2; ... }

Segunda opción:

if (condición) { sentencia1; sentencia2; ... } else { sentencia1; sentencia2; ...

Sentencia swith

Primera opción:

switch (expresión) { case expresión1:sentencia1; sentencia2; ...

break; case expresión2:sentencia1; sentencia2; ...

break; . . .

case default2: sentencia1; sentencia2; ... }

Segunda opción

switch (expresión) { case expresión1:sentencia1; sentencia2; ...

break; case expresión2:sentencia1; sentencia2; ...

break; . . .

case default2: sentencia1; sentencia2; ... }

Sentencia for

Primera opción:

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

Segunda opción:

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

Page 75: Manual Lenguaje C

Sentencia while

Primera opción:

while (condición) { sentencia1; sentencia2; ... }

Segunda opción:

while (condición) { sentencia1; sentencia2; ... }

Sentencia do-while

Primera opción:

do { sentencia1; sentencia2; ... } while (condición);

Segunda opción:

do { sentencia1; sentencia2; ... } while (condición);

Aunque estos formatos no son en absoluto fijos, lo que es muy importante es que quede bien claro las sentencias que pertenecen a cada bloque, o lo que es lo mismo, donde empieza y termina cada bloque. En bloques con muchas líneas de código y/o con muchos anidamientos, se recomienda añadir un comentario al final de cada llave de cierre del bloque, indicando a qué sentencia cierra. Por ejemplo:

for (exp1;exp2;exp3) { sentencia1; sentencia2; ... while (condición_1) { sentencia1; sentencia2; ... if (condición) { sentencia1; sentencia2; ... while (condición_2) { sentencia1; sentencia2; ... } /*while (condición_2)*/ } /*if*/ else { sentencia1; sentencia2; ... if (condición) { sentencia1; sentencia2; ...

Page 76: Manual Lenguaje C

} else { sentencia1; sentencia2; ... } } /*else*/ } /*while (condición_1)*/ } /*for*/

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

Page 77: Manual Lenguaje C

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

Page 78: Manual Lenguaje C

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

Page 79: Manual Lenguaje C

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

Page 80: Manual Lenguaje C

8 2 83 2 6 27 5 1 52 4 4 45 2 8 29 3 3 34