You are on page 1of 159

Apuntes de la asignatura: Fundamentos de inform atica

Jos e Daniel Mu noz Fr as Rafael Palacios Hielscher

2 de abril de 2002

Indice general
1. Descripci on del ordenador 1.1. Introducci on . . . . . . . . . . . . . . . . . . . . . 1.2. Arquitectura de un ordenador de prop osito general 1.2.1. La unidad central de proceso . . . . . . . . 1.2.2. Memoria . . . . . . . . . . . . . . . . . . 1.2.3. Unidad de entrada/salida . . . . . . . . . . 1.2.4. Buses . . . . . . . . . . . . . . . . . . . . 1.3. Codicaci on de la informaci on . . . . . . . . . . . 1.3.1. Sistemas de numeraci on posicionales . . . 1.3.2. Codicaci on de n umeros en un ordenador . 1.3.3. Codicaci on de caracteres alfanum ericos . 1.4. Codicaci on del programa . . . . . . . . . . . . . 1.4.1. Compiladores e int erpretes . . . . . . . . . 2. El primer programa en C 2.1. Introducci on . . . . . . . . . . . . . . . . . . . . 2.2. El sistema operativo . . . . . . . . . . . . . . . . 2.3. Creaci on de un programa en C . . . . . . . . . . 2.3.1. Primer paso: Edici on del programa fuente 2.3.2. Segundo paso: Compilaci on . . . . . . . 2.3.3. Tercer paso: Enlazado . . . . . . . . . . 2.4. Nuestro primer programa en C . . . . . . . . . . 2.4.1. Comentarios . . . . . . . . . . . . . . . 2.4.2. Directivas del preprocesador . . . . . . . 2.4.3. La funci on principal . . . . . . . . . . . 2.4.4. Las funciones . . . . . . . . . . . . . . . 2.4.5. Finalizaci on del programa . . . . . . . . 2.4.6. La importancia de la estructura . . . . . . 3. Tipos de datos 3.1. Introducci on . . . . . . . . . 3.2. Variables . . . . . . . . . . 3.3. Tipos b asicos de variables . 3.3.1. N umeros enteros . . 3.3.2. N umeros reales . . . 3.3.3. Variables de car acter 3.4. M as tipos de variables . . . . 3.5. Constantes . . . . . . . . . . 7 7 7 8 8 8 8 9 9 11 12 14 14 17 17 17 17 18 18 19 19 20 20 21 22 22 22 25 25 25 26 26 26 27 27 27

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . 3

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

4 3.6. Tama no de las variables . . . . . . . . . . . . 3.7. Escritura de variables en pantalla . . . . . . . 3.7.1. Escritura de variables de tipo entero . 3.7.2. Escritura de variables de tipo real . . 3.7.3. Escritura de variables de tipo car acter 3.7.4. Escritura de variables con formato . . 3.8. Lectura de variables por teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

INDICE GENERAL
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 30 31 32 32 33 35 35 35 35 36 37 37 37 38 38 39 40 43 43 44 45 46 49 49 50 50 53 53 55 56 56 57 59 59 59 61 61 62 63 63 66

4. Operadores en C 4.1. Introducci on . . . . . . . . . . . . . . . . . . . . 4.2. El operador de asignaci on . . . . . . . . . . . . . 4.3. Operadores para n umeros reales . . . . . . . . . 4.4. Operadores para n umeros enteros . . . . . . . . . 4.4.1. Operador resto . . . . . . . . . . . . . . 4.4.2. Operadores de incremento y decremento . 4.5. Operador unario - . . . . . . . . . . . . . . . . . 4.6. De vuelta con el operador de asignaci on . . . . . 4.7. Conversiones de tipos . . . . . . . . . . . . . . . 4.7.1. El operador asignaci on y las conversiones 4.8. Ejemplos . . . . . . . . . . . . . . . . . . . . . 5. Control de ujo: for 5.1. Introducci on . . . . . . . . . . 5.2. Sintaxis del bucle for . . . . . 5.2.1. Operadores relaciones 5.3. Doble bucle for . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

6. Control de ujo: while, do-while 6.1. Introducci on . . . . . . . . . . . . . . 6.2. Sintaxis del bucle while . . . . . . . 6.2.1. Ejemplos . . . . . . . . . . . 6.3. Sintaxis del bucle do-while. . . . . . 6.3.1. Ejemplos . . . . . . . . . . . 6.4. Es el bucle for igual que el while? . 6.4.1. Entonces cu al elijo? . . . . . 6.5. El ndice del bucle for sirve para algo 6.6. Ejercicios . . . . . . . . . . . . . . . 7. Control de ujo: if 7.1. Introducci on . . . . . . . . . . . 7.2. Sintaxis del bloque if . . . . . . 7.3. Formato de las condiciones . . . 7.3.1. Operadores relacionales 7.3.2. Operadores l ogicos . . . 7.4. Valores de verdadero y falso . . 7.5. Bloque if else-if . . . . . . 7.6. Ejercicios . . . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

INDICE GENERAL
8. Control de ujo: switch-case, break y continue 8.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . 8.2. La construcci on switch-case . . . . . . . . . . . . 8.2.1. Introducci on . . . . . . . . . . . . . . . . . 8.2.2. Sintaxis de la construcci on switch-case . . 8.2.3. Ejemplos . . . . . . . . . . . . . . . . . . . 8.3. La sentencia break . . . . . . . . . . . . . . . . . . 8.3.1. La sentencia break y el switch-case . . . . 8.3.2. La sentencia break y los bucles . . . . . . . 8.3.3. Ejemplos . . . . . . . . . . . . . . . . . . . 8.4. La sentencia continue . . . . . . . . . . . . . . . . 8.4.1. Ejemplos . . . . . . . . . . . . . . . . . . . 8.5. Ejercicios . . . . . . . . . . . . . . . . . . . . . . .

5 67 67 67 67 68 69 73 73 74 74 76 77 79 81 81 81 83 84 86 92 92 95 97 97 98 99 100 102 104 105 105 105 106 108 108 111 111 113 113 113 114 115 116 117 119 120 122

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

9. Vectores y Matrices 9.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2. Vectores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3. Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . 9.3.1. Cadenas de caracteres de longitud variable . . . . . . 9.3.2. Funciones est andar para manejar cadenas de caracteres 9.4. Matrices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5. Utilizaci on de #define con vectores y matrices . . . . . . . . 9.6. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10. Funciones 10.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . 10.2. Estructura de una funci on . . . . . . . . . . . . . . . 10.3. Prototipo de una funci on . . . . . . . . . . . . . . . 10.4. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . 10.5. Paso de argumentos a las funciones. Variables locales 10.6. Salida de una funci on y retorno de valores . . . . . . 10.7. Funciones sin argumentos . . . . . . . . . . . . . . . 10.8. Ejemplos . . . . . . . . . . . . . . . . . . . . . . . 10.8.1. Elevar al cuadrado . . . . . . . . . . . . . . 10.8.2. Factorial . . . . . . . . . . . . . . . . . . . 10.9. Paso de vectores, cadenas y matrices a funciones . . 10.9.1. Paso de vectores . . . . . . . . . . . . . . . 10.9.2. Paso de cadenas a funciones . . . . . . . . . 10.9.3. Paso de matrices a funciones . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

. . . . . . . . . . . . . .

11. Punteros 11.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2. Declaraci on e inicializaci on de punteros . . . . . . . . . . . 11.2.1. El operador unario & . . . . . . . . . . . . . . . . . 11.2.2. El operador unario * . . . . . . . . . . . . . . . . . 11.3. Operaciones con punteros . . . . . . . . . . . . . . . . . . . 11.4. Punteros y vectores . . . . . . . . . . . . . . . . . . . . . . 11.4.1. Equivalencia de punteros y vectores . . . . . . . . . 11.5. Punteros y funciones . . . . . . . . . . . . . . . . . . . . . 11.5.1. Retorno de m as de un valor por parte de una funci on

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

6 11.6. Asignaci on din amica de memoria . . . . . . 11.6.1. Las funciones calloc() y malloc() 11.6.2. La funci on free() . . . . . . . . . . 11.6.3. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

INDICE GENERAL
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 123 124 125 129 129 129 131 132 132 132 134 138 141 141 141 142 143 144 145 146 147 148 149 152 152 153 154 156

12. Archivos 12.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2. Apertura de archivos. La funci on fopen() . . . . . . . . . . . . . . . . 12.3. Cierre de archivos. La funci on fclose(). . . . . . . . . . . . . . . . . 12.4. Lectura y escritura en archivos. Las funciones fprintf() y fscanf(). 12.4.1. La funci on fprintf() . . . . . . . . . . . . . . . . . . . . . . 12.4.2. La funci on fscanf() . . . . . . . . . . . . . . . . . . . . . . . 12.5. Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.6. Funciones de entrada y salida a archivo sin formato . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . .

13. Estructuras de datos 13.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2. Declaraci on y denici on de estructuras . . . . . . . . . . . . . . . . . . . . 13.3. La sentencia typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.4. Acceso a los miembros de una estructura . . . . . . . . . . . . . . . . . . . 13.5. Ejemplo: Suma de n umeros complejos . . . . . . . . . . . . . . . . . . . . 13.6. Estructuras y funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.7. Punteros a estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.7.1. Paso de punteros a estructuras a funciones . . . . . . . . . . . . . . 13.8. Vectores de estructuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.8.1. Ejemplo: C alculo del producto escalar de dos vectores de complejos 13.9. Estructuras y archivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.9.1. Diferencia entre archivos de texto y archivos binarios . . . . . . . . 13.9.2. Para almacenar estructuras en archivos de texto . . . . . . . . . . . 13.9.3. Para almacenar estructuras en archivos binarios . . . . . . . . . . . 13.9.4. Funciones fread, fwrite y fseek . . . . . . . . . . . . . . . . .

Cap tulo 1

Descripci on del ordenador


1.1. Introducci on
Un ordenador es un equipo de procesamiento de datos. Su tarea consiste en, a partir de unos datos de entrada, generar otros datos de salida, tal como se muestra en la gura 1.1.a. La principal ventaja de un ordenador radica en que el procesamiento que realiza con los datos no es jo, sino que responde a un programa previamente introducido en e l, tal como se muestra en la gura 1.1.b. Esta propiedad dota a los ordenadores de una gran exibilidad, permitiendo que una misma m aquina sirva para nes tan dispares como dise nar circuitos electr onicos, resolver problemas matem aticos o, por qu e no, jugar a los marcianitos. N otese tambi en que debido a esto un ordenador sin un programa que lo controle es un cacharro totalmente in util. El objetivo de esta asignatura es ense nar al alumnos los fundamentos del proceso de programaci on de un ordenador usando el lenguaje C.
Programa

Datos de Entrada

Ordenador
a

Datos de Salida

Datos de Entrada

Ordenador
b

Datos de Salida

Figura 1.1: Proceso de informaci on en un ordenador

1.2. Arquitectura de un ordenador de prop osito general


En la gura 1.2 se muestra un diagrama de bloques de un ordenador de prop osito general, en el que se pueden observar tres bloques principales: la Unidad Central de proceso o CPU 1 , la memoria y el sistema de entrada/salida. Todos estos elementos se comunican mediante un conjunto de conexiones el ectricas denominadas buses.
1 Del

ingl es Central Processing Unit.

DEL ORDENADOR CAPITULO 1. DESCRIPCION


Bus de direcciones Bus de Datos Discos

C.P.U.

Memoria

E/S

Pantalla

Bus de control

Figura 1.2: Diagrama de bloques de un ordenador

1.2.1. La unidad central de proceso


La unidad central de proceso, tambi en llamada CPU o simplemente procesador, es la unidad responsable de realizar todo el procesamiento de informaci on. Para ello lee un programa de la memoria y act ua seg un las instrucciones de dicho programa. Dichas instrucciones son muy simples: leer datos de la memoria, realizar operaciones matem aticas y l ogicas simples (sumas, comparaciones. . . ), escribir resultados en la memoria, etc.

1.2.2. Memoria
Es la unidad encargada de almacenar tanto el programa que le dice a la CPU lo que tiene que hacer, como los datos con los que tiene que trabajar. Desde el punto de vista del programador consiste en un vector de posiciones de memoria a las cuales se puede acceder (leer o n. escribir) sin m as que especicar su direccio

1.2.3. Unidad de entrada/salida


Esta unidad se encarga de comunicar al ordenador con el mundo exterior y con los dispositivos de almacenamiento secundario (discos). En algunas arquitecturas aparece como una serie de posiciones de memoria m as, indistinguibles por el programador de la memoria normal, y en otras est a en un espacio de direcciones separado.

1.2.4. Buses
La interconexi on entre los elementos del ordenador se realiza mediante un bus de datos, gracias al cual el procesador lee o escribe datos en el resto de dispositivos 2 , un bus de direcciones por el cual el procesador indica a los dispositivos qu e posici on quiere leer o escribir 3 , un bus de control mediante el cual el procesador les indica el momento en el que va a realizar el acceso, si e ste va a ser de lectura o escritura, etc. Este bus de control tambi en permite a los
2 Se dice entonces que este bus es bidireccional, pues permite que la informaci on viaje desde el procesador a los dispositivos o viceversa. 3 Este bus en cambio es unidireccional, pues la direcci on viaja siempre del procesador a los dispositivos.

DE LA INFORMACION 1.3. CODIFICACION

dispositivos pedir la atenci on del procesador ante un suceso mediante un mecanismo llamado interrupci on.

1.3. Codicaci on de la informaci on


Todas las unidades estudiadas en la Secci on 1.2 est an formadas por circuitos electr o-nicos digitales que se comunican entre s . Como aprender an en la asignatura de electr onica digital, estos circuitos trabajan con se nales que toman s olo dos valores: encendido-apagado, cargadodescargado, tensi on alta-tensi on baja. A estos dos estados diferenciados se les asignan los valores binarios 0 y 1. Por tanto, dentro de un ordenador, todo discurre en forma de d gitos binarios o bits4 . Por el contrario en la vida real casi ning un problema est a basado en n umeros binarios, y la mayor a ni siquiera en n umeros. Por tanto es necesario establecer una correspondencia entre las magnitudes binarias con las que trabaja el ordenador y las magnitudes n. que existen en el mundo real. A esta correspondencia se le denomina codicaci o Si las operaciones a realizar son funciones l ogicas (AND, OR, NOT. . . ) la codicaci on es muy simple: al valor falso se le asigna el d gito binario 0 y a la condici on cierto se le asigna el 1. Si en cambio hemos de realizar operaciones matem aticas, la codicaci on de n umeros es un poco m as compleja. Antes de estudiar c omo se codican los n umeros en un ordenador, vamos a estudiar un poco de matem aticas (pero que no cunda el p anico!).

1.3.1. Sistemas de numeraci on posicionales


Al sistema de numeraci on que usamos d a a d a se le denomina posicional porque cada n umero est a formado por una serie de d gitos de forma que cada d gito tiene un peso en funci on de su posici on. El valor del n umero se forma por la suma del producto de cada d gito por su peso, as por ejemplo cuando se escribe el n umero 1327, en realidad se esta diciendo: 1327

1000

100

10

103

102

101

100 (1.1)

Como se puede apreciar, los pesos por los que se multiplica cada d gito se forman elevando un n umero, al que se denomina base a la potencia indicada por la posici on del d gito. Al d gito que est a m as a la derecha se le denomina d gito menos signicativo y se le asigna la posici on s signicativo. 0; al que est a m as a la izquierda se le denomina d gito ma Como en un sistema digital s olo existen dos d gitos binarios, 0 y 1, los n umeros dentro de un ordenador han de representarse en base 2 5 , as por ejemplo6 : 110112

24

23

22

21

20

16

27

(1.2)

Esta base es muy adecuada para las m aquinas, pero bastante desagradable para los humanos, por ejemplo el n umero 1327 en binario es 10100101111 2; bastante m as dif cil de recordar, escribir e interpretar. Por ello los humanos, que para eso somos m as listos que las m aquinas, usamos sistemas de numeraci on m as aptos a nuestras facultades. La base 10 es la
palabra bit viene del ingl es binary digit. representar n umeros en una base n hacen falta n s mbolos distintos. 6 Para indicar n umeros representados en una base distinta a la base 10, se pondr a en estos apuntes la base como sub ndice.
5 Para 4 La

10

DEL ORDENADOR CAPITULO 1. DESCRIPCION

m as c omoda para operar con ella debido a que las potencias de la base son f aciles de calcular y a que la suma expresada en (1.1) se calcula sin ninguna dicultad. Por el contrario en base 2 ni las potencias de la base ni la suma nal (1.2) son tan f aciles de calcular. Uno de los inconvenientes de la representaci on de n umeros en binario es su excesiva longitud. Para simplicar las cosas se pueden convertir los n umeros binarios a decimales y trabajar con ellos, pero eso no es tarea f acil, pues hay que realizar una operaci on como la indicada en (1.2). Para solucionar este problema, se pueden usar bases que son tambi en potencias de 2, de forma que la notaci on sea compacta y f acil de manejar por los humanos y que adem as las conversiones a binario sean f aciles de realizar. Las dos bases m as usadas son la octal (base 8) y la hexadecimal (base 16). En la tabla 1.1 se muestran las equivalencias entre binario, decimal, octal y hexadecimal. Cuadro 1.1: Conversi on entre bases Binario 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 10000 10001 Decimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Octal 0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 Hexadecimal 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11

Como se puede apreciar, los n umeros octales s olo usan 8 s mbolos (del 0 al 7) y la base hexadecimal precisa 16 (del 0 al 9 y se a naden las letras de la A a F). La conversi on entre binario y octal se realiza agrupando los bits en grupos de tres y realizando la transformaci on de cada grupo por separado, seg un la tabla 1.1: 101001011112

10 100 101 111


24578

y entre binario y hexadecimal se hace igual, pero agrupando los bits de cuatro en cuatro: 101001011112

101 0010 1111


52F16

La notaci on octal se us o en los ordenadores primitivos, en los que el ordenador comunicaba los resultados con lamparitas (como salen en las pel culas) que se agrupaban en grupos de 3 para su interpretaci on en base octal. Sin embargo, con el avance de la t ecnica, se vio que era conveniente usar n umeros binarios de 8 bits, a los que se les denomin o bytes. Estos bytes se representan mucho mejor como dos d gitos hexadecimales, uno para cada grupo de 4 bits7 . El caso es que hoy en d a la notaci on octal est a francamente en desuso, siendo lo m as
7 adem as

a cada grupo de 4 bits, representable directamente por un d gito hexadecimal, se le denomina nible.

DE LA INFORMACION 1.3. CODIFICACION

11

corriente expresar un n umero binario mediante su equivalente hexadecimal cuando ha de ser interpretado por un ser humano (dentro del ordenador por supuesto todo esta en binario).

1.3.2. Codicaci on de numeros en un ordenador


Una de las aplicaciones principales de los ordenadores es la realizaci on de c alculos matem aticos, c alculos que pueden ser tan simples como restar las 1.000 pesetas de una cuenta bancaria cuando se sacan de un cajero o tan complejos como una simulaci on de la combusti on en el cilindro de un motor de explosi on. Sin embargo se ha visto que los ordenadores s olo saben usar el 0 y el 1. Por tanto es necesario buscar m etodos para codicar los n umeros en un ordenador. La tarea no es tan f acil como pueda parecer a primera vista. En primer lugar cuantos tipos de n umeros existen? Pues tenemos en primer lugar los n umeros naturales, que son los m as simples. Si usamos restas nos hacen falta los n umeros negativos. Si queremos dividir, salvo que tengamos mucha suerte y la divisi on sea exacta, hemos de usar los n umeros racionales. Si adem as tenemos que hacer ra ces cuadradas o usar n umeros tan apasionantes como el , nos las tendremos que ver con los irracionales. Incluso si se nos ocurre hacer una ra z cuadrada de un n umero negativo tenemos que introducirnos en las maldades de los n umeros imaginarios. Los lenguajes de programaci on de prop osito general como el C o el Pascal, permiten trabajar tanto con n umeros enteros como con n umeros reales. Otros lenguajes como el FORTRAN, m as orientados al c alculo cient co, tambi en pueden trabajar con n umeros imaginarios.

Codicaci on de numeros enteros Si se desea codicar un n umero natural, la manera m as directa es usar su representaci on en binario, tal como se ha visto en la Secci on 1.3.1. No obstante, los procesadores trabajan con magnitudes de un n umero de bits predeterminados, t picamente m ultiplos pares de un byte. As por ejemplo los procesadores tipo Pentium pueden trabajar con n umeros de 1, 2 y 4 bytes, o lo que es lo mismo, de 8, 16 y 32 bits. Por tanto, antes de introducir un n umero natural en el ordenador, hemos de averiguar el n umero de bits que hacen falta para codicarlo, y luego decirle al ordenador que use un tipo de almacenamiento adecuado. Para averiguar cuantos bits hacen falta para almacenar un n umero natural no hace falta convertirlo a binario. Si nos jamos en que con n bits se pueden representar n umeros naturales que van desde 0 a 2n 1, s olo hace falta comparar el n umero a codicar con el rango del tipo en el que lo queremos codicar. As por ejemplo si queremos almacenar el n umero 327 en 8 bits, como el rango de este tipo es de 0 a 28 1 255, vemos que en 8 bits no cabe, por tanto hemos de almacenarlo en 16 bits, que al tener un rango de 0 a 2 16 1 65535 nos vale perfectamente. Para la codicaci on de n umeros negativos existen varios m etodos, aunque el m as usado es la codicaci on en complemento a dos, que posee la ventaja de que la operaci on suma es la misma que para los n umeros positivos. En esta codicaci on el bit m as signicativo vale cero para los n umeros positivos y uno para los negativos. En el resto de bits est a codicada la magnitud, aunque el m etodo de codicaci on se escapa de los nes de esta introducci on. Lo que si ha de quedar claro es que como ahora tenemos en n bits n umeros positivos y negativos, el rango total de 0 a 2n 1 se divide ahora en 2n 1 a 2n 1 1. As pues si por ejemplo queremos codicar el n umero -227 necesitaremos 16 bits, pues el rango de un n umero codicado en complemento a dos de 8 bits es de 27 a 27 1, es decir, de 128 a 127. En cambio el rango disponible en 16 bits es de 32768 a 32767.

12 Codicaci on de numeros reales

DEL ORDENADOR CAPITULO 1. DESCRIPCION

Para la mayor a de las aplicaciones de c alculo cient co se necesita trabajar con n umeros reales. Estos n umeros reales se representan seg un una mantisa de parte entera 0 y un exponente (por ejemplo 3.701 se almacena como 0 3701 10 1). Nuevamente tanto la mantisa como el exponente se almacenan en binario, seg un una codicaci on que escapa de los nes de esta breve introducci on. Antiguamente cada ordenador usaba un tipo de codicaci on en punto otante distinta. Por ello surgi o la necesidad de buscar un est andar de almacenamiento com un para todas las plataformas. De ah surgi o el est andar IEEE 854 8 que es el usado actualmente por la mayor a de los procesadores para la realizaci on de las operaciones en coma otante. Este est andar dene tanto la codicaci on de los n umeros como la manera de realizar las operaciones aritm eticas con ellos (incluidos los redondeos).

1.3.3. Codicaci on de caracteres alfanum ericos


Aunque el proceso matem atico es una parte importante dentro de un ordenador, la mayor a de los problemas en el mundo real requieren la posibilidad de trabajar con texto. Por tanto es necesario buscar un m etodo para codicar los caracteres alfanum ericos dentro de un ordenador. La manera de realizar est a codicaci on es asignar a cada car acter un valor num erico, totalmente arbitrario, almacenando las correspondencias car acter-n umero en una tabla. Al principio existieron varias codicaciones de caracteres, pues los fabricantes, como siempre, no se pusieron de acuerdo. Por ello naci o el est andar ASCII que son las siglas de American Standard Code for Information Interchange. Como su propio nombre indica este est andar es americano, y como e stos son muy dados a creer que el mundo termina donde lo hacen sus fronteras, usaron s olo n umeros de 7 bits (0 a 127) para almacenar sus caracteres y no codicaron caracteres tan estupendos como la n o la a . Esto ha dado lugar a que, incluso hoy en d a, los que tenemos la desgracia de llamarnos Mu noz, nos vemos con frecuencia atica situaci on, se extendieron los 7 bits origrebautizados a Mu oz. Para solucionar tan dram inales a 8, usando los 128 nuevos caracteres para codicar los caracteres de las lenguas no inglesas. El problema fue que nuevamente no se pusieron de acuerdo los fabricantes y cada uno lo extend a a su manera, con el consiguiente problema. Para aliviar esto, surgi o el est andar ISO 88599, que en los 128 primeros caracteres coincide con el ASCII y en los restantes 128 contiene extensiones para la mayor a de los lenguajes. Como no caben todos los caracteres de todas las lenguas en s olo 128 posiciones, se han creado 10 alfabetos ISO 8859, de los cuales el primero (ISO 8859-1 Latin-1) contiene los caracteres necesarios para codicar los caracteres de las lenguas usadas en Europa occidental. A continuaci on se muestra una tabla ASCII tomada de un sistema Unix:

Oct Dec Hex Char Oct Dec Hex Char -----------------------------------------------------------000 0 00 NUL \0 100 64 40 @ 001 1 01 SOH 101 65 41 A 002 2 02 STX 102 66 42 B 003 3 03 ETX 103 67 43 C 004 4 04 EOT 104 68 44 D
8 IEEE son las siglas del Institute of Electrical and Electronics Engineers, que es una asociaci on americana de ingenieros que se dedica entre otras tareas a la denici on de est andares relacionados con la ingenier a el ectrica y electr onica. 9 ISO son las siglas de International Standars Organization.

DE LA INFORMACION 1.3. CODIFICACION


005 006 007 010 011 012 013 014 015 016 017 020 021 022 023 024 025 026 027 030 031 032 033 034 035 036 037 040 041 042 043 044 045 046 047 050 051 052 053 054 055 056 057 060 061 062 063 064 065 066 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 ENQ ACK BEL \a BS \b HT \t LF \n VT \v FF \f CR \r SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US SPACE ! " # $ % & ( ) * + , . / 0 1 2 3 4 5 6 105 106 107 110 111 112 113 114 115 116 117 120 121 122 123 124 125 126 127 130 131 132 133 134 135 136 137 140 141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160 161 162 163 164 165 166 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] _ a b c d e f g h i j k l m n o p q r s t u v

13

\\

14 067 070 071 072 073 074 075 076 077 55 56 57 58 59 60 61 62 63 37 38 39 3A 3B 3C 3D 3E 3F 7 8 9 : ; < = > ?

DEL ORDENADOR CAPITULO 1. DESCRIPCION


167 170 171 172 173 174 175 176 177 119 120 121 122 123 124 125 126 127 77 78 79 7A 7B 7C 7D 7E 7F w x y z { | } DEL

Por ejemplo el car acter a se codica como el n umero 97 (01100001 en binario) y el car acter A se codica como 65 (00100001 en binario). Tambi en cabe destacar que el orden num erico dentro de la tabla coincide con el orden alfab etico de las letras, lo cual es muy u til cuando se desean ordenar caracteres por orden alfab etico.

1.4. Codicaci on del programa


En la secci on 1.1 se dijo que para que un ordenador realice cualquier tarea, es necesario decirle c omo debe realizarla. Esto se hace mediante un programa, el cual est a compuesto por instrucciones muy simples. Estas instrucciones tambi en est an codicadas (en binario por supuesto), seg un el denominado juego de instrucciones del procesador. Cada procesador tiene su propio juego de instrucciones y su propia manera de codicarlas, que dependen de la arquitectura interna del procesador. As por ejemplo en un microprocesador 68000 de Motorola la instrucci on para sumar 7 a una posici on de memoria indicada por el registro interno A2 es 0101111010010010. En los primeros ordenadores los programas se codicaban directamente en binario, lo cual era muy penoso y propenso a errores. Por suerte los ordenadores de entonces no eran muy potentes y los programas por tanto no eran demasiado sosticados. Aun as , como al hombre nunca le han gustado las tareas repetitivas, pronto se vio la necesidad de automatizar un poco nico f el proceso. Para ello a cada instrucci on se le asigna un mnemo acil de recordar por el hombre y se deja a un programa la tarea de convertir estos mnem onicos a sus equivalentes en binario legibles por el procesador. El ejemplo de antes se escribir a ahora como ADDQ.B #7,(A2), lo cual es mucho m as legible. A este lenguaje se le denomina lenguaje ensamblador, aunque a veces tambi en se le denomina c odigo maquina. El problema de la programaci on en ensamblador es que este lenguaje es distinto para cada procesador, pues esta muy unido a su arquitectura interna. Por ello si tenemos un programa escrito en ensamblador para un 68000 y decidimos ejecutarlo en un Pentium, tendremos que reescribirlo de nuevo. Para solucionar este problema (o al menos paliarlo bastante), se desarrollaron los lenguajes de alto nivel. Estos lenguajes parten de una arquitectura de procesador gen erica, como la que se ha introducido en este cap tulo, y denen un lenguaje independiente del procesador, por lo que una vez escrito el programa, e ste se puede ejecutar pr acticamente sin cambios en cualquier procesador.

1.4.1. Compiladores e int erpretes


En un lenguaje de alto nivel es necesario traducir el programa que introduce el usuario al c odigo m aquina del ordenador. Esta traducci on se puede realizar al mismo tiempo de la ejecuci on, de forma que se traduce cada l nea del programa de alto nivel y despu es se ejecuta.

DEL PROGRAMA 1.4. CODIFICACION

15

Como ejemplos de lenguajes que son normalmente interpretados tenemos el BASIC y el Java. El principal inconveniente de los lenguajes interpretados es que el proceso de traducci on lleva tiempo, y hay que realizarlo una y otra vez. Si se desea eliminar el tiempo de traducci on del programa de alto nivel, podemos traducirlo todo de una vez y generar un programa en c odigo m aquina que ser a ejecutable directamente por el procesador. Con ello la ejecuci on del programa ser a mucho m as r apida, pero tiene como inconveniente que cada vez que se cambia algo en el programa hay que volver a traducirlo a c odigo m aquina. Al proceso de traducci on del programa a c odigo m aquina se le denomina compilaci on, y al programa encargado de ello compilador. Ejemplos de lenguajes que son normalmente compilados son el C, el Pascal o el FORTRAN.

16

DEL ORDENADOR CAPITULO 1. DESCRIPCION

Cap tulo 2

El primer programa en C
2.1. Introducci on
Una vez descrito el funcionamiento b asico de un ordenador, vamos a realizar nuestro primer programa en lenguaje C. Veremos en este cap tulo las herramientas necesarias para crear programas, almacenarlos, compilarlos y ejecutarlos.

2.2. El sistema operativo


En el cap tulo anterior se ha descrito muy someramente el funcionamiento del ordenador. Se vio que exist an unidades de entrada salida como el teclado o la pantalla y unidades de almacenamiento secundario como discos o CDROM. El manejo de estos dispositivos es altamente complejo, sobre todo para los programadores noveles, y adem as est a estrechamente ligado al funcionamiento f sico de los dispositivos, por lo que si se cambia el dispositivo, var a la forma de usarlo. Para facilitar la vida al programador, todas las tareas sucias del ordenador como son entre otras la gesti on de la pantalla, teclado o accesos a discos las realiza el sistema operativo. Para ello, los sistemas operativos poseen una serie de funciones que hacen de interfaz entre el programador y el sistema, que se denominan interfaz del programador de aplicaciones, y com unmente se conoce con las siglas inglesas API 1 . El sistema operativo tambi en se encarga de interpretar las o rdenes que el usuario le da, bien mediante una interfaz de comandos como en MS-DOS o UNIX o bien mediante una interfaz gr aca como en Windows 95 o en X Window System. Esto permite al usuario decirle al sistema que ejecute un programa, que borre un archivo, que lo copie, que se conecte a Internet. . .

2.3. Creaci on de un programa en C


El proceso de creaci on de un programa en C, ilustrado en la gura 2.1, consta de los siguientes pasos:
1 De

Application Programmer Interface

17

18

CAPITULO 2. EL PRIMER PROGRAMA EN C

Figura 2.1: Compilaci on de un programa. Tomado de [Antonakos and Manseld, 1997].

2.3.1. Primer paso: Edici on del programa fuente


El primer paso a realizar para la creaci on de un programa es escribirlo. Para ello se necesita una herramienta llamada editor de texto, como por ejemplo el edit de MSDOS, el notepad de Windows o el vi de UNIX. Todos estos programas permiten al usuario introducir un texto en el ordenador, modicarlo y luego guardarlo en un archivo en el disco duro para su posterior recuperaci on o para que sea usado por otros programas (como por ejemplo el compilador). digo fuente. Al archivo creado se le denomina programa fuente o tambi en co T picamente se emplea una extensi on al nombre del archivo para indicar su contenido. En el caso de archivos que contienen c odigo fuente en C, el nombre ha de tener extensi on .c. Por ejemplo en la gura 2.1 el c odigo fuente que se compila se llama MYPROG.C.

2.3.2. Segundo paso: Compilaci on


Una vez creado el programa fuente es necesario traducirlo. De ello se encarga un programa llamado compilador, el cual tomando como entrada el programa fuente y los cheros cabecera (normalmente con extensi on .h), los traduce a c odigo m aquina creando lo que se

2.4. NUESTRO PRIMER PROGRAMA EN C

19

denomina c odigo objeto (con extensi on .obj o .o). Si existe alg un error sint actico en el c odigo fuente el compilador generar a un mensaje de error indicando la l nea en la que encontr o el problema y dici endonos la causa del error. En la mayor a de los casos el error estar a en la l nea indicada, aunque puede estar en l neas anteriores. Si el programa compila sin errores, podemos pasar a la siguiente fase. Si no, habr a que volver a editar el programa fuente para corregir los errores y repetir el proceso hasta que el compilador termine su tarea con e xito.

2.3.3. Tercer paso: Enlazado


Los programas en C usan siempre funciones de prop osito general que est an almacenadas en una biblioteca. Ejemplos de estas funciones son las de impresi on en pantalla, lectura del teclado, matem aticas. . . Ahora bien, si hacemos uso de una de estas funciones es necesario incluirla en nuestro programa nal. De esto se encarga un tercer programa llamado enlazador (linker en ingl es), que busca en el c odigo objeto las referencias a funciones que llamamos pero que no hemos realizado nosotros, las localiza en las bibliotecas de funciones y las enlaza con nuestro programa. El resultado nal es un programa ejecutable que contiene todo el c odigo necesario para que el procesador realice lo que le hemos indicado en nuestro programa fuente. Una vez realizado este paso podemos ejecutar el programa y comprobar si lo que hace es lo que realmente queremos. Si no es as , se habr a producido lo que se denomina un error de ejecuci on y habr a que volver al primer paso para corregir nuestro programa fuente y repetir el proceso: edici on, compilaci on, enlace y ejecuci on, hasta que el programa haga lo que realmente queremos.

2.4. Nuestro primer programa en C


Una vez descrito todo el proceso vamos a realizar nuestro primer programa en C. El programa es muy simple: se limita a escribir un mensaje en la pantalla del ordenador. A pesar de esto contiene la mayor a de los elementos del lenguaje. El programa es: /* Programa: Hola * * Descripci on: Escribe un mensaje en la pantalla del ordenador * * Revisi on 0.0: 16/02/1998 * * Autor: Jos e Daniel Mu noz Fr as */ #include <stdio.h> /* Declara las funciones de entrada-salida est andar */ main(void) { printf("Hola!\n"); /* Imprimo el mensaje */ } Veamos a continuaci on cada una de las partes que componen el programa:

20

CAPITULO 2. EL PRIMER PROGRAMA EN C

