You are on page 1of 233

E l C e s e l le n g u a je d e p ro g ra m a c i n d e lo s o c h e n ta : e s

r p id o , e fic ie n te , c o n c is o , e s tru c tu ra d o y f c il d e
tra n s p o rta r d e u n o s o rd e n a d o re s a o tro s .

H o y e n d a , e l 7 0 p o r 1 0 0 d e l software p a ra o rd e n a d o re s
p e rs o n a le s e s t s ie n d o d e s a rro lla d o e n C .

P R O G R A M A C IO N E N C . In tro d u c c i n y c o n c e p to s
a v a n z a d o s , e s u n a g u a p r c tic a q u e p o n e e n s u s m a n o s
to d o lo q u e s e n e c e s ita s a b e r p a ra c o m e n z a r a u s a r e s te
e x c ita n te le n g u a je . S e r , a d e m s , m a n u a l d e c o n s u lta
p a ra q u ie n e s s e s ie n ta n fa s c in a d o s p o r la s
p o te n c ia lid a d e s d e e s te le n g u a je , y a q u e tra ta ta m b i n
lo s a s p e c to s m s a v a n z a d o s d e l le n g u a je C : e l
p re p ro c e s a d o r C , u tiliz a c i n d e e s tru c tu ra s , c a m b io s d e
tip o , o p e ra c io n e s c o n fic h e ro s y m a n e jo d e b its . S e
c u b re ta m b i n e l u s o d e l C e n e n to rn o U N IX , la
u tiliz a c i n d e p u e rto s e n e l 8 0 8 6 /8 0 8 8 , e l re e n v o d e
e n tra d a / s a lid a , g r fic o s , m s ic a , m a c ro s , e tc .

P R O G R A M A C IO N E N C . In tro d u c c i n y c o n c e p to s
a v a n z a d o s e s t b a s a d o e n la im p la n ta c i n e s t n d a r d e l C
d e K e rn ig h a n & R itc h ie e n e l IB M P C .
Programacin
en C
In tro d u c c i n y c o n c e p to s a v a n z a d o s

M itc h e ll W a ite
S te p h e n P ra ta
D o n a ld M a rtin
Indice

Prlogo ..................................................................................................................... 9
1. Preparados... Listos... C!.......................................................................... 13
De dnde viene?C para qu?Adonde va?Uso del C.Algunas
convenciones.Un consejo.

2. Introduccin al C........................................................................................ 27
Un ejemplo sencillo de C.Explicacin.Estructura de un programa
sencillo.Claves para hacer legible un programa.Subiendo un nuevo
peldao.Y mientras estamos en ello...Hasta ahora hemos aprendi
do.Cuestiones y respuestas.Ejercicios.

3. Los datos en C........................................................................................... 45


Datos: variables y constantes.Tipos de datos.Tipos de datos
en C.Utilizacin de tipos de datos.Hasta ahora hemos aprendido.
Cuestiones y respuestas.

4. Tiras de caracteres, #define, printf( ).......................................................... 71


Introduccin a las tiras de caracteres.Constantes: el preprocesador
C.Usos y utilidades de printf( ) y scanf( ).Claves de utilizacin.
Hasta ahora hemos aprendido.Cuestiones y respuestas.

5
5. Operadores, expresiones y sentencias................................................. 97 12. Arrays y punteros....................................... ........................................... 331
Introduccin.Operadores fundamentales.Algunos operadores adi Arrays: Punteros a arrays.Funciones, arrays y punteros.
cionales.Expresiones y sentencias.Conversiones de tipo.Un pro Suplantacin de arrays por punteros.Operaciones con punteros.
grama ejemplo.Hasta ahora hemos aprendido.Cuestiones y respues Arrays multidimensionales.Punteros y arrays multidimensionales.
tas.Ejercicios. Hasta ahora hemos aprendido.Cuestiones y respuestas.Ejercicio.

6. Funciones de entrada/salida y reenvo................................................ 133 13. Tiras de caracteres y funciones relacionadas..................................... 357
E/S de un solo carcter: getchar( ) y putchar( ) .Buffers.Otra eta Definicin de tiras dentro de un programa.Entrada de tiras.Salida
pa.Reenvo.UNIX.E/S dependiente de sistema: puerto de E/S de tiras.La opcin hgaselo usted mismo.Funciones de tiras de
8086/8088.Vamos a tantear la potencia oculta de nuestro ordena caracteres.Ejemplo: ordenacin de tiras.Argumentos en lneas de
dor.Hasta ahora hemos aprendido.Cuestiones y respuestas. ejecucin.Hasta ahora hemos aprendido.Cuestiones y respuestas.
Ejercicios. Ejercicios.
7. Una encrucijada en el camino.............................................................. 163 14. Estructuras de datos y otras lindezas.................................................. 391
La sentencia if. La sentencia if con else. Quin es el ms grande: ope
radores de relacin y expresiones.Seamos lgicos.Un programa pa Problema ejemplo: Creacin de un inventario de libros.Puesta a pun
ra contar palabras.Una caricatura con caracteres.El operador con to del patrn de la estructura.Definicin de variables de estructura.
dicional: ?:. Eleccin mltiple: switch y break. Hasta ahora hemos Cmo acceder a miembros de la estructura.Arrays de estructuras.
aprendido.Cuestiones y respuestas. Estructuras anidadas.Punteros a estructuras.Cmo ensear estruc
turas a las funciones.Y despus de las estructuras, qu?Un vistazo
8. Bucles y tirabuzones.............................................................................. 203 rpido a las uniones.Otro vistazo a typedef.Hasta ahora hemos
aprendido.Cuestiones y respuestas.Ejercicios.
El bucle while. Algoritmos y seudocdigo.El bucle for. Un bucle
con condicin de salida: do while. Con qu bucle nos quedamos? 15. La biblioteca C y el fichero de entrada/salida.................................... 421
Bucles anidados.Otras sentencias de control: break, continue, goto.
Arrays.Una cuestin sobre entradas.Resumen.Hasta ahora hemos Cmo acceder a la biblioteca C.Funciones de biblioteca que ya he
aprendido.Cuestiones y respuestas.Ejercicios. mos utilizado.Comunicacin con ficheros.Qu es un fichero?
Un programa sencillo de lectura de ficheros: fopen( ), fclose( ), getc( )
9. Funcionamiento funcional de las funciones........................................ 243 y putc( ).Un programa sencillo de reduccin de ficheros.Fichero
Creacin y utilizacin de una funcin sencilla.Argumentos de funcio E/S: fprint( ), fscanf( ), fgets( ) y fputs( ).Acceso aleatorio:
nes.Devolucin de un valor desde una funcin: return. Variables lo fseek( ).Comprobacin y conversin de caracteres.Conversiones de
cales.Localizacin de direcciones: el operador &.Alteracin de va tiras de caracteres: atoi( ), atof( ).Salida: exit( ).Asignacin de me
riables en el programa de llamada.A ver cmo funcionamos.Cmo moria: malloc( ) y calloc( ).Otras funciones de biblioteca.
especificar tipos de funciones.Todas las funciones C se crean de la mis Conclusin.Hasta ahora hemos aprendido.Cuestiones y respues
ma manera.Resumen.Hasta ahora hemos aprendido.Cuestiones tas.Ejercicios.
y respuestas.Ejercicios.
APENDICE A: Lecturas adicionales.......................................................... 447
10. Modos de almacenamiento y desarrollo de programas.....................277 Lenguaje C.Programacin.El sistema operativo UNIX.
Modos de almacenamiento: Propsito.Una funcin de nmeros alea
torios.Lanza los dados.Una funcin para atrapar enteros: APENDICE B: Palabras clave en C........................................................... 449
getint( ) .Ordenacin de nmeros.Resumen.Hasta ahora hemos
aprendido.Cuestiones y respuestas.Ejercicios. Palabras clave de control de programas.

11. El preprocesador C............................................................................... 313 APENDICE C: Operadores C..................................................................... 451

Constantes simblicas: #define. Utilizacin de argumentos con APENDICE D: Tipos de datos y modos de almacenamiento . . . . . . . . . . 4 5 5
#define. Macros o funciones? Inclusin de un fichero: #include.
Otros comandos: #undef, #if, #ifdef, #ifndef, #else y #endif. Hasta Tipos de datos bsicos.Cmo declarar una variable simple.Modos
ahora hemos aprendido.Cuestiones y respuestas.Ejercicio. de almacenamiento.

7
APENDICE E: Control de flujo en el programa........................................... 459
La sentencia while.La sentencia for.La sentencia do while.
Utilizacin de sentencias if para elegir entre opciones.Eleccin mlti
ple con switch.Saltos en el programa.
Prlogo

C es un lenguaje de programacin sencillo y elegante, que se ha transfor


mado rpidamente en el medio elegido por un nmero cada vez mayor de
programadores para comunicarse con su ordenador. Este libro (Programa
cin en C, por si ha perdido la tapa) pretende ser una gua sencilla para apren
der y un libro de consulta, ms adelante, para aquellos que se sientan fasci
nados por las potencialidades de este lenguaje.
En el subttulo se recalca que es ste un libro para los que empiezan. Con
ello queremos indicar que nuestro primer objetivo es guiar al lector en sus
primeros pasos por los vericuetos bsicos del C. En programacin, la expe
riencia es el gran maestro; por ello encontrar en el libro multitud de ejem
plos con los que jugar y estudiar. Hemos utilizado figuras all donde hemos
credo que ayudaban a aclarar un determinado punto. De tanto en tanto, se
resumen y destacan las caractersticas fundamentales del C, para hacerlas f
ciles de localizar. Hay tambin cuestiones (y respuestas) que permiten com
probar lo que hemos avanzado. En principio, no suponemos que el lector
posee un conocimiento al dedillo de ningn otro lenguaje clsico de progra
macin, pero s comparamos de vez en cuando este lenguaje con los dems,
con el fin de ayudar a aquellos lectores que s dominan alguno.
La segunda parte del subttulo del libro pretende indicar que, adems de
manual de principiantes, el libro contiene bastantes ms cosas. Lo primero,
la seccin de cuestiones y respuestas apuntada anteriormente. Adems, in
tentando llegar ms lejos de lo que un primer manual alcanza, discutiremos

9
algunos de los aspectos ms avanzados del C, como utilizacin de estructu
ras, cambios de tipo, operaciones con ficheros y, en el apndice, manejo de
bits en el C, as como algunas extensiones del propio lenguaje. El libro cubre
tambin el entorno del C en microordenadores y UNIX; por ejemplo, se dis
cute el reenvo de entrada/salida en ambos entornos, y se comenta la utiliza
cin de los puertos en microprocesador 8086/8088. Los dibujos y pequeas
historietas introducidos pueden considerarse tambin como extra; por cier
to, un extra bastante agradable.
Hemos intentado, por encima de todo, hacer esta introduccin al C til,
instructiva y clara. Para sacar a este libro todo el partido posible deber us
ted, lector, jugar un papel lo ms activo posible. No se limite a leer los ejem
plos; antes bien, introdzcalos en su sistema e intente hacerlos funcionar ade
cuadamente. El C es un lenguaje de programacin muy fcil de transportar
de un sistema a otro, pero quiz encuentre alguna diferencia marginal en la
forma de ejecucin de programas en su sistema con respecto al nuestro. No
se canse de experimentar; cambie alguna parte del programa que est ejecu
tando y observe el efecto producido; modifique el programa para hacerle rea
lizar una tarea ligeramente diferente; haga caso omiso de nuestras adverten
cias en un momento determinado para ver lo que sucede; intente realizar el
mayor nmero posible de cuestiones y ejercicios. Cuanto ms ponga de su
parte, ms aprender.
Le deseamos la mejor de las suertes en el camino de aprendizaje de C.
Hemos intentado que el libro se acople a sus necesidades, y esperamos que,
por su parte, alcance los objetivos que se ha propuesto.

Mitchell Waite
Stephen Prata
Donald Martin
Preparados...
listos... C!
En este captulo encontrar:

De dnde viene?
C para qu?
Adonde va?
Uso del C
Uso de un editor para la preparacin del programa
Ficheros fuente y ficheros ejecutables
Compilacin de C en un sistema UNIX
Compilacin de C en un IBM PC (Microsoft/Lattice C)
Otra forma distinta de compilar
Algunas convenciones
Un consejo

13
Por supuesto, suponemos que la mayor parte de los lenguajes pretende)
Preparados... ser tiles, pero a menudo establecen otros objetivos adicionales. Por ejem
plo, uno de los objetivos principales del PASCAL es proporcionar una base
slida para enseanza de los principios de programacin. El BASIC, por si

listos... C! parte, se desarroll intentando asemejarse al ingls, de manera que fuese f


cilmente comprendido por estudiantes no familiarizados con ordenadores (
si son de habla inglesa, mejor). Todos estos objetivos, evidentemente, son
importantes, pero no siempre son compatibles con la utilidad pura y simple
El C ha sido creado como herramienta de programacin, manteniendo, ade
CONCEPTOS ms, una justa fama de lenguaje amistoso para el programador.

Historia del C
Virtudes del C C para qu?
Lenguajes compilados
Ejecucin de un programa C El lenguaje C se est transformando rpidamente en una de las bases de
programacin ms importantes y populares. Esta creciente utilizacin se de
be a que la gente lo prueba y le gusta; conforme vaya avanzando en su apren
dizaje, tambin usted se sentir atrado por sus numerosas virtudes. Mencio-
naremos a continuacin algunas de ellas.
El C es un lenguaje moderno, que incorpora las caractersticas de control
apuntadas como deseables por la teora y prctica de la informtica. Su pro
pio diseo hace que resulten naturales para el usuario aspectos como la pla
nificacin escalonada, programacin estructurada y diseo modular; el re
sultado es un programa ms fiable y comprensible.
Bienvenido al mundo del C. En este captulo le ayudaremos a prepararse
para emprender el aprendizaje de este poderoso lenguaje, cada vez ms po
pular. Qu necesita para estar listo? En primer lugar, necesita interesarse
por el C; suponemos que ya ha asumido este punto. No obstante, trataremos
de aumentar su inters exponiendo brevemente algunos de los aspectos ms
seductores del C. En segundo lugar, necesita una gua que le introduzca en
el lenguaje; para eso est este libro. Adems, necesita tener acceso a un orde
nador que posea un compilador C; esto lo tendr que arreglar por su cuenta.
Por ltimo, necesita saber cmo ejecutar un programa C en su sistema; le
daremos algunos consejos acerca de este particular al final del captulo.

D e d o n d e v ie n e ?
El C fue creado por Dennis Ritchie, de los Laboratorios Bell, en 1972,
cuando trabajaba, junto con Ken Thompson, en el diseo del sistema opera
tivo UNIX. Por otra parte, el C no surgi por generacin espontnea del ce
rebro de Ritchie; se deriva del lenguaje B de Thompson, el cual, a su vez...,
pero eso es otra historia. Lo importante es que el C se cre como herramien
ta para programadores. En consecuencia, su principal objetivo es ser un len
guaje til.
15
El C es un lenguaje eficiente. Su diseo aprovecha las habilidades de
los ordenadores al uso. Los programas C tienden a ser compactos y ejecutar
se con rapidez.
El C es un lenguaje porttil. Con ello queremos significar que los progra
mas C escritos en un sistema pueden ejecutarse en otros sin ninguna modifi
cacin, o con modificaciones mnimas. En este ltimo caso, a menudo las
modificaciones se reducen a cambiar unas cuantas sentencias de entrada en
un fichero de encabezamiento (header) que acompaa al programa princi
pal. Por supuesto, la mayor parte de los lenguajes pretenden ser porttiles;
sin embargo, cualquiera que haya intentado convertir un programa en BA
SIC IBM PC a Apple BASIC (y eso que son primos hermanos), o que haya
intentado ejecutar un programa FORTRAN en un gran ordenador IBM con
sistema UNIX, sabr que la cosa no es tan sencilla; de hecho, aparecen gran
nmero de pequeos detalles que pueden crear problemas. En este sentido,
el C es un lder en lenguajes porttiles. Existen compiladores C para unos
40 sistemas, que abarcan desde microprocesadores de 8 bits hasta el actual
campen mundial de velocidad en ordenadores, el Cray 1.
El lenguaje C es tambin poderoso y flexible (dos de las palabras favoritas
en la bibliografa de ordenadores). Por ejemplo, la mayor parte del sistema
operativo UNIX, poderoso y flexible (lo ve?) como pocos, est escrita en
C. Incluso estn escritos en C los compiladores e intrpretes de otros lengua
jes, como FORTRAN, APL, PASCAL, LISP, LOGO y BASIC. As pues,
cuando utilice FORTRAN en una mquina UNIX recuerde que, a la postre,
hay un programa C que est haciendo el trabajo de produccin del programa
ejecutable final. Se han utilizado programas C para resolver problemas fsi Estructuras Formato
cos e ingenieriles, e incluso para produccin de secuencias animadas en pel flexibles de control estructurado
culas como El Retomo del Jedi.
El C posee control sobre aspectos del ordenador asociados generalmente
con lenguajes ensambladores. Si lo desea, puede afinar al mximo sus pro
gramas para lograr la mayor eficiencia. GASTOS DE
El C es un lenguaje amistoso. Es lo suficientemente estructurado para INSTALACION:
10.000 pts.
ejercer buenos hbitos de programacin, pero en ningn caso le encorseta
en un mare mgnum de restricciones. BASIC
Podramos citar ms virtudes y, sin duda, algunos defectos. En lugar de GASTOS DE
INSTALACION:
profundizar ms en el asunto, vayamos a la siguiente pregunta. 100.000 pts Pascal
GASTOS DE
INSTALACION:
50.000 pts.
a donde va?
El C es ya el lenguaje predominante en el mundo de los miniordenadores
de sistemas UNIX; actualmente, se est extendiendo a los ordenadores per
sonales. Muchas compaas de software estn utilizando C con preferencia Programas reducidos Transportable
a otros lenguajes en sus programas: procesado de textos, hojas electrnicas, cdigo compacto a otros ordenadores
compiladores, etc. Estas compaas saben que el C produce programas com
pactos y eficientes, y, lo que es ms importante, saben tambin que estos pro Figura 1.1
gramas son fciles de modificar y de adaptar a nuevos modelos de ordenadores. Virtudes del C
JUEGOS DE grama C. Si, por el contrario, su formacin informtica se basa en un lenguaje
ORDENADOR intrprete, como BASIC o LOGO, o si carece por completo de formacin
SISTEMA en ningn lenguaje, encontrar el proceso un poco extrao al principio. Afor
EL RETORNO tunadamente, estamos aqu para guiarle a lo largo del mismo, y se dar cuenta
OPERATIVO
DEL JEDI UNIX de que, en realidad, es bastante directo y lgico.
Daremos, en primer lugar, un repaso rpido del proceso. En sntesis, lo
que debe hacer desde el momento que comienza a escribir el programa hasta
ejecutarlo es:
LENGUAJE 1-. Utilizar un editor para escribir el programa C.
MICROORDE MINIORDENADORES
NADORES
2. Enviar el programa a su amigo el compilador. Este comprobar si su
programa tiene algn error, y, en su caso, se lo har saber. En caso
contrario, el compilador acometer la tarea de traducir el programa
al lenguaje interno de su ordenador, y colocar la traduccin en un
Paquetes de nuevo fichero.
LENGUAJES
3. A continuacin, ya puede ejecutar el programa tecleando el nombre
ORDENADOR SOFTWARE de este nuevo fichero.
PROGRAMAS En algunos sistemas, la segunda etapa puede estar subdividida, a su vez,
DE GESTION en dos o tres subetapas, pero la idea sigue siendo la misma.
F igu ra 1.2.
A continuacin daremos una batida ms profunda de cada una de las etapas
El C se usa para... apuntadas arriba.

Otro factor que contribuye a la diseminacin del C hacia el mundo de Uso de un editor para la preparacin del programa
los ordenadores personales es la actitud de los usuarios de C UNIX, que de A diferencia del BASIC, el C no posee su propio editor. En su lugar, uti
sean poder llevar a casa sus programas C. Actualmente existen ya varios com lice un editor de propsito general que est disponible en su sistema. En un
piladores C que les permiten hacerlo. sistema UNIX, por ejemplo, podra ser ed, ex, edit, emacs o vi. En un siste.-
Pensamos que lo que es bueno para las compaas y para los veteranos ma de ordenador personal, puede ser ed, edling, Wordstar, Volkswriter o cual
del C debe serlo tambin para otros usuarios. Hay cada vez ms programa- quier otro de entre los muchos que existen.
dores que utilizan C simplemente para aprovechar sus ventajas. No hay que Con algunos de estos editores, tendr que especificar una opcin particu
ser un profesional de los ordenadores para utilizarlo. lar. Por ejemplo, si utiliza Wordstar, deber usar la opcin N, opcin de no
En resumen, el C est destinado a ser uno de los lenguajes ms importan documento.
tes de esta dcada y de los aos noventa. Se utiliza en miniordenadores y en Las dos misiones principales que tiene a su cargo son: teclear el programa
ordenadores personales. Lo usan compaas de software, estudiantes de in correctamente y escoger un nombre para el fichero en que almacene dicho
formtica y entusiastas de todas clases. Por cierto, si desea buscar un trabajo programa. Las reglas que se siguen para este nombre son muy simples: debe
escribiendo software, una de las primeras preguntas a las que deber respon ser un nombre permitido en su sistema y debe terminar con .c. He aqu dos
der s es: De manera que sabe usted C? ejemplos.
ordena.c
c suma.c

El C es un lenguaje compilado. Si no le suena esta palabra, no se preo Escoja la primera parte del nombre de manera que le recuerde lo que ha
cupe; vamos a explicarle lo que significa conforme vayamos describiendo las ce el programa. La segunda parte (.c) identifica el fichero como programa C.
etapas necesarias para producir un programa C. En el mundo mgico de los ordenadores, la parte del nombre que va seguida
Si es usuario de un lenguaje compilado como PASCAL o FORTRAN, de un punto se denomina una extensin. Se utilizan las extensiones para
encontrar familiares las etapas bsicas necesarias para echar a andar un pro informar al ordenador (y a usted mismo) sobre la naturaleza del fichero.

19
He aqu un ejemplo: utilizando un editor, preparamos el siguiente pro Ficheros fuente y ficheros ejecutables
ama y lo almacenamos en el fichero informe .c.
Nuestro programa, maravilloso sin lugar a dudas, resulta, sin embargo,
totalmente incomprensible para el ordenador. Un ordenador no entiende co
#include <stdio.h>
main() sas como #include o printf. Lo nico que entiende es cdigo mquina,
que son aberraciones tales como 10010101 y 01101001. Si queremos que el
printf("Se usa .c para acabar un fichero ce p ro gram a C \n");
ordenador se muestre cooperativo, deberemos traducir nuestro cdigo (cdi
}
go fuente) a su cdigo (cdigo mquina). El resultado de nuestros esfuerzos
El texto que acabamos de teclear se llama cdigo fuente, y se guarda ser un fichero ejecutable, que es un fichero relleno con todo el cdigo
mquina que ejecuta el ordenador para realizar su trabajo.
un fichero fuente. Es importante aclarar aqu que nuestro fichero fuente
Este asunto de la traduccin puede parecer tedioso: no se preocupe. Nos
el comienzo de un proceso, no el final. las hemos arreglado para asignar el trabajo de traduccin al propio ordena
dor. Existen programas muy inteligentes, llamados compiladores, que se
encargan del trabajo sucio. Los detalles del proceso dependen de cada siste
ma en particular; a continuacin, veremos algunos de ellos.

Compilacin de C en un sistema UNIX


El compilador C de UNIX se llama cc. Lo nico que tenemos que hacer
para compilar nuestro programa es teclear.
cc informe.c

Transcurridos unos segundos, aparecer un mensaje de UNIX para co


municarnos que nuestros deseos han sido cumplidos (podemos tambin en
contrar advertencias y mensajes de error si no hemos escrito el programa co
rrectamente; supongamos, por el momento, que se realiz todo bien). Si ahora
utilizamos ls para listar nuevos ficheros, encontraremos que ha aparecido un
nuevo fichero llamado a.out. Este es el fichero ejecutable que contiene las
traducciones (o compilacin) de nuestro programa. Para ejecutarlo, sim
plemente teclee
a.out

y nuestra sabidura se ve por fin recompensada:


Se usa .c para acabar un fichero de p ro gram a C

El programa cc combina varias etapas en una. Se comprende este punto


con mayor claridad cuando realizamos el mismo proceso en un ordenador
personal.

Compilacin de C en un IBM PC (Microsoft/Lattice C)


Las etapas concretas que se han de seguir aqu dependen del sistema ope
F igu ra 1.3
rativo y del compilador. Utilizaremos como ejemplo un compilador Micro
Intrpretes y compiladores soft C soportado en un PC DOS 1.1. (El compilador Lattice C, en el que

21
o simplemente

informe.

conseguiremos ejecutar el programa.

se basa la versin Microsoft, utiliza el mismo formato: simplemente usa lc1


y 1c2 en lugar de mc1 y mc2.)
Comenzamos de nuevo con un fichero llamado informe.c. Nuestra pri
Figura 1.5
mera orden es Preparacin de un programa C en Microsoft/Lattice C
mc1 informe
En realidad, no tiene por qu aprender qu est sucediendo en este pro
El compilador interpreta informe como informe.c. Si todo va con nor cedimiento; aun as, por si le interesa, comentaremos los puntos ms impor
malidad, esta orden produce un informe intermedio llamado informe.q. Te tantes.
clee a continuacin Qu hay aqu de nuevo? Desde luego, el fichero informe.obj es nuevo,
Es un fichero en cdigo mquina; la pregunta que surge inmediatamente es:
mc2 informe por qu no hemos parado aqu? La respuesta es que el programa complete
incluye partes que no han sido escritas. Por ejemplo, utilizamos en el progra
lo que producir el fichero llamado informe.obj. Este fichero contiene el c ma algunas subrutinas estndar de la biblioteca C. As, el programa necesi
digo objeto (cdigo en lenguaje mquina) en nuestro programa. Ms ade tar tomar estas subrutinas de donde se hallen almacenadas. Esta misin la
lante volveremos sobre este punto. Teclee despus realiza el comando link que hemos introducido.
Link forma parte del sistema operativo IBM DOS. Su misin es concate
link c informe nar nuestro cdigo objeto (informe.obj) con un fichero que contiene algunas
utilidades estndar (c.obj) y buscar la biblioteca que hemos especificado; en
lo que producir el fichero llamado informe.exe. Este era nuestro objetivo, este caso, lc.lib. A continuacin enlaza todos los elementos para producir
un fichero ejecutable. Si ahora tecleamos el programa final.
El programa cc del UNIX pasa por una secuencia similar de etapas; lo
informe.exe que sucede en este caso es que la secuencia queda inadvertida, porque el pro-

23
pio objeto se borra cuando ya no es necesario. (Pero, si se lo pedimos con Color
educacin, nos proporcionar el cdigo objeto con el nombre informe.o.) Estamos utilizando un color azul para representar las respuestas y deman
das que componen el funcionamiento interactivo entre el ordenador y el usua
Otra forma distinta de compilar rio. Tambin utilizamos el color azul en los resmenes, con el fin de hacerlo
Algunos compiladores de C adaptados a ordenadores personales utilizan ms localizables.
un camino diferente. El mtodo que hemos discutido hasta ahora produce
un fichero de cdigo objeto (extensin .obj) y utiliza el linker del sistema pa- Perifricos de entrada y salida
ra producir un fichero ejecutable (extensin .exe). El mtodo alternativo es Hay muchas formas por las que se puede comunicar un ordenador con
generar un fichero de cdigo ensamblado (extensin .asm) y utilizar a con- un usuario como usted, por ejemplo. Supondremos en adelante que los co-
tinuacin el ensamblador del sistema para producir un fichero ejecutable. mandos se introducen desde teclado, y las salidas del ordenador se leen en
Pero bueno, otro cdigo ms! El cdigo ensamblador est estrechamen- pantalla.
te relacionado con el cdigo mquina. De hecho es simplemente una repre
sentacin mnemotcnica del mismo. Por ejemplo, JMP podra significar Teclas
11101001, que es parte de un cdigo mquina que instruye al ordenador para
que salte (jump, en ingls) a un sitio diferente. (Si se imagina que con esto En general, se enva al ordenador una lnea completa de instrucciones,
queremos decir que el ordenador salte de la mesa al suelo, est usted en un apretando como final de lnea una tecla que, dependiendo del sistema, est
error; nos referimos a un salto en una direccin de memoria diferente.) Los marcada como enter, c/r o return, en minsculas o maysculas. Nos
humanos encuentran el cdigo ensamblador mucho ms digerible y fcil de referiremos a esta tecla con la notacin [enter]. Con ello queremos indicar
recordar que el cdigo mquina puro; el programa ensamblador, por su par que lo que debe hacer es pulsar esta tecla, y no teclear e-n-t-e-r.
te, se encargar de realizar la correspondiente traduccin. Tambin nos referiremos a los caracteres de control de la forma [control-d].
Esta notacin significa pulsar la tecla [d] manteniendo apretada la tecla
Pero, por qu? control.
Aquellos de ustedes que utilicen BASIC se estarn preguntando el moti Nuestro sistema
vo de todas estas etapas preliminares a conseguir ejecutar el programa; pue
den parecer simplemente una prdida de tiempo; de hecho, pueden llegar a Hay algunos aspectos del C, tales como la cantidad de espacio utilizado
ser una prdida de tiempo. Sin embargo, una vez que el programa ha sido para almacenar un nmero, que dependen del sistema utilizado. Cuando de
compilado, se ejecutar mucho ms rpidamente que un programa BASIC mos ejemplos, aludiremos frecuentemente a nuestro sistema: nos estamos
estndar. As pues, tenemos que sortear algunos inconvenientes con el fin refiriendo a un IBM PC con sistema operativo DOS 1.1 y utilizando un com
de conseguir un programa ms eficiente como producto final. pilador Lattice C.
En alguna ocasin trabajaremos tambin con programas realizados en un
sistema UNIX. En este caso nos referimos a un ordenador VAX 11/750 equi
pado con una versin UNIX BSD 4.1 de Berkeley.
convenciones
Estamos ya casi listos para empezar. Lo nico que queda es mencionar Un consejo
algunas convenciones que utilizaremos.
La mejor manera de aprender a programar es programar, no limitarse a
Tipo de letra leer. Hemos incluido en el libro multitud de ejemplos. Debe intentar ejecutar
Cuando se pretenda en el texto representar programas, entradas y salidas algunos de ellos en su sistema, para adquirir una idea mejor de cmo funciona.
de ordenador, nombre de ficheros, y variables, utilizaremos un tipo de letra Intente hacer modificaciones para ver lo que pasa. Trabaje con las cuestiones
que se asemeje al que se puede observar en una pantalla o impresora. Ya lo y ejercicios que aparecen al final de los captulos. En resumen, procure ser
hemos utilizado algunas veces antes de este punto; en caso de que le haya un alumno curioso y emprendedor; con ello lograr aprender C en profundi
pasado inadvertido, el tipo de letra tiene esta apariencia: dad y rpidamente.
Bueno, ya est usted listo y nosotros tambin; pasemos al captulo 2.
printf( " H o l a ! \ n ) ;

25
2
Introduccin
al C
En este captulo encontrar:
Un sencillo programa de C
Explicacin
Primera pasada: resumen rpido
Segunda pasada: detalles
Estructura de un programa sencillo
Claves para hacer legible un programa
Subiendo un nuevo peldao
Y mientras estamos en ello...
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios
num = 1 ;

Introduccin al C printf("Soy un modesto ");


printf("ordenador.\n);
printf("Mi numero es el %d por ser el primero.\n",num);

}
CONCEPTOS Si piensa que este programa imprime algo en la pantalla, le felicitamos,
ha acertado! Lo que probablemente no sabr es la forma exacta en que va
Estructura de un programa sencillo a aparecer el texto. Para averiguarlo, ejecutemos el programa y veamos lo
Declaracin de variables que pasa.
Utilizacin de comentarios En primer lugar, deber usar su editor, para crear un fichero que contenga
Programa legible este inocente conjunto de lneas. Deber otorgar un nombre a este fichero;
si est demasiado excitado para pensar en uno, utilice prog.c como nombre
del fichero. Compile este programa a continuacin. (Esperamos pacientemente
OPERADORES mientras usted consulta el manual del compilador de su sistema.) Ejecute el
programa. Si todo ha ido bien, la salida tendr un aspecto como:
Soy un modesto ordenador. Mi numero es el 1 por ser el primero

Desde luego, el resultado no es nada sorprendente. Pero, qu sucede con


los smbolos \ n y %d del programa? Por otra parte, algunas lneas del pro
grama tienen un aspecto bastante extrao. Es el momento de una explicacin.

Qu apariencia tiene un programa C? Quiz haya observado los peque


Explicacin
os ejemplos dados en el captulo 1, o visto algn listado en otro sitio, y en Haremos dos pasadas por el programa. En la primera aclararemos el sig
cuentra este lenguaje con un aspecto un tanto extravagante, repleto de sm nificado de cada lnea, y en la segunda veremos ms detalladamente algunas
bolos como { y *ptr + + . Segn vaya avanzando en el libro, encontrar que implicaciones y detalles dentro del mismo.
la aparicin de estos y otros smbolos caractersticos del C le parecen menos
extraos, ms familiares y quiz incluso agradables. En este captulo comen Primera pasada: resumen rpido
zaremos por presentar un programa ejemplo bastante sencillo y explicar lo #include <stdio.h> inclusin de otro fichero.
que hace. Al mismo tiempo, exploraremos algunos de los rasgos bsicos del C. Esta lnea comunica al ordenador que debe incluir informacin que se en
Si echa de menos una explicacin ms detallada, no se preocupe; ya la en cuentra en el fichero stdio.h.
contrar en los captulos siguientes.
m a i n ( ) un nombre de funcin.
Los programas C se componen de una o ms funciones, las cuales son
Ejemplo sencillo de C los mdulos bsicos del programa. En este caso concreto, el programa con
siste en una sola funcin llamada main. Los parntesis identifican main()
Vamos a observar un programa sencillo en C. Admitimos de antemano como nombre de funcin.
que el ejemplo dado es deliciosamente intil, pero nos sirve para resaltar al /* un programa sencillo */ Un Comentario
gunas caractersticas bsicas de un programa C. Ms adelante lo explicare Se pueden utilizar los smbolos /* y */ para encerrar comentarios.
mos lnea a lnea, pero antes observe el programa e intente averiguar lo que Los comentarios son notas que se introducen para hacer ms claro el pro
hace. grama. Estn pensados para el lector, y son ignorados por el ordenador.
#include <stdio.h>
main() /* un p r o g r a m a se ncillo */ {comienzo del cuerpo de la funcin
{ Esta llave marca el comienzo de las sentencias que componen la funcin.
int num;

29
Esta sentencia imprime la frase comprendida entre las comillas:
Soy un modesto
printf ("ordenador. \n")- una nueva sentencia de escritura

Esta sentencia aade


ordenador.
al final de la frase anterior. El smbolo \n es un cdigo que indica al orde
nador que salte a una nueva lnea.
printf("Mi numero es el %d por ser el primero. \n", num);
Esta lnea imprime el valor de num (que es 1) dentro de la frase que est
entre comillas. El smbolo %d indica al ordenador dnde y en qu forma
debe imprimir el valor de num.
} final
Tal como prometimos, el programa finaliza con una llave de cierre.
Hagamos ahora un estudio ms detallado del mismo programa.

Segunda pasada: detalles


#include <stdio.h>
El fichero stdio.h se suministra como parte del compilador C, y contiene
informacin de aspectos relacionados con la entrada y salida de datos (co
municaciones entre el programa y la terminal, por ejemplo). El nombre pro
cede de standard input/output header, encabezamiento estndar de en
trada/salida. (La gente del C llama encabezamiento a un conjunto de infor
maciones que van en la parte superior de un fichero.)
Algunas veces necesitar usted incluir esta lnea, y otras, no. No le pode
mos facilitar una regla segura, ya que la respuesta depende del programa y
del sistema. En nuestro sistema no hubisemos necesitado esta lnea para es
te programa en concreto; sin embargo, puede suceder que en el suyo s sea
necesaria. En cualquier caso, la inclusin de la lnea no produce ningn efec
Figura 2.1 to nocivo. En adelante, slo la introduciremos cuando la lnea sea realmente
Anatoma de un programa C necesaria en nuestro sistema.
Probablemente se estar preguntando por qu algo tan bsico como la
La definicin de la funcin terminar con una llave de cierre, }. entrada y salida de informacin no est incluida automticamente. Una res
puesta vlida podra ser que no todos los programas utilizan un paquete de
int num; una sentencia de declaracin E/S (Entrada/Salida), y la eliminacin de cargas innecesarias forma parte
Esta sentencia anuncia que se utilizar una variable llamada num y que de la filosofa del lenguaje C. Y ya que hablamos de ello, comentaremos que
esta variable ser de tipo entero (integer). esta lnea no es ni siquiera una sentencia del lenguaje C. El smbolo # la iden
tifica como lnea a ser manipulada por el preprocesador C. Tal como in
num = 1 ;- una sentencia de asignacin ferir por su nombre, el preprocesador realiza algunas tareas antes de comenzar
a actuar el compilador. Ms adelante aparecern nuevos ejemplos de instruc
Esta sentencia asigna el valor 1 a num. ciones de preprocesador.
printf ("Soy un modesto "); una sentencia de escritura main()

31
La verdad es que main es un nombre bastante abstruso, pero, en este ca El punto y coma es parte de la sentencia, y no simplemente un separador d
so, no tenemos otra eleccin posible. Un programa C comienza su ejecucin sentencias, como sucede en PASCAL.
siempre con la instruccin que recibe el nombre de main ( ), es decir, princi La palabra int es una palabra clave C que identifica uno de los tipo
pal. Todas las dems funciones podrn llevar nombres elegidos por nosotros, bsicos de datos en C. Se llaman palabras clave a aquellas que se utilizan
pero siempre ha de haber una funcin main ( ) para echar a andar el progra dentro del lenguaje; encontrar una lista de palabras clave C en el apndice.
ma. Y los parntesis? Los parntesis identifican a main ( ) como funcin; En C es obligatorio declarar todas las variables que se utilizan: con ello
ms adelante trataremos las funciones con profundidad. Por el momento nos queremos decir que se debe suministrar una lista de todas las variables que
limitaremos a repetir que las funciones son los mdulos bsicos de un pro se usarn ms adelante, indicando en cada una de ellas a qu tipo pertene-
grama C. cen. La declaracin de variables se considera en general como una Buena Idea
Estos parntesis, en general, incluyen informacin que est siendo tras Llegado a este punto, tendr probablemente en mente tres preguntas. La
pasada a la funcin. En nuestro ejemplo elemental no hay informacin algu primera, qu alternativas tengo para elegir un nombre? La segunda, que
na que pasar; por tanto, el contenido de los parntesis es nulo. Por el mo significa eso de tipos de datos? La tercera, por qu hay que declarar las va
mento, no se olvide de ponerlos; pero, por lo dems, no se preocupe por ellos. riables? Hemos preparado dos apartados para responder a la primera y a la
El fichero que contiene el programa tiene un nombre tambin; en este ca tercera preguntas.
so, s puede ser cualquier nombre elegido por nosotros en tanto en cuanto Por lo que se refiere a la segunda, trataremos de ella en el captulo 3; aqu
satisfaga las convenciones de su sistema y finalice con .c. Por ejemplo, pode va un pequeo adelanto. El C maneja varias clases (o tipos) de datos: en
mos utilizar perfecto.c o tonto.c en lugar de main.c como nombre de fichero teros, caracteres y punto flotante, por ejemplo. El hecho de declarar una
para albergar nuestro programa. variable como entero o como carcter permite al ordenador almacenar, loca-
/* un programa sencillo */ lizar e interpretar adecuadamente el dato.

Debe utilizar comentarios para hacer ms comprensible el programa para


los dems y para usted mismo. Una agradable propiedad de los comentarios C
es que se pueden colocar en la misma lnea que la sentencia que se desea acla
rar. Si el comentario es ms largo, se pueden colocar en su propia lnea o ELECCION DE NOMBRE
extenderse por ms de una. Cualquier cosa que comience por /* y termine
Le sugerimos que utilice nombres con significado para las variables. Se
por */ es ignorada por el compilador, lo cual est muy bien, ya que, por lo pueden utilizar hasta ocho caracteres por nombre. (En realidad, se pueden
dems, los comentarios suelen ser bastante ininteligibles para un compilador C. usar ms, pero en C se ignoran todos excepto los ocho primeros. As, el
ordenador no distingue escabeche y escabechina (!), ya que sus ocho prime
{y}: ros caracteres son idnticos.) Por lo dems, se pueden utilizar como carac
Las llaves indican el comienzo y final de una funcin. Unicamente se pue teres las letras minsculas, las letras maysculas, los nmeros y el smbolo
den utilizar llaves {} para este propsito, no siendo vlidos los parntesis ( ) de subrayar__, el cual cuenta como una letra. En todo caso, el primer ca
rcter debe ser una letra.
ni los corchetes [ ].
Tambin se pueden utilizar las llaves para encerrar un grupo de senten Nombres vlidos Nombres no vlidos
cias dentro del programa. Estas sentencias constituyen una unidad o blo
que. Si tiene cierta familiaridad con el lenguaje PASCAL o ALGOL, ob pepe
n o m b re 1 1n o m b re
servar que las llaves cumplen una funcin similar a las sentencias begin y M i_C a sa M i-C a sa
end de estos lenguajes. _n u m ero P aco's

int num ; Las subrutinas de biblioteca utilizan a menudo nombres que comienzan
con el smbolo de subrayar. Se hace as con la idea de que los usuarios no
La sentencia de declaracin es una de las caractersticas ms impor utilizarn generalmente nombres de este tipo; as existen pocas posibilida
tantes del C. Como ya se dijo anteriormente, en este caso concreto se decia des de que se utilice accidentalmente el nombre de alguno de los ficheros
rn dos cosas: primero, que en algn sitio de la funcin se utilizar una va de biblioteca. Es una buena poltica; por consiguiente, resistir la tentacin
riable con el nombre num. En segundo lugar, el prefijo int proclama que de utilizar nombres que comiencen por dicho smbolo, evitando as el ries
num es un entero, es decir, un nmero sin decimales. El smbolo punto y co go de una colisin con la biblioteca del sistema.
ma del final de la lnea identifica sta como una sentencia C o instruccin.

32
CUATRO BUENAS RAZONES PARA DECLARAR VARIABLES

1. Una observacin del conjunto de variables, estando todas ellas agrupadas,


hace ms fcil al lector la comprensin de la finalidad del programa. Se
mejora ms an esta caracterstica utilizando nombres de variables con
significado (por ejemplo, tasa en lugar de r) y aadiendo comentarios
para explicar el uso de las variables. Una disposicin tal del programa
es una de las primeras recetas del manual del buen programador.
2. Si se detiene a pensar en la seccin de declaracin de variables, inevita
blemente deber realizar una cierta planificacin del programa antes de
comenzar a escribirlo. Por ejemplo, con qu informacin se puede eje
cutar el programa? Qu es lo que deseo exactamente que imprima?
3. La declaracin de variables ayuda a prevenir uno de los errores de pro
gramacin ms sutiles y difciles de encontrar: el cambio accidental de
una letra en el nombre de la variable. Por ejemplo, supongamos que en
un determinado lenguaje, cuyo nombre nos reservamos, escribe la sen
tencia:

LO M O = 430.00

y, durante el programa, introduce equivocadamente:


num = 1 ;

P R EC IO = 0.150 * LO M O - 20.0

La sentencia de asignacin es una de las operaciones ms bsicas. En


en donde accidentalmente ha sustituido la letra O por el nmero 0. El este caso concreto significa dar a la variable num el valor 1. La cuarta
programa crear una nueva variable llamada L0M0, y utilizar cual lnea instrua al ordenador para que reservase espacio a la variable num; en
quier valor que se le ocurra para ella (quiz cero, quiz basura). Por tan esta lnea se le da valor a dicha variable. Posteriormente podemos asignar
to, PRECIO tendr un valor equivocado, y llevar un tiempo respetable a num un valor diferente, si lo deseamos; es por ello que decimos que num
encontrar qu ha sucedido en realidad. Esto no puede suceder en C es una variable. Ntese que la sentencia se completa con un punto y coma.
(a menos que se sea lo suficientemente estpido como para declarar dos
variables con un aspecto tan semejante), ya que el compilador se encar
gar de avisar que la variable L0M0 no est declarada.
4. Su programa en C no funcionar a menos que declare las variables. Si
las dems razones discutidas hasta ahora no le han convencido lo sufi
ciente, esperamos que sta sea bastante elocuente.

OPERADOR DE
ASIGNACION

Figura 2.2
La sentencia de asignacin es una operacin de las ms bsicas

35
printf ("Soy un modesto ") ; de otra forma, cuando se pulsa la tecla [enter], el editor abandona la lnea
printf("ordenador-\n" );
printf("Mi numero es el %d por ser el primero.\n", num); en donde estaba y comienza con una nueva, dejando la lnea anterior sin ter
minar.
El carcter nueva lnea es un ejemplo de lo que se denomina una secuencia
Estas sentencias utilizan una funcin C estndar llamada printf( ); los pa de escape. Se utiliza una secuencia de escape para representar caracteres di
rntesis nos indican, como ya se dijo, que estamos tratando con una funcin. fciles o imposibles de teclear. Como ejemplo se pueden nombrar, adems:
Todo lo que est encerrado entre ellos es informacin que se pasa desde nuestra \t, para tabulados, y \b, para retroceso. En cualquiera de estos casos, la
funcin (main( ) a la funcin printf( ). Dicha informacin se denomina el secuencia de escape comienza con el carcter barra-atrs, \ . Volveremos a
argumento de una funcin, y en el primer caso, dicho argumento es Soy este punto en el captulo 3.
un modesto. Qu hace la funcin printf( ) con este argumento? Obvia Bien, ya hemos explicado por qu nuestras tres sentencias de escritura pro
mente, observa lo que hay entre las dos comillas y lo imprime en la pantalla ducen slo dos lneas: la primera instruccin no lleva carcter nueva lnea
del terminal. dentro de ella.
En la lnea final aparece una nueva rareza: qu ha sucedido con el %d
cuando se imprime la lnea? La salida de esta lnea, recurdese, es:
printf ( )
M i n u m e r o e s e l 1 p o r s e r e l p r im e r o .

Aj! Se ha sustituido el nmero 1 en el smbolo %d al imprimir la lnea;


precsamete 1 era el valor de la variable num. Aparentemente, %d se com
porta como un acomodador que guarda el sitio en el que debe albergarse el
valor de num. Esta lnea es similar a la sentencia BASIC:
P R IN T "M i num ero es el "; num ; " por ser el primero."
printf ("QUIERO SALIR EN PANTALLA! \ n")
La versin C, en realidad, hace algo ms. El smbolo % avisa al progra
Figura 2.3
printf( ) con un argumento ma que se va a imprimir una variable en esta posicin; la letra d, por su par
te, informa que la variable a imprimir es un nmero (digit). La funcin printf( )
permite elegir el formato de las variables entre varias opciones. De hecho,
Esta lnea nos sirve de ejemplo de llamada a una funcin en C. Unica la f de la instruccin printf( ) est ah para recordarnos que es una sentencia
mente necesitamos teclear el nombre de la funcin e incluir los argumentos de impresin con formato.
necesarios entre los parntesis. Cuando el programa alcanza esta sentencia,
se transfiere el control a la funcin llamada printf( ) (en este caso). Cuando
la funcin termina la tarea que tiene asignada, independientemente de la que Estructura de un programa sencillo
sea, transfiere de nuevo el control al programa original.
La siguiente lnea se distingue de sta en los dos caracteres \n incluidos Ahora que hemos visto un ejemplo concreto, estamos ya preparados pa
dentro de las comillas. Observaremos que no forman parte de la salida en ra dar unas pocas reglas generales sobre los programas en C. Un programa
pantalla. Qu ha sucedido? Sencillamente que \n es la instruccin de co se compone de una coleccin de una o ms funciones, de las cuales una de
mienzo de una nueva lnea. Esta combinacin \n representa, en realidad, ellas debe llamarse main( ). Una funcin consta de un encabezamiento y de
un carcter nico llamado carcter nueva lnea (newline). Su significado un cuerpo. El encabezamiento contiene cualquier tipo de sentencias de pre
es: comienza una nueva lnea ajustndose al margen izquierdo. O, lo que procesador, como #include, as como el nombre de la funcin. Se puede re
es lo mismo, este carcter realiza la misma funcin que la tecla [enter] de conocer dicho nombre porque va seguido por unos parntesis, dentro de los
un teclado tpico. Pero se dir usted \n parecen dos caracteres, no cuales puede o no haber parmetros. El cuerpo de la funcin est limitado
uno. Bien, en realidad son dos caracteres, pero representan un nico carc por llaves, { }, y consiste en una serie de sentencias, cada una de las cuales
ter, para el cual no hay tecla adecuada en el teclado. Y por qu no usamos termina en un punto y coma. Nuestro ejemplo tena una sentencia de decla
la tecla [enter]? Sencillamente, porque se interpretara como una orden in racin, que indicaba el nombre y tipo de la variable que bamos a utilizar.
mediata para el editor, no como una instruccin para ser almacenada. Dicho A continuacin apareca una sentencia de asignacin, en la cual se le daba

37
un valor a la variable. Por ltimo, se incluan tres sentencias de escritura, main() { int cuatro; cuatro
compuestas en cada caso por llamadas a la funcin printf( ). 4

printf (
"% d\n",
cuatro) ; }
ENCABEZAMIENTO
#nclude < stdio.h > Instrucciones de preprocesador El compilador averigua dnde termina una sentencia y comienza la siguiente
Main ( ) Nombre de la funcin con argumentos por medio de los puntos y coma introducidos; en cualquier caso, convendr
con nosotros que la lgica del programa aparece mucho ms clara si se sigue
la convencin mencionada. Por supuesto, tampoco haba mucha lgica que
seguir en el ejemplo anterior, pero lo mejor es desarrollar las buenas costum
CUERPO
bres desde el principio.
int num; Sentencia de declaracin
num = 1; Sentencia de asignacin main () / * Pasa 4 docenas a huevos* /
print f("%d es un Sentencia de funcin
{
nmero maravilloso. \ n") int huevos, docenas; USE COMENTARIOS
ELIJA LOS NOMBRES
Figura 2.4 USE ESPACIO
Una funcin tiene encabezamiento y cuerpo UNA SENTENCIA POR LINEA
docenas = 4;
huevos = 12* docenas;
printf ("Hay %d huevos en %d docenas!", huevos, docenas);

Figura 2.5
Claves para hacer legible un programa Haga sus programas legibles
Es una buena prctica de programacin hacer que los programas sean f
cilmente legibles. Con ello se consigue que el programa sea ms fcil de com Subiendo un nuevo peldao
prender, y tambin de corregir o modificar en caso necesario. Recuerde que
tambin se est ayudando a s mismo, ya que en un futuro podr seguir con Nuestro primer ejemplo era realmente sencillo, y el siguiente no va a ser
facilidad el desarrollo del programa. Intentaremos darle a continuacin una mucho ms difcil. Es ste:
serie de consejos tiles en este sentido.
Hasta ahora hemos mencionado dos claves importantes: escoger nombre main() /* P asa 4 docenas a huevos */

de variables con significado y utilizar comentarios. Obsrvese que estas dos {

int h uevo s, doce nas;


tcnicas se complementan recprocamente. Si le damos a una variable el nombre
anchura, no necesitaremos aadir un comentario adicional que explique que docenas = 4;
huevos = 12* docenas;
esta variable representa una anchura. p r in t f ( " H a y % d h u e v o s e n % d d o c e n a s ! " , h u e v o s , d o c e n a s ) ;
Otra tcnica a utilizar es emplear lneas en blanco para separar las seccio }
nes de la funcin. Por ejemplo, en nuestro sencillo programa anterior hemos
introducido una lnea en blanco separando la seccin de declaracin de la Qu hay de nuevo aqu? Primero, hemos declarado dos variables en lu
seccin de accin (asignacin e impresin). La lnea en blanco no era ne gar de una. Todo lo que hemos necesitado es separar las dos variables (hue
cesaria desde el punto de vista del lenguaje, pero es tradicional en C utilizarla. vos y docenas) por una coma en la sentencia de declaracin.
Una cuarta tcnica que seguimos es usar una sentencia por lnea. De nue En segundo lugar, hemos realizado un clculo. Hemos desafiado la tre
vo nos encontramos con una convencin, ya que no es obligatorio en C escri menda capacidad de clculo de nuestro sistema obligndole a multiplicar
bir el programa de esta forma. De hecho, el C tiene lo que se denomina for 4 por 12. En C, como en muchos otros lenguajes, el smbolo * indica una
mato libre. Se pueden poner varias sentencias en la misma lnea o, por el multiplicacin. Por tanto, la sentencia
contrario, espaciar una sentencia en varias lneas. El ejemplo siguiente es,
en consecuencia, correcto: huevos = 12 * docenas;

39
significa: mrese el valor de la variable docenas, multipliquese por doce y
el captulo 9, pero queramos adelantarles lo fcil que es crear e incluir nues
asgnese el resultado de este clculo a la variable huevos. (A juzgar por esta
parrafada, el espaol llano no es tan claro como el C puro y simple; sta es tras propias funciones.
una de las razones por las que desarrollamos lenguajes para ordenador.)
Finalmente, hemos hecho un uso ms elaborado de la sentencia printf( ).
Si ejecuta el programa del ejemplo, la salida ser algo as:
Hasta ahora hemos aprendido
Hay 48 huevos en 4 docenas ! Se da a continuacin un resumen del difcil (aunque no imposible) proce
so de aprendizaje que habr seguido con este captulo, con los hechos ms
Esta vez hemos hecho dos sustituciones. El primer %d que aparece den- relevantes que, esperamos, haya aprendido. Se incluyen pequeos ejemplos
tro de las comillas se sustituye por el valor de la primera variable (huevos) cuando el espacio lo permite.
en la lista que aparece a continuacin de la parte entrecomillada; el segundo
Cmo llamar al fichero que contiene su programa: ojo.c, o negro.c, o
%d, por su parte, ha sido sustituido por el valor de la segunda variable (do resumen.c, etc.
cenas) de la lista. Obsrvese que la lista de variables a imprimir se coloca en
Qu nombre se debe utilizar en programas de una sola funcin: main( )
la parte final de la sentencia. La estructura de un programa sencillo: encabezamiento, cuerpo, llaves,
Este programa no tiene precisamente amplitud de miras, pero podra for-
sentencias
mar el ncleo de un programa para convertir docenas en huevos. Todo lo
Cmo se declara una variable entera: int nombre______de__la__variable;
que necesitamos es poder asignar de alguna forma nuevos valores a nuestras Cmo asignar valor a una variable: nombre____ de__la__variable = 1024;
variables; aprenderemos a hacerlo ms adelante. Cmo imprimir una frase: printf (Esto no es serio);
Cmo imprimir el valor de una variable: printf (%d, nombre_______ de__
la__variable);
Y mientras estamos en ello... El carcter nueva lnea: \ n
Cmo incluir comentarios en un programa: /* anlisis de dividendos */
He aqu un nuevo ejemplo. Hasta ahora nuestros programas han utiliza-
do la funcin estndar printf( ). Vamos a ver ahora cmo se puede incluir
y utilizar una funcin de nuestra propia cosecha. Cuestiones y respuestas
main () A continuacin se proponen algunas cuestiones para ayudarle a compro
{
printf ( "Llamare a la funcin mayordomo. \n" ) ;
bar si ha comprendido el contenido de este captulo.
mayordomo () ;
printf("Si. Traigame un cafe y el libro de C.\n"); Cuestiones
}
1. Iznogud Bagdad Milyunanoches ha preparado el siguiente programa, y se lo pre
mayordomo() senta a usted para que se lo revise. A ver si puede echarle una mano.
{
printf("Llam el seor? \n");
} include studio.h
main{ } /* Escribe el numero de dias de una semana /*
(
La salida es algo as: int d
d := 9 ;
Llamare a la funcin mayordomo. print (Hay d dias en una semana. );
Llamo el seor?
Si. Trigame un cafe y el libro de C.
2. Indicar cul sera la salida de cada una de las siguientes sentencias, suponiendo
que forman parte de un programa completo.
La funcin mayordomo( ) se define de la misma forma que main( ), con
su cuerpo encerrado entre llaves. La funcin se llama simplemente por su nom- a. printf("Yo tenia una ovejita Lucera.");
bre, incluyendo los parntesis. No volveremos a hablar de este tema hasta printf ("Que de campanitas yo le he hecho un collar. \n") ;

41
b. p r i n t f ( " P a r a t e , oh Sol\nYo te saludo!");

C. p r in t f ( ' C u a n g r it a n \ n e s o s / m i a ld i t o s \ n " ) ;

d . in t num;

num = 2;
printf("%d + %d = %d", num, num, num + num);

Respuestas
1. Lnea 1: comience la lnea con un #; el nombre del fichero es stdio.h; adems, este nombre
debe ir entre smbolos < y > .
Lnea 2: utilice ( ), no { }; el final del comentario es */, no /*.
Lnea 3: utilice {, no (.
Lnea 4: la sentencia se completa con un punto y coma.
Lnea 5: el Sr. I.B.M. ha conseguido hacer una lnea correcta, la lnea en blanco!
Lnea 6: utilice = y no : = en sentencias de asignacin (aparentemente, el Sr. I.B.M. sabe
un poco de PASCAL) la semana tiene 7 das, no 9
Lnea 7: debera ser printf (Hay %d das en una semana. \ n , d);
Lnea 8: no existe, pero debera haberla, con una llave de cierre, }.
2. a. Yo tena una ovejita Lucera.Que de campantas yo le he hecho un collar.
(Obsrvese que no hay espacio tras el punto. Si hubisemos deseado un espacio habra
mos de utilizar Que en lugar de Que).
b. Prate, oh sol!
Yo te saludo!
(Obsrvese que el cursor se ha dejado al final de la segunda lnea.)
c. Cun gritan
esos/nmalditos!
(Obsrvese que la barra (/) no tiene el mismo efecto que la barra-atrs (\).)
d. 2 + 2 = 4
(Obsrvese que cada %d se reemplaza por el correspondiente valor de la variable de la
lista. Ntese tambin que el signo + significa adicin, y que el clculo se puede realizar
dentro de una sentencia printf( ).)

Ejercicios
Leer un libro de C no es suficiente. Debe intentar escribir uno o dos programas
sencillos por s mismo y comprobar si se ejecutan de forma correcta, al igual que
los ejemplos del captulo. Presentamos aqu algunas sugerencias, pero quiz prefiera
utilizar sus propias ideas (nunca se sabe).
1. Escriba un programa que imprima su nombre.
2. Escriba un programa que escriba su nombre y direccin, utilizando tres o ms
lneas.
3. Escriba un programa que convierta su edad de aos a das. Por el momento, no
se preocupe de fracciones de aos y de aos bisiestos.

42
3
Los datos en C
En este captulo encontrar:

Datos variables y constantes


Tipos de datos
Enteros
Punto flotante
Tipos de datos en C
Tipos int, short y long
Declaracin de tipos enteros
Constantes enteras
Inicializacin de variables enteras
Utilizacin
Tipo unsigned
Tipo char
Declaracin de variables de caracteres
Constantes de caracteres
Un programa
Tipos float y double
Declaracin de variables de punto flotante
Constantes de punto flotante
Otros tipos
Tamaos de los tipos
Utilizacin de los distintos tipos de datos
Hasta ahora hemos aprendido
Cuestiones y respuestas
Los datos en C mentarios. (Como referencia, hemos incluido el nombre del programa como
comentario. Observaremos esta costumbre en futuros programas.)
/* eldorado */
/* un programa para calcular su peso en oro */
CONCEPTOS main()
{
float peso, valor; /* 2 variables en punto flotante */
Programas interactivos char pita; /* una variable caracter */
pita = ' \007';/* asigna un caracter especial a pita */
Tipos bsicos de datos printf("Vale ud. su peso en oro?\n") ;
Variables y constantes printf("Introduzca su peso en kg. y ya veremos.\n");
Declaracin de los diferentes tipos scanf("%f", &peso); /* toma un dato del usuario */
Palabras, bytes y bits valor= 400.0*peso*32.1512;
/* supone que el oro se cotiza a 400$ la onza */
/* 32.1512 pasa kg. a onzas troy * /
PALABRAS CLAVE printf ( "%cSu peso en oro equivale a $%2.2f%c. \n",
pita, valor, pita);
printf("Seguro que ud. vale mucho mas! Si el oro baja, ");
int,short, long, unsigned, char, float, double printf("coma mas\npara mantener su valor.\n");
}
OPERADORES
Cuando introduzca este programa, probablemente le interesar cambiar
sizeof el valor 400.00 al precio actual del oro en dlares por onza. Sin embargo,
sugerimos que no juguetee con la constante 32.1512, que representa el nme
ro de onzas que hay en un kilogramo (nos referimos a onzas troy, utilizadas
para metales preciosos y a kilogramos del sistema mtrico decimal, utiliza
dos para personas preciosas y de las otras). Observe que ha introducido
su peso, teclendolo, al ordenador y pulsando a continuacin la tecla en-
ter o return. Al pulsar esta tecla, el ordenador entiende que se ha termi
nado de teclear la respuesta. Cuando ejecute el programa, la salida tendr
un aspecto como ste:
Los programas funcionan con datos. La misin de un ordenador, en prin- Vale ud. su peso en oro?
:ipio, es alimentarse de nmeros, letras y palabras, y a continuacin ma- Introduzca su peso en kg. y ya veremos.
nipular estos datos. En los dos siguientes captulos nos concentraremos en 80
Su peso en oro equivale a $1028838.40.
los conceptos implicados en los datos y en sus propiedades. A continuacin, Seguro que ud. vale mucho mas! Si el oro baja, coma mas
nos meteremos con algunos datos y veremos qu podemos hacer con ellos, para mantener su valor.
hablar de datos es muy poco divertido; por tanto, tambin haremos en
este captulo un poco de manipulacin. Nos ocuparemos, en principio, de El programa tiene tambin aspectos poco aparentes. Tendr que ejecutar
las dos grandes familias de tipos de datos: enteros y de punto flotante. El C el programa por su cuenta para averiguar de qu se trata, aunque quiz el
ofrece unas cuantas variedades de estos tipos; aprenderemos cules son, nombre de una de las variables d una pista.
cmo se declaran, cmo se utilizan y, muy importante, cundo se utilizan. Qu hay de nuevo en este programa?
Tambin se discutirn las diferencias entre constantes y variables.
Empezaremos, una vez ms, observando un programa ejemplo. Como 1. Habr observado probablemente que hemos utilizado dos tipos nue
siempre, aparecern algunas arrugas poco familiares, que iremos planchan- vos en la declaracin de variable. Con anterioridad habamos usado
do para usted a lo largo del captulo. De todas formas, el propsito general slo variables de tipo entero, pero ahora hemos aadido una variable
del programa debe estar claro, de modo que lo mejor que puede hacer es in- de punto flotante y una variable carcter, de manera que podemos ma
tentar compilarlo y ejecutarlo. Para ahorrar tiempo, no introduzca los co nejar una variedad de datos ms amplia.

47
2. Hemos incluido algunas nuevas formas de escribir constantes. Ahora variable, y 32.1512 es una constante. Qu sucede con 400.0? Bien, el precio
tenemos nmeros con puntos decimales, y hemos utilizado una nota del oro no es constante en la vida real, pero nuestro programa lo trata como
cin de aspecto bastante peculiar para representar el carcter llamado constante.
pita. La diferencia entre una variable y una constante es bastante obvia: una
3. En la salida de estas nuevas clases de variables hemos usado los cdi variable puede tener asignado su valor o cambiarlo durante la ejecucin del
gos %f y %c en la funcin printf( ), con el fin de manejar variables programa; una constante, por el contrario, no puede variar. Esta diferencia
de punto flotante y de carcter, respectivamente. Hemos utilizado mo hace que el manejo de variables sea un poco ms complicado para el ordena
dificadores al cdigo para alterar la apariencia de la salida. dor y que consuma ms tiempo su proceso; de todas maneras, nuestra mara
4. Quiz la novedad ms llamativa de este programa es que es interacti villosa mquina puede con ello.
vo. El ordenador le solicita informacin, y a continuacin utiliza el
nmero que usted le suministra. Un programa interactivo es ms inte
resante que los ejemplos no interactivos que hemos usado anteriormente; /* el dorado * /
conviene destacar, adems, que los planteamientos interactivos per
miten realizar programas ms flexibles. Por ejemplo, nuestro progra
ma ejemplo se puede utilizar con cualquier peso razonable (y hasta no
razonable), y no simplemente con 80 kilogramos. No hay necesidad
de reescribir el programa cada vez que deseemos calcular el peso en
oro de una nueva persona: las funciones scanf( ) y printf( ) permiten
estas alteraciones. La funcin scanf( ) lee datos de teclado y los entre
ga al programa. Ya vimos en el captulo 2 que printf( ) lee datos del
programa y los entrega a la pantalla. Si se manejan en equipo, estas
dos funciones permiten establecer una comunicacin de doble va con
el ordenador, lo que hace que la utilizacin de la mquina sea mucho
ms divertida.

En este captulo trataremos de los dos primeros apartados, variables y


constantes de diversos tipos de datos. Los dos ltimos puntos mencionados
tratarn en el siguiente captulo, pero continuaremos utilizando en ste las
funciones scanf( ) y printf( ).

Datos: variables y constantes F igu ra 3.1


Funcionamiento de scanf ( ) y printf ( )
Un ordenador, bajo la direccin de un programa, puede realizar una enor-
me variedad de tareas diferentes. Se pueden sumar nmeros, ordenar nom-
res, controlar un altavoz o pantalla, calcular rbitas de cometas, preparar
na lista de correspondencia, dibujar muecos, tomar decisiones o cualquier Tipos de datos
otra cosa que su imaginacin consiga crear. Para realizar estas tareas, el pro
grama necesita trabajar con datos, que son los nmeros y caracteres que Ms all de la distincin entre variables y constantes interesa la diferen
contienen la informacin a utilizar. Algunos de los datos estn preseleccio- cia entre los distintos tipos de datos. Existen datos numricos; otros son
nados antes de la ejecucin del programa y mantienen sus valores inaltera- letras o, en general, caracteres. El ordenador necesita un sistema para identi
dos durante la misma; dichos datos se denominan constantes. Otros da ficar y utilizar todas estas diferentes clases de datos. En C el sistema consiste
tos pueden variar o pueden recibir nuevas asignaciones de valor durante la en reconocer algunos tipos de datos fundamentales. Si el dato es una cons
ejecucin del programa; en este caso estaremos hablando de variables. (Ya tante, el compilador es capaz, generalmente, de decirnos de qu tipo se trata
hemos utilizado este trmino en el ltimo captulo; considere la ltima frase simplemente por el aspecto que tiene; por el contrario, las variables necesi
corno una presentacin formal.) En nuestro programa ejemplo, peso es una tan un anuncio previo de su tipo en una sentencia de declaracin. Iremos com-

49
pletando los detalles conforme avancemos; por el momento, observemos los La unidad de memoria ms pequea en el ordenador se denomina bit.
tipos de datos fundamentales reconocidos en C estndar. El C utiliza 7 pala- Puede tener nicamente dos valores: 0 1 (tambin se puede decir que el
bras clave para definir estos tipos: bit est conectado o desconectado, o bien, alto o bajo; son va
rias formas de indicar lo mismo). Realmente, no se puede almacenar mu
int cha informacin en un bit, pero el ordenador tiene autntica cantidad de
long ellos; se puede decir que el bit es el ladrillo con el que construimos la me
short moria del ordenador.
unsigned
El byte es una unidad de memoria ms til. En la mayor parte de los
ordenadores un byte se compone de 8 bits. Como cada bit puede tomar el
char- valor 0 1, hay un total de 256 combinaciones (es decir, 2 elevado a la octa
va potencia) de ceros y unos formados con los bits de un byte. Con estas
float
combinaciones, por ejemplo, podemos representar los enteros comprendi
double dos entre 0 y 255 o bien un conjunto de caracteres. Esta representacin se
puede conseguir utilizando un cdigo binario, el cual emplea precisamente
ceros (0) y unos (1) para representar nmeros. Hemos incluido una discu
sin sobre el cdigo binario en el apndice; puede leerla sin compromiso.
La unidad natural de memoria para un ordenador determinado es la pa
Las cuatro primeras palabras clave se utilizan para representar enteros, labra. Para un microordenador de 8 bits, como los Sinclair o los Apple
es decir, nmeros sin parte decimal. Se pueden usar en solitario o formando originales, una palabra representa exactamente un byte. Muchos sistemas
ciertas combinaciones como unsigned short. La siguiente palabra clave, char, ms recientes, tales como el IBM PC y el Apple Lisa, son mquinas de 16
se utiliza para las letras del alfabeto y otros caracteres, como = , $, % y &. bits. Con ello se quiere decir que el tamao de la palabra son 16 bits, equi
finalmente, las dos ltimas palabras clave se usan para representar nmeros valente a 2 bytes. Los ordenadores ms grandes pueden trabajar con pala
con punto decimal. (Como es sabido, la prctica totalidad de los ordenado- bras de 32 bits, 64 bits o incluso ms. Evidentemente, cuanto mayor sea
res utilizan punto en lugar de coma en nmeros con decimales.) Los tipos la palabra, ms informacin podr almacenar. Los ordenadores suelen, en
creados con estas palabras clave se pueden dividir en dos familias, basndose general, encadenar dos o ms palabras, para poder almacenar datos de ma
yor tamao, pero este proceso hace ms lenta la ejecucin.
en la forma de almacenamiento en el ordenador. Las cinco primeras pala Supondremos en nuestros ejemplos que se dispone de un tamao de pa
bras producen tipos enteros, en tanto que las dos ltimas generan tipos labra de 16 bits, a menos que indique lo contrario.
en punto flotante.

Tipos enteros? Tipos de punto flotante? Si encuentra que estos trmi-


nos le resultan demasiado poco familiares, no se preocupe, vamos a dar un
breve repaso a los mismos a continuacin. Si no est familiarizado con tr-
minos como bits, bytes o palabras, probablemente le conviene leer Para el ser humano, la diferencia entre un nmero entero y de punto flo
en primer lugar el recuadro siguiente. Debo aprender todos los detalles? En tante queda establecida por la forma en que se escribe. Para el ordenador,
realidad, no; de igual manera que no es necesario saber los principios de los esta diferencia se refleja en la forma en que se almacena. Veamos a conti
motores de combustin interna para conducir un coche. De todas formas, nuacin cada una de las dos clases, por orden.
un pequeo barniz de conocimientos acerca de lo que sucede en el interior
de un ordenador o de un motor puede ser de gran ayuda en ocasiones. Tam El entero
bin le ayudar a ser un fascinante interlocutor.
Un entero es un nmero exacto. Carece de parte fraccionaria y, en C,
se escribe sin punto decimal. Como ejemplos podemos mencionar 2, -23
y 2456; no son enteros, sin embargo, 3.14 2/3. Los enteros se almacenan
de una manera muy directa como nmeros binarios. Para almacenar el ente
BITS, BYTES Y PALABRAS ro 7, por ejemplo, se escribe 111 en binario. As, si queremos que este nme
ro ocupe una palabra de un byte, simplemente hacemos que los 5 primeros
Los trminos bit, byte y palabra se pueden utilizar para descri bits sean 0 y los 3 ltimos sean 1. Vase la figura 3.2.
bir unidades de datos en el ordenador o unidades de memoria. Aqu nos
ocuparemos de la segunda acepcin.

51
Figura 3.2
Almacenamiento del entero 7 en cdigo binario Figura 3.3
Almacenamiento del nmero PI en punto flotante (versin decimal)

El nmero en punto flotante


Los nmeros de punto flotante corresponden ms o menos a lo que los
matemticos llaman nmeros reales. Se incluyen en ellos los nmeros com ERRORES DE REDONDEO EN PUNTO FLOTANTE
prendidos entre los enteros. Algunos ejemplos: 2.75, 3.16E7, 7.00, y 2e-8.
Obviamente, hay ms de una forma de escribir un nmero en punto flotante. Cjase un nmero. Smesele 1 y rstese del nmero original. Qu re
sultado obtenemos? Por supuesto, 1. Pero un clculo en punto flotante puede
Discutiremos ms adelante la notacin E; en sntesis, un nmero como dar una respuesta bien diferente:
3.16E7 significa que se ha de multiplicar 3.16 por 10 elevado a la sptima
potencia, es decir, un 1 seguido de 7 ceros. El 7 recibe el nombre de expo
nente. /* error en punto flotante */
El punto clave que hay que considerar aqu es que el esquema utilizado main()
{
para almacenar un nmero de punto flotante es diferente del que se usa para float a,b;
enteros. Una representacin en punto flotante implica trocear el nmero en
una parte fraccionaria y una parte de exponente, y almacenar estas partes b = 2. 0e20 + 1. 0;
a = b - 2.0e20;
separadamente. As, el 7.00 dado como ejemplo no se almacenar de la mis
ma forma que el entero 7, aunque ambos tengan el mismo valor. La analoga printf ( " % f \n", a);
decimal sera escribir 7.0 como 0.7E1, siendo 0.7 la parte fraccio }
naria, y 1, la parte exponencial. Por supuesto, el ordenador utilizar n
meros binarios y potencias de dos, en lugar de potencias de 10, para su alma La salida es:
cenamiento interno. Se puede encontrar ms informacin de esta materia en
el apndice G. Por ahora nos concentraremos en las diferencias prcticas, o.oooooo
que son las siguientes:

1. Los enteros son nmeros naturales (incluyendo los negativos), en tan La razn para un resultado tan llamativo es que el ordenador no es capaz
to que los nmeros en punto flotante pueden representar tanto nme de anotar suficientes cifras decimales para realizar la operacin correcta
ros enteros como fraccionarios. mente. El nmero 2.0e20 es un 2 seguido de 20 ceros, y al sumarle 1 estara
2. Los nmeros en punto flotante pueden abarcar un rango de valores mos intentando alterar el dgito 21. Para realizar la operacin correctamen
mucho mayor que el de los enteros. Vase tabla 3.1. te, el programa debera ser capaz de almacenar un nmero de 21 cifras. Sin
3. En algunas operaciones aritmticas, tales como la sustraccin de n embargo, un nmero en punto flotante est compuesto por 6 7 cifras que
meros muy grandes, los nmeros en punto flotante pueden presentar se gradan a mayores o menores valores por medio del exponente; estamos
condenados, por tanto, a estos errores. Por otra parte, si hubisemos utili
grandes prdidas de precisin. zado, por ejemplo, 2.0e4 en lugar de 2.0e20, habramos obtenido la respuesta
4. Las operaciones en punto flotante son, en general, ms lentas que las correcta, ya que aqu se trata de cambiar el quinto dgito, y los nmeros
operaciones entre enteros. Sin embargo, existen actualmente micropro float son lo suficientemente precisos para ello.
cesadores diseados especficamente para manejar operaciones en punto
flotante, que son bastante veloces.

53
Los datos en C Los diseadores del C permitieron la opcin de definir tres tamaos para
enteros. El tipo int se refiere generalmente al tamao de palabra estndar del
Revisaremos ahora en profundidad los caracteres especficos de los tipos ordenador que se est utilizando. Con respecto a los tipos short y long, se
bsicos de datos utilizados en C. Para cada tipo, explicaremos cmo se de- garantiza que short no es mayor que int, y que long no es menor. En algunos
clara una variable, cmo se representa una constante y cul podra ser una sistemas puede suceder que uno de estos dos tipos, o los dos, sean del mismo
aplicacin tpica. Algunos compiladores C no contienen todos estos tipos; tamao que int. Todo depende de la adaptacin realizada en su sistema en
consulte su manual para comprobar de qu tipos dispone en su caso. particular. En la tabla al final de esta seccin se presenta el nmero de bits
y los diferentes tipos de datos utilizados en algunos de los ordenadores ms
Tipos int, short y long comunes, as como el rango numrico que se puede representar en cada caso.
El C presenta una gran variedad de tipos enteros, de forma que se puede
refinar un programa hasta cumplir las especificaciones de una determinada
Declaracin de tipos enteros
mquina o tarea. Si no desea, por el momento, complicarse la vida con estos
detalles, generalmente tendr bastante con utilizar el tipo int y olvidarse del Simplemente teclee el tipo de variable y a continuacin una lista de los
resto de posibilidades. nombres de las variables a utilizar. Por ejemplo:
Los tipos de datos int, short y long son enteros con signo todos ellos.
Dicho de otra forma, los valores permitidos para estos nmeros son nme int erno;
ros enteros positivos, o negativos, o bien cero. Tambin existen en C ente- short presa;
long johns;
ros sin signo, los cuales pueden ser nicamente positivos o cero. Se utiliza int imos, vacas, cabras;
un bit para indicar el signo de un nmero, por lo que el mayor entero con
signo que se puede almacenar en una palabra ser menor que el mayor ente-
ro sin signo. Por ejemplo, una palabra de 16 bits puede almacenar un entero Utilice comas para separar los nombres de las variables, y finalice la lista
sin signo comprendido entre 0 y 65535; esta misma palabra puede albergar con un punto y coma. Las variables de un mismo tipo se pueden aunar en
cualquier entero con signo entre 32768 y + 32767. Obsrvese que el rango una sola sentencia o repartir entre varias. Por ejemplo, la sentencia de decla
total es el mismo en los dos tipos. racin
int erno, imos, vacas, cabras;

tendra el mismo efecto que las dos sentencias int del ejemplo anterior. Tam
bin podra haber empleado cuatro declaraciones int por separado, una para
cada variable.
Se pueden utilizar combinaciones como long int o short int. Su significa
do es idntico al de los tipos long y short.

Constantes enteras
Cuando se escribe un nmero en C sin punto decimal y sin exponente,
queda clasificado como entero. Por tanto, 22 y 273 son constantes ente
ras; sin embargo, 22.0 no lo es, ya que contiene un punto decimal, y 22E3,
tampoco, porque est expresada en notacin exponencial. Recuerde que el
punto en un nmero se utiliza nicamente para separar la parte entera de la
decimal; as pues, 23456 no es lo mismo que 23.456.
Si se desea identificar una constante como de tipo long, se puede hacer
colocando una L o una l al final del nmero. Se aconseja utilizar la L mays
cula, ya que es menos probable que se confunda con la cifra 1. Por ejemplo,
una constante long sera 212L. Obviamente, el nmero 212 no es muy largo
que digamos, pero si aade la L, se asegura que se almacenar en el nmero

55
adecuado de bytes. Este punto es importante cuando deseamos compatibili- Utilizacin
zar nuestro nmero con otras constantes o variables del tipo long. La duda ahora es qu tipo de variable entera con signo debo utilizar?
Es bastante probable que lo dicho hasta ahora sea todo lo que necesita Uno de los propsitos de disponer de tres tipos de diferentes tamaos es po
saber acerca de constantes; comentaremos, no obstante, que el C ofrece dos
der adaptarse a las necesidades de cada uno. Por ejemplo, si el tipo int es
opciones ms. La primera, cuando un entero comienza por la cifra 0 se inter
de una palabra de largo y long es de dos, entonces se podrn manejar nme
preta como un nmero octal. Se llaman octales los nmeros que estn ex-
presados en base 8, es decir, que se escriben como combinaciones de po ros mayores si se declaran como long. Si en su problema concreto no se van
tencias de 8. Por ejemplo, el nmero 020 representa 2 multiplicado por la a utilizar nmeros tan grandes, no use long, ya que el manejo de datos de
dos palabras hace que la ejecucin sea ms lenta. Determinar cundo debe
primera potencia de 8, siendo, por tanto, el equivalente octal de 16. Un sim-
ple 20, sin estar precedido por un 0, representa, sin embargo, a nuestro viejo utilizar int o long depender de su sistema, ya que una variable int en algu
amigo el 20. nos sistemas puede ser de mayor tamao que una long de otros. En cualquier
En segundo lugar, cuando se comienza un entero con 0x o 0X se interpre- caso, como comentbamos antes, usar int la mayor parte de las veces.
ta como un nmero hexadecimal, es decir, un nmero en base 16. Si escribi-
mos 0x20, indicamos 2 multiplicado por la primera potencia de 16, es decir, 32.
Los nmeros octales y hexadecimales son muy populares entre progra-
madores. La razn es que tanto 8 como 16 son potencias de 2, y 10 no lo OVERFLOW EN ENTEROS
es; por ello, estos sistemas de numeracin resultan ms familiares para un
ordenador. Por ejemplo, el nmero 65536, que surge a menudo en mquinas Qu sucede si un entero intenta ser mayor que el mximo nmero asig
de 16 bits, es simplemente 10000 en hexadecimal. Si desea estudiar ms pro- nado a su tipo? Para comprobarlo, vamos a asignar a un entero su valor
fundamente este tema, le remitimos al apndice G. mximo y sumarle 1 sucesivamente, a fin de verificar lo que sucede.

/* tehaspasado */
main()
Inicializacin de variables enteras {
int i = 32767;
Una funcin usual de las constantes es la inicializacin de variables,
es decir, otorgar a una variable un valor con el que comenzar en un momen- printf("%d %d %d\n", i, i + 1, i+2) ;
}
to determinado de la ejecucin. Ejemplos de inicializacin son:

erno = 1024; cuyo resultado en nuestro sistema es:


presa = -3;
johns = 12345678; 32767 -32768 -32767

Si lo desea, puede inicializar la variable en una sentencia de declaracin. El entero i se est comportando como el velocmetro de un coche. Cuando
por ejemplo: alcanza su mximo valor vuelve a empezar desde el principio. La diferencia
principal es que en un velocmetro el principio es 0, en tanto que nuestro
tipo int comienza en 32768.
int irnos = 23; Observe que a usted no se le avisa de que i ha sobrepasado (overflow)
int vacas = 32, cabras = 14; su valor mximo. As pues, deber incluir sus propias precauciones en la
short perros, gatos = 92;
programacin.
Este comportamiento descrito aqu no forma parte de las reglas del C,
En la ltima lnea se ha inicializado nicamente gatos. En principio, po- pero constituye su implementacin ms tpica.
dra parecer que tambin la variable perros ha tomado el valor 92. Por ello,
es aconsejable no poner en la misma sentencia de declaracin variables ini-
inializadas y no inicializadas.
Tipo unsigned
En general, este tipo es un modificador de alguno de los tres tipos ante
riores. Podemos utilizar como tipos, por ejemplo, unsigned int o unsigned

57
long. Si usa tan slo la palabra unsigned, se refiere implcitamente a unsig- y no
ned int. El algunos sistemas no se acepta unsigned long, y se da tambin el
caso de versiones de microprocesador en las que unsigned es un tipo separa beber = T; /* INCORRECTO */

do, con un nico tamao.


Las constantes enteras sin signo se escriben de la misma forma que las Si se omiten los apstrofos, el compilador pensar que estamos utilizan
constantes con signo, con la excepcin obvia de que no se permite el signo do una variable llamada T. Tambin pensar que nos hemos olvidado de de
menos. clarar dicha variable.
Asimismo, las variables enteras sin signo se declaran e inicializan de la En C estndar, una constante o variable char puede representar tan slo
misma forma que las dems. Por ejemplo: un nico carcter. Por tanto, la siguiente secuencia no est permitida, ya que
intenta asignar dos caracteres a la variable borrico.
unsigned int alumnos;
unsigned jugadores; char borrico;
unsigned short vicenta = 6; borrico = t u ; /* NO ACEPTABLE */

Se puede utilizar este tipo para asegurar que el valor de alguna variable Si observamos una tabla de caracteres ASCII, veremos que algunos de
nunca ser negativo. Tambin, cuando se trabaja nicamente con nmeros los caracteres no son imprimibles. Por ejemplo, el carcter nmero 7 tie
positivos, se puede aprovechar la ventaja de que el rango (en positivos) que ne por funcin hacer sonar el altavoz del ordenador. Pero, cmo podemos
se alcanza con unsigned es mayor que el tipo equivalente con signo. Una uti escribir un carcter que no se puede teclear? El C permite dos formas de ha
lizacin tpica podra ser el acceso a direcciones de memoria o a un contador. cerlo.
La primera de las formas es utilizar el propio cdigo ASCII. Simplemen
te, se usa el nmero de cdigo ASCII precedido por un carcter barra-atrs.
Tipo char Ya hemos empleado esta tcnica en nuestro programa el dorado en la lnea:
Este tipo define un entero sin signo en el rango 0 a 255; generalmente,
pita = \007 ;
dicho entero se almacena en un nico byte. El ordenador utiliza un cdigo
que transforma nmeros en caracteres, y viceversa. La mayora de ellos usan
el cdigo ASCII, descrito en el apndice; muchos ordenadores IBM (aunque Para seguir este camino hay que tener en cuenta dos puntos: el primero
no el IBM PC) utilizan un cdigo diferente llamado EBCDIC. Por nuestra es que la secuencia de cdigo ASCII est tambin encerrada entre apstro
parte, utilizaremos en todo el libro el cdigo ASCII, con el fin de poder dar fos, como si se tratase de un carcter ordinario. El segundo es que el nmero
ejemplos definidos. de cdigo debe escribirse en octal. Comentaremos, adems, que se pueden
omitir los ceros a la izquierda del cdigo. Podramos haber utilizado \07'
Declaracin de variables carcter o incluso \7 para representar este carcter. Sin embargo, no se pueden omitir
los ceros a la derecha; la representacin ' \ 020' puede escribirse como '\ 20'
Utilizamos la palabra clave char para declarar una variable carcter. Las pero no como '\ 02'
reglas concernientes a la declaracin de ms de una variable y a la inicializa- Cuando utilice cdigo ASCII observe la diferencia entre nmeros y ca
cin de las mismas son equivalentes a las de los dems tipos bsicos. Por con racteres que representan nmeros. Por ejemplo, el carcter 4 se represen
siguiente, todos los ejemplos que siguen son correctos: ta con un valor de cdigo ASCII 52. Con l nos estamos refiriendo al smbo
lo 4, y no al valor numrico 4.
char respuesta; El segundo mtodo que utiliza el C para representar caracteres raros es
char latan, ismatico; usar secuencias especiales de smbolos. Se denominan secuencias de esca
char treuse = 'S' ;
pe, y son las siguientes:
\n nueva linea
Constantes carcter \t tabulado
\b retroceso
En C, los caracteres se definen con apstrofos. As, si deseamos asignar \r retorno de carro
un valor a la variable char beber utilizaremos \f salto de pagina
\\ barra-atras(\)
\' apostrofe (' )
beber = ' T' ; /* CORRECTO */ \" com illas ( " )

59
Llegado este punto, probablemente tendr en mente dos preguntas: 1. Por
qu no encerramos las secuencias de escape entre apstrofos en el ltimo ejem
plo? 2. Cundo se debe utilizar el cdigo ASCII y cundo las secuencias
de escape? (Esperamos que sus dos preguntas sean stas, porque son las ni
cas para las que hemos preparado respuesta.)

1. Cuando un carcter, sea secuencia de escape o no, forma parte de una


cadena de caracteres encerrada entre comillas no se debe encerrar, a
su vez, entre apstrofos. Obsrvese que los dems caracteres del ejem
plo (E, l, j, e, f, e, etc.) tampoco estn encerrados por apstrofos. Una
cadena de caracteres encerrada entre comillas se denomina tira de ca
racteres. Hablaremos de este tema en el siguiente captulo.
2. Cuando pueda elegir entre el uso de una de las secuencias de escape
especiales, por ejemplo \ f', y su cdigo ASCII equivalente, por ejem
plo \016\ utilice la primera, \ f ' En primer lugar, la representa
cin es ms mnemotcnica. Adems, es ms transportable. Incluso si
su sistema no utiliza cdigo ASCII, la secuencia \f funcionar co
rrectamente.
Figura 3.4 Un programa
Constantes de la familia int
Presentamos aqu un corto programa que permite averiguar el nmero
de cdigo de un carcter en su sistema incluso si ste no trabaja en cdigo
Tambin stos deben encerrarse entre apstrofos para poder ser asgna ASCII:
dos a una variable carcter. Por ejemplo, podemos construir la sentencia
main() /* halla el numero de codigo de un carcter */
slin = \n' ; {
char ch;
y a continuacin imprimir la variable slin para avanzar la impresora o panta- printf("Introduzca un caracter. \n");
lla una lnea.
Las cinco primeras secuencias de escape son caracteres de control comu- scanf("%c", &ch) ; /* el usuario introduce un caracter */
printf("El codigo de %c es %d. \n", ch, ch) ;
nes en una impresora. El carcter nueva lnea (new line) enva el cursor a la }
lnea siguiente. El tabulado ( tab) mueve el cursor un determinado nmero
de espacios, en general 5 u 8. El retroceso (backspace) retrocede un espacio.
El carcter retorno de carro (carriage return) enva el cursor al comienzo de Al ejecutar el programa recuerde utilizar la tecla [enter] o [return] des
la lnea. Salto de pgina avanza el papel de la impresora hasta el comienzo pus de teclear el carcter. As, scanf( ) captura el carcter que ha tecleado,
de la siguiente pgina. y el smbolo & ( ampersand) se preocupa de que dicho carcter sea asignado
Las tres ltimas secuencias permiten utilizar como constantes carcter los a la variable carcter ch. A continuacin, printf( ) imprime el valor de ch
smbolos \, y (estos smbolos se utilizan para definir constantes carcter dos veces, la primera como carcter (indicada por el cdigo %c) y la segunda
como parte del comando printf( ); por ello, la situacin podra ser confusa como entero decimal (indicada por el cdigo 7od).
si se utilizaran literalmente). Por ejemplo, si desea imprimir la lnea:
Tipos float y double
El jefe dice que "una \ es una barra-atras".
Los distintos tipos int se pueden utilizar para la mayor parte de proyectos
utilice: de desarrollo en software. Sin embargo, los programas que implican un ma
yor nmero de clculos matemticos a menudo utilizan nmeros de punto
printf("El jefe dice que \"una \\ es una barra-atras\".\n"); flotante. En C se llama a estos nmeros de tipo float; corresponden a los

61
tipos real de FORTRAN y PASCAL. Con este tipo de nmeros, como ya sistemas usan algunos de los bits para aceptar mayores exponentes; as se in
observ anteriormente, se pueden representar rangos mucho mayores, inclu crementa el rango de nmeros que pueden ser aceptados.
yendo fracciones decimales. Los nmeros en punto flotante son anlogos a Otra forma de especificar el tipo double es usar la combinacin de pala
la notacin cientfica, sistema inventado por los cientficos para expresar n bras clave long float.
meros muy grandes y muy pequeos y para asustar a los advenedizos. Repa
smoslos someramente.
En notacin cientfica, los nmeros se presentan como nmeros decimales Declaracin de variables en punto flotante
multiplicados por potencias de 10. He aqu algunos ejemplos. Las variables en punto flotante se declaran e inicializan de la misma for
ma que sus primos los enteros. Por ejemplo:
NUMERO NOTACION CIENTIFICA NOTACION EXPONENCIAL
1 000 000 000 = 1.0 x 109 = 1.0e9 float noe, jonas;
double blanca;
123 000 = 1.23 x 105 = 1.23e5 float planck = 6.63e-34;
322.56 = 3.2256 x 102 = 3.2256e2
0.000056 = 5.6 x 10-5 = 5.6e-5

En la primera columna hemos representado los nmeros como se indican Constantes en punto flotante
normalmente; en la segunda, en notacin cientfica, y en la tercera, en la for Se nos presentan mltiples elecciones para escribir una constante en pun
ma en que la notacin cientfica se escribe por y para ordenadores, con una to flotante. La forma ms general de este tipo de constantes es una serie de
letra e seguida por la potencia de 10. cifras con signo incluyendo un punto decimal, a continuacin una e o E se
Generalmente, se utilizan 32 bits para almacenar un nmero en punto flo guida de un exponente con signo que indica la potencia de 10 a utilizar. Vea
tante. De ellos, se usan 8 bits para expresar el valor del exponente y su signo, mos dos ejemplos:
y 24 bits para representar la parte no exponencial. De ah se deduce una im
portante conclusin: este sistema permite una precisin de 6 7 cifras decimales -1.56E+12 2.87e3
y un rango de . (10-37 hasta 10+38). Estos nmeros, en consecuencia, re
sultan muy tiles si deseamos representar la masa del Sol (2.0e30 kilogramos) Se pueden omitir signos positivos. Tambin se puede omitir el punto de
o la carga de un protn (1,6e-19 culombios). (Nos encanta usar estos nmeros.) cimal o la parte exponencial, pero no ambos a la vez. Se puede omitir la parte
Muchos sistemas aceptan tambin el tipo double (doble precisin), que fraccionaria o la parte entera, pero no ambos (la verdad es que no quedara
utiliza el doble de bits, normalmente 64. Algunos sistemas incorporan los 32 mucho!). Algunos ejemplos de constantes vlidas en punto flotante podran ser:
bits adicionales a la parte no exponencial; de este modo se incrementara el
nmero de cifras significativas y se reduciran los errores por redondeo. Otros 3.14159 .2 4e16 .8E-5 100.
No utilice espacios dentro de una constante en punto flotante
ERROR 1.56 E + 1 2
Las constantes en punto flotante se suponen de doble precisin. Por ejem
plo, imaginemos que algo es una variable float, y que escribimos la sentencia

algo = 4.0 * 2.0

Las dos constantes, 4.0 y 2.0, han sido almacenadas como double, utili
zando (normalmente) 64 bits cada una. El producto (8, por si tena dudas)
se calcula usando aritmtica de doble precisin y, una vez obtenida la res
puesta, se recorta hasta el tamao normal de float. Este sistema asegura la
mxima precisin en los clculos.
Figura 3.5
Algunos nmeros en punto flotante

63
ERRORES POR EXCESO Y DEFECTO EN PUNTO FLOTANTE RESUMEN: COMO DECLARAR UNA VARIABLE SIMPLE
(OVERFLOW y UNDERFLOW)
1. Escoja el tipo que necesite.
Qu sucede si intentamos hacer que una variable float exceda su ran 2. Escoja el nombre para la variable.
go? Por ejemplo, suponga que multiplica 10e38 por 100 (overflow) o divide 3. Utilice el siguiente formato en las sentencias de declaracin:
10e-37 por 1000 (underflow). El resultado depende del sistema; en nuestro especificador de tipo, especificador de variable;
sistema, cualquier nmero que excede los lmites queda sustituido por el ma El especificador de tipo est formado por una o ms palabras clave defi
yor valor legal posible, mientras que cualquier valor que sobrepasa el niendo el tipo. Por ejemplo:
lmite por defecto queda sustituido por 0. En otros sistemas pueden apare int eres;
cer mensajes de aviso o paradas, o bien le pueden ofrecer la eleccin entre unsigned short ija;
varias respuestas. Intente averiguar cules son las reglas especficas que se
dan en su sistema. Si no consigue encontrar la informacin, no dude en ejer 4. Se puede declarar ms de una variable del mismo tipo en la misma senten
cer un poco la vieja tcnica de ensayo y error. cia separando los nombres de las variables por comas:
char ch, init, os;
5. Se puede inicializar una variable en la sentencia de declaracin:
float masa = 6.0E24;
RESUMEN: TIPOS BASICOS DE DATOS

Palabras clave:
Los tipos bsicos de datos se inicializan utilizando las 7 palabras clave siguien
tes: int, long, short, unsigned, char, float, double.
Enteros con signo:
Pueden tener valores positivos o negativos,
int: es el tipo entero bsico de un sistema dado.
long o long int: permite almacenar un entero al menos del tamao de int, y
posiblemente mayor.
short o short int: queda garantizado que el mayor entero short no es mayor
que el mayor int, y puede que sea menor. Normalmente, el tipo long ser ma
yor que short, y el tipo int ser el mismo que uno de ellos. Por ejemplo, en
el IBM PC Lattice C se dispone de 16 bits para short y para int, y 32 bits para
long. Todos estos datos dependen del sistema.
Enteros sin signo:
Pueden tomar valores positivos o cero nicamente. Se extiende el rango mxi
mo de valores positivos alcanzables. Utilice la palabra clave insigned antes del
tipo deseado: unsigned int, unsigned long, unsigned short.
Un tipo unsigned sin nada detrs se supone unsigned int.
Caracteres:
Son los smbolos tipogrficos tales como A, &, +. Generalmente se almace
nan en un byte de memoria,
char: es la palabra clave para definir este tipo.
Punto flotante:
Pueden tomar valores positivos o negativos,
float: es el tamao bsico de punto flotante para su sistema,
double o long float: es una unidad (posiblemente) mayor que permite almace
nar nmeros en punto flotante. Probablemente mantendr un mayor nmero
de cifras significativas, y quiz mayores exponentes.

65
Hemos averiguado el tamao de cuatro de los tipos, pero se puede modi
Otros tipos ficar fcilmente este programa para buscar el tamao de cualquier otro tipo
Con esto se acaba nuestra lista de tipos fundamentales de datos. A algu que le interese.
nos de ustedes les parecern un montn; otros, por el contrario, estarn pen
sando que no son suficientes. Qu hay de un tipo booleano o un tipo string?
Bien, el C carece de ellos, pero aun as se las arregla bastante bien con mani Utilizacin de tipos de datos
pulaciones lgicas y con tiras de caracteres (string). Precisamente en el prxi
mo captulo hablaremos de estas ltimas. Cuando intente desarrollar un programa, tome nota de las variables que
Lo que s tiene el C son otros tipos derivados de los tipos bsicos. Entre
necesita y del tipo que deben ser. La mayor parte de las veces utilizar int
estos tipos se incluyen arrays, punteros, estructuras y uniones. Aunque to
o posiblemente float para los nmeros y char para los caracteres. Declrelos
dos ellos sern tratados en captulos posteriores, comentaremos que ya he
al comienzo de la funcin que los utilice. Escoja para la variable un nombre
mos estado trabajando con punteros en los ejemplos de este captulo. (Se usan
punteros en la funcin scanf( ), indicndose en ese caso con el prefijo &.) que recuerde su significado. Cudese de ajustar los tipos al inicializar las va
riables; utilice siempre el mismo tipo para la variable y su constante corres
Tamaos de los tipos pondiente.
Damos a continuacin una tabla de los tamaos de tipos en algunos en
int manzanas = 3; /* CORRECTO */
tornos comunes de C. int naranjas = 3.0; /* INCORRECTO */
Tabla 3-1. Tamaos reales de tipos en algunos sistemas
El C es ms permisivo en este tipo de errores que otros lenguajes, como
el PASCAL; de todas formas es conveniente no desarrollar malos hbitos
desde el principio.

Hasta ahora hemos aprendido


Hemos dado un buen salto con este captulo. En el resumen nos limitare
mos a los aspectos ms prcticos de lo que hemos estudiado. Como en el ca
ptulo anterior, incluimos pequeos ejemplos cuando lo permite el espacio.
He aqu algunas de las cosas que ya debe conocer:

A cul de ellos se parece su sistema? Ejecute el siguiente programa para Cules son los tipos bsicos de datos en C: int, short, long, unsigned, char,
averiguarlo. float, double
main() Cmo se declara una variable de cualquier tipo: int contador; float dine
{ ro; etc.
printf("El tipo int ocupa %d bytes. \n", sizeof (int) ) ; Cmo escribir una constante int: 256, 023, 0XF5, etc.
printf("El tipo char ocupa %d bytes.\n", sizeof (char) ) ;
printf("El tipo long ocupa %d bytes.\n", sizeof (long) ) ; Cmo escribir una constante char: r, U', \007', ?'. etc.
printf("El tipo double ocupa %d bytes. \n", Cmo escribir una constante float: 14.92, 1.67e27, etc.
sizeof(double)); Qu son palabras, bytes y bits.
}
Cundo utilizar los diferentes tipos de datos.
El lenguaje C incluye un operador llamado sizeof que sirve para calcular
el tamao de las cosas en bytes. La salida de este programa en nuestro siste
ma es
Cuestiones y respuestas
El tipo int ocupa 2 bytes.
El tipo char ocupa 1 bytes.
El tipo long ocupa 4 bytes. Pensar un poco estas cuestiones le ayudar a digerir el material de este
El tipo double ocupa 8 bytes. captulo.

67
Cuestiones
1. Qu tipo de dato utilizara para expresar las siguientes cantidades?
a. La poblacin de Ro Frito
b. El peso medio de una pintura de Rembrandt
c. El tipo de letra ms comn en este captulo
d. El nmero de veces que aparece esta letra
2. Identifique el tipo y significado de las siguientes constantes:
a. \b
b. 1066
c. 99. 44
d. OXAA
e. 2. 0e30
3. La seorita Violina Armonio Xilofn ha pergeado un programa repleto de erro
res. Aydela a encontrarlos.
#include <stdio.h>
main
(
float g; h;
float tasa, precio;

g = e21;
tasa = precio*g;
)

Respuestas
1. a. int, posiblemente short; la poblacin es un nmero entero
b. float; es raro que el promedio sea un entero exacto
c. char
d. int; posiblemente, unsigned.
2. a. char, el carcter retroceso (backspace)
b. int histrico
c. float; medida de la pureza de un jabn, por ejemplo.
d. int hexadecimal; su valor decimal es 170
e. float; masa del Sol en kg.
3. Lnea 1: correcta
Lnea 2: debe haber un par de parntesis a continuacin de main, main( )
Lnea 3: utilice {, no (
Lnea 4: debe haber una coma entre g y h en lugar de un punto y coma
Lnea 5: correcta
Lnea 6: correcta (la lnea en blanco)
Lnea 7: debe haber, al menos, una cifra delante de la e. Tanto le21 como 1.0e21 podran
valer.
Lnea 8: ok
Lnea 9: utilice }, no )
Faltan las lneas: en primer lugar, no se asigna ningn valor a precio. La variable h no
se utiliza. Adems, el programa no informa del resultado de su clculo. Ninguno de estos errores
detendr la ejecucin del programa (aunque probablemente le aparecer un aviso por la va-
riable sin utilizar), pero eliminan la poca utilidad que tena.
4
Tiras
de caracteres,
#define, printf( )
y scan( )

En este captulo encontrar:

Introduccin a las tiras de caracteres


Longitud de tirastrlen( )
Constantes: el preprocesador C
El C como maestro del disfraz: creacin de sosias
Usos y utilidades de printf( ) y scanf( )
Utilizacin de printf( )
Modificadores de especificaciones de conversin en printf( )
Ejemplos
Utilizacin de printf( ) para efectuar conversiones
Uso de scanf( )
Claves de utilizacin
Hasta ahora hemos aprendido
Cuestiones y respuestas

71
sitio = sizeof nombre;

Tiras de caracteres, letras = strlen(nombre);


volumen = peso/DENSIDAD;
printf("Bien, %s, tu volumen es %2.2f litros. \n",
nombre, volumen);

#define, printf( ) printf("Ademas, tu nombre tiene %d letras, \n",


letras);
printf("y disponemos de %d bytes para guardarlo.\n",
sitio) ;

y scanf( )
}

La ejecucin de secretos produce resultados como el siguiente:

Hola!, como te llamas?


CONCEPTOS Angelica
Angelica, cual es tu peso en kg?
62. 5
Tiras de caracteres Bien, Angelica, tu volumen es 60.63 litros.
Preprocesador C Ademas, tu nombre tiene 8 letras,
y disponemos de 40 bytes para guardarlo.
Salida con un formato

Seguidamente comentamos las principales novedades de este programa.

1. Hemos utilizado un array para guardar una tira de caracteres;


en este caso, el nombre de una persona.
2. Hemos usado la especificacin de conversin %s para manejar la
entrada y salida de la tira.
3. Hemos utilizado el preprocesador C para definir la constante simbli
En este captulo continuaremos el baile de datos profundizando en temas ca DENSIDAD.
que van ms all de los tipos bsicos; trataremos concretamente las tiras de 4. Hemos empleado la funcin C strlen( ) para averiguar la longitud de
caracteres. Antes de ello repasaremos una importante ayuda del C, el pre- la tira.
cesador, y aprenderemos el modo de definir y utilizar constantes simbli-
cas. A continuacin, volveremos de nuevo a la cuestin ya mencionada de Este modo de funcionamiento del C puede parecer un poco complicado
comunicacin de datos entre usted y el programa; esta vez nos detendremos si se compara con los modos de entrada/salida de BASIC, por ejemplo. Sin
en las caractersticas de printf( ) y scanf( ). Seguro que a estas alturas embargo, con esta complejidad se pretende tener un mayor control sobre la
ya esperando el programa de comienzo de leccin, de manera que no va- E/S y ganar en eficiencia de ejecucin; por otra parte, tampoco es tan difcil
mos a decepcionarle. una vez que se ha repasado un par de veces.
Ahondemos ahora un poco ms en estas nuevas ideas.
/* secretos */
#define DENSIDAD 0.97 /* d e n s i d a d d e l h o m b r e e n kg por litro */
main() /* programa i n f o r m a t i v o t o t a l m e n t e i n t i l */
{
float peso, volumen; Introduccin a las tiras de caracteres
int sitio, letras;
Una tira de caracteres (string) consiste simplemente en una serie de uno
char nombre[40] ; /* o b i e n p r u e b e " s t a t i c c h a r nombre[40] */ o ms caracteres. Un ejemplo podra ser:
printf("Hola!, como te llamas?\n");
scanf("%s", nombre) ;
printf("%s, cual es tu peso en kg?\n", n o m b r e ) ; "Casi me tira el viento que soplaba"
scanf("%f", &peso) ;
73
Por cierto, habr observado que uno de los comentarios del programa
Las comillas no forman parte de la tira. Sirven para especificar el comien- le indica que poda utilizar alternativamente una declaracin ms elaborada:
zo y final de sta, al igual que los apstrofos marcaban los caracteres indivi-
duales. static char nombre[40];
En C no existe un tipo especial de variable para tiras. En su lugar se al
macenan como un array de tipo char. Ello permite imaginar los caracteres Debido a las peculiares caractersticas de la funcin scanf( ) de nuestro
de la tira almacenados en clulas de memoria adyacentes, a razn de un ca sistema, tenemos que usar esta segunda forma; sin embargo, lo ms proba
racter por clula. ble es que usted no tenga que hacerlo. Si observa que la primera forma le
da problemas, utilice la segunda; de hecho, la segunda forma funciona en
todos los sistemas, pero no hablaremos de static hasta que discutamos los
modos de almacenamiento en el captulo 10.
Esto est empezando a ponerse complicado; tenemos que crear un array,
empaquetar en l los caracteres de la tira uno a uno y recordar aadir un \0
Figura 4.1
Una tira en un array al final. Por fortuna, el ordenador se preocupa por s mismo de la mayor
parte de estos detalles.
Ejecute el siguiente programa, y ver qu fcilmente se funciona en realidad
Obsrvese en la figura el carcter \0 que ocupa la ltima posicin del
array; ste se llama carcter nulo, y es utilizado por el C para marcar el /* elogio1 */
final de la tira de caracteres. El carcter nulo no es la cifra cero, sino un ca- #define ELOGIO "!Por Jupiter, que gran nombre!"
racter no imprimible, cuyo nmero de cdigo ASCII es 0. La existencia de main()
este carcter nulo significa que el array deber disponer de al menos una c- {
char nombre[50];
lula ms del nmero de caracteres que vayamos a almacenar.
Pero qu es un array? Array es un palabra inglesa cuyo significado lite- printf("Como te llamas?\n");
ral es formacin, disposicin ordenada. En la jerga informtica se utiliza es- scanf ("%s", nombre);
printf("Hola, %s. %s\n", nombre, ELOGIO);
ta palabra como secuencia ordenada de datos de un determinado tipo. Por }
amplificar, tambin se puede considerar como una serie de clulas de memo-
ria en fila. En nuestro ejemplo hemos creado un array compuesto de 40 clu-
las de memoria, cada una de las cuales puede almacenar un valor de tipo char. El smbolo %s indica a printf( ) que imprima una tira (string). La ejecu
la sentencia de declaracin correspondiente es: cin de elogio1 debe producir una salida similar a sta:
char nombre[40]; Como te llamas?
Gonzalo Gonzalez de la Gonzalera
Hola, Gonzalo. !Por Jpiter, que gran nombre!
Los corchetes identifican la variable nombre como un array, 40, por su
parte, indica el nmero de elementos, y, finalmente, char identifica el tipo
cada uno de ellos.
No tenemos que poner el carcter nulo; dicha tarea la realiza scanf( ) cuan
Uniones simblicas do lee la entrada. ELOGIO es una "constante de tira de caracteres. Pronto
llegaremos a la sentencia #define; por el momento, lo nico que nos interesa
es que las comillas que encierran la frase a continuacin de ELOGIO identi
fican a ste como tira, y ya se encarga el ordenador de colocarle su corres
pondiente carcter nulo.
Obsrvese (y esto es importante) que scanf( ) ha ledo nicamente el pri
mer nombre de Gonzalo Gonzlez. Al realizar la lectura de entrada, scanf( )
La sentencia char nombre [3] "encadena" se detiene en el primer espacio libre (blanco, tabulado o nueva lnea) que
tres datos de tipo char encuentra. En nuestro ejemplo termina su busca en el espacio en blanco que
hay entre Gonzalo y Gonzlez. En general, scanf( ) lee palabras sim
Figura 4.2
Declaracin de un nombre de array de tipo char ples, no frases completas. Disponemos en C de otras funciones de lectura,

75
como gets( ), que nos permitirn manejar tiras de caracteres cualesquiera. Como te llamas?
Apolo
Volveremos al tema en captulos posteriores. Hola, Apolo. !por Jpiter, que gran nombre!
Otra observacin que conviene anotar es que la tira x no es lo mismo Tu nombre de 5 letras ocupa 50 clulas de memoria.
que el carcter x. Una diferencia entre ambos podra ser que x pertenece La frase de elogio tiene 30 letras y ocupa 31 clulas de memoria.
a un tipo bsico (char), mientras que x es de un tipo derivado, un array
de char. La segunda diferencia es que, en realidad, x contiene dos carac Ahora puede imaginar lo que ha sucedido. El array nombre tiene 50 clu
teres, a saber, x y el carcter nulo. las de memoria, valor que nos facilita sizeof. Slo se han utilizado las cinco
primeras para almacenar Apolo, valor del que nos informa strlen( ). La sex
ta celdilla del array nombre contiene un carcter nulo, cuya presencia indica
a strlen( ) que deje de contar.
'X' como cracter
5 caracteres 45 caracteres nulos

"X" como tira

la tira acaba con un carcter nulo

Figura 4.3
x y x
Figura 4.4
strlen( ) sabe cundo parar
Longitud de tira strlen( )
En el ltimo captulo presentamos en sociedad el operador sizeof, que nos Cuando llegamos a ELOGIO, observamos que strlen( ) nos da de nuevo
comunicaba el tamao de los diferentes tipos en bytes. La funcin strlen( ) nmero exacto de caracteres (incluyendo espacios y signos de puntuacin)
nos informa de la longitud de una tira en caracteres. Como cada carcter ne de la tira. El operador sizeof nos responde con un nmero mayor en una uni
cesita un byte para su almacenamiento, uno podra pensar que vamos a obtener dad, ya que tambin est contando el carcter nulo invisible para terminar
el mismo resultado con ambos operadores; no es as, sin embargo. Aada la tira. Observar que no tenemos que indicar al ordenador cunta memoria
mos unas cuantas lneas a nuestro ejemplo anterior, y veamos por qu: debe reservar para almacenar la frase; la tarea se realiza automticamente
contando el nmero de caracteres comprendido entre las comillas.
/* elogio2 */ Un detalle ms: en el captulo anterior hemos empleado sizeof con parn
#define ELOGIO "!Por Jupiter, que gran nombre!" tesis, y en este captulo no. La cuestin de si se deben o no utilizar parntesis
main()
{ depende de si desea saber el tamao de un determinado tipo o el de una can
char nombre[50]; tidad en concreto. Es decir, se utilizar sizeof(char) o sizeof(float), pero en
cambio se usar sizeof nombre o sizeof 6.28.
printf("Como te llamas?\n");
scanf("%s", nombre); Aqu se ha utilizado sizeof y strlen( ) para satisfacer nuestra curiosidad;
printf("Hola, %s. %s\n", nombre, ELOGIO); sin embargo, estos operadores son mucho ms valiosos que todo eso. strlen( ),
printf("Tu nombre de %d letras ocupa %d celulas de memoria. \n", por ejemplo, es muy til en todo tipo de programas de tiras de caracteres,
strlen(nombre), sizeof nombre);
printf("La frase de elogio tiene %d letras ", strlen(ELOGIO)); como veremos en el captulo 13.
printf ("y ocupa %d celulas de memoria. \n", sizeof ELOGIO); Nos ocuparemos ahora de la sentencia #define.
}

Por cierto, observe que hemos usado dos mtodos distintos para manejar
Constantes: el preprocesador C
sentencias printf( ) largas. Hemos repartido una sentencia en dos lneas; po En ocasiones se necesita utilizar una constante dentro de un programa.
demos partir la lnea entre argumentos, pero no en mitad del texto entreco Por ejemplo, se puede calcular la longitud de una circunferencia como
millado. En el otro caso hemos empleado dos sentencias printf( ) para im
primir una misma lnea. La ejecucin de este programa podra dar un resultado: circ = 3. 14 * dimetro;

77
Hemos usado aqu la constante 3.14 para representar al famoso nmero Al compilar el programa, el valor 0.015 ser sustituido en cualquier lugar
pi. Si queremos emplear una constante, podemos simplemente teclear su va que aparezca TASA. Este proceso se denomina sustitucin en tiempo de
lor real, como hemos hecho aqu; no obstante, hay buenas razones para sus compilacin. Cuando se ejecute el programa, ya estarn hechas todas las
tituir el nmero por una constante simblica, es decir, utilizar una senten sustituciones pertinentes.
cia as: Observe con atencin el formato. En primer lugar aparece #define. Debe
estar colocado completamente a la izquierda. A continuacin se indica el nom
circ = pi * diametro ; bre simblico de la constante y el valor de la misma. No se usan smbolos,
como punto y coma, ya que no se trata de una sentencia C. Por qu se escribe
TASA con maysculas? Bien, es simplemente una tradicin en C escribir estas
y dejar que el ordenador sustituya el smbolo por su valor real ms adelante. constantes con letras maysculas. As, cuando se interne en las profundida
Por qu es aconsejable esta prctica? En primer lugar, un nombre da des de un programa podr saber instantneamente si un nombre determinado
ms informacin que un nmero. Comprense las dos sentencias siguientes: corresponde a una variable o a una constante; es, por tanto, otro ejemplo
de nuestro empeo en hacer ms legibles los programas. Por supuesto que
sepaga = 0.015 * valor; el programa funcionar tambin si coloca las constantes en letras minscu
sepaga = tasa * valor;
las, pero estamos seguros de que, despus de lo dicho, se sentir un poco cul
pable si lo hace.
Cuando est escribiendo un programa largo, encontrar que la segunda Veamos a continuacin un ejemplo sencillo:
sentencia se reconoce con ms facilidad.
En segundo lugar, si definimos la constante al comienzo del programa, /* pizza */
podremos modificarla con toda facilidad. Despus de todo, los impuestos, #define PI 3.14153
ay!, varan, e incluso una constante tan libre de toda sospecha como pi fue main() /* aprendamos los misterios de la pizza */
{
en una ocasin establecida como 3 1/7 por ley en un determinado Estado float area, circun, radio;
de los Estados Unidos. (Probablemente, ms de un crculo se encontr de
pronto fuera de la ley y fugitivo de la justicia.) De esta forma, cambiar el printf("Cual es el radio de su pizza?\n");
valor de la constante supone la modificacin de una sola sentencia; si, por scanf ( "%f", &radio) ;
area = PI * radio * radio;
el contrario, utilizamos el nmero, habremos de localizarlo a lo largo de to circun = 2 . 0 * PI * radio;
do el programa. printf("Los parametros basicos de su pizza son:\n");
De acuerdo, me ha convencido. Cmo se establece una constante sim printf("circunferencia = %1.2f, area = %1.2f\n",
circun, area);
blica? Una forma de hacerlo es declarar una variable e igualarla a la cons }
tante deseada, es decir:
float tasa;
tasa = 0.015; La cadena de smbolos %1.2f de la sentencia printf( ) hace que la salida
quede redondeada a dos cifras decimales. Evidentemente, el programa no sirve
para demostrar las propiedades ms importantes de las pizzas, especialmente
Este sistema puede ser vlido para un programa pequeo, pero resulta las organolpticas, pero contribuye a iluminar una pequea parte del miste
despilfarrador de tiempo, ya que el ordenador tiene que buscar el valor de rioso mundo de los programas de pizzas.
tasa en la direccin de memoria que le corresponda cada vez que se utiliza. Un ejemplo de salida de este programa podra ser:
Este podra ser un ejemplo de sustitucin en tiempo de ejecucin, ya que las
sustituciones se realizan mientras el programa se ejecuta. Afortunadamente, Cual es el radio de su pizza?

en C se les ocurri una idea mejor. 6.0


Los parametros bsicos de su pizza son:
La idea mejor es el preprocesador C. Ya hemos visto en el captulo 2 c circunferencia = 37.70, area = 113.10
mo utiliza el preprocesador #include para incluir informacin de otro fiche
ro. Tambin permite definir constantes; simplemente aada una lnea al co
mienzo de su programa, como la siguiente: La sentencia #define se puede utilizar tambin para constantes de tipo
carcter y tiras de caracteres. Simplemente se han de emplear apstrofos pa
#define TASA 0.015 ra las primeras y comillas para las segundas. As, son ejemplos vlidos:

79
que debe ir al comienzo del programa. El preprocesador, por su parte, igno
ra si se ha usado .h o no.

El C como maestro del disfraz: creacin de sosias


Las habilidades de #define van ms all de la representacin simblica
de constantes. Consideremos, sin ir ms lejos, el programa siguiente:
#include "jerga.h"
programa
begin
entero tuyo, mio vale
suelta("Escribe un entero\n") vale
traga("%d", &tuyo) vale
mio = tuyo por DOS vale
suelta("%d es el doble de tu numero!", mio) vale
end

(aqu interviene Caramba! esto parece vagamente familiar, recuerda al PASCAL; pero
el preprocesador)
seguro que no es C. El secreto, por supuesto, est en el fichero jerga.h. Qu
contiene? Veamos:
jerg a.h
#d efine p r o g r a m a m a in ( )
#d efine begin {
#d efine end }
#d efine vale ;
#d efine traga scanf
#d efine suelta printf
#d efine DOS 2
#d efine por *
#d efine ente ro int
Figura 4.5
Lo que usted teclea y lo que se compila Este ejemplo demuestra cmo funciona el preprocesador. Se localizan en
su programa las palabras definidas en sentencias #define, y se sustituyen lite
#define PITA '\007'
ralmente por su equivalente. En nuestro ejemplo, todos los vales son trans
#define ESS 'S' formados en puntos y coma, etc., antes de proceder a la compilacin. El pro
#define NULO ' \0' grama resultante es idntico al que hubiese obtenido utilizando las palabras
#define GANAR "Lo lograste, forastero!" habituales de C. Esta potencialidad se puede usar para definir un macro;
volveremos sobre el tema en el captulo 11.
Dediqumonos ahora a caza mayor. Supongamos que desarrolla un pa Existen algunas limitaciones. As, las partes de programa encerradas en
quete completo de programas que utilizan el mismo conjunto de constantes. tre comillas no son sustituibles. Por ejemplo, la siguiente combinacin no
Le conviene hacer lo siguiente (especialmente si es perezoso): funciona:
#define MN "minimidimaximalismo"
1. Reunir todas sus sentencias #define en un fichero, llamndolo, por ejem printf("Creia profundamente en el MN.\n");
plo, const.h.
2. En el encabezado de cada uno de los programas incluir la sentencia
#include const.h. La salida ser

As, cuando ejecute uno de los programas, el preprocesador leer el fiche Creia profundamente en el MN.
ro const.h y utilizar todas las sentencias que all se encuentren. Por cierto,
el .h del final del nombre del fichero es un recordatorio de que el propio fi Sin embargo, la sentencia:
chero acta de encabezamiento ( header), es decir, una cierta informacin printf ( "Creia profundamente en el %s. \n", MN) ;

81
dar como resultado Utilizacin de printf( )
Creia profundamente en el minimidimaximalismo El programa siguiente emplea algunos de los identificadores que acaba
mos de ver:
En el segundo caso, MN est fuera de la zona entrecomillada, siendo, por
/* imprimecosas */
tanto, sustituible por su valor. #define PI 3. 14159
En resumen, el preprocesador C es una herramienta til y de gran ayuda, main ()
por lo que aconsejamos que se emplee cuando sea posible. Iremos mostran {
do ms aplicaciones del mismo segn avancemos. int numero = 5;
float ron = 13.5;
int coste = 31000;
Utilidades de printf( ) y scanf( )
printf(Las %d mujeres se bebieron %f vasos de ron. \n",
Las funciones printf( ) y scanf( ) permiten al programa comunicarse con n u m e r o , ron) ;
el exterior. Se denominan funciones de entrada/salida o funciones E/S, para printf("El valor de pi es %f. \n", P I ) ;
abreviar. No son las nicas funciones E/S que hay en C, pero s las ms ver p r i n t f ( " F a z e r non quiso q u e t a l m a l a n d r i n f a b l a r a . \n") ;
p r i n t f ( "%c%d\n", ' $ , c o s t e ) ;
stiles. Anotemos que estas funciones no forman parte de la definicin del C; }
de hecho, en C se deja la implementacin de E/S a los diseadores del com
pilador: as se consigue optimizar las funciones para cada mquina especfi
ca. Por otra parte, distintos sistemas han intentado compatibilizarse entre Evidentemente, la salida es:
s, ofreciendo versiones de scanf( ) y printf( ). As pues, lo que se dice aqu
es cierto para la mayora de los sistemas, pero si no funciona en el suyo en La s 5 m u je res se beb ieron 13 .500 000 vaso s de ron.
concreto no se deje llevar por el pnico. El valor de pi es 3.14159.
Fazer non quiso que tal malandrin fablara.
Generalmente, printf( ) y scanf( ) funcionan de la misma forma, utili $31000
zando cada una de ellas una tira de caracteres de control y una lista de
argumentos. Estudiaremos estas caractersticas; en primer lugar en
printf( ), y a continuacin en scanf( ). El formato para uso de printf( ) es ste:
Las instrucciones que se han de dar a printf( ) cuando se desea imprimir
una variable dependen del tipo de variable de que se trate. As, tendremos p r i n t f ( C o n t r o l , i t e m 1 , i t e m 2 , ........................... ) ;
que utilizar la notacin %d para imprimir un entero, y %c para imprimir
un carcter, como ya se ha hecho. A continuacin damos la lista de todos Item1, item2, etc., son las distintas variables o constantes a imprimir. Pue
los identificadores que emplea la funcin printf( ), e inmediatamente mos den tambin ser expresiones, las cuales se evalan antes de imprimir el resul
traremos cmo usarlos. En la siguiente tabla se muestran dichos identifica tado. Control es una tira de caracteres que describe la manera en que han
dores y el tipo de salida que imprimen. La mayor parte de sus necesidades de imprimirse los items. Por ejemplo, en la sentencia
queda cubierta con los cinco primeros; de todas formas, ah estn los cuatro
restantes por si desea emplearlos. printf(Las %d mujeres se bebieron %f vasos de ron. \n",
numero, ron);
IDENTIFICADOR SALIDA
%d Entero decimal
%c Caracter control sera la frase entre comillas (despus de todo, es una tira de caracte
%s Tira de caracteres res), y nmero y ron seran los items; en este caso, los valores de dos varia
%e Nmero de punto flotante en notacin exponencial bles.
%f Nmero de punto flotante en notacin decimal
%g Use %f o %e, el que sea ms corto Veamos otro ejemplo:
%u Entero decimal sin signo
%o Entero octal sin signo printf("El valor de pi es %f. \n", P I ) ;
%x Entero hexadecimal sin signo
En esta ocasin, la lista del final tiene un nico miembro, la constante
Veamos ahora cmo se utilizan. simblica PI.

83
Observe que en el segundo ejemplo el primer tem de la lista a imprimir
S entencia
era una constante carcter, no una variable.
Suponemos que ya se habr percatado de un pequeo problema. Al utili
zar la tira de control el smbolo % para identificar los especificadores de con
versin pueden aparecer complicaciones si se pretende imprimir el propio %
como smbolo; si se usa en solitario, el compilador lo tomar como especifi-
cador, y se formar un pequeo lo. El sistema para imprimirlo es simple,
Figura 4.6 basta con emplear el smbolo % duplicado. As:
Argumentos en printf ( )
pc = 2*6;
Observamos que la parte de control contiene dos clases distintas de infor printf ("Un %d%% del beneficio de Simplicio era ficticio. \n",
macin: pc) ;

1. Caracteres que se han de imprimir tal como estn. produce la salida siguiente:
2. Identificadores de datos, tambin llamados especificaciones de con
versin. Un 12% del beneficio de Simplicio era ficticio.
limitada por comillas
Modificadores de especificaciones de conversin en printf( )
Los modificadores son apndices que se agregan a los especificadores de
conversin bsicos para modificar (qu otra cosa iba a ser?) la salida. Se
colocan entre el smbolo % y el carcter que define el tipo de conversin.
caracteres caracteres
literales literales
A continuacin se da una lista de los smbolos que est permitido emplear.
Si se utiliza ms de un modificador en el mismo sitio, el orden en que se indi
can deber ser el mismo que aparece en la tabla. Tenga presente que no todas
especificacin las combinaciones son posibles.
de conversin

Modificador Significado
Figura 4.7 El tem correspondiente se comenzar a escribir empezando
Anatoma de una tira de control
en el extremo izquierdo del campo que tenga asignado (vase
abajo). Normalmente se escribe el tem de forma que acabe
Debe existir una especificacin de conversin por cada tem que aparezca a la derecha del campo.
en la lista que sigue a la tira de control. Ay de aquel que olvide este manda Ejemplo: %-10d
miento bsico! Recibir el justo castigo a su perversidad o su despiste. Una
cosa como nmero Anchura mnima del campo. En el caso de que la cantidad a
imprimir (o la tira de caracteres) no quepa en el lugar asigna
printf ("El resultado fue Calamares %d, Jibias %d.\n", tanteo1); do, se usar automticamente un campo mayor.
Ejemplo: %4d
no tiene valor asignado al segundo %d. El resultado concreto depende de
su sistema, pero le aseguramos que en el mejor de los casos obtendr datos nmero Precisin. En tipos flotantes es la cantidad de cifras que se
sin sentido. han de imprimir a la derecha del punto (es decir, el nmero
Cuando desee escribir simplemente una frase no necesita especificadores. de decimales). En el caso de tiras, es el mximo nmero de
Por el contrario, si quiere imprimir tan slo datos, puede ahorrarse la frase caracteres que se ha de imprimir.
inicial. Por ello las dos sentencias siguientes son vlidas: Ejemplo: %4.2f (dos decimales en un campo de cuatro carac
teres de ancho).
printf("Fazer non quiso que tal malandrin fablara.\n");
printf("%c%d\n",'$', coste); 1 El dato correspondiente es de tipo long en vez de int.
Ejemplo: %ld
printf(/%4.2f/\n", 1234.56) ;
Ejemplos printf("/ %3. 1f/\n", 1234.56);
printf("/%l0.3f/\n", 1234.56);
Vamos a hacer que funcionen estos modificadores. Comenzaremos por printf("/%10.3e/\n", 1234.56);
observar el efecto que produce el modificador de anchura de campo en la }
impresin de un entero. Considrese el siguiente programa:
cuya salida es:
main()
{
printf("/%d/\n", 336) ; /1234.560059/
printf("/%2d/\n", 336); /1.234560E+03/
printf("/%l0d/\n", 336) ; /123A.56/
printf("/%-10d/\n", 336) ; / 1234.560/
} / 1.234E+03/

Este programa imprime la misma cantidad cuatro veces usando 4 especi


ficaciones de conversin diferentes. Hemos colocado un smbolo / al comienzo De nuevo comenzamos con la opcin por defecto, que en este caso es %f.
y al final para que se pueda observar dnde empieza y termina cada campo. En los nmeros en punto flotante aparecen dos opciones por defecto: la
La salida del programa es la siguiente: anchura del campo y el nmero de decimales. Ninguno de ellos ha sido espe
cificado, por lo que el ordenador lo hace por su cuenta. La segunda opcin
/336 / por defecto son 6 decimales, en tanto que la primera es el campo mnimo
/336 / en que quepa el nmero completo. Observe que el nmero impreso es ligera
/ 336 /
/ 336 / mente diferente del original con el que se empez. Ello es debido a que esta
mos imprimiendo un total de 10 cifras, mientras que los nmeros en punto
La primera especificacin de conversin es %d, sin modificadores. Ob flotante de nuestro sistema tienen una precisin de 6 7 cifras a lo sumo.
servamos que el campo asignado tiene la misma anchura que el entero a im
primir. Esta es la llamada opcin por defecto, es decir, lo que hace la m
quina cuando no se le indica otra cosa.
La segunda especificacin de conversin empleada es %2d. Este especifi-
cador debera producir un campo de dos espacios de ancho, pero, al ser ma
yor el entero a imprimir, el campo se expande hasta el tamao del mismo.
As se evita que el nmero quede recortado.
El siguiente especificador es %10d Con l se consigue un campo de 10
espacios, que, en efecto, podemos ver reflejado a la salida: hay 7 espacios
en blanco y 3 dgitos entre las marcas colocadas para delimitacin. Observe
que el nmero impreso se ajusta al margen derecho de su campo.
Finalmente, la ltima especificacin empleada es %-10d. Tambin con
ella se obtiene un campo de 10 espacios, pero esta vez el nmero queda justi
ficado a la izquierda debido al signo , como ya se indic.
Una vez que domine el tema, comprobar que los especificadores y mo
dificadores que acompaan al C permiten un excelente control sobre el as
pecto de la salida de datos en sus programas.
Vayamos ahora con los formatos en un punto flotante. Como primera
providencia, prepararemos un programa como el siguiente:

main()
{
printf("/%f/\n, 1234. 56) ;
printf ("/%e/\n", 1234. 56) ;

87
El especificador siguiente es una opcin por defecto %e. Como se puede En todo caso, seremos razonables en nuestras ambiciones, y nos restringire
comprobar, escribe un nmero a la izquierda del punto decimal y seis a la mos a la familia de tipos enteros.
derecha. En cualquiera de los casos, parece que estamos obteniendo dema
siados dgitos. El remedio es especificar el nmero de decimales a escribir a Utilizacin de printf( ) para efectuar conversiones
la derecha del punto, opcin empleada en los cuatro ltimos casos. Observe que
De nuevo vamos a imprimir enteros. Como ya hemos aprendido a mane
en el cuarto y sexto caso se produce un redondeo de nuestro nmero original.
jarnos por el ancho del campo, esta vez no nos molestaremos en usar / como
Pasemos ahora a estudiar los especificadores para tiras de caracteres. De
marca para determinarlo.
diquemos nuestra atencin al ejemplo siguiente:
main()
#define PINTA "Emocionante accion!" {
main() pri nt f( "%d\ n", 336) ;
{
pri nt f ( "%o\ n", 336) ;
printf("%2s/\n", PINTA) ; pri nt f ( "%x\ n", 336) ;
printf("%22s/\n", PINTA); pri nt f ( "%d\ n", - 336) ;
printf("%22.5s/\n", PINTA); pri nt f ( "%u\ n", -336) ;
printf("%-22.5s/\n", PINTA) ; }
}
que da como salida: El resultado de la ejecucin de este programa en nuestro sistema es el si
guiente:
/Emocionante accion!/
/ Emocionante accion!/ 336
/ Emoci/ 520
/Emoci / 150
-336
65200
Ntese cmo se expande el campo para dar cabida a todos los caracteres
especificados. Obsrvese tambin que el nmero de la derecha del punto deci
mal, que actuaba como modificador de precisin, es ahora un indicador del En primer lugar, como se puede esperar, la especificacin %d imprime
nmero de caracteres a imprimir; as, el .5 incluido en el formato indica a el nmero 336, como suceda anteriormente. Sin embargo, obsrvense los re
printf( ) que imprima tan slo 5 caracteres. sultados obtenidos a continuacin. El segundo nmero es 520, equivalente
Ya hemos visto algunos ejemplos. Sera ahora capaz de preparar un de octal (es decir, base 8) del decimal 336 (5 x 64 + 2x8 + 0x1 = 336).
terminado formato que imprimiese algo con la forma siguiente? De igual forma, 150 es el equivalente hexadecimal de 336.
Por consiguiente, podemos emplear las especificaciones de conversin de
La familia NOMBRE debe tener XXX.XX millones!
printf( ) para convertir nmeros en base 10 a nmeros en base 8 16. Sim
plemente se trata de solicitar el nmero que se ha de imprimir con el especifi
En este ejemplo NOMBRE y XXX.XX representan valores a ser suminis cador' correspondiente: %d, para obtener decimal, %o, para octal, y %x,
trados por el programa, a partir de dos variables que llamaremos nombre[40] para hexadecimal. No importa la forma en que el nmero aparezca original
y dinero. mente en el programa.
Una posible solucin podra ser: Pero an hay ms. Si imprimimos 336 utilizando un 7od, no se produ
ce ningn resultado extrao. Empero, este mismo nmero con especificacin
printf("La familia %s debe tener %.2f millones! \n", %u da como resultado 65200, no 336, como cabra esperar. El especificador
nombre,. dinero) ;
%u corresponde a enteros sin signo (unsigned). El resultado procede de la
forma de almacenamiento de nmeros negativos en nuestro sistema de referen
Hasta ahora hemos jugado sobre seguro, empleando para cada tipo de va cia; concretamente se usa un mtodo denominado complemento a dos.
riable su especificador correspondiente: %f para float, etc. Sin embargo, tam En dicho mtodo, los nmeros 0 a 32767 se representan tal como estn, mien
bin podemos usar printf( ) en un programa en que se pretenda averiguar tras que los nmeros 32768 a 65535 se reservan para representar nmeros ne
el equivalente ASCII de un carcter determinado, por ejemplo. O lo que es gativos, siendo 65535 igual a -1, 65534 igual a -2, etc. Por tanto, -336
lo mismo, realizar conversiones de tipo en la propia sentencia de impresin. se representa como 65536 -336 o, lo que es lo mismo, 65200. Hay que tener

89
en cuenta que no todos los sistemas emplean este mtodo para representar No hemos agotado todas las posibilidades de combinaciones de datos y
los enteros negativos; en cualquier caso, se debe sacar una moraleja: no espere especificaciones de conversin, de manera que le aconsejamos que investigue
que una conversin %u en su sistema se limite a un simple cambio de signo. por su cuenta. Mejor an, intente comprobar si es capaz de predecir el resul
Vayamos ahora con un interesente ejemplo que concierne a los caracte tado de una determinada combinacin antes de ejecutarla.
res. Ya lo hemos usado anteriormente, y se refiere a la utilizacin de printf( )
para encontrar el cdigo ASCII de un carcter. Por ejemplo: Uso de scanf( )
printf("%c %d\n", A', A') ; Hasta ahora hemos hecho un uso bastante rudimentario de scanf( ); nos
dedicaremos ahora a explorar las restantes posibilidades.
produce Al igual que prinft( ), scanf( ) emplea una tira de caracteres de control
y una lista de argumentos. La mayor diferencia entre ambas est en esta lti
A 65 ma; printf( ) utiliza en sus listas nombres de variables, constantes y expre
como salida. A es la letra A, por supuesto, y 65 es el cdigo ASCII decimal siones; scanf( ) usa punteros a variables. Afortunadamente, no se necesita
del carcter A. Se podra haber usado tambin %o para averiguar el cdigo saber ni lo ms mnimo sobre punteros para emplear esta expresin; se trata
octal del mismo carcter. simplemente de seguir las dos reglas que se dan a continuacin:
De este modo se dispone de una forma sencilla de conocer cdigos ASCII 1. Si desea leer un valor perteneciente a cualquiera de los tipos bsicos,
de distintos caracteres, y viceversa. Si lo prefiere, tambin puede consultar coloque el nombre de la variable precedido por un &.
el apndice G, en que se encuentra una tabla completa. 2. Si lo que desea es leer una variable de tipo string (tira de caracteres),
Qu sucede si intentamos convertir en carcter un nmero mayor de 255? no use el &.
La respuesta la da la siguiente lnea de programa y su resultado
El siguiente programa es vlido:
printf("%d %c\n", 336, 336);
336 P main()
{
int edad;
El cdigo ASCII decimal de P es 80, y ya se habr percatado nuestro pers float sueldo;
char cachorro[30];
picaz lector que 336 es justamente 256 + 80. Aparentemente, el nmero se
interpreta mdulo 256 (en el argot matemtico, mdulo 256 significa el resto printf("Confiese su edad, sueldo y mascota favorita.\n");
de la divisin del nmero por 256). Dicho de otra forma, cuando el ordena scanf("%d %f", &edad, &sueldo);
scanf ("%s", cachorro); /* en array de char no se usa & */
dor alcanza un mltiplo cualquiera de 256, comienza a contar de nuevo des printf("%d %. 0f pts. %s\n ", edad, sueldo, cachorro);
de cero, por lo que 256 se tomar como 0, 257 como 1 , 5 1 1 como 255, 512 }
como 0, 513 como 1, etc.
Como colofn final, intentaremos imprimir un entero (65616) mayor que y una posible salida sera:
el valor mximo (32767) permitido para int en nuestro sistema:
Confiese su edad, sueldo y mascota favorita.
printf("%1d %d \n, 65616, 65616); 82
9676123.50 rinoceronte
82 9676123 pts. rinoceronte
El resultado es
65616 80
Scanf( ) considera que dos tems de entrada son diferentes cuando estn
separados por blancos, tabulados o espacios. Va encajando cada especifica-
Una vez ms, el ordenador ha hecho su asunto con el mdulo. En esta dor de conversin con su campo correspondiente, ignorando los blancos in
ocasin se comienza la cuenta en bloques de 65536. Los nmeros comprendidos termedios. Observe cmo se ha repartido la entrada en dos lneas. Podra
entre 32767 y 65536 habran arrojado un resultado negativo debido a la forma mos tambin haber utilizado una o cinco, con la nica condicin de dejar
peculiar de almacenamiento antes comentada. Si su sistema tiene un tamao al menos un carcter nueva lnea, tabulado o espacio entre cada dos entra
permitido para nmeros enteros distinto del nuestro, el comportamiento das. La nica excepcin es la especificacin %c, que lee el siguiente carcter,
que cabe esperar es el mismo, pero el rango ser diferente al expuesto aqu. sea blanco o no.

91
La funcin scanf( ) emplea un juego de especificadores de conversin muy Si se deja un blanco entre una especificacin de conversin y la siguiente,
semejante a! de printf( ). Las diferencias ms sobresalientes son: queda garantizado que ningn nmero de se entremezclar con otro, incluso
aunque supere el tamao que tenga asignado. Este detalle se debe a que se
1. No existe la opcin %g. imprimen todos los caracteres de la tira de control de printf( ), incluyendo
2. Las opciones %f y %e son equivalentes. Ambas aceptan un signo op los espacios.
cional, una tira de dgitos con o sin punto decimal y un campo para Cuando un nmero est destinado a aparecer dentro de una frase es a
el exponente, tambin opcional. menudo conveniente especificar un campo igual o menor que el esperado.
3. Existe una opcin %h para leer enteros short. Con ello se consigue evitar que aparezcan blancos suplementarios que afea
ran el texto. Comprubese con el ejemplo siguiente:
Por cierto, scanf( ) no es la funcin ms comn para entrada de datos
en lenguaje C. La venimos empleando desde el principio por su gran versati- printf("Pepito Conejo corrio %. 2f leguas en 10 minutos.\n",
distancia);
dad (puede leer datos de cualquiera de los tipos), pero hay que considerar
que existen otras funciones de entrada en C, como getchar( ) y gets( ), que
se acomodan mejor a tareas especficas, concretamente a la lectura de carac- producira
teres individuales y de tiras con espacios en blanco. Trataremos algunas de
Pepito Conejo corrio "4.23 leguas en 10 minutos.
estas funciones ms adelante, en los captulos 6, 13 y 15.
en tanto que si la especificacin de conversin se cambia a %l0.2f. el resul
Claves de utilizacin tado sera
P e p i t o C o n e j o c o r r io 14 .23 legu as en 10 m inu tos.
La especificacin de anchuras fijas de campos resulta muy til cuando
se desean imprimir columnas de datos. Al hacerse el ancho del campo por
defecto equivalente a la anchura del propio nmero, el uso repetido, por ejem-
plo, de Hasta ahora hemos aprendido
printf("%d %d %d\n", val1, val2, val3) ; Qu es una tira de caracteres: unos caracteres puestos en fila
Cmo escribir una tira de caracteres: Esto es una serie de caracteres pues
producira columnas desalineadas en cuanto los nmeros de una de ellas tu- tos en fila
viesen un tamao distinto. As, la salida podra tener este tenebroso aspecto: Cmo se almacena la tira: Esto es una serie de caracteres puestos en
fila \0
12 234 1222 Dnde almacenar una tira: char frase[25] o static char frase[25]
4 5 23 Cmo hallar la longitud de una tira: strlen(frase)
22334 2322 10001
Cmo imprimir una tira: printf(Vos, frase)
Cmo leer tiras de una sola palabra: scanf (%s, nombre)
(Evidentemente, suponemos que el valor de las variables ha sido alterado Cmo definir constantes numricas: #define DOS 2
entre ejecucin y ejecucin de la sentencia.) Cmo definir constantes carcter: #define OLE '!'
Por el contrario, se puede conseguir una salida ntida utilizando campos Cmo definir constantes tira: #define CUIDADO No hagas eso!
de anchura fija lo suficientemente grandes. As, la sentencia Especificaciones de conversin E/S: %d %f %e %g %c %s %u %o %x
Cmo hacer un ajuste fino de formatos de salida: %-10d %3.2f
printf ("%d9d %9d %d9d\n", val1, val2, val3) ; Cmo hacer conversiones: printf (%d %o %c n, OLE, OLE, OLE).

dara como resultado Cuestiones y respuestas


12 234 1222
Cuestiones
4 5 23
22334 2322 10001
1. Ejecute de nuevo el primer programa del captulo; esta vez indique su nombre
y apellido cuando le pregunte por su nombre. Qu sucede?; por qu?

93
2. Indicar la salida producida por cada uno de los fragmentos siguientes, suponien-
do que forman parte de un programa completo:
a. p r i n t f ( " V e n d i o l a p i n t u r a e n $%2. 2 f . \ n " , 2 . 3 4 5 e 2 ) ;
b. printf ("%c%c%c\n", ' E' , 104, '\41');
C. #define Q "Interpreta Don Juan mejor que nadie."
p r i n t f ( " %s \ n t i e n e % d c a r a c t e r e s . \ n " , Q , s t r l e n ( Q ) ) ;
d. printf("Es lo Mismo % 2.2e que %2.2f?\n", 1201.0, 1201.0);

3. En la cuestin 2.c, qu cambios habra que introducir para que la tira Q apare-
cese entrecomillada en la salida?
4. A la bonita bsqueda del error!
define B farol
define X 10
M a in ()
{
int edad;
char nombre;
prin tf("Introdu zca su nom b re.");
scanf("%s", nombre);
p r i n t f ( " M u y b i e n , %c, q u e e d a d t i e n e s ? \ n " , n o m b r e ) ;
s c a n f ( "%f" , e d a d ) ;
xp = edad + X;
p r i n t f ( " E s o e s u n %s P o r l o m e n o s t i e n e s %d . \ n " , B , x p ) ;
}

Respuestas
1. El programa revienta. La primera sentencia scanf( ) lee simplemente el primer nombre,
dejando el segundo (o el apellido) sin tocar, pero almacenado en el buffer de entrada. (Es-
te buffer es simplemente una zona de almacenamiento temporal que se usa para guardar
entradas.) Cuando la segunda sentencia scanf( ) pregunta por su peso, recoge el dato ante-
rior, su apellido, y lo toma como el peso. El programa queda as viciado e inservible. Por
otra parte, si en lugar del nombre y apellido se contesta, por ejemplo, Pepe 144, la m-
quina tomar ese 144 como peso, aunque se haya escrito antes de solicitarlo.
2. a. V e n d i l a p i n t u r a e n $ 2 3 4 . 5 0 .
b. Eh!
Nota: El primer carcter es una constante; el segundo, un entero decimal convert-
do en carcter, y el tercero, una representacin ASCII de una constante carcter,
C. Interpreta Don Juan mejor que nadie.
tiene 36 caracteres,
d. Es lo m ism o 1.20E+03 que 1201.00?

3. Recuerde las secuencias de escape del captulo 3 y pruebe


p r i n t f ( " \ " %s \ " n t i e n e %d c a r a c t e r e s . \ n " , Q , s t r l e n ( Q ) ) ;
4. Lnea 1: Se ha omitido #. farol debera ser farol.
Lnea 2: Se ha omitido #.
Lnea 6: n om b re debe ser un array; ch ar n om b re[25] servira.
Lnea 8: Debe haber un \ n en la tira de control.
Lnea 10: El %c debera ser %s.
Lnea 11 : Al ser ed ad un entero, se deber usar %d , no % f. Adems, hay que poner
no ed ad .
Lnea 12: xp no ha sido declarada.
Lnea 13: Es correcta, pero tendr problemas por mala definicin de B.
Adems, declaramos al programa culpable de falta de cuidado.

94
5
Operadores,
expresiones
y sentencias
En este captulo encontrar:
Introduccin
Operadores fundamentales
Operador de asignacin: =
Operador de adicin: +
Operador de sustraccin:
Operador signo:
Operador de multiplicacin: *
Operador de divisin: /
Precedencia en operadores
Algunos operadores adicionales
Operador mdulo: %
Operadores incremento y decremento: + + y - -
Decremento:- -
Precedencia
No se pase de listo
Expresiones y sentencias
Expresiones
Sentencias
Sentencias compuestas (bloques)
Conversiones de tipo
Operador de moldeado
Un programa ejemplo
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

97
Operadores,
tulo. Entretanto, con la intencin de que adopte un estado mental apropia
do, presentamos nuestro pequeo programa de comienzo de captulo, esta
vez con un poco de aritmtica.

expresiones /* zapatos1 */
#define TOPE 0.933
#define ESCALA 0.6167

y sentencias main()
{
/* este programa convierte numero de zapatos en cm. de pie */
float zapato, pie;
zapato = 42.0;
pie = ESCALA * z a p a t o + T O P E ;
CONCEPTOS printf("Numero z a p a t o centimetros pie\n");
printf ( " % 1 0 . 1 f % 1 6 . 2 f c m . \ n " , z a p a t o , p i e ) ;
}
Operadores y operandos
Hagamos aritmtica Qu barbaridad! Nada menos que un programa con multiplicacin y adi
Uso de while cin. Toma el nmero de sus zapatos (suponiendo que calce un 42) y le indi
Expresiones ca la longitud de su pie en centmetros. Cmo dice? Que usted lo hubiese
Sentencias simples y compuestas calculado a mano mucho ms rpido? Esa es una buena razn para llegar
Conversiones de tipo a la conclusin de que realizar un programa que calcule una sola talla de za
patos es una solemne estupidez. Podramos intentar mejorarlo reescribin
PALABRAS CLAVE dolo como programa interactivo, pero, a fuer de ser sinceros, estamos ro
zando apenas el potencial real de nuestro ordenador.
En realidad, lo que necesitamos es convencer al ordenador para que rea
while
lice clculos repetitivos. Despus de todo, una de las misiones principales de
un ordenador es la ejecucin de clculos aritmticos. En C se ofrecen varios
OPERADORES mtodos para efectuar operaciones repetidas; vamos a ver uno de ellos. Reci
be el nombre de bucle while, y nos permite hacer un anlisis ms prctico
+ - * / % + + - - (tipo) de los operadores. He aqu una versin mejorada de nuestro programa de
tallas de zapatos.

/* zapatos2 */
#define TOPE 0.933
#define ESCALA 0.6167
main ()
/ * este programa convierte numero de zapatos en cm. de pie */
float zapato, pie;
printf( "Numero zapato centmetros pie\n");
Introduccin zapato = 30.0;
while (zapato < 48. 5)
En los captulos 3 y 4 hemos comentado los tipos de datos que reconoce
{
pie = ESCALA * zapato + TOPE;
el C. Es el momento de estudiar la forma de manipular esos datos. El C ofre printf("%10.1f %16.2f cm.\n", zapato, pie);
ce muchas posibilidades. Comenzaremos con las operaciones aritmticas b zapato = zapato + 1.0;
sicas: suma, resta, multiplicacin y divisin. Adems, con el fin de hacer nues
}
printf("Usted sabe donde le aprieta el zapato.\n");
tros programas ms tiles, daremos un primer repaso a los bucles en este cap }
Una versin resumida de la salida en pantalla de zapatos2 sera: te al bucle. En nuestro ejemplo, dicha sentencia es la orden final de escritura
printf( ).
Se puede modificar con facilidad este programa con el fin de realizar to
do tipo de conversiones. Por ejemplo, si hacemos ESCALA igual a 1.8 y TOPE
igual a 32.0, tendremos un convertidor de grados centgrados a Fahrenheit.
O bien, con ESCALA igual a 0.6214 y TOPE igual a 0, convertiremos kil
metros en millas. Si hace alguno de estos cambios, le convendr cambiar
tambin los mensajes de salida, en beneficio de la claridad.
El bucle while es, por tanto, una herramienta flexible y conveniente para
el control del programa. Nos ocuparemos ahora de los operadores funda
mentales que se pueden utilizar en programacin.

(Por cierto, las constantes de conversin de tallas a centmetros se obtu Operadores fundamentales
vieron en una visita de incgnito a una zapatera. La nica zapatera que ha
ba por los alrededores era de caballeros. Ignoramos si estas constantes son En C se utilizan operadores para representar operaciones aritmticas.
idnticas para zapatos de seora; si est interesado en este particular, tendr Por ejemplo, el operador + hace que se sumen los valores situados a su iz
que averiguarlo por su cuenta.) quierda y derecha. Si el nombre operador le resulta extrao... bueno, piense
Veamos cmo funciona el bucle while. Cuando el programa llegue por que habr que llamar a esas cosas de alguna forma. Y puestos a elegir, esta
vez primera a la sentencia while, compruebe si la condicin expresada entre
parntesis se cumple o no. En nuestro caso la condicin es: rn de acuerdo con nosotros en que operador es una alternativa mejor que
esas cosas o que transactores aritmticos, por poner un ejemplo. Estu
zapato < 48.5 diaremos ahora los operadores = , + , - , y /. (En C no existe el operador
exponencial. En un captulo posterior presentaremos una funcin que ejecu
donde < es un smbolo que significa menor que. Pues bien, en el progra te esta tarea.)
ma, el valor asignado a zapato es 30.0, que resulta evidentemente menor que
48.5; por consiguiente, la condicin se cumple. En tal caso, el programa con Operador de asignacin: =
tina con la siguiente sentencia, que convierte la talla en centmetros. A con En C, el signo igual no significa igual a. En su lugar, es un operador
tinuacin imprime el resultado. La sentencia siguiente de asignacin de valores. La sentencia
zapato = zapato + 1; bmw = 2002;

incrementa en 1.0 el valor de zapato, que ahora valdr 31.0. En ese momen asigna el valor 2002 a la variable bmw. Es decir, lo que hay a la izquierda
to el programa vuelve a la sentencia while a comprobar de nuevo la veraci del signo = es el nombre de la variable, mientras que la parte derecha es el
dad de la informacin. Y por qu en ese momento? Porque la lnea siguien valor de la misma. Llamamos al smbolo = operador de asignacin. Re
te es una llave de cierre (}), y estamos utilizando un juego de llaves ({ }) para petimos, no interprete la sentencia como bmw es igual a 2002, sino como
marcar la extensin del bucle while; as, el ordenador sabe cul es el grupo asgnese el valor 2002 a la variable bmw. En el caso concreto de este ope
de sentencias que hay que repetir. Volvamos al programa. 31 es menor que rador, la accin se ejecuta de derecha a izquierda.
48.5? Pues claro, qu pregunta! De nuevo nos metemos en el grupo de sen Quiz le parezca que la distincin que hacemos entre nombre y valor de
tencias, y se ejecutan las mismas operaciones (en la jerga informtica, se lla una variable es un tanto histrinica. No hay tal; considere la siguiente sen
ma bucle al conjunto de sentencias repetidas cclicamente). El juego con tencia, muy comn en programacin:
tina hasta que zapato alcanza el valor 49.0. En ese instante, la condicin
i = i + 1;
zapato < 48.5
Desde un punto de vista matemtico, carece de sentido. Si se suma 1 a
se vuelve falsa, ya que 49 no es menor que 48.5. Qu sucede ahora? Senci un nmero finito, el resultado es, por supuesto, diferente del inicial. Consi
llamente, que el programa prosigue su tarea ejecutando la sentencia siguien derada como sentencia de asignacin, sin embargo, resulta perfectamente co-

101
rrecta. Traducida a espaol, la sentencia significara encuentre el valor de hace que se imprima el nmero 24, y no la expresin
la variable cuyo nombre es i. A tal valor, smesele 1, y a continuacin asg 4 + 20
nese este nuevo valor a la variable cuyo nombre es i.
Por el contrario, una sentencia como Los operandos pueden ser aqu tanto constantes como variables. As, la
sentencia
2002 = bmw;
ganancia = salario + sobornos;
carece de sentido en C, porque 2002 es simplemente un nmero. No se puede hace que el ordenador consulte los valores de las dos variables de la derecha,
asignar un valor a una constante por la sencilla razn de que ya lo tiene. As los sume y asigne el total a la variable ganancia.
pues, cuando se siente delante del teclado recuerde que la parte situada a la Se dice que el operador + es binario o didico, en el sentido de
izquierda del signo = debe ser el nombre de la variable. que utiliza dos operandos.
Para aquellos que les guste llamar a las cosas por su nombre, indicare
mos que lo que acabamos de llamar parte situada recibe en realidad el nom Operador de sustraccin:
bre de operando. Operando es aquello sobre lo que opera el operador.
Por ejemplo, comerse una hamburguesa se puede describir como una aplica El operador de sustraccin hace que se reste el nmero situado a su dere-
cin del operador comer sobre el operando hamburguesa cha del situado a su izquierda. La sentencia
El operador bsico de asignacin en C es un poquito ms llamativo que llevoacasa = 224.00 - 24.00;
el de la mayora de lenguajes. Pruebe a ejecutar el siguiente programa:
asigna el valor 20 a llevoacasa.
/* resultados del torneo de golf */
main()
{ Operador signo:
int jane, tarzan, chita;
El signo menos se utiliza tambin para indicar o cambiar el signo alge
chita = tarzan = jane = 68; braico de un valor. Por ejemplo, la secuencia
printf(" chita tarzan jane\n");
printf("Primer recorrido %4d %8d %S8d\n", chita, tarzan, jane); pepe = -12;
} paco = -pepe;

asigna a paco el valor 12.


La mayor parte de lenguajes de ordenador protestar en la sentencia de
triple asignacin que se realiza en este programa. En C se acepta sin proble
mas. Las asignaciones se realizan de derecha a izquierda; as, jane tomar
en primer lugar el valor 68; a continuacin lo har tarzn, y por ltimo, chi
ta. La salida de este programa ser:

chita tarzan jane


Primer recorrido 68 68 68

Hay algunos otros operadores de asignacin en C que funcionan de for


ma distinta a la aqu comentada. Prometemos solemnemente hablar de ellos
en un prximo captulo.

Operador de adicin: +
El operador de adicin hace que los dos valores situados a su izquierda
y derecha se sumen. Por ejemplo, la sentencia

printf("%d", 4 + 20); Figura 5.1


Operadores unarios y binarios

103
p r i n t f ("cuadro granos sumados granos totales fraccion\n") ;
Cuando se utiliza el signo con este sentido se dice que es un operador printf(" cosecha\n");
unario, indicando que emplea tan slo un operando. total = actual = 1.0; /* comenzamos con un grano */
printf("%4d %15.2e %16.2e %12.2e\n", cont, actual,
Operador de multiplicacin: * total, total/COSECHA);
while (cont < CUADRADOS){
La multiplicacin se indica con el smbolo *. La sentencia cont = cont + 1;
actual = 2.0 * actual; /* duplica granos en cada cuadro */
cm = 2.54 * pulg; total = total + actual;/* actualiza total */
printf("%4d %15.2e %15.2e %12.2e\n", cont, actual,
total, total/COSECHA);
multiplica la variable pulg por 2.54 y asigna la respuesta a cm. }
Necesita, por casualidad, una tabla de cuadrados? En C no existe un ope }
rador especfico para cuadrados, pero podemos emplear la multiplicacin.
La salida comienza siendo bastante inocente:
/* cuadrados */ cuadro granos sumados granos totales fraccion
cosecha
main() /* produce una tabla de cuadrados */ 1 1.00E+00 1.00E+00 2.50E-16
{ 2 2.00E+00 3.00E+00 7.50E-16
int num = 1; 3 4.00E+00 7.00E+00 1.75E-15
4 8.00E+00 1.50E+01 3.75E-15
while (num < 21) { 5 1.60E+01 3.10E+01 7.75E-15
printf ("%10d %10d", num, num*num) ; 6 3.20E+01 6.30E+01 1.58E-14
n = n + 1; 7 6.40E+01 1.27E+02 3.18E-14
} 8 1.28E+02 2.55E+02 6.38E-14
} 9 2.56E+02 5.11E+02 1.28E-13
10 5.12E+02 1.02E+03 2.58E-13

Este programa imprime los 20 primeros enteros y sus cuadrados, por si


Al cabo de 10 cuadrados el joven ha conseguido acumular algo ms de
quiere comprobarlo. 1.000 granos de trigo. Pero veamos lo que sucede en la casilla 52:
Veamos ahora otro ejemplo ms ilustrativo.
Habrn odo hablar probablemente de la historia de aquel poderoso sul
tn que deseaba recompensar a un estudiante que le haba prestado un gran 52 2.26E+15 4.52E+15 1.13E+00

servicio. Cuando el sultn le pregunt la recompensa que deseaba, ste sea


l a un tablero de ajedrez y solicit simplemente 1 grano de trigo por la pri La cantidad excede al total de la cosecha mundial! Si desea saber qu
mera casilla, 2 por la segunda, 4 por la tercera, 8 por la siguiente, y as suce sucede en el cuadro 64, tendr que ejecutar el programa usted mismo.
sivamente. El sultn, que no deba andar muy fuerte en matemticas, qued Este ejemplo es ilustrativo del fenmeno de crecimiento exponencial. El
sorprendido por la modestia de la peticin, porque estaba dispuesto a otor crecimiento de la poblacin mundial y la utilizacin de recursos energticos
garle riquezas mucho mayores; al menos, eso pensaba l. Segn muestra el por parte de la humanidad estn siguiendo leyes semejantes.
siguiente programa, nada ms lejos de la realidad. En el programa se calcula
el nmero de granos de trigo que corresponden a cada casilla y se acumula Operador de divisin: /
el total. Como el nmero de granos no es una cantidad que se maneje habi El smbolo / se utiliza en C para divisin. El valor a la izquierda de la /
tualmente, se compara tambin con una estimacin de la produccin anual se divide por el que se encuentra a su derecha. Por ejemplo
mundial expresada en granos.
cuatro = 12.0/3.0;
/* trigo */
#define CUADRADOS 64 /* cuadrados del tablero */
asigna a cuatro el valor 4.0.
#define COSECHA 7E14 /* cosecha mundial en granos */ La divisin funciona de manera diferente en tipos flotantes y enteros. La
divisin en punto flotante da como resultado un valor en punto flotante, en
main() tanto que la divisin entre enteros produce un entero, es decir, un nmero
{ sin parte decimal. Este hecho puede producir extraos resultados, como cuando
double actual, total; se divide, por ejemplo, 5 entre 3, ya que el resultado no es entero. En C, cuando
int cont = 0;
105
se realiza una divisin entera, se descarta toda la parte decimal sin ms tr las operaciones en los rdenes expuestos, se obtienen como resultados fina
mites. El proceso recibe el nombre de truncado. les 255 y 192.5, respectivamente. Por su parte, el ordenador debe tener ideas
Ejecute el programa siguiente para aclarar ideas. En l se puede compro propias, ya que, si sometemos la sentencia a su docto arbitraje, arroja como
bar la diferencia entre divisin entera y de punto flotante. resultado, para manteca, el valor 205.0.
Queda claro que el orden en que se ejecuten las operaciones afecta al re
/* divisiones que hemos aprendido */ sultado. El C necesitar, por tanto, una forma no ambigua para escoger un
main()
{ orden determinado, es decir, unas reglas prefijadas que indiquen lo que ha
printf ("division entera: 5/4 es %d \n", 5/ 4) ; de hacerse primero. Lo que se hace en C es escoger un orden salteado. A ca
printf ("division entera: 6/3 es %d \n", 6/3); da operador se le asigna un nivel de precedencia: la multiplicacin y divisin,
printf ("division entera: 7/4 es %d \n", 7/ 4) ;
printf ("division flotante: 7. /4. es %2.2f \n", 7./ 4. ) ; por ejemplo, tienen mayor precedencia que la adicin y sustraccin, y por
printf ("division mixta: 7./4 es %2-2f \n", 7./ 4) ; tanto se ejecutan antes. Qu sucede si hay dos operadores con la misma pre
} cedencia? Bien, entonces se ejecutan segn el orden en que aparecen en la
sentencia. En la mayora de los operadores, el orden elegido es de izquierda
Habr observado que hemos incluido tambin un caso de divisin mixta, a derecha (una excepcin que ya hemos apuntado es el operador asignacin,
en el que se divide un nmero en punto flotante entre un entero. El C es un = , que se ejecuta en sentido contrario). Volviendo a nuestra sentencia
lenguaje bastante ms permisivo que otros en cuestiones como mezclas de
tipos, pero, como norma, procure no hacerlo. Veamos los resultados: manteca = 25.0 + 60.0*n/ESCALA;
el orden de operacin es:
division entera: 5 / 4 es 1
division entera: 6/3 es 2 60.0*n el primer * o / de la sentencia. Suponiendo n = 6, tendremos
division entera: 7 / 4 es 1 60.0*n = 360.0. A continuacin,
division flotante: 7 . / 4. es 1.75
division mixta: 7 . / 4 es 1 . 7 5 360.0/ESCALA el segundo * o / de la sentencia. Si ESCALA es igual a
2.0, el resultado ser 180.0. Seguidamente,
25.0 + 180.0 el primer + o de la sentencia. Obtenemos as el resulta
Observe que la divisin entera no redondea al entero ms prximo, sino do final, 205.0.
que siempre lo hace por defecto. Por otra parte, el caso de divisin mixta Hay bastante gente a la que le gusta representar el orden establecido para
se ha resuelto como punto flotante; cuando en un programa C se encuentra la evaluacin en forma de diagrama. Estos diagramas reciben el nombre de
un clculo de esta clase, el entero se convierte en punto flotante antes de rea rbol de expresiones. Veamos un ejemplo.
lizar la operacin.
Las propiedades de la divisin entera resultan ser bastante tiles en algu
nos casos, como veremos en seguida en un ejemplo. Pero antes considere
mos otro importante problema que aparece cuando en una misma sentencia
se combinan varias operaciones. Este ser el motivo de nuestra prxima sec
cin.

Precedencia en operadores
Supongamos la lnea

manteca = 25.0 + 60.0*n/ESCALA;

Esta sentencia contiene una suma, una multiplicacin y una divisin. La


cuestin es: Cul se ejecuta en primer lugar? Se suman 25.0 y 60.0, el re
sultado 85.0 se multiplica por n y lo que se obtenga se divide por ESCALA?
O bien se multiplica primero 60.0 por n; el resultado se suma a 25.0, y la
respuesta se divide por ESCALA? Vayamos por partes. Asignemos a n el va Figura 5.2
lor 6.0, y a ESCALA el valor 2.0. Sustituyendo estos valores, y ejecutando Arboles de expresiones con operadores, operandos y orden de evaluacin
El diagrama demuestra cmo se reduce la expresin original por etapas Puesto que los parntesis tienen la prioridad ms alta, si vamos de izquierda
hasta llegar a un valor. a derecha en la expresin, el primer par de parntesis que nos tropezamos
Qu sucede si uno desea realizar la suma antes que la divisin? Bien, es (2 + 5), as es que calculamos su contenido y obtenemos:
entonces se puede escribir:
max = tanteo = -7*6 + (4 + 3*(2 + 3))
harina = (25.0 + 60.0*n)/ESCALA;
El siguiente par de parntesis es (4 + 3*(2 + 3)), de modo que nos toca
Cualquier cosa que se encierre entre parntesis se ejecuta con preferencia de nuevo evaluar su contenido, es decir, la expresin 4 + 3*(2 + 3). Aj,
sobre las dems. Dentro de los parntesis se mantienen las reglas ya comen ms parntesis! Aqu lo que hay que hacer en primer lugar es calcular 2 + 3.
tadas. En este ejemplo se efectuar en primer lugar la multiplicacin, y des La expresin que tenemos ahora es
pus la suma. Con esto queda completada la evaluacin del parntesis. Uni
camente entonces se realiza la divisin por ESCALA. max= tanteo = -7*6 + (4 + 3*5)
Podemos ahora hacer una tabla en que se resuman todas las reglas con
operadores que hemos utilizado hasta el momento. En el apndice C se pre Todava tenemos que acabar con los parntesis de fuera. Como * tiene
senta una tabla que comprende todos los operadores. preferencia sobre +, la expresin siguiente que se obtiene ser
Tabla 5-1. Operadores en orden decreciente de precedencia max = tanteo = -7*6 + (4 + 15)
OPERADORES ASOCIATIVIDAD
() izquierda a derecha y a continuacin
(unario) izquierda a derecha max = tanteo = -7*6 + 19
*/ izquierda a derecha
+ (sustraccin) izquierda a derecha Qu viene ahora? Si piensa que es 7*6, est equivocado. Observe que
el unario tiene prioridad mayor que el *. Se trata de un cambio de signo,
= derecha a izquierda por lo que 7 se transforma en 7, y seguidamente se multiplica 7 por 6.
Nuestra expresin original ha quedado reducida a
Obsrvese que los dos empleos del signo menos tienen diferente priori
dad. La segunda columna indica la forma en que el operador se asocia a sus max = tanteo = -42 + 19
operandos. Por ejemplo, el signo menos unario queda asociado a la cantidad
escrita a su derecha, y el operador divisin divide la cantidad de su izquierda y la ejecucin de la suma la transforma en
entre la situada a su derecha.
Comprobemos estas reglas de precedencias y prioridades con un ejemplo max = tanteo = -23
ms complicado.
En ese momento, a tanteo se le asigna el valor 23, y, por ltimo, max
/* test de precedencia */ toma tambin el valor 23. Recurdese que el operador = asocia de dere
main
{
cha a izquierda.
int max, tanteo;
max = tanteo = -(2 + 5)*6 + (4 + 3*(2 + 3));
}
printf ( "max = %d \n", max); Algunos operadores adicionales
El C tiene alrededor de 40 operadores, algunos de los cuales se utilizan
Qu valor imprimir este programa a la salida? Intente calcularlo de mucho ms que otros. Los que hemos repasado hasta ahora son los ms co
cabeza, y a continuacin ejecute el programa o lea la siguiente descripcin munes; a esa lista vamos a agregar ahora tres operadores adicionales que re
para comprobar su resultado (estamos seguros de que es correcto). sultan bastante tiles.

109
Operador mdulo: % Operadores incremento y decremento: + + y--

El operador mdulo (o resto) se emplea en aritmtica de nmeros ente El operador incremento realiza una tarea muy simple: incrementa (aumen
ros. Proporciona el resto de la divisin entera (es decir, sin decimales) del ta) el valor de su operando en 1. Se ofrecen en C dos variedades. En la pri
nmero entero situado a su izquierda entre el situado a su derecha. Por ejem mera de ellas, el + + aparece antes de la variable afectada, es el llamado
plo, 13 % 5 (lase 13 mdulo 5) es 3, ya que 13 entre 5 da un cociente de modo prefijo. En la segunda, el + + se encuentra situado detrs de la
2 y un resto de 3. variable. A esta variedad la denominaremos modo sufijo.
No incordie con este operador en nmeros de punto flotante; simplemen La diferencia entre ambos modos reside en el preciso momento en que
te, no funciona. se realiza la operacin de incremento. En primer lugar prestaremos atencin
A primera vista, este operador le suena a uno como una herramienta eso a las semejanzas, y volveremos ms adelante a las diferencias. El ejemplo si
trica puesta ah para deleite de los matemticos, pero en realidad se trata guiente demuestra el funcionamiento de ambos operadores.
de algo prctico y con grandes posibilidades. Supongamos, por ejemplo, que
usted desea realizar un programa de facturacin mensual, en el que hay que /* sumauno */
aadir una cierta cantidad extra al final de cada trimestre. Veamos cmo se Main() /* incremento: prefijo y sufijo */
puede utilizar este operador para controlar el flujo del programa. Simple {
int ultra = 0, super = 0;
mente, haga que el ordenador calcule el nmero de mes mdulo 3 (mes % 3)
y compruebe si el resultado obtenido es 0. Si es as, aada la cantidad suple while (super < 6)
{
mentaria que corresponda. Comprender mejor el sistema cuando estudie super++;
mos las sentencias condicionales (sentencias if) ms adelante. ++ultra;
Veamos ahora un ejemplo en que se usa % printf ("super = %d, ultra = %d\n", super, ultra);
}
/*segamin*/
/ * c o n v ie r t e s e g u n d o s e n m i n u t o s y s e g u n d o s * /

#d efine SM 60 / * s e g u n d o s e n u n m in u t o * / El programa da como resultado


m ai n( )

{ super = 1, ultra = 1
super = 2, ultra = 2
i n t s e g , m in , r e s t o ; super = 3, ultra = 3
super = 4, ultra = 4
p r in t f ( " C o n v ie r t e segundos en m in utos y segundos\n");
p r in t f ( " I n t r o d u z c a segundos a c o n v e r t ir . \n"); super = 5, ultra = 5
scanf("% d", & seg); /* se le e el numero de segundos */
m in = seg /SM ; /* num ero tru ncad o de m inutos */
resto = seg % S M ; /* num ero de segundos de resto*/
p r in t f ( " % d s e g u n d o s s o n % d m i n u t o s , % d s e g u n d o s . \ n " ,
Qu exageracin! Hemos contado hasta cinco dos veces! Simultnea
seg , m in, resto ); mente! (Si desea contar an ms, simplemente cambie el lmite establecido
en la sentencia while.)
} Para ser honestos, confesemos que hubisemos obtenido exactamente el
mismo resultado con las sentencias:
Un ejemplo de la salida de este programa podra ser el siguiente:
C onvie rte se gun dos e n m inu tos.y se gund os super = super + 1;
I n t r o d u z c a s e g u n d o s a c o n v e r t ir . ultra = ultra + 1;
234 234 seg undo s son 3 m inu tos, 54 seg undo s.

Por cierto, son sentencias bastante compactas. Entonces, para qu mo


Un pequeo (o gran) defecto de este programa interactivo es que se eje lestarse creando no una, sino dos formas abreviadas?
cuta procesando un nico valor de entrada. Podra usted indicar una mane En primer lugar, la forma compacta hace los programas ms elegantes
ra de que el programa solicite repetidamente nuevos nmeros para calcular? y fciles de seguir. Estos operadores dan al programa un cierto glamour que
Trataremos este problema en la siguiente seccin de este captulo, pero nos
no deja de ser agradable a la vista.
agradara mucho saber que ha encontrado su propia solucin por su cuenta.
111
cia while, nuevo incremento en uno de talla y nueva comparacin. El ciclo
se repite hasta que talla excede el valor prefijado. Observe que hemos inicia-
lizado talla a 29.0 en lugar de 30.0 para compensar el incremento previo a
la primera comparacin.

BUCLE while
PRIMERO, INCREMENTA
TALLA A 30

SEGUNDO, EVALUA
EL TEST
TERCERO, EJECUTA
ESTAS SENTENCIAS
CUARTO, VUELVE AL
COMIENZO DE BUCLE

Figura 5.3
Una pasada por el bucle

En segundo lugar, qu es lo que resulta tan excelente en esta representa


cin? Es ms compacta y, lo que es ms importante, consigue ubicar en un
Podemos, por ejemplo, reescribir parte del programa zapatos2 de la si lugar los dos procesos que controlan el bucle. El primero es el test de compa
guiente forma: racin: seguimos con el bucle o no? En este caso, el test consiste en compa
rar la talla del zapato con 48.5. El segundo proceso cambia un elemento del
talla = 30.0; test; en nuestro ejemplo, la propia talla del zapato. Supongamos que olvida
while (talla < 48.5)
{ ra incrementar la talla a cada ciclo del bucle. En ese caso, talla sera siempre
pie = ESCALA*talla + TOPE; menor que 48.5, y el bucle no acabara nunca. El ordenador se limitara a
printf ("%10.1f %16.2f cm. \n", talla, pie); repetir una y otra vez las mismas sentencias impasiblemente, y nos hallara
++talla;
} mos atrapados en lo que en el argot se llama un bucle infinito. La salida,
por dems, sera bastante montona, por lo que imaginamos que acabara
Pero todava no le hemos sacado todo el partido a estos operadores. Se por perder el inters por la misma e intentara detener el ordenador de algn
puede abreviar ms an el fragmento anterior de esta forma: modo (es conveniente tener localizada la tecla de parada de ejecucin, por
si se da el caso). Si tenemos en un mismo sitio el test y el incremento de ndice
talla = 29.0; del bucle resulta ms sencillo recordar la necesidad de incluir un cambio den
while (++talla < 48.5) { tro del bucle.
pie = ESCALA*talla + TOPE; Otra ventaja del operador incremento es que genera un cdigo compila
printf("%10.1f %16.2f cm.\n, talla, pie); do ligeramente ms eficiente, ya que su estructura se asemeja ms al cdigo
} mquina real.
Por ltimo, estos operadores tienen una caracterstica adicional que pue
Aqu se encuentran combinados el proceso de incremento de nuestro n de ser muy til en ciertas situaciones delicadas. Para comprenderla mejor,
dice y la parte comparativa del bucle while; los dos integran la misma expre observemos el siguiente programa.
sin. Este tipo de construccin es tan comn en C que merece un repaso ms
detallado. En primer lugar, cmo funciona? De forma muy sencilla. Se
aumenta en uno el valor de talla y se compara con 48.5. Si es menor, el pro main() {
grama se introduce en el bucle, ejecutndose ste una vez. Vuelta a la senten int a = 1, b = 1;
int amas, masb;

113
amas = a++; /* sufijo */ pero, con sinceridad, nadie le considerar un programador serio de C si
masb = ++b; /* prefijo */
printf(" a amas b masb\n"); se anda con esas expresiones.
printf ("%3d %5d %5d %5d\n", a, amas, b, masb) ; Le sugerimos que preste atencin a los distintos ejemplos de operadores
} incremento que irn apareciendo a lo largo del libro. Cuestinese si hubiese
usado uno u otro, o si las circunstancias aconsejaban la seleccin de uno de
Si lo escribe correctamente, y nosotros recordamos correctamente, debe terminado. Hablando de ejemplos, aqu viene otro ms:
obtener un resultado como ste Duermen alguna vez los ordenadores? Por supuesto que lo hacen, pero
generalmente no nos informan de ello. Este programa revela lo que sucede
a amas b masb en realidad.
2 1 2 2
/* ovejas */
Tanto a como b se han incrementado en 1, como era de esperar. Sin em #define MAX 40
bargo, amas contiene el valor de a antes de que ste fuera cambiado, en tan main()
{
to que masb toma el valor de b tras el incremento. He ah la diferencia pro int cont = 0;
metida entre los modos prefijo y sufijo del operador incremento.
printf("Contare ovejitas para dormirme.\n");
while (++cont < MAX)
amas = a + + sufijo: a cambia despus de ser usado su valor printf ("%d millones de ovejas y aun no me he dormido... \n",
masb = + + b prefijo: b cambia antes de ser usado su valor cont);
printf("%d millones de ovejas y zzzzzzzz.......\n", cont);
}

Ejectelo y compruebe si hace lo que usted cree. Puede, por supuesto,


que el valor de MAX sea diferente en su ordenador. (Por cierto, qu hubie
PRIMERO, incrementa a en uno PRIMERO, multiplica a por dos y ra sucedido si hubisemos empleado la forma sufija del operador de incre
asigna el resultado a q mento en lugar de la prefija?)
DESPUES, multiplica a por dos y
DESPUES, incrementa a en uno
asigna el resultado
aq Decremento: --

Figura 5.4 Existen tambin en C dos operadores decremento que se corresponden


Prefijo y sufijo con los incremento que acabamos de comentar. En ellos se utiliza -- en
lugar de + + .
Cuando se utiliza el operador en solitario en una sentencia, como ego + +;,
--cont; /* forma prefijo del operador decremento */
no importa la modalidad escogida. S importa, y mucho, cuando el operador cont--; /* forma sufijo del operador decremento */
y su operando forman parte de una expresin mayor, tal como la sentencia
de asignacin que acabamos de ver. En una situacin como sa, uno debe
tener bastante claro el resultado que desea obtener. S recordamos la vez an En el ejemplo siguiente, adems de utilizar el operador decremento, de
terior, en que emplebamos + + en un while mostramos claramente que el ordenador es tambin capaz de hacer pinitos
en poesa:
while (++talla < 18.5)

obtuvimos una tabla hasta el nmero 48. Si hubisemos usado la forma sufi /* botellas */
#define MAX 100
jo, talla + + , la tabla habra llegado hasta el 49, ya que talla se incrementa
ra despus de realizada la comparacin, en lugar de antes. main()
Evidentemente, podramos haber conseguido el mismo resultado con {
int cont = MAX + 1;
talla = talla + 1;
while (--cont > 0)
{

115
printf("%d botellas de vino en el estante, %d botellas!\n", La variable n se incrementa nicamente despus de haberse realizado la
cont, cont);
printf("Alguien paso por delante y que fue de ellas?\n"), operacin completa. Lo que indica la precedencia es que el ordenador + +
printf("Solo quedan %d botellas !\n", cont - 1); afecta nicamente a n; tambin nos indica cundo se emplear n para la eva
} luacin de la expresin, pero el momento en que n se incrementa viene deter
}
minado por la propia naturaleza del operador incremento.

La salida comienza as: No se pase de listo


Los operadores incremento pueden acabar por jugarle una mala pasada
100 botellas de vino en el estante, 100 botellas! si pretende hacer todo de una vez antes de dominarlos por completo; con ellos
Alguien paso por delante y que fue de ellas? puede llegar a cometer errores estupendos. Por ejemplo, podra parecerle que
Solo quedan 99 botellas!
99 botellas de vino en el estante, 99 botellas! se puede mejorar el programa que hemos puesto como ejemplo unas pginas
Alguien paso por delante y que fue de ellas? antes, cuya finalidad era imprimir enteros y sus cuadrados. Aprovechando
Solo quedan 98 botellas! nuestra nueva adquisicin se podra reemplazar el bucle while por

while (num < 21)


Sigue insistiendo un rato, y termina: {
printf ("%10d, %10d\n", num, num*num++) ;
}
1 botellas de vino en el estante, 1 botellas!
Alguien paso por delante y que fue de ellas?
Solo quedan 0 botellas! Parece bastante razonable. Imprimimos el nmero num, lo multiplicamos
por s mismo, para obtener un cuadrado, y a continuacin aumentamos num
Aparentemente, nuestro aprendiz de poeta tiene algn problema con los en 1. De hecho, este programa puede incluso funcionar en algunos sistemas,
plurales, pero ya lo enmendaremos cuando veamos operadores condiciona pero no en todos. El problema reside en que cuando printf( ) toma los valo
les en el captulo 7. res a imprimir puede perfectamente empezar por evaluar el ltimo argumen
Por cierto, el operador > significa mayor que. Al igual que < , es un to en primer lugar, e incrementar num antes de capturar el argumento ante
operador de relacin. Lo veremos en profundidad tambin en el captulo 7. rior. En ese caso, en lugar de obtener como resultado

5 25
Precedencia
Los operadores de incremento y decremento tienen una precedencia de podemos encontrarnos con
asociacin muy alta; tan slo son superados por los parntesis. Por ello,
6 25
x*y + + significa (x)*(y + +) y no (x*y) + + ; por otra parte, no puede ser
de otra forma, ya que esta ltima expresin carece de sentido (los operadores
de incremento y decremento afectan a una variable, y el producto x*y no lo El C otorga al compilador libertad absoluta para organizar los argumen
es, auque lo sean sus partes). tos de una funcin como mejor le parezca, incluyendo la decisin del orden
No confunda la precedencia de los operadores con el orden de evalua en que se toman, y la de si la evaluacin de expresiones se realiza al tiempo
cin. Suponga que tenemos o despus. Esta poltica aumenta la eficiencia de los compiladores, pero le
puede crear problemas si utiliza operadores incremento dentro de una funcin.
y = 2; Otra posible fuente de problemas es una sentencia como la siguiente:
n = 3;
proximo = (y + n++)*6; resp = num/2 + 5*(1 + num++);

Qu valor tomar prximo? Si sustituimos variables, De nuevo el error puede surgir porque el compilador no ejecute las cosas
en el orden que nosotros pensbamos. Lo lgico sera pensar que empezara
por num/2, y seguira por la lnea. Pues bien, en realidad puede que calcule
proximo = (2 + 3)*6 = 5*6 = 30
el ltimo trmino antes, realice el incremento y use el nuevo valor de num

117
para evaluar num/2. Muy sencillo, no hay garanta de que se vaya a compor Expresin Valor
tar de una u otra manera.
Por otra parte, es bastante sencillo evitar este tipo de problemas: -4+6 +
c = 3 + 8
5 > 3 1
1. No utilice operadores de incremento o decremento en variables que se 6 + (c = 3 + 8) 17
emplean ms de una vez como argumento de una funcin.
2. No utilice operadores de incremento o decremento en variables que se
empleen ms de una vez en una misma expresin. Qu extraa parece la ltima! Sin embargo, es perfectamente legal en C,
ya que se trata de la suma de dos subexpresiones, cada una de las cuales tiene
un valor.
Expresiones y sentencias
Sentencias
Hasta ahora hemos estado utilizando los trminos expresin y sen Las sentencias son las piezas con que se construye un programa. Un pro
tencia en los captulos anteriores, sin habernos detenido a analizarlos en grama, en realidad, es simplemente un conjunto de sentencias con algo de
profundidad. Es el momento de hacerlo. Las sentencias constituyen las eta puntuacin ortogrfica por medio. Una sentencia, adems, es una instruc
pas bsicas en que se desarrolla un programa en C, y, a su vez, la inmensa cin completa para el ordenador. En C se significan las sentencias acabndo
mayora de sentencias C estn formadas por expresiones. Parece lgico, en
las en punto y coma. Por tanto
consecuencia, empezar por estudiar las expresiones, y as lo haremos aqu.
patas = 4
Expresiones
Se llama expresin a una combinacin de operadores y operandos (recor es una expresin (que, a su vez, puede formar parte de una expresin ma
damos que el operando es aquello sobre lo que acta el operador). La expre yor), mientras que
sin ms simple posible es un operando aislado; a partir de l se pueden ir
construyendo expresiones de mayor o menor complejidad. Ejemplos de ex patas = 4 ;
presiones son:
es una sentencia.
4 Qu es lo que caracteriza a una sentencia? Debe completar una accin.
-6
4+21 La expresin
a*(b + c/d)/20
q = 5*2
x = ++q %3 2+2
q > 3
no es una instruccin completa. Indica al ordenador que sume dos y dos, pe
Como ve, los operandos pueden ser variables, constantes o combinacio ro no le dice qu tiene que hacer con el resultado. Sin embargo
nes de ambos. Algunas expresiones son combinaciones de expresiones meno
hijos =2 + 2 ;
res, que llamamos subexpresiones. Por ejemplo, en el cuarto rengln, c/d
es una subexpresin de la expresin total.
Una propiedad de las expresiones C que conviene destacar es que cada est ordenando a la mquina que almacene el resultado (4) en la direccin
expresin tiene un valor. Para averiguarlo, basta con realizar las operaciones de memoria etiquetada hijos. Una vez ejecutada esta tarea, el ordenador se
en el orden dictado por la precedencia de los operadores. En los ejemplos dirigir a realizar la siguiente que corresponda.
anteriores, algunas expresiones tienen un valor evidente; otras, sin embargo, Hasta ahora nos hemos tropezado con cuatro clases de sentencias. A con
son ms complicadas. Qu hay de las que contienen signos = ? Simplemen tinuacin presentamos un ejemplo en que se utilizan las cuatro.
te tienen el mismo valor que adquiere la variable situada a su izquierda. Y
la expresin q > 0? Las expresiones de relacin toman valor 1 cuando son /* sumame */
ciertas, y valor 0 si son falsas. A continuacin presentamos algunas expresio main() /* calcula la suma de los 20 primeros enteros */
nes y sus valores correspondientes: {
int cont, suma; /* sentencia de declaracion */

119
cont = 0;/*sentencia de asignacin */ La sentencia while pertenece a una clase que a menudo se ha dado en lla
suma = 0; /*dem*/ mar sentencias estructuradas, por poseer una estructura ms compleja que
while (cont++<20) /*sentencia*/ la de una simple sentencia de asignacin. Encontraremos una gran variedad
suma = suma + cont ; /*while*/ de sentencias estructuradas en captulos posteriores.
printf ("suma = %d\n", sum) ; /*sentencia de funcin*/
}
Sentencias compuestas (bloques)
Veamos los distintos tipos. En este momento debe estar ya bastante fami Se denomina sentencia compuesta a un conjunto de dos o ms senten
liarizado con la sentencia de declaracin. Por si acaso, le recordaremos que cias agrupadas y encerradas entre llaves; recibe tambin el nombre de blo
sirve para establecer los nombres y el tipo de variables, y hace que el ordena que. Ya hemos empleado uno en nuestro programa zapatos2 con el fin de
dor reserve posiciones de memoria para cada una de ellas. permitir que el bucle while pudiese realizar varias sentencias. Comprense
La sentencia de asignacin es el caballo de batalla de la mayora de los los dos siguientes fragmentos de programa:
programas; sirve para asignar valores a las variables. Est formada por un
nombre de variable, seguido del operador asignacin ( = ), seguido de una /* fragmento 1 */
expresin, seguida de un punto y coma (ya puede tomar aire). Observe que la indice = O;
sentencia while incluye una sentencia de asignacin como parte de la misma. while (indice++ < 10)
Las sentencias de funcin se encargan de que las funciones hagan lo que sam = 10*indice + 2;
deben hacer. En el ejemplo anterior se llama a la funcin printf( ) para que printf("sam = %d\n", sam);
imprima algunos resultados de salida.
La sentencia while se compone de tres partes distintas. En primer lugar, /* fragmento 2 */
est la palabra clave while; a continuacin, entre parntesis, aparece la con
dicin a comprobar; finalmente, viene la sentencia a ejecutar, si la condicin indice = 0;
se cumple. Dentro del bucle se acepta una sola sentencia, si bien sta puede while (indice++ < 10) {
sam = 10*indice + 2;
ser una sentencia simple, como en el ejemplo, o compuesta, en cuyo caso se printf("sam = %d\n", sam);
utilizarn llaves para marcar los lmites de la misma. Si se trata de una sen }
tencia simple, las llaves no son necesarias. Trataremos las sentencias com
puestas dentro de un momento.
Retorno del bucle

Retorno del bucle

Falso, ir a sentencia siguiente


Observe notacin prefijo; pez se
incrementa antes de la comparacin

Figura 5.5 Figura 5.6


Estructura de un bucle while sencillo Buble while con sentencia compuesta

121
En el fragmento 1, la nica sentencia incluida en el bucle es la sentencia Sentencias:
de asignacin, ya que, en ausencia de llaves, el while se extiende hasta el final Una sentencia es una orden dada al ordenador. Existen sentencias simples y
del siguiente punto y coma. La salida se imprimir una sola vez, al finalizar compuestas.
el bucle. Las sentencias simples terminan con un punto y coma. Por ejemplo:
En el fragmento 2, las llaves hacen que ambas sentencias constituyan una
sentencia compuesta, obtenindose una salida impresa a cada vuelta del bu 1. sentencias de declaracin: int dedos;
cle. A efectos de ste, las dos sentencias se consideran una sola dentro de
2. sentencias de asignacin: dedos = 12;
3. sentencias de llamada a funciones: printf(%d\n, dedos);
la estructura de while.
4. sentencias de control: while (dedos < 20)
dedos = dedos + 2;
5. sentencia nula: ;
DETALLES DE ESTILO Las sentencias compuestas, o bloques, estn formadas por una o ms senten
cias (que, a su vez, pueden ser compuestas) encerradas entre llaves. En el si
Si observamos atentamente los dos fragmentos anteriores, nos percata guiente bucle while se muestra un ejemplo:
remos del modo en que se aprovecha la identacin para reflejar el lmite
del bucle. Al compilador no le afecta para nada esta indentacin; por su par
while (edad < 100)
te, los lmites del bucle se extienden hasta el siguiente punto y coma o hasta
la llave de cierre, segn sea el caso. Para el que est revisando el programa, sapiencia = sapiencia + 1;
sin embargo, este sistema resulta muy til, ya que permite decidir al mo
mento hasta dnde se extiende este bucle.
Acabamos de mostrar un estilo popular de colocacin de llaves en un
bloque. Otra forma bastante comn de hacerlo es la siguiente:

while (indice++ < 10)


{ Conversiones de tipo
sam = 10*indice + 2;
printf ("sam = %d\n", sam); En general, en una sentencia o expresin se emplean variables y constan
}
tes de un solo tipo. Sin embargo, si en un momento dado se mezclan dichos
tipos, el C no se molesta en seguir la pista del eventual error, como hace,
En esta ltima manera se destaca sobre todo el bloque de sentencias que por ejemplo, el PASCAL. En su lugar, utiliza una serie de reglas para efec
constituyen la sentencia compuesta. En la otra se hace mayor nfasis en la
tuar automticamente conversiones de tipo. Esta caracterstica del C puede
pertenencia de ese bloque de sentencias a un bucle while. Por lo que con
cierne al compilador, una vez ms, ambas formas son idnticas.
ser muy til en ocasiones, pero tambin un arma de doble filo, en especial
En resumen, es aconsejable utilizar la indentacin como herramienta que cuando se mezclan tipos inadvertidamente (existe en muchos sistemas UNIX
ayude a destacar la estructura del programa. un programa llamado lint, que comprueba este tipo de colisiones). Es con
veniente, por tanto, tener una idea razonablemente clara del funcionamiento
de las conversiones de tipo.
Las reglas bsicas son las siguientes:
1. En cualquier operacin en que aparezcan dos tipos diferentes se eleva
RESUMEN: EXPRESIONES Y SENTENCIAS la categora del que la tiene menor para igualarla a la del mayor.
Este proceso se conoce con el nombre de promocin.
2. El rango o categora de los tipos, de mayor a menor, es el siguiente:
Expresiones: double, float, long, int, short, char. Los tipos unsigned tienen el mis
Una expresin es una combinacin de operadores y operandos. La expresin
ms simple es una constante o variable sin ms, como 22 o pepito. Ejemplos
mo rango que el tipo a que estn referidos.
de algunas expresiones ms complicadas podran ser: 55 + 22, y 3. En una sentencia de asignacin, el resultado final de los clculos se
vap = 2*(vip + (vup = 4)). reconvierte al tipo de la variable a que estn siendo asignados. El pro
ceso puede, pues, ser una promocin o una prdida de rango,
segn que la variable a asignar sea de categora superior o inferior.

123
Por inexplorados procesos de incubacin y truncado nuestro sistema ha lle
gado a asignar a ch un valor de carcter no imprimible.
La promocin suele ser un proceso bastante tranquilo, que pasa inadver
tido fcilmente. La prdida de rango, por el contrario, puede originar autn
ticas catstrofes. La razn es muy sencilla: el tipo de menor rango puede no En realidad, existe otro proceso de conversin que no hemos menciona
ser lo bastante amplio como para albergar el nmero completo. Una variable do aqu. Con el fin de conservar al mximo la precisin numrica, todas las
char, por ejemplo, puede contener el entero 101, pero no el 22334. variables y constantes float se convierten en double cuando se realizan clcu
En el programa siguiente se puede comprobar el funcionamiento de estas los aritmticos con ellas; as se reduce enormemente el error de redondeo. Por
reglas. supuesto, la respuesta final se reconvierte a float, si ese es el tipo declarado.
No tenemos que preocuparnos para nada de este ltimo tipo de conversin,
/ * conversiones */
pero es agradable saber que el compilador vela por nuestros intereses.
main ()
{ Operador de moldeado
char ch;
int i ; Usualmente, lo mejor es mantenerse apartado de las conversiones de ti
float fl; pos, en especial de las prdidas de rango. No obstante, existen ocasiones en
fl = i = ch = A; /* linea 8 */ que es conveniente, y hasta necesario, hacer alguna conversin. Las conver
printf ("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); siones discutidas hasta ahora se ejecutan automticamente, pero es posible
ch = ch + 1; /* linea 10 */
i = fl + 2*ch; / * linea 11 */
tambin especificar un tipo concreto de conversin que se desee. El proceso
fl = 2.0*ch + i; /* linea 12 * / se denomina moldeado o ahormado, y se realiza anteponiendo a la can
printf ("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); tidad a moldear el nombre del tipo requerido entre parntesis. El conjun
ch =2.0e30 /* linea 14 * /
print f ( "Ahora ch = %c\n", ch) ; to de parntesis y tipo recibe el nombre de operador de moldeado. La for
} ma general de dicho operador es

(tipo)
La ejecucin de conversiones produce el siguiente resultado:

ch = A, i = 65, fl = 65.00 donde se ha de sustituir el verdadero nombre del tipo a imponer en lugar de
ch = b, i = 197, fl = 329.00 la palabra tipo.
Ahora ch = En el siguiente ejemplo se supone que ratones es una variable de tipo int.
La segunda lnea contiene dos moldeadores a tipo int.
Veamos qu ha pasado.
Lneas 8 y 9: el carcter A se almacena como carcter en la variable ch. ratones = 1.6 + 1.7;
ratones = (int) 1.6 + (int) 1.7;
La variable de tipo int i recibe la conversin a entero del carcter, que es 65.
Por ltimo, fl almacena la conversin en punto flotante de 65, que corres
ponde a 65.00. El primer rengln del ejemplo emplea conversin automtica. Primero
Lneas 10 y 13: la variable carcter A se convierte en el entero 65, al se suman 1.6 y 1.7, dando como resultado 3.3. Este valor se trunca a 3, para
cual se le suma 1. Dicho valor se debe asignar de nuevo a la variable ch, por convertirlo en tipo int. En la segunda lnea, tanto 1.6 como 1.7 se convierten
lo que se reconvierte en carcter, que ahora ser el B, y se almacena en ch. en 1 por los operadores que los preceden, por lo que el valor asignado a rato
Lneas 11 y 13: el valor de ch se convierte en entero (66) para poder multi nes ser 2 en este caso.
plicarlo por 2. El resultado, entero, se transforma en flotante para sumarlo Como norma general, no se deben mezclar tipos. De hecho, hay muchos
(132) a fl. El resultado de la suma (197.00) se convierte en entero (tipo int) idiomas que lo prohben. Existen ocasiones, sin embargo, en que puede re
y se almacena en i. sultar til. La filosofa del C se resume en evitar barreras innecesarias al usuario
Lnea 12 y 13: el valor de ch (B) se convierte a punto flotante para po y otorgarle la responsabilidad de no abusar de su propia libertad.
der sumarse a 2.0. El valor de i (197) se convierte tambin en punto flotante
por efecto de la adicin, y el resultado (329.00) se almacena en fl.
Lneas 14 y 15: aqu intentamos un caso de prdida de rango, haciendo
ch igual a un nmero muy grande. El resultado obtenido es bastante pobre.

125
Un programa ejemplo
En la figura 5.7 hemos conseguido elaborar un programa til (sobre to
do, para los que practican footing a nivel internacional) que, adems, sirve
para revisar algunas de las ideas que se han expuesto a lo largo del captulo.
Parece largo, pero, en realidad, los clculos se realizan en seis lneas hacia
el final. El grueso del programa se dedica a una conversacin del ordenador
con el usuario. Se han introducido suficientes comentarios como para que
el programa sea casi autoexplicativo, de manera que pasaremos a estudiarlo;
ms adelante aclararemos algunos detalles.

/* footing */
#defne SM 60 / * segundos por minuto*/
#define SH 3600 / * segundos por hora */
#define MK 0.62137 /* millas por kilmetro */
main ()
{
float distk, distm; /* distancia en kilmetros y en millas */
float veloc; / * velocidad promedio en millas hora */
int min, seg; /* minutos y segundos corriendo * /
int tiempo; /*tiempo de carrera solo en segundos*/
float tporm; /* tiempo en segundos para una milla */
RESUMEN: OPERADORES EN C float mporm, sporm; /*minutos y segundos en una milla * /
printf("Este programa convierte el tiempo de una carrera\n");
Se resumen aqu todos los operadores que hemos estudiado hasta ahora. printf(en tiempo para correr una milla y en promedio de\n");
printf("velocidad en millas por hora.\n");
I. Operador de asignacin printf(" Introduzca la distancia recorrida en km.\n);
= Asigna el valor de su derecha a la variable situada a su izquierda. scanf("%f", &distk);
printf("Ahora indique el tiempo en minutos y segundos.\n);
II. Operadores aritmticos printf("Comience por los minutos.\n");
+ Suma el valor situado a su derecha y el situado a su izquierda. scanf("%d", &min) ;
Resta el valor situado a su derecha del situado a su izquierda. printf("Y ahora los segundos.\n);
scanf("%c", &seg);
Como operador unario, cambia el signo del valor situado a su derecha. tiempo = SM*min + seg; / * pasa el tiempo a segundos */
* Multiplica el valor situado a su derecha por el situado a su izquierda. distm = MK*distk; /*pasa kilmetros a millas */
/ Divide el valor situado a su izquierda entre el situado a su derecha. Si veloc = distm/tiempo*SH; / * millas por segundo
ambos valores son enteros, el resultado se trunca. multiplicado por segundos por hora = millas por hora */
% Calcula el resto de dividir el valor situado a su izquierda entre el situa tporm = (float) tiempo/distmi; /* tiempo por milla */
mporm = (int) tporm / SM; /* calcula minutos truncando */
do a su derecha (aplicable a enteros nicamente). sp o rm = (int) t p o r m % SM; / * calcula resto de segundos * /
+ + Suma 1 a la variable situada a su derecha (modo prefijo) o a su izquier printf ("Ha corrido %1.2f km (%.1.2f millas) en %d min, %d seg\n"
da (modo sufijo). , distk, distm, min, seg);
-- Igual que el anterior, pero restando 1. printf ("Este ritmo corresponde a hacer 1 milla en %d min, ",
mporm) ;
III. Miscelnea printf ("%c seg. \nSu velocidad promedio fue %1.2f mph. \n",
sizeof Entrega el valor, en bytes, del operando situado a su derecha. El ope sporm, veloc);
rando puede se un especificador de tipo, en cuyo caso va entre pa }
rntesis por ejemplo, sizeof (float), o una variable o array, que Figura 5.7
Un programa til para corredores de fondo
se usa sin parntesis sizeof pedro.
(tipo) Operador de moldeado: Convierte el valor que le sigue en el tipo es
pecificado por la(s) palabra(s) clave(s) colocada(s) entre los parnte
sis. Por ejemplo, (float) 9 convierte el entero 9 en el nmero en pun Hemos empleado el mismo sistema que utilizamos en segamin para reali
to flotante 9.0. zar la conversin de tiempo final a minutos y segundos, pero tambin hemos
usado conversiones de tipo. Por qu? Porque necesitbamos argumentos en-
127
b. x = (12 + 6)/2*3;
C. y = x = (2 + 3)/ 4;
teros en la parte de programa dedicada a segundos y minutos, pero la con d. y = 3 + 2* (x = 7/2);
versin de kilmetros a millas hay que hacerla en punto flotante. Hemos usado e. x = (int) 3.8 + 3.3;
el operador de moldeado explcitamente para realizar las conversiones.
A decir verdad, tambin es posible escribir el programa haciendo conver 2. Sospechamos que hay varios errores en el siguiente programa. Podra localizarlos?
siones automticas. De hecho, as lo hemos planteado; hemos realizado otra
versin en la que aprovechamos que tporm es de tipo entero para obligar a
main(){
que el clculo se transformase en un nmero entero. Sin embargo, esta ver
sin slo funcion en uno de nuestros dos sistemas de referencia. Se com int i = 1,
prueba as que el empleo explcito del operador hace que el programa sea ms float n;
claro no slo para el lector, sino tambin para el ordenador.
Veamos un ejemplo de salida. printf("Ojo, que va una ristra de fracciones!\n");
while (i < 30)
Este programa convierte el tiempo de una carrera n = 1/i;
en tiempo para correr una milla y en promedio de printf (" %f", n) ;
velocidad en millas por hora. printf("Eso es todo, amigos!\n");
Introduzca la distancia recorrida en km. }
10. 0
Ohora indique el tiempo en minutos y segundos.
Comience por los minutos.
36 3. Presentamos ahora el primer intento, realizado con la intencin de hacer que se-
Y ahora los segundos. gamin sea interactivo. Este programa no es satisfactorio. Por qu no? Cmo po
23 dra mejorarse?
Ha corrido 10.00 km (6.21 millas) en 36 min, 23 seg
Este ritmo corresponde a hacer 1 milla en 5 min, 51 seg.
Su velocidad promedio fue 10.25 mph. #define SM. 60
main()
{
int seg, min, resto;
Hasta ahora hemos aprendido
printf("Este programa convierte segundos a min y seg.\n");
Cmo utilizar algunos operadores: +, -, *, /, %, + + ,--, (tipo). printf("Introduzca el numero de segundos.\n");
while (seg > 0) {
Qu es un operando: aquello sobre lo que acta el operador. scanf("%d", &seg);
Qu es una expresin: una combinacin de operadores y operandos. min = seg/SM;
Cmo se evala una expresin: siguiendo el orden de precedencia. resto = seg % SM;
Cmo se reconoce una sentencia: por sus puntos y coma. printf("%d seg son %d min, %d seg. \n", seg, min, resto);
printf("Siguiente?\n);
Algunas clases de sentencias: declaracin, asignacin, bucle while, com }
puestas. printf("Adios!\n");
Cmo generar una sentencia compuesta: encerrando una serie de senten }
cias entre llaves { }.
Cmo formar una sentencia while: while (test) sentencia.
Qu sucede cuando se mezclan tipos distintos en la misma expresin: con Respuestas
versin automtica. 1. a. 30
b. 27 (no 3). (12 + 6)/(2*3) s que hubiese dado 3.
c. x = 1, y = 1 (divisin entera)

Cuestiones y respuestas d. x = 3 (divisin entera) e y = 9


e. x = 6, ya que (int)3.8 = 3; 3 + 3.3 = 6.3, que se transforma en 6 al ser x entera.
2. La lnea 3 debe terminar en punto y coma, no en coma.
Cuestiones Lnea 7: la sentencia while introduce en un bucle infinito, ya que i es menor de 30 y seguir
sindolo siempre. Probablemente, lo que se quera indicar es while(i+ + < 30).
1. Suponga que todas las variables son de tipo int. Indique el valor de cada una de Lneas 7-9: a juzgar por la indentacin, se pretenda crear una sentencia compuesta con
las siguientes variables. las lneas 8 y 9, pero, al no haber llaves, el bucle while afectar slo a la sentencia 8. Deben
a. x = (2 + 3) * 6; incluirse, pues, llaves que abarquen estas dos sentencias.
Lnea 8: al ser enteros tanto 1 como i, la divisin realizada es entera, y el resultado ser 1,

129
cuando i valga 1, y 0, para valores mayores. Si se desean sacar resultados distintos de 0
se deber reescribir esta lnea como 1.0/i, lo cual obliga a i a pasar a float (promocin)
antes de efectuarse la operacin.
Linea 9: se ha omitido el carcter nueva lnea ( \ n ) en la sentencia de control. Por tanto,
todos los nmeros se imprimirn en una sola lnea, suponiendo que quepan.
3. El problema principal reside en la relacin entre la sentencia que controla el bucle (es seg
mayor que 0?) y la sentencia scanf( ), que captura el dato en segundos. En concreto, la
primera vez que se realiza el test, el programa no ha tenido oportunidad siquiera de asig
narle un valor a seg, por lo que la comparacin se realizar con cualquier basura que por
casualidad estuviera en esa direccin de memoria. Una posible solucin, aunque terrible
mente poco elegante, podra ser inicializar seg a, digamos, 1, para que disponga de un va
lor con que ejecutar la primera pasada. Empero, esta solucin (ms bien parche) lo nico
que hace es destapar otro problema. En efecto, cuando se desee terminar el programa in
troduciendo el valor 0, el bucle no se percata de ello hasta despus de haber realizado la
operacin e imprimido el resultado de 0 segundos. Lo que necesitamos es una sentencia
scanf( ) que est colocada justo antes de la comparacin del while. Observe la modifica
cin de la parte central del programa que se ofrece a continuacin:
scanf("%d", &seg);
while (seg > O) {
min = seg/SM;
resto = seg % SM;
printf ("%d seg son %d min, %d seg. \n", seg, min, resto) ;
print f("Siguiente?\n");
scanf ( "%/d ", &seg) ;
}
La primera vez que entre el programa se emplear el valor sacado de la sentencia
scanf( ) externa al bucle. El resto de valores se tomarn en el scanf( ) colocado al
final del bucle, es decir, justo antes de la evaluacin del while siguiente. Esta es una
forma muy comn de encarar problemas semejantes al expuesto aqu.

Ejercicios
Presentamos ahora una serie de problemas para los que no se indica la respuesta.
La forma de averiguar si su solucin es correcta, o no, es teclearla en un ordenador
y ejecutarla como programa.
1. Modifique nuestro programa, que calculaba la suma de los 20 primeros enteros
(si lo prefiere, imagine que el programa calcula la cantidad de dinero que recibira
usted si le pagasen 1 el primer da, 2 el segundo, 3 el tercero, etc. Aada detrs
la unidad monetaria que prefiera). La modificacin a introducir consiste en que
el programa pregunte interactivamente el lmite superior del clculo, es decir, sus
tituya el nmero 20 por una variable a introducir desde teclado.
2. Modifique de nuevo el programa, de modo que calcule ahora la suma de los cua
drados de nmeros enteros (o, si lo prefiere, calcule cunto dinero obtendra ga
nando 1 el primer da, 4 el segundo, 9 el tercero, y as sucesivamente. Esperamos
que se encuentre satisfecho con el cambio). En C no existe ninguna funcin para
elevar al cuadrado, pero se puede utilizar la curiosa propiedad de que el cuadrado
de un nmero n es precisamente n * n.
3. Modifique el programa una vez ms, de tal forma que, cuando finalice un clcu
lo, solicite una nueva cantidad para repetir el proceso. Prepare una salida del pro
grama cuando se introduzca como dato el 0. (Clave: utilice un bucle dentro de
otro; vea tambin el problema 3 y su respuesta.)

30
6
Funciones de
entrada/salida
y reenvo
En este captulo encontrar:

E/S de un solo carcter: getchar ( ) y putchar ( )


Buffers
Otra etapa
Lectura de una sola lnea
Lectura de un fichero
Reenvo
UNIX
Reenvo de salidas
Reenvo de entradas
Reenvo combinado
Sistemas no UNIX
Comentario
E/S dependiente de sistema: puertos de E/S 8086/8088
Utilizacin de un puerto
Resumen
Vamos a tantear la potencia oculta de nuestro ordenador
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

133
Funciones de entrada/ Por otra parte, sera realmente un beneficio para todos si existiesen fun
ciones E/S estndar en todos los sistemas; as se podran escribir programas
transportables que se adaptasen fcilmente de un sistema a otro. Existen

salida y reenvo en C muchas funciones E/S de este tipo, tales como printf( ) y scanf( ). Den
tro de este tipo se incluyen tambin getchar( ) y putchar( ), funciones que
estudiaremos a continuacin.
Estas dos funciones realizan la entrada y salida de un solo carcter a la
vez. Al principio pudiera parecer una manera bastante estpida de hacer las
CONCEPTOS cosas; despus de todo, tanto usted como yo somos capaces de leer fcilmen
te agrupamientos mayores de un solo carcter. Sin embargo, este mtodo se
adapta mejor a las habilidades propias del ordenador; de hecho, constituye el
Entrada y salida (E/S) ncleo central de la mayora de programas que tratan con textos, es decir, con
getchar( ) y putchar( ) palabras ordinarias. Veremos a continuacin cmo se desenvuelven estas dos
Final de fichero (EOF, end-of-file) funciones sencillas en programas que cuentan caracteres o leen y copian fi
Reenvo: < y > cheros. De paso, aprenderemos algo acerca de los buffers, ecos y reenvo.
E/S dependiente de sistema
Bucles de retardo
E/S de un solo carcter: getchar( ) y putchar( )
La funcin getchar( ) toma un solo carcter (de ah, su nombre) del te
clado y lo entrega a un programa en ejecucin. La funcin putchar( ), por
su parte, toma una carcter de un programa en ejecucin y lo enva a la pan-

Las palabras entrada y salida tienen ms de un significado en la jerga


informtica. Podemos estar hablando de perifricos de entrada y salida, co
mo teclados, unidades de disco, impresoras de matriz de punto, etc. Tam
bin podemos referirnos a los datos que se estn utilizando en entrada y sali
da. Por ltimo, podemos indicar con este trmino las funciones que llevan
a cabo las entradas y salidas. En este captulo nos dedicaremos principalmente
a discutir estas funciones de entrada y salida (E/S, para abreviar), pero tam
bin nos referiremos de cuando en cuando a los otros significados de E/S.
Se llaman funciones de E/S a aquellas que transportan datos hacia y des
de nuestro programa. Hasta ahora hemos utilizado dos funciones de este ti
po: printf( ) y scanf( ). Nos dedicaremos ahora a contemplar algunas de las
dems opciones que ofrece el C.
Las funciones de entrada y salida no forman parte de la definicin del
C; su desarrollo queda a expensas de aquel que implemente el lenguaje en
una determinada mquina. Si usted se dedica a crear un compilador C, po
dr poner cualesquiera funciones de entrada/salida que prefiera. Si el siste
ma para el que est diseando el compilador tiene alguna caracterstica espe
cial, como la organizacin E/S de puertos del microprocesador 8086, podr
construir funciones E/S especiales que utilicen dicha caracterstica. Veremos
un ejemplo concreto de este sistema al final del captulo.
Figura 6.1
gerchar ( ) y putchar( ): los caballos de tiro en procesadores de textos

135
talla. Presentamos a continuacin un ejemplo muy sencillo. Todo lo que hace putchar(S); /* observe que en constantes de */
es tomar un carcter del teclado e imprimirlo en pantalla. Iremos modificando putchar(\n); /* caracteres se usan apostrofos */
poco a poco este programa hasta hacerlo de utilidad en una serie variada de putchar(\007);
putchar(ch); /* ch es una variable de tipo char */
aplicaciones. Ya iremos describiendo estas ltimas ms adelante; contntese putchar(getchar ());
por ahora con nuestra humilde versin de comienzo.
Podemos utilizar este ltimo ejemplo para reescribir nuestro programa
/* getput1 */ como
#include (stdio.h)
main()
{ #include (stdio.h)
char ch; main()
{
ch = getchar( ); /* linea 1 */ putehar(getchar()) ;
putchar(ch); /* linea 2 */ }
}

Esta es una forma totalmente compacta que no utiliza variables. Es ms


La mayora de los sistemas contienen las definiciones de getchar y putchar eficiente que la anterior, aunque quiz menos clara.
en el fichero de sistema stdio.h; esa es la razn por la que hemos incluido Una vez visto cmo trabajan estas dos funciones, nos ocuparemos de los
dicho fichero en el programa. La ejecucin de este programa produce salidas buffers.
como:

g [enter] Buffers
g
Cuando ejecute este programa (cualquiera de las dos versiones), en algu
o, posiblemente, como nos sistemas puede darse el caso de que la letra introducida se repita inme
diatamente. Se dice entonces que el ordenador est dando un eco de la
gg entrada. En otros sistemas, por el contrario, no sucede nada hasta que se pulsa
la tecla [enter]. El primer caso es un ejemplo de entrada "sin buffer" (o di
recta), lo que significa que el carcter tecleado est disponible inmediata
El smbolo [enter] es nuestra forma de indicar que se ha pulsado la tecla mente para el programa que ha detenido momentneamente su ejecucin. En
[enter]. En ambos casos la primera g ha sido la tecleada por usted, y la se el segundo caso, la entrada contiene un buffer que se suele considerar una
gunda es la enviada por el ordenador. especie de depsito, donde se almacenan los caracteres tecleados; all ir a
Que el resultado sea uno u otro depende de si su sistema tiene entrada buscarlos el ordenador. La palabra buffer, comn en el argot de ordenado
con buffer o no. Si ha tenido que pulsar la tecla [enter] antes de obtener la res, hace referencia a una zona de memoria que se utiliza para almacenamiento
respuesta, entonces su sistema tiene buffer. Nos dedicaremos por ahora a get-
temporal de datos, generalmente en entradas y salidas. Al pulsar la tecla [en
char( ) y putchar( ); posteriormente nos sumergiremos en el mundo de los
buffers. ter] se da va libre al programa para que capture el carcter o bloque de ca
La funcin getchar( ) carece de argumento (es decir, no hay nada entre racteres que se haya introducido. En nuestro caso particular, nicamente se
los parntesis). Simplemente captura el siguiente carcter y se otorga a s misma capturar el primer carcter, ya que esa es la misin de la funcin getchar( ).
el valor de dicho carcter. Por ejemplo, si captura la letra Q, la propia fun Por ejemplo, en un sistema con buffer se pueden obtener resultados como
cin toma el valor Q. La lnea 1 asigna entonces el valor de getchar( ) a la ste de nuestro programa anterior:
variable ch.
Por el contrario, la funcin putchar( ) s posee argumento. Entre los pa He escrito esta linea. [enter]
H
rntesis se deber colocar aquel carcter que desee imprimir. El argumento
puede ser un nico carcter (incluyendo las secuencias de escape del captu
lo 3) o una variable o funcin cuyo valor sea un nico carcter. Todos los Por su parte, el sistema sin buffer devolvera la H tan pronto como se
ejemplos siguientes son usos vlidos de putchar( ); hubiese tecleado. La entrada-salida tendra un aspecto como:
HHe escrito esta linea.

137
La segunda H procede del eco del programa. En cualquiera de los casos, Otra etapa
el programa procesa nicamente un carcter, ya que se ha llamado a la fun
cin getchar( ) una sola vez.
Vamos a intentar ahora algo ms ambicioso que la lectura y escritura de
un solo carcter. Supongamos que tenemos un gran nmero de caracteres.
Deseamos que el programa se detenga en algn momento, de modo que asig
naremos a un determinado carcter la orden de stop. En el ejemplo siguiente
se utiliza un asterisco (*) con esta finalidad. La repeticin continua del pro
grama queda encomendada a un bucle while.
/* getput2 */
/* Este programa captura e imprime caracteres
hasta que se detiene */
#include <stdio.h>
#define STOP '*' /* Da a * el nombre simbolico STOP */
main()
{
char ch;
ch = getchar(); /* linea 9 */
while ( ch != STOP ) { /* linea 10*/
putchar(ch); /* linea 1 1 * /
ch = getchar(); /* linea 12*/
}
}

Hemos empleado aqu la estructura de programa que discutamos en la


cuestin 3 del captulo 5. Al ejecutar por primera vez, putchar( ) toma su
argumento de la lnea 9; en las dems repeticiones se toma de la lnea 12,
hasta terminar el bucle. Hemos introducido tambin un nuevo operador de
relacin, ! = , que significa no igual a. Por tanto, la sentencia while per
Figura 6.2 manecer realizando bucles de lectura e impresin de caracteres hasta que
Entradas con y sin buffer el programa encuentre el carcter STOP. Podramos haber omitido la sen
tencia #define y emplear el * en la sentencia while, pero creemos que de esta
forma el significado del programa es ms obvio.
En lugar de intentar ejecutar este maravilloso programa en su ordenador,
Por qu se utilizan buffers? En primer lugar, consumen mucho menos eche un vistazo a la siguiente versin. Realiza exactamente la misma tarea,
tiempo, ya que transmiten grupos de caracteres, en forma de bloques, en lu pero el estilo de la misma es ms C.
gar de enviarlos uno a uno. Adems, si se equivoca al teclear, puede corregir
con la tecla pertinente del teclado el error. Cuando se pulsa finalmente [en- /* getput3 */
ter], se transmitir la versin correcta. #include (stdio.h)
Por otra parte, las entradas sin buffer pueden resultar adecuadas en algu #define STOP'*'
nos programas interactivos. En un procesador de textos, por ejemplo, puede main()
{
ser de utilidad que se ejecuten las rdenes en el momento en que se pulse la char ch;
tecla correspondiente. Como ven, ambos sistemas tienen sus ventajas y su
propio campo de aplicacin. while ( (ch = getchar() != STOP) /* linea 8 */
putchar(ch) ;
Cul tenemos nosotros? Se puede averiguar con facilidad ejecutando el }
programa anterior y observando el comportamiento de la salida en pantalla.
En algunos compiladores C se ofrecen entradas y con sin buffer, a eleccin. La lnea 8 de esta maravilla sustituye a las lneas 9, 10 y 12 de getput2.
En nuestro sistema, por ejemplo, existe la funcin getchar( ), para entrada Cmo es posible? En principio ejecuta el contenido del parntesis ms interno:
con buffer, y getch( ), para entrada directa.
ch = getchar()

139
Pasemos ahora a un sistema con buffer. Esta vez no suceder nada hasta
que corresponde a una expresin. Su efecto es activar getchar( ) y asignar que se pulse la tecla [enter]. Una posible salida podra ser:
su valor a ch. Con esto quedan sustituidas las lneas 9 y 12 de getput2. Para
continuar, recuerde que una expresin tiene siempre un valor, y que una ex A ver si esto anda. * Humm, no lo se. [enter]
presin de asignacin tiene el mismo valor global que la variable situada a A ver si esto anda.
la izquierda del signo =. Por tanto, el valor de (ch = getchar( )) es simple
mente el valor de ch, y as Al programa se enva la primera lnea completa. El programa lee enton
ces el rengln a razn de un carcter por bucle, y los va imprimiendo hasta
(ch = getchar()) != STOP
que encuentra el asterisco *.
Hagamos ahora que el programa sea un poco ms til. Vamos a ir con
tiene el mismo efecto que
tando los caracteres conforme se van leyendo; todo lo que tenemos que ha
ch != STOP cer son unos pequeos cambios.

Con esto se realiza la tarea que getput2 haca en la lnea 10. Este tipo de /* cuentachl */
#define STOP *
construccin (combinacin de asignacin y comparacin) es muy comn en C. main()
{
char ch;
int cuenta = 0 /* inicializa cuenta de caracteres a 0 */

while ((ch = getchar( )) != STOP) {


putchar(ch);
cuenta++; /* suma 1 a cuenta */
}
printf ( "\nHe leido un total de %d caracteres. \n"
cuenta);
}

Se puede eliminar la lnea putchar( ) si deseamos nicamente contar los


caracteres sin recibir un eco de los mismos. Con un programa tan reducido
Figura 6.3
Examen de la condicin en el bucle while como ste, y cambios menores, podemos disponer de programas que cuenten
lneas y palabras. En el siguiente captulo aprenderemos los detalles necesarios.

Lectura de una sola lnea


Al igual que en nuestro ejemplo anterior, cuando hacamos while ( + + talla Entretanto, veamos qu otras mejoras podemos hacer con las herramien
< 48.5), esta forma tiene la ventaja de colocar en el mismo lugar la condi tas de que disponemos por el momento. Por ejemplo, se puede cambiar con
cin de bucle y la accin que altera el ndice del mismo. La estructura se ase facilidad la seal de stop. Qu podemos emplear en lugar del asterisco para
meja bastante al proceso mental que puede seguir uno mismo: Quiero leer mejorar el sistema? Una posibilidad es utilizar el carcter nueva lnea (\ n).
un carcter, observarlo y decidir qu hacer a continuacin. Para ello, lo nico que hay que hacer es redefinir STOP.
Volvamos ahora al programa y ejecutmoslo. Si el sistema que est em
pleando no tiene buffer, el resultado podra ser algo as: #define STOP \n
AA vveerr ssii eessttoo aannddaa..*Creo que si.
Cul ser ahora el efecto obtenido? Bueno, el carcter nueva lnea se
transmite cuando se pulsa la tecla [enter], de modo que el programa as re
Todos los caracteres que aparecen en pantalla antes de la seal STOP (el
dactado funcionar lnea por lnea en la entrada. Por ejemplo, supongamos
asterisco) son reflejados en eco segn se estn tecleando. Incluso se duplican
los espacios. Una vez pulsada la seal STOP, sin embargo, el programa se que realizamos este cambio en cuentach1, y tecleamos la siguiente entrada:
detiene, y lo que se escribe aparece en pantalla sin eco. Que bonita es Marbella en verano,[enter]

141
La respuesta sera un fichero y comienza el siguiente resulta imprescindible disponer de un ca
rcter especial que marque el fin de fichero. Necesitan, por tanto, un carc
Que bonita es Marbella en verano, ter que no pueda aparecer en medio del fichero, al igual que nosotros necesi
He leido un total de 33 caracteres. tbamos algo que no apareciese en medio de nuestra entrada. La solucin
es disponer de un carcter llamado Fin-De-Fichero, que se suele simbolizar
En la sentencia printf( ) se ha incluido un \ n al comienzo y otro al final. como EOF (End-Of-File). La seleccin concreta del carcter EOF depen
La misin del primero es evitar que la respuesta del ordenador salga pegada de del sistema; de hecho, puede consistir en ms de un carcter; pero, en cual
a la coma del final del mensaje. quier caso, dicho carcter existe, y su compilador C conoce cul es el carc
El total indicado no incluye la tecla [enter], ya que el contador est situa ter EOF de su propio sistema.
do dentro del bucle. Cmo se puede utilizar un carcter EOF? Generalmente est definido
Disponemos ahora de un programa que lee una lnea. Dependiendo de en el fichero < stdio.h > . Una presentacin bastante comn es
las sentencias que se incluyen dentro del bucle while, el programa puede ha
cer un eco de la lnea, contar los caracteres de la misma, o ambas cosas a #define EOF (-1)
la vez. Se empiezan a entrever ciertas aplicaciones para este programa, pro
bablemente formando parte de un programa mayor. De todos modos, sera lo que permite utilizar expresiones como
agradable disponer de un programa que leyese trozos mayores de texto, in
cluso un fichero de datos completo. Para ello, lo nico que se necesita es ele while ((ch = getchar()) != EOF)
gir adecuadamente el carcter de STOP.
en sus programas. Podemos, por tanto, reescribir nuestro programa bsico
Lectura de un fichero de lectura y eco de la siguiente forma:
Cul sera el STOP ideal? Debe ser algo que no aparezca normalmente
en el texto; de esta forma se evita que el programa salte accidentalmente en /* getput4 * /
la mitad de la entrada, detenindose antes de lo que nosotros hubisemos de #include <stdio.h>
seado. main ()
{
Este tipo de problema no es nuevo, y, afortunadamente, est ya resuelto int ch;
por los tcnicos que disean sistemas de ordenadores. En realidad, su pro
blema es ligeramente diferente, pero podemos utilizar la misma solucin que while ( (ch = getchar()) != EOF)
ellos aplican. En su caso, el problema se concentra en el control de fiche putchar(ch);
ros. Se llama fichero a un bloque de memoria en el cual se almacena infor }
macin. Normalmente, un fichero se guarda en algn tipo de memoria per
manente, como disco flexible, disco duro o cinta. Para saber dnde termina
Obsrvense los siguientes puntos:

Prosa: El tiempo y el espacio 1. No tenemos que denifir EOF, ya que de eso se encarga stdio.h.
no me hacen olvidar
que soy humano
2. No tenemos que preocuparnos del valor real del carcter EOF, ya que
en la propia sentencia #define de stdio.h se usa la representacin sim
blica EOF.
3. Hemos cambiado ch de tipo; ahora es de tipo int en lugar de tipo char.
Prosa en un
fichero:
Lo hemos hecho porque char representa variables como enteros sin signo
en el rango 0 a 255, en tanto que el carcter EOF puede tener el
valor numrico 1. Tal valor sera imposible de alcanzar con una va
El carcter EOF seala riable char pero no con int. Por fortuna, getchar( ) es tambin de tipo
el final de
fichero
int, de manera que puede leer el carcter EOF.
4. El hecho de que ch sea ahora un entero no altera la funcin putchar( ).
Figura 6.4 Dicha funcin sigue imprimiendo los caracteres equivalentes.
Un fichero con EOF 5. Para utilizar este programa con entrada por teclado, necesitamos un
sistema de enviar el carcter EOF. No, no sirve teclear las letras E-O-

143
resultado sera que el contenido del fichero se imprimira en pantalla, dete
F ni tampoco teclear 1 (1 es el equivalente al cdigo ASCII del nindose cuando se alcanzase el final del fichero, ya que ah se encuentra un
carcter, no el propio carcter). En su lugar deberemos encontrar la carcter EOF. Podemos suponer, por el contrario, que descubrimos una for
tecla o combinacin de teclas que emplee nuestro sistema en particu ma de enviar salidas de programas a un fichero. Podramos entonces teclear
lar. La mayor parte de sistemas UNIX, por ejemplo, interpretan cualquier cosa en pantalla, y utilizar getput4 para almacenar lo que hemos
[control-d] (es decir, pulsar la tecla [d] manteniendo apretada la tecla tecleado. Supongamos, finalmente, que podemos realizar ambas operacio
[control]) como carcter EOF. Muchos microordenadores utilizan nes simultneamente, es decir, obtener entradas de un fichero a getput4 y en
[control-z] con la misma finalidad. viar las mismas como salida a otro fichero. En este caso, podramos utilizar
getput4 para copiar ficheros. Por consiguiente, nuestro pequeo programa
Un ejemplo de ejecucin de getput4 en un sistema con buffer podra ser: es un potencial lector, creador y copiador de ficheros: No est mal para su
Ya viene el cortejo! tamao! La clave de todo el proceso consiste en controlar el flujo de entrada
Ya viene el cortejo! y salida, que es lo que trataremos a continuacin.
Ya se oyen los claros clarines!
Ya se oyen los claros clarines!
Ruben Dario
Ruben Dario Reenvo
[control-z]
Como hemos dicho en un principio, en los procesos de entrada y salida
se encuentran involucrados datos, funciones y perifricos. Sea un programa
Cada vez que se pulsa [enter], los caracteres almacenados en el buffer se como getput4. En l se utiliza la funcin de entrada getchar( ). El perifrico
procesan, y se obtiene una copia de la lnea en pantalla. Esta accin contina de entrada (suponemos) es un teclado, y los datos de entrada son caracteres
hasta que se teclea el carcter EOF. individuales. Podra interesarnos mantener la misma funcin de entrada y
Detengmonos un momento y pensemos las posibilidades que puede ofrecer el mismo tipo de datos, pero cambiar el lugar donde el programa se dirige
getput4. Este programa copia en pantalla cualquier cosa que se introduzca a tomar los datos. Una buena pregunta a formular, y a responder, sera: C
como entrada. Supongamos que conseguimos introducir un fichero en l. El mo se las arregla un programa para saber dnde tiene que buscar su dato
de entrada?
Por defecto, un programa en C se dirige siempre a la entrada estndar
como fuente de entrada. Dicha entrada estndar puede ser cualquiera que
haya sido dispuesta de esta forma para leer datos e introducirlos en el orde
nador. As, puede referirse a una cinta magntica, tarjetas perforadas, un
teletipo, o, como supondremos en adelante, una terminal de video. Sin em
bargo, un ordenador moderno es una herramienta bastante sugestionable, a
la que se puede convencer con facilidad para que busque en otro sitio. En con
creto, se puede indicar al programa que busque sus entradas en un fichero,
en lugar de hacerlo en el teclado.
Hay dos maneras de hacer que un programa trabaje con ficheros. La pri
mera es utilizar funciones especiales que abran ficheros, o los cierren, lean,
escriban, etc. An no estamos preparados para esta va. Lo que s podremos
hacer es usar un sistema mucho ms sencillo; se trata de utilizar un programa
diseado para trabajar con teclado y pantalla, pero reenviando las entra
das y salidas por canales diferentes; por ejemplo, hacia y desde fichero. Esta
segunda opcin est ms limitada que la primera en algunos aspectos, pero
es mucho ms sencilla de utilizar.
El reenvo, tambin llamado redireccionamiento, es una caracterstica del
sistema operativo UNIX, no del C en s mismo; sin embargo, resulta tan til
que, cuando se ha implementado C en otros sistemas, a menudo se ha inclui
do en el paquete alguna forma de reenvo; adems, existe el reenvo en mu
chos de los sistemas operativos ms modernos, incluyendo MS-DOS 2; por

145
Reenvo de entrada
tanto, aunque usted no sea usuario de un sistema UNIX, tiene bastantes po
Supongamos ahora (esperamos que su mquina de suponer no se haya
sibilidades de disponer de alguna forma de reenvo. Discutiremos, en princi fundido todava) que, en realidad, lo que deseamos es enviar nuestro texto
pio, el reenvo en UNIX, y a continuacin el reenvo en otros sistemas. a un fichero llamado mifrase. Para ello se debe ejecutar

getput4 >mifrase

y a continuacin comenzar a teclear. El smbolo > es otro operador UNIX


Reenvo de salidas de reenvo. Genera un nuevo fichero llamado mifrase y enva al mismo la
Supongamos que hemos compilado nuestro programa getput4 y hemos salida de getput4 (tal salida es una copia de los caracteres que se teclean).
colocado la versin ejecutable del mismo en un fichero llamado getput4. Pa Si ya existiese un fichero con el mismo nombre mifrase, normalmente se bo
ra ejecutar el programa, lo nico que hay que hacer es teclear el nombre rrar y sustituir por el actual. (Algunos sistemas UNIX permiten la opcin
del fichero de proteger ficheros ya existentes.) Lo nico que aparece en pantalla son las
letras tecleadas por usted, mientras que la copia se manda al fichero. Para
getput4
terminar el programa, deber introducir un carcter EOF, generalmente un
[control-d] en sistemas UNIX. Haga un intento con esta nueva construccin.
y el programa se ejecutar como se describi anteriormente, tomando la en Si no se le ocurre nada que escribir, copie simplemente el ejemplo siguiente.
trada desde teclado. Supongamos ahora que deseamos emplear el programa En l se muestra una salida UNIX, en la cual suponemos que los comandos
con un fichero de texto llamado palabra. (Se llama fichero de texto a aquel van precedidos por un %. No olvide separar las lneas con un [return], de
que contiene texto, es decir, datos almacenados en forma de caracteres. Pue manera que se pueda enviar el buffer al programa.
de tratarse de un ensayo literario o de un programa C, por poner un ejem
plo; sin embargo, un fichero que contiene instrucciones en lenguaje mqui
na, como el que soporta la versin ejecutable del programa, no es un fichero % getput4 >mifrase
de texto. Ya que nuestro programa trabaja con caracteres, debe ser utilizado No tendra ningun problema en recordar cual es el
operador de reenvio que entra y cual es el que sale.
en fichero de texto.) Para ello, introduciremos, en lugar de la anterior, la si Recuerde simplemente que ambos apuntan en el sentido
guiente orden: en que fluye la informacion. Piense en este sistema
como si fuese un embudo. [control-d]
getput4 <palabras %

El smbolo < es un operador UNIX de reenvo. Su misin es hacer que


el contenido del fichero palabras se canalice hacia getput4. El programa getput4 Una vez que se detecta el [control-d], el programa finaliza y devuelve el
ignora (o al menos no le importa) si la entrada de datos procede de un fiche control al sistema operativo UNIX. Esta operacin queda indicada por la apa
ro o del teclado. Todo lo que sabe es que le estn llegando una serie de carac ricin de otro nuevo smbolo %. Cmo podemos saber que el programa ha
teres, de modo que se limita a leerlos e imprimirlos uno a uno hasta que en funcionado? Existe un comando en UNIX, el ls, que produce un listado de
cuentra un EOF. En UNIX, los ficheros y los perifricos de E/S se tratan los nombres de ficheros; si lo ejecuta, comprobar que existe ahora un nue
de igual forma, por lo que nuestro fichero es ahora el perifrico de E/S vo fichero llamado mifrase. Tambin se puede utilizar el UNIX cat para com
del sistema. Intntelo! probar el contenido, o bien usar de nuevo getput4, pero enviando esta vez ,
el fichero al programa.
getput4 <palabras
Yo no se lo que busco eternamente
en la tierra, en el aire y en el cielo %getput4 <mifrase
yo no se lo que busco; pero es algo No tendra ningun problema en recordar cual es el
que perdi no se cuando y que no encuentro. operador de reenvio que entra y cual es el que sale.
Recuerde simplemente que ambos apuntan en el sentido
en que fluye la informacion. Piense en este sistema
Lo que desde luego no garantizamos es que en el fichero por usted em como si fuese un embudo.
%
pleado surja, como en ste, Rosala de Castro.

147
Comentaremos ahora las reglas que gobiernan el empleo de los dos ope
Reenvo combinado radores de reenvo < y > .
Aun a sabiendas de que corremos el riesgo de fundir definitivamente su
neurona de suponer, imaginemos un nuevo caso. En esta ocasin deseamos 1. Un operador de reenvo conecta un programa ejecutable, incluyendo
hacer una copia del fichero mifrase, y llamarla guardafrase. Esta operacin comandos estndar UNIX, con un fichero. No pueden ser utilizados
se realiza con el comando para conectar un fichero con otro o un programa con otro.
2. El nombre del fichero ejecutable debe estar a la izquierda del opera
getput4 <mifrase >guardafrase dor, y el nombre del fichero, a su derecha.
3. No se puede utilizar ms de un fichero como entrada ni enviar la sali
y nuestros deseos se ven cumplidos. El comando
da a ms de un fichero cuando se emplean estos operadores.
getput4 >guardafrase <mifrase 4. Normalmente, es opcional colocar espacios entre los nombres y los ope
radores. Existen algunas excepciones cuando se utilizan algunos carac
es equivalente al anterior, ya que el orden en que se especifican las operacio teres que tienen algn significado especial en el entorno UNIX. As, po
nes de reenvo es indiferente. dramos haber usado getput4 < palabras o getput4 < palabras, ms
No emplee el mismo fichero como entrada y salida en un solo comando. elegante.
getput4 <mifrase > mifrase ERROR
Hasta ahora hemos dado ejemplos correctos. A continuacin presenta
La razn por la que no se puede hacer es que > mifrase hace que el fichero mos algunos incorrectos, en los que se ha supuesto que suma y cuenta son
mifrase original se borre incluso antes de ser utilizado como entrada. programas ejecutables, y pez y estrella son ficheros de texto.

pez > estrella Se viola la regla 1


suma < cuenta Se viola la regla 1
estrella > cuenta Se viola la regla 2
suma <pez < estrella Se viola la regla 3
cuenta > estrella pez Se viola la regla 3

Existe tambin en UNIX el operador > > , que permite aadir datos al
final de un fichero ya existente, y el operador tubera (pipe, |), que per
mite conectar la salida de un programa con la entrada de un segundo progra
ma. Si desea mayor informacin al respecto, consulte un libro de UNIX. (Hay
uno excelente en esta misma coleccin.)
En el siguiente ejemplo presentamos un programa muy sencillo de cifra
do criptogrfico. Si reformamos ligeramente getput, obtenemos:

/* cod ig o */
/* este programa reemplaza cada caracter del texto */
/* por el siguiente en la secuencia ASCII */
#inclu de <std io .h>
m a in ()
{
int ch;

while ( (ch = getchar()) != EOF)


putchar(ch + 1);
}

La funcin putchar( ) convierte el entero ch +1 en el carcter corres


Figura 6.5 pondiente.
Reenvo combinado
149
Compilemos el programa y almacenemos la versin ejecutable en un fi observado, cinco de ellas contienen los smbolos < y > para reenvo. El reenvo
chero llamado cdigo. A continuacin, introduzcamos en otro fichero lla general o por compilador se diferencia del reenvo UNIX en dos aspectos:
mado original el texto siguiente (empleando el editor del sistema o usando
getput4, como antes): 1. Funciona slo con programas C, mientras que el reenvo UNIX fun
ciona en cualquier programa ejecutable.
Para mejor comprensin 2. Se debe colocar un espacio entre el nombre del programa y el opera
se debe escribir sin errores. dor, y no puede existir espacio entre el operador y el nombre del fiche
ro. Un ejemplo correcto de esta notacin sera:
Si a continuacin tecleamos el comando
getput4 <palabras
codigo <original
Comentarios
el resultado podra ser algo as: El reenvo es una herramienta sencilla, pero potente. Permite transfor
mar nuestro pequeo programa getput4 en un productor, lector y copiador
de ficheros. Este enfoque es un ejemplo de la filosofa del C (y del UNIX)
de creacin de herramientas simples que pueden combinarse en multitud de
La P se ha transformado en una Q, la a en una b, etc. Observar tambin
formas para realizar tareas muy diferentes.
un par de detalles llamativos. Por ejemplo, los espacios han sido sustituidos
por signos de admiracin. Esto le recordar que el espacio es un carcter co
mo los dems, por derecho propio. Adems, las dos lneas se han transfor
mado en una. Por qu? Porque original contena un carcter nueva lnea
al final de la primera lnea; dicho carcter indica al ordenador que salte a RESUMEN: COMO REENVIAR ENTRADAS Y SALIDAS
la lnea siguiente. Pero este carcter tambin ha sido alterado; en nuestro sis
tema aparece en su lugar un ^K, que es otra forma de expresar [control-k], La mayora de sistemas C emplean reenvo, bien por estar incluidos dentro del
el cual, evidentemente, no hace comenzar una nueva lnea. Si deseramos un sistema operativo, y ser vlidos para cualquier programa, bien por estar im-
programa de cifrado que conservase la estructura de lneas original debera plementados dentro del compilador C, y nicamente para programas C. En
mos cambiar todos los caracteres menos el carcter nueva lnea. En el siguiente los ejemplos siguientes supondremos que prog es un programa ejecutable, y fich1
captulo encontraremos los comandos necesarios para ello. y fich2 son nombres de ficheros.
Reenvo de salida de un fichero: >
Sistemas no UNIX prog >fich1
En este apartado haremos nfasis en las diferencias con el sistema UNIX,
de manera que si usted se salt la parte anterior le aconsejamos que retroce Reenvo de entrada desde un fichero: <
da y la lea. Hay dos variedades distintas dentro de los sistemas no UNIX:
prog <fich2
1. Otros sistemas operativos con reenvo.
2. Compiladores C con reenvo. Reenvo combinado

No pretendemos abarcar todos los posibles sistemas operativos; simple prog <fich2 >fich1
prog >fichl <fich2
mente daremos un ejemplo, que est siendo ampliamente usado. Dicho siste
ma es el MS-DOS 2. Este sistema comenz siendo un vstago del CP/M, pe
Ambas formas emplean fich2, para la entrada, y fich1, para la salida.
ro actualmente ha evolucionado hacia el XENIX, sistema semejante al UNIX.
La versin 2 del MS-DOS 2 presenta operadores < y > , que funcionan exac Espaciado
tamente igual que los descritos en el apartado anterior. Algunos sistemas, especialmente los compiladores C, requieren que exista
Tampoco podemos cubrir todos los posibles compiladores C. Sin embar un espacio a la izquierda del operador de reenvo, y ninguno a la derecha. Otros
go, de cada 6 versiones de compilador C para microordenador que hemos sistemas, por ejemplo UNIX, aceptan cualquier combinacin de espacios o
ausencia de los mismos.
Qu se puede hacer con este programa? Bien, se puede ignorar por com
pleto; tambin se puede intentar alterar, para conseguir salidas distintas.
Se pueden tambin buscar combinaciones de caracteres que produzcan una
UN EJEMPLO GRAFICO salida agradable. Por ejemplo, los caracteres

Podemos utilizar getchar( ) y putchar( ) para producir diagramas geo


mtricos empleando caracteres. El siguiente programa realiza precisamente
esto. En l se lee un carcter y se imprime repetidamente un cierto nmero
de veces, dependiendo de su valor ASCII. Tambin a la izquierda de la fila
de caracteres se imprimen los espacios necesarios para que la lnea quede
centrada.

/* diagramas */
/ * produce un patron simetrico de caracteres */
#include <stdio.h>
main(){
int ch; /* lee caracter */
int indice;
int numch;
while ((ch = getchar()) != '\n') {
numch = ch % 26; /* genera un numero entre 0 y 25 */
indice = 0;
while (indice++ < (30 - numch))
putchar(' '); /* espacios para centrar */
indice = 0;
while (indice++ < (2*numch + 1))
putchar(ch); /* imprime ch varias veces * /
putchar('\n');
}
} E/S dependiente de sistema: puerto de E/S 8086/8088
Como ejemplo de las posibilidades que tiene el C para adaptarse a los
La nica novedad tcnica es que hemos empleado, dentro de las condi requerimientos de un sistema especfico estudiaremos ahora un tipo diferen
ciones de bucle while, subexpresiones como (30-numch). El control de los es te de dispotivo de E/S. Muchos de los microordenadores de la nueva genera
pacios iniciales se realiza dentro de un bucle while, y la impresin de los cin se basan en el chip microprocesador Intel 8086 y 8088. El ejemplo ms
caracteres, en un segundo bucle. conocido es el IBM PC, que utiliza el 8088. En nuestra exposicin nos basa
La salida de este programa depende de la entrada. Por ejemplo, si te- remos, en particular, en este ordenador, aunque los principios apuntados ms
cleamos abajo son aplicables a otros usuarios de la familia 8086/8088.
Un ordenador como el IBM tiene bastantes cosas ms que un chip 8088.
Que pasa?
Entre ellas, un teclado, un altavoz, una unidad de cassette o, quiz, de discos
flexibles, un monitor, memoria, relojes y otros microprocesadores que con
la respuesta es trolan el flujo de datos. La unidad central de proceso (incorporada dentro
del chip 8088) necesita algn sistema para comunicarse con los dems compo
nentes del ordenador. Algunas de estas comunicaciones se establecen utili
zando direcciones de memoria, y otras usando puertos de entrada/salida.
El chip 8088 tiene 65536 puertos utilizables en comunicaciones. Cada uno
de los dispositivos externos posee su propio puerto o puertos de comunica
cin con el 8088 (evidentemente, no se utilizan los 65536!). Por ejemplo,
los puertos 992, 993 y 1000 al 1004 se emplean para comunicarse con el adap-

153
tador de grficos/color. El altavoz se gobierna con el puerto 97. Esta segun
da posibilidad suena bastante ms sencilla que un adaptador de grficos/co
lor, de modo que usaremos el altavoz para ejemplarizar el empleo de los puertos
E/S.
El puerto 97 no controla directamente el altavoz. El dispotivo que lo hace
es algo con el esotrico nombre de controlador Programable de Interface Pa
ralelo 8255. Este microprocesador tiene tres registros (unidades de memo
ria pequeas y fcilmente accesibles), en los que puede almacenar nmeros, Figura 6.7
a razn de uno en cada registro. Los nmeros controlan lo que hace el dispo Puerto 97: acciones controladas por cada bit
sitivo; cada registro est conectado al 8088 a travs de un puerto, y el puerto
97, como podramos imaginar, se conecta con el registro que controla el alta
voz. De este modo, para programar el altavoz, podemos utilizar el puerto Observe los pequeos signos ms y menos de la figura. El signo + signi
para cambiar el nmero contenido por el registro. Si acertamos con el nmero fica que cuando el bit vale 1, se cumple la condicin; el signo -, por contra,
correcto, haremos que el altavoz emita un sonido. Por el contrario, si intro indica que el nivel activo es el 0. As, un 1 en el bit 3 significa que el motor
ducimos un nmero incorrecto podemos encontrarnos con serios problemas. del cassette est desconectado, mientras que un 0 en el bit 4 indica que la
Por tanto, es vital conocer qu nmero debemos enviar y cmo enviarlo; en memoria de lectura/escritura est activada.
nuestro caso particular debemos saber cmo se puede emplear el C para en Cmo conseguimos que funcione el altavoz? Por lo que se ve en la figura,
viar dicho nmero. parece que el altavoz (speaker, en ingls) est afectado por los bits 0 y 1. Po
Nos ocuparemos, en principio, del nmero a enviar. Lo primero que hay demos conectar el altavoz enviando el nmero binario 11 (equivalente al n
que saber es que un registro 8255 acepta un nmero de 8 bits, que se almacena mero decimal 3) a travs del puerto 97. Antes de intentarlo debemos perca
como nmero binario, como 01011011. Cada uno de los 8 bits almacenados tarnos de que esta accin tendra efectos colaterales, como hacer 0 el bit 4,
se considera un conmutador s-no para una determinada accin o un disposi que no es precisamente lo que deseamos que suceda. Por esta razn, no le
tivo concreto. As, la presencia de un 0 o un 1 en una disposicin determina hemos dicho todava cmo se pueden usar los puertos.
da indica si el dispositivo est conectado o no. Por ejemplo, el bit 3 (los bits Para actuar con seguridad debemos comprobar, en primer lugar, cul es
se numeran de 0 a 7, comenzando por la derecha) determina si el motor del el contenido del registro en reposo. Afortunadamente, es bastante fcil
cassette est conectado, y el bit 7 activa y desactiva el teclado. Empezar a enterarse (lo veremos en un momento). La respuesta es que el registro suele
comprender la necesidad de obrar con precaucin: si decidimos activar el contener 76 77. Traslademos estos nmeros a binario. (Quiz le con
altavoz, y olvidamos los dems bits, podemos encontrarnos con que el tecla venga hacer una pequea visita al apndice de nmeros binarios antes de con
do ha quedado accidentalmente anulado. En la siguiente figura se indica lo tinuar.) En la tabla 6-1 se muestra la conversin a binario de algunos nme
que hace cada bit. (La informacin est tomada de un manual tcnico de re ros decimales:
ferencia de IBM, y no es necesario que sepamos la mayor parte de los deta
lles contenidos en l.) Tabla 6-1. Conversin de algunos nmeros decimales a binarios
decimal nmero de bit 7 6 5 4 3 2 1 0
76 0 1 0 0 1 1 0 0
77 0 1 0 0 1 1 0 1
78 0 1 0 0 1 1 1 0
79 0 1 0 0 1 1 1 1

Sin entrar a averiguar qu puede significar algo como hold keyboard clock
low, est claro que la poltica ms conservadora es mantener todas las posi
ciones de los bits intactas, con excepcin de los bits 0 y 1. Esta accin es equi
valente a enviar al registro el nmero binario 01001111, o decimal 79. Como
Figura 6.6 precaucin adicional, leeremos previamente el contenido del registro y lo de
La conexin 8088-8255 jaremos tal como estaba una vez que hayamos conseguido que el altavoz sue-

155
Ejecutemos ahora el programa. Quiz se sienta un poco decepcionado,
porque el ordenador desconecta el sonido casi inmediatamente despus de
ne. (Las operaciones bit a bit discutidas en el apndice presentan otra forma producirse. Quedara mucho ms satisfactorio este programa si el ordenador
de asignar valores en el registro.)
De acuerdo, ya estamos listos para hacer pitar al altavoz. Y ahora qu? se detuviese durante un instante antes de desconectar el altavoz de nuevo.
Cmo podemos hacerlo? Simplemente obligando al ordenador a ejecutar
una tarea diferente entretanto. En el siguiente programa se consigue un piti
Utilizacin de un puerto do ms largo.

A travs de un puerto se pueden hacer dos cosas. Se puede enviar infor /* pito2 */
macin desde el 8088 al dispositivo conectado a l o bien leer informacin /* un pitido mas largo */
del dispositivo y pasarla al 8088. Estas tareas se realizan en lenguaje ensam #define LIMITE 10000
blador con las instrucciones OUT e IN. En C, el mtodo a seguir depende main()
{
del compilador; algunos compiladores ofrecen una funcin C anloga; por int loquehay;
ejemplo, Lattice C y Supersoft C utilizan outp( ) e inp( ). En otros compila int cont = 0; /* algo para contar */
dores los nombres pueden ser ligeramente diferentes. Si el compilador que
loquehay = inp(97);
est utilizando no ofrece esta posibilidad, lo ms probable es que pueda em outp (97, 79) ;
plear lenguaje ensamblador para definir dicha funcin, o bien insertar sim while(cont++ < LIMITE)
plemente el cdigo ensamblado directamente en su programa (una operacin ; /* una sentencia que solo gasta tiempo */
bastante sencilla). Eche un vistazo al manual de su compilador. Entretanto, outp(97, loquehay);
supondremos que dispone de las funciones outp( ) e inp( ).
}
He aqu un programa que hace pitar al ordenador.

/* pito1 */ Observar que lo nico que hace el bucle while es aumentar el valor del
/* programa que hace sonar el altavoz */
main() contador cont hasta llegar a LIMITE. El punto y coma que sigue al bucle
{ while hace que ste ejecute una sentencia nula, es decir, una sentencia que
int loquehay;
no realiza ninguna accin. Por tanto, pito2 conecta el altavoz, cuenta hasta
loquehay = inp(97); /* guarda valor inicial * / 10000 y, a continuacin, lo desconecta. Se puede, evidentemente, ajustar el
/* del puerto 97 */
printf ("puerto 97 = %d\n", loquehay); / * comprueba */ valor de LIMITE para controlar la duracin del sonido. Tambin se puede
outp(97,79); /* envia 79 al puerto; conecta altavoz */ sustituir LIMITE por una variable, y controlar la duracin por medio de va
outp(97, loquehay); / * lo deja como estaba * /
} lores introducidos con scanf( ).
Sera agradable controlar tambin el tono; de hecho es posible. Cuando
acabe de estudiar estas funciones puede, si lo desea, leerse el apndice, en
que se presenta un programa que transforma el teclado de la terminal en un
Probablemente ya habr inferido la misin de las funciones inp( ) y instrumento musical.
outp( ); por si acaso, ah va una descripcin ms formal.

inp(numero de puerto) Esta funcin devuelve un valor entero de 8 bits Resumen


(que se convierte en un entero int de 16 bits aa
diendo ceros a la izquierda) al puerto de entrada De nuevo nos hemos tropezado con dispositivos de E/S, datos de E/S
numero de puerto. En la operacin no se produ y funciones de E/S. Los dispositivos han sido el controlador 8285 y el alta
ce alteracin alguna en dicho puerto. voz, los datos, los nmeros comunicados desde y hacia uno de los registros
del 8285 y las funciones, inp( ) y outp( ). Estas funciones, o sus equivalentes
outp(numero de puerto, valor) Esta funcin enva un valor entero de 8 bits en cdigo ensamblador, son necesarias para manejar los puertos E/S del
al puerto de salida nmero de puerto. 8086/8088; los compiladores C ofrecen generalmente una o las dos opciones.
Obsrvese que el puerto se puede utilizar como entrada y salida, depen
diendo de la funcin aplicada.

157
Vamos a tantear la potencia oculta la L final) para evitar problemas con el tamao mximo int. Si se emplea
nuestro ordenador el valor 8000, no hubiera sido necesario en el IBM PC; sin embargo, si cam
biamos el valor a 12000, el nmero debe ser definido long, ya que la expre
Lo que ignora seguramente es que bajo el teclado de inocente aspecto se sin 3*LIMITE resulta 36000, que es mayor que el mximo int permitido en
esconde un brioso corcel, un verdadero purasangre. Se atreve a domarlo? dicho sistema.
Hemos creado para ello un fabuloso programa (revelado en la figura 6.8), Si su sistema no dispone de timbre o altavoz, intente cambiar la sentencia
diseado especialmente para la doma de su caballo. Es un programa que hay putchar(' \ 007') por printf(CLOP \ n).
que ejecutar para valorarlo en lo que merece. Precaucin: para conseguir el Con este programa conseguir impresionar a sus amistades, y har pro
efecto adecuado deber escoger un valor de LIMITE apropiado para su siste bablemente sonrer a aquellos que todava temen a los ordenadores.
ma. Ms adelante hablaremos de ello; por el momento, he aqu el programa. Estamos convencidos de que el programa puede ser la base de una cal
culadora C; dejamos el desarrollo de la idea a nuestro lectores.
/* Furia */
#include <stdio.h>
#define LIMITE 8000L
main()
Hasta ahora hemos aprendido
{
int num1, num2; Qu hace getchar( ): toma un carcter del teclado.
long retraso = 0; Qu hace putchar(ch): enva el carcter ch a la pantalla.
int cont = 0;
Qu significa != : no igual a.
printf("Furia, el caballo matematico, sumara dos\n"); Qu es EOF: un carcter especial que indica el final de un fichero (End
printf("enteros no muy grandes para su divertimiento\n") ; Of File).
printf("Introduzca el primer entero (que sea facil!)\n");
scanf( "%d", &num1) ; Cmo reenviar la entrada desde un fichero: programa < fichero.
printf("Gracias. Introduzca el segundo\n"); Cmo reenviar la salida a un fichero: programa > fichero.
scanf( "%d " , &num2) ;
printf("Bien, Furia, cuanto suma eso?\n");
Qu son puertos: accesos de E/S a dispositivos conectados al micropro
while( retraso++ < LIMITE); cesador.
while( cont++ < (num1 + num2 - 1))
{
Cmo utilizar puertos: inp( ) y outp( ).
putchar(' \007' ) ;
retraso = 0;
while( retraso++ < LIMITE);
putchar('\n'); }
Cuestiones y respuestas
printf("Seguro?\n"); Cuestiones
retraso = 0;
while( retraso++ < 3*LIMITE); 1. Sabemos que putchar(getchar( )) es una expresin vlida. Es tambin vlida get-
putchar('\007'); char(putchar( ))?
printf("Muy bien, Furia!\n");
} 2. Cul es el resultado de la ejecucin de las siguientes sentencias?
a. putchar(H);
Figura 6.8 b. putchar(' \007');
Un programa devorador de nmeros c. putchar(' \n);
d. putchar(' \ b');
3. Supongamos que tenemos un programa cuenta que cuenta los caracteres de un
Anotaciones tcnicas: Las sentencias while que contienen retraso se limi fichero. Preparar una orden que cuente el nmero de caracteres del fichero ensayo
tan a marcar tiempo. El punto y coma al final de la lnea indica dnde acaba y almacene el resultado en un fichero llamado ensayoct.
el bucle, el cual no incluye ninguna de las lneas siguientes. Cuando se usa 4. Dado el programa y los ficheros de la cuestin anterior, cules de los siguientes
un bucle while dentro de otro, la operacin se denomina anidado. Hemos comandos seran vlidos?
encontrado que el valor ms apropiado para LIMITE en el IBM PC es 8000: a. ensayoct < ensayo
para un VAX 11/750 preferimos un valor alrededor de 50000; en todo caso, b. cuenta ensayo
en este segundo sistema la salida se puede ver afectada por el tiempo compar c. cuenta < ensayoct
tido. Hemos hecho LIMITE igual a una constante long (ese es el origen de d. ensayo > cuenta
5. Cul es el resultado de la sentencia outp(212,23)?

159
Respuestas
1. No. getchar( ) no utiliza argumento, mientras que putchar( ) necesita uno.
2. a. imprime la letra H.
b. enva a la salida el carcter \007, que produce un pitido.
c. comienza una nueva lnea.
d. retrocede un espacio.
3. cuenta < ensayo > ensayoct o bien cuenta > ensayoct < ensayo.
4. a. invlido, ya que ensayoct no es un programa ejecutable.
b. invlido, por haberse omitido el operador de reenvo. (Sin embargo, aprenderemos ms
adelante a escribir programas que no necesitan dicho operador.)
c. vlido, da como resultado el nmero de caracteres en el mensaje producido por cuenta
en la cuestin 3.
d. invlido; el nombre del programa ejecutable debe aparecer en primer lugar.
5. Enva el nmero 23 al puerto 212.

Ejercicios
1. Escriba un programa como el descrito en la cuestin 3; es decir, un programa
que cuente el nmero de caracteres de un fichero.
2. Modifique cuenta de manera que emita un sonido cada vez que cuenta u n carc
ter. Incluya un bucle de retraso para separar un pitido del siguiente.
3. Modifique pito2 de manera que pueda introducirse como dato el lmite de conta
je del bucle cuando se ejecute el programa.

160
7
Una encrucijada
en el camino
En este captulo encontrar:

La sentencia if
La sentencia if con else
Eleccin: if-else
Eleccin mltiple: else-if
Cada else con su if
Quin es el ms grande?: operadores de relacin y expresione:
Qu es la Verdad?
Y qu ms es Verdad?
Problemas con las verdades y las mentiras
Prioridad de operadores de relacin
Seamos lgicos
Prioridades
Orden de evaluacin
Un programa para contar palabras
Una caricatura con caracteres
Anlisis del programa
Longitud de las lneas
Estructura del programa
Disposicin de los datos
Comprobacin de errores
El operador condicional: ?:
Eleccin mltiple: switch y break
Hasta ahora hemos aprendido
Cuestiones y respuestas

163
Una encrucijada La primera forma ya la conocemos de sobra: todos nuestros programas,
hasta ahora, han consistido en secuencias de sentencias. Disponemos tam
bin de un ejemplo de la segunda forma, el bucle while, y completaremos

en el camino este apartado en el captulo 8. El ltimo punto, eleccin entre diferentes ma


neras posibles de actuar, hace los programas mucho ms inteligentes y
aumenta enormemente la utilidad de un ordenador. A ello dedicaremos este
captulo.

CONCEPTOS

Toma de decisiones La sentencia if


Qu es verdadero y falso en C
Comenzaremos con un ejemplo muy simple. Ya hemos visto cmo escri
Cmo hacer comparaciones bir un programa que cuenta el nmero de caracteres de un fichero. Suponga
Lgica en C mos que, en su lugar, deseamos contar lneas. Para ello debemos contar el
nmero de caracteres nueva lnea que aparecen en el fichero. Esta operacin
PALABRAS CLAVE se realiza as:

if, else, switch, break, case, default /* cuentalineas */


#include <stdio.h>
main()
OPERADORES {
int ch;
int numlin = 0;
>> = < = < = =! =&& || ! ?:
while(( ch = getchar()) != EOF)
if (ch =='\n')
numlin++;
printf("He contado %d lineas.\n");
}

El ncleo de este programa es la sentencia

i f (ch == '\n')
numl in++;

Cuando se estudia un lenguaje de programacin, uno suea con crear pro Esta sentencia if" indica al ordenador que aumente numlin en 1 cuando
gramas poderosos, inteligentes, verstiles y tiles. Para ello necesitamos un el carcter ledo (ch) es el carcter nueva lnea. El smbolo == no es un error
lenguaje que disponga de las tres formas bsicas de control de flujo del de imprenta: significa es igual a. No confunda este operador con el opera
programa. De acuerdo con las ciencias del cmputo (ciencia del cmputo es dor de asignacin ( = ).
aquella que estudia los ordenadores, no la ciencia hecha por ordenadores: Qu sucede si ch no es igual a un carcter nueva lnea? Nada. El bucle
al menos, por el momento), un buen lenguaje de programacin debe presen while avanza para leer el siguiente carcter.
tar las siguientes formas de flujo de programa: La sentencia if que acabamos de emplear es a todos los efectos una nica
sentencia, que se extiende desde el if inicial hasta el punto y coma final. Por
1. Ejecucin de una serie de sentencias. ello no tenemos que utilizar llaves para marcar los lmites del bucle while.
2. Repeticin de una secuencia de sentencias hasta que se cumpla una de Es bastante sencillo hacer un programa que cuente a la vez caracteres y
terminada condicin. lneas; vayamos con ello.
3. Empleo de un test para decidir entre acciones alternativas.

165
/ * ccl--
contador de caracteres y lineas */ printf("Bingo!\n"); /* sentencia simple */
# include <stdio.h>
main()
{
int ch; if (joe > bob)
int numlin = 0; {
int numcar = 0; pastajoe++;
printf("Perdiste, forastero.\n");
while(( ch = getchar()) != EOF) } /* sentencia compuesta */
{
numcar++;
if (ch == '\n' )
numlin++; La forma sencilla de una sentencia if permite elegir entre ejecutar una
} sentencia (simple o compuesta) o saltarla. En C se permite tambin la elec
printf ("He contado %d caracteres y %d lineas. \n", cin entre dos sentencias empleando la estructura if-else.
numcar, numlin);
}
Eleccin: if-else
En el ltimo captulo hemos presentado un programa muy sencillo de co
Ahora el bucle while contiene dos sentencias, de manera que hemos colo dificacin en clave, que convierte cada carcter en el siguiente en la secuen
cado llaves para marcar el comienzo y final del mismo. cia ASCII. Por desgracia, tambin ha quedado convertido el carcter nueva
Podemos llamar al programa compilado ccl y utilizar un operador de reen lnea, con lo que se ha acumulado el texto en una sola lnea en la traduc
vo para contar los caracteres y lneas de un fichero llamado poe. cin. Se puede eliminar este problema creando un programa que haga una
eleccin muy sencilla: si el carcter es nueva lnea, dejarlo como est; si no,
ccl <poe
He contado 4325 caracteres y 124 lineas. convertirlo. En C se realiza esta eleccin de la siguiente forma:

El siguiente objetivo dentro del desarrollo de este programa es conseguir /* codigo1 */


que cuente palabras. Esta opcin es un poco ms complicada que lo realiza #include <stdio.h>
main()
do hasta ahora. Antes de abordarla necesitamos saber algo ms sobre las sen {
tencias if. char ch;
while ( (ch = getchar()) != EOF) {
if (ch == '\n') /* deja caracter nueva */
putchar(ch); /* linea sin alterar */
La sentencia if con else el se
putchar(ch + 1 ) ; /* cambia los demas */
La forma ms simple de una sentencia if es la que acabamos de utilizar: }
}
if (expresion)
sentencia
La vez anterior empleamos un fichero que contena el siguiente texto:
La expresin incluida en la sentencia es, generalmente, de relacin, es de Para mejor comprensin
cir, una expresin que compara el tamao de dos cantidades (x > y o c == 6, se debe escribir sin errores.
por ejemplo). Si la expresin es cierta (x es mayor que y, o c es igual a 6,
se ejecuta la sentencia que va a continuacin. Si es falsa, la sentencia se igno Si utilizamos el mismo texto con nuestro nuevo programa, el resultado
ra. Generalizando an ms, se puede emplear cualquier expresin, y si sta ser ahora:
tiene un valor 0 se toma como falsa; ampliaremos este punto ms adelante.
La porcin que corresponde a la sentencia puede ser una sentencia simple !!!!!Qbsb!nfkps!dpnqsfot j po
como en nuestro ejemplo, o una sentencia compuesta (o bloque) delimitada t f!efcf!ftdsjejs!t jo!fsspsft/
por llaves:

167
cho. Lo veremos en un ejemplo concreto. Las compaas de suministro a me
nudo cargan en los recibos tarifas que dependen de la cantidad gastada. Su
Caramba! Parece que funciona. Por cierto, se puede hacer un programa pongamos que las tarifas por consumo elctrico son:
decodificador muy sencillamente: basta con duplicar codigo1, sustituyendo
(ch + 1) por (ch 1).
Ha observado la forma general de la sentencia if-else? Es los primeros 240 kwh: 5.418 ptas. por kwh
los siguientes 300 kwh: 7.047 ptas. por kwh
if (expresion) por encima de 540 kwh: 9.164 ptas. por kwh
sentencia
else
sentencia Como sabemos que est deseando descifrar su recibo de la luz, vamos a
prepararle un programa que le permita calcular sus costos. El siguiente ejem
Si la expresin es cierta, se ejecuta la primera sentencia; si es falsa se eje plo es un primer intento de este programa.
cuta la sentencia que est colocada a continuacin de else. Las sentencias pue
den ser simples o compuestas. Como ya hemos dicho varias veces, no se ne /* reciboluz */
cesita indentacin en C, pero la forma presentada arriba es bastante estn /*calcula el recibo de la luz * /
dar. Con ella se pueden observar de un vistazo las sentencias cuya ejecucin #define TARIFA1 5.418 / * tarifa de los primeros 240 kwh */
depende de un test. #define TARIFA2 7.047 /* tarifa de los siguientes300 kwh */
#define TARIFA3 9.164 / * tarifa por encima de 540 kwh */
La sentencia if permite escoger entre realizar una accin o no. Con if-else #define BASE1 1300.0 /* coste total primeros240 kwh */
se puede escoger entre dos acciones diferentes. Qu sucede si deseramos #define BASE2 3414.0 /* coste total primeros540 kwh */
tener ms de dos alternativas? #define LIMITE1 240.0 / * primer bloque de tarifa */
#define LIMITE2 540.0/* segundo bloque de tarifa */
main()
Eleccin mltiple: else-if {
En la vida aparecen frecuentemente ms de dos posibles alternativas. Po float kwh; /* kilowatios gastados */
demos ampliar la estructura if-else con else-if, para acomodarnos a este he- float recibo; / * precio */
printf ( " Introduzca el gasto en kwh. \n") ;
scanf("%f", &kwh) ;
if (kwh < LIMITE1)
recibo = TARIFA1 * kwh;
else if (kwh < LIMITE2) /* kwh entre 240 y 540 * /
recibo = BASE1 + TARIFA2 * (kwh - 240);
else / * kwh por encima de 540 * /
recibo = BASE2 + TARIFA3 * (kwh - 540);
printf("La cuenta total por %. 1f kwh es %.0 pts.\n",
kwh, recibo);
}

Hemos empleado constantes simblicas para las tarifas; de este modo nues
tras constantes estn reunidas en un solo sitio. Si la compaa cambia las ta
rifas (lo que, por desgracia, sucede con demasiada frecuencia), el hecho de
disponer de todas ellas en el mismo lugar hace ms sencilla la modificacin
del programa. Tambin hemos utilizado smbolos para los lmites de tarifas;
dichos lmites pueden, asimismo, ser modificados eventualmente. El flujo del
programa es completamente directo, seleccionndose una de las tres frmu
las dependiendo del valor de kwh: en la figura 7.2 se ejemplifica dicho flujo.
Debemos aclarar que la nica posibilidad de que el programa alcance el pri
mer else es que kwh sea igual o mayor que 240. Por tanto, la lnea else if
(kwh < LIMITE2) equivale realmente a averiguar si kwh est comprendido
Figura 7.1 entre 240 y 540, como se advierte en el comentario del programa. De igual
if e if-else
169
como otras son ignorados por el compilador. De cualquier manera, se pre
forma, el else final slo puede alcanzarse si kwh es mayor o igual que 540 fiere la primera forma, que muestra ms claramente que estamos eligiendo
Por ltimo, observe que BASE1 y BASE2 representan el cargo total por los entre tres posibles alternativas. Esta forma hace ms fcil la revisin del pro
primeros 240 y 540 kwh, respectivamente. Para un clculo de gastos mayo grama y la comprensin de las alternativas de que se trata. La forma anida
res, lo nico que tenemos que hacer es calcular el cargo adicional por la elec da, por el contrario, resultar til cuando se deseen comparar dos cantidades
tricidad consumida en exceso respecto a estas cantidades. diferentes. Un ejemplo aplicable a nuestro programa podra ser si se estable
ciese un 10 por 100 de recargo en los kwh que excedieran de 540, nicamente
durante el verano.
Se pueden unir tantos else-if cuantos se deseen, como se comprueba en
el fragmento siguiente:

if (tanteo < 1000)


bonus = 0;
else f (tanteo < 1500)
bonus = 1;
else if (tanteo < 2000)
bonus = 2;
else if (tan teo < 25 00)
bonus = 4;
else
bonus = 6;

Este fragmento podra ser parte de un programa de juego, en donde bo


nus representa cuntas bombas de fotones adicionales o paquetes de comida
recibir usted en la siguiente ronda.
Figura 7.2
Flujo deI programa reciboluz Cada else con su if
Cuando se encuentran un gran nmero de if y else reunidos, se podra
En realidad, la construccin else-if es simplemente una variacin de lo uno preguntar cmo decide el ordenador el if que corresponde a cada else.
que ya sabamos. Por ejemplo, el ncleo del programa anterior se podra ha Por ejemplo, consideremos el siguiente trozo de programa:
ber escrito tambin
if ( numero > 6 )
f ( numero < 12 )
if (kwh < LIMITE1) prin t f("C alien te!\n");
r e c i b o = TARIFA1 * k w h ;
else
else prin tf("L o sien to, h as pe rdido!\n");
if ( kwh < LIMITE2)
re cib o = B AS E 1 + TARIFA2 * (kwh - 240);
else
re cib o = B AS E 2 + TARIFA3 * (kwh - 540);
Cundo se escribir: !Lo siento, has perdido!? Cuando nmero sea
menor o igual que 6 o cuando sea mayor que 12? Dicho de otra forma, el
Es decir, el programa consiste en una sentencia if-else, en la cual la parte else va con el primer if o con el segundo?
de sentencia else es, a su vez, otra sentencia if-else. Se dice que la segunda La respuesta es que va con el segundo if. Es decir, en la ejecucin de este
sentencia if-else est anidada en la primera. (Por cierto, toda la estructura programa obtendra una salida como sta:
if-else contabiliza como una sola sentencia, por lo que no hay necesidad de
encerrar el if-else anidado entre llaves.) Nmero Respuesta
Las dos formas son completamente equivalentes. Las nicas diferencias 5 ninguna
estn en los lugares en que colocamos los espacios y las lneas; tanto unos 10 Caliente!

15 Lo siento, has perdido!

171
La regla a observar es que el else va con el if ms prximo, a menos que RESUMEN: UTILIZACION DE SENTENCIAS if PARA
haya llaves que indiquen lo contrario. Hemos indentado nuestro programa ELEGIR ALTERNATIVAS
haciendo aparentar que else iba con el primer if; recuerde, sin embargo, que
el compilador ignora la indentacin. Si realmente desease que else fuese con
el primer if, habra que reescribir el fragmento de la siguiente manera: Palabras clave: if, else
Comentarios generales:
En cada una de las formas siguientes la sentencia puede ser simple o compues
if ( numero > 6 ) ta. Se considera verdadera, en general, cualquier expresin cuyo valor sea
{ distinto de 0.
if ( numero < 12 )
printf("Calente!\n");
} Forma 1:
else
printf("Lo siento, has perdido!\n"); if ( expresin )
sentencia

La sentencia se ejecuta si la expresin es cierta.


Ahora se obtienen las siguientes respuestas:
Forma 2:
Nmero Respuesta if ( expresin )
5 Caliente! sentencia]
10 Lo siento, has perdido! else
15 ninguna sentencia2

Si la expresin es cierta, se ejecuta sentencia1. Si es falsa, se ejecuta sentencia2.

Forma 3:
if ( expresin1 )
sentencia1
else if ( expresin2 )
sentencia2
else
sentencia3

Si expresin1 es cierta, se ejecuta sentencia1. Si expresin1 es falsa, pero


expresin2 es cierta, se ejecuta sentencia2. Si ambas son falsas, se ejecuta
sentencia3.
Ejemplo:

if (patas == 4)
printf("Debe ser un caballo. \n") ;
else if (patas > 4)
Figura 7.3 printf("No es un caballo.\n") ;
Apareando if con else else /* casos de patas < 4 */
{
patas++;
printf("Ahora tiene una pata mas.\n");
}

173
Los operadores de relacin se utilizan para formar las expresiones em
Quien es el ms grande: operadores pleadas en sentencias if y while. Estas sentencias comprueban si la expresin
es cierta o falsa. Los cuatro ejemplos siguientes contienen sentencias de rela
de relacin y expresiones cin cuyo significado, esperamos, est bastante claro.
Los operadores de relacin se emplean para hacer comparaciones. Ya he
if ( numero < 6)
mos utilizado algunos, y lo que sigue es una lista completa de estos operado prin tf("E l nu m ero de be se r m a yor.\n ");
res en C.
while ( ch != $)
OPERADOR SIGNIFICADO cont++;

< es menor que


if (total == 100)
<= es menor o igual que prin tf(H a co nsegu ido u n plen o!.\n ");
== es igual a
> = es mayor o igual que
> es mayor que if < ch > M )
!= es distinto de prin tf("E nviar este sujeto a otra linea .\n");

Con esto quedan cubiertas todas las posibilidades de relaciones numri Obsrvese que las expresiones de relacin se pueden tambin utilizar con
cas. (Los nmeros, aunque algunos de ellos sean complejos, son bastante me caracteres. Para la comparacin se emplea cdigo mquina (que hemos esta
nos complejos que los humanos.) Se debe poner especial cuidado en no con do suponiendo ASCII). Por el contrario, los operadores de relacin no sir
fundir = por = = . Algunos lenguajes de ordenador (por ejemplo, BASIC)
utilizan el mismo smbolo para el operador de asignacin y para el operador ven para comparar tiras de caracteres; en el captulo 13 se muestra cmo pro
de relacin de igualdad, pero las dos operaciones son completamente dife ceder con estas ltimas.
rentes. El operador de asignacin asigna un valor a la variable situada a su De igual forma, los operadores de relacin se pueden utilizar con nme
izquierda; sin embargo, el operador de relacin de igualdad comprueba si ros en punto flotante. Sin embargo, en estos nmeros se aconseja emplear
sus partes izquierdas y derechas son ya iguales. En ningn caso se cambia nicamente comparaciones < y >. La razn es que dos nmeros pueden
el valor de la variable de la izquierda, suponiendo que haya alguna. no ser iguales debido a errores de redondeo, aunque lgicamente debieran
serlo. Imaginemos, por ejemplo, este ejemplo equivalente en decimal. Si mul-
canoas = 3 asigna el valor 3 a canoas
canoas = = 5 comprueba si canoas tiene el valor 5
COMPARACION
Cualquier precaucin es poca a este respecto, ya que el compilador pue
de, en ocasiones, permitirle utilizar la forma errnea, dando resultados que
pueden ir desde una simple broma hasta una autntica catstrofe. Ms ade = = COMPRUEBA SI
lante veremos un ejemplo. EL VALOR DE CANOAS
ES 5

Tabla 7-1: Operadores de asignacin y de relacin de igualdad en algunos


lenguajes comunes
ASIGNACION
Lenguaje Asignacin Relacin de igualdad
BASIC = =
FORTRAN = .EQ. = ASIGNA A CANOAS
C = == EL VALOR 3
PASCAL := =
PL/I = =
LOGO make = F igu ra 7.4

175
cia if elegir el primer camino en la bifurcacin (la sentencia detrs de if)
tiplicamos 3 por 1/3, el resultado debe ser 1.0; pero si escribimos 1/3 como mientras que el segundo if tomar el camino alternativo (la sentencia detrs
nmero con 6 cifras decimales, el producto es .999999, que no es lo suficien de else). Ejecute el programa y compruebe si estamos en lo cierto.
temente igual a 1.
Cada expresin de relacin se enjuicia como cierta o falsa. Este punto Y qu ms es verdad?
presenta implicaciones de gran inters.
Hemos utilizado un 1 y un 0 como expresin de la sentencia if; podemos
emplear otros nmeros? Si lo hacemos, qu sucedera? Experimentemos.
Qu es la Verdad?
Esta pregunta se la han formulado filsofos de todas las epocas. Noso /* test de if */
mai n()
tros nos daremos el gusto de contestarla, al menos en lo que respecta al C. {
En primer lugar, recuerde que cada expresin en C siempre tiene un valor. if(200)
Esto es cierto incluso para expresiones de relacin, tal como se demuestra printf("200 es cierto.\n");
en el siguiente ejemplo. En l calculamos los valores de dos expresiones, una if(-33)
printf(-33 es cierto.\n");
cierta y una falsa. }

/* ciertoyfalso */ Los resultados son:


main()
{ 200 es cierto.
int cierto, falso; -33 es cierto.
cierto = ( 10 > 2 ); /* valor de una relacion cierta */
falso = ( 10 == 2); /* valor de una relacion falsa */ Aparentemente, el C toma 200 y 33 como cierto tambin. De hecho,
printf ( "cierto = %d; falso = %d \n", cierto, falso); cualquier valor distinto de 0 ser cierto, y nicamente se toma como fal
}
so 0. Realmente, este lenguaje tiene una nocin de la Verdad muy tolerante!

Aqu hemos asignado los valores de dos expresiones de relacin a dos va


riables. Para ser consecuentes, hemos asignado cierto al valor de una expre
sin cierta, y falso al valor de una falsa. La ejecucin del programa produce
el siguiente resultado:
cierto = 1; falso = 0

Aj! Para el C, la Verdad es 1, y la Falsedad es 0. Podemos comprobar


lo fcilmente con el siguiente programa..

/* test de la verdad */
main ()
{
if (1)
printf("1 significa cierto. \n");
else
printf("1 no significa cierto.\n");
if (0)
printf("0 no significa cierto.\n">;
else
printf("0 significa cierto.\n");
}

La suposicin de partida es que 1 se evaluar como sentencia cierta, y 0


como sentencia falsa. Si lo que suponemos es correcto, la primera senten

177
Muchos programadores hacen uso de esta definicin de verdad. Por ejem Pero, qu ha sucedido? Aparte de que el diseo del programa deja bas
plo, la frase tante que desear, hemos olvidado el aviso de prevencin que comentbamos
if(cabras != 0) un poco ms atrs, empleando
if (edad = 65)
puede sustituirse por
if (cabras) en lugar de
if (edad == 65)
ya que la expresin (cabras != 0) y la expresin (cabras) se hacen 0 o falsas
si, y slo si, cabras tiene el valor 0. Por nuestra parte, pensamos que la se
gunda forma no tiene un significado tan claro como la primera. Sin embar El efecto, como se puede ver, es desastroso. Cuando el programa alcanza
go, es ms eficiente, ya que el ordenador necesita hacer menos operaciones esa lnea toma la expresin (edad = 65). Como expresin de asignacin que
cuando se ejecuta el programa. es, hace que la variable tome el valor 65. Al ser 65 distinto de 0, la expresin
se declara cierta, y se ejecuta la siguiente instruccin de impresin. A con
tinuacin el programa vuelve al test del bucle while, con edad valiendo 65,
Problemas con las verdades y las mentiras lo cual es menor o igual a 65. Al cumplirse la condicin del test, edad se in
Esta manga ancha que muestra el C para reconocer la verdad puede crementa a 66 (debido a la notacin sufija del operador incremento + +),
crear problemas. Consideremos el siguiente programa. y se ejecuta el bucle de nuevo. Por qu no se detiene ahora? Debera hacer
lo, ya que edad es ahora mayor que 65. Pero, ay!, cuando el programa al
/* empleo */ canza nuestra sentencia errnea de nuevo, edad recupera el valor 65 otra vez.
main()
{ As el mensaje se imprime una vez ms y el bucle se repite ad infinitum. (A
int edad = 20; menos, por supuesto, que usted decida detener el programa o desenchufar
while (edad++ <= 65) el ordenador.)
{ En resumen, empleamos los operadores de relacin para formar expre
if ((edad % 20) == 0) /* edad divisible por 20? */ siones. Las expresiones de relacin tienen valor 1, si son ciertas, y 0,
printf("Ha cumplido %d. Le subimos el sueldo. \n", edad);
if (edad = 65) si son falsas. Las sentencias que emplean normalmente expresiones de rela
printf ("Ha cumplido %d. Aqui esta su reloj de oro. \n", cin como test (por ejemplo, while e if) pueden usar, en realidad, cualquier
edad);
} expresin; si su valor es distinto de 0, se tomar como cierta, y si es 0,
} como falsa.

A primera vista se podra pensar que la salida de este programa sera


Prioridad de las operaciones de relacin
Ha cumplido 40. Le subimos el sueldo. El nivel de preferencia de los operadores de relacin es menor que el de
Ha cumplido 60. Le subimos el sueldo.
Ha cumplido 65. Aqui esta su reloj de oro. + y , y mayor que el del operador de asignacin. Por ejemplo, esto signi
fica que
Sin embargo, la salida real es x > y + 2

Ha cumplido 65. Aqui estasu reloj de oro. es lo mismo que


Ha cumplido 65. Aqui esta su reloj de oro.
Ha cumplido 65. Aqui esta su reloj de oro.
Ha cumplido 65. Aqui esta su reloj de oro. x > (y + 2)
Ha cumplido 65. Aqui esta su reloj de oro.
Ha cumplido 65. Aqui esta su reloj de oro.
Tambin significa que

ch = getchar() != EOF
y as indefinidamente.
179
> es mayor que
es lo mismo que != es distinto de
II. Expresiones de relacin:
ch = (getchar( ) != EOF)
Una expresin de relacin simple consiste en una operacin de relacin con
un operando a cada lado. Si la relacin es cierta, la expresin toma el valor 1.
ya que la mayor prioridad de != indica que dicha operacin se realiza antes Si es falsa, toma el valor 0.
de la asignacin. As pues, ch tendr el valor 1 0, ya que (getchar( ) != EOF) III. Ejemplos:
es una expresin de relacin cuyo valor se asigna a ch. Comprender ahora 5 > 2 es cierta y tiene el valor 1
por qu utilizbamos parntesis en los programas de ejemplos anteriores, en (2 + a) == a es falsa y tiene el valor 0
los que desebamos que ch tomase el valor de getchar( ):

(ch = getchar( )) != EOF

Los propios operadores de relacin estn organizados en dos categoras


diferentes:
Seamos lgicos
grupo con mayor prioridad: < < = = > >
grupo con menor prioridad: == != Algunas veces es til combinar dos o ms expresiones de relacin. Por
ejemplo, supongamos que deseamos escribir un programa que cuente nica
Al igual que la mayora del resto de operadores, stos tambin asocian mente los caracteres que no sean espacios en blanco. Es decir, deseamos con
de izquierda a derecha. As, tar los caracteres que no son espacios, ni caracteres nueva lnea, ni caracteres
tabulado. Empleamos operadores lgicos para cumplir este requerimien
alfa != beta == gamma
to. El siguiente programa es un ejemplo del mtodo a seguir.
es lo mismo que
/* cuentacar */
(alfa != beta) == gamma /* cuenta caracteres no blancos */
main()
{
En C se comprueba, en primer trmino, si alfa y beta son iguales. El va int ch;
lor resultante, 1 0 (cierto o falso), se compara a continuacin con el valor int numcar = 0;
de gamma. En realidad, este tipo de expresiones no se emplean normalmen
while ((ch = getchar( ) != EOF)
te, pero creemos que es nuestro deber informar de su existencia.
if ( ch != ' ' && ch != '\n' && ch != '\t')
Recordamos al lector/a que desee mantener claras sus prioridades que en numcar++;
el apndice C hay una tabla completa de todos los operadores ordenados por printf("He contado caracteres no blancos. \n" numcar) ;
rango. }

RESUMEN: OPERADORES DE RELACION Y EXPRESIONES La ejecucin comienza como en otros muchos programas anteriores: se
lee un carcter y se comprueba si es el carcter fin de fichero (EOF). A conti
I. Operadores de relacin: nuacin aparece algo nuevo, una sentencia que utiliza el operador lgico y
Todos estos operadores comparan el valor a su izquierda con el valor a su de (and), &&. La sentencia if que lo contiene se puede interpretar de la siguiente
recha. manera:
Si el carcter no es un blanco Y, no es un carcter nueva lnea Y, no es
< es menor que un caracter de tabulado, aumenta numcar en 1.
<= es menor o igual que Para que la expresin completa sea cierta, lo deben ser las tres condicio
== es igual a nes por separado. Los operadores lgicos tienen una prioridad menor que
>= es mayor o igual que los operadores de relacin, de manera que no es necesario emplear parntesis
adicionales para agrupar las subexpresiones.

181
Existen tres operadores lgicos en C: Orden de evaluacin
Normalmente, en C no se garantiza qu parte de una expresin compleja
OPERADOR SIGNIFICADO se evala primero. Por ejemplo, en la sentencia
&& and (y) manzanas = (5 + 3) * (9 + 6) ;
|| or (o)
! not (no) la expresin 5 + 3 podra evaluarse antes de 9 + 6, o podra hacerlo des
pus; sin embargo, la precedencia de los operadores s garantiza que ambas
Supongamos que exp1 y exp2 son dos expresiones de relacin simples, sern evaluadas antes de que se realice la multiplicacin. Esta ambigedad,
como gato > rata o deuda = = 1000. En ese caso: como ya se ha comentado, se dej intencionadamente en el lenguaje, a fin
de permitir que los diseadores de compiladores pudiesen preparar versiones
1. exp1 && exp2 es cierto slo si tanto exp1 como exp2 son ciertas. ms eficientes para su sistema particular. No obstante, hay una excepcin
2. exp1 || exp2 es cierta si lo son exp1, o exp2, o ambas. a esta regla (ms bien una falta de regla), concretamente en el tratamiento
de operadores lgicos. En C se garantiza que las expresiones lgicas se eva
3. exp1 es cierta si exp1 es falsa, y viceversa. lan de izquierda a derecha. Tambin queda garantizado que tan pronto se
encuentre un elemento que invalida la expresin completa cesa la evaluacin
Veamos algunos ejemplos concretos:
de la misma. Con estas garantas se pueden emplear construcciones como
5 > 2 && 4 > 7 es falsa porque slo una de las dos subexpresiones es cierta.
while ((c = getchar( )) != EOF && c != ' \ n ' )
5 > 2 || 4 > 7 es cierta porque al menos una de las subexpresiones es cierta.
!(4 > 7) es cierta porque 4 no es mayor que 7.
La primera subexpresin asigna un valor a c, el cual debe utilizarse en
La ltima expresin, por cierto, es equivalente a la segunda subexpresin. Si no se hubiese garantizado el orden, el ordenador
podra haber intentado evaluar la segunda expresin antes de encontrar el
4 <= 7 valor de c.
Otro ejemplo podra ser
Si los operadores lgicos no le son familiares, o se encuentra incmodo
con ellos, recuerde que if ( numero != 0 & & 12/numero == 2)
printf("El numero es 5 o 6.\n");
practica && tiempo == perfeccin

Si nmero tiene un valor 0, la expresin es falsa, y el resto de la misma


no se evala. As se le evita al ordenador el trauma de intentar una divisin
Prioridades por 0. Muchos lenguajes no poseen esta caracterstica; despus de compro
El operador ! tiene una prioridad muy alta, mayor que la multiplicacin, bar que el nmero es 0, intentan todava averiguar el resultado de la siguien
igual a la de los operadores incremento e inmediatamente inferior a la de los te condicin.
parntesis. El operador && tiene mayor prioridad que || estando ambos si
tuados por debajo de los operadores de relacin y por encima de la asigna
cin. Por consiguiente, la expresin RESUMEN: OPERADORES LOGICOS Y EXPRESIONES

a > b & & b > c | | b > d I. Operadores lgicos:


Los operadores lgicos emplean usualmente expresiones de relacin como ope-
se interpretar como randos. El operador ! utiliza un solo operando. El resto usa dos, uno a la iz
quierda y otro a la derecha.
( ( a > b ) && ( b > c ) ) | | ( b > d )
&& and (y)
|| or (o)
es decir, b est comprendido entre a y c, o b es mayor que d. ! not (no)

183
{
palabra = SI;
II. Expresiones lgicas: np++;
expresin1 && expresin2 es cierta si, y slo si, ambas expresiones son cier }
tas. if ( (ch == ' ' || ch == '\n' || ch || ' \t' ) &&
expresin1 || expresin2 es cierta si una de ellas o ambas son ciertas. palabra == SI)
palabra = NO; / * final de palabra */
!expresin es cierta si la expresin es falsa, y viceversa. }
III. Orden de evaluacin:
printf("caracteres = %ld, palabras = %d, lineas = %d\n",
Las expresiones lgicas se evalan de izquierda a derecha; la evaluacin se de
nc, np, nl) ;
tiene tan pronto se descubre algo que hace falsa la expresin total. }
IV. Ejemplos
6 > 2 && 3 = = 3 es cierta
! ( 6 > 2 && 3 = = 3 ) es falsa
x != 0 && 20/x < 5 slo se evala la segunda expresin si x es Hemos tenido que emplear operadores lgicos para comprobar los tres
distinto de cero. posibles tipos de caracteres en blanco que podamos encontrar. Considere
mos, por ejemplo, la lnea
Aplicaremos ahora nuestros nuevos conocimientos a un par de ejemplos.
El primero nos recordar programas ya vistos. if (ch != ' ' && ch != ' \n' && ch != ' \t' && palabra == NO)

Programa para contar palabras que se leera si ch no es un espacio, y no es una nueva lnea, y no es un
tabulado, y no estamos en una palabra. (Las tres primeras condiciones jun
Disponemos ahora de todas las herramientas necesarias para escribir un tas estn preguntando si ch no es un espacio en blanco.) Cuando se cumplen
programa que cuente palabras (y de paso contar tambin caracteres y lneas, las cuatro condiciones a la vez, debemos estar comenzando una nueva pala
si lo deseamos). El punto clave es buscar una manera de ensear al ordenador bra; por tanto, se incrementa np; por el contrario, si estamos en mitad de
a distinguir palabras. Tomaremos un camino relativamente sencillo, definiendo una palabra, se cumplen las tres primeras condiciones, pero palabra ser SI,
una palabra como una secuencia de caracteres sin espacios en blanco. Por y np no se incrementa. Cuando se alcance el siguiente carcter espacio en blan
tanto, glymxck y r2d2 son palabras. Emplearemos una variable llama co, haremos palabra igual a NO de nuevo.
da palabra, que nos indicar si estamos o no en una. Cuando encontremos Estudie el programa, para comprobar si es capaz de contar palabras aun
un espacio en blanco (un espacio, tabulado o nueva lnea) reconoceremos que cuando se incluya ms de un carcter en blanco entre dos consecutivas.
se ha alcanzado el final de una palabra. En ese momento, el prximo carc Si desea usar el programa con un fichero, utilice reenvos.
ter no blanco localizado se considerar el comienzo de una nueva palabra,
y se incrementar el contador correspondiente en 1. El programa es el siguiente:

#include <stdio.h> Una caricatura con caracteres


#define SI 1
#define NO 0 Ocupmonos ahora de algo menos utilitarista y ms decorativo. Nos pro
main()
{ ponemos crear un programa que sea capaz de dibujar figuras compuestas por
int ch; /* para capturar caracteres caracteres. Cada lnea de la salida se compondr de una fila de caracteres
long nc = 0L; /* numero de caracteres */ nica, es decir, sin interrupciones. El programa deber permitirnos decidir
int nl = 0; /* numero de lineas
int np = 0; /* numero de palabras
el carcter, as como la longitud y posicin de la fila. El programa aceptar
int palabra = NO; / * == SI si ch esta en una palabra */ datos hasta que lea un carcter EOF. En la figura 7.5 presentamos el listado.
Supongamos que llamamos al programa ejecutable monigotes. Para eje
while ((ch = getchar( )) != EOF)
cutar dicho programa, teclearemos su nombre. A continuacin introducire
{ mos un carcter y dos nmeros; el programa responder; entonces se intro
nc++; /* cuenta caracteres */ ducir un nuevo grupo de datos, al que seguir una nueva respuesta del pro
if (ch == '\n') grama, y as sucesivamente hasta que se introduzca un carcter EOF. En un
nl++; /* cuenta lineas */
if (ch != ' ' && ch != '\n' && ch != '\t' && palabra == NO) sistema UNIX este intercambio podra ser;

185
% monigotes : 31 49
B 10 20 : 30 49
BBBBBBBBBBB
: 29 49
Y 12 18 : 27 49
YYYYYYY : 25 49
[control-d] : 30 49
% : 30 49
/ 30 49
/* Monigotes */ : 35 48
/* este programa dibuja figuras rellenas de caracteres */
: 35 48
#include <stdio.h>)
#define LONGMAX 80
main() Si ejecutamos ahora el comando monigotes < fig, la salida obtenida es
{ la que se puede observar en la figura 7.6.
int ch; /* caracter a imprimir */
int princ, final;/* puntos de comienzo y final*/
int cont; /* contador de posicion */

while((ch = getchar( )) != EOF) /* lee un caracter */


{
if (ch != '\n') /* salta caracter nueva linea */
{
scanf("%d %d", &princ, &final); /* lee limites */
if (princ > final || princ < 1 | | final > LONGMAX)
printf("Limites no validos.\n);
else
{
cont = 0;
while (++cont < princ)
putchar(' '); /* imprime blancos hasta comienzo */
while <cont++ <= final)
putchar(ch); /* imprime caracteres hasta final */
putchar('\n'); /* termina linea y comienza otra */
} /* fin del else */
} /* fin del if de ch */
} /* fin del bucle while */
/* fin de main */ Figura 7.6
Una salida del programa de caricatura
Figura 7.5
Programa de caricaturas
(Nota: La relacin vertical a horizontal en los caracteres es diferente en
impresoras y pantalla. Esto produce que figuras como la anterior aparezcan
Como se observa, el programa ha impreso el carcter B en las columnas comprimidas en la vertical, cuando se imprimen, en comparacin con las ob
10 a 20, y el carcter Y en las columnas 12 a 18. Por desgracia, si se emplea tenidas en una pantalla.)
el programa interactivamente de esta manera, las rdenes se entremezclan con
las salidas. Si deseamos que el dibujo no tenga interferencias, podemos crear Anlisis del programa
previamente un fichero que contenga los datos, y emplearlo en reenvo como Este programa es corto, pero bastante ms complicado que los ejemplos
datos para el programa. Supongamos, por ejemplo, que hemos creado un dados anteriormente. Observemos con detalle algunos de sus elementos.
fichero fig con los datos siguientes:
Longitud de las lneas
_ 30 50
| 30 50 Hemos limitado el programa para que no se pueda escribir ms all de
| 30 50 la columna 80, ya que la anchura estndar de la mayor parte de monitores
| 30 50 de vdeo y de las impresoras de tamao normal es precisamente 80 caracte
| 30 50 res. Sin embargo, puede redefinir el valor de LONGMAX si desea usar el
| 30 50
= 20 60 programa en un perifrico de mayor longitud de lnea.

187
Estructura del programa
El programa se compone de tres bucles while, una sentencia if y otra if.
else. Veamos lo que hace cada una.

while ( (ch = getchar( )) != EOF)

El propsito del primer bucle while es permitirnos leer varios conjuntos)


de datos. (Cada conjunto consiste en un carcter y dos enteros que indican
el comienzo y final de lnea.) Se lee el carcter primero, lo que permite com
binar la lectura con una comprobacin de fin de fichero (EOF). Cuando se
encuentra un carcter EOF, el programa se detiene sin leer valores de princ
y final. En los dems casos, se leen dos valores para princ y final, respectiva
mente, por medio de scanf( ), valores que son procesados a continuacin;
con esto se completa el bucle. Despus se lee un nuevo carcter y se repite
el proceso.
Observe que utilizamos dos sentencias, no una, para leer los datos. Por
qu no hemos empleado una sola?
scanf("%c %d %d", &ch, &princ, &final);

Supongamos que se hubiera hecho as. Imagine lo que sucedera cuando El cuerpo principal del programa est formado por la sentencia compuesta
el programa acaba de leer la ltima lnea de datos de un fichero. Cuando que sigue a else
el bucle comience de nuevo, lo nico que quedar en el fichero ser el carc
ter EOF. La funcin scanf( ) leer dicho carcter y lo asignar a ch. A conti cont = 0;
nuacin intentar leer un valor para princ, pero ya no hay nada en el fichero.
Entonces el ordenador musita una queja, y el programa muere ignominiosa En primer lugar, tenemos un contador que igualamos a 0.
mente. Por el contrario, si separamos la lectura del resto del carcter, damos
al ordenador una oportunidad para comprobar el EOF antes de intentar leer while (++cont < princ)
algo ms. putchar(' ');
if ( c h ! = ' \ n ' )
A continuacin comienza un bucle while que imprime espacios en blanco
hasta la posicin princ; por ejemplo, si princ es 10, se imprimen 9 espacios;
El propsito de la primera sentencia if es, simplemente, hacer ms senci por tanto, el carcter empieza a imprimirse en la columna 10. Observe que
lla la entrada de datos. Explicaremos cmo funciona en la siguiente seccin. empleamos la forma prefijo del operador incremento junto con el operador
< para conseguir este resultado. Si hubisemos usado cont + + < princ,
if (princ > final || princ < 1 || final > LONGMAX) la comparacin se habra realizado antes de incrementarse cont, imprimien
printf("Limites no validos.\n);
else do un espacio adicional.

while ( cont++ <= final)


La sentencia if-else est colocada con la intencin de evitar que el progra putchar(ch);
ma juegue con valores peligrosos de princ y final. Tambin tratamos este punto
en la siguiente seccin; no obstante, observe que hemos empleado operado El segundo bucle while de este bloque est dedicado a imprimir el carc
res lgicos y de relacin para investigar la eventual existencia de tres posibles ter desde la columna princ a la columna final. Esta vez hemos usado la for
peligros. ma sufija y el operador < =. Esta combinacin produce el efecto deseado

189
La funcin getchar( ) lee el primer carcter que encuentra, sea alfabti
de imprimir el carcter hasta la posicin final inclusive. Puede comprobarlo co, un espacio, una nueva lnea, o cualquier otra cosa. La funcin scanf( )
fcilmente por ensayo y error. hace exactamente lo mismo si se emplea el formato %c (carcter). Sin em
bargo, cuando se usa scanf( ) con formato %d (entero), sta ignora los espa
putchar('\n'); cios y los caracteres nueva lnea. Por ello, no hay problemas en colocar cual
quier nmero de espacios o caracteres nueva lnea entre el carcter ledo con
Finalmente, se utiliza putchar( \ n) para acabar la lnea y comenzar una getchar( ) y el siguiente entero ledo por scanf( ). Adems, scanf( ) lee ci
nueva. fras hasta que encuentra un carcter que no sea numrico; por ejemplo, un
espacio, un carcter nueva lnea o un carcter alfabtico. De ah que necesi
temos un espacio o nueva lnea entre el primero y el segundo entero, de for
Disposicin de los datos ma que scanf( ) pueda advertir que se ha acabado de escribir el primer n
Dedicamos este apartado a una cuestin importante: la interaccin entre mero y comienza el siguiente.
los datos de entrada y el programa que se est ejecutando. Es ste un punto Con esto queda explicado por qu debemos dejar un espacio o un carc
a considerar cuando se escribe un programa determinado. ter nueva lnea entre un carcter y el entero siguiente o entre los dos enteros.
Los datos empleados en la entrada debern tener una forma compatible Pero, por qu no podemos colocar caracteres de este tipo entre el ltimo
con las funciones de entrada utilizadas por el programa. La introduccin de entero de un conjunto de datos y el siguiente carcter? La razn es la siguiente:
datos de forma correcta es responsabilidad del usuario, por lo menos en un cuando se recomienza de nuevo el bucle while, getchar( ) actuar exactamente
programa sencillo. Un programa ms sofisticado, sin embargo, debe inten all donde scanf( ) acab; por tanto, leer justamente el carcter siguiente
tar cargar con parte de la responsabilidad de esta introduccin. En nuestro al entero ledo, aunque sea un espacio, un carcter nueva lnea o cualquier
caso, la forma ms clara para introducir los datos es:
otro.
H 10 40 Si tuviramos que seguir al pie de la letra las demandas de getchar( ), de
I 9 41 beramos preparar una estructura de datos como la siguiente:
hlO 50a20 60yl0 30
es decir, el carcter seguido por las posiciones de las columnas de comienzo
y final. Pero nuestro programa tambin acepta esta forma:
sin dejar ninguna separacin entre el segundo entero de cada grupo y el si
H guiente carcter; pero la apariencia de esta disposicin de datos es franca
10 mente horrible, y hace que el 50 parezca que pertenece a la a, y no a la h.
40
I Por eso colocamos tambin la lnea
9
41 if (ch != '\n')

o sta: que hace que el programa salte en el caso de que ch sea un carcter nueva
lnea. Este ltimo if nos permite usar
H 10 40I 9 41
h10 50
pero no sta: a20 60
y10 30
H 10 40 I 9 41
en lugar de la disposicin anterior, con un carcter nueva lnea entre el 50
Por qu hay espacios opcionales y otros que no lo son? Por qu puede y la a. El programa lee el carcter nueva lnea, no hace nada con l y a conti
haber un carcter nueva lnea, pero no un espacio entre el ltimo entero de nuacin busca un nuevo carcter.
un conjunto de datos y el primer carcter del siguiente? Estas cuestiones van
ms all de los propios lmites del programa. Para contestarlas, deberemos Comprobacin de errores
repasar el funcionamiento de getchar( ) y scanf( ). Hemos considerado tambin el problema de tropezamos con un usuario
perverso o simplemente desconocedor del funcionamiento del programa. La

191
un operador en dos partes que contiene tres operandos. El siguiente ejemplo
cuestin es que se debe realizar un control de los datos de entrada antes de calcula el valor absoluto de un nmero:
permitir que el ordenador trabaje con ellos. Una de las tcnicas usadas en
este sentido es la de comprobacin de errores. Se trata de que el ordena x=(y<0) ? -y : y;
dor compruebe el dato y decida si es aceptable dentro de su contexto. Una
iniciativa hacia este objetivo seran las dos lneas incluidas en el programa: La expresin condicional abarca la porcin de sentencia entre el signo =
y el punto y coma. El significado de la sentencia es el siguiente: si y es menor
if (princ > final || princ <1 || final > LONGMAX) ,
que 0 entonces x =- =
y; si no lo es, x y. Expresado en forma if-else sera:
printf("Limites no validos.\n") ;
if (y < O)
que forman parte de una estructura if-else que indica que la parte principal x = -y;
del programa se ejecutar slo cuando todos los test if sean falsos. else
x = y;
Contra qu nos hemos protegido? En primer lugar, no tiene sentido que
la posicin de comienzo sea mayor que la posicin final; los terminales im
primen normalmente de izquierda a derecha, y no en sentido contrario. La La forma general de la expresin condicional es expresin1 ? expresin2
expresin princ > final comprueba este posible error. En segundo lugar, la : expresin3
primera columna de una pantalla es la columna 1; no se puede escribir a la Si expresin1 es cierta (distinta de 0), la expresin condicional total toma
izquierda del margen izquierdo; la expresin princ < 1 nos preserva del error el valor de la expresin2; si expresin1 es falsa (0), toma el valor de la
subsiguiente. Finalmente, la expresin final > LONGMAX comprueba que expresin3.
nos hemos pasado del margen derecho. Se puede utilizar la expresin condicional cuando se tiene una variable
Hay alguna otra posible fuente de error? Podemos dar otros valores que puede tomar dos valores posibles. Un ejemplo tpico es hacer una varia
errneos a princ y final? Bien, siendo muy retorcidos, podramos intentar
que princ fuese mayor que LONGMAX. Pasara este valor nuestro test? No. ble igual al mayor de dos valores:
Es cierto que no comprobamos este error directamente; sin embargo, supon
max = (a > b) ? a : b;
gamos que princ es mayor que LONGMAX. En ese caso, final sera tambin
mayor que LONGMAX (en cuyo caso cazamos el error) o bien sera menor
que LONGMAX. Pero si final es menor que LONGMAX, tambin ser me En realidad, las expresiones condicionales no son necesarias, ya que se
nor que princ, en cuyo caso atrapamos el error en el primer test. Otra posible puede ejecutar la misma tarea con sentencias if-else; sin embargo, son ms com
fuente de error sera que final fuese menor que 1. Dejamos al lector que com pactas, y generan usualmente cdigos en lenguaje mquina ms compactos.
pruebe que este error tampoco cuela.
La parte de programa dedicada al test es muy simple. Si en alguna oca
sin disea un programa para una aplicacin seria, deber dedicar ms es
fuerzo a esta parte concreta. Por ejemplo, convendra que en los mensajes RESUMEN: EL OPERADOR CONDICIONAL
de error se identificara qu valor o valores son errneos y por qu. Adems,
puede proyectar su propia personalidad sobre los mensajes. Algunas posibi I. El operador condicional: ?:
lidades seran: Este operador tiene tres operandos, cada uno de los cuales es una expresin.
El valor 897654 de FINAL es algo superior al limite de pantalla. Se organizan de la siguiente forma: expresinl ? expresin2 : expresin3. El
Que cosas! El valor PRINC es mayor que FINAL. Use otro, por favor valor de la expresin total es igual al valor de la expresin2, si expresin1 es
EL VALOR DE COMIENZO HA DE SER MAYOR DE 0, ESTUPIDO. cierta, mientras que si es falsa toma el valor de la expresin3.

La redaccin concreta del mensaje es cuestin de gustos, por lo que la II. Ejemplos:
dejamos al usuario. (5 > 3 ) ? 1 : 2 tomael valor 1
(3 > 5 ) ? 1 : 2 toma el valor 2
Operador condicional: ?: (a > b ) ? a : b toma el valor mayor entre a y b

El C ofrece una forma abreviada de expresar la sentencia if-else. Se deno


mina expresin condicional y emplea el operador condicional ?:. Es ste

193
Nos sentimos un poco perezosos y nos detuvimos en la e. Veamos c
Eleccin mltiple: switch y break mo funciona en ejecucin antes de pasar a explicar sus distintas partes
Tanto el operador condicional como la construccin if-else permiten in- Deme una letra y respondere con un nombre de animial
cluir en un programa elecciones entre dos alternativas con gran facilidad. En que comience por ella.
ocasiones, sin embargo, necesitamos un programa que elija una entre varias Pulse una letra; para terminar pulse #.
a [return]
alternativas. Ya hemos visto que se puede realizar este tipo de eleccin con aranillo, oveja salvaje del Caribe
una cadena if-else if. . . else; pero en la mayora de los casos es ms Introduzca otra letra o un #.
d [return]
conveniente emplear la sentencia switch que ofrece el C. Se presenta a conti- destemplat, pinguino rojo de Kenia
nuacin un ejemplo que demuestra cmo funciona. Este programa lee una Introduzca otra letra o un #.
letra, y responde imprimiendo el nombre de un animal que comienza con di r [return]
Humm. ... ese no me lo se
cha letra. Introduzca otra letra o un #.
Q [return]
Solo me trato con letras minusculas
/* animales */ Introduzca otra letra o un #.
main() # [return]
{
char ch;
printf("Deme una letra y respondere con "); La sentencia switch funciona de la siguiente forma: a continuacin de la
printf("un nombre de animal\nque comience por ella.\n"); palabra switch hay una expresin entre parntesis; dicha expresin se evala,
printf("Pulse una letra; para terminar pulse #. \n");
y, en nuestro caso, el valor que posea se asigna finalmente a ch. A continua
while((ch = getchar()) != '#') cin, el programa rastrea la lista de etiquetas (case a :, case b :, etc.,
{ en nuestro ejemplo) hasta que encuentra una que corresponda a dicho valor;
if (ch != '\n') /* salta caracter nueva linea */
{ entonces se transfiere el control del programa a dicha lnea. Y qu sucede
if (ch >= 'a' && ch <= 'z') /* solo minusculas */ si ninguna encaja? En ese caso se utiliza la lnea marcada default:; el progra
switch (ch) ma salta all. En cualquier otra circunstancia, el programa contina con la
{
case 'a' : sentencia que sigue al bloque switch.
printf("aranillo, oveja salvaje del Caribe\n"); Cul es la misin de la sentencia break? Esta sentencia hace que el pro
break; grama se salga del switch y se dirija a la sentencia situada inmediatamente
case 'b' :
printf("babirusa, cerdo salvaje de Malasia\n");
despus del mismo (vase figura). Si no se hubiese colocado la sentencia break,
break; se ejecutaran todas la sentencias situadas entre la etiqueta correspondiente
case 'c' : y el final del switch. Por ejemplo, si eliminamos todas las sentencias break
printf("chascalote, ballena gigante del Amazonas\n" ) ; de nuestro programa y lo ejecutamos utilizando la letra d, obtendramos la
break;
case 'd' : siguiente salida:
printf("destemplado, pinguino rojo de Kenia\n");
break;
case 'e' : Deme una letra y respondere con un nombre de animal
printf("equigobo, camello siberiano\n"); que comience por ella.
break; Pulse una letra; para terminar pulse #.
default : d [return]
printf("Humm.... ese no me lo se.\n"); destemplat, pinguino rojo de Kenia
break; equigobo, canello siberiano
} Humm. ... ese no me lo se
else Introduzca otra letra o un #.
printf ("Solo me trato con letras minusculas. \n") ; # [return]
printf("Introduzca otra letra o un #.\n");
} /* fin del if de nueva linea */
} / * fin del while * /
} Como observar, todas las sentencias desde case d hasta el final del
switch han sido ejecutadas.
Figura 7.7
Programa de nombres de animales 195
Si conoce el lenguaje PASCAL, habr asociado inmediatamente la sen- Se pueden utilizar etiquetas sin sentencias cuando deseamos que varias
tencia switch a una similar que hay en este lenguaje, case. La diferencia mas etiquetas den el mismo resultado. As, el fragmento
importante entre ambas es que la sentencia switch requiere el uso de un break case F :
si se desea procesar nicamente la sentencia etiquetada. case f :
Las etiquetas de un switch deben ser constantes de tipo entero (incluyen- printf("ferocissimus, lombriz de tierra mediterranea\n");
break;
do char) o bien expresiones de constantes (es decir, expresiones que conten
gan nicamente constantes). En ningn caso se pueden emplear variables en hara que tanto F como f produjesen el mismo mensaje. Si se pulsa F, por
las etiquetas. La expresin encerrada entre dos parntesis debe tener valor ejemplo, el programa saltara a dicha lnea. Al no encontrar sentencias all,
entero (incluyendo tambin al tipo char). As, la estructura general de un switch el programa continuara ejecutndose hasta alcanzar el break.
sera: En el programa hay tambin otros dos detalles que conviene mencionar.
El primero viene determinado por la forma interactiva que hemos decidido
switch(expresion entera) darle. Nos referimos al empleo de # en lugar de EOF como seal de stop.
{ Cualquier novicio en ordenadores se sentira abrumado si se le pide introdu
case constante1 : cir un carcter EOF o incluso un carcter de control, pero el smbolo # est
sentencias;
break; (opcional) bastante claro (incluso para ellos). Al no ser necesario que el programa lea
case constante2 : un EOF, no hay tampoco necesidad de declarar ch de tipo int. Por otra par
sentencias; te, hemos colocado una sentencia if que hace que el programa ignore carac
break; (opcional)
teres nueva lnea. Tambin esta segunda caracterstica es una concesin a la
default : interactividad del programa. Si no se hubiese introducido esta sentencia
sentencias; if, cada vez que pulsamos la tecla [return] se procesara como carcter.
break; (opcional) Cundo debemos emplear un switch y cundo una construccin else-if?
}
Con frecuencia no tenemos eleccin. No se puede emplear switch cuando la
eleccin est basada en una comparacin de variables o expresiones de tipo
float; tampoco conviene usar un switch si la variable puede estar comprendi
da en un cierto rango. Es muy simple escribir
if (integer < 1000 && integer > 2)

pero intentar cubrir esta posibilidad con un switch implicara teclearse eti
quetas casi para todos los enteros comprendidos entre 3 y 999. Sin embargo,
en general el switch es ms eficiente en la ejecucin del programa.

RESUMEN: ELECCION MULTIPLE CON switch

I. Palabra clave: switch.


II. Comentarios generales:
El control del programa se transfiere a la sentencia cuya etiqueta tenga el mis
mo valor que la expresin evaluada. El programa contina ejecutando senten
cia a sentencia hasta que se redirige de nuevo con un break. Tanto la expresin
como las etiquetas deben tener valor entero (incluyendo el tipo char), y las eti
quetas deben ser constantes o expresiones formadas nicamente por constan
Suponemos en ambos casos tes. Si no se encuentra ninguna etiqueta con el valor de la expresin, el control
qu nmero tiene el valor 2 se transfiere a la sentencia etiquetada default, si existe. En caso contrario, el
control pasa a la sentencia inmediatamente despus de la sentencia switch.
F igu ra 7.8
Flujo de programa en switches, con y sin break. 197
III. Forma: Cuestiones y respuestas
switch ( expresion ) Cuestiones
{
case etiq1 : sentencia1 1. Indique de entre las siguientes proposiciones cules son ciertas y cules son falsas:
case etiq2 : sentencia2 a. 100 > 3
default : sentencia3
} b. a > c
c . 100 > 3 && a > c
d. 100 > 3 || a > c
e. ! (100 > 3)
Puede haber ms de dos sentencias con etiquetas, y el caso default es op 2. Constryase una expresin para indicar las siguientes condiciones:
cional. a. nmero es igual o mayor que 1 pero menor que 9.
b. c h no es una q ni una k .
c. nmero est entre 1 y 9, pero no es 5.
IV. Ejemplo: d. nmero no est comprendido entre 1 y 9.
3. El siguiente programa tiene expresiones de relacin innecesariamente complejas,
switch ( letra ) as como algunos errores. Simplifquelo y corrjalo.
case a :
case i : printf ("%d es una vocal\n") ; main() /* 1 */
case c : { /* 2 */
case s : printf("%d esta en la palabra \"casi\"\n", letra); int peso, altura; / * en kilogramos y centmetros 3 */
default : printf("Que usted lo pase bien.\n"); /* 4 */
} scanf("%d", peso, altura); /* 5 */
i f (peso < 40) /* 6 */
if (altura >= 172) /* 7 */
Si letra tiene el valor a o i, se imprimen los tres lenguajes; si es c printf("Es ud. muy alto para su peso. \n); /* 8 */
else if (altura < 172 && > 164) /* 9 * /
o n, se imprimen los dos ltimos. Cualquier otro valor imprime nicamen printf("Es ud. alto para su peso.\n"); / * 10 */
te el ltimo mensaje. else if (peso > 100 && (peso <= 100)) / * 11 */
if ( (altura >= 148)) /* 12 */
printf("Es bastante bajopara su peso.\n"); /* 13 */
/* 14 */
printf("Su peso es ideal.\n"); /* 15 */
El material de este captulo permite la preparacin de programas mucho } / * 16 * /
ms poderosos y ambiciosos que antes. Simplemente compare los ejemplos
de este captulo con alguno de los dados anteriormente, y comprobar la exac
titud de nuestra afirmacin. Pero queda todava un poco por aprender, y pa Respuestas
ra ello hemos preparado algunas pginas ms para su lectura y entretenimiento.
1. Son ciertas a y d.
2. a. numero > =1 && numero < 9
b. ch != q && ch != k
Nota: ch != q || ch != ksera siempre cierta, ya que si ch fuera una q no sera
una k, siendo, por tanto, cierta la segunda alternativa, lo que implicara que la combi
Hasta ahora ahora hemos aprendido nacin completa sera cierta a su vez.
c. nmero > 1 && nmero < 9 && nmero != 5
Cmo escoger entre ejecutar una sentencia o no: if d. ! (nmero > 1 && nmero < 9)
Cmo escoger entre dos alternativas: if-else o bien
nmero < = 1 || nmero > = 9
Cmo escoger entre alternativas mltiples: else-if, switch
Los operadores de relacin: > > = = = < = < ! = Nota: si decimos que un nmero no est comprendido entre 1 y 9, es lo mismo que decir
que es menor o igual que 1 mayor o igual que 9. La segunda forma es ms difcil
Los operadores lgicos: && || ! de comprender a primera vista, pero como expresin es ligeramente ms sencilla.
El operador condicional: ?: 3. La lnea 5 debe ser scanf (%d %d, &peso, &altura); no olvide los & en scanf( ). Asi
mismo, esta lnea debera estar precedida por una sentencia que solicitase los valores.
Lnea 9: lo que se indica en esta linea es (altura < 172 && altura > 164). Sin embargo,

199
la primera parte de la expresin no es necesaria, ya que altura debe ser menor de 172 por
el else-if colocado en primer lugar. As pues, una simple sentencia (altura > 164) hubiera
bastado.
Lnea 11: la condicin es redundante: la segunda subexpresin (peso no es menor ni igual
a 100) significa lo mismo que la primera. Todo lo que se necesita es un simple (peso >
100), pero el mayor problema no est ah; la lnea 11 est unida al if incorrecto. Por la
estructura del programa se ve claramente que se pretenda que este else estuviese unido a
la lnea 6. Sin embargo, est asociado al if de la lnea 9, ms reciente, segn la regla antes
comentada. Por tanto, la lnea 11 se alcanzar cuando peso sea menor de 40 y altura sea
164 o menos. Ello hace imposible que peso sea mayor que 100 cuando se llegue a esta sen
tencia.
Las lneas 7 a 9 debieran estar encerradas entre llaves. As, la lnea 11 sera una alternativa
de la lnea 6, no de la 9.
Lnea 12: simplifique la expresin a (altura < 148)
Lnea 14: este else est asociado con el ltimo if, colocado en la lnea 12. Si se encierran
las lneas 12 y 13 con llaves, se forzara a que el else quedase asociado con el if de la lnea 11.
Observe que el mensaje final se imprime solamente en aquellos pesos comprendidos entre
40 y 100 kilogramos.
8
Bucles y
tirabuzones
En este captulo encontrar:

El bucle while
Terminacin de un bucle while
Algoritmos y seudocdigo
El bucle for
For da flexibilidad
El operador coma
Zenon encuentra el bucle for
Un bucle con condicin de salida: do while
Con qu bucle nos quedamos?
Bucles anidados
Otras sentencias de control: break, continue, goto
Evite el goto
Arrays
Una cuestin sobre entradas
Resumen
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

203
Bucles y tirabuzones El bucle while
Ya hemos utilizado ampliamente este bucle; nos limitaremos ahora a re
pasarlo con un sencillo programa (quiz demasiado simple) que adivina n
CONCEPTOS meros.
/* adivinanumeros */
Bucles /* un programa para acertar numeros bastante ineficiente */
Bucles anidados #include <stdio.h>
main()
Saltos en el programa {
Empleo de bucles con arrays int sup = 1 ;
char respuesta;

PALABRAS CLAVE printf("Escoja un numero del 1 al 100. Tratare de ");


printf("acertarlo. \nResponda s si es correcto y n");
printf("\nsi me equivoco.\n");
printf ("Hmm... su numero es el %d?\n", sup);
while, do, for, break, continue, goto while((respuesta = getchar()) != 's') /* toma respuesta */
if (respuesta != '\n') /* ignora caracter nueva linea */
OPERADORES printf("Entonces debe ser el %d ; correcto ?\n", ++sup) ;
printf("Sabia que lo conseguiria!!!\n");
}
+ = -= * = /= % = ,

Observe la lgica del programa. Si se responde s, el programa abandona


el bucle y se dirige a la sentencia printf final. El programa le pide que respon
da con una n cuando su suposicin no es correcta; pero de hecho, cualquier
respuesta que no sea s enviar el programa a realizar una nueva iteracin dentro
del bucle. Sin embargo, si el carcter es nueva lnea, queda ignorado. Cual
quier otro carcter produce que se imprima como suposicin el siguiente en
tero. (Qu hubiese sucedido si se empleara sub+ + en lugar de + +sup?)
La parte de sentencia if(respuesta != " \ n") indica al programa que ig
nore los caracteres nueva lnea que se transmiten al usar la tecla [enter].
A medida que nos vamos exigiendo tareas ms complejas, el flujo de los El bucle while no necesita llaves, ya que la sentencia if, expresada en dos
programas se vuelve ms enmaraado. Necesitamos estructuras y sentencias lneas, cuenta como una nica sentencia.
para controlar y organizar el funcionamiento de los programas. El lenguaje Habr observado probablemente que ste es un programa bastante est
C nos facilita una serie de comandos para ayudarnos a desarrollar estos ex pido. Est escrito correctamente en C, y consigue la tarea que se le ha enco
tremos. Hasta ahora ya hemos visto lo til que resulta el bucle while cuando mendado, pero su forma de hacerla es muy ineficiente.
se necesita repetir una accin varias veces. En C existen otras dos estructuras Este ejemplo puede servir para indicar que la correccin no es el nico
en bucle adicionales: el bucle for y el bucle do... while. En este captulo com criterio por el cual se ha de juzgar un programa; adems, es importante la
probaremos el funcionamiento de estas dos estructuras de control, e intenta
eficiencia. Volveremos ms adelante con este programa e intentaremos ha
remos sacarles el mximo partido; para ello discutiremos tambin el empleo
de los operadores break, continue, goto y coma, los cuales pueden ser em cerlo un poco mejor.
pleados, asimismo, para controlar el flujo del programa. Tambin tratare La forma general del bucle while es
mos de pasada los arrays que a menudo se emplean en asociacin con los
bucles. while (expresin)
sentencia

Nuestros ejemplos han utilizado expresiones de relacin en la parte llama


da expresin, pero sta puede ser una expresin cualquiera. La parte de sen-

205
Este fragmento no es ninguna mejora. En l s se cambia el valor de ndi
tencia puede estar constituida por una nica sentencia que acabe en un punto ce, pero en direccin contraria! Por lo menos esta versin acabar termi
y coma de terminacin o bien una sentencia compuesta encerrada entre lla nando cuando el ndice alcance un valor inferior al ms negativo permitido
ves. Si la expresin es cierta (en general, si su valor es distinto de 0), la sen por el sistema.
tencia se ejecuta una vez y la expresin se evala de nuevo para comprobar El bucle while est dentro de la categora de bucles condicionales, em
si su certeza permanece intacta. Este ciclo de test y ejecucin se repite hasta pleando una condicin de entrada. Se llama condicional, ya que la ejecu
que la expresin se vuelve falsa (en general, 0). Cada ciclo realizado se deno cin de las sentencias depende de la condicin que se describe en la parte de
mina una iteracin. La estructura es muy similar a la de una sentencia if: expresin: Indice es menor que 5?; el ltimo carcter ledo es un EOF? La
la diferencia principal es que en la sentencia if el test y, posiblemente, la eje expresin forma una condicin de entrada, porque la condicin debe cum
cucin se realizan una sola vez, mientras que en el bucle while se puede repe plirse antes de acceder al cuerpo del bucle. En la siguiente situacin el bucle
tir un gran nmero de veces. no se ejecutar ni una sola vez, ya que la condicin es falsa de principio.

indice = 10;
while ( indice++ < 5)
printf("Que ud. lo pase bien o mejor aun\n");

Si cambia la primera lnea a


indice = 3;

el programa funcionar.

Algoritmos y seudocdigo
Figura 8.1
Estructura de un bucle while Bien, es el momento de volver a nuestro casi intil programa de acertar
nmeros. El defecto de este programa no est en la codificacin per se, sino
en el algoritmo empleado, es decir, el mtodo utilizado para aceptar el
Terminacin de un bucle while nmero. Podemos representar este mtodo de la forma siguiente:
Hay un punto CRUCIAL que debemos tener en cuenta cada vez que tra solicitar al usuario que piense un nmero
bajamos con bucles while. Cuando se construye un bucle de este tipo, se de el ordenador supone que es el 1
be incluir algo que vare el valor de la expresin de test, de manera que dicha
expresin acabe por ser falsa. En caso contrario, el bucle no finalizar nun while suposicin incorrecta, aumenta suposicin en 1
ca. Considere el siguiente ejemplo: Por cierto, esto es un ejemplo de seudocdigo, que es el arte de expre
indice = 1;
sar un programa en lenguaje normal imitando, por otra parte, el lenguaje
while (indice < 5) del ordenador. El seudocdigo es una forma til de trabajar con la lgica
printf("Buena suerte!\n"); de un programa; una vez que la lgica parece correcta, se puede uno dedicar
a los detalles de traduccin del seudocdigo a un cdigo de programacin
Este fragmento imprime su carioso mensaje indefinidamente, ya que no real. La ventaja del seudocdigo es que permite concentrarse en la lgica y
hay nada que altere el valor inicial de ndice, que estaba establecido en 1. organizacin del programa sin desperdiciar esfuerzos simultneamente preo
cupndose en cmo expresar las ideas en lenguaje de ordenador.
En nuestro caso, si queremos mejorar el programa debemos mejorar el
indice = 1; algoritmo. Un mtodo sera escoger un nmero a mitad de camino entre 1
while ( --indice < 5)
printf("La primavera ataca de nuevo!\n"); y 100 (50 es lo bastante prximo) y hacer que la parte humana del juego con
teste si la suposicin ha sido alta, baja o correcta. Si el usuario responde que

207
{ /* aumenta limite inferior si errado por defecto * /
min = sup + 1;
el nmero impreso es demasiado alto, quedaran automticamente elimina sup = (max + min)/2;
dos todos los nmeros entre 50 y 100. La siguiente suposicin del programa printf ("Demasiado bajo... Entonces sera %d\n", sup);
}
debera ser un nmero entre 1 y 49, procurando que estuviese en la parte cen else
tral de este rango. As, una nueva respuesta alto o bajo eliminara la mitad { / * indica respuestas correctas */
de los nmeros restantes y, continuando el proceso, el programa estrechara printf("No comprendo; utilice una s, una a ");
rpidamente los lmites hasta llegar al nmero correcto. Intentemos escribir printf("o una b.\n");
}
estas ideas en seudocdigo. Llamaremos max y min, respectivamente, al m }
ximo y mnimo valor alcanzable por el nmero. En principio habamos esta }
blecido estos lmites en 100 y 1, de modo que comenzaremos con ellos. printf("Sabia que lo conseguiria!!\n");
}
hacer max igual a 100 Figura 8.2
hacer max igual a 1 Programa para acertar nmeros
solicitar al usuario que piense un nmero
supongo (max + min)/2 El else final permite al usuario una nueva respuesta cuando la anterior
while sup incorrecta hacer: no se ajusta a una de las tres solicitadas. Observe tambin que hemos em
{ si sup es alto, hacer max igual a sup menos 1 pleado constantes simblicas para hacer ms sencillo el cambio de rango. Fun
si sup es bajo, hacer min igual a sup ms 1 ciona este programa? Veamos un ejemplo en el que hemos pensado el nme
nuevo sup es (max + min)/2}
ro 71.
Observe la lgica del programa: si la primera suposicin de 50 es alta,
el mximo valor posible del nmero sera 49; por el contrario, si 50 es dema Escoja un entero entre 1 y 100. Tratare de adivinarlo.
siado bajo, el mnimo valor sera 51. Responda s si he acertado, a si mi numero es demasiado alto
y b si demasiado bajo.
Realicemos ahora la traduccin de este seudocdigo a C. En la figura 8.2 Hmm... su numero es el 50?
se presenta el programa. n
No comprendo; utilice una s, una a o una b.
b
/*adivinanumeros2 * / Demasiado bajo... Entonces sera 75
/*una versin mejorada del anterior */ a
#include <stdio.h> Demasiado alto... Entonces sera 62
#define ALTO 100 b
#define BAJO 1 Demasiado bajo... Entonces sera 68
main() b
{ Demasiado bajo... Entonces sera 71
int sup = (ALTO + BAJ0) /2; s
int max = ALTO; Sabia que lo conseguira!!
int min = BAJO;
char respuesta;
printf ("Escoja un entero entre %d y %d. Tratare ", ALTO, BAJO);
printf("de adivinarlo.\nResponda s si he acertado, a si mi ") ; Puede ir algo mal en este programa? Est protegido contra usuarios que
printf("numero es demasiado alto\ny b si demasiado ">; tecleen respuestas incorrectas, de manera que esto no debe causar problemas.
printf("bajo.\n");
printf ("Hmm... su numero es el %d?\n", sup); La nica fuente de error posible es que alguien teclee a cuando debiera haber
while((respuesta = getchar ()) != 's') tecleado b, o viceversa. Por desgracia, no hay forma humana de hacer que
{ nuestro eventual usuario sea veraz o que no se equivoque. Sin embargo, hay
if (respuesta != '\n')
{ algunas reformas que podran ser suficientemente interesantes (por ejemplo,
if (respuesta == 'a') para distraer a su sobrinito de seis aos). Observe, en primer lugar, que esta
{ /* reduce limite superior si errado por exceso */ nueva aproximacin al problema necesita siete nmeros como mximo para
max = sup - 1 ;
sup = (max + min)/2; acertar cualquiera de ellos (cada suposicin reduce las posibilidades a la mi
printf("Demasiado alto... Entonces sera %d\n", sup); tad; siete suposiciones cubriran 27 1, 127, posibilidades, suficientes para
} manejar el centenar de nmeros de partida). Podemos, por tanto, modificar
else if (respuesta == 'b')
209
Aunque el formato utilizado es correcto, no es la mejor forma de abor
el programa para que cuente el nmero de suposiciones realizadas. Si este dar este tipo de situaciones, ya que las acciones que definen al bucle no estn
nmero supera a 7, se puede enviar un mensaje de protesta, y a continuacin agrupadas en un solo lugar. Ampliemos este punto.
restituir a max, min y al contador sus valores originales. Otros cambios fac Un bucle que se ha de repetir un nmero fijo de veces lleva implcitas tres
tibles para mejorar el programa podran ser modificaciones de las sentencias acciones. Se debe inicializar un contador, compararlo con un lmite e incre
if, de manera que aceptasen letras maysculas y minsculas. mentarlo cada vez que se atraviesa el bucle. La condicin de bucle while se
preocupa de la comparacin; por su parte, el operador incremento se encar
ga de cambiar el valor del lmite; tal como vimos anteriormente, se pueden
combinar estas dos acciones en una sola expresin, empleando + + <
RESUMEN: LA SENTENCIA while = NUMER. Por el contrario, la inicializacin del contador se debe reali
zar fuera del bucle, como hemos hecho en la sentencia cont = 1;. Como ve
I. Palabra clave: while mos, esta tercera accin hace correr el peligro de que alguna vez nos olvide
II. Comentarios generales: mos de inicializar el contador. Como es sabido, en programacin las cosas
La sentencia while crea un bucle que se repite hasta que la expresin de test malas que pueden suceder acaban sucediendo. Estudiaremos ahora una sen
se vuelve falsa, o 0. La sentencia while es un bucle con condicin de entrada; tencia de control que evita estos problemas.
la decisin de realizar una pasada ms del bucle se realiza antes de que ste
comience. Por tanto, es posible que el bucle se efecte cero veces. La parte
de sentencia dentro del bucle puede ser simple o compuesta.
I I I . Formato: El bucle for
while ( expresin )
sentencia Este bucle consigue agrupar las tres acciones en un solo lugar. Si emplea
La porcin sentencia se repite hasta que la expresin se vuelve falsa o 0. mos un bucle for, podramos sustituir el fragmento anterior por una sola sen
tencia:
IV. Ejemplos:
for (cont = 1; cont <= NUMERO; cont++)
while (n++ < 100) printf("Buena suerte!\n");
printf("%d %d", n, 2*n + 1);

while (chatos < 1000)


{ Esta expresin se ejecuta al final de cada bucle
chatos = chatos + ronda;
ronda = 2 * ronda;
}

En el ltimo ejemplo de bucle while se emplea una condicin indefinida;


no sabemos de antemano cuntas veces se va a ejecutar el bucle antes de que
la expresin se vuelva falsa. En muchos de nuestros ejemplos, por el contra
rio, hemos empleado bucles while con condiciones definidas, es decir, sabiendo
el nmero de repeticiones de antemano. Un ejemplo de este segundo caso po
dra ser:

cont = 1; /* inicializacion */
while (cont <=NUMERO) /* test */
printf("Buena suerte!/n"); /* accion */ La expresin se inicializa una vez antes de comenzar el bucle
cont++; /* incrementocont */
Figura 8.3
Estructura de un bucle for

211
ellos. Esta flexibilidad est sustentada en la forma en que las tres expresiones
de la especificacin for pueden utilizarse. Hasta ahora hemos empleado la
La primera expresin es una inicializacin; se realiza una sola vez, al comen primera expresin para inicializar un contador; la segunda, para expresar el
zar el bucle for. La segunda es una condicin de test; se evala antes de cada lmite del mismo, y la tercera, para incrementar el contador en 1. Cuando
ejecucin potencial del bucle; cuando la expresin es falsa (o, en general, 0) se emplea de esta forma, la sentencia for de C es prcticamente como las de
el bucle finaliza. La tercera expresin se evala al final de cada bucle. La ms que hemos mencionado. Pero existen muchas otras posibilidades, y a
hemos empleado para incrementar el valor de cont, pero no tiene por qu continuacin mostraremos 9 de ellas.
estar restringida a tal uso. El bucle for se completa con una sentencia simple
o compuesta. En la figura 8.3 se ejemplariza la estructura de este bucle. 1. Emplear el operador decremento para contar en sentido descendente
En el siguiente ejemplo empleamos un bucle for en un programa que im en lugar de ascendente.
prime una tabla de cubos:
for (n = 10; n > 0; n--)
printf("%d segundos ! \n", n) ;
/* for al cubo */ printf("Contacto!!!\ n " );
main()
{
int num; 2. Contar de dos en dos, de diez en diez, etc., si as se desea.
printf(" n n al cubo\n");
for (num = 1; num <= 6; num++)
printf("%5d %5d\n", num, num*num*num) ; for (n = 2; n < 60; n = n + 13)
} printf(" %d \n", n);

En este ejemplo, n se incrementara en 13 cada ciclo, imprimiendo


Este programa imprime los enteros del 1 al 6 y sus cubos: los dgitos 2, 15, 28, 41 y 54.
Por cierto, el C ofrece una notacin abreviada para incrementar
n n al cubo a una variable una cantidad fija. En lugar de
1 1
2 8 n = n + 13
3 27
4 64
5 125 podemos emplear
6 216
n += 13

La observacin de la primera lnea del bucle for nos informa inmediata


mente de todos los parmetros necesarios para el bucle: el valor inicial de El smbolo + = es el operador de asignacin aditivo, que suma
num, el valor final del mismo y el incremento que num sufre en cada ciclo. cualquier cosa que se encuentre a su derecha al nombre de la variable
Otro uso comn de un bucle for es hacer un contador de tiempo, con el situada a la izquierda. Vase el cuadro resumen para ms detalles.
fin de adaptar la velocidad del ordenador a niveles humanos. 3. Se pueden contar caracteres en lugar de nmeros
for (ch = 'a'; ch <= 'z'; ch++)
for (n = 1; n <= 10000; n++) printf("El caracter ASCII de %c es %d.\n" ch, ch) ;

Esta sentencia imprimira las letras de la a a la z junto con sus va


Este bucle hace que el ordenador cuente hasta 10000. El punto y coma
lores en cdigo ASCII. Este bucle funciona porque el lenguaje C al
solitario de la segunda lnea nos dice que el bucle no realiza ninguna otra
tarea. Podemos pensar en el punto y coma como una sentencia nula, es macena los caracteres como enteros, de modo que el fragmento, a efec
decir, una sentencia que no hace nada. tos del programa, est contando enteros en cualquier caso.
4. Se puede comprobar alguna otra condicin en lugar del nmero de
For de flexibilidad iteraciones. Nuestro programa de cubos anterior podra tener, en lu
gar de la sentencia
Aunque el bucle for se parece al bucle DO de FORTRAN, al FOR de PAS
CAL y al FOR...NEXT de BASIC, es mucho ms flexible que ninguno de for (num = 1; num <= 6; num++)

213
la siguiente: Observe que en el test est involucrado y, no x. Cada una de las
tres expresiones del bucle for pueden emplear diferentes variables.
for (num = 1; num*num*num <= 216; num++) Por otra parte, aunque el ejemplo es vlido, no es sntoma de un
buen estilo. El programa sera ms claro si no se mezclasen procesos
en la cual estamos limitando el tamao del bucle por el valor alcanza de cambio de ndice con un clculo algebraico.
do por los cubos, y no por el nmero de iteraciones. 7. Se pueden dejar una o ms expresiones en blanco (pero no se olvide
5. Se puede incrementar una cantidad en proporcin geomtrica en lugar del punto y coma). En este caso, asegrese simplemente de incluir dentro
de aritmtica; es decir, en vez de sumar una cantidad fija cada vez, del bucle alguna sentencia que antes o depus consiga que aqul finalice.
podemos multiplicar por una cantidad fija.

for (deuda = 100.0; deuda < 150.0; deuda = deuda*1.1)


printf("Su deuda asciende a % . 2f.\n"deuda); ans = 2; for (n = 3; ans <=25; )

Este fragmento multiplica deuda por 1.1 en cada ciclo, incremen ans = ans*n;
tndose por tanto un 10 por 100. La salida ser:
Durante la ejecucin de este bucle el valor de n ser constante e
Su deuda asciende a 100.00. igual a 3. La variable ans, por su parte, comenzar con un valor 2,
Su deuda asciende a 110.00. se incrementar a 6, 18 y obtendr un valor final de 54. (El valor 18
Su deuda asciende a 121.00. es menor que 25, de manera que el bucle for realizar una nueva itera
Su deuda asciende a 133.10.
Su deuda asciende a 146.41.
cin, multiplicando 18 por 3 para obtener 54.)
Por otra parte, la sentencia
Como ya habr imaginado, tambin hay una notacin abreviada
para multiplicar deuda por 1.1. La expresin en este caso es:
for ( ; ; )
printf("Quiero ser algo en la vida!\n");
deuda *= 1.1
es un bucle infinito, ya que un test vaco se considera cierto.
el cual ejecuta la referida multiplicacin. Como es lgico, el operador 8. No es necesario que la primera expresin inicialice una variable. En
*= es el operador de asignacin multiplicativo, el cual multiplica su lugar, puede ser, por ejemplo, una sentencia printf( ) de algn ti
la variable situada a su izquierda por cualquier otra cosa que se en
cuentre a su derecha (vase el cuadro resumen para mayor informa po. Recuerde que esta primera expresin se evala o ejecuta una sola
cin). vez, antes de entrar en el bucle.
6. Se puede utilizar cualquier expresin legal que se desee como tercera for (printf("Empiece a meter numeros\n"); num == 6; )
expresin. En todos los casos, la expresin se evaluar tras cada itera
cin. scanf("%d", &num);
printf("Ese es el que yo queria!!!\n");
for (x=1; y <= 75; y = 5*x++ + 10) Este fragmento imprimira el primer mensaje una vez, y continua
printf("%10d %10d\n", x, y);
ra aceptando nmeros hasta que se introdujera un 6.
Este fragmento imprime valores de x y de la expresin algebraica 9. Se pueden alterar los parmetros de las expresiones del bucle dentro
5*x + 10. La salida sera como sigue:
del mismo. Por ejemplo, supongamos un bucle cuyo fragmento inicial es:
1 55 for (n = 1; n < 10000; n += delta)
2 60
3 65 Tras algunas iteraciones, se puede tomar la decisin de que delta
4 70 es demasiado pequeo o demasiado grande. En ese caso, una senten
5 75
cia if en el interior del bucle puede cambiar el valor de delta. Por otra
parte, si nuestro programa es interactivo, delta puede ser alterado por
el usuario en mitad del funcionamiento del propio bucle.
En resumen, este bucle tiene una gran libertad en la seleccin de 215
diretes * = 2 es equivalente a diretes = diretes * 2
tiempo / = 2.73 es equivalente a tiempo = tiempo / 2.73
las expresiones que lo controlan, lo que le hace mucho ms til que reduce % = 3 es equivalente a reduce = reduce % 3
un simple repetidor de iteraciones. El potencial del bucle for se ve
an ms aumentado por una serie de operadores que discutiremos ms Por la parte derecha se pueden emplear nmeros o expresiones ms ela
adelante. boradas:

x *= 3*y + 12 es equivalente a x = x * (3*y + 12)


RESUMEN: LA SENTENCIA for
Estos operadores de asignacin tienen la misma prioridad que igual, es
I. Palabra clave: for decir, menor preferencia que + o *. Este hecho queda reflejado en el lti
mo ejemplo.
II. Comentarios generales: No es necesario, en realidad, utilizar estas formas; sin embargo, son ms
La sentencia for emplea tres expresiones de control, separadas por puntos y
coma, para controlar un proceso de bucle. La primera expresin de inicializa- compactas y producen un cdigo mquina mas eficiente que la forma lar
cin se ejecuta una sola vez antes de entrar al bucle. Si la expresin de test ga. En particular, resultan de utilidad cuando se intenta encajar algo en una
es cierta (distinta de 0), se ejecuta una vez el bucle completo. A continuacin especificacin de bucle for.
se evala la tercera expresin (actualizacin) y se comprueba de nuevo el test.
La sentencia for es un bucle con condicin de entrada; la decisin de realizar
una nueva pasada del bucle se toma antes de atravesarlo. Es, por tanto, posi
ble que el bucle no se ejecute ni una sola vez. La parte de sentencia de este
bucle puede estar formada por una sentencia simple o compuesta. El operador coma
III. Formato: El operador coma extiende an ms la flexibilidad del bucle for, ya que
permite incluir ms de una inicializacin o actualizacin dentro de las especi
for (incializacin; test; actualizacin) ficaciones del bucle. Por ejemplo, el siguiente programa imprime tarifas pos
sentencia
tales. (Suponemos que la tarifa es de 20 pesetas para los primeros 5 gramos
y 12 ms por cada 5 gramos adicionales.)
El bucle se repite hasta que test se vuelve falso o 0.
IV. Ejemplo:
/* tarifas postales */
for (n = 0; n < 10; n++ ) #define UNO 20
printf (" %d %d\n", n, 2*n+l ); #define OTRO 12
main()
{
int gramos, costo;

print(" gramos costo\n");


for(gramos=5, costo=UN0; gramos <=50; gramos+=5, costo+=OTRO)
printf("%5d %7d\n", gramos, costo);
OTROS OPERADORES DE ASIGNACION: + = , - = , }
*=,/=, %=

Hace algunos captulos mencionamos que existen varios operadores de Las cuatro primeras lneas de salida seran:
asignacin en C. Por supuesto, el ms bsico es =, el cual asigna simple
mente el valor de la expresin de su derecha a la variable de su izquierda. gramos costo
Los dems son operadores de actualizacin de variable. Todos ellos emplean 5 20
un nombre de variable a su izquierda y una expresin a su derecha. La va 10 32
riable queda asignada a un nuevo valor igual a su valor antiguo operado 15 44
por el valor de la expresin de su derecha. La operacin concreta a que se
somete la variable depende del operador. Por ejemplo:
Hemos usado el operador coma en la primera y tercera expresin. Su pre
sencia en la primera expresin hace que tanto gramos como costo se iniciali-
tanteo + = 20 es equivalente a tanteo = tanteo + 20
dimes - =2 es equivalente a dimes = dimes -2 cen. En la segunda expresin se conseguir que gramos se incremente en 5,

217
/= divide la variable i por la cantidad d.
y costo en 12 (el valor de OTRO) en cada iteracin. Todos los clculos nece % = proporciona el resto de la divisin entera de la variable i por la canti
sarios han sido realizados dentro de las especificaciones del bucle for. dad d.
El operador coma no est restringido a este bucle, pero es donde se utili
za con mayor frecuencia. El operador tiene otra propiedad ms: garantiza Ejemplo:
que las expresiones separadas por l se evalan de izquierda a derecha. Por conejos *= 1.6; es equivalente a conejos = conejos * 1.6;
tanto, gramos se inicializar antes que costo. En nuestro ejemplo concreto,
este punto no es importante, pero s lo sera si la expresin costo contuviese II. Miscelnea: el operador coma
la variable gramos. El operador coma enlaza dos expresiones hacindolas una sola, y garantiza que
Tambin se usa la coma como separador. Las comas de la sentencia la expresin situada a la izquierda se evala en primer lugar. Su empleo ms
comn est basado en la inclusin de ms informacin en la expresin de con
char ch, fecha; trol de un bucle for.

o de Ejemplo:
for (ronda = 2, chatos = 0; chatos < 1000; ronda *= 2)
printf("% d % d\n", tururu, tarara); chatos += ronda;

son separadores, no operadores coma.


Zenn encuentra el bucle for
Veamos cmo se puede utilizar el operador coma para resolver una vieja
paradoja. El filsofo griego Zenn argument en una ocasin que una fle
cha jams podra alcanzar su blanco. El razonamiento era el siguiente: pri
mero, la flecha recorre la mitad de la distancia hasta la diana; a continuacin
deber recorrer la mitad de la distancia restante; todava le queda por reco
rrer la mitad de lo que queda, y as hasta el infinito. Al componerse la tra
yectoria de un nmero infinito de partes, la flecha gastara un tiempo infini
to en alcanzar su destino. Sin embargo, estamos convencidos de que Zenn
no se ofrecera como blanco voluntario para demostrar su poderoso argu
mento.
Transformemos esta idea en nmeros, y supongamos que la flecha tarda
un segundo en recorrer la primera mitad de su vuelo; por tanto, tardara 1/2
segundo en viajar la mitad del resto, 1/4 de segundo en la mitad de lo que
quedaba, etc. Podemos representar el tiempo total como una serie infinita
Figura 8.4
El operador coma en el bucle for 1 + 1/2 + 1/4 + 1/8 + 1/16 +...
Escribamos un corto programa para averiguar la suma de los primeros
trminos.
/* Zenon */
#define LIMITE 15
RESUMEN: NUESTROS NUEVOS OPERADORES main() {

int cont;
I. Operadores de asignacin: float suma, x;
Cada uno de estos operadores actualiza la variable de su izquierda utilizando
el valor de su derecha en la operacin indicada. En los ejemplos siguientes abre for(suma=0.0, x=1.0, cont=1; cont <= LIMITE; cont++, x *= 2.0)
viamos izquierda y derecha como i y d, respectivamente. {
+= suma la cantidad d a la variable i. suma += 1.0/x;
printf("suma = %f en la etapa %d.\n", suma, cont);
-= resta la cantidad d de la variable i. }
*= multiplica la variable i por la cantidad d. }

219
se basa en cundo se lee el carcter nueva lnea. El bucle while imprime todos
La suma de los quince primeros trminos sera: los caracteres hasta el carcter nueva lnea exclusive, mientras que el do
while imprimira todos incluyendo el carcter nueva lnea. Unicamente despus
de haberlo impreso se comprobara el test de bucle. En resumen, en un bucle
do while, la accin va antes de la condicin del test.
La forma general de un bucle do while es:

do
sentencia
while (expresin);

La sentencia puede ser simple o compuesta.


Un bucle do while se ejecuta siempre una vez como mnimo, ya que el
test se realiza tras la ejecucin de la iteracin. Como ya se ha dicho, tanto
el bucle for como el while
Podramos continuar aadiendo ms trminos, pero ya se observa que
el total tiende a estabilizarse. Evidentemente, los matemticos han demos
trado que el total se aproxima a 2 conforme el nmero de trminos tiende
a infinito, exactamente igual que sugiere nuestro programa. Lo cual no deja
de ser un hecho afortunado, porque si Zenn estuviese en lo cierto, el movi
miento sera imposible. (Pero si el movimiento hubiera sido imposible, tam
poco habra existido Zenn.)
Qu se puede decir del programa en s mismo? Demuestra que se puede
emplear ms de un operador coma en una expresin. En este ejemplo hemos
inicializado suma, x y cont. Una vez establecidas las condiciones del bucle,
el programa en s es muy sencillo.

Bucle con condicin de salida: do while


Tanto for como while son bucles con condicin de entrada. La condicin Figura 8.5
del test se comprueba antes de cada iteracin del bucle. Existe tambin en Estructura de un bucle do while
C un bucle con condicin de salida, en el cual la condicin se comprueba
al final de cada iteracin. Esta variedad, llamada bucle do while, tiene el as
pecto siguiente:
pueden ejecutarse 0 veces, ya que el test est colocado antes de la ejecucin.
En general, debemos restringir el uso de bucles do while a los casos que re
do
{ quieran al menos una iteracin. Por ejemplo, podramos haber empleado un
ch = getchar () ; bucle do while en nuestro programa adivinador de nmeros. La estructura
putchar(ch); en seudocdigo de dicho programa habra sido:
} while (ch != ' \n' ) ;
do
La diferencia con el bucle {
hacer suposicin
while ( (ch = getchar ()) != ' \n') obtener respuesta s, a o d
putchar(ch); } while (respuesta no sea s)

221
jor mirar antes de saltar (o buclear) en lugar de hacerlo despus. Una segun
Por el contrario, se debe evitar la estructura do while en seudocdigo del da razn se fundamenta en la claridad del programa; ste resulta ms legible
tipo siguiente: si la condicin de test se encuentra al comienzo del bucle. Por ltimo, existen
muchas aplicaciones en las que es importante que el bucle se evite por com
preguntar usuario si desea continuar pleto si el test no se cumple en un primer momento.
do Supongamos que se ha decidido que necesita un bucle con condicin de en
trozo maravilloso de programa trada, cul debe ser, for o while? En parte se trata de un problema de gus
while (respuesta sea s) tos, porque, en ltimo trmino, lo que puede hacer uno tambin lo puede
hacer el otro. As, si deseamos hacer un for idntico a un while se puede omi
Aqu, y aunque el usuario responda no, se ejecutar un maravilloso trozo tir la primera y tercera expresin:
de programa, ya que el test llega demasiado tarde.
for ( ;test; )

RESUMEN: LA SENTENCIA do while que es lo mismo que

I. Palabras clave: do, while while (test)


II. Comentarios generales:
Por el contrario, para hacer un while idntico al for se debe empezar por
La sentencia do while crea un bucle que se repite hasta que la expresin de test
se vuelve falsa o 0. La sentencia do while es un bucle con condicin de salida; una inicializacin, e incluir tendencias de actualizacin:
la decisin de pasar una vez ms por el bucle se realiza despus de haberlo atra
vesado; por tanto, este bucle se ejecuta una vez como mnimo. La parte de inicializar;
sentencia del bucle puede ser simple o compuesta. while (test)
III. Formato: {
cuerpo;
do actualizar;
sentencia }
while (expresin);

La porcin sentencia se repite hasta que expresin se hace falsa o 0. que equivale a
IV. Ejemplo:
for (inicializar; test; actualizar)
do cuerpo;
scanf("%d", &numero)
while (numero != 20);
Por lo que concierne al estilo, parece apropiado usar un bucle for cuando
el bucle implique inicializar y actualizar una variable; un bucle while, sin em
bargo, puede resultar ms apropiado si las condiciones son otras. As, resul
ta natural el empleo de while en
Con qu bucle nos quedamos?
while ((ch = getehar ()) != EOF)
Una vez tomada en un programa la trascendental decisin de que necesi
tamos un bucle, la pregunta que surge es: cul se utiliza? En primer lugar,
hay que decidir si necesitamos un bucle con condicin de entrada o de salida. El bucle for, por su parte, sera una eleccin ms evidente en bucles que
En general la respuesta ser que el bucle debe llevar condicin de entrada. contengan contadores con un ndice:
Kernighan y Ritchie estiman que un bucle con condicin de salida (do
while) se necesita en el 5 por 100 de los casos. Existen varias razones por las for (cont = 1; cont <= 100; cont++)
que los estudiosos de ciencias del cmputo consideran que un bucle con con
dicin de entrada es superior. Una de ellas es la regla universal de que es me

223
Si deseamos encontrar todos los nmeros primos hasta un valor determi
nado deberemos encerrar nuestro bucle for en otro bucle. Expresado en seu-
Bucles anidados docdigo,

Se llama bucle anidado a aquel que est encerrado dentro de otro bucle. for nmero = 1 a lmite
El problema que presentamos a continuacin utiliza bucles anidados para en comprobar si nmero es primo
contrar todos los nmeros primos hasta un determinado lmite. Un nmero
primo es aquel que puede dividirse nicamente por s mismo y por la unidad. La segunda lnea representa el programa anterior. Traduciendo estas ideas
Los primeros nmeros primos son 2, 3, 5, 7 y 11. a C obtenemos:
Una manera muy directa de decidir si un nmero es primo es dividirlo
por todos los nmeros comprendidos entre 1 y l mismo. Si se encuentra que /* primos2 */
el nmero en cuestin es divisible por alguno de ellos, se deduce que ese n main() {
mero no es primo. Podemos utilizar el operador mdulo (%) para compro int numero, divisor, limite;
bar si la divisin es exacta. (Recordar el operador mdulo, no? Este ope int cont = 0;
rador da como resultado el resto de la divisin del primer operando por el
segundo. Si un nmero es divisible por otro, el operador mdulo dar como printf("Introduzca limite superior de busqueda de primos.\n");
resultado 0.) Una vez encontrado un divisor, no es necesario seguir ms ade printf("El limite ha de ser mayor o igual a 2.\n);
scanf("%d", &limite);
lante; por ello debemos prever una terminacin del proceso tan pronto como while (limite < 2) /* otra oportunidad para errores */
encontremos el primer divisor. {
Comenzaremos por comprobar un solo nmero. Para ello necesitamos printf("No esta prestando atencion! Repita de nuevo.\n");
un nico bucle. scanf("%d", &limite);
}
printf("Ahi van los primos.\n");
/* primos1 */ for (numero = 2; numero <= limite; numero++)/*bucle externo*/
main() {
{ for (divisor = 2; numero % divisor != 0; divisor++)
int numero, divisor; ;
if (divisor == numero)
printf("Que numero quiere saber si es primo?\n"); {
scanf ("%d", &numero); /* toma respuesta */ printf("%5d ", numero);
while (numero < 2)/* no aceptar*/ { if (++cont % 10 == 0)
printf("\n"); }/* empieza nueva linea cada 10 primos */
printf("Lo siento, no acepto numeros menores de 2. \n");
printf("Intentelo de nuevo\n"); }
scanf("%d", &numero); printf("\nEso es todo!\n");
} }
for (divisor = 2; numero % divisor != 0; divisor++)
; /* el test de primos se hace en las especificaciones */
if (divisor == numero) /* se ejecuta al terminar el bucle */
printf("%d es primo.\n", numero); El bucle externo selecciona cada nmero desde 2 hasta lmite. En el bucle
else interno se realiza la comprobacin. Utilizamos cont para calcular el nmero
printf("%d no es primo.\n", numero);
} de primos. Cada diez nmeros primos se comienza una nueva lnea de impre
sin. La salida podra ser:

Hemos incluido una estructura while para evitar valores de entrada que
estrellaran el programa.
Obsrvese que todos los clculos se realizan en la seccin de especifica
ciones del bucle for. La cantidad nmero se va dividiendo por divisores cada
vez mayores hasta que se encuentra un resultado exacto (es decir, nmero
% divisor igual a 0). Si el primer divisor que cumple esta condicin es el pro
pio nmero, entonces, nmero es primo; en caso contrario habremos encon
trado un divisor menor, y acabaremos el bucle antes.
Eso es todo!

225
Cuando utilice break como parte de una sentencia if estudie el caso por
El algoritmo empleado es bastante directo, pero no es, por contra, una si se pudiera reescribir la condicin (tal como hicimos arriba), de manera que
maravilla en eficiencia. Por ejemplo, si deseamos averiguar si 121 es primo, se evite la propia inclusin del break.
no hay necesidad de buscar divisores por encima de 11. Si hubiese algn divi
sor mayor que 11, el resultado de la divisin sera un nmero menor que 11, continue:
el cual habra tenido que ser localizado anteriormente. En realidad, slo hay
que buscar divisores hasta la raz cuadrada del nmero en cuestin; sin em Esta sentencia es utilizable en los tres tipos de bucles, pero no en switch.
bargo, la programacin de este algoritmo es algo ms complicada. La deja Al igual que break, interrumpe el flujo del programa; sin embargo, en lugar
mos como ejercicio para el lector inteligente. (Clave: en lugar comparar el de finalizar el bucle completo, continue hace que el resto de la iteracin se
divisor con la raz cuadrada del nmero, comprese el cuadrado del divisor evite, comenzando una nueva iteracin. Si reemplazamos el break del ejem
con el propio nmero.) plo anterior por un continue:
while ((ch = getchar()) != EOF)
Otras sentencias de control: break, continue, goto {
if (ch == ' \n' )
cont inue;
Las sentencias de bucle que hemos discutido hasta ahora, en unin con putchar(ch);
las sentencias condicionales (if, if-else y switch), contituyen los mecanismos }
de control ms importantes en C. Con ellos se puede crear la estructura glo
bal de un programa. Las tres sentencias que presentamos ahora se usan me el resultado obtenido es diferente del que se obtena con la versin break.
nos frecuentemente; un abuso de las mismas hace que los programas sean En esta nueva versin, continue hace que se ignoren los caracteres nueva l
difciles de seguir, ms propensos a errores y ms difciles de modificar. nea; sin embargo, el bucle se abandonar nicamente cuando se localice un
break: carcter EOF. De todas formas, tambin este fragmento podra haberse ex
presado de manera ms econmica:
De las tres sentencias de control comentadas en este apartado, break es,
sin duda, la ms importante; es una vieja conocida nuestra que ya apareci
while ( (ch = getchar()) != EOF)
al describir el comando switch; de hecho, su mayor importancia radica en if (ch != '\n')
la combinacin con dicho comando, donde a menudo es necesaria, pero tam putchar(ch);
bin puede emplearse en combinacin con cualquiera de las tres estructuras
de bucle. Cuando el programa llega a esta sentencia, el flujo del mismo se
Frecuentemente, como sucede en este caso, invirtiendo el test de la sen
desva, liberndose del switch, for, while o do while, en donde se encontra
ba, y dirigndose a ejecutar la siguiente sentencia de programa. Cuando se tencia if se elimina la necesidad de un continue.
usa la sentencia break en una estructura anidada, esta liberacin afecta Por otra parte, la sentencia continue puede ayudar a acortar algunos pro
nicamente al nivel de la estructura ms interna que la contenga. gramas, en especial si existen varias sentencias if-else anidadas.
En ocasiones se utiliza break para abandonar un bucle cuando hay dos
razones para dejarlo. En el siguiente ejemplo presentamos un bucle de eco goto:
que se detiene cuando lee un carcter EOF o un carcter nueva lnea:
La sentencia goto, maldicin del BASIC y FORTRAN, existe tambin en
while ( (ch = getchar() ) != EOF) C. Sin embargo, el C, a diferencia de estos dos lenguajes, puede prescindir
{ de ella casi por completo.
if (ch == \n)
break; Kernighan y Ritchie dedican a la sentencia goto el siguiente piropo: usar
putchar (ch);
} la es abusar de ella; sugieren que se use con cuentagotas o nada en absoluto.
En primer lugar, comentaremos cmo utilizarla; despus, por qu no de
Podramos hacer la lgica del programa ms clara colocando ambos tests be hacerlo.
en el mismo lugar: La sentencia goto tiene dos partes: el goto y un nombre de etiqueta. La
etiqueta tiene las mismas reglas y convenciones que se usan para nombrar
while ((ch = getchar()) != EOF && ch != '\n' ) una variable. Un ejemplo podra ser
putchar(ch);
goto parte2;

227
La estructura if-else, de la que dispone el C, permite expresar esta elec
Para que esta sentencia funcione debe haber otra sentencia que contenga cin mucho ms limpiamente:
la etiqueta parte2. Esta etiqueta se coloca al comienzo de la sentencia, sepa
rndola de la misma por medio de dos puntos. if (iberos > 14)
cascos = 3;
else
parte2: printf("Analisis del refinado:\n"); cascos = 2;
celtas = 2 * cascos;

Evite el goto
En principio, no es necesario en ningn caso emplear goto en un progra 3. Preparacin de un bucle indefinido:
ma C. Si su formacin en programacin procede de FORTRAN o BASIC,
dos lenguajes que prefieren su uso, habr desarrollado probablemente hbi leer: scanf("%d", &puntos);
tos de programacin que dependen de la utilizacin de goto. Para ayudarle if (puntos < 0)
a desintoxicarse de esta dependencia, expondremos a continuacin algunas goto etapa2;
situaciones familiares en las que se empleara goto junto con una versin al montones de sentencias;
goto leer;
estilo C. etapa2: mas cosas;

1. Manejar un if que requiere ms de una sentencia:


Utilice en su lugar un bucle while:
if (talla > 12)
goto a ;
goto b; scanf("%d , &puntos);
a: costo = costo * 1.05; while (puntos >= 0)
mo d o = 2 ; {
b: factura = costo * modo; montones de sentencias;
scanf("%d", &puntos);
}
mas cosas;
(En BASIC y FORTRAN estndar, la nica sentencia regida por el
if es la inmediatamente posterior. Hemos traducido esta situacin a
su equivalente en C.) 4. Saltar al final del bucle: use en su lugar continue.
En C estndar se usa una sentencia compuesta o bloque de mayor sen 5. Abandonar un bucle: utilice break. De hecho, break y continue son
cillez y elegancia. formas especializadas de goto. Las ventajas de su empleo estriban en
que sus nombres indican al usuario lo que se supone que deben hacer,
if (talla > 12)
y, al no requerir etiquetas, no hay peligro de colocar una etiqueta en
{ el sitio equivocado.
costo = costo * 1.05; 6. Andar saltando alegremente de un sitio a otro del programa: sencilla
modo = 2; mente, no lo haga!
}
factura = costo * modo;
Hay un nico uso de goto que est tolerado por algunos veteranos del
C, concretamente cuando se pretende salir de un conjunto de bucles anida
2. Eleccin entre dos alternativas: dos en caso de error. (Recurdese que un solo break liberara exclusivamente
del bucle ms interno.)
if (iberos > 14) while (func > 0)
goto a ; {
cascos = 2; for (i = 1; i <= 100; i++)
goto b; {
a: cascos = 3; for (j = 1; j <= 50; j++ )
b: celtas = 2 * cascos; {

229
IV. continue
dos arrobas de sentencias;
if (problema enorme) El comando continue puede utilizarse con cualquiera de las tres formas de bu
goto socorro; cle, pero no con switch. Al igual que break, hace que el control del programa
evite el resto de sentencias del bucle; sin embargo, a diferencia de l, este co
sentencias; mando inicia una nueva iteracin. En el caso de bucles for o while, se comien
}
mas sentencias; za el siguiente ciclo del bucle. En el bucle do while se comprueba la condicin
} de salida y, si procede, se empieza un nuevo ciclo.
todavia mas sentencias;
} Ejemplo:
y otras cuantas sentencias;
socorro: arreglar el lio o marcharse dignamente;
while ( (ch = getchar()) != EOF)
{
if (ch == ' ')
Como habr observado en nuestros ejemplos, las formas alternativas son continue;
ms claras que la forma goto. La diferencia se hace an ms aparente cuan putchar(ch);
do se mezclan varias de estas situaciones a la vez. Qu gotos estn ayudan numcar++;
do a un if, cules estn simulando if-else, quines estn controlando bucles, }
cules estn simplemente all porque el programador ha decidido programar
ese trozo en una esquina? Una buena sopa de gotos es la mejor receta para Este fragmento produce un eco y cuenta caracteres distintos del espacio.
crear un laberinto en lugar de un programa. Si la sentencia goto no le es fa
V. goto
miliar, mantngase as. Si, por el contrario, est acostumbrado a emplearla,
intente entrenarse en no hacerlo. Por ironas del destino, el C, que no necesi La sentencia goto hace que el control del programa salte a una nueva sentencia
con la etiqueta adecuada. Para separar la etiqueta de la sentencia etiquetada
ta goto, tiene un goto mejor que la mayora de los lenguajes, porque permite
se emplea el smbolo dos puntos. Los nombres de etiquetas siguen las mismas
utilizar palabras descriptivas como etiquetas en lugar de nmeros. reglas que los nombres de variables. La sentencia etiquetada puede aparecer
antes o despus del goto.

RESUMEN: SALTOS EN PROGRAMA Formato:

qoto etiqueta:
I. Palabras clave: break, continue, goto
II. Comentarios generales:
Estas tres instrucciones hacen que el programa salte de una determinada loca
lizacin a otra. etiqueta: sentencia
III. break
El comando break se puede utilizar con cualquiera de las tres formas de bucle Ejemplo:
y con la sentencia switch. Produce un salto en el control del programa, de ma
nera que se evita el resto del bucle o switch que lo contiene, y se reanuda la tope : ch = getchar();
ejecucin con la siguiente sentencia a continuacin de dicho bucle o switch. if (ch != 's')
goto tope;
Ejemplo:

switch (numero)
{
case 4: printf("Ha elegido bien.\n");
break;
case 5: printf("Es una excelente eleccion.\n");
break;
default: printf("Su eleccion es un asco.\n");
}

231
Los arrays pueden ser de cualquier tipo de datos:

int patos[22]; /*un array para almacenar 22 enteros * /


char alfabeto[26] /* un array para almacenar 26 caracteres */
long gordos[5OO] / * un array para almacenar 500 enteros long */

Hemos hablado anteriormente, por ejemplo, de las tiras de caracteres,


que son un caso especial de arrays de tipo char. (En general, un array char,
es aquel cuyos elementos tienen asignados valores de tipo char. Una tira de
caracteres es un array de tipo char en el cual se ha utilizado el carcter nulo,
\0, para marcar el final de la misma.)

Array de caracteres, pero no tira

Array de caracteres y tira

Carcter nulo

Figura 8.6
Arrays Arrays y tiras de caracteres

Los arrays son protagonistas de muchos programas. Permiten almace Los nmeros empleados para identificar los elementos del array se lla
nar grandes cantidades de informaciones relacionadas de una forma conve
man subndices o simplemente ndices. Los ndices deben ser enteros,
niente. Dedicaremos ms adelante un captulo completo a los arrays, pero,
como suelen estar a menudo merodeando bucles, es conveniente que comen y, como ya se ha comentado, la numeracin comienza por 0. Los elementos
cemos a utilizarlos ahora. del array se almacenan en memoria de forma continuada, tal como se mues
Un array es una serie de variables que comparten el mismo nombre bsi tra en la figura 8.7.
co y se distinguen entre s con una etiqueta numrica. Por ejemplo, la decla
racin
Int boo [4] (Dos bytes por entero)
float deuda[20];

anuncia que deuda es un array con veinte miembros o elementos. El pri


mer elemento del array se denomina deuda[0]; el segundo, deuda[l], etc., hasta
deuda[19]. Obsrvese que la numeracin de los elementos del array comien
za en 0 y no en 1. Al haber sido declarado el array de tipo float, cada uno
de sus elementos podr albergar un nmero en punto flotante. Por ejemplo, Char foo [4] (Un byte por carcter)
podramos tener

deuda[5] = 32.54;
deuda[6] = 1.2e+21;
Figura 8.7
Arrays de tipo char e int en memoria

233
un valor mayor que el que en ese momento lleva mximo, hacemos mximo
igual al nuevo valor localizado.
Existe una infinidad de aplicaciones para los arrays. La que mostramos
aqu es relativamente sencilla. Supongamos que necesitamos un programa que Unamos ahora todos los trozos. Expresando en seudocdigo,
lea 10 resultados que sern procesados ms adelante. Si utilizamos un array, leer los resultados
evitaremos inventar 10 nombres diferentes de variables, uno para cada resul imprimir un eco de los mismos
tado. Adems, podremos emplear un bucle for para la lectura. calcular e imprimir promedio
calcular e imprimir valor mximo
/* leer puntuaciones */
m a in ( )
{ Mientras estamos en ello, generalizaremos un poco ms.
int i, a [10];
/ * calificaciones */
for (i = O; i <= 9; i++) #define NUM 10
scanf("%d, &a[i]); /* lee las 10 calificaciones */ main()
printf("Las puntuaciones leidas son las siguientes:\n");
for (i = O; i <= 9; i++) {
printf("%5d", a[i]); /* comprobacion de la entrada */ int i, suma, media, mximo, valor [NUM];
printf("\n");
} printf("Introduzca los 10 valores.\n");
for (i = 0; i < NUM; i++)
scanf("%d", &valor[i]); / * lee las 10 calificaciones */
Es una buena costumbre hacer que el programa repita como un eco printf("Las puntuaciones leidas son:\n");
for (i =0; i < NUM; i++)
los valores que acaba de leer. As se asegura que los datos que est procesan printf("%5d", valor[i]); /* comprueba entrada * /
do el programa son precisamente los que uno piensa. printf("\n") ;
Como observar, el sistema empleado aqu es mucho ms conveniente que for (i = 0, sum = 0; i < NUM; i++)
suma += valor[i] ; /* suma elementos del array */
el uso de 10 sentencias scanf( ) separadas para lectura y 10 sentencias printf( ) media = suma/NUM; metodo ultramoderno de antes * /
para verificacin. El bucle for suministra una manera muy sencilla y directa printf("El promedio de las calificaciones es %d.\n", media);
de utilizar los ndices del array. for (maximo = valor[O], i = 1; i < NUM; i++)
if (valor[i] > maximo) /* comprueba cual es mayor */
Qu tipo de operaciones podemos realizar con estos resultados? Pode maximo = valor[i];
mos hallar su media, su desviacin estndar (s; sabemos como hacerlo); tam printf("La calificacion maxima es %d.\n", maximo);
bin podemos calcular cul es el mayor o, bien, ordenarlo. Nos ocuparemos }
ahora de las dos partes ms fciles: encontrar el promedio y hallar el valor
mayor.
Para hallar la media deberemos aadir este trozo al programa: Hemos sustituido 10 por una constante simblica, aprovechado el hecho
de que i = (NUM 1) es lo mismo que i < NUM.
int suma, media; Comprobaremos que funciona, y despus haremos algunos comentarios
for (i =0, suma = 0; i <= 9; i++) /* dos inicializaciones */ adicionales.
suma += a[i]; /* suma los elementos del array */
media = suma/10; /* ultramoderna forma de promediar */ Introduzca los 10 valores.
printf("El promedio de puntuacion es %d.\n", media); 76 85 62 48 98 71 66 89 70 77
Las puntuaciones leidas son:
76 85 62 48 98 71 66 89 70 77
El promedio de las calificaciones es 74.
Por su parte, para hallar el valor mximo aadiremos este otro trozo: La calificacin maxima es 98.

int maximo; Un punto a denotar es que estamos empleando cuatro bucles for distin
for (maximo = a[0], i = 1; i <= 9; i++)
if (a[i] > maximo) tos. El lector se preguntar si realmente es necesario hacerlo as. Podramos
maximo = a [i] ; haber combinado algunas operaciones en un solo bucle? La respuesta es que
printf("La puntuacion maxima es %d.\n", maximo);
s; podramos haberlo hecho; adems, habramos obtenido as un programa
ms compacto; sin embargo, nos hallamos deslumbrados por el principio de
En esta parte hemos comenzado haciendo mximo igual a a[0]. Seguida la modularidad (impresionables que somos). La idea que subyace detrs de
mente comparamos mximo con cada elemento del array. Si encontramos esa frase es que un programa debe poderse dividir en unidades separadas o

235
El problema que surge con este mtodo es que deja en manos del usuario
mdulos que realicen tareas independientes unas de otras. (Nuestro seudoc- operaciones tan delicadas como contar correctamente el nmero de datos e
digo reflejaba los cuatro mdulos de este programa.) As se consigue que el introducir dicho nmero. Un programa que se base en que el usuario haga
programa sea ms fcil de leer. Lo que es ms importante: el programa es las cosas correctamente suele ser bastante frgil.
mucho ms fcil de actualizar y modificar si cuidamos en no mezclar sus di As llegamos a la siguiente aproximacin al problema, en la cual el orde
ferentes partes. Se trata simplemente de arrojar al cesto de los papeles el m nador se encarga de contar el nmero de datos introducidos; despus de to
dulo inservible, reemplazarlo por uno nuevo y dejar el resto del programa do, los ordenadores han demostrado desde siempre cierta aptitud en contar
sin tocar. nmeros. Ahora surge un pequeo problema subsidiario, que consiste en bus
El segundo comentario a destacar es que un programa que procese exac car la manera de indicar al ordenador cundo se ha finalizado la entrada de
tamente 10 nmeros es bastante poco satisfactorio. Qu sucede si alguien nmeros. Uno de los mtodos es hacer que el usuario teclee un signo especial
falla y tenemos slo 9 resultados? Es cierto que empleando una constante para anunciar el fin de datos.
simblica para el 10 hemos conseguido que el programa sea fcil de cambiar, Esta seal debe ser del mismo tipo de datos que el resto, ya que ha de
pero an habra que compilarlo de nuevo. Existen otras alternativas? Justa ser leda por la misma sentencia de programas. Pero, por su propia naturale
mente de eso vamos a tratar. za, debe ser distinta de los datos ordinarios. Por ejemplo, si estamos leyendo
puntuaciones de un test en el cual la calificacin alcanzada por una persona
puede oscilar entre 0 y 100 puntos, no podramos emplear 74 como seal,
ya que podra ser una puntuacin real. Sin embargo, un nmero como 999
Una cuestin sobre entradas 3 podra utilizarse para esta funcin, ya que no puede, en ningn caso,
representar una calificacin. Una implementacin de este mtodo sera:
Hay varias formas de leer una serie de, por ejemplo, nmeros. Ya hemos #define STOP 999 /* signo de detencion de la entrada */
comentado algunas aqu, procurando ir de la menos a la ms conveniente. #define NUM 50
En general, la forma menos conveniente es la que acabamos de utilizar: ma i n ( )
escribir un programa que acepte un nmero fijo de valores como dato de en {
int i, cont, temp, valor[NUM]
trada (sin embargo, sera la aproximacin correcta si el nmero de datos de
entrada nunca fuera a cambiar). Por el contrario, si el nmero de datos cam printf("Comience a meter datos. Teclee 999 para indicar \n");
bia, deberemos recompilar el programa. printf("el final de los mismos. El numero maximo de datos \n");
printf( "aceptable es %d,\n", NUM);
La siguiente manera (mejor) de hacerlo es preguntar al usuario cuntos cont = 0;
datos va a introducir. Como el tamao del array est fijado por el programa, scanf("%d",&temp) ; /* lee un valor */
ste deber comprobar previamente si la respuesta del usuario supera el pro while (temp != STOP && cont <= NUM) /* ve si hay que parar */
{ /* y tambien si queda sitio para el nuevo dato */
pio tamao del array. Una vez hecho este test, se pueden empezar a introdu valor[cont++] = temp; /* guarda el dato y actualiza cont */
cir datos. Esta idea correspondera a la remodelacin del comienzo de nues if (cont < NUM + 1)
tro programa anterior: scanf("%d", &temp) ; /* lee el siguiente valor */
else
printf("Ya la has hecho!!! No me caben mas!!!\n);
printf("Cuantos datos va a introducir?\n"): }
scanf("%d", &ndat); printf("Ha introducido %d datos, a saber:\n", cont);
while (ndat > NUM) for (i = 0; i < cont; i++)
{ printf("%5d\n", valor[i]);
printf("Solo puedo manejar %d datos; introduzca "); }
printf("un numero menor.\n", NUM);
scanf("%d", &dat);
} /* se asegura que ndat <= NUM, dimension del array */ Hemos ledo los datos en una variable temporal temp y asignado su valor
for (i =0; i < ndat; i++) al array nicamente despus de comprobar que no coincida con la seal de
scanf("%d", &valor[i]);
parada. En realidad, no es necesario hacerlo de esta forma; simplemente pen
samos que as se evidencia ms el proceso de comprobacin.
A continuacin deberamos reemplazar todos los NUM del programa (ex Observe que hemos comprobado dos cosas: primero, si se haba ledo la
cepto en la sentencia #define y en la declaracin del array) por ndat. As se seal de stop; segundo, si hay espacio suficiente en el array para otro nme
consigue que el resto de operaciones afecten nicamente a aquellos elemen ro. Si llenamos el array antes de recibir la seal de stop, el programa informa
tos del array que tienen datos asignados. cortsmente que se acab la entrada de datos, y abandona el proceso.

237
Hasta ahora hemos aprendido
Observe, adems, que estamos usando la forma sufija del operador in
cremento. De este modo, cuando cont es 0, se asigna temp a valor[0], y se Las tres formas de bucles en C: while, for y do while
guidamente cont se incrementa en 1. Al terminar cada iteracin del bucle while, La diferencia entre condicin de entrada y condicin de salida en bucles
cont aventaja al ltimo ndice utilizado en el array en 1. Esta situacin es Por qu se emplean con mucha mayor frecuencia bucles con condicin
precisamente la que queremos, ya que al ser valor[0] el primer elemento del de entrada que con condicin de salida
array, valor[20] ser el elemento nmero 21, y as sucesivamente. Cuando Los dems operadores de asignacin: + = -= *= /= % =
el programa abandone finalmente el bucle, en cont se contendr el nmero Cmo usar el operador coma
total de datos ledos. A continuacin podemos emplear cont como lmite su Cundo se utilizan break y continue: rara vez
perior para los bucles for subsiguientes. Cundo se utiliza goto: cuando lo que se desea es un programa complica
Este esquema funciona bien en tanto en cuanto dispongamos de una re
serva de nmeros que no pueden ser utilizados como datos. Qu sucedera do y difcil de seguir
si debemos preparar un programa que acepte cualquier nmero del tipo ade Cmo usar un while para proteger el programa de entradas errneas
cuado como dato? Nos veramos entonces en la tesitura de que no es posible Qu es un array y cmo se declara: long aniza[8]
emplear un nmero como seal de stop.
Encontramos un problema parecido cuando estudiamos el carcter fin de
fichero (EOF). La solucin empleada entonces fue utilizar una funcin que
capturase caracteres (getchar( )), que en realidad es de tipo int. As se permi Cuestiones y respuestas
ta que la funcin leyese un carcter (EOF) que no era ciertamente un ca
rcter ordinario. Lo que necesitamos ahora es una funcin que capture ente Cuestiones
ros, pero que sea tambin capaz de leer un no entero que pueda emplearse 1. Encuntrese el valor de cuac tras la ejecucin de cada lnea.
como smbolo de parada.
Llegados a este punto tenemos que darle algunas noticias. Las buenas son
que es posible encontrar esa funcin. Las malas, que necesitamos aprender int cuac = 2;
cuac += 5;
algo ms acerca de las funciones, de manera que tenemos que posponer el cuac *= 10;
desarrollo de esta idea hasta el captulo 10. cuac -= 6;
cuac /= 8;
cuac %= 3;

Resumen
2. Qu salida producira el siguiente bucle?
El tema fundamental de este captulo ha sido el control del programa.
En C se ofrecen muchas ayudas para la estructuracin de los programas: las for (valor = 36; valor > 0; valor /= 2)
printf("%3d", valor);
sentencias while y for proporcionan bucles con condicin de entrada. Esta
ltima es particularmente adecuada en bucles que llevan aparejadas iniciali-
zaciones y actualizaciones. El operador coma permite inicializar y actualizar 3. Cmo modificara las sentencias if de adivinanmeros2 de manera que acepta
ms de una variable dentro de las especificaciones de un bucle for. Tambin sen letras maysculas y minsculas?
tenemos, para las pocas ocasiones en que es necesario, un bucle con condi 4. Sospechamos que el siguiente programa no es perfecto. Cuntos errores puede
cin de salida: la sentencia do while. Hay otras sentencias, como break, con detectar?
tinue y goto que facilitan nuevos medios de control del programa.
Ms adelante estudiaremos con detalle los arrays. Lo que hemos visto aqu main() /* linea 1*/
se puede resumir como: los arrays se declaran de la misma forma que las va { /* linea 2*/
int i, j, listado); /* linea 3* /
riables ordinarias, aadiendo un nmero entre corchetes para indicar el n
mero de elementos. El primer elemento de un array es el nmero 0, el segun for (i = 1, i <= 10, i++) /* linea 5*/
do el nmero 1, etc. Los subndices que se utilizan en la numeracin de arrays { /* linea 6*/
list[i]=2*i+3; /* linea 7*/
se pueden manipular convenientemente con el empleo de bucles. for (j = 1, j >= i, j++) /* linea 8*/
printf("%d\n", list[j]); /* linea 9*/
} /* linea 10 * /

239
5. Escriba un programa que reproduzca el siguiente diseo utilizando bucles anid
dos.

$$$$$$$$
$$$$$$$$
$$$$$$$$

6. Escriba un programa que cree un array con 26 elementos y almacene en l las


26 letras minsculas.
Respuestas
1.2, 7, 70, 64, 8, 2
2.36 18 9 4 2 1. Recuerde cmo funciona la divisin entera. 1 dividido por 2 es igual a 0
de manera que el bucle termina cuando el valor se iguala a 1
3. if (respuesta == a || respuesta == A)
4. Lnea 3: debe ser lista[10]
Lnea 5: sustituir las comas por puntos y comas
Lnea 5: el rango de i debe oscilar entre 0 y 9, no entre 1 y 10
Lnea 8: sustituir las comas por puntos y comas
Lnea 8: se debe utilizar > = en lugar de < =. De otro modo, cuando i sea 1, el bucle
es infinito
Lnea 10: se debe incluir otra llave de cierre entre las lneas 9 y 10. Una llave cierra la sen
tencia compuesta y la otra cierra el programa.
5.
main()
{
int i, j ;
for (i = 1; i <= 4; i++)
{
for <j= l ; j <= 8; j++)
printf("$");
}
printf("\n");
}
6.
main()
{
int j ;
char ch, alfa[26];
for (i =0, ch = a ; i < 26.; i++, ch++)
alfa[i] = ch;
y

Ejercicios
1. Modifiquese adivinanmeros2 con las directrices que apuntbamos, para mejo-
rar el programa.
2. Implemente la sugerencia hecha anteriormente, para mejorar la eficiencia de n
meros primos.
3. Utilizando bucles anidados, produzca el siguiente diseo:
241
9
Funcionamiento
funcional
de las funciones
En este captulo encontrar:
Creacin y utilizacin de una funcin sencilla
Argumentos de funciones
Definicin de una funcin con argumentos: argumentos formales
Llamada a una funcin con argumento: argumentos efectivos
La funcin como caja negra
Argumentos mltiples
Devolucin de un valor desde una funcin: return
Variables locales
Localizacin de direcciones: el operador &
Alteracin de variables en el programa de llamada
Punteros; un primer repaso
El operador de indireccin: *
Declaracin de punteros
Utilizacin de punteros para comunicaciones entre funciones
A ver cmo funcionamos
Cmo especificar tipos de funciones
Todas las funciones C se crean de la misma manera
Resumen
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

243
Funcionamiento tinas y procedimientos de otros lenguajes, aunque los detalles pueden ser di
ferentes. Algunas funciones producen acciones concretas; por ejemplo,
printf( ) hace que los datos se impriman en pantalla. Otras buscan un deter

funcional minado valor para que pueda ser empleado por el programa; as, strlen( )
informa al programa sobre la longitud de una determinada tira de caracte
res. En general, una funcin produce acciones, o suministra datos, o ambas

de las funciones cosas a la vez.


Por qu usamos funciones? La razn principal es para evitarnos tediosas
repeticiones de programacin. Escribiendo una sola vez la funcin apropia
da, podremos emplearla cualquier nmero de veces en un determinado pro
grama, en diferentes situaciones y localizaciones del mismo, lo que evita te
CONCEPTOS ner que hacer lo propio en el programa original. Tambin se puede emplear
la misma funcin en diferentes programas, tal como hemos hecho, por ejem
Funciones plo, con putchar( ). Aun cuando la funcin se ha de emplear en el programa
Ensamblaje de un programa una sola vez, resulta bastante til su uso, ya que un programa distribuido
Comunicacin entre funciones: argumentos, en funciones es ms modular; por tanto, ms fcil de leer y de cambiar o
arreglar. Supngase, por ejemplo, que deseamos escribir un programa que
punteros, return
realice las siguientes tareas:
Tipos de funciones
leer una lista de nmeros
PALABRAS CLAVE ordenar los nmeros
calcular su media
return imprimir un diagrama de barras

Podramos utilizar un programa como ste:

main()
{
float lista[50];
leerlista(lista);
ordenar(lista) ;
promedio(lista);
diagrama(lista);
}
La filosofa del diseo del C est basada en el empleo de funciones. Has
ta aqu hemos usado algunas funciones que nos ayudaban en nuestra progra
macin: printf( ), scanf( ), getchar( ), putchar( ) y strlenf( ). Estas funcio Por supuesto, tambin tendramos que escribir nuestras cuatro funciones:
nes venan ya dispuestas en el sistema, pero tambin hemos creado algunas leerlista( ). ordenar( ). promedio( )y diagrama( ), pero esto son simples de
por nuestra cuenta, todas ellas llamadas main( ). Los programas comienzan talles. Al utilizar nombres descriptivos de las funciones, hemos dejado bas
siempre ejecutando las instrucciones de main( ); una vez iniciado el progra tante claro lo que el programa hace y cmo est organizado.
ma, main( ) puede llamar a otras funciones, como getchar( ). En este cap A continuacin podemos afinar cada funcin por separado hasta con
tulo aprenderemos a crear otras funciones, y a hacerlas comunicarse con seguir que haga lo que se pretenda de ella. Por otra parte, si las funciones
main( ) y entre ellas mismas. son lo suficientemente generales, podremos obtener el beneficio extra de apli
Pero antes de todo, qu es una funcin? Una funcin es una unidad de carlas a otros programas.
cdigo de programa autocontenida, diseada para realizar una tarea deter Muchos programadores se plantean las funciones como cajas negras,
minada. Las funciones en C juegan el mismo papel que las funciones, subru definidas exclusivamente por la informacin que hay que suministrarles (su
entrada) y el producto recibido (su salida); lo que suceda dentro de la caja

245
Los puntos ms importante a destacar de este programa son los siguien
tes:
negra no es de nuestra incumbencia, a menos que seamos nosotros mismos 1. Hemos llamado (invocado, solicitado) la funcin asteriscos( ) en el pro
los que hemos diseado tal funcin. Por ejemplo, cuando usamos printf( ),
sabemos que hay que entregarle una tira de control y, opcionalmente, algu grama main( ) simplemente escribiendo su nombre. El sistema recuer
nos argumentos. Tambin sabemos qu salida produce printf( ) con esos da da un poco al empleado para conjugar un demonio; pero en lugar de
tos. Lo que no nos preocupa es la programacin interna que contiene printf( ), dibujar un pentgono, nos limitamos a colocar tras el nombre un pun
y que la hace comportarse de tal modo. Si consideramos las funciones de es to y coma, con el fin de crear una sentencia:
ta manera, nos podremos concentrar en el diseo global del programa, en
lugar de preocuparnos por los detalles. asteriscos();
Qu necesitamos saber de las funciones? En primer lugar, aprender c
mo se las define adecuadamente, cmo llamarlas para su utilizacin y cmo Es sta una forma de llamar funciones, pero no es la nica. Cuan
establecer comunicaciones entre una funcin y el programa que la llama. Co do el ordenador encuentra la sentencia asteriscos( ), se dirige a la fun
menzaremos tratando estos puntos por medio de un ejemplo muy sencillo, cin de ese nombre y sigue las instrucciones indicadas all. Al termi
para irnos remontando despus hasta dominar adecuadamente el tema. nar, retorna a la siguiente lnea del programa de llamada, en este
caso main( ).
creacin y utilizacin de una funcin sencilla
Nuestro primer objetivo (modesto, ciertamente) consiste en crear una fun
cin que imprima 65 asteriscos en fila. Con el fin de situarla en un contexto,
se incluir dentro de un programa para imprimir encabezados de cartas. Se
guidamente presentamos el programa completo; est compuesto por las fun
ciones main( ) y asteriscos( ).

/* encabezado1 */
#define NOMBRE "ORDENATAS, S.A."
#define DIREC "Plaza del Byte 12"
#define CIUDAD "Villabits, E- 60006"
main() {

Asteriscos() ;
printf ("%s\n", NOMBRE);
printf("%s\n", DIREC);
printf("%s\n", CIUDAD) ;
asteriscos() ;
}
/* Ahora viene la funcion asteriscos */
#include <stdio.h>
#define LIMITE 65
asteriscos()
{
int cont;

for (cont = 1; cont <= LIMITE; cont++)


putchar('*');
putchar('\n') ;
}

Figura 9.1
Flujo de control de encabezado 1

247
2. Obsrvese que hemos empleado el mismo formato para escribir aste. Argumentos de funciones
riscos( ) que para main( ). En primer lugar viene el nombre; a conti
nuacin la llave de abrir, la declaracin de variables empleadas, las El encabezado de la carta quedara ms agradable si consiguisemos que
sentencias de definicin de la funcin y la llave de cierre. Incluso he el texto estuviera centrado. Se puede centrar el texto imprimiendo un nme
mos precedido la funcin con sentencias del tipo #define y #include ro adecuado de espacios en blanco antes del resto de la lnea.
que se necesitaban en ella, pero no en main( ). Vamos a escribir una nueva funcin que imprima espacios. La funcin
espacios( ) (la vamos a llamar as) se asemejar bastante a la funcin asteris
cos( ), con la importante salvedad de que esta vez necesitamos comunica
Instrucciones de preprocesador cin entre main( ) y la funcin, con el fin de que aquel indique a sta cun
tos espacios debe imprimir.
Nombre de la funcin Seamos ms especficos. Nuestra barra de asteriscos ocupa 65 posiciones,
mientras que ORDENATAS, S.A. tiene 15 espacios. As, en nuestra primera
versin quedaban 50 espacios libres a continuacin del rengln de encabeza
miento. Si deseamos centrarlo, deberemos dejar 25 espacios a la izquierda,
Sentencia de declaracin con el fin de que queden los 25 restantes a la derecha; por tanto, deseamos
comunicar el valor 25 a la funcin espacios. Usaremos el mismo mtodo
Sentencia de control de bucle
que empleamos para comunicar el valor * a putchar( ): utilizar un argu
Sentencia de funcin mento. Por tanto, espacios(25) significar que deseamos saltar 25 espacios;
25 es el argumento. Llamaremos tres veces a la funcin espacios( ), una por
Sentencia de funcin cada lnea de la direccin. El aspecto del programa general sera:

Figura 9.2
Estructura de una funcin sencilla /* encabezado2 */
#define NOMBRE "ORDENATAS, S.A."
#define DIREC "Plaza del Byte 12"
#define CIUDAD "Villabits, E- 60006"
main ()
3. Se ha incluido asteriscos( ) y main( ) en el mismo fichero. Tambin po {
dramos haber utilizado dos ficheros distintos. Las dos formas tienen int salta;
sus ventajas e inconvenientes. Un solo fichero es ligeramente ms fcil
de compilar. Dos ficheros separados, por su parte, simplifican la utili asteriscos();
zacin de la misma funcin en programas diferentes. Discutiremos ms espacios(25); /* espacios con una constante como argumento */
adelante el empleo de dos o ms ficheros. Por ahora pondremos todas printf("%s\n", NOMBRE);
salta = (65 - strlen(DIREC))/2;
nuestras funciones en la misma cesta. El compilador advierte que /* dejamos al programa calcular los espacios a saltar */
main( ) ha terminado, al encontrar su llave de cierre; los parntesis espacios(salta);
de asteriscos( ) indican al compilador que lo que sigue es una funcin. printf("%s\n", DIREC);
Obsrvese, adems, que asteriscos( ) no va seguido por un punto y coma; espacios((65 - strlen(CIUDAD))/2);/* expresion como argumento*/
printf("%s\n", CIUDAD);
esta ausencia de punto y coma indica al compilador que estamos defi asteriscos();
niendo asteriscos( ), no utilizndola. }
Podemos imaginar, como ya indicbamos, que asteriscos( ) es una caja /* Aqui esta otra vez asteriscos() */
#include <stdio.h>
negra cuya salida es la lnea de asteriscos que se imprime. No hay en este ca #define LIMITE 65
so entrada alguna, ya que la funcin no necesita ninguna informacin de! asteriscos()
programa de llamada; dicho de otra forma, no existe en nuestro ejemplo nin {
int cont;
guna comunicacin entre ambos programas.
Vayamos a un nuevo caso en el que s se necesita comunicacin. for (cont = 1; cont <= LIMITE; cont++)
putchar ('*');
putchar('\n');
}

249
/* Y aqui viene espacios() */ lizar su tarea; hemos dado un valor a nmero utilizando un argumento
espacios(numero) efectivo o parmetro en la llamada a la funcin. Concentrmonos en la
int numero; /* el argumento se declara antes de la llave */ primera vez que usamos espacios( ):
{
int cont; /* declara otra variable dentro de las llaves */
espacios(25);
for (cont = 1; cont <= numero; cont++ )
putchar(' ');
} El parmetro 25 es un valor que se asigna al argumento formal, la varia
ble nmero; es decir, la llamada a funcin tiene el efecto siguiente:
Figura 9.3
Programa de encabezamientos numero = 25;

Ya que estbamos en ello, hemos aprovechado el programa para experi


En resumen, el argumento formal es una variable en el programa que ha
mentar tres formas diferentes de expresin del argumento. Funcionan to sido llamado, y el argumento efectivo es el valor particular asignado a tal
das? S, y aqu est la prueba
variable en el programa de llamada. Tal como se muestra en nuestro ejem
plo, este argumento especfico o parmetro puede ser una constante variable
o incluso una expresin ms complicada. En cualquiera de los casos se eva
la el argumento y se enva su valor a la funcin (en este caso, un entero).
Si nos fijamos en la ltima llamada, espacios( )

e s p a c i o s ( (65 - strlen (CIUDAD))/2);


Primero trataremos de la preparacin de una funcin con argumento; des
pus estudiaremos cmo utilizarla. nos percataremos que la expresin que forma el argumento se evalu obte
niendo un resultado de 26; a continuacin se asign el valor 26 a la variable
Definicin de una funcin con argumento: argumentos formales nmero. La funcin, de hecho, ignora o no le preocupa si el nmero proce
da de una constante variable o expresin. Insistimos, el parmetro enviado
Nuestra definicin de funcin comienza con dos lneas: es un valor especfico que se asigna a la variable conocida como argumento
formal.
espacios(numero)
int numero;

Argumento "formal" = nombre


La primera lnea indica al compilador qu espacios( ) emplea un argu creado por la definicin de la
mento, y que tal argumento se llama nmero. La segunda lnea es una decla funcin
racin que informa al compilador qu nmero es de tipo int. Ntese que el
argumento se declara antes de la llave de comienzo del cuerpo de la funcin.
Por cierto, se pueden condensar estas dos lneas en una:
espacios (int numero;)

De cualquiera de las dos formas la variable nmero se denomina un argu


mento formal. A todos los efectos es una nueva variable, y el ordenador
se encarga de colocarla en una direccin de memoria separada. Observemos Argumento "efectivo" = parmetro =
ahora cmo se utiliza esta funcin. valor = 25 inicializado en main( )

Llamada a una funcin con argumento: argumentos efectivos


El truco consiste en asignar un valor al argumento formal, en este caso
nmero. Una vez conseguido que la variable tenga un valor, el programa rea
Figura 9.4
Argumentos formales y efectivos

251
/* test de abs */
main() {
La funcin como caja negra int a = 10, b = 0, c = - 22;
int d, e, f;
Tomando nuestra funcin espacios( ) como una caja negra, considerare
mos nicamente que la entrada es el nmero de espacios que se han de saltar, d = abs( a);
y la salida es el salto de dichos espacios. La entrada se comunica a la funcin e = abs(b);
f = abs( c);
por medio de un argumento: ste es, de hecho, el vnculo de comunicacin pri ntf ("%d %d %d", d, e, f) ;
entre main( ) y espacios( ). Por otra parte, la variable cont, declarada dentro }
de la funcin, es de uso interno de la misma, y las dems funciones ignoran /* funci on valor absolut o */
su existencia. Este hecho forma parte de la perspectiva caja negra ya comen abs( x)
tada. Dicha variable cont no es la misma que la cont empleada en asteris- int x;
cos( ). {
int y;
y= ( x < o) ? -x : x; /* recuer de el oper ador ?: */
Argumentos mltiples ret urn (y) ; /* devuel ve el val or de y a main */
Cuando se necesita enviar ms de un argumento, se puede formar una }
lista separando los distintos argumentos por comas:
Y la salida:
printnum (i, j )
int i, j ; 10 0 22

printf("Nuevo valor = %d. Total acumulado = %d \n, i, j) ;


} Primero, refresquemos la memoria sobre el operador condicional, ?:. El
operador condicional en abs( ) funciona de la siguiente manera: si x es me
nor que 0, y toma el valor -x; en caso contrario, toma el valor x. Esto es
Ya hemos visto cmo comunicar informacin desde el programa de lla
precisamente lo que necesitamos, ya que si x es -5, y ser -(-5) o, lo que
madas a la funcin. Qu hay del flujo de informacin en sentido opuesto?
Ese es precisamente nuestro siguiente apartado. es lo mismo, 5.
La palabra clave return hace que el valor de la expresin encerrada entre
parntesis, cualquiera que sea, quede asignada a la funcin que contena di
construcin de un valor desde una funcin: return cho return. As, cuando se llama por primera vez abs( ) en nuestro progra
ma principal, abs(a) toma el valor 10, que queda asignado a la variable d.
Nuestro siguiente objetivo es la construccin de una funcin que calcule La variable y es interna de la funcin abs( ), pero el valor de dicha variable
el valor absoluto de un nmero. Se llama valor absoluto al valor de dicho se comunica al programa de llamada por medio de return. El efecto de
nmero despreciando su signo; as, el valor absoluto de 5 es 5, y el valor ab
soluto de -3 es 3. Llamaremos a la funcin abs( ). La entrada a abs( ) de d = abs( a);
ber contener un nmero cualquiera cuyo valor absoluto deseemos calcular.
La salida de la funcin deber ser el mismo nmero exonerado de signos ne es equivalente a decir
gativos. Evidentemente, podemos arreglrnoslas con la entrada utilizando un
argumento. La salida, como veremos, se maneja por medio de la palabra clave abs( a);
return del C. Crearemos tambin un programa main( ) sencillo, con el pro d = y;
psito de comprobar si abs( ) se comporta como es debido; recuerde que es
ta funcin debe ser llamada por otra. Los programas diseados para hacer Se puede usar la segunda forma en la prctica? No, ya que el programa
test de funciones se llaman conductores. El conductor toma la funcin principal ignora la propia existencia de y.
y le da un par de sacudidas. Si la cosa funciona, podemos instalar nuestra El valor devuelto puede ser asignado a una variable, como en nuestro ejem
nueva creacin en otro programa ms util con ciertas garantas. (Tambin plo, o utilizado como parte de una expresin. As, podramos tener:
se aplica el trmino conductor [driver] a programas que controlan mecanis
mos.) Nuestra particular solucin es: resp = 2*abs ( z) + 25;
pri ntf ("%d\n", abs(-32 + resp);

253
El uso de return tiene otro efecto adicional: finaliza la ejecucin de la fun Variables locales
cin y devuelve el control a la sentencia siguiente de la funcin de llamada
Esto ocurre incluso si la sentencia return no es la ltima de la funcin. As, Ya hemos destacado varias veces que las variables de la funcin son pri
podramos haber escrito abs( ) de la siguiente forma: vadas, y desconocidas por la funcin que la ha llamado. De igual forma, las
variables de esta ltima no son conocidas por la funcin que ha sido llama
da. Esta es la razn por la que tenemos que utilizar argumentos y return para
/* funcion valor absoluto, segunda version */ comunicar valores a uno y otro lado. Las variables conocidas nicamente por
abs(x)
int(x) ; su propia funcin se denominan locales. Hasta ahora, son las nicas que
{ hemos empleado; sin embargo, existe en C otro modo de declarar variables,
if (x < 0)
return (-x) ; de manera que puedan ser conocidas por varias funciones simultneamente.
else Estas variables no locales se denominan globales, y sern tratadas ms ade
return (x) ; lante. Entretanto, queremos dejar bien claro que las variables locales son real
}
mente locales. Incluso en el caso de que usemos el mismo nombre para varia
bles de distintas funciones, el ordenador es capaz de distinguirlas. Se puede
demostrar este ltimo punto utilizando el operador & (no confundir con el
Esta versin es ms clara que la anterior, y no utiliza la variable adicional operador &&).
y. Sin embargo, desde el punto de vista del usuario, ambas versiones son la
misma, ya que ambas necesitan la misma entrada y producen la misma sali
da; lo nico que vara es el interior de la propia funcin. Localizacin de direcciones: el operador &
Incluso el ejemplo siguiente tendra el mismo comportamiento:
El operador & devuelve la direccin en la cual se ha almacenado una va
/* funcion valor absoluto, tercera version */ riable. Si puf es el nombre de una variable, &puf es la direccin de la misma.
abs (x) Podemos imaginar las direcciones como localizaciones de memoria, pero tam
int (x) ;
{ bin como etiquetas que utiliza el ordenador para identificar las variables.
if ( x < 0) Supngase que tenemos la sentencia.
return ( x ) ;
else
return ( x ) ; puf = 24;
printf("El profesor Bonete es un petrimete.\n");
}
Y que esta variable puf se ha almacenado en 12126. En ese caso, la sen
tencia
La sentencia printf( ) no se imprimir en ningn caso, ya que su acceso printf("%d %d\n", puf, &puf) ;
queda impedido por las sentencias return anteriores. El profesor Bonete pue
de emplear cuantas veces quiera una versin compilada de esta funcin en tendr como salida
sus propios programas sin averiguar jams la verdadera opinin que le mere
ce a su ayudante. 24 1212&
Tambin se puede usar una sentencia como la siguiente:
adems, el cdigo mquina de la primera sentencia ser algo que signifique
return;
almacnese 24 en la direccin 12126.
Vamos a utilizar este operador para comprobar dnde se almacenan va
Esta ltima sentencia provoca que la funcin que la contiene acabe su eje riables con el mismo nombre en diferentes funciones.
cucin y devuelva el control a la funcin de llamada. Al no haber expresin
alguna entre parntesis, no se devuelve ningn valor.
/* test de localizaciones */
main() {

int puf = 24, bah = 5;

255
printf("En main(), puf = % d y &puf = % u\n", puf, &puf) ;
printf("En main(), bah = % d y &bah = % u\n", bah, &bah);
mikado(puf) ;
}
mikado(bah)
int bah;
{
int puf = 10;

printf("En mikado(), puf = % d y &puf = % u\n", puf, &puf) ;


printf("En mikado(), bah = % d y &bah = % u\n", bah, &bah);

Hemos usado el formato %u (entero sin signo) para imprimir las direc
ciones, por si acaso fueran mayores que el mximo tamao permitido en en
teros int. La salida de este programa en nuestro sistema es

En main(), puf = 2 y &puf = 56002


En main(), bah = 5 y &bah = 56004
En mikado(), puf = 10 y &puf = 55994
En mikado(), bah = 2 y &bah = 56000

Qu conclusin podemos sacar? Primero, que los dos pufs tienen direc
ciones diferentes. Lo mismo sucede con los dos bahs. Tal como habamos
prometido, el ordenador considera que est manejando cuatro variables dis
tintas. Adems, la llamada mikado(puf) envi el valor (2) del parmetro (puf Alteracin de variables en el programa de llamada
de main( )) al argumento formal (bah de mikado( )). Obsrvese que se trans
miti nicamente el valor. Las dos variables involucradas (puf de main() En ocasiones se necesita que una funcin realice cambios en las variables
y bah de mikado( )) retienen su propia identidad. de otra funcin diferente; por ejemplo, un problema comn que surge en or
Hemos vuelto a insistir en este punto porque lo dicho no es cierto en to denaciones de variables es proceder a su intercambio. Supongamos que tene
dos los lenguajes. En esta subrutina FORTRAN, por ejemplo, la subrutina mos dos variables llamadas x e y, y deseamos traspasar recprocamente sus
emplea las variables del programa de llamada. La subrutina puede llamar a valores. La secuencia
las variables con nombres distintos, pero las direcciones son las mismas. En
C no sucede esto; cada funcin utiliza sus propias variables. Es ms ventajo X = y;
sa esta segunda opcin, porque queda garantizado que el valor original de y = x;
las variables no se ver alterado misteriosamente por algn efecto lateral de
la funcin que ha sido llamada. Sin embargo, produce tambin algunas difi ms sencilla no funciona, ya que al ejecutarse la segunda lnea, el valor origi
cultades, como veremos en la siguiente seccin. nal de x se ha perdido. Para proceder al intercambio deberemos preparar una
variable adicional, que llamaremos temp, que almacene temporalmente el valor
de x antes de que se pierda:

te m p = x ;
x = y;
y = te m p ;

Ahora que hemos definido nuestro mtodo de actuacin, escribamos una


funcin y un pequeo programa principal para comprobarla. Para dejar bien

257
La nueva salida es:
claro qu variables pertenecen a main( ), y cules, a la funcin intercambio( ).
En principio x = 5 e y = 10.
usaremos x e y, en la primera, y u y v en la segunda. En principio u = 5 y v = 10.
Ahora u = 10 y v = 5.
Ahora x = 5 e y = 10.
/* cambio1 */
main()
{
int x = 5, y = 10;

printf("En principio x = %d e y = %d . \n", x, y); Bien, no hay ningn error en intercambia( ); la funcin intercambia los
intercambia(x,y) ; valores de u y v. El problema es la comunicacin de resultados al programa
printf("Ahora x = %d e y = %d.\n", x, y); principal main( ). Tal como hemos comentado, intercambia( ) emplea va
}
intercambia(u,v) riables diferentes a las de main( ), de modo que el intercambio de los valores
int u,v; de u y v no produce efecto sobre x e y. Podramos usar return de alguna
{
int temp; forma? Bien, podramos terminar intercambia( ) con la lnea
temp =u;
u = v;
return(u);
v = temp;
}
y cambiar la llamada en main( ) de la forma

x = intercambia(x,y);
A continuacin ejecutamos el programa

En principio x = 5 e y = 10.
De este modo recuperaramos x con su nuevo valor, pero y quedara con
Ahora x = 5 e y = 10. denada al olvido.
Con return se puede enviar al programa de llamada nicamente un valor.
En este caso necesitamos comunicar dos valores. Se puede hacer! Todo lo
Caramba, pero si no han cambiado! Incluyamos algunas sentencias que necesitamos es emplear punteros.
en intercambia( ), para comprobar si hay algo mal en ella:

Punteros: un primer repaso


/* cambio2 */
main() Punteros?; y eso qu es? En primer lugar, punteros es la traduccin
{ aceptada en el mundillo informtico de la palabra inglesa pointers. Bsica
int x = 5, y = 10;
mente, un puntero o pointer es una representacin simblica de una direc
printf("En principio x = %d e y = %d . \n", x, y); cin. Por ejemplo, hemos usado anteriormente el operador de direccin pa
intercambia(x,y) ; ra encontrar la direccin de la variable puf. Se dice que &puf es un puntero
printf("Ahora x = %d e y = %d.\n", x, y);
a puf. En realidad, la direccin es un nmero (56002, en nuestro caso), y
} la representacin simblica &puf es una constante puntero. Despus de to
intercambia (u, v)
do, no hay nada malo en que sea constante, ya que la variable puf no va a
i n t u , v; { cambiar de direccin durante la ejecucin del programa.
Tambin existen en C variables puntero. Al igual que una variable de ti
int temp; po char toma un carcter como valor, y una variable int un entero, la varia
printf("En principio u = %d y v = %d.\n", u, v) ;
temp = u; ble puntero toma como valor una direccin. Si damos a un determinado pun
u = v; tero el nombre ptr, podremos escribir sentencias como la siguiente
v = temp;
printf ("Ahora u = %d y v = %d.\n", u, v) ; }
ptr = &puf; /* asigna la direccion de puf a ptr */

259
Declaracin de punteros
Se dice que ptr apunta a puf. La diferencia entre ptr y &puf es que Ya sabemos cmo declarar variables de tipo int, etc. Cmo se puede de
ptr es variable, mientras que &puf es constante. As, si lo deseamos, pode clarar una variable puntero? Podramos suponer que es algo as:
mos hacer que ptr apunte a otra direccin:
pointer ptr; /* asi NO se declaran los punteros */
ptr = &bah; /* ahora apunta a bah en vez de puf */

Ahora el valor de ptr es la direccin de bah. Por qu no? Porque no basta con saber que una variable es un puntero.
Tambin tenemos que conocer el tipo de variable a la que est apuntando
El operador de indireccin: * dicho puntero. La razn es que las variables de tipos distintos ocupan distin
Supongamos que sabemos que ptr apunta a bah. Entonces podremos usar tas cantidades de memoria, y existen operaciones con punteros que requieren
el operador de indireccin, *, para encontrar el valor almacenado en bah. conocer el tamao de almacenamiento. A continuacin se indica cmo se de
(No confunda el operador de indireccin unario con el operador * binario claran, en realidad, los punteros:
de multiplicacin.)
int *pi; /* pu ntero a va riable de tip o int */
val = *ptr; /* busca el valor al que apunta ptr */ char *pc; /* puntero a variable tipo char */
float *pf,*pg; /* punteros a variables float */

El conjunto de estas dos ltimas sentencias, unidas, equivaldra a


val = bah; La especificacin de tipo identifica el tipo de variable apuntada, y el aste
risco (*) identifica a la propia variable como puntero. La declaracin int *pi;
est diciendo que pi es un puntero y que *pi es de tipo int.
El uso de direcciones y operadores de indireccin es una forma bastante
indirecta de llegar a este resultado, de ah el nombre de operador de indi De igual forma, el valor (*pc) apuntado por pc es de tipo char. Y de
reccin. qu tipo es pc? Lo describiremos como de tipo puntero a char o punte
ro a carcter. Su valor, como tal, es una direccin; por tanto, un entero
sin signo, de forma que deberemos emplear el formato %u para imprimir
RESUMEN: OPERADORES RELACIONADOS CON PUNTEROS un valor de pc.

I. El operador direccin:

& Seguido por un nombre de variable, da la direccin de dicha variable.


Ejemplo:

&fermin es la direccin de la variable fermin

II. El operador de indireccin:


/* Seguido por un puntero, da el valor almacenado en la direccin apuntada
por el mismo.
Ejemplo:

fermin = 22;
ptr = &fermin; /* puntero a fermin */
val = *ptr;

El efecto neto de estas sentencias es asignar el valor 22 a val.


Figura 9.5
Declaracin y uso de punteros

261
Utilizacin de punteros para comunicacin entre funciones
a continuacin, en el cuerpo de la funcin, declararemos
Acabamos de rozar simplemente la superficie de un mundo rico y fasci int temp;
nante, el mundo de los punteros; por ahora, sin embargo, nuestro mayor in
ters es utilizar punteros para resolver el problema de las comunicaciones para disponer de una variable temporal que necesitamos. Deseamos almace
El siguiente programa utiliza punteros para hacer que funcione (por fin!) nar el valor de x en temp, de forma que hacemos
la funcin de intercambio. Echmosle una ojeada, ejecutmosla, y seguida
mente intentaremos comprender cmo funciona. temp = *u;

/* cambio3 */ Recuerde, u tiene el valor &x; por consiguiente, u apunta a x. Ello signifi
main() ca que *u nos da el valor de x, que es lo que deseamos. Lo que no se debe
{
int x = 5, y = 10; hacer es
printf('En principio x = %d e y = %d.\n", x, y); temp = u; /* N O */
intercambia (&x, &y) ; /* envia las direcciones a la funcion */
printf("Ahora x = %d e y = %d. \n, x, y);
} que almacenara la direccin de x en lugar de su valor; estamos intentando
intercambia(u,v)
int *u, *v; intercambiar valores, no direcciones. De igual forma, para asignar el valor
{ de y a x, utilizamos
int temp;
temp = *u; /* temp toma el valor al que apunta u */ *u = *v;
*u = *v;
*v = temp;
} que es lo mismo que

x = y;
Despus de todo, funciona?
Resumiendo lo visto hasta ahora, desebamos una funcin que pudiese
En principio x = 5 e y = 10. alterar los valores de x e y; si enviamos a la funcin las direcciones de x e
Ahora x = 10 e y = 5.
y, estamos dando a dicha funcin acceso a esas variables. Si usamos punte
ros y el operador *, la funcin puede examinar los valores almacenados en
S, funciona.
Bien, veamos cmo funciona. Primero, nuestra funcin de llamada tiene dichas localizaciones y proceder a su intercambio.
el siguiente aspecto: En general, se pueden enviar a las funciones dos tipos de informacin acerca
de una variable. Se puede usar la forma
intercambia(&x, &y) ;
funcion1(x);
En lugar de transmitir los valores de x e y estamos enviando las direccio
nes; por consiguiente, los argumentos formales u y v que aparecen en que transmitir el valor de x. Por el contrario, si usamos la forma
intercambia (u, v) funcion2(&x) ;

tendrn valores de direcciones, y debern declararse como punteros. Como enviaremos la direccin de x. La primera forma requiere que la definicin
x e y son enteros, u y v debern ser punteros a enteros, de modo que declara de la funcin incluya un argumento formal del mismo tipo que x:
remos

int *u, *v; funcion1 (num)


int num;

263
Tambin se puede obtener el valor a partir de la direccin utilizando el ope
rador *:
La segunda forma, por su parte, necesita que la definicin de funcin in Si hacemos pbarra = &barra, entonces podremos saber el valor alma
cluya un argumento formal que sea un puntero al tipo de variable adecuado: cenado en la direccin &barra utilizando *pbarra.

funcion2 (ptr)
int *ptr;

Se usa la primera forma si la funcin necesita el valor para hacer algn


clculo o ejecutar alguna accin. Se usa la segunda forma si la funcin nece
sita alterar las variables del programa de llamada. Lo que hemos estado ha
ciendo hasta ahora con la funcin scanf( ) tena la misma finalidad. Necesi
tbamos leer un valor de la variable num, y emplebamos scanf(%d,
&num). Dicha funcin lee un valor y emplea la direccin que hemos dado
para almacenar dicho valor.
Los punteros permiten soslayar el problema de que las variables de inter-
cambia( ) eran locales. Con ellos se puede acceder a las variables de main( )
y alterarlas en sus propias direcciones.
Los usuarios de PASCAL reconocern probablemente la primera forma Figura 9.6
como envo de valores de parmetros en PASCAL, y la segunda como envo Nombres, direcciones y valores en un sistema "direccionable por
de parmetros variables (var). Probablemente los usuarios del BASIC encon bytes, como el IBM PC.
trarn el montaje un tanto complicado. Si esta seccin le ha resultado algo
esotrica, asegrese de que se ejercita un poco antes de seguir adelante; con Aunque podemos imprimir la direccin de una variable para satisfacer
un poco de prctica llegar a encontrar los punteros sencillos, normales y con nuestra curiosidad, no es se, desde luego, el uso principal del operador &.
venientes. Tiene mucha ms importancia el hecho de que, si empleamos &, * y punte
ros podemos manipular direcciones y sus contenidos simblicamente, co
mo hicimos en el programa cambio3.

VARIABLES: NOMBRES, DIRECCIONES Y VALORES A ver cmo funcionamos


Nuestra discusin sobre punteros se engarza en un problema ms gene Ahora que ya sabemos algo sobre las funciones, podemos intentar apli
ral: la relacin entre nombres, direcciones y valores de las variables. Am car nuestros conocimientos a la realizacin de algunos programas tiles. Vea
pliaremos ahora este punto. mos... qu se puede hacer?
Cuando se escribe un programa pensamos en las variables como algo Qu tal una funcin potencial, algo que permite elevar 2 a la 5.a poten
que contiene dos atributos: un nombre y un valor (tambin podramos con cia o 3 a la 3.a? En primer lugar, debemos decidir qu tipo de entrada necesi
siderar como atributo el tipo, por ejemplo; pero, por el momento, no nos
concierne). Una vez compilado y cargado el programa, el compilador supo ta el programa. Est claro; necesitamos enviar el nmero y el exponente. Po
ne tambin dos atributos a las variables: su valor y su direccin. La direc demos manejar estos dos valores con dos argumentos
cin es la versin en ordenador del nombre de la variable.
pot(base, exp)
En muchos lenguajes la direccin concierne exclusivamente al ordena int base, exp;
dor, siendo inasequible al programador. En C, sin embargo, se pueden co
nocer y emplear las direcciones a travs del operador &:
(Por ahora nos limitaremos a enteros y a nmeros relativamente pequeos.)
&barra es la direccin de la variable barra A continuacin debemos decidir la salida. Tambin, en este caso, es ob
via. Necesitamos un solo nmero como salida, la respuesta. Esto podra ex
Podemos obtener el valor de una variable empleando su nombre: presarse como
printf(%d \ n\ barra) imprime el valor de barra. return ( respuesta) ;

265
Bien, 2 a la 3.a potencia es 8, y -3 a la 3.a potencia es -27. Hasta ahora
Ahora debemos decidir el algoritmo a emplear para calcular la respuesta vamos bien. Sin embargo, 4 elevado a -2 es 1/16, no 1; y 5 elevado a la 10
potencia es 9765625, si la memoria no nos falla.
hacer respuesta igual a 1 Qu ha pasado? En primer lugar, que el programa no estaba diseado
multiplicar la respuesta por la base tantas veces como indique exp para manejar potencias negativas, de forma que se ha estrellado con ese
problema. Adems, al haber empleado el tipo int, nuestro sistema tolera ni
Quiz no quede demasiado claro cmo realizar la segunda etapa, por lo camente nmeros inferiores a 65535.
que la dividiremos en dos partes: Podemos arreglar el programa incluyendo procesado de nmeros negati
vos y utilizando nmeros de puntos flotantes para la base y la respuesta. El
Multiplicar respuesta por base y decrementar exp 1 exponente, sin embargo, deber seguir siendo entero, ya que es el nmero
Detenerse cuando exp llegue a 0. de veces que hemos de multiplicar; no se pueden efectuar 2,31 multiplicacio
nes.
Si exp fuese 3, por ejemplo, conseguiramos efectuar 3 multiplicaciones,
lo que parece bastante razonable.
Bien, pongmoslo en cdigo C. /* calculo de potencias */
double pot(base, exp)
double base;
/* calculo de potencias */ int exp;
pot(base, exp) {
int base, exp; double respuesta;
{
int respuesta; if (exp > 0)
for (respuesta = 1; exp > 0; exp--) for (respuesta = 1.0; exp > 0; exp--)
respuesta = respuesta * base; respuesta *= base;
return(respuesta); return(respuesta);
} }
else if (base != 0)
Y ahora comprobemos la funcin con un driver.
{
for (respuesta = 1.0; exp < 0; exp++)
respuesta /= base;
/* test de potencia */
return(respuesta);
main() }
{ else /* base = 0 y exp <= 0 */
int x; {
printf("0 a la potencia %d no esta permitido! \n", exp);
x = pot (2, 3) ;
printf("%d\n", x); }
x = pot (-3, 3) ;
printf("%d\n", x) ;
x = pot (4, -2) ;
printf("%d\n, x);
x = pot(5,10); Hay algunas novedades que anotar. La que primero salta a la vista es que
printf("%d\n", x) ; hemos declarado el tipo de la funcin. Como respuesta es de tipo double,
}
pot( ) tambin debe ser de tipo double, ya que pot queda asignada en el va
lor devuelto por return. Bien, y por qu no hemos declarado antes las fun
ciones? La respuesta es que las funciones C se suponen de tipo int (la mayor
Unimos las dos funciones, las compilamos y las ejecutamos. Obtenemos
el siguiente resultado: parte lo son) a menos que se indique lo contrario.
Tambin dejamos claro que no se nos han olvidado los nuevos operado
res de asignacin que vimos en el captulo 8.
8 En tercer lugar, hemos adaptado las potencias negativas a divisiones, tal
-27 como permiten las leyes del lgebra. As ha surgido un problema adicional,
1
761 la divisin por 0, que se ha evitado por medio de un mensaje de error. Devol
vemos el valor 0 para que el programa no se detenga.

267
claracin se incluirn el nombre de la funcin y los parntesis (sin ar
Se puede utilizar el mismo programa principal siempre y cuando declare
mos tambin all el tipo de pot( ). gumento), con el fin de identificar dicho nombre como funcin.

main()
/* test de potencia */ {
main() char rch, pun();
{ float plaf () ; }
int x;

x = pot (2. 0, 3) ;
printf ( "%.0f \n", x); No lo olvide. Si una funcin devuelve un valor no entero, declare el tipo
x = pot(-3.0,3); de funcin all donde se define y donde se usa.
printf("%.0f\n", x);
x = pot (4.0, -2) ;
printf("%.4f\n", x);
x = pot (5. 0, 10) ; RESUMEN: FUNCIONES
printf("%.0f\n", x);
}
I. Formatos:
Una definicin tpica de funcin tiene el siguiente formato:
Esta vez la salida es de los ms satisfactorio
nombre (listas de argumentos)
declaracin de los argumentos
a cuerpo de la funcin
-27
0.0625
9765625 La presencia de la lista de argumentos y las declaraciones es opcional. Si
se emplean variables distintas a los propios argumentos, debern declararse
dentro del cuerpo de la funcin que est comprendido entre las llaves.
Este ejemplo sugiere que incluyamos en la explicacin nuestra prxima
seccin. Ejemplo:
dif(x,y) /* nombre de la funcion y argumentos */
int x.y; /* declaracion de argumentos */
especificar tipos de funciones {
int z ;
/* comienza cuerpo de la funcion */

El tipo de una funcin queda determinado por el tipo de valor que de z = x - y;


vuelve, no por el tipo de sus argumentos. Las funciones se suponen de tipo return (z) :
int, a menos que se indique lo contrario. Si una funcin no es de tipo int. } /* fin del cuerpo de la funcion */
se deber advertir en dos sitios:

1. Al declarar el tipo de funcin en su definicin: II. Comunicacin de valores:


Para enviar valores desde el programa de llamada a la funcin se emplean
argumentos. Si las variables a y b tienen los valores 5 y 2, la llamada
char pun(ch, n) /* funcin que devuelve un caracter */
int n;
char ch; c = dif(a,b);

float plaf(num) /* funcion que devuelve un numero float *f transmite los valores 5 y 2 a las variables x e y . Los valores 5 y 2 se llaman
int num; argumentos efectivos o parmetros, y las variables x e y de la funcin dif
se denominan argumentos formales.
La palabra clave return devuelve un valor desde la funcin al programa de
2. Al declarar el tipo de la funcin en la funcin de llamada. Esta decla llamada. En nuestro ejemplo, c recibira el valor de z, que es 3.
racin debe incluirse junto con las declaraciones de variables; en la de Como norma general, una funcin no tiene efectos sobre la variable del pro
grama de llamada. Si se desea afectar dichas variables, utilcense punteros

269
printf(Pulse un caracter cualquiera- Se detiene con Q.\n");
como argumentos. Este sistema ser necesario cada vez que se desee comu ch = getchar();
nicar ms de un valor de retorno desde la funcin al programa de llamada. printf(Aja! eso era una %c!\n,ch);
III. Tipo de funciones: if (ch != 'Q')
mas();
Las funciones deben ser del mismo tipo que el valor que devuelve. Las fun }
ciones no declaradas se suponen del tipo int. Cuando una funcin sea de mas ()
otro tipo, deber ser declarada tanto en la propia funcin como en el pro {
grama de llamada. main() ;
}
Ejemplo:

main() La funcin main( ) llama a mas( ), y mas( ), a su vez, llama a main( )!


Cuando main( ) es llamada por mas( ), comienza desde el principio; de este
{
float q, x, puff(); /* declaracion en el programa de 1lamada/* modo, creamos un bucle bastante sinuoso.
De hecho, una funcin puede llamarse a s misma. Podemos simplificar
int n; el ejemplo anterior de la siguiente forma:
q = puff(x,n);
/* que trabaje main el doble */
} #include <stdio.h>
float puff(u, k) /* declaracion en la definicion de funcin */
float u; main()
int k;
{ char ch; {

float toro; printf("Pulse un caracter cualquiera. Se detiene con Q.\n);


ch = getchar();
printf(Aja! eso era una %c!\n",ch);
return(toro); /* devuelve un valor float */ if (ch != 'Q')
main();
} }

Hagamos una ejecucin sencilla para ver cmo funciona. Obsrvese que
incluso el carcter nueva lnea se transmite cuando se utiliza la tecla [enter].
las funciones C se crean de la misma manera Pulse un caracter cualquiera. Se detiene con Q.
I
En C todas las funciones del programa tienen la misma estructura. Cada Aja! eso era una I!
Pulse un caracter cualquiera. Se detiene con Q.
una puede llamar a otra funcin, o ser llamada por otra. En esto se diferen Aja! eso era una
cia el C de los procedures PASCAL, ya que en PASCAL unos procedures !
pueden estar anidados dentro de otros. Un procedure anidado ignorar cual Pulse un caracter cualquiera. Se detiene con Q.
quier otro procedure anidado en otro sitio. Q
La funcin main( ) tiene algo de especial? S, es algo especial en el senti Aja! eso era una Q!
do de que cuando un programa consta de varias funciones, la ejecucin co
mienza precisamente con la primera sentencia de main( ). Exceptuando esta El proceso por el que una funcin se llama a s misma recibe el nombre
diferencia, main( ) es una funcin como las dems; incluso puede ser llama de recursion. Un bucle establecido por recursin no funciona de la misma
da por otras funciones, como muestra el siguiente ejemplo: forma que los bucles while o do while. Cuando main( ) se llama a s misma,
no se dirige en realidad al principio exacto. En su lugar se produce un nuevo
conjunto de variables de main( ). Si hacemos imprimir las direcciones de una
/ * que trabaj e mai n */ variable en un bucle ordinario, la direccin no cambiar de iteracin a itera
#i nc l ude <st di o. h>
main() cin. Con el tipo de bucle aqu creado, la direccin cambia, ya que se crea
{ un nuevo ch cada vez. Si el programa realiza el bucle 20 veces, habr 20 nue
char ch; vas variables creadas, todas ellas llamadas ch, pero cada una en una direc
cin distinta.

271
Resumen
COMPILACION DE PROGRAMAS
CON DOS O MAS FUNCIONES Hemos empleado funciones como piezas para crear un programa mayor.
Cada funcin deber tener un propsito nico y definido. Hemos usado ar
La manera ms simple de utilizar varias funciones es colocar todas ellas gumentos para comunicar valores a la funcin, y la palabra clave return para
en el mismo fichero. En ese caso, la compilacin se realiza de la misma for enviar valores de vuelta de la funcin al programa de llamada. Si el valor
ma que la compilacin de una sola funcin. devuelto por la funcin no es de tipo int, se deber especificar el tipo de la
Otra manera es utilizar la orden de preprocesador #include. Si una fun funcin tanto en la definicin de la funcin como en la seccin de declara
cin est en el fichero fich1.c, y la segunda, en el fichero fich2.c, se habr cin del programa de llamada. Si se desea que la funcin afecte a variables
de incluir la siguiente orden en el fichero fich1.c:
del programa de llamada, se debern emplear direcciones y punteros.
#include "fich2.c"

Para ms informacin sobre #include, vase captulo 11.


Otras maneras de abordar el problema dependen bastante del sistema
empleado. Algunas de ellas pueden ser: Hasta ahora hemos aprendido
UNIX Cmo definir una funcin.
Supongamos que fich1.c y fich2.c son dos ficheros que contienen fun Cmo comunicar informacin a una funcin: utilizando argumentos.
ciones C; en ese caso, el comando La diferencia entre argumentos formales y argumentos especficos o pa
rmetros: los primeros son variables utilizados por la funcin; los segundos,
cc fich1.c fich2.c el valor enviado desde el programa de llamada.
Dnde se declaran los argumentos: despus del nombre de la funcin y
compilar ambos ficheros y producir un fichero ejecutable llamado a.out. antes de la primera llave.
Adems, se producirn los dos ficheros objeto llamados fich1.o y fich2.o. Dnde se declaran las dems variables locales: despus de la llave de abrir.
Si posteriormente se altera fich1.c, y no fich2.c, se puede compilar el pri
mero y combinarlo con la versin en cdigo objeto del segundo fichero, uti Dnde y cmo se usa return.
lizando el comando Dnde y cmo se usan direcciones y punteros para comunicacin.
cc fich1.c fich2.o

Lattice C y Microsoft C

Complese fich1.c y fich2.c por separado, produciendo dos ficheros en Cuestiones y respuestas
cdigo objeto fich1.obj y fich2.obj. Utilice a continuacin el linker para
combinarlos junto con los mdulos objeto estndar de c.obj: Cuestiones
link c fich1 fich2 1. Escriba una funcin que devuelva la suma de dos enteros.
2. Indicar los cambios, si los hay, que se deben hacer en la funcin de la cuestin
Sistemas basados en Cdigo Ensamblador 1 para conseguir que se sumen dos nmeros de tipo float.
3. Disear una funcin, alter( ), que tome dos variables de tipo int, x e y, y las trans
Algunos de ellos permiten compilar varios ficheros a la vez al estilo forme en su suma y diferencia, respectivamente.
UNIX: 4. Hay algo incorrecto en la siguiente definicin de funcin?

cc fich1.c fich2.c
s o l e (num)
o de alguna otra forma equivalente. En otros casos, deber producir mdu
los de cdigo ensamblado por separado y combinarlos en el propio proceso int num, cont; {
de ensamblaje. for (cont = 1; cont <= num; num++)
printf(" O sole mio!\n") ;
}

273
Respuestas
1. suma (j,k)
int j, k;
{
return (j + k); }
2. float suma (j,k)
float j, k;
Tambin se debe declarar float suma( ) en el programa de llamada
3. Como deseamos alterar las variables del programa de llamada, deberemos usar
direcciones y punteros; la llamada deber ser alter (&x,&y)
Una posible solucin sera

alter (px,py)
int *px, *py; /* punteros a x e y */
{
int sum, dif;
sum = *px + *py; /* suma contenidos de ambas direcciones */
dif = *px - *py;
*px = sum;
*py = dif;
}

4. S, num debe declararse antes de la primera llave, no despus. Adems, el bucle


debe incluir cont + + , no num + + .

Ejercicios
1. Escriba una funcin max(x,y) que devuelva el mayor de dos valores.
2. Disee la funcin chlinea(ch,i,j) que imprima el carcter requerido entre las co
lumnas i y j. Vase el programa de caricaturas del captulo 7.
10
Modos de
almacenamiento
y desarrollo
de programas
En este captulo encontrar: Conversin de string a ente
ro: stoi( )
Modos de almacenamiento: pro Por qu no las probamos?
psito Ordenacin de nmeros
Variables automticas Lectura de datos numricos
Variables externas Eleccin de la representa
Variables estticas cin de datos
Funciones estticas externas Final de la entrada
Variables en registros Otros aspectos
Qu modo de almacena Main( ) y getarray( )
miento empleamos? Explicacin
Una funcin de nmeros aleatorios Ordenacin de los datos
Lanza los dados Impresin de los datos
Una funcin para atrapar enteros: Resultados
getint( ) Repaso
Planteamiento Hasta ahora hemos apren
Flujo de informacin para dido
getint( ) Cuestiones y respuestas
El interior de getint( ) Ejercicios

277
una serie de funciones tiles; conforme vayamos hacindolo, trataremos de
Modos de demostrar algunas de las consideraciones que hay que tener en cuenta a la
hora de disear una funcin. En concreto, haremos nfasis en la importan
cia de la programacin modular, que permitir subdividir nuestro trabajo en

almacenamiento tareas manejables.


Pero, como hemos prometido, trataremos primero los modos de almace
namiento.

y desarrollo Modos de almacenamiento: propsito

de programas Ya hemos comentado con anterioridad que las variables locales son co
nocidas nicamente por las funciones que las contienen. En C se ofrece tam
bin la posibilidad de trabajar con variables globales conocidas por varias
funciones. Supongamos, por ejemplo, que deseamos que la variable unida
CONCEPTOS des sea conocida por dos funciones, main( ) y crtica( ).
Lo que tenemos que hacer es asignar a unidades el modo de almacena
Variables locales y globales miento externo (extern), como se puede ver:
Modos de almacenamiento
Funcin de nmeros aleatorios /* unidades como global */
Comprobacin de errores int unidades; / * una variable externa */
main()
Programacin modular {
Ordenacin extern int unidades;

printf("Cuantos reburcios hay en una drumera de harina?\n");


PALABRAS CLAVE scanf("%d", &unidades) ;
while (unidades != 3419)
critica();
auto, extern, static, register printf("Seguro que has mirado! ! !\n") ;
}
critica()
{
extern int unidades;

printf("Lo siento, chaval. Prueba otra vez.\n");


scanf("%d", &unidades) ;
}

La salida podra ser:


Una de las razones por las que el C es tan potente es por permitir contro
lar hasta los ms mnimos detalles del programa. Los modos de almacena Cuantos reburcios hay en una drumera de harina?
14
miento que se ofrecen en C son un ejemplo de este tipo de control, ya que Lo siento, chaval. Prueba otra vez
permiten determinar qu funciones conocen, qu variables y hasta cundo 3419
va a permanecer una variable en un programa. Los modos de almacenamien Seguro que has mirado!!!
to constituirn el motivo de la primera parte de este captulo.
Por otro lado, aprender a programar no es simplemente conocer las re (La verdad es que s.)
glas del lenguaje, al igual que escribir una novela (o incluso una carta), es Obsrvese que la variable unidades ha sido leda por la funcin crtica( )
algo ms que saber las reglas del espaol. En este captulo desarrollaremos la segunda vez; pero, a pesar de ello, tambin main( ) la conoca, y la ha
utilizado para abandonar el bucle while.

279
Una variable automtica abre los ojos al mundo cuando se llama a la fun
cin que la contiene. Cuando esta funcin acaba su tarea y devuelve el con
Hemos conseguido que unidades fuese una variable externa definindola trol al programa de llamada, la variable queda relegada al olvido. Su locali
antes de cualquier funcin (es decir, externamente a ella). A continuacin, zacin de memoria se emplear en adelante para otros usos.
dentro de la funcin que vaya a utilizar esa variable, volvemos a declarar la
misma anteponiendo al tipo de variable la palabra clave extern. Esta palabra Un detalle ms sobre el alcance de las variables automticas: su alcance
clave informa al ordenador que debe buscar la definicin de la variable fuera queda confinado al bloque (par de llaves) en el cual se ha declarado la varia
de la funcin. Si hubisemos omitido la palabra (extern) en, digamos, crti- ble. Siempre podemos declarar nuestras variables al comienzo del bloque de
ca( ), el ordenador habra considerado en esta funcin que exista una varia la funcin, de manera que el alcance sea la funcin completa. Pero, en prin
ble distinta con el mismo nombre unidades, pero local y, por consiguiente, cipio, uno puede tambin declarar una variable dentro de un sub-bloque. En
limitada a la funcin crtica( ). En tal caso, la otra variable unidades (inclui ese caso, la variable sera conocida nicamente dentro de la subseccin de
da en main( )) no hubiese cambiado su valor tras la ejecucin del scanf( ) la funcin. En circunstancias normales no se suele hacer uso de esta opcin,
ejecutado en crtica( ). pero hay pocas cosas que un programador que se sienta acosado no sea ca
Ya sabamos que cada variable tiene su tipo. Adems, cada variable tiene paz de intentar.
un modo de almacenamiento. Existen cuatro palabras clave en C que se em
plean para describir modos de almacenamiento: extern (por externa), auto Variables externas
(automtica), static y register. Hasta ahora no nos habamos ocupado de los
modos de almacenamiento porque las variables declaradas en una funcin Cuando una variable se define fuera de una funcin, se dice que es exter
se supone que son de modo auto, a menos que se indique lo contrario (o sea, na. Dicha variable externa puede tambin ser declarada dentro de la funcin
son automticas automticamente). que la emplea utilizando la palabra clave extern. En tal caso, la declaracin
El modo de almacenamiento de una variable queda determinado por el tendra un aspecto como el siguiente:
lugar donde se define y la palabra clave empleada, suponiendo que se use
alguna. int errumpir; /* 3 variables definidas externamente */
char cuteria;
El modo de almacenamiento es responsable de dos propiedades distintas. double blanca;
Primero, controla las funciones a las que dicha variable es accesible. Se lla main()
ma alcance de una variable a la mayor o menor extensin de la accesibili {
dad de la misma. En segundo lugar, el modo de almacenamiento determina extern int errumpir; / * declaracion de que existen 3 variables*/
extern char cuteria; / * definidas externamente* /
cunto tiempo va a persistir una variable en memoria. Pasemos a estudiar extern double blanca;
cada uno de estos modos por separado.

Variables automticas Se puede omitir por completo el grupo de declaraciones extern si las defi
Todas las variables declaradas en una funcin, por defecto, son autom niciones originales aparecen en el mismo fichero y antes de la funcin que
ticas. No obstante, si deseamos dejar bien clara nuestra intencin de que la las utiliza. Sin embargo, el uso de la palabra clave extern permite que una
variable sea automtica, podemos emplear la palabra clave auto: funcin emplee una variable externa que haya sido definida despus de la fun
cin en el mismo fichero, o incluso en un fichero diferente. (Por supuesto,
ma i n ( ) ambos ficheros debern compilarse, unirse (link o ensamblarse a la vez.)
{ Cuando se omite la palabra extern en la declaracin de la variable en una
auto i nt pl of;
funcin, se crea una nueva variable distinta y automtica con el mismo nom
bre. Conviene en estos casos etiquetar esta segunda variable con la palabra
Por ejemplo, conviene seguir este sistema para mostrar que intenciona auto, para dejar claro que se ha hecho intencionadamente y no por des
damente hemos evitado una definicin de funcin externa. Las variables auto piste.
mticas tienen alcance local. La nica funcin que conoce una variable de En los siguientes ejemplos se muestran las cuatro combinaciones posibles:
este tipo es aquella donde se ha definido. (Por supuesto, se pueden usar ar
gumentos para comunicar el valor y direccin de una variable de este tipo
/* Ejemplo 1 */
a otra funcin, pero convendr con nosotros que este conocimiento ser siem int abracadabra;
pre parcial e indirecto.) Como consecuencia de lo anterior, otras funciones main()
pueden utilizar variables con el mismo nombre, las que se tratarn como va {
riables independientes almacenadas en diferentes localizaciones de memoria. extern int abracadabra; /* se declara como externa */

281
Variables estticas
}
magia() El nombre puede parecer una contradiccin, como si fuese una variable
{
extern int abracadabra; que no puede variar. En realidad, la palabra esttica se refiere a que estas
variables quedan en memoria. El alcance de las variables estticas es el mis
} mo que el de las variables automticas; pero, a diferencia de ellas, no desa
parecen cuando la funcin que las contiene finaliza su trabajo. El ordenador
Aqu aparece una variable externa abracadabra, conocida tanto por main( ) recuerda sus valores, y permanecern all si la funcin vuelve a ser llamada
como por magia( ). otra vez. En el siguiente ejemplo se demuestra este punto, y se indica cmo
declarar una variable static.
/* Ejemplo 2 */
int abracadabra; /* variable estatica */
main() main()
{ {
extern int abracadabra; /* se declara como externa */ int cont;
} for (cont = 1; cont <= 3; cont++)
magia()
{
/* abracadabra no se declara de ninguna forma */
printf("Aqui llega iteracion %d:\n", cont);
pruebastat();
} }
}
pruebastat()
{
int muere = 1;
static int vive = 1;
De nuevo existe una variable externa abracadabra conocida por ambas
funciones. Esta vez, sin embargo, magia( ) conoce la variable por defecto. printf("muere = %d y vive = %d\n", muere++, vive++) ;
}
/* Ejemplo 3 * /
int abracadabra;
main() Observe que pruebastat( ) incrementa cada variable tras haber impreso
{ su valor. La ejecucin de este programa da la siguiente salida:
int abracadabra; / * al declararse queda como auto por defecto*/
} Aqui llega iteracion 1:
magia() muere = 1 y v i v e = 1
{ A qui llega iteracion 2:
auto int abracadabra; muere = 1 y vive = 2
Aqui llega iteracion 3:
} muere = 1 y vive = 3

La variable esttica vive recuerda que su valor se increment en 1, mien


En este caso se han creado tres variables distintas. El abracadabra de tras que la variable muere resucita y fallece cada vez que se ejecuta la fun
main( ) es automtico por defecto, y, por tanto, local a main. Esta misma cin. Este ltimo punto establece una diferencia en la inicializacin: muere
variable es automtica, porque se ha indicado explcitamente as en magia( ), se inicializa cada vez que se llama pruebastat( ), en tanto que vive se iniciali-
y queda confinada a esta funcin. La variable externa abracadabra no se co za una sola vez, cuando se compila pruebastat( ).
noce ni en main( ) ni en magia( ), pero podra ser conocida por otra funcin
del fichero en la que no se hubiese declarado localmente abracadabra. Funciones estticas externas
Estos ejemplos demuestran el alcance de las variables externas. Permane
cen en el ordenador durante toda la ejecucin del programa, y, al no perte Se puede declarar tambin una variable static externamente a las funcio
necer a ninguna funcin en concreto, no pueden eliminarse al acabar ningu nes. El resultado es la creacin de una funcin esttica externa. La dife
na de ellas. rencia entre una variable externa ordinaria y una variable externa esttica re
side en su alcance. La variable externa ordinaria se puede utilizar en funcio-

283
Hemos dicho con un poco de suerte porque la declaracin de una va
nes de cualquier fichero, mientras que la variable esttica externa puede em riable como modo registro es ms una splica que una orden directa. El or
plearse nicamente en funciones del mismo fichero, y situadas debajo de la denador intentar atender a sus demandas, pero tiene tambin que preocu
definicin de la variable. Se puede conseguir una variable esttica externa parse del nmero de registros que hay disponibles, que suele ser bastante es
colocando su definicin fuera de la funcin: caso; por tanto, es posible que no se pueda atender a su requerimiento. En
tal circunstancia, la variable se transforma en una variable automtica ordi
static arco = 1; naria.
circulo()
{

Qu modo de almacenamiento empleamos?


Dentro de poco contemplaremos un ejemplo en el cual se necesita este
tipo de variable. La respuesta a esta pregunta es casi siempre automtico. Despus de
todo, por qu si no se ha escogido como opcin por defecto? S, sabemos
que a primera vista el almacenamiento externo es bastante seductor. Hgan
Los ficheros 1 y 2 se se externas todas las variables, y uno no tendr que volver a preocuparse de
compilan conjuntamente
utilizar argumentos, punteros y comunicaciones entre funciones y toda la pa-
rafernalia restante. Desgraciadamente, tendramos que empezar a preocupar
nos porque la funcin A ha cambiado subrepticiamente las variables de la
funcin B, lo que no entraba en absoluto en nuestros planes. La evidencia
incuestionable de muchos aos de experiencia colectiva en ordenadores es que
el segundo peligro supera con mucho los superficiales encantos de un empleo
masivo de almacenamiento externo.
Una de las reglas de oro de una programacin protectora es observar el
principio cada uno sabe lo que necesita saber y nada ms. Mantenga las
tareas de cada funcin tan privadas como pueda, compartiendo el mnimo
nmero posible de valores y variables con otras funciones.
Habr tambin ocasiones en que los restantes modos sean tiles, por eso
estn ah. Sin embargo, aconsejamos que se pregunte a s mismo si realmente
necesita usarlos antes de emprender una aventura con ellos.
Se conoce tim en main ( ), figaro ( ), verde ( ) y rojo ( )
Se conoce tum nicamente en main ( ) y fgaro ( )

Figura 10.1 RESUMEN: MODOS DE ALMACENAMIENTO


Variables externas y externas static

Variables registro I. Palabras clave: auto, external, static, register


Normalmente, las variables se almacenan en la memoria del ordenador. II. Comentarios generales:
Con un poco de suerte, las variables de modo de almacenamiento registro Los modos de almacenamiento de una variable determinan su alcance y el tiem
quedan almacenadas en los registros de la CPU, en donde son mucho ms po que permanece la variable en el ordenador. El modo de almacenamiento
accesibles, y se manipulan ms rpidamente que en memoria. Por lo dems, queda, a su vez, determinado por el lugar donde se define la variable y por
las variables registro son idnticas a las variables automticas. Se organizan la palabra clave asociada que se incluya. Si una variable se define fuera de una
de la siguiente forma: funcin, se clasifica como externa y tiene alcance global. Las variables que se
declaran dentro de la funcin son automticas y locales, a menos que se em
main() plee alguna de las palabras clave restantes. Si una variable externa se define
con anterioridad a una funcin, esta ltima es capaz de reconocerla, aunque
register int rapido; no se declare internamente.

285
La variable esttica azar comienza con el valor 1 y queda alterada por
III. Propiedades
la frmula mgica cada vez que se solicitan los servicios de la funcin. El
resultado en nuestro sistema es un nmero situado en algn lugar del rango
MODO DE PALABRA DURACION ALCANCE 32768 a 32767. Los sistemas que tengan un tamao diferente de nmeros
ALMACENAMIENTO CLAVE enteros (int) producirn resultados distintos.
automtico auto temporal local Comprobaremos el funcionamiento de nuestro generador de nmeros alea
registro register temporal local torios con un sencillo driver.
esttico static persistente local
/* prueba aleat1 */
externo extern persistente global (a todos los main( )
ficheros) {
externo esttico static persistente global (a un int cont;
fichero)
for (cont =1; cont <= 5; cont++)
Los modos situados por encima de la lnea de puntos se declaran dentro de printf("%d\n", aleat ());
una funcin. }
Los situados por debajo de la lnea se definen fuera de la funcin.

Veamos ahora un ejemplo de funcin que emplea una variable esttica La salida obtenida es:
externa.
-26514
-4449
Funcin de nmeros aleatorios 20196
-20531
No se puede vivir sin una funcin que genere nmeros aleatorios. Cuan 3882
do alguien le pida que piense un nmero, podr dirigirse a esta poderosa fuente
en lugar de balbucear una splica para que le concedan tiempo. De la misma
importancia, pero quiz menos drstica, sea su utilizacin en muchos juegos Bien, parece bastante aleatorio. Vamos a ejecutarlo de nuevo. Esta vez
de ordenador. el resultado obtenido es:
En realidad, vamos a estudiar un generador de nmeros seudoaleato-
rios. Con ello queremos decir que la secuencia de nmeros que se va a obte -26514
ner es predecible (los ordenadores no se caracterizan por su espontaneidad); -4449
pero, en cualquier caso, estn razonablemente repartidos con uniformidad 20196
-20531
en el rango posible de valores. 3882
El planteamiento comienza con un nmero que se llama semilla. Se
usa la semilla para producir un nuevo nmero, el cual, a su vez, se utilizar
como nueva semilla. La nueva semilla, por su parte, puede emplearse para Dnde he visto yo antes esta secuencia? Bueno, ya avisbamos que este
producir otra nueva semilla, y as sucesivamente. Por todo lo dicho, si que generador era seudo. Cada vez que el programa principal se ejecuta, se
remos que el esquema se comporte adecuadamente, la funcin deber recor comienza con la misma semilla, 1.
dar la semilla que utiliz la ltima vez que fue llamada. Ajaj! Aqu hace Podemos vadear el problema introduciendo una segunda funcin, saleat( ),
falta una variable esttica. que permita reinicializar la semilla. El truco consiste en hacer que azar sea
A continuacin presentamos la versin 1. (S, la versin 2 viene en segui una variable esttica externa conocida nicamente por aleat( ) y saleat( ).
da.) Mantenga estas dos funciones en su propio fichero, y complelo por separa
/* aleat1 */ do. La modificacin a introducir es la siguiente:
aleat() {

static int azar = 1;


/* fichero para aleat () y saleat() */
azar = (azar * 25173 + 13849) % 65536; /* formula magica */ static int azar = 1;
return (azar);
} aleat()
{

287
azar = (azar * 25173 + 13849) % 65536;
return (azar);
/* formula magica */ EL ORDENADOR PERSONAL PARA...
}
saleat(x)
unsigned x;
{
azar = x;
}

Utilice el siguiente programa principal: UNA ESTRELLA DE CINE UN FUNCIONARIO DE PRISIONES

/* prueba aleat2 */
main()
{
int cont;
int semilla;
printf("Elija un numero como semilla.\n");
saleat(semilla); /* pone una nueva semilla */
for (cont = 1; cont <= 5; cont++)
printf("%d\n", aleat());
}
UN VOYEUR UN PELUQUERO

Ejecutemos el programa una vez:

Elija un numero como semilla.


Lanza los dados
1
-26514 Vamos a intentar simular un juego de azar muy popular, el lanzamiento
-4449 de dados. Los dados ms corrientes, con mucho, tienen seis caras; prepara
20196
-20531 remos un programa que emplee dos de estos dados. No olvidemos, sin em
3882 bargo, que hay muchas otras posibilidades: en bastantes juegos de aventuras
y fantasa se usan dados con la forma de cualquiera de los cinco poliedros
regulares: es decir, con 4, 6, 8, 12 20 caras. (Fueron los antiguos griegos
Si usamos un valor de 1 para semilla, obtenemos los mismos valores que quienes demostraron que existen cinco nicos cuerpos geomtricos regula
anteriormente. Probemos ahora con el valor 2: res, con todas las caras de la misma forma y tamao; estos poliedros son la
base de todas las variedades de dados. Se podran construir dados con otro
Elija un numero como semilla. nmero de caras, pero las posibilidades de que saliese un nmero determina
do no seran las mismas para todos ellos.)
23832 Sin embargo, los clculos en el ordenador no estn limitados por consi
20241
-1858 deraciones geomtricas, por lo que podemos disear un dado electrnico con
-30417 cualquier nmero de caras que deseemos. Empezaremos con 6 caras y gene
-16204
ralizaremos despus. Lo que deseamos es un nmero aleatorio entre 1 y 6,
pero nuestra funcin aleat( ) produce hasta ahora un nmero aleatorio en
el rango -32768 a 32767, por lo que deberemos hacerle algunos pequeos
Muy bien! Hemos conseguido un conjunto de nmeros diferente. Desa ajustes. Una posibilidad podra ser:
rrollemos ahora una aplicacin til para nuestras funciones.
1. Dividir el nmero aleatorio por 32768. El resultado sera un nmero
x en el rango -1 < =x < 1. (Previamente tendremos que haberlo con-

289
La salida sera:
vertido a tipo float, para que se puedan contener fracciones decima
les.) Introduzca semilla.
1
2. Sumar 1. El nmero ahora satisface la relacin 0 < =x < 2. Indique numero de caras por dado; 0 para terminar.
3. Dividir por 2. Ahora 0 < =x < 1. 6
4. Multiplicar por 6. En este caso 0 < =x < 6. (Bastante cerca, pero el Cuantos dados?
0 no sirve como valor.) 2
Acaba de sacar un 4 con 2 dados de 6 caras.
5. Sumar 1. En este momento 1 < = x < 7. (Nota: todava tenemos frac Cuantas caras? Pulse 0 para acabar.
cin decimal.) 6
6. Truncar a entero. Ahora tendremos el entero en el rango 1 a 6. Cuantos dados?
2
7. Para generalizar, basta con reemplazar el 6 de la etapa 4 con el nme Acaba de sacar un 7 con 2 dados de 6 caras.
ro de caras deseado. Cuantas caras? Pulse 0 para acabar.
0
La funcin que realiza estas etapas es: QUE TENGA SUERTE!!!

/* juega a los dados */ Gracias.


#define ESCALA 32768.0 Se puede utilizar cubilete( ) de muchas formas diferentes. Si se hace la
cubilete(lados)
float lados; dos igual a dos, la funcin simular el lanzamiento de una moneda con ca
{ ra = 2 y cruz = 1 (o viceversa, si realmente lo prefiere). Tambin es
float tirada;
muy fcil modificar el programa para mostrar los resultados de cada uno de
tirada = ( (float) aleat() /ESCALA + 1.0) * lados / 2.0 + 1.0; los dados, adems del total. Si necesita un mayor nmero de tiradas (un ima
return ( (int) tirada); ginario dueo del castillo barajando atributos de caracteres), se puede modi
}
ficar fcilmente el programa para producir una salida como la siguiente:

Introduzca semilla
Hemos incluido explcitamente dos moldeadores de tipo con el fin de re 10
marcar las conversiones de tipo que estn teniendo lugar. Introduzca numero de tiradas; 0 para terminar.
18
Hagamos ahora un programa para jugar a los dados: Cuantas caras y cuantos dados?
6 3
Las 18 puntuaciones conseguidas con 3 dados de 6 caras son:
/* tiradas multiples de dados */ 7 5 9 7 12 10 7 12 10 14
main() 9 8 13 9 10 7 16 10
{ Cuantas tiradas? Pulse 0 para acabar.
int dados, cont, tirada, semilla; 0
float lados;
printf("Introduzca semilla.\n");
scanf("%d", &semilla); Otro posible empleo de aleat( ) (pero no de cubilete( )) podra ser modi
saleat(semilla); ficar nuestro viejo amigo el programa de adivinar nmeros de forma que fuese
printf("Indique numero de caras por dado; 0 para terminar.\n");
scanf("%f", &lados); el ordenador el que escogiera y usted el que intentase adivinar, en lugar de
while (lados > 0) lo contrario.
{
printf("Cuantos dados?\n"); Dediqumonos ahora a otras funciones. Nuestro siguiente proyecto es el
scanf("%d", &dados); diseo de una funcin que sea capaz de leer enteros.
for (tirada = 0, cont = 1; cont <= dados; cont++)
tirada += cubilete(lados);/* calcula total de la tirada */
printf ("Acaba de sacar un %d con %d dados de %.0f caras. \n",
tirada, dados, lados); Una funcin para atrapar enteros: getint( )
printf("Cuantas caras? Pulse 0 para acabar.\n");
scanf("%f", &lados);
} Quiz le parezca un proyecto demasiado poco ambicioso. Despus de to
printf("QUE TENGA SUERTE !!\n"); do, podemos emplear scanf( ) con el formato % d si queremos leer un ente-
}
291
realmente til, deber ser capaz tambin de indicar cundo encuentra un
ro. Indudablemente, sera una solucin muy fcil (incluso perezosa), pero tiene rcter EOF. As se podra utilizar getint( ) en un bucle while que pudiese es
un grave defecto. Si se teclea por error, digamos, una T en lugar de un 6, tar leyendo enteros hasta encontrar un carcter EOF. En resumen, deseamos
scanf( ) intentar interpretar la T como un entero. Lo que deseamos es dise que getint( ) devuelva dos valores: el entero y una informacin sobre lo le
ar una funcin que estudie la entrada y que avise si no es un entero lo que do.
se ha introducido. Quiz ahora el problema no parezca tan sencillo. No obs As pues, necesitamos que la funcin devuelva dos parmetros, por lo que
tante, no debe apurarse demasiado: le tenemos reservado un excelente co queda excluido del empleo de return. Se podran usar dos punteros; sin em
mienzo. Ya tenemos nombre para nuestra nueva funcin: se llamar getint( ). bargo, la solucin ms comn para este tipo de problemas es utilizar punte
ros para hacer la mayor parte del trabajo de la funcin, y usar a continua
Planteamiento cin return para enviar algn tipo de cdigo al programa de llamada. De he
cho, scanf( ) hace justamente esta operacin. Devuelve el nmero de tems
Afortunadamente, tambin tenemos una estrategia pensada. En primer que ha encontrado, y tambin el carrter EOF si lo detecta. No hemos utili
lugar, hay que advertir que cualquier entrada puede leerse como tira de ca zado esta caracterstica de scanf( ) hasta ahora, pero podemos hacerlo em
racteres. Por ejemplo, el entero 324 se puede interpretar como una tira de pleando una llamada a la funcin de este tipo:
tres caracteres: el carcter 3, el carcter 2 y el carcter 4. Esto sugie
re el siguiente planteamiento: quees = scanf("%d", &numero) ;

1. Leer la entrada como tira de caracteres.


2. Comprobar que esta tira se compone exclusivamente de caracteres nu Siguiendo este modelo, nuestra llamada a la funcin tendr el siguiente
mricos precedidos, quiz, de un signo ms o menos. aspecto:
3. Si es as, convertir la tira en el valor numrico correcto.
4. Si no, enviar un aviso. quees = getint(&numero);

Este planteamiento es tan genial que tiene que funcionar con toda seguri La parte derecha utiliza la direccin de nmero para asignar un valor a
dad (el hecho accesorio de que ha venido utilizndose durante aos nos da nmero, mientras que el valor de quees se asignar por medio del return.
una confianza adicional). Sin embargo, antes de pasar este seudocdigo a
lenguaje de ordenador deberemos pensar un poco ms sobre lo que nuestra
funcin debe hacer. ENTRADA SALIDA
En concreto, antes de empezar a gastar neuronas en las interioridades de Direccin de
getint( ) tenemos que decidir exactamente cmo debe interaccionar la fun valor de la variable "int"
la variable "int
cin con su entorno; en otras palabras, cul va a ser el flujo de informacin.
Qu informacin deberemos enviar desde el programa de llamada? Qu
informacin deber devolverte como retorno? En qu forma se deber ex return (informacin adicional)

presar esa informacin? Una vez ms intentamos imaginar nuestra funcin


como caja negra. Nuestro primer objetivo es decidir lo que va y lo que vuel Figura 10.2
ve; despus nos preocuparemos por lo que hay dentro. Este mtodo consigue Diseo de la funcin getint( )
una interaccin mucho ms suave entre las diferentes partes de un progra
ma. De otro modo, se podra acabar encontrando la forma de instalar el mo
tor de una locomotora en un frmula 1. La funcin general sera correcta,
el problema sera la adaptacin en s. Tambin debemos decidir un cdigo para la informacin contenida en
quees. Como sabemos, una funcin no declarada se supone de tipo int, por
Flujo de informacin para getint( ) lo que nuestro cdigo lo formaremos con enteros. Vamos a utilizar la siguiente
Qu tipo de salida deseamos para nuestra funcin? Evidentemente, de convencin:
be entregar el valor del nmero que lee. Por supuesto, scanf( ) tambin lo
hace. En segundo lugar y esta es la razn por la que estamos gastando tiem 1 significar que se ha encontrado un carcter EOF.
po en crear esta funcin, debe ser capaz de distinguir lo que lee, es decir, 1 significar una tira de caracteres que contiene, por lo menos, un
si lo que ha encontrado es o no un entero. Si deseamos que la funcin sea carcter no dgito.
0 significar una tira compuesta nicamente por dgitos.

293
Limitaremos la longitud de la tira de entrada a 80 caracteres. Como es
En resumen, nuestra funcin getint( ) tiene una entrada, la direccin de sabido, las tiras finalizan con un carcter nulo, por lo que necesitaremos un
la variable entera cuyo valor va a leerse. Tiene tambin dos salidas: primera, array de 81 caracteres para dar cabida a este ltimo. No deja de ser extraor
el valor del entero ledo, que se obtendr por medio de un puntero (el argu dinariamente generosa nuestra manera de organizar la entrada, ya que se ne
mento del puntero, por tanto, funciona como un canal de informacin de cesitan nicamente 6 caracteres para introducir un entero de 16 bits con sig
doble va); segunda, un cdigo que se enviar por medio de return. Con es no. Evidentemente, se podrn introducir enteros ms largos, pero sern cor
tos detalles, el esqueleto de nuestra funcin ser: tados hasta el tamao permitido.
Con el fin de hacer el programa ms modular, encomendaremos la con
getint(ptint)
int *ptint; /* puntero a entero */ versin real de la tira en nmero a otra funcin, que llamaremos stoi( ). Tam
{ bin adoptaremos el return de stoi( ) para que enve un cdigo con informa
int quees; cin pertinente a getint( ), de manera que esta ltima pueda enviar su propio
return(quees); informe al programa de llamada. La funcin stoi( ) ejecutar las dos ltimas
} lneas del seudocdigo indicado arriba.
En la figura 10.3 se presenta el programa que hemos preparado para ge-
Grandioso! Lo nico que nos falta es rellenar el interior de nuestra funcin. tint( ). No est incluido stoi( ), que se introducir ms adelante.

El interior de getint( ) /* getint() */


#include <stdio.h>
El planteamiento general expresado hasta ahora se puede resumir en seu- #define LEN 81 /* longitud maxima de la tira de numeros */
docdigo de la siguiente forma: #define STOP -1 /* codigos de error para quees */
#define NONUM 1
#define SINUM O
leer la entrada como caracteres getint(ptint)
while no se encuentra un EOF, colocar los caracteres en una tira int *ptint; /* puntero al entero de salida */
if se encuentra EOF, enviar cdigo STOP {
else char intarr[LEN]; /* para almacenar tira de entrada */
Comprobar la tira, convertir en entero si es posible y enviar el cdigo int ch;
de control (SINUM o NONUM) int ind = O; /* indice del array */

while ( (ch = getchar()) == '\n' || ch == ' ' || ch == '\t');


Vamos a utilizar STOP, SINUM y NONUM como constantes simbli /* salta caracteres nueva linea, blancos y tabulados */
cas, que representen 1, 0 y 1, respectivamente, de la forma descrita con an while (ch != EOF && ch != '\n' && ch != ' ' && ind < LEN)
terioridad. {
intarr[ind++] = ch; /* introduce caracter en array */
Todava tenemos que tomar algunas decisiones que conciernen al diseo. ch = getchar(); /* toma otro caracter */
Cmo decidir la funcin que se ha terminado la entrada de una tira? De }
bemos limitar la longitud de la tira? intarr[ind] = '\0'; /* acaba array con un caracter nulo */
if (ch == EOF)
Nos introducimos en un territorio en el que tenemos que decidir entre la return(STOP);
conveniencia del programador y la conveniencia del usuario. La forma ms else
conveniente de atacar el problema sera terminar la tira con una tecla [enter]. return (stoi(intarr, ptint)); /* hace la conversion */
}
As se conseguira tomar una sola entrada por lnea. Por otra parte, sera muy
agradable al usuario poder colocar varios nmeros en la misma lnea:
Figura 10.3
Programa para getint()
2 34 4542 2 98

Por esta vez nos inclinaremos por el bando del usuario. La funcin consi
Comenzamos con un carcter ch. Si es blanco, o nueva lnea, o tabulado,
derar una tira como un conjunto de caracteres que comienza por un carc
ter no blanco ni nuevalnea, y finaliza cuando se encuentre el prximo carc obtenemos el siguiente carcter hasta que hay uno que no lo es. Si este lti
ter blanco o nuevalnea. De esta forma, la entrada se podr hacer en una sola mo tampoco es un EOF, lo colocamos en un array. Los caracteres que ven
lnea o en varias, a eleccin. gan a continuacin se siguen introduciendo en dicho array hasta encontrar
un carcter prohibido o alcanzar el tamao lmite. Colocamos a continua-

295
numrico. Supongamos que el carcter es 4. Este carcter tiene un valor
cin un carcter nulo ( \ 0) en la siguiente posicin del array, con el fin de numrico ASCII 52, que es la forma en que se ha almacenado. Si restamos
marcar el final de la tira de caracteres. Con ello conseguimos transformar 48, obtenemos 4, es decir,
el array en una tira de caracteres estndar. Si el ltimo carcter ledo es un
EOF, devolvemos STOP por medio del return; en caso contrario, seguimos 4 - 48 = 4
adelante e intentamos transformar la tira en enteros. Para ello llamamos a
la funcin stoi( ) que tiene encomendado ese trabajo. Qu hace stoi( )? To pero 48 es simplemente el cdigo ASCII del carcter 0; por tanto,
ma como entrada una tira de caracteres y un puntero a una variable entera.
Deber utilizar el puntero para asignar un valor a la propia variable; tam 4 - 0 = 4
bin utilizar return para enviar un informe sobre los problemas encontra
dos en la lectura de la tira, informe que ser, a su vez, utilizado por getint( ) De hecho, esta ltima sentencia se cumplir para cualquier cdigo que utilice
para elaborar el suyo propio. Estamos jugando con dos barajas! nmeros consecutivos para representar dgitos consecutivos. Si llamamos num
Una forma menos compacta de representar la utilizacin de stoi( ) es: al valor numrico y chn es un carcter que representa un nmero, tendre
mos:
quees = stoi(intarr, ptint);
return (quees); num = chn - 0;

donde quees sera una variable de tipo int. La primera sentencia asignara Con esta tcnica podemos convertir el primer carcter en un nmero. A
un valor a lo que ptint estuviese apuntando, independientemente de lo que continuacin buscaremos el siguiente miembro del array. Si es ' \ 0 ' haba
sea, y tambin asignara ese valor a quees. La segunda sentencia devolvera un nico nmero, y ya hemos terminado. Supongamos, sin embargo, que
el valor al programa que haba llamado previamente a getint( ). La nica l sea un 3. Lo convertiremos en el valor numrico 3; pero si hay un 3 en esa
nea de programa incluida en la figura anterior tiene exactamente el mismo posicin, nuestro 4 deba ser en realidad 40, y el total es 43:
efecto, con la diferencia de que no se ha empleado una variable intermedia.
Bien, lo nico que falta es escribir stoi( ), y habremos acabado el tra
num = 10 * num + chn - 0;
bajo.

Conversin de tira de caracteres en entero: stoi( ) Lo nico que tenemos que hacer ahora es continuar este proceso indefini
damente multiplicando el antiguo valor de num por 10 cada vez que encon
Describiremos, en primer lugar, la entrada y salida que debe tener esta tremos un nuevo dgito. En nuestra funcin se emplea esta tcnica.
funcin. La entrada habr de ser una tira de caracteres, por lo que stoi( ) A continuacin presentamos el listado de stoi( ). Lo mantendremos en
deber tener un argumento de tipo tira. Tambin habr dos valores de sali el mismo fichero que getint( ), por lo que usaremos los mismos #define.
da: la conversin a entero y el informe antes mencionado. Utilizaremos re
turn para este informe, pero tendremos que emplear un puntero para devol
/* convierte una tira en entero y comprueba el resultado */
ver el otro valor. As pues, deber haber un segundo argumento, que ser stoi(string, intptr)
un puntero a entero. El esqueleto de nuestra funcin tendr un aspecto como char string[]; /* tira a ser convertida en entero */
ste: int *intptr; /* valor del entero */
{
int signo =1; /* tiene en cuenta el signo + o - */
stoi(strinq, intptr) int indice = 0;
char string[]; /* string de entrada */
int *intptr; /* puntero a la variable valor del entero */
{ if (string[indice] == '-' || string[indice] == '+' )
int quees; signo = (string[indice]== '-') ? -1 : 1; /* pone signo */
*intptr = 0; /* inicializa valor */
return(quees) ; while (string[indice] >= '0' && string[indice] <= '9')
} *i nt pt r = 10 * (*i nt pt r) + st ri ng[ i ndi ce++] - ' 0' ;
i f ( st ri ng[ i ndi ce] == ' \ 0 ' )
{
*intptr = signo * (*intptr);
Bueno, ahora debemos buscar un algoritmo que haga la conversin. Por return(SINUM) ;
el momento, ignoraremos el signo y supondremos que la tira contiene nica }
mente dgitos. Miremos el primer carcter y convirtmoslo en su equivalente else /* hay un caracter no numerico distinto de '\0' */
return(NONUM);
}

297
Como se puede observar, funciona satisfactoriamente. Observe que po
demos preparar un bucle que lea enteros indefinidamente hasta encontrar
La setencia while sigue funcionando tranquilamente, convirtiendo dgi
tos en nmeros, hasta que encuentra un carcter que no corresponda a una un carcter EOF. Esta caracterstica puede resultar de utilidad.
cifra. Si tal carcter es un \0, es seal de que todo ha ido bien, porque Hay algn error? Al menos hay uno. Si colocamos un carcter EOF in
es la marca de final de la string. Cualquier otro carcter que no sea un dgito mediatamente detrs de un nmero sin dejar por medio un blanco o un ca
enva el programa al else, que emitir un informe negativo de la lectura. rcter nuevalnea, se detiene la frase de entrada y se ignora el ltimo nme
En la librera C estndar existe una funcin llamada atoi( ) (conversin ro:
ASCII a entero) muy semejante a stoi( ). Las diferencias fundamentales son
que stoi( ) comprueba si la tira enviada contiene algn carcter no numri 706 EOF /* se acepta 706 */
706EOF /*no se acepta 706 */
co, y que atoi( ) utiliza return para devolver el nmero en lugar de un punte
ro; adems, atoi( ) hace por su cuenta el salto de blancos que nosotros hace
mos en getint( ). En realidad, podramos haber realizado la comprobacin .
de la tira en getint( ) y haber empleado atoi( ) en lugar de stoi( ), pero pen Ahora que tenemos una prctica funcin de capturar enteros, nos pro
samos que es ms divertido desarrollar nuestra propia funcin. pondremos un nuevo objetivo que haga uso de la misma.
Ordenacin de nmeros
Por qu no las probamos? Una de las pruebas ms comunes a que se somete un ordenador es la or-
Es realmente lgica la lgica que hemos usado? La nica manera de com
probarlo es hacer un test de nuestras funciones en un programa de prueba:
denacin de nmeros. Desarrollaremos aqu un programa que ordene ente
/ * prueba de getint() */ ros. Como siempre, lo consideraremos como una caja negra de la que slo
#define STOP -1
# define NONUM 1 nos preocupar su entrada y salida. Nuestro plan general, mostrado en la fi
# define SINUM 0 gura 10.4, es agradablemente sencillo.
main()
{
int num, quees;

printf("Este programa deja de leer numeros con EOF.\n");


while((quees = getint(&num)) != STOP)
if (quees == SINUM)
printf("El numero %d ha sido aceptado. \n", num); nmeros
else nmeros ordenados
printf("Eso no es un numero!! Pruebe otra vez.\n");
printf("Eso es todo.\n");
}

Una posible salida sera: Figura 10.4


Programa de ordenacin como caja negra
Este programa deja de leer numeros con EOF.
100 -23
El numero 100 ha sido aceptado.
El numero -23 ha sido aceptado. Por el momento, el programa est an demasiado nebuloso para poderlo
+892 pasar a cdigo C. El siguiente paso es identificar las principales tareas que
El numero 892 ha sido aceptado. el programa debe realizar para alcanzar la meta propuesta. Se puede dividir
flock
Eso no es un numero!! Pruebe otra vez. este programa en tres tareas principales:
23tururu
Eso no es un numero!! Pruebe otra vez. 1. leer los nmeros,
775
El numero 775 ha sido aceptado. 2. ordenarlos,
[control-z] (envia el caracter EOF en nuestro sistema) 3. imprimir los nmeros ordenados.
Eso es todo.
299
Eleccin de la representacin de datos
La siguiente figura muestra esta subdivisin, conforme nos movemos del
nivel mayor de organizacin a un nivel ms detallado. Cmo se puede representar un paquete de nmeros? Podemos usar un
paquete de variables, uno por nmero. Esta solucin es tan problemtica que
ms vale olvidarla. Tambin podemos emplear un array, con un elemento
por nmero. Esto suena mucho mejor, por lo que usaremos un array.
Pero, qu tipo de array De tipo int? De tipo double? Necesitamos sa
nmeros ber para qu se va a usar el programa. Supondremos que se va a usar con
nmeros
ordenados enteros. (Y qu sucedera si fueran nmeros de tipos diferentes? Es posible
hacerlo, pero rebasa, por ahora, nuestras miras.) Usaremos un array de en
teros para almacenar los nmeros que vayamos leyendo.

Figura 10.5
Final de la entrada
Programa de ordenacin: curioseando en su interior.
Cmo sabe el programa la cantidad total de nmeros que hay que leer?
En el captulo 8 hemos discutido varias soluciones a este problema, la mayo
ra poco satisfactorias. Pero ahora disponemos de getint( ), por lo que nues
Hasta ahora hemos logrado transformar nuestra caja negra en tres cajas tro problema no es problema. Una posible solucin sera:
ms pequeas, cada una con su propia entrada y salida. Podramos asignar
cada parte a un equipo diferente de programacin, con la nica salvedad de leer un nmero
que deberamos asegurarnos que la salida numrica de leer tuviese el mis while not EOF
mo formato que el empleado por ordenar como entrada. asignarlo a un array y
Como ver, insistimos una vez ms en la modularidad. Hemos dividido leer el siguiente nmero si el array no est completo
el problema original en tres problemas menores y, por ende, ms maneja
bles. Observe que hay dos condiciones separadas que hacen que finalice la en
Qu viene ahora? Dedicaremos nuestros esfuerzos a cada una de las tres trada de nmeros: una seala EOF o un array completo.
cajas separadamente, dividindolas en unidades cada vez ms simples, hasta
alcanzar un punto en que la codificacin en lenguaje C sea obvia. Mientras
estamos en ello, dedicaremos nuestra atencin a tres detalles de importancia: Otros aspectos
la eleccin del formato de los datos, el filtrado de errores y el flujo de infor Antes de pasar estas ideas a cdigo C tenemos que tomar an algunas
macin. Para continuar con nuestro ejemplo, comencemos con la seccin de decisiones. Qu hay de comprobacin de errores? Debemos hacer esta parte
dicada a lectura. del programa como funcin aparte?
Con la primera pregunta queremos significar si vamos a tomar precau
Lectura de datos numricos ciones contra la posibilidad de que el usuario introduzca datos errneos, co
Un gran nmero de programas requiere entradas numricas, de forma que mo letras en lugar de nmeros. Sin getint( ) deberamos apoyarnos en la teo
las ideas que desarrollemos aqu podrn ser tiles en muchas otras ocasio ra del perfecto usuario, que establece que el usuario, por definicin, no
nes. La forma general de entrada en esta parte del programa est muy clara: comete errores. Sin embargo, todos sabemos que esta teora funciona con
usar un bucle para leer nmeros hasta que se hayan ledo todos. Sin embar nosotros mismos, pero no con los dems usuarios. Por fortuna, disponemos
go, no es tan fcil como parece. del informe enviado por getint( ) para ayudarnos en estos casos.
La programacin que falta se puede adaptar fcilmente a main( ); sin em
bargo, es ms modular utilizar una funcin distinta para cada una de las tres
partes principales del programa, y as es como lo haremos. La entrada a esta
funcin sern nmeros desde teclado o desde un fichero, y la salida ser un
nmeros nmeros array que contenga los nmeros desordenados. Sera interesante que el pro
grama principal supiese cuntos nmeros se han ledo; por tanto, haremos
que nuestra funcin enve tambin esta cantidad como salida. Por ltimo,
intentaremos ser un poco amistosos con el usuario, por lo que enviaremos

301
Veamos qu aspecto tiene getarray( ):
un mensaje indicando los lmites a los que se ha de sujetar, as como un eco /* getarray() usando getint() */
de su entrada. #define STOP -1 /* detecta EOF */
#define NONUM 1 /* detecta entrada no numerica */
#define SINUM 0 /* acepta numeros */
getarray(array, limite)
main( ) y getarray( ) int array[], limite;
{
Llamaremos a nuestra funcin de lectura getarray( ). Ya hemos definido int num, quees;
la funcin en trminos de entrada y salida, y hemos esquematizado su inte int indice = 0; /* indice del array */
rior en seudocdigo. Escribamos ahora la funcin y ocupmonos a continua printf("Este programa deja de leer a los %d numeros\n",
cin de adaptarla en el programa principal. limite);
El programa main( ) sera: printf("o si se pulsa un EOF.\n");
while(indice < limite && (quees = getint(&num)) != STOP)
{
/* ordena1 * / /* deja de leer al limite o al pulsar EOF */
#define TAMMAX 100 /* limite de numeros a clasificar */ if (quees == SINUM)
main () {
{ array[index++] = num;
int numeros[TAMMAX] ; /* array para entrada */ printf("El numero %d ha sido aceptado. \n", num);
int total; /* numero de entradas * / }
else if (quees == NONUM)
total = getarray(numeros,TAMMAX); / * mete entrada en array*/ printf("Eso no es un entero!! Pruebe otra vez.\n");
ordena(numeros, total); /* ordena el array */ else
imprime(numeros, total); /* imprime el array ordenado */ printf("Esto no deberia suceder. Algo va muy mal.\n");
} }
if (indice == limite) /* avisa si se ha llenado el array * /
printf("Tengo completos los %d elementos del array.\n,
Ya tenemos aqu una perspectiva global del programa. La funcin geta- limite);
return(indice);
rray( ) coloca la entrada en el array nmeros y, adems, devuelve el nmero }
total de valores que se han ledo; ese nmero se asigna a la variable total.
Vienen a continuacin ordena( ) e imprime( ), las cuales tienen an que es Es una considerable porcin de programa, que conviene aclarar en algu
cribir, ordenar el array e imprimir los resultados. Si les enviamos el nmero
total, facilitaremos su tarea y ahorraremos que tengan que hacer sus propias
nos puntos.
cuentas. Adems, definiremos en getarray( ) un tamao mximo, TAMMAX,
que definir el lmite mximo de almacenamiento del array. Explicacin
Ahora que hemos aadido total al flujo de informacin, deberemos mo Resulta un poco difcil recordar el significado de un cdigo 1, por ejem
dificar, en consecuencia, nuestro diagrama de caja negra. Vase la figura 10.6. plo, por lo que hemos empleado constantes simblicas mnemotcnicas para
representar los cdigos de error.
Hemos preparado getarray( ) para manejar cada uno de los posibles c
digos. Un cdigo STOP produce una parada en el ciclo de lectura cuando
getint( ) se encuentra un EOF cerrndole el paso. Un cdigo SINUM produ
ce el almacenamiento del nmero en el array que estaba esperndolo; ade
nmeros ms, el nmero enva un eco al usuario para comunicarle que ha sido acep
nmeros ordenados
tado. Por su parte, un cdigo NONUM enva al usuario un mensaje para
que lo intente de nuevo. (A esto se llama relaciones de buena vecindad.)
An hay otra sentencia else. En buena lgica, la nica forma de alcanzar
esta sentencia es que getint( ) devuelva un valor distinto de -1, 0 1. Sabe
mos, sin embargo, que estos valores son los nicos que van a devolverse, por
F igu ra 10.6
lo que parece que nuestra ltima sentencia es intil. Por qu la incluimos?
Programa de ordenacin: detalles
La utilizamos como ejemplo de programacin defensiva, que es el arte

303
bemos tener en cuenta que los arrays comienzan sus ndices por 0, no por
de proteger un programa contra futuras manipulaciones. Algn da, noso 1. Observe el cdigo presentado y trate de comprobar si funciona. La mane
tros, o algn otro, podemos intentar manipular getint( ) aadiendo algunos ra ms fcil de hacerlo es imaginar que lmite vale 1, y seguir el programa
otros cdigos de nuestro propio repertorio. Probablemente habremos olvi sentencia a sentencia.
dado, o nunca habrn sabido, que getarray( ) supone una de entre tres ni Frecuentemente, la parte ms complicada de un programa es la consecu
cas posibles respuestas. La funcin de este else es atrapar cualquier nueva cin de una interaccin en forma conveniente con el usuario. Es el caso de
respuesta que aparezca, lo que permitir al eventual manipulador detectar nuestro programa. Ahora que hemos conseguido preparar getarray( ), ob
rpidamente el error cometido y obrar en consecuencia. servaremos que ordena( ) es bastante ms sencilla, e imprime( ), ms senci
El tamao del array queda establecido en main( ). As pues, no tenemos lla an. Vayamos con ordena( ) en primer lugar.
que incluir este tamao cuando declaramos el argumento del array en geta-
rray( ). Sin embargo, lo hacemos incluyendo los corchetes, con el fin de de Ordenacin de los datos
jar claro que el argumento es un array.
Observemos de nuevo main( ):
int numeros[TAMMAX] ; /* define tamano en main */
int array[]; /* no se indica tamano en la funcion llamada */

nmeros nmeros
Discutiremos ms profundamente el empleo de los arrays en funciones
en el captulo 12. ordenados
total
Tambin hemos decidido usar la palabra clave return para comunicar al
programa de llamada el nmero de valores ledos. Nuestra llamada de fun
cin, por tanto, ser
total = getarray(numeros); main()
{
int numeros[TAMMAX]; /* array para entrada */
asignando un valor a total y dando valores al array nmero. int total; / * numero de entradas * /
Se estar preguntando por qu no hemos usado punteros en la llamada
total = getarray(numeros,TAMMAX); /* mete entrada en array */

total = getarray(&numeros); ordena(numeros, total); / * ordena el array * /


imprime(numeros, total); /* imprime el array ordenado */
}
Despus de todo, necesitamos que la funcin cambie el valor de algo (del
array) del programa de llamada. La respuesta quiz le sorprenda: S, esta
mos usando un puntero! En C, el nombre de un array es tambin un puntero Como vemos, la entrada a ordena( ) es un array de enteros a ser ordena
al primer elemento del mismo, es decir: dos y un contador con el nmero de elementos que se incluyen. La salida
es el propio array con los nmeros ordenados. An no hemos decidido cmo
numeros == &numeros[0] ordenarlos, por lo que tendremos que afinar la descripcin posteriormente.
Cuando getarray( ) prepara el array array, la direccin de array[0] es la Una cuestin obvia que hay que decidir es la direccin de ordenacin. Va
misma que la direccin de nmeros[0], y lo mismo sucede con el resto de los mos a ordenar los nmeros de mayor a menor, o viceversa? De nuevo, hay
subndices. As, todas las manipulaciones que getarray( ) haga en array[ ] que ser arbitrarios en este punto, y decidiremos ordenar de mayor a menor.
se estn haciendo, en realidad, en nmeros[ ]. Tambin hablaremos de la re (Podramos hacer un programa que permitiese elegir entre ambas opciones,
lacin entre punteros y arrays en el captulo 12. El hecho ms notable que pero tendramos tambin que desarrollar la forma en que el programa deci
necesitamos saber por el momento es que empleamos un array como argu diera cul de las dos opciones tomar.)
mento de funcin, afectando la funcin al array del programa de llamada. Nos ocuparemos ahora del mtodo de ordenacin. Hay un gran nmero
Cuando una funcin utiliza contadores y lmites, como es nuestro caso, de algoritmos de ordenacin para ordenadores; aqu usaremos uno de los ms
la mayora de los errores aparecen en las condiciones de contorno, all sencillos.
donde el contador alcanza su lmite. Estamos leyendo un mximo de TAM- La estrategia a seguir, expresada en seudocdigo, es:
MAX nmeros, o los estamos pasando en uno? Tenemos que prestar aten
cin a detalles como + + ndice y/o ndice + + , y < y/o < = . Tambin de for n = primero a n = penltimo elemento encontrar nmero ma
yor y colocarlo en la posicin del elemento n.

305
Hemos llamado tope al ndice del elemento del array que ha de ser ocupa
do, ya que est en el extremo de la parte no ordenada del array. El ndice
La filosofa es la siguiente: la primera vez, n = 1. Buscamos en todo el
busca, se pasea por la parte de array situada por debajo del elemento tope.
array, encontramos el nmero mayor, y lo colocamos como primer elemen
to. A continuacin, n = 2, y repetimos la operacin en todos los nmeros La mayor parte de los libros emplean i y j como ndices, pero as es ms dif
del array excepto en el primero, encontrando el mayor nmero entre los res cil observar lo que est sucediendo.
tantes y colocndolo en segunda posicin. Continuamos este proceso hasta El algoritmo recibe el nombre de ordenacin por burbuja, porque los
que alcanzamos el penltimo elemento. valores mayores van flotando lentamente hacia la superficie.
En ese momento, quedan slo dos elementos por colocar. Los compara Por ltimo, nos queda por escribir la funcin imprime( ).
mos y colocamos el mayor en penltima posicin. As se queda confinado
el menor elemento en la posicin final. Impresin de los datos
Esta tarea parece que ni pintada para un bucle for, pero falta por descri
bir el proceso de encontrar y colocar el nmero con mayor detalle. C nmeros
mo se puede encontrar el nmero mayor cada vez? Hay un forma muy senci ordenados nmeros
lla: comprense el primero y segundo elemento de la porcin de array de la imprimir ordenados
que tengamos que extraer el nmero mayor; si el segundo es mayor, inter- total
cmbiense los dos valores; comprese ahora el primer elemento con el terce
ro; si el tercero es mayor, intercmbiense tambin; cada vez que se realiza esta
operacin, el elemento mayor flota hasta la parte superior; continuamos Esta funcin es la ms sencilla:
este proceso hasta comparar el primer elemento con el ltimo; cuando haya
mos terminado, el nmero mayor estar colocado como primer elemento de /* imprime un array */
imprime(array, limite)
su porcin de array. En resumen, hemos ordenado el primer elemento del int array[], limite;
array, pero el resto es an una mezcolanza. Expresado en seudocdigo: {
int indice;
for n = segundo elemento hasta ltimo elemento comparar elemen for (indice = O; indice <= limite; indice++)
to n con primer elemento; si n es mayor, intercambiar valores. printf("%d\n", array[indice]);
}
De nuevo la tarea tiene aspecto de bucle for. Podr estar anidada dentro
del primer bucle; el bucle externo indicar qu elemento del array hay que Si deseamos algo diferente, como imprimir en filas en lugar de una sola
rellenar, y el interno buscar el valor a colocar all. Si ponemos las dos partes columna, siempre podemos volver atrs y cambiar esta funcin sin modifi
de seudocdigo juntas y las traducimos a C, tenemos la siguiente funcin: car las dems. De igual forma, si encontrsemos un algoritmo de ordenacin
que nos gustase ms, podramos reemplazar aquel mdulo. Esta es una de
/* ordena el array en orden decreciente */
ordena(array, limite) las grandes ventajas que tiene la programacin modular.
int array[], limite;
{ Resultados
int tope, busca;
for (tope = 0; tope < limite-1; tope++) Compilemos y comprobemos el paquete obtenido. Para estudiar con fa
for (busca = tope + 1; busca < limite; busca++) cilidad las condiciones de contorno, haremos TAMMAX momentneamente
if (array[busca] > array[tope]) igual a 5.
intercambia(&array[busca], &array[tope]);
} En nuestra primera ejecucin, introduciremos nmeros en el programa
hasta que rehse aceptar ms.
Aqu hemos conseguido acordarnos de que el primer elemento tiene su
E ste p rogram a deja de lee r a lo s 5 nu m ero s
bndice 0. Tambin nos hemos acordado de que desarrollamos una funcin o si se pulsa un E O F .
de intercambio en el captulo 9, por lo que la vamos a emplear aqu. Como 12 34 54 23 67
intercambia( ) funciona sobre dos elementos del array, y no sobre el array T e n g o c o m p le t o s l o s 5 e l e m e n t o s d e l a r r a y .
67
completo, hemos usado las direcciones de los elementos correspondientes. (El 54
nombre array es un puntero al array completo, pero se debe usar el operador 34 23
& para apuntar a un solo miembro.)
12

307
Bien, se ha detenido al leer 5 nmeros y ha ordenado los resultados. Com
Resumen
probaremos ahora que es capaz de detenerse por un carcter EOF.
Qu hemos conseguido? En el lado prctico, hemos desarrollado un
Este programa deja de leer a los 5 numeros generador de nmeros aleatorios y un programa de ordenacin de enteros;
o si se pulsa un EOF. durante el proceso, hemos preparado una funcin getint( ), que puede utili
456 928
-23 +16 zarse con otros programas; desde el punto de vista educativo, hemos ejem
[control-z] (transmite EOF en nuestro sistema) plarizado algunos principios generales y conceptos tiles en el diseo de
928
456 programas.
16 La consecuencia ms importante que se debe sacar es que los programas
deben ser diseados, en lugar de lucubrados en un proceso aleatorio de cre
cimiento, ensayo y error. Antes de escribir una sola lnea de programa, debe
r pensar cuidadosamente el formato y contenido de su entrada y salida; in
En menos tiempo del que se tarda en decir el coturno alcrelo de la ef tentar subdividir el programa en tareas bien definidas, programar las tareas
mera existencia vivencial, el enorme array introducido ha sido ordenado.
Exito total! No era fcil, pero tampoco imposible. Dividiendo el proble por separado y no descuidar la interrelacin entre las mismas. La idea a per
ma en parte menores, y detenindonos cada vez en el flujo de informacin seguir es llegar a la mxima modularidad. Donde sea necesario, divida un
que se debe ir acarreando, hemos reducido el problema a proporciones ma mdulo en mdulos menores. Utilice funciones para aumentar la modulari
nejables. Adems los mdulos individuales podrn utilizarse como parte de dad y claridad del programa.
programas similares. Cuando disee un programa, intente anticiparse a los posibles errores,
Con esto finalizamos los ejemplos del captulo. Revisaremos ahora bre piense en las distintas alternativas que pueden ir mal y programe de acuerdo
vemente los distintos conceptos que han aparecido en el mismo. con ellas.
Utilice trampas para errores, protegiendo problemas potenciales, o, al me
nos, enve un mensaje de alerta al usuario cuando aparezca un problema. Es
mucho mejor dar al usuario una segunda oportunidad para introducir un da
to que dejar que el programa muera ignominiosamente.
Cuando disee una funcin, decida en primer lugar su interaccin con
el programa de llamada. Establezca el flujo de informacin de entrada y de
salida. Cules sern los argumentos? Emplearemos punteros, return o am
bos? Una vez decididos los parmetros de diseo, se podr dedicar a la parte
mecnica de la funcin.
Si observa estos principios generales, sus programas sern ms prcticos
y menos proclives a errores. Conseguir tener una coleccin de funciones pa
ra uso general. Le llevar menos tiempo programar una determinada aplica
cin. Por encima de todo, las ideas apuntadas son una excelente receta para
conseguir una programacin saludable.
No olvide los modos de almacenamiento. Las variables se pueden definir
dentro o fuera de las funciones; en este ltimo caso sern variables externas
(o globales), y podrn ser utilizadas en ms de una funcin. Las variables
definidas dentro de la funcin son locales a dicha funcin y desconocidas pa
ra las dems. Como norma, utilice la variedad automtica en variables loca
les siempre que sea posible. As conseguir que las variables de la funcin
no se vean contaminadas por las actuaciones de las dems.

309
guiente nmero ms alto. Toda la informacin que se obtuvo durante la primera bsqueda
Hasta ahora hemos aprendido queda olvidada, con excepcin del nmero mximo; el segundo nmero ms alto podra
haber estado colocado en la posicin 1 durante un tiempo, y despus sera barajado con
los dems hasta caer en el fondo. As, debemos repetir un gran nmero de comparaciones
Cmo imaginar una funcin: una caja negra con flujo de informacin. realizadas la primera vez, durante el segundo ciclo, el tercero, etc.
Qu es la comprobacin de errores y por qu es conveniente. 2. Sustituya array[busca] > array[tope] por
Un algoritmo de ordenacin. array[busca] < array[tope]
Cmo hacer que una funcin cambie un array. funcin (array) 3. /* imprime un array * /
imprime(array, limite)
Cmo convertir una tira de dgitos en un nmero. int array[], limite;
Los modos de almacenamiento: auto, extern, static y register.
El alcance de cada modo de almacenamiento. int indice;
Qu modo de almacenamiento emplear: casi siempre, auto. for (indice = 0; indice <= limite; indice++)
{
printf("%10d ", array[indice]);
if (indice %5 == 4)
Cuestiones y respuestas printf(\n");
}
Cuestiones printf("\n);
}
1. Indique una situacin que demuestre una cierta ineficiencia en nuestro algoritmo 4. En primer lugar, limite los caracteres aceptables a los dgitos 0 a 7. Ms tarde, multiplique
de ordenacin. por 8 en lugar de por 10 cada vez que se detecte una nueva cifra.
2. Qu cambios habra que introducir en la subrutina de ordenacin para hacer que 5. Main( ) conoce a margarita por defecto; tambin la conocen ptalo( ) y raz( ) debido a
se ordenase de menor a mayor? la declaracin extern. Sin embargo, tallo( ) no la conoce porque estn en ficheros diferen
3. Modifique imprime( ) para que la salida contenga cinco nmeros por lnea. tes.
4. Cmo habra de alterarse stoi( ) para manejar tiras que representasen nmeros El primer lirio es local en main; la referencia a lirio realizada en ptalo( ) es un error, por
octales? que no hay ningn lirio externo en los ficheros.
5. Indicar qu funciones conocen cada una de las variables en el siguiente progra Existe un lirio esttico externo, pero lo conocen nicamente las funciones del segundo fi
ma. Hay algn error? chero.
Por otra parte, raz( ) conoce a la variable rosa externa, pero tallo( ) la sustituye por su
/*fichero 1 */ propia rosa local.
int margarita;
main()
{
int lirio;
Ejercicios
} 1. Algunos usuarios pueden verse perdidos por no saber introducir un carcter EOF.
petalo()
{ a. Modifique getarray( ) y sus funciones de llamada de forma que puedan utili
extern int margarita, lirio; zar un carcter # en su lugar.
} b. Haga otra modificacin que permita utilizar EOF o # alternativamente.
/* fichero 2 */ 2. Escriba un programa que ordene nmeros float.
static int lirio; 3. Prepare un programa que convierta un texto de letra mayscula y minscula en
int rosa; mayscula nicamente.
tallo()
{ 4. Escriba un programa que produzca texto a doble espacio a partir de texto a espa
int rosa; cio sencillo.
}
raiz()
{
extern int margarita;
}

Respuestas
1. Supongamos que estamos ordenando 20 nmeros. El mtodo realiza 19 comparaciones
para encontrar el nmero mayor de todos; a continuacin realiza 18 para encontrar el si

311
11
El
preprocesador C
En este captulo encontrar:

Constantes simblicas: #define


Utilizacin de argumentos con #define
Macros o funciones?
Inclusin de un fichero: #include
Ficheros de encabezamiento: ejemplo
Otros comandos: #undef, #if, #ifdef, #ifndef, #else y #endif
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

313
El preprocesador C Constantes simblicas: #define
El comando #define, como todos los del preprocesador, empieza con el
smbolo #. Puede aparecer en cualquier lugar del fichero fuente, y la defini
CONCEPTOS cin es vlida desde el lugar en que aparece el comando hasta el final del fi
chero. Como se ha podido ver en los captulos anteriores, lo hemos usado
para definir constantes simblicas en nuestros programas, pero su accin no
Comandos del preprocesador
se limita slo a esto, sino que posee nuevas aplicaciones, como veremos a
Constantes simblicas continuacin. En el ejemplo siguiente se ilustran algunas de las propiedades
Macros y macrofunciones y posibilidades del comando #define.
Efectos colaterales de las macros
Inclusin de ficheros / * Ejemplos sencillos de preprocesador */
Compilacin condicional #define DOS 2 /* se pueden usar comentarios */
#define MSJ "Gato escaldado del agua fria \
huye. "
COMANDOS DEL PREPROCESADOR / * una barra-atras continua definicion en otra linea */
#define CUATRO DOS*DOS
#define PX printf("X es %d.\n", x)
#define, #include, #undef, #if, #ifdef, #define FMT "X es %d.\n"
main()
#ifndef, #else, #endif {
int x = DOS;
PX;
x = CUATRO;
printf(FMT, x);
printf("%s\n", MSJ);
printf("DOS: MSJ\n");
}

Cada definicin consta de tres partes. En primer lugar, el comando #define;


seguidamente la palabra que queremos definir, que se suele denominar ma-
El lenguaje C se desarroll con el fin de cubrir las necesidades de los pro cro dentro del mundillo de los ordenadores; la macro debe ser una sola pa
gramadores atareados, y a este tipo de personas les gusta disponer de un pre labra y, por tanto, no debe tener ningn espacio en blanco dentro de ella;
procesador. Cuando escriba un programa en C, no es necesario que lo haga por ltimo, tenemos una serie de caracteres (llamados caracteres de recam
en detalle, sino que parte del trabajo pesado se lo puede dejar a este cola bio) que van a ser representados por la macro. Cuando el preprocesador
borador; l se encargar de leerse su programa antes de que caiga en las se encuentra a lo largo del programa con una macro definida anteriormente,
manos del compilador (de ah el nombre de preprocesador), y, siguiendo las casi siempre la sustituye por los caracteres de recambio (hay una excepcin,
indicaciones que le haya dejado a lo largo del programa fuente, sustituir como veremos dentro de un momento). AI proceso de cambio, por los carac
abreviaturas simblicas por las direcciones que representan, buscar otros fi teres que representa, se le denomina expansin de la macro. Obsrvese
cheros donde puede tener escritos trozos de programa e incluso puede to que el preprocesador hace el mismo caso a los comentarios que el compila
mar decisiones sobre qu partes enviar al compilador y qu partes no. Esta dor de C, es decir, ninguno. La mayora de los sistemas permiten, adems,
breve descripcin no hace justicia a la gran ayuda que representa este inter la continuacin de una definicin en otras lneas por medio del carcter barra-
mediario; quiz se vea ms claro por medio de algunos ejemplos. atrs ( \ ), no incluyndolo en los caracteres de recambio, por supuesto.
Ya hemos visto numerosos ejemplos de #include y #define a lo largo del
Ejecutemos el ejemplo y veamos cmo funciona.
libro; lo que haremos ser recopilar todos los posibles usos que ya hemos
aprendido y aadirles algunos nuevos.
X es 2.
X es 4.
Gato escaldado del agua fria huye.
DOS: MSJ

315
que cuando el programa se ejecute, se almacene en un array cuyo ltimo ele
mento sea el carcter nulo. As

# d e fin e hal 'z ' d e fin e u n a c o n s ta n te d e tip o c a r c te r m ie n tr a s q u e


#d e f i n e hap " Z " d e fin e u n a t ira d e c a ra c te re s: Z \ O

En general, all donde el preprocesador encuentra una macro la sustituye


por los caracteres que representa. Si entre esos caracteres se encuentran otras
Figura 11.1
Elementos de la definicin de una macro macros, tambin son expandidas. El nico caso donde el preprocesador no
cambia la macro es si est colocada dentro de una tira de caracteres entre
comillas. Por tanto,
He aqu lo que ha ocurrido. La sentencia printf("DOS: M S J " );

int x = dos; se convierte en int x = 2;


escribe DOS: MSJ al pie de la letra, en vez de escribir
ya que DOS es sustituido por 2. A continuacin, la sentencia 2: Gato escaldado del agua fria huye.

PX; se convierte en prntf"X es %d.\n",x);


Si lo que desea realmente es que se escriba esta ltima lnea, entonces puede
poner en su programa
en una sustitucin al por mayor. Este es un nuevo paso adelante; hasta ahora
solamente habamos empleado las macros para definir constantes. Podemos printf ( "% d: % s\n " , D O S , MSJ);
ver aqu que una macro puede simbolizar cualquier serie de caracteres, inclu
yendo a toda una expresin C. Observe que, al ser PX una constante, sola
mente escribir el valor de una variable que se llame x. ya que ahora las macros no se hallan protegidas por las comillas.
La siguiente lnea nos depara otra sorpresa. Podra haber supuesto que Cundo deben emplearse constantes simblicas? En principio, deberan
CUATRO se sustituira por 4, pero lo que ocurre realmente es: usarse en lugar de cualquier nmero. Si el nmero es una constante de un
clculo, un nombre simblico dejar ms clara su funcin. Si el nmero se
x= cuatro ; se convierte en x = dos *dos ; que pasa a x = 2*2; emplea para definir el tamao de un array, una macro le permitir aumentar
ms fcilmente el tamao del array (sobre todo, si hay que cambiar de tama
y ah termina el preprocesador su trabajo. La multiplicacin no se realiza du o varios arrays de las mismas dimensiones). Si el nmero es un valor de un
rante el preprocesado, ni siquiera durante la compilacin, sino cada vez que parmetro del sistema, por ejemplo el carcter EOF, un nombre simblico
se ejecute el programa. El preprocesador no sabe multiplicar, se limita a cam har al programa ms porttil; si se va a ejecutar en otro sistema solamente
biar unos caracteres (la macro) por otros (los caracteres de repuesto) de una hay que cambiar la definicin de EOF. La facilidad mnemotcnica de cam
forma bastante literal.
bio de parmetros e independencia del sistema hacen que merezca la pena
Como habr observado, la definicin de una macro puede hacerse en fun
cin de otras macros definidas anteriormente. (No obstante, algunos compi el empleo generoso de constantes simblicas.
ladores no permiten esta definicin anidada.) Qu? Le parece fcil, eh? Vamos a ser un poco ms intrpidos y vea
En la lnea siguiente tenemos mos las hermanas pobres de las funciones: las macros con argumentos.

printf(FMT, x) ; que se convierte en printf("X es %d.\n, x)

al ser FMT sustituida por sus caracteres correspondientes. Si alguna secuen


cia de caracteres se repite varias veces a lo largo del programa, puede resul
tarle cmodo definir una abreviatura y emplearla en lugar de teclear una y
otra vez los mismos caracteres. Las comillas de la definicin hacen que los
caracteres de repuesto sean tratados como una constante de tipo string, o sea,

317
el argumento de una funcin. El resultado del programa, al ejecutarse, pue
de proporcionarle algunas sorpresas; aqu est

z es 16.
z es 4.
CUADRADO(x) es 16.
CUADRADO(x+2) es 14.
1OO/CUADRADO(2) es 100.
CUADRADO(++x) es 30.

Las dos primeras lneas pueden considerarse normales. Observe, sin


embargo, que, a pesar de que x est situada entre comillas en la definicin
de pr, ha sido sustituida por su argumento correspondiente. Absolutamente
todos los argumentos que aparecen en la definicin son sustituidos!
La lnea tercera es ms interesante:
PR(CUADRADO(x));

se convierte en
printf("CUADRADO (x) es %d.\n", CUADRADO (x)) ;

en la primera fase de la expansin de la macro. A continuacin se expande


/* macros con argumentos */ la segunda aparicin de CUADRADO(x), que pasa a ser x*x, mientras que
#define CUADRADO(x) x*x la primera permanece como est, al estar protegida en el programa por un
#define PR(x) printf("x es %d.\n", x) par de comillas. Por tanto, la forma definitiva que llegar al procesador es:
main()
{
int x = 4; printf("CUADRADO(x) es %d.\n", x*x);
int z ;
z = CUADRADO(x) ; y produce como resultado
PR (z) ;
z = CUADRADO(2); CUADRADO(x) es 16.
PR(z) ;
PR(CUADRADO(x));
PR(CUADRADO(x+2));
PR(1OO/CUADRADO(2)); al ejecutarse el programa.
PR(CUADRADO (++x)) ; Vamos a zanjar el asunto de las comillas de una vez por todas. Si la defi
} nicin de la macro incluye un argumento entre comillas, ese argumento ser
sustituido por el preprocesador; pero despus de eso ya no se efectuar nin
guna sustitucin ulterior aunque el argumento fuera otra macro (queda pro
tegido por las comillas de la primera definicin). En nuestro ejemplo, x pasa
All donde aparezca CUADRADO(x) en el programa ser sustituido por a ser CUADRADO (x), y as se queda.
x*x. Lo que diferencia este ejemplo de los anteriores es que podemos em
En la siguiente lnea de salida tenemos un resultado ligeramente descon
plear otros smbolos distintos de x cuando usemos la macro. La x de la de
finicin de la macro se sustituye por el smbolo empleado en la llamada a certante. Recuerde que le hemos asignado a x el valor 4. Esto le podra indu
la macro en el programa. Por tanto, CUADRADO(2) se sustituye por 2*2, cir a pensar que CUADRADO(x + 2) fuera 6*6 = 36, pero el resultado di
de forma que x acta realmente como un argumento. Sin embargo, como ce que es 14, que, adems, resulta que no es un cuadrado demasiado perfec
veremos pronto, un argumento de una macro no acta exactamente como to. La causa de esta broma del ordenador es muy simple; como ya dijimos
antes, el preprocesador no efecta ningn clculo, se limita a cambiar tiras

319
La leccin que tenemos que sacar de aqu es que no debemos escatimar
de caracteres. All donde en nuestro programa aparezca x, el preprocesador los parntesis y colocarlos generosamente, para asegurarnos que las asocia
colocar x + 2; por tanto, ciones y operaciones se hacen en la forma debida.
Pero incluso esta precaucin falla en el ltimo ejemplo de lo que podra
x*x se convierte en x + 2*x + 2 mos llamar expresin masoquista de macros:

La nica multiplicacin es 2 * x. Si x vale 4, entonces el resultado de la cuadrado (++x) se convierte en ++x*++x


expresin es
y la x se incrementa dos veces, una antes y otra despus de la multiplicacin:
4 + 2*4 + 2 = 4 + 8 + 2 = 1 4
+ +x* + + x = 5*6 = 30
Este ejemplo nos revela una diferencia capital entre una llamada a una
funcin y la expansin de una macro. Una llamada a una funcin pasa el (Ya que el orden de las operaciones no est prefijado, algunos compila
valor del argumento a la funcin durante la ejecucin del programa. La ex dores realizarn el producto 6*5, pero el resultado es el mismo.)
pansin de una macro pasa la tira de caracteres del argumento al programa La nica solucin a este problema es abstenerse de emplear + + x como
antes de la compilacin. Son procesos distintos en momentos diferentes. argumento de una macro. Puede, sin embargo, usarse como argumento de
Podra arreglarse nuestra definicin para que CUADRADO(x + 2) fuera una funcin, ya que sera calculado (5) y a la funcin le llega el valor 5.
igual a 36? Faltara ms. Lo nico que necesitamos son ms parntesis:
#define CUADRADO(x) ((x)*(x)) Macros o funciones?
Segn lo anterior, CUADRADO(x + 2) se convierte en (x + 2) * (x + 2) En muchos casos podemos encontrarnos en la incertidumbre de emplear
y obtenemos el resultado apetecido, ya que en los caracteres de repuesto van una macro con argumentos o una funcin. En general, no hay una lnea divi
incluidos los parntesis. soria ntida y sencilla, pero conviene hacer algunas consideraciones.
Espere; no eche las campanas al vuelo hasta ver la siguiente lnea de sali Es preciso estar mucho ms atento cuando se usan macros que cuando
da del programa: se emplean funciones (son ms traicioneras, como acabamos de ver). Algu
nos compiladores limitan la definicin de una macro a una lnea, y quiz fuera
100/CUADRADO(2) se convierte en 100/2 * 2 mejor as. Si quiere un consejo, acte como si su compilador no permitiera
definiciones en varias lneas.
Por causa de las leyes de precedencia, esta expresin se evala de izquier La eleccin entre macros y funciones es otra forma de la lucha entre tiempo
da a derecha, con lo que obtenemos: y espacio. Las macros producen programas de mayor extensin, ya que aa
dimos una sentencia al programa. Si emplea la macro 20 veces, cuando el
(100/2) * 2 = 50 * 2 = 100 preprocesador termine su trabajo, su programa contendr 20 nuevas senten
cias. Si, por el contrario, emplea una funcin 20 veces, su programa sola
Este hacer y deshacer puede eliminarse definiendo CUADRADO(x) de la mente tendr una vez el cuerpo de la funcin; por tanto, ocupar un menor
siguiente forma: espacio. Como contrapartida, el control del programa deber saltar al punto
de comienzo de la funcin y, una vez terminada, retornar al punto de donde
#define CUADRADO(x) (x)*(x) sali; este proceso es ms lento que seguir el curso normal sin saltos.
Una ventaja adicional de las macros es que, debido a que actan sobre
que resulta en caracteres y no sobre los valores que representan, son independientes del ti
po que tengan las variables. As, nuestra CUADRADO(x) funcionar igual
100/(2 * 2) que en su momento se calcula como 100/4 = 25
con variables int o float.
Para obtener lo que esperbamos en ambos ejemplos, tenemos que hacer Generalmente, se suelen emplear macros para las funciones sencillas tal
la siguiente definicin: como:
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define ABS(X) ((X) < O ? -(X) : (X))
#define CUADRADO(x) (x*x) #define ESSIGNO(X) ((X) == '+' ||(X) == '-'? 1 : O)

321
rectorio estndar del sistema. Las comillas le dicen que lo busque en su direc
torio (o en algn otro si se le aade el nombre del fichero) en primer lugar;
(La ltima macro tiene un valor 1 (verdad) si x es un carcter de signo si no lo encuentra, en el directorio estndar.
algebraico.)
Como puntos interesantes podemos sealar: #include <stdio.h> busca en los directorios del sistema.
#include "tierno, h" busca en el directorio en el que esttrabajando
1. No hay espacios en una macro, pero puede haberlos en los caracteres #include "/usr/biff/p.h" buscaen el directorio /usr/biff
de repuesto. El preprocesador supone que la macro termina cuando
encuentra un blanco, de forma que todo lo que se encuentre detrs del
primer espacio ir a parar a los caracteres de repuesto. Si estamos trabajando en un microordenador tpico, las dos formas son
iguales, y el preprocesador buscar en el disco que le indiquemos

#include "stdio.h" busca en el disco de trabajo


#include <stdio.h> busca en el disco de trabajo
#inciude "a:stdio.h" busca en el disco a

Para qu incluir ficheros? Porque tienen la informacin que necesita.


Figura 11.2
Espacio errneo en la definicin de una macro El fichero stdio.h, por ejemplo, contiene generalmente las definiciones de EOF,
getchar( ) y putchar( ). Las dos ltimas, definidas como macros con argu
mentos.
2. Emplee parntesis para rodear a cada argumento y a la definicin co El sufijo .h se suele emplear para ficheros de encabezamiento (header),
mo un todo. Esto asegura que los trminos quedarn agrupados como es decir, con informacin que debe ir al principio del programa. Los ficheros
queremos en una expresin tal como: tenedores = 2 * MAX(invitados cabecera como tambin se llaman consisten, generalmente, en senten
+ 3, ltimo);
cias para el preprocesador. Algunos, como stdio.h, vienen con el sistema,
3. Hemos utilizado letras maysculas para los nombres de las macros con
argumentos. Este convenio no est tan generalizado como el de usar pero pueden crearse los que quiera para darle un toque personal a sus pro
maysculas para las constantes simblicas. Una buena razn para ha gramas.
cerlo as es que se distinguen mejor de las funciones en el listado del
programa y le mantienen alerta contra posibles efectos colaterales. Ficheros de encabezamiento: ejemplo

Suponga que ha desarrollado algunas funciones-macro que le resultan Suponga que le guste emplear valores booleanos. Dicho de otra manera,
cmodas. Tendr que teclearlas en cada nuevo programa que escriba? De a usted le gustara emplear CIERTO y FALSO en lugar de 1 y 0, respectiva
ninguna forma. Para hacer eso existe otro comando: #include. Aunque ya mente. Entonces podra crear un fichero que se llamase, por ejemplo, bool.h,
nos lo hemos encontrado antes, no estar de ms echarlo un vistazo ahora. que contuviera estas definiciones:
/* fichero bool.h */
#define BOOL int
Inclusin de ficheros: #include #define CIERTO 1
#define FALSO 0
Cuando el preprocesador encuentra un comando #include busca el fiche
ro que atiende por el nombre que est situado detrs y lo incluye en el fichero Un programa que empleara estas definiciones podra ser:
actual. El nombre del fichero puede venir de dos formas:
/* contador de espacios en blanco */
#include <stdio.h> nombre del fichero entre parntesis de ngulo #include <stdio.h>
#include "mifichero,h" nombre del fichero entre comillas #include bool.h"
main()
{
Si estamos trabajando en UNIX, los parntesis de ngulo le indican al char caracter;
preprocesador que busque el fichero en un (tambin puede haber varios) di int contador = 0;
BOOL espacio() ;

323
En segundo lugar, hemos incluido tres ficheros. El tercero contendr, se
while ((ch = getchar() ) != EOF) guramente, la definicin de algunas macro-funciones de uso comn.
if (espacio(caracter))
contador++; Por ltimo, hemos definido SI como 1, mientras que en bool.h habamos
printf("He contado %d espacios en blanco. \n", contador) ; definido CIERTO tambin como 1. No hay problema en esto, podemos usar
} SI y CIERTO en el mismo programa; ambos sern reemplazados por 1.
BOOL espacio(c)
char c; Si aadimos la lnea
{
if (c == ' ' || c == '\n' || c == '\t') #define CIERTO 2
return(CIERTO) ;
else
return(FALSO); al fichero, habra conflicto entre ambas definiciones, y el preprocesador con
} sidera la ltima nicamente. Algunos, incluso, le avisarn de que CIERTO
ha sido redefinido.
El comando #include no est restringido nicamente a ficheros cabecera.
Observaciones al programa: Si tiene un fichero llamado integral.c, que contiene una funcin til, puede
1. Si las dos funciones de este programa (main y espacio) se compilaran por emplear
separado, tendra que emplear el comando #include bool.h en cada una
#include "integral.h
de ellas.
2. La expresin if( espacio(carcter)) es la misma que if( espacio(carc-
ter) == VERDAD), ya que espacio(carcter) toma los valores CIERTO para introducirlo en su programa y compilarlos juntos.
o FALSO. Los comandos #include y #define son, con mucho, los ms empleados
3. Realmente, no hemos creado un nuevo tipo BOOL, ya que BOOL signifi del preprocesador C. Comentaremos el resto de los comandos un poco ms
ca lo mismo que int. Al definir la funcin con el tipo BOOL, lo nico de pasada.
que indicamos es que realiza operaciones lgicas y no aritmticas.
4. El empleo de una funcin para la realizacin de las comparaciones lgi
cas puede contribuir a la claridad de un programa. Tambin puede acor Otros comandos: #undef, #if, #ifdef, #ifndef,
tarlo si la comparacin se realiza en varios lugares.
5. Podramos haber empleado una macro en lugar de una funcin para defi #else y #endif
nir espacio( ).
Estos comandos se emplean tpicamente cuando se construyen programas
Muchos programadores suelen desarrollarse sus propios ficheros cabece de gran tamao por medio de bloques bien diferenciados. Le permiten al pro
ra para sus programas. Algunos podran estar dirigidos a un determinado gramador dejar sin efecto definiciones anteriores y producir ficheros que pue
trabajo, y otros podran emplearse con casi cualquier programa. Como los den compilarse de distintas formas.
ficheros incluidos pueden contener otros comandos #include, pueden crear El comando #undef deja sin efecto la ltima definicin de una macro.
se ficheros cabecera de una forma estructurada.
Sea el ejemplo siguiente: #define GRANDE 3
#define ENORME 5
#undef GRANDE /* ahora GRANDE no esta definido */
/* encabezamiento (header) mifichero.h */
#define ENORME 10 /* ENORME se redefine como 10 */
#include <stdio.h> #undef ENORME /* ENORME vuelve a valer 5 */
#include "bool.h" #undef ENORME / * ENORME esta ahora indefinido */

#include "func.h"
#d efin e S I 1
#defin e N O 0 Evidentemente no esperamos que un fichero como el anterior sea de mu
cha utilidad; pero suponga que dispone de un fichero #include de gran tama
o en su sistema y que le interesa usarlo, aunque tenga que cambiar tempo
En primer lugar, le recordamos que el preprocesador C reconoce los co ralmente algunas de sus definiciones en algn punto de su programa. En
mentarios entre /* y */, de forma que podremos incluir comentarios en los lugar de reajustar todo su programa, puede incluirlo en su programa direc
ficheros. tamente, y rodear la zona conflictiva con los #define y #undef que sean
necesarios.

325
Otro ejemplo podra ser el siguiente: suponga que est trabajando en un Hasta ahora hemos aprendido
conjunto de programas en colaboracin con otros programadores. Quiere de
finir una macro, pero no est seguro si su definicin ser compatible con otras Cmo definir constantes simblicas: #define DEDOS 10
realizadas por sus compaeros. Para evitar problemas de la forma ms sen Cmo incluir otros ficheros: #include etrusco.h
cilla, deje sin efecto sus definiciones en cuanto termine su zona de utilidad, Cmo definir macro-funciones: #define NEG(X) (-(X))
y, si estaban definidas anteriormente, volvern a recuperar su valor. Cundo usar constantes simblicas: a menudo
Los comandos restantes que mencionamos le permiten realizar una com Cundo usar macro-funciones: a veces
pilacin bajo determinadas condiciones. Aqu va un ejemplo: Los peligros de las macro-funciones: efectos colaterales

#ifdef VAQUERO
#include "caballo.h" /* se realiza si VAQUERO esta definido */
Cuestiones y respuestas
#define ESTABLOS 5
Cuestiones
#else
1. Sean los siguientes grupos de una o ms macros seguidas por una lnea de progra
#include "vaca.h" /* se realiza si no esta definido */ ma que las emplea. Qu resulta de la expansin de la macro? Ser vlida para
#define ESTABLOS 15
el compilador?
#endif a. #define UPD 12 /* unidades por docena */
cantidad = UPD * docenas;
b. #define BASE 4
El comando #ifdef indica que si el identificador que le sigue (VAQUE #define ALTURA BASE+BASE
superf = BASE * ALTURA
RO) ha sido definido por el preprocesador, entonces se ejecutan todos los
comandos hasta el siguiente #else o #endif. Si encuentra primero un #else, C. #define SEIS = 6;
nex = SEIS;
entonces se ejecutan los que se encuentren desde #else hasta #endif si el iden
tificador no est definido. d. #define NUEVO(X) X + 5
y = NUEVO(y);
La estructura es similar a la if-else del C. La principal diferencia reside valor = NUEVO(valor) * tasa;
en que el preprocesador no reconoce el mtodo { } de limitar un bloque; por est = NUEVO(valor) / NUEVO(y);
tanto, emplea #else (si lo hay) y #endif (que debe estar) para delimitar los nilp = tasa * NUEVO ((-valor) ;
bloques de comandos.
Estas estructuras condicionales pueden anidarse en ms de un nivel. 2. Corrija la definicin de 1.d para hacerla ms correcta.
Los comandos #ifndef e #if pueden emplearse junto con #else y #endif 3. Defina una macro-funcin que devuelva el menor de dos valores.
de la misma forma. #ifndef pregunta si el identificador no est definido; es 4. Defina una macro que sustituya a la funcin espacios(c) en el programa que cuenta
caracteres blancos.
el complementario de #ifndef. El comando #if se parece ms al if de C; es
5. Defina una macro-funcin que imprima la representacin y los valores de dos ex
seguido por una expresin constante, que se considera cierta si no es 0:
presiones enteras.

#if SISTEMA=="IBM"
#include "ibm.h" Respuestas
#endif
1. a. cantidad = 12 docenas; vlida.
b. superf = 10 * 10 + 10; vlida, pero si se quera calcular 4 * (4 + 4), debera haberse
Un uso de esta compilacin condicional es hacer que un programa sea definido as: #define ALTURA (BASE + BASE)
ms porttil. Cambiando unas pocas definiciones claves al principio de un c. lados ==6;; no vlida; aparentemente el programador olvid que estaba hablando
fichero puede conseguir la inclusin de diferentes ficheros y la definicin de con el preprocesador, no con el compilador C.
los parmetros correspondientes a distintos sistemas. d. y = y + 5; vlido
valor = valor + 5 * tasa; vlido, pero no lo que se quera, probablemente,
Estos pocos ejemplos ilustran la extraordinaria capacidad del C para el estim = valor + 5/y + 5; igual que antes,
control de los programas. def = tasa * -valor + 5; igual que antes.
2. #define NUEVO((X) + 5)
3. #define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
4. #define ESPACIO (C) ((C) == || (C) == \ n || (C) == \t)

327
#define IMPR2(X,Y) printf(X es %d e Y es %d. n, X,Y)
Como X e Y no estn expuestas a otras operaciones (como multiplicaciones) en esta ma-
cro, podemos ser ms avaros en parntesis.

Ejercicio
1. Empiece a crearse un fichero cabecera de definiciones para el
le gustara tener.
12
Arrays
y punteros
En este captulo encontrar:

Arrays
Inicializacin y clase de almacenamiento
Punteros a arrays
Funciones, arrays y punteros
Suplantacin de arrays por punteros
Operaciones con punteros
Arrays multidimensionales
Punteros y arrays multidimensionales
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

331
valores por defecto cuando no se definen explcitamente. Sean, por ejemplo,
Arrays y punteros las siguientes declaraciones:

/* algunas declaraciones de arrays * /


int temp[365]; /* array external de 365 enteros */
main()
CONCEPTOS
float lluvia[365] ; /* array automtico de 365 float */
static char codigo[12]; /* array static de 12 char */
Arrays extern temp[]; /* array external; tamano indicado */
Arrays multidimensionales
Inicializacin de arrays Recuerde tambin que los corchetes ([ ]) identifican a temp y a los dems
Punteros y operaciones con punteros como arrays, indicando el nmero encerrado entre ellos cuntos elementos
La relacin array-puntero tiene. Para indicar un elemento de un array, usaremos su nmero de orden
(su subndice, en notacin algebraica) denominado tambin ndice. El pri
OPERADORES mer elemento tiene como ndice el 0. Por tanto, temp[0] es el primer elemen
to de temp, y temp[364] es el ltimo.
& *(unario) Como lo anterior ya est manido, veamos algo nuevo.

Inicializacin y clase de almacenamiento


Bastante a menudo se emplean arrays para guardar una serie de datos uti
lizados por un programa. Por ejemplo, podemos guardar los das que tiene
cada uno de los meses en un array de 12 elementos. Para este tipo de aplica
ciones, sera conveniente disponer de una forma sencilla y cmoda de colo
car los valores en el array al principio del programa. Ya habr supuesto que
el C la tiene, pero slo para aquellos arrays que permanezcan de forma con
tinua en memoria (es decir; externos o estticos). Veamos cmo se hace.
Sabemos que para inicializar una variable el mtodo es declararla con un
En C, los arrays y los punteros mantienen relaciones ntimas (en el buen valor:
sentido), de modo que los trataremos juntos. De todas formas, antes de in
int flix = 1 ;
troducirnos en tan escabroso asunto, repasaremos y aumentaremos nuestros float flax = PI*2;
conocimientos sobre los arrays.
donde, es de suponer, hemos definido anteriormente una macro llamada PI.
Podemos hacer algo parecido con los arrays? La respuesta es nuestra favo
rita, s y no:
Los arrays clase EXTERN o STATIC pueden inicializarse.
De momento, lo nico que conocemos de un array es que est compuesto Los arrays clase AUTOMATIC o REGISTER, no.
por una serie de elementos del mismo tipo. En la declaracin de las variables Antes de tratar de inicializar un array, veamos lo que tienen si no coloca
le decimos al compilador cundo queremos un array. Por su parte, el compi mos nada en ellos
lador necesita conocer de un array lo mismo que de cualquier otra variable
(a las otras variables las denominaremos de ahora en adelante escala /* a ver que sale */
res): su tipo y su clase de almacenamiento. Pero, adems, el compilador de main()
{
be conocer el nmero de elementos que forman el array. En cuanto al tipo int bas[2]; /* array automatico */
y clase de almacenamiento, no hay ningn problema; los arrays pueden tener static int ura[2]; /* array estatico */
los mismos que cualquier variable escalar; es ms, se les aplican los mismos
printf("%d %d\n", bas[1], ura[1]);
}

333
anterior, pero con una lista demasiado corta (ms exactamente, demasiados
La salida es corta):

525 0 /* dias del mes */


int dias[12] = {31,28,31,30,31,30,31,31,30,31};
main()
Este ejemplo nos muestra la norma siguiente: si no se les da ningn valor, {
int indice;
los arrays extern y static son inicializados a 0, mientras que los automatic extern int dias[]; /* declaracion opcional */
y register contendrn lo que hubiera en la parte de memoria donde les haya
tocado caer. for (indice = 0; indice < 12; indice++)
Bueno, ya sabemos cmo inicializar un array de clase extern o static a printf("El mes %d tiene %d dias. \n", indice + 1,
dias [indice]);
0: simplemente, no haciendo nada. Normalmente esto no nos servir de mu }
cha ayuda, si lo que queremos es colocar otros valores, quiz el nmero de
das de cada mes, quiz otra cosa. Para casos como ste, podemos hacer
lo siguiente: Esta vez la salida sera:

/* dias del mes */


int dias[12] = {31,28, 31,30, 31,30, 31,31,30, 31,30, 31 };
main()
{
int indice;
extern int dias[]; /* declaracion opcional */

for (indice = 0; indice < 12; indice++)


printf("El mes %d tiene %d dias. \n", indice + 1,
dias[indice]);
}

La salida ser: Como puede observar, el compilador no tiene problemas. Cuando la lista
es corta, sigue con su mana de inicializar a 0 los restantes elementos.
Por el contrario, el compilador es inexorable si la lista es demasiado lar
ga. Este tipo de altruismo est considerado como un ERROR. De todas for
mas, no es necesario exponerse a quedar en ridculo por el simple hecho de
que el compilador sepa contar mejor que usted. Demuestre su superioridad
dejando al compilador que encuentre el tamao del array:

/* dias del mes */


int dias[12] = {31,28,31,30, 31,30,31,31,30,31};
main()
{
int indice;
extern int dias[]; /* declaracion opcional */

for (indice = 0; indice < sizeof dias/(sizeof(int)); indice++)


Realmente no es un programa muy complicado, slo se equivoca una vez
printf("El mes %d tiene %d dias. \n", indice + 1,
cada cuatro aos. Al definir das[ ] fuera de la funcin, su clase de almace dias[indice]);
namiento es externa. Para inicializarlo colocamos la lista de valores entre lla }
ves y separados por comas.
El nmero de elementos de la lista debera coincidir con el tamao del Hay dos puntos interesantes en este programa.
array. Qu pasa si nos equivocamos al contar? Probemos con el ejemplo Primero, si emplea corchetes vacos cuando inicialice un array, el compi
lador contar el nmero de trminos de la lista, y se ser el tamao del array.

335
punteros nos permiten aproximarnos a la forma de trabajo de la mquina.
Segundo, observe detenidamente la expresin de control del bucle for. Co Esto hace que los programas que utilizan punteros puedan ser ms eficien
mo no nos fiamos (adems, con razn) de nuestra habilidad con los nme tes. En particular, los punteros hacen ms rpido el trabajo con arrays. Real
ros, dejamos al ordenador que cuente por nosotros y calcule el tamao del mente, nuestra notacin como arrays es un mtodo disfrazado de empleo de
array. El operador sizeof nos da el tamao, en bytes, del objeto o tipo que punteros.
coloquemos a continuacin (ya mencionamos algo parecido en el captulo 3). Un ejemplo de esta torva maniobra es que el nombre de un array es tam
En nuestro sistema, cada elemento de tipo int ocupa 2 bytes, de forma que bin un puntero dirigido al primer elemento del array. En otras palabras, si
podemos dividir el nmero total de bytes por 2 para saber el nmero de ele antifaz[ ] es un array, entonces la igualdad
mentos; pero otros sistemas pueden tener enteros de distinta longitud; por
tanto, por mor de la generalidad, dividimos por sizeof(int). antifaz == &antifaz[0]
Aqu est el resultado de nuestro programa:
es cierta, y ambos trminos representan la direccin de memoria del primer
elemento. (Recuerde que & es el operador direccin.) Ambos trminos son
punteros constantes, ya que no pueden cambiar su valor a lo largo del pro
grama. Sin embargo, pueden ser asignados a una variable puntero, y pode
mos cambiar el valor de una variable, como demuestra el siguiente ejemplo.
Observe lo que ocurre cuando sumamos un nmero a una variable puntero.

/* adicion de punteros */
main()
{
int fechas[4] *pent, indice;
Cielos! Slo dimos 10 valores. En cualquier caso, nuestro mtodo de de float facturas, *pflot;
jar al programa el trabajo de encontrar el tamao del array nos ha evitado
una pequea catstrofe: escribir datos fuera del mismo. pent = fechas; / * asigna la direccion del array al puntero */
pflot = facturas;
Existe otro mtodo ms corto de inicializacin de arrays, pero como slo for (indice = 0; indice < 4; indice++)
funciona con tiras de caracteres, lo dejaremos para el siguiente captulo. printf("punteros + %d: %10u %10u\n",
Por ltimo, debemos indicar que se puede asignar un valor a un elemento indice, pent+indice, pflot+indice);
del array, independientemente de su clase de almacenamiento. Por ejemplo, }
el siguiente trozo de programa asigna nmeros pares a un array de clase auto-
matic:
La salida es:
/* asignacion de arrays */
main()
{
int contador, pares[50];

for (contador = 0; contador < 50; contador++)


pares[contador] = 2 * contador;
En la primera lnea escribe las direcciones de comienzo de los dos arrays;
} en la siguiente nos da el resultado de sumar 1 a las direcciones, y as sucesiva
mente. Qu?

56014 + 1 = 56016?
Punteros a arrays 56026 + 1 = 56030?
Los punteros, como recordar del captulo 9, nos proporcionan un mto Nos toma el pelo o es muy listo? Nuestro sistema direcciona cada byte
do simblico para usar direcciones de memoria. Como las instrucciones del individualmente, pero el tipo int usa dos bytes y el float cuatro. Lo que ocu
hardware de los ordenadores emplean abundantemente estas direcciones, los rre es que cuando le decimos suma 1, el C suma no un byte, sino una uni-

337
La relacin entre arrays y punteros permite que podamos usar ambas no
dad de almacenamiento del tipo al que pertenece el puntero. Para arrays esto taciones indistintamente al escribir un programa. Un ejemplo tpico es cuan
significa que la direccin cambia al siguiente elemento, no al siguiente byte. do tenemos una funcin, uno de cuyos argumentos es un array.
Esta es una de las razones por las que tenemos que declarar a qu tipo de
elementos se va a referir el puntero, ya que la direccin no es suficiente; el
computador necesita saber cuntos bytes se usan para guardar ese objeto. Funciones, arrays y punteros
(Incluso para variables escalares es necesario, si no la operacin *pt nos de
volvera un valor errneo.) Podemos encontrarnos arrays en una funcin en dos sitios. En primer lu
gar pueden estar declarados dentro del cuerpo de la funcin; el segundo caso
es que aparezcan como argumentos. Todo lo que llevamos dicho hasta ahora
se corresponde con arrays del primer tipo; ahora entraremos en detalle con
los segundos.
En el captulo 10 dejamos pendiente esta cuestin. Ahora que sabemos
ms acerca de los punteros, podemos continuar. Empezaremos por observar
el esqueleto de un programa (atencin a las declaraciones!).

/* array como argumento */


main()
{ int edad[50]; /* un array de 50 elementos */
convierte(edad);
... }
convierte(primaveras)
int primaveras[]; /* que tamao tiene este array? */
{ ...
}
Figura 12.1
Un array y suma con punteros El array edad tiene, obviamente, 50 elementos. Pero, y el array prima

Como resultado de la habilidad del C, las siguientes igualdades son veras? Sorpresa!, el array primaveras no existe! La declaracin
ciertas: int primaveras[];

fechas + 2 == &fechas[2] /* misma direccion */ no crea un array, sino un puntero. Veamos por qu se hace as.
*(fechas + 2) == fechas[2] /* mismo valor */

Esta es nuestra llamada a la funcin:


Estas relaciones resumen la estrecha conexin entre arrays y punteros. Vie convierte(edad);
nen a decir que podemos usar un puntero para identificar un elemento de
un array y tomar su valor. En esencia, son dos formas de nombrar la misma El argumento es edad; como recordar, edad es un puntero al primer ele
cosa; realmente, el compilador convierte la notacin de array a punteros, as
que esta ltima est ms prxima a la realidad que la primera. mento de los 50 del array, por tanto, la llamada de la funcin pasa a la fun
Incidentalmente, no es lo mismo *(fechas + 2) que *fechas + 2. El ope cin convierte( ) un puntero (una direccin, en otras palabras). Si el argu
rador valor (*) tiene mayor jerarqua que la suma ( + ) , as que la ltima ex mento de convierte( ) es un puntero, tambin hubiramos podido escribir.
presin significa (*fechas) + 2: convierte(primaveras)

int *primaveras;
*(fechas + 2 ) /* valor del tercer elemento de fechas */
*fechas + 2 /* suma 2 al valor del primer elemento */
{
}

339
if (n > 0)
Realmente, las declaraciones {
for (indice = 0, suma = 0; indice < n; indice++)
suma += array[indice];
int prim averas[]; return( (int) (suma/n) ); / * devuelve un entero */
int *prim averas; }
else
}
son sinnimas. Ambas definen primavera como un puntero a un entero. La printf("No hay array.\n");
principal diferencia es que la segunda nos recuerda que primaveras[ ] apun return(0);
}
ta a un array. }
Qu ocurre con el array edad? Recuerde que, cuando empleamos un pun
tero como argumento, la funcin trabaja con la variable apuntada (de la fun
cin donde se produce la llamada); por tanto, todas las operaciones que afecten No es demasiado difcil convertir este programa para que use punteros.
a la variable primaveras realmente estarn trabajando sobre el array edad Declare pa como puntero a un entero (int). A continuacin cambie el ele
de la funcin main( ). mento del array (array[indice]) por el valor correspondiente: *(pa + indice).
Veamos cmo funciona esto. Primero, la llamada a la funcin inicializa
primaveras de forma que apunte a edad[0]. Suponga ahora que en alguna
parte de convierte( ) tenemos la expresin primaveras[3]. Como vimos an /* usa punteros para calcular la media de un array
de n enteros * /
tes, eso es lo mismo que decir *(primaveras + 3). Pero si primaveras apunta
a edad[0], entonces primaveras + 3 apunta a edad[3], O sea, que int media(pa,n)
*(primaveras + 3) sea equivalente a edad[3]. Coloque ahora esta cadena de int *pa, n;
relaciones en un orden lgico, y la conclusin que obtenemos es que cambiar {
int indice;
primaveras[3] es lo mismo que cambiar *(primaveras + 3), que, a su vez, es long suma; /* si hay muchos int puede salir un long */
lo mismo que cambiar edad[3]. Y esto es lo que avisamos: si manipulamos
las primaveras se afecta la edad (real como la vida misma). if (n > 0)
{
En resumen, cuando emplee el nombre de un array como argumento de for (indice = 0, suma = 0; indice ( n; indice++)
una funcin, le pasa un puntero. La funcin usa este puntero para realizar suma += *(pa + indice);
cambios en el array original del programa del cual parti la llamada. return((int) (suma/n) ); /* devuelve un entero */
Veamos un ejemplo.
}
else
{
printf("No hay array. \n") ;
return(0);
Suplantacin de arrays por punteros }
}

Escribiremos una funcin que emplee arrays. A continuacin la reforma


remos para usar punteros.
Fcil, verdad?; pero, no tendremos que cambiar la llamada?; despus
Sea la siguiente funcin que halla la media de una array de enteros. La
de todo, nmeros en media(nmeros,n elem) era el nombre de un array. No,
entrada es el nombre del array y el nmero de sus elementos. La salida es
la media, que se devuelve a travs de un return. no es necesario ningn cambio, ya que el nombre de un array es un puntero.
La sentencia en donde se llama a la funcin podra ser algo como: Como se discuti en la seccin anterior, las declaraciones

int pa[];
printf("La media de estos valores es %d. \n,
media(numeros, nelem));
e
/* calcula la media de un array de n enteros */
int media(array, n) int *pa;
int array[], n;
{
int indice; son idnticas; en efecto, ambas dicen que pa es un puntero. Podramos usar
long suma; /* si hay muchos int puede salir un long */ la primera declaracin y emplear *(pa + indice) en el programa.

341
Aqu est la salida:
Cmo funciona la versin con punteros desde un punto de vista concep
tual? Un puntero seala al primer elemento, y el valor guardado all es aa
dido a suma; despus, el elemento sealado es el siguiente (sumamos 1 al pun
tero), y el valor almacenado all es aadido a suma, y as, sucesivamente, hasta
el ltimo. Si lo considera con detenimiento, esto es exactamente lo que hace
la version con arrays, donde el subndice acta como el dedo acusador que
va sealando a cada elemento.
Bueno, ya sabemos cmo hacer el mismo trabajo con arrays y punteros, Este ejemplo muestra las cinco operaciones bsicas que podemos realizar
y ahora podemos preguntarnos: cul usar? En primer lugar, a pesar de que con variables punteros.
los punteros y los arrays estn muy relacionados, an tienen diferencias. Los
punteros son ms generales y ms potentes, pero muchos usuarios (por lo 1. ASIGNACION: Podemos asignar una direccin a un puntero. De una
menos al principio) encuentran ms familiares y sencillos los arrays. Ade forma normal se emplear con el nombre de un array o con el opera
ms, no hay una forma sencilla, trabajando con punteros, de declarar el ta dor direccin (&). En nuestro ejemplo, asignamos a punt1 la direccin
mao de un array. La situacin ms tpica en donde se pueden emplear am de inicio del array urn; esta direccin ha cado en la clula de memoria
bos indistintamente es la que hemos visto: una funcin que opera sobre un nmero 18. (En nuestro sistema, las variables static se colocan en las
array definido en algn otro lugar. Le sugerimos que use la opcin que ms direcciones bajas.) En la variable punt2 colocamos la direccin del ter
le guste. La principal ventaja de emplear punteros en casos como ste es la cer y ltimo elemento, urn[2].
de familiarizarse con ellos, de forma que le sea ms fcil usarlos cuando ten
ga que usarlos.

Operaciones con punteros


Qu es lo que podemos hacer con los punteros? El lenguaje C le ofrece
cinco operaciones bsicas, y el programa siguiente le muestra estas posibili
dades. Para mostrar los resultados de cada operacin imprimiremos el valor
del puntero (la direccin donde apunta), el valor almacenado en dicha direc
cin y la direccin del puntero (donde se guarda su valor).

/* operaciones con punteros */


#define PR(X) printf("X = %u, *X =%d, &X = %u\n, X, *X, &X)
/* imprime el valor del puntero (una direccion), el valor */
/* almacenado en esa direccion, y la propia direccion del */
/* puntero. */
main()
{
static int urn[] = {100,200,300};
int *punt1, *punt2;
punt1 = urn; /* asigna una direccion al puntero */
punt2 = &urn[2]; /* idem */
PR(punt1); /* vease definicion macro arriba */
punt1++; /* incrementa el puntero */
PR(punt1) ;
PR(punt2);
++punt2; /*s e sale del limite del array */ 2. VALOR GUARDADO EN UNA DIRECCION: El operador * nos da
PR(punt2);
printf("punt2 - punt1 = %u\n", punt2 - punt1); el valor almacenado en la posicin de memoria apuntada. As, *punt1
vale, inicialmente, 100, que es el valor guardado en la posicin 18.

343
es un expresin muy atractiva). Sin embargo, s pueden utilizarse en
3. DIRECCION DE UN PUNTERO: Como todas las variables, los pun una suma normal.
teros tienen una direccin y un valor. El operador & nos dice dnde
est almacenado el puntero. En nuestro ejemplo, punt1 est almace VALIDO INVALIDO
nado en la direccin 55990. El valor contenido en esa clula de memo
ria es 18, la direccin de urn. punt1 + + ; urn + + ;
4. INCREMENTO DE UN PUNTERO: Podemos hacer esta tarea como x++; 3++;
una suma normal o por medio del operador de incremento. Si incre punt2 = punt1 +2; punt2 = urn + + ;
mentamos un puntero, ste se mover al siguiente elemento del array. punt2 = urn + 1; x = y + 3+ +;
Por tanto, punt + + incrementa el valor numrico de puntl1 en 2 (un
int ocupa 2 bytes) y hace que punt1 seale a urn[1] (vase la figura 5. RESTA: Dos punteros pueden restarse. Normalmente se utiliza con
siguiente). Ahora punt1 tiene el valor 20 (la siguiente direccin del array) dos punteros que sealan dentro de un mismo array, para saber cun
y punt1 tiene el valor 200, el de urn[1]. Obsrvese que la direccion de tos elementos los separan. Observe que el resultado no es en bytes, si
punt1 sigue siendo 55990. Despus de todo, una variable no se mueve no en las mismas unidades que el tamao del tipo del array.
de su sitio slo por el hecho de cambiar su valor.
Por supuesto, tambin se puede decrementar un puntero.
Hay que hacer notar algunos puntos peligrosos. El ordenador no Estas operaciones nos abren muchas puertas. Los programadores de C
le sigue la pista a un puntero; por tanto, no sabe si est apuntando suelen trabajar con arrays de punteros, punteros a funciones, arrays de pun
dentro del array. La operacin + + punt1 hizo que punt2 se moviera
teros a punteros, arrays de punteros a funciones y as sucesivamente. Por nues
otros dos bytes, y ahora apunta a lo que haya detrs del array.
tra parte nos limitaremos a los usos bsicos que ya hemos presentado. Su
primer uso es pasar informacin a y desde una funcin. Ya hemos visto que
una funcin slo puede cambiar el valor de una variable en el programa que
la llama si ste le pasa, como argumento, un puntero a la variable. En segun
do lugar se emplean en funciones diseadas para manipular arrays.

Arrays multidimensionales
Marisol Buenda de Verano es una meteorloga que tiene el futuro bas
tante nublado. La han puesto a analizar las lluvias de los ltimos cinco aos,
correspondientes a cada mes, por medio de un ordenador. Una de las prime
ras decisiones que tiene que tomar es la forma de representar los datos. Una
alternativa es usar 60 variables, una para cada dato. (Mencionamos esta po
sibilidad una vez en los captulos anteriores, y sigue siendo tan estpida co
mo entonces.) El empleo de un array con 60 elementos sera un avance, pero
quedara mejor si pudieran mantener separados los datos de cada ao. Se
podran usar 5 arrays de 12 elementos, pero es una solucin liosa, y podra
llegar a ser tan estpida como la primera, si Marisol decide estudiar los lti
F igu ra 12.2
mos 50 aos en lugar de 5. Necesitamos alguna otra posibilidad ms elegante.
Incremento de un puntero tipo int Una buena solucin es usar un array de arrays. El array principal tendra
5 elementos, que seran, a su vez, arrays de 12 trminos. He aqu la forma
Otro punto que no debe olvidarse es que slo se pueden incremen como se hace:
tar variables, y no constantes, as que un puntero constante no puede
cambiar su valor (esto puede parecer una perogrullada, pero + + urn static float lluvia[5] [12] ;

345
Tambin podemos visualizar el array lluvia como una matriz de dos di- main()
mensiones con 5 filas y 12 columnas. {
static float lluvia[ANS][DOCE] = {
{10.2, 8.1, 6.8, 4.2, 2.1, 1.8, 0.2, 0.3, 1.1, 2.3, 6.1, 7.4},
{9.2, 9.8, 4.4, 3.3, 2.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 5.2},
{6.6, 5.5, 3.8, 2.8, 1.6, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 4.2},
{4.3, 4.3, 4.3, 3.0, 2.0, 1.0, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6},
{8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.2}
};
/* inicializacion de datos de lluvia en el periodo 1970 - 74 */
int anno, mes;
float subtot, total;

printf(" ANNO LLUVIA (cm.)\n\n");


for (anno = 0, total = 0; anno < ANS; anno++)
{ /* en cada anno suma la lluvia de todos los meses */
for (mes = 0, subtot = 0; mes < DOCE; mes++)
subtot += lluvia[anno][mes];
printf("%5d %12.lf\n", 1970 + anno, subtot);
total += subtot; /* calcula lluvia total del periodo */
}
printf("\nEl promedio anual ha sido %.1f cm.\n\n", total/ANS);
printf("PROMEDIOS MENSUALES:\n\n");
printf("Ene Feb Mar Abr May Jun Jul Ago Sep Oct ");
printf(" Nov Dic\n");

for (mes = 0; mes < DOCE; mes++)


{ /* suma la lluvia de todos los annos en cada mes */
for (anno = 0, subtot = 0; anno < ANS; anno++)
subtot("%4.1f ", subtot/ANS);
}
printf("\n");
}
Figura 12.3
Array bidimensional Figura 12.4
Programa meteorolgico

Al variar el segundo subndice, nos moveremos a lo largo de una fila, y


al variar el primero, el movimiento ser vertical por una columna. En nues
tro caso particular, el segundo subndice representa los meses, y el primero,
los aos.
Vamos a emplear este array bidimensional en un programa meteorolgi
co. Nuestro programa imprimir la lluvia total cada cada ao, la media anual
y la media correspondiente a cada mes del ao. (A nosotros tambin nos gus
tara un programa que nos dijera el tiempo que iba a hacer maana, pero
tenemos problema con la funcin adivina( )). Para hallar la lluvia cada ca
da ao tenemos que sumar los datos de cada fila; para la media de lluvia de
un determinado mes, sumaremos, en primer lugar, los datos de su columna
correspondiente. La ordenacin en forma de matriz hace que estos clculos
sean fciles de visualizar y ejecutar. La figura 12.4 presenta el programa.

/* calcula totales anuales, promedio anual y promedio mensual */ Los principales puntos de inters son la inicializacin y el esquema de clcu
/* de datos pluviometricos en un periodo determinado */ lo. La inicializacin es lo ms complejo de ambos, as que veremos primero
#define DOCE 12 /* numero de meses del anno */
el esquema de clculo.
#define ANS 5 /* numero de annos a tratar */ Para hallar el total correspondiente a un ao, mantendremos fijo el ao
y correremos los meses. Este es el bucle for ms interno de la primera parte

347
Todo lo que hemos dicho para los arrays bidimensionales puede exten
del programa. A continuacin repetimos el proceso para el siguiente ao. Este derse a los de mayor nmero de dimensiones. Podemos declarar un array de
otro se corresponde con el for externo de la primera parte del programa. Las tres dimensiones de la siguiente forma:
estructuras anidadas de bucles como sta se presentan a menudo cuando se
manejan matrices (arrays bidimensionales). Un bucle controla el primer su int solido[10][20][30] ;
bndice, y el otro, el segundo.
Puede visualizar este array como 10 matrices (de 20 x 30) apiladas unas
Inicializacin de un array bidimensional sobre otras. Tambin puede considerarlo como un array de arrays de arrays,
o sea, como un array de 10 elementos, cada uno de los cuales es un array
Para la inicializacin hemos incluido cinco series de nmeros entre lla de 20 elementos, que, a su vez, son arrays de 30 enteros. La ventaja de este
ves, que, a su vez, estn encerradas entre otro par de llaves ms externas. mtodo es que se puede extender fcilmente a un mayor nmero de dimen
Los datos dentro del primer par de llaves se asignan a la primera fila del array, siones (a menos que usted sea capaz de visualizar objetos con cuatro o ms
los de segundo par, a la segunda fila, y as sucesivamente. Las reglas que con dimensiones!). Volveremos a insistir en las bidimensionales.
trolan la situacin cuando el nmero de datos no coincide con el tamao del
array son las mismas que se discutieron anteriormente. Si, por ejemplo, den
tro del primer par de llaves solamente hay 10 nmeros, slo se inicializan los
10 primeros elementos de la primera fila; los otros 2 quedan inicializados a
su valor por defecto, es decir, a 0. Si, por el contrario, hay demasiados, pro Punteros y arrays multidimensionales
ducir un error al compilarse el programa; no servirn para inicializar la se
gunda fila. Cmo se relacionan los punteros con los arrays multidimensionales? Ve
Podramos haber puesto solamente el juego de llaves externo sin colocar remos algunos ejemplos que nos conducirn a la respuesta.
los pares internos. Como el nmero de datos es el correcto, el resultado hu Sean las declaraciones
biera sido el mismo. Si, por el contrario, hubiramos colocado menos datos,
el array se hubiera ido llenando sin considerar la distribucin en filas, hasta int groucho[4][2] ; /* arr ay de int de 4 fi las y 2 col umnas */
que se terminaran, inicializando el resto con ceros. Vase la figura 12.5. int *pint ; /* puntero a enter o */

si hacemos
pint = groucho;

dnde apunta pint? Solucin: apunta al elemento de la primera fila y pri


Static int cuad[2][3] = {
mera columna;
{5.6}
{7,8} groucho == &groucho[0] [0]
};
Pero, dnde apunta pint + 1? A groucho[0][1] (fila primera y colum
na segunda) o a groucho[1][0] (fila segunda y columna primera)? Para res
ponder a esta pregunta necesitamos saber cmo se almacenan los arrays bidi
mensionales. Se guardan como arrays de una dimensin, es decir, como una
serie de clulas de memoria consecutivas. El orden se determina variando pri
mero el ndice de la derecha. En nuestro ejemplo ser:
Static int cuad[2][3] = {5,6,7,8};

Figura 12.5 groucho[ 0][0]gr oucho[0][1]groucho [ 1][0]gr oucho[1][1]groucho[ 2][0]


Dos formas de inicializar un array

349
if (n > 0)
Primero se coloca la primera fila, luego la segunda, la tercera, y as suce {
sivamente. Por tanto: for (indice = 0, suma = 0; indice < n; indice++)
suma += (long) array[indice] ;
pint == &groucho[0][0] /* fila. 1, columna 1*/ return( (int) (suma/n) );
pint + 1 == &groucho[0][1] /* fila 1, columna 2*/ }
pint + 1 == &groucho[1][0] /* fila 2, columna 1*/ else
pint + 1 == &groucho[1][1] /* fila 2, columna 2*/ {
printf("No hay array.\n");
return(0) ;
}
lo tiene?, pues entonces qu apunta pint + 5? Correcto, a }

groucho[2][1]
La salida es:
Hemos descrito un array de dos dimensiones como un array de arrays. La media de la fila 0 es 5.
Cules son los nombres de las cuatro filas, teniendo en cuenta que son arrays La media de la fila 1 es 250.
unidimensionales de dos elementos? El nombre de la primera fila es groucho[0], La media de la fila 2 es 50.
y el nombre de la cuarta es groucho[3]; le dejamos a usted el trabajo de des
cubrir cmo se llaman las otras filas. Pero el nombre de un array es tambin
un puntero a ese array que apunta al primer elemento. Esto significa que
Funciones y arrays multidimensionales
groucho[0] == &groucho[0][0] Suponga que queremos una funcin que trabaje sobre toda una matriz
groucho[1] == &groucho[1][0]
groucho[2] == &groucho[2][0] en lugar de por rodajas. Cmo deberamos elaborar las definiciones y
groucho[3] == &groucho[3][0] declaraciones? En concreto, sea una funcin que maneje la matriz cosas[][]
de nuestro ejemplo anterior. La funcin main( ) sera algo as como:
Esto es ms que una novedad. Nos permite que una funcin diseada
para trabajar con arrays unidimensionales, la podamos usar con arrays mul- /* cajn de sastre */
tidimensionales! Si se siente escptico frente a nuestras palabras, se lo de main()
{
mostraremos empleando la funcin que definimos antes para calcular el static int cosas[3][4] = {
valor medio de los elementos de una matriz. {2, 4, 6, 8},
{100, 200, 300, 400},
/* funcion unidimensional, array bidimensional */
{10, 20, 60, 90}
main() {;
{ cajon(cosas);
static int cosas[3][4] = { }
{2, 4, 6, 8},
{100, 200, 300, 400},
{10, 20, 60, 90}
}; La funcin cajn( ) coge cosas, que es un puntero al array completo, co
int fila; mo argumento. Sin preocuparnos de lo que haga cajn( ), cmo sera su
for (fila = 0; fila < 3; fila++) encabezamiento?
printf("La media de la fila %d es %d.\n", Sera quiz as?
fila, media(cosas[fila],4));

/* cosas[fila] es un array unidimensional de 4 elementos */ cajon(cosas)


} int cosas[];
/* calcula la media de un array unidimensional */
int media(array,n)
int array[], n; o estara mejor as?
{
int indice;
long suma; cajon(cosas)
int cosas[][];

351
Cuestiones y respuestas
Pues no, ambas estn mal. El primer encabezamiento podra funcionar,
pero cajn tratara a cosas como un vector (array unidimensional) de 12 ele Cuestiones
mentos. La informacin de cmo estn distribuidos (tres filas y cuatro co 1. Qu imprimir este programa?
lumnas) se ha perdido.
La segunda posibilidad falla en lo mismo. A pesar de que le indica al com #define PC(X,Y) printf("%c %c\n, X, Y)
pilador que el array tiene dos dimensiones, no le dice cmo se reparten los char ref[] = { D, O, L, T};
elementos. Dos filas y seis columnas?, dos columnas y seis filas?, o qu? main()
{
Hace falta ms informacin. char *pint;
La solucin es: int indice;
cajon(cosas) for (indice = 0, pint = ref; indice < 4; indice++, pint++)
int cosas[][4]; PC(ref[indice], *pint);
}

As le indicamos al compilador que los elementos estn agrupados en fi 2. En la cuestin anterior, por qu ref se ha declarado antes de main( )?
las de cuatro columnas. 3. Cul es el valor de *punt y *(punt + 2) en cada caso?
Los arrays de tiras de caracteres son un caso especial, ya que el carcter
nulo (null) indica el fin de cada tira. Esto nos permite hacer declaraciones a. int *pint;
static int papa[43] = {12,21,121,212};
como: pint = papa;
char *lista[] ; b. float *pint ;
static float pepa[2][2] = { {1.0, 2.0}, {3.0, 4.0} };
pint = pepa[0];
Los arrays y punteros se usan muy frecuentemente con tiras de caracte
C. int *pint;
res, y sus propiedades son algo particulares; los trataremos en el siguiente static int pipa[4] = { 10023, 7};
captulo. pint = pipa;
d. int *pint;
static int popa[2][2] = { 12, 14, 16};
pint = popa[0] ;
Hasta ahora hemos aprendido
e. int *pint;
Cmo declarar un array unidimensional: long no-id[200]; static int pupa[2][2] = { {12}, {14, 16} };
pint = pupa[0];
Cmo declarar un array bidimensional: short tablero[8][8];
Qu tipos de arrays podemos inicializar: external y static 4. Suponga la declaracin static int reja[30][100];
Cmo inicializar un array, static int sombreros[3] = {10,12,15};
Otra forma de inicializar: static int gorros[] = {3,56,2}; a. Expresar la direccin de reja[22][56]
Cul es la direccin de una variable: use el operador & b. Expresar la direccin de reja[22][0] de dos formas distintas.
Cul es el valor apuntado por un puntero: use el operador * c. Expresar la direccin de reja[0][0] de tres formas distintas.
El significado del nombre de un array: sombreros = = &sombreros[0]
Correspondencia entre arrays y punteros: si punt = sombreros, entonces Respuestas
punt + 2 = = & sombreros[2], y *(punt + 2) = = sombreros[2] 1. D D
Las cinco operaciones que podemos usar con punteros: vase el texto O O
L L
El empleo de punteros con funciones que trabajan con arrays T T

2. Con ello se consigue que la clase de almacenamiento de ref sea extern por defecto, pudien-
do ser inicializada.
3. a. 12 y 121; b. 1.0 y 3.0; c. 10023 y 0 (inicializacin automtica); d. 12 y 16; e. 12 y 14
(a la primera fila slo va el 12 debido a las llaves).

353
4. a. &reja[22][56]
b. &reja[22][0] y reja[22]
c. &reja[0][0] y reja[0] y reja

Ejercicio
1. Modifique el programa de lluvia de forma que realice los clculos usando punte-
ros, y no subndices. (A pesar de todo, hay que declarar el array.)
13
Tiras de caracteres
y funciones
relacionadas
En este captulo encontrar:

Definicin de tiras dentro de un programa


Tiras constantes
Arrays de tiras: su inicializacin
Arrays y punteros
Especificacin explcita de tamao
Arrays de tiras de caracteres
Punteros y tiras
Entrada de tiras
Preparando espacio
La funcin gets( )
La funcin scanf( )
Salida de tiras
La funcin puts( )
La funcin print( )
La opcin hgaselo usted mismo
Funciones de tiras de caracteres
La funcin strlen( )
La funcin strcat( )
La funcin strcm p( )
La funcin strcpy( )
Ejemplo: ordenacin de tiras
Argumentos en lneas de ejecucin
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

357
/* Autobombo. Hagase en solitario */
#include <stdio.h>

Tiras* de caracteres #define MSJ "Debe tener muchas cualidades. Digame algunas."
/ * constante simbolica de tira de caracteres * /
#define NULL 0
#define LIM 5

y funciones #define LONLIN 81 /* longitud maxima + 1 */


char m1[] = "Limitese a una sola linea."
/* inicializacion de un array de char externo */
char *m2 = "Si no se le ocurre nada, muerase.";

relacionadas
/ * inicializacion de un puntero a char externo */

main()
{
char nombre[LONLIN];
static char talentos[LONLIN];
CONCEPTOS int i ;
int cont = 0;
char *m3 = "\nYa basta sobre mi -- como se llama?";
/ * inicializacion de un puntero */
Tiras de caracteres static char *mistal[LIM] = { "Sumo numeros con sutileza",
Inicializacin de tiras de caracteres "Multiplico con precision", "Almaceno datos",
E/S de tira "Sigo instrucciones al pie de la letra",
"Entiendo el lenguaje C};
Utilizacin de funciones para tiras /* inicializacion de un array de tiras */
Argumentos en lneas de ejecucin
printf("Hola! Soy Juanito, su ordenador favorito.\n);
printf("%s\n", "Tengo muchas cualidades. Le dire algunas. ");
puts("Las tengo en la punta del byte...Ah, si! ahi van:");
for (i = 0; i < LIM; i++)
puts (mistal[i]); /*imprime las cualidades del ordenador*/
puts(m3);
gets(nombre);
printf("Bien, %s. %s\n", nombre, MSJ) ;
printf("%s\n%s\n", m1, m2) ;
g e t s (talentos);
puts("A ver si he cogido toda la lista:");
puts(talentos);
printf("Gracias por la informacion, %s. \n", nombre) ;
Las tiras de caracteres (character strings) constituyen uno de los tipos de }
datos ms importantes y tiles del lenguaje C. Aunque venimos usando tiras
de caracteres a lo largo de todo el libro, an nos queda mucho que aprender Figura 13.1
acerca de ellas. Por supuesto, conocemos ya su caracterstica ms bsica: una Programa para usar strings
tira de caracteres no es ms que un array de tipo char terminado con un ca
rcter nulo ( \ 0). En este captulo aprenderemos nuevos detalles acerca de
las tiras, cmo declararlas e inicializarlas, cmo meterlas y sacarlas de un pro Para ayudarle a desentraar los misterios del programa, indicamos a con
grama y, finalmente, cmo manipularlas. tinuacin su salida:
En la figura 13.1 se presenta un programa en el que se ejemplarizan va
rias formas de preparar tiras, de leerlas y de imprimirlas. Utilizaremos dos Hola! Soy Juanito, su ordenador favorito.
nuevas funciones: gets( ), que captura una tira de caracteres, y puts( ), que Tengo muchas cualidades. Le dire algunas.
la imprime. (Probablemente habr notado un cierto parecido con getchar( ) Las tengo en la punta del byte. ..Ah, si! ahi van:
Sumo numeros con sutileza
y putchar( ).) El resto del programa le resultar bastante familiar. Multiplico con precision
Almaceno datos
Sigo instrucciones al pie de la letra
Entiendo el lenguaje C
* A lo largo de la obra traduciremos el trmino character string por tira de caracteres.
Tambin puede ser traducido por cadena. Ya basta sobre mi -- Como se llama?
Jose Perez

359
con el nombre de un array, que era, a su vez, un puntero a la localizacin
Bien, Jose Perez. Debe tener muchas cualidades. Digame algunas. del array. Si lo anterior es cierto, qu tipo de salida producira esta lnea?
Limitese a una sola linea.
Si no se le ocurre nada, muerase.
/* tiras como punteros */
Borracho, mujeriego, programador, catador de queso y soltero. main()
A ver si he cogido toda la lista:
Borracho, mujeriego, programador, catador de queso y soltero. {
Gracias por la informacion, Jose Perez. printf("%s, %u, %c\n","Me", "gustas", *"tu") ;
}

Profundicemos ahora en los distintos componentes del programa. No va


mos a realizar un anlisis lnea por lnea; por el contrario, realizaremos los El formato %s imprimir la tira, por lo que debemos esperar que aparezca
comentarios de una forma ms organizada. En primer lugar, nos fijaremos Me. Por su parte, el formato %u produce como salida un entero sin signo.
en las distintas formas de definir una tira en el programa; a continuacin, Si la palabra gustas se comporta como puntero, este formato debera im
observaremos las distintas peculiaridades que intervienen en la lectura de la primir el valor del puntero gustas, es decir, la direccin del primer carc
tira; por ltimo, estudiaremos las formas de salida de la tira. ter de la tira. Por ltimo, *tu deber dar como resultado el valor de la
direccin a la que apunta este puntero, es decir, el primer carcter de la pro
pia tira. Pero bueno, realmente es as? Bien, veamos la salida:
Definicin de tiras dentro de un programa Me, 34, t
Habr observado, mientras revisaba el programa, que existen muchas for
mas diferentes de definir una tira. Nos dedicaremos ahora a repasar las ms Tatatachn! Dediqumonos ahora a las tiras almacenadas como arrays
importantes: de tiras constantes, de arrays de tipo char, de punteros a char de tipo char.
y de arrays de tiras de caracteres. Adems, el programa deber asegurarse
de que dispone de suficiente espacio de almacenamiento para guardar la tira; Arrays de tiras: inicializacin
tambin nos ocuparemos de eso. Cuando se define un array de tira de caracteres, se debe indicar al compi
lador cunto espacio debe reservar. Una forma de hacerlo es inicializar el array
Tiras constantes con una tira constante. Como ya se indic, los arrays auto no pueden inicia-
Dondequiera que el compilador encuentra cualquier cosa circundada por lizarse; por tanto, deberemos utilizar arrays de tipo static o extern para este
comillas, lo asimila como un tira constante. Los caracteres que estn ence propsito. Por ejemplo,
rrados entre las comillas, junto con un carcter \ 0, se almacenan en posi
ciones adyacentes de memoria. El ordenador cuenta el nmero de caracteres, char m1[] = "Limitese a una sola linea.";
de forma que conoce de antemano cunta memoria va a necesitar para su
almacenamiento. Nuestro programa utiliza varias tiras constantes de esta clase, inicializa el array m1 como external (por defecto), asignndole el valor de
sobre todo como argumentos de las funciones printf( ) y puts( ). Obsrvese, la tira indicada. Esta forma de inicializacin no es ms que una manera ms
adems, que podemos definir una tira constante en preprocesador con #define. corta de expresar la inicializacin estndar de arrays:
Cuando se desea que figure un signo comillas dentro de la tira, se deber
anteponer al mismo un carcter barra-atrs:

printf("\"Vuela, vuela, pajarito!\" dijo Juan.\n");

La salida es:
"Vuela, vuela, pajarito! dijo Juan.
(Observe el carcter nulo de cierre; sin l lo que tendramos sera un array
Las tiras constantes se guardan en modo de almacenamiento static. La de caracteres, no una tira.) De cualquiera de las maneras (recomendamos fer
frase entera que se coloca entre comillas acta como puntero al lugar donde vientemente la primera), el ordenador cuenta los caracteres y prepara un array
se ha almacenado la misma. La situacin es anloga a la que encontrbamos del tamao correspondiente.

361
Al igual que en los arrays comentados anteriormente, el nombre del array ARRAYS Y PUNTEROS: DIFERENCIAS
m1 es un puntero al primer elemento del mismo:
m1 == &m1[0] *m1 == 'L' *(m1+1) == m1[1] == 'i' En el texto acabamos de discutir las diferencias entre el empleo de una
declaracin de las dos formas siguientes:
De hecho, se puede utilizar notacin de punteros para la preparacin de static char nena[] = "Quiero a papa!";
la tira. Por ejemplo, usaramos char *nene = "Quiero a mama!";

char *m3 = "\nBasta por hoy - seguimos otro dia";


La diferencia ms significativa es que el puntero nena es una constante,
que es lo mismo (o casi) que decir mientras que nene es una variable. Veamos en qu queda esta diferencia
en la prctica.
static char m3[] = "\nBasta por hoy - seguimos otro dia"; En primer lugar, en ambas formas se puede usar la adicin de punteros.

Ambas declaraciones han preparado m3 como un puntero a la tira co for (i = 0; i < 6; i ++)
rrespondiente. En ambos casos, la propia tira determina la cantidad de al putchar( *(nena + i));
putchar('\n');
macenamiento que se reserva para ella misma. Sin embargo, las dos formas for (i = 0; i < 6; i++)
no son completamente idnticas. putchar( *(nene + i));
putchar('\n');

Arrays y punteros
Pero bueno, cul es la diferencia? da como salida
La forma array genera un array de 35 elementos (uno por cada carcter
y uno ms por el '\0' final) en almacenamiento esttico. Cada elemento se Quiero
inicializa a su carcter correspondiente. A partir de ese momento, el compi Quiero
lador reconocer el nombre m3 como sinnimo de la direccin del primer
elemento del array, &m3[0]. Un detalle importante a destacar aqu es que m3 Por otra parte, nicamente la versin puntero puede utilizar el opera
es un puntero constante; no se puede cambiar m3 porque implicara alterar dor incremento
la localizacin (direccin) en que est almacenado el array. Se pueden utili
zar operaciones como m3 + 1 para identificar el siguiente elemento del array, while ( *(nene) != '\0') /* acaba al final de la tira */
pero no se permite + + m3. Tenga presente que el operador incremento slo putchar( *(nene++); /* imprime caracter, avanza puntero */
se puede emplear con nombres de variables, no con constantes.
El puntero tambin genera 35 elementos en almacenamiento esttico, co resulta
locados en forma de tira. Adems, prepara una localizacin de memoria ex
tra para la variable puntero m3. Esta variable apunta inicialmente al comien Quiero a mama!
zo de la tira, pero su valor se puede alterar. As, est permitido en este caso
utilizar el operador incremento; de hecho, + + m3 hara que ste apuntase
al segundo carcter (B). Observe, adems, que no tenemos que declarar *m3 Supongamos que queremos igualar nena a nene. Se puede escribir
como static. La razn es que no estamos inicializando, en este caso, un array
de 35 elementos, sino simplemente una variable de tipo puntero. No existen nene = nena; /* nene apunta ahora al array nena */

restricciones para la inicializacin de variables ordinarias (es decir, que no


son arrays) por lo que concierne a clases de almacenamiento. pero no est permitido
Son realmente importantes estas diferencias? Frecuentemente, no; pero
todo depende de lo que se pretenda hacer. Observe el cuadro siguiente, en nena = nene; /* construccion ilegal */
el que se ofrecen algunos ejemplos. Entre tanto, volveremos al problema de
la creacin de espacio de almacenamiento para las tiras de caracteres. La situacin es anloga a escribir x = 3; o bien 3 = x;. La parte izquier
da de la sentencia de designacin debe ser siempre una variable. Por cierto,

363
Como nombre es un array pensado para ser ledo en ejecucin, el compi
lador no tiene manera alguna de saber cunto espacio debe reservar con an
la igualdad nene = nena; no hace que desaparezca la tira de caracteres de telacin, a menos que se lo indiquemos explcitamente. Dicho de otra forma,
mam; simplemente cambia la direccin almacenada en nene.
Existe una manera de alterar el mensaje de nena, que consiste en intro no existe una tira de caracteres presente que el compilador pueda utilizar co
ducirse en el propio array. mo referencia para clculo del tamao de almacenamiento. As pues, elegi
mos un tamao lo suficientemente grande como para que se pueda introdu
nena[7] = 'm' ; nena[9] = 'm' ; cir el nombre del usuario sin problemas (el tamao escogido equivale a un
regln completo de la pantalla).
o bien
Arrays de tiras de caracteres
*(nena + 7) = 'm'; *(nena + 9) = 'm';
A menudo es conveniente disponer de un array de tiras. De esta forma,
se puede utilizar un subndice para acceder a distintas tiras. Tambin hemos
Los elementos del array son variables; el nombre del mismo no lo es.
empleado esta configuracin en el ejemplo:

static char *mistal[LIM] = { "Sumo numeros con sutileza",


"Multiplico con precision", "Almaceno datos",
"Sigo instrucciones al pie de la letra",
Especificacin explcita de tamao "Entiendo el lenguaje C"};
Otra forma de disponer de sitio para almacenar la tira es declararlo direc
tamente. En la declaracin external podramos haber dicho
Estudiemos esa declaracin ms detenidamente. Como hemos seleccio
char m1[40] = "Limitese a una sola linea."; nado para LIM el valor 5, podemos afirmar que mistal es una array de cinco
punteros a tiras de caracteres. Por supuesto, cada tira es, por su parte, un
en lugar de array de caracteres, por lo que, en resumen, disponemos de cinco punteros
a arrays. El primer puntero es mistal[0], y apunta a la primera tira. El segun
char m1[] = "Limitese a una sola linea.";
do es mistal[1], y apunta a la segunda. En concreto, cada puntero apunta
al primer carcter de cada tira:
La nica precaucin a observar es que el nmero de elementos declara
dos sea, como mnimo, uno ms (el correspondiente al carcter nulo) de la *mistal[0] == 'S', *mistal[1] == 'M', *mistal[2] == 'A'
longitud de la tira. Al igual que en los dems arrays de tipo esttico o exter
no, cualquier elemento no utilizado ser inicializado a 0 (el cual, en formato
char, corresponde al carcter nulo, no al carcter 0, como dgito). y as sucesivamente.
La inicializacin sigue las reglas establecidas para arrays; la porcin en
tre llaves es equivalente a
Los elementos sobrantes se iniclalizan a \0

{{. . . },{...},...,{...}};
en la que los puntos indican las distintas entradas, que somos demasiado va
static char mascota [12] = lindo pez"; gos para escribir explcitamente. El detalle principal que queremos destacar
es que el primer conjunto de comillas corresponde a una pareja de llaves,
Figura 13.2 y se usa, por tanto, para inicializar el puntero al primer carcter de la tira.
Inicializacin de un array El siguiente conjunto de comillas inicializa el segundo puntero, etc. Entre con
juntos vecinos se coloca una coma.
Tambin aqu podramos haber declarado explcitamente el tamao de las
Observe que en el programa inicial asignamos tamao al array nombre tiras de caracteres utilizando una declaracin del tipo
char nombre[81];
static char mistal[LIM] [LONLIN] ;

365
/* punteros y tiras */
#define PX(X) printf("X = %s; valor = %u; &X = %u\n", X, X, &X)
Hay una diferencia en esta segunda forma de declaracin, en la que obte main()
nemos un array rectangular con todas las filas de la misma longitud. Por {
el contrario, static char *mnsj = "Te estas pasando!";
static char *copia;
static char * m i s t a l l [ LIM];
copia = mnsj ;
p r intf("%s\n", copia);
prepara un array no uniforme, en el que cada longitud de fila est deter PX(mnsj) ;
minada por la tira inicializada. Ese array comentado en ltimo lugar no des PX(copia);
perdicia espacio de almacenamiento alguno. }

Observando el programa se podra inferir que realiza una copia de la tira


Te ests pasando!. Adems, esta observacin a bote pronto se vera con
firmada, en princpio, por la salida del programa:

Te estas pasando!
m n sj = Te e stas p asand o!; va lor = 14 ; & m nsj = 32
c o p ia = T e e s t a s p a s a n d o ! ; v a l o r = 1 4 ; & c o p ia = 3 4

Pero estudiemos cuidadosamente la salida de PX( ). En primer lugar, X,


que ha sido sucesivamente colocada como mnsj y copia, se imprime como
tira de caracteres (%s). Hasta aqu no hay sorpresas; las dos tiras son Te
ests pasando!.
En segundo lugar...; bueno, volveremos ms adelante a este punto.
El tercer tem que se imprime en cada lnea es &X, la direccin de X. Los
dos punteros mnsj y copia estn almacenados en las direcciones 32 y 34, res
pectivamente.
Fijmonos ahora en el segundo tem, el que hemos llamado valor. Es el
propio X. El valor del puntero es la direccin que contiene. Se puede ver que
tanto mnsj como copia apuntan a la direccin de memoria 14.
El significado de este ltimo aserto es que la tira en s misma no ha sido
copiada. Lo nico que hace la sentencia copia = mnsj; es producir un segun
do puntero apuntando a la misma tira.
Y para qu todo este lo? Por qu no copiamos la tira completa? Bien,
convendr con nosotros que es mucho ms eficiente copiar una direccin que,
por ejemplo, 50 elementos distintos. A menudo, lo nico que se necesita pa
ra realizar esta tarea es la direccin.
Ahora que ya hemos discutido la definicin de tiras dentro del programa,
Figura 13.3 nos dedicaremos a la lectura de tiras de caracteres.
Arrays no uniformes y rectangulares

Punteros y tiras Entrada de tiras


Quiz habr observado, a lo largo de nuestra discusin sobre tiras, ciertas
referencias ocasionales a los punteros. La mayora de operaciones que ata- La introduccin de una tira en un programa tiene dos etapas: prepara
en a las tiras de caracteres se efectan en C con punteros. Por ejemplo, con- cin de espacio para su almacenamiento y empleo de una funcin de entrada
sideremos el siguiente programa, tan intil como instructivo: para capturar la tira.

367
/* getnombrel */
main()
{
Preparando espacio char nombre[81]; /* reserva espacio */
Lo primero que hay que hacer es disponer de un sitio donde colocar la printf("Hola, como te llamas?\n");
tira una vez que se haya ledo. Tal como se mencion con anterioridad, esto gets(nombre); /* introduce entrada en tira "nombre" */
significa contar con espacio suficiente para meter todas las tiras que se vayan printf("Bonito nombre, %s.\n", nombre) ;
a leer. No espere que el ordenador cuente la longitud de la tira conforme se }
va leyendo y prepare espacio para ella en ese momento; no funcionar (a me
nos que escriba un programa que lo haga as). Si intenta ejecutar algo como Esta funcin aceptar cualquier nombre (incluyendo espacios) de hasta
esto: 80 caracteres de largo. (Recuerde que se debe reservar un espacio para \ 0.)
static char *nombre; Observe que deseamos que gets( ) afecte algo (nombre) en el programa
scanf("%s", nombre) ; de llamada. As pues, deberemos utilizar un puntero como argumento: por
supuesto, el nombre del array es un puntero.
se encontrar con que el compilador probablemente lo traga; pero en el De todas formas, la funcin gets( ) es ms sofisticada que lo que sugiere
mismo instante que se lea nombre, se guardar sobre otros datos o parte del el ejemplo anterior. Observe ste:
cdigo del propio programa. La mayora de los programadores opinan que
una situacin as es realmente graciosa, pero nicamente cuando le sucede /* getnombre2 */
a los programas ajenos. main()
La forma ms simple de preparar espacio es definir explcitamente el ta {
mao del array en la propia declaracin: char nombre[80];
char *ptr, *gets();
char nombre[81];
printf("Hola, como te llamas?\n");
ptr = gets(nombre);
printf("%s? Ah! %s!\n", nombre ptr);
Otra posibilidad es usar funciones de la librera C que asignan memoria, }
de las cuales hablaremos en el captulo 15.
En nuestro programa, utilizamos un array de tipo auto para nombre. Lo
hicimos as porque no tenamos que inicializarlo. Una posible salida sera:
Lina vez dispuesto el espacio de almacenamiento suficiente para la tira,
se puede proceder a su lectura. Como ya hemos comentado, las subrutinas
de entrada no forman parte de la definicin del C. Sin embargo, la mayora Hola, como te llamas?
Pedro Piedra
de los sistemas han dispuesto dos funciones de librera para la captura de da Pedro Piedra? Ah! Pedro Piedra!
tos: scanf( ) y gets( ); ambas pueden utilizarse para leer tiras. De ellas, la
ms usual es gets( ), que discutiremos en primer lugar.
Gets( ) ha ejecutado la entrada de los datos de dos formas diferentes!

La funcin gets( ) 1. Utiliza el mtodo del puntero para colocar la tira en nombre.
La funcin gets( ) (del ingls get string) es muy til y manejable para pro 2. Utiliza la palabra clave return para devolver la tira a ptr. Observe que
gramas interactivos. Su actuacin consiste en la captura de una tira que se ptr es un puntero a char. Esto quiere decir que gets( ) debe devolver
introduce por el perifrico de entrada estndar de su sistema, que en adelan un valor que es, asimismo, un puntero a char; de ah la declaracin
te supondremos que es un teclado. Como la tira no tiene una longitud prede efectuada en la seccin correspondiente en el segundo ejemplo.
terminada, gets( ) necesita una forma de saber cundo debe acabar de leer.
El mtodo empleado es leer caracteres hasta encontrar un carcter nueva Este formato de declaracin
lnea ( \ n), el cual se genera pulsando la tecla [enter]. En ese momento
se toman todos los caracteres con excepcin del carcter nueva lnea, se aa char *gets();
de al final un carcter nulo ( \ 0) y se devuelve la tira al programa de llama
da. Seguidamente se presenta un sencillo ejemplo: indica que gets( ) es una funcin (por eso lleva parntesis) del tipo puntero-
a-char (por eso se le coloca un * y un char). En el primer ejemplo pudimos

369
(espacio, tabulado o nueva lnea). Por otra parte, si se especifica una anchura
de campo, por ejemplo %10s, la funcin scanf( ) recoge o bien 10 caracte
pasar el programa sin esta declaracin porque no tenamos intencin de utili res, o los caracteres que haya antes del primer espacio en blanco; la eleccin
zar el valor de retorno de gets( ). est determinada por la situacin que se d en primer lugar.
Por cierto, se puede declarar tambin una variable como puntero a una Scanf( ) devuelve un valor entero igual al nmero de tems ledos, cuan
funcin. Tendra un aspecto como ste: do funciona correctamente, o bien, cuando encuentra un carcter EOF, de
char (*fino)(); vuelve este ltimo.
/* scanf() contando */
donde fino sera un puntero a una funcin de tipo char. Volveremos sobre main()
estas curiosas declaraciones en el captulo 14. {
La estructura de la funcin gets( ) deber ser algo como: static char nombre1[40], nombre2[11] ;
int cont;
char *gets(s) printf("Introduzca dos nombres.\n");
char *s;
{ cont = scanf("%s %10s", nombre1 , nombre2) ;
char *p; printf ("He leido los %d nombres %s y %s. \n",
cont, nombre1, nombre2);
... }
return(p) ;

} Veamos dos ejemplos de salidas:


En realidad, la estructura es un poco ms complicada, porque gets( ) tie
ne dos retornos posibles. Si todo va bien, devuelve en el return la tira leda, Introduzca dos nombres.
tal como se ha dicho; si hay algo equivocado, o la funcin encuentra un ca Jacinto Juan
rcter EOF, devuelve una direccin 0 o NULL. As pues, gets( ) lleva incor He leido los 2 nombres Jacinto y Juan.
porado un cierto control de errores.
Introduzca dos nombres.
Esta disposicin hace conveniente el uso de sentencias como: Jose Papapopoulos
He leido los 2 nombres Jose y Papapopoul.
while (gets(nombre) != NULL)
en la que NULL est definido en stdio.h como 0. La faceta puntero de nues
tra reciente adquisicin asigna un valor a nombre. La faceta return, por su En el segundo ejemplo se han ledo nicamente los 10 primeros caracte
parte, asigna un valor a gets(nombre), tomado como un todo, y permite com res de Papapopoulos, por estar utilizando el formato %10s.
probar la aparicin de un EOF. Esta disposicin de dos caras de la funcin Cuando se trata de leer esto desde teclado, es aconsejable emplear gets( ).
gets( ) es ms compacta que la permitida por getchar( ), la cual devolva un Es ms fcil de utilizar, ms rpida y ms compacta. El uso principal de
scanf( ) ser en aquellos casos en que debamos introducir una mezcla de da
valor en return, pero careca de argumento. tos de tipo diferente de alguna manera estndar. Por ejemplo, si cada lnea
de entrada contiene el nombre de una herramienta, su nmero de almacn
while ((ch = getchar()) != EOF) y su precio, deber emplear la funcin scanf( ). O mejor an, escribir por
La funcin scanf( ) su cuenta una funcin a medida que lleve incluido algn control de erro
Ya hemos empleado con anterioridad scanf( ) con el formato %s para res.
leer una tira de caracteres. La mayor diferencia entre scanf( ) y gets( ) est Pasaremos ahora a discutir el proceso de salida de tiras.
en la toma de decisin del final de la tira; scanf( ), ms que una funcin para
capturar tiras, deberamos llamarla una funcin capturapalabras. Como
ya hemos visto, la funcin gets( ) captura todos los caracteres hasta encon Salida de tiras
trar un carcter nueva lnea. La funcin scanf( ) puede terminar la lectura
de dos maneras distintas. En cualquiera de las dos, la lectura comienza con De nuevo en este caso nos apoyaremos en funciones de librera, por lo
el primer carcter no blanco que se encuentra. Si se est usando el formato
%s, la tira contina hasta (exclusive) el siguiente espacio en blanco localizado
que pueden aparecer ligeras diferencias de un sistema a otro. Las dos funcio
nes fundamentales en salida de tiras de caracteres son puts( ) y printf( ).

371
Observe, tambin, que cada tira impresa por la funcin puts( ) aparece
en una lnea diferente. Lo que sucede es que cuando puts( ) encuentra el ca
La funcin puts( )
rcter nulo final, lo sustituye por un carcter nueva lnea y lo enva junto
Es sta una funcin muy fcil de utilizar; nicamente necesita un argu con la tira.
mento que sea un puntero a una tira de caracteres. En el siguiente ejemplo
se muestran algunas de las muchas formas de hacerlo: La funcin printf( )
/* puts facilon */ Ya hemos discutido printf( ) con bastante minuciosidad anteriormente.
#include <stdio.h>
#define "Naci de un #define." Al igual que puts( ), toma un puntero a una tira como argumento. La fun
main(){ cin printf( ), sin embargo, resulta ligeramente menos adecuada que puts( ),
static char str1[] = "Un array me inicializo."; pero es ms verstil.
static char *str2[] = "Un puntero me inicializo."; Una diferencia entre ambas funciones es que printf( ) no aade el carc
puts("Soy un argumento de puts().") ; ter nueva lnea tras la tira automticamente. Por ello, deberemos indicarle
puts(DEF); dnde deseamos que salte la lnea. As
puts(str1) ;
puts(str2) ;
puts(&str1[4]) ; printf("%s\", tira);
puts(str2+4);
}
tiene el mismo efecto que
La salida sera: puts(tira) ;
Soy un argumento de puts().
Naci de un #define.
Un array me inicializo. Como habr observado, la primera forma requiere ms trabajo de tecla.
Un puntero me inicializo. Tambin consume mayor tiempo de ordenador en ejecucin. Por otra parte,
rray me inicializo.
untero me inicializo. printf( ) hace ms simple la tarea de combinar tiras en una sola lnea de es
critura. Por ejemplo,
Este ejemplo nos recuerda que las frases entre comillas y los nombres de
las tiras de caracteres son punteros. Fjese, adems, en los dos ejemplos fina printf("Bien, %s, %s\n", nombre, MSJ);
les. El puntero &str1[4] apunta al quinto elemento del array str1. Dicho ele
mento contiene el carcter r, que es el utilizado por puts( ) como punto
de partida. De igual forma, str2 + 4 apunta a la clula de memoria que con combina Bien con el nombre del usuario y una tira de caracteres, todos
tiene la u de puntero, que es donde comienza la salida. ellos en la misma lnea.
Cmo sabe puts( ) dnde debe terminar? La funcin termina all don
de encuentre el primer carcter nulo. Por la cuenta que le trae, procure que
haya uno. Nunca haga esto!!
La opcin "hgaselo usted mismo"
/* Noooooo !!!!! */
main()
{ No tenemos por qu estar limitados por estas opciones de biblioteca en
static char asino[] = {'H', 'O', 'L', 'A' }; entrada/salida. Si no las posee su sistema, o simplemente no le gustan, se
puts(asino); /* no es una tira de caracteres */
puede preparar sus propias versiones a partir de getchar( ) y putchar( ).
} Supongamos que le falta la funcin puts( ). El programa siguiente es un
ejemplo de una posible implementacin:
Al no disponer asino de un carcter nulo como terminacin, no se consi
dera una tira de caracteres; adems, al faltar el carcter nulo, puts( ) no sa /* put1 -- imprime una tira */
br dnde ha de terminar; por tanto, continuar viajando por las direcciones put1(tira)
de memoria, a partir de la direccin donde se encuentre asino, hasta encon char *tira;
trar un carcter nulo. Si tiene suerte, lo encontrar en la siguiente clula de {
while(*,tira != '\0')
memoria; recuerde, sin embargo, que no siempre va a ser tan afortunado. putchar(*tira++);
putchar('\n');
}

373
en primer lugar, ejecutarla, produciendo la tira que se ha de imprimir. La
salida es la siguiente:
El puntero char tira apunta inicialmente al primer elemento del argumen
to de llamada. Una vez impreso el contenido de tal elemento, el puntero se Si tuviera dinero suficiente,
incrementa y apunta al elemento siguiente. Este proceso contina hasta que arreglaria de una vez esa gotera.
el puntero se encuentra apuntando a un elemento que contiene el carcter He contado 33 caracteres.
nulo. En ese momento se detiene la impresin y se enva un carcter nueva
lnea.
Supongamos ahora que disponemos de una funcin puts( ), pero desea A estas alturas debe ser capaz de construir una versin de gets( ) que fun
mos otra que indique, adems, el nmero de caracteres que se han impreso. cione adecuadamente; deber ser semejante, aunque mucho ms sencilla, que
Es muy fcil aadir este detalle: la funcin getint( ) que presentamos en el captulo 10.

/* put2 -- imprime una tira y cuenta sus caracteres */


put2(tira)
char *tira;
{ Funciones de tiras de caracteres
int cont = 0;
while(*tira != '\0')
{ La mayora de bibliotecas C presentan funciones para manejo de tiras de
putchar(*tira++); caracteres. Estudiaremos ahora las cuatro funciones ms tiles y ms comu
cont++;
} nes: strlen( ), strcat( ), strcmp( ) y strcpy( ).
putchar('\n'); Ya hemos utilizado strlen( ), que es la encargada de calcular la longitud
return(cont) ;
} de una tira. La usaremos en el prximo ejemplo, en una funcin dedicada
a cortar tiras demasiado largas.
La llamada La funcin strlen( )
put2("pizza") ;
/* funcin de censura */
corta(tira, largo)
imprimira la tira de caracteres pizza, mientras que la llamada char *tira;
int largo;
num = put2("pizza");
if ( strlen(tira) > largo)
*(tira + largo) = '\0';
enviara tambin un entero que simbolizase el nmero de caracteres impresos }
a la variable num; en este caso, num tomara el valor 5. He aqu una versin
ligeramente ms elaborada que utiliza funciones anidadas:
Prubela con este programa:
/* funciones anidadas */
#include <stdio.h> /* test */
mai n() main()
{
put1("Si tuviera dinero suficiente,\n"); static char mens[] = "Agarreseme que vienen curvas.";
printf("He contado %d caracteres.\n",
put2("arreglaria de una vez esa gotera.") ); puts(mens);
} corta(mens,10);
puts(mens);
}
(La razn del #include < stdio.h > es que en nuestro sistema putchar( )
est definido all, y estas funciones lo utilizan.) La salida debe ser:
Vaya! estamos empleando printf( ) para imprimir el valor de put2( ),
pero en la propia accin de bsqueda del valor de put2( ) el ordenador debe, Agarresele que vienen curvas.
Agarreseme

375
Hemos aadido 1 a las longitudes combinadas con el fin de disponer de
Nuestra funcin coloca un carcter \ 0 en el elemento undcimo del array, espacio para el carcter nulo.
que corresponde a un blanco. El resto del array contina all, pero putsf()
se detendr en el primer carcter nulo, ignorando la porcin restante. La funcin strcmp( )

La funcin strcat( ) Supongamos que desea comparar una respuesta tecleada al ordenador con
una tira almacenada previamente:
He aqu un ejemplo de lo que strcat( ) es capaz de hacer:
/* Funciona esto? */
/* uniendo dos ti ras */ #include <stdio.h>
#tinclude <stdio.h> #define RESP "Blanco"
main() main()
{ {
static char flor[80]; char prueba[40];
static char apendice[] = "huelen a zapato usado.";
puts("De que color es el caballo blanco de Santiago?");
puts("Cuales son sus flores favoritas?"); gets(prueba);
gets(flor); while (prueba != RESP)
strcat(flor, apendice); {
puts(f1or); puts("Ni idea. Prueba otra vez.");
puts(apendice); gets(prueba);
}
puts("Correcto!");
}
La salida es:
A primera vista parece elegante y funcional, pero va a mostrar un fallo
Cuales son sus flores favoritas? en ejecucin. Lo que sucede es que prueba y RESP son, en realidad, punte
las rosas ros; por lo cual, la comparacin prueba != RESP no pregunta si las dos ti
las rosas huelen a zapato usado,
huelen a zapato usado. ras son iguales, sino si las direcciones apuntadas por prueba y RESP son la
misma. Como estas dos tiras se almacenan en lugares diferentes, los dos pun
teros no podrn coincidir jams, y el/la usuario/a recibir siempre un men
Como puede observar, strcat( ) (del ingls string concatenation) toma dos saje de respuesta equivocada. Programas como ste han llevado al suicidio
tiras de caracteres como argumento, aade una copia de la segunda al final en ms de una ocasin.
de la primera y hace que esta versin combinada sea la nueva tira primera. Lo que necesitamos es una funcin que compare el contenido de las tiras,
La segunda tira de caracteres no se altera. no sus direcciones. Podramos prepararnos una por nuestra cuenta, pero nor
Precaucin! Esta funcin no comprueba si la segunda tira dispone de malmente encontraremos el trabajo ya hecho con stremp( ) (del ingls string
espacio suficiente para almacenarse tras la primera. Si no toma precauciones comparison).
al respecto, se puede encontrar con muchos problemas. Evidentemente, po
demos emplear strlen( ) para asegurarnos antes de saltar al vaco. Podemos ahora arreglar nuestro programa:
/* Este si funciona */
/* unin de dos tiras comprobando si caben */ #include <stdio.h>
#include <stdio.h> #define RESP "Blanco"
#define MAX 80 main()
main() {
{ char prueba[40];
static char flor[MAX];
static char apendice[] = " huelen a zapato usado."; puts("De que color es el caballo blanco de Santiago?");
gets(prueba);
puts("Cuales son sus flores favoritas?"); while (strcmp(prueba, RESP) != 0)
gets(flor); {
if ((strlen(apendice) + strlen(flor) + 1) < MAX) puts("Ni idea. Prueba otra vez.");
strcat(flor, apendice); gets(prueba);
puts(flor); }
} puts("Correcto! " );
}

377
primera tira precede a la segunda desde un punto de vista alfabtico, en tan
Al ser considerados como verdad los valores distintos de 0, podramos to que da valores positivos cuando el orden alfabtico es correcto. Adems,
tambin abreviar la sentencia while dejndola en while ( strcmp(prueba, si comparamos C con A obtenemos un 2 en lugar de un 1. El compor
RESP)). tamiento parece claro: la funcin devuelve la diferencia en cdigo ASCII en
Se puede deducir de este ejemplo que strcmp( ) toma dos punteros de ti tre los dos caracteres. Generalizando, strcmp( ) se mueve por la tira hasta
ras como argumentos y devuelve el valor 0 si las tiras son iguales. Si ya haba que encuentra el primer par de caracteres diferentes; entonces devuelve la di
llegado a esa conclusin por su cuenta, antese un tanto en su marcardor ferencia ASCII de los mismos. Por ejemplo, en el ltimo listado manzanas
particular. y manzana coinciden en todos los caracteres excepto en el ltimo, la s fi
Uno de los detalles ms agradables de strcmp( ) es que compara tiras de ca nal de la primera tira. Dicho carcter est emparejado a efectos de compara
racteres, no arrays. As, aunque el array prueba ocupa 40 posiciones de cin con el noveno carcter de manzana, que corresponde a su carcter nulo,
memoria, y Blanco nicamente 7 (no olvide el carcter nulo), la compara ASCII 0. Por tanto, el valor devuelto es
cin se establece nicamente en la parte de prueba que comprende desde el
principio hasta su primer carcter nulo. As pues, strcmp( ) puede emplearse 's' - ' \ 0 ' = 115 0 = 115
para comparar tiras almacenadas en arrays de diferente tamao.
Qu sucede si el usuario contesta BLANCO, o blanco, o blan en que 115 es el cdigo ASCII de s.
qusimo? Bueno, el ordenador responder que la respuesta es incorrecta.
Generalmente, uno no se preocupa del valor exacto que se ha devuelto.
Si se desea hacer un programa ms humanizado, deber prever la posibi
La forma ms usual de empleo de strcmp( ) implica conocer exclusivamente
lidad de respuestas alternativas. Existen algunos trucos en este sentido; por
ejemplo, se puede usar un #define con la respuesta como BLANCO y es si el valor de retorno es o no 0; es decir, si las tiras son iguales o diferentes;
cribir una funcin que convierta cualquier valor de entrada en maysculas tampoco nos preocupa el valor concreto cuando se trata de ordenar tiras al
nicamente; con ello se eliminara el problema surgido entre combinaciones fabticamente; en ese caso nos preocupara exclusivamente si el valor devuel
de maysculas y minsculas, pero an quedaran otros cabos por atar. to es positivo, negativo o 0.
Por cierto, qu valor devuelve strcmp( ) si las tiras son diferentes? Vea Se puede utilizar esta funcin para comprobar si un programa debe dete
mos un ejemplo: ner la entrada de datos:
/* return de strcmp */
#include <stdio.h> /* comienzo de un programa */
main() #include <stdio.h>
{ #define TAM 81
printf("%d\n", strcmp("A", "A")), "A") ); #define LIM 100
printf("%d\n", strcmp("A", "B") ); define STOP " " /* una tira nula */
printf("%d\n", strcmp("B", "A") ); main() {
printf("%d\n", strcmp("C", "A") );
printf("%\n", strcmp("manzanas", "manzana") ); static char entra[LIM][TAM];
} int ct = O;

while (gets(entra[ct]) != NULL && strcmp(entra[ct], STOP)


!= 0 && ct++ < LIM)
La salida es:
}

Este programa abandona la lectura de la entrada cuando encuentra un


carcter EOF (gets( ) devuelve un NULL (nulo) en este caso), o bien cuando
se pulsa la tecla [enter] al comienzo de una lnea (es decir, se introduce una
Tal como prometimos, la comparacin de A consigo mismo devuelve tira vaca), o tambin cuando se alcanza el lmite LIM. Una entrada de tira
un 0. Si comparamos A con B obtenemos un -1, mientras que la mis vaca permite al usuario una forma muy cmoda de acabar la introduccin
ma comparacin en orden inverso da como resultado 1. Este peculiar com de una frase.
portamiento sugiere que strcmp( ) devuelve un nmero negativo cuando la Pasemos ahora a la ltima funcin de manejo de tiras que discutiremos
en este captulo.

379
En resumen, strcpy( ) utiliza dos punteros a tiras como argumentos. El
La funcin strcpy( ) segundo puntero, que apunta a la tira original, puede ser un puntero declara
Ya hemos dicho que si pts1 y pts2 son punteros a tiras de caracteres, la do, un nombre de array, o una tira constante; sin embargo, el primer punte
expresin ro, que apunta a la copia, deber necesariamente apuntar a un array, o por
cin del mismo, de suficiente tamao como para guardar la tira a copiar.
pts2 = ptsl ; Una vez revisadas algunas funciones para manejo de tiras, nos dedicare
mos a estudiar un programa completo que maneja tiras de caracteres.
copia nicamente la direccin de la tira, no la propia tira. Supongamos, sin
embargo, que se desea copiar una tira. Para ello puede utilizar la funcin
strcpy( ). Funciona como sigue: Ejemplo: ordenacin de tiras
/* strcpy() en accion */
Abordaremos un problema prctico bastante comn: una ordenacin de
#include <stdio.h> tiras por orden alfabtico. Esta subtarea se puede presentar cuando se prepa
#define FRASE "Reconsidere su ultima entrada, por favor." ran listas de nombres, se construye un ndice y en otras muchas situaciones
main()
{ diarias. Una de las herramientas principales de tal programa deber ser nece
static char *orig = FRASE; sariamente strcmp( ), que utilizaremos para determinar el orden de dos tiras
static char copia[40]; concretas. El planteamiento general de nuestro programa incluir: lectura de
puts ( o r i g ) ; un array de tiras, ordenacin de las mismas e impresin del resultado orde
puts(copia); nado. Hace un momento presentbamos un esquema para leer tiras, el cual
strcpy(copia, orig);
puts(orig); utilizaremos para comenzar el programa. La parte de salida no tiene mayor
puts(copia); problema; respecto a la ordenacin, emplearemos el mismo algoritmo que
} usbamos para nmeros; entretanto, introduciremos en el programa un pe
queo truco: obsrvelo con atencin y vea si es capaz de descubrirlo.
La salida es:
/* lectura y clasificacin de tiras */
#include <stdio.h>
Reconsidere su ultima entrada, por favor. #define TAM 81 /* limite longitud tira contando \0 */
#define LIM 20 /* numero maximo de tiras a leer */
Reconsidere su ultima entrada, por favor. #define PARA "" /* tira nula para detener entrada */
Reconsidere su ultima entrada, por favor. main()
{
static char entra[LIM][TAM] ; /* array para entrada */
Podemos observar que la tira apuntada por el segundo argumento (orig) char *ptira[LIM] ; /* array de variables puntero */
se ha copiado en el array apuntado por el primer elemento (copia). Puede int ct =0; /* contador de entrada */
recordar el orden en que se deben introducir los argumentos observando que int k; /* contador de salida */
es el mismo orden usado en las sentencias de asignacin: la tira que va a ad
quirir el valor est a la izquierda. (La lnea en blanco que aparece despus printf ("Introduzca hasta %d lineas y las ordenare. \n", LIM);
printf("Para acabar, pulse [enter] al comienzo de linea. \n") ;
de la primera impresin de copia refleja el hecho de que los arrays de tipo while (gets(entra[ct]) != NULL && strcmp(entra[ct], PARA)
static se inicializan a 0, que son caracteres nulos en modo char.) != 0 && ct++ < LIM)
En esta funcin no es incumbencia del ordenador la preparacin de espa ptira[ct-1] = entra[ct-1]; /* apunta al array
sin ordenar */
cio para la copia en el array de destino; este detalle queda bajo su completa ordenatira(ptira, ct); /* clasificador tiras */
responsabilidad. Esta es la razn por la que hemos empleado la declaracin puts("\nAhi va la lista ordenada:\n");
for (k=0; k < ct; k++)
puts(ptira[k]); } /* tiras ordenadas */
static char copia[40];

y no /* clasificador de tiras */
ordenatira(tiras, num)
char *tiras[];
static char *copia; /* no asigna espacio para la tira */ int num;
{

381
char *temp;
int tope, busca;
f o r ( t o p e = 0 ; tope < num - 1; tope++)
for (busca = tope + 1; busca < num; busca++)
if (strcmp(tiras[tope] , tiras[busca] ) > 0)
{
temp = tiras[tope];
tiras[tope] = tiras[busca];
tiras[busca] = temp; }
}

F igu ra 13.4
Programa para lectura y ordenacin de tiras

Para probar nuestro programa, usaremos unos versos bastante ripiosos.


Introduzca hasta 20 lineas y las ordenare.
P a r a a c a b a r , p u l s e [ e n t e r ] a l comienzo de linea.
Y tambien en el trabajo
Los hay que pasan por caja
D ejan do so lo m iga ja
P orq ue se lle van e l gajo

A hi va la lis ta ordena da:

D ejan do so lo m iga ja
Los hay que pasan por caja
P orq ue se lle van e l gajo
Y tambien en el trabajo

Bien, parece que la ordenacin no ha empeorado ni mejorado la calidad


de la rima.
El truco que mencionbamos antes se refiere al uso de punteros; en lugar
de reordenar las tiras de caracteres, hemos cambiado la ordenacin de los mos que tenemos un programa en un fichero llamado rifa. La lnea de ejecu
punteros a esas tiras. Explicacin: al principio, ptira[0] apunta a entra[0], cin tendr un aspecto:
y as sucesivamente. Cada entra[] es un array de 81 elementos, y cada ptira[] % rifa
es una simple variable. El procedimiento de ordenacin reordena ptira, de
jando entra tal como est; si, por ejemplo, entra[1] debe aparecer antes que
entra[0], alfabticamente hablando, el programa conmuta su ptira, haciendo o quiz
que ptira[0] apunte a entra[l] y ptira[1] a entra[0]. Este sistema es ms senci
llo que usar, digamos, strcpy( ) para intercambiar los contenidos de las dos A> rifa

tiras entra. Vuelva a repasar la figura e intente seguir el proceso completo.


Como colofn de este captulo llenaremos un antiguo vaco de nuestras por nombrar dos sistemas comunes.
vidas, concretamente el contenido de los parntesis de main( ). Los argumentos en lnea de ejecucin son tems adicionales incluidos en
la misma lnea:
Argumentos en lneas de ejecucin % rifa -r Ton ic a

Atencin! Una lnea de ejecucin en la lnea que se teclea para ejecutar Un precioso detalle de la programacin en C es la posibilidad de leer es
su programa (as de fcil). Hasta ahora no hemos entrado en ello. Suponga
tos tems y emplearlos dentro del programa. El mecanismo es utilizar argu
mentos en main( ). Un ejemplo tpico sera:

383
mento de tipo int se suele llamar argc (del ingls argument count). El sistema
/* main() con argumentos */ utiliza los espacios en blanco para comprobar dnde acaba una tira y comienza
main(argc, argv) la siguiente. Por tanto, nuestro ejemplo eco tiene seis tiras de caracteres, mien
int argc;
char *argv[]; tras que el ejemplo anterior, rifa, tena nicamente dos. El segundo argu
{ mento de main( ) es un array de punteros a tira. Cada tira de la lnea de eje
int cont; cucin queda asignada a su propio puntero. Por convencin, se llama a este
for (cont = 1; cont < argc; cont++) array de puntero argv (del ingls argument values). En aquellos sistemas que
printf("%s ", argv[cont] ) ; es posible (algunos operativos no lo permiten), argv[0] queda asignado al pro
printf("\n"); pio nombre del programa. Por tanto, argv[l] se asigna a la siguiente tira,
}
etc. En nuestro ejemplo, tendremos:

argv[0] apunta a eco (en la mayoria de sistemas)


Coloque este programa en un fichero ejecutable llamado eco y observe
lo que sucede: argv[1] apunta a Puedo
argv[2] apunta a ser
A> eco Puedo ser de una gran ayuda. argv[6] apunta a ayuda.
Puedo ser de una gran ayuda.

Su situacin ms probable en este momento ser que ya comprende por


qu hemos llamado eco al programa, pero no se explica cmo funciona. Qui
z el prximo prrafo le sirva de ayuda (as lo esperamos).

Figura 13.6
Argumentos en lnea de ejecucin

Una vez hechas las presentaciones, ya puede identificar las variables que
utilizamos; el resto del programa debe ser muy fcil de seguir.
Muchos programadores emplean una declaracin diferente para argv:

main(argc, argv)
int argc;
char **argv;

Los compiladores C permiten a main( ) tener dos argumentos. El primer La declaracin de argv es realmente equivalente a char *argv[];. Se puede
argumento representa el nmero de tiras de caracteres que van a continua entender tambin diciendo que argv es un puntero a un puntero a char. Co
cin de la palabra de comando. Por tradicin (no por necesidad), este argu mo veremos con nuestro ejemplo, el resultado es el mismo; tenemos un array
con siete elementos; el nombre del array es un puntero al primer elemento;
por tanto, argv apunta a argv[0], y argv[0] es un puntero a char; por consi-

385
Hasta ahora hemos aprendido
guiente, incluso en la definicin original, argv es un puntero a un puntero
a char. Se puede usar cualquiera de las formas, aunque pensamos que la pri Cmo declarar una tira de caracteres: static, char, fun[ ], etc.
mera es ms clara en su significado. Cmo inicializar una tira de caracteres: static char *po = 0!
Un uso muy comn de los argumentos en lnea de ejecucin es la indica Cmo se emplea gets( ) y puts( )
cin de opciones dentro del programa. Por ejemplo, se pretende utilizar la Cmo se emplea strlen( ), strcmp( ), strcpy( ) y strcat( )
combinacin r para indicar a un programa de ordenacin que desea el or
Cmo se pueden usar argumentos en lnea de ejecucin
den inverso. Tradicionalmente, las opciones se suelen indicar usando un guin
y una letra, como r. Estos flags no significan nada en C; de hecho, de Que char *lisa y char lisa[ ] son parecidos, pero diferentes
ber usted incluir su propia programacin para reconocerlos. Cmo crear una tira constante: usando comillas
Presentamos seguidamente un ejemplo muy modesto demostrando cmo
puede un programa comprobar un flag y hacer uso de l.
Cuestiones y respuestas
/* un comienzo modesto */
#define SI 1 Cuestiones
#define NO 0
m a in (argc, argv) 1. Qu tipo de error se ha cometido en este intento de declaracin de una tira de
int aragc; caracteres?
char *argv[]; {

float array[100]; main()


int n; {
int marca = NO; char nombre[] = -{F, r, i, o};
if (argv[1][0] == '-' && argv[1][1] == 'r')
marca = SI;
2. Cul sera la salida de este programa?

. . . . . #ti nclude <st dio.h>


if marca = NO main()
ordena1(array,n); {
else static char not a[] = "Nos veremos en el examen. ";
char *ptr;
ordena2(array, n); . . . . . ptr = nota;
puts(ptr);
puts(++pt r);
nota[7] = \0 ;
puts(nota) ;
} puts(++pt r);
En este programa se comprueba si la primera tira de caracteres escrita tras }
la orden de ejecucin comienza con un guin. A continuacin se observa si
el siguiente carcter corresponde a un carcter de cdigo r. Si es as, se coloca
un flag, el cual, a su vez, hace que se acceda a una subrutina de ordena
cin diferente. Cualquier tira de caracteres tras la primera es ignorada (ya 3. Qu imprimira este programa?
dijimos que era un programa modesto).
Si se est usando el sistema UNIX, probablemente se habr observado
una gran variedad de opciones en lnea de ejecucin y de argumentos que main()
se ofrecen en este sistema operativo. Todos ellos son ejemplos de argumen {
tos C en lnea de ejecucin, ya que la mayora del propio UNIX est escrito static char comer[] = "Pipas";
en C. char *ptr;
Los argumentos en lnea de ejecucin pueden tambin ser nombres de fi ptr = comer + strlen(comer);
cheros, utilizables para dirigir all las entradas o salidas de su programa. Le while (-- ptr >= comer)
mostraremos cmo hacerlo en el captulo 15. puts(ptr);
}

387
4. Cul sera la salida de este programa?
main()
{
static char valle[30] = "ablan pero no es";
static char cita [40] = "No se de que h";
char *inclan ="toy de acuerdo.";
strcat(valle, inclan);
strcat(cita, valle);
puts(cita);
}
5. Disee una funcin que tome un puntero a tira como argumento y devuelva un
puntero al primer blanco que encuentre en dicha tira. Deber devolver un punte
ro NULL si no encuentra ningn blanco.
Respuestas
1. Se debe utilizar un modo de almacenamiento extern o static; dentro de la inicializacin
se debe incluir un \0.
2. Nos veremos en el examen,
os veremos en el examen.
Nos ve
s ve
3. s
as
pas
ipas
Pipas
4. No se de que hablan pero no estoy de acuerdo.
5. char *pblanco(tira)
char *tira;
{
while (*tira != ' ' && *tira != '\0')
tira++; /* se detiene al primer blanco o nulo */
if (*tira == '\0')
return(NULL); /* NULL = 0 */
else
return(tira) ;
}

Ejercicios
1. Disee una funcin que capture los n caracteres siguientes de la entrada, inclu
yendo blancos, tabulados y caracteres nueva lnea.
2. Modifique esta funcin de manera que se detenga tras n caracteres o tras el pri
mer blanco, tabulado o nueva lnea que aparezca, lo que suceda primero. (No
use simplemente scanf( ).)
3. Disee una funcin que capture la siguiente palabra de la entrada; defina una pa
labra como secuencia de caracteres sin blancos, tabulados o caracteres nueva l
nea.
4. Disee una funcin que explore la tira especificada hasta la primera aparicin de
un carcter determinado. La funcin tendr que devolver un puntero a dicho ca
rcter suponiendo que lo haya encontrado, y un NULL cuando el carcter desea
do no se encuentra en la tira.
14
Estructuras
de datos
y otras lindezas
En este captulo encontrar:

Problema ejemplo: creacin de un inventario de libros


Puesta a punto del patrn de la estructura
Definicin de variables de estructura
Inicializacin de una estructura
Cmo acceder a miembros de la estructura
Arrays de estructuras
Declaracin de un array de estructura
Identificacin de los miembros de un array de estructuras
Detalles del programa
Estructuras anidadas
Punteros a estructuras
Declaracin e inicializacin de un puntero estructura
Acceso a los miembros de la estructura por puntero
Cmo ensear estructuras a las funciones
Utilizacin de miembros de la estructura
Utilizacin de la direccin de la estructura
Utilizacin de un array
Y despus de las estructuras, qu?
Un vistazo rpido a las uniones
Otro vistazo a typedef
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

391
Estructuras de datos tructuras le parecern un viejo amigo. Estudiemos un ejemplo completo pa
ra comprobar la razn por la que una estructura puede ser necesaria, y cmo
crearla y utilizarla.

y otras lindezas problema ejemplo: creacin de un inventario de libros


La seorita Fina Buenaletra desea imprimir un inventario de sus libros.
CONCEPTOS Existe una cierta cantidad de informacin que le gustara colocar en cada li
bro: su ttulo, su autor, su editorial, su fecha de copyright, el nmero de
Estructuras de datos pginas, el nmero de copias y el precio; algunos de estos datos, como los
ttulos, podran almacenarse en un array de tiras de caracteres; otros necesi
Patrones, etiquetas y variables de estructuras
taran un array de tipo int o de tipo float. Si se construyen siete arrays dife
Acceso a las distintas partes de la estructura rentes, seguir la pista de cada uno de ellos puede llegar a ser bastante moles
Punteros a estructuras to, especialmente si, como la seorita Buenaletra desea, se deben sacar listas
Arrays de estructuras completas clasificadas por ttulo, autor, precio, etc. Una solucin mucho
Funciones y estructuras ms elegante sera el uso de un nico array, en el que cada elemento contu
Uniones viese toda la informacin del mismo libro.
Creacin de nuevos tipos Pero, qu formato de datos puede contener a la vez tiras y nmeros y,
de alguna forma, mantener la informacin separada? La respuesta, por su
PALABRAS CLAVE puesto, es el protagonista de este captulo: la estructura. Para ver cmo se
prepara y funciona una estructura, comenzaremos con un ejemplo bastante
struct, union, typedef limitado; con el fin de simplificar el problema, impondremos dos restriccio
nes: la primera, indicar nicamente ttulo, autor y precio; la segunda, limitar
OPERADORES el inventario a un solo libro. Si tiene ms de un libro, no se preocupe; ya
mostraremos despus cmo extender el programa.
>
Observaremos primero el listado del programa y su salida; luego estudia
remos por separado cada uno de sus puntos principales.
/* inventario de un solo libro */
#include <stdio.h>
#define MAXTIT 41 /* longitud maxima del titulo + 1 */
#define MAXAUT 31 /* longitud maxima del autor +1 */
struct biblio { /* nuestro primer patron de estructura:
la etiqueta es biblio*/
char titulo[MAXTIT]; /* tira de caracteres para titulo */
char autor[MAXAUT]; /* tira de caracteres para autor */
float precio; /* variable para precio del libro */
}; /* fin del patron de estructura */
main()

A menudo, el xito de un programa depende de un paso previo muy im {


portante: encontrar una forma adecuada de representar los datos con los que struct biblio libro; /* declara libro de tipo biblio */
el programa ha de trabajar. El lenguaje C es muy afortunado en este sentido printf("Introduzca titulo del libro.\n");
(y no por casualidad), ya que posee un medio muy potente de representar gets(libro.titulo); /* accede a la porcion titulo */
datos complejos. Este formato de datos, llamado estructura, no solamen printf("Introduzca ahora el autor.\n");
gets(libro.autor);
te es lo suficientemente flexible en su forma bsica como para representar printf("Ahora ponga el precio.\n");
multitud de datos distintos, sino que, adems, permite al usuario inventar scanf("%f", &libro.valor);
nuevos formatos. Si est familiarizado con los records del PASCAL, las es printf("%s por %s: %.2f pts.\n", libro.titulo,
libro.autor, libro.valor);
printf("%s: \"%s\" \(%.2f pts.\)\n", libro.autor,
libro.titulo, libro.valor) ; }
393
yendo otras estructuras. Por ltimo, tenemos un punto y coma que cierra
Un ejemplo de salida: la definicin del patrn.
Hemos colocado este patrn fuera de las funciones (externamente); tam
Introduzca titulo del libro. bin podramos haberlo definido en el interior de una funcin. En este se
La Cria del Cangrejo Malayo gundo caso, el patrn puede utilizarse nicamente dentro de dicha funcin.
Introduzca ahora el autor.
Federico Pastichet Cuando es externa, sin embargo, el patrn queda disponible a todas las fun
Ahora ponga el precio. ciones que haya a continuacin de la definicin en el programa; por ejem
527.50 plo, en una segunda funcin se podra haber definido
La Cria del Cangrejo Malayo por Federico Pastichet: 527.50 pts.
Federico Pastichet: "La Cria del Cangrejo Malayo" (527.50 pts.)
struct biblio cervantes;

La estructura que hemos creado tiene tres partes; en una se almacena el y tal funcin dispondra de una variable cervantes que tendra el mismo for
ttulo; en la segunda, el autor, y en la ltima, el precio. Hay tambin tres mato que el patrn enunciado.
detalles importantes a estudiar: Ya hemos dicho antes que el nombre-etiqueta es opcional, pero se ha de
emplear obligatoriamente cuando se preparan estructuras como la que he
1. Cmo preparar un formato o patrn (template) para la estructura.
2. Cmo declarar una variable que se ajuste a dicho patrn. mos puesto en el ejemplo, con el patrn definido en un lugar y las variables
3. Cmo acceder a los distintos componentes individuales de una varia en otro. Volveremos de nuevo a este punto; por el momento nos preocupare
ble de tipo estructura. mos de la definicin de variable estructura.

Puesta a punto del patrn de la estructura Definicin de variables de estructura


El patrn de la estructura es el plano maestro que describe, por as decir La palabra estructura se utiliza en dos sentidos. El primero es el senti
lo, la estructura de la estructura. Nuestro patrn tiene el siguiente aspecto: do patrn de estructura, que acabamos de discutir; el patrn es un plano
sin edificio; simplemente indica al compilador cmo hacer una cosa determi
struct biblio { nada, pero no le da los materiales para que el ordenador puede hacerla. La
char titulo [MAXTIT];
char autor[MAXAUT]; siguiente etapa es la creacin de una variable de estructura; es tambin
float precio; el segundo sentido de la palabra.
};
La lnea de nuestro programa que crea una variable de estructura es
Este patrn describe una estructura formada por dos arrays de caracteres struct biblio libro;
y una variable de tipo float. Pasemos a los detalles:
En primer lugar aparece la palabra clave struct; esta palabra identifica
a lo que viene a continuacin como una estructura. A continuacin viene una
etiqueta o rtulo (tag) opcional; en este caso la palabra biblio. Este nom
bre, biblio, es una etiqueta que nos permitir referirnos posteriormente a es
ta estructura de una manera abreviada. As, podemos presentar posterior
mente la declaracin
struct biblio libro;

en la que se declara libro como estructura de tipo biblio. Ms tarde aparece


la listas de miembros de la estructura, encerrados entre un par de llaves;
cada miembro queda descrito en su propia declaracin; por ejemplo, la por
cin ttulo es un array de char con MAXTIT elementos; los miembros pue
den ser de cualquiera de los tipos de datos que ya hemos mencionado, inclu
Figura 14.1
Disposicin de memoria para una estructura

395
Se puede hacer lo mismo con una variable estructura? S, siempre que
esta variable estructura sea externa o esttica. Lo que se debe tener en cuenta
Una vez recibida esta instruccin, el ordenador crea la variable libro. Siguiendo aqu es que, dependiendo de donde se defina la variable, no de donde est
los planos indicados en biblio, la mquina prepara espacio de almacenamiento definido el patrn, una variable estructura es o no externa. En nuestro ejem
para un array de char con MAXTIT elementos, un array de char de MA- plo, el patrn biblio es externo, pero la variable libro no lo es, ya que se ha
XAUT elementos y una variable float. Todo este espacio se coloca en el mis definido dentro de la funcin, y, por defecto, se le asigna una clase de alma
mo cesto bajo el nombre libro. (En la siguiente seccin indicaremos cmo cenamiento automtica. Supongamos, no obstante, que hemos hecho la si
sacar del cesto las variables por separado.)
guiente declaracin:
En nuestra declaracin, struct biblio juega el mismo papel que int o float
hacen en una declaracin normal. Por ejemplo, podemos declarar dos varia static struct biblio libro;
bles de tipo struct biblio, o incluso un puntero a esta clase de estructura:
struct biblio unamuno, baroja, *ptlibro; Con una clase de almacenamiento esttica, podemos inicializar la estruc
tura de la siguiente forma:
Las variables unamuno y baroja tendrn cada una de ellas su parte de
ttulo, autor y precio. Por su parte, el puntero ptlibro puede apuntar a una static struct biblio libro = {
muno, baroja o cualquier otra estructura biblio. "El Pirata y el Vaquero",
Por lo que respecta al ordenador, la sentencia "Rimsky Korsakoff",
125. 5
struct biblio libro; };

es una forma abreviada de escribir


struct biblio {
char titulo[MAXTIT]; Con el fin de hacer ms obvia las asociaciones, hemos colocado cada miem
char autor[MAXAUT]; bro en su propia lnea de inicializacin; tngase en cuenta, sin embargo, que
float precio;
} libro; /* une el nombre de la variable al patron */ lo nico que necesita el compilador son comas que separen la inicializacin
de un miembro del siguiente.
Dicho de otra forma, el proceso de definicin del patrn de estructura Una vez aclarado este punto, continuaremos nuestro recorrido por las pro
y de definicin de la variable de estructura pueden combinarse en una sola piedades de las estructuras.
etapa. La combinacin de definiciones de patrn y variables permite evitar,
si se desea, el uso del rtulo (tag):
Cmo acceder a miembros de la estructura
struct { /* sin etiqueta */
char titulo [MAXTIT]; Una estructura es una especie de superarray, en la cual un elemento pue
char autor[MAXAUT];
float precio; de ser de tipo char; el siguiente, float, y el siguiente, int. Hasta ahora hemos
} libro; accedido a los elementos individuales de un array utilizando un subndice.
Cmo podemos hacer lo mismo con los miembros de una estructura? Em
Por su parte, la forma con rtulo es mucho ms manejable si se va utili pleando un el operador de miembro de estructura. Por ejemplo, libro.pre-
zar el mismo patrn de estructura ms de una vez. cio es la porcin precio de libro. Se puede utilizar libro.precio exactamente
Existe un aspecto en la definicin de una variable estructura que no apa
rece en nuestro ejemplo: la inicializacin. Daremos un breve repaso a este igual que cualquier otra variable de tipo float; de igual forma se puede usar
punto. libro.ttulo de manera idntica a un array de char normal. As, podemos em
plear expresiones como
Inicializacin de una estructura
gets(libro.titulo);
Ya vimos con anterioridad cmo inicializar variables y arrays
int cont = 0; y
static int piso[] = {0, 1, 1, 2, 3, 5, 8};
scanf("%f", &libro.precio);

397
{
printf("Introduzca ahora el autor.\n");
En esencia, .ttulo, .autor y .precio juegan el papel de subndices en la gets(libro[cont].autor);
printf("Ahora ponga el precio. \n");
estructura biblio. scanf("%f", &libro[cont++].valor) ;
Si tenemos una segunda variable de estructura del mismo tipo, se puede while (getchar() != '\n'); /* limpia linea entrada */
emplear el mismo sistema: if (cont < MAXLIB)
printf("Introduzca el siguiente titulo.\n");
struct libro historia, geografia;
}
printf("Ahi va su lista de libros:\n");
for (indice = 0; indice < cont; indice++)
gets(historia.titulo);
gets(geografa.titulo); printf("%s por %s: %. 2f pts. \n", libro[indice].titulo,
libro[indice].autor, libro[indice].valor);
}
El .ttulo se refiere siempre al miembro de la estructura biblio. Figura 14.2
Observe que en nuestro programa inicial hemos impreso el contenido de Programa de inventario de libros
la estructura libro en dos formatos diferentes; queramos ilustrar con ello la
libertad de que se dispone en la utilizacin de miembros de la estructura.
Con esto terminamos la parte bsica. Ahora ampliaremos nuestro terri
torio de caza estudiando algunas ramificaciones del problema central de las Un ejemplo de salida sera
estructuras, incluyendo array de estructuras, estructuras de estructuras, pun
teros y estructuras, y funciones y estructuras. Introduzca el titulo del libro.
Pulse [enter] a comienzo de linea para parar.
Mi Vida en la Antartida
Arrays de estructuras Introduzca ahora el autor.
Ramon Caluroso
Ahora ponga el precio
Por de pronto, arreglaremos nuestro programa de libros para que se ajusten 333.0
a las necesidades de los que poseen dos o tres libros (los hay que incluso Introduzca el siguiente titulo.
tienen ms!). Evidentemente, cada libro puede describirse por medio de una . . . mas entradas . . .
Ahi va su lista de libros:
variable de estructura del tipo biblio; si queremos describir dos biblios necesi Mi Vida en la Antartida por Ramon Caluroso: 333.00 pts.
taremos utilizar dos variables de este tipo, etc.; si queremos trabajar con va Razon y Sinrazon por Aristoteles Wodow: 1125.00 pts.
rios o muchos libros necesitaremos, sin duda, un array de estructuras como Los Animales Racionales por Elena Feminiskaya: 380.50 pts.
la original, que es lo que hemos creado en el programa presentado en la figu Aerobic a todas horas por Juan Deltoides: 1500.50 pts.
Sistema Operativo UNIX por Waite, Martin y Prata: 2300.00 pts.
ra 14.2. Como hacer Copias Piratas por Mr. Xerox: 500.00 pts.
Sensualidad y Filosofia por Nadia Limonskowska: 1000.25 pts.
/* inventario de varios libros */ El Destino va en Bikini por Anselmo Chapuzon: 895.00 pts.
#include <stdio.h> La Historia de Tirania por Waldo Astoriampfz: 9995.00 pts.
#define MAXTIT 40 Domine su Reloj Digital por Yaguchi Kamamoto: 1350.00 pts.
#define MAXAUT 40
#define MAXLIB 100 /* numero maximo de libros */
#define STOP "" /* tira nula, finaliza entrada */
struct biblio { /* prepara patron estructura */
char titulo[MAXTIT]; Los dos puntos ms importantes a observar en un array de estructuras
char autor[MAXAUT]; son: cmo han de declararse y cmo se puede acceder a los miembros indivi
float valor;
}; duales; una vez explicados estos detalles, volveremos a considerar el progra
main() ma para fijarnos en algunos de sus aspectos ms interesantes.
{
struct biblio libro[MAXLIB] ; /* array de estructuras biblio */
int cont = 0; Declaracin de un array de estructuras
int indice;
El proceso de declaracin de un array de estructura es completamente an
printf("Introduzca titulo del libro.\n"); logo al de cualquier otro tipo de array:
printf("Pulse [enter] a comienzo de linea para parar.\n") ;
while (strcmp(gets(libro[cont].titulo),STOP) != 0 &&
cont < MAXLIB) struct biblio libro[MAXLIB];

399
Y ya que estamos con esto, intente imaginar cul es el valor de
Con ello se declara libro como un array de MAXLIB elementos. Cada ele
mento del array es una estructura del tipo biblio; por tanto, libro[0] es una libro[2].titulo[4]
estructura del tipo biblio, libro [1] una segunda estructura, etc. La figura 14.3
puede ayudarle a visualizar la idea. El nombre libro per se no es un nombre sera el quinto elemento del ttulo (es decir, el indicado por ttulo[4]) del libro
de estructura: es el nombre del array que alberga las estructuras. descrito en la tercera estructura (por aquello de ser libro[2]). En nuestro ejemplo
es concretamente el carcter A. Creemos que con esto queda claro que los
subndices escritos a la derecha del operador se aplican a los miembros
individuales, en tanto que los subndices a la izquierda del operador se apli
can al array de estructura.
Volvamos ahora con el programa.

Detalles del programa


La alteracin principal que hemos introducido con respecto al primer pro
grama es la instalacin de un bucle para leer libros sucesivamente. Comenza
mos el bucle con esta condicin while:

while (strcmp<gets(libro[cont]. titulo),STOP) != 0 &&


cont < MAXLIB)

La expresin gets(libro[count].ttulo) lee una tira de entrada como ttulo


del libro. La funcin strcmp( ) compara esta tira de caracteres con STOP,
que es simplemente una tira vaca, "", como ya habamos utilizado antes.
F igu ra 14.3 Si el usuario pulsa [enter] al comienzo de una lnea, se transmite la tira vaca
Un array de estructuras y finaliza el bucle. Disponemos tambin de un control para mantener el n
mero de libros ledos por debajo del lmite de tamao del array.
A continuacin aparece una sentencia extraa:
Identificacin de los miembros en un array de estructuras
while (getchar() = '\n'); /* limpia linea entrada */
Para identificar los miembros de un array de estructuras se aplica la mis
ma regla que empleamos para estructuras individuales: se escribe el nombre
de la estructura seguido del operador miembro y el nombre del miembro: La razn de esta sentencia obedece a una peculiaridad de scanf( ). La fun
cin scanf( ) ignora espacios y caracteres nueva lnea; as, cuando se respon
Libro[0]].precio es el precio asociado con el primer elemento del array de a la pregunta del precio del libro, si se teclea algo como

Libro[4].ttulo es el ttulo asociado con el quinto elemento del array 12.50[enter]

Observe que el subndice del array est unido a libro, no al final del nom Se transmitir a la secuencia de caracteres
bre:
12.50\n
libro.precio[2] /* INCORRECTO */
libro[2].precio /* CORRECTO */ La funcin scanf( ) recoge el 1, el 2, el ., el 5 y el 0 pero deja all el \ n
esperando a la prxima sentencia de lectura. Si no hubisemos incluido nues
Utilizamos libro[2].precio porque libro[2] es precisamente el nombre tra lnea extraa, la prxima sentencia de lectura sera gets(libro[cont.].ttu
de la variante de estructura, al igual que libro[l] es otro nombre de variable lo) en la sentencia de control del bucle; por consiguiente, leeramos el carc
de estructura, como antes lo fue Cervantes. ter nueva lnea abandonado como primer carcter, y el programa pensara
que hemos enviado una seal de stop. Esta es la razn de nuestra extraa

401
La salida sera:
sentencia. Si la observa con cuidado, comprender que devora caracteres hasta Querido Pepe,
que encuentra y elimina el carcter nueva lnea; por lo dems, no realiza nin
guna funcin con la excepcin de eliminar dicho carcter de la cola de entra Gracias por esa tarde maravillosa, Pepe.
Me has demostrado que realmente un sexador de pollos
da. Con ello se permite que gets( ) disponga de un comienzo fresco. no es una persona corriente, A ver si quedamos
Para cerrar el crculo, nos dedicaremos ahora de nuevo a explorar las po frente a un delicioso plato de alcachofas
sibilidades de las estructuras. y pasamos otra buena velada.
Hasta pronto,
Juanita
Estructuras anidadas
La primera observacin a realizar es la forma en que la estructura anida
A veces es conveniente disponer de estructuras contenidas o anidadas da se coloca en el patrn. Simplemente se declara, al igual que se hara con
en otras. Por ejemplo, Juanita Muchamarcha est preparando una estructu una variable int:
ra que contiene informacin sobre sus amigos. Uno de los miembros de la
estructura, naturalmente, es el nombre de su amigo; sin embargo, el nombre struct nombres maneja;
puede quedar representado por s mismo en una estructura en la que se dis
ponga de entradas separadas para el nombre y el apellido. La figura 14.4 es Con ello se indica que maneja es una variable del tipo struct nombres.
un ejemplo resumido del trabajo de Juanita.
Por supuesto, el fichero debe incluir tambin el patrn correspondiente de
la estructura nombres.
/* ejemplo de estructura anidada */
#define LEN 20 El segundo punto a anotar es cmo conseguir acceder a los miembros de
#define M1 " Gracias por esa tarde maravillosa, " una estructura anidada. Sencillamente se utiliza el operador dos veces:
#define M2 "Me has demostrado que realmente un "
#define M3 "no es una persona corriente, A ver si quedamos" feten.maneja.nom == "Pepe"
#define M4 "frente a un delicioso plato de "
#define M5 " y pasamos otra buena velada.
struct nombres { / * primer patron de estructura */
La construccin se interpreta de la siguiente forma, yendo de izquierda
char nom[LEN];
char apell[LEN]; a derecha:
};
(feten.maneja).nom
struct tio { /* segundo patron */
struct nombres maneja; /* estructura anidada */
char comifavo[LEN] ; es decir, primero se busca fetn; a continuacin, el miembro maneja de fe
char trabajo[LEN];
float gana; tn, y despus, el miembro nom de este ltimo.
}; Para el prximo acto presentaremos la actuacin de nuestros famosos ar
main()
{ tistas los punteros.
static struct tio feten = { /* inicializa variable */
{"Pepe", "Gafe"},
"alcachofas",
"sexador de pollos", Punteros a estructuras
3535000.00
}; Los forofos de los punteros se alegrarn de saber que se pueden utilizar
printf("Querido %s, \n\n, feten. maneja.nom) ; punteros a estructuras. La noticia es buena, al menos por tres razones: pri
printf( "%s %s. \n", M1, feten. maneja.nom) ;
printf( %s %s\n", M2, feten.trabajo) ; mera, al igual que suceda con los punteros a arrays, que resultaban ms f
printf("%s\n", M3) ; ciles de manipular (en un problema de ordenacin, por ejemplo) que los pro
printf( "%s %s\n%s\n\n', M4, feten.comifavo, M5) ; pios arrays, los punteros a estructuras son ms sencillos de manejar que las
printf("%40s%s\n", " ", "Hasta pronto,");
printf("%40s%s\n", " ", "Juanita"); estructuras en s mismas; segunda, una estructura no puede pasarse como ar
} gumento a una funcin, pero el puntero a estructura s puede; tercera, exis
ten muchas representaciones de datos realmente elaboradas que son estruc
Figura 14.4 turas que contienen punteros a otras estructuras.
Programa con estructuras anidadas

403
Declaracin e inicializacin de un puntero a estructura
En el siguiente ejemplo (figura 14.5) se muestra cmo definir un puntero La declaracin no puede ser ms fcil:
a una estructura y cmo utilizarlo para acceder a los miembros de la estruc struct tio *este;
tura.
/* puntero a estructura */ En primer lugar se coloca la palabra clave struct; despus, la etiqueta del
#define LEN 20 patrn to y un * seguido del nombre del puntero. La sintaxis es la misma
struct nombres { que la utilizada en otras declaraciones de punteros ya vistas.
char nom[LEN];
char apelI[LEN]; El puntero este se puede preparar para que apunte a cualquier estructura
}; del tipo to. Inicializamos este hacindolo apuntar a fetn[0]; obsrvese que
struct tio { hemos empleado el operador direccin:
struct nombres maneja; /* estructura anidada */
char comifavo[LEN]; este = &feten[0];
char trabajo[LEN];
float gana;
}; Las dos primeras lneas de salida muestran que esta asignacin ha sido
main()
{ correcta. Comparando las dos lneas, observamos que este apunta a fetn[0]
static struct tio feten[2] = { y este + 1 apunta a fetn[1]. Observe que sumando 1 a este se suman 84 a
{ {"Pepe", "Gafe"}, la direccin. Se debe a que cada estructura to ocupa 84 bytes de memoria:
"alcachofas",
"sexador de pollos", 20 para el nombre, 20 para el apellido, 20 para comifavo, 20 para trabajo
3535000.00 y 4 para gana, que es el tamao de un float en nuestro sistema.
},
{ {"Santi", "Fever"},
"salmon ahumado", Acceso a miembros por puntero
"programador",
9999995. 00 Tenemos a este apuntando a la estructura fetn[0]. Cmo podemos ha
} cer que este consiga extraer un valor de un miembro de fetn[0]? La tercera
};
lnea de salida muestra dos mtodos para hacerlo.
struct tio *este; /* AQUI ESTA: puntero a estructura */ El primer mtodo, el ms comn, utiliza un nuevo operador, > . Este
printf("direccion 1: %u; 2 : %u\n", &feten[1], &feten[2] ); operador se forma tecleando un guin (-) seguido del smbolo mayor que
este = &feten[0] ; /* indica al puntero donde apuntar */ ( > ) . El ejemplo siguiente le ayudar a aclarar ideas:
printf("puntero i: %u; 2: %u\n", este, este + 1);
printf ("este->gana vale %.2f: (*este).gana vale %.2f\n", este-> gana es feten[0].gana cuando este = &feten[0]
este->gana, (*este).gana );
este++ / * apunta a la siguiente estructura * /
printf("este->comifavo es %s: este->nombres.apell es %s\n", Dicho de otra manera, un puntero a estructura seguido del operador ->
este->comifavo, este->maneja.apell);
} funciona exactamente igual que un nombre de estructura seguido del opera
dor (No podemos referirnos con propiedad a este.gana porque este no
Figura 14.5 es un nombre de estructura.)
Programa con un puntero a una estructura
Es importante sealar que este es un puntero, pero este-> gana es un
miembro de la estructura apuntada. En este caso, por tanto, este-> gana
Ya que insisten, les mostraremos la salida: es simplemente una variable del tipo float.
direccion 1:12; 2 : 96
El segundo mtodo para especificar el valor de un miembro de una es
puntero 1: 12; 2 : 96 tructura obedece a la siguiente secuencia: si este == &fetn[0], se cumplir
>gana vale 3535000.00: (*este).gana vale 3535000.00 que *este = = feten[0]. Se debe a que & y * son dos operadores recprocos.
este > comifavo es salmon ahumado: este-> nombres.apell es Fever
Por tanto,
Observemos primero cmo se crea un puntero a la estructura to; despus feten[0].gana == (*este).gana
nos dedicaremos a estudiar cmo se especifican los miembros individuales
de una estructura por medio del puntero. por sustitucin. Es necesario el uso de parntesis porque el operador tie
ne mayor preferencia que el operador *.

405
En resumen, si definimos este como puntero a la estructura fetn[0], se
Cmo ensear estructuras a las funciones
cumple la siguiente equivalencia
Recordemos que los argumentos de una funcin pasan valores a dicha
feten[0].gana == (*este).gana == este->gana funcin. Cada valor es un nmero, sea de tipo int, de tipo float, o quiz
Nos dedicaremos a continuacin al problema de la interaccin entre es un cdigo ASCII o una direccin. Las estructuras son algo ms complicadas
tructuras y funciones. que un valor sencillo, por lo que no resulta sorprendente que una estructura
como tal se pueda enviar como argumento a una funcin. (Por cierto, esta
limitacin est siendo eliminada en las ltimas implementaciones.) Sin em
RESUMEN: OPERADORES DE ESTRUCTURAS Y UNIONES bargo, existen formas de enviar informacin acerca de la estructura a una
funcin. Estudiaremos tres mtodos (en realidad, dos con variaciones) en es
te prrafo.
I. El operador de pertenencia:
Este operador se emplea, junto con un nombre de unin o estructura, para
especificar un miembro de las mismas. Si nombre es el nombre de una estruc Utilizacin de los miembros de la estructura
tura, y miembro, un miembro especificado por el patrn de estructura, Al ser los miembros de la estructura variables simples (por ejemplo, un
nombre.miembro int o alguno de sus parientes, un char, un float, un double o un puntero),
se pueden utilizar como argumentos de funciones. El listado de la figura 14.6
identifica a dicho miembro de la estructura. Este operador de pertenencia pue
de usarse de igual manera con uniones. es un programa de anlisis financiero bastante primitivo, el cual suma las can
tidades depositadas por un cliente en su cuenta corriente y libreta de ahorro.
Ejemplo: De paso, observe que hemos combinado la definicin del patrn, la declara
struct { cin de variables y la inicializacin en una sola sentencia.
int codigo;
float precio;
} articulo;
/* paso de miembros de estructura a una funcion */
articulo.codigo = 1265; struct fondos {
char *banco;
Esta operacin asignara un valor al miembro cdigo de la estructura artculo. float ccorri;
II. El operador de pertenencia indirecto, > : char *ahorro;
Este operador se usa con un puntero a estructura, o a unin, para iden float cahorro;
tificar un miembro de la misma. Supongamos que ptrstr es un puntero } garcia = {
"Banco Pacifico",
a estructura, y que miembro es un miembro especificado de aqulla por 102343.25,
el patrn correspondiente. En tal caso "Banco de Poniente",
423987.21
ptrstr->miembro
};
identifica a dicho miembro de la estructura apuntada. Este operador main()
de pertenencia indirecta se puede emplear de la misma forma con uniones. {
float total, suma() ;
Ejemplo: extern struct fondos garcia; /* declaracion opcional */
struct { printf("Garcia tiene un total de %2f pts. \n",
int codigo; suma(garcia.ccorri, garcia.cahorro) );
float precio; }
} articulo, *ptrst;
ptrst = &articulo; /* funcion que suma dos numeros float */
ptrst->codigo = 3451; float suma(x,y)
float x,y;
Con ello se asignara un valor al miembro cdigo de artculo. Las tres return(x + y);
expresiones siguientes son equivalentes }
ptrst->codigo articulo.codigo (*ptrst).codigo
F igu ra 14.6
Programa que pasa miembros de estructura como argumento de funcin

407
El resultado de la ejecucin de este programa es
Garcia tiene un total de 526330.46 pts.

Funciona! Observe que la funcin sum( ) no sabe ni se preocupa si los


argumentos enviados son miembros de una estructura o no; simplemente re
quiere que sean del tipo float.
Por supuesto, si deseamos que un programa modifique el valor de un miem
bro del programa de llamada, podemos enviar la direccin de dicho miem
bro:
modifica(&garcia.cahorro);

podra muy bien ser una funcin que modificase el estado de la cuenta del
seor Garca.
El siguiente modus operandi en interacciones entre funciones y estructu
ras implica hacer saber a la funcin que est tratando con una estructura.
Utilizacin de la direccin de la estructura
Vamos a resolver el mismo problema que antes, slo que esta vez utiliza
remos la direccin de la estructura como argumento. Este sistema es correc
to, ya que la direccin es un simple nmero; sin embargo, la funcin tendr
que trabajar con la estructura fondos; por tanto, deber hacer uso del pa Tambin aqu se produce una salida idntica
trn fondos tambin. En la figura 14.7 se presenta el programa.
Garcia tiene un total de 526330.46 pts.
/* paso de la direccion de la estructura a una funcion */
struct fondos { La funcin suma( ) tiene un puntero (dinero) a una estructura fondos.
char *banco;
float ccorri; Al pasar la direccin &garca a la funcin se consigue que el puntero dinero
char *ahorro; apunte a la estructura garca. Podemos entonces utilizar el operador -> para
float cahorro;
} garcia = { acceder a los valores garca.cahorro y garca.ccorri.
"Banco Pacifico", Esta funcin podra tambin acceder al propio nombre del banco, aun
102343.25, que no lo utiliza.
"Banco de Poniente",
423987.21, Observe que debemos emplear el operador & para indicar la direccin de
}; la estructura. A diferencia del nombre del array, el nombre de la estructura
main() en solitario no es un sinnimo de su direccin.
{
float total, suma(); El siguiente mtodo se aplica a un array de estructuras, y es una variacin
del que acabamos de exponer.
printf( "Garcia tiene un total de %.2f pts. \n, suma(&garcia));
}
Utilizacin de un array
float suma(dinero)
struct fondos *dinero; Supongamos que tenemos un array de estructuras. El nombre de un array
{ es un sinnimo de su direccin; por tanto, puede pasarse a una funcin. La
return(dinero->ccorri + dinero->cahorro);
} funcin necesitar, de nuevo, poder acceder al patrn de estructura. Para
demostrar el funcionamiento de este sistema (figura 14.8), expandiremos nues
Figura 14.7 tro programa a dos personas, por lo que, en total, tendremos un array de
Programa que utiliza la direccin de la estructura en una funcin dos estructuras fondos.

409
/* paso de un array de estructuras a una funcion */
struct fondos { cin, el bucle for incrementa el puntero dinero en 1; ahora apunta a la si
char *banco; guiente estructura, garcias[1], posibilitando que se sumen el resto de cantida
float ccorri; des al total.
char *ahorro;
float cahorro; En este ejemplo hay dos detalles a destacar:
} garcias[2 = {
{ 1. Se puede usar el nombre del array para pasar a la funcin un puntero
"Banco Pacifico",
102343.25, apuntando a la primera estructura del propio array.
"Banco de Poniente". 2. A continuacin podemos emplear un puntero aritmtico para mover
423987.21 el puntero anterior a las dems estructuras del array. Observe que la
},
{ llamada a funcin
"Banca La Honradez",
97656.50, suma(&garciast[0]);
"Banco Cantonal",
176013.04
} habra tenido el mismo efecto que si hubisemos empleado el nombre
}; del array, ya que ambos se refieren a la misma direccin. La utiliza
main() cin del nombre del array es simplemente un mtodo indirecto de pa
{ sar la direccin de la estructura.
float total, suma();
printf("Los garcia tienen un total de .2f pts.\n",
}
suma(garcias) ); Y despus de las estructuras, qu?
float suma(dinero) No queremos extendernos ms en la explicacin de estructuras; sin em
struct fondos *dinero; bargo, debemos mencionar uno de los usos ms importantes de las mismas:
{
float total; la creacin de nuevos formatos de datos. Los usuarios de ordenadores han
int i ; creado formatos de datos que, para ciertos problemas, resultan mucho ms
for (i=0, total = 0; i < 2; i++, dinero++) eficientes que los arrays y estructuras simples que hemos presentado aqu.
total += dinero->ccorri + dinero-> cahorro; Estos formatos tienen nombres tales como colas, rboles binarios, pilas, ta
return(total); blas y grficos. Muchos de ellos se construyen a partir de estructuras enca
denadas. Como caso tpico, cada estructura contiene uno o dos datos y,
Figura 14.8 adems, uno o un par de punteros que apuntan a otras estructuras del mismo
Programa que enva un array de estructuras a una funcin tipo. Los punteros sirven para encadenar una estructura a la siguiente y, al

La salida:
Los Garcia tienen un total de 800000.00 pts.

(Nos ha cuadrado la suma! Y encima es un nmero redondo! Cualquie


ra pensara que nos hemos inventado los nmeros.)
El nombre del array garcias es un puntero al array. En concreto, apunta
al primer elemento del array, que es la estructura garcias[0]. As, el puntero
dinero est definido inicialmente como

dine ro = & garcia s[0];

El empleo del operador > nos permite sumar las dos cantidades del
primer Garca. Hasta aqu es muy parecido al ejemplo anterior. A continua Figura 14.9
Una estructura en rbol binario

411
La primera declaracin crea una variable nica llamada ficha. El compi
lador prepara espacio suficiente para almacenar la mayor de las posibilida
tiempo, para fabricar un camino que permita un rastreo por la estructura glo des descritas. En nuestro ejemplo, la mayor posibilidad entre las especifica
bal. Por ejemplo, la figura 14.9 muestra una estructura en rbol binario, en das es double, que necesita 64 bits, u 8 bytes, en nuestro sistema. El array
la que cada estructura individual (o nodo) se conecta con dos situadas por guarda tendr 10 elementos, cada uno de 8 bytes.
debajo de la misma. Las uniones se emplean de la siguiente manera:
Pero es que este engendro ramificado es ms eficiente que un array? Bien,
consideremos el caso de un rbol con 10 niveles de nodos. Si lo observa con ficha.numero = 23; /* se guarda 23 en ficha usando 2 bytes */
atencin, encontrar que existen 1023 nodos, en los cuales se pueden almace ficha.grande = 2.0; /* borra 23; guarda 2.0 usando 8 bytes */
ficha.letra = 'h'; /* borra 2.0; guarda h usando 1 byte */
nar, por ejemplo, 1023 palabras. Si las palabras se organizan siguiendo una
pauta coherente, se puede comenzar en el nivel superior y encontrar cualquier
palabra en 9 movimientos como mximo, segn se va bajando de un nivel Para especificar el tipo de datos que se est utilizando, se usa el operador
al siguiente. Si tuviese las palabras colocadas en un array, podra, en caso de pertenencia. Slo se guarda un valor en cada momento; no se pueden al
de desgracia extrema, tener que rastrear los 1023 elementos antes de tropezar macenar un char y un int a la vez, incluso cuando, como en este caso, hay
con la palabra deseada. espacio suficiente para ello.
Si est interesado en estructuras de datos avanzadas, le aconsejamos que Queda bajo su responsabilidad llevar la cuenta del tipo de datos que se
consulte un libro de Ciencias del Cmputo. Las estructuras del C le permiti est usando en la unin; la secuencia siguiente muestra lo que no se debe hacer:
rn reproducir los formatos que all encuentre.
ficha.letra = 'A';
Aqu ponemos punto final a las estructuras. Seguidamente, daremos un flnum = 3.02 * ficha.grande; /* ERROR ERROR ERROR */
somero repaso a otros dos modos de tratamientos de datos en C: las uniones
y typedef.
Este fragmento de programa sera errneo, porque se ha almacenado un
tipo char, mientras que en la siguiente lnea se presupone que el contenido
de ficha es del tipo double.
Un vistazo rpido a las uniones Se puede emplear el operador - > con uniones de igual manera a como
se haca con las estructuras:
Una unin es un instrumento que permite almacenar tipos de datos dife
rentes en el mismo espacio de memoria. Una utilidad tpica de tal sistema pu = & ficha;
x = pu->numero; /* equivale a x = ficha.numero */
sera la creacin de una tabla que pudiese guardar una mezcla de tipos en
un orden arbitrario, que en principio no va a ser regular ni conocido. La unin
permite la creacin de un array de unidades del mismo tamao, en las que Estudiemos ahora otro aspecto de la organizacin avanzada de datos en C.
cada una de ellas contiene datos de un tipo distinto.
Las uniones se preparan de una forma muy semejante a las estructuras.
Existe el patrn de la unin y las variables de la misma. Se definen de una Otro vistazo a typedef
sola vez, o bien utilizando una etiqueta de unin.
El siguiente es un ejemplo de patrn con etiqueta: La palabra typedef permite crear un tipo con un nombre arbitrario otor
gado por el usuario. Se parece bastante a #define en este aspecto; sin embar
union agarra { go, tiene tres diferencias:
int numero;
double grande;
char letra; 1. Al contrario que #define, typedef est limitado a otorgar nombres sim
}; blicos a tipos de datos nicamente.
2. La funcin typedef se ejecuta por compilador, no por preprocesador.
3. Dentro de sus lmites, typedef es ms flexible que #define.
La definicin de variables de una unin de tipo agarra sera:
Veamos cmo funciona. Supongamos que desamos usar la palabra real
union agarra ficha; /* variable union de tipo agarra */ en lugar de float. Para ello se define real como si fuese una variable float,
union agarra guarda[10]; /* array de 10 variables union */ y se antecede la definicin con la palabra clave typedef:
union agarra *coloca /* puntero a una variable agarra */
typedef float real;

413
hace que FRPTC sea un tipo en el que una funcin devuelve un puntero a
A partir de este momento, se puede usar real para definir variables: un array de char de cinco elementos. (Vea en el cuadro hasta dnde puede
llegar nuestra caja de sorpresas de declaraciones.)
real x, y[25], *pr; Un tercer motivo para el uso de typedef es hacer los programas ms trans
portables. Por ejemplo, supongamos que su programa necesita usar nme
El alcance de esta definicin depende de la localizacin de la sentencia ros de 16 bits. En algunos sistemas esto significara un tipo short; en otros
typedef. Si la definicin se realiza dentro de una funcin, el alcance queda puede ser tipo int. Si se usara simplemente short o int en las declaraciones,
confinado a la misma. Si la definicin es externa a la funcin, el alcance es habra que alterar todas ellas para cambiarse de un sistema a otro; en su lu
global. gar, haga lo siguiente: en un fichero #include introduzca la siguiente defini
A menudo, se emplean letras maysculas para estas definiciones, con el cin
fin de recordar al usuario que el nombre del tipo es en realidad una abrevia
tura simblica: typedef short DOSBYTES;

typedef float REAL;


Ahora podr usar DOSBYTES en el programa que define las variables short
de 16 bits. Cuando cambie el programa al otro sistema, en el que se necesita
Este ltimo ejemplo podra haberse realizado de igual forma con un tipo int en su lugar, simplemente cambie la definicin del fichero #include:
#define. Sin embargo, el que presentamos a continuacin no puede hacerse
as: typedef int DOSBYTES;
typedef char *STRING;
Este es un ejemplo ms de las caractersticas del C como lenguaje trans
Sin la palabra typedef, STRING quedara identificado como un puntero portable.
a char. Cuando se incluye la palabra, se hace que STRING sea un identifica- Cuando se use typedef, hay que tener presente que no se estn creando
dor de punteros a char. Por tanto, tipos nuevos, sino simplemente utilizando etiquetas cmodas.

STRING nombre, signo;

DECLARACIONES CURIOSAS
significa
char *nombre, *signo; El C permite la creacin de formas de datos muy elaboradas. Estamos
tratando simplemente las formas ms sencillas, pero creemos nuestro deber
Tambin se puede usar typedef con estructuras; por ejemplo: informarle del cmulo de posibilidades que se presentan. Cuando se hace
una declaracin, el nombre (o identificador) que usamos se puede modi
typedef struct COMPLEX { ficar aadindole un modificador:
float real;
float imag; Modificador Significado
};

* indica un puntero
permite usar el tipo COMPLEX para representar nmeros complejos. () indica una funcin
Una de las razones que aconseja el empleo de typedef es que aqu se pue [] indica un array
den crear nombres convenientes y reconocibles para los tipos que se utilizan
ms a menudo. Por ejemplo, muchos usuarios (quiz expertos en otros len En C se puede emplear ms de un modificador al mismo tiempo, lo que
guajes) prefieren usar STRING o su equivalente, tal como hicimos anterior permite crear una gran variedad de tipos:
mente.
Adems, los nombres typedef se usan frecuentemente para describir ti int tabla[8][8];/* un array be arrays de int */
int **ptr; /* un puntero a un puntero a int*/
pos complicados; por ejemplo, la declaracin int *rico[10]; /* un array be 10 punteros a int*/
int (*pico)[10];/* un puntero a array de 10 int*/
typedef char *FRPTC() [5]; int *oof[3][4]; /* un array de 3 punteros a array
de 4 int */
int (*uuf)[3][4]; /* un puntero a array de 3*4 int */

415
Cmo colocar un puntero a una estructura: struct coche *ptcoche;
La clave para desentraar estas declaraciones es averiguar el orden en Cmo acceder a un miembro utilizando un puntero: ptcoche>kph
que se aplican los modificadores. Para ello se siguen tres reglas. Cmo enviar un miembro a una funcin: eval(ferrari.kph)
Cmo hacer que una funcin conozca la existencia de una estructura:
1. La prioridad de un modificador es tanto mayor cuanto ms prximo veloc(&ferrari)
est el identificador. Cmo construir una estructura anidada
2. Los modificadores [ ] y ( ) tienen mayor prioridad que *.
3. Se pueden usar parntesis para agrupar parte de la expresin otorgn Cmo acceder a un miembro anidado de la estructura: ferrari.tasas.municip
dole la mxima prioridad. Cmo construir y usar arrays de estructuras: struct coche gm[5]
Cmo preparar una unin: igual que una estructura
Apliquemos estas reglas al ejemplo Cmo usar typedef: typedef struct coche BOLIDO;
int *oof[3][4] ;

El * y el [3] son adyacentes a oof, y tienen mayor prioridad que [4] (regla
Cuestiones y respuestas
1). El [3] tiene mayor prioridad que el * (regla 2). Por consiguiente, oof
es un array de tres elementos (primer modificador) de punteros (segundo Cuestiones
modificador) a un array de cuatro elementos (tercer modificador) de tipo 1. Qu errores contiene este patrn?
int (el tipo declarado).
En el caso structure {
char cuteria;
int (*uuf) [3][4] ; int num[20];
char *todo
los parntesis hacen que el modificador * tenga mayor prioridad, por lo que }
uuf es un puntero, tal como se indic en la declaracin correspondiente. 2. Esta porcin de programa, qu imprimira?
Estas reglas permiten tambin los siguientes tipos:
struct casa {
char *fump(); /* funcion que devuelve un puntero a char */ float superf;
char (*frump) (); / * puntero a una funcion que devuelve un int habitac;
tipo char */ int plantas;
char *flump()[3] ; /* funcin que devuelve un puntero a array char *direc;
de 3 elementos ce tipo char */ };
char *flimp[3]() ; /* array de 3 punteros a funcion que main()
devuelve tipo char */
{
static struct casa chalet = { 156.0, 6, 1,
"calle Barco 86"};
Si, adems, metemos estructuras en la coctelera, las posibilidades de de struct casa *signo;
claraciones llegan a ser realmente barrocas. Y las aplicaciones tambin, aun
que dejaremos este asunto a los lectores avanzados. signo = &chalet;
printf("%d %d\n", chalet.habitac, signo-> plantas) ;
printf("%s \n", chalet.direc);
printf("%c %c\n", signo-> direc[4] , chalet.direc[6] ) ;
}
Con las uniones, estructuras y typedef, el C suministra los tiles necesarios para
un manejo de datos eficiente y transportable.
3. Disee un patrn de estructura que guarde el nombre de un mes, una abreviatura
de tres letras para el mismo, el nmero de das del mes y el orden de ste.
Hasta ahora hemos aprendido 4. Defina un array de 12 estructuras del tipo preparado en la cuestin anterior, e
inicialcelo para un ao no bisiesto.
5. Escriba una funcin que, dado el nmero del mes, devuelva el nmero total de
Qu es un patrn de estructura y cmo se define das transcurridos en el ao hasta llegar a ese mes. Para este ejercicio suponga
Qu es una etiqueta de estructura y cmo se usa que se han declarado externamente el patrn de estructura y el array de las cues
Cmo definir una variable estructurada: struct coche ferrari tiones 3 y 4.
Cmo acceder a un miembro de la estructura: ferrari.kph 6. Dado el siguiente typedef, declare un array de 10 elementos de la estructura indi
cada. A continuacin, utilizando asignacin individual de miembros, haga que

417
Ejercicios
el tercer elemento describa una lente Remarkatar de distancia focal 500 mm. y 1. Vuelva a rehacer la cuestin 5, utilizando como argumento el nombre completo
apertura f/2.0.
del mes en lugar de su nmero de orden. (No olvide strcmp( ).)
typedef struct { /* definidor de lentes */ 2. Escriba un programa que solicite del usuario un da, mes y ao. El mes puede
float distfoc; /* distancia focal,mm*/ ser un nmero de mes, un nombre de mes o una abreviatura del mismo. El pro
float apert; /* apertura */
char *marca; /* marca comercial */ grama deber devolver el nmero total de das transcurridos en ese ao hasta la
} LENTE; fecha indicada.
3. Revise nuestro programa de clasificacin de libros de manera que imprima las
descripciones de libros alfabetizados por ttulos y, adems, calcule la suma total
Respuestas de sus precios.
1. La palabra clave es struct, no structure. El patrn necesita, o bien una etiqueta antes de
la llave de abrir, o un nombre de variable tras la llave de cierre. Adems, debe haber un
punto y coma tras *todo y otro al final del patrn.
2. 6 1
calle Barco 86
e B
El miembro chalet.direc es una tira de caracteres, y chalet.direc[4] es el quinto elemento
de este array.
3. st r uct mes {
char nombre[ll]; /* o char *nombre; */
char abrev[4]; /* o char *abrev; */
int dias;
int numes;
};
4. struct mes meses[12] = {
{"Enero", "Ene", 31, 1},
{"Febrero", "Feb", 28, 2},
...Etcetera...
{ "Diciembre", "Dic, 31, 12}
};
5. di as( mes)
i nt mes;
{
int indice, total;

i f ( mes <1 | | mes > 12)


return(-l); /* signo de error */
else
for (indice = 0, total = 0; indice < mes; indice++)
total += meses[indice].dias;
return(total);
}
Observe que ndice es uno menos que el nmero de mes, ya que los arrays comienzan con
el subndice 0; por ello usamos la comparacin ndice < mes en lugar de ndice <= mes.
6. LENTE gaf as[ 10] ;

gafas[2].distfoc = 500.0;
gafas[2]. apert = 2.0;
gafas[2].marca = "Remarkatar" ;

419
15
La biblioteca C
y el fichero
de entrada/salida
En este captulo encontrar:
Cmo acceder a la biblioteca C
Acceso automtico
Inclusin de ficheros
Inclusin de bibliotecas
Funciones de biblioteca que hemos utilizado
Comunicacin con ficheros
Qu es un fichero?
Un programa sencillo de lectura de ficheros: fopen( ), fclose( ), getc( ) y putc( )
Apertura de un fichero: fopen( )
Cierre de un fichero: fclose( )
Ficheros de texto con buffer
Fichero de E/S: getc( ) y putc( )
Un programa sencillo de reduccin de ficheros
Fichero de E/S: fprintf( ), fscanf( ) y fputs( )
Las funciones fprintf( ) y fscanf( )
La funcin fgets( )
La funcin fputs( )
Acceso aleatorio: fseek( )
Comprobacin y conversin de caracteres
Conversin de tiras de caracteres: atoi( ) y atof( )
Salida: exit( )
Asignacin de memoria: malloc( ) y calloc( )
Otras funciones de biblioteca
Conclusin
Hasta ahora hemos aprendido
Cuestiones y respuestas
Ejercicios

421
La biblioteca C ciones de biblioteca han de buscarse en varios sitios diferentes. Por ejemplo,
getchar( ) est definido usualmente como macro en el fichero stdio.h, en tanto
que strlen( ) generalmente se guarda en un fichero de biblioteca. En segundo
lugar, diferentes sistemas pueden tener diferentes modos de acceso a estas
funciones. A continuacin presentamos tres posibilidades.

Acceso automtico
En muchos sistemas UNIX grandes simplemente se compila el progra
ma, y las funciones de biblioteca ms comunes son accesibles automtica
mente.

CONCEPTOS Inclusin de ficheros


Si una funcin est definida como macro, se deber utilizar un #include
La biblioteca C del fichero correspondiente que contenga su definicin. A menudo, las fun
Ficheros en C ciones que realizan tareas semejantes se renen en un mismo fichero de enca
Funciones de manejo de ficheros bezamiento, con un nombre adecuado. Por ejemplo, muchos sistemas tienen
Macros de comprobacin de caracteres un fichero ctype.h que contiene varias macros cuya misin es determinar la
Funciones de asignacin de memoria naturaleza de un carcter; mayscula, dgito, etc.

Inclusin de bibliotecas
En un momento dado durante la compilacin o carga de un programa
se debe especificar una opcin de biblioteca. En nuestro sistema, por ejem
plo, existe un fichero, llamado Ic.lib, que contiene versiones compiladas de
las funciones de biblioteca, y se debe indicar al linker del IBM PC que
utilice dicha biblioteca. Incluso cuando el sistema compruebe automticamente
su biblioteca estndar, pueden existir otras bibliotecas de funciones usadas
con menor frecuencia, las cuales habr que solicitar explcitamente para po
Desde el precioso instante en que comenzamos a emplear funciones como der usarlas como opcin en tiempo de compilacin.
printf( ), getchar( ) y strlen( ) estamos, en realidad, utilizando la biblioteca Evidentemente, no podemos detallar las caractersticas especficas de to-
C. La biblioteca C contiene docenas de funciones y macros construidas para
la comodidad del usuario. Las bibliotecas pueden variar de sistema a siste
ma; no obstante, existe un ncleo de funciones, llamado biblioteca estndar,
que est contenido en la gran mayora de sistemas. Examinaremos en este
captulo 15 estas funciones ms comunes, concentrndonos en funciones de
entrada/salida y manejo de ficheros. Funciones de biblioteca que ya hemos utilizado
Sin embargo, la primera parte del captulo se dedicar a explicar cmo
se utiliza la biblioteca.
Lo que viene a continuacin es una simple recopilacin de las funciones
utilizadas hasta ahora, con el nico propsito de ahorrarle a usted el trabajo
Cmo acceder a la biblioteca C de buscarlas. En primer lugar, las funciones de E/S:

En realidad, el modo de acceso a la biblioteca C depende de su sistema, getchar() /* captura un caracter */


por lo que aconsejamos que compruebe por s mismo si los prrafos que van putchar() /* imprime un caracter */
gets() /* captura una linea */
a continuacin pueden aplicarse a su caso concreto. En primer lugar, las fun puts() /* imprime una linea */
scanf() /* captura entradas con formato */
printf() /* imprime salidas con formato */

423
de estructura de fichero. El ejemplo presentado a continuacin puede ser un
Y stas son las funciones de manejo de tiras de caracteres: caso tpico, tomado de la versin IBM del Lattice C:
struct _iobuf
strlen() /* calcula longitud de una tira */ {
strcmp() /* compara dos tiras */ char *_ptr /* puntero a buffer actual */
strcpy() /* copia una tira en otra */ int _cnt; /* contador del byte actual*/
strcat() /* combina dos tiras en una */ char *_base; / * direccion base de buffer E/S */
char _flag; /* flags de control * i
char _file; /* numero de fichero */
A esta lista aadiremos funciones para abrir y cerrar ficheros, funciones };
para comunicar con ficheros, para comprobar y convertir caracteres, para
convertir tiras, una funcin de salida y funciones para asignar memoria. #define FILE struct _iobuf /* notacion abreviada */
Como primera providencia, nos dedicaremos al problema de las comuni
caciones entre un fichero y un programa.
Una vez ms, no vamos a preocuparnos de los detalles de esta definicin.
Los puntos a destacar son que el fichero es una estructura y que el nombre
Comunicacin con ficheros abreviado, FILE, que aparece al final corresponde al patrn del fichero. (Mu
chos sistemas usan typedef para realizar la misma correspondencia.) As pues,
Con frecuencia un programa necesita obtener informacin de un fichero un programa que trabaje con ficheros utilizar el tipo estructurado FILE pa
o colocar sus resultados en otro. Un mtodo para hacerlo es utilizar los ope ra hacerlo.
radores de reenvo, < y > . Este mtodo resulta sencillo, pero es bastante Teniendo presente lo anterior, podremos comprender mejor las operaciones
limitado. Supongamos, por ejemplo, que desea escribir un programa inte de fichero.
ractivo que pida ttulos de libros (le suena?), y que se desee guardar la lista
completa en un fichero. Si se usa un reenvo, como
libros > listalib Un programa sencillo de lectura de fichero:
fopen( ), fclose( ), getc( ) y putc( )
los mensajes interactivos tambin se enviarn a listalib. Con ello no slo es
tamos introduciendo material no deseado en listalib, sino que, adems, el usua El ejemplo siguiente muestra los rudimentos del manejo de ficheros. Pa
rio no puede ver los mensajes de las cuestiones que se supone ha de responder. ra ello hemos preparado un programa muy limitado que lee el contenido de
Por fortuna, el C ofrece mtodos ms potentes de comunicacin con fi un fichero llamado test y lo imprime en pantalla. Inmediatamente despus
cheros. Uno de ellos comporta la utilizacin de la funcin fopen( ), la cual
del programa se presenta una explicacin del mismo.
abre un fichero; el uso de funciones especiales E/S, para leer o escribir en
tal fichero; por ltimo, el empleo de la funcin fclose( ), para cerrar el fiche
ro en cuestin. Antes de investigar estas funciones daremos un breve repaso /* nos dice que hay en el fichero "test" */
a la naturaleza de un fichero. #include <stdio.h>
main()
{
Que es un fichero? FILE *in;
int ch;
/* declara puntero a fichero */

Para nosotros, un fichero es una porcin de almacenamiento, general if ( (in = fopen("test", "r")) != NULL)
mente en disco, con un nombre. Podemos pensar, por ejemplo, que stdio.h /* abre fichero test para lectura, comprueba si existe */
/* el puntero FILE apunta ahora a test */
es el nombre de un fichero que contiene una cierta cantidad de informacin {
til. Por lo que respecta al sistema operativo, un fichero es algo ligeramente while ( (ch=getc(in)) != EOF) / * toma caracter de in */
ms complicado; pero es su problema, no el nuestro. Sin embargo, debemos putc(ch,stdout); /* lo envia a salida estandar */
fclose(in); /* cierra el fichero */
conocer lo que representa un fichero en un programa C. En lo concerniente }
a las funciones de ficheros discutidas hasta ahora, el C contempla sus fiche else
ros como estructuras. De hecho, el fichero stdio.h contiene una definicin printf("No puedo abrir el fichero \"test\".\n");
}

425
Tambin puede suceder que el disco est lleno, o que el nombre sea ilegal,
o cualquier otra razn que impide la apertura del fichero. Es conveniente,
Los tres detalles ms importantes a explicar son el manejo de fopen( ) por tanto, comprobar los fallos de apertura; este pequeo detalle puede aho
y de fclose( ) y la utilizacin de las funciones del fichero de E/S. Vayamos rrarnos muchos problemas.
por orden. El cierre del fichero es bastante ms fcil.
Apertura de un fichero: fopen( )
Cierre de un fichero: fclose( )
Fopen( ) es una funcin controlada por tres parmetros bsicos. El pri
mero es el nombre del fichero que se va a abrir. Este nombre se indica en En nuestro ejemplo se refleja cmo cerrar un fichero:
forma de tiras de caracteres como primer argumento de fopen( ); en nuestro
fclose(in);
caso es test.
El segundo parmetro (que es el segundo argumento de fopen( )) descri
be el uso a que se va a destinar el fichero. Existen tres usos bsicos: Simplemente utilizando la funcin fclose( ). Obsrvese que el argumento
es in, el puntero al fichero, y no test, el nombre del fichero.
r: un fichero de lectura En un programa ms serio que el presentado habramos comprobado si
w: un fichero de escritura
a: un apndice del fichero el fichero se ha cerrado normalmente. La funcin fclose( ) devuelve un valor
0 en caso de cierre satisfactorio, y 1 si no lo es.
Algunos sistemas ofrecen posibilidades adicionales, pero nos contentare
mos con stas. Observe que los cdigos utilizados son tiras de caracteres, pe Ficheros de texto con buffer
ro no constantes de tipo carcter; por ello van encerradas entre comillas. La
opcin r abre un fichero ya existente. Las otras dos opciones abren un Las funciones fopen( ) y fclose( ) trabajan con ficheros de texto con buffer,
fichero existente, si lo encuentran; si no existe, crean uno con se nombre. Con ello queremos indicar que la entrada y salida se almacenan temporal
PRECAUCION: Si se usa la opcin w en un fichero ya existente, la ver mente en un rea de memoria llamada el buffer. Cuando el buffer se llena,
sin antigua se borrar, con el fin de que el programa comience con el fiche el contenido se traspasa a un bloque de memoria, y se recomienza el proceso.
ro limpio. Una de las tareas principales de fclose( ) es el vaciado del buffer, que po
El tercer parmetro es un puntero al fichero; su valor se devuelve en la dra haber quedado parcialmente lleno al cerrar el fichero.
funcin:
Un fichero de texto es aquel en el que la informacin se almacena como
FILE *in; caracteres, utilizando un cdigo ASCII o similar. El caso opuesto es un fi
in = fopen("test", "r");
chero binario, como el que se ha de emplear para almacenar un cdigo ara
lenguaje mquina.
Las funciones de E/S que vamos a describir a continuacin estn disea
Tras la definicin, in es un puntero al fichero test. A partir de ese mo
das para trabajar nicamente con ficheros de texto.
mento, el programa se refiere al fichero por el puntero in, y no por su nom
bre test. Como usted es un lector inteligente, se le habr planteado la siguien
te duda: Si fopen( ) devuelve un puntero FILE como argumento, por Fichero E/S: getc( ) y putc( )
qu no tenemos que declarar fopen( ) como funcin de tipo puntero FILE? Las funciones getc( ) y putc( ) se comportan de forma muy semejante
Buena pregunta. La respuesta es que esta declaracin est hecha en stdio.h. a getchar( ) y putchar( ). La diferencia es que a estas dos nuevas funciones
que contiene la lnea se les tiene que indicar el fichero que deben utilizar. As, nuestro viejo amigo
FILE * f o p e n ( );
ch = getchar() ;

Existe una importante caracterstica de fopen( ) que ya hemos utilizado. significa que capturamos un carcter de la entrada estndar, pero
Si fopen( ) no consigue abrir el fichero requerido, devuelve un valor NULL
(definido como 0 en stdio.h). Y por qu no se puede abrir el fichero? Por ch = getc( in);
ejemplo, si se intenta leer un fichero no existente. Esta es la razn de la lnea
de programa indica que se ha de tomar un carcter del fichero apuntado por in.
if ( (in = fopen("test", "r")) != NULL)
De igual forma,
putc(ch, out) ;

427
{strcpy(nombre, argv[1]); copia nombre del fichero
/*
quiere decir que se enva el carcter ch al fichero apuntado por out, un pun en un array */
tero de tipo FILE. strcat(nombre, ".red"); / * une .red al nombre * /
La lista de argumentos de putc( ) comprende el propio carcter y a conti out = fopen(nombre, "w"); /* abre otro fichero como
salida en escritura */
nuacin el puntero al fichero. while ( (ch = getc(in)) != EOF)
En nuestro ejemplo, hemos utilizado if (cont++ % 3 == 0)
putc(ch, out); /* envia un caracter cada tres */
putc(ch, stdout); fclose(in);
fclose(out);
}
donde stdout es un puntero a la salida estndar; por consiguiente, esta sen else
printf("No puedo abrir el fichero \"%s\". \n",
tencia es equivalente a argv[1] ; }
}
putchar(ch);

De hecho, putchar( ) est definido con un #define en stdio.h como Figura 15.1
putc(ch,stdout). Este temible fichero tiene tambin #define de stdout y stdin Programa de reduccin de ficheros
como punteros a la salida y entrada estndar del sistema.
Sencillo, verdad? Bien, aadiremos ahora otro par de pases mgicos.
Una vez escrito y compilado el programa, lo colocamos en un fichero lla
Programa sencillo de reduccin de ficheros mado reduce. Despus lo aplicamos a un fichero, llamado mili, que contena
una nica lnea:
En el ejemplo anterior, el nombre del fichero que se deba abrir estaba
escrito dentro del programa. En realidad, esta restriccin no existe: utilizan Mi ayuno durara y deliran veteranos
do argumentos en lnea de ejecucin, podemos indicar a nuestro programa
el nombre del fichero que deseamos leer. En nuestro siguiente ejemplo (figu La orden utilizada fue
ra 15.1) procedemos de esta manera. El programa resume el contenido del
fichero por el sistema un tanto brutal de retener un carcter de cada tres. reduce mili
Por ltimo, coloca la versin resumida en un nuevo fichero, cuyo nombre
es el mismo que el del fichero antiguo con .red detrs (por reducido). Las y la salida se envi a un fichero llamado mili.red, que acab conteniendo
dos indicaciones del comienzo y final (argumentos en lnea de ejecucin y
apndice al nombre del fichero) son de uso bastante general. E1 programa Manda dinero
en s, por el contrario, tiene una utilidad muy limitada, aunque, como vere
mos, se le pueden encontrar algunas aplicaciones. Caramba! Qu suerte ms extraordinaria! Un fichero seleccionado al
azar produjo un mensaje inteligible.
/ * reduce su fichero a la tercera parte! */ A continuacin comentaremos algunos aspectos del programa.
#include <stdio.h>
main(argc, argv) Recuerde que argc es el nmero de argumentos, incluyendo el propio nom
int argc; bre del fichero del programa. Si tenemos esto en cuenta, y el sistema operati
char *argv[];
{
vo lo permite, argv[0] representar el nombre del programa, en nuestro caso
FILE *in, *out; /* declara dos punteros FILE */ reduce. Por tanto, argv[l] contendr el primer argumento, que en nuestro
int ch; ejemplo era mili. Como argv[l] es por s mismo un puntero a una tira de ca
static char nombre[20] ; /* para guardar fichero salida * / racteres, no lo hemos colocado entre comillas en la llamada funcin.
int cont = 0;
Tambin usamos argc para comprobar si existe un argumento. Si se in
if (argc < 2) /* comprueba que hay fichero de entrada */ troducen ms argumentos detrs, quedan ignorados; sin embargo, se puede
printf("Lo siento, falta nombre fichero como argumento.\n); aadir fcilmente otro bucle al programa, de forma que ste pudiera utilizar
else
{ sucesivamente nombres de ficheros y aplicarles la reduccin correspondiente
if ( (in = fopen(argv[1], "r")) != NULL) por turno.

429
A diferencia de getc( ) y putc( ), estas dos funciones toman el puntero co
mo primer argumento. Las dos restantes lo toman en ltimo lugar.
A fin de construir el nombre del fichero de salida, usamos strcpy( ) para
copiar el nombre mili en el array nombre. Seguidamente utilizamos la fun La funcin fgets( )
cin strcat( ), para unir este nombre con .red. Esta funcin utiliza tres argumentos, en lugar de uno como gets( ). Un
En el programa tenemos dos ficheros abiertos simultneamente; por tan ejemplo podra ser:
to, deberemos declarar dos punteros de tipo FILE. Observe que cada fiche
ro se abre y cierra independientemente del otro. Existen lmites en el nmero
de ficheros que se pueden tener abiertos a un tiempo; el lmite depende del /* lee de un fichero una linea cada vez */
sistema, pero generalmente est en el rango de 10 a 20. Se puede emplear #include <stdio.h>
el mismo puntero para diferentes ficheros, siempre que no haya dos ficheros #define MAXLIN 80
main()
con el mismo puntero abiertos a la vez. {
Tampoco estamos limitados simplemente a getc( ) y putc( ) en ficheros FILE *f1;
E/S. Estudiaremos ahora otras posibilidades. char *tira[MAXLIN];

f1 = fopen("cuento", "r");
while (fgets(tira, MAXLIN, f1) != NULL)
Fiche ro E/ S: puts(tira);
}
fprintf( ), fscanf( ), fgets( ) y fputsf( )

Todas las funciones de E/S que hemos utilizado en captulos anteriores El primero de los tres argumentos de fgets( ) es un puntero al lugar de
tienen su equivalente en ficheros de E/S. La diferencia fundamental es que destino de la lnea que se va a leer; en nuestro caso hemos colocado la entra
necesitaremos un puntero FILE para indicar a las nuevas funciones el fichero da en un array de char llamado tira.
con el que tienen que trabajar. Al igual que suceda con getc( ) y putc( ), El segundo argumento limita la longitud de la tira que se est leyendo.
estas funciones se usan despus de que fopen( ) ha abierto el fichero, y antes La funcin se detiene cuando se lee un carcter nuevalnea o MAXLIN1
de que fclose( ) lo cierre. caracteres, lo que suceda primero. En cualquiera de los casos, se aade un
carcter nulo (\0) al final de la tira.
Las funciones fprintf( ) y fscanf( ) El tercer argumento es, por supuesto, un puntero al fichero que se est
Estas dos funciones de E/S se comportan exactamente igual que printf( ) leyendo.
y scanf( ), excepto que requieren un argumento adicional para apuntar al Una diferencia entre gets( ) y fgets( ) es que la primera sustituye el carcter
fichero correspondiente. Este argumento est en primer lugar en la lista. Vea nuevalnea con el \ 0, en tanto que fgets( ) mantiene el carcter nuevalnea.
mos un ejemplo. Al igual que gets( ), fgets( ) devuelve un valor NULL cuando encuentra
un carcter EOF. Esto permite comprobar, como hemos hecho, si se ha lle
/* formato para usar fprintf() y fscanf() */ gado al final del fichero.
#include <stdio.h>
main()
{ La funcin fputs( )
FILE *fi;
int edad; Esta funcin es bastante semejante a puts( ). La sentencia
fi = fopen("pedro","r"); /* modo lectura */
fscanf(fi, "%d", &edad); /* fi apunta a pedro */ fputs(Por fin haces algo bien.", puntfich);
fclose(fi) ;
fi = fopen("datos", "a"); /* modo apendice */
fprintf(fi, "pedro tiene %d. \n", edad); enva la tira de caracteres Por fin haces algo bien. al fichero apuntado
/* fi apunta a datos */ por puntfich, un puntero de tipo FILE. Naturalmente, este fichero deber
fclose(fi);
} haber sido abierto con anterioridad por fopen( ). La forma ms general de
uso es
Observe que podemos usar fi en dos ficheros diferentes porque hemos ce
control = fputs(puntero tira, puntero fichero):
rrado el primero antes de abrir el segundo.
431
{
FILE *fp;
donde control es un entero que toma el valor EOF si fputs( ) encuentra un long offset = 0L; /* observe tipo long */
EOF o un error. if (numero < 2)
Al igual que puts( ), esta funcin no copia el \0 del final de la tira. puts("Necesito un fichero como argumento.");
Sin embargo, a diferencia de la anterior funcin, fputs( ) no aade un carc else
ter nuevalnea en la salida. {
if ( (fp = fopen(nombres[1] , "r")) == 0)
Estas seis funciones de E/S que acabamos de discutir nos otorgan facili printf("No puedo abrir %s.\n", nombres[1]) ;else
dades ms que suficientes para la lectura y escritura de ficheros de texto. Que {
da an otra funcin que puede ser de gran utilidad, y que pasamos a discutir while (fseek(fp,offset++,0) == 0)
a continuacin. putchar(getc(fp));
fclose(fp); }
}
}
Acceso aleatorio: fseek( ) El primero de los tres argumentos de fseek( ) es un puntero FILE al fi

La funcin fseek( ) permite tratar los ficheros como arrays, movindose


directamente a un byte determinado del fichero abierto por fopen( ). Pre
sentamos a continuacin un ejemplo directo que muestra su funcionamien chero que es objeto de la bsqueda. Dicho fichero deber haber sido abierto
to. Hemos tomado prestado, de ejemplos anteriores, el prrafo de argumen previamente con un fopen( ).
tos en lnea de ejecucin destinado a conseguir el nombre del fichero sobre El segundo argumento se denomina el offset (por ello utilizamos este
el que se va a trabajar. Observe que fseek( ) tiene tres argumentos y devuelve nombre para la variable). Este argumento indica la distancia a que debemos
un valor int. movernos desde el punto de comienzo (vase ms adelante); deber declarar
/* usa fseek() para imprimir el contenido de un fichero */ se como tipo long. Puede ser positivo (movimiento hacia adelante) o negati
#include <stdio.h> vo (movimiento hacia atrs).
main(numero, nombres) /* no hay por que usar argc y argv */ El tercer argumento es el modo que identifica el punto de referencia para
int numero;
char *nombres[] ; el offset:
MODO EL OFFSET SE MIDE DESDE

0 el comienzo de fichero
1 posicin actual
2 fin de fichero
El valor devuelto por el return de fseek( ) es 0, siempre que haya funcio

nado todo correctamente; si aparece un error, como intentar avanzar ms


all de los lmites del fichero, el valor devuelto es 1.
Ya podemos explicar ahora nuestro pequeo bucle:
while (fseek<fp,offset++, 0) == 0)

putchar(get(fp));
Al inicializarse offset a 0, la primera vez que se ejecuta el bucle tendre

mos la expresin
fseek(fp,0L,0)

lo que significa literalmente: ir al fichero apuntado por fp y localizar el byte

que est a 0 byte de distancia del comienzo. O, lo que es lo mismo, ir al pri-

433
Nuestro programa imprime el primer carcter del fichero; a continuacin,
mer byte. La funcin putchar( ), a continuacin, imprimir el contenido de el ltimo; ms tarde, el segundo; despus, el penltimo, etc. Hemos utiliza
dicho byte. La siguiente vez que se ejecute el bucle, offset se habr incremen do el mismo programa anterior, aadiendo las siguientes lneas:
tado en 1L, imprimindose, por tanto, el siguiente byte. En esencia, la varia
ble offset est actuando como un subndice de los elementos del fichero. El if (fseek(fp,-(offset + 3), 2) == 0)
proceso contina hasta que offset intenta llevar a fseek( ) ms all del fin putchar(getc(fp));
de fichero. En ese momento, fseek( ) devuelve un valor 1, y el bucle se de
tiene.
Este ltimo ejemplo tiene un carcter puramente didctico; no necesita El modo 2 significa que contamos posiciones desde el final del fichero.
mos fseek( ) en este caso, ya que con getc( ) podramos haber rastreado el El signo negativo indica que se debe contar hacia atrs. El +3 figura con
fichero byte a byte de igual forma; de hecho, fseek( ) indica a getc( ) que el fin de que se empiece a trabajar con el ltimo carcter normal del fiche
busque justamente donde esta ltima iba ya a buscar. ro, evitando algunos caracteres nuevalnea y EOF que se colocan en el final
En la figura 15.2 presentamos un ejemplo menos corriente. (La idea ori
ginal es de una obra de William Shakespeare, La Duodcima Noche.) real del fichero. (El valor exacto de este ajuste depende del sistema. Nuestros
ficheros finalizan con 2 caracteres nuevalnea seguidos por 2 EOF, por lo que
hemos retrocedido hasta pasarlos.)
/ * alterna impresion a izquierda y derecha */ Esta es la parte del programa que alterna la escritura de derecha a izquierda
#include <stdio.h> y de izquierda a derecha. Debemos mencionar que algunos sistemas no acep
main(numero, nombres) /* no hay por que usar argc y argv */
int numero; tan el modo 2 en fseek( ).
char *nombres[] ; Bien, con esto pensamos que ya hemos digerido bastantes ficheros por
{
FILE *fp; el momento. Cerraremos el tema, y nos dedicaremos a otra seccin de la bi
long offset = 0L; blioteca C.
if (numero < 2)
puts("Necesito un fichero como argumento.");
else
{
if ( (fp = fopen(nombres[1], "r")) == 0)
printf("No puedo abrir %s.\n", nombres[1] ) ; Comprobacin y conversin de caracteres
else
{
while (fseek(fp, offset++, 0) == 0) El fichero de encabezamiento ctype.h define varias funciones macros que
{ comprueban la clase a que pertenecen distintos caracteres. Por ejemplo, la
putchar(getc(fp));
if (fseek(fp,-(offset + 3), 2) == 0) funcin isalpha(c) devuelve un valor no 0 (cierto) si c es un carcter alfabti
putchar(getc(fp)); co, mientras que devuelve un 0 (falso) si el carcter no es alfabtico. Por tanto,
}
fclose(fp);
} isalpha('S') ! = 0 , pero isalpha ( ' # ' ) = = 0
}
}
A continuacin presentamos una lista con las funciones que se suelen en
Figura 15.2
contrar ms comnmente en dicho fichero. En cada caso, la funcin devuel
Programa con impresin alternante a izquierda y derecha ve un valor distinto de 0, si c pertenece a la clase comprobada, y 0, si no
es as.

Funcin el test decide si c es


La aplicacin de este programa a un fichero que contenga el nombre Ma
rinero produce este curioso resultado: isalpha(c) alfabtico
isdigit(c) dgito
MoarreinnierraoM
islower ( c ) minscula
isspace(c) espacio en blanco (espacio, tabulado o nuevalnea)
isupper(c) mayscula

435
printf("De acuerdo, va a minusculas.\n");
return(MINUS) ;
En su sistema puede que existan funciones adicionales como }
}
Funcin el test decide si c es prepfich(nombre1, nombre2)
isalnum(c) alfanumrico (alfabtico o dgito) char *nombrel, *nombre2;
isascii(c) ASCII (0-127)
iscntrl(c) carcter de control printf(" Que fichero desea convertir?\n");
gets (nombrel) ;
ispunct(c) signo de puntuacin printf( "Fichero elegido: \"%s\". \n", nombrel);
printf("Nombre del nuevo fichero convertido\n");
Existen dos funciones ms que realizan conversaciones: while (strcmp(gets(nombre2) nombrel) == NULL)
printf("El nombre ha de ser diferente.\n");
toupper(c) convierte c a maysculas printf("El fichero de salida sera \"%s\".\n", nombre2);
}
tolower(c) convierte c a minsculas
conv(nombrel, nombre2, crit)
En algunos sistemas, la conversin se intenta nicamente cuando el ca char *nombrel, *nombre2;
rcter no es del tipo de letra solicitado. Sin embargo, conviene asegurarse int crit;
{
primero de la mayusculalidad o minusculalidad del carcter. int ch;
En la figura 15.3 hay un programa que emplea algunas de estas funciones FILE *fl, *f2;
para convertir un fichero completamente a letras maysculas o a letras mi
nsculas, segn se desee. Con el fin de variar un poco, usaremos esta vez if ( (f1 = fopen(nombre1, "r")) == NULL)
printf("Lo siento, no puedo abrir %s. Adios.\n", nombrel);
un sistema interactivo para enviar la informacin requerida por el programa else
en lugar de los argumentos en lnea de ejecucin. {
puts("Alla vamos!");
f2 = fopen(nombre2, "w");
/* conversion a MAYUSCULAS o minusculas */ while ( (ch = getc(fl)) != EOF)
#include <stdio.h> {
#include <ctype.h> /* incluye un fichero de macros */ if (crit == MAYUS)
#define MAYUS 1 ch = islower(ch) ? toupper(ch) : ch;
#define MINUS 0 else
main() ch = isupper(ch) ? tolower(ch) : ch;
{ pute(ch,f2);
int crit; /* sera despues MAYUS o MINUS */ }
char fich1[14],fich2[14] /* nombres entrada y salida */ fclose(f2);
fclose(f1) ;
crit = escoge(); /* escoge mayuscula o minuscula */ puts("Terminado!");
prepfich(fichl, fich2);/* toma los nombres de ficheros */ }
conv(fich1, fich2,crit); / * realiza la conversion */ }
}
escoge() Figura 15.3
{ Programa convertidor de tipos de letras
int ch;

printf("Este programa convierte un fichero completo a\n") ) Hemos dividido el programa en tres partes: la obtencin de la decisin
printf("MAYUSCULAS o minusculas. Introduzca A si desea\n");
printf("mayusculas o I para minusculas.\n"); de usuario sobre el tipo de letra, la preparacin de nombres para los ficheros
while ((ch = getchar()) != 'A' && ch != 'a' && ch != 'I' de entrada y salida y la propia conversin. Para no oxidarnos, hemos desa
&& ch != 'i') rrollado una funcin distinta para cada parte.
printf("Introduzca una A o una I.\n");
while (getchar() != '\n') La funcin escoge( ) es bastante directa, quiz con la excepcin del bucle
; /* limpia entrada */
if ( ch == 'A' || ch == 'a') while (getchar() != '\n' )
{
printf("De acuerdo, va a mayusculas.\n");
return(MAYUS) ;
} Este bucle est incluido para resolver un problema que ya apareci en el
else captulo 14. Cuando el usuario responde a la pregunta sobre maysculas y
{
437
char ch;
static char numero[TAM];
int valor;
minsculas, deber pulsar una letra, por ejemplo la A, y a continuacin la int digito = SI;
tecla [enter], que transmite un carcter \n. La funcin inicial getchar( ) int cont = 0;
recoge la A, pero deja el carcter \n en el buffer de entrada hasta la si
puts("Introduzca un entero.\n");
guiente lectura. La funcin gets( ), que viene a continuacin dentro de prep- gets(numero);
fich( ), interpretar este carcter como una lnea vaca. Por ello empleamos if (numero[TAM-1] != '\0')
el pequeo bucle while para deshacernos del carcter nuevalnea. En reali
dad, un simple getchar( ); hubiera valido, suponiendo que el usuario pulsara puts("Demasiadas cifras; me ha fundido los bits.");
exit(l) ;
[enter] inmediatamente despus de A; sin embargo, nuestra versin permite }
tambin que se pulsen espacios entre la letra y el [enter]. while ( (ch = numero[cont]) != '\0' && dgito == SI)
La funcin prepfich( ) contiene muy pocas novedades. Observe que he if (!issign(ch) && !isdigit(ch) && !isspace(ch))
mos previsto que el usuario intente utilizar el mismo nombre como fichero digito = NO;
if (digito == SI)
de entrada y de salida. La versin estndar de fopen( ) no permite leer y es {
cribir en el mismo fichero al mismo tiempo. valor = atoi(numero) ;
La funcin conv( ) es una funcin de copia con un convertidor de tipos printf("El numero leido es %d. \n", valor);
}
aadido. El valor de crit decide qu conversin se va a realizar. La tarea la else
pueden llevar a cabo sentencias condicionales sencillas, como printf("Eso no tiene pinta de entero.\n");
}
ch = islower(ch) ? toupper(ch) : ch;
Figura 15.4
Programa que utiliza a atoi( )
Con ello se comprueba si ch es minscula; si lo es, se convierte en mays
cula; en caso contrario, se deja como est.
Las macros de ctype.h contienen herramientas tiles, adecuadas para una
Hemos incluido algunas comprobaciones de errores. Primero comproba
programacin ms agradable. Nos ocuparemos ahora de algunas funciones
mos si la tira de entrada es demasiado larga para el array de destino. Como
de conversin de naturaleza ms ambiciosa.
el array nmero es static char, est inicializado a nulos. Si el ltimo miembro
del array no es un nulo, es seal evidente de que algo ha ido mal, y el progra
Conversiones de tiras de caracteres: ma finaliza. Aqu empleamos la funcin de biblioteca exit( ), la cual permite
abandonar el programa; ahondaremos ms en esta funcin dentro de un mo
atoi( ), atof( ) mento.
Seguidamente se comprueba si la tira contiene caracteres extraos, es de
cir, algo que no sean espacios, nmeros o signos algebraicos. Con esta pre
El uso de scanf( ) para leer valores numricos no es, ni con mucho, la caucin se rechazan tiras como tres o 1.2E2. Se aceptara, sin embar
poltica ms segura. Scanf( ) depende demasiado de los errores de usuario go, algo como 34 + 2, pero ya dar cuenta de ello atoi( ). Recorde
en la introduccin de los nmeros. Muchos programadores prefieren leer in
mos que ! es un operador de negacin; por tanto, !isdigit(c) significa c no
cluso los datos numricos como tiras y convertirlos de tira al valor numrico
apropiado. Para ello se dispone de las dos funciones atoi( ) y atof( ). La pri es un dgito.
mera convierte una tira en entero, mientras que la segunda convierte la tira La lnea
en un nmero en punto flotante. En la figura 15.4 presentamos un ejemplo valor = atoi(numero) ;
de utilizacin:
demuestra cmo se utiliza atoi( ). Su argumento es un puntero a una tira;
/* utilizacion de atoi() */ en este caso hemos usado el nombre de array nmero. La funcin devuelve
#include <stdio.h> un valor int para la tira. As, 1234, que es una tira de cuatro caracteres,
#define issign(c) ( ((c) == '-' || (c) == '+') ? (1) : (0) )
#define TAM 10 se transforma en 1234, un valor entero simple.
#define SI 1 La funcin atoi( ) ignora los blancos anteriores al nmero; procesa un
#define NO 0 signo algebraico de comienzo, si existe, y a continuacin acepta dgitos hasta
main()
{ el primer carcter que no lo sea; por consiguiente, nuestro anterior ejemplo

439
Pero el C va ms all. Permite asignar ms memoria a medida que el
34 + 2 se convertira en el valor 3. Observe en las cuestiones del final programa se ejecuta. Supongamos, por ejemplo, que estamos escribiendo un
del captulo una posible implemantacin. programa interactivo, y no sabemos de antemano cuntas entradas vamos
La funcin atof( ) realiza una tarea similar en nmeros del punto flotan a realizar. Lo que se hace en un caso como ste es reservar, en principio, una
te. Devuelve un tipo double, de forma que debe ser declarada as en los pro cantidad de memoria razonable, y solicitar ms en ejecucin, si es necesario.
gramas que la utilicen. En la figura 15.5 hay un ejemplo que utiliza la funcin malloc( ) para hacer
Las versiones sencillas de atof( ) son capaces de manejar nmeros como esta tarea. Observe, adems, el uso de los punteros en este programa.
10.2, 46 y 124.26. Existen versiones ms avanzadas que aceptan, adems,
notacin exponencial, es decir, nmeros como 1.25E13. /* toma mas memoria si hace falta */
En su sistema puede que existan otras funciones que trabajan en sentido #include <stdio.h>
contrario. As, una funcin itoa( ) convertira un entero en tira, mientras que #define STOP "" /* signo para acabar la entrada */
ftoa( ) convertira un nmero de punto flotante en tira. #define BLOQUE 100 /* bytes de memoria */
#define LIM 40 / * longitud max linea entrada */
#define MAX 50 /* numero max lineas entrada */
#define DRAMA 20000 /* pausa dramatica de malloc() */
Salida: exit( ) main()
{
La funcin exit( ) proporciona una forma muy conveniente de abando char almacen[BL0QUE]; /* bloque original almacenamiento */
char sinfo[LIM]; /* receptor de entradas */
nar un programa. A menudo se acostumbra a detener un programa cuando char *fin; /* puntero a final almacenamiento */
aparece un error; cuando se llama a la funcin exit( ) desde una funcin que, char *entradas[MAX]; /* punteros a comienzos de tiras */
a su vez, ha sido llamada por el programa principal, se detiene el programa int indice = 0; /* numero de lineas introducidas */
int cont = 0; /* contador */
completo, y no simplemente la funcin. En el caso atoi( ), anterior el uso char *malloc(); /* asignador de memoria */
de exit( ), permite evitar la inclusin de una sentencia else extra que impidie
se la intervencin del resto del programa. entradas[0] = almacen;
Un gran favor que realiza exit( ) es cerrar todos los ficheros abiertos con fin = entradas[0] + BLOQUE - 1;
puts("Nombre algunas orquestas sinfonicas.");
fopen( ). Con ello se consigue que el final del programa sea mucho menos puts("Introduzca una cada vez; pulse [enter] a comienzo de");
doloroso. puts<"linea para terminar. Adelante, estoy listo.");
El argumento de exit( ) es un nmero de cdigo de error. En algunos sis while (strcmp(fgets(sinfo, LIM, stdin), STOP) != 0
&& indice < MAX)
temas este nmero puede traspasarse a otro programa cuando se llega a un {
exit en el programa de ejecucin. La convencin utilizada es que un 0 indica if (strlen(sinfo) > fin - entradas[indice])
terminacin normal, en tanto que el resto de valores indican un problema. {
/* esta parte se ejecuta si no hay suficiente memoria */
Antes de que se nos olvide, tenemos otro asunto que tratar. puts(Espere un momento, tengo que buscar mas memoria.");
entradas[indice] = malloc(BLOQUE);
fin = entradas[indice] + BLOQUE - 1;
Asignacin de memoria: malloc( ) y calloc( ) for (cont = 0; cont < DRAMA; cont++) ;
puts("Ya he encontrado otro trozo!");
}
Su programa deber disponer de memoria suficiente para almacenar los strcpy(entradas[indice], sinfo);
datos que utilice. Parte de esta asignacin de memoria se realiza autom entradas[indice + 1] = entradas[indice] + strlen(sinfo) + 1;
ticamente; por ejemplo, podemos declarar: if (++indice < MAX)
printf("Ya tenemos %d. Continue si lo desea. \n", indice);
}
char sitio[] = "Monasterio de Piedra"; puts("De acuerdo. Tenemos:");
for (cont = 0; cont < indice; cont++)
lo que garantiza disponer de memoria suficiente para almacenar esa tira. puts(entradas[cont]);
Tambin podemos ser ms explcitos y solicitar una cierta cantidad de me }
moria: Figura 15.5
Programa para aadir memoria segn haga falta
int platos[100];
Una salida de este programa podra ser:
Esta declaracin prepara 100 localizaciones de memoria, cada una de ellas
suficiente para albergar un valor int. Nombre algunas orquestas sinfonicas.
Introduzca una cada vez; pulse [enter] a comienzo de

441
linea para terminar. Adelante, estoy listo. Antes de copiar sinfo en el almacn, deberemos comprobar si tenemos
Filarmonica de Viena espacio suficiente. El puntero fin apunta al final del almacenamiento, mien
Ya tenemos 1.. Continue si lo desea. tras que el valor que tiene entradas[ndice] en ese momento corresponde al
Sinfonica de San francisco
Ya tenemos 2. Continue si lo desea. comienzo de la parte de almacenamiento sin utilizar. Si comparamos la dife
Filarmonica de Berlin rencia entre estos dos punteros con la longitud de sinfo podremos decidir si
Ya tenemos 3. Continue si lo desea. queda espacio suficiente.
Sinfonica de Radiotelevision
Ya tenemos 4. Continue si lo desea. Si no queda espacio, llamaremos a malloc( ) para preparar un nuevo blo
Sinfonica de Londres que de almacenamiento. Entonces hacemos que entradas[ndice] apunte al
Ya tenemos 5. Continue si lo desea. comienzo del nuevo bloque y que fin apunte al final del mismo. Observe que
La Concertgebouw
Espere un momento, tengo que buscar mas memoria. no disponemos de un nombre para este nuevo espacio de almacenamiento;
Ya he encontrado otro trozo! no es, por ejemplo, una extensin de almacn. La nica identificacin de que
Ya tenemos Continue si lo desea. disponemos son los punteros que apuntan a la nueva rea.
Sinfonica de Chicago
Ya tenemos 7. Contine si lo desea. Conforme el programa se va ejecutando, cada nueva tira de caracteres
queda apuntada por un miembro del array de punteros entradas. Algunas
De acuerdo. Tenemos: tiras estn en almacn; otras, en una o ms reas nuevas de almacenamiento.
Filarmonica de Viena
Sinfonica de San Francisco Sin embargo, en tanto en cuanto disponemos de los punteros, podemos acce
Filarmonica de Berlin der a las tiras, como queda demostrado por la salida del programa.
Sinfonica de Radiotelevision Este es el modo en que se usa malloc( ). Supongamos que ahora desea
Sinfonica de Londres
La Concertgebouw mos memoria de tipo int, no de tipo char. Se puede usar tambin malloc( )
Sinfonica de Chicago de la siguiente forma:
En primer lugar, observemos lo que hace malloc( ). Toma un argumento char *malloc();/*se sigue declarando como puntero a char */
entero sin signo que representa el nmero de bytes de memoria requeridos. int *nuevo ;
As, malloc(BLOQUE) solicita 100 bytes de memoria. La funcin devuelve nuevo = (int *)malloc(100);/ * se usa operador de moldeado */
un puntero char al comienzo del nuevo bloque de memoria. Hemos usado
la declaracin
Una vez ms hemos reservado 100 bytes de memoria. El operador de mol
char *mallo c ( ) ;
deado convierte el valor devuelto de puntero char a puntero int. En nuestro
para advertir al compilador de que malloc( ) devuelve un puntero char. En sistema, int ocupa dos bytes de memoria, lo que significa que nuevo + 1 in
tonces asignamos el valor de este puntero a entradas[ndice] con la sentencia crementar el puntero en dos bytes, lo cual le permite apuntar al siguiente
entero. Evidentemente, los 100 bytes reservados podrn utilizarse como al
entradas[indice] = malloc[BLOQUE); macn de 50 nmeros enteros.
Otra opcin para reservar memoria en la funcin calloc( ). Un ejemplo
Bien, observemos ahora el funcionamiento del programa. Se trata de al tpico podra ser:
macenar las tiras de entrada todas ellas en un gran array llamado almacn.
Hacemos entradas[0] igual al punto de comienzo de la primera tira: entradas[l],
char *calloc();
al punto de comienzo de la segunda, y as sucesivamente. Como etapa inter long *nuevo;
media, el programa lee la tira dentro del array sinfo. Utilizamos fgets( ) en
lugar de gets( ), para poder limitar la tira de entrada hasta ajustarse a sinfo. nuevo = (long *)calloc(100, sizeof(long));

Al igual que malloc( ), calloc( ) devuelve un puntero a char. Se debe uti


lizar el operador de moldeado si se desea almacenar un tipo diferente. Esta
nueva funcin tiene dos argumentos, ambos enteros sin signo. El primer ar
gumento es el nmero de clulas de memoria deseado; el segundo es el tama
o de cada clula en bytes. En nuestro caso, long utiliza cuatro bytes, por
F igu ra 15.6 lo que esta instruccin prepara 100 unidades de cuatro bytes cada una, utili
Sinfnicas consecutivas almacenadas en el almacn zando en total 400 bytes.

443
Hasta ahora hemos aprendido
Hemos usado sizeof (long) en lugar de 4, con el fin de hacer el cdigo Qu es una biblioteca C y cmo utilizarla
ms transportable; este fragmento funcionar incluso en sistemas en que long Cmo abrir y cerrar ficheros de texto: fopen( ) y fclose( )
tenga un tamao diferente del nuestro. Qu es un tipo FILE
La funcin calloc( ) tiene una propiedad adicional: hace todos los conte Cmo leer y escribir en ficheros: getc( ), putc( ), fgets( ), fscanf( ),
nidos del bloque iguales a 0.
fprintf( )
En la librera C de su sistema encontrar probablemente otras funciones
de gestin de memoria; le aconsejamos que revise los manuales correspon Cmo comprobar clases de caracteres: isdigitf ), isalpha( ), etc.
dientes. Cmo convertir tiras en nmeros: atoi( ), atof( )
Cmo hacer una salida rpida: exit( )
Cmo asignar memoria: malloc( ), calloc( )
Funciones de biblioteca
La mayor parte de las bibliotecas poseen algunas otras funciones en las Cuestiones y respuestas
reas que hemos repasado. Adems de funciones de asignacin de memoria,
existen funciones para liberar memoria que ya no se va a volver a emplear. Cuestiones
Pueden existir tambin otras funciones de tratamiento de tiras, quiz funcio
nes que buscan un carcter concreto o grupo de caracteres dentro de una tira. 1. Qu errores contiene este programa?
Otras funciones de fichero podran incluir open( ), close( ), create( ), main()
lseek( ), read( ) y write( ). Todas ellas cumplen las mismas tareas que las {
funciones que hemos discutido, pero a un nivel ms bsico. De hecho, las int *fp;
funciones del tipo fopen( ) estn escritas apoyndose en estas funciones ms int k;
bsicas. Su utilizacin suele ser ms molesta, pero permite el tratamiento de fp = fopen("galletas");
ficheros binarios adems de los ficheros de texto. for (k = O; k < 30; k++)
En su sistema puede existir tambin una biblioteca matemtica. Tpica fputs(fp, "Marta come galletas.");
fclose("galletas");
mente, tal biblioteca contiene una funcin raz cuadrada, una funcin po }
tencia, una funcin exponencial, varias funciones trigonomtricas y una fun
cin para generacin de nmeros aleatorios. 2. Qu se supone que hace el siguiente programa?
Le llevar algn tiempo explorar el total de funciones que ofrece su siste
ma. Si no encuentra lo que busca, fabrquese sus propias funciones. Recuer #include <stdio.h>
de que esta filosofa es parte integrante del C. Y, evidentemente, si cree que #include (ctype.h>
puede hacerlo mejor, por ejemplo, en una funcin de entrada, hgalo! main(argc,argv)
int argc;
Conforme vaya refinando y puliendo su tcnica de programacin, pasar char *argv[];
de escribir programas en C a realizar obras de arte C. {
int ch;
FILE *fp;
Conclusin if ((fp = fopen(argv[1] , "r")) == NULL)
exit(1) ;
Ha sido un camino bastante largo desde que se comenz el libro. Hemos while ((ch = getc(fp) != EOF)
if (isdigit(ch))
encontrado en l la mayora de caractersticas bsicas del lenguaje C. Las omi putchar(ch);
siones ms notables operaciones con bit y extensiones UNIX 7 se cubren fclose(fp);
brevemente en el apndice F. Hemos visto la pltora de operadores del C; }
admirado su enorme variedad de tipos de datos bsicos y derivados; obser
vado sus inteligentes estructuras de control, y tanteado su poderoso sistema 3. Existe algn problema en utilizar expresiones como isalpha(c[i]), cuando c es
de punteros. Tenemos la esperanza de haberle ayudado a prepararse para uti un array de char? Y una expresin como isalpha(c[i + + ])?
lizar C para sus propios fines. Sintese delante del teclado, y buena suerte 4. Utilice las funciones de clasificacin de caracteres para preparar una implemen-
y buenos programas! tacin de atoi( ).
5. Cmo podra asignar espacio extra para almacenar un array de estructuras?

445
Respuestas
1. Debe haber un #include < stdio.h > para las decisiones de fichero. Se declara fp como
puntero file: FILE *fp;. La funcin fopen( ) requiere un modo: fopen(galletas", w)
o quiz el modo a. Se debe cambiar el orden de los argumentos de fputs( ). La funcin
Apndice A
fclose( ) necesita un fichero file, no el nombre del fichero: fclose(fp);
2. Abrira el fichero dado como argumento en lnea de ejecucin e imprimira todos los n
meros del fichero. Debera comprobar (pero no lo hace) si existe argumento en lnea de
ejecucin.
3. La primera expresin es correcta, porque c[i] tiene un valor char. La segunda expresin
no molestar al operador, pero puede dar resultados inesperados. La razn es que isal-
Lecturas
pha( ) es una macro, en la que con toda probabilidad aparece su argumento dos veces (com
probacin de minsculas y comprobacin de maysculas), lo cual producira dos incre
mentos en i. Es aconsejable evitar el uso de operadores incremento en los argumentos de
una llamada funcin macro.
4. #include <stdio.h>
adicionales
#include <ctype. h>
#define issign(c) ( ((c) == '-' || (c) == '+') ? (1) : (0) )
atoi(s)
char *s; {
int i = 0;
int n, signo;
while (isspace(s[i]) )
i++; / * salta espacios en blanco * /
Si desea aprender ms acerca de programacin y de programacin en C,
signo = 1;
if (issign(s[i]) / * maneja signo opcional */ encontrar tiles las siguientes referencias. Por lo que sabemos, el libro que
signo = (s[i++] == '+' ) ? 1 : -1; est en sus manos es el primer libro de C en espaol; por tanto, las referen
for (n = 0; isdigit(s[i]); i++)
n = 10*n + s[i] - '0'; cias de lenguaje C se refieren, necesariamente, a lenguas extranjeras.
return(signo*n);
}
5. Supongamos que vino es la etiqueta de la estructura. Las siguientes sentencias, colocadas
adecuadamente en el programa, cumplirn la tarea solicitada.
Lenguaje C
struct vino *ptrvino ; Kernighan, Brian W., y Ritchie, Dennis M.: The C Programming Language.
char *calloc() ; Prentice-Hall, 1978.
ptrvino = (struct vino *) calloc(100, sizeof(struct vino));
Este libro es la mayor autoridad en C, y el primer libro sobre el tema.
Obsrvese que uno de los autores, Dennis Ritchie, es el creador del C. Est
incluida en l la definicin oficial del C y, adems, un gran nmero de ejem
plos interesantes. Sin embargo, supone que el lector est familiarizado con
Ejercicios programacin de sistemas.
1. Escriba un programa de copia de ficheros que utilice el nombre original del fiche Feer, Alan R.: The C Puzzle Book. Prentice-Hall, 1982.
ro y el nombre del fichero copiado como argumento en lnea de ejecucin.
2. Escriba un programa que tome todos los ficheros dados en una serie de argumen Ritchie, D. M.; Johnson S. C.; Lesk, M. E. y Kernighan, B. W.: The C
tos de lnea de ejecucin, y los imprima uno tras otro en pantalla. Utilice arge Programming Languaje, en The Bell System Technical Journal, v. 57,
para establecer un bucle. n. 6, julio-agosto 1978.
3. Modifique nuestro programa de inventario de libros del captulo 14 de tal forma Este artculo discute la historia del C y da una visin de conjunto de las
que la informacin se pueda aadir a un fichero llamado mislibros.
4. Utilizando gets( ) y atoi( ), construya el equivalente a nuestra funcin getint( )
caractersticas de diseo.
del captulo 10. BYTE: v. 8, n. 8, agosto 1983.
5. Escriba de nuevo el programa contador de palabras del captulo 7 utilizando ma-
cros de ctype.h y un argumento en lnea de ejecucin con el fichero a procesar. Este nmero de la revista B YTE est dedicado al C. Incluye artculos que
discuten su historia, filosofa y utilidad. Se estudian y evalan veinte compi-

447
ladores de C para microprocesadores. Tambin se incluye una bibliografa
extensa y puesta al da de libros y artculos sobre C. Cada referencia biblio
grfica incluye un corto resumen del libro o artculo en cuestin.
Apndice B
Programacin
Wirth, N.: Algoritmos + Estructuras de Datos = Programas. Ed. del Casti
Palabras clave
llo, 1983.

Kernighan, Brian W., y Plauger, P. J.: The Elements of Programming Style,


2.a ed., McGraw-Hill, 1978.
en C
Software Tools. Addison-Wesley, 1976.

Ghezzi, C. y Jazayeri, M.: Programming Language Concepts, John Wiley


& Sons, 1982.
Schneider, G. M., y Brueli, S. C.: Advanced Programming and Problems
Solving with Pascal. J. Wiley & Sons, 1981.
Las palabras clave son las palabras empleadas en un lenguaje para expre
El primero es uno de los pocos libros de programacin racional traduci sar las acciones del mismo. Las palabras clave del C son reservadas; es decir,
dos al castellano. Su autor es el inventor del PASCAL. En la misma lnea, tam no se pueden utilizar para otros fines, como nombres de variables.
bin basado en PASCAL, aunque de mayor nivel, es el ltimo libro de la lista.

Sistema operativo UNIX Palabras clave de control de programas


Bucles
Waite, Mitchell; Martin, Don, y Prata, Stephen: UNIX sistema V. Anaya
Multimedia, 1986. for while do
Este libro facilita una introduccin sencilla al sistema operativo UNIX,
incluyendo algunas poderosas mejoras de Berkeley. Decisin y eleccin

if else switch case default

Saltos

break continue goto

Tipos de datos

char int short long unsigned float


double struct union typedef

Modos de almacenamiento
auto extern register static

449
Miscelnea

return sizeof

An no implementado
Apndice C
entry

Disponible nicamente en algunos sistemas


Operadores C
asm endasm fortran enum

El C est lleno de operadores. Presentamos aqu una tabla de los mismos


indicando el rango de prioridad de cada uno, y cmo se ejecutan. A conti
nuacin comentaremos brevemente los operadores, con excepcin de los ope
radores de bit, que se discuten en el apndice F.

451
IV. Operadores lgicos
La accin de estos operadores es la siguiente: Los operadores lgicos utilizan normalmente expresiones de relacin co
mo operandos. El operador ! toma un operando situado a su derecha; el res
I. Operadores aritmticos
to toma dos: uno a su derecha y otro a su izquierda.
+ Suma los valores situados a su derecha y a su izquierda.
&& AND Lgico: la expresin combinada es cierta si ambos operandos lo son, y
Resta el valor de su derecha del valor de su izquierda. falsa en cualquier otro caso.
Como operador unario, cambia el signo del valor situado a su derecha. || OR Lgico: la expresin combinada es cierta si uno o ambos operandos lo son,
* Multiplica el valor de su derecha por el valor de su izquierda. y falsa en cualquier otro caso.
/ Divide el valor situado a su izquierda por el valor situado a su derecha. Cuan ! NOT Lgico: la expresin es cierta si el operando es falso, y viceversa.
do los dos operandos son enteros, la respuesta se trunca.
% Proporciona el resto de la divisin del valor de la izquierda por el valor de
la derecha (slo enteros). V. Operadores relacionados con punteros
+ + Suma 1 al valor de la variable situada a su izquierda (modo prefijo) o de la
variable situada a su derecha (modo sufijo). & Operador direccin: cuando va seguido por el nombre de una variable, entre
--Igual que + +, pero restando 1. ga la direccin de dicha variable: &nana es la direccin de la variable nana
* Operador de indireccin: cuando va seguido por un puntero, entrega el valor
almacenado en la direccin apuntada por l:
II. Operadores de asignacin nana = 22;
ptr = &nana; /* puntero a nana */
= Asigna el valor de su derecha a la variable de su izquierda. val = *ptr;

Cada uno de los siguientes operadores actualiza la variable de su izquier El efecto neto es asignar a val el valor 22.
da con el valor de su derecha utilizando la operacin indicada. Usaremos d
e i para derecha e izquierda. VI. Operadores de estructuras y uniones
El operador de pertenencia (punto) se utiliza junto con el nombre de la estruc
+ = Suma la cantidad d a la variable i. tura o unin, para especificar un miembro de las mismas. Si tenemos una
- = Resta la cantidad d de la variable i. estructura cuyo nombre es nombre, y miembro es un miembro especificado
* = Multiplica la variable i por la variable d. por el patrn de la estructura,
/ = Divide la variable i entre la cantidad d.
% = Proporciona el resto de la divisin de la variable i por la cantidad d. nombre.miembro

identifica dicho miembro de la estructura. El operador de pertenencia pue


Ejemplo: de utilizarse de la misma forma en uniones. Ejemplo:
conejos *= 1.6; es lo mismo que conejos = conejos * 1.6;
struct {
int codigo;
float precio;
III. Operadores de relacin } articulo;

Cada uno de estos operadores compara el valor de su izquierda con el articulo.codigo = 1265;
valor de su derecha. La expresin de relacin formada por un operador y
sus dos operandos toma el valor 1 si la expresin es cierta, y el valor 0 si es Con esto se asigna un valor al miembro cdigo de la estructura artculo.
falsa. - > El operador de pertenencia indirecto: se usa con un puntero estructura o uni
para identificar un miembro de las mismas. Supongamos que ptrstr es un
< menor que puntero a una estructura que contiene un miembro especificado en el pa-
<= menor o igual a trn de estructura con el nombre miembro. En este caso
== igual a
>= mayor o igual a ptrstr>miembro
> mayor que identifica al miembro correspondiente de la estructura apuntada. El opere
!= distinto de dor de pertenencia indirecto puede utilizarse de igual forma con uniones
Ejemplo:
struct {
int codigo;
float precio;
Apndice D
} articulo, *ptrstr;
ptrstr = &articulo;
ptrstr-> codigo = 3451;
Tipos de datos
De este modo se asigna un valor al miembro cdigo de la estructura artculo.
Las tres expresiones siguientes son equivalentes:
y modos de
ptrstr->codigo

VII. Miscelneas
articulo.codigo (*ptrstr).codigo

almacenamiento
sizeof Devuelve el tamao, en bytes, del operando situado a su derecha. El operan
do puede ser un especificador de tipo, en cuyo caso se emplean parntesis;
por ejemplo, sizeof (float). Puede ser tambin el nombre de una variable
concreta o de un array, en cuyo caso no se emplean parntesis: sizeof foto.
(tipo) Operador de moldeado: convierte el valor que vaya a continuacin en el tipo Tipos de datos bsicos
especificado por la palabra clave encerrada entre los parntesis. Por ejem
plo, (float)9 convierte el entero 9 en el nmero de punto flotante 9.0. Palabras clave: Los tipos de datos bsicos se preparan utilizando las 7 pala
El operador coma une dos expresiones en una, garantizando que se evala
bras clave siguientes: int, short, unsigned, char, float, double.
en primer lugar la expresin situada a la izquierda; una aplicacin tpica
es la inclusin de ms informacin en la expresin de control de un bucle Enteros con signo: Pueden ser valores positivos o negativos,
for: int: Es el tipo de entero bsico de un sistema dado,
long o long int: Puede almacenar un entero que, como mnimo, es del tama
for (chatos = 2, ronda = 0; ronda < 1000; chatos *= 2) o del mayor int y, posiblemente, mayor que short o short int; el mayor
ronda += chatos;
entero short es menor o igual que el mayor int, pudiendo ser menor,
long normalmente ser mayor que short, e int ser del mismo tamao que
?: El operador condicional: utiliza tres operandos, cada uno de los cuales es una
expresin; se ordenan de la siguiente forma: uno de los dos. Por ejemplo, el IBM PC Lattice C tiene short e int de
expresin1 ? expresin2 : expresin 3 16 bits y long de 32 bits. Todos estos datos dependen del sistema.
El valor de la expresin completa equivale al de la expresin2 si expresin1 Enteros sin signos: Estos enteros slo pueden tomar valores positivos o 0.
es cierta, y al valor de expresin3 en caso contrario. Ejemplos: Con ello se extiende el rango del mayor valor positivo alcanzable. Utilice
la palabra clave unsigned delante del tipo deseado:
(5 > 3 ) ? 1 : 2 toma el valor 1 unsigned int, unsigned long, unsigned short.
(3 > 5 ) ? 1 : 2 toma el valor 2 unsigned en solitario se considera unsigned int.
(a > b ) ? a : b toma el valor mayor entre a y b. Caracteres: Son smbolos tipogrficos como A, & y + . Generalmente se al
macenan en un byte de memoria cada uno.
char: palabra clave para este tipo.
Punto flotante: Pueden tener valores positivos o negativos,
float: Tamao bsico de punto flotante para el sistema,
double o long float: una unidad (posiblemente) mayor para almacenar n
meros en punto flotante. Puede permitir ms cifras significativas y qui
z exponentes mayores.
MODO DE
Como declarar una variable simple ALMACENAMIENTO PALABRA CLAVE DURACION ALCANCE
externo extern permanente global (todos los fi
1. Escoja el tipo que necesite. cheros)
2. Escoja un nombre para la variable. externo esttico static permanente global (un fichero)
3. Utilice el siguiente formato en una sentencia de declaracin:
especificador de tipo nombre de variable; Los modos situados por encima de la lnea de puntos se declaran dentro
de las funciones.
El especificador de tipo se forma con una o ms de las palabras clave de Los modos situados debajo de la lnea se definen fuera de la funcin.
tipo. Algunos ejemplos:
int eres;
unsigned short presa;

4. Se puede declarar en la misma sentencia ms de una variable del mismo


tipo, separando los nombres de variables por comas:
char ch, init, os;

5. Se puede inicializar una variable en la propia sentencia de declaracin:

float masa = 6.0e24;

modos de almacenamiento
I. Palabras clave: auto, external, static, register

II. Comentarios generales


El modo de almacenamiento de una variable determina el alcance de la
misma y el tiempo que la variable perdura en el programa. El modo de alma
cenamiento queda fijado por la posicin en que se define la variable y por
su palabra clave asociada. Las variables que se definen fuera de las funcio
nes son externas (external), y tienen alcance global. Las variables declaradas
dentro de una funcin son automticas y locales, a menos que se utilice una
palabra clave diferente. Las variables externas definidas antes de una fun
cin son conocidas por la misma incluso si no se declaran explcitamente dentro
de ella.

III. Propiedades
MODO DE
ALMACENAMIENTO PALABRA CLAVE DURACION ALCANCE
automtico auto temporal local
registro register temporal local
esttico static permanente local
Apndice E
Control de flujo
en el programa

En C existen diversas estructuras de control para guiar el flujo del pro


grama. Se resumen aqu las sentencias de bucles (while, for y do while), las
de ramificacin (if, if else y switch) y las sentencias de salto (goto, break y
continue).

La sentencia while

Palabra clave: while

Comentarios generales:
La sentencia while crea un bucle que se repite hasta que la expresin de
test se vuelve falsa o 0. La sentencia while es un bucle con condicin de en
trada. La decisin de atravesar el bucle una vez ms se realiza antes de atra
vesarlo; por consiguiente, es posible ejecutar el bucle 0 veces. La parte de
sentencia del formato puede ser una sentencia simple o compuesta.

Formato:
while ( expresin )
sentencia

La porcin sentencia se repite hasta que la expresin se vuelve falsa o 0.

459
Ejemplos: de salida; la decisin de atravesar el bucle se realiza despus de haberlo atra
vesado. As pues, el bucle debe ejecutarse por lo menos una vez. La parte
while (n++ < 100)
sentencia del formato puede ser una sentencia simple o compuesta.
printf(" %d %d\n, n, 2*n + 1 ) ;
Formato:
while (ronda < 1000)
{ do
ronda = ronda + chatos; sentencia
chatos = 2 * chatos; while ( expresin );
}
La porcin sentencia se repite hasta que la expresin se vuelve falsa o 0.
La sentencia for Ejemplo:
Palabra clave: for
do
scanf("%d", &numero);
Comentarios generales: while (numero != 20);
La sentencia for utiliza tres expresiones de control, separadas por puntos
y comas, para controlar el proceso de bucle. La expresin de inicializacin
se ejecuta una vez, antes de comenzar el bucle. Si la expresin de test es cierta Utilizacin de sentencias if para elegir entre opciones
(distinta de 0) el bucle se ejecuta una vez. A continuacin se evala la expre
sin de actualizacin, y en este momento se comprueba de nuevo la expre Palabras clave: if, else
sin de test. La sentencia for es tambin un bucle con condicin de entrada;
la decisin de atravesar el bucle una vez ms se realiza antes de hacerlo. Es, Comentarios generales:
por tanto, posible que el bucle no se atraviese ni una sola vez. La parte sen
tencia del formato puede ser una sentencia simple o compuesta. En cada uno de los formatos siguientes, la sentencia puede ser simple o
compuesta. Una expresin se considera cierta por generalizacin cuando su
Formato: valor es distinto de 0.
for ( inicializacin ; test ; actualizacin)
sentencia; Formato 1:
if ( expresin )
El bucle se repite hasta que el test se vuelve falso o 0. sentencia

Ejemplo: La sentencia se ejecuta si la expresin es cierta.

for (n = 0; n < 10; n++) Formato 2:


printf(" %d %d\n", n, 2*n + 1);
if ( expresin )
sentencia 1 else
sentencia2
Si la expresin es cierta, se ejecuta la sentencia1. En caso contrario,
La sentencia do while
se ejecuta la sentencia2.
Palabras clave: do, while Formato 3:
Comentarios generales:
if ( expresin1 )
La sentencia do while crea un bucle que se repite hasta que la expresin
sentencia1
le test se vuelve falsa o 0. La sentencia do while es un bucle con condicin
else if ( expresin2 ) Ejemplo:
sentencia2 switch (letra)
else {
sentencia3 case 'a' :
case 'i' : print("%d es una vocal\n", letra);
case 'c' :
Si la expresin1 es cierta, se ejecuta la sentencia1. Si la expresin1 es fal case 's' : printf("%d esta en la palabra \"casi\"\n", letra);
la, pero la expresin2 es cierta, se ejecuta la sentencia2. Si ambas expresio- default : printf("Que usted lo pase bien. \n"):
nes son falsas, se ejecuta la sentencia3. }

Ejemplo: Si letra tiene el valor a o i, se imprimen los tres mensajes; si vale 'c'
o 's' se imprimen nicamente los dos ltimos; cualquier otro valor imprim
if (patas == 4) simplemente el ltimo mensaje.
printf("Debe ser un caballo o.\n");
else if (patas > 4)
printf("No es un caballo. \n") ;
else /* se hace si patas < 4 */
{
Saltos en el programa
patas++;
printf("Ahora tiene una pata mas.\n">; Palabras clave: break, continue, goto
}

Comentarios generales:
Estas tres instrucciones producen un salto en el flujo de programa desde
una localizacin a otra diferente.
Eleccin mltiple con switch
break
Palabra clave: switch El comando break se puede utilizar con cualquiera de los tres formato
de bucle y, adems, con la sentencia switch. Cuando el programa alcanza un
Comentarios generales: break, se deja sin ejecutar el resto del bucle o switch que lo contiene, y se
El control de programa salta a la sentencia etiquetada con el valor de la transfiere el control a la sentencia inmediatamente posterior a dicho bucle
expresin. El flujo de programa contina a travs de la siguiente sentencia o switch.
del switch, a menos que se vuelva a redirigir. Tanto la expresin como las
etiquetas deben tener valores enteros (incluyendo tipo char), y las etiquetas Ejemplo:
deben ser constantes o expresiones formadas por constantes. Cuando ningu-
na de las etiquetas coincide con el valor de la expresin, el control se trans switch (numero)
fiere a la sentencia etiquetada default, si existe. En caso contrario, se enva {
case 4: printf("Excelente eleccion!\n");
el control a la sentencia siguiente al switch. break;
case 5: printf("Es una eleccion razonable.\n");
Formato: break;
default: printf("Su eleccion es un asco.\n");
switch ( expresin ) }
{
case etiq1 : sentencia1
case etiq2 : sentencia2 continue
default : sentencia3
} El comando continue se puede emplear con cualquiera de los tres forma
tos de bucle, pero no con switch. Al igual que el caso anterior, el programa
El nmero de sentencias etiquetadas puede ser mayor de dos, y el caso no ejecuta las sentencias restantes del bucle donde se encuentra situado. En
default es opcional. bucles for o while se comienza a ejecutar el siguente ciclo del bucle. En bu-
cles do while se comprueba la condicin de salida; si se cumple, se comienza
el nuevo ciclo.
Ejemplo:

while ( (ch = getchar() ) != EOF)


if (ch == ' ' )
continue;
putchar(ch);
chcont ++;
}

Este fragmento produce un eco y cuenta caracteres que no sean espacios,


goto
Una sentencia goto hace que el control de programa salte incondicional
mente a la sentencia que contenga la etiqueta indicada. Se utiliza un smbolo
dos puntos para separar la etiqueta de la sentencia etiqueta. Los nombres de
etiquetas siguen las mismas reglas que los nombres de variables. La sentencia
etiquetada puede estar situada antes o despus del goto.

Formato:
goto etiq;
. . .

etiq : sentencia

Ejemplo:

tope : ch = getchar();
. . .
if (ch != 's')
goto tope;

464

You might also like