Professional Documents
Culture Documents
Introduccin a la programacin en C
EDICIONS UPC
Los autores, 2000 Edicions UPC, 2000 Edicions de la Universitat Politcnica 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
Produccin:
CPET (Centre de Publicacions del Campus Nord) La Cup. Gran Capit s/n, 08034 Barcelona
Introducci n a la programaci n en C o o
Marco A. Pe a n Jos M. Cela e Departament dArquitectura de Computadors Universitat Polit` cnica de Catalunya e 08034 Barcelona, Espa a n marcoa@ac.upc.es cela@ac.upc.es 19 de junio de 2000
Indice General
Indice General
Indice de Figuras Indice de Tablas Prefacio 1 Conceptos b sicos de programaci n a o 1.1 Ordenador y perif ricos . . . . . e 1.2 Bits, bytes y palabras . . . . . . 1.3 Lenguajes de programaci n . . . o 1.3.1 Lenguajes de bajo nivel . 1.3.2 Lenguajes de alto nivel . 1.4 Elaboraci n de un programa . . o 1.5 Traductores . . . . . . . . . . . 1.5.1 Ensambladores . . . . . 1.5.2 Int rpretes . . . . . . . e 1.5.3 Compiladores . . . . . . 2 Primer contacto con C 2.1 Un poco de historia . . . . . . 2.2 Caractersticas del lenguaje . . 2.3 Creaci n de un programa . . . o 2.4 Primeros pasos con C . . . . . 2.5 El modelo de compilaci n de C o v vii ix 1 1 2 2 3 3 4 5 5 5 6 7 7 7 8 9 10 13 13 13 14 14 15 16 17 17 17
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
3 Empezando a programar 3.1 Identicadores . . . . . . . . . . . 3.2 Estructura de un programa . . . . 3.3 Variables y constantes . . . . . . . 3.3.1 Variables . . . . . . . . . 3.3.2 Constantes . . . . . . . . 3.3.3 Entrada y salida de valores 3.4 Expresiones . . . . . . . . . . . . 3.4.1 Operador de asignaci n . . o 3.4.2 Operadores aritm ticos . . e
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
Indice General
ii
3.5
3.4.3 Operadores relacionales 3.4.4 Operadores l gicos . . . o 3.4.5 Prioridad de operadores Ejercicios . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
18 19 19 20 23 23 25 26 27 28 31 33 33 35 36 38 38 38 39 41 41 42 43 44 44 45 45 46 47 49 49 50 50 52 53 54 54 55 55 56 57
4 Construcciones condicionales 4.1 Construcci n if . . . . . . . . o 4.1.1 Variante if-else . . 4.1.2 Variante if-else-if 4.2 El operador condicional ? . . . 4.3 Construcci n switch . . . . . o 4.4 Ejercicios . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
5 Construcciones iterativas 5.1 Construcci n while . . . . . . . . . o 5.2 Construcci n do-while . . . . . . o 5.3 Construcci n for . . . . . . . . . . o 5.3.1 El operador coma (,) . . . . . 5.3.2 Equivalencia for-while . . 5.4 Las sentencias break y continue 5.5 Ejercicios . . . . . . . . . . . . . . . 6 Tipos de datos elementales 6.1 N meros enteros . . . . . . . u 6.1.1 Modicadores . . . . 6.1.2 Resumen . . . . . . . 6.2 Caracteres . . . . . . . . . . . 6.2.1 Caracteres especiales . 6.2.2 Enteros y el tipo char 6.2.3 Conversiones de tipos 6.3 N meros reales . . . . . . . . u 6.4 Ejercicios . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
7 Tipos de datos estructurados: Tablas 7.1 Vectores . . . . . . . . . . . . . . 7.1.1 Consulta . . . . . . . . . 7.1.2 Asignaci n . . . . . . . . o 7.1.3 Ejemplos . . . . . . . . . 7.2 Matrices . . . . . . . . . . . . . . 7.2.1 Consulta . . . . . . . . . 7.2.2 Asignaci n . . . . . . . . o 7.2.3 Ejemplo . . . . . . . . . . 7.3 Tablas multidimensionales . . . . 7.3.1 Ejemplo . . . . . . . . . . 7.4 Cadenas de caracteres . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
. . . . . . . . . . .
iii
Indice General
7.5
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
57 59 60 61 63 63 64 65 65 66 66 67 68 69 70 70 71 71 72 75 75 76 76 77 78 79 83 84 87 87 89 89 90 91 91 91 91 92 93 94 94 95
8 Otros tipos de datos 8.1 Estructuras . . . . . . . . . . . . . . . . . . 8.1.1 Declaraci n de variables . . . . . . . o 8.1.2 Acceso a los campos . . . . . . . . . 8.1.3 Asignaci n . . . . . . . . . . . . . . o 8.1.4 Ejemplo . . . . . . . . . . . . . . . . 8.2 Uniones . . . . . . . . . . . . . . . . . . . . 8.2.1 Ejemplo . . . . . . . . . . . . . . . . 8.3 Tipos de datos enumerados . . . . . . . . . . 8.4 Denici n de nuevos tipos de datos . . . . . o 8.5 Tiras de bits . . . . . . . . . . . . . . . . . . 8.5.1 Operador de negaci n . . . . . . . . o 8.5.2 Operadores l gicos . . . . . . . . . . o 8.5.3 Operadores de desplazamiento de bits 8.6 Ejercicios . . . . . . . . . . . . . . . . . . . 9 Punteros 9.1 Declaraci n y asignaci n de direcciones o o 9.1.1 Declaraci n . . . . . . . . . . . o 9.1.2 Asignaci n de direcciones . . . o 9.2 Indirecci n . . . . . . . . . . . . . . . o 9.3 Operaciones con punteros . . . . . . . . 9.4 Punteros y tablas . . . . . . . . . . . . 9.5 Punteros y estructuras . . . . . . . . . . 9.6 Ejercicios . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
10 Funciones 10.1 Generalidades . . . . . . . . . . . . . . . 10.2 Denici n y llamada . . . . . . . . . . . o 10.2.1 Denici n . . . . . . . . . . . . . o 10.2.2 Prototipos . . . . . . . . . . . . . 10.2.3 Llamada . . . . . . . . . . . . . . 10.3 Variables y par metros . . . . . . . . . . a 10.3.1 Variables locales . . . . . . . . . 10.3.2 Variables globales . . . . . . . . 10.3.3 Par metros formales . . . . . . . a 10.4 Devoluci n de resultados . . . . . . . . . o 10.5 Paso de par metros . . . . . . . . . . . . a 10.5.1 Paso de par metros por valor . . . a 10.5.2 Paso de par metros por referencia a
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
Indice General
iv
10.5.3 Las tablas y las funciones . . . 10.5.4 Par metros en la funci n main a o 10.6 Recursividad . . . . . . . . . . . . . . 10.7 Ejercicios . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. 96 . 99 . 100 . 101 105 107 109 111 111 113 113 113 116 119 119 119 120 122 123 123 124 124 124 126 127 128 129 129 129 135 135 135 136 138 141 143
11 Ficheros 11.1 Abrir y cerrar cheros . . . . . . . . . . . . . . . . 11.2 Leer y escribir en cheros . . . . . . . . . . . . . . 11.3 Otras funciones para el manejo de cheros . . . . . 11.3.1 feof . . . . . . . . . . . . . . . . . . . . 11.3.2 ferror . . . . . . . . . . . . . . . . . . 11.3.3 fflush . . . . . . . . . . . . . . . . . . 11.4 Ficheros est ndar: stdin, stdout, stderr a 11.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . A El preprocesador A.1 Directiva include . . . . . . A.2 Directivas define y undef . A.3 Directivas ifdef y ifndef . A.4 Macros . . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
B La librera est ndar a B.1 Manipulaci n de cadenas de caracteres . . o B.2 Entrada y salida . . . . . . . . . . . . . . B.2.1 Entrada y salida b sica . . . . . . a B.2.2 Entrada y salida con formato . . . B.2.3 Ficheros . . . . . . . . . . . . . . B.3 Funciones matem ticas . . . . . . . . . . a B.4 Clasicaci n y manipulaci n de caracteres o o B.5 Conversi n de datos . . . . . . . . . . . . o B.6 Manipulaci n de directorios . . . . . . . o B.7 Memoria din mica . . . . . . . . . . . . a
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
C Sistemas de numeraci n o C.1 Naturales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.2 Enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.3 Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . C.3.1 Problemas derivados de la representaci n en coma otante o D Tabla de caracteres ASCII E Bibliografa y recursos WEB
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
Indice de Figuras
Indice de Figuras
1.1 1.2 1.3 1.4 1.5 2.1 4.1 4.2 5.1 5.2 5.3 7.1 7.2 7.3 9.1 Niveles de abstracci n en los lenguajes de programaci n . . . . . o o Cronologa en el desarrollo de algunos lenguajes de programaci n o Ciclo de vida de un programa . . . . . . . . . . . . . . . . . . . . Fases en la interpretaci n de un programa . . . . . . . . . . . . . o Fases en la compilaci n de un programa . . . . . . . . . . . . . . o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 4 5 6 11 24 29 34 35 36 50 54 56 82 99
Modelo de compilaci n de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . o Esquema de funcionamiento de if y de if-else . . . . . . . . . . . . . . . . . . . Esquema de funcionamiento de switch . . . . . . . . . . . . . . . . . . . . . . . . . Esquema de funcionamiento de while . . . . . . . . . . . . . . . . . . . . . . . . . Esquema de funcionamiento de do-while . . . . . . . . . . . . . . . . . . . . . . . Esquema de funcionamiento de for . . . . . . . . . . . . . . . . . . . . . . . . . . . Representaci n gr ca de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . o a Representaci n gr ca de una matriz . . . . . . . . . . . . . . . . . . . . . . . . . . . o a Representaci n gr ca de una tabla de tres dimensiones . . . . . . . . . . . . . . . . . o a Acceso a una matriz mediante un puntero . . . . . . . . . . . . . . . . . . . . . . . .
vii
Indice de Tablas
Indice de Tablas
3.1 3.2 3.3 3.4 3.5 6.1 6.2 6.3 6.4 8.1 C.1 C.2 C.3 C.4 Palabras reservadas de C . . . . . . . . . . . . Operadores aritm ticos en C . . . . . . . . . . e Operadores relacionales y l gicos en C . . . . . o Tabla de verdad de los operadores l gicos en C o Prioridad y asociatividad de los operadores en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 17 19 19 20 42 44 45 46 71 135 136 137 138
Representaci n de enteros en decimal, octal y hexadecimal o Resumen de tipos de datos enteros . . . . . . . . . . . . . Caracteres interpretados como enteros . . . . . . . . . . . Resumen de tipos de datos reales . . . . . . . . . . . . . .
Tabla de verdad de los operadores l gicos . . . . . . . . . . . . . . . . . . . . . . . . o Representaci n de n meros naturales en binario natural o u Representaci n de n meros enteros en complemento a 2 o u Representaci n de n meros enteros en exceso 2e;1 . . . o u Representaci n de n meros reales . . . . . . . . . . . . o u . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ix
Pr logo o
Pr logo o
Este libro surge a partir de la experiencia docente de los autores en la asignatura Introducci n a los o ordenadores de la Escola T` cnica Superior dEnginyeria de Telecomunicaci de Barcelona, de la Unie o versitat Polit` cnica de Catalunya. Como su ttulo indica, se trata de un texto de introduci n a la proe o gramaci n en lenguaje C. El libro pretende ce irse a los aspectos fundamentales del est ndar ANSI C o n a actual. Aunque hoy en da existen otros lenguajes de programaci n muy populares como C++ o JAVA, o la comprensi n de estos lenguajes exige un s lido conocimiento de las bases de programaci n en C. o o o El texto est concebido como un curso completo y por lo tanto debe ser ledo de forma secuencial. a Al nal de cada captulo 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 ejemu a plos resueltos en el texto. El lector debe recordar que la programaci n es una t cnica aplicada, igual que o e tocar un instrumento musical, y por lo tanto requiere muchas horas de ensayo para ser dominada. Los ejercicios propuestos son lo sucientemente simples como para no requerir conocimientos adicionales de otras materias (matem ticas, fsica, contabilidad, etc). a Uno de los puntos m s importantes para quien empieza a programar es adoptar desde el principio a un buen estilo de programaci n. Esto es, escribir las construcciones del lenguaje de una forma clara o y consistente. En este sentido, los ejemplos resueltos en el texto muestran un buen estilo b sico de a programaci n, por lo que se recomienda al lector imitar dicho estilo cuando realice sus programas. o Los ap ndices A y B son de lectura obligada antes de comenzar a desarrollar programas de complee jidad media. En dichos ap ndices el lector se familiarizar con el uso del preprocesador y de la librera e a est ndar. Ambas son herramientas fundamentales para desarrollar programas. a El ap ndice C incluye una descripci n de los formatos de datos en el computador. Este tema no e o es propiamente de programci n, pero la comprensi n de dichos formatos ayuda al programador a eno o tender mejor conceptos b sicos, como las operaciones de conversi n de tipos. El lector puede leer este a o ap ndice en cualquier momento, aunque recomendamos leerlo antes del captulo 6. e En las referencias bibliogr cas se indican algunas direcciones web donde el lector podr encontrar a a preguntas y respuestas comunes de quienes se inician en el lenguaje C. As mismo, el lector podr a encontrar materiales adicionales sobre programaci n, historia del lenguaje, etc. o
Captulo 1
Programador
Lenguaje natural
Lenguaje de programacin
Lenguaje mquina
Ordenador
COBOL FORTRAN LISP Ensamblador Algol BASIC PL/I Prolog SIMULA Logo
Pascal
1950
1955
1960
1965
1970
1975
1980
1985
1990
1995
Mantenimiento 11111111111111 00000000000000 11111111111111 00000000000000 Diseo Explotacin 1111111 0000000 00000000000 0000000 000000000000011111111111 1111111 111111111111111111111111 00000000000 Codificacin 1111111111111 0000000000000 1111111111111 0000000000000
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 o a escrito en un lenguaje de alto nivel deber traducirse a lenguaje m quina, de forma que pueda ser a a entendido y ejecutado por el ordenador. Para ello cada tipo de ordenador deber disponer de unos a programas especiales que realicen dicha traducci n (ver Sec. 1.5). o
An lisis a
En la fase de an lisis se estudia cu l es el problema a resolver y se especican a muy alto nivel los a a 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 a a de proceder al dise o es muy importante haber comprendido correctamente los requerimientos del n problema.
Diseno
Una vez bien denido el problema y las lneas generales para solucionarlo, se requiere una soluci n o adecuada a un conjunto de recursos determinado. Tanto fsicos: en qu ordenador va a funcionar la e aplicaci n, de qu tipo de perif ricos se dispone . . . , como l gicos: qu sistema operativo se usar , qu o e e o e a e herramientas de desarrollo, qu bases de datos . . . Finalmente se dise ar un conjunto de algoritmos e n a que resuelvan los distintos subproblemas en que se haya dividido el desarrollo.
Codicaci n o
Consiste en la traducci n de los algoritmos dise ados previamente, utilizando el lenguaje y entorno de o n desarrollo escogidos en la fase anterior. Ser necesario realizar pruebas que garanticen al m ximo la a a calidad de los programas desarrollados. Entre otras cosas, que est n libres de errores. e La documentaci n generada en esta fase junto con la de las fases anteriores ser muy util en el o a futuro para las eventuales actuaciones de mantenimiento.
Explotaci n o
Los diferentes programas desarrollados en la fase anterior se instalan en el entorno nal de trabajo. Si es necesario se instalar n tambi n otras herramientas de utilidad, necesarias para el correcto funcionaa e miento del sistema. Se debe proporcionar documentaci n, manuales de usuario, formaci n, etc. o o
Mantenimiento
En esta fase se realizar n correcciones al sistema desarrollado, bien para solventar errores no depuraa dos, bien para cambiar o a adir nuevas funcionalidades requeridas por el cliente. Dependiendo de la n importancia del caso, ser necesario retomar el ciclo de vida a nivel de codicaci n, dise o o incluso a o n an lisis (ver Fig. 1.3). a Cuanto mejor se haya documentado el desarrollo en las primeras fases del ciclo de vida, menor ser a el tiempo necesario para llevar a cabo los distintos tipos de mantenimiento.
1.5 Traductores
Como ya se ha comentado, el unico 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 traduca a u tor. Este, a su vez, ser el encargado de comprobar que los programas est n escritos correctamente, de a e acuerdo con la denici n del lenguaje de programaci n empleado. Pueden distinguirse varios tipos de o o traductores:
1.5.1 Ensambladores
Los programas ensambladores son los encargados de traducir a lenguaje m quina los programas escritos a en lenguaje ensamblador. La correspondencia entre ambos lenguajes es muy directa, por lo que los ensambladores suelen ser programas relativamente sencillos.
1.5. Traductores
Figura 1.5: Fases en la compilaci n de un programa o La principal desventaja de los int rpretes es su lentitud para ejecutar los programas, pues es necee sario vericar la sintaxis y realizar la traducci n en cada ejecuci n. o o
1.5.3 Compiladores
La funci n de un compilador consiste en traducir un programa fuente escrito en un lenguaje de alto o nivel a su equivalente en c digo m quina (tambi n llamado c digo objeto). o a e o Mientras que un int rprete traduce y ejecuta al mismo tiempo cada una de las instrucciones, un e compilador analiza, traduce y posteriormente ejecuta todo el programa en fases completamente separadas (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 o a a interpretado. El proceso de compilaci n o Edici n Consiste en escribir el programa fuente usando el lenguaje de programaci n seleccionado y su o o grabaci n en un chero. Para ello es necesario usar un programa editor, que puede o no formar o parte del entorno de desarrollo. Compilaci n En esta fase se verica la sintaxis del programa fuente y se traduce el programa a c digo o o m quina (objeto). Si se producen errores, el compilador muestra informaci n del tipo de error y a o d nde se ha producido. o Montaje Consistente en la combinaci n de los diferentes m dulos objeto y libreras del lenguaje para o o crear un programa ejecutable. Esta fase se conoce tambi n como linkado. e Ejecuci n En esta fase se invoca al programa de la manera adecuada dependiendo del sistema operativo o sobre el que vaya a funcionar. Como nota nal, cabe decir que todo lenguaje de programaci n puede ser tanto interpretado como o compilado. Sin embargo, dependiendo del las caractersticas 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 e o lenguaje BASIC es mayoritariamente interpretado, mientras que C es compilado.
Captulo 2
Popularidad. Existe una gran variedad de compiladores, libreras, herramientas de apoyo a la programaci n, etc. Es el lenguaje predominante en el entorno UNIX. o Portabilidad. El mismo programa escrito en C puede compilarse y ejecutarse sin pr cticamente a ning n cambio en diferentes ordenadores. Esto se debe en gran parte al est ndar ANSI C. u a Sencillez. C utiliza pocas palabras clave, por lo que puede aprenderse f cilmente. a Estructura y modularidad. Los programas en C pueden escribirse agrupando el c digo en funcioo nes que a su vez se agrupan en distintos m dulos. De esta forma, el c digo puede reutilizarse. o o De acuerdo con esto, C representa una buena elecci n como lenguaje de programaci n. Sin emo o bargo, seguro que el lector ha odo hablar de los lenguajes C++, Java y de la programaci n orientada a o objetos, adem s de preguntarse sobre las diferencias entre C y C++. Pues bien, C++ puede verse como a un superconjunto de C, lo que signica que casi cualquier aspecto de C es perfectamente v lido en C++ a (pero no al rev s). Java por su parte, al igual que C++, tambi n se basa en la sintaxis de C. e e Finalmente, diremos que aunque C es considerado como un lenguaje de alto nivel, mantiene muchas caractersticas de los lenguajes de bajo nivel, por lo que podra clasicarse como de nivel bajo-medio.
Edici n o
El primer paso consiste en usar un editor de textos y crear un chero que contenga el c digo del o programa en C. Este c digo, normalmente llamado c digo fuente, servir para dar instrucciones precisas o o a al ordenador. Por ejemplo, la siguiente linea de c digo fuente en C indica al ordenador que debe mostrar o el mensaje entre comillas en la pantalla: printf( "Esto es un mensaje" ); El formato del texto admitido por la mayora de compiladores se basa en el C digo Est ndar Ameri o a cano para el Intercambio de Informaci n (ASCII). La mayor parte de los procesadores de texto utilizan o c digos especiales para dar formato a los documentos, por lo que normalmente no pueden ser usados o como editores de programas. Hoy en da, la mayora de los entornos de programaci n incluyen un editor, sin embargo, otros no. o En estos casos pueden usarse otros programas gen ricos de edici n de textos ASCII proporcionados por e o 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 chero fuente de un programa debe grabarse con un nombre. Normalmente, el nombre del chero debe permitir intuir qu hace el programa. Adicionalmente, los cheros fuente en C suelen e tener la extensi n .c para identicarlos f cilmente. o a
Compilaci n o
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 a o la realiza un programa llamado compilador que, dado un chero fuente, produce un chero con las instrucciones de lenguaje m quina correspondientes al programa fuente original. El nuevo chero a recibe el nombre de chero objeto. El chero objeto suele tener el mismo nombre que el chero fuente, pero con la extensi n .OBJ (o o .o en UNIX).
Montaje
En el tercer paso, las diferentes partes del c digo compilado se combinan para crear el programa ejecuo table. Parte del lenguaje C consiste en una librera de funciones precompiladas que contiene c digo objeto. o Las funciones en esta librera realizan operaciones de uso frecuente, como mostrar datos en pantalla o leer datos de un chero. La funci n printf del ejemplo anterior es una funci n de dicha librera. As o o pues, el chero objeto producido al compilar el chero fuente debe combinarse con el c digo objeto de o la librera para crear el chero del programa ejecutable. Cabe destacar que en la mayora 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. o o
f g
Todo programa en C debe tener una y s lo una funci n main(). Esta funci n deber constar de una o o o a serie de sentencias (en este caso vaca) delimitada por los smbolos f g. Dichas sentencias especican la secuencia de acciones que el programa deber llevar a cabo. a En C pueden ponerse comentarios en cualquier lugar del programa, utilizando los smbolos /* */. El compilador de C ignora todo el texto entre el inicio del comentario (/*) y el nal del mismo (*/). A adir comentarios a un programa en C no incrementa el tama o de los cheros objeto ni ejecutable, n n ni tampoco ralentiza la ejecuci n del programa. Veamos un ejemplo de programa con comentarios: o /* Mi primer programa en C */ void main()
f g
/* ...
Sin embargo, no es posible poner un comentario dentro de otro. Por ejemplo sera ilegal:
10
f g
/* ...
Pero veamos un programa no tan simple. Por ejemplo, el siguiente programa usa la funci n o printf, predenida en la librera est ndar stdio.h, para mostrar un mensaje en la pantalla. a /* Mi primer programa en C */ #include <stdio.h> void main()
f g
nn"
);
Preprocesador
Aunque en el ap ndice A se ver en detalle esta parte del proceso de compilaci n, seguidamente se e a o describen algunos aspectos b sicos. a El preprocesador toma como entrada el c digo fuente y es el responsable de eliminar los comeno tarios (ya que en realidad no representan ninguna instrucci n) y de interpretar las directivas especiales o del preprocesador, denotadas por el smbolo #. Por el momento destacaremos s lamente dos de las o directivas m s utilizadas: a #include, que incluye un chero externo dentro del chero fuente. Se usar n los smbolos a < > para indicar que el chero se encuentra en un directorio del entorno de compilaci n, difeo rente del directorio de trabajo actual. Por el contrario, se usar n los smbolos " " para indicar a chero locales. Por ejemplo: #include <math.h> incluye el chero con las deniciones de las funciones matem ticas de la librera est ndar. a a #include <stdio.h> incluye el chero con las deniciones de las funciones de entrada y salida de la librera est ndar. a #include "funciones.h" incluye el chero funciones.h del directorio actual. #define, que dene un nombre simb lico. Cuando el preprocesador encuentra un nombre o simb lico en el programa lo substituye por el valor que se le haya asociado con la directiva o #define. #define NUM ELEMENTOS 100 dene la constante NUM ELEMENTOS con valor 100. #define PI 3.1416 dene la constante PI.
11
Cdigo fuente
Preprocesador
Compilador
El compilador de C recibe el c digo fuente producido por el preprocesador y lo traduce a c digo objeto o o (cheros con extensi n .OBJ en MS-Windows, o extensi n .o en UNIX). o o
Montador
Si un chero fuente hace referencia a funciones de una librera (como la librera est ndar) o a funciones a denidas en otros cheros fuente, el montador se encarga de: combinar todos los cheros objeto correspondientes, vericar que s lo uno de ellos contenga la funci n principal main() y o o crear el chero nalmente ejecutable.
12
13
3. Empezando a programar
Captulo 3
Empezando a programar
3.1 Identicadores
Un identicador en un lenguaje de programaci n es un nombre utilizado para referir un valor constante, o una variable, una estructura de datos compleja, o una funci n, dentro de un programa. Todo identicao dor est formado por una secuencia de letras, n meros y caracteres de subrayado, con la restricci n de a u o que siempre debe comenzar por una letra o un subrayado y que no puede contener espacios en blanco. Cada compilador ja un m ximo para la longitud de los identicadores, siendo habitual un m ximo de a a 32 caracteres. C diferencia entre may sculas y min sculas, seg n lo cual C considerar los identicadores contador, u u u a Contador y CONTADOR, por ejemplo, como diferentes. En cualquier caso, nunca pueden utilizarse las palabras reservadas del lenguaje para la construcci n o de identicadores. De acuerdo con el est ndar ANSI, C consta unicamente de 32 palabras reservadas a (ver Tab. 3.1). Tabla 3.1: Palabras reservadas de C auto break case char const continue default do double else enum extern oat for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
14
Por el momento se tratar unicamente la declaraci n de la funci n main() que corresponde al a o o programa principal. La ejecuci n de un programa escrito en C siempre comienza por dicha funci n. o o Por esta raz n, en un programa s lo puede haber una funci n con dicho nombre. La denici n de tipos o o o o de datos se deja para el captulo 8. Toda funci n en C, y en particular la funci n main(), tiene la siguiente estructura: o o tipo datos nombre funcin ( parmetros ) o a
f g
No entraremos aqu en las particularidades de las funciones como el paso de par metros y la de a voluci n de resultados de un tipo de datos determinado (ver Cap. 10). Comentaremos simplemente o que tanto la devoluci n de resultados como los par metros son opcionales, y que en la mayora de o a programas sencillos no se usan en la denici n del programa principal. o A continuaci n se muestra, como ejemplo, un programa para evaluar la expresi n 3 o o /* Evaluando una expresin */ o #include <stdio.h> void main()
5
; =
32 4
%dnn", c );
El cuerpo del programa principal lo constituyen todas las lneas de programa comprendidas entre a los smbolos f y g. En cada una de dichas lneas 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.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 oro denador con el n 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 variao 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 signicativo a la variable e indicar el tipo de datos a que o corresponden los valores que almacenar . A continuaci n se muestra la sintaxis m s sencilla de una a o a sentencia de declaraci n para una sola variable. o tipo datos nombre variable; Adem s, en una sola sentencia pueden declararse varias variables de un mismo tipo de datos, sepaa 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. o tipo datos nombre variable = valor inicial;
3.3.2 Constantes
C admite dos tipos diferentes de constantes: literales y simb licas. o Constantes literales Todo valor que aparece directamente en el c digo fuente cada vez que es necesario para una operaci n o o 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 e como un valor real de coma otante. Este tipo de constantes puede escribirse tambi n utilizando alguna e de las notaciones cientcas com nmente aceptadas (ver Sec. 6.3). u Por el contrario, el resto de constantes num ricas son consideradas por el compilador, como valores e enteros. Pueden usarse tres formatos alternativos: Toda constante que comience por un dgito distinto de 0 es interpretada como un entero decimal (esto es, en base 10). Se especican mediante los dgitos del 0 al 9 y el signo positivo o negativo. Si una constante comienza con el dgito 0, se interpreta como un entero octal (base 8). Se especican mediante los dgitos 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 especican mediante los dgitos 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. a o e
16
Constantes simb licas o Una constante simb lica es una constante representada mediante un nombre (smbolo) en el programa. o 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 haramos con una variable. Una o constante simb lica se declara una sola vez, indicando el nombre y el valor que representa. o Las constantes simb licas tienen dos ventajas claras respecto a las literales. Supongamos el siguieno te c digo para calcular el permetro de una circunferencia y el area del crculo que dene: o perimetro = 2 * 3.14 * radio; area = 3.14 * radio * radio; Si por el contrario se hubiese denido una constante simb lica de nombre PI y valor 3.14, podramos o escribir un c digo mucho m s claro: o a 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 a o a a preciso de la constante , como 3.14159. En el primer caso debera substituirse uno a uno el valor 3.14 en todo el programa. En el segundo caso, bastara cambiar la denici n de la constante PI con o el nuevo valor. El m todo m s habitual para denir constantes en C es la directiva del preprocesador #define. e a Por ejemplo, en el caso anterior podramos haber escrito: #define PI 3.14159 Es decir, el nombre simb lico y a continuaci n el valor constante que representa. o o
17
3. Empezando a programar
Tabla 3.2: Operadores aritm ticos en C e Unarios Signo negativo Incremento Decremento Suma Resta Multiplicaci n o Divisi n o M dulo o
;
++
;; ;
=
% +
Binarios
correspondencia entre el formato y la lista de valores, tanto en n mero como en el tipo de los mismos. u Finalmente, la secuencia especial nn indica un salto de lnea. Por su parte, scanf es una funci n para la entrada de valores a una estructura de datos, y en o 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 a la variable num. N tese que en el caso de scanf se antepone el smbolo & a las variables. Por o 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 especica un valor. Los elementos que o o a o constituyen una expresi n son: constantes, variables y operadores. o
3.4. Expresiones
18
caso si el valor de la variable se modica despu s o antes de la evaluaci n de la expresi n en la que e o o aparece. Por ejemplo, la siguiente lnea de c digo: o 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 o u o los dos operandos de la divisi n son enteros, el cociente que se obtiene es el correspondiente a la o divisi n entera (el cociente no se redondea, sino que se trunca). Si alguno de los operandos es un valor o real, el resultado de la divisi n ser tambi n real. Por ejemplo, x = 3/2; asigna el valor 1 a la o a e 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 o el resto de una divisi n entera, por lo que sus operandos deben ser tambi n enteros. Por ejemplo, o e 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 exa a u presiones del estilo de i = i + 5; o x = x * (y + 2);. Este tipo de expresiones puede escribirse en C de forma compacta como: expresin1 o que es equivalente a: expresin1 = expresin1 o o op expresin2 o op = expresin2 o
Seg n esto, la asignaci n i = i + 5; puede reescribirse como i += 5; y la asignaci n u o o x = x * (y + 2); como x *= y + 2;. N tese que esta ultima expresi n no signica en o o ning n caso x = (x * y) + 2;. u Como puede verse, un uso abusivo de los operadores abreviados de C puede hacer difcil de leer un programa. C permite escribir expresiones muy compactas, pero que pueden generar confusi n al ser o ledas. Hay que recordar siempre que la legibilidad (mantenimiento) de un programa es tan importante como su correcto funcionamiento.
19
3. Empezando a programar
Tabla 3.3: Operadores relacionales (izquierda) y l gicos (derecha) en C o Menor que Mayor que Menor o igual que Mayor o igual que Igual que Distinto que
= =
== ! =
&&
jj
!
Tabla 3.4: Tabla de verdad de los operadores l gicos en C o A Cierto Cierto Falso Falso B Cierto Falso Cierto Falso !A Falso Falso Cierto Cierto A && B Cierto Falso Falso Falso A jj B Cierto Cierto Cierto Falso
el valor num rico entero cero, y un resultado CIERTO como cualquier valor entero diferente de cero. e 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 o = . 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.
jj
La tabla 3.4 muestra la tabla de verdad para los operadores l gicos. De acuerdo con dicha tabla, o 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.5. Ejercicios
20
Tabla 3.5: Prioridad y asociatividad de los operadores en C Operador Par ntesis e NO l gico o Signo negativo Incremento Decremento Multiplicaci n o Divisi n o M dulo o Suma Resta Menor que Menor o igual que Mayor que Mayor o igual que Igual que Distinto que Y l gico o O l gico o Asignaciones Smbolo
()
;
++
;;
=
% Izquierda a derecha Izquierda a derecha Izquierda a derecha
== ! =
&&
jj
= =
+ =
%=
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 area y su permetro, y mostrar los resultados a por pantalla. 2. Pedir una cantidad de segundos y mostrar por pantalla a cu ntas horas, minutos y segundos a corresponden. 3. Suponiendo que previamente se ha realizado la declaraci n int x = 7, y; , calcular el o valor de la variable y tras evaluar cada una de las siguientes sentencias de asignaci n: o (a) y (b) y (c) y (d) y
=
;2
(y
;;x;
x); x;
+ = = =
2;
==
y++
2 6
20 % 6 2
15
21
3. Empezando a programar
15
==
= <
16 3
jj
2 6
(4 3
;
>
2) 5
7 != 4 && 4
jj
<
1
+
2) && 3
<
12
3.5. Ejercicios
22
23
4. Construcciones condicionales
Captulo 4
Construcciones condicionales
Una de las construcciones importantes que pueden especicarse 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, cono o dicionalmente. Para ello ser necesario especicar dichas condiciones (ver Sec. 3.4) y disponer de un a mecanismo para indicar qu acciones tomar dependiendo de c mo se eval e una determinada condici n e o u o en un momento dado de la ejecuci n del programa. o Antes de empezar, un recordatorio. Como ya de coment en la secci n 3.4.3, C no dispone de o o valores booleanos o l gicos, que podran usarse en la evaluaci n de condiciones. En su defecto, C o o simula los valores falso y cierto, como el valor num rico cero, y cualquier valor no cero (incluyendo e negativos), respectivamente. As pues, en este captulo veremos las distintas maneras que C ofrece para controlar el ujo de ejecuci n de un programa de forma condicional, que son: o la construcci n if, o el operador condicional ?, y la construcci n switch. o
4.1 Construcci n if o
La construcci n if es similar a la existente en otros lenguajes de programaci n, aunque en C poo o see ciertas peculiaridades. El formato general de esta construcci n para decidir si una determinada o sentencia debe ejecutarse o no (alternativa simple) es el siguiente: if (condicin) o sentencia; La construcci n if puede escribirse tambi n de forma m s general para controlar la ejecuci n de un o e a o grupo de sentencias, de la siguiente manera:
4.1. Construcci n if o
24
Cierto
Condicin
Falso
Grupo de sentencias 1
Grupo de sentencias 2
(a)
(b)
El funcionamiento de la construcci n if es muy simple. En primer lugar se eval a la condici n, o u o que no es otra cosa que una expresi n de tipo entero. A continuaci n, si la expresi n se ha evaluado o o o como cierta, se ejecuta la sentencia o grupo de sentencias. En caso contrario la ejecuci n del programa o contin a por la siguiente sentencia en orden secuencial (ver Fig. 4.1 (a)). u El siguiente ejemplo muestra el uso de la construcci n if. El programa lee un n mero entero y lo o u transforma en el impar inmediatamente mayor, si es que no era ya impar. #include <stdio.h> void main()
*/
N tese que despu s de la condici n no se escribe ;. Escribir ; detr s de la condici n equivaldra o e o a o a que la construcci n if ejectutase un conjunto vaco de sentencias, lo cual no tiene ning n sentido. o u N tese, sin embargo, que tal hecho es v lido sint cticamente (no produce ning n error de compilaci n), o a a u o por lo que deber tenerse cuidado al escribir esta construcci n. Algo similar ocurre con los bucles a o for y while (ver Cap. 5).
25
4. Construcciones condicionales
f g f g
grupo de sentencias 1;
As pues, si la condici n es cierta se ejecutar la primera sentencia (el primer grupo de sentencias), y si o a es falsa se ejecutar la segunda sentencia (el segundo grupo). Ver gura 4.1 (b). a El siguiente programa muestra el uso de esta construcci n. El programa calcula el m ximo de dos o a n meros enteros: u #include <stdio.h> void main() int a, b, max; scanf( "%d %d", &a, &b ); if (a > b) max = a; else max = b; prinf( "El mximo es: %dnn", max ); a
Es importante destacar que la sentencia en la construcci n else es opcional, es decir, puede ser o nula. Ve moslo en el siguiente ejemplo que determina si un n mero es par: a u #include <stdio.h> void main()
int x;
4.1. Construcci n if o
26
El hecho de que la construcci n else sea opcional puede causar problemas de ambig edad al o u 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 este no a tenga ya asociada otra sentencia else. A continuaci n se muestran dos porciones de programa pr cticamente iguales, pero con comportao a mientos completamente diferentes. Se deja para el lector el an lisis de ambos casos. a . . . if (n > 0) if (a > b) z = a; else z = b; . . . . . . if (n > 0)
f g
if (a > b) z = a;
else z = b; . . .
f g f g f g f g
grupo de sentencias 1;
27
4. Construcciones condicionales
Las condiciones se eval an secuencialmente de arriba hacia abajo hasta encontrar una que d como u e resultado cierto. En ese punto, se ejecuta el grupo de sentencias correspondiente a dicha condici n. El o resto de condiciones y sentencias asociadas se ignoran. En caso de que ninguna de las condiciones se eval e cierta, se ejecutara el grupo de sentencias por defecto. Como en todos los casos anteriores, el u ultimo else es opcional. A continuaci n se muestra un ejemplo del uso de esta construcci n: o o #include <stdio.h> void main()
int hora; scanf( "%d", &hora ); if ((hora >= 0) && (hora < 12)) printf( "Buenos das" ); else if ((hora >= 12) && (hora < 18)) printf( "Buenas tardes" ); else if ((hora >= 18) && (hora < 24)) printf( "Buenas noches" ); else printf( "Hora no vlida" ); a
De manera que si la primera expresi n se eval a cierta, toda la expresi n toma el valor de la segunda o u o expresi n. En cambio, si la primera expresi n se eval a falsa, toda la expresi n toma el valor de la o o u o tercera expresi n. o Un ejemplo tpico del uso de este operador es el c lculo del m ximo de dos valores. En la siguiente a a sentencia, c toma el valor del m ximo entre la variable a y b. a c = (a > b) ? a : b;
Esto mismo podra haberse escrito usando la construcci n if-else como: o if (a > b) c = a; else c = b;
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: o printf("El mnimo es %d
nn",
((x < y) ?
x :
y) );
case constante 1 : grupo de sentencias break; case constante 2 : grupo de sentencias break; . . . case constante N : grupo de sentencias break; default : grupo de sentencias break;
1;
2;
N;
por defecto;
donde la expresi n debe ser de tipo entero o car cter, al igual que todas las constantes asociadas a cada o a etiqueta case. Es importante resaltar que no pueden usarse variables o expresiones en los distintos case, sino s lo constantes. o El funcionamiento de la construcci n switch es como sigue. En primer lugar se eval a la o u expresi n. Seguidamente su valor es comparado secuencialmente con el de las diferentes constantes en o los case. Si el valor de la expresi n coincide con alguna de ellas, se ejecuta el grupo de sentencias o correspondiente y switch concluye gracias a la sentencia break. En caso contrario, y si existe el caso default (que es opcional), se ejecutara el grupo de sentencias por defecto (ver Fig. 4.2). Cabe mencionar de forma especial, la sentencia break que volveremos a ver en captulos sucesi vos. En general, break se utiliza para nalizar de forma forzada la ejecuci n dentro de un bloque de o c digo, de manera que la siguiente sentencia a ejecutar ser la primera sentencia justo despu s de dicho o a e bloque. En la construcci n switch, break es necesario para concluir la ejecuci n del grupo de o o sentencias asociado al caso cuya constante coincide con el valor de la expresi n. As pues, la sentencia o a ejecutar despu s de break en un switch, ser la primera sentencia posterior a la llave g que e a cierra el switch.
29
4. Construcciones condicionales
Expresin
Grupo de sentencias 1
Grupo de sentencias 2
...
Grupo de sentencias N
Figura 4.2: Esquema de funcionamiento de switch La construcci n switch tambi n podra escribirse de forma equivalente mediante sentencias del o e tipo if-else-if, de la siguiente forma: if (expresin == constante 1) o
f g f f g f g
grupo de sentencias 1;
else if (expresin == constante 2) o grupo de sentencias 2; g. . . else if (expresin == constante N) o grupo de sentencias N;
que, como puede verse, es mucho m s ineciente en tiempo de ejecuci n, puesto que la expresi n debe a o o evaluarse repetidas veces, una para cada condici n. o El siguiente ejemplo muestra un programa que hace uso de switch para traducir a caracteres un dgito entre 1 y 5. #include <stdio.h> void main() int num; scanf( "%d", &num ); switch ( num )
30
f
case 1 : printf( "Uno.nn" ); break; case 2 : printf( "Dos.nn" ); break; . . . case 5 : printf( "Cinco.nn" ); break; default : printf( "El dgito est fuera de rango.nn" ); a break;
Finalmente, cabe decir que el grupo de sentencias asociado a un case puede ser vaco. 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() int num; scanf( "%d", &num ); switch ( num )
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( "Dgito no controlado.nn" ); break;
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 mnimo a de ellos. 2. Dado el siguiente programa, realizar un seguimiento de la ejecuci n en los siguientes supuestos: o (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() int a, b, c, d; scanf( "%d %d %d %d", &a, &b, &c, &d ); if ( ((a > 0) || (b > a)) && (c != d) )
f g f g g
a = c; b = 0;
else c += d; c = (c == 0) ? b = a + c + d;
(c + b) :
(c - a);
3. Escribir un programa que lea un valor entero y determine si es m ltiplo de 2 y de 5. u 4. Escribir un programa que muestre por pantalla la ecuaci n de una recta en un plano, Ax + By + o C = 0, leyendo previamente las coordenadas de dos de sus puntos (x1 , y1) y (x2 , y2). Recordar que:
A y ;y
=
2
y B y
=
x ;x ;x
2 1)
y ;y
2
1)
4.4. Ejercicios
32
33
5. Construcciones iterativas
Captulo 5
Construcciones iterativas
Hasta ahora hemos visto algunos aspectos b sicos del control del ujo de ejecuci n de un programa en a o C. Este captulo presenta los mecanismos que C ofrece para la ejecuci n repetida de sentencias, bien o un n mero prejado de veces, bien dependiendo de cierta condici n. Es decir, mecanismos para la u o creaci n de bucles de ejecuci n. o o C proporciona las siguientes construcciones iterativas: la construcci n while, o la construcci n do-while, y o la construcci n for. o
f g
grupo de sentencias;
El funcionamiento de esta construcci n es bastante simple. El cuerpo del bucle, es decir, la seno tencia o grupo de sentencias dentro del bucle, se ejecuta mientras el valor de la expresi n que act a de o u condici n sea cierto. En el momento en que la condici n sea falsa, la ejecuci n del programa contin a o o o u secuencialmente con la siguiente instrucci n tras el bucle (ver Fig. 5.1). o
34
Figura 5.1: Esquema de funcionamiento de while El siguiente ejemplo calcula la media de una secuencia de n meros enteros ledos por teclado acau bada en ;1: #include <stdio.h> void main()
int num, cont, suma; cont = 0; suma = 0; scanf( "%d", &num ); while (num != -1)
f g
if (cont != 0) printf( "La media es %dnn", sum/cont ); else printf( "La secuencia es vaca.nn" );
En la construcci n while la condici n se eval a al principio del bucle. Por ello, si cuando se alo o u canza el bucle por primera vez, la condici n es falsa, el cuerpo del bucle no se ejecuta nunca (imagnese o el caso, en el ejemplo anterior, en que el primer n mero de la secuencia sea ;1). Como consecuencia, u o 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 (imagnese o e o qu ocurrira si se elimina la sentencia scanf del cuerpo del bucle del ejemplo anterior). Por ello, e habitualmente las sentencias del cuerpo del bucle modican las variables que aparecen en la condici n, o de forma que esta sea falsa en alg n momento. u Por otra parte, la condici n del bucle (y esto es extensible a las diferentes construcciones repetitivas) o
35
5. Construcciones iterativas
Grupo de sentencias
Condicin 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 e o o o ejemplo, los siguiente bucles while (x--)
...
while (x = x+1); son perfectamente v lidos. En ambos casos el cuerpo del bucle se repetir mientras el valor de x sea a a o e distinto de 0. N tese que en el segundo caso el cuerpo del bucle es nulo, lo cual tambi n es posible.
f g
N tese que tanto para ejecutar una sola sentencia como para ejecutar un grupo de ellas, las llaves o f g son igualmente necesarias. Esta construcci n funciona de manera muy similar a la construcci n while. Sin embargo, al o o contrario que esta, do-while ejecuta primero el cuerpo del bucle y despu s eval a la condici n. Por e u o lo cual, el cuerpo del bucle se ejecuta como mnimo 1 vez (ver Fig. 5.2). El siguiente ejemplo cuenta el n mero de veces que aparece el n mero u u n meros enteros acabada en ;1: u #include <stdio.h> void main() int num, cont;
3
en una secuencia de
36
Sentencia inicial
Falso
Condicin Cierto
Grupo de sentencias
Incremento / Decremento
do
scanf( "%d", &num ); if (num == 3) cont++; g while (num != -1); printf( "El 3 ha aparecido %d vecesnn", cont );
Es importante destacar el uso de ; despu s de la condici n, a diferencia de en la construcci n e o o while , donde no se utiliza. Finalmente, cabe decir que tradicionalmente, tanto la construcci n while como la construcci n o o do-while se utilizan en bucles donde se desconoce a priori el n mero exacto de iteraciones. u
37
5. Construcciones iterativas
f g
grupo de sentencias;
La primera parte de la construcci n for acostumbra a ser una sentencia de asignaci n donde se o o inicializa alguna variable que controla el n mero de veces que debe ejecutarse el cuerpo del bucle. Esta u sentencia se ejecuta una sola ocasi n, antes de entrar por primera vez al cuerpo del bucle. o La segunda parte corresponde a la condici n que indica cu ndo naliza el bucle, de la misma forma o a que en las construcciones iterativas anteriores. En este caso, la condici n se eval a antes de ejecutar el o u cuerpo del bucle, por lo que al igual que en la construcci n while, el cuerpo puede ejecutarse entre 0 o y N veces, donde N depende de la condici n. o 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 e o bucle. La gura 5.3 muestra esquem ticamente el funcionamiento del bucle for. a El programa del siguiente ejemplo utiliza la construcci n for para calcular el sumatorio o #include <stdio.h> void main()
X
10
=1
f g g
Las tres partes de la construcci n for son opcionales, por lo que es posible omitir alguna o todas o ellas. En cualquier caso, los punto y coma (;) separadores son siempre necesarios. Un ejemplo cl sico a de este tipo de bucle for es el bucle innito (nunca concluye la ejecuci n): o for ( ; 1 ; )
f g
/* Grupo de sentencias */
Tradicionalmente la construcci n for se utiliza en bucles donde el n mero exacto de iteraciones o u es conocido a priori, y puede controlarse mediante una variable que act a como contador. u
38
f g
/* Grupo de sentencias */
As pues, las variables i y j se inicializan a 0 y 10, respectivamente, antes de comenzar la ejecuci n o del bucle. En la segunda parte de la construcci n, aparecen dos condiciones, i < 10 y j > 0. Si o alguna de ellas es falsa, la ejecuci n del bucle se detiene. Finalmente, tras ejecutarse el cuerpo del bucle, o i se incrementa en 1 y j se decrementa en 2, tras lo cual vuelven a comprobarse las condiciones, y as sucesivamente.
f g
sentencia; incremento/decremento;
break
Esta sentencia tiene una doble nalidad. Por un lado, indica el nal de un case en la construcci n o switch, como ya se vi en la secci n 4.3. Y por otro, para forzar la terminaci n inmediata de la ejecuo o o ci n de un bucle. De esta forma, se permite salir de la construcci n repetitiva ignorando la evaluaci n o o o de la condici n. Si bien su uso est reconocido como no muy elegante, permite en ocasiones escribir o a programas m s legibles y compactos. a
continue
Esta sentencia se utiliza unicamente en las construcciones repetitivas. Su funci n es la de evitar que se o ejecute todo el c digo a continuaci n de ella y hasta el nal del cuerpo del bucle, durante una iteraci n o o o determinada. El siguiente ejemplo pretende ilustrar el uso de estas sentencias:
39
5. Construcciones iterativas
do
f g f g
*/
if (num > 100) printf( "Valor demasiado grandenn" ); continue; /* No ejecutar el resto de sentencias e ir al final del bucle. */
5.5 Ejercicios
Se recomienda realizar los siguientes ejercicios utilizando las diferentes construcciones iterativas presentadas. 1. Escribir un programa que calcule la suma de los 20 primeros n meros m ltiplos de 5 o de 7. u u 2. Escribir un programa que calcule la potencia de un n mero entero, dado su valor y el del expou nente. 3. Escribir un programa que lea N n meros enteros y muestre el mayor y el menor de todos ellos. u 4. Escribir un programa que escriba la tabla de multiplicar de un n mero ledo por teclado. u 5. Escribir un programa que muestre la serie de Fibonacci hasta un lmite dado. Recordar que la serie de Fibonacci se dene como
= 1
= 1
Fi Fi;
=
Fi;
6. Escribir un programa que convierta un n mero entero positivo a cualquier base de numeraci n u o dada, igual o inferior a 10. 7. Escribir un programa que determine si un n mero entero dado es primo o no. u 8. Escribir un programa que calcule el factorial de un n mero entero ledo por teclado. u 9. Escribir un programa que calcule la suma de todos los n meros m ltiplos de 5 comprendidos u u entre dos enteros ledos por teclado. u 10. Escribir un programa que muestre los 15 primeros n meros de la serie de Fibonacci.
5.5. Ejercicios
40
41
Captulo 6
Donde se especica una lista de nombres de variables separados por comas. Los n meros enteros en C se expresan mediante una serie de dgitos, precedidos o no por un signo u o ;. Si el n mero es positivo, el signo + es opcional. Habitualmente se utiliza la notaci n decimal, u o aunque tambi n es posible usar notaci n octal y hexadecimal. En los n meros expresados en octal, se e o u utiliza un 0 como primer dgito, mientras que en los n meros expresados en hexadecimal, el n mero u u es precedido por un 0 y una equis may scula o min scula (0x o 0X). La tabla 6.1 muestra algunos u u ejemplos de representaci n de constantes enteras en los distintos formatos. o
+
42
Tabla 6.1: Representaci n de enteros en decimal, octal y hexadecimal o Decimal 27 4026 -149 Octal 033 07672 -0225 Hexadecimal 0X1B 0xFBA -0x95
6.1.1 Modicadores
C dene una serie de modicadores para el tipo de datos entero. El modicador short Este modicador se emplea para representar n meros enteros de 16 bits, independientemente del prou o cesador, por lo que su rango es ;32768 32767]. As pues, hay entornos de programaci n donde el tama o y rango de una variable short int coincide con el de int. Mientras que en otros entorn nos, una variable de tipo short int ocupa la mitad de espacio y tiene un rango mucho menor. La declaraci n de variables de este tipo tiene la forma: o short int O simplemente: short lista de variables; lista de variables;
No existe ninguna manera de especicar explcitamente constantes de tipo short int. El modicador long Este modicador se emplea para representar n meros enteros con un rango mayor al permitido por u int, por lo que tambi n ocupan m s espacio en memoria. Por lo tanto las variables del tipo long e a int pueden ocupar 32 o 64 bits seg n el entorno. Habitualmente son 64 bits, lo que da un rango de u representacion de ;9223372036854775808 9223372036854775807], esto es ;263 ;263 ; 1]. La declaraci n de variables es como sigue: o long int O simplemente: long lista de variables; lista de variables;
Para especicar explcitamente que una constante es de tipo long int, debe escribirse una letra ele (may scula o min scula), justo detr s del valor constante. Cabe decir, que esto s lo es necesario u u a o en caso que el valor de la constante que se desea especicar est dentro del rango de int. Es ree o comendable el uso de L, pues l puede confundirse con el dgito 1. A continuaci n se muestra un ejemplo:
43
long x; . . . x = -554L; x = 187387632; El modicador signed Es el modicador usado por defecto para todo dato de tipo int, por lo que no suele utilizarse de forma explcita. En cualquier caso, las declaraciones tiene la forma: signed int O simplemente: int lista de variables; lista de variables;
El modicador unsigned Este modicador permite especicar n meros enteros sin signo. Como consecuencia de eliminar el u signo de la representaci n, el rango de valores positivos se amplia hasta 0 65535] si se emplean 16 o o bits, o 0 4294967295] si se emplean 32 bits. La declaraci n de variables se realiza como: unsigned int O simplemente: unsigned lista de variables; lista de variables;
Pueden especicarse constantes de tipo unsigned, escribiendo una letra u may scula justo detr s u a del valor constante, como en: unsigned x; . . . x = 45728; x = 345U; Finalmente, cabe comentar que el modicador unsigned puede combinarse con short y long. Por ejemplo, los datos de tipo unsigned long tienen un rango v lido de 0 264 ; 1], es a decir 0 18446744073709551615].
6.1.2 Resumen
La tabla 6.2 resume los distintos tipos de datos enteros que se han presentado, mostrando su rango, ocupaci n de memoria en bits y el modicador de formato para printf y scanf. o
6.2. Caracteres
44
Tabla 6.2: Resumen de tipos de datos enteros Tipo int unsigned int short int unsigned short int long int unsigned long int
Rango
2147483648 2147483647]
2
64
63
0 2
;
1]
1]
6.2 Caracteres
Este tipo de datos se identica con la palabra reservada char. Comprende un conjunto ordenado y nito de caracteres, representados mediante c digos num ricos. La codicaci n m s extendida es el o e o a est ndar ASCII que utiliza 8 bits. As pues, el tipo de datos char es internamente tratado como a un entero, aunque puede manipularse como un car cter propiamente. El ap ndice D muestra la tabla a e completa de c digos ASCII y los caracteres asociados. La declaraci n de variables se realiza como: o o char lista de variables;
La forma habitual de expresar una constante de tipo char es mediante el car cter correspondiente a entre comillas: char x, y; . . . x = f; y = ?; x = 5; Debido a la representaci n interna de los caracteres mediante n meros enteros, pueden realizarse o u operaciones aritm ticas como: F+5 , o lo que es lo mismo, 70 + 53 = 123, que equivale al e a car cter f; o como F+5, esto es 70 + 5 = 75 que equivale al car cter K. a Por la misma raz n tambi n se establece un orden entre los caracteres, lo que permite usar los o e operadores relacionales habituales. As pues, la comparaci n a <= h da como resultado el o valor cierto, o la comparaci n K > V da como resultado el valor falso. o
45
Tabla 6.3: Caracteres interpretados como enteros Tipo char signed char unsigned char
; ;
Rango
128 127] 128 127] 0 255]
Formato %c %d %u
na nn nf nr nt n n" n0
6.2.2 Enteros y el tipo char
C digo o
Signicado pitido salto de lnea salto de p gina a principio de la misma lnea tabulador ap strofe o comillas car cter nulo a
Puesto que el tipo char est internamente representado mediante c digos num ricos de 8 bits, una a o e variable de este tipo puede interpretarse tambi n como una variable entera. As pues, pueden utilizarse e tambi n los modicadores signed y unsigned. Los rangos de valores se muestran en la tabla 6.3. e
46
Tabla 6.4: Resumen de tipos de datos reales Tipo float double long double
1 1754945
2 225073858507202
1 797693134862312 8 4
: :
3 4028232
: :
Rango
5 9
: :::E ; : :::E
E; E E; E
38 + 38 4932
Tama no 4 bytes
308
8 bytes 16 bytes
+ 308
+ 4932
Si i es una variable entera con un valor en el rango pondiente de la siguiente forma: c = (char)((int)0 + i);
0 9]
1 X
i
=1
, mientras que
1000
. Expresando el error
47
k X i
=1
i ;
k; X i
=1
i j<
A continuaci n se muestra un programa que realiza dicho c lculo: o a #include <stdio.h> #define LIMITE 1000 void main ()
int i, fin; float suma, t, epsilon; suma = 0.0F; fin = 0; i = 1; scanf( "%f", &epsilon ); while (!fin)
g g
t = 1.0F / (float)i; suma = suma + t; i++; fin = ((t < epsilon) || (i > LIMITE));
Obs rvese el uso del modicador de formato %f para la entrada y salida de valores de coma otante, e y el uso de la variable fin para controlar la terminaci n del bucle. o En el chero de cabeceras math.h (#include <math.h>), perteneciente a la librera est ndar a (ver Ap. B), se denen una serie de funciones matem ticas para operar con valores reales, como: a sqrt para la raz cuadrada, sin y cos para senos y cosenos, etc.
6.4 Ejercicios
1. Escribir un programa que cuente el n mero de veces que aparece una letra en una secuencia de u caracteres acabada en . . 2. Escribir un programa que lea un car cter de teclado e informe de si es alfab tico, num rico, a e e blanco o un signo de puntuaci n. o 3. Escribir un programa que convierta una secuencia de dgitos entrados por teclado al n mero ente u ro correspondiente. Sup ngase que el primer dgito ledo es el de mayor peso. Obs rvese tambi n o e e que el peso efectivo de cada dgito ledo es desconocido hasta haber concluido la introducci n de o la secuencia.
6.4. Ejercicios
48
4. Sean las variables enteras i y j con valores 5 y 7, respectivamente. Y las variables de a a coma otante f y g con valores 5:5 y ;3:25, respectivamente. Cu l ser el resultado de las siguientes asignaciones? (a) i = i % 5; (b) f = (f - g) / 2; (c) j = j * (i - 3); (d) f = f % g; (e) i = i / (j - 2); 5. Escribir un programa que calcule y muestre por pantalla las races de una ecuaci n de segundo o 2 grado, leyendo previamente los coecientes A, B y C de la misma: Ax + Bx + C = 0. 6. Escribir un programa que calcule el permetro de una circunferencia y que lo muestre por pantalla con cuatro decimales de precisi n. Si el radio introducido por el usuario es negativo, el permetro o resultante ser 0. a 7. Escribir un programa para calcular de forma aproximada el n mero e. Recordar que u
1 X
i
=0
1 !
49
Captulo 7
7.1 Vectores
Los vectores, tambi n llamados tablas unidimensionales, son estructuras de datos caracterizadas por: e Una colecci n de datos del mismo tipo. o Referenciados mediante un mismo nombre. Almacenados en posiciones de memoria fsicamente contiguas, de forma que, la direcci n de o memoria m s baja corresponde a la del primer elemento, y la direcci n de memoria m s alta a o a corresponde a la del ultimo elemento. El formato general para la declaraci n de una variable de tipo vector es el siguiente: o tipo de datos nombre tabla [tamao]; n donde: tipo de datos indica el tipo de los datos almacenados por el vector. Recordemos que todos los elementos del vector son forzosamente del mismo tipo. Debe aparecer necesariamente en la declaraci n, puesto que de ella depende el espacio de memoria que se reservar para almacenar o a el vector.
7.1. Vectores
50
N-1
...
Primer elemento del vector ltimo elemento del vector
Figura 7.1: Representaci n gr ca de un vector de N elementos o a nombre tabla es un identicador que usaremos para referiremos tanto al vector como un todo, como a cada uno de sus elementos. tamao es una expresi n entera constante que indica el n mero de elementos que contendr el n o u a vector. El espacio ocupado por un vector en memoria se obtiene como el producto del n mero de u elementos que lo componen y el tama o de cada uno de estos. n
7.1.1 Consulta
El acceso a un elemento de un vector se realiza mediante el nombre de este y un ndice entre corche tes ([ ]). El ndice representa la posici n relativa que ocupa dicho elemento dentro del vector y se o especica mediante una expresi n entera (normalmente una constante o una variable). Formalmente: o nombre vector[ndice]; A continuaci n se muestran algunas formas v lidas de acceso a los elementos de un vector: o a int contador[10]; int i, j, x; . . . x = contador[1]; x = contador[i]; x = contador[i * 2 + j]; . . . Como muestra la gura 7.1, el primer elemento de un vector en C se sit a en la posici n 0, mientras u o u que el ultimo lo hace en la posici n N ; 1 (N indica el n mero de elementos del vector). Por esta o raz n, el ndice para acceder a los elementos del vector debe permanecer entre estos dos valores. Es o responsabilidad del programador garantizar este hecho, para no acceder a posiciones de memoria fuera del vector, lo cual producira errores imprevisibles en el programa.
7.1.2 Asignaci n o
La asignaci n de valores a los elementos de un vector se realiza de forma similar a como se consultan. o Ve moslo en un ejemplo: a
51
int contador[3]; . . . contador[0] = 24; contador[1] = 12; contador[2] = 6; En muchas ocasiones, antes de usar un vector (una tabla en general) por primera vez, es necesario dar a sus elementos un valor inicial. La manera m s habitual de inicializar un vector en tiempo de a ejecuci n consiste en recorrer secuencialmente todos sus elementos y darles el valor inicial que les o corresponda. Ve moslo en el siguiente ejemplo, donde todos los elementos de un vector de n meros a u enteros toman el valor 0: #define TAM 100 void main()
N tese que el bucle recorre los elementos del vector empezando por el elemento 0 (i=0) y hasta el o elemento TAM-1 (condici n i<TAM). o Existe tambi n un mecanismo que permite asignar un valor a todos los elementos de un tabla con e una sola sentencia. Concretamente en la propia declaraci n de la tabla. La forma general de inicializar o una tabla de cualquier n mero de dimensiones es la siguiente: u tipo de datos nombre tabla [tam1]...[tamN] =
lista valores
g;
La lista de valores no deber contener nunca m s valores de los que puedan almacenarse en la tabla. a a Veamos algunos ejemplos: int contador[3] = f24, 12, 6g; /* Correcto */ char vocales[5] = fa, e, i, o, ug; /* Correcto */ int v[4] = f2, 6, 8, 9, 10, 38g; /* Incorrecto */ Finalmente, cabe destacar que no est permitido en ning n caso comparar dos vectores (en general a u dos tablas de cualquier n mero de dimensiones) utilizando los operadores relacionales que vimos en la u secci n 3.4.3. Tampoco est permitida la copia de toda una tabla en otra con una simple asignaci n. Si o a o se desea comparar o copiar toda la informaci n almacenada en dos tablas, deber hacerse elemento a o a elemento. Los mecanismos de acceso descritos en esta secci n son id nticos para las matrices y las tablas mulo e tidimensionales. La unica diferencia es que ser necesario especicar tantos ndices como dimensiones a posea la tabla a la que se desea acceder. Esto lo veremos en las siguientes secciones.
7.1. Vectores
52
7.1.3 Ejemplos
A continuaci n se muestra un programa que cuenta el n mero de apariciones de los n meros 0, 1, 2 y o u u 3 en una secuencia de enteros acabada en ;1.
#include <stdio.h> void main () int num, c0=0, c1=0, c2=0, c3=0; scanf("%d", &num); while (num != -1)
g g
if (num == 0) c0++; if (num == 1) c1++; if (num == 2) c2++; if (num == 3) c3++; scanf( "%d", &num );
Qu ocurrira si tuvi semos que contar las apariciones de los cien primeros n meros enteros? e e u Deberamos declarar cien contadores y escribir cien construcciones if para cada caso? La respuesta, como era de esperar, se halla en el uso de vectores. Ve moslo en el siguiente programa: a #include <stdio.h> #define MAX 100 void main () int i, num, cont[MAX]; for (i= 0; i< MAX; i++) cont[i] = 0; scanf("%d", &num); while (num != -1) f if ((num >= 0) && (num <= MAX)) cont[num]++; scanf( "%d", &num );
g g
for (i= 0; i< MAX; i++) printf( "Contador [%d] = %dnn", i, cont[i] );
Veamos nalmente otro ejemplo en el que se muestra c mo normalizar un vector de n meros reales. o u La normalizaci n consiste en dividir todos los elementos del vector por la raz cuadrada de la suma de o sus cuadrados. Destaca el uso de la constante MAX para denir el n mero de elementos del vector y u de la funci n sqrt para el c lculo de races cuadradas. o a
53
float vector[MAX], modulo; int i; /* Lectura del vector. */ for (i= 0; i< MAX; i++) scanf("%f", &vector[i]); /* Calcular mdulo. */ o modulo = 0.0; for (i= 0; i< MAX; i++) modulo = modulo + (vector[i] * vector[i]); modulo = sqrt(modulo); /* Normalizar */ for (i= 0; i< MAX; i++) vector[i] /= modulo; /* Escritura del vector. */ for (i= 0; i< MAX; i++ ) printf( "%f ", vector[i] );
7.2 Matrices
Las matrices, tambi n llamadas tablas bidimensionales, no son otra cosa que vectores con dos dimene siones. Por lo que los conceptos de acceso, inicializaci n, etc. son similares. o La declaraci n de una variable matriz tiene la forma siguiente: o tipo de datos nombre tabla [tamao dim1][tamao dim2]; n n Donde tamao dim1 y tamao dim2 indican, respectivamente, el n mero de las y de n n u columnas que componen la matriz. La gura 7.2 muestra la representaci n gr ca habitual de una o a matriz de datos. Otro hecho importante es que las matrices en C se almacenan por las. Es decir, que los elementos de cada la se sit an en memoria de forma contigua. As pues, en la matriz de la gura anterior, el u primer elemento almacenado en memoria es el (0,0), el segundo el (0,1), el tercero el (0,2), . . . , (0,M-1), despu s (1,0), y as sucesivamente hasta el ultimo elemento, es decir (N-1,M-1). e
7.2. Matrices
54
M-1
N-1
7.2.1 Consulta
El acceso a un elemento de una matriz se realiza mediante el nombre de esta y dos ndices (uno para cada dimensi n) entre corchetes. El primer ndice representa la la y el segundo la columna en que se o encuentra dicho elemento. Tal como muestra la gura 7.2, el ndice de las las tomar valores entre 0 y a a u el n mero de las menos 1, mientras que el ndice de las columnas tomar valores entre 0 y el n mero u de columnas menos 1. Es responsabilidad del programador garantizar este hecho. nombre matriz[ndice 1][ndice 2];
7.2.2 Asignaci n o
Comentaremos unicamente la inicializaci n de una matriz en la propia declaraci n. El siguiente ejemo o plo declara una matriz de tres las y cuatro columnas y la inicializa. Por claridad, se ha usado identaci n o en los datos, aunque hubiesen podido escribirse todos en una sola lnea. int mat[3][4] =
24, 12, 6, 17, 15, 28, 78, 32, 0, 44, 3200 , -34 g;
La inicializaci n de matrices, y en general de tablas multidimensionales, puede expresarse de forma o m s clara agrupando los valores mediante llaves (f g), siguiendo la estructura de la matriz. As pues, a el ejemplo anterior tambi n podra escribirse como: e int mat[3][4] =
ff f f g;
55
7.2.3 Ejemplo
El siguiente ejemplo calcula la matriz traspuesta de una matriz de enteros. La matriz tendr unas a dimensiones m ximas seg n la constante MAX. a u #include <stdio.h> #define MAX 20 void main()
int filas, columnas, i, j; int matriz[MAX][MAX], matrizT[MAX][MAX]; /* Lectura matriz */ printf( "Num. filas, Num. columnas: " ); scanf( "%d%d", &filas, &columnas ); printf ("Introducir matriz por filas:" ); for (i= 0; i< filas; i++) for (j= 0; j< columnas; j++)
f g
/* Traspuesta */ for (i= 0; i< filas; i++) for (j= 0; j< columnas; j++) matrizT[j][i] = matriz[i][j]; /* Escritura del resultado */ for (i= 0; i< filas; i++) for (j= 0; j< columnas; j++) printf( "nnmatrizT[%d][%d] = %d ", i, j, matrizT[i][j] );
Obs rvese que para recorrer todos los elementos de una matriz es necesario el empleo de dos bucles e u anidados que controlen los ndices de las y columnas (siempre entre 0 y el n mero de las o columnas menos 1). En este ejemplo todos los recorridos se realizan por las, es decir, que primero se visitan todos los elementos de una la, luego los de la siguiente, etc. Finalmente, cabe comentar que para el recorrido de tablas multidimensionales ser necesario el empleo de tantos bucles como dimensiones a tenga la tabla.
56
M-1
... ... ... ... ... ... ... ... ... ... ... ... ...
0 1 2 3
...
ltimo elemento de la tabla 0
N-1
tipo de datos nombre tabla [tamao dim1] ...[tamao dimN]; n n Para acceder a un elemento en particular ser necesario usar tantos ndices como dimensiones: a nombre vector[ndice 1] ...[ndice N]; Aunque pueden denirse tablas de m s de tres dimensiones, no es muy habitual hacerlo. La gua ra 7.3 muestra como ejemplo la representaci n gr ca habitual de una tabla de tres dimensiones. o a
7.3.1 Ejemplo
El siguiente ejemplo muestra el empleo de una tabla multidimensional. Concretamente, el programa utiliza una tabla de 3 dimensiones para almacenar 1000 n meros aleatorios. Para generar n meros u u aleatorios se usa la funci n rand de la librera est ndar stdlib.h. Tambi n se ha usado la funci n o a e o getchar (stdio.h), que interrumpe el programa y espera a que el usuario presione una tecla. #include <stdio.h> #include <stdlib.h> #define DIM 10 void main()
57
for (b= 0; b< DIM; b++) for (c= 0; c< DIM; c++) tabla random[a][b][c] = rand(); /* Muestra series de DIM en DIM elementos. for (a= 0; a< DIM; a++) for (b= 0; b < DIM; b++) */
f g g g
c a s
c o \0
En C pueden denirse constantes correspondientes a cadenas de caracteres. Se usan comillas dobles para delimitar el principio y el nal de la cadena, a diferencia de las comillas simples empleadas con las constantes de tipo car cter. Por ejemplo, la cadena constante "H" tiene muy poco que ver con el a car cter constante H, si observamos la representaci n interna de ambos: a o
"H"
H \0
7.4.1 Asignaci n o
Mientras que la consulta de elementos de una cadena de caracteres se realiza de la misma forma que con los vectores, las asignaciones tienen ciertas peculiaridades.
58
Como en toda tabla, puede asignarse cada car cter de la cadena individualmente. No deber olvia a darse en ning n caso que el ultimo car cter v lido de la misma debe ser el car cter nulo (n0). El u a a a siguiente ejemplo inicializa la cadena de caracteres cadena con la palabra "casco". N tese que o las tres ultimas posiciones del vector no se han usado. Es m s, aunque se les hubiese asignado alg n a u car cter, su contenido sera ignorado. Esto es, el contenido del vector en las posiciones posteriores al a car cter nulo es ignorado. a char cadena[10]; . . . cadena[0] cadena[1] cadena[2] cadena[3] cadena[4] cadena[5]
= = = = = =
c; a; s; c; o; n0;
La inicializaci n de una cadena de caracteres durante la declaraci n puede hacerse del mismo modo o o que en los vectores, aunque no debe olvidarse incluir el car cter nulo al nal de la cadena. Sin embargo, a existe un m todo de inicializaci n propio de las cadena de caracteres, cuyo formato general es: e o char nombre [tamao] = "cadena"; n Usando este tipo de inicializaci n, el car cter nulo es a adido autom ticamente al nal de la cadena. o a n a As pues, una inicializaci n tpica de vectores como la siguiente: o char nombre[10] =
N, U, R, I, A, n0
g;
puede hacerse tambi n de forma equivalente como: e char nombre[10] = "NURIA"; Finalmente, la inicializaci n anterior puede hacerse sin necesidad de especicar el tama o del veco n tor correspondiente. En este caso, el compilador se encarga de calcularlo autom ticamente, reservando a espacio de memoria suciente para almacenar la cadena, incluyendo el car cter nulo al nal. As pues, a la siguiente declaraci n reserva memoria para almacenar 6 caracteres y los inicializa adecuadamente o con las letras de la palabra NURIA: char nombre[] = "NURIA"; La cadena vaca Otra curiosidad de las cadenas de caracteres se reere a la cadena vaca, " " , que consta unicamente del car cter nulo. Puesto que los caracteres posteriores al car cter nulo son ignorados, convertir una a a cadena con cualquier valor almacenado a la cadena vaca es tan simple como asignar el car cter nulo a a la posici n 0 de dicha cadena. He aqu un ejemplo: o char cadena [12] = "Una frase"; . . . cadena[0] = n0; /* Ahora es una cadena vaca */
59
10
11
"Una frase" U n a
\0
cadena[0] = \0;
0 1 2 3 4 5 6 7 8 9 10 11
"" \0 n a
\0
60
7.4.3 Ejemplos
Para nalizar, veamos un par de ejemplos de manejo de cadenas de caracteres. El siguiente programa cuenta el n mero de veces que se repite una palabra en una frase. El programa u emplea la funci n de comparaci n de cadenas strcmp. Dicha funci n devuelve 0 en caso de que las o o o cadenas comparadas sean iguales (ver Sec. B.1). #include <stdio.h> #include <string.h> #define MAXLIN 100 void main() char pal[MAXLIN]; char palfrase[MAXLIN]; char c; int total = 0; printf( "nnPALABRA:" ); scanf( "%s", pal ); printf( "nnFRASE:" ); c = ; while (c != nn) /* La que buscamos. */ /* Una palabra de la frase.
*/
f g g
A continuaci n se muestra otro ejemplo que hace uso de las cadenas de caracteres. El programa lee o dos cadenas de caracteres, las concatena, convierte las letras min sculas en may sculas y viceversa, y u u nalmente escribe la cadena resultante. #include <stdio.h> #include <string.h> void main() char cad1[80], cad2[80], cad3[160]; int i, delta; printf( "Introduzca la primera cadena:nn" ); gets(cad1); printf( "Introduzca la segunda cadena:nn" ); gets( cad2 ); /* cad3 = cad1 + cad2 */ strcpy( cad3, cad1 );
61
g g
if ((cad3[i] >= a) cad3[i] -= delta; else if ((cad3[i] >= cad3[i] += delta; i++;
&& (cad3[i] <= z)) /* Convierte a mayscula */ u A) && (cad3[i] <= Z)) /* Convierte a minscula */ u
%s
nn",
cad3 );
7.5 Ejercicios
1. D nde est el error en el siguiente programa? o a void main()
int vector [10]; int x = 1; for (x= 1; x<= 10; x++) vector[x] = 23;
2. Escribir un programa que lea del teclado un vector de 10 n meros enteros, lo invierta y nalmente u lo muestre de nuevo. 3. Escribir un programa que cuente el n mero de palabras de m s de cuatro caracteres en una frase. u a se almacena en forma de vector cuyo ultimo elemento es el car cter .. Esta a 4. Escribir un programa que lea del teclado dos n meros enteros de hasta 20 dgitos y los sume. u Usar vectores para almacenar los dgitos de cada n mero. u 5. Escribir un programa que decida si una palabra es palndroma o no. La palabra se almacena en un vector de caracteres acabado en .. 6. Escribir un programa para calcular la moda de un conjunto de n meros enteros. La moda es el u valor que se repite m s veces. a 7. D nde est el error en el siguiente programa? o a void main()
7.5. Ejercicios
62
for (x= 0; x< 3; x++) for (y= 0; y< 10; y++) matrix[x][y] = 0;
8. Escribir un programa que inicialice cada elemento de una matriz de enteros con el valor de la suma del n mero de la y columna en que est situado. u a 9. Escribir un programa que calcule la suma de dos matrices de enteros. 10. Escribir un programa que calcule los puntos de silla de una matriz de enteros. Un elemento de una matriz es un punto de silla si es el mnimo de su la y el m ximo de su columna. a 11. Escribir un programa que determine si una matriz es sim trica. e 12. Escribir un programa que multiplique dos matrices. 13. Escribir un programa que lea una frase del teclado y cuente los espacios en blanco. 14. Escribir un programa que, dada una cadena de caracteres y un entero correspondiente a una posici n v lida dentro de ella, genere una nueva cadena de caracteres que contenga todos los o a caracteres a la izquierda de dicha posici n, pero en orden inverso. o 15. Escribir un programa que, dada una cadena de caracteres, la limpie de caracteres blancos. Por ejemplo, la cadena "Esto es una frase" deber transformarse en "Estoesunafrase". a Escribir dos versiones, una utilizando una cadena auxiliar y otra versi n que realice los cambios o sobre la misma cadena. 16. Escribir un programa que lea dos cadenas de caracteres, las compare e informe de si son iguales o diferentes. No usar la funci n de la librera est ndar strcmp. o a
63
Captulo 8
8.1 Estructuras
En el captulo 7 hemos estudiado el tipo abstracto de datos tabla, formado por un conjunto de elementos todos ellos del mismo tipo de datos. En una estructura, sin embargo, los elementos que la componen pueden ser de distintos tipos. As pues, una estructura puede agrupar datos de tipo car cter, enteros, a cadenas de caracteres, matrices de n meros . . . , e incluso otras estructuras. En cualquier caso, es u habitual que todos los elementos agrupados en una estructura tengan alguna relaci n entre ellos. En o adelante nos referiremos a los elementos que componen una estructura como campos. La denici n de una estructura requiere especicar un nombre y el tipo de datos de todos los campos o que la componen, as como un nombre mediante el cual pueda identicarse toda la agrupaci n. Para o ello se utiliza la palabra reservada struct de la forma siguiente: struct nombre estructura
g;
El siguiente ejemplo dene una estructura denominada cliente en la que puede almacenarse la cha bancaria de una persona. El signicado de los diferentes campos es obvio:
8.1. Estructuras
64
Puede decirse que la denici n de una estructura corresponde a la denici n de una plantilla o o gen rica que se utilizar posteriormente para declarar variables. Por tanto la denici n de una estructura e a o no representa la reserva de ning n espacio de memoria. u
tipo campo 1 nombre campo 1; nombre campo 2; tipo campo 2 . . . nombre campo N; tipo campo N lista de variables;
N tese que al declararse las variables al mismo tiempo que se dene la estructura, el nombre de esta o junto a la palabra reservada struct se hace innecesario y puede omitirse. Por otra parte, suponiendo que la estructura nombre estructura se haya denido previamente, la declaraci n de variables en el segundo caso sera: o struct nombre estructura lista de variables; Siguiendo con el ejemplo anterior, seg n la primera variante de declaraci n, podramos escribir: u o
struct char nombre[100]; long int dni; char domicilio[200]; long int no cuenta; float saldo; antiguo cliente, nuevo cliente;
O bien, de acuerdo con la segunda variante donde se asume que la estructura cliente se ha denido previamente: struct cliente antiguo cliente, nuevo cliente;
65
8.1.3 Asignaci n o
La asignaci n de valores a los campos de una variable estructura puede realizarse de dos formas difeo rentes. Por un lado, accediendo campo a campo, y por otro, asignando valores para todos los campos en el momento de la declaraci n de la variable. o A continuaci n se muestran algunas posibles maneras de asignar datos a los campos individuales o de la variable nuevo cliente declarada previamente. Como puede verse, cada campo es tratado como si de una variable se tratase, con lo que pueden usarse funciones como strcpy para copiar una cadena de caracteres sobre un campo de ese tipo, o gets para leerla del teclado, etc. strcpy( nuevo cliente.nombre, "Federico Sancho Buch" ); nuevo cliente.dni = 23347098; gets( nuevo cliente.domicilio ); scanf( "%ld",&nuevo cliente.no cuenta ); nuevo cliente.saldo += 10000.0; Por otra parte, el siguiente ejemplo muestra la inicializaci n completa de todos los campos de una o variable de tipo estructura en el momento de su declaraci n: o struct cliente nuevo cliente =
f
"Federico Sancho Buch", 23347098, "Rue del Percebe 13 - Madrid", 7897894, 1023459.34
g;
Finalmente, tambi n es posible copiar todos los datos de una variable estructura a otra variable e estructura del mismo tipo mediante una simple asignaci n: o nuevo cliente = antiguo cliente; No est permitido en ning n caso, comparar dos estructuras utilizando los operadores relacionales que a u vimos en la secci n 3.4.3. o
8.2. Uniones
66
8.1.4 Ejemplo
El siguiente programa dene las estructuras de datos para gestionar un conjunto de chas personales. N tese el uso de estructuras anidadas, as como el uso de tablas de estructuras. N tese tambi n el uso o o e de la funci n gets para leer cadenas de caracteres. Se deja como ejercicio para el lector escribir este o mismo programa usando scanf en lugar de gets para leer dichas cadenas. #include <stdio.h> struct datos char nombre[20]; char apellido[20]; long int dni;
g; f
g; f
void main() int i; struct tablapersonas tabla; printf( "Nmero de personas: " ); u scanf( "%d", &tabla.numpersonas ); for (i= 0; i< tabla.numpersonas; i++)
printf( "nnNombre: " ); gets( tabla.persona[i].nombre ); printf( "nnApellido: " ); gets( tabla.persona[i].apellido ); printf( "nnDNI: " ); scanf( "%ld", &tabla.persona[i].dni );
8.2 Uniones
Al igual que las estructuras, las uniones tambi n pueden contener m ltiples campos de diferentes tipos e u de datos. Sin embargo, mientras que cada campo en una estructura posee su propia area de almacena miento, en una uni n, todos los campos que la componen se hallan almacenados en la misma area de o memoria. El espacio de memoria reservado por el compilador corresponde al del campo de la uni n o que requiere mayor espacio de almacenamiento. El resto de campos comparten dicho espacio. As pues, los campos de una uni n est n solapados en memoria, por lo que, en un momento dado de o a la ejecuci n del programa, s lo podr haber almacenado un dato en uno de los campos. Es responsabilio o a
67
dad del programador hacer que el programa mantenga control sobre qu campo contiene la informaci n e o almacenada en la uni n en cada momento. Intentar acceder al tipo de informaci n equivocado puede o o producir resultados sin sentido. La denici n de una uni n es muy similar a la de una estructura, excepto por el uso de la palabra o o reservada union: union nombre union
g;
tipo campo 1 nombre campo 1; tipo campo 2 nombre campo 2; . . . tipo campo N nombre campo N;
Tanto la declaraci n de variables de tipo uni n como el acceso a los campos se expresa de forma o o similar a como se mostr en las estructuras. o Finalmente, diremos que una estructura puede ser el campo de una uni n y viceversa. Igualmente, o pueden denirse tablas de uniones, etc.
8.2.1 Ejemplo
El siguiente ejemplo dene tres estructuras para almacenar la informaci n asociada a tres tipos difereno tes de m quinas voladoras (jet, helicoptero y carguero). Finalmente dene una estructura a gen rica que puede contener, alternativamente, los datos de cualquiera de ellos (un aeroplano). e Para controlar de qu tipo es el objeto almacenado en la uni n datos, se utiliza la variable tipo. e o
g; f
g; f
g; f
union aeroplano struct jet jet u; struct helicoptero helicopter u; struct carguero carguero u;
g;
68
struct un aeroplano
g;
El siguiente ejemplo dene una enumeraci n denominada dia semana, cuyo signicado es obo vio. enum dia semana
Las constantes que denen los posibles valores de la enumeraci n son representadas internamente o como constantes enteras. El compilador asigna a cada una de ellas un valor en funci n del orden o o (empezando por 0) en que aparecen en la denici n. As pues, en el ejemplo anterior tenemos que e o LUNES es 0, MARTES es 1, etc. Sin embargo, podra ser de inter s modicar dicha asignaci n por defecto, asignando a las constantes otro valor num rico. Ver el siguiente ejemplo: e enum dia semana
Tambi n puede asignarse el mismo valor num rico a diferentes constantes, as como dejar que el e e compilador numere algunas por defecto. Ver el siguiente ejemplo: enum dia semana
69
La declaraci n de variables puede hacerse de dos formas diferentes. Bien en la misma denici n o o de la enumeraci n, bien posteriormente. Por su similitud con la declaraci n de variables en el caso de o o estructuras y uniones, veremos simplemente un ejemplo del segundo caso. El mismo c digo muestra o algunos ejemplos de utilizaci n de las constantes denidas en un tipo enumerado. o enum dia semana
void main() enum dia semana dia; . . . if (dia <= VIERNES) printf( "Es laborable" ); . . . dia = MARTES; /* Muestra el valor de dia */ printf( "Hoy es: %d", dia ); . . .
typedef float Testatura; typedef char Tstring [30]; typedef enum Tsexo =
HOMBRE, MUJER
g;
70
typedef struct
f g f
void main() Tpersona paciente; . . . scanf( "%f", &paciente.alt ); gets( paciente.nombre ); printf( "%s", paciente.apellido ); paciente.sex = MUJER;
unsigned short a; a= 0x5b3c; /* a = /* b = b= a; printf( " a= %x b= printf( " a= %u b= printf( " a= %d b= 0101 1011 1010 0100 %xnn", a, %unn", a, %dnn", a, 0011 1100 */ 1100 0011 */ b ); b ); b );
71
Tabla 8.1: Tabla de verdad de los operadores l gicos o x 0 0 1 1 y 0 1 0 1 AND (&) 0 0 0 1 OR ( j ) 0 1 1 1 XOR (b) 0 1 1 0
mostrara en pantalla los siguientes mensajes: a= 0x5b3c a= 23356 a= 23356 b= 0xa4c3 b= 42179 b= -23357
8.6. Ejercicios
72
unsigned short a, d, e; short b, c, f, g; a= b= c= d= e= f= g= 28; -28; 3; a << a >> b << b >> /* /* /* /* /* /* /* a b c d e f g = = = = = = = 0000 1111 0000 0000 0000 1111 1111 0000 1111 0000 0000 0000 1111 1111 0001 1110 0000 1110 0000 0010 1111 1100 0100 0011 0000 0011 0000 1100 */ */ */ = 224 */ = 3 */ = -224 */ = -3 */
c; c; c; c;
Es importante se alar que los operadores de desplazamiento tienen un signicado aritm tico en n e base 10. El desplazamiento a la derecha de 1 bit es equivalente a dividir por 2, obteni ndose el cociente e de la division entera. El desplazamiento a la izquierda de 1 bit es equivalente a multiplicar por 2. Por lo tanto, cuando trabajemos con variables enteras: var << n equivale a var * 2n , y var >> n equivale a var / 2n .
8.6 Ejercicios
1. Escribir un programa que, dadas dos fechas, indique cu l de las dos es anterior a la otra. Para a ello, el programa deber denir una estructura para el tipo de datos fecha (da, mes y a o). a n 2. Denir una estructura de datos para representar polinomios de hasta grado 10. Escribir un programa capaz de sumar dos polinomios expresados con dicha estructura. 3. Dada la siguiente denici n de tipos de datos: o typedef char Tstring [50]; typedef struct
f g
Escribir un programa que dena un vector de N agentes, permita la introducci n de los datos por o teclado y, nalmente, muestre los datos del agente con la m xima media de ventas. a 4. Dada la siguiente denici n de tipo de datos: o typedef struct
f g
73
escribir un programa que dena un vector con los datos de N pases y permita introducirlos por teclado. Finalmente, el programa deber mostrar el nombre de los pases cuya temperatura a mnima sea inferior a la media de las temperaturas mnimas. 5. Dada la siguiente denici n de tipos de datos: o typedef char Tstring [50]; typedef struct
f g f
typedef struct Tnif nif; Tstring nombre; Tstring direc; long int telf; Tempresa;
escribir un programa que dena un vector con los datos de N empresas y permita introducirlos por teclado. Dado el NIF de una empresa, el programa deber permitir mostrar los datos de la a misma, as como eliminarla del vector, reorganiz ndolo para no dejar espacios vacos. a
75
9. Punteros
Captulo 9
Punteros
Un puntero es una variable que contiene como valor una direcci n de memoria. Dicha direcci n correso o ponde habitualmente a la direcci n que ocupa otra variable en memoria. Se dice entonces que el puntero o apunta a dicha variable. La variable apuntada puede ser de cualquier tipo elemental, estructurado o incluso otro puntero. Los punteros constituyen una parte fundamental del lenguaje C y son b sicos para comprender toda a la potencia y exibilidad que ofrece el lenguaje. Son especialmente importantes en la programaci n o a bajo nivel, donde se manipula directamente la memoria del ordenador. Algunas de las ventajas que aporta el uso de punteros en C son: Constituyen la unica forma de expresar algunas operaciones. Su uso produce c digo compacto y eciente. o Son imprescindibles para el paso de par metros por referencia a funciones. a Tienen una fuerte relaci n con el manejo eciente de tablas y estructuras. o Permiten realizar operaciones de asignaci n din mica de memoria y manipular estructuras de o a datos din micas. a Finalmente, cabe advertir al lector que los punteros son tradicionalmente la parte de C m s difcil de a comprender. Adem s deben usarse con gran precauci n, puesto que al permitir manipular directamente a o la memoria del ordenador, pueden provocar fallos en el programa. Estos fallos suelen ser bastante difciles de localizar y de solucionar.
76
9.1.1 Declaraci n o
En la declaraci n de variables puntero se usa tambi n el operador * , que se aplica directamente a la o e variable a la cual precede. El formato para la declaraci n de variables puntero es el siguiente: o tipo de datos * nombre variable puntero; N tese que un puntero debe estar asociado a un tipo de datos determinado. Es decir, no puede asignarse o la direcci n de un short int a un puntero a long int. Por ejemplo: o int *ip; declara una variable de nombre ip que es un puntero a un objeto de tipo int. Es decir, ip contendr a direcciones de memoria donde se almacenaran valores enteros. No debe cometerse el error de declarar varios punteros utilizando un solo *. Por ejemplo: int *ip, x; declara la variable ip como un puntero a entero y la variable x como un entero (no un puntero a un entero). El tipo de datos utilizado en la declaraci n de un puntero debe ser del mismo tipo de datos que o las posibles variables a las que dicho puntero puede apuntar. Si el tipo de datos es void, se dene un puntero gen rico de forma que su tipo de datos implcito ser el de la variable cuya direcci n se le e a o asigne. Por ejemplo, en el siguiente c digo, ip es un puntero gen rico que a lo largo del programa o e apunta a objetos de tipos distintos, primero a un entero y posteriormente a un car cter. a void *ip; int x; char car; . . . ip = &x; ip = &car;
Al igual que cualquier variable, al declarar un puntero, su valor no est denido, pues es el corresa pondiente al contenido aleatorio de la memoria en el momento de la declaraci n. Para evitar el uso o indebido de un puntero cuando a n no se le ha asignado una direcci n, es conveniente inicializarlo con u o el valor nulo NULL, denido en el chero de la librera est ndar stdio.h. El siguiente ejemplo a muestra dicha inicializaci n: o int *ip = NULL; De esta forma, si se intentase acceder al valor apuntado por el puntero ip antes de asignarle una direcci n v lida, se producira un error de ejecuci n que interrumpira el programa. o a o
77
9. Punteros
double num; double *pnum = NULL; . . . pnum = # printf( "La direccin contenida en pnum es: o
%p", pnum );
9.2 Indirecci n o
Llamaremos indirecci n a la forma de referenciar el valor de una variable a trav s de un puntero que o e apunta a dicha variable. En general usaremos el t rmino indirecci n para referirnos al hecho de ree o ferenciar el valor contenido en la posici n de memoria apuntada por un puntero. La indirecci n se o o realiza mediante el operador *, que precediendo al nombre de un puntero indica el valor de la variable cuya direcci n est contenida en dicho puntero. A continuaci n se muestra un ejemplo del uso de la o a o indirecci n: o int x, y; int *p; . . . x = 20; p = &x; *p = 5498; y = *p;
Despu s de ejecutar la sentencia *p = 5498; la variable x, apuntada por p , toma por valor 5498. e Finalmente, despu s de ejecutar la sentencia y = *p; la variable y toma por valor el de la variable e x, apuntada por p. Para concluir, existe tambi n la indirecci n m ltiple, en que un puntero contiene la direcci n de e o u o otro puntero que a su vez apunta a una variable. El formato de la declaraci n es el siguiente: o tipo de datos ** nombre variable puntero; En el siguiente ejemplo, pnum apunta a num, mientras que ppnum apunta a pnum. As pues, la sentencia **ppnum = 24; asigna 24 a la variable num. int num; int *pnum; int **ppnum; . . . pnum = # ppnum = &pnum; **ppnum = 24; printf( "%d", num );
78
La indirecci n m ltiple puede extenderse a m s de dos niveles, aunque no es recomendable por la o u a dicultad que supone en la legibilidad del c digo resultante. o La utilizaci n de punteros debe hacerse con gran precauci n, puesto que permiten manipular direco o tamente la memoria. Este hecho puede provocar fallos inesperados en el programa, que normalmente son difciles de localizar. Un error frecuente consiste en no asignar una direcci n v lida a un puntero o a antes de usarlo. Veamos el siguiente ejemplo: int *x; . . . *x = 100; El puntero x no ha sido inicializado por el programador, por lo tanto contiene una direcci n de memoria o aleatoria, con lo que es posible escribir involuntariamente en zonas de memoria que contengan c digo o o datos del programa. Este tipo de error no provoca ning n error de compilaci n, por lo que puede ser u o difcil de detectar. Para corregirlo es necesario que x apunte a una posici n controlada de memoria, o por ejemplo: int y; int *x; . . . x = &y; *x = 100;
Asignaci n de punteros o
Es posible asignar un puntero a otro puntero, siempre y cuando ambos apunten a un mismo tipo de datos. Despu s de una asignaci n de punteros, ambos apuntan a la misma variable, pues contienen la e o misma direcci n de memoria. En el siguiente ejemplo, el puntero p2 toma por valor la direcci n de o o memoria contenida en p1. As pues, tanto y como z toman el mismo valor, que corresponde al valor de la variable x, apuntada tanto por p1 como por p2. int x, y, z; int *p1, *p2; . . . x = 4; p1 = &x; p2 = p1; y = *p1; z = *p2;
79
9. Punteros
Comparaci n de punteros o
Normalmente se utiliza la comparaci n de punteros para conocer las posiciones relativas que ocupan o en memoria las variables apuntadas por los punteros. Por ejemplo, dadas las siguientes declaraciones, int *p1, *p2, precio, cantidad; *p1 = &precio; *p2 = &cantidad; la comparaci n p1 > p2 permite saber cu l de las dos variables (precio o cantidad) ocupa una o a posici n de memoria mayor. Es importante no confundir esta comparaci n con la de los valores de las o o variables apuntadas, es decir, *p1 > *p2 .
Es importante advertir que aunque C permite utilizar aritm tica de punteros, esto constituye una e pr ctica no recomemdable. Las expresiones aritm ticas que manejan punteros son difciles de entender a e y generan confusi n, por ello son una fuente inagotable de errores en los programas. Como veremos o en la siguiente secci n, no es necesario usar expresiones aritm ticas con punteros, pues C proporciona o e una notaci n alternativa mucho m s clara. o a
80
lo que el compilador no permitir que las instrucciones del programa modiquen la direcci n contenida a o en dicho nombre. As pues, dada una declaraci n como char tab[15] , el empleo de tab es o equivalente al empleo de &tab[0]. Por otro lado, la operaci n tab = tab + 1 generar un error o a de compilaci n, pues representa un intento de modicar la direcci n del primer elemento de la tabla. o o C permite el uso de punteros que contengan direcciones de los elementos de una tabla para acceder a ellos. En el siguiente ejemplo, se usa el puntero ptab para asignar a car el tercer elemento de tab, leer de teclado el quinto elemento de tab y escribir por pantalla el d cimo elemento del vector e tab. char car; char tab[15]; char *ptab; . . . ptab = &tab; ptab = ptab + 3; car = *(ptab); /* Equivale a car = tab[3]; */ scanf( "%c", ptab+5 ); printf( "%c", ptab+10 ); Pero la relaci n entre punteros y tablas en C va a n m s all . Una vez declarado un puntero que o u a a apunta a los elementos de una tabla, pueden usarse los corchetes para indexar dichos elementos, como si de una tabla se tratase. As, siguiendo con el ejemplo anterior, sera correcto escribir: scanf( "%c", ptab[0] );/* ptab[0] equivale a tab[0] */ ptab[7] = R; /* ptab[7] equivale a *(ptab +7) */ Por lo tanto, vemos que no es necesario usar expresiones aritm ticas con punteros; en su lugar e usaremos la sintaxis de acceso a una tabla. Es importante subrayar que este tipo de indexaciones s lo o son v lidas si el puntero utilizado apunta a los elementos de una tabla. Adem s, no existe ning n tipo a a u de vericaci n al respecto, por lo que es responsabilidad del programador saber en todo momento si o est accediendo a una posici n de memoria dentro de una tabla o ha ido a parar fuera de ella y est a o a sobreescribiendo otras posiciones de memoria. El siguiente ejemplo muestra el uso de los punteros a tablas para determinar cu l de entre dos veca tores de enteros es m s fuerte. La fuerza de un vector se calcula como la suma de todos sus elementos. a #include <stdio.h> #define DIM 10 void main()
int v1[DIM], v2[DIM]; int i, fuerza1, fuerza2; int *fuerte; /* Lectura de los vectores. for (i= 0; i< DIM; i++) scanf( "%d ", &v1[i] ); */
81
9. Punteros
for (i= 0; i< DIM; i++) scanf( "%d ", &v2[i] ); /* Clculo de la fuerza de los vectores. a fuerza1 = 0; fuerza2 = 0; for (i= 0; i< DIM; i++) */
f g
if (fuerza1 > fuerza2) fuerte = v1; else fuerte = v2; /* Escritura del vector ms fuerte. a for (i= 0; i< DIM; i++) printf( "%d ", fuerte[i] ); */
En el caso de usar punteros para manipular tablas multidimensionales, es necesario usar f rmulas o de transformaci n para el acceso a los elementos. Por ejemplo, en el caso de una matriz de n las y o m columnas, el elemento que ocupa la la i y la columna j se referencia por medio de un puntero como puntero[i*m+j]. En el siguiente ejemplo se muestra el acceso a una matriz mediante un puntero. float mat[3][5]; float *pt; pt = mat; pt[i*5+j] = 4.6; /* Equivale a mat[i][j]=4.6 */
Cuando usamos el puntero para acceder a la matriz, la expresi n pt[k] signica acceder al elemento o de tipo float que est k elementos por debajo del elemento apuntado por pt . Dado que en C las a matrices se almacenan por las, para acceder al elemento de la la i columna j deberemos contar o cuantos elementos hay entre el primer elemento de la matriz y el elemento [i][j ]. Como la numeraci n comienza en cero, antes de la la i hay exactamente i las, y cada una tiene m columnas. Por lo tanto hasta el primer elemento de la la i tenemos i m elementos. Dentro de la la i, por delante del elemento j , hay j elementos. Por lo tanto tenemos que entre mat[0][0] y mat[i][j] hay i m j elementos. La gura 9.1 muestra como est dispuesta en memoria la matriz de este ejemplo a y una explicaci n gr ca del c lculo descrito. o a a
82
...
i*m elementos
A[0][m-1]
A[i][0]
...
i*m+j elementos
Fila i
A[i][j]
A[i][m-1]
A[n-1][m-1]
...
...
...
...
j elementos
83
9. Punteros
Puede decirse que en general a una tabla puede accederse tanto con ndices como con punteros (usando la aritm tica de punteros). Habitualmente es m s c modo el uso de ndices, sin embargo en el e a o paso de par metros a una funci n (ver Cap. 10) donde se recibe la direcci n de una tabla, es necesario a o o utilizar punteros. Como veremos, dichos punteros podr n usarse como tales o usando la indexaci n a o tpica de las tablas.
f g f
typedef struct Tnif nif; Tstring nombre; Tstring direc; long int telf; Tempresa;
De forma que en el programa puede declararse una variable cliente de tipo Tnif y un puntero pc a dicho tipo, as como asignar a pc la direcci n de inicio de la variable estructura cliente : o Tnif cliente; Tnif *pc; . . . pc = &cliente;
9.6. Ejercicios
84
Para acceder a un campo individual en una estructura apuntada por un puntero puede usarse el operador de indirecci n * junto con el operador punto (.) habitual de las estructuras. Vemos un o ejemplo: (*pc).letra = Z; scanf( "%ld", &(*pc).num ); Los par ntesis son necesarios ya que el operador punto (.) tiene m s prioridad que el operador *. e a N tese que el operador de direcci n & en la llamada a scanf se reere al campo num y no al o o puntero pc. El hecho de que sea obligatorio usar par ntesis en esta notaci n hace que se generen errores debido e o al olvido de los par ntesis adecuados. Para evitar estos errores, C posee otra notaci n para acceder a e o los campos de una estructura apuntada por un puntero. El acceso a campos individuales puede hacerse tambi n mediante el operador especial de los pune teros a estructuras -> (gui n seguido del smbolo de mayor que). As pues, puede escribirse el mismo o ejemplo anterior como: pc->letra = Z; scanf( "%ld", &pc->num ); El operador -> tiene la misma prioridad que el operador punto (.). Es recomendable usar el operador -> al manejar structs apuntados por punteros. El acceso a los campos de una estructura anidada a partir de un puntero puede hacerse combinando los operadores de punteros con el punto (.). Veamos el siguiente ejemplo: Tempresa emp; Tempresa *pe; char inicial; . . . pe = &emp; pe->nif.letra = Z; scanf( "%ld", &pe->nif.num ); gets( pe->nombre ); inicial = pe->nombre[0]; . . .
9.6 Ejercicios
1. Escribir un programa que pase a may sculas la inicial de todas las palabras en una cadena de u caracteres. Usar un puntero a dicha cadena para acceder a los elementos de la misma. 2. Escribir un programa que calcule el m ximo de un vector de n meros enteros, utilizando un a u puntero para acceder a los elementos del vector. 3. Escribir un programa que, usando punteros, copie en orden inverso una cadena de caracteres en otra.
85
9. Punteros
4. Un programa en C contiene las siguientes instrucciones: char u, v; char *pu, *pv; . . . v = A; pv = &v; *pv = v + 1; u = *pv + 1; pu = &u; Si cada car cter ocupa 1 byte de memoria y la variable u se sit a en la direcci n FC8 (hexadea u o cimal), responder a las siguientes preguntas: (a) Qu valor representa &v ? e (b) Qu valor se asigna a pv ? e (c) Qu valor representa *pv ? e (d) Qu valor se asigna a u ? e (e) Qu valor representa &u ? e (f) Qu valor se asigna a pu ? e (g) Qu valor representa *pu ? e 5. Un programa en C contiene las siguientes instrucciones: float a = 0.1, b = 0.2; float c, *pa, *pb; . . . pa = &a; *pa = 2 * a; pb = &b; c = 5 * (*pb - *pa); Si cada variable de tipo float ocupa 4 bytes de memoria y la variable a se sit a en la u direcci n 1230 (hexadecimal), responder a las siguientes preguntas: o (a) Qu valor representa &a ? e (b) Qu valor representa &c ? e (c) Qu valor representa &pb ? e (d) Qu valor se asigna a pa ? e (e) Qu valor representa *pa ? e (f) Qu valor representa &(*pa) ? e (g) Qu valor se asigna a pb ? e (h) Qu valor representa *pb ? e
9.6. Ejercicios
86
(i) Qu valor se asigna a c ? e 6. Un programa de C contiene las siguientes sentencias: int i, j = 25; int *pi, *pj = &j; . . . *pj = j + 5; i = *pj + 5; pi = pj; *pi = i + j; Suponiendo que cada variable entera ocupa 2 bytes de memoria. Si la variable i se sit a en la u o direcci n 1000 y la variable j en la direcci n 1002, entonces: o (a) Qu valor representan &i y por &j ? e (b) Qu valor se asigna a pj , *pj y a i ? e (c) Qu valor representa pi ? e (d) Qu valor se asigna a *pi ? e (e) Qu valor representa pi + 2 ? e (f) Qu valor representa la expresi n (*pi + 2) ? e o (g) Qu valor representa la expresi n *(pi + 2) ? e o
87
10. Funciones
Captulo 10
Funciones
Hasta ahora hemos visto como el programa principal (main( )) utiliza funciones de la librera est ndar a de C para realizar algunas tareas comunes (printf( ), scanf( ), . . . ). C permite tambi n la dee nici n de funciones por parte del programador. Como veremos, al usar funciones denidas por el o programador, los programas pueden estructurarse en partes m s peque as y sencillas. Cada una de esa n tas partes debe responder a un prop sito unico e identicable, pudiendo adem s utilizarse en distintos o a lugares del programa. La distribuci n del c digo de un programa usando funciones se conoce como o o modularizaci n. o El dise o modular de programas ofrece diversas ventajas. Por ejemplo, muchos programas requien ren la ejecuci n de un mismo grupo de instrucciones en distintas partes del programa. Este grupo de o instrucciones puede incluirse dentro de una sola funci n, a la que se puede llamar cuando sea necesario. o Adem s, puede proporcionarse un conjunto de datos (par metros) diferente a la funci n cada vez que a a o se la llama. Es importante tambi n la claridad en la l gica del programa resultante de la descomposici n del e o o mismo en partes bien denidas. Un programa concebido de esta forma es mucho m s f cil de escribir y a a de depurar, no s lo por el propio programador, sino tambi n (y m s importante) por otros programadoo e a res que posteriormente deban mantener el programa. Este hecho es especialmente cierto en programas grandes donde participan muchos programadores. La utilizaci n de funciones permite tambi n la construcci n a medida de libreras de funciones de o e o uso frecuente. Por ejemplo, un programador especializado en el desarrollo de programas matem ticos a podra crear una librera con funciones para el manejo de matrices, n meros complejos, etc. De esta u forma se evita la reescritura del mismo c digo repetidas veces para distintos programas. o
10.1 Generalidades
Una funci n es una porci n de programa, identicable mediante un nombre, que realiza determinadas o o tareas bien denidas por un grupo de sentencias sobre un conjunto de datos. Las operaciones que realiza la funci n son siempre las mismas, pero los datos pueden variar cada vez que se llame a la funci n. o o Todo programa en C consta de una o m s funciones, una (y s lo una) de la cuales debe llamarse a o main. La ejecuci n del programa comienza siempre en dicha funci n, desde donde puede llamarse o o a otras funciones de la librera est ndar o denidas por el programador. Al llamar a una funci n se a o
10.1. Generalidades
88
ejecutan las sentencias que la componen y, una vez completada la ejecuci n de la funci n, la ejecuci n o o o del programa contin a desde el punto en que se hizo la llamada a la funci n. u o Generalmente, al llamar a una funci n se le proporciona un conjunto de datos (par metros) que o a se procesan ejecutando las sentencias que la componen. La funci n devuelve un solo valor mediante o la sentencia return. Algunas funciones reciben par metros, pero no devuelven nada (como la funa ci n printf), mientras que otras no reciben par metros pero s devuelven un valor (como la funci n o a o rand). El programa del siguiente ejemplo calcula el n mero combinatorio u
m n
m n m;n
! ! (
)!
, donde
es necesario calcular el factorial de tres n mero diferentes. Una posible realizaci n de este programa, u o si no se tuviese en cuenta el uso de funciones, sera la siguiente: #include <stdio.h> void main() long int m, n, fm = 1, fn = 1, fdif = 1; float res; int i; printf( "Introduzca m y n: " ); scanf( "%d %d", &m, &n ); for (i= 2; i<= m; i++) fm = fm * i; for (i= 2; i<= n; i++) fn = fn * i; for (i= 2; i<= m-n; i++) fdif = fdif * i; res = (float) fm / ((float)fn* (float)fdif); printf( "m sobre n = %fnn", res );
Como puede verse, el c digo para el c lculo del factorial se halla triplicado. Una soluci n m s clara y o a o a elegante puede obtenerse usando funciones: #include <stdio.h> long int fact ( int x )
void main()
89
10. Funciones
f
long int m, n; float res; printf( "Introduzca m y n: " ); scanf( "%d %d", &m, &n ); res = (float) fact(m) / ((float)fact(n)*(float)fact(m-n)); printf( "m sobre n = %fnn", res );
En el ejemplo se ha denido la funci n fact, que recibe como par metro un valor de tipo int, al que o a se ha llamado x, y devuelve un resultado de tipo long int. El c digo en el interior de la funci n o o calcula el factorial de x acumul ndolo sobre la variable local f. Finalmente la funci n devuelve el a o resultado del c lculo mediante la sentencia return. Obs rvese que la denici n de una funci n se a e o o asemeja a la del programa principal. De hecho, el programa principal main es una funci n. o Seguidamente veremos m s formalmente c mo denir funciones, c mo llamarlas, las distintas vaa o o riantes del paso de par metros, etc. a
f g
cuerpo
tipo: es el tipo de datos del valor que devuelve la funci n. Si no se especica ninguno, C asume o que la funci n devuelve un valor de tipo entero. o nombre funcin: identicador que se usar posteriormente para llamar a la funci n. o a o tipoi parami: tipo y nombre de cada uno de los par metros que recibe la funci n. Se espea o cican entre par ntesis y separados por comas. Algunas funciones pueden no tener par metros. e a Los par metros de la declaraci n se denominan par metros formales, ya que representan los a o a nombres con que referirse dentro de la funci n a los datos que se transeren a esta desde la parte o del programa que hace la llamada. cuerpo: conjunto de declaraci n de variables y sentencias de ejecuci n (incluyendo llamadas a o o funciones) necesarias para la realizaci n de la tarea especicada por la funci n. Debe incluir una o o o m s sentencias return para devolver un valor al punto de llamada. a
90
10.2.2 Prototipos
Si en el punto del programa donde se va a realizar una llamada a una funci n, dicha funci n ya ha sido o o denida previamente, entonces ya se conocen sus caractersticas (tipo del resultado, n mero y tipo de u los par metros, etc.), por lo que la llamada puede realizarse sin problemas. Sin embargo, si la funci n a o que se va a llamar se halla denida posteriormente al punto desde donde se realiza la llamada, entonces debe crearse un prototipo de la funci n a la cual se desea llamar. Dicho prototipo deber colocarse o a antes del punto donde se haga la primera llamada a la funci n, y consta unicamente de la cabecera de o dicha funci n. o El prototipo de una funci n puede interpretarse como un aviso al compilador, para que cuando o encuentre una llamada a dicha funci n pueda conocer el tipo del resultado que devuelve y la informaci n o o sobre los par metros que recibe. a A continuaci n se muestra el formato general de un prototipo, que corresponde a la cabecera de la o funci n seguida de un punto y coma: o tipo nombre funcin(tipo1 param1, ..., tipoN paramN); o De acuerdo con esto, el programa del ejemplo anterior podra haberse escrito de otro modo, de niendo la funci n fact con posterioridad a main y usando un prototipo: o #include <stdio.h> long int fact ( int x ); /* Prototipo */
void main() long int m, n; float res; printf( "Introduzca m y n: " ); scanf( "%d %d", &m, &n ); res = (float) fact(m) / ((float)fact(n)*(float)fact(m-n)); printf( "m sobre n = %fnn", res );
g f
long int fact ( int x ) int i; long int f = 1; for (i= 2; i<= x; i++) f = f * i; return(f);
La utilizaci n de prototipos de funciones no es obligatorio en C. Sin embargo, es aconsejable, ya o que facilitan la comprobaci n y detecci n de errores entre las llamadas a funciones y las deniciones o o correspondientes.
91
10. Funciones
10.2.3 Llamada
Finalmente, la llamada a una funci n se realiza con el nombre de la misma y una lista de par metros o a (si es que los requiere) entre par ntesis. El n mero y tipo de los par metros empleados en la llamada a e u a la funci n debe coincidir con el n mero y tipo de los par metros formales de la denici n o prototipo. o u a o Adicionalmente, si la funci n devuelve alg n valor (es decir, no es de tipo void) la llamada a la o u funci n debe estar incluida en una expresi n que recoja el valor devuelto. Siguiendo con el ejemplo del o o factorial: fm = fact(m); prod = fact(n)*fact(m-n); Los datos empleados en la llamada a una funci n reciben el nombre de par metros reales, ya que o a se reeren a la informaci n que se transere a la funci n para que esta se ejecute. Como veremos m s o o a adelante, los identicadores de los par metros formales son locales a la funci n, en el sentido de que a o no son reconocidos desde fuera de esta. Por tanto, los nombres de los par metros formales no tienen a por qu coincidir con los nombres de la variables usadas como par metros reales en el momento de la e a llamada.
m n
lo que no son accesibles fuera de ella. De forma similar, las variables m, n y res son locales a la funci n main. o
92
funciones repertidas en diferentes cheros, deberemos incluir en uno de los cheros la declaraci n y o repetir en los dem s cheros la declaraci n precedida de la palabra extern. Tambi n es posible a o e denir variables globales unicamente dentro de un chero. Para ello antepondremos en la declaraci n o la palabra static. El siguiente ejemplo muestra las declaraciones de tres variables globales. La variable A es accesible en todo el programa y est declarada en este chero. La variable B es accesible en todo el programa, a pero est declarada en otro chero, es decir, hay otras funciones adem s de las de este chero que a a pueden acceder a ella. Finalmente, la variable C s lo es accesible por las funciones de este chero. o int A; extern int B; static int C; void main ()
f g f g f g
int func1() . . .
int func2() . . .
El uso de variables globales debe hacerse con precauci n, puesto que al poderse modicar desde o cualquier funci n del programa donde sean accesibles, pueden producirse efectos laterales difciles de o detectar y corregir. Es una buena pr ctica de programaci n no emplear variables globales salvo en a o casos muy excepcionales. En general, el uso de una variable global puede substituirse por una variable local al programa principal y el adecuado paso de par metros a todas las funciones que requieran a acceder a dicha variable.
93
10. Funciones
f g f
int loc = 10; printf( "En func(), loc=%d y &loc=%pnn", loc, &loc ); printf( "En func(), par=%d y &par=%pnn", par, &par );
void main() int loc = 24, par = 5; printf( "En main(), loc=%d y &loc=%pnn", loc, &loc ); printf( "En main(), par=%d y &par=%pnn", par, &par ); func(loc);
Al compilar y ejecutar este ejemplo, observaremos que las variables loc y par se sit an en direcu ciones de memoria diferentes (y por tanto, son variables diferentes) si estamos dentro de la funci n o o en el programa principal.
f f
g g
printf( "MAX=%d", y );
Si la funci n no es de tipo void, la sentencia return, adem s de especicar la terminaci n o a o de la funci n, puede especicar la devoluci n de un valor para que pueda ser utilizado en el punto o o donde se hizo la llamada. El valor devuelto, y por ende el tipo de la funci n, puede ser cualquiera de o los tipos elementales, estructurados o denidos por el programador, excepto tablas. Aunque en C es legal que una funci n retorne cualquier tipo de estructura, por cuestiones de eciencia en la ejecuci n o o del programa no es conveniente retornar estructuras muy grandes en tama o. Por ejemplo, estructuras n en cuyo interior haya tablas. Cuando se desea que una funci n devuelva tipos de datos complejos, se o utiliza el paso de par metros por referencia que veremos en la secci n 10.5. a o
94
En el siguiente programa se ha rescrito la funci n maximo para que devuelva el m ximo de dos o a enteros, en lugar de mostrarlo por pantalla.
En la parte del programa que hace la llamada puede usarse el valor devuelto por la funci n dentro de o cualquier expresi n v lida (en particular una sentencia de escritura): o a printf( "MAX(%d,%d)=%d", a, b, maximo(a,b) );
f g f
void main() int x = 5; printf( "Antes x = %dnn", x ); cuadrado( x ); printf( "Despus x = %dnn", x ); e
95
10. Funciones
Antes x = 5 Dentro x = 25 Despus x = 5 e Como puede verse, la modicaci n sobre el par metro formal no afecta a la variable del programa o a principal. En el paso de par metros por valor, la transferencia de informaci n es s lo en un sentido, es decir, a o o desde la parte del programa donde se hace la llamada hacia el interior de la funci n, pero no al rev s. o e Gracias a ello, es posible utilizar expresiones como par metros reales, en lugar de necesariamente a variables. Esto es as puesto que el valor asignado al par metro formal es el resultado de la evaluaci n a o de dicha expresi n. Es m s, si el par metro real es una variable, su valor es protegido de posibles o a a modicaciones por parte de la funci n. o
96
* para referenciar el valor del par metro real. Por ejemplo, la sentencia aux = *x; asigna a la a variable local aux el valor de la variable a del programa principal, puesto que el par metro formal a x contiene una referencia a la variable a. #include <stdio.h> void swap(int *x, int *y) int aux; /* Se asigna a aux el valor referenciado por x */ aux = *x; /* Se asigna el valor referenciado por y al valor referenciado por x */ *x = *y; /* El valor referenciado por y pasa a ser el valor de aux */ *y = aux;
g f
void main() int a, b; scanf( "%d %d", &a, &b ); swap( &a, &b ); printf( "Los nuevos valores son a=%d y b=%dnn", a, b );
Como puede verse, el paso de par metros por referencia tiene una estrecha relaci n con el uso de a o punteros y direcciones de memoria que vimos en el captulo 9. De hecho un purista del lenguaje dira que en C no existe el paso por referencia y que todo paso de par metros se hace por valor. El paso por a referencia se simula mediante el paso (por valor) de punteros a las variables externas a la funci n y o que se desean modicar desde el interior de la misma.
97
10. Funciones
scanf( "%d", &v[i] ); max = v[0]; for (i= 1; i< 20; i++) max = maximo( max, v[i] ); printf( "El elemento mayor es:
%dnn", max );
Algo muy diferente es el paso de una tabla a una funci n. La unica manera que C ofrece para ello o es el paso por referencia de toda la tabla en cuesti n. Sin embargo, al contrario que en el paso por o referencia habitual, no se usan los smbolos & y * . En su lugar se utiliza directamente el nombre de la tabla, que constituye de por s una referencia al primer elemento de la tabla, tal como vimos en el captulo 9. Por lo tanto, el par metro formal de la funci n debe ser un puntero para poder recoger la a o direcci n de inicio de la tabla. o El siguiente ejemplo dene una funci n para el c lculo de la media de los elementos de un vector o a de n meros de coma otante: u #include <stdio.h> #define DIM 20 float media( float vec[], int n )
int j; float sum; sum = 0.0; for (j= 0; j< n; j++) sum = sum + vec[j]; return (sum/(float)n);
g f
void main() float med, v[DIM]; int i; printf( "Introducir elementos del vector:nn" ); for (i= 0; i< DIM; i++) scanf( "%f", &v[i] ); med = media( v, DIM ); printf( "La media es: %fnn", med );
La cabecera de la funci n media hubiese podido escribirse tambi n como: o e float media( float *vec, int n ) Es importante notar que la denici n del par metro vec en la funci n media es una declaraci n o a o o de un puntero. Es decir, no existe diferencia alguna entre float *vec y float vec[]. El motivo de usar esta nueva notaci n es dar mayor claridad al programa. Por lo tanto, cuando un par metro o a
98
sea un puntero a una tabla usaremos la notaci n tipo nombrePuntero[], mientras que cuano do tengamos un puntero a cualquier otro tipo de datos (tanto tipos elementales como estructurados) usaremos la notaci n tipo *nombrePuntero. o Finalmente, la cabecera de la funci n media tambi n pudiera haberse escrito como: o e float media( float vec[DIM], int n ) En este caso de nuevo el par metro vec es un puntero a una tabla, pero ahora adem s indicamos el a a tama o de la tabla, lo que clarica a n m s el programa. Notar que esto s lo es posible hacerlo si las n u a o dimensi nes de la tabla son constantes. Es decir, una expresi n como o o float media( float vec[n], int n ) sera incorrecta. Vemos ahora un ejemplo con una matriz. Deseamos hacer una funci n que multiplique una matriz o por un vector. La soluci n propuesta es la siguiente: o #include <stdio.h> #define MAXFIL 3 #define MAXCOL MAXFIL void matXvec( int nfil, int ncol, float A[], float x[], float y[] ) f /*Calcula y = A*x */ int i, j; for (i= 0; i< nfil; i++)
f g
y[i] = 0.0; for (i= 0; i< ncol; i++) y[i] = y[i] + A[i*MAXCOL+j] * x[j];
g f
void main() int nfil, ncol; float v1[MAXCOL], v2[MAXFIL], M[MAXFIL][MAXCOL]; ... /* Leer los nfil, ncol, A, x */ matXvec( nfil, ncol, M, v1, v2 ); ... /* Mostrar y */
N tese que los par metros formales A, x e y son todos punteros. Se accede a los elementos de o a la matriz a trav s de un puntero que se ala al primer elemento ([0][0]). En la secci n 9.4 vimos e n o la forma de realizar un acceso de este tipo. N tese tambi n que en la f rmula que da el n mero de o e o u elementos entre el primero y el elemento [i][j], se debe usar MAXCOL y no ncol. Es decir,
99
10. Funciones
A A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7] A[8] M[0][0] ncol=2 M[0][1] M[0][2] MAXCOL = 3 M[1][0] M[1][1] M[1][2] M[2][0] M[2][1] M[2][2]
Figura 10.1: Acceso a una matriz mediante un puntero debemos contar todos los elementos que hay en memoria entre el primero y el elemento [i][j], incluyendo los que no son usados por el algoritmo. La gura 10.1 muestra este ejemplo gr camente a suponiendo que ncol vale 2, MAXCOL vale 3, y que queremos acceder el elemento [2][1] de la matriz.
i*MAXCOL+j = 2*3+1=7
10.6. Recursividad
100
f g
El siguiente programa de ejemplo permite introducir el nombre del usuario del programa al ejecutarlo y mostrar un mensaje que lo incluya. #include <stdio.h> void main( int argc, char *argv[] ) if (argc > 2)
f g f g f g g
As pues, si hemos llamado al programa ejecutable saludo y se escribe en la lnea de ordenes del sistema operativo saludo Pepe, la salida del programa ser : a Yo te saludo Pepe Si alguno de los par metros que se pasan al programa contiene espacios en blanco o tabuladores, a deben usarse comillas en la ejecuci n del programa. o Lnea de ordenes del sistema operativo: saludo "Pepe Prez" e Salida del programa: Yo te saludo Pepe Prez e
10.6 Recursividad
Se llama recursividad a un proceso en el que una funci n se llama a s misma repetidamente hasta o que se satisface una cierta condici n. Este proceso se emplea para c lculos repetitivos en los que el o a resultado de cada iteraci n se determina a partir del resultado de alguna iteraci n anterior. o o Frecuentemente un mismo problema puede expresarse tanto de forma recursiva como de forma iterativa. En estos casos, debido a que la ejecuci n recursiva requiere de numerosas llamadas a funciones, o es preferible utilizar una soluci n no recursiva. Sin embargo, en otros casos, la escritura de una soluo ci n no recursiva puede resultar extraordinariamente compleja. Es entonces cuando es apropiada una o soluci n recursiva. o
101
10. Funciones
A continuaci n se presentan dos funciones recursivas para el c lculo del factorial y el c lculo de o a a valores de la serie de Fibonacci:
f g f g
Otro ejemplo de soluci n recursiva es este programa para invertir los elementos de un vector usando o la funci n swap: o #define N 10 void invert( int v[], int i, int j ) swap( &v[i], &v[j] ); i++; j--; if (i < j) invert( v, i, j );
g f
void main() int i, vector[N]; for(i= 0; i< N; i++) scanf( "%d", &vector[i] ); invert( v, 0, N-1 ); for(i= 0; i< N; i++) printf( "%dnn", vector[i] );
10.7 Ejercicios
1. El c lculo de a
ex
grande. Escribir una funci n pot o a o o o donde i y x son par metros enteros de la funci n. Usando esta funci n y la funci n fact del
n X xi i i
!
para
10.7. Ejercicios
102
principio del captulo, escribir un programa que calcule ex de forma aproximada para un valor de n dado. 2. Escribir un programa para calcular frac del problema anterior. 3. Escribir un programa para calcular frac. 4. Se dispone de las dos funciones siguientes:
sen x
(
) =
1 X xi i i
=0
2 +1
(2
+ 1)!
1)
cos x
(
) =
1 X xi i ; i
2 =0
(2 )!
1)
void f1( int x, int *y, int a, int b ) x = x + *y = *y x = x + *y = *y printf( 1; + 1; a; + b; "%d %dnn", x, *y );
g f
Y del programa principal: #include <stdio.h> void main() int a = 0, b = 0; llamada printf( "%d %dnn", a, b );
Indicar el resultado de ejecutar este programa en caso de que llamada se substituya por: f1( a, &b, a, b ); o bien por f2( a, &b ); 5. A continuaci n se muestra el esqueleto de un programa en C: o
103
10. Funciones
f g f g f
a = P; b = Q; return ((a<b)?(int)a:(int)b);
int f2( char *c1, char *c2 ) *c1 = R; *c2 = S; return ((*c1==*c2)?(int)*c1:(int)*c2);
void main() char a, b; int i, j; . . . a = X; b = Y; i = f1( a, b ); printf( "a=%c,b=%cnn", a, b ); . . . j = f2( &a, &b ); printf( "a=%c,b=%cnn", a, b );
(a) Qu valores se asignan a i y j en main? e (b) Qu valores escribe la primera llamada a printf? e (c) Qu valores escribe la segunda llamada a printf? e 6. Qu valor calcula el siguiente programa? e
void func( int p[] ) int i, sum = 0; for(i= 3; i< 7; ++i) sum = sum + p[i]; printf( "suma = %d", sum );
g f g
10.7. Ejercicios
104
7. Determinar si un n mero no negativo es perfecto o tiene alg n amigo. Dos n meros son amigos u u u cuando la suma de los divisores de uno de ellos es igual al otro n mero. Por ejemplo: 220 y 284 u son amigos. Por otra parte, un n mero es perfecto cuando la suma de sus divisores es el mismo. u Por ejemplo 6 = 3 + 2 + 1 es perfecto. 8. Dado el siguiente tipo de datos: #include <stdio.h> typedef struct
f g
describir la salida generada por cada uno de los siguientes programas: (a) void func( Tcolores X )
g f
X.a = "cian"; X.b = "magenta"; X.c = "amarillo"; printf( "%s%s%snn", X.a, X.b ,X.c ); return();
void main() Tcolores col = f "rojo", "verde", "azul" g; printf( "%s%s%snn", col.a, col.b, col.c ); func( col ); printf( "%s%s%snn", col.a, col.b, col.c );
g
(b)
g f
X->a = "cian"; X->b = "magenta"; X->c = "amarillo"; printf( "%s%s%snn", X->a, X->b, X->c ); return();
void main() Tcolores col = f "rojo", "verde", "azul" g; printf( "%s%s%snn", col.a, col.b, col.c ); func( & col ); printf( "%s%s%snn", col.a, col.b, col.c );
105
11. Ficheros
Captulo 11
Ficheros
Es preciso alg n mecanismo que permita almacenar de forma permamente ciertos datos de los prou gramas. Por ejemplo, un programa que gestione la contabilidad de una empresa necesita una serie de informaciones iniciales (balance hasta ese momento, lista de compras, lista de ventas, etc ). De igual forma, genera una serie de informaciones que deben ser almacenadas cuando el programa naliza. Desde el punto de vista del hardware, hay diferentes dispositivos para almacenar informaci n de o forma permanente: discos duros, unidades de cinta, CDs, disquetes, etc. Para un programador, el dispositivo fsico que se use carece de importancia. Los programas deben funcionar tanto si la informaci n o est en un disco duro como en un CD como en una cinta. Por lo tanto, es preciso un conjunto de funcioa nes (una librera) que permita realizar almacenamiento permamente de informaci n, pero omitiendo los o detalles especcos de cada dispositivo hardware. Esta librera de funciones la proporciona el sistema operativo. Para ocultarle al programador los detalles especcos de cada dispositivo hardware, se usa el con cepto de chero. Un chero es un objeto abstracto sobre el cual se puede leer y escribir informaci n. o Existen dos tipos fundamentales de cheros: cheros de texto y cheros binarios. En los cheros de texto la informaci n se almacena usando caracteres (c digos ASCII). Por ejemplo, una variable de o o tipo int se almacena en la memoria como una secuencia de bits que debe ser interpretada como un c digo complemento a dos. Sin embargo, cuando escribimos dicha variable en un chero de texto, o lo que almacenamos es un conjunto de caracteres que representan el valor de la variable en base 10. Una variable tipo de int que en memoria se almacenase como 1 0 0 en un chero de texto se o escribira como ;2147483648. En los cheros binarios la informaci n se almacena de igual forma que en la memoria, mediante la misma secuencia de unos y ceros. Usar cheros de texto tiene la ventaja de que la informaci n almacenada puede ser visualizada y o comprendida por un ser humano. Pero tiene el inconveniente de ocupar aproximadamente tres veces m s espacio que los cheros binarios. Por ejemplo, la variable de tipo int mostrada anteriormente, a ocupa 4 bytes en un chero binario (lo mismo que ocupa en memoria), y ocupa 11 bytes (signo y 10 cifras) en un chero de texto. Existen diferentes funciones para trabajar con cheros de texto y con cheros binarios. En este libro nos ocuparemos unicamente de los cheros de texto. Un chero de texto almacena la informaci n como una secuencia de c digos ASCII. La gura 11.1 o o muestra un ejemplo de almacenamiento de un chero de texto. En esta gura se muestra el aspecto que
z }| {
31
106
Ventana de acceso
69 115 116 111 32 101 115 10 ... 130 116 111 EOF
Cdigo del carcter E Cdigo del carcter s Cdigo del carcter t Cdigo del carcter o Cdigo del carcter Espacio en blanco Cdigo del carcter e Cdigo del carcter s Cdigo del carcter \n Cdigo del carcter t Cdigo del carcter o Cdigo del carcter .
Figura 11.1: Almacenamiento de un chero de texto tendra un chero de texto al mostrarse en pantalla, as como la secuencia de bytes que se almacenaran en disco. Todos los cheros de texto nalizan com un car cter especial que indica el nal del chero. a Este car cter lo representamos mediante la constante EOF (End Of File) que se halla denida en el a chero stdio.h. Cuando queramos leer o escribir informaci n en un chero de texto deberemos hacerlo de forma o secuencial. Por ejemplo, si queremos leer el chero de la gura 11.1, deberemos leer en primer lugar el primer car cter y as sucesivamente. Esto se debe a que existe una ventana asociada al chero que s lo a o puede avanzar secuencialmente, nunca a saltos. El sistema operativo usa variables del tipo FILE para manejar los dispositivos hardware asociados a cheros. La denici n del tipo FILE se encuentra en el chero stdio.h. Todos los programas o que manejen cheros deber n incluir stdio.h. Una variable del tipo FILE es una estructura cuyo a contenido s lo puede ser entendido y manejado por funciones del sistema operativo. Dicha estructura o contiene informaci n, por ejemplo, de la pista y el sector del disco donde comienza el chero, etc. o Dado que las variables de tipo FILE pertenecen al sistema operativo, nunca tendremos una variable de este tipo en nuestros programas. Lo unico que necesitamos son punteros a dichas variables. Esto es, si queremos manejar un chero dentro de un programa, deberemos tener una declaraci n como la o siguiente: FILE *fp; El puntero fp debe ser inicializado mediante la funci n fopen, de forma que apunte a una o variable del sistema operativo que contenga los datos del chero concreto que usemos. Las funciones de lectura y escritura en el chero unicamente necesitan conocer dicho puntero para saber la variable que deben usar. Para utilizar un chero se debe realizar una secuencia ja de acciones:
107
11. Ficheros
1. Abrir el chero. Esto signica decirle al sistema operativo que inicialice una variable de tipo FILE, de forma que a partir de ese momento, las acciones de lectura/escritura que utilicen dicha variable se realicen realmente en el dispositivo hardware correspondiente. Esto se hace llamando a una funci n fopen. o 2. Leer o Escribir en el chero. Para ello usaremos las funciones fscanf y fprintf. Estas funciones s lo necesitan dos datos: la variable de tipo FILE asociada al chero y la informaci n o o que queremos leer o escribir en dicho chero. 3. Cerrar el chero. Esto signica indicar al sistema operativo que ya no necesitamos la variable tipo FILE asociada al chero. Para ello se usa la funci n fclose. o
108
int fclose( FILE *fp ) Si no se produce ning n error al cerrar el chero fclose retorna 0. En caso contrario retorna la u constante EOF (recordemos que esta constante est denida en stdio.h). Veamos algunos ejemplos a del uso de fopen y fclose. En el siguiente ejemplo abrimos un chero llamado miFichero.txt para leer la informaci n o contenida en el. En caso de error al abrir el chero mostramos un mensaje en pantalla y nalizamos la ejecuci n del programa mediante la funci n exit. Finalmente cerramos el chero. o o #include <stdio.h> void main( ) FILE *fp; fp = fopen( "miFichero.txt", "r" ); if (fp == NULL)
f g
A continuaci n se muestra otro ejemplo para el caso en que abrimos un chero para escritura. o El nombre del chero es introducido por el usuario a trav s del teclado. Si el chero ya existe, ser e a destruido y creado de nuevo. Esta acci n la realiza de forma autom tica la funci n fopen. Finalmente o a o cerramos el chero. #include <stdio.h> #define N 256 void main( )
FILE *fp; char nombreFichero[N]; printf( " Nombre del fichero (< %d caracteres): scanf( "%s%*c", nombreFichero ); fp = fopen( nombreFichero, "w" ); if (fp == NULL) ", N );
f g
109
11. Ficheros
FILE *fp; char nombreFichero[N]; int lon = 0; int vec[MAXELE]; printf( "Fichero de entrada(< %d caracteres): scanf( "%s%*c", nombreFichero ); fp = fopen( nombreFichero, "r" ); if (fp == NULL) ", N );
110
g f g
exit(0);
fscanf( fp, "%d", &lon ); if (lon < MAXELE) for (i= 0; i< lon; i= i+1) fscanf( fp, "%d", &vec[i] );
else printf( "El vector tiene demasiados elementosnn" ); fclose( fp ); . . . /* Aqu podemos modificar vec */ . . . printf( "Fichero de salida(< %d caracteres): ", N ); scanf( "%s%*c", nombreFichero ); fp = fopen( nombreFichero, "w" ); if (fp == NULL)
f g
fprintf( fp, "%dnn", lon ); for (i= 0; i< lon; i= i+1) fprintf( fp, "%dnn", vec[i] ); fclose( fp );
En el ejemplo anterior, si el nombre del chero de salida es el mismo que el nombre del chero de entrada, los datos iniciales se perder n, ya que al abrir el chero en modo "w", el chero que ya exista a es destruido y creado de nuevo. En este ejemplo, sin embargo, leemos de un chero y el resultado del programa es a adido al nal del mismo chero. n #include <stdio.h> #define N 256 #define MAXELE 100 void main( )
FILE *fp; char nombreFichero[N]; int lon; int vec[MAXELE]; printf( "Nombre del fichero(< %d caracteres): scanf( "%s%*c", nombreFichero ); fp = fopen( nombreFichero, "r" ); if (fp == NULL) ", N );
111
11. Ficheros
f g f g
fscanf( fp, "%d", &lon ); if (lon < MAXELE) for (i= 0; i< lon; i= i+1) fscanf( fp, "%d", &vec[i] );
else printf( "El vector tiene demasiados elementosnn" ); fclose( fp ); . . . /* Aqu trabajamos con vec */ . . . fp = fopen( nombreFichero, "a" ); if (fp == NULL)
f g
fprintf( fp, "%dnn", lon ); for (i= 0; i< lon; i= i+1) fprintf( fp, "%dnn", vec[i] ); fclose( fp );
112
FILE *fp; char nombreFichero[N]; int lon; int vec[MAXELE]; printf( " Nombre del fichero(< %d caracteres): scanf( "%s%*c", nombreFichero ); fp = fopen( nombreFichero, "r" ); if (fp == NULL) ", N );
f g f
lon = 0; while (!feof(fp) && (lon < MAXELE)) kk = fscanf( fp, if (kk == 1) lon++; if (!feof(fp) && printf( "Todo cabe "%d", &vec[lon] );
g g
fclose( fp ); . . .
Supongamos que el chero contiene s lo tres lneas como las siguientes: o 123 254 -35 Al ejecutar el programa, el bucle while realizar cuatro iteraciones. En la tercera iteraci n se leer a o a del chero el n mero -35 y se almacenar en vec[2]. Sin embargo, la funci n feof a n no u a o u indicar el nal del chero, es decir, retornar 0. En la cuarta iteraci n, la funci n fscanf detectar a a o o a el car cter EOF y por lo tanto no podr leer ning n valor v lido. As pues, en vec[3] no almaa a u a cenamos nada (se queda como estaba, con un valor aleatorio). Podremos saber que esta situaci n ha o ocurrido consultando el valor retornado por la funci n fscanf. En este ejemplo, como s lo leemos o o una variable, fscanf debe retornar 1 si ha podido realizar una lectura correcta. N tese que el proo grama s lo incrementa el valor de lon si la lectura ha sido correcta. Despu s de la cuarta iteraci n, o e o feof retornar un valor diferente de 0 (cierto). a
113
11. Ficheros
11.3.2 ferror
La cabecera de esta funci n es la siguiente: o int ferror( FILE *fp ) La funci n ferror retorna un valor diferente de 0 si ha ocurrido alg n error en una lectuo u ra/escritura previa en el chero se alado por fp. En caso contrario retorna 0. n
11.3.3 fflush
La cabecera de esta funci n es la siguiente: o int fflush( FILE *fp ) Cuando escribimos en un chero, en realidad la escritura no se produce en el mismo momento de ejecutar la funci n fprintf. Sin embargo, esta funci n deja la informaci n a escribir en un o o o buffer temporal del sistema operativo. M s tarde, cuando el sistema operativo lo decida (est libre a e de otras tareas, por ejemplo), se vuelca el contenido de dicho buffer sobre el chero fsico. De esta forma en un computador con varios usuarios se puede organizar de forma m s eciente el acceso a las a unidades de almacenamiento de informaci n. La funci n fflush puede utilizarse para forzar en o o el instante deseado el volcado del buffer sobre el chero. Si no se produce ning n error, la funci n u o fflush retorna 0, en caso contrario retorna EOF. Un ejemplo tpico del uso de fflush es la depuraci n de programas. Supongamos que tenemos o un error en un programa y para encontrarlo ponemos diversos fprintf, que muestran valores de algunas variables. Si no ponemos despu s de cada fprintf una llamada a fflush, no veremos el e valor que queremos en el momento en que realmente se produce, lo que nos puede llevar a conclusiones err neas sobre el comportamiento del programa. o
114
El siguiente ejemplo muestra un programa para multiplicar una matriz por un vector. Los datos de entrada se leen de stdin, es decir, del teclado. Por otra parte, los datos de salida se escriben en stdout (pantalla), y los mensajes de error en stderr (tambi n la pantalla). N tese que stdin, e o stdout y stderr no est n declaradas en el programa, puesto que ya lo est n en stdio.h. Cuando a a el programa funciona usando teclado/pantalla, muestra una serie de mensajes en pantalla explicando al usuario los datos que debe introducir. Sin embargo, cuando se usan cheros, estos mensajes no tienen sentido, por lo que no son mostrados. #include <stdio.h> #define MAXFILAS 10 #define MAXCOLUMNAS MAXFILAS void main( )
int i, j, k, Nfilas, Ncolumnas; double x[MAXCOLUMNAS], y[MAXFILAS]; double A[MAXFILAS][MAXCOLUMNAS]; char car; FILE *fi = stdin; FILE *fo = stdout; FILE *fe = stderr; printf( "Entrada/Salida por ficheros? scanf( "%c", &car ); if (car == s|| car == S) (s/n)" );
f g f g f g g
fo = fopen( "Salida.txt", "w" ); if (fo == NULL) printf( "Error abriendo Salida.txtnn" ); exit(0);
fe = fopen( "Errores.txt", "w" ); if (fe == NULL) printf( "Error abriendo Errores.txtnn" ); exit(0);
115
11. Ficheros
f g
if (fo == stdout) fprintf( fo, " N. columnas = " ); fscanf( fi, "%d", &Ncolumnas ); if (Ncolumnas > MAXCOLUMNAS)
f g
for (i= 0; i< Nfilas; i++) for (j= 0; j< Ncolumnas; j++)
if (fo == stdout) fprintf( fo, "A[%d][%d] = ", i, j ); k = fscanf( fi, "%lf", &A[i][j] ); if (k != 1)
f g f g
for (i= 0; i< Nfilas; i++) if (fo == stdout) fprintf( fo, "x[%d] = ", i ); k = fscanf( fi, "%lf", &x[i] ); if (k != 1)
f g f g
for (i= 0; i< Nfilas; i++) y[i] = 0.0; for (j= 0; j< Ncolumnas; j++) y[i] = y[i] + A[i][j] * x[j];
11.5. Ejercicios
116
g
for (i= 0; i< Nfilas; i++) fprintf( fo, "y[%d] = %lfnn", i, y[i] ); if (fi != stdin) fclose( fi ); if (fo != stdout) fclose( fo ); if (fe != stderr) fclose( fe );
11.5 Ejercicios
1. Se dispone de dos cheros con n meros enteros ordenados de menor a mayor. Escribir los siu guientes programas de forma que el chero resultante contenga los n meros ordenados de mayor u a menor. Un programa que construya un chero con todos los n meros que est n en ambos cheros u a simult neamente (AND de cheros). a Un programa que construya un chero con todos los n meros que est n en cualquiera de u a los dos cheros (OR de cheros). En el chero resultante no debe haber n meros repetidos. u Un programa que construya un chero con los n meros que est n en cualquiera de los dos u a cheros, pero no en los dos simult neamente (XOR de cheros). a 2. Se dispone de un chero que contiene texto y se pretende realizar una compactaci n del mismo. o Para ello se substituyen las secuencias de cuatro o m s caracteres blancos por la secuencia #n# a , donde n indica el n mero de caracteres blancos que se han substituido. Para evitar confuci n, u o el car cter # se sustituye por ##. Dise ar una funci n que lea de un chero un texto no a n o compactado y lo compacte seg n los criterios expuestos. Dise ar tambi n otra funci n que lea u n e o un chero de texto resultado de una compactaci n previa y lo descompacte. o 3. Se dispone de un chero que contiene un n mero no determinado de etiquetas. Cada etiqueta u es un conjunto de datos sobre un determinado individuo (nombre, direcci n, tel fono, etc). La o e etiqueta est formada por 3 lneas consecutivas de texto. Cada lnea de texto tiene 15 caracteres a como m ximo. Las etiquetas est n separadas por una lnea que contiene unicamente el car cter *. a a a Se desea dise ar un programa que permita construir un nuevo chero que contenga las etiquetas n del chero original pero organizadas en columnas de 3 etiquetas (que empezar n respectivamente a en las columnas 0, 20 y 40). Supondremos que las lneas de un chero pueden tener un m ximo de a 80 caracteres. Las las de etiquetas deben estar separadas por una lnea en blanco. Por ejemplo: Fichero de Entrada Juan Prez e c/ Aragn o Tlf. 932 491 134
117
11. Ficheros
Pedro Lpez o Avd. Europa Tlf. 931 113 456 Juan Garca c/ Gracia Lrida e Andrs Villa e Tlf. 931 113 457 Badalona Pedro Cubero Tlf. 971 456 789 Mallorca
Fichero de Salida Juan Prez e c/ Aragn o Tlf. 932 491 134 Andrs Villa e Tlf. 931 113 457 Badalona
Pedro Lpez o Avd. Europa Tlf. 931 113 456 Pedro Cubero Tlf. 971 456 789 Mallorca
4. Se dispone de un chero compuesto unicamente por letras may sculas, espacios en blanco, comas u y puntos. El contenido de este chero tiene las siguientes caractersticas: Entre palabra y palabra puede haber cualquier n mero de espacios en blanco. u Entre una palabra y un signo de puntuaci n puede haber cualquier n mero de espacios en o u blanco. Entre un signo de puntuaci n y una palabra puede haber cualquier n mero de espacios en o u blanco. El primer y ultimo car cter del texto de entrada es una letra. a Debemos realizar un algoritmo que escriba en un chero de caracteres el contenido del chero de entrada formateado de tal manera que en el texto resultante se cumplan los siguientes requisitos: Todas las palabras estar n escritas con letras min sculas excepto la primera letra despu s a u e de un punto y la primera letra del texto. Entre palabra y palabra s lo puede haber un blanco. o Entre la ultima letra de una palabra y un signo de puntuaci n no debe haber ning n blanco. o u
11.5. Ejercicios
118
Entre un signo de puntuaci n y la primera letra de una palabra debe haber un espacio en o blanco. El ultimo car cter debe ser un punto. a
119
A. El preprocesador
Ap ndice A e
El preprocesador
El preprocesador es una herramienta muy util para el programador. Las directivas del preprocesador son en realidad simples comandos de edici n, es decir, comandos que modican el chero con c digo o o fuente del programa, de igual forma que lo haramos mediante un editor de textos. El chero modicado por el preprocesador sigue siendo un chero de texto. Las directivas del preprocesador se distinguen de las lneas de c digo C porque empiezan con el o smbolo # en la primera columna. Es importante hacer notar que es obligatorio que el smbolo # est e en la primera columna, ya que en caso contrario se gener un error de compilaci n. a o En este ap ndice veremos las directivas m s importantes del preprocesador. e a
120
El principal uso de la directiva define es substituir un texto por otro texto. Por ejemplo: #define N 100 signica que el preprocesador sustituir el smbolo N por el texto 100 dentro del programa. A a continuaci n se muestra un fragmento de c digo antes y despu s de ser tratado por el preprocesador: o o e Antes del preprocesador ... for (i= 0; i< N; i++) Numeros[i] = i; ... Despu s del preprocesador e ... for (i= 0; i< 100; i++) Numeros[i] = i; ... N tese que la palabra Numeros no ha sido substituida por 100umeros. S lo se ha substituido el o o texto N all donde las reglas sint cticas de C indican que dicho texto es el nombre de un smbolo. a Esto se aprovecha para la denici n de constantes. Normalmente estas constantes son las dimeno siones m ximas de tablas del programa. De esta forma, cuando deseemos modicar estas dimensiones, a bastar modicar la lnea de c digo que contiene el define, sin tener que buscar por el programa a o todas las apariciones de las constantes. La directiva define tiene otros usos importantes que veremos en las secciones A.3 y A.4.
121
A. El preprocesador
existe un define que dena el smbolo nombre el c digo que nalmente se complia corresponde al o fragmento indicado por cdigo1. En caso contrario, el c digo compilado corresponde a cdigo2. o o o Por otra parte, en la directiva ifndef, si no existe un define para el smbolo nombre, el c digo compilado es el correspondiente a cdigo1. En caso contrario, el c digo compilado es el o o o correspondiente a cdigo2. En ambas directivas el uso de else es optativo. Veamos algunos o ejemplos. Supongamos que un programa debe mostrar ciertos valores en pantalla para estar seguros de su funcionamiento. Pero esto s lo es necesario hacerlo mientras el programa est en la fase de desarrollo. o a Una vez nalizada esta fase, no es necesario que muestre toda esa informaci n. Una soluci n consistir o o a en borrar manualmente las lneas de c digo pertinentes, pero si el programa es grande (miles o millones o de lneas de c digo) podemos cometer errores f cilmente al eliminar dichas lneas. En el siguiente o a c digo, el smbolo DEBUG controla si se compila o no el printf. N tese que la directiva #define o o DEBUG no le asigna valor a la constante DEBUG, smplemente la dene como smbolo. #define DEBUG ... for (i= 0; i< Nfilas; i++) y[i] = 0.0; for (j= 0; j< Ncolumnas; j++)
#ifdef DEBUG printf( "y[%d]= %lf, x[%d]= %lf, A[%d][%d]= %lfnn", i, y[i], j, x[j], i, j, A[i][j] ); #endif y[i] = y[i] + A[i][j] * x[j];
Supongamos ahora que necesitamos mostrar en pantalla los recursos que usa un programa (memoria, tiempo de ejecuci n, etc). Para ello debemos llamar a una funci n del sistema operativo. Pero en o o cada sistema operativo el nombre de dicha funci n puede ser diferente, o puede que incluso no exista o dicha funci n. El siguiente c digo muestra una soluci n para que, en cualquier caso, el programa se o o o pueda compilar sin problemas: ... printf( "Recursos usados por el programann" ); #ifdef WINDOWS printf( "Funcion no disponible en sistema WINDOWSnn" ); #else getrusage( RUSAGE SELF, &rusage ); ... #endif ...
A.4. Macros
122
A.4 Macros
La directiva define tambi n permite denir macros. La sintaxis de una macro es la siguiente: e #define nombreMacro( param1, param2, ... ) cdigo o
donde cdigo es un conjunto v lido de sentencias en C, y param1, etc. son smbolos que aparecen o a en cdigo. Cuando el preprocesador se ejecuta, substituye cada llamada a la macro por el texto o escrito en cdigo, substituyendo dentro de cdigo los smbolos param1, etc. por los valores que o o tengan en la llamada a la macro. Veamos un ejemplo: #define SWAP( p1, p2, p3 ) p3=p1; p1=p2; p2=p3;
En el c digo anterior hemos denido la macro SWAP. Esta macro tiene tres par metros p1, p2 y o a p3. Donde esta macro sea invocada, el preprocesador susbtituir la macro por el c digo indicado. Esta a o macro sirve para intercambiar los valores de los par metros p1 y p2, usando el par metro p3 como a a una variable temporal. Veamos a continuaci n qu hara el preprocesador con dos llamas a la macro o e SWAP en el siguente programa: Antes del preprocesador double x, y, z; int a, b, c; ... SWAP ( x, y, z ); SWAP ( a, b, c ); ... Despu s del preprocesador e double x, y, z; int a, b, c; ... z=x; x=y; y=z; c=a; a=b; b=c; ... Una macro siempre se puede substituir por una funci n. Pero si las sentencias de la macro son muy o simples, podemos gastar m s tiempo llamando y retornando de la funci n que ejecutando su c digo. a o o Adem s, en un ejemplo como el anterior vemos que la misma macro sirve para valores enteros, reales, a caracteres, estructuras, etc. Sin embargo necesitaramos una funci n diferente para cada tipo de datos o distinto.
123
Ap ndice B e
124
que s2, respectivamente. char *strcpy( char dest[], char src[] ) Copia la cadena scr en la cadena dest. La cadena dest debe tener suciente espacio para albergar la cadena src. char *strdup( char s[] ) Devuelve un puntero a una nueva cadena que es un duplicado de la cadena s. int strlen( char s[] ) Devuelve la longitud de la cadena s, sin contar el car cter a n0. char *strncat( char dest[], char src[], int n ) Similar a strcat, a excepci n de que s lo se concatenan al nal de dest, los n primeros caracteres de la cadena o o src. int strncmp( char s1[], char s2[], int n ) Similar a strcmp, a excepci n o de que s lo se comparan los n primeros caracteres de ambas cadenas. o char *strncpy( char dest[], char src[], int n ) Similar a strcpy, a excepci n de que s lo se copian en dest los n primeros caracteres de la cadena src. o o int strncasecmp( char s1[], char s2[], int n ) Similar a strcasecmp, a excepci n de que s lo se comparan los n primeros caracteres de ambas cadenas. o o char *strrchr( char s[], int c ) Devuelve un puntero a la posici n de la ultima o ocurrencia del car cter c en la cadena s. a char *strstr( char s1[], char s2[] ) Devuelve un puntero a la posici n de la o primera ocurrencia de la cadena s2 en la cadena s1.
125
Escribe en pantalla la lista de argumentos de acuerdo con el formato especicado para cada uno de ellos, y devuelve el n mero de caracteres escritos. El formato consta de caracteres ordinarios (que se escriben u directamente en pantalla) y de especicadores de formato denotados por el car cter %. Debe haber a tantos especicadores de formato como argumentos. La forma general de uno de estos especicadores es la siguiente: %[-|+][ancho][.prec][h|l|L]tipo donde: tipo especica el tipo de datos del argumento seg n la tabla: u tipo c i, d o x, X u s f e, E g, G p % Argumento char int char * double/oat puntero ninguno Formato de salida car cter a entero decimal: dgitos 0, . . . , 9 entero octal: dgitos 0, . . . , 7 entero hexadecimal: dgitos 0, . . . , 9, A, . . . , F entero decimal sin signo cadena de caracteres hasta n0 [-]dddd.dddd notaci n cientca: [-]d.dddd[e/E][+/-]ddd o la forma m s compacta entre %f y %e a direcci n de memoria o car cter % a
[h|l|L] como modicador del tipo de datos b sico. Se usa h para short int, l para a long int y double, y L para long double. [.prec] n mero de decimales al escribir un n mero de coma otante, o n mero de caracteres u u u al escribir una cadena de caracteres. [ancho] n mero de espacios empleados para la escritura. Si es inferior al necesario se ignora. u ancho puede tomar dos valores: n Se emplean n espacios rellenando con blancos el espacio sobrante. 0n Se emplean n espacios rellenando con 0s el espacio sobrante. [-|+] Se usa - para justicar a la izquierda rellenando con blancos, y + para forzar la escritura del signo en los n meros. u Veamos algunos ejemplos ilustrativos: printf( printf( printf( printf( printf( "%030.5f", 1.5236558 ); "%+30.5f", 1.5236558 ); "%+-030.5f", 1.5236558 ); "%8.3s", "hola" ); "%-08.3s", "hola" ); 000000000000000000000001.52326 +1.52326 +1.523260000000000000000000000 hol hol00000
126
Lee datos del teclado (car cter a car cter) y los coloca en las direcciones de memoria especicadas en la a a lista de argumentos de acuerdo con el formato. Devuelve el n mero de argumentos ledos. El formato u consta de caracteres ordinarios (que se espera se tecleen) y de especicadores de formato denotados por el car cter %. Debe haber tantos especicadores de formato como direcciones de argumentos donde a almacenar los datos ledos. La forma general de uno de estos especicadores es la siguiente: %[*][ancho][h|l|L]tipo donde: tipo especica el tipo de datos del argumento seg n la tabla: u tipo c i d o x u I D O X U s f e, E g, G p % Argumento char * int * long int * char [] double/oat puntero ninguno Entrada esperada car cter a entero decimal, octal o hexadecimal entero decimal entero octal entero hexadecimal entero decimal sin signo entero decimal, octal o hexadecimal entero decimal entero octal entero hexadecimal entero decimal sin signo cadena de caracteres hasta blanco, tabulador o salto de lnea n mero en coma otante u direcci n de memoria hexadecimal: YYYY:ZZZZ o ZZZZ o car cter % a
* no asigna el argumento ledo a ninguna variable de los argumentos. El resto de campos del modicador son id nticos al caso de printf. Sin embargo existen un par e de convenciones especiales que merece la pena destacar: %[set] que permite leer una cadena de caracteres hasta encontrar un car cter que no pertenezca a al conjunto set especicado. Dicho car cter no se lee. a %[set] que permite leer una cadena de caracteres hasta encontrar un car cter que pertenezca a al conjunto set especicado. Dicho car cter no se lee. a
B.2.3 Ficheros
int fclose( FILE *fich ) Cierra el chero fich y devuelve un c digo de error. o int feof( FILE *fich ) Comprueba si se ha llegado al nal del chero fich.
127
int ferror( FILE *fich ) Comprueba si se ha producido alg n error en alguna operau ci n sobre el chero fich. o int fflush( FILE *fich ) Fuerza la escritura en disco de las escrituras diferidas realizadas sobre fich. int fgetc( FILE *fich ) Lee un car cter de fich. a char *fgets( char string[], int max, FILE *fich ) Lee una cadena de hasta max caracteres de fich. FILE *fopen( char nombre[], char modo[] ) Abre el chero con el nombre y modo de apertura especicados. modo puede ser: "r" para lectura, "w" para escritura y "a" para a adir informaci n al nal del chero. n o int fprintf( FILE *fich, char formato[], ... fich. Ver printf. ) Escritura con formato en
int fputc( int c, FILE *fich ) Escribe el car cter c en fich. a int fputs( char string[], FILE *fich ) Escribe una cadena de caracteres en fich. int fscanf( FILE *fich, char formato[], ... fich. Ver scanf. int getc( FILE *fich ) Lee un car cter de fich. a int putc( int c, FILE *fich ) Escribe el car cter c en fich. a void rewind( FILE *fich ) Sit a el cursor para lecturas/escrituras de fich al prinu cipio del mismo. int sprintf( char string[], char formato[], ... mato en una cadena caracteres. Ver printf. int sscanf( char buffer[], char formato[], ... de una cadena de caracteres. Ver scanf. ) Escritura con for) Lectura con formato de
int ungetc( int c, FILE *fich ) Devuelve el car cter c, ledo previamente, al a chero fich de donde fue ledo.
128
double atan2( double y, double x ) Calcula el arco tangente de y/x. double ceil( double x ) Calcula el entero m s peque o que es mayor que x. a n double cos( double x ) Calcula el coseno de x en radianes. double cosh( double x ) Calcula el coseno hiperb lico de x. o double exp( double x ) Calcula ex . double fabs( double x ) Calcula el valor absoluto de x. double floor( double x ) Calcula el entero m s grande que es menor que x. a labs( long n ) Calcula el valor absoluto de n. double log( double x ) Calcula el logaritmo natural de x. double log10( double x ) Calcula el logaritmo en base 10 de x. double pow( double x, double y ) Calcula xy . double sin( double x ) Calcula el seno de x en radianes. double sinh( double x ) Calcula el seno hiperb lico de x. o double sqrt( double x ) Calcula la raz cuadrada de x. void srand( unsigned seed ) Fija un nuevo germen para el generador de n meros u aleatorios (rand). double tan( double x ) Calcula la tangente de x en radianes. double tanh( double x ) Calcula la tangente hiperb lica de x. o
129
int ispunct( int c ) Devuelve cierto si c es un smbolo de puntuaci n. o int isspace( int c ) Devuelve cierto si c es un car cter de espaciado. a int isupper( int c ) Devuelve cierto si c es una letra may scula. u int isxdigit( int c ) Devuelve cierto si c es un dgito hexadecimal. int toascii( int c ) Obtiene el c digo ASCII de c. o tolower( int c ) Convierte c a min scula. u int toupper( int c ) Convierte c a may scula. u
130
de necesitarla. C dispone de las siguiente funciones para gestionar de forma din mica la memoria, todas a ellas est n denidas en el chero stdlib.h: a void *malloc( size t num bytes ) Reserva un bloque de memoria de num bytes bytes. Devuelve un puntero al primer byte del bloque de memoria reservado, o NULL si no hay suciente memoria disponible. void *calloc( size t num elems, size t tam elem ) Reserva un bloque de memoria capaz de almacenar num elems de tam elem bytes cada uno. Este espacio de memoria es inicializado con ceros. Devuelve un puntero al primer byte del bloque de memoria reservado, o NULL si no hay suciente memoria disponible. void *realloc( void *ptr, size t num bytes ) Cambia el tama o del bloque de memoria apuntado por ptr para que tenga num bytes bytes. n Devuelve un puntero al primer byte del nuevo bloque de memoria reservado, o NULL si no hay suciente memoria disponible. void free( void *ptr ) Libera el bloque de memoria apuntado por ptr. Dicho bloque debe haber sido previamente obtenido mediante malloc, calloc o realloc. Si ptr es NULL, no se hace nada. u El tipo de datos size t es un n mero natural (sin signo). Cuando llamamos a estas funciones y les pasamos como par metros varibles de tipo entero (short, int o long), se realiza una conversi n a o de tipo de forma autom tica. a Junto con estas funciones usaremos el operador de C sizeof(tipo de datos). Este operador a retorna el n mero de bytes que ocupa una variable del tipo tipo de datos, tanto si este tipo est u predenido en C (int, float, etc.) como si es un tipo denido por el programador. Veamos algunos ejemplos que ilustran el empleo de memoria din mica. a El siguiente ejemplo gestiona de forma din mica dos vectores. a #include <stdio.h> #include <stdlib.h> typedef struct
f g
131
do
f g
printf( "Cuntos alumnos hay?nn" ); a scanf( "%d", &lon ); while( lon < 0 );
nota = malloc( lon*sizeof(double) ); alumno = malloc( lon*sizeof(Tpersona) ); if ((nota == NULL) || (alumno == NULL))
f g
*/
for (i= 0; i< lon; i++) printf( "Alumno:%d nombre:%s DNI:%ld nota:%lfnn", i, alumno[i].nom, alumno[i].DNI, nota[i] ); free( alumno ); free( nota );
Hay varios puntos a destacar: Notar que en la declaraci n de variables no declaramos ning n vector. En su lugar declaramos o u punteros. En este caso un puntero al tipo double y otro al tipo Tpersona. En estos punteros almacenamos las direcciones de memoria que devuelve malloc. Para indicarle a la funci n malloc qu cantidad de bytes de memoria necesitamos, hemos o e usado el operador sizeof. Los bytes requeridos son el n mero de elementos multiplicado por u el tama o en bytes de cada elemento. Cabe notar que sizeof se puede usar tambi n con tipos n e denidos por el programador como el tipo Tpersona. Despu s de llamar a malloc comprobamos que los punteros no sean NULL. Si lon es muy e grande, puede ocurrir que el computador no tenga memoria suciente. Esta comprobaci n de o error siempre debe hacerse. Cuando tenemos los bloques de memoria ya reservados, podemos acceder a ellos a trav s de los e punteros. En este caso, la notaci n para acceder a trav s de los punteros es id ntica a la que se o e e usa con vectores. Pero no debemos olvidar que alumno y nota son punteros. En el momento en que los bloques de memoria ya no son necesarios, debemos liberarlos. Para ello usamos la funci n free. Cualquier intento de acceder a los bloques de memoria despu s o e de la llamada a free generara un error de ejecuci n. o
132
En este ejemplo multiplicamos la matriz A por el vector x dejando el resultado en el vector y. #include <stdio.h> #include <stdlib.h>
f g f g
printf( " Nmero de filas?nn" ); u scanf( "%d", &nfil ); while( nfil < 0 );
do printf( " Nmero de columnas?nn" ); u scanf( "%d", &ncol ); while( ncol < 0 );
A = malloc( nfil*ncol*sizeof(double) ); x = malloc( ncol*sizeof(double) ); y = calloc( nfil, sizeof(double) ); if ((x == NULL) || (y == NULL) || (A == NULL))
f g
... /* Introduccin del vector x y la matrix A */ o ... for (i= 0; i< nfil; i++)
f g
for (i= 0; i< nfil; i++) printf( "y[%d] = %lfnn", i, y[i] ); free( A );
133
free( x ); free( y );
Los puntos m s destacables son los siguientes: a Para reservar memoria para el vector y utilizamos la funci n calloc. De esta forma el bloque o de memoria queda inicializado a cero y no es necesario inicializar cada componente de y en el algoritmo de multiplicaci n. o La notaci n para acceder a los bloques de memoria x e y a trav s de los punteros coincide con o e la que usaramos si fuesen vectores declarados de forma est tica. Pero no pasa lo mismo con la a matriz. Finalmente, en este ejemplo modicamos el tama o de un bloque de memoria, que previamnete n haba sido reservado mediante la funci n malloc. o #include <stdio.h> #include <stdlib.h>
f g
printf( "Longitud del vector?nn" ); scanf( "%d", &lon1 ); while( lon1 < 0 );
f g
134
f g
free( vec );
La funci n realloc nos permite modicar el tama o del bloque de memoria reservado, pero no o n modica los datos almacenados en dicho bloque. Es decir: Si lon2 < lon1, tendremos un bloque de memoria m s peque o, pero los lon2 valores a n almacenados seguir n siendo los mismos. a Si lon2 > lon1, tendremos un bloque de memoria m s grande. Los primeros lon1 vaa lores ser n los mismos que haba antes de la llamada a realloc, mientras que los lon2 a lon1 valores nales ser n aleatorios (no estar n inicializados). a a
135
C. Sistemas de numeraci n o
Ap ndice C e
Sistemas de numeraci n o
Un computador usa el sistema de numeraci n en base 2 debido a c mo funcionan los dispositivos o o electr nicos b sicos (los transistores) que lo forman. En el sistema de numeraci n en base dos s lo o a o o existen 2 cifras el 0 y el 1. A las cifras de un n mero en base 2 se las denomina bits. A un grupo de 8 u bits se le denomina byte.
C.1 Naturales
Los n meros naturales se representan mediante un c digo llamado binario natural, que consiste simu o u plemente en expresar el n mero en base 2. Si disponemos n bits para representar n meros naturales u n combinaciones que se muestran en la tabla C.1. tendremos las 2
C.2 Enteros
Los n meros enteros se representan mediante un c digo llamado complemento a 2. Este c digo se usa u o o porque simplica notablemente la construcci n de los circuitos electr nicos necesarios para realizar o o operaciones aritm ticas, fundamentalmente sumas y restas. e El complemento a 2 de un n mero se calcula de la siguiente forma: si el n mero es positivo su u u complemento a 2 coincide con su expresi n en binario natural; si el n mero es negativo se debe escribir o u la representaci n en binario natural de su m dulo (valor absoluto), complementar a 1 dicha expresi n o o o Tabla C.1: Representaci n de n meros naturales en binario natural o u Valor Decimal
0 1 2 3 4
n;1
...
C.3. Reales
136
;2n; ;
n;
...
1 0 1
...
1
+2
(cambiar los 0 por 1 y viceversa), y nalmente sumarle 1. Por ejemplo si disponemos de 4 bits para representar n meros enteros y deseamos representar el n mero ;3, tendramos que: u u 3 = 0011 1100 + 1 -3 = 1101 En general, si disponemos de muestran en la tabla C.2. Complemento a 1 Complemento a 2
La codicaci n en complemento a 2 tiene algunas propiedades interesantes como las siguientes: o El cero tiene una unica representacion: 0...0 = 1...1 + 1 Todos los n meros del mismo signo empiezan por el mismo bit: 1 para los negativos, 0 para los u positivos La suma/resta de numeros en complemento a 2 se puede realizar mediante la suma binaria bit a bit, despreciando el ultimo acarreo. Por ejemplo: -2 - 1 = -3 2 - 1 = 1 1....10 0...010 + + 1...1 1...1 = = 1...10 0...01
C.3 Reales
Los n meros reales se representan mediante un c digo llamado coma otante. Este c digo es simpleu o o mente la representaci n en notaci n cientca normalizada y en base 2. o o Recordemos que un numero en notaci n cientca se representa mediante la siguiente expresi n: o o Donde la mantisa es un n mero real con la coma decimal colocada u a la derecha o a la izquierda de la primera cifra signicativa (normalizaci n por la derecha o por la o o izquierda); la base es la misma que la base de numeraci n usada para representar mantisa y exponente; u u y el exponente es un n mero entero. Los siguientes ejemplos muestran n meros representados en notaci n cientca en base 10, normalizados por la derecha y por la izquierda: o
mantisa baseexponente.
137
C. Sistemas de numeraci n o
;2e; ;
e;
...
1 0 1
...
1
+2
3 141592
2 53547
10
10
3
= =
0 3141592 10
0 253547
10
2
La notaci n cientca usada por los computadores tiene algunos detalles especiales debidos a que o usa la base 2. 1. En la memoria unicamente se almacena: una secuencia de m bits que representa la mantisa, una secuencia de e bits que representa el exponente, el signo de la mantisa se almacena usando 1 bit (0 signica positivo y 1 signica negativo). La base no es necesario almacenarla, ya que siempre es 2. Si pudi semos ver un n mero real e u almacenado en la memoria del computador veramos una secuencia de bits como la siguiente:
signo
101
010
mantisa
exponente
10010
2. En base 2 s lo existen 2 cifras, el 0 y el 1. As pues, el primer bit signicativo siempre ser un 1, o a por lo que este primer bit no se almacena en la memoria. Los circuitos electr nicos que operan o con datos en coma otante ya tienen en cuenta que delante de todos los bits de la mantisa siempre hay un 1. A este bit que no se almacena se le denomina bit oculto o implcito. u 3. El exponente se almacena usando un c digo llamado exceso 2e;1 , donde e es el n mero o de bits usados para almacenar el exponente. Este c digo proviene de rotar de forma cclica o e;1 posiciones la tabla del complemento a 2 (ver Tab. C.3). 2 Para calcular un c digo en exceso podemos usar la siguiente f rmula: o o Valor decimal
=
e;
Notar que en el c digo en exceso todos los n meros negativos comienzan por 0 y todos los o u n meros positivos comienzan por 1. Adem s, a valores crecientes les corresponden c digos que u a o ledos en binario natural tambi n son crecientes. Es decir: ;2 < 1 , 01 : : : 10 < 10 : : : 01. e De esta forma podemos comparar n meros de un c digo en exceso usando un simple comparador u o de n meros en binario natural. Sin embargo, los circuitos para sumar/restar n meros codicados u u en exceso, ya no ser n un simple sumador binario. Por razones hist ricas se decidi usar el a o o c digo en exceso en vez del c digo en complemento a 2 para representar los exponentes. o o
C.3. Reales
138
x x x ::: x
Mantisa
Exponente
0 0 0
0 0 1
4. Debido al uso del bit oculto, el n mero cero no se puede representar, ya que sea cual sea la manu tisa, esta nunca ser cero. Dado que el n mero cero es un n mero importante, que es necesario a u u poder representar, se hace una excepci n a la regla de representaci n. De forma arbitraria se o o decide que el n mero cero se representa mediante la combinaci n en que todos los bits de la u o mantisa y el exponente son ceros. 5. Adem s, las diferentes combinaciones de mantisa con el exponente cuyo c digo son todo ceros a o tampoco se usan como n meros. Estos c digos se reservan como c digos de error, generados u o o por los circuitos aritm ticos cuando se producen condiciones de error como: una divisi n en que e o el divisor es cero, la raz cuadrada de un n mero negativo, etc. A estos c digos de error se les u o denomina NAN (del ingles, Not A Number). Por lo tanto:
El n mero representable m s cercano a cero es 1:0 u a toda su mantisa con ceros y el exponente es la combinaci n o
(2
1 1
; ;
1) 1)
::: :::
01 1
El n mero representable m s grande es u a ) tiene toda su mantisa con unos y el exponente es la combinaci n o
;m 2(1 ; 2
(
:::
+1)
01
:::
En la tabla C.4 se muestran ordenados los c digos de un formato de coma otante. El smbolo o x signica cualquier bit 0 o 1. 6. Cabe notar que cuantos m s bits se usen para representar la mantisa, mayor precisi n se tiene en a o la representaci n (menor error relativo). Cuantos m s bits usemos para representar el exponente o a mayor rango num rico abarcaremos. e Si cada computador usase su propio sistema de representaci n en coma otante, podramos tener o valores diferentes al ejecutar el mismo programa en computadores diferentes. Para evitar este problema, la representaci n en coma otante est estandarizada por la organizaci n IEEE (Institute of Electrical o a o and Electronics Engineers) bajo el est ndar IEEE-754. Actualmente todos los computadores cumplen a con dicho est ndar. a
139
C. Sistemas de numeraci n o
Debido a que el rango de representaci n es nito, las operaciones aritm ticas no tienen las propieo e dades habituales. Por ejemplo, la suma ya no es asociativa. Supongamos un formato de coma otante donde el n mero m ximo representable es el 8:0. Entonces tendremos que: u a
(8 0 + 2 0)
8 0 + (2 0
: ; : 6 : : ; : : ; : : ; : ) ERROR : ; : : ; : : ) CORRECTO
(8 0 + 2 0)
4 0 = 8 0 + (2 0 4 0
4 0)
4 0 = 10 0
4 0) = 8 0
2 0 = 6 0
A esta situaci n se la denomina overow (desbordamiento por arriba), y ocurre cuando una operao ci n genera un n mero mayor que el m ximo representable. Adem s del overow puede producirse el o u a a underow (desbordamiento por abajo) cuando el resultado de una operaci n cae en la zona de n meros o u situados entre 0 y el n mero mnimo representable. u En conclusi n, al realizar c lculos en coma otante deberemos tener en cuenta el orden en el que o a se realizan las operaciones, para evitar las citadas situaciones de error.
C.3. Reales
140
141
Ap ndice D e
142
Tabla D.1: Caracteres y sus c digos ASCII o ASCII 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 char NUL n0 SOH STX ETX EOT ENQ ACK BEL na BS nb HT nt LF nn VT nv FF nf CR nr SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US ASCII 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 char SP ! # $ % & ASCII 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 char @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ ASCII 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 char a b c d e f g h i j k l m n o p q r s t u v w x y z
( )
+
;
. / 0 1 2 3 4 5 6 7 8 9 : ; = ?
n
]
DEL
143
Ap ndice E e
144
The Development of the C Language Dennis M. Ritchie History of Programming Languages Conference (HOPL-II), 1993. http://cm.bell-labs.com/cm/cs/who/dmr/chist.html Interesante artculo de divulgaci n que describe la historia de la creaci n del lenguaje C, o o all por los a os 70. a n WEB: Programming in C http://www.lysator.liu.se/c/index.html Un sitio WEB muy interesante. Incluye multitud de enlaces a recursos en Internet sobre programaci n en C, historia del lenguaje, curiosidades, etc. o WEB: Frequently Asked Questions about C http://www.eskimo.com/ scs/C-faq/top.html En este sitio WEB encontraremos una recopilaci n de las preguntas m s habituales sobre o a programaci n en lenguaje C del grupo de noticias en Internet comp.lang.c . o