2.4.1. Comentarios
En primer lugar vemos las l neas: /* Programa: Hola * * Descripci on: Escribe un mensaje en la pantalla del ordenador * * Revisi on 0.0: 16/02/1998 * * Autor: Jos e Daniel Mu noz Fr as */ que forman la cha del programa. La nalidad de esta cha es la documentaci on del programa, de forma que cualquier persona sepa el nombre, la nalidad, la revisi on y el autor del programa sin m as que leer el principio del archivo del c odigo fuente. Si observamos m as detenidamente las l neas anteriores veremos que comienzan por /* y terminan por */ (los dem as * se han colocado con nes decorativos). En C todo el texto encerrado entre /* y */ se denomina comentario y es ignorado por el compilador, de forma que el programador pueda escribir lo que quiera con nes de documentaci on del c odigo. Esto, que puede parecer una tonter a en un programa tan simple como el mostrado en esta secci on, es fundamental cuando se abordan programas m as grandes. Existen dos tipos de comentarios2: los que acabamos de ver, tipo cha del programa, y los que se insertan en el c odigo para aclarar operaciones que no sean obvias. Ejemplos de este tipo de comentarios son: andar */ #include <stdio.h> /* Declara las funciones de entrada-salida est printf("Hola!\n"); /* Imprimo el mensaje */ En este caso, al ser el primer programa que realizamos, los comentarios incluidos son obvios, habi endose a nadido simplemente con nes ilustrativos.

2.4.2. Directivas del preprocesador


Todas las l neas que comienzan por el car acter # son directivas del preprocesador de C. Este preprocesador es una parte del compilador que se encarga de realizar varias tareas para preparar nuestro archivo de c odigo fuente antes de realizar el proceso de compilaci on. Las directivas del preprocesador le dan instrucciones a e ste para que realice alg un tipo de proceso. As en la l nea: andar */ #include <stdio.h> /* Declara las funciones de entrada-salida est se le dice al preprocesador que incluya el chero cabecera stdio.h. Este chero ya ha sido creado por el programador del compilador, aunque ya veremos m as adelante que tambi en nosotros podemos incluir nuestros propios cheros cabecera. El proceso de inclusi on de cheros realizado por el preprocesador consiste en sustituir la l nea: #include <stdio.h> por el contenido del chero stdio.h. As , si suponemos que el contenido del chero stdio.h es:
2 Desde

el punto de vista del programador, pues para el compilador todos son iguales.

2.4. NUESTRO PRIMER PROGRAMA EN C


/* Este es el fichero stdio.h*/ el c odigo fuente despu es de pasar por el preprocesador queda como: /* Programa: Hola * * Descripci on: Escribe un mensaje en la pantalla del ordenador * * Revisi on 0.0: 16/02/1998 * * Autor: Jos e Daniel Mu noz Fr as */ /* Este es el fichero stdio.h*/ /* Declara las funciones de entrada-salida est andar */ main(void) { printf("Hola!\n"); /* Imprimo el mensaje */ }

21

La utilidad de incluir cheros es la de poder escribir en un chero declaraciones de funciones, de estructuras de datos. . . que sean usadas repetidamente por nuestros programas, de forma que no tengamos que escribir cada vez que realizamos un nuevo programa dichas declaraciones. En este ejemplo, antes de poder usar la funci on printf(), es necesario decirle al compilador que esa funci on existe en una biblioteca, y que no debe preocuparse si no est a en nuestro archivo de c odigo fuente, pues ya se encargar a el enlazador de a nadirla al programa ejecutable. Existen m as directivas del preprocesador que se ir an estudiando a lo largo del curso.

2.4.3. La funci on principal


Todo programa en C esta constituido por una o m as funciones. Cuando se ejecuta un programa, e ste ha de empezar siempre por un lugar predeterminado. En BASIC por ejemplo el programa comienza a ejecutarse por la l nea 1. En C en cambio, para dotarlo de mayor exibilidad, la ejecuci on arranca desde el comienzo de la funci on main(). Por tanto, en nuestro programa vemos que despu es de incluir los archivos de cabecera necesarios, aparece la l nea: main(void) n de la funci que indica que el bloque que sigue a continuaci on es la denicio on principal. Este bloque est a entre { y } y dentro est an las instrucciones de nuestro programa, que en este ejemplo sencillo es s olo una llamada a una funci on del sistema para imprimir por pantalla un mensaje.

22

CAPITULO 2. EL PRIMER PROGRAMA EN C

2.4.4. Las funciones


La realizaci on de un programa complejo requiere la divisi on del problema a resolver en partes m as peque nas hasta que se llega a un nivel de complejidad que puede programarse en unas pocas l neas de c odigo. Esta metodolog a de dise no se denomina Arriba-Abajo, renamientos sucesivos o Top-Down en ingl es. Para permitir este tipo de desarrollo el lenguaje C permite la descomposici on del programa en m odulos a los que se denominan funciones. Estas funciones permiten agrupar las instrucciones necesarias para la resoluci on de un problema determinado. El uso de funciones tiene dos ventajas. La primera es que permite que unos programadores usen funciones desarrolladas por otros. La segunda es que mejora la legibilidad del c odigo al dividirse un programa grande en funciones de peque no tama no que permiten concentrarse s olo en una parte del c odigo total. En esta primera parte del curso s olo se van a usar funciones ya escritas por otros programadores, dej andose para la parte nal la el manejo y creaci on de funciones. Para usar una funci on ya creada s olo hacen falta dos cosas: Incluir el chero cabecera donde se declara la funci on a usar. Realizar la llamada a la funci on con los argumentos apropiados. La primera parte ya se ha visto como se realiza. La segunda requiere simplemente escribir el nombre de la funci on, seguida por sus argumentos encerrados entre par entesis. As para llamar a la funci on printf() en el programa se hace: printf("Hola!\n"); /* Imprimo el mensaje */ En el que se ha pasado el argumento "Hola!\n" a la funci on. Este argumento es una cadena de caracteres, que es lo que escribir a la funci on printf() en la pantalla. En realidad lo que se imprime es Hola!, pues los caracteres \n forman lo que se denomina una secuencia de escape. Las secuencias de escape son secuencias de dos o m as caracteres que representan caracteres que no son imprimibles. En este caso la secuencia \n signica que se avance una l nea. Existen m as secuencias de escape que se estudiar an m as adelante. Otro detalle que se aprecia en la l nea de c odigo anterior es que al nal se ha puesto un ;. Este car acter indica el nal de cada instrucci on de C y ha de ponerse obligatoriamente. Esto presenta como inconveniente el que a los programadores noveles se les olvida con frecuencia, con el consiguiente l o del compilador y ristra de mensajes de error, a menudo sin sentido para el pobre novatillo. La principal ventaja es que as una instrucci on puede ocupar m as de una l nea sin ning un problema, como por ejemplo: resultado_de_la_operacion = variable_1 + variable_2 + variable_muy_chula + la_ultima_variable_que_hay_que_sumar;

2.4.5. Finalizaci on del programa


El programa naliza su ejecuci on cuanto termina de ejecutarse la funci on main(), lo cual ocurre cuando se llega a la llave que cierra el bloque de la funci on (}).

2.4.6. La importancia de la estructura


El mismo programa anterior se pod a haber escrito de la siguiente manera: #include <stdio.h> main(void){printf("Hola!\n");}

2.4. NUESTRO PRIMER PROGRAMA EN C

23

Este programa desde el punto de vista del compilador es id entico al programa original, es decir, el compilador generar a el mismo programa ejecutable que antes. El inconveniente de este estilo de programaci on es que el c odigo fuente es bastante m as dif cil de leer y entender. Por tanto, aunque al compilador le da lo mismo la estructura que posea nuestro c odigo fuente, a los pobres lectores de nuestra obra s que les interesa. Por tanto a lo largo del curso, y especialmente en el laboratorio, se penalizar a enormemente a aquellos alumnos rebeldes que no sigan las normas de estilo de un buen programador en C, que se resumen en: Escribir al principio de cada programa un comentario que incluya el nombre del programa, describa la tarea que realiza, indique la revisi on, fecha y el nombre del programador. Cada instrucci on ha de estar en una l nea separada. Las instrucciones pertenecientes a un bloque han de estar sangradas respecto a las llaves que lo delimitan: { printf("Hola!\n"); } Hay que comentar todos los aspectos importantes del c odigo escrito.

24

CAPITULO 2. EL PRIMER PROGRAMA EN C

Cap tulo 3

Tipos de datos
3.1. Introducci on
Este cap tulo introduce las variables y los tipos de datos b asicos de C (int, float, double y char). Tambi en se explican las funciones b asicas de entrada y salida que van a permitir leer desde teclado y escribir por pantalla variables de cualquier tipo. Tanto las variables como las funciones de entrada y salida se utilizan en todos los programas.

3.2. Variables
Todos los datos que maneja un programa se almacenan en variables. Estas variables tienen un nombre arbitrario denido por el programador e invisible para el usuario del programa. Ni el resultado, ni la velocidad de c alculo ni la cantidad de memoria utilizada dependen del nombre de las variables. Algunos ejemplos son x, i, variable temporal, Resistencia2. . . Sin embargo hay que tener en cuenta que existen algunas restricciones en el nombre de las variables: Los nombres pueden contener letras a..z, A..Z, d gitos 0..9 y el car acter ; sin embargo siempre deben comenzar por una letra. Otros caracteres de la tabla ASCII como por ejemplo el gui on -, el punto ., el espacio , el d olar $ . . . no son v alidos. Algunos nombres de variables no son v alidos por ser palabras reservadas del lenguaje, por ejemplo if, else, for, while, etc. El compilador dar a un error si se intentan utilizar nombres de variables no v alidos. Es importante recordar que C es un lenguaje de programaci on que distingue min us-culas y may usculas y por lo tanto la variable X1 es diferente a la variable x1. Generalmente en C todos los nombres de variables se denen en min usculas y en los nombres formados por varias palabras se utiliza el car acter para separarlas. Por ejemplo suele escribirse variable temporal en lugar de variabletemporal, que resulta m as confuso de leer para el propio programador y para otros programadores que revisen su c odigo. 25

26

CAPITULO 3. TIPOS DE DATOS

3.3. Tipos b asicos de variables


El lenguaje C proporciona cuatro tipos b asicos de variables para manejar n umeros enteros, n umeros reales y caracteres. Otros tipos de variables se denen a partir de estos tipos b asicos.

3.3.1. Numeros enteros


Los n umeros enteros, positivos o negativos, se almacenan en variables de tipo int. La manera de declarar una variable llamada mi primer entero como entera es escribir la siguiente l nea: int mi_primer_entero; Una vez declarada la variable se puede almacenar en ella cualquier valor entero que se encuentre dentro del rango del tipo int. Por ejemplo, en la variable mi primer entero se puede almacenar el valor 123, pero no el valor 54196412753817103 por ser demasiado grande ni 7.4 por ser un n umero real. El rango de valores que pueden almacenarse en variables de tipo int est a relacionado con el n umero de bytes que el ordenador utilice para este tipo de variables, seg un se ha visto en la secci on 1.3. Al denir una variable de tipo int, el compilador se encarga de reservar unas posiciones de la memoria del ordenador donde se almacenar a el valor de la variable. Inicialmente el contenido de estas posiciones de memoria es desconocido 1, hasta que se asigna valor a la variable, por ejemplo: int i; i=7; Al hacer la asignaci on, el valor 7 se convierte a ceros y unos (de acuerdo a la codicaci on de los n umeros enteros) y el resultado se almacena en las posiciones de memoria reservadas para la variable i.

3.3.2. Numeros reales


Los n umeros reales se almacenan en variables de tipo float o de tipo double. La manera de declarar variables de estos tipos es an aloga al caso de variables enteras: float x; double z; La u nica diferencia entre float y double es que la primera utiliza menos cantidad de memoria; como consecuencia tiene menor precisi on y menor rango 2 pero utiliza menos memoria. Como se vio en el cap tulo de introducci on, las variables reales se codican en forma de una mantisa, donde se almacenan las cifras signicativas, y un exponente. Ambos pueden ser positivos o negativos.
error muy com un en los programadores novatos es suponer que las variables no inicializadas valen cero. El rango de una variable est a relacionado con los valores m aximo y m nimo que puede almacenar mientras que la precisi on est a relacionada con el n umero de cifras signicativas (o n umero de decimales). Por ejemplo el rango de las variables float suele ser del orden de 1E+38 y la precisi on de unas 6 cifras signicativas.
2 1 Un

TIPOS DE VARIABLES 3.4. MAS

27

3.3.3. Variables de car acter


Un car acter es una letra, un d gito, un signo de puntuaci on o en general cualquier s mbolo que puede escribirse en pantalla o en una impresora. El lenguaje C s olo tiene un tipo b asico para manejar caracteres, el tipo char, que permite denir variables que almacenan un car acter. Ya se ver a m as adelante que para almacenar cadenas de caracteres, por ejemplo nombres y direcciones, hay que trabajar con conjuntos de variables tipo char (vectores de char). La manera de declarar la variable c como tipo char es la siguiente: char c; Hay que tener en cuenta que los caracteres se codican en memoria de una manera un poco especial. Como se vio en el cap tulo 1, la tabla ASCII asocia un n umero de orden a cada car acter, y cuando se guarda una letra en una variable tipo char, el compilador almacena el n umero de orden en la posici on de memoria reservada para la variable. Por ejemplo si se escribe c=z; se almacenar a el valor 122 en la posici on de memoria reservada para la variable c; es decir se almacenar a 01111010. Ejercicio a en la posici on de memoria 1. Es igual hacer c=9; que c=9;? Que valor se almacenar reservada para la variable c en cada caso?

3.4. M as tipos de variables


Existen unos modicadores que permiten denir m as tipos de variables a partir de los tipos b asicos. Estos modicadores son short (corto), long (largo) y unsigned (sin signo), que pueden combinarse con los tipos b asicos de distintas maneras. Los nuevos tipos de variables a los que dan lugar son los siguientes (algunos pueden abreviarse): short int long int long double unsigned int unsigned short int unsigned long int unsigned char = = = = = short long unsigned unsigned short unsigned long

En general los modicadores short y long cambian el tama no de la variable b asica y por lo tanto cambian el rango de valores que pueden almacenar. Por otro lado el modicador unsigned, que no puede aplicarse a los tipos reales, modica el rango de las variables sin cambiar el espacio que ocupan en memoria. Por ejemplo, en un ordenador donde el tipo int puede almacenar valores en el rango desde -32768 hasta 32767, una variable unsigned int tendr a un rango desde 0 hasta 65535, ya que no utiliza n umeros negativos. En ambos casos el n umero de valores diferentes que pueden tomar las variables es el mismo (65536 valores posibles = 216 ).

3.5. Constantes
Las constantes son, en general, cualquier n umero que se escribe en el c odigo y que por lo tanto no puede modicarse una vez que el programa ha sido compilado y enlazado. Si

28

CAPITULO 3. TIPOS DE DATOS

escribimos por ejemplo i=7, la variable i tomar a siempre el valor 7, ya que este n umero es jo en el programa: es una constante. El compilador considera como constantes de tipo int todos los numeros enteros, positivos o negativos, que se escriben en un programa. Para que estas constantes se consideren de tipo long deben llevar al nal una L o una l, para que se consideren de tipo unsigned deben llevar una u o una U y los enteros largos sin signo debe llevar ul o bien UL. Ver el siguiente ejemplo: int entero; unsigned sin_signo; long largo; unsigned long sin_signo_largo; entero=7; sin_signo=55789u; largo=-300123456L; /*es mejor L, porque l se confunde con un 1 */ sin_signo_largo=2000000000UL; Las constantes de tipo entero tambi en pueden escribirse en octal y en hexadecimal, lo que nos ahorra tener que hacer las conversiones a decimal mientras escribimos el programa. Los n umeros en base 8 se escriben empezando con cero y en base 16 empezando por 0x o por 0X. Las tres l neas siguientes son equivalentes: i=31; /*Decimal*/ i=037; /*Octal*/ i=0x1F; /*Hexadecimal, tambi en vale poner 0x1f*/ Los numeros reales se identican porque llevan un punto (123.4) o un exponente (1e-2) o ambas cosas. En estos casos la constante se considera de tipo double. Las constantes de caracteres se escriben entre comillas simples como en el siguiente ejemplo: char c; c=z; En realidad las constantes de caracteres se convierten autom aticamente a tipo int de acuerdo a la tabla ASCII, o sea que la instrucci on anterior es equivalente a escribir: char c; c=122; /* Esta asignaci on es correcta en C, aunque c es de tipo char, no int */

Determinados caracteres especiales se pueden escribir utilizando secuencias de escape que comienzan por el car acter \, como \n en el programa del cap tulo 2. Los ejemplos m as t picos son los siguientes: \n \r \0 \t \f cambio de l nea (newline) retorno de carro (return) car acter nulo (NULL). No confundir con el car acter 0. TAB cambio de p agina (form feed)

DE LAS VARIABLES 3.6. TAMANO


\ \" \\ comilla simple comilla doble la barra \

29

de las variables 3.6. Tamano


El tama no de cada tipo de variable no est a jado por el lenguaje, sino que depende del tipo del ordenador y a veces del tipo de compilador que se utilice. Todo programa que necesite conocer el tama no de una determinada variable puede utilizar el operador sizeof para obtenerlo, pero es una mala t ecnica de programaci on suponer un tama no jo. El operador sizeof puede utilizarse tanto con tipos concretos (sizeof(int)) como con variables (sizeof(i)). umero de bytes que ocupa la variable en El operador sizeof devuelve un entero que es el n memoria. El tipo short ocupa al menos 2 bytes en todos los compiladores y long ocupa un m nimo de 4, mientras que int suele ser de tama no 2 o 4 dependiendo del tipo de ordenador (m nimo 2). En denitiva, lo que ocurrir a en cualquier implantaci on del lenguaje es que: sizeof(short int) sizeof(int) sizeof(long int).

3.7. Escritura de variables en pantalla


La funci on m as utilizada para mostrar el valor de una variable por pantalla es la funci on printf(), que forma parte de la biblioteca est andar de funciones de entrada y salida en C. Esta funci on est a declarada en el archivo de cabeceras (header le) stdio.h, por lo tanto es necesario incluir este archivo en el programa para que el compilador pueda hacer las comprobaciones oportunas sobre la sintaxis de la llamada a printf. Sin embargo, al ser una funci on de la biblioteca est andar, no es necesario incluir ninguna biblioteca (library) especial para generar el ejecutable, ya que dicha librer a se enlaza por defecto. La funci on printf permite escribir cualquier texto por pantalla, como se mostr o en el programa hola.c del cap tulo anterior. Pero tambi en se puede utilizar para mostrar el valor de cualquier variable y adem as nos permite especicar el formato de salida. Los valores de las variables pueden aparecer mezclados con el texto, por lo tanto es necesario denir una cadena de caracteres especial donde quede clara la posici on en la que deben mostrarse los valores de las variables. Las posiciones donde aparecen las variables se denen utilizando el car acter % seguido de una letra que indica el tipo de la variable. Ejemplo: /********************* Programa: Entero.c Descripci on: Escribe un n umero entero Revisi on 0.1: 16/FEB/1999 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { int dia; dia=47;

30 printf("Hoy es el d a %d del a no\n",dia); }

CAPITULO 3. TIPOS DE DATOS

En el programa anterior la funci on printf sustituye el texto %d por el valor de la variable dia expresada como un entero. Este programa escribe por pantalla la siguiente l nea: Hoy es el d a 47 del a no Cada tipo de variable requiere utilizar una letra diferente despu es del car acter %, los formatos m as normales para los tipos b asicos de variables se muestran en la siguiente tabla: formato %d %f %c tipo de variable int float y double char

La funci on printf puede escribir varias variables a la vez, del mismo tipo o de tipo distinto, pero hay que tener cuidado para incluir en la cadena de caracteres tantos % como n umero de variables. Adem as hay que tener cuidado para introducir correctamente los formatos de cada tipo de variable. Ejemplo: /********************* Programa: Tipos.c Descripci on: Escribe variables de distintos tipos Revisi on 0.1: 16/FEB/1999 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { int dia; double temp; char unidad; dia=47; temp=11.4; unidad=C; printf("D a %d, Temperatura %f %c\n",dia,temp,unidad); } --------------- Salida -------------------------D a 47, Temperatura 11.400000 C

3.7.1. Escritura de variables de tipo entero


Las variables de tipo entero pueden escribirse en varios formatos: decimal, octal o hexadecimal. La funci on printf no proporciona un formato para escribir el valor en binario.

3.7. ESCRITURA DE VARIABLES EN PANTALLA

31

Los formatos para variables de tipo entero son %d para mostrar el valor en decimal, %o para mostrarlo en octal y %X o %x para mostrarlo en hexadecimal3. En formato octal y hexadecimal se muestra el valor sin signo. printf("Decimal %d, en octal %o, en hexadecimal %X\n",dia,dia,dia); escribe la siguiente l nea en pantalla: Decimal 47, en octal 57, en hexadecimal 2F Los tipos de variables derivados de int, como short, long, unsigned. . . utilizan los caracteres de formato que se muestran en la siguiente tabla: formato %d %hd %ld %u %hu %lu tipo de variable int short long unsigned int unsigned short unsigned long

3.7.2. Escritura de variables de tipo real


Las variables de tipo real, tanto float como double se escriben con formato %f, %e, %E, %g o %G, dependiendo del aspecto que se quiera obtener. Con %f siempre se escribe el punto decimal y no se escribe exponente, mientras que con %e siempre se escribe una e para el exponente y con %E se escribe una E. Por ejemplo: /********************* Programa: Formatos.c Descripci on: Escribe n umeros reales en distintos formatos Revisi on 0.0: 16/FEB/1999 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { printf("%f\n",12345.6789); printf("%e\n",12345.6789); printf("%E\n",12345.6789); } --------------- Salida -------------------------12345.678900 1.234568e+04 1.234568E+04
3 La u nica diferencia entre %X y %x es que con el primer formato los s mbolos de la A a la F del n umero en base hexadecimal se escriben en may usculas mientras que con el segundo se escriben en min usculas

32

CAPITULO 3. TIPOS DE DATOS

El formato %g es el m as seguro cuando no se conoce de qu e orden es el valor a escribir ya que elige autom aticamente entre %f (que es m as bonito) y %e (donde cabe cualquier n umero por grande que sea).

3.7.3. Escritura de variables de tipo car acter


Las variables tipo char se escriben con formato %c lo que hace que aparezca el car acter en pantalla. Tambi en es cierto que una variable tipo char puede escribirse en formato decimal, es decir con %d, ya que los tipos char e int son compatibles en C. En caso de escribir una variable tipo char con formato %d se obtiene el n umero de orden correspondiente en la tabla ASCII. Comprobad el siguiente programa viendo la tabla ASCII. /********************* * Programa: Caracter.c * Descripci on: Escribe variables de tipo char * Revisi on 0.0: 16/FEB/1999 * Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { char c; c=z; printf("Car acter %c\n",c); printf("Valor %d\n",c); } --------------- Salida -------------------------Car acter z Valor 122

3.7.4. Escritura de variables con formato


La funci on printf tambi en permite denir el tama no con el que aparece cada variable en la pantalla y la alineaci on, derecha o izquierda, que debe tener. Esto se hace escribiendo n umeros entre el car acter % y el car acter que dene el tipo de variable. En lugar de enumerar las reglas que se utilizan para denir estos formatos, lo m as pr actico es mostrar una lista de ejemplos que se puede utilizar como referencia r apida con mayor comodidad. variables enteras printf(":%5d: \n",i); printf(":%-5d: \n",i); printf(":%05d: \n",i); printf(":%2d: \n",i); variables reales printf(":%12f: \n",x); printf(":%12.4f: \n",x); --> --> --> --> : 123: :123 : :00123: :123: Hay espacio suficiente on izquierda Alineaci Llena con ceros No cabe, formato por defecto

--> -->

: 1024.251000: : 1024.2510:

Por defecto 6 decimales .4 indica 4 decimales

3.8. LECTURA DE VARIABLES POR TECLADO


printf(":%-12.4f: \n",x); printf(":%12.1f: \n",x); printf(":%3f: \n",x); printf(":%.3f: \n",x); printf(":%12e: \n",x); printf(":%12.4e: \n",x); printf(":%12.1e: \n",x); printf(":%3e: \n",x); printf(":%.3e: \n",x); --> --> --> --> --> --> --> --> -->

33

:1024.2510 : Alineaci on izquierda : 1024.3: Redondea correctamente :1024.251000: No cabe-> por defecto :1024.251: Tama no por defecto con 3 dec. :1.024251e+03: : 1.0243e+03: : 1.0e+03: :1.024251e+03: :1.024e+03: 12 caracteres Idem. pero con 4 dec. Idem. pero con 1 dec. No cabe. Por defecto 3 decimales.

3.8. Lectura de variables por teclado


La funci on m as c omoda para lectura de variables desde teclado es la funci on scanf, que en se trata de una funci on de la biblioteca est andar tiene un formato similar a printf. Tambi de entrada y salida y por lo tanto funciona en cualquier compilador de C. En este apartado s olo se ver a la manera de leer variables de tipo b asico aunque scanf es una funci on potente que admite muchas opciones. El siguiente programa lee una variable del teclado y escribe el valor en pantalla /********************* Programa: Leer.c Descripci on: Lee una variable con scanf Revisi on 0.0: 16/MAR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { int a; printf("Dame un valor "); scanf("%d",&a); printf("Has escrito %d\n",a); } Puede observarse que la sintaxis de scanf y de printf es similar. La funci on scanf recibe una cadena de caracteres donde se dene el formato de la variable a leer y luego el nombre de la variable precedido de un signo &. Este signo & es fundamental para que scanf funcione correctamente. En el cap tulo de funciones se explicar a por qu e es necesario. Los formatos de los distintos tipos de variables son los mismos que para printf salvo que las variables float se leen con formato %f y las double se leen con %lf. Para escritura ambas utilizan el formato %f, si bien la mayor a de los compiladores tambi en admiten especicar el formato como %lf para escritura y se evita la peque na inconsistencia que existe entre printf y scanf.

34

CAPITULO 3. TIPOS DE DATOS

Ejercicios
1. Escribe una tabla con todos los tipos de datos que incluya los formatos que utiliza cada uno con printf y scanf

Cap tulo 4

Operadores en C
4.1. Introducci on
En el cap tulo anterior se han visto los tipos de datos b asicos del lenguaje C, c omo leer estos datos desde teclado y c omo imprimirlos en la pantalla. Puesto que los ordenadores son equipos cuya misi on es el proceso de datos, los lenguajes de programaci on proveen al programador con una serie de operadores que permiten realizar las operaciones necesarias sobre los datos. En este cap tulo vamos a estudiar los distintos operadores soportados por el lenguaje C.

4.2. El operador de asignaci on


El operador de asignaci on en C se representa mediante el signo =. Este operador asigna el valor de la expresi on situada a su derecha a la variable situada a su izquierda, as por ejemplo, para asignar a la variable n el valor 2 se escribe: n = 2; No ha de confundirse este operador con la igualdad en sentido matem atico, aunque se use el mismo s mbolo. En matem aticas se puede escribir 4 2 2, cosa que dar a un soberano error en C al intentar compilar, pues 4 no es una variable. Tampoco es legal en C la instrucci on 2 = n, aunque matem aticamente sea una expresi on correcta. n situada a su derecha y En resumen el operador de asignaci on en C evalua la expresio asigna su valor a la variable situada a su izquierda:

variable = expresi on;

4.3. Operadores para numeros reales


En la mayor a de las aplicaciones es necesario operar con n umeros reales. Para hacer esto posible en el lenguaje C, e ste soporta las operaciones matem aticas b asicas, es decir, la suma, resta, multiplicaci on y divisi on. El resto de operaciones (potenciaci on 1, logaritmos, funciones trigonom etricas. . . ) est an implantadas en una biblioteca matem atica, debiendo incluirse el chero math.h si se desean usar2 .
contrario que en otros lenguajes el operador no es el de elevar un n umero a una potencia. es necesario decirle al enlazador que a nada la biblioteca matem atica, lo cual depende del sistema de desarrollo usado.
2 Adem as 1 Al

35

36

CAPITULO 4. OPERADORES EN C

Las operaciones de suma, resta, multiplicaci on y divisi on se representan en C mediante los operadores +, -, * y / respectivamente. Supongamos que estamos desarrollando un programa de facturaci on y que tenemos que calcular el importe total a partir de la base imponible para un IVA del 16 %. La forma de realizarlo ser a: total = base * 1.16; En donde se realiza el producto de la variable base por la constante 1.16 para despu es asignar el resultado a la variable total. En una misma expresi on se pueden combinar varios operadores, aplic andose en este caso las mismas reglas de precedencia usadas en matem aticas, es decir, en primer lugar se realizan las multiplicaciones y las divisiones, seguidas de las sumas y restas. Cuando las operaciones tienen la misma precedencia, por ejemplo 2*pi*r, e stas se realizan de izquierda a derecha. Siguiendo con el ejemplo anterior, si tenemos dos art culos de los cuales el primero es un libro, al que se le aplica un 4 % de IVA y el segundo una tableta de chocolate, a la que se le aplica (injustamente) un IVA del 16 %, el c alculo del importe total de la factura se realizar a en C mediante la siguiente l nea de c odigo: total = 1.04*ValorLibro + 1.16*ValorChocolate; En donde en primer lugar se eval ua 1.04*ValorLibro, seguidamente 1.16*ValorChocolate y por u ltimo se suman los resultados de ambas operaciones, asign andose el valor de la suma a la variable total. Si se desea alterar el orden de precedencia preestablecido, se pueden usar par entesis al igual que en matem aticas, eso si, s olo par entesis; las llaves y los corchetes tienen otros signicados en el lenguaje. Siguiendo con el ejemplo anterior, si los dos art culos tienen el mismo tipo de IVA, el c alculo del total de la factura se escribir a en C: total = 1.16*(ValorChocolate + ValorHelado); En este caso se realiza en primer lugar la suma de ValorChocolate a ValorHelado y el resultado se multiplicar a por la constante 1.16, asign andose el resultado a la variable total. Si hubi esemos escrito la l nea anterior sin los par entesis, es decir: total = 1.16 * ValorChocolate + ValorHelado; /*Factura mal calculada*/ El programa multiplicar a 1.16 por ValorChocolate y al resultado le sumar a el ValorHelado, por lo que ser amos perseguidos duramente por el sco por facturar helados sin IVA.

4.4. Operadores para numeros enteros


El lenguaje C tambi en soporta las operaciones b asicas para los n umeros enteros. La funcionalidad y los s mbolos usados son los mismos que los usados para los n umeros reales. La u nica diferencia est a en el operador de divisi on, que en el caso de n umeros enteros da como resultado la parte entera de la divisi on. As por ejemplo 3/2 da como resultado 1, lo que puede parecer malo, pero a un hay cosas peores como 1/2, que da como resultado 0, lo cual tiene un gran peligro en expresiones como: resultado = 1/2*3;

4.5. OPERADOR UNARIO -

37

Como los dos operadores tienen la misma precedencia, la expresi on se eval ua de izquierda a derecha, con lo cual en primer lugar se efect ua 1/2 con el resultado antes anunciado de 0 patatero, que al multiplicarlo despu es por el 3 vuelve a dar 0, como todo el mundo habr a ya adivinado. Si en cambio se escribe: resultado = 3*1/2; El resultado ahora es de 1, con lo que se aprecia que en lenguaje C cuando se trabaja con n umeros enteros lo de la propiedad conmutativa no funciona nada bien, y deja bien claro que, en contra de la creencia popular, los ordenadores no son infalibles realizando c alculos matem aticos, sobre todo cuando el programador no sabe muy bien lo que est a haciendo y descuida temas como los redondeos de las variables o los rangos m aximos de los tipos.

4.4.1. Operador resto


Siguiendo con la divisi on de n umeros enteros, existe un operador que nos da el resto de la divisi on (no es tan bueno como tener los decimales, pero al menos es un consuelo). El operador resto se representa mediante el s mbolo %. As por ejemplo 4 %2 da como resultado 0 y 1 %2 da como resultado 1. Una utilidad de este operador es la de averiguar si un determinado n umero es m ultiplo de otro; por ejemplo el n umero 4580169 es m ultiplo de 33 porque 4580169 % 33 da cero. Por supuesto este operador no tiene sentido con n umeros reales, por lo que el compilador se quejar a si lo intentamos usar en ese caso.

4.4.2. Operadores de incremento y decremento


Dado que una de las aplicaciones principales de los n umeros enteros en los programas es la realizaci on de contadores, que usualmente se incrementan o decrementan de uno en uno, los dise nadores de C vieron aconsejable denir unos operadores para este tipo de operaciones 3. El operador incremento se representa a nadiendo a la variable que se desee incrementar dos s mbolos +. La sintaxis es por tanto: NombreVariable++. As por ejemplo la l nea: Contador++; Sumar a uno a la variable Contador. El operador decremento es id entico al de incremento, sin mas que sustituir los + por -. Siguiendo el ejemplo anterior: Contador--; Le restar a uno a la variable Contador.

4.5. Operador unario Hasta ahora, todos los operadores que se han discutido eran binarios, es decir, operaban con dos n umeros: el situado a su izquierda con el situado a su derecha. El operador unario toma s olo un valor situado a su derecha y le cambia el signo. As en: i = -c; el operador - toma el valor de la variable c y le cambia el signo. Por tanto si c vale 17, al nalizar la ejecuci on de la instrucci on, la variable i contendr a el valor -17.
3 Adem as todos los procesadores tienen instrucciones especiales de incremento y decremento, usualmente m as r apidas que la de suma normal, con lo que estos operadores le permiten al compilador usar ese tipo de instrucciones, consiguiendo un programa m as eciente.

38

CAPITULO 4. OPERADORES EN C

4.6. De vuelta con el operador de asignaci on


Ya se dijo al principio que el operador de asignaci on en C no deb a confundirse con la igualdad en sentido matem atico. Un uso muy frecuente del operador de asignaci on en C es el ilustrado en la instrucci on: i = i + 7; Esto, que matem aticamente no tiene ning un sentido, en los lenguajes de programaci on es una pr actica muy com un. El funcionamiento de la instrucci on no tiene nada de especial. Tal como se explic o en la secci on 4.2 el operador de asignaci on eval ua en primer lugar la expresi on que hay situada a su derecha para despu es asign arsela a la variable situada a su izquierda. Obviamente da igual que la variable a la que le asignamos el valor intervenga en la expresi on, tal como ocurre en este caso. As pues en el ejemplo anterior, si la variable i vale 3, al evaluar la expresi on se le sumar a al valor actual de i un 7, con lo que el resultado de la evaluaci on de la expresi on ser a 10, que se almacenar a en la variable i. Como este tipo de instrucciones son muy comunes en C, existe una construcci on espec ca on a realizar (+ en este caso). As la para realizarlas: preceder el operador = por la operaci instrucci on anterior se puede escribir: i += 7; Con los dem as operadores la situaci on es similar: t /= 2 asigna a t el resultado de la expresi on t/2. La ventaja de este tipo de construcci on queda maniesta cuando se usa un nombre de variable largo: variable_de_cuyo_nombre_no_quiero_acordarme += 2; En donde, aparte del ahorro en el desgaste del teclado, nos ahorramos la posibilidad de escribir mal la variable la segunda vez, y facilitamos la lectura a otros programadores, que no tienen que comprobar que las variables a ambos lados del = son la misma.

4.7. Conversiones de tipos


En las expresiones se pueden mezclar variables de distintos tipos, es decir, un operador binario puede tener a su izquierda un operando de un tipo, como por ejemplo int y a su derecha un operando de otro tipo, como por ejemplo double. En estos casos el compilador autom aticamente se encargar a de realizar las conversiones de tipos adecuadas. Estas conversiones trabajan siempre promoviendo el tipo inferior al tipo superior, obteni endose un resultado que es del tipo superior. De esta forma las operaciones se realizan siempre con la precisi on adecuada. Por ejemplo si i es una variable de tipo int y d es de tipo double, la expresi on i*d convierte en primer lugar el valor de i a tipo double y luego multiplica el valor convertido por d. El resultado obtenido de la operaci on es tambi en de tipo double. Las reglas de conversi on se resumen en: Si cualquier operando es de tipo long double Se convierte el otro operando a long double Si no: Si cualquier operando es de tipo double Se convierte el otro operando a double Si no: Si cualquier operando es de tipo float

4.7. CONVERSIONES DE TIPOS


