Download - Introducción a la programación en c

Transcript
Page 1: Introducción a la programación en c

AULA POLITÈCNICA / ETSETB

EDICIONS UPC

Marco A. Peña BasurtoJosé M. Cela Espín

Introducción ala programación en C

Page 2: Introducción a la programación en c

Primera edición: septiembre de 2000

Diseño de la cubierta: Manuel Andreu

© Los autores, 2000

© Edicions UPC, 2000Edicions de la Universitat Politècnica de Catalunya, SLJordi Girona Salgado 31, 08034 BarcelonaTel.: 934 016 883 Fax: 934 015 885Edicions Virtuals: www.edicionsupc.esE-mail: [email protected]

Producción: CPET (Centre de Publicacions del Campus Nord)La Cup. Gran Capità s/n, 08034 Barcelona

Depósito legal: B-32.449-2000ISBN: 84-8301-429-7

Quedan rigurosamente prohibidas, sin la autorización escrita de los titulares del copyright, bajo las san-ciones establecidas en las leyes, la reproducción total o parcial de esta obra por cualquier medio o pro-cedimiento, comprendidos la reprografía y el tratamiento informático, y la distribución de ejemplares deella mediante alquiler o préstamo públicos.

Page 3: Introducción a la programación en c

Introduccion a la programacion en C

Marco A. PenaJose M. Cela

Departament d’Arquitectura de ComputadorsUniversitat Politecnica de Catalunya

08034 Barcelona, [email protected]

[email protected]

19 de junio de 2000

Page 4: Introducción a la programación en c

i Indice General

Indice General

Indice de Figuras v

Indice de Tablas vii

Prefacio ix

1 Conceptos basicos de programacion 11.1 Ordenador y perifericos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Bits, bytes y palabras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3 Lenguajes de programacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.3.1 Lenguajes de bajo nivel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3.2 Lenguajes de alto nivel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.4 Elaboracion de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.5 Traductores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.5.1 Ensambladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.5.2 Interpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.5.3 Compiladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2 Primer contacto con C 72.1 Un poco de historia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2 Caracterısticas del lenguaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.3 Creacion de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.4 Primeros pasos con C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.5 El modelo de compilacion de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3 Empezando a programar 133.1 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.2 Estructura de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.3 Variables y constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

3.3.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143.3.2 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.3.3 Entrada y salida de valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

3.4 Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.4.1 Operador de asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.4.2 Operadores aritmeticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Page 5: Introducción a la programación en c

Indice General ii

3.4.3 Operadores relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183.4.4 Operadores logicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193.4.5 Prioridad de operadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4 Construcciones condicionales 234.1 Construccion if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

4.1.1 Variante if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254.1.2 Variante if-else-if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4.2 El operador condicional ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.3 Construccion switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

5 Construcciones iterativas 335.1 Construccion while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335.2 Construccion do-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355.3 Construccion for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

5.3.1 El operador coma (,) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385.3.2 Equivalencia for-while . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

5.4 Las sentencias break y continue . . . . . . . . . . . . . . . . . . . . . . . . . . 385.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

6 Tipos de datos elementales 416.1 Numeros enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

6.1.1 Modificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426.1.2 Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

6.2 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446.2.1 Caracteres especiales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446.2.2 Enteros y el tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456.2.3 Conversiones de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

6.3 Numeros reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

7 Tipos de datos estructurados: Tablas 497.1 Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

7.1.1 Consulta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507.1.2 Asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507.1.3 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

7.2 Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 537.2.1 Consulta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547.2.2 Asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547.2.3 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

7.3 Tablas multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557.3.1 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

7.4 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

Page 6: Introducción a la programación en c

iii Indice General

7.4.1 Asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577.4.2 Manejo de cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . 597.4.3 Ejemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

7.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

8 Otros tipos de datos 638.1 Estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

8.1.1 Declaracion de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648.1.2 Acceso a los campos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658.1.3 Asignacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658.1.4 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

8.2 Uniones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668.2.1 Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

8.3 Tipos de datos enumerados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 688.4 Definicion de nuevos tipos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . 698.5 Tiras de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

8.5.1 Operador de negacion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708.5.2 Operadores logicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718.5.3 Operadores de desplazamiento de bits . . . . . . . . . . . . . . . . . . . . . . 71

8.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

9 Punteros 759.1 Declaracion y asignacion de direcciones . . . . . . . . . . . . . . . . . . . . . . . . . 75

9.1.1 Declaracion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769.1.2 Asignacion de direcciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

9.2 Indireccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 779.3 Operaciones con punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789.4 Punteros y tablas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 799.5 Punteros y estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 839.6 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

10 Funciones 8710.1 Generalidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8710.2 Definicion y llamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

10.2.1 Definicion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8910.2.2 Prototipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9010.2.3 Llamada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

10.3 Variables y parametros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9110.3.1 Variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9110.3.2 Variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9110.3.3 Parametros formales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

10.4 Devolucion de resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9310.5 Paso de parametros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

10.5.1 Paso de parametros por valor . . . . . . . . . . . . . . . . . . . . . . . . . . . 9410.5.2 Paso de parametros por referencia . . . . . . . . . . . . . . . . . . . . . . . . 95

Page 7: Introducción a la programación en c

Indice General iv

10.5.3 Las tablas y las funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9610.5.4 Parametros en la funcion main . . . . . . . . . . . . . . . . . . . . . . . . . 99

10.6 Recursividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10010.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

11 Ficheros 10511.1 Abrir y cerrar ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10711.2 Leer y escribir en ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10911.3 Otras funciones para el manejo de ficheros . . . . . . . . . . . . . . . . . . . . . . . . 111

11.3.1 feof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11111.3.2 ferror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11311.3.3 fflush . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

11.4 Ficheros estandar: stdin, stdout, stderr . . . . . . . . . . . . . . . . . . . 11311.5 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116

A El preprocesador 119A.1 Directiva include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119A.2 Directivas define y undef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119A.3 Directivas ifdef y ifndef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120A.4 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

B La librerıa estandar 123B.1 Manipulacion de cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . 123B.2 Entrada y salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

B.2.1 Entrada y salida basica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124B.2.2 Entrada y salida con formato . . . . . . . . . . . . . . . . . . . . . . . . . . . 124B.2.3 Ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

B.3 Funciones matematicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127B.4 Clasificacion y manipulacion de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 128B.5 Conversion de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129B.6 Manipulacion de directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129B.7 Memoria dinamica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

C Sistemas de numeracion 135C.1 Naturales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135C.2 Enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135C.3 Reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

C.3.1 Problemas derivados de la representacion en coma flotante . . . . . . . . . . . 138

D Tabla de caracteres ASCII 141

E Bibliografıa y recursos WEB 143

Page 8: Introducción a la programación en c

v Indice de Figuras

Indice de Figuras

1.1 Niveles de abstraccion en los lenguajes de programacion . . . . . . . . . . . . . . . . 21.2 Cronologıa en el desarrollo de algunos lenguajes de programacion . . . . . . . . . . . 31.3 Ciclo de vida de un programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.4 Fases en la interpretacion de un programa . . . . . . . . . . . . . . . . . . . . . . . . 51.5 Fases en la compilacion de un programa . . . . . . . . . . . . . . . . . . . . . . . . . 6

2.1 Modelo de compilacion de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

4.1 Esquema de funcionamiento de if y de if-else . . . . . . . . . . . . . . . . . . . 244.2 Esquema de funcionamiento de switch . . . . . . . . . . . . . . . . . . . . . . . . . 29

5.1 Esquema de funcionamiento de while . . . . . . . . . . . . . . . . . . . . . . . . . 345.2 Esquema de funcionamiento de do-while . . . . . . . . . . . . . . . . . . . . . . . 355.3 Esquema de funcionamiento de for . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

7.1 Representacion grafica de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . 507.2 Representacion grafica de una matriz . . . . . . . . . . . . . . . . . . . . . . . . . . . 547.3 Representacion grafica de una tabla de tres dimensiones . . . . . . . . . . . . . . . . . 56

9.1 Acceso a una matriz mediante un puntero . . . . . . . . . . . . . . . . . . . . . . . . 82

10.1 Acceso a una matriz mediante un puntero . . . . . . . . . . . . . . . . . . . . . . . . 99

11.1 Almacenamiento de un fichero de texto . . . . . . . . . . . . . . . . . . . . . . . . . 106

Page 9: Introducción a la programación en c

vii Indice de Tablas

Indice de Tablas

3.1 Palabras reservadas de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.2 Operadores aritmeticos en C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.3 Operadores relacionales y logicos en C . . . . . . . . . . . . . . . . . . . . . . . . . . 193.4 Tabla de verdad de los operadores logicos en C . . . . . . . . . . . . . . . . . . . . . 193.5 Prioridad y asociatividad de los operadores en C . . . . . . . . . . . . . . . . . . . . . 20

6.1 Representacion de enteros en decimal, octal y hexadecimal . . . . . . . . . . . . . . . 426.2 Resumen de tipos de datos enteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446.3 Caracteres interpretados como enteros . . . . . . . . . . . . . . . . . . . . . . . . . . 456.4 Resumen de tipos de datos reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

8.1 Tabla de verdad de los operadores logicos . . . . . . . . . . . . . . . . . . . . . . . . 71

C.1 Representacion de numeros naturales en binario natural . . . . . . . . . . . . . . . . 135C.2 Representacion de numeros enteros en complemento a 2 . . . . . . . . . . . . . . . . 136C.3 Representacion de numeros enteros en exceso 2

e�1 . . . . . . . . . . . . . . . . . . . 137C.4 Representacion de numeros reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

D.1 Caracteres y sus codigos ASCII . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

Page 10: Introducción a la programación en c

ix Prologo

Prologo

Este libro surge a partir de la experiencia docente de los autores en la asignatura Introduccion a losordenadores de la Escola Tecnica Superior d’Enginyeria de Telecomunicacio de Barcelona, de la Uni-versitat Politecnica de Catalunya. Como su tıtulo indica, se trata de un texto de introducion a la pro-gramacion en lenguaje C. El libro pretende cenirse a los aspectos fundamentales del estandar ANSI Cactual. Aunque hoy en dıa existen otros lenguajes de programacion muy populares como C++ o JAVA,la comprension de estos lenguajes exige un solido conocimiento de las bases de programacion en C.

El texto esta concebido como un curso completo y por lo tanto debe ser leıdo de forma secuencial.Al final de cada capıtulo hay un conjunto de ejercicios propuestos. El lector debe tratar de resolver elmayor numero posible de estos ejercicos. De igual forma es una buena practica el programar los ejem-plos resueltos en el texto. El lector debe recordar que la programacion es una tecnica aplicada, igual quetocar un instrumento musical, y por lo tanto requiere muchas horas de ensayo para ser dominada. Losejercicios propuestos son lo suficientemente simples como para no requerir conocimientos adicionalesde otras materias (matematicas, fısica, contabilidad, etc).

Uno de los puntos mas importantes para quien empieza a programar es adoptar desde el principioun buen estilo de programacion. Esto es, escribir las construcciones del lenguaje de una forma claray consistente. En este sentido, los ejemplos resueltos en el texto muestran un buen estilo basico deprogramacion, por lo que se recomienda al lector imitar dicho estilo cuando realice sus programas.

Los apendices A y B son de lectura obligada antes de comenzar a desarrollar programas de comple-jidad media. En dichos apendices el lector se familiarizara con el uso del preprocesador y de la librerıaestandar. Ambas son herramientas fundamentales para desarrollar programas.

El apendice C incluye una descripcion de los formatos de datos en el computador. Este tema noes propiamente de programcion, pero la comprension de dichos formatos ayuda al programador a en-tender mejor conceptos basicos, como las operaciones de conversion de tipos. El lector puede leer esteapendice en cualquier momento, aunque recomendamos leerlo antes del capıtulo 6.

En las referencias bibliograficas se indican algunas direcciones web donde el lector podra encontrarpreguntas y respuestas comunes de quienes se inician en el lenguaje C. Ası mismo, el lector podraencontrar materiales adicionales sobre programacion, historia del lenguaje, etc.

Page 11: Introducción a la programación en c

1 1. Conceptos basicos de programacion

Capıtulo 1

Conceptos basicos de programacion

1.1 Ordenador y perifericos

Un ordenador solo es capaz de ejecutar ordenes y operaciones muy basicas, tales como:

� Aritmetica entera: sumar, restar, multiplicar, etc.

� Comparar valores numericos o alfanumericos

� Almacenar o recuperar informacion

Con la combinacion de estas operaciones basicas, y gracias a su gran potencia de calculo, el orde-nador puede llevar a cabo procesos muy complejos. Sin embargo, en cualquier caso existe una estrechadependencia del ordenador con el programador. Es el programador quien indica a la maquina como yque debe hacer, mediante la logica y el razonamiento previo, expresado en forma de un programa.

En definitiva, el ordenador solo es capaz de aceptar datos de entrada, procesarlos y facilitar otrosdatos o resultados de salida. Los datos se introducen u obtienen del ordenador mediante los perifericosde entrada y salida. Estos son los encargados de facilitar la relacion entre el corazon del ordenador y elmundo exterior, y en particular los usuarios de ordenadores. Dependiendo de su funcion particular, losperifericos pueden clasificarse en:

Perifericos de entrada: cuya funcion es facilitar la introduccion de datos y ordenes al ordenador: te-clado, raton, lapiz optico, lector de codigo de barras, escaner, tableta digitalizadora, etc.

Perifericos de salida: cuya funcion es mostrar al exterior informacion almacenada en memoria o losresultados de las operaciones realizadas por el ordenador: pantalla, impresora, plotter, etc.

Perifericos de entrada y salida: capaces tanto de introducir como de extraer informacion del ordena-dor: discos y cintas magneticos, discos opticos, etc.

Perifericos de comunicacion: encargados de establecer y facilitar el intercambio de informacion entredos ordenadores: modem, tarjetas de red (Ethernet, Token Ring, RDSI, . . . ), etc.

Page 12: Introducción a la programación en c

1.2. Bits, bytes y palabras 2

Lenguaje máquina

Lenguaje natural

Programador

Ordenador

Lenguaje de programación

Figura 1.1: Niveles de abstraccion en los lenguajes de programacion

1.2 Bits, bytes y palabras

La unidad de memoria mas pequena en un ordenador se denomina bit (del ingles binary digit). Puedetomar unicamente dos posibles valores: 0 o 1. En ocasiones, debido a la relacion intrınseca con losvalores en las senales electricas en circuitos digitales, se dice que un bit esta bajo o alto, o bien desco-nectado o conectado. Como puede verse, no es posible almacenar mucha informacion en un solo bit.Sin embargo, un ordenador posee cantidades ingentes de ellos, por lo que podrıa decirse que los bitsson los bloques basicos con los que se construye la memoria del ordenador.

El byte, compuesto por ocho bits (algunos autores se refieren a esta unidad como octeto), es unaunidad de memoria mas util. Puesto que cada bit puede tomar el valor 0 o 1, en un byte pueden represen-tarse hasta 28 = 256 combinaciones de ceros y unos (256 codigos binarios). Con estas combinacionespueden representarse, por ejemplo, los enteros entre 0 y 255 (0 : : : 28 � 1), un conjunto de caracteres,etc.

La unidad natural de memoria para un ordenador es la palabra. Los ordenadores de sobremesaactuales, por ejemplo, suelen trabajar con palabras de 32 o 64 bits. En grandes ordenadores, el tamanode la palabra puede ser mucho mayor, pero siempre formada por un numero de bits, potencia de 2. Encualquier caso, los ordenadores encadenan dos o mas palabras de memoria con el fin de poder almacenardatos complejos y, en general, de mayor tamano.

1.3 Lenguajes de programacion

Un lenguaje de programacion podrıa definirse como una notacion o conjunto de sımbolos y caracteresque se combinan entre sı siguiendo las reglas de una sintaxis predefinida, con el fin de posibilitar latransmision de instrucciones a un ordenador. Dichos sımbolos y caracteres son traducidos internamentea un conjunto de senales electricas representadas en sistema binario, es decir, solo dos valores: 0 y 1.Esta traduccion es necesaria porque el procesador solo entiende ese lenguaje, al cual nos referiremoscomo lenguaje maquina.

Page 13: Introducción a la programación en c

3 1. Conceptos basicos de programacion

1950 1955 1960 1965 1970 1975 1980 1985 1990 1995

FORTRAN

LISP

Algol

COBOL

BASICPL/I

Logo

SIMULAProlog

Forth

Modula-2

Smalltalk

Ada

Pascal

C

JavaC++Ensamblador

Figura 1.2: Cronologıa en el desarrollo de algunos lenguajes de programacion

1.3.1 Lenguajes de bajo nivel

Se incluyen en esta categorıa aquellos lenguajes que por sus caracterısticas se encuentran mas proximosa la arquitectura del ordenador, como el lenguaje maquina y el lenguaje ensamblador.

Lenguaje maquina

Cualquier problema que deseemos resolver se plantea en primer lugar en nuestro lenguaje natural.Sin embargo, para que la secuencia de pasos que resuelven el problema pueda ser entendida por unordenador, debe traducirse a un lenguaje muy basico denominado lenguaje maquina.

El lenguaje maquina se caracteriza por ser el unico que es directamente inteligible por el ordenador,puesto que se basa en la combinacion de dos unicos sımbolos (0 y 1) denominados bits. Ademas cadaprocesador posee su propio lenguaje maquina, por lo que un programa escrito en lenguaje maquina deun procesador X no podra, en principio, ejecutarse en un procesador Y.

Lenguaje ensamblador

Constituye una evolucion del lenguaje maquina. Se basa en la utilizacion de mnemotecnicos, esto es,abreviaturas de palabras que indican nombres de instrucciones. Para programar en lenguaje ensambla-dor es necesario conocer en profundidad la estructura y funcionamiento interno del ordenador, ası comodominar el uso de diferentes sistemas de numeracion, como el binario, hexadecimal, octal, etc.

En general, los programas escritos en ensamblador requieren mucho menos espacio de memoria yse ejecutan mas rapidamente que si se hubiesen desarrollado en un lenguaje de alto nivel, puesto queestan optimizados para una arquitectura especıfica. Sin embargo, esto ultimo es un inconveniente, puescausa que los programas no sean portables de un ordenador a otro con un procesador distinto.

1.3.2 Lenguajes de alto nivel

Se engloban aquı todos los lenguajes de programacion que por sus caracterısticas se asemejan mas allenguaje natural del programador. Algunos de los mas conocidos son: FORTRAN, BASIC, Pascal,Modula, C, Ada, Java, etc. (ver Fig. 1.2).

La caracterıstica mas importante de estos lenguajes es que son independientes de la arquitectura delordenador, por lo que un programa escrito en un lenguaje de alto nivel puede ejecutarse sin problemas

Page 14: Introducción a la programación en c

1.4. Elaboracion de un programa 4

���������������������������������������

���������������������������������������

��������������������������������������������������������

��������������������������������

����������������������������

��������������������������������������������

Análisis

Diseño

Codificación

Explotación

Mantenimiento

Figura 1.3: Ciclo de vida de un programa

en otros ordenadores con procesadores distintos. Por ello, el programador no necesita conocer a fondoel funcionamiento del ordenador en el que programa, sino que el lenguaje le permite abstraerse de losdetalles de bajo nivel. Esta abstraccion de la arquitectura de la maquina implica que todo programaescrito en un lenguaje de alto nivel debera traducirse a lenguaje maquina, de forma que pueda serentendido y ejecutado por el ordenador. Para ello cada tipo de ordenador debera disponer de unosprogramas especiales que realicen dicha traduccion (ver Sec. 1.5).

1.4 Elaboracion de un programa

El desarrollo de un programa para solucionar un determinado problema informaticamente puede resu-mirse en el ya clasico concepto de ciclo de vida. Este puede desglosarse en los siguientes pasos a seguirsecuencialmente: analisis, diseno, codificacion, explotacion y mantenimiento (ver Fig. 1.3).

Analisis

En la fase de analisis se estudia cual es el problema a resolver y se especifican a muy alto nivel losprocesos y estructuras de datos necesarios, de acuerdo con las necesidades del cliente. Para realizarun buen analisis sera necesario interaccionar con el cliente y conocer a fondo sus necesidades. Antesde proceder al diseno es muy importante haber comprendido correctamente los requerimientos delproblema.

Diseno

Una vez bien definido el problema y las lıneas generales para solucionarlo, se requiere una solucionadecuada a un conjunto de recursos determinado. Tanto fısicos: en que ordenador va a funcionar laaplicacion, de que tipo de perifericos se dispone . . . , como logicos: que sistema operativo se usara, queherramientas de desarrollo, que bases de datos . . . Finalmente se disenara un conjunto de algoritmosque resuelvan los distintos subproblemas en que se haya dividido el desarrollo.

Codificacion

Consiste en la traduccion de los algoritmos disenados previamente, utilizando el lenguaje y entorno dedesarrollo escogidos en la fase anterior. Sera necesario realizar pruebas que garanticen al maximo lacalidad de los programas desarrollados. Entre otras cosas, que esten libres de errores.

La documentacion generada en esta fase junto con la de las fases anteriores sera muy util en elfuturo para las eventuales actuaciones de mantenimiento.

Page 15: Introducción a la programación en c

5 1. Conceptos basicos de programacion

Instrucción 1 Intérprete Ejecución 1

Instrucción 2 Intérprete Ejecución 2

Instrucción 3 Intérprete Ejecución 3

. . .

Figura 1.4: Fases en la interpretacion de un programa

Explotacion

Los diferentes programas desarrollados en la fase anterior se instalan en el entorno final de trabajo. Sies necesario se instalaran tambien otras herramientas de utilidad, necesarias para el correcto funciona-miento del sistema. Se debe proporcionar documentacion, manuales de usuario, formacion, etc.

Mantenimiento

En esta fase se realizaran correcciones al sistema desarrollado, bien para solventar errores no depura-dos, bien para cambiar o anadir nuevas funcionalidades requeridas por el cliente. Dependiendo de laimportancia del caso, sera necesario retomar el ciclo de vida a nivel de codificacion, diseno o inclusoanalisis (ver Fig. 1.3).

Cuanto mejor se haya documentado el desarrollo en las primeras fases del ciclo de vida, menor serael 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 lenguajemaquina. Por ello, si se programa usando lenguajes de alto nivel sera necesario algun programa traduc-tor. Este, a su vez, sera el encargado de comprobar que los programas esten escritos correctamente, deacuerdo con la definicion del lenguaje de programacion empleado. Pueden distinguirse varios tipos detraductores:

1.5.1 Ensambladores

Los programas ensambladores son los encargados de traducir a lenguaje maquina los programas escritosen lenguaje ensamblador. La correspondencia entre ambos lenguajes es muy directa, por lo que losensambladores suelen ser programas relativamente sencillos.

1.5.2 Interpretes

El objetivo de un interprete es procesar una a una las instrucciones de un programa escrito en un lenguajede alto nivel. Para cada instruccion se verifica la sintaxis, se traduce a codigo maquina y finalmente seejecuta. Es decir, que la traduccion y la ejecucion se realizan como una sola operacion (ver Fig. 1.4).

Page 16: Introducción a la programación en c

1.5. Traductores 6

ERRORES

Ejecución

Edición

Compilación

MontajeFuente

Objeto

Ejecutable

Figura 1.5: Fases en la compilacion de un programa

La principal desventaja de los interpretes es su lentitud para ejecutar los programas, pues es nece-sario verificar la sintaxis y realizar la traduccion en cada ejecucion.

1.5.3 Compiladores

La funcion de un compilador consiste en traducir un programa fuente escrito en un lenguaje de altonivel a su equivalente en codigo maquina (tambien llamado codigo objeto).

Mientras que un interprete traduce y ejecuta al mismo tiempo cada una de las instrucciones, uncompilador analiza, traduce y posteriormente ejecuta todo el programa en fases completamente separa-das (ver Fig. 1.5). Ası pues, una vez se ha compilado un programa, no es necesario volverlo a compilarcada vez. Esto hace que la ejecucion de un programa compilado sea mucho mas rapida que la de unointerpretado.

El proceso de compilacion

Edicion Consiste en escribir el programa fuente usando el lenguaje de programacion seleccionado y sugrabacion en un fichero. Para ello es necesario usar un programa editor, que puede o no formarparte del entorno de desarrollo.

Compilacion En esta fase se verifica la sintaxis del programa fuente y se traduce el programa a codigomaquina (objeto). Si se producen errores, el compilador muestra informacion del tipo de error ydonde se ha producido.

Montaje Consistente en la combinacion de los diferentes modulos objeto y librerıas del lenguaje paracrear un programa ejecutable. Esta fase se conoce tambien como linkado.

Ejecucion En esta fase se invoca al programa de la manera adecuada dependiendo del sistema operativosobre el que vaya a funcionar.

Como nota final, cabe decir que todo lenguaje de programacion puede ser tanto interpretado comocompilado. Sin embargo, dependiendo del las caracterısticas del lenguaje y del uso mayoritario a queeste destinado, es normal asociar a cada lenguaje una forma de traduccion particular. Por ejemplo, ellenguaje BASIC es mayoritariamente interpretado, mientras que C es compilado.

Page 17: Introducción a la programación en c

7 2. Primer contacto con C

Capıtulo 2

Primer contacto con C

2.1 Un poco de historia

El lenguaje de programacion C fue desarrollado por Dennis Ritchie en los Laboratorios Bell de laempresa de comunicaciones AT&T, en 1972. C fue creado inicialmente con un proposito muy concreto:el diseno del sistema operativo UNIX. Sin embargo, pronto se revelo como un lenguaje muy potente yflexible, lo que provoco que su uso se extendiese rapidamente, incluso fuera de los Laboratorios Bell.De esta forma, programadores de todo el mundo empezaron a usar el lenguaje C para escribir programasde todo tipo.

Durante anos, el estandar de facto del lenguaje C fue el definido en el libro El lenguaje de pro-gramacion C, escrito por Brian Kernighan y Dennis Ritchie en 1978. Sin embargo, con el tiempoproliferaron distintas versiones de C, que con sutiles diferencias entre ellas, empezaron a causar proble-mas de incompatibilidad a los programadores. Con el fin de cambiar esta situacion, el Instituto Nacionalde Estandares Americano (mas conocido como ANSI) creo un comite en 1983 para establecer una de-finicion estandar de C que fuese no ambigua e independiente de la arquitectura interna de cualquierordenador. Finalmente, en 1989 se establecio el estandar ANSI C. Actualmente, cualquier compiladormoderno soporta ANSI C. Sin embargo, probablemente debido a razones comerciales, la opcion pordefecto en muchos compiladores de C es una version propia del desarrollador del compilador. Dichaversion suele ser ligeramente diferente e incompatible con el estandar ANSI C.

El lenguaje C debe su nombre a su predecesor, el lenguaje B desarrollado por Ken Thompson,tambien en los Laboratorios Bell.

2.2 Caracterısticas del lenguaje

Actualmente existe gran variedad de lenguajes de programacion de alto nivel entre los que elegir, comoBASIC, Pascal, C, C++, Java, etc. Todos ellos pueden usarse para resolver la mayorıa de proyectos deprogramacion. Sin embargo, existen algunas razones que hacen de C el preferido de muchos programa-dores:

� Potencia y flexibilidad. Se ha usado en contextos tan dispares como el desarrollo de sistemasoperativos, procesadores de texto, graficos, bases de datos, compiladores de otros lenguajes, etc.

Page 18: Introducción a la programación en c

2.3. Creacion de un programa 8

� Popularidad. Existe una gran variedad de compiladores, librerıas, herramientas de apoyo a laprogramacion, etc. Es el lenguaje predominante en el entorno UNIX.

� Portabilidad. El mismo programa escrito en C puede compilarse y ejecutarse sin practicamenteningun cambio en diferentes ordenadores. Esto se debe en gran parte al estandar ANSI C.

� Sencillez. C utiliza pocas palabras clave, por lo que puede aprenderse facilmente.

� Estructura y modularidad. Los programas en C pueden escribirse agrupando el codigo en funcio-nes que a su vez se agrupan en distintos modulos. De esta forma, el codigo puede reutilizarse.

De acuerdo con esto, C representa una buena eleccion como lenguaje de programacion. Sin em-bargo, seguro que el lector ha oıdo hablar de los lenguajes C++, Java y de la programacion orientada aobjetos, ademas de preguntarse sobre las diferencias entre C y C++. Pues bien, C++ puede verse comoun superconjunto de C, lo que significa que casi cualquier aspecto de C es perfectamente valido en C++(pero no al reves). Java por su parte, al igual que C++, tambien se basa en la sintaxis de C.

Finalmente, diremos que aunque C es considerado como un lenguaje de alto nivel, mantiene muchascaracterısticas de los lenguajes de bajo nivel, por lo que podrıa clasificarse como de nivel bajo-medio.

2.3 Creacion de un programa

La creacion de un programa de ordenador consta generalmente de una serie de pasos claramente dife-renciados.

Edicion

El primer paso consiste en usar un editor de textos y crear un fichero que contenga el codigo delprograma en C. Este codigo, normalmente llamado codigo fuente, servira para dar instrucciones precisasal ordenador. Por ejemplo, la siguiente linea de codigo fuente en C indica al ordenador que debe mostrarel mensaje entre comillas en la pantalla:

printf( "Esto es un mensaje" );

El formato del texto admitido por la mayorıa de compiladores se basa en el Codigo Estandar Ameri-cano para el Intercambio de Informacion (ASCII). La mayor parte de los procesadores de texto utilizancodigos especiales para dar formato a los documentos, por lo que normalmente no pueden ser usadoscomo editores de programas.

Hoy en dıa, la mayorıa de los entornos de programacion incluyen un editor, sin embargo, otros no.En estos casos pueden usarse otros programas genericos de edicion de textos ASCII proporcionados porel 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. EnOS/2, pueden usarse E y EPM.

El fichero fuente de un programa debe grabarse con un nombre. Normalmente, el nombre delfichero debe permitir intuir que hace el programa. Adicionalmente, los ficheros fuente en C suelentener la extension .c para identificarlos facilmente.

Page 19: Introducción a la programación en c

9 2. Primer contacto con C

Compilacion

