Professional Documents
Culture Documents
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.
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.
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
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
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.
21
o simplemente
informe.
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 ;
}
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
29
Esta sentencia imprime la frase comprendida entre las comillas:
Soy un modesto
printf ("ordenador. \n")- una nueva sentencia de escritura
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.
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
LO M O = 430.00
P R EC IO = 0.150 * LO M O - 20.0
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 .
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 */
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:
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.
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.
51
Figura 3.2
Almacenamiento del entero 7 en cdigo binario Figura 3.3
Almacenamiento del nmero PI en punto flotante (versin decimal)
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:
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 */
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.)
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
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.
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( )
71
sitio = sizeof nombre;
y scanf( )
}
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
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?
79
que debe ir al comienzo del programa. El preprocesador, por su parte, igno
ra si se ha usado .h o no.
(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/
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).
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?
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;
Operador de adicin: +
El operador de adicin hace que los dos valores situados a su izquierda
y derecha se sumen. Por ejemplo, la sentencia
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
Precedencia en operadores
Supongamos la lnea
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 * /
{ 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.
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
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);
}
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.
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
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:
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)
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:
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-
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 */ }
}
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*/
}
}
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 */
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
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.
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;
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
/* 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.
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
CONCEPTOS
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:
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:
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
Forma 3:
if ( expresin1 )
sentencia1
else if ( expresin2 )
sentencia2
else
sentencia3
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++;
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
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. }
/* 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");
}
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.
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( ):
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
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:
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 */
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.
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.
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
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.
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;
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");
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);
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);
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.
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:
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;
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);
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; )
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;
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.
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:
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];
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.
$$$$$$$$
$$$$$$$$
$$$$$$$$
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
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;
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;
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
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() {
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;
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
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 ;
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:
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 */
I. El operador direccin:
fermin = 22;
ptr = &fermin; /* puntero a fermin */
val = *ptr;
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
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;
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 */
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:
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"
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;
}
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
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;
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
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()
{
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() {
287
azar = (azar * 25173 + 13849) % 65536;
return (azar);
/* formula magica */ EL ORDENADOR PERSONAL PARA...
}
saleat(x)
unsigned x;
{
azar = x;
}
/* 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
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!!!
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) ;
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)
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.
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;
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 */
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:
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");
}
315
que cuando el programa se ejecute, se almacene en un array cuyo ltimo ele
mento sea el carcter nulo. As
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.
se convierte en
printf("CUADRADO (x) es %d.\n", CUADRADO (x)) ;
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:
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
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:
333
anterior, pero con una lista demasiado corta (ms exactamente, demasiados
La salida es corta):
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:
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];
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!).
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 */
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 }
}
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.
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;
/* 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;
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));
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:
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
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") ;
}
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!";
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 */
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:
{{. . . },{...},...,{...}};
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. }
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
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) ;
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.
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;
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
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
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:
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[]; {
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:
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.
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.
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; };
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.
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.
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.
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;
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;
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.
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:
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
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
putchar(get(fp));
Al inicializarse offset a 0, la primera vez que se ejecuta el bucle tendre
mos la expresin
fseek(fp,0L,0)
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.
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));
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.
Saltos
Tipos de datos
Modos de almacenamiento
auto extern register static
449
Miscelnea
return sizeof
An no implementado
Apndice C
entry
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
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;
modos de almacenamiento
I. Palabras clave: auto, external, static, register
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
La sentencia 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
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: 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:
Formato:
goto etiq;
. . .
etiq : sentencia
Ejemplo:
tope : ch = getchar();
. . .
if (ch != 's')
goto tope;
464