Se convierte el otro operando a float Si no: Si cualquier operando es de tipo long Se convierte el otro operando a long Si no: Se convierten char y short a int

39

La u ltima l nea quiere decir que aunque los operandos de una operaci on sean los dos de aticamente a int, para realizar la tipo char o short, el compilador los promociona autom operaci on con mayor precisi on. Cuidado con la divisi on y los enteros En una expresi on como la siguiente: resultado = 1/2*d; como todos los operadores tiene la misma precedencia, la expresi on se eval ua de izquierda a derecha, realiz andose en primer lugar la operaci on 1/2, en la que como los dos operandos son dos constantes de tipo int, se realiza la divisi on entera, obteni endose un 0 como resultado. La a el siguiente operaci on a realizar ser a la multiplicaci on que, si d es de tipo double, convertir resultado anterior (0) a double y luego lo multiplicar a por d, obteni endose un resultado nada deseado. Si se realiza la operaci on al rev es, es decir, d*1/2 el resultado ser a ahora correcto por qu e? Para evitar este tipo de situaciones conviene evitar usar constantes enteras cuando trabajemos en expresiones con valores reales. As la mejor manera de evitar el problema anterior es escribir: resultado = 1.0/2.0*d Ahora bien, este m etodo no es v alido cuando tenemos que usar en una expresi on variables enteras junto con variables reales. En este caso puede ser necesario forzar una conversi on que el compilador no realizar a de modo autom atico. Por ejemplo, si las variables i1 e i2 son de tipo int y la variable d es de tipo double, en la expresi on i1/i2*d se realizar a la divisi on de n umeros enteros, con el consabido peligro y falta de precisi on. Para conseguir que la divisi on se realice con n umeros de tipo double podemos reordenar las operaciones como se hizo antes, o mejor, forzar al compilador a que realice la conversi on. Para forzar la conversi on se usa el operador unario molde, llamado cast en ingl es, que tiene el formato: (tipo al que se desea convertir) variable a convertir El ejemplo anterior forzando las conversiones quedar a como: resultado = (double) i1 / (double) i2 * d; Como el alumno aventajado habr a notado s olo ser a necesario poner un molde en cualquiera de las dos variables enteras Por qu e?

4.7.1. El operador asignaci on y las conversiones


Si el resultado de una expresi on no es del tipo de la variable a la que es asignado, el compilador realizar a autom aticamente la conversi on necesaria. En caso de que el tipo de la variable sea superior al de la expresi on no existir a ning un problema, pero si es inferior, se pueden producir p erdidas de precisi on (por ejemplo al convertir de double a int se pierde la

40

CAPITULO 4. OPERADORES EN C

parte decimal), y en algunos casos resultados err oneos (por ejemplo al asignar un valor grande de tipo long a un int). Como resumen a esta secci on cabe decir que se ha de ser extremadamente cuidadoso/a cuando en una instrucci on se mezclen variables y constantes de tipos distintos si se desean evitar resultados err oneos, que a menudo s olo se dan en ciertas situaciones, funcionando el programa correctamente en las dem as.

4.8. Ejemplos
Para aanzar los conceptos vamos a estudiar a continuaci on algunos ejemplos de expresiones matem aticas en C. main(void) { int ia, ib; int ires; double da, db; double dres; ia ib da db = = = = 1; 3; 2.3; 3.7; = = = = = ia + (ib*2 + ia*3); /* Ej 1 */ ia + (ib*2 + ia*3); /* Ej 2 */ ia + da*(ib/2.0 + ia/2.0); /* Ej 3*/ ia + da*(ib/2.0 + ia/2.0); /* Ej 4*/ -da*(ia + ib*(da + db/3.0)); /* Ej 5*/

ires dres dres ires dres }

En el primer ejemplo, se eval ua en primer lugar la expresi on que est a entre par entesis. Como esta expresi on contiene multiplicaciones y sumas, en primer lugar se realizan las multiplicaciones, sum andose seguidamente los resultados. Por u ltimo al resultado se le suma ia y se almacena el n umero obtenido en la variable ires. Como todas las variables involucradas son de tipo int no se realiza ninguna conversi on de tipos. N otese que en este caso los par entesis son innecesarios. El segundo ejemplo es id entico al primero, salvo que el resultado, que recordemos es de tipo int, se almacena en una variable de tipo double. Se realizar a por tanto una conversi on de tipos del resultado de int a double antes de realizar la asignaci on, lo que no debe de dar ning un problema. En el tercer ejemplo se realizan en primer lugar las divisiones de dentro del par entesis, en las que debido a que el segundo operando es una constante real, los operandos enteros se endose por tanto un resultado de tipo double que se multiplica convierten a double, obteni por da para sumarlo despu es a ia, que al ser un int ha de convertirse previamente a double. El resultado se almacena en una variable de tipo double, por lo que no se realizan m as conversiones de tipos. En el cuarto ejemplo el proceso es el descrito para el ejemplo anterior, salvo que al asignar el n umero de tipo double que resulta a uno de tipo int, se realiza una conversi on de double

4.8. EJEMPLOS

41

a int, por lo que se almacena en ires la parte entera del resultado de la expresi on, siempre y cuanto esta est e dentro el rango del tipo int, perdi endose para siempre la parte decimal. Por u ltimo en el ejemplo quinto se muestra la posibilidad de anidar varias expresiones entre par entesis. En este caso se eval ua en primer lugar el par entesis interno, el resultado se multiplica por ib y despu es se suma ia al resultado anterior. Todo ello se multiplica por da cambiado de signo. Que conversiones de tipos se han realizado en este ejemplo?

Ejercicios
1. Calcular el valor que resulta de cada una de las expresiones del programa anterior.

42

CAPITULO 4. OPERADORES EN C

Cap tulo 5

Control de ujo: for


5.1. Introducci on
Es habitual que los programas realicen tareas repetitivas o iteraciones (repetir las mismas operaciones pero cambiando ligeramente los datos). Esto no supone ning un problema para el programador novato, que despu es de aprender a cortar y pegar puede repetir varias veces el mismo c odigo, pero dentro de un l mite. Por ejemplo, un programa que escriba 3 veces la direcci on de la universidad para imprimir unos sobres ser a el siguiente: /********************* Programa: Sobres.c Descripci on: Escribe la direcci on de la Universidad en tres sobres. Revisi on 0.0: 10/MAR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { printf("Universidad Pontificia Comillas\n"); printf("c/Alberto Aguilera 23\n"); printf("E-28015 Madrid\f"); printf("Universidad Pontificia Comillas\n"); printf("c/Alberto Aguilera 23\n"); printf("E-28015 Madrid\f"); printf("Universidad Pontificia Comillas\n"); printf("c/Alberto Aguilera 23\n"); printf("E-28015 Madrid\f"); } Este programa es f acil de escribir, a base de copy y paste, cuando s olo se quieren imprimir tres sobres, pero qu e ocurre si queremos imprimir sobres para todos los alumnos de la universidad? 43

44

CAPITULO 5. CONTROL DE FLUJO: FOR

Todos los programas que se han visto hasta ahora tienen un ujo continuo desde arriba hasta abajo, es decir las instrucciones se ejecutan empezando en la primera l nea y descendiendo hasta la u ltima. Esto obliga a copiar parte del c odigo cuando se quiere que el programa repita una determinada tarea. Sin embargo el lenguaje C, como cualquier otro lenguaje de programaci on, proporciona maneras de modicar el ujo del programa permitiendo pasar varias veces por un conjunto de instrucciones. Se llama bucle de un programa a un conjunto de instrucciones que se repite varias veces. Es decir, es una zona del programa en la que el ujo deja de ser descendente y vuelve hacia atr as para repetir la ejecuci on de una serie de instrucciones. El bucle m as sencillo, que se introduce en este cap tulo, es el bucle for, que generalmente se utiliza para repetir parte del c odigo un cierto n umero de veces.

5.2. Sintaxis del bucle for


El bucle for queda denido por tres argumentos: sentencia inicial, condici o n de salida y sentencia nal de bucle. Estos argumentos se escriben separados por punto y coma y no por coma como en las funciones. for(sentencia inicial ; condici on ; sentencia nal ){ instrucci on 1; instrucci on 2; ... instrucci on n; } Lo mejor es ver un ejemplo: 1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> main(void) { int i; for(i=0; i<3; i++) { printf("Universidad Pontificia Comillas\n"); printf("c/Alberto Aguilera 23\n"); printf("E-28015 Madrid\f"); } }

Este programa hace lo mismo que el programa anterior, pero podemos escribir el n umero de sobres que queramos simplemente cambiando el 3 de la l nea 8 por otro n umero. En este ejemplo la instrucci on i=0 es la sentencia inicial, que se ejecuta antes de entrar en el bucle. La condici on es i<3, que indica que el bucle se va a repetir mientras sea cierto que la variable i tiene un valor menor que 3. Como inicialmente se asigna el valor 0 a la variable i, inicialmente se cumple la condici on del bucle, por lo tanto el ujo del programa entra en el bucle y se ejecutan todas las sentencias comprendidas entre las dos llaves ( { en la l nea 8, y } en la l nea 12). Al alcanzar la l nea 12 se ejecuta la sentencia nal de bucle, en este caso i++ y luego se vuelve a comprobar la condici on. En la primera iteraci on la variable i vale 0, pero al llegar al nal del bucle la instrucci on i++ hace que pase a valer 1, entonces se vuelve a comprobar la

5.2. SINTAXIS DEL BUCLE FOR

45

condici on i<3 que se cumple y por lo tanto el ujo vuelve a la l nea 9. Este bucle se ejecuta 3 veces en total, durante la primera pasada i vale 0, al llegar a la l nea 12 pasa a valer 1 y hace la segunda pasada, entonces pasa a valer 2 y hace la tercera. Al terminar la tercera pasada, la instrucci on i++ hace que i valga 3 y por lo tanto deja de cumplirse la condici on i<3 ya que 3 no es menor que 3. Como puede apreciarse, la variable i controla el n umero de veces que se ejecuta el bucle. Por ello a este tipo de variables se les denomina variables de control. Es muy importante hacer un buen uso del sangrado para facilitar la lectura del programa. En este ejemplo puede observarse que todas las instrucciones del programa tienen un sangrado de tres espacios, pero las instrucciones del bucle (l neas 9 a 11) tienen un sangrado adicional de otros tres espacios. De esta manera se ve claramente que la llave de la l nea 12 cierra el bucle for de la l nea 8 mientras que la llave de la l nea 14 cierra la funci on main haciendo pareja con la llave de la l nea 5. Puesto que el compilador ignora todos los espacios y los cambios de l nea, cada programador puede elegir su estilo propio, lo importante es ser coherente en todo el c odigo. Por ejemplo en lugar de 3 espacios pueden escribirse siempre 2, lo que no vale es poner unas veces 3 y otras 2 porque entonces las columnas salen hechas un churro. Tampoco es correcto imprimir los programas utilizando un tipo de letra proporcionado 1 como Times o Helv etica sino que los programas deben imprimirse en tipos de letra monoespaciados como por ejemplo Courier. Por u ltimo destacar las siguientes nociones: la sentencia inicial siempre se ejecuta la condici on siempre se comprueba antes de cada iteraci on. Por lo tanto puede ocurrir que nunca se llegue a entrar en el bucle si desde el principio no se cumple la condici on. Por ejemplo for(i=0; i<-3; i++) nunca entra en el bucle. la sentencia nal se ejecuta al terminar cada iteraci on. Por lo tanto si no se entra en el bucle esta sentencia no se ejecuta nunca.

5.2.1. Operadores relaciones


Veamos ahora una breve introducci on a las operadores relacionales que pueden utilizarse para escribir la condici on del bucle. Los operadores relacionales se utilizan para poder comparar los valores de distintas variables o para comparar el valor de una variable con una constante. En principio todas las comparaciones deben realizarse entre variables y constantes del mismo tipo, aunque se aplican las conversiones autom aticas de tipos descritas en el cap tulo anterior. Los operadores relaciones son: mayor que: > menor que: < mayor o igual a: >= menor o igual a: <= igual a: == distinto de: !=
Los tipos de letra proporcionados son aquellos en los que cada letra tiene una anchura diferente, por ejemplo la letra m es m as ancha que la letra l, mientras los tipos monoespaciados son aquellos en los que todas las letras y signos tienen la misma anchura.
1

46

CAPITULO 5. CONTROL DE FLUJO: FOR

No debe confundirse el operador de asignaci on = con el operador relacional de igualdad == que no modica el valor de las variables. Es decir, es totalmente distinto escribir i=7, que introduce el valor 7 en la variable i independientemente del valor que tuviese e sta, que escribir i==7, que compara el valor de la variable i con la constante 7. Ejercicio 1. Comprobar que otra forma de escribir el bucle del ejemplo ser a poner for(i=0; i!=3; i++).

5.3. Doble bucle for


Tambi en se pueden hacer bucles dentro de otros bucles, s olo hace falta tener cuidado al colocar las llaves y tener cuidado de utilizar variables de control diferentes. Pensemos por ejemplo en un robot que tiene que cortar un c esped de 20 m de ancho por 5 m de largo. Un programa para hacer esto ser a: /********************* Programa: CortaCesped.c Descripci on: Programa para manejar un robot que corta el cesped. Revisi on 0.0: 12/MAR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> #include <robot.h> /*Declara las funciones para el manejo del robot*/ main(void) { int i,j; /*Inicialmente el robot est a en la esquina R******************** ********************* ********************* ********************* ********************* El robot debe ir avanzando hacia la derecha mientras corta el c esped y al llegar al final retrocede y baja un metro. .................... ...R**************** ******************** ******************** ******************** */ for(i=0; i<5; i++) { ColocarEnPosicion(); /*Se coloca mirando a la derecha*/

5.3. DOBLE BUCLE FOR


for(j=0; j<20; j++) { AvanzaDerecha(); printf("He avanzado un metro\n"); } printf("He llegado al final, vuelvo\n"); RegresarIzquierda(); BajarUnMetro(); } printf("Trabajo terminado. } Esto es f acil.\n");

47

En caso de bucles anidados es m as importante todav a tener cuidado con utilizar un sangrado correcto, de manera que se identique claramente el comienzo y el nal de cada bucle. En este ejemplo tambi en se puede observar que no es lo mismo escribir la instrucci on ColocarEnPosicion() antes del for interno que despu es de la llamada a BajarUnMetro(). Aunque parezca lo mismo, s olo se garantiza que el robot empieza a cortar el c esped en la direcci on adecuada cuando el programa est a escrito como en el ejemplo. Es importante tener cuidado con las inicializaciones.

Ejercicios
1. Escribir un programa que escriba con printf todos los n umeros del 0 al 20. 2. Modicar el programa anterior para que escriba s olo los n umeros pares del 2 al 20.

48

CAPITULO 5. CONTROL DE FLUJO: FOR

Cap tulo 6

Control de ujo: while, do-while


6.1. Introducci on
Imaginemos que tenemos un robot en una habitaci on como la mostrada en la gura 6.1. El robot s olo obedece a dos instrucciones sencillas: avanzar un cent metro hacia adelante y girar g grados a la derecha. Nuestra tarea es conseguir que el robot salga por la puerta, pero no sabemos a priori donde est a, por lo que una soluci on a base de un bucle for como el estudiado en el tema anterior que avance el robot hasta la pared, una instrucci on de giro de 90 grados, otro bucle para que avance hasta la puerta y un u ltimo giro de -90 grados para salir no nos sirve, al no conocer a priori el n umero de iteraciones que deseamos realizar en cada bucle.

bip bip

Figura 6.1: Robot

Como futuros ingenieros que somos no nos vamos a rendir f acilmente ante semejante problema. Una soluci on es colocar al robot un sensor que nos diga cuando toca la pared, y hacer avanzar el robot hasta que toque la pared, girar 90 grados, y seguir avanzando pegado a la pared, lo que seguir a siendo detectado por nuestro estupendo sensor, hasta que lleguemos a la puerta, momento en el cual giraremos -90 grados para salir, cumpliendo con e xito nuestro digo1 como: objetivo. El algoritmo descrito se puede expresar usando pseudoc o
1 Un

pseudoc odigo es una manera de expresar un algoritmo usando lenguaje natural y a un nivel de detalle muy

alto.

49

50

CAPITULO 6. CONTROL DE FLUJO: WHILE, DO-WHILE


mientras que el robot no toque la pared { avanza_1_cm_adelante(); /* Avanza hacia la pared */ } girar(90); mientras que el robot toque la pared { avanza_1_cm_adelante(); /*Avanza pegado a la pared hacia la puerta*/ } girar(-90); /* Se pone delante de la puerta */ avanza_1_cm_adelante(); /* Sale por la puerta! */

Como podemos apreciar, el algoritmo consiste en repetir una serie de instrucciones mientras una condici on es cierta. Para ello todos los lenguajes estructurados poseen unas sentencias de control espec cas, que el caso del C son los bucles while y do-while.

6.2. Sintaxis del bucle while


Este tipo de bucles repiten una serie de instrucciones mientras una condici on es cierta. La sintaxis es: while(condici on){ instrucci on 1; instrucci on 2; ... instrucci on n; } El funcionamiento del bucle es como sigue: en primer lugar se eval ua la expresi on condici on. Si el resultado es falso no se ejecutar a ninguna de las instrucciones del bucle, el cual est a delimitado, al igual que en el caso del bucle for por dos llaves ({ y }). Por tanto la ejecuci on continuar a despu es de la llave }. Si por el contrario la condici on es cierta, se ejecutar an todas las instrucciones del bucle. Despu es de ejecutar la u ltima instrucci on del bucle (instrucci on n;) se vuelve a comprobar la condicio a el bucle n y al igual que al principio se terminar si es falsa o se realizar a otra iteraci on si es cierta, y as sucesivamente.

6.2.1. Ejemplos
Veamos a continuaci on algunos ejemplos del uso del bucle while. Sacar al robot de la habitaci on Se desea resolver en C el problema propuesto en la introducci on. El fabricante del robot, muy gentilmente, nos ha cedido varias rutinas en C para que su manejo sea m as f acil. Estas rutinas son: avanza 1 cm adelante() que hace avanzar al robot un cent metro hacia adelante. gira(double angulo) que es una funci on que admite un double como par ametro y que hace que el robot gire a derechas el n umero de grados indicado en dicho par ametro. toca pared() que es una funci on que vale uno si el robot toca la pared y cero si no la toca.

6.2. SINTAXIS DEL BUCLE WHILE

51

El ejemplo descrito mediante pseudoc odigo en la introducci on se realizar a en lenguaje C de la siguiente manera: /* Programa: SacaRob * * Descripci on: Saca al robot de una habitaci on como la mostrada en * la figura 6.1. * * Revisi on 0.0: 10/03/1998 * * Autor: El robotijero loco. */ #include <stdio.h> #include <robot.h> /* Declara las funciones del robot */ main(void) { while(toca_pared() != 1){ avanza_1_cm_adelante(); /* Avanza hacia la pared */ } girar(90); while(toca_pared() == 1){ avanza_1_cm_adelante(); /*Avanza pegado a la pared hacia la puerta*/ } girar(-90); /* Se pone delante de la puerta */

avanza_1_cm_adelante(); /* Sale por la puerta! */ } Suma de series. A nuestro hermanito peque no le han mandado en la escuela la ardua tarea realizar las sumas siguientes: 1

27

34

54

34

356

1234

34

2378

23

2345

Como nuestro hermanito peque no no tiene a un calculadora por haberse gastado el presupuesto familiar previsto para el caso en chicles, nos pide que le dejemos la nuestra. El problema es que se acaban de gastar las pilas y es domingo por la tarde, con lo que s olo tenemos dos opciones: hacer las sumas a mano o realizar un peque no programa en C, lo cual parece ser lo m as razonable, dado que existen fundadas expectativas de que el pr oximo d a de clase el malvado profesor de matem aticas vuelva a atormentar a nuestro hermanito con m as sumas enormes.

52

CAPITULO 6. CONTROL DE FLUJO: WHILE, DO-WHILE

Como buenos programadores que somos ya a estas alturas, antes de encender el ordenador tomamos papel y l apiz para analizar el problema, el cual no es m as que sumar una serie de n umeros. Lo primero que se nos ocurre es realizar un programa que realice las sumas del problema de nuestro hermanito: soluci on f acil, pero poco apropiada, pues s olo es v alida para este ejercicio y no para los que se avecinan. Lo siguiente que se nos ocurre es realizar un bucle, que vaya pidiendo los n umeros por el teclado y los vaya sumando. El problema ahora es que el n umero de sumandos var a de una suma a otra. La soluci on a este problema es doble: podemos pedir al usuario al principio del programa el n umero de sumandos y con esta informaci on usar un bucle for; pero el inconveniente de esta soluci on es que hay que contar antes todos los n umeros, lo cual, aparte de ser un rollo, es propenso a errores. La segunda soluci on es usar un bucle while y decirle al usuario que despu es del u ltimo n umero introduzca un cero para indicar al programa el nal de la serie. Un posible pseudoc odigo ser a: pedir n umero; mientras n umero no sea cero { sumar el n umero a la suma anterior; pedir n umero; } imprimir total; De este pseudoc odigo lo u nico que nos queda por resolver es c omo hacer para sumar el n umero a la suma anterior. Este tipo de operaciones son muy comunes en C y se ndola a cero antes de entrar en el bucle y dentro resuelven creando una variable, inicializa del bucle se incrementa con el valor deseado, es decir: int suma; on */ suma = 0; /* Inicializaci bucle{ /* bucle for, while... */ suma += lo_que_sea; } hacer lo que sea con suma; En este pseudoc odigo primero se dene la variable suma como entera (obviamente el tipo en cada caso ser a el que haga falta). Despu es hay que inicializarla, pues dentro del bucle se hace suma += lo que sea que como recordar a es una manera abreviada de expresar suma = suma + lo que sea y por tanto si la variable suma no se inicializa antes de entrar en el bucle, el valor de dicha variable puede ser cualquier cosa, con lo que el valor de la suma nal ser a err oneo y el suspenso de nuestro hermanito cargar a para siempre sobre nuestras conciencias. Una vez resueltos todos los problemas de nuestro programa podemos pasar a escribirlo en C, quedando como: 01 /* Programa: SumaSer 02 * 03 * Descripci on: Suma una serie de n umeros introducidos por teclado. La 04 * serie se termina introduciendo un cero, momento en el 05 * que se imprime el total de la suma por pantalla. 06 * 07 * Revisi on 0.0: 10/03/1998

6.3. SINTAXIS DEL BUCLE DO-WHILE.


08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 * * Autor: El hermano del hermanito sin calculadora. */ #include <stdio.h> main(void) { int suma; /* Valor de la suma */ int numero; /* numero introducido por teclado */

53

printf("Introduzca los n umeros a sumar. Para terminar teclee un 0\n"); suma = 0; /* inicializo el total de la suma */

scanf("%d",&numero); /* pido el primer n umero */ while(numero != 0){ suma += numero; scanf("%d",&numero); } printf("El valor de la suma es: %d\n", suma); }

6.3. Sintaxis del bucle do-while.


En la secci on anterior se ha visto que el bucle while comprueba su condici on antes de ejecutar el bucle. Esto nos obliga a que si la condici on depende de alguna instrucci on ejecutada dentro del bucle, esa misma instrucci on hay que realizarla antes del bucle, tal como ocurri o en la l nea 23 del ejemplo anterior. Para simplicar este tipo de situaciones existe el bucle do-while, el cual hace una primera pasada por el bucle, comprobando su condici on al nal, terminando el bucle si e sta es falsa, o volvi endolo a ejecutar si es cierta. La sintaxis es: do{ instrucci on 1; instrucci on 2; ... instrucci on n; }while(condici on); En este caso se ejecutar an las instrucciones instruccio on n y despu es se n 1. . . instrucci evaluar a la condici on. Si es falsa se contin ua con la instrucci on que sigue al bucle y si es cierta se vuelve a repetir el bucle (instruccio on n), se eval ua la condici on y n 1. . . instrucci as sucesivamente.

6.3.1. Ejemplos
Como el movimiento se demuestra andando, vamos a ver un ejemplo del bucle do-while.

54 Suma de series. Versi on 1.0

CAPITULO 6. CONTROL DE FLUJO: WHILE, DO-WHILE

Vamos a resolver el problema de la suma de series de la secci on 6.2.1 usando un bucle do-while. En este caso, despu es de la fase de an alisis que se realiz o en dicha secci on llegamos a que otro posible pseudoc odigo que soluciona el mismo problema es: hacer{ pedir n umero; sumar el n umero a la suma anterior; }mientras n umero no sea cero; imprimir total; Que expresado en C resulta: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /* Programa: SumaSer * * Descripci on: Suma una serie de n umeros introducidos por teclado. La * serie se termina introduciendo un cero, momento en el * cual se imprime el total de la suma por pantalla * * Revisi on 1.0: 10/03/1998 * * Autor: El hermano del hermanito sin calculadora. */ #include <stdio.h> main(void) { int suma; /* Valor de la suma */ int numero; /* numero introducido por teclado */ printf("Introduzca los n umeros a sumar. Para terminar teclee un 0\n"); suma = 0; /* inicializo el total de la suma */ do{ scanf("%d",&numero); suma += numero; }while(numero != 0); printf("El valor de la suma es: %d\n", suma); }

Comparando esta soluci on con la anterior usando un bucle while, vemos que la u nica diferencia es que ahora no hace falta pedir el primer n umero antes de comenzar el bucle, pues e ste se ejecuta al menos una vez, comprob andose la condici on al nal. Por tanto, es posible leer el n umero dentro del bucle y comprobar si es cero al nal de e ste. Otra cosa que cambia es el orden de las dos instrucciones que hay dentro del bucle. Se deja como ejercicio la explicaci on del porqu e de semejante cambio.

6.4. ES EL BUCLE FOR IGUAL QUE EL WHILE?

55

6.4. Es el bucle for igual que el while?


Alg un alumno aventajado se habr a dado cuenta de que el bucle while y el for son muy parecidos. En realidad cualquier cosa que se realice con uno de ellos se puede realizar con el otro. As un bucle while gen erico como: while(condici on){ instrucci on 1; instrucci on 2; ... instrucci on n; } Se realizar a con un for de la siguiente manera: for( ; condici on ; ){ instrucci on 1; instrucci on 2; ... instrucci on n; } En donde se aprecia que no se ha puesto nada en la condici on inicial que como recordar an iba entre el primer par entesis del for y el primer ; ni en la condici on nal que iba despu es del u ltimo ; y antes del par entesis que cierra el for. El compilador en este caso no da ning un error, sino que simplemente no hace nada antes de empezar el bucle ni tampoco hace nada al nal de cada iteraci on. Por tanto el funcionamiento de este bucle for es el siguiente: al principio eval ua la condici on, si es falsa se contin ua despu es del bucle y si es cierta se ejecuta el bucle, volvi endose a comprobar la condicio endose el ciclo. Dicho comportamiento n y repiti como podr a apreciar mi querido lector es el explicado no hace muchas l neas atr as cuando se intentaba exponer el funcionamiento del bucle while. Del mismo modo un bucle for se puede expresar mediante un while, aunque no tan elegantemente. As el bucle for: for(sentencia inicial ; condici on ; sentencia nal ){ instrucci on 1; instrucci on 2; ... instrucci on n; } mediante un bucle while se expresar a como: sentencia inicial; while(condici on){ instrucci on 1; instrucci on 2; ... instrucci on n; sentencia nal;

56 }

CAPITULO 6. CONTROL DE FLUJO: WHILE, DO-WHILE

6.4.1. Entonces cu al elijo?


La elecci on entre un tipo de bucle u otro debe hacerse intentando conseguir la mayor claridad posible del c odigo escrito. Si se desea repetir un conjunto de instrucciones mientras una condici on sea cierta, entonces lo m as natural es usar un bucle while, pues muestra mucho m as claramente lo que se desea hacer. Si en cambio queremos realizar una determinada tarea un determinado numero de veces, lo m as claro es usar un bucle for, que incluye dentro de la sentencia de control las instrucciones que permiten inicializar la variable que hace de contador e incrementarla al nal de cada iteraci on.

6.5. El ndice del bucle for sirve para algo


En los ejemplos dados hasta ahora, la variable que permit a llevar la cuenta de las veces que ejecut abamos el bucle for se usaba u nica y exclusivamente para eso. Sin embargo, al ser una variable normal y corriente, es posible usar dicha variable dentro del bucle, pero eso s , no es muy recomendable cambiar su valor, pues se generan programas confusos y por tanto dif ciles de mantener. Veamos un ejemplo para aclarar este tema: supongamos que acabamos de llegar a casa despu es de una clase de matem aticas en la que nos han explicado las series aritm eticas y nos han dado la f ormula: 1 2 El problema es que como est abamos hablando con el compa nero (como de costumbre), justo cuando termin abamos de copiar la f ormula el profesor la borr o de la pizarra, con lo que no estamos muy seguros de ella. Como ya estamos hechos unos artistas del C, decidimos hacer un programilla que calcule el valor de una serie de n n umeros y comprobar as la delidad de nuestros apuntes. El pseudoc odigo del programa es muy simple: 1

nn

pedir valor final n; inicializar suma total; for(i=1; i<=n; i++){ sumar i a la suma total; } imprimir suma total; calcular el valor de la serie e imprimirla; Una vez comprobado que el pseudoc odigo realiza lo que queremos pasamos a la acci on, codicando el siguiente programa: 1 /* Programa: SerAri 2 * 3 * Descripci on: Suma una serie aritm etica de 1 a n, siendo n un 4 * dato introducido por el usuario, imprimiendo el 5 * valor de la suma y el de la formula n*(n+1)/2. 6 * 7 * Revisi on 0.0: 10/03/1998 8 *

6.6. EJERCICIOS
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 * Autor: El alumno charlat an. */ #include <stdio.h> main(void) { int suma; /* Valor de la suma */ int numero; /* Valor final de la serie */ int i; /* ndice del for */ printf("Introduzca el n umero final de la serie.\n"); scanf("%d", &numero); suma = 0; /* inicializo el total de la suma */

57

for(i=1; i<=numero; i++){ suma += i; } printf("El valor de la suma es: %d\n", suma); printf("Y el de la f ormula es: %d\n", numero*(numero+1)/2 ); }

En este programa se han introducido dos novedades: la primera ha sido la anunciada previamente de usar la variable de control del bucle i dentro del bucle (l nea 26). La segunda es la de usar una expresi on dentro del printf en la linea 30. El funcionamiento de esta instrucci on es el siguiente: se eval ua en primer lugar la expresi on numero*(numero+1)/2 y el valor resultante es el que se imprime en pantalla.

6.6. Ejercicios
1. Realizar un programa para calcular multiplicaciones del tipo: 2 * 3 * 35 345 * 34 * 3.5 * 23 * 2.1 2 * 2.234 Hacer dos versiones: una en la que se pida al usuario al principio del programa la cantidad de n umeros que desea multiplicar para pasar seguidamente a pedirle cada uno de los n umeros a multiplicar. En la otra versi on el usuario introducir a uno a uno los n umeros, indic andole al programa que ya no hay m as mediante la introducci on de un 1. 2. Realizar un programa para comprobar la veracidad de la siguiente f ormula para calcular la suma de una serie geom etrica: 1

n2

Pistas: La instrucci on nal de los bucles for puede ser tambi en i+=2. Para calcular n2 se puede hacer n*n

58

CAPITULO 6. CONTROL DE FLUJO: WHILE, DO-WHILE

Cap tulo 7

Control de ujo: if
7.1. Introducci on
Es habitual que los programas realicen tareas diferentes en funci on del valor de determinadas variables. La instrucci on if permite denir bloques de c odigo que no son de ejecuci on obligatoria y por lo tanto son bloques de instrucciones que el programa se puede saltar. Esta decisi on depende del valor de una condici on l ogica, que a su vez suele depender del valor que adquieran determinadas variables del programa. Tambi en es posible denir dos bloques de instrucciones alternativos, de manera que s olo se ejecute uno u otro en funci on del resultado de la condici on l ogica.

7.2. Sintaxis del bloque if


Existen dos formatos b asicos para denir instrucciones que se ejecutan de manera condicional. Un bloque que se puede ejecutar o no (if normal), y dos bloques que se ejecutan uno u otro (bloque if-else). El formato en cada caso es el siguiente: if normal: if (condici on) { instrucci on 1; instrucci on 2; ... instrucci on n; } bloque if-else: if (condici on) { instrucci on 1.1; instrucci on 1.2; ... instrucci on 1.n; } else { instrucci on 2.1; instrucci on 2.2; ... 59

60 instrucci on 2.m; }

CAPITULO 7. CONTROL DE FLUJO: IF

Veamos como ejemplo un programa que pregunta el valor de dos n umeros enteros y que escribe en pantalla cu al es el mayor de los dos. Este programa debe utilizar scanf para preguntar los valores y luego un if para decidir si se escribe el primero o el segundo. /********************* Programa: Compara.c Descripci on: Lee dos n umeros del teclado y los compara on 0.0: 16/FEB/1999 Revisi Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { int a,b; printf("Dame un valor entero "); scanf("%d",&a); printf("Dame otro valor "); scanf("%d",&b); if (a>b){ printf("El mayor es %d\n",a); } else { printf("El mayor es %d\n",b); } } Otra aplicaci on muy interesante es evitar errores de c alculo en los programas, como por ejemplo las divisiones por cero o las ra ces cuadradas de n umeros negativos. El siguiente ejemplo es un programa que incluye un control para evitar las divisiones por cero. En realidad el ejemplo parece algo tonto porque en caso de error simplemente termina con un exit, pero un programa m as completo puede tomar otras medidas como por ejemplo volver a preguntar el valor del denominador. La funci on exit() hace que el programa termine inmediatamente y le devuelve al sistema operativo el valor de su argumento1. Esta manera de terminar un programa antes de que llegue lo est la ejecuci on al nal de la funci on main, so a permitida en caso de detectarse un error que impida continuar con la ejecuci on normal del programa. /********************* Programa: Division.c Descripci on: Calcula el cociente de dos n umeros. El programa incluye un control que evita dividir por cero. Revisi on 0.0: 16/FEB/1999
1 Por convenio, un programa que termina a causa de un error debe devolver al sistema operativo un valor distinto de cero.

7.3. FORMATO DE LAS CONDICIONES


Autor: Rafael Palacios *********************/ #include <stdio.h> #include <stdlib.h> main(void) { int numerador, denominador; double division; printf("Numerador? "); scanf("%d",&numerador); printf("Denominador? "); scanf("%d",&denominador); if (denominador==0) { printf("Error, el resultado es infinito\n"); exit(1); } division=(double)numerador/denominador; printf("La divisi on vale: %f\n",division); }

61

7.3. Formato de las condiciones


Las condiciones que aparecen en los bloques if son las mismas que pueden aparecer en los bucles while, do-while y en el segundo t ermino de los bucles for. Son expresiones que normalmente incluyen operadores l ogicos y relacionales. Este apartado resume estos operadores, aunque muchos de ellos ya se han visto en cap tulos anteriores.

7.3.1. Operadores relacionales


Son operadores que permite comparar variables y/o constantes entre si. Los operadores relaciones son: mayor que: > menor que: < mayor o igual a: >= menor o igual a: <= igual a: == distinto de: != Las reglas de precedencia establecen que los operadores >, >=, <, <= se eval uan en primer lugar y posteriormente se eval uan los operadores == y !=. Esta regla no es muy importante ya que normalmente no se mezclan estos operadores en la misma condici on. M as interesante es saber que la precedencia de los operadores matem aticos es mayor que la de los

62

CAPITULO 7. CONTROL DE FLUJO: IF

operadores l ogicos y por lo tanto condiciones como la siguiente se eval uan realizando primero los c alculos y luego las comparaciones: (a > 3*b) (a+b == 0) /* Esto hace ( a > (3*b) ) */ /* Esto hace ( (a+b) == 0 ) */

7.3.2. Operadores l ogicos