Puesto que el ordenador es incapaz de entender directamente un lenguaje de alto nivel como C, antes deque un programa pueda ejecutarse en el ordenador debe traducirse a lenguaje maquina. Esta traduccionla realiza un programa llamado compilador que, dado un fichero fuente, produce un fichero con lasinstrucciones de lenguaje maquina correspondientes al programa fuente original. El nuevo ficherorecibe el nombre de fichero objeto.

El fichero objeto suele tener el mismo nombre que el fichero fuente, pero con la extension .OBJ (o.o en UNIX).

Montaje

En el tercer paso, las diferentes partes del codigo compilado se combinan para crear el programa ejecu-table.

Parte del lenguaje C consiste en una librerıa de funciones precompiladas que contiene codigo objeto.Las funciones en esta librerıa realizan operaciones de uso frecuente, como mostrar datos en pantalla oleer datos de un fichero. La funcion printf del ejemplo anterior es una funcion de dicha librerıa. Asıpues, el fichero objeto producido al compilar el fichero fuente debe combinarse con el codigo objeto dela librerıa para crear el fichero del programa ejecutable.

Cabe destacar que en la mayorıa de compiladores actuales, como los que funcionan en MS-DOS oMS-Windows, compilacion y montaje se realizan como si fuesen una sola accion.

2.4 Primeros pasos con C

A continuacion se muestra el programa mas sencillo posible en C:

void main()f

g

Todo programa en C debe tener una y solo una funcion main(). Esta funcion debera constar de unaserie de sentencias (en este caso vacıa) delimitada por los sımbolos f g. Dichas sentencias especificanla secuencia de acciones que el programa debera llevar a cabo.

En C pueden ponerse comentarios en cualquier lugar del programa, utilizando los sımbolos /* */.El compilador de C ignora todo el texto entre el inicio del comentario (/*) y el final del mismo (*/).Anadir comentarios a un programa en C no incrementa el tamano de los ficheros objeto ni ejecutable,ni tampoco ralentiza la ejecucion del programa. Veamos un ejemplo de programa con comentarios:

/* Mi primer programa en C */void main()f

/* Otro comentario */g

/* ... y otro comentario */

Sin embargo, no es posible poner un comentario dentro de otro. Por ejemplo serıa ilegal:

Page 20: Introducción a la programación en c

2.5. El modelo de compilacion de C 10

/* Mi primer programa en C */void main()f

/* Otro comentario /* Comentario ilegal */ */g

/* ... y otro comentario */

Pero veamos un programa no tan simple. Por ejemplo, el siguiente programa usa la funcionprintf, predefinida en la librerıa estandar stdio.h, para mostrar un mensaje en la pantalla.

/* Mi primer programa en C */#include <stdio.h>void main()f

printf( "Mi primer mensaje en pantalla nn" );g

2.5 El modelo de compilacion de C

A continuacion se describe el modelo de compilacion de C y los distintos procesos implicados: prepro-cesador, compilador y montador (ver Fig. 2.1).

Preprocesador

Aunque en el apendice A se vera en detalle esta parte del proceso de compilacion, seguidamente sedescriben algunos aspectos basicos.

El preprocesador toma como entrada el codigo fuente y es el responsable de eliminar los comen-tarios (ya que en realidad no representan ninguna instruccion) y de interpretar las directivas especialesdel preprocesador, denotadas por el sımbolo #. Por el momento destacaremos solamente dos de lasdirectivas mas utilizadas:

� #include, que incluye un fichero externo dentro del fichero fuente. Se usaran los sımbolos< > para indicar que el fichero se encuentra en un directorio del entorno de compilacion, dife-rente del directorio de trabajo actual. Por el contrario, se usaran los sımbolos " " para indicarfichero locales. Por ejemplo:

– #include <math.h> incluye el fichero con las definiciones de las funciones ma-tematicas de la librerıa estandar.

– #include <stdio.h> incluye el fichero con las definiciones de las funciones deentrada y salida de la librerıa estandar.

– #include "funciones.h" incluye el fichero funciones.h del directorio actual.

� #define, que define un nombre simbolico. Cuando el preprocesador encuentra un nombresimbolico en el programa lo substituye por el valor que se le haya asociado con la directiva#define.

– #define NUM ELEMENTOS 100 define la constante NUM ELEMENTOS con valor 100.

– #define PI 3.1416 define la constante PI.

Page 21: Introducción a la programación en c

11 2. Primer contacto con C

Montador

Preprocesador

Compilador

Código fuente

Librerías

Código objeto

Código ejecutable

Figura 2.1: Modelo de compilacion de C

Compilador

El compilador de C recibe el codigo fuente producido por el preprocesador y lo traduce a codigo objeto(ficheros con extension .OBJ en MS-Windows, o extension .o en UNIX).

Montador

Si un fichero fuente hace referencia a funciones de una librerıa (como la librerıa estandar) o a funcionesdefinidas en otros ficheros fuente, el montador se encarga de:

� combinar todos los ficheros objeto correspondientes,

� verificar que solo uno de ellos contenga la funcion principal main() y

� crear el fichero finalmente ejecutable.

Page 22: Introducción a la programación en c

2.5. El modelo de compilacion de C 12

Page 23: Introducción a la programación en c

13 3. Empezando a programar

Capıtulo 3

Empezando a programar

3.1 Identificadores

Un identificador en un lenguaje de programacion es un nombre utilizado para referir un valor constante,una variable, una estructura de datos compleja, o una funcion, dentro de un programa. Todo identifica-dor esta formado por una secuencia de letras, numeros y caracteres de subrayado, con la restriccion deque siempre debe comenzar por una letra o un subrayado y que no puede contener espacios en blanco.Cada compilador fija un maximo para la longitud de los identificadores, siendo habitual un maximo de32 caracteres.

C diferencia entre mayusculas y minusculas, segun lo cual C considerara los identificadorescontador,Contador y CONTADOR, por ejemplo, como diferentes.

En cualquier caso, nunca pueden utilizarse las palabras reservadas del lenguaje para la construccionde identificadores. De acuerdo con el estandar ANSI, C consta unicamente de 32 palabras reservadas(ver Tab. 3.1).

Tabla 3.1: Palabras reservadas de C

auto double int structbreak else long switchcase enum register typedefchar extern return unionconst float short unsignedcontinue for signed voiddefault goto sizeof volatiledo if static while

3.2 Estructura de un programa

Todo programa en C consta basicamente de los siguientes elementos:

� Directivas del preprocesador

� Definiciones de tipos de datos

� Declaraciones de funciones

Page 24: Introducción a la programación en c

3.3. Variables y constantes 14

Por el momento se tratara unicamente la declaracion de la funcion main() que corresponde alprograma principal. La ejecucion de un programa escrito en C siempre comienza por dicha funcion.Por esta razon, en un programa solo puede haber una funcion con dicho nombre. La definicion de tiposde datos se deja para el capıtulo 8.

Toda funcion en C, y en particular la funcion main(), tiene la siguiente estructura:

tipo datos nombre funcion ( parametros )f

variables locales;

secuencia de sentencias;g

No entraremos aquı en las particularidades de las funciones como el paso de parametros y la de-volucion de resultados de un tipo de datos determinado (ver Cap. 10). Comentaremos simplementeque tanto la devolucion de resultados como los parametros son opcionales, y que en la mayorıa deprogramas sencillos no se usan en la definicion del programa principal.

A continuacion se muestra, como ejemplo, un programa para evaluar la expresion 3 � 5� 32=4:

/* Evaluando una expresion */#include <stdio.h>void main()f

int a, b, c = 5;a = 3 * c;b = 32 / 4;c = a - b;printf( "El valor de la expresion es: %dnn", c );

g

El cuerpo del programa principal lo constituyen todas las lıneas de programa comprendidas entrelos sımbolos f y g. En cada una de dichas lıneas puede haber una o mas sentencias. Una sentencia esuna orden completa para el ordenador. Toda sentencia debe acabar con un punto y coma (;).

3.3 Variables y constantes

Los programas de ordenador utilizan diferentes tipos de datos, por lo que requieren de algun mecanismopara almacenar el conjunto de valores usado. C ofrece dos posibilidades: variables y constantes. Unavariable es un objeto donde se guarda un valor, el cual puede ser consultado y modificado durante laejecucion del programa. Por el contrario, una constante tiene un valor fijo que no puede ser modificado.

3.3.1 Variables

Toda variable debe declararse antes de ser usada por primera vez en el programa. Las sentencias dedeclaracion de variables indican al compilador que debe reservar cierto espacio en la memoria del or-denador con el fin de almacenar un dato de tipo elemental o estructurado. Por ejemplo, la siguiente

Page 25: Introducción a la programación en c

15 3. Empezando a programar

declaracion de variables indica al compilador que debe reservar espacio en la memoria para tres varia-bles de tipo entero, a las que nos referiremos con los nombres a, b y c:

int a, b, c;

La declaracion consiste en dar un nombre significativo a la variable e indicar el tipo de datos a quecorresponden los valores que almacenara. A continuacion se muestra la sintaxis mas sencilla de unasentencia de declaracion para una sola variable.

tipo datos nombre variable;

Ademas, en una sola sentencia pueden declararse varias variables de un mismo tipo de datos, sepa-rando los nombres de las variables mediante comas:

tipo datos nombre variable1, ..., nombre variableN;

Opcionalmente, es posible asignar un valor inicial a las variables en la propia declaracion.

tipo datos nombre variable = valor inicial;

3.3.2 Constantes

C admite dos tipos diferentes de constantes: literales y simbolicas.

Constantes literales

Todo valor que aparece directamente en el codigo fuente cada vez que es necesario para una operacionconstituye una constante literal. En el siguiente ejemplo, los valores 20 y 3 son constantes literales deltipo de datos entero:

int cont = 20;cont = cont + 3;

Si una constante numerica contiene un punto decimal, el compilador considera dicha constantecomo un valor real de coma flotante. Este tipo de constantes puede escribirse tambien utilizando algunade las notaciones cientıficas comunmente aceptadas (ver Sec. 6.3).

Por el contrario, el resto de constantes numericas son consideradas por el compilador, como valoresenteros. Pueden usarse tres formatos alternativos:

� Toda constante que comience por un dıgito distinto de 0 es interpretada como un entero decimal(esto es, en base 10). Se especifican mediante los dıgitos del 0 al 9 y el signo positivo o negativo.

� Si una constante comienza con el dıgito 0, se interpreta como un entero octal (base 8). Seespecifican mediante los dıgitos del 0 al 7 y el signo positivo o negativo.

� Finalmente, las constantes que comienzan por 0x o 0X se interpretan como enteros en basehexadecimal (base 16). Se especifican mediante los dıgitos del 0 al 9, las letras de la A a la F, yel signo positivo o negativo.

Para saber mas sobre los distintos sistemas de numeracion, ver el apendice C.

Page 26: Introducción a la programación en c

3.3. Variables y constantes 16

Constantes simbolicas

Una constante simbolica es una constante representada mediante un nombre (sımbolo) en el programa.Al igual que las constantes literales, no pueden cambiar su valor. Sin embargo para usar el valorconstante, se utiliza su nombre simbolico, de la misma forma que lo harıamos con una variable. Unaconstante simbolica se declara una sola vez, indicando el nombre y el valor que representa.

Las constantes simbolicas tienen dos ventajas claras respecto a las literales. Supongamos el siguien-te codigo para calcular el perımetro de una circunferencia y el area del cırculo que define:

perimetro = 2 * 3.14 * radio;area = 3.14 * radio * radio;

Si por el contrario se hubiese definido una constante simbolica de nombre PI y valor 3.14, podrıamosescribir un codigo mucho mas claro:

perimetro = 2 * PI * radio;area = PI * radio * radio;

Es mas, imaginemos ahora que para incrementar la precision del calculo se desea usar un valor maspreciso de la constante �, como 3.14159. En el primer caso deberıa substituirse uno a uno el valor3.14 en todo el programa. En el segundo caso, bastarıa cambiar la definicion de la constante PI conel nuevo valor.

El metodo mas habitual para definir constantes en C es la directiva del preprocesador #define.Por ejemplo, en el caso anterior podrıamos haber escrito:

#define PI 3.14159

Es decir, el nombre simbolico y a continuacion el valor constante que representa.

3.3.3 Entrada y salida de valores

Aunque en el apendice B se veran con mas detalle las funciones de la librerıa estandar printf yscanf, se introducen en este punto con el fin de poder realizar algunos programas sencillos.

C utiliza operaciones de entrada y salida con formato. Por ejemplo, la funcion printf usa comocaracter especial de formato el sımbolo de porcentaje (%). El caracter que sigue a este sımbolo define elformato de un valor (constante, variable o expresion). Por ejemplo, %c para valores de tipo caracter o%d para valores de tipo entero. El siguiente ejemplo muestra por pantalla el contenido de una variablede tipo caracter (ch), y una variable entera (num).

char ch;int num;. . .printf( "Esto es un caracter: %cnn", ch );printf( "Y esto un entero: %dnn", num );

El formato es todo aquello especificado entre las comillas, al que le sigue una lista de variables,constantes o expresiones separadas por comas. Es responsabilidad del programador asegurar la perfecta

Page 27: Introducción a la programación en c

17 3. Empezando a programar

Tabla 3.2: Operadores aritmeticos en C

Unarios Signo negativo �

Incremento ++

Decremento ��

Binarios Suma +

Resta �

Multiplicacion �

Division =

Modulo %

correspondencia entre el formato y la lista de valores, tanto en numero como en el tipo de los mismos.Finalmente, la secuencia especial nn indica un salto de lınea.

Por su parte, scanf es una funcion para la entrada de valores a una estructura de datos, y enparticular 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 caracter en la variable ch y seguidamente un valor entero enla variable num. Notese que en el caso de scanf se antepone el sımbolo & a las variables. Porel momento, no debemos olvidar utilizarlo, y tengamos en mente que el uso de & tiene que ver condirecciones de memoria y punteros (ver Cap. 9).

3.4 Expresiones

Una expresion es una formula matematica cuya evaluacion especifica un valor. Los elementos queconstituyen una expresion son: constantes, variables y operadores.

3.4.1 Operador de asignacion

El operador de asignacion permite asignar valores a las variables. Su sımbolo es un signo igual =. Esteoperador asigna a la variable que esta a la izquierda del operador el valor que esta a la derecha. Unejemplo de expresiones validas con el operador de asignacion son: x = 1; z = 1.35; .

3.4.2 Operadores aritmeticos

Ademas de los operadores aritmeticos tradicionales, C proporciona algunos operadores adicionales (verTab. 3.2).

La expresion, x++; equivale a x = x+1;, y x--; equivale a x = x-1;. Aunque en elpasado algunos compiladores generaban codigo mas eficiente si se usaba los operadores ++, -- enlugar de sus expresiones equivalentes, esto ya no es cierto en los compiladores modernos. Los opera-dores ++ y -- pueden usarse tanto de manera postfija (mas habitual) como prefija, indicando en cada

Page 28: Introducción a la programación en c

3.4. Expresiones 18

caso si el valor de la variable se modifica despues o antes de la evaluacion de la expresion en la queaparece. Por ejemplo, la siguiente lınea de codigo:

x = ((++z) - (w--)) % 100;

es equivalente al siguiente grupo de sentencias:

z = z + 1;x = (z - w) % 100;w = w - 1;

Notese que en C no existe ningun operador especial para la division entera, de forma que cuandolos dos operandos de la division son enteros, el cociente que se obtiene es el correspondiente a ladivision entera (el cociente no se redondea, sino que se trunca). Si alguno de los operandos es un valorreal, el resultado de la division sera tambien real. Por ejemplo, x = 3/2; asigna el valor 1 a lavariable x (que debe ser entera), mientras que x = 3.0/2; o x = 3/2.0; asigna el valor1.5 a la variable x (que debe ser real). Finalmente, el operador de modulo (%) permite obtenerel resto de una division entera, por lo que sus operandos deben ser tambien enteros. Por ejemplo,x = 8 % 5; asigna el valor 3 a la variable entera x.

Existe ademas una manera abreviada de expresar ciertos calculos en C. Es muy comun tener ex-presiones del estilo de i = i + 5; o x = x * (y + 2);. Este tipo de expresiones puedeescribirse en C de forma compacta como:

expresion1 op = expresion2

que es equivalente a:

expresion1 = expresion1 op expresion2

Segun esto, la asignacion i = i + 5; puede reescribirse como i += 5; y la asignacionx = x * (y + 2); como x *= y + 2;. Notese que esta ultima expresion no significa enningun caso x = (x * y) + 2;.

Como puede verse, un uso abusivo de los operadores abreviados de C puede hacer difıcil de leer unprograma. C permite escribir expresiones muy compactas, pero que pueden generar confusion al serleıdas. Hay que recordar siempre que la legibilidad (mantenimiento) de un programa es tan importantecomo su correcto funcionamiento.

3.4.3 Operadores relacionales

Los operadores relacionales se utilizan principalmente para elaborar condiciones en las sentencias con-dicionales e iterativas (ver Cap. 4 y Cap. 5).

La tabla 3.3 resume los distintos operadores de relacion en C. Al relacionar (comparar) dos expre-siones mediante uno de estos operadores se obtiene un resultado logico, es decir: ‘CIERTO’ o ‘FALSO’.Por ejemplo, la expresion 4 > 8 da como resultado el valor falso, la expresion num == num dacomo resultado cierto, la expresion 8 <= 4 da como resultado falso, etc.

Es interesante destacar que a diferencia de otros lenguajes, C no dispone de un tipo de datos es-pecıfico para los valores logicos o booleanos. En su lugar, C representa un resultado ‘FALSO’ como

Page 29: Introducción a la programación en c

19 3. Empezando a programar

Tabla 3.3: Operadores relacionales (izquierda) y logicos (derecha) en C

Menor que <

Mayor que >

Menor o igual que <=

Mayor o igual que >=

Igual que ==

Distinto que ! =

Conjuncion o Y logico &&Disyuncion u O logico jj

Negacion o NO logico !

Tabla 3.4: Tabla de verdad de los operadores logicos en C

A B ! A A && B A jj BCierto Cierto Falso Cierto CiertoCierto Falso Falso Falso CiertoFalso Cierto Cierto Falso CiertoFalso Falso Cierto Falso Falso

el valor numerico entero cero, y un resultado ‘CIERTO’ como cualquier valor entero diferente de cero.Es muy importante recordar este hecho de ahora en adelante.

Un error habitual es confundir el operador relacional de igualdad == con el operador de asignacion= . Por ejemplo, la sentencia x = 3 asigna el valor 3 a la variable x, mientras que x == 3 comparael valor de x con la constante 3.

3.4.4 Operadores logicos

Los operadores logicos (ver Tab. 3.3) se utilizan principalmente en conjuncion con los relacionales paraelaborar condiciones complejas en las sentencias condicionales e iterativas (ver Cap. 4 y Cap. 5).

Es importante no confundir los operadores logicos && y jj con los operadores de manejo de bits& y j que veremos en la seccion 8.5.

La tabla 3.4 muestra la tabla de verdad para los operadores logicos. De acuerdo con dicha tabla,las expresiones 4 && 0, !(4 > 1) y 5 <= 0 dan como resultado 0 (falso), mientras quelas expresiones 4 jj 9, (8 == 4*2) && (5 > 2) y 2 && (4 < 9) dan como resultado 1(cierto).

3.4.5 Prioridad de operadores

La tabla 3.5 muestra los operadores vistos anteriormente, ası como la prioridad entre ellos. La prioridaddesciende al descender en la tabla. Tambien se muestra la asociatividad para operadores con el mismonivel de prioridad.

Por ejemplo, segun dicha tabla, la expresion (a < 10 && 2 * b < c) se interpretara como(a < 10) && ((2 * b) < c).

Page 30: Introducción a la programación en c

3.5. Ejercicios 20

Tabla 3.5: Prioridad y asociatividad de los operadores en C

Operador Sımbolo AsociatividadParentesis () Izquierda a derechaNO logico ! Derecha a izquierdaSigno negativo �

Incremento ++

Decremento ��

Multiplicacion � Izquierda a derechaDivision =

Modulo %Suma + Izquierda a derechaResta �

Menor que < Izquierda a derechaMenor o igual que <=

Mayor que >

Mayor o igual que >=

Igual que == Izquierda a derechaDistinto que ! =

Y logico && Izquierda a derechaO logico jj Izquierda a derechaAsignaciones = + = � = Derecha a izquierda

� = = = %=

3.5 Ejercicios

Escribir un programa para cada uno de los siguientes ejercicios:

1. Pedir la base y la altura de un rectangulo, calcular su area y su perımetro, y mostrar los resultadospor pantalla.

2. Pedir una cantidad de segundos y mostrar por pantalla a cuantas horas, minutos y segundoscorresponden.

3. Suponiendo que previamente se ha realizado la declaracion int x = 7, y; , calcular elvalor de la variable y tras evaluar cada una de las siguientes sentencias de asignacion:

(a) y = �2 + ��x;

(b) y + = 2;

(c) y = (y == x);

(d) y = y++ � x;

4. Evaluar las siguientes expresiones:

(a) 5 = 2 + 20 % 6

(b) 4 � 6 = 2 � 15 = 2

Page 31: Introducción a la programación en c

21 3. Empezando a programar

(c) 5 � 15 = 2 = (4 � 2)

(d) 8 == 16 jj 7 != 4 && 4 < 1

(e) (4 � 3 < 6 jj 3 > 5 � 2) && 3 + 2 < 12

Page 32: Introducción a la programación en c

3.5. Ejercicios 22

Page 33: Introducción a la programación en c

23 4. Construcciones condicionales

Capıtulo 4

Construcciones condicionales

Una de las construcciones importantes que pueden especificarse en un programa es el hecho de realizardiferentes tareas en funcion de ciertas condiciones. Esto es, ejecutar una parte del codigo u otra, con-dicionalmente. Para ello sera necesario especificar dichas condiciones (ver Sec. 3.4) y disponer de unmecanismo para indicar que acciones tomar dependiendo de como se evalue una determinada condicionen un momento dado de la ejecucion del programa.

Antes de empezar, un recordatorio. Como ya de comento en la seccion 3.4.3, C no dispone devalores booleanos o logicos, que podrıan usarse en la evaluacion de condiciones. En su defecto, C“simula” los valores falso y cierto, como el valor numerico cero, y cualquier valor no cero (incluyendonegativos), respectivamente.

Ası pues, en este capıtulo veremos las distintas maneras que C ofrece para controlar el flujo deejecucion de un programa de forma condicional, que son:

� la construccion if,

� el operador condicional ?, y

� la construccion switch.

4.1 Construccion if

La construccion if es similar a la existente en otros lenguajes de programacion, aunque en C po-see ciertas peculiaridades. El formato general de esta construccion para decidir si una determinadasentencia debe ejecutarse o no (alternativa simple) es el siguiente:

if (condicion)sentencia;

La construccion if puede escribirse tambien de forma mas general para controlar la ejecucion de ungrupo de sentencias, de la siguiente manera:

Page 34: Introducción a la programación en c

4.1. Construccion if 24

Condición

sentenciasGrupo de

Cierto

(a)

Condición

Grupo de Grupo desentencias 1 sentencias 2

FalsoCierto

(b)

Figura 4.1: Esquema de funcionamiento de if y de if-else

if (condicion)f

sentencia 1;sentencia 2;. . .sentencia N;

g

El funcionamiento de la construccion if es muy simple. En primer lugar se evalua la condicion,que no es otra cosa que una expresion de tipo entero. A continuacion, si la expresion se ha evaluadocomo cierta, se ejecuta la sentencia o grupo de sentencias. En caso contrario la ejecucion del programacontinua por la siguiente sentencia en orden secuencial (ver Fig. 4.1 (a)).

El siguiente ejemplo muestra el uso de la construccion if. El programa lee un numero entero y lotransforma en el impar inmediatamente mayor, si es que no era ya impar.

#include <stdio.h>void main()f

int a;

scanf("%d", &a);if (a % 2 == 0) /* Comprobar si a es par. */

a = a + 1;printf( "Ahora es impar: %dnn", a );

g

Notese que despues de la condicion no se escribe ‘;’. Escribir ‘;’ detras de la condicion equivaldrıaa que la construccion if ejectutase un conjunto vacıo de sentencias, lo cual no tiene ningun sentido.Notese, sin embargo, que tal hecho es valido sintacticamente (no produce ningun error de compilacion),por lo que debera tenerse cuidado al escribir esta construccion. Algo similar ocurre con los buclesfor y while (ver Cap. 5).

Page 35: Introducción a la programación en c

25 4. Construcciones condicionales

4.1.1 Variante if-else

Existe otra forma mas general, denominada alternativa doble, que ofrece dos alternativas de ejecucion,en funcion de si la condicion se evalua cierta o falsa.

if (condicion)sentencia 1;

elsesentencia 2;

Y para un grupo de sentencias:

if (condicion)f

grupo de sentencias 1;g

elsef

grupo de sentencias 2;g

Ası pues, si la condicion es cierta se ejecutara la primera sentencia (el primer grupo de sentencias), y sies falsa se ejecutara la segunda sentencia (el segundo grupo). Ver figura 4.1 (b).

El siguiente programa muestra el uso de esta construccion. El programa calcula el maximo de dosnumeros enteros:

#include <stdio.h>void main()f

int a, b, max;

scanf( "%d %d", &a, &b );if (a > b)

max = a;else

max = b;prinf( "El maximo es: %dnn", max );

g

Es importante destacar que la sentencia en la construccion else es opcional, es decir, puede sernula. Veamoslo en el siguiente ejemplo que determina si un numero es par:

#include <stdio.h>void main()f

int x;

Page 36: Introducción a la programación en c

4.1. Construccion if 26

scanf( "%d", &x );if (x % 2 == 0)

printf( "Es par.nn" );else ;

g

El hecho de que la construccion else sea opcional puede causar problemas de ambiguedad alcompilador cuando se utilizan construcciones if o if-else anidadas. Para solventar el problemase ha establecido una regla muy sencilla que todo compilador de C tiene en cuenta. La regla consisteen que una sentencia else se asocia con el if precedente mas cercano siempre y cuando este notenga ya asociada otra sentencia else.

A continuacion se muestran dos porciones de programa practicamente iguales, pero con comporta-mientos completamente diferentes. Se deja para el lector el analisis de ambos casos.

. . .if (n > 0)

if (a > b)z = a;

elsez = b;

. . .

. . .if (n > 0)f

if (a > b)z = a;

g

elsez = b;

. . .

4.1.2 Variante if-else-if

Existe finalmente otra construccion alternativa muy comun, conocida como if-else-if o simple-mente else-if. Su construccion, donde las condiciones se plantean de forma escalonada, se muestraa continuacion:

if (condicion 1)f

grupo de sentencias 1;g

else if (condicion 2)f

grupo de sentencias 2;g

. . .else if (condicion N)f

grupo de sentencias N;g

elsef

grupo de sentencias por defecto;g

Page 37: Introducción a la programación en c

27 4. Construcciones condicionales

Las condiciones se evaluan secuencialmente de arriba hacia abajo hasta encontrar una que de comoresultado cierto. En ese punto, se ejecuta el grupo de sentencias correspondiente a dicha condicion. Elresto de condiciones y sentencias asociadas se ignoran. En caso de que ninguna de las condiciones seevalue cierta, se ejecutarıa el grupo de sentencias por defecto. Como en todos los casos anteriores, elultimo else es opcional.

A continuacion se muestra un ejemplo del uso de esta construccion:

#include <stdio.h>void main()f

int hora;

scanf( "%d", &hora );if ((hora >= 0) && (hora < 12))

printf( "Buenos dıas" );else if ((hora >= 12) && (hora < 18))

printf( "Buenas tardes" );else if ((hora >= 18) && (hora < 24))

printf( "Buenas noches" );else

printf( "Hora no valida" );g

4.2 El operador condicional ?

El operador condicional ? es el unico operador ternario de C. La forma general de las expresionesconstruidas con este operador es la siguiente:

expresion 1 ? expresion 2 : expresion 3;

De manera que si la primera expresion se evalua cierta, toda la expresion toma el valor de la segundaexpresion. En cambio, si la primera expresion se evalua falsa, toda la expresion toma el valor de latercera expresion.

Un ejemplo tıpico del uso de este operador es el calculo del maximo de dos valores. En la siguientesentencia, c toma el valor del maximo entre la variable a y b.

c = (a > b) ? a : b;

Esto mismo podrıa haberse escrito usando la construccion if-else como:

if (a > b)c = a;

elsec = b;

Page 38: Introducción a la programación en c

4.3. Construccion switch 28

De esta manera, algunas sentencias if-else sencillas pueden escribirse de manera muy compactamediante el operador ?.

Finalmente, el operador condicional, por ser en realidad un operador para expresiones, puede usarseen lugares donde no puede usarse un if-else, como se muestra a continuacion:

printf("El mınimo es %d nn", ((x < y) ? x : y) );

4.3 Construccion switch

Esta construccion permite especificar multiples sentencias al estilo if-else-if, pero de maneramas compacta, legible y elegante. Su forma general es la siguiente:

switch ( expresion )f

case constante 1 :grupo de sentencias 1;break;

case constante 2 :grupo de sentencias 2;break;

. . .case constante N :

grupo de sentencias N;break;

default :grupo de sentencias por defecto;break;

g

donde la expresion debe ser de tipo entero o caracter, al igual que todas las constantes asociadas a cadaetiqueta case. Es importante resaltar que no pueden usarse variables o expresiones en los distintoscase, sino solo constantes.

El funcionamiento de la construccion switch es como sigue. En primer lugar se evalua laexpresion. Seguidamente su valor es comparado secuencialmente con el de las diferentes constantes enlos case. Si el valor de la expresion coincide con alguna de ellas, se ejecuta el grupo de sentenciascorrespondiente y switch concluye gracias a la sentencia break. En caso contrario, y si existe elcaso default (que es opcional), se ejecutarıa el grupo de sentencias por defecto (ver Fig. 4.2).

