Introduccion Al Desarrollo de SW
Introduccion Al Desarrollo de SW
Introduccion Al Desarrollo de SW
Jordi Mas
Coordinador
Ingeniero de software en la empresa de cdigo abierto Ximian, donde trabaja en la implementacin del proyecto libre Mono. Como voluntario, colabora en el desarrollo del procesador de textos Abiword y en la ingeniera de las versiones en cataln del proyecto Mozilla y Gnome. Es tambin coordinador general de Softcatal. Como consultor ha trabajado para empresas como Menta, Telpolis, Vodafone, Lotus, eresMas, Amena y Terra Espaa.
Primera edicin: marzo 2004 Fundaci per a la Universitat Oberta de Catalunya Av. Tibidabo, 39-43, 08035 Barcelona Material realizado por Eureca Media, SL Autores: Josep Antoni Prez Lpez y Llus Ribas i Xirgo Depsito legal: B-7.600-2004 ISBN: 84-9788-119-2
Se garantiza permiso para copiar, distribuir y modificar este documento segn los trminos de la GNU Free Documentation License, Version 1.2 o cualquiera posterior publicada por la Free Software Foundation, sin secciones invariantes ni textos de cubierta delantera o trasera. Se dispone de una copia de la licencia en el apartado "GNU Free Documentation License" de este documento.
FUOC XP04/90793/00018
ndice
Agradecimientos ............................................................
1. Introduccin a la programacin ............................... 1.1. Introduccin ....................................................... 1.2. Un poco de historia de C..................................... 1.3. Entorno de programacin en GNU/C ................. 1.3.1. Un primer programa en C ..................... 1.3.2. Estructura de un programa simple .......... 1.4. La programacin imperativa ............................... 1.4.1. Tipos de datos bsicos ........................... 1.4.2. La asignacin y la evaluacin de expresiones ....................................... 1.4.3. Instrucciones de seleccin ....................... 1.4.4. Funciones estndar de entrada y de salida ............................................. 1.5. Resumen ............................................................ 1.6. Ejercicios de autoevaluacin ............................... 1.6.1. Solucionario ..........................................
11 11 12 16 17 21 23 24 28 33 35 40 41 42
ANOTACIONES
2. La programacin estructurada ................................. 2.1. Introduccin ....................................................... 2.2. Principios de la programacin estructurada ......... 2.3. Instrucciones iterativas ........................................ 2.4. Procesamiento de secuencias de datos ................ 2.4.1. Esquemas algortmicos: recorrido y bsqueda ........................................... 2.4.2. Filtros y tuberas ..................................... 2.5. Depurado de programas .................................... 2.6. Estructuras de datos ........................................... 2.7. Matrices ............................................................. 2.7.1. Declaracin ........................................... 2.7.2. Referencia ............................................. 2.7.3. Ejemplos ............................................... 2.8. Estructuras heterogneas .................................... 2.8.1. Tuplas ................................................... 2.8.2. Variables de tipo mltiple .......................
45 45 48 49 52 53 59 62 66 67 68 70 71 74 74 77
FUOC XP04/90793/00018
Software libre
2.9.
2.10.
2.11. 2.12.
Tipos de datos abstractos .................................... 2.9.1. Definicin de tipos de datos abstractos .... 2.9.2. Tipos enumerados .................................. 2.9.3. Ejemplo ................................................. Ficheros ............................................................. 2.10.1. Ficheros de flujo de bytes ........................ 2.10.2. Funciones estndar para ficheros ............ 2.10.3. Ejemplo ................................................. Principios de la programacin modular ............... Funciones ........................................................... 2.12.1. Declaracin y definicin ......................... 2.12.2. mbito de las variables .......................... 2.12.3. Parmetros por valor y por referencia ..... 2.12.4. Ejemplo ................................................. Macros del preprocesador de C .......................... Resumen ............................................................ Ejercicios de autoevaluacin ................................ 2.15.1. Solucionario ...........................................
3. Programacin avanzada en C. Desarrollo eficiente de aplicaciones ........................................... 3.1. Introduccin ....................................................... 3.2. Las variables dinmicas ...................................... 3.3. Los apuntadores ................................................. 3.3.1. Relacin entre apuntadores y vectores ..... 3.3.2. Referencias de funciones ......................... 3.4. Creacin y destruccin de variables dinmicas ..... 3.5. Tipos de datos dinmicos .................................... 3.5.1. Cadenas de caracteres ........................... 3.5.2. Listas y colas .......................................... 3.6. Diseo descendente de programas ...................... 3.6.1. Descripcin ............................................ 3.6.2. Ejemplo ................................................. 3.7. Tipos de datos abstractos y funciones asociadas ........................................................... 3.8. Ficheros de cabecera .......................................... 3.8.1. Estructura ............................................... 3.8.2. Ejemplo ................................................. 3.9. Bibliotecas .......................................................... 3.9.1. Creacin ............................................... 3.9.2. Uso ....................................................... 3.9.3. Ejemplo ................................................. 3.10. Herramienta make .............................................. 3.10.1. Fichero makefile .....................................
125 125 127 129 132 135 137 139 140 145 156 157 158 159 166 166 169 172 172 173 174 175 176
ANOTACIONES
4
FUOC XP04/90793/00018
3.11. Relacin con el sistema operativo. Paso de parmetros a programas ............................... 3.12. Ejecucin de funciones del sistema operativo ....... 3.13. Gestin de procesos ........................................... 3.13.1. Definicin de proceso ............................ 3.13.2. Procesos permanentes ............................ 3.13.3. Procesos concurrentes ............................ 3.14. Hilos .................................................................. 3.14.1. Ejemplo ................................................. 3.15. Procesos ............................................................ 3.15.1. Comunicacin entre procesos ................. 3.16. Resumen ............................................................ 3.17. Ejercicios de autoevaluacin ............................... 3.17.1. Solucionario ..........................................
179 181 183 184 185 188 189 190 193 198 202 204 211
ANOTACIONES
4. Programacin orientada a objetos en C++ ............. 4.1. Introduccin ....................................................... 4.2. De C a C++ ..................................................... 4.2.1. El primer programa en C++ .................. 4.2.2. Entrada y salida de datos ....................... 4.2.3. Utilizando C++ como C ........................ 4.2.4. Las instrucciones bsicas ........................ 4.2.5. Los tipos de datos .................................. 4.2.6. La declaracin de variables y constantes ........................................... 4.2.7. La gestin de variables dinmicas .......... 4.2.8. Las funciones y sus parmetros ............... 4.3. El paradigma de la programacin orientada a objetos ........................................................... 4.3.1. Clases y objetos ..................................... 4.3.2. Acceso a objetos .................................... 4.3.3. Constructores y destructores de objetos ............................................. 4.3.4. Organizacin y uso de bibliotecas en C ++................................................ 4.4. Diseo de programas orientados a objetos .......... 4.4.1. La homonimia ....................................... 4.4.2. La herencia simple ................................. 4.4.3. El polimorfismo ..................................... 4.4.4. Operaciones avanzadas con herencia ..... 4.4.5. Orientaciones para el anlisis y diseo de programas ........................................ 4.5. Resumen ............................................................ 4.6. Ejercicios de autoevaluacin ............................... 4.6.1. Solucionario ..........................................
219 219 221 221 223 226 227 227 230 231 235 240 242 246 250 257 261 262 267 273 279
FUOC XP04/90793/00018
Software libre
5. Programacin en Java ............................................... 309 5.1. Introduccin ....................................................... 309 5.2. Origen de Java .................................................. 312 5.3. Caractersticas generales de Java ........................ 313 5.4. El entorno de desarrollo de Java ......................... 317 5.4.1. La plataforma Java ................................ 318 5.4.2. Mi primer programa en Java .................. 319 5.4.3. Las instrucciones bsicas y los comentarios ................................... 320 5.5. Diferencias entre C++ y Java ............................. 321 5.5.1. Entrada/salida ....................................... 321 5.5.2. El preprocesador .................................... 324 5.5.3. La declaracin de variables y constantes . 325 5.5.4. Los tipos de datos .................................. 325 5.5.5. La gestin de variables dinmicas ........... 326 5.5.6. Las funciones y el paso de parmetros .... 328 5.6. Las clases en Java .............................................. 329 5.6.1. Declaracin de objetos ........................... 330 5.6.2. Acceso a los objetos ............................... 331 5.6.3. Destruccin de objetos ........................... 332 5.6.4. Constructores de copia ........................... 333 5.6.5. Herencia simple y herencia mltiple ........ 333 5.7. Herencia y polimorfismo ..................................... 334 5.7.1. Las referencias this y super ................ 334 5.7.2. La clase Object ...................................... 334 5.7.3. Polimorfismo .......................................... 335 5.7.4. Clases y mtodos abstractos ................... 335 5.7.5. Clases y mtodos finales ......................... 336 5.7.6. Interfaces ............................................... 337 5.7.7. Paquetes ................................................ 339 5.7.8. El API (applications programming interface) de Java .................................................. 340 5.8. El paradigma de la programacin orientada a eventos ........................................................... 341 5.8.1. Los eventos en Java ................................ 342 5.9. Hilos de ejecucin (threads) ................................. 344 5.9.1. Creacin de hilos de ejecucin ............... 345 5.9.2. Ciclo de vida de los hilos de ejecucin .... 348 5.10. Los applets ......................................................... 349 5.10.1. Ciclo de vida de los applets .................... 350 5.10.2. Manera de incluir applets en una pgina HTML .............................. 351 5.10.3. Mi primer applet en Java ........................ 352 5.11. Programacin de interfaces grficas en Java .............................................................. 353 5.11.1. Las interfaces de usuario en Java ............ 354
6
ANOTACIONES
FUOC XP04/90793/00018
5.11.2. Ejemplo de applet de Swing ................... 5.12. Introduccin a la informacin visual .................... 5.13. Resumen ............................................................ 5.14. Ejercicios de autoevaluacin ............................... 5.14.1. Solucionario ..........................................
ANOTACIONES
FUOC XP04/90793/00018
Agradecimientos
Los autores agradecen a la Fundacin para la Universitat Oberta de Catalunya (http://www.uoc.edu) la financiacin de la primera edicin de esta obra, enmarcada en el Mster Internacional en Software Libre ofrecido por la citada institucin.
ANOTACIONES
FUOC XP04/90793/00018
1. Introduccin a la programacin
1.1. Introduccin
En esta unidad se revisan los fundamentos de la programacin y los conceptos bsicos del lenguaje C. Se supone que el lector ya tiene conocimientos previos de programacin bien en el lenguaje C, bien en algn otro lenguaje. Por este motivo, se incide especialmente en la metodologa de la programacin y en los aspectos crticos del lenguaje C en lugar de hacer hincapi en los elementos ms bsicos de ambos aspectos. El conocimiento profundo de un lenguaje de programacin parte no slo del entendimiento de su lxico, de su sintaxis y de su semntica, sino que adems requiere la comprensin de los objetivos que motivaron su desarrollo. As pues, en esta unidad se repasa la historia del lenguaje de programacin C desde el prisma de la programacin de los computadores. Los programas descritos en un lenguaje de programacin como C no pueden ser ejecutados directamente por ninguna mquina. Por tanto, es necesario disponer de herramientas (es decir, programas) que permitan obtener otros programas que estn descritos como una secuencia de rdenes que s que pueda ejecutar directamente algn computador.
de libre acceso disponible tanto en plataformas Microsoft como GNU/Linux. Dado que las primeras requieren de un sistema operativo que no se basa en el software libre, la explicacin se centrar en las segundas. El resto de la unidad se ocupar del lenguaje de programacin C en lo que queda afectado por el paradigma de la programacin imperativa y su modelo de ejecucin. El modelo de ejecucin trata de la
11
Nota
Una plataforma es, en este contexto, el conjunto formado por un tipo de ordenador y un sistema operativo.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
forma como se llevan a cabo las instrucciones indicadas en un programa. En el paradigma de la programacin imperativa, las instrucciones son rdenes que se llevan a cabo de forma inmediata para conseguir algn cambio en el estado del procesador y, en particular, para el almacenamiento de los resultados de los clculos realizados en la ejecucin de las instrucciones. Por este motivo, en los ltimos apartados se incide en todo aquello que afecta a la evaluacin de expresiones (es decir, al clculo de los resultados de frmulas), a la seleccin de la instruccin que hay que llevar a cabo y a la obtencin de datos o a la produccin de resultados. En esta unidad, pues, se pretende que el lector alcance los objetivos siguientes: 1. Repasar los conceptos bsicos de la programacin y el modelo de ejecucin de los programas. 2. Entender el paradigma fundamental de la programacin imperativa. 3. Adquirir las nociones de C necesarias para el seguimiento del curso. 4. Saber utilizar un entorno de desarrollo de software libre para la programacin en C (GNU/Linux y tiles GNU/C).
ANOTACIONES
12
Nota
Como curiosidad cabe decir que bastaron unas trece mil lneas de cdigo C (y un millar escaso en ensamblador) para programar el sistema operativo Unix del computador PDP-11.
El lenguaje ensamblador es aquel que tiene una correspondencia directa con el lenguaje mquina que entiende el procesador. En otras
FUOC XP04/90793/00018
palabras, cada instruccin del lenguaje mquina se corresponde con una instruccin del lenguaje ensamblador. Por el contrario, las instrucciones del lenguaje C pueden equivaler a pequeos programas en el lenguaje mquina, pero de frecuente uso en los algoritmos que se programan en los computadores. Es decir, se trata de un lenguaje en el que se emplean instrucciones que slo puede procesar una mquina abstracta, que no existe en la realidad (el procesador real slo entiende el lenguaje mquina). Por ello, se habla del C como lenguaje de alto nivel de abstraccin y del ensamblador como lenguaje de bajo nivel. Esta mquina abstracta se construye parcialmente mediante un conjunto de programas que se ocupan de gestionar la mquina real: el sistema operativo. La otra parte se construye empleando un programa de traduccin del lenguaje de alto nivel a lenguaje mquina. Estos programas se llaman compiladores si generan un cdigo que puede ejecutarse directamente por el computador, o intrpretes si es necesaria su ejecucin para llevar a cabo el programa descrito en un lenguaje de alto nivel. Para el caso que nos ocupa, resulta de mucho inters que el cdigo de los programas que constituyen el sistema operativo sea lo ms independiente de la mquina posible. nicamente de esta manera ser viable adaptar un sistema operativo a cualquier computador de forma rpida y fiable. Por otra parte, es necesario que el compilador del lenguaje de alto nivel sea extremadamente eficiente. Para ello, y dado los escasos recursos computacionales (fundamentalmente, capacidad de memoria y velocidad de ejecucin) de los ordenadores de aquellos tiempos, se requiere que el lenguaje sea simple y permita una traduccin muy ajustada a las caractersticas de los procesadores. ste fue el motivo de que en el origen de C estuviera el lenguaje denominado B, desarrollado por Ken Thompson para programar Unix para el PDP-7 en 1970. Evidentemente, esta versin del sistema operativo tambin incluy una parte programada en ensamblador, pues haba operaciones que no podan realizarse sino con la mquina real.
13
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Queda claro que se puede ver el lenguaje C como una versin posterior (y mejorada con inclusin de tipos de datos) del B. Ms an, el lenguaje B estaba basado en el BCPL. ste fue un lenguaje desarrollado por Martn Richards en 1967, cuyo tipo de datos bsico era la palabra memoria; es decir, la unidad de informacin en la que se divide la memoria de los computadores. De hecho, era un lenguaje ensamblador mejorado que requera de un compilador muy simple. A cambio, el programador tena ms control de la mquina real. A pesar de su nacimiento como lenguaje de programacin de sistemas operativos, y, por tanto, con la capacidad de expresar operaciones de bajo nivel, C es un lenguaje de programacin de propsito general. Esto es, con l es posible programar algoritmos de aplicaciones (conjuntos de programas) de muy distintas caractersticas como, por ejemplo, software de contabilidad de empresas, manejo de bases de datos de reservas de aviones, gestin de flotas de transporte de mercancas, clculos cientficos, etctera.
Bibliografa
La definicin de las reglas sintcticas y semnticas del C aparece en la obra siguiente: B.W. Kernighan; D.M. Ritchie (1978). The C Programming Language. Englewood Cliffs, NJ: Prentice-Hall. Concretamente en el apndice C Reference Manual.
La relativa simplicidad del lenguaje C por su escaso nmero de instrucciones permite que sus compiladores generen un cdigo en lenguaje mquina muy eficiente y, adems, lo convierten en un lenguaje fcilmente transportable de una mquina a otra.
ANOTACIONES
14
Por otra parte, el repertorio de instrucciones de C posibilita realizar una programacin estructurada de alto nivel de abstraccin. Cosa que redunda en la programacin sistemtica, legible y de fcil mantenimiento.
FUOC XP04/90793/00018
Esta simplicidad, no obstante, ha supuesto la necesidad de disponer para C de un conjunto de funciones muy completas que le confieren una gran potencia para desarrollar aplicaciones de todo tipo. Muchas de estas funciones son estndar y se encuentran disponibles en todos los compiladores de C.
Nota
Una funcin es una secuencia de instrucciones que se ejecuta de forma unitaria para llevar a cabo alguna tarea concreta. Por lo tanto, a mayor nmero de funciones ya programadas, menos cdigo habr que programar.
Las funciones estndar de C se recogen en una biblioteca: la biblioteca estndar de funciones. As pues, cualquier programa puede emplear todas las funciones que requiera de la misma, puesto que todos los compiladores disponen de ella. Finalmente, dada su dependencia de las funciones estndar, C promueve una programacin modular en la que los propios programadores tambin pueden preparar funciones especficas en sus programas. Todas estas caractersticas permitieron una gran difusin de C que se normaliz por la ANSI (American National Standards Association) en 1989 a partir de los trabajos de un comit creado en 1983 para proporcionar una definicin no ambigua e independiente de mquina del lenguaje. La segunda edicin de Kernighan y Ritchie, que se public en 1988, refleja esta versin que se conoce como ANSI-C.
Nota
La versin original de C se conocera, a partir de entonces, como K&R C, es decir, como el C de Kernighan y Ritchie. De esta manera, se distinguen la versin original y la estandarizada por la ANSI.
El resto de la unidad se dedicar a explicar un entorno de desarrollo de programas en C y a repasar la sintaxis y la semntica de este lenguaje.
15
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El kernel de un sistema operativo es el software que constituye su ncleo fundamental y, de ah, su denominacin (kernel significa, entre otras cosas, la parte esencial'). Este ncleo se ocupa, bsicamente, de gestionar los recursos de la mquina para los programas que se ejecutan en ella.
ANOTACIONES
16
El kernel Linux, compatible con el de Unix, fue desarrollado por Linus Torvalds en 1991 e incorporado como kernel del sistema operativo de GNU un ao ms tarde. En todo caso, cabe hacer notar que todas las llamadas distribuciones de Linux son, de hecho, versiones del sistema operativo GNU/Linux que, por tanto, cuentan con software de GNU (editor de textos
FUOC XP04/90793/00018
Emacs, compilador de C, etc.) y tambin con otro software libre como puede ser el procesador de textos TeX. Para el desarrollo de programas libres se emplear, pues, un ordenador con el sistema operativo Linux y el compilador de C de GNU (gcc). Aunque se puede emplear cualquier editor de textos ASCII, parece lgico emplear Emacs. Un editor de textos ASCII es aqul que slo emplea los caracteres definidos en la tabla ASCII para almacenar los textos. (En el Emacs, la representacin de los textos se puede ajustar para distinguir fcilmente los comentarios del cdigo del programa en C, por ejemplo.) Aunque las explicaciones sobre el entorno de desarrollo de software que se realizarn a lo largo de esta unidad y las siguientes hacen referencia al software de GNU, es conveniente remarcar que tambin es posible emplear herramientas de software libre para Windows, por ejemplo. En los apartados siguientes se ver cmo emplear el compilador gcc y se revisar muy sucintamente la organizacin del cdigo de los programas en C.
Un programa es un texto escrito en un lenguaje simple que permite expresar una serie de acciones sobre objetos (instrucciones) de forma no ambigua.
mos de conocer las reglas del lenguaje para que aquello que se exprese sea correcto tanto lxica como sintcticamente. Las normas del lenguaje de programacin C se vern progresivamente a lo largo de las unidades correspondientes (las tres primeras). Adems, deberemos procurar que eso tenga sentido y exprese exactamente aquello que se desea que haga el programa. Por si no
17
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
El smbolo del dlar ($) se emplea para indicar que el intrprete de comandos del sistema operativo del ordenador puede aceptar una nueva orden.
fuera poco, habremos de cuidar el aspecto del texto de manera que sea posible captar su significado de forma rpida y clara. Aunque algunas veces se indicar alguna norma de estilo, normalmente ste se describir implcitamente en los ejemplos. Para escribir un programa en C, es suficiente con ejecutar el emacs: $ emacs hola.c & Ahora ya podemos escribir el siguiente programa en C: #include <stdio.h> main( ) { printf( ""Hola a todos! \n" ); } /* main */ Es muy importante tener presente que, en C (y tambin en C++ y en Java), se distinguen maysculas de minsculas. Por lo tanto, el texto del programa tiene que ser exactamente como se ha mostrado a excepcin del texto entre comillas y el que est entre los smbolos /* y */. El editor de textos emacs dispone de mens desplegables en la parte superior para la mayora de operaciones y, por otra parte, acepta comandos introducidos mediante el teclado. A tal efecto, es necesario teclear tambin la tecla de control (CTRL o C-) o la de carcter alternativo junto con la del carcter que indica la orden.
Nota
El nombre del fichero que contiene el programa en C tiene extensin .c para que sea fcil identificarlo como tal.
Ejemplo
No es lo mismo printf que PrintF.
A modo de breve resumen, se describen algunos de los comandos ms empleados en la siguiente tabla:
Tabla 1. Comando Files Find file Files Save buffer 18 Secuencia C-x, C-f C-x, C-s Explicacin Abre un fichero. El fichero se copia en un buffer o rea temporal para su edicin. Guarda el contenido del buffer en el fichero asociado.
ANOTACIONES
FUOC XP04/90793/00018
Comando Files Save buffer as Files Insert file Files Exit (cursor movement) (cursor movement) (line killing) Edit Paste
Secuencia C-x, C-w C-x, C-i C-x, C-c C-a C-e C-k C-y
Explicacin Escribe el contenido del buffer en el fichero que se le indique. Inserta el contenido del fichero indicado en la posicin del texto en el que se encuentre el cursor.
Finaliza la ejecucin de
emacs.
Sita el cursor al principio de la lnea. Sita el cursor al final de la lnea. Elimina la lnea (primero el contenido y luego el salto de lnea). Pega el ltimo texto eliminado o copiado. Para copiar un trozo de texto, puede eliminarse primero y recuperarlo en la misma posicin y, finalmente, en la posicin de destino. Elimina el texto desde la ltima marca hasta el cursor. Deshace el ltimo comando.
Nota
Para una mejor comprensin de los comandos se recomienda leer el manual de emacs o, en su caso, del editor que se haya elegido para escribir los programas.
Edit Copy
C-y, , C-y
C-w C-u
Una vez editado y guardado el programa en C, hay que compilarlo para obtener un fichero binario (con ceros y unos) que contenga una versin del programa traducido a lenguaje mquina. Para ello, hay que emplear el compilador gcc: $ gcc c hola.c
Nota
En un tiempo gcc signific compilador de C de GNU, pero, dado que el compilador entiende tambin otros lenguajes, ha pasado a ser la coleccin de compiladores de GNU. Por este motivo, es necesario indicar en qu lenguaje se han escrito los programas mediante la extensin del nombre de los ficheros correspondientes. En este caso, con hola.c, emplear el compilador de C. Con ello, se obtendr un fichero ( ola.o), denominado fichero h objeto. Este archivo contiene ya el programa en lenguaje mquina derivado del programa con el cdigo C, llamado tambin cdigo fuente. Pero desgraciadamente an no es posible ejecutar este programa, ya que requiere de una funcin (printf) que se encuentra en la biblioteca de funciones estndar de C.
19
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Para obtener el cdigo ejecutable del programa, ser necesario enlazar (en ingls: link): $ gcc hola.o o hola Como la biblioteca de funciones estndar est siempre en un lugar conocido por el compilador, no es necesario indicarlo en la lnea de comandos. Eso s, ser necesario indicar en qu fichero se quiere el resultado (el fichero ejecutable) con la opcin o seguida del nombre deseado. En caso contrario, el resultado se obtiene en un fichero llamado a.out. Habitualmente, el proceso de compilacin y enlazado se hacen directamente mediante: $ gcc hola.c o hola Si el fichero fuente contuviese errores sintcticos, el compilador mostrar los mensajes de error correspondientes y deberemos corregirlos antes de repetir el procedimiento de compilacin. En caso de que todo haya ido bien, dispondremos de un programa ejecutable en un fichero llamado hola que nos saludar vehementemente cada vez que lo ejecutemos: $ ./hola Hola a todos! $
Nota
Se debe indicar el camino de acceso al fichero ejecutable para que el intrprete de rdenes pueda localizarlo. Por motivos de seguridad, el directorio de trabajo no se incluye por omisin en el conjunto de caminos de bsqueda de ejecutables del intrprete de comandos.
ANOTACIONES
20
Este procedimiento se repetir para cada programa que realicemos en C en un entorno GNU.
FUOC XP04/90793/00018
} /* main */ En esta organizacin, las primeras lneas del fichero son comentarios que identifican su contenido, autor y versin. Esto es importante, pues hay que tener presente que el cdigo fuente que creemos debe ser fcilmente utilizable y modificable por otras personas... y por nosotros mismos! Dada la simplicidad del C, muchas de las operaciones que se realizan en un programa son, en realidad, llamadas a funciones estntienen y qu valores devuelven, es necesario incluir en el cdigo de nuestro programa las declaraciones de las funciones que se emplearn. Para ello, se utiliza el comando #include del llamado preprocesador de C, que se ocupa de montar un fichero nico de entrada para el compilador de C. Los ficheros que contienen declaraciones de funciones externas a un determinado archivo fuente son llamados ficheros de cabeceras
21
Nota
Los comentarios son textos libres que se escriben entre los dgrafos /* y */.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Las cabeceras, en C, son la parte en la que se declara el nombre de una funcin, los parmetros que recibe y el tipo de dato que devuelve.
Tanto estos ficheros como el del cdigo fuente de un programa pueden contener definiciones de constantes simblicas. A continuacin presentamos una muestra de estas definiciones:
#define VACIO #define NUMERO_OCTAL #define MAX_USUARIOS #define CODIGO_HEXA #define PI #define PRECISION #define CADENA
/* El carcter ASCII NUL /* Un valor octal /* Un valor hexadecimal /* Un nmero real /* Otro nmero real
*/ */ */ */ */
"cadena de caracteres"
Estas constantes simblicas son reemplazadas por su valor en el fichero final que se suministra al compilador de C. Es importante recalcar que su uso debe redundar en una mayor legibilidad del cdigo y, por otra parte, en una facilidad de cambio del programa, cuando fuese necesario. Cabe tener presente que las constantes numricas enteras descritas en base 8, u octal, deben estar prefijadas por un 0 y las expresadas en base 16, o hexadecimal, por 0x
ANOTACIONES
22
Ejemplo
020 no es igual a 20, puesto que este ltimo coincide con el valor veinte en decimal y el primero es un nmero expresado en base 8, cuya representacin binaria es 010000, que equivale a 16, en base 10.
Finalmente, se incluir el programa en el cuerpo de la funcin principal. Esta funcin debe estar presente en todo programa, pues la
FUOC XP04/90793/00018
primera instruccin que contenga es la que se toma como punto inicial del programa y, por tanto, ser la primera en ser ejecutada.
3. Realizar el clculo u operacin indicada en la instruccin y, segn la operacin que se realice, grabar el resultado en la memoria. 4. Determinar cul es la siguiente instruccin que hay que ejecutar. 5. Volver al primer paso. La CPU hace referencia a las instrucciones y a los datos que pide a la memoria o a los resultados que quiere escribir mediante el nmero
23
ANOTACIONES
FUOC XP04/90793/00018
Software libre
de posicin que ocupan en la misma. Esta posicin que ocupan los datos y las instrucciones se conoce como direccin de memoria. En el nivel ms bajo, cada direccin distinta de memoria es un nico byte y los datos y las instrucciones se identifican por la direccin del primero de sus bytes. En este nivel, la CPU coincide con la CPU fsica de que dispone el computador. En el nivel de la mquina abstracta que ejecuta C, se mantiene el hecho de que las referencias a los datos y a las instrucciones sea la direccin de la memoria fsica del ordenador, pero las instrucciones que puede ejecutar su CPU de alto nivel son ms potentes que las que puede llevar a cabo la real. Independientemente del nivel de abstraccin en que se trabaje, la memoria es, de hecho, el entorno de la CPU. Cada instruccin realiza, en este modelo de ejecucin, un cambio en el entorno: puede modificar algn dato en memoria y siempre implica determinar cul es la direccin de la siguiente instruccin a ejecutar. Dicho de otra manera: la ejecucin de una instruccin supone un cambio en el estado del programa. ste se compone de la direccin de la instruccin que se est ejecutando y del valor de los datos en memoria. As pues, llevar a cabo una instruccin implica cambiar de estado el programa. En los prximos apartados se describirn los tipos de datos bsicos que puede emplear un programa en C y las instrucciones fundamentales para cambiar su estado: la asignacin y la seleccin condicional de la instruccin siguiente. Finalmente, se vern las funciones estndar para tomar datos del exterior (del teclado) y para mostrarlos al exterior (a travs de la pantalla).
ANOTACIONES
24
FUOC XP04/90793/00018
Nota
Las palabras de la especificacin entre parntesis son opcionales en las declaraciones de las variables correspondientes. Por otra parte, hay que tener presente que las especificaciones pueden variar levemente con otros compiladores.
25
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Hay que recordar especialmente los distintos rangos de valores que pueden tomar las variables de cada tipo para su correcto uso en los programas. De esta manera, es posible ajustar su tamao al que realmente sea til. El tipo carcter (char) es, de hecho, un entero que identifica una posicin de la tabla de caracteres ASCII. Para evitar tener que traducir los caracteres a nmeros, stos se pueden introducir entre comillas simples (por ejemplo: 'A'). Tambin es posible representar cdigos no visibles como el salto de lnea ('\n') o la tabulacin ('\t').
Ejemplo
En este estndar, la letra A mayscula se encuentra en la posicin nmero 65.
Nmeros reales
Este tipo de datos es ms complejo que el anterior, pues su representacin binaria se encuentra codificada en distintos campos. As pues, no se corresponde con el valor del nmero que se podra extraer de los bits que los forman. Los nmeros reales se representan mediante signo, mantisa y exponente. La mantisa expresa la parte fraccionaria del nmero y el exponente es el nmero al que se eleva la base correspondiente:
[+/-] mantisa x base exponente
En funcin del nmero de bits que se utilicen para representarlos, los valores de la mantisa y del exponente sern mayores o menores. Los distintos tipos de reales y sus rangos aproximados se muestran en la siguiente tabla (vlida en sistemas GNU de 32 bits):
ANOTACIONES
26
Tabla 3. Especificacin float double long double Nmero de bits 32 (4 bytes) 64 (8 bytes) 96 (12 bytes) Rango de valores
3,4 x 10 38 1,7 x 10 308 1,1 x 10 4.932
Como se puede deducir de la tabla anterior, es importante ajustar el tipo de datos real al rango de valores que podr adquirir una deter-
FUOC XP04/90793/00018
minada variable para no ocupar memoria innecesariamente. Tambin cabe prever lo contrario: el uso de un tipo de datos que no pueda alcanzar la representacin de los valores extremos del rango empleado provocar que stos no se representen adecuadamente y, como consecuencia, el programa correspondiente puede comportarse de forma errtica.
Declaraciones
La declaracin de una variable supone manifestar su existencia ante el compilador y que ste planifique una reserva de espacio en la memoria para contener sus datos. La declaracin se realiza anteponiendo al nombre de la variable la especificacin de su tipo (char, int, float, double), que puede, a su vez, ir precedida de uno o varios modificadores (signed, unsigned, short, long). El uso de algn modificador hace innecesaria la especificacin int salvo para long. Por ejemplo: unsigned short natural; /* La variable 'natural' se /* declara como un /* entero positivo. int char float i, j, k; opcion; percentil; /* Variables enteras con signo. /* Variable de tipo carcter. /* Variable de tipo real. */ */ */ */ */ */
Por comodidad, es posible dotar a una variable de un contenido inicial determinado. Para ello, basta con aadir a la declaracin un signo igual seguido del valor que tendr al iniciarse la ejecucin del programa. Por ejemplo: int char float unsigned importe = 0; opcion = 'N'; angulo = 0.0; contador = MAXIMO;
El nombre de las variables puede contener cualquier combinacin de caracteres alfabticos (es decir, los del alfabeto ingls, sin '', ni '', ni ningn tipo de carcter acentuado), numricos y tambin el smbolo del subrayado (_); pero no puede empezar por ningn dgito.
27
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Es conveniente que los nombres con los que bautizamos las variables identifiquen su contenido o su utilizacin en el programa.
La potencia (y la posible dificultad de lectura de los programas) de C se encuentra, precisamente en las expresiones. De hecho, cualquier expresin se convierte en una instruccin si se pone un punto y coma al final: todas las instrucciones de C acaban en punto y coma. Evidentemente, evaluar una expresin no tiene sentido si no se asigna el resultado de su evaluacin a alguna variable que pueda almacenarlo para operaciones posteriores. As pues, el primer operador que se debe aprender es el de asignacin:
Nota
Una expresin es cualquier combinacin sintcticamente vlida de operadores y operandos que pueden ser variables o constantes.
ANOTACIONES
28
Es importante no confundir el operador de asignacin (=) con el de comparacin de igualdad (==); pues en C, ambos son operadores que pueden emplearse entre datos del mismo tipo.
FUOC XP04/90793/00018
Operadores
Aparte de la asignacin, los operadores ms habituales de C y que aparecen en otros lenguajes de programacin se muestran en la tabla siguiente:
Tabla 4. Clase Operadores + Aritmtico * % > Relacional < == ! Lgico && || >= <= != / Significado suma y resta producto y divisin mdulo o resto de divisin entera (slo para enteros) mayor que y mayor e igual que menor que y menor e igual que igual a y diferente de NO ( proposicin lgica) Y (deben cumplirse todas las partes) y O lgicas
Los operadores aritmticos sirven tanto para los nmeros reales, como para los enteros. Por este motivo, se realizan implcitamente todas las operaciones con el tipo de datos de mayor rango.
A este comportamiento implcito de los operadores se le llama promocin de tipos de datos y se realiza cada vez que se opera con datos de tipos distintos. Por ejemplo, el resultado de una operacin con un entero y un real la letra e que separa mantisa de exponente) ser siempre un nmero real. En cambio, si la operacin se efecta slo con enteros, el resultado ser siempre el del tipo de datos entero de mayor rango. De esta manera: real = 3 / 2 ; tiene como resultado asignar a la variable real el valor 1.0, que es el resultado de la divisin entera entre 3 y 2 transformado en un real
29
ANOTACIONES
FUOC XP04/90793/00018
Software libre
cuando se hace la operacin de asignacin. Por eso se escribe 1.0 (con el punto decimal) en lugar de 1. Aun con esto, el operador de asignacin siempre convierte el resultado de la expresin fuente al tipo de datos de la variable receptora. Por ejemplo, la asignacin siguiente: entero = 3.0 / 2 + 0.5; asigna el valor 2 a entero. Es decir, se calcula la divisin real (el nmero 3.0 es real, ya que lleva un punto decimal) y se suma 0.5 al resultado. Esta suma acta como factor de redondeo. El resultado es de tipo real y ser truncado (su parte decimal ser eliminada) al ser asignado a la variable entero.
Coercin de tipo
Para aumentar la legibilidad del cdigo, evitar interpretaciones equivocadas e impedir el uso errneo de la promocin automtica de tipos, resulta conveniente indicar de forma explcita que se hace un cambio de tipo de datos. Para ello, se puede recurrir a la coercin de tipos; esto es: poner entre parntesis el tipo de datos al que se desea convertir un determinado valor (est en una variable o sea ste constante): ( especificacin_de_tipo ) operando As pues, siguiendo el ejemplo anterior, es posible convertir un nmero real al entero ms prximo mediante un redondeo del siguiente modo: entero = (int) (real + 0.5);
ANOTACIONES
30
En este caso, se indica que la suma se hace entre dos reales y el resultado, que ser de tipo real, se convierte explcitamente a entero mediante la coercin de tipos.
Operadores relacionales
Hay que tener presente que en C no hay ningn tipo de datos lgico que se corresponda con 'falso' y 'cierto'. As pues, cualquier dato
FUOC XP04/90793/00018
Esto puede no suceder con los datos de tipo real, pues hay que tener presente que incluso los nmeros infinitesimales cerca del 0 sern tomados como indicacin de resultado lgico 'cierto'.
Como consecuencia de ello, los operadores relacionales devuelven 0 para indicar que la relacin no se cumple y distinto de 0 en caso afirmativo. Los operadores && y || slo evalan las expresiones necesarias para determinar, respectivamente, si se cumplen todos los casos o slo algunos. As pues, && implica la evaluacin de la expresin que constituye su segundo argumento slo en caso de que el primero haya resultado positivo. Similarmente, || slo ejecutar la evaluacin de su segundo argumento si el primero ha resultado 'falso'. As pues: (20 > 10) || ( 10.0 / 0.0 < 1.0 ) dar como resultado 'cierto ' a pesar de que el segundo argumento no se puede evaluar (es una divisin por cero!). En el caso anterior, los parntesis eran innecesarios, pues los operadores relacionales tienen mayor prioridad que los lgicos. Aun as, es conveniente emplear parntesis en las expresiones para dotarlas de mayor claridad y despejar cualquier duda sobre el orden de evaluacin de los distintos operadores que puedan contener.
Otros operadores
Como se ha comentado, C fue un lenguaje inicialmente concebido para la programacin de sistemas operativos y, como consecuencia, con un alto grado de relacin con la mquina, que se manifiesta en la existencia de un conjunto de operadores orientados a facilitar una traduccin muy eficiente a instrucciones del lenguaje mquina.
31
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En particular, dispone de los operadores de autoincremento (++) y autodecremento (--), que se aplican directamente sobre variables cuyo contenido sea compatible con enteros. Por ejemplo:
La diferencia entre la forma prefija (es decir, precediendo a la variable) y la forma postfija de los operadores estriba en el momento en que se hace el incremento o decremento: en forma prefija, se hace antes de emplear el contenido de la variable.
Ejemplo
Por otra parte, tambin es posible hacer operaciones entre bits. Estas operaciones se hacen entre cada uno de los bits que forma parte de un dato compatible con entero y otro. As pues, es posible hacer una Y, una O, una O-EX (slo uno de los dos puede ser cierto) y una negacin lgica bit a bit entre los que forman parte de un dato y los de otro. (Un bit a cero representa 'falso' y uno a uno, 'cierto'.) Los smbolos empleados para estos operadores a nivel de bit son: Para la Y lgica: & (ampersand) Para la O lgica: | (barra vertical) Para la O exclusiva lgica: ^ (acento circunflejo) Para la negacin o complemento lgico: ~ (tilde) A pesar de que son operaciones vlidas entre datos compatibles con enteros, igual que los operadores lgicos, es muy importante tener presente que no dan el mismo resultado. Por ejemplo: ( 1 && 2 )
ANOTACIONES
32
FUOC XP04/90793/00018
es cierto, pero ( 1 & 2 ) es falso, puesto que da 0. Para comprobarlo, basta con ver lo que pasa a nivel de bit: 1 == 0000 0000 0000 0001 2 == 0000 0000 0000 0010 1 & 2 == 0000 0000 0000 0000 La lista de operadores en C no termina aqu. Hay otros que se vern ms tarde y algunos que quedarn en el tintero.
En el caso del if es posible ejecutar ms de una instruccin, tanto si la condicin se cumple como si no, agrupando las instrucciones en
33
ANOTACIONES
La evaluacin de la expresin debe resultar en un dato compatible con entero. Este resultado se compara con los valores indicados en cada case y, de ser igual a alguno de ellos, se ejecutan todas las instrucciones a partir de la primera indicada en ese caso y hasta el final del bloque del switch. Es posible romper esta secuencia introduciendo una instruccin break; que finaliza la ejecucin de la secuencia de instrucciones. Opcionalmente, es posible indicar un caso por omisin (default) que permite especificar qu instrucciones se ejecutaran si el resultado de la expresin no ha producido ningn dato coincidente con los casos previstos.
FUOC XP04/90793/00018
Software libre
un bloque. Los bloques de instrucciones son instrucciones agrupadas entre llaves: { instruccin_1; instruccin_2; ... instruccin_N; } En este sentido, es recomendable que todas las instrucciones condicionales agrupen las instrucciones que se deben ejecutar en cada caso: if( condicin ) { instrucciones } else { instrucciones } /* if */ De esta manera se evitan casos confusos como el siguiente: if( a > b ) mayor = a ; menor = b ; diferencia = mayor menor; En este caso se asigna b a menor, independientemente de la condicin, pues la nica instruccin del if es la de asignacin a mayor. Como se puede apreciar, las instrucciones que pertenecen a un mismo bloque empiezan siempre en la misma columna. Para facilitar la identificacin de los bloques, stos deben presentar un sangrado a la derecha respecto de la columna inicial de la instruccin que los gobierna (en este caso: if, switch y case).
ANOTACIONES
34
Por convenio, cada bloque de instrucciones debe presentar un sangrado a la derecha respecto de la instruccin que determina su ejecucin.
Dado que resulta frecuente tener que asignar un valor u otro a una variable es funcin de una condicin, es posible, para estos casos,
FUOC XP04/90793/00018
emplear un operador de asignacin condicional en lugar de una instruccin if: condicin ? expresin_si_cierto : expresin_si_falso As pues, en lugar de: if( condicion ) else Se puede escribir: var = condicin ? expresin_si_cierto : expresin_si_falso; Ms aun, se puede emplear este operador dentro de cualquier expresin. Por ejemplo: var = expresin_si_cierto; var = expresin_si falso;
Es preferible limitar el uso del operador condicional a los casos en que se facilite la lectura.
Ya hemos comentado que una funcin no es ms que una serie de instrucciones que se ejecuta como una unidad para llevar a cabo una tarea concreta. Como idea, puede tomarse la de las funciones matemticas, que realizan alguna operacin con los argumentos dados y devuelven el resultado calculado.
35
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El lenguaje C cuenta con un amplio conjunto de funciones estndar entre las que se encuentran las de entrada y de salida de datos, que veremos en esta seccin. El compilador de C tiene que saber (nombre y tipo de datos de los argumentos y del valor devuelto) qu funciones utilizar nuestro programa para poder generar el cdigo ejecutable de forma correcta. Por tanto, es necesario incluir los ficheros de cabecera que contengan sus declaraciones en el cdigo de nuestro programa. En este caso: #include <stdio.h>
ANOTACIONES
36
La tabla siguiente muestra la correspondencia entre los smbolos de la cadena del formato de impresin y los caracteres ASCII.n se utiliza para indicar un dgito de un nmero:
Tabla 6. Indicacin de carcter \n \f Carcter ASCII new line (salto de lnea) form feed (salto de pgina)
FUOC XP04/90793/00018
Carcter ASCII backspace (retroceso) tabulator (tabulador) ASCII nmero nnn ASCII nmero nnn (en octal) ASCII nmero nn (en hexadecimal) backslash (barra invertida)
Los especificadores de campo tienen el formato siguiente: %[-][+][anchura[.precisin]]tipo_de_dato Los corchetes indican que el elemento es opcional. El signo menos se emplea para indicar alineacin derecha, cosa habitual en la impresin de nmeros. Para stos, adems, si se especifica el signo ms se conseguir que se muestren precedidos por su signo, sea positivo o negativo. La anchura se utilizar para indicar el nmero mnimo de caracteres que se utilizarn para mostrar el campo correspondiente y, en el caso particular de los nmeros reales, se puede especificar el nmero de dgitos que se desea mostrar en la parte decimal mediante la precisin. El tipo de dato que se desea mostrar se debe incluir obligatoriamente y puede ser uno de los siguientes:
Tabla 7. Enteros %d %i %u %o %x En decimal En decimal En decimal sin signo En octal sin signo En hexadecimal %g %f %e Reales En punto flotante Con formato exponencial: [+/-]0.000e[+/-]000 con e minscula o mayscula (%E) En formato e, f, o d. %c %s %% Otros Carcter Cadena de caracteres El signo de % (listado no completo)
Ejemplo
printf( "El importe de la factura nm.: %5d", num_fact ); printf( "de Sr./-a. %s sube a %.2f_\n", cliente, importe );
Para los tipos numricos es posible prefijar el indicador de tipo con una ele a la manera que se hace en la declaracin de tipos con long. En este caso, el tipo double debe tratarse como un "long float" y, por tanto, como "%lf".
37
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Ejemplo
putchar( \n' );
Ejemplo
puts( "Hola a todos!\n" );
puts( "cadena de caracteres" ) Muestra una cadena de caracteres por la salida estndar.
Lee del buffer del teclado para trasladar su contenido a las variables que tiene como argumentos. La lectura se efecta segn el formato indicado, de forma similar a la especificacin de campos empleada para printf.
Para poder depositar los datos ledos en las variables indicadas, esta funcin requiere que los argumentos de la lista de variables sean las direcciones de memoria en las que se encuentran. Por este motivo, es necesario emplear el operador direccin de (&). De esta manera, scanf deja directamente en las zonas de memoria correspondiente la informacin que haya ledo y, naturalmente, la variable afectada ver modificado su contenido con el nuevo dato.
ANOTACIONES
38
Es importante tener presente que si se especifican menos argumentos que especificadores de campo en el formato, los resultados pueden ser imprevisibles, pues la funcin cambiar el contenido de alguna zona de memoria totalmente aleatoria.
FUOC XP04/90793/00018
Ejemplo
scanf( "%d", &num_articulos ); En el ejemplo anterior, scanf lee los caracteres que se tecleen para convertirlos (presumiblemente) a un entero. El valor que se obtenga se colocar en la direccin indicada en su argumento, es decir, en el de la variable num_articulos. Esta lectura de caracteres del buffer de memoria del teclado se detiene cuando el carcter ledo no se corresponde con un posible carcter del formato especificado. Este carcter se devuelve al buffer para una posible lectura posterior. Para la entrada que se muestra en la figura siguiente, la funcin detiene la lectura despus del espacio en blanco que separa los nmeros tecleados. ste y el resto de caracteres se quedan en el buffer para posteriores lecturas.
Figura 1.
La funcin scanf devuelve el nmero de datos correctamente ledos. Es decir, todos aquellos para los que se ha encontrado algn texto compatible con una representacin de su tipo.
Devuelve un carcter ledo por la entrada estndar (habitualmente, el buffer del teclado). En caso de que no pueda leer ningn carcter, devuelve el carcter EOF. Esta constante est definida en stdio.h y, por tanto, puede emplearse para determinar si la lectura ha tenido xito o, por lo contrario, se ha producido algn error o se ha llegado al final de los datos de entrada.
39
Ejemplo
opcion = getchar();
ANOTACIONES
getchar( )
FUOC XP04/90793/00018
Software libre
gets( cadena_caracteres )
Ejemplo
gets( nombre_usuario );
Lee de la entrada estndar toda una serie de caracteres hasta encontrar un final de lnea (carcter '\n'). Este ltimo carcter es ledo pero no se almacena en la cadena de caracteres que tiene por argumento. De no leer nada devuelve NULL, que es una constante definida en stdio.h y cuyo valor es 0.
1.5. Resumen
En esta unidad se ha visto el procedimiento de ejecucin de los programas en un computador. La unidad que se ocupa de procesar la informacin (unidad central de procesamiento o CPU) lee una instruccin de la memoria y procede a su ejecucin. Esta operacin implicar un cambio del estado del entorno del programa, es decir, del contenido de alguna de sus variables y de la direccin de la instruccin siguiente. Este modelo de ejecucin de las instrucciones, que siguen la mayora de procesadores reales, se replica en el paradigma de la programacin imperativa para los lenguajes de alto nivel. En particular, se ha visto cmo esto es cierto en el lenguaje de programacin C. Por este motivo, se han repasado las instrucciones de este lenguaje que permiten modificar el entorno mediante cambios en los datos y cambios en el orden secuencial en que se encuentran las instrucciones en la memoria. Los cambios que afectan a los datos de los programas son, de hecho, asignaciones a las variables que los contienen. Se ha visto que las variables son espacios en la memoria del computador a los que podemos hacer referencia mediante el nombre con que se declaran y a los que, internamente, se hace referencia mediante la direccin de la primera palabra de la memoria que ocupan. Se ha hecho hincapi en la forma de evaluacin de las expresiones teniendo en cuenta la prioridad entre los operadores y cmo se pue40
ANOTACIONES
FUOC XP04/90793/00018
de organizar mejor mediante el uso de parntesis. En este sentido, se ha comentado la conveniencia de emplear coerciones de tipo explcitas para evitar usos equvocos de la promocin automtica de los tipos de datos. Esta promocin se debe realizar para que los operadores puedan trabajar siempre con operandos de un mismo tipo, que siempre coincide con el de mayor rango de representacin. En cuanto al control del flujo de ejecucin que, normalmente, sigue el orden en que se encuentran las instrucciones en memoria, se han mencionado las instrucciones bsicas de seleccin de secuencias de instrucciones: la instruccin if y la instruccin switch. Aprovechando estas explicaciones, se ha introducido la forma especial de considerar los datos lgicos ( falso' y 'cierto') en C. As ' pues, cualquier dato puede ser, en un momento dado, empleado como valor lgico. En relacin a este tema, se ha comentado la forma especial de evaluacin de los operadores Y y O lgicos, que no evalan las expresiones derechas en caso de que puedan determinar el valor lgico resultante con el primer argumento (el que viene dado por la expresin que les precede). En el ltimo apartado, se han repasado las funciones estndar bsicas de entrada y de salida de datos, de manera que sea posible construir programas para probar no slo todos los conceptos y elementos de C explicados, sino tambin el entorno de desarrollo de GNU/C. As pues, se puede comprobar en la prctica en qu consiste la compilacin y el enlace de los programas. La tarea del compilador de C es la de traducir el programa en C a un programa en lenguaje mquina. El enlazador se ocupa de aadir al cdigo de esta versin el cdigo de las funciones de la biblioteca que se utilicen en el progra-
ANOTACIONES
FUOC XP04/90793/00018
Software libre
{ int a, b, suma; printf( "Teclea un nmero entero: " ); scanf( "%d", &a ); printf( "Teclea otro nmero entero: " ); scanf( "%d", &b ); suma = a + b; printf( "%d + %d = %d\n", a, b, suma ); } /* main */ 2) Haced un programa que, dado un importe en euros y un determinado porcentaje de IVA, calcule el total. 3) Haced un programa que calcule cunto costara 1Kg o 1 litro de un producto sabiendo el precio de un envase y la cantidad de producto que contiene. 4) Modificad el programa anterior para que calcule el precio del producto para la cantidad deseada, que tambin deber darse como entrada. 5) Haced un programa que calcule el cambio que hay que devolver conociendo el importe total a cobrar y la cantidad recibida como pago. El programa debe advertir si el importe pagado es insuficiente. 6) Haced un programa que, dado el nmero de litros aproximado que hay en el depsito de un coche, su consumo medio cada 100 km y una distancia en kilmetros, indique si es posible recorrerla. En caso negativo, debe indicar cuntos litros habra que aadir al depsito.
1.6.1. Solucionario
ANOTACIONES
42
1) Basta con seguir los pasos indicados. Como ejemplo, si el fichero se llamase suma.c, esto es lo que debera hacerse despus de haberlo creado: $ gcc suma.c o suma $ suma Teclea un nmero entero: 154 Teclea otro nmero entero: 703 154 + 703 = 857 $
FUOC XP04/90793/00018
2) #include <stdio.h> main() { float importe, IVA, total; printf( "Importe = " ); scanf( "%f", &importe ); printf( "%% IVA = " ); scanf( "%f", &IVA ); total = importe * ( 1.0 + IVA / 100.0 ); printf( "Total = %.2f\n", total ); } /* main */ 3) #include <stdio.h> main() { float precio, precio_unit; int cantidad; printf( "Precio = " ); scanf( "%f", &precio ); printf( "Cantidad (gramos o mililitros) = " ); scanf( "%d", &cantidad ); precio_unit = precio * 1000.0 / (float) cantidad; printf( "Precio por Kg/Litro = %.2f\n", precio_unit ); } /* main */ 4) #include <stdio.h> main() { int cantidad, canti_compra; printf( "Precio = " ); scanf( "%f", &precio ); printf( "Cantidad (gramos o mililitros) = " ); scanf( "%d", &cantidad ); printf( "Cantidad a adquirir = " ); scanf( "%d", &canti_compra ); precio_unit = precio / (float) cantidad;
43
ANOTACIONES
FUOC XP04/90793/00018
Software libre
precio_compra = precio_unit * canti_compra; printf( "Precio de compra = %.2f\n", precio_compra ); } /* main */ 5) #include <stdio.h> main() { float importe, pago; printf( "Importe = " ); scanf( "%f", &importe ); printf( "Pago = " ); scanf( "%f", &pago ); if( pago < importe ) { printf( "Importe de pago insuficiente.\n" ); } else { printf( "Cambio = %.2f euros.\n", pago - importe ); } /* if */ } /* main */ 6) #include <stdio.h> #define RESERVA 10 main() { int litros, distancia, consumo; float consumo_medio; printf( "Litros en el depsito = " ); scanf( "%d", &litros ); printf( "Consumo medio cada 100Km = " ); scanf( "%f", &consumo_medio ); printf( "Distancia a recorrer = " ); scanf( "%d", &distancia ); consumo = consumo_medio * (float) distancia / 100.0; if( litros < consumo ) { printf( "Faltan %d Ltrs.\n", consumolitros+RESERVA ); } else { printf( "Se puede hacer el recorrido.\n" ); } /* if */ } /* main */
ANOTACIONES
44
FUOC XP04/90793/00018
2. La programacin estructurada
2.1. Introduccin
La programacin eficaz es aquella que obtiene un cdigo legible y fcilmente actualizable en un tiempo de desarrollo razonable y cuya ejecucin se realiza de forma eficiente. Afortunadamente, los compiladores e intrpretes de cdigo de programas escritos en lenguajes de alto nivel realizan ciertas optimizaciones para reducir el coste de su ejecucin. Sirva como ejemplo el hecho de que muchos compiladores tienen la capacidad de utilizar el mismo espacio de memoria para diversas variables, siempre que stas no se utilicen simultneamente, claro est. Adems de sta y otras optimizaciones en el uso de la memoria, tambin pueden incluir mejoras en el cdigo ejecutable tendentes a disminuir el tiempo final de ejecucin. Pueden, por ejemplo, aprovechar los factores comunes de las expresiones para evitar la repeticin de clculos. Con todo esto, los programadores pueden centrar su tarea en la preparacin de programas legibles y de fcil mantenimiento. Por ejemplo, no hay ningn problema en dar nombres significativos a las variables, pues la longitud de los nombres no tiene consecuencias en un cdigo compilado. En este mismo sentido, no resulta lgico emplear trucos de programacin orientados a obtener un cdigo ejecutable ms eficiente si ello supone disminuir la legibilidad del cdigo mejora significativa del coste de ejecucin y, en cambio, implican dificultad de mantenimiento del programa y dependencia de un determinado entorno de desarrollo y de una determinada mquina. Por este motivo, en esta unidad se explica cmo organizar el cdigo fuente de un programa. La correcta organizacin de los programas supone un incremento notable de su legibilidad y, como consecuencia, una disminucin de los errores de programacin y facilidad de
45
ANOTACIONES
FUOC XP04/90793/00018
Software libre
mantenimiento y actualizacin posterior. Ms an, resultar ms fcil aprovechar partes de sus cdigos en otros programas. En el primer apartado se trata de la programacin estructurada. Este paradigma de la programacin est basado en la programacin imperativa, a la que impone restricciones respecto de los saltos que pueden efectuarse durante la ejecucin de un programa. Con estas restricciones se consigue aumentar la legibilidad del cdigo fuente, permitiendo a sus lectores determinar con exactitud el flujo de ejecucin de las instrucciones. Es frecuente, en programacin, encontrarse con bloques de instrucciones que deben ejecutarse repetitivamente. Por tanto, es necesario ver cmo disponer el cdigo correspondiente de forma estructurada. En general, estos casos derivan de la necesidad de procesar una serie de datos. As pues, en el primer apartado, no slo se ver la programacin estructurada, sino tambin los esquemas algortmicos para realizar programas que traten con series de datos y, cmo no, las correspondientes instrucciones en C. Por mucho que la programacin estructurada est encaminada a reducir errores en la programacin, stos no se pueden eliminar. As pues, se dedica un apartado a la depuracin de errores de programacin y a las herramientas que nos pueden ayudar a ello: los depuradores. El segundo apartado se dedica a la organizacin lgica de los datos de los programas. Hay que tener presente que la informacin que procesan los computadores y su resultado est constituido habitualmente por una coleccin variopinta de datos. Estos datos, a su vez, pueden estar constituidos por otros ms simples. En los lenguajes de programacin se da soporte a unos tipos bsicos de datos, es decir, se incluyen mecanismos (declaraciones, operaciones e instrucciones) para emplearlos en los cdigos fuente que se escriben con ellos. Por tanto, la representacin de la informacin que se maneja en un programa debe hacerse en trminos de variables que contengan da46
Nota
Un salto es un cambio en el orden de ejecucin de las instrucciones por el que la siguiente instruccin no es la que encuentra a continuacin de la que se est ejecutando.
ANOTACIONES
FUOC XP04/90793/00018
tos de los tipos fundamentales a los que el lenguaje de programacin correspondiente d soporte. No obstante, resulta conveniente agrupar conjuntos de datos que estn muy relacionados entre s en la informacin que representan. Por ejemplo: manejar como una nica entidad el nmero de das de cada uno de los meses del ao; el da, el mes y el ao de una fecha; o la lista de los das festivos del ao. En el segundo apartado, pues, se tratar de aquellos aspectos de la programacin que permiten organizar los datos en estructuras mayores. En particular, se vern las clases de estructuras que existen y cmo utilizar variables que contengan estos datos estructurados. Tambin se ver cmo la definicin de tipos de datos a partir de otros tipos, estructurados o no, beneficia a la programacin. Dado que estos nuevos tipos no son reconocidos en el lenguaje de programacin, son llamados tipos de datos abstractos. El ltimo apartado introduce los principios de la programacin modular, que resulta fundamental para entender la programacin en C. En este modelo de programacin, el cdigo fuente se divide en pequeos programas estructurados que se ocupan de realizar tareas muy especficas dentro del programa global. De hecho, con esto se consigue dividir el programa en subprogramas de ms fcil lectura y comprensin. Estos subprogramas constituyen los llamados mdulos, y de ah se deriva el nombre de esta tcnica de programacin. En C todos los mdulos son funciones que suelen realizar acciones muy concretas sobre unas pocas variables del programa. Ms an, cada funcin suele especializarse en un tipo de datos concreto. Dada la importancia de este tema, se insistir en los aspectos de la lo referente al mecanismo que se utiliza durante la ejecucin de los programas para proporcionar y obtener datos de las funciones que incluyen. Finalmente, se tratar sobre las macros del preprocesador de C. Estas macros tienen una apariencia similar a la de las llamadas de las funciones en C y pueden llevar a confusiones en la interpretacin del cdigo fuente del programa que las utilice.
47
Nota
Un tipo de datos abstracto es aquel que representa una informacin no contemplada en el lenguaje de programacin empleado. Puede darse el caso de que haya tipos de datos soportados en algn lenguaje de programacin que, sin embargo, en otros no lo estn y haya que tratarlos como abstractos.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El objetivo principal de esta unidad es que el lector aprenda a organizar correctamente el cdigo fuente de un programa, pues es un indicador fundamental de la calidad de la programacin. De forma ms concreta, el estudio de esta unidad pretende que se alcancen los objetivos siguientes: 1. Conocer en qu consiste la programacin estructurada. 2. Saber aplicar correctamente los esquemas algortmicos de tratamiento de secuencias de datos. 3. Identificar los sistemas a emplear para la depuracin de errores de un programa. 4. Saber cules son las estructuras de datos bsicas y cmo emplearlas. 5. Saber en qu consiste la programacin modular. 6. Conocer la mecnica de la ejecucin de las funciones en C.
Lecturas complementarias
E.W. Dijkstra (1968). The goto statement consireded harmful E.W. Dijkstra (1970). Notes on structured programming
ANOTACIONES
48
La programacin estructurada consiste en la organizacin del cdigo de manera que el flujo de ejecucin de sus instrucciones resulte evidente a sus lectores.
Un teorema formulado el ao 1966 por Bhm y Jacopini dice que todo programa propio debera tener un nico punto de entrada y un nico punto de salida, de manera que toda instruccin entre estos dos puntos es ejecutable y no hay bucles infinitos.
FUOC XP04/90793/00018
La conjuncin de estas propuestas proporciona las bases para la construccin de programas estructurados en los que las estructuras de control de flujo se pueden realizar mediante un conjunto de instrucciones muy reducido. De hecho, la estructura secuencial no necesita ninguna instruccin adicional, pues los programas se ejecutan normalmente llevando a cabo las instrucciones en el orden en que aparecen en el cdigo fuente. En la unidad anterior se coment la instruccin if, que permite una ejecucin condicional de bloques de instrucciones. Hay que tener presente que, para que sea un programa propio, debe existir la posibilidad de que se ejecuten todos los bloques de instrucciones. Las estructuras de control de flujo iterativas se comentarn en el apartado siguiente. Vale la pena indicar que, en cuanto a la programacin estructurada se refiere, slo es necesaria una nica estructura de control de flujo iterativa. A partir de sta se pueden construir todas las dems.
49
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Como se puede apreciar, todos los bucles pueden reducirse a un bucle mientras. Aun as, hay casos en los que resulta ms lgico emplear alguna de sus variaciones. Hay que tener presente que la estructura del flujo de control de un programa en un lenguaje de alto nivel no refleja lo que realmente hace el procesador (saltos condicionales e incondicionales) en el aspecto del control del flujo de la ejecucin de un programa. Aun as, el lenguaje C dispone de instrucciones que nos acercan a la realidad de la mquina, como la de salida forzada de bucle (break;) y la de la continuacin forzada de bucle (continue;). Adems, tambin cuenta con una instruccin de salto incondicional (goto) que no debera de emplearse en ningn programa de alto nivel.
Normalmente, la programacin de un bucle implica determinar cul es el bloque de instrucciones que hay que repetir y, sobre todo, bajo qu condiciones hay que realizar su ejecucin. En este sentido, es muy importante tener presente que la condicin que gobierna un bucle es la que determina la validez de la repeticin y, especialmente, su finalizacin cuando no se cumple. Ntese que debe existir algn caso en el que la evaluacin de la expresin de la condicin d como resultado un valor 'falso'. En caso contrario, el bucle se repetira indefinidamente (esto es lo que se llamara un caso de bucle infinito).
Habiendo determinado el bloque iterativo y la condicin que lo gobierna, tambin cabe programar la posible preparacin del entorno antes del bucle y las instrucciones que sean necesarias a su conclusin: su inicializacin y su finalizacin.
ANOTACIONES
50
La instruccin iterativa debe escogerse en funcin de la condicin que gobierna el bucle y de su posible inicializacin.
En los casos en que sea posible que no se ejecuten las instrucciones del bucle, es conveniente emplear while. Por ejemplo, para calcular cuntos divisores tiene un nmero entero positivo dado:
FUOC XP04/90793/00018
/* Bucle: ____________________________________*/ while( divisor < numero ) { if( numero % divisor == 0 ) ndiv = ndiv + 1; divisor = divisor + 1; } /* while */ /* Finalizacin: _____________________________*/ if( numero > 0 ) ndiv = ndiv + 1; /* ... */ A veces, la condicin que gobierna el bucle depende de alguna variable que se puede tomar como un contador de repeticiones; es decir, su contenido refleja el nmero de iteraciones realizado. En estos casos, puede considerarse el uso de un bucle for. En concreto, se podra interpretar como iterar el siguiente conjunto de instrucciones para todos los valores de un contador entre uno inicial y uno final dados. En el ejemplo siguiente, se muestra esta interpretacin en cdigo C: /* ... */ unsigned int contador; /* ... */ for( contador = INICIO ; contador FINAL ; contador = contador + INCREMENTO ) { instrucciones } /* for */ /* ... */ A pesar de que el ejemplo anterior es muy comn, no es necesario que la variable que acte de contador tenga que incrementarse, ni que tenga que hacerlo en un paso fijo, ni que la condicin slo deba consistir en comprobar que haya llegado a su valor final y, ni mucho menos, que sea una variable adicional que no se emplee en las instrucciones del cuerpo a iterar. De hecho sera muy til que fuera alguna variable cuyo contenido se modificara a cada iteracin y que, con ello, pudiese emplearse como contador.
51
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Es recomendable evitar el empleo del for para los casos en que no haya contadores. En su lugar, es mucho mejor emplear un while. En algunos casos, gran parte de la inicializacin coincidira con el cuerpo del bucle, o bien se hace necesario evidenciar que el bucle se ejecutar al menos una vez. Si esto se da, es conveniente utilizar una estructura do...while. Como ejemplo, veamos el cdigo de un programa que realiza la suma de distintos importes hasta que el importe ledo sea cero: /* ... */ float total, importe; /* ... */ total = 0.00; printf( "SUMA" ); do { printf( " + " ); scanf( "%f", &importe ); total = total + importe; } while( importe != 0.00 ); printf( " = %.2f", total ); /* ... */ La constante numrica real 0.00 se usa para indicar que slo son significativos dos dgitos fraccionarios, ya que, a todos los efectos, seria igual optar por escribir 0.0 o, incluso, 0 (en el ltimo caso, el nmero entero seria convertido a un nmero real antes de realizar la asignacin). Sea cual sea el caso, la norma de aplicacin es la de mantener siempre un cdigo inteligible. Por otra parte, la eleccin del tipo de instruccin iterativa depende del gusto esttico del programador y de su experiencia, sin mayores consecuencias en la eficiencia del programa en cuanto a coste de ejecucin.
ANOTACIONES
52
FUOC XP04/90793/00018
Ejemplo
En el primer caso, se tratara del procesamiento de informacin de una serie de datos procedentes del dispositivo de entrada estndar. Un ejemplo del segundo sera aquel en el que se deben procesar una serie de valores que adquiere una misma variable. En los dos casos, el tratamiento de la secuencia se puede observar en el cdigo del programa, pues debe realizarse mediante alguna instruccin iterativa. Habitualmente, este bucle se corresponde con un esquema algortmico determinado. En los siguientes apartados veremos los dos esquemas fundamentales para el tratamiento de secuencias de datos.
Recorrido
En otras palabras, supone tratar cada uno de los elementos de la secuencia, desde el primero hasta el ltimo. Si el nmero de elementos de que constar la secuencia es conocido a priori y la inicializacin del bucle es muy simple, entonces puede ser conveniente emplear un bucle for. En caso contrario, los bucles
53
ANOTACIONES
FUOC XP04/90793/00018
Software libre
ms adecuados son el bucle while o el dowhile si se sabe que habr, al menos, un elemento en la secuencia de datos. El esquema algortmico del recorrido de una secuencia sera, en su versin para C, el que se presenta a continuacin:
/* inicializacin para el procesamiento de la secuencia*/ /* (puede incluir el tratamiento del primer elemento) while( ! /* final de secuencia */ ) { /* tratar el elemento */ /* avanzar en secuencia */ } /* while */ /* finalizacin del procesamiento de la secuencia /* (puede incluir el tratamiento del ltimo elemento) */ */ */
El patrn anterior podra realizarse con alguna otra instruccin iterativa, si las circunstancias lo aconsejaran. Para ilustrar varios ejemplos de recorrido, supongamos que se desea obtener la temperatura media en la zona de una estacin meteorolgica. Para ello, procederemos a hacer un programa al que se le suministran las temperaturas registradas a intervalos regulares por el termmetro de la estacin y obtenga la media de los valores introducidos. As pues, el cuerpo del bucle consiste, simplemente, en acumular la temperatura (tratar el elemento) y en leer una nueva temperatura (avanzar en la secuencia):
/* ... */ acumulado = acumulado + temperatura; cantidad = cantidad + 1; scanf( %f, &temperatura ); /* ... */
ANOTACIONES
54
En este bloque iterativo se puede observar que temperatura debe tener un valor determinado antes de poderse acumular en la variable acumulado, la cual, a su vez, tambin tiene que estar inicializada. Similarmente, cantidad deber inicializarse a cero.
FUOC XP04/90793/00018
Por ello, la fase de inicializacin y preparacin de la secuencia ya est lista: /* ... */ unsigned int cantidad; float acumulado; /* ... */ cantidad = 0; acumulado = 0.00; scanf( "%f", &temperatura ); /* bucle ... */ Aun queda por resolver el problema de establecer la condicin de finalizacin de la secuencia de datos. En este sentido, puede ser que la secuencia de datos tenga una marca de final de secuencia o que su longitud sea conocida. En el primero de los casos, la marca de final debe de ser un elemento especial de la secuencia que tenga un valor distinto del que pueda tener cualquier otro dato. En este sentido, como se sabe que una temperatura no puede ser nunca inferior a 273,16 oC (y, mucho menos, una temperatura ambiental), se puede emplear este valor como marca de final. Por claridad, esta marca ser una constante definida en el preprocesador: #define MIN_TEMP 273.16 Cuando se encuentre, no deber ser procesada y, en cambio, s que debe hacerse la finalizacin del recorrido, calculando la media: /* ... */ float media; /* ... fin del bucle */ if( cantidad > 0 ) { media = acumulado / (float) cantidad; } else { media = MIN_TEMP; } /* if */ /* ... */
55
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En el clculo de la media, se comprueba primero que haya algn dato significativo para computarse. En caso contrario, se asigna a media la temperatura de marca de final. Con todo, el cdigo del recorrido sera el mostrado a continuacin: /* ... */ cantidad = 0; acumulado = 0.00; scanf( "%f", &temperatura ); while( ! ( temperatura == MIN_TEMP ) ) { acumulado = acumulado + temperatura; cantidad = cantidad + 1; scanf( "%f", &temperatura ); } /* while */ if( cantidad > 0 ) { media = acumulado / (float) cantidad; } else { media = MIN_TEMP; } /* if */ /* ... */ Si la marca de final de secuencia se proporcionara aparte, la instruccin iterativa debera ser una do..while. En este caso, se supondr que la secuencia de entrada la forman elementos con dos datos: la temperatura y un valor entero tomado como valor lgico que indica si es el ltimo elemento: /* ... */ cantidad = 0; acumulado = 0.00; do { scanf( "%f", &temperatura ); acumulado = acumulado + temperatura; cantidad = cantidad + 1; scanf( "%u", &es_ultimo ); } while( ! es_ultimo ); if( cantidad > 0 ) { media = acumulado / (float) cantidad; } else { media = MIN_TEMP; } /* if */ /* ... */
56
ANOTACIONES
FUOC XP04/90793/00018
En caso de que se conociera el nmero de temperaturas (NTEMP) que se han registrado, bastara con emplear un bucle de tipo for:
/* ... */ acumulado = 0.00; for( cant = 1; cant NTEMP; cant = cant + 1 ) { scanf( "%f", &temperatura ); acumulado = acumulado + temperatura; } /* for */ media = acumulado / (float) NTEMP; /* ... */
Bsqueda
Las bsquedas consisten en recorridos, mayoritariamente parciales, de secuencias de datos de entrada. Se recorren los datos de una secuencia de entrada hasta encontrar el que satisfaga una determinada condicin. Evidentemente, si no se encuentra ningn elemento que satisfaga la condicin, se realizar el recorrido completo de la secuencia.
De forma general, la bsqueda consiste en recorrer una secuencia de datos de entrada hasta que se cumpla una determinada condicin o se acaben los elementos de la secuencia. No es necesario que la condicin afecte a un nico elemento.
Siguiendo el ejemplo anterior, es posible hacer una bsqueda que detenga el recorrido cuando la media progresiva se mantenga en un margen de 1 oC respecto de la temperatura detectada durante ms de 10 registros.
El esquema algortmico es muy parecido al del recorrido, salvo por el hecho de que se incorpora la condicin de bsqueda y que, a la
57
ANOTACIONES
FUOC XP04/90793/00018
Software libre
salida del bucle, es necesario comprobar si la bsqueda se ha resuelto satisfactoriamente o no: /* inicializacin para el procesamiento de la secuencia */ /* (puede incluir el tratamiento del primer elemento) encontrado = FALSO; while( ! /* final de secuencia */ && !encontrado ) { /* tratar el elemento */ if( /* condicin de encontrado */ ) { encontrado = CIERTO; } else { /* avanzar en secuencia */ } /* if */ } /* while */ /* finalizacin del procesamiento de la secuencia if( encontrado ) { /* instrucciones */ } else { /* instrucciones */ } /* if */ En este esquema se supone que se han definido las constantes FALSO y CIERTO del modo siguiente: #define FALSO 0 #define CIERTO 1 Si se aplica el patrn anterior a la bsqueda de una media progresiva estable, el cdigo fuente sera el siguiente: /* ... */ cantidad = 0; acumulado = 0.00; scanf( "%f", &temperatura ); seguidos = 0; encontrado = FALSO; while( ! ( temperatura == MIN_TEMP ) && ! encontrado ) { acumulado = acumulado + temperatura; cantidad = cantidad + 1; media = acumulado / (float) cantidad;
58
*/
*/
ANOTACIONES
FUOC XP04/90793/00018
if( mediatemperatura+1.0 || temperatura-1.0media ) { seguidos = seguidos + 1; } else { seguidos = 0; } /* if */ if( seguidos == 10 ) { encontrado = CIERTO; } else { scanf( "%f", &temperatura ); } /* if */ } /* while */ /* ... */ En los casos de bsqueda no suele ser conveniente emplear un for, ya que suele ser una instruccin iterativa que emplea un contador que toma una serie de valores desde uno inicial hasta uno final. Es decir, hace un recorrido por la secuencia implcita de todos los valores que toma la variable de conteo.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
suma = suma + sumando; printf( "%.2f ", suma ); } /* while */ } /* main */ Otro filtro, quiz ms til, podra tratarse de un programa que sustituya los tabuladores por el nmero de espacios en blanco necesarios hasta la siguiente columna de tabulacin: #include <stdio.h> #define TAB 8 main() { char caracter; unsigned short posicion, tabulador; posicion = 0; caracter = getchar(); while( caracter != EOF ) { switch( caracter ) { case '\t':/* avanza hasta siguiente columna */ for( tabulador = posicion; tabulador < TAB; tabulador = tabulador + 1 ) { putchar( ' ' ); } /* for */ posicion = 0; break; case '\n': /* nueva lnea implica columna 0 */ putchar( caracter ); posicion = 0; break; default: putchar( caracter ); posicion = (posicion + 1) % TAB; } /* switch */ caracter = getchar(); } /* while */ } /* main */ Estos pequeos programas pueden resultar tiles por s mismos o bien combinados. As pues, la secuencia de datos de salida de uno puede
60
ANOTACIONES
FUOC XP04/90793/00018
constituir la secuencia de entrada de otro, constituyndose lo que denominamos una tubera (pipe, en ingls) la idea visual es que por un extremo de la tubera se introduce un flujo de datos y por el otro se obtiene otro flujo de datos ya procesado. En el camino, la tubera puede incluir uno o ms filtros que retienen y/o transforman los datos.
Ejemplo
Un filtro podra convertir una secuencia de datos de entrada consistentes en tres nmeros (cdigo de artculo, precio y cantidad) a una secuencia de datos de salida de dos nmeros (cdigo e importe) y el siguiente podra consistir en un filtro de suma, para recoger el importe total.
Para que esto sea posible, es necesario contar con la ayuda del sistema operativo. As pues, no es necesario que la entrada de datos se efecte a travs del teclado ni tampoco que la salida de datos sea obligatoriamente por la pantalla, como dispositivos de entrada y salida estndar que son. En Linux (y tambin en otros SO) se puede redirigir la entrada y la salida estndar de datos mediante los comandos de redireccin. De esta manera, se puede conseguir que la entrada estndar de datos sea un fichero determinado y que los datos de salida se almacenen en otro fichero que se emplee como salida estndar. En el ejemplo anterior, se puede suponer que existe un fichero (ticket.dat) con los datos de entrada y queremos obtener el total de la compra. Para ello, podemos emplear un filtro para calcular los importes parciales, cuya salida ser la entrada de otro que obtenga el total. Para aplicar el primer filtro, ser necesario que ejecutemos el programa correspondiente (al que llamaremos calcula_importes) redirigiendo la entrada estndar al fichero ticket.dat, y la salida al fichero importes.dat: $ calcula_importes <ticket.dat >importes.dat Con ello, importes.dat recoger la secuencia de pares de datos (cdigo de artculo e importe) que el programa haya generado por
61
ANOTACIONES
FUOC XP04/90793/00018
Software libre
la salida estndar. Las redirecciones se determinan mediante los smbolos menor que para la entrada estndar y mayor que para la salida estndar. Si deseamos calcular los importes de otras compras para luego calcular la suma de todas ellas, ser conveniente aadir a importes.dat todos los importes parciales de todos los boletos de compra. Esto es posible mediante el operador de redireccin de salida doblado, cuyo significado podra ser aadir al fichero la salida estndar del programa: $ calcula_importes <otro_ticket.dat >>importes.dat Cuando hayamos recogido todos los importes parciales que queramos sumar, podremos proceder a llamar al programa que calcula la suma: $ suma <importes.dat Si slo nos interesa la suma de un nico boleto de compra, podemos montar una tubera en la que la salida del clculo de los importes parciales sea la entrada de la suma: $ calcula_importes <ticket.dat | suma Como se puede observar en el comando anterior, la tubera se monta con el operador de tubera representado por el smbolo de la barra vertical. Los datos de la salida estndar de la ejecucin de lo que le precede los transmite como entrada estndar al programa que tenga a continuacin.
ANOTACIONES
62
FUOC XP04/90793/00018
correcta programacin se habla de error de lgica. En cambio, si el error tiene su razn en la violacin de las normas del lenguaje de programacin se habla de un error de sintaxis (aunque algunos errores tengan una naturaleza lxica o semntica).
Nota
Debug es el trmino ingls para referirse al depurado de errores de programas de computador. El verbo se puede traducir por eliminar bichos y tiene su origen en un reporte de 1945 sobre una prueba del computador Mark II realizada en la Universidad de Harvard. En el informe se registr que se encontr una polilla en un rel que provocaba su mal funcionamiento. Para probar que se haba quitado el bicho (y resuelto el error), se incluy la polilla en el mismo informe. Fue sujetada mediante cinta adhesiva y se aadi un pie que deca primer caso de una polilla encontrada. Fue tambin la primera aparicin del verbo debug (quitar bichos) que tom la acepcin actual.
Los errores de sintaxis son detectados por el compilador, ya que le impiden generar cdigo ejecutable. Si el compilador puede generar cdigo a pesar de la posible existencia de un error, el compilador suele emitir un aviso. Por ejemplo, es posible que una expresin en un if contenga un operador de asignacin, pero lo habitual es que se trate de una confusin entre operadores de asignacin y de comparacin: /* ... */ if( a = 5 ) b = 6; /* ... */ Ms an, en el cdigo anterior se trata de un error de programacin, puesto que la instruccin parece indicar que es posible que b pueda no ser 6. Si atendemos a la condicin del if, se trata de una asignacin del valor 5 a la variable a, con resultado igual al valor asignado. As pues, como el valor 5 es distinto de cero, el resultado es siempre afirmativo y, consecuentemente, b siempre toma el valor 6.
63
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Por este motivo, es muy recomendable que el compilador nos d todos los avisos que pueda. Para ello, debe ejecutarse con el argumento siguiente: $ gcc Wall o programa programa.c El argumento Wall indica que se d aviso de la mayora de casos en los que pueda haber algn error lgico. A pesar de que el argumento parece indicar que se nos avisar sobre cualquier situacin, an hay algunos casos sobre los que no avisa.
Los errores ms difciles de detectar son los errores lgicos que escapan incluso a los avisos del compilador. Estos errores son debidos a una programacin indebida del algoritmo correspondiente, o bien, a que el propio algoritmo es incorrecto. En todo caso, despus de su deteccin hay que proceder a su localizacin en el cdigo fuente.
Para la localizacin de los errores ser necesario determinar en qu estado del entorno se producen; es decir, bajo qu condiciones ocurren. Por lo tanto, es necesario averiguar qu valores de las variables conducen el flujo de ejecucin del programa a la instruccin en la que se manifiesta el error. Desafortunadamente, los errores suelen manifestarse en un punto posterior al del estado en el que realmente se produjo el fallo del comportamiento del programa. As pues, es necesario poder observar el estado del programa en cualquier momento para seguir su evolucin hasta la manifestacin del error con el propsito de detectar el fallo que lo causa. Para aumentar la observabilidad de un programa, es habitual introducir testigos (tambin llamados chivatos) en su cdigo de manera que nos muestren el contenido de determinadas variables. De todas maneras, este procedimiento supone la modificacin del programa cada vez que se introducen nuevos testigos, se eliminan los que resultan innecesarios o se modifican.
64
ANOTACIONES
FUOC XP04/90793/00018
Por otra parte, para una mejor localizacin del error, es necesario poder tener control sobre el flujo de ejecucin. La controlabilidad implica la capacidad de modificar el contenido de las variables y de elegir entre distintos flujos de ejecucin. Para conseguir un cierto grado de control es necesario introducir cambios significativos en el programa que se examina. En lugar de todo lo anterior, es mejor emplear una herramienta que nos permita observar y controlar la ejecucin de los programas para su depuracin. Estas herramientas son los llamados depuradores (debuggers, en ingls). Para que un depurador pueda realizar su trabajo, es necesario compilar los programas de manera que el cdigo resultante incluya informacin relativa al cdigo fuente. As pues, para depurar un programa, deberemos compilarlo con la opcin g: $ gcc Wall o programa g programa.c En GNU/C existe un depurador llamado gdb que nos permitir ejecutar un programa, hacer que se pare en determinadas condiciones, examinar el estado del programa cuando est parado y, por ltimo, cambiarlo para poder experimentar posibles soluciones. El depurador se invoca de la siguiente manera: $ gdb programa En la tabla siguiente se muestran algunos de los comandos que podemos indicarle a GDB:
Empieza a ejecutar el programa por su primera instruccin. El programa slo se detendr en un punto de parada, cuando el depurador reciba un aviso de parada (es decir, con el tecleo de control y C simultneamente), o bien cuando espere alguna entrada de datos. Establece un punto de parada antes de la primera instruccin que se encuentra en la lnea indicada del cdigo fuente. Si se omite el nmero de lnea, entonces lo establece en la primera instruccin de la lnea actual; es decir, en la que se ha detenido. Elimina el punto de parada establecido en la lnea indicada o, si se omite, en la lnea actual. Contina la ejecucin despus de una detencin. 65
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Comando next print expresin help quit Ejecuta la instruccin siguiente y se detiene.
Accin
Imprime el resultado de evaluar la expresin indicada. En particular, la expresin puede ser una variable y el resultado de su evaluacin, su contenido. Muestra la lista de los comandos. Finaliza la ejecucin de GDB
Los puntos de parada o breakpoints son marcas en el cdigo ejecutable que permiten al depurador conocer si debe parar la ejecucin del programa en curso o, por el contrario, debe continuar permitiendo su ejecucin. Estas marcas se pueden fijar o eliminar a travs del propio depurador. Con ello, es posible ejecutar porciones de cdigo de forma unitaria. En particular, puede ser conveniente introducir un punto de parada en main antes de proceder a su ejecucin, de manera que se detenga en la primera instruccin y nos permita realizar un mejor seguimiento de la misma. Para tal efecto, basta con el comando break main, ya que es posible indicar nombres de funciones como puntos de parada. De todas maneras, es mucho ms prctico emplear algn entorno grfico en el que pueda verse el cdigo fuente al mismo tiempo que la salida del programa que se ejecuta. Para ello, se puede emplear, por ejemplo, el DDD (Data Display Debugger) o el XXGDB. Los dos entornos emplean el GDB como depurador y, por tanto, disponen de las mismas opciones. No obstante, su manejo es ms fcil porque la mayora de comandos estn a la vista y, en todo caso, en los mens desplegables de que disponen.
ANOTACIONES
66
FUOC XP04/90793/00018
Una estructura homognea es aquella cuyos datos son todos del mismo tipo y una heterognea puede estar formada por datos de tipo distinto.
En los apartados siguientes se revisarn las principales estructuras de datos en C, aunque existen en todos los lenguajes de programacin estructurada. Cada apartado se organiza de manera que se vea cmo se pueden llevar a cabo las siguientes operaciones sobre las variables:
Declararlas, para que el compilador les reserve el espacio correspondiente. Inicializarlas, de manera que el compilador les d un contenido inicial (que puede cambiar) en el programa ejecutable resultante. Referenciarlas, de forma que se pueda acceder a su contenido, tanto para modificarlo como para leerlo.
Como es obligatorio anteponer el tipo de la variable en su declaracin, resulta conveniente identificar a los tipos de datos estructurados con un nombre de tipo. Estos nuevos tipos de datos se conocen como tipos de datos abstractos. El ltimo apartado estar dedicado a ellos.
Las matrices son estructuras de datos homogneas de tamao fijo. Es decir, se representa siempre una informacin que emplea un nmero determinado de datos. Tambin se llaman arreglos (una traduccin bastante directa del trmino array en ingls), tablas (para las de una o dos dimensiones) o vectores (si son unidimensionales). En el caso particular de los vectores de caracteres, reciben el nombre de cadenas de caracteres o strings, en ingls.
67
ANOTACIONES
2.7. Matrices
FUOC XP04/90793/00018
Software libre
2.7.1. Declaracin
A continuacin se muestran cuatro declaraciones de matrices distintas y un esquema de su distribucin en la memoria del computador. El nmero de bytes de cada divisin depende del tipo de dato de cada matriz correspondiente. En el esquema ya se avanza el nombre de cada dato dentro de la matriz, en el que se distingue el nombre comn de la matriz y una identificacin particular del dato, que se corresponde con la posicin del elemento en ella. Es muy importante tener presente que las posiciones siempre se numeran desde 0, en C.
Figura 2. : vector vector[0] : vector[1] : vector[2] : vector[3] : cadena cadena[0] : (1 byte) : cadena[19] : tabla tabla[0] : (2 bytes) : tabla[9] : matriz matriz[0][0] : matriz[0][1] : : matriz[2][1] : matriz[2][2] : : (8 bytes) (4 bytes)
ANOTACIONES
68
A continuacin, se muestran las declaraciones de las variables que conduciran hasta la distribucin en memoria que se ha visto:
FUOC XP04/90793/00018
La primera declaracin prepara un vector de 4 enteros con signo; la segunda, una cadena de 20 caracteres; la tercera, una tabla de 10 enteros positivos, y la ltima reserva espacio para una matriz de 3 3 nmeros reales de doble precisin.
Nota
La matriz cuadrada se almacena en la memoria por filas; es decir, primero aparece la primera fila, luego la segunda y as hasta la ltima. En caso de que hubiera que declarar una matriz con un nmero mayor de dimensiones, bastara con aadir su tamao entre corchetes en la posicin deseada entre el nombre de la estructura y el punto y coma final.
Como se ha comentado, las cadenas de caracteres son, realmente, matrices unidimensionales en C. Es decir, que tienen una longitud mxima fijada por el espacio reservado al vector que les corresponde. Aun as, las cadenas de caracteres pueden ser de longitud variable y, por tanto, se utiliza un marcador de final. En este caso, se trata del carcter NUL del cdigo ASCII, cuyo valor numrico es 0. En el ejemplo anterior, la cadena puede ser cualquier texto de hasta 19 caracteres, pues es necesario prever que el ltimo carcter es el de fin de cadena ('\0'). En todos los casos, especialmente cuando se trata de variables que hayan de contener valores constantes, se puede dar un valor inicial a cada uno de los elementos que contienen. Hay que tener presente que las matrices se guardan en memoria por aparece inmediatamente despus del nombre de la variable) de una matriz. En este caso, tomar las dimensiones necesarias para contener los datos presentes en su inicializacin. El resto de dimensiones deben de estar fijadas de manera que cada elemento de la primera dimensin tenga una ocupacin de memoria conocido. En los ejemplos siguientes, podemos observar distintas inicializaciones para las declaraciones de las variables anteriores.
69
ANOTACIONES
FUOC XP04/90793/00018
Software libre
vector[4] = { 0, 1, 2, 3 }; cadena[20] = { 'H', 'o', 'l', 'a', '\0' }; tabla[10] = { 98, 76, 54, 32, 1, }; matriz[3][3] = {{0.0, 0.1, 0.2}, {1.0, 1.1, 1.2}, {2.0, 2.1, 2.2} }; En el caso de la cadena de caracteres, los elementos en posiciones posteriores a la ocupada por '\0' no tendrn ningn valor inicial. Es ms, podrn tener cualquier valor. Se trata, pues, de una inicializacin incompleta. Para facilitar la inicializacin de las cadenas de caracteres tambin es posible hacerlo de la manera siguiente: char cadena[20] = "Hola"; Si, adems, la cadena no ha de cambiar de valor, es posible aprovechar que no es necesario especificar la dimensin, si sta se puede calcular a travs de la inicializacin que se hace de la variable correspondiente: char cadena[] = "Hola"; En el caso de la tabla, se realiza una inicializacin completa al indicar, con la ltima coma, que todos los elementos posteriores tendrn el mismo valor que el ltimo dado.
2.7.2. Referencia
Para hacer referencia, en alguna expresin, a un elemento de una matriz, basta con indicar su nombre y la posicin que ocupa dentro de ella: matriz[i0 ][i1 ]...[in] donde ik son expresiones el resultado de las cuales debe de ser un valor entero. Habitualmente, las expresiones suelen ser muy simples: una variable o una constante.
70
ANOTACIONES
FUOC XP04/90793/00018
Por ejemplo, para leer de la entrada estndar los datos para la matriz de reales dobles de 3 3 que se ha declarado anteriormente, se podra hacer el programa siguiente en el que, por supuesto, las variables fila y columna son enteros positivos: /* ... */ for( fila = 0; fila < 3; fila = fila + 1 ) { for( columna = 0; columna < 3; columna = columna + 1 ) { printf( "matriz[%u][%u]=? ", fila, columna ); scanf( "%lf ", &dato ); matriz[fila][columna] = dato; } /* for */ } /* for */ /* ... */ Es muy importante tener presente que el compilador de C no aade cdigo para comprobar la validez de los ndices de las matrices. Por lo tanto, no se comprueban los lmites de las matrices y se puede hacer referencia a cualquier elemento, tanto si pertenece a la matriz como si no. Esto es siempre responsabilidad del programador! Adems, en C, los corchetes son operadores de acceso a estructuras homogneas de datos (es decir, matrices) que calculan la posicin de un elemento a partir de la direccin de memoria base en la que se encuentran y el argumento que se les da. Esto implica que es posible, por ejemplo, acceder a una columna de una matriz cuadrada (por ejemplo: int A[3][3]; ) indicando slo su primer ndice (por ejemplo: pcol = A[0]; ). Ms an, es posible que se cometa el error de referirse a un elemento en la forma A[1,2] (comn en otros lenguajes de programacin). En este caso, el compilador acepta la referencia al tratarse de una forma vlida de acceder a la ltima columna de la matriz, puesto que do es el de la ltima expresin evaluada; es decir, para el ejemplo dado, la referencia A[1,2] sera, en realidad, A[2]. la coma es un operador de concatenacin de expresiones cuyo resulta-
2.7.3. Ejemplos
En este primer ejemplo, el programa comprobar si una palabra o frase corta es un palndromo; es decir, si se lee igual de izquierda a derecha que de derecha a izquierda.
71
Ejemplo
Uno de los palndromos ms conocidos en castellano es el siguiente: dbale arroz a la zorra el abad.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
#include <stdio.h> #define LONGITUD #define NULO main( ) { char unsigned int texto[LONGITUD]; longitud, izq, der; 81 '\0'
printf( "Comprobacin de palndromos.\n" ); printf( "Introduzca texto: "); gets( texto ); longitud = 0; while( texto[longitud] != NULO ) { longitud = longitud + 1; } /* while */ izq = 0; der = longitud; while( ( texto[izq] == texto[der] ) && ( izq < der ) ) { izq = izq + 1; der = der 1; } /* while */ if( izq < der ) { printf( "No es palndromo.\n" ); } else { printf( "Es palndromo!\n" ); } /* if */ } /* main */
Como gets toma como argumento la referencia de toda la cadena de caracteres, es decir, la direccin de la posicin inicial de memoria que ocupa, no es necesario emplear el operador de direccin de.
ANOTACIONES
72
El siguiente programa que se muestra almacena en un vector los coeficientes de un polinomio para luego evaluarlo en un determinado punto. El polinomio tiene la forma siguiente: P(x) = aMAX_GRADO-1xMAX_GRADO + ... + a2 x2 + a1x + a0
FUOC XP04/90793/00018
El programa deber evaluar el polinomio para una x determinada segn el mtodo de Horner, en el cual el polinomio se trata como si estuviera expresado en la forma:
P(x) = (... (aMAX_GRADO-1 x + aMAX_GRADO-2)x + ... + a1)x + a0 De esta manera, el coeficiente de mayor grado se multiplica por x y le suma el coeficiente del grado precedente. El resultado se vuelve a multiplicar por x, siempre que en este proceso no se haya llegado al trmino independiente. Si as fuera, ya se habra obtenido el resultado final. #include <stdio.h> #define MAX_GRADO 16 main( ) { double a[MAX_GRADO]; double x, resultado; int grado, i;
Nota
Con este mtodo se reduce el nmero de operaciones que habr que realizar, pues no es necesario calcular ninguna potencia de x .
printf( "Evaluacin de polinomios.\n" ); a[i] = 0.0; } /* for */ printf( "grado mximo del polinomio = ? "); scanf( "%d", &grado ); if( ( 0 grado ) && ( grado < MAX_GRADO ) ) { for( i = 0; i grado; i = i + 1 ) { printf( "a[%d]*x^%d = ? ", i, i ); scanf( "%lf", &x);
73
ANOTACIONES
FUOC XP04/90793/00018
Software libre
a[i] = x; } /* for */ printf( "x = ? " ); scanf( "%lf", &x ); result = 0.0; for( i = grado; i > 0; i = i - 1 ) { resultado = x * resultado + a[i-1]; } /* for */ printf( "P(%g) = %g\n", x, resultado, x) ); } else { printf( "El grado debe estar entre 0 y %d!\n", MAX_GRADO-1 ); /* printf */ } /* if */ } /* main */ Es conveniente, ahora, programar estos ejemplos con el fin de adquirir una cierta prctica en la programacin con matrices.
Ejemplo
Las fechas (da, mes y ao), los datos personales (nombre, apellidos, direccin, poblacin, etctera), las entradas de las guas telefnicas (nmero, propietario, direccin), y tantas otras.
2.8.1. Tuplas
Las tuplas son conjuntos de datos de distinto tipo. Cada elemento dentro de una tupla se identifica con un nombre de campo especfico. Estas tuplas, en C, se denominan estructuras (struct). Del mismo modo que sucede con las matrices, son tiles para organizar los datos desde un punto de vista lgico. Esta organizacin lgica supone poder tratar conjuntos de datos fuertemente relacionados entre s como una nica entidad. Es decir, que los programas que las empleen reflejarn su relacin y, por tanto, sern mucho ms inteligibles y menos propensos a errores.
74
ANOTACIONES
FUOC XP04/90793/00018
Nota
Se consigue mucha ms claridad si se emplea una tupla para una fecha que si se emplean tres enteros distintos (da, mes y ao). Por otra parte, las referencias a los campos de la fecha incluyen una mencin a que son parte de la misma; cosa que no sucede si estos datos estn contenidos en variables independientes.
Declaracin
La declaracin de las estructuras heterogneas o tuplas en C empieza por la palabra clave struct, que debe ir seguida de un bloque de declaraciones de las variables que pertenezcan a la estructura y, a continuacin, el nombre de la variable o los de una lista de variables que contendrn datos del tipo que se declara. Dado que el procedimiento que se acaba de describir se debe repetir para declarar otras tuplas idnticas, es conveniente dar un nombre (entre struct y el bloque de declaraciones de sus campos) a las estructuras declaradas. Con esto, slo es necesario incluir la declaracin de los campos de la estructura en la de la primera variable de este tipo. Para las dems, ser suficiente con especificar el nombre de la estructura. Los nombres de las estructuras heterogneas suelen seguir algn convenio para que sea fcil identificarlas. En este caso, se toma uno de los ms extendidos: aadir _s como posfijo del nombre. El ejemplo siguiente describe cmo podra ser una estructura de datos relativa a un avin localizado por un radar de un centro de control de aviacin y la variable correspondiente (avion). Como se puede observar, no se repite la declaracin de los campos de la misma en la posterior declaracin de un vector de estas estructuras para contener la informacin de hasta MAXNAV aviones (se supone que es la mxima concentracin de aviones posible al alcance de ese punto de control y que ha sido previamente definido): struct avion_s { double radio, angulo;
75
ANOTACIONES
FUOC XP04/90793/00018
Software libre
altura; nombre[33];
unsigned codigo; struct avion_s aviones[MAXNAV]; Tambin es posible dar valores iniciales a las estructuras empleando una asignacin al final de la declaracin. Los valores de los distintos campos tienen que separarse mediante comas e ir incluidos entre llaves:
struct persona_s { char nombre[ MAXLONG ]; unsigned short edad; } persona = { "Carmen" , 31 }; struct persona_s ganadora = { "desconocida", 0 }; struct persona_s gente[] = { { "Eva", 43 }, { "Pedro", 51 }, { "Jess", 32 }, { "Anna", 37 }, { "Joaqun", 42 } }; /* struct persona_s gente */
Referencia
La referencia a un campo determinado de una tupla se hace con el nombre del campo despus del nombre de la variable que lo contiene, separando los dos mediante un operador de acceso a campo de estructura (el punto). En el programa siguiente se emplean variables estructuradas que contienen dos nmeros reales para indicar un punto en el plano de forma cartesiana (struct cartesiano_s) y polar (struct polar_s). El programa pide las coordenadas cartesianas de un punto y las transforma en coordenadas polares (ngulo y radio, o distancia respecto del origen). Obsrvese que se declaran dos variables con una inicializacin directa: prec para indicar la precisin con que se trabajar y pi para almacenar el valor de la constante en la misma precisin.
76
Ejemplo
ganadora.edad = 25; inicial = gente[i].nombre[0];
ANOTACIONES
FUOC XP04/90793/00018
#include <stdio.h> #include <math.h> main( ) { struct cartesiano_s { double x, y; } double prec = 1e-9; double pi = 3.141592654; printf( "De coordenadas cartesianas a polares.\n" ); printf( "x = ? "); scanf( "%lf", &(c.x) ); printf( "y = ? "); scanf( "%lf", &(c.y) ); p.radio = sqrt( c.x * c.x + c.y * c.y ); if( p.radio < prec ) { /* si el radio es cero ... p.angulo = 0.0; /* ... el ngulo es cero. } else { if( -prec<c.x && c.x<prec ) { /* si c.x es cero ... */ if( c.y > 0.0 )p.angulo = 0.5*pi; elsep.angulo = -0.5*pi; } else { p.angulo = atan( c.y / c.x ); } /* if */ } /* if */ printf( "radio = %g\n", p.radio ); printf( "ngulo = %g (%g grados sexagesimales)\n", p.angulo, p.angulo*180.0/pi ); /* printf */ } /* main */ El programa anterior hace uso de las funciones matemticas estnrespectivamente. Para ello, es necesario que se incluya el fichero de cabeceras (#include fuente. <math.h>) correspondiente en el cdigo */ */ c; struct polar_s { double radio, angulo; } p;
ANOTACIONES
FUOC XP04/90793/00018
Software libre
en su declaracin y el compilador reserva espacio para contener al que ocupe mayor tamao de todos ellos. Su declaracin es parecida a la de las tuplas.
Ejemplo
El uso de esta clase de variables puede suponer un cierto ahorro de espacio. No obstante, hay que tener presente que, para gestionar estos campos de tipo variable, es necesario disponer de informacin (explcita o implcita) del tipo de dato que se almacena en ellos en un momento determinado. As pues, suelen ir combinados en tuplas que dispongan de algn campo que permita averiguar el tipo de datos del contenido de estas variables. Por ejemplo, vase la declaracin de la siguiente variable (seguro), en la que el campo tipo_bien permite conocer cul de las estructuras del tipo mltiple est presente en su contenido: struct seguro_s { unsigned char char char poliza; tomador[31]; NIF[9]; tipo_bien; /* 'C': vivienda, */ /* 'F': vida, */ /* ''M': vehculo. */ union { struct { char ref_catastro[]; float superficie; } vivienda; struct { struct fecha_s nacimiento; char beneficiario[31]; } vida; struct {
78
ANOTACIONES
FUOC XP04/90793/00018
char matricula[7]; struct fecha_s fabricacion; unsigned short siniestros; } vehculo; } datos; unsigned valor; unsigned prima; } seguro; Con ello, tambin es posible tener informacin sobre una serie de seguros en una misma tabla, independientemente del tipo de pliza que tengan asociados: struct seguro_s asegurados[ NUMSEGUROS ]; En todo caso, el uso de union resulta bastante infrecuente.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
persona_t alumnos[MAX_GRUPO];
Es muy recomendable emplear siempre un nombre de tipo que identifique los contenidos de las variables de forma significativa dentro del problema que ha de resolver el programa que las utiliza.
ANOTACIONES
80
Por ello, a partir de este punto, todos los ejemplos emplearn tipos abstractos de datos cuando sea necesario. Por lo que atae a este texto, se preferir que los nombres de tipo terminen siempre en _t.
FUOC XP04/90793/00018
81
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Tambin es posible y recomendable definir un tipo de dato asociado: typedef enum bool_e { FALSE = 0, TRUE = 1 } bool; En este caso particular, se emplea el nombre bool en lugar de bool_t o logico por coincidir con el nombre del tipo de datos primitivo de C++. Dado lo frecuente de su uso, en el resto del texto se considerar definido. (No obstante, habr que tener presente que una variable de este tipo puede adquirir valores distintos de 1 y ser, conceptualmente, cierta' o TRUE.)
2.9.3. Ejemplo
En los programas vistos con anterioridad se emplean variables de tipos de datos estructurados y que son (o podran ser) frecuentemente usados en otros programas de la misma ndole. As pues, es conveniente transformar las declaraciones de tipo de los datos estructurados en definicin de tipos. En particular, el programa de evaluacin de polinomios por el mtodo de Horner debera haber dispuesto de un tipo de datos estructurado que representara la informacin de un polinomio (grado mximo y coeficientes). El programa que se muestra a continuacin contiene una definicin del tipo de datos polinomio_t para identificar a sus componentes como datos de un mismo polinomio. El grado mximo se emplea tambin para saber qu elementos del vector contienen los coeficientes para cada grado y cules no. Este programa realiza la derivacin simblica de un polinomio dado (la derivacin simblica implica obtener otro polinomio que representa la derivada de la funcin polinmica dada como entrada). #include <stdio.h> #define MAX_GRADO 16 typedef struct polinomio_s { int double grado; a[MAX_GRADO];
ANOTACIONES
82
} polinomio_t;
FUOC XP04/90793/00018
p.grado = 0; /* inicializacin de (polinomio_t) p p.a[0] = 0.0; printf( "Derivacin simblica de polinomios.\n" ); printf( "Grado del polinomio = ? " ); scanf( "%d", &(p.grado) ); if( ( 0 p.grado ) && ( p.grado < MAX_GRADO ) ) { for( i = 0; i p.grado; i = i + 1 ) { /* lectura printf( "a[%d]*x^%d = ? ", i, i ); scanf( "%lf", &coef ); p.a[i] = coef; } /* for */ for( i = 0; i < p.grado; i = i + 1 ) { /* derivacin p.a[i] = p.a[i+1]*(i+1); } /* for */ if( p.grado > 0 ) { p.grado = p.grado -1; } else { p.a[0] = 0.0; } /* if */ printf( "Polinomio derivado:\n" ); for( i = 0; i < p.grado; i = i + 1 ) { /* impresin printf( "%g*x^%d +", p.a[i], i ); } /* for */ } else { printf( "El grado del polinomio tiene que estar" ); printf( " entre 0 y %d!\n", MAX_GRADO-1 ); } /* if */ } /* main */ printf( "%g\n", p.a[i] );
*/
*/
*/
83
ANOTACIONES
FUOC XP04/90793/00018
Software libre
2.10. Ficheros
Los ficheros son una estructura de datos homognea que tiene la particularidad de tener los datos almacenados fuera de la memoria principal. De hecho son estructuras de datos que se encuentran en la llamada memoria externa o secundaria (no obstante, es posible que algunos ficheros temporales se encuentren slo en la memoria principal). Para acceder a los datos de un fichero, el ordenador debe contar con los dispositivos adecuados que sean capaces de leer y, opcionalmente, escribir en los soportes adecuados. Por el hecho de residir en soportes de informacin permanentes, pueden mantener informacin entre distintas ejecuciones de un mismo programa o servir de fuente y depsito de informacin para cualquier programa. Dada la capacidad de muchos de estos soportes, el tamao de los ficheros puede ser mucho mayor incluso que el espacio de memoria principal disponible. Por este motivo, en la memoria principal slo se dispone de una parte del contenido de los ficheros en uso y de la informacin necesaria para su manejo. No menos importante es que los ficheros son estructuras de datos con un nmero indefinido de stos. En los prximos apartados se comentarn los aspectos relacionados con los ficheros en C, que se denominan ficheros de flujo de bytes (en ingls, byte streams). Estos ficheros son estructuras homogneas de datos simples en los que cada dato es un nico byte. Habitualmente, los hay de dos tipos: los ficheros de texto ASCII (cada byte es un carcter) y los ficheros binarios (cada byte coincide con algn byte que forma parte de algn dato de alguno de los tipos de datos que existen).
Ejemplo
Unidades de disquete, de disco duro, de CD, de DVD, de tarjetas de memoria, etc.
ANOTACIONES
84
FUOC XP04/90793/00018
ria (binarios), bien como una cadena de caracteres (textuales). En este apartado nos ocuparemos especialmente de estos ltimos por ser los ms habituales. Dado que estn almacenados en un soporte externo, es necesario disponer de informacin sobre los mismos en la memoria principal. En este sentido, toda la informacin de control de un fichero de este tipo y una parte de los datos que contiene (o que habr de contener, en caso de escritura) se recoge en una nica variable de tipo FILE. El tipo de datos FILE es una tupla compuesta, entre otros campos, por el nombre del fichero, la longitud del mismo, la posicin del ltimo byte ledo o escrito y un buffer (memoria temporal) que contiene BUFSIZ byte del fichero. Este ltimo es necesario para evitar accesos al dispositivo perifrico afectado y, dado que las operaciones de lectura y escritura se hacen por bloques de bytes, para que stas se hagan ms rpidamente. Afortunadamente, hay funciones estndar para hacer todas las operaciones que se acaban de insinuar. Al igual que la estructura FILE y la constante BUFSIZ, estn declaradas en el fichero stdio.h. En el prximo apartado se comentarn las ms comunes.
Nota
En el tercer caso es necesario tener cuidado: si el fichero ya existiera, se perdera todo su contenido!
ANOTACIONES
FUOC XP04/90793/00018
Software libre
"b", respectivamente. Si se omite esta informacin, el fichero se abre en modo texto. Una vez abierto, se puede leer su contenido o bien escribir nuevos datos en l. Despus de haber operado con el fichero, hay que cerrarlo. Esto es, hay que indicar al sistema operativo que ya no se trabajar ms con l y, sobre todo, hay que acabar de escribir los datos que pudieran haber quedado pendientes en el buffer correspondiente. De todo esto se ocupa la funcin estndar de cierre de fichero. En el cdigo siguiente se refleja el esquema algortmico para el trabajo con ficheros y se detalla, adems, una funcin de reapertura de ficheros que aprovecha la misma estructura de datos de control. Hay que tener en cuenta que esto supone cerrar el fichero anteriormente abierto: /* ... */ /* Se declara una variable para que contenga /* la referencia de la estructura FILE: FILE* fichero; /* ... */ fichero = fopen( nombre_fichero, modo_apertura ); /* El modo de apertura puede ser "r" para lectura, /* "w" para escritura, "a" para aadidura y /* "r+", "w+" o "a+" para actualizacin (leer/escribir). /* Se le puede aadir el sufijo /* "t" para texto o "b" para binario. if( fichero != NULL ) { /* Tratamiento de los datos del fichero. /* Posible reapertura del mismo fichero: fichero = freopen( nombre_fichero, */ */ */ */ */ */ */ */ */
ANOTACIONES
modo_apertura, fichero ); /* freopen */ /* Tratamiento de los datos del fichero. fclose( fichero ); } /* if */ En los prximos apartados se detallan las funciones estndar para trabajar con ficheros de flujo o streams. Las variables que se em86
*/
FUOC XP04/90793/00018
plean en los ejemplos son del tipo adecuado para lo que se usan y, en particular, flujo es de tipo FILE*; es decir, referencia a estructura de fichero.
fscanf( flujo, "formato" [,lista_de_&variables ) De funcionamiento similar a scanf(), devuelve como resultado el nmero de argumentos realmente ledos. Por tanto, ofrece una manera indirecta de determinar si se ha llegado al final del fichero. En este caso, de todas maneras, activa la condicin de fin de fichero. De hecho, un nmero menor de asignaciones puede deberse, simplemente, a una entrada inesperada, como por ejemplo, leer un carcter alfabtico para una conversin "%d". Por otra parte, esta funcin devuelve EOF (del ingls end of file) si se ha llegado a final de fichero y no se ha podido realizar ninguna asignacin. As pues, resulta mucho ms conveniente emplear la funcin que comprueba esta condicin antes que comprobarlo de forma indirecta mediante el nmero de parmetros correctamente ledos (puede que el fichero contenga ms datos) o por el retorno de EOF (no se produce si se ha ledo, al menos, un dato).
Ejemplo
fscanf( flujo, %u%c, &num_dni, &letra_nif ); fscanf( flujo, %d%d%d, &codigo, &precio, &cantidad );
feof( flujo ) Devuelve 0 en caso de que no se haya llegado al final del fichero. En caso contrario devuelve un valor distinto de cero, es decir, que es cierta la condicin de final de fichero.
87
ANOTACIONES
FUOC XP04/90793/00018
Software libre
fgetc( flujo ) Lee un carcter del flujo. En caso de no poder efectuar la lectura por haber llegado a su fin, devuelve EOF. Esta constante ya est definida en el fichero de cabeceras stdio.h; por lo tanto, puede emplearse libremente dentro del cdigo.
Nota
Es importante tener presente que puede haber ficheros que tengan un carcter EOF en medio de su flujo, pues el final del fichero viene determinado por su longitud.
fgets( cadena, longitud_maxima, flujo ) Lee una cadena de caracteres del fichero hasta encontrar un final de lnea, hasta llegar a longitud_maxima (1 para la marca de final de cadena) de caracteres, o hasta fin de fichero. Devuelve NULL si encuentra el final de fichero durante la lectura.
Ejemplo
if( fgets( cadena, 33, flujo ) != NULL ) puts( cadena );
fprintf( flujo, "formato" [, lista_de_variables] ) La funcin fprintf() escribe caracteres en el flujo de salida indicado con formato. Si ha ocurrido algn problema, esta funcin devuelve el ltimo carcter escrito o la constante EOF.
ANOTACIONES
88
fputc( caracter, flujo ) La funcin fputc()escribe caracteres en el flujo de salida indicado carcter a carcter. Si se ha producido un error de escritura o bien
FUOC XP04/90793/00018
el soporte est lleno, la funcin fputc() activa un indicador de error del fichero. Este indicador se puede consultar con la funcin ferror( flujo ), que retorna un cero (valor lgico falso) cuando no hay error.
fputs( cadena, flujo ) La funcin fputs()escribe caracteres en el flujo de salida indicado permitiendo grabar cadenas completas. Si ha ocurrido algn problema, esta funcin acta de forma similar a fprintf().
fseek( flujo, desplazamiento, direccion ) Desplaza el cabezal lector/escritor respecto de la posicin actual con el valor del entero largo indicado en desplazamiento si direccion es igual a SEEK_CUR. Si esta direccin es SEEK_SET, entonces desplazamiento se convierte en un desplazamiento respecto del principio y, por lo tanto, indica la posicin final. En ltima posicin del fichero. Si el reposicionamiento es correcto, devuelve 0.
rewind( flujo ) Sita el cabezal al principio del fichero. Esta funcin es equivalente a: seek( flujo, 0L, SEEK_SET );
89
ANOTACIONES
FUOC XP04/90793/00018
Software libre
donde la constante de tipo long int se indica con el sufijo L. Esta funcin permitira, pues, releer un fichero desde el principio.
2.10.3. Ejemplo
Se muestra aqu un pequeo programa que cuenta el nmero de palabras y de lneas que hay en un fichero de texto. El programa entiende como palabra toda serie de caracteres entre dos espacios en blanco. Un espacio en blanco es cualquier carcter que haga que isspace()devuelva cierto. El final de lnea se indica con el carcter de retorno de carro (ASCII nmero 13); es decir, con '\n'. Es importante observar el uso de las funciones relacionadas con los ficheros de flujo de bytes. Como se ver, la estructura de los programas que trabajan con estos ficheros incluye la codificacin de alguno de los esquemas algortmicos para el tratamiento de secuencias de datos (de hecho, los ficheros de flujo son secuencias de bytes) En este caso, como se realiza un conteo de palabras y lneas, hay que recorrer toda la secuencia de entrada. Por lo tanto, se puede observar que el cdigo del programa sigue perfectamente el esquema algortmico para el recorrido de secuencias.
ANOTACIONES
/* Fichero: npalabras.c #include <stdio.h> #include <ctype.h> /* Contiene: isspace() typedef enum bool_e { FALSE = 0, TRUE = 1 } bool;
90
*/ */
FUOC XP04/90793/00018
main() { char nombre_fichero[FILENAME_MAX]; FILE *flujo; bool en_palabra; char c; unsigned long int npalabras, nlineas; printf( "Contador de palabras y lneas.\n" ); printf( "Nombre del fichero: "); gets( nombre_fichero ); flujo = fopen( nombre_fichero, "rt" ); if( flujo != NULL ) { npalabras = 0; nlineas = 0; en_palabra = FALSE; while( ! feof( flujo ) ) { c = fgetc( flujo ); if( c == '\n' ) nlineas = nlineas + 1; if( isspace( c ) ) { if( en_palabra ) { en_palabra = FALSE; npalabras = npalabras + 1; } /* if */ } else { /* si el carcter no es espacio en blanco en_palabra = TRUE; } /* if */ } /* while */ printf( "Numero de palabras = %lu\n", npalabras ); printf( "Numero de lneas = %lu\n", nlineas ); } else { } /* if */ } /* main */ printf( "No puedo abrir el fichero!\n" ); */
Nota
La deteccin de las palabras se hace comprobando los finales de palabra, que tienen que estar formadas por un carcter distinto del espacio en blanco, seguido por uno que lo sea.
91
ANOTACIONES
FUOC XP04/90793/00018
Software libre
2.12. Funciones
En C, a las agrupaciones de cdigo en las que se divide un determinado programa se las llama, precisamente, funciones. Ms an, en C, todo el cdigo debe estar distribuido en funciones y, de hecho, el propio programa principal es una funcin: la funcin principal (main). Generalmente, una funcin incluir en su cdigo, como mucho, la programacin de unos pocos esquemas algortmicos de procesamiento de secuencias de datos y algunas ejecuciones condicionales o alternativas. Es decir, lo necesario para realizar una tarea muy concreta.
ANOTACIONES
92
FUOC XP04/90793/00018
supone describir su contenido. Tal diferenciacin ya se ha visto para las variables, pero slo se ha insinuado para las funciones. La declaracin consiste exactamente en lo mismo que para las variables: manifestar su existencia. En este caso, de todas maneras hay que describir los argumentos que toma y el resultado que devuelve para que el compilador pueda generar el cdigo, y poderlas emplear.
En cambio, la definicin de una funcin se corresponde con su programa, que es su contenido. De hecho, de forma similar a las variables, el contenido se puede identificar por la posicin del primero de sus bytes en la memoria principal. Este primer byte es el primero de la primera instruccin que se ejecuta para llevar a cabo la tarea que tenga programada.
Declaraciones
La declaracin de una funcin consiste en especificar el tipo de dato que devuelve, el nombre de la funcin, la lista de parmetros que recibe entre parntesis y un punto y coma que finaliza la declaracin: tipo_de_dato nombre_funcin( lista_de_parmetros ); Hay que tener presente que no se puede hacer referencia a funciones que no estn declaradas previamente. Por este motivo, es necesario incluir los ficheros de cabeceras de las funciones estndar de la biblioteca de C como stdio.h, por ejemplo.
Si una funcin no ha sido previamente declarada, el compilador supondr que devuelve un entero. De la misma manera, si se omite el tipo de dato que retorna, supondr que es un entero.
93
ANOTACIONES
FUOC XP04/90793/00018
Software libre
La lista de parmetros es opcional y consiste en una lista de declaraciones de variables que contendrn los datos tomados como argumentos de la funcin. Cada declaracin se separa de la siguiente por medio de una coma. Por ejemplo: float nota_media( float teo, float prb, float pract ); bool aprueba( float nota, float tolerancia ); Si la funcin no devuelve ningn valor o no necesita ningn argumento, se debe indicar mediante el tipo de datos vaco (void): void avisa( char mensaje[] ); bool si_o_no( void ); int lee_codigo( void );
Definiciones
La definicin de una funcin est encabezada siempre por su declaracin, que ahora debe incluir forzosamente la lista de parmetros si los tiene. Esta cabecera no debe finalizar con punto y coma, sino que ir seguida del cuerpo de la funcin, delimitada entre llaves de apertura y cierre: tipo_de_dato nombre_funcin( lista_de_parmetros ) { /* cuerpo de la funcin: */ /* 1) declaracin de variables locales /* 2) instrucciones de la funcin } /* nombre_funcin */ Tal como se ha comentado anteriormente, la definicin de la funcin */ */
ANOTACIONES
94
ya supone su declaracin. Por lo tanto, las funciones que realizan tareas de otros programas y, en particular, del programa principal (la funcin main) se definen con anterioridad.
Llamadas
El mecanismo de uso de una funcin en el cdigo es el mismo que se ha empleado para las funciones de la biblioteca estndar de C: basta
FUOC XP04/90793/00018
con referirse a ellas por su nombre, proporcionarles los argumentos necesarios para que puedan llevar a cabo la tarea que les corresponda y, opcionalmente, emplear el dato devuelto dentro de una expresin, que ser, habitualmente, de condicin o de asignacin. El procedimiento por el cual el flujo de ejecucin de instrucciones pasa a la primera instruccin de una funcin se denomina procedimiento de llamada. As pues, se hablar de llamadas a funciones cada vez que se indique el uso de una funcin en un programa. A continuacin se presenta la secuencia de un procedimiento de llamada: 1. Preparar el entorno de ejecucin de la funcin; es decir, reservar espacio para el valor de retorno, los parmetros formales (las variables que se identifican con cada uno de los argumentos que tiene), y las variables locales. 2. Realizar el paso de parmetros; es decir, copiar los valores resultantes de evaluar las expresiones en cada uno de los argumentos de la instruccin de llamada a los parmetros formales. 3. Ejecutar el programa correspondiente. 4. Liberar el espacio ocupado por el entorno local y devolver el posible valor de retorno antes de regresar al flujo de ejecucin de instrucciones en donde se encontraba la llamada. El ltimo punto se realiza mediante la instruccin de retorno que, claro est, es la ltima instruccin que se ejecutar en una funcin: return expresin;
Nota
Esta instruccin debe aparecer vaca o no aparecer si la funcin es de tipo void; es decir, si se la ha declarado explcitamente para no devolver ningn dato.
En el cuerpo de la funcin se puede realizar una llamada a la misma. Esta llamada se denomina llamada recursiva, ya que la defi95
ANOTACIONES
FUOC XP04/90793/00018
Software libre
nicin de la funcin se hace en trminos de ella misma. Este tipo de llamadas no es incorrecto pero hay que vigilar que no se produzcan indefinidamente; es decir, que haya algn caso donde el flujo de ejecucin de las instrucciones no implique realizar ninguna llamada recursiva y, por otra parte, que la transformacin que se aplica a los parmetros de stas conduzca, en algn momento, a las condiciones de ejecucin anterior. En particular, no se puede hacer lo siguiente: /* ... */ void menu( void ) { /* mostrar men de opciones, /* ejecutar opcin seleccionada menu(); /* ... */ La funcin anterior supone realizar un nmero indefinido de llamadas a menu() y, por tanto, la continua creacin de entornos locales sin su posterior liberacin. En esta situacin, es posible que el programa no pueda ejecutarse correctamente tras un tiempo por falta de memoria para crear nuevos entornos. */ */
ANOTACIONES
96
En el cdigo de una funcin se pueden emplear todas las variables globales (las que son visibles por cualquier instruccin del programa), todos los parmetros formales (las variables que equivalen a los argumentos de la funcin), y todas las variables locales (las que se declaran dentro del cuerpo de la funcin). En algunos casos puede no ser conveniente utilizar variables globales, pues dificultaran la compresin del cdigo fuente, cosa que di-
FUOC XP04/90793/00018
ficultara el posterior depurado y mantenimiento del programa. Para ilustrarlo, veamos el siguiente ejemplo: #include <stdio.h> unsigned int A, B; void reduce( void ) { if( A < B ) B = B - A; else A = A - B; } /* reduce */ void main( void ) { printf( "El MCD de: " ); scanf( "%u%u", &A, &B ); while( A!=0 && B!=0 ) reduce(); printf( "... es %u\n", A + B ); } /* main */ Aunque el programa mostrado tiene un funcionamiento correcto, no es posible deducir directamente qu hace la funcin reduce(), ni tampoco determinar de qu variables depende ni a cules afecta. Por tanto, hay que adoptar como norma que ninguna funcin dependa o afecte a variables globales. De ah el hecho de que, en C, todo el cdigo se distribuye en funciones, se deduce fcilmente que no debe haber ninguna variable global. As pues, todas las variables son de mbito local (parmetros formalocal de una funcin y slo pueden ser empleadas por las instrucciones dentro de la misma.
Las variables locales se crean en el momento en que se activa la funcin correspondiente, es decir, despus de ejecutar la instruccin de llamada de la funcin. Por este motivo, tienen una clase de almacenamiento denominada automtica, ya que son creadas y destruidas de forma automtica en el procedimiento de llamada a funcin. Esta
97
ANOTACIONES
FUOC XP04/90793/00018
Software libre
int una_funcion_cualquiera( int a, int b ) { /* ... */ auto int variable_local; /* resto de la funcin */ } /* una_funcion_cualquiera */ A veces resulta interesante que la variable local se almacene temporalmente en uno de los registros del procesador para evitar tener que actualizarla continuamente en la memoria principal y acelerar, con ello, la ejecucin de las instrucciones involucradas (normalmente, las iterativas). En estos casos, se puede aconsejar al compilador que genere cdigo mquina para que se haga as; es decir, para que el almacenamiento de una variable local se lleve a cabo en uno de los registros del procesador. De todas maneras, muchos compiladores son capaces de llevar a cabo tales optimizaciones de forma autnoma.
Esta clase de almacenamiento se indica con la palabra clave register: int una_funcion_cualquiera( int a, int b ) { /* ... */ register int contador; /* resto de la funcin */ } /* una_funcion_cualquiera */
ANOTACIONES
98
Para conseguir el efecto contrario, se puede indicar que una variable local resida siempre en memoria mediante la indicacin volatile como clase de almacenamiento. Esto slo resulta conveniente cuando la variable puede ser modificada de forma ajena al programa.
FUOC XP04/90793/00018
/* ... */ volatile float temperatura; /* resto de la funcin */ } /* una_funcion_cualquiera */ En los casos anteriores, se trataba de variables automticas. Sin embargo, a veces resulta interesante que una funcin pueda mantener la informacin contenida en alguna variable local entre distintas llamadas. Esto es, permitir que el algoritmo correspondiente recuerde algo de su estado pasado. Para conseguirlo, hay que indicar que la variable tiene una clase de almacenamiento static; es decir, que se encuentra esttica o inamovible en memoria: int una_funcion_cualquiera( int a, int b ) { /* ... */ static unsigned numero_llamadas = 0; numero_llamadas = numero_llamadas + 1; /* resto de la funcin */ } /* una_funcion_cualquiera */ En el caso anterior es muy importante inicializar las variables en la declaracin; de lo contrario, no se podra saber el contenido inicial, previo a cualquier llamada a la funcin.
Como nota final, indicar que las clases de almacenamiento se utilizan muy raramente en la programacin en C. De hecho, a excepcin de static, las dems prcticamente no tienen efecto en un compilador actual.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En este sentido, hay dos posibilidades: que los argumentos reciban el resultado de la evaluacin de la expresin correspondiente o que se sustituyan por la variable que se indic en el parmetro real de la misma posicin. El primer caso, se trata de un paso de parmetros por valor, mientras que el segundo, se trata de un paso de variable (cualquier cambio en el argumento es un cambio en la variable que consta como parmetro real).
El paso por valor consiste en asignar a la variable del parmetro formal correspondiente el valor resultante del parmetro real en su misma posicin. El paso de variable consiste en sustituir la variable del parmetro real por la del parmetro formal correspondiente y, consecuentemente, poder emplearla dentro de la misma funcin con el nombre del parmetro formal.
En C, el paso de parmetros slo se efecta por valor; es decir, se evalan todos los parmetros en la llamada y se asigna el resultado al parmetro formal correspondiente en la funcin.
Para modificar alguna variable que se desee pasar como argumento en la llamada de una funcin, es necesario pasar la direccin de memoria en la que se encuentra. Para esto se debe de emplear el operador de obtencin de direccin (&) que da como resultado la direccin de memoria en la que se encuentra su argumento (variable, campo de tuplo o elemento de matriz, entre otros). Este es el mecanismo que se emplea para que la funcin scanf deposite en las variables que se le pasan como argumento los valores que lee.
100
ANOTACIONES
FUOC XP04/90793/00018
Por otra parte, en las funciones llamadas, los parmetros formales que reciben una referencia de una variable en lugar de un valor se deben declarar de manera especial, anteponiendo a su nombre un asterisco. El asterisco, en este contexto, se puede leer como el contenido cuya posicin inicial se encuentra en la variable correspondiente. Por tanto, en una funcin como la mostrada a continuacin, se leera el contenido cuya posicin inicial se encuentra en el parmetro formal numerador es de tipo entero. De igual forma se leera para el denominador:
void simplifica( int *numerador, int *denominador ) { int mcd; mcd = maximo_comun_divisor( *numerador, *denominador ); *numerador = *numerador / mcd; *denominador = *denominador / mcd; } /* simplifica */ /* ... */ simplifica( &a, &b ); /* ... */
Aunque se insistir en ello ms adelante, hay que tener presente que el asterisco, en la parte del cdigo, debera leerse como el contenido de la variable que est almacenada en la posicin de memoria del argumento correspondiente. Por lo tanto, deberemos emplear *parmetro_formal cada vez que se desee utilizar la variable pasada por referencia.
El programa siguiente calcula numricamente la integral de una funcin en un intervalo dado segn la regla de Simpson. Bsicamente, el mtodo consiste en dividir el intervalo de integracin en un nmero determinado de segmentos de la misma longitud que constituyen la base de unos rectngulos cuya altura viene determinada por el valor de la funcin a integrar en el punto inicial del segmento. La suma de las reas de estos rectngulos dar la superficie aproximada defini101
ANOTACIONES
2.12.4. Ejemplo
FUOC XP04/90793/00018
Software libre
da por la funcin, el eje de las X y las rectas perpendiculares a l que pasan por los puntos inicial y final del segmento de integracin:
Figura 3.
/* Fichero: simpson.c #include <stdio.h> #include <math.h> double f( double x ) { return 1.0/(1.0 + x*x); } /* f */ double integral_f( double a, double b, int n ) { double result; double x, dx;
*/
ANOTACIONES
102
int
i;
result = 0.0; if( (a < b) && (n > 0) ) { x = a; dx = (b-a)/n; for( i = 0; i < n; i = i + 1 ) { result = result + f(x);
FUOC XP04/90793/00018
x = x + dx; } /* for */ } /* if */ return result; } /* integral_f */ void main( void ) { double int a, b; n;
printf( "Integracin numrica de f(x).\n" ); printf( "Punto inicial del intervalo, a = ? " ); scanf( "%lf", &a ); printf( "Punto final del intervalo, b = ? " ); scanf( "%lf", &b ); printf( "Nmero de divisiones, n = ? " ); scanf( "%d", &n ); printf( "Resultado, integral(f)[%g,%g] = %g\n", a, b, integral_f( a, b, n ) ); /* printf */ } /* main */
El uso de las macros puede ayudar a la clarificacin de pequeas partes del cdigo mediante el uso de una sintaxis similar a la de las llamadas a las funciones.
103
ANOTACIONES
FUOC XP04/90793/00018
Software libre
De esta manera, determinadas operaciones simples pueden beneficirse de un nombre significativo en lugar de emplear unas construcciones en C que pudieran dificultar la comprensin de su intencionalidad.
Ejemplo
#define absoluto( x ) ( x < 0 ? x : x ) #define redondea( x ) ( (int) ( x + 0.5) ) #define trunca( x ) ( (int) x )
Hay que tener presente que el nombre de la macro y el parntesis izquierdo no pueden ir separados y que la continuacin de la lnea, caso de que el comando sea demasiado largo) se hace colocando una barra invertida justo antes del carcter de salto de lnea. Por otra parte, hay que advertir que las macros hacen una sustitucin de cada nombre de parmetro aparecido en la definicin por la parte del cdigo fuente que se indique como argumento. As pues: absoluto( 2*entero + 1 ) se sustituira por: ( 2*entero + 1 < 0 ? 2*entero + 1 : 2*entero + 1 ) con lo que no sera correcto en el caso de que fuera negativo.
Nota
En este caso, sera posible evitar el error si en la definicin se hubieran puesto parntesis alrededor del argumento.
ANOTACIONES
104
2.14. Resumen
La organizacin del cdigo fuente es esencial para confeccionar programas legibles que resulten fciles de mantener y de actualizar. Esto
FUOC XP04/90793/00018
es especialmente cierto para los programas de cdigo abierto, es decir, para el software libre. En esta unidad se han repasado los aspectos fundamentales que intervienen en un cdigo organizado. En esencia, la organizacin correcta del cdigo fuente de un programa depende tanto de las instrucciones como de los datos. Por este motivo, no slo se ha tratado de cmo organizar el programa sino que adems se ha visto cmo emplear estructuras de datos. La organizacin correcta del programa empieza por que ste tenga un flujo de ejecucin de sus instrucciones claro. Dado que el flujo de instrucciones ms simple es aqul en el que se ejecutan de forma secuencial segn aparecen en el cdigo, es fundamental que las instrucciones de control de flujo tengan un nico punto de entrada y un nico punto de salida. En este principio se basa el mtodo de la programacin estructurada. En este mtodo, slo hay dos tipos de instrucciones de control de flujo: las alternativas y las iterativas. Las instrucciones iterativas suponen otro reto en la determinacin del flujo de control, ya que es necesario determinar que la condicin por la que se detiene la iteracin se cumple alguna vez. Por este motivo, se han repasado los esquemas algortmicos para el tratamiento de secuencias de datos y se han visto pequeos programas que, adems de servir de ejemplo de programacin estructurada, son tiles para realizar operaciones de filtro de datos en tuberas (procesos encadenados). Para organizar correctamente el cdigo y hacer posible el tratamiento de informacin compleja es necesario recurrir a la estructuracin ma debe reflejar aquellas operaciones que se realizan en la informacin y no tanto lo que supone llevar a cabo los datos elementales que la componen. Por este motivo, no slo se ha explicado cmo declarar y emplear datos estructurados, sean stos de tipo homogneo o heterogneo, sino que tambin se ha detallado cmo definir nuevos tipos de datos a partir de los tipos de datos bsicos y estructurados. A estos nuevos tipos de datos se los llama tipos abstractos de datos pues son transparentes para el lenguaje de programacin.
105
ANOTACIONES
de los datos. En este aspecto, hay que tener presente que el progra-
FUOC XP04/90793/00018
Software libre
Al hablar de los datos tambin se ha tratado de los ficheros de flujo de bytes. Estas estructuras de datos homogneas se caracterizan por tener un nmero indefinido de elementos, por residir en memoria secundaria, es decir, en algn soporte de informacin externo y, finalmente, por requerir de funciones especficas para acceder a sus datos. As pues, se han comentado las funciones estndar en C para operar con este tipo de ficheros. Fundamentalmente, los programas que los usan implementan esquemas algortmicos de recorrido o de bsqueda en los que se incluye una inicializacin especfica para abrir los ficheros, una comprobacin de final de fichero para la condicin de iteracin, operaciones de lectura y escritura para el tratamiento de la secuencia de datos y, para acabar, una finalizacin que consiste, entre otras cosas, en cerrar los ficheros empleados. El ltimo apartado se ha dedicado a la programacin modular, que consiste en agrupar las secuencias de instrucciones en subprogramas que realicen una funcin concreta y susceptible de ser empleado ms de una vez en el mismo programa o en otros. As pues, se sustituye en el flujo de ejecucin todo el subprograma por una instruccin que se ocupar de ejecutar el subprograma correspondiente. Estos subprogramas se denominan funciones en C y a la instruccin que se ocupa de ejecutarlas, instruccin de llamada. Se ha visto cmo se lleva a cabo una llamada a una funcin y que, en este aspecto, lo ms importante es el paso de parmetros. El paso de parmetros consiste en transmitir a una funcin el conjunto de datos con los que deber realizar su tarea. Dado que la funcin puede necesitar devolver resultados que no se puedan almacenar en una variable simple, algunos de estos parmetros se emplean para pasar referencias a variables que tambin podrn contener valores de retorno. As pues, se ha analizado tambin toda la problemtica relacionada con el paso de parmetros por valor y por referencia.
ANOTACIONES
106
1) Haced un programa para determinar el nmero de dgitos necesarios para representar a un nmero entero dado. El algoritmo
FUOC XP04/90793/00018
consiste en hacer divisiones enteras por 10 del nmero hasta que el resultado sea un valor inferior a 10. 2) Haced un programa que determine a cada momento la posicin de un proyectil lanzado desde un mortero. Se deber mostrar la altura y la distancia a intervalos regulares de tiempo hasta que alcance el suelo. Para ello, se supondr que el suelo es llano y que se le proporcionan, como datos de entrada, el ngulo del can y el intervalo de tiempo en el que se mostrarn los datos de salida. Se supone que la velocidad de salida de los proyectiles es de 200 m/s.
Nota
Para ms detalles, se puede tener en cuenta que el tubo del mortero es de 1 m de longitud y que los ngulos de tiro varan entre 45 y 85 grados. En el siguiente esquema se resumen las distintas frmulas que son necesarias para resolver el problema:
Figura 4.
donde x0 e y0 es la posicin inicial (puede considerarse 0 para los dos), y es el ngulo en radianes ( radianes = 180). 3) Se desea calcular el capital final acumulado de un plan de pensiones sabiendo el capital inicial, la edad del asegurado (se supone que se jubilar a los 65) y las aportaciones y los porcentajes de inters rendidos de cada ao. (Se supone que las aportaciones son de carcter anual.)
107
ANOTACIONES
FUOC XP04/90793/00018
Software libre
4) Programad un filtro que calcule la media, el mximo y el mnimo de una serie de nmeros reales de entrada. 5) Implementad los filtros del ltimo ejemplo del apartado 2.4.2. Filtros y tuberas; es decir: calculad los importes de una secuencia de datos { cdigo de artculo, precio, cantidad } que genere otra secuencia de datos { cdigo de artculo, importe } y realizad, posteriormente, la suma de los importes de esta segunda secuencia de datos. 6) Haced un programa que calcule la desviacin tpica que tienen los datos de ocupacin de un aparcamiento pblico a lo largo de las 24 horas de un da. Habr, por tanto, 24 datos de entrada. Estos datos se refieren al porcentaje de ocupacin (nmero de plazas ocupadas con relacin al nmero total de plazas) calculado al final de cada hora. Tambin se deber indicar las horas del da que tengan un porcentaje de ocupacin inferior a la media menos dos veces la desviacin tpica, y las que lo tengan superior a la media ms dos veces esta desviacin.
Nota
La desviacin tpica se calcula como la raz cuadrada de la suma de los cuadrados de las diferencias entre los datos y la media, dividida por el nmero de muestras.
7) Averiguad si la letra de un NIF dado es o no correcta. El procedimiento de su clculo consiste en realizar el mdulo 23 del nmero correspondiente. El resultado da una posicin en una secuencia de letras (TRWAGMYFPDXBNJZSQVHLCKE). La letra situada en dicha posicin ser la letra del NIF.
Nota
ANOTACIONES
108
Para poder efectuar la comparacin entre letras, es conveniente convertir la que proporcione el usuario a mayscula. Para ello se debe emplear toupper(), cuya declaracin est en ctype.h y que devuelve el carcter correspondiente a la letra mayscula de la que ha recibido como argumento. Si no es un carcter alfabtico o se trata de una letra ya mayscula, devuelve el mismo carcter.
FUOC XP04/90793/00018
8) Haced un programa que calcule el mnimo nmero de monedas necesario para devolver el cambio sabiendo el importe total a cobrar y la cantidad recibida como pago. La moneda de importe mximo es la de 2 euros y la ms pequea, de 1 cntimo.
Nota
Es conveniente tener un vector con los valores de las monedas ordenados por valor. 9) Resumid la actividad habida en un terminal de venta por artculos. El programa debe mostrar, para cada cdigo de artculo, el nmero de unidades vendidas. Para ello, contar con un fichero generado por el terminal que consta de pares de nmeros enteros: el primero indica el cdigo del artculo y el segundo, la cantidad vendida. En caso de devolucin, la cantidad aparecer como un valor negativo. Se sabe, adems, que no habr nunca ms de 100 cdigos de artculos diferentes.
Nota
Es conveniente disponer de un vector de 100 tuplas para almacenar la informacin de su cdigo y las unidades vendidas correspondientes. Como no se sabe cuntas tuplas sern necesarias, tngase en cuenta que se deber disponer de una variable que indique los que se hayan almacenado en el vector (de 0 al nmero de cdigos distintos -1). 10) Reprogramad el ejercicio anterior de manera que las operaciones que afecten al vector de datos se lleven a cabo en el cuerpo de funciones especficas.
Nota
Definid un tipo de dato nuevo que contenga la informacin de los productos. Se sugiere, por ejemplo, el que se muestra a continuacin. typedef struct productos_s { unsigned int n; /* Nmero de productos. */ venta_t producto[MAX_PRODUCTOS]; } productos_t;
109
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Recurdese que habr de pasar la variable de este tipo por referencia. 11) Buscad una palabra en un fichero de texto. Para ello, realizad un programa que pida tanto el texto de la palabra como el nombre del fichero. El resultado deber ser un listado de todas las lneas en que se encuentre dicha palabra. Se supondr que una palabra es una secuencia de caracteres alfanumricos. Es conveniente emplear la macro isalnum(), que se encuentra declarada en ctype.h, para determinar si un carcter es o no alfanumrico.
Nota
2.15.1. Solucionario
1) /* ---------------------------------------------------- */ /* Fichero: ndigitos.c */ /* ---------------------------------------------------- */ #include <stdio.h>
ANOTACIONES
main() { unsigned int numero; unsigned int digitos; printf( "El nmero de dgitos para representar: " ); scanf( "%u", &numero ); digitos = 1;
110
FUOC XP04/90793/00018
while( numero > 10 ) { numero = numero / 10; digitos = digitos + 1; } /* while */ printf( "... es %u.\n", digitos ); } /* main */ 2) /* ---------------------------------------------------/* Fichero: mortero.c /* ---------------------------------------------------#include <stdio.h> #include <math.h> #define V_INICIAL 200.00 #define L_TUBO #define G #define PI main() { double angulo, inc_tiempo, t; double v_x, v_y; /* Velocidades horizontal y vertical. double x0, x, y0, y; printf( "Tiro con mortero.\n " ); printf( "ngulo de tiro [grados sexagesimales] =? " ); scanf( "%lf", &angulo ); angulo = angulo * PI / 180.0; printf( "Ciclo de muestra [segundos] =? " ); scanf( "%lf", &inc_tiempo ); y0 = L_TUBO * sin( angulo ); t = 0.0; v_x = V_INICIAL * cos( angulo ); v_y = V_INICIAL * sin( angulo ); do { x = x0 + v_x * t; y = y0 + v_y * t - 0.5 * G * t * t; printf( "%6.2lf s: ( %6.2lf, %6.2lf )\n", t, x, y );
111
*/ */ */
/* m/s /* m /* m/(s*s)
*/ */ */
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
t = t + inc_tiempo; } while ( y > 0.0 ); } /* main */ 3) /* ---------------------------------------------------/* Fichero: pensiones.c /* ---------------------------------------------------#include <stdio.h> #define EDAD_JUBILACION 65 main() { unsigned int edad; float capital, interes, aportacion; */ */ */
printf( "Plan de pensiones.\n " ); printf( "Edad =? " ); scanf( "%u", &edad ); printf( "Aportacin inicial =? " ); scanf( "%f", &capital ); while( edad < EDAD_JUBILACION ) { printf( "Inters rendido [en %%] =? " ); scanf( "%f", &interes ); capital = capital*( 1.0 + interes/100.0 ); printf( "Nueva aportacin =? " ); scanf( "%f", &aportacion ); capital = capital + aportacion; edad = edad + 1; printf( "Capital acumulado a %u aos: %.2f\n", edad, capital ); /* printf */ } /* while */ } /* main */ 4) /* ---------------------------------------------------/* Fichero: estadistica.c /* ---------------------------------------------------#include <stdio.h>
112
ANOTACIONES
*/ */ */
FUOC XP04/90793/00018
unsigned int cantidad, leido_ok; printf( "Mnimo, media y mximo.\n " ); leido_ok = scanf( "%lf", &numero ); if( leido_ok == 1 ) { cantidad = 1; suma = numero; minimo = numero; maximo = numero; leido_ok = scanf( "%lf", &numero ); while( leido_ok == 1 ) { suma = suma + numero; if( numero > maximo ) maximo = numero; if( numero < minimo ) minimo = numero; cantidad = cantidad + 1; leido_ok = scanf( "%lf", &numero ); } /* while */ printf( "Mnimo = %g\n", minimo ); printf( "Media = %g\n", suma / (double) cantidad ); printf( "Mximo = %g\n", maximo ); } else { printf( "Entrada vaca.\n" ); } /* if */ } /* main */ 5) /* ---------------------------------------------------/* Fichero: calc_importes.c /* ---------------------------------------------------#include <stdio.h> main() { unsigned int leidos_ok, codigo; int float cantidad; precio, importe; */ */
ANOTACIONES
*/
FUOC XP04/90793/00018
Software libre
&codigo, &precio, &cantidad ); /* scanf */ while( leidos_ok == 3 ) { importe = (float) cantidad * precio; printf( "%u %.2f\n", codigo, importe ); leidos_ok = scanf( "%u%f%i", &codigo, &precio, &cantidad ); /* scanf */ } /* while */ } /* main */ /* ---------------------------------------------------/* Fichero: totaliza.c /* ---------------------------------------------------#include <stdio.h> main() { unsigned int leidos_ok, codigo; int float double cantidad; importe; total = 0.0; */ */ */
leidos_ok = scanf( "%u%f", &codigo, &importe ); while( leidos_ok == 2 ) { total = total + importe; leidos_ok = scanf( "%u%f", &codigo, &importe ); } /* while */ printf( "%.2lf\n", total ); } /* main */ 6) /* ---------------------------------------------------/* Fichero: ocupa_pk.c /* ---------------------------------------------------#include <stdio.h> #include <math.h> main() { unsigned int hora;
114
*/ */ */
ANOTACIONES
FUOC XP04/90793/00018
printf( "Estadstica de ocupacin diaria:\n" ); /* Lectura de ratios de ocupacin por horas: for( hora = 0; hora < 24; hora = hora + 1 ) { printf( "Porcentaje de ocupacin a las %02u horas =? ", hora ); /* printf */ scanf( "%f", &porcentaje ); ratio_acum[hora] = porcentaje; } /* for */ /* Clculo de la media:
media = 0.0;
*/
*/
for( hora = 0; hora < 24; hora = hora + 1 ) { media = media + ratio_acum[hora]; } /* for */ media = media / 24.0; /* Clculo de la desviacin tpica: desv = 0.0; for( hora = 0; hora < 24; hora = hora + 1 ) { dif = ratio_acum[ hora ] - media; desv = desv + dif * dif; } /* for */ desv = sqrt( desv ) / 24.0; /* Impresin de los resultados: printf( "Desviacin tpica: %.2lf\n", desv ); printf( "Horas con porcentaje muy bajo: ", desv ); for( hora = 0; hora < 24; hora = hora + 1 ) { if( ratio_acum[ hora ] < dif ) printf( "%u ", hora ); } /* for */ printf( "\nHoras con porcentaje muy alto: ", desv ); for( hora = 0; hora < 24; hora = hora + 1 ) { dif = media + 2*desv; if( ratio_acum[ hora ] > dif ) printf( "%u ", hora ); } /* for */ printf( "\nFin.\n" );
115
*/
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
} /* main */ 7) /* ---------------------------------------------------/* Fichero: valida_nif.c /* ---------------------------------------------------#include <stdio.h> #include <ctype.h> main() { char char char NIF[ BUFSIZ ]; letra; codigo[] = "TRWAGMYFPDXBNJZSQVHLCKE" ; unsigned int DNI; */ */ */
printf( "Dime el NIF (DNI-letra): " ); gets( NIF ); sscanf( NIF, "%u", &DNI ); DNI = DNI % 23; letra = NIF[ strlen(NIF)-1 ]; letra = toupper( letra ); if( letra == codigo[ DNI ] ) { printf( "NIF vlido.\n" ); } else { printf( "Letra %c no vlida!\n", letra ); } /* if */ } /* main */ 8) /* ---------------------------------------------------/* Fichero: cambio.c /* ---------------------------------------------------*/ */ */
ANOTACIONES
FUOC XP04/90793/00018
int int
printf( "Importe : " ); scanf( "%lf", &importe ); printf( "Pagado : " ); scanf( "%lf", &pagado ); cambio_cents = (int) 100.00 * ( pagado - importe ); if( cambio_cents < 0 ) { printf( "PAGO INSUFICIENTE\n"); } else { printf( "A devolver : %.2f\n", (float) cambio_cents / 100.0 ); /* printf */ i = NUM_MONEDAS - 1; while( cambio_cents > 0 ) { nmonedas = cambio_cents / cents[i] ; if( nmonedas > 0 ) { cambio_cents = cambio_cents - cents[i]*nmonedas; printf( "%u monedas de %.2f euros.\n", nmonedas, (float) cents[i] / 100.0 ); /* printf */ } /* if */ i = i - 1; } /* while */ } /* if */ } /* main */ 9) /* ------------------------------------------------------- */ /* ------------------------------------------------------- */ #include <stdio.h> typedef struct venta_s { unsigned int codigo; int cantidad; } venta_t;
117
ANOTACIONES
/* Fichero: resumen_tpv.c
*/
FUOC XP04/90793/00018
Software libre
#define MAX_PRODUCTOS 100 main() { FILE char int venta_t *entrada; nombre[BUFSIZ]; num_prod, i; venta, producto[MAX_PRODUCTOS];
printf( "Resumen del da en TPV.\n" ); printf( "Fichero: "); gets( nombre ); entrada = fopen( nombre, "rt" ); if( entrada != NULL ) { num_prod = 0; leidos_ok = fscanf( entrada, "%u%i", &(venta.codigo), &(venta.cantidad) ); /* fscanf */ while( leidos_ok == 2 ) { /* Bsqueda del cdigo en la tabla: i = 0; while( ( i < num_prod ) && ( producto[i].codigo != venta.codigo ) ) { i = i + 1; } /* while */ if( i < num_prod ) { /* Cdigo encontrado: producto[i].cantidad = producto[i].cantidad + venta.cantidad; } else { /* Cdigo no encontrado nuevo producto: */ */ */
ANOTACIONES
producto[num_prod].codigo = venta.codigo; producto[num_prod].cantidad = venta.cantidad; num_prod = num_prod + 1; } /* if */ /* Lectura de siguiente venta: leidos_ok = fscanf( entrada, "%u%i", &(venta.codigo), &(venta.cantidad) ); /* fscanf */ } /* while */
118
*/
FUOC XP04/90793/00018
printf( "Resumen:\n" ); for( i=0; i<num_prod; i = i + 1 ) { printf( "%u : %i\n", producto[i].codigo, producto[i].cantidad ); /* printf */ } /* for */ } else { printf( "No puedo abrir %s!\n", nombre ); } /* if */ } /* main */ 10) /* ... */ int busca( unsigned int codigo, productos_t *pref ) { int i; i = 0; while( (i < (*pref).n ) && ( (*pref).producto[i].codigo != codigo ) ) { i = i + 1; } /* while */ if( i == (*pref).n ) i = -1; return i; } /* busca */ void anyade( venta_t venta, productos_t *pref ) { unsigned int n;
if( n < MAX_PRODUCTOS ) { (*pref).producto[n].codigo = venta.codigo; (*pref).producto[n].cantidad = venta.cantidad; (*pref).n = n + 1; } /* if */ } /* anyade */ void actualiza( venta_t venta, unsigned int posicion,
119
productos_t *pref )
ANOTACIONES
n = (*pref).n;
FUOC XP04/90793/00018
Software libre
{ (*pref).producto[posicion].cantidad = (*pref).producto[posicion].cantidad + venta.cantidad; } /* actualiza */ void muestra( productos_t *pref ) { int i; for( i=0; i<(*pref).n; i = i + 1 ) { printf( "%u : %i\n", (*pref).producto[i].codigo, (*pref).producto[i].cantidad ); /* printf */ } /* for */ } /* muestra */ void main( void ) { FILE char int venta_t productos_t *entrada; nombre[BUFSIZ]; i; venta; productos;
printf( "Resumen del da en TPV.\n" ); printf( "Fichero: "); gets( nombre ); entrada = fopen( nombre, "rt" ); if( entrada != NULL ) { productos.n = 0;
ANOTACIONES
leidos_ok = fscanf( entrada, "%u%i", &(venta.codigo), &(venta.cantidad) ); /* scanf */ while( leidos_ok == 2 ) { i = busca( venta.codigo, &productos ); if( i < 0 ) anyade( venta, &productos ); else actualiza( venta, i, &productos ); leidos_ok = fscanf( entrada, "%u%i", &(venta.codigo), &(venta.cantidad)
120
FUOC XP04/90793/00018
); /* scanf */ } /* while */ printf( "Resumen:\n" ); muestra( &productos ); } else { printf( "No puedo abrir %s!\n", nombre ); } /* if */ } /* main */ 11) /* ---------------------------------------------------/* Fichero: busca.c /* ---------------------------------------------------#include <stdio.h> #include <ctype.h> typedef enum bool_e { FALSE = 0, TRUE = 1 } bool; #define LONG_PALABRA 81 typedef char palabra_t[LONG_PALABRA]; bool palabras_iguales( palabra_t p1, palabra_t p2 ) { int i = 0; while( (p1[i]!='\0') && (p2[i]!='\0') && (p1[i]==p2[i])) { i = i + 1; return p1[i]==p2[i]; } /* palabras_iguales */ unsigned int lee_palabra( palabra_t p, FILE *entrada ) { unsigned int i, nlin; bool char termino; caracter;
121
*/ */ */
ANOTACIONES
} /* while */
FUOC XP04/90793/00018
Software libre
i = 0; nlin = 0; termino = FALSE; caracter = fgetc( entrada ); while( !feof( entrada ) && !termino ) { if( caracter == '\n' ) nlin = nlin + 1; if( isalnum( caracter ) ) { p[i] = caracter; i = i + 1; caracter = fgetc( entrada ); } else { if( i > 0 ) { termino = TRUE; } else { caracter = fgetc( entrada ); } /* if */ } /* if */ } /* while */ p[i] = '\0'; return nlin; } /* lee_palabra */ void primera_palabra( palabra_t palabra, char *frase ) { int i, j; i = 0; while( frase[i]!='\0' && isspace( frase[i] ) ) { i = i + 1; } /* while */ j = 0; while( frase[i]!='\0' && !isspace( frase[i] ) ) { palabra[j] = frase[i]; i = i + 1; j = j + 1; } /* while */ palabra[j] = '\0'; } /* primera_palabra */ void main( void )
122
ANOTACIONES
FUOC XP04/90793/00018
unsigned int numlin; printf( "Busca palabras.\n" ); printf( "Fichero: "); gets( nombre ); entrada = fopen( nombre, "rt" ); if( entrada != NULL ) { printf( "Palabra: "); gets( nombre ); primera_palabra( palabra, nombre ); printf( "Buscando %s en fichero...\n", palabra ); numlin = 1; while( !feof( entrada ) ) { numlin = numlin + lee_palabra( palabra2, entrada ); if( palabras_iguales( palabra, palabra2 ) ) { printf( "... lnea %lu\n", numlin ); } /* if */ } /* while */ } else { printf( "No puedo abrir %s!\n", nombre ); } /* if */ } /* main */
123
ANOTACIONES
FUOC XP04/90793/00018
3.1. Introduccin
La programacin de una aplicacin informtica suele dar como resultado un cdigo fuente de tamao considerable. Aun aplicando tcnicas de programacin modular y apoyndose en funciones estndar de biblioteca, resulta complejo organizar eficazmente el cdigo. Mucho ms si se tiene en cuenta que, habitualmente, se trata de un cdigo desarrollado por un equipo de programadores. Por otra parte, hay que tener presente que mucha de la informacin con que se trabaja no tiene un tamao predefinido ni, la mayora de las veces, se presenta en la forma ms adecuada para ser procesada. Esto comporta tener que reestructurar los datos de manera que los algoritmos que los tratan puedan ser ms eficientes. Como ltimo elemento a considerar, pero no por ello menos importante, se debe tener en cuenta que la mayora de aplicaciones estn constituidas por ms de un programa. Por lo tanto, resulta conveniente organizarlos aprovechando las facilidades que para ello nos ofrece tanto el conjunto de herramientas de desarrollo de software como el sistema operativo en el que se ejecutar. En esta unidad se tratar de diversos aspectos que alivian los problemas antes mencionados. As pues, desde el punto de vista de un programa en el contexto de una aplicacin que lo contenga (o del que sea el nico), es importante la adaptacin al tamao real de los datos a procesar y su disposicin en estructuras dinmicas, la organizacin del cdigo para que refleje el algoritmo que implementa y, por ltimo, contar con el soporte del sistema operativo para la coordinacin con otros programas dentro y fuera de la misma aplicacin y, tambin, para la interaccin con el usuario.
125
ANOTACIONES
FUOC XP04/90793/00018
Software libre
La representacin de informacin en estructuras dinmicas de datos permite ajustar las necesidades de memoria del programa al mnimo requerido para la resolucin del problema y, por otra parte, representar internamente la informacin de manera que su procesamiento sea ms eficiente. Una estructura dinmica de datos no es ms que una coleccin de datos cuya relacin no est establecida a priori y que puede modificarse durante la ejecucin del programa. Esto, por ejemplo, no es factible mediante un vector, pues los datos que contienen estn relacionados por su posicin dentro de l y, adems, su tamao debe de estar predefinido en el programa. En el primer apartado se tratar de las variables dinmicas y de su empleo como contenedores de datos que responden a las exigencias de adaptabilidad a la informacin a representar y acomodamiento respecto del algoritmo que debe tratar con ellas. El cdigo fuente de una aplicacin, sea sta un conjunto de programas o uno nico, debe organizarse de manera que se mantengan las caractersticas de un buen cdigo (inteligible, de fcil mantenimiento y coste de ejecucin ptimo). Para ello, no slo hay que emplear una programacin estructurada y modular, sino que hay que distribuirlo en diversos ficheros de manera que sea ms manejable y, por otra parte, se mantenga su legibilidad. En el apartado dedicado al diseo descendente de programas se tratar, precisamente, de los aspectos que afectan a la programacin ms all de la programacin modular. Se tratar de aspectos relacionados con la divisin del programa en trminos algortmicos y de la agrupacin de conjuntos de funciones fuertemente relacionadas. Dado que el cdigo fuente se reparte en diversos ficheros, se tratar tambin de aquellos aspectos relacionados con su compilacin y, en especial, de la herramienta make. Dada la complejidad del cdigo fuente de cualquier aplicacin, resulta conveniente emplear todas aquellas funciones estndar que aporta el sistema operativo. Con ello se consigue, adems de reducir su complejidad al dejar determinadas tareas como simples instrucciones de llamada, que resulte ms independiente de mquina. As pues, en el ltimo captulo se trata de la relacin entre los programas
126
ANOTACIONES
Nota
Sobre este tema se vio un ejemplo al hablar de las tuberas en la unidad anterior.
FUOC XP04/90793/00018
y los sistemas operativos, de manera que sea posible comunicar los unos con los otros y, adems, los programas entre s. Para finalizar, se tratar, aunque brevemente, de cmo distribuir la ejecucin de un programa en diversos flujos (o hilos) de ejecucin. Esto es, de cmo realizar una programacin concurrente de manera que varias tareas puedan resolverse en un mismo intervalo de tiempo. Esta unidad pretende mostrar aquellos aspectos de la programacin ms involucrados con el nivel ms alto de abstraccin de los algoritmos. As pues, al finalizar su estudio, el lector debera alcanzar los objetivos siguientes: 1) Emplear adecuadamente variables dinmicas en un programa. 2) Conocer las estructuras dinmicas de datos y, en especial, las listas y sus aplicaciones. 3) Entender el principio del diseo descendente de programas. 4) Ser capaz de desarrollar una biblioteca de funciones. 5) Tener conocimientos bsicos para la relacin del programa con el sistema operativo. 6) Asimilar el concepto de hilo de ejecucin y los rudimentos de la programacin concurrente.
Un programa que realice anlisis sintcticos (que, por otra parte, podra formar parte de una aplicacin de tratamiento de textos) deber procesar frases de tama127
ANOTACIONES
FUOC XP04/90793/00018
Software libre
os distintos en nmero y categora de palabras. Adems, las palabras pueden estar relacionadas de muy diferentes maneras. Por ejemplo, los adverbios lo estn con los verbos y ambos se diferencian de las que forman el sintagma nominal, entre otras. Las relaciones existentes entre los elementos que forman una informacin determinada se pueden representar mediante datos adicionales que las reflejen y nos permitan, una vez calculados, realizar un algoritmo ms eficiente. As pues, en el ejemplo anterior resultara mucho mejor efectuar los anlisis sintcticos necesarios a partir del rbol sintctico que no de la simple sucesin de palabras de la frase a tratar. El tamao de la informacin, es decir, el nmero de datos que la forman, afecta significativamente al rendimiento de un programa. Ms an, el uso de variables estticas para su almacenamiento implica o bien conocer a priori su tamao mximo, o bien limitar la capacidad de tratamiento a slo una porcin de la informacin. Adems, aunque sea posible determinar el tamao mximo, se puede producir un despilfarro de memoria innecesario cuando la informacin a tratar ocupe mucho menos. Las variables estticas son aquellas que se declaran en el programa de manera que disponen de un espacio reservado de memoria durante toda la ejecucin del programa. En C, slo son realmente estticas las variables globales. Las variables locales, en cambio, son automticas porque se les reserva espacio slo durante la ejecucin de una parte del programa
ANOTACIONES
128
y luego son destruidas de forma automtica. Aun as, son variables de carcter esttico respecto del mbito en el que se emplean, pues tiene un espacio de memoria reservado y limitado. Las variables dinmicas, sin embargo, pueden crearse y destruirse durante la ejecucin de un programa y tienen un carcter global; es decir, son visibles desde cualquier punto del programa. Dado que es posible crear un nmero indeterminado de estas variables, permiten ajustarse al tamao requerido para representar la informacin
FUOC XP04/90793/00018
de un problema particular sin desperdiciar espacio de memoria alguno. Para poder crear las variables dinmicas durante la ejecucin del programa, es necesario contar con operaciones que lo permitan. Por otra parte, las variables dinmicas carecen de nombre y, por tanto, su nica identificacin se realiza mediante la direccin de la primera posicin de memoria en la que residen. Por ello, es necesario contar con datos que puedan contener referencias a variables dinmicas de manera que permitan utilizarlas. Como sus referencias son direcciones de memoria, el tipo de estos datos ser, precisamente, el de direccin de memoria o apuntador, pues su valor es el indicador de dnde se encuentra la variable referenciada. En los siguientes apartados se tratar, en definitiva, de todo aquello que atae a las variables dinmicas y a las estructuras de datos que con ellas se pueden construir.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En ref_entero el contenido de la direccin de memoria almacenada es un dato de tipo entero. En cadena el contenido de la direccin de memoria almacenada es un carcter. En apuntador el contenido de la direccin de memoria almacenada es de tipo otro_t. En ap_nodo, el contenido de la direccin de memoria almacenada es de tipo nodo_t.
La referencia a las variables sin el operador de indireccin es, simplemente, la direccin de memoria que contienen.
El tipo de apuntador viene determinado por el tipo de dato del que tiene la direccin. Por este motivo, por ejemplo, se dir que ref_entero es un apuntador de tipo entero, o bien, que se trata de un apuntador a un tipo de datos entero. Tambin es posible declarar apuntadores de apuntadores, etc. En el ejemplo siguiente se puede observar que, para hacer referencia al valor apuntado por la direccin contenida en un apuntador, hay que emplear el operador de contenido de la direccin de:
*/ */ */
int **pptr; /* un apuntador a un apuntador de entero. /* ... */ a = b = 5; ptr = &a; *ptr = 20; /* a == 20 ptr = &b; pptr = &ptr; **pptr = 40;/* b == 40 /* ... */
130
ANOTACIONES
*/
*/
FUOC XP04/90793/00018
En la figura siguiente se puede apreciar cmo el programa anterior va modificando las variables a medida que se va ejecutando. Cada columna vertical representa las modificaciones llevadas a cabo en el entorno por una instruccin. Inicialmente (la columna de ms a la izquierda), se desconoce el contenido de las variables:
Figura 5.
En el caso particular de las tuplas, cabe recordar que el acceso tambin se hace mediante el operador de indireccin aplicado a los apuntadores que contienen sus direcciones iniciales. El acceso a sus campos no cambia: struct alumno_s { cadena_t nombre; unsigned short dni, nota; } alumno; struct alumno_s *ref_alumno; /* ... */ alumno.nota = *ref_alumno.nota; /* ... */ Para que quede claro el acceso a un campo de una tupla cuya direc-
/* ... */ alumno.nota = (*ref_alumno).nota; /* ... */ Si se quiere enfatizar la idea del apuntador, es posible emplear el operador de indireccin para tuplos que se asemeja a una flecha que apunta a la tupla correspondiente:
131
ANOTACIONES
FUOC XP04/90793/00018
Software libre
/* ... */ alumno.nota = ref_alumnonota; /* ... */ En los ejemplos anteriores, todas las variables eran de carcter esttico o automtico, pero su uso primordial es como referencia de las variables dinmicas.
vector_real == &(vector_real[0])
ANOTACIONES
132
Es muy importante no modificar el contenido del identificador, pues se podra perder la referencia a todo el vector!
Por otra parte, los apuntadores se manejan con una aritmtica especial: se permite la suma y la resta de apuntadores de un mismo tipo
FUOC XP04/90793/00018
y enteros. En el ltimo caso, las sumas y restas con enteros son, en realidad, sumas y restas con los mltiplos de los enteros, que se multiplican por el tamao en bytes de aquello a lo que apuntan. Sumar o restar contenidos de apuntadores resulta algo poco habitual. Lo ms frecuente es incrementarlos o decrementarlos para que apunten a algn elemento posterior o anterior, respectivamente. En el ejemplo siguiente se puede intuir el porqu de esta aritmtica especial. Con ella, se libera al programador de tener que pensar cuntos bytes ocupa cada tipo de dato: /* ... */ int vector[DIMENSION], *ref_entero; /* ... */ ref_entero = vector; ref_entero = ref_entero + 3; *ref_entero = 15; /* Es equivalente a vector[3] = 15 */ /* ... */ En todo caso, existe el operador sizeof (tamao de) que devuelve como resultado el tamao en bytes del tipo de datos de su argumento. As pues, el caso siguiente es, en realidad, un incremento de ref_entero de tal manera que se aade 3*sizeof(int) a su contenido inicial.: /* ... */ ref_entero = ref_entero + 3; /* ... */ Por tanto, en el caso de los vectores, se cumple que: vector[i] == *(vector+i) Es decir, que el elemento que se encuentra en la posicin i-sima es el que se encuentra en la posicin de memoria que resulta de sumar i*sizeof(*vector) a su direccin inicial, indicada en vector.
Nota
El operador sizeof se aplica al elemento apuntado por vector, puesto que, de aplicarse a vector, se obtendra el tamao en bytes que ocupa un apuntador.
133
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En el siguiente ejemplo, se podr observar con ms detalle la relacin entre apuntadores y vectores. El programa listado a continuacin toma como entrada un nombre completo y lo separa en nombre y apellidos: #include <stdio.h> #include <ctype.h> typedef char frase_t[256]; char *copia_palabra( char *frase, char *palabra ) /* Copia la primera palabra de la frase en palabra. /* frase : apuntador a un vector de caracteres. /* palabra : apuntador a un vector de caracteres. /* Devuelve la direccin del ltimo carcter ledo /* en la frase. { while( *frase!='\0' && isspace( *frase ) ) { frase = frase + 1; } /* while */ while( *frase!='\0' && !isspace( *frase ) ) { *palabra = *frase; palabra = palabra + 1; frase = frase + 1; } /* while */ *palabra = '\0'; return frase; } /* palabra */ main( void ) { frase_t nombre_completo, nombre, apellido1, apellido2; char *posicion; printf( "Nombre y apellidos? " ); gets( nombre_completo ); posicion = copia_palabra( nombre_completo, nombre ); posicion = copia_palabra( posicion, apellido1 ); posicion = copia_palabra( posicion, apellido2 ); printf( "Gracias por su amabilidad, Sr/a. %s.\n", apellido1 ); /* printf */ } /* main */
134
*/ */ */ */ */
ANOTACIONES
FUOC XP04/90793/00018
Nota
Ms adelante, cuando se trate de las cadenas de caracteres, se insistir de nuevo en la relacin entre apuntadores y vectores.
Nota
El parntesis que encierra al nombre del apuntador y al asterisco que le precede es necesario para que no se confunda con la declaracin de una funcin cuyo valor devuelto sea un apuntador a un nmero real.
Sirva como ejemplo un programa para la integracin numrica de un determinado conjunto de funciones. Este programa es parecido al que ya se vio en la unidad anterior con la modificacin de que la ferencia de la funcin de la que hay que calcular la integral: /* Programa: integrales.c */ #include <stdio.h> #include <math.h> double f0( double x ) { return x/2.0; double f1( double x ) { return 1+2*log(x); } }
ANOTACIONES
FUOC XP04/90793/00018
Software libre
result = 0.0; if( (a < b) && (n > 0) ) { x = a; dx = (b-a)/n; for( i = 0; i < n; i = i + 1 ) { result = result + (*fref)(x); x = x + dx; } /* for */ } /* if */ return result; } /* integral_f */ void main( void ) { double int double a, b; n, fnum; (*fref)( double x );
printf( "Integracin numrica de f(x).\n" ); printf( "Punto inicial del intervalo, a = ? " ); scanf( "%lf", &a ); printf( "Punto final del intervalo, b = ? " ); scanf( "%lf", &b ); printf( "Nmero de divisiones, n = ? " ); scanf( "%d", &n ); printf( "Nmero de funcin, fnum = ?"); scanf( "%d", &fnum ); switch( fnum ) { case 1 : fref = f1; break; case 2 : fref = f2; break; default: fref = f0; } /* switch */ printf( "Resultado, integral(f)[%g,%g] = %g\n", a, b, integral_f( a, b, n, fref ) ); /* printf */ } /* main */
136
ANOTACIONES
FUOC XP04/90793/00018
Como se puede observar, el programa principal podra perfectamente sustituir las asignaciones de referencias de funciones por llamadas a las mismas. Con esto, el programa sera mucho ms claro. Aun as, como se ver ms adelante, esto permite que la funcin que realiza la integracin numrica pueda residir en alguna biblioteca y ser empleada por cualquier programa.
Nota
El tipo de datos size_t es, simplemente, un tipo de entero sin signo al que se lo ha denominado as porque representa tamaos.
ANOTACIONES
/* ... */
FUOC XP04/90793/00018
Software libre
conveniente emplear siempre el operador sizeof. As, el ejemplo anterior tendra que haber sido escrito de la forma siguiente: /* ... */ apuntador = (char *)malloc( 31 * sizeof(char) ); /* ... */
sizeof devuelve el nmero de bytes necesario para contener el tipo de datos de la variable o del tipo de datos que tiene como argumento, excepto en el caso de las matrices, en que devuelve el mismo valor que para un apuntador.
A veces, es necesario ajustar el tamao reservado para una variable dinmica (sobre todo, en el caso de las de tipo vector), bien porque falta espacio para nuevos datos, bien porque se desaprovecha gran parte del rea de memoria. Para tal fin, es posible emplear la funcin de relocalizacin de una variable: void * realloc( void *apuntador, size_t nuevo_tamao ); El comportamiento de la funcin anterior es similar a la de malloc: devuelve NULL si no ha podido encontrar un nuevo emplazamiento para la variable con el tamao indicado. Cuando una variable dinmica ya no es necesaria, se tiene que destruir; es decir, liberar el espacio que ocupa para que otras variables dinmicas lo puedan emplear. Para ello hay que emplear la funcin free: /* ... */ free( apuntador ); /* ... */ Como sea que esta funcin slo libera el espacio ocupado pero no modifica en absoluto el contenido del apuntador, resulta que ste an tiene la referencia a la variable dinmica (su direccin) y, por
138
ANOTACIONES
FUOC XP04/90793/00018
tanto, existe la posibilidad de acceder a una variable inexistente. Para evitarlo, es muy conveniente asignar el apuntador a NULL: /* ... */ free( apuntador ); apuntador = NULL; /* ... */ De esta manera, cualquier referencia errnea a la variable dinmica destruida causar error; que podr ser fcilmente corregido.
Las estructuras de datos estticas son, por definicin, lo opuesto a las estructuras de datos dinmicas. Es desu interrelacin no varan en toda la ejecucin del programa correspondiente. Por ejemplo, un vector siempre tendr una longitud determinada y todos los elementos a excepcin del primero y del ltimo tienen un elemento precedente y otro siguiente.
En caso de almacenar las estructuras de datos dinmicas en estructuras estticas, es recomendable comprobar si se conoce el n139
ANOTACIONES
FUOC XP04/90793/00018
Software libre
mero mximo y la cantidad media de datos que puedan tener. Si ambos valores son parecidos, se puede emplear una variable de vector esttica o automtica. Si son muy diferentes, o bien se desconocen, es conveniente ajustar el tamao del vector al nmero de elementos que haya en un momento determinado en la estructura de datos y, por lo tanto, almacenar el vector en una variable de carcter dinmico. Las estructuras de datos dinmicas se almacenan, por lo comn, empleando variables dinmicas. As pues, puede verse una estructura de datos dinmica como una coleccin de variables dinmicas cuya relacin queda establecida mediante apuntadores. De esta manera, es posible modificar fcilmente tanto el nmero de datos de la estructura (creando o destruyendo las variables que los contienen) como la propia estructura, cambiando las direcciones contenidas en los apuntadores de sus elementos. En este caso, es habitual que los elementos sean tuplos y que se denominen nodos. En los apartados siguientes se vern los dos casos, es decir, estructuras de datos dinmicas almacenadas en estructuras de datos estticas y como colecciones de variables dinmicas. En el primer caso, se tratar de las cadenas de caracteres, pues son, de largo, las estructuras de datos dinmicas ms empleadas. En el segundo, de las listas y de sus aplicaciones.
ANOTACIONES
FUOC XP04/90793/00018
Por lo tanto, habra que inicializarla de la siguiente manera: char cadena[20] = { 'H', 'o', 'l', 'a', '\0' } ; Las declaraciones de cadenas de caracteres inicializadas mediante texto implican que la marca de final se aada siempre. Por ello, la declaracin anterior es equivalente a: char cadena[20] = "Hola" ; Aunque el formato de representacin de cadenas de caracteres sea estndar en C, no se dispone de instrucciones ni de operadores que trabajen con cadenas: no es posible hacer asignaciones ni comparaciones de cadenas, es necesario recurrir a las funciones estndar (declaradas en string.h) para el manejo de cadenas:
int
strlen
( char *cadena ); ( char *destino, char *fuente ); ( char *destino, char *fuente ); ( char *origen ); ( char *cadena1, char *cadena2 ); ( char *cadena, char carcter );
char * strcpy char * strcat char * strdup char * strcmp char * strchr
char * strncpy ( char *destino, char *fuente, int nm_car ); char * strncat ( char *destino, char *fuente, int nm_car );
char * strncmp ( char *kdna1, char *kdna2, int nm_car ); char * strrchr ( char *cadena, char carcter );
La longitud real de una cadena de caracteres kdna en un momento determinado se puede obtener mediante la funcin siguiente: strlen ( kdna ) El contenido de la cadena de caracteres apuntada por kdna a kdna9, se puede copiar con strcpy( kdna9, kdna ). Si la cadena fuente puede ser ms larga que la capacidad del vector correspondiente a la de destino, con strncpy( kdna9, kdna, LONG_KDNA9 1 ). En este ltimo caso, hay que prever que la cadena resultante no lleve un '\0' al final. Para solucionarlo, hay que reservar el ltimo carcter de la copia con conteo para poner un
141
ANOTACIONES
FUOC XP04/90793/00018
Software libre
'\0' de guarda. Si la cadena no tuviera espacio reservado, se tendra que hacer lo siguiente: /* ... */ char *kdna9, kdna[LONG_MAX]; /* ... */ kdna9 = (char *) malloc( strlen( kdna ) + 1 ); if( kdna9 != NULL ) strcpy( kdna9, kdna ); /* ... */
Nota
De esta manera, kdna9 es una cadena con el espacio ajustado al nmero de caracteres de la cadena almacenada en kdna, que se deber liberar meidante un free( kdna9 ) cuando ya no sea necesaria. El procedimiento anterior se puede sustituir por: /* ... */ kdna9 = strdup( kdna ); /* ... */ La comparacin entre cadenas se hace carcter a carcter, empezando por el primero de las dos cadenas a comparar y continuando por los siguientes mientras la diferencia entre los cdigos ASCII sea 0. La funcin strcmp() retorna el valor de la ltima diferencia. Es decir, un valor negativo si la segunda cadena es alfabticamente mayor que la primera, positivo en caso opuesto, y 0 si son iguales. Para entenderlo mejor, se adjunta un posible cdigo para la funcin de comparacin de cadenas: int strcmp( char *cadena1, char *cadena2 ) { while( (*cadena1 != '\0') && (*cadena2 != '\0') && (*cadena1 == *cadena2) ) { cadena1 = cadena1 + 1; cadena2 = cadena2 + 1; } /* while */ return *cadena1 - *cadena2; } /* strcmp */
142
ANOTACIONES
FUOC XP04/90793/00018
La funcin strncmp() hace lo mismo que strcmp() con los primeros nm_car caracteres. Finalmente, aunque hay ms, se comentan las funciones de bsqueda de caracteres dentro de cadenas. Estas funciones retornan el apuntador al carcter buscado o NULL si no se encuentra en la cadena: strchr() realiza la bsqueda desde el primer carcter. strrchr() inspecciona la cadena empezando por la derecha; es decir, por el final.
Ejemplo
char *strchr( char *cadena, char caracter ) { while( (*cadena != \0 ') && (*cadena2 != caracter ) ) { cadena = cadena + 1; } /* while */ return cadena; } /* strchr */
Todas las funciones anteriores estn declaradas en string.h y, por tanto, para utilizarlas, hay que incluir este fichero en el cdigo fuente del programa correspondiente.
En stdio.h tambin hay funciones estndar para operar con cadenas, como gets() y puts(), que sirven para la entrada y la salida de datos que sean cadenas de caracteres y que ya fueron descritas en su momento. Tambin contiene las declaraciones de sscanf() y sprintf() para la lectura y la escritura de cadenas con formato. scanf() y printf() a excepcin de que la lectura o escritura se realizan en una cadena de caracteres en lugar de hacerlo en el dispositivo estndar de entrada o salida: sprintf( char *destino, /* Cadena en la que "imprime". char *formato [, lista_de_variables] ); /* sprintf */
143
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
/* Devuelve el nmero de variables /* cuyo contenido ha sido actualizado. /* Cadena de la que se "lee".
*/ */ */
[,lista_de_&variables] ); /* sscanf */
Cuando se utiliza sprintf() se debe comprobar que la cadena destino tenga espacio suficiente para contener aquello que resulte de la impresin con el formato dado. Cuando se utiliza sscanf(),se debe comprobar siempre que se han ledo todos los campos: la inspeccin de la cadena origen se detiene al encontrar la marca de final de cadena independientemente de los especificadores de campo indicados en el formato.
En el siguiente ejemplo, se puede observar el cdigo de una funcin de conversin de una cadena que represente un valor hexadecimal (por ejemplo: 3D) a un entero positivo (siguiendo el ejemplo anterior: 3D(16 = 61) mediante el uso de las funciones anteriormente mencionadas. La primera se emplea para prefijar la cadena con 0x, puesto que ste es el formato de los nmeros hexadecimales en C, y la segunda, para efectuar la lectura de la cadena obtenida aprovechando que lee nmeros en cualquier formato estndar de C.
unsigned hexaVal( char *hexadecimal ) { unsigned numero; char *hexaC; hexaC = (char *) malloc( ( strlen( hexadecimal ) + 3 ) * sizeof( char ) ); /* malloc */
144
ANOTACIONES
FUOC XP04/90793/00018
if( hexaC != NULL ) { sprintf( hexaC, "0x%s", hexadecimal ); sscanf( hexaC, "%x", &numero ); free( hexaC ); } else { numero = 0; /* La conversin no se ha realizado!*/ } /* if */ return numero; } /* hexaVal */
Nota
Recordad la importancia de liberar el espacio de las cadenas de caracteres creadas dinmicamente que no se vayan a utilizar. En el caso anterior, de no hacer un free( hexaC ), la variable seguira ocupando espacio, a pesar de que ya no se podra acceder a ella, puesto que la direccin est contenida en el apuntador hexaC, que es de tipo automtico y, por tanto, se destruye al finalizar la ejecucin de la funcin. Este tipo de errores puede llegar a causar un gran desperdicio de memoria.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
mediante variables dinmicas. En este caso, cada elemento de la lista ser un nodo del tipo siguiente: typedef struct nodo_s { int dato; struct nodo_s *siguiente; } nodo_t, *lista_t; El tipo nodo_t se corresponde con un nodo de la lista y que lista_t es un apuntador a un nodo cuya tupla tiene un campo siguiente que, a la vez, es un apuntador a otro nodo, y as repetidamente hasta tener enlazados toda una lista de nodos. A este tipo de listas se las denomina listas simplemente enlazadas, pues slo existe un enlace entre un nodo y el siguiente. Las listas simplemente enlazadas son adecuadas para algoritmos que realicen frecuentes recorridos secuenciales. En cambio, para aqullos en que se hacen recorridos parciales en ambos sentidos (hacia delante y hacia atrs en la secuencia de nodos) es recomendable emplear listas doblemente enlazadas; es decir, listas cuyos nodos contengan apuntadores a los elementos siguientes y anteriores. En ambos casos, si el algoritmo realiza inserciones de nuevos elementos y destrucciones de elementos innecesarios de forma muy frecuente, puede ser conveniente tener el primer y el ltimo elemento enlazados. Estas estructuras se denominan listas circulares y, habitualmente, los primeros elementos estn marcados con algn dato o campo especial.
ANOTACIONES
146
FUOC XP04/90793/00018
En los apartados siguientes se comentarn estas tres operaciones y se darn los programas de las funciones para llevarlas a cabo sobre una lista simplemente enlazada. Para acceder a un nodo determinado es imprescindible obtener su direccin. Si se supone que se trata de obtener el ensimo elemento en una lista y devolver su direccin, la funcin correspondiente necesita como argumentos tanto la posicin del elemento buscado, como la direccin del primer elemento de la lista, que puede ser NULL si sta est vaca. Evidentemente, la funcin retornar la direccin del nodo ensimo o NULL si no lo ha encontrado: nodo_t *enesimo_nodo( lista_t lista, unsigned int n ) { while( ( lista != NULL ) && ( n != 0 ) ) { lista = lista siguiente; n = n 1; } /* while */ return lista; } /* enesimo_nodo */ En este caso, se considera que la primera posicin es la posicin nmero 0, de forma parecida a los vectores en C. Es imprescindible comprobar que ( lista != NULL ) se cumple, puesto que, de lo contrario, no podra ejecutarse lista = listasiguiente;: no se puede acceder al campo siguiente de un nodo que no existe. Para eliminar un elemento en una lista simplemente enlazada, es necesario disponer de la direccin del elemento anterior, ya que el campo siguiente de este ltimo debe actualizarse convenientemente. Para ello, es necesario extender la funcin anterior de manera que devuelva tanto la direccin del elemento buscado como la del elemento anterior. Como sea que tiene que devolver dos datos, hay que hacerlo a travs de un paso por referencia: hay que pasar las direcciones de los apuntadores a nodo que contendrn las direcciones de los nodos: void enesimo_pq_nodo( lista_t lista, /* Apuntador al primer nodo. /* Posicin del nodo buscado.
147
*/ */
unsigned int n,
ANOTACIONES
FUOC XP04/90793/00018
Software libre
nodo_t nodo_t {
**pref,/* Ref. apuntador a nodo previo.*/ **qref)/* Ref. apuntador a nodo actual.*/
nodo_t *p, *q; p = NULL; /* El anterior al primero no existe. q = lista; while( ( q != NULL ) && ( n != 0 ) ) { p = q; q = qsiguiente; n = n 1; } /* while */ *pref = p; *qref = q; } /* enesimo_pq_nodo */ */
El programa de la funcin que destruya un nodo debe partir de que se conoce tanto su direccin (almacenada en q) como la del posible elemento anterior (guardada en el apuntador p). Dicho de otra manera, se pretende eliminar el elemento siguiente al apuntado por p.
Cuando se realizan estos programas, es ms que conveniente hacer un esquema de la estructura de datos dinmica sobre el que se indiquen los efectos de las distintas modificaciones en la misma.
ANOTACIONES
148
As pues, para programar esta funcin, primero estableceremos el caso general sobre un esquema de la estructura de la cual queremos eliminar un nodo y, luego, se programar atendiendo a las distintas excepciones que puede tener el caso general. Habitualmente, stas vienen dadas por tratar el primer o el ltimo elemento, pues son aquellos que no tienen precedente o siguiente y, como consecuencia, no siguen la norma de los dems en la lista.
FUOC XP04/90793/00018
En la siguiente imagen se resume el procedimiento general de eliminacin del nodo siguiente al nodo p:
Figura 6.
Nota
Evidentemente, no es posible eliminar un nodo cuando p == NULL. En este caso, tanto (1) como (2) no pueden ejecutarse por implicar referencias a variables inexistentes. Ms an, para p == NULL se cumple que se desea eliminar el primer elemento, pues es el nico que no tiene ningn elemento que le preceda. As pues, hemos de proteger la ejecucin de (1) y (2) con una instruccin condicional que decida si se puede llevar a cabo el caso general o, por el contrario, se elimina el primero de los elementos. Algo parecido sucede con (3) y (4), que no pueden ejecutarse si q == NULL.
La funcin para destruir el nodo podra ser como la siguiente: int destruye_nodo( lista_t *listaref, nodo_t *p, nodo_t *q) { int data = 0 /* Valor por omisin de los datos.
149
/* Apuntador a referencia 1.er nodo. /* Apuntador a nodo previo. /* Apuntador a nodo a destruir.
*/ */ */ */
ANOTACIONES
FUOC XP04/90793/00018
Software libre
if( p != NULL ) { /* q = psiguiente; (no es necesario) */ psiguiente = qsiguiente; } else { if( q != NULL ) *listaref = qsiguiente; } /* if */ if( q!= NULL ) { data = qdato; free( q ); } /* if */ return data; } /* destruye_nodo */
Al eliminar el primer elemento, es necesario cambiar la direccin contenida en lista para que apunte al nuevo primer elemento, salvo que q tambin sea NULL).
Para insertar un nuevo nodo, cuya direccin est en t, basta con realizar las operaciones indicadas en la siguiente figura:
Figura 7.
ANOTACIONES
150
Nota
En este caso, el nodo t quedar insertado despus del nodo q. Como se puede observar, es necesario que q != NULL para poder realizar las operaciones de insercin.
FUOC XP04/90793/00018
void inserta_siguiente_nodo( lista_t *listaref, /* Apuntador a referencia 1.er nodo. nodo_t nodo_t { if( q != NULL ) { tsiguiente = qsiguiente; qsiguiente = t; } else { /* La lista est vaca. *listaref = t; } /* if */ } /* inserta_siguiente_nodo */ */ *q, *t) /* Apuntador a nodo en la posicin. /* Apuntador a nodo a insertar. */ */ */
Para que la funcin anterior pueda ser til, es necesario disponer de una funcin que permita crear nodos de la lista. En este caso:
nodo_t *crea_nodo( int data ) { nodo_t *nodoref; nodoref = (nodo_t *)malloc( sizeof( nodo_t ) ); if( nodoref != NULL ) { nodorefdato = data; nodorefsiguiente = NULL; } /* if */ return nodoref; } /* crea_nodo */
Si se trata de insertar en alguna posicin determinada, lo ms frecuente suele ser insertar el nuevo elemento como precedente del indicado; es decir, que ocupe la posicin del nodo referenciado y desplace al resto de nodos una posicin a la derecha. Las operaciones que hay que realizar para ello en el caso general se muestran en la siguiente figura:
151
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Figura 8.
Siguiendo las indicaciones de la figura anterior, el cdigo de la funcin correspondiente sera el siguiente:
void inserta_nodo( lista_t *listaref, /* nodo_t *p, /* nodo_t *q, /* nodo_t *t) /* { if( p != NULL ) { psiguiente = t; } else { /* Se inserta *listaref = t; } /* if */ tsiguiente = q; } /* inserta_nodo */
a a a a
*/ */ */ */
*/
Con todo, la insercin de un nodo en la posicin ensima podra construirse de la siguiente manera:
ANOTACIONES
bool inserta_enesimo_lista( lista_t unsigned int { nodo_t *p, *q, *t; bool retval;
152
*listaref, /* Apuntador a referencia 1.er nodo. int n, dato) /* Posicin de la insercin. /* Dato a insertar. /* Devuelve FALSE si no se puede.
*/ */ */ */
FUOC XP04/90793/00018
t = crea_nodo( dato ); if( t != NULL ) { enesimo_pq_nodo( *listaref, n, &p, &q ); inserta_nodo( listaref, p, q, t ); retval = TRUE; } else { retval = FALSE; } /* if */ return retval; } /* inserta_enesimo_lista */
De igual manera, podra componerse el cdigo para la funcin de destruccin del ensimo elemento de una lista. Normalmente, adems, las listas no sern de elementos tan simples como enteros y habr que sustituir la definicin del tipo de datos nodo_t por uno ms adecuado.
Los criterios de bsqueda de nodos suelen ser ms sofisticados que la bsqueda de una determinada posicin. Por lo tanto, hay que tomar las funciones anteriores como un ejemplo de uso a partir del cual se pueden derivar casos reales de aplicacin.
Colas
Las colas son, de hecho, listas en las que se inserta por un extremo y se elimina por el otro. Es decir, son listas en las que las operaciones de insercin y de eliminacin se restringen a unos casos muy determinados. Esto permite hacer una gestin mucho ms eficaz de las mismas. En este sentido, es conveniente disponer de un tuplo que facilite tanto el acceso directo al primer elemento como al ltimo. De esta manera, las operaciones de eliminacin e insercin se pueden resolver sin necesidad de bsquedas en la lista.
153
ANOTACIONES
FUOC XP04/90793/00018
Software libre
As pues, esta clase de colas debera tener una tupla de control como la siguiente: typedef struct cola_s { nodo_t *primero; nodo_t *ultimo; } cola_t; Grficamente:
Figura 9.
Para ejemplificar las dos operaciones, supondremos que los elementos de la cola sern simples enteros, es decir, que la cola ser una lista de nodos del tipo de datos nodo_t que ya se ha visto. Con ello, la insercin sera como sigue: bool encola( cola_t *colaref, int dato ) /* Devuelve FALSE si no se puede aadir el dato. */ { nodo_t *q, *t; bool retval;
ANOTACIONES
154
t = crea_nodo( dato ); if( t != NULL ) { tsiguiente = NULL; q = colarefultimo; if( q == NULL ) { /* Cola vaca: */ colarefprimero = t;
FUOC XP04/90793/00018
} else { qsiguiente = t; } /* if */ colarefultimo = t; retval = TRUE; } else { retval = FALSE; } /* if */ return retval; } /* encola */
Y la eliminacin:
bool desencola( cola_t *colaref, int *datoref ) /* Devuelve FALSE si no se puede eliminar el dato. { nodo_t *q; bool retval; q = colarefprimero; if( q != NULL ) { colarefprimero = qsiguiente; *datoref = destruye_nodo( &q ); if( colarefprimero == NULL ) { /* Cola vaca: colarefultimo = NULL; } /* if */ retval = TRUE; } else { retval = FALSE; return retval; } /* desencola */ } /* if */ */ */
ANOTACIONES
FUOC XP04/90793/00018
Software libre
int dato = 0; if( *pref != NULL ) { dato = (*pref) dato; free( *pref ); *pref = NULL; } /* if */ return dato; } /* destruye_nodo */ Las colas se utilizan frecuentemente cuando hay recursos compartidos entre muchos usuarios.
Ejemplo
Para gestionar una impresora, el recurso es la propia impresora y los usuarios, los ordenadores conectados a la misma. Para controlar una mquina del tipo su turno: el recurso es el vendedor y los usuarios son los clientes. En general, cuando se hacen inserciones en cola se tiene en cuenta qu elemento se est insertando; es decir, no se realizan siempre por el final, sino que el elemento se coloca segn sus privilegios sobre los dems. En este caso, se habla de colas con prioridad. En este tipo de colas, la eliminacin siempre se realiza por el principio, pero la insercin implica situar al nuevo elemento en la ltima posicin de los elementos con la misma prioridad. Ciertamente, hay otros tipos de gestiones especializadas con listas y, ms all de las listas, otros tipos de estructuras dinmicas de datos como los rboles (por ejemplo, un rbol sintctico) y los grafos (por ejemplo, una red de carreteras). Desafortunadamente, no se dispone
ANOTACIONES
156
de suficiente tiempo para tratarlos, pero hay que tenerlos en cuenta cuando los datos del problema puedan requerirlo.
Recordemos que la programacin modular estriba en dividir el cdigo en subprogramas que realicen una funcin concreta. En esta uni-
FUOC XP04/90793/00018
dad se tratar especialmente de la manera como pueden agruparse estos subprogramas de acuerdo a las tareas que les son encomendadas y, en definitiva, de cmo organizarlos para una mejor programacin del algoritmo correspondiente.
Los algoritmos complejos suelen reflejarse en programas con muchas lneas de cdigo. Por lo tanto se impone una programacin muy cuidadosa para que el resultado sea un cdigo legible y de fcil mantenimiento.
3.6.1. Descripcin
El fruto de una programacin modular es un cdigo constituido por diversos subprogramas de pocas lneas relacionados entre ellos mediante llamadas. As pues, cada uno de ellos puede ser de fcil comprensin y, consecuentemente, de fcil mantenimiento.
La tcnica de diseo descendente es, de hecho, una tcnica de diseo de algoritmos en la que se resuelve el algoritmo principal abstrayendo los detalles, que luego se solucionan mediante otros algoritmos de la misma manera. Es decir, se parte del nivel de abstraccin ms alto y, para todas aquellas acciones que no puedan trasladarse de forma directa a alguna instruccin del lenguaje de programacin elegido, se disean los algoritmos correspondientes de forma independiente del principal siguiendo el mismo principio.
Nota
El diseo descendente consiste en escribir programas de algoritmos de unas pocas instrucciones e implementar las instrucciones no primitivas con funciones cuyos programas sigan las mismas normas anteriores.
En la prctica, esto comporta disear algoritmos de manera que permite que la programacin de los mismos se haga de forma totalmente modular.
157
ANOTACIONES
Una instruccin primitiva sera aquella que pueda programarse directamente en un lenguaje de programacin.
FUOC XP04/90793/00018
Software libre
3.6.2. Ejemplo
El diseo descendente de programas consiste, pues, en empezar por el programa del algoritmo principal e ir refinando aquellas instrucciones gruesas convirtindolas en subprogramas con instrucciones ms finas. De ah la idea de refinar. Evidentemente, el proceso termina cuando ya no hay ms instrucciones gruesas para refinar. En este apartado se ver un ejemplo simple de diseo descendente para resolver un problema bastante habitual en la programacin: el de la ordenacin de datos para facilitar, por ejemplo, su consulta. Este problema es uno de los ms estudiados en la ciencia informtica y existen diversos mtodos para solucionarlo. Uno de los ms simples consiste en seleccionar el elemento que debera encabezar la clasificacin (por ejemplo, el ms pequeo o el mayor, si se trata de nmeros), ponerlo en la lista ordenada y repetir el proceso con el resto de elementos por ordenar. El programa principal de este algoritmo puede ser el siguiente: /* ... */ lista_t /* ... */ inicializa_lista( &ordenados ); while( ! esta_vacia_lista( pendientes ) ) { elemento = extrae_minimo_de_lista( &pendientes ); pon_al_final_de_lista( &ordenados, elemento ); } /* while */ /* ... */ En el programa anterior hay pocas instrucciones primitivas de C y, por tanto, habr que refinarlo. Como contrapartida, su funcionamiento resulta fcil de comprender. Es importante tener en cuenta que los operadores de direccin de (el signo &) en los parmetros de las llamadas de las funciones indican que stas pueden modificar el contenido de los mismos. La mayor dificultad que se encuentra en el proceso de refinado es, habitualmente, identificar las partes que deben describirse con ins158
pendientes, ordenados;
elemento_t elemento;
ANOTACIONES
FUOC XP04/90793/00018
trucciones primitivas; es decir, determinar los distintos niveles de abstraccin que deber tener el algoritmo y, consecuentemente, el programa correspondiente. Generalmente, se trata de conseguir que el programa refleje al mximo el algoritmo del que proviene. Es un error comn pensar que aquellas operaciones que slo exigen una o dos instrucciones primitivas no pueden ser nunca contempladas como una instruccin no primitiva. Una norma de fcil adopcin es que todas las operaciones que se realicen con un tipo de datos abstracto sean igualmente abstractas; es decir, se materialicen en instrucciones no primitivas (funciones).
bool esta_vacia_lista( lista_t lista ); elemento_t extrae_minimo_de_lista( lista_t *ref_lista ); void pon_al_final_de_lista( lista_t *rlst, elemento_t e );
Como se puede observar, no hay operaciones que afecten a datos del tipo elemento_t. En todo caso, es seguro que el programa har uso de ellas (lectura de datos, insercin de los mismos en la lista, comparacin entre elementos, escritura de resultados, etc.) en al159
ANOTACIONES
FUOC XP04/90793/00018
Software libre
guna otra seccin. Por lo tanto, tambin se habrn de programar las operaciones correspondientes. En particular, si se observa el siguiente cdigo se comprobar que aparecen funciones para tratar con datos del tipo elemento_t:
elemento_t extrae_minimo_de_lista( lista_t *ref_lista ) { ref_nodo_t bool elemento_t actual, minimo; es_menor; peque;
principio_de_lista( ref_lista ); if( esta_vacia_lista( *ref_lista ) ) { inicializa_elemento( &peque ); } else { minimo = ref_nodo_de_lista( *ref_lista ); peque = elemento_en_ref_nodo( *ref_lista, minimo ); avanza_posicion_en_lista( ref_lista ); while( !es_final_de_lista( *ref_lista ) ) { actual = ref_nodo_de_lista( *ref_lista ); es_menor = compara_elementos( elemento_en_ref_nodo( *ref_lista, actual ), peque ); /* compara_elementos */ if( es_menor ) { minimo = actual; peque = elemento_en_ref_nodo( *ref_lista, minimo ); } /* if */ avanza_posicion_en_lista( ref_lista ); } /* while */ muestra_elemento( peque ); elimina_de_lista( ref_lista, minimo ); } /* if */ return peque; } /* extrae_minimo_de_lista */
ANOTACIONES
Como se puede deducir del cdigo anterior, al menos son necesarias dos operaciones para datos del tipo elemento_t: inicializa_elemento(); compara_elementos();
160
FUOC XP04/90793/00018
Adems, son necesarias cuatro operaciones ms para listas: principio_de_lista(); es_final_de_lista(); avanza_posicion_en_lista(); elimina_de_lista(); Se ha aadido tambin el tipo de datos ref_nodo_t para tener las referencias de los nodos en las listas y dos operaciones: ref_nodo_de_lista para obtener la referencia de un determinado nodo en la lista y elemento_en_ref_nodo para obtener el elemento que se guarda en el nodo indicado. Con esto se demuestra que el refinamiento progresivo sirve para determinar qu operaciones son necesarias para cada tipo de datos y, por otra parte, se refleja que las listas constituyen un nivel de abstraccin distinto y mayor que el de los elementos. Para completar la programacin del algoritmo de ordenacin, es necesario desarrollar todas las funciones asociadas a las listas, y luego a los elementos. De todas maneras, lo primero que hay que hacer es determinar los tipos de datos abstractos que se emplearn. Las listas pueden materializarse con vectores o con variables dinmicas, segn el tipo de algoritmos que se empleen. En el caso del ejemplo de la ordenacin, depender en parte de los mismos criterios generales que se aplican para tomar tal decisin y, en parte, de las caractersticas del mismo algoritmo. Generalmente, se elegir una implementacin con vectores si el desaprovechamiento medio de los mismos es pequeo, y particularmente se tiene en cuenta que el alcantidades modestas de datos (por ejemplo, unos centenares como mucho). As pues, de escoger la primera opcin, el tipo de datos lista_t sera: #define LONG_MAXIMA 100 typedef struct lista_e {
161
ANOTACIONES
FUOC XP04/90793/00018
Software libre
elemento_t
nodo[ LONG_MAXIMA ]; */ */
unsigned short posicion; /* Posicin actual de acceso. unsigned short cantidad; /* Longitud de la lista. } lista_t;
Tambin hara falta definir el tipo de datos para la referencia de los nodos: typedef unsigned short ref_nodo_t; De esta manera, el resto de operaciones que son necesarias se corresponderan con las funciones siguientes: void inicializa_lista( lista_t *ref_lista ) { (*ref_lista).cantidad = 0; (*ref_lista).posicion = 0; } /* inicializa_lista */ bool esta_vacia_lista( lista_t lista ) { return ( lista.cantidad == 0 ); } /* esta_vacia_lista */ bool es_final_de_lista( lista_t lista ) { return ( lista_posicion == lista.cantidad ); } /* es_final_de_lista */ void principio_de_lista( lista_t *lista_ref ) { lista_refposicion = 0; } /* principio_de_lista */ ref_nodo_t ref_nodo_de_lista( lista_t lista ) { return lista.posicion; } /* ref_nodo_de_lista */ elemento_t elemento_en_ref_nodo( lista_t lista,
162
ANOTACIONES
FUOC XP04/90793/00018
ref_nodo_t refnodo) { return lista.nodo[ refnodo ]; } /* elemento_en_ref_nodo */ void avanza_posicion_en_lista( lista_t *lista_ref ) { if( !es_final_de_lista( *lista_ref ) ) {
(*lista_ref).posicion = (*lista_ref).posicion + 1;
} /* if */ } /* avanza_posicion_en_lista */ elemento_t elimina_de_lista( lista_t *ref_lista, ref_nodo_t refnodo) { elemento_t eliminado; ref_nodo_t pos, ultimo; if( esta_vacia_lista( *ref_lista ) ) { inicializa_elemento( &eliminado ); } else { eliminado = (*ref_lista).nodo[ refnodo ]; ultimo = (*ref_lista).cantidad - 1; for( pos = refnodo; pos < ultimo; pos = pos + 1 ) { (*ref_lista).nodo[pos] = (*ref_lista).nodo[pos+1]; } /* for */ (*ref_lista).cantidad = (*ref_lista).cantidad - 1; } /* if */ return eliminado; } /* elimina_de_lista */ elemento_t extrae_minimo_de_lista( lista_t *ref_lista ); void pon_al_final_de_lista( lista_t *ref_lista, elemento_t elemento ) { if( (*ref_lista).cantidad < LONG_MAXIMA ) { (*ref_lista).nodo[(*ref_lista).cantidad] = elemento; (*ref_lista).cantidad = (*ref_lista).cantidad + 1;
163
ANOTACIONES
FUOC XP04/90793/00018
Software libre
} /* if */ } /* pon_al_final_de_lista */ Si se examinan las funciones anteriores, todas asociadas al tipo de datos lista_t, se ver que slo requieren de una operacin con el tipo de datos elemento_t: compara_elementos. Por lo tanto, para completar el programa de ordenacin, ya slo falta definir el tipo de datos y la operacin de comparacin. Si bien las operaciones con las listas sobre vectores eran genricas, todo aquello que afecta a los elementos depender de la informacin cuyos datos se quieran ordenar. Por ejemplo, suponiendo que se deseen ordenar por nmero de DNI las notas de un examen, el tipo de datos de los elementos podra ser como sigue: typedef struct elemento_s { unsigned int float } dato_t, *elemento_t; La funcin de comparacin sera como sigue: bool compara_elementos( elemento_t menor, elemento_t mayor ) { return ( menorDNI < mayorDNI ); } /* compara_elementos */ La funcin de incializacin sera como sigue: void inicializa_elemento( elemento_t *ref_elem ) { *ref_elem = NULL; } /* inicializa_elemento */ Ntese que los elementos son, en realidad, apuntadores de variables dinmicas. Esto, a pesar de requerir que el programa las construya
164
DNI; nota;
ANOTACIONES
FUOC XP04/90793/00018
y destruya, simplifica, y mucho, la codificacin de las operaciones en niveles de abstraccin superiores. No obstante, es importante recalcar que siempre habr que preparar funciones para la creacin, destruccin, copia y duplicado de cada uno de los tipos de datos para los que existen variables dinmicas.
Las funciones de copia y de duplicado son necesarias puesto que la simple asignacin constituye una copia de la direccin de una variable a otro apuntador, es decir, se tendrn dos referencias a la misma variable en lugar de dos variables distintas con el mismo contenido.
Ejemplo
La copia de contenidos debe, pues, realizarse mediante una funcin especfica y, claro est, si la variable que debe contener la copia no est creada, deber procederse primero a crearla, es decir, se har un duplicado. En resumen, cuando hayamos de programar un algoritmo en diseo descendente, habremos de programar las funciones de creacin, destruccin y copia para cada uno de los tipos de datos abstractos que contenga. Adems, se programar como funcin toda operacin que se realice con cada tipo de datos para que queden reflejados los distintos niveles de abstraccin presentes en el algoritmo. Con todo, aun a costa de alguna lnea de cdigo ms, se consigue un programa inteligible y de fcil manutencin.
165
ANOTACIONES
FUOC XP04/90793/00018
Software libre
ANOTACIONES
166
En los ficheros de cabecera se da a conocer todo aquello relativo a un tipo de datos abstracto y a las funciones para su manejo.
3.8.1. Estructura
Los ficheros de cabecera llevan la extensin .h y su contenido debe organizarse de tal manera que sea de fcil lectura. Para ello, primero
FUOC XP04/90793/00018
se deben colocar unos comentarios indicando la naturaleza de su contenido y, sobre todo, de la funcionalidad del cdigo en el fichero .c correspondiente. Despus, es suficiente con seguir la estructura de un programa tpico de C: inclusin de ficheros de cabecera, definicin de constantes simblicas y, por ltimo, declaracin de las funciones.
Sirva como ejemplo el siguiente fichero de cabecera para operar con nmeros complejos: /* Fichero: /* /* complejos.h complejos del tipo (X + iY) en los que X es la parte real e Y, la imaginaria. */ */ */ */ */
/* Revisin: 0.0 (original) #ifndef _NUMEROS_COMPLEJOS_H_ #define _NUMEROS_COMPLEJOS_H_ #include <stdio.h> #define PRECISION 1E-10 typedef struct complex_s { double real, imaginario; } *complex_t;
complex_t nuevo_complejo( double real, double imaginario ); void borra_complejo( complex_t complejo ); void imprime_complejo( FILE *fichero, complex_t complejo ); complex_t opuesto_complejo( complex_t complejo ); complex_t suma_complejos( complex_t c1, complex_t c2 ); /* etctera */ #endif /* _NUMEROS_COMPLEJOS_H_ */ Las definiciones de tipos y constantes y las declaraciones de funciones se han colocado como el cuerpo del comando del preprocesador
167
ANOTACIONES
FUOC XP04/90793/00018
Software libre
#ifndef ... #endif. Este comando pregunta si una determinada constante est definida y, de no estarlo, traslada al compilador lo que contenga el fichero hasta la marca de final. El primer comando del cuerpo de este comando condicional consiste, precisamente, en definir la constante _NUMEROS_COMPLEJOS_H_ para evitar que una nueva inclusin del mismo fichero genere el mismo cdigo fuente para el compilador (es innecesario si ya lo ha procesado una vez). Los llamados comandos de compilacin condicional del preprocesador permiten decidir si un determinado trozo de cdigo fuente se suministra al compilador o no. Los resumimos en la tabla siguiente:
Tabla 8. Comando #if expresin #ifdef SMBOLO #ifndef SMBOLO Significado Las lneas siguientes son compiladas si expresin ? 0. Se compilan las lneas siguientes si SMBOLO est definido. Se compilan las lneas siguientes si SMBOLO no est definido. Finaliza el bloque compilado si se cumple la condicin e inicia el bloque a compilar en caso contrario. Encadena un else con un if. Indica el final del bloque de compilacin condicional.
Nota
Un smbolo definido (por ejemplo: SMBOLO) puede anularse mediante: #undef SMBOLO y, a partir de ese momento, se considera como no definido. Las formas: #ifdef SMBOLO #ifndef SMBOLO
168
ANOTACIONES
FUOC XP04/90793/00018
son abreviaciones de: #if defined( SMBOLO ) #if !defined( SMBOLO ) respectivamente. La funcin defined puede emplearse en expresiones lgicas ms complejas. Para acabar esta seccin, cabe indicar que el fichero de cdigo fuente asociado debe incluir, evidentemente, su fichero de cabecera: /* Fichero: /* ... */ #include "complejos.h" /* ... */
Nota
complejos.c
*/
La inclusin se hace indicando el fichero entre comillas dobles en lugar de utilizar parntesis angulares (smbolos de mayor y menor que) porque se supone que el fichero que se incluye se encuentra en el mismo directorio que el fichero que contiene la directiva de inclusin. Los parntesis angulares se deben usar si se requiere que el preprocesador examine el conjunto de caminos de acceso a directorios estndar de ficheros de inclusin, como es el caso de stdio.h, por ejemplo.
3.8.2. Ejemplo
En el ejemplo de la ordenacin por seleccin, tendramos un fichero de cabecera para cada tipo de datos abstracto y, evidentemente, los correspondientes ficheros con el cdigo en C. Adems, dispondramos de un tercer fichero que contendra el cdigo del programa principal. La figura siguiente refleja el esquema de relaciones entre estos ficheros:
169
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Figura 10.
Cada fichero de cdigo fuente puede compilarse independientemente. Es decir, en este ejemplo habra tres unidades de compilacin. Cada una de ellas puede, pues, ser desarrollada independientemente de las dems. Lo nico que deben respetar aquellas unidades que tienen un fichero de cabecera es no modificar ni los tipos de datos ni las declaraciones de las funciones. De esta manera, las dems unidades que hagan uso de ellas no debern modificar sus llamadas y, por tanto, no requerirn ningn cambio ni compilacin. Es importante tener en cuenta que slo puede existir una unidad con una funcin main (que es la que se corresponde con ordena.c, en el ejemplo dado). Las dems debern ser compendios de funciones asociadas a un determinado tipo de datos abstracto. El uso de ficheros de cabecera permite, adems, cambiar el cdigo de alguna funcin, sin tener que cambiar por ello nada de los programas que la emplean. Claro est, siempre que el cambio no afecte al contrato establecido en el fichero de cabecera correspondiente.
ANOTACIONES
170
En el fichero de cabecera se describe toda la funcionalidad que se provee para un determinado tipo de datos abstracto y, con esta descripcin, se adquiere el compromiso de que se mantendr independientemente de cmo se materialice en el cdigo asociado.
FUOC XP04/90793/00018
Para ilustrar esta idea se puede pensar en que, en el ejemplo, es posible cambiar el cdigo de las funciones que trabajan con las listas para que stas sean de tipo dinmico sin cambiar el contrato adquirido en el fichero de cabecera. A continuacin se lista el fichero de cabecera para las listas en el caso de la ordenacin: /* Fichero: lista.h #ifndef _LISTA_VEC_H_ #define _LISTA_VEC_H_ #include <stdio.h> #include "bool.h" #include "elemento.h" #define LONG_MAXIMA 100 typedef struct lista_e { elemento_t nodo[ LONG_MAXIMA ]; */ */ unsigned short posicion; /* Posicin actual de acceso. unsigned short cantidad; /* Longitud de la lista. } lista_t; void inicializa_lista( lista_t *ref_lista ); bool esta_vacia_lista( lista_t lista ); bool es_final_de_lista( lista_t lista ); void principio_de_lista( lista_t *lista_ref ); ref_nodo_t ref_nodo_de_lista( lista_t lista ); elemento_t elemento_en_ref_nodo( lista_t lista, ref_nodo_t refnodo void avanza_posicion_en_lista( lista_t *lista_ref ); elemento_t extrae_minimo_de_lista( lista_t *ref_lista ); void pon_al_final_de_lista( lista_t *ref_lista, elemento_t elemento ); #endif /* _LISTA_VEC_H_ */
171
*/
ANOTACIONES
);
FUOC XP04/90793/00018
Software libre
Como se puede observar, se podra cambiar la forma de extraccin del elemento mnimo sin necesidad de modificar el fichero de cabecera, y menos an la llamada en el programa principal. Sin embargo, un cambio en el tipo de datos para, por ejemplo, implementar las listas mediante variables dinmicas, implicara volver a compilar todas las unidades que hagan uso de las mismas, aunque no se modifiquen las cabeceras de las funciones. De hecho, en estos casos, resulta muy conveniente mantener el contrato al respecto de las mismas, pues evitar modificaciones en los cdigos fuente de las unidades que hagan uso de ellas.
3.9. Bibliotecas
Las bibliotecas de funciones son, de hecho, unidades de compilacin. Como tales, cada una dispone de un fichero de cdigo fuente y uno de cabecera. Para evitar la compilacin repetitiva de las bibliotecas, el cdigo fuente ya ha sido compilado (ficheros de extensin .o) y slo quedan pendientes de su enlace con el programa principal. Las bibliotecas de funciones, de todas maneras, se distinguen de las unidades de compilacin por cuanto slo se incorporan dentro del programa final aquellas funciones que son necesarias: las que no se utilizan, no se incluyen. Los ficheros compilados que permiten esta opcin tienen la extensin .l.
3.9.1. Creacin
Para obtener una unidad de compilacin de manera que slo se incluyan en el programa ejecutable las funciones utilizadas, hay que compilarla para obtener un fichero de tipo objeto; es decir, con el cdigo ejecutable sin enlazar: $ gcc c o biblioteca.o biblioteca.c Se supone, que en el fichero de biblioteca.c tal como se ha indicado, hay una inclusin del fichero de cabecera apropiado.
172
ANOTACIONES
FUOC XP04/90793/00018
Una vez generado el fichero objeto, es necesario incluirlo en un archivo (con extensin .a) de ficheros del mismo tipo (puede ser el nico si la biblioteca consta de una sola unidad de compilacin). En este contexto, los archivos son colecciones de ficheros objeto reunidos en un nico fichero con un ndice para localizarlos y, sobre todo, para determinar qu partes del cdigo objeto corresponden a qu funciones. Para crear un archivo hay que ejecutar el siguiente comando: $ ar biblioteca.a biblioteca.o Para construir el ndice (la tabla de smbolos y localizaciones) debe de ejecutarse el comando: $ ar s biblioteca.a o bien: $ ranlib biblioteca.a El comando de gestin de archivos ar permite, adems, listar los ficheros objeto que contiene, aadir o reemplazar otros nuevos o modificados, actualizarlos (se lleva a cabo el reemplazo si la fecha de modificacin es posterior a la fecha de inclusin en el archivo) y eliminarlos si ya no son necesarios. Esto se hace, respectivamente, con los comandos siguientes: $ ar -t biblioteca.a $ ar r nuevo.o biblioteca.a $ ar u actualizable.o biblioteca.a $ ar d obsoleto.o biblioteca.a Con la informacin de la tabla de smbolos, el enlazador monta un programa ejecutable empleando slo aquellas funciones a las que se refiere. Para lo dems, los archivos son similares a los ficheros de cdigo objeto simples.
3.9.2. Uso
El empleo de las funciones de una biblioteca es exactamente igual que el de las funciones de cualquier otra unidad de compilacin.
173
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Basta con incluir el fichero de cabecera apropiado e incorporar las llamadas a las funciones que se requieran dentro del cdigo fuente.
3.9.3. Ejemplo
En el ejemplo de la ordenacin se ha preparado una unidad de compilacin para las listas. Como las listas son un tipo de datos dinmico que se utiliza mucho, resulta conveniente disponer de una biblioteca de funciones para operar con ellas. De esta manera, no hay que programarlas de nuevo en ocasiones posteriores. Para transformar la unidad de listas en una biblioteca de funciones de listas hay que hacer que el tipo de datos no dependa en absoluto de la aplicacin. En caso contrario, habra que compilar la unidad de listas para cada nuevo programa. En el ejemplo, las listas contenan elementos del tipo elemento_t, que era un apuntador a dato_t. En general, las listas podrn tener elementos que sean apuntadores a cualquier tipo de datos. Sea como sea, todo son direcciones de memoria. Por este motivo, en lo que atae a las listas, los elementos sern de un tipo vaco, que el usuario de la biblioteca de funciones habr de definir. As pues, en la unidad de compilacin de las listas se incluye:
typedef void *elemento_t; Por otra parte, para realizar la funcin de extraccin del elemento ms pequeo, ha de saber a qu funcin llamar para realizar la comparacin. Por tanto, se le aade un parmetro ms que consiste en la direccin de la funcin de comparacin:
ANOTACIONES
elemento_t extrae_minimo_de_lista( lista_t *ref_lista, bool (*compara_elementos)( elemento_t, elemento_t ) ); /* extrae_minimo_de_lista */ El segundo argumento es un apuntador a una funcin que toma como parmetros dos elementos (no es necesario dar un nombre a
174
FUOC XP04/90793/00018
los parmetros formales) y devuelve un valor lgico de tipo bool. En el cdigo fuente de la definicin de la funcin, la llamada se efectuara de la siguiente manera: /* ... */ es_menor = (*compara_elementos)( elemento_en_ref_nodo( lista, actual ), peque ); /* compara_elementos */ /* ... */ Por lo dems, no habra de hacerse cambio alguno. As pues, la decisin de transformar alguna unidad de compilacin de un programa en biblioteca depender fundamentalmente de dos factores: El tipo de datos y las operaciones han de poder ser empleados en otros programas. Raramente se deben emplear todas las funciones de la unidad.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
De esta manera, no es necesario preocuparse de qu ficheros hay que generar y cules no hace falta actualizar. Por otra parte, evita tener que ejecutar individualmente una serie de comandos que, para programas grandes, puede ser considerable.
El propsito de las herramientas de tipo make es determinar automticamente qu piezas de un programa deben ser recompiladas y ejecutar los comandos pertinentes.
La herramienta gmake (o, simplemente, make) es una utilidad make de GNU (www.gnu.org/software/make) que se ocupa de lo anteriormente mencionado. Para poder hacerlo, necesita un fichero que, por omisin, se denomina makefile. Es posible indicarle un fichero con otro nombre si se la invoca con la opcin f: $ make f fichero_objetivos
ANOTACIONES
El smbolo # se emplea para introducir una lnea de comentarios. Toda regla requiere que se indique cul es el objetivo y, despus de los dos puntos, se indiquen los ficheros de los cules depende. En las lneas siguientes se indicarn los comandos que deben de ejecutarse. Cualquier lnea que quiera continuarse deber finali176
FUOC XP04/90793/00018
zar con un salto de lnea precedido del carcter de escape o barra inversa (\). Tomando como ejemplo el programa de ordenacin de notas de un examen por DNI, podra construirse un makefile como el mostrado a continuacin para simplificar la actualizacin del ejecutable final: # Compilacin del programa de ordenacin: clasifica : ordena.o nota.o lista.a gcc g o clasifica ordena.o nota.o lista.a ordena.o : ordena.c gcc g c o ordena.o ordena.c nota.o : nota.c nota.h gcc g c o nota.o nota.c lista.a : lista.o ar r lista.a lista.o ; ranlib lista.a lista.o : lista.c lista.h gcc g c o lista.o lista.c La herramienta make procesara el fichero anterior revisando los prerrequisitos del primer objetivo, y si stos son a su vez objetivos de otras reglas, se proceder de la misma manera para estos ltimos. Cualquier prerrequisito terminal (que no sea un objetivo de alguna otra regla) modificado con posterioridad al objetivo al que afecta provoca la ejecucin en serie de los comandos especificados en las siguientes lneas de la regla. Es posible especificar ms de un objetivo, pero make slo examinar procese otros objetivos, ser necesario indicarlo as en su invocacin. Es posible tener objetivos sin prerrequisitos, en cuyo caso, siempre se ejecutarn los comandos asociados a la regla en la que aparecen. Es posible tener un objetivo para borrar todos los ficheros objeto que ya no son necesarios.
177
ANOTACIONES
las reglas del primer objetivo final que encuentre. Si se desea que
FUOC XP04/90793/00018
Software libre
Ejemplo
Retomando el ejemplo anterior, sera posible limpiar el directorio de ficheros innecesarios aadiendo el siguiente texto al final del makefile: # Limpieza del directorio de trabajo: limpia : rm f ordena.o nota.o lista.o Para conseguir este objetivo, bastara con introducir el comando: $ make limpieza Es posible indicar varios objetivos con unos mismos prerrequisitos:
# Compilacin del programa de ordenacin: todo depura ptimo : clasifica depura : CFLAGS := -g optimo : CFLAGS := -O clasifica : ordena.o nota.o lista.a gcc $(CFLAGS) o clasifica ordena.o nota.o lista.a # resto del fichero ...
Nota
A la luz del fragmento anterior, podemos ver lo siguiente: Si se invoca make sin argumento, se actualizar el objetivo todo (el primero que encuentra), que depende de la actualizacin del objetivo secundario clasifica. Si se especifica el objetivo depura o el optimo, habr de comprobar la consistencia de dos reglas: en la primera se indica que para conseguirlos hay que actualizar clasifica y, en la segunda, que hay que asignar a la variable CCFLAGS un valor.
178
ANOTACIONES
FUOC XP04/90793/00018
Dado que make primero analiza las dependencias y luego ejecuta los comandos oportunos, el resultado es que el posible montaje de compila se har con el contenido asignado a la variable CCFLAGS en la regla precedente: el acceso al contenido de una variable se realiza mediante el operador correspondiente, que se representa por el smbolo del dlar. En el ejemplo anterior ya puede apreciarse que la utilidad de make va mucho ms all de lo que aqu se ha contado y, de igual forma, los makefile tienen muchas maneras de expresar reglas de forma ms potente. Aun as, se han repasado sus principales caractersticas desde el punto de vista de la programacin y visto algunos ejemplos significativos.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
mer parmetro sea el nmero de argumentos que se le pasan a travs del segundo, que consiste en un vector de cadenas de caracteres. Para aclarar cmo funciona este procedimiento de paso de parmetros, sirva como ejemplo el siguiente programa, que nos descubrir qu sucede cuando lo invocamos desde un shell con un cierto nmero de argumentos: /* Fichero: args.c */ #include <stdio.h> int main( int argc, char *argv[] ) { int i; printf( "Nm. argumentos, argc = %i\n", argc ); for( i = 0; i < argc; i = i + 1 ) { printf( "Argumento argv[%i] = \"%s\"\n", i, argv[i] ); } /* for */ return argc; } /* main */ Si se hace la invocacin con el comando siguiente, el resultado ser el que se indica a continuacin: $ args -prueba nmero 1 Nm. argumentos, argc = 4 Argumento argv[0] = "args" Argumento argv[1] = "-prueba" Argumento argv[2] = "nmero" Argumento argv[3] = "1" $ Es importante tener presente que la propia invocacin de programa
ANOTACIONES
180
se toma como argumento 0 del comando. As pues, en el ejemplo anterior, la invocacin en la que se dan al programa tres parmetros se convierte, finalmente, en una llamada a la funcin main en la que se adjuntar tambin el texto con el que ha sido invocado el propio programa como primera cadena de caracteres del vector de argumentos. Puede consultarse el valor retornado por main para determinar si ha habido algn error durante la ejecucin o no. Generalmente, se
FUOC XP04/90793/00018
toma por convenio que el valor devuelto debe ser el cdigo del error correspondiente o 0 en ausencia de errores.
Nota
En el cdigo fuente de la funcin es posible emplear las constantes EXIT_FAILURE y EXIT_SUCCESS, que estn definidas en stdlib.h, para indicar el retorno con o sin error, respectivamente.
En el siguiente ejemplo, se muestra un programa que efecta la suma de todos los parmetros presentes en la invocacin. Para ello, emplea la funcin atof, declarada en stdlib.h, que convierte cadenas de texto a nmeros reales. En caso de que la cadena no representara un nmero, devuelve un cero: /* Fichero: suma.c */ #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[] ) { float int suma = 0.0; pos = 1;
Nota
En este caso, el programa devuelve siempre el valor 0, puesto que no hay errores de ejecucin que indicar.
while( pos < argc ) { suma = suma + atof( argv[ pos ] ); pos = pos + 1; } /* while */ printf( " = %g\n", suma ); return 0; } /* main */
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Adems, el hecho de que los programas se definan en trminos de las rutinas (o funciones) provedas por el SO aumenta su portabilidad, su capacidad de ser ejecutados en mquinas distintas. En definitiva, los hace independientes de la mquina, pero no del SO, obviamente.
En C, muchas de las funciones de la biblioteca estndar emplean las rutinas del SO para llevar a cabo sus tareas. Entre estas funciones se encuentran las de entrada y salida de datos, de ficheros y de gestin de memoria (variables dinmicas, sobre todo).
Generalmente, todas las funciones de la biblioteca estndar de C tienen la misma cabecera y el mismo comportamiento, incluso con independencia del sistema operativo; no obstante, hay algunas que dependen del sistema operativo: puede haber algunas diferencias entre Linux y Microsoft. Afortunadamente, son fcilmente detectables, pues las funciones relacionadas a un determinado sistema operativo estn declaradas en ficheros de cabecera especficos.
En todo caso, a veces resulta conveniente ejecutar los comandos del shell del sistema operativo en lugar de ejecutar directamente las funciones para llevarlos a trmino. Esto permite, entre otras cosas, que el programa correspondiente pueda describirse a un nivel de abstraccin ms alto y, con ello, aprovechar que sea el mismo intrprete de comandos el que complete los detalles necesarios para llevar a trmino la tarea encomendada. Generalmente, se trata de ejecutar rdenes internas del propio shell o aprovechar sus recursos (caminos de bsqueda y variables de entorno, entre otros) para ejecutar otros programas.
ANOTACIONES
182
Para poder ejecutar un comando del shell, basta con suministrar a la funcin system la cadena de caracteres que lo describa. El valor que devuelve es el cdigo de retorno del comando ejecutado o 1 en caso de error.
En Linux, system( comando ) ejecuta /bin/sh c comando; es decir, emplea sh como intrprete de comandos. Por lo tanto, stos tienen que ajustarse a la sintaxis del mismo.
FUOC XP04/90793/00018
En el programa siguiente muestra el cdigo devuelto por la ejecucin de un comando, que hay que introducir entre comillas como argumento del programa: /* Fichero: ejecuta.c */ #include <stdio.h> #include <stdlib.h> int main( int argc, char *argv[] ) { int codigo; if( argc == 2 ) { codigo = system( argv[1] ); printf( "%i = %s\n", codigo, argv[1] ); } else { printf( "Uso: ejecuta \"comando\"\n" ); } /* if */ return 0; } /* main */ Aunque simple, el programa anterior nos da una cierta idea de cmo emplear la funcin system.. El conjunto de rutinas y servicios que ofrece el sistema operativo va ms all de dar soporte a las funciones de entrada y salida de datos, de manejo de ficheros y de gestin de memoria, tambin permite lanzar la ejecucin de programas dentro de otros. En el apartado siguiente, se tratar con ms profundidad el tema de la ejecucin de programas.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Normalmente, a pesar de que el sistema operativo sea capaz de gestionar una mquina con varios procesadores, habr ms programas a ejecutar que recursos para ello. Por este motivo, siempre tendr que poder planificar la ejecucin de un determinado conjunto de programas en un nico procesador. La planificacin puede ser: Secuencial. Una vez finalizada la ejecucin de un programa, se inicia la del programa siguiente (tambin se conoce como ejecucin por lotes). Intercalada. Cada programa dispone de un cierto tiempo en el que se lleva a cabo una parte de su flujo de ejecucin de instrucciones y, al trmino del periodo de tiempo asignado, se ejecuta una parte de otro programa. De esta manera, se pueden ejecutar diversos programas en un mismo intervalo de tiempo dando la sensacin de que su ejecucin progresa paralelamente. Apoyndose en los servicios (funciones) que ofrece el SO al respecto de la ejecucin del software, es posible, entre otras cosas, ejecutarlo disociado de la entrada/salida estndar y/o partir su flujo de ejecucin de instrucciones en varios paralelos.
ANOTACIONES
FUOC XP04/90793/00018
Los procesos que comparten un mismo entorno o estado, con excepcin evidente de la referencia a la instruccin siguiente, son denominados hilos o hebras (en ingls, threads), mientras que los que tienen entornos distintos son denominados simplemente procesos. Con todo, un programa puede organizar su cdigo de manera que lleve a trmino su tarea mediante varios flujos de instrucciones paralelos, sean stos simples hilos o procesos completos.
Nota
Literalmente, un demonio es un espritu maligno, aunque se supone que los procesos denominados como tales no deberan serlo.
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
Esta llamada consigue que el cuerpo del programa sea un demonio que trabaja en el directorio raz (como si hubiera hecho un cd / ) y que est disociado de las entradas y salidas estndar (en realidad, redirigidas al dispositivo vaco: /dev/null). La funcin devuelve un cdigo de error, que es cero si todo ha ido bien.
Ejemplo
Para ilustrar el funcionamiento de los demonios, se muestra un programa que avisa al usuario de que ha transcurrido un cierto tiempo. Para ello, habr que invocar al programa con dos parmetros: uno para indicar las horas y minutos que deben transcurrir antes de advertir al usuario y otro que contenga el texto del aviso. En este caso, el programa se convertir en un demonio no disociado del terminal de entrada/salida estndar, puesto que el aviso aparecer en el mismo.
/* Fichero: alarma.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "bool.h" int main( int argc, char *argv[] )
{ unsigned int horas; unsigned int minutos; unsigned int segundos;
*/
ANOTACIONES
cha
*aviso, *separador;
if( argc == 3 ) { separador = strchr( argv[1], : ' ); if( separador != NULL ) { horas = atoi( argv[1] ); minutos = atoi( separador+1 ); } else { horas = 0;
FUOC XP04/90793/00018
} /* if */ segundos = (horas*60 + minutos) * 60; aviso = argv[2]; if( daemon( FALSE, TRUE ) ) { printf( No puede instalarse el avisador :-(\n ); } else { printf( Alarma dentro de %i horas y %i minutos.\n, horas, minutos ); /* printf */ printf( Haz $ kill %li para apagarla.\n, getpid() ); /* printf */ } /* if */ sleep( segundos ); printf( %s\007\n, aviso ); printf( Alarma apagada.\n ); } else { printf( Uso: %s horas:minutos \aviso\\n, argv[0] ); } /* if */ return 0; } /* main */
La lectura de los parmetros de entrada ocupa buena parte del cdigo. En particular, lo que necesita ms atencin es la extraccin de las horas y de los minutos; para ello, se buscan los dos puntos (con strchr, declarada en string.h) y luego se toma la cadena entera para determinar el valor de las horas y la cadena a partir de los dos puntos para los minutos. La espera se realiza mediante una llamada a la funcin sleep, que toma como argumento el nmero de segundos en los que el progra-
Finalmente, para dar al usuario la posibilidad de parar la alarma, se le informa del comando que debe introducir en el shell para matar el proceso (es decir, para finalizar su ejecucin). A tal efecto, se le muestra el nmero de proceso que se corresponde con el demonio instalado. Este identificador se consigue llamando a la funcin getpid(), en el que PID significa, precisamente, identificador de proceso
187
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Uno de los usos fundamentales de los demonios es el de la implementacin de procesos proveedores de servicios.
Cuando dos o ms procesos comparten un mismo procesador, no hay ms remedio que ejecutarlos por tramos en un determinado perodo de tiempo dentro del que, efectivamente, se puede observar una evolucin progresiva de los mismos.
ANOTACIONES
188
Aumentar el rendimiento respecto de la entrada/salida de datos. Para aumentar el rendimiento respecto de la E/S del programa puede resultar conveniente que uno de los procesos se ocupe de la relacin con la entrada de datos, otro del clculo que haya que hacer con ellos y, finalmente, otro de la salida de resultados. De esta manera, es posible realizar el clculo sin detenerse a dar salida a los resultados o esperar datos de entrada. Ciertamente, no siempre cabe hacer tal particin y el nmero de procesos puede variar mucho en funcin de las necesidades del
FUOC XP04/90793/00018
programa. De hecho, en este caso se trata, fundamentalmente, de separar procesos de naturaleza lenta (por ejemplo, los que deben comunicarse con otros bien para recibir bien para transmitir datos) de otros procesos ms rpidos, es decir, con mayor atencin al clculo. En los siguientes apartados se comentan varios casos de programacin concurrente tanto con procesos ligeros (los hilos) como con procesos completos o pesados (por contraposicin a los ligeros).
3.14. Hilos
Un hilo, hebra o thread es un proceso que comparte el entorno con otros del mismo programa, lo que comporta que el espacio de memoria sea el mismo. Por tanto, la creacin de un nuevo hilo slo implica disponer de informacin sobre el estado del procesador y la instruccin siguiente para el mismo. Precisamente por este motivo son denominados procesos ligeros.
Los hilos son flujos de ejecucin de instrucciones independientes que tienen mucha relacin entre s.
Para emplearlos en C sobre Linux, es necesario hacer llamadas a funciones de hilos del estndar de POSIX. Este estndar define una interfaz portable de sistemas operativos (originalmente, Unix) para acrnimo. Las funciones de POSIX para hilos estn declaradas en el fichero pthread.h y se debe de enlazar el archivo de la biblioteca correspondiente con el programa. Para ello, hay que compilar con el comando siguiente: $ gcc o ejecutable codigo.c lpthread
189
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
La opcin lpthread indica al enlazador que debe incluir tambin la biblioteca de funciones POSIX para hilos.
3.14.1. Ejemplo
Mostraremos un programa para determinar si un nmero es primo o no como ejemplo de un programa desenhebrado en dos hilos. El hilo principal se ocupar de buscar posibles divisores mientras que el secundario actuar de observador para el usuario: leer los datos que maneja el hilo principal para mostrarlos por el terminal de salida estndar. Evidentemente, esto es posible porque comparten el mismo espacio de memoria. La creacin de los hilos requiere que su cdigo est dentro de una funcin que slo admite un parmetro del tipo (void *). De hecho las funciones creadas para ser hilos POSIX deben obedecer a la cabecera siguiente: (void *)hilo( void *referencia_parmetros ); As pues, ser necesario colocar en una tupla toda la informacin que se quiera hacer visible al usuario y pasar su direccin como parmetro de la misma. Pasamos a definir la tupla de elementos que se mostrar: /* ... */ typedef struct s_visible { unsigned long numero; unsigned long divisor; bool fin; } t_visible; /* ... */
Nota
ANOTACIONES
190
El campo fin servir para indicar al hilo hijo que el hilo principal (el padre) ha acabado su tarea. En este caso, ha determinado si el nmero es o no, primo.
FUOC XP04/90793/00018
/* ... */ void *observador( void *parametro ) { t_visible *ref_vista; ref_vista = (t_visible *)parametro; printf( " ... probando %012lu", 0 ); do { printf( "\b\b\b\b\b\b\b\b\b\b\b\b" ); printf( "%12lu", ref_vistadivisor ); } while( !(ref_vistafin) ); printf( "\n" ); return NULL; } /* observador */ /* ... */
Nota
El carcter '\b' se corresponde con un retroceso y que, dado que los nmeros se imprimen con 12 dgitos (los ceros a la izquierda se muestran como espacios), la impresin de 12 retrocesos implica borrar el nmero que se haya escrito anteriormente.
Para
crear
el
hilo
del
observador,
basta
con
llamar
pthread_create() con los argumentos adecuados. A partir de ese momento, un nuevo hilo se ejecuta concurrentemente con el cdigo del programa: /* ... */ int main( int argc, char *argv[] ) { int pthread_t t_visible bool codigo_error; /* Cdigo de error a devolver. */ id_hilo; vista; resultado; /* Identificador del hilo. /* Datos observables. /* Indicador de si es primo.
191
*/ */ */
ANOTACIONES
FUOC XP04/90793/00018
Software libre
{ int pthread_t t_visible bool codigo_error; id_hilo; vista; resultado; /* /* /* /* Cdigo de error a devolver. Identificador del hilo. Datos observables. Indicador de si es primo. */ */ */ */
if( argc == 2 ) { vista.numero = atol( argv[1] ); vista.fin = FALSE; codigo_error = pthread_create( &id_hilo, /* Referencia en la que poner el ID. NULL, /* Referencia a posibles atributos. observador, /* Funcin que ejecutar el hilo. (void *)&vista /* Argumento de la funcin. ); /* pthread_create */ if( codigo_error == 0 ) { resultado = es_primo( &vista ); vista.fin = TRUE; pthread_join( id_hilo, NULL ); if( resultado )printf( Es primo.\n ); else printf( No es primo.\n ); codigo_error = 0; } else { printf( No he podido crear un hilo observador!\n ); codigo_error = 1; } /* if */ } else { printf( Uso: %s nmero\n, argv[0] ); codigo_error = -1; } /* if */ return codigo_error; } /* main */
*/ */ */ */
Nota
Despus de crear el hilo del observador, se comprueba que el nmero sea primo y, al regresar de la funcin es_primo(), se pone el campo fin a TRUE para que el observador finalice su ejecucin. Para esperar a que efectivamente haya acabado, se llama a pthread_join(). Esta funcin espera a que el hilo cuyo identificador se haya dado como primer argumento llegue al final de su ejecucin y, por tanto, se produzca una unin de los hilos (de ah el apelativo en ingls join). El segundo argumento se emplea para recoger posibles datos devueltos por el hilo.
192
ANOTACIONES
FUOC XP04/90793/00018
Para terminar el ejemplo, sera necesario codificar la funcin es_primo(), que tendra como cabecera la siguiente: /* ... */ bool es_primo( t_visible *ref_datos ); /* ... */ La programacin se deja como ejercicio. Para resolverlo adecuadamente, cabe tener presente que hay que emplear ref_datos->divisor como tal, puesto que la funcin observador() la lee para mostrarla al usuario. En este caso, no existe ningn problema en que los hilos de un programa tengan el mismo espacio de memoria; es decir, el mismo entorno o contexto. De todas maneras, suele ser habitual que el acceso a datos compartidos por ms de un hilo sea sincronizado. En otras palabras, que se habilite algn mecanismo para impedir que dos hilos accedan simultneamente al mismo dato, especialmente para modificarlo, aunque tambin para que los hilos lectores lean los datos debidamente actualizados. Estos mecanismos de exclusin mutua son, de hecho, convenios de llamadas a funciones previas y posteriores al acceso a los datos.
Nota
Se puede imaginar como funciones de control de un semforo de acceso a una plaza de aparcamiento: si est libre, el semforo estar en verde y, si est ocupada, estar en rojo hasta que se libere.
3.15. Procesos
Un proceso es un flujo de ejecucin de instrucciones con un entorno propio y, por tanto, con todas las atribuciones de un programa. Puede dividir, pues, el flujo de ejecucin de instrucciones en otros procesos (ligeros o no) si as se considera conveniente por razones de eficiencia.
193
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En este caso, la generacin de un nuevo proceso a partir del proceso principal implica realizar la copia de todo el entorno de este ltimo. Con ello, el proceso hijo tiene una copia exacta del entorno del padre en el momento de la divisin. A partir de ah, tanto los contenidos de las variables como la indicacin de la instruccin siguiente puede divergir. De hecho, actan como dos procesos distintos con entornos evidentemente diferentes del mismo cdigo ejecutable. Dada la separacin estricta de los entornos de los procesos, generalmente stos se dividen cuando hay que realizar una misma tarea sobre datos distintos, que lleva a cabo cada uno de los procesos hijos de forma autnoma. Por otra parte, existen tambin mecanismos para comunicar procesos entre s: las tuberas, las colas de mensajes, las variables compartidas (en este caso, se cuenta con funciones para implementar la exclusin mutua) y cualquier otro tipo de comunicacin que pueda establecerse entre procesos distintos. Para crear un nuevo proceso, basta con llamar a fork(), cuya declaracin se encuentra en unistd.h, y que devuelve el identificador del proceso hijo en el padre y cero en el nuevo proceso:
Figura 11.
ANOTACIONES
194
Nota
El hecho de que fork() devuelva valores distintos en el proceso padre y en el hijo permite a los flujos de instrucciones siguientes determinar si pertenecen a uno u otro.
FUOC XP04/90793/00018
El programa siguiente es un ejemplo simple de divisin de un proceso en dos: el original o padre y la copia o hijo. Para simplificar, se supone que tanto el hijo como el padre realizan una misma tarea. En este caso, el padre espera a que el hijo finalice la ejecucin con wait(), que requiere de la inclusin de los ficheros sys/types.h y sys/wait.h: /* Fichero: ej_fork.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> /* ... */ int main( void ) { pid_t int proceso; estado; */
printf( "Proceso padre (%li) iniciado.\n", getpid() ); proceso = fork(); if( proceso == 0 ) { printf( "Proceso hijo (%li) iniciado.\n", getpid() ); tarea( "hijo" ); printf( "Fin del proceso hijo.\n" ); } else { tarea( "padre" ); wait( &estado ); /* Espera la finalizacin del hijo. printf( "Fin del proceso padre.\n" ); } /* if */ return 0; } /* main */ Para poner de manifiesto que se trata de dos procesos que se ejecutan paralelamente, resulta conveniente que las tareas del padre y del hijo sean distintas o que se hagan con datos de entrada diferentes.
195
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Para ilustrar tal caso, se muestra una posible programacin de la funcin, tarea en la que se hace una repeticin de esperas. Para poder observar que la ejecucin de los dos procesos puede no estar intercalada siempre de la misma manera, tanto el nmero de repeticiones como el tiempo de las esperas se pone en funcin de nmeros aleatorios provistos por la funcin random(), que arranca con un valor semilla calculado mediante srandom() con un argumento que vare con las distintas ejecuciones y con el tipo de proceso (padre o hijo): /* ... */ void tarea( char *nombre ) { unsigned int contador; srandom( getpid() % ( nombre[0] * nombre[2]) ); contador = random() % 11 + 1; while( contador > 0 ) { printf( "... paso %i del %s\n", contador, nombre ); sleep( random() % 7 + 1 ); contador = contador - 1; } /* while */ } /* tarea */ /* ... */ En el ejemplo anterior, el padre espera a un nico hijo y realiza una misma tarea. Esto, evidentemente, no es lo habitual. Es mucho ms comn que el proceso padre se ocupe de generar un proceso hijo para cada conjunto de datos a procesar. En este caso, el programa principal se complica ligeramente y hay que seleccionar en funcin del valor devuelto por fork() si las instrucciones pertenecen al padre o a uno de los hijos. Para ilustrar la codificacin de tales programas, se muestra un programa que toma como argumentos una cantidad indeterminada de nmeros naturales para los que averigua si se trata o no, de nmeros primos. En este caso, el programa principal crear un proceso hijo para cada nmero natural a tratar:
ANOTACIONES
196
FUOC XP04/90793/00018
/* ... */ int main( int argc, char *argv[] ) { int unsigned long int pid_t int contador; numero, divisor; proceso; estado;
if( argc > 1 ) { proceso = getpid(); printf( "Proceso %li iniciado.\n", proceso ); contador = 1; while( proceso != 0 && contador < argc ) { /* Creacin de procesos hijo: */ numero = atol( argv[ contador ] ); contador = contador + 1; proceso = fork(); if( proceso == 0 ) { printf( "Proceso %li para %lu\n", getpid(), numero ); /* printf */ divisor = es_primo( numero ); if( divisor > 1 ) { printf( "%lu no es primo.\n", numero );
printf( Su primer divisor es %lu\n, divisor );
} else { printf( "%lu es primo.\n", numero ); } /* if */ } /* if */ } /* while */ while( proceso != 0 && contador > 0 ) {
} /* while */ if( proceso !=0 ) printf( "Fin.\n"); } else { printf( "Uso: %s natural_1 ... natural_N\n", argv[0] ); } /* if */ return 0; } /* main */
197
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
El bucle de creacin se interrumpe si proceso==0 para evitar que los procesos hijo puedan crear nietos con los mismos datos que algunos de sus hermanos. Hay que tener presente que el cdigo del programa es el mismo tanto para el proceso padre, como para el hijo. Por otra parte, el bucle final de espera slo debe aplicarse al padre, es decir, al proceso en el que se cumpla que la variable proceso sea distinta de cero. En este caso, basta con descontar del contador de procesos generados una unidad para cada espera cumplida. Para comprobar su funcionamiento, falta disear la funcin es_primo(), que queda como ejercicio. Para ver el funcionamiento de tal programa de manera ejemplar, es conveniente introducir algn nmero primo grande junto con otros menores o no primos.
ANOTACIONES
FUOC XP04/90793/00018
Recordad que en la unidad anterior ya se defini tubera. Una tubera consiste, de hecho, en dos ficheros de flujo de bytes, uno de entrada y otro de salida, por el que se comunican dos procesos distintos.
Como se puede apreciar en el cdigo siguiente, la funcin para abrir una tubera se llama pipe() y toma como argumento la direccin de un vector de dos enteros en donde depositar los descriptores de los ficheros de tipo stream que haya abierto: en la posicin 0 el de salida, y en la 1, el de entrada. Despus del fork(), ambos procesos tienen una copia de los descriptores y, por tanto, pueden acceder a los mismos ficheros tanto para entrada como para salida de datos. En este caso, el proceso hijo cerrar el fichero de entrada y el padre, el de salida; puesto que la tubera slo comunicar los procesos en un nico sentido: de hijo a padre. (Si la comunicacin se realizara en ambos sentidos, sera necesario establecer un protocolo de acceso a los datos para evitar conflictos.): /* ... */ int main( int argc, char *argv[] ) { unsigned long int pid_t int int if( argc == 2 ) { numero = atol( argv[ 1 ] ); if( pipe( desc_tuberia ) != -1 ) { proceso = fork(); if( proceso == 0 ) { /* Proceso hijo: close( desc_tuberia[0] ); divisores_de( numero, desc_tuberia[1] ); close( desc_tuberia[1] ); } else { /* Proceso principal o padre:
199
*/
*/
ANOTACIONES
FUOC XP04/90793/00018
Software libre
close( desc_tuberia[1] ); muestra_divisores( desc_tuberia[0] ); wait( &estado ); close( desc_tuberia[0] ); printf( "Fin.\n" ); } /* if */ } else { printf( "No puedo crear la tuberia!\n" ); } /* if */ } else { printf( "Uso: %s numero_natural\n", argv[0] ); } /* if */ return 0; } /* main */ Con todo, el cdigo de la funcin muestra_divisores() en el proceso padre podra ser como el que se muestra a continuacin. En l, se emplea la funcin de lectura read(), que intenta leer un determinado nmero de bytes del fichero cuyo descriptor se le pasa como primer argumento. Devuelve el nmero de bytes efectivamente ledos y su contenido lo deposita a partir de la direccin de memoria indicada: /* ... */ void muestra_divisores( int desc_entrada ) { size_t nbytes; factor_t factor; do { nbytes = read( desc_entrada, (void *)&factor, sizeof( factor_t ) ); /* read */ if( nbytes > 0 ) { printf( "%lu ^ %lu\n", factor.divisor, factor.potencia ); /* printf */ } while( nbytes > 0 ); } /* muestra_divisores */ /* ... */
200
ANOTACIONES
FUOC XP04/90793/00018
Para completar el ejemplo, se muestra una posible programacin de la funcin divisores_de() en el proceso hijo. Esta funcin emplea write() para depositar los factores recin calculados en el fichero de salida de la tubera: /* ... */ void divisores_de( unsigned long int { factor_t f; int numero, desc_salida )
f.divisor = 2; while( numero > 1 ) { f.potencia = 0; while( numero % f.divisor == 0 ) { f.potencia = f.potencia + 1; numero = numero / f.divisor; } /* while */ if( f.potencia > 0 ) { write( desc_salida, (void *)&f, sizeof( factor_t ) ); } /* if */ f.divisor = f.divisor + 1; } /* while */ } /* divisores_de */ /* ... */
Con este ejemplo se ha mostrado una de las posibles formas de comunicacin entre procesos. En general, cada mecanismo de comunicacin tiene unos usos preferentes
Nota
Las tuberas son adecuadas para el paso de una cantidad relativamente alta de datos entre procesos, mientras que las colas de mensajes se adaptan mejor a procesos que se comunican poco frecuentemente o de forma irregular.
201
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En todo caso, hay que tener presente que repartir las tareas de un programa en varios procesos supondr un cierto incremento de la complejidad por la necesaria introduccin de mecanismos de comunicacin entre ellos. As pues, es importante valorar los beneficios que tal divisin pueda aportar al desarrollo del programa correspondiente.
3.16. Resumen
Los algoritmos que se emplean para procesar la informacin pueden ser ms o menos complejos segn la representacin que se escoja para la misma. Como consecuencia, la eficiencia de la programacin est directamente relacionada con las estructuras de datos que se empleen en sta. Por este motivo se han introducido las estructuras dinmicas de datos, que permiten, entre otras cosas, aprovechar mejor la memoria y cambiar la relacin entre ellos como parte del procesado de la informacin. Las estructuras de datos dinmicas son, pues, aqullas en las que el nmero de datos puede variar durante la ejecucin del programa y cuyas relaciones, evidentemente, pueden cambiar. Para ello, se apoyan en la creacin y destruccin de variables dinmicas y en los mecanismos para acceder a ellas. Fundamentalmente, el acceso a tales variables se debe hacer mediante apuntadores, puesto que las variables dinmicas no disponen de nombres con los que identificarlas. Se ha visto tambin un ejemplo comn de estructuras de datos dinmicas como las cadenas de caracteres y las listas de nodos. En particular, para este ltimo caso se ha revisado no slo la posible programacin de las funciones de gestin de los nodos en una lista, sino tambin una forma especial de tratamiento de las mismas en la que se emplean como representaciones de colas. Dado lo habitual del empleo de muchas de estas funciones para estructuras de datos dinmicas comunes, resulta conveniente agruparlas en archivos de ficheros objeto: las bibliotecas de funciones. De
202
ANOTACIONES
FUOC XP04/90793/00018
esta manera, es posible emplear las mismas funciones en programas diversos sin preocuparse de su programacin. Aun as, es necesario incluir los ficheros de cabecera para indicar al compilador la forma de invocar a tales funciones. Con todo, se repasa el mecanismo de creacin de bibliotecas de funciones y, adems, se introduce el uso de la utilidad make para la generacin de ejecutables que resultan de la compilacin de diversas unidades del mismo programa y de los archivos de biblioteca requeridos. Por otra parte, tambin se ha visto cmo la relacin entre los distintos tipos de datos abstractos de un programa facilitan la programacin modular. De hecho, tales tipos se clasifican segn niveles de abstraccin o, segn se mire, de dependencia de otros tipos de datos. As pues, el nivel ms bajo de abstraccin lo ostentan los tipos de datos abstractos que se definen en trminos de tipos de datos primitivos. De esta manera, el programa principal ser aquel que opere con los tipos de datos de mayor nivel de abstraccin. El resto de mdulos del programa sern los que provean al programa principal de las funciones necesarias para realizar tales operaciones. Por lo tanto, el diseo descendente de algoritmos, basado en la jerarqua que se establece entre los distintos tipos de datos que emplean, es una tcnica con la que se obtiene una programacin modular eficiente. En la prctica, cada tipo de datos abstracto deber acompaarse de las funciones para operaciones elementales como creacin, acceso a datos, copia, duplicado y destruccin de las variables dinmicas correspondientes. Ms aun, deber estar contenida en una becera adecuado. Finalmente, en el ltimo captulo, se ha insistido en la organizacin del cdigo, no tanto con relacin a la informacin que debe procesar, sino ms en relacin con la forma de hacerlo. En este sentido, resulta conveniente aprovechar al mximo las facilidades que nos ofrece el lenguaje de programacin C para utilizar las rutinas de servicio del sistema operativo.
203
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Cuando la informacin a tratar deba ser procesada por otro programa, es posible ejecutarlos desde el flujo de ejecucin de instrucciones del que se est ejecutando. En este caso, sin embargo, la comunicacin entre el programa llamado y el llamador es mnima. Como consecuencia, debe ser el mismo programa llamado el que obtenga la mayor parte de la informacin a tratar y el que genere el resultado. Se ha tratado tambin de la posibilidad de dividir el flujo de ejecucin de instrucciones en varios flujos diferentes que se ejecutan concurrentemente. De esta manera, es posible especializar cada flujo en un determinado aspecto del tratamiento de la informacin o, en otros casos, realizar el mismo tratamiento sobre partes distintas de la informacin. Los flujos de ejecucin de instrucciones se pueden dividir en hilos (threads) o procesos. A los primeros tambin se les denomina procesos ligeros, pues son procesos que comparten el mismo contexto (entorno) de ejecucin. El tipo de tratamiento de la informacin ser el que determine qu forma de divisin es la mejor. Como norma se puede tomar la del grado de compartimiento de la informacin: si es alto, entonces es mejor un hilo, y si es bajo, un proceso (entre ellos, no obstante, hay diversos mecanismos de comunicacin segn el grado particular de relacin que tengan). En todo caso, parte del contenido de esta unidad se ver de nuevo en las prximas pues, tanto C++ como Java, facilitan la programacin con tipos de datos abstractos, el diseo modular y la distribucin de la ejecucin en diversos flujos de instrucciones.
ANOTACIONES
FUOC XP04/90793/00018
palabra_t siguiente_palabra( char *frase, unsigned int inicio ) { /* ... */ } int main( void ) { FILE char palabra_t *entrada; nombre[BUFSIZ]; palabra, palabra2;
unsigned int numlin, pos; printf( "Busca palabras.\n" ); printf( "Fichero: "); gets( nombre ); entrada = fopen( nombre, "rt" ); if( entrada != NULL ) { printf( "Palabra: "); gets( nombre ); palabra = siguiente_palabra( nombre, 0 ); printf( "Buscando %s en fichero...\n", palabra ); numlin = 1; while( fgets( nombre, BUFSIZ-1, entrada ) != NULL ) { numlin = numlin + 1; pos = 0; palabra2 = siguiente_palabra( nombre, pos ); while( palabra2 != NULL ) { if( !strcmp( palabra, palabra2 ) ) { printf( "... lnea %lu\n", numlin ); } /* if */ pos = pos + strlen( palabra2 ); free( palabra2 ); palabra2 = siguiente_palabra( nombre, pos ); } /* while */ free( palabra ); fclose( entrada ); printf( "Fin.\n" ); } else { printf( "No puedo abrir %s!\n", nombre ); } /* if */ return 0; } /* main */
205
ANOTACIONES
} /* while */
FUOC XP04/90793/00018
Software libre
Se debe de programar, pues, la funcin siguiente_palabra(). 2) Componed, a partir de las funciones provistas en el apartado 3.5.2, la funcin para eliminar el elemento ensimo de una lista de enteros. El programa principal deber ser el siguiente:
int main( void ) { lista_t char int unsigned int
printf( Gestor de listas de enteros.\n ); lista = NULL; do { printf( [I]nsertar, [E]liminar, [M]ostrar o [S]alir? ); /* printf */ do opcion = getchar(); while( isspace(opcion) ); opcion = toupper( opcion ); switch( opcion ) { case I' : printf( Dato =? ); scanf( %i, &dato ); printf( Posicion =? ); scanf( %u, &n ); if( !inserta_enesimo_lista( &lista, n, dato ) ) { printf( No se insert.\n ); } /* if */ break; case E' : printf( Posicion =? ); scanf( %u, &n ); if( elimina_enesimo_lista( &lista, n, &dato ) ) { printf( Dato = %i\n, dato ); } else { printf( No se elimin.\n ); } /* if */ break; case ' M': muestra_lista( lista ); break; } /* switch */ } while( opcion != 'S' ); while( lista != NULL ) { elimina_enesimo_lista( &lista, 0, &dato ); } /* while */ printf( Fin.\n ); return 0; } /* main */
206
ANOTACIONES
FUOC XP04/90793/00018
Tambin hay que programar la funcin muestra_lista() para poder ver su contenido. 3) Haced un programa que permita insertar y eliminar elementos de una cola de enteros. Las funciones que deben emplearse se encuentran en el apartado referente a colas del apartado 3.5.2. Por lo tanto, slo cabe desarrollar la funcin principal de dicho programa, que puede inspirarse en la mostrada en el ejercicio anterior. 4) Programad el algoritmo de ordenacin por seleccin visto en el apartado 3.6 para clasificar un fichero de texto en el que cada lnea tenga el formato siguiente: DNI nota '\n' Por tanto, los elementos sern del mismo tipo de datos que el visto en el ejemplo. El programa principal ser:
int main( void ) { FILE char lista_t ref_nodo_t elemento_t *entrada; nombre[ BUFSIZ ]; pendientes, ordenados; refnodo; elemento;
printf( Ordena listado de nombres.\n ); printf( Fichero =? ); gets( nombre ); entrada = fopen( nombre, rt ); if( entrada != NULL ) { inicializa_lista( &pendientes ); while( fgets( nombre, BUFSIZ-1, entrada ) != NULL ) { pon_al_final_de_lista( &pendientes, elemento ); } /* if */ inicializa_lista( &ordenados ); while( ! esta_vacia_lista( pendientes ) ) { elemento = extrae_minimo_de_lista( &pendientes ); pon_al_final_de_lista( &ordenados, elemento ); } /* while */ printf( Lista ordenada por DNI:\n ); principio_de_lista( &ordenados );
207
ANOTACIONES
FUOC XP04/90793/00018
Software libre
while( !es_final_de_lista( ordenados ) ) { refnodo = ref_nodo_de_lista( ordenados ); elemento = elemento_en_ref_nodo(ordenados, refnodo); muestra_elemento( elemento ); avanza_posicion_en_lista( &ordenados ); } /* while */ printf( Fin.\n ); } else { printf( No puedo abrir %s!\n, nombre ); } /* if */ return 0; } /* main */
Por lo tanto, tambin ser necesario programar las funciones siguientes: elemento_t crea_elemento( unsigned int DNI,
float nota ); elemento_t lee_elemento( char *frase ); void muestra_elemento( elemento_t elemento );
Nota
En este caso, los elementos de la lista no son destruidos antes de finalizar la ejecucin del programa porque resulta ms simple y, adems, se sabe que el espacio de memoria que ocupa se liberar en su totalidad. Aun as, no deja de ser una mala prctica de la programacin y, por lo tanto, se propone como ejercicio libre, la incorporacin de una funcin para eliminar las variables dinmicas correspondientes a cada elemento antes de acabar la ejecucin del programa.
ANOTACIONES
208
5) Implementad el programa anterior en tres unidades de compilacin distintas: una para el programa principal, que tambin puede dividirse en funciones ms manejables, una para los elementos y otra para las listas, que puede transformarse en biblioteca. 6) Haced un programa que acepte como argumento un NIF y valide la letra. Para ello, tmese como referencia el ejercicio de autoevaluacin nmero 7 de la unidad anterior.
FUOC XP04/90793/00018
7) Transformad la utilidad de bsqueda de palabras en ficheros de texto del primer ejercicio para que tome como argumentos en la lnea de comandos tanto la palabra a buscar como el nombre del fichero de texto en el que tenga que realizar la bsqueda. 8) Cread un comando que muestre el contenido del directorio como si de un ls als | more se tratara. Para ello, hay que hacer un programa que ejecute tal mandato y devuelva el cdigo de error correspondiente. 9) Programad un despertador para que muestre un aviso cada cierto tiempo o en una hora determinada. Para ello, tomar como referencia el programa ejemplo visto en la seccin de procesos permanentes. El programa tendr como argumento la hora y minutos en que se debe mostrar el aviso indicado, que ser el segundo argumento. Si la hora y minutos se precede con el signo '+', entonces se tratar como en el ejemplo, es decir, como el lapso de tiempo que debe pasar antes de mostrar el aviso. Hay que tener presente que la lectura del primer valor del primer argumento puede hacerse de igual forma que en el programa avisador del tema, puesto que el signo '+' se interpreta como indicador de signo del mismo nmero. Eso s, hay que leer especficamente argv[1][0] para saber si el usuario ha introducido el signo o no. Para saber la hora actual, es necesario emplear las funciones de biblioteca estndar de tiempo, que se encuentran declaradas en time.h, y cuyo uso se muestra en el programa siguiente: /* Fichero: horamin.c #include <stdio.h> #include <time.h> int main( void ) { time_ttiempo; struct tm *tiempo_desc; time( &tiempo ); tiempo_desc = localtime( &tiempo ); printf( "Son las %2d y %2d minutos.\n",
209
ANOTACIONES
*/
FUOC XP04/90793/00018
Software libre
tiempo_desc->tm_hour, tiempo_desc->tm_min ); /* printf */ return 0; } /* main */ 10) Probad los programas de deteccin de nmeros primos mediante hilos y procesos. Para ello, es necesario definir la funcin es_primo() de manera adecuada. El siguiente programa es una muestra de tal funcin, que aprovecha el hecho de que ningn divisor entero ser mayor que la raz cuadrada del nmero (se aproxima por la potencia de 2 ms parecida): /* Fichero: es_primo.c #include <stdio.h> #include "bool.h" int main( int argc, char *argv[] ) { unsigned long int numero, maximo, divisor; bool primo; if( argc == 2 ) { numero = atol( argv[1] ); primo = numero < 4; /* 0 ... 3, considerados primos. if( !primo ) { divisor = 2; primo = numero % divisor != 0; if( primo ) { maximo = numero / 2; while( maximo*maximo > numero ) maximo = maximo/2; maximo = maximo * 2; divisor = 1; while( primo && (divisor < maximo) ) { divisor = divisor + 2; primo = numero % divisor != 0; } /* while */ } /* if */ } /* if */ printf( "... %s primo.\n", primo? "es" : "no es" ); } else { printf( "Uso: %s nmero_natural\n", argv[0] ); } /* if */ return 0; } /* main */
210
*/
*/
ANOTACIONES
FUOC XP04/90793/00018
3.17.1. Solucionario
1) Como ya tenemos el programa principal, es suficiente con mostrar la funcin siguiente_palabra: palabra_t siguiente_palabra( char { unsigned int fin, longitud; palabra_t palabra; *frase, unsigned int inicio)
while( frase[inicio]!='\0' && !isalnum(frase[inicio]) ) { inicio = inicio + 1; } /* while */ fin = inicio; while( frase[fin]!='\0' && isalnum( frase[fin] ) ) { fin = fin + 1; } /* while */ longitud = fin - inicio; if( longitud > 0 ) { palabra = (palabra_t)malloc((longitud+1)*sizeof(char)); if( palabra != NULL ) { strncpy( palabra, &(frase[inicio]), longitud ); palabra[longitud] = '\0'; } /* if */ } else { palabra = NULL; } /* if */ return palabra; } /* siguiente_palabra */ 2) bool elimina_enesimo_lista( lista_t *listaref, /* Apuntador a referencia 1er nodo.*/ unsigned int n, int { nodo_t *p, *q, *t; bool retval;
211
*/ */ */
*datoref)
ANOTACIONES
FUOC XP04/90793/00018
Software libre
enesimo_pq_nodo( *listaref, n, &p, &q ); if( q != NULL ) { *datoref = destruye_nodo( listaref, p, q ); retval = TRUE; } else { retval = FALSE; } /* if */ return retval; } /* elimina_enesimo_lista */ void muestra_lista( lista_t lista ) { nodo_t *q; if( lista != NULL ) { q = lista; printf( "Lista = " ); while( q != NULL ) { printf( "%i ", q->dato ); q = q->siguiente; } /* while */ printf( "\n" ); } else { printf( "Lista vaca.\n" ); } /* if */ } /* muestra_lista */ 3) int main( void ) { cola_t char int cola; opcion; dato;
ANOTACIONES
printf( "Gestor de colas de enteros.\n" ); cola.primero = NULL; cola.ultimo = NULL; do { printf( "[E]ncolar, [D]esencolar, [M]ostrar o [S]alir? " ); /* printf */ do opcion = getchar(); while( isspace(opcion) ); opcion = toupper( opcion );
212
FUOC XP04/90793/00018
switch( opcion ) { case 'E': printf( "Dato =? "); scanf( "%i", &dato ); if( !encola( &cola, dato ) ) { printf( "No se insert.\n" ); } /* if */ break; case 'D': if( desencola( &cola, &dato ) ) { printf( "Dato = %i\n", dato ); } else { printf( "No se elimin.\n" ); } /* if */ break; case 'M': muestra_lista( cola.primero ); break; } /* switch */ } while( opcion != 'S' ); while( desencola( &cola, &dato ) ) { ; } printf( "Fin.\n" ); return 0; } /* main */ 4) elemento_t crea_elemento( unsigned int DNI, float nota ) { elemento_t elemento; elemento = (elemento_t)malloc( sizeof( dato_t ) ); if( elemento != NULL ) { elemento->DNI = DNI; elemento->nota = nota; } /* if */ return elemento; } /* crea_elemento */ elemento_t lee_elemento( char *frase ) { unsigned int DNI; double nota; int leido_ok;
213
ANOTACIONES
FUOC XP04/90793/00018
Software libre
elemento_t elemento; leido_ok = sscanf( frase, "%u%lf", &DNI, ¬a ); if( leido_ok == 2 ) { elemento = crea_elemento( DNI, nota ); } else { elemento = NULL; } /* if */ return elemento; } /* lee_elemento */ void muestra_elemento( elemento_t elemento ) { printf( "%10u %.2f\n", elemento->DNI, elemento->nota ); } /* muestra_elemento */ 5) Vase el apartado 3.6.2. 6) char letra_de( unsigned int DNI ) { char codigo[] = "TRWAGMYFPDXBNJZSQVHLCKE" ; return codigo[ DNI % 23 ]; } /* letra_de */ int main( int argc, char *argv[] ) { unsigned int DNI; char letra; int codigo_error; if( argc == 2 ) { sscanf( argv[1], "%u", &DNI ); letra = argv[1][ strlen(argv[1])-1 ]; letra = toupper( letra ); if( letra == letra_de( DNI ) ) { printf( "NIF vlido.\n" ); codigo_error = 0; } else { printf( "Letra %c no vlida!\n", letra ); codigo_error = -1; } /* if */ } else { printf( "Uso: %s DNI-letra\n", argv[0] ); codigo_error = 1; } /* if */ return codigo_error; } /* main */
214
ANOTACIONES
FUOC XP04/90793/00018
7) int main( int argc, char *argv[] ) { FILE char palabra_t int *entrada; nombre[BUFSIZ]; palabra, palabra2; codigo_error;
unsigned int numlin, pos; if( argc == 3 ) { palabra = siguiente_palabra( argv[1], 0 ); if( palabra != NULL ) { entrada = fopen( argv[2], "rt" ); if( entrada != NULL ) { /* (Vase: Enunciado del ejercicio 1.) } else { printf( "No puedo abrir %s!\n", argv[2] ); codigo_error = -1; } /* if */ } else { printf( "Palabra %s invlida!\n", argv[1] ); codigo_error = -1; } /* if */ } else { printf( "Uso: %s palabra fichero\n", argv[0] ); codigo_error = 0; } /* if */ return codigo_error; } /* main */ 8) int main( int argc, char *argv[] ) { int codigo_error; codigo_error = system( "ls als | more" ); return codigo_error; } /* main */ */
215
ANOTACIONES
FUOC XP04/90793/00018
Software libre
9) int main( int argc, char *argv[] ) { unsigned int horas; unsigned int minutos; unsigned int segundos; char time_t struct tm *aviso, *separador; tiempo; *tiempo_desc;
if( argc == 3 ) { separador = strchr( argv[1], ':' ); if( separador != NULL ) { horas = atoi( argv[1] ) % 24; minutos = atoi( separador+1 ) % 60; } else { horas = 0; minutos = atoi( argv[1] ) % 60; } /* if */ if( argv[1][0]!='+' ) { time( &tiempo ); tiempo_desc = localtime( &tiempo ); if( minutos < tiempo_desc->tm_min ) { minutos = minutos + 60; horas = horas - 1; } /* if */ if( horas < tiempo_desc->tm_hour ) { horas = horas + 24; } /* if */ minutos = minutos - tiempo_desc->tm_min; horas = horas - tiempo_desc->tm_hour; } /* if */ segundos = (horas*60 + minutos) * 60; aviso = argv[2]; if( daemon( FALSE, TRUE ) ) { printf( "No puede instalarse el avisador :-(\n" ); } else { printf( "Alarma dentro de %i horas y %i minutos.\n",
216
ANOTACIONES
FUOC XP04/90793/00018
horas, minutos ); /* printf */ printf( "Haz $ kill %li para apagarla.\n", getpid() ); /* printf */ } /* if */ sleep( segundos ); printf( "%s\007\n", aviso ); printf( "Alarma apagada.\n" ); } else { printf( "Uso: %s [+]HH:MM \"aviso\"\n", argv[0] ); printf( "(con + es respecto de la hora actual)\n" ); printf( "(sin + es la hora del dia)\n" ); } /* if */ return 0; } /* main */ 10) Se trata de repetir el cdigo dado dentro de una funcin que tenga la cabecera adecuada para cada caso.
217
ANOTACIONES
FUOC XP04/90793/00018
4.1. Introduccin
Hasta el momento se ha estudiado cmo abordar un problema utilizando los paradigmas de programacin modular y el diseo descendente de algoritmos. Con ellos se consigue afrontar un problema complejo mediante la descomposicin en problemas ms simples, reduciendo progresivamente su nivel de abstraccin, hasta obtener un nivel de detalle manejable. Al final, el problema se reduce a estructuras de datos y funciones o procedimientos. Para trabajar de forma eficiente, las buenas prcticas de programacin nos aconsejan agrupar los conjuntos de rutinas y estructuras relacionados entre s en unidades de compilacin, que luego seran enlazados con el archivo principal. Con ello, se consigue lo siguiente: Localizar con rapidez el cdigo fuente que realiza una tarea determinada y limitar el impacto de las modificaciones a unos archivos determinados. Mejorar la legibilidad y comprensin del cdigo fuente en conjunto al no mezclarse entre s cada una de las partes. No obstante, esta recomendable organizacin de los documentos del proyecto slo proporciona una separacin de los diferentes archivos y, por otro lado, no refleja la estrecha relacin que suele haber entre los datos y las funciones. En la realidad muchas veces se desea implementar entidades de forma que se cumplan unas propiedades generales: conocer las entradas que precisan, una idea general de su funcionamiento y las salidas que generan. Generalmente, los detalles concretos de la implemen219
ANOTACIONES
FUOC XP04/90793/00018
Software libre
tacin no son importantes: seguramente habr decenas de formas posibles de hacerlo. Se puede poner como ejemplo un televisor. Sus propiedades pueden ser la marca, modelo, medidas, nmero de canales; y las acciones a implementar seran encender o apagar el televisor, cambiar de canal, sintonizar un nuevo canal, etc. Cuando utilizamos un aparato de televisin, lo vemos como una caja cerrada, con sus propiedades y sus conexiones. No nos interesa en absoluto sus mecanismos internos, slo deseamos que acte cuando apretamos el botn adecuado. Adems, ste se puede utilizar en varias localizaciones y siempre tendr la misma funcin. Por otro lado, si se estropea puede ser sustituido por otro y sus caractersticas bsicas (tener una marca, encender, apagar, cambiar de canal, etc.) continan siendo las mismas independientemente de que el nuevo aparato sea ms moderno. El televisor es tratado como un objeto por s mismo y no como un conjunto de componentes. Este mismo principio aplicado a la programacin se denomina encapsulamiento. El encapsulamiento consiste en implementar un elemento (cuyos detalles se vern ms adelante) que actuar como una caja negra, donde se especificarn unas entradas, una idea general de su funcionamiento y unas salidas. De esta forma se facilita lo siguiente: La reutilizacin de cdigo. Si ya se dispone de una caja negra que tenga unas caractersticas coincidentes con las necesidades definidas, se podr incorporar sin interferir con el resto del proyecto. El mantenimiento del cdigo. Se pueden realizar modificaciones sin que afecten al proyecto en conjunto, mientras se continen cumpliendo las especificaciones de dicha caja negra. A cada uno de estos elementos los llamaremos objetos (en referencia a los objetos de la vida real a los que representa). Al trabajar con objetos, lo que supone un nivel de abstraccin mayor, se afronta el diseo de una aplicacin no pensando en la secuencia de instrucciones a realizar, sino en la definicin de los objetos que intervienen y las relaciones que se establecen entre ellos.
220
ANOTACIONES
FUOC XP04/90793/00018
En esta unidad estudiaremos un nuevo lenguaje que nos permite implementar esta nueva visin que supone el paradigma de la programacin orientada a objetos: C++. Este nuevo lenguaje se basa en el lenguaje C al que se le dota de nuevas caractersticas. Por este motivo, en primer lugar, se establece una comparacin entre ambos lenguajes en los mbitos comunes que nos permite un aprendizaje rpido de sus bases. A continuacin, se nos presenta el nuevo paradigma y las herramientas que el nuevo lenguaje proporciona para la implementacin de los objetos y sus relaciones. Finalmente, se muestra cmo este cambio de filosofa afecta al diseo de aplicaciones. En esta unidad se pretende que los lectores, partiendo de sus conocimientos del lenguaje C, puedan conocer los principios bsicos de la programacin orientada a objetos utilizando el lenguaje C++ y del diseo de aplicaciones siguiendo este paradigma. En concreto, al finalizar el estudio de esta unidad, el lector habr alcanzado los objetivos siguientes: 1) Conocer las diferencias principales entre C y C++, inicialmente sin explorar an la tecnologa de objetos. 2) Comprender el paradigma de la programacin orientada a objetos. 3) Saber implementar clases y objetos en C++. 4) Conocer las propiedades principales de los objetos: la herencia, la homonimia y el polimorfismo. 5) Poder disear una aplicacin simple en C++ aplicando los principios del diseo orientado a objetos.
4.2. De C a C++
ANOTACIONES
FUOC XP04/90793/00018
Software libre
guaje C. No obstante, puede convertirse en una limitacin si el programador no explora las caractersticas adicionales que nos proporciona el nuevo lenguaje y que aportan una serie de mejoras bastante interesantes. Tradicionalmente, en el mundo de la programacin, la primera toma de contacto con un lenguaje de programacin se hace a partir del clsico mensaje de hola mundo y, en este caso, no haremos una excepcin. Por tanto, en primer lugar, escribid en vuestro editor el siguiente texto y guardadlo con el nombre ejemplo01.cpp: #include <iostream> int main() { cout << "hola mundo \n" ; return 0; } Comparando este programa con el primer programa en C, observamos que la estructura es similar. De hecho, como se ha comentado, el C++ se puede ver como una evolucin del C para implementar la programacin orientada a objetos y, como tal, mantiene la compatibilidad en un porcentaje muy alto del lenguaje. La nica diferencia observable la encontramos en la forma de gestionar la salida que se hace a travs de un objeto llamado cout. La naturaleza de los objetos y de las clases se estudiar en profundidad ms adelante, pero, de momento, podremos hacernos una idea considerando la clase como un tipo de datos nuevo que incluye atributos y funciones asociadas, y el objeto como una variable de dicho tipo de datos. La definicin del objeto cout se halla dentro de la librera <iostream>, que se incluye en la primera lnea del cdigo fuente Tambin llama la atencin la forma de uso, mediante el direccionamiento (con el smbolo <<) del texto de hola mundo sobre el objeto cout, que genera la salida de este mensaje en la pantalla.
222
Nota
La extensin .cpp indica al compilador que el tipo de cdigo fuente es C++.
ANOTACIONES
FUOC XP04/90793/00018
Como el tema del tratamiento de las funciones de entrada/salida es una de las principales novedades del C++, comenzaremos por l para desarrollar las diferencias entre un lenguaje y el otro.
Tal como se ha comentado en unidades anteriores, el funcionamiento de la entrada/salida en C se produce a travs de libreras de funciones, la ms importante de las cuales es la <stdio.h> o <cstdio> (entrada/salida estndar). Dichas funciones (printf, scanf, fprint, fscanf, etc.) continan siendo operativas en C++, aunque no se recomienda su uso al no aprovechar los beneficios que proporciona el nuevo entorno de programacin.
Nota
Ambas formas de expresar el nombre de la librera <xxxx.h> o <cxxxx> son correctas, aunque la segunda se considera actualmente la forma estndar de incorporar libreras C dentro del lenguaje C++ y la nica recomendada para su uso en nuevas aplicaciones.
C++, al igual que C, entiende la comunicacin de datos entre el programa y la pantalla como un flujo de datos: el programa va enviando datos y la pantalla los va recibiendo y mostrando. De la misma manera se entiende la comunicacin entre el teclado (u otros dispositivos de entrada) y el programa.
223
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Para gestionar estos flujos de datos, C++ incluye la clase iostream, que crea e inicializa cuatro objetos: cin. Maneja flujos de entrada de datos. cout. Maneja flujos de salida de datos. cerr. Maneja la salida hacia el dispositivo de error estndar, la pantalla. clog. Maneja los mensajes de error. A continuacin, se presentan algunos ejemplos simples de su uso. #include <iostream> int main() { int numero; cout << "Escribe un nmero: "; cin >> nmero; } En este bloque de cdigo se observa lo siguiente: La declaracin de una variable entera con la que se desea trabajar. El texto Escribe un nmero (que podemos considerar como un flujo de datos literal) que deseamos enviar a nuestro dispositivo de salida. Para conseguir nuestro objetivo, se direcciona el texto hacia el objeto cout mediante el operador >>. El resultado ser que el mensaje saldr por pantalla. Una variable donde se desea guardar la entrada de teclado. Otra vez el funcionamiento deseado consistir en direccionar el flujo de entrada recibido en el teclado (representado/gestionado por el objeto cin) sobre dicha variable. La primera sorpresa para los programadores en C, acostumbrados al printf y al scanf, es que no se le indica en la instruccin el
224
ANOTACIONES
FUOC XP04/90793/00018
formato de los datos que se desea imprimir o recibir. De hecho, sta es una de las principales ventajas de C++: el compilador reconoce el tipo de datos de las variables y trata el flujo de datos de forma consecuente. Por tanto, simplificando un poco, se podra considerar que los objetos cin y cout se adaptan al tipo de datos. Esta caracterstica nos permitir adaptar los objetos cin y cout para el tratamiento de nuevos tipos de datos (por ejemplo, structs), cosa impensable con el sistema anterior. Si se desea mostrar o recoger diversas variables, simplemente, se encadenan flujos de datos: #include <iostream> int main() { int i, j, k; cout << "Introducir tres nmeros"; cin >> i >> j >> k; cout << "Los nmeros son: " cout << i << ", " << j << " y " << k; } En la ltima lnea se ve cmo en primer lugar se enva al cout el flujo de datos correspondiente al texto Los nmeros son: ; despus, el flujo de datos correspondiente a la variable i; posteriormente, el texto literal , , y as hasta el final. En el caso de la entrada de datos por teclado, cin leer caracteres hasta la introduccin de un carcter de salto de lnea (return o \n). Despus, ir extrayendo del flujo de datos introducido caracteres hasta encontrar el primer espacio y dejar el resultado en la variable i. El resultado de esta operacin ser tambin un flujo de datos (sin el primer nmero que ya ha sido extrado) que recibir el mismo tratamiento: ir extrayendo caracteres del flujo de datos hasta el siguiente separador para enviarlo a la siguiente variable. El proceso se repetir hasta leer las tres variables. Por tanto, la lnea de lectura se podra haber escrito de la siguiente manera y hubiera sido equivalente, pero menos clara: ( ( ( cin >> i ) >> j ) >> k )
225
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Si se desea mostrar la variable en un formato determinado, se debe enviar un manipulador del objeto que le indique el formato deseado. En el siguiente ejemplo, se podr observar su mecnica de funcionamiento: #include <iostream> #include <iomanip> // Se debe incluir para la definicin de los // manipuladores de objeto cout con parmetros int main() { int i = 5; float j = 4.1234; cout << setw(4) << i << endl; //muestra i con anchura de 4 car. cout << setprecision(3) << j << endl; // muestra j con 3 decimales }
Hay muchas otras posibilidades de formato, pero no es el objetivo de este curso. Esta informacin adicional est disponible en la ayuda del compilador.
ANOTACIONES
FUOC XP04/90793/00018
Dentro de las instrucciones bsicas, podramos incluir las de entrada/salida. En este caso, s que se presentan diferencias significativas entre C y C++, diferencias que ya hemos comentado en el apartado anterior.
Adems, es importante resaltar que se ha incluido una nueva forma de aadir comentarios dentro del cdigo fuente para contribuir a mejorar la lectura y el mantenimiento del mismo. Se conserva la forma clsica de los comentarios en C como el texto incluido entre las secuencias de caracteres /* (inicio de comentario) y */ (fin de comentario), pero se aade una nueva forma: la secuencia // que nos permite un comentario hasta final de lnea.
Ejemplo
/* Este texto est comentado utilizando la forma clsica de C. Puede contener la cantidad de lneas que se desee. */ // // Este texto utiliza el nuevo formato de comentarios // hasta final de lnea que incorpora C++ //
ANOTACIONES
FUOC XP04/90793/00018
Software libre
// ... { int i = 0, num; bool continuar; continuar = true; do { i++; cout <<"Para finalizar este bucle que ha pasado"; cout << i << "veces, teclea un 0 "; cin >> num; if (num == 0) continuar = false; } while (continuar); } Aunque el uso de variables de tipo lgico o booleano ya era comn en C, utilizando como soporte los nmeros enteros (el 0 como valor falso y cualquier otro valor como verdadero), la nueva implementacin simplifica su uso y ayuda a reducir errores. Adems, el nuevo tipo de datos slo ocupa un byte de memoria, en lugar de los dos bytes que utilizaba cuando se simulaba con el tipo int. Por otro lado, hay que destacar las novedades respecto de los tipos estructurados struct, enum o union). En C++ pasan a ser ( considerados descriptores de tipos de datos completos, evitando la necesidad del uso de la instruccin typedef para definir nuevos tipos de datos. En el ejemplo que sigue, se puede comprobar que la parte de la definicin del nuevo tipo no vara:
ANOTACIONES
228
struct fecha { int dia; int mes; int anyo; }; enum diasSemana {LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO};
FUOC XP04/90793/00018
Lo que se simplifica es la declaracin de una variable de dicho tipo, puesto que no se tienen que volver a utilizar los trminos struct, enum o union, o la definicin de nuevos tipos mediante la instruccin typedef: fecha aniversario; diasSemana festivo; Por otro lado, la referencia a los datos tampoco vara. // ... aniversario.dia = 2; aniversario.mes = 6; aniversario.anyo = 2001; festivo = LUNES; En el caso de la declaracin de variables tipo enum se cumplen dos funciones: Declarar diasSemana como un tipo nuevo. Hacer que LUNES corresponda a la constante 0, MARTES a la constante 1 y as sucesivamente. Por tanto, cada constante enumerada corresponde a un valor entero. Si no se especifica nada, el primer valor tomar el valor de 0 y las siguientes constantes irn incrementando su valor en una unidad. No obstante, C++ permite cambiar este criterio y asignar un valor determinado a cada constante:
enum comportamiento {HORRIBLE = 0, MALO, REGULAR = 100, BUENO = 200, MUY_BUENO, EXCELENTE}; De esta forma, HORRIBLE tomara el valor de 0, MALO el valor de 1, REGULAR el valor de 100, BUENO el de 200, MUY_BUENO, el de 201 y EXCELENTE, el de 202.
229
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Otro aspecto a tener en cuenta es la recomendacin en esta nueva versin que se refiere a realizar las coerciones de tipos de forma explcita. La versin C++ se recomienda por su legibilidad:
int i = 0; long v = (long) i; // coercin de tipos en C long v = long (i); // coercin de tipos en C++
const float PI = 3.14159; Una vez definida dicha constante, no se le puede asignar ningn valor y, por tanto, siempre estar en la parte derecha de las expresiones. Por otro lado, una constante siempre tiene que estar inicializada:
const float radio;// ERROR!!!!!!!! El uso de constantes no es nuevo en C. La forma clsica para definirlas es mediante la instruccin de prepocesador #define. #define PI 3.14159 En este caso, el comportamiento real es sustituir cada aparicin del texto PI por su valor en la fase de preprocesamiento del texto. Por tanto, cuando analiza el texto, el compilador slo ve el nmero 3.14159 en lugar de PI.
230
ANOTACIONES
FUOC XP04/90793/00018
No obstante, mientras el segundo caso corresponde a un tratamiento especial durante el proceso previo a la compilacin, el uso de const permite un uso normalizado y similar al de una variable pero con capacidades limitadas. A cambio, recibe el mismo tratamiento que las variables respecto al mbito de actuacin (slo en el fichero de trabajo, a menos que se le indique lo contrario mediante la palabra reservada extern) y tiene un tipo asignado, con lo cual se podrn realizar todas las comprobaciones de tipos en fase de compilacin haciendo el cdigo fuente resultante ms robusto.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
// de memoria apunt_i //Por tanto, se modifica el valor de i. cout << "Valor Original : " << i << endl ;
cout << "A travs del apuntador : " << *apunt_i << endl ; // La salida mostrar: // Valor original: 3 // A travs del apuntador: 3
ANOTACIONES
FUOC XP04/90793/00018
De forma similar, se puede liberar toda la memoria reservada por el vector (cada uno de los objetos creados y el vector mismo) utilizando la forma delete []. fecha * lunasLlenas = new fecha[12]; ... delete [] lunasLlenas; Si se omiten los corchetes, el resultado ser eliminar nicamente el primer objeto del vector, pero no el resto creando una fuga de memoria; es decir, memoria reservada a la cual no se puede acceder en el futuro.
Apuntadores const
En la declaracin de apuntadores en C++ permite el uso de la palabra reservada const. Y adems existen diferentes posibilidades.
const int * ap_i; int * const ap_j; // *ap_i permanece constante
// Direccin ap_j es constante, pero no su valor const int * const ap_k; // Tanto la direccin ap_k como su valor *ap_k sern constantes
Es decir, en el caso de apuntadores se puede hacer constante su valor (*ap_i) o su direccin de memoria (ap_i), o las dos cosas. Para no confundirse, basta con fijarse en el texto posterior a la palabra reservada const. Con la declaracin de apuntadores constantes el programador le indica al compilador cundo se desea que el valor o la direccin que contiene un apuntador no sufran modificaciones. Por tanto, cualquier intento de asignacin no prevista se detecta en tiempo de compilacin. De esta forma se reduce el riesgo de errores de programacin.
Referencias
Para la gestin de variables dinmicas C++ aade un nuevo elemento para facilitar su uso: las referencias. Una referencia es un
233
ANOTACIONES
FUOC XP04/90793/00018
Software libre
alias o un sinnimo. Cuando se crea una referencia, se inicializa con el nombre de otra variable y acta como un nombre alternativo de sta. Para crearla se escribe el tipo de la variable destino, seguido del operador de referencia & ) y del nombre de la referencia. Por ( ejemplo,
int i; int & ref_i = i; En la expresin anterior se lee: la variable ref_i es una referencia a la variable i. Las referencias siempre se tienen que inicializar en el momento de la declaracin (como si fuera una const). Hay que destacar que aunque el operador de referencia y el de direccin se representan de la misma forma ( ), corresponden a & operaciones diferentes, aunque estn relacionados entre s. De hecho, la caracterstica principal de las referencias es que si se pide su direccin, devuelven la de la variable destino.
#include <iostream> int main() { int i = 10; int & ref_i = i; ref_i = 3; //Se asigna el valor 3 a la posicin
ANOTACIONES
234
Con este ejemplo se puede comprobar que las dos direcciones son idnticas, y como la asignacin sobre ref_i tiene el mismo efecto que si hubiera sido sobre i.
FUOC XP04/90793/00018
Otras caractersticas de las referencias son las siguientes: No se pueden reasignar. El intento de reasignacin se convierte en una asignacin en la variable sinnima. No se le pueden asignar un valor nulo. El uso principal de las referencias es el de la llamada a funciones, que se ver a continuacin.
Nota
Aqu no utilizaremos el trmino por referencia para no inducir a confusin con las referencias de C++.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
int x = 2, y = 3; cout << " Antes. x = " << x << " y = " << y << endl; intercambiar(&x , &y); cout << " Despus. x = " << x << " y = " << y << endl; } void intercambiar(int *i, int *j) { int k; k = *i; *i = *j; *j = k; } Se puede comprobar que el uso de las desreferencias (*) dificulta su comprensin. Pero en C++ se dispone de un nuevo concepto comentado anteriormente: las referencias. La nueva propuesta consiste en recibir el parmetro como referencia en lugar de apuntador: #include <iostream> void intercambiar(int &i, int &j); int main() { int x = 2, y = 3; cout << " Antes. x = " << x << " y = " << y << endl; intercambiar(x , y); //NO intercambiar(&x , &y); cout << " Despues. x = " << x << " y = " << y << endl; } void intercambiar(int & i, int & j) { int k;
ANOTACIONES
236
k = i; i = j; j = k; } El funcionamiento de esta nueva propuesta es idntico al de la anterior, pero la lectura del cdigo fuente es mucho ms simple al utilizar el operador de referencia (&) para recoger las direcciones de memoria de los parmetros.
FUOC XP04/90793/00018
No obstante, hay que recordar que las referencias tienen una limitaciones (no pueden tomar nunca un valor nulo y no pueden reasignarse). Por tanto, no se podrn utilizar las referencias en el paso de parmetros cuando se desee pasar un apuntador como parmetro y que ste pueda ser modificado (por ejemplo, obtener el ltimo elemento en una estructura de datos de cola). Tampoco se podrn utilizar referencias para aquellos parmetros que deseamos considerar como opcionales, al existir la posibilidad de que no pudieran ser asignados a ningn parmetro de la funcin que los llama, por lo que tendran que tomar el valor null, (lo que no es posible). En estos casos, el paso de parmetros por variable se debe continuar realizando mediante el uso de apuntadores.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
De este modo se pueden conseguir las ventajas de eficiencia al evitar los procesos de copia no deseados sin tener el inconveniente de estar desprotegido ante modificaciones no deseadas.
Sobrecarga de funciones
C admite un poco de flexibilidad en las llamadas a funciones al permitir el uso de un nmero de parmetros variables en la llamada a una funcin, siempre y cuando sean los parmetros finales y en la definicin de dicha funcin se les haya asignado un valor para el caso que no se llegue a utilizar dicho parmetro. C++ ha incorporado una opcin mucho ms flexible, y que es una de las novedades respecto al C ms destacadas: permite el uso de diferentes funciones con el mismo nombre (homonmia de funciones). A esta propiedad tambin se la denomina sobrecarga de funciones.
Las funciones pueden tener el mismo nombre pero deben tener diferencias en su lista de parmetros, sea en el nmero de parmetros o sea en variaciones en el tipo de stos.
Hay que destacar que el tipo del valor de retorno de la funcin no se considera un elemento diferencial de la funcin, por tanto, el compilador muestra error si se intenta definir dos funciones con el mismo nombre e idntico nmero y tipo de parmetros pero que retornen valores de distintos tipos. El motivo es que el compilador no puede distinguir a cul de las funciones definidas se desea llamar.
ANOTACIONES
238
FUOC XP04/90793/00018
{ int numEntero = 123; float numReal = 12.3; int numEnteroAlCuadrado; float numRealAlCuadrado; cout << Ejemplo para elevar nmeros al cuadrado\n; cout << Nmeros originales \n; cout << Nmero entero: << numEntero << \n; cout << Nmero real: << numReal << \n; numEnteroAlCuadrado = elevarAlCuadrado (numEntero); numRealAlCuadrado = elevarAlCuadrado (numReal); cout << Nmeros elevados al cuadrado \n; cout << Nmero entero:<< numEnteroAlCuadrado << \n; cout << Nmero real: << numRealAlCuadrado << \n; return 0; } int elevarAlCuadrado (int num) { cout << Elevando un nmero entero al cuadrado \n; return ( num * num); } float elevarAlCuadrado (float num) { cout << Elevando un nmero real al cuadrado \n; return ( num * num); }
El hecho de sobregargar la funcin ElevarAlCuadrado ha que es intrnsicamente la misma operacin. Con ello, hemos evitado tener que definir dos nombres de funcin diferentes: ElevarAlCuadradoNumerosEnteros ElevarAlCuadradoNumerosReales De esta forma, el mismo compilador identifica la funcin que se desea ejecutar por el tipo de sus parmetros y realiza la llamada correcta.
239
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Cmo es posible que se tengan tantas dificultades si las personas son capaces de realizar acciones complejas en su vida cotidiana? La razn es muy sencilla: en nuestra vida cotidiana no se procede con los mismos criterios. La descripcin de nuestro entorno se hace a partir de objetos: puertas, ordenadores, automviles, ascensores, personas, edificios, etc., y estos objetos cumplen unas relaciones ms o menos simples: si una puerta est abierta se puede pasar y si est cerrada no se puede. Si un automvil tiene una rueda pinchada, se sustituye y se puede volver a circular. Y no necesitamos conocer toda la mecnica del automvil para poder realizar esta operacin! Ahora bien, nos podramos imaginar nuestro mundo si al cambiar el neumtico nos dejara de funcionar el limpiapa240
ANOTACIONES
FUOC XP04/90793/00018
rabrisas? Sera un caos. Eso es casi lo que sucede, o al menos no podemos estar completamente seguros de que no suceda, con los paradigmas anteriores. El paradigma de la orientacin a objetos nos propone una forma diferente de enfocar la programacin sobre la base de la definicin de objetos y de las relaciones entre ellos. Cada objeto se representa mediante una abstraccin que contiene su informacin esencial, sin preocuparse de sus dems caractersticas. Esta informacin est compuesta de datos (variables) y acciones (funciones) y, a menos que se indique especficamente lo contrario, su mbito de actuacin est limitado a dicho objeto (ocultamiento de la informacin). De esta forma, se limita el alcance de su cdigo de programacin, y por tanto su repercusin, sobre el entorno que le rodea. A esta caracterstica se le llama encapsulamiento. Las relaciones entre los diferentes objetos pueden ser diversas, y normalmente suponen acciones de un objeto sobre otro que se implementan mediante mensajes entre los objetos. Una de las relaciones ms importante entre los objetos es la pertenencia a un tipo ms general. En este caso, el objeto ms especfico comparte una serie de rasgos (informacin y acciones) con el ms genrico que le vienen dados por esta relacin de inclusin. El nuevo paradigma proporciona una herramienta para poder reutilizar todos estos rasgos de forma simple: la herencia. Finalmente, una caracterstica adicional es el hecho de poder comporcomo polimorfismo (un objeto, muchas formas). Adems, esta propiedad adquiere toda su potencia al ser capaz de adaptar este comportamiento en el momento de la ejecucin y no en tiempo de compilacin.
Ejemplo
Un perro es un animal, un camin es un vehculo, etc.
Ejemplo
Ejemplo de acciones sobre objetos en la vida cotidiana: llamar por telfono, descolgar el telfono, hablar, contestar, etc.
El paradigma de programacin orientada a objetos se basa en estos cuatro pilares: abstraccin, encapsulacin, herencia y polimorfismo.
241
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Deseamos describir un aparato de vdeo. La descripcin se puede hacer a partir de sus caractersticas como marca, modelo, nmero de cabezales, etc. o a travs de sus funciones como reproduccin de cintas de vdeo, grabacin, rebobinado, etc. Es decir, tenemos dos visiones diferentes para tratar el mismo aparato. El primer enfoque correspondera a una coleccin de variables, mientras que el segundo correspondera a una coleccin de funciones.
El uso de las clases nos permite integrar datos y funciones dentro de la misma entidad.
El hecho de reunir el conjunto de caractersticas y funciones en el mismo contenedor facilita su interrelacin, as como su aislamiento del resto del cdigo fuente. En el ejemplo del aparato de vdeo, la reproduccin de una cinta implica, una vez puesta la cinta, la accin de unos motores que hacen que la cinta se vaya desplazando por delante de unos cabezales que leen la informacin.
ANOTACIONES
242
En realidad, a los usuarios el detalle del funcionamiento nos es intrascendente, sencillamente, vemos el aparato de vdeo como una caja que tiene una ranura y unos botones, y sabemos que en su interior contiene unos mecanismos que nos imaginamos bastante complejos. Pero tambin sabemos que a nosotros nos basta con pulsar el botn Play. A este concepto se le denomina encapsulacin de datos y funciones en una clase. Las variables que estn dentro de la clase reciben el
FUOC XP04/90793/00018
nombre de variables miembro o datos miembro , y a las funciones se las denomina funciones miembro o mtodos de la clase. Pero las clases corresponden a elementos abstractos; es decir, a ideas genricas y en nuestra casa no disponemos de un aparato de vdeo en forma de idea, sino de un elemento real con unas caractersticas y funciones determinadas. De la misma forma, en C++ necesitaremos trabajar con los elementos concretos. A estos elementos los llamaremos objetos.
Ejemplo
En la clase Perro presentada, cada uno de los objetos ocupar 8 bytes de memoria: 4 bytes para la variable miembro edad de tipo entero y 4 bytes para la variable miembro altura. Las definiciones de las funciones miembro, en nuestro caso ladrar, no implican reserva de espacio.
243
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Aunque sta es la forma habitual, tambin es posible implementar las funciones miembro de forma inline. Para ello, despus de la declaracin del mtodo y antes del punto y coma (;) se introduce el cdigo fuente de la funcin:
ANOTACIONES
244
class Perro { // lista de variables miembro int edad; int altura; // lista de funciones miembro
FUOC XP04/90793/00018
Nota
void ladrar() { cout << "Guau"; }; }; Este tipo de llamadas slo es til cuando el cuerpo de la funcin es muy reducido (una o dos instrucciones).
Declaracin de un objeto
As como una clase se puede asimilar como un nuevo tipo de datos, un objeto slo corresponde a la definicin de un elemento de dicho tipo. Por tanto, la declaracin de un objeto sigue el mismo modelo: unsigned int numeroPulgas; // variable tipo unsigned int Perro sultan; // objeto de la clase Perro.
245
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
protected corresponde a un caso ms especfico y se estudiar en el apartado Herencia.
ANOTACIONES
FUOC XP04/90793/00018
Se ha declarado la funcin ladrar() como pblica permitiendo el acceso desde fuera de la clase, pero se han mantenido ocultos los valores de edad y altura al declararlos como privados. Vemoslo en la implementacin de un programa en forma completa: #include <iostream> class Perro { public: void ladrar() const { cout << "Guau"; }; private: int edad; int altura; }; int main() { Perro sultan; //Error compilacin. Uso variable privada sultan.edad = 4; cout << sultan.edad; } En el bloque main se ha declarado un objeto sultan de la clase Perro. Posteriormente, se intenta asignar a la variable miembro edad el valor de 4. Como dicha variable no es pblica, el compilador da error indicando que no se tiene acceso a ella. Igualmente, nos mostrara un error de compilacin similar para la siguiente fila. Una solucin en este caso sera declarar la variable miembro edad como public.
247
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Una regla general de diseo recomienda mantener los datos miembro como privados, y manipularlos a travs de funciones pblicas de acceso donde se obtiene o se le asigna un valor.
En nuestro ejemplo, se podra utilizar las funciones obtenerEdad() y asignarEdad() como mtodos de acceso a los datos. Declarar la variable edad como privada nos permitira cambiar el tipo en entero a byte o incluso sustituir el dato por la fecha de nacimiento. La modificacin se limitara a cambiar el cdigo fuente en los mtodos de acceso, pero continuara de forma transparente fuera de la clase, pues se puede calcular la edad a partir de la fecha actual y la fecha de nacimiento o asignar una fecha de nacimiento aproximada a partir de un valor para la edad. class Perro { public: Perro(int, int); Perro(int); Perro(); ~Perro(); // mtodo constructor // // // mtodo destructor // mtodos de acceso // // // mtodos de la clase
ANOTACIONES
248
void asignarAltura(int); //
FUOC XP04/90793/00018
private: int edad; int altura; }; Perro:: ladrar() { cout << "Guau"; } void Perro:: asignarAltura (int nAltura) { altura = nAltura; } int Perro:: obtenerAltura (int nAltura) { return (altura); } void Perro:: asignarEdad (int nEdad) { edad = nEdad; } int Perro:: obtenerEdad () { return (edad); }
El apuntador this
Otro aspecto a destacar de las funciones miembro es que siempre tienen acceso al propio objeto a travs del apuntador this. De hecho, la funcin miembro obtenerEdad tambin se podra expresar de la siguiente forma: int Perro:: obtenerEdad () { return (thisedad); }
Visto de este modo, parece que su importancia sea escasa. No obstante, poder referirse al objeto sea como apuntador this o en su forma desreferenciada (*this) le da mucha potencia. Veremos ejemplos ms avanzados al respecto cuando se estudie la sobrecarga de operadores.
249
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El constructor siempre recibe el nombre de la clase, sin valor de retorno (ni siquiera void) y puede tener parmetros de inicializacin.
Perro::Perro() { edad = 0; } o Perro::Perro(int nEdad)// Nueva edad del perro { edad = nEdad; } En caso de que no se defina especficamente el constructor en la cla-
ANOTACIONES
250
se, el compilador utiliza el constructor predeterminado, que consiste en el nombre de la clase, sin ningn parmetro y tiene un bloque de instrucciones vaco. Por tanto, no hace nada. Perro::Perro() { } Esta caracterstica suena desconcertante, pero permite mantener el mismo criterio para la creacin de todos los objetos.
FUOC XP04/90793/00018
En nuestro caso, si deseamos inicializar un objeto de la clase Perro con una edad inicial de 1 ao, utilizamos la siguiente definicin del constructor: Perro(int nuevaEdad); De esta manera, la llamada al constructor sera de la siguiente forma: Perro sultan(1); Si no hubiera parmetros, la declaracin del constructor a utilizar dentro de la clase sera la siguiente: Perro(); La declaracin del constructor en main o cualquier otra funcin del cuerpo del programa quedara del modo siguiente: Perro sultan(); Pero en este caso especial se puede aplicar una excepcin a la regla que indica que todas las llamadas a funciones van seguidas de parntesis aunque no tengan parmetros. El resultado final sera el siguiente: Perro sultan; El fragmento anterior es una llamada al constructor Perro() y coincide con la forma de declaracin presentada inicialmente y ya conocida. De la misma forma, siempre que se declara un mtodo constructor, se debera declarar un mtodo destructor que se encarga de limpiar cuando ya no se va a usar ms el objeto y liberar la memoria utilizada.
El destructor siempre va antecedido por una tilde (~), tiene el nombre de la clase, y no tiene parmetros ni valor de retorno.
251
ANOTACIONES
FUOC XP04/90793/00018
Software libre
~Perro(); En caso de que no definamos ningn destructor, el compilador define un destructor predeterminado. La definicin es exactamente la misma, pero siempre tendr un cuerpo de instrucciones vaco:
Perro::~Perro() { } Incorporando las nuevas definiciones al programa, el resultado final es el siguiente: #include <iostream> class Perro { public: Perro(int edad); // constructor con un parmetro Perro(); ~Perro(); void ladrar(); private: int edad; int altura; }; Perro:: ladrar() { cout << "Guau"; } int main() { Perro sultan(4); sultan.ladrar(); } // Inicializando el objeto // con una edad de 4. // constructor predeterminado // destructor
ANOTACIONES
252
FUOC XP04/90793/00018
El constructor de copia
El compilador, adems de proporcionar de forma predeterminada a las clases un mtodo constructor y un mtodo destructor, tambin proporciona un mtodo constructor de copia. Cada vez que se crea una copia de un objeto se llama al constructor de copia. Esto incluye los casos en que se pasa un objeto como parmetro por valor a una funcin o se devuelve dicho objeto como retorno de la funcin. El propsito del constructor de copia es realizar una copia de los datos miembros del objeto a uno nuevo. A este proceso tambin se le llama copia superficial . Este proceso, que generalmente es correcto, puede provocar fuertes conflictos si entre las variables miembros a copiar hay apuntadores. El resultado de la copia superficial hara que dos apuntadores (el del objeto original y el del objeto copia) apunten a la misma direccin de memoria: si alguno de ellos liberara dicha memoria, provocara que el otro apuntador, al no poder percatarse de la operacin, se quedara apuntando a una posicin de memoria perdida generando una situacin de resultados impredecibles. La solucin en estos casos pasa por sustituir la copia superficial por una copia profunda en la que se reservan nuevas posiciones de memoria para los elementos tipo apuntador y se les asigna el contenido de las variables apuntadas por los apuntadores originales. La forma de declarar dicho constructor es la siguiente: Perro :: Perro (const Perro & unperro); En esta declaracin se observa la conveniencia de pasar el parmetro como referencia constante pues el constructor no debe alterar el objeto. La utilidad del constructor de copia se observa mejor cuando alguno de los atributos es un apuntador. Por este motivo y para esta prueba cambiaremos el tipo de edad a apuntador a entero. El resultado final sera el siguiente:
253
ANOTACIONES
FUOC XP04/90793/00018
Software libre
class Perro { public: Perro(); ~Perro(); Perro(const Perro & rhs); int obtenerEdad(); private: int *edad; }; Perro :: obtenerEdad() { return (*edad) } Perro :: Perro () { edad = new int; * edad = 3; } Perro :: ~Perro () { delete edad; edad = NULL; } Perro :: Perro (const Perro & rhs) // Constructor de copia { edad = new int; *edad = rhs.obtenerEdad(); // Se reserva nueva memoria // Copia el valor edad // en la nueva posicin } int main() { Perro sultan(4); } // Inicializando con edad 4. // Destructor // Constructor // constructor predeterminado // destructor // constructor de copia // mtodo de acceso
ANOTACIONES
254
FUOC XP04/90793/00018
Si estamos creando una clase Animales, nos puede interesar conservar en alguna variable el nmero total de
255
ANOTACIONES
FUOC XP04/90793/00018
Software libre
perros que se han creado hasta el momento, o hacer una funcin que nos permita contar los perros aunque todava no se haya creado ninguno. La solucin es preceder la declaracin de las variables miembro o de las funciones miembro con la palabra reservada static. Con ello le estamos indicando al compilador que dicha variable, o dicha funcin, se refiere a la clase en general y no a ningn objeto en concreto. Tambin se puede considerar que se est compartiendo este dato o funcin con todas las instancias de dicho objeto. En el siguiente ejemplo se define una variable miembro y una funcin miembro estticas:
class Perro { // ... static int numeroDePerros; //normalmente ser privada static int cuantosPerros() { return numeroDePerros; } }; Para acceder a ellos se puede hacer de dos formas: Desde un objeto de la clase Perro. Perro sultan = new Perro(); sultan.numeroDePerros; //normalmente ser privada sultan.cuantosPerros(); Utilizando el identificador de la clase sin definir ningn objeto. Perro::numeroDePerros; //normalmente ser privada Perro::cuantosPerros();
ANOTACIONES
256
Pero hay que tener presente un aspecto importante: las variables y las funciones miembro static se refieren siempre a la clase y no a ningn objeto determinado, con lo cual el objeto this no existe.
FUOC XP04/90793/00018
Como consecuencia, en las funciones miembro estticas no se podr hacer referencia ni directa ni indirectamente al objeto this y:
Slo podrn hacer llamadas a funciones estticas, pues las funciones no estticas siempre esperan implcitamente a dicho objeto como parmetro. Slo podrn tener acceso a variables estticas, porque a las variables no estticas siempre se accede a travs del mencionado objeto. No se podrn declarar dichas funciones como const pues ya no tiene sentido.
Nota
Las extensiones utilizadas de forma ms estndar son .hpp (ms utilizadas en entornos Windows), .H y .hxx (ms utilizadas en entornos Unix) o incluso .h (al igual que en C).
Nota
Las extensiones utilizadas de forma ms estndar son .cpp (ms frecuentes en entornos Windows) .C y .cxx (ms utilizadas en entornos Unix).
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Fichero perro.hpp (fichero de cabecera de la clase) class Perro { public: Perro(int edad); Perro(); ~Perro(); int obtenerEdad(); //mtodo destructor // mtodos de acceso //mtodos constructores
int asignarEdad(int); int asignarAltura(int); int obtenerAltura(); void ladrar(); private: int edad; int altura; }; Fichero perro.cpp (fichero de implementacin de la clase) // mtodos propios
ANOTACIONES
258
int Perro:: obtenerAltura (int nAltura) { return (altura); } void Perro:: asignarEdad (int nEdad) { edad = nEdad; } int Perro:: obtenerEdad () { return (edad); }
FUOC XP04/90793/00018
Fichero ejemplo.cpp #include <perro.hpp> int main() { //Inicializando el objeto con edad 4. Perro sultan (4); sultan.ladrar(); }
ANOTACIONES
FUOC XP04/90793/00018
Software libre
cada vez que se desea implementar una nueva lista de otro tipo de objetos (por ejemplo, una lista de caballos). Adems, cada vez que se deseara modificar una operacin de las listas, se debera cambiar cada una de sus personalizaciones. Por tanto, rpidamente se intuye que sta implementacin no sera eficiente. La forma eficiente de hacerlo es generar un cdigo genrico que realice las operaciones de las listas para un tipo que se le puede indicar con posterioridad. Este cdigo genrico es el de las plantillas o tipos parametrizados. Despus de este breve comentario, se intuye la eficiencia y la potencia de esta nueva caracterstica y tambin su complejidad, que, como ya hemos comentado, sobrepasa el objetivo de este curso. No obstante, para un dominio avanzado del C++ este tema es imprescindible y se recomienda la consulta de otras fuentes bibliogrficas para completar estos conocimientos. No obstante, mientras que la definicin e implementacin de una librera de plantillas puede llegar a adquirir una gran complejidad, el uso de la Librera Estndar de Plantillas (STL) es accesible. En el siguiente ejemplo, se trabaja con la clase set que define un conjunto de elementos. Para ello, se ha incluido la librera set incluida en la STL. #include <iostream> #include <set> int main() { // define un conjunto de enteros <int> set<int> setNumeros; //aade tres nmeros al conjunto de nmeros setNumeros.insert(123); setNumeros.insert(789); setNumeros.insert(456); // muestra cuntos nmeros tiene // el conjunto de nmeros
260
ANOTACIONES
FUOC XP04/90793/00018
cout << "Conjunto nmeros: " << setNumeros.size() << endl; // se repite el proceso con un conjunto de letras //define conjunto de carcteres <char> set<char> setLetras; setLetras.insert('a'); setLetras.insert('z'); cout << "Conjunto letras: " << setLetras.size() << endl; return 0; } En este ejemplo, se han definido un conjunto de nmeros y un conjunto de letras. Para el conjunto de nmeros, se ha definido la variable setNumeros utilizando la plantilla set indicndole que se utilizarn elementos de tipo <int>. Este conjunto define varios mtodos, entre los cuales el de insertar un elemento al conjunto ( .insert) o contar el nmero de elementos ( size). Para el . segundo se ha definido la variable setLetras tambin utilizando la misma plantilla set pero ahora con elementos tipo <char>. La salida del programa nos muestra el nmero de elementos introducidos en el conjunto de nmeros y, posteriormente, el nmero de elementos introducidos en el conjunto de letras.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Una vez asimilado el alcance del cambio de paradigma, se proporcionarn nuevas reglas para el diseo de aplicaciones.
4.4.1. La homonimia
Tal como su definicin indica, homonimia se refiere al uso de dos o ms sentidos (en nuestro caso, lase operaciones) con el mismo nombre. En programacin orientada a objetos, hablaremos de homonimia al utilizar el mismo nombre para definir la misma operacin diversas veces en diferentes situaciones aunque, normalmente, con la misma idea de fondo. El ejemplo ms claro es definir con el mismo nombre las operaciones que bsicamente cumplen el mismo objetivo pero para diferentes objetos. En nuestro caso, diferenciaremos entre sus dos formas principales: la homonimia (o sobrecarga) de funciones y la homonimia de operadores.
ANOTACIONES
262
FUOC XP04/90793/00018
O, tal como se vio, se puede dar el caso de que siempre se desee inicializar dicha clase a partir de una edad determinada, o de una edad y una altura determinadas: Perro::Perro(int nEdad) // Nueva edad del perro { edad = nEdad; } Perro::Perro(int nEdad, int n:altura) // Nueva defin. { edad = nEdad; altura = nAltura; } En los tres casos, se crea una instancia del objeto Perro. Por tanto, bsicamente estn realizando la misma operacin aunque el resultado final sea ligeramente diferente. Del mismo modo, cualquier otro mtodo o funcin de una clase puede ser sobrecargado.
Sobrecarga de operadores
En el fondo, un operador no es ms que una forma simple de expresar una operacin entre uno o ms operandos, mientras que una funcin nos permite realizar operaciones ms complejas. Por tanto, la sobrecarga de operadores es una forma de simplificar las expresiones en las operaciones entre objetos. En nuestro ejemplo, se podra definir una funcin para incrementar la edad de un objeto Perro.
Perro Perro::incrementarEdad() { ++edad; return (*this); } // la llamada resultante sera Sultan.IncrementarEdad()
263
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Aunque la funcin es muy simple, podra resultar un poco incmoda de utilizar. En este caso, podramos considerar sobrecargar el operador ++ para que, al aplicarlo sobre un objeto Perro, se incrementara automticamente su edad. La sobrecarga del operador se declara de la misma forma que una funcin. Se utiliza la palabra reservada operator, seguida del operador que se va a sobrecargar. En el caso de las funciones de operadores unitarios, no llevan parmetros (a excepcin del incremento o decremento postfijo que utilizan un entero como diferenciador). #include <iostream> class Perro { public: Perro(); Perro(nEdad); ~Perro(); int obtenerEdad(); const Perro & operator++(); // Operador ++i const Perro & operator++(int); // Operador i++ private: int edad; }; Perro::Perro(): edad(0) { } Perro::Perro(int nEdad): edad(nEdad) { } int Perro::obtenerEdad() { return (edad); } const Perro & Perro::operator++() {
264
ANOTACIONES
FUOC XP04/90793/00018
Perro temp = *this; ++edad; return (temp); } int main() { Perro sultan(3); cout << "Edad de Sultan al comenzar el programa \n " ; cout << sultan.obtenerEdad() << endl; ++sultan; cout << "Edad de Sultan despus de un aniversario \n "; cout << sultan.obtenerEdad() << endl; sultan++; cout << "Edad de Sultan despus de otro aniversario\n"; cout << sultan.obtenerEdad(); } En la declaracin de sobrecarga de los operadores, se observa cmo se devuelve una referencia const a un objeto tipo Perro. De esta forma, se protege la direccin del objeto original de cualquier cambio no deseado. Tambin es posible observar que las declaraciones para el operador postfijo y prefijo son prcticamente iguales, y slo cambia el tipo de argumento. Para diferenciar ambos casos, se estableci la convencin de que el operador postfijo tuviera en la declaracin un parmetro tipo int (aunque este parmetro no se usa). const Perro & operator++(); const Perro & operator++(int); // Operador ++i // Operador i++
265
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En la implementacin de ambas funciones, tambin hay diferencias significativas: En el caso del operador prefijo, se procede a incrementar el valor de la edad del objeto y se retorna el objeto modificado a travs del apuntador this. const Perro & Perro::operator++() { ++edad; return (*this); } En el caso del operador postfijo, se requiere devolver el valor del objeto anterior a su modificacin. Por este motivo, se establece una variable temporal que recoge el objeto original, se procede a su modificacin y se retorna la variable temporal. const Perro & Perro::operator++(int x) { Perro temp = *this; ++edad; return (temp); } La definicin de la sobrecarga del operador suma, que es un operador binario, sera como sigue:
// En este caso, la suma de dos objetos tipo Perro // no tiene NINGN SENTIDO LGICO. // SLO a efectos de mostrar como sera // la declaracin del operador, se ha considerado
ANOTACIONES
266
// como resultado posible // retornar el objeto Perro de la izquierda // del operador suma con la edad correspondiente // a la suma de las edades de los dos perros. const Perro & Perro::operator+(const Perro & rhs) { Perro temp = *this; temp.edad = temp.edad + rhs.edad; return (temp);
FUOC XP04/90793/00018
Nota
Dado lo desconcertante de la lgica empleada en el ejemplo anterior, queda claro tambin que no se debe abusar de la sobrecarga de operadores. Slo debe utilizarse en aquellos casos en que su uso sea intuitivo y ayude a una mayor legibilidad del programa.
4.4.2.
La herencia simple
Los objetos no son elementos aislados. Cuando se estudian los objetos, se establecen relaciones entre ellos que nos ayudan a comprenderlos mejor.
Ejemplo
Un perro y un gato son objetos diferentes, pero tienen una cosa en comn: los dos son mamferos. Tambin lo son los delfines y las ballenas, aunque se muevan en un entorno muy diferente, aunque no los tiburones, que entraran dentro de la categora de peces. Qu tienen todos estos objetos en comn? Todos son animales.
Se puede establecer una jerarqua de objetos, en la cual un perro es un mamfero, un mamfero es un animal, un animal es un ser vivo, etc. Entre ellos se establece la relacin es un. Este tipo de relacin es muy habitual: un guisante es una verdura, que es un tipo de vegetal; un disco duro es una unidad de almacenamiento, que a su vez es un tipo de componente de un ordenador. Al decir que un elemento es un tipo de otro, establecemos una especializacin: decimos que el elemento tiene unas caractersticas generales compartidas y otras propias. La herencia es una manera de representar las caractersticas que se reciben del nivel ms general. La idea de perro hereda todas las caractersticas de mamfero; es decir, mama, respira con pulmones, se mueve, etc., pero tambin
267
ANOTACIONES
FUOC XP04/90793/00018
Software libre
presenta unas caractersticas concretas propias como ladrar o mover la cola. A su vez, los perros tambin se pueden dividir segn su raza: pastor alemn, caniche, doberman, etc. Cada uno con sus particularidades, pero heredando todas las caractersticas de los perros. Para representar estas relaciones, C++ permite que una clase derive de otra. En nuestro caso, la clase Perro deriva de la clase Mamfero. Por tanto, en la clase Perro no ser necesario indicarle que mama, ni que respira con pulmones, ni que se mueve. Al ser un mamfero, hereda dichas propiedades adems de aportar datos o funciones nuevas. De la misma forma, un Mamfero se puede implementar como una clase derivada de la clase Animal, y a su vez hereda informacin de dicha clase como la de pertenecer a los seres vivos y moverse. Dada la relacin entre la clase Perro y la clase Mamifero, y entre la clase Mamifero y la clase Animal, la clase Perro tambin heredar la informacin de la clase Animal: Un perro es un ser vivo que se mueve!
Implementacin de la herencia
Para expresar esta relacin en C++, en la declaracin de una clase despus de su nombre se pone dos puntos (:), el tipo de derivacin (pblica o privada) y el nombre de la clase de la cual deriva: class Perro : public Mamifero El tipo de derivacin, que de momento consideraremos pblica, se estudiar con posterioridad. Ahora, enfocaremos nuestra atencin sobre cmo queda la nueva implementacin: #include <iostream> enum RAZAS { PASTOR_ALEMAN, CANICHE, DOBERMAN, YORKSHIRE }; class Mamifero
268
ANOTACIONES
FUOC XP04/90793/00018
void asignarEdad(int nEdad) { edad = nEdad } ; // mtodos de acceso int obtenerEdad() const { return (edad) }; protected: int edad; }; class Perro : public Mamifero { public: Perro(); ~Perro(); int obtenerRaza() const; void ladrar() const { cout << "Guau "; }; private: RAZAS raza; }; class Gato : public Mamifero { public: Gato(); // mtodo constructor // mtodos propios // mtodo constructor // mtodo destructor
~Gato(); // mtodo destructor { cout << "Miauuu"; } // mtodos propios }; En la implementacin de la clase Mamfero, en primer lugar se han definido su constructor y destructor por defecto. Dado que el dato miembro edad que disponamos en la clase Perro no es una caracterstica exclusiva de esta clase, sino que todos los mamferos tienen una edad, se ha trasladado el dato miembro edad y sus mtodos de acceso ( obtenerEdad y asignarEdad) a la nueva clase.
269
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Nota
Cabe destacar que la declaracin de tipo protected para el dato miembro edad permite que sea accesible desde las clases derivadas. Si se hubiera mantenido la declaracin de private, no hubieran podido verlo ni utilizarlo otras clases, ni siquiera las derivadas. Si se hubiese declarado public, habra sido visible desde cualquier objeto, pero se recomienda evitar esta situacin. Dentro de la clase Perro, hemos aadido como dato nuevo su raza, y hemos definido sus mtodos de acceso (obtenerRaza y asignarRaza), as como su constructor y destructor predefinido. Se contina manteniendo el mtodo ladrar como una funcin de la clase Perro: los dems mamferos no ladran.
A la hora de destruir un objeto de la clase Perro, el proceso es el inverso: en primer lugar se llama al destructor de Perro, liberando as su informacin especfica, y, posteriormente, se llama al destructor de Mamifero. Hasta el momento, sabemos inicializar los datos de un objeto de la clase que estamos definiendo, pero tambin es habitual que en el constructor de una clase se desee inicializar datos pertenecientes a su clase base. El constructor de la clase Mamifero ya realiza esta tarea, pero es posible que slo nos interese hacer esta operacin para los perros y no
270
ANOTACIONES
Ejemplo
Siguiendo con el ejemplo, con la clase Perro, adems de inicializar su raza, tambin podemos inicializar su edad (como es un mamfero, tiene una edad).
FUOC XP04/90793/00018
para todos los animales. En este caso, podemos realizar la siguiente inicializacin en el constructor de la clase Perro: Perro :: Perro() { asignarRaza(CANICHE); asignarEdad(3); }; Al ser asignarEdad un mtodo pblico perteneciente a su clase base, ya lo reconoce automticamente. En el ejemplo anterior, hemos definido dos mtodos ladrar y maullar para las clases Perro y Gato respectivamente. La gran mayora de los animales tienen la capacidad de emitir sonidos para comunicarse; por tanto, se podra crear un mtodo comn que podramos llamar emitirSonido, al cual podramos dar un valor general para todos los animales, excepto para los perros y los gatos, caso en que se podra personalizar: #include <iostream> enum RAZAS {PASTOR_ALEMAN, CANICHE, DOBERMAN, YORKSHIRE }; class Mamifero { public: Mamifero(); ~Mamifero(); void asignarEdad(int nEdad) int obtenerEdad() const { return (edad); }; void emitirSonido() const { cout << "Sonido"; }; protected: int edad; }; class Perro : public Mamifero
271
ANOTACIONES
{ edad = nEdad; } ;
// mtodos de acceso
FUOC XP04/90793/00018
Software libre
{ public: Perro(); ~Perro(); int obtenerRaza() const; void emitirSonido() const { cout << "Guau"; }; private: RAZAS raza; }; // mtodos propios // mtodo constructor // mtodo destructor
class Gato : public Mamifero { public: Gato(); // mtodo constructor ~Gato();// mtodo destructor void emitirSonido () const { cout << "Miauuu"; } }; // mtodos propios
ANOTACIONES
272
ungato.emitirSonido; }
El mtodo emitirSonido tendr un resultado final segn lo llame un Mamfero, un Perro o un Gato. En el caso de las clases derivadas (Perro y Gato) se dice que se ha redefinido la funcin miembro de la clase base. Para ello, la clase derivada debe definir la funcin base con la misma signatura (tipo de retorno, parmetros y sus tipos, y el especificador const).
FUOC XP04/90793/00018
Hay que diferenciar la redefinicin de funciones de la sobrecarga de funciones. En el primer caso, se trata de funciones con el mismo nombre y la misma signatura en clases diferentes (la clase base y la clase derivada). En el segundo caso, son funciones con el mismo nombre y diferente signatura, que estn dentro de la misma clase.
El resultado de la redefinicin de funciones es la ocultacin de la funcin base para las clases derivadas. En este aspecto, hay que tener en cuenta que si se redefine una funcin en una clase derivada, quedarn ocultas tambin todas las sobrecargas de dicha funcin de la clase base. Un intento de usar alguna funcin ocultada generar un error de compilacin. La solucin consistir en realizar en la clase derivada las mismas sobrecargas de la funcin existentes en la clase base. No obstante, si se desea, todava se puede acceder al mtodo ocultado anteponiendo al nombre de la funcin el nombre de la clase base seguido del operador de mbito (::). unperro.Mamifero::emitirSonido();
4.4.3.
El polimorfismo
En el ejemplo que se ha utilizado hasta el momento, slo se ha considerado que la clase Perro (clase derivada) hereda los datos y mexistente es ms fuerte. C++ permite el siguiente tipo de expresiones: Mamifero *ap_unmamifero = new Perro; En estas expresiones a un apuntador a una clase Mamifero no le asignamos directamente ningn objeto de la clase Mamifero, sino
273
ANOTACIONES
FUOC XP04/90793/00018
Software libre
que le asignamos un objeto de una clase diferente, la clase Perro, aunque se cumple que Perro es una clase derivada de Mamifero. De hecho, sta es la naturaleza del polimorfismo: un mismo objeto puede adquirir diferentes formas. A un apuntador a un objeto mamifero se le puede asignar un objeto mamifero o un objeto de cualquiera de sus clases derivadas. Adems, como se podr comprobar ms adelante, esta asignacin se podr hacer en tiempo de ejecucin.
Funciones virtuales
Con el apuntador que se presenta a continuacin, se podr llamar a cualquier mtodo de la clase Mamifero. Pero lo interesante sera que, en este caso concreto, llamara a los mtodos correspondientes de la clase Perro. Esto nos lo permiten las funciones o mtodos virtuales: #include <iostream> enum RAZAS { PASTOR_ALEMAN, CANICHE, DOBERMAN, YORKSHIRE }; class Mamifero { public: Mamifero(); // mtodo constructor virtual ~Mamifero(); // mtodo destructor virtual void emitirSonido() const { cout << "emitir un sonido" << endl; }; protected: int edad; }; class Perro : public Mamifero { public: Perro(); virtual ~Perro(); int obtenerRaza() const;
ANOTACIONES
274
FUOC XP04/90793/00018
virtual void emitirSonido() const { cout << "Guau" << endl; }; // mtodos propios private: RAZAS raza; }; class Gato : public Mamifero { public: Gato(); virtual ~Gato(); // mtodo constructor // mtodo destructor
virtual void emitirSonido() const { cout << "Miau" << endl; }; // mtodos propios }; class Vaca : public Mamifero { public: Vaca(); virtual ~Vaca(); // mtodo constructor // mtodo destructor
virtual void emitirSonido() const { cout << "Muuu" << endl; }; //mtodos propios }; int main() { Mamifero * ap_mamiferos[3]; int i;
ap_mamiferos [1] = new Vaca; ap_mamiferos [2] = new Perro; for (i=0; i<3 ; i++) ap_mamiferos[i] emitirSonido(); return 0; }
275
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Al ejecutar el programa, en primer lugar se declara un vector de 3 elementos tipo apuntador a Mamifero, y se inicializa a diferentes tipos de Mamifero (Gato, Vaca y Perro). Posteriormente, se recorre este vector y se procede a llamar al mtodo emitirSonido para cada uno de los elementos. La salida obtenida ser: Miau Muuu Guau El programa ha detectado en cada momento el tipo de objeto que se ha creado a travs del new y ha llamado a la funcin emitirSonido correspondiente. Esto hubiera funcionado igualmente aunque se le hubiera pedido al usuario que le indicara al programa el orden de los animales. El funcionamiento interno se basa en detectar en tiempo de ejecucin el tipo del objeto al que se apunta y ste, en cierta forma, sustituye las funciones virtuales del objeto de la clase base por las que correspondan al objeto derivado. Por todo ello, se ha definido la funcin miembro emitirSonido de la clase Mamifero como funcin virtual.
ANOTACIONES
276
como tales. No obstante, para una mayor claridad en el cdigo, se recomienda hacerlo. Si la funcin no estuviera declarada como virtual, el programa entendera que debe llamar a la funcin de la clase base, independientemente del tipo de apuntador que fuera. Tambin es importante destacar que, en todo momento, se trata de apuntadores a la clase base (aunque se haya inicializado con un
FUOC XP04/90793/00018
objeto de la clase derivada), con lo cual slo pueden acceder a funciones de la clase base. Si uno de estos apuntadores intentara acceder a una funcin especfica de la clase derivada, como por ejemplo obtenerRaza(), provocara un error de funcin desconocida. A este tipo de funciones slo se puede acceder directamente desde apuntadores a objetos de la clase derivada.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
las variedades de arte (sus clases derivadas). El criterio para declarar una clase como tipo de datos abstracto siempre es subjetivo y depender del uso que se desea de las clases en la aplicacin. class ObraDeArte { public: ObraDeArte(); virtual ~ObraDeArte (); virtual void mostrarObraDeArte() = 0; //virtual pura void asignarAutor(String autor); String obtenerAutor(); String autor; }; class Pintura : public ObraDeArte { public: Pintura(); Pintura ( const Pintura & ); virtual ~Pintura (); virtual void mostrarObraDeArte(); void asignarTitulo(String titulo); String obtenerTitulo(); private: String titulo; }; Pintura :: mostrarObraDeArte() { cout << "Fotografa Pintura \n" } class Escultura : public ObraDeArte { public: Escultura(); Escultura ( const Escultura & ); virtual ~Escultura (); virtual void mostrarObraDeArte(); void asignarTitulo(String titulo); String obtenerTitulo(); private: String titulo; }; Escultura :: mostrarObraDeArte() { cout << "Fotografa Escultura \n" }
278
ANOTACIONES
FUOC XP04/90793/00018
Dentro de esta clase abstracta, se ha definido una funcin virtual que nos muestra una reproduccin de la obra de arte. Esta reproduccin varia segn el tipo de obra. Podra ser en forma de fotografa, de video, de una lectura de un texto literario o teatral, etc. Para declarar la clase ObraDeArte como un tipo de datos abstracto, y por tanto, no instanciable por ningn objeto slo es necesario declarar una funcin virtual pura. Para asignar una funcin virtual pura, basta con tomar una funcin virtual y asignarla a 0: virtual void mostrarObraDeArte() = 0; Ahora, al intentar instanciar un objeto ObraDeArte (mediante new ObraDeArte) dara error de compilacin. Al declarar funciones virtuales puras se debe tener en cuenta que esta funcin miembro tambin se hereda. Por tanto, en las clases derivadas se debe redefinir esta funcin. Si no se redefine, la clase derivada se convierte automticamente en otra clase abstracta.
Herencia mltiple
La herencia mltiple permite que una clase se derive de ms de una clase base.
Figura 12.
279
ANOTACIONES
FUOC XP04/90793/00018
Software libre
class A : public B, public C En este ejemplo, la clase A se deriva de la clase B y de la clase C. Ante esta situacin, surgen algunas preguntas: Qu sucede cuando las dos clases derivadas tienen una funcin con el mismo nombre? Se podra producir un conflicto de ambigedad para el compilador que se puede resolver aadiendo a la clase A una funcin virtual que redefina esta funcin, con lo que se resuelve explcitamente la ambigedad. Qu sucede si las clases derivan de una clase base comn? Como la clase A deriva de la clase D por parte de B y por parte de C, se producen dos copias de la clase D (ved la ilustracin), lo cual puede provocar ambigedades. La solucin en este caso la proporciona la herencia virtual.
Figura 13.
Mediante la herencia virtual se indica al compilador que slo se desea una clase base D compartida; para ello, las clases B y C se definen como virtuales.
Figura 14.
ANOTACIONES
280
FUOC XP04/90793/00018
class B: virtual D class C: virtual D class A : public B, public C Generalmente, una clase inicializa slo sus variables y su clase base. Al declarar una clase como virtual, el constructor que inicializa las variables corresponde al de la clase ms derivada.
Herencia privada
A veces no es necesario, o incluso no se desea, que las clases derivadas tengan acceso a los datos o funciones de la clase base. En este caso se utiliza la herencia privada. Con la herencia privada las variables y funciones miembro de la clase base se consideran como privadas, independientemente de la accesibilidad declarada en la clase base. Por tanto, para cualquier funcin que no sea miembro de la clase derivada son inaccesibles las funciones heredadas de la clase base.
281
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El proceso a seguir es el siguiente: Conceptualizar. Los proyectos normalmente surgen de una idea que gua el desarrollo completo de ste. Es muy til identificar el objetivo general que se persigue y velar porque se mantenga en las diferentes fases del proyecto. Analizar. Es decir, determinar las necesidades (requerimientos) que debe cubrir el proyecto. En esta fase, el esfuerzo se centra en comprender el dominio (el entorno) del problema en el mundo real (qu elementos intervienen y cmo se relacionan) y capturar los requerimientos. El primer paso para conseguir el anlisis de requerimientos es identificar los casos de uso que son descripciones en lenguaje natural de los diferentes procesos del dominio. Cada caso de uso describe la interaccin entre un actor (sea persona o elemento) y el sistema. El actor enva un mensaje al sistema y ste acta consecuentemente (respondiendo, cancelando, actuando sobre otro elemento, etc.). A partir de un conjunto completo de casos de uso, se puede comenzar a desarrollar el modelo del dominio, el documento donde se refleja todo lo que se conoce sobre el dominio. Como parte de esta modelizacin, se describen todos los objetos que intervienen (que al final podrn llegar a corresponder a las clases del diseo). El modelo se suele expresar en UML (lenguaje de modelado unificado), cuya explicacin no es objetivo de esta unidad. A partir de los casos de uso, podemos describir diferentes escenarios, circunstancias concretas en las cuales se desarrolla el caso de uso. De esta forma, se puede ir completando el conjunto de interacciones posibles que debe cumplir nuestro modelo. Cada escenario se caracteriza tambin en un entorno, con unas condiciones previas y elementos que lo activan. Todos estos elementos se pueden representar grficamente mediante diagramas que muestren estas interacciones.
282
Ejemplo
Si se disea una aplicacin para cajeros automticos, un caso de uso podra ser retirar dinero de la cuenta.
Ejemplo
En el caso del proyecto del cajero automtico, seran objetos el cliente, el cajero automtico, el banco, el recibo, el dinero, la tarjeta de crdito, etc.
Nota
UML slo es una convencin comnmente establecida para representar la informacin de un modelo.
ANOTACIONES
Ejemplo
En el caso del proyecto del cajero automtico, un posible escenario sera que el cliente deseara retirar el dinero de la cuenta y no hubiera fondos.
FUOC XP04/90793/00018
Adems, se debern tener en cuenta las restricciones que suponen el entorno en que funcionar u otros requerimientos proporcionados por el cliente. Disear. A partir de la informacin del anlisis, se enfoca el problema en crear la solucin. Podemos considerar el diseo como la conversin de los requerimientos obtenidos en un modelo implementable en software. El resultado es un documento que contiene el plan del diseo. En primer lugar, se debe identificar las clases que intervienen. Una primera (y simple) aproximacin a la solucin del problema consiste en escribir los diferentes escenarios, y crear una clase para cada sustantivo. Posteriormente, se puede reducir este nmero mediante la agrupacin de los sinnimos. Una vez definidas las clases del modelo, podemos aadir las clases que nos sern tiles para la implementacin del proyecto (las vistas, los informes, clases para conversiones o manipulaciones de datos, uso de dispositivos, etc.). Establecido el conjunto inicial de clases, que posteriormente se puede ir modificando, se puede proceder a modelar las relaciones e interacciones entre ellas. Uno de los puntos ms importantes en la definicin de una clase es determinar sus responsabilidades: un principio bsico es que cada clase debe ser responsable de algo. Si se identifica claramente esta responsabilidad nica, el cdigo resultante ser ms fcil de mantener. Las responsabilidades que no corresponden a una clase, las delega a las clases relacionadas. En esta fase tambin se establecen las relaciones entre los objetos del diseo que pueden coincidir, o no, con los objetos del anlisis. Pueden ser de diferentes tipos. El tipo de relacin que ms se ha comentado en esta unidad son las relaciones de generalizacin que posteriormente se han implementado a partir de la herencia pblica, pero hay otras cada una con sus formas de implementacin. La informacin del diseo se completa con la inclusin de la dinmica entre las clases: la modelacin de la interaccin de las clases entre ellas a travs de diversos tipos de diagramas grficos.
283
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El documento que recoge toda la informacin sobre el diseo de un prorgama se denomina plan de diseo.
Implementar. Para que el proyecto se pueda aplicar, se debe convertir el plan de diseo a un cdigo fuente, en nuestro caso, en C++. El lenguaje elegido proporciona las herramientas y mecnicas de trabajo para poder trasladar todas las definiciones de las clases, sus requerimientos, sus atributos y sus relaciones desde el mundo del diseo a nuestro entorno real. En esta fase nos centraremos en codificar de forma eficiente cada uno de los elementos del diseo. Probar. En esta fase se comprueba que el sistema realiza lo que se espera de l. Si no es as, se deben revisar las diferentes especificaciones a nivel de anlisis, diseo o implementacin. El diseo de un buen conjunto de pruebas basado en los casos de uso nos puede evitar muchos disgustos en el producto final. Siempre es preferible disponer de un buen conjunto de pruebas que provoque muchos fallos en las fases de anlisis, diseo o implementacin (y por tanto se pueden corregir), a encontrarse dichos errores en la fase de distribucin. Distribuir. Se entrega al cliente una implementacin del proyecto para su evaluacin (del prototipo) o para su instalacin definitiva.
ANOTACIONES
FUOC XP04/90793/00018
cada vez a un refinamiento superior, lo que permite adaptarlo a los cambios producidos por el mayor conocimiento del proyecto por parte de los diseadores, de los desarrolladores y del mismo cliente. Este mtodo tambin proporciona otra ventaja en la vida real: se facilita la entrega en la fecha prevista de versiones completas aunque en un estado ms o menos refinado. De alguna manera, permite introducir la idea de versiones lo suficientemente buenas, y que posteriormente se pueden ir refinando segn las necesidades del cliente.
4.5. Resumen
En esta unidad, hemos evolucionado desde un entorno de programacin C que sigue el modelo imperativo, donde se toma como base de actuacin las instrucciones y su secuencia, a un modelo orientado a objetos donde la unidad base son los objetos y su interrelacin. Para ello, hemos tenido que comprender las ventajas que supone utilizar un modelo de trabajo ms abstracto, pero ms cercano a la descripcin de las entidades que se manejan en el mundo real y sus relaciones, y que nos permite enfocar nuestra atencin en los conceptos que se desea implementar ms que en el detalle final que suponen las lneas de cdigo. Posteriormente, hemos estudiado las herramientas que proporciona C++ para su implementacin: las clases y los objetos. Adems de relativas al modelo de orientacin a objetos que proporciona C++. En concreto, se han estudiado la herencia entre clases, la homonimia y el polimorfismo. Finalmente, hemos visto que, debido al cambio de filosofa en el nuevo paradigma de programacin, no se pueden aplicar los mismos principios para el diseo de los programas y por ello se han introducido nuevas reglas de diseo coherentes con l.
285
ANOTACIONES
FUOC XP04/90793/00018
Software libre
ANOTACIONES
FUOC XP04/90793/00018
Al salir un usuario del ascensor, se debe solicitar su cdigo y actualizar la carga del ascensor al tiempo que se presenta el siguiente mensaje: <#codigo# sale del ascensor>. Al salir, el usuario se despide de las personas del ascensor(<#codigo# dice> Adis) si las hubiera, y stas le corresponden el saludo de forma individual (<#codigo# responde> Adis). Para simplificar, consideraremos que no puede haber nunca dos pasajeros con el mismo cdigo. Despus de cada operacin, se debe poder mostrar el estado del ascensor (ocupacin y carga). 3) Ampliad el ejercicio anterior incorporando tres posibles idiomas en los que los usuarios puedan saludar. Al entrar, se debe solicitar tambin cul es el idioma de la persona: IDIOMA: [1] Cataln [2] Castellano [3] Ingls En cataln, el saludo es Bon dia y la despedida, Adu. En castellano, el saludo es Buenos das y la despedida, Adis. En ingls, el saludo es Hello y la despedida, Bye.
4.6.1.
1)
Solucionario
ascensor01.hpp class Ascensor { private: int ocupacion; int ocupacionmaxima; public: // Constructores y destructores Ascensor(); ~Ascensor(); // Funciones de acceso
287
ANOTACIONES
FUOC XP04/90793/00018
Software libre
void mostrarOcupacion(); int obtenerOcupacion(); void modificarOcupacion(int difOcupacion); // Funciones del mtodo bool persona_puedeEntrar(); bool persona_puedeSalir(); void persona_entrar(); void persona_salir(); }; ascensor01.cpp #include <iostream> #include "ascensor01.hpp" // Constructores y destructores Ascensor::Ascensor(): ocupacion(0), ocupacionmaxima(6) { } Ascensor::~Ascensor() { } // Funciones de acceso int Ascensor::obtenerOcupacion() { return (ocupacion); } void Ascensor::modificarOcupacion(int difOcupacion) { ocupacion += difOcupacion; } void Ascensor::mostrarOcupacion() { cout << "Ocupacion actual: " << ocupacion << endl;} bool Ascensor::persona_puedeEntrar() { return (true); } bool Ascensor::persona_puedeSalir() { bool hayOcupacion; if (obtenerOcupacion() > 0) hayOcupacion = true;
288
ANOTACIONES
FUOC XP04/90793/00018
else hayOcupacion = false; return (hayOcupacion); } void Ascensor::persona_entrar() { modificarOcupacion(1); } void Ascensor::persona_salir() { int ocupacionActual = obtenerOcupacion(); if (ocupacionActual>0) modificarOcupacion(-1); } ejerc01.cpp #include <iostream> #include "ascensor01.hpp" int main(int argc, char *argv[]) { char opc; bool salir = false; Ascensor unAscensor; do { cout << endl cout << "ASCENSOR: [1]Entrar [2]Salir cin >> opc; switch (opc) { cout << "opc Entrar" << endl; unAscensor.persona_entrar(); break; case '2': cout << "opc Salir" << endl; if (unAscensor.persona_puedeSalir()) unAscensor.persona_salir(); else cout << "Ascensor vacio " << endl; break;
289
[0]Finalizar ";
ANOTACIONES
case '1':
FUOC XP04/90793/00018
Software libre
case '0': salir = true; break; } unAscensor.mostrarOcupacion(); } while (! salir); return 0; } 2) ascensor02.hpp #ifndef _ASCENSOR02 #define _ASCENSOR02 #include "persona02.hpp" class Ascensor { private: int ocupacion; int carga; int ocupacionMaxima; int cargaMaxima; Persona *pasajeros[6]; public: // Constructores y destructores Ascensor(); ~Ascensor(); // Funciones de acceso void mostrarOcupacion(); int obtenerOcupacion(); void modificarOcupacion(int difOcupacion); void mostrarCarga(); int obtenerCarga(); void modificarCarga(int difCarga); void mostrarListaPasajeros(); // Funciones del mtodo bool persona_puedeEntrar(Persona *);
ANOTACIONES
290
FUOC XP04/90793/00018
bool persona_seleccionar(Persona *localizarPersona, Persona **unaPersona); void persona_entrar(Persona *); void persona_salir(Persona *); void persona_saludarRestoAscensor(Persona *); void persona_despedirseRestoAscensor(Persona *); }; #endif ascensor 02.cpp #include <iostream> #include "ascensor02.hpp" // // Constructores y destructores // // En el constructor, inicializamos los valores mximos // de ocupacin y carga mxima de ascensor // y el vector de pasajeros a apuntadores NULL Ascensor::Ascensor(): ocupacion(0), carga(0), ocupacionMaxima(6), cargaMaxima(500) { for (int i=0;i<=5;++i) {pasajeros[i]=NULL;} } Ascensor::~Ascensor() { // Liberar codigos de los pasajeros for (int i=0;i<=5;++i) } // Funciones de acceso int Ascensor::obtenerOcupacion() { return (ocupacion); } void Ascensor::modificarOcupacion(int difOcupacion) { ocupacion += difOcupacion; }
291
ANOTACIONES
{ if (!(pasajeros[i]==NULL)) {delete(pasajeros[i]);} }
FUOC XP04/90793/00018
Software libre
void Ascensor::mostrarOcupacion() { cout << "Ocupacion actual: " << ocupacion ; } int Ascensor::obtenerCarga() { return (carga); } void Ascensor::modificarCarga(int difCarga) { carga += difCarga; } void Ascensor::mostrarCarga() { cout << "Carga actual: " << carga ; } bool Ascensor::persona_puedeEntrar(Persona *unaPersona) { bool tmpPuedeEntrar; // si la ocupacin no sobrepasa el lmite de ocupacin y // si la carga no sobrepasa el limite de carga // puede entrar if (ocupacion + 1 > ocupacionMaxima) { cout << " Aviso: Ascensor completo. No puede entrar. " cout << endl; return (false); } if (unaPersonaobtenerPeso() + carga > cargaMaxima) { cout << "Aviso: El ascensor supera su carga mxima."; cout << " No puede entrar. " << endl;
ANOTACIONES
return (false); } return (true); } bool Ascensor::persona_seleccionar(Persona *localizarPersona, Persona **unaPersona) { int contador;
292
FUOC XP04/90793/00018
// Se debe seleccionar un pasajero del ascensor. bool personaEncontrada = false; if (obtenerOcupacion() > 0) { contador=0; do { if (pasajeros[contador]!=NULL) { if ((pasajeros[contador]obtenerCodigo()== localizarPersonaobtenerCodigo() )) { *unaPersona=pasajeros[contador]; personaEncontrada=true; break; } } contador++; } while (contador<ocupacionMaxima); if (contador>=ocupacionMaxima) {*unaPersona=NULL;} } return (personaEncontrada); } void Ascensor::persona_entrar(Persona *unaPersona) { int contador; modificarOcupacion(1); modificarCarga(unaPersona->obtenerPeso()); cout << unaPersonaobtenerCodigo(); cout << " entra en el ascensor " << endl; // hemos verificado anteriormente que hay plazas libres do { if (pasajeros[contador]==NULL ) { pasajeros[contador]=unaPersona; break; }
293
ANOTACIONES
contador=0;
FUOC XP04/90793/00018
Software libre
contador++; } while (contador<ocupacionMaxima); } void Ascensor::persona_salir(Persona *unaPersona) { int contador; contador=0; do { if ((pasajeros[contador]==unaPersona )) { cout << unaPersonaobtenerCodigo(); cout << " sale del ascensor " << endl; pasajeros[contador]=NULL; // Modificamos la ocupacin y la carga modificarOcupacion(-1); modificarCarga(-1 * (unaPersona->obtenerPeso())); break; } contador++; } while (contador<ocupacionMaxima); if (contador == ocupacionMaxima) { cout << "Ninguna persona con este cdigo. "; cout << "Nadie sale del ascensor" << endl;} } void Ascensor::mostrarListaPasajeros() { int contador; Persona *unaPersona;
ANOTACIONES
if (obtenerOcupacion() > 0) { cout << "Lista de pasajeros del ascensor: " << endl; contador=0; do { if (!(pasajeros[contador]==NULL )) { unaPersona=pasajeros[contador];
294
FUOC XP04/90793/00018
cout << unaPersona->obtenerCodigo() << "; "; } contador++; } while (contador<ocupacionMaxima); cout << endl; } else { cout << "El ascensor esta vaco" << endl; } } void Ascensor::persona_saludarRestoAscensor( Persona *unaPersona) { int contador; Persona *otraPersona; if (obtenerOcupacion() > 0) { contador=0; do { if (!(pasajeros[contador]==NULL )) { otraPersona=pasajeros[contador]; if (!(unaPersona->obtenerCodigo()== otraPersona->obtenerCodigo())) { cout << otraPersona->obtenerCodigo(); cout << " responde: " ; otraPersona->saludar(); cout << endl; } } contador++; } while (contador<ocupacionMaxima); } } void Ascensor::persona_despedirseRestoAscensor( Persona *unaPersona) { int contador; Persona *otraPersona; if (obtenerOcupacion() > 0)
295
ANOTACIONES
FUOC XP04/90793/00018
Software libre
{ contador=0; do { if (!(pasajeros[contador]==NULL )) { otraPersona=pasajeros[contador]; if (!(unaPersonaobtenerCodigo()== otraPersonaobtenerCodigo())) { cout << otraPersona->obtenerCodigo(); cout << " responde: " ; otraPersona->despedirse(); cout << endl; } } contador++; } while (contador<ocupacionMaxima); } } persona02.hpp #ifndef _PERSONA02 #define _PERSONA02 class Persona { private: int codigo; int peso; public: // Constructores Persona(); Persona(int codigo, int peso); Persona(const Persona &); ~Persona(); // Funciones de acceso int obtenerCodigo(); void asignarCodigo(int); int obtenerPeso() const;
296
ANOTACIONES
FUOC XP04/90793/00018
void asignarPeso(int nPeso); void asignarPersona(int); void asignarPersona(int,int); void solicitarDatos(); void solicitarCodigo(); void saludar(); void despedirse(); }; #endif persona02.cpp #include <iostream> #include "persona02.hpp" Persona::Persona() { } Persona::Persona(int nCodigo, int nPeso) { codigo = nCodigo; peso = nPeso; } Persona::~Persona() { } int Persona::obtenerPeso() const { return (peso); } void Persona::asignarPeso(int nPeso) { peso = nPeso; } int Persona::obtenerCodigo() { return (codigo); } void Persona::asignarCodigo(int nCodigo) { codigo= nCodigo;} void Persona::asignarPersona(int nCodigo) { thiscodigo = nCodigo;}
297
ANOTACIONES
FUOC XP04/90793/00018
Software libre
void Persona::asignarPersona(int nCodigo, int nPeso) { asignarCodigo(nCodigo); asignarPeso(nPeso); } void Persona:: saludar() { cout << "Hola \n" ; }; void Persona:: despedirse () { cout << "Adios \n" ; }; void Persona::solicitarCodigo() { int nCodigo; cout << "Codigo: "; cin >> nCodigo; cout << endl; codigo = nCodigo; } ejerc02.cpp #include <iostream> #include "ascensor02.hpp" #include "persona02.hpp" void solicitarDatos(int *nCodigo, int *nPeso) { cout << endl; cout << "Codigo: "; cin >> *nCodigo; cout << endl; cout << "Peso: "; cin >> *nPeso; cout << endl; } int main(int argc, char *argv[]) { char opc;
298
ANOTACIONES
FUOC XP04/90793/00018
bool salir = false; Ascensor unAscensor; Persona * unaPersona; Persona * localizarPersona; do { cout << endl << "ASCENSOR: "; cout << "[1]Entrar [2]Salir [3]Estado [0]Finalizar "; cin >> opc; switch (opc) { case '1': // opcin Entrar { int nPeso; int nCodigo; solicitarDatos(&nCodigo, &nPeso); unaPersona = new Persona(nCodigo, nPeso); if (unAscensor.persona_puedeEntrar(unaPersona)) { unAscensor.persona_entrar(unaPersona); if (unAscensor.obtenerOcupacion()>1) { cout << unaPersona->obtenerCodigo(); cout << " dice: " ; unaPersona->saludar(); cout << endl; // Ahora responden las dems unAscensor.persona_saludarRestoAscensor(unaPersona); } } } case '2': // opcin Salir { unaPersona = NULL; localizarPersona = new Persona; localizarPersona->solicitarCodigo(); if (unAscensor.persona_seleccionar ( {
299
localizarPersona, &unaPersona))
ANOTACIONES
break;
FUOC XP04/90793/00018
Software libre
unAscensor.persona_salir(unaPersona); if (unAscensor.obtenerOcupacion()>0) { cout << unaPersona->obtenerCodigo() cout << " dice: " ; unaPersona->despedirse(); cout << endl; // Ahora responden las dems unAscensor.persona_despedirseRestoAscensor(unaPersona); delete (unaPersona); } } else { cout<<"No hay ninguna persona con este cdigo"; cout << endl; } delete localizarPersona; break; } case '3': //Estado { unAscensor.mostrarOcupacion(); cout << " - "; // Para separar ocupacin de carga unAscensor.mostrarCarga(); cout << endl; unAscensor.mostrarListaPasajeros(); break; } case '0': { salir = true; break; } } } while (! salir); return 0; } 3) ascensor03.hpp y ascensor03.cpp coinciden con
ANOTACIONES
FUOC XP04/90793/00018
persona03.hpp #ifndef _PERSONA03 #define _PERSONA03 class Persona { private: int codigo; int peso; public: // Constructores Persona(); Persona(int codigo, int peso); Persona(const Persona &); ~Persona(); // Funciones de acceso int obtenerCodigo(); void asignarCodigo(int); int obtenerPeso() const; void asignarPeso(int nPeso); void asignarPersona(int,int); void solicitarCodigo(); virtual void saludar(); virtual void despedirse(); }; class Catalan: public Persona { public: Catalan() { asignarCodigo (0); asignarPeso (0); }; Catalan(int nCodigo, int nPeso) { asignarCodigo (nCodigo); asignarPeso (nPeso); };
301
ANOTACIONES
FUOC XP04/90793/00018
Software libre
virtual void saludar() { cout << "Bon dia"; }; virtual void despedirse() { cout << "Adeu"; }; }; class Castellano: public Persona { public: Castellano() { asignarCodigo (0); asignarPeso (0); }; Castellano(int nCodigo, int nPeso) { asignarCodigo (nCodigo); asignarPeso (nPeso); }; virtual void saludar() { cout << "Buenos das"; }; virtual void despedirse() { cout << "Adis"; }; }; class Ingles : public Persona { public: Ingles() { asignarCodigo (0); asignarPeso (0); }; Ingles(int nCodigo, int nPeso) { asignarCodigo (nCodigo); asignarPeso (nPeso); };
302
ANOTACIONES
FUOC XP04/90793/00018
virtual void saludar() { cout << "Hello"; }; virtual void despedirse() { cout << "Bye"; }; }; #endif persona03.cpp #include <iostream> #include "persona03.hpp" Persona::Persona() { } Persona::Persona(int nCodigo, int nPeso) { codigo = nCodigo; peso = nPeso; } Persona::~Persona() { } int Persona::obtenerPeso() const { return (peso); } void Persona::asignarPeso(int nPeso) { peso = nPeso; } int Persona::obtenerCodigo()
void Persona::asignarCodigo(int nCodigo) { thiscodigo = nCodigo; } void Persona::asignarPersona(int nCodigo, int nPeso) { asignarCodigo(nCodigo); asignarPeso(nPeso); }
303
ANOTACIONES
{ return (codigo); }
FUOC XP04/90793/00018
Software libre
void Persona:: saludar() { cout << "Hola \n" ; }; void Persona:: despedirse () { cout << "Adis \n" ; }; void Persona::solicitarCodigo{ int nCodigo; cout << "Codigo: "; cin >> nCodigo; cout << endl; asignarCodigo (nCodigo); } ejerc03.cpp #include <iostream> #include "ascensor03.hpp" #include "persona03.hpp" void solicitarDatos(int *nCodigo, int *nPeso, int *nIdioma) { cout << endl; cout << "Codigo: "; cin >> *nCodigo; cout << endl; cout << "Peso: "; cin >> *nPeso; cout << endl; cout << "Idioma: [1] Cataln [2] Castellano [3] Ingls "; cin >> *nIdioma; cout << endl; } int main(int argc, char *argv[]) { char opc; bool salir = false; Ascensor unAscensor; Persona * unaPersona; Persona * localizarPersona;
304
ANOTACIONES
FUOC XP04/90793/00018
do { cout << endl << "ASCENSOR: "; cout << "[1]Entrar [2]Salir [3]Estado [0]Finalizar"; cin >> opc; switch (opc) { case '1': // Opcin Entrar { int nPeso; int nCodigo; int nIdioma; solicitarDatos(&nCodigo, &nPeso, &nIdioma); switch (nIdioma) { case 1: { unaPersona = new Catalan(nCodigo, nPeso); break; } case 2: { unaPersona=new Castellano(nCodigo, nPeso); break; } case 3: { unaPersona = new Ingles(nCodigo, nPeso); break; } } if (unAscensor.persona_puedeEntrar(unaPersona)) {
{ cout << unaPersona->obtenerCodigo(); cout << " dice: " ; unaPersona->saludar(); cout << endl; // Ahora responden las dems unAscensor.persona_saludarRestoAscensor (unaPersona); } }
305
ANOTACIONES
unAscensor.persona_entrar(unaPersona); if (unAscensor.obtenerOcupacion()>1)
FUOC XP04/90793/00018
Software libre
break; } case '2': //Opcin Salir { localizarPersona = new Persona; unaPersona = NULL; localizarPersonasolicitarCodigo(); if (unAscensor.persona_seleccionar(localizarPersona, & unaPersona)) { unAscensor.persona_salir(unaPersona); if (unAscensor.obtenerOcupacion()>0) { cout << unaPersona->obtenerCodigo(); cout << " dice: " ; unaPersona->despedirse(); cout << endl; // Ahora responden las dems unAscensor.persona_despedirseRestoAscensor (unaPersona); delete (unaPersona) ; } } else { cout<<"No hay ninguna persona con este cdigo"; cout << endl; } delete localizarPersona; break; } case '3': //Estado { unAscensor.mostrarOcupacion(); cout << " - "; // Para separar Ocupacion de Carga unAscensor.mostrarCarga(); cout << endl; unAscensor.mostrarListaPasajeros(); break; } case '0':
ANOTACIONES
306
FUOC XP04/90793/00018
307
ANOTACIONES
FUOC XP04/90793/00018
5.1. Introduccin
En las unidades anteriores, se ha mostrado la evolucin que han experimentado los lenguajes de programacin en la historia y que han ido desembocando en los diferentes paradigmas de programacin. Inicialmente, el coste de un sistema informtico estaba marcado principalmente por el hardware: los componentes internos de los ordenadores eran voluminosos, lentos y caros. En comparacin, el coste que generaban las personas que intervenan en su mantenimiento y en el tratamiento de la informacin era casi despreciable. Adems, por limitaciones fsicas, el tipo de aplicaciones que se podan manejar eran ms bien simples. El nfasis en la investigacin en informtica se centraba bsicamente en conseguir sistemas ms pequeos, ms rpidos y ms baratos. Con el tiempo, esta situacin ha cambiado radicalmente. La revolucin producida en el mundo del hardware ha permitido la fabricacin de ordenadores en los que no se poda ni soar hace 25 aos, pero esta revolucin no ha tenido su correspondencia en el mundo del software. En este aspecto, los costes materiales se han reducido considerablemente mientras que los relativos a personal han aumentado progresivamente. Tambin se ha incrementado la complejidad en el uso del software, entre otras cosas debido al aumento de interactividad con el usuario. En la actualidad muchas de las lneas de investigacin buscan mejorar el rendimiento en la fase de desarrollo de software donde, de momento, la intervencin humana es fundamental. Mucho de este esfuerzo se centra en la generacin de cdigo correcto y en la reutilizacin del trabajo realizado. En este camino, el paradigma de la programacin orientada a objetos ha supuesto una gran aproximacin entre el proceso de desarrollo de
309
ANOTACIONES
FUOC XP04/90793/00018
Software libre
aplicaciones y la realidad que intentan representar. Por otro lado, la incorporacin de la informtica en muchos componentes que nos rodean tambin ha aumentado en gran medida el nmero de plataformas diversas sobre las cuales es posible desarrollar programas. Java es un lenguaje moderno que ha nacido para dar solucin a este nuevo entorno. Bsicamente, es un lenguaje orientado a objetos pensado para trabajar en mltiples plataformas. Su planteamiento consiste en crear una plataforma comn intermedia para la cual se desarrollan las aplicaciones y, despus, trasladar el resultado generado para dicha plataforma comn a cada mquina final. Este paso intermedio permite: Escribir la aplicacin slo una vez. Una vez compilada hacia esta plataforma comn, la aplicacin podr ser ejecutada por todos los sistemas que dispongan de dicha plataforma intermedia. Escribir la plataforma comn slo una vez. Al conseguir que una mquina real sea capaz de ejecutar las instrucciones de dicha plataforma comn, es decir, que sea capaz de trasladarlas al sistema subyacente, se podrn ejecutar en ella todas las aplicaciones desarrolladas para dicha plataforma. Por tanto, se consigue el mximo nivel de reutilizacin. El precio es el sacrificio de parte de la velocidad. En el orden de la generacin de cdigo correcto, Java dispone de varias caractersticas que se irn viendo a lo largo de esta unidad. En todo caso, de momento se desea destacar que Java se basa en C++, con lo cual se consigue mayor facilidad de aprendizaje para gran nmero de desarrolladores (reutilizacin del conocimiento), pero se le libera de muchas de las cadenas que C++ arrastraba por su compatibilidad con el C. Esta limpieza tiene consecuencias positivas: El lenguaje es ms simple, pues se eliminan conceptos complejos raras veces utilizados.
310
ANOTACIONES
FUOC XP04/90793/00018
El lenguaje es ms directo. Se ha estimado que Java permite reducir el nmero de lneas de cdigo a la cuarta parte. El lenguaje es ms puro, pues slo permite trabajar en el paradigma de la orientacin a objetos. Adems, la juventud del lenguaje le ha permitido incorporar dentro de su ncleo algunas caractersticas que sencillamente no existan cuando se crearon otros lenguajes, como las siguientes: La programacin de hilos de ejecucin (threads), que permite aprovechar las arquitecturas con multiprocesadores. La programacin de comunicaciones (TCP/IP, etc.) que facilita el trabajo en red, sea local o Internet. La programacin de applets, miniaplicaciones pensadas para ser ejecutadas por un navegador web. El soporte para crear interfaces grficas de usuario y un sistema de gestin de eventos, que facilitan la creacin de aplicaciones siguiendo el paradigma de la programacin dirigida por eventos. En esta unidad se desea introducir al lector en este nuevo entorno de programacin y presentar sus principales caractersticas, y se pretende que, partiendo de sus conocimientos del lenguaje C++, alcance los objetivos siguientes: 1) Conocer el entorno de desarrollo de Java. 2) Ser capaz de programar en Java. 3) Entender los conceptos del uso de los hilos de ejecucin y su aplicacin en el entorno Java. 4) Comprender las bases de la programacin dirigida por eventos y ser capaz de desarrollar ejemplos simples. 5) Poder crear applets simples.
311
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Por la variedad de dispositivos y procesadores existentes en el mercado y sus continuos cambios buscaban un entorno de trabajo que no dependiera de la mquina en la que se ejecutara.
Para ello disearon un esquema basado en una plataforma intermedia sobre la cual funcionara un nuevo cdigo mquina ejecutable, y esta plataforma se encargara de la traslacin al sistema subyacente. Este cdigo mquina genrico estara muy orientado al modo de funcionar de la mayora de dichos dispositivos y procesadores, por lo cual la traslacin final haba de ser rpida. El proceso completo consistira, pues, en escribir el programa en un lenguaje de alto nivel y compilarlo para generar cdigo genrico (los bytecodes) preparado para ser ejecutado por dicha plataforma (la mquina virtual). De este modo se conseguira el objetivo de poder escribir el cdigo una sola vez y poder ejecutarlo en todas partes donde estuviera disponible dicha plataforma (Write Once, Run EveryWhere). Teniendo estas referencias, su primer intento fue utilizar C++, pero por su complejidad surgieron numerosas dificultades, por lo que decidieron disear un nuevo lenguaje basndose en C++ para facilitar su aprendizaje. Este nuevo lenguaje deba recoger, adems, las propiedades de los lenguajes modernos y reducir su complejidad eliminando aquellas funciones no absolutamente imprescindibles. El proyecto de creacin de este nuevo lenguaje recibi el nombre inicial de Oak, pero como el nombre estaba registrado, se rebautiz
312
ANOTACIONES
FUOC XP04/90793/00018
con el nombre final de Java. Consecuentemente, la mquina virtual capaz de ejecutar dicho cdigo en cualquier plataforma recibi el nombre de mquina virtual de Java (JVM - Java virtual machine). Los primeros intentos de aplicacin comercial no fructificaron, pero el desarrollo de Internet foment tecnologas multiplataforma, por lo que Java se revel como una posibilidad interesante para la compaa. Tras una serie de modificaciones de diseo para adaptarlo, Java se present por primera vez como lenguaje para ordenadores en el ao 1995, y en enero de 1996, Sun form la empresa Java Soft para desarrollar nuevos productos en este nuevo entorno y facilitar la colaboracin con terceras partes. El mismo mes se dio a conocer una primera versin, bastante rudimentaria, del kit de desarrollo de Java, el JDK 1.0. A principios de 1997 apareci la primera revisin Java, la versin 1.1, mejorando considerablemente las prestaciones del lenguaje, y a finales de 1998 apareci la revisin Java 1.2, que introdujo cambios significativos. Por este motivo, a esta versin y posteriores se las conoce como plataformas Java 2. En diciembre del 2003, la ltima versin de la plataforma Java2 disponible para su descarga en la pgina de Sun es Java 1.4.2. La verdadera revolucin que impuls definitivamente la expansin del lenguaje la caus la incorporacin en 1997 de un intrprete de Java en el navegador Netscape.
Nota
Podis encontrar esta versin en la direccin siguiente: http://java.sun.com
ple, orientado a objetos, distribuido, robusto, seguro, de arquitectura neutra, portable, interpretado, de alto rendimiento, multitarea y dinmico.
Analicemos esta descripcin: Simple . Para facilitar el aprendizaje, se consider que los lenguajes ms utilizados por los programadores eran el C y el C++.
313
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Descartado el C++, se dise un nuevo lenguaje que fuera muy cercano a l para facilitar su comprensin. Con este objetivo, Java elimina una serie de caractersticas poco utilizadas y de difcil comprensin del C++, como, por ejemplo, la herencia mltiple, las coerciones automticas y la sobrecarga de operadores. Orientado a objetos. En pocas palabras, el diseo orientado a objetos enfoca el diseo hacia los datos (objetos), sus funciones e interrelaciones (mtodos). En este punto, se siguen esencialmente los mismos criterios que C++. Distribuido. Java incluye una amplia librera de rutinas que permiten trabajar fcilmente con los protocolos de TCP/IP como HTTP o FTP. Se pueden crear conexiones a travs de la red a partir de direcciones URL con la misma facilidad que trabajando en forma local. Robusto. Uno de los propsitos de Java es buscar la fiabilidad de los programas. Para ello, se puso nfasis en tres frentes: Estricto control en tiempo de compilacin con el objetivo de detectar los problemas lo antes posible. Para ello, utiliza una estrategia de fuerte control de tipos, como en C++, aunque evitando algunos de sus agujeros normalmente debidos a su compatibilidad con C. Tambin permite el control de tipos en tiempo de enlace. Chequeo en tiempo de ejecucin de los posibles errores dinmicos que se pueden producir. Eliminacin de situaciones propensas a generar errores. El caso ms significativo es el control de los apuntadores. Para ello, los trata como vectores verdaderos, controlando los valores posibles de ndices. Al evitar la aritmtica de apuntadores (sumar desplazamiento a una posicin de memoria sin controlar sus lmites) se evita la posibilidad de sobreescritura de memoria y corrupcin de datos.
314
ANOTACIONES
FUOC XP04/90793/00018
Seguro. Java est orientado a entornos distribuidos en red y, por este motivo, se ha puesto mucho nfasis en la seguridad contra virus e intrusiones, y en la autenticacin. Arquitectura neutra. Para poder funcionar sobre variedad de procesadores y arquitecturas de sistemas operativos, el compilador de Java proporciona un cdigo comn ejecutable desde cualquier sistema que tenga la presencia de un sistema en tiempo de ejecucin de Java. Esto evita que los autores de aplicaciones deban producir versiones para sistemas diferentes (como PC, Apple Macintosh, etc.). Con Java, el mismo cdigo compilado funciona para todos ellos. Para ello, Java genera instrucciones bytecodes diseadas para ser fcilmente interpretadas por una plataforma intermedia (la mquina virtual de Java) y traducidas a cualquier cdigo mquina nativo al vuelo. Portable. La arquitectura neutra ya proporciona un gran avance respecto a la portabilidad, pero no es el nico aspecto que se ha cuidado al respecto.
Ejemplo
En Java no hay detalles que dependan de la implementacin, como podra ser el tamao de los tipos primitivos. En Java, a diferencia de C o C++, el tipo int siempre se refiere a un nmero entero de 32 bits con complemento a 2 y el tipo float un nmero de 32 bits siguiendo la norma IEEE 754. La portabilidad tambin viene dada por las libreras. Por ejemplo, hay una clase Windows abstracta y sus implementaciones para Windows, Unix o Macintosh. Interpretado. Los bytecodes en Java se traducen en tiempo de ejecucin a instrucciones de la mquina nativa (son interpretadas) y no se almacenan en ningn lugar. Alto rendimiento. A veces se requiere mejorar el rendimiento producido por la interpretacin de los bytecodes, que ya es bas315
ANOTACIONES
FUOC XP04/90793/00018
Software libre
tante bueno de por s. En estos casos, es posible traducirlos en tiempo de ejecucin al cdigo nativo de la mquina donde la aplicacin se est ejecutando. Esto es, compilar el lenguaje de la JVM al lenguaje de la mquina en la que se haya de ejecutar el programa. Por otro lado, los bytecodes se han diseado pensando en el cdigo mquina por lo que el proceso final de la generacin de cdigo mquina es muy simple. Adems, la generacin de los bytecodes es eficiente y se le aplican diversos procesos de optimizacin. Multitarea. Java proporciona dentro del mismo lenguaje herramientas para construir aplicaciones con mltiples hilos de ejecucin, lo que simplifica su uso y lo hace ms robusto. Dinmico. Java se dise para adaptarse a un entorno cambiante. Por ejemplo, un efecto lateral del C++ se produce debido a la forma en la que el cdigo se ha implementado. Si un programa utiliza una librera de clases y sta cambia, hay que recompilar todo el proyecto y volverlo a redistribuir. Java evita estos problemas al hacer las interconexiones entre los mdulos ms tarde, permitiendo aadir nuevos mtodos e instancias sin tener ningn efecto sobre sus clientes. Mediante las interfaces se especifican un conjunto de mtodos que un objeto puede realizar, pero deja abierta la manera como los objetos pueden implementar estos mtodos. Una clase Java puede implementar mltiples interfaces, aunque slo puede heredar de una nica clase. Las interfaces proporcionan flexibilidad y reusabilidad conectando objetos segn lo que queremos que hagan y no por lo que hacen. Las clases en Java se representan en tiempo de ejecucin por una clase llamada Class, que contiene las definiciones de las clases en tiempo de ejecucin. De este modo, se pueden hacer comprobaciones de tipo en tiempo de ejecucin y se puede confiar en los tipos en Java, mientras que en C++ el compilador solo confa en que el programador hace lo correcto.
316
ANOTACIONES
FUOC XP04/90793/00018
Entre los IDEs disponibles actualmente se puede destacar el proyecto Eclipse, que, siguiendo la filosofa de cdigo abierto, ha conseguido un paquete de desarrollo muy completo (SDK .- standard development kit) para diversos sistemas operativos (Linux, Windows, Sun, Apple, etc.). Este paquete est disponible para su descarga en http://www.eclipse.org. Otro IDE interesante es JCreator, que adems de desarrollar una versin comercial, dispone de una versin limitada, de fcil manejo. Este paquete est disponible para su descarga en http://www.jcreator.com.
Otra caracterstica particular de Java es que se pueden generar varios tipos de aplicaciones: Aplicaciones independientes. Un fichero que se ejecuta directamente sobre la mquina virtual de la plataforma.
317
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Applets. Miniaplicaciones que no se pueden ejecutar directamente sobre la mquina virtual, sino que estn pensadas para ser cargadas y ejecutadas desde un navegador web. Por este motivo, incorpora unas limitaciones de seguridad extremas. Servlets. Aplicaciones sin interfaz de usuario para ejecutarse desde un servidor y cuya funcin es dar respuesta a las acciones de navegadores remotos (peticin de pginas HTML, envo de datos de un formulario, etc.). Su salida generalmente es a travs de ficheros, como por ejemplo, ficheros HTML. Para generar cualquiera de los tipos de aplicaciones anteriores, slo se precisa lo siguiente: Un editor de textos donde escribir el cdigo fuente en lenguaje Java. La plataforma Java, que permite la compilacin, depurado, ejecucin y documentacin de dichos programas.
ANOTACIONES
318
Mquina virtual (MV). Como ya hemos comentado, una de las principales caractersticas que proporciona Java es la independencia de la plataforma hardware: una vez compilados, los programas se deben poder ejecutar en cualquier plataforma. La estrategia utilizada para conseguirlo es generar un cdigo ejecutable neutro (bytecode) como resultado de la compilacin. Este cdigo neutro, que est muy orientado al cdigo mquina, se ejecuta
FUOC XP04/90793/00018
desde una mquina hipottica o mquina virtual. Para ejecutar un programa en una plataforma determinada basta con disponer de una mquina virtual para dicha plataforma. Application programming interface (API). El API de Java es una gran coleccin de software ya desarrollado que proporciona mltiples capacidades como entornos grficos, comunicaciones, multiproceso, etc. Est organizado en libreras de clases relacionadas e interfaces. Las libreras reciben el nombre de packages. En el siguiente esquema, se puede observar la estructura de la plataforma Java y como la mquina virtual asla el cdigo fuente (.java) del hardware de la mquina:
Programa.java API Java Mquina virtual Java Plataforma basada en hardware
ANOTACIONES
/**
FUOC XP04/90793/00018
Software libre
2) Compilar el programa generando un fichero bytecode. Para ello, utilizaremos el compilador javac, que nos proporciona el entorno de desarrollo, y que traduce el cdigo fuente a instrucciones que la JVM pueda interpretar. Si despus de teclear javac HolaMundo.java en el intrprete de comandos, no se produce ningn error, obtenemos nuestro primer programa en Java: un fichero HolaMundo.class. 3) Ejecutar el programa en la mquina virtual de Java. Una vez generado el fichero de bytecodes, para ejecutarlo en la JVM slo deberemos escribir la siguiente instruccin, para que nuestro ordenador lo pueda interpretar, y nos aparecer en pantalla el mensaje de bienvenida Hola mundo!: java HolaMundo
La nica consideracin a tener en cuenta es que, en Java, las expresiones condicionales (por ejemplo, la condicin if) deben retornar un valor de tipo boolean, mientras que C++, por compatibilidad con C, permita el retorno de valores numricos y asimilaba 0 a false y los valores distintos de 0 a true. Respecto a los comentarios, Java admite las formas provenientes de C++ ( /* ... */ y // ... ) y aade una nueva: incluir el texto entre las secuencias /** (inicio de comentario) y */ (fin de comentario). De hecho, la utilidad de esta nueva forma no es tanto la de comentar, sino la de documentar. Java proporciona herramientas (por ejemplo, javadoc) para generar documentacin a partir de los cdigos fuentes que extraen el contenido de los comentarios realizados siguiendo este modelo.
Ejemplo
/** * * */ Texto comentado con la nueva forma de Java para su inclusin en documentacin generada automticamente
ANOTACIONES
320
FUOC XP04/90793/00018
5.5.1. Entrada/salida
Como Java est pensado principalmente para trabajar de forma grfica, las clases que gestionan la entrada / salida en modo texto se han desarrollado de manera muy bsica. Estn reguladas por la clase System que se encuentra en la librera java.lang, y de esta clase se destacan tres objetos estticos que son los siguientes: System.in. Recibe los datos desde la entrada estndar (normalmente el teclado) en un objeto de la clase InputStream (flujo de entrada). System.out. Imprime los datos en la salida estndar (normalmente la pantalla) un objeto de la clase OutputStream (flujo de salida). System.err. Imprime los mensajes de error en pantalla. Los mtodos bsicos de que disponen estos objetos son los siguientes: System.in.read(). Lee un carcter y lo devuelve en forma de entero. System.out.print(var). Imprime una variable de cualquier tipo primitivo. System.out.println(var). Igual que el anterior pero aadiendo un salto de lnea final.
321
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Por tanto, para escribir un mensaje nos basta utilizar bsicamente las instrucciones System.out.print() y System.out.println(): int unEntero = 35; double unDouble = 3.1415; System.out.println("Mostrando un texto"); System.out.print("Mostrando un entero "); System.out.println (unEntero); System.out.print("Mostrando un double "); System.out.println (unDouble); Mientras que la salida de datos es bastante natural, la entrada de datos es mucho menos accesible pues el elemento bsico de lectura es el carcter. A continuacin se presenta un ejemplo en el que se puede observar el proceso necesario para la lectura de una cadena de caracteres:
String miVar; InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); // La entrada finaliza al pulsar la tecla Entrar miVar = br.readLine();
Si se desea leer lneas completas, se puede hacer a travs del objeto BufferedReader, cuyo mtodo readLine() llama a un lector de caracteres (un objeto Reader) hasta encontrar un smbolo de final de lnea (\n o \r). Pero en este caso, el flujo de entrada es un objeto InputStream, y no tipo Reader. Entonces, necesitamos una clase que acte como lectora para un flujo de datos InputStream. Ser la clase InputStreamReader.
ANOTACIONES
322
No obstante, el ejemplo anterior es vlido para Strings. Cuando se desea leer un nmero entero u otros tipos de datos, una vez realizada la lectura, se debe hacer la conversin. Sin embargo, esta conversin puede llegar a generar un error fatal en el sistema si el texto introducido no coincide con el tipo esperado. En este caso, Java nos obliga a considerar siempre dicho control de errores. La gestin de errores (que provocan las llamadas excepciones) se hace, igual que en C++, a travs de la sentencia try {... } catch {...} finally {...}.
FUOC XP04/90793/00018
A continuacin, veremos cmo se puede disear una clase para que devuelva un nmero entero ledo desde teclado: Leer.java import java.io.*; public class Leer { public static String getString() { String str = ""; try { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); str = br.readLine(); } catch(IOException e) { System.err.println("Error: " + e.getMessage()); } return str; // devolver el dato tecleado } public static int getInt() { try { return Integer.parseInt(getString()); } catch(NumberFormatException e) return Integer.MIN_VALUE; // valor ms pequeo } } // getInt // se puede definir una funcin para cada tipo... public static double getDouble() {} // getDouble } // Leer
323
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En el bloque try { ... } se incluye el trozo de cdigo susceptible de sufrir un error. En caso de producirse, se lanza una excepcin que es recogida por el bloque catch { ... }. En el caso de la conversin de tipos string a nmeros, la excepcin que se puede producir es del tipo NumberFormatException. Podra haber ms bloques catch para tratar diferentes tipos de excepcin. En el ejemplo, si se produce error el valor numrico devuelto corresponde al mnimo valor posible que puede tomar un nmero entero. El bloque finally { ... } corresponde a un trozo de cdigo a ejecutar tanto si ha habido error, como si no (por ejemplo, cerrar ficheros), aunque su uso es opcional. De forma similar, se pueden desarrollar funciones para cada uno de los tipos primitivos de Java. Finalmente, la lectura de un nmero entero sera como sigue: int i; ... i = Leer.getInt( );
5.5.2. El preprocesador
Java no dispone de preprocesador, por lo que diferentes rdenes (generalmente, originarias de C) se eliminan. Entre stas, las ms conocidas son las siguientes: defines. Estas rdenes para la definicin de constantes, ya en C++ haban perdido gran parte de su sentido al poder declarar variables const, y ahora se implementan a partir de las variables final. include. Esta orden, que se utilizaba para incluir el contenido de un fichero, era muy til en C++, principalmente para la reutilizacin de los ficheros de cabeceras. En Java, no hay ficheros de cabecera y las libreras (o paquetes) se incluyen mediante la instruccin import.
324
ANOTACIONES
FUOC XP04/90793/00018
ANOTACIONES
FUOC XP04/90793/00018
Software libre
dra asimilarse a los apuntadores en otros lenguajes. No obstante, al no permitir las operaciones explcitas con las direcciones de memoria, para acceder a ellas bastar con utilizar el nombre de la variable. Por otro lado, Java elimina los tipos struct y union que se pueden implementar con class y que se mantenan en C++ por compatibilidad con C. Tambin elimina el tipo enum, aunque se puede emular utilizando constantes numricas con la palabra clave final. Tambin se eliminan definitivamente los typedefs para la definicin de tipos, que en C++ ya haban perdido gran parte de su sentido al hacer que las clases, Structs, Union y Enum fueran tipos propios. Finalmente, slo admite las coerciones de tipos automticas (type casting) en el caso de conversiones seguras; es decir, donde no haya riesgo de perder ninguna informacin. Por ejemplo, admite las conversiones automticas de tipo int a float, pero no en sentido inverso donde se perderan los decimales. En caso de posible prdida de informacin, hay que indicarle explcitamente que se desea realizar la conversin de tipos. Otra caracterstica muy destacable de Java es la implementacin que realiza de los vectores. Los trata como a objetos reales y genera una excepcin (error) cuando se superan sus lmites. Tambin dispone de un miembro llamado length para indicar su longitud, lo que proporciona un incremento de seguridad del lenguaje al evitar accesos indeseados a la memoria. Para trabajar con cadenas de caracteres, Java dispone de los tipos String y StringBuffer. Las cadenas definidas entre comillas dobles se convierten automticamente a objetos String, y no pueden modificarse. El tipo StringBuffer es similar, pero permite la modificacin de su valor y proporciona mtodos para su manipulacin.
ANOTACIONES
326
FUOC XP04/90793/00018
quier error en su gestin puede acarrear problemas muy graves en la aplicacin y, quizs, en el sistema. De hecho, la presencia de los apuntadores en C y C++ se deba al uso de cadenas y de vectores. Java proporciona objetos tanto para las cadenas, como los vectores, por lo que, para estos casos, ya no son necesarios los apuntadores. La otra gran necesidad, los pasos de parmetros por variable, queda cubierta por el uso de referencias. Como en Java el tema de la seguridad es primordial, se opt por no permitir el uso de apuntadores, al menos en el sentido en que se entendan en C y C++. En C++, se prevean dos formas de trabajar con apuntadores: Con su direccin, permitiendo incluso operaciones aritmticas sobre ella (apuntador). Con su contenido (* apuntador). En Java se eliminan todas las operaciones sobre las direcciones de memoria. Cuando se habla de referencias se hace con un sentido diferente de C++. Una variable dinmica corresponde a la referencia al objeto (apuntador): Para ver el contenido de la variable dinmica, basta utilizar la forma (apuntador). Para crear un nuevo elemento, se mantiene el operador new. Si se asigna una variable tipo referencia (por ejemplo, un objeto) a otra variable del mismo tipo (otro objeto de la misma clase) el misma posicin de la segunda variable. El resultado final es que el contenido de ambas es el mismo. contenido no se duplica, sino que la primera variable apunta a la
Java no permite operar directamente con las direcciones de memoria, lo que simplifica el acceso a su contenido: se hace a travs del nombre de la variable (en lugar de utilizar la forma desreferenciada *nombre_variable).
327
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Otro de los principales riesgos que entraa la gestin directa de la memoria es la de liberar correctamente el espacio ocupado por las variables dinmicas cuando se dejan de utilizar. Java resuelve esta problemtica proporcionando una herramienta que libera automticamente dicho espacio cuando detecta que ya no se va a volver a utilizar ms. Esta herramienta conocida como recolector de basura (garbage collector) forma parte del Java durante la ejecucin de sus programas. Por tanto, no es necesaria ninguna instruccin delete, basta con asignar el apuntador a null, y el recolector de memoria detecta que la zona de memoria ya no se utiliza y la libera. Si lo deseamos, en lugar de esperar a que se produzca la recoleccin de basura automticamente, podemos invocar el proceso a travs de la funcion gc(). No obstante, para la JVM dicha llamada slo se considera como una sugerencia.
ANOTACIONES
328
Por otro lado, Java continua soportando la sobrecarga de funciones, aunque no permite al programador la sobrecarga de operadores, a pesar de que el compilador utiliza esta caracterstica internamente.
FUOC XP04/90793/00018
En el caso de los tipos de datos primitivos, los mtodos siempre reciben una copia del valor original, que no se puede modificar. En el caso de tipo de datos de referencia, tambin se copia el valor de dicha referencia. No obstante, por la naturaleza de las referencias, los cambios realizados en la variable recibida por parmetro tambin afectan a la variable original. Para modificar las variables pasadas por parmetro a la funcin, debemos incluirlas como variables miembro de la clase y pasar como argumento la referencia a un objeto de dicha clase.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
x = coordx; y = coordy; } // calcula la distancia a otro punto float distancia(Punto2D npunto) { int dx = x npunto.x; int dy = y npunto.y; return ( Math.sqrt(dx * dx + dy * dy)); } } La primera diferencia es la inclusin de la definicin de los mtodos en el interior de la clase y no separada como en C++. Al seguir este criterio, ya no es necesario el operador de mbito (::). La segunda diferencia es que en Java no es preciso el punto y coma (;) final. Las clases se guardan en un fichero con el mismo nombre y con la extensin .java (Punto2.java). Una caracterstica comn a C y C++ es que Java tambin es sensible a las maysculas, por lo cual la clase Punto2D es diferente a punto2d o pUnTo2d. Java permite guardar ms de una clase en un fichero pero slo permite que una de ellas sea pblica. Esta clase ser la que dar el nombre al archivo. Por tanto, salvo raras excepciones, se suele utilizar un archivo independiente para cada clase.
ANOTACIONES
330
En la definicin de la clase, de forma similar a C++, se declaran los atributos (o variables miembro) y los mtodos (o funciones miembro) tal como se puede observar en el ejemplo anterior.
FUOC XP04/90793/00018
Punto2D
puntoUno;
El resultado es que puntoUno es una referencia a un objeto de la clase Punto2D. Inicialmente, esta referencia tiene valor null y no ha hecho ninguna reserva de memoria. Para poder utilizar esta variable para guardar informacin, es necesario crear una instancia mediante el operador new. Al utilizarlo, se llama al constructor del objeto Punto2D definido.
puntoUno = new Punto2D(2,2); // inicializando a (2,2)
Una diferencia importante en Java respecto a C++, es el uso de referencias para manipular los objetos. Como se ha comentado anteriormente, la asignacin de dos variables declaradas como objetos slo implica la asignacin de su referencia: Punto2D puntoDos;
puntoDos = puntoUno; Si se aade la instruccin anterior, no se ha hecho ninguna reserva especfica de memoria para la referencia a objeto puntoDos. Al realizar la asignacin, puntoDos har referencia al mismo objeto apuntado por puntoUno, y no a una copia. Por tanto, cualquier cambio sobre los atributos de puntoUno se vern reflejados en puntoDos.
float dist; i = puntoUno.x; dist = puntoUno.distancia(5,1); En C++ se poda acceder al objeto a travs de la desreferencia de un apuntador a dicho objeto (*apuntador), en cuyo caso, el acceso a sus atributos o mtodos poda hacerse a travs del operador
331
ANOTACIONES
int i;
FUOC XP04/90793/00018
Software libre
punto (*apuntador.atributo) o a travs de su forma de acceso abreviada mediante el operador (apuntadoratributo). En Java, al no existir la forma desreferenciada *apuntador, tampoco existe el operador . Finalmente Java, igual que C++, permite el acceso al objeto dentro de los mtodos de la clase a travs del objeto this.
ANOTACIONES
FUOC XP04/90793/00018
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En el ejemplo anterior, se dice que la clase Perro es una clase derivada de la clase Mamifero. Tambin es posible leer la relacin en el sentido contrario indicando que la clase Mamifero es una superclase de la clase Perro.
En C++ era posible la herencia mltiple, es decir, recibir los atributos y mtodos de varias clases. Java no admite esta posibilidad, aunque en cierta manera permite una funcionalidad parecida a travs de las interfaces.
ANOTACIONES
334
FUOC XP04/90793/00018
Podemos decir que la clase Object es la superclase de la cual derivan directa o indirectamente todas las dems clases en Java. La clase Object proporciona una serie de mtodos comunes, entre los cuales los siguientes: public boolean equals ( Object obj ). Se utiliza para comparar el contenido de dos objetos y devuelve true si el objeto recibido coincide con el objeto que lo llama. Si slo se desean comparar dos referencias a objeto, se pueden utilizar los operadores de comparacin == y !=. protected Object Clone ( ). Retorna una copia del objeto.
5.7.3. Polimorfismo
C++ implementaba la capacidad de una variable de poder tomar varias formas a travs de apuntadores a objetos. Como se ha comentado, Java no dispone de apuntadores y cubre esta funcin a travs de referencias, pero el funcionamiento es similar. Mamifero mamiferoUno = new Perro; Mamifero mamiferoDos = new Mamifero;
ANOTACIONES
FUOC XP04/90793/00018
Software libre
La implementacin en C++ se haca a travs de las funciones virtuales puras, cuya forma de representarla es, como menos, un poco peculiar: se declaraban asignando la funcin virtual a 0. La implementacin de Java para estos casos es mucho ms sencilla: anteponer la palabra reservada abstract al nombre de la funcin. Al declarar una funcin como abstract, ya se indica que la clase tambin lo es. No obstante, es recomendable explicitarlo en la declaracin anteponiendo la palabra abstract a la palabra reservada class. El hecho de definir una funcin como abstract obliga a que las clases deriv adas que puedan recibir este mtodo la redefinan. Si no lo hacen, heredan la funcin como abstracta y, como consecuencia, ellas tambin lo sern, lo que impedir instanciar objetos de dichas clases. abstract class ObraDeArte { String autor; ObraDeArte(){} //constructor abstract void mostrarObraDeArte(); //abstract void asignarAutor(String nAutor) { autor = nAutor; } String obtenerAutor(); { return (autor); } }; En el ejemplo anterior, se ha declarado como abstracta la funcin mostrarObraDeArte(), lo que obliga a redefinirla en las clases derivadas. Por tanto, no incluye ninguna definicin. Por otro lado, destacamos que, al ser una clase abstracta, no ser posible hacer un new ObraDeArte.
ANOTACIONES
336
FUOC XP04/90793/00018
zadas, no pueden ser modificadas. El mismo concepto se puede aplicar a clases y mtodos: Las clases finales no tienen ni pueden tener clases derivadas. Los mtodos finales no pueden ser redefinidos en las clases derivadas.
El uso de la palabra reservada final se convierte en una medida de seguridad para evitar usos incorrectos o maliciosos de las propiedades de la herencia que pudiesen suplantar funciones establecidas.
5.7.6. Interfaces
Una interfaz es una coleccin de definiciones de mtodos (sin sus implementaciones), cuya funcin es definir un protocolo de comportamiento que puede ser implementado por cualquier clase independientemente de su lugar en la jerarqua de clases. Al indicar que una clase implementa una interfaz, se le obliga a redefinir todos los mtodos definidos. En este aspecto, las interfaces se asemejan a las clases abstractas. No obstante, mientras una clase slo puede heredar de una superclase (slo permite herencia simple), puede implementar varias interfaces. Ello slo indica que cumple con cada uno de los protocolos definidos en cada interfaz. A continuacin presentamos un ejemplo de declaracin de interfaz: public interface NombreInterfaz Extends SuperInterfaz1, SuperInterfaz2 { cuerpo interfaz }
Si una interfaz no se especifica como pblica, slo ser accesible para las clases definidas en su mismo paquete.
337
ANOTACIONES
FUOC XP04/90793/00018
Software libre
El cuerpo de la interfaz contiene las declaraciones de todos los mtodos incluidos en ella. Cada declaracin se finaliza en punto y coma (;) pues no tienen implementaciones e implcitamente se consideran public y abstract. El cuerpo tambin puede incluir constantes en cuyo caso se consideran public, static y final. Para indicar que una clase implementa una interface, basta con aadir la palabra clave implements en su declaracin. Java permite la herencia mltiple de interfaces: class MiClase extends SuperClase implements Interfaz1, interfaz2 { ... } Cuando una clase declara una interfaz, es como si firmara un contrato por el cual se compromete a implementar los mtodos de la interfaz y de sus superinterfaces. La nica forma de no hacerlo es declarar la clase como abstract, con lo cual no se podr instanciar objetos y se transmitir esa obligacin a sus clases derivadas. De hecho, a primera vista parece que hay muchas similitudes entre las clases abstractas y las interfaces pero las diferencias son significativas: Una interfaz no puede implementar mtodos, mientras que las clases abstractas si que lo hacen. Una clase puede tener varias interfaces, pero slo una superclase.
ANOTACIONES
338
Las interfaces no forman parte de la jerarqua de clases y, por tanto, clases no relacionadas pueden implementar la misma interfaz. Otra caracterstica relevante de las interfaces es que al definirlas se est declarando un nuevo tipo de datos referencia. Una variable de dicho tipo de datos se podr instanciar por cualquier clase que implemente esa interfaz. Esto proporciona otra forma de aplicar el polimorfismo.
FUOC XP04/90793/00018
5.7.7. Paquetes
Para organizar las clases, Java proporciona los paquetes. Un paquete (package) es una coleccin de clases e interfaces relacionadas que proporcionan proteccin de acceso y gestin del espacio de nombres. Las clases e interfaces siempre pertenecen a un paquete.
Nota
De hecho, las clases e interfaces que forman parte de la plataforma de Java pertenecen a varios paquetes organizados por su funcin: java.lang incluye las clases fundamentales, java.io las clases para entrada/salida, etc.
El hecho de organizar las clases en paquetes evita en gran medida que pueda haber una colisin en la eleccin del nombre.
Para definir una clase o una interfaz en un paquete, basta con incluir en la primera lnea del archivo la expresin siguiente: package miPaquete; Si no se define ningn paquete, se incluye dentro del paquete por defecto (default package), lo que es una buena solucin para pequeas aplicaciones o cuando se comienza a trabajar en Java. Para acceder al nombre de la clase, se puede hacer a travs del
miPaquete.MiClase Otra posibilidad es la importacin de las clases pblicas del paquete mediante la palabra clave import. Despus, es posible utilizar el nombre de la clase o de la interfaz en el programa sin el prefijo de ste: import miPaquete.MiClase; //importa slo la clase import miPaquete.* // importa todo el paquete
339
ANOTACIONES
nombre largo:
FUOC XP04/90793/00018
Software libre
Ejemplo
La importacin de java.awt no incluye las clases del subpaquete java.awt.event.
Hay que tener en cuenta que importar un paquete no implica importar los diferentes subpaquetes que pueda contener.
Por convencin, Java siempre importa por defecto del paquete java.lang. Para organizar todas las clases y paquetes posibles, se crea un subdirectorio para cada paquete donde se incluyen las diferentes clases de dicho paquete. A su vez, cada paquete puede tener sus subpaquetes, que se encontrarn en un subdirectorio. Con esta organizacin de directorios y archivos, tanto el compilador como el intrprete tienen un mecanismo automtico para localizar las clases que necesitan otras aplicaciones.
Ejemplo
La clase graficos.figuras.rectangulo se encontrara dentro del paquete graficos.figuras y el archivo estara localizado en graficos\figuras\rectangulo.java.
ANOTACIONES
Tabla 9. Clases incorporadas Clases fundamentales para el lenguaje como la clase String y otras. Clases para la entrada y salida a travs de flujos de datos, y ficheros del sistema. Clases de utilidad como colecciones de datos y clases, el modelo de eventos, facilidades horarias, generacin aleatoria de nmeros, y otras. 340
Paquete
FUOC XP04/90793/00018
Clases incorporadas Clase que agrupa todas las funciones matemticas. Clase con utilidades para crear applets y clases que las applets utilizan para comunicarse con su contexto. Clases que permiten la creacin de interfaces grficas con el usuario, y dibujar imgenes y grficos. Clases con componentes grficos que funcionan igual en todas las plataformas Java. Clases responsables de la seguridad en Java (encriptacin, etc.). Clases con funciones para aplicaciones en red. Clase que incorpora el JDBC para la conexin de Java con bases de datos.
Figura 15.
341
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Dentro de este funcionamiento secuencial, el proceso recibe sucesos externos que pueden ser esperados (entradas de datos del usuario por teclado, ratn u otras formas, lecturas de informacin del sistema, etc.) o inesperados (errores de sistema, etc.). A cada uno de estos sucesos externos lo denominaremos evento. En los paradigmas anteriores, los eventos no alteran el orden del flujo de instrucciones previsto: se les atiende para resolverlos o, si no es posible, se produce una finalizacin del programa. En el paradigma de programacin dirigida por eventos no se fija una secuencia nica de acciones, sino que prepara reacciones a los eventos que puedan ir sucediendo una vez iniciada la ejecucin del programa. Por tanto, en este modelo son los datos introducidos los que regulan la secuencia de control de la aplicacin. Tambin se puede observar que las aplicaciones difieren en su diseo respecto de los paradigmas anteriores: estn preparadas para permanecer en funcionamiento un tiempo indefinido, recibiendo y gestionando eventos.
Figura 16.
ANOTACIONES
342
FUOC XP04/90793/00018
del cdigo entre la generacin del evento y su manipulacin que nos facilitar su programacin. Diferenciaremos los cuatro tipos de elementos que intervienen: El evento (qu se recibe). En la gran mayora de los casos, es el sistema operativo quien proporciona el evento y gestiona finalmente todas las operaciones de comunicaciones con el usuario y el entorno. Se almacena en un objeto derivado de la clase Event y que depende del tipo de evento sucedido. Los principales tienen relacin con el entorno grfico y son: ActionEvent, KeyEvent, MouseEvent, AdjustmentEvent, WindowEvent, TextEvent, ItemEvent, FocusEvent, ComponentEvent, ContainerEvent. Cada una de estas clases tiene sus atributos y sus mtodos de acceso. La fuente del evento (dnde se recibe). Corresponde al elemento donde se ha generado el evento y, por tanto, recoge la informacin para tratarla o, en nuestro caso, para traspasarla a su gestor de eventos. En entornos grficos, suele corresponder al elemento con el cual el usuario ha interactuado (un botn, un cuadro de texto, etc.). El gestor de eventos (quin lo gestiona). Es la clase especializada que indica, para cada evento, cul es la respuesta deseada. Cada gestor puede actuar ante diferentes tipos de eventos con slo asignarle los perfiles adecuados. El perfil del gestor (qu operaciones debe implementar el gestor). Para facilitar esta tarea existen interfaces que indican los mtodos a implementar para cada tipo de evento. Normalmente, el nombre de esta interfaz es de la forma <nombreEvento>Listener (literalmente, el que escucha el evento).
Ejemplo
KeyListener es la interfaz para los eventos de teclado y consideralos tres mtodos siguientes: keyPressed, keyReleased y keyTyped. En algunos casos, la obligacin de implementar todos los mtodos supone una carga intil. Para estas situaciones, Java proporciona adaptadores <nombreEvento>Adapter que implementan los diferentes mtodos vacos permitiendo as redefinir slo aquellos mtodos que nos interesan.
343
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Los principales perfiles (o interfaces) definidos por Java son los siguientes: ActionListener, KeyListener, MouseListener,
WindowListener, TextListener, ItemListener, FocusListener, AdjustmentListener, ComponentListener y ContainerListener .
Todos ellos derivados de la interfaz EventListener. Finalmente, basta con establecer la relacin entre la fuente del evento y su gestor. Para ello, en la clase fuente aadiremos un mtodo del tipo add<nombreEvento>Listener. De hecho, se podra considerar que los eventos no son realmente enviados al gestor de eventos, sino que es el propio gestor de eventos el que es asignado al evento.
Nota
Ejemplo
Si a un objeto botn de la clase Button deseamos aadirle un Listener de los eventos de ratn haremos: boton.addMouseListener(gestorEventos) .
Comprenderemos ms fcilmente el funcionamiento de los eventos a travs de un ejemplo prctico, como el que muestra la creacin de un applet mediante la librera grfica Swing que se ver ms adelante en esta unidad.
ANOTACIONES
FUOC XP04/90793/00018
instrucciones. En este caso, todos los hilos de ejecucin comparten el mismo espacio de memoria y se utiliza el mismo contexto y los mismos recursos asignados al proceso. Java incorpora la posibilidad de que un proceso tenga mltiples hilos de ejecucin simultneos. El conocimiento completo de su implementacin en Java supera los objetivos del curso y, a continuacin, nos limitaremos a conocer las bases para la creacin de los hilos y su ciclo de vida.
5.9.1.
En Java, hay dos formas de crear hilos de ejecucin: Crear una nueva clase que herede de java.lang.Thread y sobrecargar el mtodo run() de dicha clase. Crear una nueva clase con la interfaz java.lang.Runnable donde se implementar el mtodo run(), y despus crear un objeto de tipo Thread al que se le pasa como argumento un objeto de la nueva clase. Siempre que sea posible se utilizar la primera forma, por su simplicidad. No obstante, si la clase ya hereda de alguna otra superclase, no ser posible derivar tambin de la clase Thread (Java no permite la herencia mltiple), con lo cual se deber escoger la segunda forma. Veamos un ejemplo de cada una de las formas de crear hilos de ejecucin: Creacin de hilos de ejecucin derivando de la clase Thread ProbarThread.java class ProbarThread { public static void main(String args[] ) { AThread a = new AThread(); BThread b = new BThread();
345
ANOTACIONES
FUOC XP04/90793/00018
Software libre
a.start(); b.start(); } } class AThread extends Thread { public void run() { int i; for (i=1;i<=10; i++) System.out.print(" A"+i); } } class BThread extends Thread { public void run() { int i; for (i=1;i<=10; i++) System.out.print(" B"+i); } } En el ejemplo anterior, se crean dos nuevas clases que derivan de la clase Thread: las clases AThread y BThread. Cada una de ellas muestra en pantalla un contador precedido por la inicial del proceso. En la clase ProbarThreads, donde tenemos el mtodo main(), se procede a la instanciacin de un objeto para cada una de las cla-
ANOTACIONES
346
ses Thread y se inicia su ejecucin. El resultado final ser del tipo (aunque no por fuerza en este orden): A1 B1 A2 B2 A3 B3 A4 B4 A5 B5 A6 B6 A7 B7 A8 B8 A9 B9 A10 B10 Finalmente, solo hacer notar que en la ejecucin ProbarThreads se ejecutan 3 hilos: el principal y los dos creados.
FUOC XP04/90793/00018
Creacin de hilos de ejecucin implementando la interfaz Runnable Probar2Thread.java class Probar2Thread { public static void main(String args[]) { AThread a = new AThread(); BThread b = new BThread(); a.start(); b.start(); } } class AThread implements Runnable { Thread t; public void start() { t = new Thread(this); t.start(); } public void run() { int i; for (i=1;i<=50; i++) System.out.print(" A"+i); } } class BThread implements Runnable { Thread t; public void start() { t = new Thread(this); t.start(); } public void run() { int i; for (i=1;i<=50; i++) System.out.print(" B"+i); } }
347
ANOTACIONES
FUOC XP04/90793/00018
Software libre
En este ejemplo, se puede observar que la clase principal main() no ha cambiado, pero s lo ha hecho la implementacin de cada una de las clases AThread y BThread. En cada una de ellas, adems de implementar la interfaz Runnable, se tiene que definir un objeto de la clase Thread y redefinir el mtodo start() para que llame al start() del objeto de la clase Thread pasndole el objeto actual this. Para finalizar, dos cosas: es posible pasarle un nombre a cada hilo de ejecucin para identificarlo, puesto que la clase Thread tiene el constructor sobrecargado para admitir esta opcin: public Thread (String nombre); public Thread (Runnable destino, String nombre); Siempre es posible recuperar el nombre a travs del mtodo: public final String getName();
5.9.2.
El ciclo de vida de los hilos de ejecucin se puede representar a partir de los estados por los que pueden pasar: Nuevo (new): el thread se acaba de crear pero todava no est inicializado, es decir, todava no se ha ejecutado el mtodo start(). Ejecutable (runnable): el thread se est ejecutando o est en disposicin para ello.
ANOTACIONES
348
Bloqueado (blocked o not runnable): el thread est bloqueado por algn mensaje interno sleep(), suspend() o wait() o por alguna actividad interna, por ejemplo, en espera de una entrada de datos. Si est en este estado, no entra dentro de la lista de tareas a ejecutar por el procesador. Para volver al estado de Ejecutable, debe recibir un mensaje interno resume() o notify() o finalizar la situacin que provocaba el bloqueo.
FUOC XP04/90793/00018
Muerto (dead): el mtodo habitual de finalizar un thread es que haya acabado de ejecutar las instrucciones del mtodo run(). Tambin podra utilizarse el mtodo stop(), pero es una opcin considerada peligrosa y no recomendada.
Figura 17.
ANOTACIONES
FUOC XP04/90793/00018
Software libre
plo, que slo puedan leer y escribir ficheros desde su servidor (y no desde el ordenador local), que slo puedan acceder a informacin limitada en el ordenador donde se ejecutan, etc. Los applets no tienen ventana propia, que se ejecutan en una ventana del navegador. Desde el punto de vista del programador, destacan los siguientes aspectos: No necesitan mtodo main. Su ejecucin se inicia por otros mecanismos. Derivan siempre de la clase java.applet.Applet y, por tanto, deben redefinir algunos de sus mtodos como init(), start(), stop() y destroy(). Tambin suelen redefinir otros mtodos como paint(), update() y repaint() heredados de clases superiores para tareas grficas. Disponen de una serie de mtodos para obtener informacin sobre el applet o sobre otros applets en ejecucin en la misma pgina como getAppletInfo(), getAppletContext(), getParameter(), etc.
ANOTACIONES
350
void start(). Se llama cuando la pgina se ha cargado, parado (por minimizacin de la ventana, cambio de pgina web, etc.) y se ha vuelto a activar. void stop(). Se llama de forma automtica al ocultar el
applet. En este mtodo, se suelen parar los hilos que se estn ejecutando para no consumir recursos innecesarios. void destroy(). Se llama a este mtodo para liberar los recursos (menos la memoria) del applet.
FUOC XP04/90793/00018
Figura 18.
Al ser los applets aplicaciones grficas que aparecen en una ventana del navegador, tambin es til redefinir el siguiente mtodo: void paint(Graphics g). En esta funcin se debe incluir todas las operaciones con grficos, porque este mtodo es llamado cuando el applet se dibuja por primera vez y cuando se redibuja.
NAME = unnombre lo cual le permite comunicarse con otros applets ARCHIVE = unarchivo donde se guardan las clases en un .zip o un .jar PARAM NAME = param1 VALUE = valor1 para poder pasar parmetros al applet.
351
ANOTACIONES
FUOC XP04/90793/00018
Software libre
ANOTACIONES
FUOC XP04/90793/00018
Las interfaces grficas permiten una comunicacin mucho ms gil con el usuario facilitando su interaccin con el sistema en mltiples puntos de la pantalla. Se puede elegir en un momento determinado entre mltiples operaciones disponibles de naturaleza muy variada (por ejemplo, introduccin de datos, seleccin de opciones de men, cambios de formularios activos, cambios de aplicacin, etc.) y, por tanto, mltiples flujos de instrucciones, siendo cada uno de ellos respuesta a eventos diferenciados.
Los programas que utilizan dichas interfaces son un claro ejemplo del paradigma de programacin dirigido por eventos.
Con el tiempo, las interfaces grficas han ido evolucionando y han ido surgiendo nuevos componentes (botones, listas desplegables, botones de opciones, etc.) que se adaptan mejor a la comunicacin entre los usuarios y los ordenadores. La interaccin con estado y cada cambio de estado es un suceso susceptible de necesitar o provocar una accin determinada. Es decir, un posible evento. cada uno de estos componentes genera una serie de cambios de
La programacin de las aplicaciones con interfaces grficas se elabora a partir de una serie de componentes grficos (desde formularios hasta controles, como los botones o las etiquetas), que se definen como objetos propios, con sus variables y sus mtodos.
353
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Mientras que las variables corresponden a las diferentes propiedades necesarias para la descripcin del objeto (longitudes, colores, bloqueos, etc. ), los mtodos permiten la codificacin de una respuesta a cada uno de los diferentes eventos que pueden sucederle a dicho componente.
ANOTACIONES
FUOC XP04/90793/00018
ANOTACIONES
FUOC XP04/90793/00018
Software libre
ANOTACIONES
FUOC XP04/90793/00018
As pues, la programacin visual resultara ser una tcnica para describir programas cuyos flujos de ejecucin se adapten a los paradigmas anteriormente citados. Por tanto, a pesar de la posible confusin aportada por los nombres de varios entornos de programacin como la familia Visual de Microsoft (Visual C++, Visual Basic, etc.), a estos lenguajes se les debe continuar clasificando como lenguajes textuales, aunque su entorno grfico de desarrollo s que puede suponer una aproximacin hacia la programacin visual.
5.13. Resumen
En esta unidad se ha presentado un nuevo lenguaje de programacin orientado a objetos que nos proporciona independencia de la plataforma sobre la que se ejecuta. Para ello, proporciona una mquina virtual sobre cada plataforma. De este modo, el desarrollador de aplicaciones slo debe escribir su cdigo fuente una nica vez y compilarlo para generar un cdigo ejecutable comn, consiguiendo, de esta manera, que la aplicacin pueda funcionar en entornos dispares como sistemas Unix, sistemas Pc o Apple McIntosh. Esta filosofa es la que se conoce como write once, run everywhere. Java naci como evolucin del C++ y adaptndose a las condiciones anteriormente descritas. Se aprovecha el conocimiento previo de los programadores en los lenguajes C y C++ para facilitar una aproximacin rpida al lenguaje. Al necesitar Java un entorno de poco tamao, permite incorporar su uso en navegadores web. Como el uso de estos navegadores implica, normalmente, la existencia de un entorno grfico, se ha aprovechado esta situacin para introducir brevemente el uso de bibliotecas grficas y el modelo de programacin dirigido por eventos. Asimismo, Java incluye de forma estndar dentro de su lenguaje operaciones avanzadas que en otros lenguajes realiza el sistema operativo o bibliotecas adicionales. Una de estas caractersticas es la programacin de varios hilos de ejecucin (threads) dentro del mismo proceso. En esta unidad, hemos podido introducirnos en el tema.
357
ANOTACIONES
FUOC XP04/90793/00018
Software libre
3. Implementad una aplicacin que pueda diferenciar si una figura de cuatro vrtices es un cuadrado, un rectngulo, un rombo u otro tipo de polgono. Se definen los casos de la siguiente forma: Cuadrado: lados 1,2,3 y 4 iguales; 2 diagonales iguales Rectngulo: lados 1 y 3, 2 y 4 iguales; 2 diagonales iguales Rombo: lados 1,2,3 y 4 iguales, diagonales diferentes Polgono: los dems casos Para ello, se definen la clase Punto2D definiendo las coordenadas x, y, y el mtodo distancia a otro punto.
Ejemplo
ANOTACIONES
358
(0,0) (1,0) (1,1) (0,1) Cuadrado (0,1) (1,0) (2,1) (1,2) Cuadrado (0,0) (2,0) (2,1) (0,1) Rectngulo (0,2) (1,0) (2,2) (1,4) Rombo
FUOC XP04/90793/00018
5.14.1. Solucionario
1. Leer.java import java.io.*; public class Leer { public static String getString() { String str = ""; try { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); str = br.readLine(); } catch(IOException e) { System.err.println("Error: " + e.getMessage()); } return str; // devolver el dato tecleado } public static int getInt() { try { return Integer.parseInt(getString()); } catch(NumberFormatException e) { return 0; // Integer.MIN_VALUE } } // getInt public static double getDouble() { try { return Double.parseDouble(getString()); } catch(NumberFormatException e) { return 0; // Double.MIN_VALUE } } // getDouble } // Leer
359
ANOTACIONES
FUOC XP04/90793/00018
Software libre
2. construirFecha.java import java.io.*; public class construirFecha { static String nombreMes(int nmes) { String strmes; switch (nmes) { case 1: { strmes = "enero"; break; } case 2: { strmes = "febrero"; break; } case 3: { strmes = "marzo"; break; } case 4: { strmes = "abril"; break; } case 5: { strmes = "mayo"; break; } case 6: { strmes = "junio"; break; } case 7: { strmes = "julio"; break; } case 8: { strmes = "agosto"; break; } case 9: { strmes = "septiembre"; break; } case 10: { strmes = "octubre"; break; } case 11: { strmes = "noviembre"; break; } case 12: { strmes = "diciembre"; break; } default: { strmes = " -- "; break; } } // switch nmes return (strmes); } // nombreMes public static void main(String args[]) { String poblacion; int dia, mes, ao; String mifecha, strmes; System.out.print(" Poblacin: "); poblacion = Leer.getString(); System.out.print(" Dia: "); dia = Leer.getInt(); System.out.print(" Mes: "); mes = Leer.getInt(); System.out.print(" Ao: "); ao = Leer.getInt(); mifecha = poblacion + ", " + dia; mifecha = mifecha +" de "+ nombreMes(mes) +" de "+ ao; System.out.print(" La fecha introducida es: "); System.out.println(mifecha); } // main } // class
360
ANOTACIONES
FUOC XP04/90793/00018
3. Punto2D.java class Punto2D { public int x, y; // inicializando al origen de coordenadas Punto2D() { x = 0; y = 0; } // inicializando a una coordenada x,y determinada Punto2D(int coordx, int coordy) { x = coordx; y = coordy; }
// calcula la distancia a otro punto double distancia(Punto2D miPunto) { int dx = x - miPunto.x; int dy = y - miPunto.y; return ( Math.sqrt(dx * dx + dy * dy)); } } AppReconocerFigura.java class AppReconocerFigura { static public void main(String args[]) { int i; int coordx, coordy; // Introducir 4 puntos e // indicar cul es el ms cercano al origen.
// entrar datos for (i=0; i<4; i++) { System.out.println("Entrar el punto (" + i + ")" ); System.out.print("Coordenada x " ); coordx = Leer.getInt(); System.out.print("Coordenada y " ); coordy = Leer.getInt();
361
ANOTACIONES
FUOC XP04/90793/00018
Software libre
listaPuntos[i] = new Punto2D(coordx, coordy); } //for // // // // // // // // indicar si los 4 puntos forman un cuadrado: dist1 = dist2 = dist3 = dist4 diag1 = diag2 rombo: dist1 = dist2 = dist3 = dist4 diag1 <> diag2 rectangulo: dist1 = dist3, dist2 = dist4 diag1 = diag2 poligono: otros casos
double dist[] = new double[4]; double diag[] = new double[3]; // calculo de distancias for (i=0; i<3; i++) { dist[i] = listaPuntos[i].distancia(listaPuntos[i+1]); System.out.print("Distancia "+i + " " + dist[i] ); } //for dist[3] = listaPuntos[3].distancia(listaPuntos[0]); System.out.println("Distancia "+i + " " + dist[3] ); // calculo de diagonales for (i=0; i<2; i++) { diag[i] = listaPuntos[i].distancia(listaPuntos[i+2]); } //for if ( (dist[0] == dist[2]) && (dist[1] == dist[3]) ) { // es cuadrado, rectngulo o rombo if (dist[1] == dist[2]) { // es cuadrado o rombo if (diag[0] == diag[1]) { System.out.println("Es un cuadrado"); } else { System.out.println("Es un rombo"); } // if } else { // es rectangulo if (diag[0] == diag[1]) { System.out.println("Es un rectngulo"); } } else { System.out.println("Es un polgono"); } // if } } else { System.out.println("Es un poligono"); } // if } // main } // class
362
ANOTACIONES
FUOC XP04/90793/00018
4. AppAscensor.java import java.io.*; public class AppAscensor{ static int n_codigo, n_peso, n_idioma; public static void solicitarDatos() { System.out.print ("Codigo: "); n_codigo = Leer.getInt(); System.out.print("Peso: "); n_peso = Leer.getInt(); System.out.print( "Idioma: [1] Cataln [2] Castellano [3] Ingls "); n_idioma = Leer.getInt(); } // solicitarDatos public static void mostrarEstadoAscensor(Ascensor nA) { nA.mostrarOcupacion(); System.out.print(" - "); nA.mostrarCarga(); System.out.println(" "); nA.mostrarListaPasajeros(); } // mostrarEstadoAscensor public static void main( String[] args) { int opc; boolean salir = false; Ascensor unAscensor; Persona unaPersona; Persona localizarPersona; opc=0; unAscensor = new Ascensor();// inicializamos ascensor unaPersona = null;// inicializamos unaPersona do { System.out.print( "ASCENSOR: [1]Entrar [2]Salir [3]Estado [0]Finalizar "); opc = Leer.getInt(); switch (opc) { case 1: { // Opcion Entrar solicitarDatos(); switch (n_idioma)
363
ANOTACIONES
FUOC XP04/90793/00018
Software libre
case 1: { //"Catalan" unaPersona = new Catalan (n_codigo, n_peso); break; } case 2: { //"Castellano" unaPersona = new Castellano (n_codigo, n_peso); break; } case 3: { //"Ingles" unaPersona = new Ingles(n_codigo, n_peso); break; } default: { //"Ingles" unaPersona = new Ingles(n_codigo, n_peso); break; } } //switch n_idioma if (unAscensor.persona_PuedeEntrar(unaPersona)) { unAscensor.persona_Entrar(unaPersona); if (unAscensor.obtenerOcupacion()>1) { System.out.print(unaPersona.obtenerCodigo()); System.out.print(" dice: "); unaPersona.saludar(); System.out.println(" "); // Responden las demas unAscensor.restoAscensor_Saludar(unaPersona); } } //puede entrar break;
localizarPersona = new Persona(); //Por ejemplo unaPersona = null; localizarPersona.solicitarCodigo(); if (unAscensor.persona_Seleccionar(localizarPersona)) { unaPersona = unAscensor.obtenerRefPersona(); unAscensor.persona_Salir( unaPersona ); if (unAscensor.obtenerOcupacion()>0) { System.out.print(unaPersona.obtenerCodigo()); System.out.print(" dice: "); unaPersona.despedirse(); System.out.println(" "); // Responden las demas unAscensor.restoAscensor_Despedirse(unaPersona); unaPersona=null; } } else { System.out.println( "No hay ninguna persona con este cdigo"); } // seleccionar
364
ANOTACIONES
FUOC XP04/90793/00018
localizarPersona=null; break; } case 3: { //Estado mostrarEstado(unAscensor); break; } case 0: { //Finalizar System.out.println("Finalizar"); salir = true; break; } } //switch opc } while (! salir); } // main } //AppAscensor Ascensor.java import java.io.*; class Ascensor { private int ocupacion; private int carga; private int ocupacionMaxima; private int cargaMaxima; private Persona pasajeros[]; private Persona refPersonaSeleccionada; // // Constructores y destructores // { ocupacion = 0; carga=0; ocupacionMaxima=6; cargaMaxima=500; pasajeros = new Persona[6]; refPersonaSeleccionada = null; } //Ascensor()
365
ANOTACIONES
Ascensor()
FUOC XP04/90793/00018
Software libre
// Funciones de acceso int ObtenerOcupacion() { return (ocupacion); } void ModificarOcupacion(int dif_ocupacion) { ocupacion += dif_ocupacion; } void MostrarOcupacion() { System.out.print("Ocupacion actual: "); System.out.print(ocupacion ); } int obtenerCarga() { return (carga); } void modificarCarga(int dif_carga) { carga += dif_carga; } void mostrarCarga() { System.out.print("Carga actual: "); System.out.print(carga) ; } Persona obtenerRefPersona() {return (refPersonaSeleccionada);} boolean persona_PuedeEntrar(Persona unaPersona) { // si la ocupacin no sobrepasa el lmite de ocupacin y // si la carga no sobrepasa el lmite de carga // ->puede entrar boolean tmpPuedeEntrar; if (ocupacion + 1 > ocupacionMaxima) { System.out.println( "Aviso: El ascensor est completo. No puede entrar"); return (false); } if (unaPersona.obtenerPeso() + carga > cargaMaxima ) { System.out.println( "Aviso: El ascensor supera su carga mxima. No puede entrar");
366
ANOTACIONES
FUOC XP04/90793/00018
return (false); } return (true); } boolean persona_Seleccionar(Persona localizarPersona) { int contador; // Se selecciona persona entre pasajeros del ascensor. boolean personaEncontrada = false; if (obtenerOcupacion() > 0) { contador=0; do { if (pasajeros[contador] != null) { if(pasajeros[contador].igualCodigo(localizarPersona) { refPersonaSeleccionada=pasajeros[contador]; personaEncontrada=true; break; } } contador++; } while (contador<ocupacionMaxima); if (contador>=ocupacionMaxima) {refPersonaSeleccionada=null;} } return (personaEncontrada); } void persona_Entrar(Persona unaPersona) { int contador; modificarOcupacion(1); modificarCarga(unaPersona.obtenerPeso()); System.out.print(unaPersona.obtenerCodigo()); System.out.println(" entra en el ascensor "); contador=0; // hemos verificado anteriormente que hay plazas libres do { if (pasajeros[contador]==null )
367
ANOTACIONES
FUOC XP04/90793/00018
Software libre
{ pasajeros[contador]=unaPersona; break; } contador++; } while (contador<ocupacionMaxima); } void persona_Salir(Persona unaPersona) { int contador; contador=0; do { if ((pasajeros[contador]==unaPersona )) { System.out.print(unaPersona.obtenerCodigo()); System.out.println(" sale del ascensor "); pasajeros[contador]=null; // Modificamos la ocupacion y la carga modificarOcupacion(-1); modificarCarga(-1 * (unaPersona.obtenerPeso())); break; } contador++; } while (contador<ocupacionMaxima); if (contador==ocupacionMaxima) {System.out.println( " No hay persona con este cdigo. No sale nadie ");} } void mostrarListaPasajeros() { int contador; Persona unaPersona; if (obtenerOcupacion() > 0) { System.out.println("Lista de pasajeros del ascensor:"); contador=0; do { if (!(pasajeros[contador]==null )) {
368
ANOTACIONES
FUOC XP04/90793/00018
unaPersona=pasajeros[contador]; System.out.print(unaPersona.obtenerCodigo()); System.out.print("; "); } contador++; } while (contador<ocupacionMaxima); System.out.println(""); } else { System.out.println("El ascensor esta vaco"); } } void restoAscensor_Saludar(Persona unaPersona) { int contador; Persona otraPersona;
if (obtenerOcupacion() > 0) { contador=0; do { if (!(pasajeros[contador]==null )) { otraPersona=pasajeros[contador]; if (!unaPersona.igualCodigo(otraPersona) ) { System.out.print(otraPersona.obtenerCodigo()); System.out.print(" responde: "); otraPersona.saludar(); System.out.println(""); } } contador++; } while (contador<ocupacionMaxima); } } void restoAscensor_Despedirse(Persona unaPersona) { int contador; Persona otraPersona; if (obtenerOcupacion() > 0)
369
ANOTACIONES
FUOC XP04/90793/00018
Software libre
{ contador=0; do { if (!(pasajeros[contador]==null )) { otraPersona=pasajeros[contador]; if (!(unaPersona.igualCodigo(otraPersona)) { System.out.print(otraPersona.obtenerCodigo()); System.out.print(" responde: "); otraPersona.despedirse(); System.out.print(" "); } } contador++; } while (contador<ocupacionMaxima); } } } // class Ascensor Persona.java import java.io.*; class Persona { private int codigo; private int peso; Persona() { } Persona(int n_codigo, int n_peso) { codigo = n_codigo; peso = n_peso; } public int obtenerPeso() { return (peso); } public void asignarPeso(int n_peso) { peso = n_peso; } public int obtenerCodigo()
370
ANOTACIONES
FUOC XP04/90793/00018
{ return (codigo); } public void asignarCodigo(int n_codigo) { this.codigo = n_codigo; } public void asignarPersona(int n_codigo, int n_peso) { asignarCodigo( n_codigo ); asignarPeso( n_peso ); } void saludar() {}; void despedirse() {}; public void solicitarCodigo() { int n_codigo=0; System.out.print ("Codigo: "); n_codigo = Leer.getInt(); asignarCodigo (n_codigo); } public boolean igualCodigo(Persona otraPersona) { return (this.obtenerCodigo()==otraPersona.obtenerCodigo()); } } //class Persona Catalan.java class Catalan extends Persona { Catalan() { Persona(0, 0); }; Catalan(int n_codigo, int n_peso) { Persona (n_codigo, n_peso); }; void saludar() { System.out.println("Bon dia"); }; void despedirse() { System.out.println("Adu"); }; }
371
ANOTACIONES
FUOC XP04/90793/00018
Software libre
Castellano.java class Castellano extends Persona { Castellano() { Persona(0, 0); }; Castellano(int n_codigo, int n_peso) { Persona (n_codigo, n_peso); }; void saludar() { System.out.println("Buenos das"); }; void despedirse() { System.out.println("Adis"); }; } Ingles.java class Ingles extends Persona { Ingles () { Persona(0, 0); }; Ingles (int n_codigo, int n_peso) { Persona (n_codigo, n_peso); }; void saludar() { System.out.println("Hello"); }; void despedirse() { System.out.println("Bye"); }; }
ANOTACIONES
372
FUOC XP04/90793/00018
Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
383
ANOTACIONES
FUOC XP04/90793/00018
Software libre
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. Sections then there are none. The Document may contain zero
ANOTACIONES
384
The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
FUOC XP04/90793/00018
A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standardconforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.
385
ANOTACIONES
FUOC XP04/90793/00018
Software libre
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies.
3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
386
ANOTACIONES
FUOC XP04/90793/00018
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machinereadable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
387
ANOTACIONES
FUOC XP04/90793/00018
Software libre
B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice.
H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
388
ANOTACIONES
FUOC XP04/90793/00018
K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
389
ANOTACIONES
FUOC XP04/90793/00018
Software libre
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements".
ANOTACIONES
390
6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
FUOC XP04/90793/00018
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the
391
ANOTACIONES
FUOC XP04/90793/00018
Software libre
translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the See http://
ANOTACIONES
392
Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
FUOC XP04/90793/00018
Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the FrontCover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
393
ANOTACIONES