Son operadores que permiten relacionar varias expresiones. Las operaciones b asicas son AND (la condici on es cierta s olo si las dos expresiones son ciertas), OR (la condici on es cierta si una o las dos expresiones son ciertas) y la negaci on (invierte el resultado l ogico de la expresi on que le sigue). Los operadores l ogicos son: AND l ogico: && OR l ogico: || negaci on: ! Estos operadores permiten denir condiciones complicadas en todos los bucles y en los if. Hay que tener en cuenta que la precedencia del operador && es mayor que la del operador ||. (a>3 && b==4 || c!=7) /* Esto hace ( (a>3 && b==4) || c!=7 ) */

En caso de duda lo mejor es poner los par entesis necesarios antes que perder el tiempo haciendo pruebas en el programa. Es decir, o se mira la documentaci on y se hacen las cosas bien a la primera o se ponen par entesis hasta que la operaci on quede clara. Esto u ltimo tiene la ventaja de que estar a claro para todo el que lea el programa, aunque no se acuerde muy bien de las precedencias de los operadores. Por u ltimo conviene destacar que en C los operadores relacionales son operadores binarios, es decir, realizan la comparaci on entre los operandos que est an a su derecha y a su izquierda; siendo el resultado de dicha comparaci on un 1 si la condici on es cierta o un 0 si es falsa. Si, acostumbrados a la notaci on matem atica, se escribe: 0 <= a <= 10 para expresar la condici on 0 a 10, el resultado de dicha expresi on ser a siempre 1, independientemente del valor de la variable a. Puede adivinar por qu e? Si se tiene en cuenta que en C las expresiones se eval uan de izquierda a derecha, en primer lugar se evaluar a el primer a a su izquierda (0) con el que est a a su operador <=, el cual compara el operando que est derecha (a). El resultado de esta expresi on ser a 0 si a es menor que 0 o 1 en caso contrario. Dicho resultado ser a el operando izquierdo del siguiente operador relacional, con lo que se un haya sido el resultado de la comparaci on evaluar a la expresi on 0 <= 10 o 1 <= 10 seg anterior. En cualquier caso el resultado nal de la expresi on ser a cierto (1). Para expresar en C la condici on 0 a 10 correctamente, es necesario dividir la relaci on en dos, es decir, en primer lugar comprobar si a es mayor o igual que 0 y en segundo lugar si a es menor o igual que 10. Por u ltimo es necesario usar un operador l ogico para relacionar ambas expresiones, de forma que el resultado sea cierto cuando ambas condiciones sean ciertas. Se atreve a escribir la condici on? Por si acaso no se ha atrevido, e sta se muestra a continuaci on:

(0 <= a) && (a <= 10)

7.4. VALORES DE VERDADERO Y FALSO

63

7.4. Valores de verdadero y falso


Todas las operaciones l ogicas y relacionales devuelven un valor tipo int que puede ser 1 para verdadero o 0 para falso. Por lo tanto las condiciones que se escriben en los bucles o en los if suelen tener valor 1 o 0. Sin embargo las condiciones se consideran falsas s olo cuando valen 0 y verdaderas cuando valen cualquier otra cosa. En este sentido cualquier variable de tipo entero puede utilizarse como condici on y har a que la condici on sea falsa s olo si su valor es cero. Esta es la raz on fundamental para que en C no sea necesario un tipo de variable para operaciones l ogicas (que s olo pueda valer 1 o 0) como ocurre en otros lenguajes de programaci on. En C la siguiente l nea es v alida: i=a>3; En esta instrucci on la variable i toma el valor 1 o 0 en funci on del valor que tenga la as las siguientes condiciones son equivalentes para comprobar si una variable variable a. Adem entera vale verdadero: if (a!=0) { printf("Verdadero!\n"); } if (a) { printf("Verdadero!\n"); } lo mismo ocurre para comprobar si una variable es cero o falso, se puede preguntar de dos maneras: if (a==0) { printf("Falso\n"); } if (!a) { printf("Falso\n"); } En denitiva, la u nica denici on categ orica es que 0 es falso. En ambos casos queda m as clara la primera expresi on l ogica (if(a!=0)) que la segunda (if(a)).

7.5. Bloque if else-if


Es muy habitual en los programas tener que realizar varias preguntas en cadena, de manera que la estructura del bloque if-else se puede complicar bastante. Veamos el siguiente ejemplo: /********************* Programa: Alturas1.c Descripci on: Pregunta la altura de una persona Revisi on 0.0: 16/FEB/1999 Autor: Rafael Palacios *********************/

64 #include <stdio.h> main(void) { double altura; printf("Cu al es su altura? "); scanf("%lf",&altura);

CAPITULO 7. CONTROL DE FLUJO: IF

if (altura<1.5) { printf("Usted es bajito\n"); } else { if (altura<1.7) { printf("Usted no es alto\n"); } else { if (altura<1.9) { printf("Usted es alto\n"); } else { printf("Juega al baloncesto?\n"); } } } printf("Fin del programa.\n"); } En este tipo de estructuras de programa hay que ser muy cuidadoso para colocar correctamente las llaves y es imprescindible hacer un buen uso del sangrado para facilitar la lectura del programa. Unicamente remarcar que el siguiente programa aunque es mucho m as compacto no hace lo mismo que el anterior: /********************* Programa: Alturas2.c Descripci on: Pregunta la altura de una persona on 1.0: 16/FEB/1999 Revisi Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { double altura; printf("Cu al es su altura? "); scanf("%lf",&altura); if (altura<1.5){ printf("Usted es bajito\n"); } if (altura<1.7){

7.5. BLOQUE IF ELSE-IF


printf("Usted no es alto\n"); } if (altura<1.9){ printf("Usted es alto\n"); }else{ printf("Juega al baloncesto?\n"); } printf("Fin del programa.\n"); }

65

Este programa es incorrecto porque por ejemplo para alturas de 1.4 m se escriben tres mensajes en lugar de uno, ya que este valor cumple las tres condiciones de los tres if. En el caso del primer programa, al ser cierto el primer if, se escribe Es usted bajito y se salta directamente al u ltimo printf del programa porque el resto de las instrucciones forman parte del else del primer if. Para este tipo de situaciones se puede usar el bloque if else-if, cuya sintaxis es: if (condici on) { instrucci on 1.1; instrucci on 1.2; ... instrucci on 1.m; } else if (condici on) { instrucci on 2.1; instrucci on 2.2; ... instrucci on 2.n; ... } else { instrucci on 3.1; instrucci on 3.2; ... instrucci on 3.p; } El funcionamiento de esta construcci on es el siguiente: Se eval ua la condici o n del primer if. Si es cierta se ejecuta el bloque perteneciente a este if. Si es falsa se eval ua la condici o n del else if que le sigue, ejecut andose su bloque si dicha condici o n es cierta o pasando al siguiente else if si es falsa. Si ninguna de las condiciones es cierta se ejecutar a el bloque del u ltimo else en caso de que exista, pues es opcional su uso. Usando el bloque if else-if el programa anterior se puede escribir, de una manera mucho m as clara como: /********************* Programa: Alturas3.c Descripci on: Pregunta la altura de una persona Revisi on 0.0: 16/FEB/1999 Autor: Rafael Palacios *********************/ #include <stdio.h>

66

CAPITULO 7. CONTROL DE FLUJO: IF

main(void) { double altura; printf("Cu al es su altura? "); scanf("%lf",&altura); if (altura<1.5){ printf("Usted es bajito\n"); }else if (altura<1.7){ printf("Usted no es alto\n"); }else if (altura<1.9){ printf("Usted es alto\n"); }else{ printf("Juega al baloncesto?\n"); } printf("Fin del programa.\n"); }

7.6. Ejercicios
1. Repetir el programa anterior pero sin utilizar else. Es decir, preguntando en el mismo if si altura es menor que 1.7 y mayor o igual a 1.5. Pista: Hay que utilizar los operadores
l ogicos.

Cap tulo 8

Control de ujo: switch-case, break y continue


8.1. Introducci on
En este tema vamos a estudiar las u ltimas sentencias de control de ujo de C. En primer lugar vamos a estudiar la construcci on switch-case, que es usada para ejecutar un trozo de c odigo u otro en funci on del valor de una variable de control. Para nalizar se estudiar an las sentencias break y continue que permiten modicar el ujo normal de ejecuci on dentro de un bucle.

8.2. La construcci on switch-case


8.2.1. Introducci on
En muchos programas es necesario realizar una tarea u otra en funci on de una variable, ya sea e sta el resultado de un c alculo o un valor introducido por el usuario. Imaginemos que tenemos un robot como el descrito en el cap tulo 6 y que deseamos realizar un controlador interactivo, es decir, un programa mediante el cual el usuario introduzca una serie de comandos al programa y e ste mueva al robot seg un dichos comandos. Como se recordar a, el robot s olo obedec a a dos instrucciones b asicas: avanzar un cent metro hacia adelante y girar un determinado n umero de grados a la derecha. Adem as dispon a de un avanzado sensor que detectaba si el robot estaba tocando alg un obst aculo. Como tambi en recordar a, dispon amos de tres funciones que nos hab a suministrado muy gentilmente el fabricante del robot para gobernarlo (al robot, no al fabricante, claro est a). Estas rutinas tambi en se describieron en el cap tulo 6, pero las volvemos a poner a continuaci on para mayor comodidad del lector: avanza 1 cm adelante() hace avanzar al robot un cent metro hacia adelante. gira(double angulo) es una funci on que admite un double como par ametro y que hace que el robot gire a derechas el n umero de grados indicado en dicho par ametro. toca pared() es una funci on que vale uno si el robot toca la pared y cero si no la toca. Para que el robot pueda ser gobernado de forma interactiva, el programa deber a pedir al usuario un comando y actuar en consecuencia. Dicho comando podr a ser cualquier cosa, 67

68

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE

aunque lo m as c omodo es un n umero o una letra: por ejemplo 1 signicar a avanzar hacia adelante y 2 girar un n umero de grados (que habr a que preguntarle previamente al usuario). Un posible pseudoc odigo del programa ser a: pedir comando al usuario; si es 1: avanza_1_cm_adelante(); si es 2: pedir n umero de grados; gira(n umero de grados); Este tipo de situaciones son muy comunes en todos los programas. Por ello, aunque este tipo de decisiones se podr an resolver con sentencias if de la forma: if(comando == 1){ avanza_1_cm_adelante(); }else if (comando == 2){ pedir n umero de grados; gira(n umero de grados); } algunos lenguajes poseen estructuras de control espec cas para resolver m as f acilmente este tipo de decisiones. En el caso de C esta estructura de control es la construcci on switch-case.

8.2.2. Sintaxis de la construcci on switch-case


La sintaxis de esta construcci on es: switch(expresi on){ case constante 1: instrucci on 1 1; ... instrucci on 1 n1; break; ... case constante n: instrucci on n 1; ... instrucci on n nn; break; default: instrucci on d 1; ... instrucci on d nd; break; } Antes de comenzar a explicar los entresijos del funcionamiento de esta estructura de control conviene destacar algunos aspectos de su sintaxis: Despu es de la palabra clave case y de su constante el s mbolo que sigue son dos puntos (:) y no un punto y coma (;).

SWITCH-CASE 8.2. LA CONSTRUCCION


Al nal de cada bloque de instrucciones de un case se escribe la sentencia break.

69

S olo hay dos llaves: la de despu es del switch y la del nal. Los bloques de instrucciones pertenecientes a cada uno de los case vienen delimitados por el case y por el break del nal. Una vez advertido esto, pasemos a describir el funcionamiento de esta construcci on. En primer lugar se eval ua la expresi on que sigue al switch, con lo que se obtendr a un valor que ha de ser entero. Este valor se compara entonces con la constante entera que sigue al primer case (la constante 1). Si el resultado de la comparaci on es falso se prueba suerte con el siguiente case y as sucesivamente hasta encontrar alg un case cuya constante sea igual al resultado de la expresi on. Si esto ocurre se ejecutar an las instrucciones situadas entre el afortunado case y el break que naliza su bloque de instrucciones, continuando despu es con las instrucciones que sigan a la llave que cierra el switch. Si ning un case tiene a su lado una constante que coincida con el valor de la expresio an las instrucciones situadas entre n, entonces se ejecutar el default y el u ltimo break, y luego se continuar a con las instrucciones que siguen despu es del switch. Conviene hacer notar que se puede escribir un bloque switch-case sin el apartado de default. Si esto ocurre y se da el desafortunado caso de que ninguno de los case contenga el valor de la constante resultante de la evaluaci on de la expresi o on del n, entonces la ejecuci programa contin ua tranquilamente despu es de la llave que cierra el switch-case. Para aclarar ideas, veamos el siguiente ejemplo de un switch-case: switch (a){ case 1: printf("a vale uno\n"); break; case 2: printf("a vale dos\n"); break; default: printf("a no vale ni uno ni dos\n"); break; } Supongamos que a vale 1. Como acabamos de decir, en primer lugar se eval ua la expresi on que hay entre los par entesis situados despu es del switch. En este caso la expresi on es simplemente una variable, por lo que el resultado de la expresi on ser a el valor de la variable, es decir un 1. El programa compara entonces el valor as obtenido con el que hay a continuaci on del primer case, que como hemos tenido mucha suerte, es tambi en un 1, con lo que se ejecutar a el conjunto de instrucciones situadas entre este case y el siguiente break que en este ejemplo tan simple consta tan solo de la instrucci on printf("a vale uno\n").

8.2.3. Ejemplos
Veamos a continuaci on algunos ejemplos para ilustrar el uso de la construcci on switch-case. El robot con control manual Continuando con el ejemplo presentado en la introducci on de este cap tulo (apartado 8.2.1) el pseudoc odigo del programa se puede traducir a C usando un switch-case de la siguiente manera:

70 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE


/* Programa: RobMan * * Descripci on: Controla el robot de forma manual. Para ello espera a que on y ejecuta entonces la * el usuario introduzca su elecci on solicitada por el usuario. * opci * * Revisi on 0.0: 15/03/1998 * * Autor: El robotijero loco. */ #include <stdio.h> #include <robot.h> main(void) { int comando; /* Contendr a el comando introducido por el usuario */ float grados; /* Grados a girar por el robot*/ printf("Introduzca la opci on: "); scanf("%d", &comando); switch(comando){ case 1: avanza_1_cm_adelante(); break; case 2: printf("Introduzca el n umero de grados que desea girar: "); scanf("%f", &grados); gira(grados); break; default: printf("Error: opci on no existente\n"); break; } }

Ahora bien, salvo que el usuario tenga muy buena memoria, no se acordar a para siempre de los comandos, por lo que cada vez que tenga que ejecutar el programa tendr a que consultar el manual de usuario para ver qu e comandos tiene disponibles. Para evitar semejante engorro la soluci on es bien sencilla: imprimamos al comienzo del programa una lista de los comandos disponibles, junto con una breve descripci on de lo que hacen. Haciendo esto, el programa al arrancar podr a mostrar por pantalla lo siguiente: Control manual del robot. Men u de opciones: 1.- Avanzar 1 cm hacia adelante. 2.- Girar. Introduzca opci on:

SWITCH-CASE 8.2. LA CONSTRUCCION

71

A esto se le conoce con el nombre de menu de opciones, dado su parecido con los men us de los restaurantes (aunque presentan el grave inconveniente de que no se pueden comer). Para conseguir esto, basta con a nadir al programa anterior entre sus l neas 19 y 20 las instrucciones: printf(" printf(" printf(" printf(" Control manual del robot.\n\n"); Men u de opciones:\n"); 1.- Avanzar 1 cm hacia adelante.\n"); 2.- Girar.\n\n");

Un ejemplo de sesi on con el programa, suponiendo que dicho programa se llama robman, ser a: u:\c\grupo00\robman> robman Control manual del robot. u de opciones: Men 1.- Avanzar 1 cm hacia adelante. 2.- Girar. Introduzca opci on: 1 u:\c\grupo00\robman> robman Control manual del robot. u de opciones: Men 1.- Avanzar 1 cm hacia adelante. 2.- Girar. on: 2 Introduzca opci Cuantos grados desea girar: 27 u:\c\grupo00\robman> Como podemos apreciar el manejo del robot as es un poco pesado, pues para cada instrucci on que deseemos darle hemos de volver a llamar al programa. Para hacer un poco m as f acil la vida del manejador del robot, podemos realizar un bucle que englobe a todo el programa anterior. Un posible pseudoc odigo de esta soluci on ser a: repetir{ u imprimir men pedir comando al usuario; si es 1: avanza_1_cm_adelante(); si es 2: umero de grados; pedir n umero de grados); gira(n }mientras el usuario quiera; El problema ahora est a en como descubrir cuando el usuario quiere abandonar el programa. Obviamente la u nica manera de hacerlo (hasta que no se inventen los ordenadores que lean el pensamiento) es pregunt arselo directamente. Para ello nada m as f acil que incluir una nueva opci on en el men u de forma que cuando se elija se cambie la variable l ogica que controla el bucle. Un posible pseudoc odigo en el que se usa la opci on 3 para salir del programa ser a:

72

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE


salir = falso; repetir{ imprimir men u pedir comando al usuario; si es 1: avanza_1_cm_adelante(); si es 2: pedir n umero de grados; gira(n umero de grados); si es 3: salir = cierto; }mientras salir sea falso; Y el programa escrito en C quedar a como sigue:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

/* Programa: RobMan * * Descripci on: Controla el robot de forma manual. Para ello espera * a que el usuario introduzca su elecci on y ejecuta * entonces la opci on solicitada por el usuario. * * Revisi on 0.1: 16/03/1998 u de opciones y se introduce un * Se imprime un men * bucle para repetir el programa. * on 0.0: 15/03/1998 * Revisi * * Autor: El robotijero loco. */ #include <stdio.h> #include <robot.h> main(void) { int comando; /* Contendr a el comando introducido por el usuario */ float grados; /* Grados a girar por el robot*/ int salir; /* Variable l ogica que pondremos a 1 cuando el usuario desee salir del programa */ salir = 0; /* inicializamos la variable de control para que el bucle se repita hasta que se pulse la opci on 3.*/ do{ printf(" Control manual del robot.\n\n"); printf(" Men u de opciones:\n"); printf(" 1.- Avanzar 1 cm hacia adelante.\n"); printf(" 2.- Girar.\n"); printf(" 3.- Salir del programa.\n\n"); printf("Introduzca la opci on: "); scanf("%d", &comando);

8.3. LA SENTENCIA BREAK


36 37 switch(comando){ 38 case 1: 39 avanza_1_cm_adelante(); 40 break; 41 case 2: 42 printf("Introduzca el n umero de grados que desea girar: "); 43 scanf("%f", &grados); 44 gira(grados); 45 break; 46 case 3: 47 salir = 1; 48 break; 49 default: on no existente\n"); 50 printf("Error: opci 51 break; 52 } 53 }while(salir == 0); 54 55 printf("Que pase un buen d a.\n"); 56 } Ejercicios 1. Modicar el programa anterior para usar un bucle while en lugar del do-while.

73

2. Comprobar el funcionamiento del programa escrito en el ejercicio anterior en un ordenador. Como no disponemos del robot ni de las funciones para manejarlo, sustituir las llamadas a las funciones del robot por printfs que indiquen la llamada a la funci on correspondiente para poder comprobar f acilmente el correcto funcionamiento de nuestro programa.

8.3. La sentencia break


Esta sentencia hace que el ujo de programa salga del bloque en el que se encuentre. Este bloque puede ser una construcci on switch-case tal como acabamos de ver, o cualquiera de los bucles que hemos visto en cap tulos anteriores: for, while o do-while.

8.3.1. La sentencia break y el switch-case


En el caso de la construcci on switch-case hemos visto que ha de usarse la sentencia break expl citamente para indicarle al programa que salga de la construcci on. Esto que puede parecer sin sentido, tiene su utilidad en ciertos casos en los que hay que ejecutar el mismo conjunto de instrucciones para varios valores de la expresi on que controla el switch-case. Imaginemos que tenemos que realizar un programa con un men u para un usuario que es al ergico a los n umeros. Si se da esta situaci on la u nica opci on que nos queda es usar un men u con letras. En este caso ser a deseable que el programa respondiese igual si se introduce la misma letra en min uscula o en may uscula, lo cual se resuelve f acilmente en C de la siguiente manera: switch(letra){

74

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE


case A: case a: printf("Opci on a del men u\n"); break; case B: case b: printf("Opci on b del men u\n"); break; default: on no contemplada\n"); printf("Opci break; }

En este ejemplo si letra vale A el programa salta a la instrucci on que sigue a case A: y continuar a ejecut andose hasta que encuentre un break o hasta que llegue el nal del switch. Por tanto si se da este caso (letra vale A) se ejecutar a printf("Opci on a del men u\n") y despu es se saldr a del switch. Conviene destacar que la sentencia case a: es una etiqueta del switch-case, no una instrucci on ejecutable.

8.3.2. La sentencia break y los bucles


En ciertas ocasiones es necesario salir apresuradamente de un bucle, ya sea este for, while o do-while, normalmente cuando se da alguna situaci on extraordinaria, situaci on que se descubre normalmente mediante un if. Un esquema t pico de un bucle con un break es el siguiente: for(n=0; n<27; n++){ inst_1; inst_2; ... if(situacion_extraordinaria){ break; } inst_3; ... } En este ejemplo el bucle se repetir a 27 veces, salvo que se de la situaci on extraordinaria, en cuyo caso se ejecutar a la sentencia break situada dentro del if y se saldr a inmediatamente del bucle, es decir, no se ejecutar an en esta iteraci on las instrucciones inst 3 y siguientes. Conviene recalcar que en caso de que se salga del bucle con la instrucci on break el valor de la variable de control no ser a 27 como en una ejecuci on normal del bucle, sino menor.

8.3.3. Ejemplos
Tabla de logaritmos Se desea imprimir una tabla de logaritmos desde un valor inicial hasta un valor nal, en pasos decrecientes de 0.1. El usuario introducir a el valor inicial y luego el valor nal (menor que el inicial). El programa imprimir a entonces la tabla de logaritmos. La soluci on obvia a este problema es el uso de un bucle for desde el valor inicial hasta el nal. El problema de

8.3. LA SENTENCIA BREAK

75

esta soluci on es que si el usuario no sabe mucho de matem aticas, puede introducir un valor nal negativo, con lo que el programa terminar a de una forma poco amigable. Para evitar que se calculen logaritmos de n umeros menores o iguales a cero, podemos interrumpir el bucle con una sentencia break si detectamos que el bucle llega a un n umero menor o igual a cero. El programa escrito en C quedar a: /* Programa: CalcLog * * Descripci on: Calcula una tabla de logaritmos desde un valor inicial * introducido por el usuario hasta otro final, tambi en * introducido por el usuario y menor que el anterior; en * pasos de 0.1. * * Revisi on 0.0: 24/03/1998 * * Autor: El tablero aplicado. */ #include <stdio.h> #include <stdlib.h> #include <math.h> main(void) { double val_inic; /* Valor inicial */ double val_fin; /* Valor final */ double num; /* n umero usado para recorrer la tabla*/ printf("Valor Inicial = "); scanf("%lf",&val_inic); printf("Valor Final = "); scanf("%lf",&val_fin); /* imprimo una cabecera */ printf("\n numero logaritmo\n"); for(num = val_inic; num >= val_fin; num -= 0.1){ if(num <= 0.0){ break; } printf("%7.3f %7.3f\n", num, log(num) ); } } Un ejemplo de una sesi on con el programa muestra su correcto funcionamiento: Valor Incial = 1.01 Valor Final = -1.01 numero logaritmo

76 1.010 0.910 0.810 0.710 0.610 0.510 0.410 0.310 0.210 0.110 0.010

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE


0.010 -0.094 -0.211 -0.342 -0.494 -0.673 -0.892 -1.171 -1.561 -2.207 -4.605

En la que se aprecia que el bucle se interrumpe cuando num se hace igual a -0.09 gracias a la sentencia break, terminando el programa.

8.4. La sentencia continue


En ciertos casos no es necesario ejecutar la parte nal de una iteraci on dentro de un bucle cuando se da una situaci on extraordinaria, situaci on que se descubre normalmente mediante un if. En estos casos se puede utilizar la sentencia continue para volver al principio del bucle y comenzar la siguiente iteraci on, salt andonos lo que nos quedaba de la iteraci on anterior. Un esquema de este tipo de situaciones es el siguiente: for(n=0; n<27; n++){ inst_1; inst_2; ... if(condicion_extraordinaria){ continue; } inst_3; ... } En este caso siempre que sea cierta la condici on extraordinaria no se ejecutar an las instrucciones inst 3 y siguientes. Hay que destacar sin embargo que si se incrementar a el valor de n y se comparar a su valor con 27 antes de proseguir con una nueva iteraci on del bucle. El uso de la sentencia continue genera en la mayor a de los casos programas m as dif ciles de leer que si se usa un if para ejecutar la parte nal del bucle s olo cuando no sea cierta la condici on extraordinaria. As el programa anterior podr a escribirse de una manera mucho m as clara como: for(n=0; n<27; n++){ inst_1; inst_2; ... if(!condicion_extraordinaria){ inst_3; ... } }

8.4. LA SENTENCIA CONTINUE

77

Como se puede observar el ujo del programa est a mucho m as claro en este caso, pues la ejecuci on es siempre desde el principio del bucle hasta el nal, sin saltos raros por el medio.

8.4.1. Ejemplos
Tabla de senos, cosenos, logaritmos Se desea confeccionar una tabla de senos, cosenos y logaritmos (naturales y en base 10) para los n umeros comprendidos entre y espaciados a intervalos de 0.1. Para confeccionar esta tabla lo natural es realizar un bucle for desde hasta que calcule las funciones y las imprima. El problema que se nos presenta es que los logaritmos no est an denidos para n umeros menores o iguales a cero. Una posible soluci on a este problema es colocar estas dos operaciones al nal del bucle y usar la sentencia continue para saltarnos el nal del bucle siempre que el operando sea menor o igual que cero. Una posible implantaci on del programa en C podr a ser:

/* Programa: CalcTabla * * Descripci on: Calcula una tabla de senos, cosenos y logaritmos en * base natural y decimal para los n umeros comprendidos * entre -pi y pi con intervalos de 0.1 * * Revisi on 0.0: 24/03/1998 * * Autor: El tablero aplicado. */ #include <stdio.h> #include <stdlib.h> #include <math.h> main(void) { double num; /* n umero usado para recorrer la tabla*/ /* imprimo una cabecera */ printf("\n numero seno coseno

logn

log10\n");

for(num = -PI; num <= PI; num += 0.1){ /* el seno y el coseno se calculan siempre */ printf("%7.3f %7.3f %7.3f ", num, sin(num), cos(num) ); if(num <= 0.0){ printf("\n");/* si no calculo los logaritmos he de dar el salto de carro que se dar a en su printf */ continue; } printf("%7.3f %7.3f\n", log(num), log10(num)); } }

78

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE


La salida del programa se muestra a continuaci on:

numero seno coseno logn log10 -3.142 -0.000 -1.000 -3.042 -0.100 -0.995 -2.942 -0.199 -0.980 -2.842 -0.296 -0.955 ........................................ -0.342 -0.335 0.942 -0.242 -0.239 0.971 -0.142 -0.141 0.990 -0.042 -0.042 0.999 0.058 0.058 0.998 -2.840 -1.234 0.158 0.158 0.987 -1.843 -0.800 0.258 0.256 0.967 -1.353 -0.588 0.358 0.351 0.936 -1.026 -0.446 ........................................ 2.758 0.374 -0.927 1.015 0.441 2.858 0.279 -0.960 1.050 0.456 2.958 0.182 -0.983 1.085 0.471 3.058 0.083 -0.997 1.118 0.485 En donde se han eliminado l neas intermedias para ahorrar papel y no aburrir demasiado al lector. Este mismo ejemplo se podr a haber codicado sin usar continue de la siguiente manera: /* Programa: CalcTabla * * Descripci on: Calcula una tabla de senos, cosenos y logaritmos en * base natural y decimal para los n umeros comprendidos * entre -pi y pi con intervalos de 0.1 * * Revisi on 0.1: 24/03/1998 * No se hace uso ahora de la sentencia continue. on 0.0: 24/03/1998 * Revisi * * Autor: El tablero aplicado. */ #include <stdio.h> #include <stdlib.h> #include <math.h> main(void) { double num; /* n umero usado para recorrer la tabla*/ /* imprimo una cabecera */ printf("\n numero seno coseno

logn

log10\n");

for(num = -PI; num <= PI; num += 0.1){

8.5. EJERCICIOS
/* el seno y el coseno se calculan siempre */ printf("%7.3f %7.3f %7.3f ", num, sin(num), cos(num) ); if(num > 0.0){ printf("%7.3f %7.3f", log(num), log10(num)); } printf("\n"); /* Imprimo el final de la linea de la tabla */ } }

79

Que como se puede apreciar es m as f acil de entender que el anterior. N otese tambi en que la condici on del if ahora es distinta. Por qu e?

8.5. Ejercicios
1. Modicar el programa CalcLog presentado en la secci on 8.3.3 de forma que se compruebe que el usuario introduce siempre un valor inicial mayor que el valor nal, dando un error en caso contrario y saliendo del programa. 2. Modicar el programa anterior para que cuando el usuario introduzca un valor err oneo se le avise con un mensaje de error y se le vuelva a pedir de nuevo el valor nal. 3. Realizar el programa para controlar el robot de forma interactiva presentado en la secci on 8.2.3 pero usando un men u con letras, ya que el futuro usuario ha mostrado una profunda alergia a los n umeros, maniesta con dolores de cabeza, congesti on nasal y escozores en la piel. 4. Escribir el programa anterior en el ordenador. Funciona correctamente?. En caso de que no funcione correctamente Qu e funcionamiento an omalo presenta?. Si el funcionamiento an omalo es que despu es de introducir una opci on nos la ejecuta y luego sin que el usuario haga nada m as, el programa dice el solito que se ha introducido una opci on err onea a qu e puede ser debido esto? (Pista: al introducir una opci on despu es se da al intro, que el ordenador interpreta como un car acter m as llamado \n). Como se podr a solucionar este problema tan desagradable? (Esto u ltimo es para nota).

80

CAPITULO 8. CONTROL DE FLUJO: SWITCH-CASE, BREAK Y CONTINUE

Cap tulo 9

Vectores y Matrices
9.1. Introducci on
En el cap tulo 3 se han descrito los tipos de datos b asicos que permiten declarar variables. Pero estos tipos de datos s olo permiten construir variables que almacenen un u nico valor. En este cap tulo se ve c omo denir variables capaces de almacenar m as de un valor, lo que nos permite trabajar con mayor comodidad con grandes conjuntos de datos. Estas variables se utilizan principalmente para trabajar con vectores, cadenas de caracteres y matrices.

9.2. Vectores
Un vector es un conjunto de datos del mismo tipo que se almacenan en el ordenador en posiciones de memoria consecutivas y a los cuales se accede mediante un mismo nombre de variable. La caracter stica fundamental es que todos los datos de un vector son del mismo tipo, por ejemplo todos son int o todos son double. La denici on de vectores es parecida a la denici on de variables, salvo que se debe especicar el tama no del vector entre corchetes [ ]. Por ejemplo, para denir un vector llamado vec que pueda almacenar 10 valores tipo double se escribe: double vec[10]; La manera de acceder a los valores de estas variables es ahora diferente, ya que de todos los elementos que componen el vector es necesario especicar cu al queremos modicar. Esta especicaci on se realiza indicando entre corchetes el n umero de orden del elemento, teniendo en cuenta que la numeraci on de los elementos siempre empieza en cero 1 . vec[0]=54.23; vec=4.5; vec[10]=98.5; /*Primer elemento del vector*/ /* ERROR */ /* ERROR */

La segunda asignaci on no es correcta porque vec no es una variable tipo double sino un vector y por lo tanto todas las asignaciones deben indicar el n umero de orden del elemento al que queremos acceder. El vector vec del ejemplo tiene tama no 10 porque fue declarado como
1 En matem aticas el primer elemento de los vectores suele ser el elemento 1 y por lo tanto en el vector a se habla de a1 como primer elemento y en la matriz A se habla de A1 1

81

82

CAPITULO 9. VECTORES Y MATRICES

double vec[10] esto signica que almacena 10 elementos. Los 10 elementos se numeran desde el 0 hasta el 9 y por lo tanto, el elemento 10 no existe y la tercera asignaci on tampoco es v alida. Es importante destacar que el compilador no dar a error en el u ltimo caso ya que no comprueba los rangos de los vectores. Este tipo de asignaci on hace que el valor 98.5 se escriba fuera de la zona de memoria correspondiente al vector vec, y probablemente machaca los valores de otras variables del programa. Hay que prestar especial atenci on ya que s olo en algunos casos aparece un error de ejecuci on (generalmente cuando el ndice del vector es muy grande) pero en otras ocasiones no aparece ning un mensaje de error y el programa simplemente funciona mal. Como se puede sospechar, la mejor manera de trabajar con vectores es utilizando bucles for. El siguiente ejemplo declara un vector de tama no 10, lo inicializa d andole valores crecientes y luego escribe todo el contenido del vector.

/********************* Programa: vector.c Descripci on: Inicializa un vector de enteros y luego escribe su contenido por pantalla. *********************/ #include <stdio.h> main(void) { double x[10]; int i; /* inicializaci on */ for(i=0; i<10; i++) { x[i]=2*i; } /* imprimir valores */ for(i=0; i<10; i++) { printf("El elemento %d vale %4.1f\n",i,x[i]); } } --- SALIDA -------------------El elemento 0 vale 0.0 El elemento 1 vale 2.0 El elemento 2 vale 4.0 El elemento 3 vale 6.0 El elemento 4 vale 8.0 El elemento 5 vale 10.0 El elemento 6 vale 12.0 El elemento 7 vale 14.0 El elemento 8 vale 16.0 El elemento 9 vale 18.0

9.3. CADENAS DE CARACTERES

83

9.3. Cadenas de caracteres


Las cadenas de caracteres son conjuntos de letras que forman palabras o frases. Por ejemplo el nombre de una persona o su direcci on se almacenan en forma de cadenas de caracteres dentro de la memoria del ordenador. En el lenguaje de programaci on C no existe un tipo de datos espec co para manejar cadenas de caracteres, sino que e stas se almacenan en vectores de datos tipo char. Si por ejemplo queremos denir una variable capaz de almacenar nombres de personas de hasta 10 caracteres de largo, tendremos que denir un vector de char de longitud 10 de la manera siguiente: char nombre[10]; Utilizando este tipo de denici on puede deducirse que siempre se puede acceder a cualquier car acter de la cadena de forma individual. Esto se hace especicando el ndice del vector al que queremos acceder. Un programa an alogo al anterior, que inicializa e imprime un vector, pero aplicado a cadenas de caracteres, es el siguiente: /********************* Programa: cadenas1.c Descripci on: Inicializa un vector de char y luego escribe el contenido del vector car acter por car acter. Revisi on 0.0: 14/ABR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { char nombre[10]; int i; /* inicializaci on */ nombre[0]=H; nombre[1]=o; nombre[2]=l; nombre[3]=a; /* imprimir valores */ for(i=0; i<4; i++) { printf("%c",nombre[i]); } printf("\n"); } En el programa anterior s olo hay cuatro valores en la cadena de caracteres, por lo tanto el n umero 4 se utiliza para controlar el nal del bucle. A pesar de que la variable nombre tiene espacio para almacenar 10 caracteres, ser a un error poner el valor 10 como control de nal de bucle ya que los elementos quinto y siguientes no est an inicializados. Si hubi esemos puesto el valor 10 en lugar 4 en el bucle for la salida del programa podr a ser:

84 Hola!@#$%&

CAPITULO 9. VECTORES Y MATRICES

Las cadenas de caracteres suelen tener una longitud variable; es decir, aunque tengamos memoria reservada para almacenar nombres de 10 caracteres unas veces tendremos nombres largos y otras veces tendremos nombres cortos. A la hora de imprimir, copiar o comparar estos nombres es necesario conocer su longitud.

9.3.1. Cadenas de caracteres de longitud variable