Cabe mencionar de forma especial, la sentencia break que volveremos a ver en capıtulos sucesi-vos. En general, break se utiliza para finalizar de forma forzada la ejecucion dentro de un bloque decodigo, de manera que la siguiente sentencia a ejecutar sera la primera sentencia justo despues de dichobloque. En la construccion switch, break es necesario para concluir la ejecucion del grupo desentencias asociado al caso cuya constante coincide con el valor de la expresion. Ası pues, la sentenciaa ejecutar despues de break en un switch, sera la primera sentencia posterior a la llave g quecierra el switch.

Page 39: Introducción a la programación en c

29 4. Construcciones condicionales

Grupo desentencias 1

Grupo desentencias 2

Expresión

. . . Grupo desentencias N

Grupo de

por defectosentencias

Figura 4.2: Esquema de funcionamiento de switch

La construccion switch tambien podrıa escribirse de forma equivalente mediante sentencias deltipo if-else-if, de la siguiente forma:

if (expresion == constante 1)f

grupo de sentencias 1;g

else if (expresion == constante 2)f

grupo de sentencias 2;g . . .else if (expresion == constante N)f

grupo de sentencias N;g

elsef

grupo de sentencias por defecto;g

que, como puede verse, es mucho mas ineficiente en tiempo de ejecucion, puesto que la expresion debeevaluarse repetidas veces, una para cada condicion.

El siguiente ejemplo muestra un programa que hace uso de switch para traducir a caracteres undıgito entre 1 y 5.

#include <stdio.h>void main()f

int num;

scanf( "%d", &num );switch ( num )

Page 40: Introducción a la programación en c

4.3. Construccion switch 30

f

case 1 :printf( "Uno.nn" );break;

case 2 :printf( "Dos.nn" );break;

. . .case 5 :

printf( "Cinco.nn" );break;

default :printf( "El dıgito esta fuera de rango.nn" );break;

g

g

Finalmente, cabe decir que el grupo de sentencias asociado a un case puede ser vacıo. Este casoparticular tiene su utilidad cuando se desea que varias etiquetas case ejecuten un mismo grupo desentencias. Por ejemplo:

#include <stdio.h>void main()f

int num;

scanf( "%d", &num );switch ( num )f

case 1:case 3:case 7:

printf( "Es un uno, un tres o un siete.nn" );break;

case 4:case 8:

printf( "Es un cuatro, o un ocho.nn" );break;

default:printf( "Dıgito no controlado.nn" );break;

g

g

Page 41: Introducción a la programación en c

31 4. Construcciones condicionales

4.4 Ejercicios

1. Escribir un programa que lea tres valores enteros y muestre por pantalla el maximo y el mınimode ellos.

2. Dado el siguiente programa, realizar un seguimiento de la ejecucion en los siguientes supuestos:

(a) a = 0, b = 0, c = 5, d = 3

(b) a = 2, b = 1, c = 5, d = 3

(c) a = 2, b = 1, c = 2, d = 2

(d) a = 2, b = 1, c = 0, d = 0

#include <stdio.h>void main()f

int a, b, c, d;

scanf( "%d %d %d %d", &a, &b, &c, &d );if ( ((a > 0) || (b > a)) && (c != d) )f

a = c;b = 0;

g

elsef

c += d;c = (c == 0) ? (c + b) : (c - a);b = a + c + d;

g

printf( "%d %d %d %dnn", a, b, c, d );g

3. Escribir un programa que lea un valor entero y determine si es multiplo de 2 y de 5.

4. Escribir un programa que muestre por pantalla la ecuacion de una recta en un plano, Ax+By+

C = 0, leyendo previamente las coordenadas de dos de sus puntos (x1, y1) y (x2, y2). Recordarque:

A = y2 � y1 y B = y1 � (x2 � x1)� x1 � (y2 � y1)

Page 42: Introducción a la programación en c

4.4. Ejercicios 32

Page 43: Introducción a la programación en c

33 5. Construcciones iterativas

Capıtulo 5

Construcciones iterativas

Hasta ahora hemos visto algunos aspectos basicos del control del flujo de ejecucion de un programa enC. Este capıtulo presenta los mecanismos que C ofrece para la ejecucion repetida de sentencias, bienun numero prefijado de veces, bien dependiendo de cierta condicion. Es decir, mecanismos para lacreacion de bucles de ejecucion.

C proporciona las siguientes construcciones iterativas:

� la construccion while,

� la construccion do-while, y

� la construccion for.

5.1 Construccion while

La construccion while es similar a la existente en otros lenguajes de programacion. Sin embargo,debido a que en C toda sentencia puede considerarse como una expresion, la construccion while deC ofrece cierta potencia anadida.

La forma mas general para la ejecucion repetida de una sola sentencia es:

while (condicion)sentencia;

O para la ejecucion repetida de un grupo de sentencias:

while (condicion)f

grupo de sentencias;g

El funcionamiento de esta construccion es bastante simple. El cuerpo del bucle, es decir, la sen-tencia o grupo de sentencias dentro del bucle, se ejecuta mientras el valor de la expresion que actua decondicion sea cierto. En el momento en que la condicion sea falsa, la ejecucion del programa continuasecuencialmente con la siguiente instruccion tras el bucle (ver Fig. 5.1).

Page 44: Introducción a la programación en c

5.1. Construccion while 34

Condición

sentenciasGrupo de

Cierto

Falso

Figura 5.1: Esquema de funcionamiento de while

El siguiente ejemplo calcula la media de una secuencia de numeros enteros leıdos por teclado aca-bada en �1:

#include <stdio.h>void main()f

int num, cont, suma;

cont = 0;suma = 0;scanf( "%d", &num );while (num != -1)f

cont++;suma = suma + num;scanf( "%d", &num );

g

if (cont != 0)printf( "La media es %dnn", sum/cont );

elseprintf( "La secuencia es vacıa.nn" );

g

En la construccion while la condicion se evalua al principio del bucle. Por ello, si cuando se al-canza el bucle por primera vez, la condicion es falsa, el cuerpo del bucle no se ejecuta nunca (imagıneseel caso, en el ejemplo anterior, en que el primer numero de la secuencia sea �1). Como consecuencia,el cuerpo de un bucle while puede ejecutarse entre 0 y N veces, donde N depende de la condicion.

Notese tambien que si la condicion permanece cierta, el bucle puede no terminar nunca (imagıneseque ocurrirıa si se elimina la sentencia scanf del cuerpo del bucle del ejemplo anterior). Por ello,habitualmente las sentencias del cuerpo del bucle modifican las variables que aparecen en la condicion,de forma que esta sea falsa en algun momento.

Por otra parte, la condicion del bucle (y esto es extensible a las diferentes construcciones repetitivas)

Page 45: Introducción a la programación en c

35 5. Construcciones iterativas

sentenciasGrupo de

Condición

Falso

Cierto

Figura 5.2: Esquema de funcionamiento de do-while

no tiene por que ser simplemente una expresion logica, sino que puede ser cualquier expresion. Porejemplo, los siguiente bucles

while (x--) f ... g

while (x = x+1);

son perfectamente validos. En ambos casos el cuerpo del bucle se repetira mientras el valor de x seadistinto de 0. Notese que en el segundo caso el cuerpo del bucle es nulo, lo cual tambien es posible.

5.2 Construccion do-while

La forma general de la construccion do-while es la siguiente:

dof

sentencia; o grupo de sentencias;g while (condicion);

Notese que tanto para ejecutar una sola sentencia como para ejecutar un grupo de ellas, las llavesf g son igualmente necesarias.

Esta construccion funciona de manera muy similar a la construccion while. Sin embargo, alcontrario que esta, do-while ejecuta primero el cuerpo del bucle y despues evalua la condicion. Porlo cual, el cuerpo del bucle se ejecuta como mınimo 1 vez (ver Fig. 5.2).

El siguiente ejemplo cuenta el numero de veces que aparece el numero 3 en una secuencia denumeros enteros acabada en �1:

#include <stdio.h>void main()f

int num, cont;

Page 46: Introducción a la programación en c

5.3. Construccion for 36

Condición

Sentencia inicial

Incremento / Decremento

Grupo de sentencias

Cierto

Falso

Figura 5.3: Esquema de funcionamiento de for

cont = 0;

dof

scanf( "%d", &num );if (num == 3)

cont++;g while (num != -1);printf( "El 3 ha aparecido %d vecesnn", cont );

g

Es importante destacar el uso de ‘;’ despues de la condicion, a diferencia de en la construccionwhile , donde no se utiliza.

Finalmente, cabe decir que tradicionalmente, tanto la construccion while como la construcciondo-while se utilizan en bucles donde se desconoce a priori el numero exacto de iteraciones.

5.3 Construccion for

Esta construccion iterativa no presenta un formato fijo estricto, sino que admite numerosas variantes, loque la dota de gran potencia y flexibilidad.

Su forma mas general para la ejecucion repetida de una sola sentencia es:

for (sentencia inicial ; condicion ; incremento/decremento)sentencia;

o para la ejecucion repetida de un grupo de sentencias:

Page 47: Introducción a la programación en c

37 5. Construcciones iterativas

for (sentencia inicial ; condicion ; incremento/decremento)f

grupo de sentencias;g

La primera parte de la construccion for acostumbra a ser una sentencia de asignacion donde seinicializa alguna variable que controla el numero de veces que debe ejecutarse el cuerpo del bucle. Estasentencia se ejecuta una sola ocasion, antes de entrar por primera vez al cuerpo del bucle.

La segunda parte corresponde a la condicion que indica cuando finaliza el bucle, de la misma formaque en las construcciones iterativas anteriores. En este caso, la condicion se evalua antes de ejecutar elcuerpo del bucle, por lo que al igual que en la construccion while, el cuerpo puede ejecutarse entre 0

y N veces, donde N depende de la condicion.

La tercera parte corresponde normalmente a una sentencia de incremento o decremento sobre lavariable de control del bucle. Esta sentencia se ejecuta siempre despues de la ejecucion del cuerpo delbucle.

La figura 5.3 muestra esquematicamente el funcionamiento del bucle for.

El programa del siguiente ejemplo utiliza la construccion for para calcular el sumatorio10Xi=1

i3 :

#include <stdio.h>void main()f

int i, cubo, suma;

suma = 0;for (i = 0 ; i <= 10 ; i++)f

cubo = i * i * i;suma += cubo;

g printf( "El sumatorio es %dnn", suma );g

Las tres partes de la construccion for son opcionales, por lo que es posible omitir alguna o todasellas. En cualquier caso, los punto y coma (;) separadores son siempre necesarios. Un ejemplo clasicode este tipo de bucle for es el bucle infinito (nunca concluye la ejecucion):

for ( ; 1 ; )f

/* Grupo de sentencias */g

Tradicionalmente la construccion for se utiliza en bucles donde el numero exacto de iteracioneses conocido a priori, y puede controlarse mediante una variable que actua como contador.

Page 48: Introducción a la programación en c

5.4. Las sentencias break y continue 38

5.3.1 El operador coma (,)

C permite la utilizacion de mas de una sentencia en la primera y tercera partes de la construccion for,ası como de mas de una condicion en la segunda parte. Por ejemplo, el siguiente bucle es valido en C:

for (i = 0, j = 10 ; i < 10, j > 0 ; i++, j-=2)f

/* Grupo de sentencias */g

Ası pues, las variables i y j se inicializan a 0 y 10, respectivamente, antes de comenzar la ejecuciondel bucle. En la segunda parte de la construccion, aparecen dos condiciones, i < 10 y j > 0. Sialguna de ellas es falsa, la ejecucion del bucle se detiene. Finalmente, tras ejecutarse el cuerpo del bucle,i se incrementa en 1 y j se decrementa en 2, tras lo cual vuelven a comprobarse las condiciones, yası sucesivamente.

5.3.2 Equivalencia for-while

Como se ha podido ver, C trata el bucle for de manera muy similar al bucle while, usando unacondicion para decidir cuando concluye la ejecucion. De hecho, todo bucle for puede escribirse deforma equivalente mediante un bucle while de la siguiente forma:

sentencia inicial;while (condicion)f

sentencia;incremento/decremento;

g

5.4 Las sentencias break y continue

C proporciona dos mecanismos para alterar la ejecucion de las construcciones iterativas: las sentenciasbreak y continue.

break

Esta sentencia tiene una doble finalidad. Por un lado, indica el final de un case en la construccionswitch, como ya se vio en la seccion 4.3. Y por otro, para forzar la terminacion inmediata de la ejecu-cion de un bucle. De esta forma, se permite salir de la construccion repetitiva ignorando la evaluacionde la condicion. Si bien su uso esta reconocido como no muy elegante, permite en ocasiones escribirprogramas mas legibles y compactos.

continue

Esta sentencia se utiliza unicamente en las construcciones repetitivas. Su funcion es la de evitar que seejecute todo el codigo a continuacion de ella y hasta el final del cuerpo del bucle, durante una iteraciondeterminada.

El siguiente ejemplo pretende ilustrar el uso de estas sentencias:

Page 49: Introducción a la programación en c

39 5. Construcciones iterativas

dof

scanf("%d", &num);if (num < 0)f

printf( "Valor ilegalnn" );break; /* Abandonar el bucle. */

g

if (num > 100)f

printf( "Valor demasiado grandenn" );continue; /* No ejecutar el resto de sentencias

e ir al final del bucle. */g

/* Procesar el valor leıdo */. . .

g while (num != 0 );. . .

5.5 Ejercicios

Se recomienda realizar los siguientes ejercicios utilizando las diferentes construcciones iterativas pre-sentadas.

1. Escribir un programa que calcule la suma de los 20 primeros numeros multiplos de 5 o de 7.

2. Escribir un programa que calcule la potencia de un numero entero, dado su valor y el del expo-nente.

3. Escribir un programa que lea N numeros enteros y muestre el mayor y el menor de todos ellos.

4. Escribir un programa que escriba la tabla de multiplicar de un numero leıdo por teclado.

5. Escribir un programa que muestre la serie de Fibonacci hasta un lımite dado. Recordar que laserie de Fibonacci se define como

F0 = 1 F1 = 1 Fi = Fi�1 + Fi�2

6. Escribir un programa que convierta un numero entero positivo a cualquier base de numeraciondada, igual o inferior a 10.

7. Escribir un programa que determine si un numero entero dado es primo o no.

8. Escribir un programa que calcule el factorial de un numero entero leıdo por teclado.

9. Escribir un programa que calcule la suma de todos los numeros multiplos de 5 comprendidosentre dos enteros leıdos por teclado.

10. Escribir un programa que muestre los 15 primeros numeros de la serie de Fibonacci.

Page 50: Introducción a la programación en c

5.5. Ejercicios 40

Page 51: Introducción a la programación en c

41 6. Tipos de datos elementales

Capıtulo 6

Tipos de datos elementales

Hasta el momento se ha usado implıcitamente el tipo de datos entero en todos los ejemplos presentados.En este capıtulo entraremos en los detalles de este tipo de datos, ası como de otros tipos de datospredefinidos en C.

C proporciona los siguientes tipos de datos elementales:

� numeros enteros,

� caracteres, y

� numeros de coma flotante (reales).

6.1 Numeros enteros

En el apendice C se describe el formato usado para almacenar numeros enteros en memoria. Es impor-tante que el lector se familiarice con este formato para obtener una mejor compresion de esta seccion.

La palabra clave utilizada para especificar el tipo de datos entero es int. Dependiendo del orde-nador, del sistema operativo y el compilador utilizados, el tamano de un entero en memoria varıa. Sinembargo, hoy en dıa la mayorıa de los computadores almacenan las variables del tipo int en 32 bits (4bytes), por lo que el rango representable de numeros enteros va desde �2147483648 hasta 2147483647(esto es, desde �231 hasta 231�1). Por otro lado, en el entorno IBM-PC aun existen compiladores endonde un entero solo ocupa 16 bits (2 bytes), con un rango entre �32768 y 32767 (desde �215 hasta215 � 1). Por ejemplo, Turbo C 2.0, Visual C++ 1.5, etc.

Aunque ya se ha visto previamente, el formato para declarar variables enteras es:

int lista de variables;

Donde se especifica una lista de nombres de variables separados por comas.

Los numeros enteros en C se expresan mediante una serie de dıgitos, precedidos o no por un signo+ o �. Si el numero es positivo, el signo + es opcional. Habitualmente se utiliza la notacion decimal,aunque tambien es posible usar notacion octal y hexadecimal. En los numeros expresados en octal, seutiliza un 0 como primer dıgito, mientras que en los numeros expresados en hexadecimal, el numeroes precedido por un 0 y una equis mayuscula o minuscula (0x o 0X). La tabla 6.1 muestra algunosejemplos de representacion de constantes enteras en los distintos formatos.

Page 52: Introducción a la programación en c

6.1. Numeros enteros 42

Tabla 6.1: Representacion de enteros en decimal, octal y hexadecimal

Decimal Octal Hexadecimal27 033 0X1B4026 07672 0xFBA-149 -0225 -0x95

6.1.1 Modificadores

C define una serie de modificadores para el tipo de datos entero.

El modificador short

Este modificador se emplea para representar numeros enteros de 16 bits, independientemente del pro-cesador, por lo que su rango es [�32768; 32767]. Ası pues, hay entornos de programacion donde eltamano y rango de una variable short int coincide con el de int. Mientras que en otros entor-nos, una variable de tipo short int ocupa la mitad de espacio y tiene un rango mucho menor. Ladeclaracion de variables de este tipo tiene la forma:

short int lista de variables;

O simplemente:

short lista de variables;

No existe ninguna manera de especificar explıcitamente constantes de tipo short int.

El modificador long

Este modificador se emplea para representar numeros enteros con un rango mayor al permitido porint, por lo que tambien ocupan mas espacio en memoria. Por lo tanto las variables del tipo longint pueden ocupar 32 o 64 bits segun el entorno. Habitualmente son 64 bits, lo que da un rango derepresentacion de [�9223372036854775808; 9223372036854775807], esto es [�263;�263 � 1]. Ladeclaracion de variables es como sigue:

long int lista de variables;

O simplemente:

long lista de variables;

Para especificar explıcitamente que una constante es de tipo long int, debe escribirse una letraele (mayuscula o minuscula), justo detras del valor constante. Cabe decir, que esto solo es necesarioen caso que el valor de la constante que se desea especificar este dentro del rango de int. Es re-comendable el uso de ‘L’, pues ‘l’ puede confundirse con el dıgito 1. A continuacion se muestra unejemplo:

Page 53: Introducción a la programación en c

43 6. Tipos de datos elementales

long x;. . .x = -554L;x = 187387632;

El modificador signed

Es el modificador usado por defecto para todo dato de tipo int, por lo que no suele utilizarse de formaexplıcita. En cualquier caso, las declaraciones tiene la forma:

signed int lista de variables;

O simplemente:

int lista de variables;

El modificador unsigned

Este modificador permite especificar numeros enteros sin signo. Como consecuencia de eliminar elsigno de la representacion, el rango de valores positivos se amplia hasta [0; 65535] si se emplean 16bits, o [0; 4294967295] si se emplean 32 bits. La declaracion de variables se realiza como:

unsigned int lista de variables;

O simplemente:

unsigned lista de variables;

Pueden especificarse constantes de tipo unsigned, escribiendo una letra u mayuscula justo detrasdel valor constante, como en:

unsigned x;. . .x = 45728;x = 345U;

Finalmente, cabe comentar que el modificador unsigned puede combinarse con short ylong. Por ejemplo, los datos de tipo unsigned long tienen un rango valido de [0; 264 � 1], esdecir [0; 18446744073709551615].

6.1.2 Resumen

La tabla 6.2 resume los distintos tipos de datos enteros que se han presentado, mostrando su rango,ocupacion de memoria en bits y el modificador de formato para printf y scanf.

Page 54: Introducción a la programación en c

6.2. Caracteres 44

Tabla 6.2: Resumen de tipos de datos enteros

Tipo Rango Tamano Formatoint [�2147483648; 2147483647] 32 bits %dunsigned int [0; 4294967295] 32 bits %ushort int [�32768; 32767] 16 bits %dunsigned short int [0; 65535] 16 bits %ulong int [�263; 263 � 1] 64 bits %ldunsigned long int [0; 264 � 1] 64 bits %lu

6.2 Caracteres

Este tipo de datos se identifica con la palabra reservada char. Comprende un conjunto ordenado yfinito de caracteres, representados mediante codigos numericos. La codificacion mas extendida es elestandar ASCII que utiliza 8 bits. Ası pues, el tipo de datos char es internamente tratado comoun entero, aunque puede manipularse como un caracter propiamente. El apendice D muestra la tablacompleta de codigos ASCII y los caracteres asociados. La declaracion de variables se realiza como:

char lista de variables;

La forma habitual de expresar una constante de tipo char es mediante el caracter correspondienteentre comillas:

char x, y;. . .x = ’f’;y = ’?’;x = ’5’;

Debido a la representacion interna de los caracteres mediante numeros enteros, pueden realizarseoperaciones aritmeticas como: ’F’+’5’ , o lo que es lo mismo, 70 + 53 = 123, que equivale alcaracter ’f’; o como ’F’+5, esto es 70 + 5 = 75 que equivale al caracter ’K’.

Por la misma razon tambien se establece un orden entre los caracteres, lo que permite usar losoperadores relacionales habituales. Ası pues, la comparacion ’a’ <= ’h’ da como resultado elvalor cierto, o la comparacion ’K’ > ’V’ da como resultado el valor falso.

6.2.1 Caracteres especiales

Algunos de los caracteres definidos por el estandar ASCII no son directamente imprimibles, por lo quepara utilizarlos en un programa es necesario un mecanismo especial. En realidad, C ofrece dos:

� Puede usarse el codigo ASCII asociado al caracter, que puede representarse tanto en decimal,octal o hexadecimal. Por ejemplo, el caracter ’n9’ representa un tabulador mediante su codigoASCII en decimal; el caracter ’n040’ corresponde al espacio en blanco mediante su codigoASCII en octal, etc.

� O bien, pueden usarse secuencias de escape especiales, como ’nn’ , para forzar un salto delınea, que hemos utilizado en ejemplos anteriores. La siguiente tabla muestra los mas utilizados:

Page 55: Introducción a la programación en c

45 6. Tipos de datos elementales

Tabla 6.3: Caracteres interpretados como enteros

Tipo Rango Tamano Formatochar [�128; 127] 1 byte %csigned char [�128; 127] 1 byte %dunsigned char [0; 255] 1 byte %u

Codigo Significadona pitidonn salto de lıneanf salto de paginanr principio de la misma lıneant tabuladorn’ apostrofen" comillasn0 caracter nulo

6.2.2 Enteros y el tipo char

Puesto que el tipo char esta internamente representado mediante codigos numericos de 8 bits, unavariable de este tipo puede interpretarse tambien como una variable entera. Ası pues, pueden utilizarsetambien los modificadores signed y unsigned. Los rangos de valores se muestran en la tabla 6.3.

6.2.3 Conversiones de tipos

En general, en C puede convertirse el tipo de una variable o constante a otro tipo cualquiera. Para ello,basta con escribir delante de dicha variable el nuevo tipo entre parentesis. Aunque no profundizaremosen la conversion de tipos en C, veremos su utilidad en este caso, para obtener el codigo ASCII de uncaracter y viceversa.

En el ejemplo de la izquierda, se asigna a la variable cod el codigo ASCII del caracter ’A’ al-macenado en la variable c, que es 65. En el ejemplo de la derecha, se asigna a la variable c el caractercorrespondiente al codigo ASCII 98 almacenado en la variable cod, que es el caracter ’b’.

int cod;char c;. . .c = ’A’;cod = (int) c;. . .

int cod;char c;. . .cod = 98;c = (char) cod;. . .

Para concluir, un par de aspectos practicos:

� Si la variable c almacena algun caracter del rango ’0’...’9’, puede obtenerse su valornumerico equivalente (no confundir con el codigo ASCII) mediante la sentencia:

i = (int)c - (int)’0’;

Page 56: Introducción a la programación en c

6.3. Numeros reales 46

Tabla 6.4: Resumen de tipos de datos reales

Tipo Rango Tamano Formatofloat �1:1754945E� 38 4 bytes %f, %e, %g

�3:4028232E+ 38

double �2:225073858507202E� 308 8 bytes %f, %e, %g�1:797693134862312E+ 308

long double �8:4 : : : E � 4932 16 bytes %f, %e, %g�5:9 : : : E + 4932

� Si i es una variable entera con un valor en el rango [0; 9], puede obtenerse el caracter corres-pondiente de la siguiente forma:

c = (char)((int)’0’ + i);

6.3 Numeros reales

Nuevamente, en el apendice C se describe el formato de coma flotante usado para almacenar numerosreales. Es importante que el lector se familiarice con este formato para obtener una mejor comprensionde esta seccion.

En C existen basicamente dos tipos de numeros reales, los de precision simple (float) y los deprecision doble (double). Un valor de tipo float ocupa 4 bytes (32 bits) y permite representarnumeros reales con 8 dıgitos de precision. Un valor de tipo double ocupa 8 bytes (64 bits) y permiterepresentar numeros reales con 16 dıgitos de precision (ver Tab. 6.4).

Las constantes reales pueden representarse mediante dos notaciones: la decimal, constituida por unaserie de dıgitos donde la parte decimal se situa detras de un punto, por ejemplo -5.034 o 443.43;y la notacion cientıfica o exponencial, formada por una parte en notacion decimal que representa lamantisa, seguida del caracter ‘E’ o ‘e’ y un numero entero que representa el exponente, como porejemplo: -3.56E67 o 7.9e-15.

Por defecto, las constantes reales se consideran de tipo double. Para especificar explıcitamenteconstantes de tipo float, debe escribirse una letra efe mayuscula tras la constante, como en el si-guiente ejemplo:

double x;float y;. . .x = 34E23;y = 2.3E12F;

Algunos compiladores admiten el uso del modificador long para el tipo double. Este tipo devariables se almacenan en 16 bytes y proporcionan precision cuadruple (32 dıgitos).

Para concluir, veamos un ejemplo de uso de numeros reales en un programa que calcula el suma-

torio1Xi=1

1

icon un error inferior a un valor � , mientras que i � 1000. Expresando el error

Page 57: Introducción a la programación en c

47 6. Tipos de datos elementales

matematicamente tenemos:

j

kXi=1

1

i�

k�1Xi=1

1

ij < �

A continuacion se muestra un programa que realiza dicho calculo:

#include <stdio.h>#define LIMITE 1000void main ()f

int i, fin;float suma, t, epsilon;

suma = 0.0F;fin = 0;i = 1;scanf( "%f", &epsilon );while (!fin)f

t = 1.0F / (float)i;suma = suma + t;i++;fin = ((t < epsilon) || (i > LIMITE));

g

printf( "La suma es %fnn", suma );g

Observese el uso del modificador de formato %f para la entrada y salida de valores de coma flotante,y el uso de la variable fin para controlar la terminacion del bucle.

