Anda di halaman 1dari 272

Indice General

índice de Figuras v

índice de Tablas vii

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 ¡

2 Tipos de datos y operadores 29


2.1 Identificadores...................................... 30
2.2 Variables y constantes......................... 31
2.3 Tipos de datos elem entales................ 33
2.4 Tipos avanzados................................... 44
2.5 Definición de tipos con t y pedef . . . 49
2.6 Conversión de tip o s ............................ 49
2.7 Escritura de datos con pri ntf . . . . 50
2.8 Lectura de datos con s c anf ....... 53
Evite estos errores......................................... 54
Problemas resu elto s......................................
'Problema de e x a m e n ...................................
Problemas propuestos...................................

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

3.8 Sentencias break y conti nue 81


Evite estos errores............................ A 1
Problemas resu elto s......................... C§4)
Problema de e x a m e n ...................... 96
Problemas propuestos...................... 97

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

5 Funciones y programación estructurada 117


5.1 Funciones en C ........................................................................................................................... 118
5.2 Paso de parámetros a una función ............................................................................................ 120
5.3 Recursividad ............................................................................................................................... 121
5.4 Macros ........................................................................................................................................ 122
5.5 Ámbito de las variables y tipos de alm acenam iento............................................................... 123
Evite estos errores.................................................................................................................................. 124
Problemas resueltos.................................................................................................................................. \}25a
Problema de e x a m e n .................................................. ,140 ;
Problemas propuestos........................................................................................................................... 142

6 Arrays y strings 145


6.1 Definición de array s..................................................... 146
6.2 Uso de un a r r a y ........................................................................................................................... 146
6.3 Relación entre punteros y a r r a y s ..................................................... 148
6.4 Paso de arrays a f u n c io n e s ........................................................................................................ 150
6.5 Arrays d in ám ico s........................................................................................................................ 152
6.6 Arrays m ultidim ensionales........................................................................................................ 156
6.7 Cadenas de caracteres.................................................................................................................. 159
Evite estos errores.................................................................................................................................. 166
Problemas resueltos............................................................................................................................... 166
Problema de e x a m e n ............................................................................................................................... 185
Problemas propuestos............................................................................................: ............................ 188

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

8 Entrada/salida a archivos 229


8.1 Archivos: conceptos y operaciones............................................... 230
8.2 Operaciones con archivos usando stdi o . h ............................................................................ 231
Evite estos errores.................................................. 245
Problemas resu elto s............................................................................................................................... 246
Problema de e x a m e n ........................................................................................................................... 260
Problemas propuestos........................................................................................................................... 261

A Entornos de programación enLenguaje C 263


A .l Entorno de programaciónen LIN U X /U N IX ............................................................................. 265
A. 2 Visual C de M icrosoft.................................................................................................................. 268

Bibliografía 271

índice de Materias 273

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

Figura I. I Arquitectura de un computador típico actual

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

1.2.1 Los números con signo


¿Como representar los signos + y —en un mundo binario? Es sencillo: como sólo hay dos signos, basta
con dedicar un bit para representar el signo. Pero, ¿qué bit? Pues depende del sistema de representación
(hay varios), pero en los más comunes se reserva para el signo el bit situado más a la izquierda. Los más
comunes son:
■ Representación en signo-magnitud.
■ Representación en complemento a 2.
En la representación en signo-magnitud, el bit de la izquierda es el de signo: 0 indica positivos y 1
negativos. Los números positivos y negativos son iguales, exceptuando el signo. La representación en signo-
magnitud con números de 4 bits sería:
Binario Decimal
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 -0
1001 -1
1010 -2
1011 -3
1100 -4
1101 -5
1110 -6
lili -7
Como puede ver, hay 2 representaciones para el cero: el positivo (0000) y el negativo (1000).
Para evitar estas ambigüedades y facilitar los cálculos y representaciones de números, se ha adoptado
casi umversalmente el sistema de representación en complemento a 2. La conversión es un poco más difícil
de entender inicialmente, pero la representación en complemento a 2 con números de 4 bits sería:
Binario Deci mal
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 -8
1001 -7
1010 -6
1011 -5
1100 -4
1101 -3
1110 -2
lili -1
Para obtener la representación en complemento a dos de un número, basta con complementar el número,
es decir, cambiar los ceros por unos y los unos por ceros y sumar uno al resultado. Así, por ejemplo, el
número -3 en complemento a dos se obtiene complementando el número 3 y sumando uno al resultado:

4 • ITES-Paraninfo
Capítulo I / Fundamentos de programación

Número complementar sumar 1 (complemento a dos)


0011 1100 1101
En cualquier caso, con 4 bits se pueden representar números sin signo desde 0 hasta 15. Los números
con signo en complemento a dos pertenecen al intervalo [ - 8, 7 ]. Una prueba de que la conversión está
bien hecha es sumar el número positivo y el negativo. La suma total debe ser 0.

►EJEMPLO I . I Sume los números 3 y -3, representandos en complemento a dos.


Re s o l u c i ó n .
3 -> 0011
-3 -> 1101

0000 -> 0000

1.2.2 Números con decimales


¿Cómo se calcula la representación binaria de 17.25? Es sencillo. Se hace como si fuera un entero, pero
usando también potencias negativas de 2:
17 / 2 = 8 resto 1
8 / 2 = 4 resto 0
4 / 2 = 2 resto 0
2 / 2 = 1 resto 0
1 / 2 = 0 resto 1 La parte entera es 10001

0.25 * 2 = 0.50 binario 0 (dígito a la izquierda de la coma)


0.50 * 2 = 1.00 binario 1 (dígito a la izquierda de la coma)
fin, la parte decimal es 00
La parte decimal es 01
De donde el número binario resultante de convertir 17.25 es 10001.0100.

1.2.3 Números en coma flotante


La representación de números en coma flotante se utiliza para representar números reales, es decir,
números con decimales. La representación en coma flotante permite representar un número usando el si­
guiente formato:

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

■ La base del exponente es 2.


■ El exponente e de un número se almacena como e + 127. A este tipo de representación se le denomina
representación en exceso a 127.

1.2.4 Representación de caracteres


Los caracteres se representan normalmente usando un byte (8 bits). Existe una representación estándar
que se usa actualmente a nivel mundial, que es el juego de caracteres ASCII. Hace algunos años se usaban
otros juegos de caracteres, como por ejemplo el EBCDIC.
El juego de caracteres ASCII utiliza los 7 primeros bits para representar:
■ 32 caracteres de control, como salto de línea (LF), el carácter nulo (NUL), etc. Estos caracteres se
usan en los terminales de entrada/salida, para control en protocolos y módem y en las aplicaciones
programadas. No son representables mediante ningún carácter alfanumérico.
■ Caracteres del alfabeto inglés en mayúsculas y minúsculas.
■ Números del 0 al 9.
■ Signos de puntuación ingleses, como el punto, la coma, etc.
■ Algunos signos especiales para los computadores, como @.
En total, el juego de caracteres ASCII consta de 127 caracteres. Como este conjunto no permitía repre­
sentar caracteres de algunos lenguajes, como la ñ o los acentuados del español, se propuso usar el bit 8 de
cada byte para extender el juego de caracteres ASCII y dar cabida a las cosas especiales de cada lenguaje.
Así, por ejemplo, todos los caracteres acentuados del español, como á, é, etc., están por encima del 127.

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

Compiladores Editores Bibliotecas


Softw are
de Sistema
Siste m a operativo

H a rd w a re

Figura 1.2 Software en un computador

Como se puede ver en la Figura 1.2, hay dos capas fundamentales:


■ Software de sistema, es decir, aquel que proporciona los mecanismos de gestión del hardware, como
el sistema operativo y las utilidades para desarrollar el software de aplicación (compiladores, editores,
bibliotecas, etc.). Sirve como base para el desarrollo y ejecución de otros programas y permite que el

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

Figura 1.3 Tipos de bloques en programación estructurada.

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.

EJEMPLO 1.2 Aplicaciones del principio de ocultación de información.

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

RESOLUCIÓN. Aplicando la técnica descrita brevemente, se observan en la descripción los siguientes


elementos:
■ Tipos estructurados: hospital, paciente, historia clínica, enfermedades, actos médicos.
■ Atributos del paciente: nombre, apellidos, dirección, edad e historia.
■ Atributos de la historia: lista de enfermedades y actos médicos.
■ Funciones para la historia: crear, destruir, modificar, archivar y actualizar.

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.

► ejemplo 1.4 Programa difícil de leer. Ejecútelo, por favor.


'L os programas que concursan se pueden ver en la página Web http://www.ioccc.org/

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

Figura 1.4 Ciclo de desarrollo de un programa

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.

!► EJEMPLO 1.5 Primer programa en C: saludo al programador.


Re s o l u c i ó n .
/*
* Este es su primer programa en C.
* Imprime un saludo por pantalla.
*/

#include <stdio.h>
#defi ne DESTINO "Lector"

/* Programa principal *1
int main(int arge, char **argv)

printf ("Hola aprendiz de programador en C \n");


printf ("Gracias por ser nuestro %s\n", DESTINO);

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.

EJEMPLO 1.6 Compilación, enlazado y ejecución del primer programa en C.

R E S O LU C IÓ N . En Linux y UNIX, para compilar el programa se puede usar el compilador de software


libre denominado gcc. Esta herramienta compila y enlaza el programa mediante el siguiente mandato:
gcc ejemplo_01_05.c -o ejemplo_01_05
que compila el primer programa, almacenado en el archivo ejemplo_01_05.c y almacena el ejecutable
resultante en el archivo indicado mediante la opción - o, es decir e j empl o_01_05. Si no se usa esta opción,
se genera un ejecutable que siempre se llama a .out.
Para ejecutar el programa basta con usar el mandato . / ej empl o_01_05 en el directorio donde está el
ejecutable.

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

1.6.1 Entrada/Salida básica


Aunque la entrada/salida se tratará más adelante, es necesario conocer los rudimentos para poder desa­
rrollar ejemplos de manera temprana. En C hay dos funciones básicas para la entrada/salida con formato:
printf para salida estándar y scanf para la entrada estándar. Sus posibilidades son bastante grandes,
permitiendo escribir y leer una línea de datos con un cierto formato. Por ejemplo, la sentencia;
printf ("\n Hola aprendiz de programador en C. Xs", nombre);
imprimirá por la salida estándar (normalmente la pantalla) el mensaje más los datos contenidos en la variable
nombre, con un salto de línea previo indicado mediante \n.
La función printf que se definirá con más detalle en el próximo capítulo, tiene el siguiente formato
general;
printf(formato, argl, arg2, ... argN);
donde formato incluye la cadena de caracteres a imprimir y una serie de especificadores de formato. Un
especificador de formato incluye, entre otros, un especificador de conversión, que indica la forma en la que
se imprimirán los datos que se encuentran en argl..... argN. El especificador de conversión que se
utiliza para imprimir un entero decimal es Xd. De esta forma, la siguiente sentencia:
printf("Número Xd", 345);
imprime por la pantalla:
Número 345
La entrada con formato usando scanf es similar. Esta función tiene un formato genérico similar a la
función pri ntf:
scanf (formato, argl..... argN);
donde la cadena formato es similar a la utilizada en la función printf. Los especificadores de conversión
utilizados también son los mismos a los empleados en la función printf. 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. Un puntero representa
una dirección de memoria. Así, si se desea almacenar un valor en la variable temperatura, su dirección
de memoria vendrá dada por &temperatura.

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) */

printf ("Escriba un carácter: ");


scanf ("Xc", &c);
printf ("La letra es Xc \n", c );

printf ("Escriba un numero entero: ");


scanf ("7od", &i):
printf ("El numero es Xi \n", i):

printf ("Escriba un numero real flotante: ");


scanf ("%f", &f);
printf ("El numero es %f \n", f);

printf ("Escriba un string: ");


scanf ("Xs", tira);
printf ("El string es Xs \n", tira);

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?

Paso 2. Diseño del programa.


Hacer un buen diseño es fundamental para poder codificar una solución del problema. Suele haber varios
diseños alternativos, por lo que es importante elegir la mejor opción. Además, habitualmente es necesario

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.

Paso 6. Optimización del programa.


Después de tener un programa correcto y validado, es necesario estudiar si se puede mejorar la solución
adoptada para que el programa sea más eficiente, más fácil de usar o más amigable.
La posibilidad de mejoras nos conducirá hacia atrás en los pasos descritos, hasta llegar, incluso, al paso
de diseño. Cuanto más atrás haya que ir, más costosa será la nueva solución.

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 .

u = 1 • 27 + 1 ■26 + 0 • 25 + 0 • 24 + 0 • 23 + 0 • 22 + 0 • 2 1 + 1 • 2o = 128 + 64 + 1 = 193

¡ML2 Obtenga la representación en binario del número decimal 58.


Re s o l u c i ó n .
58 / 2 = 29 r e s to 0
29 / 2 = 14 r e s to 1
i 14 ./ 2 = 7 re s to 0
7 / 2=3 r e s to 1
3 / 2 = 1 re s to 1
1 / 2 = 0 re s to 1
De donde el número binario resultante de convertir 58 es 111010, como se puede comprobar convir-
tiéndolo a decimal,»

►1 3 Convierta el número binario 11000001 abase octal y calcule su valor decimal.


R e s o l u c i ó n . Su valor en base octal sería 301, resultante de obtener los bits en grupos de 3 empezando
por la derecha:
001 -> 1 000-> 0 l l - > 3
'» El valor decimal obtenido a partir del octal es:

v = 3 • 82 + 0 • 81 + 1 • 8o = 3 • 64 + 1 = 193

►I j4 Convierta el número binario 11000001 a base hexadecimal y calcule su valor decimal.


RESOLUCIÓN. Su valor en base hexadecimal sería Cl, resultante de obtener los bits en grupos de 4
empezando por la derecha:
0001 -> 1 .1100 -> c
El valor decimal obtenido a partir del hexadecimal es:

v = C * 161 + 1 * 16° = 12* 1 6 + 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

I ► 1.7 Represente en formato IEEE 754 el número 3.25


Re s o l u c i ó n . En primer lugar se obtiene la representación binaria del número 3.25. Aplicando lo vis­
to en este capítulo se obtiene 11.01. Para representar este número en el formato IEEE 754, es necesario
normalizar el número desplazando la coma a la izquierda:

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

0000 00000000 Rei ni ci a r l a máqui na


0001 00011101 Encender con intensidad de FF01
0010 00100000 Apagar el interruptor
0001 00011102 Encender con intensidad de FF02
0010 00100000 Apagar el interruptor
0010 00110000 Apagar la máquina

FF01 00000010 Intensidad 2


FF02 00000101 Intensidad 5

18 • ITES-Paraninfo
Capítulo I / Fundamentos de programación

Figura 1.5 Diseño modular de alto nivel de una agenda electrónica

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

RESET 00000000 Reiniciar la máquina


ON MEM 0001XXXX Encender con la intensidad
OFF 00100000 Apagar el interruptor
HALT 00110000 Apagar la 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.

► 1.13 Codifique el diseño anterior.


Re s o l u c i ó n .
/* Búsqueda lineal de un número mediante un bucle */

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

/* lectura del número a buscar */


printf ("Escriba el numero: ");
scanf("%d", &numero);

/* 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;

/* Si encontrado es -1, no se ha encontrado el número */


i f (encontrado == -1)
printfCEl número no está en el vector \n");

return(O);

O sifiq u e el diseño anterior comprobando el número que se introduce.


R e s o l u c ió n .
#'*■ Búsqueda lineal de un número mediante un bucle */

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

/* lectura del número a buscar *!


printf ("Escriba el numero: ");
if (scanf("%d", &numero) < 1) (
printfCEl dato introducido no es un número\n");
return(0);

!* 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

/* Si encontrado es -1 no se ha encontrado el número */


if (encontrado == -1)
printf("El número no está en el vector \n");

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.15 Especifique las pruebas a realizar en la codificación anterior.


Re s o l u c i ó n .
1. Codificación de la lectura del valor de entrada.
■ Pruebe que es correcta leyendo varios números enteros e imprimiéndolos por la salida.
■ Compruebe que si introduce un valor que no es entero no es admitido.
2. Codificación de la escritura del valor de salida.
■ Pruebe que es correcta escribiendo varios números enteros.
3. Codificación del algoritmo de búsqueda lineal.
■ Pruebe que es correcta leyendo varios números enteros e imprimiendo el resultado del algoritmo.
■ Pruebe que hace los bucles bien imprimiendo un mensaje a cada vuelta del bucle.

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

/* lectura del número a buscar */


printf ("Escriba el numero; ");

22 • ITES-Paraninfo
Capítulo I / Fundamentos de programación

If (scanf ("%d", &numero) < 1) {


printfCEl dato introducido no es un número\n") ;
exit(O);

/* comprueba si el número es negativo, si lo es finaliza el


* programa
ir /

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 */

i* comprueba si el número esta en el vector '*/


if (numero < vectorEO] || numero > vector[999]) I
printfCEl número %d no está en el vector\n", numero);
1
el se I
posición = (el Final - elComienzo) / 2;

/* 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

printf ("Encontrado numero %i en %i vueltas\n", numero, vueltas);


printf ("Poisicion %d ", posición);

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.

► 1.17 Especifique las pruebas a realizar en la codificación anterior.


Re s o l u c i ó n .
1. Pruebas con un valor que se encuentre en el vector.
2. Pruebas con un valor que no se encuentre en el vector.
3. Pruebas con un valor negativo.
4. Pruebas cuando se lee un dato erróneo.

!► 1.18 Indique los errores que presenta el siguiente programa.


Re s o l u c i ó n .
I*
* Ejemplo de programa con errores
I
int M a i n O I
// Declaración de variables
float PI = 3.1416 II constante PI int area = 0; II variables auxiliares

II Parte ejecutiva del programa


II Area de un circulo
área = PI * Radio * Radio;
I **
* Cálculo del área
*!
■printf ("El área de circulo de radio = 5 es %f", area);

A continuación se enumeran los errores que aparecen en el programa anterior:


1. No se ha cerrado el primer comentario.
2. La función ma in se ha escrito con la primera letra en mayúscula (Ma in).
3. Se declaran variables después de un comentario de línea. No se verán.
4. La cadena utilizada en printf (El area del circulo de radio = 5 es \%f) no finaliza con ".
5. Falta la llave de cierre del método principal.

Pro blem as Pro pu esto s


I . I Identifique los cinco componentes principales de un computador.
1.2 ¿Cuál es la diferencia entre memoria volátil y no volátil?
1.3 ¿Qué significa el acrónimo UCP?
1.4 ¿Cuántos bytes tiene una memoria RAM de 64 KB? (
1.5 Entre en los computadores de los laboratorios de su escuela o en el computador de una tienda e identifique
los distintos componentes del computador que se pueden ver. ¿Nota algunos dispositivos de entrada y salida
únicos?
1.6 Entre en el laboratorio de computadores de su escuela y busque la velocidad de la UCP, el tamaño de la
RAM y la capacidad de disco duro de sus computadores.
1.7 Convierta los siguientes números binarios a números decimales: 1010,110011, 110,01 y lililí.

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

el valor decimal de los números de 8 bits en complemento a dos siguientes:


a) 10101100
b) 10000001
O 11000000
d) 10100101
e) 11111111
t ) 10000000

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.21 ¿Quién inventó el lenguaje de programación C?

1.22 ¿A qué sistema operativo están ligados los orígenes del lenguaje de programación C?

1.23 Describa las ventajas del lenguaje de programación C.

1.24 Describa las desventajas 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.26 Explique los principales bloques de un programa en C.

1.27 ¿Para qué sirven los comentarios?

1.28 ¿Qué signo de puntuación termina las sentencias en C?

1.29 ¿Cómo se delimitan los bloques de código en un programa C?

1.30 ¿Cómo se especifican los comentarios en lenguaje C?

1.31 ¿Deben tener los programas en C un programa principal? ¿Cómo se denomina?

1.32 ¿Describa el ciclo edición/compilación/ejecución de un programa C? ¿Qué es?

1.33 ¿Qué herramienta se usa para escribir los programas en C?

1.34 ¿Qué hace un compilador?

1.35 ¿Qué hace un enlazador?

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.37 Indique un ejemplo de encapsulación en el funcionamiento de un contestador automático telefónico.

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.40 Denomine los componentes de un programa C.

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? .

1.44 Identifique todos los errores en el siguiente programa:

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

El objetivo de este capítulo es explicar la sintaxis y t. nEjCjimamon' se n- qr _ elf-entales


del lenguaje C. Estos tipos elementales se usarán pe-v^-.-i—e r g r a n n=ar oto ie isj.,:■ s e c á i s por el
usuario. Por tanto, del correcto uso de dichos tipos derence-q que e. oag?: xesuE; Tiar>c ; _e -i friables
usadas estén bien definidas. La importancia de los tipos e- C m — -’scsheé : que zr. .er.guajes,
como Java, debido a que C no es un lenguaje fuertemente ruad: j a x tamo. « ru¿?;e :_^r- : trnersión
implícita de tipos de datos.
Los temas que se tratan en el capítulo son:
■ Presentación de los conceptos de identificador, tipo, variable y oocsuote
■ Definición y uso correcto de variables y constantes de los tipos elemeuuue:
■ Presentación de los operadores aplicables a los tipos elementales eu el lengua;e I tus : :e m o n ­
dad de los mismos.
■ Presentación de los tipos de datos avanzados más habituales.
■ Definición de tipos por parte de los programadores.
■ Conversión de tipos de datos.

2.1 I d e n t if ic a d ores

Un identificador es un nombre que se asigna a los distintos elementos de un programe.. : : n : pueden


ser variables, nombres de funciones, etc. Para construir identificadores, en C se pueden mar caracteres
alfabéticos en mayúsculas y minúsculas, así como los diez dígitos decimales (del 0 al 9 f y el sume} a i? . o
aunque no pueden empezar por un número. Por ejemplo, as_DE_23 es un identificador válido, per: : : ol E
no lo es. Los distintos identificadores de una misma sentencia se separan mediante espacies m blanco
o tabuladores. Además, es importante saber que C es sensible a mayúsculas y minúsculas, por lo que el
identificador pepe es distinto al identificador Pepe. Por tanto, los siguientes identificadores sen válidos
en C:
casa mi casal _MES
MES_1 mes_l a890
_890 _a890 M8a90
Como ejemplo de identificadores no válidos en C se muestran los siguientes:
-numero utiliza el carácter -
3 n um e ro utiliza un dígito como carácter inicial
n ume r o_ " utiliza un carácter ilegal "
numeroSl utiliza un carácter ilegal $
Las palabras reservadas son identificadores que tienen un significado especial para el compilador del
lenguaje de programación. C, al igual que todos los lenguajes de programación, define una serie de palabras
reservadas, que se muestran en la Tabla 2.1. Así, por ejemplo, la palabra reservada whi 1e se utiliza para
construir un determinado tipo de bucle y la palabra reservada int se emplea para definir tipos de datos
enteros. Obviamente, las palabras reservadas no se pueden usar como identificadores de elementos del
lenguaje definidos por el usuario (constantes, variables, etc.). Todas las palabras reservadas en C deben
escribirse en minúsculas. La Tabla 2.1 incluye también las palabras reservadas que se han incorporado al
nuevo estándar de C (C99).
La forma de escribir los identificadores no está reglada en C, por lo que se pueden escribir como se
desee. Sin embargo, hay dos notaciones muy extendidas:
■ Escribir las distintas palabras juntas y con la primera letra en mayúscula. Si el identificador se co­
rresponde con una variable la primera letra del identificador se escribe en minúscula. Por ejemplo:
di as Del Mes. Si el identificador se corresponde con una función, la primera letra del identificador se
escribe en mayúscula. Por ejemplo: CalcularAreaCuadrado.
■ Escribirlas distintas palabras separadas por caracteres subrayado _. Por ejemplo: di as_del ares.
Se puede usar cualquiera de las dos, pero es importante seguir una convención para facilitar la legibilidad
y la comprensión de los programas.

30 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores

auto else 1ong typedef


break enum regi ster union
case extern return unsi gned
char fi oat short voi d
const for signed voiati1e
continue goto si zeof whi 1 e
default if stati c . -Bool
do inli ne struct -Compì ex
double int swi tch -Imagi na ry

Tabla 2 .1 Palabras reservadas en C

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.

►EJEMPLO 2 . 1 Definición de variables de tipos elementales en C


Re s o l u c i ó n .
int temperaturaHorno; // variable de tipo int
long numeroTelefono; // variable de tipo long
short k; // variable de tipo short
float interes; // variable de tipo float

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>

► EJEMPLO 2.2 Definición de variables con valor inicial en C.


Re s o l u c i ó n .
Int temperaturaHorno = 136; /* variable de tipo int */
long numeroTelefono= 916216574 /* variable de tipo long */
short k = 21; /* variable de tipo short*/
float interes = 4.5; /* variable de tipo float*/

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.

I ► EJEMPLO 2.3 Definición de constantes de tipos elementales en C.


Re s o l u c i ó n .
Ct e . de tipo int
Li_
L

