índice de Figuras v
Prólogo ix
1 Fundamentos de programación 1
1.1 Estructura de un com putador..................................................................................................... 2
1.2 Representación de la información, en un computador............................................................... 3
1.3 Software y lenguajes de program ación..................................................................................... 6
1.4 Fundamentos de la programación estructurada......................................................................... 7
1.5 El lenguaje de programación C ..................................................................................................... 10
L6 Desarrollo de programas en el lenguaje C ............................................................................... 11
(E7 Solución de problemas de program ación...................................................................................... 15
Evite estos errores..................................................................................................................................... 16
Problemas resu elto s.............................................................................................................................. (17)
Problemas propuestos........................................................................................................................... . 24 ¡
3 Sentencias de control 73
3.1 Introducción a las sentencias de control 74
3.2 Sentencia if ................... 75
3.3 Sentencia if -e 1 se ............. 76
3.4 Sentencia whi 1 e ................................ 76
3.5 Sentencia f o r .................. 77
3.6 Sentencia do-whi 1 e ............ 78
3.7 Sentencia swi t c h ............... 79
Indice General
4 Punteros 99
4.1 Punteros y m em oria..................................................................................................................... 100
4.2 Definición de p u n te ro s.............................................................................................. 101
4.3 Operadores de p u nteros............................................................................................................... 102
4.4 Punteros y memoria d in á m ic a .................................................................................................. 104
Evite estos errores.................................................................................................................................. 104
Problemas resueltos...............................................................................................................................
Problema de examen ........................................................................................................................... ‘ IIJJ
Problemas propuestos........................................................................................................................... 115
7 Estructuras 191
7.1 Definición y procesamiento de estru ctu ras............................................................................... 192
7.2 Arrays de estru c tu ras.................................................................................................................. 200
7.3 U n io n e s ........................................................................................................................................ 201
7.4 Estructuras de datos autorreferenciadas..................................... 203
Evite estos errores.................................................................................................................................. 205
Problemas resueltos...............................................................................................................................( 205,
Problema de e x a m e n ........................................................................................................................... 2 l4 :
Problemas propuestos........................................................................................................................... 227
ii • ITES-Paraninfo
Indice General
Bibliografía 271
ITES-Paraninfo • iii
En este capítulo...
I. I Estructura de un computador
1.2 Representación de la información en
un computador
1.3 Software y lenguajes de programación
1.4 Fundamentos de la programación
estructurada
1.5 El lenguaje de programación C
1.6 Desarrollo de programas en el lenguaje C
1.7 Solución de problemas de. programación
Evite estos errores
Problemas resueltos
Problemas propuestos
Fundam entos de
program ación
Problemas resueltos de C
El objetivo de este capítulo es mostrar al lector algunos aspectos de la estructura de un computador, los
rudimentos de la programación y los elementos más básicos del lenguaje C. A partir de estos conocimientos
se mostrará al lector cuáles son los pasos fundamentales para resolver un problema de programación, desde
su análisis hasta sus posibles optimizaciones.
Los temas que se tratan en el capítulo son:
■ Presentación de la estructura de un computador y de las formas básicas de representar la información.
■ Aspectos básicos del software y de los lenguajes de programación.
■ Conceptos de programación estructurada.
■ Aspectos básicos del lenguaje C.
■ Solución de problemas de programación.
Al final se muestran los errores más frecuentes en los temas tratados y se proponen ejercicios a resolver.
1.1 E s t r u c t u r a de u n c o m p u t a d o r
Todos los computadores personales actuales tienen una estructura similar, en la cual destacan cuatro
componentes básicos: UCP, memoria RAM, dispositivos de almacenamiento y unidades de entrada/salida
(véase Figura 1.1).
La unidad central de proceso (UCP) es el elemento principal del computador, porque es donde se ejecu
tan las instrucciones que se leen de la memoria RAM. Tiene tres elementos principales: unidad de control,
responsable de ejecutar instrucciones y controlar el computador; unidad aritmético-lógica, dónde se reali
zan las operaciones aritméticas y lógicas; banco de registros de alta velocidad, donde se almacenan datos e
instrucciones mientras se ejecutan.
La evolución de la velocidad de procesamiento de la UCP ha sido vertiginosa en los últimos años, de
forma que prácticamente se ha doblado cada año. En el Intel 8080, un microprocesador de 1974, la velocidad
de la UCP era de 2 MHz. La velocidad de reloj del Pentium Pro-200 de Intel, de 1995, era de 200 MHz.
Actualmente, los Pentium IV tienen velocidades de reloj de 1,8 GHz.
La Memoria de acceso aleatorio (RAM) es el sistema de almacenamiento universal de los computadores.
En ella se almacenan todos los datos e instrucciones necesarios para ejecutar los programas, así como
los que se generan durante la ejecución de los mismos. Sin embargo, no proporciona almacenamiento
permanente, ya que su contenido se pierde cuando falta la alimentación eléctrica, por lo que se denomina
memoria volátil. El tamaño de la memoria RAM ha crecido también vertiginosamente. Hace 5 años, los PC
tenían 32 MB de memoria RAM. Actualmente es habitual comprar PC con 512 MB, siendo difícil encontrar
chips de memoria menores de 128 MB, es decir, 128 * 1.024 * 1.024 bytes.
Para solventar el problema de la volatilidad de la memoria RAM y proporcionar mayor capacidad de
almacenamiento, se usan en los computadores los dispositivos de almacenamiento. Estos dispositivos son
2 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
más lentos que la memoria principal, pero permiten almacenar volúmenes grandes de datos, por lo que es
habitual que se usen para almacenar los propios programas cuando no se ejecutan, los datos de entrada y
los de salida, el sistema operativo, etc. Dispositivos de este estilo son los discos Winchester (discos duros),
los disquetes, los CDROM, las cintas magnéticas, etc. El dispositivo más frecuente de almacenamiento
actualmente para los computadores personales es el disco duro, que proporciona almacenamiento mucho
más rápido y de mayor capacidad que los otros. Actualmente es habitual encontrar en un PC discos de 60
GB de capacidad.
Los dispositivos de entrada/salida (E/S) constituyen la base de la comunicación entre el computador y el
resto del mundo, incluyendo usuarios y redes. Actualmente, los PC incluyen dispositivos típicos para llevar
a cabo tres tareas de entrada/salida: interfaz con el usuario, conexión con sistemas de almacenamiento y
conexiones a redes.
1.2 R e p r e s e n t a c ió n de l a in f o r m a c ió n en u n
COMPUTADOR
Los computadores representan la información usando dos dígitos, 0 y 1, es decir, usando el sistema
binario. Los dígitos de este sistema se denominan bits (binary digits). Con este sistema, los números se
calculan usando potencias de 2, de forma similar a como se calculan en el sistema decimal con potencias
de 10.
El valor de un número se calcula usando potencias crecientes de 2, empezando por 0.
El paso inverso, es decir, pasar de decimal a binario es igualmente fácil. Sólo hay que dividir por 2
sucesivamente hasta que se termine el número, de la forma siguiente:
1. Divida el número por 2.
2. El resto es el valor del bit de la posición binaria más baja (0).
3. Divida el cociente por 2.
4. El resto es el valor del bit de la posición binaria siguiente.
5. Repita los pasos 3 y 4 hasta que no se pueda dividir más, es decir, el cociente es 0.
Los números binarios se pueden representar fácilmente en otras potencias cuya base es múltiplo de 2,
como la de base ocho, u octal, y la base 16, o hexadecimal.
Una forma fácil de pasar de binario a octal es agrupar los bits de 3 en 3 comenzando por la derecha y
representar su número octal:
Binario Dígito Octal
000 0
001 1
010 2
011 3
100 4
101 5
110 6
111 7
De forma similar se puede representar la información en base hexadecimal (16). Basta con agrupar los
bits en grupos de 4 comenzando por la derecha y representar sus números equivalentes en hexadecimal:
Binario Hexadeci mal Deci mal Bi nari o Hexadeci mal Dec
0000 0 0 1000 8 8
0001 1 1 1001 9 9
0010 2 2 1010 A 10
0011 3 3 1011 B 11
0100 4 4 1100 C 12
0101 5 5 1101 D 13
0110 6 6 1110 E 14
0111 7 7 lili F 15
ITES-Paraninfo • 3
Problemas resueltos de C
4 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
sM ■B sE
Donde:
■ s es el signo + o -.
■ M es la mantisa o parte significativa.
■ B es la base, que en el caso binario es 2.
■ E es el exponente, que también puede tener signo.
Una forma habitual de representar los números en coma flotante en el computador consiste en utilizar el
estándar IEEE 754, que descompone un número de 32 bits de la siguiente forma:
signo exponente mantisa
bit 31 bits 30-23 bits 22-0
donde:
■ La mantisa se representa en signo magnitud, siendo el signo el bit 31. Las mantisas se representan de
forma normalizada, con un 1 situado siempre a la izquierda de la coma. Así, la mantisa 1.0011, se
representa como 0011. Como puede observarse, sólo se almacena la parte decimal, el bit situado a la
izquierda de la coma no se almacena y se considera implícito dentro de la representación.
ITES-Paraninfo • 5
Problemas resueltos de C
1.3 S o f t w a r e y l e n g u a je s de p r o g r a m a c ió n
Por sí mismo, un computador no haría nada útil. Debe haber un programa que dirija al computador
para realizar alguna tarea específica. Por supuesto, la habilidad para programar un computador para realizar
diferentes tareas es lo que hace al computador más potente. Un programa es una secuencia de instrucciones
que le dice al computador qué debe hacer en un lenguaje que entiende.
A medida que la arquitectura y la programación de los computadores se fue complicando, los fabricantes
de computadores empezaron a proporcionar utilidades para hacer más fácil su administración y programa
ción. Así, a mediados de la década de 1960 se pasó de los lenguajes de control de programas a los sistemas
operativos y de los ensambladores a los compiladores de lenguajes de alto nivel. Para distinguir estos pro
gramas de los que desarrolla cada programador, se han acuñado los términos software de sistema y software
de aplicación, respectivamente.
Softw are de
A p lic a c ió n P ro g ra m a s de usuario, a p lica cio n e s
H a rd w a re
6 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
software de aplicación pueda acceder al hardware subyacente. Los sistemas operativos para computado
res personales más populares actualmente son Windows 2000 y LINUX. Además de su software propio
incluyen muchas bibliotecas y utilidades externas al sistema operativo, pero que también se consideran
software de sistema.
■ Software de aplicación, es decir, aplicaciones que facilitan el acceso a los servicios del computador.
A veces, como en el caso de Windows, son estas aplicaciones las que caracterizan al sistema de com
putación. En el caso de Windows, por ejemplo, su interfaz gráfica, su explorador y sus herramientas
ofimáticas constituyen software de aplicación muy útil para los usuarios. Los navegadores Web y las
utilidades de correo electrónico son también ejemplos de aplicaciones muy útiles. El software de apli
cación es en buena parte el responsable del éxito de los computadores actuales, ya que proporciona
utilidades con gran valor añadido para los usuarios, como las de autoedición, las bases de datos, las
hojas de cálculo, los gestores de presentaciones, etc.
Además del sistema operativo y sus bibliotecas, el software de sistema incluye editores para introducir
textos y programas en el computador, compiladores, intérpretes, etc. Los compiladores son especialmente
importantes, ya que permiten traducir un programa escrito en un lenguaje de alto nivel a un formato inter
medio, denominado código objeto. Este código objeto se traduce a lenguaje máquina mediante un enlazador
(,linker). Cada lenguaje necesita su propio compilador, pero el enlazador es el mismo para cada arquitectura
y tipo de archivo ejecutable.
Hay muchos lenguajes de programación, al igual que hay muchas lenguas en el mundo. Sin embargo,
se pueden clasificar fácilmente atendiendo a su complejidad en:
■ Lenguajes máquina. Cada UCP entiende su propio lenguaje, denominado lenguaje máquina. Las ins
trucciones de lenguaje máquina están codificadas en binario y son de muy bajo nivel — una instrucción
de máquina puede transferir los contenidos de una posición de memoria a un registro de la UCP o sumar
números almacenados en dos registros. Incluso en los computadores actuales, el lenguaje máquina es
bastante primitivo y escribir un programa directamente en lenguaje máquina es tedioso, ya que se debe
usar directamente la codificación binaria de las instrucciones.
■ Lenguajes ensamblador. Permiten una programación simbólica con instrucciones lógicas, que posterior
mente se traducen a lenguaje máquina. Puesto que los programas escritos en lenguaje ensamblador no
son reconocidos por la UCP, es necesario usar un ensamblador para traducir los programas en lenguaje
ensamblador a sus equivalentes en lenguaje máquina. Comparado con escribir programas en lenguaje
máquina, escribir programas en lenguaje ensamblador es mucho más rápido, pero no suficientemente
rápido como para escribir programas complejos.
■ Lenguajes de alto nivel. Permiten a los programadores expresar estructuras de datos y de control de
forma más sencilla y más parecida a la lógica de las aplicaciones que a las necesidades arquitectónicas
del computador. El primero de ellos fue el FORTRAN, del inglés Formula Translator, es decir, traductor
de fórmulas. Con este lenguaje se podían expresar directamente estructuras de control, como f or, if o
whi 1e, declarar estructuras complejas, como vectores o matrices, y operaciones matemáticas, como la
suma, la división o expresiones compuestas. Durante los años siguientes surgieron distintos lenguajes de
alto nivel, como COBOL (COmmon Business OrientedLanguage), PL-I, Pascal, C, C++, Ada, Java, etc.
Casi todos ellos mejor adaptados, o diseñados, para el desarrollo de un tipo de aplicaciones determinado.
1.4 F u n d a m e n t o s de l a p r o g r a m a c ió n e s t r u c t u r a d a
Los grandes avances en el campo del hardware de los computadores no se han correspondido con avan
ces equivalentes en el desarrollo de programas, una tarea que sigue siendo fundamentalmente manual. El
problema es que el hardware sin software no funciona y, por tanto, todos los usuarios tienen grandes ex
pectativas respecto al software de aplicación, lo cual supone que el trabajo necesario para desarrollar las
aplicaciones es cada vez mayor.
La producción de aplicaciones más potentes suele significar la presencia de una complejidad cada vez
mayor. Sin embargo, y sea cual sea la complejidad del sistema, el software producido debe ser fiable,
adaptable, reusable y mantenible.
ITES-Paraninfo • 7
Problemas resueltos de C
Para conseguir estos objetivos, es necesario aplicar de forma rigurosa criterios de diseño claros y bien
definidos que permitan hacer frente a la complejidad de las aplicaciones, para lo cual en C se pueden usar
técnicas de diseño y programación estructurada.
La programación estructurada tiene varios principios básicos:
■ Dividir la aplicación en módulos autocontenidos.
■ Dividir la funcionalidad de cada módulo usando funciones y procedimientos, cada uno de los cuales
debe encargarse únicamente de resolver una tarea.
■ Programar las funciones usando una estructura de bloques.
■ Cada bloque de código debe tener un punto único de entrada y de salida, lo que implica que no se
deben usar sentencias de salto incondicional (como goto), que son tan populares en algunos lenguajes
de programación como FORTRAN o BASIC.
■ Cuando el computador finaliza la ejecución de un bloque, continúa con otro o finaliza.
Un módulo es un archivo de código que incluye una serie de funciones o procedimientos que realizan
tareas similares o relacionadas lógicamente entre sí. Utilizando un módulo se pueden definir tipos de da
tos abstractos. Un tipo de datos abstracto es un tipo de datos declarado conjuntamente con una serie de
operaciones que manipulan dicho tipo de datos. Para modularizar una aplicación, se puede usar un esque
ma de diseño arriba-abajo (top-down) que, empezando por lo más general, permita descender hasta lo más
particular. Este proceso se denomina especialización. A través de este proceso se obtiene una jerarquía
que muestra la relación existente entre módulos comunes. Estas jerarquías permiten comprender mejor las
estructuras complejas, al aplicar el principio de ’’divide y vencerás”.
El empleo de módulos para encapsular a un tipo de datos abstracto permite separar la definición del
tipo de su posible implementación. Desde el punto de vista de utilización del módulo, lo importante es la
funcionalidad y la interfaz que ofrece, no tanto la forma en la que se ha implementado dicha funcionalidad.
La división de una aplicación en una serie de módulos independientes presenta varias ventajas:
1. Los módulos permiten encapsular partes del sistema mediante interfaces bien definidas, de forma que
se facilita la ocultación de la información y el desarrollo de tipos de datos abstractos.
2. Facilita el desarrollo, ya que cada módulo incluye una serie de tareas lógicamente relacionadas que
pueden ser desarrolladas por un equipo de programadores distinto.
3. Cada uno de los módulos puede ser compilado y probado por separado. Debido a que cada módulo
incluye una pequeña parte de la funcionalidad de todo el programa, su prueba es más sencilla que la
prueba del todo el programa. Esto, a su vez, permite obtener un código más fiable ya que sobre una
pequeña parte del programa se pueden realizar más pruebas que sobre toda la aplicación final.
4. Un mismo módulo puede ser utilizado en varias partes de un mismo programa, con lo que se reduce el
esfuerzo de programación. Además, un módulo se puede utilizar en programas distintos que requieran
funcionalidad similar.
Las funciones y procedimientos son segmentos de código fuente independientes que se encargan de
resolver una tarea determinada. Es preferible diseñar una función distinta para cada cometido que tener
cajones de sastre que hacen varias cosas en función de datos de entrada. Una función debe declararse
primero y después definirse. La declaración de una función en C se realiza mediante lo que se denomina
prototipo de una función, que consta del nombre de la función y de información importante relativa a la
misma. La definición de una función en C incluye el código fuente utilizado por la función.
Un código fuente estructurado en bloques está formado por grupos de instrucciones con una entrada y
una salida bien definidas. Antes de usar la programación estructurada, los programas se construían como
listas continuas de instrucciones, sin ningún tipo de estructura. Las rupturas de secuencia se hacían mediante
instrucciones de salto goto, al estilo del lenguaje ensamblador. El uso indiscriminado de sentencias goto
hace muy difícil leer y entender un programa.
En programación estructurada se definen tres tipos de bloques (véase la Figura 1.3):
■ Bloque secuencial. Conjunto lineal de sentencias.
■ Bloque repetitivo. Bucle que permite la posibilidad de repetir un conjunto de sentencias de código. Por
ejemplo, for, whi 1e.
■ Bloque de selección o bifurcación. Permite la posibilidad de ejecutar un bloque u otro dependiendo del
valor de entrada de la condición del bloque de selección.
8 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
í 4" i
?
fa lso verdadero fat
A cció n
D e cisió n D ecisió n
f
1 verdadero
A cció n
▼
A cción A cció n
|
w
A cció n
f
Secuencial Selección Repetición
Todos los programas se pueden diseñar usando las técnicas y bloques de la programación estructurada.
Cualquier algoritmo se puede convertir a su equivalente estructurado. Por lo tanto, no hay ninguna excusa
para escribir un programa que no tenga estructura de bloques.
RESOLUCIÓN. En un computador, por ejemplo, se puede cambiar la UCP o un disco duro sin que los
cambios sean aparentes una vez terminados.
Por ejemplo, si se proporciona una función lista para los usuarios de una agenda electrónica, el usuario
no necesita saber qué tipo de estructura de datos se usa para la lista (array, punteros, etc.). Le basta con
saber el máximo de usuarios que se admite en la agenda.
En un automóvil, los usuarios no necesitan saber si la marca del servofreno es BOSCH y qué tipo de
tecnología usa para controlar la frenada.
¿Cómo identificar los módulos y funciones de un sistema? No hay un sistema umversalmente aceptado,
por lo que cada diseñador usa sus propias técnicas. Sin embargo, sí hay heurísticas ampliamente aceptadas
que dan buen resultado. Una de ellas es leer cuidadosamente las especificaciones del sistema y hacer lo
siguiente:
1. Identifique los nombres comunes en la descripción. Por ejemplo, automóvil, cuenta bancaria, etc. Suelen
denotar tipos estructurados de datos, cuyo tratamiento se agrupa en un módulo.
2. Identifique las frases que indican una relación ”es”. Por ejemplo, un Opel Vectra es un automóvil. Suelen
indicar la relación entre un tipo de objeto y el módulo en el que se debe tratar.
3. Identifique los adjetivos que califican a los nombres. Por ejemplo, color, cilindrada, etc. Suelen ser
atributos del tipo estructurado de datos.
4. Identifique los verbos de las frases. Por ejemplo, un cliente compra un automóvil y lo matricula. Suelen
identificar funciones (u operaciones) que soportan la funcionalidad del tipo estructurado de datos.
Al final de este trabajo hay que repasar de nuevo los tipos estructurados, sus funciones y la agrupación
modular realizada, porque en muchos casos se pueden hacer reagrupaciones o derivar módulos más parti
culares para subconjuntos del tipo estructurado. Por ejemplo, los vehículos son automóviles y los turismos
son un subconjunto de los automóviles.
EJEMPLO 1.3 Identifique tipos estructurados de datos, atributos y funciones a partir de la siguiente descripción:
Todo paciente de hospital tiene una historia clínica en la que se incluye su nombre, apellidos, dirección y
edad como datos identificativos. Además, incluye una lista de enfermedades posibles y de actos médicos
realizados sobre dicho paciente. La historia se puede crear, destruir y archivar. Además, se puede modificar
y actualizar.
ITES-Paraninfo • 9
Problemas resueltos de C
1.5 E l l e n g u a j e de p r o g r a m a c ió n C
La historia de C es ciertamente curiosa, ya que este lenguaje de programación no surge de un proceso
de diseño deliberado. A primeros de la década de 1970, un pequeño equipo de investigación de los labo
ratorios Bell, de ATT, dirigido por Brian Thompson, comenzó a desarrollar un nuevo sistema operativo
para controlar un minicomputador PDP-11. El sistema operativo se denominó UNICS (Unique Computer
System) y con el tiempo acabaría siendo UNIX. Uno de los principales problemas que encontraron fue la
dificultad de programar en el lenguaje ensamblador del PDP-11, además de los problemas de transporte a
otros computadores que planteaban los sistemas escritos en ensamblador. Debido a ello, entraron en contac
to con Dennis Ritchie, un diseñador de lenguajes de programación de los laboratorios Bell. Thompson ya
había desarrollado otros dos lenguajes, denominados A y B, para programar UNIX, por lo que Ritchie tomó
muchas ideas de B, y de un lenguaje anterior denominado BCPL, y diseñó el lenguaje de programación C.
C es un lenguaje de propósito general ampliamente utilizado, cuyas principales características pueden
resumirse en los puntos siguientes:
■ Presenta características de bajo nivel: C trabaja con la misma clase de objetos que la mayoría de los
computadores (caracteres, números y direcciones). Esto permite la creación de programas eficientes.
■ Está estrechamente asociado con el sistema operativo UNIX. UNIX y su software fueron escritos en C.
■ Es un lenguaje adecuado para programación de sistemas por su utilidad en la escritura de sistemas
operativos, ya que C permite tener un control casi absoluto del computador.
■ Es adecuado también para cualquier otro tipo de aplicación, ya que ha sido diseñado sin limitaciones y se
puede extender fácilmente. Además, se ajusta muy bien a los esquemas de diseño jerárquico (top-down).
■ Es un lenguaje relativamente pequeño: sólo ofrece sentencias de control sencillas y funciones.
■ No ofrece mecanismos de E/S (entrada/salida). Todos los mecanismos de alto nivel se encuentran fuera
del lenguaje y se ofrecen como funciones de biblioteca.
■ Permite la creación de programas transportables, es decir, programas que pueden ejecutarse sin cambios
en multitud de computadores.
■ Permite programación estructurada y diseño modular, lo que mejora la apariencia, la comprensión y el
mantenimiento de los programas.
Sin embargo, C tiene características, como las siguientes, que permiten su mal uso:
■ No es un lenguaje fuertemente tipado.
■ Es bastante permisivo con la conversión de datos.
■ Sin una programación metódica puede ser propenso a errores difíciles de encontrar.
■ La versatilidad de C permite crear programas difíciles de leer, como el que se muestra a continuación.
El Programa siguiente, por ejemplo, da como resultado el cálculo del número 1.000 aunque nadie lo
diría a simple vista. Puede probar a ejecutarlo para ver su resultado. Existen concursos de programadores
C ofuscados que se dedican a escribir este tipo de programas1. Sin embargo, este tipo de programación es
aceptable únicamente como divertimento.
10 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
Re s o l u c i ó n .
#define _ -F<00|| --F-00--;
int F=00,00=00;
mai n()jF_00();printf("%\.3f\n",4.*-F/00/00);}F_00()
1
¿Habría adivinado que suma 1.00? Difícilmente. Nunca haga programas de este estilo, salvo cuando esté
en un concurso de programación (en C son muy populares).
1.6 D e s a r r o l l o de p r o g r a m a s en el l e n g u a j e C
El desarrollo del software requiere, en cualquier lenguaje y entorno, realizar una serie de operaciones de
finidas, que se conocen como el ciclo de desarrollo de un programa o ciclo de edición/compilación/ejecución
(véase la Figura 1.4). Cuando se desarrollan programas, los programadores editan en primer lugar archivos
de texto, denominados código fuente. Estos archivos contienen la especificación del programa en el len
guaje de programación elegido, pero distan mucho de poder ser ejecutados. Para ello, es necesario compilar
dichos programas para obtener el código objeto, un código representado en un formato binario intermedio
que puede ser enlazado con otros módulos ya compilados y con bibliotecas para obtener un código ejecu
table. Este último sí que se puede cargar en un computador y ser ejecutado repetidamente. Después de ver
el comportamiento del programa, los programadores suelen encontrar problemas o quieren hacer cambios
al programa, en cuyo caso se inicia de nuevo el ciclo mediante la edición del código afecto por los cambios.
Como se muestra en la Figura 1.4, para poder realizar un programa en C se necesitan, entre otros, los
siguientes elementos:
■ Editor de texto.
■ Preprocesador.
■ Compilador.
Código fiiente
Ejecutable
ITES-Paraninfo • / /
Problemas resueltos de C
■ Ensamblador.
■ Archivos de cabecera.
■ Archivos de biblioteca.
■ Enlazador.
■ Depurador.
El preprocesador incluye dentro del archivo de código fuente los archivos indicados mediante la directiva
#i ncl ude. Además, sustituye las macros definidas mediante la directiva #def ine por sus valores reales
■(por ejemplo, PI por 3.141516).
Un componente clave de estos sistemas es el compilador, un programa que lee un programa escrito en
un lenguaje de programación y genera un programa de salida en lenguaje ensamblador o en otro lenguaje
de programación distinto al del origen. La entrada al compilador se denomina código fuente y la salida
se denomina código objeto. Los traductores se dividen típicamente según su lenguaje fuente y destino.
Habitualmente, los compiladores de C usan como código fuente programas en lenguaje C y generan como
código de salida programas en el lenguaje ensamblador del sistema destino. Este código ensamblador es
ensamblado posteriormente para obtener un lenguaje máquina binario. Un programa en lenguaje máquina
binario se denomina a menudo código objeto. El proceso de usar un compilador para traducir un programa
en lenguaje de alto nivel se denomina compilación.
Otro componente clave es el enlazador. Un enlazador combina archivos objetos y bibliotecas de forma
que puedan ser ejecutados como una sola unidad. Una biblioteca es un recipiente que contiene archivos
de código objeto que han sido desarrollados para cumplir alguna tarea o función en particular. Todos los
entornos de desarrollo de C incluyen varias bibliotecas estándares, como la 1ibe, y algunas específicas de
ese entorno, como las interfaces gráficas de usuario (GUI, Graphical User Interface). Si se quiere fabricar
código transportable, es necesario usar siempre las bibliotecas estándares. La salida del enlazador es un
código que puede ser ejecutado por el computador. Este código se denomina a menudo ejecutable. Usando
una herramienta del sistema operativo denominada cargador, el archivo ejecutable puede cargarse en la
memoria del computador y ser ejecutado.
Durante el resto del libro se van a presentar numerosos ejemplos y problemas resueltos en C en los que
se evitará el mal uso del lenguaje y se usarán los criterios de programación estructurada mostrados. Sin
embargo, nos parece interesante presentar aquí un primer ejemplo sencillo y explicar sobre el mismo los
elementos fundamentales del lenguaje.
#include <stdio.h>
#defi ne DESTINO "Lector"
/* Programa principal *1
int main(int arge, char **argv)
La primera parte del Primer Programa se compone de tres elementos: comentarios, directivas del pre
procesador y definición de la función principal. Estos tres componentes se incluyen universalmente en todos
12 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
los programas de C. Se puede escribir un programa C que incluya una única definición de función, pero eso
no es normal. En cualquier programa no trivial, verá estos tres componentes:
■ Comentarios dentro del programa. En todos los lenguajes, los programas contienen comentarios además
de las instrucciones. En ellos se define el propósito del programa, se explica el significado del código
y se proporciona cualquier otra descripción que pueda ayudar a los programadores a comprender el
programa. En C, un comentario comienza con el símbolo /* y finaliza con */, todo lo que haya en
medio de estos símbolos no será tenido en cuenta por el compilador.
En la nueva revisión del estándar de C aparecida en diciembre de 1999, se ha añadido un nuevo tipo de
comentario, similar al utilizado en C++. Con / / se puede incluir un comentario de línea de la siguiente
forma:
// muestra un mensaje por pantalla
p rin tfC 'H o la \ n " );
■ Archivos incluidos dentro del programa y macros. Las definiciones de este bloque permiten al prepro
cesador de C saber que:
• Debe incluir en el archivo del programa los archivos que se indican con la directiva #i ncl ude. En este
caso se trata de std i o . h, un archivo con funciones para imprimir el mensaje por pantalla.
• En las macros, debe sustituir las secuencias de la izquierda que se indican con la directiva #def i ne
por las que están a la derecha. En este caso, se sustituirá DESTINO por L ector en todo el programa.
Hay que indicar que la inclusión de archivos y la definición de constantes son tratadas por el preproce
sador en una etapa previa a la compilación.
■ Definición de funciones. Todo programa C debe tener al menos una función: la función principal de
nominada ma i n, que es la que se ejecuta en primer lugar. Además de ésta, se pueden definir todas las
funciones necesarias. No importa que mai n esté al principio o al final del programa, lo que importa
es que sea única y que incluya alguna sentencia entre las llaves { y }, que delimitan la extensión del
programa principal.
La función mai n llama a otra (pri ntf) para escribir por la pantalla:
printf ("Hola aprendiz de programador en C \n");
Además de esos bloques, es necesario conocer los delimitadores de los mismos. Así, en el lenguaje C:
■ Todas las sentencias terminan con ;. A excepción de las definiciones de función y las que acaban en } o
cierran un comentario.
■ Todas las sentencias de una función deben estar encerradas entre { y }. Estas llaves delimitan bloques de
código que son significativos para el compilador.
■ Todos los comentarios se incluyen entre /* y */. Lo que haya en medio de esos símbolos no es tenido
en cuenta por el compilador y no significa nada desde el punto de vista de la ejecución del programa.
EJEMPLO 1.7 Implementación del primer programa usando la función imprimí resal udo.
ITES-Paraninfo • 13
Problemas resueltos de C
Re s o l u c i ó n .
/*
* Esta es una versión de su primer programa en C.
* Imprime un saludo por pantalla usando una función.
*/
#include <stdio.h>
#define DESTINO "Lector"
/* Función imprimí'r_saludo */
void imprimir_sa1udo (char * destino)
(
printf("Gracias por ser nuestro Xs\n", destino);
/* Programa principal */
int main(int argc, char **argv)
I
printf ("Hola aprendiz de programador en C \n");
/* llamada a la función */
imprimir_saludo (DESTINO);
14 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
” EJEMPLO 1.8 Programa que realiza entrada y salida básica desde consola para distintos tipos de datos.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
!* Definición de variables */
int i = 0; /* variable de tipo entero *!
float f = 0.0F; /* variable de tipo real *!
char c = ’a ’ ; /* variable de tipo carácter */
charti ra[32]; /* cadena de caracteres (string) */
Como puede observar, se utiliza el especificador de formato Xd para leer y escribir enteros, el especifica-
dor Xf para números reales, el especificador Xc para los caracteres y el especificador Xs para las cadenas de
caracteres. Observe que para leer una cadena de caracteres no se utiliza el operador &. Aunque este aspecto
se discutirá más adelante en el libro, baste decir que la variable utilizada para representar una cadena de
caracteres, representa la dirección de memoria en la que se almacena la cadena y por tanto no es necesario
utilizar el operador de dirección.
1.7 So l u c ió n de p r o b l e m a s de p r o g r a m a c ió n
Para resolver problemas de programación, hay que realizar seis etapas: análisis, diseño, codificación,
pruebas, validación y optimización. Es importante realizar bien cada uno de los pasos para conseguir pro
gramas bien construidos, robustos y correctos.
Paso 1. Análisis de requisitos.
El análisis indica la especificación de requisitos del cliente. Por ejemplo, en un buscador de números
dentro de un vector un requisito puede ser minimizar el número de operaciones de búsqueda.
Hay que responder a varias preguntas:
■ ¿Cuáles son las entradas del programa? ¿Qué formato tienen? ¿Cómo se van a leer?
■ ¿Qué salida debe producir el programa? ¿En qué formato? ¿Dónde se va a escribir?
■ ¿Qué pasos son necesarios para poder procesar la entrada hasta llegar a la salida?
ITES-Paraninfo • 15
Problemas resueltos de C
diseñar algoritmos para llevar a cabo los pasos de procesamiento. Los algoritmos están subyacentes en todos
los programas y aplicaciones. Si se quiere resolver un problema matemático o de organización, es necesario
diseñar uno o varios algoritmos que reflejen la solución del problema antes de afrontar su programación en
un computador. Al fin y al cabo, un computador no es capaz de resolver los problemas por sí mismo, sólo
ejecuta lo que se le programa. Por otra parte, un programa bien hecho pero basado en un algoritmo mal
diseñado proporcionará un rendimiento muy pobre.
Paso 3. Codificación del programa en C.
En la fase de codificación, se implementa el diseño de un programa real, en nuestro caso, un programa C.
Para evitar posibles comportamientos anómalos es necesario tratar de forma exhaustiva todos los posi
bles errores que puedan surgir en un programa. Para ello hay que especificar dichos errores cuando se diseña
el programa en cuestión. Por ejemplo, un aspecto importante a tratar en todos los programas es comprobar
que la introducción de datos se realiza de forma correcta.
Paso 4. Prueba del programa.
Cuando la codificación está completa, se pasa a la fase de prueba. En esta fase, se ejecuta el programa
usando distintos tipos de datos para verificar que el programa funciona de acuerdo a la especificación.
Se pueden realizar dos pruebas en los programas: pruebas unitarias y pruebas de integración. Es de
cir, pruebas de la funcionalidad de cada función individualmente y pruebas de que las funciones trabajan
juntas correctamente. La actividad que se lleva a cabo para eliminar errores de programación se denomina
depuración.
Para probar bien un programa no hay que esperar a tenerlo todo codificado. Se puede desarrollar en
varias fases y probar que cada una de ellas es correcta antes de pasar a las siguientes.
Paso 5. Validación del programa.
Finalmente, después de que se han llevado a cabo las pruebas con éxito, se entra en la fase de validación,
en la cual el programa se pondrá en funcionamiento real.
Para validar bien un programa es necesario desarrollar un conjunto completo de pruebas de la funciona
lidad del programa. Además, en este paso se comprueba si el programa cumple los requisitos especificados:
¿Maneja bien la entrada? ¿La interfaz de usuario está bien? ¿Qué ocurre cuando recibe datos erróneos? Para
validar un programa es necesario ejecutar también casos erróneos y ver si se detectan.
Si todo va bien se entra en la fase de operación. La actividad más importante y más costosa dentro de la
fase de operación es el mantenimiento del software.
E v it e es t o s er r o r es
En esta sección se muestran algunos de los errores de programación que se cometen más frecuentemente
cuando se programa con los elementos del lenguaje C mostrados en este Capítulo. Leer esta sección será de
gran ayuda para que el lector pueda evitar cometer estos mismos errores. Con ello podrá eliminar un alto
porcentaje de los errores que se cometen habitualmente al escribir programas en C.
Omisión del punto y coma Todas las sentencias de C deben terminar con punto y coma.
Comentarios anidados Recuerde que los comentarios no se pueden anidar, es decir, dentro de un comen
tario no puede aparecer el símbolo /*.
Comentarios mal finalizados Un comentario debe comenzar con el símbolo / * y finalizar con * /. Si se
olvida alguno de estos símbolos se provocará un error.
Olvido de declarar la función ma in Todos los programas C deben tener una función ma in en su programa
principal.
16 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
Olvido de las llaves de la función ma i n Todas las sentencias de la función ma i n, y en general de todas las
funciones, deben ir encerradas entre { y 1.
P r o b l e m a s R e s u e l t o s ___________________________
fr lLII Obtenga el valor decimal del número binario 11000001.
Re s o l u c i ó n .
v = 3 • 82 + 0 • 81 + 1 • 8o = 3 • 64 + 1 = 193
► 13 Convierta a binario los números 4, -5, 0, -17, 127 y -127 usando 8 bits en signo-magnitud.
Re s o l u c ió n .
4 -> 00000100
-5 -> 10000101
0 -> 00000000
-17 -> 10010001
127 -> 01111111
-127 -> 11111111
ITES-Paraninfo • / 7
Problemas resueltos de C
I ► 1.6 Convierta a binario los números 4, -5, 0, -17, 127 y -127 usando 8 bits en complemento a 2.
Re s o l u c i ó n .
4 -> 00000100
-5 -> 11111011
0 -> 00000000
-17 -> 11101111
127 -> 01111111
-1 2 7 -> 10000001
11.01 = 1.101-2 1
Una vez normalizado, ya se dispone del bit de signo, en este caso 0 (el número es positivo), y de la
mantisa a almacenar, en este caso, 101. Recuerde que el bit situado a la izquierda de la coma se considera
implícito y no se representa.
Basta por obtener el exponente a representar. Como se parte del número 1.101 • 21, el exponente a
representar será 127+1 = 128. Por lo tanto, el número 3.25 se representa de la siguiente forma:
signo exponente mantisa
1 10000000 10100000000000000000000
► 1.8 Sea una máquina como un interruptor electrónico con distintas intensidades, que tiene únicamente cuatro
instrucciones como las siguientes:
Descripción de la instrucción Codificación binaria
Reiniciar el interruptor 0 0 0 0 0 00 0
Registrar pulsación encendido 0001 xxxx
Registrar pulsación apagado 0010 0000
Apagar el interruptor 0011 0000
Escriba un programa que encienda y apague el interruptor de la máquina anterior con distintas intensi
dades, suponiendo que las intensidades están a partir de la posición de memoria 1100.
RESOLUCIÓN. Un resumen del programa pedido sería como el siguiente:
Posición de
Memori a Instrucci ón Comentan' os
18 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
1.9 ¿Cuántas instrucciones de ensamblador serían necesarias para codificar programas para el interruptor ante
rior?
RESOLUCIÓN. Para el interruptor anterior sería suficiente con un ensamblador de cuatro instrucciones,
que podrían ser las siguientes:
Cód igos Instrucci ón Comentan’os
ensamblador máquina
. 10 Diseñe los módulos básicos de una agenda electrónica que incluya un libro de direcciones, citas, notas y
calendario.
RESOLUCIÓN. Si se aplica un enfoque de diseño arriba-abajo se pueden obtener los módulos que se
muestran en la Figura 1.5. Como se puede ver, el diseño sigue un esquema jerárquico bien estructurado
en el que cada módulo descansa sobre la funcionalidad de los inferiores. Para el diseño se ha seguido un
enfoque arriba-abajo que va de lo general a lo particular. El módulo citas y calendario hace uso del módulo
fechas y horas que ofrece utilidades para manipular fechas y horas, como obtener la fecha y hora actual o
comparar fechas.
Cada módulo se puede diseñar de forma separada y aplicando el principio de ocultación de información.
Por ejemplo, el módulo citas se puede ver como un tipo de datos abstracto que incluirá un tipo de datos
para representar las citas del usuario, junto con una serie de funciones que manipularán dicha estructura
de datos, como por ejemplo insertar una nueva cita o eliminar una cita. En un computador, por ejemplo, se
puede cambiar la UCP o un disco duro sin que los cambios sean aparentes una vez terminados.
.1 I Especifique los requisitos de un programa que busque un número dentro de un vector de 1.000 elementos
enteros positivos ordenados de menor a mayor. Para comprobar su eficiencia debe imprimir el número de
pasos necesarios para la búsqueda.
RESOLUCIÓN. El programa debe ser capaz de leer el número a buscar.
El programa debe ser capaz de calcular los pasos de búsqueda y de imprimirlos por pantalla.
El programa debe recorrer el vector para encontrar el número objetivo.
El programa debe terminar cuando se haya encontrado el número buscado.
ITES-Paraninfo • 19
Problemas resueltos de C
► 1.12 Partiendo del análisis anterior, diseñe un programa que busque un número dentro de un vector de 1.000
elementos enteros.
RESOLUCIÓN. En este caso, el diseño es sencillo. El vector puede definirse como:
int vector[1000]; /* define un vector de 1000 enteros */
Para resolver el problema se elige un algoritmo de búsqueda lineal, con un pseudocódigo como el
siguiente:
contador = 0
vueltas = 1
numero = 1eerNumeroEntero
mientras (no encontrado) Y (contador < 1000)
Si (numero == vector[contador])
Imprimir "Encontrado el numero en vueltas "vueltas
En otro caso
incrementar contador
incrementar vueltas
fin mientras
Como se puede ver, el diseño es suficientemente detallado para pasar directamente a la codificación.
#include <stdio.h>
int main(void)
(
/* Definición de variables */
int contador = 0;
int vueltas = 1;
int vector[1000]; ¡* define un vector de 1000 enteros */
int numero;
int encontrado = -1;
/* se rellena el vector *t
for (contador=0; contador < 1000; contador++)
vector[contador] = contador; /* Asigna valor al vector */
contador = 0;
while (encontrado < 0 && contador < 1000) /* Bucle de búsqueda */
{
if (vector[contador] == numero)
1
printf ("Encontrado numero %i en %i vueltas \n", numero, vueltas);
printf ("Posición = %d\n", contador);
encontrado = 1;
20 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
el se
contador = contador+1;
vueltas = vueltas + 1;
return(O);
¡fiseUude <stdio.h>
i»t Eaín(void)
Definición de variables */
int contador = 0;
int vueltas = 1;
ínt vector[1000]; /* define un vector de 1000 enteros */
Int numero;
int encontrado = -1;
!* se rellena el vector *¡
for (contador=0; contador < 1000; contador++)
vector[contador] = contador; /* Asigna valor al vector */
contador = 0;
while (encontrado < 0 && contador < 1000) I* Bucle de búsqueda */
!
if (vector[contador] = numero)
1
printf("Encontrado numero %i en %i vueltas \n", numero, vueltas);
printf("Posicion = %d\n", contador);
encontrado = 1;
1
el sel
contador = contador+1;
vueltas = vueltas + 1;
ITES-Paraninfo • 2 /
Problemas resueltos de C
return(O);
Para comprobar si el número se lee correctamente, el programa comprueba el valor que devuelve la función
scanf. Esta función devuelve el número de datos que se han leído correctamente. En este ejemplo, si la
función devuelve un valor menor que 1, significa que no ha leído correctamente el número. En dicho caso,
el programa finaliza su ejecución mostrando el error por pantalla.
► 1.16 Optimice el programa anterior para que encuentre los números con menos operaciones.
RESOLUCIÓN. Analice este programa. Verá que hace 547 iteraciones del bucle for para encontrar el
número 546. Pero si numero fuera 999, necesitaría 1.000 iteraciones. No parece un algoritmo muy optimi
zado.
¿Hay alternativas? Sí, las hay, puesto que el vector se encuentra ordenado de menor a mayor. Se puede
usar un algoritmo que intente optimizar la búsqueda reduciendo en cada paso el rango de búsqueda. Una
forma de acotar el rango de búsqueda podría ser dividir el campo por dos sucesivamente. El programa
siguiente muestra este algoritmo.
/* Búsqueda rápida de un número en un vector */
#include <stdio.h>
int main(void) {
/* Definición de variables */
int posicion = 0;
int vector[1000];
int numero;
int vueltas = 1;
int elComienzo = 0;
int el Final = 1000;
22 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
i f (numero < 0 ) t
printfC'Ei número introducido no es positivo \n");
exi t(0 );
se rellena el vector */
fo r {posicion=0; posición < 1000; posicion++)
vector[posicion] = posición; I* Asigna valor al vector */
/* bucle de búsqueda */
while (vectorEposicion] != numero) {
if (vectorEposicion] <= numero) {
printf("vector[%d] = td < %d\n",posicion,vectorEposicion],numero);
elComienzo = posición;
)
el se {
printf("vector[%d] = %d > %d\n",posicion,vector[posicion],numero);
el Final = posi ci o n ;
1
posición = (elFinal - elComienzo) / 2 + elComienzo;
vueltas = vueltas + 1;
1
Si analiza este programa verá que^ necesita muchas menos iteraciones (sólo 7) que el anterior y que
sus rendimiento sólo depende del logaritmo del tamaño del vector y no de la posición del elemento. Es un
algoritmo muy optimizado, cuya traza del programa es:
Escriba el numero: 546
vector[500] =500 < 546
vector[750] =750 >546
vector[625] =625 > 546.
vector[562] =562 > 546
vector[531] = 531 < 546
Encontrado numero 546 en 6 vueltas
Poisicion 546
ITES-Paraninfo • 23
Problemas resueltos de C
Aunque puede no ser el caso, gran parte de los programas se basan en algoritmos. Por lo tanto, es
necesario diseñar bien los algoritmos antes de abordar la programación.
24 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
Conviértalos siguientes números decimales a números binarios: 35, 125, 567 y 98.
.LA ¿Cuál es el máximo número decimal que se puede representar con 4 bits? ¿Y con 16 bits? ¿Y con n bits?
M Si un computador tiene 48 MB de RAM, ¿cuántos bytes tiene?
II ¿En qué se diferencian los lenguajes de programación de alto nivel de los lenguajes de programación de
tojo nivel?
"2 Considere un lenguaje de programación hipotético denominado KONA. Usando k o n a , se puede escribir un
programa para calcular la suma e imprimir la suma de 20 enteros introducidos por el usuario, como:
Let sum = 0;
repeat 20 times {
let X = next input;
add X to sum;
printout sum;
¿Es KONA un lenguaje de alto nivel? ¿Por qué o por qué no?
II« computador tiene 640 KB de memoria. ¿Cuántos bytes de memoria tiene exactamente?
MI Busque un anuncio de una compañía de venta de computadores por correo. Averigüe lo que significan los
«anónimos y términos en el anuncio. Algunos de estos términos y acrónimos que podría ver son cache,
Enchufar-y-Usar (Plug-and-Play), SCSI, EIDE, modo de ráfaga, EDO, EPP y 56 K.
J 5 L a capacidad de los discos duros se duplica cada tres años, mientras que el coste permanece constante. Los
riscos duros actuales de 3.5 pulgadas tienen una capacidad de aproximadamente 9 gigabytes y cuestan 250
caros. ¿En 3 años más, cuánto esperaríamos pagar por 18 gigabytes?
En di sistema operativo que está usando, diga el nombre de los mandatos que manipulan archivos. En
pnriñcnlnr, nombre los mandatos que llevan a cabo las siguientes acciones:
■ Borrar un archivo.
■ Renombrar un archivo.
■ Copiar un archivo.
■ Mover un archivo.
■ Crear un directorio.
■ Borrar un directorio.
\7 Obtenga el valor decimal de los siguientes números: 010012, 03748, 01101002, 40335 y A32E16.
G w iierta los números siguientes a la base especificada:
■ 7778 a hexadecimal
■ AD1116 a binario
■ 010010112 a octal
■ 111116 a octal
■ 010011112a hexadecimal
■ 010011112 a octal
■ 37718 a binario
■ 435616 a octal
ITES-Paraninfo • 25
Problemas resueltos de C
1.20 Represente el número 47.125 en coma flotante utilizando el estándar IEEE 754.
1.22 ¿A qué sistema operativo están ligados los orígenes del lenguaje de programación C?
1.25 ¿Todos los programas en C son difíciles de entender? ¿Qué hace que un programa sea fácil de comprender,
modificar y depurar?
1.36 Considere un cajero automático en un banco. ¿Cuáles son algunas de las propiedades relevantes del cajero
para las siguientes personas?
■ Un usuario.
■ Una persona de mantenimiento.
■ Un empleado del banco.
■ El presidente del banco.
1.38 La mayor parte de las organizaciones tienen una estructura jerárquica. Elija una organización a la que
pertenezca y dibuje un diagrama que ilustre su jerarquía.
1.39 Considere un programa que mantiene una agenda electrónica. Diseñe una jerarquía de relaciones para los
tipos estructurados como Persona, ContactoProfesional, Amigo y Estudiante que pueda ser usada para im-
plementar dicho programa.
1.41 Describa los pasos a seguir para ejecutar una aplicación C y las herramientas a usar en cada paso. ¿Qué son
los archivos fuente y los archivos objeto? ¿Qué tipos de errores distintos se pueden detectar en cada paso?
1.42 Describa el objetivo de los comentarios. Nombre los tipos de comentarios disponibles. ¿Se podrían incluir
marcas de comentario dentro de un comentario?
1.43 ¿Cuál es el objetivo de la directiva incl ude? ¿Tienen siempre los programas C que incluir una directiva
include? .
26 • ITES-Paraninfo
Capítulo I / Fundamentos de programación
/*
Programa EjercícioErrores
Un programa con muchos errores.
//
inelude <stdio.h;
void Main( )
í
Casa miCasa;
int ventanas = 10
ventanas_de_mi_casa (ventanas);
!
ITES-Paraninfo • 2 7
En este capítulo...
2 .1 Identificadores
2.2 Variables y constantes
2.3 Tipos de datos elementales
2.4 Tipos avanzados
2.5 Definición de tipos con 1
2.6 Conversión de tipos
2.7 Escritura de datos con ■ ■
2.8 Lectura de datos con
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
T ipos de datos
y operadores
!.
P ro b le m a s resueltos d e C
2.1 I d e n t if ic a d ores
30 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
2.2 V a r ia b l e s y c o n s t a n t e s
En esta sección se muestran las generalidades de variables y constantes en C. Los detalles específicos
de estos elementos para cada tipo de datos se muestran en las secciones dedicadas a dichos tipos de datos.
2.2.1 Variables
Una variable es una representación alfanumérica de una posición de memoria. Como tal, se caracteriza
por tres propiedades: posición de memoria que almacena el valor, tipo de datos almacenado y nombre
que se refiere a esa posición de memoria. Una variable debe estar obligatoriamente ligada a un tipo de
datos. Por ejemplo, int a = 3 ;, declara una variable a de tipo int cuyo valor inicial es 3, El tamaño de
la zona de memoria, en bytes, dependerá del tipo de datos que se almacene en la variable. Las variables
pueden contener diferentes valores durante la ejecución de un programa. La sintaxis de declaración de una
variable es:
<t ipo de datos> <nombrel> [,<nombre2> .. ,<nombreN>]
Las variables son identificadores y, como todo identificador en C deben escribirse siguiendo las reglas
vistas anteriormente para construir los identificadores. Además, todas las variables en C, deben definirse
antes de su uso. Además, en una misma línea se pueden definir varias variables del mismo tipo.
El tipo de datos puede ser cualquiera de los tipos básicos de C, que estudiaremos en este capítulo, de los
tipos avanzados o de cualquier tipo definido por el programador.
En C se pueden definir variables sin valor inicial y variables con valor inicial. El formato de definición
presentado anteriormente permite definir variables sin valor inicial. Cuando se desea asignar un valor inicial
a una variable se utiliza el siguiente formato genérico:
<tipo_de_datos> <nombrel> = <va!or>
ITE S-Paraninfo • 3 1
Problemas resueltos de C
Esta práctica es muy buena, porque de esta forma se sabe a ciencia cierta el valor inicial de la variable,
cuestión de la que no se puede estar seguro en caso contrario y que puede dar lugar a errores si se trabaja
con las variables sin darles un valor (este asunto se estudiará más adelante).
2.2.2 Constantes
El lenguaje C permite definir constantes simbólicas que representan un valor determinado, cuyo valor
no cambia a lo largo del programa. Se definen mediante macros a través de la directiva ((define usando la
sintaxis siguiente:
((define <nombre_constante> <valor>
donde nombre.constante representa el nombre simbólico que se da a la constante y valor su valor. La
palabra define indica que la constante tiene un valor que se fija durante todo el período de vida que
dura la ejecución de un programa y que el preprocesador debe sustituir las ocurrencias del nombre de la
constante por su valor. Por ejemplo, ((defi ne PI 3 . 141516, define una constante PI que representa al
número 3 . 141516.
Aunque no hay convenciones definidas respecto a los nombres de las constantes, se suelen escribir con
letras mayúsculas, como se puede ver en el ejemplo siguiente.
((define 37
1—
LU
a_1
al
LU
/* *!
U
3 2 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
2.3.1 Operadores
C define operaciones básicas sobre los tipos elementales. Por ejemplo, los operadores aritméticos per
miten realizar operaciones matemáticas básicas con números enteros. Una expresión como la siguiente:
resultado = numero * 5 / 4 + 3 ;
Es una expresión aritmética compuesta por operadores aritméticos y operandos, donde los operandos
deben ser de tipo numérico. La Tabla 2.3 muestra los operadores que se pueden aplicar a cada tipo, ordena
dos de mayor a menor prioridad. El orden de prioridad indica qué operador se aplica en primer lugar cuando
hay varios presentes en una expresión aritmética. Así, en el ejemplo anterior, resultado se calcula en el
orden siguiente:
1. resultado = numero * 5
2. resultado = resultado / 4
2. resultado = resultado + 3
La mejor forma de evitar problemas por la prioridad de operadores es usar paréntesis para indicar cómo
se deben ejecutar las operaciones. Por ejemplo:
resultado = (numero * (5 / 4)) + 3;
ITES-Paraninfo • 3 3
Problemas resueltos de C
El operador s izeof devuelve el tamaño en bytes que ocupa un determinado tipo de datos o variable. El
resultado de este operador será distinto dependiendo del computador en el que se use, ya que el número de
bytes utilizado para representar cada uno de los datos básicos depende de la forma en la que los representa
cada computador.
En las secciones siguientes se mostrarán ejemplos de uso de estos operadores con cada tipo de datos.
► EJEMPLO 2.4 Defina variables para representar las personas que hay en un teatro, las líneas telefónicas de un
país, los días de la semana o la posición de un bit en octeto.
Re s o l u c i ó n .
int espectadores = 0;
long 1 ineasTelefonicas = 0;
short diaSemana = 7;
short bitOcteto = 0;
A pesar de que no existe un valor inicial por defecto, siempre que se conozca el valor inicial del que
parten los valores es conveniente asignar ese valor a la variable en el momento de su definición. En caso de
que no se conozca o no se haya definido ese valor, convendrá dar igualmente un valor inicial.
Los tipos de datos enteros permiten representar números positivos y negativos. Cuando se desea definir
una variable que almacene sólo números positivos se utiliza la palabra reservada un s ign ed. Así, la siguiente
definición:
unsigned int presión;
define una variable (presi on) entera sin signo.
Además, los tipos de datos enteros se pueden especificar de diferentes formas. A continuación, se mues
tra úna lista en la que se presentan las diferentes formas de especificar cada uno de los tipos de datos
enteros:
■ short, si gned short, short intosigned short int
■unsigned shortounsigned short int
34 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
► EJEMPLO 2.5 Ejemplo que muestran los tamaños utilizados en el computador en el que se ejecuta el programa
para los tipos de datos enteros.
Re s o l u c i ó n .
/*
* Programa que muestra los tamaños utilizados en el computador
* en el que se ejecuta el programa para los tipos de datos enteros
* (archivo limits.h).
*/
#i nclude <stdi o .h>
#i ncl ude <1 imi ts .h>
int main(void)
return(O) ;
!
ITES-Paraninfo • 3 5
Problemas resueltos de C
Si se asigna a una variable de un tipo un valor que está fuera de su rango de representación, como por
ejemplo:
unsigned int = -1;
no se obtiene un error de compilación, no siendo esto indicado de ninguna forma por el compilador o en
tiempo de ejecución. Este es uno de los principales problemas de C: su permisividad con los tipos. Por ello,
hay que ser cuidadoso y comprobar que los valores de entrada están dentro de sus rangos.
Una forma más sencilla de comprobar el tamaño que ocupa cada tipo en memoria es usar el operador
si zeof.
► EJEMPLO 2.6 Ejemplo que muestra, usando si zeof, los tamaños en bytes que ocupan los tipos enteros en
memoria en el computador. La salida de este programa será distinta dependiendo del computador en el que
lo ejecute, ya que el número de bytes utilizado para representar cada uno de los datos básicos depende de
la forma en la que los representa cada computador.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
I
pri ntf("Un char ocupa Xd bytes\n", sizeof(char));
pri ntf("Un signed char ocupa Xd bytesVn", sizeof(signed char));
pri ntf("Un unsigned char ocupa Xd bytesVn", sizeof(unsigned char));
pri ntf("Un int ocupa Xd bytes\n", s izeof(int));
pri ntf("Un signed int ocupa %d bytes \n” , sizeof(signed int));
pri ntf("Un unsigned int ocupa Xd bytes \n", sizeof(unsigned int));
pri ntf("Un short ocupa Xd bytes \n", sizeof(short));
pri ntf("Un signed short ocupa Xd bytesVn", sizeof(signed short));
pri ntf("Un unsigned short ocupa Xd bytesVn", sizeof(unsigned short));
pri ntf("Un long ocupa Xd bytes\n", sizeof(long));
pri ntf("Un signed long ocupa Xd bytes\n", sizeof(signed long));
pri ntf("Un unsigned long ocupa Xd bytesVn", sizeof(unsigned long));
pri ntf("Un long long ocupa Xd bytes\n", sizeofdong long));
pri ntf("Un signed long long ocupa Xd bytes\n", sizeof(signed long long));
pri ntf("Un unsigned long long ocupa Xd bytesVn", sizeof(unsigned long long));
pri ntf("Un float ocupa Xd bytesVn", sizeof(float));
pri ntf("Un double ocupa Xd bytesVn", sizeof(double));
printfC'Un long double ocupa Xd bytesVn", sizeofdong double));
return(O);
!► EJEMPLO 2.7 Escriba un programa que lea dos números y aplique sobre ellos todos los operadores aritméticos.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
1
/* definición de variables */
int a ;
3 6 • ITES-Paraninfo
Capitulo 2 I Tipos de datos y operadores
int b;
int suma;
int resta;
int producto;
int división;
int modulo;
int postincremento;
int preincremento;
int postdecremento;
int predecremento;
suma = a + b;
resta = a - b;
producto = a * b;
di vi si on = a / b ;
modulo = a % b ;
postincremento = a++;
prei ncremento = ++a;
postdecremento = b--;
predecremento = --b;
return(0);
1
Recuerde que cuando el operador de división se aplica sobre números enteros, el resultado que se obtiene
es otro número entero, es decir, se trunca el resultado. Asimismo, recuerde que el operador módulo %sólo
se puede aplicar a variables de tipo entero.
O peradores de bits
C incluye operadores de bits que permiten realizar ciertas operaciones aritméticas binarias, que manejan
cada bit individual de una palabra de memoria.
Se pueden clasificar en las siguientes categorías:
■ Operador de complemento a uno.
■ Operadores lógicos binarios.
■ Operadores de desplazamiento.
El operador de complemento a uno (~) invierte los bits de su operando: los unos se transforman en ceros
y los ceros en unos.
Los operadores lógicos binarios de C son;
■ AND binario. Operador: &.
ITES-Paraninfo «3 7
Problemas resueltos de C
■ OR binario. Operador: j.
■ OR exclusivo binario (XOR). Operador: A.
Las operaciones se realizan bit a bit según la tabla siguiente:
a b a &b a | b aAb
i 1 1 1 0
i 0 0 1 1
0 1 0 1 1
0 0 0 0 0
Los operadores de desplazamiento permiten desplazar con signo los bits de una palabra a izquierda o a
derecha. Hay dos tipos de operadores de desplazamiento:
■ Desplazamiento a la izquierda: <<
■ Desplazamiento a la derecha: >>
Ambos requieren dos operandos:
■ El primero es un operando de tipo entero que representa el patrón de bits a desplazar.
■ El segundo es un entero sin signo que indica el número de desplazamientos.
Por ejemplo, si a = 8 (1000 en binario), las operaciones de desplazamiento darían los resultados si
guientes:
3 8 • tTES-Pararímfo
Capítulo 2 / Tipos de datos y operadores
►EJEMPLO 2.8 Escriba un programa que lea el área de un círculo y obtenga su radio. Para calcular la raíz cuadrada
de un número se puede utilizar la función de biblioteca sqrt definida en el archivo de cabecera math .h.
Re s o l u c i ó n .
#include <stdio.h>
#include <math.h>
#defi ne PI 3.14159
float radio;
float area;
radi o = sqrt(area/PI);
return(0) ;
La biblioteca math
La biblioteca math proporciona funciones matemáticas que permiten realizar cálculos complejos de
tipo trigonométrico, raíces cuadradas, logaritmos, etc. Se puede obtener una descripción completa de las
funciones que proporciona la biblioteca math en la ayuda de su entorno de desarrollo (Por ejemplo, man
sqrt ). A continuación se muestran algunos ejemplos de funciones de esta biblioteca:
x = eos(a) Devuelve el coseno de a
x = at an ( a ) Devuelve el arco tangente de a
x = exp(a) Devuelve el valor de e (2.718) elevado a a
x = pow(a , b) Devuelve el valor de a elevado a la b
ITES-Paraninfo • 3 9
Problemas re su e lto s d e C
Estos tipos de datos permiten representar números complejos de la forma u + iv, donde u y u se con
sideran como números reales. Las constantes de este tipo se representan como a+I*b, donde a y b se
representan como valores reales e I se utiliza para el número i .
También existen tipos de datos para trabajar con números imaginarios de la forma iu. Estos tipos son:
float _Imaginary
double _Imaginary
long double _Imaginary
Para poder trabajar con estos tipos de datos es necesario que incluya en sus programas el archivo
complex.h.
Aunque C99 introduce estos nuevos tipos de datos, sin embargo no es obligatorio que un compilador
concreto los soporte, por lo que puede encontrarse con compiladores en los que no se pueda trabajar con
estos tipos de datos.
!► EJEMPLO 2.9 Escriba un programa que defina los números complejos 2 +¿3 y 1 + i 2 y calcule la suma y la resta.
Re s o l u c i ó n .
#include <stdio.h>
#include <complex.h>
#include <math.h>
int main(void)
f
float _Complex a ;
float _Complex b ;
f1oat _Complex c ;
a = 2 + 1*3;
b = 1 + 1*2;
c = a + b;
printf("Suma = %f + i*%f\n", creal(c), cimag(c));
c = a - b;
printf("Resta = %f + i*%f\n", creal(c), cimag(c)):
return(0) ;
1
El programa hace uso de las funciones crea! y cimag para acceder a la parte real e imaginaria del
número respectivamente.
4 0 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
Secuencia Significado
\n nueva línea
\t fabulador
\b backspace
\r retomo de carro
\" comillas
V apóstrofo
\\ backslash
\? signo de interrogación
I ► EJEMPLO 2 . 10 Escriba un programa que muestre por pantalla las letras minúsculas y su código ASCII.
R e s o l u c i ó n . La primera letra minúscula es la ’ a ’. Por ello, se comienza buscando su código como
punto de partida.
#include <stdio.h>
int main(void) {
int i ;
return(O); -
Las operaciones aplicables a los caracteres son todas aquellas aplicables a los tipos numéricos. Por tanto,
se puede operar con ellos o sus códigos, se pueden asignar y se pueden comparar.
ITES-Paraninfo • 4 1
Problemas re su e lto s de C
considera true cualquier valor numérico distinto de 0 y f al se al valor 0. El tipo de datos lógico o boolea-
no se suele representar en C mediante una variable de tipo int. En cuanto a los valores trueyfalsese
suelen definir como macros, de la siguiente forma:
//define TRUE 1
//define FALSE 0
A las variables de tipo lógico se les pueden aplicar los operadores que se muestran en la Tabla 2.3, de
entre los cuales merece la pena destacar el significado de los operadores lógicos NOT (!), AND (&&) y OR
( | | ) , cuyos resultados se ajustan a tablas lógicas descritas en la Tabla 2.7:
A B ' A A && B A || B
0 0 1 0 0
1 0 0 0 1
0 1 0 1
1 1 1 1
Aparte de los operadores lógicos, los operadores relaciónales también están muy relacionados con el
tipo de datos lógico, ya que el resultado de aplicar este tipo de operadores es un valor que se considera ver
dadero o falso. Estos operadores se utilizan para construir expresiones lógicas. En la Tabla 2.8 se muestran
los operadores relaciónales de C.
Operador Función
< menor que
> mayor que
<= menor o igual que
>= mayor o igual que
== igual que
!= distinto que
A partir de las Tablas 2.7 y 2.8 se puede evaluar cualquier expresión lógica, aunque no esté expresada
en variables lógicas. En el ejercicio siguiente se puede observar cómo se trabaja con operadores lógicos y
cómo el resultado de un operador relacional es de tipo booleano.
► EJEMPL02. I I Escriba un programa que declare dos macros con valores verdadyfalsoydos variables enteras
con valores 5 y 4. Haga con ellas todas las operaciones lógicas posibles.
Re s o l u c ió n .
/* Programa que hace operadones lógicas *!
//inelude <stdio.h>
//define true 1
//define false 0
int main(void)
í
int i = 5, j = 4;
int aux;
4 2 • ITES-Paraninfo
Capitulo 2 / Tipos de datos / operadores
printf("\noperaciones lógicas
printfC'Vn NOT true es % d ", !true);
printf("\n NOT falsees I d ”, (false);
/ ^Operadores reíacionales */
printfC Mayor o igual que ..");
printf("\n (i >= j ) = % d ", (i >= j )); /* t r u e *!
printf("\n (j >= i) = % d ", (j >= i)); /* f a l s e *!
printf("\n (i != j) = % d ", (i != j )); /* true *!
return(0);
Hay que decir, sin embargo, que en la nueva revisión del estándar de C que apareció en diciembre de
1999, se ha introducido el tipo de datos _Bool,con los valores true y fal se, disponible en el archivo de
cabecera stdbool .h. Sin embargo, debido a su reciente aparición, todavía no está disponible en muchos
compiladores de C.
I ► EJEMPLO 2.12 Escriba un programa que defina un tipo enumerado para los días de la semana, una variable del
tipo e imprima los días de la semana.
Re s o l u c i ó n .
#include <stdio.h>
di a = 1 unes ;
printf ("\n Primer día de la semana: %d", dia);
ITES-Paraninfo • 4 3
Problemas resueltos de C
di a = martes;
printf ("\n Segundo día de la semana: M " , d ia );
di a = mi ercoles;
printf ("\n Tercer día de la semana: %d", d ia );
di a = jueves;
printf C'\n Cuarto día de la semana: % d " , d ia );
di a = viernes;
printf ("\n Quinto día de la semana: % d " , dia);
di a = sabado;
printf ("\n Sexto día de la semana: %d", dia);
dia = dia + 1;
printf ("\n Ultimo día de la semana: % d " , dia);
return(O);
1
Es interesante resaltar que no hay formato de impresión para tipos enumerados. Se proyectan sobre sus
valores enteros. Cada uno de los elementos de la enumeración lleva asociado un valor entero. Por defecto,
el primer elemento tiene asociado el valor 0, el segundo el valor 1 y así sucesivamente. De acuerdo a esto,
sabado es igual al valor 5. Debido a que un valor de tipo enumerado se considera como un entero, se
pueden utilizar los operadores aritméticos como ocurre en la expresión d ia = dia + 1.
También es posible asignar un valor distinto a cada uno de los elementos de la enumeración. A con
tinuación se muestra un ejemplo en el que se define el tipo enumerado Respuesta, que puede tomar dos
valores: No con valor —1 y Si con valor 1.
enum Respuesta (No = -1, Si = 11;
En C se puede recurrir a los tipos enumerados para definir el tipo de datos booleano de la siguiente
forma:
enum Boolean (false, truel;
De esta forma, false será tratado como falso (valor 0) y t rué como verdadero (valor 1).
2.4 T ip o s a v a n z a d o s
A partir de los tipos básicos descritos, se definen en C tipos avanzados compuestos por colecciones o
agrupaciones de elementos de tipos básicos. Los tipos avanzados son los arrays, las cadenas de caracteres,
las estructuras y los punteros. Aunque estos tipos se van a estudiar con más detalle en capítulos posteriores,
se presentan en esta sección unos breves ejemplos de los mismos.
2.4.1 Arrays
Un a r r a y es un conjunto de datos del mismo tipo, a los que se da un nombre común. Para definir un
array en C se utiliza la sintaxis siguiente:
tipo_dato nómbrelarray[dimension];
Donde t ipo _dat o representa el tipo de los elementos que constituyen el array, nombre_arrayel nombre
de la variable utilizado para el array y dimensión el número de elementos del array. Así, la siguiente
definición:
int números[40];
Define un array denominado números que está formado por 40 números de tipo int. A cada elemento
de un array se accede a través de un índice. En C, el índice inferior siempre es 0 y el superior viene
determinado por el número de elementos del array menos uno. Para acceder a un elemento del array se
usa la expresión a rray[el emento]. Así, números [0] representa el primer elemento del array números y
números [8] representa el noveno elemento del array.
4 4 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
.
2 4 .2 Cadenas de caracteres
Un tipo de datos muy habitual en cualquier lenguaje de programación es el string o cadena de carac
teres. En C una cadena de caracteres constituye un caso particular de array formado por una secuencia de
caracteres. La forma de definir una cadena de caracteres en C es la siguiente:
char nombre„cadena[1ongitud];
Una cadena de caracteres está formada por caracteres individuales, que se representan mediante un único
carácter encerrado entre comillas simples. Las cadenas de caracteres se representan mediante una secuencia
de caracteres encerrada entre dobles comillas (")• Para que un array de caracteres sea considerado como
un string, es necesario que finalice con el carácter nulo de C ( ’ \ 0 ’). Las siguientes constantes representan
cadenas de caracteres en C:
"amigo" "hola lector" "a"
Observe que " a " es distinto de ’ a ’. La primera representa una cadena de caracteres compuesta por un
único carácter (el carácter ' a ’), mientras que la segunda representa el carácter individual ' a ’ .
La asignación de un valor inicial a una cadena de caracteres se puede hacer en la definición de la misma.
► EJEMPLO 2 . 13 Ejemplo que define la cadena de caracteres "Hola " de varias formas, la imprime y la copia de
un string a otro. Para terminar imprime los caracteres de la segunda.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
/ * a s i g n a va 1 o r c? c a d e n a 3 */
strcpyícadena 3, ' Hola Ho la ") ;
pri ntf("Caden a i es: %s\n" , cadenal)
pri ntf("Caden a 2 es: %s\n" , cadena2)
pri ntf("Caden a 3 es: %s\n" , cadena3)
/* c o p i a c a d e n a l e n c a d e n a 2 */
strcpy(cadena2 , cadenal);
return(O) ;
Con la primera definición, se define una cadena con el valor "Hola". Observe que se ha utilizado como
tamaño para la cadena el valor 5, es decir, los cuatro caracteres de la cadena más el carácter nulo final.
En la segunda no se indica el tamaño de la cadena, sin embargo, se asumirá que la cadena tiene los
cuatro caracteres de "Hola" más el carácter nulo final ( ’ \ 0 ’) que se introduce por defecto.
La tercera forma de asignar un valor a una cadena de caracteres es utilizando la función s t r cpy, como
en la tercera asignación. En este caso, la cadena debe tener capacidad suficiente para albergar el string
origen.
Para imprimir una cadena de caracteres se puede utilizar la función p ri n tf y el especificador de con
versión %s.
ITES-Paraninfo • 4 5
Problemas resueltos de C
Para ver la longitud de una cadena se puede usar la función strl en. Las funciones de biblioteca que
manipulan cadenas de caracteres se encuentran declaradas en el archivo de cabecera st rin g .h
Es importante tener cuidado a la hora de reservar el tamaño para una cadena de caracteres. Si se reserva
menos espacio del necesario, se genera un error en el programa porque se escribe en memoria fuera de
la cadena. El lenguaje C no detecta ni avisa de este error, por lo que es muy difícil de encontrar y puede
originar errores raros al cabo de un tiempo, debido a fallos ocultos generados por el error del string.
► EJEMPLO 2 . 14 Ejemplo que define una cadena de tamaño 10 y copia sobre ella una de tamaño 17.
Re s o l u c ió n .
/*
Programa que hace operaciones erróneas con strings
debidas a la declaración con tamaño insuficiente
*/
#include <stdio.h>
#include <string.h>
int main(void)
(
char cadena[10];
int i = 3;
return(0);
1
Está definiendo una cadena de caracteres con capacidad para almacenar una cadena de hasta 10 ca
racteres incluido el carácter nulo, pero se copian sobre ella 17 caracteres; los 16 caracteres de la cadena
"Esto es un error" (incluidos los espacios en blanco) y el carácter nulo final. Sin embargo, cadena
sólo tiene reservado 10 caracteres, por lo que el resto se copiarán sobre una zona de memoria que no está
reservada para cadena y en la que se pueden almacenar otras variables.
Para ponerlo todavía peor, si se pregunta por la longitud de una cadena, el programa dice que es 17
(aunque se declaró 10), habiéndose perdido la variable declarada posteriormente.
Este tipo de licencias con los tipos de datos son, sin duda, la mayor debilidad del lenguaje C y ya se han
resuelto en otros lenguajes como Ada, Java o C++.
2.4.3 Estructuras
C, al igual que muchos lenguajes de programación, permite definir estructuras. Una estructura está
compuesta por elementos individuales que pueden ser de distinto tipo. A cada uno de los elementos de una
estructura se le denomina miembro. En C, una estructura se define de la siguiente forma;
struct nombre_estructura
I
tipo_dato_l miembro_l;
tipo_dato_2 miembro_2;
tipo_dato_N miembro_N;
);
46 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
donde nombre .estructura es el identificador utilizado como nombre para la estructura, tipo-dato.i
es el tipo de datos del miembro i y mi embro.i el identificador utilizado como nombre para el miembro
situado en la posición i . Una vez definida una estructura se pueden definir variables de tipo estructura de la
siguiente forma:
►EJEMPLO 2 . 15 Ejemplo que define una estructura de tipo fecha, define dos variables, las inicializa con una fecha
y las imprime. A continuación copia la segunda sobre la primera y las imprime de nuevo.
Re s o l u c i ó n .
#include <stdio.h>
/* Definición de la estructuna */
struct Fecha
1
int di a;
char mes[16]:
int anyo;
int main(void)
1
/* /aniñóles de tipo Fecha *!
struct Fecha hoy;
■>: struct Fecha ayer;
" • - C- •- -
boyddTaj^ 30; /* di a 30 */
strcpy(hoy.mes,"Junio"); / * mes de junio */
hoy.anyo = 2002; /* año 2002 */
ayer.di a = 29; /* di a 29 */
strcpyíayer.mes, "Junio"); /* mes de junio */
ayer.anyo = 2002; /* año 2002 */
/* se copia la estructuna */
ayer = hoy;
return(0);
Para acceder a los miembros de una estructura se utiliza el operador de miembro (.).
En C se pueden asignar entre sí estructuras del mismo tipo, a diferencia de lo que ocurre con los array.
Sin embargo, no se pueden comparar estructuras entre sí, ni se pueden imprimir directamente.
ITES-Paraninfo « 4 7
Problemas resueltos de C
2.4.4 Punteros
Como se vio en el Capítulo 1, la memoria del computador se encuentra organizada en una serie de
palabras, cada una de las cuales ocupa un múltiplo determinado de bytes. Dentro de la memoria, cada dato
ocupa un cierto número de bytes. Por ejemplo, un tipo de datos int ocupa normalmente 4 bytes y un tipo
de datos c h a r ocupa un byte. Cuando se define una variable, el compilador reserva en memoria los bytes
necesarios para representar los datos que puede contener esa variable. Cuando se define una variable de tipo
int, normalmente se reserva en memoria una zona de 4 bytes, generalmente una palabra.
Un puntero es un tipo de datos de C que, a diferencia del resto de tipos de datos, no almacena datos en
sí, como pueden ser números enteros, estructuras, etc., sino que almacena direcciones de memoria.
Para definir un puntero en C se utiliza la sintaxis siguiente:
ti po_de_dato *va ri able_puntero;
donde ti po_de_dato representa un tipo de datos e n C y vari able .puntero el nombre de la variable de
tipo puntero. Con una definición de este tipo, se está indicando que vari able .puntero podrá almacenar
direcciones de memoria en las que se almacenen datos de tipo ti po.de.dato.
Existen dos operadores que permiten trabajar con punteros: el operador de dirección y el de indirección.
El operador de dirección (&) permite obtener la dirección de memoria en la que se almacena una deter
minada variable. Así, si k es una variable cualquiera, &k representa la dirección de memoria en la que se
almacena k. El operador de indirección permite acceder al contenido de una determinada dirección de me
moria. Así, si px es una variable de tipo puntero a entero (*i nt), *px representa el contenido almacenado
en la dirección px.
Re s o l u c ió n .
#include <stdio.h>
return(O);
4 8 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
2.5 D e f in ic ió n de t ip o s c o n t y p e d e f
En muchas ocasiones es útil crear nuevos tipos de datos para mejorar la legibilidad de los programas.
Para poder crear un nuevo tipo de datos en C, se utiliza la palabra reservada typedef con la siguiente
sintaxis:
typedef tipo_de_datos nuevo_tipo;
donde nuevo_ti po es el nuevo tipo que se desea crear y ti po_de_datos, el tipo ya definido en C, que se
utilizará para el nuevo tipo.
I ► EJEMPLO 2 . 17 Programa que define un tipo Temperatura a partir de f 1o at. A partir de él define un termostato
con una temperatura mínima y máxima y realiza operaciones con variables de este tipo.
Re s o l u c ió n .
#include <stdio.h>
/* Definición de tipos */
int main(void)
(
TermostatoACME termo;
termo.temp_min = -3.5;
termo.temp_max = 50.0;
2.6 C o n v e r s ió n de t ip o s
C no es un lenguaje fuertemente tipado, lo que significa que se pueden definir variables de un tipo y
asignarles directamente valores de otro tipo. Por tanto, el código siguiente no originaría un error en C:
int longitud = 3;
float espacio;
espacio = longitud;
Sin embargo, para que estas operaciones se realicen de forma correcta, es necesario convertir el valor de
la derecha de la asignación al tipo de la izquierda, bien de forma implícita o explícita.
C realiza conversión implícita entre los operadores de las expresiones aritméticas. Estas conversiones se
pueden llevar a cabo por dos motivos:
■ Por reglas de promoción automática. Estas reglas convierten charoshortaint, enteros a 1o ng si uno
de los operandos lo es, reales a doubl e si uno de los operandos lo es, etc.
ITES-Paraninfo • 4 9
Problemas resueltos de C
Re s o l u c ió n .
#include <stdio.h>
int main(void)
(
i nt 1ongi tud = 3;
float espacio, expresión;
espacio = longitud;
expresión = espacio * longitud;
return(O);
}
El código anterior es correcto porque hay una asignación implícita por conversión y f 1o at es de mayor
tamaño que int. Además, expresión promociona automáticamente longitudafloat.
Si no se quiere confiar en la conversión implícita, la otra opción es hacer conversión expl ici ta de los
tipos necesarios. La conversión explícita se lleva a cabo con la siguiente sintaxis:
■ <variable> = (<tipo de datos)) <expresión>;
Así, para evitar las posibles ambigüedades, el ejemplo anterior se puede programar de la forma siguiente;
int main(void) 1
int longitud = 3;
float espacio, expresión;
/* conversiones explícitas */
espacio = (float) longitud;
expresión = espacio * (float)1ongitud;
return(0) ;
2.7 E s c r it u r a de d a t o s c o n p r i n t f
Ya se ha ido viendo a lo largo del capítulo el uso de la función printf para la escritura de valores y
variables. La función pri ntf permite además que la salida que se obtiene tenga un formato determinado.
Para ello, se utilizan especificadores de formato más generales de los vistos hasta ahora, que se representan
genéricamente con la sintaxis siguiente;
%[flags][ancho][.preci si ón][1ongitudlespeci ficador_de_conversi ón
5 0 • ITES-Paran'mfo
Capítulo 2 / Tipos de datos y operadores
Como puede observarse, un especificador de formato siempre comienza con el símbolo 1 seguido por
una serie de elementos. Los corchetes ([ ]) utilizados en este especificador de formato genérico representan
elementos opcionales. El campo especificador de conversión del especificador de formato es obligatorio e
indica la forma en la que se representarán los datos. La Tabla 2.9 muestra los especificadores de conversión
de la función pri ntf.
Especificador Significado
de formato
%c Imprime un carácter individual
%d %i Imprime un entero decimal con signo
/ u Imprime un entero decimal sin signo
%0 Imprime un entero octal con signo
%ou Imprime un entero octal sin signo
s-s
S-S
X
► EJEMPLO 2.20 Ejemplo que escribe un número con distintos formatos de salida.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
float b = 123.25;
double numero =4.2;
return(O);
ITES-Paraninfo • S I
Problemas resueltos de C
Formato e 1.232500e+02
Formato g 123.25
El campo longitud del especificador de formato puede ser h, 1 o L y se utiliza según los siguientes
criterios:
■ El tipo h se emplea como prefijo de los especificadores de conversión para números enteros (d, i, u, o y
x) para indicar que el argumento a imprimir es de tipo s ho rt.
■ El tipo 1 se emplea como prefijo de los especificadores de conversión para números enteros para indicar
que el argumento a imprimir es de tipo 1ong. Se utiliza 11 para números de tipo 1ong 1ong.
■ El tipo L se usa como prefijo para los especificadores de conversión de los números reales para especi
ficar! ong double.
El campo precisión tiene el siguiente significado en función del tipo de datos con el que se use:
■ Para los números enteros indica el mínimo número de dígitos que deben escribirse, rellenando con ceros
a la izquierda si es necesario.
■ Para los especificadores %f y %e indica el número de dígitos decimales que se escribirán, redondeando
el resultado. Para el especificador %q representa el número de dígitos significativos que se escriben.
■ Para el especificador %s indica el número de caracteres que se imprimirán.
I ► EJEMPLO 2 . 2 1 Ejemplo que escribe los datos de salida con especificadores de precisión (o ancho de campo).
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
(
int numEntero = 4;
char cadena[10] = "Agenda":
float numReal = 123.2547834739;
/* impresión de valores */
pri ntf("% ,4d\n", numEntero
pri ntf ("7o.3s\n", cadena) ;
pri ntf ("7c.4f\n", numReal);
pri ntf ("7c.4e\n", numReal);
pri ntf ("7o.4g\n", numReal);
return(0);
52 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
El campo ancho se utiliza para indicar el número de posiciones que se utilizarán en la escritura de
un valor. En caso de que el valor ocupe más posiciones que el especificado en el campo ancho, éste se
incrementará las posiciones necesarias.
El campo fl ags complementa las capacidades de formato de pri ntf. Los valores posibles son los
siguientes:
- (signo menos). El valor se justifica a la izquierda dentro del ancho especificado.
+ (signo más). El valor se justifica a la derecha dentro del ancho especificado.
Un espacio en blanco. Imprime un espacio en blanco delante del valor, si éste es positivo. Si se utiliza con
+, entonces se ignora.
#. Antepone o, x o X cuando se utiliza con el especificador octal o hexadecimal respectivamente. Si se
utiliza con los especificadores de conversión para números reales se fuerza a que el valor contenga un
punto decimal.
Por último, en el formato de la función pri ntf hay algunos caracteres problemáticos, como ", que
delimita la cadena de formato y X, que se utiliza en los especificadores de formato. Existen, además, otros
caracteres de control, como el de nueva línea, que, para poderse imprimir, se deben representar como se
cuencias de escape. Las secuencias de escape, que ya se vieron anteriormente, se representan mediante
el carácter \ seguido por una letra o conjunto de dígitos. Cuando se desea imprimir el carácter X, debe
anteponerse antes el carácter %.
return(0) ;
La función printf siempre comienza la salida de los datos en el lugar en el que se encuentra actual
mente el cursor.
ITES-Paraninfo • 5 3
Problemas resueltos de C
donde la cadena formato es similar la utilizada en la función pri ntf. Los especificadores de conversión
utilizados también son los mismos a los empleados en la función pri ntf. Cada uno de los argumentos de
la función es un puntero a la variable en la que se quiere almacenar el dato a leer. Recuerde que un puntero
representa una dirección de memoria.
int main(void)
char cadena[128];
char c;
int e ;
float r;
return(O);
Observe que cuando se desea leer una cadena de caracteres no es necesario utilizar el operador de
dirección &, puesto que una variable de tipo array (o cadena de caracteres) ya representa la dirección de
memoria.
E v it e es t o s er r o r es
Aunque a lo largo del libro se irán describiendo muchos errores de programación que se cometen cuando
se programa en C, a continuación se describen los primeros errores que se cometen cuando se empieza a
programar en C.
Problemas con las mayúsculas y las minúsculas Es muy habitual definir una variable, como por ejem
plo temperaturaHorno, y utilizarla después como TemperaturaHorno. Esto provocará un error de
compilación, puesto que estos dos identificadores son distintos en C.
Omisión del punto y coma Todas las sentencias en C deben acabar en punto y coma.
Uso de punto y coma en la directiva #defi ne Ya se comentó anteriormente que el preprocesador susti
tuye todas las ocurrencias de las constantes simbólicas definidas con #def ine en todo el programa. Por
5 4 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
RESOLUCIÓN. Los identificadores mi -casa, mi *casa, MESZUNO, mesíl y Ía890 son inválidos, porque
usan separadores y símbolos no aceptados por la sintaxis del lenguaje.
El identificador 890a es inválido porque los identificadores no pueden comenzar por un número.
► 2.3 Escriba un programa C que defina, usando nombres descriptivos, variables de C que representen: meses del
año, segundos en un minuto, una posición de la memoria de un computador y un valor lógico. Asígneles un
valor y escríbalo por pantalla.
Re s o l u c i ó n .
#include <stdio.h>
#de f i ne TRUE 1
i n t mai n(voi d)
í
/* Defin i c i ó n de varidbles */
int mesDelAnyo;
ITES-Paraninfo • 55
Problemas resueltos de C
int segundos;
int tamanyoMemoria;
int valorLogico;
mesDelAnyo = 4;
printf C'\n mesDelAnyo = tú", mesDelAnyo);
segundos = 34;
printf ("\n segundos = %d", segundos);
■tamanyoMemori a = 20;
printf ("\n tamanyoMemoria = %d", tamanyoMemoria);
valorLogi co = TRUE;
printf ("\n valorLogico = ti", valorLogico);
return(O) ;
lI
Es interesante observar que no hay un tipo bool ean como ocurre en otros lenguajes de programación.
Se implementa mediante números enteros, correspondiendo el valor 0 con el valor lógico fa 1se y un valor
distinto de cero, generalmente el 1, con el valor lógico true.
► 2.4 Escriba un programa que defina, usando nombres descriptivos, constantes en C que representen los meses
del año, los segundos de un minuto, que el tamaño de la memoria de un computador es 2 KB, un valor
lógico verdadero y la razón entre grados Farenheit y Celsius. El programa debe imprimir los valores por
pantalla.
Re s o l u c i ó n .
#include <stdio.h>
/* Definición de constantes */
int main(void)
{
printf ("MESESDELANYO = M\n", MESESDELANYO);
printf ("SEGUND0S_MINUT0 = %d\n", SEGUND0S_MIÑUTO);
printf ("TAMANYOMEMORIA = M\n", TAMANYOMEMORIA);
printf ("VERDADERO = %d\n", VERDADERO);
printf ("CELSIUSFARENHEIT = %f\n", CELSIUSFARENHEIT);
printf ("Tipo de interés = %f\n", PREC10_DINERO);
return(O);
I ► 2.5 Escriba un programa que defina, usando nombres descriptivos, una constante en C que represente el número
de meses del año y una variable para almacenar un número de mes, inicializada con el valor 5. El programa
debe imprimir por la pantalla el orden del mes dentro del año y la porción de año transcurrido.
56 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
Re s o l u c i ó n .
#include <sdtdio.h>
int main(void) {
int mes = 5; /* Mes del año *t
return(O);
► 2.6 Escriba un programa que defina la constante PI como 3.1416, que calcule el área de un círculo, cuyo radio
se pide por pantalla, y la imprima por pantalla.
Re s o l u c i ó n .
#i n c l u d e <stdio.h>
i n t mai n(voi d)
)
/* Definición de variables */
f l o a t area = 0;
i n t radio = 5;
/* Área de un circulo */
area = PI * radio * radio;
printf ("El área de un círculo de radio = %d es %f \n", radio, area);
return(O);
► 2.7 Escriba un programa que lea un número por la entrada estándar, lo multiplique por 20 e imprima su división
por 10. A continuación debe sumar dicho número a la multiplicación y volver a imprimir su división por
10. Si el resto no es cero, debe imprimirlo también.
Re s o l u c i ó n .
#include <stdio.h>
#defi ne DIVISOR 10
ÍTES-Paraninfo • 5 7
Problemas resueltos de C
di vi s = muít / DIVISOR;
printf {"%d div %d es %d\n", mult, DIVISOR, divis);
suma = muít + n ;
printf ("%d + %d es %d\n", mult, n, suma);
if ((suma % DIVISOR) != 0)
printf ("%d módulo 10 es %d\n", suma, (suma % DIVISOR));
return(0);
!
Observe que la división entera trunca el resultado al entero más próximo por debajo. Por ejemplo, 23/4
da 5.
i ► 2.8 Escriba un programa que defina dos variables enteras, i y j, y dos reales, x e y, y lea sus valores por la
entrada estándar. Use con ellas las operaciones aritméticas enteras, reales +, -, *. /, y %. A continuación
mezcle ambos tipos para * y /. Compruebe si i es mayor que j y j mayor que i e imprima el resultado.
Imprima todos los resultados.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
/* Variables */
int i ;
int j ;
float x;
f1oat y ;
58 • ITES-Pa ranínfo
Capítulo 2 i Tipos de datos y o p e ra d o re s
return(O) ;
I ► 2.9 Escriba un programa que lea un número entero e indique el número de bits puestos a 1.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
(
int n;
int i;
int mascara = 1;
int numeroUnos = 0;
ITES-Paraninfo • 59
Problemas resueltos de C
numerollnos ++;
!► 2 . 10 Escriba un programa que defina dos variables enteras, i y j, y una constante entera que contenga la
máscara 9. Lea los valores de las variables por la entrada estándar y compruebe si i es mayor que j y
j mayor que i e imprima el resultado. A continuación, haga i igual a 129 y realice máscaras &y | a nivel
de bit con 9. A continuación haga i igual a 1, desplácelo 4 bits a la izquierda y el resultado dos bits a la
derecha. Imprima todos los resultados.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
I
int i = 27 ;
int j = 32;
int aux;
/* Operadores lógicos */
printf("\n Operadores Lógicos");
pri ntf (" \n------------------------ ");
pr-intf("\n AND Logico");
aux = (i >= j ) && (j >= i);
printf("\n (i >= j ) && (j >= i) = % d " , aux); //falso
printf("\n OR Logico");
aux = (i >= j ) || (j >= i) ;
printf("\n (i >= j ) || (j >= i ) = °Ád", aux); //verdad
60 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
return(0);
► 2.11 Escriba un programa que permita calcular la cantidad mensual a pagar y el pago total para un préstamo de
una cantidad, un interés anual y una duración en años dada.
La fórmula a usar para calcular el pago mensual es la siguiente:
Particularice el ejemplo para 100.000 euros, tipo de interés del 3,5% y una duración de 15 años.
Re s o l u c i ó n .
^include <stdio.h>
#include <math.h>
jldefine MESES_EN_UN_ANY0 12
int main(void)
(
float cuantiaPrestamo = 0.0;
float interesAnual = 0.0;
float interesMensual;
int duracionPrestamo = 1;
double pagoMensual = 0.0;
double pagoTotal = 0.0;
int numeroPagos = 0;
/* Entrada de datos */
printf ("\n Cuantía del préstamo: ");
scanf ("%f", &cuantiaPrestamo);
ITES-Paraninfo • 6 1
Problemas resueltos de C
/* Salida de datos */
printf("\n Cuantía del préstamo: %9.2f euros", cuantiaPrestamo)
printf("\n Interés anual: %4.2f ", interesAnual);
printf(''\n Duración del préstamo (años): %d", duracionPrestamo)
printf("\n Número de pagos: %d", numeroPagos);
printf("\n Pago mensual: %8.2f euros", pagoMensual);
printf("\n Cantidad total a pagar: Z9.Zf euros", pagoTotal);
return(0);
► 2. 12 Escriba un programa que calcule la ecuación del espacio recorrido por un objeto que se mueve a velocidad
constante. Había recorrido un espacio inicial de 5.5 m y se mueve a velocidad constante de 3.2 m/s. Debe
pedir por la entrada estándar los segundos que se mueve.
Re s o l u c i ó n .
/* Cálculo del espacio que recorre un móvil *!
#include <stdio.h>
int main(void)
!
float tiempo = 22.3F; /* Tiempo del recorrido *¡
float espacio = 0;
i* operaciones solicitadas */
espacio = Espaci olni ci al + Velocidad * tiempo;
/* Salida de resultados */
printf ("Espacio recorrido = %f metros\n", espacio );
return(O);
62 • ITES-Paraninfo
Capítulo 2 / Tipos de cczz.
► 2.13 Escriba un programa que calcule las raíces de la ecuación cuadrática: a * x 2 + b * x + c = 0ylcts imprima
por pantalla. Debe pedir por pantalla los coeficientes a, b y c.
R e s o l u c i ó n . Para resolver esta ecuación, hay que resolver la fórmula cuadrática:
—b ± V¿>2 —4ac
2a
#1nclude <stdio.h>
#include <math.h>
/* C á l c u l o d e l a s r a í c e s e i m p r e s i ó n d e r e s u l t a d o s *i
printf ("Las raíces de % f x~2 + % f x + % f son: \n", a, b , c );
[► 2.14 Escriba un programa que obtenga la representación de ’ a ’ en letra y en número y que pida un número
entero, imprima su carácter asociado y luego lo imprima como un entero.
Re s o l u c ió n .
#i nc l ude <s t di o. h>
i n t main (voi d)
1
/* Definición de variables */
i n t codigo_a;
char pr i mer aLet r a = 97;
char c a r á c t e r ;
¡TES-Paraninfo • 6 3
Problemas resueltos de C
int numero;
return(O);
Si introduce un número por encima de 127, como 234, observará que al imprimirlo como número no
imprime 127 sino —22. ¿Por qué? Pues porque se usa representación en complemento a 2 y con el octavo
bit a 1 se entiende que es un número entero negativo.
► 2 . 1S Escriba un programa que ordene de menor a mayor dos caracteres que se lean por pantalla y los escriba
ordenados por pantalla.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I
char carl;
char car2;
!► 2 . 16 Escriba un programa que defina un array con 40 números positivos, lo inicialice con valores desde 0 a 39,
los copie a otro e imprima el segundo array.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I
/* Definición de los vectores *!
int vector_l [40];
int vector_2 [40];
int i = 0 ;
/* se inicial iza *!
64 • ITES-Paraninfo
Capítulo 2 I T ipos de d a to s y o p e ra d o re s
/* se copia */
for (i=0; i< 40; i++)
vector_2[i] = vector_l[i];
return(O);
i
En este ejemplo se puede ver que la única forma de copiar un array en otro es elemento a elemento. No se
puede utilizar el operador de asignación directamente para copiar un array en otro.
► 2 . 17 Escriba un programa que defina una estructura que represente un punto del espacio bidimensional. El pro
grama debe leer dos puntos del espacio e indicar si son iguales.
Re s o l u c ió n .
#include <stdio.h>
struct Puntol
float x;
float y;
int main(void)
I
/* Definición de los vectores */
struct Punto pl;
struct Punto p2;
return(0);
ITES-Paraninfo • 6 5
Problemas resueltos de C
I ► 2. 18 Escriba un programa que defina una variable entera x con valor 3 y una d o ub 1e y con valor 5.0. Calcule la
expresión z = 200 y + x e imprima el resultado como doubl e, como un entero y como byte, siendo z
un f 1oat.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I■
int x = 3;
double y = 5.0;
float z;
z = (f1oat)(200 * y + x );
/* Impresión de resultados *1
printfC'El valor float es: %f \n", z );
printfC'El valor double es: %f \n", (double)z );
printfC'El valor integer es: %d \n", (int)z );
printfC'El valor char es: %c \n", (char)z );
return(0);
1
Como se puede apreciar, la conversión de tipos puede conllevar posibles pérdidas de precisión y trun
camientos cuando se pasa de tipos de mayor tamaño a tipos de menor tamaño y cuando se convierten tipos
reales a enteros. Observe el resultado que imprime el valor char, correspondiente a los 8 primeros bits del
valor de z.
► 2. 19 Escriba un programa que lea dos números complejos y obtenga su suma, resta, división y multiplicación.
Re s o l u c ió n .
#include <stdio.h>
#include <complex.h>
#include <math.h>
int main(void)
float _Complex a;
f1oat _Complex b;
float _Complex c;
float x, y; /* variables auxiliares para leer complejos *!
c = a + b;
printf("La suma.es %f + I*%f\n", creal(c), cimag(c));
66 • IT E S - P o r a n in f o
Capítulo 2 / Tipos de datos y operadores
c = a - b;
pri ntf("La resta es + I*%f\n", creai(c), cimag(c) ) ;
c = a * b;
pri ntf("El producto es %f + I*%f\n ", creai(c ), c im ag (c ))
c = a / b;
pri ntf("La división es %f + I*%f\n ", creai(c), cimag(c))
return( 0);
Re s o l u c ió n .
#include <stdio.h>
int Main(void)
I
#define PI 3.1416; /* constante PI */
float area = 0.0; /* variables auxiliares */
int radio = 5;
/* Área de un círculo */
area = PI * Radio * Radio;
printf ("El área de circulo de radio = %d es %5.2 metros cuadrados",
radio, area);
if (area = 3.0)
printf ("El área de circulo es %4.2f \n, area);
I
Pro blem a de Ex a m e n
!► 2.2 I Escriba un programa que permita leer por la entrada estándar e imprimir los datos de una persona. La ficha
debe incluir: nombre y primer apellido, sexo (V/H), estado civil (S/C/V/D), número de hijos, dirección,
ciudad, distrito postal, país, peso en Kg. y altura en centímetros.
Una vez impresos los datos, se calculará el índice de masa corporal y se imprimirá por pantalla. La
fórmula del índice de masa corporal se calcula con la fórmula siguiente, donde la altura está en metros:
altura2
ITES-Paraninfo • 6 7
Problemas resueltos de C
Re s o l u c ió n .
/*
Programa que imprime una ficha personal
y calcula el indice de masa corporal
*/
//include <stdio.h>
//include <math.h>
int main(void)
char nombre[32];
char apel1ido[32];
char sexo = 'V ’;
char estadoCi vi1 = ’C ’;
int numHijos = 2;
char direccion[32];
char ciudad[32];
int distritoPostal = 28080;
char pais[32] = "Beluchistan";
float peso = 70.8;
int altura = 168;
float imc = 0.0, aux =0.0;
/* Salida de datos */
printf("\n Nombre: %s ", nombre );
printf("\n Apellido: Xs ", ' apellido );
printfC'Vn Sexo: Xc ", sexo);
printf("\n Estado civil: Xc ", estadoCivi 1);
6 8 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
return(O);
1
Observe que no se pueden introducir blancos dentro de un string cuando se está leyendo desde la entrada.
Para eso hay que usar funciones de entrada de modo línea, como gets.
I T E S - P o r a n in f o • 69
Problemas resueltos de C
Un kilobyte son 1.024 bytes. Un megabyte son (kilobyte * 1.024) bytes. Un gigabyte es (megabyte * 1.024)
bytes.
x¡ — Xi-\ * 3.
2. 14 Escriba un programa en C que defina dos números reales y calcule su suma, resta, multiplicación y división.
Defina también un entero e imprima la multiplicación y división del primer real por el entero.
2. 16 Escriba un programa que defina un número real, calcule su raíz cuadrada, la redondee al entero más próximo
(por ejemplo, 2 para 2.3 y 3 para 2.7) e imprima los resultados.
2. 17 Escriba un programa en C que defina el nombre de una persona y use variables lógicas para indicar si tiene
hijos o no, si es hombre o mujer, si tiene estudios o no, etc. Incluya todos los aspectos que se le ocurran.
2. 18 Escriba un programa que defina dos variables enteras, i y j, con valores 2567 y 341 respectivamente. Realice
operaciones AND y OR a nivel de bit, desplace ambos dos bits a la derecha, dos bits a la izquierda, una
AND de ambos con 3 y un OR de ambos con 3. Imprima los resultados.
2. 19 Escriba un programa que reciba un número de horas y calcule las semanas, días y horas que representan.
2 .2 0 Escriba un programa que convierta medidas de pulgadas a centímetros, teniendo en cuenta que una pulgada
son 2,54 cm.
2.21 Escriba un programa que asigne a una variable de tipo cha r un valor 345 y un valor -7 respectivamente.
¿Qué ocurre?
2.2 2 Escriba un programa que haga una división por cero. ¿Qué ocurre?
2.23 Escriba un programa que convierta una cantidad en pesetas a euros (1 euro = 166,386 pesetas) y los euros a
dólares (1 dolar = 0,98 euros). Imprima los resultados.
2.2 4 Escriba un programa que defina un número entero x con valor 6 y un valor real y con valor 2,0. Calcule, e
imprima los resultados, de las siguientes operaciones:
■ x * y, imprimiendo el resultado como entero.
■ 3 * x * y, imprimiendo el resultado como float.
■ 3 * x / y, imprimiendo el resultado como double.
■ x elevado a y.
¿Alguna operación hace algo extraño? ¿Por qué?
2.25 Escriba un programa en C que use la biblioteca math para calcular el seno, el coseno y la tangente de un
ángulo. Pruebe con valores positivos y negativos e imprima los resultados.
2.2 6 Escriba un programa en C que defina un string de 96 caracteres, lo rellene de aes y lo imprima por pantalla.
¿Qué ocurre?
7 0 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores
#include <stdio.h>
#define PI 3.14156;
int Main()
i
float radio;
ITES-Paraninfo • 7 /
En este capítulo...
3.1 Introducción a las sentencias de control
3.2 Sentencia
3.3 Sentencia '
3.4 Sentencia .-.r. i
3.5 Sentencia : "
3.6 Sentencia.:
3.7 Sentencia •.-. i ‘ ■
3.8 Sentencias !. >= .L y : ' ' r 1■ ■■'
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
Sentencias de control
Problemas resueltos de C
3 .1 In t r o d u c c i ó n a las s e n t e n c ia s de c o n t r o l
Un programa de computador se puede definir como una secuencia ordenada de instrucciones, dedicadas
a ejecutar una tarea. Debido a esto, aparece el concepto de flujo de ejecución de un programa, que define el
orden que siguen las sentencias durante la ejecución del mismo.
El flujo de ejecución de un programa viene determinado por una serie de patrones o estructuras de pro
gramación. Cada una de estas estructuras de programación se comporta exteriormente como una sentencia
única, de forma que pueda ser concatenada dentro de otras estructuras y así componer el flujo de ejecución
de un programa completo. Estas estructuras de programación son independientes del lenguaje de programa
ción concreto que se esté utilizando, siendo aplicables a cualquier lenguaje de programación existente en la
actualidad.
Los tipos de estructuras de programación que existen en la práctica totalidad de los lenguajes de progra
mación son los siguientes:
■ Secuencia: constituida por 0, 1 ó Ai sentencias que se ejecutan según el orden en que han sido escritas.
Es la estructura más simple y la pieza más básica a la hora de componer estructuras.
■ Selección: consta de una sentencia especial de decisión y de un conjunto de secuencias de instrucciones.
La sentencia de decisión genera un resultado delimitado dentro de un rango preseleccionado (general
mente verdadero o falso) y, dependiendo del resultado obtenido, se ejecutará una de las secuencias
de instrucciones. Pueden existir tantas secuencias de instrucciones como valores posibles del rango de
decisión.
■ Iteración: consta de una sentencia especial de decisión y de una secuencia de instrucciones. La sentencia
de decisión sólo genera dos tipos de resultado (verdadero o falso). La secuencia de instrucciones se
ejecutará de forma iterativa mientras que la sentencia de decisión genere el resultado correcto, en caso
contrario finalizará la ejecución de la estructura de iteración.
La Figura 3.1 incluye el diagrama de flujo para cada uno de los posibles tipos de estructuras, donde C
representa una sentencia de decisión (cuyo valor podrá ser verdadero o falso) y S i (i=1,2, .. .) representa
la secuencia a ejecutar.
falso
verdadero
S2 I
?
SI
1Í
Secuencia Selección Iteración
7 4 • ITES-Paraninfo
Capítulo 3 I Sentencias de control
En el lenguaje C, una sentencia simple debe finalizar con un ’ ; ’ para así identificarla como una única
sentencia. Asimismo, el lenguaje C considera que toda sentencia, finalizada con ’ ; constituye de por sí
una secuencia de instrucciones con una sola sentencia. Por ejemplo, la expresión:
contador = 0;
constituye una secuencia de instrucciones con una sola sentencia.
Si se pretende generar secuencias de más de una sentencia es necesario agrupar entre delimitadores las
sentencias que las componen. Para ello se utilizan los delimitadores ’{’ y ’)’. Así, la expresión:
{
contador = 0;
contador = contador + 1;
1
constituye una secuencia de instrucciones compuesta por dos sentencias.
3.2 S e n t e n c ia i f
La sentencia if permite implementar una estructura de tipo selección, es decir, permite decidir qué
secuencia de código se va a ejecutar a continuación.
La sintaxis general de la sentencia if es la siguiente:
if (expresión)
secuencia de instrucciones
El funcionamiento de la sentencia if es el siguiente:
■ Si la expresión correspondiente al if devuelve un valor verdadero (representado por un valor numérico
distinto de cero), se ejecutará la secuencia de instrucciones subsiguiente.
■ Si la expresión correspondiente al if devuelve un valor falso (representado por un valor numérico igual
a cero), la sentencia if finalizará y se ejecutará la sentencia siguiente.
La sentencia if acepta cualquier tipo de secuencias de instrucciones como parte de su estructura. Cuan
do esta secuencia esté formada por una única sentencia no será necesario el empleo de llaves. Sin embar
go, si la secuencia de instrucciones es compleja, este conjunto de instrucciones deberá ir encerrado entre
paréntesis, tal y como se muestra a continuación:
if (expresión)
í
sentencia 1;
sentencia 2;
► EJEMPLO 3.1 Dado el siguiente fragmento de código, ¿qué error se aprecia en él?
if (numero % 2 == 0);
printf("Ei número es par\n");
RESOLUCIÓN. El fragmento de código anterior, a pesar de ser válido, no realiza correctamente su come
tido. Esto se debe a que la sentencia if finaliza con ’ ; Para observar mejor esto, conviene reescribir la
sentencia anterior de la siguiente forma:
if (numero % 2 == 0)
ITES-Paraninfo • 75
Problemas resueltos de C
3.3 S e n t e n c ia i f - e l se
La sentencia if - el se es una forma ampliada de la sentencia if, por lo tanto forma parte también de
las estructuras de selección y todo lo dicho anteriormente para la sentencia if también se aplica aquí. La
sentencia if - e 1 se permite seleccionar entre dos secuencias distintas de instrucciones.
La sintaxis general de la sentencia if -e 1se es la siguiente:
if (expresión)
secuencia de instrucciones 1
ei se
secuencia de instrucciones 2
El funcionamiento de la sentencia if -e 1se es el siguiente:
■ Si el resultado de evaluar expresión devuelve un valor verdadero (representado por un valor numérico
distinto de cero), se ejecutará la secuencia de instrucciones situada a continuación del if (secuencia de
instrucciones 1).
■ Si la expresión devuelve un valor falso (representado por un valor numérico igual a cero), se ejecutará
la secuencia de instrucciones situada a continuación del e is e (secuencia de instrucciones 2).
La expresión debe ir encerrada entre paréntesis para distinguirla del resto del código. Las secuencias de
instrucciones deberán ir encerradas entre llaves cuando se trate de más de una sentencia.
El diagrama de flujo de la sentencia if - e 1se está reflejado en la Figura 3.2.
'•áií
falso verdadero
■
... - expresión
Secuencia 2 S ecuencia 1
3.4 S e n t e n c ia whi 1e
La sentencia wh i1e forma parte de las estructuras de iteración. Su función consiste en ejecutar de forma
repetida una secuencia de instrucciones determinada.
La sintaxis general de la sentencia wh i1e es la siguiente:
while (expresión)
secuencia de instrucciones
El funcionamiento de la sentencia wh i1e es el siguiente:
■ Mientras que la expresión devuelva un valor verdadero (representado por un valor numérico distinto de
cero), se ejecutará repetidamente la secuencia de instrucciones, evaluando nuevamente la expresión en
cada iteración.
■ Si la expresión devuelve un valor falso (representado por un valor numérico igual a cero), finalizará la
ejecución de la sentencia whi ie.
76 . ITES-Paraninfo
Capítulo 3 I Sentencias de control
La expresión asociada al wh i1e debe ir encerrada entre paréntesis para distinguirla del resto del código.
La secuencia de instrucciones deberá estar formada tal y como se explicó al comienzo del capítulo.
El diagrama de flujo de la sentencia wh i1e está reflejado en la Figura 3.3.
falso
expresión != 0
í v e rd a d e ro
W
Secuencia
RESOLUCIÓN. Se trata de un bucle infinito. La condición asociada a la sentencia while es siempre cierta y,
por lo tanto, se ejecuta de forman indefinida la sentencia p rin t f incluida dentro del bucle while. Conviene
tener cuidado con este tipo de bucles, puesto que no se puede salir de ellos.
3.5 S e n t e n c ia f o r _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
La sentencia fo r forma parte también de las estructuras de iteración. Su función consiste, por tanto, en
ejecutar un número determinado de veces una secuencia de instrucciones.
La sintaxis general de la sentencia for es la siguiente:
for (expresión 1; expresión 2; expresión 3)
secuencia de instrucciones
El funcionamiento de la sentencia for es el siguiente:
■ Primera vez que se ejecuta la sentencia for:
• Se ejecuta la expresión n° 1.
• Se evalúa la expresión n° 2 y se compara el resultado.
* Si el resultado es verdadero se ejecuta la secuencia del for y se vuelve a empezar la sentencia
completa.
* Si el resultado es falso, finaliza la ejecución de la sentencia.
■ Segunda y sucesivas veces que se ejecuta la sentencia for:
• Se ejecuta la expresión n° 3.
• Se evalúa la expresión n° 2 y se compara el resultado.
* Si el resultado es verdadero se ejecuta la secuencia del for y se vuelve a empezar la sentencia
completa.
* Si el resultado es falso, finaliza la ejecución de la sentencia.
ITES-Paraninfo • 77
Problemas resueltos de C
Las tres expresiones deben ir separadas entre sí por ’ ; ’ y el conjunto de dichas expresiones debe estar
encerrado entre paréntesis, para distinguirlo del resto del código. La secuencia de instrucciones deberá estar
formada tal y como se explicó al comienzo del capítulo.
El diagrama de flujo de la sentencia for se muestra en la Figura 3.4.
expresión 1
falso
Expresión 2 != 0 :i
| verdadero
Secuencia
expresión 3
El uso de la sentencia f o r es útil cuando se quiere realizar una determinada tarea un número determinado
de veces. Por ejemplo, si se quiere ejecutar un grupo de sentencias N veces, se puede recurrir al siguiente
fragmento de código:
int contador;
O lo que es lo mismo:
int contador;
3.6 S e n t e n c ia d o - w h i l e
La sentencia do-whi 1e forma parte también de las estructuras de iteración. Su función consiste, por
tanto, en ejecutar varias veces una secuencia de instrucciones determinada.
La sintaxis general de la sentencia do - wh i1e es la siguiente:
do
secuencia de instrucciones
whiie (expresión);
El funcionamiento de la sentencia do-whi ie es el siguiente:
■ En primer lugar se ejecuta la secuencia de instrucciones.
■ Después se evalúa exp res ión y se comprueba su resultado. Si es verdadero se repite'la ejecución de la
sentencia. Si es falso, finaliza dicha ejecución.
7 8 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
La expresión debe ir encerrada entre paréntesis, para distinguirla del resto del código. Si la secuencia de
instrucciones está formada por más de una sentencia, todas ellas deben ir encerradas entre llaves.
El diagrama de fluj o de la sentencia d o -w h i1e está reflej ado en la Figura 3.5.
Secuencia
v e rd a d e ro
........ expresión != 0
falso
I ► EJEMPLO 3.3 Dado el siguiente fragmento de código, conviértalo en otro equivalente que utilice la sentencia
whi 1e.
do {
printf("Introduzca un número: ");
scanfC'M", &N);
printf("El número introducido es %d\n", N);
whi 1e(N > 0);
RESOLUCIÓN. Toda sentencia de iteración se puede convertir a cualquiera de las otras sentencias de
iteración. En este caso, el fragmento de código equivalente al anterior es el siguiente:
printf("Introduzca un número: ");
scanf("Xd", &N); ■
printfCEl número introducido es %d\n". N);
whi l e ( N > 0) {
printf("Introduzca un número: ”);
scanf("%d", &N);
printfCEl número introducido es % d \n", N);
i
Observe la diferencia que existe entre la utilización de un bucle while y la utilización de un bucle
do - whi 1e. El primero es un bucle que se denomina de tipo 0 —N. En este tipo de bucles primero se evalúa
la condición y luego se ejecuta el grupo de sentencias asociado al whi 1e. Si no se cumple la condición
inicialmente el bucle no se ejecutará, es decir, el bucle se puede ejecutar 0 o más veces. El bucle do - whi 1e,
sin embargo, es un bucle que se denomina de tipo 1 —N. En este caso, las sentencias del bucle se ejecutan
al menos una vez, puesto que primero se realiza la iteración y luego se realiza la comprobación.
3.7 S e n t e n c ia sw itch
La sentencia swi tch pertenece a las estructuras de tipo selección, es decir, permite decidir qué secuencia
de código se va a ejecutar a continuación. En concreto permite elegir desde 1 hasta N secuencias de código
distintas.
La sintaxis general de la sentencia swi tch es la siguiente:
switch (expresión) (
case expresión 1:
sentencia 1.1;
ITES-Paraninfo • 79
Problemas resueltos de C
sentenci a 1.2
break;
case expresión N:
sentenci a N.l
sentenci a N.2
break;
default:
sentenci a D. 1
sentenci a D.2
í
^f
expresión
80 • ITES-Paraninfo
Capitulo 3 / Sentencias de control
3.8 S e n t e n c ia s b r e a k y c o n t i nue
Las sentencias breakycontinue son sentencias que modifican el comportamiento de otras sentencias
de control simplemente por el hecho de estar incluidas en algún punto de su secuencia de instrucciones.
La sentencia b re a k afecta a las sentencias switch,while, f o r y d o - w h i l e d e l a forma siguiente:
■ Si aparece una sentencia break dentro la secuencia de instrucciones de cualquiera de las sentencias
anteriores, dicha sentencia termina inmediatamente.
■ Si aparece en el interior de un bucle anidado sólo finaliza la sentencia de iteración más interna, el resto
se ejecuta de forma normal.
La sentencia cont i nué afecta a las sentencias while, f o r y d o - w h i l e d e l a forma siguiente:
■ Si aparece una sentencia cont i nué dentro la secuencia de instrucciones de cualquiera de las sentencias
de iteración anteriores, dicha sentencia da por terminada la iteración actual y se ejecuta una nueva
iteración, evaluando de nuevo la expresión condicional del bucle.
■ Si aparece en el interior de un bucle anidado sólo afecta a la sentencia de iteración más interna, el resto
se ejecuta de forma normal.
La Figura 3.7 muestra, sobre el diagrama de flujo de un bucle whi 1e, el efecto de usar las sentencias
continueybreak dentro de dicho bucle. Como se puede apreciar en la figura, usar continué equivale a
decir que se ha cumplido la condición para repetir el bucle. Por el contrario, usar break equivale a decir que
no se cumple dicha condición y que, por tanto, el bucle debe terminar. ¿A qué equivalen estas instmcciones
en ensamblador? Pues a saltos incondicionales, exactamente igual que el goto.
falso
e x p r e s i ó n != 0
verdadero
I Secu e n c i a
continue
I break-
E v it e est o s er r o r es
A continuación se describen los principales errores que aparecen cuando se utilizan las sentencias de
control presentadas en este capítulo.
No utilizar secuencias delimitadas entre llaves Prescindir de las llaves cuando se programan sentencias
de control puede dar lugar a errores difíciles de detectar y depurar. Estos errores se producen, sobre todo,
cuando se modifica el código con posterioridad. Este error genera un comportamiento completamente
distinto del programa.
ITES-Paraninfo • 8 /
Problemas resueltos de C
Incluir un ’ ; ’ junto a la sentencia de control Finalizar todas las sentencias de C con ’ ; ’ no es correcto
en todos los casos. Imagine que escribe lo siguiente:
i f (0 == contador); printf ("Contador vacio\n");
Aunque el if se evalúe bien no imprime el mensaje. ¿Por qué? Pues porque el ’ ; ’ de detrás indica
precisamente eso: no hacer nada. Si ocurre esto mismo en sentencias fo r o wh i 1e, lo que ocurre es que
no se ejecuta el bucle correspondiente.
Confusiones entre asignación y comparación de igualdad Este error es muy frecuente y difícil de de
tectar. Si se escribe if (contador = 0), se está asignando 0 al contador, no se está haciendo una com
paración. Esto se evita poniendo la constante primero, es decir if ( 0 = contador), lo que daría un
error de compilación y detectaría el problema.
Uso de sentencias no explícitas en las sentencias de control Imagine que escribe: while (contador)
{ . . . . }. ¿Qué condición controla el bucle? Un programador de C sabe que el bucle funciona mientras
contador sea distinto que cero. Sin embargo, para que la semántica quede clara es mucho mejor escribir
while (0 != contador) { . . . . ¡, lo que no deja lugar a duda.
Suposiciones erróneas en los valores de control del bucle Imagine que en el caso anterior se quiere que
el bucle funcione mientras contador es positivo y que decremente contador de 2 en 2. Asumiendo
que while (contador) funciona mientras sea distinto de cero, es un error común suponer que sólo
funciona mientras sea positivo. Imagine que el contador empieza con valor 5.
contador = 5; while (contador) I
contador = contador - 2;
)
La serie de decremento sería 5 , 3 , 1, -1, . . . y estaríamos en un bucle infinito.
Bucles infinitos A veces no se controla bien la condición de control y se ejecuta un bucle infinito. Por
ejemplo, si se escribe:
contador = 0; valor = 2;
while (contador < 20){
contador =contador* 2;
valor = valor * 2:
1
El bucle no termina nunca. Por ello, es conveniente tener centinelas que avisen si hay desbordamiento o
sobrepasamiento de la condición. En el ejemplo anterior, por ejemplo, se cumple el primer caso, ya que
val or crecería hasta el mayor positivo posible hasta convertirse en un negativo (por el complemento a
2). Un posible centinela sería comprobar que valor > 0.
Uso de condición de control imprecisa para un bucle Este caso se da cuando se usan variables de control
que no son discretas, como por ejemplo un número real. En este caso, los redondeos y la precisión
pueden dar lugar a que no se alcance un valor exacto de comparación. Por ejemplo:
contador = 0.0;
whi l e (contador != 1)
Icontador += 0.33:1
Este bucle no acaba porque 0.33 * 3 son 0.99 y 0.33 * 4 son 1.22. Para estos tipos de datos es mejor usar
límites que condiciones exactas. Si las necesita exactas, hay que aumentar la precisión de la condición de
control, Por ejemplo, incrementar con 0.3333333333, hasta que se alcance el valor exacto. Esta solución
funcionará o no dependiendo de la precisión de la representación de cada computador, por lo que no es
recomendable.
Errores de tipo fuera por uno Si se quiere ejecutar un bucle controlado por contador n veces, se puede
hacer lo siguiente:
contador = 1;
whi1e (contador < n )j
contador ++;
1
Sin embargo, este bucle sólo se ejecuta n - 1 veces. Imagine que escribe:
82 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
cont ador = 0;
whi l e ( cont ador <= n) {
cont ador ++;
1
Entonces se ejecuta n+1 veces. Evite este tipo de errores de fuera por uno. Son fáciles de cometer y muy
difíciles de detectar. ¿Qué debería escribir? Pues lo siguiente:
cont ador = 0;
whi l e ( cont ador < n)
{cont ador ++;1
Confundir el criterio de comparación del f or Si se escribe:
fo r ( i =0; i < 10; i ++) . . .
se ejecuta el contenido del bucle 10 veces. Si en lugar de eso se escribe:
fo r ( i = 0 ; i == 10, i ++) . . .
no ocurre lo mismo. ¿Por qué? Pues porque se ejecuta la primera iteración y se compara la condición de
control (i ==10). Como no es cierta, se termina el bucle.
Intentar escribir expresiones de control del fo r muy complejas Es preferible escribir dos bucles de
control que usar expresiones de control muy complejas, cuyo resultado puede ser muy distinto del
esperado. Tampoco hay que usar expresiones complejas en los incrementos. Observe que, como ya se
ha indicado, se podría meter un programa entero dentro de un f or , afectando incluso a variables que
no están controlando el bucle. Esto no es muy lógico y da lugar a errores graves de mantenimiento. Por
ejemplo, si se quiere incrementar la variable cont ador y multiplicarla por sí misma 10 veces, se puede
escribir:
cont ador = 0;
f or ( i =0; i < 10; i ++) I
cont ador ++;
1
Sin embargo, también se podría hacer lo siguiente:
fo r (i =0, contador=0 ; i < 10; i ++, c o n t a d o r ++ ) ;
Evite este tipo de programación siempre que pueda.
Olvido de la sentencia break en el swi t ch Es frecuente que los programadores olviden la sentencia
break dentro de uno o varios case de una estructura swi tch. El resultado es que se ejecuta todo el
código de ese case y los siguientes hasta que se encuentre una sentencia break, dando lugar a compor
tamientos extraños. Algunos programadores usan esta técnica cuando hay que hacer código incremental
dependiendo de una condición. Por ejemplo: si el mes es enero, calcular nómina, si es febrero, acumular
enero y calcular nómina febrero. Este tipo de procesamiento debe quedar claramente explicado, a no
ser que se quieran originar errores graves en posteriores modificaciones del código. En principio, todo
bloque case debería tener un b r e a k al final.
Uso de la misma variable de control en bucles anidados Habitualmente, cuando se programa se usan
variables del tipo i, j, k, ... para controlar bucles. En bloques anidados es frecuente que se olvide la
variable de control y se reutilice una ya usada en bloques más externos o usada para otro propósito.
¿Cómo se puede evitar esto? De dos formas: reserve ese tipo de variables exclusivamente para el control
de bucles y no use variables poco descriptivas (como cont ador) para controlar los bucles.
Suponer que el compilador asigna valor cero a las variables sin inicializar Si se escribe i nt i ; . . .
whi l e (cont ador < 10) . . . se está asumiendo que el valor inicial de i es 0. Esto no tiene por qué
ser cierto. Hay compiladores que sí lo hacen, pero otros ponen un valor aleatorio o un valor negativo
alto. ¡Nunca haga este tipo de suposiciones en las declaraciones de variable!
Efecto de las sentencias break y cont i nue A menudo, los programadores noveles.piensan que estas
sentencias afectan al nivel más exterior de un conjunto de bucles anidados. Recuerde que no es así. Sólo
afectan al comportamiento del bucle al que pertenecen.
Confusión de las sentencias break y cont i nue No es infrecuente confundir ambas palabras reservadas.
¡Tenga cuidado! Originan errores muy difíciles de detectar.
ITES-Paraninfo • 83
Problemas resueltos de C
i n t mai n(voi d)
I
i n t numero;
i f (numero % 2 == 0)
p r i n t f C ' E l número es p a r \ n " ) ;
r e t u r n (0) ;
I
El programa comienza definiendo el número a leer. Una vez leído el número, utiliza la sentencia i f para
evaluar la expresión numero % 2 == 0. Esta sentencia será cierta cuando el resto de dividir numero entre
2 sea 0, es decir, cuando el número sea par. En este caso, la expresión anterior se considerará cierta y se
ejecutará la sentencia p ri nt f. Como puede observarse en este ejemplo, la expresión asociada al i f debe
ir encerrada entre paréntesis, en caso contrario la sentencia estará mal construida y dará lugar a un error de
compilación.
► 3.2 Escriba un programa que solicite al usuario una letra (mayúscula o minúscula) e indique si es una vocal o
una consonante
Re s o l u c i ó n .
#i n c l u d e <s t di o. h>
#i nc l ude <ctype.h>
i f ( s c a n f í "%c",&ch) < 1)
/ * e rr o r en la i n t r o d u c c i ó n del c a r á c t e r *!
p r i n t f ( " Err or en l a i n t r o d u c c i ó n del c a r á c t e r \ n " );
el se i f ( ! i s a l p h a ( c h ))
/ * el c a r á c t e r no es una l e t r a */
p r i n t f ( " E r r o r , el c a r á c t e r no es una l e t r a \ n " ) ;
el se i f ( (ch == ’a ’ ) | | (ch == ’e ’ ) ||
(ch == ’ i ’ ) | | (ch == ’o ’ ) | | (ch == ’ u ’ ) )
p r i n t f C ' E l c a r á c t e r %c es una v o c a l \ n ", c h );
el se
p r i n t f C ' E l c a r á c t e r l e es una consonant e\ n" , c h ) ;
return(O);
8 4 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
I ► 3.3 Escriba un programa en C que lea un número entero e indique si el número se puede expresar como el
cuadrado de dos números enteros. Por ejemplo, el número 9 se puede expresar como el cuadrado de 3 x 3,
sin embargo, el número 8 no se puede expresar como el cuadrado de dos números enteros. Para resolver
este ejercicio se puede utilizar la función de biblioteca sq r t declarada en el archivo de cabecera mat h. h y
que permite calcular la raíz cuadrada de un número.
Re s o l u c i ó n .
#i nc l ude <s t di o. h>
#i n c l u d e <math.h>
i n t mai n(voi d) {
i n t numero;
i nt rai z ;
r e t u r n (0);
R e s o l u c i ó n . Una forma sencilla de resolver este ejemplo es construir el diagrama de flujo asociado al
conjunto de sentencias anterior. Este diagrama de flujo se puede apreciar en la Figura 3.8. Siguiendo dicho
diagrama de flujo, se puede comprobar que el resultado final de la variable z es igual al valor de la variable
x, es decir, 2.
► 3.5 Dadas las ecuaciones de dos rectas y = ax + b e y = ex + d, escriba un programa que lea los coeficientes
de estas dos rectas e indique si las rectas son iguales, paralelas o se cortan en un punto. En este último caso,
el programa debe imprimir el punto de corte de ambas rectas.
#include <stdio.h>
int main(void)
ITES-Paraninfo • 8 5
Problemas resueltos de C
x =2
v e rd a d e ro
x != 3
z = 0 falso
x 1
Z=X z = 1
if (a == c && b == d )
printf("Las rectas son iguales\n");
else if (a == c)
printfC'Las rectas son paraleas\n");
el se {
/* las rectas se cortan en un punto */
xCorte = (d-b) / (a-c);
yCorte = xCorte * a + b;
printf("El punto de corte (x,y) \n");
printf("(Xf,Xf)\n", xCorte, yCorte);
1
return(O);
► 3.6 Escriba un programa que calcule el mínimo, el máximo y la media de una lista de números enteros positivos
introducidos por el usuario. La lista finalizará cuando se introduzca un número negativo.
Re s o l u c i ó n .
#include <stdio.h>
86 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
mínimo = n
máximo = n
suma = n
ntotal = 1 /* Cantidad de números leídos */
media = 0
I* Leer el resto */
printf("Introducir un número: ");
ret = scanf( " % d",&n) ;
while ((ret == 1) && (n >= 0)) (
if (n < minimo)
mi ni mo = n ;
if (n > máximo)
máximo = n;
suma = suma + n;
ntotal ++ ;
pri ntf("\n" ) :
pri ntf("mi ni mo=%d\n".minimo);
pri ntf("maxi mo=%d\n".máximo);
pri ntf ( "medi a = M \ n " .medi a ) :
p r in t f ( " \n " ) ;
return (0):
► 3.7 Escriba un programa que lea un número N mayor que O y calcule la siguiente suma 1 + 2 + 3 + ... + N.
Re s o l u c i ó n .
#include <stdio.h>
int main(void) ¡
int numero;
int suma = 0;
IT E S - P a r a n in f o • 87
X\ )
Problemas resueltos de C
return (0);
1
El programa anterior define una variable de tipo int, denominada suma, que se utilizará para ir acumu
lando el valor de la suma.
I ► 3.8 Después de ejecutar el siguiente fragmento de programa, ¿cuál será el valor final de la variable x?
i nt x = 0;
i n t n = 16;
whi l e (n % 2 == 0) {
x = x + n:
n = n / 2;
)
RESOLUCIÓN. Para resolver este tipo de ejercicios, conviene construir una tabla como la que se muestra
en la Tabla 3.1. Como se puede apreciar en esta tabla, el grupo de sentencias asociado al whi 1e se ejecuta
cuatro veces y el resultado final almacenado en la variable x es 30. En la quinta iteración el resultado de la
condición n 1 2 = = 0 es falso y se sale del bucle whi 1e.
Iteración X n n%2 == 0
Inicialmente 0 16 -
Primera 0 16 verdadero
Primera 16 8 verdadero
Segunda 16 8 verdadero
Segunda 24 4 verdadero
Tercera 24 4 verdadero
Tercera 28 2 verdadero
Cuarta 28 2 verdadero
Cuarta 30 1 verdadero
Quinta 30 1 falso
1. 50
2. 0
88 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
3. 25
4. 100
R e s o l u c i ó n . 50
► 3.1 I Escriba un programa en C que lea dos números: a de tipo fl o at y b de tipo i nt . El programa debe
calcular ab.
Re s o l u c i ó n .
#i n c l u d e <s t di o. h>
i n t mai n(voi d)
I
f l o a t a;
int b;
f l o a t pot enci a = 1;
int y,
f o r ( j = 0; j < b; j++)
pot enci a = pot enci a * a;
return(O);
!
El programa inicialmente lee los dos números (a y b). Se utiliza la sentencia i f junto con la función
s canf para determinar si los datos se han introducido correctamente. Recuerde que la función s canf de
vuelve un valor de tipo entero que indica el número de elementos que se han leído correctamente. Si el
ITES-Paraninfo • 8 9
Problemas resueltos de C
resultado que devuelve es menor que 1, indicará que no se ha leído correctamente el dato que se esperaba.
Es importante que en sus programas haga este tipo de comprobaciones para asegurarse de que trabaja con
datos correctos. Esta es una buena práctica de programación.
Una vez leídos los dos números, el programa utiliza una sentencia for para calcular el valor de la
potencia. La Tabla 3.2 muestra la ejecución de este bucle cuando a — 8 y b — 3.
Tabla 3.2 Ejecución del bucle del programa del Problema 3.1 I
!► 3. 12 Escriba un programa que solicite del usuario un número N y luego muestre por pantalla la siguiente ejecu
ción:
1
1 2
1 2 3
12 3 4
1 2 3 ........ N
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
í
int N;
int i ;
int j ;
90 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
return(O);
}
En este ejemplo se ha recurrido a la concatenación de dos estructuras de repetición, en este caso dos
sentencias for. Esta forma de concatenación se conoce como anidamiento de bucles y es muy habitual su
uso en programas reales.
► 3.13 Dado el siguiente fragmento de programa, ¿cuántas veces se ejecuta la sentencia pri ntf?
int i ,j ;
R e s o l u c i ó n . El bucle exterior (el primer bucle for) se ejecuta 9 veces, desde i=1 hasta i=9. Por cada
ejecución de este bucle, el segundo bucle se ejecuta 10 - i veces. Es decir, para i=1 se ejecuta 9 veces, para
i=2 se ejecuta 8 veces y así sucesivamente. Es decir, la sentencia pri ntf se ejecuta 9 + 8 + 7 + Ó + 5 +
4 + 3 + 2 + 1 = 4 5 veces.
► 3. 14 Escriba un programa que lea de forma repetida un número N. Para cada número leído el programa calculará
la suma 1 + 2 + 3 + ... + 1V. Una vez mostrado el resultado, el programa preguntará al usuario si desea
continuar; si se introduce s el programa continuará la ejecución, en caso contrario finalizará.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I
int numero;
int suma;
int j ;
char opcion;
do I
printf ("Introduzca un número (Debe ser mayor que 0): ");
scanf (" t ú ", &numero);
suma = 0;
for (j=l; j <= numero; j++)
suma = suma + j ;
return(O);
I
Como puede observarse, en este programa también se ha recurrido a una estructura que utiliza bucles
anidados. El bucle interno (for) se utiliza para calcular la suma correspondiente a cada número leído.
El bucle externo (do-whi 1e) se utiliza para determinar la finalización del programa. El empleo del bucle
do-whi 1 e suele ser útil en este tipo de situaciones, tal y como se muestra en el ejemplo anterior. Algo
interesante a destacar de este programa es la sentencia scanf utilizada para leer la variable opcion. Esta
ITES-Paraninfo • 91
Problemas resueltos de C
sentencia ha utilizado como especificador de formato " \ n %c ". Se ha utilizado el carácter de salto de línea
para recuperar el salto de línea introducido anteriormente. Si no se hubiera colocado este salto de línea la
función p r i nt f habría leído el salto de línea anterior y el programa habría tenido un comportamiento no
deseado.
► 3. 15 Un número perfecto es un entero positivo igual a la suma de sus divisores propios. Un divisor propio es
un entero positivo distinto que el número en sí mismo que divide al número de forma exacta (es decir, sin
resto). Por ejemplo, 6 es un número perfecto porque la suma de sus divisores propios 1, 2 y 3 es igual a 6.
8 no es un número perfecto, porque la suma de sus divisores propios, 1 + 2 + 4 es distinto de 8. Escriba un
programa que acepte un entero positivo y determine si es un número perfecto. Igualmente, muestre todos
los divisores propios del número.
Re s o l u c ió n .
#i nc l ude <s t di o. h>
i n t mai n(voi d)
í
i n t numero;
i nt divisor;
i n t suma;
di vi s o r = l ;
suma=0;
p r i n t f ( " L o s d i v i s o r e s propi os de %d son: \n ". numero);
whi l e ( d i v i s o r < numero ) {
i f (numero 1 d i v i s o r == 0) (
pr i n t f C' Zd ", d i v i s o r ) ; / * d i v i s o r propi o */
suma = suma + d i v i s o r ;
d i v i s o r++;
I
p r i n t f ( ” \ n ");
i f ( numero==suma)
p r i n t f C ' El número %d es p e r f e c t o \ n " . n u m e r o ) ;
el se
p r i n t f C ' E l número tú NO es p e r f e c t o \ n " . n u m e r o ) ;
return(O);
i n t mai n(voi d)
92 • ITES-Paraninfo
Capítulo 3 / Sentencias de control
i nt c ;
p r i n t f ( "Código \ t C a r á c t e r \ n ");
f o r (c = 0; c < 256; c++)
pri n t f ( "Id \ t %c\ n", c , c );
return(O);
)
La clave del programa anterior reside en la sentencia p r i nt f . La variable e se imprime en primer lugar
en formato numérico y en segundo en formato carácter, mostrándose en este caso el carácter correspondiente
al código ASCII.
► 3. 17 Escriba un programa que lea un mes en número (1 para enero, 2 para febrero, etc.) y un año e indique el
número de dias de ese mes. Recuerde que un año es bisiesto si es divisible por cuatro, excepto cuando es
divisible por 100, a no ser que sea divisible por 400. Así, 1900, no fue bisiesto pero el año 2000 si lo fue.
Re s o l u c ió n .
#i nc l ude <s t di o. h>
i nt mai n(voi d )
(
/ * D e f i n i c i ó n de v a r i a b l e s */
i nt mes;
i n t ani o;
p r i n t f C I n t r o d u z c a un mes: ");
s canf ( "7od ' ' , &mes);
swi t ch (mes)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
p r i n t f C ' E l número de dí as es 31 \ n ” ):
break;
case 2: / * f eb r er o */
/ * se determina s i es b i s i e s t o *!
i f (( a n i o% 4 == 0) && ( ( a ni o % 100 != 0)
|| ( ani o % 400) == 0))
p r i n t f C ' El número de dí as es 29 \ n " ) ;
el se
printfC' El número de dí a s es 28 \ n");
break;
case 4:
case 6:
case 9:
ITES-Paraninfo • 9 3
Problemas resueltos de C
case 11:
printfC'El número de días es 30 \n");
break;
default:
pri ntf("Error en la introducción de datos1');
)
return(O);
!► 3. 18 Escriba un programa que lea un número entero y lo descomponga en factores primos. Ejemplo:
18 = 2 * 3 * 3
11 = 11
35 = 5 * 7
40 = 2 * 2 * 2 * 5
Re s o l u c ió n .
#i nclude <stdi o .h>
int main(void)
(
/* Definición de variables */
int numero;
int factorPrimo;
f actorPrimo = 2;-
else
factorPrimo++;
► 3. 19 Escriba un juego de adivinanza. El programa pedirá al usuario dos números (el número inferior y el número
superior), por ejemplo 1 y 100, y un número de intentos, por ejemplo, 4. El programa obtendrá a continua
ción un número aleatorio entre 1 y 100, y el usuario deberá adivinarlo utilizando como mucho 4 intentos.
Cada vez que el usuario introduce un número, el programa le dice si es mayor o menor. Al final, el programa
indica si se ha ganado o no.
Para obtener un número aleatorio se puede utilizar la función de biblioteca rand que permite generar
números pseudoaleatorios. Para que la secuencia de números pseudoaleatorios sea distinta de una ejecución
94 • IT E S - P o r o n in f o
Capítulo 3 / Sentencias de control
a otra del programa es necesario fijar la semilla, utilizando la función de biblioteca srand con un valor
distinto. Para ello se puede utilizar la expresión srand(time(NULL)) que fija el valor de la semilla con la
hora actual. Las funciones randysrandse encuentran definidas en el archivo de cabecera std 1 ib .h y la
función ti me en el archivo ti me .h.
Re s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
srandíti me(NULL));
/*
* randO genera un número entre 0 y RAND_MAX
* pero se necesita uno entre superior e inferior.
*i
escala = (1.0 - (double)rand() / RAND_MAX) ;
secreto = superior - escala * (superior - inferior) ;
return (0);
Capítulo 3 / Sentencias de control
a otra del programa es necesario fijar la semilla, utilizando la función de biblioteca srand con un valor
distinto. Para ello se puede utilizar la expresión s rand (t i me (NU LL)) que fija el valor de la semilla con la
hora actual. Las funciones randy srand se encuentran definidas en el archivo de cabecera stdlib.hyla
funci ónt i meenel ar chi vot i me. h.
Re s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
srand(time(NULL));
/*
* randO genera un n ú m e r o entre 0 y RAHD_MAX
* pero se necesita uno entre superior e inferior.
*/
escala = (1.0 - (double)rand() / RAND_MAX) ;
secreto = superior - escala * (superior - inferior) ;
return (0);
ITES-Paraninfo • 95
Problemas resueltos de C
!► 3.20 Escriba un programa que calcule el número ir. Para su cálculo se puede utilizar la siguiente expresión:
rr 11 1
1 1
1 ^^ c-ir
4 _ t + S 7 + _ (2n + 1)
n=0
Re s o l u c ió n .
^include <stdio.h>
#include <math.h>
int main(void)
í
/* D e f i n i c i ó n de variables */
float pi; /* número PI calculado */
int n;
pi = 0.0;
n = 0.0;
/* u n a iteración más */
n ++;
return(0);
Pro blem a de Ex a m e n
!► 3.21 Escriba un programa que pida un número al usuario y muestre por pantalla el resultado de su raíz cuadrada.
Para su resolución no se puede utilizar la función de biblioteca s qr t . Para realizar este ejercicio se puede
utilizar un método aproximado que permita llegar a la solución con un error menor de 0.0001.
Re s o l u c ió n .
jfinclude <stdio.h>
#include Cmath.h)
main(\/oid)
/* D e f i n i c i ó n de va r i ab
f 1oat epsi1on = 0 .0001 ;
fl oat xl, x 2 , mi tad ;
fl oat rai z ;
fl oat numero ;
96 • ITES-Paraninfo
Capítulo 3 / S e n te n c ia s de control
xl = 0.0;
x2 = numero;
mitad = fabs(xl-x2) / 2.0;
rai z = xl + mi tad;
while (fabsCraiz * raiz - numero) > epsilon) 1
printf("xl:%f x2:%f raiz:%f\n", xl, x2, raiz);
3.7 Construya un programa que lea un número decimal y devuelva su correspondiente representación binaria
usando la técnica de divisiones sucesivas.
3.8 Redacte un programa que lea dos caracteres e imprima el valor decimal asociado al número hexadecimal
formado por dichos caracteres. Los caracteres deben pertenecer al rango 0-9, A-F.
3.9 Dado un mes del año y el día de la semana en que comienza, escriba un programa que muestre por pantalla
la representación del calendario correspondiente a dicho mes tal y como se ve en el siguiente ejemplo.
Agosto
L M X J V S D
ITES-Paraninfo • 97
Problemas resueltos de C
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
3.10 ¿Cuántas veces se ejecuta la función pri ntf en el siguiente fragmento de programa?
a = 9;
for (i = 0 ; i < 100; i++)
if ((0 == a % 4) ¡| (i % 2) == 0)
pri ntf ( "7od %d \n", a , i);
3.11 Escriba un programa que solicite un número de segundos y muestre por pantalla dicha cantidad de tiempo
en horas, minutos y segundos.
3.12 Construya un programa que realice las operaciones de suma, resta, multiplicación y división. El programa
deberá mostrar el siguiente menú.
Programa calculadora
3. 14 Escriba un programa que lea los centros (x, y) y los radios de dos circunferencias e indique el número de
puntos en común que tienen ambas circunferencias.
3. 15 Escriba un programa que lea cuatro puntos (x, y) del espacio e indique si estos puntos forman un cuadrado
o un rectángulo.
98 • ITES-Paraninfo
r
En este capítulo...
4 .1 Punteros y memoria
4.2 Definición de punteros
4.3 Operadores de punteros
4.4 Punteros y memoria dinámica
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
Punteros
■ N fe
Problemas resueltos de C
El objetivo de este capítulo es explicar el concepto de puntero y su relación directa con la memoria del
computador. Así mismo, se explicarán los operadores relacionados con los punteros y cómo utilizarlos para
manejar la memoria dinámica.
Los temas que se tratan en el capítulo son:
■ Presentación de los punteros en C y su relación con la memoria de un computador.
■ Presentación de los operadores de dirección e indirección.
■ Presentación del operador s iz e of y de la constante NUIL utilizada para distinguir los punteros nulos.
■ Introducción a la memoria dinámica. Presentación de las funciones mallocyfree
4 .1 Puntero s y m e m o r ia
Cuando se habla de punteros en cualquier lenguaje ha de hacerse referencia a la forma en que está
estructurada la memoria de un computador. Por ello, antes de entrar en la definición de lo que es un puntero,
se realizará un breve repaso de la estructura y del funcionamiento básico de la memoria en un computador.
En pocas palabras, se puede definir la memoria del computador como el lugar donde se almacenan
los datos y las instrucciones que están siendo procesados. Si bien es cierto que se puede considerar como
memoria cualquier sistema de almacenamiento (ya sean discos duros, CD-ROM, etc.), en este capítulo
cuando se hable de memoria del computador se hará referencia a la memoria RAM, o lo que es lo mismo,
a la memoria donde se guardan los programas y los datos durante la ejecución de los mismos.
La memoria de un computador se compone de un gran número de celdas de información denominadas
bytes. Cada uno de estos bytes puede almacenar un valor representado por 8 cifras binarias (8 ceros o unos).
A cada una de estas cifras binarias se la denomina bit. Por tanto, un byte estará compuesto por 8 bits.
A cada una de estas celdas se le asigna un número de identificación, que permite distinguir a unas
de otras. A este número se le denomina dirección de memoria, y tiene como propósito el servir como
identificación o referencia, la cual podrá ser utilizada para acceder a una celda específica.
De esta forma, dada la dirección de memoria de una celda, se podrá obtener su valor actual y modificarlo.
La Figura 4.1 muestra la organización típica de la memoria de un computador.
Dirección
0 ' Contenido 1
, [
2 !
Cuando se accede a una variable almacenada en memoria el compilador necesita disponer de los si
guientes datos:
■ Número de bytes que componen la variable.
■ Dirección de memoria del byte inicial de la variable.
Por tanto, para poder utilizar una variable es necesario definir correctamente estos dos elementos. La
forma de indicar estos datos dentro de un programa en C se realiza de la siguiente forma: la dirección
de memoria correspondiente al byte inicial de una variable viene representada por el nombre de dicha
variable, de forma que el compilador será el encargado de sustituir el nombre de dicha variable por su
dirección de memoria correspondiente dentro del programa ejecutable final. En cuanto al número de bytes
que componen dicha variable, éstos viene definidos por el tipo de datos de dicha variable. Por tanto, el
compilador se encargará de reservar, para dicha variable, el número de bytes que indique su tipo de datos.
10 0 • ITES-Paraninfo
Capítulo 4 I Punteros
Como ejemplo, la Tabla 4.1 muestra el número de bytes asociados a algunos tipos de datos básicos del
lenguaje C.
Los punteros constituyen un nuevo tipo de variables, y como tal su función es almacenar información.
La principal diferencia con el resto de los tipos de datos es la naturaleza de la información que almacenan.
Mientras que el resto de tipos de datos almacenan datos relativos a los cálculos del programa, los punteros
almacenan direcciones de memoria, es decir, hacen referencia a otra zona de memoria donde se encontrarán
los verdaderos datos.
Dicho de modo más formal, los punteros son un tipo de datos cuya misión es almacenar la dirección
de memoria donde se encuentra el dato al cual hacen referencia, es decir, un puntero apunta a un dato.
Recuerde que, como se mencionó en la sección anterior, para poder hacer referencia correctamente a un
dato de memoria se debe conocer su dirección inicial de memoria y el número de celdas que ocupa, es
decir, su tamaño.
4.2 D e f in ic ió n d e p u n t e r o s
RESOLUCIÓN. Según la definición anterior, la variable ref Dato será de tipo puntero y su tipo base será
el tipo long. Por tanto, los datos referenciados por las direcciones de memoria que almacene deberían ser
ITE.S-Paraninfo • 101
Problemas resueltos de C
puntero
FE87A20
tratados como datos de tipo 1ong, o lo que es lo mismo, los datos que haya almacenados en esa dirección
de memoria deberían ser siempre de tipo 1ong.
4.3 O p e r a d o r e s d e p u n t e r o s ____________________________
10 2 • ITES-Paraninfo
Capítulo 4 / Punteros
Para poder hacer un uso correcto de los punteros es importante conocer el tamaño de los tipos de datos.
Si bien es fácil conocer el tamaño de los tipos predefinidos del lenguaje, no es tan fácil averiguar el tamaño
de tipos más complejos, como los definidos por el usuario.
Para resolver este problema el lenguaje C dispone de un operador que devuelve el tamaño que ocupa en
memoria un tipo de datos o una variable; el operador s izeof. La sintaxis de este operador es la siguiente:
si zeof(expresi ón)
La expresión puede ser un tipo de datos, una variable o una expresión compleja. Este operador devuelve
como resultado un valor de tipo entero que representa el tamaño de la variable o del tipo de datos en bytes.
ITES-Paranmfo • 10 3
Problemas resueltos de C
4.4 Punteros y m e m o r ia d in á m ic a
La memoria dinámica es una zona de memoria perteneciente al programa y que es distinta de la memoria
de datos de dicho programa. La memoria de datos es la que se encarga de almacenar todas las variables
correspondientes al programa (una parte de esta memoria de datos está dedicada a las variables locales
y la otra está dedicada a las variables globales). Por contra, la memoria dinámica delimita una zona de
memoria donde la reserva no se realiza definiendo variables, sino utilizando funciones específicas de reserva
y liberación de memoria. Esta reserva de memoria se realiza en tiempo de ejecución, no en tiempo de
compilación.
E v it e es t o s er r o r es
A continuación se describen los errores de programación típicos que aparecen cuando se utilizan punte
ros.
Utilizar punteros que no tengan valores válidos Es muy importante asegurase de la validez de un pun
tero antes de acceder a la memoria referenciada por él. Si se accede a la memoria referenciada por un
puntero que no sea válido, los efectos producidos serán del todo impredecibles.
No inicializar los punteros Siempre que se defina una variable de tipo puntero debe inicializarse a un
valor adecuado (generalmente se inicializan al valor NULL). De esta forma, se evitarán que muchos de
los accesos erróneos a memoria pasen desapercibidos.
Realizar conversiones de menor a mayor tamaño Cuando se realiza una conversión de punteros, es con
veniente asegurarse de que toda la memoria que va a ser accedida mediante este nuevo puntero está
reservada. Por este motivo, cuando se realiza una conversión entre un puntero de un tipo base y otro
puntero cuyo tipo base tiene un tamaño mayor que el original, al acceder a la memoria mediante ese
nuevo puntero se está accediendo a una memoria que (en la mayoría de los casos) no ha sido reservada
para ese fin. Por tanto, este hecho puede dar lugar a resultados impredecibles.
Como ejemplo, considere las siguientes líneas de código:
104 • ITES-Paraninfo
Capítulo 4 / Punteros
En este caso, la variable donde se encuentra el dato real es de tipo short y su dirección se almacena
en un puntero a short (refDatoShort). Sin embargo, la sentencia pri ntf muestra el contenido de la
posición de memoria consecutiva a refDatoShort mediante la expresión *( refDatoShort+1). Esta
dirección de memoria no ha sido reservada y puede estar siendo utilizada por otra variable u otra entidad.
Como consecuencia, dicho acceso puede producir resultado insospechados.
No comprobar el resultado de la función mal 1oc La función de biblioteca mal 1oc puede producir dos
resultados distintos. Devolver una dirección de memoria válida y reservarla en caso de que hubiera es
pacio libre suficiente, o bien puede devolver el valor NU L L indicando de esta forma que no se dispone de
memoria suficiente para la reserva solicitada. Hay que contar siempre con estos dos posibles resultados.
Por tanto, es necesario comprobar siempre si el valor devuelto es NU LL y, en caso afirmativo, generar un
error indicando la falta de memoria. El siguiente es un ejemplo de dicha comprobación.
refDatoLong = malloc(sizeofdong));
i f ( NULL == refDatoLong) i
printf("Error, no hay memoria\n");
return(-1);
}
/*
* Continúa el programa de forma normal
*/
La sentencia if comprueba si el resultado obtenido es igual a NU L L. Si no es así, se ejecuta el programa
de forma normal. En caso contrario se muestra por pantalla un mensaje de error indicando la falta de
memoria y se finaliza el programa.
ITES-Paraninfo • 105
Problemas resueltos de C
No olvide nunca incluir estas comprobaciones, nunca se sabe cuándo se agotará la memoria.
Utilizar la función free con punteros no obtenidos con mall-ftc La función de biblioteca free está di
señada para liberar la memoria reservada previamente con la función ma 11 oc. Sin embargo, si se le pasa
como parámetro otra dirección de memoria no reservada con mal! oc el resultado será impredecible,
siendo el más probable la corrupción de la memoria dinámica, lo que redundará en fallos constantes en
las funciones mallocyfree. El siguiente ejemplo muestra lo que no se debe hacer:
short Dato = 0x4141;
short * refDatoShort;
Re s o l u c ió n .
#include <stdio.h>
106 • /TES-Poraninfo
Capítulo 4 / Punteros
int main(void)
i
long Dato = 0;
1ong * ref Dato;
r e f Dat o = &Dato;
p r i n t f ( " L a d i r e c c i ó n de la v a r i a b l e Dato es = %p\n" , & D at o ) ;
p r i n t f í " E l v a l o r de la v a r i a b l e ref Dat o es = % p \ n " , r e f D a t o ) ;
return(O);
Como se puede observar, en dicho programa se definen dos variables, una es una variable de tipo long
cuyo nombre es Dato y la otra es un puntero que referencia a dato de tipo long cuyo nombre es refDato.
En la sentencia:
r e f Dat o = &Dato;
se asigna a la variable r e f D at o l a dirección de memoria de la variable Dato. Para ello se utiliza el operador
de dirección. Por último, se muestra por pantalla tanto la dirección de memoria de la variable Dato devuelta
por el operador de dirección como el valor de la variable refDato. Como es de esperar, ambos valores
deben coincidir.
i n t mai n ( v o i d )
1 ong Dato = 0;
long * ref Dato;
Dato = 10;
pri n t f ( "El cont eni do de la v a r i a b l e Dato es = % l d \ n " , Dato);
pri n t f ( "El cont eni do de la d i r . almacenada en ref Dat o es = % l d \ n " ,
* r e f D a t o );
* r e f D a t o = 5;
p r i n t f (" E1 nuevo cont eni do de la v a r i a b l e Dato es = % l d \ n " , Dato);
p r i n t f (" E1 nuevo cont eni do de la d i r . en ref Dat o es = % l d \ n " ,
*refDato);
r e t u r n (0);
ITES-Paraninfo • 107
Problemas resueltos de C
dato 0 dato 10
. refDato =£dato;
L______ _ dato = 10;
—
refDato ?? refDato
------- -
Del funcionamiento de este programa se deduce lo siguiente: una vez que se ha asignado la dirección
de memoria de una variable cualquiera a una variable de tipo puntero, existen dos formas de acceder al
contenido de dicha variable. La primera es a través del nombre de la variable que contiene el dato. La
segunda consiste en aplicar el operador de indirección sobre el puntero que almacena dicha dirección.
Cualquiera de las dos formas permite tanto consultar el dato como modificarlo.
c = &a ;
d = c;
*d = *c + *(&b);
Responda razonadamente a las siguientes preguntas.
1. ¿Las variables c y d se almacenan en la misma dirección de memoria?
2. ¿Las variables c y d referencian a la misma dirección de memoria?
3. ¿La sentencia *c = 4 ; modifica el contenido de la variable a, de la variable b, de las dos o de ninguna?
4. ¿Qué valor tomará a al finalizar la ejecución? ¿Y b?
R e s o l u c ió n .
1. Las variables c y d son dos variables de tipo puntero distintas, por lo tanto su dirección de memoria es
distinta.
2. La variable c contiene la dirección de a (c = &a ;) y d se iguala a c (d = c;), por lo tanto las dos
referencian a la variable a.
3. Dado que la variable c contiene la dirección de memoria de a, al aplicarle el operador de indirección
(*c) se accede al contenido de a. Por tanto, la sentencia *c = 4; modifica el contenido dea.
4. Analizando la sentencia *d = *c + *(&b) y dado que d y c referencian a la variablea, lo anterior
sería equivalente a a = a + b, de lo que se deduce que a = 0 + 5 = 5. Por tanto, el valor de a es 5.
En cuanto a b no modifica su valor.
► 4.4 Indique cuál debe ser el tipo de las variables varl,var2yvar3 para que el siguiente fragmento de progra
ma sea correcto.
10 8 • ITES-Paraninfo
Capítulo 4 / Punteros
vari = 5.5;
va r2 = &va rl;
*var3 = var2;
*var2 = vari + **var3;
RESOLUCIÓN. Para que va r i pueda contener la constante real 5.5 debe ser de tipo f 1oat. Así mismo
para que var2 pueda almacenar la dirección de va r i debe ser de tipo puntero a f l o a t ( f l o a t *). Por
ultimo, para que el contenido de la dirección referenciada por var3 (*var3) pueda contener el valor de
var2, var3 deberá ser un puntero al tipo de va r2 (f 1o at *). Por tanto, var3 deberá ser de tipo f 1o a t **.
I ► 4.5 Dado el siguiente fragmento de código, indique si existe algún error o provocará algún aviso al compilar.
En caso afirmativo indique cómo solucionarlo,
long Dato = 0x41414141;
long * refDatoLong;
s h o r t * refDatoShort;
RESOLUCIÓN. L o primero que se puede ver al estudiar el código es que en la primera línea se define una
variable de tipo 1ong (Dato) a la que se le asigna el valor 0x41414141. Este valor constituye la represen
tación hexadecimal de un número. Además, se declaran dos punteros, uno de tipo 1ong * (refDatoLong)
y otro de tipo short *(ref DatoShort).
La línea siguiente asigna la dirección de la variable Dato al puntero refDatoLong. Como Dato es de
tipo Long y el tipo base de refDatoLong también es 1ong, la sentencia es correcta y no provoca ni errores
ni avisos del compilador.
La última línea asigna el valor del puntero refDatoLong en el puntero refDatoShort. Como el tipo
base de refDatoLong es 1ong y el tipo base de refDatoShort es short dicha asignación generará un
aviso al compilar, a pesar de llevarse a cabo. Esta asignación implica que si accedemos a Dato a través de
refDatoLong obtendremos un valor de tipo long (0x41414141) mientras que si accedemos a D a t o a trav és
de refDatoShort obtendremos un valor de tipo short (0x4141, sólo la mitad del dato original).
Para evitar el aviso de compilación es necesario indicar explícitamente la conversión de punteros. Para
ello se modifica la línea en cuestión de la siguiente forma.
!* Convertir un puntero a long en un puntero a short */
refDatoShort = ( s h o r t *) refDatoLong;
Así, el compilador sabrá que nuestra intención era realizar una conversión entre dos punteros con distinto
tipo base, y que no se trata de ningún error de programación.
Re s o l u c ió n . Para calcular el resultado basta con consultar la Tabla 4.1. De esta forma, el tamaño de
un char es de 1 byte, el tamaño de un int es de 4 bytes (normalmente) y el tamaño de un fl oat es de
4 bytes. Por contra, los tipos char *, i nt * y f 1 o a t * son todos clases distintas de punteros. Dado que
ITES-Paraninfo • 109
Problemas resueltos de C
un puntero es una dirección de memoria y, normalmente, dichas direcciones ocupan 4 bytes, entonces el
tamaño de los tres tipos de punteros será de 4 bytes, independientemente de su tipo base.
!► 4.7 Indique si existe algún error en el siguiente código y cuál es. De existir, ¿qué ocurrirá al ejecutar el progra
ma? ¿Qué habría que hacer para saberlo con seguridad?
1ong Dato = 0 ;
long * refDato;
RESOLUCIÓN. El código anterior contiene un error garrafal, el intentar acceder a la zona de memoria
referenciada por un puntero sin inicializar. Dado que no conocemos qué dirección de memoria está alma
cenada en dicho puntero (refDato), al escribir o leer de esa posición desconocida de memoria podemos
estar modificando una zona inocua, una variable cualquiera del programa, incluso podemos estar leyendo
parte del código del programa. Algunas veces el acceso a dicha zona de memoria provocará un error de
ejecución, mientras que otras el programa parecerá funcionar perfectamente. Por tanto, será muy difícil e
incluso imposible detectar y corregir el error.
Para evitarlo es necesario inicializar todos los punteros con un valor no válido, usando la constante
NU L L. De esta forma, cuando se acceda al contenido referenciado por un puntero igual a N U L L el programa
generará un error de ejecución que permitirá detectar y corregir el fallo. Así, el codigo anterior quedaría
como sigue.
long Dato = 0;
long * refDato = NULL;
► 4 .8 Realice un programa que sea capaz de convertir un dato de tipo 1o n g en dos datos de tipo s h o rt (parte baj a
y parte alta) usando aritmética de punteros.
R e s o l u c ió n .
#include <stdio.h>
int main(void)
I
1ong Dato = 0x41413737;
long * refDatoLong;
short * refDatoShort;
1 1 0 * ITES-Paraninfo
Capítulo 4 / Punteros
En este ejemplo se dispone de un dato de tipo long (Dato) al cual se accede a través de un puntero
cuyo tipo base es 1ong (refDatoLong). Dado que un dato de tipo 1ong ocupa 4 bytes, lo que el programa
devuelve al utilizar el puntero refDatoLong son los 4 bytes siguientes a la dirección de memoria indicada
en dicho puntero.
Ahora convertimos dicho puntero en un puntero con el tipo base igual a short (asignando su valor a
un puntero de tipo short *, refDatoShort). Dado que el tipo short ocupa solo 2 bytes, si utilizamos el
puntero refDatoShort para acceder al dato, el programa sólo nos devolverá los 2 primeros bytes a partir
de la dirección almacenada en el puntero (parte baja del número).
Acceder a los siguientes 2 bytes es igual que acceder al dato de tipo short consecutivo al actual (au
mentar la dirección de memoria en 2 bytes que es el tamaño del tipo short). Para ello se usa la aritmética
de punteros. Así, la expresión *(refDatoShort + 1) accede al contenido del dato short consecutivo al
indicado por el puntero refDatoShort (aumenta la dirección de memoria almacenada en refDatoShort
en 2 bytes, el tamaño del tipo short) y por tanto devuelve la parte alta de dato de tipo 1ong original.
int main(void)
1
long * refDatoLong = NULL;
(*refDatoLong) = 0x41414141;
/* Libera la memoria */
free(refDatoLong);
refDatoLong = NULL;
return(O);
Lo primero es declarar un puntero del tipo base deseado, en este caso de tipo base long(refDatoLong),
el cual se inicializa con el valor NULL.
Para reservar memoria dinámica para dicho puntero, el programa en vez de definir una variable utiliza
la función mal 1oc.
El parámetro que se pasa a la función malloc debe ser el tamaño en bytes de la memoria a reservar.
Para calcularlo se utiliza el operador s izeof que permite averiguar el número de bytes que ocupa un tipo
de datos (en este caso el tipo 1ong). El resultado devuelto por mal 1oc es la dirección de memoria inicial de
la memoria reservada, esta dirección es la que se almacena en el puntero refDatoLong. La comprobación
posterior se asegura de que la función mal 1oc no devuelva como resultado el valor NULL. Si esto ocurriera
¡TES-Paraninfo • 111
Problemas resueltos de C
significaría que no se ha podido reservar memoria para ese tamaño y, por consiguiente, no se puede utilizar
el puntero.
Una vez reservada la memoria, se puede trabajar con dicho puntero igual que si se le hubiera asignado la
dirección de memoria de una variable. En este ejemplo se asigna un valor concreto a la memoria que tiene
asignada y se muestra la dirección de la memoria asignada y su valor actual.
Una vez que la memoria asignada deja de ser necesaria, hay que liberarla con la función f ree.
La función f ree espera como parámetro la dirección de memoria devuelta por la función mal 1oc y
almacenada en r e f Dato Long. Una vez ejecutada la función f ree no se debe acceder a la memoria referen-
ciada por dicho puntero. Si se hiciera, se producirían consecuencias impredecibles. Para evitarlo lo mejor es
asignar el valor N U L L al puntero justo después de liberar la memoria. De esta forma se evita que se acceda
de forma indebida a esa memoria.
*pr l = 4;
free(prl);
pr l = malloc ( s i z e o f ( i n t ));
*pr l = 5;
* ( p r l + l ) = 3;
pr l = 5;
I
Re s o l u c ió n .
1. No se puede asignar un valor en la dirección referenciada por la variable p rl mientras no le haya sido
asignada la dirección de una variable o una zona de memoria dinámica con malloc,
2. No se puede liberar un puntero con f ree si previamente no ha sido reservado con malloc.
3. Sise reserva una zona de memoria con ma 11 o c es conveniente que sea liberada con f r e e cuando ya no
se use.
4. Si usa aritmética de punteros (* ( p r 1+1)) para acceder a la zona contigua de memoria, debe asegurarse
de que dicha memoria está reservada (por ejemplo, aumentando el tamaño del ma 11 oc).
5. No se debe asignar un valor constante a un puntero, salvo el valor N U LL.
***ptr = 5.5;
La variable ptr es un puntero al tipo base fl oat ** que es otro puntero. Lo primero es reservar memo
ria para almacenar el puntero de tipo float **, usando una sentencia malloc. De esta forma podremos
acceder al nuevo puntero usando la expresión *pt r.
1 1 2« ITES-Paraninfo
Capitulo 4 / Punteros
La expresión *ptr referencia a un puntero cuyo tipo base es f 1oat *. Lo siguiente es reservar espacio
en memoria para el nuevo puntero de tipo f1oat *, usando una sentencia mal 1oc. De esta forma podremos
acceder al nuevo puntero usando la expresión **ptr.
La expresión **ptr referencia a un puntero cuyo tipo base es f 1oat. Lo siguiente es reservar espacio
en memoria para el nuevo valor de tipo fl oat, usando una sentencia mal 1oc. De esta forma podremos
acceder a dicho valor de tipo f 1oat usando la expresión ***ptr.
Por último, para asignar un valor real basta con realizar la asignación utilizando la expresión ***pt r.
!► 4 . 12 Diseñe un programa que reserve dinámicamente un buffer de 512 caracteres y que los muestre por pantalla
uno por uno, usando punteros y aritmética de punteros.
Re s o l u c ió n .
#include <stdio.h>
Pr o b le m a de Ex a m e n
► 4. 13 Escriba un programa que permita introducir un número N de datos, todos del mismo tipo, si bien éste puede
serint,charofloat, almacenándolos en memoria dinámica y mostrándolos luego por pantalla.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
{
int *bufferlnt = NULL;
char *bufferChar = NULL;
float *bufferFloat = NULL;
char tipo;
int num,i;
ITES-Paraninfo 113
Problemas resueltos de C
switch (tipo) (
case ’i ’
case ’I ’:
bufferlnt = (int *)maIIoc (num * sizeof(int));
for (i=0;i<num;i++) {
printf("Introduzca el entero num. %d: ",i);
scant ("%d",(bufferlnt+i));
)
printf("\nLista de números introducidos\n") ;
for (i=0;i<num;i++) i
printfC'El entero num. %á es %d\n",i, *(bufferlnt+i));
)
break;
case ’c ’:
case ’C ’:
bufferChar = (char *)malloc (num * sizeof(char));
for (i=0;i<num;i++) (
printf(" Introduzca el carácter num. %d: ” ,i);
scanf ("\n%c",(bufferChar+i));
}
printf("\nLista de caracteres introducidos\n");
for (i=0;i <num;i++) I
printfC'El carácter num. %ú es %c\n",i, *(bufferChar+i));
}
break;
case ’f ’ :
case ’F’:
bufferFloat = (float *)malloc (num * sizeof(float));
for (i=0;i<num;i++) (
printf("Introduzca el real num. %d: ",i);
scanf ("%f",(bufferFloat+i));
}
printf("\nLista de números introducidos\n");
for (i=0;i<num;i++) j
printfC'El real num. %ü es %f\n",i, *(bufferFloat+i));
}
break;
El programa propuesto como solución debe primeramente preguntar al usuario el tipo de datos a alma
cenar (char, int o f 1oat). Para ello se introduce la primera letra de la palabra en una variable de tipo
cha r. Luego hay que preguntar el número de datos a tratar.
Según el tipo de datos, el tratamiento es diferente pero análogo. Lo primero es crear una zona de memo
ria dinámica donde almacenar los datos, usando ma 11 oc. El tamaño será igual al del número de elementos
por el tamaño de uno de ellos (s izeof), y la dirección de memoria obtenida se almacenará en un puntero
cuyo tipo base será igual al del tipo en cuestión. Este último punto es necesario para que la aritmética de
punteros funcione correctamente.
1 1 4 * ÍTES-Paraninfo
Capítulo 4 / Punteros
Una vez reservada la memoria, se rellena utilizando un bucle de lectura del teclado. Para ello se utiliza
un f or usando un contador desde 0 hasta el máximo menos uno. Para acceder a cada elemento se utiliza
la expresión punt ero + cont ador, de esta forma la dirección del primer elemento será punt ero + Oy
la del último será punt ero + (máx-1). Esta dirección de memoria es la que se pasa como parámetro a la
función scanf para obtener el valor correspondiente.
Por último, sólo queda realizar otro bucle igual para mostrar con p rint f todos y cada uno de los valores.
Para acceder a un valor en concreto bastaría con utilizar el operador de indirección sobre la expresión que
devuelve su dirección de memoria. Así, el valor del primer elemento será *(puntero + 0) y la del último
será *(puntero + (máx- 1) ).
b = &a:
c = b;
a = *c + *b;
¿Cuál de las siguientes afirmaciones es cierta?
■ Las variables b y c se almacenan en la misma dirección de memoria.
■ La sentencia * c = 4 ; modificaría el contenido de la variable a.
■ a tomará un valor indeterminado.
■ c almacena la dirección de la variable b.
p = &a;
a = 1;
■ Pl = & p2 ; *p2
II
I
~a
■ p2 = &x; *p2 == 4;
■ P? = pl: Pl = & x ; *p2 = 4 ;
■ P2 = &pl ;: pl == & x ; **p2 = 4;
ITES-Paraninfo • /15
Problemas resueltos de C
int * a;
i nt x , y ;
x = 1;
a = &x ;
y = 2;
x = y * 2;
p r i n t f ( "%d %d \ n ” , y , * a ) ;
¿Qué imprimirá la sentencia pri ntf?
p = &nl;
q = &n 2 ;
*q = *p + *p;
¿Cuál de las siguientes afirmaciones es correcta?
■ ni = 10yn2 = 5
■ ni = 10yn2 = 10
■ La sentencia *q = *p + *p es ilegal.
■ ni = 10yn2 = 20
p = &c;
c = 2;
q = p;
4.8 ¿Cuál de las siguientes expresiones permite reservar memoria para un vector de 10 elementos de tipo float?
f1oat) mal 1oc (10);
f 1oat *) mal 1oc(:10);
f 1oat *) mal 1oc(:io * si zeof(f1oat));
f 1oat *) mal 1oc(:io * si zeof(float *))
1 1 6 « ITES-Paraninfo
/
En este capítulo...
5.1 Funciones en C
5.2 Paso de parámetros a una fundón
5.3 Recursividad
5.4 Mncros
5.5 Ambito de las variables y tipos de
almacenamiento
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
5 .1 F u n c io n e s en C
Las funciones en C son las piezas básicas que componen los programas. Por tanto, es importante saber
cómo se deben utilizar de forma correcta.
Las funciones se definen como fragmentos de código independientes con nombre y entidad propia, que
se agrupan en módulos de programación que no son más que archivos de código independientes que se
compilan por separado y que luego se enlazan entre sí para formar el programa completo.
Otra forma de ver las funciones es como entidades aisladas de cálculo (similares a una función ma
temática) a las cuales se suministra unos datos de entrada, y que después de realizar el correspondiente
procesamiento de dichos datos, generan unos determinados datos de salida como resultado.
En el caso del lenguaje C, a la hora de definir funciones es necesario tener en cuenta los siguientes
puntos:
■ El lenguaje C se basa en el uso de funciones. No se puede escribir ninguna línea de código ejecutable
(excluyendo declaraciones y definiciones) que no pertenezca a una función.
■ El lenguaje C no dispone de procedimientos, sólo permite el uso de funciones. Para emular el uso de
procedimientos se utilizan funciones que devuelvan como valor de retorno un dato de tipo v o id (lo que
es igual a no devolver ningún dato).
■ No se puede definir una función dentro de otra función. Todas las funciones deben estar en el mismo
nivel.
■ Siempre debe existir una función denominada ma in dentro del código del programa. Esta función será
la que se ejecutará cuando se arranque el programa.
secuencia de instrucciones
l a
donde Ti poRetorno y Ti poParaml son tipos de datos válidos en C, y NombreFunci ón y Paraml son
identificadores válidos. Los pares TipoParaml y Paraml deben separarse utilizando una coma entre cada
dos pares. La secuenci a de definición de vari abl es estará compuesta por líneas de código donde
se definen las variables que utiliza la función. No se puede introducir ningún otro tipo de instrucción. La
secuencia de instrucciones se compone de una colección de sentencias válidas de C.
El sentido que tiene la anterior definición es el siguiente: se ha codificado una función de nombre
NombreFunci ón la cual acepta como parámetros los valores asignados en las variables Paraml (las cuales
se conocen como parámetros formales) hasta Pa ram N, y devuelve como valor de retorno un valor de tipo
TipoRetorno. Para realizar esta función se definen las variables indicadas en secuencia de definición
de va r iabl e y se ejecutan las instrucciones definidas en secuenci a de instrucciones.
1 1 8 » ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
El número de parámetros que acepta una función puede variar desde ninguno hasta tantos como se
quiera. Sin embargo, el tipo de retorno es requisito imprescindible, si bien se puede utilizar el tipo vo id
para indicar que no se desea devolver ningún valor de retorno.
Las variables definidas con los nombres Paraml hasta ParamN son las encargadas de almacenar los
valores que se pasan a la función y que provienen de la llamada mediante la cual se ejecuta la función. Estas
variables reciben el nombre de parámetros formales, dado que se comportan como parámetros genéricos
cuyo valor sólo se conocerá en el momento de ejecutar la función.
Una función finaliza, o bien cuando termina su secuencia de sentencias, o bien cuando ejecuta una
sentencia return. Esta sentencia también es la encargada de devolver el valor de retorno de la función.
La sintaxis de la sentencia return es la siguiente:
return (expresión):
La expresión debe desembocar en un valor del mismo tipo de datos que devuelve la función.
La sentencia return puede estar situada en cualquier lugar de la secuencia de instrucciones de la fun
ción. Cuando se ejecuta dicha sentencia ocurren dos cosas:
1. La función finaliza su ejecución. El control del programa regresa a la función que haya realizado la
llamada a esa función. Si la función donde se encuentra la sentencia ret u rn es la función m a in finalizará
la ejecución del programa.
2. Se devuelve el valor de la expresión a la función que realizó la llamada.
ITES-Paraninfo • 119
Problemas resueltos de C
int y que todos los parámetros son de tipo int, lo cual puede que no se corresponda con la definición
posterior de la llamada.
Para que el compilador pueda conocer el tipo y los parámetros de las funciones antes de su uso se
utilizan los prototipos de funciones. El formato de un prototipo de función es el siguiente:
TipoRetorno NombreFunción (TipoParaml NombreParaml);
En un prototipo se indica el nombre de la función, su tipo y los parámetros que acepta, finalizando con
un punto y coma. Un prototipo lo que hace es declarar una función, es decir, indicar al compilador que
existe una función con un nombre, tipo y parámetros concretos. La definición lo que incluye es el código
de la función.
Normalmente, cuando se codifica un módulo se incluyen al principio del mismo todas los prototipos de
las funciones que contiene. De esta forma se podrá llamar a todas las funciones del módulo sin preocuparse
de dónde están situadas.
► EJEMPLO 5.1 Escriba la declaración de una función que calcule la potencia ab.
R e s o l u c ió n .
double potencia (double a, double b);
La declaración indica que la función deseada se llama potencia, que recoge dos parámetros de entrada (a
y b) de tipo do ubi e y que devuelve un valor de retorno de tipo d o ubi e, el cual, se supone, será el resultado
de calcular la potencia solicitada.
5.2 Pa s o d e p a r á m e t r o s a u n a f u n c ió n
Para que una función sea útil debe ser capaz de tomar los datos de entrada y obtener con ellos los
resultados pertinentes, que serán devueltos como datos de salida. En una función en C la única forma de
comunicar una función con el código que la ejecuta es mediante los parámetros que recibe y el valor de
retorno.
Mientras que el valor de retorno siempre será considerado como un resultado o dato de salida, los
parámetros de la función pueden ser utilizados a la vez como datos de entrada o datos de salida. Existen dos
métodos distintos de realizar el paso de parámetros a una función: por valor o por referencia.
12 0 • ITES-Paraninfo
Capítulo 5 / Fundones y programación estructurada
El punto a destacar de todo esto es que la función puede modificar el parámetro que se pasa a la función.
Esto quiere decir dos cosas:
■ Cualquier modificación que se haga en la función se mantendrá una vez que termine la función.
■ Los datos que se pasen por referencia deberán estar almacenados en memoria. No es posible, por tanto,
pasar por referencia valores constantes ni expresiones.
El paso de parámetros por referencia permite utilizar dichos parámetros como datos de salida, y (en caso
de inicializar a algún valor la dirección de memoria antes de la llamada) también como datos de entrada.
La forma de realizar el paso de parámetros por referencia es la siguiente:
■ Se declara una variable (va rl) del tipo adecuado (Ti pol) en el código llamante. Dicha variable puede
estar o no inicializada.
■ Se realiza la llamada utilizando como parámetro real una expresión que devuelva la dirección de me
moria de la variable. La forma de realizar esto es utilizar el operador de dirección con dicha variable
(&va rl).
■ En la definición de la función la variable local que actúa de parámetro formal (paraml) debe ser un
puntero al tipo de la variable original (Ti po 1 *).
■ En el código de la función para poder leer o escribir en la variable original se deberá utilizar siempre el
operador de indirección con la variable local que actúa de parámetro formal (*paraml).
Siguiendo estas reglas, es posible modificar desde la función llamada una variable situada dentro del
código llamante y así disponer de un mejor método de introducir datos en la función y de obtener sus
resultados.
► EJEMPLO 5.2 Escriba una función que incremente una variable que se le pase como parámetro.
Re s o l u c ió n .
#i nc l ude <stdio.h>
i n t mai n(voi d)
I
i n t var = 1;
printf ("valor de var antes: %d\n",var);
incrementar (&var);
printf ("valor de var después: %d\n",var);
I
Este ejemplo sigue las reglas descritas anteriormente.
■ Declara la variable va r de tipo int en la función ma in.
■ El parámetro real de la llamada es la dirección de va r (&va r).
■ El paramétro formal declarado en la función es un puntero a int (i nt *).
■ Dentro de la función se accede al dato usando el operador de indirección (*a).
5.3 R e c u r s iv id a d
Una vez que se conoce la forma de realizar llamadas a funciones una cuestión que queda en el aire es
la siguiente: ¿qué ocurre si una función realiza una llamada a ella misma? En un principio la respuesta más
intuitiva sería que dicha función se ejecuta de nuevo. Sin embargo, esto que parece tan evidente plantea una
serie de cuestiones a responder.
ITES-Paraninfo •121
Problemas resueltos de C
En primer lugar, la pregunta sería si esto realmente es así. Si se realiza una llamada a una función desde
dentro de dicha función entonces se ejecuta de nuevo dicha función. Esta repuesta es afirmativa en la mayor
parte de los lenguajes de alto nivel modernos. Sin embargo, es necesario destacar que no todos los lenguajes
soportan esta posibilidad. En el caso de C, esto sí es posible.
En segundo lugar y como pregunta crucial está el hecho de saber dónde acaba la ejecución de dicha
función. Si la función se llama a sí misma antes de terminar quiere decir que la función volverá a empezar.
Por tanto, si esto continuara indefinidamente, el programa no tendría fin (igual que un bucle infinito). La
forma de evitar esto consiste en variar los parámetros que se pasan a la función en cada llamada y utilizarlos
para saber si debe volverse a realizar la llamada o si la función debe terminar.
Dado su especial relevancia y sus características, este tipo de llamadas se denominan llamadas recur
sivas y las funciones que realizan este tipo de llamadas se denominan funciones recursivas.
La forma de entender cómo funcionan es estudiando la traza de ejecución de una función recursiva.
Todas las funciones recursivas deben distinguir entre al menos dos ramas de ejecución:
■ La rama de salida: en ella se encuentra el final de la función recursiva. Ocurre cuando los valores de los
parámetros alcanzan la condición de salida.
■ La rama recursiva: esta rama es la que realiza la llamada recursiva. Cada vez que se realice una llamada
recursiva habrá que modificar los parámetros que se le pasen de forma que se aproximen cada vez más
a la condición de salida.
Si los parámetros modificados no se aproximan hacia la condición de salida o la condición de salida
impide que la progresión de los parámetros se ajuste a ella, en esos casos las llamadas recursivas no tendrán
fin. Por tanto, es muy importante asegurarse de que para cualquier valor inicial la progresión de parámetros
que se genera pueda ajustarse en algún momento a la condición de salida.
5.4 Macros
Las macros son trozos de código extraídos de la funciones y que el preprocesador reintroduce antes de
comenzar el proceso de compilación. Estos trozos de código pueden incorporar parámetros para adaptarlos
a cada situación.
La sintaxis de la definición de una macro es la siguiente:
#define NombreMacro(paraml, ..., paramN) Codigo <FindeLinea>
donde NombreMacro constituye el nombre de la macro y puede ser cualquier identificador válido en C.
paraml,..., paramN son los parámetros de la macro y también son identificadores válidos en C. Codi go es
el conjunto de líneas de código que componen la macro, las cuales no tienen por qué formar necesariamente
un bloque de sentencias completo. <FindeLinea> es el carácter oculto que marca los fines de línea en
un archivo de texto e indica el final de la definición de la macro.
Los identificadores paraml, ..., paramN pueden aparecer dentro de las líneas que forman Codigo. La
definición de la macro finaliza en cuanto se encuentra el carácter <FindeLinea>. Por tanto, todo el texto
que componga Codigo debe estar escrito en una sola línea o bien evitar que el preprocesador detecte los
caracteres de <FindeLinea> que aparezcan en Codigo. Esto se consigue anteponiendo a cada uno de
ellos el carácter \.
Las macros utilizan la directiva de preprocesador para ser definidas. Esto quiere decir que las macros
son componentes que maneja el preprocesador del lenguaje C, no el compilador. El compilador no entiende
de macros. Estas deben haber sido procesadas con anterioridad por el preprocesador.
Cuando el preprocesador detecta la definición de una macro la almacena y sustituye cada aparición de
la macro por su definición:
La sintaxis de llamada a una macro es la siguiente:
NorabreMacroíparamReall, parara Re al 2)
NombreMacro debe coincidir con el identificador de la definición de la macro, parara Re all,...,param-
Rea 1 N son expresiones válidas en C.
En el momento en el que el preprocesador detecta una llamada a una macro, realiza el proceso de
instanciación de dicha macro. Primero sustituye en el código de la macro todas las ocurrencias de los
12 2 • ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
identificadores paraml, ...,paramN por las expresiones pa ramReal 1 , paramRealN correspondientes, sin
evaluarlas, por tanto tan sólo se realiza una sustitución textual.
Una vez terminada la sustitución de los parámetros se procede a la inserción de la macro en el código
llamante. Para ello, se sustituye la llamada a dicha macro por el código asociado y con sus parámetros
sustituidos correctamente.
Todo este proceso lo realiza el preprocesador. Por tanto, una vez terminada toda la fase de sustituciones
e inserciones será cuando se realice la compilación del código.
► EJEMPLO 5.3 Realice un programa que devuelva el máximo de dos números utilizando una macro.
Re s o l u c ió n .
#include <stdio.h>
/*
* Macro máximo
*/
#define maximoCa, b ) (( a > b ) ? a : b )
int main(void)
1
int x, y;
int mayor;
/* 1iamada a la macro */
mayor = maximo(x,y);
return (0);
i
El programa define una macro para calcular el máximo de dos números. La macro utiliza un operador
condicional para elegir el mayor de los dos números. Lo único distinto es cómo se realiza la inserción de la
macro. La llamada es semejante a la de una función.
/* //amada a la macro */
mayor = maximo(x,y);
Cuando el preprocesador encuentra la línea anterior, procede a sustituirla por la definición de la macro.
El resultado que obtiene el preprocesador es el siguiente:
mayor = ( ( - x > y ) ? x : y ) ;
Observe que se ha sustituido a por x y b por y. Una vez realizada la sustitución se procederá a la
compilación del programa.
5.5 A M B IT O DE LAS V A R IA B L E S Y T IP O S DE
A L M A C E N A M IE N T O _____________________________________
El lenguaje C define dos tipos de almacenamiento'.
■ Automático: se corresponde a las variables locales que se definen dentro de una función.
■ Estático: variables locales o globales cuyo valor persiste entre las distintas inicializaciones de un objeto.
Si son variables locales se distinguen de las automáticas por utilizar la palabra reservada stati c.
ITES-Paraninfo • 123
Problemas resueltos de C
Las variables automáticas (las definidas dentro de una función sin utilizar la partícula stati c) se crean
cuando se llama a la función y desaparecen cuando finaliza su ejecución, por tanto no conservan el valor de
una llamada a otra. Las variables automáticas pueden ser inicializadas con un valor constante cuando son
declaradas. Esta inicialización tendrá lugar cada vez que se llame a la función que contenga dicha variable.
Las variables estáticas proporcionan almacenamiento permanente para la variable, de forma que no
se destruye cuando finaliza la ejecución de la función en la que se define. Además, su valor perdura de
una ejecución a otra. Las variables estáticas pueden ser inicializadas con un valor constante cuando son
declaradas, pero esa inicialización sólo tiene lugar la primera vez que se llama al objeto y se usa la variable.
E v it e es t o s e r r o r es
A continuación se describen los principales errores que se cometen cuando se utilizan los conceptos
vistos en este Capítulo.
Utilizar punto y coma en la definición de una función Cuando se define una función, su cabecera no
termina con punto y coma. El punto y coma se utiliza en las declaraciones, es decir, en los prototipos de
las funciones.
12 4 • ITES-Paranirtfo
Capítulo 5 / Fundones y programadón estructurada
Llamar a una función con un número diferente de parámetros Cuando se pasan a una función, más o
menos parámetros de los que espera, el compilador sólo avisa, pero no genera un error de compilación.
Hay que asegurarse de que se pasa el número exacto de parámetros.
Devolver un valor que no coincide con el tipo de la función Cuando devuelva un valor utilizando ret u rn
asegúrese de que el resultado de la expresión que sitúa dentro de return coincide con el tipo de la
función. De nuevo, el compilador avisa, pero no genera un error de compilación.
No utilizar return en una función que devuelve un valor Cuando una función devuelve un valor, hay
que utilizar return para devolverlo.
Pasar un parámetro real con distinto tipo al parámetro formal Cuando llame a una función, el tipo
de los parámetros reales debe coincidir con el tipo de los correspondientes parámetros formales. Si no
coinciden, el compilador avisará. Si ejecuta el programa, puede que obtenga resultados erróneos.
Olvidar los paréntesis en una función que no acepta parámetros Aunque una función no acepte
parámetros, cuando se realiza la llamada a la misma deben utilizarse paréntesis, sin nada entre ellos.
Si no se colocan los paréntesis, se obtiene un error de compilación.
Declarar un prototipo que no coincide con la definición de la función Cuando defina una función,
asegúrese de que tanto la definición como su declaración tienen el mismo prototipo, es decir, devuelven
el mismo tipo y aceptan el mismo número y tipo de parámetros.
Definir una función recursiva que no tiene rama de salida Toda función recursiva debe tener una rama
de salida que permita poner fin a la recursividad. En caso contrario, el programa no acabará nunca.
No finalizar con punto y coma las definiciones de macros Cuando define una macro utilizando la direc
tiva define, recuerde que la macro finaliza con el salto de línea, no con punto y coma.
Definir macros sin paréntesis Aunque no es obligatorio incluir el código de una macro entre paréntesis,
se recomienda que lo haga para que la sustitución que hace el preprocesador se haga sin problemas.
Definir dos variables globales con el mismo nombre Cuando desarrolle una aplicación compuesta por
varios módulos, no defina variables globales con el mismo nombre en distintos módulos. Aunque cada
uno de ellos compilará por separado sin problemas, la creación del ejecutable final fallará debido a la
definición de dos variables con el mismo nombre.
Definir una variable global con el mismo nombre que una función Todas las variables y funciones en
un programa deben utilizar identificadores distintos.
Definir dos funciones con el mismo nombre No se pueden definir funciones en módulos distintos que
tengan el mismo nombre, a no ser que se definan como privadas a cada uno de ellos utilizando stati c.
Asumir un valor por defecto para las variables automáticas Las variables automáticas que se definen
de forma local se crean en la llamada a la función y tienen un valor indefinido. No puede, por tanto,
hacer ninguna suposición sobre el valor que pueden tener por defecto.
IT E S - P a r o n in f o • 125
Problemas resueltos de C
int aux;
/*
* Secuencia de instrucciones,
*/
aux = numero * 2;
printfC El doble de %d es %d.\n", numero, aux);
En la función anterior se distinguen claramente las distintas partes de la definición de una función.
■ Cabecera: en ella se distingue el nombre de la función (dupl icar), el tipo de retorno (voi d) y el
parámetro incorporado (número).
■ Declaración de variables: aquí se declaran las variables propias de la función (aux).
■ Secuencia de instrucciones: donde se ejecuta el proceso asociado a la función (Ej. printf).
T ► 5.2 Modifique la función del problema anterior para que devuelva como retorno el doble del número que recibe
como parámetro
Re s o l u c ió n .
1*
* Función que devuelve el resultado
* de multiplicar un número por 2
*/
int duplicar (int numero)
(
/*
* Secuencia de declaración de variables
*/
int aux;
/*
* Secuencia de instrucciones
*/
aux = numero * 2;
12 6 • ITES-Paraninfo
Capítulo 5 / Fundones y programación estructurada
Los cambios introducidos que hay que resaltar son los siguientes:
■ Se ha modificado el tipo de retorno en la cabecera; en lugar de voi d se ha utilizado el tipo int dado
que el resultado a devolver es un número entero.
■ Se ha incluido al final de la secuencia de instrucciones la sentencia' retu rn, la cual finaliza la ejecución
de la función. Así mismo, dicha sentencia incluye una expresión¡ que devuelve el doble del número
introducido. Dicha expresión es de tipo i nt, al igual que el tipo de retomo de la función.
► 5.3 Escriba una función que calcule el área de un círculo dado su radio.
R e s o l u c ió n .
float areaCirculo (float radio)
I
return ((radio * radio) * 3.1416);
)
i n t main (voi d)
{
float result;
result = areaCirculo(5.0);
I ► 5.4 Escriba una función que, dado un número, imprima una por una sus cifras.
R e s o l u c ió n .
void cifras (int num)
{
whi1e (num 1=0) {
printf ("cifra: %d\n",num % 10);
num = num / 10;
1
I
int main(void)
{
ci fras (1999) ;
►5. 5 Escriba una función que reciba tres números y devuelva la media de los tres.
R e s o l u c ió n .
int media (int a, int b, int c)
i
return ((a+b+c)/3);
1
int main(void)
{
int result;
ITES-Paraninfo • /2 7
Problemas resueltos de C
I ► 5.6 Escriba una función que devuelva el resultado de elevar un número real a una determinada potencia.
R e s o l u c ió n .
double potencia(double val, unsigned pow)
1
double ret_val = 1.0;
unsigned i;
return(ret_val);
int main(void )
1
double result;
I ► 5.7 Realice un programa que calcule el máximo (por medio de una función) dado un número y su cuadrado.
R e s o l u c ió n .
#include <stdio.h>
/*
* Función máximo
■* D e v u e l v e el máximo de a y b.
*/
float maximo(float a, float b)
1
float max;
if (a > b) 1
max = a;
I else (
max = b;
1
return(max); /* devuelve el valor máximo *!
int main(void)
f
float x , y ;
float mayor;
/* l l a m a d a a la f u n c i ó n */
mayor = maximoí x, (x*x) );
128 • ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
return (0);
Este programa incorpora la función maxi mo, la cual devuelve como retorno el máximo de entre dos
números. Así mismo se incluye una función ma in que forma el cuerpo del programa y es la que se ejecuta
en primer lugar.
En la función ma in se encuentra la llamada a la función máximo, como se puede ver la llamada en sí
se considera como una expresión, dado que se está asignando a una variable. Esto es así porque una vez
finalizada su ejecución la llamada se evalúa como si fuera una expresión cuyo resultado es el valor retomado
por la función (el cual será asignado a la variable).
Por otra parte, se ve que los parámetros reales pueden estar compuestos por una expresión cualquiera.
Mientras que el primer parámetro es un variable (x), el segundo parámetro es una expresión compleja (x *
x). Lo único importante es que el resultado final sea del mismo tipo que el parámetro formal reflejado en la
cabecera de la función (f 1oat).
► 5.8 Indique si es correcto el siguiente código y, si no lo es, indique qué modificaciones deberían realizarse.
#include <stdio.h>
int main(void)
I
float x, y;
float mayor;
/* ñamada a la función */
mayor = máximo! x, (x*x) );
return (0);
I
/*
* Función máximo
* Devuelve el máximo de a y b.
*/
float maximoífloat a, float b)
(
float max;
if (a > b) (
max = a ;
} else {
max = b;
)
return(max); / * devuelve el valor máximo */
ITES-Paraninfo • 1 2 9
Problemas resueltos de C
R E S O LU C IÓ N . El código en cuestión tiene el siguiente error. La función mai n que llama a la función
maxi mo está definida antes que la función a la cual llama. Así, el compilador al llegar a la llamada a la
función máximo no conocerá cuál es su definición y supondrá que tanto su valor de retorno como sus
parámetros son del tipo int, produciéndose un malentendido que genera un problema al compilar.
Hay dos formas de evitarlo. Una es reordenando la definición de las funciones, lo cual no es aconse
jable cuando haya un número importante de funciones. La otra es añadiendo al principio del programa un
prototipo de las funciones implementadas. Esta última solución es la adecuada y soluciona el problema sin
coartar la libertad a la hora de ordenar las funciones.
El código final sería algo así:
#include <stdio.h>
/* P r o t o t i p o de la fu nc i ón máximo *!
float maximoífloat a, float b);
int main(void)
I
/* .......... */
/*
* Función máximo
* Devuelve el máximo de a y b.
*/
float maximo(float a, float b)
í
/* .......... */
I ► 5.9 Escriba un programa que calcule el máximo y el mínimo de dos números introducidos por pantalla, tanto el
máximo como el mínimo deben realizarse en funciones independientes.
R e s o l u c ió n .
#include <stdio.h>
130 • IT E S - P a r o n in f o
Capítulo 5 / Fundones y programadón estructurada
if (x < y) (
return (y);
I else I
return (x);
I ► 5. 10 Indique razonadamente cuál será la salida por pantalla del siguiente programa.
#include <stdio.h>
int main(void)
{
int x = 2;
int y = 3;
funcion(x, y);
return(O);
1
return;
ITES-Paraninfo •
131
Problemas resueltos de C
linclude <stdio.h>
v o i d f u n c i ó n ( i n t a, int b ) ;
int main( v o i d )
{ '
int x = 2;
int y = 3;
f u n c i ó n (x, y) ; -------------
a tom a el valor de x (a = 2)
v o i d f u n c i ó n (int a, int b>
b tom a el valor de y (b = 3)
{
a = 0;
b = 0;
La clave de este comportamiento reside en que los parámetros reales se copian en los parámetros for
males y la función trabaja con éstos. Por tanto, cualquier modificación del parámetro formal no afectará al
parámetro real que se pasa a la función.
► 5.11 Indique razonadamente cuál será la salida por pantalla del siguiente programa.
#i n c l u d e <stdio.h>
i n t mai n(voi d)
i
i n t x = 5;
printf("Antes de la función: %d\n", x);
incrementar(&x);
13 2 » ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
#include <stdio.h>
v o i d i n c r e m e n t a r (int * a ) ;
int main ( v o i d )
{
int x = 5;
Se copia Sx en a
a. i &X
X 5
!► 5.12 Escriba una función que intercambie el valor de dos variables que se pasen como parámetros.
R e s o l u c ió n .
void intercambiar (int *x, int *y)
i
int temp;
temp = *x ;
*x = *y;
*y = temp;
ITES-Paraninfo • 133
Problemas resueltos de C
int main(void)
I
int a=l, b=2 ;
intercambiar(&a,&b);
► S. 13 Indique razonadamente la ejecución del siguiente programa que calcula el factorial de un número.
#i nc l ude <stdio.h>
if (1 >= dato) !
/* por esta rama finaliza la función recursiva */
resultado = 1;
! else I
/* aquí se ejecuta la llamada recursiva */
/* modificando los parámetros*/
resultado = dato * factorial(dato - 1);
)
return (resultado);
)
int main(void)
I
int valor;
int fact;
fact = factorial(valor);
return (0);
dato == 3 ==>> (1 >= dato) == falso ==>> ejecutar bloque del else
-> } else 1
-> /* aquí se ejecuta la llamada recursiva modificando los parámetros*/
134 • I T E S - P o r a n in f o
Capítulo 5 / Fundones y programadón estructurada
dato == 2 ==>> (1 >= dato) == falso ==>> ejecutar bloque del else
-> 1 else {
-> /* aquí se ejecuta la llamada recursiva modificando los parámetros*/
-> resultado = dato * factorial(dato - 1);
dato == 1, resultado == 1;
-> return(resultado);
ITES-Paraninfo • 135
Problemas resueltos de C
Una vez finalizada la sentencia if se ejecuta la sentencia retu rn y se termina la llamada. El valor que
devuelve esta llamada es el que se indicó en la sentencia re t u rn. En este caso el valor de retorno es el valor
de la variable resultado. Por tanto, el retomo de llamada f actori al (1) será igual a 1.
A continuación se reanuda la ejecución de la llamada anterior, en este caso dicha llamada era facto
rial (2).
Se reanuda la ejecución de la llamada factorial(2)
dato == 2, factorial(1) == 1
dato == 2, resultado == 2
-> return(resulfado);
dato == 3, factorial(2) == 2
dato == 3, resultado == 6
-> return(resultado);
► 5. 14 ¿Cuál es la salida de estas dos funciones si se ejecutan con el parámetro 13 (f uncí ( 13 ) y f unc2(13))?
¿Por qué el resultado es distinto?
void funcl ( i n t num )
printf("%d, ",num%2);
if ( num > 0 )
funcl(num/2);
13 6 • ITES-Paraninfo
Capítulo 5 / Fundones y programaáón estructurada
► 5.15 Escriba una macro que devuelva el valor entero más cercano a un número real.
Resolución.
#include <stdio.h>
Resolución.
#define intercambiará,y) \
do { \
int temp; \
temp = (x); \
(x) = (y); \
(y) = temp; \
1 while (0)
int main(void)
I
int a=l, b=2;
ITES-Paraninfo • 137
Problemas resueltos de C
/* función auxiliar */
void funAux (void)
I
int varLocal = 0;
static int varEstatica = 0;
int main(void)
int varLocal = 0;
static int varEstatica = 0;
int i ;
13 8 • ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
■ Existe una sola variable denominada va rGl obal, esta función es global al programa por lo que puede
acceder desde cualquier función y también es estática. Así, su valor se inicializa a 0 en el momento
de arrancar el programa, y después se incrementa en 1 dos veces, una en la función mai n y otra en la
función f unAux, antes de imprimirse.
En definitiva, se comprueba que el comportamiento de las variables es bien distinto según su ámbito y
su tipo de almacenamiento.
5.18 Realice un programa que obtenga por teclado las coordenadas de dos vectores y devuelva su producto
escalar y su producto vectorial. Deberán emplearse funciones para realizar tanto el producto escalar como
el producto vectorial.
R e s o l u c ió n .
#include <stdio.h>
int xl,x2,yl,y2,zl,z2;
int x,y,z ;
fTES-Paraninfo • 1 3 9
Problemas resueltos de C
int *x,
* int *y, int *z)
*x = (yl*z2) - (y2*zl);
*y = (zl*x2) - (z2*xl);
*z = (xl*y2) - (x2*y1) ;
En este programa se emplean dos funciones: escalar y vectorial. Para no tener que preocuparse del
orden de dichas funciones se han colocado al principio del programa los correspondientes prototipos. Como
la función es ca 1a r óolo devuelve un valor entero se ha optado por utilizar el retomo de la función para este
fin. En cambio, en la función vectori al se devuelven tres valores enteros (las coordenadas del vector). Se
ha optado por utilizar el paso de parámetros por referencia para retomar el valor.
Indicar que si bien los nombres de los parámetros de las funciones son iguales en las dos funciones,
eso no tiene transcendencia, ya que dichos parámetros son variables locales a dichas funciones y su ámbito
no abarca mas allá de dicha función. De hecho, las variables locales usadas en la función principal tienen
también el mismo nombre.
Alguien puede optar por utilizar variables globales en la función vectorial para retornar el resultado.
Esta táctica es desaconsejable, ya que el uso de variables globales, en general, complica la lectura del código
y su comprensión y posterior mantenimiento.
Pro blem a de Ex a m e n
► 5. 19 Escriba un programa que, dadas dos circunferencias mediante su centro y su radio, calcule cuántos puntos
en común tienen dichas circunferencias (cero, uno, dos o infinitos).
R e s o l u c ió n .
#include <stdio.h>
#include <math.h>
/*
* Esta función calcula la distancia entre dos puntos
*/
double distancia (double xl, double yl,
double x 2 , double y2 )
double vx,vy;
vx = xl-x2;
vy = yl-y2;
return sqrt((vx*vx) + (vy*vy));
dist = distancia(xl , y l , x2 , y 2 ) ;
14 0 • ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
sumaRadios = rl+r2;
if (0 == dist) {
if ( rl == rZ ) {
/* Infinitos puntos */
result = -1;
1 else I
/* Cero puntos */
result = 0;
1
1 else if (dist < sumaRadios) (
/* Dospuntos */
result = 2;
1 else if (dist == sumaRadios) I
/* Un punto */
result = 1;
) else { /* (dist > sumaRadios) */
I* Cero puntos *!
result = 0;
/* Retornando el resultado */
return (result);
/*
* Función principal
*/
int main (void)
I
double x l , y l , rl;
double x2, y2, r2;
int estado;
!* Mostrando el resultado */
ITES-Paraninfo • 141
Problemas resueltos de C
i f (estado == -1) {
pri ntf ("Las dos circunferencias son igua es\n
i else (
pri ntf ("El número de puntos de corte es: íd\n estado);
Para realizar esta programa, se han empleado dos funciones. Una de ellas, la función d i s t a n c i a , calcula
la distancia entre dos puntos del plano. Dicha función se empleará para medir la distancia entre los centros
de las circunferencias. Esta función emplea la función sq rt de la bilioteca matemática para calcular la raíz
cuadrada.
La otra función, denominada puntoCorte, es la encargada de averiguar el número de puntos de corte
entre las dos circunferencias y devolverlo como valor de retorno. Para ello, primero averigua la distancia
entre los dos centros (utilizando la función distancia)yla suma de los radios.
Si la distancia es cero y los radios son iguales es que se trata de la misma circunferencia y todos sus
puntos coinciden (para representar un número infinito de puntos de corte se utiliza el valor -1). Si la distancia
es cero y los radios no son iguales entonces no existe ningún punto de corte.
En otro caso, si la distancia es menor que la suma de los radios, entonces existen dos puntos de corte. Si
la distancia es igual a la suma de los radios, entoces sólo existe un punto de corte. Por último, si la distancia
es mayor que la suma de los radios, entonces no existe ningún punto de corte.
La función principal es la que se encarga de solicitar al usuario los datos de las dos circunferencias, de
llamar a la función puntoCorte y de mostrar por pantalla el resultado del cálculo.
La función recibe los números x, y y z y los deja ordenados en los parámetros ni, n2 y n3.
5.7 Escriba un programa que lea dos fechas (día, mes y año) y diga el número de días que hay entre ellas.
5.8 Escriba un programa que calcule las raíces reales de la ecuación de segundo grado:
a x2 + bx + c = 0
14 2 • ITES-Paraninfo
Capítulo 5 / Fundones y programadón estructurada
5.9 Escriba una función que reciba como argumento un número entero y devuelva el número de dígitos de ese
número.
5.10 Escriba una función que reciba como argumento un número entero y devuelva el número con sus dígitos
invertidos. Para el número 1234, la función debe devolver 4321.
5.11 Escriba una función que imprima los N primeros números primos.
5.12 Escriba una función que reciba dos parámetros, un número de filas y un número de columnas y dibuje:
* * * * *
* * * * *
* * * * *
5.13 Escriba un programa que calcule los pagos mensuales de una hipoteca y el total a pagar. El programa debe
solicitar el capital, el interés anual y el número de años, y debe escribir la cuota a pagar mensualmente. Para
calcular la cuota se utiliza la siguiente fórmula:
C ■R
Donde C es el capital del préstamo, R la tasa de interés mensual (en tanto por uno) y N el número de pagos
a realizar.
5. 14 Escriba un programa que pida al usuario la cuota que desea pagar al mes de un préstamo hipotecario, el
interés y el número de años. El programa debe indicar el capital que le pueden prestar.
5. 15 Escriba una versión iterativa de la función f a c t o r i a l .
5. 16 Escriba un programa que muestre los números de la serie de Fibonacci. La serie de Fibonnaci:
0, 1, 2, 3, 5, 8, 1 3 ___
Se define de la siguiente forma:
fib(0) = 0
fib(l) = 1
fib(n) = fib(n — 1) + fib(n —2)
Defina una función recursiva denominada Fibonacci (n ) que devuelva el n-ésimo número de la serie.
Modifique el ejercicio anterior y defina la función Fi bonacci ( n ) de forma iterativa.
5. 17 La función de biblioteca ra nd definida en std 1 i b . h devuelve un número pseudoaleatorio comprendido en
tre 0 y RAND.MAX, definida también el archivo stdl i b .h. Defina una función que permita obtener números
aleatorios en el intervalo [a, b],
5. 18 Escriba un programa que juegue al juego del Rojo-amarillo-verde. El programa genera tres dígitos aleatorios
distintos entre 0 y 9. A estos dígitos se les asignan las posiciones 1, 2 y 3. El objetivo del juego es adivinar
los dígitos así como sus posiciones correctas en el menor número de intentos posibles. Para cada intento,
el jugador proporciona tres dígitos para las posiciones 1, 2 y 3. El programa responde con una pista que
consta de rojo, amarillo y verde. Si un dígito está en la posición correcta, la respuesta es verde. Si el dígito
adivinado está en una posición incorrecta, la respuesta es amarillo. Si el dígito para una posición dada no
coincide con ninguno de los tres dígitos, la respuesta es rojo. A continuación se muestra un ejemplo de
respuestas para los dígitos 6, 5 y 8 en las posiciones 1, 2 y 3 respectivamente:
In tento Pi sta
1 2 5 rojo rojo amarillo
8 5 3 amari 1lo verde rojo
8 5 6 amari 1lo verde ama ri 11 o
6 5 8 verde verde verde
ITES-Paraninfo • 143
En este capítulo...
6 .1 Definición de arrays
6.2 Uso de un array
6.3 Relación entre punteros y arrays
6.4 Paso de arrays a funciones
6.5 Arrays dinámicos
6.6 Arrays multidimensionales
6.7 Cadenas de caracteres
Evito estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
A rrays y strings
Problemas resueltos de C
El objetivo de este capítulo es presentar el concepto de array. Un array representa una estructura de datos,
que permite almacenar una secuencia finita de valores del mismo tipo a los que se les da un nombre común y
a los que se accede a través de un índice (su posición en la secuencia). Su origen se encuentra en el concepto
matemático de vector.
Además, se presentan los strings, imprescindibles en cualquier lenguaje de programación. En el lenguaje
C un string o cadena de caracteres (en el texto se utilizan los dos términos indistintamente) puede definirse
como un array de caracteres, por lo que arrays y strings se encuentran muy relacionados entre sí.
Los temas que se tratan en este capítulo son:
■ Definición y uso de un array.
■ Arrays de longitud variable.
■ Arrays multidimensionales.
■ Definición y uso de strings.
6.1 D e f in ic ió n d e a r r a y s
Por ejemplo, si se desea almacenar los 20 primeros números primos, ¿habría que definir 20 variables
de tipo int para almacenarlos? Este no parece un enfoque muy razonable. En su lugar, se puede definir el
siguiente array:
int primeros_primos[20];
Un array o vector representa una serie de valores del mismo tipo dispuestos en memoria de forma
consecutiva y a los que se accede a través de su índice (posición). Como se puede observar, la definición de
un array consta de tres elementos principales:
■ int: es el tipo de datos de cada elemento del array.
■ pri meros_pri mos: es el nombre de la variable de tipo array de enteros.
■ [20].: expresión numérica entera que indica el número máximo de elementos que podrá almacenar el
array.
Si se conocen los valores que toman los componentes del array en el momento de su definición, es
posible realizar estos dos pasos simultáneamente: definir y asignar valores al array. A continuación se
presentan tres ejemplos:
int a rrayl [] = II, 2, 3, 4, 5);
int array2 [5] = {1, 2, 3, 4, 5);
int array3 [8] = {1, 2, 3, 4, 51;
En las definiciones anteriores se puede apreciar que si no se indica el número de elementos (el caso de
la variable a r ray 1) se toma como tal el número de valores asignados. Es posible inicializar parcialmente el
array (como en el caso de la variable a r ray3), pero siempre se asignan los valores a los primeros elementos
del array, desde la posición cero en adelante. Por tanto, no se puede dar valor a un elemento si no se ha dado
valor a los anteriores.
6.2 U S O DE U N ARRAY
Por una parte, se observa que una variable de tipo array permite empaquetar una secuencia de varia
bles del mismo tipo. Por otra parte, lo que se pretende con una variable es poder almacenar un valor y
posteriormente recuperar el valor almacenado.
Operar con todo el array sólo es posible en la inicialización del mismo. En C no es posible operar con
todo el array para:
■ Asignar una tupia de valores directamente.
■ Asignar un array a otro array.
■ Comparar arrays directamente.
146 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
jí?/. N o acced er a
[- 1 ] este elem en to
ni a lo s anteriores
[0 ]
[1 ]
...
[1 9 ]
N o acced er a
[2 0 ] ■4 este elem en to,
n i a los sigu ien tes
► EJEMPLO 6.1 Escriba un programa que lea las temperaturas medias de los doce meses del año y calcule la
temperatura media del año.
Re s o l u c ió n .
#include <stdio.h>
#define NUMER0_MESES 12
■int main(void)
1
/* definición de variables */
float temperaturaMeses[NUMERO_MESES];
int j ;
float temperaturaMedia = 0;
ITES-Paraninfo • ¡ 4 7
Problemas resueltos de C
return (0);
1
En este ejemplo se muestra que la única forma que existe para leer un array es hacerlo elemento a
elemento. De igual forma, si se desea imprimir un array sólo se puede hacer elemento a elemento. Para leer
la temperatura de un mes se utiliza la expresión:
scanf("%f", &temperaturaMeses[j])
En esta expresión temperaturatieses [ j ] se comporta a todos los efectos como una variable de tipo
f 1o at, lo que implica que para almacenar el valor leído en dicha posición del array debe utilizar el operador
de dirección (&) aplicado a temperaturaMeses [ j ].
6.3 R e l a c ió n e n t r e p u n t e r o s y a r r a y s
Para entender mejor la forma de manejar arrays dinámicos (donde el número máximo de elementos se
fija durante la ejecución del programa y no durante la compilación) y para entender mejor el paso de arrays
como parámetros de funciones, es necesario conocer la relación entre punteros y arrays. En esta sección se
analiza dicha relación.
Como se vio en el Capítulo 4, cuando se define una variable el compilador realiza dos tareas: asig
na en memoria una dirección para la variable y reserva el espacio necesario para almacenar los valores
correspondientes al tipo de la variable.
Cuando se define un array de la siguiente forma:
TipoDato V[n];
El nombre de la variable correspondiente al array (V) representa la dirección de comienzo en memoria
del array, es decir, la dirección del primer elemento del array. El compilador reservará en memoria espacio
para n elementos. El tamaño en bytes que necesita reservar el compilador viene dado por la siguiente
expresión:
n * s i z e o f (TipoDato)
En la Figura 6.2 se ilustra el proceso de definición de un array. Como se puede comprobar en dicha
figura, el nombre del array representa la dirección de comienzo del array.
Como el nombre de la variable de tipo array representa la dirección del primer elemento del array, se
cumple, por tanto, la siguiente equivalencia:
V == &(V[0])
Por esta razón, para los arrays se puede usar la notación asociada a los punteros. De esta forma y
aplicando la aritmética de punteros vista en el Capítulo 4, se puede generalizar la expresión anterior de tal
forma que:
■ Las expresiones (array + i)y&array[i] representan la direcci ón de la componente i del array.
14 8 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
i n t V [5] ;
V [0 ]
V [1 ]
V [2 ]
V [3]
V [4]
i n t V [5] ;
II
i n t *p; p = NULL;
<
►f V [ 0 ] ; v ► V[0] V ► V[0] ¡
v ►j V [0 ] I«-- .
V[1] '
V +2 == p + 2 ► __V [2 ] !
y [3 r 1
: v[4] 1
p ' &V[0] i
■ Una zona de memoria dinámica reservada con anterioridad (usando, por ejemplo, la función de biblio
teca mal loe).
Por tanto, un puntero puede usarse:
■ Para recorrer los elementos del array, en lugar de a través de un índice. Por ejemplo, el siguiente bucle
recorre un array usando un índice:
in t array[10];
f o r (i =0; a r r a y [ i ] != -1; i++) (
■ Para construir arrays dinámicos, es decir, arrays cuyo tamaño se fija en tiempo de ejecución, no de
compilación, como los vistos hasta este momento. En la Sección 6.5 se trata este tipo de arrays.
ITES-Paraninfo • 1 4 9
Problemas resueltos de C
6.4 Pa s o d e a r r a y s a f u n c io n e s
Un array se puede pasar como parámetro a una función. Considere la siguiente definición:
int numeros[10];
Si se quiere pasar este array a una función, ésta debe declararse de la siguiente forma:
Ti po Funci on (int v[ ]);
Observe que en el parámetro formal de la función no se indica el tamaño del array. Esto quiere decir que
a la función se le puede pasar un array de cualquier tamaño siempre que sus elementos sean de tipo int.
Para pasar el array números a la función anterior, la llamada debe realizarse como sigue:
Funcion(numeros) ;
El parámetro real que se pasa a la función coincide con el nombre del array sin corchetes.
Recuerde que un array representa la dirección de comienzo del mismo. Por ello, cuando se pasa un array
a un función, lo que realmente se está pasando es la dirección del primer elemento del array.
El hecho de que lo que se pase a una función sea la dirección del primer elemento del mismo, hace
que los arrays en C se pasen por referencia, sin necesidad de utilizar el operador de dirección (&). Es decir,
dentro de la función se pueden modificar los valores del array.
Debido a que la función espera la dirección del primer elemento, ésta también puede declararse de la
siguiente forma:
T ipo Función (int *v);
siendo esta definición equivalente a la inicialmente propuesta.
Como se ha comentado anteriormente, en la declaración de una función que incluye arrays como
parámetros formales no se indica el tamaño del array. Por tanto, la función no recibe información algu
na relativa al tamaño del array. Para demostrarlo, se propone el siguiente ejemplo.
I ► EJEMPLO 6.2 Escriba un programa que imprima el número de bytes que ocupa un array pasado por parámetro a
una función.
Re s o l u c ió n .
#include <stdio.h>
int b[5];
150 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
Normalmente, las funciones que incluyen arrays como parámetros formales utilizan un segundo pará
metro que se usa en la llamada para indicar el número de elementos que tiene el array que se pasa a la
función:
Tipo Funcion(int p[], int elementos);
► EJEMPLO 6.3 Escriba una función que permita leer los elementos de un array y otra que los imprima.
R e s o l u c ió n .
.^include <stdio.h>
/*
v es el vector que se lee
n almacena el número de elementos leídos
*/
void LeerArray(int v[], int *n)
I
int j = 0; /* índice del array */
int ret; /* valor de retorno de scanf */
int valor;
return;
int main(void)
ITES-Paraninfo • I S I
Problemas resueltos de C
I
int array[MAX_ELEMENTOS];
int elementos; /* almacena el número de elementos del
array */
LeerArray(array, &elementos);
EscribirArrayíarray, elementos);
return(O);
1
El prototipo de la función que permite leer los elementos del array es:
void LeerArray( i n t v[], i n t *n);
El primer argumento representa el array que se quiere leer y en el segundo, la función almacenará el
número de elementos leídos. Por eso, lo que se pasa a la función es un puntero.
El prototipo de la función que permite escribir los elementos del array es:
void EscribirArrayíconst int v[], int n);
El primer argumento representa el vector que se quiere imprimir y el segundo el número de elementos del
array que se pasa a la función. Como se puede observar en este prototipo, se ha utilizado la palabra reservada
const asociada al primer argumento. Con esta palabra se está indicando al compilador que el array que
se pasa a la función no va a ser modificado dentro de la misma. Esta palabra reservada es puramente
informativa y permite que el compilador genere una advertencia en caso de que se intente modificar el array
dentro de la función. Se trata sólo de una advertencia y no de un error de compilación.
Un aspecto clave a recordar cuando se trabaja con funciones y arrays es que los arrays siempre se pasan
por referencia y que una función no puede devolver (utilizando r e t ur n) un array completo, solo se puede
hacer a través de los parámetros de la misma. Lo que sí puede devolver es un puntero que apunte a una zona
donde se almacene un array.
6.5 A r r a y s d in á m ic o s
Un aspecto clave en la definición de un array es la determinación del número máximo de elementos que
puede almacenar. En los ejemplos mostrados hasta este momento este valor se fijaba cuando se escribía
el programa, es decir, el valor se fijaba en tiempo de compilación. ¿Qué ocurre, sin embargo, cuando este
número no se conoce hasta que se ejecuta el programa? En este caso es necesario recurrir al empleo de
arrays dinámicos, es decir, arrays cuyo tamaño se fija en tiempo de ejecución.
Cuando se desea definir un array dinámico en C, hay que recurrir al empleo de punteros y mecanismos
de gestión de memoria dinámica mediante el uso de las funciones de biblioteca mallocyfree.
Para presentar el ciclo de vida de un array dinámico, se va a utilizar un ejemplo en el que se define un
array en tiempo de ejecución compuesto por diez enteros. El ciclo de vida es el siguiente:
1. Definición de un puntero a entero (que representa el array dinámico) junto con la definición de una
variable (numEl ementos) que almacena el tamaño:
int *array;
int numElementos;
int i :
2. Se establece el número de elementos en el array:
numElementos = 10;
3. Se crea el array reservando memoria dinámica:
array = (int *) ma11oc(numElementos * siz e o f (int));
4. Se usa como array:
fo r ( i= 0 ; i < numElementos ; i++) j
array[i] = 0; /* equivale a ’*(array+i)=0’ */
152 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
5. Cuando el array ya no se utiliza, se libera el espacio de memoria antes solicitado utilizando la función
de biblioteca f ree.:
free(array);
En la definición del puntero, el compilador reserva espacio para almacenar una variable de tipo puntero
a int, pero no reserva ningún otro espacio adicional. Cuando en tiempo de ejecución se ejecuta:
array = (int *) mal 1oc(numElementos * sizeof(int));
Se reserva un espacio contiguo de memoria (bloque) suficiente para almacenar 10 elementos de tipo
int y se guarda la dirección de inicio del bloque de memoria en la variable array. Observe que el valor
devuelto por la función ma 11 oc se ha convertido utilizando el casting de C a int *, debido a que array és
de tipo int *, mientras que ma 11 oc devuelve un puntero de tipo voi d *.
Una vez que array apunta aun bloque de memoria reservado con mal 1oc, se puede trabajar con array
como si fuera un array normal. Así, por ejemplo, para acceder al tercer elemento se puede utilizar a rray [ 2 ].
De igual forma, también se puede utilizar la expresión *( array + 2)
A2=NULL;
A2 NULL
► j A2 [ 0]
A 2 [i ]
A 2 [2 ]
;_A2"[3]'
A2[4]
En la Figura 6.4 se ilustra de forma gráfica el ciclo de vida de un array dinámico. En dicha figura se
resaltan los pasos seguidos en la gestión de la memoria dinámica.
Como se muestra en el segundo paso, es importante asignar el valor NU LL a un puntero para indicar que
no apunta nada. Después de la definición o de usar la función f ree, el valor de un puntero es una dirección
de memoria no válida, pero puede confundirse como válida. La única forma de saber que un puntero no
apunta a un bloque de memoria válido es asegurarse de asignar el valor NUIL.
Las principales diferencias que existen entre un array dinámico y uno estático son las siguientes:
■ En el primer caso es el programador quien tiene que encargarse de reservar espacio en tiempo de ejecu
ción. En el segundo caso, el espacio de memoria lo reserva el compilador en tiempo de compilación.
■ Una variable que representa un array estático no se puede modificar, es decir, no puede aparecer a la
izquierda de una sentencia de asignación. Sin embargo, el valor de la variable array definida anterior
mente sí se puede modificar. De hecho, se modifica cuando se le asigna el valor que devuelve ma 11 oc.
ITES-Paraninfo • 153
Problemas resueltos de C
La función rea 11 oc cambia el tamaño del bloque de memoria apuntado por pt r a un nuevo tamaño de
n bytes. Si el nuevo tamaño es mayor, el contenido anterior permanecerá inalterado, sin embargo, el nuevo
espacio reservado tendrá datos sin incializar que conviene inicializar con algún valor concreto. Esta función
también permite reducir el tamaño del bloque de memoria, quitando los elementos situados al final.
En ambos casos (reducción o ampliación) la función devuelve un puntero a la nueva zona donde se
encuentran los datos, que puede ser igual o distinta a ptr. Si la función no puede reservar el espacio
solicitado devuelve NU LL y el bloque original queda intacto.
► EJEMPLO 6.4 Escriba un programa que lea un array de caracteres, carácter a carácter, e imprima dicho array
carácter a carácter. La introducción de los datos debe finalizar con el salto de línea.
R e s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
/* Se lee un carácter */
scanf("%c", &(pchar[ultimo]));
15 4 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
return NULL;
/* Se lee un carácter */
scanf ("%c", &(pchar[ultimo]));
pri ntf("\n");
return (0);
1
La función real 1oc cambia el tamaño de un bloque de memoria que previamente ha sido reservado
con la función m a 11 o c. Cuando se realiza una petición de memoria (tanto con m a 11 o c como con re a 11 o c)
es importante comprobar el valor devuelto. Si dicho valor es NULL, el sistema no ha podido encontrar
memoria suficiente para nuestra petición. Aunque esto ocurra muy pocas veces, es importante contemplarlo
para conseguir un programa de calidad.
Aunque el índice del último elemento (variable ul ti mo en el programa) es siempre una unidad menor
al número de elementos (variable nchar), se han definido dos variables para que quede más claro. En el
programa se utiliza el carácter '\0 ' para indicar el último carácter del array de caracteres. Como más tarde
se verá, ésta es la forma de definir las cadenas de caracteres en C.
ITES-Paraninfo • I S 5
Problemas resueltos de C
Anteriormente se dijo que una función no podía retornar un array, es decir, no se puede retomar todo el
contenido del array. Lo que sí se puede es devolver un puntero al comienzo del array, como se observa en
la función leeHensaje.
6.6 A r r a y s m u l t id im e n s io n a l e s
Hasta ahora se ha trabajado con vectores de una única dimensión, llamados arrays unidimensionales,
pero el lenguaje de programación C también permite definir arrays de varias dimensiones.
Suponga que se desea almacenar las temperaturas de un punto geográfico durante un año, minuto a
minuto. Para este problema, se podría usar la siguiente definición:
f l o a t temperaturas [366][24][60];
En ella encontramos:
■ f 1oat es el tipo de datos de cada elemento.
■ temperaturas es el nombre de la variable utilizada para el array multidimensional.
■ [3661 [24] [60]: indica el número de elementos del array en cada dimensión. La primera dimensión
tiene un total de 366 elementos. La segunda 24 y la tercera 60. La primera dimensión está junto al
nombre de la variable.
Aunque el ejemplo presenta tres dimensiones, podrían definirse muchas más. Lo habitual es usar dos y
tres dimensiones. Si un array unidimensional se conoce como vect or , un array bidimensional se denomina
matri z.
[ 0 ] [0 ] [0][1]
[1 ] [0] [1] [1]
[2] [0] [2 ] [1]
[3 ] [0] [3] [1]
Un array multidimensional también es descrito como un array de arrays de... Más concretamente, la
variable mat r i z es un array de cuatro elementos ( m a t r i z [0] . . . m a t r i z [3]), cuyos elementos son a su
vez arrays de dos elementos cada uno (ma t r i z [ i ] [ 0 ] ... ma t r i z [ i ] [ 1 ], para i de 0 a 3).
En un array multidimensional de n dimensiones, los elementos de la última dimensión, que forman un
vector, se almacenan siempre en memoria consecutivamente. En el caso de una matriz (n=2), esto significa
que los valores se almacenan por filas, a diferencia de otros lenguajes de programación como Fortran, donde
156 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
[3] [0]
i [3] [1]
se almacenan por columnas. En la Figura 6.6 se muestra de forma gráfica cómo todos los elementos de una
misma fila se almacenan consecutivamente.
► EJEMPLO 6.5 Escriba un programa que lea una matriz de 5x5 enteros e indique la posición del elemento mínimo.
R e s o l u c ió n .
#include <stdio.h>
#define N 5
int ma in ()
(
i nt matri z[N][N];
1nt i , j ;
int minimo; /* valor mínimo */
int iMin, jMin; !* índices para el valor mínimo *!
/* se lee la matriz */
for (i = 0; i < N; i++)
for (j = 0; j < N; j++) {
printf("Introduzca el elemento [%d][%d]: ", i, j);
scanfC'Zd", &matri z [i ] [j ]);
minimo = matriz[0][0];
jMin = iMin = 0;
ITBS-Paraninfo • 15 7
Problemas resueltos de C
return (0);
De igual forma, para pasar un array multidimensional por parámetro sólo es necesario indicar el nombre
del array como parámetro real, sin corchetes.
158 • IT E S - P a r o n in f o
Capítulo 6 / Arrays y strings
int **m;
M m = (int**)malloc(4*sizeof(int *));
meo] ro]
M [ 0 ] [1]
*• M[l] [0]
M [ l ] [1]
M[2] [0]
M[2] [1]
--- *■ M[3] [0]
M [ 3 ] [1]
6.7 C a d en a s de caracteres
En el lenguaje C los textos y frases se definen como una secuencia finita de caracteres consecutivos. A
esta secuencia de caracteres se le denomina cadena de caracteres o string. A lo largo del texto se usarán
ambos términos indistintamente.
Para representar un string se usa un array de caracteres (no hay un tipo propio, como en otros lenguajes).
Las operaciones habituales con cadenas de caracteres se definen como funciones de la biblioteca st rin g .h
(no hay operadores propios del lenguaje). El valor que toma un string es una secuencia de caracteres,
normalmente los definidos en la tabla de caracteres ASCII, terminados en un carácter especial que indica el
fin de la cadena.
Como se puede apreciar, conociendo la definición y uso de un array, un string es un caso particular: el
de un array de caracteres. A continuación se repasan los principales aspectos para trabajar con string.
ITES-Paraninfo • 159
Problemas resueltos de C
cadena3 = cadenal ;
Se define cadena3 como un puntero a un string; el compilador reserva memoria para la cadena de
caracteres "Hola" y asigna la dirección de comienzo a cadena3. A continuación se asigna a cadena3 la
dirección de cadenal, con lo que cadena3 pasa a representar también a cadenal. Un detalle importante
es que la zona de memoria a la que antes hacía referencia cadena3 se ha perdido. Es importante tener
mucho cuidado con el manejo de punteros. Hay que tener en cuenta también que en la asignación cadena3
= cadenal, no se copia todo el contenido de la cadena, lo único que se hace es que cadena3 apunte (se
trata de un puntero) al comienzo de la cadena cadenal.
Para evitar este tipo de problemas se puede definir el puntero de la siguiente forma:
160 • ITES-Paraninfo
Capítulo 6 I Arrays y strings
¡*
* Acceso al segundo elemento
*/
carácter = cadena[l];
/*
* Modificación del primer carácter
*1
cadena[0] = ’M ’;
/*
* Acortar la cadena a "Mar"
*/
cadena[3]=’\0’;
La última sentencia del ejemplo anterior asigna al cuarto elemento de cadena (con índice 3) el valor
’ \ 0 ’ (fin de cadena) por lo que en memoria sigue ocupando lo mismo, pero representa a una cadena de
caracteres de menor longitud ("Mar").
Al igual que ocurre con los arrays, debe asegurarse de que el índice que se utiliza para acceder a los
caracteres individuales de un string se encuentra dentro del rango válido.
► EJEMPLO 6.6 Escriba un programa que lea una cadena de caracteres y calcule su longitud.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
{
char cadena[MAX_CADENA];
int longitud = 0;
return (0);
1
En este programa se utiliza la función sc an f con el especificador de formato " %s " para leer una cadena de
caracteres. Observe que debido a que el nombre del array (en este caso una cadena de caracteres) representa
la dirección de memoria del comienzo del mismo, no es necesario el empleo del operador de dirección para
que la función sc a nf modifique el contenido de la cadena.
ITES-Paraninfo • 161
Problemas resueltos de C
Otra posible solución para este problema sería utilizar un puntero en lugar de un índice para recorrer la
cadena, tal y como se muestra a continuación:
#include <stdio.h>
int main(void)
(
char cadena[MAX_CADENA];
int longitud = 0;
char *aux;
*
aux = cadena;
► EJEMPLO 6.7 Escriba un programa al que se le pase como parámetro una cadena de caracteres, e imprima dicha
cadena suprimiendo de la misma todas las vocales.
R e s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
/*
* Se pide un argumento al programa.
*/
if (argc != 2) {
printf ("XnUso: %s <mensaje a acortar>\n", argv[0]);
exit(-l);
162 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
/*
* Se calcula la longitud del mensaje.
*/
mensaje=argv[l];
longitud=0;
for (i=0; mensaje[i]!= ’\0 ’; i++) {
1ongi tud++;
/*
* Se reserva memoria para crear un 'clon'.
*/
mensaje_duplica = (char *)mal1oc(sizeof(char)*(1ongitud + 1));
if (mensaje_duplica == NULL) (
p r i n t f ( " N o se puede a s i g n a r me mo r i a \ n ");
return(O);
/*
* Ya podemos usar y modificar el ’clon’.
*/
j=0;
for (i= 0 ; i<=1ongi tud; i++)
(
if (
(mensaje[i] != *a ) &&
(mensaje[i] != ’e ) &&
(mensaje[i] != ’i ) &&
(mensaje[i] != *0 ) &&
(mensaje[i] != ’u )
m e n s a j e _ d u p l i c a [ j ] = m e n s a j e t i ];
j++;
/*
* Se imprime el resultado.
*/
pri n t f ( " % s \ n " . me ns aj e_d u p1i c a ) ;
/*
* Hay que liberar la memoria cuando no se necesite.
*/
f ree(mensaj e _ d u p l i c a ) ;
return (0);
1
Este programa ilustra el uso del paso de parámetros a un programa. Hasta el momento, todos los ejem
plos y programas que se han presentado definían la función ma i n como una función que devuelve un entero
y no acepta argumentos. Sin embargo, esta función puede aceptar los argumentos que se pasan al programa
ITES-Paraninfo • 163
Problemas resueltos de C
en la línea de mandatos cuando se ejecuta éste. Para ello, la función main debe definirse de la siguiente
forma:
int main(int argc, char *argv[])
donde argc representa el número de argumentos que se pasan al programa incluyendo el propio nombre
del programa y a rgv es un vector de cadenas de caracteres que almacena cada uno de los argumentos que
se pasan al programa incluyendo el propio nombre del programa. Así, cuando un programa se ejecuta de la
siguiente forma:
programa argl arg2 arg3
El parámetro a rgc de la función main almacenará 4 y a rgv tendrá el siguiente contenido:
■ argv[0] apuntará la cadena programa.
■ argv[l] apuntará la cadena a rgl.
■ argv[2] apuntará la cadena a rg2.
■ a rgv [3] apuntará la cadena a rg3.
Función Significado
strcpy Copia una cadena en otra
strlen Devuelve la longitud de una cadena
strcat Concatena cadenas
strcmp Compara dos cadenas
strcasecmp Compara dos cadenas, ignorando mayúsculas y minúsculas
strchr Busca un carácter dentro de una cadena
strrchr Busca un carácter dentro de una cadena pero en dirección
contraria: de fin a principio
strstr Busca una cadena dentro de otra
En stdl ib .h también se declaran algunas funciones interesantes relacionadas con el manejo de cadenas
(véase la Tabla 6.2). Las más habituales tienen mucho que ver con la conversión desde una cadena de
caracteres a otro tipo.
164 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
Función Significado
at o i Convierte una cadena a un entero (int)
atol Convierte una cadena a un entero largo ( 1on g)
at of Convierte una cadena a un real (doub 1e)
Función Significado
puts Escribe una cadena seguida de un retomo de carro
gets Lee una cadena terminada en un retomo de carro (el
cual se sustituye por ’ \ 0 ’)
putcha r Escribe un único carácter
getcha r Lee un único carácter
pri ntf Permite escribir una cadena de caracteres usando el
formato " %s " y escribir un carácter usando el formato
"%c", etc.
scant Permite leer una cadena de caracteres usando el for
mato " %s " y leer un carácter usando el formato " %c ",
etc.
En el archivo de cabecera stdi o .h están las funciones necesarias para la lectura y escritura de cadenas
de caracteres. En la Tabla 6.3 se resumen estas funciones.
La función gets es un ejemplo de una función m u y peligrosa, puesto que no comprueba el tamaño
del array donde se almacenan los caracteres. Este tipo de errores (sobreescribir una zona de memoria no
reservada previamente) son difíciles de detectar. Si dicha zona de memoria no es usada frecuentemente, el
número de veces que aparece el error es también poco frecuente y por tanto más difícil de detectar.
Por ello, es preciso usar cadenas de caracteres dinámicas y diseñar funciones como leeMensaje, mos
trada en el Ejemplo 6.4.
Si en el entorno de desarrollo se dispone de la función strdup, se puede sustituir el siguiente fragmento
de código:
/*
* Se reserve memoria para crear un ’clon'.
' */
mensaje_duplica = (char *) malloc ( sizeof(char) * (longitud + 1) ):
if (mensaje_duplica == NUIL) (
perror("malloc:");
exit(-2);
1
/*
* Se copia todos los caracteres de cadena5, incluido el ’\ 0 ’.
*/
strcpy(mensaje_dupl ica ,argv[l ]■);
Por el siguiente:
/*
* Se crea un 'clon ’.
*/
mensaje_duplica = strdup(argv[l]);
Se propone al lector realizar dicho cambio para comprobar sus efectos.
ITES-Paraninfo • 16 5
Problemas resueltos de C
E v it e es t o s er r o r es
A continuación se repasan los principales errores que se cometen cuando se manipulan arrays:
Olvidar que el primer índice de un array es 0 El primer índice de un array es siempre 0. En muchas
ocasiones se comete el error de empezar por 1.
Utilizar como índice un valor que no sea entero Los índices que se utilizan para acceder deben ser de
tipo entero, como char, short o i nt.
Acceder a una posición del array fuera de rango Cuando se accede a los elementos de un array no
se hace ninguna comprobación. Suele ser un error frecuente acceder a posiciones que no pertenecen al
array. Asegúrese de que siempre utiliza como índice un valor comprendido entre 0 y elementos — 1,
siendo elementos el número de elementos del array.
Definir un puntero y no reservar memoria Un error también muy frecuente consiste en definir una
variable de tipo puntero, que se utilizará como un array dinámico y no reservar espacio para el array con
mal 1oc. Recuerde que la definición de un puntero sólo reserva espacio para el puntero. Es necesario
utilizar ma 11 oc para reservar espacio en memoria para las componentes del array dinámico.
Acceder a una array multidimensional de forma inadecuada Cuando se quiere, por ejemplo, acceder
a la componente i,j de un array M, no utilice la expresión M [ i ,j]. En su lugar, utilice la expresión
M [ i ] [ j ].
Olvidar contabilizar el carácter de fin de cadena Cuando reserve espacio para una cadena tenga en
cuenta que la cadena debe incluir al final el carácter nulo.
Usar comillas simples para representar cadenas de caracteres Las comillas simples se utilizan para
representar caracteres individuales. Para las cadenas de caracteres se utilizan dobles comillas.
No cerrar bien una cadena de caracteres Asegúrese de que las cadenas de caracteres utilizadas como
valores constantes se encierran entre dobles comillas.
No comprobar los índices Para acceder a un string, hay que estar seguro de no usar un índice situado
fuera de rango.
Utilizar el operador de dirección en la función s canf Cuando se pasa una cadena a la función scanf
para su lectura, no ha de utilizar el operador de dirección.
Utilizar un string definido como puntero sin reservar espacio Cuando defina un string utilizando un
puntero a cha r hay que reservar espacio para la cadena utilizando mal 1 oc.
Copiar una cadena en otra que no tiene espacio suficiente Cuando copie una cadena en otra, asegúrese
de que la cadena destino tiene espacio suficiente para almacenar la cadena que se copia.
Utilizar scanf para leer una línea que incluye espacios La función scanf utiliza como delimitador el
espacio en blanco, por tanto, no se podrá utilizar para leer una línea que incluya espacios en blanco. En
su lugar puede utilizar la función gets.
P r o b l e m a s R e s u e lt o s
6.1 Modifique el Ejemplo 6.1 de forma que el programa imprima al final los meses con temperatura inferior a
la media anual.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I
/* defini elón de variables *!
166 • ITES-Paran'mfo
Capítulo 6 / Arrays y strings
float temperatu’
r aMeses[NUMERO_MESES];
int j ;
float temperaturaMedia = 0;
return (0);
► 6.2 Modifique el Ejemplo 6.1 de forma que el programa indique el mes con la temperatura mínima y el mes
con la temperatura máxima.
Re s o l u c ió n .
#include <stdio.h>
#define NUMER0_MESES 12
int main(void)
I
/* definición de variables */
float temperaturaMeses[NUMERO_MESES];
float temperaturaMinima;
float temperaturaMaxima;
int indiceMin; /* indice del mes con temperatura minima */
int indiceMax; /* indice del mes con temperatura máxima */
int j ;
ITES-Paraninfo • /67
Problemas resueltos de C
return (0);
}
Este programa utiliza las variables indiceMin e indiceMax para almacenar los índices de los meses con
la temperatura mínima y máxima respectivamente.
► 6.3 Escriba un programa que lea las notas obtenidas por los alumnos de una asignatura. La introducción de
datos finalizará cuando se introduzca el fin de archivo. El programa a continuación tiene que imprimir la
nota media del grupo. Considere que el número máximo de alumnos por grupo es 100.
Re s o l u c ió n .
#include <stdio.h>
int main(void)
■ {
/* definición de variables */
float notas[MAX_ALUMN0S];
float nota;
int numeroAlumnos = 0; /* contabi1iza el número de alumnos*/
float notaMedia = 0.0;
int ret; /* almacena el valor de retorno de scanf *1
int j ;
16 8 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
el se
printf("No se ha introducido ninguna nota\n");
return (0);
Ì
En este programa se utiliza la variable numeroAlumnos para contabilizar el número de notas que se han
introducido. Recuerde que la función scanf devuelve un valor entero que indica el número de elementos
correctamente leídos o bien fin de archivo, indicado mediante la constante EOF definida en el archivo de
cabecera stdio . h (el fin de archivo se indica en MS-DOS y Windows mediante Ctrl -Z y en sistemas
Linux y Unix mediante C t r1 -D).
► 6.4 Amplíe la funcionalidad del programa anterior de forma que el programa imprima al final la nota máxima
introducida.
R E S O L U C IÓ N . Puesto que este Ejemplo es similiar al anterior, sólo se van a indicar en este apartado los
cambios introducidos en el mismo. En el programa se define la variable de tipo puntero:
float *notaMaxima;
que se utilizará para apuntar a la componente del array not as que almacena la nota máxima. Una vez
definida dicha variable, a continuación se muestra la parte final del programa (la parte anterior es similar a
la del ejemplo anterior).
I* se obtiene la nota media y la máxima *!
notaMaxima = ¬as[0]; /* equivalente a notaMaxima = notas */
if (numeroAlumnos !=0) (
for (j = 0; j < numeroAlumnos; j++) {
notaMedia = notaMedia + notasüj];
ITES-Paraninfo • 1 6 9
Problemas resueltos de C
notaMaxima = ¬as[j];
el se
printfC'No se ha introducido ninguna nota\n");
► 6.5 Escriba un programa que lea por la entrada estándar diez números enteros distintos de cero y a continuación,
lea una secuencia de enteros indicando si están entre los diez valores leídos. Si se lee el valor cero, el
programa finaliza su ejecución.
Re s o l u c i ó n .
#include <stdio.h>
#include <stdlib.h>
#define NELTOS 10
int vector[NELTOS] ;
do (
pri ntf(" Introduzca el número entero a buscarCn");
1eidos=scanf("%d",&n) ;
if (1ei dos != 1 ) I
printf("Error al leer un número entero\n");
return (-1);
if (n == 0 ) i /* fin */
break;
t* se busca el elemento */
encontrado=0;
170 • IT E S - P a r o n in f o
Capítulo 6 / Arrays y strings
if (encontrado == 0)
printf("El emento NO encontradoVn");
el se
printf("El emento encontrado (posición %d )\n",i);
) while (1);
return (0);
Escriba una función que reciba como parámetro un array y un elemento. La función debe determinar si el
elemento se encuentra en el array, devolviendo un valor 0 si el elemento no se encuentra y 1 si el elemento
se encuentra en el array.
Re s o l u c ió n .
#include <stdio.h>
/* prototipos de la funciones */
/*
v es el vector que se lee
n almacena el número de elementos leídos
*/
void LeerArray(int v[], int *n)
1
int j = 0; /* índice del array */
int ret; í* valor de retorno de scanf */
int valor;
ITES-Paraninfo • 171
Problemas resueltos de C
I■
/* se devuelve también el número de elementos leídos */
*n = j ;
return;
J++;
return(esta);
int main(void)
1
int array[MAX_ELEMENTOS];
int n; /* almacena el número de elementos del array */
int elemento;
LeerArrayíarray, &n);
if (Estatarray, n, elemento) == 1)
printf("%d está en el array\n", elemento);
el se
printf("M no está en el array\n", elemento);
return(O);
#define DIMENSION 10
int i , j ;
1 7 2 » ITES-Paraninfo
Capítulo 6 / Arrays y strings
pri ntf("\n");
/*
* Definición de tres arrays bidimensional es
*/
float a[DIMENSI0N][DIMENSION];
float b[DIMENSION][DIMENSION];
float c[DIMENSI0N][DIMENSION];
1eerMatri z(a);
1eerMatri z(b);
sumarMatriz(a, b, c);
imprimi rMatri z (c );
return (0);
► 6.7 Escriba un programa que sume dos matrices cuadradas utilizando matrices dinámicas.
Re s o l u c ió n .
#include <stdio.h>
ITES-Paraninfo • 1 73
Problemas resueltos de C
return (m );
free(m);
return;
pri ntf("\n");
1 7 4 « ITES-Paraninfo
Capítulo 6 / Arrays y strings
int i , j ;
int main(void)
/* Sumar matrices */
/* Imprimir el resultado */
return (0);
ITES-Paraninfo • 17 5
Problemas resueltos de C
I ► 6.8 Escriba un programa que lea dos vectores y calcule el vector suma correspondiente. El programa debe
solicitar al usuario el número de elementos de los vectores.
Re s o l u c ió n .
#1 nclude <stdio.h>
#include <stdlib.h>
/* prototipo de la función */
void LeerVectorlint *v, int n);
void SumarVectoresíconst int *vl, const int *v2, int *vs, int n);
void EscribirVector(const int *v, int n);
/*
v es el vector que se lee
n es el número máximo de elementos
*/
void LeerVector(int *v, int n)
(
int j = 0; /* indice del array */
int ret; /* valor de retorno de scanf */
int valor;
while (j < n) {
printf("Introduzca un elemento: ");
ret = scanf("M", &valor);
if (ret < 1)
printf("Error en la introducción del dato\n");
el se {
v[j] = val or;
j++;
return;
void SumarVectores(const int *vl, const int *v2, int *vs, int n)
(
int j ;
return ;
17 6 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
return;
int main(void)
I
int *vl,
* *v2, *vs;
int n; '/* número de elementos del array *1
return!0);
► 6.9 Escriba una función que copie una cadena en otra, asumiendo que hay suficiente hueco para copiarla.
Re s o l u c ió n .
#include <stdio.h>
j EOL ioteca
d e S e v i l l a .
ITES-Paraninfo • 1 77
Problemas resueltos de C
CopiarCadena(cadenal, cadena2);
return(O):
)
El prototipo.de la función CopiarCadena es el siguiente:
void CopiarCadena(const char *origen, char *destino)
lo que indica que la cadena o rig en no se va a modificar en la funci ón y sí la cadena destino. Recuerde
que los arrays y las cadenas se pueden modificar dentro de las funciones.
► 6.10 Escriba un programa que lea un vector de enteros y los ordene de menor a mayor.
Re s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
178 • ITES-Paraninfo
Capitulo 6 / Arrays y strings
return;
inicioVector(vector,NELTOS);
OrdenarPorBurbuja(vector,NELTOS);
printf("Vector ordenado:\n");
printVector(vector,NELTOS);
pri ntf("\n");
return (0);
1
Para ordenar el vector se ha utilizado el método de la burbuja. Este método se basa en realizar sucesi
vos recorridos del vector. En cada etapa del recorrido se intercambian aquellos pares de elementos que se
encuentren desordenados. El proceso finaliza cuando no se realizan más cambios.
► 6 . 1 I Implemente la función mi_strcat con la misma funcionalidad que strcat. Esta función concatena una
cadena de caracteres al final de otra.
Re s o l u c i ó n .
char *mi_strcat ( char *dst, const char *src )
{
int len_src, len_dst, i;
1en_src=strlen(src);
1en_dst=strlen(dst);
for (i=0; i<=len_src; i++) {
d st [1en_dst+i] = src[i];
return dst;
► 6.12 Escriba un programa que imprima todos los argumentos que se le pasan en la línea de mandatos a excepción
del nombre del programa.
ITES-Paraninfo • 1 79
Problemas resueltos de C
Re s o l u c ió n .
#include <stdio.h>
return(O);
Re s o l u c ió n .
int mi_strcmp ( const char *a, const char *b )
i
int len_a, len_b, min, i;
1en_a=str!en(a );
1en_b=strlen(b );
min=(len_a < len_b) ? len_a : len_b ;
for (i=0; i<=min; i++) f
if (a[i] > b[i]) {
return 1;
}
if (a[i] < bCi]) (
return -1;
Re s o l u c ió n .
char *mi_strchr( const char *string, int c )
I
int 1en, i ;
1en=strlen(stri ng);
for (i=0; i<1en ; i++) {
if (string[i] == (char)c) i
return &(stri ng[i]);
return NULL;
18 0 • ITES-Paraninfo
Capítulo 6 / Arrays y strings
. 15 Escriba un programa que lea una cadena de caracteres e indique si la cadena es un palíndromo. Un palíndro
mo es una cadena que se lee de igual forma de derecha a izquierda y de izquierda a derecha. Por ejemplo,
dabalearrozalazorraelabacL es un caso típico de palíndromo.
Re s o l u c ió n .
//include <stdio.h>
//include <string.h>
longitud = strlen(origen);
InvertirCadena(cadena , aux);
if (strcmp(cadena, aux)== 0)
return (true);
else
return (false);
int main(void)
(
char cadena[MAX_CADENA];
if (EsPalindromoícadena))
printf("%s es un palindromoXn", cadena);
el se
printfC'Xs no es un pal indromoXn", cadena);
ITES-Paraninfo • 181
Problemas resueltos de C
return(O);
)
En este programa se ha definido una función que permite invertir una cadena de caracteres. Para ver si una
cadena es un palíndromo se invierte y se compara con la original. Para ello se utiliza la función de biblioteca
strcmp. Esta función devuelve 0 cuando las dos cadenas pasadas como argumentos son iguales, un valor
menor que 0 si la primera es menor que la segunda utilizando el orden lexicográfico y un valor positivo en
caso contrario.
i f (argc != 2) I
printf("\nUso: %s <mensaje a acortar>\n", argv[0]);
return(O);
/*
* Se calcula la longitud del mensaje.
*/
longitud = strlen(argv[l]);
/*
* Se reserva memoria para crear un ’clon’.
*/
mensajeDupl ica = (char *)mal1oc(sizeof(char)*(longitud + D ) ;
if (mensajeDuplica == NUIL) I
printf("Error, no se puede reserva memoria\n");
return(O);
/*
* Se copian todos los caracteres de cadena5, incluido el ’\ 0 ’.
*/
strcpy(mensajeDuplica, argvfl]);
/*
* Ya podemos usar y modificar el ’clon’.
*!
j=0;
for (i= 0 ; i<==1 ongi tud ; i++) {
if (strchr("aeiou”.mensajeDuplica[i]) == NULL) {
mensajeDuplica[j] = mensajeDuplica[i];
j++;
182 • IT E S - P o r a n in f o
Capítulo 6 / Arrays y strings
/*
* Se imprime el resultado.
*/
pri ntf("%s\n".mensajeDupli ca);
/*
* Hay que liberar la memoria cuando no se necesite.
*/
free(mensajeDuplica);
return (O );
► 6. 17 Escriba un programa que permita manipular polinomios de grado N. El programa pedirá al usuario el
grado máximo de los polinomios con los que va a trabajar. Debe implementar las siguientes funciones:
crear un polinomio de grado N, destruir un polinomio, leer un polinomio, escribir un polinomio, sumar dos
polinomios y multiplicar dos polinomios.
Re s o l u c ió n .
#include <stdio.h>
#include <std1ib .h>
ITES-Paraninfo • 183
Problemas resueltos de C
int i ;
el se (
resultado = crearPolinomio(grado2);
if (resultado == NULL)
return resultado;
return resultado;
if (resultado == NULL)
return resultado;
18 4 • ITES-Paraninfo
Capítulo 6 I Arrays y stringi
return resultado;
p2 = crearPoli nomio(5) ;
1eerPoli nomi o(5,p2 ) ;
p rin t f ("\n " ) ;
resultado = sumar(5,pl,5,p2);
printfC'El resultado de la suma es:");
escribirPolinomio(5,resultado);
destruirPoi inomio(&resultado);
pri ntf("\n");
destruirPolinomio(&pl);
destruirPo1inomio(&p2);
return (0);
Pro blem a de Ex a m e n
► 6 . 18 Un amigo necesita una agenda que le liste las diez tareas más importantes ordenadas de más prioritaria a
menos prioritaria. Nuestro amigo ha implementado una agenda llamada listaTareas como un array de
10 elementos que son cadena de caracteres. Por desgracia, la prioridad de las tareas cambia frecuentemente
y nuestro amigo nos pide ayuda. Le aconsejamos que se defina un segundo array, llamado porPri ori dad,
de 10 elementos de tipo entero, que almacena el índice de la tarea en el primer array. De esta forma, para la
tarea de prioridad i, su descripción se encuentra en la posición p o rP rio rid ad [ i] del array listaTareas.
Dado que nuestro amigo no tiene tiempo, nos pide que escribamos un programa que lea la lista de tareas
ordenada por prioridad desde teclado y permita cambiar la prioridad usando nuestra idea.
Re s o l u c ió n .
#include <stdio.h>
#include <string.h>
#defi ne MAX_LONG_TAREA 1024
char 1istaTareas[10][MAX_L0NG_TAREA] ;
int porPrioridad[10] ;
ITES-Paraninfo • 185
Problemas resueltos de C
return indice;
return opcion;
/* Gestión de la lista *t
sali r=0;
while (salir == 0) (
opcion= 1eerOpcion();
switch (opcion) {
case ’a ’;
/* Leer el índice */
indice = leerlndiceO;
186 • IT E S - P a r o n in f o
Capítulo 6 / Arrays y strings
/* Leer la tarea */
printf("Introducir la tarea. \n") ;
printf("> ");
scanf("%[~\n]",listaTareas[indice]);
ch=getchar(); /* leer \n */
!* Indicar la prioridad */
porPrioridad[indice]=indice;
break ;
case ’H ’:
/* Leer el antiguo índice *!
indi ce = 1eerlndi ce() ;
if ( (indice < 0) || (indice > 9 ) )
(
printf("Error : índice no válido\n");
conti nue;
break;
case ’m ’:
/* Leer el antiguo índice */
indi ce = 1eerlndi ce();
if ( (indice < 0) || (indice > 9) )
I
printf("Error: índice no válido\n");
conti nue;
break;
case '1’;
for (i=0 ; i<10 ; i++) 1
if (porPrioridad[i] != -1) (
printf("%2d -> %s\n",
i,listaTareas[porPrioridad[i]]);
pri ntf("\n\n");
break;
ITES-Paraninfo • 1 8 7
Problemas resueltos de C
case ’s ’:
printfC'Fin del programa\n");
sal i r=l;
break;
default:
printf("Error: opción desconocida: %c\n" .opcion);
break;
return (0);
m = medi a(n ,
C\J
LO
3.
>
4. m = medi a ( n , v) ;
= ( 1 , 2 . 3 . 4 , 5, 6 , 7 ,
+->
C
>
_
i nt i , *a *
f o r (i = 0; i < 10; i ++) 1
a = &v [ i ] ;
*a = v[0] + i ;
18 8 • ITES-Paraninfo
Capítulo 6 / Arrays y strin g s
6 .8 ¿Qué sucede si se ejecuta free( arr ay ) siendo array una variable definida como cha r array2[4]?
6. 12 Dada una matriz de 4x4 cuyos elementos están todos inicializados con un valor igual a 2, ¿cuál será el valor
final de la variable tot después de ejecutarse el siguiente fragmento?
tot=0;
for (i=0; i<4; i++)
for (j=0; j<i ; j++)
tot+=Matri z[i][j] ;
1. 12
2 . 20
3. 16
4. 32
6. 13 Realice un programa que permita leer por teclado la temperatura media de cada día de la semana e indique
el día más caluroso y el más frío.
6. 14 Escriba un programa que calcule el producto de dos matrices de 10 por 10.
6. 15 Un usuario desea conocer el santo correspondiente a un día del año. El usuario indica el mes y el día de
ese mes y el programa imprime el santo asociado. ¿Cómo puede definirse el Santoral? ¿Qué ventajas y qué
inconvenientes tiene?
ITES-Paraninfo • 189
Problemas resueltos de C
6. 16 Si se decide usar un array bidimensional para el ejercicio anterior, en el que la primera dimensión se co
rresponde con el mes y la segunda con el día dentro del mes, escriba un programa que implemente la
funcionalidad del Santoral usando arrays multidimensionales dinámicos.
6. 17 Escriba una función que permita ordenar de mayor a menor los elementos de un vector con componentes
de tipo i nt.
6. 18 Escriba un programa que lea una línea y la imprima al revés.
6. 19 Escriba un programa que recorra todos los caracteres de la tabla ASCII e imprima aquellos que son impri
mibles. Puede utilizar la función de biblioteca i sgraph, definida en <c t ype . h> que determina si el carácter
pasado por parámetro es imprimible.
6.2 0 Escriba un programa que lea una línea y la imprima cifrada. Cada letra será cifrada utilizando la siguiente
expresión (Código ASCII + 7) módulo 10.
6.21 Escriba un programa que lea un texto (hasta fin de archivo) y calcule el número de palabras del mismo.
6.2 2 Escriba un programa que lea un texto (hasta fin de archivo) y contabilice el número de líneas del mismo.
6.23 Escriba un programa que lea un texto (hasta fin de archivo) e indique qué letras no han aparecido en el texto.
6.2 4 Modifique el programa anterior, para que el programa diga además el número de veces que aparece cada
una de las letras del alfabeto en el texto.
6.25 Escriba un programa que lea un texto (hasta fin de archivo) e indique el porcentaje de palabras que tiene
menos de 5 caracteres y el porcentaje de palabras con 5 o más caracteres.
6.26 Escriba un programa que lea palabras (hasta fin de archivo) y las escriba con todos sus caracteres en
mayúscula.
6.2 7 Escriba un programa que lea palabras (hasta fin de archivo) e imprima sólo aquellas de acaban en "as".
6.28 Escriba un programa que lea un número binario y muestre su valor decimal. Almacene los dígitos binarios
en un string.
6.2 9 Escriba un programa que lea un texto y lo escriba dejando un solo espacio entre palabra y palabra. Es decir,
si aparecen dos espacios entre dos palabras, sólo debe quedar uno.
6.3 0 Escriba un programa que lea una línea y rote su contenido. Por ejemplo, para:
■ En linea
Se deberá obtener:
■ En linea
■ n linea E
■ linea En
■ linea En
■ inea En 1
■ nea En li
■ eaEnl i n
■ aEnl i ne
■ En linea
■ En linea
190 • ITES-Paraninfo
En este capítulo...
7.1 Definición y procesamiento de escruccuras
7.2 Arrays de estructuras
7.3 Uniones
7.4 Estructuras de datos autorreferenciadas
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
Estructuras
Problemas resueltos d e C
En este capítulo se presenta otro tipo de datos muy útil: las estructuras. Una estructura permite representar
una tupia de valores muy relacionados entre sí. A toda la tupia se le da un nombre, pero se accede a cada
valor a través de un identificador.
Utilizando un maletín como símil, se podría decir que los miembros de la estructura representan los
compartimentos y la estructura completa el maletín. El uso típico de una estructura es transportar una serie
de valores, usados conjuntamente, entre las distintas partes de un programa.
Las cuatro partes en las que se divide este capítulo son:
■ Definición y procesamiento de estructuras.
■ Arrays de estructuras.
■ Uniones.
■ Estructuras de datos autorreferenciadas.
7 .1 D e f in ic ió n y p r o c e s a m ie n t o de e s t r u c t u r a s ________
Una estructura es un tipo de datos que permite agrupar bajo un mismo nombre elementos del mismo o
de distinto tipo de datos, que se encuentran muy relacionados entre sí.
Cada elemento se denomina miembro de la estructura. Una estructura permite modelizar entidades
donde los miembros de la estructura normalmente representan los atributos o propiedades de dicha entidad.
Por ejemplo, para la gestión de los libros en una pequeña biblioteca es preciso conocer el título del libro
(ti tul o), su autor (autor), el ISBN (ISBN), el estante donde se coloca (estante), si está prestado o no
(prestado) y el nombre de la persona a la que se ha prestado (el iente).
Todos estos campos formarían la entidad FichaLibro para un gestor de libros. Esta entidad se puede
modelizar en C mediante una estructura como la siguiente:
s t r u c t FichaLibro {
La estructura tiene una finalidad parecida a los arrays, pero sus diferencias son claras:
Los elementos de un array son todos del mismo tipo, en una estructura pueden ser de diferentes tipos de
datos.
En un array se selecciona un elemento por su posición dentro del array, mientras que en una estructura
cada elemento tiene su propio identificador.
Un array modela mejor una secuencia de valores, mientras que una estructura modela de forma natural
las partes de un todo.
De forma genérica una estructura se define de la siguiente manera:
struct NombreDeLaEstructura {
Ti poDato_l mi embro_l:
T ipoDato_2 mi embro_2;
TipoDato_N miembro_N;
19 2 • ITES-Paraninfo
Capitulo 7 / Estructuras
Donde:
■ NombreDeLaEstructura : es el nombre que se da a la estructura.
■ Ti poDato_i : es el tipo de datos del i-ésimo miembro.
■ mi embro_i : es el nombre del i-ésimo miembro de la estructura.
A partir de esta estructura Fic ha Lib ro se pueden definir las variables 1 ib roA y 1 ib ro B de la siguiente
forma:
struct FichaLibro libroA;
struct FichaLibro libroB;
Una segunda posibilidad para definir estas variables es utilizar una definición de estructura más com
pacta:
struct Fi chaLibro 1
1 libroA, libroB ;
Una tercera posibilidad para definir estas variables es:
/* D e f i n i c i ó n d e l a estructura */
struct FichaLibro {
/* D e f i n i c i ó n d e l t i p o ’ F i c h a L i b r o ’ * /
typedef struct FichaLibro FichaLibro ;
! ► EJEMPLO 7.1 Definición de una estructura que represente un instante dado de tiempo.
R e s o l u c ió n .
struct Instante
i
I
int hora ;
1nt mi ñuto ;
int segundo ;
ITES-Paraninfo • 19 3
Problemas resueltos de C
► EJEMPLO 7 .2 ¿Cómo podría inicializarse en la definición una variable del siguiente tipo?
s t r u c t punto
(
int x[4];
int y [4];
char nombre[100];
R e s o l u c ió n .
struct punto
1
int x[4];
int y[4];
char n omb r e [ 100 ] ;
Si una estructura tiene miembros de tipo estructura o array, la inicialización se realiza recursivamente.
Es posible inicializar parcialmente una estructura (como en el caso de la variable 1 ib roB), pero siempre
se asignan los valores a los primeros miembros de la estructura, desde el primer miembro en adelante. En
C99 es posible inicializar los miembros en el orden que se desee. Por ejemplo:
Fi cha Li bro 1 ibroA = {
.ISBN = "ISBN",
.autor = "autor",
.prestado = 0,
.titulo = "titulo",
) ;
Se utiliza la expresión:
.mi embro=Valor
para indicar a qué miembro hay que asignar el valor indicado.
194 • ITES-Paraninfo
Capítulo 7 / Estructuras
► EJEMPLO 7.3 Dadas las siguientes definiciones de estructuras, indique cómo se accede a los siguientes miembros
delibrol:
1. Día inicial del préstamo.
2. Primer carácter del miembro t i t u l o .
3. Valor apuntado por el miembro es t a nt e .
/* Definición de las estructuras */
struct Fecha I
int di a ;
int mes;
int anyo
1 :
struct Fi chaLibro {
int prestado ;
char cl iente[256]
struct Fecha inic
ITES-Paraninfo • 195
Problemas resueltos de C
Re s o l u c ió n .
1. - 1 ibrol.ini cio_prestamo.di a
2. - 1 ibrol.ini ci o_pres tamo.ti tul o[0]
3. - * U i brol.ini cio_prestamo.estante)
No es posible que un miembro de una estructura tenga como tipo la propia estructura. Así, la siguiente
definición es incorrecta:
struct FichaLibro {
/* Uso de la variable */
(*referencia_a_libro2).prestado = 0;
19 6 • ITES-Paraninfo
Capítulo 7 / Estructuras
En donde la variable referenci a_a_l ibro2 toma como valor la dirección de memoria de una zona
reservada con la función de biblioteca mal 1oc. Cuando no se necesite guardar valores, será necesario
liberar la zona de memoria anteriormente reservada con ma 11 oc usando la función f ree.
En el fragmento de código anterior, se muestra cómo se accede a un miembro de una estructura a través
de un puntero:
(*referencia_a_libro2).prestado = 0;
Mientras que referenci a_a_l ibro2 representa el puntero a la estructura, (*referenci a_a_l ibro2)
representa el contenido de la estructura. Por tanto, sólo es necesario usar el operador . para acceder a un
miembro de dicha estructura. En esta expresión se utilizan los dos operadores * y . conjuntamente.
Pero existe otra manera de expresarlo:
referenci a_a_libro2->prestado = 0;
Como se aprecia, el operador - > indica que en su parte izquierda está la dirección de memoria de una
estructura, a la que hay que acceder y seleccionar el miembro que está en la parte derecha del operador.
Suponga que un int ocupa un tamaño de 4 bytes y un char ocupa un tamaño de 1 byte.
Dada la siguiente definición de variable:
s t r u c t Datos datos;
Si los miembros de la estructura para la variable datos se colocan en memoria consecutivamente, se
obtendría la disposición que se muestra en la Figura 7. l(a).
(a) S in re lle n o
c a rá c te r
e n te ro 1 s tr u c t D atos
(b) A ju s te a 4 b y te s
c a rá c te r
b y te s de re lle n o
s tr u c t D atos
e n te ro
Figura 7.1 Posibles disposiciones para los miembros de una estructura de relleno
Otra alternativa, que se muestra también en la Figura 7.1 (b), podría dejar un hueco entre el miembro
carácter y el miembro entero. De esta forma, la dirección en memoria del siguiente miembro de la
estructura es múltiplo de cuatro.
La existencia de posible espacio de relleno entre los miembros de una estructura implica que no puede
determinarse su tamaño mediante la suma de los tamaños de sus miembros. Por tanto, siempre se cumple:
s i z e o f ( s t r u c t Datos) >= s i z e o f ( i n t ) + s i z e o f ( c h a r )
ITES-Paraninfo • 197
Problemas re su e lto s d e C
La existencia de dichos espacios nos impiden calcular la dirección relativa al comienzo de la estructura
donde se sitúa un miembro. Por ello, en C99 se ha definido un nuevo operador: of f setof. Dicho operador
permite determinar el desplazamiento en bytes desde el comienzo de una estructura, en el que se sitúa un
miembro de la misma.
La sintaxis del operador es:
offsetof(struct X, miembroDeX)
Por ejemplo, para imprimir el desplazamiento del miembro int de la estructura struct Datos se
usará:
printf("desplazamiento de entero es %d\n",
offsetof(struct Datos, entero)) ;
► EJEMPLO 7.4 Indique cómo se pasa como parámetro por referencia una variable 1 ibro2 definida como puntero
a rechaLi bro.
Re s o l u c i ó n .
/* Definición de una variable de tipo ’FichaLibro' *!
FichaLibro *1 ibro2 = NULL;
/* Reservar memoria */
1 ibro2 = mal 1oc(siz e o f ( FichaLibro)) ;
if (1 ibro2 == NULL) ¡
printfC'No es posible reservar memoria\n");
198 • IT E S -P o ra n ir tfo
Capítulo 7 / Estructuras
/* Reservar memoria *!
i ibro3 = crearFi cha() ;
ITES-Paran'mfo m 19 9
Problemas resueltos de C
A) B)
Faso de parámetro por referencia. Paso de parámetro por valor.
Figura 7.2 Diferencias entre el paso de estructuras por referencia y por valor.
Es bastante frecuente usar conjuntamente estructuras y arrays. Como se ha visto, una estructura agrupa
los atributos de una entidad, por ejemplo los atributos título, autor, etc. para la entidad FichaLibro. Un array
de estructuras representa una lista de entidades, por lo que actúa como una pequeña tabla de una base de
datos; los identificadores de la columna son los atributos y los identificadores de fila son los índices del
array.
► EJEMPLO 7.5 Proponer una posible definición de los pisos de un edificio con oficinas, usando una array de
estructuras.
2 0 0 • ITES-Paraninfo
Capítulo 7 / Estructuras
R e s o l u c ió n .
struct Piso
int planta;
int escalera;
char letra;
En esta propuesta los pisos se identifican por la planta a la que pertenecen (número entero), la escalera
por donde se accede (número entero) y la letra asociada al piso (un carácter).
struct Piso
{
int planta;
int escalera;
char letra;
st r u c t Piso o f i c i n a s [4];
o f i c i n a s [ 3 ] .letra = 'a'
■
rt
Como se muestra en la Figura 7.3, las oficinas de un edificio se definen como un array de Cuatro elemen
tos de tipo s t r u c t Piso. Para modificar la letra del piso de la cuarta oficina, se usa la siguiente asignación:
of i ci ñ a s [3].1e t r a = ’a ’
7.3 U n io n e s
Una unión es similar a una estructura, sin embargo, los elementos que forman la unión comparten la
misma zona de memoria. Esto quiere decir que sólo tiene validez en un momento dado uno de los miembros
de la unión. La forma de definir una unión es:
unión nombreUnion
TipoDato_l miembro_l;
TipoDato_2 miembro_2;
TipoDato_N miembro_N;
Donde:
■ nombreUnion: es el nombre que se da a la unión.
■ Ti poDato_i : es el tipo del i-ésimo miembro.
■ mi emb ro_i : es el nombre del i-ésimo miembro de la unión.
Cuando se define una unión, el compilador reserva en memoria espacio para el miembro de la unión que
más ocupa.
ITES-Paraninfo • 2 0 1
Problemas resueltos de C
► EJEMPLO 7.6 Escriba un programa que defina una unión con los siguientes miembros:
int entero;
double real ;
char string[16];
R e s o l u c ió n .
#include <stdio.h>
#include <string.h>
unión Numero (
int entero;
double real;
char string[16];
int main(void)
I
unión Numero dato;
dato.entero = 8;
printfC'El valor entero de la unión es %d\n",
dato.entero);
dato.real = 8.24;
printfC'El valor real de la unión es %f\n",
dato, real );
strcpy(dato.string,"8.24132'');
printfC'El valor string de la unión es %s\n",
dato.string) ;
returní 0);
í
Cuando se ejecuta el programa se obtiene el siguiente resultado:
El tamaño de un int es 4
El tamaño de un double es 8
El tamaño de un char[16] es 16
El tamaño de la unión es 16
El valor entero de la unión es 8
El valor real de la unión es 8.240000
El valor string de la unión es 8.24132
Como podrá observar, el tamaño de la unión coincide con el tamaño del miembro más grande de la
estructura, que en este caso es de array de 16 caracteres. Cuando se utiliza el miembro entero de la unión,
2 0 2 • ¡TES-Paraninfo
Capítulo 1 / Estructuras
se accede a la unión y se interpreta el dato almacenado en la misma con un valor de tipo i nt. Sin embargo,
cuando se utiliza el miembro real, el valor almacenado en la unión se interpreta como un valor de tipo
doubl e.
En la Figura 7.4 se aprecia mejor este hecho.
.e n t e ro
{JL , .re a l / 1 -8 2 4 ^8=24^132
.s t r in g
► EJEMPLO 7 .7 Defina una unión que represente una fecha (día, mes y año) como string y permita cambiar sus
miembros y se refleje en la fecha automáticamente
R e s o l u c ió n .
struct Fecha2
char di a[2] ;
char mes[2] ;
char anyo[4]
! ;
union FechaFacil
char fechal[8];
struct Fecha2 fecha2;
1 ;
Suponga que es necesario conocer todo el historial de clientes a los que se ha prestado un libro, siendo
el primero de la lista el último usuario al que se ha prestado. De esta forma, si hay un desperfecto en el
libro, se puede averiguar quién es el responsable.
Cuando llega un cliente a por un libro, hay que apuntar sus datos y, además, indicar cuál fue el cliente
anterior. Cuando llegue el siguiente cliente, se toman sus datos (nombre, etc.) y también se apunta una
referencia al cliente anterior. Si es el primer cliente, no hay un cliente anterior.
La estructura que modela un cliente puede ser;
struct Cliente 1
char nombre[1001];
struct Cliente * anterior;
);
ITES-Paraninfo • 2 0 3
Problemas resueltos de C
li| ¡1
üí
anterior anterior anterior anterior
Este ejemplo de la lista de usuarios puede servir de modelo de otros muchos ejemplos, con los que tiene
en común algo muy importante: define una serie de individuos (clientes en este caso) de manera que cada
individuo tiene algún conocimiento de otros individuos de la lista (el anterior en este caso).
Gracias a las estructuras autorreferenciadas es posible que cada individuo exprese su relación con otros
individuos.
Intentar escribir directamente la definición de la estructura presentada en la Figura 7.6 formaría un bucle
en la definición: para definir el individuo necesita que se defina antes el grupo y para definir el grupo se
necesita definir antes el individuo.
Para resolver este problema, podemos declarar antes las estructuras, sin indicar sus miembros:
¡* Declaraciones adelantadas de las estructuras */
struct Prestamos ;
2 0 4 • ITES-Paraninfo
Capítulo 7 / Estructuras
struct Cliente ;
char nombre[l
struct Prestamos ;k grupo;
struct Cli ente * anteri or
);
E v it e est o s e r r o r es
A continuación se describen los principales errores que se pueden cometer cuando se trabaja con estruc
turas.
Olvidar usar struct en la definición de estructuras Es fácil olvidar la palabra reservada s t r u c t en la
definición o declaración de una variable. Algunos compiladores admiten el formato usado en C++ que
permite omitir dicha palabra, sin embargo, el código probablemente no compilará en otros compiladores.
Por tanto, siempre que defina una variable utilice la palabra reservada s t r u c t .
Acceder a los miembros de un puntero a estructura sin inicializar Cuando se define un puntero a una
estructura, se reserva espacio para el puntero, no para la estructura a la que apunta. Por tanto, es un error
acceder a los miembros de un puntero que no apunta a ninguna estructura.
Calcular el tamaño de la estructura a partir del de los miembros Recuerde que cuando se define una
estructura el compilador puede añadir bytes de relleno entre los miembros, por lo que el tamaño de
la estructura puede ser mayor que la suma de los tamaños de los miembros de la misma. Por tanto, el
tamaño de la estructura no tiene por qué ser igual a la suma del tamaño de sus miembros.
Utilizar el operador . con punteros a estructuras Para acceder a los miembros a los que apunta un pun
tero a estructura se ha de utilizar el operador - >, no el operador ..
Comparar estructuras No se pueden comparar estructuras entre sí. Para comprobar si dos estructuras
tienen el mismo contenido habrá de comprobarlo elemento a elemento.
P r o b l e m a s R e s u e lt o s
.I Modelice usando una estructura el tipo de datos que permita describir un CD de música.
R e s o l u c ió n .
struct Descripe ionCD
(
char ti tul oC1000];
char arti sta[1000];
char albumClOOO];
int anyo;
char generollOOO];
char comentan os[1000];
1 ;
ITES-Paraninfo • 2 0 5
Problemas resueltos de C
struct ni vel1
i
struct m'vel2 a 1 [10] ;
Suponga que se ha definido la variable prueba como de tipo s t r u c t ni vel 1. ¿Cuál de las siguientes
expresiones permite acceder al campo p 1 del primer elemento de a 1?
1. p r u e b a . a l ü l d . p l
2. p r ue ba . a l [ 0 ] ->pl
3. c r u e b a . a l [ 0 ] . pl
4. pr u e b a . a l [ 0 ] . *pl
R e s o l u c ió n .
;r _.eba .al [1]. pl /* NO, porque el índice del primer elemento es 0 */
c-_,eba.al[0]->pl /* NO, para acceder a pl no es necesario '-> ’ */
: r^eba.a 1[0].pl /* SI */
prueba.al [0].*pl !* NO, porque para acceder a pl no es necesario */
^ Definición de la estructura */
struct FichaLibro I
char ti tulo[256];
char autor[256];
char ISBNC256];
char estante[256] ;
int prestado:
char cl iente[256];
int main(void)
I
/* Lectura de los datos del libro 1 */
206 • ITES-Paraninfo
Capítulo 7 / Estructuras
if (1 ibrol.prestado == 1)
1
printf("\nIntroduzca el nombre del cliente al que se ");
printfC'le ha prestado el libro 1: ");
scanfC'Xs", 1 ibrol.eliente);
if (1 ibro2.prestado == 1)
{
printf("\nIntroduzca el nombre del cliente al que se ");
printf("le ha prestado el libro 2: ");
scanf("Xs", 1 ibro2.el iente);
1
if (
(stremp!1 ibrol.ti tul o ,1 ibro2.ti tul o) == 0) &&
(stremp!1 ibrol.autor,1 ibro2.autor) == 0) &&
(stremp!1 ibrol.ISBN,1 ibro2.ISBN) == 0)
ITES-Paraninfo • 2 0 7
Problemas resueltos d e C
return(O) ;
)
El programa comienza definiendo la estructura a utilizar en el programa, junto con dos variables: 1 ib ro 1
y 1 ib ro2. La función rilain solicita al usuario que introduzca los datos de los libros. A continuación com
para si ambos libros son iguales. Para ello se comparan todos los miembros distintivos, aunque bastaría
hacerlo con el miembro ISBN.
En este programa se pueden apreciar algunos detalles importantes:
■ Se ha definido la estructura fuera de cualquier función de forma que se pueda utilizar en cualquier parte
del programa. Si se define una estructura dentro de un bloque, sólo puede usarse esta definición en dicho
bloque y subloques que tenga anidados.
■ Para leer por la entrada estándar (el teclado normalmente) el valor de un miembro, se utiliza la función
s oanf:
scanf("%s", 1ibrol.titulo);
Como se puede observar, se usa la expresión 1ib ro 1. t it u 1o sin el operador &puesto que el miembro
tJtu" o es un string.
► 7.4 Escriba un programa que determine la distancia entre dos puntos del espacio, usando punteros.
R e s o l u c ió n .
=c‘:r¡c1ude <stdio.h>
=-"°dude <std!ib.h>
ude <string.h>
---iccl ude <math.h>
Definición del tipo ’Punto’ */
typedef
struct
float coordenada_x:
float coordenada_y;
float coordenada_z:
=m to ;
^ Definición de les variables puntol y punto2 *!
-unto *puntol, *punto2;
int main(void)
208 • I T E S - P o r a n in f o
Capítulo 7 / Estructuras
el se
free(puntol);
free(punto2); *
return(O);
1
En este programa se repasa la definición de un puntero a estructura, la asignación de un valor a dicho
puntero y el acceso a los miembros de la estructura apuntada.
I ► 7.5 Escriba un programa que indique la disposición en memoria de una estructura struct Datos formada por
dos miembros: carácter de tipo char y entero de tipo i nt.
R e s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
struct Datos
1
ITES-Paraninfo • 2 0 9
Problemas resueltos de C
char carácter;
int entero;
int main(void)
{
/* Se imprime el tamaño de los distintos tipos de datos */
printf("Tamaño tipos de datos\n");
pri ntf ("--------------------- \n");
printf("sizeof(char) = %d\n",
sizeof(char));
printf("sizeof(int) = Xd\n",
si zeof(i nt));
printf("sizeof(struct Datos) = %d\n",
sizeof(struct Datos));
p rin t f (" \n ");
return (0) ;
1
Este programa permite averiguar la forma en la que el compilador dispone los miembros de la estructura
struct Datos en el computador donde se ejecuta. Primero imprime por pantalla el tamaño de los distintos
tipos de datos y a continuación el desplazamiento de cada miembro de la estructura usando el operador
offsetof.
► 7.6 Escriba un programa que determine si dos libros tienen igual título, con punteros y funciones.
R e s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char titulo[256];
char autor[256];
char ISDN[256];
char estante[256];
int prestado;
char cliente[256];
1 FichaLibro ;
2 1 0 « ITES-Paraninfo
Capítulo 7 / Estructuras
/*
* Crea una ficha vacía
*/
FichaLibro * crearFicha ( void )
{
FichaLibro *f1ibro;
/* Reservar memoria *1
flibro = mal 1oc(sizeof(FichaLibro));
if (f1ibro == NULL) {
printfC'No es posible reservar memoria\n");
return NULL;
/*
* Borra una ficha
*/
void el iminarFicha ( FichaLibro **fichaX )
í
free(*fichaX);
*fichaX=NULL;
/*
* Lee una ficha
*/
void leerFicha (FichaLibro *libroX)
i
printf("Introduzca el titulo del libro:\n");
scanf("%s", 1ibroX->titulo);
/*
I T E S - P o r a n in f o • 211
Problemas resueltos de C
!*
* Programa principal
*/
int main(void)
1
FichaLibro *fichal, *ficha2;
return(O);
El programa es un ejemplo interesante en el que se define el tipo de datos FichaLibro y una serie de
funciones que permiten realizar las acciones de gestión de variables de dicho tipo.
Este programa añade algunos detalles importantes:
■ Se añade la función eliminarFicha que tiene un parámetro de tipo puntero a FichaLibro que se
pasa por referencia. Dicha función libera la zona de memoria apuntada y asigna NULL como valor al
puntero. Para pasar como parámetro por referencia una variable de tipo puntero aFichaLibro, hay que
definir la función como:
void elimintarFicha ( FichaLibro **fichaX ) ;
■ La función de comparación igualAutor espera dos estructuras, sin embargo el programa principal
maneja dos punteros a estructuras. Por esta razón, usando el operador * se pasan como parámetro real
las estructuras:
if (igualAutor(*fichal,*ficha2))
2 1 2 » ÍTES-Paraninfo
Capítulo 7 / Estructuras
I ► 7.7 Escriba un programa que lea la información de diez libros y determine los libros que tengan el mismo título
que el título del primer libro.
R e s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char ti tulo[256];
char autor[256];
char ISDNÜ256];
char estante[256];
int prestado;
char el iente[256];
) FichaLibro ;
int main(void)
{
i nt i ;
ITES-Paraninfo » 2 13
Problemas resueltos de C
return(O);
Al principio del programa se definen los tipos de datos a utilizar en el programa: FichaLibro, PtrFi-
a o r e y ArrPtrFi chaLi bro.
A continuación, se define la variable 1 ib r o s . La variable 1 i b r o s es un array de punteros a la estructura
r ■';- a _io ro. El tipo de dicha variable tiene tres niveles: en el primero se encuentra un array, en el segundo
un puntero y en el tercero una estructura. La siguiente figura muestra una representación gráfica del tipo de
datos junto con las dos expresiones, equivalentes entre sí, para acceder al tercer carácter del array cliente.
* (libros[O])
(*(libros[1]).cliente)[2]
(libros[1]->cliente)[2]
2 1 4 # ITES-Paraninfo
Capítulo 7 / Estructuras
En este programa se ha definido el tipo de datos de tercer nivel (la estructura F ic ha Li b ro), y el tipo de
datos de segundo nivel (el puntero a dicha estructura PtrFi cha Li bro) así como un tipo de datos que
representa un array de PtrFi cha Li bro, denominado ArrPtrFichaLibro.
■ En el momento de acceder a un valor almacenado en la variable, hay que ir seleccionando del tipo
de datos del nivel menos profundo al nivel más profundo. En la Figura 7.7 se muestran dos expresio
nes equivalentes entre sí, para acceder a un componente. Para ello, primero se selecciona el segundo
elemento de array:
1 ib ros[1]
Y como es un puntero a la estructura Fi cha Li bro, se accede al miembro cliente:
(i ibrosCl])->ciiente
Y como, a su vez, este miembro es un array de caracteres, se accede al tercer carácter:
dibros[l]->ciiente)[2]
■ Es recomendable usar los paréntesis, es decir ( y ), para separar claramente a qué expresión se aplica el
operador de acceso. Repasando dichos operadores se tiene:
• [ ] para acceder a un elemento del array.
• * para acceder al elemento apuntado por un puntero.
• . para acceder a un miembro de una estructura.
Estos dos últimos pueden combinarse usando un solo operador: - > el de acceso a un miembro de una
estructura a través de un puntero a la misma.
I ► 7.8 Diseñe y codifique un programa que lea por teclado los nombres de los clientes que han tenido prestado un
libro, empezando por el primer cliente, los almacene en un histórico e imprima dicha lista de clientes.
R e s o l u c i ó n . Para realizar el programa pedido, se comienza por la definición de los tipos de datos a
usar. La definición del histórico se basa en la definición propuesta en el apartado 7.4:
s t r u c t C1i ente (
char nombre[1001];
s t r u c t Cliente * anterior;
/*
* Definición del histórico de clientes mediante
ITES-Paraninfo * 2 1 5
Problemas resueltos de C
* Programa principal
int main(void)
Il
Histórico histórico;
Histórico nuevo;
char nombre[1001];
struct Cliente * individuo;
struct Cliente * anterior;
216» IT E S - P o r a n in f o
C a p ítu lo 7 / E stru ctu ra s
/* Se libera la memoria */
freed ndi vi dúo);
/* 5) Terminar el programa */
return (0);
► 7.9 Diseñe y codifique un programa que lea por teclado los nombres de los clientes que han tenido prestado
un libro, empezando por el primer cliente, los almacene en un histórico e imprima dicha lista de clientes,
usando esta vez una biblioteca que se encargue de la gestión del histórico.
R e s o l u c ió n . Para realizar el programa, se usan los siguientes tipos:
struct InfoCliente i
char nombre[1001];
struct InfoCliente * anterior;
struct ListaClientes {
int numeroClientes;
- struct InfoCliente * ultimo;
struct InfoCliente * primero;
ITES-Paraninfo . 2 / 7
Problemas resueltos de C
};
struct InfoCliente {
char nombre[1001];
struct InfoCliente * anterior;
struct ListaClientes I
int numeroClientes;
struct InfoCliente * ultimo;
struct InfoCliente * primero;
hi storico->numeroClientes = 0;
hi stori co->ultimo = NULL;
hi storico->primero = NULL;
void agregar
(
2 1 8 * ITES-Paraninfo
Capitulo 7 / Estructuras
/* Se reserva memoria *!
nuevo=mal1oc(si zeof(C1 iente));
if (NULL==nuevo) i
perror("mal loe:");
return;
void borrar
(
Histórico ^histórico /* por referencia *1
)
Cliente * individuo;
C1i ente * anterior;
ITES-Paraninfo • 219
Problemas resueltos de C
return individuo->nombre;
return individuo->anterior;
■'e-ee ''ultimo
return hi storico->ultimo ;
T aclama principal
i nt ■■: v o i d )
-"'sccrico histórico;
char ■'ombreClOOl];
I~-erze * indi viduo;
220 • [TESr-Paranirífo
Capítulo 7 / Estructuras
gets(nombre);
while ( s t r c m p ( n o m b r e ) != 0)
I
agregar(&hi stori co,nombre) ;
printf("Introducir el nombre del cliente\n");
printf("(Pul se Intro para terminar): ");
gets(nombre) ;
/* Terminar el programa */
borrar(&hi stori co);
return (0);
(
Como se puede ver, el código del programa principal es más fácil de entender. Además, no aparece
ningún detalle de como está definido un histórico internamente, puesto que las funciones de la biblioteca
ocultan dichos detalles.
[► 7. 10 Como se ha comentado en el Capítulo 6, no es posible usar el operador de asignación (”=”) entre dos arrays.
Por otra parte, sí es posible usar el operador de asignación entre dos estructuras del mismo tipo, de forma
que se copian los valores almacenados miembro a miembro. Si un miembro de una estructura es un array,
¿puede usarse el operador de asignación en ese caso? Si es posible, escriba un programa que lo demuestre.
R e s o l u c ió n .
/*
* Sí es posible usar el operador de asignad ón
* entre variables que son estructuras.
* Este es un programa de ejemplo que lo demuestra.
*
*/
^include <stdio.h>
ITES-Paraninfo • 2 2 1
Problemas resueltos de C
return (0):
► 7 . 1 I En una aplicación se precisa disponer de la temperatura de un deposito tanto en grados centígrados como
en grados farenheit. Para facilitar la gestión de la temperatura, se define la siguiente estructura:
s t r u c t Temperatura
int farenheit;
int centigrados;
Diseñey codifique las funciones PonerCenti grados, PonerFarenhei t, ObtenerCenti grados y Obte
ner rar en he it. Las funciones Poner. . . modifican ambos miembros de la estructura (aplicando la con
versión apropiada). Las funciones Obtener... acceden al miembro asociado.
R e s o l u c ió n .
struct Temperatura
float farenheit;
f1oat centigrados; .
“ .centigrados = centigrados;
T.farenheit = 32.0 + (centigrados * 9.0)/5.0;
T.farenheit = farenheit;
T .centigrados = (farenheit - 32.0) * (5.0/9.0);
return temp->centigrados;
222 • ITES-Paraninfo
Capítulo 7 / Estructuras
► 7.12 Para mantener la descripción de diferentes tipos de vivienda, un programador piensa en utilizar una estruc
tura por cada tipo de vivienda que él gestiona:
enum TipoVivi enda i apartamentos, chalets ) ;
struct Apartamento
i
enum TipoVivienda tipo ;
int metros_cuadrados_construi dos;
int dormitorios;
struct Chalet
í
enum TipoVivienda tipo ;
int metros_cuadrados_construi dos;
int metros_cuadrados_jardin;
int dormitorios;
int numero_cuartos_banyos;
1 ;
Para simplificar el código, el programador piensa usar una unión. Defina una unión que permita representar
una vivienda de forma genérica, donde cada miembro es de un tipo de vivienda.
R e s o l u c ió n .
unión Vivienda
I
enum TipoVivienda tipo ;
struct Apartamento ;
struct Chalet ;
► 7. 13 Diseñe e implemente un tipo abstracto de datos que permita gestionar un vector con un número variable de
elementos. La interfaz ha de permitir crear un vector, destruir un vector anteriormente creado, almacenar
un valor en una posición dada del vector y recuperar un valor de una posición del vector.
R e s o l u c ió n .
#include <stdlib.h>
typedef
struct
í
int *datos;
int numeróle 1ementos;
) Vector;
v = mal!oc(sizeof(Vector)) ;
if (v==NULL) return NULL;
v->datos=malloc(numero_elementos*si zeof(int));
¡TES-Paraninfo • 2 2 3
Problemas resueltos de C
if i7 !=NULL) I
if (v->datos!=NULL)
free(v->datos) ;
^ ”e e ( v ) ;
Pro blem a de Ex a m e n
► 7.14 Escriba, usando estructuras de datos autorreferenciadas, la definición de tipos y las funciones de gestión que
permitan modelizar la cola de una peluquería. Se aconseja definir, al menos, tres funciones: i ni ci a rCol a
que inicializa una variable para que describa una cola vacía, a La C o 1 a que inserta el nombre de un individuo
en la cola y ~_.~urno que extrae el nombre del primer individuo presente en la cola.
Re s o l u c ió n .
#include <stdio.h>
fíinclude <stdlib.h>
#include <string.h>
2 2 4 • ITES-Paraninfo
Capítulo 7 / Estructuras
struct Cola (
struct ElementoCola *ultimo;
struct ElementoCola *primero;
/* Reservar memoria *!
indi vi duo=malloc(sizeof(struct ElementoCola));
if (NULL==individuo) (
perrorC"mal1o c:");
return NULL;
return individuo;
ITES-Paraninfo • 225
Problemas resueltos de C
/* Sal ir de 1a col a *!
indi vi duo=col a ->primero;
col a->primero=i ndividuo->siguiente;
if (NULL == cola->primero) {
cola->ultimo = NULL;
I* Copiar valores */
strcpyí nombre,indi vi duo->cl iente);
/* Liberar memoria *!
free(i ndi vi dúo);
int "3'Xvoid)
T ”Rosa" va detras */
szrcpy(nombre,"Rosa");
2LaCola(&colaPeluqueria.nombre);
o-intf("siguíente: %s\n".nombre);
226 • ITES-Paraninfb
Capítulo 7 / Estructuras
/* Luego "Rosa"... */
suTurno(&colaPeíuqueri a.nombre);
printfC'el turno es de: %s\n".nombre);
return (0):
s t r u c t complejo p l , *p2;
¿Cuál de las siguientes sentencias es correcta?
1. pl = &p2;
2. pri n t f ( "%d", p l . real );
3. p r i n t f ( " %f " , p l - > i m a g i n a r i o );
4. p r i n t f ( "%f", p 2 . r e a l ) ;
7.2 ¿Cuál de las siguientes afirmaciones es cierta?
1. No se puede realizar la asignación de estructuras completas.
2. Una función no puede devolver una estructura.
3. La comparación de estructuras no es una operación permitida en C.
4. Una estructura no se puede pasar por valor a una función.
7.3 Dado el siguiente fragmento de programa:
struct estructura I
double x;
doubl e y;
í:
s t r u c t e s t r u c t u r a pl ;
s t r u c t e s t r u c t u r a component es[100];
¿Cuál de las siguientes sentencias es válida?
1. componentes[5] = p l . x ;
2. component es. x [ i ] = p l . x ;
3. component es[ 5 ] . x = pl ->x;
4. component es[5]. x = p l . y ;
7.4 Dado el siguiente fragmento de programa:
s t r u c t puntol
f l o a t x;
float y :
1:
¿Cuál de las siguientes sentencias permite definir un nuevo tipo de datos denominado punt o_t ?
1. t ype de f punto punt o_t ;
¡TES-Paraninfo » 2 2 7
Problemas resueltos de C
s t r u c t punto pu ntos[10];
i nt i ;
f o r ( i =0; i< 10; i++) í
puntos[i].x = i ;
puntos[i] .y = puntos[i] .x + 2 * i;
4. ..;2->:l
7.7 Defina una estructura para representar los distintos elementos de una instrucción en ensamblador (véase el
Capímlo 1).
7.8 Defina una estructura que permita representar información relativa a un computador.
7.9 Defina una estructura que permita representar los datos de un alumno para una asignatura dada. En una asig
natura cada alumno puede tener un máximo de 4 convocatorias y en cada convocatoria se pueden realizar
dos exámenes.
7.10 Defina una estructura que represente una punto del espacio bidimensional y escriba un programa que lea
tres puntos e indique si están alineados.
7.1 I Utilizando la estructura anterior escriba un programa que lea cuatro puntos y diga si los cuatro puntos
forman un cuadrado.
7.12 Defina una estructura que represente un número imaginario. Codifique las funciones de comparación, asig
nación. suma y resta entre números imaginarios.
7.13 .Uñada al programa desarrollado en el ejercicio anterior la funcionalidad necesaria para multiplicar y dividir
números complejos.
7.14 Escriba un programa que lea un máximo de hasta 20 fechas y las muestre ordenadas. Utilice un array para
almacenar las fechas y ordene las componentes del array a continuación.
7.15 Escriba un programa que lea los coeficientes de una ecuación de segundo grado y almacene en un vector
los valores de x e y para cien puntos equidistantes entre [—1, 1],
228 • ITES-Paraninfo
En este capítulo...
8 .1 Archivos: conceptos y operaciones
8.2 Operaciones con archivos usando
Evite estos errores
Problemas resueltos
Problema de examen
Problemas propuestos
Entrada/salida a archivos
P ro b le m a s resu eltos d e C
Este capítulo está destinado a facilitar el conocimiento de los principales aspectos de la entrada y salida a
archivos con el lenguaje C.
Para entender y conocer estos aspectos, no hay que limitarse a conocer el conjunto de funciones y tipos
de datos asociados con la gestión de archivos. Por ello, es necesario presentar el uso de dichas funciones y
tipos de datos dentro de distintos escenarios simplificados que definen las situaciones más simples en las
que se necesitan guardar los datos en un archivo.
Los aspectos a tratar en este capítulo son:
■ Presentación de los conceptos relacionados con los archivos.
« Operaciones con archivos.
■ Operaciones con los datos de un archivo.
8 .1 A r c h iv o s : c o n c e p t o s y o p e r a c io n e s
Datos
■ Archivo
Carpeta
Los datos contenidos en el archivo no pueden ser accedidos directamente. Para poder acceder a los datos
de un archivo hay que usar las funciones de la biblioteca s t di o . h en el orden correcto. Las tareas que se
llevan a cabo habitualmente con archivos son:
■ Crear un archivo.
■ Borrar un archivo.
■ Cambiar de nombre un archivo.
■ Abrir un archivo para operar con él.
■ Cambiar el puntero de la posición actual.
■ .Añadir un dato en la posición actual.
■ Leer un dato de la posición actual.
■ Cerrar un archivo, para dejar de operar con él.
Para realizar dichas tareas se cuenta con los servicios del sistema operativo y la biblioteca estándar del
lenguaje C. Usar los servicios del sistema operativo crea programas que no son portables. Si el programa ha
de ejecutarse en otro sistema operativo, es necesario volver a escribir las llamadas usadas en la gestión de
archivos. Por esta razón, se recomienda usar siempre que sea posible la biblioteca estándar del lenguaje C:
230 • ÍTES-Pcrcninfc
Capítulo 8 I Entrada/salida a archivos
U N IX /L IN U X
read ( & | i 3 )
W in 3 2
ReadFile ( Mi , | , 3 , M, NULL )
En las siguientes secciones se detalla cómo es la interfaz de la biblioteca stdi o .h y cómo se usa.
8.2 O p e r a c io n e s c o n a r c h iv o s u s a n d o stdi o . h
En la biblioteca stdi o .h se define el tipo de datos FILE * para representar un archivo. Este tipo de
datos se conoce como stream. Cuando se crea o abre un archivo se crea un stream (flujo) en el que se
almacena toda la información que necesitan las funciones de stdi o .h para poder operar.
E s c ritu ra d e d a to s L e c tu ra d e d a to s
1) A b rir 1) A b r ir
(< n o m b re del a rc h iv e » , (« n o m b re d e l a rc h iv o » ,
< c o n tro l d e a p e rtu ra > ) -> FILE* « c o n tro l d e a p e rtu ra » ) -> FILE*
^
i
2 ) S altarA (F ILE *,
i
2) S a lta rA (F IL E *,
< p o s ic ió n X>) < p o s ic ió n X>)
i i
___ 3) E s c rib ir(F iL E *, 3) LeerfFILE *,
< va lo re s> ) « v a lo re s » )
i
4) C erra r(F ILE *)
i
4) C erra r(F ILE *)
En la Figura 8.3 se muestran dos situaciones típicas: la escritura de datos en un archivo y la posterior
lectura de los mismos. Para ambas situaciones se muestran los pasos habituales, indicando de forma genérica
las operaciones que han de usarse de una biblioteca de entrada/salida (en adelante E/S) y su relación con un
stream.
En las siguientes secciones se presentan las ideas asociadas a un stream de E/S, así como las funciones
de la biblioteca s t d i o . h.
ITES-Paraninfo • 2 3 1
Probjemas resueltos de C
1 f
stream
fd__ buffer
12345] I I
- 4
} f
I 1 2 3 4 5 6 7 8 9
La razón de usar este almacén es la optimización de las pequeñas escrituras y lecturas. Un ejemplo puede
ilustrar esta razón. Suponga que realizar una escritura de un carácter cuesta 1 milisegundo y un programa
quiere realizar la escritura de 1 millón de caracteres. Si se realizan una a una estas escrituras el coste total
es de 1 millón de milisegundos, o lo que es lo mismo, casi 17 minutos. Dado que el disco está dividido en
bloques, el coste de escribir un bloque entero es poco más que el coste de escribir un único carácter en el
bloque. Suponga que el coste de escribir un bloque de 1.024 caracteres sea 5 milisegundos. Si se escribe
el millón de caracteres en bloques de 1.024 caracteres, se necesitarán escribir 977 bloques. En total 4.885
milisegundos (977 * 5), es decir, casi 5 segundos. La diferencia entre 17 minutos y 5 segundos es muy
significativa.
Aunque la gestión del almacén es transparente, es posible indicar la estrategia de gestión del buffer
asociado a un archivo en la biblioteca de C. Las estrategias disponibles son:
■ Con buffering. Con este método, se lee un bloque de datos desde al archivo al buffer cada vez que éste
está s acio. Los datos llegan a la aplicación desde el buffer del stream.
■ Con buffering de línea. Con este método, se lee un bloque de datos desde el archivo al buffer cada vez
que éste está vacío. Pero se entiende que el buffer está delimitado por el carácter de salto de línea. Los
datos llegan a la aplicación desde el buffer del stream. Esta estrategia se usa frecuentemente en archivos
de texto.
■ Sin buffering. Con este método, los datos se envían a la aplicación en cuanto llegan al buffer del stream.
Además, se leen a medida que los solicita la información.
Todo archivo abierto lleva asociado un puntero de posición que indica el lugar donde se realizará la
siguiente lectura o escritura. Cuando se crea un archivo, el puntero de la posición es cero (comienzo del
archivo). Según se escribe en el archivo, el punto donde se escribe es el mismo donde terminó la escritura
anterior, salvo que se indique una nueva posición con la función f seek.
232 • /1ES-Paraxinfo
Capítulo 8 / EntradaIsalida a archivos
Además de todo lo anterior, en el momento de abrir o crear un stream hay que indicar de qué tipo de
archivo se trata. Se definen dos tipos de stream:
■ De texto. Formados por líneas de caracteres, cada una de las cuales está formada por cero o más ca
racteres terminados con un carácter de salto de línea. La última línea puede no terminar en un salto de
línea.
■ Binarios. Formados por una secuencia ordenada de caracteres, sin separadores especiales, que pueden
almacenar cualquier tipo de información.
Hay más información asociada a un stream, como un indicador de error que indica si se ha producido
un error de E/S y un indicador de fin de archivo (EOF, End Of File) que indica si se ha alcanzado el fin del
archivo.
Cuando se inicia un programa se abren, por defecto, tres streams de texto, por lo que no deben ser
abiertos explícitamente a posteriori, so pena de obtener un error. Estos streams son:
■ Entrada estándar (s td in), que permite leer de la entrada convencional del computador, que suele ser el
teclado.
■ Salida estándar (stdout), que permite escribir en la salida convencional del computador, que suele ser
la pantalla.
■ Salida de error estándar (stderr), que permite escribir en la salida convencional de error del compu
tador, que suele ser también la pantalla, si bien en algunos casos existen otros dispositivos, tales como
impresoras, asociados a estas salidas de error.
Estos tres streams son tres variables globales que pueden usarse en cualquier archivo que incluya directa
o indirectamente la biblioteca stdi o .h.
En las siguientes secciones se presentarán las funciones de la biblioteca stdi o .h.
ITES-Paraninfo • 2 3 3
Problemas resueltos de C
el se
return(0);
234 • ITES-Parcnirrfb
Capítulo 8 / Entrada/salida a archivos
y modo especifica la forma de apertura del archivo. Los valores del campo modo junto a su comportamiento
se presentan en la siguiente tabla. El comportamiento de cualquier otro modo de apertura no está definido.
Modo Significado
"r " Abre un archivo existente sólo para lectura.
" w" Abre un nuevo archivo para escritura. Si existe el archivo se
borra su contenido. Si no existe se crea.
"a " Abre un archivo existente para añadir datos al final. Si no
existe se crea.
" r+ " Abre un archivo existente para lectura y escritura.
"w+" Abre un archivo nuevo para escritura y lectura. Si existe lo
borra. Si no existe lo crea.
" a+" Abre un archivo para leer y añadir.
En caso de error, fopen devuelve NULL indicando que el archivo no se pudo abrir. Las causas de los
fallos de apertura son variadas, pero algunas están relacionadas con el modo de apertura solicitado. Por
ejemplo, si se abre un archivo con modo " r ", se obtendrá un fallo si el archivo no existe o no se puede leer.
Otro motivo de errores viene dado por la macro FOPEN_MAX definida en el archivo s t di o . h. Esta macro
indica el máximo número de archivos que puede tener abierta una aplicación simultáneamente. Cualquier
intento de abrir más archivos devolverá un error. Otro motivo común es no tener permisos suficientes. Por
ello es conveniente revisar que el programa tiene los permisos apropiados para acceder a los archivos. Por
ejemplo, no suele ser posible crear un archivo en un directorio del sistema.
Una vez que se lean o escriban todos los datos en el archivo, hay que cerrar el archivo. Como antes se
ha indicado, cualquier archivo abierto se puede cerrar mediante la función:
resultado = f c l o s e ( d e s c ) ;
Donde r e s u l t a d o es un número entero igual a 0 si el archivo se cerró bien y distinto de cero si hubo
algún error.
I ► EJEMPLO 8.2 Cree un programa que lea desde la entrada estándar líneas con el formato ’’cantidad: %f %14s de
%20s” usando la función fscanf.
Re s o l u c i ó n .
#include <stdio.h>
int main(void)
{
int leidos:
int linea;
float cantidad;
char unidad[15], elemento[21];
char ch;
ITES-Paraninfo • 235
Problemas resueltos de C
; e c : (stdi n ,NULL);
if (Ifeof(stdin))
I
/* Imprimir error */
fprintf(stdout, "linea %d no sigue el formato\n",1 inea);
else
/* Imprimir valores */
fprintf(stdout, "1eidos=M\n", leídos);
fprintf(stdout, "cantidad=%f\n", cantidad);
fprintf(stdout, "unidad=%s\n", unidad);
fprintf(stdout, "elemento=%s\n", elemento);
;a-cJc5;=l.000000
es:=12345678901234
e_e-e-.-c=12345678901234567890
2 3 6 • ITES-Paraninfb
Capítulo 8 / Entrada!salida a archivos
Para escribir datos con formato en un stream se usa la función fp rint f, que tiene el formato siguiente:
escritos = fprintf(descriptor, formato, ...);
Todos los formatos y opciones de la función printf son válidos para fprintf, pero aplicados a un
stream.
La función fpri ntf devuelve el número de elementos escritos de los especificados en el formato, o
un número negativo si se produce un error de escritura. En caso de que se produzca un error de escritura
devuelve un valor negativo.
► EJEMPLO 8 .3 Cree un programa que lea desde la entrada estándar, líneas con el formato ’’cantidad: %f %14s
de %20s” usando la función fscanf y escriba en la salida estándar esta información usando la función
fprintf.
R e s o l u c ió n .
#include <stdio.h>
int main(void)
{
int leidos, escritos;
float cantidad;
char unidad[15], elemento[21];
/*
* Bucle para leer hasta que se alcance el fin
* de la entrada (EOF) o se produzca un error.
*/
do
/* Lectura. de datos */
leidos = fscanf(stdin,
"cantidad: %f %14s de %20s",
Scantidad, unidad, elemento);
fscanf(stdin, "%*[~\n]");
if (leidos < 0)
(
fprintf(stderr, "Error de lectura en el stream stdin\n");
/* Escritura de datos */
escritos = fprintf(stdout,
"cantidad: %f %14s de %20s\n",
cantidad, unidad, elemento);
if (escritos < 0)
I
fprintf(stderr, "Error de escritura en el stream stdout\n");
return(O);
1
El Programa anterior ilustra el uso de otras dos funciones disponibles para la E/S con streams:
res = feof(stream);
res = ferror(stream);
ITES-Paraninfo • 2 3 7
Problemas resueltos de C
Para gestionar el bujfer asociado a un stream se ha de usar una de las siguientes funciones:
t;::.' stream, buf):
'es = setvbuf(stream, buf, modo, tamanyo);
.Ambas permiten definir el buffer de memoria que se quiere usar, en lugar del asignado por defecto en
la biblioteca so si o . h. Ambas funciones han de usarse después de abrir (o crear) el archivo pero antes de
realizar cualquier operación sobre el archivo.
Es importante asegurar que el buffer de memoria que se asocia al archivo esté disponible hasta después
del cierre del stream. Hay que tener presente que tanto si se indica un buffer como si se usa el asignado por
defecto en la biblioteca s t d i o . h, se usa memoria dinámica. Por tanto, un fallo en la gestión de memoria
puede dar como resultado archivos cuyo contenido no sea el esperado.
La función s e t buf se define como:
icid ;stbuf( FILE * fp, char * buf );
siendo fp el descriptor del stream al que se quiere cambiar el buffer. El parámetro buf puede ser bien NULL
para indicar que no se quiere usar buffer intermedio, o bien la dirección de memoria a usar como buffer. El
tamaño de dicha zona hadeserBUFSIZ, que es una constante definida en s t di o . h.
La función s e t v b u f s e define como:
238 • ÍT E S -P ú ra rm ^
Capítulo 8 / Entrada/salida a archivos
void setvbuf ( FILE * fp, char * buf, int modo, size_t tamaño );
Igual que para set buf, fp el descriptor del stream al que se quiere cambiar su buffer intermedio. El
parámetro tamaño indica el tamaño del buffer a usar. Si el valor del parámetro buf es N U L L, entonces la
función setvbuf se encarga de pedir la memoria. Si por el contrario, el valor del parámetro buf no es
NULL, se entenderá como la dirección de comienzo de la zona de memoria cuyo tamaño viene dado por el
parámetro tamaño.
Además, la función setvbuf permite definir el modo de buffering a aplicar, existiendo tres posibles
valores:
■ _10 FBF para buffer completo.
■ _10 LB F para buffer completo en base a líneas.
■ _10NBF para no usar buffer.
El resultado de la llamada es 0 si todo ha ido bien y EOF si ha habido algún error.
Ambos parámetros son importantes si se quieren efectuar optimizaciones en la E/S de un stream. Por
ejemplo, para archivos de texto es posible usar un modo de buffering como el _10 LB F con un buffer de
tamaño 100, puesto que las líneas en los archivos de texto no suelen superar los 80 caracteres. Por el
contrario, si desea leer del archivo una imagen de 24 KB de golpe, es posible que no necesite usar un buffer
intermedio, indicando para ello el valor riONBF en el modo de buffering.
En caso de que se use el buffer para el almacenamiento intermedio de datos cuando se escribe sobre un
stream y se quiera escribir los datos del buffer en disco, ha de usar la función:
res = ff1ush(stream);
Esta función envía todos los datos pendientes de escritura al sistema operativo para que sean escritos
lo antes posible. Si stream tiene un valor NULL, la llamada a esta función envía los datos de los buffers de
todos los streams que tiene abierta la aplicación al sistema operativo. El resultado de la llamada es 0 si todo
ha ido bien y EOF si ha habido algún error.
En algunas aplicaciones se escribe muy poco al buffer, por lo que tarda mucho en llenarse y escribirse a
disco. Con la función f f 1 us h se está seguro de que no se queda ningún dato sin escribir en el disco, puesto
que dicha función fuerza la escritura de los datos y el vaciado del buffer.
► EJEMPLO 8 .4 Cree un programa que copie los caracteres que lea de la entrada estándar hacia la salida estándar,
asegurándose de que los caracteres son escritos, como mucho, cuando la cantidad de éstos llegue a 10.
Dicho programa ha de modificar el almacén asociado al stream de la salida estándar para que tenga una
capacidad de 256 caracteres.
R e s o l u c ió n .
#include <stdio.h>
int main(void)
1
char car;
int i :
char buffer[TAM_BUFFER];
int resultado ;
ITES-Paraninfo • 239
Problemas resueltos de C
if {(i % 10)== 0)
resultado = fflush(stdout);
if (resultado != 0)
fprintf(stderr, "Error al volcar el buffer\n");
return j );
ITES-Paraninfo • 2 4 1
Problemas resueltos de C
Para elegir una de las estrategias es preciso analizar qué es mejor globalmente. En general:
■ Escribir campo a campo permite intercambiar datos usando archivos entre distintos programas en dis
tintas plataformas.
■ Escribir toda la estructura es más eficiente para guardar datos temporales que puede usar una aplicación.
► EJEMPLO 8 .5 Cree un programa que guarde una estructura Fi chali bro en un archivo para poder leerla poste
riormente utilizando la primera estrategia.
R e s o l u c ió n .
#1 nclude <stdio.h>
#irelude <string.h>
T r'.zión de la estructura *1
struct EstructuraFichaLibro {
string[i]=getc(descriptor);
2 4 2 • ITES-Paraninfb
Capítulo 8 / Entrada/salida a archivos
int main(void)
I
FILE *descriptor;
int longitud;
/* 3) Al final, se cierra */
fclose(descri ptor);
/* 3) Al final, se cierra */
ITES-Paraninfo • 243
Problemas re su e /to s d e C
tseidescriptor) ;
-stsrnlC);
I ► EJEMPLO 8.6 Cree un programa que guarde una estructura Fi cha Li bro en un archivo para poder leerla poste-
don-ente utilizando la segunda estrategia.
Re s o l u c ió n .
d ' i u C e <std1o.h>
cea < stri ng.h>
;'í- vcu1o[256];
c-s- s_,cor[256];
ISDN[256];
z'tr escante[256];
--n ;-sstado;
z~ 'ente[256];
void)
rI_E -descriptor;
2 4 4 • /TcSJñ-r-
Capítulo 8 / Entrada!salida a archivos
/* 3) Al final, se cierra */
fclose(descri ptor);
/* 3) Al final, se cierra */
fcloseídescriptor);
return(O);
E v it e es t o s er r o r es
A continuación se describen los principales errores que se pueden cometer cuando se trabaja con entrada
y salida en C.
No comprobar los posibles errores al abrir un archivo Es habitual que al crear o abrir un nuevo archivo
aparezcan errores distintos en cada ejecución. Estos errores pueden ser debidos a muchas cosas, como
que no exista el archivo, que la ruta no sea correcta, etc. Es importante tenerlo en cuenta para que el
programa sea capaz de tratar estos inconvenientes.
ITES-Paraninfo • 2 4 5
Proù>er-cs rs&rssüx de C
No comprobar el final de archivo en las lecturas Cuando se realiza la lectura de un archivo, es obligatorio
ccmrrobar si se alcanza el final del archivo. Si no se hace esto, los resultados obtenidos pueden no ser
No cerrar los archivos cuando no se utilicen Aunque cuando un programa finaliza se cierran todos los
archivos abiertos, es conveniente cerrar los archivos al finalizar su uso. Conviene recordar que existe un
sepe rara los archivos que pueden estar abiertos al mismo tiempo, y si no se cierran, eso puede dar lugar
a errares en el funcionamiento del programa.
No comprobar que el formato del archivo es correcto Las funciones de lectura con formato (como
- e ; ='-■! requieren que el archivo siga las directrices del formato dispuesto. Si esto no es así, las funcio
nes correspondientes indicarán dicho fallo. Si no se está absolutamente seguro del formato del archivo,
es necesario comprobar los resultados devueltos por las funciones de lectura con formato. De esta forma
se roeos detectar y notificar los errores de formato que pueda tener el archivo.
Realizar operaciones en un archivo antes de abrirlo o después de cerrarlo Como es lógico pensar,
beata el momento de abrir el archivo el st ream asociado a él no existe, y una vez cerrado el archivo, el
st ' e s~ desaparece. Por tanto, realizar operaciones en un st re am antes de abrir el archivo o después de
cerrarlo conducirá inevitablemente a errores en el programa.
Utilizar un buffer de lectura de tamaño menor que el tamaño de lectura deseado Cuando se realizan
jactaras directas con la función f read, hay que vigilar que el dato que se quiere leer quepa comple
tamente en el buffer utilizado a tal efecto. Si éste no es el caso, el dato será truncado por lo que dará
Trecientas cuando vaya a ser usado.
Incrementar el tamaño del archivo realizando una búsqueda Existen programadores que pretenden
lamentar el tamaño de un archivo utilizando la función f s e e k para acceder más allá del final de archivo.
Sen embargo, este hecho no aumenta el archivo hasta que no se realiza una escritura en esa nueva
reste::" del archivo.
Re s o l u c ió n .
staio. h>
- -n "s ■- , : i d )
•'t "esLcado;
" J s m d de nombre */
m : d t s : : = renarne (". /ej empi o .txt ", "./ejempl o_l .txt" ) ;
-- ms brado != 0)
int res;
return(0);
I ► 8.3 Cree un programa que abra y cierre un archivo y escriba 1.000 veces la letra ”a” en filas de 80 caracteres.
R e s o l u c ió n .
#include <stdio.h>
int main(void)
1
FILE *desc;
char letra;
int num;
/* 3) Al final, se cierra */
fclose(desc);
ITES-Paraninfo • 2 4 7
Problemas resueltos de C
/* 3) Al final, se cierra */
fclose(desc);
return(O);
int main(void)
-1LE *fent;
-ILE *fsal;
char car;
-eturn 10);
► 8.5 Cree un programa que copie un archivo en otro usando funciones de E/S de caracteres.
248 • ÍT E S -~ ~ -.-é
Capítulo 8 / Entrada/salida a archivos
R e s o l u c ió n .
#i n c l ude <stdio.h>
int main(void)
i
FILE *fent;
FILE *fsal;
int car;
int resultado = 0;
return(O);
► 8.6 Cree un programa que copie un archivo en otro usando funciones de E/S de líneas de caracteres.
R e s o l u c ió n .
#include <stdio.h>
#include <string.h>
int main(void)
I
FILE *fent;
FILE *fsal;
char car[120];
/TES-Paraninfo • 2 4 9
Problemas resueltos de C
char ^datos;
int -es = 0;
I ► 8.7 Cree en rrcgrama que escriba 10 veces la letra ’a’ dejando espacio suficiente entre cada par de letras
Re s o l u c ió n .
se ssdio.h>
--s - a_ - .oid )
/* 3) Al final, se cierra */
fclose(desc);
return(0);
)
El programa anterior escribe diez veces la letra ’a ’, dejando espacio para escribir 5 letras entre cada
par de letras ’a ’. Para ello, usa la función f seek, saltando 6 posiciones (5 de relleno y 1 donde se escribe
la siguiente letra ’a ’).
► 8.8 Cree un programa que escriba, lea y almacene en un archivo, un vector de seis elementos de tipo int.
R e s o l u c ió n .
#include <stdio.h>
/* Definición de la estructura */
int vectorlH = { 1, 3, 5, 7, 9, 11 1;
int vector2[6];
IT E S - P o r a n in f o • 251
Problemas rasu efess as C
t J final, se cierra */
:se sescriptor);
^ I ~inal, se cierra */
-z~ : se :escri ptor);
scdout, "\n");
I ► 8 ,9 F y —'V. — p^-.cTOTnn que lea el texto contenido en el archivo ’’entrada.txt” y escriba dicho contenido, pero
szsuaayende la aparición del carácter tabulador por cuatro espacios en blanco consecutivos.
Re so lu c ió n .
#-':',:e scrio.hd
" t _ ■ . ci d)
252 .
Capítulo 8 / Entrada/salida a archivos
el se í
return(O);
► 8. 10 Escriba un programa que lea el texto contenido en el archivo ’’entrada.txt” y escriba dicho contenido, pero
sustituyendo la secuencia de espacios en blanco consecutivos por un solo espacio en blanco.
R e s o l u c ió n .
#include <stdio.h>
int main(void)
i
FILE *fentrada;
FILE *fsal ida ;
int car;
int resultado;
int inicio_secuencia;
ITES-Paraninfo • 2 5 3
Probienxs r e s p e t e s d e C
if (inicio_secuencia = 1)
ini ci o_secuenci a=0;
if (inicio_secuencia == 0) j
ini ci o_secuenci a=l;
~ se los streams */
z :ze -entrada);
:se -'ss' ida) ;
!► 8 . 1 I Escribe u r urograma que lea el texto contenido en el archivo ’’entrada.txt” y escriba por la salida estándar
el 2U35íro ¿e veces que aparecen las palabras según su longitud (con un máximo de 10). Así, por ejemplo,
la sabes, para un archivo sería:
' : : s ^ ^ e n c i as
254 • ÍT E S ^ ts ’-js t r / i
Capitulo 8 / Entrada/salida a archivos
GO
3
4 4
LO
CO
6 6
7 7
4
co
9 4
10 0
R e s o l u c ió n .
#include <stdio.h>
^include <string.h>
int ocurrencias[10] ;
char palabra[1000];
int main(void)
í
FILE *fentrada;
int len;
int i•
/* Apertura del archivo entrada.txt */
fentrada = fopen(/entrada.txt", "r");
if (fentrada == NULL)
(
fprintf(stderrError: el archivo \ "entrada .txt\" no\n");
fprintf(stderr," pudo abrirse para lecturaVn");
return(O);
1
/* Iniciar el número de ocurrencias */
for (i=0; i<10; i++)
ocurrenci as[i] = 0;
/* Contar pa labras *1
while (fscanf(fentrada, "XIOOOs", palabra) != EOF)
(
len = strlen(pal abra) ;
ocurrencias[len] ++ ;
1
/* Cierre de los streams */
fclose(fentrada);
/* Imprimir resultados *!
for (i=0; i<10; i++)
printfC'Xd -> %d\n", i ,ocurrencias[i]);
return(O);
► 8. 12 Escriba un programa que lea el texto contenido en el archivo ’’entrada.txt” y escriba dicho contenido, pero
cambiando las letras mayúsculas a minúsculas y viceversa.
R e s o l u c ió n .
#include <stdio.h>
#include <ctype.h>
¡TES-Paraninfo • 2 5 5
Problemas resueltos be C
-i -e:.'isdo = O ;
- ce 7 . -e d e l a r c h i v o e n t r a d a . t x t */
-e'C'rie = Í3pen(/entrada .txt", "r");
. == NULL)
; a r > ’a ’) | | ( c a r < ’z ’) )
-esultado = fputc(toupper(car), fsalida);
- ; e e ce l o s s t r e a m s *!
- : :se -‘e-c "ada);
-: : s e - s e ' 'Ida);
!► 8.13 En el sirciente orosrama se han cometido una serie de fallos. Señale dichos fallos justificando su respuesta
^ce s: ;i o . h>
— 7 -a"' ;oid ;
2 5 6 • ITES-Pc-
Capitulo 8 / Entradalsalida a archivos
if (fent == EOF)
(
fprintf(stderr, "Error abriendo entrada,txt\n");
return(O);
elose(fent);
elose(fsal);
return(O);
!
Re s o l u c ió n .
#include <stdio.h>
int main(void)
I
FILE *fent; /* -> FILE * y no FILE solamente */
FILE *fsal;
char c a r;
IT E S - P a r o n in f o »257
Problemas resueltos de C
'---(fsal, " % c " , EOF) ;*/ /* -> no hay que escribir el EOF *!
retiirn .C ) ;
int
int -'■ '''eas;
^ de lectura */
- :a" == ’\n ’)
•■‘■'neas ++ ;
^ o f - e e i r el número de lineas */
'-i.~ ero de lineas: %d\n", ni ineas);
!► 8 .15 Escriba un programa que reciba como argumentos el nombre de un archivo y una palabra y contabilice el
número de veces que aparece dicha palabra (aunque sea como parte de otras) en el archivo.
Re s o l u c i ó n .
#ir.c'uae <;ooJo.h>
:rar ^-nombre;
258 • ITES-Pornntfc
Capítulo 8 / Entradat'salida a archivos
FI LE * f d e s c r i p t o r ;
ehar * p a l a b r a ;
char car;
int i , nveces;
/* 4) Bucle de lectura */
nveces = 0 ;
i = 0 ;
car = f g e t c C f d e s c r i p t o r ) ;
do (
car = f g e t c ( f d e s c r i p t o r ) ;
i ++ ;
if ( p a l a b r a [ i ] == ’ \0 ’ ) j
nveces ++ ;
break;
i = 0 ;
) while (c ar != EOF) ;
ITES-Paraninfo • 2 5 9
Problemas resueltos de C
E I m p r i m i r e l número d e o c u r r e n c i a s */
z r ' r z~ ("húmero de o c u r r e n c i a s : X d \ n " , n v e c e s );
ret u r n i l } ;
Pro blem a de Ex a m e n
!► 8 . 16 Escriba un programa que concatene el contenido de dos archivos de texto en uno solo. El nombre de los tres
archivos (dos de entrada y uno de salida) se pasa como argumentos al programa.
R e s o l u c ió n .
#include < s c d i o . h >
if (arec != 4) (
fpr-ntf(stderr,
"Uso: Xs < e n tr a da l > <entrada2> < s a l i d a > \ n " ,
a r g v [0] ) :
r e tu rn ( 0 ) ;
/*• A c e - t u r a d e l a r c h i v o e n t r a d a l */
fentradal = f o p e n ( a r g v [ l] , " r " ) ;
i f ( f e n t r a d a l == NULL)
f
f p r in t f ( s t d e r r ,"Error: el a r c h i v o V ' X s V ’ n o \ n " ,a r g v [ l ] );
f p r i n t f ( s t d e r r ," pudo a b r i r s e para l e c t u r a \ n " ) ;
return(O);
2 6 0 • ITES-Paraninfo
C a p ítu lo 8 /
E n tra d a / sa lid a a a rch ivos
/* Copia a s a1ida */
while ((car = fgetc(fentradal)) != EOF)
(
resultado = fputc(car, fsalida);
if (resulfado == EOF)
fprintf(stderr, "Error: al escribir ’%c ’\n", car);
)
while ((car = fgetc(fentrada2)) != EOF)
(
resultado = fputc(car, fsalida);
if (resultado == EOF)
fprintf(stderr, "Error: al escribir ’%c’\n", car);
fclose(fentradal);
fclose(fentrada2);
fclose(fsalida);
return(O);
ITES-Paraninfo • 261
Problemas resueltos de C
<Hl>Hola mundo...</Hl>
</HTML>
Se imprime por la salida estándar:
Hola mundo...
8.6 Escriba un programa que elimine los comentarios de un programa escrito en C almacenado en un archivo.
8.7 Realice un programa que compare el contenido de dos archivos de texto e imprima las líneas donde se
encuentren diferencias.
8.8 Construya un programa para una biblioteca que solicite del usuario el título, autor y editorial de un libro, e
introduzca los datos en un archivo, Además, cuando el usuario lo solicite el programa realizará un listado
con los datos de cada uno de los libros almacenados en el archivo,
8.9 Dado un archivo, realice un programa que devuelva el número de ocurrencias de una palabra determinada
en dicho archivo.
8. 10 Escriba un programa que dado un archivo de texto, permita imprimir por la salida estándar las palabras de
todas las líneas que ocupan una posición dada. Así, por ejemplo, dado un archivo con el siguiente contenido:
uno dos tres cuatro
cinco seis siete ocho
nueve diez once doce
Si se pide imprimir las palabras que ocupan la segunda posición, la salida será:
dos
seis
di ez
8.11 Redacte un programa que lea un archivo binario y realice un volcado de su contenido, mostrando, en hexa-
decimal, el valor de cada byte leído.
8 .12 Construya un programa que, dado un archivo binario, devuelva el número de bytes que contiene.
8.13 Realice un programa que copie un archivo y que al tiempo inserte otro archivo distinto dentro del primero
en una posición determinada.
8 . 1 4 Escriba un programa que muestre por pantalla el contenido de un archivo, a partir de una determinada
posición.
8.15 Escriba un programa que intercambie el orden de los bytes de un archivo sin realizar ninguna copia del
archivo.
2 6 2 • ¡TES-Paranínfo
En este capítulo...
A. I Entorno de programación en LINUX/UNIX
A.2 Visual C de Microsoft
Entornos de
program ación
en Lenguaje C
iHMHHNHflIÉriHflNNk
Entornos de programcáón en Lenguaje C
Programar aplicaciones en lenguaje C supone conocer y usar las bibliotecas de referencia del lenguaje y
los entornos de programación más frecuentes de dicho lenguaje. Para construir una aplicación usando el
lenguaje C es necesario indicar en los programas los archivos con:
* La definición de prototipos y tipos de datos, por ejemplo < s td i o . h>, para que puedan compilarse los
módulos del programa, así como el directorio, o directorios, en que se encuentren.
* Le s archivos de bibliotecas del compilador que deben enlazarse con la aplicación para crear un archivo
ejecutarle, incluyendo el camino donde localizar dichas bibliotecas.
« Las opciones de compilación que deben activarse para compilar y enlazar la aplicación.
Cada compilador proporciona un entorno de programación, más o menos integrado, para simplifi
car el proceso de construcción del software. Dentro de este entorno existe habitualmente un proyecto, o
~ a • e ~ ~V . que almacena la información que especifica cómo compilar y enlazar una aplicación. En es
te apéndice se muestra brevemente el entorno de programación en el sistema operativo Windows y en
UNIX'LINUX. Ambos incluyen herramientas para desarrollar programas, pero el nivel de integración de
las mismas suele ser muy distinto. Los entornos que se estudian en este apéndice corresponden al compila
dor de Visual C. de Microsoft, y al compilador gee del lenguaje C para UNIX/LINUX.
Como ejemplo de trabajo, se utilizará una versión simplificada del programa re í oj (véase el Programa
A), un programa avanzado que permite cambiar la interrupción del reloj en un computador personal con el
sistema operativo MS/DOS.
ino m ~
uro- (0);
r_msg(&msg);
: : ' S 0 ’U.i
: :-_03sk (&msg);
En la Figura A.l se muestra la estructura de los módulos del programa reloj. Como se puede ver, está
compuesto t>:>: mes módulos: reloj .c, clock_task.c y hardware.c.Nose explica más en detalle lo que
2 6 4 • ITES-Paraninfb
A. I. E N T O R N O D E PR O G R A M A C IÓ N E N L IN U X / U N IX
contiene cada módulo, porque no es relevante para este Apéndice. Sin embargo, sí es interesante conocer
cómo está organizado el código fuente y de apoyo de la aplicación:
■ El directorio donde están los archivos fuente es $HOME en el caso de LINUX y
c:\jesus\docencia\sos2\apendi ce2\re1oj
en el caso de Windows.
■ Los archivos de código fuente *. c y los de cabecera *. h están en ese directorio.
■ Dentro de ese directorio hay un directorio apoyo donde se almacena código fuente compilado de ayuda
(*. o) y la bibliteca de apoyo (1 iba poyo) cuando se cree.
A. 1 Entorno de p r o g r a m a c ió n en LINUX/UNIX______
Para editar los programas se puede usar cualquiera de los editores de texto disponibles en LINUX, como
vi o emacs. Los programas deben almacenarse en archivos con cualquier nombre, pero con extensión . c.
Para compilar un archivo fuente único o varios que no necesiten muchas bibliotecas, se puede usar el
compilador gcc. Suponga que se quisiera compilar el programa del archivo hol a .c. Basta con situarse en
la línea de mandatos y teclear:
gcc hola.c
Si todo va bien, el compilador genera un ejecutable que, por defecto en LINUX y UNIX, se llama
a .out. Si se quiere que el ejecutable generado se llame como el archivo fuente, basta con usar la opción - o
de la forma siguiente:
gcc hola.c -o hola
Si obtiene errores, hay que editar el archivo fuente e irlos eliminando. ¿Qué ocurre si hay errores de
ejecución? Se puede compilar el programa con la opción - g para incluir los símbolos del depurador y a
continuación depurar el programa ejecutable, usando el depurador gdb, de la forma siguiente:
gcc -g hola.c -o hola
gdb hola
Para saber más del gcc y el gdb puede recurrir al manual del sistema operativo: man gccyman gdb,
respectivamente.
Algunos mandatos internos del gdb son:
■ run Arranca la ejecución del programa.
■ brea k Establece un breakpoint (un número de línea o el nombre de una función).
■ 1 is t Imprime las líneas de código especificadas.
■ p rint Imprime el valor de una variable.
■ continué Continúa la ejecución del programa después de un breakpoint.
• next Ejecuta la siguiente línea. Si se trata de una llamada a función, la ejecuta completa.
■ step Ejecuta la siguiente línea. Si se trata de una llamada a función, ejecuta sólo la llamada y se para al
principio de la misma.
ITES-Paraninfo • 2 6 5
Entornos de programación en Lenguaje C
.SUFFIXES:
.SUFFIXES: ,c $(SUFFIXES)
CC = gcc
DIR_AP0Y0 = ./apoyo
#
# La variable CFLAGS especifica dónde encontrar
# los archivos a incluir desde el material de código.
# La siguiente regla le indica a (\tt makel cómo procesar los archivos con
# extensión c. Normalmente esto no es necesario porque la extensión
# .c está definida para make.
.c .o :
$ CCC) $(CFLAGS) -c $<
#
# La variable LDFLAGS especifica dónde encontrar
2 6 6 • ITES-Paraninfo
A. I. E N T O R N O D E PR O G R A M A C IO N E N U N U X / U N IX
# los archivos de las bibliotecas. Las bibli otecas•del sistema se incluyen por
# defecto.
LDFLAGS=-L$(DIR_A POYO)/I ib
#
# La variable LIBS especifica al compilador qué bibliotecas
# de archivos objeto se deben usar para construir la aplicación, además de las
# del sistema.
#
LIBS= -1 reí oj
#
# La variable OBJS especifica al compilador qué archivos
# objeto se deben crear para construir la aplicación.
#
#
# La siguiente regla especifica cómo construir
# el ejecutable del programa, asi como las dependencias
# de archivos #include C.h )
#
reloj: $(OBJS)
$(CC) $(OBJS) $(LDFLAGS) $(LIBS) -o reloj
reloj.o: reloj.h
elock_task.o : reloj.h
hardware.o: reloj.h
elean:
rm -f *.o reloj
¡TES-Paraninfo • 2 6 7
Entornos d e programaáón en Lenguaje C
La segunda línea define una variable que informa al cargador de UNIX, 1d, dónde debe buscar los archivos
de biblioteca. Además de buscar en este directorio, el compilador siempre busca en los directorios del
sistema, que suelen ser /usr/1 ib y sus subdirectorios. La variable LIBS especifica al compilador qué
bibliotecas de archivos objeto se deben usar para construir la aplicación.
5 ; C ) $ (CFLAGS) -c $<
EEES=',e'oj .o elock_task.o hardware.o
Define la regla de dependencia implícita que especifica que un tipo de archivo se construye a partir de
otro y describe cómo realizar su construcción. La variable OB JS con los nombres de los archivos objeto que
forman la aplicación. En este ejemplo, las líneas anteriores especifican que los archivos . o se construyen a
partir de los archivos . c mediante el compilador de C. La aplicación reloj tiene tres módulos: reí oj .o ,
; ’ :: • t s . : y hardware.o. Para una aplicación diferente, habría que modificar esta línea para asignar
a IEEE los módulos objeto de dicha aplicación.
El siguiente conjunto de líneas es el corazón del ma kef i1e. Estas líneas son reglas de dependencia que
especifican cómo construir la aplicación. Por ejemplo, las siguientes líneas:
S(OBJS)
5 ;I l(OBJS) $ CLDFLAGS) $(LIBS) -o reloj
del ~= - e - J ' e de reí o j especifican que re í oj depende de la variableOBJS, que tiene el valor reí o j . o,
el gc>_0 5£ • : y hardware.o. Además, depende de LIBS, que tiene el valor 1 i brel o j . a. Si cualquiera de
estos archivos objeto es más reciente que re í o j , ma ke ejecuta la línea de mandato que produce una nueva
versión de ■ ‘ e ’ más reciente que los archivos objeto. Si todos los archivos objeto son más antiguos que
r e l e j , significa que r e l o j está actualizado y, por lo tanto, no se necesita realizar ninguna acción. Después
de que se hayan construido todos los archivos objeto necesarios, su fecha de actualización será más reciente
que la de - e l ; j . por lo que se ejecuta el mandato que construye r e 1o j . La lista completa de opciones del
programa - o se puede obtener mediante el siguiente mandato:
Para ejecutar una aplicación en UNIX basta con teclear el nombre de la aplicación en el prompt del
intérprete de mandatos.
A .2 V is u a l C de M ic r o s o f t
Este compilador proporciona un entorno de programación y desarrollo totalmente integrado. A través
del mismo se puede acceder a todas las utilidades necesarias para escribir, compilar y probar un programa.
En la Figura A.2 se muestra la ventana principal del compilador Visual C de Microsoft. En la parte superior
de la ventana hay un menú con mandatos tales como File, Edit, View y Help. Debajo de los elementos
del menú hay una barra de herramientas con varios botones, que proporcionan acceso rápido a muchos de
los mandatos usados frecuentemente: open, close, help, etc. Se puede obtener ayuda de cualquiera de las
características del IDE ejecutando el mandato Help.
El primer paso para crear un archivo de proyecto para una aplicación de Sistemas Operativos es crear
un espacio de trabajo y un proyecto, mediante el menú: File-^New. Dentro de la caja de diálogo New,
debe especificarse el tipo de aplicación que se está construyendo. La elección correcta depende de que la
aplicación requiera una consola. Cualquier aplicación que acepte entrada de teclado del usuario mediante
el objeto de iostream ci n o escriba en la pantalla mediante el objeto de iostream cout requiere una
consola. Para este tipo de aplicación, la selección apropiada es Win32 Console Application. Si la aplicación
no utiliza la biblioteca iostream, la selección apropiada es Win32 Application.
El paso final es especificar la posición del proyecto. En el ejemplo, se quiere que el proyecto resida en
c:\jesus\docenci a\sos2\apendi ce2\reloj
por lo que habrá que navegar hasta
c:\jesus\docencia\sos2\apendice2\reloj
2 6 8 • ITES-Paraninfo
A.2. VISUAL C D E M IC R O SO F T
Det-jyc,« Perm
. Set A afveanftgtíatitsñ,.,
){ p o s ib ií
,tf("l - Poner Alarida Mi’’);
'ConBgwiÉons... :ntí("2 — Borrar Alarma '--n");
.ntf("3 - Poner Ciclo Mi");
mtf("4 — Borrar Ciclo Mi");
print£!"5 — Leer hora de la aplicación Xn“);
p n n t £ (" 6 — Cambiar hora de la aplicación 'Mi");
printf("? - Fijar tick de reloj de la aplicación Mi");
printf('’8 — Fijar el vector de interrupción del reloj Mi");
printf("9 — Salir Mi");
scanf ("y,d Mi", ¿opcion).
< p u ls a d a , e j e c u t a r a c c ió n . c o r r e s p o : id ie ¡
i£ (opcion == 9)
exit(O);
else {
construir_msg(tmsg);
clook_task (&insg);
>
.... .
Luí__¡
y teclear reloj en el campo Project ñame:. Para crear el proyecto, se debe pulsar el botón OK.
Para compilar el archivo re 1oj .c sólo hay que indicarlo en la opción Build, que se muestra en la figura
A.3.
El compilador de Visual C de Microsoft almacena la información de cómo construir una aplicación en
un archivo de proyecto. Los archivos de proyecto tienen la extensión .dsp. En el caso del ejemplo, habría un
nuevo archivo denominado r e l o j .dsp. Para salvarlo en un archivo de proyecto, se usa el menú: F ile^S a ve
WorkSpace
Después de crear el archivo de proyecto, inicialmente el proyecto está vacío y habrá que añadirle los
archivos fuente del proyecto. El programa de ejemplo, reí oj, consta de varios módulos fuente: reí oj.c,
clock_task.cy hardware.c. Además, como material de apoyo se supone que se proporciona una biblio
teca y un archivo de definición de estructuras de datos (*. h).
Para activar la caja de diálogo para añadir archivos al proyecto, se ejecuta el mandato: Project—>Add to
Project^- Files. Para poder compilar y enlazar el proyecto, es necesario definir correctamente las opciones
del proyecto para poder encontrar los archivos a incluir y las bibliotecas que se van a usar. Esto se consigue
mediante el mandato: Tools^Options. Para ello, se ejecuta el menú Show directories fo r —>• Inelude files,
y se teclea la ruta en la ventana Directories. En el ejemplo del reloj, esta ruta es
c:\jesus\docencia\sos2\apendice2\reloj\apoyo
Para compilar y enlazar la aplicación, se usa el menú: Build—>Build reloj.exe. Este mandato hace que se
compilen todos los módulos que han cambiado desde la última compilación y que después se enlacen todos
los archivos objeto y las bibliotecas pedidas en una unidad ejecutable. El ejecutable se escribe en el archivo
reloj.exe puesto que ése es el nombre del proyecto.
Para ejecutar una aplicación utilizando el C de Microsoft, existen dos opciones:
■ ejecución desde el IDE de Visual C y
■ ejecución desde una consola de MS-DOS.
Para ejecutar desde el IDE de Visual C, lo único que hay que hacer es ejecutar la opción: Build^-Execute
reloj.exe. Este mandato solicita al IDE que ejecute el archivo reloj.exe usando para ello todos los recursos
que sean necesarios. Si se usa entrada/salida por consola, se abre una ventana consola. La otra opción es
ITES-Paraninfo • 2 6 9
Entornos Se p ro g ra m a c ió n en Lenguaje C
ejecutar el programa desde una ventana consola o de MS/DOS, que se puede crear activando el icono de
MS DOS en el menú Inicio. Una vez en la ventana consola, se debe cambiar el directorio a la posición del
ejecutarle ¿e la aplicación y, a continuación, ejecutar la aplicación tecleando su nombre.
Deparar una aplicación utilizando el C de Microsoft, usando el IDE de Visual C, es realmente fácil. Hay
dos cgczozes básicas:
■ Deparar una vez enlazada la aplicación.
■ Enlazar y ¿epurar al mismo tiempo.
La perrera es más aconsejable, porque en este caso se puede estar seguro de que no hay errores de enla
zare. Para ejecutar desde el IDE de Visual C, lo único que hay que hacer es ejecutar la opción: B u ild ^S ta rt
D ibui. Cuarzo se ejecuta esta opción, aparece una nueva ventana que permite ejecutar el programa de for
ma ccrnrua G o'. ejecutar hasta donde está el cursor (Run to cursor) o pararse en una llamada a función y
saltar zeztre ¿el código fuente de la misma (Step into). Con estas tres llamadas básicas se puede controlar la
ejecucicz ¿el programa. Los errores salen en la pantalla de debajo del código fuente, mientras que el flujo
ie e-ecucroz se controla en esta ventana.
Bibliografía
En este capítulo se proporcionan una serie de referencias bibliográficas sobre el lenguaje de progra
mación C. Hay muchos más libros que los indicados para aprender a programar en C, sin embargo, no
todos ellos son adecuados. Depende mucho del nivel de conocimiento del lenguaje y de los conceptos de
programación que tenga el lector.
En cualquier caso, todos los libros de C que se proponen son libros que explican el lenguaje. Este libro
de problemas sirve como complemento para todos ellos.
■ J.L. Antonakos, K.C. Mansfield Jr. Programación estructurada en C. Prentice-Hall, 1997.
■ F. García, J. Carretero, J. Fernández y A. Calderón. El lenguaje de Programación C: Diseño e Imple-
mentación de Programas. Prentice-Hall, 2002.
■ B. Gottfried. Programación en C. McGraw-Hill, 1999.
■ B. Kemighan y D. Ritchie. El Lenguaje de Programación C. Prentice-Hall, 1983.
■ D.R. Llanos Ferraris. Curso de C Bajo Unix. Paraninfo Thomson Learning, 2001.
■ P. de Miguel. Fundamentos de los Computadores. Ed. Paraninfo, 1998.
■ E. Quero. Programación en lenguaje C: [ejercicios y problemas]. Paraninfo, 1998.
■ H. Schildt. C: Manual de Referencia. McGraw-Hill, 1990.
■ M. Waite, S. Praia y D. Martin. Programación en C: Introducción y Conceptos Avanzados. Anaya Mul
timedia, 1992.