En el fichero de cabeceras math.h (#include <math.h>), perteneciente a la librerıa estandar(ver Ap. B), se definen una serie de funciones matematicas para operar con valores reales, como:sqrt para la raız cuadrada, sin y cos para senos y cosenos, etc.

6.4 Ejercicios

1. Escribir un programa que cuente el numero de veces que aparece una letra en una secuencia decaracteres acabada en ’.’ .

2. Escribir un programa que lea un caracter de teclado e informe de si es alfabetico, numerico,blanco o un signo de puntuacion.

3. Escribir un programa que convierta una secuencia de dıgitos entrados por teclado al numero ente-ro correspondiente. Supongase que el primer dıgito leıdo es el de mayor peso. Observese tambienque el peso efectivo de cada dıgito leıdo es desconocido hasta haber concluido la introduccion dela secuencia.

Page 58: Introducción a la programación en c

6.4. Ejercicios 48

4. Sean las variables enteras i y j con valores 5 y 7, respectivamente. Y las variables decoma flotante f y g con valores 5:5 y �3:25, respectivamente. ¿Cual sera el resultado de lassiguientes 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 raıces de una ecuacion de segundogrado, leyendo previamente los coeficientes A, B y C de la misma: Ax2 +Bx+ C = 0.

6. Escribir un programa que calcule el perımetro de una circunferencia y que lo muestre por pantallacon cuatro decimales de precision. Si el radio introducido por el usuario es negativo, el perımetroresultante sera 0.

7. Escribir un programa para calcular de forma aproximada el numero e. Recordar que e =

1Xi=0

1

i!.

Page 59: Introducción a la programación en c

49 7. Tipos de datos estructurados: Tablas

Capıtulo 7

Tipos de datos estructurados: Tablas

En este capıtulo veremos algunos de los mecanismos que C ofrece para la creacion de tipos de datoscomplejos. Estos se construyen, en un principio, a partir de los tipos de datos elementales predefinidospor el lenguaje (ver Cap. 6).

Comenzaremos hablando del tipo abstracto de datos tabla, tanto de una (vectores), dos (matrices) omultiples dimensiones. En C existe un tratamiento especial para los vectores de caracteres, por lo quededicaremos una parte de este capıtulo a su estudio. Se deja para el capıtulo 8 el estudio de otros tiposde datos estructurados, como las estructuras, las uniones, y los tipos de datos enumerados.

Las tablas en C son similares a las que podemos encontrar en otros lenguajes de programacion.Sin embargo, se definen de forma diferente y poseen algunas peculiaridades derivadas de la estrecharelacion con los punteros. Volveremos a esto mas adelante en el capıtulo 9.

7.1 Vectores

Los vectores, tambien llamados tablas unidimensionales, son estructuras de datos caracterizadas por:

� Una coleccion de datos del mismo tipo.

� Referenciados mediante un mismo nombre.

� Almacenados en posiciones de memoria fısicamente contiguas, de forma que, la direccion dememoria mas baja corresponde a la del primer elemento, y la direccion de memoria mas altacorresponde a la del ultimo elemento.

El formato general para la declaracion de una variable de tipo vector es el siguiente:

tipo de datos nombre tabla [tamano];

donde:

� tipo de datos indica el tipo de los datos almacenados por el vector. Recordemos que todoslos elementos del vector son forzosamente del mismo tipo. Debe aparecer necesariamente en ladeclaracion, puesto que de ella depende el espacio de memoria que se reservara para almacenarel vector.

Page 60: Introducción a la programación en c

7.1. Vectores 50

del vectorÚltimo elementoPrimer elemento

del vector

10 2 N-13

. . .

Figura 7.1: Representacion grafica de un vector de N elementos

� nombre tabla es un identificador que usaremos para referiremos tanto al vector como untodo, como a cada uno de sus elementos.

� tamano es una expresion entera constante que indica el numero de elementos que contendra elvector. El espacio ocupado por un vector en memoria se obtiene como el producto del numero deelementos que lo componen y el tamano de cada uno de estos.

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 posicion relativa que ocupa dicho elemento dentro del vector y seespecifica mediante una expresion entera (normalmente una constante o una variable). Formalmente:

nombre vector[ındice];

A continuacion se muestran algunas formas validas de acceso a los elementos de un vector:

int contador[10];int i, j, x;. . .x = contador[1];x = contador[i];x = contador[i * 2 + j];. . .

Como muestra la figura 7.1, el primer elemento de un vector en C se situa en la posicion 0, mientrasque el ultimo lo hace en la posicion N � 1 (N indica el numero de elementos del vector). Por estarazon, el ındice para acceder a los elementos del vector debe permanecer entre estos dos valores. Esresponsabilidad del programador garantizar este hecho, para no acceder a posiciones de memoria fueradel vector, lo cual producirıa errores imprevisibles en el programa.

7.1.2 Asignacion

La asignacion de valores a los elementos de un vector se realiza de forma similar a como se consultan.Veamoslo en un ejemplo:

Page 61: Introducción a la programación en c

51 7. Tipos de datos estructurados: Tablas

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 necesariodar a sus elementos un valor inicial. La manera mas habitual de inicializar un vector en tiempo deejecucion consiste en recorrer secuencialmente todos sus elementos y darles el valor inicial que lescorresponda. Veamoslo en el siguiente ejemplo, donde todos los elementos de un vector de numerosenteros toman el valor 0:

#define TAM 100void main()f

int vector[TAM], i;

for (i= 0; i< TAM; i++)vector[i] = 0;

g

Notese que el bucle recorre los elementos del vector empezando por el elemento 0 (i=0) y hasta elelemento TAM-1 (condicion i<TAM).

Existe tambien un mecanismo que permite asignar un valor a todos los elementos de un tabla conuna sola sentencia. Concretamente en la propia declaracion de la tabla. La forma general de inicializaruna tabla de cualquier numero de dimensiones es la siguiente:

tipo de datos nombre tabla [tam1]...[tamN] = f lista valores g;

La lista de valores no debera contener nunca mas valores de los que puedan almacenarse en la tabla.Veamos algunos ejemplos:

int contador[3] = f24, 12, 6g; /* Correcto */char vocales[5] = f’a’, ’e’, ’i’, ’o’, ’u’g; /* Correcto */int v[4] = f2, 6, 8, 9, 10, 38g; /* Incorrecto */

Finalmente, cabe destacar que no esta permitido en ningun caso comparar dos vectores (en generaldos tablas de cualquier numero de dimensiones) utilizando los operadores relacionales que vimos en laseccion 3.4.3. Tampoco esta permitida la copia de toda una tabla en otra con una simple asignacion. Sise desea comparar o copiar toda la informacion almacenada en dos tablas, debera hacerse elemento aelemento.

Los mecanismos de acceso descritos en esta seccion son identicos para las matrices y las tablas mul-tidimensionales. La unica diferencia es que sera necesario especificar tantos ındices como dimensionesposea la tabla a la que se desea acceder. Esto lo veremos en las siguientes secciones.

Page 62: Introducción a la programación en c

7.1. Vectores 52

7.1.3 Ejemplos

A continuacion se muestra un programa que cuenta el numero de apariciones de los numeros 0, 1, 2 y3 en una secuencia de enteros acabada en �1.

#include <stdio.h>void main ()f

int num, c0=0, c1=0, c2=0, c3=0;

scanf("%d", &num);while (num != -1)f

if (num == 0) c0++;if (num == 1) c1++;if (num == 2) c2++;if (num == 3) c3++;scanf( "%d", &num );

g

printf( "Contadores:%d, %d, %d, %dnn", c0, c1, c2, c3 );g

¿Que ocurrirıa si tuviesemos que contar las apariciones de los cien primeros numeros enteros?¿Deberıamos declarar cien contadores y escribir cien construcciones if para cada caso? La respuesta,como era de esperar, se halla en el uso de vectores. Veamoslo en el siguiente programa:

#include <stdio.h>#define MAX 100void main ()f

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

for (i= 0; i< MAX; i++)printf( "Contador [%d] = %dnn", i, cont[i] );

g

Veamos finalmente otro ejemplo en el que se muestra como normalizar un vector de numeros reales.La normalizacion consiste en dividir todos los elementos del vector por la raız cuadrada de la suma desus cuadrados. Destaca el uso de la constante MAX para definir el numero de elementos del vector yde la funcion sqrt para el calculo de raıces cuadradas.

Page 63: Introducción a la programación en c

53 7. Tipos de datos estructurados: Tablas

#include <math.h>#include <stdio.h>#define MAX 10void main()f

float vector[MAX], modulo;int i;

/* Lectura del vector. */for (i= 0; i< MAX; i++)

scanf("%f", &vector[i]);

/* Calcular modulo. */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] );g

7.2 Matrices

Las matrices, tambien llamadas tablas bidimensionales, no son otra cosa que vectores con dos dimen-siones. Por lo que los conceptos de acceso, inicializacion, etc. son similares.

La declaracion de una variable matriz tiene la forma siguiente:

tipo de datos nombre tabla [tamano dim1][tamano dim2];

Donde tamano dim1 y tamano dim2 indican, respectivamente, el numero de filas y decolumnas que componen la matriz. La figura 7.2 muestra la representacion grafica habitual de unamatriz de datos.

Otro hecho importante es que las matrices en C se almacenan “por filas”. Es decir, que los elementosde cada fila se situan en memoria de forma contigua. Ası pues, en la matriz de la figura anterior, elprimer elemento almacenado en memoria es el (0,0), el segundo el (0,1), el tercero el (0,2), . . . ,(0,M-1), despues (1,0), y ası sucesivamente hasta el ultimo elemento, es decir (N-1,M-1).

Page 64: Introducción a la programación en c

7.2. Matrices 54

. . .

. . .

. . .

. . .

. . .

. . . Primer elemento

de la matriz

de la matriz

Último elemento

10 2 M - 13

. . . 0

1

2

3

N - 1

Figura 7.2: Representacion grafica de una matriz de N filas y M columnas

7.2.1 Consulta

El acceso a un elemento de una matriz se realiza mediante el nombre de esta y dos ındices (uno paracada dimension) entre corchetes. El primer ındice representa la fila y el segundo la columna en que seencuentra dicho elemento. Tal como muestra la figura 7.2, el ındice de las filas tomara valores entre 0 yel numero de filas menos 1, mientras que el ındice de las columnas tomara valores entre 0 y el numerode columnas menos 1. Es responsabilidad del programador garantizar este hecho.

nombre matriz[ındice 1][ındice 2];

7.2.2 Asignacion

Comentaremos unicamente la inicializacion de una matriz en la propia declaracion. El siguiente ejem-plo declara una matriz de tres filas y cuatro columnas y la inicializa. Por claridad, se ha usado identacionen los datos, aunque hubiesen podido escribirse todos en una sola lınea.

int mat[3][4] = f 24, 12, 6, 17,15, 28, 78, 32,0, 44, 3200 , -34

g;

La inicializacion de matrices, y en general de tablas multidimensionales, puede expresarse de formamas clara agrupando los valores mediante llaves (f g), siguiendo la estructura de la matriz. Ası pues,el ejemplo anterior tambien podrıa escribirse como:

int mat[3][4] = f f 24, 12, 6, 17 g,f 15, 28, 78, 32 g,f 0, 44, 3200 , -34 g

g;

Page 65: Introducción a la programación en c

55 7. Tipos de datos estructurados: Tablas

7.2.3 Ejemplo

El siguiente ejemplo calcula la matriz traspuesta de una matriz de enteros. La matriz tendra unasdimensiones maximas segun la constante MAX.

#include <stdio.h>#define MAX 20void main()f

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

printf( "nnmatriz[%d][%d] = ", i, j );scanf( "%d", &matriz[i][j] );

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] );g

Observese que para recorrer todos los elementos de una matriz es necesario el empleo de dos buclesanidados que controlen los ındices de filas y columnas (siempre entre 0 y el numero de filas o columnasmenos 1). En este ejemplo todos los recorridos se realizan “por filas”, es decir, que primero se visitantodos los elementos de una fila, luego los de la siguiente, etc. Finalmente, cabe comentar que para elrecorrido de tablas multidimensionales sera necesario el empleo de tantos bucles como dimensionestenga la tabla.

7.3 Tablas multidimensionales

Este tipo de tablas se caracteriza por tener tres o mas dimensiones. Al igual que vectores y matrices,todos los elementos almacenados en ellas son del mismo tipo de datos.

La declaracion de una tabla multidimensional tiene la forma siguiente:

Page 66: Introducción a la programación en c

7.3. Tablas multidimensionales 56

10 2 M - 13

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

. . .

Primer elementode la tabla

de la tabla

Último elemento

0

1

2

3

N - 10

2

1

Figura 7.3: Representacion grafica de una tabla de tres dimensiones: N �M � 3

tipo de datos nombre tabla [tamano dim1] ...[tamano dimN];

Para acceder a un elemento en particular sera necesario usar tantos ındices como dimensiones:

nombre vector[ındice 1] ...[ındice N];

Aunque pueden definirse tablas de mas de tres dimensiones, no es muy habitual hacerlo. La figu-ra 7.3 muestra como ejemplo la representacion grafica habitual de una tabla de tres dimensiones.

7.3.1 Ejemplo

El siguiente ejemplo muestra el empleo de una tabla multidimensional. Concretamente, el programautiliza una tabla de 3 dimensiones para almacenar 1000 numeros aleatorios. Para generar numerosaleatorios se usa la funcion rand de la librerıa estandar stdlib.h. Tambien se ha usado la funciongetchar (stdio.h), que interrumpe el programa y espera a que el usuario presione una tecla.

#include <stdio.h>#include <stdlib.h>#define DIM 10void main()f

int tabla random [DIM][DIM][DIM], a, b, c;

for (a= 0; a< DIM; a++)

Page 67: Introducción a la programación en c

57 7. Tipos de datos estructurados: Tablas

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

for (c= 0; c < DIM; c++)f

printf( "nn tabla[%d][%d][%d] = ", a, b, c );printf( "%d", tabla random[a][b][c] );

g

printf( "nnPulse ENTER para seguir" );getchar();

g

g

7.4 Cadenas de caracteres

Las cadenas de caracteres son vectores de tipo caracter (char) que reciben un tratamiento especial parasimular el tipo de datos “string”, presente en otros lenguajes de programacion.

Para que un vector de caracteres pueda ser considerado como una cadena de caracteres, el ultimode los elementos utiles del vector debe ser el caracter nulo (codigo ASCII 0). Segun esto, si se quieredeclarar una cadena formada por N caracteres, debera declararse un vector con N + 1 elementos detipo caracter. Por ejemplo, la declaracion char cadena[6]; reserva suficiente espacio en memoriapara almacenar una cadena de 5 caracteres, como la palabra "casco":

10 2 3 4 5

c a s c o \0

En C pueden definirse constantes correspondientes a cadenas de caracteres. Se usan comillas doblespara delimitar el principio y el final de la cadena, a diferencia de las comillas simples empleadas conlas constantes de tipo caracter. Por ejemplo, la cadena constante "H" tiene muy poco que ver con elcaracter constante ’H’, si observamos la representacion interna de ambos:

\0H"H" H’H’

7.4.1 Asignacion

Mientras que la consulta de elementos de una cadena de caracteres se realiza de la misma forma quecon los vectores, las asignaciones tienen ciertas peculiaridades.

Page 68: Introducción a la programación en c

7.4. Cadenas de caracteres 58

Como en toda tabla, puede asignarse cada caracter de la cadena individualmente. No debera olvi-darse en ningun caso que el ultimo caracter valido de la misma debe ser el caracter nulo (’n0’). Elsiguiente ejemplo inicializa la cadena de caracteres cadena con la palabra "casco". Notese quelas tres ultimas posiciones del vector no se han usado. Es mas, aunque se les hubiese asignado alguncaracter, su contenido serıa ignorado. Esto es, el contenido del vector en las posiciones posteriores alcaracter nulo es ignorado.

char cadena[10];

. . .cadena[0] = ’c’;cadena[1] = ’a’;cadena[2] = ’s’;cadena[3] = ’c’;cadena[4] = ’o’;cadena[5] = ’n0’;

La inicializacion de una cadena de caracteres durante la declaracion puede hacerse del mismo modoque en los vectores, aunque no debe olvidarse incluir el caracter nulo al final de la cadena. Sin embargo,existe un metodo de inicializacion propio de las cadena de caracteres, cuyo formato general es:

char nombre [tamano] = "cadena";

Usando este tipo de inicializacion, el caracter nulo es anadido automaticamente al final de la cadena.Ası pues, una inicializacion tıpica de vectores como la siguiente:

char nombre[10] = f ’N’, ’U’, ’R’, ’I’, ’A’, ’n0’ g;

puede hacerse tambien de forma equivalente como:

char nombre[10] = "NURIA";

Finalmente, la inicializacion anterior puede hacerse sin necesidad de especificar el tamano del vec-tor correspondiente. En este caso, el compilador se encarga de calcularlo automaticamente, reservandoespacio de memoria suficiente para almacenar la cadena, incluyendo el caracter nulo al final. Ası pues,la siguiente declaracion reserva memoria para almacenar 6 caracteres y los inicializa adecuadamentecon las letras de la palabra NURIA:

char nombre[] = "NURIA";

La cadena vacıa

Otra curiosidad de las cadenas de caracteres se refiere a la cadena vacıa, "" , que consta unicamentedel caracter nulo. Puesto que los caracteres posteriores al caracter nulo son ignorados, convertir unacadena con cualquier valor almacenado a la cadena vacıa es tan simple como asignar el caracter nulo ala posicion 0 de dicha cadena. He aquı un ejemplo:

char cadena [12] = "Una frase";. . .cadena[0] = ’n0’; /* Ahora es una cadena vacıa */

Page 69: Introducción a la programación en c

59 7. Tipos de datos estructurados: Tablas

10 2 3 4 5 6 7 8 9 10 11

U n a f r a s e \0

cadena[0] = ’\0’;

"Una frase"

""

10 2 3 4 5 6 7 8 9 10 11

\0 n a f r a s e \0

7.4.2 Manejo de cadenas de caracteres

Aunque C no incorpora en su definicion operadores para el manejo de cadenas de caracteres, todo com-pilador de C proporciona una librerıa estandar (string.h) con funciones para facilitar su utilizacion.Destacar algunas de ellas:

� strlen para obtener la longitud de la cadena, sin contar el caracter nulo,

� strcpy para copiar una cadena en otra,

� strcat para concatenar dos cadenas,

� strcmp para comparar dos cadenas, etc.

Para mas informacion sobre estas y otras funciones, consultar el apendice B.

Entrada y salida

En cuanto a la entrada y salida de cadenas de caracteres, existe un formato especial %s que puedeutilizarse en las funciones scanf y printf. Por ejemplo, la siguiente sentencia leera una cadena decaracteres en la variable cad. Solo se asignaran caracteres mientras no sean caracteres blancos, tabu-ladores o saltos de lınea. Por lo tanto, el empleo de %s solo tendra sentido para la lectura de palabras.Ademas del formato %s existen los formatos %[ˆabc] y %[abc], que permiten leer respectivamenteuna cadena de caracteres hasta encontrar algun caracter del conjunto fa, b, cg, o bien hasta no encontrarun caracter del conjunto fa, b, cg. En cualquier caso el caracter del conjunto fa, b, cg no es leıdo. Verel apendice B para mas informacion sobre el empleo de scanf y la lectura de cadenas de caracteres.

char cad[20];. . .scanf("%s", cad);

Notese que, en el ejemplo, no se ha antepuesto el sımbolo & a la variable cad. Por el momento,tengamoslo en mente y esperemos hasta el capıtulo 9 para comprender a que se debe este hecho.

La librerıa estandar de entrada y salida (stdio.h) proporciona ademas las funciones gets yputs, que permiten leer de teclado y mostrar por pantalla una cadena de caracteres completa, respec-tivamente (ver el apendice B para mas detalles).

Page 70: Introducción a la programación en c

7.4. Cadenas de caracteres 60

7.4.3 Ejemplos

Para finalizar, veamos un par de ejemplos de manejo de cadenas de caracteres.

El siguiente programa cuenta el numero de veces que se repite una palabra en una frase. El programaemplea la funcion de comparacion de cadenas strcmp. Dicha funcion devuelve 0 en caso de que lascadenas comparadas sean iguales (ver Sec. B.1).

#include <stdio.h>#include <string.h>#define MAXLIN 100void main()f

char pal[MAXLIN]; /* La que buscamos. */char palfrase[MAXLIN]; /* Una palabra de la frase. */char c;int total = 0;

printf( "nnPALABRA:" );scanf( "%s", pal );printf( "nnFRASE:" );c = ’ ’;while (c != ’nn’)f

scanf( "%s%c", palfrase, &c );if (strcmp(pal, palfrase) == 0)

total++;g

printf( "nnLa palabra %s aparece %d veces.", pal, total );g

A continuacion se muestra otro ejemplo que hace uso de las cadenas de caracteres. El programa leedos cadenas de caracteres, las concatena, convierte las letras minusculas en mayusculas y viceversa, yfinalmente escribe la cadena resultante.

#include <stdio.h>#include <string.h>void main()f

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 );

Page 71: Introducción a la programación en c

61 7. Tipos de datos estructurados: Tablas

strcat( cad3, cad2 );i = 0;delta = ’a’ - ’A’;while (cad3[i] != ’n0’)f

if ((cad3[i] >= ’a’) && (cad3[i] <= ’z’))cad3[i] -= delta; /* Convierte a mayuscula */

else if ((cad3[i] >= ’A’) && (cad3[i] <= ’Z’))cad3[i] += delta; /* Convierte a minuscula */

i++;g

printf( "La cadena resultante es: %s nn", cad3 );g

7.5 Ejercicios

1. ¿Donde esta el error en el siguiente programa?

void main()f

int vector [10];int x = 1;

for (x= 1; x<= 10; x++)vector[x] = 23;

g

2. Escribir un programa que lea del teclado un vector de 10 numeros enteros, lo invierta y finalmentelo muestre de nuevo.

3. Escribir un programa que cuente el numero de palabras de mas de cuatro caracteres en una frase.Esta se almacena en forma de vector cuyo ultimo elemento es el caracter ’.’.

4. Escribir un programa que lea del teclado dos numeros enteros de hasta 20 dıgitos y los sume.Usar vectores para almacenar los dıgitos de cada numero.

5. Escribir un programa que decida si una palabra es palındroma o no. La palabra se almacena enun vector de caracteres acabado en ’.’.

6. Escribir un programa para calcular la moda de un conjunto de numeros enteros. La moda es elvalor que se repite mas veces.

7. ¿Donde esta el error en el siguiente programa?

void main()f

int matriz [10][3], x, y;

Page 72: Introducción a la programación en c

7.5. Ejercicios 62

for (x= 0; x< 3; x++)for (y= 0; y< 10; y++)

matrix[x][y] = 0;g

8. Escribir un programa que inicialice cada elemento de una matriz de enteros con el valor de lasuma del numero de fila y columna en que esta situado.

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 deuna matriz es un punto de silla si es el mınimo de su fila y el maximo de su columna.

11. Escribir un programa que determine si una matriz es simetrica.

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 unaposicion valida dentro de ella, genere una nueva cadena de caracteres que contenga todos loscaracteres a la izquierda de dicha posicion, pero en orden inverso.

15. Escribir un programa que, dada una cadena de caracteres, la limpie de caracteres blancos. Porejemplo, la cadena "Esto es una frase" debera transformarse en "Estoesunafrase".Escribir dos versiones, una utilizando una cadena auxiliar y otra version que realice los cambiossobre la misma cadena.

16. Escribir un programa que lea dos cadenas de caracteres, las compare e informe de si son igualeso diferentes. No usar la funcion de la librerıa estandar strcmp.

Page 73: Introducción a la programación en c

63 8. Otros tipos de datos

Capıtulo 8

Otros tipos de datos

En este capıtulo veremos los restantes mecanismos que C ofrece para la creacion y manejo de tipo dedatos complejos. Concretamente las estructuras y las uniones. Por otra parte, se incluye tambien unapartado que estudia los tipos de datos enumerados y otro donde se trata la definicion de nuevos tiposde datos por parte del programador.

8.1 Estructuras

En el capıtulo 7 hemos estudiado el tipo abstracto de datos tabla, formado por un conjunto de elementostodos ellos del mismo tipo de datos. En una estructura, sin embargo, los elementos que la componenpueden ser de distintos tipos. Ası pues, una estructura puede agrupar datos de tipo caracter, enteros,cadenas de caracteres, matrices de numeros . . . , e incluso otras estructuras. En cualquier caso, eshabitual que todos los elementos agrupados en una estructura tengan alguna relacion entre ellos. Enadelante nos referiremos a los elementos que componen una estructura como campos.

La definicion de una estructura requiere especificar un nombre y el tipo de datos de todos los camposque la componen, ası como un nombre mediante el cual pueda identificarse toda la agrupacion. Paraello se utiliza la palabra reservada struct de la forma siguiente:

struct nombre estructuraf

tipo campo 1 nombre campo 1;tipo campo 2 nombre campo 2;. . .tipo campo N nombre campo N;

g;

El siguiente ejemplo define una estructura denominada cliente en la que puede almacenarse laficha bancaria de una persona. El significado de los diferentes campos es obvio:

struct clientef

char nombre[100];long int dni;

Page 74: Introducción a la programación en c

8.1. Estructuras 64

char domicilio[200];long int no cuenta;float saldo;

g

Puede decirse que la definicion de una estructura corresponde a la definicion de una “plantilla”generica que se utilizara posteriormente para declarar variables. Por tanto la definicion de una estructurano representa la reserva de ningun espacio de memoria.

8.1.1 Declaracion de variables

La declaracion de variables del tipo definido por una estructura puede hacerse de dos maneras diferen-tes. Bien en la misma definicion de la estructura, bien posteriormente. La forma generica para el primercaso es la siguiente:

struct nombre estructuraf

tipo campo 1 nombre campo 1;tipo campo 2 nombre campo 2;. . .tipo campo N nombre campo N;

g lista de variables;

Notese que al declararse las variables al mismo tiempo que se define la estructura, el nombre de estajunto a la palabra reservada struct se hace innecesario y puede omitirse.

Por otra parte, suponiendo que la estructura nombre estructura se haya definido previamen-te, la declaracion de variables en el segundo caso serıa:

struct nombre estructura lista de variables;

Siguiendo con el ejemplo anterior, segun la primera variante de declaracion, podrıamos escribir:

structf

char nombre[100];long int dni;char domicilio[200];long int no cuenta;float saldo;

g antiguo cliente, nuevo cliente;

O bien, de acuerdo con la segunda variante donde se asume que la estructura cliente se ha definidopreviamente:

struct cliente antiguo cliente, nuevo cliente;

Page 75: Introducción a la programación en c

65 8. Otros tipos de datos

8.1.2 Acceso a los campos

Los campos de una estructura de manejan habitualmente de forma individual. El mecanismo que Cproporciona para ello es el operador “punto” (.). La forma general para acceder a un campo en unavariable de tipo estructura es el siguiente:

variable.nombre campo

Ası pues, podrıamos acceder a los campos de la variable nuevo cliente como el nombre,numero de dni o el saldo de la siguiente forma:

nuevo cliente.nombre nuevo cliente.dni nuevo cliente.saldo

8.1.3 Asignacion

La asignacion de valores a los campos de una variable estructura puede realizarse de dos formas dife-rentes. Por un lado, accediendo campo a campo, y por otro, asignando valores para todos los camposen el momento de la declaracion de la variable.

A continuacion se muestran algunas posibles maneras de asignar datos a los campos individualesde la variable nuevo cliente declarada previamente. Como puede verse, cada campo es tratadocomo si de una variable se tratase, con lo que pueden usarse funciones como strcpy para copiar unacadena 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 inicializacion completa de todos los campos de unavariable de tipo estructura en el momento de su declaracion:

struct cliente nuevo cliente = f

"Federico Sancho Buch",23347098,"Rue del Percebe 13 - Madrid",7897894,1023459.34

g;

Finalmente, tambien es posible copiar todos los datos de una variable estructura a otra variableestructura del mismo tipo mediante una simple asignacion:

nuevo cliente = antiguo cliente;

No esta permitido en ningun caso, comparar dos estructuras utilizando los operadores relacionales quevimos en la seccion 3.4.3.

Page 76: Introducción a la programación en c

8.2. Uniones 66

8.1.4 Ejemplo

El siguiente programa define las estructuras de datos para gestionar un conjunto de fichas personales.Notese el uso de estructuras anidadas, ası como el uso de tablas de estructuras. Notese tambien el usode la funcion gets para leer cadenas de caracteres. Se deja como ejercicio para el lector escribir estemismo programa usando scanf en lugar de gets para leer dichas cadenas.

#include <stdio.h>struct datosf

char nombre[20];char apellido[20];long int dni;

g;struct tablapersonasf

int numpersonas;struct datos persona[100];

g;void main()f

int i;struct tablapersonas tabla;

printf( "Numero de personas: " );scanf( "%d", &tabla.numpersonas );for (i= 0; i< tabla.numpersonas; i++)f

printf( "nnNombre: " );gets( tabla.persona[i].nombre );printf( "nnApellido: " );gets( tabla.persona[i].apellido );printf( "nnDNI: " );scanf( "%ld", &tabla.persona[i].dni );

g

g

8.2 Uniones

Al igual que las estructuras, las uniones tambien pueden contener multiples campos de diferentes tiposde datos. Sin embargo, mientras que cada campo en una estructura posee su propia area de almacena-miento, en una union, todos los campos que la componen se hallan almacenados en la misma area dememoria. El espacio de memoria reservado por el compilador corresponde al del campo de la unionque requiere mayor espacio de almacenamiento. El resto de campos comparten dicho espacio.

Ası pues, los campos de una union estan solapados en memoria, por lo que, en un momento dado dela ejecucion del programa, solo podra haber almacenado un dato en uno de los campos. Es responsabili-

Page 77: Introducción a la programación en c

67 8. Otros tipos de datos

dad del programador hacer que el programa mantenga control sobre que campo contiene la informacionalmacenada en la union en cada momento. Intentar acceder al tipo de informacion equivocado puedeproducir resultados sin sentido.

La definicion de una union es muy similar a la de una estructura, excepto por el uso de la palabrareservada union:

union nombre unionf

tipo campo 1 nombre campo 1;tipo campo 2 nombre campo 2;. . .tipo campo N nombre campo N;

g;

Tanto la declaracion de variables de tipo union como el acceso a los campos se expresa de formasimilar a como se mostro en las estructuras.

Finalmente, diremos que una estructura puede ser el campo de una union y viceversa. Igualmente,pueden definirse tablas de uniones, etc.

8.2.1 Ejemplo

El siguiente ejemplo define tres estructuras para almacenar la informacion asociada a tres tipos diferen-tes de maquinas voladoras (jet, helicoptero y carguero). Finalmente define una estructuragenerica que puede contener, alternativamente, los datos de cualquiera de ellos (un aeroplano).Para controlar de que tipo es el objeto almacenado en la union datos, se utiliza la variable tipo.

struct jetf

int num pasajeros;. . .

g;struct helicopterof

int capacidad elevador;. . .

g;struct carguerof

int carga maxima;. . .

g;union aeroplanof

struct jet jet u;struct helicoptero helicopter u;struct carguero carguero u;

g;

Page 78: Introducción a la programación en c

8.3. Tipos de datos enumerados 68

struct un aeroplanof

/* 0:jet, 1:helicoptero, 2:carguero */int tipo;union aeroplano datos;

g;

8.3 Tipos de datos enumerados

Un objeto enumerado consiste en un conjunto ordenado de constantes enteras cuyos nombres indicantodos los posibles valores que se le pueden asignar a dicho objeto.

La definicion de una “plantilla” de un objeto enumerado requiere especificar un nombre medianteel cual pueda identificarse, ası como la lista de constantes de los posibles valores que puede tomar. Paraello se utiliza la palabra reservada enum de la forma siguiente:

enum nombre enumeracion f constante 1,constante 2,. . .constante N;g;

El siguiente ejemplo define una enumeracion denominada dia semana, cuyo significado es ob-vio.

enum dia semana f LUNES, MARTES, MIERCOLES, JUEVES, VIERNES,SABADO, DOMINGOg;

Las constantes que definen los posibles valores de la enumeracion son representadas internamentecomo constantes enteras. El compilador asigna a cada una de ellas un valor en funcion del orden(empezando por 0) en que aparecen en la definicion. Ası pues, en el ejemplo anterior tenemos queLUNES es 0, MARTES es 1, etc. Sin embargo, podrıa ser de interes modificar dicha asignacion pordefecto, asignando a las constantes otro valor numerico. Ver el siguiente ejemplo:

enum dia semana f LUNES=2, MARTES=3, MIERCOLES=4,JUEVES=5, VIERNES=6,SABADO=7, DOMINGO=1g;

Tambien puede asignarse el mismo valor numerico a diferentes constantes, ası como dejar que elcompilador numere algunas por defecto. Ver el siguiente ejemplo:

enum dia semana f LUNES=1, MARTES, MIERCOLES, JUEVES, VIERNES,SABADO, DOMINGO=10, FESTIVO=10g;

donde los valores asociados a las constantes son, respectivamente: 1, 2, 3, 4, 5, 6, 10 y 10.

Page 79: Introducción a la programación en c

69 8. Otros tipos de datos

La declaracion de variables puede hacerse de dos formas diferentes. Bien en la misma definicionde la enumeracion, bien posteriormente. Por su similitud con la declaracion de variables en el caso deestructuras y uniones, veremos simplemente un ejemplo del segundo caso. El mismo codigo muestraalgunos ejemplos de utilizacion de las constantes definidas en un tipo enumerado.

