Prólogo El posible lector de este manual, puede preguntarse y con razón, ¿Por qué es necesario otro libro de C? Efectivamente en las bibliotecas de nuestros centros hay numerosos libros de texto para aprender C. Asimismo en la red hay también infinidad de manuales o apuntes editados por distintos departamentos universitarios. Por tanto, necesidad como tal no hay. Sin embargo, aparte de aquello tan castizo de “cada maestrito tiene su librito”, el objetivo de estos apuntes es dar una visión ligeramente distinta de la habitual. Desde nuestro punto de vista, la enseñanza del lenguaje C en los estudios de ingeniería se presenta de una forma antipática, dando más importancia al lenguaje a que el alumno aprenda a programar. El objetivo de estas páginas es aprender C pero simultáneamente aprender a programar con algunos fundamentos adecuados para un curso de iniciación a la programación. Algunas cuestiones que distinguen en los temas que siguen son las siguientes: Se simplifica el lenguaje C obviando algunos detalles que no añaden ninguna funcionalidad interesante: sentencia do_while, operador ?, operador ++ como prefjo, operadores de bits, tipos sin sentido actualmente como short, unsigned, etc. Se trabaja con funciones desde el primer momento, planteando todos los problemas para ser resueltos mediante ellas. De esta forma el alumno debe aprender desde el principio los conceptos de modularidad y reutilización. Las variables arrays o struct deben ser siempre declaradas a través de sus tipos previamente definidos. Igualmente los argumentos de las funciones deben ser tipos declarados. Los arrays siempre son tratados diferenciando tamaño y dimensión. Las funciones que tratan arrays como entrada o salida deben recibir o devolver el tamaño del array respectivamente. Los tratamientos de los algoritmos básicos sobre arrays: recorridos, filtros, búsqueda, etc, se exponen mediante esquemas. Los punteros se explican más tarde de lo que suele ser habitual, y sólo con su uso imprescindible, esto es, para definir argumentos de entrada/salida a funciones. En los problemas siempre se pide una función y el programa principal que la invoque. Numerosos ejercicios reutilizan las funciones de problemas anteriores. Los problemas son algorítmicos, puros de aprender a programar, pero no son algoritmos que podrían denominarse de “idea feliz”, sino que tienen un esquema claro asociado. No se hace apenas mención a los típicos y tan extendidos problemas o ejercicios, pensados más para desarrollar la memoria que para aprender a programar. No encontrará en este manual los ejercicios que dan importancia fundamental a la sintaxis del C, buscando más el error del alumno que su aprendizaje. José C. Riquelme Santos Catedrático de Universidad ETS Ingeniería Informática Universidad de Sevilla Autor: José C. Riquelme (Universidad de Sevilla) TEMA 1. Conceptos de programación. 1. Conceptos básicos. Ordenador o computador. Un ordenador es un sistema capaz de almacenar y procesar con gran rapidez una gran cantidad de información. Además, un ordenador tiene capacidad para comunicarse con el exterior, recibiendo datos, órdenes y programas como entrada (por medio del teclado, del ratón, de un dispositivo USB, Bluetooth, etc.), y proporcionando resultados de distinto tipo como salida (en la pantalla, por la impresora, mediante un fichero en disco, etc.). Partes de un ordenador. Un computador en general, o un PC en particular, constan de distintas partes interconectadas entre sí y que trabajan conjunta y coordinadamente. Las principales son: • Procesador o CPU (Central Processing Unit, o unidad central de proceso). Se encarga de realizar las operaciones aritméticas y lógicas, así como de coordinar el funcionamiento de todos los demás componentes. • Memoria principal o memoria RAM (Random Access Memory). Es el componente del computador donde se guardan los datos y los programas que la CPU está utilizando. Se llama también a veces memoria volátil, porque su contenido se borra cuando se apaga el ordenador, o simplemente cuando se reinicializa. • Disco duro. El disco duro es capaz de mantener la información –datos y programas– de modo estable, también con el computador apagado. El computador no puede trabajar directamente con los datos del disco, sino que antes tiene que transferirlos a la memoria principal. De ordinario cada disco duro está fijo en un determinado computador. • Dispositivos externos de memoria. Desde los antiguos disquetes, los CD o DVD o las memorias USB son elementos que sirven para transportar y guardar de manera fácil información y datos que nos interese guardar. Con la aparición de la “nube” o sitios en internet que nos permiten guardar e intercambiar datos entre ordenadores conectados (Google Drive, Dropbox, etc), estos dispositivos cada vez tienen menos uso. • Pantalla o monitor. Es el elemento “visual” del sistema. A través de él el computador se comunica de forma gráfica o mediante texto con el usuario. • Ratón. Es el dispositivo más utilizado para introducir información no alfanumérica, como por ejemplo, seleccionar una entre varias opciones en un menú o caja de diálogo. Su principal utilidad consiste en mover con facilidad el cursor por la pantalla. • Teclado. Es el elemento más utilizado para introducir información alfanumérica en el ordenador. Programa informático. Es una lista de instrucciones con una finalidad concreta que se ejecutan habitualmente de modo secuencial (una detrás de otra) aunque en ciertos ordenadores es posible también en paralelo. Estas instrucciones se escriben en un fichero fuente siguiendo unas reglas que vienen dadas por un lenguaje de programación concreto. Normalmente un programa procesa unos datos a partir de una entrada de información y presenta al acabar unos resultados de salida. Almacenamiento de datos. La memoria de un computador está constituida por un gran número de unidades elementales, llamadas bits, que almacenan sólo unos o ceros. Un conjunto de 8 bits recibe el nombre de byte u octeto. La capacidad de almacenamiento de memoria de un ordenador suele medir en Kilobytes (1024 bytes), Megabytes o Tema 1. Conceptos de programación simplemente "megas" (1024 Kbytes) y Gigabytes o "gigas" (1024 Mbytes). Durante la ejecución de un programa las instrucciones y datos están habitualmente en la memoria principal o RAM. Los datos de entrada a un programa suelen ser introducidos desde teclado o leídos desde un almacenamiento externo. Los datos de salida suelen ser mostrados en pantalla o escritos en un archivo. Sistema Operativo. Es un programa o conjunto de programas que en un sistema informático gestiona los recursos de hardware y facilita el uso de programas de aplicación como el explorador de ficheros o el navegador web. Lenguajes de programación. Las instrucciones de un programa deben estar escritas en un lenguaje comprensible por el ordenador y dependiendo de la cercanía de ese lenguaje a la máquina concreta se hablan de lenguajes de bajo o alto nivel. El lenguaje de más bajo nivel es el lenguaje máquina binario constituido por un conjunto de unos y ceros, que evidentemente es incomprensible para el ser humano. A partir de ahí los lenguajes ensambladores constituyen el siguiente nivel, comprensibles pero difíciles de programar. Finalmente los lenguajes de alto nivel son capaces de escribir instrucciones con una estructura sintáctica comprensible por el programador y que convenientemente “traducidas” son capaces de ejecutarse en un ordenador. Lenguajes de alto nivel primitivos como Fortran o Cobol de mediados del siglo XX han ido evolucionando hasta lenguajes como Java o Python. Todos los programas excepto los escritos en código máquina binario deben ser “traducidos” para que puedan ser ejecutados. El fichero fuente traducido se suele denominar fichero ejecutable. Compilador. Un compilador es un programa que “traduce” un conjunto de instrucciones escritas en un lenguaje de alto nivel a un lenguaje comprensible por el ordenador. Normalmente el compilador está integrado con otras funcionalidades constituyendo un Entorno de Desarrollo Integrado (IDE en inglés). Un IDE además del compilador, suele disponer de un editor, un depurador y otras herramientas que facilitan la construcción y prueba de programas. Una de las principales tareas de un buen compilador es ayudar al programador a descubrir los errores sintácticos del programa. Como se ha dicho anteriormente los lenguajes de alto nivel tienen una sintaxis bastante estricta, es decir, la estructura de cada instrucción y sus relaciones con las demás están fuertemente condicionadas por un conjunto de reglas sintácticas. Estas reglas obligan al programador a ser muy cuidadoso en la escritura de un programa para que pueda ser traducido por el compilador. Un buen IDE debe proporcionar información adecuada sobre por qué una instrucción no está bien escrita para que el programador pueda corregirla. Otra tarea básica del IDE es el depurador o facilidad que ofrece la posibilidad de ejecutar paso a paso un programa controlando si el orden de las sentencias y los datos que procesa son los que se esperaba o no. 2. Programación de ordenadores 2.1 El lenguaje C. El lenguaje C es un lenguaje de programación creado en 1972 por Dennis M. Ritchie en los Laboratorios Bell. Es un lenguaje cuyo origen es la implementación del sistema operativo Unix. C es apreciado por la eficiencia del código que produce y es el lenguaje de programación más popular para crear software de sistemas, aunque también se utiliza para crear aplicaciones. Se trata de un lenguaje de medio nivel pero con muchas características de bajo nivel. Dispone de las estructuras de control típicas de los lenguajes de alto nivel pero, a su vez, dispone de sentencias y Autor: José C. Riquelme (Universidad de Sevilla) operadores que permiten un control a muy bajo nivel, esto es, cercano a la máquina. La compilación de un programa C se realiza en varias fases que normalmente son automatizadas y ocultadas por el IDE: 1. Preprocesado consistente en modificar el código fuente en C según una serie de instrucciones (denominadas directivas de preprocesado) simplificando de esta forma el trabajo del compilador. 2. Compilación que genera el código objeto a partir del código ya preprocesado. 3. Enlazado que une los códigos objeto de los distintos módulos y bibliotecas externas (como las bibliotecas del sistema) para generar el programa ejecutable final. El fichero fuente de un programa en C tiene la extensión .c, el código objeto después de la fase de compilación mantiene el mismo nombre y la extensión .obj, finalmente el fichero ejecutable después de la fase de enlazado tiene la extensión .exe. 2.2 Ventajas del lenguaje C. Aunque la portabilidad es un estándar en los actuales lenguajes de programación, el C fue uno de los primeros en la portabilidad de los ficheros fuente. Quiere esto decir que un programa desarrollado en una determinada máquina y sistema operativo podrá ser ejecutado en otra, con mínimas modificaciones y una simple recompilación. Esto fue conseguido gracias a la adopción en 1989 del conocido como ANSI C, también llamado C estándar. De esta manera, ANSI C está soportado hoy en día por casi la totalidad de los compiladores y cualquier programa escrito sólo en C estándar funciona (una vez compilado) correctamente en cualquier plataforma que disponga de una implementación de C compatible. Un núcleo del lenguaje simple, con funcionalidades añadidas importantes, como funciones matemáticas y de manejo de archivos, proporcionadas por bibliotecas. Es un lenguaje muy flexible que permite programar con múltiples estilos. Ésta es una cualidad que también puede ser un problema cuando se está aprendiendo a programar. Un sistema de tipos que impide operaciones sin sentido. Acceso a memoria de bajo nivel mediante el uso de punteros. Un conjunto reducido de palabras clave. Permite definir tipos de datos como vectores y estructuras que permite manipular como un todo un conjunto de datos. 2.3 Otros conceptos relacionados. Ingeniería del Software. El desarrollo de aplicaciones software con las restricciones habituales de todo proceso productivo, esto es, con el menor esfuerzo y coste y la mejor calidad posible, requiere de una metodología propia. La ingeniería del software es la disciplina que trata de dar un enfoque sistemático y disciplinado al diseño, desarrollo y mantenimiento del software. Desde este punto de vista, en la actualidad podemos afirmar que la programación de ordenadores no es un arte, a pesar de que durante años la mala interpretación del título de la obra de DE Knuth “The Art of Computer Programming” parecía decir lo contrario. La programación de ordenadores debe ser Tema 1. Conceptos de programación entonces contemplada como un proceso ingenieril, base de la ingeniería del software y pilar fundamental de numerosos problemas que resuelven las distintas ingenierías. Algoritmo. Según la RAE un algoritmo es un conjunto ordenado y finito de operaciones que permite hallar la solución de un problema. Ejemplos cotidianos de algoritmos suelen ser las recetas de cocina o las instrucciones para montar un mueble. En la programación de ordenadores se suele entender por algoritmo una lista de instrucciones que se ejecutan secuencialmente, que normalmente recibe de entrada un conjunto de datos y después de realizar operaciones aritméticas o lógicas con ellos produce un determinado resultado de salida. Un algoritmo suele representarse en programación mediante un diagrama de flujo o un seudocódigo. Diagrama de flujo. Es una representación gráfica de las sentencias de un algoritmo donde cada tipo de sentencia tiene una forma determinada: rombo (bifurcación condicional), rectángulo (asignación), cuadrilátero (lectura o escritura), etc. Las líneas que unen estas figuras representan la secuencia u orden en que se deben ejecutar. Los diagramas de flujo fueron muy usados en los años 70 y 80 del siglo pasado, pero ahora se consideran obsoletos porque no son apropiados para representar fácilmente la división de un problema en módulos ni para representar adecuadamente tipos de datos estructurados. El siguiente diagrama de flujo calcula el factorial de un número: Leer n i=1 f=1 NO i≤n SÍ f=f*i i=i+1 Escribir n y f Seudocódigo. Es un conjunto de instrucciones que en un lenguaje natural intentan describir un algoritmo. El lenguaje empleado para escribir el seudocódigo de un algoritmo suele ser una variante unificada de las principales sentencias o estructuras de control usadas por la mayoría de los lenguajes de programación. De esta manera un seudocódigo se aproxima bastante a la versión final de un programa codificado pero sin Autor: José C. Riquelme (Universidad de Sevilla) tener en cuenta las exigencias sintácticas de un lenguaje de programación. Por ejemplo el seudocódigo del problema anterior sería: Inicio Leer n Hacer i igual a 1 Hacer f igual a 1 Mientras i sea menor o igual a n Hacer f igual a f × i Hacer i igual a i + 1 FinMientras Escribir el factorial de n es f Fin Código fuente. Es el conjunto de sentencias escritas en un lenguaje de programación que una vez compiladas permiten al ejecutarse resolver un problema. Por ejemplo, un programa en C que resuelve el problema anterior es el siguiente: #include <stdio.h> void main(void){ int i, f, n; scanf("%d",&n); i=1; f=1; while (i<=n){ f=f*i; i++; } printf("el factorial de %d es %d\n",n,f); } Fundamentos de Programación. En esta asignatura se sentarán las bases de la programación de ordenadores, siguiendo un conjunto de reglas que convierten la construcción de software en un proceso ingenieril, donde no sólo es importante que el código funcione correctamente, sino que además esté bien hecho, esto es, sea fácil de diseñar, programar y mantener. Para ello en esta asignatura insistiremos desde el principio en tres cuestiones que consideramos básicas: La primera es la obligación de estructurar el código en trozos más pequeños que resuelvan un subproblema concreto. Estos trozos de código han recibido a lo largo de los años distintos nombres: módulos, subrutinas, procedimientos, funciones, etc. Un módulo principal invocará la ejecución de los otros módulos. El módulo principal se comunicará con los módulos mediante el uso de parámetros. En C estos módulos reciben el nombre de funciones. El módulo principal de nuestros programas en C entonces será habitualmente una sucesión de llamadas a funciones. La segunda cuestión es la necesidad de definir los tipos de datos antes de usarlo. Como se ha dicho anteriormente el C es un lenguaje que da mucha libertad al programador y a pesar de ser un lenguaje fuertemente tipificado permite ciertas licencias en su uso que no se permitirán en esta asignatura. Los tipos Tema 1. Conceptos de programación estructurados, vectores (array) o registros (struct) deben definirse como tipos siempre que se vayan a usar. No se usarán las características del C que son de bajo nivel. Esto es, no se hará uso de cuestiones como la aritmética de punteros, ni se accederá a los vectores a partir de sus direcciones de memoria. Únicamente en los casos estrictamente necesarios, esto es, el paso de argumentos de entrada/salida en las funciones se hará uso del concepto de puntero. Modularización. En las líneas anteriores se ha resuelto el problema de obtener el factorial de un número mediante diagrama de flujo, seudocódigo y código en C. Imaginemos que quisiéramos calcular el factorial de varios números. Por ejemplo, para calcular el valor de un número combinatorio, es necesario calcular tres factoriales: ( ) ( ) ¿Es adecuada por tanto la solución que se ha hecho antes? La respuesta es que no, porque en su construcción se ha ignorado la primera de las reglas que hemos enunciado para esta asignatura. La modularización de un programa tiene numerosas ventajas: Reduce la complejidad del problema que se quiere resolver dividiéndolo en problemas más sencillos. Elimina el código duplicado, favoreciendo la reutilización de código y ahorrando en el tiempo de desarrollo de las aplicaciones. Limita los efectos de los cambios al módulo que se desea modificar. Oculta detalles de implementación en problemas complejos. Facilita la depuración y prueba de las aplicaciones. Mejora la legibilidad del código. Facilita la portabilidad del código. Por todo lo anterior, la mejor forma de resolver el problema de dado un número calcular su factorial es definir un módulo o subprograma (función en C) que reciba como argumento el entero n y devuelva el valor de su factorial. El concepto de función en C es el mismo que el usado en matemáticas. Por ejemplo, la función f(x)=2*x+3, contiene el concepto de que se recibe una entrada x y se devuelve un valor calculado como su doble más tres. Por tanto, para esta asignatura la única solución válida en C para calcular el factorial de un número sería: #include <stdio.h> int factorial(int); void main(void){ int n, fac; scanf("%d",&n); fac=factorial(n); printf("el factorial de %d es %d\n",n,fac); } int factorial (int m){ Autor: José C. Riquelme (Universidad de Sevilla) int i, f; i=1; f=1; while (i<=m){ f=f*i; i++; } return f; } 3. Ejercicios. Intente escribir un seudocódigo que intente resolver los siguientes problemas. Para cada problema identifique claramente qué debe recibir como entrada y qué debe devolver cómo salida. 1. 2. 3. 4. 5. 6. 7. 8. 9. Obtenga la suma de los n primeros números naturales. Dados dos números devolver el menor de los dos Dados dos números devolver el mayor de los dos Obtenga la suma de los números que hay entre dos números naturales, ambos incluidos. Dados dos números naturales a y b halle la potencia de a elevado a b. Dados tres valores con los coeficientes de una ecuación de segundo grado, obtenga según los casos sus dos soluciones o raíces, una sola raíz o indique que no hay solución real. Lea un número indeterminado de números hasta que lea uno negativo y calcule la media de los valores leídos. Lea un número indeterminado de números hasta que lea uno negativo y calcule el máximo. Lea un número indeterminado de números hasta que lea uno negativo y calcule el mínimo.