Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
AULA POLITÈCNICA / ETSETB EDICIONS UPC Marco A. Peña Basurto José M. Cela Espín Introducción a la programación en C Primera edición: septiembre de 2000 Diseño de la cubierta: Manuel Andreu © Los autores, 2000 © Edicions UPC, 2000 Edicions de la Universitat Politècnica de Catalunya, SL Jordi Girona Salgado 31, 08034 Barcelona Tel.: 934 016 883 Fax: 934 015 885 Edicions Virtuals: www.edicionsupc.es E-mail: edicions-upc@upc.es Producción: CPET (Centre de Publicacions del Campus Nord) La Cup. Gran Capità s/n, 08034 Barcelona Depósito legal: B-32.449-2000 ISBN: 84-8301-429-7 Quedan rigurosamente prohibidas, sin la autorización escrita de los titulares del copyright, bajo las san- ciones establecidas en las leyes, la reproducción total o parcial de esta obra por cualquier medio o pro- cedimiento, comprendidos la reprografía y el tratamiento informático, y la distribución de ejemplares de ella mediante alquiler o préstamo públicos. Introducción a la programación en C Marco A. Peña José M. Cela Departament d’Arquitectura de Computadors Universitat Politècnica de Catalunya 08034 Barcelona, España marcoa@ac.upc.es cela@ac.upc.es 19 de junio de 2000 i Índice General Índice General Índice de Figuras v Índice de Tablas vii Prefacio ix 1 Conceptos básicos de programación 1 1.1 Ordenador y periféricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Bits, bytes y palabras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3 Lenguajes de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3.1 Lenguajes de bajo nivel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3.2 Lenguajes de alto nivel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.4 Elaboración de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.5 Traductores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.5.1 Ensambladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.5.2 Intérpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.5.3 Compiladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2 Primer contacto con C 7 2.1 Un poco de historia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Caracterı́sticas del lenguaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.3 Creación de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.4 Primeros pasos con C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.5 El modelo de compilación de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 3 Empezando a programar 13 3.1 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.2 Estructura de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.3 Variables y constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.3.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.3.2 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3.3.3 Entrada y salida de valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 3.4 Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4.1 Operador de asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.4.2 Operadores aritméticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Índice General ii 3.4.3 Operadores relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.4.4 Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4.5 Prioridad de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4 Construcciones condicionales 23 4.1 Construcción if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.1.1 Variante if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 4.1.2 Variante if-else-if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.2 El operador condicional ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.3 Construcción switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 4.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5 Construcciones iterativas 33 5.1 Construcción while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 5.2 Construcción do-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 5.3 Construcción for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 5.3.1 El operador coma (,) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.3.2 Equivalencia for-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.4 Las sentencias break y continue . . . . . . . . . . . . . . . . . . . . . . . . . . 38 5.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 6 Tipos de datos elementales 41 6.1 Números enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 6.1.1 Modificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 6.1.2 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 6.2 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 6.2.1 Caracteres especiales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 6.2.2 Enteros y el tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 6.2.3 Conversiones de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 6.3 Números reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 6.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 7 Tipos de datos estructurados: Tablas 49 7.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 7.1.1 Consulta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 7.1.2 Asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 7.1.3 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 7.2 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 7.2.1 Consulta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 7.2.2 Asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 7.2.3 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 7.3 Tablas multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 7.3.1 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 7.4 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 iii Índice General 7.4.1 Asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 7.4.2 Manejo de cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . 59 7.4.3 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 7.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 8 Otros tipos de datos 63 8.1 Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 8.1.1 Declaración de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 8.1.2 Acceso a los campos . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . 65 8.1.3 Asignación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 8.1.4 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 8.2 Uniones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 8.2.1 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 8.3 Tipos de datos enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 8.4 Definición de nuevos tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 8.5 Tiras de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 8.5.1 Operador de negación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 8.5.2 Operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 8.5.3 Operadores de desplazamiento de bits . . . . . . . . . . . . . . . . . . . . . . 71 8.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 9 Punteros 75 9.1 Declaración y asignación de direcciones . . . . . . . . . . . . . . . . . . . . . . . . . 75 9.1.1 Declaración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 9.1.2 Asignación de direcciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 9.2 Indirección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 9.3 Operaciones con punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 9.4 Punteros y tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 9.5 Punteros y estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 9.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 10 Funciones 87 10.1 Generalidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 10.2 Definición y llamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 10.2.1 Definición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 10.2.2 Prototipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 10.2.3 Llamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 10.3 Variables y parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 10.3.1 Variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 10.3.2 Variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 10.3.3 Parámetros formales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 10.4 Devolución de resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 10.5 Paso de parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 10.5.1 Paso de parámetros por valor . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 10.5.2 Paso de parámetros por referencia . . . . . . . . . . . . . . . . . . . . . . . . 95 Índice General iv 10.5.3 Las tablas y las funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 10.5.4 Parámetros en la función main . . . . . . . . . . . . . . . . . . . . . . . . . 99 10.6 Recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 10.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 11 Ficheros 105 11.1 Abrir y cerrar ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 11.2 Leer y escribir en ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 11.3 Otras funciones para el manejo de ficheros . . . . . . . . . . . . . . . . . . . . . . . . 111 11.3.1 feof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 11.3.2 ferror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 11.3.3 fflush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 11.4 Ficheros estándar: stdin, stdout, stderr . . . . . . . . . . . . . . . . . . . 113 11.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 A El preprocesador 119 A.1 Directiva include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 A.2 Directivas define y undef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 A.3 Directivas ifdef y ifndef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 A.4 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 B La librerı́a estándar 123 B.1 Manipulación de cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . 123 B.2 Entrada y salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 B.2.1 Entrada y salida básica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 B.2.2 Entrada y salida con formato . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 B.2.3 Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 B.3 Funciones matemáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 B.4 Clasificación y manipulación de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 128 B.5 Conversión de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 B.6 Manipulación de directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 B.7 Memoria dinámica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 C Sistemas de numeración 135 C.1 Naturales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 C.2 Enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 C.3 Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 C.3.1 Problemas derivados de la representación en coma flotante . . . . . . . . . . . 138 D Tabla de caracteres ASCII 141 E Bibliografı́a y recursos WEB 143 v Índice de Figuras Índice de Figuras 1.1 Niveles de abstracción en los lenguajes de programación . . . . . . . . . . . . . . . . 2 1.2 Cronologı́a en el desarrollo de algunos lenguajes de programación . . . . . . . . . . . 3 1.3 Ciclo de vida de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.4 Fases en la interpretación de un programa . . . . . . . . . . . . . . . . . . . . . . . . 5 1.5 Fases en la compilación de un programa . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.1 Modelo de compilación de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 4.1 Esquema de funcionamiento de if y de if-else . . . . . . . . . . . . . . . . . . . 24 4.2 Esquema de funcionamiento de switch . . . . . . . . . . . . . . . . . . . . . . . . . 29 5.1 Esquema de funcionamiento de while . . . . . . . . . . . . . . . . . . . . . . . . . 34 5.2 Esquema de funcionamiento de do-while . . . . . . . . . . . . . . . . . . . . . . . 35 5.3 Esquema de funcionamiento de for . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7.1 Representación gráfica de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 7.2 Representación gráfica de una matriz . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 7.3 Representación gráfica de una tabla de tres dimensiones . . . . . . . . . . . . . . . . . 56 9.1 Acceso a una matriz mediante un puntero . . . . . . . . . . . . . . . . . . . . . . . . 82 10.1 Acceso a una matriz mediante un puntero . . . . . . . . . . . . . . . . . . . . . . . . 99 11.1 Almacenamiento de un fichero de texto . . . . . . . . . . . . . . . . . . . . . . . . . 106 vii Índice de Tablas Índice de Tablas 3.1 Palabras reservadas de C . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . 13 3.2 Operadores aritméticos en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.3 Operadores relacionales y lógicos en C . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4 Tabla de verdad de los operadores lógicos en C . . . . . . . . . . . . . . . . . . . . . 19 3.5 Prioridad y asociatividad de los operadores en C . . . . . . . . . . . . . . . . . . . . . 20 6.1 Representación de enteros en decimal, octal y hexadecimal . . . . . . . . . . . . . . . 42 6.2 Resumen de tipos de datos enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 6.3 Caracteres interpretados como enteros . . . . . . . . . . . . . . . . . . . . . . . . . . 45 6.4 Resumen de tipos de datos reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 8.1 Tabla de verdad de los operadores lógicos . . . . . . . . . . . . . . . . . . . . . . . . 71 C.1 Representación de números naturales en binario natural . . . . . . . . . . . . . . . . 135 C.2 Representación de números enteros en complemento a 2 . . . . . . . . . . . . . . . . 136 C.3 Representación de números enteros en exceso 2e�1 . . . . . . . . . . . . . . . . . . . 137 C.4 Representación de números reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 D.1 Caracteres y sus códigos ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 ix Prólogo Prólogo Este libro surge a partir de la experiencia docente de los autores en la asignatura Introducción a los ordenadores de la Escola Tècnica Superior d’Enginyeria de Telecomunicació de Barcelona, de la Uni- versitat Politècnica de Catalunya. Como su t́ıtulo indica, se trata de un texto de introdución a la pro- gramación en lenguaje C. El libro pretende ceñirse a los aspectos fundamentales del estándar ANSI C actual. Aunque hoy en dı́a existen otros lenguajes de programación muy populares como C++ o JAVA, la comprensión de estos lenguajes exige un sólido conocimiento de las bases de programación en C. El texto está concebido como un curso completo y por lo tanto debe ser leı́do de forma secuencial. Al final de cada capı́tulo hay un conjunto de ejercicios propuestos. El lector debe tratar de resolver el mayor número posible de estos ejercicos. De igual forma es una buena práctica el programar los ejem- plos resueltos en el texto. El lector debe recordar que la programación es una técnica aplicada, igual que tocar un instrumento musical, y por lo tanto requiere muchas horas de ensayo para ser dominada. Los ejercicios propuestos son lo suficientemente simples como para no requerir conocimientos adicionales de otras materias (matemáticas, fı́sica, contabilidad, etc). Uno de los puntos más importantes para quien empieza a programar es adoptar desde el principio un buen estilo de programación. Esto es, escribir las construcciones del lenguaje de una forma clara y consistente. En este sentido, los ejemplos resueltos en el texto muestran un buen estilo básico de programación, por lo que se recomienda al lector imitar dicho estilo cuando realice sus programas. Los apéndices A y B son de lectura obligada antes de comenzar a desarrollar programas de comple- jidad media. En dichos apéndices el lector se familiarizará con el uso del preprocesador y de la librerı́a estándar. Ambas son herramientas fundamentales para desarrollar programas. El apéndice C incluye una descripción de los formatos de datos en el computador. Este tema no es propiamente de programción, pero la comprensión de dichos formatos ayuda al programador a en- tender mejor conceptos básicos, como las operaciones de conversión de tipos. El lector puede leer este apéndice en cualquier momento, aunque recomendamos leerlo antes del capı́tulo 6. En las referencias bibliográficas se indican algunas direcciones web donde el lector podrá encontrar preguntas y respuestas comunes de quienes se inician en el lenguaje C. Aśı mismo, el lector podrá encontrar materiales adicionales sobre programación, historia del lenguaje, etc. 1 1. Conceptos básicos de programación Capı́tulo 1 Conceptos básicos de programación 1.1 Ordenador y periféricos Un ordenador sólo es capaz de ejecutar órdenes y operaciones muy básicas, tales como: � Aritmética entera: sumar, restar, multiplicar, etc. � Comparar valores numéricos o alfanuméricos � Almacenar o recuperar información Con la combinación de estas operaciones básicas, y gracias a su gran potencia de cálculo, el orde- nador puede llevar a cabo procesos muy complejos. Sin embargo, en cualquier caso existe una estrecha dependencia del ordenador con el programador. Es el programador quien indica a la máquina cómo y qué debe hacer, mediante la lógica y el razonamiento previo, expresado en forma de un programa. En definitiva, el ordenador sólo es capaz de aceptar datos de entrada, procesarlos y facilitar otros datos o resultados de salida. Los datos se introducen u obtienen del ordenador mediante los periféricos de entrada y salida. Éstos son los encargados de facilitar la relación entre el corazón del ordenador y el mundo exterior, y en particular los usuarios de ordenadores. Dependiendo de su función particular, los periféricos pueden clasificarse en: Periféricos de entrada: cuya función es facilitar la introducción de datos y órdenes al ordenador: te- clado, ratón, lápiz óptico, lector de código de barras, escáner, tableta digitalizadora, etc. Periféricos de salida: cuya función es mostrar al exterior información almacenada en memoria o los resultados de las operaciones realizadas por el ordenador: pantalla, impresora, plotter, etc. Periféricos de entrada y salida: capaces tanto de introducir como de extraer información del ordena- dor: discos y cintas magnéticos, discos ópticos, etc. Periféricos de comunicación: encargados de establecer y facilitar el intercambio de información entre dos ordenadores: módem, tarjetas de red (Ethernet, Token Ring, RDSI, . . . ), etc. 1.2. Bits, bytes y palabras 2 Lenguaje máquina Lenguaje natural Programador Ordenador Lenguaje de programación Figura 1.1: Niveles de abstracción en los lenguajes de programación 1.2 Bits, bytes y palabras La unidad de memoria más pequeña en un ordenador se denomina bit (del inglés binary digit). Puede tomar únicamente dos posibles valores: 0 o 1. En ocasiones, debido a la relación intrı́nseca con los valores en las señales eléctricas en circuitos digitales, se dice que un bit está bajo o alto, o bien desco- nectado o conectado. Como puede verse, no es posible almacenar mucha información en un solo bit. Sin embargo, un ordenador posee cantidades ingentes de ellos, por lo que podŕıa decirse que los bits son los bloques básicos con los que se construye la memoria del ordenador. El byte, compuesto por ocho bits (algunos autores se refieren a esta unidad como octeto), es una unidad de memoria más útil. Puesto que cada bit puede tomar el valor 0 o 1, en un byte pueden represen- tarse hasta 28 = 256 combinaciones de ceros y unos (256 códigos binarios). Con estas combinaciones pueden representarse, por ejemplo, los enteros entre 0 y 255 (0 : : : 28 � 1), un conjunto de caracteres, etc. La unidad natural de memoria para un ordenador es la palabra. Los ordenadores de sobremesa actuales, por ejemplo, suelen trabajar con palabras de 32 o 64 bits. En grandes ordenadores, el tamaño de la palabra puede ser mucho mayor, pero siempre formada por un número de bits, potencia de 2. En cualquier caso, los ordenadores encadenan dos o más palabras de memoria con el fin de poder almacenar datos complejos y, en general, de mayor tamaño. 1.3 Lenguajes de programación Un lenguaje de programación podrı́a definirse como una notación o conjunto de sı́mbolos y caracteres que se combinan entre sı́ siguiendo las reglas de una sintaxis predefinida, con el fin de posibilitar la transmisión de instrucciones a un ordenador. Dichos sı́mbolos y caracteres sontraducidos internamente a un conjunto de señales eléctricas representadas en sistema binario, es decir, sólo dos valores: 0 y 1. Esta traducción es necesaria porque el procesador sólo entiende ese lenguaje, al cual nos referiremos como lenguaje máquina. 3 1. Conceptos básicos de programación 1950 1955 1960 1965 1970 1975 1980 1985 1990 1995 FORTRAN LISP Algol COBOL BASIC PL/I Logo SIMULA Prolog Forth Modula-2 Smalltalk Ada Pascal C JavaC++Ensamblador Figura 1.2: Cronologı́a en el desarrollo de algunos lenguajes de programación 1.3.1 Lenguajes de bajo nivel Se incluyen en esta categorı́a aquellos lenguajes que por sus caracterı́sticas se encuentran más próximos a la arquitectura del ordenador, como el lenguaje máquina y el lenguaje ensamblador. Lenguaje máquina Cualquier problema que deseemos resolver se plantea en primer lugar en nuestro lenguaje natural. Sin embargo, para que la secuencia de pasos que resuelven el problema pueda ser entendida por un ordenador, debe traducirse a un lenguaje muy básico denominado lenguaje máquina. El lenguaje máquina se caracteriza por ser el único que es directamente inteligible por el ordenador, puesto que se basa en la combinación de dos únicos sı́mbolos (0 y 1) denominados bits. Además cada procesador posee su propio lenguaje máquina, por lo que un programa escrito en lenguaje máquina de un procesador X no podrá, en principio, ejecutarse en un procesador Y. Lenguaje ensamblador Constituye una evolución del lenguaje máquina. Se basa en la utilización de mnemotécnicos, esto es, abreviaturas de palabras que indican nombres de instrucciones. Para programar en lenguaje ensambla- dor es necesario conocer en profundidad la estructura y funcionamiento interno del ordenador, ası́ como dominar el uso de diferentes sistemas de numeración, como el binario, hexadecimal, octal, etc. En general, los programas escritos en ensamblador requieren mucho menos espacio de memoria y se ejecutan más rápidamente que si se hubiesen desarrollado en un lenguaje de alto nivel, puesto que están optimizados para una arquitectura especı́fica. Sin embargo, esto último es un inconveniente, pues causa que los programas no sean portables de un ordenador a otro con un procesador distinto. 1.3.2 Lenguajes de alto nivel Se engloban aquı́ todos los lenguajes de programación que por sus caracterı́sticas se asemejan más al lenguaje natural del programador. Algunos de los más conocidos son: FORTRAN, BASIC, Pascal, Modula, C, Ada, Java, etc. (ver Fig. 1.2). La caracterı́stica más importante de estos lenguajes es que son independientes de la arquitectura del ordenador, por lo que un programa escrito en un lenguaje de alto nivel puede ejecutarse sin problemas 1.4. Elaboración de un programa 4 ������������� ������������� ������������� ������������� ������������� ������������� �������������� �������������� �������������� �������������� �������� �������� �������� �������� ������� ������� ������� ������� ����������� ����������� ����������� ����������� Análisis Diseño Codificación Explotación Mantenimiento Figura 1.3: Ciclo de vida de un programa en otros ordenadores con procesadores distintos. Por ello, el programador no necesita conocer a fondo el funcionamiento del ordenador en el que programa, sino que el lenguaje le permite abstraerse de los detalles de bajo nivel. Esta abstracción de la arquitectura de la máquina implica que todo programa escrito en un lenguaje de alto nivel deberá traducirse a lenguaje máquina, de forma que pueda ser entendido y ejecutado por el ordenador. Para ello cada tipo de ordenador deberá disponer de unos programas especiales que realicen dicha traducción (ver Sec. 1.5). 1.4 Elaboración de un programa El desarrollo de un programa para solucionar un determinado problema informáticamente puede resu- mirse en el ya clásico concepto de ciclo de vida. Éste puede desglosarse en los siguientes pasos a seguir secuencialmente: análisis, diseño, codificación, explotación y mantenimiento (ver Fig. 1.3). Análisis En la fase de análisis se estudia cuál es el problema a resolver y se especifican a muy alto nivel los procesos y estructuras de datos necesarios, de acuerdo con las necesidades del cliente. Para realizar un buen análisis será necesario interaccionar con el cliente y conocer a fondo sus necesidades. Antes de proceder al diseño es muy importante haber comprendido correctamente los requerimientos del problema. Diseño Una vez bien definido el problema y las ĺıneas generales para solucionarlo, se requiere una solución adecuada a un conjunto de recursos determinado. Tanto f́ısicos: en qué ordenador va a funcionar la aplicación, de qué tipo de periféricos se dispone . . . , como lógicos: qué sistema operativo se usará, qué herramientas de desarrollo, qué bases de datos . . . Finalmente se diseñará un conjunto de algoritmos que resuelvan los distintos subproblemas en que se haya dividido el desarrollo. Codificación Consiste en la traducción de los algoritmos diseñados previamente, utilizando el lenguaje y entorno de desarrollo escogidos en la fase anterior. Será necesario realizar pruebas que garanticen al máximo la calidad de los programas desarrollados. Entre otras cosas, que estén libres de errores. La documentación generada en esta fase junto con la de las fases anteriores será muy útil en el futuro para las eventuales actuaciones de mantenimiento. 5 1. Conceptos básicos de programación Instrucción 1 Intérprete Ejecución 1 Instrucción 2 Intérprete Ejecución 2 Instrucción 3 Intérprete Ejecución 3 . . . Figura 1.4: Fases en la interpretación de un programa Explotación Los diferentes programas desarrollados en la fase anterior se instalan en el entorno final de trabajo. Si es necesario se instalarán también otras herramientas de utilidad, necesarias para el correcto funciona- miento del sistema. Se debe proporcionar documentación, manuales de usuario, formación, etc. Mantenimiento En esta fase se realizarán correcciones al sistema desarrollado, bien para solventar errores no depura- dos, bien para cambiar o añadir nuevas funcionalidades requeridas por el cliente. Dependiendo de la importancia del caso, será necesario retomar el ciclo de vida a nivel de codificación, diseño o incluso análisis (ver Fig. 1.3). Cuanto mejor se haya documentado el desarrollo en las primeras fases del ciclo de vida, menor será el tiempo necesario para llevar a cabo los distintos tipos de mantenimiento. 1.5 Traductores Como ya se ha comentado, el único lenguaje directamente inteligible por el ordenador es el lenguaje máquina. Por ello, si se programa usando lenguajes de alto nivel será necesario algún programa traduc- tor. Éste, a su vez, será el encargado de comprobar que los programas estén escritos correctamente, de acuerdo con la definición del lenguaje de programación empleado. Pueden distinguirse varios tipos de traductores: 1.5.1 Ensambladores Los programas ensambladores son los encargados de traducir a lenguaje máquina los programas escritos en lenguaje ensamblador. La correspondencia entre ambos lenguajes es muy directa, por lo que los ensambladores suelen ser programas relativamente sencillos. 1.5.2 Intérpretes El objetivo de un intérprete es procesar una a una las instrucciones de un programa escrito en un lenguaje de alto nivel. Para cada instrucción se verifica la sintaxis, se traduce a código máquina y finalmente se ejecuta. Es decir, que la traducción y la ejecución se realizan como una sola operación (ver Fig. 1.4). 1.5. Traductores 6 ERRORES Ejecución Edición Compilación Montaje Fuente Objeto Ejecutable Figura 1.5: Fases en la compilación de un programa La principal desventaja de los intérpretes es su lentitud para ejecutar los programas, pues es nece- sario verificar la sintaxis y realizar la traducción en cada ejecución. 1.5.3 Compiladores La función de un compilador consiste en traducir unprograma fuente escrito en un lenguaje de alto nivel a su equivalente en código máquina (también llamado código objeto). Mientras que un intérprete traduce y ejecuta al mismo tiempo cada una de las instrucciones, un compilador analiza, traduce y posteriormente ejecuta todo el programa en fases completamente separa- das (ver Fig. 1.5). Ası́ pues, una vez se ha compilado un programa, no es necesario volverlo a compilar cada vez. Esto hace que la ejecución de un programa compilado sea mucho más rápida que la de uno interpretado. El proceso de compilación Edición Consiste en escribir el programa fuente usando el lenguaje de programación seleccionado y su grabación en un fichero. Para ello es necesario usar un programa editor, que puede o no formar parte del entorno de desarrollo. Compilación En esta fase se verifica la sintaxis del programa fuente y se traduce el programa a código máquina (objeto). Si se producen errores, el compilador muestra información del tipo de error y dónde se ha producido. Montaje Consistente en la combinación de los diferentes módulos objeto y librerı́as del lenguaje para crear un programa ejecutable. Esta fase se conoce también como linkado. Ejecución En esta fase se invoca al programa de la manera adecuada dependiendo del sistema operativo sobre el que vaya a funcionar. Como nota final, cabe decir que todo lenguaje de programación puede ser tanto interpretado como compilado. Sin embargo, dependiendo del las caracteŕısticas del lenguaje y del uso mayoritario a que esté destinado, es normal asociar a cada lenguaje una forma de traducción particular. Por ejemplo, el lenguaje BASIC es mayoritariamente interpretado, mientras que C es compilado. 7 2. Primer contacto con C Capı́tulo 2 Primer contacto con C 2.1 Un poco de historia El lenguaje de programación C fue desarrollado por Dennis Ritchie en los Laboratorios Bell de la empresa de comunicaciones AT&T, en 1972. C fue creado inicialmente con un propósito muy concreto: el diseño del sistema operativo UNIX. Sin embargo, pronto se reveló como un lenguaje muy potente y flexible, lo que provocó que su uso se extendiese rápidamente, incluso fuera de los Laboratorios Bell. De esta forma, programadores de todo el mundo empezaron a usar el lenguaje C para escribir programas de todo tipo. Durante años, el estándar de facto del lenguaje C fue el definido en el libro El lenguaje de pro- gramación C, escrito por Brian Kernighan y Dennis Ritchie en 1978. Sin embargo, con el tiempo proliferaron distintas versiones de C, que con sutiles diferencias entre ellas, empezaron a causar proble- mas de incompatibilidad a los programadores. Con el fin de cambiar esta situación, el Instituto Nacional de Estándares Americano (más conocido como ANSI) creó un comité en 1983 para establecer una de- finición estándar de C que fuese no ambigua e independiente de la arquitectura interna de cualquier ordenador. Finalmente, en 1989 se estableció el estándar ANSI C. Actualmente, cualquier compilador moderno soporta ANSI C. Sin embargo, probablemente debido a razones comerciales, la opción por defecto en muchos compiladores de C es una versión propia del desarrollador del compilador. Dicha version suele ser ligeramente diferente e incompatible con el estándar ANSI C. El lenguaje C debe su nombre a su predecesor, el lenguaje B desarrollado por Ken Thompson, también en los Laboratorios Bell. 2.2 Caracterı́sticas del lenguaje Actualmente existe gran variedad de lenguajes de programación de alto nivel entre los que elegir, como BASIC, Pascal, C, C++, Java, etc. Todos ellos pueden usarse para resolver la mayoŕıa de proyectos de programación. Sin embargo, existen algunas razones que hacen de C el preferido de muchos programa- dores: � Potencia y flexibilidad. Se ha usado en contextos tan dispares como el desarrollo de sistemas operativos, procesadores de texto, gráficos, bases de datos, compiladores de otros lenguajes, etc. 2.3. Creación de un programa 8 � Popularidad. Existe una gran variedad de compiladores, libreŕıas, herramientas de apoyo a la programación, etc. Es el lenguaje predominante en el entorno UNIX. � Portabilidad. El mismo programa escrito en C puede compilarse y ejecutarse sin prácticamente ningún cambio en diferentes ordenadores. Esto se debe en gran parte al estándar ANSI C. � Sencillez. C utiliza pocas palabras clave, por lo que puede aprenderse fácilmente. � Estructura y modularidad. Los programas en C pueden escribirse agrupando el código en funcio- nes que a su vez se agrupan en distintos módulos. De esta forma, el código puede reutilizarse. De acuerdo con esto, C representa una buena elección como lenguaje de programación. Sin em- bargo, seguro que el lector ha oı́do hablar de los lenguajes C++, Java y de la programación orientada a objetos, además de preguntarse sobre las diferencias entre C y C++. Pues bien, C++ puede verse como un superconjunto de C, lo que significa que casi cualquier aspecto de C es perfectamente válido en C++ (pero no al revés). Java por su parte, al igual que C++, también se basa en la sintaxis de C. Finalmente, diremos que aunque C es considerado como un lenguaje de alto nivel, mantiene muchas caracterı́sticas de los lenguajes de bajo nivel, por lo que podrı́a clasificarse como de nivel bajo-medio. 2.3 Creación de un programa La creación de un programa de ordenador consta generalmente de una serie de pasos claramente dife- renciados. Edición El primer paso consiste en usar un editor de textos y crear un fichero que contenga el código del programa en C. Este código, normalmente llamado código fuente, servirá para dar instrucciones precisas al ordenador. Por ejemplo, la siguiente linea de código fuente en C indica al ordenador que debe mostrar el mensaje entre comillas en la pantalla: printf( "Esto es un mensaje" ); El formato del texto admitido por la mayorı́a de compiladores se basa en el Código Estándar Ameri- cano para el Intercambio de Información (ASCII). La mayor parte de los procesadores de texto utilizan códigos especiales para dar formato a los documentos, por lo que normalmente no pueden ser usados como editores de programas. Hoy en dı́a, la mayorı́a de los entornos de programación incluyen un editor, sin embargo, otros no. En estos casos pueden usarse otros programas genéricos de edición de textos ASCII proporcionados por el sistema. Por ejemplo, en UNIX pueden usarse editores como ed, ex, edit, vi, emacs, o nedit, entre otros. En MS-Windows puede usarse el Bloc de Notas. En MS-DOS puede usarse edit. En OS/2, pueden usarse E y EPM. El fichero fuente de un programa debe grabarse con un nombre. Normalmente, el nombre del fichero debe permitir intuir qué hace el programa. Adicionalmente, los ficheros fuente en C suelen tener la extensión .c para identificarlos fácilmente. 9 2. Primer contacto con C Compilación Puesto que el ordenador es incapaz de entender directamente un lenguaje de alto nivel como C, antes de que un programa pueda ejecutarse en el ordenador debe traducirse a lenguaje máquina. Esta traducción la realiza un programa llamado compilador que, dado un fichero fuente, produce un fichero con las instrucciones de lenguaje máquina correspondientes al programa fuente original. El nuevo fichero recibe el nombre de fichero objeto. El fichero objeto suele tener el mismo nombre que el fichero fuente, pero con la extensión .OBJ (o .o en UNIX). Montaje En el tercer paso, las diferentes partes del código compilado se combinan para crear el programa ejecu- table. Parte del lenguaje C consiste en una librerı́a de funciones precompiladas que contiene código objeto. Las funciones en esta librerı́a realizan operaciones de uso frecuente, como mostrar datos en pantalla o leer datos de un fichero. La función printf del ejemplo anterior es una función de dicha librerı́a. Ası́ pues, el fichero objeto producido al compilar el fichero fuente debe combinarse con el código objeto de la librerı́apara crear el fichero del programa ejecutable. Cabe destacar que en la mayorı́a de compiladores actuales, como los que funcionan en MS-DOS o MS-Windows, compilación y montaje se realizan como si fuesen una sola acción. 2.4 Primeros pasos con C A continuación se muestra el programa más sencillo posible en C: void main() f g Todo programa en C debe tener una y sólo una función main(). Esta función deberá constar de una serie de sentencias (en este caso vacı́a) delimitada por los sı́mbolos f g. Dichas sentencias especifican la secuencia de acciones que el programa deberá llevar a cabo. En C pueden ponerse comentarios en cualquier lugar del programa, utilizando los śımbolos /* */. El compilador de C ignora todo el texto entre el inicio del comentario (/*) y el final del mismo (*/). Añadir comentarios a un programa en C no incrementa el tamaño de los ficheros objeto ni ejecutable, ni tampoco ralentiza la ejecución del programa. Veamos un ejemplo de programa con comentarios: /* Mi primer programa en C */ void main() f /* Otro comentario */ g /* ... y otro comentario */ Sin embargo, no es posible poner un comentario dentro de otro. Por ejemplo seŕıa ilegal: 2.5. El modelo de compilación de C 10 /* Mi primer programa en C */ void main() f /* Otro comentario /* Comentario ilegal */ */ g /* ... y otro comentario */ Pero veamos un programa no tan simple. Por ejemplo, el siguiente programa usa la función printf, predefinida en la librerı́a estándar stdio.h, para mostrar un mensaje en la pantalla. /* Mi primer programa en C */ #include <stdio.h> void main() f printf( "Mi primer mensaje en pantalla nn" ); g 2.5 El modelo de compilación de C A continuación se describe el modelo de compilación de C y los distintos procesos implicados: prepro- cesador, compilador y montador (ver Fig. 2.1). Preprocesador Aunque en el apéndice A se verá en detalle esta parte del proceso de compilación, seguidamente se describen algunos aspectos básicos. El preprocesador toma como entrada el código fuente y es el responsable de eliminar los comen- tarios (ya que en realidad no representan ninguna instrucción) y de interpretar las directivas especiales del preprocesador, denotadas por el sı́mbolo #. Por el momento destacaremos sólamente dos de las directivas más utilizadas: � #include, que incluye un fichero externo dentro del fichero fuente. Se usarán los sı́mbolos < > para indicar que el fichero se encuentra en un directorio del entorno de compilación, dife- rente del directorio de trabajo actual. Por el contrario, se usarán los sı́mbolos " " para indicar fichero locales. Por ejemplo: – #include <math.h> incluye el fichero con las definiciones de las funciones ma- temáticas de la librerı́a estándar. – #include <stdio.h> incluye el fichero con las definiciones de las funciones de entrada y salida de la librerı́a estándar. – #include "funciones.h" incluye el fichero funciones.h del directorio actual. � #define, que define un nombre simbólico. Cuando el preprocesador encuentra un nombre simbólico en el programa lo substituye por el valor que se le haya asociado con la directiva #define. – #define NUM ELEMENTOS 100 define la constante NUM ELEMENTOS con valor 100. – #define PI 3.1416 define la constante PI. 11 2. Primer contacto con C Montador Preprocesador Compilador Código fuente Librerías Código objeto Código ejecutable Figura 2.1: Modelo de compilación de C Compilador El compilador de C recibe el código fuente producido por el preprocesador y lo traduce a código objeto (ficheros con extensión .OBJ en MS-Windows, o extensión .o en UNIX). Montador Si un fichero fuente hace referencia a funciones de una librerı́a (como la librerı́a estándar) o a funciones definidas en otros ficheros fuente, el montador se encarga de: � combinar todos los ficheros objeto correspondientes, � verificar que sólo uno de ellos contenga la función principal main() y � crear el fichero finalmente ejecutable. 2.5. El modelo de compilación de C 12 13 3. Empezando a programar Capı́tulo 3 Empezando a programar 3.1 Identificadores Un identificador en un lenguaje de programación es un nombre utilizado para referir un valor constante, una variable, una estructura de datos compleja, o una función, dentro de un programa. Todo identifica- dor está formado por una secuencia de letras, números y caracteres de subrayado, con la restricción de que siempre debe comenzar por una letra o un subrayado y que no puede contener espacios en blanco. Cada compilador fija un máximo para la longitud de los identificadores, siendo habitual un máximo de 32 caracteres. C diferencia entre mayúsculas y minúsculas, según lo cual C considerará los identificadorescontador, Contador y CONTADOR, por ejemplo, como diferentes. En cualquier caso, nunca pueden utilizarse las palabras reservadas del lenguaje para la construcción de identificadores. De acuerdo con el estándar ANSI, C consta únicamente de 32 palabras reservadas (ver Tab. 3.1). Tabla 3.1: Palabras reservadas de C auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while 3.2 Estructura de un programa Todo programa en C consta básicamente de los siguientes elementos: � Directivas del preprocesador � Definiciones de tipos de datos � Declaraciones de funciones 3.3. Variables y constantes 14 Por el momento se tratará únicamente la declaración de la función main() que corresponde al programa principal. La ejecución de un programa escrito en C siempre comienza por dicha función. Por esta razón, en un programa sólo puede haber una función con dicho nombre. La definición de tipos de datos se deja para el capı́tulo 8. Toda función en C, y en particular la función main(), tiene la siguiente estructura: tipo datos nombre función ( parámetros ) f variables locales; secuencia de sentencias; g No entraremos aquı́ en las particularidades de las funciones como el paso de parámetros y la de- volución de resultados de un tipo de datos determinado (ver Cap. 10). Comentaremos simplemente que tanto la devolución de resultados como los parámetros son opcionales, y que en la mayoŕıa de programas sencillos no se usan en la definición del programa principal. A continuación se muestra, como ejemplo, un programa para evaluar la expresión 3 � 5� 32=4: /* Evaluando una expresión */ #include <stdio.h> void main() f int a, b, c = 5; a = 3 * c; b = 32 / 4; c = a - b; printf( "El valor de la expresión es: %dnn", c ); g El cuerpo del programa principal lo constituyen todas las lı́neas de programa comprendidas entre los sı́mbolos f y g. En cada una de dichas ĺıneas puede haber una o más sentencias. Una sentencia es una orden completa para el ordenador. Toda sentencia debe acabar con un punto y coma (;). 3.3 Variables y constantes Los programas de ordenador utilizan diferentes tipos de datos, por lo que requieren de algún mecanismo para almacenar el conjunto de valores usado. C ofrece dos posibilidades: variables y constantes. Una variable es un objeto donde se guarda un valor, el cual puede ser consultado y modificado durante la ejecución del programa. Por el contrario, una constante tiene un valor fijo que no puede ser modificado. 3.3.1 Variables Toda variable debe declararse antes de ser usada por primera vez en el programa. Las sentencias de declaración de variables indican al compilador que debe reservar cierto espacio en la memoria del or- denador con el fin de almacenar un dato de tipo elemental o estructurado. Por ejemplo, la siguiente 15 3. Empezando a programar declaración de variables indica al compilador que debe reservar espacio en la memoria para tres varia- bles de tipo entero, a las que nos referiremos con los nombres a, b y c: int a, b, c; La declaración consiste en dar un nombre significativo a la variable e indicar el tipo de datos aque corresponden los valores que almacenará. A continuación se muestra la sintaxis más sencilla de una sentencia de declaración para una sola variable. tipo datos nombre variable; Además, en una sola sentencia pueden declararse varias variables de un mismo tipo de datos, sepa- rando los nombres de las variables mediante comas: tipo datos nombre variable1, ..., nombre variableN; Opcionalmente, es posible asignar un valor inicial a las variables en la propia declaración. tipo datos nombre variable = valor inicial; 3.3.2 Constantes C admite dos tipos diferentes de constantes: literales y simbólicas. Constantes literales Todo valor que aparece directamente en el código fuente cada vez que es necesario para una operación constituye una constante literal. En el siguiente ejemplo, los valores 20 y 3 son constantes literales del tipo de datos entero: int cont = 20; cont = cont + 3; Si una constante numérica contiene un punto decimal, el compilador considera dicha constante como un valor real de coma flotante. Este tipo de constantes puede escribirse también utilizando alguna de las notaciones cientı́ficas comúnmente aceptadas (ver Sec. 6.3). Por el contrario, el resto de constantes numéricas son consideradas por el compilador, como valores enteros. Pueden usarse tres formatos alternativos: � Toda constante que comience por un dı́gito distinto de 0 es interpretada como un entero decimal (esto es, en base 10). Se especifican mediante los dı́gitos del 0 al 9 y el signo positivo o negativo. � Si una constante comienza con el dı́gito 0, se interpreta como un entero octal (base 8). Se especifican mediante los dı́gitos del 0 al 7 y el signo positivo o negativo. � Finalmente, las constantes que comienzan por 0x o 0X se interpretan como enteros en base hexadecimal (base 16). Se especifican mediante los dı́gitos del 0 al 9, las letras de la A a la F, y el signo positivo o negativo. Para saber más sobre los distintos sistemas de numeración, ver el apéndice C. 3.3. Variables y constantes 16 Constantes simbólicas Una constante simbólica es una constante representada mediante un nombre (sı́mbolo) en el programa. Al igual que las constantes literales, no pueden cambiar su valor. Sin embargo para usar el valor constante, se utiliza su nombre simbólico, de la misma forma que lo harı́amos con una variable. Una constante simbólica se declara una sola vez, indicando el nombre y el valor que representa. Las constantes simbólicas tienen dos ventajas claras respecto a las literales. Supongamos el siguien- te código para calcular el perı́metro de una circunferencia y el área del cı́rculo que define: perimetro = 2 * 3.14 * radio; area = 3.14 * radio * radio; Si por el contrario se hubiese definido una constante simbólica de nombre PI y valor 3.14, podrı́amos escribir un código mucho más claro: perimetro = 2 * PI * radio; area = PI * radio * radio; Es más, imaginemos ahora que para incrementar la precisión del cálculo se desea usar un valor más preciso de la constante �, como 3.14159. En el primer caso debeŕıa substituirse uno a uno el valor 3.14 en todo el programa. En el segundo caso, bastaŕıa cambiar la definición de la constante PI con el nuevo valor. El método más habitual para definir constantes en C es la directiva del preprocesador #define. Por ejemplo, en el caso anterior podrı́amos haber escrito: #define PI 3.14159 Es decir, el nombre simbólico y a continuación el valor constante que representa. 3.3.3 Entrada y salida de valores Aunque en el apéndice B se verán con más detalle las funciones de la librerı́a estándar printf y scanf, se introducen en este punto con el fin de poder realizar algunos programas sencillos. C utiliza operaciones de entrada y salida con formato. Por ejemplo, la función printf usa como carácter especial de formato el sı́mbolo de porcentaje (%). El carácter que sigue a este sı́mbolo define el formato de un valor (constante, variable o expresión). Por ejemplo, %c para valores de tipo carácter o %d para valores de tipo entero. El siguiente ejemplo muestra por pantalla el contenido de una variable de tipo carácter (ch), y una variable entera (num). char ch; int num; . . . printf( "Esto es un carácter: %cnn", ch ); printf( "Y esto un entero: %dnn", num ); El formato es todo aquello especificado entre las comillas, al que le sigue una lista de variables, constantes o expresiones separadas por comas. Es responsabilidad del programador asegurar la perfecta 17 3. Empezando a programar Tabla 3.2: Operadores aritméticos en C Unarios Signo negativo � Incremento ++ Decremento �� Binarios Suma + Resta � Multiplicación � División = Módulo % correspondencia entre el formato y la lista de valores, tanto en número como en el tipo de los mismos. Finalmente, la secuencia especial nn indica un salto de lı́nea. Por su parte, scanf es una función para la entrada de valores a una estructura de datos, y en particular a una variable. Su formato es similar al de printf. Por ejemplo: char ch; int num; . . . scanf( "%c%d", &ch, &num ); Permite introducir desde el teclado un carácter en la variable ch y seguidamente un valor entero en la variable num. Nótese que en el caso de scanf se antepone el sı́mbolo & a las variables. Por el momento, no debemos olvidar utilizarlo, y tengamos en mente que el uso de & tiene que ver con direcciones de memoria y punteros (ver Cap. 9). 3.4 Expresiones Una expresión es una fórmula matemática cuya evaluación especifica un valor. Los elementos que constituyen una expresión son: constantes, variables y operadores. 3.4.1 Operador de asignación El operador de asignación permite asignar valores a las variables. Su sı́mbolo es un signo igual =. Este operador asigna a la variable que está a la izquierda del operador el valor que está a la derecha. Un ejemplo de expresiones válidas con el operador de asignación son: x = 1; z = 1.35; . 3.4.2 Operadores aritméticos Además de los operadores aritméticos tradicionales, C proporciona algunos operadores adicionales (ver Tab. 3.2). La expresión, x++; equivale a x = x+1;, y x--; equivale a x = x-1;. Aunque en el pasado algunos compiladores generaban código más eficiente si se usaba los operadores ++, -- en lugar de sus expresiones equivalentes, esto ya no es cierto en los compiladores modernos. Los opera- dores ++ y -- pueden usarse tanto de manera postfija (más habitual) como prefija, indicando en cada 3.4. Expresiones 18 caso si el valor de la variable se modifica después o antes de la evaluación de la expresión en la que aparece. Por ejemplo, la siguiente lı́nea de código: x = ((++z) - (w--)) % 100; es equivalente al siguiente grupo de sentencias: z = z + 1; x = (z - w) % 100; w = w - 1; Nótese que en C no existe ningún operador especial para la división entera, de forma que cuando los dos operandos de la división son enteros, el cociente que se obtiene es el correspondiente a la división entera (el cociente no se redondea, sino que se trunca). Si alguno de los operandos es un valor real, el resultado de la división será también real. Por ejemplo, x = 3/2; asigna el valor 1 a la variable x (que debe ser entera), mientras que x = 3.0/2; o x = 3/2.0; asigna el valor 1.5 a la variable x (que debe ser real). Finalmente, el operador de módulo (%) permite obtener el resto de una división entera, por lo que sus operandos deben ser también enteros. Por ejemplo, x = 8 % 5; asigna el valor 3 a la variable entera x. Existe además una manera abreviada de expresar ciertos cálculos en C. Es muy común tener ex- presiones del estilo de i = i + 5; o x = x * (y + 2);. Este tipo de expresiones puede escribirse en C de forma compacta como: expresión1 op = expresión2 que es equivalente a: expresión1 = expresión1 op expresión2 Según esto, la asignación i = i + 5; puede reescribirse como i += 5; y la asignación x = x * (y + 2); como x *= y + 2;. Nótese que esta última expresión no significa en ningún caso x = (x *y) + 2;. Como puede verse, un uso abusivo de los operadores abreviados de C puede hacer dif́ıcil de leer un programa. C permite escribir expresiones muy compactas, pero que pueden generar confusión al ser leı́das. Hay que recordar siempre que la legibilidad (mantenimiento) de un programa es tan importante como su correcto funcionamiento. 3.4.3 Operadores relacionales Los operadores relacionales se utilizan principalmente para elaborar condiciones en las sentencias con- dicionales e iterativas (ver Cap. 4 y Cap. 5). La tabla 3.3 resume los distintos operadores de relación en C. Al relacionar (comparar) dos expre- siones mediante uno de estos operadores se obtiene un resultado lógico, es decir: ‘CIERTO’ o ‘FALSO’. Por ejemplo, la expresión 4 > 8 da como resultado el valor falso, la expresión num == num da como resultado cierto, la expresión 8 <= 4 da como resultado falso, etc. Es interesante destacar que a diferencia de otros lenguajes, C no dispone de un tipo de datos es- pecı́fico para los valores lógicos o booleanos. En su lugar, C representa un resultado ‘FALSO’ como 19 3. Empezando a programar Tabla 3.3: Operadores relacionales (izquierda) y lógicos (derecha) en C Menor que < Mayor que > Menor o igual que <= Mayor o igual que >= Igual que == Distinto que ! = Conjunción ó Y lógico && Disyunción u O lógico jj Negación ó NO lógico ! Tabla 3.4: Tabla de verdad de los operadores lógicos en C A B ! A A && B A jj B Cierto Cierto Falso Cierto Cierto Cierto Falso Falso Falso Cierto Falso Cierto Cierto Falso Cierto Falso Falso Cierto Falso Falso el valor numérico entero cero, y un resultado ‘CIERTO’ como cualquier valor entero diferente de cero. Es muy importante recordar este hecho de ahora en adelante. Un error habitual es confundir el operador relacional de igualdad == con el operador de asignación = . Por ejemplo, la sentencia x = 3 asigna el valor 3 a la variable x, mientras que x == 3 compara el valor de x con la constante 3. 3.4.4 Operadores lógicos Los operadores lógicos (ver Tab. 3.3) se utilizan principalmente en conjunción con los relacionales para elaborar condiciones complejas en las sentencias condicionales e iterativas (ver Cap. 4 y Cap. 5). Es importante no confundir los operadores lógicos && y jj con los operadores de manejo de bits & y j que veremos en la sección 8.5. La tabla 3.4 muestra la tabla de verdad para los operadores lógicos. De acuerdo con dicha tabla, las expresiones 4 && 0, !(4 > 1) y 5 <= 0 dan como resultado 0 (falso), mientras que las expresiones 4 jj 9, (8 == 4*2) && (5 > 2) y 2 && (4 < 9) dan como resultado 1 (cierto). 3.4.5 Prioridad de operadores La tabla 3.5 muestra los operadores vistos anteriormente, ası́ como la prioridad entre ellos. La prioridad desciende al descender en la tabla. También se muestra la asociatividad para operadores con el mismo nivel de prioridad. Por ejemplo, según dicha tabla, la expresión (a < 10 && 2 * b < c) se interpretará como (a < 10) && ((2 * b) < c). 3.5. Ejercicios 20 Tabla 3.5: Prioridad y asociatividad de los operadores en C Operador Sı́mbolo Asociatividad Paréntesis () Izquierda a derecha NO lógico ! Derecha a izquierda Signo negativo � Incremento ++ Decremento �� Multiplicación � Izquierda a derecha División = Módulo % Suma + Izquierda a derecha Resta � Menor que < Izquierda a derecha Menor o igual que <= Mayor que > Mayor o igual que >= Igual que == Izquierda a derecha Distinto que ! = Y lógico && Izquierda a derecha O lógico jj Izquierda a derecha Asignaciones = + = � = Derecha a izquierda � = = = %= 3.5 Ejercicios Escribir un programa para cada uno de los siguientes ejercicios: 1. Pedir la base y la altura de un rectángulo, calcular su área y su perı́metro, y mostrar los resultados por pantalla. 2. Pedir una cantidad de segundos y mostrar por pantalla a cuántas horas, minutos y segundos corresponden. 3. Suponiendo que previamente se ha realizado la declaración int x = 7, y; , calcular el valor de la variable y tras evaluar cada una de las siguientes sentencias de asignación: (a) y = �2 + ��x; (b) y + = 2; (c) y = (y == x); (d) y = y++ � x; 4. Evaluar las siguientes expresiones: (a) 5 = 2 + 20 % 6 (b) 4 � 6 = 2 � 15 = 2 21 3. Empezando a programar (c) 5 � 15 = 2 = (4 � 2) (d) 8 == 16 jj 7 != 4 && 4 < 1 (e) (4 � 3 < 6 jj 3 > 5 � 2) && 3 + 2 < 12 3.5. Ejercicios 22 23 4. Construcciones condicionales Capı́tulo 4 Construcciones condicionales Una de las construcciones importantes que pueden especificarse en un programa es el hecho de realizar diferentes tareas en función de ciertas condiciones. Esto es, ejecutar una parte del código u otra, con- dicionalmente. Para ello será necesario especificar dichas condiciones (ver Sec. 3.4) y disponer de un mecanismo para indicar qué acciones tomar dependiendo de cómo se evalúe una determinada condición en un momento dado de la ejecución del programa. Antes de empezar, un recordatorio. Como ya de comentó en la sección 3.4.3, C no dispone de valores booleanos o lógicos, que podrı́an usarse en la evaluación de condiciones. En su defecto, C “simula” los valores falso y cierto, como el valor numérico cero, y cualquier valor no cero (incluyendo negativos), respectivamente. Ası́ pues, en este capı́tulo veremos las distintas maneras que C ofrece para controlar el flujo de ejecución de un programa de forma condicional, que son: � la construcción if, � el operador condicional ?, y � la construcción switch. 4.1 Construcción if La construcción if es similar a la existente en otros lenguajes de programación, aunque en C po- see ciertas peculiaridades. El formato general de esta construcción para decidir si una determinada sentencia debe ejecutarse o no (alternativa simple) es el siguiente: if (condición) sentencia; La construcción if puede escribirse también de forma más general para controlar la ejecución de un grupo de sentencias, de la siguiente manera: 4.1. Construcción if 24 Condición sentencias Grupo de Cierto (a) Condición Grupo de Grupo de sentencias 1 sentencias 2 FalsoCierto (b) Figura 4.1: Esquema de funcionamiento de if y de if-else if (condición) f sentencia 1; sentencia 2; . . . sentencia N; g El funcionamiento de la construcción if es muy simple. En primer lugar se evalúa la condición, que no es otra cosa que una expresión de tipo entero. A continuación, si la expresión se ha evaluado como cierta, se ejecuta la sentencia o grupo de sentencias. En caso contrario la ejecución del programa continúa por la siguiente sentencia en orden secuencial (ver Fig. 4.1 (a)). El siguiente ejemplo muestra el uso de la construcción if. El programa lee un número entero y lo transforma en el impar inmediatamente mayor, si es que no era ya impar. #include <stdio.h> void main() f int a; scanf("%d", &a); if (a % 2 == 0) /* Comprobar si a es par. */ a = a + 1; printf( "Ahora es impar: %dnn", a ); g Nótese que después de la condición no se escribe ‘;’. Escribir ‘;’ detrás de la condición equivaldrı́a a que la construcción if ejectutase un conjunto vacı́o de sentencias, lo cual no tiene ningún sentido. Nótese, sin embargo, que tal hecho es válido sintácticamente (no produce ningún error de compilación), por lo que deberá tenerse cuidado al escribir esta construcción. Algo similar ocurre con los bucles for y while (ver Cap. 5). 25 4. Construcciones condicionales 4.1.1 Variante if-else Existe otra forma más general, denominada alternativa doble, que ofrece dos alternativas de ejecución, en función de si la condición se evalúa cierta o falsa. if (condición) sentencia 1; else sentencia 2; Y para un grupo de sentencias: if (condición) f grupo de sentencias 1; g else f grupo de sentencias 2; g Ası́ pues, si la condición es cierta se ejecutará la primera sentencia (el primer grupo de sentencias), y si es falsa se ejecutará la segunda sentencia (el segundo grupo). Ver figura 4.1 (b). El siguiente programa muestra el usode esta construcción. El programa calcula el máximo de dos números enteros: #include <stdio.h> void main() f int a, b, max; scanf( "%d %d", &a, &b ); if (a > b) max = a; else max = b; prinf( "El máximo es: %dnn", max ); g Es importante destacar que la sentencia en la construcción else es opcional, es decir, puede ser nula. Veámoslo en el siguiente ejemplo que determina si un número es par: #include <stdio.h> void main() f int x; 4.1. Construcción if 26 scanf( "%d", &x ); if (x % 2 == 0) printf( "Es par.nn" ); else ; g El hecho de que la construcción else sea opcional puede causar problemas de ambigüedad al compilador cuando se utilizan construcciones if o if-else anidadas. Para solventar el problema se ha establecido una regla muy sencilla que todo compilador de C tiene en cuenta. La regla consiste en que una sentencia else se asocia con el if precedente más cercano siempre y cuando éste no tenga ya asociada otra sentencia else. A continuación se muestran dos porciones de programa prácticamente iguales, pero con comporta- mientos completamente diferentes. Se deja para el lector el análisis de ambos casos. . . . if (n > 0) if (a > b) z = a; else z = b; . . . . . . if (n > 0) f if (a > b) z = a; g else z = b; . . . 4.1.2 Variante if-else-if Existe finalmente otra construcción alternativa muy común, conocida como if-else-if o simple- mente else-if. Su construcción, donde las condiciones se plantean de forma escalonada, se muestra a continuación: if (condición 1) f grupo de sentencias 1; g else if (condición 2) f grupo de sentencias 2; g . . . else if (condición N) f grupo de sentencias N; g else f grupo de sentencias por defecto; g 27 4. Construcciones condicionales Las condiciones se evalúan secuencialmente de arriba hacia abajo hasta encontrar una que dé como resultado cierto. En ese punto, se ejecuta el grupo de sentencias correspondiente a dicha condición. El resto de condiciones y sentencias asociadas se ignoran. En caso de que ninguna de las condiciones se evalúe cierta, se ejecutarı́a el grupo de sentencias por defecto. Como en todos los casos anteriores, el último else es opcional. A continuación se muestra un ejemplo del uso de esta construcción: #include <stdio.h> void main() f int hora; scanf( "%d", &hora ); if ((hora >= 0) && (hora < 12)) printf( "Buenos dı́as" ); else if ((hora >= 12) && (hora < 18)) printf( "Buenas tardes" ); else if ((hora >= 18) && (hora < 24)) printf( "Buenas noches" ); else printf( "Hora no válida" ); g 4.2 El operador condicional ? El operador condicional ? es el único operador ternario de C. La forma general de las expresiones construidas con este operador es la siguiente: expresión 1 ? expresión 2 : expresión 3; De manera que si la primera expresión se evalúa cierta, toda la expresión toma el valor de la segunda expresión. En cambio, si la primera expresión se evalúa falsa, toda la expresión toma el valor de la tercera expresión. Un ejemplo tı́pico del uso de este operador es el cálculo del máximo de dos valores. En la siguiente sentencia, c toma el valor del máximo entre la variable a y b. c = (a > b) ? a : b; Esto mismo podrı́a haberse escrito usando la construcción if-else como: if (a > b) c = a; else c = b; 4.3. Construcción switch 28 De esta manera, algunas sentencias if-else sencillas pueden escribirse de manera muy compacta mediante el operador ?. Finalmente, el operador condicional, por ser en realidad un operador para expresiones, puede usarse en lugares donde no puede usarse un if-else, como se muestra a continuación: printf("El mı́nimo es %d nn", ((x < y) ? x : y) ); 4.3 Construcción switch Esta construcción permite especificar múltiples sentencias al estilo if-else-if, pero de manera más compacta, legible y elegante. Su forma general es la siguiente: switch ( expresión ) f case constante 1 : grupo de sentencias 1; break; case constante 2 : grupo de sentencias 2; break; . . . case constante N : grupo de sentencias N; break; default : grupo de sentencias por defecto; break; g donde la expresión debe ser de tipo entero o carácter, al igual que todas las constantes asociadas a cada etiqueta case. Es importante resaltar que no pueden usarse variables o expresiones en los distintos case, sino sólo constantes. El funcionamiento de la construcción switch es como sigue. En primer lugar se evalúa la expresión. Seguidamente su valor es comparado secuencialmente con el de las diferentes constantes en los case. Si el valor de la expresión coincide con alguna de ellas, se ejecuta el grupo de sentencias correspondiente y switch concluye gracias a la sentencia break. En caso contrario, y si existe el caso default (que es opcional), se ejecutarı́a el grupo de sentencias por defecto (ver Fig. 4.2). Cabe mencionar de forma especial, la sentencia break que volveremos a ver en capı́tulos sucesi- vos. En general, break se utiliza para finalizar de forma forzada la ejecución dentro de un bloque de código, de manera que la siguiente sentencia a ejecutar será la primera sentencia justo después de dicho bloque. En la construcción switch, break es necesario para concluir la ejecución del grupo de sentencias asociado al caso cuya constante coincide con el valor de la expresión. Ası́ pues, la sentencia a ejecutar después de break en un switch, será la primera sentencia posterior a la llave g que cierra el switch. 29 4. Construcciones condicionales Grupo de sentencias 1 Grupo de sentencias 2 Expresión . . . Grupo de sentencias N Grupo de por defecto sentencias Figura 4.2: Esquema de funcionamiento de switch La construcción switch también podrı́a escribirse de forma equivalente mediante sentencias del tipo if-else-if, de la siguiente forma: if (expresión == constante 1) f grupo de sentencias 1; g else if (expresión == constante 2) f grupo de sentencias 2; g . . . else if (expresión == constante N) f grupo de sentencias N; g else f grupo de sentencias por defecto; g que, como puede verse, es mucho más ineficiente en tiempo de ejecución, puesto que la expresión debe evaluarse repetidas veces, una para cada condición. El siguiente ejemplo muestra un programa que hace uso de switch para traducir a caracteres un dı́gito entre 1 y 5. #include <stdio.h> void main() f int num; scanf( "%d", &num ); switch ( num ) 4.3. Construcción switch 30 f case 1 : printf( "Uno.nn" ); break; case 2 : printf( "Dos.nn" ); break; . . . case 5 : printf( "Cinco.nn" ); break; default : printf( "El dı́gito está fuera de rango.nn" ); break; g g Finalmente, cabe decir que el grupo de sentencias asociado a un case puede ser vacı́o. Este caso particular tiene su utilidad cuando se desea que varias etiquetas case ejecuten un mismo grupo de sentencias. Por ejemplo: #include <stdio.h> void main() f int num; scanf( "%d", &num ); switch ( num ) f case 1: case 3: case 7: printf( "Es un uno, un tres o un siete.nn" ); break; case 4: case 8: printf( "Es un cuatro, o un ocho.nn" ); break; default: printf( "Dı́gito no controlado.nn" ); break; g g 31 4. Construcciones condicionales 4.4 Ejercicios 1. Escribir un programa que lea tres valores enteros y muestre por pantalla el máximo y el mı́nimo de ellos. 2. Dado el siguiente programa, realizar un seguimiento de la ejecución en los siguientes supuestos: (a) a = 0, b = 0, c = 5, d = 3 (b) a = 2, b = 1, c = 5, d = 3 (c) a = 2, b = 1, c = 2, d = 2 (d) a = 2, b = 1, c = 0, d = 0 #include <stdio.h> void main() f int a, b, c, d; scanf( "%d %d %d %d", &a, &b, &c, &d ); if ( ((a > 0) || (b > a)) && (c != d) ) f a = c; b = 0; g else f c += d; c = (c == 0) ? (c + b) : (c - a); b = a + c + d; g printf( "%d %d %d %dnn", a, b, c, d ); g 3. Escribir un programa que lea un valor entero y determine si es múltiplo de 2 y de 5. 4. Escribir un programa que muestre por pantalla la ecuación de una recta en un plano, Ax+By+ C = 0, leyendo previamente las coordenadas de dos desus puntos (x1, y1) y (x2, y2). Recordar que: A = y2 � y1 y B = y1 � (x2 � x1)� x1 � (y2 � y1) 4.4. Ejercicios 32 33 5. Construcciones iterativas Capı́tulo 5 Construcciones iterativas Hasta ahora hemos visto algunos aspectos básicos del control del flujo de ejecución de un programa en C. Este capı́tulo presenta los mecanismos que C ofrece para la ejecución repetida de sentencias, bien un número prefijado de veces, bien dependiendo de cierta condición. Es decir, mecanismos para la creación de bucles de ejecución. C proporciona las siguientes construcciones iterativas: � la construcción while, � la construcción do-while, y � la construcción for. 5.1 Construcción while La construcción while es similar a la existente en otros lenguajes de programación. Sin embargo, debido a que en C toda sentencia puede considerarse como una expresión, la construcción while de C ofrece cierta potencia añadida. La forma más general para la ejecución repetida de una sola sentencia es: while (condición) sentencia; O para la ejecución repetida de un grupo de sentencias: while (condición) f grupo de sentencias; g El funcionamiento de esta construcción es bastante simple. El cuerpo del bucle, es decir, la sen- tencia o grupo de sentencias dentro del bucle, se ejecuta mientras el valor de la expresión que actúa de condición sea cierto. En el momento en que la condición sea falsa, la ejecución del programa continúa secuencialmente con la siguiente instrucción tras el bucle (ver Fig. 5.1). 5.1. Construcción while 34 Condición sentencias Grupo de Cierto Falso Figura 5.1: Esquema de funcionamiento de while El siguiente ejemplo calcula la media de una secuencia de números enteros leı́dos por teclado aca- bada en �1: #include <stdio.h> void main() f int num, cont, suma; cont = 0; suma = 0; scanf( "%d", &num ); while (num != -1) f cont++; suma = suma + num; scanf( "%d", &num ); g if (cont != 0) printf( "La media es %dnn", sum/cont ); else printf( "La secuencia es vacı́a.nn" ); g En la construcción while la condición se evalúa al principio del bucle. Por ello, si cuando se al- canza el bucle por primera vez, la condición es falsa, el cuerpo del bucle no se ejecuta nunca (imagı́nese el caso, en el ejemplo anterior, en que el primer número de la secuencia sea �1). Como consecuencia, el cuerpo de un bucle while puede ejecutarse entre 0 y N veces, donde N depende de la condición. Nótese también que si la condición permanece cierta, el bucle puede no terminar nunca (imagı́nese qué ocurrirı́a si se elimina la sentencia scanf del cuerpo del bucle del ejemplo anterior). Por ello, habitualmente las sentencias del cuerpo del bucle modifican las variables que aparecen en la condición, de forma que ésta sea falsa en algún momento. Por otra parte, la condición del bucle (y esto es extensible a las diferentes construcciones repetitivas) 35 5. Construcciones iterativas sentencias Grupo de Condición Falso Cierto Figura 5.2: Esquema de funcionamiento de do-while no tiene por qué ser simplemente una expresión lógica, sino que puede ser cualquier expresión. Por ejemplo, los siguiente bucles while (x--) f ... g while (x = x+1); son perfectamente válidos. En ambos casos el cuerpo del bucle se repetirá mientras el valor de x sea distinto de 0. Nótese que en el segundo caso el cuerpo del bucle es nulo, lo cual también es posible. 5.2 Construcción do-while La forma general de la construcción do-while es la siguiente: do f sentencia; o grupo de sentencias; g while (condición); Nótese que tanto para ejecutar una sola sentencia como para ejecutar un grupo de ellas, las llaves f g son igualmente necesarias. Esta construcción funciona de manera muy similar a la construcción while. Sin embargo, al contrario que ésta, do-while ejecuta primero el cuerpo del bucle y después evalúa la condición. Por lo cual, el cuerpo del bucle se ejecuta como mı́nimo 1 vez (ver Fig. 5.2). El siguiente ejemplo cuenta el número de veces que aparece el número 3 en una secuencia de números enteros acabada en �1: #include <stdio.h> void main() f int num, cont; 5.3. Construcción for 36 Condición Sentencia inicial Incremento / Decremento Grupo de sentencias Cierto Falso Figura 5.3: Esquema de funcionamiento de for cont = 0; do f scanf( "%d", &num ); if (num == 3) cont++; g while (num != -1); printf( "El 3 ha aparecido %d vecesnn", cont ); g Es importante destacar el uso de ‘;’ después de la condición, a diferencia de en la construcción while , donde no se utiliza. Finalmente, cabe decir que tradicionalmente, tanto la construcción while como la construcción do-while se utilizan en bucles donde se desconoce a priori el número exacto de iteraciones. 5.3 Construcción for Esta construcción iterativa no presenta un formato fijo estricto, sino que admite numerosas variantes, lo que la dota de gran potencia y flexibilidad. Su forma más general para la ejecución repetida de una sola sentencia es: for (sentencia inicial ; condición ; incremento/decremento) sentencia; o para la ejecución repetida de un grupo de sentencias: 37 5. Construcciones iterativas for (sentencia inicial ; condición ; incremento/decremento) f grupo de sentencias; g La primera parte de la construcción for acostumbra a ser una sentencia de asignación donde se inicializa alguna variable que controla el número de veces que debe ejecutarse el cuerpo del bucle. Esta sentencia se ejecuta una sola ocasión, antes de entrar por primera vez al cuerpo del bucle. La segunda parte corresponde a la condición que indica cuándo finaliza el bucle, de la misma forma que en las construcciones iterativas anteriores. En este caso, la condición se evalúa antes de ejecutar el cuerpo del bucle, por lo que al igual que en la construcción while, el cuerpo puede ejecutarse entre 0 y N veces, donde N depende de la condición. La tercera parte corresponde normalmente a una sentencia de incremento o decremento sobre la variable de control del bucle. Esta sentencia se ejecuta siempre después de la ejecución del cuerpo del bucle. La figura 5.3 muestra esquemáticamente el funcionamiento del bucle for. El programa del siguiente ejemplo utiliza la construcción for para calcular el sumatorio 10X i=1 i 3 : #include <stdio.h> void main() f int i, cubo, suma; suma = 0; for (i = 0 ; i <= 10 ; i++) f cubo = i * i * i; suma += cubo; g printf( "El sumatorio es %dnn", suma ); g Las tres partes de la construcción for son opcionales, por lo que es posible omitir alguna o todas ellas. En cualquier caso, los punto y coma (;) separadores son siempre necesarios. Un ejemplo clásico de este tipo de bucle for es el bucle infinito (nunca concluye la ejecución): for ( ; 1 ; ) f /* Grupo de sentencias */ g Tradicionalmente la construcción for se utiliza en bucles donde el número exacto de iteraciones es conocido a priori, y puede controlarse mediante una variable que actúa como contador. 5.4. Las sentencias break y continue 38 5.3.1 El operador coma (,) C permite la utilización de más de una sentencia en la primera y tercera partes de la construcción for, ası́ como de más de una condición en la segunda parte. Por ejemplo, el siguiente bucle es válido en C: for (i = 0, j = 10 ; i < 10, j > 0 ; i++, j-=2) f /* Grupo de sentencias */ g Ası́ pues, las variables i y j se inicializan a 0 y 10, respectivamente, antes de comenzar la ejecución del bucle. En la segunda parte de la construcción, aparecen dos condiciones, i < 10 y j > 0. Si alguna de ellas es falsa, la ejecución del bucle se detiene. Finalmente, tras ejecutarse el cuerpo del bucle, i se incrementa en 1 y j se decrementa en 2, tras lo cual vuelven a comprobarse las condiciones, y ası́ sucesivamente. 5.3.2 Equivalencia for-while Como se ha podido ver, C trata el bucle for de manera muy similar al bucle while, usando una condición para decidir cuándo concluye la ejecución. De hecho, todo bucle for puede escribirse
Compartir