enum dia semana f LUNES=1, MARTES, MIERCOLES, JUEVES, VIERNES,SABADO, DOMINGO=10, FESTIVO=10

g;

void main()f

enum dia semana dia;

. . .if (dia <= VIERNES)

printf( "Es laborable" );. . .dia = MARTES;/* Muestra el valor de dia */printf( "Hoy es: %d", dia );. . .

g

8.4 Definicion de nuevos tipos de datos

C ofrece al programador la posibilidad de definir nuevos tipos de datos mediante la palabra reservadatypedef. Los nuevos tipos definidos pueden utilizarse de la misma forma que los tipos de datos pre-definidos por el lenguaje. Es importante destacar que typedef tiene especial utilidad en la definicionde nuevos tipos de datos estructurados, basados en tablas, estructuras, uniones o enumeraciones.

La sintaxis general para definir un nuevo tipo de datos es la siguiente:

typedef tipo de datos nombre nuevo tipo;

De esta forma se ha definido un nuevo tipo de datos de nombre nombre nuevo tipo cuyaestructura interna viene dada por tipo de datos.

A continuacion se muestran algunos ejemplos de definicion de nuevos tipos de datos, ası como suutilizacion en un programa:

typedef float Testatura;

typedef char Tstring [30];

typedef enum Tsexo = f HOMBRE, MUJER g;

Page 80: Introducción a la programación en c

8.5. Tiras de bits 70

typedef structf

Tstring nombre, apellido;Testatura alt;Tsexo sex;

g Tpersona;

void main()f

Tpersona paciente;

. . .scanf( "%f", &paciente.alt );gets( paciente.nombre );printf( "%s", paciente.apellido );paciente.sex = MUJER;

g

8.5 Tiras de bits

C es un lenguaje muy versatil que permite programar con un alto nivel de abstraccion. Sin embarco, Ctambien permite programar a muy bajo nivel. Esta caracterıstica es especialmente util, por ejemplo, paraprogramas que manipulan dispositivos hardware, como el programa que controla el funcionamiento deun modem, etc. Este tipo de programas manipulan tiras de bits.

En C, una tira de bits se debe almacenar como una variable entera con o sin signo. Es decir,como una variable de tipo char, short, int, long, unsigned, etc. Seguidamente veremos lasoperaciones que C ofrece para la manipulacion tiras de bits.

8.5.1 Operador de negacion

Este operador tambien recibe el nombre de operador de complemento a 1, y se representa mediante elsımbolo �. La funcion de este operador consiste en cambiar los 1 por 0 y viceversa. Por ejemplo, elsiguiente programa:

#include <stdio.h>void main()f

unsigned short a;

a= 0x5b3c; /* a = 0101 1011 0011 1100 */b= �a; /* b = 1010 0100 1100 0011 */printf( " a= %x b= %xnn", a, b );printf( " a= %u b= %unn", a, b );printf( " a= %d b= %dnn", a, b );

g

Page 81: Introducción a la programación en c

71 8. Otros tipos de datos

Tabla 8.1: Tabla de verdad de los operadores logicos

x y AND (&) OR ( j ) XOR (b)0 0 0 0 00 1 0 1 11 0 0 1 11 1 1 1 0

mostrarıa en pantalla los siguientes mensajes:

a= 0x5b3c b= 0xa4c3a= 23356 b= 42179a= 23356 b= -23357

como resultado de intercambiar por sus complementarios los bits de la variable a.

8.5.2 Operadores logicos

C permite realizar las operaciones logicas AND (&), OR ( j ) y XOR (b), tambien llamadas respectiva-mente “Y”, “O” y “O exclusivo”. La tabla 8.1 muestra la tabla de verdad de estas operaciones, dondex e y son bits.

Cuando aplicamos estos operadores a dos variables la operacion logica se realiza bit a bit. Veamosloen el siguiente ejemplo:

a= 0x5b3c; /* a = 1101 1011 0001 1101 */b= 0xa4c3; /* b = 1010 0101 1100 1011 */c= a & b; /* c = 1000 0001 0000 1001 */d= a j b; /* d = 1111 1111 1101 1111 */e= a b b; /* e = 0111 1110 1101 0110 */

8.5.3 Operadores de desplazamiento de bits

Existen dos operadores de desplazamiento que permiten desplazar a derecha o izquierda una tira debits. El operador de desplazamiento a la dercha se denota como >> y el operador de desplazamientoa la izquierda se denota como <<. Su uso es como sigue:

var1 << var2var1 >> var2

donde var1 es la tira de bits desplazada y var2 es el numero de bits desplazados. En el desplaza-miento a la izquierda se pierden los bits de mas a la izquierda de var1, y se introducen ceros por laderecha. De forma similar, en el desplazamiento a la derecha se pierden los bits de mas a la derecha devar1, y por la izquierda se introducen, o bien ceros (si la variable es de tipo unsigned, o bien serepite el ultimo bit (si la variable es de tipo signed).

El siguiente ejemplo muestra el resultado de aplicar los operadores de desplazamiento de bits:

Page 82: Introducción a la programación en c

8.6. Ejercicios 72

unsigned short a, d, e;short b, c, f, g;

a= 28; /* a = 0000 0000 0001 1100 */b= -28; /* b = 1111 1111 1110 0100 */c= 3; /* c = 0000 0000 0000 0011 */d= a << c; /* d = 0000 0000 1110 0000 = 224 */e= a >> c; /* e = 0000 0000 0000 0011 = 3 */f= b << c; /* f = 1111 1111 0010 0000 = -224 */g= b >> c; /* g = 1111 1111 1111 1100 = -3 */

Es importante senalar que los operadores de desplazamiento tienen un significado aritmetico enbase 10. El desplazamiento a la derecha de 1 bit es equivalente a dividir por 2, obteniendose el cocientede la division entera. El desplazamiento a la izquierda de 1 bit es equivalente a multiplicar por 2. Porlo 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 cual de las dos es anterior a la otra. Paraello, el programa debera definir una estructura para el tipo de datos fecha (dıa, mes y ano).

2. Definir una estructura de datos para representar polinomios de hasta grado 10. Escribir un pro-grama capaz de sumar dos polinomios expresados con dicha estructura.

3. Dada la siguiente definicion de tipos de datos:

typedef char Tstring [50];

typedef structf

Tstring nombre;Tstring area;long int ventas[4];

g Tagente;

Escribir un programa que defina un vector de N agentes, permita la introduccion de los datos porteclado y, finalmente, muestre los datos del agente con la maxima media de ventas.

4. Dada la siguiente definicion de tipo de datos:

typedef structf

Tstring pais;int temp max;int temp min;

g Ttemp;

Page 83: Introducción a la programación en c

73 8. Otros tipos de datos

escribir un programa que defina un vector con los datos de N paıses y permita introducirlospor teclado. Finalmente, el programa debera mostrar el nombre de los paıses cuya temperaturamınima sea inferior a la media de las temperaturas mınimas.

5. Dada la siguiente definicion de tipos de datos:

typedef char Tstring [50];

typedef structf

long int num;char letra;

g Tnif;

typedef structf

Tnif nif;Tstring nombre;Tstring direc;long int telf;

g Tempresa;

escribir un programa que defina un vector con los datos de N empresas y permita introducirlospor teclado. Dado el NIF de una empresa, el programa debera permitir mostrar los datos de lamisma, ası como eliminarla del vector, reorganizandolo para no dejar espacios vacıos.

Page 84: Introducción a la programación en c

75 9. Punteros

Capıtulo 9

Punteros

Un puntero es una variable que contiene como valor una direccion de memoria. Dicha direccion corres-ponde habitualmente a la direccion que ocupa otra variable en memoria. Se dice entonces que el punteroapunta a dicha variable. La variable apuntada puede ser de cualquier tipo elemental, estructurado oincluso otro puntero.

Los punteros constituyen una parte fundamental del lenguaje C y son basicos para comprender todala potencia y flexibilidad que ofrece el lenguaje. Son especialmente importantes en la programaciona bajo nivel, donde se manipula directamente la memoria del ordenador. Algunas de las ventajas queaporta el uso de punteros en C son:

� Constituyen la unica forma de expresar algunas operaciones.

� Su uso produce codigo compacto y eficiente.

� Son imprescindibles para el paso de parametros por referencia a funciones.

� Tienen una fuerte relacion con el manejo eficiente de tablas y estructuras.

� Permiten realizar operaciones de asignacion dinamica de memoria y manipular estructuras dedatos dinamicas.

Finalmente, cabe advertir al lector que los punteros son tradicionalmente la parte de C mas difıcil decomprender. Ademas deben usarse con gran precaucion, puesto que al permitir manipular directamentela memoria del ordenador, pueden provocar fallos en el programa. Estos fallos suelen ser bastantedifıciles de localizar y de solucionar.

9.1 Declaracion y asignacion de direcciones

En la declaracion de punteros y la posterior asignacion de direcciones de memoria a los mismos, seutilizan respectivamente los operadores unarios * y &. El operador & permite obtener la direccionque ocupa una variable en memoria. Por su parte, el operador de indireccion * permite obtener elcontenido de un objeto apuntado por un puntero.

Page 85: Introducción a la programación en c

9.1. Declaracion y asignacion de direcciones 76

9.1.1 Declaracion

En la declaracion de variables puntero se usa tambien el operador * , que se aplica directamente a lavariable a la cual precede. El formato para la declaracion de variables puntero es el siguiente:

tipo de datos * nombre variable puntero;

Notese que un puntero debe estar asociado a un tipo de datos determinado. Es decir, no puede asignarsela direccion de un short int a un puntero a long int. Por ejemplo:

int *ip;

declara una variable de nombre ip que es un puntero a un objeto de tipo int. Es decir, ip contendradirecciones 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 unentero).

El tipo de datos utilizado en la declaracion de un puntero debe ser del mismo tipo de datos quelas posibles variables a las que dicho puntero puede apuntar. Si el tipo de datos es void, se defineun puntero generico de forma que su tipo de datos implıcito sera el de la variable cuya direccion se leasigne. Por ejemplo, en el siguiente codigo, ip es un puntero generico que a lo largo del programaapunta a objetos de tipos distintos, primero a un entero y posteriormente a un caracter.

void *ip;int x;char car;. . .ip = &x; /* ip apunta a un entero */ip = &car; /* ip apunta a un caracter */

Al igual que cualquier variable, al declarar un puntero, su valor no esta definido, pues es el corres-pondiente al contenido aleatorio de la memoria en el momento de la declaracion. Para evitar el usoindebido de un puntero cuando aun no se le ha asignado una direccion, es conveniente inicializarlo conel valor nulo NULL, definido en el fichero de la librerıa estandar stdio.h. El siguiente ejemplomuestra dicha inicializacion:

int *ip = NULL;

De esta forma, si se intentase acceder al valor apuntado por el puntero ip antes de asignarle unadireccion valida, se producirıa un error de ejecucion que interrumpirıa el programa.

9.1.2 Asignacion de direcciones

El operador & permite obtener la direccion que ocupa una variable en memoria. Para que un punteroapunte a una variable es necesario asignar la direccion de dicha variable al puntero. El tipo de datos depuntero debe coincidir con el tipo de datos de la variable apuntada (excepto en el caso de un punterode tipo void). Para visualizar la direccion de memoria contenida en un puntero, puede usarse elmodificador de formato %p con la funcion printf:

Page 86: Introducción a la programación en c

77 9. Punteros

double num;double *pnum = NULL;. . .pnum = &num;printf( "La direccion contenida en ‘pnum’ es: %p", pnum );

9.2 Indireccion

Llamaremos indireccion a la forma de referenciar el valor de una variable a traves de un puntero queapunta a dicha variable. En general usaremos el termino indireccion para referirnos al hecho de re-ferenciar el valor contenido en la posicion de memoria apuntada por un puntero. La indireccion serealiza mediante el operador *, que precediendo al nombre de un puntero indica el valor de la variablecuya direccion esta contenida en dicho puntero. A continuacion se muestra un ejemplo del uso de laindireccion:

int x, y;int *p; /* Se usa * para declarar un puntero */. . .x = 20;p = &x;*p = 5498; /* Se usa * para indicar el valor de la

variable apuntada */y = *p; /* Se usa * para indicar el valor de la

variable apuntada */

Despues de ejecutar la sentencia *p = 5498; la variable x, apuntada por p , toma por valor 5498.Finalmente, despues de ejecutar la sentencia y = *p; la variable y toma por valor el de la variablex, apuntada por p.

Para concluir, existe tambien la indireccion multiple, en que un puntero contiene la direccion deotro puntero que a su vez apunta a una variable. El formato de la declaracion es el siguiente:

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 = &num;ppnum = &pnum;**ppnum = 24;printf( "%d", num );

Page 87: Introducción a la programación en c

9.3. Operaciones con punteros 78

La indireccion multiple puede extenderse a mas de dos niveles, aunque no es recomendable por ladificultad que supone en la legibilidad del codigo resultante.

La utilizacion de punteros debe hacerse con gran precaucion, puesto que permiten manipular direc-tamente la memoria. Este hecho puede provocar fallos inesperados en el programa, que normalmenteson difıciles de localizar. Un error frecuente consiste en no asignar una direccion valida a un punteroantes 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 direccion de memoriaaleatoria, con lo que es posible escribir involuntariamente en zonas de memoria que contengan codigoo datos del programa. Este tipo de error no provoca ningun error de compilacion, por lo que puede serdifıcil de detectar. Para corregirlo es necesario que x apunte a una posicion controlada de memoria,por ejemplo:

int y;int *x;. . .x = &y;*x = 100;

9.3 Operaciones con punteros

En C pueden manipularse punteros mediante diversas operaciones como asignacion, comparacion yoperaciones aritmeticas.

Asignacion de punteros

Es posible asignar un puntero a otro puntero, siempre y cuando ambos apunten a un mismo tipo dedatos. Despues de una asignacion de punteros, ambos apuntan a la misma variable, pues contienen lamisma direccion de memoria. En el siguiente ejemplo, el puntero p2 toma por valor la direccion dememoria contenida en p1. Ası pues, tanto y como z toman el mismo valor, que corresponde al valorde 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;

Page 88: Introducción a la programación en c

79 9. Punteros

Comparacion de punteros

Normalmente se utiliza la comparacion de punteros para conocer las posiciones relativas que ocupanen memoria las variables apuntadas por los punteros. Por ejemplo, dadas las siguientes declaraciones,

int *p1, *p2, precio, cantidad;*p1 = &precio;*p2 = &cantidad;

la comparacion p1 > p2 permite saber cual de las dos variables (precio o cantidad) ocupa unaposicion de memoria mayor. Es importante no confundir esta comparacion con la de los valores de lasvariables apuntadas, es decir, *p1 > *p2 .

Aritmetica de punteros

Si se suma o resta un numero entero a un puntero, lo que se produce implıcitamente es un incrementoo decremento de la direccion de memoria contenida por dicho puntero. El numero de posiciones dememoria incrementadas o decrementadas depende, no solo del numero sumado o restado, sino tambiendel tamano del tipo de datos apuntado por el puntero. Es decir, una sentencia como:

nombre puntero = nombre puntero + N;

se interpreta internamente como:

nombre puntero = direccion + N * tamano tipo de datos;

Por ejemplo, teniendo en cuenta que el tamano de un float es de 4 bytes, y que la variablenum se situa en la direccion de memoria 2088, ¿cual es el valor de pnum al final del siguiente codigo?

float num, *punt, *pnum;. . .punt = &num;pnum = punt + 3;

La respuesta es 2100. Es decir, 2088 + 3 � 4.

Es importante advertir que aunque C permite utilizar aritmetica de punteros, esto constituye unapractica no recomemdable. Las expresiones aritmeticas que manejan punteros son difıciles de entendery generan confusion, por ello son una fuente inagotable de errores en los programas. Como veremosen la siguiente seccion, no es necesario usar expresiones aritmeticas con punteros, pues C proporcionauna notacion alternativa mucho mas clara.

9.4 Punteros y tablas

En el lenguaje C existe una fuerte relacion entre los punteros y las estructuras de datos de tipo tabla(vectores, matrices, etc.).

En C, el nombre de una tabla se trata internamente como un puntero que contiene la direccion delprimer elemento de dicha tabla. De hecho, el nombre de la tabla es una constante de tipo puntero, por

Page 89: Introducción a la programación en c

9.4. Punteros y tablas 80

lo que el compilador no permitira que las instrucciones del programa modifiquen la direccion contenidaen dicho nombre. Ası pues, dada una declaracion como char tab[15] , el empleo de tab esequivalente al empleo de &tab[0]. Por otro lado, la operacion tab = tab + 1 generara un errorde compilacion, pues representa un intento de modificar la direccion del primer elemento de la tabla.

C permite el uso de punteros que contengan direcciones de los elementos de una tabla para accedera ellos. En el siguiente ejemplo, se usa el puntero ptab para asignar a car el tercer elemento detab, leer de teclado el quinto elemento de tab y escribir por pantalla el decimo elemento del vectortab.

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 relacion entre punteros y tablas en C va aun mas alla. Una vez declarado un puntero queapunta a los elementos de una tabla, pueden usarse los corchetes para indexar dichos elementos, comosi de una tabla se tratase. Ası, siguiendo con el ejemplo anterior, serıa 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 aritmeticas con punteros; en su lugarusaremos la sintaxis de acceso a una tabla. Es importante subrayar que este tipo de indexaciones soloson validas si el puntero utilizado apunta a los elementos de una tabla. Ademas, no existe ningun tipode verificacion al respecto, por lo que es responsabilidad del programador saber en todo momento siesta accediendo a una posicion de memoria dentro de una tabla o ha ido a parar fuera de ella y estasobreescribiendo otras posiciones de memoria.

El siguiente ejemplo muestra el uso de los punteros a tablas para determinar cual de entre dos vec-tores de enteros es mas fuerte. La fuerza de un vector se calcula como la suma de todos sus elementos.

#include <stdio.h>#define DIM 10void main()f

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] );

Page 90: Introducción a la programación en c

81 9. Punteros

for (i= 0; i< DIM; i++)scanf( "%d ", &v2[i] );

/* Calculo de la fuerza de los vectores. */fuerza1 = 0;fuerza2 = 0;for (i= 0; i< DIM; i++)f

fuerza1 = fuerza1 + v1[i];fuerza2 = fuerza2 + v2[i];

g

if (fuerza1 > fuerza2)fuerte = v1;

elsefuerte = v2;

/* Escritura del vector mas fuerte. */for (i= 0; i< DIM; i++)

printf( "%d ", fuerte[i] );g

En el caso de usar punteros para manipular tablas multidimensionales, es necesario usar formulasde transformacion para el acceso a los elementos. Por ejemplo, en el caso de una matriz de n filas ym columnas, el elemento que ocupa la fila i y la columna j se referencia por medio de un punterocomo puntero[i*m+j]. En el siguiente ejemplo se muestra el acceso a una matriz mediante unpuntero.

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 expresion pt[k] significa acceder al elementode tipo float que esta k elementos por debajo del elemento apuntado por pt . Dado que en C lasmatrices se almacenan por filas, para acceder al elemento de la fila i columna j deberemos contarcuantos elementos hay entre el primer elemento de la matriz y el elemento [i][j]. Como la numeracioncomienza en cero, antes de la fila i hay exactamente i filas, y cada una tiene m columnas. Por lotanto hasta el primer elemento de la fila i tenemos i�m elementos. Dentro de la fila i, por delantedel elemento j, hay j elementos. Por lo tanto tenemos que entre mat[0][0] y mat[i][j] hayi�m� j elementos. La figura 9.1 muestra como esta dispuesta en memoria la matriz de este ejemploy una explicacion grafica del calculo descrito.

Page 91: Introducción a la programación en c

9.4. Punteros y tablas 82

A[0][0]

. . .

A[0][m-1]

A[i][m-1]

A[i][0]

A[i][j]

A[n-1][0]

. . .

A[n-1][m-1]

. . .

. . .

. . .

. . .

Fila 0

Fila i

Fila n-1

i*m elementos

j elementos

i*m+j elementos

Puntero pt

Figura 9.1: Acceso a una matriz mediante un puntero

Punteros y cadenas de caracteres

Una cadena de caracteres puede declararse e inicializarse sin necesidad de especificar explıcitamentesu longitud. La siguiente declaracion es un ejemplo:

char mensaje[] = "Reiniciando sistema";

La cadena de caracteres mensaje ocupa en memoria 20 bytes (19 mas el caracter nulo ’n0’). Elnombre mensaje corresponde a la direccion del primer caracter de la cadena. En esta declaracion,mensaje es un puntero constante, por lo que no puede modificarse para que apunte a otro caracter dis-tinto del primer caracter de la cadena. Sin embargo, puede usarse mensaje para acceder a caracteresindividuales dentro de la cadena, mediante sentencias como mensaje[13]=’S’, etc.

La misma declaracion puede hacerse usando un puntero:

char *pmens = "Reiniciando sistema";

En esta declaracion, pmens es una variable puntero inicializada para apuntar a una cadena constante,que como tal no puede modificarse. Sin embargo, el puntero pmens puede volver a utilizarse paraapuntar a otra cadena.

Page 92: Introducción a la programación en c

83 9. Punteros

Puede decirse que en general a una tabla puede accederse tanto con ındices como con punteros(usando la aritmetica de punteros). Habitualmente es mas comodo el uso de ındices, sin embargo en elpaso de parametros a una funcion (ver Cap. 10) donde se recibe la direccion de una tabla, es necesarioutilizar punteros. Como veremos, dichos punteros podran usarse como tales o usando la indexaciontıpica de las tablas.

9.5 Punteros y estructuras

Los punteros a estructuras funcionan de forma similar a los punteros a variables de tipos elementales,con la salvedad de que en las estructuras se emplea un operador especıfico para el acceso a los campos.

Puede accederse a la direccion de comienzo de una variable estructura en memoria mediante elempleo del operador de direccion &. Ası pues, si var es una variable estructura, entonces &var re-presenta la direccion de comienzo de dicha variable en memoria. Del mismo modo, puede declararseuna variable puntero a una estructura como:

tipo estructura *pvar;

donde tipo estructura es el tipo de datos de las estructuras a las que pvar puede apuntar.Ası pues, puede asignarse la direccion de comienzo de una variable estructura al puntero de la formahabitual:

pvar = &var;

Veamos un ejemplo. A continuacion se define, entre otros, el tipo de datos Tnif como:

typedef char Tstring [50];

typedef structf

long int num;char letra;

g Tnif;

typedef structf

Tnif nif;Tstring nombre;Tstring direc;long int telf;

g Tempresa;

De forma que en el programa puede declararse una variable cliente de tipo Tnif y un punteropc a dicho tipo, ası como asignar a pc la direccion de inicio de la variable estructura cliente :

Tnif cliente;Tnif *pc;. . .pc = &cliente;

Page 93: Introducción a la programación en c

9.6. Ejercicios 84

Para acceder a un campo individual en una estructura apuntada por un puntero puede usarse eloperador de indireccion * junto con el operador punto (.) habitual de las estructuras. Vemos unejemplo:

(*pc).letra = ’Z’;scanf( "%ld", &(*pc).num );

Los parentesis son necesarios ya que el operador punto (.) tiene mas prioridad que el operador *.Notese que el operador de direccion & en la llamada a scanf se refiere al campo num y no alpuntero pc.

El hecho de que sea obligatorio usar parentesis en esta notacion hace que se generen errores debidoal olvido de los parentesis adecuados. Para evitar estos errores, C posee otra notacion para acceder alos campos de una estructura apuntada por un puntero.

El acceso a campos individuales puede hacerse tambien mediante el operador especial de los pun-teros a estructuras -> (guion seguido del sımbolo de mayor que). Ası pues, puede escribirse el mismoejemplo 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 combinandolos 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 mayusculas la inicial de todas las palabras en una cadena decaracteres. Usar un puntero a dicha cadena para acceder a los elementos de la misma.

2. Escribir un programa que calcule el maximo de un vector de numeros enteros, utilizando unpuntero para acceder a los elementos del vector.

3. Escribir un programa que, usando punteros, copie en orden inverso una cadena de caracteres enotra.

Page 94: Introducción a la programación en c

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 caracter ocupa 1 byte de memoria y la variable u se situa en la direccion FC8 (hexade-cimal), responder a las siguientes preguntas:

(a) ¿Que valor representa &v ?

(b) ¿Que valor se asigna a pv ?

(c) ¿Que valor representa *pv ?

(d) ¿Que valor se asigna a u ?

(e) ¿Que valor representa &u ?

(f) ¿Que valor se asigna a pu ?

(g) ¿Que valor representa *pu ?

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 situa en ladireccion 1230 (hexadecimal), responder a las siguientes preguntas:

(a) ¿Que valor representa &a ?

(b) ¿Que valor representa &c ?

(c) ¿Que valor representa &pb ?

(d) ¿Que valor se asigna a pa ?

(e) ¿Que valor representa *pa ?

(f) ¿Que valor representa &(*pa) ?

(g) ¿Que valor se asigna a pb ?

(h) ¿Que valor representa *pb ?

Page 95: Introducción a la programación en c

9.6. Ejercicios 86

(i) ¿Que valor se asigna a c ?

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 situa en ladireccion 1000 y la variable j en la direccion 1002, entonces:

(a) ¿Que valor representan &i y por &j ?

(b) ¿Que valor se asigna a pj , *pj y a i ?

(c) ¿Que valor representa pi ?

(d) ¿Que valor se asigna a *pi ?

(e) ¿Que valor representa pi + 2 ?

(f) ¿Que valor representa la expresion (*pi + 2) ?

(g) ¿Que valor representa la expresion *(pi + 2) ?

Page 96: Introducción a la programación en c

87 10. Funciones

Capıtulo 10

Funciones

Hasta ahora hemos visto como el programa principal (main( )) utiliza funciones de la librerıa estandarde C para realizar algunas tareas comunes (printf( ), scanf( ), . . . ). C permite tambien la de-finicion de funciones por parte del programador. Como veremos, al usar funciones definidas por elprogramador, los programas pueden estructurarse en partes mas pequenas y sencillas. Cada una de es-tas partes debe responder a un proposito unico e identificable, pudiendo ademas utilizarse en distintoslugares del programa. La distribucion del codigo de un programa usando funciones se conoce comomodularizacion.

El diseno modular de programas ofrece diversas ventajas. Por ejemplo, muchos programas requie-ren la ejecucion de un mismo grupo de instrucciones en distintas partes del programa. Este grupo deinstrucciones puede incluirse dentro de una sola funcion, a la que se puede llamar cuando sea necesario.Ademas, puede proporcionarse un conjunto de datos (parametros) diferente a la funcion cada vez quese la llama.

Es importante tambien la claridad en la logica del programa resultante de la descomposicion delmismo en partes bien definidas. Un programa concebido de esta forma es mucho mas facil de escribir yde depurar, no solo por el propio programador, sino tambien (y mas importante) por otros programado-res que posteriormente deban mantener el programa. Este hecho es especialmente cierto en programasgrandes donde participan muchos programadores.

La utilizacion de funciones permite tambien la construccion a medida de librerıas de funciones deuso frecuente. Por ejemplo, un programador especializado en el desarrollo de programas matematicospodrıa crear una librerıa con funciones para el manejo de matrices, numeros complejos, etc. De estaforma se evita la reescritura del mismo codigo repetidas veces para distintos programas.

10.1 Generalidades

Una funcion es una porcion de programa, identificable mediante un nombre, que realiza determinadastareas bien definidas por un grupo de sentencias sobre un conjunto de datos. Las operaciones que realizala funcion son siempre las mismas, pero los datos pueden variar cada vez que se llame a la funcion.

Todo programa en C consta de una o mas funciones, una (y solo una) de la cuales debe llamarsemain. La ejecucion del programa comienza siempre en dicha funcion, desde donde puede llamarsea otras funciones de la librerıa estandar o definidas por el programador. Al llamar a una funcion se

Page 97: Introducción a la programación en c

10.1. Generalidades 88

ejecutan las sentencias que la componen y, una vez completada la ejecucion de la funcion, la ejecuciondel programa continua desde el punto en que se hizo la llamada a la funcion.

Generalmente, al llamar a una funcion se le proporciona un conjunto de datos (parametros) quese procesan ejecutando las sentencias que la componen. La funcion devuelve un solo valor mediantela sentencia return. Algunas funciones reciben parametros, pero no devuelven nada (como la fun-cion printf), mientras que otras no reciben parametros pero sı devuelven un valor (como la funcionrand).

El programa del siguiente ejemplo calcula el numero combinatorio

m

n

!= m!

n! (m�n)!, donde

es necesario calcular el factorial de tres numero diferentes. Una posible realizacion de este programa,si no se tuviese en cuenta el uso de funciones, serıa la siguiente:

#include <stdio.h>void main()f

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 );

g

Como puede verse, el codigo para el calculo del factorial se halla triplicado. Una solucion mas clara yelegante puede obtenerse usando funciones:

#include <stdio.h>long int fact ( int x )f

int i;long int f = 1;

for (i= 2; i<= x; i++)f = f * i;

return(f);g

void main()

Page 98: Introducción a la programación en c

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 );

g

En el ejemplo se ha definido la funcion fact, que recibe como parametro un valor de tipo int, al quese ha llamado x, y devuelve un resultado de tipo long int. El codigo en el interior de la funcioncalcula el factorial de x acumulandolo sobre la variable local f. Finalmente la funcion devuelve elresultado del calculo mediante la sentencia return. Observese que la definicion de una funcion seasemeja a la del programa principal. De hecho, el programa principal main es una funcion.

Seguidamente veremos mas formalmente como definir funciones, como llamarlas, las distintas va-riantes del paso de parametros, etc.

10.2 Definicion y llamada

10.2.1 Definicion

La definicion de una funcion se hace de forma similar a la de la funcion main. Su forma mas genericaconsta basicamente de dos partes: un lınea llamada cabecera donde se especifica el nombre de lafuncion, el tipo del resultado que devuelve y los parametros que recibe; y un conjunto de sentenciasencerrado entre llaves formando el cuerpo.