B asicamente existen dos m etodos para manejar cadenas de caracteres de longitud variable dentro de un ordenador. El primero es almacenar un valor entero que nos indique cu antas letras est an escritas realmente (en el ejemplo anterior este valor ser a 4). Este m etodo requiere almacenar en alg un lado el valor de la longitud lo que puede resultar algo inc omodo. Una soluci on ser a utilizar el primer elemento del vector de char para almacenar la longitud, de esta manera el vector almacena la longitud y los caracteres conjuntamente. El programa anterior quedar a de la siguiente manera: /********************* Programa: cadenas2.c Descripci on: Inicializa un vector de char y luego escribe el contenido del vector caracter por caracter. Revisi on 0.0: 14/ABR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { char nombre[10]; int i; /* inicializaci on */ nombre[0]=4; nombre[1]=H; nombre[2]=o; nombre[3]=l; nombre[4]=a; /* imprimir valores */ for(i=1; i<=nombre[0]; i++) printf("%c",nombre[i]); } printf("\n"); } Notar que el valor inicial y la condici on del bucle for han cambiando. Esta soluci on es v alida aunque tiene dos inconvenientes fundamentales: Se utiliza un car acter para guardar la longitud de la cadena y por lo tanto en lugar de 10 letras s olo podemos almacenar un m aximo de 9.

9.3. CADENAS DE CARACTERES

85

La longitud m axima de la cadena de caracteres es 127, ya que el valor m aximo que puede almacenarse en la variable nombre[0] es 127 por ser de tipo char. La segunda manera de manejar cadenas de caracteres de longitud variable es utilizar un car acter especial para indicar el nal de la cadena. Este es el m etodo utilizando normalmente en el lenguaje C y existen un mont on de funciones dentro de la biblioteca est andar de C para manejar cadenas de caracteres basadas en este m etodo. Las caracter sticas de este m etodo son: En un vector de tama no n s olo caben n ocupa una posici on.

1 letras, ya que el car acter de nal de cadena

No existe l mite en la longitud de la cadena, el u nico l mite viene dado por el tama no del vector. Si se dene un vector de tama no 1000, como por ejemplo char nombre[1000], se pueden almacenar sin ning un problema hasta 999 caracteres. En cada posici on de la cadena se puede almacenar cualquier car acter, a excepci on del car acter denido como nal de cadena. Dado que existe un car acter que indica el nal de la cadena, no es posible almacenar en ella cualquier tipo de informaci on ya que dicho car acter no puede formar parte de la cadena. Para evitar el mayor n umero de problemas, en el lenguaje C se ha elegido como car acter de nal de cadena el car acter n umero 0 de la tabla ASCII (tambi en llamado NULL o car acter nulo y representado mediante \0). Existen muchos caracteres de uso poco frecuente, como por ejemplo el car acter %, que podr an haberse utilizado para marcar el nal de la cadena. Sin embargo estos caracteres dar an problemas antes o despu es. Si por ejemplo se desea realizar un programa para imprimir octavillas con la frase 0,7 % YA para pedir mayor justicia en este mundo enfermo en el que el 20 % de la poblaci on de los paises del norte posee el 80 % de la riqueza, el ordenador s olo imprmir a 0,7, lo cual perturbar a un poco el mensaje original. El car acter seleccionado en C, \0, no da problemas porque nunca se utiliza como parte de una cadena de caracteres, ya que no es un car acter imprimible. Usando esta t ecnica, el programa anterior puede escribirse de la siguiente manera: /********************* Programa: cadenas3.c Descripci on: Inicializa un vector de char y luego escribe el contenido del vector caracter por caracter hasta llegar al caracter NULL. on 0.0: 14/ABR/1998 Revisi Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { char nombre[10]; int i; /* inicializaci on */ nombre[0]=H; nombre[1]=o; nombre[2]=l; nombre[3]=a;

86 nombre[4]=\0; /* imprimir valores */ for(i=0; nombre[i]!=\0; i++) printf("%c",nombre[i]); } printf("\n"); }

CAPITULO 9. VECTORES Y MATRICES

Hay que destacar que este bucle for escribe los caracteres en pantalla siempre que e stos sean sean diferentes del car acter NULL. Cuando el contador i alcance la posici on en la que as este bucle est a almacenado el car acter NULL, el bucle termina sin hacer el printf. Adem es v alido incluso si la cadena de caracteres est a vac a; es decir, si el primer car acter es directamente el car acter NULL entonces el bucle no arranca y no se escribe nada. Otra manera de escribir este bucle, pero utilizando while en lugar de for es la siguiente: i=0; while(nombre[i] != NULL) { printf("%c",nombre[i]); i++; } Por haber elegido el car acter 0 como indicador de nal de cadena, y gracias a que las condiciones de los bucles s olo se consideran falsas cuando valen 0, la condici on del while puede ser el propio car acter nombre[i]. Lo mismo ocurre en el ejemplo del bucle for, donde la condici on tambi en puede ser nombre[i] en lugar de nombre[i]!=\0. Sin embargo el c odigo resultante queda menos claro.

9.3.2. Funciones est andar para manejar cadenas de caracteres


Existen una serie de funciones en la biblioteca est andar del lenguaje C que facilitan el manejo de cadenas de caracteres, siempre que estas cadenas cumplan la norma de estar termion se muestran algunas de estas funciones, con una nadas con un car acter \0. A continuaci breve explicaci on y un peque no programa que simula su comportamiento. Es m as interesante comprender el funcionamiento de los programas que aprender lo que hace cada una de las funciones (dado que esto gura en el manual del compilador). Entendiendo estos programas no es dif cil crear nuevas funciones espec cas que no se incluyen en la biblioteca est andar, como por ejemplo funciones para cambiar min usculas por may usculas. Todas las funciones que se ven a continuaci on est an denidas en el archivo string.h por lo tanto hay que incluir este archivo al principio del programa para que el compilador pueda comprobar que las estamos utilizando correctamente. Sin embargo todas estas funciones son parte de la biblioteca est andar y no hace falta a nadir ninguna biblioteca al enlazar el programa. Longitud de una cadena: strlen(), string length strlen calcula la longitud de una cadena de caracteres. Este c alculo se realiza recorriendo la cadena hasta encontrar el car acter \0. /********************* Programa: cadenas4.c Descripci on: Inicializa un vector de char y luego escribe

9.3. CADENAS DE CARACTERES


el contenido del vector car acter por car acter. Utiliza la funci on strlen para obtener la longitud de la cadena de caracteres. Revisi on 0.0: *********************/ #include <stdio.h> #include <string.h> main(void) { char nombre[10]; int i; int longitud; /* inicializaci on */ nombre[0]=H; nombre[1]=o; nombre[2]=l; nombre[3]=a; nombre[4]=\0; /* calculo la longitud */ longitud=strlen(nombre); /* imprimir valores */ for(i=0; i<longitud; i++) { printf("%c",nombre[i]); } printf("\n"); }

87

N otese que en la llamada a strlen se pone directamente el nombre de la variable (sin corchetes []). Este programa es equivalente al siguiente, que no utiliza strlen. /********************* Programa: cadenas5.c Descripci on: Inicializa un vector de char y luego escribe el contenido del vector caracter por caracter. Calcula la longitud de la cadena de caracteres mediante un bucle while. Revisi on 0.0: 14/ABR/1998 Autor: Rafael Palacios *********************/ #include <stdio.h> main(void) { char nombre[10];

88 int i; int longitud; /* inicializaci on */ nombre[0]=H; nombre[1]=o; nombre[2]=l; nombre[3]=a; nombre[4]=\0; /* calculo la longitud */ longitud=0; while(nombre[longitud] != 0) longitud++; } /* imprimir valores */ for(i=0; i<longitud; i++) { printf("%c",nombre[i]); } printf("\n"); } Copiar o inicializar: strcpy(), string copy

CAPITULO 9. VECTORES Y MATRICES

Esta funci on copia cadenas de caracteres. Es importante recordar que no es v alido asignar una constante a un vector y por lo tanto las siguientes instrucciones son incorrectas: vec=4.5; /* Error si vec es vector*/ nombre="Pepito"; /* Error */ Esta funci on no verica que el tama no del vector de destino sea sucientemente grande para guardar la cadena que se quiere copiar, por lo que el programador ser a responsable de garantizar que esto ocurra para no producir fallos indeseables en el programa. El formato de la llamada a strcpy es el siguiente: strcpy(destino, origen); es decir, tiene una sintaxis equivalente a la asignaci on normal de variables, del tipo: destino=origen; Esta funci on es muy u til para inicializar cadenas de caracteres, poniendo una constante entre comillas dobles como argumento origen. Esta inicializaci on evita tener que denir car acter por car acter el vector. Los siguientes principios de programa son equivalentes: #include <string.h> main(void) { char nombre[10];

main(void) { char nombre[10];

9.3. CADENAS DE CARACTERES


/* inicializaci on */ nombre[0]=H; nombre[1]=o; nombre[2]=l; nombre[3]=a; nombre[4]=\0; : : : /* inicializaci on */ strcpy(nombre,"Hola");

89

: : :

Es importante destacar que la segunda versi on incluye el archivo string.h para utilizar la funci on strcpy y que no hace falta escribir el car acter \0 porque est a impl cito en todas las cadenas de caracteres constantes (aquellas que se escriben directamente en el c odigo encerradas entre comillas). El siguiente programa explica el comportamiento de la funci on strcpy para copiar la variable a en la variable dir (ambas son vectores de char de tama no suciente). for(i=0; a[i]!=\0; i++) { dir[i]=a[i]; } dir[i]=a[i]; /* Copio el NULL */ Este bucle for copia car acter a car acter la variable a en la variable dir, borrando la informaci on que pudiese tener previamente. La u ltima l nea de este c odigo es fundamental, ya que garantiza que la variable dir tenga car acter de nal de cadena. Cuando a[0] vale \0 el bucle no arranca y la u ltima l nea hace dir[0]=a[0] por lo tanto copia el car acter NULL. Cuando la cadena a no est a vac a, el bucle copia car acter por car acter todas las letras hasta llegar a NULL (car acter que no copia) entonces la u ltima l nea copia el NULL en su sitio. Comparaci on de cadenas: strcmp(), string compare Esta funci on compara cadenas de caracteres. Principalmente se utiliza para ver si dos nombres son iguales o para ordenarlos alfab eticamente. La funci on devuelve un valor de tipo int que puede ser cero, negativo o positivo. Por ejemplo la comparaci on de las variables nombre1 y nombre2 puede dar los siguientes resultados: i=strcmp(nombre1,nombre2); La variable entera i tomar a los siguientes valores: i=0 si nombre1 es igual a nombre2. Esto signica que todos los caracteres del vector nombre1 desde el ndice 0 hasta el car acter NULL (incluyendo a e ste) son iguales a los correspondientes caracteres del vector nombre2. Pero puede ocurrir que el resto de los caracteres (desde el car acter NULL hasta el nal del vector) sean diferentes, lo cual da lo mismo, ya que estos caracteres no forman parte de la cadena. Tambi en puede ocurrir que los vectores sean de diferente tama no, ya que s olo se comparan los caracteres anteriores a NULL. i<0 si nombre1 es menor que nombre2, entendiendo que nombre1 se ordena alfab eticamente por delante de nombre2. En este caso hay que tener en cuenta que la ordenaci on sigue los criterios de la tabla ASCII; es decir los n umeros son menores que las letras, las letras may usculas son menores que las min usculas y las letras acentuadas son mayores

90

CAPITULO 9. VECTORES Y MATRICES


que cuaquier otro car acter normal. Por ejemplo: 3Com < Andaluc a < Sevilla < boreal < n ordico < Africa i>0 si nombre1 es mayor que nombre2. Un programa que tiene un funcionamiento equivalente a strcmp(a,b) es: i=0; resultado=0; while(a[i]!=\0 && resultado==0) { if (a[i]<b[i]) { resultado=-1; } else if (a[i]>b[i]) { resultado=1; } i++; } if (resultado==0) { /* el while termin o porque a[i] vale \0 */ if (b[i]!=\0) { /* la cadena b es m as larga que a */ resultado=-1; } }

Ejercicios 1. Comprobar el funcionamiento del programa para distintos ejemplos. Comprobar si funciona para palabras como fulmina y fulminante, alumno y alumna, etc. Da lo mismo en qu e variables (a o b) se almacenen las palabras? Visualizaci on de cadenas de caracteres La funci on printf puede utilizarse para visualizar cadenas de caracteres, no hace falta ninguna otra funci on especial. Cuando se pone el c odigo %s dentro de la especicaci on de formato, la funci on printf se encarga de escribir todos los caracteres del vector de char hasta encontrar el car acter NULL. i=0; while(nombre[i]) { printf("%c",nombre[i]); } printf("\n\n"); El programa anterior es equivalente a la instrucci on: printf("%s\n\n",nombre); N otese que ahora no se ponen corchetes, ya que queremos pasar a la funci on printf el vector de char nombre completo, no uno de sus elementos.

9.3. CADENAS DE CARACTERES


Lectura de cadenas de caracteres desde teclado

91

La funci on scanf() puede utilizarse para leer cadenas de caracteres desde teclado utilizando el formato %s, al igual que se hace con printf. Ejemplo: /******** Programa: Lectura.c Descripci on: Lee una cadena de caracteres con scanf Revisi on 0.0 ********/ #include <stdio.h> main(void) { char a[100]; scanf("%s",a); printf("Has escrito: %s\n",a); } Es importante tener en cuenta que el programa no puede saber cu antos caracteres va a escribir el usuario y por lo tanto la variable a debe tener un tama no sucientemente grande. En este ejemplo se ha jado el tama no del vector a en 100 elementos, por lo tanto el usuario puede escribir hasta 99 caracteres sin que falle el programa. Tambi en es importante tener en cuenta que la funci on scanf realiza un manejo un poco especial de los caracteres espacio (ASCII 32). La funci on scanf ignora todos los espacios que preceden a una palabra y deja de leer del teclado cuando encuentra un espacio; es decir, s olo vale para leer palabras sueltas. Una buena alternativa cuando se desea leer una frase completa es utilizar la funci on gets(). La funci on gets() permite leer desde teclado cadenas de caracteres que contengan espacios. Al leer una cadena de caracteres con gets todos los espacios que escriba el usuario se almacenan en la cadena. /******** Programa: Lectura2.c Descripci on: Lee una cadena de caracteres con gets Revisi on 0.0 ********/ #include <stdio.h> main(void) { char a[100]; gets(a); printf("Has escrito: %s\n",a); } Ejercicios 1. Escribir un programa que lea una cadena con scanf y con gets, comparando los resultados. Introducir una cantidad de caracteres mayor que la dimensi on de la cadena y ver

92 si se produce alg un resultado an omalo.

CAPITULO 9. VECTORES Y MATRICES

9.4. Matrices
La denici on y utilizaci on de matrices es an aloga al caso de los vectores, salvo que ahora tenemos dos dimensiones. Una matriz mat de tama no 3x5 para n umeros enteros se dene como: int mat[3][5]; El acceso a los 15 elementos de esta matriz se realiza siempre utilizando dos ndices que tambi en se escriben entre corchetes y que tambi en empiezan por cero. Por lo tanto los elementos de la matriz mat tiene los siguientes nombres: mat[0][0] mat[1][0] mat[2][0] mat[0][1] mat[1][1] mat[2][1] mat[0][2] mat[1][2] mat[2][2] mat[0][3] mat[1][3] mat[2][3] mat[0][4] mat[1][4] mat[2][4]

Al igual que en el caso de los vectores, no existen funciones especiales para imprimir ni operar con matrices por lo que generalmente se utilizan bucles for. En el caso de las matrices de dos dimensiones, como la del ejemplo anterior, se utilizan dos bucles for anidados para recorrer todos los elementos. El siguiente programa dene una matriz de 3x5 e inicializa a 7 todos sus elementos: /********************* Programa: matriz.c Descripci on: Inicializa a 7 todos los elementos de una matriz de enteros de 3x5 Revisi on 0.0: 14/ABR/1998 Autor: Rafael Palacios *********************/ main(void) { int mat[3][5]; int i,j; for(i=0; i<3; i++) { for(j=0; j<5; j++) mat[i][j]=7; } } }

9.5. Utilizaci on de #define con vectores y matrices


Resulta muy u til denir los tama nos de vectores y matrices en funci on de par ametros, ya que luego se pueden modicar f acilmente sin tener que revisar todo el programa. Estos par ametros se denen con la instrucci on #define que es una directiva del preprocesador, al igual que #include.

DE #DEFINE CON VECTORES Y MATRICES 9.5. UTILIZACION

93

El programa anterior quedar a de la siguiente manera al utilizar un par ametro N para el n umero de las y un par ametro M para el n umero de columnas. /********************* Programa: matriz2.c Descripci on: Inicializa a 7 todos los elementos de una matriz de enteros de N por M. Se utiliza #define para definir el valor de N y M. on 0.0: 14/ABR/1998 Revisi Autor: Rafael Palacios *********************/ #define N 3 #define M 5 main(void) { int mat[N][M]; int i,j; for(i=0; i<N; i++) { for(j=0; j<M; j++) mat[i][j]=7; } } } Si se quiere modicar el programa para que trabaje con matrices de tama no 30x50 basta con modicar las dos primeras l neas y volver a compilar. Todo el programa est a escrito en funci on de los dos par ametros, tanto el tama no de las matrices como los l mites de los bucles, y por lo tanto es v alido para cualquier valor de N y M. Ya se dijo en el cap tulo 3 que los nombres de variables suelen escribirse en min usculas, para mayor claridad al leer el programa. Para diferenciar las variables de los par ametros, e stos suelen escribirse en may usculas, como se ha hecho en el ejemplo. En caso de querer preguntar al usuario el tama no de las matrices con las que quiere calcular, habr a que denir tama nos jos sucientemente grandes (ya se ver a m as adelante c omo se denen matrices y vectores de tama no variable). Al utilizar tama nos jos es imprescindible comprobar, antes de iniciar los c alculos, que el valor introducido por el usuario no sea mayor que el tama no asignado para las matrices. El compilador no comprueba si se accede a elementos que se salen del vector por lo que el programa puede fallar estrepitosamente sin darnos muchas pistas. El siguiente ejemplo es un programa para multiplicar una matriz por un vector. /* Programa: Matriz1 Descripci on: Calcula el producto de una matriz por un vector Revisi on 0.1: mar/98 Autor: Rafael Palacios Hielscher */ #include <stdlib.h> #include <stdio.h>

94 #define N 100 #define M 100

CAPITULO 9. VECTORES Y MATRICES

main(void) { double mat[N][M]; /* Matriz */ dobule vec[M]; /* vector */ int i; /* contador */ int j; /* otro contador (necesario para recorrer la matriz)*/ int n; /* n umero de filas de la matriz */ int m; /* n umero de columnas de la matriz, que es igual al n umero de elementos del vector*/ double tmp; /* variable temporal */ printf("N umero de filas? "); scanf("%d",&n); printf("N umero de columnas? "); scanf("%d",&m); if (n>N || m>M) { printf("Error, este programa no vale para " "tama nos tan grandes.\n"); printf("Adi os\n"); exit(1); } /*** Lectura de datos ***/ /* matriz */ for(i=0; i<n; i++) { for(j=0; j<m; j++) { printf("mat[%d][%d]= ",i,j); scanf("%lf",&mat[i][j]); } } /* vector */ for(i=0; i<m; i++) { printf("vec[%d]= ",i); scanf("%lf",&vec[i]); } /*** C alculos y Salida ***/ for(i=0; i<n; i++) { tmp=0; for(j=0; j<m; j++) { tmp+=mat[i][j]*vec[j]; } printf("Resultado[%d]=%f\n",i,tmp); } }

9.6. EJERCICIOS

95

9.6. Ejercicios
1. Escribir un programa que pregunte el tama no de una matriz cuadrada, que pregunte todos sus elementos y que calcule la traspuesta. Luego debe mostrar el resultado ordenadamente (en forma de matriz). 2. Bas andose en el programa del producto de matriz por vector (programa Matriz1), escribir un programa para calcular el producto de dos matrices a y b. El resultado debe guardarse temporalmente en la matriz c y luego debe mostrarse por pantalla.

96

CAPITULO 9. VECTORES Y MATRICES

Cap tulo 10

Funciones
10.1. Introducci on
Una t ecnica muy empleada en la resoluci on de problemas es la conocida vulgarmente como divide y vencer as. Los programas de ordenador, salvo los m as triviales, son problemas cuya soluci on es muy compleja y a menudo dan aut enticos dolores de cabeza al programador. La manera m as elegante de construir un programa es dividir la tarea a realizar en otras tareas m as simples. Si estas tareas m as simples no son a un lo sucientemente sencillas, se vuelven a dividir, procediendo as hasta que cada tarea sea lo sucientemente simple como para resolverse con unas cuantas l neas de c odigo. A esta metodolog a de dise no se le conoce como dise no de arriba-abajo, mundialmente conocido como top-down por aquello del ingl es. Otras ventajas de la divisi on de un problema en m odulos claramente denidos es que facilita el trabajo en equipo y permite reutilizar m odulos creados con anterioridad si e stos se han dise nado de una manera generalista. Esta metodolog a se hace imprescindible hasta en los programas m as sencillos; por ello, todos los lenguajes de programaci on tienen soporte para realizar c omodamente esta divisi on en tareas simples. En el caso de C el mecanismo usado para dividir el programa en trozos son las funciones (que en otros lenguajes de programaci on como el FORTRAN o el BASIC, son llamadas subrutinas). Una funci on en C consta de unos argumentos de entrada, una salida, y un conjunto de instrucciones que denen su comportamiento. Esto permite aislar la funci on del resto del programa, ya que la funci on puede considerarse como un programa aparte que toma sus argumentos de entrada, realiza una serie de operaciones con ellos y genera una salida; todo ello sin interactuar con el resto del programa. Esta metodolog a de dise no presenta numerosas ventajas, entre las que cabe destacar: La complejidad de cada tarea es mucho menor que la de todo el programa, siendo abordable. Se puede repartir el trabajo entre varios programadores, encarg andose cada uno de ellos de una o varias funciones de las que se compone el programa. Esto permite cosas tales como reducir el tiempo total de programaci on (si tenemos un ej ercito de programadores), el que cada grupo de funciones las realice un especialista en el tema (por ejemplo las de c alculo intensivo las puede realizar un matem atico, las de contabilidad un contable. . . ). 97

98

CAPITULO 10. FUNCIONES


Al ir construyendo el programa por m odulos se pueden ir probando estos m odulos conforme se van terminando, sin necesidad de esperar a que se termine el programa completo. Esto hace que, tanto la prueba de los m odulos, como la correcci on de los errores cometidos en ellos, sea mucho m as f acil al tener que abarcar solamente unas cuantas l neas de c odigo, en lugar de las miles que tendr a el programa completo. Una vez construido el programa, tambi en el uso de funciones permite depurar los problemas que aparezcan m as f acilmente, pues una vez identicada la funci on que falla, s olo hay que buscar el fallo dentro de dicha funci on y no por todo el programa. Permite usar funciones creadas por otros programadores y almacenadas en bibliotecas. Esto ya se ha venido haciendo durante todo el curso. Funciones como printf(), scanf(), strlen()... son funciones creadas por los programadores del compilador y almacenadas en la biblioteca est andar de C. Adem as existen innidad de bibliotecas, muchas de ellas de dominio p ublico, para la realizaci on de tareas tan diversas como c alculos matriciales, resoluci on de sistemas de ecuaciones diferenciales, bases de datos, interfaz con el usuario. . . Si se realizan lo sucientemente generales, las tareas se puede reutilizar en otros programas. As por ejemplo, si en un programa es necesario convertir una cadena a may usculas y realizamos una funci on que realice dicha tarea, esta funci on se podr a usar en otros programas sin ning un cambio. Si por el contrario la tarea de convertir a may usculas se incrusta dentro del programa, su reutilizaci on ser a much simo m as dif cil.

10.2. Estructura de una funci on


Como se ha dicho en la introducci on, una funci on tiene unos argumentos de entrada, un valor de salida y una serie de instrucciones que forman el cuerpo de la funci on. Por ejemplo una funci on que devuelve el cuadrado de su argumento se escribir a de la siguiente manera: double Cuad(double x) { return x*x; } La sintaxis gen erica de la denici on de una funci on es la siguiente: tipo devuelto NombreFunci on(tipo 1 argumento 1,. . . ,tipo n argumento n) { instrucci on 1; instrucci on 2; ... instrucci on n; return expresi on devuelta; } en donde: tipo devuelto es el tipo del dato que devuelve la funci on. Si se omite, el compilador de C supone que es un int; pero para evitar errores es importante especicar el tipo siempre. Si la funci on no devuelve ning un valor ha de usarse el especicador de tipo void.

10.3. PROTOTIPO DE UNA FUNCION

99

NombreFunci on es el nombre de la funci on y ser a usado para llamar a la funci on desde cualquier parte del programa. Este nombre tiene las mismas limitaciones que los nombres de las variables discutidos en la secci on 3.2. La u nica diferencia es que generalmente los nombres compuestos suelen escribirse mezclando may usculas y min usculas, por ejemplo una funci on que borre la pantalla podr a llamarse BorrarPantalla() 1 . tipo 1 argumento 1,. . . ,tipo n argumento n es la lista de argumentos que recibe la funci on. Estos argumentos, tambi en llamados par ametros2, pueden ser cualquier tipo de dato de C: n umeros enteros o reales, caracteres, vectores. . . { es la llave que marca el comienzo del cuerpo de la funci on. instrucci on 1. . . instrucci on n son las instrucciones que componen el cuerpo de la funci on y se escriben con un sangrado de algunos espacios para delimitar m as claramente d onde comienza y d onde termina la funci on. return expresi on devuelta; es la u ltima instrucci on de la funci on y hace que e sta tera mine y devuelva el resultado de la evaluaci on de expresio n devuelta a quien la hab llamado. Si el tipo devuelto de la funci on es void, se termina la funci on con la instrucci on return; (sin expresi on devuelta). S olo en este caso se puede omitir la instrucci on return, con lo cual la funci on termina al llegar a la llave }. } es la llave que cierra el cuerpo de la funci on.

10.3. Prototipo de una funci on


En la secci on anterior se ha visto c omo se dene una funci on, pero para que una funci on pueda usarse en otras partes del programa es necesario colocar al principio de e ste lo que se denomina el prototipo de la funci on. La misi on de este prototipo es la de declarar la funci on al resto del programa, lo cual permite al compilador comprobar que cada una de las llamadas a la funci on es correcta, es decir, el compilador verica: Que los argumentos son correctos, tanto en n umero como en tipo, con lo que se evitan errores como el olvido de un par ametro o suministrar un par ametro de un tipo err oneo. En este u ltimo caso el prototipo permite realizar una conversi on de tipos si e sta es posible. Por ejemplo si una funci on que tiene como argumento un double recibe un int se realizar a autom aticamente la conversi on de int a double. Si la conversi on no es posible, se generar a un error; as , si a una funci on que tiene como argumento una matriz se le suministra en la llamada un entero el compilador generar a un error, evit andose la creaci on de un programa que fallar a estrepitosamente cuando se realizara la llamada err onea. Que el uso del valor devuelto por la funci on sea acorde con su tipo. Por ejemplo no se puede asignar a una variable el valor devuelto por una funci on si la variable es de tipo int y la funci on devuelve un vector.
1 Para distinguir a una funci on de una variable cuando se habla de ella, se termina su nombre con dos par entesis, tal como se acaba de hacer. 2 No confundir los par ametros de una funci on con los par ametros del programa, denidos mediante sentencias #define.

100

CAPITULO 10. FUNCIONES

El uso de prototipos es opcional: el est andar de C s olo obliga a declarar las funciones si e stas devuelven un tipo distinto de int. Sin embargo el uso de prototipos es extremadamente recomendable pues permite al compilador detectar errores que, de no existir el prototipo, pasar an inadvertidamente al programa y e ste fallar a estrepitosamente al intentar ejecutarlo. En un intento de ahorrar al alumno innumerables dolores de cabeza en las llamadas a las funciones, en este curso cualquier funci on que aparezca sin su correspondiente prototipo implicar a una grave sanci on para el alumno (materializable en bajada de puntos, multas, tironcillos de orejas. . . ) La sintaxis del prototipo de una funci on es la siguiente: tipo devuelto NombreFunci on(tipo 1 argumento 1,. . . ,tipo n argumento n); Que como puede observarse se corresponde con la primera l nea de la denici on de la funci on pero terminada en un punto y coma, con lo que los m as avispados ya habr an adivinado que la t ecnica del cortar y pegar ser a sumamente u til en la escritura de prototipos.

10.4. Ejemplo
Para aclarar ideas veamos un ejemplo sencillo que ilustra el uso de una funci on dentro de un programa. Se necesita realizar un programa para calcular la suma de una serie de n umeros reales de la forma:

n a

a 1

La manera correcta de realizar la suma de la serie es denir una funci on para ello. Esto permitir a aislar esta tarea del resto del programa, poder usar la funci on en otros programas y todas las ventajas adicionales discutidas en la introducci on. La denici on de la funci on se realizar a de la siguiente manera: 37 /* Funci on: SumaSerie() 38 * 39 * Descripci on: Suma la serie aritm etica comprendida entre sus 40 * argumentos a y b. 41 * 42 * Argumentos: int a: Valor inicial de la serie. 43 * int b: Valor final de la serie. 44 * 45 * Valor devuelto: int: Resultado de la serie. 46 * 47 * Revisi on 0.1: 19/04/1998. 48 * 49 * Autor: El funcionario novato. 50 */ 51 52 int SumaSerie(int a, int b) 53 { 54 int suma; /* variable para almacenar la suma parcial de la serie*/ 55 56 suma = 0;

10.4. EJEMPLO
57 58 while(a<=b){ 59 suma +=a; 60 a++; 61 } 62 return suma; 63 }

101

En primer lugar cabe destacar la cha de la funci on. Es an aloga a la cha del programa pero a nadiendo la lista de argumentos y el valor devuelto. Esta cha es la parte m as importante de la funci on de cara a su reutilizaci on en otros programas, tanto por nosotros como por otros programadores, pues sin necesidad de leer ni una sola l nea de c odigo de la funci on podemos saber la tarea que realiza, los datos que necesitamos suministrarle y el valor que nos devuelve. Ni que decir tiene que en este curso la ausencia de la cha en una denici on de funci on implicar a castigos desmedidos contra el alumno. A continuaci on de la cha se encuentra la denici on de la funci on, en la que apreciamos en la primera l nea (52) el tipo devuelto, el nombre de la funci on y su lista de argumentos entre par entesis3 . En la siguiente l nea se encuentra la llave { que delimita el comienzo del cuerpo de la funci on. A continuaci on, ya dentro del cuerpo de la funci on, se encuentran las deniciones de las variables necesarias en el interior de la funci on. Estas variables se denominan variables locales puesto que s olo son accesibles desde dentro de la funci on. En este ejemplo la variable suma, declarada en la l nea 54, s olo es conocida dentro de la funci on y por tanto su valor s olo puede ser le do o escrito desde dentro de la funci on, pero no desde fuera 4. Despu es de las deniciones de las variables locales se encuentran las instrucciones necesarias para que la funci on realice su tarea. Cabe destacar que, tal como se aprecia en las l neas 58, 59 y 60, el uso de los par ametros de la funci on dentro del cuerpo de e sta se realiza de la misma manera que el de las dem as variables. on devolviendo Por u ltimo en la l nea 62 se usa la instrucci on return para salir de la funci el valor de suma. Veamos a continuaci on el resto del programa para ilustrar el modo de llamar a una funci on. 1 /* Programa: SumaSerie 2 * 3 * Descripci on: Calcula la suma de una serie aritm etica entre un valor 4 * inicial y un valor final. Para ello se apoya en la 5 * funci on SumaSerie(). 6 * 7 * Revisi on 0.1: 19/04/1998 8 * 9 * Autor: El funcionario novato. 10 */ 11 12 #include <stdio.h>
que no se termina esta sentencia con un punto y coma las variables locales se crean al entrar en la funci on y se destruyen al salir de ella, por lo que f sicamente es imposible conocer o modicar sus valores fuera de la funci on porque las variables ni siquiera existen.
4 De hecho 3 N otese

102 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <stdlib.h> /* prototipos de las funciones */ int SumaSerie(int a, int b);

CAPITULO 10. FUNCIONES

main(void) { int inicial; /* Valor inicial de la serie */ int final; /* Valor final de la serie */ int resultado; /* Resultado de la suma de la serie */ printf("Valor inicial de la serie: "); scanf("%d", &inicial); printf("Valor final de la serie: "); scanf("%d", &final); resultado = SumaSerie(inicial, final); printf("El resultado es: %d\n", resultado); }

En donde lo primero que cabe destacar es la inclusi on del prototipo de la funci on al principio del chero (l nea 17). Esto permite que el compilador conozca el prototipo antes de que ocurra cualquier llamada a la funci on SumaSerie() dentro del chero y pueda comprobar que las llamadas a dicha funci on son correctas (n umero de argumentos, tipos y valor devuelto). Dentro de la funci on main() se piden al usuario los valores inicial y nal de la serie y en la l nea 31 se llama a la funci on SumaSerie(). Esta l nea ilustra dos aspectos muy importantes de las funciones en C: Una funci on que devuelve un tipo t se puede usar en cualquier expresi on en donde se puede usar una variable del mismo tipo t. En nuestro caso se puede introducir en cualquier lugar en el que introducir amos un n umero o una variable de tipo int, como por ejemplo en una asignaci on como ha sido el caso. Los nombres de los argumentos en la llamada no tienen por qu e ser iguales a los nombres usados para dichos argumentos en la denici on de la funci on. Esta caracter stica, junto con la posibilidad de trabajar con variables locales, es la que permite reutilizar f acilmente las funciones.

10.5. Paso de argumentos a las funciones. Variables locales


En el ejemplo anterior se acaba de ilustrar el mecanismo de llamada a una funci on. Sin embargo al lector espabilado igual le ha surgido una duda: que le ocurre a la variable inicial que hemos pedido al usuario dentro de la funci on main()? Como se puede apreciar en la l nea 60 del programa, dentro de la funci on SumaSerie() se incrementa el valor del primer argumento, el cual se corresponde con la variable inicial dentro de main(); la pregunta es si este incremento se realiza tambi en sobre la variable inicial. La respuesta es que no. Todas las variables de la funci on, tanto los argumentos (a y b) como las denidas localmente (suma) son variables temporales que se crean cuando se llama a la funci on y que desaparecen

10.5. PASO DE ARGUMENTOS A LAS FUNCIONES. VARIABLES LOCALES

103

al salir de e sta. En la llamada a la funci on (l nea 31) los valores almacenados en las variables inicial y final se copian en las variables temporales a y b con lo que cualquier operaci on que se realice con estas copias (lecturas, escrituras, incrementos. . . ) no inuir an para nada en las variables originales. Ejercicios 1. Para ilustrar lo que acabamos de discutir, introducir el programa anterior en el ordenador y modicarlo para que se imprima en la pantalla el valor inicial de la serie, el valor nal y su suma. En primer lugar realizar la impresi on desde main() y en segundo lugar desde dentro de la funci on SumaSerie() observando las diferencias. Esto que se acaba de discutir es uno de los aspectos m as importantes de las funciones: el hecho de que la funci on trabaje con sus propias variables la aisla del resto del programa, pues se evita modicar de forma accidental una variable que no pertenezca a la funci on. Para ilustrar este aspecto, imaginemos que el programa en el que hay que realizar la suma de la serie descrita en el apartado anterior es mucho m as largo y que como no hacemos nunca caso del profesor, decidimos no usar funciones en el programa. Para sumar la serie se podr a hacer: ... suma = 0; while(inicial<=final){ suma +=inicial; inicial++; } ... Los problemas que plantea esta soluci on son varios: En primer lugar el valor inicial nos lo hemos cargado al ir increment andolo, por lo que si intentamos realizar la tarea propuesta en el ejercicio 1 nos daremos cuenta que el valor inicial es igual que el nal despu es de sumarse la serie. Hemos usado una variable llamada suma para almacenar la suma parcial de la serie, pero que ocurre si en otra parte del programa se ha usado una variable tambi en llamada suma? Obviamente el valor que tuviese desaparece con el c alculo de la serie, y si se usa despu es el programa fallar a estrepitosamente y para colmo ser a dif cil encontrar el error. Si en otra parte se quiere calcular otra serie entre las variables ini y fin y queremos aprovechar este trozo de c odigo tendremos que cortar y pegar esta parte del programa en la parte apropiada y luego cambiar inicial por ini y final por fin y tal vez suma por cualquier otra cosa si queremos conservar el valor de la serie anterior. Adem as, si se nos olvida cambiar alguna de las variables, el compilador no dar a ning un mensaje de error ya que todas las variables son v alidas; pero sin embargo el resultado ser a incorrecto. Como puede comprobar el alumno, las ventajas de usar funciones son muy numerosas y el u nico inconveniente es aprender a usarlas, pero como esto va a ser imprescindible para aprobar el curso, no s e a que est as esperando para empezar a practicar.

104

CAPITULO 10. FUNCIONES

10.6. Salida de una funci on y retorno de valores


Para salir de una funci on se usa la instrucci on return seguida del valor que se desea devolver. Su sintaxis es: return expresi on ; En donde expresi on es cualquier expresi on v alida de C que, una vez evaluada, da el valor que se desea devolver. Si el tipo de dato de la expresi on no coincide con el tipo de la funci on, se realizar an las conversiones apropiadas siempre que e stas sean posibles o el compilador generar a un mensaje de error si dicha conversi on no es posible. La instrucci on de salida puede aparecer en cualquier parte de la funci on, pudiendo existir varias. Por ejemplo una funci on que devuelve el m aximo de sus dos argumentos se puede escribir como: int Max(int a, int b) { if(a > b){ return a; }else{ return b; } } Sin embargo no conviene usar esta t ecnica, pues cualquier funci on es m as f acil de entender si siempre se sale de ella por el mismo sitio. Si se desea realizar una funci on que no devuelva nada se debe usar el tipo void. As si tenemos una funci on que no devuelve nada su declaraci on ser a: void FuncionQueNoDevuelveNada(int arg1, double arg2); y su denici on ser a: void FuncionQueNoDevuelveNada(int arg1, double arg2) { ... } En este tipo de funciones que no devuelven nada, la instrucci on de salida es opcional, terminando la funci on autom aticamente cuando el ujo del programa alcanza la llave (}) que cierra el cuerpo de la funci on. Si se desea poner expl citamente la instrucci on de salida o si se desea salir desde otro punto de la funci on la sintaxis es: return ; dando el compilador un aviso en caso de que return est e seguido de alguna expresi on. Del mismo modo si una funci on que ha de devolver un valor no lo devuelve mediante la instrucci on return, el compilador dar a un aviso.