((define 37
1—
LU
a_1

al
LU

/* *!
U

((define T E L_U RGENCIAS 112 /* Ct e . de tipo int */


#defi ne PI 3.1416 /* Ct e . de tipo f l oat */
((define MENSAJE "Pulse Intro" /* Ct e . de tipo S t r i n g */
((defi ne VOCALPRIMERA ’a ’ /* Ct e . de tipo char */
((define TRUE 1 /* Ct e . de tipo boo 1 ea n*/
((define VELOCIDA D_LUZ 300000 /* Ct e . de tipo int */
((define PI 3.1416 í* Ct e . de tipo float *1
Observe que las sentencias que definen macros no tienen punto y coma al final de las mismas.
No es posible asignar valor a las constantes después de su definición, porque ya lo tienen y no se puede
modificar. Sin embargo, nunca se debe confundir una constante con una variable con valor inicial. Hay dos
diferencias fundamentales:
1. Su definición se diferencia porque las constantes se definen con la directiva del preprocesador ((define.
2. El valor de la constante no puede cambiar, por lo que se obtiene un error si se trata de asignar un valor
a una constante.
Se puede terminar esta sección indicando que los distintos tipos de constantes en C son:
■ Caracteres: ’ a ’, ’ b ’
■ Cadenas de caracteres: "Cadena"
■ Valores enteros, que pueden expresarse de distintas formas:
• Notación decimal: 987
• Notación hexadecimal: 0x25 ó 0X25
• Notación octal: 034
• Enteros sin signo: 485U
• Enteros de tipo long: 485L
• Enteros sin signo de tipo long: 485UL
• Valores negativos (signo menos): -987
■ Valores reales (coma flotante):
• Ejemplos: 12,14, 8., .34

3 2 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores

• Notación exponencial: .2e+9,1.04E-12


• Valores negativos (signo menos): -12 -2e+9

2.3 T IP O S DE DATO S ELEM EN T A LES


En C hay tres tipos de datos elementales, lo que significa básicos o fundamentales, a partir de los cuales
se construyen todos los demás: el tipo char (carácter), el tipo int (entero) y sus variantes, y el tipo f 1oat
(real) y variantes. Estos tipos, junto con sus variantes, que se muestran en la Tabla 2.2, sirven principalmente
para hacer operaciones aritméticas, representación de caracteres y de valores lógicos.
En esa sección se estudian los tipos elementales y sus características. Además, se aprende a usar dichas
características a través de ejemplos.

Tipo Tamaño Representa


char 1 byte Un carácter
signed char 1 byte Un carácter con signo
unsigned char 1 byte Un carácter sin signo
int 1 palabra (4 bytes) Entero con signo
signed int 1 palabra (4 bytes) Entero con signo
unsigned int 1 palabra (4 bytes) Entero sin signo
short 2 bytes Entero corto con signo
signed short 2 bytes Entero corto con signo
unsigned short 2 bytes Entero corto sin signo
1ong 1 palabra (4 bytes) Entero largo con signo
signed long 1 palabra (4 bytes) Entero largo con signo
unsigned long 1 palabra (4 bytes) Entero largo sin signo
long long 2 palabras (8 bytes) Entero más largo con signo
signed long long 2 palabras (8 bytes) Entero más largo con signo
unsigned long long 2 palabras (8 bytes) Entero más largo sin signo
fl oat 1 palabra (4 bytes) N° en coma flotante
double 2 palabras (8 bytes) N° en coma flotante de doble precisión
long double 4 palabras (16 bytes) N° en coma flotante de mayor precisión

Tabla 2.2 Tipos elementales de C

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

Operador Significado Tipos aceptados


x++ x - - Postincremento/Postdec. Alfanuméricos
++x --x +x -x Postincremento/Postdec. Alfanuméricos
1 No lógico Booleanos
(ti polexpr Creación o conversión de tipo Todos
* / % Mult., división, módulo Numéricos
+ - Suma, resta Alfanuméricos
« » Desplazamiento binario Todos
< < = > = > Comparaciones de orden Alfanuméricos
== != Test de igualdad Todos
& AND binario Todos
! OR binario Todos
&& AND lógico Booleanos
II OR lógico Booleanos
= += -= *= /= %=&=
1= <<= >>= >»= Asignación Todos (del mismo tipo)

Tabla 2.3 Prioridad de operadores en 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.

2.3.2 Tipos numéricos enteros


Los tipos de datos que permiten representar números enteros en C son cuatro: short,int,longylong
1ong. Todos ellos permiten representar números positivos y negativos sin partes decimales. Por ello, los
enteros son el tipo de datos a usar para todo aquello que se pueda contar en unidades enteras.

► 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

■ int, signedo signed int


■ unsignedounsigned int
■long,signed long,long intosigned long int
■unsigned 1ongounsigned long int
■ long long,signed long long,long long intosigned long long int
■unsigned long 1ongounsigned long long int
La diferencia principal entre estos tipos está en su capacidad de representación y en si permiten repre­
sentar signo o no. La palabra reservada unsi gned indica que se trata de un número sin signo, es decir,
positivo. La Tabla 2.4 muestra los rangos de valores de cada tipo y su valor inicial por defecto. El rango
de un tipo se puede obtener mediante los calificadores MIN y MAX de cada tipo respectivamente, que se
pueden encontrar en el archivo 1 imi ts.h del sistema. Este archivo incluye, entre otros, los valores mínimo
y máximo que se pueden representar para cada uno de los tipos de datos en el computador en el que esté
ejecutando sus programas.

Tipo Bits Valor mín. Valor máx.


short 16 -32768 32767
int 32 -2147483648 -2147483647
1ong 64 -9223372036854775808 -9223372036854775807

Tabla 2.4 Rangos de los tipos de datos para enteros

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

pri ntf ("número de bits po r char: %d\n", CHAR_BIT ):


pri ntf ("mínimo valor para un si gned char: %d\n", SCHAR_M IN);
p r i nt f ("máximo valor para un signed char %d\n", SCHAR_ MAX) ;
pri ntf ("máxi mo valor pa ra un unsigned char: %d\n ", UCHAR_MAX)
pri ntf ("mínimo val or para un si gned short %d\n", SHRT_ MIN) ;
pri ntf ("máximo val or para un si gned short %d\n", SHRT_ MAX) ;
p rint f ("máxi mo val or para un unsigned short %d\n ", USHRT_MAX)
pri ntf ("mi ni mo val or para un int %d\n", INT_MIN) ;
pri ntf ("máximo valor para un int %d\n", INT_MAX)1
pri ntf ("máximo valor para un unsigned int %ud\n" , UINT_MAX) ;
pri ntf ("minimo val or para un long %1d\n", LONG_MIN);
pri ntf ("máximo val or para un long %ld\n", LONG_MAX);
pri ntf ("máxi mo val or pa ra un unsigned long %1d\n ", UL0NG_MAX)

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

O peraciones aritm éticas con enteros


Una vez vistos los tipos y sus rangos, se presentan algunos ejemplos de operaciones con enteros.

!► 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;

printf("Introduzca dos números a y b: ");


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

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;

printf("Suma = %d\n", suma);


printf("Resta = % d\n", resta);
printf("Producto = %d\n", producto);
printf("Di vi sión = %d\n", división);
printf("Modulo = %d\n", modulo);
printf("Postincremento de %d = %d\n", a, postincremento);
printf("Preincremento de % d = %d\n", a, preincremento);
printf("Postdcremento de % d = %d\n", b, postdecremento);
printf("Preincremento de %d = %d\n", b, preincremento);

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:

Expresión Valor decimal Valor binario


a << 1 16 10000
a << 2 32 100000
a << 3 64 1000000
a >> 3 1 0001

2.3.3 Tipos numéricos reales


En cuanto a los números reales o números en coma flotante, existen tres tipos básicos en lenguaje C,
que se muestran en la Tabla 2.5 junto con su tamaño típico en bytes. La diferencia entre ellos se encuentra
en la precisión de los números que se pueden representar. Es decir, el conjunto de valores de tipo f 1oat
es un subconjunto del conjunto de valores de tipo doubl e y el conjunto de valores de tipo do ubi e es un
subconjunto del conjunto de valores de tipo long double.

Tipo Tamaño Representa


float 4 bytes Número en coma flotante
double 8 bytes Número en coma flotante de doble precisión
long double 16 bytes Número en coma flotante de doble precisión extendida

Tabla 2.5 Tipos de datos básicos reales en C

Estos tipos pueden representar mediante tres tipos de notación:


■ Base decimal, utilizando un punto para separar la parte entera de la decimal. Las siguientes constantes
representan valores reales utilizando notación decimal-punto:
1234.898 0.233
■ Mantisa decimal con base decimal y exponente entero. En este caso la base se sustituye por las letras E
o e. Las siguientes constantes representan valores reales utilizando notación exponencial decimal:
23.4e2 Representa23.4 x 102
-1.9E-18 Representa—1.9 x 10-18
.00988e4 Representa 0.00988 x 104
■ Mantisa hexadecimal con base dos y exponente entero. En este caso la base se sustituye por las letras
Pop.

3 8 • tTES-Pararímfo
Capítulo 2 / Tipos de datos y operadores

Las siguientes constantes representan valores reales utilizando esta notación:


0x23.4p 14 Representa 0x23.4 x 214
0x654.4p2 Representa 0x654.4 x 22
La precisión de las constantes en coma flotante se puede especificar con las letras F y L (en mayúscula
o en minúscula). La primera se utiliza para indicar valores de precisión normal y la segunda para valores
de doble precisión. Así, la constante 2.34E48L representa un valor en coma flotante de doble precisión,
mientras 2.34E48F representa el mismo valor, pero con precisión normal.

O peraciones con reales


Una vez vistos los tipos, sus rangos y los operadores básicos, se presenta un ejemplo de operaciones con
reales.

►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

int mai n(void)

float radio;
float area;

printf("Introduzca el area del círculo: ");


scanf("%f", &area);

radi o = sqrt(area/PI);

printf("El círculo de area %f tiene de radio


a rea, radio)

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

2.3.4 Números complejos


C99 introduce nuevos tipos de datos para trabajar con números complejos. Estos tipos son:
f1oat _Complex
double _Complex
1ong double _Complex

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.

2.3.5 Tipo carácter


C proporciona un tipo básico para manipular caracteres. El tipo c h a r permite representar valores con­
sistentes en un único carácter, tales como:
'a’, ’x ’, ’D ’, ’7 ’
Los caracteres admitidos como válidos en C son aquellos incluidos en la tabla del estándar ASCII ex­
tendido, para poder representar los símbolos de lenguajes distintos al Inglés. Habitualmente, la mayoría de
los lenguajes de programación usan el estándar ASCII para representar los caracteres. El estándar ASCII
incluye 128 caracteres, que representan el alfabeto inglés, los dígitos del 0 al 9 y algunos caracteres especia­
les. Posteriormente se amplió a 256 caracteres para incluir los caracteres existentes en alfabetos europeos
comunes (como la á o la A). Para ser compatible con todos estos lenguajes, y con la mayoría de las aplica­
ciones del mundo occidental, en C también se usan los 256 primeros caracteres para representar el código
ASCII extendido.

4 0 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores

Las variables de tipo carácter se definen como cualquier otra en C:


char letra;
char numero = ’0 ’;
Se puede mostrar el código ASCII de un carácter sin más que convertirlo a un tipo int. Igualmente, se
puede obtener el carácter asociado a un número entero entre 0 y 255 sin más que convertirlo a cha r, como
se muestra en las sentencias siguientes:
i n t codi go_a = ( i n t ) ’ a ’ ;
char primeraletra = (char) 97;
También se pueden obtener estos valores asignando caracteres a enteros y viceversa. Esta técnica es
problemática porque da lugar a pérdidas de información y redondeos no deseados. Tenga en cuenta que al
asignar un entero a un carácter sólo se toma el primer byte.
Hay ciertos caracteres que son problemáticos, porque se usan en el lenguaje C para otras cosas (co­
mo ", que delimita la cadena de formato) o que no se pueden representar directamente, como ocurre con los
códigos ASCII entre 0 y 31 denominados caracteres de control (como por ejemplo el salto de línea). Estos
caracteres se usan mediante secuencias de escape. Una secuencia de escape es un carácter que está repre­
sentado por el carácter \ seguido por una letra o conjunto de dígitos. La Tabla 2.6 muestra las secuencias
de escape definidas en C.

Secuencia Significado
\n nueva línea
\t fabulador
\b backspace
\r retomo de carro
\" comillas
V apóstrofo
\\ backslash
\? signo de interrogación

Tabla 2.6 Secuencias de escape

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 ;

printf ('1Las letras minusc ul as y sus codiigo s ASCII son


for (i = ’-a’; i < 'a’+ 26; i++)
printf C "carácter: %c Valor;; %d\n", (char) i , 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.

2.3.6 Tipo lógico


El lenguaje C, a diferencia de otros muchos lenguajes de programación, no ha dispuesto tradicional­
mente de un tipo de datos booleano para representar los valores verdadero (true) o falso (false). En C se

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

Tabla 2.7 Tablas de operaciones lógicas

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

Tabla 2.8 Operadores relaciónales de C

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;

!* Operadores lógicos con booleanos *!

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

aux = true && false;


printf("\n true AND false es %d ", aux);

aux = true || false;


printf("\n true OR false es %d ", aux);

/ ^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 *!

/* Operadores lógicos con enteros */


pri ntf("\n operaciones lógicas con enteros...");
printf("\n N0T(i >= j ) = % d ", !(i >= j ) ); / * f a l s e *!

aux = (i >= j ) && (j >= i);


printf("\n (i >= j) AND (j >= i) = °Ád ", aux); /* false */

aux = (i >= j ) || (j >= i );


printf("\n (i >= j ) OR (j >= i) = %d ", aux); /* 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.

2.3.7 Tipos enumerados


Un tipo enumerado permite definir un conjunto de constantes simbólicas con valor entero.

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>

t* Definición del tipo enumerado *!


enum DiasSemana (lunes, martes, miércoles, jueves, viernes,
sabado, domingo);
int main(void)
(
/* d e f i n i c i ó n d e u n a v a r i a b l e de tipo DiasSemana */
enum DiasSemana d ia ;

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)

char cadenal [5] = "Hola";


char cadena2 [] = "Hola";
char cadena3 [126];
int i :

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

for (i=0 ; i< strlen(cadena2); i++)


printf ( El elemento %d de cadena2 es %c\n",
i , cadena2[i ] ) ;

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;

strcpyCcadena, "Esto es un error");


printf("Cadena es: %s\n", cadena);

for (i= 0 ; i< strlen(cadena); i++)


printf ("El elemento %d de cadena es %c\n", i, cadena[i]);

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:

struct nombre_estructura variable_estructura;


La principal diferencia que existe entre un array y una estructura es que un array agrupa datos del mismo
tipo y una estructura puede agrupar datos de distinto tipo.

►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 */

printf ("Hoy es; I d de %s de %d\n", hoy.dia, hoy.mes, hoy.anyo);


printf ("Ayer era; %d de %s de %d\n", ayer.di a , ayer.mes, ayer.anyo);

/* se copia la estructuna */
ayer = hoy;

printf ("Hoy es; I d de ts de %d\n",hoy.di a , hoy.mes, hoy.anyo);


printf ("Ayer era; %d de %s de %d\n", ayer.dia, ayer.mes, ayer.anyo);

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.

► EJEMPLO 2.1 6 Programa de ejemplo que utiliza punteros.

Re s o l u c ió n .
#include <stdio.h>

int maini voi d )

int x; /* variable de tipo entero */


int y; I* variable de tipo entero *1
int *px; /* variable de tipo puntero a entero */

x = 5; /* asigna a x el valor 5*/


px = &x; i* asigna a px la dirección de x */
y = *px; !* asigna a y el contenido de la dirección
almacenada en p x *!

printfC'x esta en la dirección %p y su valor es % d \n", &x, x);


printfC'y esta en la dirección % p y su valor es % d \n", &y, y);
printf("px esta en la dirección %p y su valor es %p \n", &px, px);
printfCEl valor de *px es %d\n", *px);

return(O);

Observe que en el programa se ha utilizado el especificador de conversión tp para imprimir el valor


almacenado en un puntero. Cuando se ejecuta el programa anterior se obtiene una salida similar a ésta:
x esta en la dirección 0x1000 y su valor es 5
y esta en la dirección 0x2000 y su valor es 5
px esta en la dirección 0x3000 y su valor es 0x1000
El valor de *px es 5

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 */

typedef float Temperatura;


struct Termostato
(
Temperatura temp_min; / * temperatura mínima del termostato */
Temperatura temp_max; / * temperatura máxima del termostato *i

typedef struct Termostato TermostatoACME;

int main(void)
(
TermostatoACME termo;

termo.temp_min = -3.5;
termo.temp_max = 50.0;

printf ("La temperatura puede oscilar entre %4.2f y %4.2f \n",


termo.temp_mi n , termo.temp_max);
return(O);
1
Al no ser C un lenguaje fuertemente tipado, a todos los efectos, el tipo de datos Temperatura será
tratado como un f l o a t .

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

■ Por asignación. Cuando la variable de la izquierda es de un tipo numérico el valor de la derecha se


convierte automáticamente a ese tipo si el tipo de la izquierda es de mayor precisión.

! ► EJEMPLO 2. 18 Conversiones implícitas de tipos numéricos.

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;

printf ("Espacio es % f y su expresión es %f\n",


espacio, expresion);

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;

I ► EJEMPLO 2. 19 Conversiones explícitas de tipos numéricos.


Re s o l u c ió n .
#include <stdio.h>

int main(void) 1
int longitud = 3;
float espacio, expresión;

/* conversiones explícitas */
espacio = (float) longitud;
expresión = espacio * (float)1ongitud;

printf ("Espacio es %f y su expresión es %f\n", espacio, expresión);

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

Imprime un entero hexadecimal con signo


X

%xu %Xu Imprime un entero hexadecimal signo signo


%f %F Imprime un doubl e con el punto decimal
te %E Imprime un doubl e en notación exponencial
%a %A Imprime un d o ub 1e en notación exponencial
%g %G Imprime un doubl e
%s Imprime una cadena de caracteres
%p Imprime el valor de un puntero

Tabla 2.9 Especificadores de conversión de pri ntf

El especificador de conversión % f imprime un valor doubl e con decimales. El número de decimales


que se muestran por defecto es 6, redondeándose el resultado. Los especificadores fe y Z E representan el
número en notación exponencial, empleando también 6 dígitos para la parte decimal. Los especificadores
%a y %A representan el número de la forma 0xh.hhhp ± e. Los especificadores %g y %G imprimen el número
en notación exponencial sólo si es exponente que resulta de la conversión es menor que -4 o mayor o igual
que la precisión.

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

/* Impresión de resu 1tados */


pri ntf("Formato f %f\n", b) \
pri ntf("Formato e %e\n ", b) ",
pri ntf("Formato g %g\n\n " j b)

/* Impresión de resul tados con formato erróneo */


printf("Numero %f en entero es %d\n", numero, numero)
printf("Numero t f en char es %c\n" numero, numero);

return(O);

Cuando se ejecuta este programa se obtiene la siguiente salida:


Formato f 123.250000

ITES-Paraninfo • S I
Problemas resueltos de C

Formato e 1.232500e+02
Formato g 123.25

Numero 4.200000 en entero es -858993459


Numero 4.200000 en char es I
Observe que cuando se emplea un especificador de conversión equivocado no se produce un error de
compilación, el compilador sólo genera una advertencia. Esto puede dar lugar a impresiones incorrectas que
pueden provocar confusiones. Es el caso de la impresión de numero que produce la salida —858993459,
que, como se puede observar, nada tiene que ver con el número 4.2.

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

Produce el siguiente resultado:


0004
Age
123.2548
1 .2325e+02
123.3

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 %.

► EJEMPLO 2.22 Programa que da formato con secuencias de escape.


Re s o l u c i ó n .
#include <stdio.h>

int mai n(voi d )


{ /* Secuencias de escape con \ */
printf ("\\n es un salto de línea \n \n");
printf ("\\t es un \t tabulador \n \n ;
printf ("\\v es un \v tabulador vertical \n"):
printf ("\\b es un \b retroceso (backspace) \n");
printf ("\\r mueve el cursor al \r principio de la línea actual \n");
printf ("\\a es una \a alerta visible o audible \n");
printf ("\\f mueve el cursor a la \f siguiente páginalógica \n");
printf (" \ \\ 'imprime una comilla simple \n");
printf ("\" imprime dobles comillas \n");
printf ("\\\\ imprime el carácter \\ \n");
printf ("\\? imprime el carácter \? \n");
printf ("Wddd es un carácter ASCII en octal Xo \n", 68);
printf ("Wxdd es un carácter ASCII en hexadecimal Xx \n", 68);
/* Secuencias de escape con % */
printfCEsto imprime un XX \n \n");

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.

2.8 Lectura de datos co n scanf


Cuando se desea leer un dato y almacenarlo en una variable se puede utilizar la función scanf. Esta
función tiene un formato genérico similar a la función printf:
scanf(formato, argl..... argN);

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.

r ►EJEMPLO 2.23 Ejemplo de lectura de distintos valores usando scanf.


Re s o l u c ió n .
#include <stdio.h>

int main(void)

char cadena[128];
char c;
int e ;
float r;

pri¡ntf ("Introduzca un ca rácter


sc¿in f ("\n%c &c);
p r ¡ntf ("Ha introduc:i do %c\n", c) ;

pr ¡ntf ("Introduzca una cadena:


sc¿ïn f ("7os ",, cadena));
p r ¡ntf ("Ha introduc;i do %s\n", cadena)

pr_¡ntf ("Introduzca un entero:


sc¿jn f ("%d", &e) ;
p r ¡ntf ("Ha introduci do %d\n", e) ;

p r ¡ntf (" Intcroduzca un real: ")


sc¿jn f ("%f", & r ) ;
pr intf ("Ha introduci do %f\n", 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

ello, si se usa un punto y coma al final de la definición de la constante, posiblemente se obtendrá un


error de compilación.
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.
Empleo de variables no definidas Es también muy común utilizar en los programas variables no defini­
das, bien porque se ha olvidado su definición o bien porque existe algún problema con las mayúsculas
y las minúsculas.
Cadenas de caracteres sin dobles comillas Cuando se utiliza una cadena de caracteres en un programa
como literal y no se abre o cierra adecuadamente utilizando ", se produce un error de compilación.
No utilizar el operador de dirección en la función scanf Cuando se lee una variable, que no sea una ca­
dena de caracteres, debe utilizarse el operador de dirección &.
Olvido de los paréntesis en la función ma in Todas las sentencias de la función ma in, y en general de to­
das las funciones, deben ir encerradas entre { y ¡.
Empleo inadecuado de los especificadores de conversión Cuando se emplea un especificador de conver­
sión incorrecto en la función printfoscanf pueden obtenerse resultados inesperados. Por ejemplo, si
utiliza el especificador de conversión %d para escribir un número real, la función pri ntf no imprimirá
el número deseado.

Pro b lem a s Resu elto s


► 2.1 ¿Cuál de los identificadores siguientes es válido y cuál no?
casa mi-casa mi*casa micasal
.MES MES-1 MESZUNO mesíl
a980 890a _890 $a890

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.2 Indique si los siguientes identificadores son el mismo para el lenguaje C.


Traca, traca, traCa.

Re s o l u c i ó n . Son distintos, porque C es sensible a los caracteres en minúscula y en mayúscula. Por


tanto, los identificadores Traca,tracaytraCa representan cosas distintas en lenguaje C.

► 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 */

#defi ne MESESDELANYO 12 /* Meses del año */


#defi ne SEGUND0S_MINUT0 60 / * Segundos en un minuto*/
#defi ne TAMANYOMEMORIA 2048 /* Tamaño de memoria */
#define VERDADERO 1 /* Constante verdadero */
#defi ne CELSIUSFARENHEIT 1.8 / * Conversión de grados *í
#define PRECI0_DIÑERO 3.9 / * Tipo de interés */

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>

#define MESESDELANYO 12 /* Meses del año *í

int main(void) {
int mes = 5; /* Mes del año *t

printf ("Mes %d de %d \n ", mes, MESESDELANYO);


printf ("Porción de anyo transcurrida %4.2f \n",
(float)mes / MESESDELANYO); i

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>

#de f i ne PI 3.1416 /* constante PI */

i n t mai n(voi d)
)
/* Definición de variables */

f l o a t area = 0;
i n t radio = 5;

printf ("Introduzca el radio del círculo: ");


scanf (" %d ”, &radio);

/* Á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

int main (void)


{
1
int n = 5;
int suma = 1, muí t = 1, di vi s =1;

pri ntf ("Escriba un número entero

ÍTES-Paraninfo • 5 7
Problemas resueltos de C

scanf ("%d", &n);

/* operadones sol ici tadas */


muít = n * 20;
printf ("%d * 20 es %d\n", n, mult);

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

di vis = suma / DIVISOR;


printf ("%d div %d es %d\n", suma, DIVISOR, divis);

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 ;

/* lectura de números enteros */


printf("Escriba un número entero i: ");
scanf ("7od " , &i ) ;

printf("Escriba un número entero j : ");


scanf("%d", &j);

/ * O p e r a d ones con enteros */


P rint f (" \n Números enteros");
P ri ntf("\n );
P ri ntf("\n Valores de variables );
P rint f (" \n i = %d" , i );
P ri ntf("\n j = %d", j );
P ri ntf(" \n Operaciones ...");

58 • ITES-Pa ranínfo
Capítulo 2 i Tipos de datos y o p e ra d o re s

pri ntf("\n + j = %d\ (i + j ) ) ;


pri ntf("\n - j = %d", (i - j ) ) :
pri ntf("\n * j = %d", (i * j )) ;
pri ntf("\n / j = %d", (i / j ) ) :
pri ntf("\n % j = %d\n (i % j ) ) ;

/* lectura de números reales */


printf ("Escriba un número reai x: ");
scanf {"% f", &x);

printf ("Escriba un número real y: ");


scanf ("%f", &y);

/* Operad ones con reales */


printf("\n Números reales");
pri ntf (" \n----------------------------- ");
printf("\n Valores de variables...");
pri ntf("\n x = %f", x );
pri ntf("\n y = %f", y ) ;
printf("\n Operaciones ...");
printf("\n x + y = %f", (x + y));
printf("\n x - y = 7f", (x - y));
printf("\n x * y = %f", (x * y));
printf("\n x / y = %f", (x / y));
pri ntf("\n x \% y = %f\n", ((int)x % (int)y));

/* Operaciones con mezcla de tipos */


printf("\n Operaciones con mezcla de tipos...");
pri ntf ("\n--------------------------------");
p rint f("\n j + y = %f", (j + y));
printf("\n i * x = %f", (i * x));
printf("\n j / x = %f\n", (j / x ));

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;

printf("Introduzca un número: ");


scanf("%d " , &n);

for (i=0; i < 8*sizeof(int) ; i++) (


if ((n & mascara) != 0 )

ITES-Paraninfo • 59
Problemas resueltos de C

numerollnos ++;

/* se desplaza la máscara a la izquierda */


mascara = mascara << 1 ;

printf("%d tiene %d unos\n", n, numeroünos);


return(O);

!► 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>

#define MASCARA 9 /* 00...01001 */

int main(void)
I
int i = 27 ;
int j = 32;
int aux;

printf ("Escriba un número entero i; ");


scanf Cid", &i ) ; // lee un entero

printf ("Escriba un número entero j: ");


scanf ( " 7 o d", &j ) ; // lee un entero

/* Operadores relaciónales */'


printf("\n Mayor o igual que...");
pri ntf ("\n----------------------- ");
printf("\n i >= j = %d", (i >= j)); //falso
printf(”\n j >= i = %d ", (j >= i)); //verdad
printf("\n i != j = %d", (i != j)j; ¡/verdad
printf("\n i != i = %d", (i != i)); //falso

/* 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

/* Operadores a nivel de bit */

60 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores

printf("\n Operadores de Bit");


pri ntf("\n );
printf("\n i = %d MASCARA = %d", i, MASCARA);
printf("\n AND binario");
j = i & MASCARA;
printf("\n i & MASCARA = %d", j);
p r int f ("\n 0 R binario");
j = i | MASCARA;
printf("\n i | MASCARA = %d", j );
j = i << 4;
printf("\n Desplazamiento binario izdo.");
pri ntf("\n i « 4 = Id", j );
j= i >> 2;
printf("\n Desplazamiento binario dcho.");
printfí"\n i >> 2 = %d", j );

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:

cantidad * interés mensual


cuota =
1
(1 + interés mensual)numero de Pas°s

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;

printfCEste programa calcula el pago mensual y ");


printf("total para un préstamo");

/* Entrada de datos */
printf ("\n Cuantía del préstamo: ");
scanf ("%f", &cuantiaPrestamo);

printf ("\n Interés anual: ");

ITES-Paraninfo • 6 1
Problemas resueltos de C

sca.nf C"%f", &interesAnual);

printf ("\n Duración del préstamo en años: ");


scanf ("%d", &duracionPrestamo);

/* Cálculo del préstamo */


interesMensual = interesAnual / 100.0 / MESES_EN_UN_ANY0;
numeroPagos = duracionPrestamo * MESES_EN„UN_ANYO;
pagoMensual = (cuantiaPrestamo * interesMensual) /
(1 - pow(1/(1 + interesMensual ), numeroPagos ) );
pagoTotal = pagoMensual * numeroPagos:

/* 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>

#define Espaciolnicial 5.5F


#define Velocidad 3.2F

int main(void)
!
float tiempo = 22.3F; /* Tiempo del recorrido *¡
float espacio = 0;

printf ("Tiempo de desplazamiento: ");


scanf("%f", &tiempo);

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>

int mai n (void)


1
float a;
float b;
float c;
float raizl = 0.0, raiz2 = 0.0;
float discriminante = 0.0;

printf ("Escriba el coeficiente a: ");


scanf("%f", &a);
printf ("Escriba el coeficiente b: ");
scant("%f", &b);
printf ("Escriba el coeficiente c: ");
scanf("%f", &c);

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

discriminante = (float)(pow(b,2) - 4*a*c);


_if (di seri mi nante < 0)
pri ntf (" No exi sten " ) ;
el se
I
raizl = (float)((-b + sqrt(discriminante)) / 2*a);
raiz2 = (float)((-b - sqrt(di seri minante ) ) / 2*a);
printf ( " % f y % f \n", raizl, raiz2 );
1
return(0) ;

[► 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;

codi go_a = (int ) ’a ’;


printf ("El código de ’a ’ es; %d\n", codigo_a);
printf ("La primera letra es: %c\n", primeraLetra);

printf ("Escriba un número positivo; ");


scanfC'M", &numero);
printf ("Su carácter correspondí'ente es; %c\n", (char)numero);
carácter = numero;
printf ("El carácter impreso como número es: %d", carácter);

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;

printf ("\n Escriba los dos caracteres: ");


scanf ("%c", &carl);
scanf ("%c", &car2);

printf (”\n Los caracteres ordenados son ");


if (carl < car2)
printf ("7oc y %c", cari, car2 );
el se
printf ("%c y %c", car2, cari );

!► 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

for ( i =0; i < 40; i++)


vector_l[i] = i ;

/* se copia */
for (i=0; i< 40; i++)
vector_2[i] = vector_l[i];

for (1=0; i< 40; i++)


printf ("El elemento %d de vector_2 es %i\n",
i , vector_2[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;

/* se leen los dos puntos */


printf("Introduzca la coordenada x del pl: ");
scanf("%f", &pl.x );
printf(”Introduzca la coordenada y del pl: ");
scanfC'Xf", &p1.y );
printf("Introduzca la coordenada x del p2: ");
scanf ("7of", &p2.x);
printf("Introduzca la coordenada y del p2: ");
scanf("%f", &p2.x);

/* se comprueba si los dos puntos son iguales */


if (pl.x == p2.x && pl.y == p2.y)
printfC'Los dos puntos son iguales\n");
el se
printfC'Los dos puntos son distintos\n");

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 *!

printf("Introduzca a + ib: ");


scanf("%f %f ", &x, &y);
a = x + I*y;

printf(" Introduzca c + id: ");


scanf ("7of %f", &x , &y) ;
b = x + I*y;

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

!► 2.2 0 Indique los errores que aparecen en el siguiente programa.

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

A continuación se enumeran los errores que aparecen en el programa anterior:


1. Se ha definido la constante PI utilizando ; al final de la sentencia.
2. La función ma i n se ha escrito con la primera letra en mayúscula (Ma in).
3. Se ha utilizado una variable no definida, área.
4. Se ha usado la variable Ra d i o y se debería haber utilizado radio.
5. La cadena utilizada en pr intf (El área del circulo de radio = 5 es ) no finaliza con ".
6. Se ha confundido el operador = de asignación con el == de comparación.

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;

printf ("\n Escriba el nombre; ");


scant("%s”, nombre);
printf (”\n Escriba el apellido: '
scant("%s", apel1 ido);
printf ("\n Escriba la dirección: ') ;
scant("Xs", di recci on);
printf C \ n Escriba la ciudad: ");
scant("%s", ci udad);
printf ("\n Di stri to Postal : ");
scant("%d", &distritoPostal);
printf ("\n Escri ba el país: ");
scant("%s ", p a is);
printf ("\n Escriba el sexo (V/H): ");
scant("\nXc' , &sexo);
printf C'\n Escriba estado civil (C/S/D): ")
scant("\n%c' , SestadoCivil);
printf ("\n Escriba altura en cm.: ");
scant ("M", &altura);
printf ("\n Escri ba peso en Kg.: ");
scant ("Xf", &peso);

printfC Este programa imprime la ficha de \n");


printfC una persona y su índice de masa corporal \n")

/* 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

printf("\n Número de hijos: %d ", numHijos );


printf("\n Dirección: %s ", dirección );
printf("\n Ciudad: %s ", ciudad );
printf("\n Distrito Postal: %d ", distritoPostal );
printf("\n País: %s ”, país );
printf("\n Peso: %4.1f Kgrs. ", peso );
printf("\n Altura: %d cm. ", altura );

/* Cálculo del Indice de masa corporal */


aux = (float)(altura/100.0);
imc = peso / (aux*aux);
printf("\n índice de masa corporal: %4.2f\n", imc );

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.

Pro blem as Pro pu esto s


2 . 1 ¿Cuáles de los identificadores siguientes son válidos en C?
JAVA mi -casal 2_MES 12 3 _ad
MES -1 $mes_l aBC890 AÑOS 1
_casa_890 aa$a890 $_90 - -nro
a+b+c %$a890 %porci en __nro
2 .2 ¿Son los siguientes identificadores iguales o distintos en C?
JAVA C JAva JaVa
MES_1 mes_l MES1 MESÍl
2.3 ¿Qué es un literal?
2.4 ¿Qué es un operador?
2.5 ¿Cómo se describe una constante en C?
2.6 ¿Cuáles son los tipos elementales de C?
2 .7 ¿Cuál es la sintaxis para definir una variable? ¿Y una constante? ¿Y la conversión de tipos?
2.8 ¿Qué tipo de datos se usarían para definir variables que representen los valores siguientes?
1. Los años de un niño.
2. Las medidas de un edificio.
3. El nombre de una persona.
4. La letra de su apartamento.
5. Una variable que indique si alguien tiene hijos o no.
2.9 Escriba un programa en C que escribasu nombre y dirección completa.
2 . 10 Escriba un programa en C que calculelos meses, días, horas, minutos y segundos que hay en 15 años.
2 . 1 I Escriba un programa que calcule el área de un cilindro de radio r y altura h. La constante PI es 3.1416.
Efectúe varios cálculos para probar que funciona bien.
2. 12 Escriba un programa que calcule la capacidad de un disco duro de un computador que tiene 12.000 cilindros,
16 pistas, 8 sectores por pista y sectores de 512 bytes. Exprese su tamaño en bytes, kilobytes, megabytes y
gigabytes. El tamaño del disco se calcula como:
capacidad = cilindros * pistas * sectores * bytes

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.

2 . 1 3 Escriba un programa que calcule, empezando con xq = 1, la serie:

x¡ — Xi-\ * 3.

Imprima cada resultado hasta i = 5 y, al final, la suma de todos ellos.

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. 15 ¿Qué errores hay en el código siguiente?


#define cilindros = 16;
int capacidad = 3.0;
f 1oat pi stas = 4 . 0 ;
ci 1 indros = 32;

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?

2.2 7 Detecte y corrija los errores del siguiente programa en C.

7 0 • ITES-Paraninfo
Capítulo 2 / Tipos de datos y operadores

#include <stdio.h>
#define PI 3.14156;

int Main()
i
float radio;

printf("Introduzca el radio: ")


scanf("%f", radio);

/* Se calcula el area del círculo


area = PI * radio * Radio;
printfC'El area del circulo es %5.4d\n, area);

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

El objetivo de este capítulo es explicar, mediante la resolución de problemas, la sintaxis y el funcionamiento


de las sentencias de control del lenguaje C. Estas sentencias de control son las que definen el flujo de
ejecución de un programa.
Los temas que se cubren en el capítulo son:
■ Introducción de los tres tipos básicos de sentencias de control: secuencia, selección e iteración.
■ Presentación de las estructuras de selección de C: (i f, i f - el se y swi tch).
■ Presentación de las sentencias de control de C dedicadas a definir estructuras de iteración: (whi 1e, for
y do-whi 1 e).

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


Secuencia Selección Iteración

Figura 3.1 Tipos de estructuras de programació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)

printf("El número es par\n"):


Esta sentencia es completamente idéntica a la anterior y, como puede observarse, la sentencia asociada
al if no es la sentencia pri ntf, sino la sentencia vacía que en C se expresa mediante ’ ;

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

Figura 3.2 Diagrama de flujo de la sentencia if-el se

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

Figura 3.3 Diagrama de flujo de la sentencia whi 1e

I ► EJEMPLO 3.2 ¿Qué hace el siguiente fragmento de código?


whi l e ( 1 )
printf("Dentro del bucle while\n");

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

Figura 3.4 Diagrama de flujo de la sentencia for

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;

for (contador = 1; contador <= N ; contador=contador + 1) (

O lo que es lo mismo:
int contador;

for (contador = 0; contador < N; contador=contador + 1) |

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

Figura 3.5 Diagrama de flujo de la sentencia do-whi 1e

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

El funcionamiento de la sentencia swi tch es el siguiente:


■ En primer lugar se evalúa la expresión del swi tch. Ésta debe dar como resultado un valor entero o un
carácter (en otro caso se producirá un fallo al compilar).
■ Después, el programa empezará a ejecutar a partir de la sentencia case cuya expresión coincida con el
resultado obtenido de la expresión del swi tch. Se ejecutarán todas las sentencias que haya a continua­
ción hasta encontrar una sentencia b r e a k , o s e llegue al final de la sentencia sw it c h completa. Si en el
camino se encuentran más sentencias case, éstas serán ignoradas.
■ Si no se encuentra ninguna sentencia case cuya expresión coincida con el resultado del sw it ch, pueden
ocurrir dos cosas:
• Que no exista una sentencia defa ul t, en cuyo caso finalizará la sentencia swi tch.
• Que exista una sentencia def aul t, en cuyo caso se ejecutarán todas las sentencias que haya a conti­
nuación hasta encontrar una sentencia brea k, o se llegue al final de la sentencia swi tch completa.
La expresión del swi tch debe ir encerrada entre paréntesis para distinguirla del resto del código. Las
expresiones de los case no necesitan paréntesis, pero deben terminar con para distinguirlas del res­
to del código. Las sentencias son instrucciones independientes, si bien pueden sustituirse por secuencias
completas.
Cada swi tch debe terminar con un def aul t. Esta opción por defecto permite realizar acciones siempre
que no se cumplan los criterios de los case, lo que es muy útil para controlar errores.
El diagrama de flujo de la sentencia swi tch está reflejado en la Figura 3.6.

í
^f
expresión

= ex p resió n l =expresión2 = expresión n

Secuencia 1 Secuencia 2 Secuencia n

Figura 3.6 Diagrama de flujo de la sentencia swi tch

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-

Figura 3.7 Influencia d e c o n t i n u e y b r e a k dentro de un bucle

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

Pr o blem a s Resu elto s


► 3.1 Escriba un programa en C que. lea un número entero e indique si el número es par.
Re s o l u c i ó n .
#i ncl ude <s t di o . h>

i n t mai n(voi d)
I
i n t numero;

p r i n t f ( " I nt r oduzca un número; ");


scanf("%d", &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 n t main ( i n t ar gc, char *ar gv[ ] )


i
char ch;

p r i n t f ( " I nt r oduzca un c a r á c t e r : ");

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 ;

pri n t f ( " Int roduzca un n úmero: ");


sea nf (" %d", &numer o ) ;
rai z = s q r t (numero );

i f ( r ai z * rai z == nume ro)


pri n t f ( "%á se puede expr e s a r como %d x %d\n",
numero, rai z , r a i z ) ;
el s e
pri n t f ( "%d no se pu ede expr e s a r como el cuadrado
numero);

r e t u r n (0);

I ► 3.4 Dado el siguiente fragmento de programa, ¿cuál es el resultado final de la variable z?


x = 2; ‘
i f (x != 3)
(
i f (x — 1)
Z = 1;
el se
Z = X;
1
el se
z = 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

Figura 3.8 Diagrama de flujo de las sentencias del Problema 3.4

int a, b; /* coefi ci entes de la primera recta */


int c , d ; /* coefi ci entes de la segunda recta */
float xCorte; /* puntos de corte */
float yCorte;

printf("Primera recta (y=ax+b)\n");


printf(”Introduzca a y b: ");
scanf("Xd Xd". &a, &b);

printf("Segunda recta (y=cx+d)\n");


printf("Introduzca c y d: ");
scanf("Xd Xd", &c , &d);

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

int mairi ( int argc, char *argv[] )


{
int n;
int minimo, maximo, media;
int ret, ntotal, suma;

/* Leer el pritner nùmero */


printf("Introducir un nùmero: ") ;
ret = scanf("%d ",&n) ;
if ((ret == 0 ) || (n < 0 ) )
return (0);

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

printf("Introducir un número: ");


ret = scanf("%d",&n) ;

media = suma / 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

printf("Introduzca un número: ");


scanf("%d", &numero);

while (numero > 0)


i
suma = suma + numero:
numero = numero - 1;

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

Tabla 3.1 Ejecución del fragmento de programa del Problema 3.8

I ► 3.9 ¿Cuántas veces se ejecuta la función p rintf en el siguiente fragmento de programa?


a = 9;
for (i = 0 ; i < 100; i++)
if ((a % 4 == 0) || (i % 2) — = 0)
printf("%d Id \n" , a , i);

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 0 En el siguiente fragmento de programa, ¿cuántas veces se ejecuta la función p r i n t f ?


fo r (i = 1; i <=20; i = i + 2 ) ¡
a = 0;
do I
p r i n t f ( "%d %d\n", i , a );
a = a + 1;
) while (a < 10);
)
1. 90
2. 100
3. 81
4. 110
Re s o l u c i ó n . 100.

► 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,

p r i n t f ( " I nt r oduzca a: ");


i f ( s c a n f ( "%f", &a) < 1) (
p r i n t f ( " Error en la l e c t u r a de a \ n " ) ;
return(O);
1
p r i n t f ( " I nt r oduzca b: ");
i f (scanf("%d", &b) < 1) (
p r i n t f ( " Err or en la l e c t u r a de b \ n ");
return(O);

f o r ( j = 0; j < b; j++)
pot enci a = pot enci a * a;

p r i n t f ( " %f el evado a M = %f\n", a, b, p o t e n c i 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.

Ejecución j a b potenci a j < b


Primera ejecución de for 0 8 3 1 -
Evaluación de condición 0 8 3 1 Verdadero
Primera iteración 0 8 3 8 -
Ejecución de j++ 1 8 3 8 -
Evaluación de condición 1 8 3 8 Verdadero
Segunda iteración 1 8 3 64 -
Ejecución de j++ 2 8 3 64 -
Evaluación de condición 2 8 3 64 Verdadero
Tercera iteración: 1 8 3 512 -

Ejecución de j++ 3 8 3 512 -


Evaluación de condición 3 8 3 512 Falso: Fin

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 ;

printf(" Introduzca un número: ");


if (scanfC'M", &N) < 1) (
printf("Error en la introducción de los datos\n");
return(O);

for ( i = 1; i <= N; i ++) !


for (j = 1; j <= i ; j++)
p r i n t f ( " M ", j) ;
pri n t f ( " \ n " );

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 ;

for(i = 1; i < 10; i++)


for(k = i; k < 10; k++)
pri ntf (" 7 d %d\n", i , k);

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 ;

printf("La suma es %d\n", suma);


printf("Desea continuar [s|n]: ");
scanf("\n%c", &opcion);
1 whi1e (opcion == ’s ’);

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;

p r i n t f ( " Int r oduzca un número e n t e r o : ");


s c a n f ( "%ó",&numero);

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

!► 3. 16 Escriba un programa que imprima la tabla ASCII.


Re s o l u c ió n .
#i nc l ude <s t di o. h>

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

p r i n t f ( " Int roduzca un año: ");


s c a n f C T d " , &anio);

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;

/ * lectura del número */


printf ("Introduzca un número: ");
scanf("%d", &numero);

f actorPrimo = 2;-

printf("%d = ", numero):


while( factorPrimo <= numero ) (
if( (numero % factorPrimo) == 0 )(
printf("%d ", factorPrimo);
numero = numero / factorPrimo;

else
factorPrimo++;

p rint f ("\n " ) ;


return(O);

► 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>

int main ( int argc, char *argv[] )


I
int superior, inferior, intentos;
int secreto, numero;
double escala;
int i ;

srandíti me(NULL));

printf("Introduzca el número superior: ");


scanf("% d", &superi or);

printf("Introduzca el número inferior; ");


scanf("%d",&i nferi or);

printf("Introduzca el número de intentos: ");


scanf("%d",&intentos);

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

for (i=0; idintentos; i++) j

printf("Introduzca un número; ");


scanf(”%d",&numero);

if (numero > secreto)


printf("El número %d es mayor.\n", numero);
else if (numero < secreto)
printfC'El número %d es menor.\n", numero);
else /* son igua 1es */
break;
)
if (i < intentos)
printf("dEnhorabuena! , ha ganado.\n") ;
el se
printfCLo siento, ha perdido, el número era fe.

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>

int main ( int argc, char *argv[] )


(
int superior, inferior, intentos;
int secreto, numero;
double escala;
int i ;

srand(time(NULL));

printf("Introduzca el número superior: ");


scanf ("7od" ,&superi or);

pri ntf(''Introduzca el número inferior: ");


scanf("%d",&inferior);

printf("Introduzca el número de intentos: ");


scanf("%d",&intentos);

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

for (i=0; ifintentos; i++) I

printf("Introduzca un número: ");


scanf("%d",&numero);

if (numero > secreto)


printf("El número % d es mayor.\n",numero);
else if (numero < secreto)
printfC'El número % d es menor.\n".numero);
el se / * s o n i g u a l e s */
break;
1
if (i < intentos)
printf("<Enhorabuena! , ha ganado.\n");
el se
printfC'Lo siento, ha perdido, el número era Xd.\n",secreto);

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;

while (n < 100000) {


pi = pi + (pow(-l, n) / (2*n+D);

/* u n a iteración más */
n ++;

I * el valor real de p i hay que obtenerlo muí t i p l i c a n d o */


/* p o r c u a t r o * /
printf("E1 valor de PI es: %.8f " , 4 * pi);

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

printf ("Introduzca un número: ");


scanf("%f", &numero);

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

if (raiz * raiz > numero)


x2 = raiz;
el se
xl = raiz;

mitad = fabs(xl-x2) / 2.0;


raiz = xl + mitad;

printf ("La raíz aproximada de %f es %f\n", numero , raiz);


return(O);

Pro blem as Pro pu esto s


3 . 1 Escriba un programa que solicite del usuario tres números e indique cuál de ellos es el mayor.
3.2 Escriba un programa que, dada una cierta cantidad de dinero, calcule cuál es el número mínimo de monedas
de curso legal que equivalen a dicha cantidad.
3.3 Dados dos números, escriba un programa que muestre si uno de ellos es múltiplo del otro.
3.4 Escriba un programa que muestre por pantalla si un número dado es o no primo.
3.5 Escriba un programa que muestre por pantalla la lista de los 100 primeros números primos.
3.6 Escriba un programa que calcule la siguiente expresión matemática.

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

A) RealIiz a r una suma


B) Real izar una resta
C) Real izar una mui ti pi icaci ón
D) Real izar una división
E) Sal ir de 1 programa

In1;roduzca :SU Of)c ió n :


Una vez elegida la opción se solicitarán dos números y se mostrará el resultado correspondiente. El progra­
ma sólo debe finalizar mediante la correspondiente opción del menú.
3. 13 Después de ejecutar el siguiente fragmento de programa, ¿cuál es el valor final de la variable k?
int x = 12,■ J = 5, k = 0;
if (0 ==: X % 4)
for (j = 0; j < 10; j = j + 4)
k == k + j ;
el se
for U = 0; j < 10; j = j + 2)
k = k + j;

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 !

Figura 4.1 Organización de la memoria de un computador

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

Tipo Tamaño en bytes


char 1
short 2
int 2 - 4 (normalmente 4)
long 4
f 1oat 4
double 8

Tabla 4.1 Tamaño de algunos datos básicos en C

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

La sintaxis de la definición de una variable de tipo puntero es la siguiente:


tipo_base * variable
El tipo base puede ser cualquier tipo predefinido en C o cualquiera definido por el usuario. El identifi-
cador utilizado para la variable debe ser un identificador válido.
Esta definición se puede leer como sigue: la variable definida podrá almacenar una dirección de memoria
y los datos que se almacenen en dicha dirección de memoria serán tratados como datos del tipo base.
El tamaño en bytes de una variable de tipo puntero viene dado por el tamaño de dirección que utiliza el
computador. Dicho tamaño suele coincidir con el tamaño de palabra de cada computador, y a su vez limita
el tamaño máximo de memoria que puede utilizarse en dicho computador. Por ejemplo, los computadores
de 32 bits suelen utilizar punteros cuyo tamaño es de 4 bytes, o sea 32 bits.
Como se puede observar, cuando se define una variable de tipo puntero no basta con indicar este hecho.
También es necesario indicar a qué tipo de datos se hace referencia, utilizando para ello el tipo base. Esto
es así porque para acceder a un dato en memoria no basta con saber su dirección, sino también su tamaño,
así mientras que la dirección es el valor que se almacena en el puntero, el tamaño del dato se denota con el
tipo base utilizado en la definición del puntero. Este tipo base también se usa para que el compilador pueda
realizar comprobación de tipos cuando se emplee el dato referenciado por dicho puntero como parte de una
expresión.
La Figura 4.2 representa la relación que existe en la memoria entre una variable puntero y el dato al cual
hace referencia.

► EJEMPLO 4.1 Explique el significado de esta definición.


long * refDato;

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

Figura 4.2 Disposición del par puntero-dato dentro de la memoria

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 ____________________________

4.3.1 Operador de dirección


Recuerde que todas las variables constan de un conjunto de celdas contiguas de memoria a las cuales se
hace referencia mediante una dirección de memoria representada por el nombre de la variable.
Para poder obtener dicha dirección de memoria a partir de una variable concreta es necesario utilizar un
operador especial. Dicho operador se conoce como operador de dirección y se expresa con el símbolo &.
La sintaxis del operador de dirección es la siguiente:
& variable
Donde v a r i a b l e hace referencia a una variable cualquiera almacenada en memoria.
Para almacenar las direcciones de memoria obtenidas con este operador será necesario disponer de una
variable de tipo puntero cuyo tipo base sea igual al tipo de la variable original.
i

4.3.2 Operador de indirección


El operador de indirección es el encargado de acceder al dato almacenado en una dirección de memo­
ria, la cual generalmente se obtiene a partir de una variable de tipo puntero.
La sintaxis del operador de indirección es la siguiente:
* expresión
La expresión debe hacer referencia a una dirección de memoria, en caso contrario el compilador devol­
verá un error de compilación.
Este operador realiza la acción opuesta al operador de dirección, permitiendo acceder a los datos locali­
zados en una dirección de memoria. Este operador no se puede aplicar directamente a ninguna variable que
no sea de tipo puntero.

4.3.3 Conversión de punteros y el tipo voi d


Para definir un puntero es necesario indicar el tipo base que referencia. Sin embargo, todos los punteros
son direcciones de memoria exactamente iguales unas a otras. Este hecho permite que en el lenguaje C se
pueda transferir el contenido de una variable puntero a otra incluso si sus tipos bases no son iguales, aunque
como esto suele implicar un error en el programa el compilador suele dar un aviso al compilar. Si se quiere
evitar el aviso del compilador, es necesario indicar explícitamente que dicha operación es correcta. Para ello
se utiliza la siguiente sintaxis.
TipoA * PunteroA;
TipoB * PunteroB;

10 2 • ITES-Paraninfo
Capítulo 4 / Punteros

PunteroA = (Ti poA *) PunteroB


Siendo TipoAyTipoB dos tipos distintos de datos en C.
Esta operación se denomina conversión explícita de punteros. Indica que la asignación del valor de
PunteroB de tipo Ti poB en la variable PunteroA de tipo Ti poA es una operación correcta, evitando el
consiguiente aviso del compilador.
Para facilitar la conversión entre punteros con distintos tipos base, se introdujo en el lenguaje C un tipo
de datos especial, el tipo voi d. Este tipo de datos no representa por sí mismo ningún dato almacenable, de
hecho cuando una función u operando no recibe o no devuelve ningún parámetro, se codifica de forma que
reciba o devuelva un valor del tipo v o id.
Sin embargo, la verdadera utilidad del tipo vo id estriba en utilizar los punteros cuyo tipo base sea el tipo
voi d (por tanto su tipo real sería voi d *). Estos punteros pueden ser utilizados como punteros genéricos
(sin tipo base). Por tanto, será posible convertir cualquier otro puntero a, o desde, un puntero a v o id sin que
el compilador genere ningún aviso, ni informe de ningún error.

4.3.4 El operador sizeof

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.

4.3.5 Puntero nulo


Uno de los mayores problemas en la programación con punteros es la utilización de punteros definidos
sin valor inicial. Estos punteros, al no haber sido inicializados, contendrán valores aleatorios y los resultados
producidos al acceder a su contenido serán del todo impredecibles. Por ello hay que asegurarse de que todos
los punteros sin un dato válido contengan un valor conocido. Así, de esa forma, si se accede por error a su
contenido el error producido será, cuando menos, predecible.
Para ello es necesario seleccionar una dirección de memoria neutra, es decir, que cumpla que ninguna
variable real pueda disponer de esa misma dirección de memoria. La dirección de memoria que cumple estas
características es la dirección con el valor 0. Por convenio es imposible que la dirección de memoria número
0 contenga ningún dato válido relacionado con el programa. De hecho, el compilador está preparado para
que cualquier acceso a la dirección de memoria 0 genere un error de ejecución, terminando con el programa
en curso.
Para facilitar el uso de esta dirección de memoria se utiliza una constante expresamente reservada por el
propio lenguaje y definida dentro de varios de los archivos de cabecera del sistema (por ejemplo std i o . h).
Esta constante utiliza el identificador NULL, definido con valor 0.

4.3.6 Aritmética de punteros


La aritmética de punteros consiste básicamente en tratar los punteros como variables numéricas (que
contienen direcciones de memoria) e incrementar © decreméntar estas variables para acceder a las direc­
ciones de memoria contiguas a la dirección original. La aritmética de punteros permite que se realicen
accesos a los elementos del tipo base almacenados en direcciones contiguas, tan sólo basta con incrementar
o decrementar las direcciones de memoria almacenadas en los correspondientes punteros.
Si sumamos un 1 a un puntero de un tipo base Ti poA, dicho puntero contendrá el valor original del
puntero más el tamaño del tipo base Tipo A en bytes, apuntando, de esta forma, al valor del tipo base
consecutivo en memoria.

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.

4.4.1 Función mal 1oc


El prototipo de la función ma 11 oc se encuentra en el archivo de cabecera stdl ib .h. Su sintaxis es la
siguiente:
void * mal 1oc(int):
La función ma 11 oc tiene como parámetro un valor de tipo int y devuelve un puntero de tipo voi d *.
La función mal! oc recibe como parámetro el tamaño en bytes del bloque de memoria que se quiere
reservar. La función se encarga de buscar una zona de memoria contigua, del tamaño deseado, dentro de
la zona de memoria dinámica. Si la encuentra, devuelve la dirección de memoria de la celda inicial. Dicha
dirección de memoria se devuelve como un puntero genérico (de tipo voi d *). En caso de no encontrar
suficiente memoria para satisfacer la petición, la función devolverá el valor N U L L.

4.4.2 Función f ree


El prototipo de la función free se encuentra en el archivo de cabecera stdl ib.h. Su sintaxis es la
siguiente:
void freeívoid *);
La función free solicita un puntero de tipo void * y no devuelve nada.
El funcionamiento de la función free se resume de la siguiente forma. La función free requiere como
parámetro la dirección de memoria inicial de una zona de memoria reservada en la memoria dinámica. Esta
dirección debe ser tal y como la suministra la función ma 11 oc. La función free se encarga de marcar como
libre dicha zona de memoria y de incorporarla al resto de la memoria dinámica. En caso de que el parámetro
que se le pase sea NULL la función free no hará nada. Si el parámetro es una dirección de memoria que no
ha sido reservada previamente por ma 11 oc los resultados serán impredecibles.

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

short Dato = 0x4141;


long * refDatoLong;
short * refDatoShort;

/* Asignar la dirección de un long al puntero a short */


refDatoShort = &Dato;

/* Convertir un puntero a short en un puntero a long */


refDatoLong = refDatoShort;

printfC'El dato referenciado por refDatoLong es = 0xXx\n", *refDatoLong);


En este caso se puede ver que la variable donde se encuentra el dato real es de tipo short y que su
dirección se almacena en un puntero a short. Posteriormente se convierte dicho puntero en un puntero
a long y por último se muestra el dato referenciado por el puntero a long. Como el tipo long es más
grande que el tipo s ho rt, el dato referenciado será mayor que la memoria reservada. Esto implica que
además de devolver un resultado inesperado se puede producir cualquier tipo de fallo, siendo el resultado
final impredecible.
Sobrepasar la memoria reservada En el caso de utilizar la aritmética de punteros para hacer referencia
a las posiciones contiguas, es necesario asegurarse de que la nueva memoria a la cual se accede está
reservada. En caso contrario, si se accede a dicha memoria se producirán resultados impredecibles.
Observe las siguientes líneas de ejemplo:
short Dato = 0x4141;
short * refDatoShort;

/* Asignar la dirección de un long al puntero a short *!


refDatoShort = &Dato;

!* Acceder a la posición consecuti va *!


printfC'El dato consecutivo a refDatoShort es = 0x%x\n",
*(refDatoShort+1));

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;

/* Asignar la dirección de un long al puntero a short *!


refDatoShort = &Dato;

I* Uso erróneo de la función free */


free(refDatoshort);
Este código define una variable de tipo short y su dirección se almacena en un puntero a short
(refDatoshort). A continuación, intenta liberar la memoria de dicho puntero con la función free.
Pero dado que dicha memoria pertenece a una variable y no procede de la función mal 1 oc, los resulta­
dos que puedan producirse serán impredecibles, y lo que es peor, normalmente no saldrán a la luz hasta
la próxima utilización de las funciones de la memoria dinámica.
No liberar una dirección de memoria obtenida con ma 11 oc cuando no se utiliza Siempre que se reser­
ve una zona de memoria con la función ma 11 oc y ya no se necesite más, será necesario liberarla con
la función free. Si esto no se realiza, dicha memoria quedará inservible y no podrá ser utilizada más
hasta que no finalice el programa. Un caso particular de este problema aparece cuando la dirección de
memoria devuelta por la función mal 1oc está almacenada en un único puntero y se asigna un nuevo
valor a dicho puntero antes de haber liberado la memoria con la función free tal y como demuestra el
siguiente ejemplo.
short Dato = 0x4141;
short * refDatoshort;

/ * asignar memoria dinámica a refDatoshort*/


refDatoshort = ma11oc(sizeof(short));
if (NULL == refDatoShort) {
printf("Error, no hay memoria\n");
return(-1);

/* asignar un nuevo valor a refDatoshort */


refDatoShort = &Dato;
El resultado es que la dirección obtenida de la función ma 11 oc se pierde y, por lo tanto, a partir de ahora
resulta imposible liberar dicha memoria.
Si no se libera la memoria tarde o temprano ésta se acabará y el programa finalizará erróneamente por
falta de memoria.

Pro blem a s Resuelto s


I ► 4 . 1 Escriba un programa que obtenga la dirección de memoria de una variable, la almacene en una variable de
tipo puntero y luego muestre por pantalla su valor.

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.

► 4.2 Indique cuál es la salida por pantalla del siguiente programa


#i nc l ude < s t d i o . h >

i n t mai n ( v o i d )

1 ong Dato = 0;
long * ref Dato;

ref Dat o = &Dato;


p r i n t f C ' 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 a t o ) ;
p r i n t f C ' E l v a l o r de la v a r i a b l e r e f Dat o es = % p \ n " , r e f D a t o ) ;

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

R E S O L U C IÓ N . Este programa es.una modificación de la solución del ejemplo anterior. El comienzo


de ambos programas es idéntico. En ambos programas se dispone de una variable de tipo long (Dato),
inicializada a cero, y de una variable de tipo puntero a long (ref Dato) a la cual se le ha asignado la
dirección de la variable Dato. De esta forma, al imprimir por pantalla la dirección de Dato y el valor de
ref Dat o el resultado ha de ser idéntico.
El programa asigna a Dato el valor 10 y se encarga de mostrar por pantalla el valor de dicha variable.
Después, muestra el resultado de aplicar el operador de indirección a la variable r e f Dat o (*refDato).
Dado que la dirección almacenada en la variable ref Dat o es la dirección de la variable Dato, el resultado

ITES-Paraninfo • 107
Problemas resueltos de C

de aplicar el operador de indirección a la variable refDato debe proporcionar el contenido de la variable


Dato. Por tanto, los dos resultados mencionados arriba deberán ser idénticos (e iguales a 10).
Por último, se modifica el valor de la expresión *refDato asignándole el número 5. Dicha expresión
permite acceder al contenido de la dirección de memoria almacenada en refDato. Después se vuelve
a mostrar por pantalla tanto el valor de Dato como el resultado de la expresión *refDato. Tal y como se
mencionó anteriormente, el puntero refDat o referencia a la variable Dato. Por tanto, el resultado de ambas
operaciones será nuevamente idéntico y, en este caso, igual a 5.

dato 0 dato 10

. refDato =£dato;
L______ _ dato = 10;

refDato ?? refDato

------- -

Figura 4.3 Ejecución del programa de ejemplo

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.

► 4.3 Dado el siguiente fragmento de programa


int a = 0, b = 5;
int *c = NULL, *d = NUIL;

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;

/* Asignar la dirección de un long al puntero a long */


refDatoLong = &Dato;

I* Convertir un puntero a long en un puntero a short *!


refDatoShort = refDatoLong;

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.

► 4.6 Indique el resultado numérico de las siguientes expresiones


■ si zeof(char)
■ si zeof(i nt)
■ si zeof(f 1oa t)
■ si zeof(char *)
■ si zeof(i nt *)
■ si zeof(f 1oat *)

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;

/* Asignar el contenido de Dato a un puntero sin inicial izar */


*refDato = Dato;

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;

/* Asignar el contenido de Dato a un puntero sin inicializar */


/* Generará un error de ejecución (SEGMENTATION FAULT) */
*refDato = Dato;

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

!* Asignar la dirección de un long al puntero a long *!


refDatoLong = HDato;

/* Convertir un puntero a long en un puntero a short */


refDatoShort = (short *) refDatoLong;

printfC'El dato completo es = 0x%x\n\n",*refDatoLong);


printfC'La parte baja es = 0x%x\n\n",*refDatoShort);
printf("La parte alta es = 0x%x\n", *( refDatoShort + 1));
return(O);

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.

!► 4.9 Realice un programa que reserve dinámicamente un dato de tipo long


Re s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>

int main(void)
1
long * refDatoLong = NULL;

/* Reserva memoria para un dato de tipo long */


refDatoLong = mal 1oc(sizeof(1ong)) ;
if (NULL == refDatoLong) {
printf("Error, no hay memoria\n");
return(-l);

(*refDatoLong) = 0x41414141;

printfC'La dirección devuelta por malloc es = %p\n",refDatoLong);


printfC'El dato almacenado en esa dirección es = 0x%lx\n",
*refDatoLong);

/* 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.

► 4. 10 Indique razonadamente qué errores aparecen en este código,


i n t mai n(voi d)
(
int * p r l , *pr2;
i nt d a t l = 5, dat 2 = 4;

*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.

► 4.11 Dada la siguiente definición de puntero:


f l o a t *** ptr;
Escriba un código que, mediante malloc, reserve memoria dinámica para poder almacenar una constante
fl oat referenciada a través de ptr.
Re s o l u c ió n . El código es el siguiente,
f l o a t *** ptr;

ptr = (float **) mal 1oc(sizeof(f1oat **));


*ptr = (float *) mal 1oc(sizeof(f1oat *));
**ptr = (float) mal 1oc(sizeof(f1oat));

***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>

int main (void)


I
char *buffer = NUIL;
int cont;

buffer = (char *)malloc(512*sizeof(char));


if (NULL == buffer) {
printf("Error: no ha memoria\n");
exit(-l);
1
for (cont = 0; cont < 512; cont++) {
printf ("dir: Xp, valor: %c\n",(buffer+cont), *(buffer+cont));
1
free (buffer);
' 1
Lo primero es definir una variable de tipo char * (b uff e r), después reserva memoria para 512 caracte­
res usando la función mal 1oc. El tamaño a reservar será igual a 512 por el tamaño de un carácter (si zeof),
el puntero devuelto se almacena en buffer (si es igual a NULL es que ha habido un error). Por último, se
realiza un bucle con un contador (cont) de 0 a 511 y, usando aritmética de punteros, se imprime la dirección
de cada carácter (buffer+cont) y su contenido (*( buffer+cont )).
Antes de salir del programa conviene liberar la memoria dinámica reservada usando la función f ree.

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

printf ("Tipo a introducir. (C)har, (I)nt o (F)loat: ")


scant ("%c",&tipo);
printf ("Numero de elementos: ");
scant ("%d",&num);

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

Pro b lem a s Pr o p u esto s


4.1 ¿Cuál de las siguientes afirmaciones es falsa?
■ Un puntero representa la dirección de memoria del objeto al que apunta.
■ El operador de indirección (*) permite obtener la dirección de una variable.
■ Si p es un puntero, *p representa el contenido almacenado en la dirección a la que apunta p.
■ El operador & no se puede aplicar a variables de tipo puntero.

4.2 Dado el siguiente fragmento de programa


fl oat a =^ 0.001
fl oat *b;
fl oat *c ;

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.

4.3 En el siguiente fragmento de programa, ¿qué contiene la variable p?


int a ;
int * p ;

p = &a;
a = 1;

4.4 Dadas las siguientes definiciones de variables:


int x
int *pl;
int **p2;
¿Cuál de las siguientes sentencias permite que x tome ef valor 4 de forma correcta?
-t*
*
X

■ Pl = & p2 ; *p2
II

I
~a

■ p2 = &x; *p2 == 4;
■ P? = pl: Pl = & x ; *p2 = 4 ;
■ P2 = &pl ;: pl == & x ; **p2 = 4;

4.5 Dado el siguiente fragmento de programa:

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?

4.6 Después de ejecutar el siguiente fragmento de código:


float n i = 1 0 ;
float n2 = 5;
float *p, *q;

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

4.7 Dado el siguiente fragmento de programa, ¿qué contiene la variable q?


float c;
float * p , * q;

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

Funciones y program ación


estructurada
Problemas resueltos de C

En este capítulo se describe el uso de las funciones en C y la forma de aplicarlas en la construcción de


programas estructurados.
Los temas que se tratan en este capítulo son:
■ Definición y declaración de funciones en C.
■ Llamadas a funciones.
■ Paso de parámetros a una función.
■ Recursividad.
■ Macros.
■ Ámbito de las variables y tipos de almacenamiento.

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.

5.1.1 Definición de funciones


La sintaxis requerida para definir una función en el lenguaje C es la siguiente:
TipoRetorno NombreFuncion (TipoParaml Paraml, .... TipoParamN ParamN)
i
secuencia de definición de variables

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.

5.1.2 La sentencia return

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.

5.1.3 Llamadas a funciones


Para ejecutar una función es necesario realizar una llamada a dicha función desde el código de otra
función (salvo la función mai n que es llamada por el sistema cada vez que se ejecuta el programa). La
sintaxis requerida para realizar una llamada a una función es la siguiente:
NombreFuncion (ExpresiónParaml, .... ExpresiÓnParamN)
El identificador NombreFunci ón debe coincidir con el nombre de la función que se desea ejecutar. Las
expresiones Expresi ónParaml,..., Expresi ÓnParamN deben ser cada una de ellas del mismo tipo que el
parámetro formal que ocupa su misma posición en la definición de la función.
En el momento en el que se evalúe la llamada a dicha función se procederá a ejecutar su código. El
primer paso consistirá en evaluar cada una de las expresiones que forman los parámetros de la llamada. Di­
chos parámetros se conocen como parámetros reales, ya que constituyen el valor real de dichos parámetros
durante la ejecución actual de la función. El valor resultante de evaluar estas expresiones será copiado en
las variables de la función que actúan como parámetros formales.
Una vez instanciados los parámetros formales se procederá a ejecutar el cuerpo de la función. Este
código se ejecutará hasta que se alcance el final de la función o se ejecute una sentencia return.
Una vez alcanzado el final de la función o si se ejecuta una sentencia r e t ur n, el control volverá a la
función que realizó la llamada. Cuando esto ocurra, la llamada a la función será sustituida por el valor
expresado en la sentencia return (si no se ha ejecutado una sentencia return el valor sustituido será
aleatorio) y se continuará ejecutando la función actual.
De esta forma se pueden utilizar las llamadas a funciones como expresiones cuyo resultado será del tipo
que retorna la función.

5.1.4 Declaración de funciones: Prototipos


Con lo visto hasta ahora se puede deducir que si se quiere utilizar una función es suficiente con definirla
para que pueda ser invocada desde cualquier punto del programa. Para poder utilizar una función el com­
pilador necesita conocer el tipo que devuelve la función y los parámetros que acepta, de forma que pueda
comprobar que los tipos de los parámetros reales coinciden con los tipos de los parámetros formales. Cuan­
do una función llama a otra definida anteriormente, el compilador ya lo sabe, pero ¿qué ocurre cuando se
llama a una función que no ha sido definida previamente? El compilador asumirá que la función devuelve

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.

5.2.1 Paso de parámetros por valor


Es el método por defecto utilizado en C y el único que está soportado directamente por el lenguaje. Con­
siste en copiar el valor del parámetro real en el parámetro formal, es decir, utilizar el valor de la expresión
de la llamada como valor de inicio de la variable local que actúa de parámetro formal. La función puede
trabajar con el parámetro formal y modificarlo sin que estos cambios se vean reflejados en el código que
realizó la llamada. De esta forma, los parámetros reales no sufrirán cambios cuando la función finalice.
El paso de parámetros por valor sólo permite utilizar dichos parámetros como datos de entrada, siendo
imposible utilizarlos para devolver resultados o datos de salida.
En el lenguaje C el paso de parámetros por valor es el método por defecto, por tanto para emplearlo
basta con utilizar directamente la facilidad de lenguaje.

5.2.2 Paso de parámetros por referencia


El paso de parámetros por referencia se caracteriza por no transportar el dato correspondiente de forma
directa: lo que se pasa a la función es una referencia a la dirección de memoria donde se almacena dicho
dato. De esta forma, la función utiliza dicha referencia para modificar el dato real, en lugar de trabajar sobre
una copia del dato.
El lenguaje C no dispone de dicha funcionalidad por lo que el programador deberá realizar el paso de la
referencia de forma explícita mediante punteros. El sistema es el mismo que se usa en el resto de lenguajes,
sólo que es el programador el que lo realiza, en lugar de hacerlo el lenguaje.

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>

void incrementar ( i n t *a)


¡
(*a) = (*a) + 1;

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;

printf("Introduzca dos números:'");


scanf("%d", &x);
scanf("%d", &y);

/* 1iamada a la macro */
mayor = maximo(x,y);

printf("El máximo es %d\n", mayor);

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.

I ► e j e m p l o 5 .4 Comente la salida del programa siguiente.


#i nclude <stdi o .h>
void imp ri mi r( )
i
t
stat ic int vari
int va r2 = 0 ;
vari = vari + 1
var2 = var2 + 1
pri n t f ("vari =
l
f
int main (void)
i
i
int i ;
for (i=0 :i < 3;
imprimi r( ) ;

Re s o l u c ió n . La salida del programa es la siguiente:


va rl = 1, var2 = 1
va rl = 2, var2 = 1
vari = 3, var2 = 1
Esto es así porque la variable vari, al ser estática, conserva el valor entre las diferentes llamadas a la
función impri mi r. En cambio, la variable va r2, al ser automática, no conserva el valor entre las diferentes
llamadas.
El ámbito de una variable define la visibilidad de la misma, es decir, desde dónde se puede acceder a
dicha variable. En cuanto al ámbito, las variables pueden ser locales o globales.
Una variable local es la que se define dentro de una función. El ámbito de una variable local se restringe
a la función en la que se define, por tanto no es visible desde fuera de la función. Las variables locales pueden
ser, como se ha visto anteriormente, automáticas o estáticas (según incluyan o no la partícula stati c). En
el primer caso, las variables se crean cuando se llama a la función y se destruyen cuando la función acaba.
En el segundo caso, el valor de la variable perdura de una ejecución de la función a otra.
Una variable global es la que se define fuera de las funciones, por tanto puede ser accedida desde
cualquier función. Las variables globales son siempre estáticas.

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.

Pro blem as Resueltos


► 5.1 Realice una función que imprima por pantalla el doble del número que recibe como parámetro.
Re s o l u c ió n .
/*
* Función que muí tipli ce un número pesado como argumento por 2
* e imprime el resultado
*/
void duplicar (int numero)
(
/*
* Secuencia de declaración de variables
*/

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

/* No hay valor de retorno porque el tipo de retorno es void */

int main (void)


i
duplicar (5);

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;

/* Fin de la función y su valor de retorno *1


return(aux);
I

int main (void)


(
int result;

result = duplicar (5);

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;

r e s u l t = medi a (1, 5, 3);

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;

for(i = 0 ; i < pow; i++)


ret_val *=
* val;

return(ret_val);

int main(void )
1
double result;

result = potencia(100.0, 2);

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;

printf("Introduzca el num: ");


scanfC'Xf", &x);

/* 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

printfC'El máximo es %f\n", mayor);

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;

pri n t f ( "Introduzca el num: '');


s c a n f ( " % f ", &x);

/* ñamada a la función */
mayor = máximo! x, (x*x) );

printfC'El máximo es %f\n”, mayor);

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>

int max (int x , int y)


int mi n (int X , int y)

int mai n (vo id)


i1
int numl ,num2;

pri ntf ("Introduci el primer numero: ");


sea nf (" %d" ,numl);
pri ntf (" Introduci el segundo numero: "):
sea nf (" %d" ,num2);

pri ntf ("El maxi mo es: %d\n",max(numl,num2));


pri ntf ("El mínimo es: %d\n",min(numl,num2));
ret urn (0);
1

int max (int x , int y)

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

int min (int x, int y)


{
if (x > y) {
return (y);
) else I
return (x);

I ► 5. 10 Indique razonadamente cuál será la salida por pantalla del siguiente programa.
#include <stdio.h>

void función(int a, int b);

int main(void)
{
int x = 2;
int y = 3;

printf("Antes de la llamada: %d %d\n", x, y);

funcion(x, y);

printf("Después de la llamada: %d %d\n", x, y);

return(O);
1

void función(int a, int b)


(
a = 0;
b = 0;

printfC Dentro de la función: %d %d\n", a, b);

return;

R E S O LU C IÓ N . Cuando se ejecuta este programa se obtiene la siguiente salida:


Antes de la llamada: 2 3
Dentro de la función: 0 0
Después de la llamada: 2 3
Como podrá comprobar aunque dentro de la función se modifican los parámetros formales, los paráme­
tros reales x e y que se pasan a la función permanecen sin cambios después de la llamada. El funcionamiento
del programa se puede apreciar mejor en la Figura 5.1.

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;

p r i n t f (" Antes de la l l amada: %d %d\n", x, y);

f u n c i ó n (x, y) ; -------------

p r i n t f ( " D e s p u é s de la l l a m a d a '%.d %d\n", x, y);


r e t u r n (0);

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;

p r i n t f (" D e n t r o de la función: %d %d\n", a, b ) ;


return;

Figura 5.1 Paso de parámetros por valor del Programa 5.10

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>

void incrementar(int *a);


*

i n t mai n(voi d)
i
i n t x = 5;
printf("Antes de la función: %d\n", x);

incrementar(&x);

printf("Después de la función: %d\n", x);


return;

void incrementarCint *a)


I
*a = *a + 1;
printfC Dentro de la función: %d\n", *a);

RESOLUCIÓN. Cuando se ejecuta este programa se obtiene la siguiente salida:


Antes de la función: 5
Dentro de la función: 6
Después de la función: 6

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;

p r i n t f (" Antes de la función: %d\n", x);

Se copia Sx en a

a. i &X

X 5

Figura 5.2 Paso de parámetros por referencia del Programa 5.11

Observe en el programa anterior el prototipo de la función:


void incrementar(i n t *a);
La función no devuelve ningún tipo y acepta como parámetro un puntero a int, es decir, la dirección de
memoria en la que se almacene un valor de tipo i nt . Esto quiere decir que la función trabaja internamente
con una dirección de memoria y, por tanto, es capaz de modificar el contenido de dicha posición.
La función incrementar modifica el valor de la variable x. utilizando para ello el puntero almacenado
en el parámetro formal a y el operador de indirección (*a). El comportamiento de este programa se ilustra
en la Figura 5.2.
En la Figura 5.2, lo que se pasa a la función no es el valor de x, sino la dirección de memoria en la
que se encuentra x. Una dirección de memoria se almacena en C en variables de tipo puntero, por ello el
parámetro formal de la función es de tipo puntero a int.
Cuando se realiza la llamada, lo que se copia en el parámetro formal a es una dirección de memoria,
la dirección de la variable x (&x). La función incrementar trabaja internamente con la variable de tipo
puntero a. La expresión:
*a = *a + 1;
Lo que hace es incrementar el valor que se encuentra en la dirección de memoria almacenada en a, que
en este caso coincide con la variable x.

!► 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>

int factorial (int dato)


i
int resultado;

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;

printf ("introduzca un número; ");


scanf("%d",&valor);

fact = factorial(valor);

printf ("El factorial es %d\n", fact);

return (0);

R E S O LU C IÓ N . Para exponer claramente una ejecución típica de la función factori al se ejecutará la


llamada factorial (3) y se verá cuál es la traza de ejecución que se genera.
Ejecución de la llamada factorial (3)
dato == 3

-> int resultado;

-> if (1 >= dato) {

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

-> resultado = dato * factor!al(dato - 1);

Ejecución de la llamada factorial(dato -1) => factorial(2)


Durante la ejecución de la llamada factori al (3) se asigna el valor 3 al parámetro formal dato. A
continuación, ejecuta una sentencia if la cual comprueba si el valor de dato es menor o igual que 1. Como
dato es mayor que 1 se ejecuta la rama de el se. La sentencia situada en la rama del else calcula el
factorial de dato a partir del factorial de ( dato - 1). Esto implica que primero es necesario ejecutar la
llamada factorial (dato - 1) antes de poder terminar la llamada actual. Como en esta llamada d ato es
igual a 3 , la llamada concreta a realizar es factorial (2).

Ejecución de la llamada factorial(2)


dato == 2

-> int resultado;

-> if (i >= dato) j

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

Ejecución de la llamada factorial(dato -1) => factori a 1(1)


Al ejecutar la llamada factorial (2) se sigue el mismo proceso que con la llamada anterior. La única
diferencia es que ahora el valor del parámetro formal es 2. Como de todas formas no se cumple la condición
del if se vuelve a ejecutar la rama correspondiente al el se. De esta forma es necesario, de nuevo, calcular
la llamada factorial(dato - 1) antes de terminar la llamada actual. Como en esta llamada dato es
igual a 2, la nueva llamada a realizar es factorial (1).

Ejecución de la llamada factorial(1)


dato == 1

-> int resultado;

-> if (1 >= dato) I

dato == 1 ==>> (1 >= dato) == verdadero ==>> ejecutar bloque del if

-> /* por esta rama finaliza la función recursiva */


-> resultado = 1;
.-> 1

dato == 1, resultado == 1;

-> return(resultado);

Fin de la llamada factorial(1) ==>> retorno de función == 1

Continuar la ejecución de la llamada anterior {factorial(2)1


Al ej ecutar la llamada factorial (1) se vuelve a seguir el mismo proceso que con la llamada anterior.
Pero ahora el valor del parámetro formal es 1. Por tanto, la condición del if sí se cumple y la rama que se
ejecuta es la suya. Dicha rama ejecuta una sentencia que asigna a la variable resultado el valor 1.

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

-> resultado = dato * factorial(1) = 2 * 1

dato == 2, resultado == 2

-> return(resulfado);

Fin de la llamada factorial(2) ==>> retorno de función == 2

Continuar la ejecución de la llamada anterior (factorial(3)1


Una vez finalizada la llamada f actori al (1) se termina de evaluar la sentencia donde estaba situada.
Dicha sentencia asignaba aresultadoel número obtenido al multiplicar el valor de dato con el resultado
de la llamada f actori al (1). Dado que dato es igual a 2 y el resultado de la llamada factor ial (1) es 1,
el valor de resul t ado será igual a 2.
Cuando termina la sentencia if se ejecuta la sentencia return y se termina esta llamada. El valor que
se devuelve es el que se indicó en la sentencia r et ur n. En este caso el valor de retomo es el valor de la
variable resultado. Por tanto, el retomo de llamada factorial (2) será igual a 2.
A continuación se reanuda la ejecución de la llamada anterior, en este caso la llamada factorial (3).
Se reanuda la ejecución de la llamada factorial(3)

dato == 3, factorial(2) == 2

-> resultado = >dato * factorial(2) = 3 * 2

dato == 3, resultado == 6

-> return(resultado);

Fin de la llamada factorial(3) ==>> retorno de función == 6


Una vez finalizada la llamada factor ial (2) se continúa evaluando la sentencia donde estaba situada.
Dicha sentencia asignaba a resultado la multiplicación del valor de dato y el resultado de la llamada
factorial (2). Dado que dato es igual a 3 y el resultado de la llamada factori al ( 2 ) es 2, el valor de
resultado seráigualaó.
Por último, se ejecuta la sentencia returnyse termina la llamada. El valor que devuelve esta llamada
es el que se indicó en la sentencia return. En este caso el valor de retorno es el valor de la variable
resul tado, que es 6.

► 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

void func2 ( int num )


(
if ( num > 0 )
func2(num/2);
printf("%d, ",num%2);

R e s o l u c i ó n . La salida de f uncí (13) es:


1. 0, 1, 1, o,

La salida de func2( 13) es:


0, 1, 1, 0, 1,
Los resultados son inversos porque en f un c 1 se imprime el resultado antes de la llamada recursiva y en
f unc2 se imprime después.

► 5.15 Escriba una macro que devuelva el valor entero más cercano a un número real.
Resolución.
#include <stdio.h>

#define round(x) ( ((x ) - (float)((int)(x)) < 0.5) ? \


C(int)(x)) : \
((int)(x-1)) ) \

int main (void)


¡
float num;

printf ("Introduzca un número real: ");


scanf ("%f",&num);
printf ("El redondeo de es %d\n",num,round(num));
% f

► 5. 16 Escriba una macro para intercambiar el valor de dos variables.

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;

intercambi ar(a ,b);

ITES-Paraninfo • 137
Problemas resueltos de C

!► 5. 17 Indique de forma razonada la salida por pantalla del siguiente programa.


int varGlobal = 0;

/* función auxiliar */
void funAux (void)
I
int varLocal = 0;
static int varEstatica = 0;

/* incrementar las tres variables *t


varLocal = varLocal + 1;
varGlobal = varGlobal + 1;
varEstatica = varEstatica + 1;

printf ("varLocal = %d , varGlobal = %ú, varEstatica = %d\n",


varLocal, varGlobal, varEstatica);

int main(void)

int varLocal = 0;
static int varEstatica = 0;
int i ;

for (i = 0 ;i < 3 ;i++){


1* incremen tar las tres var
varLocal = varLocal + 1 ;
varGlobal = varGlobal + 1;
va rEstati ca = varEstatrica +
/* 7 1amar a la función */
funAux;
1
Ì
ret urn (0 ) ;

R e s o l u c i ó n . Cuando se ejecuta este programa se obtiene la siguiente salida;

varLocal = 1, varGlobal = 2, va rEstati ca = 1


varLocal = 1, varGlobal = 4, va rEstati ca = 2
varLocal = 1, varGlobal = 6, va rEstati ca = 3
Veamos una por una cada variable:
■ Existen dos variables denominadas varLocal, una en la función f un A ux y otra en la función m a in , las
dos son variables locales automáticas. Eso quiere decir que sólo se pueden acceder desde su correspon­
diente función y que se crean de nuevo en cada llamada a la función. De esta forma el valor siempre es
igual a 1, dado que en cada llamada a la función funAux se crea de nuevo la variable y se inicializa a 0,
luego se incrementa en 1 y se imprime.
■ Existen dos variables denominadas varEstatica, una en la función f unA ux y otra en la función m a in,
las dos son variables locales estáticas. Eso quiere decir que sólo se pueden acceder desde su correspon­
diente función y que se crean en la primera llamada a la función, manteniéndose su valor durante toda la
ejecución. Así, su valor se incrementa en 1 en cada interacción dado que la variable se crea en la primera
llamada a la función funAuxyse inicializa a 0, y luego, en las siguientes llamadas, se incrementa en 1
y se imprime.

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 escalar (int xl, int yl, int zl,


int x2 , int y 2 , int z2 );
void vectorial (int xl , int yl, int zl,
int x2, int y2, int z2,
int *x , int *y , int *z);

int main (void)

int xl,x2,yl,y2,zl,z2;
int x,y,z ;

printf ("Introducir la coordenada x del vector 1: )


scanf ("Xd",xl);
printf ("Introducir la coordenada y del vector 1: )
scanf ("%d ",y 1);
printf ("Introducir la coordenada z del vector 1: )
scanf C M ' . z l ) ;
printf ("Introducir la coordenada x del vector 2: )
scanf ("%d",x2);
printf ("Introducir la' coordenada y del vector 2: )
scanf ("%d",y2);
printf ("Introducir la coordenada z del vector 2: )
scanf ("íd" ,z2);

printf ("El producto escalar es: %d\n",


escalar(xl,yl,zl,x2,y2,z2));
vectorial (xl,yl,zl,x2,y2,z2,&x,&y,&z) ;
printf ("El producto vectorial es: (%d,%d,%d)\n" ,x,y ,z);
return (0);

int escalar (int xl, int yl, int zl,


int x2, int y2, int z2)
(
return ((xl*x2) + (yl*y2) + (zl*z2));

void vectorial (int xl , int yl, int zl,


int x2, int y2, int z2,

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

* Esta función devuelve el número de puntos de corte


* entre las dos circunferencias (-1 es infinito).
*!
int puntoCorte (double xl, double yl, double rl,
double x2, double y2, double r2 )
I
double dist, sumaRadios;
int result:

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;

/* Recogiendo los datos del usuario */


pri ntf ("Ci rcunferenci a 1. Introduzca la X: ")
scant ("%lf", &xl);
pri ntf ("Ci rcunferenci a 1. Introduzca 1 a Y: ")
scant ("%1f", &y 1);
p r int f ("Ci rcunferenci a 1. Introduzca el radi o
scant ("% 1f" , & r1) ;

pri ntf ("Ci rcunferenci a 2. Introduzca 1a X: ")


scant ("%lf", &x2);
pri ntf ("Ci rcunferenci a 2. Introduzca 1a Y: ")
scant ("%lf", &y2);
p rint f ("Ci rcunferenci a 2. Introduzca el radio
scant ("%lf", &r2);

/* Calculando los puntos de corte */


estado'= puntoCorte (xl,yl,rl,x2,y2,r2);

!* 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.

Pro blem as Pro pu esto s


5 . 1 ¿Cuál de las siguientes afirmaciones es falsa?
1. La ejecución de una función finaliza con la sentencia re t u rn.
2. Los argumentos que se utilizan en la definición de una función se denominan parámetros formales.
3. Una función sólo puede devolver un valor.
4. Todas las funciones deben devolver siempre un valor.

5.2 ¿Cuál de las siguientes afirmaciones es falsa?


1. Una función sólo puede devolver un valor.
2. El ámbito de una variable estática es local a la función donde se define.
3. Los argumentos que se utilizan en la llamada a una función se denominan parámetros reales.
4. Una función no puede devolver un valor de tipo puntero.
5.3 Escriba una función que acepte como parámetro un número entero N y calcule 1 + 2 + 3 + -- - + 1V.
5.4 Escriba la función:
int esBisiesto(int arrio);

Que devuelva 1 si el año pasado como argumento es bisiesto y 0 en caso contrario.


5.5 Escriba una función que acepte como parámetro una letra minúscula y devuelva la correspondiente ma­
yúscula.
5.6 Escriba la función:
void ordenar(int x, int y, int z, int *nl, int *n2, int *n3);

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

En general, tratar con un array implica trabajar elemento a elemento.


El acceso a los elementos de un array se realiza mediante un índice. El índice utilizado para acceder a
un elemento individual del array debe ser una expresión de tipo entero.
El índice del primer elemento de un array en el lenguaje C es siempre cero (0). Por ello, el índice de un
elemento en el array es una unidad menor que su ordinal, y por tanto, si un array tiene n elementos entonces
el índice del primero es el cero (0) y el índice del último es n-1:
Si se usa un valor incorrecto como índice (-1, -2, ... ó 20, 21, ...), se accederá a una posición memoria
no asignada con anterioridad, y por tanto, se leerá un valor incorrecto o incluso es posible que termine la
ejecución del programa si la zona de memoria a la que se accede está protegida (véase la Figura 6.1). El
lenguaje C no realiza comprobaciones de índices, por tanto debe comprobar siempre que se utiliza el índice
de forma correcta.

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

Figura 6.1 Indices para acceder a los valores de un array de 20 elementos

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

/* lectura de las temperaturas medias de los meses */


for (j = 0; j < NUMER0_MESES; j++) I
printf("Introduzca la temperatura del mes %d: ", j+1);
if (scanf("%f", &temperaturaMeses[j]) < 1)1
printf("Error en la introducción de datos\n");
return(0);

/* se calcula la temperatura media */


for (j = 0; j < NUMER0_MESES; j++)
temperaturaMedia = temperaturaMedia + temperaturaMeses[j];

temperaturaMedia = temperaturaMedia / NUMEROJMESES;

ITES-Paraninfo • ¡ 4 7
Problemas resueltos de C

printfCLa temperatura media anual es %f\n", temperaturaMedi a );

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.2.1 El número de elementos en un array


Al contrario que otros lenguajes de programación, el lenguaje C no tiene operadores específicos para
trabajar con todo el array de una vez. Por ello, hay que recordar que para asignar, comparar, etc., hay que
trabajar elemento a elemento.
Cobra especial importancia conocer el número de elementos presentes en el array en un instante dado,
puesto que en la definición sólo se indica el número máximo de elementos a almacenar. Para conocer el
número de elementos presentes en el array hay dos estrategias:
■ definir, a la vez que ei array, una variable entera que almacene el número de elementos, o bien,
■ utilizar un valor que nunca se almacenaría en el array como indicador del último elemento.
La segunda opción es delicada y hay que estar muy seguros de que el valor elegido no pertenece al
dominio de la aplicación, ya que en este caso no se podría diferenciar este indicador del resto de posibles
valores.

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]

Figura 6.2 Disposición en memoria de un array

■ Las expresiones * ( a r r a y + i ) y a r r a y [ i ] representan el contenido de la posición i del array.


Para los punteros puede usarse la notación asociada a los arrays, siempre que el puntero apunte a una
zona de memoria válida:
■ La zona de memoria correspondiente a un array. Esto se puede apreciar mejor en la Figura 6.3.

i n t V [5] ;

II
i n t *p; p = NULL;

<
►f V [ 0 ] ; v ► V[0] V ► V[0] ¡

V[l] V[l] 1 V[1]


V[2] V[2] v [ 2] 1
V [ 3] V [ 3] !
, V t3] i
' V[4] ^ ! V[4] V [4 ]

5 r ? ""i p j NULL : P &v Eo] ¡

v ►j V [0 ] I«-- .
V[1] '
V +2 == p + 2 ► __V [2 ] !
y [3 r 1
: v[4] 1

p ' &V[0] i

Figura 6.3 Relación entre punteros y arrays

■ 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++) (

Y puede expresarse de la siguiente forma, usando un puntero:


i n t a r r a y [10] ;
i n t *punt ero ;
f o r ( punt er o = a r r a y ; (^punt er o) != -1; puntero++) !

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

void f (int a[], int n )


I
printf("sizeof (a ): %d\n", si zeof(a ));
pri ntf("si zeof(b): %d\n", n);
)

int main (void)


(
f(b,sizeof(b));
return (0);
1
Cuando se ejecuta este programa se obtiene la siguiente salida:
sizeof(a): 4
sizeof(b): 20
Como se puede ver tras la ejecución del programa, en la función f, el resultado de si zeof ( a ) es 4,
tamaño equivalente a un puntero de 32 bits (4 bytes). Por tanto, para la función es desconocido el número
de elementos que tiene que procesar.
Para resolver este problema se pueden usar las dos técnicas comentadas anteriormente: almacenar un
valor en el array que indique que no hay más elementos o usar una variable extra para almacenar el número
de elementos presentes en el array.

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>

#define MAX_ELEMENTOS 100

/* prototipos de las funciones */


void LeerArray(int v[], int *n);
void EscribirArray(const int v[], int n);

/*
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;

printf("Introduzca un elemento: ");


ret = scanfC'Zd", &valor);
while (ret != EOF && j < MAX_ELEMENT0S) (
if (ret < 1)
printf("Error en la introducción del dato\n");
else {
v[j] = valor;
J++:

printf("Introduzca un elemento: ");


ret = scanfCZd", &valor);
1
/* se devuelve también el número de elementos leídos */
*n = j ;
return;

void EscribirArray(const int v[], int n)


I
int j;

for(j = 0 ; j < n; j++)


printfC'El valor del elemento %d es %d\n", j, v[j]);

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]

Figura 6.4 Arrays dinámicos con punteros

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.

6.5.1 ¿Qué ocurre cuando un array dinámico debe crecer de tamaño?


Cuando se reserva espacio para un array dinámico, éste se comporta como un array normal, es decir, su
tamaño queda prefijado. Si se necesita más espacio para un array se puede utilizar la función de biblioteca
real 1oc cuyo prototipo es el siguiente:
void * reallocívoid *ptr, int n);

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>

char *leeMensaje ( void )


1
char *pchar ;
char *nuevo_pchar ;
int nchar;
int ultimo;

/* Ini ci al mente, el número de caracteres (nchar) es uno *!


nchar=l;

/* El Indice del último carácter es una unidad menos (ultimo) */


ul timo=0;

/* Se reserva memoria para un único carácter *!


pchar=malloc( sizeof(char) * nchar );
if (pchar == NULL) I
perrorC"mal loe:");
return NULL;

/* Se lee un carácter */
scanf("%c", &(pchar[ultimo]));

/* Se lee el resto de caracteres */


while (pcharlultimo] != ’\n’)
I
/* Se incrementa el número de caracteres */
nchar ++;

/* Se aumenta el espacio de memoria *!


nuevo_pchar = real 1oc(pchar, sizeof(char) * (nchar) );

I* Si se produce un error, se libera


* la memoria reservada y finaliza la
* ejecución de la función
*(
if (nuevo_pchar == NULL) I
perror("real loe:");
free(pchar);

15 4 • ITES-Paraninfo
Capítulo 6 / Arrays y strings

return NULL;

/* Se incrementa el índice del último.


* También se actualiza el puntero ’pchar’
*!
ultimo ++; ■
pchar = nuevo_pchar;

/* Se lee un carácter */
scanf ("%c", &(pchar[ultimo]));

/* Se cambia el carácter ’\ n ’ por ’\ 0 ’ */


pchar[ul timo] = ’\0’;

/* Devolver el array leído' */


return pchar ;

int main (void)


i
char *msg ;
int i ;

/* Se lee una línea por la entrada estándar */


printf("Introduzca un mensaje terminando con la ");
printf("tecla <Intro>: \n");
msg = 1eeMensaje() ;

/* Si no es NULL, se imprime el array de caracteres */


printfC'El mensaje es:\n");
if (msg != NULL) {
for (i= 0 ; msg[i] != ’\0’; i++) f
pri ntf("%c",msg[i]);

pri ntf("\n");

/* Liberar la memoria usada */


free(msg);

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.

6.6.1 Inicialización y acceso a un array multidimensional


Considere ahora la siguiente definición de una matriz de 4 filas y 2 columnas:
int matriz [ 4 ] [2];
Si se conocen los valores de la matriz, se puede inicializar de forma similar a un array:
int matriz[4][2] = {
11, 12
21, 22
31, 32
41, 42

Para acceder al segundo elemento de la tercera fila se utiliza la siguiente expresión:


matri z[2][1]
Como se muestra en la Figura 6.5, el índice de una dimensión toma valores de la misma manera que lo
hace un vector: desde cero hasta el número de elementos menos uno.

[ 0 ] [0 ] [0][1]
[1 ] [0] [1] [1]
[2] [0] [2 ] [1]
[3 ] [0] [3] [1]

Figura 6.5 Distribución de los índices de una matriz

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

ìnt matriz[4][2]; [0] [0]


[0 ] [1 ]
[0 ][0] [ 0 ] [1]
[1] [0]
[1 ][0] [ 1 ] [1]
[1] [1]
[2] [0] [ 2 ] [1]
[ 2] [ 0]
[3 ] [0] [ 3 ] [1]
[2] [1]

[3] [0]

i [3] [1]

F igura 6.6 Disposición de matri z en memoria

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;

I* se calciala la posición del minimo elemento */


for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
. if (matriz[i][j] < minimo) {
minimo = matriz[i][j];
iMin = i ;
j M in = j ;

printfC'El minimo elemento %d esta en %d %d\n",


mi ni mo , iMin, jMin);

ITBS-Paraninfo • 15 7
Problemas resueltos de C

return (0);

6.6.2 Arrays multidimensionales como parámetros de funciones


La diferencia al definir un array multidimensional como parámetro de una función con respecto a los
arrays unidimensionales es que es necesario especificar el tamaño de todas las dimensiones, pudiendo op­
cionalmente omitirse el tamaño de la primera:
void funcion_ejemplo (char array5D[][2][4][8][16])

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.

6.6.3 Arrays multidimensionales dinámicos


Al igual que ocurre con los arrays unidimensionales, también se pueden usar punteros para definir arrays
multidimensionales de manera dinámica.
El proceso de definición de un array multidimensional dinámico consiste en utilizar un nivel de indirec­
ción por cada dimensión del array. De esta manera, si, por ejemplo, hay dos dimensiones en el array, hay
que definir un puntero a puntero.
Se va a describir dicho proceso a continuación para crear de forma dinámica una matriz de 4 filas y 2
columnas. El primer paso consiste en definir una variable (m en nuestro caso), de tipo puntero a puntero a
i n t (un nivel de indirección por cada dimensión):
i n t ** m;
El siguiente paso consiste en reservar memoria para cada dimensión, empezando por la primera dimen­
sión. Si la variable m es de tipo i n t **, entonces un elemento de la primera dimensión como m[ i ] es de
tipo i n t *. Por tanto, hay que reservar espacio para cuatro filas (cuatro punteros a entero):
m = ( i n t **) mal l oc(4 * s i z e o f ( i n t * ) ) ;
La ejecución de la sentencia anterior se puede apreciar en la Figura 6.7.
Como cada uno de los componentes m[ i ] es un puntero a i n t entonces su tamaño en bytes viene dado
por la expresión si zeof( i nt *).
El tercer paso (y último para una matriz) consiste en reservar espacio para cada uno de los elementos
presentes en la segunda dimensión:
for (j = 0; j < 4; j++) (
m[j] = ( i n t *) mai l oc(2 * s i z e o f ( i n t ) );
}
El fragmento de código anterior muestra cómo, para cada fila, se reserva memoria para almacenar los
valores de todas las columnas.
La ventaja añadida que presenta una definición dinámica frente a una estática en el caso de un array
multidimensional es que pueden crearse matrices donde cada fila tiene un número de columnas diferente a
las demás filas.
Cuando se quiere liberar la matriz dinámica es necesario realizar el recorrido inverso, utilizando la
función de biblioteca f re e.
En el ejemplo anterior, primero se libera el espacio usado para las columnas y luego el usado para las
filas:
/* Liberar espacio reservado para las columnas */
for (j = 0; j < 4: j++) (
free(m[j]);

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

f o r (j=0; j < 4; j++)


MIO] m[j] = (int*)malloc(2*sizeof(int));
M[l]
M[2]
M[3] ^ t M[0]
M[l]
M[2] -
----- M[3]

meo] ro]
M [ 0 ] [1]

*• M[l] [0]
M [ l ] [1]

M[2] [0]
M[2] [1]
--- *■ M[3] [0]
M [ 3 ] [1]

Figura 6.7 Creación de una matriz de forma dinámica

/* Liberar espacio reservado para las filas *t


free(m);

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.

6.7.1 Representación de cadenas de caracteres


En el lenguaje C, una cadena de caracteres se representa como un array de caracteres. El valor de una
cadena de caracteres se escribe entre dobles comillas:
"Hola"
Y representa a la siguiente secuencia de caracteres:
’H ’ ’o ’ ’1 ’ ’a ’ ' \0 ’
Por convención, todas las cadenas de caracteres en C finalizan con el carácter nulo de C (’\0 ’). Este
carácter indica el fin de una cadena de caracteres. Si el array no incluye este carácter al final no se conside­
rará como una cadena de caracteres, sino como un simple array de caracteres.
La longitud de la cadena "hola" es igual a 4 caracteres (no se incluye el carácter nulo para el cálculo
de su longitud), sin embargo, la cadena ocupa en memoria el espacio de 5 caracteres.

ITES-Paraninfo • 159
Problemas resueltos de C

En un string se pueden utilizar los siguientes tipos de caracteres:


■ Especiales, como el retorno de carro ( ’ \ n ’),
■ Imprimibles, como la letra a ( ’ a ’),
■ No imprimibles, como por ejemplo el sonido del altavoz ( ’ \ a ’).
Estos caracteres pueden expresarse de varias maneras:
■ Como carácter: ( ’ a ’)
■ Su valor decimal en la tabla ASCII: (\97)
■ Su valor octal en la tabla ASCII: (\0141)
■ Su valor hexadecimal en la tabla ASCII: (\0x61).
Por ejemplo, la siguiente cadena:
"\a A1erta\32\n"
representa un pitido por el altavoz ( ’ \ a ’), junto con la palabra "Al er t a" seguida de un espacio en blanco
( ’ \ 32 ’) y un retorno de carro ( ’ \ n ’).
Es importante diferenciar entre un carácter individual y una cadena:
■ ’ a ’ representa un carácter individual, que ocupa en memoria el espacio de un carácter.
■ "Hola" representa una cadena de caracteres, que ocupa en memoria el espacio de 5 caracteres ( ’ H’ ,
ó ’ , ’ 1 ’ , á ’ , ’ \0 ’).
■ " a " representa una cadena de caracteres compuesta por un único carácter, que ocupa en memoria el
espacio de 2 caracteres ( ’ a ’ , ’ \ 0 ’).

6.7.2 Definición y manipulación de cadenas de caracteres


Una cadena de caracteres se define como un array de tipo char:
char cadenal[] = "Hola";
char cadena2[10] = "Hola":
Como en los demás tipos de arrays, en el primer caso el número de elementos lo toma del valor a
almacenar:
’H' ’o ’ 'I' ’a ’ ’\0’
Es decir, cinco, mientras que en el segundo caso se reserva memoria para diez caracteres y se da valor a
los cinco primeros solamente.
Ambas definiciones podrían realizarse también así:
char ca de na l [ ] = ( ’H’ , ’o ’ , ’1 ’ , ’a ’ , ’ \ 0 ’ };
char cadena2[10] = { ’H’ , ’o ’ , ’1 ’ , ’a ’ , ’ \ 0 ’ };
También pueden usarse punteros por su estrecha relación con los arrays, como se ha comentado ante­
riormente. Los punteros se pueden utilizar para:
■ Recorrer los caracteres de un string.
■ Crear strings dinámicos.
Así, por ejemplo, en el siguiente fragmento de código:
char cadenalt] = "Hola";
char cadena2[10] = "Hola";
char *cadena3 = "Hola" ;

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

char * const cadena3 = "Hola";


Lo que define cadena3 como una constante.
Para acceder a un carácter de la cadena se utiliza el operador [ ] como en cualquier otro tipo de array.
En el siguiente ejemplo, definimos un string que contiene la cadena de caracteres "marta";
char cadena[] = "marta";
char carácter;

¡*
* 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>

#def ine MAX_CADENA 100

int main(void)
{
char cadena[MAX_CADENA];
int longitud = 0;

printf("Introduzca una cadena de caracteres; ");


scanfC'Zs", cadena);

while (cadena[longitud] != ’\0 ’)


1ongi tud++;

printfCLa longitud de %s es %d\n", cadena, longitud);

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>

#defi ne MAX_CADÉNA 100

int main(void)
(
char cadena[MAX_CADENA];
int longitud = 0;
char *aux;
*

printf("Introduzca una cadena de caracteres: ");


scanf("%s", cadena);

aux = cadena;

while (*aux != ’\0 ’) I


aux++;
1ongi tud++;

printf("La longitud de %s es %d\n", cadena, longitud):


return (0);

6.7.3 Cadenas de caracteres dinámicas


Crear una cadena de caracteres dinámica, cuyo tamaño se fija en tiempo de ejecución, es similar a crear
un array dinámico, pero la diferencia estriba en acordarse de que hay que reservar un carácter de más
(reservado para el carácter ’\0 ’, el carácter fin de 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>

int mainíint argc, char *argv[])


I
char *mensaje;
char *mensaje_dupl ica ;
int i , j , 1ongitud;

/*
* 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++;

mensaje_dupi ica[j] = ’\0 ’;

/*
* 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.

6.7.4 Paso de cadenas de caracteres a funciones


El paso de una cadena de caracteres como parámetro a una función se realiza como ya se ha comentado
para el caso de los arrays:
Tipo Función (char cadena[]);
Tipo Función (char *cadena);
Ambos casos son equivalentes. Al invocar la función hay que indicar sólo el nombre de la variable
(sin []):
char cadena[] = "Mensaje" ;
Funcion(cadena);
Los strings siempre se pasan por referencia, no por valor. Debido a que el último carácter del string debe
ser el carácter nulo, la función siempre puede saber la longitud y cuándo finaliza la cadena. Sin embargo,
se puede recurrir al paso de un segundo argumento de tipo entero que indique su longitud, si es más claro
para el programador.

6.7.5 Principales funciones de biblioteca para manejar cadenas de caracte­


res
En el archivo de cabecera st rin g .h se encuentran declaradas varias funciones que permiten manipular
cadenas de caracteres. La Tabla 6.1 presenta algunas de estas funciones.

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

Tabla 6.1 Algunas funciones del archivo s t r i ng. h

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)

Tabla 6.2 Algunas funciones del archivo stdl ib. h

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.

Tabla 6.3 Funciones de entrada y salida de stdi o.h

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>

#def ine NUMER0__MESES 12

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;

/* lectura de las temperaturas medias de los meses */


for (j = 0; j < NUMER0_MESES; j++) (
printf("Introduzca la temperatura del mes %d: ", j+1);
if (scanfC'Xf", &temperaturaMeses[j]) < l)f
printf("Error en la introducción de datosVn");
return(O);

/* se calcula la temperatura media */


for (j = 0; j < NUMER0_MESES; j++)
temperaturaMedia = temperaturaMedia + temperaturaMeses[j];

temperaturaMedia = temperaturaMedia / NUMER0_MESES;

printf( "Meses con temperatura inferior a la media.\n");


for (j = 0; j < NUMER0_MESES; j++) '
if (temperaturaMeses[j] < temperaturaMedia)
printfC'El mes %d tiene %f grados.\n",
j+1, temperaturaMeses[j]);

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 ;

/* lectura de las temperaturas medias de los meses */


for (j = 0; j < NUMER0_MESES; j++) (
printf("Introduzca la temperatura del mes 1d: ", j+1);
if (scanf("%f", &temperaturaMeses[j]) < 1){
printf("Error en la introducción de datos\n");
return(O);

ITES-Paraninfo • /67
Problemas resueltos de C

/* Se obtiene la temperatura mínima y máxima */


indiceMin = indiceMax = 0 ;
temperaturaMinima = temperaturaMesesEO];
temperaturaMaxima = temperaturaMeses[0];
for (j = 1; j < NUMER0_MESES; j++) i
if (temperaturaMesesEj] < temperaturaMinima) {
temperaturaMinima = temperaturaMesesEj];
indi ceMin = j ;

if (temperaturaMesesEj] > temperaturaMaxima) {


temperaturaMaxima = temperaturaMesesEj];
indi ceMax = j ;

printfC'El mes %d tiene la temperatura minima %f\n",


indi ceMi n+1, temperaturaMesesEi ndi ceMi n]);

printfC'El mes %d tiene la temperatura máxima %f\n",


indi ceMax+1, temperaturaMesesEindiceMax]);

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>

#defi ne MAX ALUMNOS 100

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 ;

t* lectura de las notas de los alumnos */

printf("Introduzca una nota (EOF para finalizar); ");


ret = scanfC’Xf” , &nota);
while(ret != EOF && numeroAlumnos < MAX_ALUMN0S ) (
if (ret < 1)

16 8 • ITES-Paraninfo
Capítulo 6 / Arrays y strings

printf("Error en la introducción de datos\n");


el se {
notas[numeroAl u'mnos] = nota;
numeroAlumnos++;

printf("Introduzca una nota (EOF para finalizar): ");


ret = scanf("%f", &nota);

/* se comprueba si se ha llegado al final *!


if (numeroAlumnos == MAX_ALUMNOS)
printfC'No se pueden introducir más notas \n");

!* se obtiene la nota media *t


if (numeroAlumnos !=0) I
for (j = 0; j < numeroAlumnos; j++)
notaMedia = notaHedia + notas[j];

notaMedia = notaMedia / numeroAlumnos;

printf("La nota media de los %d alumnos es %.2f\n",


numeroAlumnos, notaMedia);

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 = &notas[0]; /* equivalente a notaMaxima = notas */

if (numeroAlumnos !=0) (
for (j = 0; j < numeroAlumnos; j++) {
notaMedia = notaMedia + notasüj];

if (notas(j] > *notaMaxima)


/* se apunta a la nota máxima *!

ITES-Paraninfo • 1 6 9
Problemas resueltos de C

notaMaxima = &notas[j];

notaMedia = notaMedia / numeroAlumnos;

printfC'La nota media de los %d alumnos es %.2f\n",


numeroAlumnos, notaMedia);

I * se imprime la nota máxima */


printfC'La nota máxima de la clase es %.2f\n", *notaMaxima);

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

int main (vo id )


I
int i , 1eidos;
int n, encontrado;

for (i=0; i<NELTOS; i++) {


printf("Introduzca un número entero (%d):\n",i);
leidos=scanf("%d",&(vector[i])) ;
if (leídos !=1) (
printfC'No es posible leer el elemento %dVn",i);
return (-1);

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

for (i=0; KNELTOS; i++) {


if (vector[i] == n) {
encontrado=l;
break;

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>

#define MAX_ELEMENTOS 100

/* prototipos de la funciones */

void LeerArray(int v[], int *n);

int Esta(const int v[], int n,int elemento);

/*
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;

printf("Introduzca un elemento: ");


ret = scanf("%d", &valor);
while (ret != EOF && j < MAX_ELEMENT0S) (
if (ret < 1)
printf("Error en la introducción del dato\n");
el se {
v[j] = valor;
J++;

printf("Introduzca un elemento: ");


ret = scanf("%d", &valor);

ITES-Paraninfo • 171
Problemas resueltos de C

I■
/* se devuelve también el número de elementos leídos */
*n = j ;
return;

int Esta(const int v[], int n, int elemento)


1
int j = 0;
int esta = 0;

whi 1e (j < n && ¡esta) {


if (v[j] == elemento)
esta = 1;

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

printf("Introduzca el dato a buscar; ");


scanf("%d", ¿elemento);

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

► e je m p l o 6.8 Escriba un programa que sume dos matrices cuadradas de 10 x 10.


Re s o l u c i ó n .
#include <stdio.h>

#define DIMENSION 10

void sumarMatrizlconst float a[DIMENSION][DIMENSION],


const float b[DIMENSI0N][DIMENSION],
float c[DIMENSI0N][DIMENSION])

int i , j ;

1 7 2 » ITES-Paraninfo
Capítulo 6 / Arrays y strings

ford = 0; i < DIMENSION; i++) {


for(j = 0; j < DIMENSION; j++) (
c[i][j] = a [i ] [j ] + b[i][j];

void imprimirMatriz (const float mCDIMENSION][DIMENSION]) i


I n t i ,

for(i = 0; i < DIMENSION; i++) I


for(j = 0; j < DIMENSION; j++) !
printf("%f\t ", m [i][j]);
1
. printf("\n");

pri ntf("\n");

void leerMatriz (float mCDIMENSION][DIMENSION])


(
i nt i , j ;

for(i = 0; i < DIMENSION; i++) (


for(j = 0; j < DIMENSION; j++) {
printf("Introduzca el elemento %d %d :", i, j);
scanfd'Xf", &(m[i][j]));
I
p rin t f (" \n ");

int main (int argc, char *argv[])

/*
* 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

#i nclude <stdli b .h>

float **crearMatriz(int filas, int columnas)


(
float **m;
int j ;

m = (float **) mallocCfi1as * sizeof(float *));


for (j = 0 ; j < filas; j++) {
m [j] = (float *)mal1oc(columnas * sizeof(float));

return (m );

void destruírMatriz(float **m, int filas)


(
int j ;

for (j = 0; j < filas; j++) (


free(m[j]);

free(m);
return;

void 1eerMatriz(float **m, int filas, int columnas)


(
int i , j ;
float dato;

for (i = 0; i < fi1 as; i++) {


for (j = 0; j < columnas; j++) {
printf("El emento [%d,%d]: ", i,j);
scanf("%f", &d at o );
m [i][j]=dato;
I
pri ntf("\n");

void imprimí'rMatriz(const float **m, int filas, int columnas) I


int i , j ;

for (i = 0 ; i < filas; i++) I


for (j = 0; j < columnas; j++) (
pri ntf("[ %e ] \t",m[i][j]);

pri ntf("\n");

1 7 4 « ITES-Paraninfo
Capítulo 6 / Arrays y strings

void sumarMatriz(float **a, float **b, float **c,int filas,


int columnas )

int i , j ;

for d = 0; i < filas; i++) (


for(j = 0; j < columnas; j++) (
c[i]fj] = aCi ][j] + b[i][j];

int main(void)

float **A, **E>, **C;


int filas, columnas;

/* Leer las dimensiones de la matriz *!

printf("Número de filas: ");


scanf("%d", &fi las);
printf("Número de columnas: ");
scanf("%d", &columnas);

I* Crear las tres matrices */

A = crearMatriz(fi 1as, columnas);


B = crearMatriz(fi1 as, columnas);
C = crearMatriz(fi 1 as, columnas);

I* Leer las tres matrices *!

printf("Lectura de la matriz A:\n");


1eerMatriz(A, filas, columnas);
printf("Lectura de la matriz B:\n");
1eerMatriz(B, filas, columnas);

/* Sumar matrices */

sumarMatriz(A, B, C, filas, columnas);

/* Imprimir el resultado */

pri ntf("A + B :\n");


imprimirMatriztC, filas, columnas);

/* Liberar el espacio asociado a las matrices */


destruirMatriz(A,filas);
destruirMatrizíB,filas);
destrui rMatri z(C,filas);

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>

#def ine MAX_ELEMENTOS 10

/* 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 ;

for (j = 0 ; j < n ; j++)


v s [j] = vl[j] + v 2[j ];

return ;

void EscribirVector(const int *v, int n)


I
int j ;

17 6 • ITES-Paraninfo
Capítulo 6 / Arrays y strings

for (j = 0; j < n; j++)


printf("v[%d] = %d\n", j, v[j]);

return;

int main(void)
I
int *vl,
* *v2, *vs;
int n; '/* número de elementos del array *1

printf("Introduzca el número de elementos de los arrays: ");


scanf("%d", &n);

I* se crean los arrays *¡


vi = (int *)malloc(n * sizeof(int));
v2 = (int *)malloc(n * sizeof(int));
vs = (int *)malloc(n * sizeof(int));

/* se comprueba si se ha reservado memoria correctamente */


if (vi == NUIL ¡| v2 == NULL || vs == NULL)
printf("Error en la creación de los vectoresln");
el se í
printf("Introduzca el primer vector \n");
LeerVector(vi, n );
printf("Introduzca el segundo vector \n");
LeerVector!v2, n);

SumarVectores(vl, v2, vs, n);


EscribirVector(vs, n);

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>

#define MAX 128

void CopiarCadena(const char *origen, char *destino)

while (*origen != ’\0 ’) {


*destino = *origen ;
ori gen++;
desti no++; E S C U E L A S Í
d e I n g e n i e r o s

j EOL ioteca
d e S e v i l l a .

/* se copia el carácter nulo */


*desti no = ’\0’;
return;

ITES-Paraninfo • 1 77
Problemas resueltos de C

int mai n(void)


{
char cadenal[MAX];
char cadena2[MAX];

printf("Introduzca una cadena: ");


scanfC'Xs", cadenal):

CopiarCadena(cadenal, cadena2);

printf("La nueva cadena es %s\n", 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>

#define NELTOS 100

void inicioVector (int vector[], int neltos ) {


int i ;

for (i= 0 ; Kneltos; i++){


vector[i ] = neltos - i ;

void printVector ( const int vector[], int neltos ) I


int i ;

for (i=0; Kneltos; i++)f


printfC %d ", vecto r [i ]);

/* método de ordenación por burbuja */


void OrdenarPorBurbuja (int vector[], int neltos )
(
int i , j ;
int intercambio;

for (j=0; jdneltos; j++) {

178 • ITES-Paraninfo
Capitulo 6 / Arrays y strings

for (i=0; i<(neltos 1); i++) I


/*■ si elementos descolocados, se intercambian */
if (vector[i] > vector[i+l]) (
intercambi o = vectorti] ;
vector[i] = vector[i+l] ;
vector[i+l] = intercambio ;

return;

int main (void )


(
int vector[NELT0S] ;

inicioVector(vector,NELTOS);

pri ntf("Vector ini ci al:\n");


printVector(vector,NELTOS);
pri ntf("\n");

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>

int maindnt arge, char *argv[])


i
int i ;

for (i = 1; i < argc; i++) I


printfC’El argumendo %d es %s\n", i, argv[i]);
1

return(O);

► 6. 13 Implemente la función mi _strcmp con la misma funcionalidad que strcmp

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;

return 0; /* son iguales */

► 6. 14 Implemente la función mi.strchr con la misma funcionalidad que st rc h r

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>

//define MAX_CADENA 512

/* definición de tipo booleano */

enum boolean (false, truel;


typedef enum boolean boolean;

void InvertirCadena(const char *origen, char *destino)


(
int j = 0;
int longitud;

longitud = strlen(origen);

for (j = 0; j < longitud; j++)


destino[1ongitud-1 -j] = origenfj];

/* se copia el carácter nulo al final de destino */


destino[longitud] = ’\0 ’;
return;

boolean EsPalindromolconst char *cadena)


í
char aux[MAX_CADENA];

InvertirCadena(cadena , aux);
if (strcmp(cadena, aux)== 0)
return (true);
else
return (false);

int main(void)
(
char cadena[MAX_CADENA];

printf("Introduzca unacadena: ");


scanf("%s", 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.

► 6. 16 Escriba de nuevo el Ejemplo 6.7, utilizando, en este caso, funciones de biblioteca.


Re s o l u c ió n .
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int maindnt argc, char *argv[])


I
char *mensajeDuplica;
int i , j , 1ongitud;

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

mensaj eDuplica[j] = '\0’;

/*
* 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>

/* definición del tipo polinomio */


typedef int ^Polinomio;

/* Definición de las operaciones típicas */

Polinomio crearPolinomio ( int grado ) I


return (Polinomio) mal!oc(grado*sizeof(int));

void destruirPolinomio ( Polinomio *p )


I
if ((*p) != NULL) {
free(*p);
(*p) = NULL;

void 1eerPolinomio ( int grado, Polinomio p )


í
int i ;

for (i=0; i<grado; i++)l


printfí"\nIntroduzca el coeficiente para x~%d: ",i);
scanf("%d",&(p[i])):

void escribirPolinomio ( int grado, Polinomio p )

ITES-Paraninfo • 183
Problemas resueltos de C

int i ;

for (i=grado-1; i>=0; i--) i


if (p[i] != 0)
printfC + %d*x~%d ", pCi ], i);

Polinomio sumar(int gradol, Polinomio pl, int grado2, Polinomio p2)


I
int i ;
Polinomio resultado;

if (gradol > grado2) {


resultado = crearPolinomio(gradol);
if (resultado == NULL)
return resultado;

for (i=0; i<grado2; i++)


resultado[i] = p1[i] + p2[i] ;
for (i=grado2; itgradol; i++)
resultado[i] = pl[i] ;

el se (
resultado = crearPolinomio(grado2);
if (resultado == NULL)
return resultado;

for (i=0; Kgradol; i++)


resultado[i] = p1[i] + p2[i] ;
for (i=gradol; i<grado2;- i++)
resultado[i] = pl[i ] ;

return resultado;

Polinomio multiplicar (int gradol, Polinomio pl, int grado2,Polinomio p2 )


(
int i , j ;
Polinomio resultado;

resultado = crearPolinomio(gradol + grado2);

if (resultado == NULL)
return resultado;

for (i=0; i<(gradol+grado2); i++)


resultadofi] = 0;

for (i=0; itgradol; i++)


for (j=0; j<grado2; j++)

18 4 • ITES-Paraninfo
Capítulo 6 I Arrays y stringi

restiitado[i+j3 = resultado [i+j] + pl[i] * p2[j] ;

return resultado;

int main (void)


I
Polinomio pl, p2, resultado;

pi = crearPoli nomi o (5);


1eerPoli nomi o(5,pl);
pri ntf("\n");

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

resultado = muí tipli car(5,pl,5,p2);


printfC'El resultado de la multiplicación es:");
es cri bi rPoli nomi o(5+5.resultado);
destruirPolinomioí&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

int leerlndice (void)


I
int indice ;
char ch;

printf("Introducir la posición de inserción. ");


printf("(un nùmero del 0 al 9 )\n" ) ;
printf("> ");
scant("%d",&i ndice) ;
ch=getchar(); /* leer \n */

return indice;

char leerOpcion (void)


I
c.har opcion;

printf("Elegir una opción:\n");


printf("\t(a) Añadir una tarea.\n");
printf("\t(M) Aumentar la prioridad de una tarea.\n");
printf("\t(m) Disminuir la prioridad de una tarea.\n");
printf("\t(1) Listar tareas ordenadas por prioridad.\n")
printf("\t(s) Salir del programa.\n");
pri ntf( ;
scanf("%c",&opci on);

return opcion;

int main ( int argc, char *argv[] )


1
int i , indi ce, copi a ;
char opcion, ch;
int salir;

/* Iniciar las variables *¡


for (i=0; i<10; i++) (
porPri ori d ad[i]=-1;
¡
for (i=0; i<10; i++) (
strcpy( 1 istaTareasti ] , " ;

/* 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

if ( (indice < 0) || (indice > 9) )


(
printf("Error: indice no válido\n");
conti nue;

/* 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;

copi a=porPri ori dad[indi ce -1] ;


porPrioridad[indice-l]=porPrioridad[indice];
porPri ori dad[i ndi ce]=copi a ;

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;

copi a=porPriori dad[i ndi ce+1];


porPri oridad[i ndi ce+1]=porPri ori dad[indi ce];
porPri ori dad[i ndice]=copi a ;

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

Pro blem as Pro pu esto s


6 . 1 Defina una variable llamada arrayPrueba como un array de 25 elementos de tipo double.
6.2 ¿Qué sucede si accede al elemento a r r a y l [ - l ] ?
6.3 Dado el siguiente fragmento del programa:
f l o a t m e d i a d n t a, f l o a t x [ ] ) ;
i nt n ;
f l o a t v [25];
f l o a t m;
¿Cuál de las siguientes llamadas a la función media es correcta?
1. m = medi a ( v , n ) ;
2. m = medi a ( n , *v ) ;
l_ i
i—i

m = medi a(n ,
C\J
LO

3.
>

4. m = medi a ( n , v) ;

Dado el siguiente fragmento de programa:


1i
i—i
t—1
0

= ( 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 ;

¿Cuál de las siguientes afirmaciones es cierta?


1. La definición del vector v es incorrecta;
2. v [ 5 ] es igual a 7.
3. v [3 ] es igual a 4.
4. La asignación a = &v [ i] es incorrecta.
6.5 Se desea usar una variable global de tipo array definida en otro módulo en una función llamada i mpri mi r-
Estado. ¿Qué alternativas se podrían usar? ¿Es necesario indicar el tamaño en las declaraciones asociadas?
6.6 ¿Cuál de las siguientes expresiones permite reservar memoria para un vector de números de tipo f 1oat?
1. (float) mal 1oc (10);
2. (float *) mal 1oc(10);
3. (float *) mallocdO * si zeof (f 1oat));
4. (float *) mallocdO * sizeof(float * ) ) ;
6.7 Dado el siguiente fragmento de programa:

18 8 • ITES-Paraninfo
Capítulo 6 / Arrays y strin g s

int v[10] = !2,4,6,8,10,1,3,5,7,91;


int i , x = 0;
for(i=2; i<7; i++)
x = x + v [i ] ;
¿Cuál es el valor final de la variable x?
1. 28
2. 33
3. El fragmento de programa es incorrecto ya que v y x son de distinto tipo.
4. 22

6 .8 ¿Qué sucede si se ejecuta free( arr ay ) siendo array una variable definida como cha r array2[4]?

6.9 ¿Puede pasarse un array por valor a una función?


6. 10 Después de ejecutar el siguiente fragmento de programa
float v[5] = 11,2,4,6,8);
int i ;
float *p ;
for (i = 0; i < 5 ; i++) 1
p = &v Ci ];
*p = *p + i ;
1
¿Cuál de las siguientes afirmaciones es cierta?
1. La sentencia *p = *p + i es incorrecta.
2. v [3] = 9
3. v [0] = 2
4. La definición del vector v es incorrecta.

6.11 ¿A qué equivale la expresión vector[i]?


1. *(vector + i )
2. *(vector) + i
3. &(vector + i)
4. &(vector) + i

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 {

char ti tulo[256]: /* "El lenguaje de P r o g r a m a d ón C" *!


char autor[256]; /* "Félix García Car b a l ! e i r a et all" */
char ISBNC256] ; /* "84-205-3178-2" */
char estante[256] ; /*
,,12„
int prestado ; /* 0 -> no, 1 -> si */
char cliente[256] ; /* "Maria" */

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

char ti tulo [256];


char autor[256] ;
char ISBNC256] ;
char estante[256]
int prestado ;
char cli ente[256]

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 {

char ti tul o[256];


char autor[256];
char ISBN[256]:
char estante[256];
int prestado;
char cl iente[256];

/* 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 ;

/* D efinición de las variables */


FichaLibro libroA, libroB;
De entre estas tres posibilidades se recomienda la última, ya que permite separar la definición del tipo
de datos, es decir, la definición de la estructura de la definición de las variables de ese tipo. Además, gracias
a la definición del nuevo tipo de datos usando la palabra reservada typedef no se precisa utilizar la palabra
reservada struct junto con el nombre de la estructura, algo que puede resultar engorroso.

! ► 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

7.1.1 Inicialización de los miembros de una estructura


Cuando se define una estructura es posible asignar valores a los miembros individuales de la misma. Por
ejemplo:
Fi chaLi bro 1 ibroA = {
"titulo",
"autor",
"ISBN" ,
"estante",
0,
"el iente",

Fi cha Li bro 1 ibroB = (


"ti tul o",
"autor",
"ISBN",
"estante",

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

struct punto pl = ( (1, 4, 4, 1}, {1, 1, 4, 4), "cuadrado" I ;

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

7.1.2 Uso de las estructuras


Con una estructura se puede trabajar a dos niveles: con la estructura completa y con sus miembros de
forma independiente.
Trabajar con la estructura como un todo es algo limitado; es posible en la operación de asignación, pero
no en la operación de comparación.
Es posible asignar a una variable de un tipo estructura el valor de otra del mismo tipo. También es
posible pasar una estructura como parámetro a una función y que una función devuelva una estructura.
Trabajar con la estructura como un todo no es posible en muchos compiladores antiguos. En dichos
compiladores no es posible realizar la asignación entre estructuras. Por suerte, hoy es muy difícil (pero no
imposible) encontrarse este problema.
Para acceder a un miembro de una estructura se usa el operador de miembro (.).
Por ejemplo, para acceder al miembro ti tul o del 1 ibroA se utiliza la siguiente expresión:
1 ibroA.ti tul o
A la izquierda del operador . está la variable de tipo estructura y a la derecha está el nombre del miembro
a seleccionar. En el siguiente programa se muestra cómo se usa una estructura.
Un miembro de una estructura puede ser de un tipo de datos básico, como cha r, int, etc., pero también
puede ser un array, un puntero o una estructura. En el caso de tener estructuras anidadas, para acceder a un
miembro anidado se usa el operador miembro tantas veces como sea necesario para alcanzarlo.

► 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 {

char ti tul o [256] ;


char autor[256];
char ISBNC256];
char ^estante ;

int prestado ;
char cl iente[256]
struct Fecha inic

/ * Definición del tipo ’FichaLibro’ */


typedef struct Fichatibro FichaLibro ;

/* Definición de la variable librol */


FichaLibro librol;

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 {

struct FichaLibro anterior_libro_del_autor ;


1 :
Pero sí es posible que un miembro de una estructura sea un puntero a la propia estructura. Esto es:
s t r u c t FichaLibro I

struct FichaLibro *anterior_libro_dei_autor ;

7.1.3 Punteros a estructuras


La definición de una variable de tipo puntero a una estructura es similar a la definición de cualquier otro
tipo de puntero. Por ejemplo:
struct FichaLibro *anterior_iibro_del_autor ;
La variable anterior .1 ibro_d el .autor se define como puntero a una estructura de tipo struct
Fi cha Li bro.
Una variable de tipo puntero permite almacenar direcciones de memoria; en este caso, direcciones de
memoria en la que se almacenen valores de tipo struct FichaLibro. Por tanto, el hecho de definir un
puntero a una estructura, como en el ejemplo, no permite usar dicha estructura directamente, es decir, no se
debe acceder a los miembros si antes no se almacena ninguna dirección de memoria válida.
Podemos dar una dirección de memoria válida de dos maneras:
■ A partir de la dirección de otra estructura, como por ejemplo:
/* Defi ni ci ón de las variables */
struct FichaLibro 1ibrol ;
struct FichaLibro *referencia_a_librol ;

¡* Defi ni ci ón de las variables */


referencia_a_iibrol = & 1 ibrol ;
En donde la variable referenci a_a_l ibrol toma como valor la dirección de la variable 1 ibrol,
usando el operador &.
■ A partir de la dirección de una zona de memoria dinámica, como por ejemplo:
/* Definición de las variables */
struct FichaLibro *referencia_a_libro2 ;

/* Reservar memoria con malloc */


referencia_a_libro2= mal 1oc(sizeofístruct FichaLibro)) ;

/* Uso de la variable */
(*referencia_a_libro2).prestado = 0;

/* Liberar memoria con free */


free(referenci a_a_i ibro2);

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.

7.1.4 Disposición de las estructuras en memoria


El compilador coloca los miembros de una estructura en memoria en el mismo orden en el que son
definidos, pero el compilador no tiene por qué colocarlos consecutivamente. El compilador puede añadir
espacios de memoria de relleno entre los miembros para facilitar su acceso por parte del procesador. Esto se
debe a que, para muchos procesadores, si la dirección de comienzo de los miembros cumple determinadas
condiciones, el acceso a memoria se realiza más rápidamente.
Considere, por ejemplo, la siguiente definición:
s t r u c t Datos
(
char carácter;
i n t entero;

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

7.1.5 Paso de estructuras a funciones


Suponga que se quiere codificar una función que compare dos fichas de dos libros, de manera que
devuelva el entero 1 si son del mismo autor y el entero 0 si son de autores diferentes. A dicha función hay
que pasar dos parámetros, de tipo estructura FichaLibro.
Si se desea pasar una estructura por valor, esto es, si no se desea que se modifiquen los valores de la
estructura dentro de la función, hay que definir el parámetro formal como:
int igualAutor (FichaLibro libroX, FichaLibro libroY);
Y llamar a la función indicando como parámetro real el nombre de las variables:
I* Definición de dos variables de tipo ’FichaLibro’ *!
FichaLibro librol, 1 ibro2;

/* Llamada a la función de comparación *1


igualAutor(1ibrol,1ibro2);
Además de la anterior función, se desea codificar una función que rellene la ficha de un libro. Dicha
función pide los datos al usuario y guarda los valores en la ficha indicada. En este caso sólo hay un parámetro
formal que ha de ser modificado por la función.
Si se desea pasar por referencia, esto es, si se quiere que la función pueda modificar la estructura, hay
que definir el parámetro como un puntero a la estructura:
void leerFicha (FichaLibro *libroX);
Y para invocar a dicha función, hay que indicar como parámetro real un puntero (dirección de memoria)
a la estructura que se desea modificar:
i* Definición de una variable de tipo ’Fichatibro’ *!
FichaLibro librol;

i* Llamar a la función de lectura *!


1eerFi cha(&1i brol);
En el anterior fragmento de código se define una variable librol de tipo FichaLibro y se usa el
operador &para extraer la dirección de dicha variable.*1

► 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

/* Llamar a la función de lectura */


if (1 ibro2 != NULL)
1eerFi cha(1 ibro2);

/* Resto del código */

/* Liberar la memoria cuando ya no sea necesaria */


if (1 ibro2 != NULL) I
f reeU ibro2);
1 ibro2 = NULL;
1
Si se define una variable de tipo puntero aFichaLibro, para pasar dicha estructura sólo hay que indicar
el nombre de la variable, puesto que su valor es la dirección de memoria donde está la estructura.
También se pueden definir funciones que devuelvan estructuras o punteros a estructuras. Por ejemplo, se
desea codificar una función que devuelva la ficha vacía de un libro de forma dinámica, es decir, devuelva un
puntero a FichaLibro que apunte a una zona de memoria reservada dinámicamente. Dicha función puede
definirse como:
FichaLibro * crearFicha ( void ) ;
Y se invocaría de la siguiente forma:
/* Definición de una variable de tipo ’Fichatibro’ */
FichaLibro *1 ib ro3 = NULL;

/* Reservar memoria *!
i ibro3 = crearFi cha() ;

!* Llamar a la función de lectura */


i f ( 1 ibro3 != NULL)
1eerFi cha(1 ibro3);

/* Resto del código */

/* Liberar la memoria cuando ya no sea necesaria */


i f (1 ibro3 != NULL) (
freed ibro3);
1 ibro2 = NULL;
)
Como se observa, se parece mucho al fragmento de código anterior, pero se ha cambiado la llamada a
la función m a11 o c y la gestión de errores asociada a m a 11 o c por la llamada a la función crearFicha que
realiza estas labores. Esto hace más fácil de entender lo que realiza la función ma i n puesto que la lectura
del código está más cerca de indicar qué se hace del cómo se hace.
Cuando se pasa una estructura por valor a una función, lo que se hace es copiar todos los miembros en
el parámetro formal de la función. Si la estructura es muy grande, este proceso será costoso e ineficiente.
Sin embargo, cuando se pasa a la función un puntero a una estructura, lo único que se pasa es la dirección
de memoria en la que se encuentra la estructura, por lo que es más eficiente.
Como ya se ha comentado, pasar un puntero a la estructura se utiliza en el paso de parámetros por
referencia (véase a ) de la figura 7.2). El mecanismo es similar, sin embargo, ahora se quieren garantías de
no modificar la estructura en la función. Para ello se utiliza el modificador const.
Añadir el modificador const, como ya se vio en el Capítulo 4, al principio de la definición (y de­
claración) del parámetro permite indicar al compilador que compruebe, en tiempo de compilación, que

ITES-Paran'mfo m 19 9
Problemas resueltos de C

A) B)
Faso de parámetro por referencia. Paso de parámetro por valor.

int main (void) int main (void)


{
FichaLibro f1;r- ***
| cliente

Figura 7.2 Diferencias entre el paso de estructuras por referencia y por valor.

la estructura no es modificada directamente en la función. La mayoría de los compiladores avisan como


advertencia (no como error) si se intenta modificar directamente.
Veamos cómo se usa el modificador const en el caso de la función igualAutor. Para pasar los
parámetros por valor de forma eficiente, dicha función se podría haber definido de la siguiente forma:
int igualAutor (const FichaLibro *libroX, const FichaLibro *libroY)
l
i f (strcmp(1 ibroX->titul o ,1 ibroY->titul o) == 0)
return 1 ; /* iguales */
el se
return 0 ; /* distintos */
)
El modificador co n st le indica al programador que la zona de memoria apuntada por libroXylibroY
es constante y no va a ser modificada en la función. Si se usa esta versión, hay que modificar la llamada a
dicha función, que se realiza en la función ma in mediante:
if (igualAutorC&fichal,&ficha2))

Es decir, se pasan como parámetros las direcciones de las fichas.

7.2 A rrays de estru ctu ra s

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;

planta [escalera! letra

st r u c t Piso o f i c i n a s [4];

planta escalera letra oficinas [ 0 ]


oficinas [ 1 ]
oficinas [ 2 ]
oficinas [3]

o f i c i n a s [ 3 ] .letra = 'a'

rt

Figura 7.3 Ejemplo de un array de estructuras.

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;

printfC'El tamaño de un int es %d\n",


si zeof(int));
printf("El tamaño de un double es %d\n",
si zeof(double));
printfC'El tamaño de un char[16] es %d\n",
si zeof(char[16]));
printfC'El tamaño de la unión es %d\n",
sizeoflunion Numero));

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.

union Numero dato;

.e n t e ro
{JL , .re a l / 1 -8 2 4 ^8=24^132
.s t r in g

dato.entero = 8; dato.real = 8.24 dato.string = 8.24132

Figura 7.4 Unión Numero definida en el programa.

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

7.4 Estru ctu ras d e d a t o s a u t o r r e f e r e n c ia d a s

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

Si analiza esta definición, la estructura Cliente tiene dos miembros:


■ el miembro nombre de tipo string para almacenar el nombre del cliente y
■ el miembro anterior que es un puntero a otra estructura Cliente para almacenar la referencia al
siguiente cliente. Si se trata del primer cliente, puede almacenarse en el miembro de una variable de tipo
C1 iente el valor NULL.
Este tipo de estructuras en las cuales uno o varios miembros son punteros a la estructura en sí misma se
denominan estructuras autorreferenciadas y permiten crear estructuras de datos avanzadas como listas,
colas, pilas y árboles (véase la Figura 7.5).

nombre nombre nombre nombre

li| ¡1
üí
anterior anterior anterior anterior

Figura 7.5 Modelo para la lista de préstamos.

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.

7.4.1 Definiciones mutuamente recursivas


Es posible que cada individuo pueda hacer referencia al grupo al que pertenece, como se muestra en la
figura 7.6.

Cliente Cliente Cliente

Figura 7.6 Definición mutuamente recursiva.

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 ;

struct Cli ente (

char nombre[l
struct Prestamos ;k grupo;
struct Cli ente * anteri or
);

struct Près tamos j

struct Cliente * prri mero ;


struct Cli ente * ul ti mo ;

Esto se conoce como declaración adelantada.

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

I ► 7.2 Se tiene la siguiente definición:


s t r u c t nivel 2
1
char * p l ;
char a 2 110];

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 */

I ► 7.3 Escriba un programa que determine si dos libros son iguales.


R e s o l u c ió n .
-ifinclude <stdio.h>
--include <string.h>

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

/* Definición del tipo ’FichaLibro’ */


typedef struct FichaLibro FichaLibro ;

/* Defi ni ci ón de las variables */


FichaLibro librol, 1 ib ro2

int main(void)
I
/* Lectura de los datos del libro 1 */

206 • ITES-Paraninfo
Capítulo 7 / Estructuras

printf("\nIntroduzca el titulo del libro 1: ");


scanfC'Xs", 1 ibrol.ti tul o);

printf("\nIntroduzca el autor del libro 1: ");


scanfC'Xs", 1 ibrol.autor);

printf("Vnlntroduzca el ISBN del libro 1: ");


scanfC'Xs", 1 ibrol.ISBN);

printf!"\nIntroduzca el estante donde está el libro 1: ");


scanfC'Xs", 1 ibrol.estante);

printf!"\nIntroduzca 1 si 1ibrol está prestado, ");


printfC'O en caso contrario: ");
scanf("Xd” , &(1 ibrol.prestado));

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

/* Lectura de los datos del libro 2 *!


printf("\nIntroduzca el titulo del libro 2: ");
scanfC'Xs", 1 ibro2.ti tul o);

printf("\nIntroduzca el autor del libro 2: ");


scanfC'Xs", 1 ibro2.autor);

printf("\nIntroduzca el ISBN del libro 2: ");


scanfC'Xs", 1ibro2.ISBN);

printf!"\nIntroduzca el estante donde está el libro 2: ");


scanfC'Xs", 1 ibro2.estante);

printf!"\nIntroduzca 1 si 1 ibro2 está prestado, ");


pri ntf!"0 en caso contrario: ");
scanf("Xd", &(1 ibro2.prestado));

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

/* Comparadón de libro 1 con libro 2 */

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

printfC'Los libros 1 y 2 son iguales\n");


)
el se
1
printfC'Los libros 1 y 2 NO son iguales\n");

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)

/* Reservar memoria para los puntos *t


puntol = malloc(sizeof(Punto));
punto2 = mal 1oc(sizeof(Punto));
if ((puntol == NULL) || (punto2 == NULL)) 1
printfCNo es posible reservar memori a\n");

208 • I T E S - P o r a n in f o
Capítulo 7 / Estructuras

return (-1); /* fin del programa */


)
/* Leer las coordenadas de los puntos */
printf("Introduzca la coordenada x del punto 1: ")
scanf("%f", Mpuntol- >coordenada__x) ) ;
printf("Introduzca la coordenada y del punto 1: ")
scanf("%f", &(puntol ->coordenada_y)):
printf("Introduzca la coordenada z del punto 1: ")
scanf("Xf", &(puntol ->coordenada__z) ) ;

printf("Introduzca la coordenada x del punto 2: ")


scanf("%f", &(punto2- >coordenada__x ) ) ;
printf("Introduzca la coordenada y del punto 2: ")
scanf("Xf", &(punto2- >coordenada_y));
printf("Introduzca la coordenada z del punto 2: ")
scanf("Xf", &(punto2->coordenada_z));
/* Compara los puntos */
if (
(puntol->coordenada_x == punto2->coordenada_x) &&
(puntol->coordenada_y == punto2->coordenada_y) &&
(puntol->coordenada_z == punto2->coordenada_z)
)

printfC'Los puntos 1 y 2 son iguales\n");

el se

printfC'Los puntos 1 y 2 NO son iguales\n");


printfC'La distancia es %f\n",
sqrt(pow(punto2->coordenada_x - puntol->coordenada_x,2)+
pow(punto2->coordenada_y - puntol->coordenada_y,2)+
pow(punto2->coordenada_z - puntol->coordenada_z,2)));
1
/* Liberar memoria usada en los puntos */

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

!* Se imprime el desplazamiento dentro de la estructura */


printf("Disposición de los miembros\n");
pri ntf ("--------------------------- \n");
printf("offsetof(struct Datos, carácter) = %d\n",
offsetof(struct Datos, carácter));
printf("offsetof(struct Datos, entero) = %d\n",
offsetof(struct Datos, entero));
pri ntf("\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>

/* Definición del tipo ’FichaLibro’ *1


typedef
struct {

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;

/* Asignar el valor por defecto a todos los campos */


strcpy(flibro-Stitulo,”");
strcpy (fl ib r o - > a u t o r ;
strcpy(fl i b r o - > I S D N , );
strcpy(flibro->estante,'"');
flibro->prestado = 0;
strcpy (fl ibro->cl iente,"'');

return fli bro;

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

printf("Introduzca el autor del libro:\n");


scanfC'Xs", 1ibroX->autor);

pri.ntf(" Introduzca el ISDN del libro:\n”);


scanf("%s", 1ibroX->ISDN);

printf("Introduzca el estante del 1 ibro:\n") ;


scanf("%s", 1 ibroX->estante);

/*

I T E S - P o r a n in f o • 211
Problemas resueltos de C

* Compara el autor de dos fichas


*/
int igualAutor (FichaLibro libroX, FichaLibro libroY)
{
if (strcmpUibroX.ti tul o ,1 ibroY.ti tul o ) == 0)
return 1 ; /* iguales */
el se
return 0 ; /* distintos */

!*
* Programa principal
*/
int main(void)
1
FichaLibro *fichal, *ficha2;

/* Crear las fichas */


fi chal = crearFi chai);
ficha2 = crear Ficha();
if ( (fi chal == NULL) || (ficha2 == NULL) )
return (-1);

/* Leer el titulo de los libros */


1eerFi cha(fi chal);
’eerFicha(ficha2);

i* Comparar los autores */


if (igualAutor(*fichal,*ficha2))
printf("Los libros 1 y 2 si tienen el mismo tituloXn");
el se
printfC'Los libros 1 y 2 NO tienen el mismo titulo\n");

/* Liberar memoria usada en las fichas */


el imi narFi cha(&fi chal);
eliminarFicha(&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>

/* Definición del tipo ’FichaLibro’ */


typedef
struct I

char ti tulo[256];
char autor[256];
char ISDNÜ256];
char estante[256];
int prestado;
char el iente[256];

) FichaLibro ;

/* Defi ni ci ón del tipo puntero a FichaLibro *¡


typedef FichaLibro *PtrFichaLibro ;

/* Definición del tipo array a punteros a Fi chati bro */


#defi ne MAX_ARRPTRFICHALIBRO 10
typedef PtrFichaLibro ArrPtrFichaLibro[MAX_ARRPTRFICHALIBRO] ;

/* Defi ni ci ón de la variable libros */ ■


ArrPtrFichaLibro libros;

int main(void)
{
i nt i ;

/* Reservar memoria para los libros */


for (i=0; i<MAX_ARRPTRFICHALIBRO; i++) {
1 ibros[i] = mal 1oc(sizeof(FichaLibro));
if (1 ibros[i] == NULL) {
printfC'No es posible reservar memoria\n");
return (-1); /* fin del programa *¡

/* Leer el titulo de los libros */


for (i=0; i<MAX_ARRPTRFICHALIBRO; i++) (
printf("Introduzca el titulo del libro %d: ", i);
scanf("%s", (libros[i])->titulo);

¡* Comparar la primera ficha con el resto */


for (i=1; i<MAX_ARRPTRFICHALIBRO; i++) (
if (strcmp(1 ibros[0]->ti tul o ,1 ibros[i]->t itul o ) == 0)

ITES-Paraninfo » 2 13
Problemas resueltos de C

printf("El titulo del libro Id j 1 es igual. \n"


i+1);
el se
printf("El titulo del libro Id y 1 no es igu al .\ n " ,
i+1);
I

/* L iberar memoria usada en los libros */


for (1=0; i<MAX_ARRPTRFICHALIBRO; i++) (
free(1 ibros[i ]);

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]

Figura 7.7 Estructuras definidas en el Ejercicio 7.7

Finalmente, se define la función ma in, la cual realiza los siguientes pasos:


1. Reservar memoria y asignar la dirección de la zona de memoria reservada a los punteros del array.
2. Leer el título de los libros.
3. Comparar el título del primer libro con el título de los restantes libros.
4. Liberar la memoria usada para guardar los datos relativos a los libros.
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. Pero el programa también añade algunas ideas
importantes:
■ En el momento de definir una variable de este tipo, lo habitual es definir primero los tipos de datos a
usar y luego la variable. Los tipos de datos se definen del nivel más profundo al nivel menos profundo.

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;

t ype de f s t r u c t Cliente * Histórico;


Con esta definición, el histórico será un puntero al último cliente, puesto que a partir del último se puede
acceder al resto de los clientes.
A continuación se indican los pasos principales que ha de realizar el programa pedido:
1) Dar valor inicial al histórico
2) Leer los nombres de los clientes
3) Imprimir en pantalla los nombres de los clientes
4) Liberar la memoria de cada cliente
5) Terminar el programa
Para entender el desarrollo se aconseja pensar en un ejemplo de uso del programa y dibujar con lápiz y
borrador, usando diagramas de cajas similares a los presentados en el libro, el valor de los miembros de las
estructuras tras cada paso.
Finalmente, para codificar el programa, se procede a insertar el listado de pasos como comentarios y se
añade tras cada comentario la sección de código que lo implementa.
#i nc l ude <stdio.h>
#i nc l ude <stdlib.h>
#i n c l u d e <string.h>

/*
* Definición del histórico de clientes mediante

ITES-Paraninfo * 2 1 5
Problemas resueltos de C

* el tipo ’struct Cliente’


*/

struct Cli ente {


char nombre[1001];
struct Cliente * anterior;

typedef struct Cliente * Histórico;

* Programa principal

int main(void)
Il
Histórico histórico;
Histórico nuevo;
char nombre[1001];
struct Cliente * individuo;
struct Cliente * anterior;

/* 1) Dar valor inicial al histórico */


histórico = (Historico)(NUIL);

/* 2) Leer los nombres de los clientes */

/* Se lee el nombre del primer cliente */


printf("Introducir el nombre del clienteVn");
printfí"(Pulse Intro para terminar): ");
gets(nombre);

/* Mientras que sea un nombre válido: */


while (strcmpínombre,"") != 0)
(
/* Se reserva memoria para un nuevo cliente */
nuevo=malloc(sizeof(struct Cliente));
i f (NULL==nuevo) {
perror("mal loe:");
return -1;

/* Se establecen los valores del nuevo cliente */


nuevo->anteri or=hi storico;
strcpy(nuevo->nombre,nombre);

!* Se establecen los valores del nuevo histórico */


hi stori co=nuevo;

/* Y se lee el siguiente nombre */


printfí"Introducir el nombre del clienteVn");
printf("(Pul se Intro para terminar): ");
gets(nombre);

216» IT E S - P o r a n in f o
C a p ítu lo 7 / E stru ctu ra s

/* 3) Imprimir en pantalla los nombres de los clientes */

I* Se referencia al último cliente */


individuo = histórico;
pri ntf("\n");

/* Mientras que sea un cliente válido: */


whi1e (indi vi dúo != NULL)
I
/* Se imprime el nombre *í
pri ntf("%s\n",indivi duo->nombre);

/* Y se sigue con el anterior cliente */


individuo = individuo->anterior;

/* 4) Liberar la memoria de cada cliente */

/* Se referencia al último cliente */


individuo = histórico;

/* Mientras que sea un cliente válido: */


while (individuo != NULL)
I
/* Se guarda la referencia del anterior cliente */
anterior = individuo->anterior;

/* Se libera la memoria */
freed ndi vi dúo);

/* Y se sigue con el anterior cliente */


individuo = anterior ;

/* 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

};

typedef struct InfoCliente Cliente;


typedef struct ListaCli entes Histórico;
A continuación se listan las operaciones que se van a realizar sobre un histórico:
1) ponerVacio(historico): Establece un histórico como vacío.
2) agregar!historico,nombre): Añade un nuevo cliente al histórico.
3) borraríhistorico): Borra todos los clientes del histórico.
4) anteriorCcliente): Devuelve la referencia al anterior cliente de uno dado.
5) ultimo!historico); Devuelve la referencia al último cliente presente en el
histórico.
6) nombreCli ente!el iente); Devuelve el nombre del cliente referenciado.
Finalmente, para cada una de las operaciones anteriormente comentadas, se escribe una función que la
implemente. Para escribir el código del programa, partimos del código del programa anterior y sustituimos
las secciones de código por otras en las que se llame a la función apropiada de la biblioteca creada.
#include <stdio.h>
#inelude <stdlib.h>
#include <string.h>

* Definición del histórico de clientes

struct InfoCliente {
char nombre[1001];
struct InfoCliente * anterior;

struct ListaClientes I

int numeroClientes;
struct InfoCliente * ultimo;
struct InfoCliente * primero;

typedef struct InfoCliente Cliente;


typedef struct ListaClientes Histórico;

* Definición de las operaciones sobre un Histórico


rr;
void ponerVacio
(
Histórico * histórico í* por referencia */

hi storico->numeroClientes = 0;
hi stori co->ultimo = NULL;
hi storico->primero = NULL;

void agregar
(

2 1 8 * ITES-Paraninfo
Capitulo 7 / Estructuras

Histórico * histórico, /* por referencia */


char * nombre /* por valor *¡

C1i ente *nuevo;

/* Se comprueba el valor de los parámetros *1


if (histórico == NULL)
return;
if (nombre == NULL)
return;

/* Se reserva memoria *!
nuevo=mal1oc(si zeof(C1 iente));
if (NULL==nuevo) i
perror("mal loe:");
return;

/* Se establecen los valores para el nuevo elemento */


nuevo->anterior=hi stori co->ultimo ;
strcpy(nuevo->nombre,nombre);

/* Se establecen los valores para el histórico *!


hi storico->ultimo=nuevo;
(hi storico->numeroCli entes)++;

void borrar
(
Histórico ^histórico /* por referencia *1
)

Cliente * individuo;
C1i ente * anterior;

/* Se comprueba el valor de los parámetros */


if (individuo == NULL)
return;

/* Se libera la memoria de cada individuo */


individuo = historico->ultimo ;
while (individuo != NULL)
{
anterior = individuo->anterior;
free(i ndi viduo);
individuo = anterior ;

/* Se establece el histórico como vacío */


hi stori co->numeroCli entes=0;
hi stori co->ulti mo=NULL;
hi stori co->primero=NULL;

ITES-Paraninfo • 219
Problemas resueltos de C

char *nombreCli ente


<
\

Cliente individuo /* por valor */

/* Se comprueba el valor de los parámetros */


if (individuo == NULL)
return NULL;

return individuo->nombre;

C" ■'enee *anteri or

Cliente *indivi duo /* por valor */

~ Se comprueba el valor de los parámetros */


if (i ndi viduo == NULL)
return NULL;

return individuo->anterior;

■'e-ee ''ultimo

-■'seorico *historico /* por valor */

” Se comprueba el valor de los parámetros */


if '•i stori co == NULL)
return NULL;

return hi storico->ultimo ;

T aclama principal

i nt ■■: v o i d )

-"'sccrico histórico;
char ■'ombreClOOl];
I~-erze * indi viduo;

^ Sa*" valor inicial al histórico */


:s^erVaci o(&hi stori co) ;

¡leer ios nombres de ios clientes */


printf("Introducir el nombre del cliente\n");
printf("(Pul se Intro para terminar): ");

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

/* Imprimir eri pantalla los nombres de los clientes */


pri ntf("\n");
individuo = ultimo(&histori co);
while (individuo != NULL)
(
printf("%s\n",nombreCliente(individuo));
individuo = anteri or(indivi duo);

/* 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>

/* Estructura con un miembro array */


struct X
1
char id;
int m [10];

/* Dos variables de esa estructura */


struct X c = f ’c ’, i 1 ) i ;
struct X d = { ’d ’, { 3 I } ;

int main ( int argc, char *argv[] )

ITES-Paraninfo • 2 2 1
Problemas resueltos de C

printf("identidad en c antes de asignación: %c\n”,c.id);


printf("valor de c antes de asignación: M \ n " ,c.m[0]);

/* Asignación entre variables */


c = d;

printfC'identidad en c después de asignación: %c\n",c.id);


printfCvalor de c después de asignación: %d\n",c.m[0]);

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

void 'onerCentigrados ( struct Temperatura *T, float centigrados )

“ .centigrados = centigrados;
T.farenheit = 32.0 + (centigrados * 9.0)/5.0;

void PonerFarenheit ( struct Temperatura *T, float farenheit )

T.farenheit = farenheit;
T .centigrados = (farenheit - 32.0) * (5.0/9.0);

float ObtenerCentigrados ( struct Temperatura *temp )

return temp->centigrados;

float ObtenerFarenheit ( struct Temperatura *temp


(
return temp->farenheit;

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;

Vector *crear ( int numero_elementos )


(
Vector * v ;

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 ■;v->datos==NULL) return NUIL;

.->r.umero_el ementos=numero_el ementos ;


return v;

void destruir ( Vector *v )

if i7 !=NULL) I
if (v->datos!=NULL)
free(v->datos) ;
^ ”e e ( v ) ;

vo<d a'rracenar ( Vector *v, i.nt indice, int valor )

if .i=NULL) && (v->numero_elementos>indice))


.->datos)[indice] = valor;

irt ,'e:..;erar ( Vector * v , int indice, int *valor )

if .¡=NULL) && (v->numero_elementos>indice)) 1

’\alor) = ( (v->datos )[i ndi ce] ) ;


return 1;

T . ' c - i se indica que hay un error */


return -1;

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 .

t _ a cz'e de una peluquería...

#include <stdio.h>
fíinclude <stdlib.h>
#include <string.h>

* Definición de la cola mediante:

2 2 4 • ITES-Paraninfo
Capítulo 7 / Estructuras

* - Jos tipos 'struct ElementoCola ’ y 'struct Cola’


* - las operaciones ’primeroEnCola ’,
* ’siguienteAlaCola ’ y 'suTurno'
*/
struct ElementoCola (
char el iente[1000];
struct ElementoCola * siguiente;

struct Cola (
struct ElementoCola *ultimo;
struct ElementoCola *primero;

void iniciarCola ( struct Cola *cola )


{
if (cola — NUL O
return; I * N a d a que hacer */

!* Dar un valor inicial a los miembros de la cola */


cola->primero=NULL;
col a ->u1timo=NULL;

struct ElementoCola * alaCola ( struct Cola *cola, char *nombre )


1
struct ElementoCola * individuo;

/* Reservar memoria *!
indi vi duo=malloc(sizeof(struct ElementoCola));
if (NULL==individuo) (
perrorC"mal1o c:");
return NULL;

/* Establecer valores para el nuevo elemento */


i ndi vi d ú o - > s igui ente=NULL;
strcpy(individuo->cliente,nombre);

if (col a - >pri mero == NULL) {


/* I n s e r c i ó n d e l p r i m e r o */
col a *>primero=i ndi vi dúo;
col a->ultimo=i ndi vi dúo;
}
el se
i .
/* Dar la vez en la cola */
(col a ->u1timo)->siguiente=i ndi vi dúo;
col a->ultimo=i ndividuo;

return individuo;

ITES-Paraninfo • 225
Problemas resueltos de C

void suTurno ( struct Cola *cola, char *nombre )

struct ElementoCola * individuo;

/* Si hay alguien, es su turno... */


if (NULL!=cola->primero) {

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

' z'cgrama principal

int "3'Xvoid)

struct Cola colaPeluqueria;


char nombre[1001];

^ Dar valor a los miembros de la cola */


"'nic’arColaC&colaPeluqueria);

"María" es la primera en la cola... */


s*rcpy(nombre,"María");
a_aCola(&cola Peíuqueri a.nombre);
or“ntf("primera: %s\n",nombre);

T ”Rosa" va detras */
szrcpy(nombre,"Rosa");
2LaCola(&colaPeluqueria.nombre);
o-intf("siguíente: %s\n".nombre);

... Y de 1a misma forma para el resto de clientes.


printf("\n");

/* El turno primero toca a "María"... */


suTurno(&colaPeluqueria,nombre);
printfC'el turno es de; %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);

/* .. . Y de la misma forma para el r es t o de c l i e n t e s . */


printf("\n");

return (0):

Pro blem as Pro pu esto s


7 . 1 Dadas lal siguientes definiciones de variables:
s t r u c t complejo {
f l o a t real ;
f l o a t imaginario:

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

2. ypedef struct punto punto_t;


3. ypedef punto_t struct punto;
4. ypedef punto_t punto;

7.5 Dado el siguiente fragmento de programa:


s t r u c t punto!
f l o a t x, y;

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;

¿Cuál de las siguientes afirmaciones es falsa?


1. .X 1 y puntos[5].y = 15
:.,--:s[2] .X 2 y puntos[6].y = 16
3. .X 3 y puntos[3].y = 9
4. •y

7.6 Dadas las siguientes definiciones:


s t r u c t "1 l i n t e l ;1;
s t r u c t "2 {i nt el; s t r u c t rl c 2 ;} *v;
¿Cuál de las siguientes expresiones es correcta?
1. . .. . .

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

En el Capítulo 1 se describieron los principales componentes de un computador. En dicho capítulo se


presentaron dos a los que aquí se prestará especial atención: la memoria y el disco duro.
La memoria del computador es rápida, pero también es volátil, ya que su contenido se pierde al apagar el
computador. Para conseguir tener medios de almacenamiento en los que guardar los datos y recuperarlos en.
cualquier momento, se usan unidades de almacenamiento secundario como los discos duros. Estos medios
tienen la ventaja de que la información no se pierde cuando se apaga el computador (si el computador
se apaga de forma apropiada), por lo que son usados por todos los sistemas operativos para proporcionar
almacenamiento permanente.
Mientras que la memoria del computador se presenta al programador como un espacio plano y contiguo,
donde se reserva la zona a usar con la función de biblioteca mal 1oc y se libera con f ree, un disco duro
presenta una organización y gestión un poco más elaborada.
El centro de esta organización es el archivo. Un archivo, a semejanza de un archivo de oficina, es
donde se almacenan los datos de usuario. A los archivos se les da un nombre, una cadena de caracteres que
permite identificar el archivo para acceder o modificar los datos que contiene. En una oficina los archivos
son guardados en archivadores; de forma similar, los sistemas operativos permiten organizar los archivos en
carpetas o directorios. En la Figura 8.1 se muestra este símil.

Datos

■ Archivo

Carpeta

Figura 8 .1 Relación entre datos, archivos y carpetas.

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

s t di o .h. Como se muestra en la Figura 8.2, la implementación de la biblioteca estándar de gestión de


archivos se encarga de invocar a las funciones apropiadas del sistema operativo, ofreciendo siempre la
misma interfaz.

U N IX /L IN U X

fread ( & ! ¡ | sizeof(

read ( & | i 3 )

W in 3 2

fread ( & ¡ ¡ ; ; sizeof( : ¡ ) ^ 3 ^, ¡)

ReadFile ( Mi , | , 3 , M, NULL )

Figura 8.2 Distintas interfaces para la gestión de archivos.

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

Figura 8.3 Pasos habituales en la escritura y lectura de archivos.

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

8.2.1 Stream s (Flujos) de E/S


Como se ha comentado anteriormente, el tipo de datos FILE * permite representar un archivo, guardán­
dose toda la información necesaria. ¿Qué tipo de información se guarda?
Cuando se crea o abre un stream, la biblioteca s td i o . h realiza la petición del servicio correspondiente al
sistema operativo. El sistema operativo en respuesta genera un descriptor de archivo para poder identificarlo.
Parte de la información del stream es el descriptor asociado al archivo. Este descriptor es transparente,
aunque hay una función para conocerlo (fileno).
La asociación supone, en general, crear un almacén intermedio (buffer) por donde pasan los datos del
archivo. La gestión de este almacén es transparente para el programador y tanto su dirección como su
tamaño también se guardan como parte de la información del stream. La Figura 8.4 muestra la situación de
un stream después de realizar la primera lectura.

leer (<stream >,... <buffer del usuario>);

1 f
stream

fd__ buffer
12345] I I
- 4

read ( < fd > ,... <buffer> );

} f
I 1 2 3 4 5 6 7 8 9

F igura 8.4 Asociación de un archivo a un s tr e a m

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.

8.2.2 Creación y cierre de un archivo


La creación de un archivo se lleva a cabo en C mediante la función:
descriptor = fopen(nombre_archivo, modo);
donde descriptor se define como:
FILE *descriptor;
y modo debe ser "w" para especificar la creación de un archivo en el que se quiere escribir. Esta función
da como resultado la creación del archivo indicado en nombre_archi vo y su apertura para escritura. El
descriptor del stream asignado se devuelve en descriptor. Si el archivo no se puede crear o abrir para
escritura, la función topen devuelve NULL.
Una vez que se escriban todos los datos en el archivo, hay que cerrar el archivo. Cualquier archivo
abierto se puede cerrar mediante la función:
resultado = fc1ose(desc);
donde resultado es un número entero igual a 0 si el archivo se cerró bien y distinto de cero si hubo algún
error.
El cierre de un archivo rompe la asociación que existe entre él y el flujo asociado.

I ► EJEMPLO 8.1 Escriba un programa que crea el archivo ’’ejemplo.txt”.


Re s o l u c i ó n .
#include <stdio.h>

int main ( int argc, char *argv[] )


1
FILE *descriptor;

/* 1) Se crea el archivo y se abre para escritura */


descriptor= topen("./ejemplo.txt", "w");
if (descriptor == NULL)

ITES-Paraninfo • 2 3 3
Problemas resueltos de C

2 rintf("Error, no se puede crear el archivo\n");

el se

* 2) Archivo creado y abierto para escritura. Se escribe algo */


rintf(descriptor, "%ú", lililí);

^ 3) Al final, se cierra el archivo*/


fie’ose(descri ptor);

return(0);

El programa anterior muestra un ejemplo de creación de un archivo.


La función f open se encarga de crear y abrir para escritura el archivo. Si el archivo existe se borra su
contenido, truncándolo y poniendo su longitud a cero.
La función fel ose cierra el stream y efectúa todas las operaciones que han quedado pendientes. Por
ejemplo, en los streams de salida, se asegura de que la longitud del archivo sea la última actualizada,
modifica tiempos de acceso y vuelca al disco, si es necesario, los datos pendientes de escritura que quedan
en el buffer del stream.
¿Qué ocurre si se termina una aplicación sin cerrar los streams! El comportamiento no está definido
en el estándar de C, por lo que puede pasar cualquier cosa. Casi todos los compiladores efectúan un cierre
automático de los streams abiertos cuando se termina una aplicación, pero no es obligatorio, por lo que
puede haber casos en los que el archivo quede en un estado incoherente o se pierdan los datos del buffer que
todavía no se habían copiado a disco.

8.2.3 Cambio de nombre y borrado de un archivo


El lenguaje C permite cambiar el nombre de un archivo, de forma que se pueda acceder al mismo
mediante un nombre nuevo y que ya no esté accesible mediante su antiguo nombre. El cambio de nombre
de un archivo se lleva a cabo en C mediante la función:
'rsf::;: = rename(nombre_antiguo, nuevo_nombre);
donde r os - " t a do es un número entero igual a 0 si el archivo se renombró bien y distinto de cero si hubo
algún error.
El resultado del renombrado de un archivo cuando ya existe un archivo con el nuevo nombre no está
definido en el estándar y depende de cada implementación.
El borrado de un archivo se lleva a cabo en C mediante la función:
ros-'ta:: = removetnombre_archivo);
donde - e 3 ' s a d o es un número entero igual a 0 si el archivo se borró bien y distinto de cero si hubo algún
error.
El resultado del borrado de un archivo cuando el archivo a borrar está abierto no está definido en el
estándar y depende de cada implementación.

8.2.4 Apertura y cierre de un archivo


La otra forma de asociar un archivo a un stream es abrirlo mediante la función:
rosroróror = fopen(nombre_archivo, modo);
donde soseroptor se declara como:
rI_E ’descriptor;

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.

8.2.5 Lectura y escritura con formato


Las operaciones de E/S a archivos descritas en el estándar C permiten acceder a los datos de los archivos
con un cierto formato, que puede ser definido por el usuario. Las principales operaciones de E/S con formato
son las de lectura, a través de la función f s eanf y las de escritura, a través de la función f p ri nt f.
La función f s c anf , cuyo prototipo se describe a continuación, permite leer datos de un stream. Todos
los formatos y opciones de la función s canf son válidos para f s canf , pero aplicados a un stream.
l e i d o s = f s c a n f ( d e s c r i p t o r , format o, . . . ) ;
La función f s c a n f devuelve el número de elementos leídos, de los especificados en el formato, o un
número negativo si se produce un error de lectura. En caso de que el error se produzca antes de la conversión
de formatos, esta función devuelve un EOF, esto es, la constante que representa el fin de archivo.

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

T E u d e para leer hasta que se alcance el fin


” re la entrada (EOF) o se produzca un error.

; e c : (stdi n ,NULL);

* Leer con formato */


"sidos = fscanf(stdin,
"cantidad: %f %14s de %20s",
&cantidad, unidad, elemento);
if (1eidos != 3)

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

' _eer hasta el salto de línea */


do ■
ch=getc(stdi n);
while ((ch != ’\n’) && (ch != EOF)) ;

” Incrementar el número de líneas leídas */


' ""3++;

«hile (!feof(stdin) && !ferror(stdin));

En el programa anterior se puede apreciar cómo se indica a la función f s c a n f el formato de lectura,


pero no se incluye el retorno de carro (’\ n ’) Una vez leídos los datos según el formato, en un bucle usando
la función : e c : se leen todos los caracteres sobrantes y el retomo de carro final.
En el siguiente fragmento se muestra la ejecución del programa. Se ha introducido cantidad: 1.0
lll'-Ef'Erll234 de 12345678901234567890 y un retomo de carro, seguido de la secuencia de teclas
control-D í es decir, pulsando las dos teclas a la vez) que es la forma en UNIX/Linux de generar el cierre de
la entrada estándar para ese programa (fin de archivo)
1.0 12345678901234 de 12345678901234567890

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

1 while (!feof(stdin) && Iferror(stdin));

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

La primera función (feof) permite comprobar si se ha alcanzado el final de un stream, es decir, si se ha


alcanzado un EOF. En caso negativo devuelve cero, mientras que en caso afirmativo (si se ha llegado al fin
del archivo) devuelve un valor distinto de cero.
La segunda ( f e r r o r ) permite comprobar si se ha generado un error en las operaciones de E/S sobre un
stream. Si no existe error, devuelve cero. En caso contrario, devuelve un valor distinto de cero, pero dicho
valor no está definido en el estándar.
Por tanto, el bucle del programa de ejemplo se repite mientras no se alcance el fin del archivo y no haya
un error de lectura de datos.

Funciones d e e n tra d a y salida d e c a ra c te re s


Para llevar a cabo E/S de caracteres sobre los streams se pueden usar las funciones fgetcyfputc, que
son idénticas a las funciones de E/S para caracteres getcyputc usadas para la entrada y salida estándar.
La función:
imitse" = fgetc(stream);
lee el siguiente carácter de streamylo devuelve como un uns i gned cha r, lo que significa que se pueden
leer caracteres que usen los 8 bits, como el ASCII Extendido o los archivos binarios, sin perder información
debido a las conversiones a cha r implícitas. Si encuentra el fin del archivo, devuelve EOF.
La función:
'es = *putc(caracter, stream):
escribe s e ' s s c e r e n s t r e a m e n l a posición a la que apunta el indicador de posición del mismo. Si todo es
correcto, avanza el indicador o puntero de posición del stream y devuelve en res el carácter escrito, como
sin número entero. Si se produce un error, devuelve EOF.
Los stream de bytes se pueden leer con funciones de E/S orientadas a línea, similares a las funciones
res s y : . c s descritas para la E/S estándar. La función:
L e s ; s , n , stream);
lee. como máximo, n-1 caracteres de stream y los almacena en el string (char *) s. Ahora bien, si hay
algún carácter de fin de línea (/\n/) o se alcanza el fin de archivo entre los n caracteres a leer, sólo se lee
hasta ei carácter de fin de línea o hasta el final del archivo. En caso de error, el valor de s devuelto es NU L L.
La canción:
'es = “SutsCs , stream);
escribe el string s en st re am en la posición a la que apunta el indicador de posición del mismo. El carácter
nulo de terminación del string s no se escribe. Si todo es correcto, devuelve un valor no negativo, que suele
ser la longitud del string escrito. Si hay un error devuelve EOF.

G estión del buffer d e un stream

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>

#define TAM_BUFFER 256

int main(void)
1
char car;
int i :
char buffer[TAM_BUFFER];
int resultado ;

/* Fijar para stdout el almacén en buf, con buffering


* completo y tamaño de buffer 256 *!
resultado = setvbuf(stdout, buffer, _I0FBF, TAM_BUFFER);
i f (resultado != 0)
fprintf(stderr, "Error al definir el nuevo buffer \n");

!* Lectura de la entrada estándar y escritura en la *


* salida estándar. Se hace volcado del stream cada

ITES-Paraninfo • 239
Problemas resueltos de C

” 11 caracteres usando fflush. *1

«hile ;fscanf(stdin, "%c", &car) != EOF)

ntf (stdout, "%c", car);

if {(i % 10)== 0)

resultado = fflush(stdout);
if (resultado != 0)
fprintf(stderr, "Error al volcar el buffer\n");

return j );

8.2.6 Cambio del puntero de la posición


El estándar C define funciones para efectuar E/S con acceso directo a una determinada zona de un stream
sin tener que recorrerlo totalmente. Para ello hay que usar la función fseek:
'■‘t ~seek( FILE * st ream,
long i n t des pl azami ent o,
i n t des dedonde) ;
Que permite modificar el valor del indicador de posición de stream, sumando un despl azami ent o
relativo a la posición asociada a desdedonde. En caso de error devuelve un valor no nulo y cero en caso de
ejecución correcta. Pero, ¿qué puntos del archivo se pueden tomar como referencia para los desplazamientos
en t e t t e t m t e l E n e l archivo s t d i o . h se indican tres macros con los posibles valores de referencia para
el desplazamiento:
SEEEL_SET Indica un desplazamiento relativo al inicio del archivo.
SFFK C1 R Indica un desplazamiento relativo a la posición actual del archivo.
SFFK F VD Indica un desplazamiento relativo al final del archivo.
.'.Como se calcula la posición final del desplazamiento? Con una operación de suma:
tes :e + despl azami ent o
Para hacer un desplazamiento desde S EEK_CUR puede ser conveniente conocer el valor del puntero de
posición antes del salto. Este valor se puede obtener mediante la función:
' s r c ñnt f t e l 1 (FILE * st r eam) ;
Que devuelve el valor del puntero de posición si todo va bien y -1 si hay un error.
¿ Y qué ocurre si se salta desde el fin del archivo? Pues ocurre que se está agrandando el archivo, yendo
más allá del E1L cosa perfectamente legal en C. Se puede saltar tan lejos como se quiera, pero si se desea
que la modificación de la longitud del archivo permanezca es necesario escribir algo después del salto en la
nueva nosición. Si no escribe, el cambio del indicador del salto no tiene efecto.

8.2.7 Funciones de entrada y salida para archivos binarios


La principales funciones de C que sirven para efectuar lectura y escritura de datos binarios son f read y
~...' ■r a. y su declaración es:
Lze_z -readívoid * ptr,
s i z e _ t t a m_ e l ,
s i z e _ t num_el,
FILE * s t r e a m ) ;
Capítulo 8 I Entrada/salida a archivos

size_t fwrite(void * ptr,


size__t tam_el,
size_t num_el,
FILE * stream);
Para ambas funciones, los datos binarios se representan como un vector de elementos. La función f re ad
permite leer desde stream a una zona de memoria, cuya dirección de comienzo es ptr, un número num_el
de elementos, donde el tamaño de cada elemento expresado en bytes es t am_e 1. Si todo va bien, devuelve el
número de elementos leídos correctamente e incrementa el indicador de posición en el stream. Un número
menor que num_el indica que se ha alcanzado el fin del archivo.
La función fw rit e permite escribir un número n um_e 1 de elementos, de tamaño t am_e 1, desde una zona
de memoria, indicado por ptr, a un stream. Si todo va bien, devuelve el número de elementos escritos
correctamente e incrementa el indicador de posición en el stream. La devolución de un número menor que
num_el indica un error de escritura.

8.2.8 Funciones de entrada y salida para tipos de datos compuestos


En el capítulo 7 se definían dos variables (1 ib roA y 1 ib roB) de la siguiente forma:
/* Definición de la estructura */
struct FichaLibro {

char ti tul o [256] ;


char autor[256];
char ISBNC256] ;
char estante[256]
int prestado ;
char cliente[256]

/* Definición del tipo ’FichaLibro’ *!


typedef struct FichaLibro FichaLibro ;

/* Defi ni ci ón de las variables */


FichaLibro libr-oA, libroB;
En el caso de tener que escribir una estructura en un stream, es posible usar dos estrategias diferentes,
pero igualmente válidas:
■ Escribir uno a uno cada uno de los campos, utilizando las funciones como pri ntf, putc, etc.
■ Escribir toda la estructura a la vez usando la función fwri te, indicando la dirección de comienzo y el
tamaño de la estructura en bytes (usando si zeof).
De esta forma, la función fwri te ve una estructura cuyo tamaño es de, por ejemplo, 1.260 bytes como
un vector de 1.260 bytes cuya dirección de comienzo es la dirección de comienzo de la estructura. Son
dos formas de ver la misma zona de memoria.
Cada una tiene una serie de ventajas, pero, al mismo tiempo, una serie de inconvenientes:
■ Si se escribe cada campo por separado, los problemas pueden presentarse en la lectura puesto que es
fácil leer un número, pero para recuperar un string es necesario delimitarlo. Antes de leer el string es
preciso conocer cuánto ocupa o usar algún carácter especial que permita delimitarlo.
■ Escribir toda la estructura de una sola vez es más eficiente (sólo se realiza un acceso a disco) pero
menos portable. Suponga que se escribe una estructura que tenga un campo que es de tipo int en un
computador donde los enteros son de 32 bits. En un computador donde los enteros son de 64 bits no
puede leerse, puesto que lo que se escribe usando fwri te es el contenido de la zona de memoria donde
se guarda el valor usando la representación para el computador de 32 bits), pero no es válido en el
computador de 64 bits.

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 {

char ti tul o[256] ;


char autor[256] ;
char ISDN[256] ;
char estante[256] ;
•nt prestado:
char c :iente[256] ;

T ,e~~ r~ción del tipo ’Fichatibro’ */


typedef struct EstructuraFichaLibro FichaLibro ;

^ te^-'r-’c-ón de las variables */


~- z ' i J z rc 1 ibroA = {
"titulo",
"autor",
"ISDN",
"estante",
0,
"el iente",
1 :
r" a ; -c 1 ibroB;

-‘nt ‘eer_string ( FILE *descriptor,


char *string,
int longitud )

for 11=0; iClongitud; i++)

string[i]=getc(descriptor);

^ si hay un error, termina la lectura */


if (string[i] == EOF) {
stri ng[i]=’\0’;
return i ;

2 4 2 • ITES-Paraninfb
Capítulo 8 / Entrada/salida a archivos

/* añadir fin de string y terminar lectura */


string[1ongi tud] = ’\0’;
return longitud;

int main(void)
I
FILE *descriptor;
int longitud;

1 * 1 ) Creación del archivo */


descriptor= fopen(/campo-a-campo.txt", "vi");
if (descriptor == NULL)
I
printf("Error, no se puede crear el archivo\n");
returnC-1);

1 * 2 ) Se escribe el libro campo a campo */


fpri ntf(descriptor,"%á Xs\n",strien(1 ibroA.ti tul o) ,1 ibroA.ti tul o) ;
fprintf(descri ptor,"Id XsAn",strien(1 ibroA.autor),1 ibroA.autor);
fpri ntf(descri ptor,"%d %s\n”,stri en(1 ibroA.ISDN),1 ibroA.ISDN);
fpri ntf(descri ptor, "%d %s\n",strlen(libroA.estante),libroA.estante);
fprintf(descriptor,"%d\n",1ibroA.prestado);
fpri ntf(descri ptor,"Xd Xs\n",strien(1 ibroA.el iente),1 ibroA.el iente) ;

/* 3) Al final, se cierra */
fclose(descri ptor);

1 * 1 ) Apertura del archivo *!


descriptor“ fopen("./campo-a-campo.txt", "r");
if (descriptor == NULL)
1
printf("Error, no se puede abrir el archivo\n");
return(-1 ) ;

¡* 2) Se lee el libro campo a campo *!


fscanf(descriptor,"Xd ”,&1ongitud);
1eer_stri ng(descriptor,libroB.titulo,longitud);
fscanf(descriptor,"%d ",&1ongitud) ;
1eer_stri ng(descriptor,libroB.autor,longitud);
fscanf(descri ptor,"%d " ,&1ongi tud);
1eer_stri ng(descriptor,libroB.ISDN,longitud);
fscanf(descriptor,"%d ",&longitud);
1eer_stri ng(descriptor,libroB.estante,longitud);
fscanf(descriptor,"%d\n",&(libroB.prestado));
fscanf(descri ptor,"Xd ",&1ongi tud);
1eer_stri ng(descriptor,libroB.el iente,longitud);

/* 3) Al final, se cierra */

ITES-Paraninfo • 243
Problemas re su e /to s d e C

tseidescriptor) ;

- 5= escriben el libro campo a campo eri pantalla */


-;'-'---istdout,"%d %s\n",strlen(libroB.titulo),libroB.titulo);
stdout,"%d %s\n",strien(1 ibroB.autor),1 ibroB.autor) ;
-s-éstdout,"%d %s\n",strlen(1i broB. ISDN), libroB. ISDN);
--d - (stdout,"%d %s\n",stri en (11 broB.estante),1 ibroB.estante) ;
stdout,"%d\n",1 ibroB .prestado);
-a- (stdo ut,"%d %s\n",stri en (1 ibroB.cl iente ), 1 ibroB.cl iente ) ;

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

- Ce~' -~-JcJ6n de la estructura */


s tn:: Ess^cturaFichaLi bro i

;'í- vcu1o[256];
c-s- s_,cor[256];
ISDN[256];
z'tr escante[256];
--n ;-sstado;
z~ 'ente[256];

- del tipo ’Fi chati bro ’ */


z.-zaza- struct EstructuraFichaLibro FichaLibro ;

- bé-^'-z'ín de las variables */


r-~:-ad : -; ' ibroA = {
"titulo",
"autor",
"ISDN",
"estante",
O,
"cli ente",
1 ;
r- :' t d : 1 ibroB;

void)

rI_E -descriptor;

- E. Creación del archivo */


tes ptor= fopen("./todo-junto.txt", "w");
-- descriptor == NULL)

2 4 4 • /TcSJñ-r-
Capítulo 8 / Entrada!salida a archivos

printf("Error, no se puede crear el archivo\n" ) ;


returní-1 ) ;

/* 2) Se escribe el libro de una sola vez */


fwrite(&(1 ibroA), /* dirección de comienzo */
sizeof(1 ibroA), ¡* tamaño de cada elemento */
1, /* número de elementos */
descriptor); !* stream a donde escribir */

/* 3) Al final, se cierra */
fclose(descri ptor);

/* 1) Apertura del archivo */


descriptor= fopen("./todo-junto.txt", "r");
if (descriptor == NULL)
I
printf("Error, no se puede abrir el archivo\ri");
return(-1);

/* 2) Se lee el libro de una sola vez *1


fread(&( 1 ibroB), í* dirección de comienzo */
sizeof(1 ibroB), I* tamaño de cada elemento */
1, /* número de elementos */
descriptor); /* stream a donde escribir */

/* 3) Al final, se cierra */
fcloseídescriptor);

/* Se escribe el libro campo a campo en pantalla */


fprintf(stdout,"%d %s\n",strlen(libroB.titulo),libroB.titulo);
fprintf(stdout,"%d %s\n",'Strlen(1 ibroB.autor),libroB.autor);
fprintf(stdout,"%d Zs\n",strlen(libroB.ISDN),libroB.ISDN);
fprintf(stdout,"Zd Zs\n",strlen(libroB.estante),1 ibroB.estante);
fpri ntf(stdout,"%d\n",li broB.prestado);
fpri ntf(stdout,"%d %s\n",strlen(libroB.cliente),libroB.cliente);

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.

P ro blem a s Resu elto s


1 ► 8 .1 tsem ba m programa que renombre el archivo ej empi o .txt a ej empi o_l.txt.

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)

: "--t~ '"Error , no se puede renombrar el archivo\n");

I ► 8.2 Escrita i r programa que borre el archivo ’’ejemplo_l.txt”.


Re s o l u c ió n .
ri'trire strio.h>
Capítulo 8 / Entrada/salida a archivos

int res;

/* Borrado con remove */


res = removet"./ejemplo_l.txt");
if (res != 0)
(
printf("Error, no se puede borrar el archivo\n");

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;

/* 1) Creación del archivo ejemplo.txt */


dése = fopen(/ejemplo.txt", "w");
if (dése == NULL)
1
printf("Error, no se puede crear el archivo\n");
1
el se
i
/* 2) Se escriben 1000 ’a ’, añadiendo un
* salto de linea cada 80 caracteres */
for (num = 0; num < 1000; num++)
{
fprintf(dése, "%c", ’a ’);
if ((num % 80) == 0)
fprintf(dése, "%c", ’\n’);

/* 3) Al final, se cierra */
fclose(desc);

/* 1) Apertura del archivo ejemplo.txt *!


dése = f o p e n ( / e j e m p l o .txt", "r");
if (dése == NULL)
(
printf("Error, no se puede abrir el archivo\n");
}
el se

ITES-Paraninfo • 2 4 7
Problemas resueltos de C

/* 2) Se lee la primera letra */


fscanf(desc, "%c", &1etra);
printf(”%c\n", letra);

/* 3) Al final, se cierra */
fclose(desc);

return(O);

► 8.4 Cree un programa que copie un archivo en otro usando streams.


R e s o l u c ió n .
#include <stdio.h>

int main(void)

-1LE *fent;
-ILE *fsal;
char car;

' 1) Apertura del archivo de entrada */


-ere = fopen("./entrada.txt", "r");
if Ifent == NULL)

-printf(stderr, "Error abriendo entrada.txt\n");


return(O);

T 2) Creación del archivo de salida */


~z =~ = fopen("./sal ida.txt", "w ");
Jí "sal == NULL)

"jrlntfístderr, "Error creando sal ida.txt\n");


ose(fent);
return(O);

- 31 Suele de lectura y escritura */


p'n-'le (fscanf(fent, "%c", &car) != EOF)

" c i n t f (fsal, "%c", car);

- Cierre de los streams de entrada y salida */


- :se 1fent);
:se 1~ sa 1);

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

/* 1) Apertura del archivo de entrada *1


fent = fopenC/entrada.txt", "r");
if (fent == NULL)
{
fprintf(stderr, "Error abriendo entrada.txt \n”);
return(O);

/* 2) Creación del archivo de salida *t


fsal = topen ("./sai ida .txt", "w");
if (fsal == NULL)
I
fprintf(stderr, "Error creando salida.txt \n");
fc1ose(fent);
return(O);

/* 3) Bucle de lectura y escritura */


while ((car = fgetc(fent)) != EOF)
1
resultado = fputc(car, fsal);
if (.resultado == EOF)
fprintf(stderr, "Error al escribir %c \n", car);

/* 4) Cierre de los streams de entrada y de salida */


fclose(fent);
fclose(fsal );

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;

” 1/ Apertura del archivo de entrada */


-'e-s = fopen ("./entrada .txt", "r”);
-’f -'ent — NULL)

-intf(stderr, "Error abriendo entrada.txt \n");


return(O);

” i Creación del archivo de salida */


sa = fopen("./sal ida .txt", "w");
-'sal == NULL)

ntf(stderr, "Error creando salida.txt \n");


sse(fent);
--eturn(O);

Cuele de lectura y escritura con lineas */

. - Lectura de la línea siguiente */


sacos = fgets(car, 120, fent);
1 datos == NULL)
fprintf(stderr, "Error al leer \n");
el se
fprintf(stdin, "Longitud linea leída: %d \n", strlen(datos));
- Escritura de la línea */
-es = fputsídatos, fsal);
>es == EOF)
fprintf(stderr, "Error al escribir %s \n", datos);

■ 'e 1datos != NULL);

- - yerre de los streams de entrada y de salida */


:se -ent);
- s ase _sal );

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 )

250 • ÍTES-K i J 'ir-L


Capítulo 8 / Entrada!'salida a archivos

!* 1) Creación del archivo ejemplo.txt */


dése = topen ("./e jempl o .txt", "vi");
if (dése == NULL)
(
printf("Error, no se puede crear el archivo\n");
}
el se
{
/* 2) Se escriben 10 'a', dejando espacio
* para 5 letras */
for (num = 0; num < 10; num++)
1
fprintf(dése, "%c", 'a');
fseek(desc,6,SEEK_CUR);

/* 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];

int ¡mai n( void)


I
FILE *descriptor;
int i ;

/* 1) Creación del archivo *!


descriptor fopen("./vectorl .txt”, "w");
if (descriptor == NULL)
i
printf("Error , no se puede crear el archivo\n");
return(-1);

/* 2) Se escribe el libro de una sola vez */


fwri te(vectorl, /* dirección de comienzo */
si zeof(i nt), /* tamaño de cada elemento *1
6, /* número de elementos */
descri ptor); !* stream a donde escribir */

IT E S - P o r a n in f o • 251
Problemas rasu efess as C

t J final, se cierra */
:se sescriptor);

t : -reciura del archivo *¡


zeizr-zzz*= fopenC'./vectorl.txt", "r");
J- :es:'Jptor == NULL)

Error, no se puede abrir el archivo\n");


'erurn1-1);

T d 5e lee el libro de una sola vez */


--es; ,ezzor2, I* dirección de comienzo *1
s'zeof(int), !* tamaño de cada elemento */
f. !* número de elementos *1
sescriptor); /* stream a donde escribir */

^ I ~inal, se cierra */
-z~ : se :escri ptor);

- _'f escibe el libro campo a campo en pantalla */


ssdout,"vector2=");
' =1 ; i <6; i++)

— s- stdout,"%d ", vector2[i ]);

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)

-l_i ■ ' - e - 0 ' ’a d a ;


rl_í --s-i da ;

- 'c 'esri tado = 0;

' -:e-Zsra del archivo entrada.txt */


-a'Z'zzz = fopení/entrada.txt", "r");
— ' -'e'S'-ada == NULL)

J-a-(stderr,"Error: el archivo \"entrada.txt\" no\n");


-rf( stderr," pudo abrirse para lectura\n");
' e s j r n 10);

252 .
Capítulo 8 / Entrada/salida a archivos

I* Creación del archivo salida.txt */


fs a 1 ida = fopen("./sal ida.txt", "w");
if (fsal ida == NULL)
í
fprintf(stderrError: el archivo \"sal ida.txt\" no\n”);
fprintf(stderr," pudo abrirse para escritura\n");
fclose(fentrada);
return(O);

/* Copia, sustituyendo ’\t' por " " */


while ((car = fgetc( fentrada)) !=■ EOF)
(
if (ca r == ’\t ’) (

resultado = fprintf(fsalida," ");


if (resultado != 4)
fprintf(stderr, "Error: al escribir \" \"\n");

el se í

resultado = fputc(car, fsalida);


if (resultado == EOF)
fprintf(stderr, "Error: al escribir ’%c ’\n", car);

/* Cierre de Jos streams */


fclose(fentrada);
fclose(fsali da) ;

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;

/* Apertura del archivo entrada.txt */


fentrada = fopen(",/entrada.txt", "r");
if (fentrada == NULL)
1
fprintf(s t d e r r E r r o r : el archivo \"entrada.txt\" no\n");

ITES-Paraninfo • 2 5 3
Probienxs r e s p e t e s d e C

-J■~z-f'stderr," pudo abrirse para lectura\n");


’■■eturn 10);

” O-esc^rr del archivo salida.txt */


~iz = fopen("./sai ida.txt", "w");
;a == NULL)

*: -cbstderr,"Error: el archivo \"salida .txt\" no\n");


stderr," pudo abrirse para escritura\n");
:se ’^entrada);
r e s J r n 10);

^ O;;";, sustituyendo secuencia de blancos por ’ ’ *!


— :J:_se suenci a=0;

•'■'e car = fgetc(fentrada)) != EOF)

if (inicio_secuencia = 1)
ini ci o_secuenci a=0;

resultado = fputcicar, fsalida);


i f (resultado == EOF)
fprintf(stderr, "Error: al escribir ’%c ’\n", car);

if (inicio_secuencia == 0) j
ini ci o_secuenci a=l;

resultado = fputc(car, fsalida);


if (resultado == EOF)
fprintf(stderr, "Error: al escribir ’Xc ’\n", car);

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

--'-7 - stderr, "Error: el archivo \ "entrada .txt\" no\n");


---7 - stderr," pudo abrirse para 1ectura\n");

- f-eíC'C" del archivo salida.txt *!


~zi zi = -open("./sal ida.txt", "w");
^ J ;; == NULL)

-7 - s t d e r r E r r o r : el archivo \"sal id a .txt\" no\n");


- 7 ---7 - .stderr ," pudo abrirse para escritura\n");
- :se -entrada);

- Lees. s u s t i t u y e n d o ' \ t ' p o r " " *!


a :cr = fgetc(fentrada)) != EOF)

; a r > ’a ’) | | ( c a r < ’z ’) )
-esultado = fputc(toupper(car), fsalida);

■•esultado = fputc(tolowerCcar), fsalida);

J-' -espitado == EOF)


ntf(stderr, "Error: al escribir ’%c'\n", car);

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

é c = f open ("./entrada .txt", "r");

2 5 6 • ITES-Pc-
Capitulo 8 / Entradalsalida a archivos

if (fent == EOF)
(
fprintf(stderr, "Error abriendo entrada,txt\n");
return(O);

fsai = fopen("./sal ida.txt", "w");


if (fsal == NULL)
{
fprintfístderr, "Error creando salida.txtVn");
return(O);

while ( fscanf( fent, " %c ", &car ) != EOF )


I
fprintf(fsal, "%c", car);
1
fprintf(fsal,"%c",EOF);

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;

fent = fopen(/entrada.txt", "r");


if (fent == NULL) /* -> es NULL y no EOF */
{
fprintfístderr, "Error abriendo entrada.txt\n");
return(O);

fsal = topen("./sal ida.txt", "w");


if (fsal == NULL)
I
fprintf(stderr, "Error creando sal ida.txt\n");
fclose(fent); !* -> hay que cerrar el stream */
return(O);

while (fscant(fent,"%c",&car) != EOF) /* -> el formato es T e" */


¡
fprintf(fsal, "%c", car);

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 *!

- :se;-est); / * -> hay que usar fclose */


- z~ :se ;-‘sal ) ; /* -> hay que usar fclose */

retiirn .C ) ;

!► 8 . 14 Escriba un programa que cuente el número de líneas presentes en el archivo ’’entrada.txt”.


Re s o l u c i ó n .
#1nc!ude <str: o.h>

int - 5 -' void)

int
int -'■ '''eas;

t E -re-tura del archivo de e n t r a d a */


~ e t =*:-pen(/entrada .txt", "r");
if -‘e-o = NULL)

■■J --o-) s t d e r r , " E r r o r abri endo e n t r a d a . t x t \ n " ) ;


r e t u m ) D);

^ de lectura */

whiTe :a- = fgetc(fent)) != EOF)

- :a" == ’\n ’)
•■‘■'neas ++ ;

.'■'e-’-’s del stream de entrada */

^ 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>

ir.t int arge, char *argv[] )

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

1 * 1 ) Comprobar el número de parámetros */


if ( a r gc != 3) {

p rin tfC 'U so; %s <nombre del a r c h i v o ) "


a r g v [ 0 ] );
p r i n t f ( " <p al a b r a a b u s c a r > \ n " ) ;
return (0);

1 * 2 ) Referencias a los parámetros */


f n o mb r e = a r g v [ l ] ;
palabra=argv[2] ;

/ * 3) Apertura del archivo */


f d e s c r i p t o r = fopení fnombre, "r");
if ( f d e s c r i p t o r == NULL)
f
f p r i n t f ( s t d e r r , " E r r o r abri endo % s \ n " ,
fnombre);
return(O);

/* 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 (

while ( ( c a r != EOF) && ( car != p a l a b r a [ i ] ) ) {


car = f g e t c ( f d e s c r i p t o r ) ;

while ( ( c a r != EOF) && (c ar == pal a b r a [i])) i

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

r f : -Ierre del stream de entrada */


-c 'e s e (-descri p t o r ) ;

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 >

int mal'- i int argc, char * a r g v [ ] )


{
FI LE f e n t r a d a l , *fentrada2;
FI LE ’"fsal ida ;
int ce";
int r e s u l t a d o = 0;

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

/ * ■ Apertura del archivo entrada2 */


f ent ra d a 2 = f o p e n ( a r g v [ 2 ] , " r " ) ;
i f ( f e nt r a da 2 == NULL)
í
f p r i n t f ( s t d e r r E r r o r : el a r c h i v o V ’X s V n o \ n " , a r g v [ 2 ] );
fprintfístderr," pudo a b r i r s e para 1e c t u r a \ n ” );
fclose(fentradal);
return(O);

/* Creación del archivo salida *!

2 6 0 • ITES-Paraninfo
C a p ítu lo 8 /
E n tra d a / sa lid a a a rch ivos

fsalida = fopen(argv[3], "w");


if (fsalida == NUIL)
í
fprintf(stderrError: el archivo \"%s\" no\n" ,argv[3]);
fprintf(stderr," pudo abrirse para escritura\n");
fclose(fentradal);
return(O);

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

/* Cierre de ios streams */

fclose(fentradal);
fclose(fentrada2);
fclose(fsalida);

return(O);

Pro blem as Pro pu esto s


8 . 1 ¿Cuál de las siguientes sentencias permite crear un archivo denominado archivo.txt?
■ fopen(archivo.txt, "w");
■ fopen("archivo.txt", "r");
■ fopen("archivo.txt", w);
■ fopen("archivo.txt", "w");
8.2 Realice un programa que lea de un archivo cadena de caracteres como ” 10C” ó ”9F” e imprima en la salida
estándar la temperatura expresada en grados Celsius.
8.3 Redacte un programa que lea un archivo de texto y muestre dicho texto en pantalla situando una sola palabra
por línea.
8.4 Construya un programa que actúe como un pequeño editor de textos. El programa deberá leer al inicio el
nombre del archivo. A continuación se introducirá por teclado el texto que deberá ser guardado en el archivo
correspondiente. El programa finalizará cuando se pulse Ctrl-Z (final de archivo).
8.5 Diseñe un programa que tome como entrada el contenido de un archivo de texto y lo muestre por pantalla
eliminando la secuencia de caracteres entre ’< ’y ’> ’(ambos inclusive). Por ejemplo, dado un archivo con
el siguiente contenido:
<HTML>

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.

► EJEMPLO A . I Programa principal de Reloj

ino m ~

: /* inicial iza el manejador */

- ornsemar pantalla de operaciones posibles */


'1 - Leer hora de la aplicación \n");
"1 - Cambiar hora de la aplicación \n");
:---o- V - Fijar el tick del reloj de la aplicación \n");
;~-m~ - Fijar el vector de interrupción del reloj \n");
: r~ V - Salir \n");
’ V e ' ceclado */
s ;a-- V ; \n", opcion);
T s‘ :::Vn correcta pulsada, ejecutar acción correspondiente */

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

Figura A . I Módulos del programa reí oj

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

■ q u it Termina la ejecución del depurador.

A.I.I make y makefi 1e


¿Qué hay que hacer para compilar programas grandes con varios módulos o que necesitan bibliotecas?
La mejor solución es usar la utilidad make que le permite compilar aplicaciones enteras a partir de la infor­
mación que encuentra en el archivo ma kef i1e. El archivo ma kef i 1e describe, en UNIX y LINUX, cómo
construir una aplicación, incluyendo las dependencias de bibliotecas y archivos. El programa make utiliza
esta información para determinar qué archivos deben recompilarse y enlazarse para producir una unidad
ejecutable de la aplicación, de forma que sólo se compilen aquellos que han cambiado o que dependen de
un archivo que ha cambiado.
Para compilar usando makefi 1e en UNIX o LINUX, hay que ir al directorio donde se encuentra el
ma ke~i 1e de la aplicación y ejecutar el mandato make.
El Programa A. 1.1 contiene el m a ke f i1 e del programa reloj, que especifica cómo construir el ejecuta­
ble re 1oj . Por convención, una especificación demakefilese almacena típicamente en un archivo llamado
ma kef i1 e o Ma kef i1 e. Para ejecutar el programa ma ke y construir una aplicación, sólo hay que teclear el
mandato ma k.e dentro de un intérprete de mandatos (shell). El programa ma ke busca en el directorio actual
un archivo llamado makefileoMakefileylo procesa.

I ► EJEMPLO A . 2 iVlakefile de reloj


#
# Las siguientes lineas especifican que los archivos de C
# tendrán una extensión .c.

.SUFFIXES:
.SUFFIXES: ,c $(SUFFIXES)

# Asignar a CC el nombre del compilador de C a usar para compilar


# los programas.

CC = gcc

# Asignar a DIR_AP0Y0 la ruta del directorio que contiene


# los directorios del material de código.
# Las bibliotecas del sistema se incluyen por defecto.

DIR_AP0Y0 = ./apoyo

#
# La variable CFLAGS especifica dónde encontrar
# los archivos a incluir desde el material de código.

CFLAGS=-1 $(DIR_A POYO)/inelude -g

# 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.
#

0BJS=reloj.o clock_task.o hardware.o

#
# 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

# Regla clean. La ejecución de ’make clean’ borra todos los archivos


# objeto y el. ejecutable

elean:
rm -f *.o reloj

En un ma kef i 1e, un comentario comienza con el carácter #. La especificación de un ma kef i 1e puede


ser muy oscura, y difícil de mantener, si no se comenta adecuadamente.
Merece la pena comentar la línea:
DIR_APOYO = ./apoyo
Asigna la ruta que se corresponde con los directorios de inclusión y de la biblioteca del material de
código para la construcción del programa reí oj. Es siempre mejor usar variables relativas a la situación
del programa a construir, porque así se puede instalar la aplicación en distintos directorios cada vez sin que
haya que modificar el archivo ma kef i 1e.
CFLAGS=-1 $(DIR_APOYO)
LDFLAGS=-L$(DIR_A POYO)/I ib
LIBS= -reloj
Las líneas establecen variables que informan al compilador de C dónde debe buscar los archivos de
inclusión del material de código del usuario y los del sistema. Además de buscar en este directorio, el
compilador siempre busca en los directorios del sistema, que suelen ser / usr/i ncl ude y sus subdirectorios.

¡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

jp á gie m : Inserí =rc;ec¡: ¡¡urd Jcois Wirde.v Hea -I5SX


V isa, g g i i¡~ Cc.voüe re’o;..c Ct:l+!"7
£ á U O •1 H;
~ E'jiid rslci.dll F7
|| ( G m * j
. jJjtj Bacch Build..,
'
_

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í__¡

Figura A .2 Ventana principal del Visual C de Microsoft

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

Figura A .3 Compilación de reloj en el Visual C de Microsoft

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.

Anda mungkin juga menyukai