tipo nombre funcion(tipo1 param1, ..., tipoN paramN)f

cuerpog

� tipo: es el tipo de datos del valor que devuelve la funcion. Si no se especifica ninguno, C asumeque la funcion devuelve un valor de tipo entero.

� nombre funcion: identificador que se usara posteriormente para llamar a la funcion.

� tipoi parami: tipo y nombre de cada uno de los parametros que recibe la funcion. Se espe-cifican entre parentesis y separados por comas. Algunas funciones pueden no tener parametros.Los parametros de la declaracion se denominan parametros formales, ya que representan losnombres con que referirse dentro de la funcion a los datos que se transfieren a esta desde la partedel programa que hace la llamada.

� cuerpo: conjunto de declaracion de variables y sentencias de ejecucion (incluyendo llamadas afunciones) necesarias para la realizacion de la tarea especificada por la funcion. Debe incluir unao mas sentencias return para devolver un valor al punto de llamada.

Page 99: Introducción a la programación en c

10.2. Definicion y llamada 90

10.2.2 Prototipos

Si en el punto del programa donde se va a realizar una llamada a una funcion, dicha funcion ya ha sidodefinida previamente, entonces ya se conocen sus caracterısticas (tipo del resultado, numero y tipo delos parametros, etc.), por lo que la llamada puede realizarse sin problemas. Sin embargo, si la funcionque se va a llamar se halla definida posteriormente al punto desde donde se realiza la llamada, entoncesdebe crearse un prototipo de la funcion a la cual se desea llamar. Dicho prototipo debera colocarseantes del punto donde se haga la primera llamada a la funcion, y consta unicamente de la cabecera dedicha funcion.

El prototipo de una funcion puede interpretarse como un aviso al compilador, para que cuandoencuentre una llamada a dicha funcion pueda conocer el tipo del resultado que devuelve y la informacionsobre los parametros que recibe.

A continuacion se muestra el formato general de un prototipo, que corresponde a la cabecera de lafuncion seguida de un punto y coma:

tipo nombre funcion(tipo1 param1, ..., tipoN paramN);

De acuerdo con esto, el programa del ejemplo anterior podrıa haberse escrito de otro modo, defi-niendo la funcion fact con posterioridad a main y usando un prototipo:

#include <stdio.h>long int fact ( int x ); /* Prototipo */

void main()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 );

g

long int fact ( int x )f

int i;long int f = 1;

for (i= 2; i<= x; i++)f = f * i;

return(f);g

La utilizacion de prototipos de funciones no es obligatorio en C. Sin embargo, es aconsejable, yaque facilitan la comprobacion y deteccion de errores entre las llamadas a funciones y las definicionescorrespondientes.

Page 100: Introducción a la programación en c

91 10. Funciones

10.2.3 Llamada

Finalmente, la llamada a una funcion se realiza con el nombre de la misma y una lista de parametros(si es que los requiere) entre parentesis. El numero y tipo de los parametros empleados en la llamada ala funcion debe coincidir con el numero y tipo de los parametros formales de la definicion o prototipo.Adicionalmente, si la funcion devuelve algun valor (es decir, no es de tipo void) la llamada a lafuncion debe estar incluida en una expresion que recoja el valor devuelto. Siguiendo con el ejemplo delfactorial:

fm = fact(m);prod = fact(n)*fact(m-n);

Los datos empleados en la llamada a una funcion reciben el nombre de parametros reales, ya quese refieren a la informacion que se transfiere a la funcion para que esta se ejecute. Como veremos masadelante, los identificadores de los parametros formales son locales a la funcion, en el sentido de queno son reconocidos desde fuera de esta. Por tanto, los nombres de los parametros formales no tienenpor que coincidir con los nombres de la variables usadas como parametros reales en el momento de lallamada.

10.3 Variables y parametros

Las variables de un programa pueden clasificarse en funcion del ambito en el cual son conocidas y portanto accesibles. El ambito de las variables, ası como su tiempo de vida, depende del lugar donde sehallen declaradas dentro del programa. Ası pues, se distinguen los siguientes tipos: variables locales,variables globales y parametros formales.

10.3.1 Variables locales

Una variable local se halla declarada al comienzo del cuerpo de una funcion (esto incluye a la funcionmain). Su ambito se circunscribe al bloque de sentencias que componen el cuerpo de la funcion, porlo que solo son conocidas dentro de el. Por otra parte, su tiempo de vida va desde que se entra en lafuncion hasta que se sale de ella, por lo que las variables locales se crean al comenzar la ejecucion dela funcion y se destruyen al concluir dicha ejecucion.

En el ejemplo del calculo de

m

n

!, las variables i y f son locales a la funcion fact, por

lo que no son accesibles fuera de ella. De forma similar, las variables m, n y res son locales a lafuncion main.

10.3.2 Variables globales

Una variable global se halla declarada fuera de toda funcion del programa al principio del ficheroprincipal. Su ambito se extiende a lo largo de todas las funciones del programa. Su tiempo de vida estalimitado por el tiempo que dura la ejecucion del programa, por lo que las variables globales se crean alcomenzar a ejecutar el programa y se destruyen cuando este concluye la ejecucion.

Si todas las funciones del programa estan incluidas en un mismo fichero, basta con escribir unasola vez al principio del fichero la declaracion de las variables globales. Sin embargo, si tenemos las

Page 101: Introducción a la programación en c

10.3. Variables y parametros 92

funciones repertidas en diferentes ficheros, deberemos incluir en uno de los ficheros la declaracion yrepetir en los demas ficheros la declaracion precedida de la palabra extern. Tambien es posibledefinir variables globales unicamente dentro de un fichero. Para ello antepondremos en la declaracionla palabra static.

El siguiente ejemplo muestra las declaraciones de tres variables globales. La variable A es accesibleen todo el programa y esta declarada en este fichero. La variable B es accesible en todo el programa,pero esta declarada en otro fichero, es decir, hay otras funciones ademas de las de este fichero quepueden acceder a ella. Finalmente, la variable C solo es accesible por las funciones de este fichero.

int A;extern int B;static int C;void main ()f

. . .g

int func1()f

. . .g

int func2()f

. . .g

El uso de variables globales debe hacerse con precaucion, puesto que al poderse modificar desdecualquier funcion del programa donde sean accesibles, pueden producirse efectos laterales difıciles dedetectar y corregir. Es una buena practica de programacion no emplear variables globales salvo encasos muy excepcionales. En general, el uso de una variable global puede substituirse por una varia-ble local al programa principal y el adecuado paso de parametros a todas las funciones que requieranacceder a dicha variable.

10.3.3 Parametros formales

El ambito y el tiempo de vida de un parametro formal en una funcion son los mismos que los de unavariable local a dicha funcion. Es decir, que el ambito es toda la funcion y que la variable se crea alentrar en la funcion y se destruye al salir de la misma.

Como hemos comentado, el ambito de las variables locales y los parametros de una funcion secircunscribe unicamente al interior de la misma. Es decir, que ni las variables ni los parametros formalesde una funcion son accesibles desde fuera de ella. Incluso en el caso de que el programa use el mismonombre para variables de distintas funciones, el compilador es capaz de diferenciarlas. Para demostraresto usaremos el operador de direccion & en un ejemplo muy sencillo:

Page 102: Introducción a la programación en c

93 10. Funciones

void func( int par )f

int loc = 10;printf( "En func(), loc=%d y &loc=%pnn", loc, &loc );printf( "En func(), par=%d y &par=%pnn", par, &par );

g

void main()f

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);

g

Al compilar y ejecutar este ejemplo, observaremos que las variables loc y par se situan en direc-ciones de memoria diferentes (y por tanto, son variables diferentes) si estamos dentro de la funcion oen el programa principal.

10.4 Devolucion de resultados

Cuando una funcion termina de realizar la tarea para la que fue disenada, devuelve el control de laejecucion a la parte del programa desde donde se hizo la llamada. Para concluir la ejecucion de unafuncion se utiliza la sentencia return, que fuerza la salida de la funcion en el punto donde se haespecificado dicha sentencia. Si no existe ninguna sentencia return, la funcion termina con la llaveque cierra el cuerpo.

El siguiente ejemplo muestra dos maneras de escribir la misma funcion para el calculo del maximode dos numeros enteros:

void maximo( int x, int y )f

int max;

if (x > y)max = x;

elsemax = y;

printf( "MAX=%d", max );g

void maximo( int x, int y )f

if (x > y)f

printf( "MAX=%d", x );return;

g

printf( "MAX=%d", y );g

Si la funcion no es de tipo void, la sentencia return, ademas de especificar la terminacionde la funcion, puede especificar la devolucion de un valor para que pueda ser utilizado en el puntodonde se hizo la llamada. El valor devuelto, y por ende el tipo de la funcion, puede ser cualquiera delos tipos elementales, estructurados o definidos por el programador, excepto tablas. Aunque en C eslegal que una funcion retorne cualquier tipo de estructura, por cuestiones de eficiencia en la ejecuciondel programa no es conveniente retornar estructuras muy grandes en tamano. Por ejemplo, estructurasen cuyo interior haya tablas. Cuando se desea que una funcion devuelva tipos de datos complejos, seutiliza el paso de parametros por referencia que veremos en la seccion 10.5.

Page 103: Introducción a la programación en c

10.5. Paso de parametros 94

En el siguiente programa se ha rescrito la funcion maximo para que devuelva el maximo de dosenteros, en lugar de mostrarlo por pantalla.

int maximo( int x, int y )f

if (x > y)return(x);

elsereturn(y);

g

En la parte del programa que hace la llamada puede usarse el valor devuelto por la funcion dentro decualquier expresion valida (en particular una sentencia de escritura):

printf( "MAX(%d,%d)=%d", a, b, maximo(a,b) );

10.5 Paso de parametros

Como ya hemos comentado, los parametros de una funcion no son mas que variables que actuan deenlace entre la parte del programa donde se realiza la llamada y el cuerpo de la funcion. Ası pues,los parametros formales de una funcion son variables locales a esta. Como tales, se crean y recibensus valores al entrar en la funcion, y se destruyen al salir de la misma. El paso de parametros puederealizarse de dos formas: por valor o por referencia.

10.5.1 Paso de parametros por valor

Al entrar a la funcion, los parametros formales reciben una copia del valor de los parametros reales. Portanto las modificaciones sobre los parametros formales son locales y no afectan a los parametros realesde la parte del programa que hace la llamada a la funcion. Veamos un ejemplo:

#include <stdio.h>void cuadrado ( int x )f

x = x * x;printf( "Dentro x = %dnn", x );

g

void main()f

int x = 5;

printf( "Antes x = %dnn", x );cuadrado( x );printf( "Despues x = %dnn", x );

g

El resultado de ejecutarlo sera:

Page 104: Introducción a la programación en c

95 10. Funciones

Antes x = 5Dentro x = 25Despues x = 5

Como puede verse, la modificacion sobre el parametro formal no afecta a la variable del programaprincipal.

En el paso de parametros por valor, la transferencia de informacion es solo en un sentido, es decir,desde la parte del programa donde se hace la llamada hacia el interior de la funcion, pero no al reves.Gracias a ello, es posible utilizar expresiones como parametros reales, en lugar de necesariamentevariables. Esto es ası puesto que el valor asignado al parametro formal es el resultado de la evaluacionde dicha expresion. Es mas, si el parametro real es una variable, su valor es protegido de posiblesmodificaciones por parte de la funcion.

10.5.2 Paso de parametros por referencia

Hasta este punto hemos visto como proporcionar datos a una funcion (paso de parametros por valor)o como hacer que la funcion devuelva resultados con la sentencia return. Sin embargo, ¿comopodrıamos hacer que una funcion devolviese mas de un valor? O bien, ¿como podrıamos conseguir quelas modificaciones sobre los parametros formales afectasen tambien a los parametros reales? Es decir,¿como podrıamos modificar variables del ambito en el que se realizo la llamada desde el interior de unafuncion?

La respuesta a estas cuestiones se halla en la utilizacion del paso de parametros por referencia. Estetipo de paso de parametros se conoce tambien en C como paso por direccion o paso por puntero.

En este caso los parametros reales son una referencia (puntero) a las variables de la parte del pro-grama que realiza la llamada y no las variables en sı. La referencia se copia en los parametros formales,de forma que dentro de la funcion puede usarse dicha referencia para modificar una variable que, deotra forma, no serıa accesible desde el interior de la funcion (modificar el valor referenciado).

El paso de parametros por referencia implica el uso de los operadores de direccion ( & ) y puntero( * ) de C:

� & , que antepuesto a una variable permite obtener la direccion de memoria en que se halla ubicada.Se usara en los parametros reales de la llamada a una funcion para pasarle por referencia dichavariable. En otras palabras, el parametro que se pasa a la funcion es un puntero a una variable.Ya hemos utilizado esto anteriormente en las llamadas a la funcion scanf, donde las variablesen que se almacenan los valores leıdos del teclado se pasan por referencia.

� * , que se utiliza tanto en la declaracion de los parametros formales de la funcion como en elcuerpo de la misma. Aparecera precediendo al nombre de un parametro formal en la cabecerapara indicar que dicho parametro sera pasado por referencia (sera un puntero). Aparecera en elcuerpo de la funcion, antepuesto al nombre de un parametro formal, para acceder al valor de lavariable externa a la funcion y referenciada por el parametro formal.

En el siguiente ejemplo se muestra la funcion swap que intercambia el valor de sus dos parametros.Para ello los parametros formales x e y se han declarado de paso por referencia usando * en lacabecera. De forma complementaria, en la llamada a la funcion se ha usado & para pasar a la funcionuna referencia a las variables a y b del programa principal. Dentro de la funcion usamos nuevamente

Page 105: Introducción a la programación en c

10.5. Paso de parametros 96

* para referenciar el valor del parametro real. Por ejemplo, la sentencia aux = *x; asigna a lavariable local aux el valor de la variable a del programa principal, puesto que el parametro formalx contiene una referencia a la variable a.

#include <stdio.h>void swap(int *x, int *y)f

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

void main()f

int a, b;

scanf( "%d %d", &a, &b );swap( &a, &b );printf( "Los nuevos valores son a=%d y b=%dnn", a, b );

g

Como puede verse, el paso de parametros por referencia tiene una estrecha relacion con el uso depunteros y direcciones de memoria que vimos en el capıtulo 9. De hecho un purista del lenguaje dirıaque en C no existe el paso por referencia y que todo paso de parametros se hace por valor. El paso porreferencia se “simula” mediante el paso (por valor) de punteros a las variables externas a la funcion yque se desean modificar desde el interior de la misma.

10.5.3 Las tablas y las funciones

Los elementos individuales de una tabla (vector, matriz, etc.) se pasan a las funciones como si devariables individuales se tratase, tanto por valor como por referencia.

El siguiente ejemplo busca el elemento mayor en un vector de numeros enteros. Para ello utiliza lafuncion maximo que vimos anteriormente. Observese el paso por referencia del elemento i-esimo deun vector, a la funcion scanf y, por valor, a la funcion maximo.

#include <stdio.h>void main()f

int max, v[20], i;

printf( "Introducir elementos del vector:nn" );for (i= 0; i< 20; i++)

Page 106: Introducción a la programación en c

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 );

g

Algo muy diferente es el paso de una tabla a una funcion. La unica manera que C ofrece para elloes el paso por referencia de toda la tabla en cuestion. Sin embargo, al contrario que en el paso porreferencia habitual, no se usan los sımbolos & y * . En su lugar se utiliza directamente el nombre dela tabla, que constituye de por sı una referencia al primer elemento de la tabla, tal como vimos en elcapıtulo 9. Por lo tanto, el parametro formal de la funcion debe ser un puntero para poder recoger ladireccion de inicio de la tabla.

El siguiente ejemplo define una funcion para el calculo de la media de los elementos de un vectorde numeros de coma flotante:

#include <stdio.h>#define DIM 20float media( float vec[], int n )f

int j;float sum;

sum = 0.0;for (j= 0; j< n; j++)

sum = sum + vec[j];return (sum/(float)n);

g

void main()f

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 );

g

La cabecera de la funcion media hubiese podido escribirse tambien como:

float media( float *vec, int n )

Es importante notar que la definicion del parametro vec en la funcion media es una declaracionde un puntero. Es decir, no existe diferencia alguna entre float *vec y float vec[]. El mo-tivo de usar esta nueva notacion es dar mayor claridad al programa. Por lo tanto, cuando un parametro

Page 107: Introducción a la programación en c

10.5. Paso de parametros 98

sea un puntero a una tabla usaremos la notacion tipo nombrePuntero[], mientras que cuan-do tengamos un puntero a cualquier otro tipo de datos (tanto tipos elementales como estructurados)usaremos la notacion tipo *nombrePuntero.

Finalmente, la cabecera de la funcion media tambien pudiera haberse escrito como:

float media( float vec[DIM], int n )

En este caso de nuevo el parametro vec es un puntero a una tabla, pero ahora ademas indicamos eltamano de la tabla, lo que clarifica aun mas el programa. Notar que esto solo es posible hacerlo si lasdimensiones de la tabla son constantes. Es decir, una expresion como

float media( float vec[n], int n )

serıa incorrecta.

Vemos ahora un ejemplo con una matriz. Deseamos hacer una funcion que multiplique una matrizpor un vector. La solucion propuesta es la siguiente:

#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

y[i] = 0.0;for (i= 0; i< ncol; i++)

y[i] = y[i] + A[i*MAXCOL+j] * x[j];g

g

void main()f

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 */

g

Notese que los parametros formales A, x e y son todos punteros. Se accede a los elementos dela matriz a traves de un puntero que senala al primer elemento ([0][0]). En la seccion 9.4 vimosla forma de realizar un acceso de este tipo. Notese tambien que en la formula que da el numero deelementos entre el primero y el elemento [i][j], se debe usar MAXCOL y no ncol. Es decir,

Page 108: Introducción a la programación en c

99 10. Funciones

M[0][0]

M[0][2]

M[0][1]

M[1][0]

M[1][2]

M[1][1]

M[2][0]

M[2][2]

M[2][1]

MAXCOL = 3

ncol=2

A

i*MAXCOL+j = 2*3+1=7

A[0]

A[3]

A[4]

A[5]

A[6]

A[1]

A[2]

A[7]

A[8]

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 figura 10.1 muestra este ejemplo graficamentesuponiendo que ncol vale 2, MAXCOL vale 3, y que queremos acceder el elemento [2][1] de lamatriz.

10.5.4 Parametros en la funcion main

Hasta el momento hemos empleado la funcion main sin parametros. Sin embargo, es posible pasarparametros a la funcion principal del programa, desde la lınea de ordenes del sistema operativo. Losparametros de la funcion main son dos; se conocen tradicionalmente como argc y argv, aunquepueden tomar cualquier nombre.

El parametro argc es un valor entero que contiene el numero de parametros dados al programaal ser ejecutado desde la lınea de ordenes del sistema operativo. El nombre del programa se consideracomo el primer parametro, por lo que el valor mınimo de argc es 1.

El parametro argv es un vector de punteros a cadenas de caracteres. Estas cadenas toman el valorde cada uno de los parametros dados al programa al ejecutarlo. Cada parametro de la lınea de ordenesdebe estar separado por un espacio o un tabulador.

Page 109: Introducción a la programación en c

10.6. Recursividad 100

Ası pues, el formato de la funcion main con parametros es el siguiente:

void main( int argc, char *argv[] )f

/* Cuerpo de la funcion. */g

El siguiente programa de ejemplo permite introducir el nombre del usuario del programa al ejecu-tarlo y mostrar un mensaje que lo incluya.

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

if (argc > 2)f

printf( "Demasiados parametrosnn" );g

else if (argc < 2)f

printf( "Faltan parametrosnn" );g

elsef

printf( "Yo te saludo %snn", argv[1] );g

g

Ası pues, si hemos llamado al programa ejecutable saludo y se escribe en la lınea de ordenesdel sistema operativo saludo Pepe, la salida del programa sera:

Yo te saludo Pepe

Si alguno de los parametros que se pasan al programa contiene espacios en blanco o tabuladores,deben usarse comillas en la ejecucion del programa.

Lınea de ordenes del sistema operativo: saludo "Pepe Perez"Salida del programa: Yo te saludo Pepe Perez

10.6 Recursividad

Se llama recursividad a un proceso en el que una funcion se llama a sı misma repetidamente hastaque se satisface una cierta condicion. Este proceso se emplea para calculos repetitivos en los que elresultado de cada iteracion se determina a partir del resultado de alguna iteracion anterior.

Frecuentemente un mismo problema puede expresarse tanto de forma recursiva como de forma ite-rativa. En estos casos, debido a que la ejecucion recursiva requiere de numerosas llamadas a funciones,es preferible utilizar una solucion no recursiva. Sin embargo, en otros casos, la escritura de una solu-cion no recursiva puede resultar extraordinariamente compleja. Es entonces cuando es apropiada unasolucion recursiva.

Page 110: Introducción a la programación en c

101 10. Funciones

A continuacion se presentan dos funciones recursivas para el calculo del factorial y el calculo devalores de la serie de Fibonacci:

int fact( int x )f

if (x <= 1)return (1);

return (x * fact(x-1));g

int fibo( int n )f

if ((n==0) || (n==1))return (1);

return (fibo(n-1)+fibo(n-2));g

Otro ejemplo de solucion recursiva es este programa para invertir los elementos de un vector usandola funcion swap:

#define N 10void invert( int v[], int i, int j )f

swap( &v[i], &v[j] );i++;j--;if (i < j)

invert( v, i, j );g

void main()f

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] );g

10.7 Ejercicios

1. El calculo de ex puede aproximarse mediante el calculo de

nXi=0

xi

i!para n suficientemente

grande. Escribir una funcion pot que permita calcular la potencia i-esima de un numero x,donde i y x son parametros enteros de la funcion. Usando esta funcion y la funcion fact del

Page 111: Introducción a la programación en c

10.7. Ejercicios 102

principio del capıtulo, escribir un programa que calcule ex de forma aproximada para un valor

de n dado.

2. Escribir un programa para calcular sen(x) =1Xi=0

x2i+1

(2i+ 1)!(�1)i. Utilizar las funciones pot y

frac del problema anterior.

3. Escribir un programa para calcular cos(x) =

1Xi=0

x2i

(2i)!(�1)i. Utilizar las funciones pot y

frac.

4. Se dispone de las dos funciones siguientes:

void f1( int x, int *y, int a, int b )f

x = x + 1;*y = *y + 1;x = x + a;*y = *y + b;printf( "%d %dnn", x, *y );

g

void f2( int a, int *b )f

a = a + 1;*b = *b + 1;a = a + a;*b = *b + *b;printf( "%d %dnn", a, *b );

g

Y del programa principal:

#include <stdio.h>void main()f

int a = 0, b = 0;

llamadaprintf( "%d %dnn", a, b );

g

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 continuacion se muestra el esqueleto de un programa en C:

Page 112: Introducción a la programación en c

103 10. Funciones

int f1( char a, char b )f

a = ’P’;b = ’Q’;return ((a<b)?(int)a:(int)b);

g

int f2( char *c1, char *c2 )f

*c1 = ’R’;*c2 = ’S’;return ((*c1==*c2)?(int)*c1:(int)*c2);

g

void main()f

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 );

g

(a) ¿Que valores se asignan a i y j en main?

(b) ¿Que valores escribe la primera llamada a printf?

(c) ¿Que valores escribe la segunda llamada a printf?

6. ¿Que valor calcula el siguiente programa?

void func( int p[] )f

int i, sum = 0;

for(i= 3; i< 7; ++i)sum = sum + p[i];

printf( "suma = %d", sum );g

void main()f

int v[10] = f1,2,3,4,5,6,7,8,9,10g;func( &v[2] );

g

Page 113: Introducción a la programación en c

10.7. Ejercicios 104

7. Determinar si un numero no negativo es perfecto o tiene algun amigo. Dos numeros son amigoscuando la suma de los divisores de uno de ellos es igual al otro numero. Por ejemplo: 220 y 284

son amigos. Por otra parte, un numero es perfecto cuando la suma de sus divisores es el mismo.Por ejemplo 6 = 3 + 2 + 1 es perfecto.

8. Dado el siguiente tipo de datos:

#include <stdio.h>typedef structf

char a[10];char b[10];char c[10];

g Tcolores;

describir la salida generada por cada uno de los siguientes programas:

(a) void func( Tcolores X )f

X.a = "cian";X.b = "magenta";X.c = "amarillo";printf( "%s%s%snn", X.a, X.b ,X.c );return();

g

void main()f

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) void func(Tcolores *X)f

X->a = "cian";X->b = "magenta";X->c = "amarillo";printf( "%s%s%snn", X->a, X->b, X->c );return();

g

void main()f

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

Page 114: Introducción a la programación en c

105 11. Ficheros

Capıtulo 11

Ficheros

Es preciso algun mecanismo que permita almacenar de forma permamente ciertos datos de los pro-gramas. Por ejemplo, un programa que gestione la contabilidad de una empresa necesita una serie deinformaciones iniciales (balance hasta ese momento, lista de compras, lista de ventas, etc ). De igualforma, genera una serie de informaciones que deben ser almacenadas cuando el programa finaliza.

Desde el punto de vista del hardware, hay diferentes dispositivos para almacenar informacion deforma permanente: discos duros, unidades de cinta, CDs, disquetes, etc. Para un programador, el dispo-sitivo fısico que se use carece de importancia. Los programas deben funcionar tanto si la informacionesta en un disco duro como en un CD como en una cinta. Por lo tanto, es preciso un conjunto de funcio-nes (una librerıa) que permita realizar almacenamiento permamente de informacion, pero omitiendo losdetalles especıficos de cada dispositivo hardware. Esta librerıa de funciones la proporciona el sistemaoperativo.

Para ocultarle al programador los detalles especıficos de cada dispositivo hardware, se usa el con-cepto de fichero. Un fichero es un objeto abstracto sobre el cual se puede leer y escribir informacion.

Existen dos tipos fundamentales de ficheros: ficheros de texto y ficheros binarios. En los ficherosde texto la informacion se almacena usando caracteres (codigos ASCII). Por ejemplo, una variable detipo int se almacena en la memoria como una secuencia de bits que debe ser interpretada comoun codigo complemento a dos. Sin embargo, cuando escribimos dicha variable en un fichero de texto,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