10.7. FUNCIONES SIN ARGUMENTOS

105

10.7. Funciones sin argumentos


Al igual que se pueden denir funciones que no devuelven nada, tambi en se pueden denir funciones que no reciben ning un argumento. Un ejemplo de este tipo de funciones es getchar(), disponible en la librer a est andar, la cual lee un car acter de la entrada est andar y lo devuelve como un int. El prototipo de esta funci on es: int getchar(void); en donde podemos apreciar que al no recibir ning un argumento se ha puesto void como lista de argumentos. En general una funci on que no toma ning un argumento se declara como: tipo devuelto nombre funci on(void);

10.8. Ejemplos
Veamos a continuaci on algunos ejemplos m as para aclarar ideas.

10.8.1. Elevar al cuadrado


Como en C no existe la operaci on para elevar un n umero a una potencia y la funci on pow() es demasiado ineciente para calcular un cuadrado, hemos decidido realizar una funci on que devuelva el cuadrado de su argumento. Si trabajamos con n umeros reales tanto el argumento como el valor devuelto por la funci on ser an de tipo double. La funci on, junto con un peque no programa que ilustra su uso, se muestra a continuaci on. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* Programa: Cuadrados * * Descripci on: Ejemplo de uso de la funci on cuadrado(). * * Revisi on 0.0: 15/04/1998 * * Autor: El funcionario novato. */ #include <stdio.h> /* prototipos de las funciones */ double cuadrado(double a); main(void) { double valor; printf("Introduzca un valor "); scanf("%lf",&valor); printf("El cuadrado de %g es: %g\n", valor, cuadrado(valor)); }

106 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

CAPITULO 10. FUNCIONES

/* Funci on: cuadrado() * on: Devuelve el cuadrado de su argumento * Descripci * * Argumentos: double a: Valor del que se calcula el cuadrado. * * Valor devuelto: double: El argumento a al cuadrado. * on 0.0: 15/04/1998. * Revisi * * Autor: El funcionario novato. */ double cuadrado(double a) { return a*a; }

N otese que la llamada a la funci on cuadrado() aparece ahora dentro de la lista de argumentos de printf() (l nea 22). Tal como se dijo en la secci on 10.4 una llamada a funci on puede aparecer en cualquier lugar donde lo har a un objeto del mismo tipo que el devuelto por la funci on. Como la funci on cuadrado es de tipo double puede aparecer en cualquier lugar en el que pondr amos un double y ciertamente un argumento de printf() es un buen lugar para un double. Ejercicios 1. Modicar el programa anterior para calcular el cuadrado de un n umero entero. Modicar en primer lugar s olo el programa principal, de forma que se lea un n umero entero y se llame a la misma funci on de antes y se imprima el resultado. Funciona todo correctamente? Que conversiones autom aticas se est an realizando? 2. Modicar ahora la funci on cuadrado para que calcule el cuadrado de un int e integrarla en el programa anterior. 3. Usar la funci on cuadrado() dise nada en el ejercicio 2 en el ejemplo de los apuntes. Funciona correctamente? Que tipo de conversiones se realizan?

10.8.2. Factorial
Una funci on muy usada en estad stica que no esta disponible en la librer a matem atica est andar de C es el factorial. En este ejemplo vamos a realizar una funci on que calcula el factorial de un n umero entero. La funci on junto con un programa que la usa se muestra a continuaci on: 1 /* Programa: Factoriales 2 * 3 * Descripci on: Ejemplo de uso de la funci on factorial(). 4 * 5 * Revisi on 0.0: 15/04/1998

10.8. EJEMPLOS
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 * * Autor: El funcionario novato. */ #include <stdio.h> /* prototipos de las funciones */ long int factorial(long int a); main(void) { long int valor; printf("Introduzca un valor "); scanf("%ld",&valor); printf("El factorial de %ld es: %ld\n", valor, factorial(valor)); } /* Funci on: factorial() * * Descripci on: Devuelve el factorial de su argumento * * Argumentos: long int a: Valor del que se calcula el factorial. * * Valor devuelto: long int: El factorial del argumento a. * on 0.0: 15/04/1998. * Revisi * * Autor: El funcionario novato. */ long int factorial(long int a) { long int fact; /* valores parciales de factorial */ fact = 1; while(a>0){ fact *= a; a--; } return fact; }

107

Ejercicios 1. Introducir el programa anterior en el ordenador y comprobar su funcionamiento para valores positivos y negativos. Da siempre resultados correctos? 2. Modicar la funci on factorial() para que devuelva -1 en caso de que no se pueda

108

CAPITULO 10. FUNCIONES


calcular el factorial de su argumento. Corregir el programa principal para que imprima un mensaje de error si factorial() devuelve un -1.

10.9. Paso de vectores, cadenas y matrices a funciones


En ocasiones es necesario que una funci on trabaje con vectores o matrices (o ambas cosas). Sin embargo, si cada vez que se llamase a una funci on se realizara una copia del vector o matriz, el mecanismo de llamada ser a tremendamente ineciente en cuanto las matrices tuviesen una longitud apreciable. Debido a esto, en C no se copia el vector o la matriz cuando se pasan como argumento a una funci on, sino que se le dice en qu e parte de la memoria est a para que la funci on trabaje sobre el vector o la matriz original. Esta soluci on aunque muy eciente puede ser muy peligrosa en manos de un programador inexperto, pues cada modicaci on que se realice en el vector o la matriz desde dentro de la funci on modica el vector o la matriz original, por lo que se ha de ser extremadamente cuidadoso con el manejo de vectores y matrices dentro de las funciones para evitar modicaciones no deseadas.

10.9.1. Paso de vectores


El prototipo de una funci on que acepta un vector como argumento es: tipo devuelto NombreFunci on(tipo nombre vector[] ); Es decir, se ponen dos corchetes [] a continuaci on del nombre del vector. Por ejemplo, una funci on que calcule el producto escalar de dos vectores de tres dimensiones tendr a como prototipo: double ProdEscalar(double vect1[], double vect2[]); Y la denici on de la funci on, junto con un breve programa que ilustra su uso, es la siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* Programa: Producto Escalar * * Descripci on: Ejemplo de uso de la funci on ProdEscalar(). * * Revisi on 0.0: 15/04/1998 * * Autor: El funcionario novato. */ #include <stdio.h> /* prototipos de las funciones */ double ProdEscalar(double vect1[], double vect2[]); main(void) { double vect1[3]={1, 2, 3}; double vect2[3]={3, 2, 1}; printf("El producto escalar vale %g\n", ProdEscalar(vect1, vect2));

10.9. PASO DE VECTORES, CADENAS Y MATRICES A FUNCIONES


22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 }

109

/* Funci on: ProdEscalar() * on: Devuelve el producto escalar de dos vectores de 3 dimensiones * Descripci * * Argumentos: double vect1[]: primer vector. * double vect2[]: segundo vector. * * Valor devuelto: double: El producto escalar de los dos vectores * on 0.0: 15/04/1998. * Revisi * * Autor: El funcionario novato. */ double ProdEscalar(double vect1[], double vect2[]) { int i; double producto; producto = 0; for(i=0; i<3; i++){ producto += vect1[i]*vect2[i]; } return producto; }

En este ejemplo se han introducido dos novedades: La primera es la inicializaci on de los vectores realizada en las l neas 18 y 19. Este tipo de inicializaci on de los vectores asigna al primer elemento del vector el primer elemento de la lista entre llaves, al segundo elemento el segundo valor y as sucesivamente hasta terminar la lista. As pues la l nea 18 es equivalente a las l neas: vect1[0] = 1; vect1[2] = 2; vect1[3] = 3; pero mucho m as compacta como habr a observado el lector. La segunda novedad es la manera de llamar a una funci on pas andole un vector. Esto se ha mostrado en la l nea 21, en la que se llama a la funci on ProdEscalar(). Como puede observar para pasar los vectores a la funci on se escribe su nombre sin corchetes en la lista de argumentos: ProdEscalar(vect1, vect2). Por u ltimo destacar que, aunque en el prototipo y en la denici on de la funci on no se especica el tama no de los vectores, dentro del cuerpo de la funci on se supone que son de dimensi on tres, pues el bucle que realiza el producto escalar comienza en la coordenada 0 y termina en la 2, tal como se aprecia en las l neas 44 a 46. Esto es un grave inconveniente, pues no hay manera de comprobar desde dentro de la funci on la dimensi on de los vectores que se han pasado como argumentos. Por tanto es necesario ser muy cuidadoso cuando se pasen vectores o matrices a funciones y pasar siempre los vectores de la longitud adecuada. Si se desea realizar una funci on que trabaje con vectores de cualquier dimensi on habr a que indicar de alguna manera el tama no del vector, por ejemplo mediante un argumento adicional.

110

CAPITULO 10. FUNCIONES

As , si se generaliza la funci on ProdEscalar() para trabajar con vectores de cualquier dimensi on, el programa anterior queda:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 /* Programa: Producto Escalar * * Descripci on: Ejemplo de uso de la funci on ProdEscalar(). * * Revisi on 0.1: 20/04/1998 * * Autor: El funcionario novato. */ #include <stdio.h> /* prototipos de las funciones */ double ProdEscalar(int dimension, double vect1[], double vect2[]); main(void) { double vect1[3]={1, 2, 3}; double vect2[3]={3, 2, 1}; printf("El producto escalar vale %g\n", ProdEscalar(3, vect1, vect2)); } /* Funci on: ProdEscalar() * on: Devuelve el producto escalar de dos vectores cuya * Descripci on viene dada por el primer argumento * dimensi * on de los vectores on: Dimensi * Argumentos: int dimensi * double vect1[]: primer vector. * double vect2[]: segundo vector. * * Valor devuelto: double: El producto escalar de los dos vectores * on 1.0: 20/04/1998. * Revisi * * Autor: El funcionario novato. */ double ProdEscalar(int dimension, double vect1[], double vect2[]) { int i; double producto; producto = 0; for(i=0; i<dimension; i++){ producto += vect1[i]*vect2[i]; } return producto; }

10.9. PASO DE VECTORES, CADENAS Y MATRICES A FUNCIONES

111

10.9.2. Paso de cadenas a funciones


Todo lo que se acaba de discutir sobre el paso de vectores a funciones es aplicable al paso de cadenas a funciones, pues como recordar a las cadenas de caracteres no eran m as que vectores de tipo char.

10.9.3. Paso de matrices a funciones


El paso de matrices es un poco distinto al de los vectores. En ellas es necesario indicar todas las dimensiones de la matriz salvo la primera. Por ejemplo si queremos realizar una funci on para inicializar matrices de 3x5, el prototipo de la funci on ser a: void InicializaMatriz(int matriz[][5]); y su denici on es: void InicializaMatriz(int matriz[][5]) { int n; /* ndices para recorrer la matriz */ int m; for(n=0; n<3; n++){ for(m=0; m<5; m++){ mat[n][m] = 0; } } } y para llamarla desde otra funci on se escribe el nombre de la matriz sin corchetes de la misma forma que se hace con los vectores: ... int mat[3][5]; ... InicializaMatriz(mat); ... Por u ltimo cabe destacar dos cosas: Que el compilador no realiza ning un tipo de comprobaci on sobre la coincidencia de las dimensiones de la matriz que se le pasa a la funci on con las que e sta espera. Es responsabilidad del programador que esto ocurra para evitar fallos catastr ocos en el programa. Al contrario de lo que ocurr a con los vectores, no se pueden realizar funciones que trabajen con matrices de cualquier dimensi on, pues el compilador necesita saber las dimensiones de la matriz para acceder a ella. Ejercicios 1. Realizar una funci on para calcular el determinante de una matriz de 3 de la funci on ser a:

3. El prototipo

112 double Det(double mat[][3]); 2. Escribir una funci on para multiplicar dos matrices de 3

CAPITULO 10. FUNCIONES

3. El prototipo ser a:

void ProdMat(double A[][3], double B[][3], double Res[][3]);

Cap tulo 11

Punteros
11.1. Introducci on
Los punteros son un tipo de variable un poco especial, ya que en lugar de almacenar valores (como las variables de tipo int o de tipo double) los punteros almacenan direcciones de memoria. Utilizando variables normales s olo pueden modicarse valores, mientras que utilizando punteros pueden manipularse direcciones de memoria o valores. Pero no se trata simplemente de un nuevo tipo de dato que se utiliza ocasionalmente, sino que los punteros son parte esencial del lenguaje C. Entre otras ventajas, los punteros permiten: Denir vectores y matrices de tama no variable, que utilizan s olo la cantidad de memoria necesaria y cuyo tama no puede reajustarse din amicamente. Denir estructuras de datos m as completas como listas de datos o a rboles. Que las funciones devuelvan m as de un valor.

11.2. Declaraci on e inicializaci on de punteros


Los punteros se declaran igual que las variables normales, pero con un asterisco (*) delante del nombre de la variable. Por ejemplo: int a; int *pa; En este ejemplo la variable a es de tipo entero y puede almacenar valores como 123 o -24, mientras que la variable p es un puntero y almacena direcciones de memoria. Aunque todos los punteros almacenan direcciones de memoria, existen varios tipos de puntero dependiendo de la declaraci on que se haga. Por ejemplo: int *pti; double *ptd; Tanto pti como ptd son punteros y almacenan direcciones de memoria, por lo tanto almacenan datos del mismo tipo y ocupan la misma cantidad de memoria, es decir, sizeof(pti) == sizeof(ptd). La u nica diferencia es que el dato almacenado en la direcci on de memoria contenida en el puntero pti es un entero, mientras que el dato almacenado en la direcci on 113

114
Direccin Valor Variable

CAPITULO 11. PUNTEROS


Direccin Valor Variable

C2B8

pti

C2B8

123

C37A

ptd

C37A

7.4e-3

Figura 11.1:

de memoria contenida en ptd es un double. Se dice por lo tanto que pti apunta a un entero (puntero a entero) mientras que ptd apunta a un double (puntero a double). En la gura 11.1 aparece un ejemplo en el que pti apunta a una direcci on de memoria (0xC2B8) donde se encuentra almacenado el valor entero 123, y ptd apunta a otra direcci on de memoria (0xC37A) donde se encuentra almacenado el valor 7.4e-3.

11.2.1. El operador unario &


Igual que las variables normales tienen un valor inicial desconocido despu es de su declaraci on, tambi en los punteros almacenan inicialmente una direcci on desconocida; es decir, apuntan a una direcci on aleatoria que puede incluso no existir. Aunque se puede asignar un valor de direcci on a un puntero, esto no es nada aconsejable ya que ser a una casualidad que en la posici on de memoria que se asigne exista un valor del tipo correspondiente al puntero. Si se escribe: int *pti; pti=0xC2B8; /* ojo */

ser a una casualidad que en la direcci on de memoria 0xC2B8 hubiese un valor entero. Por lo tanto en los pr oximos ejemplos vamos a hacer que nuestros punteros apunten a datos correctos mediante la declaraci on de variables auxiliares. Esto es algo que no parece muy u til, pero vale para explicar el funcionamiento de los punteros. El operador & se utiliza para obtener la direcci on de memoria de cualquier variable del programa. Si nuestro programa tiene una variable entera que se llama ii, entonces podemos obtener la direcci on de memoria que el compilador ha preparado para almacenar su valor escribiendo &ii. int ii; int *pti; ii=78; pti=&ii; /* variable entera */ /* puntero a entero */ /* inicializo ii */ /* pti apunta a ii */

Al aplicar el operador & a la variable ii no se obtiene el valor entero almacenado en ella sino la direcci on de memoria en donde se encuentra dicho valor. Por lo tanto &ii es un puntero a entero porque es una direcci on de memoria donde se haya un entero. Suponiendo que el compilador reserva la posici on de memoria 0x34FF para la variable ii, la ejecuci on del programa anterior llevar a a la organizaci on de memoria que se muestra en la gura 11.2

E INICIALIZACION DE PUNTEROS 11.2. DECLARACION


Direccin Valor Variable

115

34FF

pti

34FF

78

ii

Figura 11.2:

El compilador dar a un aviso si se intenta realizar una asignaci on en la que no corresponden los tipos, por ejemplo al asignar &ii a un puntero a double, ya que &ii devuelve un puntero a int. Este tipo de comprobaciones del compilador evita muchos errores de programaci on. Por tanto hay que estar muy atento a los mensajes del compilador y hay que activar todos los avisos. Asignaciones entre dos punteros tambi en son v alidas siempre que los dos punteros sean del mismo tipo. Por ejemplo, si se compila el programa siguiente: 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> main() { int ii; double *pd; ii = 27; pd = &ii; /* ERROR */ printf("%lf\n", *pd*2); }

el compilador (gcc en este caso1 ) generar a el siguiente aviso: tema11_2.c:10: warning: assignment from incompatible pointer type donde se avisa que en la l nea 10 se asigna al puntero pd la direcci on de un dato que no es compatible con su tipo. A pesar de este aviso se genera un programa que si se ejecuta imprime por pantalla el valor -3.997604 en lugar de 54. Ejercicios 1. Sabr a explicar por qu e el programa anterior imprime un valor err oneo?

11.2.2. El operador unario *


Hasta ahora los punteros s olo han sido variables para guardar direcciones de memoria, pero esto ser a poco u til si no pudi esemos manipular el valor almacenado en dichas posiciones
1 Cualquier

otro compilador generar a un aviso parecido.

116
Situacin inicial Direccin Valor Variable Despus de hacer *pti+=8; Direccin Valor Variable

CAPITULO 11. PUNTEROS


Despus de hacer pti+=8; Direccin Valor Variable

34FF

pti

34FF

pti

351F

pti

34FF

78

ii

34FF

86

ii

34FF

86

ii

351F

????

Figura 11.3:

n, permite acceder al valor de memoria. El operador unario *, llamado operador de indireccio por medio del puntero. int ii; int *pti; pti=&ii; *pti=78; /* variable entera */ /* puntero a entero */ /* pti apunta a ii */ /* equivale a hacer ii=78; */

La asignaci on *pti=78 introduce el valor entero 78 en la posici on de memoria almacenada en el puntero pti. Como previamente se ha almacenado en pti la direcci on de memoria de la variable ii, la asignaci on equivale a dar el valor 78 directamente a la variable ii. Es decir, tras la asignaci on pti=&ii disponemos de dos maneras de manipular los valores enteros almacenados en la variable ii: directamente mediante ii o indirectamente mediante el puntero pti. De momento esto no parece muy u til pero es importante que quede claro.

11.3. Operaciones con punteros


Una vez entendido lo que es un puntero veamos las operaciones que pueden realizarse con estas variables tan especiales. En primer lugar ya se ha visto que admiten la operaci on de asignaci on para hacer que apunten a un lugar coherente con su tipo. Pero tambi en admite operaciones de suma y diferencia que deben entenderse como cambios en la direcci on a la que apunta el puntero. Es decir, si tenemos un puntero a entero que almacena una determinada direcci on de memoria y le sumamos una cantidad, entonces cambiar a la direcci on de memoria y el puntero quedar a apuntando a otro sitio. Es importante diferenciar claramente las dos formas de manipular el puntero: *pti+=8; pti+=8; La primera l nea suma 8 al entero al que apunta pti y por lo tanto el puntero sigue apuntando al mismo sitio. La segunda l nea suma 8 posiciones de memoria a la direcci on almacenada en el puntero, por lo tanto e ste pasa a apuntar a otro sitio. (ver gura 11.3).

11.4. PUNTEROS Y VECTORES

117

De nuevo hacer que el puntero pase a apuntar a un lugar desconocido vuelve a ser peligroso y habr a que tener cuidado con este tipo de operaciones. Es muy importante distinguir entre las operaciones con punteros utilizando el operador * y sin utilizarlo. Utilizando el operador * el puntero se convierte en una variable normal y por lo tanto admite todas las operaciones de estas variables. Por ejemplo ii=20 * *pti, *pti=a+b, *pti+=7 etc. En operaciones normales de suma, resta, multiplicaci on, divisi on, comparaciones... no hace falta ning un cuidado especial, s olo que el asterisco vaya unido al nombre del puntero para que no se confunda con el operador de multiplicaci on. Las operaciones de incremento y decremento puede dar lugar a confusi on, por la mezcla de operadores, y se aconseja escribirlas siempre con par entesis adicionales: ++(*p), (*p)++, --(*p) y (*p)--. on del puntero y por lo tanto cambia el Sin utilizar el operador * se manipula la direcci lugar al que apunta. La operaci on m as normal es incrementar la direcci on en uno, con ello se pasa a apuntar al dato que ocupa la siguiente posici on de memoria. La aplicaci on fundamental de esta operaci on es recorrer posiciones de memoria consecutivas. Adem as de valer para sgonear la memoria del ordenador, e sto se utiliza para recorrer vectores (cuyos datos siempre se almacenan en posiciones de memoria consecutivas). Pero antes de pasar al apartado de vectores veamos cuanto se incrementa realmente un puntero. Generalmente cada posici on de memoria del ordenador puede almacenar un byte, por lo tanto aumentando la direcci on de un puntero en 1 pasar amos a apuntar al siguiente byte de la memoria. Esto es algo bastante claro aunque dif cil de manejar, porque como se ha visto repetidamente a lo largo del curso, cada tipo de variable ocupa un tama no de memoria diferente. Por ejemplo los char s ocupan 1 byte, pero los int ocupan 2 o 4 bytes, los double ocupan 8, etc. Adem as, estos tama nos dependen del ordenador y del compilador que se utilice. Para solucionar este problema, los punteros de cualquier tipo siempre apuntan al primer byte de la codicaci on de cada dato. Cuando se accede al dato mediante el operador *, el compilador se encarga de acceder a los bytes necesarios a partir de la direcci on almacenada en el puntero. En el caso de punteros a char s olo se accede al byte almacenado en la direcci on de memoria del puntero, mientras que el caso de punteros a double se accede a la posici on de memoria indicada en el puntero y a los 7 bytes siguientes. Todo este mecanismo ocurre de manera autom atica y el programador s olo debe preocuparse de declarar punteros a char cuando quiere trabajar con valores char o punteros a double cuando quiere trabajar con valores double. Tambi en para facilitar las cosas, la operaci on de sumar 1 a un puntero hace que su direcci on se incremente la cantidad necesaria para pasar a apuntar al siguiente dato del mismo tipo. Es decir s olo en el caso de variables que ocupan 1 byte en memoria (variables char) la operaci on de incremento aumenta en 1 la direcci on de memoria, en los dem as casos la aumenta m as. Lo que hay que recordar es que siempre se incrementa un dato completo y no hace falta recordar cu anto ocupa cada dato en la memoria. Por ejemplo, si ptc es un puntero a char que vale 0x3B20, la operaci on ptc++ har a que pase a valer 0x3B21. Por otro lado si ptd es un puntero a double que vale 0x3B20, la operaci on ptd++ har a que pase a valer 0x3B28 (ver la gura 11.4).

11.4. Punteros y vectores


Una de las aplicaciones m as frecuentes de los punteros es el manejo de vectores y cadenas de caracteres. Como se recordar a todos los elementos de un vector se almacenan en posiciones de memoria consecutivas y por lo tanto basta conocer la posici on de memoria del primer elemento para poder recorrer todo el vector con un puntero. El siguiente ejemplo inicializa un vector de double por el m etodo normal y luego escribe su contenido con la ayuda de un

118
pti++ Direccin Valor Variable

CAPITULO 11. PUNTEROS

Direccin

Valor

Variable

3B20

pti

3B20 3B21

1 byte 1 byte

ptd++ Direccin Valor Variable Direccin Valor Variable

3B20

ptd

3B20 8 bytes 3B28 8 bytes

Figura 11.4:

puntero. /* Programa de punteros y vectores Descripci on: Este programa inicializa un vector y luego escribe sus valores Versi on: 1.0 */ #define N 10 #include <stdio.h> main(void) { double a[N]; int i; double *pd;

/* vector */ /* contador */ /* puntero a double */

/*** Inicializaci on ***/ for(i=0; i<N; i++) { a[i]=7.8*i; } /*** Imprimir valores ***/ pd=&a[0]; /* Apunto al primer elemento del vector */

11.4. PUNTEROS Y VECTORES


for(i=0; i<N; i++) { printf("%f\n",*pd); /* *pd es de tipo double */ pd++; /* pd pasa a apuntar al siguiente elemento */ } }

119

Hay que tener en cuenta que cuando termina el programa el puntero pd queda apuntado a un lugar no v alido, ya que queda fuera del vector a. Supongamos ahora que queremos copiar el vector a en el vector b de manera que sea su inverso; es decir, si a vale [1,2,3,4] queremos que b valga [4,3,2,1]. Generar b a partir de a es muy sencillo utilizando dos punteros pa y pb, uno creciente y otro decreciente: pa=&a[0]; /* pa apunta al primer elemento del vector a */ pb=&b[N-1]; /* pb apunta al ultimo elemento del vector b */ for(i=0; i<N; i++) { *pb=*pa; /* copio un elemento */ pa++; /* avanzo un elemento */ pb--; /* retrocedo un elemento */ } Con mucha frecuencia se utilizan varios punteros sobre el mismo vector para copiar datos o hacer manipulaciones sin tener que andar con la complicaci on de manejar varios ndices. Los ejemplos m as t picos son las transformaciones de cadenas de caracteres. Ejercicios 1. Escribir un programa que obtenga la simetr a de una cadena de caracteres. Por ejemplo, on" el resultado debe ser "avi onn oiva". si la cadena vale "avi

11.4.1. Equivalencia de punteros y vectores


Cuando se dene un vector, tal como se ha visto en el ejemplo anterior: double a[N]; lo que ocurre es que se crea espacio en memoria para almacenar N elementos de tipo double y se crea un puntero constante2, llamado a, que apunta al principio del bloque de memoria reservada. Por tanto para hacer que el puntero pd apunte al principio de la matriz a se puede hacer pd=&a[0] o simplemente pd=a. El operador [ ] Cuando se accede a un elemento de la matriz mediante: a[3]=2.7; el programa toma el puntero constante a, le suma el valor que hay escrito entre los corchetes (3) y escribe en dicha direcci on el valor 2.7. Es decir, la sentencia anterior es equivalente a escribir:
2 El

programa no puede cambiar la direcci on almacenada en un puntero constante. Por ejemplo a=&ii es ilegal

120 *(a+3)=2.7;

CAPITULO 11. PUNTEROS

Adem as este operador se puede aplicar sobre cualquier puntero, no s olo sobre los punteros constantes, por lo que los dos trozos de c odigo que siguen son equivalentes: a[3]=2.7; pd=a; pd[3]=2.7;

A modo de resumen, si tenemos un vector vec de 100 elementos y queremos asignar el valor 45 al 5o elemento, pondremos: vec[4]=45; En caso de tener un puntero punt que apunta al primer elemento de vector, podemos realizar la misma asignaci on de tres maneras: Moviendo el puntero y luego volviendo atr as punt+=4; *punt=45; punt-=4; Calculando la direcci on directamente *(punt+4)=45; Utilizando corchetes, que es lo m as c omodo. punt[4]=45;

11.5. Punteros y funciones


Hasta ahora hemos visto que los argumentos de una funci on no se modican, ya que la funci on trabaja sobre copias de los mismos. Esto es u til y muy seguro la mayor a de las veces, pero en otras ocasiones nos puede interesar modicar realmente el valor de las variables. Por ejemplo la siguiente funci on para cambiar el valor de dos variables no hace nada: /* Funci on: Cambio() Descripci on: Intenta cambiar el valor de dos variables Comentario: No lo consigue, hacen falta punteros. Revisi on: 1.0 Autor: Novato */ void cambio(int a, int b) { int tmp; tmp=a; a=b; b=tmp; }

11.5. PUNTEROS Y FUNCIONES

121

Esta funci on no hace nada porque cambia los valores de a y b, pero e stos son copias de las variables que se utilizan al llamar a la funci on. Si la llamada es cambio(x,y) entonces a es una copia de x y b es una copia de y. Aunque la funci on cambia los valores de a y b, al terminar y volver al programa principal, los valores de x e y no se ven alterados. La manera de conseguir que la funci on cambio sea u til es trabajar con punteros. La versi on 1.1 de esta funci on utiliza como argumentos dos punteros en lugar de dos enteros; el nuevo prototipo es: void cambio(int *pa, int *pb); Al recibir las direcciones de las dos variables, s tenemos acceso a los valores originales 3 y por lo tanto se puede hacer el cambio. Ahora se trata de cambiar los valores contenidos en las direcciones apuntadas por los punteros pa y pb. /* Funci on: Cambio() Descripci on: Intenta cambiar el valor de dos variables Comentario: OK on: 1.1 Revisi Autor: El que sabe de punteros */ void cambio(int *pa, int *pb) { int tmp; tmp=*pa; *pa=*pb; *pb=tmp; } La u nica duda ser a C omo llamamos a la funci on para darle las direcciones en lugar de los valores? La respuesta ya se ha visto porque el operador & nos facilita la direcci on de cualquier variable. En este caso la llamada para cambiar los valores de las variables x e y es la siguiente: cambio(&x, &y); De hecho, llevamos utilizando llamadas de este estilo desde que se mencion o por primera vez la funci on scanf. Por qu e en la funci on scanf se escriben las variables con un & mientras que en printf se escriben sin nada? La respuesta es sencilla despu es de haber entendido los punteros y las funciones. La funci on printf se encarga de escribir el valor de nuestras variables y le da igual tener acceso directo al valor o trabajar sobre una copia; para mayor facilidad de programaci on (y para mayor seguridad) se opta por pasarle una copia. Sin embargo la funci on scanf lee un valor del teclado y lo escribe dentro de una de las variables de la funci on que la llama. Por lo tanto necesita acceso directo a la direcci on de memoria donde se almacena el valor de la variable. Y la u ltima pregunta es Por qu e es tan importante especicar correctamente el formato de las variables en scanf? Porque si el formato dice %f la funci on scanf escribe en total 4 bytes (sizeof(float)) a partir de la direcci on de memoria, pero si el formato dice %lf entonces scanf escribe 8 bytes. Cuando la variable no coincide con el tipo especicado pueden ocurrir dos cosas: que se escribe fuera de las posiciones de memoria reservadas para la variable (probablemente machacando la variable de al lado) o que no se escribe suciente (dejando parte de la variable sin inicializar).
3 Usando

el operador de indirecci on * sobre el puntero.

122

CAPITULO 11. PUNTEROS

11.5.1. Retorno de m as de un valor por parte de una funci on


Como se ha visto anteriormente una funci on s olo puede devolver un valor mediante la sentencia return. Sin embargo en muchas ocasiones es deseable que una funci on devuelva m as de un valor. En estos casos la t ecnica usada consiste en pasarle a la funci on la direcci on de las variables donde queremos que nos devuelva sus resultados; de modo que la funci on pueda modicar directamente el valor de dichas variables. Por ejemplo, si queremos realizar una funci on que devuelva la suma y el producto de dos valores, dicha funci on se escribir a: void SumaProd(double *psuma, double *pprod, double dato1, double dato2) { *psuma = dato1 + dato2; *pprod = dato1 * dato2; } y la manera de llamarla para que devuelva la suma y el producto de 2 y 3 en las variables sum y prod ser a: SumaProd(&sum, &prod, 2.0, 3.0); Por convenio, en estos casos se suelen colocar en la lista de argumentos las variables en las que devuelve la funci on sus resultados en primer lugar.

11.6. Asignaci on din amica de memoria


Existen innidad de aplicaciones en las cuales no se conoce la cantidad de memoria necesaria para almacenar los datos hasta que no se ejecuta el programa. Si por ejemplo se desea escribir un programa que calcule el producto escalar de dos vectores, la dimensi on de e stos no se conoce hasta que no se le pregunta al usuario al comienzo del programa. En este tipo de situaciones las posibles soluciones son dos: Crear vectores de un tama no jo lo sucientemente grande. Crear el vector din amicamente al ejecutarse el programa. La primera soluci on presenta dos inconvenientes: el primero es que si el usuario necesita calcular el producto escalar de dos vectores de dimensi on mayor a la dimensi on m axima que se eligi o al escribir el programa, tendr a que llamarnos para que incrementemos dicha dimensi on m axima (lo cual puede estar bien para cobrarle soporte t ecnico, pero dar a mucho que hablar sobre nuestra habilidad como programadores). El segundo inconveniente es que normalmente se est a desperdiciando una gran cantidad de memoria en denir un vector demasiado grande del cual s olo se va a utilizar una peque na parte en la mayor a de los casos. En esta situaci on, dado que todos los sistemas operativos actuales son multitarea, si un programa usa toda la memoria del ordenador no se podr an arrancar otros programas. Estos dos inconvenientes se solucionan con el uso de memoria din amica: mediante esta t ecnica, una vez que se est a ejecutando el programa y se conoce la cantidad de memoria que se necesita, se realiza una llamada al sistema operativo para solicitarle un bloque de memoria libre del tama no adecuado. Si queda memoria, el sistema operativo nos devolver a un puntero que apunta al comienzo de dicho bloque. Este puntero nos permite acceder a la memoria a nuestro antojo, siempre y cuando no nos salgamos de los l mites del bloque 4. Una vez que
4 Si intentamos leer o escribir fuera del bloque de memoria que se nos ha asignado, los resultados pueden ser desastrosos en funci on de la zona de memoria a la que accedamos err oneamente. Por tanto hay que ser muy cuidadosos cuando se usan estas t ecnicas.

DINAMICA 11.6. ASIGNACION DE MEMORIA

123