31z }| {0 � � � 0 en un fichero de texto se

escribirıa como �2147483648. En los ficheros binarios la informacion se almacena de igual forma queen la memoria, mediante la misma secuencia de unos y ceros.

Usar ficheros de texto tiene la ventaja de que la informacion almacenada puede ser visualizada ycomprendida por un ser humano. Pero tiene el inconveniente de ocupar aproximadamente tres vecesmas espacio que los ficheros binarios. Por ejemplo, la variable de tipo int mostrada anteriormente,ocupa 4 bytes en un fichero binario (lo mismo que ocupa en memoria), y ocupa 11 bytes (signo y 10cifras) en un fichero de texto.

Existen diferentes funciones para trabajar con ficheros de texto y con ficheros binarios. En estelibro nos ocuparemos unicamente de los ficheros de texto.

Un fichero de texto almacena la informacion como una secuencia de codigos ASCII. La figura 11.1muestra un ejemplo de almacenamiento de un fichero de texto. En esta figura se muestra el aspecto que

Page 115: Introducción a la programación en c

106

69115

115

11611132101

10...

130116111EOF

Código del carácter ECódigo del carácter sCódigo del carácter tCódigo del carácter oCódigo del carácter Espacio en blancoCódigo del carácter eCódigo del carácter sCódigo del carácter \n

Código del carácter tCódigo del carácter oCódigo del carácter .

Esto esun ejemplode cómose almacenaun ficherode texto.

Ventanade acceso

Figura 11.1: Almacenamiento de un fichero de texto

tendrıa un fichero de texto al mostrarse en pantalla, ası como la secuencia de bytes que se almacenarıanen disco. Todos los ficheros de texto finalizan com un caracter especial que indica el final del fichero.Este caracter lo representamos mediante la constante EOF (End Of File) que se halla definida en elfichero stdio.h.

Cuando queramos leer o escribir informacion en un fichero de texto deberemos hacerlo de formasecuencial. Por ejemplo, si queremos leer el fichero de la figura 11.1, deberemos leer en primer lugar elprimer caracter y ası sucesivamente. Esto se debe a que existe una ventana asociada al fichero que solopuede avanzar secuencialmente, nunca a saltos.

El sistema operativo usa variables del tipo FILE para manejar los dispositivos hardware asociadosa ficheros. La definicion del tipo FILE se encuentra en el fichero stdio.h. Todos los programasque manejen ficheros deberan incluir stdio.h. Una variable del tipo FILE es una estructura cuyocontenido solo puede ser entendido y manejado por funciones del sistema operativo. Dicha estructuracontiene informacion, por ejemplo, de la pista y el sector del disco donde comienza el fichero, etc.Dado que las variables de tipo FILE pertenecen al sistema operativo, nunca tendremos una variablede este tipo en nuestros programas. Lo unico que necesitamos son punteros a dichas variables. Estoes, si queremos manejar un fichero dentro de un programa, deberemos tener una declaracion como lasiguiente:

FILE *fp;

El puntero fp debe ser inicializado mediante la funcion fopen, de forma que apunte a unavariable del sistema operativo que contenga los datos del fichero concreto que usemos. Las funcionesde lectura y escritura en el fichero unicamente necesitan conocer dicho puntero para saber la variableque deben usar.

Para utilizar un fichero se debe realizar una secuencia fija de acciones:

Page 116: Introducción a la programación en c

107 11. Ficheros

1. Abrir el fichero. Esto significa decirle al sistema operativo que inicialice una variable de tipoFILE, de forma que a partir de ese momento, las acciones de lectura/escritura que utilicen dichavariable se realicen realmente en el dispositivo hardware correspondiente. Esto se hace llamandoa una funcion fopen.

2. Leer o Escribir en el fichero. Para ello usaremos las funciones fscanf y fprintf. Estasfunciones solo necesitan dos datos: la variable de tipo FILE asociada al fichero y la informacionque queremos leer o escribir en dicho fichero.

3. Cerrar el fichero. Esto significa indicar al sistema operativo que ya no necesitamos la variabletipo FILE asociada al fichero. Para ello se usa la funcion fclose.

11.1 Abrir y cerrar ficheros

La cabecera de la funcion fopen es la siguiente:

FILE * fopen( char nombreFichero[], char modoAcceso[] )

donde nombreFichero es una cadena de caracteres con el nombre del fichero fısico que se quiereusar, y modoAcceso es una cadena de caracteres que indica la accion que realizaremos sobre elfichero.

Existen tres modos basicos para abrir ficheros:

� "r": Abrir un fichero ya existente para lectura.

� "w": Abrir un fichero nuevo para escritura. Si el fichero ya existıa, sera destruido y creado denuevo.

� "a": Abrir un fichero ya existente para anadir informacion; esto es, escribir al final del mismo.Si el fichero no existıa se creara uno nuevo.

Ademas de estos modos existen otros de uso menos frecuente:

� "r+": Abrir un fichero ya existente tanto para lectura como escritura.

� "w+": Abrir un fichero nuevo tanto para lectura como escritura. Si el fichero ya existıa seradestruido y creado de nuevo.

� "a+": Abrir un fichero ya existente para leer y anadir. Si el fichero no existıa se creara unonuevo.

La funcion fopen retorna la constante NULL si no ha podido abrir el fichero. Esta condicion deerror debe ser comprobada siempre que se use la funcion fopen. La constante NULL esta definidaen stdio.h.

Cuando ya hemos acabado de utilizar el fichero, debemos indicarselo al sistema operativo mediantela funcion fclose, que libera la variable de tipo FILE asociada al fichero. La cabecera de la funcionfclose es la siguiente:

Page 117: Introducción a la programación en c

11.1. Abrir y cerrar ficheros 108

int fclose( FILE *fp )

Si no se produce ningun error al cerrar el fichero fclose retorna 0. En caso contrario retorna laconstante EOF (recordemos que esta constante esta definida en stdio.h). Veamos algunos ejemplosdel uso de fopen y fclose.

En el siguiente ejemplo abrimos un fichero llamado miFichero.txt para leer la informacioncontenida en el. En caso de error al abrir el fichero mostramos un mensaje en pantalla y finalizamos laejecucion del programa mediante la funcion exit. Finalmente cerramos el fichero.

#include <stdio.h>void main( )f

FILE *fp;

fp = fopen( "miFichero.txt", "r" );if (fp == NULL)f

printf( "Error abriendo miFichero.txtnn" );exit(0);

g

. . ./* Aquı podemos leer datos del fichero */. . .fclose( fp );

g

A continuacion se muestra otro ejemplo para el caso en que abrimos un fichero para escritura.El nombre del fichero es introducido por el usuario a traves del teclado. Si el fichero ya existe, seradestruido y creado de nuevo. Esta accion la realiza de forma automatica la funcion fopen. Finalmentecerramos el fichero.

#include <stdio.h>#define N 256void main( )f

FILE *fp;char nombreFichero[N];

printf( " Nombre del fichero (< %d caracteres): ", N );scanf( "%s%*c", nombreFichero );fp = fopen( nombreFichero, "w" );if (fp == NULL)f

printf( "Error abriendo %snn", nombreFichero );exit(0);

g

. . .

Page 118: Introducción a la programación en c

109 11. Ficheros

/* Aquı podemos escribir datos en el fichero */. . .fclose( fp );

g

11.2 Leer y escribir en ficheros

Una vez abierto un fichero, podemos leer y escribir en el mediante las funciones fscanf y fprint.Las cabeceras de estas funciones son:

int fscanf( FILE *fp, char formato[], <lista variables> )int fprintf( FILE *fp, char formato[], <lista variables> )

La funcion fscanf permite leer del fichero apuntado por fp, mientras que la funcion fprintf per-mite escribir en el fichero apuntado por fp. El uso de estas funciones es analogo al de scanf yprintf, que permiten leer variables desde el teclado y escribir variables en pantalla, respectivamente.Por tanto, formato es una cadena de caracteres que describe el formato de las variables a leer/escribir.Por su parte, <lista variables> contiene las direcciones de memoria de todas las variables enel caso de fscanf y las variables propiamente dichas en el caso de fprintf. Los operadores deformato se hallan descritos en el apendice B.

La funcion fprintf retorna el numero de bytes (caracteres) escritos en el fichero, o un numeronegativo en caso de que ocurra algun error en la escritura. La funcion fscanf retorna el numero devariables correctamente leıdas, o la constante EOF en caso de error.

Veamos algunos ejemplo de uso de fscanf y fprintf.

En el siguiente ejemplo leemos un vector de enteros de un fichero de entrada. El fichero contiene enla primera lınea el numero de elementos del vector. El resto de lıneas del fichero contienen un elementodel vector en cada lınea. Finalmente, el programa escribe el vector en un fichero de salida usando elmismo formato que en el fichero de entrada.

#include <stdio.h>#define N 256#define MAXELE 100void main( )f

FILE *fp;char nombreFichero[N];int lon = 0;int vec[MAXELE];

printf( "Fichero de entrada(< %d caracteres): ", N );scanf( "%s%*c", nombreFichero );fp = fopen( nombreFichero, "r" );if (fp == NULL)f

printf( "Error abriendo %snn", nombreFichero );

Page 119: Introducción a la programación en c

11.2. Leer y escribir en ficheros 110

exit(0);g

fscanf( fp, "%d", &lon );if (lon < MAXELE)f

for (i= 0; i< lon; i= i+1)fscanf( fp, "%d", &vec[i] );

g

elseprintf( "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

printf( "Error abriendo %snn", nombreFichero );exit(0);

g

fprintf( fp, "%dnn", lon );for (i= 0; i< lon; i= i+1)

fprintf( fp, "%dnn", vec[i] );fclose( fp );

g

En el ejemplo anterior, si el nombre del fichero de salida es el mismo que el nombre del fichero deentrada, los datos iniciales se perderan, ya que al abrir el fichero en modo "w", el fichero que ya existıaes destruido y creado de nuevo. En este ejemplo, sin embargo, leemos de un fichero y el resultado delprograma es anadido al final del mismo fichero.

#include <stdio.h>#define N 256#define MAXELE 100void main( )f

FILE *fp;char nombreFichero[N];int lon;int vec[MAXELE];

printf( "Nombre del fichero(< %d caracteres): ", N );scanf( "%s%*c", nombreFichero );fp = fopen( nombreFichero, "r" );if (fp == NULL)

Page 120: Introducción a la programación en c

111 11. Ficheros

f

printf( "Error abriendo %snn", nombreFichero );exit(0);

g

fscanf( fp, "%d", &lon );if (lon < MAXELE)f

for (i= 0; i< lon; i= i+1)fscanf( fp, "%d", &vec[i] );

g

elseprintf( "El vector tiene demasiados elementosnn" );

fclose( fp );. . ./* Aquı trabajamos con vec */. . .fp = fopen( nombreFichero, "a" );if (fp == NULL)f

printf( "Error abriendo %snn", nombreFichero );exit(0);

g

fprintf( fp, "%dnn", lon );for (i= 0; i< lon; i= i+1)

fprintf( fp, "%dnn", vec[i] );fclose( fp );

g

11.3 Otras funciones para el manejo de ficheros

11.3.1 feof

En la mayorıa de ocasiones debemos leer un fichero sin saber su tamano a priori y, por lo tanto, sinsaber la cantidad de datos que debemos leer. En esta situacion se hace necesaria una funcion que nosindique cuando se alcanza el final de fichero. Esta funcion es feof, cuya cabecera es la siguiente:

int feof( FILE *fp )

La funcion feof retorna un numero diferente de 0 (cierto) cuando el caracter especial EOF hasido alcanzado en una lectura previa del fichero senalado por fp. En caso contrario, retorna 0 (falso).Es muy importante notar que la funcion feof solo indica ”fin de fichero” si previamente hemosrealizado una lectura mediante fscanf que no ha podido leer nada (que ha alcanzado el caracterEOF). Veamos algunos ejemplos del uso de feof.

El programa del siguiente ejemplo lee de un fichero los elementos de un vector de enteros. Elfichero contiene un elemento del vector en cada lınea. Notese que en el bucle while se controlan doscondiciones: alcanzar el fin del fichero y llenar completamente el vector.

Page 121: Introducción a la programación en c

11.3. Otras funciones para el manejo de ficheros 112

#include <stdio.h>#define N 256#define MAXELE 100void main( )f

FILE *fp;char nombreFichero[N];int lon;int vec[MAXELE];

printf( " Nombre del fichero(< %d caracteres): ", N );scanf( "%s%*c", nombreFichero );fp = fopen( nombreFichero, "r" );if (fp == NULL)f

printf( "Error abriendo %snn", nombreFichero );exit(0);

g

lon = 0;while (!feof(fp) && (lon < MAXELE))f

kk = fscanf( fp, "%d", &vec[lon] );if (kk == 1)

lon++;if (!feof(fp) && (lon == MAXELE))

printf( "Todo el contenido del fichero nocabe en el vectornn" );

g

fclose( fp );. . .

g

Supongamos que el fichero contiene solo tres lıneas como las siguientes:

123254-35

Al ejecutar el programa, el bucle while realizara cuatro iteraciones. En la tercera iteracion se leeradel fichero el numero -35 y se almacenara en vec[2]. Sin embargo, la funcion feof aun noindicara el final del fichero, es decir, retornara 0. En la cuarta iteracion, la funcion fscanf detectarael caracter EOF y por lo tanto no podra leer ningun valor valido. Ası pues, en vec[3] no alma-cenamos nada (se queda como estaba, con un valor aleatorio). Podremos saber que esta situacion haocurrido consultando el valor retornado por la funcion fscanf. En este ejemplo, como solo leemosuna variable, fscanf debe retornar 1 si ha podido realizar una lectura correcta. Notese que el pro-grama solo incrementa el valor de lon si la lectura ha sido correcta. Despues de la cuarta iteracion,feof retornara un valor diferente de 0 (cierto).

Page 122: Introducción a la programación en c

113 11. Ficheros

11.3.2 ferror

La cabecera de esta funcion es la siguiente:

int ferror( FILE *fp )

La funcion ferror retorna un valor diferente de 0 si ha ocurrido algun error en una lectu-ra/escritura previa en el fichero senalado por fp. En caso contrario retorna 0.

11.3.3 fflush

La cabecera de esta funcion es la siguiente:

int fflush( FILE *fp )

Cuando escribimos en un fichero, en realidad la escritura no se produce en el mismo momentode ejecutar la funcion fprintf. Sin embargo, esta funcion deja la informacion a escribir en unbuffer temporal del sistema operativo. Mas tarde, cuando el sistema operativo lo decida (este librede otras tareas, por ejemplo), se vuelca el contenido de dicho buffer sobre el fichero fısico. De estaforma en un computador con varios usuarios se puede organizar de forma mas eficiente el acceso a lasunidades de almacenamiento de informacion. La funcion fflush puede utilizarse para forzar enel instante deseado el volcado del buffer sobre el fichero. Si no se produce ningun error, la funcionfflush retorna 0, en caso contrario retorna EOF.

Un ejemplo tıpico del uso de fflush es la depuracion de programas. Supongamos que tenemosun error en un programa y para encontrarlo ponemos diversos fprintf, que muestran valores dealgunas variables. Si no ponemos despues de cada fprintf una llamada a fflush, no veremos elvalor que queremos en el momento en que realmente se produce, lo que nos puede llevar a conclusioneserroneas sobre el comportamiento del programa.

11.4 Ficheros estandar: stdin, stdout, stderr

En C existen tres constantes del tipo FILE *, definidas en stdio.h, llamadas stdin, stdout ystderr. El puntero stdin apuntan a un fichero abierto solo para lectura. Los punteros stdout ystderr apuntan a ficheros abiertos solo para escritura.

Estos punteros estan inicializados por el sistema operativo de forma que una lectura de stdin seaen realidad una lectura del teclado. Es decir, que una llamada como

fscanf( stdin, "%d", &i )es equivalente a

scanf( "%d", &i ) .De igual forma, los ficheros asignados a stdout y stderr estan inicialmente redirigidos a la

pantalla, de forma quefprintf( stdout, "Holann" ) o fprintf( stderr, "Holann" )

tienen el mismo efecto queprintf( "Holann" ) .

Las constantes stdin, stdout y stderr pueden ser usadas para inicializar varibles del tipoFILE * de forma que la entrada/salida sea a traves de teclado/pantalla.

Page 123: Introducción a la programación en c

11.4. Ficheros estandar: stdin, stdout, stderr 114

El siguiente ejemplo muestra un programa para multiplicar una matriz por un vector. Los datosde entrada se leen de stdin, es decir, del teclado. Por otra parte, los datos de salida se escribenen stdout (pantalla), y los mensajes de error en stderr (tambien la pantalla). Notese que stdin,stdout y stderr no estan declaradas en el programa, puesto que ya lo estan en stdio.h. Cuandoel programa funciona usando teclado/pantalla, muestra una serie de mensajes en pantalla explicando alusuario los datos que debe introducir. Sin embargo, cuando se usan ficheros, estos mensajes no tienensentido, por lo que no son mostrados.

#include <stdio.h>#define MAXFILAS 10#define MAXCOLUMNAS MAXFILASvoid main( )f

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? (s/n)" );scanf( "%c", &car );if (car == ’s’|| car == ’S’)f

fi = fopen( "Entrada.txt", "r" );if (fi == NULL)f

printf( "Error abriendo Entrada.txtnn" );exit(0);

g

fo = fopen( "Salida.txt", "w" );if (fo == NULL)f

printf( "Error abriendo Salida.txtnn" );exit(0);

g

fe = fopen( "Errores.txt", "w" );if (fe == NULL)f

printf( "Error abriendo Errores.txtnn" );exit(0);

g

g

if (fo == stdout)fprintf( fo, " N. filas = " );

Page 124: Introducción a la programación en c

115 11. Ficheros

fscanf( fi, "%d", &Nfilas );if (Nfilas > MAXFILAS)f

fprintf( fe, "Demasiadas filasnn" );exit (0);

g

if (fo == stdout)fprintf( fo, " N. columnas = " );

fscanf( fi, "%d", &Ncolumnas );if (Ncolumnas > MAXCOLUMNAS)f

fprintf( fe, "Demasiadas columnasnn" );exit (0);

g

for (i= 0; i< Nfilas; i++)for (j= 0; j< Ncolumnas; j++)f

if (fo == stdout)fprintf( fo, "A[%d][%d] = ", i, j );

k = fscanf( fi, "%lf", &A[i][j] );if (k != 1)f

fprintf( fe, "Error leyendo la matriznn" );exit (0);

g

g

for (i= 0; i< Nfilas; i++)f

if (fo == stdout)fprintf( fo, "x[%d] = ", i );

k = fscanf( fi, "%lf", &x[i] );if (k != 1)f

fprintf( fe, "Error leyendo el vectornn" );exit (0);

g

g

for (i= 0; i< Nfilas; i++)f

y[i] = 0.0;for (j= 0; j< Ncolumnas; j++)

y[i] = y[i] + A[i][j] * x[j];

Page 125: Introducción a la programación en c

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 );

g

11.5 Ejercicios

1. Se dispone de dos ficheros con numeros enteros ordenados de menor a mayor. Escribir los si-guientes programas de forma que el fichero resultante contenga los numeros ordenados de mayora menor.

� Un programa que construya un fichero con todos los numeros que estan en ambos ficherossimultaneamente (AND de ficheros).

� Un programa que construya un fichero con todos los numeros que estan en cualquiera delos dos ficheros (OR de ficheros). En el fichero resultante no debe haber numeros repetidos.

� Un programa que construya un fichero con los numeros que estan en cualquiera de los dosficheros, pero no en los dos simultaneamente (XOR de ficheros).

2. Se dispone de un fichero que contiene texto y se pretende realizar una compactacion del mismo.Para ello se substituyen las secuencias de cuatro o mas caracteres blancos por la secuencia #n#, donde n indica el numero de caracteres blancos que se han substituido. Para evitar confucion,el caracter # se sustituye por ##. Disenar una funcion que lea de un fichero un texto nocompactado y lo compacte segun los criterios expuestos. Disenar tambien otra funcion que leaun fichero de texto resultado de una compactacion previa y lo descompacte.

3. Se dispone de un fichero que contiene un numero no determinado de etiquetas. Cada etiquetaes un conjunto de datos sobre un determinado individuo (nombre, direccion, telefono, etc). Laetiqueta esta formada por 3 lıneas consecutivas de texto. Cada lınea de texto tiene 15 caracterescomo maximo. Las etiquetas estan separadas por una lınea que contiene unicamente el caracter *.Se desea disenar un programa que permita construir un nuevo fichero que contenga las etiquetasdel fichero original pero organizadas en columnas de 3 etiquetas (que empezaran respectivamenteen las columnas 0, 20 y 40). Supondremos que las lıneas de un fichero pueden tener un maximo de80 caracteres. Las filas de etiquetas deben estar separadas por una lınea en blanco. Por ejemplo:

Fichero de EntradaJuan Perezc/ AragonTlf. 932 491 134

Page 126: Introducción a la programación en c

117 11. Ficheros

Pedro LopezAvd. EuropaTlf. 931 113 456�

Juan Garcıac/ GraciaLerida�

Andres VillaTlf. 931 113 457Badalona�

Pedro CuberoTlf. 971 456 789Mallorca�

Fichero de SalidaJuan Perez Pedro Lopez Juan Garcıac/ Aragon Avd. Europa c/ GraciaTlf. 932 491 134 Tlf. 931 113 456 Lerida

Andres Villa Pedro CuberoTlf. 931 113 457 Tlf. 971 456 789Badalona Mallorca

4. Se dispone de un fichero compuesto unicamente por letras mayusculas, espacios en blanco, comasy puntos. El contenido de este fichero tiene las siguientes caracterısticas:

� Entre palabra y palabra puede haber cualquier numero de espacios en blanco.

� Entre una palabra y un signo de puntuacion puede haber cualquier numero de espacios enblanco.

� Entre un signo de puntuacion y una palabra puede haber cualquier numero de espacios enblanco.

� El primer y ultimo caracter del texto de entrada es una letra.

Debemos realizar un algoritmo que escriba en un fichero de caracteres el contenido del fichero deentrada formateado de tal manera que en el texto resultante se cumplan los siguientes requisitos:

� Todas las palabras estaran escritas con letras minusculas excepto la primera letra despuesde un punto y la primera letra del texto.

� Entre palabra y palabra solo puede haber un blanco.

� Entre la ultima letra de una palabra y un signo de puntuacion no debe haber ningun blanco.

Page 127: Introducción a la programación en c

11.5. Ejercicios 118

� Entre un signo de puntuacion y la primera letra de una palabra debe haber un espacio enblanco.

� El ultimo caracter debe ser un punto.

Page 128: Introducción a la programación en c

119 A. El preprocesador

Apendice A

El preprocesador

El preprocesador es una herramienta muy util para el programador. Las directivas del preprocesadorson en realidad simples comandos de edicion, es decir, comandos que modifican el fichero con codigofuente del programa, de igual forma que lo harıamos mediante un editor de textos. El fichero modificadopor el preprocesador sigue siendo un fichero de texto.

Las directivas del preprocesador se distinguen de las lıneas de codigo C porque empiezan con elsımbolo # en la primera columna. Es importante hacer notar que es obligatorio que el sımbolo # esteen la primera columna, ya que en caso contrario se genera un error de compilacion.

En este apendice veremos las directivas mas importantes del preprocesador.

A.1 Directiva include

La directiva include permite incluir en el fichero de codigo fuente otros ficheros de texto. Estadirectiva puede usarse dos formas distintas:

#include <fichero.h>#include "miFichero.h"

Cuando el fichero incluido pertenece al sistema operativo se usan los sımbolos < > para delimitar elnombre del fichero. Si el fichero no forma parte del sistema operativo se usan los sımbolos " ". Encualquier caso, el efecto de un include es el mismo: se sustituye la lınea donde aparece la directivapor el contenido del fichero indicado.

A.2 Directivas define y undef

Como su nombre indica, la directiva define permite definir sımbolos. Por su parte, la directivaundef permite eliminar sımbolos previamente definidos. El uso de estas directivas es el siguente:

#define nombreSımbolo valorSımbolo#undef nombreSımbolo

donde nombreSımbolo es el nombre del sımbolo que definimos/eliminamos y valorSımbo-lo es el valor que damos a dicho sımbolo. Dar valor al sımbolo es optativo.

Page 129: Introducción a la programación en c

A.3. Directivas ifdef y ifndef 120

El principal uso de la directiva define es substituir un texto por otro texto. Por ejemplo:

#define N 100

significa que el preprocesador sustituira el sımbolo N por el texto 100 dentro del programa. Acontinuacion se muestra un fragmento de codigo antes y despues de ser tratado por el preprocesador:

Antes del preprocesador...for (i= 0; i< N; i++)

Numeros[i] = i;...

Despues del preprocesador...for (i= 0; i< 100; i++)

Numeros[i] = i;...

Notese que la palabra Numeros no ha sido substituida por 100umeros. Solo se ha substituido eltexto N allı donde las reglas sintacticas de C indican que dicho texto es el nombre de un sımbolo.

Esto se aprovecha para la definicion de constantes. Normalmente estas constantes son las dimen-siones maximas de tablas del programa. De esta forma, cuando deseemos modificar estas dimensiones,bastara modificar la lınea de codigo que contiene el define, sin tener que buscar por el programatodas las apariciones de las constantes.

La directiva define tiene otros usos importantes que veremos en las secciones A.3 y A.4.

A.3 Directivas ifdef y ifndef

En ocasiones es preciso que determinados fragmentos de codigo fuente solo se compilen si se cumplenciertas condiciones. A este hecho se le denomina compilacion condicional. Las directivas ifdef yifndef sirven para realizar dicho tipo de compilacion. El uso de estas directivas es el siguiente:

#ifdef nombrecodigo1

#elsecodigo2

#endif

#ifndef nombrecodigo1

#elsecodigo2

#endif

donde nombre es un sımbolo definido mediante la directiva define. Los textos indicados porcodigo1 y codigo2 representan fragmentos de codigo fuente en C. En la directiva ifdef, si

Page 130: Introducción a la programación en c

121 A. El preprocesador

existe un define que defina el sımbolo nombre el codigo que finalmente se complia corresponde alfragmento indicado por codigo1. En caso contrario, el codigo compilado corresponde a codigo2.Por otra parte, en la directiva ifndef, si no existe un define para el sımbolo nombre, elcodigo compilado es el correspondiente a codigo1. En caso contrario, el codigo compilado es elcorrespondiente a codigo2. En ambas directivas el uso de else es optativo. Veamos algunosejemplos.

Supongamos que un programa debe mostrar ciertos valores en pantalla para estar seguros de sufuncionamiento. Pero esto solo es necesario hacerlo mientras el programa esta en la fase de desarrollo.Una vez finalizada esta fase, no es necesario que muestre toda esa informacion. Una solucion consistiraen borrar manualmente las lıneas de codigo pertinentes, pero si el programa es grande (miles o millonesde lıneas de codigo) podemos cometer errores facilmente al eliminar dichas lıneas. En el siguientecodigo, el sımbolo DEBUG controla si se compila o no el printf. Notese que la directiva #defineDEBUG no le asigna valor a la constante DEBUG, sımplemente la define como sımbolo.

#define DEBUG...for (i= 0; i< Nfilas; i++)f

y[i] = 0.0;for (j= 0; j< Ncolumnas; j++)f

#ifdef DEBUGprintf( "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];g

g

Supongamos ahora que necesitamos mostrar en pantalla los recursos que usa un programa (memo-ria, tiempo de ejecucion, etc). Para ello debemos llamar a una funcion del sistema operativo. Pero encada sistema operativo el nombre de dicha funcion puede ser diferente, o puede que incluso no existadicha funcion. El siguiente codigo muestra una solucion para que, en cualquier caso, el programa sepueda compilar sin problemas:

...printf( "Recursos usados por el programann" );#ifdef WINDOWS

printf( "Funcion no disponible en sistema WINDOWSnn" );#else

getrusage( RUSAGE SELF, &rusage );...

#endif...

Page 131: Introducción a la programación en c

A.4. Macros 122

A.4 Macros

La directiva define tambien permite definir macros. La sintaxis de una macro es la siguiente:

#define nombreMacro( param1, param2, ... ) codigo

donde codigo es un conjunto valido de sentencias en C, y param1, etc. son sımbolos que aparecenen codigo. Cuando el preprocesador se ejecuta, substituye cada llamada a la macro por el textoescrito en codigo, substituyendo dentro de codigo los sımbolos param1, etc. por los valores quetengan en la llamada a la macro.

Veamos un ejemplo:

#define SWAP( p1, p2, p3 ) p3=p1; p1=p2; p2=p3;

En el codigo anterior hemos definido la macro SWAP. Esta macro tiene tres parametros p1, p2 yp3. Donde esta macro sea invocada, el preprocesador susbtituira la macro por el codigo indicado. Estamacro sirve para intercambiar los valores de los parametros p1 y p2, usando el parametro p3 comouna variable temporal. Veamos a continuacion que harıa el preprocesador con dos llamas a la macroSWAP en el siguente programa:

Antes del preprocesadordouble x, y, z;int a, b, c;...SWAP ( x, y, z );SWAP ( a, b, c );...

Despues del preprocesadordouble 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 funcion. Pero si las sentencias de la macro son muysimples, podemos gastar mas tiempo llamando y retornando de la funcion que ejecutando su codigo.Ademas, en un ejemplo como el anterior vemos que la misma macro sirve para valores enteros, reales,caracteres, estructuras, etc. Sin embargo necesitarıamos una funcion diferente para cada tipo de datosdistinto.

Page 132: Introducción a la programación en c

123 B. La librerıa estandar

Apendice B

La librerıa estandar

Como se ha visto, una de las caracterısticas de C es que su sintaxis se basa en un conjunto muy reducidode palabras reservadas (ver Tab. 3.1). Por esta razon, las operaciones de entrada y salida, el manejo decadenas de caracteres, las funciones matematicas, etc. no forman parte propiamente del lenguaje C.Todas estas funcionalidades, y muchas otras mas, se hallan implementadas en una librerıa de funcionesy tipos de datos especiales que se conoce como la librerıa estandar.

El programador puede acceder a la librerıa estandar mediante un conjunto de ficheros de cabeceras(con extension ‘.h’). Ası pues, si un programa utiliza alguna de las funciones de la librerıa, dichoprograma debera incluir el fichero de cabeceras correspondiente donde se halle definida dicha funcion.Por ejemplo, si el programa utiliza una funcion de entrada y salida como printf, debera incluir elfichero stdio.h de la siguiente forma: #include <stdio.h>.

Este apendice resume algunas de las funciones disponibles en dicha librerıa agrupandolas segunel fichero de cabeceras donde se hallan definidas. Para cada funcion, se proporciona su nombre,parametros y resultado devuelto, ası como una breve descripcion. Cabe decir que alguno estos datospuede diferir de un sistema a otro, por lo que es recomendable consultar los manuales correspondientespara mayor seguridad.

B.1 Manipulacion de cadenas de caracteres

Las siguientes funciones se hallan definidas en el fichero de cabeceras string.h:

� int strcasecmp( char s1[], char s2[] ) Compara las dos cadenas s1 y s2,ignorando mayusculas y minusculas. Devuelve un entero menor, igual o mayor que 0, si s1 esmenor, igual o mayor lexicograficamente que s2, respectivamente.

� char *strcat( char dest[], char src[] ) Concatena la cadena src al finalde la cadena dest. La cadena dest debe tener suficiente espacio para albergar la cadenaresultante.

� char *strchr( char s[], int c ) Devuelve un puntero a la posicion de la primeraocurrencia del caracter c en la cadena s.

� int strcmp( char s1[], char s2[] ) Compara las dos cadenas s1 y s2. De-vuelve un entero menor, igual o mayor que 0, si s1 es menor, igual o mayor lexicograficamente

Page 133: Introducción a la programación en c

B.2. Entrada y salida 124

que s2, respectivamente.

� char *strcpy( char dest[], char src[] ) Copia la cadena scr en la cadenadest. La cadena dest debe tener suficiente espacio para albergar la cadena src.

� char *strdup( char s[] ) Devuelve un puntero a una nueva cadena que es un duplica-do de la cadena s.

� int strlen( char s[] ) Devuelve la longitud de la cadena s, sin contar el caracter‘n0’.

� char *strncat( char dest[], char src[], int n ) Similar a strcat, a ex-cepcion de que solo se concatenan al final de dest, los n primeros caracteres de la cadenasrc.

� int strncmp( char s1[], char s2[], int n ) Similar a strcmp, a excepcionde que solo se comparan los n primeros caracteres de ambas cadenas.

� char *strncpy( char dest[], char src[], int n ) Similar a strcpy, a ex-cepcion de que solo se copian en dest los n primeros caracteres de la cadena src.

� int strncasecmp( char s1[], char s2[], int n ) Similar a strcasecmp,a excepcion de que solo se comparan los n primeros caracteres de ambas cadenas.

� char *strrchr( char s[], int c ) Devuelve un puntero a la posicion de la ultimaocurrencia del caracter c en la cadena s.

� char *strstr( char s1[], char s2[] ) Devuelve un puntero a la posicion de laprimera ocurrencia de la cadena s2 en la cadena s1.

B.2 Entrada y salida

Las siguientes funciones se hallan definidas en el fichero de cabeceras stdio.h.

B.2.1 Entrada y salida basica

Hay varias funciones que proporcionan entrada y salida basica. Probablemente las mas conocidas sean:

� int getchar() Lee un caracter del teclado.

� char *gets( char string[] ) Lee una cadena de caracteres del teclado.

� int putchar( char ch ) Escribe un caracter por pantalla. Devuelve el caracter escrito.

� int puts( char string[] ) Escribe una cadena de caracteres por pantalla. Devuelveel numero de caracteres escritos.

B.2.2 Entrada y salida con formato

Ya hemos visto el uso de la entrada y salida con formato mediante las funciones printf y scanf.Veamoslas ahora con mas detalle:

Page 134: Introducción a la programación en c

125 B. La librerıa estandar

int printf( char format[], ... )

Escribe en pantalla la lista de argumentos de acuerdo con el formato especificado para cada uno de ellos,y devuelve el numero de caracteres escritos. El formato consta de caracteres ordinarios (que se escribendirectamente en pantalla) y de especificadores de formato denotados por el caracter %. Debe habertantos especificadores de formato como argumentos. La forma general de uno de estos especificadoreses la siguiente:

%[-|+][ancho][.prec][h|l|L]tipo

donde:

� tipo especifica el tipo de datos del argumento segun la tabla:

tipo Argumento Formato de salidac char caracter

i, d int entero decimal: dıgitos 0, . . . , 9o ” entero octal: dıgitos 0, . . . , 7

x, X ” entero hexadecimal: dıgitos 0, . . . , 9, A, . . . , Fu ” entero decimal sin signos char * cadena de caracteres hasta ’n0’f double/float [-]dddd.dddd

e, E ” notacion cientıfica: [-]d.dddd[e/E][+/-]dddg, G ” la forma mas compacta entre %f y %e

p puntero direccion de memoria% ninguno caracter %

� [h|l|L] como modificador del tipo de datos basico. Se usa h para short int, l paralong int y double, y L para long double.

� [.prec] numero de decimales al escribir un numero de coma flotante, o numero de caracteresal escribir una cadena de caracteres.

� [ancho] numero de espacios empleados para la escritura. Si es inferior al necesario se ignora.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 justificar a la izquierda rellenando con blancos, y + para forzar laescritura del signo en los numeros.

Veamos algunos ejemplos ilustrativos:

printf( "%030.5f", 1.5236558 ); 000000000000000000000001.52326printf( "%+30.5f", 1.5236558 ); +1.52326printf( "%+-030.5f", 1.5236558 ); +1.523260000000000000000000000printf( "%8.3s", "hola" ); holprintf( "%-08.3s", "hola" ); hol00000

Page 135: Introducción a la programación en c

B.2. Entrada y salida 126

int scanf( char format[], ... )

Lee datos del teclado (caracter a caracter) y los coloca en las direcciones de memoria especificadas en lalista de argumentos de acuerdo con el formato. Devuelve el numero de argumentos leıdos. El formatoconsta de caracteres ordinarios (que se espera se tecleen) y de especificadores de formato denotados porel caracter %. Debe haber tantos especificadores de formato como direcciones de argumentos dondealmacenar los datos leıdos. La forma general de uno de estos especificadores es la siguiente:

%[*][ancho][h|l|L]tipo

donde:

� tipo especifica el tipo de datos del argumento segun la tabla:

tipo Argumento Entrada esperadac char * caracteri int * entero decimal, octal o hexadecimald ” entero decimalo ” entero octalx ” entero hexadecimalu ” entero decimal sin signoI long int * entero decimal, octal o hexadecimalD ” entero decimalO ” entero octalX ” entero hexadecimalU ” entero decimal sin signos char [] cadena de caracteres hasta blanco, tabulador o salto de lıneaf double/float numero en coma flotante

e, E ” ”g, G ” ”

p puntero direccion de memoria hexadecimal: YYYY:ZZZZ o ZZZZ% ninguno caracter %

� * no asigna el argumento leıdo a ninguna variable de los argumentos.

El resto de campos del modificador son identicos al caso de printf. Sin embargo existen un parde convenciones especiales que merece la pena destacar:

� %[set] que permite leer una cadena de caracteres hasta encontrar un caracter que no pertenezcaal conjunto set especificado. Dicho caracter no se lee.

� %[ˆset] que permite leer una cadena de caracteres hasta encontrar un caracter que pertenezcaal conjunto set especificado. Dicho caracter no se lee.

B.2.3 Ficheros

� int fclose( FILE *fich ) Cierra el fichero fich y devuelve un codigo de error.

� int feof( FILE *fich ) Comprueba si se ha llegado al final del fichero fich.

Page 136: Introducción a la programación en c

127 B. La librerıa estandar

� int ferror( FILE *fich ) Comprueba si se ha producido algun error en alguna opera-cion sobre el fichero fich.

� int fflush( FILE *fich ) Fuerza la escritura en disco de las escrituras diferidas reali-zadas sobre fich.

� int fgetc( FILE *fich ) Lee un caracter de fich.

� char *fgets( char string[], int max, FILE *fich ) Lee una cadena de has-ta max caracteres de fich.

� FILE *fopen( char nombre[], char modo[] ) Abre el fichero con el nombre ymodo de apertura especificados. modo puede ser: "r" para lectura, "w" para escritura y"a" para anadir informacion al final del fichero.

� int fprintf( FILE *fich, char formato[], ... ) Escritura con formato enfich. Ver printf.

� int fputc( int c, FILE *fich ) Escribe el caracter c en fich.

� int fputs( char string[], FILE *fich ) Escribe una cadena de caracteres enfich.

� int fscanf( FILE *fich, char formato[], ... ) Lectura con formato defich. Ver scanf.

� int getc( FILE *fich ) Lee un caracter de fich.

� int putc( int c, FILE *fich ) Escribe el caracter c en fich.

� void rewind( FILE *fich ) Situa el cursor para lecturas/escrituras de fich al prin-cipio del mismo.

� int sprintf( char string[], char formato[], ... ) Escritura con for-mato en una cadena caracteres. Ver printf.

� int sscanf( char buffer[], char formato[], ... ) Lectura con formatode una cadena de caracteres. Ver scanf.

� int ungetc( int c, FILE *fich ) Devuelve el caracter c, leıdo previamente, alfichero fich de donde fue leıdo.

B.3 Funciones matematicas

Las siguientes funciones se hallan definidas en el fichero de cabeceras math.h:

� double acos( double x ) Calcula el arco coseno de x.

� double asin( double x ) Calcula el arco seno de x.

� double atan( double x ) Calcula el arco tangente de x.

Page 137: Introducción a la programación en c

B.4. Clasificacion y manipulacion de caracteres 128

� double atan2( double y, double x ) Calcula el arco tangente de y/x.

� double ceil( double x ) Calcula el entero mas pequeno que es mayor que x.

� double cos( double x ) Calcula el coseno de x en radianes.

� double cosh( double x ) Calcula el coseno hiperbolico de x.

� double exp( double x ) Calcula ex.

� double fabs( double x ) Calcula el valor absoluto de x.

� double floor( double x ) Calcula el entero mas grande que es menor que x.

� 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 hiperbolico de x.

� double sqrt( double x ) Calcula la raız cuadrada de x.

� void srand( unsigned seed ) Fija un nuevo germen para el generador de numerosaleatorios (rand).

� double tan( double x ) Calcula la tangente de x en radianes.

� double tanh( double x ) Calcula la tangente hiperbolica de x.

B.4 Clasificacion y manipulacion de caracteres

Las siguientes funciones se hallan definidas en el fichero de cabeceras ctype.h:

� int isalnum( int c ) Devuelve cierto si c es un caracter alfanumerico.

� int isalpha( int c ) Devuelve cierto si c es una letra.

� int isascii( int c ) Devuelve cierto si c corresponde a un codigo ASCII.

� int iscntrl( int c ) Devuelve cierto si c es un caracter de control.

� int isdigit( int c ) Devuelve cierto si c es un dıgito decimal.

� int isgraph( int c ) Devuelve cierto si c es un caracter grafico.

� int islower( int c ) Devuelve cierto si c es una letra minuscula.

� int isprint( int c ) Devuelve cierto si c es un caracter imprimible.

Page 138: Introducción a la programación en c

129 B. La librerıa estandar

� int ispunct( int c ) Devuelve cierto si c es un sımbolo de puntuacion.

� int isspace( int c ) Devuelve cierto si c es un caracter de espaciado.

� int isupper( int c ) Devuelve cierto si c es una letra mayuscula.

� int isxdigit( int c ) Devuelve cierto si c es un dıgito hexadecimal.

� int toascii( int c ) Obtiene el codigo ASCII de c.

� tolower( int c ) Convierte c a minuscula.

� int toupper( int c ) Convierte c a mayuscula.

B.5 Conversion de datos

Las siguientes funciones se hallan definidas en el fichero de cabeceras stdlib.h:

� double atof( char string[] ) Convierte una cadena de caracteres en un numero decoma flotante.

� int atoi( char string[] ) Convierte una cadena de caracteres en un numero entero.

� int atol( char string[] ) Convierte una cadena de caracteres en un numero enterode doble precision.

B.6 Manipulacion de directorios

Las siguientes funciones se hallan definidas en el fichero de cabeceras dir.h:

� int chdir( char path[] ) Cambia el directorio de trabajo actual de acuerdo con elpath especificado.

� char *getcwd( char path[], int numchars ) Devuelve el nombre del directoriode trabajo actual.

� int mkdir( char path[] ) Crea un nuevo directorio con el nombre especificado enpath.

� int rmdir( char path[] ) Borra el directorio con el nombre especificado en path.

B.7 Memoria dinamica

Una de las caracterısticas mas importantes de un programa es la cantidad de memoria que necesita paraejecutarse. Es importante que un programa no desperdicie memoria. Esto plantea un serio problemacuando declaramos las variables, esencialmente las tablas, ya que deberemos dimensionar el espacio dememoria para el peor caso posible.

Para evitar este problema existen funciones que permiten una gestion dinamica de la memoria, esdecir, permiten que un programa adquiera memoria segun la necesite, y la vaya liberandola cuando deje

Page 139: Introducción a la programación en c

B.7. Memoria dinamica 130

de necesitarla. C dispone de las siguiente funciones para gestionar de forma dinamica la memoria, todasellas estan definidas en el fichero stdlib.h:

� void *malloc( size t num bytes )Reserva un bloque de memoria de num bytes bytes. Devuelve un puntero al primer byte delbloque de memoria reservado, o NULL si no hay suficiente 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 cadauno. Este espacio de memoria es inicializado con ceros. Devuelve un puntero al primer byte delbloque de memoria reservado, o NULL si no hay suficiente memoria disponible.

� void *realloc( void *ptr, size t num bytes )Cambia el tamano del bloque de memoria apuntado por ptr para que tenga num bytes bytes.Devuelve un puntero al primer byte del nuevo bloque de memoria reservado, o NULL si no haysuficiente memoria disponible.

� void free( void *ptr )Libera el bloque de memoria apuntado por ptr. Dicho bloque debe haber sido previamenteobtenido mediante malloc, calloc o realloc. Si ptr es NULL, no se hace nada.

El tipo de datos size t es un numero natural (sin signo). Cuando llamamos a estas funciones yles pasamos como parametros varibles de tipo entero (short, int o long), se realiza una conversionde tipo de forma automatica.

Junto con estas funciones usaremos el operador de C sizeof(tipo de datos). Este operadorretorna el numero de bytes que ocupa una variable del tipo tipo de datos, tanto si este tipo estapredefinido en C (int, float, etc.) como si es un tipo definido por el programador.

Veamos algunos ejemplos que ilustran el empleo de memoria dinamica.

El siguiente ejemplo gestiona de forma dinamica dos vectores.

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

typedef structf

long DNI;char nom[256];

g Tpersona;

void main( )f

int i, lon;double *nota;Tpersona *alumno;

Page 140: Introducción a la programación en c

131 B. La librerıa estandar

dof

printf( "Cuantos alumnos hay?nn" );scanf( "%d", &lon );

g while( lon < 0 );

nota = malloc( lon*sizeof(double) );alumno = malloc( lon*sizeof(Tpersona) );

if ((nota == NULL) || (alumno == NULL))f

printf( " No hay memoria suficientenn" );exit(0);

g

.../* Introduccion de datos y notas de cada alumno. */...

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 );

g

Hay varios puntos a destacar:

� Notar que en la declaracion de variables no declaramos ningun vector. En su lugar declaramospunteros. En este caso un puntero al tipo double y otro al tipo Tpersona. En estos punterosalmacenamos las direcciones de memoria que devuelve malloc.

� Para indicarle a la funcion malloc que cantidad de bytes de memoria necesitamos, hemosusado el operador sizeof. Los bytes requeridos son el numero de elementos multiplicado porel tamano en bytes de cada elemento. Cabe notar que sizeof se puede usar tambien con tiposdefinidos por el programador como el tipo Tpersona.

� Despues de llamar a malloc comprobamos que los punteros no sean NULL. Si lon es muygrande, puede ocurrir que el computador no tenga memoria suficiente. Esta comprobacion deerror siempre debe hacerse.

� Cuando tenemos los bloques de memoria ya reservados, podemos acceder a ellos a traves de lospunteros. En este caso, la notacion para acceder a traves de los punteros es identica a la que seusa 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. Paraello usamos la funcion free. Cualquier intento de acceder a los bloques de memoria despuesde la llamada a free generarıa un error de ejecucion.

Page 141: Introducción a la programación en c

B.7. Memoria dinamica 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>

void main( )f

int i, j, nfil, ncol;double *x, *y, *A;

dof

printf( " Numero de filas?nn" );scanf( "%d", &nfil );

g while( nfil < 0 );

dof

printf( " Numero de columnas?nn" );scanf( "%d", &ncol );

g 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

printf( " No hay memoria suficientenn" );exit(0);

g

.../* Introduccion del vector x y la matrix A */...

for (i= 0; i< nfil; i++)f

for (j= 0; j< ncol; j++)y[i] = y[i] + A[i*ncol+j] * x[j];

g

for (i= 0; i< nfil; i++)printf( "y[%d] = %lfnn", i, y[i] );

free( A );

Page 142: Introducción a la programación en c

133 B. La librerıa estandar

free( x );free( y );

g

Los puntos mas destacables son los siguientes:

� Para reservar memoria para el vector y utilizamos la funcion calloc. De esta forma el bloquede memoria queda inicializado a cero y no es necesario inicializar cada componente de y en elalgoritmo de multiplicacion.

� La notacion para acceder a los bloques de memoria x e y a traves de los punteros coincide conla que usarıamos si fuesen vectores declarados de forma estatica. Pero no pasa lo mismo con lamatriz.

Finalmente, en este ejemplo modificamos el tamano de un bloque de memoria, que previamnetehabıa sido reservado mediante la funcion malloc.

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

void main( )f

int lon1, lon2;double *vec;

dof

printf( "Longitud del vector?nn" );scanf( "%d", &lon1 );

g while( lon1 < 0 );

vec = malloc( lon1*sizeof(double) );

if (vec == NULL)f

printf( " No hay memoria suficientenn" );exit(0);

g

.../* Aquı trabajamos con vec */...

dof

printf( " Nueva longitud del vector?nn" );scanf( "%d", &lon2 );

Page 143: Introducción a la programación en c

B.7. Memoria dinamica 134

g while( lon2 < 0 );

vec = realloc( vec, lon2*sizeof(double) );

if (vec == NULL)f

printf( " No hay memoria suficientenn" );exit(0);

g

.../* Aquı trabajamos con vec */...

free( vec );g

La funcion realloc nos permite modificar el tamano del bloque de memoria reservado, pero nomodifica los datos almacenados en dicho bloque. Es decir:

� Si lon2 < lon1, tendremos un bloque de memoria mas pequeno, pero los lon2 valoresalmacenados seguiran siendo los mismos.

� Si lon2 > lon1, tendremos un bloque de memoria mas grande. Los primeros lon1 va-lores seran los mismos que habıa antes de la llamada a realloc, mientras que los lon2 -lon1 valores finales seran aleatorios (no estaran inicializados).

Page 144: Introducción a la programación en c

135 C. Sistemas de numeracion

Apendice C

Sistemas de numeracion

Un computador usa el sistema de numeracion en base 2 debido a como funcionan los dispositivoselectronicos basicos (los transistores) que lo forman. En el sistema de numeracion en base dos soloexisten 2 cifras el 0 y el 1. A las cifras de un numero en base 2 se las denomina bits. A un grupo de 8bits se le denomina byte.

C.1 Naturales

Los numeros naturales se representan mediante un codigo llamado binario natural, que consiste sim-plemente en expresar el numero en base 2. Si disponemos n bits para representar numeros naturalestendremos las 2n combinaciones que se muestran en la tabla C.1.

C.2 Enteros

Los numeros enteros se representan mediante un codigo llamado complemento a 2. Este codigo se usaporque simplifica notablemente la construccion de los circuitos electronicos necesarios para realizaroperaciones aritmeticas, fundamentalmente sumas y restas.

El complemento a 2 de un numero se calcula de la siguiente forma: si el numero es positivo sucomplemento a 2 coincide con su expresion en binario natural; si el numero es negativo se debe escribirla representacion en binario natural de su modulo (valor absoluto), complementar a 1 dicha expresion

Tabla C.1: Representacion de numeros naturales en binario natural

Valor Decimal Binario Natural0 0.....01 0....012 0...0103 0...0104 0...100

. . . . . .2n � 1 1.....1

Page 145: Introducción a la programación en c

C.3. Reales 136

Tabla C.2: Representacion de numeros enteros en complemento a 2

Valor Decimal Complemento a 2�2n�1 10....0

. . . . . .�1 1.....10 0.....01 0....01

. . . . . .+2n�1 � 1 01....1

(cambiar los 0 por 1 y viceversa), y finalmente sumarle 1. Por ejemplo si disponemos de 4 bits pararepresentar numeros enteros y deseamos representar el numero �3, tendrıamos que:

3 = 00111100 Complemento a 1+ 1

-3 = 1101 Complemento a 2

En general, si disponemos de n bits, podemos representrar los numeros en complemento a 2 que semuestran en la tabla C.2.

La codificacion en complemento a 2 tiene algunas propiedades interesantes como las siguientes:

� El cero tiene una unica representacion: 0...0 = 1...1 + 1

� Todos los numeros del mismo signo empiezan por el mismo bit: 1 para los negativos, 0 para lospositivos

� La suma/resta de numeros en complemento a 2 se puede realizar mediante la suma binaria bit abit, despreciando el ultimo acarreo. Por ejemplo:

-2 - 1 = -3 1....10 + 1...1 = 1...102 - 1 = 1 0...010 + 1...1 = 0...01

C.3 Reales

Los numeros reales se representan mediante un codigo llamado coma flotante. Este codigo es simple-mente la representacion en notacion cientıfica normalizada y en base 2.

Recordemos que un numero en notacion cientıfica se representa mediante la siguiente expresion:�mantisa � base

exponente. Donde la mantisa es un numero real con la coma decimal colocadaa la derecha o a la izquierda de la primera cifra significativa (normalizacion por la derecha o por laizquierda); la base es la misma que la base de numeracion usada para representar mantisa y exponente;y el exponente es un numero entero. Los siguientes ejemplos muestran numeros representados ennotacion cientıfica en base 10, normalizados por la derecha y por la izquierda:

Page 146: Introducción a la programación en c

137 C. Sistemas de numeracion

Tabla C.3: Representacion de numeros enteros en exceso 2e�1

Valor Decimal Complemento a 2 exceso 2e�1

�2e�1 10....0 0.....0. . . . . . . . .�1 1.....1 01....10 0.....0 10....01 0....01 10...01

. . . . . . . . .+2e�1 � 1 01....1 11....1

�3; 141592 � 100 = �0; 3141592 � 101

2; 53547 � 10�3 = 0; 253547 � 10�2

La notacion cientıfica usada por los computadores tiene algunos detalles especiales debidos a queusa 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 significa positivo y 1 significa negativo).

La base no es necesario almacenarla, ya que siempre es 2. Si pudiesemos ver un numero realalmacenado en la memoria del computador verıamos una secuencia de bits como la siguiente:

1 bitz}|{1|{z}

signo

m bitsz }| {101 : : : 010| {z }mantisa

e bitsz }| {10010| {z }

exponente

2. En base 2 solo existen 2 cifras, el 0 y el 1. Ası pues, el primer bit significativo siempre sera un 1,por lo que este primer bit no se almacena en la memoria. Los circuitos electronicos que operancon datos en coma flotante ya tienen en cuenta que delante de todos los bits de la mantisa siemprehay un 1. A este bit que no se almacena se le denomina bit oculto o implıcito.

3. El exponente se almacena usando un codigo llamado exceso 2e�1, donde e es el numerode bits usados para almacenar el exponente. Este codigo proviene de rotar de forma cıclica2e�1 posiciones la tabla del complemento a 2 (ver Tab. C.3).

Para calcular un codigo en exceso podemos usar la siguiente formula:

Valor decimal = Valor en binario natural � 2e�1

Notar que en el codigo en exceso todos los numeros negativos comienzan por 0 y todos losnumeros positivos comienzan por 1. Ademas, a valores crecientes les corresponden codigos queleıdos en binario natural tambien son crecientes. Es decir: �2 < 1 , 01 : : : 10 < 10 : : : 01.De esta forma podemos comparar numeros de un codigo en exceso usando un simple comparadorde numeros en binario natural. Sin embargo, los circuitos para sumar/restar numeros codificadosen exceso, ya no seran un simple sumador binario. Por razones historicas se decidio usar elcodigo en exceso en vez del codigo en complemento a 2 para representar los exponentes.

Page 147: Introducción a la programación en c

C.3. Reales 138

Tabla C.4: Representacion de numeros reales

Valor Decimal Signo Mantisa Exponente�0 x 0 : : : 0 0 : : : 0

NAN x x : : : x 0 : : : 0

�M��nimo x 0 : : : 0 0 : : : 1

: : : : : : : : : : : :

�M�aximo x 1 : : : 1 1 : : : 1

4. Debido al uso del bit oculto, el numero cero no se puede representar, ya que sea cual sea la man-tisa, esta nunca sera cero. Dado que el numero cero es un numero importante, que es necesariopoder representar, se hace una excepcion a la regla de representacion. De forma arbitraria sedecide que el numero cero se representa mediante la combinacion en que todos los bits de lamantisa y el exponente son ceros.

5. Ademas, las diferentes combinaciones de mantisa con el exponente cuyo codigo son todo cerostampoco se usan como numeros. Estos codigos se reservan como codigos de error, generadospor los circuitos aritmeticos cuando se producen condiciones de error como: una division en queel divisor es cero, la raız cuadrada de un numero negativo, etc. A estos codigos de error se lesdenomina NAN (del ingles, Not A Number). Por lo tanto:

� El exponente mınimo es �(2e�1 � 1) y se representa mediante la combinacion 0 : : : 01.

� El exponente maximo es +(2e�1 � 1), y se representa mediante la combinacion 1 : : : 1.

� El numero representable mas cercano a cero es �1:0 � 2Exp:M��nimo. Este numero tienetoda su mantisa con ceros y el exponente es la combinacion 0 : : : 01.

� El numero representable mas grande es �2(1 � 2�(m+1)) � 2Exp:M�aximo. Este numerotiene toda su mantisa con unos y el exponente es la combinacion 1 : : : 1.

En la tabla C.4 se muestran ordenados los codigos de un formato de coma flotante. El sımbolox significa cualquier bit 0 o 1.

6. Cabe notar que cuantos mas bits se usen para representar la mantisa, mayor precision se tiene enla representacion (menor error relativo). Cuantos mas bits usemos para representar el exponentemayor rango numerico abarcaremos.

Si cada computador usase su propio sistema de representacion en coma flotante, podrıamos tenervalores diferentes al ejecutar el mismo programa en computadores diferentes. Para evitar este problema,la representacion en coma flotante esta estandarizada por la organizacion IEEE (Institute of Electricaland Electronics Engineers) bajo el estandar IEEE-754. Actualmente todos los computadores cumplencon dicho estandar.

C.3.1 Problemas derivados de la representacion en coma flotante

Para representar numeros reales en un computador siempre usaremos una secuencia finita de bits. Enconsecuencia, nunca podremos representar todos los numeros. Al subconjunto de numeros que pode-mos representar en un formato coma flotante se le denomina rango de representacion.

Page 148: Introducción a la programación en c

139 C. Sistemas de numeracion

Debido a que el rango de representacion es finito, las operaciones aritmeticas no tienen las propie-dades habituales. Por ejemplo, la suma ya no es asociativa. Supongamos un formato de coma flotantedonde el numero maximo representable es el 8:0. Entonces tendremos que:

(8:0 + 2:0)� 4:0 6= 8:0 + (2:0� 4:0)

(8:0 + 2:0)� 4:0 = 10:0� 4:0 ) ERROR

8:0 + (2:0� 4:0) = 8:0� 2:0 = 6:0 ) CORRECTO

A esta situacion se la denomina overflow (desbordamiento por arriba), y ocurre cuando una opera-cion genera un numero mayor que el maximo representable. Ademas del overflow puede producirse elunderflow (desbordamiento por abajo) cuando el resultado de una operacion cae en la zona de numerossituados entre 0 y el numero mınimo representable.

En conclusion, al realizar calculos en coma flotante deberemos tener en cuenta el orden en el quese realizan las operaciones, para evitar las citadas situaciones de error.

Page 149: Introducción a la programación en c

C.3. Reales 140

Page 150: Introducción a la programación en c

141 D. Tabla de caracteres ASCII

Apendice D

Tabla de caracteres ASCII

Ademas de poder representar valores numericos, es preciso que un ordenador sea capaz de almacenarlos signos alfanumericos que usamos normalmente en el lenguaje humano. Para ello se usa un codigo de8 bits llamado codigo ASCII. Cada combinacion de este codigo representa un caracter. Los perifericos,como la pantalla o una impresora, son capaces de reconocer estos codigos y mostrar/imprimir el sımbolocorrespondiente.

Entre los caracteres representados ademas de los sımbolos que usamos para escribir (letras, cifras,signos de puntuacion, etc) tambien hay caracteres de control, necesarios para el funcionamiento de losperifericos. Por ejemplo, el caracter nn provoca un salto de lınea cuando es mostrado en una pantalla.

Originalmente el codigo ASCII solo usaba 7 bits para codificar caracteres, el octavo bit (bit de pa-ridad) se usaba para control de errores. Por lo tanto, el codigo ASCII original solo podıa representar128 = 27 caracteres diferentes. Actualmente, este control de errores no es necesario debido al perfec-cionamiento de los equipos, por ello el octavo bit puede ser usado tambien para codificar caracteres. Aeste codigo se le conoce como ASCII extendido y codifica 256 = 28 caracteres.

La tabla D.1 muestra los primeros 128 caracteres de acuerdo con la codificacion estandar ASCII.

Page 151: Introducción a la programación en c

142

Tabla D.1: Caracteres y sus codigos ASCII

ASCII char ASCII char ASCII char ASCII char0 NUL n0 32 SP ’ ’ 64 @ 96 ‘1 SOH 33 ! 65 A 97 a2 STX 34 ” 66 B 98 b3 ETX 35 # 67 C 99 c4 EOT 36 $ 68 D 100 d5 ENQ 37 % 69 E 101 e6 ACK 38 & 70 F 102 f7 BEL na 39 0 71 G 103 g8 BS nb 40 ( 72 H 104 h9 HT nt 41 ) 73 I 105 i10 LF nn 42 � 74 J 106 j11 VT nv 43 + 75 K 107 k12 FF nf 44 , 76 L 108 l13 CR nr 45 � 77 M 109 m14 SO 46 . 78 N 110 n15 SI 47 / 79 O 111 o16 DLE 48 0 80 P 112 p17 DC1 49 1 81 Q 113 q18 DC2 50 2 82 R 114 r19 DC3 51 3 83 S 115 s20 DC4 52 4 84 T 116 t21 NAK 53 5 85 U 117 u22 SYN 54 6 86 V 118 v23 ETB 55 7 87 W 119 w24 CAN 56 8 88 X 120 x25 EM 57 9 89 Y 121 y26 SUB 58 : 90 Z 122 z27 ESC 59 ; 91 [ 123 f

28 FS 60 ¡ 92 n 124 —29 GS 61 = 93 ] 125 g

30 RS 62 ¿ 94 ^ 126 �

31 US 63 ? 95 127 DEL

Page 152: Introducción a la programación en c

143 E. Bibliografıa y recursos WEB

Apendice E

Bibliografıa y recursos WEB

El Lenguaje de programacion CBrian W. Kernighan y Dennis M. RitchiePrentice Hall, 1992, segunda edicion.http://cm.bell-labs.com/cm/cs/cbook/index.html

De obligada referencia, es el primer libro sobre lenguaje C. Uno de los autores, DennisM. Ritchie, es uno de los creadores del lenguaje. Incluye la definicion oficial de C y ungran numero de ejemplos interesantes, aunque en algunos aspectos el material presentadoes obsoleto. Presupone que el lector esta familiarizado con la programacion de sistemas.

Programacion en CByron GottfriedMc Graw Hill, 1997, segunda edicion.

Extenso y eshaustivo libro con multitud de ejemplos detallados. Esta estructurado comoun libro de texto, por lo que contiene gran cantidad de ejercicios, cuestiones de repaso, etc.Para principiantes.

The Annotated ANSI C StandardHerbert SchildtOsborne - Mc Graw Hill, 1993.

El estandar ANCI C comentado y anotado por Herbert Shildt, miembro observador delcomite encargado de desarrollar el estandar. ANSI C determina las reglas fundamentalesque todo programador en C deberıa observar para crear programas funcionales y portables.Para lectores avanzados.

C by exampleGreg M. PerryQue Corporation, 1993.

Interesante libro que ensena a programar en C, construyendo programas paso a paso, desdeel primer momento. Separa los conceptos complicados del lenguaje en varios capıtuloscortos, de facil asimilacion. Incluye gran cantidad de ejercicios. Para principiantes.

Page 153: Introducción a la programación en c

144

The Development of the C LanguageDennis M. RitchieHistory of Programming Languages Conference (HOPL-II), 1993.http://cm.bell-labs.com/cm/cs/who/dmr/chist.html

Interesante artıculo de divulgacion que describe la historia de la creacion del lenguaje C,alla por los anos 70.

WEB: Programming in Chttp://www.lysator.liu.se/c/index.html

Un sitio WEB muy interesante. Incluye multitud de enlaces a recursos en Internet sobreprogramacion en C, historia del lenguaje, curiosidades, etc.

WEB: Frequently Asked Questions about Chttp://www.eskimo.com/ scs/C-faq/top.html

En este sitio WEB encontraremos una recopilacion de las preguntas mas habituales sobreprogramacion en lenguaje C del grupo de noticias en Internet comp.lang.c .