se termina de usar la memoria, esta debe liberarse al sistema operativo para que los dem as programas puedan usarla. Esta t ecnica permite por tanto un manejo m as racional de la memoria, aparte de permitirnos crear estructuras de datos m as complejas como listas enlazadas y a rboles. Sin embargo estos temas quedan fuera del alcance de este curso.

11.6.1. Las funciones calloc() y malloc()


Estas dos funciones permiten al programa solicitar al sistema operativo un bloque de memoria de un tama no dado. Ambas funciones devuelven un puntero al principio del bloque solicitado o NULL si no hay suciente memoria. Es muy importante por tanto vericar siempre que se solicite memoria al sistema operativo que e ste nos devuelve un puntero v alido y no un NULL para indicarnos que no tiene tanta memoria disponible. Los prototipos de ambas funciones son: void *calloc(size_t numero elementos, size_t tama no elemento); void *malloc(size_t tama no bloque); y se encuentran en el archivo cabecera stdlib.h que habr a de ser incluido al principio del chero para que el compilador pueda comprobar que las llamadas se realizan correctamente. Por lo dem as, como ambas funciones pertenecen a la librer a est andar, no hace falta a nadir ninguna librer a adicional en el enlazado. La funci on calloc() reserva un bloque de memoria para un numero elementos de tama n o elemento, inicializa con ceros el bloque de memoria y devuelve un puntero gen erico (void *) que apunta al principio del bloque o NULL en caso de que no exista suciente memoria libre. La funci on malloc() reserva un bloque de memoria de taman o bloque (medido en bytes) y devuelve un puntero al principio del bloque o NULL en caso de que no exista suciente memoria libre. Por ejemplo para crear un vector de 100 enteros se podr a realizar lo siguiente: int *pent; /* puntero al principio de la vector */ ... pent = (int *) calloc(100, sizeof(int)); if(pent == NULL){ /* Si no hay memoria */ printf("Error: No hay suficiente memoria\n"); exit(1); } /* Si hay memoria el programa contin ua y podemos usar el vector reci en construido */ pent[0] = 27; /* recu erdese la equivalencia entre punteros y vectores */ ... La petici on de memoria del programa anterior podr a haberse realizado tambi en de la siguiente manera5: pent = (int *) malloc(100*sizeof(int)); En ambas llamadas se reserva un bloque de 100 enteros y se devuelve un puntero al principio de dicho bloque. N otese que en el prototipo de estas dos funciones se especica que se
5 Aunque

en este caso la memoria no se inicializar a con ceros.

124

CAPITULO 11. PUNTEROS

devuelve un puntero de tipo void. Este tipo es un puntero gen erico que ha de convertirse mediante un molde (cast) al tipo de puntero con el que vamos a manejar el bloque. En el ejemplo, como se deseaba crear un vector de 100 enteros, el puntero devuelto se ha convertido en un puntero a entero mediante el molde (int *). Por u ltimo conviene destacar que en las llamadas a las funciones calloc() y malloc() se ha usado sizeof(int) para especicar la cantidad de memoria que se necesita. Esto es muy importante de cara a la portabilidad del programa. Si aprovechando que conocemos el tama no de un int en un PC con GCC hubi esemos puesto un 4 en lugar de sizeof(int), el programa no funcionar a bien si lo compilamos en Borland C para MS-DOS, el cual usa enteros de 2 bytes.

11.6.2. La funci on free()


Una vez que se ha terminado de usar la memoria es necesario liberarla para que quede disponible para los dem as programas. El no hacerlo har a que cada vez que se ejecute nuestro programa desaparezca un trozo de memoria que no se recuperar a hasta que reiniciemos el equipo6. La funci on free() libera la memoria previamente asignada mediante calloc() o malloc(). Se ha recalcado lo de previamente asignada porque liberar memoria que no ha sido asignada7 es un grave error que puede dejar colgados a algunos sistemas operativos. El prototipo de esta funci on es: void free(void *puntero al bloque); que tambi en est a en el archivo cabecera stdlib.h. Esta funci on, al igual que sus compa neras de faenas: calloc() y malloc(), se encuentra en la librer a est andar. La funci on simplemente libera el bloque a cuyo principio apunta el puntero al bloque. Siguiendo con el ejemplo anterior para liberar la memoria previamente asignada habr a que realizar: free(pent); Hay que destacar que el puntero al bloque ha de apuntar exactamente al principio del bloque, es decir, ha de ser el puntero devuelto por la llamada a la funci on de petici on de memoria (calloc() o malloc()). Por ejemplo, el siguiente trozo de c odigo har a que el programa aborte al hacer la llamada a free() o si el sistema operativo no es muy espabilado lo dejar a incluso colgado (se recomienda no probarlo en el examen): int *pent; pent = calloc(100, sizeof(int)); if(pent == NULL){ printf("Error: Se ha gastado la memoria. Compre m as.\n"); exit(1); } ... pent++; /* peligro!! */ ... free(pent); /* Error catastr ofico */
6 Salvo 7O

que el sistema operativo sea muy astuto. liberarla varias veces, que es el error m as habitual.

DINAMICA 11.6. ASIGNACION DE MEMORIA

125

Tambi en es un grave error el seguir usando la memoria una vez liberada, pues aunque tenemos un puntero que apunta al principio del bloque, este bloque ya no pertenece a nuestro programa y por tanto puede estar siendo usado por otro programa (o por nuestro programa si se ha solicitado m as memoria), por lo que cualquier lectura nos puede devolver un valor distinto del escrito y cualquier escritura puede modicar datos de otro programa (o de otro bloque del nuestro), tal como se muestra en el siguiente ejemplo: int *pent; /* puntero al principio de la vector */ int a; /* Variable auxiliar */ pent = (int *) calloc(100, sizeof(int)); if(pent == NULL){ /* Si no hay memoria */ printf("Error: No hay suficiente memoria\n"); exit(1); } pent[0] = 27; /* recu erdese la equivalencia entre punteros y vectores */ free(pent); a = pent[0]; /* ERROR! El bloque al que apunta pent ya no pertenece a nuestro programa. Por tanto puede que en a no se escriba 27 */ pent[0] = 40; /* ERROR! Estamos modificando memoria que ya no es nuestra y los resultados pueden ser catastr oficos */

11.6.3. Ejemplo
Para jar ideas vamos a ver a continuaci on un programa en el que se hace uso de asignaci on din amica de memoria. Se ha de escribir un programa que pida un vector al usuario y una vez que e ste lo haya introducido, ha de generar otro vector que contenga s olo los elementos del primer vector que sean n umeros pares e imprimirlos en la pantalla. Por supuesto la dimensi on de ambos vectores es desconocida antes de ejecutar el programa. Como no se conoce la dimensi on de ninguna de los vectores y sabemos ya un mont on acerca de asignaci on din amica de memoria decidimos, en lugar de crear dos vectores enormes que valgan para cualquier caso, crear los vectores del tama no justo cuando se ejecute el programa. Un pseudoc odigo del programa se muestra a continuaci on: on del vector al usuario Pedir dimensi Pedir memoria para el vector Pedir al usuario cada uno de los elementos del vector umero de elementos pares del vector Contar el n umeros pares. Pedir memoria para el vector de n umeros pares del primer vector al segundo Copiar los n Imprimir el segundo vector El programa completo se muestra a continuaci on:
1 /* Programa: BuscaPar 2 *

126
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

CAPITULO 11. PUNTEROS


* Descripci on: Pide al usuario un vector de enteros y genera otro que * contiene s olo los elementos pares de dicho vector, * imprimi endolo antes de terminar. * * Revisi on 0.0: 19/05/1998 * * Autor: El programador din amico. */

#include <stdio.h> #include <stdlib.h> main(void) { int *pvec_usu; /* Puntero al vector introducido por el usuario (creado din amicamente) */ int *pvec_par; /* Puntero al vector de elementos pares (creado din amicamente)*/ int dim_usu; /* Dimensi on del vector del usuario */ int dim_par; /* Dimensi on del vector de elementos pares */ int n; /* Indice para los for */ int m; /* Indice para recorrer la matriz de elementos pares */ printf("Introduzca la dimensi on del vector: "); scanf("%d",&dim_usu); /* Asignamos memoria para el vector del usuario. Contiene dim_usu enteros */ pvec_usu = (int *) calloc(dim_usu, sizeof(int)); if(pvec_usu == NULL){ /* Estamos sin memoria */ printf("Error: no hay suficiente memoria para un vector de %d elementos\n", dim_usu); exit(1); } /* Pedimos los elementos del vector */ for(n=0; n<dim_usu; n++){ printf("Elemento %d = ", n); scanf("%d", &(pvec_usu[n]) ); } /* Contamos los pares en la variable dim_par */ dim_par = 0; /* De momento no hay ning un elemento par */ for(n=0; n<dim_usu; n++){ if( (pvec_usu[n]%2) == 0 ){ /* es par */ dim_par ++; } } /* Se asigna memoria para los n umeros pares */ pvec_par = (int *) calloc(dim_par, sizeof(int));

DINAMICA 11.6. ASIGNACION DE MEMORIA


57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 }

127

if(pvec_par == NULL){ /* Estamos sin memoria */ printf("Error: no hay suficiente memoria para un vector de %d elementos\n", dim_par); free(pvec_usu); /* Antes de salir hay que LIBERAR la memoria que ya nos hab an dado */ exit(1); } /* Se copian los elementos pares */ m = 0; /* Indice para el vector de elementos pares (inicialmente apunta al primer elemento) */ for(n=0; n<dim_usu; n++){ if( (pvec_usu[n]%2) == 0 ){ /* es par */ pvec_par[m] = pvec_usu[n]; /* copio el elemento */ m++; /* e incremento el ndice del vector par */ } } /* Se imprimen el vector de elementos pares */ printf("\n-----------------------\n"); /* Para separar los dos vectores */ for(n=0; n<dim_par; n++){ printf("Elemento par %d = %d\n", n, pvec_par[n]); } /* y antes de salir se libera la memoria que nos hab an asignado */ free(pvec_usu); free(pvec_par); exit(0);

Un ejemplo de la ejecuci on del programa se muestra a continuaci on: Introduzca Elemento 0 Elemento 1 Elemento 2 la dimensi on del vector: 3 = 1 = 2 = 4

----------------------Elemento par 0 = 2 Elemento par 1 = 4 Ejercicios 1. Modicar el programa anterior para que la petici on de datos se realice en una funci on. El prototipo de dicha funci on ha de ser: void PideVec(int *pvec, int dimension) 2. Puesto que los vectores siempre se recorren desde principio a n y de elemento en elemento puede ser m as f acil usar punteros para recorrer los vectores. Modicar el programa del apartado anterior para que la b usqueda de elementos pares, la creaci on de la matriz de n umeros pares y su impresi on, se realicen mediante punteros en lugar de

128

CAPITULO 11. PUNTEROS


mediante ndices (para no perder los punteros al principio del bloque de forma que podamos realizar un free() sin problemas se recomienda denir dos punteros auxiliares e inicializarlos con el mismo valor que los punteros base.)

Cap tulo 12

Archivos
12.1. Introducci on
Cualquiera que haya manejado un ordenador habr a experimentado con desolaci on como un corte de corriente hace que desaparezca en un momento todo el trabajo que no hab a guardado en el disco. La raz on de semejante desastre estriba en que la memoria RAM del ordenador se borra en cuanto se corta la corriente. Adem as la memoria RAM es muy cara por lo que su cantidad es limitada, pero sin embargo es necesario que dentro de un ordenador se almacenen una gran cantidad de programas y de datos que pueden ocupar varios Gigabytes. Por todo esto es necesario un sistema de almacenamiento secundario que no se borre al cortar la corriente y que tenga una gran capacidad. El sistema actual de almacenamiento secundario m as usado es el disco duro, dada su rapidez y capacidad de almacenamiento, aunque existen una gran variedad de sistemas como diskettes, CDROM. . . El manejo de todos estos sistemas lo realiza el sistema operativo, el cual organiza la informaci on en forma de archivos repartidos en directorios (tambi en llamados carpetas). De esta forma cada archivo tiene una identicaci on u nica dentro del ordenador compuesta por el nombre del archivo y por la ruta o camino de acceso hasta e l por el a rbol de directorios. Hasta ahora los programas que hemos realizado en este curso han tomado datos desde el teclado y han impreso sus resultados en la pantalla. Sin embargo en muchas ocasiones es necesario leer datos desde un archivo que se ha almacenado previamente en el disco (bien por el propio usuario o bien por otro programa), escribir los resultados en un archivo de forma que pueda ser usado posteriormente tanto por el usuario como por otro programa o usar un archivo en disco como medio de almacenamiento de los datos de un programa de forma que se mantengan cuando se apague el ordenador (por ejemplo en aplicaciones de bases de datos). La biblioteca est andar de C proporciona un conjunto de funciones para el manejo de archivos. En este tema introductorio s olo se van a estudiar cuatro funciones b asicas que permiten trabajar con archivos de texto.

12.2. Apertura de archivos. La funci on fopen()


Antes de usar un archivo en disco es necesario decirle al sistema operativo que lo localice, que evite que otros procesos accedan al archivo mientras nuestro programa lo tenga abierto y que reserve unas zonas de memoria para trabajar con el archivo. Esto se realiza con la funci on fopen() cuyo prototipo es: 129

130 Modo r r+ w w+ a

CAPITULO 12. ARCHIVOS


Descripci on Abre el archivo para leer. El archivo ha de existir. Se posiciona al principio del archivo. Abre el archivo para leer y escribir. El archivo ha de existir. Se posiciona al principio del archivo. Abre el archivo para escribir. Si el archivo existe, borra su contenido y si no existe, crea uno nuevo. Abre el archivo para escribir y leer. Si el archivo existe, borra su contenido y si no existe, crea uno nuevo. Abre el archivo para anadir . Si el archivo no existe crea uno nuevo, pero si existe no borra su contenido. Se posiciona al nal del archivo de forma que sucesivas escrituras se a naden al archivo original. Ha de a nadirse a cualquiera de los modos anteriores si el archivo que se va a abrir contiene datos binarios en lugar de texto ASCII1 . Cuadro 12.1: Modos de apertura de los archivos.

FILE *fopen(char *Nombre completo del archivo, char *modo); En donde Nombre completo del archivo es una cadena de caracteres que contiene el nombre del archivo, incluyendo el camino completo si dicho archivo no esta situado en el directorio actual. Este nombre ha de ser adem as un nombre legal para el sistema operativo. Por ejemplo si se trabaja en MS-DOS el nombre de archivo no puede tener m as de ocho caracteres seguidos de un punto y tres caracteres m as para la extensi on. El modo del archivo es otra cadena de caracteres que indica el tipo de operaciones que vamos a realizar con e l. En la tabla 12.1 se muestran todos los modos aceptados por la funci on. El valor devuelto por la funci on es un puntero a archivo, que es un puntero que apunta a una estructura de datos llamada FILE que est a denida en stdio.h y que contiene toda la informaci on que necesitan el resto de las funciones que trabajan con archivos, como el modo del archivo, los buffers del archivo, errores. . . Si ocurre alg un tipo de error en la apertura (como intentar abrir un archivo que no existe en modo "r") fopen() devuelve un NULL. Por tanto despu es de la llamada a fopen() hay que vericar que el puntero devuelto es v alido (al igual que se realiza con los punteros devueltos por calloc() y malloc()). Por ejemplo si queremos abrir un archivo para leer de e l cuyo nombre es pepe habr a que escribir: #include <stdio.h> ... main(void) { FILE *pfich; ... pfich = fopen("pepe", "r+"); if(pfich == NULL){ printf("Error: No se puede abrir el archivo.\n"); exit(1); } ...
1 En

este curso no se va a trabajar con cheros binarios debido a su mayor complejidad.

FCLOSE(). 12.3. CIERRE DE ARCHIVOS. LA FUNCION

131

en donde se ha supuesto que el archivo pepe est a situado en el directorio de trabajo actual. En caso de que no exista el archivo pepe o si no se encuentra en el directorio actual, fopen() no podr a abrirlo y devolver a un NULL, por lo que el programa despu es de imprimir un mensaje de error terminar a su ejecuci on. Si se quiere abrir un archivo para a nadir al nal de e l m as datos y dicho archivo est a situado en el directorio c:\dani con el nombre datos.m, el programa ser a igual que el anterior salvo que la llamada a fopen() ser a: pfich = fopen("c:\\dani\\datos.m", "a"); Recu erdese que \\ es traducido por el compilador de C por una \ al generar la cadena de caracteres, de la misma manera que \n se traduce por un salto de carro. El archivo abierto ser a por tanto c:\dani\datos.m.

12.3. Cierre de archivos. La funci on fclose().


Una vez que se ha terminado de usar un archivo hay que cerrarlo. Con la operaci on de cierre se escriben a disco todos los datos que aun queden en el buffer 2 , se desconecta el archivo del programa y se libera el puntero al archivo. Como la mayor a de los sistemas operativos tienen limitado el m aximo n umero de archivos que un programa puede tener abiertos a la vez, es conveniente cerrar los archivos una vez que ya no se necesite usarlos. Sin embargo hay que ser cuidadoso para no seguir usando el puntero al archivo que se acaba de cerrar, pues los resultados ser an impredecibles. Por si esto fuera poco, tambi en es muy peligroso cerrar un archivo m as de una vez. Para evitar p erdidas de datos, cuando un programa termina normalmente 3 la funci on fclose() se llama autom aticamente para cerrar todos los archivos abiertos. A pesar de esto, es conveniente acostumbrarse a cerrar uno mismo los archivos que haya abierto por si acaso falla el cierre autom atico (pues ello ocasionar a p erdidas en nuestros datos) y as se exigir a en este curso. El prototipo de la funci on fclose() es: int fclose(FILE *puntero al archivo); En donde puntero al archivo es el puntero a archivo devuelto por la funci on fopen() al abrir el archivo que se desea cerrar ahora. El valor devuelto por la funci on es cero si el archivo se cerr o con e xito o -1 si ocurri o alg un tipo de error al cerrarlo; aunque independientemente de este resultado el archivo se cierra igualmente. Errores t picos pueden ser que el puntero que le pasamos no apunta a ning un archivo o que el disco se ha llenado y no se ha podido vaciar el buffer con la consiguiente p erdida de datos. Por esto u ltimo es conveniente vericar que el archivo se ha cerrado correctamente comprobando el valor de retorno, pues aunque no podemos hacer ya nada para solucionar la p erdida de datos, por lo menos podemos avisar al usuario de que ha ocurrido alg un problema. Para terminar veamos por ejemplo c omo se har a para cerrar el archivo que se abri o en el ejemplo de la secci on 12.2:
2 Para lograr una mayor eciencia en los accesos a disco, las escrituras del programa se realizan en una memoria intermedia, llamada buffer, vaci andose e sta al disco cuando existe una cantidad suciente de bytes o al cerrar el archivo como se acaba de decir. 3 Aqu la palabra normalmente incluye cualquier salida controlada del programa, es decir, mediante una llamada a exit() o por la llegada al nal del main(). Un programa que se aborta con Ctrl-C o que termina inesperadamente por un error de programaci on (divisi on por cero. . . ) no se considera como terminado normalmente.

132

CAPITULO 12. ARCHIVOS

if( fclose(pfich) != 0){ printf("Error al cerrar el archivo\n"); printf("Es posible que se haya producido una p erdida de datos.\n"); printf("Lo siento.\n"); }

12.4. Lectura y escritura en archivos. Las funciones fprintf() y fscanf().


Para escribir y leer de archivos de texto se usan las funciones fprintf() y fscanf(), cuyo funcionamiento es id entico al de printf() y scanf() respectivamente, salvo que ahora es necesario indicar en qu e archivo han de escribir o leer.

12.4.1. La funci on fprintf()


El prototipo de la funci on fprintf() es: fprintf(FILE *puntero al archivo, const char *cadena de formato, ...); En donde puntero al archivo es el puntero a archivo devuelto por la llamada a fopen() al abrir el archivo sobre el que se desea escribir o leer. La cadena de formato es una cadena que especica el formato en el que se imprimen el resto de argumentos de la llamada. Esta cadena se construye de la misma forma que la de la funci on printf(), la cual se describi o en el la secci on 3.7. Por u ltimo los tres puntos (...) del prototipo de printf() indican que a metros, los cuales se imprimir la cadena de formato le sigue un numero variable de para an seg un lo especicado en ella. Por ejemplo, para escribir en el archivo abierto en la secci on 12.2 no hay m as que hacer: fprintf(pfich, "El valor de %d en hexadecimal es %x\n", 15, 15);

12.4.2. La funci on fscanf()


la funci on fscanf() tiene por prototipo: int fscanf(FILE *puntero al archivo, const char *cadena de formato, ...); El valor devuelto por fscanf() es un poco complejo de describir. En caso de que solo se quiera leer un valor (por ejemplo fscanf(pfich, "%d", &num)) el valor devuelto es 1 si se ha le do correctamente el valor o 0 si ha ocurrido un error en la lectura por una mala especicaci on del formato (por ejemplo si en este caso que se esperaba un entero (%d) en el archivo se encuentra una cadena de caracteres). Si no se ha le do nada porque se ha llegado al nal del archivo, el valor devuelto ser a EOF. En el caso de que se quieran leer varios valores en una misma llamada a fscanf() (Por ejemplo fscanf(pfich, "%d %d", &n1, &n2)), fscanf() devuelve el n umero de valores que se han le do (y que por tanto estar an ya escritos en sus respectivas variables 4). Si ocurre un error antes de haber realizado alguna lectura, el comportamiento es igual al descrito anteriormente para el caso en el que s olo se le a un argumento (0 si hay error de formato o EOF si se ha
4 Recu erdese que fscanf() escribe directamente en la posici on de memoria de la variable en la que se desea guardar el valor le do.

12.4. LECTURA Y ESCRITURA EN ARCHIVOS. LAS FUNCIONES FPRINTF() Y FSCANF().133


llegado al nal del archivo). Si en cambio el error ocurre cuando ya se ha realizado alguna lectura, pero no todas, el valor devuelto es igual al n umero de valores le dos, independientemente de que el error producido haya sido de formato o de n de archivo. Para ilustrar el uso de fscanf() supongamos que en el archivo abierto en la secci on 12.2 hay escritos una serie de n umeros en punto otante de los cuales se desea calcular su suma. En el siguiente listado se muestra un programa que abre el archivo, lee los datos y calcula la suma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /* Programa: Suma * * Descripci on: Lee el archivo "pepe" que contiene n umeros en punto flotante * y calcula su suma. * * Revisi on 0.0: 25/05/1998 * * Autor: Jos e Daniel Mu noz Fr as. */ #include <stdio.h> #include <stdio.h> void main(void) { int n; FILE *pfich; double dato; double sum_tot;

/* /* /* /*

Valor devuelto por scanf */ Puntero al archivo */ dato le do del fichero */ Suma de los datos */

pfich = fopen("pepe", "r+"); if(pfich == NULL){ printf("Error: No se puede abrir el fichero \"pepe\"\n"); exit(1); } sum_tot = 0; /* Inicializaci on de la suma antes de entrar en el bucle*/ n = fscanf(pfich, "%lf", &dato); while(n!=EOF && n!=0){ /* termina en cuanto ocurra un error de lectura*/ sum_tot+=dato; n = fscanf(pfich, "%lf", &dato); } printf("El valor de la suma es: %lf\n", sum_tot); if( fclose(pfich) != 0){ printf("Error al cerrar el fichero\n"); exit(1); } }

Este programa, si el archivo contiene: 1.3 3.4 4.5

134 123.4 al ejecutarse imprimir a por pantalla: El valor de la suma es: 132.600000

CAPITULO 12. ARCHIVOS

Como se puede apreciar en las l neas 27 y 30, el uso de fscanf() es id entico al uso de scanf() salvo por la existencia del puntero al archivo desde donde se lee (pfich). Tambi en cabe destacar la condici on de salida del bucle while. Como se puede apreciar en la l nea 28 se sale cuando el valor devuelto por fscanf() sea EOF, en cuyo caso se habr a acabado el chero y no tiene sentido seguir leyendo; o cuando devuelva un cero, indicando que no se ha le do el dato debido a un error de formato. Seg un esto u ltimo, si el archivo pepe tuviese el siguiente contenido: 2.3 3 hola t o 4 5.89 cual ser a la salida del programa?

12.5. Ejemplo
Para jar ideas vamos a mostrar a continuaci on un ejemplo completo en el que se trabaja con archivos. En los primeros tiempos de la inform atica s olo exist an terminales en modo texto, por lo que cuando alg un programa ten a que representar gr acamente una funci on se recurr a frecuentemente a usar la impresora como salida gr aca. Vamos a retroceder por tanto unos cuantos a nos en la historia de la inform atica y vamos a realizar un programa que, partiendo de un archivo en el que est an escritas las notas de los alumnos de programaci on, genere otro archivo con un histograma de las notas. En dicho histograma una barra representar a el n umero de ocurrencias en el archivo de la nota correspondiente. Por ejemplo si el archivo de notas es el siguiente: 7.1 7.2 9.2 6.5 8.3 8.4 7.5 6.7 5.6 9.4 10 9 8 5.5 5.6 5.0 6.7

12.5. EJEMPLO
6.4 6.2 6.0 6.1 7.0 7.9 7.6 7.7 8.5 10

135

lo cual indica que todos los alumnos han estudiado un mont on, el programa generar a el siguiente archivo: 0 1 2 3 4 5 6 7 8 9 10

**** ******* ******* **** *** **

La forma de interpretar esta gr aca es la siguiente: como en la l nea del 5 hay cuatro asteriscos, esto indica que hay cuatro notas entre 5 y 6 (sin incluir el 6) o dicho de una manera m as formal, el n umero de notas contenidas en el intervalo 5 6 es cuatro. La excepci on es el 10; su l nea indica el n umero de notas iguales a 10. El programa a realizar se puede dividir en varias tareas:

Abrir los archivos. Leer el archivo de notas y contar cuantas notas est an dentro de un intervalo dado. Para ello se usar a un vector de enteros en el que en su primer elemento almacenaremos el n umero de notas contenidas en el intervalo 0 1 , en el segundo las contenidas en 1 2 y as sucesivamente. La excepci on ser a el elemento n umero 10 que s olo almacenar a las notas iguales a 10.

Escribir en un archivo el histograma. Para ello no hay m as que imprimir una l nea para cada intervalo, en la que se escribir an tantos asteriscos como notas haya en dicho intervalo (lo cual est a indicado en el n umero almacenado en el elemento del vector correspondiente a dicho intervalo). La primera tarea se ha realizado dentro de la funci on main(), pero las otras dos se han realizado en las funciones CuentaNotas() y EscribeHisto() respectivamente. El listado del programa completo se muestra a continuaci on:
1 /* Programa: ImpGraf 2 * 3 * Descripci on: Lee un archivo que contiene las notas de los alumnos (una

136
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

CAPITULO 12. ARCHIVOS


* nota (con decimales) por l nea almacenada) y genera otro * archivo con un histograma de las notas. * * Revisi on 0.0: 25/05/1998 * * Autor: Jos e Daniel Mu noz Fr as. */

#include <stdio.h> void CuentaNotas(FILE *pfnot, int histo[]); void CreaHisto(FILE *pfhis, int histo[]); void main(void) { int histo_not[11]; /* Vector para almacenar el n umero de ocurrencias de la nota correspondiente. Si histo_not[9] vale 50 es que 50 alumnos han sacado una nota entre 9 y 10 ( animo chicos/as) */ FILE *pfnotas; /* Puntero al archivo de notas */ FILE *pfhisto; /* Puntero al archivo de histograma */ /* Se abren los dos ficheros */ pfnotas = fopen("notas","r"); if(pfnotas == NULL){ printf("Error: No se ha podido abrir el fichero \"notas\"\n"); exit(1); } pfhisto = fopen("histogra","w"); if(pfhisto == NULL){ printf("Error: No se ha podido abrir el fichero \"histogra\"\n"); fclose(pfnotas); /* cierro el fichero que ya hab a abierto */ exit(1); } CuentaNotas(pfnotas, histo_not); CreaHisto(pfhisto, histo_not); fclose(pfnotas); fclose(pfhisto); } /* * * * * * * * * Funci on: CuentaNotas() Descripci on: Lee las notas del fichero cuyo puntero se pasa como argumento y crea el vector con el histograma. Argumentos: FILE *pfnot: Puntero al archivo de notas. int histo[]: Histograma. Revisi on 0.0: 25/05/1998.

12.5. EJEMPLO

137

58 * 59 * Autor: Jos e Daniel Mu noz Fr as. 60 */ 61 62 void CuentaNotas(FILE *pfnot, int histo[]) 63 { 64 int n; /* N umero de datos le do por fscanf() */ 65 int i; /* Indice para los for */ 66 double nota_act; /* Almacena la nota que se acaba de leer del fichero */ 67 68 /* Inicializo la matriz del histograma */ 69 for(i=0; i<11; i++){ 70 histo[i] = 0; 71 } 72 73 n = fscanf(pfnot, "%lf", &nota_act); 74 while(n!=EOF && n!=0){ /* termina en cuanto ocurra un error de lectura */ 75 if( (nota_act < 0) || (nota_act > 10) ){ /* Se verifica que las notas 76 sean v alidas */ 77 printf("Error en el fichero de entrada. Las notas han de estar entre" 78 "0 y 10\n"); 79 exit(1); 80 } a la nota */ e casilla est 81 for(i=0; i<11; i++){ /* Se busca en qu 82 if( (i <= nota_act) && (nota_act < i+1) ){ 83 histo[i]++; 84 break; /* una vez que he situado la nota en su casilla salgo del for */ 85 } 86 } 87 n = fscanf(pfnot, "%lf", &nota_act); 88 } 89 } 90 91 /* Funci on: CreaHisto() 92 * 93 * Descripci on: Representa "gr aficamente" en un fichero el histograma que 94 * se pasa en el vector histo. Para ello por cada elemento de 95 * dicho vector se transforma en una fila de asteriscos (un 96 * asterisco representa una unidad) 97 * 98 * Argumentos: FILE *pfhis: Puntero al archivo de notas. 99 * int histo[]: Histograma. 100 * 101 * Revisi on 0.0: 25/05/1998. 102 * 103 * Autor: Jos e Daniel Mu noz Fr as. 104 */ 105 106 void CreaHisto(FILE *pfhis, int histo[]) 107 { 108 int i; /* Indice para los for */ 109 int contador; /* cuenta los asteriscos escritos */ 110 111 for(i=0; i<11; i++){

138
112 113 114 115 116 117 118 119 120 121 } 122 }

CAPITULO 12. ARCHIVOS


contador = 0; fprintf(pfhis,"%2d ", i); /* Escribe la nota a la que corresponde la "barra" del histograma */ while(histo[i] > contador){ fprintf(pfhis, "*"); contador++; } fprintf(pfhis, "\n"); /* Escribe el salto de l nea para que la nea */ siguiente nota se escriba en otra l

Ejercicios 1. Modicar el programa anterior para que sea f acil cambiar el car acter con el que se imprime el histograma. Para ello usar la sentencia #define (por ejemplo #define CAR_HISTO #). 2. Realizar lo mismo que en el ejercicio anterior pero pidi endole al usuario al principio del programa el car acter con el que se desea que se imprima el histograma. 3. Realizar un programa que a partir de un archivo que contiene una serie de n umeros (en el mismo formato que el usado para el archivo de notas) calcule la media y la varianza de la serie y la imprima por pantalla. Para ello se aconseja usar funciones que lean el archivo y que devuelvan el resultado de sus c alculos. As por ejemplo para calcular la media se podr a crear una funci on con el prototipo double media(FILE *pfich); 4. Realizar un programa que a partir del archivo de notas cree un archivo con el histograma de notas y a continuaci on imprima la media y la varianza de las notas. Nota: Para volver
a leer un archivo desde el principio se puede usar la funci on rewind(), cuyo prototipo es: void rewind(FILE *pfich);

5. Realizar un histograma en el que los intervalos representados sean de medio punto, es decir, se tendr a una la para las notas contenidas en 0 0 5 , otra para 0 5 1 y as sucesivamente. Para ello usar una sentencia #define para crear un par ametro que indique el n umero de intervalos en que se divide el conjunto de notas.

12.6. Funciones de entrada y salida a archivo sin formato


Hasta ahora toda la entrada y salida a archivos se ha realizado exclusivamente con las funciones fprintf() y fscanf(). Estas funciones tienen la ventaja de realizar las complejas conversiones de formato que son necesarias para poder transformar los valores binarios usados internamente en el ordenador a cadenas de caracteres usadas por los humanos. Por ejemplo antes de imprimir un n umero entero, que conviene recordar que se almacena como una secuencia de bits en la memoria del ordenador, es necesario transformar esa secuencia de bits en una secuencia de caracteres que represente al n umero en la base elegida (normalmente base 10 (%d) o base 16 (%x)). El inconveniente de esto es que el c odigo encargado de realizar las conversiones ocupa memoria y tarda algo de tiempo en ejecutarse. Si embargo en la mayor a de los casos no es necesario escribir o leer n umeros. Por ello en la librer a est andar de C existen funciones que que escriben o leen caracteres (o cadenas de caracteres) sin conversi on de formatos. Estas funciones son fgetc()/fputc() para leer/escribir un car acter de/en un archivo y fgets()/fputs() para leer/escribir una cadena de caracteres de/en un archivo. Los prototipos de estas funciones son:

12.6. FUNCIONES DE ENTRADA Y SALIDA A ARCHIVO SIN FORMATO


int fgetc(FILE *puntero a archivo); int fputc(int car acter, FILE *puntero a archivo); char *fgets(char *cadena, int tam cad, FILE *puntero a archivo); int fputs(const char *cadena, FILE *puntero a archivo);

139

La funci on fgetc() lee el siguiente car acter desde el archivo como un unsigned char y lo devuelve convertido a int. Si se llega al nal del chero u ocurre alg un error el valor devuelto es EOF5 acter (convertido a unsigned char) que se le pasa La funci on fputc() escribe el car como argumento en el archivo al que apunta puntero a archivo. El valor devuelto es el car acter escrito (convertido a int) o EOF si ha ocurrido alg un error. fgets() lee una cadena de caracteres del archivo puntero a archivo y los almacena en la cadena de caracteres cadena. La lectura se acaba cuando se encuentra el car acter de nueva l nea \n (que se escribe en la cadena), cuando se encuentra el n de chero (en este caso no se escribe \n en la cadena) o cuando se han le do tam cad 1 caracteres. En todos estos casos se escribe un car acter \0 en la cadena a continuaci on del u ltimo car acter le do. Por supuesto la cadena de caracteres ha de tener espacio suciente como para almacenar todos los caracteres que se puedan leer en la funci on (tam cad). El valor devuelto es un puntero a la cadena le da o NULL si ha ocurrido alg un error o se ha llegado al nal del chero. fputs() escribe la cadena en el archivo al que apunta puntero a archivo sin el \0 del nal. El valor devuelto es un n umero positivo si se ha escrito la cadena correctamente o EOF en caso de error. Para ilustrar el uso de estas funciones vamos a estudiar un ejemplo:

Imprimir un archivo Vamos escribir en C un programa que imprima por pantalla el contenido de un chero cuyo nombre se pide al usuario al principio del programa (el funcionamiento del programa ser a similar al del comando type de MS-DOS). El programa, despu es de abrir el archivo, leer a car acter a car acter el contenido del archivo e ir a imprimiendo dichos caracteres por pantalla. Un pseudoc odigo del programa es el siguiente Pedir al usuario el nombre del archivo que se ha de imprimir. Mientras no se llegue al final del archivo{ leer un car acter del archivo imprimir el car acter } Cerrar el archivo. Y un programa que realiza esta tarea se muestra a continuaci on.
1 /* Programa: ImpArch 2 * 3 * Descripci on: Imprime el contenido del archivo cuyo nombre se pide al 4 * usuario por la pantalla.
5 Puede parecer un poco extra no el leer un car acter y devolverlo como un int. La raz on de esto es la de poder distinguir los caracteres normales de los errores, pues si se devolviese el car acter le do como un unsigned char, al ocupar la tabla ASCII todos los valores que se pueden representar mediante un unsigned char, no quedan valores libres para representar el EOF. La soluci on es devolver un entero en el que los caracteres ocupan de 0 a 255 de forma que el resto de valores puedan usarse para devolver c odigos de error.

140
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 * * Revisi on 0.0: 26/05/1998 * * Autor: Jos e Daniel Mu noz Fr as. */ #include <stdio.h> #define TAM_CAD 256

CAPITULO 12. ARCHIVOS

main(void) { char nom_fich[TAM_CAD]; /* Nombre del fichero que se va a abrir */ char letra; /* car acter le do desde el fichero y escrito en pantalla */ FILE *pfich; /* Puntero al fichero que se imprime */ printf("Introduzca el nombre del fichero que desea imprimir: "); gets(nom_fich); pfich = fopen(nom_fich, "r"); if(pfich == NULL){ printf("Error al intentar abrir el fichero \"%s\"\n", nom_fich); exit(1); } letra = fgetc(pfich); while(letra != EOF){ printf("%c", letra); letra = fgetc(pfich); } fclose(pfich); }

Cap tulo 13

Estructuras de datos
13.1. Introducci on
En muchas situaciones un objeto no puede describirse por un solo dato, sino por una serie de ellos. Por ejemplo el historial m edico de un paciente consta de varios campos, siendo cada uno de ellos es de un tipo distinto. Un ejemplo de historial m edico, junto con los tipos de datos utilizados podr a ser el mostrado en la siguiente tabla: Dato Nombre Edad Peso No Seguridad Social Historia Tipo de dato Cadena de caracteres N umero entero N umero en punto otante N umero entero Cadena de caracteres Tipo en C char[100] unsigned short float unsigned long char[1000]

Si se desea realizar un programa para gestionar una base de datos con los historiales de los pacientes, ser a mucho m as c omodo poder agrupar todos los datos de un paciente bajo una misma variable compuesta. En C estos tipos de variables denidos como una agrupaci on de datos, que pueden ser de distintos tipos, se denominan estructuras. Hasta ahora hab amos visto que C dispon a de unos tipos b asicos para manejar n umeros y caracteres y que a partir de estos tipos b asicos se pod an crear vectores y matrices, que eran agrupaciones de datos pero de un mismo tipo. Las estructuras de datos son un paso m as all a, al permitir agrupar en una misma variable datos no solo de un mismo tipo, sino de tipos variados, tal como se va a mostrar en este cap tulo.

13.2. Declaraci on y denici on de estructuras


La sintaxis de la declaraci on de una estructura es: struct nombre de estructura{ tipo 1 nombre miembro 1; tipo 2 nombre miembro 2; ... tipo n nombre miembro n; 141

142 };

CAPITULO 13. ESTRUCTURAS DE DATOS

Esta declaraci on no reserva espacio en la memoria para la estructura; simplemente crea una plantilla con el formato de la estructura. La sintaxis para denir una variable del tipo estructura previamente declarado es: struct nombre de estructura variable; Esta sentencia reservar a un espacio en la memoria para albergar una estructura nombre de estructura, a la cual se podr a acceder mediante la etiqueta variable. Por ejemplo la declaraci on de una estructura para almacenar el historial m edico de un paciente se escribir a: struct paciente{ char nombre[100]; unsigned short edad; float peso; unsigned long n_seg_social; char historia[1000]; }; Y para crear una variable del tipo estructura paciente se escribe: struct paciente pepito; N otese que una vez declarada una estructura, dicha estructura se puede considerar como un nuevo tipo de dato con el nombre struct nombre de estructura, y que la declaraci on de variables de ese nuevo tipo de dato es id entica a la del resto de variables; comp arense si no las dos deniciones siguientes: unsigned long numero; struct paciente juanito;

13.3. La sentencia typedef


typedef permite dar un nombre arbitrario a un tipo de datos de C, ya sea un tipo b asico (int, char. . . ) o derivado (struct paciente. . . ). Por ejemplo si se sabe que en un PC un short ocupa dos bytes, se puede escribir al comienzo del programa la denici on de tipos: typedef unsigned short WORD; Y a partir de entonces, WORD se convierte en un sin onimo de unsigned short, por lo que si se desea denir una variable de dos bytes se puede hacer: WORD edad; En lugar de: unsigned short edad;

13.4. ACCESO A LOS MIEMBROS DE UNA ESTRUCTURA

143

Lo cual aparte del ahorro de espacio en la denici on de enteros de dos bytes tiene la ventaja de que si se desea portar el programa a una m aquina en la que los enteros de dos bytes son el tipo int s olo habr a que cambiar la declaraci on de WORD al principio del programa, dejando intacto el resto de deniciones. Tambi en se puede usar typedef para simplicar la denici on de variables de tipo estructura. Para ello se puede unir la declaraci on de la estructura con una denici on de tipos de la forma: typedef struct paciente{ char nombre[100]; unsigned short edad; float peso; unsigned long n_seg_social; char historia[1000]; }PACIENTE; La denici on de variables de tipo estructura paciente se podr a realizar ahora como: PACIENTE pepito; Lo cual mejora bastante la legibilidad del programa. En resumen el uso de typedef tiene dos nalidades: Permite una mejor documentaci on del programa a dar nombres m as signicativos a los tipos, especialmente a los derivados. Mejora la portabilidad del programa, al poder encapsular tipos que dependan de la m aquina en sentencias typedef.

13.4. Acceso a los miembros de una estructura


Para acceder a los miembros de una estructura se usa el operador punto. Siguiendo con el ejemplo del historial m edico, para inicializar el historial pepito se escribir a: strcpy(pepito.nombre, "Jos e P erez L opez"); pepito.edad = 27; pepito.peso = 67.5; pepito.n_seg_social = 27345190; strcpy(pepito.historia, "Al ergico a las Sulfamidas"); y para imprimir los datos de un historial se podr a escribir: printf("Paciente %s:\n Edad: %d\n Peso: %f\n N Seg Soc. %ld\n" " Historia: %s\n", pepito.nombre, pepito.edad, pepito.peso, pepito.n_seg_social, pepito.historia); Como puede apreciarse, aunque pepito es una variable de tipo struct paciente, o lo que es lo mismo, del tipo PACIENTE si se ha realizado el typedef de la secci on 13.3; sus miembros obtenidos con el operador punto son variables normales, es decir, pepito.nombre es una cadena de caracteres y como tal se inicializa con la funci on strcpy() y se imprime con el formato %s. De la misma manera pepito.edad es un int y pepito.peso es un float. En denitiva el uso de los miembros de una estructura es id entico al de las dem as variables.

144

CAPITULO 13. ESTRUCTURAS DE DATOS

13.5. Ejemplo: Suma de numeros complejos


Se desea realizar un programa que pida al usuario dos n umeros complejos y muestre el resultado de su suma. Para facilitar las cosas se va a declarar una estructura para almacenar un n umero complejo, de forma que el programa sea m as f acil de realizar y de entender. A continuaci on se muestra un listado del programa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 /* Programa: SumaComp * * Descripci on: Pide al usuario dos n umeros complejos y calcula su suma * * Revisi on 0.0: 10/05/1999 * * Autor: El programador estructurado. */ #include <stdio.h> /* Declaraci on de una estructura para contener n umeros complejos */ typedef struct complejo{ double real; double imag; }COMPLEJO; void main(void) { COMPLEJO comp1, comp2; /* Los dos n umeros complejos que se pedir an al usuario desde el teclado */ COMPLEJO result; /* Resultado de comp1+comp2 */ printf("Introduzca la parte real del primer complejo: "); scanf("%lf", &comp1.real); printf("Introduzca la parte imaginaria del primer complejo: "); scanf("%lf", &comp1.imag); printf("Introduzca la parte real del segundo complejo: "); scanf("%lf", &comp2.real); printf("Introduzca la parte imaginaria del segundo complejo: "); scanf("%lf", &comp2.imag); result.real = comp1.real + comp2.real; result.imag = comp1.imag + comp2.imag; printf("\nEl resultado de la suma es %lf + %lf j\n", result.real, result.imag); }

Como puede apreciarse, en primer lugar se ha declarado la estructura complejo y se le ha asociado el tipo COMPLEJO1 (l neas 1316). N otese adem as que dicha declaraci on se ha realizado antes de comenzar la funci on main(). Esto u ltimo permite que el formato de la estructura se pueda usar en todas las funciones del programa. Si por el contrario la declaraci on
1 El nombre de la estructura y el nombre del tipo no tienen por qu e coincidir, aunque es conveniente para evitar confusiones.

13.6. ESTRUCTURAS Y FUNCIONES

145

de la estructura complejo se hubiese escrito dentro de la funci on main(), dicha declaraci on ser a privada a la funci on main() y por tanto no se podr a usar fuera de dicha funci on. En segundo lugar es necesario destacar que el uso de los miembros de una estructura es id entico al de una variable que tenga su tipo. Por ejemplo en la l nea 25 se usa el operador & para obtener la direcci on del miembro real de la estructura comp1 para que scanf() pueda escribir en dicha variable el n umero que introduzca el usuario por el teclado. Conviene destacar tambi en que la precedencia del operador punto es mayor2 que la del operador & y por tanto la direcci on que se obtiene es la del miembro de la estructura.

13.6. Estructuras y funciones


Dos estructuras del mismo tipo se pueden igualar entre s , es decir si se desea por ejemplo obtener una copia de una estructura del tipo paciente declarada en la secci on 13.2 se puede hacer simplemente: ... struct paciente struct paciente ... /* aqu estar a ... copia_de_pepito pepito; copia_de_pepito; la inicializaci on de pepito */ = pepito;

Como se recordar a en las llamadas a funciones los argumentos de la funci on se copian en unas variables locales y el valor devuelto por la funci on tambi en se copia a la variable a la que se asigna la funci on en el programa principal. Por tanto una funci on para inicializar una estructura del tipo struct complejo se puede escribir: struct complejo InicComplejo(double real, double imag) { struct complejo resultado; resultado.real = real; resultado.imag = imag; return resultado; } Para llamar a la funci on desde un programa se escribir a simplemente: ... struct complejo comp1; ... comp1 = InicComplejo(2.0, 3.7); ... Como se puede apreciar la funci on recibe dos n umeros reales y crea un n umero complejo llamado resultado. Dicha estructura al terminar la funci on es devuelta al programa principal, en donde se copia a la estructura comp1, que es tambi en una estructura de tipo complejo.
2 De hecho el operador punto, junto con el operador corchete ([ ]) para referenciar elementos de matrices y el operador echa (->) que se estudiar a mas adelante, son los operadores con la mayor precedencia en C.

146 Ejercicios

CAPITULO 13. ESTRUCTURAS DE DATOS

1. Suponiendo que se ha denido con typedef la estructura struct complejo como COMPLEJO, tal como se hizo en el ejemplo de la secci on 13.5; modicar los ejemplos anteriores para usar el nuevo tipo COMPLEJO. El paso de par ametros es similar. Por ejemplo una funci on que devuelva la suma de dos complejos que se pasan como argumentos ser a: COMPLEJO SumaComp(COMPLEJO c1, COMPLEJO c2) { COMPLEJO res; res.real = c1.real + c2.real; res.imag = c1.imag + c2.imag; return res; } Ejercicios 1. Modicar el programa de la secci on 13.5 para que la suma de los dos n umeros complejos se realice mediante una llamada a la funci on SumaComp(). Sin embargo el paso de estructuras de un tama no elevado a una funci on es un proceso costoso, tanto en memoria como en tiempo. Por ello el paso de estructuras a funciones s olo debe de realizarse cuando estas sean de peque no tama no, tal como se acaba de hacer con la estructura COMPLEJO. Por ejemplo si se quiere pasar a una funci on una estructura del tipo PACIENTE, descrita en la secci on 13.3, se copiar an 1110 bytes. En estos casos se opta siempre por pasar a la funci on un puntero a la estructura en lugar de la estructura completa, del mismo modo que con los vectores y matrices.

13.7. Punteros a estructuras


Un puntero a estructura se declara de la misma forma que un puntero a una variable ordinaria. As por ejemplo en la declaraci on: struct paciente *ppaciente; Se ha creado un puntero a una estructura de tipo paciente, declarada en la secci on 13.2. Si se usa typedef para denir un alias a la declaraci on de estructura, tal como se hizo en la secci on 13.3, se podr a crear el mismo puntero de la forma: PACIENTE *ppaciente; Para obtener la direcci on de una estructura se usa el operador & al igual que con el resto de variables. Por ejemplo, para hacer que el puntero ppaciente apunte a la estructura pepito se escribir a: PACIENTE pepito; /* Historial de un paciente */ PACIENTE *ppaciente; /* Puntero a un historial de un paciente */ ppaciente = &pepito; /* ppaciente ahora apunta a pepito */

13.7. PUNTEROS A ESTRUCTURAS

147

Para acceder a un elemento de una estructura a trav es de un puntero se utiliza el operador de indirecci on *, al igual que con el resto de variables, as si ppaciente apunta a una estructura de tipo paciente, *ppaciente es la estructura y (*ppaciente).edad es la edad del paciente. Si en el ejemplo anterior se desea imprimir la cha del paciente usando el puntero ppaciente hay que escribir: printf("Paciente %s:\n Edad: %d\n Peso: %f\n N Seg Soc. %ld\n" " Historia: %s\n", (*ppaciente).nombre, (*ppaciente).edad, (*ppaciente).peso, (*ppaciente).n_seg_social, (*ppaciente).historia); En estos casos los par entesis son estrictamente necesarios, pues tal como se dijo antes, el operador punto tiene una precedencia mayor que el operador *. Si no se ponen los par entesis *ppaciente.edad acceder a a la direcci on de memoria contenida en edad, es decir, ser a equivalente a *(ppaciente.edad). Como edad no es un puntero sino un entero, el programa acceder a a una posici on de memoria indeterminada y que adem as no le pertenecer a, provoc andose un grave error. Como los punteros a estructuras se usan con bastante frecuencia en C, existe una notaci on alternativa para acceder a un miembro de una estructura mediante un puntero, que es el operador echa ->3 . As por ejemplo, las dos asignaciones siguientes son equivalentes: (*ppaciente).edad = 32; ppaciente->edad = 32;

13.7.1. Paso de punteros a estructuras a funciones


Tal como se dijo en la secci on 13.6 el paso de estructuras de gran tama no a funciones mediante copia es tremendamente ineciente. En estos casos se puede pasar a la funci on la direcci on de la estructura original en lugar de una copia de e sta. Esto tiene la ventaja de aumentar considerablemente la rapidez del proceso de llamada de la funci on al tener que copiarse s olo un puntero a la estructura en lugar de la estructura completa. Sin embargo, dado que la funci on trabaja ahora con la estructura original en lugar de con una copia de e sta, se han de extremar las precauciones para no modicar accidentalmente el contenido de e sta. Por ejemplo, una funci on para imprimir una estructura del tipo PACIENTE ser a:
void ImpriPaciente(PACIENTE *ppaciente) { printf("Paciente %s:\n Edad: %d\n Peso: %f\n N Seg Soc. %ld\n" " Historia: %s\n", ppaciente->nombre, ppaciente->edad, ppaciente->peso, ppaciente->n_seg_social, ppaciente->historia); }

Y una funci on para inicializar una estructura del tipo PACIENTE pidi endole los datos al usuario ser a:
void InitPaciente(PACIENTE *ppaciente) { printf("Nombre del paciente: "); gets(ppaciente->nombre);
3 El

operador echa est a formado por un - (s mbolo menos) y un > (s mbolo mayor que)

148

CAPITULO 13. ESTRUCTURAS DE DATOS

printf("Edad del paciente: "); scanf("%d", &ppaciente->edad ); printf("Peso del paciente: "); scanf("%f", &ppaciente->peso ); printf("N de la Seguridad Social: "); scanf("%ld", &ppaciente->n_seg_social ); while(getchar()!=\n); /* scanf() termina de leer en cuanto termina el n umero, dejando el \n del final de la cadena que introduce el usuario en el buffer de lectura. Si no se eliminan mediante este bucle los caracteres que scanf no ha le do, dichos caracteres ser an le dos por gets y se almacenar an en la historia del paciente */ printf("Historial del paciente: "); gets(ppaciente->historia); }

Por u ltimo, un programa que muestra c omo se llamar a a ambas funciones es:
#include <stdio.h> typedef struct paciente{ char nombre[100]; unsigned short edad; float peso; unsigned long n_seg_social; char historia[1000]; }PACIENTE; void InitPaciente(PACIENTE *ppaciente); void ImpriPaciente(PACIENTE *ppaciente); main() { PACIENTE paciente; InitPaciente(&paciente); printf("\nLos datos del paciente son:\n\n"); ImpriPaciente(&paciente); }

13.8. Vectores de estructuras


Sup ongase que se necesita realizar un programa que calcule el producto vectorial de dos vectores de n umeros complejos. Una soluci on podr a ser crear cuatro vectores, dos para las partes real e imaginaria del primer vector de n umeros complejos y otros dos para las partes real e imaginaria del segundo vector de complejos. Sin embargo la soluci on ideal a este problema

13.8. VECTORES DE ESTRUCTURAS

149

es crear dos vectores de estructuras de tipo COMPLEJO con las que se trabajar a mucho m as c omodamente. La declaraci on de un vector de estructuras es id entica a la de un vector de cualquier otro tipo de dato. Por ejemplo para crear un vector de 4 estructuras de tipo COMPLEJO basta con escribir: COMPLEJO vector_complejo[4]; Y para acceder a los miembros de una estructura que a su vez es un elemento del vector se hace: vector_complejo[0].real vector_complejo[0].imag ... vector_complejo[3].real vector_complejo[3].imag = 4.3; = 23.27; = 2.1; = 3.7;

N otese que al igual que el resto de vectores la numeraci on de los elementos comienza en 0. Conviene tener en cuenta que al igual que con el resto de los vectores, al denir un vector de estructuras se reserva un espacio suciente en la memoria para contener a las estructuras que componen el vector y se crea un puntero constante que apunta al comienzo de dicha zona de memoria. En este ejemplo vector complejo es un puntero constante de tipo COMPLEJO * que apunta al principio del vector de cuatro estructuras de tipo COMPLEJO.

13.8.1. Ejemplo: C alculo del producto escalar de dos vectores de complejos


Se desea realizar un programa que calcule el producto escalar de dos vectores de n umeros complejos que el usuario introducir a por el teclado. La dimensi on de los vectores ser a variable, pidi endose al usuario al principio del programa. Tal como se puede apreciar en el listado del programa, lo primero que se escribe es la declaraci on de la estructura complejo junto con la denici on del alias COMPLEJO (l neas 1619). De esta forma, a partir de la l nea 19 el tipo COMPLEJO puede usarse al igual que el resto de tipos de datos de C, tal como se aprecia por ejemplo en los prototipos de las funciones (l neas 22 y 23).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /* Programa: PVecComp * * Descripci on: Pide al usuario dos vectores de n umeros complejos y calcula * su producto escalar. * * Revisi on 0.0: 19/05/1999 * * Autor: El programador estructurado. */ #include <stdio.h> #define MAX_DIM 100 /* Dimensi on de los vectores de complejos */ /* Declaraci on de una estructura para contener n umeros complejos */

150
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 typedef struct complejo{ double real; double imag; }COMPLEJO;

CAPITULO 13. ESTRUCTURAS DE DATOS

/* Prototipos de las funciones */ void PideVec(COMPLEJO *pvec, int dim); COMPLEJO ProdEsc(COMPLEJO *pvec1, COMPLEJO *pvec2, int dim); void main(void) { COMPLEJO vec1[MAX_DIM], vec2[MAX_DIM]; /* Vectores introducidos por el usuario */ COMPLEJO res; /* Resultado del producto escalar */ int dim; /* Dimensi on de los vectores*/ printf("Introduzca la dimensi on de los vectores: "); scanf("%d", &dim); printf("\nIntroduzca los complejos del vector 1\n"); PideVec(vec1, dim); printf("\nIntroduzca los complejos del vector 2\n"); PideVec(vec2, dim); res = ProdEsc(vec1, vec2, dim); printf("\nEl producto escalar es: %lf + %lf i\n", res.real, res.imag); } /* Funci on: PideVec() * on dim umeros complejos de dimensi on: Pide al usuario un vector de n * Descripci * * Argumentos: COMPLEJO *pvec1: Puntero al vector de estructuras de complejos * donde se escribir an los n umeros introducidos * por el usuario. * int dim: Dimensi on del vector de complejos. */ void PideVec(COMPLEJO *pvec, int dim) { int n; for(n=0; n<dim; n++){ printf("Parte real del elemento %d: ", n); scanf("%lf", &(pvec[n].real) ); printf("Parte imaginaria del elemento %d: ", n); scanf("%lf", &(pvec[n].imag) ); } } /* Funci on: ProdEsc() *

13.8. VECTORES DE ESTRUCTURAS

151

70 * Descripci on: Calcula el producto escalar de dos vectores de n umeros 71 * complejos 72 * 73 * Argumentos: COMPLEJO *pvec1: 74 * COMPLEJO *pvec2: Punteros a los vectores de complejos. 75 * int dim: Dimensi on de los vectores de complejos. 76 * 77 * Valor devuelto: COMPLEJO: Resultado del producto escalar. 78 */ 79 80 COMPLEJO ProdEsc(COMPLEJO *pvec1, COMPLEJO *pvec2, int dim) 81 { 82 int n; 83 COMPLEJO resul; 84 85 resul.real = 0; /* Inicializaci on del resultado */ 86 resul.imag = 0; 87 88 for(n=0; n<dim; n++){ 89 resul.real += pvec1[n].real*pvec2[n].real - pvec1[n].imag*pvec2[n].imag; 90 resul.imag += pvec1[n].real*pvec2[n].imag + pvec1[n].imag*pvec2[n].real; 91 } 92 return resul; 93 }

La funci on main() consta simplemente de las deniciones de los vectores de estructuras (linea 27) y de las llamadas a las funciones PideVec() y ProdEsc(), que es donde se realiza todo el proceso del programa. La funci on PideVec() se encarga de pedir al usuario las partes real e imaginaria de cada elemento del vector y de almacenarlas en vector cuya direcci on se le pasa como argumento (pvec). N otese que se ha usado el operador corchete para acceder a cada uno de los elementos del vector. Por u ltimo la funci on ProdEsc() se encarga de calcular el producto escalar mediante la f ormula:
dim 1

i 0

V1 i V2 i

En donde V1 i y V1 i son las partes real e imaginaria del elemento i del vector de complejos V1 . Para calcular el sumatorio de la f ormula anterior, en cada iteraci on del bucle for de las l neas 8891 se calcula un sumando de dicho sumatorio y se acumula dicho sumando en el n umero complejo resul, que previamente se ha inicializado a cero (l neas 8586). La funci on devuelve (mediante copia) el valor de dicho n umero complejo al programa principal. Ejercicios 1. Modicar el programa principal para impedir que el usuario introduzca una dimensi on err onea para los vectores: si el usuario intenta introducir una dimensi on mayor del tama no m aximo del vector MAX DIM o menor o igual a 0, se dar a un mensaje de error y se volver a a pedir la dimensi on de nuevo.

V1 i V2 i

V1 i V2 i

V1 i V2 i i

152

CAPITULO 13. ESTRUCTURAS DE DATOS

2. Modicar el programa principal para crear los vectores din amicamente. Una vez conocida la dimensi on de estos, se asignar a memoria para los dos vectores mediante una llamada a la funci on calloc(). 3. Modicar la funci on ProdEsc() para trabajar con el operador echa -> en lugar del operador corchete [] para acceder a los elementos de los vectores de complejos en el c alculo del producto escalar.

13.9. Estructuras y archivos


Existen dos tipos de archivos en funci on del formato de almacenamiento: los archivos de texto y los archivos binarios. Hasta ahora s olo se han visto los archivos de texto ya que son los m as utilizados, los m as sencillos y valen para almacenar variables de los tipos b asicos (int, float, double...). Para almacenar estructuras en archivos tambi en es posible utilizar archivos de texto, como se ver aa continuaci on, sin embargo lo m as normal en este caso es utilizar archivos binarios.

13.9.1. Diferencia entre archivos de texto y archivos binarios


Los archivos de texto s olo contienen caracteres imprimibles como letras, n umeros y signos de puntuaci on. Estos archivos se pueden abrir y modicar con un editor de texto como por ejemplo el Bloc de notasde Windows. Normalmente se escriben con la funci on fprintf que se encarga de convertir las variables num ericas a caracteres de acuerdo al formato especicado. Por ejemplo si una variable entera vale 2563, al escribir el valor de esta variable en pantalla mediante la funci on printf o en un archivo mediante la funci on fprintf se obtiene una secuencia de caracteres que depende de la especicaci on de formato. Con formato " %5d" se generan los 5 caracteres , 2, 5, 6 y 3; mientras que con formato " %06d" se generan los caracteres 0, 0, 2, 5, 6 y 3. a=2563; printf("a vale:%5d\n",a); printf("a vale:%06d\n",a);

---> --->

a vale: 2563 a vale:002563

En denitiva los archivos de tipo texto son legibles porque s olo contienen caracteres como n umeros, letras y signos de puntuaci on. Las funciones fprintf y fscanf en encargan de hacer las traducciones de formato num erico a caracteres y viceversa. Los archivos binarios se utilizan para almacenar la informaci on como una copia exacta de la memoria del ordenador. Por ejemplo, si una variable entera vale 2563 estar a almacenada en la memoria del ordenador en forma de 2 bytes (en un PC con Windows sizeof(int) vale 2). De acuerdo a la codicaci on de los n umeros enteros estos dos bytes ser an (en hexadecimal) el 0A y el 03 o lo que es igual el 0000 1010 y el 0000 0011 en formato binario. Estos bits son los que realmente se encuentran almacenados en la memoria. Cuanto este n umero entero se guarda en un archivo binario se guardan 2 bytes dentro del archivo (como ya se ha dicho ser an los bytes 0A y 03). Como norma general los archivos binarios no se pueden ver con un editor de texto, ya que ser a una casualidad que los bytes que contienen fuesen caracteres legibles. Siguiendo con el ejemplo anterior, ni el car acter 0A ni el car acter 03 son imprimibles. Tambi en se puede decir que los archivos binarios y los programas que utilizan archivos binarios no son f acilmente portables a otros ordenadores u otros compiladores. Esto es evidente ya que el tama no de las variables no es siempre igual, una variable tipo int ocupa 2 bytes en un PC con sistema operativo Windows, pero normalmente

13.9. ESTRUCTURAS Y ARCHIVOS

153

ocupa 4 bytes en una estaci on de trabajo con sistema operativo Unix. Adem as, no es s olo un problema de tama no sino que unos procesadores ordenan la memoria de una manera (primero 0A y luego 03) y otros al rev es (primero 03 y luego 0A). Es importante tener en cuenta todas estas caracter sticas de los archivos binarios cuando se adaptan programas que funcionan en una conguraci on determinada a otra conguraci on distinta.

13.9.2. Para almacenar estructuras en archivos de texto


Los miembros de una estructura pueden almacenarse individualmente dentro de un archivo de texto mediante la funci on fprintf. Para ello hay que abrir el archivo, escribir el valor de cada estructura (campo por campo) y cerrar el archivo. Ver el siguiente programa de ejemplo donde se utilizan estructuras con el nombre y el precio de 2 productos. /* Programa: texto.c Descripci on: Inicializa un vector de dos elementos tipo estructura y escribe todos los datos en un archivo de texto. Revisi on 0.0: 1/jun/1999 Autor: El programador de texto. */ #include <stdio.h> /* printf, fprintf, fopen, fclose */ #include <stdlib.h> /* exit */ #include <string.h> /* strcpy */ #define N 2 /* Dimensi on de los vectores (n umero de productos) */ /* Declaraci on de una estructura para los productos */ typedef struct { char nombre[12]; int precio; }PROD; void main(void) { PROD producto[N]; /* Vector de productos en el almac en */ int i; /* contador para los bucles*/ FILE *f; /* Descriptor del archivo de texto */ /*** Inicializo del vector ***/ strcpy(producto[0].nombre,"CD Rosana"); producto[0].precio=2563; strcpy(producto[1].nombre,"CD Titanic"); producto[1].precio=2628; /*** Creo el archivo ***/ f=fopen("datos.txt","w"); if (f==NULL) { printf("Error al crear el archivo datos.txt\n"); exit(1); }

154

CAPITULO 13. ESTRUCTURAS DE DATOS

for (i=0; i<N; i++) { fprintf(f,"%s\n",producto[i].nombre); fprintf(f,"%6d\n",producto[i].precio); } fclose(f); } El archivos datos.txt que produce el programa anterior tiene cuatro l neas porque se ha puesto un car acter de cambio de l nea al nal de cada instrucci on fprintf. El archivo tiene el siguiente aspecto: CD Rosana 2563 CD Titanic 2628 Los n umeros no aparecen ajustados al margen izquierdo porque se ha puesto un formato %6d que escribe dos espacios delante de cada n umero de cuatro cifras. El archivo tiene en total 35 caracteres que son los siguientes: CD Rosana

2563 CD Titanic 2628


Nota: Se han representado los espacios como y los caracteres de cambio de l nea como para que se pueda leer mejor.

Ejercicios 1. Comprobar que cada estructura escribe un n umero diferente de caracteres en el archivo. 2. Cu al ser a el archivo resultante si los fprintf fuesen los siguientes?: fprintf(f,"%15s\n",producto[i].nombre); fprintf(f,"%-6d\n",producto[i].precio); Cu antos caracteres ocupar a el archivo?

13.9.3. Para almacenar estructuras en archivos binarios


Las estructuras pueden almacenarse dentro de un archivo binario mediante la funci on fwrite. Para ello hay que abrir el archivo en modo binario, escribir las estructuras y cerrar el archivo. La funci on fwrite no utiliza especicadores de formato sino que realiza un volcado de la memoria en el archivo. El prototipo de la funci on fwrite es: size_t fwrite(void *estructura, size_t tamaNo, size_t numero, FILE *archivo); Donde size t es equivalente a unsigned long int, es decir se reere a un n umero, y void * es un puntero gen erico que vale para cualquier tipo de estructura. Por lo tanto, esta funci on recibe como argumentos un puntero a una estructura (la direcci on de memoria de la estructura), luego el tama no de la estructura en bytes (que se obtiene con sizeof()), el n umero de estructuras que queremos guardar y nalmente el descriptor del archivos en el cual queremos escribir. Ver el siguiente programa de ejemplo que es equivalente al programa del apartado anterior.

13.9. ESTRUCTURAS Y ARCHIVOS

155

/* Programa: binario.c Descripci on: Inicializa un vector de dos elementos tipo estructura y escribe todos los datos en un archivo binario. on 0.0: 1/jun/1999 Revisi Autor: El programador binario. */ #include <stdio.h> /* printf, fwrite, fopen, fclose */ #include <stdlib.h> /* exit */ #include <string.h> /* strcpy */ #define N 2 /* Dimensi on de los vectores (n umero de productos) */ /* Declaraci on de una estructura para los productos */ typedef struct { char nombre[12]; int precio; }PROD; void main(void) { PROD producto[N]; /* Vector de productos en el almac en */ int i; /* contador para los bucles*/ FILE *f; /* Descriptor del archivo de texto */ /*** Inicializo del vector ***/ strcpy(producto[0].nombre,"CD Rosana"); producto[0].precio=2563; strcpy(producto[1].nombre,"CD Titanic"); producto[1].precio=2628; /*** Creo el archivo ***/ f=fopen("datos.dat","wb"); if (f==NULL) { printf("Error al crear el archivo datos.dat\n"); exit(1); } for (i=0; i<N; i++) { fwrite(&producto[i], sizeof(producto[i]),1,f); } fclose(f); } Notar que al abrir el archivo se especica el modo wb, la b indica acceso binario. El archivos datos.dat que produce el programa anterior no puede verse en un editor de texto porque contiene caracteres especiales. Ocupa en total 28 caracteres ya que cada estructura ocupa 14 bytes, que son 12 caracteres del primer nombre, y 2 bytes para el int del precio. Por lo tanto el archivo puede representarse como: CD Rosana ????CD Titanic ???

156

CAPITULO 13. ESTRUCTURAS DE DATOS

Nota: Se han representado los espacios como , los caracteres \0 de nal de cadena como y otros caracteres no imprimibles como ?. En este caso no hay cambios de l nea. Los 12 primeros caracteres corresponden al nombre del primer producto, que es un vector de char de longitud 12 de los cuales s olo 10 caracteres est an inicializados (9 de textttCD Rosana y el 10o el car acter NULL que pone la funci on strcpy) por lo tanto luego aparecen 2 caracteres desconocidos. A continuaci on viene el n umero 2563 codicado como entero que ocupa 2 bytes (que son el 0A y el 03 en hexadecimal). Otra manera de representar el contenido del archivo es la siguiente:

C D R o s a n a \0 ??? ??? C D T i t a n i c \0 ??? Ejercicio

0A 0A

03 44

1. Comprobar que en lugar del bucle for se pod a haber escrito la siguiente l nea: fwrite(&producto[0], sizeof(producto[0]),N,f); Esta instrucci on escribe las N estructuras del vector producto de una sola vez. 2. Cu antos caracteres ocupar a el archivo si el precio se guardase en una variable tipo long en lugar de tipo int? 3. Cu al ser a el archivo resultante si el nombre del primer producto fuese CD-R" y el precio 200?

13.9.4. Funciones fread, fwrite y fseek


Para leer estructuras de un archivo binario se utiliza la funci on fread que es equivalente a la funci on fwrite que se ha visto anteriormente. Los prototipos de estas funciones son los siguiente: size_t fread (void *estructura, size_t tamaNo, size_t numero, FILE *archivo); size_t fwrite(void *estructura, size_t tamaNo, size_t numero, FILE *archivo); Ambas funciones hacen un acceso directo a la memoria del ordenador a partir de la direcci on de la estructura. La funci on fread lee del archivo tamaNo*numero bytes y los guarda a partir de la direcci on de memoria estructura. Por el contrario, la funci on fwrite toma los bytes de la memoria y los escribe en el archivo. En ambos casos las funciones devuelven un valor de tipo size t que es el n umero de estructuras que han sido capaces de leer o de escribir. El valor devuelto suele ser igual al valor solicitado (numero) salvo que se produzca un error. En el caso de escritura es muy raro que se produzca un error, pero en caso de lectura permite localizar el nal del archivo. Una de las caracter sticas m as importantes de los archivo binarios de estructuras es que cada registro ocupa un espacio constante, que es el tama no de la estructura. Esto permite avanzar o retroceder en el archivo para ir a leer un registro concreto, es decir, se permite el acceso directo a los datos. La funci on que permite moverse en un archivo a una posici on concreta se llama fseek y tiene el siguiente prototipo: size_t fseek(FILE *archivo, long posicion, int origen); Donde origen especica desde d onde se calcula la posici on. Puede valer:

13.9. ESTRUCTURAS Y ARCHIVOS


1. SEEK SET desde el principio del archivo. Es la opci on m as normal. 2. SEEK CUR desde la posici on actual. Permite avanzar o retroceder. 3. SEEK END desde el nal del archivo. Permite ir r apidamente al nal del archivo. Ejemplo: fseek(fp,1,SEEK_SET); /* Va a la primera posici on del archivo */ fseek(fp,10*sizeof(producto[0]),SEEK_SET); /* Va al 10o producto */ fseek(fp,2*sizeof(producto[0]),SEEK_CUR); /* Me salto dos productos */

157

158

CAPITULO 13. ESTRUCTURAS DE DATOS

Bibliograf a
[Antonakos and Manseld, 1997] Antonakos, J. L. and Manseld, K. C. (1997). Programaci on estructurada en C. Prentice Hall Iberia, Madrid. [Delannuy, ] Delannuy, C. El Libro de C como Primer Lenguaje. Eyrolles, ediciones Gesti on 2000 S. A., Barcelona. [Kernighan and Ritchie, ] Kernighan, B. W. and Ritchie, D. M. El lenguaje de programaci o n C. Prentice Hall, segunda edici on. [Roberts, 1995] Roberts, E. S. (1995). The Art and Science of C. Addison Wesley.

159

You might also like