Você está na página 1de 806

Introduccin

a la programacin
con C
Andrs Marzal
Isabel Gracia

Departamento de leng
uajes y sistemas informticos
Codis dassignatura
II04 i IG04

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


troduccin a la programacin con C - UJI

In

Edita: Publicacions de la Universitat Jaume I. Servei de Comunicaci i Publicacion


s
Campus del Riu Sec. Edifici Rectorat i Serveis Centrals. 12071 Castell de la P
lana
http://www.tenda.uji.es
e-mail: publicacions@uji.es
Collecci Sapientia, 29
www.sapientia.uji.es
ISBN: 978-84-693-0143-2

Aquest text est subjecte a una llicncia Reconeixement-NoComercial-CompartirIgual d


e
Creative Commons, que permet copiar, distribuir i comunicar pblicament lobra sempr
e
que especifique lautor i el nom de la publicaci i sense objectius comercials, i ta
mb permet crear obres derivades, sempre que siguen distribudes amb aquesta mateixa llicn
cia.
http://creativecommons.org/licenses/by-nc-sa/2.5/es/deed.ca

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


Introduccin a la programacin con C - UJI

II

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


ramacin con C - UJI

III

Introduccin a la prog

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


amacin con C - UJI

IV

Introduccin a la progr

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


cin con C - UJI

Introduccin a la programa

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


amacin con C - UJI

VI

Introduccin a la progr

Haba un libro junto a Alicia, en la mesa; y mientras permaneca sentada


observando al Rey Blanco [. . . ], pasaba las hojas para ver si encontrab
a algn
trozo que poder leer: . . . Porque est todo en una lengua que no entiendo,
se dijo.
Estaba as:
RODNGIREJ
sasomiliga savot sal y ad le aballicoC
.edral le ne nabanerrab y nabapocsorig
,sovogrub sol nabatse selbarived odoT
.satsar sacela sal nabamarblis y
Durante un rato, estuvo contemplando esto perpleja; pero al final se le o
curri
una brillante idea. Ah, ya s!, es un libro del Espejo, naturalmente! Si lo
pongo delante de un espejo, las palabras se vern otra vez del derecho.
LEWIS CARROLL, Alici
a a travs del espejo.
El lenguaje de programacin C es uno de los ms utilizados (si no el que ms) en la
programacin de sistemas software. Es similar a Python en muchos aspectos fundamen
tales: presenta las mismas estructuras de control (seleccin condicional, iteracin),
permite
trabajar con algunos tipos de datos similares (enteros, flotantes, secuencias),
hace posible
definir y usar funciones, etc. No obstante, en muchas otras cuestiones es un len
guaje muy
diferente.
C presenta ciertas caractersticas que permiten ejercer un elevado control sob
re la
eficiencia de los programas, tanto en la velocidad de ejecucin como en el consumo
de
memoria, pero a un precio: tenemos que proporcionar informacin explcita sobre gran
cantidad de detalles, por lo que generalmente resultan programas ms largos y comp
licados que sus equivalentes en Python, aumentando as la probabilidad de que cometamo
s
errores.
En este captulo aprenderemos a realizar programas en C del mismo nivel que
los que sabamos escribir en Python tras estudiar el captulo 4 del primer volumen.
Aprenderemos, pues, a usar variables, expresiones, la entrada/salida, funciones
definidas
en mdulos (que en C se denominan bibliotecas) y estructuras de control. Lo nico que
dejamos pendiente de momento es el tratamiento de cadenas en C, que es sensiblem
ente
diferente al que proporciona Python.

1
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

Nada mejor que un ejemplo de programa en los dos lenguajes para que te lleve
s una
primera impresin de cun diferentes son Python y C. . . y cun semejantes. Estos dos
programas, el primero en Python y el segundo en C, calculan el valor de

b

i
i=a
para sendos valores enteros de a y b introducidos por el usuario y tales que 0 a
b.
from math import
Pedir lmites inferior y superior.
a int raw input
while a 0
print
a int raw input
b int raw input
while b a
print
b int raw input

Calcular el sumatorio de la raz cuadrada de i para i entre a y b.


s 0.0
for i in range a b 1
s
sqrt i
Mostrar el resultado.
print
print

a b s

include
include
int main void
int a b i
float s
Pedir lmites inferior y superior.
printf
scanf
a
while a 0
printf
printf
scanf
a
printf
scanf
while b
printf

b
a
a

printf
scanf

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
troduccin a la programacin con C -UJI
cUJI

2


In

Calcular el sumatorio de la raz cuadrada de i para i entre a y b.


s 0.0
for i a i
b i
s
sqrt i
Mostrar el resultado.
printf
printf

a b s

return 0
En varios puntos de este captulo haremos referencia a estos dos programas. No
los
pierdas de vista.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 Compara los programas
y
. Analiza sus semejanzas y
diferencias. Qu funcin desempean las llaves en
? Qu funcin crees
que desempean las lneas 6 y 7 del programa C? A qu elemento de Python se parecen
las dos primeras lneas de
? Qu simi
litudes y diferencias aprecias entre
las estructuras de control de Python y C? Cmo crees que se interpreta el bucle for
del
programa C? Por qu algunas lneas de
finalizan en punto y coma y otras
no? Qu diferencias ves entre los comentarios Python y los comentarios C?
...............................................................................
...

Python y C no slo se diferencian en su sintaxis, tambin son distintos en el modo e


n
que se traducen los programas a cdigo de mquina y en el modo en que ejecutamos los
programas.
Python es un lenguaje interpretado: para ejecutar un programa Python,
suministramos al intrprete un fichero de texto (tpicamente con extensin

) con

su
cdigo fuente. Si deseamos ejecutar
, por ejemplo, hemos
de escribir
en la lnea de rdenes Unix. Como resultado, el i
ntrprete
va leyendo y ejecutando paso a paso el programa. Para volver a ejecuta
rlo, has
de volver a escribir
en la lnea de rdenes, con
lo que
se repite el proceso completo de traduccin y ejecucin paso a paso. Aunqu
e no
modifiquemos el cdigo fuente, es necesario interpretarlo (traducir y ej
ecutar paso
a paso) nuevamente.

Intrprete
Python

Resultados
C es un lenguaje compilado: antes de ejecutar un programa escrito por

nosotros,
suministramos su cdigo fuente (en un fichero con extensin ) a un compilad
or
de C. El compilador lee y analiza todo el programa. Si el programa est
correctamente escrito segn la definicin del lenguaje, el compilador genera un
nuevo
fichero con su traduccin a cdigo de mquina, y si no, muestra los errores
que ha
detectado. Para ejecutar el programa utilizamos el nombre del fichero
generado. Si
no modificamos el cdigo fuente, no hace falta que lo compilemos nuevame
nte para
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
 3
c
UJI
Introduccin a la prog
ramacin con C - UJI

Un poco de historia
C ya tiene sus aitos. El nacimiento de C est estrechamente vinculado al del
sistema
operativo Unix. El investigador Ken Thompson, de AT&T, la compaa telefnica e
stadounidense, se propuso disear un nuevo sistema operativo a principios de l
os setenta.
Dispona de un PDP-7 en el que codific una primera versin de Unix en lenguaje
ensamblador. Pronto se impuso la conveniencia de desarrollar el sistema en
un lenguaje de
programacin de alto nivel, pero la escasa memoria del PDP-7 (8K de 18 bits
) hizo que
ideara el lenguaje de programacin B, una versin reducida de un lenguaje ya
existente:
BCPL. El lenguaje C apareci como un B mejorado, fruto de las demandas impu
estas
por el desarrollo de Unix. Dennis Ritchie fue el encargado del diseo del l
enguaje C y
de la implementacin de un compilador para l sobre un PDP-11.
C ha sufrido numerosos cambios a lo largo de su historia. La primera
versin estable
del lenguaje data de 1978 y se conoce como K&R C, es decir, C de Kernighan y
Ritchie. Esta versin fue descrita por sus autores en la primera edicin del l
ibro The
C Programming Language (un autntico best-seller de la informtica). La adopcin d
e
Unix como sistema operativo de referencia en las universidades en los aos
80 populariz
enormemente el lenguaje de programacin C. No obstante, C era atractivo por
s mismo
y pareca satisfacer una demanda real de los programadores: disponer de un
lenguaje
de alto nivel con ciertas caractersticas propias de los lenguajes de bajo
nivel (de ah
que a veces se diga que C es un lenguaje de nivel intermedio).
La experiencia con lenguajes de programacin diseados con anterioridad,
como
Lisp o Pascal, demuestra que cuando el uso de un lenguaje se extiende es
muy probable
que proliferen variedades dialectales y extensiones para aplicaciones muy
concretas,
lo que dificulta enormemente el intercambio de programas entre diferentes
grupos de
programadores. Para evitar este problema se suele recurrir a la creacin de
un comit
de expertos que define la versin oficial del lenguaje. El comit ANSI X3J9 (
ANSI
son las siglas del American National Standards Institute), creado en 1983
, considera
la inclusin de aquellas extensiones y mejoras que juzga de suficiente inte
rs para la
comunidad de programadores. El 14 de diciembre de 1989 se acord qu era el C
estndar y se public el documento con la especificacin en la primavera de 1990
.
El estndar se divulg con la segunda edicin de The C Programming Language,
de Brian Kernighan y Dennis Ritchie. Un comit de la International Standard
s Office
(ISO) ratific el documento del comit ANSI en 1992, convirtindolo as en un estn
dar

internacional. Durante mucho tiempo se conoci a esta versin del lenguaje co


mo ANSIC para distinguirla as del K&R C. Ahora se prefiere denominar a esta varia
nte C89 (o
C90) para distinguirla de la revisin que se public en 1999, la que se conoc
e por C99
y que es la versin estndar de C que estudiaremos.
C ha tenido un gran impacto en el diseo de otros muchos lenguajes. Ha
sido, por
ejemplo, la base para definir la sintaxis y ciertos aspectos de la semntic
a de lenguajes
tan populares como Java y C .

volver a ejecutar el programa: basta con volver a ejecutar el fichero g


enerado por
el compilador.
Para ejecutar
, por ejemplo, primero hemos de usar un com
pilador
para producir un nuevo fichero llamado
.
Compilador de C
Podemos ejecutar el programa escribiendo
en la lnea de rdenes Unix.1
Resultad
os
1
Por razones de seguridad es probable que no baste con escribir
para poder ejecutar un
programa con ese nombre y que reside en el directorio activo. Si es as, prueba co
n
.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

4


Introduccin a la programacin con C -UJI


cUJI

Si queremos volver a ejecutarlo, basta con escribir de nuevo


; no es
necesario volver a compilar el contenido del fichero
.
Resultados
La principal ventaja de compilar los programas es que se gana en velocidad d
e ejecucin, ya que cuando el programa se ejecuta est completamente traducido a cdigo de
mquina y se ahorra el proceso de traduccin simultnea que conlleva interpretar un
programa. Pero, adems, como se traduce a cdigo de mquina en una fase independiente
de la fase de ejecucin, el programa traductor puede dedicar ms tiempo a intentar e
ncontrar la mejor traduccin posible, la que proporcione el programa de cdigo de mqui
na
ms rpido (o que consuma menos memoria).
Nosotros usaremos un compilador concreto de C:
(en su versin 3.2 o s
uperior)2 .
Su forma de uso ms bsica es sta:
La opcin
es abreviatura de output, es decir, salida, y a ella le sigue el nombre
del
fichero que contendr la traduccin a cdigo mquina del programa. Debes tener presente
que dicho fichero slo se genera si el programa C est correctamente escrito.
Si queremos compilar el programa
hemos de usar una opcin e
special:
La opcin
se debe usar siempre que nuestro programa utilice funciones del md
ulo
matemtico (como sqrt, que se usa en
). Ya te indicaremos por qu en
la
seccin dedicada a presentar el mdulo matemtico de C.
C99 y
Por defecto,
acepta programas escritos en C89 con extensiones introd
ucidas por
GNU (el grupo de desarrolladores de muchas herramientas de Linux). Muchas
de esas
extensiones de GNU forman ya parte de C99, as que
es, por defecto,
el compilador
de un lenguaje intermedio entre C89 y C99. Si en algn momento da un aviso
indicando
que no puede compilar algn programa porque usa caractersticas propias del C
99 no
disponibles por defecto, puedes forzarle a compilar en modo C99 as:

Has de saber, no obstante, que


an no soporta el 100% de C99 (aunqu
e s todo
lo que te explicamos en este texto).
El compilador
acepta muchas otras variantes de C. Puedes forzarl
e a aceptar
una en particular asignando a la opcin
el valor
, ,
o
.

Empezaremos por presentar de forma concisa cmo traducir la mayor parte de los pro
gramas Python que aprendimos a escribir en los captulos 3 y 4 del primer volumen
a
programas equivalentes en C. En secciones posteriores entraremos en detalle y no
s dedicaremos a estudiar las muchas posibilidades que ofrece C a la hora de selecciona
r tipos
de datos, presentar informacin con sentencias de impresin en pantalla, etc.
2
La versin 3.2 de es la primera en ofrecer un soporte suficiente de C99. Si
usas una versin anterior,
es posible que algunos (pocos) programas del libro no se compilen correctamente.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2

5
Introduccin a la programacin con C - UJI
UJI
c

1.

Los programas (sencillos) presentan, generalmente, este aspecto:


include
Posiblemente otros

include

int main void


Programa principal
return 0
Hay, pues, dos zonas: una inicial cuyas lneas empiezan por include (equi
valentes
a las sentencias import de Python) y una segunda que empieza con una lne
a
int main void y comprende las sentencias del programa principal mas una
lnea
return 0 , encerradas todas ellas entre llaves ( y ).
De ahora en adelante, todo texto comprendido entre llaves recibir el nom
bre de
2.
riable

bloque.
Toda variable debe declararse antes de ser usada. La declaracin de la va

consiste en escribir el nombre de su tipo (int para enteros y float par


a flotantes)3
seguida del identificador de la variable y un punto y coma. Por ejemplo
, si vamos a
usar una variable entera con identificador a y una variable flotante co
n identificador
b, nuestro programa las declarar as:
include
int main void
int a
float b
Sentencias donde se usan las variables
return 0
No es obligatorio que la declaracin de las variables tenga lugar justo a
l principio
del bloque que hay debajo de la lnea int main void , pero s conveniente.4
Si tenemos que declarar dos o ms variables del mismo tipo, podemos hacer
lo
en una misma lnea separando los identificadores con comas. Por ejemplo,
si las
variables x, y y z son todas de tipo float, podemos recurrir a esta for
ma compacta
de declaracin:
include
int main void

float x y z

3
Recuerda que no estudiaremos las variables de tipo cadena hasta el prximo ca
ptulo.
4
En versiones de C anteriores a C99 s era obligatorio que las declaraciones s
e hicieran al principio de
un bloque. C99 permite declarar una variable en cualquier punto del programa, si
empre que ste sea anterior
al primer uso de la misma.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

6


return 0

3.
Las sentencias de asignacin C son similares a las sentencias de asignac
in Python:
a mano izquierda del smbolo igual ( ) se indica la variable a la que se
va a asignar
el valor que resulta de evaluar la expresin que hay a mano derecha. Cad
a sentencia
de asignacin debe finalizar con punto y coma.
include
int main void
int a
float b
a
b

2
0.2

return 0
Como puedes ver, los nmeros enteros y flotantes se representan igual qu
e en
Python.
4.
Python.

Las expresiones se forman con los mismos operadores que aprendimos en


Bueno, hay un par de diferencias:
Los operadores Python and, or y not se escriben en C, respec

tivamente, con
, y ;
No hay operador de exponenciacin (que en Python era
).
Hay operadores para la conversin de tipos. Si en Python escri
bamos float x
para convertir el valor de x a flotante, en C escribiremos f
loat x para expresar lo mismo. Fjate en cmo se disponen los parntesis: los op
eradores de
conversin de tipos son de la forma tipo .
include
int main void
int a
float b
a
b

13 2
2.0 1.0

return 0

Las reglas de asociatividad y precedencia de los operadores son casi l


as mismas
que aprendimos en Python. Hay ms operadores en C y los estudiaremos ms
adelante.
5.
Para mostrar resultados por pantalla se usa la funcin printf . La funcin
recibe uno
o ms argumentos separados por comas:
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
cUJI
Introduccin a la programacin con C -UJI

7

primero, una cadena con formato, es decir, con marcas de la fo


rma

para representar enteros y marcas

para representar flotantes (en

los que podemos


usar modificadores para, por ejemplo, controlar la cantidad de
espacios que
ocupar el valor o la cantidad de cifras decimales de un nmero fl
otante);
y, a continuacin, las expresiones cuyos valores se desea mostra
r (debe haber
una expresin por cada marca de formato).
include
int main void
int a
float b
a
b

13 2
2.0 1.0

printf

1
a b

return 0
La cadena con formato debe ir encerrada entre comillas dobles, no simp
les. El
carcter de retorno de carro ( ) es obligatorio si se desea finalizar la
impresin
con un salto de lnea. (Observa que, a diferencia de Python, no hay oper
ador de
formato entre la cadena de formato y las expresiones: la cadena de for
mato se
separa de la primera expresin con una simple coma).
Como puedes ver, todas las sentencias de los programas C que estamos p
resentando
finalizan con punto y coma.
6. Para leer datos de teclado has de usar la funcin scanf . Fjate en este ejemp
lo:
include
int main void
int a
float b
scanf
scanf
printf

a
b
a b

return 0
La lnea 8 lee de teclado el valor de un entero y lo almacena en a. La ln

ea 9 lee
de teclado el valor de un flotante y lo almacena en b. Observa el uso
de marcas
de formato en el primer argumento de scanf :

seala la lectura de u

n int y
la de un float. El smbolo que precede al identificador de la variable e
n la que
se almacena el valor ledo es obligatorio para variables de tipo escalar
.
Si deseas mostrar por pantalla un texto que proporcione informacin acer
ca de lo
que el usuario debe introducir, hemos de usar nuevas sentencias printf
:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

8


include
int main void
int a
float b
printf
scanf
printf
scanf

a
b

printf

a b

return 0
7.

La sentencia if de Python presenta un aspecto similar en C:


include
int main void
int a
printf
scanf

if a 2
printf
printf

return 0
Ten en cuenta que:
la condicin va encerrada obligatoriamente entre parntesis;
y el bloque de sentencias cuya ejecucin est supeditada a la sati
sfaccin de
la condicin va encerrado entre llaves (aunque matizaremos esta
afirmacin
ms adelante).
Naturalmente, puedes anidar sentencias if.
include
int main void
int a
printf
scanf
if a
Andrs aMarzal/Isabel

a
2

Introduccin

Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

9

printf
if a 0
printf

return 0
Tambin hay sentencia if else en C:
include
int main void
int a
printf
scanf
if a 2
printf

a
0

else
printf
return 0
No hay, sin embargo, sentencia if elif, aunque es fcil obtener el mismo
efecto con
una sucesin de if else if:
include
int main void
int a
printf
scanf

if a 0
printf
else if a
printf
else if a
printf
else
printf
return 0

0
0

Introduccin
Andrs aMarzal/Isabel
la programacin con -C ISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

10
10

8.
La sentencia while de C es similar a la de Python, pero has de tener en
cuenta
la obligatoriedad de los parntesis alrededor de la condicin y que las sen
tencias
que se pueden repetir van encerradas entre un par de llaves:
include
int main void
int a
printf
scanf

while a
printf
a
1

0
a

printf
return 0
9.

Tambin puedes usar la sentencia break en C:


include
int main void
int a b
printf
scanf

b 2
while b a
if a b 0
break
b
if b
a
printf
else
printf

1
a
a

return 0
10.
ncia

Los mdulos C reciben el nombre de bibliotecas y se importan con la sente


include. Ya hemos usado include en la primera lnea de todos nuestros pr

ogramas: include
ciones de

. Gracias a ella hemos importado las fun

entrada/salida scanf y printf . No se puede importar una sola funcin de


una biblioteca: debes importar el contenido completo de la biblioteca.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

11
11

Las funciones matemticas pueden importarse del mdulo matemtico con includ
e
y sus nombres son los mismos que vimos en Python (sin para
el seno,
cos para el coseno, etc.).
include
include
int main void
float b
printf
scanf

if b
0.0
printf
sqrt b
else
printf
return 0
No hay funciones predefinidas en C. Muchas de las que estaban predefin
idas en
Python pueden usarse en C, pero importndolas de bibliotecas. Por ejempl
o, abs
(valor absoluto) puede importarse del mdulo

(por standard library,

es
decir, biblioteca estndar).
Las (aproximaciones a las) constantes y e se

ueden im ortar de la bib

lioteca
matemtica, ero sus identificadores son ahora

, res ectivament

e.
No est mal: ya sabes traducir rogramas Python sencillos a C (aunque no sabe
mos
traducir rogramas con definiciones de funcin, ni con variables de ti o cadena, n
i con
listas, ni con registros, ni con acceso a ficheros. . . ). Qu tal racticar con un
os ocos
ejercicios?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Traduce a C este programa Python.
a
int raw input
b
int raw input
if a b
maximo
else
maximo

a
b

print
3

maximo
Traduce a C este programa Python.

n int raw input


m int raw input
if n m
print

100
n m

else
print
n m
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
12
12
ramacin con C - UJI

Introduccin a la prog

c
UJI

Traduce a C este programa Python.


from math import sqrt
x1 float raw input
y1 float raw input
x2 float raw input
y2 float raw input
dx x2 x1
dy y2 y1
distancia sqrt dx 2
print

5
a
b

dy

distancia

Traduce a C este programa Python.


float raw input
float raw input

if a
0
x
b a
print
else
if b
0
print
else
print
6

Traduce a C este programa Python.


from math import log
x 1.0
while x 10.0
print x
x x 1.0

log x

Traduce a C este programa Python.


n 1
while n 6
i 1
while i 6
print n i
i i 1
print
n n 1

Traduce a C este programa Python.


from math import pi
opcion 0
while opcion 4
print
print
print
print
print
opcion int raw input
radio

float raw input

Andrs Marzal/Isabel
Introduccin
Gracia

a la programacin con- ISBN:


C
978-84-693-0143-2
uccin a la programacin con C - UJI
c
UJI

13
13

Introd

if opcion 1
diametro 2 radio
print
diametro
elif opcion 2
perimetro 2 pi radio
print
perimetro
elif opcion 3
area pi radio
2
print
area
elif opcion 0 or opcion 4
print
opcion
...............................................................................
...
Ya es hora, pues, de empezar con los detalles de C.

Un programa C no es ms que una coleccin de declaraciones de variables globales y d


e
definiciones de constantes, macros, tipos y funciones. Una de las funciones es e
special:
se llama main (que en ingls significa principal) y contiene el cdigo del programa
principal. No nos detendremos a explicar la sintaxis de la definicin de funciones
hasta
el captulo 3, pero debes saber ya que la definicin de la funcin main empieza con
int main void y sigue con el cuerpo de la funcin encerrado entre un par de llaves.
La funcin main debe devolver un valor entero al final (tpicamente el valor 0), por
lo que
finaliza con una sentencia return que devuelve el valor 0.5
La estructura tpica de un programa C es sta:
Importacin de funciones variables constantes etc
Definicin de constantes y macros
Definicin de nuevos tipos de datos
Declaracin de variables globales
Definicin de funciones
int main void
Declaracin de variables propias del programa principal o sea locales a ma
in
Programa principal
return 0
Un fichero con extensin que no define la funcin main no es un programa C
completo.
Si, por ejemplo, tratamos de compilar este programa incorrecto (no define mai
n):
E
int a

a 1
5
El valor 0 se toma, por un convenio, como seal de que el programa finaliz cor
rectamente. El sistema
operativo Unix recibe el valor devuelto con el return y el intrprete de rdenes, po
r ejemplo, puede tomar
una decisin acerca de qu hacer a continuacin en funcin del valor devuelto.
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

14
14

el compilador muestra el siguiente mensaje (u otro similar, segn la versin del com
pilador
que utilices):

Fjate en la tercera lnea del mensaje de error:


.

As como en Python la indentacin determina los diferentes bloques de un programa, e


n
C la indentacin es absolutamente superflua: indentamos los programas nicamente par
a
hacerlos ms legibles. En C se sabe dnde empieza y dnde acaba un bloque porque
ste est encerrado entre una llave abierta ( ) y otra cerrada ( ).
He aqu un ejemplo de bloques anidados en el que hemos indentado el cdigo para
facilitar su lectura:
include
int main void
int a b c minimo
scanf
scanf
scanf
if a b
if a c
minimo
else
minimo
else
if b c
minimo
else
minimo
printf
return 0

a
b
c
a
c

b
c
minimo

Este programa podra haberse escrito como sigue y sera igualmente correcto:
include
int main void

int a b c minimo
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

15
15

scanf
a
scanf
b
scanf
c
if a b
if a c
minimo
else minimo c

else
if b c
minimo
else minimo c
printf
return 0

b
minimo

Cuando un bloque consta de una sola sentencia no es necesario encerrarla ent


re
llaves. Aqu tienes un ejemplo:
include
int main void
int a b c minimo
scanf
a
scanf
b
scanf
c
if a b
if a c minimo
else minimo c
else
if b c minimo
else minimo c
printf
return 0

b
minimo

De hecho, como if else es una nica sentencia, tambin podemos suprimir las llaves
restantes:
include
int main void
int a b c minimo
scanf
scanf
scanf
if a b
if a
else
else
if b
else

a
b
c
c minimo
minimo c

c minimo
minimo c

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
16
16
cUJI
Intro
duccin a la programacin con C -UJI

printf
return 0

minimo

Debes tener cuidado, no obstante, con las ambigedades que parece producir un
slo
else y dos if:
E
E
include
int main void
int a b c minimo
scanf
scanf
scanf
if a b
if a c
printf
else
printf
printf
return 0

a
b
c

minimo

Cul de los dos if se asocia al else? C usa una regla: el else se asocia al if ms prx
imo
(en el ejemplo, el segundo). Segn esa regla, el programa anterior no es correcto.
El
sangrado sugiere una asociacin entre el primer if y el else que no es la que inte
rpreta
C. Para que C entienda la intencin del autor es necesario que explicites con llaves
el
alcance del primer if:
include
int main void
int a b c minimo
scanf
scanf
scanf
if a b
if a c
printf
else
printf
printf
return 0

a
b
c

minimo

Ahora que has adquirido la prctica de indentar los programas gracias a la dis
ciplina
impuesta por Python, sguela siempre, aunque programes en C y no sea necesario.
Una norma: las sentencias C acaban con un punto y coma. Y una excepcin a la

norma: no hace falta poner punto y coma tras una llave cerrada.6
Dado que las sentencias finalizan con punto y coma, no tienen por qu ocupar u
na
lnea. Una sentencia como a 1 podra escribirse, por ejemplo, en cuatro lneas:
Habr una excepcin a esta norma: las construcciones struct, cuya llave de cierr
e debe ir seguida de un
6
punto y coma.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

17
17

La indentacin no importa. . . pero nadie se pone de acuerdo


En C no es obligatorio indentar los programas, aunque todos los program
adores estn
de acuerdo en que un programa sin una correcta indentacin es ilegible. Pero
no hay
consenso en lo que significa indentar correctamente! Hay varios estilos d
e indentacin
en C y cada grupo de desarrolladores escoge el que ms le gusta. Te prese
ntamos unos
pocos estilos:
a) La llave abierta se pone en la misma lnea con la estructura de contro
l y la llave de
cierre va en una lnea a la altura del inicio de la estructura:
if a 1
b 1
c 2
b) dem, pero la llave de cierre se dispone un poco a la derecha:
if a 1
b 1
c 2
c) La llave abierta va en una lnea sola, al igual que la llave cerrada.
Ambas se disponen
a la altura de la estructura que gobierna el bloque:
if a

1
b
c

1
2

d) dem, pero las dos llaves se disponen ms a la derecha y el contenido de


l bloque
ms a la derecha:
if a

1
b
c

1
2

e) Y an otro, con las llaves a la misma altura que el contenido del bloq
ue:
if a

1
b
c

1
2

No hay un estilo mejor que otro. Es cuestin de puro convenio. An as, h


ay ms
de una discusin subida de tono en los grupos de debate para desarrollado
res de C.

Increble, no? En este texto hemos optado por el primer estilo de la lista
(que, naturalmente, es el correcto
) para todas las construcciones del lenguaje
a excepcin
de la definicin de funciones (como main), que sigue el convenio de inden
tacin que
relacionamos en tercer lugar.

a
1

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

18
18

Pero aunque sea lcito escribir as esa sentencia, no tienen ningn sentido y hace ms
difcil la comprensin del programa. Recuerda: vela siempre por la legibilidad de lo
s
programas.
Tambin podemos poner ms de una sentencia en una misma lnea, pues el compilador
sabr dnde empieza y acaba cada una gracias a los puntos y comas, las llaves, etc.
El
programa
, por ejemplo, podra haberse escrito as:
include
include
int main void
printf

int a b i float s

Pedir lmites inferior y superior.

scanf

a while a 0

printf
scan

printf
f

a
printf

scanf

b while b a

pr

intf
a printf
scanf
b
Calcular el sumatorio de la raz cuadrada de i para i entre
a y b.
s 0.0 for i a i
b i
s
sqrt i
Mostrar
el resultado
printf
printf
a b s return 0
Obviamente, hubiera sido una mala eleccin: un programa escrito as, aunque correcto
, es
completamente ilegible.7
Un programador de C experimentado hubiera escrito sumatorio c utilizando lla
ves
slo donde resultan necesarias y, probablemente, utilizando unas pocas lneas menos.
Estudia las diferencias entre la primera versin de sumatorio c y esta otra:
include
include
int main void
int a b i
float s

anf

Pedir lmites inferior y superior.


printf
scanf
while a 0
printf
a
printf
while b
printf
scanf

a
sc

scanf

a
b

Calcular el sumatorio de la raz cuadrada de i para i entre a y b.


s 0.0
for i a i
b i
s
sqrt i

Mostrar el resultado.
printf

a b s

return 0
7
Quiz hayas reparado en que las lneas que empiezan con include son especiales
y que las tratamos
de forma diferente: no se puede jugar con su formato del mismo modo que con las
dems: cada sentencia
include debe ocupar una lnea y el carcter debe ser el primero de la lnea.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

19
19

International Obfuscated C Code Contest


Es posible escribir programas ilegibles en C, hasta tal punto que hay un c
oncurso internacional de programas ilegibles!: el International Obfuscated C Code Cont
est (IOCCC).
Este programa (en K&R C, ligeramente modificado para que compile con
) concurs
en 1989:

Sabes qu hace? Slo imprime en pantalla


! Este otro, de la edicin de
1992, es un generador de anagramas escrito por Andreas Gustafsson (AG
):

El programa lee un diccionario de la entrada estndar y recibe el nmero de p


alabras
del anagrama (precedido por un guin) y el texto del que se desea obtener u
n anagrama.
Si compilas el programa y lo ejecutas as descubrirs algunos anagramas curio
sos

Por pantalla aparecern decenas de anagramas, entre ellos

y
. Usando un diccionario espaol y diferentes nmeros
de
palabras obtendrs, entre otros, stos:
o
.
Ya sabes: puedes escribir programas ilegibles en C. Procura que tus pr
ogramas no
merezcan una mencin de honor en el concurso!

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

20
20

Los lenguajes de programacin en los que el cdigo no debe seguir un formato det
erminado de lneas y/o bloques se denominan de formato libre. Python no es un lengua
je
de formato libre; C s.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9 Este programa C incorrecto tiene varios errores que ya puedes detectar. Indica
cules son:
include
int a b
scanf
while a
scanf
scanf
printf

scanf

b
a
b
a b

10 Indenta correctamente este programa C.


include
int main void
int a b
scanf
scanf
while a
scanf
scanf

a
b
b
a
b

printf
return 0

a b

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

C99 permite escribir comentarios de dos formas distintas. Una es similar a la de


Python:
se marca el inicio de comentario con un smbolo especial y ste se prolonga hasta el
final
de lnea. La marca especial no es , sino . El segundo tipo de comentario puede ocu
par
ms de una lnea: empieza con los caracteres
y finaliza con la primera aparicin
del
par de caracteres .
En este ejemplo aparecen comentarios que abarcan ms de una lnea:

Un programa de ejemplo.
Propsito: mostrar algunos efectos que se pueden lograr con
comentarios de C
include

Programa principal

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
21
21
ramacin con C - UJI

Introduccin a la prog

c
UJI

int main void


int a b c
int m

Los tres nmeros.


Variable para el mximo de los tres.

Lectura de un nmero
printf
scanf
a
... de otro ...
printf
scanf
b
... y de otro ms.
printf
scanf
c
if a b
if a c
En este caso a > b y a > c.
m a
else Y en este otro caso b < a c.
m c
else
if b c
En este caso a b y b > c.
m b
else Y en este otro caso a b c.
m c
Impresin del resultado.
printf
a b c m
return 0
Uno de los comentarios empieza al principio de la lnea 1 y finaliza al final
de la
lne 6 (sus dos ltimos caracteres visibles son un asterisco y una barra). Hay otro
que
empieza en la lnea 10 y finaliza en al lnea 12. Y hay otros que usan las marcas
y
en lneas como la 20 o la 22, aunque hubisemos podidos usar en ambos casos la marca
.
Los comentarios encerrados entre
y
no se pueden anidar. Este frag
mento de
programa es incorrecto:

Por qu? Parece que hay un comentario dentro de otro, pero no es as: el comentario
que empieza en el primer par de caracteres
acaba en el primer par de caracte
res ,
no en el segundo. El texto del nico comentario aparece aqu enmarcado:

As pues, el fragmento
no forma parte de comentario alguno y no tien
e
sentido en C, por lo que el compilador detecta un error.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11 Haciendo pruebas durante el desarrollo de un programa hemos decidido comentar
una lnea del programa para que, de momento, no sea compilada. El programa nos que
da
as:
include
int main void

int a b i j
scanf
scanf
i a

a
b

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
2222
Introduccin a la prog
ramacin con C - UJI
c
UJI

j 1
while i
b
printf
j 2
i 1

i j

printf
return 0

Compilamos el programa y el compilador no detecta error alguno. Ahora decidimos


comentar el bucle while completo, as que aadimos un nuevo par de marcas de comentari
o
(lneas 11 y 17):
include
int main void
int a b i j
scanf
scanf
i a
j 1

a
b

while i
b
printf
j
2
i 1

i j

printf
return 0

Al compilar nuevamente el programa aparecen mensajes de error. Por qu?


12 Da problemas este otro programa con comentarios?
include
int main void
int a b i j
scanf
scanf
i a
j 1

a
b

while i
b
printf
j
2
i 1
printf
return 0
13

i j

Cmo se interpreta esta sentencia?

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
roduccin a la programacin con C - UJI
c
UJI

2323

Int

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

Por valores literales nos referimos a valores de nmeros, cadenas y caracteres dad
os
explcitamente. Afortunadamente, las reglas de escritura de literales en C son sim
ilares a
las de Python.

Una forma natural de expresar un nmero entero en C es mediante una secuencia de


dgitos. Por ejemplo, 45, 0 o 124653 son enteros. Al igual que en Python, est prohi
bido
insertar espacios en blanco (o cualquier otro smbolo) entre los dgitos de un liter
al entero.
Hay ms formas de expresar enteros. En ciertas aplicaciones resulta til expresa
r
un nmero entero en base 8 (sistema octal) o en base 16 (sistema hexadecimal). Si
una
secuencia de dgitos empieza en 0, se entiende que codifica un nmero en base 8. Por
ejemplo, 010 es el entero 8 (en base 10) y 0277 es el entero 191 (en base 10). P
ara
codificar un nmero en base 16 debes usar el par de caracteres 0x seguido del nmero
en cuestin. El literal 0xff, por ejemplo, codifica el valor decimal 255.
Pero an hay una forma ms de codificar un entero, una que puede resultar extraa
al
principio: mediante un carcter entre comillas simples, que representa a su valor
ASCII.
El valor ASCII de la letra a minscula, por ejemplo, es 97, as que el literal
es el
valor 97. Hasta tal punto es as que podemos escribir expresiones como
1
, que es el
valor 98 o, lo que es lo mismo,
.
Se puede utilizar cualquiera de las secuencias de escape que podemos usar co
n las
cadenas. El literal
, por ejemplo, es el valor 10 (que es el cdigo ASCII del
salto de
lnea).
Ni ord ni chr
En C no son necesarias las funciones ord o chr de Python, que convertan ca
racteres
en enteros y enteros en caracteres. Como en C los caracteres son enteros,
no resulta
necesario efectuar conversin alguna.

Los nmeros en coma flotante siguen la misma sintaxis que los flotantes de Python.
Un
nmero flotante debe presentar parte decimal y/o exponente. Por ejemplo, 20.0 es u
n
flotante porque tiene parte decimal (aunque sea nula) y 2e1 tambin lo es, pero po
rque

tiene exponente (es decir, tiene una letra e seguida de un entero). Ambos repres
entan al
nmero real 20.0. (Recuerda que 2e1 es 2 101 .) Es posible combinar en un nmero par
te
decimal con exponente: 2.0e1 es un nmero en coma flotante vlido.

As como en Python puedes optar por encerrar una cadena entre comillas simples o
dobles, en C slo puedes encerrarla entre comillas dobles. Dentro de las cadenas p
uedes
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

24
24

utilizar secuencias de escape para representar caracteres especiales. Afortunada


mente,
las secuencias de escape son las mismas que estudiamos en Python. Por ejemplo, e
l salto
de lnea es
y la comilla doble es .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14 Traduce a cadenas C las siguientes cadenas Python:
1.
2.
3.
4.
5.
6.
7.
...............................................................................
...
Te relacionamos las secuencias de escape que puedes necesitar ms frecuent
emente:
Secuencia

Valor
(alerta): produce un aviso audibl

e o visible.
(backspace, espacio atrs): el curs
or retrocede un espacio a la izquierda.
(form feed, alimentacin de pgina):
pasa a una nueva pgina.
(newline, nueva lnea): el cursor p
asa a la primera posicin de la
siguiente lnea.
(carriage return, retorno de carr
o): el cursor pasa a la primera posicin
de la lnea actual.
(tabulador): desplaza el cursor a
la siguiente marca de tabulacin.
nmero_octal
o IsoLatin) es el nmero octal

muestra la barra invertida.


muestra la comilla doble.
muestra el carcter cuyo cdigo ASCII (
indicado. El nmero octal puede ten

er uno, dos o tres dgitos octales.


Por ejemplo

equivale a

, pues el valor ASCII del carcter


nmero_hexadecimal
ase 16 y puede tener uno

cero es 48, que en octal es 60.


dem, pero el nmero est codificado en b
o dos dgitos hexadecimales. Por ej

emplo,

tambin equivale a
, pues 48 en decimal es 30 en

hexadecimal.
muestra el interrogante.

Es pronto para aprender a utilizar variables de tipo cadena. Postergamos este


asunto
hasta el apartado 2.2.

En Python tenemos dos tipos numricos escalares: enteros y flotantes8 . En C hay u


na
gran variedad de tipos escalares en funcin del nmero de cifras o de la precisin con
8
Bueno, esos son los que hemos estudiado. Python tiene, adems, enteros largos
. Otro tipo numrico no
secuencial de Python es el complejo.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
25
25
ramacin con C - UJI

Introduccin a la prog

c
UJI

la que deseamos trabajar, as que nos permite tomar decisiones acerca del compromi
so
entre rango/precisin y ocupacin de memoria: a menor rango/precisin, menor ocupacin
de memoria.
No obstante, nosotros limitaremos nuestro estudio a cinco tipos de datos esc
alares: int,
unsigned int, float, char y unsigned char. Puedes consultar el resto de tipos es
calares
en el apndice A. Encontrars una variedad enorme: enteros con diferente nmero de
bits, con y sin signo, flotantes de precisin normal y grande, booleanos, etc. Esa
enorme
variedad es uno de los puntos fuertes de C, pues permite ajustar el consumo de m
emoria
a las necesidades de cada programa. En aras de la simplicidad expositiva, no obs
tante,
no la consideraremos en el texto.
int
El tipo de datos int se usa normalmente para representar nmeros enteros. La espec
ificacin de C no define el rango de valores que podemos representar con una variab
le
de tipo int, es decir, no define el nmero de bits que ocupa una variable de tipo
int. No
obstante, lo ms frecuente es que ocupe 32 bits. Nosotros asumiremos en este texto
que
el tamao de un entero es de 32 bits, es decir, 4 bytes.
Como los enteros se codifican en complemento a 2, el rango de valores que po
demos
representar es [2147483648, 2147483647], es decir, [231 , 231 1]. Este rango es suf
iciente
ara las a licaciones que resentaremos. Si resulta insuficiente o excesivo ara
alguno
de tus rogramas, consulta el catlogo de ti os que resentamos en el a ndice A.
En C, tradicionalmente, los valores enteros se han utilizado ara codificar
valores
booleanos. El valor 0 re resenta el valor lgico falso y cualquier otro valor re res
enta
cierto. En la ltima revisin de C se ha introducido un ti o booleano, aunque no lo
usaremos en este texto orque, de momento, no es frecuente encontrar rogramas q
ue lo
usen.
unsigned int
Para qu des erdiciar el bit ms significativo en una variable entera de 32 bits que
nunca almacenar valores negativos? C te ermite definir variables de ti o entero s
in
signo. El ti o tiene un nombre com uesto or dos alabras: unsigned int (aunque la
alabra unsigned, sin ms, es sinnimo de unsigned int).
Gracias al a rovechamiento del bit extra es osible aumentar el rango de val
ores
ositivos re resentables, que asa a ser [0, 232 1], o sea, [0, 4294967295].
float
El ti o de datos float re resenta nmeros en coma flotante de 32 bits. La codifica
cin
de coma flotante ermite definir valores con decimales. El mximo valor que uedes

almacenar en una variable de ti o float es 3.40282347 1038 . Recuerda que el fac


tor
exponencial se codifica en los programas C con la letra e (o E) seguida del exponent
e.
Ese valor, pues, se codifica as en un programa C: 3.40282347e38. El nmero no nulo
ms
pequeo (en valor absoluto) que puedes almacenar en una variable float es 1.175494
35
1038 (o sea, el literal flotante 1.17549435e 38). Da la im resin, ues, de que od
emos
re resentar nmeros con 8 decimales. No es as: la recisin no es la misma ara todos
los valores: es tanto mayor cuanto ms rximo a cero es el valor.

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

26
26

char
El ti o char, aunque tenga un nombre que arezca sugerir el trmino carcter (que
en ingls es character) designa en realidad a una variante de enteros: el conjunto d
e
nmeros que odemos re resentar (en com lemento a 2) con un solo byte (8 bits). El
rango de valores que uede tomar una variable de ti o char es muy limitado: [128,
127].
Es frecuente usar variables de ti o char ara almacenar caracteres (de ah su
nombre)
codificados en ASCII o alguna de sus extensiones (como IsoLatin1). Si una variab
le a es
de ti o char, la asignacin a
es absolutamente equivalente a la asignacin a
48,
ues el valor ASCII del dgito 0 es 48.
unsigned char
Y del mismo modo que haba una versin ara enteros de 32 bits sin signo, hay una
versin de char sin signo: unsigned char. Con un unsigned char se uede re resenta
r
cualquier entero en el rango [0, 255].

Recuerda que en C toda variable usada en un rograma debe declararse antes de se


r
usada. Declarar la variable consiste en darle un nombre (identificador) y asigna
rle un
ti o.

Las reglas ara construir identificadores vlidos son las mismas que sigue Python:
un
identificador es una sucesin de letras (del alfabeto ingls), dgitos y/o el carcter d
e
subrayado ( ) cuyo rimer carcter no es un dgito. Y al igual que en Python, no ue
des
usar una alabra reservada como identificador. He aqu la relacin de alabras reser
vadas
del lenguaje C: auto, break, case, char, const, continue, default, do, double, e
lse, enum,
extern, float, for, goto, if, int, long, register, return, short, signed, sizeof
, static, struct,
switch, ty edef, union, unsigned, void, volatile y while.

Una variable se declara recediendo su identificador con el ti o de datos de la


variable.
Este fragmento, or ejem lo, declara una variable de ti o entero, otra de ti o e
ntero de
un byte (o carcter) y otra de ti o flotante:
int a
char b
float c
Se uede declarar una serie de variables del mismo ti o en una sola sentenci

a de
declaracin se arando sus identificadores con comas. Este fragmento, or ejem lo,
declara
tres variables de ti o entero y otras dos de ti o flotante.
int x y z
float u v
En
se declaran tres variables de ti o int, a, b
y i, y una de ti o float,
s.
Una variable declarada como de ti o entero slo uede almacenar valores de ti
o
entero. Una vez se ha declarado una variable, es im osible cambiar su ti o, ni s
iquie
ra volviendo a declararla. Este rograma, or ejem lo, es incorrecto or el inte
nto de
redeclarar el ti o de la variable a:
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
UJI
c

27
27

C, ocu acin de los datos, com lemento a 2 y ortabilidad


Los nmeros enteros con signo se codifican en com lemento a 2. Con n bits
uedes
re resentar valores enteros en el rango [2n1 , 2n1 1]. Los valores ositivos
se re re
sentan en binario, sin ms. Los valores negativos se codifican re resentand
o en binario
su valor absoluto, invirtiendo todos sus bits y aadiendo 1 al resultado. S
u n que
trabajamos con datos de ti o char (8 bits). El valor 28 se re resenta en
binario as
. El valor 28 se obtiene tomando la re resentacin binaria de 28,
invirtiendo
sus bits (
), y aadiendo uno. El resultado es
.
Una ventaja de la notacin en com lemento a 2 es que sim lifica el diseo
de circuitos
ara la realizacin de clculos aritmticos. Por ejem lo, la resta es una sim l
e suma.
Si deseas restar a 30 el valor 28, basta con sumar 30 y 28 con la misma
circuitera
electrnica utilizada ara efectuar sumas convencionales:

El com lemento a 2 uede gastarte malas asadas si no eres consciente


de cmo fun
ciona. Por ejem lo, sumar dos nmeros ositivos uede roducir un resultado
negativo!
Si trabajas con 8 bits y sumas 127 y 1, obtienes el valor 128:

ro

Este fenmeno se conoce como desbordamiento. C no aborta la ejecucin del

grama cuando se roduce un desbordamiento: da or bueno el resultado y si


gue. Mala
cosa: uede que demos or bueno un rograma que est roduciendo resultados
err
neos.
El estndar de C no define de modo claro la ocu acin de cada uno de sus
ti os
de datos lo cual, unido a fenmenos de desbordamiento, dificulta notablemen
te la or
tabilidad de los rogramas. En la mayora de los com iladores y ordenadores
actuales,
una variable de ti o int ocu a 32 bits. Sin embargo, en ordenadores ms ant
iguos era
frecuente que ocu ara slo 16. Un rograma que su onga una re resentacin may
or que
la real uede resultar en la comisin de errores en tiem o de ejecucin. Por
ejem lo, si
una variable a de ti o int ocu a 32 bits y vale 32767, ejecutar la asigna
cin a a 1
almacenar en a el valor 32768; ero si el ti o int ocu a 16 bits, se almac
ena el valor
32768.
Puede que demos or bueno un rograma al com ilarlo y ejecutarlo en u
na lataforma

determinada, ero que falle estre itosamente cuando lo com ilamos y ejecu
tamos en una
lataforma diferente. O, eor an, uede que el error ase inadvertido dura
nte mucho
tiem o: el rograma no abortar la ejecucin y roducir resultados incorrectos
que
odemos no detectar. Es un roblema muy grave.
Los roblemas relacionados con la garanta de oder ejecutar un mismo
rograma en
diferentes lataformas se conocen como roblemas de ortabilidad. Pese a
los muchos
roblemas de ortabilidad de C, es el lenguaje de rogramacin en el que se
ha escrito
buena arte de los rogramas que hoy ejecutamos en una gran variedad de
lataformas.

include
int main void
int a
float a

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con C ISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

28
28

char, unsigned char, ASCII e IsoLatin1


La tabla ASCII tiene caracteres asociados a valores com rendidos entre 0
y 127, as
que todo carcter ASCII uede almacenarse en una variable de ti o char. Per
o, en
realidad, nosotros no usamos la tabla ASCII ura, sino una extensin suya: Is
oLatin1
(tambin conocida or ISO88591 o ISO885915, si incluye el smbolo del eur
o). La
tabla IsoLatin1 nos ermite utilizar caracteres acentuados y otros smbolos
es eciales
ro ios de las lenguas romnicas occidentales. Qu ocurre si asignamos a una v
ariable
de ti o char el carcter
? El cdigo IsoLatin1 de es 225, que es un valo
r numrico
mayor que 127, el mximo valor entero que odemos almacenar en una variable
de ti o
char. Mmmm. S, ero 225 se codifica en binario como esta secuencia de cero
s y unos:
. Si inter retamos dicha secuencia en com lemento a dos, tenem
os el valor
31, y ese es, recisamente, el valor que resulta almacenado. Podemos evita
r este
inconveniente usando el ti o unsigned char, ues ermite almacenar valore
s entre 0 y
255.

a 2
return 0
Al com ilarlo obtenemos este mensaje de error:

El com ilador nos indica que la variable a resenta un conflicto de ti os en


la lnea
6 y que ya haba sido declarada reviamente en la lnea 5.

Debes tener resente que el valor inicial de una variable declarada est indefinid
o. Jams
debes acceder al contenido de una variable que no haya sido reviamente iniciali
zada.
Si lo haces, el com ilador no detectar error alguno, ero tu rograma resentar un
com ortamiento indeterminado: a veces funcionar bien, y a veces mal, lo cual es
eor que
un funcionamiento siem re incorrecto, ues odras llegar a dar or bueno un rogr
ama
mal escrito. En esto C se diferencia de Python: Python abortaba la ejecucin de un
rograma cuando se intentaba usar una variable no inicializada; C no aborta la e
jecucin,
ero resenta un com ortamiento indeterminado.
Puedes inicializar las variables en el momento de su declaracin. Para ello, b

asta con
aadir el o erador de asignacin y un valor a continuacin de la variable en cuestin.
Mira este ejem lo:
include
int main void
int a 2
float b 2.0 c d

1.0 e

return 0
En l, las variables a, b y d se inicializan en la declaracin y las variables c y e
no
tienen valor definido al ser declaradas.
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C  UJI
UJI
c

29
29

Recuerda que acceder a variables no inicializadas es una fuente de graves err


ores.
Acostmbrate a inicializar las variables tan ronto uedas.

La funcin de im resin de informacin en antalla utilizada habitualmente es rintf .


Es
una funcin dis onible al incluir stdio h en el rograma. El uso de rintf es lige
ramente
ms com licado que el de la sentencia rint de Python, aunque no te resultar difcil
si
ya has a rendido a utilizar el o erador de formato en Python ( ).
En su forma de uso ms sim le, rintf ermite mostrar una cadena or antalla.
include
int main void
rintf
rintf
return 0
La funcin rintf no aade un salto de lnea automticamente, como s haca rint en
Python. En el rograma anterior, ambas cadenas se muestran una a continuacin de o
tra.
Si deseas que haya un salto de lnea, debers escribir al final de la cadena.
include
int main void
rintf
rintf
return 0

r
intf
Marcas de formato ara nmeros
Para mostrar nmeros enteros o flotantes has de usar necesariamente cadenas con fo
rmato.
Afortunadamente, las marcas que a rendiste al estudiar Python se utilizan en C.
Eso s,
hay algunas que no te hemos resentado an y que tambin se recogen en esta tabla:
Ti o
int
unsigned int
float
char
unsigned char

Marca

Por ejem lo, si a es una variable de ti o int con valor 5, b es una variable de
ti o float
con valor 1.0, y c es una variable de ti o char con valor 100, esta llamada a la
funcin
rintf :

rintf
a b c
muestra or

antalla esto:

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C  UJI
UJI
c

30
30

Ojo! a la cadena de formato le sigue una coma, y no un o erador de formato com


o
suceda en Python. Cada variable se se ara de las otras con una coma.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15 Que mostrar por pantalla esta llamada a printf suponiendo que a es de tipo
entero y vale 10?
printf
a 1 2 2
...............................................................................
...
Las marcas de formato para enteros aceptan modificadores, es decir, puedes a
lterar la
representacin introduciendo ciertos caracteres entre el smbolo de porcentaje y el
resto
de la marca. Aqu tienes los principales:
Un nmero positivo: reserva un nmero de espacios determinado (el que se i
ndique)
para representar el valor y muestra el entero alineado a la derecha.
Ejemplo: la sentencia
printf

10

muestra en pantalla:

Un nmero negativo: reserva tantos espacios como indique el valor absolu


to del
nmero para representar el entero y muestra el valor alineado a la izqui
erda.
Ejemplo: la sentencia
printf

10

muestra en pantalla:

Un nmero que empieza por cero: reserva tantos espacios como indique el
nmero
para representar el entero y muestra el valor alineado a la derecha. L
os espacios
que no ocupa el entero se rellenan con ceros.
Ejemplo: la sentencia
printf

10

muestra en pantalla:

El signo : muestra explcitamente el signo (positivo o negativo) del ent


ero.
Ejemplo: la sentencia
printf
muestra en pantalla:

10

Hay dos notaciones alternativas para la representacin de flotantes que podemo


s
seleccionar mediante la marca de formato adecuada:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
31
31
ramacin con C - UJI

Introduccin a la prog

c
UJI

Tipo
float
float

Notacin
Convencional
Cientfica

Marca

La forma convencional muestra los nmeros con una parte entera y una decimal separ
adas
por un punto. La notacin cientfica representa al nmero como una cantidad con una
sola cifra entera y una parte decimal, pero seguida de la letra e y un valor enter
o.
Por ejemplo, en notacin cientfica, el nmero 10.1 se representa con 1.010000e 01 y s
e
interpreta as: 1.01 101 .
Tambin puedes usar modificadores para controlar la representacin en pantalla d
e
los flotantes. Los modificadores que hemos presentado para los enteros son vlidos
aqu.
Tienes, adems, la posibilidad de fijar la precisin:
Un punto seguido de un nmero: indica cuntos decimales se mostrarn.
Ejemplo: la sentencia
printf

10.1

muestra en pantalla:

Marcas de formato para texto


Y an nos queda presentar las marcas de formato para texto. C distingue entre cara
cteres
y cadenas:
Tipo
carcter
cadena

Marca

Atencin! La marca
muestra como carcter un nmero entero. Naturalmente, el carcter
que se muestra es el que corresponde al valor entero segn la tabla ASCII (o, en t
u
ordenador, IsoLatin1 si el nmero es mayor que 127). Por ejemplo, la sentencia
printf
97
muestra en pantalla:

Recuerda que el valor 97 tambin puede representarse con el literal


, as que esta
otra sentencia
printf
tambin muestra en pantalla esto:

An no sabemos almacenar cadenas en variables, as que poca aplicacin podemos


encontrar de momento a la marca . He aqu, de todos modos, un ejemplo trivial de u
so:

printf
En pantalla se muestra esto:
32
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
Introduccin a la programacin con C - UJI
c
UJI

32

Tambin puedes usar nmeros positivos y negativos como modificadores de estas


marcas. Su efecto es reservar los espacios que indiques y alinear a derecha o iz
quierda.
Aqu tienes un programa de ejemplo en el que se utilizan diferentes marcas de f
ormato
con y sin modificadores.
include
int main void
char c
int i 1000000
float f 2e1
printf
c c
printf
printf

i i i
f

f f
return 0
El resultado de ejecutar el programa es la impresin por pantalla del siguiente te
xto:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16 Qu muestra por pantalla cada uno de estos programas?
a)
include
int main void
char i
for i
printf
printf
return 0

i
i

b)
include
int main void
char i
for i 65 i
printf
printf
return 0
c)
include

90 i
i

Introduccin a la programacin con C


33
33
c
UJI
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la progra
macin con C - UJI

int main void


int i
for i
printf
printf
return 0

i
i

d)
include
int main void
int i
for i
printf
printf
return 0

i
i i

i
int i

e)
include
int main void
char i
for i
printf
printf
return 0

Ojo: la

es minscula.

17 Disea un programa que muestre la tabla ASCII desde su elemento de cdigo


numrico 32 hasta el de cdigo numrico 126. En la tabla se mostrarn los cdigos ASCII,
adems de las respectivas representaciones como caracteres de sus elementos. Aqu ti
enes
las primeras y ltimas lneas de la tabla que debes mostrar (debes hacer que tu prog
rama
muestre la informacin exactamente como se muestra aqu):

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

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
c
UJI
Introduccin a la programacin con C - UJI

3434

Hay un rico juego de marcas de formato y las recogemos en el apndice A. Conslt


alo
si usas tipos diferentes de los que presentamos en el texto o si quieres mostrar
valores
enteros en base 8 o 16. En cualquier caso, es probable que necesites conocer una
marca
especial, , que sirve para mostrar el smbolo de porcentaje. Por ejemplo, la sente
ncia
printf
100
muestra en pantalla:

Antes de presentar con cierto detalle la entrada de datos por teclado mediante s
canf , nos
conviene detenernos brevemente para estudiar algunas cuestiones relativas a las
variables
y la memoria que ocupan.
Recuerda que la memoria es una sucesin de celdas numeradas y que una direccin
de memoria no es ms que un nmero entero. La declaracin de una variable supone la
reserva de una zona de memoria lo suficientemente grande para albergar su conten
ido.
Cuando declaramos una variable de tipo int, por ejemplo, se reservan 4 bytes de
memoria
en los que se almacenar (codificado en complemento a 2) el valor de dicha variabl
e.
Modificar el valor de la variable mediante una asignacin supone modificar el patrn
de
32 bits (4 bytes) que hay en esa zona de memoria.
Este programa, por ejemplo,
include
int main void
int a b
a
b

0
a

return 0
reserva 8 bytes para albergar dos valores enteros.9 Imagina que a ocupa los byte
s 1000
1003 y b ocupa los bytes 10041007. Podemos representar la memoria as:
996:

01010010

10101000

01110011

1000:

01011010

00111101

00111010

1004:

10111011

10010110

01010010

1008:

11010111

01000110

11110010

11110010
11010111

01010011

01011101

Observa que, inicialmente, cuando se reserva la memoria, sta contiene un patrn


de
bits arbitrario. La sentencia a 0 se interpreta como almacena el valor 0 en la di
reccin
de memoria de a, es decir, almacena el valor 0 en la direccin de memoria 100010 .
Este es el resultado de ejecutar esa sentencia:
9
En el apartado 3.5.2 veremos que la reserva se produce en una zona de memo
ria especial llamada pila.
No conviene que nos detengamos ahora a considerar los matices que ello introduce
en el discurso.
10
En realidad, en la zona de memoria 10001003, pues se modifica el contenido
de 4 bytes. En aras de la
brevedad, nos referiremos a los 4 bytes slo con la direccin del primero de ellos.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

35
35

11

11110010

00

00000000

10

01010011

10

01011101

996:

01010010

10101000

011100

1000:

00000000

00000000

000000

1004:

10111011

10010110

010100

1008:

11010111

01000110

111100

La asignacin b a 8 se interpreta como calcula el valor que resulta de sumar 8 al


contenido de la direccin de memoria 1000 y deja el resultado en la direccin de mem
oria
1004.
11

11110010

00

00000000

00

00001000

10

01011101

996:

01010010

10101000

011100

1000:

00000000

00000000

000000

1004:

00000000

00000000

000000

1008:

11010111

01000110

111100

Hemos supuesto que a est en la direccin 1000 y b en la 1004, pero podemos saber
en qu direcciones de memoria se almacenan realmente a y b? S: el operador permite
conocer la direccin de memoria en la que se almacena una variable:
include
int main void
int a b
a
b

0
a

printf

unsigned int

printf

unsigned int

a
b
return 0
Observa que usamos la marca de formato
para mostrar el valor de la dire
ccin de
memoria, pues debe mostrarse como entero sin signo. La conversin a tipo unsigned
int
evita molestos mensajes de aviso al compilar.11
Al ejecutar el programa tenemos en pantalla el siguiente texto (puede que si
ejecutas
t mismo el programa obtengas un resultado diferente):

O sea, que en realidad este otro grfico representa mejor la disposicin de las
variables en memoria:

3221222572:

01010010

10101000

0111

3221222576:
b
3221222580:
00000000
a
3221222584:
01011101

00000000

00000000

0000

00000000

00000000

0000

11010111

01000110

1111

0011

11110010

0000

00001000

0000
0010
11

Hay una marca especial, , que muestra directamente la direccin de memoria si


n necesidad de efectuar
la conversin a unsigned int, pero lo hace usando notacin hexadecimal.
Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
6

363
c

Introduccin a la programacin con C - UJI


UJI

Normalmente no necesitamos saber en qu direccin de memoria se almacena una


variable, as que no recurriremos a representaciones grficas tan detalladas como la
s que
hemos presentado. Usualmente nos conformaremos con representar las variables esc
alares
mediante cajas y representaremos su valor de una forma ms cmodamente legible que
como una secuencia de bits. La representacin anterior se simplificar, pues, as:
a

Las direcciones de memoria de las variables se representarn con flechas que apunt
an a
sus correspondientes cajas:
&a
a
0
&b
b
8
Ahora que hemos averiguado nuevas cosas acerca de las variables, vale la pen
a que
reflexionemos brevemente sobre el significado de los identificadores de variable
s all
donde aparecen. Considera este sencillo programa:
include
int main void
int a b
a 0
b a
scanf
a a

b
b

return 0
Cmo se interpreta la sentencia de asignacin a 0? Se interpreta como almacena el
valor 0 en la direccin de memoria de a. Y b a?, cmo se interpreta? Como almacena
una copia del contenido de a en la direccin de memoria de b. Fjate bien, el identif
icador
a recibe interpretaciones diferentes segn aparezca a la izquierda o a la derecha
de una
asignacin:
a la izquierda del igual, significa la direccin de a,
y a la derecha, es decir, en una expresin, significa el contenido de a.
La funcin scanf necesita una direccin de memoria para saber dnde debe depositar
un resultado. Como no estamos en una sentencia de asignacin, sino en una expresin,
es necesario que obtengamos explcitamente la direccin de memoria con el operador b
.
As, para leer por teclado el valor de b usamos la llamada scanf
b .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI

OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18 Interpreta el significado de la sentencia a a b.
................................................................................
..

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
37
37
ramacin con C - UJI

Introduccin a la prog

c
UJI

La funcin scanf , disponible al incluir


, permite leer datos por teclado.
La funcin
scanf se usa de un modo similar a printf : su primer argumento es una cadena con
marcas
de formato. A ste le siguen una o ms direcciones de memoria. Si deseas leer por te
clado
el valor de una variable entera a, puedes hacerlo as:
scanf
a
Observa que la variable cuyo valor se lee por teclado va obligatoriamente preced
ida por
el operador : es as como obtenemos la direccin de memoria en la que se almacena el
valor de la variable. Uno de los errores que cometers con mayor frecuencia es omi
tir el
carcter que debe preceder a todas las variables escalares en scanf .
Recuerda: la funcin scanf recibe estos datos:
Una cadena cuya marca de formato indica de qu tipo es el valor que vamo
s a leer
por teclado:
Tipo
int
unsigned int
float
char como entero
char como carcter
unsigned char como entero
unsigned char como carcter

Marca

La direccin de memoria que corresponde al lugar en el que se depositar e


l valor
ledo. Debemos proporcionar una direccin de memoria por cada marca de for
mato
indicada en el primer argumento.
Observa que hay dos formas de leer un dato de tipo char o unsigned char: como en
tero
(de un byte con o sin signo, respectivamente) o como carcter. En el segundo caso,
se
espera que el usuario teclee un solo carcter y se almacenar en la variable su valo
r
numrico segn la tabla ASCII o su extensin IsoLatin.
Una advertencia: la lectura de teclado en C presenta numerosas dificultades
prcticas.
Es muy recomendable que leas el apndice B antes de seguir estudiando y absolutame
nte
necesario que lo leas antes de empezar a practicar con el ordenador. Si no lo ha
ces,
muchos de tus programas presentarn un comportamiento muy extrao y no entenders
por qu. T mismo.

Muchos de los smbolos que representan a los operadores de Python que ya conoces s
on
los mismos en C. Los presentamos ahora agrupados por familias. (Consulta los niv
eles
de precedencia y asociatividad en la tabla de la pgina 44.) Presta especial atenc

in a
los operadores que no conoces por el lenguaje de programacin Python, como son los
operadores de bits, el operador condicional o los de incremento/decremento.
Operadores aritmticos Suma ( ), resta ( ), producto ( ), divisin ( ), mdulo o resto
de
la divisin ( ), identidad ( unario), cambio de signo ( unario).
No hay operador de exponenciacin.12
12
Pero hay una funcin de la biblioteca matemtica que permite calcular la pot
encia de un nmero: pow.
38
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

38

Errores frecuentes en el uso de scanf


Es responsabilidad del programador pasar correctamente los datos a scanf
. Un error
que puede tener graves consecuencias consiste en pasar incorrectamente la
direccin
de memoria en la que dejar el valor ledo. Este programa, por ejemplo, es er
rneo:
scanf

La funcin scanf no est recibiendo la direccin de memoria en la que reside a, s


ino el
valor almacenado en a. Si scanf interpreta dicho valor como una direccin d
e memoria
(cosa que hace), guardar en ella el nmero que lea de teclado. Y el compilado
r no
necesariamente detectar el error! El resultado es catastrfico.
Otro error tpico al usar scanf consiste en confundir el tipo de una va
riable y/o la
marca de formato que le corresponde. Por ejemplo, imagina que c es una va
riable de
tipo char. Este intento de lectura de su valor por teclado es errneo:
scanf

A scanf le estamos pasando la direccin de memoria de la variable c. Hasta


ah, bien.
Pero c slo ocupa un byte y a scanf le estamos diciendo que rellene 4 bytes c
on
un nmero entero a partir de esa direccin de memoria. Otro error de consecue
ncias
gravsimas. La marca de formato adecuada para leer un nmero de tipo char hub
iera
sido
.
scanf

La divisin de dos nmeros enteros proporciona un resultado de tipo entero


(como
ocurra en Python).
Los operadores aritmticos slo funcionan con datos numricos13 . No es posib
le,
por ejemplo, concatenar cadenas con el operador (cosa que s podamos hacer
en
Python).
La dualidad carcter-entero del tipo char hace que puedas utilizar la sum
a o la
resta (o cualquier otro operador aritmtico) con variables o valores de t
ipo char.
Por ejemplo

1 es una expresin vlida y su valor es

(o, equivale

ntemente,
el valor 98, ya que
equivale a 97). (Recuerda, no obstante, que u
n carcter no
es una cadena en C, as que
1 no es
.)
Operadores lgicos Negacin o no-lgica ( ), y-lgica o conjuncin (
-lgica o

) y o

disyuncin ( ).
Los smbolos son diferentes de los que aprendimos en Python. La negacin er
a all
not, la conjuncin era and y la disyuncin or.
C sigue el convenio de que 0 significa falso y cualquier otro valor sig
nifica cierto.
As pues, cualquier valor entero puede interpretarse como un valor lgico,
igual que
en Python.
Operadores de comparacin Igual que ( ), distinto de ( ), menor que ( ), mayor que
( ), menor o igual que ( ), mayor o igual que ( ). Son viejos conocidos. Un
a diferencia con respecto a Python: slo puedes usarlos para comparar valores escal
ares.
No puedes, por ejemplo, comparar cadenas mediante estos operadores.
13
Y la suma y la resta trabajan tambin con punteros. Ya estudiaremos la denomi
nada aritmtica de
punteros ms adelante.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

39
39

-Wall
Cuando escribimos un texto en castellano podemos cometer tres tipos de err
ores:
Errores lxicos: escribimos palabras incorrectamente, con errores
ortogrficos, o
usamos palabras inexistentes. Por ejemplo:

.
Errores sintcticos: las palabras son vlidas y estn bien escritas, p
ero faltan componentes de una frase (como el sujeto o el verbo), los component
es no concuerdad
o no ocupan la posicin adecuada, etc. Por ejemplo:
,
.
Errores semnticos: la frase est correctamente construida pero care
ce de significado vlido en el lenguaje. Por ejemplo:
,
.
Lo mismo ocurre con los programas C; pueden contener errores de los tres t
ipos:
Errores lxicos: usamos carcteres no vlidos o construimos incorrecta
mente componentes elementales del programa (como identificadores, cadenas
, palabras clave,
etc.). Por ejemplo: ,
.
Errores sintcticos: construimos mal una sentencia aunque usamos p
alabras vlidas. Por ejemplo: while a 10 a
1 , b 2
3 .
Errores semnticos: la sentencia no tiene un significado vlido. Por
ejemplo, si a es
de tipo float, estas sentencias contienen errores semnticos: scanf
a
(no se puede leer a como entero), if a 1.0
a 2.0 (no compa
ra el valor
de a con 1.0: le asigna 1.0 a a).
El compilador de C no deja pasar errores lxicos o sintcticos: informa y no t
raduce el
programa a cdigo de mquina. Con los errores semnticos, sin embargo, el compil
ador es
ms indulgente: la filosofa de C es suponer que el programador puede tener un
a razn
para hacer lo que expresa en los programas, aunque no tenga un significado
correcto
a primera vista. No obstante, y para segn qu posibles errores, el compilador
puede
emitir avisos (warnings). El nivel de avisos es configurable. La opcin
(Warning
all) activa todos los avisos, lo que incluye algunos potenciales errores se
mnticos. Este
programa errneo, por ejemplo, no genera ningn aviso al compilarse sin Wall:
E
include
int main void
float a
scanf
if a 0.0
return 0

a
a 2.0

Pero si lo compilas con


:

El compilador advierte de posibles errores semnticos en las lneas 5 y 6.


Necesitars
prctica para descifrar mensajes tan parcos o extraos como los que produce
, as
que acostmbrate a compilar con
. (Y hazlo siempre que tu programa p
resente un
comportamiento anmalo en ejecucin.)

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

40
40

Lecturas mltiples con scanf


No te hemos contado todo sobre scanf . Puedes usar scanf para leer ms de un
valor.
Por ejemplo, este programa lee dos valores enteros con un solo scanf :
include
int main void
int a b
printf
scanf
printf
return 0

b
a b

Tambin podemos especificar con cierto detalle cmo esperamos que el usuario i
ntroduzca la informacin. Por ejemplo, con scanf
a b indicamos que el u
suario
debe separar los enteros con un guin; y con scanf
a b especi
ficamos que esperamos encontrar los enteros encerrados entre parntesis y separa
dos por
comas.
Lee la pgina de manual de scanf (escribiendo
en el intrp
rete de
rdenes Unix) para obtener ms informacin.

La evaluacin de una comparacin proporciona un valor entero: 0 si el resul


tado es
falso y cualquier otro si el resultado es cierto (aunque normalmente el
valor para
cierto es 1).
Operadores de bits Complemento ( ), y ( ), o ( ), o exclusiva ( ), desplazamiento a izquierdas ( ), desplazamiento a derechas ( ). Estos operadores trabaj
an
directamente con los bits que codifican un valor entero. Aunque tambin estn d
isponibles en Python, no los estudiamos entonces porque son de uso infrecuent
e en
ese lenguaje de programacin.
El operador de complemento es unario e invierte todos los bits del valo
r. Tanto
como y son operadores binarios. El operador devuelve un valor cuyo n-sim
o
bit es 1 si y slo si los dos bits de la n-sima posicin de los operandos so
n tambin
1. El operador devuelve 0 en un bit si y solo si los correspondientes b
its en los
operandos son tambin 0. El operador devuelve 1 si y slo si los correspond
ientes
bits en los operandos son diferentes. Lo entenders mejor con un ejemplo.
Imagina
que a y b son variables de tipo char que valen 6 y 3, respectivamente.
En binario, el

valor de a se codifica como


y el valor de b como
. El resultado
de a b es 7, que corresponde al valor en base diez del nmero binario
.
El resultado de a b es, en binario,
, es decir, el
valor decimal 2. El
resultado binario de a b es
, que en base 10 es 5.
Finalmente, el
resultado de a es
, es decir, 7 (recuerda que un nmero con
signo est
codificado en com lemento a 2, as que si su rimer bit es 1, el nmero es
negativo).
Los o eradores de des lazamiento des lazan los bits un nmero dado de os
iciones
a izquierda o derecha. Por ejem lo, 16 como valor de ti o char es
, as
que 16
1 es 32, que en binario es
, y 16
1 es 8, que en
binario es
.
O eradores de asignacin Asignacin ( ), asignacin con suma ( ), asignacin con res
ta ( ), asignacin con roducto ( ), asignacin con divisin ( ), asignacin con
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

41
41

O eradores de bits y rogramacin de sistemas


C resenta una enorme coleccin de o eradores, ero quiz los que te resulten
ms
llamativos sean los o eradores de bits. Difcilmente los utilizars en rogra
mas con
vencionales, ero son insustituibles en la rogramacin de sistemas. Cuando
manejes
informacin a muy bajo nivel es robable que necesites acceder a bits y mod
ificar sus
valores.
Por ejem lo, el control de ciertos uertos del ordenador asa or lee
r y asignar
valores concretos a ciertos bits de direcciones virtuales de memoria. Pue
de que oner
a 1 el bit menos significativo de determinada direccin ermita detener la
actividad de
una im resora conectada a un uerto aralelo, o que el bit ms significativ
o nos alerte
de si falta a el en la im resora.
Si deseas saber si un bit est o no activo, uedes utilizar los o erado
res y .
Para saber, or ejem lo, si el octavo bit de una variable x est activo, u
edes calcular
x
1
7 . Si el resultado es cero, el bit no est activo; en caso cont
rario, est activo.
Para fijar a 1 el valor de ese mismo bit, uedes hacer x x
1
7 .
Los o eradores de bits emulan el com ortamiento de ciertas instruccio
nes dis onibles
en los lenguajes ensambladores. La facilidad que ro orciona C ara escri
bir rogramas
de bajo nivel es grande, y or ello C se considera el lenguaje a elegir cua
ndo hemos
de escribir un controlador ara un dis ositivo o el cdigo de un sistema o
erativo.

es

mdulo ( ), asignacin con des lazamiento a izquierda (

), asignacin con d

lazamiento a derecha (
), asignacin con y ( ), asignacin con o ( ),
asignacin con o exclusiva ( ).
Puede resultarte extrao que la asignacin se considere tambin un o erador.

Que
sea un o erador ermite escribir asignaciones mlti les como sta:
a

Es un o erador asociativo or la derecha, as que las asignaciones se eje


cutan en
este orden:
a

El valor que resulta de evaluar una asignacin con es el valor asignado a


su
arte izquierda. Cuando se ejecuta b 1, el valor asignado a b es 1, as q
ue ese
valor es el que se asigna tambin a a.
La asignacin con una o eracin o hace que a la variable de la izquierda se

le asigne el resultado de o erar con o su valor con el o erando derecho.


Por
ejem lo, a
3 es equivalente a a a 3.
Este ti o de asignacin con o eracin recibe el nombre de asignacin aumentad
a.
O erador de tamao sizeof.
El o erador sizeof uede a licarse a un nombre de ti o (encerrado entre
arntesis)
o a un identificador de variable. En el rimer caso devuelve el nmero de
bytes que
ocu a en memoria una variable de ese ti o, y en el segundo, el nmero de
bytes
que ocu a esa variable. Si a es una variable de ti o char, tanto sizeof
a como
sizeof char devuelven el valor 1. Ojo: recuerda que
es literal
entero, as que
sizeof
vale 4.
O eradores de coercin o conversin de ti os (en ingls ty e casting o erator). Pue
des convertir un valor de un ti o de datos a otro que sea com atible. Para el
lo
dis ones de o eradores de la forma ti o , donde ti o es int, float, etc.
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

42
42

Por ejem lo, si deseas efectuar una divisin entre enteros que no ierda
decimales al
convertir el resultado a un flotante, uedes hacerlo como te muestra es
te rograma:
include
int main void
float x
int a 1 b
x

float b

En este ejem lo, hemos convertido el valor de b a un float antes de efe


ctuar la
divisin. Es similar a la funcin float de Python, slo que en Python se haca
la
conversin con una llamada a funcin como float b , y aqu utilizamos un o er
ador
refijo: float b. Es una notacin bastante extraa, as que es robable que t
e
confunda durante un tiem o. En la siguiente seccin abundaremos en la con
versin
de ti os en C.
O erador condicional ( ).
Este o erador no tiene correlato en Python. Hay tres o erandos: una condicin
y
dos ex resiones14 . El resultado de la o eracin es el valor de la rimera ex
resin si
la condicin es cierta y el valor de la segunda si es falsa. Por ejem lo, la
asignacin
a

10

100

200

almacena en a el valor 100 o 200, de endiendo de si x es o no es mayor


que 10.
Es equivalente a este fragmento de rograma:
if x 10
a 100
else
a 200
O eradores de incremento/decremento Preincremento ( en forma refija), ostincre

mento ( en forma ostfija), redecremento ( en forma refija), ostdecremen
to
( en forma ostfija).
Estos o eradores no tienen equivalente inmediato en Python. Los o erado
res de
incremento y decremento ueden ir delante de una variable (forma refij
a) o detrs
(forma ostfija). En ambos casos incrementan ( ) o decrementan ( ) en u
na unidad
el valor de la variable entera.

Si i vale 1, valdr 2 des us de ejecutar i o i , y valdr 0 des us de ejecuta


r
i o i . Hay una diferencia im ortante entre a licar estos o eradores
en forma
refija o sufija.
La ex resin i rimero se evala como el valor actual de i y des us
hace
que i incremente su valor en una unidad.
La ex resin i rimero incrementa el valor de i en una unidad y
des us se
evala como el valor actual (que es el que resulta de efectuar e
l incremento).
14
Lo cierto es que hay tres ex resiones, ues la com aracin no es ms que una e
x resin. Si dicha
ex resin devuelve el valor 0, se inter reta el resultado como falso; en caso contra
rio, el resultado es
cierto.
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

43
43

Si el o erador se est a licando en una ex resin, esta diferencia tiene


im ortancia.
Su ongamos que i vale 1 y que evaluamos esta asignacin:
a

La variable a acaba valiendo 1 e i acaba valiendo 2. Fjate: al ser un


ostincremen
to, rimero se devuelve el valor de i, que se asigna a a, y des us se
incrementa
i.
Al ejecutar esta otra asignacin obtenemos un resultado diferente:
a

Tanto a como i acaban valiendo 2. El o erador de reincremento rimer


o asigna
a i su valor actual incrementado en una unidad y des us devuelve ese v
alor (ya
incrementado), que es lo que finalmente estamos asignando a a.
Lo mismo ocurre con los o eradores de re y ostdecremento, ero, nat
uralmente,
decrementado el valor en una unidad en lugar de incrementarlo.
Que haya o eradores de re y ostincremento (y re y ostdecremento)
te debe
arecer una rareza excesiva y ensars que nunca necesitars hilar tan fi
no. Si es
as, te equivocas: en los

rximos ca tulos usaremos o eradores de increme

nto y
necesitaremos escoger entre

reincremento y ostincremento.

Nos dejamos en el tintero unos ocos o eradores ( , , , , , y


unario. Los resentaremos cuando convenga y se amos algo ms de C.
C
Ya debes entender de dnde viene el nombre C : es un C incrementado, o sea,
mejorado. En realidad C es mucho ms que un C con algunas mejoras: es un le
nguaje
orientado a objetos, as que facilita el diseo de rogramas siguiendo una fi
losofa
diferente de la
. Pero esa es
otra historia.

ro ia de los lenguajes im erativos y rocedurales como C

En esta tabla te relacionamos todos los o eradores (incluso los que an no te


hemos
resentado con detalle) ordenados or recedencia (de mayor a menor) y con su ar
idad
(nmero de o erandos) y asociatividad:
O erador
Aridad

Asociatividad
ostfijo

jo

izquierda

jo

derecha

izquierda

sizeof

ti o

refijo

ostfi
refi

izquierda

izquierda

izquierda

izquierda

izquierda

izquierda

izquierda

izquierda

izquierda

izquierda

derecha

izquierda

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
44
44

Introduccin a la rogramacin con C UJI


cUJI

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19 Sean a, b y c tres variables de tipo int cuyos valores actuales son 0, 1 y 2,
respectivamente. Qu valor tiene cada variable tras ejecutar esta secuencia de asignacion
es?
a
a
c
a
b
b
c
a

c
b

a b
b c
a 0
a 2
a
2
a b c

20 Qu hace este programa?


include
int main void
int a b c r
printf

scanf
a

printf

sc

anf

b
printf

r
b

scanf
a

printf

return 0
21 Haz un programa que solicite el valor de x y muestre por pantalla el resultad
o de
evaluar x 4 x 2 + 1. (Recuerda que en C no hay o erador de ex onenciacin.)
22 Disea un programa C que solicite la longitud del lado de un cuadrado y muestre
por pantalla su permetro y su rea.
23 Disea un programa C que solicite la longitud de los dos lados de un rectngulo
y muestre por pantalla su permetro y su rea.
24 Este programa C es problemtico:
include
int main void
int a b
a 2147483647
b a a
printf
a
printf
b

return 0
Al compilarlo y ejecutarlo hemos obtenido la siguiente salida por pantalla:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
45
45
ramacin con C - UJI

Introduccin a la prog

c
UJI

Qu ha ocurrido?
25 Disea un programa C que solicite el radio r de una circunferencia y muestre
por pantalla su permetro (2r) y su rea (r 2 ).
26 Si a es una variable de tipo char con el valor 127, qu vale a? Y qu vale
a? Y si a es una variable de tipo unsigned int con el valor 2147483647, qu vale a
?
Y qu vale a?
27 Qu resulta de evaluar cada una de estas dos expresiones?
a) 1

b) 1

1
1

0
0

1
1

28 Por qu si a es una variable entera a 2 proporciona el mismo resultado que


a 1? Con qu operacin de bits puedes calcular a 2? Y a 32? Y a 128?
29
Qu hace este programa?
include
int main void
unsigned char a b
printf

scanf

printf

scanf

a
b
a
b
a
printf
printf

b
a
b
a
b

return 0
(Nota: la forma en que hace lo que hace viene de un viejo truco de la pr
ogramacin
en ensamblador, donde hay ricos juegos de instrucciones para la manipulacin de da
tos
bit
. . . a. .bit.)
.....................................................................
........

El sistema de tipos escalares es ms rgido que el de Python, aunque ms rico. Cuando


se evala una expresin y el resultado se asigna a una variable, has de tener en cue
nta
el tipo de todos los operandos y tambin el de la variable en la que se almacena.
Ilustraremos el comportamiento de C con fragmentos de programa que utilizan
estas
variables:
char c
int i
float x

Introduccin a la programacin con C

46
46
c

UJI
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
ntroduccin a la programacin con C - UJI

5
3
2?
Recuerda que en Python podamos combinar operadores de comparacin para for
mar
expresiones como 5 3 2. Esa, en particular, se evala a True, pues 5 es m
ayor que 3 y
3 es mayor que 2. C tambin acepta esa expresin, pero con un significado c
ompletamente
diferente basado en la asociatividad por la izquierda del operador : en
primer lugar
evala la subexpresin 5 3, que proporciona el valor cierto; pero como cierto e
s
1 (valor por defecto) y 1 no es mayor que 2, el resultado de la evaluac
in es 0, o sea,
falso.
Ojo con la interferencia entre ambos lenguajes! Problemas como ste su
rgirn con
frecuencia cuando aprendas nuevos lenguajes: construcciones que signifi
can algo en el
lenguaje que conoces bien tienen un significado diferente en el nuevo.

Si asignas a un entero int el valor de un entero ms corto, como un char, el e


ntero
corto promociona a un entero int automticamente. Es decir, es posible efectuar es
ta
asignacin sin riesgo alguno:
i
c
Podemos igualmente asignar un entero int a un char. C se encarga de hacer la con
versin
de tipos pertinente:
c
i
Pero, cmo? En un byte (lo que ocupa un char) no caben cuatro (los que ocupa un int)
!
C toma los 8 bits menos significativos de i y los almacena en c, sin ms. La conve
rsin
funciona correctamente, es decir, preserva el valor, slo si el nmero almacenado en
i est
comprendido entre 128 y 127.
Observa este rograma:
include
int main void
int a b
char c d
a 512
b 127
c a
d b
rintf
return 0

c d

Produce esta salida or

antalla:

Por qu el rimer resultado es 0? El valor 512, almacenado en una variable de


ti o
int, se re resenta con este atrn de bits:
. Sus
8 bits menos significativos se almacenan en la variable c al ejecutar la asignac
in c a,
es decir, c almacena el atrn de bits
, que es el valor decimal 0.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30 Qu mostrar por pantalla este programa?
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
47
47
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
int main void
int a b
char c d
unsigned char e f
a 384
b 256
c a
d b
e a
f b
printf
printf

c d
e f

return 0
...............................................................................
...
Si asignamos un entero a una variable flotante, el entero promociona a su va
lor
equivalente en coma flotante. Por ejemplo, esta asignacin almacena en x el valor
2.0 (no
el entero 2).
x
2
Si asignamos un valor flotante a un entero, el flotante se convierte en su
equivalente
entero (si lo hay!). Por ejemplo, la siguiente asignacin almacena el valor 2 en i
(no el
flotante 2.0).
i
2.0
Y esta otra asignacin almacena en i el valor :
i
0.1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31 Qu valor se almacena en las variables i (de tipo int) y x (de tipo float) tras
ejecutar cada una de estas sentencias?
a) i
) x

2
2.0

4.0

c) i
g) x

2
2

4
4

b) i 1 2
d) i 2.0 4
f) x 2.0 4
h) x 1 2
...............................................................................
...
Aunque C se encarga de efectuar implcitamente muchas de las conversiones de
tipo,
puede que en ocasiones necesites indicar explcitamente una conversin de tipo. Para
ello, debes preceder el valor a convertir con el tipo de destino encerrado entre
parntesis.
As:
i
int 2.3
En este ejemplo da igual poner int que no ponerlo: C hubiera hecho la conversin i
mplcitamente. El trmino int es el operador de conversin a enteros de tipo int. Hay un
operador de conversin para cada tipo: char , unsigned int float , etc. . . Recuer
da
que el smbolo tipo es un operador unario conocido como operador de coercin o

conversin de tipos.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32 Qu valor se almacena en las variables i (de tipo int) y x (de tipo float) tras
ejecutar estas sentencias?
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
4848
Introduccin a la pro
gramacin con C - UJI
c
UJI

a) i

float 2
e) x
2.0
int 4.0
i) x
float 1
2
b) i
1
float 2
f) x
int 2.0
4
j) x 1
float 2
c) i
int
2
4
g) x
int 2.0
4
d) i
int 2
float 4
h) x 2
float 4
...............................................................................
...

Las lneas que empiezan con una palabra predecida por el carcter son especiales.
Las palabras que empiezan con se denominan directivas. El compilador no llega a
ver
nunca las lneas que empiezan con una directiva. Qu queremos decir exactamente con
que no llega a verlas? El compilador
es, en realidad, un programa que control
a varias
etapas en el proceso de traduccin de C a cdigo de mquina. De momento, nos interesa
considerar dos de ellas:
el preprocesador,
y el traductor de C a cdigo de mquina (el compilador propiamente dicho).
Preprocesador

ompilador
El preprocesador es un programa independiente, aunque es infrecuente invocarlo d
irectamente. El preprocesador del compilador
se llama
.
Las directivas son analizadas e interpretadas por el preprocesador. La direc
tiva include
seguida del nombre de un fichero (entre los caracteres y ) hace que el preproces
ador
sustituya la lnea en la que aparece por el contenido ntegro del fichero (en ingls in
clude significa incluye). El compilador, pues, no llega a ver la directiva, sino el re
sultado
de su sustitucin.
Nosotros slo estudiaremos, de momento, dos directivas:
define, que permite definir constantes,
e include, que permite incluir el contenido de un fichero y que se usa
para importar
funciones, variables, constantes, etc. de bibliotecas.

define
Una diferencia de C con respecto a Python es la posibilidad que tiene el primero
de
definir constantes. Una constante es, en principio15 , una variable cuyo valor n
o puede ser
modificado. Las constantes se definen con la directiva define. As:
define
valor

Cada lnea define slo puede contener el valor de una constante.


Por ejemplo, podemos definir los valores aproximados de y del nmero e as:
define
3.1415926535897931159979634685442
define
2.7182818284590450907955982984276
15
Lo de en rinci io est justificado. No es cierto que las constantes de C sean
variables. Lee el cuadro
titulado El re rocesador y las constantes ara saber qu son exactamente.
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
49
49

Introduccin a la rogramacin con C UJI


cUJI

Intentar asignar un valor a


o a en el rograma roduce un error que dete
cta el
com ilador16 .
Observa que no hay o erador de asignacin entre el nombre de la constante y su
valor y que la lnea no acaba con unto y coma17 . Es robable que cometas ms de un
a
vez el error de escribir el o erador de asignacin o el unto y coma.
No es obligatorio que el nombre de la constante se escriba en maysculas, ero
s
un convenio am liamente ado tado.
const
C99 ro one una forma alternativa de definir constantes mediante una nueva alab
ra
reservada: const. Puedes usar const delante del ti o de una variable inicializad
a en la
declaracin ara indicar que su valor no se modificar nunca.
include
int main void
const float
float r a

3.14

rintf
scanf
a

r
i

rintf

r
a

return 0
Pero la osibilidad de declarar constantes con const no nos libra de la dire
ctiva
define, ues no son de a licacin en todo lugar donde conviene usar una constante.
Ms adelante, al estudiar la declaracin de vectores, nos referiremos nuevamente a e
sta
cuestin.

Es frecuente definir una serie de constantes con valores consecutivos. Imagina u


na a li
cacin en la que escogemos una o cin de un men como ste:

Cuando el usuario escoge una o cin, la almacenamos en una variable (llammosla


o cion) y seleccionamos las sentencias a ejecutar con una serie de com araciones
como
las que se muestran aqu esquemticamente18 :
16
Has ledo ya el cuadro El re rocesador y las constantes?

17
A qu es eras

ara leer el cuadro El re rocesador y las constantes?

18
Ms adelante estudiaremos una estructura de seleccin que no es if y que se usa
normalmente ara
es ecificar este ti o de acciones.
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C  UJI
UJI
c

50
50

El re rocesador y las constantes


Como ya dijimos, el com ilador no lee directamente nuestros ficheros con e
xtensin .
Antes son tratados or otro rograma al que se conoce como re rocesador.
El re ro
cesador (que suele ser el rograma c , or C re rocessor) rocesa las deno
minadas
directivas (lneas que em iezan con ). Cuando el re rocesador encuentra la
directiva
define, la elimina, ero recuerda la asociacin establecida entre un ident
ificador y un
texto; cada vez que encuentra ese identificador en el rograma, lo sustitu
ye or el texto.
Un ejem lo ayudar a entender el orqu de algunos errores misteriosos de C cu
ando
se trabaja con constantes. Al com ilar este rograma:
define

3.14

int main void


int a PI
return 0
el re rocesador lo transforma en este otro rograma (sin modificar nuestr
o fichero).
Puedes com robarlo invocando directamente al re rocesador:

El resultado es esto:
int main void
int a 3.14
return 0
Como uedes ver, una vez re rocesado, no queda ninguna directiva en el rog
rama y
la a aricin del identificador ha sido sustituida or el texto . Un erro
r t ico es
confundir un define con una declaracin normal de variables y, en consecuenc
ia, oner
una asignacin entre el identificador y el valor:
define

3.14

int main void


int a PI
return 0
El rograma resultante es incorrecto. Por qu? El com ilador ve el siguiente
rograma
tras ser re rocesado:

int main void


int a
3.14
return 0
La tercera lnea del rograma resultante no sigue la sintaxis del C!

if o cion
1
Cdigo ara cargar registros
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con C ISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

51
51

else if o cion
2
Cdigo ara guardar registros
else if o cion

El cdigo resulta un tanto ilegible orque no vemos la relacin entre los valores nu
mricos
y las o ciones de men. Es ms legible recurrir a constantes:
define
1
define
2
define
3
define
4
define
5
define
6
define
7

if o cion
Cdigo ara cargar registros
else if o cion
Cdigo ara guardar registros
else if o cion
Puedes ahorrarte la retahla de defines con los denominados ti os enumerados.
Un
ti o enumerado es un conjunto de valores con nombre. Fjate en este ejem lo:
enum

Cargar 1 Guardar Anyadir Borrar Modificar Buscar Finalizar

if o cion
Cargar
Cdigo ara cargar registros
else if o cion
Guardar
Cdigo ara guardar registros
else if o cion

Anyadir

La rimera lnea define los valores Cargar, Guardar, . . . como una sucesin de
valores
correlativos. La asignacin del valor 1 al rimer elemento de la enumeracin hace qu
e la
sucesin em iece en 1. Si no la hubisemos escrito, la sucesin em ezara en 0.
Es habitual que los enum a arezcan al rinci io, tras los include y define.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33 Qu valor tiene cada identificador de este tipo enumerado?
enum
Primera
Segunda Tercera Penultima
Ultima
(No te hemos explicado qu hace la segunda asignacin. Comprueba que la explicacin que das es correcta con un programa que muestre por pantalla el valor de cada
identificador.)

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

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
5252
Introduccin a la prog
ramacin con C - UJI
c
UJI

Los tipos enumerados sirven para algo ms que asignar valores a opciones de me
n.
Es posible definir identificadores con diferentes valores para series de element
os como
los das de la semana, los meses del ao, etc.
enum
Lunes Martes Miercoles Jueves Viernes Sabado Domingo
enum
Invierno Primavera Verano Otonyo
enum
Rojo Verde Azul
i
nclude
En C, los mdulos reciben el nombre de bibliotecas (o libreras, como traduccin fonticamente similar del ingls library). La primera lnea de
es sta:
include
Con ella se indica que el programa hace uso de una biblioteca cuyas funciones, v
ariables, tipos de datos y constantes estn declaradas en el fichero
, que
es abreviatura de standard input/output (entrada/salida estndar). En particular, el progra
ma
usa las funciones printf y scanf de
. Los ficheros con
extensin
se denominan ficheros cabecera (la letra es abreviatura de header, que en ingls
significa cabecera).
A diferencia de Python, C no permite importar un subconjunto de las funcione
s proporcionadas por una biblioteca. Al hacer include de una cabecera se importan tod
as
sus funciones, tipos de datos, variables y constantes. Es como si en Python ejec
utaras la
sentencia from mdulo import .
Normalmente no basta con incluir un fichero de cabecera con include para pod
er
compilar un programa que utiliza bibliotecas. Es necesario, adems, compilar con o
pciones
especiales. Abundaremos sobre esta cuestin inmediatamente, al presentar la librera
matemtica.

Podemos trabajar con funciones matemticas incluyendo


en nuestros prog
ramas.
La tabla 1.1 relaciona algunas de las funciones que ofrece la biblioteca matemtic
a.
Todos los argumentos de las funciones de
son de tipo flotante.19
La biblioteca matemtica tambin ofrece algunas constantes matemticas predefinida
s.
Te relacionamos algunas en la tabla 1.2.
No basta con escribir include
para poder usar las funciones mate
mticas:
has de compilar con la opcin
:

Por qu? Cuando haces include, el preprocesador introduce un fragmento de texto


que dice qu funciones pasan a estar accesibles, pero ese texto no dice qu hace cad
a fun-

cin y cmo lo hace (con qu instrucciones concretas). Si compilas sin


lador
se quejar:

, el compi

Lo cierto es que son de tipo double (vase el apndice A), pero no hay problema
si las usas con valores
19
y variables de tipo float, ya que hay conversin automtica de tipos.

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

53
53

Funcin C
sqrt x
sin x
cos x
tan x
asin x
acos x
atan x
exp x
exp10 x
log x
log10 x
log2 x
pow x y
fabs x
round x

Funcin matemtica
raz cuadrada de x
seno de x
coseno de x
tangente de x
arcoseno de x
arcocoseno de x
arcotangente de x
el nmero e elevado a x
10 elevado a x
logaritmo en base e de x
logaritmo en base 10 de x
logaritmo en base 2 de x
x elevado a y
valor absoluto de x
redondeo al entero ms prximo a

ceil x
floor x

redondeo superior de x
redondeo inferior de x

Tabla 1.1: Algunas funciones matemticas disponibles en la biblioteca


.
Constante

Valor
una aproximacin

del nmer

una

aproximacin

del nmer

una
una
una

a roximacin
a roximacin
a roximacin

una
una

a roximacin
a roximacin

de /2
de /4
de 1/

de 2
de log2

una

a roximacin

de log1

o e
o

e
0 e
Tabla 1.2: Algunas constantes dis onibles en la bibliotec
a

El mensaje advierte de que hay una referencia indefinida a sqrt. En realidad n


o
se est quejando el com ilador, sino otro rograma del que an no te hemos dicho
nada: el enlazador (en ingls, linker). El enlazador es un rograma que detecta en u
n
rograma las llamadas a funcin no definidas en un rograma C y localiza la defini
cin
de las funciones (ya com iladas) en bibliotecas. El fichero
que incl
umos con
define contiene la cabecera de las funciones matemticas, ero no su cuer o. El
cuer
o de dichas funciones, ya com ilado (es decir, en cdigo de mquina), reside en otr
o
fichero:
. Para qu vale el fichero
si no tiene el cuer
o de
las funciones? Para que el com ilador com ruebe que estamos usando correctamente
las
funciones (que suministramos el nmero de argumentos adecuado, que su ti o es el q

ue
debe ser, etc.). Una vez que se com rueba que el rograma es correcto, se roced
e a ge
nerar el cdigo de mquina, y ah es necesario egar (enlazar) el cdigo de mquina
de las funciones matemticas que hemos utilizado. El cuer o ya com ilado de sqrt,
or
ejem lo, se encuentra en
(
es abreviatura de math libra
ry). El
enlazador es el rograma que enlaza el cdigo de mquina de nuestro rograma con el
cdigo de mquina de las bibliotecas que usamos. Con la o cin
le indicamos al
en
lazador que debe resolver las referencias indefinidas a funciones matemticas util
izando
.
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la rogramacin con C
Introduccin a la rogramacin con C  UJI
c
UJI

54
54

La o cin

evita tener que escribir


al final. Estas dos invoca
ciones del com ilador son equivalentes:

El

roceso com leto de com ilacin cuando enlazamos con


uede

re resentarse grficamente as:


Pre rocesador
dor

Com ila

Enlazador

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34 Disea un programa C que solicite la longitud delos tres lados de un tringulo
(a, b y c) y muestre por pantalla su permetro y su rea ( s(s a)(s b)(s c), donde
s = (a + b + c)/2.).
Com ila y ejecuta el rograma.
35 Disea un programa C que solicite el radio r de una circunferencia y muestre
por pantalla su permetro (2r) y su rea (r 2 ). Utiliza la a roximacin a redefinida
en la biblioteca matemtica.
Com ila y ejecuta el rograma.
...............................................................................
...

Las estructuras de control de C son arecidas a las de Python. Bueno, hay alguna
ms
y todas siguen unas reglas sintcticas diferentes. Em ecemos estudiando las estruc
turas
de control condicionales.

La sentencia de seleccin if
La estructura de control condicional fundamental es el if. En C se escribe as:
if condicin
sentencias
Los arntesis que encierran a la condicin son obligatorios. Como en Python no lo s
on,
es fcil que te equivoques or no onerlos. Si el bloque de sentencias consta de u
na sola
sentencia, no es necesario encerrarla entre llaves:
if condicin
sentencia

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con ISBN:
C
9788469301432
55
55
ramacin con C  UJI

Introduccin a la rog

c
UJI

La sentencia de seleccin if else


Hay una forma if else, como en Python:
if condicin
sentencias si
else
sentencias no
Si uno de los bloques slo tiene una sentencia, generalmente
as
llaves:
if condicin
sentencia si
else
sentencias no

uedes eliminar l

if condicin
sentencias si
else
sentencia no
if condicin
sentencia si
else
sentencia no
Ojo: la indentacin no significa nada ara el com ilador. La onemos nicamente
ara facilitar la lectura. Pero si la indentacin no significa nada nos enfrentamo
s a un
roblema de ambigedad con los if anidados:
if
condicin
if otra condicin
sentencias si
else
??????
sentencias no
A cul de los dos if ertenece el else? Har el com ilador de C una inter retacin
como la que sugiere la indentacin en el ltimo fragmento o como la que sugiere este
otro?:
if condicin
if otra condicin
sentencias si
else

???
???
sentencias no

C rom e la ambigedad trabajando con esta sencilla regla: el else ertenece al


if
libre ms cercano. Si quisiramos ex resar la rimera estructura, deberamos aadir
llaves ara determinar com letamente qu bloque est dentro de qu otro:
if
condicin
if otra condicin
sentencias si

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

56
56

else
sentencias no
A esar de que el if externo contiene una sola sentencia (otro y, or tanto,
las llaves
son redundantesif), las llaves son necesarias ara indicar que el else va asocia
do a la
condicin exterior.
No hay sentencia elif: la combinacin else if
C no tiene una estructura elif como la de Python,
es usar
else if donde hubieras uesto un elif en Python:
if condicin
sentencias si

ero tam oco la necesita. Pued

else if condicin2
sentencias si2
else if condicin3
sentencias si3
else
sentencias no
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36 Disea un programa C que pida por teclado un nmero entero y diga si es par o
impar.
37 Disea un programa que lea dos nmeros enteros y muestre por pantalla, de estos
tres mensajes, el que convenga:

.
38 Tambin en C es problemtica la divisin por 0. Haz un programa C que resuelva
la ecuacin ax + b = 0 solicitando por teclado el valor de a y b (ambos de tipo fl
oat).
El programa detectar si la ecuacin no tiene solucin o si tiene infinitas soluciones
y,
en cualquiera de los dos casos, mostrar el pertinente aviso.
39 Disea un programa que solucione ecuaciones de segundo grado. El programa
detectar y tratar por separado las siguientes situaciones:
la ecuacin tiene dos soluciones reales;
la ecuacin tiene una nica solucin real;
la ecuacin no tiene solucin real;
la ecuacin tiene infinitas soluciones.
40 Realiza un programa que proporcione el desglose en billetes y monedas de una
cantidad exacta de euros. Hay billetes de 500, 200, 100, 50, 20, 10 y 5 euros y
monedas
de 1 y 2 euros.
Por ejemplo, si deseamos conocer el desglose de 434 euros, el programa mostra
r por
pantalla el siguiente resultado:
Introduccin a la programacin

Andrs Marzal/Isabel

con- ISBN:
Gracia
C
978-84-693-0143-2

5757
c
UJI
Introduccin a la prog
ramacin con C - UJI

Observa que la palabra billete (y moneda) concuerda en nmero con la cantidad


de billetes (o monedas) y que si no hay piezas de un determinado tipo (en el eje
mplo, de
1 euro), no muestra el mensaje correspondiente.
41 Disea un programa C que lea un carcter cualquiera desde el teclado, y muestre e
l
mensaje
cuando el carcter sea una letra mayscula y el mensaje

cuando sea una minscula. En cualquier otro caso, no mostrar


mensaje alguno. (Considera nicamente letras del alfabeto ingls.)
42 Disea un programa que lea cinco nmeros enteros por teclado y determine cul
de los cuatro ltimos nmeros es ms cercano al primero.
(Por ejemplo, si el usuario introduce los nmeros 2, 6, 4, 1 y 10, el programa
responder
que el nmero ms cercano al 2 es el 1.)
43 Disea un programa que, dado un nmero entero, determine si ste es el doble
de un nmero impar.
(Ejemplo: 14 es el doble de 7, que es impar.)
...............................................................................
...
La sentencia de seleccin switch
Hay una estructura condicional que no existe en Python: la estructura de seleccin
mltiple. Esta estructura permite seleccionar un bloque de sentencias en funcin del
valor de
una expresin (tpicamente una variable).
switch expresin
case valor1
sentencias
break
case valor2
sentencias
break
default
sentencias
break
El fragmento etiquetado con default es opcional.
Para ilustrar el uso de switch, nada mejor que un programa que muestra algo
por
pantalla en funcin de la opcin seleccionada de un men:
include
int main void
int opcion
printf
printf
scanf

opcion

Introduccin a la programacin con C

58
58

c
UJI
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

switch opcion
case 1
printf
break
case 2
printf
break
default
printf
break
return 0
Aunque resulta algo ms elegante esta otra versin, que hace uso de tipos enum
erados:

include
enum

Saludar 1 Despedirse

int main void


int opcion
printf
printf
scanf
opcion
switch opcion
case Saludar
printf
break
case Despedirse
printf
break
default
printf
break
return 0
Un error tpico al usar la estructura switch es olvidar el break que hay al fin
al de
cada opcin. Este programa, por ejemplo, presenta un comportamiento curioso:
E
include
enum

Saludar 1 Despedirse

int main void


int opcion
printf
printf
scanf

opcion

switch opcion
case Saludar
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

59
59

printf
case Despedirse
printf
default
printf
return 0
Si seleccionas la opcin 1, no sale un nico mensaje por pantalla, salen tres:
,
y
! Y si seleccionas la opcin 2, salen dos mensajes:
y
! Si no hay break, el flujo de control que entra en un case
ejecuta las
acciones asociadas al siguiente case, y as hasta encontrar un break o salir del s
witch
por la ltima de sus lneas.
El compilador de C no seala la ausencia de break como un error porque, de hec
ho,
no lo es. Hay casos en los que puedes explotar a tu favor este curioso comportam
iento
del switch:
include
enum

Saludar 1 Despedirse Hola Adios

int main void


int opcion
printf
printf
printf
printf
scanf
opcion
switch opcion
case Saludar
case Hola
printf
break
case Despedirse
case Adios
printf
break
default
printf
break
return 0
Ves por qu?

El bucle while

El bucle while de Python se traduce casi directamente a C:


while condicin
sentencias

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

60
60

Nuevamente, los parntesis son obligatorios y las llaves pueden suprimirse si el b


loque
contiene una sola sentencia.
Veamos un ejemplo de uso: un programa que calcula x n para x y n enteros:
include
int main void
int x n i r
printf
printf
r 1
i 0
while i n
r x
i

scanf
scanf

printf

x
n

x n r

return 0

El bucle do while
Hay un bucle iterativo que Python no tiene: el do while:
do
sentencias
while condicin
El bucle do while evala la condicin tras cada ejecucin de su bloque, as que es segur
o
que ste se ejecuta al menos una vez. Podramos reescribir
para us
ar un
bucle do while:
include
include
int main void
int a b i
float s
Pedir lmites inferior y superior.
do
printf
if a 0 printf
while a 0

scanf

scanf

do
printf
if b a printf
while b a

Calcular el sumatorio de la raz cuadrada de i para i entre a y b.


s 0.0

for i a i

b i

sqrt i

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

61
61

Mostrar el resultado.
printf
a b s
return 0
Los bucles do while no aaden potencia al lenguaje, pero s lo dotan de mayor ex
presividad. Cualquier cosa que puedas hacer con bucles do while, puedes hacerla tam
bin
con slo bucles while y la ayuda de alguna sentencia condicional if, pero probable
mente
requerirn mayor esfuerzo por tu parte.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44 Escribe un programa que muestre un men en pantalla con dos opciones: saludar
y salir. El programa pedir al usuario una opcin y, si es vlida, ejecutar su accin
asociada. Mientras no se seleccione la opcin salir, el men reaparecer y se solicitar
nuevamente una opcin. Implementa el programa haciendo uso nicamente de bucles
do while.
45 Haz un programa que pida un nmero entero de teclado distinto de 1. A continuacin, el programa generar una secuencia de nmeros enteros cuyo primer nmero es el
que hemos ledo y que sigue estas reglas:
si el ltimo nmero es par, el siguiente resulta de dividir a ste por la mi
tad;
si el ltimo nmero es impar, el siguiente resulta de multiplicarlo por 3
y aadirle
1.
Todos los nmeros se irn mostrando por pantalla conforme se vayan generando. El
proceso se repetir hasta que el nmero generado sea igual a 1. Utiliza un bucle do
while.
...............................................................................
...
El bucle for
El bucle for de Python existe en C, pero con importantes diferencias.
for inicializacin condicin incremento
sentencias
Los parntesis de la primera lnea son obligatorios. Fjate, adems, en que los tres ele
mentos entre parntesis se separan con puntos y comas.
El bucle for presenta tres componentes. Es equivalente a este fragmento de cdi
go:
inicializacin
while condicin
sentencias
incremento
Una forma habitual de utilizar el bucle for es la que se muestra en este ejem
plo, que
imprime por pantalla los nmeros del 0 al 9 y en el que suponemos que i es de tipo

int:
for i 0 i
printf

10 i
i

Es equivalente, como decamos, a este otro fragmento de programa:

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
6262
Introduccin a la prog
ramacin con C - UJI
c
UJI

Comparaciones y asignaciones
Un error frecuente es sustituir el operador de comparacin de igualdad por
el de asignacin en una estructura if o while. Analiza este par de sentencias:
?
a 0
if a 0

Lo que escribi... bien o mal?

Parece que la condicin del if se evala a cierto, pero no es as: la comparacin


es, en realidad, una asignacin. El resultado es que a recibe el valor 0 y
que ese 0,
devuelto por el operador de asignacin, se considera la representacin del va
lor falso.
Lo correcto hubiera sido:
a 0
if a

Lo que quera escribir.

Aunque esta construccin es perfectamente vlida, provoca la emisin de un


mensaje
de error en muchos compiladores, pues suele ser fruto de un error.
Los programadores ms disciplinados evitan cometer este error escribien
do siempre
la variable en la parte derecha:
a 0
if 0

Correcto.

De ese modo, si se confunden y usan en lugar de , se habr escrito una expr


esin
incorrecta y el compilador detendr el proceso de traduccin a cdigo de mquina:
a 0
if 0 a

i 0
while i
printf
i

Mal: error detectable por el compilador.

10
i

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46 Implementa el programa de clculo de x n (para x y n entero) con un bucle for.
47 Implementa un programa que dado un nmero de tipo int, ledo por teclado, se
asegure de que slo contiene ceros y unos y muestre su valor en pantalla si lo int
erpretamos como un nmero binario. Si el usuario introduce, por ejemplo, el nmero 1101,
el

programa mostrar el valor 13. Caso de que el usuario introduzca un nmero formado p
or
nmeros de valor diferente, indica al usuario que no puedes proporcionar el valor
de su
interpretacin como nmero binario.
48 Haz un programa que solicite un nmero entero y muestre su factorial. Utiliza u
n
entero de tipo long long para el resultado. Debes usar un bucle for.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
63
63
ramacin con C - UJI

Introduccin a la prog

c
UJI

49 El nmero de combinaciones de n elementos tomados de m en m es:


 
m
n
n!
Cn =
=
.
m
(n m)! m!
Disea un rograma que ida el valor de n y m y calcule Cnm . (Ten en cuenta que n
ha
de ser mayor o igual que m.)
(Puedes com robar la validez de tu rograma introduciendo los valores n = 15
y
m = 10: el resultado es 3003.)
50 Qu muestra por pantalla este programa?
include
int main void
int a
c

127 b
a

1024 c i

printf
a 2147483647
for i 0 i 8 sizeof a
printf
c a
a
1

c
i
0

printf
a 1
for i 0 i 8 sizeof a i
if c a
0 c 1
else c 1
a
1
a 2147483647
for i 0 i 8 sizeof a
printf
c a
a
1

i
0

printf
return 0
51 Cuando no era corriente el uso de terminales grficos de alta resolucin era comn
representar grficas de funciones con el terminal de caracteres. Por ejemplo, un p
eriodo
de la funcin seno tiene este aspecto al representarse en un terminal de caractere
s (cada
punto es un asterisco):

Andrs Marzal/Isabel
Introduccin
Gracia

a la programacin con- ISBN:


C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

64
64

Haz un programa C que muestre la funcin seno utilizando un bucle que recorre el p
eriodo
2 en 24 asos (es decir, re resentndolo con 24 lneas).
52 Modifica el programa para que muestre las funciones seno (con asteriscos) y
coseno (con sumas) simultneamente.
...............................................................................
...
Variables de bucle de usar y tirar
C99 ha copiado una buena idea de C : permitir que las variables de bucle
se definan
all donde se usan y dejen de existir cuando el bucle termina. Fjate en este
programa:
include
int main void
int a

for int i 0 i
printf
a
1

32 i
i a

return 0
La variable i, el ndice del bucle, se declara en la mismsima zona de inicia
lizacin del
bucle. La variable i slo existe en el mbito del bucle, que es donde se usa.

Hacer un bucle que recorra, por ejemplo, los nmeros pares entre 0 y 10 es senc
illo:
basta sustituir el modo en que se incrementa la variable ndice:
for i 0 i
10 i
i
2
printf
i
aunque la forma habitual de expresar el incremento de i es esta otra:
for i 0 i
10 i
2
printf
i
Un bucle que vaya de 10 a 1 en orden inverso presenta este aspecto:
for i 10 i
0 i
printf
i

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53 Disea un programa C que muestre el valor de 2n para todo n entre 0 y un valor
entero proporcionado por teclado.

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
6565
c
UJI
Introduccin a la prog
ramacin con C - UJI

54 Haz un programa que pida al usuario una cantidad de euros, una tasa de inters
y un nmero de aos y muestre por pantalla en cunto se habr convertido el capital
inicial transcurridos esos aos si cada ao se aplica la tasa de inters introducida.
Recuerda que un capital C a un inters del x por cien durante n aos se conviert
e
en C (1 + x/100)n .
(Prueba tu programa sabiendo que 10 000 euros al 4.5% de inters anual se conv
ierten
en 24 117.14 euros al cabo de 20 aos.)
55 Un vector en un espacio tridimensional es una tripleta de valores reales (x,
y, z).
Deseamos confeccionar un programa que permita operar con dos vectores. El usuari
o ver
en pantalla un men con las siguientes opciones:

Tras la ejecucin de cada una de las acciones del men ste reaparecer en pantalla,
a
menos que la opcin escogida sea la nmero 9. Si el usuario escoge una opcin diferent
e,
el programa advertir al usuario de su error y el men reaparecer.
Las opciones 4 y 5 pueden proporcionar resultados distintos en funcin del ord
en
de los operandos, as que, si se escoge cualquiera de ellas, aparecer un nuevo men
que permita seleccionar el orden de los operandos. Por ejemplo, la opcin 4 mostra
r el
siguiente men:

Nuevamente, si el usuario se equivoca, se le advertir del error y se le permi


tir
corregirlo.
La opcin 8 del men principal conducir tambin a un submen para que el usuario
decida sobre qu vector se aplica el clculo de longitud.
Puede que necesites que te refresquemos la memoria sobre los clculos a realiz
ar.
Quiz la siguiente tabla te sea de ayuda:
Operacin
Suma: (x1 , y1 , z1 ) + (x2 , y2 , z2 )
, z1 + z2 )
Diferencia: (x1 , y1 , z1 ) (x2 , y2 , z2 )
z2 )
Producto escalar: (x1 , y1 , z1 ) (x2 , y2 , z2 )
2
Producto vectorial: (x1 , y1 , z1 ) (x2 , y2 , z2 )
x1 z2 , x1 y2 y1 x2 )

Clculo
(x1 + x2 , y1 + y2
(x1 x2 , y1 y2 , z1
x1 x2 + y1 y2 + z1 z
(y1 z2 z1 y2 , z1 x2

180
x1 x2 + y1 y2 + z1 z2
ngulo entre (x1 , y1 , z1 ) y (x2 , y2 , z2 )


arccos

x + y2 + z 2 x 2 + y2 + z 2

2
1

2


Longitud de (x, y, z)

x 2 + y2 + z 2

Ten en cuenta que tu rograma debe contem lar toda osible situacin exce cion
al:
divisiones or cero, races con argumento negativo, etc..
...............................................................................
...
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con ISBN:
C
9788469301432
Introduccin a la

rogramacin con C  UJI


c
UJI

66
66

La sentencia break tambin est dis onible en C. De hecho, ya hemos visto una a lica
cin
suya en la estructura de control switch. Con ella uedes, adems, abortar al insta
nte la
ejecucin de un bucle cualquiera (while, do while o for).
Otra sentencia de C que uede resultar til es continue. Esta sentencia finali
za la
iteracin actual, ero no aborta la ejecucin del bucle.
Por ejem lo, cuando en un bucle while se ejecuta continue, la siguiente sent
encia a
ejecutar es la condicin del bucle; si sta se cum le, se ejecutar una nueva iteracin
del
bucle.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56 Qu muestra por pantalla este programa?
include
int main void
int i
i 0
while i 10
if i 2
0
i
continue
printf
i

for i 0 i 10 i
if i 2
0
continue
printf
i
return 0
57

58

Traduce a C este programa Python.


car raw input
if
car lower
print
else
if not car
or
print
print
else
print
Traduce a C este programa Python.
from math import pi
radio float raw input
opcion
while opcion
and opcion
print
print

or car
car

and opcion

print
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
67
67
ramacin con C - UJI

Introduccin a la prog

c
UJI

print
opcion raw
if opcion
diametro
print
elif opcion
perimetro
print
elif opcion
area pi
print
else
print
opcion
59

input
2

radio
diametro
2

pi

radio
perimetro

radio

2
area

Traduce a C este programa Python.


anyo int raw input
if anyo 4 0 and anyo

100

0 or anyo

400

0
print
else
print
60
limite

anyo
anyo
Traduce a C este programa Python.
int raw input

for num in range 1 limite 1


creo que es primo 1
for divisor in range 2 num
if num divisor
0
creo que es primo 0
break
if creo que es primo
1
print num
61 Escribe un programa que solicite dos enteros n y m asegurndose  de que m sea
mayor o igual que n. A continuacin, muestra por pantalla el valor de m
i=n 1/i.
62 Escribe un programa que solicite un nmero entero y muestre todos los nmeros
primos entre 1 y dicho nmero.
63 Haz un programa que calcule el mximo comn divisor (mcd) de dos enteros
positivos. El mcd es el nmero ms grande que divide exactamente a ambos nmeros.
64 Haz un programa que calcule el mximo comn divisor (mcd) de tres enteros
positivos.
65 Haz un programa que vaya leyendo nmeros y mostrndolos por pantalla hasta
que el usuario introduzca un nmero negativo. En ese momento, el programa acabar
mostrando un mensaje de despedida.
66 Haz un programa que vaya leyendo nmeros hasta que el usuario introduzca un
nmero negativo. En ese momento, el programa mostrar por pantalla el nmero mayor
de cuantos ha visto.
...............................................................................
...

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2

6868

Introduccin a la programacin con C - UJI


c
UJI

Me llamo Alicia, Majestad dijo Alicia con mucha educacin; pero aadi
para sus adentros: Vaya!, en realidad no son ms que un mazo de cartas.
No tengo por qu tenerles miedo!.
LEWIS CARROLL, Alicia en el Pas de
las Maravillas.
En este captulo vamos a estudiar algunas estructuras que agrupan varios datos, pe
ro cuyo
tamao resulta conocido al compilar el programa y no sufre modificacin alguna duran
te
su ejecucin. Empezaremos estudiando los vectores, estructuras que se pueden asimi
lar a
las listas Python. En C, las cadenas son un tipo particular de vector. Manejar c
adenas en
C resulta ms complejo y delicado que manejarlas en Python. Como contrapartida, es
ms
fcil definir en C vectores multidimensionales (como las matrices) que en Python.
En este
captulo nos ocuparemos tambin de ellos. Estudiaremos adems los registros en C, que
permiten definir nuevos tipos como agrupaciones de datos de tipos no necesariame
nte
idnticos. Los registros de C son conceptualmente idnticos a los que estudiamos en
Python.

Un vector (en ingls, array) es una secuencia de valores a los que podemos acceder
mediante ndices que indican sus respectivas posiciones. Los vectores pueden asimi
larse
a las listas Python, pero con una limitacin fundamental: todos los elementos del
vector
han de tener el mismo tipo. Podemos definir vectores de enteros, vectores de flo
tantes,
etc., pero no podemos definir vectores que, por ejemplo, contengan a la vez ente
ros y
flotantes. El tipo de los elementos de un vector se indica en la declaracin del v
ector.
C nos permite trabajar con vectores estticos y dinmicos. En este captulo nos o
cupamos nicamente de los denominados vectores estticos, que son aquellos que tienen
tamao fijo y conocido en tiempo de compilacin. Es decir, el nmero de elementos del
vector no puede depender de datos que suministra el usuario: se debe hacer explci
to mediante una expresin que podamos evaluar examinando nicamente el texto del programa
.

Un vector a de 10 enteros de tipo int se declara as:


int a 10
69
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

69

Sin cortes
Los vectores C son mucho ms limitados que las listas Python. A los problem
as relacionados con el tamao fijo de los vectores o la homogeneidad en el tipo
de sus
elementos se une una incomodidad derivada de la falta de operadores a los
que nos
hemos acostumbrado como programadores Python. El operador de corte, por e
jemplo, no
existe en C. Cuando en Python desebamos extraer una copia de los elementos
entre i
y j de un vector a escribamos a i j 1 . En C no hay operador de corte. . .
ni operador
de concatenacin o repeticin, ni sentencias de borrado de elementos, ni se e
ntienden
como accesos desde el final los ndices negativos, ni hay operador de perte
nencia, etc.
Echaremos de menos muchas de las facilidades propias de Python.

El vector a comprende los elementos a 0 , a 1 , a 2 , . . . , a 9 , todos de tip


o int. Al
igual que con las listas Python, los ndices de los vectores C empiezan en cero.
En una misma lnea puedes declarar ms de un vector, siempre que todos compartan
el mismo tipo de datos para sus componentes. Por ejemplo, en esta lnea se declara
n dos
vectores de float, uno con 20 componentes y otro con 100:
float a 20
b 100
Tambin es posible mezclar declaraciones de vectores y escalares en una misma
lnea. En este ejemplo se declaran las variables a y c como vectores de 80 caracte
res y
la variable b como escalar de tipo carcter:
char a 80
b c 80
Se considera mal estilo declarar la talla de los vectores con literales de e
ntero. Es
preferible utilizar algn identificador para la talla, pero teniendo en cuenta que
ste debe
corresponder a una constante:
define
80
char a
Esta otra declaracin es incorrecta, pues usa una variable para definir la tal
la del
vector1 :
int talla
80
char a talla

!No siempre es vlido!

Puede que consideres vlida esta otra declaracin que prescinde de constantes de
finidas con define y usa constantes declaradas con const, pero no es as:
const int talla
80
char a talla

!No siempre es vlido!

Una variable const es una variable en toda regla, aunque de slo lectura.

Una vez creado un vector, sus elementos presentan valores arbitrarios. Es un err
or suponer
que los valores del vector son nulos tras su creacin. Si no lo crees, fjate en est
e programa:
1
Como siempre, hay excepciones: C99 permite declarar la talla de un vector
con una expresin cuyo valor
slo se conoce en tiempo de ejecucin, pero slo si el vector es una variable local a
una funcin. Para evitar
confusiones, no haremos uso de esa caracterstica en este captulo y lo considerarem
os incorrecto.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

70
70

Introduccin a la programacin con C -UJI


cUJI

include
define

int main void


int i a
for i 0 i
printf
return 0

i
a i

Observa que el acceso a elementos del vector sigue la misma notacin de Python: us
amos
el identificador del vector seguido del ndice encerrado entre corchetes. En una e
jecucin
del programa obtuvimos este resultado en pantalla (es probable que obtengas resu
ltados
diferentes si repites el experimento):

Evidentemente, no son cinco ceros.


Podemos inicializar todos los valores de un vector a cero con un bucle for
:
include
define

10

int main void


int i a
for i 0 i
a i 0
for i 0 i
printf

i
i
a i

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67 Declara e inicializa un vector de 100 elementos de modo que los componentes d
e
ndice par valgan 0 y los de ndice impar valgan 1.
68 Escribe un programa C que almacene en un vector los 50 primeros nmeros de
Fibonacci. Una vez calculados, el programa los mostrar por pantalla en orden inve
rso.
69 Escribe un programa C que almacene en un vector los 50 primeros nmeros de
Fibonacci. Una vez calculados, el programa pedir al usuario que introduzca un nmer
o
y dir si es o no es uno de los 50 primeros nmeros de Fibonacci.
...............................................................................

...

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
7171
Introduccin a la prog
ramacin con C - UJI
c
UJI

Cuestin de estilo: constantes o literales al declarar la talla de un vector


?
Por qu es preferible declarar el tamao de un vector con una constante? Porqu
e la talla
del vector puede aparecer en diferentes puntos del programa y es posible
que algn da
modifiquemos el programa para trabajar con una talla diferente. En tal ca
so, deberamos
editar muchas lneas diferentes del programa (quiz decenas o cientos). Basta
ra que
olvidsemos modificar una o que modificsemos una de ms para que el programa f
uera
errneo. Fjate en este programa C:
include
int main void
int i a 10
for i 0
for i 0
for i 0
for i 0
return 0

b 10
i
i
i
i

10
10
10
10

i
i
i
i

a i
0
b i
0
printf
printf

a i
b i

Las tallas de a y b aparecen en seis lugares. Imagina que deseas modifica


r el programa
para que a pase a tener 20 enteros: tendrs que modificar slo tres de esos d
ieces. Ello
te obliga a leer el programa detenidamente y, cada vez que encuentres un
10, pensar si
ese 10 en particular es o no es la talla de a. Complicado. Estudia esta v
ersin:
include
define
define

10
10

int main void


int i a TALLA A

b TALLA B

for i 0
for i 0
for i 0

i
i
i

TALLA
TALLA
TALLA

A
B
A

i
i
i

a i
0
b i
0
printf

for i 0

TALLA

printf

a i
b i
return 0
Si ahora has de modificar a para que tenga 20 elementos, basta con editar
la lnea
3: cambia el 10 por un 20. Ms rpido y con mayor garanta de no cometer errore
s.
Por qu en Python no nos preocup esta cuestin? Recuerda que en Python no haba
declaracin de variables, que las listas podan modificar su longitud durante

la ejecucin
de los programas y que podas consultar la longitud de cualquier secuencia
de valores
con la funcin predefinida len. Python ofrece mayores facilidades al progra
mador, pero
a un precio: menos velocidad de ejecucin y ms consumo de memoria.

Hay una forma alternativa de inicializar vectores. En este fragmento se defi


nen e
inicializan dos vectores, uno con todos sus elementos a 0 y otro con una secuenc
ia
ascendente de nmeros:
define
int a

5
0 0 0 0 0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
2

Introduccin a la programacin con C -UJI


cUJI

72
7

int b

1 2 3 4 5

Ten en cuenta que, al declarar e inicializar simultneamente un vector, debes indi


car
explcitamente los valores del vector y, por tanto, esta aproximacin slo es factible
para
la inicializacin de unos pocos valores.
Omisin de talla en declaraciones con inicializacin y otro modo de inicializar
Tambin puedes declarar e inicializar vectores as:
int a
int b

0 0 0 0 0
1 2 3 4 5

El compilador deduce que la talla del vector es 5, es decir, el nmero de val


ores que
aparecen a la derecha del igual. Te recomendamos que, ahora que ests aprendi
endo,
no uses esta forma de declarar vectores: siempre que puedas, opta por una q
ue haga
explcito el tamao del vector.
En C99 es posible inicializar slo algunos valores del vector. La sintaxis
es un poco
enrevesada. Aqu tienes un ejemplo en el que slo inicializamos el primer y ltim
o
elementos de un vector de talla 10:
int a

Vamos a ilustrar lo aprendido desarrollando un sencillo programa que calcule y m


uestre
los nmeros primos menores que , para un valor de fijo y determinado en el propio
programa. Usaremos un mtodo denominado la criba de Eratstenes, uno de los algoritm
os
ms antiguos y que debemos a un astrnomo, gegrafo, matemtico y filsofo de la antigua
Grecia. El mtodo utiliza un vector de valores booleanos (unos o ceros). Si la cel
da de
ndice i contiene el valor 1, consideramos que i es primo, y si no, que no lo es.
Inicialmente,
todas las celdas excepto la de ndice 0 valen 1. Entonces tachamos (ponemos un 0 en)
las celdas cuyo ndice es mltiplo de 2. Acto seguido se busca la siguiente casilla
que
contiene un 1 y se procede a tachar todas las casillas cuyo ndice es mltiplo del nd
ice
de esta casilla. Y as sucesivamente. Cuando se ha recorrido completamente el vect
or, las
casillas cuyo ndice es primo contienen un 1.
Vamos con una primera versin del programa:
include
define

100

int main void


int criba

i j

Inicializacin
criba 0
0
for i 1 i
i
criba i
1
Criba de Eratstenes
for i 2 i
i
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

73
73

if criba i
for j 2 i j
criba i j

j
0

Mostrar los resultados


for i 0 i
i
if criba i
printf
i
return 0
Observa que hemos tenido que decidir qu valor toma , pues el vector criba debe te
ner un
tamao conocido en el momento en el que se compila el programa. Si deseamos conoce
r
los, digamos, primos menores que 200, tenemos que modificar la lnea 3.
Mejoremos el programa. Es necesario utilizar 4 bytes para almacenar un 0 o u
n 1?
Estamos malgastando memoria. Esta otra versin reduce a una cuarta parte el tamao
del vector criba:
include
define

100

int main void


char criba
int i j
Inicializacin
criba 0
0
for i 1 i
i
criba i
1
Criba de Eratstenes
for i 2 i
i
if criba i
for j 2 i j
j
criba i j 0
Mostrar los resultados
for i 0 i
i
if criba i
printf
i
return 0
Mejor as.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70 Puedes ahorrar tiempo de ejecucin haciendo que i tome valores entre 2 y la raz
cuadrada de . Modifica el programa y comprueba que obtienes el mismo resultado.
...............................................................................
...

Queremos efectuar estadsticas con una serie de valores (las edades de 15 personas
), as
que vamos a disear un programa que nos ayude. En una primera versin, solicitaremos
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
7474
Introduccin a la prog
ramacin con C - UJI
c
UJI

Optimiza, pero no te pases


C permite optimizar mucho los programas y hacer que estos consuman la meno
r memoria
posible o que se ejecuten a mucha velocidad gracias a una adecuada seleccin
de
operaciones. En el programa de la criba de Eratstenes, por ejemplo, an podem
os
reducir ms el consumo de memoria: para representar un 1 o un 0 basta un sol
o bit.
Como en un char caben 8 bits, podemos proponer esta otra solucin:
include
include
define

100

int main void


char criba
a versin anterior.
int i j

8 1

Inicializacin
criba 0
254

Ocupa unas 8 veces menos que l

Pone todos los bits a 1 except

o el primero.
for i 1 i
criba i

8 i
255

Criba de Eratstenes
for i 2 i
i
if criba i 8
1
el bit en posicin i vale 1.
for j 2 i j
j
criba i j 8
Pone a 0 el bit en posicin i j.

Pone todos los bits a 1.

i 8
1

Pregunta si
i j

Mostrar los resultados


for i 0 i
i
if criba i 8
1
i 8
printf
i
return 0
Buf! La legibilidad deja mucho que desear. Y no slo eso: consultar si un det
erminado
bit vale 1 y fijar un determinado bit a 0 resultan ser operaciones ms costo
sas que
consultar si el valor de un char es 1 o, respectivamente, fijar el valor d
e un char a 0,
pues debes hacerlo mediante operaciones de divisin entera, resto de divisin
entera,
desplazamiento, negacin de bits y el operador .
Vale la pena reducir la memoria a una octava parte si, a cambio, el pro
grama
pierde legibilidad y, adems, resulta ms lento? No hay una respuesta definiti
va a esta
pregunta. La nica respuesta es: depende. En segn qu aplicaciones, puede resul
tar

necesario, en otras no. Lo que no debes hacer, al menos de momento, es obs


esionarte
con la optimizacin y complicar innecesariamente tus programas.

las edades de todas las personas y, a continuacin, calcularemos y mostraremos por


pantalla la edad media, la desviacin tpica, la moda y la mediana. Las frmulas para
el
clculo de la media y la desviacin tpica de n elementos son:
n
i=1 xi
x =
,
 n
n
i=1 (xi x)
2
=
,
n
Andr aMarzal/Iabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

75
75

Introduccin a la programacin con C -UJI


cUJI

donde xi e la edad del individuo nmero i.2 La moda e la edad que m vece aparec
e
(i do o m edade aparecen mucha vece con la mxima frecuencia, aumiremo que
una cualquiera de ella e la moda). La mediana e la edad tal que el 50% de la
edade
on inferiore o iguale a ella y el retante 50% on mayore o iguale.
Empezamo por la declaracin del vector que albergar la 15 edade y por leer l
o
dato:
include
define

15

int main void


int edad

Lectura de edade
for i 0 i
i
printf
i 1
canf
edad i
return 0
Vale la pena que te detenga a obervar cmo indicamo a canf que lea la celda de
ndice i en el vector edad: uamo el operador delante de la exprein edad i . E l
o
que caba eperar: edad i e un ecalar de tipo int, y ya abe que canf epera 
u
direccin de memoria.
Paamo ahora a calcular la edad media y la deviacin tpica (no te ha de upo
ner
dificultad alguna con la experiencia adquirida al aprender Python):
include
include
define

15

int main void


int edad
i uma edad
float uma deviacion media deviacion
Lectura de edade
for i 0 i
i
printf
i 1
canf
edad i
Clculo de la media
uma edad 0
for i 0 i
i

uma edad
media uma edad

edad i
float

Clculo de la deviacion tpica


uma deviacion 0.0

Hay una definicin alternativa de la deviacin tpica en la que el denominado


r de la fraccin e n 1.
Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
UJI
c

76
76

for i 0 i
i
suma desviacion edad i
media
media
desviacion sqrt suma desviacion
Im resin de resultados
rintf
rintf

edad

media
desviacion

return 0
El clculo de la moda (la edad ms frecuente) resulta ms roblemtica. Cmo abor
dar el clculo? Vamos a resentar dos versiones diferentes. Em ezamos or una que
consume demasiada memoria. Dado que trabajamos con edades, odemos asumir que
ninguna edad iguala o su era los 150 aos. Podemos crear un vector con 150 contado
res,
uno ara cada osible edad:
include
include
define
define

15
150

int main void


int edad
i suma edad
float suma desviacion media desviacion
int contador
frecuencia moda
Lectura de edades
for i 0 i
i
rintf
i 1
scanf
edad i
Clculo de la media
suma edad 0
for i 0 i
i
suma edad
edad i
media suma edad
float

Clculo de la desviacion t ica


suma desviacion 0.0
for i 0 i
i
suma desviacion
edad i
media
media
desviacion sqrt suma desviacion

Clculo de la moda
for i 0 i
i
s contadores.
contador i
0
for i 0 i
i
contador edad i
dor asociado a edad i .
moda
1

edad

Inicializacin de lo

Incrementamos el conta

frecuencia 0
for i 0 i
i
edad con mayor valor en contador).
if contador i
frecuencia
frecuencia contador i
moda i

Buscamos la moda (

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
7

Introduccin a la rogramacin con C UJI


cUJI

77
7

Im resin de resultados
rintf
rintf
rintf

media
desviacion
moda

return 0
Esta solucin consume un vector de 150 elementos enteros cuando no es estrictam
ente
necesario. Otra osibilidad asa or ordenar el vector de edades y contar la lon
gitud de
cada secuencia de edades iguales. La edad cuya secuencia sea ms larga es la moda:
include
include
define

15

int main void


int edad
i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda
Lectura de edades
for i 0 i
i
rintf
i 1
scanf
edad i
Clculo de la media
suma edad 0
for i 0 i
i
suma edad
edad i
media suma edad
float
Clculo de la desviacion t ica
suma desviacion 0.0
for i 0 i
i
suma desviacion
edad i
media

edad i

media
desviacion sqrt suma desviacion
Clculo de la moda
for i 0 i
1 i

Ordenacin mediante

burbuja.
for j 0 j
i j
if edad j
edad j 1
aux edad j
edad j
edad j 1
edad j 1
aux
frecuencia 0
frecuencia moda 0
moda
1
for i 0 i

1 i

Bsqueda de la serie de valores idntico

s ms larga.
if edad i
edad i 1
frecuencia
if frecuencia frecuencia moda
frecuencia moda frecuencia
Introduccin a la rogramacin con C
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432

78
78
cUJI

Introduccin a la

rogramacin con C UJI

moda

edad i

else
frecuencia

Im resin de resultados
rintf
rintf
rintf

media
desviacion
moda

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71 Contiene en cada instante la variable frecuencia el verdadero valor de la frecuencia de aparicin de un valor? Si no es as, qu contiene? Afecta eso al clculo
efectuado? Por qu?
72 Esta nueva versin del programa presenta la ventaja adicional de no fijar un
lmite mximo a la edad de las personas. El programa resulta, as, de aplicacin ms
general. Son todo ventajas? Ves algn aspecto negativo? Reflexiona sobre la velocida
d
de ejecucin del programa comparada con la del programa que consume ms memoria.
...............................................................................
...
Slo nos resta calcular la mediana. Mmmm. No hay que hacer nuevos clculos para
conocer la mediana: gracias a que hemos ordenado el vector, la mediana es el val
or que
ocupa la posicin central del vector, es decir, la edad de ndice
2.
include
include
define

15

int main void


int edad
i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana
Lectura de edades
for i 0 i
i
printf
i 1
scanf

edad i

Clculo de la media
suma edad 0
for i 0 i
i
suma edad
edad i
media suma edad
float
Clculo de la desviacion tpica
suma desviacion 0.0
for i 0 i
i

suma desviacion
edad i
edad i
media
desviacion sqrt suma desviacion

media

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
7979
Introduccin a la prog
ramacin con C - UJI
c
UJI

Clculo de la moda
for i 0 i
1 i
iante burbuja.
for j 0 j
i j
if edad j
edad j 1
aux edad j
edad j
edad j 1
edad j 1
aux

Ordenacin med

frecuencia 0
frecuencia moda 0
moda
1
for i 0 i
1 i
if edad i
edad i 1
if
frecuencia frecuencia moda
Ver ejercicio 73.
frecuencia moda frecuencia
moda edad i
else
frecuencia

Clculo de la mediana
mediana edad

Impresin de resultados
printf
printf
printf
printf

media
desviacion
moda
mediana

return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73 Fjate en la lnea 44 del programa y comprala con las lneas 44 y 45 de su
versin anterior. Es correcto ese cambio? Lo sera este otro?:
if frecuencia
frecuencia moda
...............................................................................
...
Bueno, vamos a modificar ahora el programa para que el usuario introduzca cua
ntas
edades desee hasta un mximo de 20. Cuando se introduzca un valor negativo para la
edad, entenderemos que ha finalizado la introduccin de datos.
include
include
define

20

int main void


int edad
i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana
Lectura de edades

for i 0 i
printf

i
i 1

scanf

edad i

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
8080
Introduccin a la prog
ramacin con C - UJI
c
UJI

if edad i
break

return 0
Mmmm. Hay un problema: si no damos 20 edades, el vector presentar toda una serie
de valores sin inicializar y, por tanto, con valores arbitrarios. Sera un grave e
rror tomar
esos valores por edades introducidas por el usuario. Una buena idea consiste en
utilizar
una variable entera que nos diga en todo momento cuntos valores introdujo realmen
te
el usuario en el vector edad:
include
include
define

20

int main void


int edad
personas i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana
Lectura de edades
personas 0
for i 0 i
printf
scanf
edad i
if edad i
0
break
personas

i
i 1

return 0
La constante que hasta ahora se llamaba
ha pasado a llamarse
.
Se pretende reflejar que su valor es la mxima cantidad de edades de personas que
podemos manejar, pues el nmero de edades que manejamos realmente pasa a estar en
la variable entera personas.
Una forma alternativa de hacer lo mismo nos permite prescindir del ndice i:
include
include
define
int main void

20

int edad
personas j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

81
81

s 1

Lectura de edades
personas 0
do
printf
personas 1
scanf
edad personas
personas
while personas
0
personas

edad persona

return 0
Imagina que se han introducido edades
punta
(conceptualmente) al final de la serie de
fectuar
los clculos pertinentes:
0
1
2
11 12 13 14 15 16 17 18
edad
7 -55 0 391 0
personas
MAX PERSONAS

de 10 personas. La variable personas a


valores que hemos de considerar para e
3
19

18 30 18 19 19 31
-6 89 322 -2

10

27 66 -1 88

10
20

Ya podemos calcular la edad media, pero con un cuidado especial por las posi
bles
divisiones por cero que provocara que el usuario escribiera una edad negativa com
o edad
de la primera persona (en cuyo caso personas valdra 0):
include
include
define

20

int main void


int edad
personas i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana

s 1

Lectura de edades
personas 0
do
printf
personas 1
scanf
edad personas
personas
while personas
0
personas

edad persona

if personas 0
Clculo de la media
suma edad 0
for i 0 i personas i
suma edad
edad i
media suma edad
float personas
Clculo de la desviacion tpica
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
82
82
cUJI
Introduccin a la programacin con C -UJI

suma desviacion 0.0


for i 0 i personas i
suma desviacion edad i
media edad i
media
desviacion sqrt suma desviacion personas
Clculo de la moda
for i 0 i personas 1 i
mediante burbuja.
for j 0 j personas i j
if edad j
edad j 1
aux edad j
edad j
edad j 1
edad j 1
aux

Ordenacin

frecuencia 0
frecuencia moda 0
moda
1
for i 0 i personas 1 i
if edad i
edad i 1
if
frecuencia frecuencia moda
frecuencia moda frecuencia
moda edad i
else
frecuencia

Clculo de la mediana
mediana edad personas 2
Impresin de resultados
printf
printf
printf
printf

media
desviacion
moda
mediana

else
printf
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74 Cuando el nmero de edades es par no hay elemento central en el vector ordenado
,
as que estamos escogiendo la mediana como uno cualquiera de los elementos centrale
s.
Utiliza una definicin alternativa de edad mediana que considera que su valor es l
a media
de las dos edades que ocupan las posiciones ms prximas al centro.
75 Modifica el ejercicio anterior para que, caso de haber dos o ms valores con la
mxima frecuencia de aparicin, se muestren todos por pantalla al solicitar la moda.
76 Modifica el programa anterior para que permita efectuar clculos con hasta 100
personas.
77 Modifica el programa del ejercicio anterior para que muestre, adems, cuntas
edades hay entre 0 y 9 aos, entre 10 y 19, entre 20 y 29, etc. Considera que ning
una

edad es igual o superior a 150.


Ejemplo: si el usuario introduce las siguientes edades correspondientes a 12
personas:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
8383
Introduccin a la prog
ramacin con C - UJI
c
UJI

el programa mostrar (adems de la media, desviacin tpica, moda y mediana), la siguiente tabla:

78 Modifica el programa para que muestre un histograma de edades. La tabla anter


ior
se mostrar ahora como este histograma:

Como puedes ver, cada asterisco representa la edad de una persona.


79 Modifica el programa anterior para que el primer y ltimo rangos de edades
mostrados en el histograma correspondan a tramos de edades en los que hay al men
os
una persona. El histograma mostrado antes aparecer ahora as:

80 Modifica el programa del ejercicio anterior para que muestre el mismo histogr
ama
de esta otra forma:

...............................................................................
...
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
roduccin a la programacin con C - UJI
c
UJI

84
84

Int

Deseamos implementar una calculadora para polinomios de grado menor o igual que
10.
Un polinomio p(x) = p0 + p1 x + p2 x 2 + p3 x 3 + + p10 x 10 puede representarse
con un
vector en el que se almacenan sus 11 coeficientes (p0 , p1 , . . . , p10 ). Vamo
s a construir un
programa C que permita leer por teclado dos polinomios p(x) y q(x) y, una vez led
os,
calcule los polinomios s(x) = p(x) + q(x) y m(x) = p(x) q(x).
Empezaremos definiendo dos vectores p y q que han de poder contener 11 valor
es en
coma flotante:
include
define

11

int main void


float p

Como leer por teclado 11 valores para p y 11 ms para q es innecesario cuando


trabajamos con polinomios de grado menor que 10, nuestro programa leer los datos
pidiendo en primer lugar el grado de cada uno de los polinomios y solicitando nic
amente
el valor de los coeficientes de grado menor o igual que el indicado:
E

include
define

11

int main void


float p
int grado
int i

Lectura de p
do
printf
scanf
grado
while grado 0
grado
for i 0 i grado i
printf
i scanf

p i

Lectura de q
do
printf
scanf
grado
while grado 0
grado
for i 0 i grado i
printf
i scanf
return 0

q i

El programa presenta un problema: no inicializa los coeficientes que correpo


nden a
los trminos x n , para n mayor que el grado del polinomio. Como dichos valores de
ben
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

85
85

ser nulos, hemos de inicializarlos explcitamente (en aras de la brevedad mostramo


s
nicamente la inicializacin de los coeficientes de p):

int main void


float p
int grado
int i

Lectura de p
do
printf
1
scanf
grado
while grado 0
grado
for i 0 i grado i
printf
i scanf

p i

for i grado 1 i
p i 0.0

return 0
Ahora que hemos ledo los polinomios, calculemos la suma. La almacenaremos en
un
nuevo vector llamado s. La suma de dos polinomios de grado menor que
es un polinomio de grado tambin menor que
, as que el vector s
tendr
talla
.

int main void


float p

El procedimiento para calcular la suma de polinomios es sencillo. He aqu el cl


culo
y la presentacin del resultado en pantalla:
include
define

11

int main void


float p
int grado
int i
Lectura de p
do
printf
1

scanf
grado
while grado 0
grado
for i 0 i grado i
printf
i scanf

p i

for i grado 1 i

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

86
86

p i

0.0

Lectura de q
do
printf
1
scanf
grado
while grado 0
grado
for i 0 i grado i
printf
i scanf

q i

for i grado 1 i
q i 0.0

Clculo de la suma
for i 0 i
s i
p i
q i

Presentacin del resultado


printf
s 0
for i 1 i
printf
s i
printf

i
i

return 0
Aqu tienes un ejemplo de uso del programa con los polinomios p(x) = 5 + 3x + 5x 2
+ x 3
y q(x) = 4 4x 5x 2 + x 3 :

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81 Modifica el programa anterior para que no se muestren los coeficientes nulos.
82 Tras efectuar los cambios propuestos en el ejercicio anterior no aparecer nada
por pantalla cuando todos los valores del polinomio sean nulos. Modifica el prog
rama
para que, en tal caso, se muestre por pantalla
.
83 Tras efectuar los cambios propuestos en los ejercicios anteriores, el polinom
io
empieza con un molesto signo positivo cuando s0 es nulo. Corrige el programa par
a que
el primer trmino del polinomio no sea precedido por el carcter .
84 Cuando un coeficiente es negativo, por ejemplo 1, el rograma anterior muestra
su corres ondiente trmino en antalla as:
. Modifica el rograma ante

rior ara que un trmino con coeficiente negativo como el del ejem lo se muestre a

s:
.
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con ISBN:
C
9788469301432
8787
Introduccin a la rog
ramacin con C  UJI
c
UJI

...............................................................................
...
Nos queda lo ms difcil: el roducto de los dos olinomios. Lo almacenaremos en
un
vector llamado m. Como el roducto de dos olinomios de grado menor o igual que
n es un
olinomio de grado menor o igual que 2n, la talla del vector m no es
:

int main void


float

q
s

float m 2

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
85 Entiendes por qu hemos reservado 2
1 elementos para m y
no 2
?
...............................................................................
...
El coeficiente mi , para valores de i entre 0 y el grado mximo de m(x), es de
cir, entre
los enteros 0 y 2
2, se calcula as:

i
mi =

pj

qij .
j=0
Deberemos tener cuidado de no acceder errneamente a elementos de
rango de ndices vlidos.
Im lementemos ese clculo:
include
define

11

int main void


float

q
s

float m 2
int grado
int i j

Lectura de
do
rintf
1
scanf
grado
while grado 0
grado
for i 0 i grado i

o q fuera del

rintf

i scanf

for i grado 1 i
i 0.0

Lectura de q
do
rintf
1
scanf
grado
while grado 0
grado
for i 0 i grado i
rintf
i scanf

q i

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con ISBN:
C
9788469301432
88
88
ramacin con C  UJI

Introduccin a la rog

c
UJI

for i grado 1 i
q i 0.0

Clculo de la suma
for i 0 i
s i
i
q i

Presentacin del resultado


rintf
s 0
for i 1 i
rintf
s i
rintf

i
i

Clculo del roducto


for i 0 i 2
m i
0.0
for j 0 j i j
if j
m i
j
q i j

1 i
i j

Presentacin del resultado


rintf
m 0
for i 1 i 2
1 i
rintf
m i i
rintf
return 0
Observa que nos hubiera venido bien definir sendas funciones ara la lectura
y es
critura de los olinomios, ero al no saber definir funciones todava, hemos tenid
o que
co iar dos veces el fragmento de rograma corres ondiente.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
86 El programa que hemos diseado es ineficiente. Si, por ejemplo, trabajamos con
polinomios de grado 5, sigue operando con los coeficientes correspondientes a x
6 , x 7 ,. . . ,
x 10 , que son nulos. Modifica el programa para que, con la ayuda de variables e
nteras,
recuerde el grado de los polinomios p(x) y q(x) en sendas variables talla p y ta
lla q y
use esta informacin en los clculos de modo que se opere nicamente con los coeficien
tes
de los trminos de grado menor o igual que el grado del polinomio.
...............................................................................
...
Ahora que hemos presentado tres programas ilustrativos del uso de vectores
en C,
fjate en que:
El tamao de los vectores siempre se determina en tiempo de compilacin.
En un vector podemos almacenar una cantidad de elementos menor o igual
que la
declarada en su capacidad, nunca mayor.
Si almacenamos menos elementos de los que caben (como en el programa q

ue
efecta estadsticas de una serie de edades), necesitas alguna variable au
xiliar que
te permita saber en todo momento cuntas de las celdas contienen informa
cin. Si
aades un elemento, has de incrementar t mismo el valor de esa variable.
Ya sabes lo suficiente sobre vectores para poder hacer frente a estos ejer
cicios:
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
8989
c
UJI
Introduccin a la prog
ramacin con C - UJI

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87 Disea un programa que pida el valor de 10 nmeros enteros distintos y los
almacene en un vector. Si se da el caso, el programa advertir al usuario, tan pro
nto sea
posible, si introduce un nmero repetido y solicitar nuevamente el nmero hasta que s
ea
diferente de todos los anteriores. A continuacin, el programa mostrar los 10 nmeros
por pantalla.
88 En una estacin meteorolgica registramos la temperatura (en grados centgrados)
cada hora durante una semana. Almacenamos el resultado en un vector de 168 compo
nentes (que es el resultado del producto 7 24). Disea un programa que lea los dat
os
por teclado y muestre:
La mxima y mnima temperaturas de la semana.
La mxima y mnima temperaturas de cada da.
La temperatura media de la semana.
La temperatura media de cada da.
El nmero de das en los que la temperatura media fue superior a 30 grados
.
El da y hora en que se registr la mayor temperatura.
89 La cabecera
incluye la declaracin de funciones para generar nmero
s
aleatorios. La funcin rand, que no tiene parmetros, devuelve un entero positivo al
eatorio. Si deseas generar nmeros aleatorios entre 0 y un valor dado , puedes evalu
ar
rand
1 . Cuando ejecutas un programa que usa rand, la semilla del gener
ador de
nmeros aleatorios es siempre la misma, as que acabas obteniendo la misma secuencia
de nmeros aleatorios. Puedes cambiar la semilla del generador de nmeros aleatorios
pasndole a la funcin srand un nmero entero sin signo.
Usa el generador de nmeros aleatorios para inicializar un vector de 10 elemen
tos
con nmeros enteros entre 0 y 4. Muestra por pantalla el resultado. Detecta y mues
tra, a
continuacin, el tamao de la sucesin ms larga de nmeros consecutivos iguales.
(Ejemplo: si los nmeros generados son
, el tramo ms l
argo
formado por nmeros iguales es de talla 3 (los tres doses al final de la secuencia
), as
que por pantalla aparecer el valor 3.)
90
Modifica el ejercicio anterior para que trabaje con un vector de 100
elementos.
91 Genera un vector con 20 nmeros aleatorios entre 0 y 100 y muestra por pantalla
el vector resultante y la secuencia de nmeros crecientes consecutivos ms larga.
(Ejemplo: la secuencia
es la secuencia creciente ms
larga
en la serie de nmeros
.)
92 Escribe un programa C que ejecute 1000 veces el clculo de la longitud de la
secuencia ms larga sobre diferentes secuencias aleatorias (ver ejercicio anterior

) y que
muestre la longitud media y desviacin tpica de dichas secuencias.
93 Genera 100 nmeros aleatorios entre 0 y 1000 y almacnalos en un vector.
Determina a continuacin qu nmeros aparecen ms de una vez.
94 Genera 100 nmeros aleatorios entre 0 y 10 y almacnalos en un vector. Determina
a continuacin cul es el nmero que aparece ms veces.
95 Disea un programa C que almacene en un vector los 100 primeros nmeros
primos.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
90
90
ramacin con C - UJI

Introduccin a la prog

c
UJI

96 Disea un programa C que lea y almacene en un vector 10 nmeros enteros


asegurndose de que sean positivos. A continuacin, el programa pedir que se introduz
ca
una serie de nmeros enteros y nos dir si cada uno de ellos est o no en el vector. E
l
programa finaliza cuando el usuario introduce un nmero negativo.
97 Disea un programa C que lea y almacene en un vector 10 nmeros enteros
asegurndose de que sean positivos. A continuacin, el programa pedir que se introduz
ca
una serie de nmeros enteros y nos dir si cada uno de ellos est o no en el vector. E
l
programa finaliza cuando el usuario introduce un nmero negativo.
Debes ordenar por el mtodo de la burbuja el vector de 10 elementos tan pronto
se
conocen sus valores. Cuando debas averiguar si un nmero est o no en el vector, uti
liza
el algoritmo de bsqueda dicotmica.
...............................................................................
...

Es importante que conozcas bien cmo se disponen los vectores en memoria. Cuando s
e
encuentra esta declaracin en un programa:
int a 5
el compilador reserva una zona de memoria contigua capaz de albergar 5 valores d
e tipo
int. Como una variable de tipo int ocupa 4 bytes, el vector a ocupar 20 bytes.
Podemos comprobarlo con este programa:
include
define

int main void


int a
printf

sizeof

a 0
printf
return 0

sizeof a

El resultado de ejecutarlo es ste:

Cada byte de la memoria tiene una direccin. Si, pongamos por caso,
a
empieza en la direccin 1000, a 0 se almacena en los bytes 10001003, a
bytes 10041007, y as sucesivamente. El ltimo elemento, a 4 , ocupar
1019:
996:
1000:
1004:
1008:

el vector
1 en los
los bytes 1016
a[0]
a[1]
a[2]

1012:
1016:

a[3]
a[4]

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

91
91

Big-endian y little-endian
Lo bueno de los estndares es. . . que hay muchos donde elegir. No hay form
a de ponerse
de acuerdo. Muchos ordenadores almacenan los nmeros enteros de ms de 8 bits
disponiendo los bits ms significativos en la direccin de memoria ms baja y o
tros, en
la ms alta. Los primeros se dice que siguen la codificacin big-endian y los s
egundos,
little-endian.
Pongamos un ejemplo. El nmero 67586 se representa en binario con cuatr
o bytes:

Supongamos que ese valor se almacena en los cuatro bytes que empiezan en
la direccin
1000. En un ordenador big-endian, se dispondran en memoria as (te indicamos b
ajo
cada byte su direccin de memoria):

En un ordenador little-endian, por contra, se representara de esta otra


forma:

Los ordenadores PC (que usan microprocesadores Intel y AMD), por ejemplo,


son littleendian y los Macintosh basados en microprocesadores Motorola son big-endian.
Aunque nosotros trabajamos en clase con ordenadores Intel, te mostraremos
los valores
binarios como ests acostumbrado a verlos: con el byte ms significativo a la
izquierda.
La diferente codificacin de unas y otras plataformas plantea serios pr
oblemas a la
hora de intercambiar informacin en ficheros binarios, es decir, ficheros q
ue contienen
volcados de la informacin en memoria. Nos detendremos nuevamente sobre est
a cuestin
cuando estudiamos ficheros.
Por cierto, lo de little-endian y big-endian viene de Los viajes de Gulliv
er,
la novela de Johnathan Swift. En ella, los liliputienses debaten sobre un
a importante
cuestin poltica: deben abrirse los huevos pasados por agua por su extremo gr
ande,
como defiende el partido Big-Endian, o por su extremo puntiagudo, como ma
ntiene el
partido Little-Endian?

Recuerdas el operador que te presentamos en el captulo anterior? Es un operador


unario que permite conocer la direccin de memoria de una variable. Puedes aplicar
el
operador a un elemento del vector. Por ejemplo, a 2 es la direccin de memoria en
la que empieza a 2 , es decir, la direccin 1008 en el ejemplo.

Veamos qu direccin ocupa cada elemento de un vector cuando ejecutamos un programa sobre un computador real:
include
define

int main void


int a

for i 0 i
printf

i
i

unsigned int

a i
return 0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

92
92

Al ejecutar el programa obtenemos en pantalla lo siguiente (puede que obtengas u


n
resultado diferente si haces la prueba t mismo, pues el vector puede estar en un
lugar
cualquiera de la memoria):

Ves? Cada direccin de memoria de una celda de a se diferencia de la siguiente


en
4 unidades.
Recuerda que la funcin de lectura de datos por teclado scanf modifica el valo
r de
una variable cuya direccin de memoria se le suministra. Para depositar en la zona
de
memoria de la variable el nuevo valor necesita conocer la direccin de memoria. Po
r esa
razn precedamos los identificadores de las variables con el operador . Este progra
ma,
por ejemplo, lee por teclado el valor de todos los componentes de un vector util
izando el
operador para conocer la direccin de memoria de cada uno de ellos:
include
define

int main void


int a

for i 0 i
printf
i
scanf

i
a i

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
98 Qu problema presenta esta otra versin del mismo programa?
include
define

int main void


int a
for i 0 i
printf
i
scanf
return 0

i
i
a i

...............................................................................
...
Analiza este programa:
include
define

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
93
93
ramacin con C - UJI

Introduccin a la prog

c
UJI

int main void


int a

for i 0 i
printf
nsigned int a i

i
i

printf
return 0

unsigned int a

He aqu el resultado de ejecutarlo:

Observa que la direccin de memoria de las lneas primera y ltima es la misma. En


consecuencia, esta lnea:
printf
unsigned int
a 0
es equivalente a esta otra:
printf

unsigned int a

As pues, a expresa una direccin de memoria (la de su primer elemento), es decir, a


es
un puntero o referencia a memoria y es equivalente a a 0 . La caracterstica de qu
e
el identificador de un vector represente, a la vez, al vector y a un puntero que
apunta
donde empieza el vector recibe el nombre dualidad vector-puntero, y es un rasgo
propio
del lenguaje de programacin C.
Representaremos esquemticamente los vectores de modo similar a como representbamos las listas en Python:
0 1
2 3
4
a

0 0 0 0 0

Fjate en que el grfico pone claramente de manifiesto que a es un puntero, pues se


le
representa con una flecha que apunta a la zona de memoria en la que se almacenan
los
elementos del vector. Nos interesa disear programas con un nivel de abstraccin tal
que
la imagen conceptual que tengamos de los vectores se limite a la del diagrama.
Mentiremos cada vez menos
Lo cierto es que a no es exactamente un puntero, aunque funciona como tal
. Sera ms
justo representar la memoria as:
0 1 2
3 4
a 0 0 0 0 0
Pero, por el momento, conviene que consideres vlida la representacin en la
que a es
un puntero. Cuando estudiemos la gestin de memoria dinmica abundaremos en e

sta
cuestin.

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
9494
c
Introduccin a la programacin con C - UJI
UJI

Recuerda que el operador obtiene la direccin de memoria en la que se encuentr


a un
valor. En esta figura te ilustramos a 0 y a 2 como sendos punteros a sus respect
ivas
celdas en el vector.
&a[2]
0 1
2
3 4
a

0 0 0 0 0

&a[0]
Cmo encuentra C la direccin de memoria de un elemento del vector cuando accedemos a travs de un ndice? Muy sencillo, efectuando un clculo consistente en sumar
al puntero que seala el principio del vector el resultado de multiplicar el ndice
por
el tamao de un elemento del vector. La expresin a 2 , por ejemplo, se entiende com
o
accede al valor de tipo int que empieza en la direccin a con un desplazamiento de
2 4 bytes. Una sentencia de asignacin como a 2
0 se interpreta como almacena
el valor 0 en el entero int que empieza en la direccin de memoria de a ms 2 4 byte
s.

Aqu tienes un programa con un resultado que puede sorprenderte:


include
define

int main void


int v

for i 0 i
v i i
w i
10
printf
printf
printf
printf
i i
printf
printf
w 0 w
printf
w 1 w
printf
w 2 w
printf
printf
v 0 v
printf
v 1 v
printf
v 2 v

i
i

unsigned int
unsigned int
0
unsigned int
1
unsigned int
2
unsigned int
0
unsigned int
1
unsigned int
2

int
int
int
int
int
int

printf
printf
v
2 v 2
printf
v
3 v 3
printf
v
4 v 4
printf
w 5 w 5
printf
w
1 w 1
printf
v
5 v 5
printf

unsigned
unsigned
unsigned
unsigned
unsigned
unsigned

return 0
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

95
95

Introduccin a la programacin con C -UJI


cUJI

Aqu tienes el resultado de su ejecucin3 :

La salida es una tabla con tres columnas: en la primera se indica el objeto


que se
est estudiando, la segunda corresponde a la direccin de memoria de dicho objeto4 y
la tercera muestra el valor almacenado en dicho objeto. A la vista de las direcc
iones
de memoria de los objetos i, v 0 , v 1 , v 2 , w 0 , w 1 y w 2 , el compilador h
a
reservado la memoria de estas variables as:
3221222636:
3221222640:
3221222644:
3221222648:
3221222652:
3221222656:
3221222660:
3221222664:

3
10
11
12

i
w[0]
w[1]
w[2]

0
1
2

v[0]
v[1]
v[2]

Fjate en que las seis ltimas filas de la tabla corresponden a accesos a v y w


con
ndices fuera de rango. Cuando tratbamos de acceder a un elemento inexistente en un
a
lista Python, el intrprete generaba un error de tipo (error de ndice). Ante una si
tuacin
similar, C no detecta error alguno. Qu hace, pues? Aplica la frmula de indexacin, si
n
ms. Estudiemos con calma el primer caso extrao: v 2 . C lo interpreta como: acceder
al valor almacenado en la direccin que resulta de sumar 3221222656 (que es donde
empieza el vector v) a (2) 4 (2 es el ndice del vector y 4 es tamao de un int).
Haz el clculo: el resultado es 3221222648. . . la misma direccin de memoria que ocu
a
el valor de w 2 ! Esa es la razn de que se muestre el valor 12. En la ejecucin del
3
Nuevamente, una advertencia: uede que obtengas un resultado diferente al
ejecutar el rograma en
tu ordenador. La asignacin de direcciones de memoria a cada objeto de un rograma
es una decisin que
ado ta el com ilador con cierta libertad.
4
Si ejecutas el rograma en tu ordenador, es robable que obtengas valores
distintos ara las direcciones
de memoria. Es normal: en cada ordenador y con cada ejecucin se uede reservar un
a zona de memoria
distinta ara los datos.
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C  UJI

96
96

UJI
c

rograma, v 2 y w 2 son exactamente lo mismo. Encuentra t mismo una ex licacin


ara los restantes accesos ilcitos.
Ojo! Que se ueda hacer no significa que sea aconsejable hacerlo. En absoluto
. Es
ms: debes evitar acceder a elementos con ndices de vector fuera de rango. Si no co
nviene
hacer algo as, or qu no com rueba C si el ndice est en el rango correcto antes de
acceder a los elementos y, en caso contrario, nos seala un error? Por eficiencia.
Un ro
grama que maneje vectores acceder a sus elementos, muy robablemente, en numerosa
s
ocasiones. Si se ha de com robar si el ndice est en el rango de valores vlidos, cad
a
acceso se enalizar con un ar de com araciones y el rograma se ejecutar ms lenta
mente. C sacrifica seguridad or velocidad, de ah que tenga cierta fama (justific
adsima)
de lenguaje eligroso.

Este rograma retende co iar un vector en otro, ero es incorrecto:


E
E
define
10
int main void
int original
int co ia
co ia

1 2 3 4 5 6 7 8 9 10

original

return 0
Si com ilas el rograma, obtendrs un error en la lnea 8 que te im edir obtener un
ejecutable:
. El mensaje de error nos indi
ca que
no es osible efectuar asignaciones entre ti os vectoriales.
Nuestra intencin era que antes de ejecutar la lnea 8, la memoria resentara es
te
as ecto:
0 1 2 3 4
5
6 7 8 9
original
5

1 2 3 4 5 6 7 8 9 10
0 1 2 3 4

9
co ia

y, una vez ejecutada la lnea 8 llegar a una de estas dos situaciones:


1.
obtener en co ia una co ia del contenido de original:
0 1
4
5 6 7 8 9
original

1 2 3 4 5 6 7

8 9 10
0
4

co ia

1 2 3 4 5 6 7

8 9 10
2.
al:

o conseguir que, como en Python, co ia a unte al mismo lugar que origin


0

9
original

1 2 3 4 5 6 7

8 9 10
0
4

9
co ia

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
97
97

Introduccin a la rogramacin con C UJI


cUJI

Violacin de segmento
Los errores de acceso a zonas de memoria no reservada se cuentan entre los
eores. En
el ejem lo, hemos accedido a la zona de memoria de un vector salindonos del
rango
de indexacin vlido de otro, lo cual ha roducido resultados desconcertantes.
Pero odra habernos ido an eor: si tratas de escribir en una zona de me
moria
que no ertenece a ninguna de tus variables, cosa que uedes hacer asignan
do un valor
a un elemento de vector fuera de rango, es osible que se genere una exce
cin du
rante la ejecucin del rograma: intentar escribir en una zona de memoria qu
e no ha
sido asignada a nuestro roceso dis ara, en Unix, una seal de violacin de seg
men
to (segmentation violation) que rovoca la inmediata finalizacin de la ejecu
cin del
rograma. Fjate en este rograma:
E

include
int main void
int a 10
a 10000

return 0
Cuando lo ejecutamos en un ordenador bajo Unix, obtenemos este mensaje
antalla:

or

El rograma ha finalizado abru tamente al ejecutar la asignacin de la


lnea 7.
Estos errores en la gestin de memoria se manifiestan de formas muy var
ue
den roducir resultados extraos, finalizar la ejecucin incorrectamente o inc
luso blo
quear al com utador. Bloquear al com utador? S, en sistemas o erativos oco
robustos,
como Microsoft Windows, el ordenador uede quedarse bloqueado. (Probableme
nte has
ex erimentado la sensacin usando algunos rogramas comerciales en el entorn
o Mi
crosoft Windows.) Ello se debe a que ciertas zonas de memoria deberan estar
fuera del
alcance de los rogramas de usuario y el sistema o erativo debera rohibir
accesos
ilcitos. Unix mata al roceso que intenta efectuar accesos ilcitos (de ah que
terminen
con mensajes como Violacin de segmento). Microsoft Windows no tiene la recau
cin
de rotegerlas, as que las consecuencias son mucho eores.
Pero casi lo eor es que tu rograma uede funcionar mal en unas ocas
iones y bien
iadas:

en otras. El hecho de que el rograma ueda funcionar mal algunas veces


bien el
resto es eligrossimo: como los errores ueden no manifestarse durante
esarrollo
del rograma, cabe la osibilidad de que no los detectes. Nada eor que
r or bueno
un rograma que, en realidad, es incorrecto.
Tenlo siem re resente: la gestin de vectores obliga a estar siem
endiente de
no rebasar la zona de memoria reservada.

y
el d
da
re

Pero no ocurre ninguna de las dos cosas: el identificador de un vector esttico se


considera
un untero inmutable. Siem re a unta a la misma direccin de memoria. No uedes
asignar un vector a otro orque eso significara cambiar el valor de su direccin. (
Observa,
adems, que en el segundo caso, la memoria asignada a co ia quedara sin untero que
la referenciara.)
Si quieres co iar el contenido de un vector en otro debes hacerlo elemento a
elemento:

define

10

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

98
98

int main void


int original
int co ia
int i
for i 0 i
co ia i

1 2 3 4 5 6 7 8 9 10

i
original i

return 0

En Python odamos com arar listas. Por ejem lo, 1 2 3


1 1 1 3 devolva Tru
e.
Ya lo habrs adivinado: C no ermite com arar vectores. Efectivamente.
Si quieres com arar dos vectores, has de hacerlo elemento a elemento:
define

int main void


int original
int co ia
int i son iguales
son iguales 1

1 2 3
1 1 1 3
Su onemos que todos los elementos son iguales dos a

dos.
i 0
while i
son iguales
if co ia i
original i
Pero basta con que dos elementos no
sean iguales...
son iguales 0
... ara que los vectores sean distinto
s.
i
if son iguales
rintf
else
rintf
return 0

Las cadenas son un ti o de datos bsico en Python, ero no en C. Las cadenas de C


son vectores de caracteres (elementos de ti o char) con una eculiaridad: el tex
to de la
cadena termina siem re en un carcter nulo. El carcter nulo tiene cdigo ASCII 0 y
odemos re resentarlo tanto con el entero 0 como con el carcter
(recuerda
que
es una forma de escribir el valor entero 0). Ojo! No confundas
con
: el
rimero vale 0 y el segundo vale 48.
Las cadenas estticas en C son, a diferencia de las cadenas Python, mutables.
Eso

significa que uedes modificar el contenido de una cadena durante la ejecucin de


un
rograma.

Introduccin a la rogramacin con C


Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
cUJI
Introduccin a la rogramacin con C UJI

99
99

Las cadenas se declaran como vectores de caracteres, as que debes ro orcionar el


nmero mximo de caracteres que es ca az de almacenar: su ca acidad. Esta cadena, o
r
ejem lo, se declara con ca acidad ara almacenar 10 caracteres:
char a 10
Puedes inicializar la cadena con un valor en el momento de su declaracin:
char a 10
Hemos declarado a como un vector de 10 caracteres y lo hemos inicializado asignnd
ole la
cadena
. Fjate: hemos almacenado en a una cadena de menos de 10 caracter
es.
No hay roblema: la longitud de la cadena almacenada en a es menor que la ca aci
dad
de a.

A sim le vista,
ocu a 6 bytes, ues contamos en ella 6 caracteres,
ero no es
as. En realidad,
ocu a 7 bytes: los 6 que corres onden a los 6 caract
eres que
ves ms uno corres ondiente a un carcter nulo al final, que se denomina terminador
de
cadena y es invisible.
Al declarar e inicializar una cadena as:
char a 10
la memoria queda de este modo:
0
6

9
a

c a d e n a \0

Es decir, es como si hubisemos inicializado la cadena de este otro modo equivalen


te:
char a 10
Recuerda, ues, que hay dos valores relacionados con el tamao de una cadena
:
su ca acidad, que es la talla del vector de caracteres;
su longitud, que es el nmero de caracteres que contiene, sin contar el t
erminador
de la cadena. La longitud de la cadena debe ser siem re estrictamente m
enor que
la ca acidad del vector ara no desbordar la memoria reservada.
Y or qu toda esta com licacin del terminador de cadena? Lo normal al trabajar
con una variable de ti o cadena es que su longitud vare conforme evoluciona la ej
ecucin
del rograma, ero el tamao de un vector es fijo. Por ejem lo, si ahora tenemos e
n a el
texto
y ms tarde decidimos guardar en ella el texto
, que t
iene un
carcter menos, estaremos asando de esta situacin:
0
1 2 3 4 5

9
a

c a d e n a \0

a esta otra:
0
6

t e x t o \0

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
100
100

Introduccin a la rogramacin con C UJI


cUJI

Una cadena de longitud uno no es un carcter


Hemos dicho en el ca tulo anterior que una cadena de un slo carcter, or eje
m lo
, no es lo mismo que un carcter, or ejem lo
. Ahora uedes saber
or qu: la
diferencia estriba en que
ocu a dos bytes, el que corres onde al carc
ter
y el
que corres onde al carcter nulo
, mientras que
ocu a un solo by
te.
Fjate en esta declaracin de variables:
char a
char b 2
He aqu una re resentacin grfica de las variables y su contenido:
a y
0
b

y \0

Recuerda:
Las comillas sim les definen un carcter y un carcter ocu a un sol
o byte.
Las comillas dobles definen una cadena. Toda cadena incluye un
carcter nulo
invisible al final.

Fjate en que la zona de memoria asignada a a sigue siendo la misma. El truco del
terminador ha ermitido que la cadena decrezca. Podemos conseguir tambin que crez
ca
a voluntad. . . ero siem re que no se rebase la ca acidad del vector.
Hemos re resentado las celdas a la derecha del terminador como cajas vacas,
ero
no es cierto que lo estn. Lo normal es que contengan valores arbitrarios, aunque
eso no
im orta mucho: el convenio de que la cadena termina en el rimer carcter nulo hac
e que
el resto de caracteres no se tenga en cuenta. Es osible que, en el ejem lo ante
rior, la
memoria resente realmente este as ecto:
0
1 2 3 4 5
6
7 8
9
a

t e x t o \0 a u \0 x

Por comodidad re resentaremos las celdas a la derecha del terminador con cajas v
acas,
ues no im orta en absoluto lo que contienen.
Qu ocurre si intentamos inicializar una zona de memoria reservada ara slo 10
!
chars con una cadena de longitud mayor que 9?
char a 10
Mal!

Estaremos cometiendo un gravsimo error de rogramacin que, osiblemente, no detect


e
el com ilador. Los caracteres que no caben en a se escriben en la zona de memori
a que
sigue a la zona ocu ada or a.
0
a

s u

e r c a l i f r a g i l i s t i c o e s

i a l i d o s o \0

Ya vimos en un a artado anterior las osibles consecuencias de ocu ar memoria qu


e no
nos ha sido reservada: uede que modifiques el contenido de otras variables o qu
e trates
de escribir en una zona que te est vetada, con el consiguiente aborto de la ejecu
cin
del rograma.
Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
101
101

Introduccin a la rogramacin con C  UJI


UJI
c

Como resulta que en una variable con ca acidad ara, or ejem lo, 80 caracte
res
slo caben realmente 79 caracteres a arte del nulo, ado taremos una curiosa rctica
al
declarar variables de cadena que nos ermitir almacenar los 80 caracteres (adems d
el
nulo) sin crear una constante confusin con res ecto al nmero de caracteres que cab
en
en ellas:
include
define

80

int main void


char cadena
s: 80 caracteres ms el terminador

Reservamos 81 caractere

return 0

Las cadenas se muestran con rintf y la adecuada marca de formato sin que se re
senten
dificultades es eciales. Lo que s resulta roblemtico es leer cadenas. La funcin sc
anf
resenta una seria limitacin: slo uede leer alabras, no frases. Ello nos obligar
a resentar una nueva funcin (gets). . . que se lleva fatal con scanf .
Salida con rintf
Em ecemos or considerar la funcin rintf , que muestra cadenas con la marca de f
ormato
. Aqu tienes un ejem lo de uso:
include
define

80

int main void


char cadena

rintf

cadena

return 0
Al ejecutar el rograma obtienes en antalla esto:

Puedes alterar la resentacin de la cadena con modificadores:


include
define

80

int main void


char cadena

rintf

cadena

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C  UJI
UJI
c

102
102

rintf
rintf

cadena
cadena

return 0

Y si deseamos mostrar una cadena carcter a carcter? Podemos hacerlo llamando a


rintf sobre cada uno de los caracteres, ero recuerda que la marca de formato a
sociada
a un carcter es :
include
define

80

int main void


char cadena
int i

i 0
while cadena i
rintf
i

cadena i

return 0
Este es el resultado de la ejecucin:

Entrada con scanf


Poco ms hay que contar acerca de rintf . La funcin scanf es un reto mayor. He aqu
un ejem lo que retende leer e im rimir una cadena en la que odemos guardar has
ta
80 caracteres (sin contar el terminador nulo):
include
define

80

int main void


Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
ntroduccin a la rogramacin con C  UJI
UJI
c

103
103

char cadena
scanf
rintf

1
cadena
caden

a
return 0
Ojo! No hemos uesto el o erador delante de cadena! Es un error? No. Con las
cadenas no hay que oner el carcter del identificador al usar scanf . Por qu? Porqu
e
scanf es era una direccin de memoria y el identificador, or la dualidad vector
untero,
es una direccin de memoria!
Recuerda: cadena 0 es un char, ero cadena, sin ms, es la direccin de memoria
en la que em ieza el vector de caracteres.
Ejecutemos el rograma e introduzcamos una alabra:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
99 Es vlida esta otra forma de leer una cadena? Prubala en tu ordenador.
include
define

80

int main void


char cadena
scanf
printf

1
cadena 0
cad

ena
return 0
...............................................................................
...
Cuando scanf recibe el valor asociado a cadena, recibe una direccin de memori
a y,
a partir de ella, deja los caracteres ledos de teclado. Debes tener en cuenta que
si los
caracteres ledos exceden la capacidad de la cadena, se producir un error de ejecuc
in.
Y por qu printf no muestra por pantalla una simple direccin de memoria cuando
ejecutamos la llamada printf
cadena ? Si e
s cierto
lo dicho, cadena es una direccin de memoria. La explicacin es que la marca
es
interpretada por printf como me pasan una direccin de memoria en la que empieza
una cadena, as que he de mostrar su contenido carcter a carcter hasta encontrar un
carcter nulo.
Lectura con gets
Hay un problema prctico con scanf : slo lee una palabra, es decir, una secuencia de
caracteres no blancos. Hagamos la prueba:

E
E
include
define

80

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
104
104
ramacin con C - UJI

Introduccin a la prog

c
UJI

int main void


char cadena
scanf
printf

1
cadena
cadena

return 0
Si al ejecutar el programa tecleamos un par de palabras, slo se muestra la primer
a:

Qu ha ocurrido con los restantes caracteres tecleados? Estn a la espera de ser


ledos! La siguiente cadena leda, si hubiera un nuevo scanf , sera
. Si es lo
que
queramos, perfecto, pero si no, el desastre puede ser maysculo.
Cmo leer, pues, una frase completa? No hay forma sencilla de hacerlo con scanf
.
Tendremos que recurrir a una funcin diferente. La funcin gets lee todos los caract
eres
que hay hasta encontrar un salto de lnea. Dichos caracteres, excepto el salto de
lnea,
se almacenan a partir de la direccin de memoria que se indique como argumento y s
e
aade un terminador.
Aqu tienes un ejemplo:
include
define

11

int main void


char a
printf
printf
printf

1
gets a
gets b

a b
return 0
Ejecutemos el programa:

Lectura de cadenas y escalares: gets y sscanf


Y ahora, vamos con un problema al que te enfrentars en ms de una ocasin: la lectura
alterna de cadenas y valores escalares. La mezcla de llamadas a scanf y a gets,
produce
efectos curiosos que se derivan de la combinacin de su diferente comportamiento f
rente
a los blancos. El resultado suele ser una lectura incorrecta de los datos o incl

uso el
bloqueo de la ejecucin del programa. Los detalles son bastante escabrosos. Si tie
nes
curiosidad, te los mostramos en el apartado B.3.
Presentaremos en este captulo una solucin directa que debers aplicar siempre qu
e
tu programa alterne la lectura de cadenas con blancos y valores escalares (algo
muy
frecuente). La solucin consiste en:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

105
105

Overflow exploit
El manejo de cadenas C es complicado. . . y peligroso. La posibilidad de
que se almacenen ms caracteres de los que caben en una zona de memoria reservada para
una
cadena ha dado lugar a una tcnica de cracking muy comn: el overflow exploit
(que
significa aprovechamiento del desbordamiento), tambin conocido por smash the
stack
(machacar la pila).
Si un programa C lee una cadena con scanf o gets es vulnerable a este
tipo de
ataques. La idea bsica es la siguiente. Si c es una variable local a una f
uncin (en
el siguiente captulo veremos cmo), reside en una zona de memoria especial:
la pila.
Podemos desbordar la zona de memoria reservada para la cadena c escribien
do un texto
ms largo del que cabe en ella. Cuando eso ocurre, estamos ocupando memoria
en una
zona de la pila que no nos pertenece. Podemos conseguir as escribir informac
in en
una zona de la pila reservada a informacin como la direccin de retorno de l
a funcin.
El exploit se basa en asignar a la direccin de retorno el valor de una dir
eccin en
la que habremos escrito una rutina especial en cdigo mquina. Y cmo conseguimo
s
introducir una rutina en cdigo mquina en un programa ajeno? En la propia cad
ena
que provoca el desbordamiento, codificndola en binario! La rutina de cdigo
mquina
suele ser sencilla: efecta una simple llamada al sistema operativo para qu
e ejecute un
intrprete de rdenes Unix. El intrprete se ejecutar con los mismos permisos qu
e el
programa que hemos reventado. Si el programa atacado se ejecutaba con per
misos de
, habremos conseguido ejecutar un intrprete de rdenes como
. El
ordenador
es nuestro!
Y cmo podemos proteger a nuestros programas de los overflow exploit? Pu
es,
para empezar, no utilizando nunca scanf o gets directamente. Como es posi
ble leer
de teclado carcter a carcter (lo veremos en el captulo dedicado a ficheros),
podemos
definir nuestra propia funcin de lectura de cadenas: una funcin de lectura
que controle
que nunca se escribe en una zona de memoria ms informacin de la que cabe.
Dado que gets es tan vulnerable a los overflow exploit, el compilador
de C te dar
un aviso cuando la uses. No te sorprendas, pues, cuando veas un mensaje c
omo ste:

Si vas a leer una cadena usar gets.

Y si vas a leer un valor escalar, proceder en dos pasos:


leer una lnea completa con gets (usa una variable auxiliar para ell
o),
y extraer de ella los valores escalares que se deseaba leer con ay
uda de la
funcin sscanf .
La funcin sscanf es similar a scanf (fjate en la s inicial), pero no obtiene informa
cin
leyndola del teclado, sino que la extrae de una cadena.
Un ejemplo ayudar a entender el procedimiento:
include
define
define

80
40

int main void


int a b
char frase
char linea

1
1

printf
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

106
106

gets linea

sscanf linea

sscanf linea

printf
gets frase
printf
gets linea
printf
printf

a b
frase

return 0
En el programa hemos definido una variable auxiliar, linea, que es una caden
a con
capacidad para 80 caracteres ms el terminador (puede resultar conveniente reserva
r ms
memoria para ella en segn qu aplicacin). Cada vez que deseamos leer un valor escala
r,
leemos en linea un texto que introduce el usuario y obtenemos el valor escalar c
on la
funcin sscanf . Dicha funcin recibe, como primer argumento, la cadena en linea; co
mo
segundo, una cadena con marcas de formato; y como tercer parmetro, la direccin de
la
variable escalar en la que queremos depositar el resultado de la lectura.
Es un proceso un tanto incmodo, pero al que tenemos que acostumbrarnos. . . d
e
momento.

Este programa, que pretende copiar una cadena en otra, parece correcto, pero no
lo es:
define
10
int main void
char original
char copia
copia

1
1

original

return 0
Si compilas el programa, obtendrs un error que te impedir obtener un ejecutable. R
ecuerda: los identificadores de vectores estticos se consideran punteros inmutable
s y, a
fin de cuentas, las cadenas son vectores estticos (ms adelante aprenderemos a usar
vectores dinmicos). Para efectuar una copia de una cadena, has de hacerlo carcter
a
carcter.
define
10
int main void
char original

char copia
int i

for i 0 i
i
copia i original i
return 0
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

107
107

Fjate en que el bucle recorre los 10 caracteres que realmente hay en original per
o, de
hecho, slo necesitas copiar los caracteres que hay hasta el terminador, incluyndol
e a
l.
define
10
int main void
char original
char copia
int i

1
1

for i 0 i
i
copia i
original i
if copia i
break
return 0
0
3

9
original

c a d e n a \0
0
1
2

copia

c a d e n a \0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
100 Qu problema presenta esta otra versin del mismo programa?
define
10
int main void
char original
char copia
int i
for i 0 i
if copia i
break
else
copia i

1
1
i

original i

return 0
...............................................................................
...
An podemos hacerlo mejor:
define
10
int main void
char original

char copia
int i

for i

0 original i

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
108
108
ramacin con C - UJI

Introduccin a la prog

c
UJI

copia i
copia i

original i

return 0
Ves? La condicin del for controla si hemos llegado al terminador o no. Como el
terminador no llega a copiarse, lo aadimos tan pronto finaliza el bucle. Este tip
o de
bucles, aunque perfectamente legales, pueden resultar desconcertantes.
El copiado de cadenas es una accin frecuente, as que hay funciones predefinida
s
para ello, accesibles incluyendo la cabecera
:
include
define

10

int main void


char original
char copia

1
1

strcpy copia original


iginal en copia.

Copia el contenido de or

return 0
Ten cuidado: strcpy (abreviatura de string copy) no comprueba si el destino de la
copia
tiene capacidad suficiente para la cadena, as que puede provocar un desbordamient
o. La
funcin strcpy se limita a copiar carcter a carcter hasta llegar a un carcter nulo.
Tampoco est permitido asignar un literal de cadena a un vector de caracteres
fuera
de la zona de declaracin de variables. Es decir, este programa es incorrecto:
define
10
int main void
char a

!Mal!

return 0
Si deseas asignar un literal de cadena, tendrs que hacerlo con la ayuda de strcpy
:
include
define

10

int main void


char a
strcpy a
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
101 Disea un programa que lea una cadena y copie en otra una versin encriptada.
La encriptacin convertir cada letra (del alfabeto ingls) en la que le sigue en la t
abla
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
109
109
ramacin con C - UJI

Introduccin a la prog

c
UJI

Copias (ms) seguras


Hemos dicho que strcpy presenta un fallo de seguridad: no comprueba si el
destino es
capaz de albergar todos los caracteres de la cadena original. Si quieres a
segurarte de
no rebasar la capacidad del vector destino puedes usar strncpy, una versin
de strcpy
que copia la cadena, pero con un lmite al nmero mximo de caracteres:
include
define

10

int main void


char original
char copia
mo,

1
1

strncpy copia original


1 caracteres.

Copia, a lo su

return 0
Pero tampoco strncpy es perfecta. Si la cadena original tiene ms caracteres
de los que
puede almacenar la cadena destino, la copia es imperfecta: no acabar en
. De
todos modos, puedes encargarte t mismo de terminar la cadena en el ltimo carc
ter,
por si acaso:
include
define

10

int main void


char original
char copia
strncpy copia original
copia

1
1
1

return 0

ASCII (excepto en el caso de las letras z y Z, que sern sustituidas por a y A,


respectivamente.) No uses la funcin strcpy.
102 Disea un programa que lea una cadena que posiblemente contenga letras maysculas y copie en otra una versin de la misma cuyas letras sean todas minsculas. N
o
uses la funcin strcpy.
103 Disea un programa que lea una cadena que posiblemente contenga letras maysculas y copie en otra una versin de la misma cuyas letras sean todas minsculas.
Usa la funcin strcpy para obtener un duplicado de la cadena y, despus, recorre la
copia
para ir sustituyendo en ella las letras maysculas por sus correspondientes minscul

as.
...............................................................................
...

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

110
110

El convenio de terminar una cadena con el carcter nulo permite conocer fcilmente l
a
longitud de una cadena:
include
define

80

int main void


char a
int i

printf
gets a
i 0
while a i
i
printf

return 0
Calcular la longitud de una cadena es una operacin frecuentemente utilizada, a
s
que est predefinida en la biblioteca de tratamiento de cadenas. Si inclumos la cab
ecera
, podemos usar la funcin strlen (abreviatura de string length):
include
include
define

80

int main void


char a
int l

printf
gets a
l strlen a
printf

return 0
Has de ser consciente de qu hace strlen: lo mismo que haca el primer programa,
es
decir, recorrer la cadena de izquierda a derecha incrementando un contador hasta
llegar al
terminador nulo. Esto implica que tarde tanto ms cuanto ms larga sea la cadena. Ha
s de
estar al tanto, pues, de la fuente de ineficiencia que puede suponer utilizar di
rectamente
strlen en lugares crticos como los bucles. Por ejemplo, esta funcin cuenta las voc
ales
minsculas de una cadena leda por teclado:
include
include
define

80

int main void


char a

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

111
111

El estilo C
El programa que hemos presentado para calcular la longitud de una cadena e
s un
programa C correcto, pero no es as como un programador C expresara esa misma
idea.
No hace falta que el bucle incluya sentencia alguna!:
include
define

80

int main void


char a
int i

printf
gets a
i 0
while a i
y sentencia alguna en el while.
printf

Observa que no ha
i 1

return 0
El operador de postincremento permite aumentar en uno el valor de i justo
despus de
consultar el valor de a i . Eso s, hemos tenido que modificar el valor most
rado como
longitud, pues ahora i acaba valiendo uno ms.
Es ms, ni siquiera es necesario efectuar comparacin alguna. El bucle se p
uede
sustituir por este otro:
i 0
while a i
El bucle funciona correctamente porque el valor
significa falso cua
ndo se interpreta como valor lgico. El bucle itera, pues, hasta llegar a un valor falso
, es decir, a un
terminador.

Algunos problemas con el operador de autoincremento


Qu esperamos que resulte de ejecutar esta sentencia?
int a 5
i 1
a i

0 0 0 0 0
i

Hay dos posibles interpretaciones:


Se evala primero la parte derecha de la asignacin, as que i pasa a
valer 2 y
se asigna ese valor en a 2 .
Se evala primero la asignacin, con lo que se asigna el valor 1 en
a 1 y,

despus, se incrementa el valor de i, que pasa a valer 2.


Qu hace C? No se sabe. La especificacin del lenguaje estndar indica que el re
sultado
est indefinido. Cada compilador elige qu hacer, as que ese tipo de sentencia
s pueden
dar problemas de portabilidad. Conviene, pues, evitarlas.

int i contador
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2

112
112

Introduccin a la programacin con C -UJI


cUJI

while o for
Los bucles while pueden sustituirse muchas veces por bucles for equivalen
tes, bastante
ms compactos:
include
define

80

int main void


char a
int i

printf
gets a
for i 0 a i
ncia alguna en el for.
printf

Tampoco hay sente


i

return 0
Tambin aqu es superflua la comparacin:
for i 0 a i

Todas las versiones del programa que hemos presentado son equivalentes
. Escoger
una u otra es cuestin de estilo.

a i

printf
gets a
contador 0
for i 0 i strlen a i
if a i
a i
a i
contador
printf

a i
contador

return 0
Pero tiene un problema de eficiencia. Con cada iteracin del bucle for se llama a
strlen
y strlen tarda un tiempo proporcional a la longitud de la cadena. Si la cadena t
iene,
pongamos, 60 caracteres, se llamar a strlen 60 veces para efectuar la comparacin,
y
para cada llamada, strlen tardar unos 60 pasos en devolver lo mismo: el valor 60.
Esta
nueva versin del mismo programa no presenta ese inconveniente:
include
include
define

80

int main void


char a
1
int i longitud contador
printf
gets a
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

113
113

Introduccin a la programacin con C -UJI


cUJI

longitud strlen cadena


contador 0
for i 0 i longitud i
if a i
a i
a i
contador
printf

a i
a i
con

tador
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
104 Disea un programa que lea una cadena y la invierta.
105 Disea un programa que lea una palabra y determine si es o no es palndromo.
106 Disea un programa que lea una frase y determine si es o no es palndromo.
Recuerda que los espacios en blanco y los signos de puntuacin no se deben tener e
n
cuenta a la hora de determinar si la frase es palndromo.
107 Escribe un programa C que lea dos cadenas y muestre el ndice del carcter de
la primera cadena en el que empieza, por primera vez, la segunda cadena. Si la s
egunda
cadena no est contenida en la primera, el programa nos lo har saber.
(Ejemplo: si la primera cadena es
y la segu
nda es
, el programa mostrar el valor 3.)
108 Escribe un programa C que lea dos cadenas y muestre el ndice del carcter de
la primera cadena en el que empieza por ltima vez una aparicin de la segunda caden
a.
Si la segunda cadena no est contenida en la primera, el programa nos lo har saber.
(Ejemplo: si la primera cadena es
y la segu
nda es
, el programa mostrar el valor 16.)
109 Escribe un programa que lea una lnea y haga una copia de ella eliminando los
espacios en blanco que haya al principio y al final de la misma.
110 Escribe un programa que lea repetidamente lneas con el nombre completo de
una persona. Para cada persona, guardar temporalmente en una cadena sus iniciales
(las
letras con maysculas) separadas por puntos y espacios en blanco y mostrar el resul
tado
en pantalla. El programa finalizar cuando el usuario escriba una lnea en blanco.
111 Disea un programa C que lea un entero n y una cadena a y muestre por pantalla
el valor (en base 10) de la cadena a si se interpreta como un nmero en base n. El
valor
de n debe estar comprendido entre 2 y 16. Si la cadena a contiene un carcter que
no
corresponde a un dgito en base n, notificar el error y no efectuar clculo alguno.
Ejemplos:
si a es

y n es 16, se mostrar el valor 255;

si a es
;

y n es 15, se notificar un error:

si a es

y n es 2, se mostrar el valor 15.

112 Disea un programa C que lea una lnea y muestre por pantalla el nmero de
palabras que hay en ella.

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

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
114
114
ramacin con C - UJI

Introduccin a la prog

c
UJI

Python permita concatenar cadenas con el operador . En C no puedes usar para


concatenar cadenas. Una posibilidad es que las concatenes t mismo a mano, con bucle
s.
Este programa, por ejemplo, pide dos cadenas y concatena la segunda a la primera
:
include
define

80

int main void


char a
1
int longa longb
int i

printf
gets a
printf
gets b
longa strlen a
longb strlen b
for i 0 i longb i
a longa i b i
a longa longb
printf

return 0
Pero es mejor usar la funcin de librera strcat (por string concatenate):
include
include
define

80

int main void


char a

printf
gets a
printf
gets b
strcat a b

Equivale a la asignacin Python a

b
printf

return 0
Si quieres dejar el resultado de la concatenacin en una variable distinta, de
bers
actuar en dos pasos:
include
include
define
int main void

80

char a

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

115
115

printf
gets a
printf
gets b
strcpy c a
strcat c b

sta seguida de...


... sta equivale a la sentencia Python c
a

printf

return 0
Recuerda que es responsabilidad del programador asegurarse de que la cadena
que
recibe la concatenacin dispone de capacidad suficiente para almacenar la cadena r
esultante.
Por cierto, el operador de repeticin de cadenas que encontrbamos en Python (op
erador ) no est disponible en C ni hay funcin predefinida que lo proporcione.
Un carcter no es una cadena
Un error frecuente es intentar aadir un carcter a una cadena con strcat o a
signrselo
como nico carcter con strcpy:
char linea 10
char caracter
!
!
strcat linea caracter
strcpy linea

Mal!
Mal!

Recuerda: los dos datos de strcat y strcpy han de ser cadenas y no es ace
ptable que
uno de ellos sea un carcter.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
113 Escribe un programa C que lea el nombre y los dos apellidos de una persona e
n
tres cadenas. A continuacin, el programa formar una sla cadena en la que aparezcan
el nombre y los apellidos separados por espacios en blanco.
114 Escribe un programa C que lea un verbo regular de la primera conjugacin y
lo muestre por pantalla conjugado en presente de indicativo. Por ejemplo, si lee
el texto
, mostrar por pantalla:

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

Tampoco los operadores de comparacin ( , , , , , ) funcionan con cadenas.

Existe, no obstante, una funcin de


que permite paliar esta carencia
de C:
strcmp (abreviatura de string comparison). La funcin strcmp recibe dos cadenas, a y
b,
y devuelve un entero. El entero que resulta de efectuar la llamada strcmp a b co
difica
el resultado de la comparacin:
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
116
116
c
UJI
Introduccin a la prog
ramacin con C - UJI

es menor que cero si la cadena a es menor que b,


es 0 si la cadena a es igual que b, y
es mayor que cero si la cadena a es mayor que b.
Naturalmente, menor significa que va delante en orden alfabtico, y mayor que va d
etrs.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115 Disea un programa C que lea dos cadenas y, si la primera es menor o igual
que la segunda, imprima el texto menor o igual.
116 Qu valor devolver la llamada strcmp
?
117 Escribe un programa que lea dos cadenas, a y b (con capacidad para 80 caracteres), y muestre por pantalla 1 si a es menor que b, 0 si a es igual que b, y 1
si a es
mayor que b. Est rohibido que utilices la funcin strcm .
...............................................................................
...

No slo
contiene funciones tiles ara el tratamiento de cadenas. En
encontrars unas funciones que ermiten hacer cmodamente reguntas acerca de los
caracteres, como si son maysculas, minsculas, dgitos, etc:
isalnum carcter : devuelve cierto (un entero cualquiera distinto de cer
o) si carcter
es una letra o dgito, y falso (el valor entero 0) en caso contrario,
isal ha carcter : devuelve cierto si carcter es una letra, y falso en ca
so contrario,
n tabu

isblank carcter : devuelve cierto si carcter es un es acio en blanco o u


lador,
isdigit carcter devuelve cierto si carcter es un dgito, y falso en caso c

ontrario,
iss ace carcter : devuelve cierto si carcter es un es acio en blanco, un
salto de
lnea, un retorno de carro, un tabulador, etc., y falso en caso contrari
o,
islower carcter : devuelve cierto si carcter es una letra minscula, y fal
so en
caso contrario,
isu er carcter : devuelve cierto si carcter es una letra mayscula, y fal
so en
caso contrario.
Tambin en
encontrars un ar de funciones tiles ara convertir caracteres d
e
minscula a mayscula y viceversa:
tou er carcter : devuelve la mayscula asociada a carcter, si la tiene; s

i no,
devuelve el mismo carcter,
tolower carcter : devuelve la minscula asociada a carcter, si la tiene; s
i no,
devuelve el mismo carcter.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118 Qu problema presenta este programa?

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
117
117
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
include
int main void
char b 2
if isalpha b
printf
else
printf
return 0
...............................................................................
...
sprintf
Hay una funcin que puede simplificar notablemente la creacin de cadenas cuyo contenido se debe calcular a partir de uno o ms valores: sprintf , disponible incluy
endo la
cabecera
(se trata, en cierto modo, de la operacin complementaria de s
scanf ).
La funcin sprintf se comporta como printf , salvo por un detalle: no escribe texto
en
pantalla, sino que lo almacena en una cadena.
Fjate en este ejemplo:
include
define

80

int main void


char a
char b
char c

1
1
1

sprintf c
printf

a b

return 0
Si ejecutas el programa aparecer lo siguiente en pantalla:
Como puedes ver, se ha asignado a c el valor de a seguido de un espacio en b
lanco y
de la cadena b. Podramos haber conseguido el mismo efecto con llamadas a strcpy c
a ,
strcat c
y strcat c b , pero sprintf resulta ms legible y no cuesta mucho
aprender a usarla, pues ya sabemos usar printf . No olvides que t eres responsable de
que la
informacin que se almacena en c quepa.
En Python hay una accin anloga al sprintf de C: la asignacin a una variable
de una cadena formada con el operador de formato. El mismo programa se podra habe
r

escrito en Python as:


Ojo: programa Python
a
b
c
a b
Operacin anloga a sprintf en C.
print c
Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C - UJI
UJI
c

118
118

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
119 Qu almacena en la cadena a la siguiente sentencia?
sprintf a
1 48 2
120 Escribe un programa que pida el nombre y los dos apellidos de una persona. C
ada
uno de esos tres datos debe almacenarse en una variable independiente. A continu
acin,
el programa crear y mostrar una nueva cadena con los dos apellidos y el nombre
(separado de los apellidos por una coma). Por ejemplo, Juan Prez Lpez dar lugar a l
a
cadena
.
...............................................................................
...

Vamos a implementar un programa que lee por teclado una lnea de texto y muestra
por pantalla una cadena en la que las secuencias de blancos de la cadena origina
l
(espacios en blanco, tabuladores, etc.) se han sustituido por un slo espacio en b
lanco.
Si, por ejemplo, el programa lee la cadena
,
mostrar por pantalla la cadena normalizada
.
include
include
include
define

80

int main void


char a
1
int longitud i j
printf
gets a
longitud strlen a
b 0
a 0
j 1
for i 1 i longitud i
if isspace a i
isspace a i 1
b j
a i
b j
printf
b

isspace a i

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC

IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
121 Modifica
para que elimine, si
los hay, los blancos inicial y final
de la cadena normalizada.
122 Haz un programa que lea una frase y construya una cadena que slo contenga
sus letras minsculas o maysculas en el mismo orden con que aparecen en la frase.
123 Haz un programa que lea una frase y construya una cadena que slo contenga
sus letras minsculas o maysculas en el mismo orden con que aparecen en la frase, p
ero
sin repetir ninguna.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
119
119
ramacin con C - UJI

Introduccin a la prog

c
UJI

124 Lee un texto por teclado (con un mximo de 1000 caracteres) y muestra por
pantalla la frecuencia de aparicin de cada una de las letras del alfabeto (consid
era
nicamente letras del alfabeto ingls), sin distinguir entre letras maysculas y minscu
las
(una aparicin de la letra y otra de la letra cuentan como dos ocurrencias de la l
etra
).
...............................................................................
...

Podemos declarar vectores de ms de una dimensin muy fcilmente:


int a 10 5
float b 3 2
4
En este ejemplo, a es una matriz de 10 5 enteros y b es un vector de tres dimens
iones
con 3 2 4 nmeros en coma flotante.
Puedes acceder a un elemento cualquiera de los vectores a o b utilizando tan
tos ndices como dimensiones tiene el vector: a 4 2 y b 1 0 3 , por ejemplo, son element
os
de a y b, respectivamente.
La inicializacin de los vectores multidimensionales necesita tantos bucles an
idados
como dimensiones tengan stos:
int main void
int a 10 5
float b 3 2
int i j k

for i 0 i 10 i
for j 0 j 5 j
a i j 0
for i 0 i 3 i
for j 0 j 2 j
for k 0 k 4 k
b i j k 0.0
return 0
Tambin puedes inicializar explcitamente un vector multidimensional:
int c 3
3
1 0 0
0 1 0
0 0 1

Cuando el compilador de C detecta la declaracin de un vector multidimensional, re


serva
tantas posiciones contiguas de memoria como sea preciso para albergar todas sus
celdas.
Por ejemplo, ante la declaracin int a 3 3 , C reserva 9 celdas de 4 bytes, es

decir,
36 bytes. He aqu cmo se disponen las celdas en memoria, suponiendo que la zona de
memoria asignada empieza en la direccin 1000:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

120
120

996:
1000:
a[0][0]
1004:
a[0][1]
1008:
a[0][2]
1012:
a[1][0]
1016:
a[1][1]
1020:
a[1][2]
1024:
a[2][0]
1028:
a[2][1]
1032:
a[2][2]
1036:
Cuando accedemos a un elemento a i j , C sabe a qu celda de memoria acceder
sumando a la direccin de a el valor i 3 j 4 (el 4 es el tamao de un int y el 3 es
el
nmero de columnas).
Aun siendo conscientes de cmo representa C la memoria, nosotros trabajaremos c
on
una representacin de una matriz de 3 3 como sta:
0
1
2
0
a
1
2

Como puedes ver, lo relevante es que a es asimilable a un puntero a la zona de m


emoria
en la que estn dispuestos los elementos de la matriz.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
125 Este programa es incorrecto. Por qu? Aun siendo incorrecto, produce cierta
salida por pantalla. Qu muestra?
include
define

int main void


int a
int i j
for i 0 i
for j 0 j
a i j

i
j
10 i j

for j 0 j
printf

j
a 0

return 0
...............................................................................
...

Para ilustrar el manejo de vectores multidimensionales construiremos ahora un pr


ograma
que lee de teclado dos matrices de nmeros en coma flotante y muestra por pantalla
121
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
121
Introduccin a la programa
cin con C - UJI
c
UJI

su suma y su producto. Las matrices ledas sern de 3 3 y se denominarn a y b. El


resultado de la suma se almacenar en una matriz s y el del producto en otra p.
Aqu tienes el programa completo:
include
define

int main void


float a
float s
int i j k

b
p

Lectura de la matriz a
for i 0 i
i
for j 0 j
j
printf
a i

Lectura de la matriz b
for i 0 i
i
for j 0 j
j
printf
b i

i j

scanf

i j

scanf

j
Clculo de la suma
for i 0 i
i
for j 0 j
j
s i j
a i j
Clculo del producto
for i 0 i
i
for j 0 j
j
p i j
0.0
for k 0 k
k
p i j
a i k

b i

Impresin del resultado de la suma


printf
for i 0 i
i
for j 0 j
j
printf
s i j
printf
Impresin del resultado del producto
printf
for i 0 i
i
for j 0 j
j
printf
p i j
printf
return 0

b k

An no sabemos definir nuestras propias funciones. En el prximo captulo volver


emos
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

122
122

a ver este programa y lo modificaremos para que use funciones definidas por noso
tros.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
126 En una estacin meteorolgica registramos la temperatura (en grados centgrados)
cada hora durante una semana. Almacenamos el resultado en una matriz de 7 24 (ca
da
fila de la matriz contiene las 24 mediciones de un da). Disea un programa que lea
los
datos por teclado y muestre:
La mxima y mnima temperaturas de la semana.
La mxima y mnima temperaturas de cada da.
La temperatura media de la semana.
La temperatura media de cada da.
El nmero de das en los que la temperatura media fue superior a 30 grados
.
127 Representamos diez ciudades con nmeros del 0 al 9. Cuando hay carretera que
une directamente a dos ciudades i y j, almacenamos su distancia en kilmetros en l
a celda
d i j de una matriz de 10 10 enteros. Si no hay carretera entre ambas ciudades,
el valor almacenado en su celda de d es cero. Nos suministran un vector en el qu
e se
describe un trayecto que pasa por las 10 ciudades. Determina si se trata de un t
rayecto
vlido (las dos ciudades de todo par consecutivo estn unidas por un tramo de carret
era)
y, en tal caso, devuelve el nmero de kilmetros del trayecto. Si el trayecto no es
vlido,
indcalo con un mensaje por pantalla.
La matriz de distancias debers inicializarla explcitamente al declararla. El v
ector
con el recorrido de ciudades debers leerlo de teclado.
128 Disea un programa que lea los elementos de una matriz de 4 5 flotantes
y genere un vector de talla 4 en el que cada elemento contenga el sumatorio de l
os
elementos de cada fila. El programa debe mostrar la matriz original y el vector
en este
formato (evidentemente, los valores deben ser los que correspondan a lo introduc
ido por
el usuario):

...............................................................................
...
El programa que hemos presentado adolece de un serio inconveniente si nuest
ro objetivo era construir un programa general para multiplicar matrices: slo puede traba
jar
con matrices de TALLA TALLA, o sea, de 3 3. Y si quisiramos trabajar con matrices
de tamaos arbitrarios? El primer problema al que nos enfrentaramos es el de que la
s
matrices han de tener una talla mxima: no podemos, con lo que sabemos por ahora,

reservar un espacio de memoria para las matrices que dependa de datos que nos sumi
nistra
el usuario en tiempo de ejecucin. Usaremos, pues, una constante
con u
n valor
razonablemente grande: pongamos 10. Ello permitir trabajar con matrices con un nme
ro
de filas y columnas menor o igual que 10, aunque ser a costa de malgastar memoria
.
include
define

10

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
123
123
ramacin con C - UJI

Introduccin a la prog

c
UJI

int main void


float a
float s

b
p

El nmero de filas y columnas de a se pedir al usuario y se almacenar en sendas


variables: filas a y columnas a. Este grfico ilustra su papel: la matriz a es de
10 10,
pero slo usamos una parte de ella (la zona sombreada) y podemos determinar qu zona
es porque filas a y columnas a nos sealan hasta qu fila y columna llega la zona til
:
columnas a
3

0
2

9
0
a
1
2
3
4

las a

5
6
7
8
9

Lo mismo se aplicar al nmero de filas y columnas de . Te mostramos el programa has


ta
el punto en que leemos la matriz a:
include
define

10

int main void


float a
b
float s
p
int filas a columnas a filas b columnas b
int i j k

Lectura de la matriz a
printf
filas a
printf
columnas a

scanf
scanf

for i 0 i filas a i
for j 0 j columnas a j
printf
scanf
a i
j

i j

(Encrgate t mismo de la lectura de .)


La suma slo es factible si
es igual a
y
es igual a
.

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
124
124
I
cUJI

Introduccin a la programacin con C -UJ

include
define

10

int main void


float a
b
float s
p
int filas a columnas a filas b columnas b
int filas s columnas s
int i j k

a i

Lectura de la matriz a
printf
printf
for i 0 i filas a i
for j 0 j columnas a j
printf
j

scanf
scanf

filas a
columnas a
i j

scanf

Lectura de la matriz b
Clculo de la suma
if filas a
filas b
columnas a
filas s filas a
columnas s columnas a
for i 0 i filas s i
for j 0 j filas s j
s i j
a i j
b i j

columnas b

Impresin del resultado de la suma


if filas a
filas b columnas a columnas b
printf
for i 0 i filas s i
for j 0 j columnas s j
printf
s i j
printf
else
printf

Recuerda que una matriz de n m elementos se puede multiplicar por otra de n m


elementos slo si m es igual a n (o sea, el nmero de columnas de la primera es igual
al de filas de la segunda) y que la matriz resultante es de dimensin n m .
include
define

10

int main void


float a
float s

b
p

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

125
125

int filas a columnas a filas b columnas b


int filas s columnas s filas p columnas p
int i j k
Lectura de la matriz a
printf

scanf

filas

printf

scanf

colum

a
nas a
for i 0 i filas a i
for j 0 j columnas a j
printf
a i j

i j

scanf

Lectura de la matriz b
printf

scanf

filas

printf

scanf

colum

b
nas b
for i 0 i filas b i
for j 0 j columnas b j
printf
b i j
Clculo de la suma
if filas a
filas b
columnas a
filas s filas a
columnas s columnas a
for i 0 i filas s i
for j 0 j filas s j
s i j
a i j
b i j
Clculo del producto
if columnas a
filas b
filas p filas a
columnas p columnas b
for i 0 i filas p i
for j 0 j columnas p j
p i j
0.0
for k 0 k columnas a k
p i j
a i k b k

Impresin del resultado de la suma


if filas a
filas b columnas a columnas b
printf
for i 0 i filas s i
for j 0 j columnas s j
printf
s i j
printf
else
printf
Impresin del resultado del producto

i j

columnas b

scanf

if columnas a
filas b
printf
for i 0 i filas p i
for j 0 j columnas p j
printf
p i j
Introduccin
Andrs aMarzal/Isabel
la programacin con -C ISBN: 978-84-693-0143-2
Gracia
6

12
12

Introduccin a la programacin con C -UJI


cUJI

printf
else
printf
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
129 Extiende el programa de calculadora matricial para efectuar las siguientes o
peraciones:
Producto de una matriz por un escalar. (La matriz resultante tiene la
misma dimensin que la original y cada elemento se obtiene multiplicando el esca
lar por la
celda correspondiente de la matriz original.)
Transpuesta de una matriz. (La transpuesta de una matriz de n m es una
matriz
de m n en la que el elemento de la fila i y columna j tiene el mismo v
alor que
el que ocupa la celda de la fila j y columna i en la matriz original.)
130 Una matriz tiene un valle si el valor de una de sus celdas es menor que el d
e
cualquiera de sus 8 celdas vecinas. Disea un programa que lea una matriz (el usua
rio
te indicar de cuntas filas y columnas) y nos diga si la matriz tiene un valle o no
. En
caso afirmativo, nos mostrar en pantalla las coordenadas de todos los valles, sus
valores
y el de sus celdas vecinas.
La matriz debe tener un nmero de filas y columnas mayor o igual que 3 y menor
o igual que 10. Las casillas que no tienen 8 vecinos no se consideran candidatas
a ser
valle (pues no tienen 8 vecinos).
Aqu tienes un ejemplo de la salida esperada para esta matriz de 4 5:
1 2 9 5 5
3 2 9 4 5
6 1 8 7 6
6 3 8 0 9

(Observa que al usuario se le muestran filas y columnas numeradas desde 1, y


no
desde 0.)
131 Modifica el programa del ejercicio anterior para que considere candidata a v
alle
a cualquier celda de la matriz. Si una celda tiene menos de 8 vecinos, se consid
era que
la celda es valle si su valor es menor que el de todos ellos.

Para la misma matriz del ejemplo del ejercicio anterior se obtendra esta sali
da:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
127
127
ramacin con C - UJI

Introduccin a la prog

c
UJI

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

Por lo dicho hasta el momento, est claro que un vector de cadenas es una matriz d
e
caracteres. Este fragmento de programa, por ejemplo, declara un vector de 10 cad
enas
cuya longitud es menor o igual que 80:
define
80
char v 10
1
Cada fila de la matriz es una cadena y, como tal, debe terminar en un carcter nul
o.
Este fragmento declara e inicializa un vector de tres cadenas:
define
80
char v 3

Puedes leer individualmente cada cadena por teclado:


include
define

80

int main void


char v 3
int i

for i 0 i 3 i
printf
gets v i
printf

v i

return 0
Vamos a desarrollar un programa til que hace uso de un vector de caracteres:
un
pequeo corrector ortogrfico para ingls. El programa dispondr de una lista de palabra
s
en ingls (que encontrars en la pgina web de la asignatura, en el fichero
),
solicitar al usuario que introduzca por teclado un texto en ingls y le informar de
qu palabras considera errneas por no estar includas en su diccionario. Aqu tienes un
ejemplo de uso del programa:
Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
c

128
128

Introduccin a la programacin con C - UJI


UJI

El fichero
s ahora las primeras y
ltimas lneas:
define
define
char diccionario

es una cabecera de la que te mostramo

45378
28
1

La variable diccionario es un vector de cadenas (o una matriz de caracteres,


segn lo
veas) donde cada elemento es una palabra inglesa en minsculas. La constante
nos indica el nmero de palabras que contiene el diccionario y
es la l
ongitud
de la palabra ms larga (28 bytes), por lo que reservamos espacio para
1
caracteres (29 bytes: 28 ms el correspondiente al terminador nulo).
Las primeras lneas de nuestro programa son stas:
include
include
Fjate en que inclumos el fichero
encerrando su nombre entre comillas dob
les,
y no entre y . Hemos de hacerlo as porque
es una cabecera nuestra
y no
reside en los directorios estndar del sistema (ms sobre esto en el siguiente captul
o).
El programa empieza solicitando una cadena con gets. A continuacin, la dividi
r en
un nuevo vector de palabras. Supondremos que una frase no contiene ms de 100 pala
bras
y que una palabra es una secuencia cualquiera de letras. Si el usuario introduce
ms de
100 palabras, le advertiremos de que el programa slo corrige las 100 primeras. Un
a vez
formada la lista de palabras de la frase, el programa buscar cada una de ellas en
el
diccionario. Las que no estn, se mostrarn en pantalla precedidas del mensaje:
. Vamos all: empezaremos por la lectura de la frase y su descomp
osicin
en una lista de palabras.
include
include
include
include

Introduccin a la programacin con C

129
129
c

UJI
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

define
define
define

1000
100
100

int main void


char frase
1
char palabra
1
int palabras
Nmero de palabras en la frase
int lonfrase i j
Lectura de la frase
printf
gets frase
lonfrase

strlen frase

Descomposicin en un vector de palabras


i 0
while i lonfrase
isalpha frase i
i
Saltarse las no-letras iniciales.
palabras 0
while i lonfrase

Recorrer todos los caracteres

Avanzar mientras vemos caracteres e ir formando la palabra palab


ra palabras .
j 0
while i lonfrase

isalpha frase i

palabra palabras j

frase i
palabra palabras j

El terminador es responsabilida

d nuestra.
Incrementar el contador de palabras.
palabras
if palabras
Y finalizar si ya no caben ms p
alabras
break
Saltarse las no-letras que separan esta palabra de la siguiente
(si las hay).
while i lonfrase

isalpha frase i

Comprobacin de posibles errores


for i 0 i palabras i
printf
palabra i
return 0
Buf! Complicado, no? Ya estamos echando en falta el mtodo split de Python! No nos
viene mal probar si nuestro cdigo funciona mostrando las palabras que ha encontra
do en
la frase. Por eso hemos aadido las lneas 4446. Una vez hayas ejecutado el programa
y
comprobado que funciona correctamente hasta este punto, comenta el bucle que mue
stra
las palabras:
Comprobacin de posibles errores

for i 0 i palabras i
printf
palabra i
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
132 Un programador, al copiar el programa, ha sustituido la lnea que reza as:
while i lonfrase
isalpha frase i
i
Saltarse las no-letras iniciales.
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
130
130
c
UJI
Introduccin a la prog
ramacin con C - UJI

por esta otra:


while frase i
i
Saltarse las no-letras iniciales.

isalpha frase i

Es correcto el programa resultante? Por qu?


133 Un programador, al copiar el programa, ha sustituido la lnea que reza as:
while i lonfrase
isalpha frase i
i
Saltarse las no-letras iniciales.
por esta otra:
while frase i
Saltarse las no-letras iniciales.

isalpha frase i

Es correcto el programa resultante? Por qu?


134 Un programador, al copiar el programa, ha sustituido la lnea que reza as:
while i lonfrase
isalpha frase i
palabra palabra
s
j
frase i
por esta otra:
while isalpha frase i
frase i

palabra palabras

Es correcto el programa resultante? Por qu?


135 Un programador, al copiar el programa, ha sustituido la lnea que reza as:
while i lonfrase
isalpha frase i
i
Saltarse las no-letras iniciales.
por esta otra:
while
frase i

isalpha frase i

palabra palabras

Es correcto el programa resultante? Por qu?


...............................................................................
...
Sigamos. Nos queda la bsqueda de cada palabra en el diccionario. Una primera i
dea
consiste en buscar cada palabra de la frase recorriendo el diccionario desde la
primera
hasta la ltima entrada:

?
Estn todas las palabras en el diccionario?
for i 0 i palabras i
encontrada 0
?
for j 0 j
j
if strcmp palabra i diccionario j
Es palabra i igual que diccionario j ?
encontrada 1
break
if
palabra i
return 0

encontrada
printf

Ten en cuenta lo que hace strcmp: recorre las dos cadenas hasta encontrar alguna
diferencia entre ellas o concluir que son idnticas. Es, por tanto, una operacin bastante
costosa
en tiempo. Podemos reducir el nmero de comparaciones? Claro! Como el diccionario
est ordenado alfabticamente, podemos abortar el recorrido cuando llegamos a una vo
z
del diccionario posterior (segn el orden alfabtico) a la que buscamos:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
131
131

Introduccin a la programacin con C - UJI


c
UJI

?
Estn todas las palabras en el diccionario?
for i 0 i palabras i
encontrada 0
?
for j 0 j
j
Es palabra i igual que diccionario j ?
if strcmp palabra i diccionario j
0
encontrada 1
break
else if strcmp palabra i
?palabra i diccionario j ?
break
if encontrada
printf
labra i

diccionario j

pa

return 0
Con esta mejora hemos intentado reducir a la mitad el nmero de comparaciones con
cadenas del diccionario, pero no hemos logrado nuestro objetivo: aunque, en prome
dio,
efectuamos comparaciones con la mitad de las palabras del diccionario, estamos l
lamando
dos veces a
! Es mejor almacenar el resultado de una sola llamada a
en
una variable:

?
Estn todas las palabras en el diccionario?
for i 0 i palabras i
encontrada 0
for j 0 j
j
?
comparacion strcmp palabra i diccionario j
if comparacion
0
Es palabra i igual que dicciona
rio j ?
encontrada 1

break
?

else if comparacion
or que diccionario j ?
break
if

encontrada
printf

Es palabra i men

pa

labra i
return 0
(Recuerda declarar comparacion como variable de tipo entero.)
El diccionario tiene 45378 palabras. En promedio efectuamos, pues, 22689 com
paraciones por cada palabra de la frase. Mmmm. An podemos hacerlo mejor. Si la lista
est

ordenada, podemos efectuar una bsqueda dicotmica. La bsqueda dicotmica efecta


un nmero de comparaciones reducidsimo: bastan 16 comparaciones para decidir si una
palabra cualquiera est o no en el diccionario!

Estn todas las palabras en el diccionario?

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
32

1
13

Introduccin a la programacin con C -UJI


cUJI

for i 0 i palabras i
encontrada 0
izquierda 0
derecha
while izquierda derecha
j
izquierda derecha 2
comparacion strcmp palabra i

dicci

onario j
if comparacion 0
derecha j
else if comparacion 0
izquierda j 1
else
encontrada 1
break

if

encontrada
printf
palabra i
return 0
(Debes declarar derecha e izquierda como enteros.)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
136 Escribe un programa C que lea un texto (de longitud menor que 1000) y obteng
a
un vector de cadenas en el que cada elemento es una palabra distinta del texto (
con un
mximo de 500 palabras). Muestra el contenido del vector por pantalla.
137 Modifica el programa del ejercicio anterior para que el vector de palabras s
e
muestre en pantalla ordenado alfabticamente. Debers utilizar el mtodo de la burbuja
para ordenar el vector.
138
Representamos la baraja de cartas con un vector de cadenas. Los palos s
on
,
,
y
. Las cartas con nmeros entre 2 y 9
se describen con el texto
(ejemplo:
,
). Los ases
se describen con la cadena
, las sotas con
,
los caballos
con
y los reyes con
.
Escribe un programa que genere la descripcin de las 40 cartas de la baraja. U
sa
bucles siempre que puedas y compn las diferentes partes de cada descripcin con str
cat
o sprintf . A continuacin, baraja las cartas utilizando para ello el generador de
nmeros
aleatorios y muestra el resultado por pantalla.
139 Disea un programa de ayuda al diagnstico de enfermedades. En nuestra base
de datos hemos registrado 10 enfermedades y 10 sntomas:
char enfermedades 10
20
char sintomas 10 20

Almacenamos en una matriz de 10 10 valores booleanos (1 o 0) los sntomas que


presenta cada enfermedad:
char sintomatologia 10
10
1 0 1
0 0 0

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
133
133
ramacin con C - UJI

Introduccin a la prog

c
UJI

La celda sintomatologia i j vale 1 si la enfermedad i presenta el sntoma j, y 0 e


n
caso contrario.
Disea un programa que pregunte al paciente si sufre cada uno de los 10 sntomas
y,
en funcin de las respuestas dadas, determine la enfermedad que padece. Si la desc
ripcin
de sus sntomas no coincide exactamente con la de alguna de las enfermedades, el s
istema
indicar que no se puede emitir un diagnstico fiable.
140 Modifica el programa anterior para que, cuando no hay coincidencia absoluta
de
sntomas, muestre las tres enfermedades con sintomatologa ms parecida. Si, por ejemp
lo,
una enfermedad presenta 9 coincidencias con la sintomatologa del paciente, el sis
tema
mostrar el nombre de la enfermedad y el porcentaje de confianza del diagnstico (90
%).
141 Vamos a implementar un programa que nos ayude a traducir texto a cdigo
Morse. Aqu tienes una tabla con el cdigo Morse:

El programa leer una lnea y mostrar por pantalla su traduccin a cdigo Morse. Ten
en cuenta que las letras se deben separar por pausas (un espacio blanco) y las p
alabras
por pausas largas (tres espacios blancos). Los acentos no se tendrn en cuenta al
efectuar
la traduccin (la letra , por ejemplo, se representar con ) y la letra
se mos
trar
como una
. Los signos que no aparecen en la tabla (comas, admiraciones, et
c.) no se
traducirn, excepcin hecha del punto, que se traduce por la palabra
. Te con
viene
pasar la cadena a maysculas (o efectuar esta transformacin sobre la marcha), pues
la
tabla Morse slo recoge las letras maysculas y los dgitos.
Por ejemplo, la cadena
se traducir por

Debes usar un vector de cadenas para representar la tabla de traduccin a Mors


e.
El cdigo Morse de la letra
, por ejemplo, estar accesible como una cadena
en
morse
.
(Tal vez te sorprenda la notacin morse
. Recuerda que
es el nmero 6
5, pues
el carcter
tiene ese valor ASCII. As pues, morse
y morse 65 son lo mi
smo.
Por cierto: el vector de cadenas morse slo tendr cdigos para las letras maysculas y
los dgitos; recuerda inicializar el resto de componentes con la cadena vaca.)
142 Escribe un programa que lea un texto escrito en cdigo Morse y lo traduzca al
cdigo alfabtico.
Si, por ejemplo, el programa lee por teclado esta cadena:

mostrar en pantalla el texto

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

Los vectores permiten agrupar varios elementos de un mismo tipo. Cada elemento d
e un
vector es accesible a travs de un ndice.
En ocasiones necesitars agrupar datos de diferentes tipos y/o preferirs accede
r a
diferentes elementos de un grupo de datos a travs de un identificador, no de un nd
ice. Los
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

134
134

registros son agrupaciones heterogneas de datos cuyos elementos (denominados camp


os)
son accesibles mediante identificadores. Ya hemos estudiado registros en Python,
as que
el concepto y su utilidad han de resultarte familiares.
Veamos ahora un diseo tpico de registro. Supongamos que deseamos mantener los
siguientes datos de una persona:
su nombre (con un mximo de 40 caracteres),
su edad (un entero),
su DNI (una cadena
Podemos definir un registro
define
define

de 9 caracteres).
persona antes de la aparicin de main:
40
9

struct Persona
char nombre
1
int edad
char dni
1
Fjate en el punto y coma: es fcil olvidarse de ponerlo.
La definicin de un registro introduce un nuevo tipo de datos en nuestro programa.
En el
ejemplo hemos definido el tipo struct Persona (la palabra struct forma parte del
nombre
del tipo). Ahora puedes declarar variables de tipo struct Persona as:
struct Persona pepe juan ana
En tu programa puedes acceder a cada uno de los campos de una variable de tipo s
truct
separando con un punto el identificador de la variable del correspondiente ident
ificador
del campo. Por ejemplo, pepe edad es la edad de Pepe (un entero que ocupa cuatro
bytes), juan nombre es el nombre de Juan (una cadena), y ana dni 8 es la letra d
el DNI
de Ana (un carcter).
Cada variable de tipo struct Persona ocupa, en principio, 55 bytes: 41 por e
l nombre,
4 por la edad y 10 por el DNI. (Si quieres saber por qu hemos resaltado lo de en
principio, lee el cuadro Alineamientos.)
Este programa ilustra cmo acceder a los campos de un registro leyendo por tec
lado
sus valores y mostrando por pantalla diferentes informaciones almacenadas en l:
include
include
define
define
struct Persona
char nombre
int edad
char dni
int main void

40
9
1
1

struct Persona ejemplo


char linea 81
int i longitud

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

135
135

Alineamientos
El operador sizeof devuelve el tamao en bytes de un tipo o variable. Analiz
a este
programa:
include
struct Registro
char a
int b
int main void
printf

sizeof str

uct Registro
return 0
Parece que vaya a mostrar en pantalla el mensaje
, pues un
char ocupa 1 byte y un int ocupa 4. Pero no es as:

La razn de que ocupe ms de lo previsto es la eficiencia. Los ordenadores


con
arquitectura de 32 bits agrupan la informacin en bloques de 4 bytes. Cada u
no de esos
bloques se denomina palabra. Cada acceso a memoria permite traer al procesad
or
los 4 bytes de una palabra. Si un dato est a caballo entre dos palabras, re
quiere dos
accesos a memoria, afectando seriamente a la eficiencia del programa. El c
ompilador
trata de generar un programa eficiente y da prioridad a la velocidad de ej
ecucin frente al
consumo de memoria. En nuestro caso, esta prioridad se ha traducido en que
el segundo
campo se almacene en una palabra completa, aunque ello suponga desperdicia
r 3 bytes
en el primero de los campos.

printf
printf
ejemplo edad
printf

gets ejemplo nombre


gets linea sscanf linea
gets ejemplo dni

printf
printf
printf
printf
longitud strlen ejemplo nombre
for i 0 i longitud i
if ejemplo nombre i
ejemplo nombre i
printf
ejemplo nombre i

ejemplo nombre
ejemplo edad
ejemplo dni

printf
printf
longitud strlen ejemplo dni
if ejemplo dni longitud 1
ejemplo dni longitud 1
printf
else
printf
ejemplo dni longitud 1
return 0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
6
136

13

Introduccin a la programacin con C -UJI


cUJI

Los registros pueden copiarse ntegramente sin mayor problema. Este programa,
por
ejemplo, copia el contenido de un registro en otro y pasa a minsculas el nombre d
e la
copia:
include
include
include
define
define

40
9

struct Persona
char nombre
int edad
char dni

1
1

int main void


struct Persona una copia
char linea 81
int i longitud
printf
printf
una edad
printf
copia

gets una nombre


gets linea sscanf linea
gets una dni
una

Copia

longitud strlen copia nombre


for i 0 i longitud i
copia nombre i tolower copia nombre i
printf
printf
printf

una nombre
una edad
una dni

printf
printf
printf

copia nombre
copia edad
copia dni

return 0
Observa que la copia se efecta incluso cuando los elementos del registro son
vectores.
O sea, copiar vectores con una mera asignacin est prohibido, pero copiar registros
es
posible. Un poco incoherente, no?
Por otra parte, no puedes comparar registros. Este programa, por ejemplo, ef
ecta una
copia de un registro en otro para, a continuacin, intentar decirnos si ambos son
iguales
o no:
E
E

include
define
define

40
9

struct Persona
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

137
137

char nombre
int edad
char dni

1
1

int main void


struct Persona una copia
char linea 81
int i longitud
printf
printf

gets una nombre


gets linea sscanf linea

printf

gets una dni

una

edad
copia

una

if copia
printf
else
printf

Copia
una

Comparacin ilegal.

return 0
Pero ni siquiera es posible compilarlo. La lnea 24 contiene un error que el compi
lador
seala como
, o sea, operandos invlidos para la
operacin binaria . Entonces, cmo podemos decidir si dos registros son iguales?
Comprobando la igualdad de cada uno de los campos de un registro con el correspo
ndiente
campo del otro:
include
define
define

40
9

struct Persona
char nombre
int edad
char dni

1
1

int main void


struct Persona una copia
char linea 81
int i longitud
printf
printf

gets una nombre


gets linea scanf linea

printf

gets una dni

una

edad
copia

una

Copia

if strcmp copia nombre una nombre

copia edad

na edad
strcmp copia dni una dni 0
printf
else
printf
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

138
138

return 0

Una razn para no comparar


Si C sabe copiar una estructura bit a bit, por qu no sabe compararlas bit a bi
t?
El problema estriba en construcciones como las cadenas que son campos de
un registro.
Considera esta definicin:
struct Persona
char nombre 10
char apellido 10
Cada dato de tipo struct Persona ocupa 20 bytes. Si una persona a tie
ne su campo
a nombre con valor
, slo los cinco primeros bytes de su nombre ti
enen un valor
bien definido. Los cinco siguientes pueden tener cualquier valor aleatori
o. Otro registro
b cuyo campo b nombre tambin valga
(y tenga idntico apellido)
puede tener
valores diferentes en su segundo grupo de cinco bytes. Una comparacin bit a
bit nos
dira que los registros son diferentes.
La asignacin no entraa este tipo de problema, pues la copia es bit a bit.
Como
mucho, resulta algo ineficiente, pues copiar hasta los bytes de valor inde
finido.

Una forma de inicializacin


C permite inicializar registros de diferentes modos, algunos bastante int
eresantes desde
el punto de vista de la legibilidad. Este programa, por ejemplo, define u
n struct y crea
e inicializa de diferentes formas, pero con el mismo valor, varias variab
les de este tipo:
struct Algo
int x
char nombre 10
float y

struct Algo
struct Algo

a
b

2.0
x 1 nombre

struct Algo

nombre

struct Algo

2.

0
1
dx 1
strcpy d nombre

2.0

d y 2.0

Los vectores estticos tienen una talla fija. Cuando necesitamos un vector cuya ta
lla vara
o no se conoce hasta iniciada la ejecucin del programa usamos un truco: definimos
un vector cuya talla sea suficientemente grande para la tarea que vamos a aborda
r y
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
cUJI
Introduccin a la programacin con C -UJI

139
139

mantenemos la talla real en una variable. Lo hemos hecho con el programa que calcu
la
algunas estadsticas con una serie de edades: definamos un vector edad con capacida
d
para almacenar la edad de
y una variable personas, cuyo valor si
empre era
menor o igual que
, nos indicaba cuntos elementos del vector cont
enan
realmente datos. Hay algo poco elegante en esa solucin: las variables edad y pers
onas
son variables independientes, que no estn relacionadas entre s en el programa (sal
vo
por el hecho de que nosotros sabemos que s lo estn). Una solucin ms elegante pasa
por crear un registro que contenga el nmero de personas y, en un vector, las edad
es.
He aqu el programa que ya te presentamos en su momento convenientemente modificad
o
segn este nuevo principio de diseo:
include
include
define

20

struct ListaEdades
int edad
edades.
int talla

Vector con capacidad para


Nmero de edades realmente almacenadas.

int main void


struct ListaEdades personas
int i j aux suma edad
float suma desviacion media desviacion
int moda frecuencia frecuencia moda mediana

Lectura de edades
personas talla 0
do
printf
personas talla 1
scanf
personas edad personas talla
personas talla
while personas talla
personas edad personas talla
0
personas talla
if

personas talla 0
Clculo de la media
suma edad 0
for i 0 i personas talla i
suma edad
personas edad i
media suma edad
float personas talla
Clculo de la desviacion tpica
suma desviacion 0.0
for i 0 i personas talla i
suma desviacion
personas edad i media

perso

nas edad i

media
desviacion sqrt suma desviacion personas talla
Clculo de la moda
for i 0 i personas talla 1 i
Ordenacin mediante burbuja.
for j 0 j personas talla i j
if personas edad j
personas edad j 1
aux personas edad j
personas edad j personas edad j 1

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

140
140

personas edad j 1

aux

frecuencia 0
frecuencia moda 0
moda
1
for i 0 i personas talla 1 i
if personas edad i
personas edad i 1
if
frecuencia frecuencia moda
frecuencia moda frecuencia
moda personas edad i
else
frecuencia

Clculo de la mediana
mediana personas edad personas talla 2
Impresin de resultados
printf
printf
printf
printf

media
desviacion
moda
mediana

else
printf
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
143 Modifica el programa de clculo con polinomios que sirvi de ejemplo en el
apartado 2.1.5 para representar los polinomios mediante registros. Cada registro
contendr
dos campos: el grado del polinomio y el vector con los coeficientes.
...............................................................................
...

Hay mtodos estadsticos que permiten obtener una recta que se ajusta de forma ptima
a una serie de puntos en el plano.
y = mx + b

Si disponemos de una serie de n puntos (x1 , y1 ), (x2 , y2 ), . . . , (xn , yn


), la recta de ajuste
y = mx + b que minimiza el cuadrado de la distancia vertical de todos los puntos
a la

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
141
141
ramacin con C - UJI

Introduccin a la prog

c
UJI

recta se puede obtener efectuando los siguientes clculos:


n
 n

n
i=1 xi
i=1 yi n i=1 xi yi
m =
n
2
n 2
,
i=1 xi
n i=1 xi
n
 n 2  n
 n
i=1 yi
i=1 xi
i=1 xi
b =

n
2
n ni=1 xi2
i=1 xi


i=1 xi yi
.

Las frmulas asustan un oco, ero no contienen ms que sumatorios. El rograma que
vamos a escribir lee una serie de untos (con un nmero mximo de, ongamos, 1000),
y
muestra los valores de m y b.
Modelaremos los untos con un registro:
struct Punto
float x y
El vector de untos, al que en rinci io denominaremos , tendr talla 1000:
define
1000
struct Punto
Pero 1000 es el nmero mximo de untos. El nmero de untos dis onibles efectivamente
ser menor o igual y su valor deber estar accesible en alguna variable. Olvidmonos d
el
vector : nos conviene definir un registro en el que se almacenen vector y talla
real del
vector.
struct ListaPuntos
struct Punto unto
int talla
Observa que estamos anidando structs.
Necesitamos ahora una variable del ti o que hemos definido:
include
define

1000

struct Punto
float x y
struct ListaPuntos
struct Punto unto
int talla
int main void
struct ListaPuntos lista
Reflexionemos brevemente sobre cmo
able
lista:

odemos acceder a la informacin de la vari

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
Introduccin a la rogramacin con C UJI
cUJI

142
142

Ex resin
lista
Puntos. Contiene un vector

Ti o y significado
Es un valor de ti o struct Lista
de 1000 untos y un entero.
Es un entero. Indica cuntos eleme

lista talla
ntos del vector contie

nen informacin.
Es un vector de 1000 valores de

lista unto
ti o struct Punto.
lista unto 0
y es de ti o struct Punto,

Es el

rimer elemento del vector

as que est com uesto or dos flota


ntes.
lista unto 0 x
o del vector. Su ti o es

Es el cam o x del rimer element


float.
Es el cam o y del ltimo elemento

lista unto lista talla 1 y


con informacin del

vector. Su ti o es float.
Error! Si lista unto es un vecto

lista unto x
r, no odemos acceder

al cam o x.
Error! Si lo anterior era incorre

lista unto x 0
cto, sto lo es an ms.
lista unto 0 x
erador de inde

Error! Qu hace un unto antes del o


xacin?
Error! La variable lista no es un

lista 0 unto
vector, as que no uedes

a licar el o erador de indexacin


sobre ella.
Ahora que tenemos ms claro cmo hemos modelado la informacin, vamos a
el roblema ro uesto. Cada uno de los sumatorios se recalcular cuando se
ledo
los untos. De ese modo, sim lificaremos significativamente las ex resiones
culo
de m y b. Debes tener en cuenta que, aunque en las frmulas se numeran los
em ezando en 1, en C se em ieza en 0.
Veamos el rograma com leto:
include
define
struct Punto
float x y
struct ListaPuntos
struct Punto unto
int talla
int main void
struct ListaPuntos lista
float sx sy sxy sxx

1000

resolver
hayan
de cl
untos

float m b
int i
Lectura de untos
rintf
for i 0 i lista talla i
rintf
lista unto i x
rintf
lista unto i y

scanf

Introduccin a la rogramacin con C

lista talla
i

scanf

scanf

143
c

UJI
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la rogramacin con C  UJI

143

Clculo de los sumatorios


sx 0.0
for i 0 i lista talla i
sx
lista unto i x
sy 0.0
for i 0 i lista talla i
sy lista unto i y
sxy 0.0
for i 0 i lista talla i
sxy lista unto i x

lista unto i y

sxx 0.0
for i 0 i lista talla i
sxx lista unto i x

lista unto i x

Clculo de m y b e im resin de resultados


if sx sx lista talla sxx
0
rintf
else
m
sx sy lista talla sxy
sx sx lista talla
sxx
rintf
m
b
sy sxx sx sxy
lista talla sxx sx sx
rintf
b
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
144 Disea un programa que lea una lista de hasta 1000 puntos por teclado y los
almacene en una variable (del tipo que t mismo definas) llamada representantes. A
continuacin, ir leyendo nuevos puntos hasta que se introduzca el punto de coordena
das
(0, 0). Para cada nuevo punto, debes encontrar cul es el punto ms prximo de los
almacenados en representantes. Calcula la distancia entre dos puntos como la dis
tancia
eucldea.
145 Deseamos efectuar clculos con enteros positivos de hasta 1000 cifras, ms de
las que puede almacenar un int (o incluso long long int). Define un registro que
permita
representar nmeros de hasta 1000 cifras con un vector en el que cada elemento es
una
cifra (representada con un char). Representa el nmero de cifras que tiene realmen
te el
valor almacenado con un campo del registro. Escribe un programa que use dos vari
ables
del nuevo tipo para leer dos nmeros y que calcule el valor de la suma y la resta
de
estos (supondremos que la resta siempre proporciona un entero positivo como resu
ltado).
...............................................................................
...

Estamos en condiciones de abordar la implementacin de un programa moderadamente


complejo: la gestin de una coleccin de CDs (aunque, todava, sin poder leer/escribir
en
fichero). De cada CD almacenaremos los siguientes datos:
el ttulo (una cadena con, a lo sumo, 80 caracteres),
el intrprete (una cadena con, a lo sumo, 40 caracteres),
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
144
144
c
UJI
Introduccin a la prog
ramacin con C - UJI

la duracin (en minutos y segundos),


el ao de publicacin.
Definiremos un registro para almacenar los datos de un CD y otro para representa
r la
duracin, ya que sta cuenta con dos valores (minutos y segundos):
define
80
define
40
struct Tiempo
int minutos
int segundos
struct CompactDisc
char titulo
1
char interprete
struct Tiempo duracion
int anyo

Vamos a usar un vector para almacenar la coleccin, definiremos un mximo nmero de


CDs: 1000. Eso no significa que la coleccin tenga 1000 discos, sino que puede ten
er a lo
sumo 1000. Y cuntos tiene en cada instante? Utilizaremos una variable para mantene
r el
nmero de CDs presente en la coleccin. Mejor an: definiremos un nuevo tipo de regist
ro
que represente a la coleccin entera de CDs. El nuevo tipo contendr dos campos: el
vector
de discos (con capacidad limitada a 1000 unidades) y el nmero de discos en el vec
tor.
He aqu la definicin de la estructura y la declaracin de la coleccin de CDs:
define
1000

struct Coleccion
struct CompactDisc cd
int cantidad
struct Coleccion mis cds
Nuestro programa permitir efectuar las siguientes acciones:
Aadir un CD a la base de datos.
Listar toda la base de datos.
Listar los CDs de un intrprete.
Suprimir un CD dado su ttulo y su intrprete.
(El programa no resultar muy til hasta que aprendamos a utilizar ficheros en C, pu
es
al finalizar cada ejecucin se pierde toda la informacin registrada.)
He aqu el programa completo:

include
include
define

80

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

145
145

define
define
define

1000
80
40

enum

Anyadir 1 ListadoCompleto ListadoPorInterprete Suprimir Salir

struct Tiempo
int minutos
int segundos
struct CompactDisc
char titulo
1
char interprete
struct Tiempo duracion
int anyo

struct Coleccion
struct CompactDisc cd
int cantidad
int main void
struct Coleccion mis cds
int opcion i j
char titulo
1

interprete

char linea

Para evitar los problemas de

1
scanf.
Inicializacin de la coleccin.
mis cds cantidad 0
Bucle principal: men de opciones.
do
do
printf
printf
printf
printf
printf
printf
printf
printf
gets linea sscanf linea
if opcion 1
opcion 5
printf
while opcion 1
opcion 5
switch opcion
case Anyadir
Aadir un CD.
if mis cds cantidad
printf
else
printf
gets mis cds cd mis cds cantidad titulo
printf
gets mis cds cd mis cds cantidad interprete

opcion

printf
Introduccin
Andrs aMarzal/Isabel
la programacin con -C ISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

146
146

gets linea
sscanf linea

mis cds cd mis cds cant

printf
gets linea
sscanf linea

mis cds cd mis cds cant

printf
gets linea
sscanf linea

mis cds cd mis cds cant

idad duracion minutos

idad duracion segundos

idad anyo
mis cds cantidad
break
case ListadoCompleto
Listar todo.
for i 0 i mis cds cantidad i
printf
mis cds cd i titulo
mis cds cd i interprete
mis cds cd i duracion minutos
mis cds cd i duracion segundos
mis cds cd i anyo
break

case ListadoPorInterprete
Listar por intrprete.
printf
gets interprete
for i 0 i mis cds cantidad i
if strcmp interprete mis cds cd i interprete
0
printf
mis cds
mis cds cd
mis cds cd
mis cds cd
mis cds cd

i
cd i titulo
i interprete
i duracion minutos
i duracion segundos
i anyo

break
case Suprimir
Suprimir CD.
printf
gets titulo
printf
gets interprete
for i 0 i mis cds cantidad i
if strcmp titulo mis cds cd i titulo
0
strcmp interprete mis cds cd i interprete
0
break
if i mis cds cantidad
for j i 1 j mis cds cantidad j
mis cds cd j 1
mis cds cd j
mis cds cantidad
break
while opcion
printf
return 0

Salir

En nuestro programa hemos separado la definicin del tipo struct Coleccion de


la
declaracin de la variable mis cds. No es necesario. Podemos definir el tipo y dec
larar
la variable en una sola sentencia:
struct Coleccion
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

147
147

Introduccin a la programacin con C -UJI


cUJI

struct CompactDisc cd
int cantidad
mis cds
Declara la variable mis cds como de tipo struct Coleccion.
Apuntemos ahora cmo enriquecer nuestro programa de gestin de una coleccin de
discos compactos almacenando, adems, las canciones de cada disco. Empezaremos por
definir un nuevo registro: el que modela una cancin. De cada cancin nos interesa e
l
ttulo, el autor y la duracin:
struct Cancion
char titulo
1
char autor
1
struct Tiempo duracion
Hemos de modificar el registro struct CompactDisc para que almacene hasta, d
igamos,
20 canciones:
define
20
struct CompactDisc
char titulo
1
char interprete
1
struct Tiempo duracion
int anyo
struct Cancion cancion
Vector de canciones.
int canciones
Nmero de canciones que realmente hay.
Cmo leemos ahora un disco compacto? Aqu tienes, convenientemente modificada,
la porcin del programa que se encarga de ello:
int main void
int segundos
switch opcion
case Anyadir
Aadir un CD.
if mis cds cantidad
printf
else
printf
gets mis cds cd mis cds cantidad titulo
printf
gets mis cds cd mis cds cantidad interprete
printf
gets linea sscanf linea
mis cds cd mis cds cantidad
anyo
do
printf
gets linea
sscanf linea

mis cds cd mis cds cantidad canci

ones
while mis cds cd mis cds cantidad canciones
for i 0 i mis cds cd mis cds cantidad canciones i
printf
i
gets mis cds cd mis cds cantidad cancion i titulo
printf
i

gets mis cds cd mis cds cantidad cancion i autor


printf
i
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

148
148

gets linea
sscanf linea
mis cds cd mis cds cantidad cancion i dur
acion minutos
printf
gets linea
sscanf linea
mis cds cd mis cds cantidad cancion i dur
acion segundos
segundos 0
for i 0 i mis cds cd mis cds cantidad canciones i
segundos 60 mis cds cd mis cds cantidad cancion i du
racion minutos
mis cds cd mis cds cantidad cancion i du
racion segundos
mis cds cd mis cds cantidad duracion minutos segundos 6
0
mis cds cd mis cds cantidad duracion segundos segundos
60
mis cds cantidad
break

Observa cmo se calcula ahora la duracin del compacto como suma de las duraciones
de todas sus canciones.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
146 Disea un programa C que gestione una agenda telefnica. Cada entrada de la
agenda contiene el nombre de una persona y hasta 10 nmeros de telfono. El programa permitir aadir nuevas entradas a la agenda y nuevos telfonos a una entrada ya
existente. El men del programa permitir, adems, borrar entradas de la agenda, borra
r
nmeros de telfono concretos de una entrada y efectuar bsquedas por las primeras
letras del nombre. (Si, por ejemplo, tu agenda contiene entradas para Jos Martnez,
Josefa Prez y Jaime Primero, una bsqueda por Jos mostrar a las dos primeras
personas y una bsqueda por J las mostrar a todas.)
...............................................................................
...

Los registros son nuevos tipos de datos cuyo nombre viene precedido por la palab
ra struct.
C permite definir nuevos nombres para los tipos existentes con la palabra clave
typedef.
He aqu un posible uso de typedef:
define
80
define
40
struct Tiempo
int minutos
int segundos

typedef struct Tiempo TipoTiempo


struct Cancion
char titulo
char autor
TipoTiempo duracion

1
1

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
149
149
ramacin con C - UJI

Introduccin a la prog

c
UJI

typedef struct Cancion TipoCancion


struct CompactDisc
char titulo
1
char interprete
1
TipoTiempo duracion
int anyo
TipoCancion cancion
Vector de canciones.
int canciones
Nmero de canciones que realmente hay.
Hay una forma ms compacta de definir un nuevo tipo a partir de un registro:
define
80
define
40
typedef struct
int minutos
int segundos
TipoTiempo
typedef struct
char titulo
char autor
TipoTiempo duracion
TipoCancion

1
1

typedef struct
char titulo
1
char interprete
1
TipoTiempo duracion
int anyo
TipoCancion cancion
Vector de canciones.
int canciones Nmero de canciones que realmente hay.
TipoCompactDisc
typedef struct
TipoCompactDisc cd
int cds
TipoColeccion
int main void
TipoColeccion mis cds
Observa que, sistemticamente, hemos utilizado iniciales maysculas para los nom
bres
de tipos de datos (definidos con typedef y struct o slo con struct). Es un buen c
onvenio
para no confundir variables con tipos. Te recomendamos que hagas lo mismo o, en
su
defecto, que adoptes cualquier otro criterio, pero que sea coherente. El renombr
amiento
de tipos no slo sirve para eliminar la molesta palabra clave struct, tambin permit
e
disear programas ms legibles y en los que resulta ms fcil cambiar tipos globalmente.
Imagina que en un programa nuestro representamos la edad de una persona con
un
valor entre 0 y 127 (un char). Una variable edad se declarara as:

char edad
No es muy elegante: una edad no es un carcter, sino un nmero. Si definimos un nuevo
tipo, el programa es ms legible:
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
150
150
cUJI
Introduccin a la programacin con C -UJI

typedef char TipoEdad


TipoEdad edad
Es ms, si ms adelante deseamos cambiar el tipo char por int, slo hemos de cambiar l
a
lnea que empieza por typedef, aunque hayamos definido decenas de variables del ti
po
TipoEdad:
typedef int TipoEdad
TipoEdad edad
Los cambios de tipos y sus consecuencias
Hemos dicho que typedef define nuevos tipos y facilita sustituir un tipo
por otro en diferentes versiones de un programa. Es cierto, pero problemtico. Considera qu
e definimos
un tipo edad como carcter sin signo y que definimos una variable de dicho
tipo cuyo
valor leemos de teclado:
include
typedef unsigned char TipoEdad
int main void
TipoEdad mi edad
scanf
printf
return 0

mi edad
mi edad

Qu pasa si, posteriormente, decidimos que el TipoEdad debiera ser un ent


ero de
32 bits? He aqu una versin errnea del programa:
include
typedef int TipoEdad
int main void
TipoEdad mi edad
!
printf
!
scanf
printf

mi edad

Mal!
mi edad

Mal!
return 0
Y por qu es errneo? Porque debiramos haber modificado adems las marcas de

formato de scanf y printf : en lugar de

deberamos usar ahora . C no

es un
lenguaje idneo para este tipo de modificaciones. Otros lenguajes, como C s
oportan
de forma mucho ms flexible la posibilidad de cambiar tipos de datos, ya qu
e no obligan
al programador a modificar un gran nmero de lneas del programa.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

151
151

Un momento despus, Alicia atravesaba el cristal, y saltaba gilmente a la


habitacin del Espejo.
LEWIS CARROLL, Alicia
a travs del espejo.
Vamos a estudiar la definicin y uso de funciones en C. El concepto es el mismo qu
e ya
estudiaste al aprender Python: una funcin es un fragmento de programa parametriza
do
que efecta unos clculos y, o devuelve un valor como resultado, o tiene efectos lat
erales
(modificacin de variables globales o argumentos, volcado de informacin en pantalla
, etc.),
o ambas cosas. La principal diferencia entre Python y C estriba en el paso de pa
rmetros.
En este aspecto, C presenta ciertas limitaciones frente a Python, pero tambin cie
rtas
ventajas. Entre las limitaciones tenemos la necesidad de dar un tipo a cada parme
tro
y al valor de retorno, y entre las ventajas, la posibilidad de pasar variables e
scalares y
modificar su valor en el cuerpo de la funcin (gracias al uso de punteros).
Estudiaremos tambin la posibilidad de declarar y usar variables locales, y vo
lveremos
a tratar la recursividad. Adems, veremos cmo implementar nuestros propios mdulos
mediante las denominadas unidades de compilacin y la creacin de ficheros de cabece
ra.
Finalmente, estudiaremos la definicin y el uso de macros, una especie de pseud
ofunciones que gestiona el preprocesador de C.

En C no hay una palabra reservada (como def en Python) para iniciar la definicin
de
una funcin. El aspecto de una definicin de funcin en C es ste:
tipo de retorno identificador
parmetros
cuerpo de la funcin
El cuerpo de la funcin puede contener declaraciones de variables locales (tpicamen
te
en sus primeras lneas).
Aqu tienes un ejemplo de definicin de funcin: una funcin que calcula el logaritm
o
en base b (para b entero) de un nmero x. La hemos definido de un modo menos compa
cto
de lo que podemos hacer para ilustrar los diferentes elementos que puedes encont
rar en
una funcin:
float logaritmo float x int b
float logbase resultado
152

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


Introduccin a la programacin con C - UJI

152

logbase log10 b
resultado log10 x
return resultado

logbase

Detengmonos a analizar brevemente cada uno de los componentes de la definicin


de una funcin e identifiqumoslos en el ejemplo:
El tipo de retorno indica de qu tipo de datos es el valor devuelto por
la funcin como
resultado (ms adelante veremos cmo definir procedimientos, es decir, fun
ciones sin
valor de retorno). Puedes considerar esto como una limitacin frente a P
ython: en
C, cada funcin devuelve valores de un nico tipo. No podemos definir una
funcin
que, segn convenga, devuelva un entero, un flotante o una cadena, como
hicimos
en Python cuando nos convino.
En nuestro ejemplo, la funcin devuelve un valor de tipo float.
float logaritmo float x int b
float logbase resultado
logbase log10 b
resultado log10 x
return resultado

logbase

El identificador es el nombre de la funcin y, para estar bien formado,


debe observar
las mismas reglas que se siguen para construir nombres de variables. E
so s, no
puedes definir una funcin con un identificador que ya hayas usado para
una
variable (u otra funcin).
El identificador de nuestra funcin de ejemplo es logaritmo:
float logaritmo float x int b
float logbase resultado
logbase log10 b
resultado log10 x
return resultado

logbase

Entre parntesis aparece una lista de declaraciones de parmetros separada


s por
comas. Cada declaracin de parmetro indica tanto el tipo del mismo como s
u
identificador1 .
Nuestra funcin tiene dos parmetros, uno de tipo float y otro de tipo int
.
float logaritmo float x
float logbase resultado

int b

logbase log10 b
resultado log10 x
return resultado

logbase

1
Eso en el caso de parmetros escalares. Los parmetros de tipo vectorial se
estudiarn ms adelante.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

153
153

El cuerpo de la funcin debe ir encerrado entre llaves, aunque slo conste


de una
sentencia. Puede empezar por una declaracin de variables locales a la q
ue sigue
una o ms sentencias C. La sentencia return permite finalizar la ejecucin
de la
funcin y devolver un valor (que debe ser del mismo tipo que el indicado
como tipo
de retorno). Si no hay sentencia return, la ejecucin de la funcin finali
za tambin
al acabar de ejecutar la ltima de las sentencias de su cuerpo, pero es
un error
no devolver nada con return si se ha declarado la funcin como tal, y no
como
procedimiento.
Nuestra funcin de ejemplo tiene un cuerpo muy sencillo. Hay una declara
cin de
variables (locales) y est formado por tres sentencias, dos de asignacin
y una de
devolucin de valor:
float logaritmo float x int b
float logbase resultado
logbase log10 b
resultado log10 x
return resultado

logbase

La sentencia (o sentencias) de devolucin de valor forma(n) parte del cu


erpo y
empieza(n) con la palabra return. Una funcin puede incluir ms de una sen
tencia
de devolucin de valor, pero debes tener en cuenta que la ejecucin de la
funcin
finaliza con la primera ejecucin de una sentencia return.
float logaritmo float x int b
float logbase resultado
logbase log10 b
resultado log10 x
return resultado

logbase

La funcin logaritmo se invoca como una funcin cualquiera de


include
include
float logaritmo float x int b
float logbase resultado
logbase log10 b
resultado log10 x
return resultado

logbase

int main void


float y
y logaritmo 128.0 2
printf
y
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

154
154

return 0
Si ejecutamos el programa tenemos:

Es necesario que toda funcin se defina en el programa antes de la primera lnea


en que se usa. Por esta razn, todas nuestras funciones se definen delante de la f
uncin
main, que es la funcin que contiene el programa principal y a la que, por tanto,
no se
llama desde ningn punto del programa.2
Naturalmente, ha resultado necesario incluir la cabecera
en el pro
grama, ya
que usamos la funcin log10. Recuerda, adems, que al compilar se debe enlazar con l
a
biblioteca matemtica, es decir, se debe usar la opcin
de
.
Esta ilustracin te servir para identificar los diferentes elementos de la defi
nicin
de una funcin y de su invocacin:
Tipo de retorno
Identificador
Parmetros formale
s (o simplemente parmetros)
float logaritmo float x int b
Cabecera
float logbase resultado
Declaracin de variables locales
logbase

log10 b ;
Cuerpo

resultado
log10 x
logbase
return resultado
Sentencia de devolucin de valor
..
.
int main void
float y
..
.
y
logaritmo 128.0 2
ocacin o activacin
..
.
eales

Identificador
Llamada, inv
Argumentos o parmetros r

Ah! Te hemos dicho antes que la funcin logaritmo no es muy compacta. Podramos
haberla definido as:
float logaritmo float x int b
return log10 x

log10 b

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147 Define una funcin que reciba un int y devuelva su cuadrado.
Nuevamente hemos de matizar una afirmacin: en realidad slo es necesario que se
haya declarado el
2
prototipo de la funcin. Ms adelante daremos ms detalles.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
155
155
ramacin con C - UJI

Introduccin a la prog

c
UJI

148 Define una funcin que reciba un float y devuelva su cuadrado.


149 Define una funcin que reciba dos float y devuelva 1 (cierto) si el primero es
menor que el segundo y 0 (falso) en caso contrario.
150 Define una funcin que calcule el volumen de una esfera a partir de su radio r
.
(Recuerda que el volumen de una esfera de radio r es 4/3r 3 .)
151
El seno hiperblico de x es
ex ex
sinh =
.
2
Disea una funcin C que efecte el calculo de senos hi erblicos. (Recuerda que ex se
uede calcular con la funcin ex , dis onible incluyendo
y enlazando el rog
rama
ejecutable con la librera matemtica.)
152 Disea una funcin que devuelva cierto (el valor 1) si el ao que se le suministra
como argumento es bisiesto, y falso (el valor 0) en caso contrario.
153 La distancia de un punto (x0 , y0 ) a una recta Ax + By + C = 0 viene dada p
or
Ax0 + By0 + C

.
A2 + B 2
Disea una funcin que reciba los valores que definen una recta y los valores que de
finen
un punto y devuelva la distancia del punto a la recta.
...............................................................................
...
Veamos otro ejemplo de definicin de funcin:
int minimo int a int b int c
d=

if a
b
if a
c
return a
else
return c
else
if b
c
return b
else
return c
La funcin minimo devuelve un dato de tipo int y recibe tres datos, tambin de tipo
int. No hay problema en que aparezca ms de una sentencia return en una funcin. El
comportamiento de return es el mismo que estudiamos en Python: tan pronto se eje
cuta,
finaliza la ejecucin de la funcin y se devuelve el valor indicado.
Observa que main es una funcin. Su cabecera es int main void . Qu significa
void? Significa que no hay parmetros. Pero no nos adelantemos. En este mismo captu
lo
hablaremos de funciones sin parmetros.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
154 Define una funcin que, dada una letra minscula del alfabeto ingls, devuelva
su correspondiente letra mayscula. Si el carcter recibido como dato no es una letr
a
minscula, la funcin la devolver inalterada.
155 Qu error encuentras en esta funcin?

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
156
156
ramacin con C - UJI

Introduccin a la prog

c
UJI

int minimo int a b c


if a
b
return a
if b
a
return b
return c

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

Cada funcin puede definir sus propias variables locales definindolas en su cuerpo.
C
permite, adems, definir variables fuera del cuerpo de cualquier funcin: son las va
riables
globales.

Las variables que declaramos justo al principio del cuerpo de una funcin son vari
ables
locales.
 Este programa, por ejemplo, declara dos variables locales para calcular el
sumatorio bi=a i. La variable local a sumatorio con identificador i nada tiene que v
er con la
variable del mismo nombre que es local a main:
include
int sumatorio int a int b
int i s
s 0
for i a i
s
i
return s

Variables locales a sumatorio.


b i

int main void


int i
for i 1 i
printf

Variable local a main.


10 i
i sumatorio 1 i

return 0
Las variables locales
.
La zona en la que
slo son
visibles en el cuerpo
. . . . . . . . . . .

i y s de sumatorio slo viven durante las llamadas a sumatorio


es visible una variable es su mbito. Las variables locales
de la funcin en la que se declaran; se es su mbito.
. . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI

OS . . . . . . . . . . . . .
156 Disea una funcin que
157 Disea una funcin que
que si n es negativo, x n es

. . . . . . . . . . . . . . . . . . . . . . .
calcule el factorial de un entero n.
calcule x n , para n entero y x de tipo float. (Recuerda
el resultado de multiplicar 1/x por s mismo n veces.)

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con ISBN:
C
9788469301432
157
157
ramacin con C  UJI

Introduccin a la rog

c
UJI

Variables locales a bloques


El conce to de variable local no est limitado, en C, a las funciones. En r
ealidad, uedes
definir variables locales en cualquier bloque de un rograma. Fjate en est
e ejem lo:
include
int main void
int i
for i 0 i 3 i
int j
for j 0 j 3 j
rintf
rintf

i j

return 0
La variable j slo existe en el bloque en el que se ha declarado, es decir,
en la zona
sombreada. Ese es su mbito. La variable i tiene un mbito que engloba al de
j.
Puedes com robar, ues, que una variable local a una funcin es tambin u
na variable
local a un bloque: slo existe en el bloque que corres onde al cuer o de la
funcin.
Como ya te dijimos en un cuadro del ca tulo 1, C99 ermite declarar va
riables de
ndice de bucle de usar y tirar. Su mbito se limita al bucle. Aqu tienes un e
jem lo en
el que hemos sombreado el mbito de la variable j:
include
int main void
int i
for i 0 i 3 i
for int j 0 j 3 j
rintf
rintf

i j

return 0

158 El valor de la funcin ex puede aproximarse con el desarrollo de Taylor:


x2 x3 x4
+
+
+
2! 3! 4!
Disea una funcin que aproxime el valor de ex usando n trminos del desarrollo de
Taylor, siendo n un nmero entero positivo. (Puedes usar, si te conviene, la funcin
de
exponenciacin del ltimo ejercicio para calcular los distintos valores de x i , aun
ex 1 + x +

que hay
formas ms eficientes de calcular x/1!, x 2 /2!, x 3 /3!, . . . , sabes cmo? Plantate
cmo
generar un trmino de la forma x i /i! a partir de un trmino de la forma x i1 /(i 1)
!.)
159 El valor de la funcin coseno puede aproximarse con el desarrollo de Taylor:
cos(x) 1
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

x2 x4 x6
+

+
2! 4! 6!

158
158

Disea una funcin que aproxime el coseno de un valor x usando n trminos del desarrol
lo
de Taylor, siendo n un nmero entero positivo.
160 Disea una funcin que diga si un nmero es perfecto o no. Si el nmero es
perfecto, devolver cierto (el valor 1) y si no, devolver falso (el valor 0). Un nmero
es perfecto si es igual a la suma de todos sus divisores (excepto l mismo).
161 Disea una funcin que diga si un nmero entero es o no es capica.
................................................................................
..

Las variables globales se declaran fuera del cuerpo de cualquier funcin y son acc
esibles desde cualquier punto del programa posterior a su declaracin. Este fragmento
de
programa, por ejemplo, define una variable global i y una variable local a main
con el
mismo identificador:
include
int i

Variable global i.

int doble void


i
2
return i

Referencia a la variable global i.


Referencia a la variable global i.

int main void


int i

Variable local i.

for i 0 i 5 i
Referencias a la variable local i.
printf
doble
Ojo: el valor mostrado corresponde a la i g
lobal.
return 0
Fjate en la prdida de legibilidad que supone el uso del identificador i en dif
erentes
puntos del programa: hemos de preguntarnos siempre si corresponde a la variable
local
o global. Te desaconsejamos el uso generalizado de variables globales en tus pro
gramas. Como evitan usar parmetros en funciones, llegan a resultar muy cmodas y es fci
l
que abuses de ellas. No es que siempre se usen mal, pero se requiere una cierta
experiencia para formarse un criterio firme que permita decidir cundo resulta conve
niente
usar una variable global y cundo conviene suministrar informacin a funciones media
nte
parmetros.
Como estudiante te pueden parecer un recurso cmodo para evitar suministrar in
for-

macin a las funciones mediante parmetros. Ese pequeo beneficio inmediato es, crenos,
un lastre a medio y largo plazo: aumentar la probabilidad de que cometas errores
al
intentar acceder o modificar una variable y las funciones que definas en un prog
rama
sern difcilmente reutilizables en otros. Ests aprendiendo a programar y pretendemos
evitar que adquieras ciertos vicios, as que te prohibimos que las uses. . . salvo
cuando
convenga que lo hagas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
162 Qu muestra por pantalla este programa?
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
159
159
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
int contador

Variable global.

void fija int a


contador

int decrementa int a


contador
a
return contador
void muestra int contador
printf

contador

void cuenta atras int a


int contador
for contador a contador
printf
contador
printf

0 contador

int main void


int i
contador 10
i 1
while contador
0
muestra contador
cuenta atras contador
muestra i
decrementa i
i
2
...............................................................................
...

Puedes definir una funcin sin parmetros dejando la palabra void como contenido de
la lista de parmetros. Esta funcin definida por nosotros, por ejemplo, utiliza la
funcin
rand de
para devolver un nmero aleatorio entre 1 y 6:
int dado void
return rand

Para llamar a la funcin dado hemos de aadir un par de parntesis a la derecha de


l
identificador, aunque no tenga parmetros.
Ya te habamos anticipado que la funcin main es una funcin sin parmetros que

devuelve un entero:
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

160
160

include
include
int dado void
return rand

int main void


int i
for i 0 i 10 i
printf
dado
return 0

Calidad de los nmeros aleatorios


La funcin rand est pobremente implementada en muchas mquinas y genera patron
es
repetitivos que hacen poco aleatoria la secuencia de nmeros generada. Este
problema
se agudiza si observamos los bits menos significativos de los nmeros gener
ados. . . y
eso es, precisamente, lo que estamos haciendo con expresiones como rand
6! Una
forma de paliar este problema es usar una expresin diferente:
int dado void
return int
e

1
La constante

double rand
1

doubl

es el mayor nmero aleatorio que puede devolver ran

d. La
divisin hace que el nmero generado est en el intervalo [0, 1[.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
163 El programa
siempre genera la misma se
cuencia de nmeros aleatorios.
Para evitarlo, debes proporcionar una semilla diferente con cada ejecucin del pro
grama.
El valor de la semilla se suministra como nico argumento de la funcin srand. Modif
ica
para que solicite al usuario la introduccin del valor semilla.
................................................................................
..
Un uso tpico de las funciones sin parmetros es la lectura de datos por teclado
que deben satisfacer una serie de restricciones. Esta funcin, por ejemplo, lee un
nmero
entero de teclado y se asegura de que sea par:
int lee entero par void
int numero

scanf
numero
while numero 2
0
printf
numero
numero scanf

numero

return numero
Otro uso tpico es la presentacin de mens de usuario con lectura de la opcin
seleccionada por el usuario:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
161
161
amacin con C - UJI

Introduccin a la progr

c
UJI

int menu principal void


int opcion
do
printf
printf
printf
printf
printf
scanf
if opcion 1
opcion 4
printf
while opcion 1
opcion 4

opcion

return opcion

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
164 Disea una funcin que lea por teclado un entero positivo y devuelva el valor
ledo. Si el usuario introduce un nmero negativo, la funcin advertir del error por
pantalla y leer nuevamente el nmero cuantas veces sea menester.
...............................................................................
...

Un procedimiento, como recordars, es una funcin que no devuelve valor alguno. Los
procedimientos provocan efectos laterales, como imprimir un mensaje por pantalla, m
odificar
variables globales o modificar el valor de sus parmetros.
Los procedimientos C se declaran como funciones con tipo de retorno void. Mi
ra este
ejemplo:
include
void saludos void
printf
En un procedimiento puedes utilizar la sentencia return, pero sin devolver v
alor alguno. Cuando se ejecuta una sentencia return, finaliza inmediatamente la ejecucin
del
procedimiento.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
165 Disea un procedimiento que reciba un entero n y muestre por pantalla n asteriscos seguidos con un salto de lnea al final.
166 Disea un procedimiento que, dado un valor de n, dibuje con asteriscos un tringulo rectngulo cuyos catetos midan n caracteres. Si n es 5, por ejemplo, el proce
dimiento
mostrar por pantalla este texto:

Puedes usar, si te conviene, el procedimiento desarrollado en el ejercicio anter


ior.
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
162
162
ramacin con C - UJI

Introduccin a la prog

c
UJI

167 Disea un procedimiento que reciba un nmero entero entre 0 y 99 y muestre


por pantalla su trascripcin escrita. Si le suministramos, por ejemplo, el valor 3
1, mostrar
el texto
...............................................................................
...

Aunque modifiques el valor de un parmetro escalar en una funcin o procedimiento, e


l
valor de la variable pasada como argumento permanece inalterado. La funcin bits d
el
siguiente programa, por ejemplo, modifica en su cuerpo el valor de su parmetro nu
m:
include
int bits unsigned int num
int b

do
b
num
2
while num

return b
int main void
unsigned int numero
int bitsnumero
printf

scanf

numero
bitsnumero
printf
return 0

bits numero
bitsnumero numero

Al ejecutar el programa y teclear el nmero 128 se muestra por pantalla lo siguien


te:

Como puedes ver, el valor de numero permanece inalterado tras la llamada a b


its,
aunque en el cuerpo de la funcin se modifica el valor del parmetro num (que toma e
l
valor de numero en la llamada). Un parmetro es como una variable local, slo que su
valor inicial se obtiene copiando el valor del argumento que suministramos. As pu
es, num
no es numero, sino otra variable que contiene una copia del valor de numero. Es
lo que
se denomina paso de parmetro por valor.

Llegados a este punto conviene que nos detengamos a estudiar cmo se gestiona
la
memoria en las llamadas a funcin.

En C las variables locales se gestionan, al igual que en Python, mediante una pi


la. Cada
funcin activada se representa en la pila mediante un registro de activacin o trama
de
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

163
163

activacin. Se trata de una zona de memoria en la que se almacenan las variables l


ocales
y parmetros junto a otra informacin, como el punto desde el que se llam a la funcin.
Cuando iniciamos la ejecucin del programa
, se activa automticame
nte
la funcin main. En lla tenemos dos variables locales: numero y bitsnumero.
bitsnumero
main
numero
Si el usuario teclea el valor 128, ste se almacena en numero:
bitsnumero
main
numero 128
Cuando se produce la llamada a la funcin bits, se crea una nueva trama de activac
in:
b
bits
num
llamada desde lnea
21
bitsnumero
main
numero 128
El parmetro num recibe una copia del contenido de numero y se inicializa la varia
ble
local b con el valor 0:
b

bits
num 128
llamada desde lnea
21
bitsnumero
main
numero 128
Tras ejecutar el bucle de bits, la variable b vale 8. Observa que aunque num ha
modificado
su valor y ste provena originalmente de numero, el valor de numero no se altera:
b

bits
num
0
llamada desde lnea
21
bitsnumero
main
numero 128
La trama de activacin de bits desaparece ahora, pero dejando constancia del valor
devuelto por la funcin:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
164
164

Introduccin a la programacin con C -UJI


cUJI

return 8
bitsnumero
main
numero 128
Y, finalmente, el valor devuelto se copia en bitsnumero:

bitsnumero

main
numero 128
Como ves, las variables locales slo viven durante la ejecucin de cada funcin. C
obtiene una copia del valor de cada parmetro y la deja en la pila. Cuando modific
amos
el valor de un parmetro en el cuerpo de la funcin, estamos modificando el valor de
la
copia, no el de la variable original.
Este otro programa declara numero como una variable global y trabaja directa
mente
con dicha variable:
include
unsigned int numero
int bits void
int b

do
b
numero 2
while numero

return b
int main void
int bitsnumero
printf

scanf

numero
bitsnumero
bits
printf
bitsnumero numero
return 0
Las variables globales residen en una zona especial de la memoria y son acce
sibles
desde cualquier funcin. Representaremos dicha zona como un rea enmarcada con una
lnea discontnua. Cuando se inicia la ejecucin del programa, sta es la situacin:

variables globales

main

bitsnumero

numero
165
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
Introduccin a la programacin con C - UJI
c
UJI

165

En main se da valor a la variable global numero:


variables globales

main
numero 128

bitsnumero

Y se llama a continuacin a bits sin argumento alguno:


bits
variables globales
b
llamada desde lnea 22
main
numero 128

bitsnumero

El clculo de bits modifica el valor de numero. Tras la primera iteracin del bucle
while,
sta es la situacin:
bits
variables globales
b
1
llamada desde lnea 22
main
numero

bitsnumero
64

Cuando finaliza la ejecucin de bits tenemos:


variables globales
return
main
numero

bitsnumero
0

Entonces se copia el valor devuelto en bitsnumero:


variables globales

main
numero

bitsnumero
0

El mensaje que obtenemos en pantalla es:

Bueno. Ahora sabes qu pasa con las variables globales y cmo acceder a ellas
desde las funciones. Pero repetimos lo que te dijimos al aprender Python: pocas
veces
est justificado acceder a variables globales, especialmente cuando ests aprendiend
o.
Evtalas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168 Estudia este programa y muestra grficamente el contenido de la memoria cuando
se van a ejecutar por primera vez las lneas 24, 14 y 5.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
166
166
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
int cuadrado int i
return i

int sumatorio int a int b


int i s
s 0
for i a i b i
s
cuadrado i
return s
int main void
int i j
i 10
j 20
printf
return 0

sumatorio i j

169 Este programa muestra por pantalla los 10 primeros nmeros primos. La funcin
siguiente genera cada vez un nmero primo distinto. Gracias a la variable global u
ltimoprimo la funcin recuerda cul fue el ltimo nmero primo generado. Haz una traza
paso a paso del programa (hasta que haya generado los 4 primeros primos). Muestr
a el
estado de la pila y el de la zona de variables globales en los instantes en que
se llama
a la funcin siguienteprimo y cuando sta devuelve su resultado
include
int ultimoprimo

int siguienteprimo void


int esprimo

do
ultimoprimo
esprimo 1
for i 2 i ultimoprimo 2 i
if ultimoprimo i 0
esprimo 0
break
while esprimo
return ultimoprimo
int main void

int i
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
ntroduccin a la programacin con C - UJI
c
UJI

167
167

printf
for i 0 i 10 i
printf
siguienteprimo
return 0
...............................................................................
...
No hay problema con que las variables locales a una funcin sean vectores. Su
contenido se almacena siempre en la pila. Este programa, por ejemplo, cuenta la
cantidad
de nmeros primos entre 1 y el valor que se le indique (siempre que no supere cier
ta
constante ) con la ayuda de la criba de Eratstenes. El vector con el que se efecta
la
criba es una variable local a la funcin que efecta el conteo:
include
define

10

int cuenta primos int n

Cuenta el nmero de primos entre 1 y

n.
char criba
int i j numprimos
Comprobemos que el argumento es vlido
if n
return 1
Devolvemos 1 si no es vlido.
Inicializacin
criba 0
0
for i 1 i n i
criba i
1
Criba de Eratstenes
for i 2 i n i
if criba i
for j 2 i j n j
criba i j 0
Conteo de rimos
num rimos 0
for i 0 i n i
if criba i
num rimos
return num rimos

int main void


int hasta cantidad
rintf
cantidad cuenta rimos hasta
if cantidad
1
rintf

scanf

hasta

1
else
rintf

hasta cantid

ad
return 0
Introduccin a la rogramacin con C
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
cUJI
Introduccin a la

rogramacin con C UJI

168
168

Cuando el rograma inicia su ejecucin, se crea una trama de activacin en la qu


e se
albergan las variables hasta y cantidad. Su ongamos que cuando se solicita el va
lor de
hasta el usuario introduce el valor 6. He aqu el as ecto de la memoria:

hasta
6
main
cantidad
Se efecta entonces (lnea 40) la llamada a cuenta rimos, con lo que se crea un
a
nueva trama de activacin. En ella se reserva memoria ara todas las variables loc
ales
de cuenta rimos:

n
0
2

9
criba

cuenta_ rimos
j
i
num rimos
llamada
desde lnea 40
hasta

main
cantidad
Observa que el vector criba ocu a memoria en la ro ia trama de activacin. Com le
ta
t mismo el resto de acciones ejecutadas or el rograma ayudndote de una traza de
la
ila de llamadas a funcin con grficos como los mostrados.

Te hemos dicho en el a artado 2.1 que los vectores han de tener talla conocida e
n tiem o
de com ilacin. Es hora de matizar esa afirmacin. Los vectores locales a una funcin
ueden determinar su talla en tiem o de ejecucin. Veamos un ejem lo:
include
int cuenta rimos int n
y n.

Cuenta el nmero de rimos entre 1

char criba n
int i j num rimos
Inicializacin
criba 0
0
for i 1 i n i
criba i
1
Criba de Eratstenes
for i 2 i n i
Introduccin
Andrs aMarzal/Isabel
la rogramacin con CISBN: 9788469301432
Gracia
169
169

cUJI
Introduccin a la rogramacin con C UJI

if criba i
for j 2 i j n j
criba i j 0
Conteo de rimos
num rimos 0
for i 0 i n i
if criba i
num rimos
return num rimos

int main void


int hasta cantidad
rintf

scanf

has

ta
cantidad
rintf

cuenta rimos hasta


hasta cant

idad
return 0
Fjate en cmo hemos definido el vector criba: la talla no es un valor constante
, sino
n, un armetro cuyo valor es desconocido hasta el momento en que se ejecute la fu
ncin.
Esta es una caracterstica de C99 y su one una mejora interesante del lenguaje.

Este rograma ilustra el modo en que odemos declarar y asar


es
a una funcin:
include
define

void incrementa int a


int i
for i 0 i
a i

int main void


int i v
rintf
for i 0 i
v i
i
rintf

i
i v i

armetros vectorial

incrementa v
rintf
Introduccin
Andrs aMarzal/Isabel
la rogramacin con CISBN: 9788469301432
Gracia
Introduccin a la rogramacin con C UJI
cUJI

170
170

for i 0 i
rintf
return 0

i
i v i

Fjate en cmo se indica que el armetro a es un vector de enteros: aadiendo un ar


de corchetes a su identificador. En la lnea 23 asamos a incrementa el vector v. Q
u
ocurre cuando modificamos com onentes del armetro vectorial a en la lnea 10?
Si ejecutamos el rograma obtenemos el siguiente texto en antalla:

El contenido de v se ha modificado! Ocurre lo mismo que ocurra en Python: los


vectores s modifican su contenido cuando se altera el contenido del res ectivo a
rmetro
en las llamadas a funcin.
Cuando se asa un armetro vectorial a una funcin no se efecta una co ia de
su contenido en la ila: slo se co ia la referencia a la osicin de memoria en la
que em ieza el vector. Por qu? Por eficiencia: no es infrecuente que los rogramas
manejen vectores de tamao considerable; co iarlos cada vez en la ila su ondra inv
ertir
una cantidad de tiem o que, ara vectores de tamao medio o grande, odra ralentiza
r
drsticamente la ejecucin del rograma. La a roximacin ado tada or C hace que slo
sea necesario co iar en la ila 4 bytes, que es lo que ocu a una direccin de memo
ria. Y
no im orta cun grande o equeo sea un vector: la direccin de su rimer valor siem r
e
ocu a 4 bytes.
Veamos grficamente, ues, qu ocurre en diferentes instantes de la ejecucin del
rograma. Justo antes de ejecutar la lnea 23 tenemos esta dis osicin de elementos
en
memoria:
0
1

2
v
main

2
i

En el momento de ejecutar la lnea 10 or


memoria resenta este as ecto:

rimera vez, en la funcin incrementa, la

a
incrementa
i

0
llamada d

esde lnea 23
0
1

v
main
1

2
i

Andrs Marzal/Isabel
Introduccin
Gracia
a la rogramacin con CISBN: 9788469301432
171
171

Introduccin a la rogramacin con C  UJI


UJI
c

Ves? El armetro a a unta a v. Los cambios sobre elementos del vector a que tienen
lugar al ejecutar la lnea 10 tienen efecto sobre los corres ondientes elementos d
e v,
as que v refleja los cambios que ex erimenta a. Tras ejecutar el bucle de increme
nta,
tenemos esta situacin:
a
incrementa
i

3
llamada de

sde lnea 23
0
1

2
v
main

3
i

Y una vez ha finalizado la ejecucin de incrementa, sta otra:


0
1

2
v
main

3
i

Y qu ocurre cuando el vector es una variable global? Pues bsicamente lo mismo:


las referencias no tienen or qu ser direcciones de memoria de la ila. Este rog
rama
es bsicamente idntico al anterior, slo que v es ahora una variable global:
include
define

int v
void incrementa int a
int i
for i 0 i
a i
int main void
int i

rintf
for i 0 i
v i
i
rintf

incrementa v
rintf
for i 0 i
rintf
return 0

i v i

i v i

Andrs aMarzal/Isabel
Introduccin
Gracia
la rogramacin con CISBN: 9788469301432
172
172

Introduccin a la rogramacin con C UJI


cUJI

Analicemos qu ocurre en diferentes instantes de la ejecucin del rograma. Just


o
antes de ejecutar la lnea 24, existen las variables locales a main y las variable
s globales:
variables globales

main
1

Al llamar a incrementa se suministra un untero a la zona de memoria de variable


s
globales, ero no hay roblema alguno: el armetro a es un untero que a unta a e
sa
direccin.
a
variables globales
incrementa

i
0
llamada desde lnea 24

main
1

Los cambios al contenido de a se manifiestan en v:


a
variables globales
incrementa

i
3
llamada desde lnea 24

main
2

Y una vez ha finalizado la ejecucin de incrementa, el contenido de v queda modifi


cado:
variables globales

main
2

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
170 Disea un programa C que manipule polinomios de grado menor o igual que
10. Un polinomio se representar con un vector de float de tamao 11. Si p es un vec
tor
que representa un polinomio, p i es el coeficiente del trmino de grado i. Disea un
procedimiento suma con el siguiente perfil:
void suma float p
fl
oat q
float r
El procedimiento modificar r para que contenga el resultado de sumar los polinomi
os p
y q.
171 Disea una funcin que, dada una cadena y un carcter, diga cuntas veces
aparece el carcter en la cadena.
...............................................................................
...
Hemos visto cmo pasar vectores a funciones. Has de ser consciente de que no h
ay
forma de saber cuntos elementos tiene el vector dentro de una funcin: fjate en que
no
se indica cuntos elementos tiene un parmetro vectorial. Si deseas utilizar el valo
r de
la talla de un vector tienes dos posibilidades:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
173
173
ramacin con C - UJI

Introduccin a la prog

c
UJI

1.

saberlo de antemano,

2.
o proporcionarlo como parmetro adicional.
Estudiemos la primera alternativa. Fjate en este fragmento de programa:
include
define
define

20
10

void inicializa int z


int i
for i 0 i
z i 0

void imprime int z


int i
for i 0 i
printf
printf

i
z i

int main void


int x
int y
!
inicializa x
inicializa y

Ojo!
!

imprime x
imprime y

Ojo!

return 0
Siguiendo esta aproximacin, la funcin inicializa slo se puede utilizar con vectores
de
int de talla
, como x. No puedes llamar a inicializa con y: si lo haces (y
C te deja
hacerlo!) cometers un error de acceso a memoria que no te est reservada, pues el b
ucle
recorre
componentes, aunque y slo tenga
. Ese error puede abor
tar la
ejecucin del programa o, peor an, no hacindolo pero alterando la memoria de algn
modo indefinido.
Este es el resultado obtenido en un ordenador concreto:

El programa no ha abortado su ejecucin, pero ha mostrado 20 valores del vector


y,
que slo tiene 10.
Cmo podemos disear una funcin que pueda trabajar tanto con el vector x como
con el vector y? Siguiendo la segunda aproximacin propuesta, es decir, pasando co
mo
parmetro adicional la talla del vector en cuestin:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

174
174

La dualidad vector/puntero, el paso de vectores y el paso por referencia


Al pasar un vector a una funcin pasamos una direccin de memoria: el inicio d
e la zona
reservada para el vector. Cuando pasamos una variable escalar por referenc
ia tambin
pasamos una direccin de memoria: aquella en la que empieza la zona reservad
a para
el valor escalar. Qu diferencia hay entre una y otra direccin? Ninguna: un pu
ntero
siempre es un puntero. Fjate en este programa:
include
define

10

void procedimiento int a

int b
!

printf
printf

a b 0
a 0 b

Ojo!

int main void


int x

i y

10

for i 0 i
i
printf
procedimiento y x
printf
procedimiento x y
printf
procedimiento x 0

x i

x 1

return 0
Esta es la salida resultante de su ejecucin:

Observa qu ha ocurrido: en procedimiento se puede usar a y b como si fu


eran
vectores o escalares pasados por referencia. Y podemos pasar a procedimien
to tanto
la direccin de un vector de ints como la de un escalar de tipo int. La conc
lusin es
clara: int a e int b son sinnimos cuando se declara un parmetro, pues en
ambos casos se describen punteros a direcciones de memoria en las que resi
den sendos
valores enteros (o donde empieza una serie de valores enteros). Aunque sea
n expresiones
sinnimas y, por tanto, intercambiables, interesa que las uses correctamente,
pues as
mejorar la legibilidad de tus programas: usa int cuando quieras pasar la di
reccin
de un entero y int
cuando quieras pasar la direccin de un vector de ent

eros.

include
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

175
175

define
define
void inicializa int z

20
10
int talla

int i
for i 0 i talla i
z i 0
void imprime int z

int talla

int i
for i 0 i talla i
printf
z i
printf

int main void


int x
int y
inicializa x
inicializa y
imprime x
imprime y
return 0
Ahora puedes llamar a la funcin inicializa con inicializa x
o
inicializa y
. Lo mismo ocurre con imprime. El parmetro talla toma el valor ap
ropiado
en cada caso porque t se lo ests pasando explcitamente.
ste es el resultado de ejecutar el programa ahora:

Correcto.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
172 Disea un procedimiento ordena que ordene un vector de enteros. El procedimiento recibir como parmetros un vector de enteros y un entero que indique el tamao
del vector.
173 Disea una funcin que devuelva el mximo de un vector de enteros. El tamao
del vector se suministrar como parmetro adicional.
174 Disea una funcin que diga si un vector de enteros es o no es palndromo
(devolviendo 1 o 0, respectivamente). El tamao del vector se suministrar como parme
tro
adicional.
175 Disea una funcin que reciba dos vectores de enteros de idntica talla y diga si
son iguales o no. El tamao de los dos vectores se suministrar como parmetro adicion
al.

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
176
176
c
UJI
Introduccin a la prog
ramacin con C - UJI

176 Disea un procedimiento que reciba un vector de enteros y muestre todos sus
componentes en pantalla. Cada componente se representar separado del siguiente co
n
una coma. El ltimo elemento ir seguido de un salto de lnea. La talla del vector se
indicar con un parmetro adicional.
177 Disea un procedimiento que reciba un vector de float y muestre todos sus
componentes en pantalla. Cada componente se representar separado del siguiente co
n
una coma. Cada 6 componentes aparecer un salto de lnea. La talla del vector se ind
icar
con un parmetro adicional.
...............................................................................
...

C permite modificar el valor de variables escalares en una funcin recurriendo a s


us
direcciones de memoria. Analicemos el siguiente ejemplo:
include
void incrementa int
a

int main void


int b
b 1
printf
incrementa
printf
return 0

b
b
b

Al ejecutarlo, aparece en pantalla el siguiente texto:

Efectivamente, b ha modificado su valor tras la llamada a incrementa. Observ


a la
forma en que se ha declarado el nico parmetro de incrementa: int a. O sea, a es de
l
tipo int . Un tipo de la forma tipo significa puntero a valor de tipo tipo. Tenemos
,
por tanto, que a es un puntero a entero. No le pasamos a la funcin el valor de un
entero, sino el valor de la direccin de memoria en la que se encuentra un entero.
Fjate ahora en cmo pasamos el argumento en la llamada a incrementa de la lnea
14, que es de la forma incrementa b . Estamos pasando la direccin de memoria de b
(que es lo que proporciona el operador ) y no el valor de b. Todo correcto, ya q
ue hemos
dicho que la funcin espera la direccin de memoria de un entero.
Al principio de la ejecucin de incrementa tendremos esta situacin:

incrementa

a
llamada desde lnea

14
main
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
177
177

Introduccin a la programacin con C - UJI


c
UJI

El parmetro a es un puntero que apunta a b. Fjate ahora en la sentencia que


incrementa el valor apuntado por a (lnea 5):
a

El asterisco que precede a a no indica multiplicacin. Ese asterisco es un operador


unario que hace justo lo contrario que el operador : dada una direccin de memoria
,
accede al valor de la variable apuntada. (Recuerda que el operador obtena la dire
ccin
de memoria de una variable.) O sea, C interpreta a como accede a la variable apu
ntada
por a, que es b, as que a
1 equivale a b 1 e incrementa el contenido de la
variable
b.
El & de los parmetros de scanf
Ahora ya puedes entender bien por qu las variables escalares que suministr
amos a
scanf para leer su valor por teclado van precedidas por el operador : com
o scanf debe
modificar su valor, ha de saber en qu direccin de memoria residen. No ocurr
e lo mismo
cuando vamos a leer una cadena, pero eso es porque el identificador de la
variable ya
es, en ese caso, una direccin de memoria.

Qu pasara si en lugar de a
1 hubisemos escrito a
1? Se hubiera incrementado la direccin de memoria a la que apunta el puntero, nada ms.
Y si hubisemos escrito a ? Lo mismo: hubisemos incrementado el valor de la
direccin almacenada en a. Y a ?, funcionara? A primera vista diramos que s, pero
no funciona como esperamos. El operador
postfijo tiene mayor nivel de pre
cedencia
que el operador unario , as que a (post)incrementa la direccin a y accede a su
contenido, por ese rden. Nuevamente habramos incrementado el valor de la direccin
de memoria, y no su contenido. Si quieres usar operadores de incremento/decremen
to,
tendrs que utilizar parntesis para que los operadores se apliquen en el orden dese
ado:
a
.
Naturalmente, no slo puedes acceder as a variables locales, tambin las variable
s
globales son accesibles mediante punteros:
include
int b

Variable global.

void incrementa int


a

int main void


b 1
printf

incrementa
printf
return 0

b
b

El aspecto de la memoria cuando empieza a ejecutarse la funcin incrementa e


s ste:

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
8

Introduccin a la programacin con C -UJI


cUJI

178
17

variables globales
incrementa

a
llamada desde lnea 14
main

b 1

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
178 Disea un procedimiento que modifique el valor del parmetro de tipo float para
que valga la inversa de su valor cuando ste sea distinto de cero. Si el nmero es c
ero,
el procedimiento dejar intacto el valor del parmetro.
Si a vale 2.0, por ejemplo, inversa a har que a valga 0.5.
179 Disea un procedimiento que intercambie el valor de dos nmeros enteros.
Si a y b valen 1 y 2, respectivamente, la llamada intercambia a b har que a
pase a valer 2 y b pase a valer 1.
180 Disea un procedimiento que intercambie el valor de dos nmeros float.
181 Disea un procedimiento que asigne a todos los elementos de un vector de
enteros un valor determinado. El procedimiento recibir tres datos: el vector, su
nmero
de elementos y el valor que que asignamos a todos los elementos del vector.
182 Disea un procedimiento que intercambie el contenido completo de dos vectores
de enteros de igual talla. La talla se debe suministrar como parmetro.
183 Disea un procedimiento que asigne a un entero la suma de los elementos de
un vector de enteros. Tanto el entero (su direccin) como el vector se suministrarn
como
parmetros.
...............................................................................
...
Un uso habitual del paso de parmetros por referencia es la devolucin de ms
de un valor como resultado de la ejecucin de una funcin. Vemoslo con un ejemplo.
Diseemos una funcin que, dados un ngulo (en rdines) y un rdio r, clcule el
vlor de x = r cos() e y = r sin():

r
y

No podemos diser un funcin que devuelv los dos vlores. Hemos de diser un procedimiento que devuelv los vlores resultntes como prmetros psdos por refere
nci:

include
include

void clcul xy flot lf flot rdio flot


flot
y
x
y

rdio
rdio

cos lf
sin lf

Andrs Mrzl/Isbel Grci - ISBN: 978-84-693-0143-2


179
mcin con C - UJI
Introduccin  l progrmcin con C
179
c
UJI

Introduccin  l progr

En C slo hy pso por vlor


Este prtdo intent que prends  distinguir el pso de prmetros por v
lor y por
referenci. Pero l relidd es que C slo tiene pso de prmetros por vlor!
Cundo
pss un referenci, ests psndo explcitmente un direccin de memori gr
cis
l operdor , y lo que hce C es copir dich direccin en l pil, es deci
r, ps por
vlor un direccin pr simulr el pso de prmetros por referenci. L ext
r form
de psr el prmetro hce que tengs que usr el operdor cd vez que des
es
cceder  l en el cuerpo de l funcin.
En otros lengujes, como Pscl, es posible indicr que un prmetro se
ps por
referenci sin que tengs que usr un operdor (equivlente ) l efectu
r el pso
o un operdor (equivlente ) cundo uss el prmetro en el cuerpo de l f
uncin.
Por ejemplo, este progrm Pscl incluye un procedimiento que modific e
l vlor de
su prmetro:

pr

C es un extensin de C que permite el pso de prmetros por referenci. Us


ello el crcter en l declrcin del prmetro:
include
void increment int

int min void


int b
b 1
printf
increment b
printf
return 0

b
b

(Aunque no veng  cuento, observ lo diferente que es C de Pscl (y un


s, lo
semejnte que es) y cmo el progrm C present un specto muy semejnte 
uno
equivlente escrito en C.)

Y cmo llmmos l procedimiento? Aqu tienes un ejemplo de uso:

Andrs Mrzl/Isbel
Introduccin
Grci
l progrmcin con -CISBN: 978-84-693-0143-2
Introduccin  l progrmcin con C -UJI
cUJI

180
180

int min void


flot r ngulo horizontl verticl
printf

scnf
printf
r
clcul xy ngulo r
printf
verticl

ngulo

scnf

horizontl

verticl
horizontl

return 0
Ves? Ls vribles horizontl y verticl no se inicilizn en min: reciben vlor
es como
resultdo de l llmd  clcul xy.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
184 Disea una funcin que calcule la inversa de calcula xy, es decir, que obtenga
el valor del radio y del ngulo a partir de x e y.
185 Disea una funcin que reciba dos nmeros enteros a y b y devuelva, simultneamente, el menor y el mayor de ambos. La funcin tendr esta cabecera:
void minimax int a int b int
min int
max
186 Disea una funcin que reciba un vector de enteros, su talla y un valor de tipo
entero al que denominamos buscado. La funcin devolver (mediante return) el valor 1
si
buscado tiene el mismo valor que algn elemento del vector y 0 en caso contrario.
La
funcin devolver, adems, la distancia entre buscado y el elemento ms prximo a l.
La cabecera de la funcin ha de ser similar a sta:
int busca int vector
int talla int buscado int
distancia
Te ponemos un par de ejemplos para que veas qu debe hacer la funcin.
include
define

Define aqu la funcin


int main void
int v

distancia encontrado buscado i

for i 0 i
printf

i
scanf
printf
scanf
encontrado busca v
istancia
if encontrado

v i

buscado
buscado

printf

uscado
else
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
181
181
c
UJI
Introduccin a la prog
ramacin con C - UJI

printf
distancia
printf
scanf

buscado

encontrado busca v
if encontrado
printf
else
printf
distancia

buscado

distancia
buscado

return 0
Al ejecutar el programa obtenemos esta salida por pantalla:

187 Modifica la funcin del ejercicio anterior para que, adems de la distancia al
elemento ms prximo, devuelva el valor del elemento ms prximo.
188 Modifica la funcin del ejercicio anterior para que, adems de la distancia al
elemento ms prximo y el elemento ms prximo, devuelva el valor de su ndice.
...............................................................................
...

No slo puedes pasar escalares y vectores como argumentos, tambin puedes pasar registros. El paso de registros es por valor, o sea, copiando el contenido en la p
ila, a menos
que t mismo pases un puntero a su direccin de memoria.
Este programa, por ejemplo, define un tipo de datos para representar puntos
en un
espacio de tres dimensiones y una funcin que calcula la distancia de un punto al
origen:
include
include
struct Punto
float x y z
float distancia struct Punto p
return sqrt p x p x

py py

pz pz

int main void


struct Punto pto
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
2

18
182

Introduccin a la programacin con C - UJI


c
UJI

pto x
pto y
pto z

1
1
1

printf
return 0

distancia pto

Al pasar un registro a la funcin, C copia en la pila cada uno de los valores de s


us
campos. Ten en cuenta que una variable de tipo struct Punto ocupa 24 bytes (cont
iene 3
valores de tipo float). Variables de otros tipos registro que definas pueden ocu
par cientos
o incluso miles de bytes, as que ve con cuidado: llamar a una funcin pasando regis
tros
por valor puede resultar ineficiente. Por cierto, no es tan extrao que un registr
o ocupe
cientos de bytes: uno o ms de sus campos podra ser un vector. Tambin en ese caso se
estara copiando su contenido ntegro en la pila.
Eso s, como ests pasando una copia, las modificaciones del valor de un campo e
n
el cuerpo de la funcin no tendrn efectos perceptibles fuera de la funcin.
Como te hemos anticipado, tambin puedes pasar registros por referencia. En ta
l caso
slo se estar copiando en la pila la direccin de memoria en la que empieza el regist
ro
(y eso son 4 bytes), mida lo que mida ste. Se trata, pues, de un paso de parmetros
ms
eficiente. Eso s, has de tener en cuenta que los cambios que efectes a cualquier c
ampo
del parmetro se reflejarn en el campo correspondiente de la variable que suministr
aste
como argumento.
Esta funcin, por ejemplo, define dos parmetros: uno que se pasa por referencia
y
otro que se pasa por valor. La funcin traslada un punto p en el espacio (modifica
ndo los
campos del punto original) de acuerdo con el vector de desplazamiento que se ind
ica con
otro punto (traslacion):
void traslada struct Punto
p struct Punto traslacion
p x
p y
p z

traslacion x
traslacion y
traslacion z

Observa cmo hemos accedido a los campos de p. Ahora p es una direccin de memoria
(es de tipo struct Punto ), y p es la variable apuntada por p (y por tanto, es d
e tipo
struct Punto). El campo x es accedido con p x: primero se accede al contenido de
la
direccin de memoria apuntada por p, y luego al campo x del registro p, de ah que
usemos parntesis.
Es tan frecuente la notacin p x que existe una forma compacta equivalente:
void traslada struct Punto
p struct Punto traslacion
p
p

x
y

traslacion x
traslacion y

traslacion z

La forma p x es absolutamente equivalente a p x.


Recuerda, pues, que dentro de una funcin se accede a los campos de forma dist
inta
segn se pase un valor por copia o por referencia:
1.
con el operador punto, como en traslacion x, si la variable se ha pasa
do por valor;
2.
erencia

con el operador flecha, como en p x, si la variable se ha pasado por ref


(equivalentemente, puedes usar la notacin p x).

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
cUJI
Introduccin a la programacin con C -UJI

183
183

Acabemos este apartado mostrando una rutina que pide al usuario que introduz
ca las
coordenadas de un punto:
void lee punto struct Punto
p
printf
printf
printf

scanf
scanf
scanf

p
p
p

x
y
z

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
189 Este ejercicio y los siguientes de este bloque tienen por objeto construir u
na serie
de funciones que permitan efectuar transformaciones afines sobre puntos en el pl
ano. Los
puntos sern variables de tipo struct Punto, que definimos as:
struct Punto
float x y
Disea un procedimiento muestra punto que muestre por pantalla un punto. Un punto
p
tal que p x vale 2.0 y p y vale 0.2 se mostrar en pantalla as: 2.000000 0.200000 .
El
procedimiento muestra punto recibir un punto por valor.
Disea a continuacin un procedimiento que permita leer por teclado un punto. El
procedimiento recibir por referencia el punto en el que se almacenarn los valores
ledos.
190 La operacin de traslacin permite desplazar un punto de coordenadas (x, y) a
(x + a, y + b), siendo el desplazamiento (a, b) un vector (que representamos con
otro
punto). Implementa una funcin que reciba dos parmetros de tipo punto y modifique e
l
primero de modo que se traslade lo que indique el vector.
191 La operacin de escalado transforma un punto (x, y) en otro (ax, ay), donde a
es
un factor de escala (real). Implementa una funcin que escale un punto de acuerdo
con el
factor de escala a que se suministre como parmetro (un float).
192 Si rotamos un punto (x, y) una cantidad de radianes alrededor del origen,
obtenemos el punto
(x cos y sin , x sin + y cos ).
Define una funcin ue rote un punto la cantidad de grados ue se especifiue.
193 La rotacin de un punto (x, y) una cantidad de radianes alrededor de un punto
(a, b) se puede efectuar con una traslacin con el vector (a, b), una rotacin de
radianes con respecto al origen y una nueva traslacin con el vector (a, b). Disea
una
funcin ue permita trasladar un punto un nmero dado de grados alrededor de otro
punto.
194 Disea una funcin que diga si dos puntos son iguales.
195 Hemos definido un tipo registro para representar complejos as:
struct Complejo
float real
float imag
Disea e implementa los siguientes procedimientos para su manipulacin:

leer un complejo de teclado;


mostrar un complejo por pantalla;
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
184
184
ramacin con C - UJI

Introduccin a la prog

c
UJI


el mdulo de un complejo (|a + bi| =

a2 + b2 );

el opuesto de un complejo ((a + bi) = a bi);


el conjugado de un complejo (a + bi = a bi);
la suma de dos complejos ((a + bi) + (c + di) = (a + c) + (b + d)i);
la diferencia de dos complejos ((a + bi) (c + di) = (a c) + (b d)i);
el producto de dos complejos ((a + bi) (c + di) = (ac bd) + (ad + bc)i
);
la divisin de dos complejos ( a+bi
c+di =
ac+bd
c2 +d2
+

bcad
c2 +d2
i

).
196 Define un tipo registro y una serie de funciones para representar y manipula
r
fechas. Una fecha consta de un da, un mes y un ao. Debes implementar funciones que
permitan:
mostrar una fecha por pantalla con formato dd mm aaaa (por ejemplo, el
7 de junio
de 2001 se muestra as: 07 06 2001);
mostrar una fecha por pantalla como texto (por ejemplo, el 7 de junio
de 2001 se
muestra as:

);

leer una fecha por teclado;


averiguar si una fecha cae en ao bisiesto;
averiguar si una fecha es anterior, igual o posterior a otra, devolvie
ndo los valores
1, 0 o 1 respectivamente,
comprobar si una fecha existe (por ejemplo, el 29 de febrero de 2002 n
o existe):
calcular la diferencia de das entre dos fechas.
...............................................................................
...

El paso de vectores multidimensionales no es una simple extensin del paso de vect


ores
unidimensionales. Veamos. Au tienes un programa incorrecto en el ue se define u
na
funcin ue recibe una matriz y devuelve su elemento mximo:

include
define

int maximo int a


int i j m
m a 0 0
for i 0 i
for j 0 j
if a i j
m a i

i
j
m
j

return m
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la programacin con C  UJI
Introduccin a la programacin con C
c
UJI

185
185

int main void


int matriz
int i j
for i 0 i
for j 0 j
matriz i

i
j
i j

printf
return 0

maximo matriz

El compilador no acepta ese programa. Por u? Fjate en la declaracin del parmetro.
Qu hay de malo? C no puede resolver los accesos de la forma a i j . Si recuerdas,
a i j significa accede a la celda cuya direccin se obtiene sumando a la direccin
a el valor i
+ j, donde
es el nmero de columnas de la mat
riz
a (en nuestro caso, sera
). Pero, cmo sabe la funcin cuntas columnas tiene a?
No hay forma de saberlo viendo una definicin del estilo int a
!
La versin correcta del programa debe indicar explcitamente cuntas columnas tien
e
la matriz. Hela au:
include
define

int maximo int a


int i j m
m a 0 0
for i 0 i
for j 0 j
if a i j
m a i

i
j
m
j

return m
int main void
int matriz
int i j
for i 0 i
for j 0 j
matriz i
printf
return 0

i
j

j
i j
maximo matriz

No ha sido necesario indicar cuntas filas tiene la matriz (aunue somos libres de
hacerlo).
La razn es sencilla: el nmero de filas no hace falta para calcular la direccin en l
a ue
reside el valor a i j .
As pues, en general, es necesario indicar explcitamente el tamao de cada una de

las
dimensiones del vector, excepto el de la primera (ue puedes declarar o no, a vo
luntad).
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
cUJI
Introduccin a la programacin con C UJI

186
186

Slo as obtiene C informacin suficiente para calcular los accesos a elementos del ve
ctor
en el cuerpo de la funcin.
Una consecuencia de esta restriccin es ue no podremos definir funciones capa
ces
de trabajar con matrices de tamao arbitrario. Siempre hemos de definir explcitamen
te
el tamao de cada dimensin excepto de la primera. Habr una forma de superar este
inconveniente, pero tendremos ue esperar al siguiente captulo para poder estudia
rla.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
197 Vamos a disear un programa capaz de jugar al tres en raya. El tablero se
representar con una matriz de 3 3. Las casillas sern caracteres. El espacio en bla
nco
representar una casilla vaca; el carcter
repr
esentar una casilla ocupada con un
crculo y el carcter
representar una casilla marcada con un
a cruz.
Disea una funcin que muestre por pantalla el tablero.
Disea una funcin que detecte si el tablero est lleno.
Disea una funcin que detecte si algn jugador consigui hacer tres en raya.
Disea una funcin que solicite al usuario la jugada de los crculos y modif
ique el
tablero adecuadamente. La funcin debe detectar si la jugada es vlida o n
o.
Disea una funcin que, dado un tablero, realice la jugada que corresponde
a las
cruces. En una primera versin, haz que el ordenador ponga la cruz en la
primera
casilla libre. Despus, modifica la funcin para que el ordenador realice
la jugada
ms inteligente.
Cuando hayas diseado todas las funciones, monta un programa que las use y permita
jugar al tres en raya contra el computador.
198 El juego de la vida se juega sobre una matriz cuyas celdas pueden estar viva
s o
muertas. La matriz se modifica a partir de su estado siguiendo unas sencilla reg
las que
tienen en cuenta los, como mucho, 8 vecinos de cada casilla:
Si una celda viva est rodeada por 0 o 1 celdas vivas, muere de soledad.
Si una celda viva est rodeada por 4 celdas vivas, muere por superpoblac
in.
Si una celda viva est rodeada por 2 o 3 celdas vivas, sigue viva.
Una celda muerta slo resucita si est rodeada por 3 celdas vivas.
Disea una funcin que reciba una matriz de 1010 celdas en la que el valor 0 represen
ta
celda muerta y el valor 1 representa celda viva. La funcin modificar la matriz de
acuerdo con las reglas del juego de la vida. (Avisos: Necesitars una matriz auxil

iar. Las
celdas de los bordes no tienen 8 vecinos, sino 3 o 5.)
A continuacin, monta un programa que permita al usuario introducir una dispos
icin
inicial de celdas y ejecutar el juego de la vida durante n ciclos, siendo n un v
alor
introducido por el usuario.
Aqu tienes un ejemplo de partida de 3 ciclos con una configuracin inicial curios
a:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
187
187
ramacin con C - UJI

Introduccin a la prog

c
UJI

199 Implementa el juego del buscaminas. El juego del buscaminas se juega en un


tablero de dimensiones dadas. Cada casilla del tablero puede contener una bomba
o estar
vaca. Las bombas se ubican aleatoriamente. El usuario debe descubrir todas las ca
sillas
que no contienen bomba. Con cada jugada, el usuario descubre una casilla (a part
ir de
sus coordenadas, un par de letras). Si la casilla contiene una bomba, la partida
finaliza
con la derrota del usuario. Si la casilla est libre, el usuario es informado de c
untas
bombas hay en las (como mucho) 8 casillas vecinas.
Este tablero representa, en un terminal, el estado actual de una partida sob
re un
tablero de 8 8:

Las casillas con un punto no han sido descubiertas an. Las casillas con un nmero h
an
sido descubiertas y sus casillas vecinas contienen tantas bombas como se indica
en el
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
c
UJI
Introduccin a la programacin con C - UJI

188
188

nmero. Por ejemplo, la casilla de coordenadas ( ,


) tiene 3 bombas en la ve
cindad
y la casilla de coordenadas (
, ), ninguna.
Implementa un programa que permita seleccionar el nivel de dificultad y, una
vez
escogido, genere un tablero y permita jugar con l al jugador.
Los niveles de dificultad son:
fcil: tablero de 8 8 con 10 bombas.
medio: tablero de 15 15 con 40 bombas.
difcil: tablero de 20 20 con 100 bombas.
Debes disear funciones para desempear cada una de las acciones bsicas de una
partida:
dado un tablero y las coordenadas de una casilla, indicar si contiene
bomba o no,
dado un tablero y las coordenadas de una casilla, devolver el nmero de
bombas
vecinas,
dado un tablero y las coordenadas de una casilla, modificar el tablero
para indicar
que la casilla en cuestin ya ha sido descubierta,
dado un tablero, mostrar su contenido en pantalla,
etc.
...............................................................................
...

Una funcin puede devolver valores de cualquier tipo escalar o de registros, pero
no puede
devolver vectores3 . La razn es simple: la asignacin funciona con valores escalare
s y
registros, pero no con vectores.
Ya hemos visto cmo devolver valores escalares. A ttulo ilustrativo te presenta
mos un
ejemplo de definicin de registro y definicin de funcin que recibe como parmetros un
punto (x, y) y un nmero y devuelve un nuevo punto cuyo valor es (ax, ay):
struct Punto
float x y
struct Punto escala struct Punto p float a
struct Punto q
qx
qy

a
a

px
py

return q
Eso es todo. . . por el momento. Volveremos a la cuestin de si es posible devolve
r vectores
cuando estudiemos la gestin de memoria dinmica.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
200 Vuelve a implementar las funciones de manipulacin de puntos en el plano
(ejercicios 189194) para que no modifiquen el valor del registro struct Punto que
se
suministra como parmetro. En su lugar, devolvern el punto resultante como valor de

retorno de la llamada a funcin.


3
Al menos no hasta que sepamos ms de la gestin de memoria dinmica
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
189
189
ramacin con C - UJI

Introduccin a la prog

c
UJI

201 Implementa nuevamente las funciones del ejercicio 195, pero devolviendo un n
uevo
complejo con el resultado de operar con el complejo o complejos que se suministr
an como
parmetros.
...............................................................................
...

Pongamos en prctica lo aprendido diseando una versin simplificada de un juego de


rescate espacial (Galaxis)4 al que denominaremos miniGalaxis.
MiniGalaxis se juega con un tablero de 9 filas y 20 columnas. En el tablero
hay
5 nufragos espaciales y nuestro objetivo es descubrir dnde se encuentran. Contamos
para ello con una sonda espacial que podemos activar en cualquier casilla del ta
blero. La
sonda dispara una seal en las cuatro direcciones cardinales que es devuelta por u
nos
dispositivos que llevan los nufragos. La sonda nos dice cuntos nufragos espaciales
han respondido, pero no desde qu direcciones enviaron su seal de respuesta. Cuando
activamos la sonda en las coordenadas exactas en las que se encuentra un nafrago,
lo
damos por rescatado. Slo disponemos de 20 sondas para efectuar el rescate, as que
las
hemos de emplear juiciosamente. De lo contrario, la muerte de inocentes pesar sob
re
nuestra conciencia.
Lo mejor ser que te hagas una idea precisa del juego jugando. Al arrancar apa
rece
esta informacin en pantalla:

El tablero se muestra como una serie de casillas. Arriba tienes letras para
identificar
las columnas y a la izquierda nmeros para las filas. El ordenador nos informa de
que
an quedan 5 nufragos por rescatar y que disponemos de 20 sondas. Se ha detenido
mostrando el mensaje
: est esperando a que digamos en qu coordenadas lanzamos una sonda. El ordenador acepta una cadena que contenga un dgito y
una letra (en cualquier orden) y la letra puede ser minscula o mayscula. Lancemos
nuestra primera sonda: escribamos
y pulsemos la tecla de retorno de carro. H
e aqu
el resultado:

4
El nombre y la descripcin puede que te hagan concebir demasiadas esperanzas
: se trata de un juego
muy sencillito y falto de cualquier efecto especial. Galaxis fue concebido por C
hristian Franz y escrito para
el Apple Macintosh. Ms tarde, Eric Raymond lo reescribi para que fuera ejecutable
en Unix.

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

190
190

El tablero se ha redibujado y muestra el resultado de lanzar la sonda. En la


casilla
de coordenadas
aparece un cero: es el nmero de nafragos que hemos detectado co
n
la sonda. Mala suerte. Las casillas que ahora aparecen con un punto son las expl
oradas
por la sonda. Ahora sabes que en ninguna de ellas hay un nufrago. Sigamos jugando
:
probemos con las coordenadas . Aqu tienes la respuesta del ordenador:

En la casilla de coordenadas
aparece un uno: la sonda ha detectado la pres
encia
de un nufrago en alguna de las 4 direcciones. Sigamos. Probemos en :

Dos nufragos detectados. Parece probable que uno de ellos est en la columna .
Lancemos otra sonda en esa columna. Probemos con 2 :

Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
c
Introduccin a la programacin con C - UJI
UJI

191
191

Bravo! Hemos encontrado a uno de los nufragos. En el tablero se muestra con u


na
. Ya slo quedan 4.
Bueno. Con esta partida inacabada puedes hacerte una idea detallada del jue
go.
Diseemos el programa.
Empezamos por definir las estructuras de datos. La primera de ellas, el tab
lero de
juego, que es una simple matriz de 920 casillas. Nos vendr bien disponer de consta
ntes
que almacenen el nmero de filas y columnas para usarlas en la definicin de la matr
iz:
include
define
define

9
20

int main void


char espacio
return 0
La matriz espacio es una matriz de caracteres. Hemos de inicializarla con caract
eres
,
que indican que no se han explorado sus casillas. En lugar de inicializarla en m
ain, vamos
a disear una funcin especial para ello. Por qu? Para mantener main razonablemente
pequeo y mejorar as la legibilidad. A estas alturas no debe asustarnos definir fun
ciones
para las diferentes tareas.
include
define
define

9
20

define
void inicializa tablero char tablero
Inicializa el tablero de juego marcando todas las casillas como no
sondeadas.
int i j
for i 0 i
for j 0 j
tablero i

i
j
j

int main void


char espacio
inicializa tablero espacio
return 0

Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
c
Introduccin a la programacin con C - UJI
UJI

192
192

Pasamos la matriz indicando el nmero de columnas de la misma.5 En el interior de


la
funcin se modifica el contenido de la matriz. Los cambios afectarn a la variable q
ue
suministremos como argumento, pues las matrices se pasan siempre por referencia.
Hemos de mostrar por pantalla el contenido de la matriz en ms de una ocasin.
Podemos disear un procedimiento que se encargue de esta tarea:
include
define
define

9
20

define

void muestra tablero char tablero


Muestra en pantalla el tablero de juego.
int i j
Etiquetar con una letra cada columna.
printf
for j 0 j
j
printf
printf
for i 0 i
printf
for j 0 j
printf
printf

i
i

Etiqueta de cada fila.


j
tablero i j

int main void


char espacio
inicializa tablero espacio
muestra tablero espacio
return 0
El procedimiento muestra tablero imprime, adems, del contenido del tablero, e
l nombre de las columnas y el nmero de las filas.
Por cierto, hay una discrepancia entre el modo con que nos referimos a las c
asillas
(mediante un dgito y una letra) y el modo con el que lo hace el programa (mediant
e
dos nmeros enteros). Cuando pidamos unas coordenadas al usuario lo haremos con un
a
sentencia como sta:
define
int main void

80

No hemos usado el nombre espacio, sino tablero, con el nico objetivo de resal
tar que el parmetro puede
5
ser cualquier matriz (siempre que su dimensin se ajuste a lo esperado), aunque no
sotros slo usaremos
la matriz espacio como argumento. Si hubisemos usado el mismo nombre, es probable
que hubisemos
alimentado la confusin entre parmetros y argumentos que experimentis algunos.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
UJI
c

193
193

char coordenadas

printf

scanf

coordenadas

Como ves, las coordenadas se leern en una cadena. Nos convendr disponer, pues, de
una funcin que traduzca esa cadena a un par de nmeros y otra que haga lo contrario:
void de fila y columna a numero y letra int fila int columna char coordenad
as
Convierte una fila y columna descritas numricamente en una fila y co
lumna descritas
como una cadena con un dgito y una letra.
coordenadas 0
coordenadas 1
coordenadas 2

fila
columna

int de numero y letra a fila y columna char coordenadas


int fila in
t columna
Convierte una fila y columna con un dgito y una letra (minscula o mays
cula) en
cualquier orden a una fila y columna descritas numricamente.
if strlen coordenadas 2
return 0
if coordenadas 0
coordenadas 0
pha coordenadas 1
fila coordenadas 0
columna toupper coordenadas 1
return 1
if coordenadas 1
coordenadas 1
pha coordenadas 0
columna toupper coordenadas 0
fila coordenadas 1
return 1

isal

isal

return 0
La primera funcin (de fila y columna a numero y letra) es muy sencilla: recibe el
valor de la fila y el valor de la columna y modifica el contenido de un puntero
a una cadena.
Observa que es responsabilidad nuestra terminar correctamente la cadena coordena
das.
La segunda funcin es algo ms complicada. Una razn para ello es que efecta cierto
tratamiento de errores. Por qu? Porque la cadena coordenadas ha sido introducida p
or
el usuario y puede contener errores. Usamos un convenio muy frecuente en los pro
gramas
C:
Los valores se devuelven en la funcin mediante parmetros pasados por refe
rencia,

y la funcin devuelve un valor que indica si se detect o no un error (devu


elve 0 si
hubo error, y 1 en caso contrario).
De este modo es posible invocar a la funcin cuando leemos el contenido de la cade
na
de esta forma:
printf

scanf

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

coordenadas

194
194

while de numero y letra a fila y columna coordenadas

fila

columna
printf
scanf

coordenadas

Sigamos. Hemos de disponer ahora 5 nufragos en el tablero de juego. Podramos


ponerlos directamente en la matriz espacio modificando el valor de las casillas
pertinentes, pero en tal caso muestra tablero los mostrara, revelando el secreto de su po
sicin
y reduciendo notablemente el inters del juego
. Qu hacer? Una posibilidad con
siste en usar una matriz adicional en la que poder disponer los nufragos. Esta nu
eva
matriz no se mostrara nunca al usuario y sera consultada por el programa cuando se
necesitara saber si hay un nufrago en alguna posicin determinada del tablero. Si b
ien
es una posibilidad interesante (y te la propondremos ms adelante como ejercicio),
nos
decantamos por seguir una diferente que nos permitir practicar el paso de registr
os a
funciones. Definiremos los siguientes registros:

define

struct Naufrago
int fila columna
int encontrado

?
Coordenadas
Ha sido encontrado ya?

struct GrupoNaufragos
struct Naufrago naufrago
int cantidad

El tipo registro struct Naufrago mantiene la posicin de un nufrago y permite saber si sigue perdido o si, por el contrario, ya ha sido encontrado. El tipo regi
stro
struct GrupoNaufragos mantiene un vector de nufragos de talla
. Aunque el juego indica que hemos de trabajar con 5 nufragos, usaremos un campo adici
onal
con la cantidad de nufragos realmente almacenados en el vector. De ese modo resul
tar
sencillo modificar el juego (como te proponemos en los ejercicios al final de es
ta seccin)
para que se juegue con un nmero de nufragos seleccionado por el usuario.
Guardaremos los nufragos en una variable de tipo struct GrupoNaufragos:

int main void

char espacio
struct GrupoNaufragos losNaufragos
inicializa tablero espacio
muestra tablero espacio
return 0
El programa debera empezar realmente por inicializar el registro losNaufragos ubi
cando
a cada nufrago en una posicin aletoria del tablero. Esta funcin (errnea) se encarga
de ello:

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

195
195

include
void pon naufragos struct GrupoNaufragos grupoNaufragos int cantidad
Situa aleatoriamente cantidad nufragos en la estructura grupoNaufrag
os.
PERO LO HACE MAL.
int fila columna
grupoNaufragos cantidad 0
while grupoNaufragos cantidad cantidad
fila rand
columna rand
grupoNaufragos naufrago grupoNaufragos cantidad fila fila
grupoNaufragos naufrago grupoNaufragos cantidad columna columna
grupoNaufragos naufrago grupoNaufragos cantidad encontrado 0
grupoNaufragos cantidad

Por qu est mal? Primero hemos de entenderla bien. Analicmosla paso a


pecemos por la cabecera: la funcin tiene dos parmetros, uno que es una
(un
puntero) a un registro de tipo struct GrupoNaufragos y un entero que nos
cuntos
nufragos hemos de poner al azar. La rutina empieza inicializando a cero
ad de
nufragos ya dispuestos mediante una lnea como sta:
grupoNaufragos
cantidad
0

paso. Emreferencia
indica
la cantid

Entiendes por qu se usa el operador flecha?: la variable grupoNaufragos es un punt


ero,
as que hemos de acceder a la informacin apuntada antes de acceder al campo cantida
d.
Podramos haber escrito esa misma lnea as:
grupoNaufragos
cantidad
0
pero hubiera resultado ms incmodo (e ilegible). A continuacin, la funcin repite cantidad veces la accin consistente en seleccionar una fila y columna al azar (media
nte la
funcin rand de
) y lo anota en una posicin del vector de nufragos. Puede
que esta lnea te resulte un tanto difcil de entender:
grupoNaufragos
naufrago grupoNaufragos
cantidad
fila fila
pero no lo es tanto si la analizas paso a paso. Veamos. Empecemos por el ndice qu
e
hemos sombreado arriba. La primera vez, es 0, la segunda 1, y as sucesivamente. E
n aras
de comprender la sentencia, nos conviene reescribir la sentencia poniendo de mom
ento
un 0 en el ndice:
grupoNaufragos
naufrago 0 fila
fila
Ms claro, no? Piensa que grupoNaufragos naufrago es un vector como cualquier otro,
as que la expresin grupoNaufragos naufrago 0 accede a su primer elemento. De
qu tipo es ese elemento? De tipo struct Naufrago. Un elemento de ese tipo tiene u
n
campo fila y se accede a l con el operador punto. O sea, esa sentencia asigna el
valor

de fila al campo fila de un elemento del vector naufrago del registro que es apu
ntado por
grupoNaufragos. El resto de la funcin te debe resultar fcil de leer ahora. Volvamo
s a la
cuestin principal: por qu est mal diseada esa funcin? Fcil: porque puede ubicar dos
nufragos en la misma casilla del tablero. Cmo corregimos el problema? Asegurndonos
de que cada nufrago ocupa una casilla diferente. Tenemos dos posibilidades:
Generar las posiciones de cinco nufragos al azar y comprobar que son tod
as
diferentes entre s. Si lo son, perfecto: hemos acabado; si no, volvemos
a repetir
todo el proceso.
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

196
196

Ir generando la posicin de cada nufrago de una en una, comprobando cada v


ez
que sta es distinta de la de todos los nufragos anteriores. Si no lo es,
volvemos
a generar la posicin de este nufrago concreto; si lo es, pasamos al sigui
ente.
La segunda resulta ms sencilla de implementar y es, a la vez, ms eficiente. Aqu la
tienes implementada:
void pon naufragos struct GrupoNaufragos grupoNaufragos int cantidad
Sita aleatoriamente cantidad nufragos en la estructura grupoNaufragos
.
int fila columna ya hay uno ahi i
grupoNaufragos cantidad 0
while grupoNaufragos cantidad
cantidad
fila rand
columna rand
ya hay uno ahi 0
for i 0 i grupoNaufragos cantidad i
if fila grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
ya hay uno ahi 1
break
if

ya hay uno ahi


grupoNaufragos naufrago grupoNaufragos

cantidad fila fil

grupoNaufragos naufrago grupoNaufragos

cantidad columna

grupoNaufragos naufrago grupoNaufragos

cantidad encontra

a
columna
do 0
grupoNaufragos cantidad

Nos vendr bien disponer de una funcin que muestre por pantalla la ubicacin y estado
de cada nufrago. Esta funcin no resulta til para el juego (pues perdera toda la grac
ia),
pero s para ayudarnos a depurar el programa. Podramos, por ejemplo, ayudarnos con
llamadas a esa funcin mientras jugamos partidas de prueba y, una vez dado por bue
no
el programa, no llamarla ms. En cualquier caso, aqu la tienes:
void muestra naufragos struct GrupoNaufragos grupoNaufragos
Muestra las coordenadas de cada nufrago e informa de si sigue perdid
o.
til para depuracin del programa.
int i
char coordenadas 3
for i 0 i grupoNaufragos cantidad i
de fila y columna a numero y letra grupoNaufragos naufrago i fila
grupoNaufragos naufrago i columna
coordenadas
printf
i coordenadas
if grupoNaufragos naufrago i encontrado
printf

else
printf

La funcin est bien, pero podemos mejorarla. Fjate en cmo pasamos su parmetro:
por valor. Por qu? Porque no vamos a modificar su valor en el interior de la funcin
.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

197
197

En principio, la decisin de pasarlo por valor est bien fundamentada. No obstante,


piensa en qu ocurre cada vez que llamamos a la funcin: como un registro de tipo
struct GrupoNaufragos ocupa 64 bytes (haz cuentas y comprubalo), cada llamada a l
a
funcin obliga a copiar 64 bytes en la pila. El problema se agravara si en lugar de
trabajar con un nmero mximo de 5 nufragos lo hiciramos con una cantidad mayor.
Es realmente necesario ese esfuerzo? La verdad es que no: podemos limitarnos a co
piar
4 bytes si pasamos una referencia al registro. Esta nueva versin de la funcin efec
ta el
paso por referencia:
void muestra naufragos struct GrupoNaufragos grupoNaufragos
Muestra las coordenadas de cada nufrago e informa de si sigue perdid
o.
til para depuracin del programa.
int i fila columna
char coordenadas 3
for i 0 i grupoNaufragos cantidad i
de fila y columna a numero y letra grupoNaufragos naufrago i fila
grupoNaufragos naufrago i columna
coordenadas
printf
i coordenadas
if grupoNaufragos naufrago i encontrado
printf
else
printf

Es posible usar el adjetivo const para dejar claro que pasamos el puntero po
r eficiencia, pero no porque vayamos a modificar su contenido:
void muestra naufragos const struct GrupoNaufragos
grupoN
aufragos
Hagamos una prueba para ver si todo va bien por el momento:

int main void


struct GrupoNaufragos losNaufragos
pon naufragos losNaufragos 5
muestra naufragos losNaufragos
return 0
Compilemos y ejecutemos el programa. He aqu el resultado:

Bien: cada nufrago ocupa una posicin diferente. Ejecutmoslo de nuevo

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

198
198

Eh! Se han ubicado en las mismas posiciones! Qu gracia tiene el juego si en


todas las partidas aparecen los nufragos en las mismas casillas? Cmo es posible que
ocurra algo as? No se generaba su ubicacin al azar? S y no. La funcin rand genera
nmeros pseudoaleatorios. Utiliza una frmula matemtica que genera una secuencia de
nmeros de forma tal que no podemos efectuar una prediccin del siguiente (a menos
que conozcamos la frmula, claro est). La secuencia de nmeros se genera a partir
de un nmero inicial: la semilla. En principio, la semilla es siempre la misma, as
que
la secuencia de nmeros es, tambin, siempre la misma. Qu hacer, pues, si queremos
obtener una diferente? Una posibilidad es solicitar al usuario el valor de la se
milla,
que se puede modificar con la funcin srand, pero no parece lo adecuado para un ju
ego
de ordenador (el usuario podra hacer trampa introduciendo siempre la misma semill
a).
Otra posibilidad es inicializar la semilla con un valor aleatorio. Con un valor a
leatorio?
Tenemos un pez que se muerde la cola: resulta que necesito un nmero aleatorio para
generar nmeros aleatorios! Mmmmm. Tranquilo, hay una solucin: consultar el reloj d
el
ordenador y usar su valor como semilla. La funcin time (disponible incluyendo
)
nos devuelve el nmero de segundos transcurridos desde el inicio del da 1 de enero
de
1970 (lo que se conoce por tiempo de la era Unix) y, naturalmente, es diferente
cada vez
que lo llamamos para iniciar una partida. Aqu tienes la solucin:
include
int main void
struct GrupoNaufragos losNaufragos
srand time 0
pon naufragos losNaufragos 5
muestra naufragos losNaufragos
return 0
Efectuemos nuevas pruebas:

Bravo! Son valores diferentes de los anteriores. Ejecutemos nuevamente el p


rograma:

Introduccin a la programacin con C

199
199
c

UJI
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

Perfecto! A otra cosa.


Ya hemos inicializado el tablero y dispuesto los nufragos en posiciones al az
ar. Diseemos una funcin para el lanzamiento de sondas. La funcin (que ser un procedimiento)
recibir un par de coordenadas, el tablero de juego y el registro que contiene la
posicin
de los nufragos y har lo siguiente:
modificar el tablero de juego sustituyendo los smbolos por
en las direcciones cardinales desde el punto de lanzamiento de la sonda,
y modificar la casilla en la que se lanz la sonda indicando el nmero de nuf
ragos
detectados, o marcndola con una

si hay un nufrago en ella.

define
define
define
void lanzar sonda int fila int columna char tablero
const struct GrupoNaufragos grupoNaufragos
Lanza sonda en las coordenadas indicadas. Actualiza el tablero con
el resultado del
sondeo. Si se detecta un nufrago en el punto de lanzamiento de la so
nda, lo rescata.
int detectados

0 i

Recorrer la vertical
for i 0 i
i
if hay naufrago i columna grupoNaufragos
detectados
if tablero i columna
tablero i columna
Recorrer la horizontal
for i 0 i
i
if hay naufrago fila i grupoNaufragos
detectados
if tablero fila i
tablero fila i

if

Ver si acertamos y hay un nufrago en esta misma casilla.


hay naufrago fila columna grupoNaufragos
tablero fila columna
En tal caso, ponemos una

X.
rescate fila columna grupoNaufragos
else
tablero fila
columna
no, el nmero de nufragos detectados.

detectados

Y si

Esta funcin se ayuda con otras dos: hay naufrago y rescate. La primera nos in
dica
si hay un nufrago en una casilla determinada:
int hay naufrago int fila int columna const struct GrupoNaufragos grupoNauf
ragos
Averigua si hay un nufrago perdido en las coordenadas fila columna .
Si lo hay devuelve 1; si no lo hay, devuelve 0.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

200
200

Introduccin a la programacin con C -UJI


cUJI

int i
for i 0 i grupoNaufragos cantidad i
if fila
grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
return 1
return 0
Y la segunda lo marca como rescatado:
void rescate int fila int columna struct GrupoNaufragos grupoNaufragos
Rescata al nufrago que hay en las coordenadas indicadas.
int i
for i 0 i grupoNaufragos cantidad i
if fila grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
grupoNaufragos naufrago i encontrado 1
Ya podemos ofrecer una versin ms completa del programa principal:
int main void
char espacio
struct GrupoNaufragos losNaufragos
char coordenadas
1
int fila columna
srand time 0
pon naufragos losNaufragos 5
inicializa tablero espacio
muestra tablero espacio

ila

while
printf
scanf
coordenadas
while de numero y letra a fila y columna coordenadas
columna
printf
scanf
coordenadas
lanzar sonda fila columna espacio
muestra tablero espacio

losNaufragos

return 0
Cundo debe finalizar el bucle while exterior? Bien cuando hayamos rescatado a todo
s
los nufragos, bien cuando nos hayamos quedado sin sondas. En el primer caso habre
mos
vencido y en el segundo habremos perdido:
define

20

int perdidos const struct GrupoNaufragos

grupoNaufragos

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

201
201

Cuenta el nmero de nufragos que siguen perdidos.


int contador

0 i

for i 0 i grupoNaufragos cantidad i


if grupoNaufragos naufrago i encontrado
contador
return contador

int main void


char espacio
struct GrupoNaufragos losNaufragos
int sondas disponibles
char coordenadas
1
int fila columna
srand time 0
pon naufragos losNaufragos 5
inicializa tablero espacio
muestra tablero espacio
while sondas disponibles 0
perdidos losNaufragos
0
printf
perdidos losNaufragos
printf
sondas disponibles
printf
scanf
coordenadas
while de numero y letra a fila y columna coordenadas fila

olumna
printf
scanf

coordenadas

lanzar sonda fila columna espacio


muestra tablero espacio
sondas disponibles
if perdidos losNaufragos
0
printf
das disponibles
else
printf
perdidos losNaufragos

losNaufragos

son

return 0
Hemos definido una nueva funcin, perdidos, que calcula el nmero de nufragos que
permanecen perdidos.
Y ya est. Te mostramos finalmente el listado completo del programa:
include
include
include
include
include

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

202
202

define
define
define
define
define

9
20
80
5
20

define
define
define
Conversin entre los dos modos de expresar coordenadas
void de fila y columna a numero y letra int fila int columna char coordenad
as
Convierte fila y columna descritas numricamente en fila y columna de
scritas
como una cadena con un dgito y una letra.
coordenadas 0
coordenadas 1
coordenadas 2

fila
columna

int de numero y letra a fila y columna char coordenadas


int fila in
t columna
Convierte una fila y columna con un dgito y una letra (minscula o mays
cula) en
cualquier orden a una fila y columna descritas numricamente.
printf
coordenadas
if strlen coordenadas
2
return 0
if coordenadas 0
coordenadas 0
a coordenadas 1
fila coordenadas 0
columna toupper coordenadas 1
return 1
if coordenadas 1
coordenadas 1
a coordenadas 0
columna toupper coordenadas 0
fila coordenadas 1
return 1
return 0

Nufragos
struct Naufrago
int fila columna
int encontrado

?
Coordenadas
Ha sido encontrado ya?

isalph

isalph

struct GrupoNaufragos
struct Naufrago naufrago
int cantidad

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

203
203

void pon naufragos struct GrupoNaufragos grupoNaufragos int cantidad


Situa aleatoriamente cantidad nufragos en la estructura grupoNaufrag
os.
int fila columna ya hay uno ahi i
grupoNaufragos cantidad 0
while grupoNaufragos cantidad
cantidad
fila rand
columna rand
ya hay uno ahi 0
for i 0 i grupoNaufragos cantidad i
if fila grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
ya hay uno ahi 1
break
if

ya hay uno ahi


grupoNaufragos naufrago grupoNaufragos

cantidad fila fil

grupoNaufragos naufrago grupoNaufragos

cantidad columna

grupoNaufragos naufrago grupoNaufragos

cantidad encontra

a
columna
do 0
grupoNaufragos cantidad

int hay naufrago int fila int columna const struct GrupoNaufragos grupoNauf
ragos
Averigua si hay un nufrago perdido en las coordenadas fila columna .
Si lo hay devuelve 1; si no lo hay, devuelve 0.
int i
for i 0 i grupoNaufragos cantidad i
if fila
grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
return 1
return 0

void rescate int fila int columna struct GrupoNaufragos grupoNaufragos


Rescata al nufrago que hay en las coordenadas indicadas.
int i
for i 0 i grupoNaufragos cantidad i
if fila grupoNaufragos naufrago i fila
columna
grupoNaufragos naufrago i columna
grupoNaufragos naufrago i encontrado 1
int perdidos const struct GrupoNaufragos grupoNaufragos
Cuenta el nmero de nufragos que siguen perdidos.
int contador

0 i

for i 0 i grupoNaufragos cantidad i


if grupoNaufragos naufrago i encontrado
contador
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

204
204

return contador
void muestra naufragos const struct GrupoNaufragos grupoNaufragos
Muestra las coordenadas de cada naufrago e informa de si sigue perd
ido.
til para depuracin del programa.
int i
char coordenadas 3
for i 0 i grupoNaufragos cantidad i
de fila y columna a numero y letra grupoNaufragos naufrago i fila
grupoNaufragos naufrago i columna
coordenadas
printf
i coordenadas
if grupoNaufragos naufrago i encontrado
printf
else
printf

Tablero
void inicializa tablero char tablero
Inicializa el tablero de juego marcando todas las casillas como no
sondeadas.
int i j
for i 0 i
for j 0 j
tablero i

i
j
j

void muestra tablero char tablero


Muestra en pantalla el tablero de juego.
int i j
Etiquetar con una letra cada columna.
printf
for j 0 j
j
printf
printf
for i 0 i
printf
for j 0 j
printf
printf

Sonda

i
i

Etiqueta de cada fila.


j
tablero i j

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

205
205

void lanzar sonda int fila int columna char tablero


struct GrupoNaufragos grupoNaufragos
Lanza sonda en las coordenadas indicadas. Actualiza el tablero con
el resultado del
sondeo. Si se detecta un nufrago en el punto de lanzamiento de la so
nda, lo rescata.
int detectados

0 i

Recorrer la vertical
for i 0 i
i
if hay naufrago i columna grupoNaufragos
detectados
if tablero i columna
tablero i columna
Recorrer la horizontal
for i 0 i
i
if hay naufrago fila i grupoNaufragos
detectados
if tablero fila i
tablero fila i
Ver si acertamos y hay una nufrago en esta misma casilla.
if hay naufrago fila columna grupoNaufragos
tablero fila columna
En tal caso, ponemos una X.
rescate fila columna grupoNaufragos
else
tablero fila
columna
o, nmero de nufragos detectados.

detectados

Si n

int main void


char espacio
struct GrupoNaufragos losNaufragos
int sondas disponibles
char coordenadas
1
int fila columna
srand time 0
pon naufragos losNaufragos 5
inicializa tablero espacio
muestra tablero espacio
while sondas disponibles 0
perdidos losNaufragos
0
printf
perdidos losNaufragos
printf
sondas disponibles
printf
scanf
coordenadas
while de numero y letra a fila y columna coordenadas fila
columna
printf
scanf
coordenadas
lanzar sonda fila columna espacio

losNaufrago

s
muestra tablero espacio
sondas disponibles

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia

206
206

Introduccin a la programacin con C -UJI


cUJI

if perdidos losNaufragos
printf

0
sondas disponibles

else
printf
perdidos losNaufragos
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
202 Reescribe el programa para que no se use una variable de tipo struct GrupoNa
ufragos
como almacn del grupo de nufragos, sino una matriz paralela a la matriz espacio.
Cada nufrago se representar con un
mi
entras permanezca perdido, y con una
cuando haya sido descubierto.
203 Siempre que usamos rand en miniGalaxis calculamos un par de nmeros aleatorios. Hemos definido un nuevo tipo y una funcin:
struct Casilla
int fila columna
struct Casilla casilla al azar void
struct Casilla casilla
casilla fila rand
casilla columna rand
return casilla
Y proponemos usarlos as:
void pon naufragos struct GrupoNaufragos grupoNaufragos int cantidad
Situa aleatoriamente cantidad nufragos en la estructura grupoNaufrag
os.
int fila columna ya hay uno ahi i
struct Casilla casilla
grupoNaufragos cantidad 0
while grupoNaufragos cantidad
cantidad
casilla casilla al azar
ya hay uno ahi 0
for i 0 i grupoNaufragos cantidad i
if casilla fila
grupoNaufragos naufrago i fila
casilla columna
grupoNaufragos naufrago i columna
ya hay uno ahi 1
break
if

ya hay uno ahi


grupoNaufragos naufrago grupoNaufragos
cantidad fila casilla fila
grupoNaufragos naufrago grupoNaufragos
cantidad columna casilla columna
grupoNaufragos naufrago grupoNaufragos
cantidad encontrado 0
grupoNaufragos cantidad

Es correcto el programa con estos cambios?


Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
207
207
c
UJI
Introduccin a la programac
in con C - UJI

204 Como siempre que usamos rand calculamos un par de nmeros aleatorios, hemos
modificado el programa de este modo:
struct Naufrago naufrago al azar void
struct Naufrago naufrago
naufrago fila rand
naufrago columna rand
naufrago encontrado 0
return naufrago
void pon naufragos struct GrupoNaufragos grupoNaufragos int cantidad
Situa aleatoriamente cantidad nufragos en la estructura grupoNaufrag
os.
int fila columna ya hay uno ahi i
struct Naufrago un naufrago
grupoNaufragos cantidad 0
while grupoNaufragos cantidad
cantidad
un naufrago naufrago al azar
ya hay uno ahi 0
for i 0 i grupoNaufragos cantidad i
if un naufrago fila
grupoNaufragos naufrago i fila
un naufrago columna
grupoNaufragos naufrago i columna
ya hay uno ahi 1
break
if

ya hay uno ahi


grupoNaufragos naufrago grupoNaufragos

cantidad

n naufrago
grupoNaufragos cantidad

Es correcto el programa con estos cambios?


205 Modifica el juego para que el usuario pueda escoger el nivel de dificultad.
El
usuario escoger el nmero de nufragos perdidos (con un mximo de 20) y el nmero de
sondas disponibles.
206 Hemos construido una versin simplificada de Galaxis. El juego original slo se
diferencia de ste en las direcciones exploradas por la sonda: as como las sondas d
e
miniGalaxis exploran 4 direcciones, las de Galaxis exploran 8. Te mostramos el r
esultado
de lanzar nuestra primera sonda en las coordenadas 4 de un tablero de juego Gala
xis:

Implementa el juego Galaxis.


...............................................................................
...
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:

978-84-693-0143-2

Introduccin a la programacin con C - UJI


c
UJI

208
208

Es posible definir funciones recursivas en C. La funcin factorial de este program


a, por
ejemplo, define un clculo recursivo del factorial:
include
int factorial int n
if n 1
return 1
else
return n

factorial n 1

int main void


int valor
printf
scanf
printf
valor factorial valor

valor

return 0
Nada nuevo. Ya conoces el concepto de recursin de Python. En C es lo mismo. T
iene
inters, eso s, que estudiemos brevemente el aspecto de la memoria en un instante d
ado.
Por ejemplo, cuando llamamos a factorial 5 , que ha llamado a factorial 4 , que
a su vez
ha llamado a factorial 3 , la pila presentar esta configuracin:
factorial
n

3
llamada

desde lnea 8
factorial
n

4
llamada

desde lnea 8
factorial
n

5
llamada

desde lnea 17
main

valor

5
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
207 Disea una funcin que calcule recursivamente x n . La variable x ser de tipo

float y n de tipo int.


208 Disea una funcin recursiva que calcule el n-simo nmero de Fibonacci.
209 Disea una funcin recursiva
para calcular el nm
ero combinatorio n sobre m
sabiendo que
 
n
=
1,
n
 
n
=
1,
0
 

 

n
n1
n1
=
+
.
m
m
m1
209
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la programacin con C
209
Introduccin a la programa
cin con C  UJI
c
UJI

210 Disea un procedimiento recursivo llamado muestra bin que reciba un nmero
entero positivo y muestre por pantalla su codificacin en binario. Por ejemplo, si
llamamos
a muestra bin 5 , por pantalla aparecer el texto
.
...............................................................................
...

Vamos a estudiar ahora un mtodo recursivo de ordenacin de vectores: mergesort (que


se
podra traducir por ordenacin por fusin o mezcla). Estudiemos primero la aproximacin
que sigue considerando un procedimiento equivalente para ordenar las 12 cartas d
e un
palo de la baraja de cartas. La ordenacin por fusin de un palo de la baraja consis
te en
lo siguiente:
Dividir el paquete de cartas en dos grupos de 6 cartas;
ordenar por fusin el primer grupo de 6 cartas;
ordenar por fusin el segundo grupo de 6 cartas;
fundir los dos grupos, que ya estn ordenados, tomando siempre la carta
con nmero
menor de cualquiera de los dos grupos (que siempre ser la primera de un
o de los
dos grupos).
Ya ves dnde aparece la recursin, no? Para ordenar 12 cartas por fusin hemos de
ordenar dos grupos de 6 cartas por fusin. Y para ordenar cada grupo de 6 cartas p
or
fusin tendremos que ordenar dos grupos de 3 cartas por fusin. Y para ordenar 3 gru
pos
de cartas por fusin. . . Cundo finaliza la recursin? Cuando nos enfrentemos a casos
triviales. Ordenar un grupo de 1 sola carta es trivial: siempre est ordenado!
Desarrollemos un ejemplo de ordenacin de un vector con 16 elementos:
0
1
2
3
4
5
6 7 8 9 10
11 12
13 14 15
11 21 3 1 98 0 12 82 29 30 11 18 43 4 75 37
1.

Empezamos separando el vector en dos subvectores de 8 elementos:


0
1
2
3
4
5
6
7
8
9 10 11 12 13 14 15
11 21 3 1 98 0 12 82
29 30 11 18 43 4 75 37

2.

ordenamos por fusin el primer vector, con lo que obtenemos:


0

7
0 1 3 11

12 21 82 98
3.

y ordenamos por fusin el segundo vector, con lo que obtenemos:


0

7
4 11 18

29 30 37 43 75
4.

y ahora fundimos ambos vectores ordenados, obteniendo as un nico vector or

denado:
0
5

10

11

12

13

1
14

15

0 1 3 4 11 11 12 18 21 29 30 37 43 75 82
98

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
210
210
programacin con C - UJI

Introduccin a la

c
UJI

La idea bsica de la fusin es sencilla: se recorren ambos vectores de izqu


ierda a
derecha, seleccionando en cada momento el menor elemento posible. Los d
etalles
del proceso de fusin son un tanto escabrosos, as que lo estudiaremos con
calma
un poco ms adelante.
Podemos representar el proceso realizado con esta imagen grfica:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
11 21 3 1 98 0 12 82 29 30 11
18 43 4 75 37
dividir el problema (de talla 16)

en dos problemas (de talla 8),


0
1
7
8
9
10
11

3
12

4
13

14

15

11 21 3 1 98 0 12 82
29 30 11 18 43 4 75 37
resolver i
ndependientemente cada problema
0
6

7
8

10

11

12

13

14

15

0 1 3 11 12 21 82 98
4 11 18 29 30 37 43 75
y combinar ambas soluciones.
0
3
11

4
12

5
13

6
14

1
8

2
10

15
0 1 3 4 11 11 12 18 21 29 30 3

7 43 75 82 98
Est claro que hemos hecho trampa: las lneas de trazo discontinuo esconden un proceso
complejo, pues la ordenacin de cada uno de los vectores de 8 elementos supone la
ordenacin (recursiva) de dos vectores de 4 elementos, que a su vez. . . Cundo acaba
el proceso recursivo? Cuando llegamos a un caso trivial: la ordenacin de un vecto
r que
slo tenga 1 elemento.
He aqu el proceso completo:
0
1
2
3
4
5
6
7 8
9
10
11
12
13
14
15
11 21 3 1 98 0 12
82 29 30 11 18 43 4 75 37

0
6

7
9

10

11

12

13

14

15

11 21 3 1 98 0 12 82
29 30 11 18 43 4 75 37
0
4
10
Divisiones

11

12

13

14

15

11 21 3 1
98 0 12 82

29 30 11 18
43 4 75 37
0

6
10

11

8
12

11 21

13

14

15

3 1

98 0

12 82

29 30

11 18

43 4

75 37

10

11

4
8

12

13

14

5
11

21

12
18

11
0

1
82

43
2
6

10

75

4
8

12

11 21

37

11

13

14

15

1 3

98

12 82

29 30

11 18

4 43
0

4
10

30

98
29

1
5

2
6

37 75

11

12

13

14

15

1 3 11 21
0 12 82 98

11 18 29 30
4 37 43 75

Fusiones

0
6
8

7
9

10

11

12

13

14

15

0 1 3 11 12 21 82 98
4 11 18 29 30 37 43 75
0
2
11

3
12

4
13

5
14

1
9

10

15
0 1 3 4 11 11 12 1

8 21 29 30 37 43 75 82 98
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
211
211
Int
roduccin a la programacin con C -UJI

cUJI

Nos queda por estudiar con detalle el proceso de fusin. Desarrollemos primero
una
funcin que recoja la idea bsica de la ordenacin por fusin: se llamar mergesort
y recibir un vector v y, en principio, la talla del vector que deseamos ordenar.
Esta
funcin utilizar una funcin auxiliar merge encargada de efectuar la fusin de vectores
ya ordenados. Aqu tienes un borrador incompleto:
void mergesort int v
int talla
if talla
1
return
else
mergesort la primera mitad de v
mergesort la segunda mitad de v
merge la primera mitad de v la segunda mitad de v

Dejemos para ms adelante el desarrollo de merge. De momento, el principal pro


blema
es cmo expresar lo de la primera mitad de v y la segunda mitad de v. Fjate: en
el fondo, se trata de sealar una serie de elementos consecutivos del vector v. Cu
ando
ordenbamos el vector del ejemplo tenamos:
0
1 2 3
4
5
6
7 8 9 10
11 12 13 14 15
11 21 3 1 98 0 12 82 29 30 11 18 43 4 75 37
El primer subvector es la serie de valores entre el primer par de flechas, y el
segundo
subvector es la serie entre el segundo par de flechas. Modifiquemos, pues, mergeso
rt
para que trabaje con subvectores, es decir, con un vector e ndices que sealan dnde
empieza y dnde acaba cada serie de valores.
void mergesort int v

int inicio

int final

if final inicio
0
return
else
mergesort v inicio
inicio final 2
mergesort v
inicio final
2 1 final
merge la primera mitad de v la segunda mitad de v

Perfecto. Acabamos de expresar la idea de dividir un vector en dos sin necesidad


de
utilizar nuevos vectores.
Nos queda por detallar la funcin merge. Dicha funcin recibe dos subvectores
contiguos ya ordenados y los funde, haciendo que la zona de memoria que ambos oc
upan
pase a estar completamente ordenada. Este grfico muestra cmo se fundiran, paso a
paso, dos vectores, a y b para formar un nuevo vector c. Necesitamos tres ndices,
i, j y
k, uno para cada vector:
0
1
2
3
0
1
2
3
0
1
2
3
4
5
6 7

1 3 11 21
i

0 12 82 98
j

k
Inicialmente, los tres ndices valen 0. Ahora comparamos a i con b j , seleccionam
os
el menor y almacenamos el valor en c k . Es necesario incrementar i si escogimos
un
elemento de a y j si lo escogimos de b. En cualquier caso, hemos de incrementar
tambin
la variable k:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
212
212
amacin con C -UJI
cUJI

Introduccin a la progr

0
6

1 3 11 21
i

0 12 82 98
j

k
El proceso se repite hasta que alguno de los dos primeros ndices, i o j, se sale de
l
vector correspondiente, tal y como ilustra esta secuencia de imgenes:
0
1
2
3
0
1
2 3
0
1 2 3 4 5 6 7
1 3 11 21
i

0 12 82 98
j

0 1

k
1

0
6

1 3 11 21

0 12 82 98
j

0 1 3

k
1

0
6

1 3 11 21

0 12 82 98

0 1 3

11
i

k
1

0
6

1 3 11 21

0 12 82 98

0 1 3

11 12
i

k
1

0
6

1 3 11 21

0 12 82 98

0 1 3

11 12 21
i

k
Ahora, basta con copiar los ltimos elementos del otro vector al final de c:
0
1
2
3
0
1
2 3
0
1 2 3 4 5 6 7
1 3 11 21

0 12 82 98

0 1 3

11 12 21 82
i

k
1

0
6

1 3 11 21

0 12 82 98

0 1 3

11 12 21 82 98
i
k

Un ltimo paso del proceso de fusin debera copiar los elementos de c en a y b, que e
n
realidad son fragmentos contiguos de un mismo vector.
Vamos a por los detalles de implementacin. No trabajamos con dos vectores ind
ependientes, sino con un slo vector en el que se marcan subvectores con pares de
ndices.
void merge int v

int inicio1 int final1 int inicio2 int final

2
int i j k
int c final2 inicio1 1
tiempo de ejecucin.

Vector de talla determinada en

i inicio1
j inicio2
k 0
while i final1 j
if v i v j
c k
v i
else
c k
v j

final2

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
3

213
21

Introduccin a la programacin con C -UJI


cUJI

while i
c k

final1
v i

while j
c k

final2
v j

for k 0 k final2 inicio1 1 k


v inicio1 k c k
El ltimo paso del procedimiento se encarga de copiar los elementos de c en el vec
tor
original.
Ya est. Bueno, an podemos efectuar una mejora para reducir el nmero de parmetros: fjate en que inicio2 siempre es igual a final1 1. Podemos prescindir de uno
de los
dos parmetros:
void merge int v
int inicio1 int final1 int final2
int i j k
int c final2 inicio1 1
i inicio1
j final1 1
k 0
while i final1 j
if v i v j
c k
v i
else
c k
v j

final2

while i
c k

final1
v i

while j
c k

final2
v j

for k 0 k final2 inicio1 1 k


v inicio1 k c k
Veamos cmo quedara un programa completo que use mergesort:
include
define

100

void merge int v

int inicio1 int final1 int final2

int i j k
int c final2 inicio1 1
i inicio1
j final1 1
k 0
while i final1
if v i v j

final2

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

214
214

c k
else
c k

v i
v j

while i
c k

final1
v i

while j
c k

final2
v j

for k 0 k final2 inicio1 1 k


v inicio1 k c k
void mergesort int v

int inicio int final

if final inicio 0
return
else
mergesort v inicio inicio final 2
mergesort v inicio final
2 1 final
merge v inicio inicio final 2 final

int main void


int mivector
int i talla
talla 0
for i 0 i
i
printf
scanf
mivector i
if mivector i 0
break
talla
mergesort mivector 0 talla 1
printf
for i 0 i talla i
printf
mivector i
printf
return 0
He aqu una ejecucin del programa:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

215
215

Mergesort y el estilo C
Los programadores C tienden a escribir los programas de una forma muy co
mpacta.
Estudia esta nueva versin de la funcin merge:
void merge int v

int inicio1 int final1 int final2

int i j k
int c final2 inicio1 1
for i inicio1 j final1 1

k 0 i final1

j fin

c k
v i
v j
while i final1 c k
while j final2 c k
for k 0 k final2 inicio1

v i
v j
v i
v j
1 k v inicio1 k

al2

c k
Observa que los bucles for aceptan ms de una inicializacin (separndolas por
comas)
y permiten que alguno de sus elementos est en blanco (en el primer for la
accin
de incremento del ndice est en blanco). No te sugerimos que hagas t lo mism
o: te
prevenimos para que ests preparado cuando te enfrentes a la lectura de pr
ogramas C
escritos por otros.
Tambin vale la pena apreciar el uso del operador ternario para evitar
una estructura
condicional if else que en sus dos bloques asigna un valor a la misma ce
lda del vector.
Es una prctica frecuente y da lugar, una vez acostumbrado, a programas ba
stante
legibles.

C debe conocer la cabecera de una funcin antes de que sea llamada, es decir, debe
conocer el tipo de retorno y el nmero y tipo de sus parmetros. Normalmente ello no
plantea ningn problema: basta con definir la funcin antes de su uso, pero no siemp
re
es posible. Imagina que una funcin f necesita llamar a una funcin g y que g, a su
vez, necesita llamar a f (recursin indirecta). Cul ponemos delante? La solucin es
fcil: da igual, la que quieras, pero debes hacer una declaracin anticipada de la f
uncin
que defines en segundo lugar. La declaracin anticipada no incluye el cuerpo de la
funcin: consiste en la declaracin del tipo de retorno, identificador de funcin y li
sta de
parmetros con su tipo, es decir, es un prototipo o perfil de la funcin en cuestin.
Estudia este ejemplo6 :
int impar int a
int par int a

if a 0
return 1
else
return impar a 1
El ejemplo es meramente ilustrativo: hay formas mucho ms eficientes de saber
si un nmero es par o
6
impar.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

216
216

int impar int a


if a 0
return 0
else
return par a 1
La primera lnea es una declaracin anticipada de la funcin impar, pues se usa antes
de haber sido definida. Con la declaracin anticipada hemos adelantado la informacin
acerca de qu tipo de valores aceptar y devolver la funcin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
211 Dibuja el estado de la pila cuando se llega al caso base en la llamada recur
siva
impar 7 .
...............................................................................
...
La declaracin anticipada resulta necesaria para programas con recursin indirec
ta,
pero tambin la encontrars (o usars) en programas sin recursin. A veces conviene
definir funciones en un orden que facilite la lectura del programa, y es fcil que
se defina
una funcin despus de su primer uso. Pongamos por caso el programa
en
el que hemos implementado el mtodo de ordenacin por fusin: puede que resulte ms
legible definir primero mergesort y despus merge pues, a fin de cuentas, las hemo
s
desarrollado en ese orden. De definirlas as, necesitaramos declarar anticipadament
e
merge:
include
define
void merge int v

100
int inicio1 int final1 int final2
Declaracin anticipada.

void mergesort int v

int inicio int final

if final inicio 0
return
else
mergesort v inicio inicio final
2
mergesort v inicio final
2 1 final
merge v inicio inicio final
2 final
Podemos usarla: se ha declarado antes.

void merge int v


int inicio1 int final1 int final2
Y ahora se define.

El preprocesador permite definir un tipo especial de funciones que, en el fondo,


no lo son:

las macros. Una macro tiene parmetros y se usa como una funcin cualquiera, pero la
s
llamadas no se traducen en verdaderas llamadas a funcin. Ahora vers por qu.
Vamos con un ejemplo:
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
217
217
c
UJI
Introduccin a la prog
ramacin con C - UJI

Prototipo por defecto y declaracin anticipada


Si usas una funcin antes de definirla y no has preparado una declaracin ant
icipada,
C deduce el tipo de cada parmetro a partir de la forma en la que se le inv
oca. Este
truco funciona a veces, pero es frecuente que sea fuente de problemas. Co
nsidera este
ejemplo:
int f int y
return 1

g y

float g float x
return x x
En la lnea 3 se usa g y an no se ha definido. Por la forma de uso, el compi
lador
deduce que su perfile es int g int x . Pero, al ver la definicin, detecta
un conflicto.
El problema se soluciona alterando el orden de definicin de las funcion
es o, si se
prefiere, mediante una declaracin anticipada:
float g float x
int f int y
return 1

g y

float g float x
return x x

define

x x x

La directiva con la que se define una macro es define, la misma con la que decla
rbamos
constantes. La diferencia est en que la macro lleva uno o ms parmetros (separados
por comas) encerrados entre parntesis. Este programa define y usa la macro
:
include
define

x x x

int main void


printf
return 0
El compilador no llega a ver nunca la llamada a
epro-

. La razn es que el pr

cesador la sustituye por su cuerpo, consiguiendo que el compilador vea esta otra
versin
del programa:
include

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

218
218

int main void


printf
return 0

2 2 2

Las macros presentan algunas ventajas frente a las funciones:


Por regla general, son ms rpidas que las funciones, pues al no implicar
una
llamada a funcin en tiempo de ejecucin nos ahorramos la copia de argume
ntos
en pila y el salto/retorno a otro lugar del programa.
No obligan a dar informacin de tipo acerca de los parmetros ni del valo
r de
retorno. Por ejemplo, esta macro devuelve el mximo de dos nmeros, sin i
mportar
que sean enteros o flotantes:
define
Pero tienen serios inconvenientes:
La definicin de la macro debe ocupar, en principio, una sola lnea. Si o
cupa ms
de una lnea, hemos de finalizar todas menos la ltima con el carcter just
o
antes del salto de lnea. Incmodo.
No puedes definir variables locales.7
No admiten recursin.
Son peligrossimas. Qu crees que muestra por pantalla este programa?:
include
define

x x x

int main void


printf
return 0

3 3

36?, es decir, el cuadrado de 6? Pues no es eso lo que obtienes, sino 1


5. Por
qu? El preprocesador sustituye el fragmento

3 3 por. . . 3 3

3 3!
El resultado es, efectivamente, 15, y no el que esperbamos. Puedes evi
tar este
problema usando parntesis:
include
define

main void
printf
return 0

3 3

7
No del todo cierto, pero no entraremos en detalles.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

219
219

Ahora el fragmento

3 3 se sustituye por 3 3
3 3 , que es lo que
esperamos. Otro problema resuelto.
No te fes. Ya te hemos dicho que las macros son peligrosas. Sigue estan
do
mal. Qu esperas que calcule 1.0

3 3 ?, el valor de 1/3

6, es decir, 0.02777. . . ? Te equivocas. La expresin 1.0


onvierte en
1.0 3 3
3 3 , que es 1/6 6, o sea, 1, no 1/36.
La solucin pasa por aadir nuevos parntesis:

3 3 se c

include
define

Ahora s? La expresin 1.0

3 3 se convierte en 1.0
3 3
3 3
, que
es 1/36. Pero todava hay un problema: si ejecutamos este fragmento de cdigo:
i
3
z
i
la variable se incrementa 2 veces, y no una sla. Ten en cuenta que el compilador
traduce
lo que ve, y ve esto:
i
3
z
i
i
Y este problema no se puede solucionar.
Recuerda! Si usas macros, toda precaucin es poca.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
212 Disea una macro que calcule la tangente de una cantidad de radianes. Puedes
usar las funciones sin y cos de
, pero
ninguna otra.
213 Disea una macro que devuelva el mnimo de dos nmeros, sin importar si son
enteros o flotantes.
214 Disea una macro que calcule el valor absoluto de un nmero, sin importar si
es entero o flotante.
215 Disea una macro que decremente una variable entera si y slo si es positiva.
La macro devolver el valor ya decrementado o inalterado, segn convenga.
...............................................................................
...

inline
Los inconvenientes de las macros desaconsejan su uso. Lenguajes como C dan sopor
te a
las macros slo por compatibilidad con C, pero ofrecen alternativas mejores. Por e
jemplo,
puedes definir funciones inline. Una funcin inline es como cualquier otra funcin,
slo
que las llamadas a ella se gestionan como las llamadas a macros: se sustituye la
llamada
por el cdigo que se ejecutara en ese caso, o sea, por el cuerpo de la funcin con lo
s

valores que se suministren para los parmetros. Las funciones inline presentan muc
has
ventajas frente a la macros. Entre ellas, la posibilidad de utilizar variables l
ocales o la
no necesidad de utilizar parntesis alrededor de toda aparicin de un parmetro.
Las funciones inline son tan tiles que compiladores como
las integr
an desde
hace aos como extensin propia del lenguaje C y han pasado a formar parte del lengu
aje
C99. Al compilar un programa C99 como ste:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
220
220
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
inline int doble int a
return a

int main void


int i
for i 0 i 10 i
printf
doble i 1
return 0
no se genera cdigo de mquina con 10 llamadas a la funcin doble. El cdigo de
mquina que se genera es virtualmente idntico al que se genera para este otro progr
ama
equivalente:
include
int main void
int i
for i 0 i 10 i
printf

i 1

return 0
Hay ocasiones, no obstante, en las que el compilador no puede efectuar la su
stitucin
de la llamada a funcin por su cuerpo. Si la funcin es recursiva, por ejemplo, la s
ustitucin
es imposible. Pero aunque no sea recursiva, el compilador puede juzgar que una f
uncin
es excesivamente larga o compleja para que compense efectuar la sustitucin. Cuand
o se
declara una funcin como inline, slo se est sugiriendo al compilador que efecte la
sustitucin, pero ste tiene la ltima palabra sobre si habr o no una verdadera llamada
a funcin.
static
Hay un tipo especial de variable local: las variables static. Una variable stati
c es invisible
fuera de la funcin, como cualquier otra variable local, pero recuerda su valor en
tre
diferentes ejecuciones de la funcin en la que se declara.
Veamos un ejemplo:
include
int turno void
static int contador
return contador

int main void


Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

221
221

int i
for i 0 i 10 i
printf
turno
return 0
Si ejecutas el programa aparecern por pantalla los nmeros del 0 al 9. Con cada lla
mada,
contador devuelve su valor y se incrementa en una unidad, sin olvidar su valor e
ntre
llamada y llamada.
La inicializacin de las variables static es opcional: el compilador asegura q
ue empiezan valiendo 0.
Vamos a volver a escribir el programa que presentamos en el ejercicio 169 pa
ra generar
nmeros primos consecutivos. Esta vez, vamos a hacerlo sin usar una variable globa
l que
recuerde el valor del ltimo primo generado. Usaremos en su lugar una variable loc
al
static:
include
int siguienteprimo void
static int ultimoprimo
int esprimo
int i

do
ultimoprimo
esprimo 1
for i 2 i ultimoprimo 2 i
if ultimoprimo i 0
esprimo 0
break
while esprimo
return ultimoprimo
int main void
int i
printf
for i 0 i 10 i
printf
siguienteprimo
return 0
Mucho mejor. Si puedes evitar el uso de variables globales, evtalo. Las variables
locales
static pueden ser la solucin en bastantes casos.

Hay un tipo de parmetro especial que puedes pasar a una funcin: otra funcin!
Veamos un ejemplo. En este fragmento de programa se definen sendas funciones
C
que aproximan numricamente la integral definida en un intervalo para las funcione
s
matemticas f (x) = x 2 y f (x) = x 3 , respectivamente:
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

222
222

float integra cuadrado float a float b int n


int i
float s x
s 0.0
x a
for i 0 i n i
s x x b a
x
b a n

return s
float integra cubo float a float b int n
int i
float s x
s 0.0
x a
for i 0 i n i
s x x x b a
x
b a n

return s
Las dos funciones que hemos definido son bsicamente iguales. Slo difieren en su id
entificador y en la funcin matemtica que integran. No sera mejor disponer de una nica
funcin C, digamos integra, a la que suministremos como parmetro la funcin matemtica
que queremos integrar? C lo permite:
float integra float a float b int n float
f float
int i
float s x
s 0.0
x a
for i 0 i n i
s f x
b a
x
b a n

return s
Hemos declarado un cuarto parmetro que es de tipo puntero a funcin. Cuando llamamo
s
a integra, el cuarto parmetro puede ser el identificador de una funcin que reciba
un
float y devuelva un float:
include
float integra float a float b int n float
int i
float s x

float

0.0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

223
223

x a
for i 0 i n i
s f x
b a
x
b a n

return s
float cuadrado float x
return x x
float cubo float x
return x x x
int main void
printf

integra 0.0 1.0 10 c

printf

integra 0.0 1.0 10 c

uadrado
ubo
return 0
La forma en que se declara un parmetro del tipo puntero a funcin resulta un tant
o
complicada. En nuestro caso, lo hemos declarado as: float f float . El primer flo
at
indica que la funcin devuelve un valor de ese tipo. El f indica que el parmetro f
es un puntero a funcin. Y el float entre parntesis indica que la funcin trabaja con
un
parmetro de tipo float. Si hubisemos necesitado trabajar con una funcin que recibe
un
float y un int, hubisemos escrito float f float int en la declaracin del parmetro.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
216 Puedes usar la funcin integra para calcular la integral definida de la funcin
matemtica sin(x)? Cmo?
217 Disea una funcin C capaz de calcular

b
f (
i),
i=a
siendo f una funcin matemtica cualquiera que recibe un entero y devuelve un entero
.
218 Disea una funcin C capaz de calcular

b 
d
f
(i, j),
i=a j=c

siendo f una funcin matemtica cualquiera que recibe dos enteros y devuelve un ente
ro.
...............................................................................
...

Cuando te enfrentas a la escritura de un programa largo, individualmente o en eq


uipo,
te resultar virtualmente imposible escribirlo en un nico fichero de texto. Resulta
ms
prctico agrupar diferentes partes del programa en ficheros independientes. Cada f
ichero
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
224
224
amacin con C - UJI

Introduccin a la progr

c
UJI

puede, por ejemplo, agrupar las funciones, registros y constantes propias de cie
rto tipo
de clculos.
Proceder as tiene varias ventajas:
Mejora la legibilidad del cdigo (cada fichero es relativamente breve y a
grupa
temticamente las funciones, registros y constantes).
La compilacin es ms rpida (cuando se modifica un fichero, slo es necesario
compilar ese fichero).
Y, quiz lo ms importante, permite reutilizar cdigo. Es un beneficio a medi
o y
largo plazo. Si, por ejemplo, te dedicas a programar videojuegos tridim
ensionales,
vers que todos ellos comparten ciertas constantes, registros y funciones
definidas
por t o por otros programadores: tipos de datos para modelar puntos, polg
onos,
texturas, etctera; funciones que los manipulan, visualizan, leen/escribe
n en disco,
etctera. Puedes definir estos elementos en un fichero y utilizarlo en cu
antos programas desees. Alternativamente, podras copiar-y-pegar las funciones, co
nstantes
y registros que uno necesita en cada programa, pero no es conveniente e
n absoluto:
corregir un error en una funcin obligara a editar todos los programas en
los que
se peg; por contra, si est en un solo fichero, basta con corregir la defi
nicin una
sola vez.
C permite escribir un programa como una coleccin de unidades de compilacin.
El concepto es similar al de los mdulos Python: cada unidad agrupa definiciones d
e
variables, tipos, constantes y funciones orientados a resolver cierto tipo de pr
oblemas.
Puedes compilar independientemente cada unidad de compilacin (de ah el nombre) de
modo que el compilador genere un fichero binario para cada una de ellas. El enla
zador
se encarga de unir en una ltima etapa todas las unidades compiladas para crear un
nico fichero ejecutable.
Lo mejor ser que aprendamos sobre unidades de compilacin escribiendo una muy
sencilla: un mdulo en el que se define una funcin que calcula el mximo de dos nmero
enteros. El fichero que corresponde a esta unidad de compilacin se llamar
.
He aqu su contenido:
int maximo int a int b
if a b
return a
else
return b
El programa principal se escribir en otro fichero llamado
ho programa
llamar a la funcin maximo:

. Dic

include
int main void
int x y
printf
scanf
printf
scanf
printf

x
y
maximo x y

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

225
225

return 0
Hemos marcado el programa como incorrecto. Por qu? Vers, estamos usando una funcin, maximo, que no est definida en el fichero
. Cmo sabe el compilador
cuntos parmetros recibe dicha funcin?, y el tipo de cada parmetro?, y el tipo del
valor de retorno? El compilador se ve obligado a generar cdigo de mquina para llam
ar
a una funcin de la que no sabe nada. Mala cosa.
Cmo se resuelve el problema? Puedes declarar la funcin sin definirla, es decir,
puedes declarar el aspecto de su cabecera (lo que denominamos su prototipo) e in
dicar
que es una funcin definida externamente:
include
extern int maximo int a int b
int main void
int x y
printf
scanf
printf
scanf
printf
return 0

x
y
maximo x y

El prototipo contiene toda la informacin til para efectuar la llamada a la funcin,


pero no contiene su cuerpo: la cabecera acaba con un punto y coma. Fjate en que l
a
declaracin del prototipo de la funcin maximo empieza con la palabra clave extern.
Con
ella se indica al compilador que maximo est definida en algn mdulo externo. Tambin
puedes indicar con extern que una variable se define en otro mdulo.
Puedes compilar el programa as:

La compilacin necesita tres pasos: uno por cada unidad de compilacin y otro para
enlazar.
1.
ichero

El primer paso (
o unidad de compilacin

) traduce a cdigo de mquina el f


. La opcin

indica al compil

ador que
es un mdulo y no define a la funcin main. El resultado de
la compilacin se deja en un fichero llamado

. La extensin abrevi

a el
trmino object code, es decir, cdigo objeto. Los ficheros con extensin
contienen el cdigo de mquina de nuestras funciones8 , pero no es direct
amente
ejecutable.

2.
El segundo paso (
) es
similar al primero y genera el fichero
a partir de
.
8
. . . pero no slo eso: tambin contienen otra informacin, como la denominada
tabla de smbolos.

Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
226
226

Introduccin a la programacin con C - UJI


UJI
c

3.
El tercer paso (
) es
especial. El
compilador recibe dos ficheros con extensin y genera un nico fichero eje
cutable, llamado principal. Este ltimo paso se encarga de enlazar las dos
unidades
compiladas para generar el fichero ejecutable.
Por enlazar entendemos que las llamadas a funciones cuyo cdigo de mquina
era desconocido (estaba en otra unidad de compilacin) se traduzcan en sa
ltos
a las direcciones en las que se encuentran los subprogramas de cdigo mqu
ina
correspondientes (y que ahora se conocen).
Aqu tienes un diagrama que ilustra el proceso:
Paso 1
Compilador
Paso 3
Enla
zador

principal
Paso 2
Compilador
Puedes ahorrarte un paso fundiendo los dos ltimos en uno slo. As:

Este diagrama muestra todos los pasos del proceso a los que aludimos:
Paso 1
Compilador
Compilador
zador

Enla

principal
Paso 2

Para conseguir un programa ejecutable es necesario que uno de los mdulos (pero
slo uno de ellos!) defina una funcin main. Si ningn mdulo define main o si main se
define en ms de un mdulo, el enlazador protestar y no generar fichero ejecutable
alguno.

Hemos resuelto el problema de gestionar diferentes unidades de compilacin, pero l


a
solucin de tener que declarar el prototipo de cada funcin en toda unidad de compil
acin
que la usa no es muy buena. Hay una mejor: definir un fichero de cabecera. Los f
icheros
de cabecera agrupan las declaraciones de funciones (y cualquier otro elemento) d
efinidos
en un mdulo. Las cabeceras son ficheros con extensin (es un convenio: la h es
abreviatura de header).

Nuestra cabecera ser este fichero:


extern int maximo int a int b
Para incluir la cabecera en nuestro programa, escribiremos una nueva dir
ectiva include:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2

227
227

Introduccin a la programacin con C - UJI


UJI
c

Documentacin y cabeceras
Es importante que documentes bien los ficheros de cabecera, pues es frecue
nte que los
programadores que usen tu mdulo lo consulten para hacerse una idea de qu ofr
ece.
Nuestro mdulo podra haberse documentado as:

Mdulo: extremos
Propsito: funciones para clculo de valores mximos
y mnimos.
Autor: A. U. Thor.
Fecha: 12 de enero de 1997
Estado: Incompleto. Falta la funcin minimo.
extern int maximo int a int b
Calcula el mximo de dos nmero enteros a y b.
Y por qu los programadores no miran directamente el fichero
en
lugar del
cuando quieren consultar algo? Por varias razones. Una de ellas es que, po
siblemente,
el
no est accesible. Si el mdulo es un producto comercial, probablemente
slo les
hayan vendido el mdulo ya compilado (el fichero ) y el fichero de cabecera.
Pero
incluso si se tiene acceso al , puede ser preferible ver el . El fichero
puede
estar plagado de detalles de implementacin, funciones auxiliares, variables
para uso
interno, etc., que hacen engorrosa su lectura. El fichero de cabecera cont
iene una somera
declaracin de cada uno de los elementos del mdulo que se publican para su uso
en
otros mdulos o programas, as que es una especie de resumen del .

include
include
int main void
int x y
printf
scanf
printf
scanf
printf
return 0

x
y
maximo x y

La nica diferencia con respecto a otros include que ya hemos usado estriba en el
uso de
comillas dobles para encerrar el nombre del fichero, en lugar de los caracteres
y .
Con ello indicamos al preprocesador que el fichero
se encuent
ra en nuestro
directorio activo. El preprocesador se limita a sustituir la lnea en la que apare
ce include
por el contenido del fichero. En un ejemplo tan sencillo no hem
os ganado
mucho, pero si el mdulo
contuviera muchas funciones, con slo una
lnea
habramos conseguido importarlas todas.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

228
228

Aqu tienes una actualizacin del grfico que muestra el proceso completo de compi
lacin:
Compilador
Preprocesador
Enlazador

Compilador

principal

No slo puedes declarar funciones en los ficheros de cabecera. Tambin puedes defini
r
constantes, variables y registros.
Poco hay que decir sobre las constantes. Basta con que las definas con defi
ne en
el fichero de cabecera. Las variables, sin embargo, s plantean un problema. Este
mdulo,
por ejemplo, declara una variable entera en
:
int variable
Si deseamos que otras unidades de compilacin puedan acceder a esa variable, tendr
emos
que incluir su declaracin en la cabecera. Cmo? Una primera idea es poner, directame
nte,
la declaracin as:
E
E
int variable
Pero es incorrecta. El problema radica en que cuando incluyamos la cabecera
en nuestro programa, se insertar la lnea int variable , sin ms, as que se estar definiendo una nueva variable con el mismo identificador que otra. Y declarar dos va
riables
con el mismo identificador es un error.
Quien detecta el error es el enlazador: cuando vaya a generar el programa ej
ecutable,
encontrar que hay dos objetos que tienen el mismo identificador, y eso est prohibi
do.
La solucin es sencilla: preceder la declaracin de variable en la cabecera
con la palabra reservada extern:
extern int variable
De ese modo, cuando se compila un programa que incluye a
, el comp
ilador
sabe que variable es de tipo int y que est definida en alguna unidad de compilacin
,
por lo que no la crea por segunda vez.

Finalmente, puedes declarar tambin registros en las cabeceras. Como los programas
que construiremos son sencillos, no se plantear problema alguno con la definicin d
e
registros: basta con que pongas su declaracin en la cabecera, sin ms. Pero si tu p

rograma
incluye dos cabeceras que, a su vez, incluyen ambas a una tercera donde se defin
en
constantes o registros, puedes tener problemas. Un ejemplo ilustrar mejor el tipo
de
dificultades al que nos enfrentamos. Supongamos que un fichero
define u
n registro:

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

229
229

Bibliotecas
Ya has usado funciones y datos predefinidos, como las funciones y las con
stantes matemticas. Hemos hablado entonces del uso de la biblioteca matemtica. Por qu bi
blioteca y no mdulo? Una biblioteca es ms que un mdulo: es un conjunto de
mdulos.
Cuando se tiene una plyade de ficheros con extensin , conviene empaqueta
rlos
en uno solo con extensin (por archive). Los ficheros con extensin son
similares a los ficheros con extensin
: meras colecciones de ficheros.
De hecho,
tar (tape archiver) es una evolucin de
(por archiver), el programa con el
ue
se manipulan los ficheros con extensin .
La biblioteca matemtica, por ejemplo, agrupa un montn de mdulos. En un s
istema
Linux se encuentra en el fichero
y puedes consulta
r su contenido
con esta orden:

Como puedes ver, hay varios ficheros con extensin en su interior. (Slo
te
mostramos el principio y el final del resultado de la llamada, pues hay u
n total de 395
ficheros!)
Cuando usas la biblioteca matemtica compilas as:

o, equivalentemente, as:

En el segundo caso hacemos explcito el nombre de la biblioteca en la q


ue se
encuentran las funciones matemticas. El enlazador no slo sabe tratar ficher
os con

extensin : tambin sabe buscarlos en los de extensin .


En cualquier caso, sigue siendo necesario que las unidades de compila
cin conozcan
el perfil de las funciones que usan y estn definidas en otros mdulos o bibl
iotecas. Por
eso inclumos, cuando conviene, el fichero
en nuestros programas.
Hay infinidad de bibliotecas que agrupan mdulos con utilidades para di
ferentes
campos de aplicacin: resolucin de problemas matemticos, diseo de videojuegos,
reproduccin de msica, etc. Algunas son cdigo abierto, en cuyo caso se distri
buyen
con los ficheros de extensin , los ficheros de extensin y alguna utilidad
para facilitar la compilacin (un makefile). Cuando son comerciales es frec
uente que se
mantenga el cdigo fuente en privado. En tal caso, se distribuye el fichero
con extensin
(o una coleccin de ficheros con extensin ) y uno o ms ficheros con extensin
.

Cabecera
struct
int a
Fin de cabecera
Ahora, los ficheros
existencia de sendas funciones:

incluyen a

y declaran la

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

230
230

Introduccin a la programacin con C -UJI


cUJI

Cabecera
include
int funcion de b punto h int x
Fin de cabecera

Cabecera
include
int funcion de c punto h int x
Fin de cabecera
Y, finalmente, nuestro programa incluye tanto a

como a

include
include
include
int main void

El resultado es que el
acaba quedando incluido dos veces! Tras el paso de
por el preprocesador, el compilador se enfrenta, a este texto:
include
Cabecera
Cabecera
struct
int a

.
.

Fin de cabecera

int funcion de b punto h int x


Fin de cabecera
.
Cabecera
Cabecera
struct
int a

.
.

Fin de cabecera
int funcion de c punto h int x
Fin de cabecera
.
int main void

Andrs aMarzal/Isabel

Introduccin

Gracia
la programacin con -CISBN: 978-84-693-0143-2

Introduccin a la programacin con C -UJI


cUJI

231
231

El compilador encuentra, por tanto, la definicin de struct por duplicado, y n


os avisa
del error. No importa que las dos veces se declare de la misma forma: C lo conside
ra
ilegal. El problema puede resolverse reescribiendo
(y, en general, cualq
uier fichero
cabecera) as:
Cabecera de
ifndef
define
struct
int a
endif
Fin de cabecera de
Las directivas ifndef/ endif marcan una zona de cdigo condicional. Se interpretan
as: si la constante
no est definida, entonces incluye el fragmento hasta el e
ndif,
en caso contrario, sltate el texto hasta el endif. O sea, el compilador ver o no lo
que
hay entre las lneas 3 y 8 en funcin de si existe o no una determinada constante. N
o
debes confundir estas directivas con una sentencia if: no lo son. La sentencia i
f permite
ejecutar o no un bloque de sentencias en funcin de que se cumpla o no una condicin
en tiempo de ejecucin. Las directivas presentadas permiten que el compilador vea
o no
un fragmento arbitrario de texto en funcin de si existe o no una constante en tie
mpo de
compilacin.
Observa que lo primero que se hace en ese fragmento de programa es definir l
a
constante
(lnea 3). La primera vez que se incluya la cabecera
no e
star an
definida
, as que se incluirn las lneas 38. Uno de los efectos ser que
pasar
a estar definida. La segunda vez que se incluya la cabecera
,
ya est
ar definida,
as que el compilador no ver por segunda vez la definicin de struct .
El efecto final es que la definicin de struct slo se ve una vez. He aqu lo que
resulta de
tras su paso por el preprocesador:
include
Cabecera
Cabecera
struct
int a

.
.

Fin de cabecera

int funcion de b punto h int x


Fin de cabecera
.
Cabecera
.
Cabecera
.
Fin de cabecera

int funcion de c punto h int x


Fin de cabecera
.
int main void

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

232
232

La segunda inclusin de
no ha supuesto el copiado del texto guardado entre dir
ectivas
ifndef endif. Ingenioso, no?

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

233
233

La Reina se puso congestionada de furia, y, tras lanzarle una mirada fel


ina,
empez a gritar: Que le corten la cabeza! Que le corten. . . !.
LEWIS CARROLL, Alicia en el Pas d
e las Maravillas.
Vimos en el captulo 2 que los vectores de C presentaban un serio inconveniente co
n
respecto a las listas de Python: su tamao deba ser fijo y conocido en tiempo de co
mpilacin, es decir, no podamos alargar o acortar los vectores para que se adaptaran
al
tamao de una serie de datos durante la ejecucin del programa. C permite una gestin
dinmica de la memoria, es decir, solicitar memoria para albergar el contenido de
estructuras de datos cuyo tamao exacto no conocemos hasta que se ha iniciado la ejecucin
del programa. Estudiaremos aqu dos formas de superar las limitaciones de tamao que
impone el C:
mediante vectores cuyo tamao se fija en tiempo de ejecucin,
y mediante registros enlazados, tambin conocidos como listas enlazadas (o
, simplemente, listas).
Ambas aproximaciones se basan en el uso de punteros y cada una de ellas presenta
diferentes ventajas e inconvenientes.

Sabemos definir vectores indicando su tamao en tiempo de compilacin:


define
10
int a
Pero, y si no sabemos a priori cuntos elementos debe albergar el vector?1 Por lo
estudiado hasta el momento, podemos definir
como el nmero ms grande de
elementos posible, el nmero de elementos para el peor de los casos. Pero, y si no
podemos determinar un nmero mximo de elementos? Aunque pudiramos, y si ste
fuera tan grande que, en la prctica, supusiera un despilfarro de memoria intolera
ble
para situaciones normales? Imagina una aplicacin de agenda telefnica personal que,
1
En la seccin 3.5.3 vimos cmo definir vectores locales cuya talla se decide al
ejecutar una funcin:
lo que denominamos vectores de longitud variable. Nos proponemos dos objetivos: po
r una parte, poder
redimensionar vectores globales; y, por otro, vamos a permitir que un vector cre
zca y decrezca en tamao
cuantas veces queramos. Los vectores de longitud variable que estudiamos en su mom
ento son inapropiados
para cualquiera de estos dos objetivos.
234
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

234

por si acaso, reserva 100000 entradas en un vector. Lo ms probable es que un usua


rio
convencional no gaste ms de un centenar. Estaremos desperdiciando, pues, unas 999
00
celdas del vector, cada una de las cuales puede consistir en un centenar de byte
s. Si
todas las aplicaciones del ordenador se disearan as, la memoria disponible se agot
ara
rapidsimamente.
malloc free
Afortunadamente, podemos definir, durante la ejecucin del programa, vectores cuyo
tamao es exactamente el que el usuario necesita. Utilizaremos para ello dos funcion
es de
la biblioteca estndar (disponibles incluyendo la cabecera
):
malloc (abreviatura de memory allocate, que podemos traducir por reservar
memoria): solicita un bloque de memoria del tamao que se indique (en bytes)
;
free (que en ingls significa liberar): libera memoria obtenida con malloc,
es decir,
la marca como disponible para futuras llamadas a malloc.
Para hacernos una idea de cmo funciona, estudiemos un ejemplo:
include
include
int main void
int a
int talla i
printf

scanf

tall

a
a malloc talla sizeof int
for i 0 i talla i
a i i
free a
a
return 0
Fjate en cmo se ha definido el vector a (lnea 6): como int a, es decir, como punter
o
a entero. No te dejes engaar: no se trata de un puntero a un entero, sino de un p
untero
a una secuencia de enteros. Ambos conceptos son equivalentes en C, pues ambos so
n
meras direcciones de memoria. La variable a es un vector dinmico de enteros, pues
su
memoria se obtiene dinmicamente, esto es, en tiempo de ejecucin y segn convenga a
las necesidades. No sabemos an cuntos enteros sern apuntados por a, ya que el valor
de talla no se conocer hasta que se ejecute el programa y se lea por teclado.
Sigamos. La lnea 10 reserva memoria para talla enteros y guarda en a la direc

cin
de memoria en la que empiezan esos enteros. La funcin malloc presenta un prototip
o
similar a ste:

void

malloc int bytes

Es una funcin que devuelve un puntero especial, del tipo de datos void . Qu signifi
ca
void ? Significa puntero a cualquier tipo de datos, o sea, direccin de memoria,
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

235
235

sin ms. La funcin malloc no se usa slo para reservar vectores dinmicos de enteros:
puedes reservar con ella vectores dinmicos de cualquier tipo base. Analicemos aho
ra el
argumento que pasamos a malloc. La funcin espera recibir como argumento un nmero
entero: el nmero de bytes que queremos reservar. Si deseamos reservar talla valor
es
de tipo int, hemos de solicitar memoria para talla sizeof int bytes. Recuerda qu
e
sizeof int es la ocupacin en bytes de un dato de tipo int (y que estamos asumiend
o
que es de 4).
Si el usuario decide que talla valga, por ejemplo, 5, se reservar un total de
20 bytes
y la memoria quedar as tras ejecutar la lnea 10:
0 1 2 3
4
a
Es decir, se reserva suficiente memoria para albergar 5 enteros.
Como puedes ver, las lneas 1112 tratan a a como si fuera un vector de enteros
cualquiera. Una vez has reservado memoria para un vector dinmico, no hay diferenc
ia
alguna entre l y un vector esttico desde el punto de vista prctico. Ambos pueden
indexarse (lnea 12) o pasarse como argumento a funciones que admiten un vector de
l
mismo tipo base.
Aritmtica de punteros
Una curiosidad: el acceso indexado a 0 es equivalente a a. En general, a
i es
equivalente a a i , es decir, ambas son formas de expresar el concepto acc
ede al
contenido de la direccin a con un desplazamiento de i veces el tamao del ti
po base.
La sentencia de asignacin a i
i podra haberse escrito como a i
i. En C es
posible sumar o restar un valor entero a un puntero. El entero se interpr
eta como un
desplazamiento dado en unidades tamao del tipo base (en el ejemplo, 4 bytes,
que
es el tamao de un int). Es lo que se conoce por aritmtica de punteros.
La aritmtica de punteros es un punto fuerte de C, aunque tambin tiene s
us detractores: resulta sencillo provocar accesos incorrectos a memoria si se usa
mal.
Finalmente, la lnea 13 del programa libera la memoria reservada y la lnea 14 gu
arda
en a un valor especial:
. La funcin free tiene un prototipo similar a ste:

void free void

puntero

Como ves, free recibe un puntero a cualquier tipo de datos: la direccin de memori
a en la
que empieza un bloque previamente obtenido con una llamada a malloc. Lo que hace

free
es liberar ese bloque de memoria, es decir, considerar que pasa a estar disponib
le para
otras posibles llamadas a malloc. Es como cerrar un fichero: si no necesito un r
ecurso, lo
libero para que otros lo puedan aprovechar.2 Puedes aprovechar as la memoria de f
orma
ptima.
Recuerda: tu programa debe efectuar una llamada a free por cada llamada a ma
lloc.
Es muy importante.
Conviene que despus de hacer free asignes al puntero el valor
, espec
ialmente
si la variable sigue viva durante bastante tiempo.
es una constante definid
a en
. Si un puntero vale
, se entiende que no apunta a un bloque de m
emoria.
Grficamente, un puntero que apunta a
se representa as:
2
Y, como en el caso de un fichero, si no lo liberas t explcitamente, se liber
a automticamente al finalizar
la ejecucin del programa. An as, te exigimos disciplina: oblgate a liberarlo t mismo
tan pronto dejes de
necesitarlo.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
6

236
23

Introduccin a la programacin con C -UJI


cUJI

Liberar memoria no cambia el valor del puntero


La llamada a free libera la memoria apuntada por un puntero, pero no m
odifica el valor
de la variable que se le pasa. Imagina que un bloque de memoria de 10
enteros que
empieza en la direccin 1000 es apuntado por una variable a de tipo int
, es decir,
imagina que a vale 1000. Cuando ejecutamos free a , ese bloque se libe
ra y pasa a
estar disponible para eventuales llamadas a malloc, pero a sigue valien
do 1000! Por
qu? Porque a se ha pasado a free por valor, no por referencia, as que fr
ee no tiene
forma de modificar el valor de a. Es recomendable que asignes a a el v
alor
despus
de una llamada a free, pues as haces explcito que la variable a no apunt
a a nada.
Recuerda, pues, que es responsabilidad tuya y que conviene hacerlo
: asigna explcitamente el valor
a todo puntero que no apunte a memoria reserv
ada.

La funcin malloc puede fallar por diferentes motivos. Podemos saber cundo ha f
allado porque malloc lo notifica devolviendo el valor
. Imagina que solicitas 2
megabytes
de memoria en un ordenador que slo dispone de 1 megabyte. En tal caso, la funcin
malloc devolver el valor
para indicar que no pudo efectuar la reserva de
memoria
solicitada.
Los programas correctamente escritos deben comprobar si se pudo obtener la m
emoria
solicitada y, en caso contrario, tratar el error.
a malloc talla
sizeof int
if a
printf
else

Es posible (y una forma de expresin idiomtica de C) solicitar la memoria y comprob


ar si
se pudo obtener en una nica lnea (presta atencin al uso de parntesis, es importante)
:
if
else

a malloc talla
printf

sizeof int

Nuestros programas, sin embargo, no incluirn esta comprobacin. Estamos aprendiendo


a programar y sacrificaremos las comprobaciones como sta en aras de la legibilida
d
de los programas. Pero no lo olvides: los programas con un acabado profesional d
eben
comprobar y tratar posibles excepciones, como la no existencia de suficiente mem
oria.
Tambin puedes usar
para inicializar punteros y dejar explcitamente clar
o que
no se les ha reservado memoria.
include
include
int main void
int
a
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Introduccin a la programacin con C
c
UJI

237
237

Fragmentacin de la memoria
Ya hemos dicho que malloc puede fracasar si se solicita ms memoria de la d
isponible en
el ordenador. Parece lgico pensar que en un ordenador con 64 megabytes, de
los que el
sistema operativo y los programas en ejecucin han consumido, digamos, 16 m
egabytes,
podamos solicitar un bloque de hasta 48 megabytes. Pero eso no est garanti
zado.
Imagina que los 16 megabytes ya ocupados no estn dispuestos contiguamente
en la
memoria sino que, por ejemplo, se alternan con fragmentos de memoria libr
e de modo
que, de cada cuatro megabytes, uno est ocupado y tres estn libres, como mue
stra esta
figura:

En tal caso, el bloque de memoria ms grande que podemos obtener con malloc
es de
slo tres megabytes!
Decimos que la memoria est fragmentada para referirnos a la alternanci
a de bloques
libres y ocupados que limita su disponibilidad. La fragmentacin no slo limi
ta el mximo
tamao de bloque que puedes solicitar, adems, afecta a la eficiencia con la
que se
ejecutan las llamadas a malloc y free.

int talla i
printf

scanf

talla
a malloc talla sizeof int
for i 0 i talla i
a i i
free a
a
return 0

Es hora de poner en prctica lo aprendido desarrollando un par de ejemplos.


Creacin de un nuevo vector con una seleccin, de talla desconocida, de elementos de
otro vector
Empezaremos por disear una funcin que recibe un vector de enteros, selecciona aque
llos
cuyo valor es par y los devuelve en un nuevo vector cuya memoria se solicita dinm
icamente.
int
selecciona pares int a
int talla
int i j numpares
int pares

Primero hemos de averiguar cuntos elementos pares hay en a.


for i 0 i talla i
if a i
2
0
numpares
Ahora podemos pedir memoria para ellos.
pares malloc numpares sizeof int
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

238
238

Aritmtica de punteros y recorrido de vectores


La aritmtica de punteros da lugar a expresiones idiomticas de C que deberas s
aber
leer. Fjate en este programa:
include
include
int main void
int a
int talla i
int p
printf

scanf

tall

a
a malloc talla sizeof int
for i 0 p a i talla i
p
p i
free a
a
return 0
El efecto del bucle es inicializar el vector con la secuencia 0, 1, 2. . .
El puntero p empieza
apuntando a donde a, o sea, al principio del vector. Con cada autoincremen
to, p , pasa
a apuntar a la siguiente celda. Y la sentencia p i asigna al lugar apuntad
o por p el
valor i.

Y, finalmente, copiar los elementos pares en la zona de memoria soli


citada.
j 0
for i 0 i talla i
if a i
2
pares j

0
a i

return pares
Observa que devolvemos un dato de tipo int , es decir, un puntero a entero; buen
o, en
realidad se trata de un puntero a una secuencia de enteros (recuerda que son con
ceptos
equivalentes en C). Es la forma que tenemos de devolver vectores desde una funcin
.
Este programa, por ejemplo, llama a selecciona pares:
include
include
include
define

10

int main void


int vector

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

239
239

int

seleccion

Llenamos el vector con valores aleatorios.


srand time 0
for i 0 i
i
vector i rand
Se efecta ahora la seleccin de pares.
seleccion selecciona pares vector
La variable seleccion apunta ahora a la zona de memoria con los el
ementos pares.
?
S, pero, cuntos elementos pares hay?
for i 0 i
i
printf
seleccion i
free seleccion
seleccion
return 0
Tenemos un problema al usar selecciona pares: no sabemos cuntos valores ha se
leccionado. Podemos modificar la funcin para que modifique el valor de un parmetro
que pasamos por referencia:
int

selecciona pares int a

int talla int

numpares

int i j
int pares
Contamos los elementos pares en el parmetro numpares, pasado por refe
rencia.
numpares 0
for i 0 i talla i
if a i
2
0
numpares
pares

malloc

numpares

sizeof int

j 0
for i 0 i talla i
if a i
2 0
pares j
a i
return pares
Ahora podemos resolver el problema:
include
include
include
define
int

10
selecciona pares int a

int talla int

numpares

int i j
int pares
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

240
240

Contamos los elementos pares en el parmetro numpares, pasado por ref


erencia.
numpares 0
for i 0 i talla i
if a i
2
0
numpares
pares

malloc

numpares

sizeof int

j 0
for i 0 i talla i
if a i
2 0
pares j
a i
return pares
int main void
int vector
i
int seleccion seleccionados
Llenamos el vector con valores aleatorios.
srand time 0
for i 0 i
i
vector i rand
Se efecta ahora la seleccin de pares.
seleccion selecciona pares vector
seleccionados
La variable seleccion apunta ahora a la zona de memoria con los elem
entos pares.
Adems, la variable seleccionados contiene el nmero de pares.
Ahora los mostramos en pantalla.
for i 0 i seleccionados i
printf
seleccion i
free seleccion
seleccion
return 0
Por cierto, el prototipo de la funcin, que es ste:
int
selecciona pares int a
int talla int

seleccionado

s
puede cambiarse por este otro:
int
selecciona pares int
s

a int talla int

seleccionado

Conceptualmente, es lo mismo un parmetro declarado como int a que como int a:


ambos son, en realidad, punteros a enteros3 . No obstante, es preferible utiliza
r la primera
forma cuando un parmetro es un vector de enteros, ya que as lo distinguimos fcilmen
te
de un entero pasado por referencia. Si ves el ltimo prototipo, no hay nada que te
permita
saber si a es un vector o un entero pasado por referencia como seleccionados. Es
ms

legible, pues, la primera forma.


3
En realidad, hay una pequea diferencia. La declaracin int a hace que a sea un
puntero inmutable,
mientras que int a permite modificar la direccin apuntada por a haciendo, por eje
mplo, a . De todos
modos, no haremos uso de esa diferencia en este texto.
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
cUJI
Introduccin a la programacin con C -UJI

241
241

No puedes devolver punteros a datos locales


Como un vector de enteros y un puntero a una secuencia de enteros son, en
cierto modo,
equivalentes, puede que esta funcin te parezca correcta:
int

primeros void
int i v 10
for i 0 i 10 i
v i i 1
return v

La funcin devuelve, a fin de cuentas, una direccin de memoria en la que emp


ieza una
secuencia de enteros. Y es verdad: eso es lo que hace. El problema radica
en que la
memoria a la que apunta no existe fuera de la funcin! La memoria que ocupa v
se
libera tan pronto finaliza la ejecucin de la funcin. Este intento de uso de
la funcin,
por ejemplo, trata de acceder ilegalmente a memoria:
int main void
int

a primeros
printf

a i

No existe a i .

Recuerda: si devuelves un puntero, ste no puede apuntar a datos locales.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
219 Disea una funcin que seleccione todos los nmeros positivos de un vector de
enteros. La funcin recibir el vector original y un parmetro con su longitud y devol
ver
dos datos: un puntero al nuevo vector de enteros positivos y su longitud. El pun
tero
se devolver como valor de retorno de la funcin, y la longitud mediante un parmetro
adicional (un entero pasado por referencia).
220 Desarrolla una funcin que seleccione todos los nmeros de un vector de float
mayores que un valor dado. Disea un programa que llame correctamente a la funcin y
muestre por pantalla el resultado.
221 Escribe un programa que lea por teclado un vector de float cuyo tamao se
solicitar previamente al usuario. Una vez ledos los componentes del vector, el pro
grama
copiar sus valores en otro vector distinto que ordenar con el mtodo de la burbuja.
Recuerda liberar toda memoria dinmica solicitada antes de finalizar el programa.
222 Escribe una funcin que lea por teclado un vector de float cuyo tamao se
solicitar previamente al usuario. Escribe, adems, una funcin que reciba un vector c
omo
el ledo en la funcin anterior y devuelva una copia suya con los mismos valores, pe
ro
ordenados de menor a mayor (usa el mtodo de ordenacin de la burbuja o cualquier ot
ro
que conozcas).

Disea un programa que haga uso de ambas funciones. Recuerda que debes liberar
toda memoria dinmica solicitada antes de finalizar la ejecucin del programa.
223 Escribe una funcin que reciba un vector de enteros y devuelva otro con sus n
mayores valores, siendo n un nmero menor o igual que la talla del vector original
.
224 Escribe una funcin que reciba un vector de enteros y un valor n. Si n es meno
r
o igual que la talla del vector, la funcin devolver un vector con las n primeras c
eldas
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
242
242
ramacin con C - UJI

Introduccin a la prog

c
UJI

del vector original. En caso contrario, devolver un vector de n elementos con un


copia
del contenido del original y con valores nulos hasta completarlo.
...............................................................................
...
No resulta muy elegante que una funcin devuelva valores mediante return y, a l
a vez,
mediante parmetros pasados por referencia. Una posibilidad es usar nicamente valor
es
pasados por referencia:
include
include
include
define

10

void selecciona pares int a


int numpares

int talla int

pares

int i j
numpares 0
for i 0 i talla i
if a i 2 0
numpares
pares

malloc

j 0
for i 0 i talla i
if a i 2 0
pares j

numpares

sizeof int

a i

int main void


int vector
i
int seleccion seleccionados
srand time 0
for i 0 i
i
vector i rand
selecciona pares vector
ccionados

seleccion

sele

for i 0 i seleccionados i
printf
seleccion i
free seleccion
seleccion
return 0
Fjate en la declaracin del parmetro pares en la lnea 7: es un puntero a un vecto
r
de enteros, o sea, un vector de enteros cuya direccin se suministra a la funcin. Po

r qu?
Porque a resultas de llamar a la funcin, la direccin apuntada por pares ser una nuev
a
direccin (la que obtengamos mediante una llamada a malloc). La lnea 16 asigna un v
alor
a pares. Resulta interesante que veas cmo se asigna valores al vector apuntado po
r
pares en la lnea 21 (los parntesis alrededor de pares son obligatorios). Finalmen
te,
observa que seleccion se declara en la lnea 27 como un puntero a entero y que se
pasa
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
3

243
24

Introduccin a la programacin con C -UJI


cUJI

la direccin en la que se almacena dicho puntero en la llamada a selecciona pares


desde
la lnea 33.
Hay una forma alternativa de indicar que pasamos la direccin de memoria de u
n
puntero de enteros. La cabecera de la funcin selecciona pares podra haberse defini
do
as:
void selecciona pares int a
int talla int
pares in
t
numpares
Ves cmo usamos un doble asterisco?
Ms elegante resulta definir un registro vector dinmico de enteros que almacene
conjuntamente tanto el vector de elementos propiamente dicho como el tamao del ve
ctor4 :

include
include
include
struct VectorDinamicoEnteros
int elementos
Puntero a la zona de memoria con los elementos.
int talla
Nmero de enteros almacenados en esa zona de memoria.
struct VectorDinamicoEnteros selecciona pares struct VectorDinamicoEnteros
entrada
Recibe un vector dinmico y devuelve otro con una seleccin de los elemen
tos
pares del primero.
int i j
struct VectorDinamicoEnteros pares
pares talla 0
for i 0 i entrada talla i
if entrada elementos i
pares talla
pares elementos

2
malloc pares talla

0
sizeof int

j 0
for i 0 i entrada talla i
if entrada elementos i 2
0
pares elementos j entrada elementos i
return pares
int main void
int i
struct VectorDinamicoEnteros vector seleccionados
vector talla 10
vector elementos malloc vector talla
srand time 0
for i 0 i vector talla i

sizeof int

vector elementos i rand


4
Aunque recomendemos este nuevo mtodo para gestionar vectores de tamao variabl
e, has de saber,
cuando menos, leer e interpretar correctamente parmetros con tipos como int a , i
nt a, int a o int a,
pues muchas veces tendrs que utilizar bibliotecas escritas por otros programadore
s o leer cdigo fuente de
programas cuyos diseadores optaron por estos estilos de paso de parmetros.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
44

Introduccin a la programacin con C -UJI


cUJI

244
2

Valores de retorno como aviso de errores


Es habitual que aquellas funciones C que pueden dar lugar a errores nos advie
rtan
de ellos mediante el valor de retorno. La funcin malloc, por ejemplo, devuelve
el valor
cuando no consigue reservar la memoria solicitada y un valor diferente
cuando s
lo consigue. La funcin scanf , que hemos estudiado como si no devolviese valor
alguno,
s lo hace: devuelve el nmero de elementos cuyo valor ha sido efectivamente ledo.
Si,
por ejemplo, llamamos a scanf
a b , la funcin devuelve el valo
r 2 si
todo fue bien (se ley el contenido de dos variables). Si devuelve el valor 1,
es porque
slo consigui leer el valor de a, y si devuelve el valor 0, no consigui leer ning
uno de
los dos. Un programa robusto debe comprobar el valor devuelto siempre que se
efecte
una llamada a scanf ; as:
if

scanf
a
printf
else
Situacin normal.

Las rutinas que nosotros diseamos deberan presentar un comportamiento similar.


La
funcin selecciona pares, por ejemplo, podra implementarse as:
int

int selecciona pares int a


numpares

int talla int

pares

int i j
numpares 0
for i 0 i talla i
if a i
2 0
numpares
pares malloc numpares sizeof int
if pares
Algo fue mal: no conseguimos la memoria.
numpares 0
Informamos de que el vector tiene capacidad 0
...
return 0

y devolvemos el valor 0 para advertir de que hubo

un error.
j 0
for i 0 i talla i
if a i
2
0
pares j
a i
return 1
Si llegamos aqu, todo fue bien, as que avisamos de ello c
on el valor 1.
Aqu tienes un ejemplo de uso de la nueva funcin:
n

if
seleccionados

selecciona pares vector

seleccio

Todo va bien.
else
Algo fue mal.
Hay que decir, no obstante, que esta forma de aviso de errores empieza a q
uedar
obsoleto. Los lenguajes de programacin ms modernos, como C o Python, suelen
basar la deteccin (y el tratamiento) de errores en las denominadas excepciones.

seleccionados

selecciona pares vector

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

245
245

for i 0 i seleccionados talla i


printf
seleccionados elementos i
free seleccionados elementos
seleccionados elementos
seleccionados talla 0
return 0
El nico problema de esta aproximacin es la potencial fuente de ineficiencia qu
e
supone devolver una copia de un registro, pues podra ser de gran tamao. No es nues
tro
caso: un struct VectorDinamicoEnteros ocupa slo 8 bytes. Si el tamao fuera un prob
lema,
podramos usar una variable de ese tipo como parmetro pasado por referencia. Usaramo
s
as slo 4 bytes:
include
include
struct VectorDinamicoEnteros
int elementos
int talla
void selecciona pares struct VectorDinamicoEnteros entrada
struct VectorDinamicoEnteros pares
int i j
pares talla 0
for i 0 i entrada talla i
if entrada elementos i
pares talla
pares

elementos

2
malloc pares

0
talla

sizeof i

nt
j 0
for i 0 i entrada talla i
if entrada elementos i
pares elementos j

0
entrada elementos i

int main void


int i
struct VectorDinamicoEnteros vector seleccionados
vector talla 10
vector elementos malloc vector talla
for i 0 i vector talla i
vector elementos i rand
selecciona pares vector

sizeof int

seleccionados

for i 0 i seleccionados talla i


printf
seleccionados elementos i
free seleccionados elementos
seleccionados elementos
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
246
2
46

Introduccin a la programacin con C -UJI


cUJI

seleccionados talla

return 0
Como ves, tienes muchas soluciones tcnicamente diferentes para realizar lo mis
mo.
Debers elegir en funcin de la elegancia de cada solucin y de su eficiencia.
Listas Python
Empieza a quedar claro que Python es un lenguaje mucho ms cmodo que C para
gestionar vectores dinmicos, que all denominbamos listas. No obstante, debes
tener
presente que el intrprete de Python est escrito en C, as que cuando manejas
listas
Python ests, indirectamente, usando memoria dinmica como malloc y free.
Cuando creas una lista Python con una orden como a
0
5 o
a
0 0 0 0 0 , ests reservando espacio en memoria para 5 elementos y a
signndole a cada elemento el valor 0. La variable a puede verse como un simpl
e puntero
a esa zona de memoria (en realidad es algo ms complejo).
Cuando se pierde la referencia a una lista (por ejemplo, cambiando el
valor asignado
a a), Python se encarga de detectar automticamente que la lista ya no es a
puntada por
nadie y de llamar a free para que la memoria que hasta ahora ocupaba pase
a quedar
libre.

Representacin de polgonos con un nmero arbitrario de vrtices


Desarrollemos un ejemplo ms: un programa que lea los vrtices de un polgono y calcul
e
su permetro. Empezaremos por crear un tipo de datos para almacenar los puntos de
un
polgono. Nuestro tipo de datos se define as:
struct Punto
float x y
struct Poligono
struct Punto p
int puntos
Fjate en que un polgono presenta un nmero de puntos inicialmente desconocido,
por lo que hemos de recurrir a memoria dinmica. Reservaremos la memoria justa par
a
guardar dichos puntos en el campo p (un puntero a una secuencia de puntos) y el
nmero
de puntos se almacenar en el campo puntos.
Aqu tienes una funcin que lee un polgono por teclado y devuelve un registro con
el resultado:
struct Poligono lee poligono void
int i

struct Poligono pol


printf
scanf
pol puntos
pol p malloc pol puntos sizeof struct Punto
for i 0 i pol puntos i
printf
i
printf
scanf
pol p i x
printf
scanf
pol p i y
return pol
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

247
247

Es interesante la forma en que solicitamos memoria para el vector de puntos:


pol p
malloc pol puntos
sizeof struct Punto
Solicitamos memoria para pol puntos celdas, cada una con capacidad para un dato
de
tipo struct Punto (es decir, ocupando sizeof struct Punto bytes).
Nos vendr bien una funcin que libere la memoria solicitada para almacenar un
polgono, ya que, de paso, pondremos el valor correcto en el campo puntos:
void libera poligono struct Poligono
pol
free pol p
pol p
pol puntos 0
Vamos ahora a definir una funcin que calcula el permetro de un polgono:
float perimetro poligono struct Poligono pol
int i
float perim

0.0

for i 1 i pol puntos i


perim
sqrt pol p i x pol p i 1 x
pol p i x pol p i 1 x
pol p i y pol p i 1 y
pol p i y pol p i 1 y
perim
sqrt pol p pol puntos 1 x pol p 0
pol p pol puntos 1 x pol p
pol p pol puntos 1 y pol p
pol p pol puntos 1 y pol p
return perim

x
0 x
0 y
0 y

Es importante que entiendas bien expresiones como pol p i x. Esa, en particular,


significa: del parmetro pol, que es un dato de tipo struct Poligono, accede al compone
nte i del
campo p, que es un vector de puntos; dicho componente es un dato de tipo struct
Punto,
pero slo nos interesa acceder a su campo x (que, por cierto, es de tipo float).
Juntemos todas las piezas y aadamos un sencillo programa principal que invoq
ue a
las funciones desarrolladas:
include
include
struct Punto
float x y
struct Poligono
struct Punto p
int puntos
struct Poligono lee poligono void
int i

struct Poligono pol

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

248
248

printf
scanf
pol puntos
pol p malloc pol puntos sizeof struct Punto
for i 0 i pol puntos i
printf
i
printf
scanf
pol p i x
printf
scanf
pol p i y
return pol
void libera poligono struct Poligono

pol

free pol p
pol p
pol puntos 0
float perimetro poligono struct Poligono pol
int i
float perim

0.0

for i 1 i pol puntos i


perim
sqrt pol p i x pol p i 1 x
pol p i x pol p i 1 x
pol p i y pol p i 1 y
pol p i y pol p i 1 y
perim
sqrt pol p pol puntos 1 x pol p 0
pol p pol puntos 1 x pol p
pol p pol puntos 1 y pol p
pol p pol puntos 1 y pol p
return perim

x
0 x
0 y
0 y

int main void


struct Poligono un poligono
float perimetro
un poligono lee poligono
perimetro perimetro poligono un poligono
printf
perimetro
libera poligono un poligono
return 0
No es el nico modo en que podramos haber escrito el programa. Te presentamos
ahora una implementacin con bastantes diferencias en el modo de paso de parmetros:
include
include
struct Punto
float x y
struct Poligono
struct Punto p

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
roduccin a la programacin con C -UJI
cUJI

249
249

Int

int puntos
void lee poligono struct Poligono

pol

int i
printf
puntos
pol p malloc pol puntos
for i 0 i pol puntos i
printf
i
printf
scanf
printf
scanf

void libera poligono struct Poligono

scanf

pol

sizeof struct Punto


pol
pol

p i x
p i y

pol

free pol p
pol p
pol puntos 0
float perimetro poligono const struct Poligono
int i
float perim

pol

0.0

for i 1 i pol puntos i


perim
sqrt pol p i x pol p i 1 x
pol p i x pol p i 1 x
pol p i y pol p i 1 y
pol p i y pol p i 1 y
perim
sqrt pol p pol puntos 1 x pol p
0
x
pol p pol puntos 1 x pol
p
0 x
pol p pol puntos 1 y pol
p 0 y
pol p pol puntos 1 y pol
p 0 y
return perim
int main void
struct Poligono un poligono
float perimetro
lee poligono un poligono
perimetro perimetro poligono un poligono
printf
perimetro
libera poligono un poligono
return 0
En esta versin hemos optado, siempre que ha sido posible, por el paso de parmetros
por referencia, es decir, por pasar la direccin de la variable en lugar de una co

pia de su
contenido. Hay una razn para hacerlo: la eficiencia. Cada dato de tipo struct Pol
igono
esta formado por un puntero (4 bytes) y un entero (4 bytes), as que ocupa 8 bytes
.
Si pasamos o devolvemos una copia de un struct Poligono, estamos copiando 8 byte
s.
Si, por contra, pasamos su direccin de memoria, slo hay que pasar 4 bytes. En este
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
250
250

Introduccin a la programacin con C -UJI


cUJI

caso particular no hay una ganancia extraordinaria, pero en otras aplicaciones m


anejars
structs tan grandes que el paso de la direccin compensar la ligera molestia de la
notacin de acceso a campos con el operador .
Puede que te extrae el trmino const calificando el parmetro de perimetro poligo
no.
Su uso es opcional y sirve para indicar que, aunque es posible modificar la info
rmacin
apuntada por pol, no lo haremos. En realidad suministramos el puntero por cuestin
de
eficiencia, no porque deseemos modificar el contenido. Con esta indicacin consegu
imos
dos efectos: si intentsemos modificar accidentalmente el contenido, el compilador
nos
advertira del error; y, si fuera posible, el compilador efectuara optimizaciones q
ue no
podra aplicar si la informacin apuntada por pol pudiera modificarse en la funcin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
225 Funciona esta otra implementacin de perimetro poligono?
float perimetro poligono struct Poligono pol
int i
float perim

0.0

for i 1 i pol puntos 1 i


perim
sqrt pol p i pol puntos x pol p i 1
x
pol p i pol puntos x pol p i
1 x
pol p i pol puntos y pol p i
1 y
pol p i pol puntos y pol p i
1 y
return perim
226 Disea una funcin que cree un polgono regular de n lados inscrito en una
circunferencia de radio r. Esta figura muestra un pentgono inscrito en una circun
ferencia
de radio r y las coordenadas de cada uno de sus vrtices:
(r cos(
2/5), r sin(2/5))
(r cos(2 2/5), r sin(2 2/5))
r
(r cos(0), r sin(0))
(r cos(2 3/5), r sin(2 3/5))
(r cos(2 4/5)
, r sin(2 4/5))
Utiliza la funcin para crear polgonos regulares de talla 3, 4, 5, 6, . . . ins
critos en una
circunferencia de radio 1. Calcula a continuacin el permetro de los sucesivos polgo
nos

y comprueba si dicho valor se aproxima a 2.


227 Disea un programa que permita manipular polinomios de cualquier grado. Un
polinomio se representar con el siguiente tipo de registro:
struct Polinomio
float p
int grado
Como puedes ver, el campo p es un puntero a float, o sea, un vector dinmico de fl
oat.
Disea y utiliza funciones que hagan lo siguiente:
Leer un polinomio por teclado. Se pedir el grado del polinomio y, tras
reservar
memoria suficiente para sus coeficientes, se pedir tambin el valor de ca
da uno
de ellos.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
251
251
ramacin con C - UJI

Introduccin a la prog

c
UJI

Evaluar un polinomio p(x) para un valor dado de x.


Sumar dos polinomios. Ten en cuenta que cada uno de ellos puede ser de
diferente
grado y el resultado tendr, en principio, grado igual que el mayor grad
o de los
operandos. (Hay excepciones; piensa cules.)
Multiplicar dos polinomios.
228 Disea un programa que solicite la talla de una serie de valores enteros y
dichos valores. El programa ordenar a continuacin los valores mediante el procedim
iento
mergesort. (Ten en cuenta que el vector auxiliar que necesita merge debe tener c
apacidad
para el mismo nmero de elementos que el vector original.)
...............................................................................
...
Reserva con inicializacin automtica
La funcin calloc es similar a malloc, pero presenta un prototipo diferente
y hace algo
ms que reservar memoria: la inicializa a cero. He aqu un prototipo (similar
al) de
calloc:
void

calloc int nmemb int size

Con calloc, puedes pedir memoria para un vector de talla enteros as:
a

calloc talla sizeof int

El primer parmetro es el nmero de elementos y el segundo, el nmero de bytes


que
ocupa cada elemento. No hay que multiplicar una cantidad por otra, como h
acamos con
malloc.
Todos los enteros del vector se inicializan a cero. Es como si ejecuts
emos este
fragmento de cdigo:
a malloc talla sizeof int
for i 0 i talla i
a i

Por qu no usar siempre calloc, si parece mejor que malloc? Por eficienci
a. En
ocasiones no desears que se pierda tiempo de ejecucin inicializando la memo
ria a
cero, ya que t mismo querrs inicializarla a otros valores inmediatamente. R
ecuerda
que garantizar la mayor eficiencia de los programas es uno de los objetiv
os del lenguaje
de programacin C.

Las cadenas son un caso particular de vector. Podemos usar cadenas de cualquier
longitud
gracias a la gestin de memoria dinmica. Este programa, por ejemplo, lee dos cadena
s

y construye una nueva que resulta de concatenar a stas.


include
include
include
define

80

int main void


char cadena1
char cadena3

cadena2

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

252
252

printf
printf

gets cadena1
gets cadena2

cadena3
cadena2

malloc
strlen cadena1
sizeof char

strlen

strcpy cadena3 cadena1


strcat cadena3 cadena2
printf
cadena3
free cadena3
cadena3
return 0
Como las dos primeras cadenas se leen con gets, hemos de definirlas como cad
enas
estticas. La tercera cadena reserva exactamente la misma cantidad de memoria que
ocupa.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
229 Disea una funcin que lea una cadena y construya otra con una copia invertida
de la primera. La segunda cadena reservar slo la memoria que necesite.
230 Disea una funcin que lea una cadena y construya otra que contenga un ejemplar de cada carcter de la primera. Por ejemplo, si la primera cadena es
,
la segunda ser
. Ten en cuenta que la segunda cadena debe ocupar
la menor cantidad de memoria posible.
...............................................................................
...
Sobre la mutabilidad de las cadenas
Es posible inicializar un puntero a cadena de modo que apunte a un litera
l de cadena:
char

Pero, ojo!, la cadena apuntada por p es, en ese caso, inmutable: si intent
as asignar un
char a p i , el programa puede abortar su ejecucin. Por qu? Porque los liter
ales de
cadena residen en una zona de memoria especial (la denominada zona de texto)
que
est protegida contra escritura. Y hay una razn para ello: en esa zona resid
e, tambin,
el cdigo de mquina correspondiente al programa. Que un programa modifique s
u
propio cdigo de mquina es una psima prctica (que era relativamente frecuente
en
los tiempos en que predominaba la programacin en ensamblador), hasta el pu
nto de
que su zona de memoria se marca como de slo lectura.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
231 Implementa una funcin que reciba una cadena y devuelva una copia invertida.
(Ten en cuenta que la talla de la cadena puede conocerse con strlen, as que no es
necesario que suministres la talla explcitamente ni que devuelvas la talla de la
memoria
solicitada con un parmetro pasado por referencia.)
Escribe un programa que solicite varias palabras a un usuario y muestre e
l resultado
de invertir cada una de ellas.
...............................................................................
...

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
253
253
c
UJI
Introduccin a la prog
ramacin con C - UJI

Podemos extender la idea de los vectores dinmicos a matrices dinmicas. Pero el asu
nto
se complica notablemente: no podemos gestionar la matriz como una sucesin de elem
entos contiguos, sino como un vector dinmico de vectores dinmicos.

Analiza detenidamente este programa:


define stdio h
define stdlib h
int main void
float
m
int filas columnas
printf
printf

scanf
scanf

filas
columnas

reserva de memoria
m malloc filas sizeof float
for i 0 i filas i
m i
malloc columnas sizeof float
trabajo con m i

liberacin de memoria
for i 0 i filas i
free m i
free m
m
return 0
Analicemos poco a poco el programa.
Declaracin del tipo
Empecemos por la declaracin de la matriz (lnea 6). Es un puntero un poco extrao: se
declara como float m. Dos asteriscos, no uno. Eso es porque se trata de un pun
tero a
un puntero de enteros o, equivalentemente, un vector dinmico de vectores dinmicos
de
enteros.
Reserva de memoria
Sigamos. Las lneas 9 y 10 solicitan al usuario los valores de filas y columnas. E
n la
lnea 13 encontramos una peticin de memoria. Se solicita espacio para un nmero filas
de punteros a float. Supongamos que filas vale 4. Tras esa peticin, tenemos la si
guiente
asignacin de memoria para m:

25
4
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

254

0
m
1

El vector m es un vector dinmico cuyos elementos son punteros (del tipo float
). De
momento, esos punteros no apuntan a ninguna zona de memoria reservada. De ello s
e
encarga la lnea 15. Dicha lnea est en un bucle, as que se ejecuta para m 0 , m 1 ,
m 2 , . . . El efecto es proporcionar un bloque de memoria para cada celda de m.
He aqu
el efecto final:
0 1 2
3 4
0
m
0 1 2
3 4
1

4
2

4
3

Acceso a filas y elementos


Bien. Y cmo se usa m ahora? Como cualquier matriz! Pensemos en qu ocurre cuando
accedemos a m 1 2 . Analicemos m 1 2 de izquierda a derecha. Primero tenemos
a m, que es un puntero (tipo float ), o sea, un vector dinmico a elementos del ti
po
float . El elemento m 1 es el segundo componente de m. Y de qu tipo es? De tipo
float , un nuevo puntero o vector dinmico, pero a valores de tipo float. Si es un
vector
dinmico, lo podemos indexar, as que es vlido escribir m 1 2 . Y de qu tipo es eso?
De tipo float. Fjate:
m es de tipo float
m 1 es de tipo float ;
m 1

2 es de tipo float.

Con cada indexacin, desaparece un asterisco del tipo de datos.


Liberacin de memoria: un free para cada malloc
Sigamos con el programa. Nos resta la liberacin de memoria. Observa que hay una
llamada a free por cada llamada a malloc realizada con anterioridad (lneas 2024).
Hemos de liberar cada uno de los bloques reservados y hemos de empezar a hacerlo
por
los de segundo nivel, es decir, por los de la forma m i . Si empezsemos liberando m
,
cometeramos un grave error: si liberamos m antes que todos los m i , perderemos e
l
puntero que los referencia y, en consecuencia, no podremos liberarlos!
free m
?
m
liberacin de memoria incorrecta: qu es m i ahora que m vale
?
for i 0 i filas i
free m i

255
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
Introduccin a la programacin con C - UJI
c
UJI

255

Ms eficiencia, menos reservas de memoria


Te hemos enseado una forma estndar de pedir memoria para matrices dinmicas.
No es la nica. Es ms, no es la ms utilizada en la prctica. Por qu? Porque oblig
a
a realizar tantas llamadas a malloc (y despus a free) como filas tiene la
matriz ms
uno. Las llamadas a malloc pueden resultar ineficientes cuando su nmero es
grande.
Es posible reservar la memoria de una matriz dinmica con slo dos llamadas a
malloc.
include
int main void
int
m
int filas columnas
filas
columnas
Reserva de memoria.
m malloc filas sizeof int
m 0
malloc filas columnas sizeof int
for i 1 i filas i
m i m i 1
columnas
Liberacin de memoria.
free m 0
free m
return 0
La clave est en la sentencia m i

m i 1

columnas: el contenido de m i

pasa a
ser la direccin de memoria columnas celdas ms a la derecha de la direccin m
i 1 .
9

He aqu una representacin grfica de una matriz de 5 4:


0 1
2 3
4 5
10 11 12 13 14 15 16 17 18 19
m

0
1
2
3
4

Matrices dinmicas y funciones


El paso de matrices dinmicas a funciones tiene varias formas idiomticas que convie
ne
que conozcas. Imagina una funcin que recibe una matriz de enteros para mostrar su

contenido por pantalla. En principio, la cabecera de la funcin presentara este asp


ecto:
void muestra matriz int
m
El parmetro indica que es de tipo puntero a punteros a enteros. Una forma alternati
va
de decir lo mismo es sta:
void muestra matriz int
m
Se lee ms bien como vector de punteros a entero. Pero ambas expresiones son sinnimas de vector de vectores a entero. Uno se siente tentado de utilizar esta otra ca
becera:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
256
256
con C -UJI

Introduccin a la programacin

cUJI

void muestra matriz int m


Pero no funciona.
y
que hemos omitido
Sigamos con la
void muestra

!Mal!

Es incorrecta. C entiende que queremos pasar una matriz esttica


el nmero de columnas.
funcin:
matriz int

int i j
for i 0 i
for j 0 j
printf
printf

i
j
m i

Observa que necesitamos suministrar el nmero de filas y columnas explcitamente par


a
saber qu rango de valores deben tomar i y j:
void muestra matriz int
m int filas int columnas
int i j
for i 0 i filas i
for j 0 j columnas j
printf
m i j
printf

Supongamos ahora que nos piden una funcin que efecte la liberacin de la memoria
de una matriz:
void libera matriz int
m int filas int columnas
int i
for i 0 i filas i
free m i
free m
Ahora resulta innecesario el paso del nmero de columnas, pues no se usa en la fun
cin:
void libera matriz int

m int filas

int i
for i 0 i filas i
free m i
free m
Falta un detalle que hara mejor a esta funcin: la asignacin del valor
a m al fin
al
de todo. Para ello tenemos que pasar una referencia a la matriz, y no la propia
matriz:
void libera matriz int
m int filas

int i
257
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
oduccin a la programacin con C - UJI
c
UJI

257

Intr

for i 0 i filas i
free m i
free m
m
Qu horror! Tres asteriscos en la declaracin del parmetro m! C no es, precisamente,
el colmo de la elegancia.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
232 Disea una funcin que reciba un nmero de filas y un nmero de columnas y
devuelva una matriz dinmica de enteros con filascolumnas elementos.
233 Disea un procedimiento que reciba un puntero a una matriz dinmica (sin
memoria asignada), un nmero de filas y un nmero de columnas y devuelva, mediante e
l
primer parmetro, una matriz dinmica de enteros con filascolumnas elementos.
...............................................................................
...
La gestin de matrices dinmicas considerando por separado sus tres variables (p
untero a memoria, nmero de filas y nmero de columnas) resulta poco elegante y da lug
ar
a funciones con parmetros de difcil lectura. En el siguiente apartado aprenders a u
sar
matrices dinmicas que agrupan sus tres datos en un tipo registro definido por el
usuario.

Presentaremos ahora un ejemplo de aplicacin de lo aprendido: un programa que mult


iplica dos matrices de tallas arbitrarias. Empezaremos por definir un nuevo tipo
de datos
para nuestras matrices. El nuevo tipo ser un struct que contendr una matriz dinmica
de float y el nmero de filas y columnas.
struct Matriz
float
m
int filas columnas
Diseemos ahora una funcin que cree una matriz dado el nmero de filas y el
nmero de columnas:
struct Matriz crea matriz int filas int columnas
struct Matriz mat
int i
if filas
0 columnas 0
mat filas mat columnas 0
mat m
return mat
mat
mat
mat
for

filas filas
columnas columnas
m malloc filas sizeof float
i 0 i filas i

mat m i
return mat

malloc columnas sizeof float

Hemos tenido la precaucin de no pedir memoria si el nmero de filas o columnas n


o
son vlidos. Para crear una matriz de, por ejemplo, 3 4, llamaremos a la funcin as:
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
258
258
ramacin con C - UJI

Introduccin a la prog

c
UJI

struct Matriz matriz


matriz

crea matriz 3 4

Hay una implementacin alternativa de crea matriz:


void crea matriz int filas int columnas struct Matriz
mat
int i
if filas 0
mat filas
mat m

columnas 0
mat columnas

else
mat
mat
mat
for

filas filas
columnas columnas
m malloc filas sizeof float
i 0 i filas i
mat m i
malloc columnas sizeof float

En este caso, la funcin (procedimiento) se llamara as:


struct Matriz matriz
crea matriz 3 4

matriz

Tambin nos vendr bien disponer de un procedimiento para liberar la memoria de


una matriz:
void libera matriz struct Matriz

mat

int i
if mat m
for i 0 i mat filas i
free mat m i
free mat m
mat
mat
mat

m
filas 0
columnas

Para liberar la memoria de una matriz dinmica m, efectuaremos una llamada com
o
sta:
libera matriz
m
Como hemos de leer dos matrices por teclado, diseemos ahora una funcin capaz d
e
leer una matriz por teclado:
struct Matriz lee matriz void
int i j filas columnas
struct Matriz mat

printf

scanf

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

filas

259
259

printf

scanf

column

as
mat

crea matriz filas columnas

for i 0 i filas i
for j 0 j columnas j
printf
scanf
mat m i

i j
j

return mat
Observa que hemos llamado a crea matriz tan pronto hemos sabido cul era el nme
ro
de filas y columnas de la matriz.
Y ahora, implementemos un procedimiento que muestre por pantalla una matriz:
void muestra matriz struct Matriz mat
int i j
for i 0 i mat filas i
for j 0 j mat columnas j
printf
mat m i j
printf

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
234 En muestra matriz hemos pasado la matriz mat por valor. Cuntos bytes se
copiarn en pila con cada llamada?
235 Disea una nueva versin de muestra matriz en la que mat se pase por referencia.
Cuntos bytes se copiarn en pila con cada llamada?
...............................................................................
...
Podemos proceder ya mismo a implementar una funcin que multiplique dos matr
ices:
struct Matriz multiplica matrices struct Matriz a struct Matriz b
int i j k
struct Matriz c
if a columnas

b filas

plicar
c filas c columnas 0
cm
return c
c crea matriz a filas b columnas
for i 0 i c filas i
for j 0 j c columnas j
cm i j
0.0
for k 0 k a columnas k
cm i j
am i k
bm k
j
return c

No se pueden multi

No todo par de matrices puede multiplicarse entre s. El nmero de columnas de l


a
primera ha de ser igual al nmero de filas de la segunda. Por eso devolvemos una m
atriz
vaca (de 0 0) cuando a columnas es distinto de b filas.
Ya podemos construir el programa principal:
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
260
260
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
definicin de funciones
int main void
struct Matriz a b c
a lee matriz
b lee matriz
c multiplica matrices a b
if c m
printf
else
printf
muestra matriz c
libera matriz
libera matriz
libera matriz

a
b
c

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
236 Disea una funcin que sume dos matrices.
237 Pasar estructuras por valor puede ser ineficiente, pues se debe obtener una
copia en pila de la estructura completa (en el caso de las matrices, cada variab
le de
tipo struct Matriz ocupa 12 bytes un puntero y dos enteros, cuando una referencia
supone la copia de slo 4 bytes). Modifica la funcin que multiplica dos matrices pa
ra
que sus dos parmetros se pasen por referencia.
238 Disea una funcin que encuentre, si lo hay, un punto de silla en una matriz.
Un punto de silla es un elemento de la matriz que es o bien el mximo de su fila y
el
mnimo de su columna a la vez, o bien el mnimo de su fila y el mximo de su columna
a la vez. La funcin devolver cierto o falso dependiendo de si hay algn punto de sil
la.
Si lo hay, el valor del primer punto de silla encontrado se devolver como valor d
e un
parmetro pasado por referencia.
...............................................................................
...

Hemos aprendido a definir matrices dinmicas con un vector dinmico de vectores dinmicos. El primero contiene punteros que apuntan a cada columna. Una caracterstica
de
las matrices es que todas las filas tienen el mismo nmero de elementos (el nmero d
e
columnas). Hay estructuras similares a las matrices pero que no imponen esa rest
riccin.
Pensemos, por ejemplo, en una lista de palabras. Una forma de almacenarla en mem
oria
es la que se muestra en este grfico:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
261
261
ramacin con C - UJI

Introduccin a la prog

c
UJI

0
listapal

a n u a l
1
d a d i v o s

o
2
m a n o
3
t a c o
Ves? Es parecido a una matriz, pero no exactamente una matriz: cada palabra o
cupa
tanta memoria como necesita, pero no ms. Este programa solicita al usuario 4 pala
bras
y las almacena en una estructura como la dibujada:
include
include
include
define
define

4
80

int main void


char
listapal
char linea
int i

Pedir memoria y leer datos


listapal malloc
sizeof char
for i 0 i
i
printf
gets linea
listapal i
malloc strlen linea
strcpy listapal i linea

sizeof char

Mostrar el contenido de la lista


for i 0 i
i
printf
i listapal i
Liberar memoria
for i 0 i
i
free listapal i
free listapal
return 0
Este otro programa slo usa memoria dinmica para las palabras, pero no para el
vector de palabras:
include
include
include

define
define

4
80

int main void


262
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

262

char listapal
char linea
int i

Pedir memoria y leer datos


for i 0 i
i
printf
gets linea
listapal i
malloc strlen linea

sizeof

char
strcpy listapal i linea
Mostrar el contenido de la lista
for i 0 i
i
printf
i listapal i
Liberar memoria
for i 0 i
i
free listapal i
return 0
Fjate en cmo hemos definido listapal: como un vector esttico de 4 punteros a caract
eres
(char listapal
).
Vamos a ilustrar el uso de este tipo de estructuras de datos con la escritur
a de
una funcin que reciba una cadena y devuelva un vector de palabras, es decir, vamo
s
a implementar la funcionalidad que ofrece Python con el mtodo split. Empecemos po
r
considerar la cabecera de la funcin, a la que llamaremos extrae palabras. Est clar
o
que uno de los parmetros de entrada es una cadena, o sea, un vector de caracteres
:
extrae palabras char frase
No hace falta suministrar la longitud de la cadena, pues sta se puede calcular co
n la
funcin strlen. Cmo representamos la informacin de salida? Una posibilidad es devolve
r
un vector de cadenas:
char
extrae palabras char frase
O sea, devolvemos un puntero ( ) a una serie de datos de tipo char , o sea, cade
nas.
Pero an falta algo: hemos de indicar explcitamente cuntas palabras hemos encontrado
:
char
extrae palabras char frase
int
numpals
Hemos recurrido a un parmetro adicional para devolver el segundo valor. Dicho parm
etro
es la direccin de un entero, pues vamos a modificar su valor. Ya podemos codifica
r el
cuerpo de la funcin. Empezaremos por contar las palabras, que sern series de carac
teres
separadas por blancos (no entraremos en mayores complicaciones acerca de qu es un
a
palabra).
char
extrae palabras char frase
int
numpals

int i lonfrase
lonfrase strlen frase
numpals 1
for i 0 i lonfrase 1 i
if frase i
frase i 1

num

pals
if frase 0

numpals

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
3

Introduccin a la programacin con C -UJI


cUJI

263
26

Acceso a argumentos de la lnea de comandos


Los programas que diseamos en el curso suponen que main no tiene parmetros.
No
siempre es as.
La funcin main puede recibir como argumentos las opciones que se indica
n en la
lnea de comandos cuando ejecutas el programa desde la lnea de rdenes Unix. El
siguiente programa muestra por pantalla un saludo personalizado y debe lla
marse as:

Aqu tienes el cdigo fuente:


include
include
main int argc char

argv

if argc
3
printf
else
if strcmp argv 1
printf
else
printf

0
argv 2

El argumento argc indica cuntas palabras se han usado en la lnea de rdenes.


El
argumento argv es un vector de char , es decir, un vector de cadenas (una
cadena es un
vector de caracteres). El elemento argv 0 contiene el nombre del programa
(en nuestro
caso,
) que es la primera palabra, argv 1 el de la segunda (que es
peramos
que sea
) y argv 2 la tercera (el nombre de la persona a la que salu
damos).
La estructura argv, tras la invocacin
, es:
argv

0
s a l u d a
1
- n
2
n o m b r e

Ya podemos reservar memoria para el vector de cadenas, pero an no para cada una d
e
ellas:
char
extrae palabras char frase
int numpals
int i lonfrase
char palabras
lonfrase strlen frase

numpals 1
for i 0 i lonfrase 1 i
if frase i
frase i 1

numpal

s
if frase 0
palabras

numpals
malloc

numpals

sizeof char

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

264
264

Ahora pasamos a reservar memoria para cada una de las palabras y, tan pronto hag
amos
cada reserva, escribirla en su porcin de memoria:
char
extrae palabras char frase
int numpals
int i j inicio pal longitud pal palabra actual lonfrase
char palabras
lonfrase strlen frase
numpals 1
for i 0 i lonfrase 1 i
if frase i
frase i 1
numpals
if frase 0
numpals
palabras

malloc

palabra actual
i 0
if frase 0
while frase
mos blancos iniciales.

numpals

sizeof char

0
i

while i lonfrase
inicio pal i
while frase
i
remos la palabra.

lonfrase

Salta

lonfrase

Recor

longitud pal i inicio pal


Calculamos nmero de caracteres en la p
alabra actual.
Reservamos memoria.
palabras palabra actual malloc longitud pal 1 sizeof char
Y copiamos la palabra de frase al vector de palabras.
for j inicio pal j i j
palabras palabra actual j inicio pal
frase j
palabras palabra actual j inicio pal
while frase
mos blancos entre palabras.

palabra actual

lonfrase

Salta

Y pasamos a la siguiente.

return palabras
Buf! Complicado, verdad? Veamos cmo se puede usar la funcin desde el programa
principal:
E
E
include
include

int main void


char linea

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia

265
265
cUJI

Introduccin a la programacin con C -UJI

int numero palabras i


char
las palabras
printf
gets linea
las palabras extrae palabras linea
for i 0 i numero palabras i
printf
las palabras i

numero palabras

return 0
Ya? An no. Aunque este programa compila correctamente y se ejecuta sin problemas,
hemos de considerarlo incorrecto: hemos solicitado memoria con malloc y no la he
mos
liberado con free.
include
include

int main void


char linea
1
int numero palabras i
char
las palabras
printf
gets linea
las palabras extrae palabras linea
for i 0 i numero palabras i
printf
las palabras i

numero palabras

for i 0 i numero palabras i


free las palabras i
free las palabras
las palabras
return 0
Ahora s.
n
Hemos considerado la creacin de estructuras bidimensionales (matrices o vectores
de
vectores), pero nada impide definir estructuras con ms dimensiones. Este sencillo
programa pretende ilustrar la idea creando una estructura dinmica con 3 3 3 elemento
s,
inicializarla, mostrar su contenido y liberar la memoria ocupada:
include
include
int main void

int
e enteros.

Tres asteriscos: vector de vectores de vectores d

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

266
266

int i j k
Reserva de memoria
a malloc 3 sizeof int
for i 0 i 3 i
a i
malloc 3 sizeof int
for j 0 j 3 j
a i j
malloc 3 sizeof int
Inicializacin
for i 0 i 3 i
for j 0 j 3 j
for k 0 k 3 k
a i j k i j k
Impresin
for i 0 i 3 i
for j 0 j 3 j
for k 0 k 3 k
printf

i j k a i

k
Liberacin de memoria.
for i 0 i 3 i
for j 0 j 3 j
free a i j
free a i
free a
a
return 0
En la siguiente figura se muestra el estado de la memoria tras la inicializac
in de la
matriz tridimensional:

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

267
267

0 1 2
0

1 2 3
0

2 3 4
0

2
0

a
0
1 2 3
0

2
1

2 3 4
2
0

3 4 5
0

2 3 4
0

3 4 5
0

4 5 6

Muchos programas no pueden determinar el tamao de sus vectores antes de empezar a


trabajar con ellos. Por ejemplo, cuando se inicia la ejecucin de un programa que
gestiona
una agenda telefnica no sabemos cuntas entradas contendr finalmente. Podemos fijar
un nmero mximo de entradas y pedir memoria para ellas con malloc, pero entonces
estaremos reproduciendo el problema que nos llev a presentar los vectores dinmicos
.
Afortunadamente, C permite que el tamao de un vector cuya memoria se ha solicitad
o
previamente con malloc crezca en funcin de las necesidades. Se usa para ello la f
uncin
realloc, cuyo prototipo es (similar a) ste:

void

realloc void

puntero int bytes

Aqu tienes un ejemplo de uso:


include
int main void
int

a
malloc 10
o para 10 enteros.
a
realloc a 20
para que quepan 20.
a
realloc a 5
duce a slo 5 (los 5 primeros).

sizeof int
sizeof int
sizeof int

Se pide espaci
Ahora se ampla
Y ahora se re

free a
return 0

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
68

2
26

Introduccin a la programacin con C -UJI


cUJI

La funcin realloc recibe como primer argumento el puntero que indica la zona de m
emoria que deseamos redimensionar y como segundo argumento, el nmero de bytes que
deseamos asignar ahora. La funcin devuelve el puntero a la nueva zona de memoria.
Qu hace exactamente realloc? Depende de si se pide ms o menos memoria de la
que ya se tiene reservada:
Si se pide ms memoria, intenta primero ampliar la zona de memoria asigna
da. Si
las posiciones de memoria que siguen a las que ocupa a en ese instante
estn
libres, se las asigna a a, sin ms. En caso contrario, solicita una nueva
zona de
memoria, copia el contenido de la vieja zona de memoria en la nueva, li
bera la vieja
zona de memoria y nos devuelve el puntero a la nueva.
Si se pide menos memoria, libera la que sobra en el bloque reservado. U
n caso
extremo consiste en llamar a realloc con una peticin de 0 bytes. En tal
caso, la
llamada a realloc es equivalente a free.
Al igual que malloc, si realloc no puede atender una peticin, devuelve un puntero
a
. Una cosa ms: si se llama a realloc con el valor
como primer parmet
ro,
realloc se comporta como si se llamara a malloc.
Como puedes imaginar, un uso constante de realloc puede ser una fuente de in
eficiencia. Si tienes un vector que ocupa un 1 megabyte y usas realloc para que ocu
pe 1.1
megabyes, es probable que provoques una copia de 1 megabyte de datos de la zona
vieja
a la nueva. Es ms, puede que ni siquiera tengas memoria suficiente para efectuar
la
nueva reserva, pues durante un instante (mientras se efecta la copia) estars usand
o 2.1
megabytes.
Desarrollemos un ejemplo para ilustrar el concepto de reasignacin o redimensi
onamiento de memoria. Vamos a disear un mdulo que permita crear diccionarios de
palabras. Un diccionario es un vector de cadenas. Cuando creamos el diccionario,
no
sabemos cuntas palabras albergar ni qu longitud tiene cada una de las palabras.
Tendremos que usar, pues, memoria dinmica. Las palabras, una vez se introducen en
el
diccionario, no cambian de tamao, as que bastar con usar malloc para gestionar sus
reservas de memoria. Sin embargo, la talla de la lista de palabras s vara al aadir
palabras, as que deberemos gestionarla con malloc/realloc.
Empecemos por definir el tipo de datos para un diccionario.
struct Diccionario
char
palabra
int palabras
Aqu tienes un ejemplo de diccionario que contiene 4 palabras:
palabra
0
a n u a l

palabras
4

1
d a d i v o s o
2
m a n o
3
t a c o

Un diccionario vaco no tiene palabra alguna ni memoria reservada. Esta funcin c


rea
un diccionario:
struct Diccionario crea diccionario void
struct Diccionario d
d palabra
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

269
269

d palabras
return d

Ya podemos desarrollar la funcin que inserta una palabra en el diccionario. Lo


primero
que har la funcin es comprobar si la palabra ya est en el diccionario. En tal caso,
no
har nada:
void inserta palabra en diccionario struct Diccionario
d char pal
int i
for i 0 i d palabras i
if strcmp d palabra i
return

pal

Si la palabra no est, hemos de aadirla:


void inserta palabra en diccionario struct Diccionario
d char pal
int i
for i 0 i d palabras i
if strcmp d palabra i
return

pal

d palabra realloc d palabra d palabras 1


sizeof char
d palabra d palabras
malloc strlen pal 1
sizeof char
strcpy d palabra d palabras pal
d palabras
Podemos liberar la memoria ocupada por un diccionario cuando no lo necesitamo
s
ms llamando a esta otra funcin:
void libera diccionario struct Diccionario

int i
if d palabra
for i 0 i d palabras i
free d palabra i
free d palabra
d palabra
d palabras 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
239 Disea una funcin que devuelva cierto (valor 1) o falso (valor 0) en funcin de

si una palabra pertenece o no a un diccionario.


240 Disea una funcin que borre una palabra del diccionario.
241 Disea una funcin que muestre por pantalla todas la palabras del diccionario
que empiezan por un prefijo dado (una cadena).
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
270
270
ramacin con C - UJI

Introduccin a la prog

c
UJI

No es lo mismo un puntero que un vector


Aunque C permite considerarlos una misma cosa en muchos contextos, hay al
gunas
diferencias entre un puntero a una serie de enteros, por ejemplo, y un ve
ctor de enteros.
Consideremos un programa con estas declaraciones:
int
int
int
int

vector 10
escalar
puntero
otro puntero

A los punteros debe asignrseles explcitamente algn valor:


a la nada: puntero
;
a memoria reservada mediante malloc: puntero
malloc 5 sizeof int
;
a la direccin de memoria de una variable escalar del tipo a
l que puede
apuntar el puntero: puntero escalar;
a la direccin de memoria en la que empieza un vector: punte
ro
vector;
a la direccin de memoria de un elemento de un vector: punte
ro vector 2 ;
a la direccin de memoria apuntada por otro puntero: puntero otro puntero;
a una direccin calculada mediante aritmtica de punteros: por
ejemplo,
puntero vector 2 es lo mismo que puntero
vector 2 .
Los vectores reservan memoria automticamente, pero no puedes red
imensionarlos.
Es ilegal, por ejemplo, una sentencia como sta: vector puntero.
Eso s, las funciones que admiten el paso de un vector va parmetros, admiten
tambin
un puntero y viceversa. De ah que se consideren equivalentes.
Aunque suponga una simplificacin, puedes considerar que un vector es un
puntero
inmutable (de valor constante).

242 Disea una funcin que muestre por pantalla todas la palabras del diccionario
que acaban con un sufijo dado (una cadena).
...............................................................................
...
La funcin que determina si una palabra pertenece o no a un diccionario requie
re
tanto ms tiempo cuanto mayor es el nmero de palabras del diccionario. Es as porque
el diccionario est desordenado y, por tanto, la nica forma de estar seguros de que
una palabra no est en el diccionario es recorrer todas y cada una de las palabras
(si,
por contra, la palabra est en el diccionario, no siempre es necesario recorrer el
listado
completo).
Podemos mejorar el comportamiento de la rutina de bsqueda si mantenemos el
diccionario siempre ordenado. Para ello hemos de modificar la funcin de insercin d
e
palabras en el diccionario:

void inserta palabra en diccionario struct Diccionario d char pal


int i j
for i 0 i d palabras i
if strcmp d palabra i
est, no hay nada que hacer.
return
if strcmp d palabra i
ramos su posicin (orden alfabtico)
break

pal
pal

Si ya

Encont

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
271
2
71

Introduccin a la programacin con C - UJI


c
UJI

Si llegamos aqu, la palabra no est y hay que insertarla en la posicin


i, desplazando
antes el resto de palabras.
d

Reservamos espacio en la lista de palabras para una ms.


palabra realloc d palabra d palabras 1
sizeof char
Desplazamos el resto de palabras para que haya un hueco en el vector

.
for j d palabras j i j
d palabra j
malloc strlen d palabra j 1
strcpy d palabra j d palabra j 1
free d palabra j 1

1 sizeof char

Y copiamos en su celda la nueva palabra


d palabra i
malloc strlen pal 1
sizeof char
strcpy d palabra i pal
d palabras
Buf! Las lneas 2022 no hacen ms que asignar a una palabra el contenido de otra
(la que ocupa la posicin j recibe una copia del contenido de la que ocupa la posi
cin
j 1). No hay una forma mejor de hacer eso mismo? S. Transcribimos nuevamente las
ltimas lneas del programa, pero con una sola sentencia que sustituye a las lneas 202
2:

for j d palabras j i i
d palabra j
d palabra j 1
Y copiamos en su celda la nueva palabra
d palabra i
malloc strlen pal 1
sizeof char
strcpy d palabra i pal
d palabras
No est mal, pero no hemos pedido ni liberado memoria dinmica! Ni siquiera hemos
usado strcpy, y eso que dijimos que haba que usar esa funcin para asignar una cade
na
a otra. Cmo es posible? Antes hemos de comentar qu significa una asignacin como
sta:
d
palabra j
d
palabra j 1
Significa que d palabra j apunta al mismo lugar al que apunta d palabra j 1 .
Por qu? Porque un puntero no es ms que una direccin de memoria y asignar a un
puntero el valor de otro hace que ambos contengan la misma direccin de memoria, e
s
decir, que ambos apunten al mismo lugar.
Veamos qu pasa estudiando un ejemplo. Imagina un diccionario en el que ya hem
os
insertado las palabras anual, dadivoso, mano y taco y que vamos a insertar ahora
la palabra feliz. Partimos, pues, de esta situacin:
palabra
0
a n u a l
palabras
4
1
d a d i v o s o
2
m a n o

3
t a c o
Una vez hemos redimensionado el vector de palabras, tenemos esto:
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

272
272

palabra
0
a n u a l
palabras
4

1
d a d i v o s o
2
m a n o
3
t a c o
4

La nueva palabra debe insertarse en la posicin de ndice 2. El bucle ejecuta la asi


gnacin
para j tomando los valores 4 y 3. Cu
ando se
ejecuta la iteracin con j igual a 4, tenemos:
palabra
0
a n u a l
palabras
4
1
d a d i v o s o
2
m a n o
3
t a c o
4

La ejecucin de la asignacin ha hecho que d palabra 4 apunte al mismo lugar que


d palabra 3 . No hay problema alguno en que dos punteros apunten a un mismo bloq
ue
de memoria. En la siguiente iteracin pasamos a esta otra situacin:
palabra
0
a n u a l
palabras
4
1
d a d i v o s o
2
m a n o
3
t a c o
4

Podemos reordenar grficamente los elementos, para ver que, efectivamente, estamos
haciendo hueco para la nueva palabra:
palabra
0
a n u a l

palabras
4

1
d a d i v o s o
2

3
m a n o
4
t a c o
El bucle ha acabado. Ahora se pide memoria para el puntero d palabra i (siendo i
igual a 2). Se piden 6 bytes (feliz tiene 5 caracteres ms el terminador nulo):

273
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
roduccin a la programacin con C - UJI
c
UJI

273

Int

palabra
0
a n u a l
palabras
4

1
d a d i v o s o
2

3
m a n o
4
t a c o
Y, finalmente, se copia en d palabra i el contenido de pal con la
se
incrementa el valor de d palabras:
palabra
0
a n u
palabras
5
1
d a d
2
f e l
3
m a n
4
t a c

funcin strcpy y

a l
i v o s o
i z
o
o

274
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C
uccin a la programacin con C - UJI
c
UJI

274

Introd

Podemos ahora implementar una funcin de


Una primera idea consiste en buscar desde el
re
la palabra buscada o cuando se encuentre una
a
buscada. En este ltimo caso sabremos que la
ma
ms eficiente de saber si una palabra est o
bsqueda dicotmica.

bsqueda de palabras ms eficiente.


principio y parar cuando se encuent
palabra mayor (alfabticamente) que l
palabra no existe. Pero an hay una for
no en una lista ordenada: mediante una

int buscar en diccionario struct Diccionario d char pal


int izquierda centro derecha
izquierda 0
derecha d palabras
while izquierda derecha
centro
izquierda derecha 2
if strcmp pal d palabra centro
0
return 1
else if strcmp pal d palabra centro
derecha centro
else
izquierda centro 1

return 0
Podemos hacer una pequea mejora para evitar el sobrecoste de llamar dos veces
a
la funcin strcmp:
int buscar en diccionario struct Diccionario d char pal
int izquierda centro derecha comparacion
izquierda 0
derecha d palabras
while izquierda derecha
centro
izquierda derecha 2
comparacion strcmp pal d palabra centro
if comparacion
0
return 1
else if comparacion 0
derecha centro
else
izquierda centro 1
return 0
Juntemos todas las piezas y aadamos una funcin main que nos pida primero las
palabras del diccionario y, a continuacin, nos pida palabras que buscar en l:
include
include
include
define

80

struct Diccionario
char
palabra
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
troduccin a la programacin con C -UJI
cUJI

275
275

In

int palabras
struct Diccionario crea diccionario void
struct Diccionario d
d palabra
d palabras 0
return d
void libera diccionario struct Diccionario

int i
if d palabra
for i 0 i d palabras i
free d palabra i
free d palabra
d palabra
d palabras 0

void inserta palabra en diccionario struct Diccionario d char pal


int i j
for i 0 i d palabras i
if strcmp d palabra i
est, no hay nada que hacer.
return
if strcmp d palabra i
mos encontrado su posicin (orden alfabtico)
break

pal
pal

0
0

Si ya
Aqu he

Si llegamos aqu, la palabra no est y hay que insertarla en la posicin


i, desplazando
antes el resto de palabras.
d

Reservamos espacio en la lista de palabras para una ms.


palabra realloc d palabra d palabras 1
sizeof char
Desplazamos el resto de palabras para que haya un hueco en el vecto

r.
for j d palabras j i j
d palabra j
malloc strlen d palabra j 1
strcpy d palabra j d palabra j 1
free d palabra j 1
Y copiamos en su celda la nueva palabra
d palabra i
malloc strlen pal 1
sizeof char
strcpy d palabra i pal
d palabras

1 sizeof char

int buscar en diccionario struct Diccionario d char pal


int izquierda centro derecha comparacion

izquierda 0
derecha d palabras
while izquierda derecha
Introduccin
Andrs aMarzal/Isabel
la programacin con -C ISBN: 978-84-693-0143-2
Gracia
276
276

Introduccin a la programacin con C -UJI


cUJI

centro
izquierda derecha 2
comparacion strcmp pal d palabra centro
if comparacion
0
return 1
else if comparacion 0
derecha centro
else
izquierda centro 1
return 0
int main void
struct Diccionario mi diccionario
int num palabras
char linea
1
mi diccionario

crea diccionario

printf
gets linea sscanf linea
num palabras
while mi diccionario palabras
num palabras
printf
mi diccionario palabras 1
gets linea
inserta palabra en diccionario mi diccionario linea
do
printf
gets linea
if strlen linea 0
if buscar en diccionario mi diccionario linea
printf
else
printf
while strlen linea
libera diccionario

0
mi diccionario

return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
243 Cuntas comparaciones se hacen en el peor de los casos en la bsqueda dicotmica de una palabra cualquiera en un diccionario con 8 palabras? Y en un dicciona
rio
con 16 palabras? Y en uno con 32? Y en uno con 1024? Y en uno con 1048576? (Nota:
el valor 1048576 es igual a 220 .)
244 Al insertar una nueva palabra en un diccionario hemos de comprobar si exista
previamente y, si es una palabra nueva, averiguar en qu posicin hay que insertarla
. En
la ltima versin presentada, esa bsqueda se efecta recorriendo el diccionario palabra
a palabra. Modifcala para que esa bsqueda sea dicotmica.
245 Disea una funcin que funda dos diccionarios ordenados en uno slo (tambin
ordenado) que se devolver como resultado. La fusin se har de modo que las palabras
que estn repetidas en ambos diccionarios aparezcan una sla vez en el diccionario f
inal.

...............................................................................
...
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
277
277
ramacin con C - UJI

Introduccin a la prog

c
UJI

Vamos a desarrollar un ejemplo completo: una agenda telefnica. Utilizaremos vecto


res
dinmicos en diferentes estructuras de datos del programa. Por ejemplo, el nombre
de las
personas registradas en la agenda y sus nmeros de telfonos consumirn exactamente
la memoria que necesitan, sin desperdiciar un slo byte. Tambin el nmero de entradas
en la agenda se adaptar a las necesidades reales de cada ejecucin. El nombre de
una persona no cambia durante la ejecucin del programa, as que no necesitar redimensionamiento, pero la cantidad de nmeros de telfono de una misma persona s (se
pueden aadir nmeros de telfono a la entrada de una persona que ya ha sido dada de
alta). Gestionaremos esa memoria con redimensionamiento, del mismo modo que usar
emos redimensionamiento para gestionar el vector de entradas de la agenda: gastar
emos
exactamente la memoria que necesitemos para almacenar la informacin.
Aqu tienes un texto con el tipo de informacin que deseamos almacenar:

Para que te hagas una idea del montaje, te mostramos la representacin grfica d
e
las estructuras de datos con las que representamos la agenda del ejemplo:
0
1
2
3 4 5
6 7
8
9 10
P e p e

e r e z \0
0
4

10
A n a

G a r c a \0
2

8
J u a

G i l \0
0

M a r a
persona

P a z \0
nombre
nombre

personas
4

telefono

telefono

telefonos telefonos
2
3
2
3 4
5 6
7

nombre

nombre

telefono

telefono

telefonos
telefonos
0
1
8
9 10
0

10
0

10
1

10
2

9
0

9
1

Empezaremos proporcionando soporte para el tipo de datos entrada: un nombre


y un listado de telfonos (un vector dinmico).
include
include
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
278
278
on C - UJI
UJI
c

Introduccin a la programacin c

Entradas
struct Entrada
char nombre
char
telefono
int telefonos

Nombre de la persona.
Vector dinmico de nmeros de telfono.
Nmero de elementos en el anterior vecto

r.
void crea entrada struct Entrada e char nombre
Inicializa una entrada con un nombre. La lista de telfonos se pone a
.
Reservamos memoria para el nombre y efectuamos la asignacin.
e nombre malloc strlen nombre 1 sizeof char
strcpy e nombre nombre
e
e

telefono
telefonos

void anyadir telefono a entrada struct Entrada


e char
telefono
e telefono realloc e telefono e telefonos 1 sizeof char
e telefono e telefonos
malloc strlen telefono 1 sizeof char
strcpy e telefono e telefonos telefono
e telefonos
void muestra entrada struct Entrada e
Podramos haber pasado e por valor, pero resulta ms eficiente (y no much
o ms
incmodo) hacerlo por referencia: pasamos as slo 4 bytes en lugar de 12.
int i
printf
for i 0 i e
printf
telefono i

nombre

telefonos i
i 1 e

void libera entrada struct Entrada

int i
free e nombre
for i 0 i e telefonos i
free e telefono i
free e telefono
e
e
e

nombre
telefono
telefonos

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

246 Modifica anyadir telefono a entrada para que compruebe si el telfono ya


haba sido dado de alta. En tal caso, la funcin dejar intacta la lista de telfonos de
esa
entrada.
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
279
279
c
UJI
Introduccin a la prog
ramacin con C - UJI

...............................................................................
...
Ya tenemos resuelta la gestin de entradas. Ocupmonos ahora del tipo agenda y d
e
su gestin.
Agenda
struct Agenda
struct Entrada
int personas

persona

Vector de entradas
Nmero de entradas en el vector

struct Agenda crea agenda void


struct Agenda a
a persona
a personas
return a

void anyadir persona struct Agenda

a char

nombre

int i
Averiguar si ya tenemos una persona con ese nombre
for i 0 i a personas i
if strcmp a persona i nombre nombre
0
return
Si llegamos aqu, es porque no tenamos registrada a esa persona.
a persona realloc a persona a personas 1
sizeof struct Ent
rada
crea entrada a persona a personas nombre
a personas
void muestra agenda struct Agenda
Pasamos a as por eficiencia.

int i
for i 0 i a personas i
muestra entrada a persona i
struct Entrada
a char nombre

buscar entrada por nombre struct Agenda

int i
for i 0 i a personas i
if strcmp a persona i nombre nombre
return a persona i
Si llegamos aqu, no lo hemos encontrado. Devolvemos
para indicar que no

conocemos a esa persona.


return

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

280
280

Introduccin a la programacin con C -UJI


cUJI

void libera agenda struct Agenda

int i
for i 0 i a personas i
libera entrada a persona i
free a persona
a persona
a personas 0
Fjate en el prototipo de buscar entrada por nombre: devuelve un puntero a un
dato
de tipo struct Entrada. Es un truco bastante utilizado. Si no existe una persona
con el
nombre indicado, se devuelve un puntero a
, y si existe, un puntero a es
a persona.
Ello nos permite, por ejemplo, mostrarla a continuacin llamando a la funcin que mu
estra
entradas, pues espera un puntero a un struct Entrada. Ahora vers cmo lo hacemos en
el programa principal.
Un lenguaje para cada tarea
Acabas de ver que el tratamiento de cadenas en C es bastante primitivo y
nos obliga
a estar pendientes de numerosos detalles. La memoria que ocupa cada caden
a ha de
ser explcitamente controlada por el programador y las operaciones que pode
mos hacer
con ellas son, en principio, primitivas. Python, por contra, libera al pr
ogramador de
innumerables preocupaciones cuando trabaja con objetos como las cadenas o
los vectores
dinmicos (sus listas). Adems, ofrece de fbrica numerosas utilidades para manip
ular
cadenas (cortes, funciones del mdulo string, etc.) y listas (mtodo append,
sentencia del,
cortes, ndices negativos, etc.). Por qu no usamos siempre Python? Por eficie
ncia. C
permite disear, por regla general, programas mucho ms eficientes que sus eq
uivalentes
en Python. La mayor flexibilidad de Python tiene un precio.
Antes de programar una aplicacin, hemos de preguntarnos si la eficienc
ia es un
factor crtico. Un programa con formularios (con un interfaz grfico) y/o acc
esos a una
base de datos funcionar probablemente igual de rpido en C que en Python, ya
que el
cuello de botella de la ejecucin lo introduce el propio usuario con su (le
nta) velocidad
de introduccin de datos y/o el sistema de base de datos al acceder a la in
formacin.
En tal caso, parece sensato escoger el lenguaje ms flexible, el que permit
a desarrollar
la aplicacin con mayor facilidad. Un programa de clculo matricial, un siste
ma de
adquisicin de imgenes para una cmara de vdeo digital, etc. han de ejecutarse
a una
velocidad que (probablemente) excluya a Python como lenguaje para la impl
ementacin.

Hay lenguajes de programacin que combinan la eficiencia de C con parte


de la
flexibilidad de Python y pueden suponer una buena solucin de compromiso en
muchos
casos. Entre ellos hemos de destacar el lenguaje C , que estudiars el prxim
o curso.
Y hay una opcin adicional: implementar en el lenguaje eficiente las ru
tinas de
clculo pesadas y usarlas desde un lenguaje de programacin flexible. Python
ofrece un
interfaz que permite el uso de mdulos escritos en C o C . Su uso no es tri
vial, pero
hay herramientas como SWIG o Boost.Python que simplifican enormemente estas
tareas.
Ya podemos escribir el programa principal.
include
include
define
define
define

200
40
80

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

281
281

enum

Ver 1 AltaPersona AnyadirTelefono Buscar Salir

Programa principal
int main void
struct Agenda miagenda
struct Entrada encontrada
int opcion
char nombre
1
char telefono
1
char linea
1
miagenda

crea agenda

do
printf
printf
printf
printf
printf
printf
printf
gets linea

sscanf linea

opcion

switch opcion
case Ver
muestra agenda
break
case AltaPersona
printf
gets nombre
anyadir persona
break

miagenda

miagenda nombre

case AnyadirTelefono
printf
gets nombre
encontrada buscar entrada por nombre
nombre
if encontrada
printf
nombre
printf
nombre
else
printf
gets telefono
anyadir telefono a entrada encontrada telefono
break
case Buscar

miagenda

printf
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

282
282

gets nombre
encontrada buscar entrada por nombre
miagenda nombre
if encontrada
printf
nombre
else
muestra entrada encontrada
break
while opcion
libera agenda

Salir
miagenda

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
247 Disea una funcin que permita eliminar una entrada de la agenda a partir del
nombre de una persona.
248 La agenda, tal y como la hemos implementado, est desordenada. Modifica el
programa para que est siempre ordenada.
...............................................................................
...

Hemos aprendido a crear vectores dinmicos. Podemos crear secuencias de elementos


de cualquier tamao, aunque hemos visto que usar realloc para adaptar el nmero de
elementos reservados a las necesidades de la aplicacin es una posible fuente de i
neficiencia, pues puede provocar la copia de grandes cantidades de memoria. En esta
seccin
aprenderemos a crear listas con registros enlazados. Este tipo de listas ajustan
su consumo de memoria al tamao de la secuencia de datos que almacenan sin necesidad de
llamar a realloc.
Una lista enlazada es una secuencia de registros unidos por punteros de mane
ra que
cada registro contiene un valor y nos indica cul es el siguiente registro. As pues
, cada
registro consta de uno o ms campos con datos y un puntero: el que apunta al sigui
ente
registro. El ltimo registro de la secuencia apunta a. . . nada. Aparte, un puntero
maestro
apunta al primero de los registros de la secuencia. Fjate en este grfico para ir c
aptando
la idea:
info
sig
info
sig
info
sig
lista
Conceptualmente, es lo mismo que se ilustra en este grfico:
0

lista
3 8 2
Pero slo conceptualmente. En la implementacin, el nivel de complejidad al que nos
enfrentamos es mucho mayor. Eso s, a cambio ganaremos en flexibilidad y podremos of
recer
versiones eficientes de ciertas operaciones sobre listas de valores que implemen
tadas con
vectores dinmicos seran muy costosas. Por otra parte, aprender estas tcnicas supone
una inversin a largo plazo: muchas estructuras dinmicas que estudiars en cursos de
Estructuras de Datos se basan en el uso de registros enlazados y, aunque mucho ms
complejas que las simples secuencias de valores, usan las mismas tcnicas bsicas de
gestin de memoria que aprenders ahora.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
283
283
ramacin con C - UJI

Introduccin a la prog

c
UJI

Redimensionamiento con holgura


Utilizamos realloc para aumentar celda a celda la reserva de memoria de l
os vectores que
necesitan crecer. Estos redimensionamientos, tan ajustados a las necesida
des exactas
de cada momento, pasan factura: cada realloc es potencialmente lento, pue
s sabes que
puede disparar la copia de un bloque de memoria. Una modo de paliar este
problema
consiste en crecer varias celdas cuando nos quedamos cortos de memoria en
un vector.
Un campo adicional en el registro, llammosle capacidad, indica cuntas celda
s tiene
reservadas el vector y otro campo, digamos, talla, indica cuntas de ellas
estn realmente
ocupadas. Por ejemplo, este vector, en el que slo ocupamos de momento tres
celdas (las
marcadas en negro), tendra talla 3 y capacidad 5:
0
1 2
3
4
a
Cuando necesitamos escribir un nuevo dato en una celda adicional, com
probamos
si talla es menor o igual que capacidad. En tal caso, no redimensionamos
el vector:
incrementamos el valor de talla. Pero en caso contrario, nos curamos en s
alud y redimensionamos pidiendo memoria para, pongamos, 10 celdas ms (y, consecuentem
ente,
incrementamos capacidad en 10 unidades). As reducimos las llamadas a reall
oc a la
dcima parte. Incrementar un nmero fijo de celdas no es la nica estrategia po
sible.
Otra aproximacin consiste en duplicar la capacidad cada vez que se precisa
agrandar
el vector. De este modo, el nmero de llamadas a realloc es proporcional al
logaritmo
en base 2 del nmero de celdas del vector.
struct
vector Dinmico con Holgura (VDH)
int dato
int talla capacidad

lla

struct
crea VDH void
struct
vdh
vdh dato malloc 1 sizeof int
0 vdh capacidad
1
return vdh
void anyade dato struct

capacidad

if vdh
vdh
vdh
sizeof

talla
vdh capacidad
capacidad 2
dato realloc vdh dato vdh
int

vdh ta

vdh int undato

vdh

dato vdh

talla

undato

void elimina ultimo struct

vdh

if vdh talla vdh capacidad 2


vdh capacidad 0
vdh capacidad 2
vdh dato realloc vdh dato vdh capacidad sizeof int
vdh

talla

Ciertamente, esta aproximacin desperdicia memoria, pero en una cantidad q


ue
puede ser tolerable para nuestra aplicacin. Python usa internamente una va
riante de
esta tcnica al modificar la talla de una lista con el mtodo append.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
284
284

Introduccin a la programacin con C -UJI


cUJI

Para ir aprendiendo a usar estas tcnicas, gestionaremos ahora una lista con r
egistros
enlazados. La manejaremos directamente, desde un programa principal, y nos centr
aremos
en la realizacin de una serie de acciones que parten de un estado de la lista y l
a dejan
en otro estado diferente. Ilustraremos cada una de las acciones mostrando con to
do detalle
qu ocurre paso a paso. En el siguiente apartado encapsularemos cada accin elemental
(aadir elemento, borrar elemento, etc.) en una funcin independiente.

Vamos a crear una lista de enteros. Empezamos por definir el tipo de los registr
os que
componen la lista:
struct Nodo
int info
struct Nodo
sig
Un registro de tipo struct Nodo consta de dos elementos:
un entero llamado info, que es la informacin que realmente nos importa,
y un puntero a un elemento que es. . . otro struct Nodo! (Observa que h
ay cierto
nivel de recursin o autoreferencia en la definicin: un struct Nodo conti
ene un
puntero a un struct Nodo. Por esta razn, las estructuras que vamos a es
tudiar se
denominan a veces estructuras recursivas.)
Si quisiramos manejar una lista de puntos en el plano, tendramos dos opcio
nes:
1.

definir un registro con varios campos para la informacin relevante:


struct Nodo
float x
float y
struct Nodo

sig
x

sig

sig

sig
1.1

0.2

7.1

0.1

3.7
lista
y
2.1
2.

definir un tipo adicional y utilizar un nico campo de dicho tipo:


struct Punto
float x
float y

struct Nodo
struct Punto info
struct Nodo sig
info
g

info

sig

info

si

sig
x

x
1.1

0.2

3.7
y

y
lista
7.1

0.1

2.1

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

285
285

Introduccin a la programacin con C -UJI


cUJI

Cualquiera de las dos opciones es vlida, si bien la segunda es ms elegante.


Volvamos al estudio de nuestra lista de enteros. Creemos ahora el puntero maes
tro,
aqul en el que empieza la lista de enteros:
int main void
struct Nodo

lista

No es ms que un puntero a un elemento de tipo struct Nodo. Inicialmente, la lista


est
vaca. Hemos de indicarlo explcitamente as:
int main void
struct Nodo

lista

Tenemos ahora esta situacin:


lista
O sea, lista no contiene nada, est vaca.

Empezaremos aadiendo un nodo a la lista. Nuestro objetivo es pasar de la lista an


terior
a esta otra:
info
sig
lista
8
Cmo creamos el nuevo registro? Con malloc:
int main void
struct Nodo
lista

lista
malloc sizeof struct Nodo

ste es el resultado:
info
sig
lista
Ya tenemos el primer nodo de la lista, pero sus campos an no tienen los valores q
ue
deben tener finalmente. Lo hemos representado grficamente dejando el campo info e
n
blanco y sin poner una flecha que salga del campo sig.
Por una parte, el campo info debera contener el valor 8, y por otra, el campo
sig
debera apuntar a
:
int main void
struct Nodo

lista

lista
lista
lista

malloc sizeof struct Nodo


info 8
sig

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
6
286

Introduccin a la programacin con C -UJI


cUJI

28

No debe sorprenderte el uso del operador


en las asignaciones a campos del
registro.
La variable lista es de tipo struct Nodo , es decir, es un puntero, y el operado
r permite
acceder al campo de un registro apuntado por un puntero. He aqu el resultado:
info sig
lista
8
Ya tenemos una lista con un nico elemento.
Vamos a aadir un nuevo nodo a la lista, uno que contenga el valor 3 y que ubi
caremos
justo al principio de la lista, delante del nodo que contiene el valor 8. O sea,
partimos de
esta situacin:
info sig
lista
8
y queremos llegar a esta otra:
info
info

sig

sig
lista

8
En primer lugar, hemos de crear un nuevo nodo al que deber apuntar lista. El
campo sig del nuevo nodo, por su parte, debera apuntar al nodo que contiene el va
lor 8.
Empecemos por la peticin de un nuevo nodo que, ya que debe ser apuntado por lista
,
podemos pedir y rellenar as:
int main void
struct Nodo
lista
lista
lista

lista
malloc sizeof struct Nodo
info 3
sig
No sabemos cmo expresar esta asignacin.

Algo ha ido mal! Cmo podemos asignar a lista sig la direccin del siguiente nodo
con valor 8? La situacin en la que nos encontramos se puede representar as:
info sig
3
lista

info
8

sig

No somos capaces de acceder al nodo que contiene el valor 8! Es lo que denominamo


s
una prdida de referencia, un grave error en nuestro programa que nos imposibilita
seguir
construyendo la lista. Si no podemos acceder a un bloque de memoria que hemos pe
dido
con malloc, tampoco podremos liberarlo luego con free. Cuando se produce una prdi
da

de referencia hay, pues, una fuga de memoria: pedimos memoria al ordenador y no


somos
capaces de liberarla cuando dejamos de necesitarla. Un programa con fugas de mem
oria
corre el riesgo de consumir toda la memoria disponible en el ordenador. Hemos de
estar
siempre atentos para evitar prdidas de referencia. Es uno de los mayores peligros
del
trabajo con memoria dinmica.
Cmo podemos evitar la prdida de referencia? Muy fcil: con un puntero auxiliar.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
287
287

Introduccin a la programacin con C -UJI


cUJI

int main void


struct Nodo

lista

aux

aux lista
lista malloc sizeof struct Nodo
lista info 3
lista sig aux
La declaracin de la lnea 3 es curiosa. Cuando declaras dos o ms punteros en una
sola lnea, has de poner el asterisco delante del identificador de cada puntero. E
n una
lnea de declaracin que empieza por la palabra int puedes declarar punteros a enter
os y
enteros, segn precedas los respectivos identificadores con asterisco o no. Detengm
onos
un momento para considerar el estado de la memoria justo despus de ejecutarse la
lnea
6, que reza aux lista:
aux
info
8

lista

sig

El efecto de la lnea 6 es que tanto aux como lista apuntan al mismo registro
. La
asignacin de un puntero a otro hace que ambos apunten al mismo elemento. Recuerda
que un puntero no es ms que una direccin de memoria y que copiar un puntero a otro
hace que ambos contengan la misma direccin de memoria, es decir, que ambos apunte
n
al mismo lugar.
Sigamos con nuestra traza. Veamos cmo queda la memoria justo despus de ejecut
ar
la lnea 7, que dice lista malloc sizeof struct Nodo :
aux
info
nfo

sig

sig
lista
8

La lnea 8, que dice lista


o del nuevo nodo (apuntado
por lista) el valor 3:

info

3, asigna al campo inf

aux
info
nfo

sig

sig
lista

8
La lista an no est completa, pero observa que no hemos perdido la referencia a
l
ltimo fragmento de la lista. El puntero aux la mantiene. Nos queda por ejecutar l
a lnea
9, que efecta la asignacin lista sig aux y enlaza as el campo sig del primer nodo
con el segundo nodo, el apuntado por aux. Tras ejecutarla tenemos:

aux
info
nfo

sig

sig
lista

8
Perfecto! Seguro? Y qu hace aux apuntando an a la lista? La verdad, nos da
igual. Lo importante es que los nodos que hay enlazados desde lista formen la li
sta
que queramos construir. No importa cmo quedan los punteros auxiliares: una vez han
desempeado su funcin en la construccin de la lista, son suprfluos. Si te quedas ms
tranquilo, puedes aadir una lnea con aux
al final del programa para que aux
no
quede apuntando a un nodo de la lista, pero, repetimos, es innecesario.
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
288
288

Introduccin a la programacin con C -UJI


cUJI

Marqumonos un nuevo objetivo. Intentemos aadir un nuevo nodo al final de la lista.


Es
decir, partiendo de la ltima lista, intentemos obtener esta otra:
info
sig
inf
o
sig
info
sig
lista
3
8
2
Qu hemos de hacer? Para empezar, pedir un nuevo nodo, slo que esta vez no estar
apuntado por lista, sino por el que hasta ahora era el ltimo nodo de la lista. De
momento,
lo mantendremos apuntado por un puntero auxiliar. Despus, accederemos de algn modo
al campo sig del ltimo nodo de la lista (el que tiene valor 8) y haremos que apun
te
al nuevo nodo. Finalmente, haremos que el nuevo nodo contenga el valor 2 y que t
enga
como siguiente nodo a
. Intentmoslo:
int main void
struct Nodo

lista

aux

aux malloc sizeof struct Nodo


lista sig sig aux
aux info 2
aux sig
return 0
Veamos cmo queda la memoria paso a paso. Tras ejecutar la lnea 6 tenemos:
info
sig
aux
info
info

sig

sig
lista

8
O sea, la lista que cuelga de lista sigue igual, pero ahora aux apunta a un nu
evo nodo. Pasemos a estudiar la lnea 7, que parece complicada porque contiene vari
as
aplicaciones del operador . Esa lnea reza as: lista sig sig aux. Vamos a ver qu
significa leyndola de izquierda a derecha. Si lista es un puntero, y lista sig es
el
campo sig del primer nodo, que es otro puntero, entonces lista sig sig es el cam
po sig
del segundo nodo, que es otro puntero. Si a ese puntero le asignamos aux, el cam
po sig
del segundo nodo apunta a donde apuntar aux. Aqu tienes el resultado:
info

sig

info

sig

aux

info

sig

lista

8
An no hemos acabado. Una vez hayamos ejecutado las lneas 8 y 9, el trabajo esta
r
completo:
aux

info

info
2

sig

info

sig

sig
lista

8
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
289
289
cUJI

Introduccin a la programacin con C -UJI

Y es so lo que buscbamos? S. Reordenemos grficamente los diferentes componentes para que su disposicin en la imagen se asemeje ms a lo que esperbamos:
aux
sig

info
sig
3

info
lista

sig

info
8

2
Ahora queda ms claro que, efectivamente, hemos conseguido el objetivo. Esta f
igura
y la anterior son absolutamente equivalentes.
An hay algo en nuestro programa poco elegante: la asignacin lista sig sig aux
es complicada de entender y da pie a un mtodo de adicin por el final muy poco extensible. Qu queremos decir con esto ltimo? Que si ahora queremos aadir a la lista
de 3 nodos un cuarto nodo, tendremos que hacer lista sig sig sig aux. Y si quisiramos aadir un quinto, lista sig sig sig sig aux Imagina que la lista tiene
100 o 200 elementos. Menuda complicacin proceder as para aadir por el final! No
podemos expresar la idea aadir por el final de un modo ms elegante y general? S.
Podemos hacer lo siguiente:
1. buscar el ltimo elemento con un bucle y mantenerlo referenciado con un punt
ero
auxiliar, digamos aux;
aux
info
info

sig

sig
lista

8
2.
gamos

pedir un nodo nuevo y mantenerlo apuntado con otro puntero auxiliar, di


nuevo;
aux
info
info

sig

sig
lista

8
info

sig

nuevo
3.
po sig

escribir en el nodo apuntado por nuevo el nuevo dato y hacer que su cam
apunte a

;
aux
info
info

sig

sig
lista

8
nuevo
4.

info
2

sig

hacer que el nodo apuntado por aux tenga como siguiente nodo al nodo ap

untado
por nuevo.
aux
info
sig

info

sig
lista

8
info
sig
nuevo

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
290
290
n C -UJI
cUJI

Introduccin a la programacin co

Lo que es equivalente a este otro grfico en el que, sencillamente, hemos


reorganizado la disposicin de los diferentes elementos:
aux
info
info

sig

info
lista

sig

sig
3

2
nuevo
Modifiquemos el ltimo programa para expresar esta idea:
int main void
struct Nodo

lista

aux

nuevo

aux lista
while aux sig
aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info 2
nuevo sig
aux sig nuevo
return 0
La inicializacin y el bucle de las lneas 68 buscan al ltimo nodo de la lista y l
o
mantienen apuntado con aux. El ltimo nodo se distingue porque al llegar a l, aux s
ig
es
, de ah la condicin del bucle. No importa cun larga sea la lista: tanto si
tiene
1 elemento como si tiene 200, aux acaba apuntando al ltimo de ellos.5 Si partimos
de
una lista con dos elementos, ste es el resultado de ejecutar el bucle:
aux
info
info

sig

sig
lista

8
nuevo
Las lneas 911 dejan el estado de la memoria as:
aux
info
info

sig

sig
lista

8
nuevo

info
2

Finalmente, la lnea 12 completa la adicin del nodo:

sig

aux
info
info

sig

sig
lista

8
info
2

nuevo

sig

5
Aunque falla en un caso: si la lista est inicialmente vaca. Estudiaremos este
problema y su solucin
ms adelante.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
291
291
-UJI
cUJI

Introduccin a la programacin con C

Y ya est. Eso es lo que buscbamos.


La inicializacin y el bucle de las lneas 68 se pueden expresar en C de una forma
mucho ms compacta usando una estructura for:
int main void
struct Nodo

lista

aux

nuevo

for aux lista aux sig


aux
sig
nuevo malloc sizeof struct Nodo
nuevo info 2
nuevo sig
aux sig nuevo

aux

return 0
Observa que el punto y coma que aparece al final del bucle for hace que no tenga
sentencia alguna en su bloque:
for aux
lista aux
sig
aux
aux
sig
El bucle se limita a desplazar el puntero aux hasta que apunte al ltimo elemento
de la lista. Esta expresin del bucle que busca el elemento final es ms propia de l
a
programacin C, ms idiomtica.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
249 Hemos diseado un mtodo (que mejoraremos en el siguiente apartado) que
permite insertar elementos por el final de una lista y hemos necesitado un bucle
. Har
falta un bucle para insertar un elemento por delante en una lista cualquiera? Cmo
haras para convertir la ltima lista en esta otra?:
info
sig
info sig
info
sig
info sig
lista
1
3
8
2
...............................................................................
...

Vamos a aprender ahora a borrar elementos de una lista. Empezaremos por ver cmo
eliminar el primer elemento de una lista. Nuestro objetivo es, partiendo de esta
lista:
info sig
i
nfo
sig
info sig
lista
3
8
2
llegar a esta otra:
info
info

sig

sig
lista

2
Como lo que deseamos es que lista pase a apuntar al segundo elemento de la li
sta,
podramos disear una aproximacin directa modificando el valor de lista:

int main void


struct Nodo

lista

lista
lista
sig
la cabeza original de la lista.

aux

nuevo

!Mal! Se pierde la referencia a

return 0
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
292
292
ramacin con C - UJI

Introduccin a la prog

c
UJI

El efecto obtenido por esa accin es ste:


info
info sig
lista
3
2

sig

info

sig

Fugas de memoria, colapsos y recogida de basura


Muchos programas funcionan correctamente. . . durante un rato. Cuando lle
van un tiempo
ejecutndose, sin embargo, el ordenador empieza a ralentizarse sin explicac
in aparente
y la memoria del ordenador se va agotando. Una de las razones para que es
to ocurra
son las fugas de memoria. Si el programa pide bloques de memoria con mall
oc y no
los libera con free, ir consumiendo ms y ms memoria irremediablemente. Llega
r un
momento en que no quede apenas memoria libre y la que quede, estar muy fra
gmentada,
as que las peticiones a malloc costarn ms y ms tiempo en ser satisfechas. . .
si es
que pueden ser satisfechas! La saturacin de la memoria provocada por la fu
ga acabar
colapsando al ordenador y, en algunos sistemas operativos, obligando a re
iniciar la
mquina.
El principal problema con las fugas de memoria es lo difciles de detec
tar que
resultan. Si pruebas el programa en un ordenador con mucha memoria, puede
que no
llegues a apreciar efecto negativo alguno al efectuar pruebas. Dar por bu
eno un programa
errneo es, naturalmente, peor que saber que el programa an no es correcto.
Los lenguajes de programacin modernos suelen evitar las fugas de memor
ia proporcionando recogida de basura (del ingls garbage collection) automtica. Los s
istemas de
recogida de basura detectan las prdidas de referencia (origen de las fugas
de memoria)
y llaman automticamente a free por nosotros. El programador slo escribe lla
madas
a malloc (o la funcin/mecanismo equivalente en su lenguaje) y el sistema s
e encarga
de marcar como disponibles los bloques de memoria no referenciados. Lengu
ajes como
Python, Perl, Java, Ruby, Tcl y un largo etctera tiene recogida de basura
automtica,
aunque todos deben la idea a Lisp un lenguaje diseado en los aos 50 (!!!) que
ya
incorporaba esta avanzada caracterstica.

Efectivamente, hemos conseguido que la lista apuntada por lista sea lo que p
retendamos, pero hemos perdido la referencia a un nodo (el que hasta ahora era el prim
ero)

y ya no podemos liberarlo. Hemos provocado una fuga de memoria.


Para liberar un bloque de memoria hemos de llamar a free con el puntero que
apunta
a la direccin en la que empieza el bloque. Nuestro bloque est apuntado por lista,
as
que podramos pensar que la solucin es trivial y que bastara con llamar a free antes
de modificar lista:
int main void
struct Nodo

lista

aux

nuevo

!
free lista
lista lista
moria vlida.

sig

Mal! lista no apunta a una zona de me

return 0
Pero, claro, no iba a resultar tan sencillo. La lnea 7, que dice lista lista sig,
no
puede ejecutarse! Tan pronto hemos ejecutado la lnea 6, tenemos otra fuga de memo
ria:
info
info

sig

sig
lista

2
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

293
293

O sea, hemos liberado correctamente el primer nodo, pero ahora hemos perdido
la
referencia al resto de nodos y el valor de lista sig est indefinido. Cmo podemos
arreglar esto? Si no liberamos memoria, hay una fuga, y si la liberamos perdemos
la
referencia al resto de la lista. La solucin es sencilla: guardamos una referencia
al resto
de la lista con un puntero auxiliar cuando an estamos a tiempo.
int main void
struct Nodo

lista

aux lista
free lista
lista aux

aux

nuevo

sig

return 0
Ahora s. Veamos paso a paso qu hacen las ltimas tres lneas del programa. La
asignacin aux lista sig introduce una referencia al segundo nodo:
aux
info
ig

info

sig
lista

sig

info

s
8

2
Al ejecutar free lista , pasamos a esta otra situacin:
aux
info
ig

info

sig
lista

s
8

2
No hay problema. Seguimos sabiendo dnde est el resto de la lista: cuelga de
aux. As pues, podemos llegar al resultado deseado con la asignacin lista aux:
aux
info
info

sig

sig
lista

2
Vas viendo ya el tipo de problemas al que nos enfrentamos con la gestin de lis
tas?
Los siguientes apartados te presentan funciones capaces de inicializar listas, d
e insertar,
borrar y encontrar elementos, de mantener listas ordenadas, etc. Cada apartado t
e presentar una variante de las listas enlazadas con diferentes prestaciones que permiten
elegir
soluciones de compromiso entre velocidad de ciertas operaciones, consumo de memo
ria y
complicacin de la implementacin.

Vamos a desarrollar un mdulo que permita manejar listas de enteros. En el fichero


de
cabecera declararemos los tipos de datos bsicos:
struct Nodo
int info
struct Nodo

sig

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
294
294

Introduccin a la programacin con C -UJI


cUJI

Como ya dijimos, este tipo de nodo slo alberga un nmero entero. Si necesitsemos una
lista de float deberamos cambiar el tipo del valor del campo info. Y si quisisemos
una
lista de personas, podramos aadir varios campos a struct Nodo (uno para el nombre,
otro para la edad, etc.) o declarar info como de un tipo struct Persona definido
previamente
por nosotros.
Una lista es un puntero a un struct Nodo, pero cuesta poco definir un nuevo
tipo para
referirnos con mayor brevedad al tipo lista:

typedef struct Nodo

TipoLista

Ahora, podemos declarar una lista como struct Nodo o como TipoLista, indistintam
ente.
Por claridad, nos referiremos al tipo de una lista con TipoLista y al de un punt
ero a un
nodo cualquiera con struct Nodo , pero no olvides que ambos tipos son equivalent
es.
Definicin de struct con typedef
Hay quienes, para evitar la escritura repetida de la palabra struct, recu
rren a la inmediata creacin de un nuevo tipo tan pronto se define el struct. Este cdigo,
por ejemplo,
hace eso:
typedef struct Nodo
int info
struct Nodo sig
TipoNodo
Como struct Nodo y TipoNodo son sinnimos, pronto se intenta definir la est
ructura
as:
typedef struct Nodo
!
int info
TipoNodo sig
TipoNodo

Mal!

Pero el compilador emite un aviso de error. La razn es simple: la primera


aparicin de
la palabra TipoNodo tiene lugar antes de su propia definicin.

Nuestra primera funcin crear una lista vaca. El prototipo de la funcin, que declaram
os
en la cabecera
, es ste:

extern TipoLista lista vacia void


y la implementacin, que proporcionamos en una unidad de compilacin

, resulta
trivial:
include
include
TipoLista lista vacia void
return
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

295
295

La forma de uso es muy sencilla:


include
include
int main void
TipoLista lista
lista

lista vacia

return 0
Ciertamente podramos haber hecho lista
, sin ms, pero queda ms elegante
proporcionar funciones para cada una de las operaciones bsicas que ofrece una lis
ta, y
crear una lista vaca es una operacin bsica.

Nos vendr bien disponer de una funcin que devuelva cierto o falso en funcin de si l
a
lista est vaca o no. El prototipo de la funcin es:

extern int es lista vacia TipoLista lista


y su implementacin, muy sencilla:
int es lista vacia TipoLista lista
return lista

Ahora vamos a crear una funcin que inserta un elemento en una lista por la cabeza
, es
decir, haciendo que el nuevo nodo sea el primero de la lista. Antes, veamos cul e
s el
prototipo de la funcin:

extern TipoLista inserta por cabeza TipoLista lista int valor


La forma de uso de la funcin ser sta:
include
int main void
TipoLista lista
lista
lista
lista
lista
return 0

lista vacia
inserta por cabeza lista 2
inserta por cabeza lista 8
inserta por cabeza lista 3

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
ntroduccin a la programacin con C -UJI
cUJI

296
296

o, equivalentemente, esta otra:


include
int main void
TipoLista lista
lista

inserta por cabeza inserta por cabeza


inserta por cabeza lista vacia

3
return 0
Vamos con la implementacin de la funcin. La funcin debe empezar pidiendo un
nuevo nodo para el nmero que queremos insertar.
TipoLista inserta por cabeza TipoLista lista int valor
struct Nodo
nuevo

nuevo
info

malloc sizeof struct Nodo

valor

Ahora hemos de pensar un poco. Si lista va a tener como primer elemento a nuevo,
podemos enlazar directamente lista con nuevo?
TipoLista inserta por cabeza TipoLista lista int valor
mal
struct Nodo

nuevo

malloc sizeof struct Nodo

nuevo info valor


lista nuevo

La respuesta es no. An no podemos. Si lo hacemos, no hay forma de enlazar nuevo s


ig
con lo que era la lista anteriormente. Hemos perdido la referencia a la lista or
iginal.
Vemoslo con un ejemplo. Imagina una lista como sta:
info
sig
info
s
ig
lista
8
2
La ejecucin de la funcin (incompleta) con valor igual a 3 nos lleva a esta otra si
tuacin:
info
sig
nuevo
3
info

sig

info

sig
lista

Hemos perdido la referencia a la vieja lista. Una solucin sencilla consiste en, ant
es
de modificar lista, asignar a nuevo sig el valor de lista:

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

297
297

TipoLista inserta por cabeza TipoLista lista int valor


struct Nodo

nuevo

malloc sizeof struct Nodo

nuevo info valor


nuevo sig lista
lista nuevo
return lista
Tras ejecutarse la lnea 3, tenemos:
info

sig

info

sig

nuevo
info

sig
lista

2
Las lneas 5 y 6 modifican los campos del nodo apuntado por nuevo:
info
sig
nuevo
3
info
info

sig

sig
lista

2
Finalmente, la lnea 7 hace que lista apunte a donde nuevo apunta. El resultad
o final
es ste:
info
sig
nuevo
3
info
info

sig

sig
lista

2
Slo resta redisponer grficamente la lista para que no quepa duda de la correccin de
la solucin:
nuevo
info
sig

info
lista
2

sig

info

sig
3

Hemos visto, pues, que el mtodo es correcto cuando la lista no est vaca. Lo ser
tambin si suministramos una lista vaca? La lista vaca es un caso especial para el q
ue
siempre deberemos considerar la validez de nuestros mtodos.
Hagamos una comprobacin grfica. Si partimos de esta lista:
lista

y ejecutamos la funcin (con valor igual a 10, por ejemplo), pasaremos momentneamen
te
por esta situacin:
info
sig
nuevo
10
lista
y llegaremos, al final, a esta otra:
Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
298
298

Introduccin a la programacin con C -UJI


cUJ

nuevo
info
10

lista

sig

Ha funcionado correctamente. No tendremos tanta suerte con todas las funcione


s que
vamos a disear.

Nos interesa conocer ahora la longitud de una lista. La funcin que disearemos reci
be
una lista y devuelve un entero:

extern int longitud lista TipoLista lista


La implementacin se basa en recorrer toda la lista con un bucle que desplace u
n
puntero hasta llegar a
. Con cada salto de nodo a nodo, incrementaremos un
contador
cuyo valor final ser devuelto por la funcin:
int longitud lista TipoLista lista
struct Nodo
int contador

aux
0

for aux lista aux


contador
return contador

aux

Hagamos una pequea traza. Si recibimos esta lista:


info
sig
info sig
lista
3
2

aux

info

sig

sig

la variable contador empieza valiendo 0 y el bucle inicializa aux haciendo que a


punte al
primer elemento:
aux
info
info

sig

info

sig

sig
lista

2
En la primera iteracin, contador se incrementa en una unidad y aux pasa a apuntar
al
segundo nodo:
aux

info
info

sig

info

sig

sig
lista

2
Acto seguido, en la segunda iteracin, contador pasa a valer 2 y aux pasa a apunta
r al
tercer nodo:
aux
info
info

sig

info

sig

sig
lista

2
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
99
Introduccin a la programacin con C - UJI
Introduccin a la programacin con C
c
UJI

2
299

Finalmente, contador vale 3 y aux pasa a apuntar a


:
aux
info
sig

info
lista
2

sig

info

sig
3

Ah acaba el bucle. El valor devuelto por la funcin es 3, el nmero de nodos de la li


sta.
Observa que longitud lista tarda ms cuanto mayor es la lista. Una lista con n
nodos
obliga a efectuar n iteraciones del bucle for. Algo similar (aunque sin manejar
listas
enlazadas) nos ocurra con strlen, la funcin que calcula la longitud de una cadena.
La forma de usar esta funcin desde el programa principal es sencilla:
include
include
int main void
TipoLista lista
printf

longitud lista lista

return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
250 Funcionar correctamente longitud lista cuando le pasamos una lista vaca?
251 Disea una funcin que reciba una lista de enteros con enlace simple y devuelva
el valor de su elemento mximo. Si la lista est vaca, se devolver el valor 0.
252 Disea una funcin que reciba una lista de enteros con enlace simple y devuelva
su media. Si la lista est vaca, se devolver el valor 0.
...............................................................................
...

Ahora que sabemos recorrer una lista no resulta en absoluto difcil disear un proce
dimiento que muestre el contenido de una lista en pantalla. El prototipo es ste:

extern void muestra lista TipoLista lista


y una posible implementacin, sta:
void muestra lista TipoLista lista
struct Nodo
for aux

aux
lista aux

aux

aux

sig
printf

aux

info

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253 Disea un procedimiento que muestre el contenido de una lista al estilo Python
.
Por ejemplo, la lista de la ltima figura se mostrar como 3 8 2 . Fjate en que la
coma slo aparece separando a los diferentes valores, no despus de todos los nmeros.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
300
300
ramacin con C - UJI

Introduccin a la prog

c
UJI

254 Disea un procedimiento que muestre el contenido de una lista como se indica
en el siguiente ejemplo. La lista formada por los valores 3, 8 y 2 se representa
r as:

(La barra vertical representa a .)


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

Diseemos ahora una funcin que inserte un nodo al final de una lista. Su prototipo
ser:

extern TipoLista inserta por cola TipoLista lista int valor


Nuestra funcin se dividir en dos etapas: una primera que localice al ltimo elem
ento
de la lista, y otra que cree el nuevo nodo y lo una a la lista.
Aqu tienes la primera etapa:
E
E
TipoLista inserta por cola TipoLista lista int valor
struct Nodo
for aux

aux
lista aux

sig

aux

aux

sig

Analicemos paso a paso el bucle con un ejemplo. Imagina que la lista que nos
suministran en lista ya tiene tres nodos:
info
sig
info
sig
info sig
lista
3
8
2
La primera iteracin del bucle hace que aux apunte al primer elemento de la lista:
aux
info
info

sig
lista

sig

info

sig

2
Habr una nueva iteracin si aux sig es distinto de
, es decir, si el nodo apu
ntado por aux no es el ltimo de la lista. Es nuestro caso, as que iteramos haciendo
aux aux sig, o sea, pasamos a esta nueva situacin:
aux

info
info

sig
lista

sig

info

sig

2
Sigue siendo cierto que aux
? S. Avanzamos aux un nodo ms
a la derecha:
aux

sig es distinto de

info
info

sig
lista

sig

info

sig

2
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
301
301

Introduccin a la programacin con C - UJI


c
UJI

Y ahora? Es cierto que aux sig es distinto de


? No, es igual a
. Ya h
emos
llegado al ltimo nodo de la lista. Fjate en que hemos parado un paso antes que cua
ndo
contbamos el nmero de nodos de una lista; entonces la condicin de iteracin del bucle
era otra: aux
.
Podemos proceder con la segunda fase de la insercin: pedir un nuevo nodo y en
lazarlo
desde el actual ltimo nodo. Nos vendr bien un nuevo puntero auxiliar:
E
E
TipoLista inserta por cola TipoLista lista int valor
struct Nodo

aux

nuevo

for aux lista aux sig


aux
sig
nuevo malloc sizeof struct Nodo
nuevo info valor
nuevo sig
aux sig nuevo
return lista

aux

El efecto de la ejecucin de las nuevas lneas, suponiendo que el valor es 10, es ste
:
info
sig
nuevo
10
aux
info
fo

sig

info

sig
lista

sig

in

2
Est claro que ha funcionado correctamente, no? Tal vez resulte de ayuda ver la mis
ma
estructura reordenada as:
aux
nuevo
info

sig
lista

info
sig
info
sig
3
10

info

sig

Bien, entonces, por qu hemos marcado la funcin como incorrecta? Veamos qu ocurre
si la lista que nos proporcionan est vaca. Si la lista est vaca, lista vale
. En
la
primera iteracin del bucle for asignaremos a aux el valor de lista, es decir,
. Para
ver si pasamos a efectuar la primera iteracin, hemos de comprobar antes si aux si
g es
distinto de
. Pero es un error preguntar por el valor de aux sig cuando aux
es
! Un puntero a
no apunta a nodo alguno, as que no podemos preguntar p
or el
valor del campo sig de un nodo que no existe. Ojo con este tipo de errores!: los

accesos
a memoria que no nos pertenece no son detectables por el compilador. Se manifiesta
n
en tiempo de ejecucin y, normalmente, con consecuencias desastrosas6 , especialme
nte
al efectuar escrituras de informacin. Cmo podemos corregir la funcin? Tratando a la
lista vaca como un caso especial:
TipoLista inserta por cola TipoLista lista int valor
struct Nodo

aux

nuevo

if lista
lista malloc sizeof struct Nodo
lista info valor
6
En Linux, por ejemplo, obtendrs un error (tpicamente Segmentation fault) y se a
bortar inmediatamente la ejecucin del programa. En Microsoft Windows es frecuente que el ordenado
r se cuelgue.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
302
302
cUJI

Introduccin a la programacin con C -UJI

lista

sig

else
for aux lista aux sig
aux
nuevo malloc sizeof struct Nodo
nuevo info valor
nuevo sig
aux sig nuevo

aux

sig

return lista
Como puedes ver, el tratamiento de la lista vaca es
Ya te
lo advertimos antes: comprueba siempre si tu funcin
situaciones extremas. La lista vaca es un caso para
ar
la validez de tu aproximacin.
La funcin puede retocarse factorizando acciones
if else:

muy sencillo, pero especial.


se comporta adecuadamente en
el que siempre deberas comprob
comunes a los dos bloques del

TipoLista inserta por cola TipoLista lista int valor


struct Nodo

aux

nuevo

nuevo malloc sizeof struct Nodo


nuevo info valor
nuevo sig
if lista
lista nuevo
else
for aux lista aux sig
aux sig nuevo

aux

aux

sig

return lista
Mejor as.

La funcin que vamos a disear ahora recibe una lista y devuelve esa misma lista sin
el
nodo que ocupaba inicialmente la posicin de cabeza. El prototipo ser ste:

extern TipoLista borra cabeza TipoLista lista


Implementmosla. No podemos hacer simplemente lista lista sig. Ciertamente, el
lo
conseguira que, en principio, los nodos que cuelgan de lista formaran una lista cor
recta,
pero estaramos provocando una fuga de memoria al no liberar con free el nodo de l
a
cabeza (ya lo vimos cuando introdujimos las listas enlazadas):
info
sig
info sig
info sig
lista
3
8

2
Tampoco podemos empezar haciendo free lista para liberar el primer nodo, pue
s
entonces perderamos la referencia al resto de nodos. La memoria quedara as:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

303
303

info
info

sig

info

sig

sig
lista

2
Quin apuntara entonces al primer nodo de la lista?
La solucin requiere utilizar un puntero auxiliar:
E

TipoLista borra cabeza TipoLista lista


struct Nodo

aux

aux lista sig


free lista
lista aux
return lista
Ahora s, no? No. Falla en el caso de que lista valga , es decir, cuando nos pasan
una
lista vaca. La asignacin aux lista sig es errnea si lista es
. Pero la soluc
in
es muy sencilla en este caso: si nos piden borrar el nodo de cabeza de una lista
vaca,
qu hemos de hacer? Absolutamente nada!:
TipoLista borra cabeza TipoLista lista
struct Nodo
if lista
aux lista
free lista
lista aux

aux
sig

return lista
Tenlo siempre presente: si usas la expresin aux
sig para cualquier puntero aux,
has de estar completamente seguro de que aux no es
.

Vamos a disear ahora una funcin que elimine el ltimo elemento de una lista. He aqu
su prototipo:

extern TipoLista borra cola TipoLista lista


Nuevamente, dividiremos el trabajo en dos fases:
1.
2.
ig a

localizar el ltimo nodo de la lista para liberar la memoria que ocupa,


y hacer que el hasta ahora penltimo nodo tenga como valor de su campo s

.
La primera fase consistir bsicamente en esto:
E

TipoLista borra cola TipoLista lista


struct Nodo

aux

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


04
Introduccin a la programacin con C - UJI
Introduccin a la programacin con C
c
UJI

3
304

aux

for aux
sig

lista aux

sig

aux

Alto! Este mismo bucle ya nos di problemas cuando tratamos de insertar por la cola
: no
funciona correctamente con listas vacas. De todos modos, el problema tiene fcil so
lucin:
no tiene sentido borrar nada de una lista vaca.
E
E
TipoLista borra cola TipoLista lista
struct Nodo

aux

if lista
for aux
aux
sig

lista aux

sig

aux

return lista
Ahora el bucle solo se ejecuta con listas no vacas. Si partimos de esta lista:
aux
info
o

sig

info
lista
2

sig

inf

sig
3

el bucle hace que aux acabe apuntando al ltimo nodo:


aux
info
o

sig

info
lista
2

sig

inf

sig
3

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
255 Seguro que el bucle de borra cola funciona correctamente siempre? Piensa si
hace lo correcto cuando se le pasa una lista formada por un solo elemento.
...............................................................................
...
Si hemos localizado ya el ltimo nodo de la lista, hemos de liberar su memor
ia:
E
E
TipoLista borra cola TipoLista lista
struct Nodo
if lista
for aux lista aux
aux
sig

aux
sig

aux

free aux

Llegamos as a esta situacin:


aux
info
ig

info

sig
lista

8
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
305
305
c
UJI
Introduccin a la prog
ramacin con C - UJI

Fjate: slo nos falta conseguir que el nuevo ltimo nodo (el de valor igual a 8) teng
a
como valor del campo sig a
. Problema: y cmo sabemos cul es el ltimo nodo?
No se puede saber. Ni siquiera utilizando un nuevo bucle de bsqueda del ltimo nodo
,
ya que dicho bucle se basaba en que el ltimo nodo es reconocible porque tiene a
como valor de sig, y ahora el ltimo no apunta con sig a
.
El truco consiste en usar otro puntero auxiliar y modificar el bucle de bsqueda
del ltimo para haga que el nuevo puntero auxiliar vaya siempre un paso por detrs
de aux. Observa:
E
E
TipoLista borra cola TipoLista lista
struct Nodo

aux

atras

if lista
for atras
g

atras

aux
aux

aux

lista aux
aux sig

si

free aux

Fjate en el nuevo aspecto del bucle for. Utilizamos una construccin sintctica que an
no conoces, as que nos detendremos brevemente para explicarla. Los bucles for per
miten
trabajar con ms de una inicializacin y con ms de una accin de paso a la siguiente
iteracin. Este bucle, por ejemplo, trabaja con dos variables enteras, una que tom
a valores
crecientes y otra que toma valores decrecientes:
for i 0
j 10 i 3 i j
printf
i j
Ojo! Es un nico bucle, no son dos bucles anidados. No te confundas! Las diferentes
inicializaciones y pasos de iteracin se separan con comas. Al ejecutarlo, por pan
talla
aparecer esto:

Sigamos con el problema que nos ocupa. Veamos, paso a paso, qu hace ahora el
bucle. En la primera iteracin tenemos:
atras

aux
info

info

sig

info
lista
2

Y en la segunda iteracin:
atras

aux
info

sig

info
lista
2

sig

sig

sig

inf

sig
3

Y en la tercera:
atras
aux
info
o

sig

info
lista
2

sig

inf

sig
3

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
306
306
amacin con C - U JI
UJI
c

Introduccin a la progr

Ves? No importa cun larga sea la lista; el puntero atras siempre va un paso por de
trs
del puntero aux. En nuestro ejemplo ya hemos llegado al final de la lista, as que
ahora
podemos liberar el nodo apuntado por aux:
atras
aux
info
info

sig

sig
lista

8
Ahora podemos continuar: ya hemos borrado el ltimo nodo, pero esta vez s que sabem
os
cul es el nuevo ltimo nodo.
E
E
TipoLista borra cola TipoLista lista
struct Nodo
if lista
for atras
atras
free aux
atras sig

aux

aux aux

atras
aux
sig

aux

lista aux

sig

Tras ejecutar la nueva sentencia, tenemos:


atras
aux
info
info

sig

sig
lista

8
An no hemos acabado. La funcin borra cola trabaja correctamente con la lista v
aca,
pues no hace nada en ese caso (no hay nada que borrar), pero, funciona correctame
nte
cuando suministramos una lista con un nico elemento? Hagamos una traza.
Tras ejecutar el bucle que busca a los elementos ltimo y penltimo, los puntero
s
atras y aux quedan as:
atras
aux
info

sig
lista

3
Ahora se libera el nodo apuntado por aux:
atras
aux

lista
Y, finalmente, hacemos que atras sig sea igual
. Pero, eso es imposi
ble! El
puntero atras apunta a
, y hemos dicho ya que
no es un nodo y, por
tanto, no
tiene campo alguno.
Tratemos este caso como un caso especial. En primer lugar, cmo podemos detecta
rlo?
Viendo si atras vale
. Y qu hemos de hacer entonces? Hemos de hacer que list
a
pase a valer
, sin ms.
TipoLista borra cola TipoLista lista
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
307
307
cUJI

Introduccin a la programacin con C -UJI

struct Nodo
if lista
for atras
atras
free aux
if atras
lista
else
atras sig

aux

atras
aux

aux aux

aux

lista aux
sig

sig

return lista
Ya est. Si aplicsemos este nuevo mtodo, nuestro ejemplo concluira as:
atras
aux
lista
Hemos aprendido una leccin: otro caso especial que conviene estudiar explcitam
ente
es el de la lista compuesta por un solo elemento.
Insistimos en que debes seguir una sencilla regla en el diseo de funciones co
n
punteros: si accedes a un campo de un puntero ptr, por ejemplo, ptr sig o ptr in
fo,
pregntate siempre si cabe alguna posibilidad de que ptr sea
; si es as, tie
nes un
problema que debes solucionar.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256 Funcionan correctamente las funciones que hemos definido antes (clculo de la
longitud, insercin por cabeza y por cola y borrado de cabeza) cuando se suministr
a una
lista compuesta por un nico elemento?
...............................................................................
...

Vamos a disear ahora una funcin que no modifica la lista. Se trata de una funcin qu
e
nos indica si un valor entero pertenece a la lista o no. El prototipo de la func
in ser
ste:

extern int pertenece TipoLista lista int valor


La funcin devolver 1 si valor est en la lista y 0 en caso contrario.
Qu aproximacin seguiremos? Pues la misma que seguamos con los vectores: recorrer cada uno de sus elementos y, si encontramos uno con el valor buscado, dev
olver
inmediatamente el valor 1; si llegamos al final de la lista, ser que no lo hemos
encontrado,
as que en tal caso devolveremos el valor 0.

int pertenece TipoLista lista int valor


struct Nodo

aux

for aux lista aux

aux

aux

ig
if aux info
return 1
return 0

valor

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
308
308
ramacin con C - UJI

Introduccin a la prog

c
UJI

sta ha sido fcil, no?


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
257 Funciona correctamente pertenece cuando se suministra
como valor de
lista, es decir, cuando se suministra una lista vaca? Y cuando se suministra una l
ista
con un nico elemento?
...............................................................................
...

El problema que abordamos ahora es el diseo de una funcin que recibe una lista y u
n
valor y elimina el primer nodo de la lista cuyo campo info coincide con el valor
.

extern TipoLista borra primera ocurrencia TipoLista lista int valor


Nuestro primer problema consiste en detectar el valor en la lista. Si el val
or no est
en la lista, el problema se resuelve de forma trivial: se devuelve la lista inta
cta y ya est.
E
E
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo

aux

for aux lista aux

aux

aux

sig
if aux info

valor

return lista

Veamos con un ejemplo en qu situacin estamos cuando llegamos a la lnea marcada con puntos suspensivos. En esta lista hemos buscado el valor 8, as que podem
os
representar la memoria as:
aux
info
sig

info
sig
lista
2

sig

info
8

Nuestro objetivo ahora es, por una parte, efectuar el siguiente empalme entr
e nodos:
aux

info
sig

info
sig
lista
2

sig

info
8

y, por otra, eliminar el nodo apuntado por aux:


aux
info
info
sig
lista
2

sig

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
309
309
ramacin con C - UJI

Introduccin a la prog

c
UJI

Problema: cmo hacemos el empalme? Necesitamos conocer cul es el nodo que


precede al que apunta aux. Eso sabemos hacerlo con ayuda de un puntero auxiliar
que
vaya un paso por detrs de aux:
E
E
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo

aux

atras

for atras
atras

aux
if aux info
atras sig

aux lista aux


aux
sig
valor
aux sig

aux

return lista
El puntero atras empieza apuntando a
y siempre va un paso por detrs de aux.
atras
aux
info
info

sig

info
lista

sig

sig
3

Es decir, cuando aux apunta a un nodo, atras apunta al anterior. La primera i


teracin
cambia el valor de los punteros y los deja en este estado:
atras
aux
info
sig

info
lista
2

sig

info

sig
3

Es correcta la funcin? Hay una fuente de posibles problemas. Estamos asignando


algo a atras sig. Cabe alguna posibilidad de que atras sea
? S. El puntero a
tras
es
cuando el elemento encontrado ocupa la primera posicin. Fjate en este eje
mplo
en el que queremos borrar el elemento de valor 3:
atras
aux
info
info

sig

info
lista

sig

sig
3

El empalme procedente en este caso es ste:


atras
aux
info
info

sig

info

sig

sig

lista
8

3
2

TipoLista borra primera ocurrencia TipoLista lista int valor


struct Nodo

aux

atras

for atras
atras

aux aux
if aux info
if atras

aux

aux lista aux


sig
valor

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
310
310
on C -UJI
cUJI

Introduccin a la programacin c

lista aux sig


else
atras sig aux

sig

return lista
Ahora podemos borrar el elemento apuntado por aux con tranquilidad y devolver la
lista
modificada:
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo
atras

for atras
aux aux
if aux info
if atras
lista aux
else
atras sig
free aux
return lista

aux
aux lista aux
aux
valor

atras
sig

sig
aux sig

return lista

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
258 Funciona borra primera ocurrencia cuando ningn nodo de la lista contiene el
valor buscado?
259 Funciona correctamente en los siguientes casos?
lista vaca;
lista con un slo elemento que no coincide en valor con el buscado;
lista con un slo elemento que coincide en valor con el buscado.
Si no es as, corrige la funcin.
................................................................................
..

Borrar todos los nodos con un valor dado y no slo el primero es bastante ms compli
cado,
aunque hay una idea que conduce a una solucin trivial: llamar tantas veces a la f
uncin
que hemos diseado en el apartado anterior como elementos haya originalmente en la
lista. Pero, como comprenders, se trata de una aproximacin muy ineficiente: si la
lista
tiene n nodos, llamaremos n veces a una funcin que, en el peor de los casos, reco
rre la
lista completa, es decir, da n pasos para completarse. Es ms eficiente borrar todos

los
elementos de una sola pasada, en tiempo directamente proporcional a n.
Supn que recibimos esta lista:
info
sig
info
sig
sig
info
sig
info
sig
lista
3
8
8
1

info
2

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
311
311
ramacin con C - UJI

Introduccin a la prog

c
UJI

y nos piden eliminar todos los nodos cuyo campo info vale 8.
Nuestro problema es localizar el primer 8 y borrarlo dejando los dos puntero
s auxiliares en un estado tal que podamos seguir iterando para encontrar y borrar el s
iguiente
8 en la lista (y as con todos los que haya). Ya sabemos cmo localizar el primer 8.
Si
usamos un bucle con dos punteros (aux y atras), llegamos a esta situacin:
atras
aux
info
sig

sig

info

lista

info

sig

info

sig
sig

info

Si eliminamos el nodo apuntado por aux, nos interesa que aux pase a apuntar
al
siguiente, pero que atras quede apuntando al mismo nodo al que apunta ahora (sie
mpre
ha de ir un paso por detrs de aux):
atras

aux

info
lista
2

sig

info
info
3
8

sig
sig

info

sig

Bueno. No resultar tan sencillo. Deberemos tener en cuenta qu ocurre en una


situacin especial: el borrado del primer elemento de una lista. Aqu tienes una sol
ucin:
TipoLista borra valor TipoLista lista int valor
struct Nodo
atras
aux lista
while aux
if aux info
if atras
lista aux
else
atras sig
free aux
if atras
aux lista
else
aux atras

aux

atras

valor
sig
aux sig

sig

else
atras aux
aux aux sig
return lista
Hemos optado por un bucle while en lugar de un bucle for porque necesitamos un m
ayor

control de los punteros auxiliares (con el for, en cada iteracin avanzamos ambos
punteros
y no siempre queremos que avancen).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
260 Funciona borra valor con listas vacas? Y con listas de un slo elemento? Y con
una lista en la que todos los elementos coinciden en valor con el entero que bus
camos?
Si falla en alguno de estos casos, corrige la funcin.
...............................................................................
...
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
312
312
a programacin con C - UJI

Introduccin a l

c
UJI

while y for
Hemos dicho que el bucle for no resulta conveniente cuando queremos tener
un gran
control sobre los punteros auxiliares. No es cierto. El bucle for de C pe
rmite emular a
cualquier bucle while. Aqu tienes una versin de borra valor (eliminacin de t
odos los
nodos con un valor dado) que usa un bucle for:
TipoLista borra valor TipoLista lista int valor
struct Nodo
for atras
if aux info
if atras
lista aux
else
atras sig
free aux
if atras
aux lista
else
aux atras

aux

atras

aux lista aux


valor
sig
aux sig

sig

else
atras aux
aux aux sig
return lista
Observa que en el bucle for hemos dejado en blanco la zona que indica cmo
modificar
los punteros aux y atras. Puede hacerse. De hecho, puedes dejar en blanco
cualquiera de
los componentes de un bucle for. Una alternativa a while 1 , por ejemplo,
es for
.

Vamos a disear una funcin que permite insertar un nodo en una posicin dada de la
lista. Asumiremos la siguiente numeracin de posiciones en una lista:
info
sig
info sig
info sig
lista
3
8
2
0
1
2
3
O sea, insertar en la posicin 0 es insertar una nueva cabeza; en la posicin 1,
un
nuevo segundo nodo, etc. Qu pasa si se quiere insertar un nodo en una posicin mayor
que la longitud de la lista? Lo insertaremos en ltima posicin.
El prototipo de la funcin ser:

extern TipoLista inserta en posicion TipoLista lista int pos int valor
y aqu tienes su implementacin:
TipoLista inserta en posicion TipoLista lista int pos int valor
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
13

313
3

Introduccin a la programacin con C -UJI


cUJI

struct Nodo
int i
nuevo
nuevo
aux

aux

atras

nuevo

malloc sizeof struct Nodo


info valor

for i 0 atras
aux lista i pos
i
atras aux aux aux
nuevo sig aux
if atras
lista nuevo
else
atras sig nuevo
return lista

sig

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
261 Modifica la funcin para que, si nos pasan un nmero de posicin mayor que el
nmero de elementos de la lista, no se realice insercin alguna.
...............................................................................
...

Las listas que hemos manejado hasta el momento estn desordenadas, es decir, sus n
odos
estn dispuestos en un orden arbitrario. Es posible mantener listas ordenadas si l
as
inserciones se realizan utilizando siempre una funcin que respete el orden.
La funcin que vamos a desarrollar, por ejemplo, inserta un elemento en una li
sta ordenada de menor a mayor de modo que la lista resultante sea tambin una lista orde
nada
de menor a mayor.
TipoLista inserta en orden TipoLista lista int valor
struct Nodo
nuevo
nuevo

aux

atras

nuevo

malloc sizeof struct Nodo


info valor

for atras

aux lista aux


atras aux aux aux
sig
if valor
aux info
Aqu insertamos el nodo entre atras y aux.
nuevo sig aux
if atras
lista nuevo
else
atras sig nuevo
Y como ya est insertado, acabamos.
return lista

Si llegamos aqu, es que nuevo va al final de la lista.


nuevo sig
if atras

lista nuevo
else
atras sig nuevo
return lista
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
314
314
c
UJI
Introduccin a la prog
ramacin con C - UJI

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
262 Haz una traza de la insercin del valor 7 con inserta en orden en cada una de
estas listas:
a)
info
o

sig

info
lista
8

sig

info
lista
23

sig

info
lista
9

sig

sig

inf

b)
info
o

sig

sig

inf

12

15

c)
info
o

sig

sig

inf

d)
lista
e)
inf
o

sig
lista

f)
inf
o

sig
lista

10

263 Disea una funcin de insercin ordenada en lista que inserte un nuevo nodo si
y slo si no haba ningn otro con el mismo valor.
264 Determinar la pertenencia de un valor a una lista ordenada no requiere que
recorras siempre toda la lista. Disea una funcin que determine la pertenencia a un
a
lista ordenada efectuando el menor nmero posible de comparaciones y desplazamient
os
sobre la lista.
265 Implementa una funcin que ordene una lista cualquiera mediante el mtodo de
la burbuja.
266 Disea una funcin que diga, devolviendo el valor 1 o el valor 0, si una lista e
st

ordenada o desordenada.
...............................................................................
...

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
315
315
ramacin con C - UJI

Introduccin a la prog

c
UJI

La funcin que disearemos ahora recibe dos listas y devuelve una nueva lista que re
sulta
de concatenar (una copia de) ambas.
TipoLista concatena listas TipoLista a TipoLista b
TipoLista c
struct Nodo

aux

nuevo

anterior

for aux a aux


aux aux
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
c nuevo
anterior nuevo

sig

for aux b aux


aux aux
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
c nuevo
anterior nuevo

sig

if anterior
anterior sig
return c

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
267 Disea una funcin que aada a una lista una copia de otra lista.
268 Disea una funcin que devuelva una lista con los elementos de otra lista que
sean mayores que un valor dado.
269 Disea una funcin que devuelva una lista con los elementos comunes a otras
dos listas.
270 Disea una funcin que devuelva una lista que es una copia invertida de otra
lista.
...............................................................................
...

Acabaremos este apartado con una rutina que recibe una lista y borra todos y cad
a uno
de sus nodos. A estas alturas no debera resultarte muy difcil de entender:
TipoLista libera lista TipoLista lista
struct Nodo aux
aux

lista

otroaux

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
316
316
ramacin con C - UJI

Introduccin a la prog

c
UJI

while aux
otroaux aux
free aux
aux otroaux

sig

return
Alternativamente podramos definir la rutina de liberacin como un procedimien
to:
void libera lista TipoLista

lista

struct Nodo aux

otroaux

aux
lista
while aux
otroaux aux
free aux
aux otroaux

sig

lista
De este modo nos aseguramos de que el puntero lista fija su valor a
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271 Disea una funcin que devuelva un corte de la lista. Se proporcionarn como
parmetros dos enteros i y j y se devolver una lista con una copia de los nodos que
ocupan las posiciones i a j 1, ambas includas.
272 Disea una funcin que elimine un corte de la lista. Se proporcionarn como
parmetros dos enteros i y j y se eliminarn los nodos que ocupan las posiciones i a
j 1, ambas includas.
...............................................................................
...

Te ofrecemos, a modo de resumen, todas las funciones ue hemos desarrollado a lo


largo
de la seccin junto con un programa de prueba (faltan, naturalmente, las funciones
cuyo
desarrollo se propone como ejercicio).
struct Nodo
int info
struct Nodo
typedef struct Nodo
extern
extern
extern
extern
extern

sig
TipoLista

TipoLista lista vacia void


int es lista vacia TipoLista lista
TipoLista inserta por cabeza TipoLista lista int valor
TipoLista inserta por cola TipoLista lista int valor
TipoLista borra cabeza TipoLista lista

extern
extern
extern

TipoLista borra cola TipoLista lista


int longitud lista TipoLista lista
void muestra lista TipoLista lista

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con ISBN:
C
9788469301432
317
317
ramacin con C  UJI

Introduccin a la prog

c
UJI

extern
extern
extern
extern
extern
extern
extern

int pertenece TipoLista lista int valor


TipoLista borra primera ocurrencia TipoLista lista int valor
TipoLista borra valor TipoLista lista int valor
TipoLista inserta en posicion TipoLista lista int pos int valor
TipoLista inserta en orden TipoLista lista int valor
TipoLista concatena listas TipoLista a TipoLista b
TipoLista libera lista TipoLista lista

include
include
include
TipoLista lista vacia void
return
int es lista vacia TipoLista lista
return lista
TipoLista inserta por cabeza TipoLista lista int valor
struct Nodo

nuevo

malloc sizeof struct Nodo

nuevo info valor


nuevo sig lista
lista nuevo
return lista
TipoLista inserta por cola TipoLista lista int valor
struct Nodo

aux

nuevo

nuevo malloc sizeof struct Nodo


nuevo info valor
nuevo sig
if lista
lista nuevo
else
for aux lista aux sig
aux sig nuevo
return lista
TipoLista borra cabeza TipoLista lista
struct Nodo
if lista
aux lista
free lista
lista aux

aux
sig

aux

aux

sig

Introduccin
Andrs aMarzal/Isabel
la programacin con C ISBN: 9788469301432
Gracia
Introduccin a la programacin con C UJI
cUJI

318
318

return lista
TipoLista borra cola TipoLista lista
struct Nodo
if lista
for atras
atras aux aux
free aux
if atras
lista
else
atras sig

aux

atras
aux

aux

lista aux

sig

sig

return lista
int longitud lista TipoLista lista
struct Nodo
int contador

aux
0

for aux lista aux


contador
return contador

aux

aux

sig

void muestra lista TipoLista lista


Como la solucin al ejercicio 254, no como lo vimos en el texto.
struct Nodo aux
printf
for aux
printf
printf

lista aux

aux
aux

aux

sig

info

int pertenece TipoLista lista int valor


struct Nodo

aux

for aux lista aux


if aux info
valor
return 1
return 0

aux

aux

sig

TipoLista borra primera ocurrencia TipoLista lista int valor


struct Nodo

aux

for atras
aux lista aux
aux aux
aux sig
if aux info
valor
if atras
lista aux sig
else
atras sig aux sig

atras
atras

free aux
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
319
319

Introduccin a la programacin con C UJI


cUJI

return lista
return lista
TipoLista borra valor TipoLista lista int valor
struct Nodo

aux

atras
aux lista
while aux
if aux info
if atras
lista aux
else
atras sig
free aux
if atras
aux lista
else
aux atras

atras

valor
sig
aux sig

sig

else
atras aux
aux aux sig
return lista
TipoLista inserta en posicion TipoLista lista int pos int valor
struct Nodo
int i
nuevo
nuevo

aux

atras

nuevo

malloc sizeof struct Nodo


info valor

for i 0 atras
aux lista i pos
atras aux aux aux sig
nuevo sig aux
if atras
lista nuevo
else
atras sig nuevo
return lista

aux

TipoLista inserta en orden TipoLista lista int valor


struct Nodo
nuevo
nuevo
aux

aux

atras

nuevo

malloc sizeof struct Nodo


info valor

for atras
aux lista aux
atras
aux sig
if valor
aux info
Au insertamos el nodo entre atras y aux.

aux

nuevo sig aux


Introduccin
Andrs aMarzal/Isabel
la programacin con CISBN: 9788469301432
Gracia
Introduccin a la programacin con C UJI
cUJI

320
320

if atras
lista nuevo
else
atras sig nuevo
Y como ya est insertado, acabamos.
return lista
Si llegamos au, es ue nuevo va al final de la lista.
nuevo sig
if atras
lista nuevo
else
atras sig nuevo
return lista
TipoLista concatena listas TipoLista a TipoLista b
TipoLista c
struct Nodo

aux

nuevo

anterior

for aux a aux


aux aux
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
c nuevo
anterior nuevo

sig

for aux b aux


aux aux
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
c nuevo
anterior nuevo

sig

if anterior
anterior sig
return c
TipoLista libera lista TipoLista lista
struct Nodo aux
aux lista
while aux
otroaux aux
free aux
aux otroaux
return

Andrs aMarzal/Isabel

otroaux

sig

Introduccin

Gracia
la programacin con CISBN: 9788469301432

troduccin a la programacin con C UJI


cUJI

321
321

In

include
include
int main void
TipoLista l l2 l3
printf
l lista vacia
muestra lista l
printf
printf
l inserta por
l inserta por
l inserta por
muestra lista

es lista vacia l
cabeza l 2
cabeza l 8
cabeza l 3
l

printf

longitud lis

ta l
printf
l inserta por
l inserta por
l inserta por
muestra lista

cola l 1
cola l 5
cola l 10
l

printf
l borra cabeza l
muestra lista l
printf
l borra cola l
muestra lista l
printf

pertenece

printf

pertenece

l 5
l 7
printf
l inserta por cola l 1
muestra lista l
printf
l borra primera ocurrencia l 1
muestra lista l
printf
l borra primera ocurrencia l 1
muestra lista l
printf
l borra primera ocurrencia l 1
muestra lista l
printf
l inserta por cola l 2

l inserta por cabeza l 2


muestra lista l
Introduccin
Andrs aMarzal/Isabel
la programacin con C ISBN: 9788469301432
Gracia
Introduccin a la programacin con C UJI
cUJI

322
322

printf
l borra valor l 2
muestra lista l
printf
l borra valor l 8
muestra lista l
printf
l inserta en posicion l 0 1
muestra lista l
printf
l inserta en posicion l 2 10
muestra lista l
printf
l inserta en posicion l 1 3
muestra lista l
printf
l inserta en orden
l inserta en orden
l inserta en orden
l inserta en orden
muestra lista l

l
l
l
l

4
0
20
5

printf
l2 lista vacia
l2 inserta por cola l2 30
l2 inserta por cola l2 40
l2 inserta por cola l2 50
muestra lista l2
printf
l3 concatena listas l l2
muestra lista l3
printf
l libera lista l
l2 libera lista l2
l3 libera lista l3
muestra lista l
muestra lista l2
muestra lista l3
return 0
Recuerda ue debes compilar estos programas en al menos dos pasos:

Este es el resultado en pantalla de la ejecucin de

Introduccin a la programacin con C

Andrs Marzal/Isabel Gracia  ISBN: 9788469301432

323
323

c
duccin a la programacin con C  UJI
UJI

Intro

Las listas ue hemos estudiado hasta el momento son muy rpidas para, por ejemplo,
la
insercin de elementos por la cabeza. Como la cabeza est permanentemente apuntada
por un puntero, basta con pedir memoria para un nuevo nodo y hacer un par de aju
stes
con punteros:
hacer ue el nodo ue sigue al nuevo nodo sea el ue era apuntado por

el puntero

a cabeza,
y hacer ue el puntero a cabeza apunte ahora al nuevo nodo.
No importa cun larga sea la lista: la insercin por cabeza es siempre igual de rpida
.
Reuiere una cantidad de tiempo constante. Pero la insercin por cola est seriament
e
Introduccin a la programacin
Andrs Marzal/Isabel
con CISBN: 9788469301432
Gracia
c
Introduccin a la programacin con C  UJI
UJI

324
324

penalizada en comparacin con la insercin por cabeza. Como no sabemos dnde est el
ltimo elemento, hemos de recorrer la lista completa cada vez ue deseamos aadir po
r
la cola. Una forma de eliminar este problema consiste en mantener siempre dos pu
nteros:
uno al primer elemento de la lista y otro al ltimo.
La nueva estructura de datos ue representa una lista podra definirse as:

ig

struct Nodo
int info
struct Nodo

sig

struct Lista cc
struct Nodo
struct Nodo

cabeza
cola

Podemos representar grficamente una lista con punteros a cabeza y cola as:
info
sig
info s
info sig
lista
cabeza
cola
3

2
Los punteros lista cabeza y lista cola forman un nico objeto del tipo lista cc.
Vamos a presentar ahora unas funciones ue gestionan listas con punteros a c
abe
za y cola. Afortunadamente, todo lo aprendido con las listas del apartado anteri
or nos
vale. Eso s, algunas operaciones se simplificarn notablemente (aadir por la cola, p
or
ejemplo), pero otras se complicarn ligeramente (eliminar la cola, por ejemplo), y
a ue
ahora hemos de encargarnos de mantener siempre un nuevo puntero (lista cola) apu
ntando
correctamente al ltimo elemento de la lista.

La funcin ue crea una lista vaca es, nuevamente, muy sencilla. El prototipo es ste
:
extern struct Lista cc crea lista cc vacia void
y su implementacin:
struct Lista cc crea lista cc vacia void
struct Lista cc lista
lista cabeza lista cola
return lista

Una lista vaca puede representarse as:


lista
cabeza
cola

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
25
325

Introduccin a la programacin con C UJI


cUJI

La insercin de un nodo en cabeza slo reuiere, en principio, modificar el valor de


l campo
cabeza, no? Veamos, si tenemos una lista como sta:
info
sig
info sig
info sig
lista
cabeza
cola
3
8

y deseamos insertar el valor 1 en cabeza, basta con modificar lista cabeza y aju
star
el campo sig del nuevo nodo para ue apunte a la antigua cabeza. Como puedes ver
,
lista cola sigue apuntando al mismo lugar al ue apuntaba inicialmente:
info
sig
info
si
g
info sig
info sig
lista
cabeza
cola
1
8

3
2

Ya est, no? No. Hay un caso en el ue tambin hemos de modificar lista cola adems
de lista cabeza: cuando la lista est inicialmente vaca. Por u? Porue el nuevo nodo
de la lista ser cabeza y cola a la vez.
Fjate, si partimos de esta lista:
lista
cabeza
cola

e insertamos el valor 1, hemos de construir esta otra:


info

sig
lista
cabeza
cola

1
Si slo modificsemos el valor de lista cabeza, tendramos esta otra lista mal formada
en
la ue lista cola no apunta al ltimo elemento:
info

sig
lista
cabeza

cola
1

Ya estamos en condiciones de presentar la funcin:


struct Lista cc inserta por cabeza struct Lista cc lista int valor
struct Nodo

nuevo

nuevo malloc sizeof struct Nodo


nuevo info valor
nuevo sig lista cabeza
if lista cabeza
lista cola nuevo
lista cabeza nuevo
return lista

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
326
326
acin con C UJI
cUJI

Introduccin a la program

La insercin de un nodo en cola no es mucho ms complicada. Como sabemos siempre cul


es el ltimo elemento de la lista, no hemos de buscarlo con un bucle. El procedimi
ento a
seguir es ste:
1.

Pedimos memoria para un nuevo nodo apuntado por un puntero nuevo,


info

sig

nuevo
info
info
lista

sig

sig

info

sig

cabeza
cola
3
8

2.
asignamos un valor a nuevo
acemos ue el nuevo

info y h
sig sea
info
1

nuevo

sig
info

info
lista

sig

sig

info

sig

cabeza
cola
3
8

3.

hacemos ue lista cola

sig apunte a nuevo,


info
1

nuevo

sig

info
sig

info
lista

sig

info

sig

cabeza
cola
3
8

4.

y actualizamos lista cola para ue pase a apuntar a nuevo.


nuevo

info
1

sig

info
sig

info
lista

sig

info

sig

cabeza
cola
3
8

Reordenando el grfico tenemos:


nuevo
info
info sig

info
info

sig

sig
sig

lista
cabeza
cola
3
8

La nica precaucin ue hemos de tener es ue, cuando la lista est inicialmente
vaca, se modifiue tanto el puntero a la cabeza como el puntero a la cola para u
e
ambos apunten a nuevo.
struct Lista cc inserta por cola struct Lista cc lista int valor
struct Nodo

nuevo

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
327
327
Introduccin a la programacin con C UJI
cUJI

nuevo
nuevo
nuevo

malloc sizeof struct Nodo


info valor
sig

if lista cola
lista cola sig nuevo
lista cola nuevo
else
lista cabeza
return lista

lista cola

nuevo

Fjate: la insercin por cola en este tipo de listas es tan eficiente como la in
sercin
por cabeza. No importa lo larga ue sea la lista: siempre cuesta lo mismo insert
ar por
cola, una cantidad constante de tiempo. Acaba de rendir su primer fruto el conta
r con
punteros a cabeza y cola.

Eliminar un elemento de la cabeza ha de resultar sencillo con la experiencia ad


uirida:
1.

Si la lista est vaca, no hacemos nada.

2.
Si la lista tiene un slo elemento, lo eliminamos y ponemos lista cabeza
y lista cola
a
.
3. Y si la lista tiene ms de un elemento, como sta:
info
nfo

sig

info
lista

sig

sig
cabeza
cola
3

seguimos este proceso:


a) Mantenemos un puntero auxiliar apuntando a la actual cabeza,
aux
info
info

sig

info
lista

sig
cabeza
cola
3

sig

b) hacemos ue lista cabeza apunte al sucesor de la cabeza actual,


aux
info
info

sig

info
lista

sig

sig
cabeza
cola
3

2
c) y liberamos la memoria ocupada por el primer nodo.
aux

info

sig

info
lista

sig
cabeza
cola

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
328
328
cUJI
n C UJI

Introduccin a la programacin co

struct Lista cc borra cabeza struct Lista cc lista


struct Nodo

aux

Lista vaca: nada ue borrar.


if lista cabeza
return lista
Lista con un solo nodo: se borra el nodo y la cabeza y la cola pasan
.
if lista cabeza
lista cola
free lista cabeza
lista cabeza lista cola
return lista

a ser

Lista con ms de un elemento.


aux lista cabeza
lista cabeza aux sig
free aux
return lista

El borrado del ltimo elemento de una lista con punteros a cabeza y cola plantea u
n pro
blema: cuando hayamos eliminado el nodo apuntado por lista cola, a uin debe apunt
ar
lista cola? Naturalmente, al ue hasta ahora era el penltimo nodo. Y cmo sabemos
cul era el penltimo? Slo hay una forma de saberlo: buscndolo con un recorrido de
los nodos de la lista.
Nuevamente distinguiremos tres casos distintos en funcin de la talla de la l
ista:
1.
Si la lista est vaca, no hacemos nada.
2.
Si la lista tiene un nico elemento, liberamos su memoria y hacemos ue l
os
punteros a cabeza y cola apunten a
.
3. En otro caso, actuaremos como en este ejemplo,
info
sig
info
sig
info
sig
lista
cabeza
cola
3
8

2
a) buscamos el penltimo elemento (sabremos cul es porue si se le apunt

con aux, entonces aux sig coincide con lista cola) y lo apuntamos
con una
variable auxiliar aux,
aux
info
info

sig

info

sig

lista
cabeza

sig

cola
3
8

2
b) hacemos ue el penltimo no tenga siguiente nodo (ponemos su campo s

ig a

) para ue as pase a ser el ltimo,


aux
info
info

sig

info

sig

sig

lista
cabeza
cola
3
8

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
329
329
on C UJI

Introduccin a la programacin c

cUJI

c) liberamos la memoria del ue hasta ahora era el ltimo nodo (el apu

ntado

por lista cola)


aux
info
sig

info

sig
lista
cabeza
cola
3

8
d) y, finalmente, hacemos ue lista cola apunte a aux.
aux
info
sig

info

sig
lista
cabeza
cola
3

struct Lista cc borra cola struct Lista cc lista


struct Nodo

aux

Lista vaca.
if lista cabeza
return lista
Lista con un solo nodo.
if lista cabeza
lista cola
free lista cabeza
lista cabeza lista cola
return lista

a aux

Lista con ms de un nodo.


for aux lista cabeza aux sig
aux
sig
aux sig
free lista cola
lista cola aux
return lista

lista col

Fjate en la condicin del bucle: detecta si hemos llegado o no al penltimo nodo pre
guntando si el ue sigue a aux es el ltimo (el apuntado por lista cola).
La operacin de borrado de la cola no es, pues, tan eficiente como la de borra
do de la
cabeza, pese a ue tenemos un puntero a la cola. El tiempo ue necesita es direc
tamente

proporcional a la longitud de la lista.


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
273 Disea una funcin que determine si un nmero pertenece o no a una lista con
punteros a cabeza y cola.
274 Disea una funcin que elimine el primer nodo con un valor dado en una lista
con punteros a cabeza y cola.
275 Disea una funcin que elimine todos los nodos con un valor dado en una lista
con punteros a cabeza y cola.
276 Disea una funcin que devuelva el elemento que ocupa la posicin n en una lista
con puntero a cabeza y cola. (La cabeza ocupa la posicin 0.) La funcin devolver com
o
valor de retorno 1 o 0 para, respectivamente, indicar si la operacin se pudo comp
letar
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
330
330
ramacin con C - UJI

Introduccin a la prog

c
UJI

con xito o si fracas. La operacin no se puede completar con xito si n es negativo o


si
n es mayor o igual que la talla de la lista. El valor del nodo se devolver en un
parmetro
pasado por referencia.
277 Disea una funcin que devuelva un corte de la lista. Se recibirn dos ndices
i y j y se devolver una nueva lista con punteros a cabeza y cola con una copia de
los
nodos que van del que ocupa la posicin i al que ocupa la posicin j 1, ambos includo
s.
La lista devuelta tendr punteros a cabeza y cola.
278 Disea una funcin de insercin ordenada en una lista ordenada con punteros a
cabeza y cola.
279 Disea una funcin que devuelva el menor valor de una lista ordenada con
punteros a cabeza y cola.
280 Disea una funcin que devuelva el mayor valor de una lista ordenada con
punteros a cabeza y cola.
281 Disea una funcin que aada a una lista con punteros a cabeza y cola una
copia de otra lista con punteros a cabeza y cola.
...............................................................................
...

Vamos a dotar a cada nodo de dos punteros: uno al siguiente nodo en la lista y o
tro al
anterior.
Los nodos sern variables de este tipo:
struct DNodo
int info
struct DNodo
struct DNodo

ant
sig

Valor del nodo.


Puntero al anterior.
Puntero al siguiente.

Una lista es un puntero a un struct DNodo (o a


). Nuevamente, definiremos u
n tipo
para poner nfasis en que un puntero representa a la lista que cuelga de l.
typedef struct DNodo
TipoDLista
Aqu tienes una representacin grfica de una lista doblemente enlazada:
ant
info
sig
ant info
sig
ant info sig
lista
3
8
2
Observa que cada nodo tiene dos punteros: uno al nodo anterior y otro al siguien
te. Qu
nodo sigue al ltimo nodo? Ninguno, o sea,
. Y cul antecede al primero? Ninguno,
es decir,
.

La insercin por cabeza es relativamente sencilla. Tratemos en primer lugar el cas


o general: la insercin por cabeza en una lista no vaca. Por ejemplo, en sta:

ant
ant

info

info

sig

sig
lista

2
Vamos paso a paso.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
331
331

Introduccin a la programacin con C - UJI


c
UJI

1.

Empezamos pidiendo memoria para un nuevo nodo:


ant

info

sig

ant

info

sig

nuevo
ant

info

sig
lista

2
2.

Asignamos el valor que nos indiquen al campo info:


ant

info
3

sig

ant

info

sig

nuevo
ant

info

sig
lista

2
3.

Ajustamos sus punteros ant y sig:


ant

info
3

sig

ant

info

sig

nuevo
ant

info

sig
lista

2
4.

Ajustamos el puntero ant del que hasta ahora ocupaba la cabeza:


ant

info
3

sig

ant

info

sig

nuevo
ant

info

sig
lista

2
5.

Y, finalmente, hacemos que lista apunte al nuevo nodo:


nuevo
ant

info
3

sig

ant

info

sig

lista
ant

info

sig
8

2
El caso de la insercin en la lista vaca es trivial: se pide memoria para un nu
evo
nodo cuyos punteros ant y sig se ponen a
y hacemos que la cabeza apunte

a dicho
nodo.
Aqu tienes la funcin que codifica el mtodo descrito. Hemos factorizado y dispue
sto
al principio los elementos comunes al caso general y al de la lista vaca:
TipoDLista inserta por cabeza TipoDLista lista int valor
struct DNodo
nuevo
nuevo
nuevo
nuevo

nuevo
malloc sizeof struct DNodo
info valor
ant
sig lista

if lista
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

332
332

Introduccin a la programacin con C -UJI


cUJI

lista

ant

nuevo

lista nuevo
return lista
Te proponemos como ejercicios algunas de las funciones bsicas para el manejo
de
listas doblemente enlazadas:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
282 Disea una funcin que inserte un nuevo nodo al final de una lista doblemente
enlazada.
283 Disea una funcin que borre la cabeza de una lista doblemente enlazada. Presta
especial atencin al caso en el que la lista consta de un slo elemento.
...............................................................................
...

Vamos a desarrollar la funcin de borrado del ltimo elemento de una lista doblement
e
enlazada, pues presenta algn aspecto interesante.
Desarrollemos nuevamente el caso general sobre una lista concreta para deduc
ir el
mtodo a seguir. Tomemos, por ejemplo, sta:
ant
info
sig
ant
info
sig
ant
info
sig
lista
3
8
2
1.
Empezamos localizando el ltimo elemento de la lista (con un bucle) y ap
untndolo
con un puntero:
aux
ant
ant

2.

info
lista
8

info

sig

sig

ant

info

sig

3
2

Y localizamos ahora el penltimo en un slo paso (es aux


ant):
atras
aux
ant

ant

3.

info
lista
8

sig

info
ant

sig
info

Se elimina el ltimo nodo (el apuntado por aux):

sig

atras

aux
ant

info

sig

ant

info

sig

lista
3

4.
Y se pone el campo sig del que hasta ahora era penltimo (el apuntado po
r atras)
a
.
atr
as
aux
ant
sig

ant

info
lista
8

info

sig
3

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
333
333
ntroduccin a la programacin con C - UJI
c
UJI

El caso de la lista vaca tiene fcil solucin: no hay nada que borrar. Es ms probl
emtica la lista con slo un nodo. El problema con ella estriba en que no hay element
o
penltimo (el anterior al ltimo es
). Tendremos, pues, que detectar esta sit
uacin y
tratarla adecuadamente.
TipoDLista borra por cola TipoDLista lista
struct DNodo

aux

atras

Lista vaca.
if lista
return lista
Lista con un nodo.
if lista sig
free lista
lista
return lista
Caso general.
for aux lista aux
atras aux ant
free aux
atras sig
return lista

sig

aux aux

sig

Tratemos ahora el caso de la insercin de un nuevo nodo en la posicin n de una list


a
doblemente enlazada.
ant
info
sig
ant
info
sig
ant info sig
lista
3
8
2
0
1
2
3
Si n est fuera del rango de ndices vlidos, insertaremos en la cabeza (si n es negativ
o)
o en la cola (si n es mayor que el nmero de elementos de la lista).
A simple vista percibimos ya diferentes casos que requerirn estrategias difer
entes:
La lista vaca: la solucin en este caso es trivial.
Insercin al principio de la lista: seguiremos la misma rutina diseada p
ara insertar
por cabeza y, por qu no, utilizaremos la funcin que diseamos en su momen
to.
Insercin al final de la lista: dem.7
Insercin entre dos nodos de una lista.

Vamos a desarrollar completamente el ltimo caso. Nuevamente usaremos una list


a
concreta para deducir cada uno de los detalles del mtodo. Insertaremos el valor 1
en la
posicin 2 de esta lista:
ant
info
sig
ant
info
sig
ant info sig
lista
3
8
2
7
Ver ms adelante el ejercicio 285.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
334
334

Introduccin a la programacin con C -UJI


cUJI

1.
Empezamos localizando el elemento que ocupa actualmente la posicin n. Un
simple
bucle efectuar esta labor:
a
ux
ant
sig

ant

info
lista
2

2.

info

sig

ant

info

sig
3

Pedimos memoria para un nuevo nodo, lo apuntamos con el puntero nuevo y

le
asignamos el valor:
a
ux
ant
sig

ant

ant

info
lista
2

info

info

sig

ant

info

sig
3

sig
nuevo

1
3.

Hacemos que nuevo

sig sea aux:


a

ux
ant
sig

ant

ant

info
lista
2

info

info

sig

ant

info

sig
3

sig
nuevo

1
4.

Hacemos que nuevo

ant sea aux

ant:
a

ux
ant
sig

ant

ant

info
lista
2

info

info

sig

ant

info

sig
3

sig
nuevo

1
5.

Ojo con este paso, que es complicado. Hacemos que el anterior a aux ten

ga como
siguiente a nuevo, es decir, aux ant sig nuevo:
a
ux
ant
sig

ant

ant

info
lista
2

info

info

sig

ant

info

sig
3

sig
nuevo

1
6.

Y ya slo resta que el anterior a aux sea nuevo con la asignacin aux
ant
nuevo:
a

ux
ant
sig

ant

ant

info
lista
2

info

info

sig

ant

info

sig
3

sig
nuevo

1
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
35

335
3
Introduccin a la programacin con C -UJI
cUJ

Ahora que tenemos claro el procedimiento, podemos escribir la funcin:


TipoDLista inserta en posicion TipoDLista lista int pos int valor
struct DNodo
int i

aux

nuevo

Caso especial: lista vaca


if lista
lista inserta por cabeza lista valor
return lista
Insercin en cabeza en lista no vaca.
if pos
0
lista inserta por cabeza lista valor
return lista
Insercin no en cabeza.
nuevo malloc sizeof struct DNodo
nuevo info valor
for i 0 aux lista i pos
aux
i
aux
aux
sig
if aux
Insercin por cola.
lista inserta por cola lista valor
else
nuevo sig aux
nuevo ant aux ant
aux ant sig nuevo
aux ant nuevo
return lista
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
284 Reescribe la funcin de insercin en una posicin dada para que no efecte
llamadas a la funcin inserta por cabeza.
285 Reescribe la funcin de insercin en una posicin dada para que no efecte
llamadas a la funcin inserta por cola. Es ms eficiente la nueva versin? Por qu?
286 Qu ocurrira si las ltimas lneas de la funcin fueran stas?:
nuevo sig aux
nuevo ant aux ant
aux ant nuevo
aux ant sig nuevo
return lista
Es correcta ahora la funcin? Haz una traza con un caso concreto.
...............................................................................
...

Nuevamente hay un par de casos triviales: si la lista est vaca, no hay que hacer n

ada
y si la lista tiene un slo elemento, slo hemos de actuar si ese elemento tiene el
valor
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
336
336
ramacin con C - UJI

Introduccin a la prog

c
UJI

buscado, en cuyo caso liberaremos la memoria del nodo en cuestin y convertiremos


la
lista en una lista vaca.
Desarrollemos un caso general. Supongamos que en esta lista hemos de elimin
ar el
primer y nico nodo con valor 8:
ant
info
sig
ant
info
sig
ant
info
sig
lista
3
8
2
Vamos paso a paso:
1.
r aux:

Empezamos por localizar el elemento y apuntarlo con un puntero auxilia


aux
ant

info

sig
lista

ant

info

sig

ant

sig
3

8
2.
gali-

info
2

Hacemos que el que sigue al anterior de aux sea el siguiente de aux (qu
matas!). O sea, hacemos aux ant sig aux sig:
aux
ant

info

sig
lista

ant

info

info

sig

ant

sig
3

3.
Ahora hacemos que el que antecede al siguiente de aux sea el anterior
a aux. Es
decir, aux sig ant aux ant:
aux
ant
info

sig
lista

ant

info

sig

ant

sig
3

4.

info
2

Y ya podemos liberar la memoria ocupada por el nodo apuntado con aux:


aux
ant
ant

info

lista

info
sig
3

sig

Hemos de ser cautos. Hay un par de casos especiales que merecen ser tratados
aparte:
el borrado del primer nodo y el borrado del ltimo nodo. Veamos cmo proceder en el
primer caso: tratemos de borrar el nodo de valor 3 en la lista del ejemplo anter
ior.
1.
nta al

Una vez apuntado el nodo por aux, sabemos que es el primero porque apu
mismo nodo que lista:
aux
ant

info

sig
lista

ant

8
2.
puntero

info

info

sig

ant

sig
3

2
Hacemos que el segundo nodo deje de tener antecesor, es decir, que el

aux sig ant valga


sig ant aux

(que, por otra parte, es lo mismo que hacer aux


ant)

:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
337
337
on C -UJI
cUJI

Introduccin a la programacin c

aux
info

sig

ant

info

ant
sig

lista

info

sig

ant

3.

info

Ahora hacemos que lista pase a apuntar al segundo nodo (lista aux
sig):
aux
sig

ant

info

ant
sig

lista

info

sig

ant

4.

Y por fin, podemos liberar al nodo apuntado por aux (free aux ):
aux
ant

info

sig

ant

info

sig

lista
8

Vamos a por el caso en que borramos el ltimo elemento de la lista:


1.
ltimo

Empezamos por localizarlo con aux y detectamos que efectivamente es el


porque aux sig es

aux
info

sig

ant

info

ant
sig

lista

info

sig

ant

2
2.

Hacemos que el siguiente del que antecede a aux sea


: (aux
ant sig
):

aux
info

sig

ant

info

ant
sig

lista
8

3
2

3.

info

Y liberamos el nodo apuntado por aux:

sig

ant

aux
ant
ant

info

info

sig

sig
lista

TipoDLista borra primera ocurrencia TipoDLista lista int valor


struct DNodo
for aux lista aux
if aux info
valor
break

aux
aux aux

sig

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


338
Introduccin a la programacin c
on C - UJI
Introduccin a la programacin con C
338
c
UJI

if aux
return lista

No se encontr.

if aux ant

Es el primero de la lista

.
lista aux sig
else
aux ant sig aux

sig

if aux sig

No es el ltimo de la lista

.
aux sig ant

aux

ant

free aux
return lista

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
287 Disea una funcin que permita efectuar la insercin ordenada de un elemento
en una lista con enlace doble que est ordenada.
288 Disea una funcin que permita concatenar dos listas doblemente enlazadas. La
funcin recibir las dos listas y devolver una lista nueva con una copia de la primer
a
seguida de una copia de la segunda.
289 Disea una funcin que devuelva una copia invertida de una lista doblemente
enlazada.
...............................................................................
...

Ya sabemos manejar listas con puntero a cabeza y listas con punteros a cabeza y
cola.
Hemos visto que las listas con puntero a cabeza son ineficientes a la hora de aad
ir
elementos por la cola: se tarda tanto ms cuanto mayor es el nmero de elementos de
la
lista. Las listas con puntero a cabeza y cola permiten realizar operaciones de i
nsercin
por cola en un nmero constante de pasos. An as, hay operaciones de cola que tambin
son ineficientes en esta ltima estructura de datos: la eliminacin del nodo de cola
, por
ejemplo, sigue necesitando un tiempo proporcional a la longitud de la lista.
La estructura que presentamos en esta seccin, la lista doblemente enlazada c
on
puntero a cabeza y cola, corrige la ineficiencia en el borrado del nodo de cola.
Una lista
doblemente enlazada con puntero a cabeza y cola puede representarse grficamente a
s:
ant
info
sig
ant
info
sig
ant
info
sig
lista
cabeza
cola
3

La definicin del tipo es fcil ahora que ya hemos estudiado diferentes tipos
de listas:

struct DNodo
int info
struct DNodo
struct DNodo

ant
sig

struct DLista cc
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
339
339
ramacin con C - UJI

Introduccin a la prog

c
UJI

struct DNodo
struct DNodo

cabeza
cola

typedef struct DLista cc TipoDListaCC


Slo vamos a presentarte una de las operaciones sobre este tipo de listas: el
borrado
de la cola. El resto de operaciones te las proponemos como ejercicios.
Con cualquiera de las otras estructuras de datos basadas en registros enlaza
dos, el
borrado del nodo de cola no poda efectuarse en tiempo constante. sta lo hace posib
le.
Cmo? Lo mejor es que, una vez ms, despleguemos los diferentes casos y estudiemos
ejemplos concretos cuando convenga:
Si la lista est vaca, no hay que hacer nada.
Si la lista tiene un solo elemento, liberamos su memoria y ponemos los
punteros a
cabeza y cola a
.
Y si la lista tiene ms de un elemento, como sta:
ant
ant

info

sig

info
ant

sig
info

si

g
lista
cabeza
cola
3
8

hacemos lo siguiente:
a) localizamos al penltimo elemento, que es lista cola
ant, y lo mantenemos
apuntado con un puntero auxiliar aux:
ant
sig
fo

ant

info

sig

aux
info
ant

in

sig
lista
cabeza
cola
3
8

b) liberamos la memoria apuntada por lista cola:


aux

ant

info

sig

ant

info

sig

lista
cabeza
cola
3

c) ponemos aux

sig a

aux
ant
info

sig

ant

info

sig

lista
cabeza
cola
3

d) y ajustamos lista cola para que apunte ahora donde apunta aux:
aux
ant
info

sig

ant

info

sig

lista
cabeza
cola
3

Ya podemos escribir el programa:

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
340
340
Introduccin a la programacin con C -UJI
cUJI

TipoDListaCC borra cola TipoDListaCC lista


if lista cabeza
return lista
if lista cabeza
lista cola
free lista cabeza
lista cabeza lista cola
return lista
aux lista cola
free lista cola
aux sig
lista cola aux
return lista

ant

Ha sido fcil, no? No ha hecho falta bucle alguno. La operacin se ejecuta en un nmero
de pasos que es independiente de lo larga que sea la lista.
Ahora te toca a t desarrollar cdigo. Practica con estos ejercicios:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
290 Disea una funcin que calcule la longitud de una lista doblemente enlazada
con punteros a cabeza y cola.
291 Disea una funcin que permita insertar un nuevo nodo en cabeza.
292 Disea una funcin que permita insertar un nuevo nodo en cola.
293 Disea una funcin que permita borrar el nodo de cabeza.
294
Disea una funcin que elimine el primer elemento de la lista con un val
or dado.
295
Disea una funcin que elimine todos los elementos de la lista con un va
lor dado.
296 Disea una funcin que inserte un nodo en una posicin determinada que se
indica por su ndice.
297 Disea una funcin que inserte ordenadamente en una lista ordenada.
298 Disea una funcin que muestre por pantalla el contenido de una lista, mostrando
el valor de cada celda en una lnea. Los elementos se mostrarn en el mismo orden co
n
el que aparecen en la lista.
299 Disea una funcin que muestre por pantalla el contenido de una lista, mostrando
el valor de cada celda en un lnea. Los elementos se mostrarn en orden inverso.
300 Disea una funcin que devuelva una copia invertida de una lista doblemente
enlazada con puntero a cabeza y cola.
...............................................................................
...

Hemos estudiado cuatro tipos diferentes de listas basadas en registros enlazados


. Por
qu tantas? Porque cada una supone una solucin de compromiso diferente entre veloci
dad
y consumo de memoria.
Empecemos por estudiar el consumo de memoria. Supongamos que una variable de
l
tipo del campo info ocupa m bytes, que cada puntero ocupa 4 bytes y que la lista
consta
Andrs Marzal/Isabel

Introduccin

Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2

341
341
ramacin con C - UJI

Introduccin a la prog

c
UJI

de n elementos. Esta tabla muestra la ocupacin en bytes segn la estructura de dato


s
escogida: lista con enlace simple (simple), lista con enlace simple y puntero a ca
beza
y cola (simple cabeza/cola), lista con enlace doble (doble), lista con enlace doble
y
puntero a cabeza y cola (doble cabeza/cola).
memoria (b
ytes)
simple
4 + n (4
+ m)
simple cabeza/cola
8 + n (4
+ m)
doble
4 + n (8
+ m)
doble cabeza/cola
8 + n (8
+ m)
Esta otra tabla resume el tiempo que requieren algunas operaciones sobre los
cuatro
tipos de lista:
doble
doble cabeza/cola
Insertar por cabeza
constante
constante
Borrar cabeza
constante
constante
Insertar por cola
lineal
constante
Borrar cola
lineal
constante
Buscar un nodo concreto
lineal
lineal
Invertir la lista
lineal
lineal

simple

simple cabeza/cola

constante

constante

constante

constante

lineal

constante

lineal

lineal

lineal

lineal

cuadrtico

cuadrtico

Hemos indicado con la palabra constante que se requiere una cantidad de tiemp
o
fija, independiente de la longitud de la lista; con la palabra lineal, que se requ
iere un
tiempo que es proporcional a la longitud de la lista; y con cuadrtico, que el coste
crece
con el cuadrado del nmero de elementos.
Para que te hagas una idea: insertar por cabeza un nodo en una lista cuesta
siempre
la misma cantidad de tiempo, tenga la lista 100 o 1000 nodos. Insertar por la co
la en una
lista simplemente enlazada con puntero a cabeza, sin embargo, es unas 10 veces ms
lento
si la lista es 10 veces ms larga. Esto no ocurre con una lista simplemente enlaza
da que
tenga puntero a cabeza y cola: insertar por la cola en ella siempre cuesta lo mi
smo. Ojo
con los costes cuadrticos! Invertir una lista simplemente enlazada de 1000 elemen
tos es
100 veces ms costoso que invertir una lista con 10 veces menos elementos.
En la tabla hemos marcado algunos costes con un asterisco. Son costes para
el peor
de los casos. Buscar un nodo concreto en una lista obliga a recorrer todos los n

odos
slo si el que buscamos no est o si ocupa la ltima posicin. En el mejor de los casos,
el coste temporal es constante: ello ocurre cuando el nodo buscado se encuentra
en la
lista y, adems, ocupa la primera posicin. De los anlisis de coste nos ocuparemos ms
adelante.
Un anlisis de la tabla de tiempos permite concluir que la lista doblemente e
nlazada
con punteros a cabeza y cola es siempre igual o mejor que las otras estructuras.
Debemos
escogerla siempre? No, por tres razones:
1.
Aunque la lista ms compleja requiere tiempo constante en muchas operacio
nes,
stas son algo ms lentas y sofisticadas que operaciones anlogas en las otra
s
estructuras ms sencillas. Son, por fuerza, algo ms lentas.
2.
El consumo de memoria es mayor en la lista ms compleja (8 bytes adiciona
les para
cada nodo y 8 bytes para los punteros a cabeza y cola, frente a 4 bytes
adicionales
para cada nodo y 4 bytes para un puntero a cabeza en la estructura ms se
ncilla),
as que puede no compensar la ganancia en velocidad o, sencillamente, es
posible
que no podamos permitirnos el lujo de gastar el doble de memoria extra.
3.

Puede que nuestra aplicacin slo efecte operaciones baratas sobre cualquier
lista. Imagina que necesitas una lista en la que siempre insertas y eli
minas nodos
por cabeza, jams por el final. Las cuatro estructuras ofrecen tiempo con
stante para
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

342
342

Introduccin a la programacin con C -UJI


cUJI

esas dos operaciones, slo que, adems, las dos primeras son mucho ms senci
llas
y consumen menos memoria que las dos ltimas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
301 Rellena una tabla similar a la anterior para estas otras operaciones:
a) Insertar ordenadamente en una lista ordenada.
b) Insertar en una posicin concreta.
c) Buscar un elemento en una lista ordenada.
d) Buscar el elemento de valor mnimo en una lista ordenada.
e) Buscar el elemento de valor mximo en una lista ordenada.
f) Unir dos listas ordenadas de modo que el resultado est ordenado.
g) Mostrar el contenido de una lista por pantalla.
h) Mostrar el contenido de una lista en orden inverso por pantalla.
302 Vamos a montar una pila con listas. La pila es una estructura de datos en la
que
slo podemos efectuar las siguientes operaciones:
insertar un elemento en la cima,
eliminar el elemento de la cima,
consultar el valor del elemento de la cima.
Qu tipo de lista te parece ms adecuado para implementar una pila? Por qu?
303 Vamos a montar una cola con listas. La cola es una estructura de datos en la
que slo podemos efectuar las siguientes operaciones:
insertar un elemento al final de la cola,
eliminar el elemento que hay al principio de la cola,
consultar el valor del elemento que hay al principio de la cola.
Qu tipo de lista te parece ms adecuado para construir una cola? Por qu?
...............................................................................
...

En este apartado vamos a desarrollar una aplicacin prctica que usa listas: un prog
rama
para la gestin de una coleccin de discos compactos. Cada disco compacto contendr
un ttulo, un intrprete, un ao de edicin y una lista de canciones. De cada cancin nos
interesar nicamente el ttulo.
Las acciones del programa, que se presentarn al usuario con un men, son stas.
1.

Aadir un disco.

2.

Buscar discos por ttulo.

3.

Buscar discos por intrprete.

4.

Buscar discos por ttulo de cancin.

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
343
343
ramacin con C - UJI

Introduccin a la prog

c
UJI

5.

Mostrar el contenido completo de la coleccin.

6.
prete.

Eliminar un disco de la base de datos dados su ttulo y el nombre del intr

7.
Finalizar.
A priori no sabemos cuntas canciones hay en un disco, ni cuntos discos hay que
almacenar en la base de datos, as que utilizaremos listas para ambas entidades. N
uestra
coleccin ser, pues, una lista de discos que, a su vez, contienen listas de cancion
es.
No slo eso: no queremos que nuestra aplicacin desperdicie memoria con cadenas que
consumen ms memoria que la necesaria, as que usaremos memoria dinmica tambin
para la reserva de memoria para cadenas.
Lo mejor es dividir el problema en estructuras de datos claramente diferenci
adas (una
para la lista de discos y otra para la lista de canciones) y disear funciones par
a manejar
cada una de ellas. Atencin al montaje que vamos a presentar, pues es el ms complic
ado
de cuantos hemos estudiado.
struct Cancion
char titulo
struct Cancion
sig
typedef struct Cancion

TipoListaCanciones

struct Disco
char titulo
char interprete
int anyo
TipoListaCanciones canciones
struct Disco sig
typedef struct Disco

TipoColeccion

Hemos optado por listas simplemente enlazadas y con puntero a cabeza.


Aqu tienes una representacin grfica de una coleccin con 3 discos compactos:
0
1
2
3
4 5 6 7
8
9
10
0
1
2
3
4
5
6
7
8
9
10
11
12 13
E x p r e s s i o n \0
J o h n
C o l t r a n e \0
0
8

10

14

11

15

T a n g e r i n e
D r e a m \0
0

6
12

7
13

L o g o s \0
I g n a c i o \0
0

V a n g e l i s \0
titulo
titulo
titulo

sig

sig
sig
interprete

interprete
nterprete

coleccion
anyo

anyo

anyo
1972
1982
1977
canciones
canciones
anciones

titulo
titulo

titulo

sig

titulo

sig

sig
sig

titulo

sig

0
0
5

titulo

sig

7
L o g o s \0
I g n a c i o \0
0

2
1

3
2

4
3

1
0

8
titulo

sig

O g u
D o m

n d e \0
i n i o n \0

0
2
4

3
5

8
titulo

sig
T o

b e \0

O f f e r i n g \0
0

2
6

3
0

5
1

7
2

3
9

10
4

10
E x p

r e s s i o n \0
N u m b e r
o n e \0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
344
344
Introduccin a la programacin con C -UJ
I

cUJI

Empezaremos por disear la estructura que corresponde a una lista de canciones


.
Despus nos ocuparemos del diseo de registros del tipo disco compacto. Y acabaremos
definiendo un tipo coleccin de discos compactos.
Vamos a disear funciones para gestionar listas de canciones. Lo que no vamos
a
hacer es montar toda posible operacin sobre una lista. Slo invertiremos esfuerzo e
n las
operaciones que se van a utilizar. stas son:
Crear una lista vaca.
Aadir una cancin a la lista. (Durante la creacin de un disco iremos pidie
ndo las
canciones y aadindolas a la ficha del disco.)
Mostrar la lista de canciones por pantalla. (Esta funcin se usar cuando
se muestre
una ficha detallada de un disco.)
Buscar una cancin y decir si est o no est en la lista.
Borrar todas las canciones de la lista. (Cuando se elimine un disco de
la base de
datos tendremos que liberar la memoria ocupada por todas sus canciones
.)
La funcin de creacin de una lista de canciones es trivial:
TipoListaCanciones crea lista canciones void
return
Pasemos a la funcin que aade una cancin a una lista de canciones. No nos indican
que las canciones deban almacenarse en un orden determinado, as que recurriremos
al
mtodo ms sencillo: la insercin por cabeza.
TipoListaCanciones anyade cancion TipoListaCanciones lista char titulo
struct Cancion

nuevo

nuevo titulo malloc strlen titulo


sizeof char
strcpy nuevo titulo titulo
nuevo sig lista
lista nuevo
return lista

malloc sizeof struct Cancion


1

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
304 La verdad es que insertar las canciones por la cabeza es el mtodo menos
indicado, pues cuando se recorra la lista para mostrarlas por pantalla aparecern
en
orden inverso a aqul con el que fueron introducidas. Modifica anyade cancion para
que
las canciones se inserten por la cola.
305 Y ya que sugerimos que insertes canciones por cola, modifica las estructuras
necesarias para que la lista de canciones se gestione con una lista de registros
con
puntero a cabeza y cola.
...............................................................................
...
Mostrar la lista de canciones es muy sencillo:
void muestra canciones TipoListaCanciones lista

struct Cancion
for aux lista aux
printf
aux

aux
aux aux
titulo

sig

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
345
345
ramacin con C - UJI

Introduccin a la prog

c
UJI

Buscar una cancin es un simple recorrido que puede terminar anticipadamente ta


n
pronto se encuentra el objeto buscado:
int contiene cancion con titulo TipoListaCanciones lista char titulo
struct Cancion

aux

for aux lista aux


aux aux
if strcmp aux titulo titulo 0
return 1
return 0

sig

Borrar todas las canciones de una lista debe liberar la memoria propia de ca
da nodo,
pero tambin debe liberar la cadena que almacena cada ttulo, pues tambin se solicit
con malloc:
TipoListaCanciones libera canciones TipoListaCanciones lista
struct Cancion

aux

siguiente

aux lista
while aux
siguiente aux sig
free aux titulo
free aux
aux siguiente
return
No ha sido tan difcil. Una vez sabemos manejar listas, las aplicaciones prctic
as
se disean reutilizando buena parte de las rutinas que hemos presentado en apartad
os
anteriores.
Pasamos a encargarnos de las funciones que gestionan la lista de discos. Com
o es
habitual, empezamos con una funcin que crea una coleccin (una lista) vaca:
TipoColeccion crea coleccion void
return
Aadir un disco obliga a solicitar memoria tanto para el registro en s como par
a
algunos de sus componentes: el ttulo y el intrprete:
TipoColeccion anyade disco TipoColeccion lista char titulo char interprete
int anyo TipoListaCanciones canciones
struct Disco

disco

disco malloc sizeof struct Disco


disco titulo malloc strlen titulo 1 sizeof char
strcpy disco titulo titulo
disco interprete malloc strlen interprete 1 sizeof char
strcpy disco interprete interprete
disco anyo anyo
disco canciones canciones
disco sig lista

lista disco
return lista
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

346
346

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
306 Modifica anyade disco para que los discos estn siempre ordenados alfabticamente por intrprete y, para cada intrprete, por valor creciente del ao de edicin.
................................................................................
..
Y la memoria solicitada debe liberarse ntegramente: si al reservar memoria pa
ra un
disco ejecutamos tres llamadas a malloc, habr que efectuar tres llamadas a free:
TipoColeccion libera coleccion TipoColeccion lista
struct Disco

aux

siguiente

aux lista
while aux
siguiente aux sig
free aux titulo
free aux interprete
aux canciones libera canciones aux
canciones
free aux
aux siguiente
return
Mostrar por pantalla el contenido de un disco es sencillo, especialmente si u
samos
muestra canciones para mostrar la lista de canciones.
void muestra disco struct Disco eldisco
printf
eldisco titulo
printf
eldisco interprete
printf
eldisco anyo
printf
muestra canciones eldisco canciones
Mostrar la coleccin completa es trivial si usamos la funcin que muestra un d
isco:
void muestra coleccion TipoColeccion lista
struct Disco

aux

for aux lista aux


muestra disco aux

aux aux

sig

Las funciones de bsqueda de discos se usan en un contexto determinado: el de


mostrar, si se encuentra el disco, su contenido por pantalla. En lugar de hacer
que la
funcin devuelva el valor 1 o 0, podemos hacer que devuelva un puntero al registro
cuando
lo encuentre o
cuando el disco no est en la base de datos. Aqu tienes las fu
nciones
de bsqueda por ttulo y por intrprete:
struct Disco
busca disco por titulo disco TipoColeccion coleccio
n char titulo
struct Disco

aux

for aux coleccion aux

aux aux

sig
if strcmp aux titulo titulo
return aux
return

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
347
347
amacin con C - UJI

Introduccin a la progr

c
UJI

struct Disco
ar interprete

busca disco por interprete TipoColeccion coleccion ch

struct Disco

aux

for aux coleccion aux


aux aux
if strcmp aux interprete interprete
return aux
return

sig
0

La funcin de bsqueda por ttulo de cancin es similar, slo que llama a la funcin que
busca una cancin en una lista de canciones:
struct Disco
busca disco por titulo cancion TipoColeccion coleccio
n char titulo
struct Disco

aux

for aux coleccion aux


aux aux sig
if contiene cancion con titulo aux canciones titulo
return aux
return
Slo nos queda por definir la funcin que elimina un disco de la coleccin dado s
u
ttulo:
TipoColeccion borra disco por titulo e interprete TipoColeccion coleccion c
har titulo
char interprete
struct Disco aux

atras

for atras
aux coleccion aux
atras aux aux aux sig
if strcmp aux titulo titulo
0 strcmp aux interprete interprete
0
if atras
coleccion aux sig
else
atras sig aux sig
free aux titulo
free aux interprete
aux canciones libera canciones aux canciones
free aux
return coleccion
return coleccion
Ya tenemos todas las herramientas para enfrentarnos al programa principal:
include
include
include
include
define
enum

1000
Anyadir 1 BuscarPorTituloDisco BuscarPorInterprete BuscarPorTit

uloCancion
Mostrar EliminarDisco Salir

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

348
348

int main void


int opcion
TipoColeccion coleccion
char titulo disco
1 titulo cancion
interprete 1
char linea
1
int anyo
struct Disco undisco
TipoListaCanciones lista canciones
coleccion

crea coleccion

do
printf
printf
printf
printf
printf
printf
printf
printf
printf

gets linea

sscanf linea

opcion
switch opcion
case Anyadir
printf
gets titulo disco
printf
gets interprete
printf
gets linea sscanf linea
anyo
lista canciones crea lista canciones
do
printf
gets titulo cancion
if strlen titulo cancion
0
lista canciones anyade cancion lista canciones titulo c
ancion
while strlen titulo cancion
0
coleccion anyade disco coleccion titulo disco
interprete anyo lista canciones
break
case BuscarPorTituloDisco
printf
gets titulo disco
undisco busca disco por titulo disco coleccion titulo disco
if undisco
muestra disco undisco
else
printf
titulo disco
break
case BuscarPorInterprete
printf
gets interprete
undisco busca disco por interprete coleccion interprete
if undisco
muestra disco undisco
else
printf
interprete
break

case BuscarPorTituloCancion
Introduccin a la programacin con C
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
cUJI
Introduccin a la programacin con C -UJI

349
349

printf
gets titulo cancion
undisco busca disco por titulo cancion coleccion titulo can
cion
if undisco
muestra disco undisco
else
printf
titulo cancion
break
case Mostrar
muestra coleccion coleccion
break
case EliminarDisco
printf
gets titulo disco
printf
gets interprete
coleccion borra disco por titulo e interprete coleccion titul
o disco
interprete
break
while opcion
coleccion

Salir
libera coleccion coleccion

return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
307 Modifica el programa para que se almacene la duracin de cada cancin (en
segundos) junto al ttulo de la misma.
308 La funcin de bsqueda de discos por intrprete se detiene al encontrar el primer
disco de un intrprete dado. Modifica la funcin para que devuelva una lista con una
copia
de todos los discos de un intrprete. Usa esa lista para mostrar su contenido por
pantalla
con muestra coleccion y elimnala una vez hayas mostrado su contenido.
309 Disea una aplicacin para la gestin de libros de una biblioteca. Debes mantener dos listas: una lista de libros y otra de socios. De cada socio recordamos e
l nombre,
el DNI y el telfono. De cada libro mantenemos los siguientes datos: ttulo, autor,
ISBN,
cdigo de la biblioteca (una cadena con 10 caracteres) y estado. El estado es un p
untero
que, cuando vale
, indica que el libro est disponible y, en caso contrario
, apunta al
socio al que se ha prestado el libro.
El programa debe permitir dar de alta y baja libros y socios, as como efectua
r el
prstamo de un libro a un socio y gestionar su devolucin. Ten en cuenta que no es
posible dar de baja a un socio que posee un libro en prstamo ni dar de baja un li
bro
prestado.
...............................................................................
...

La posibilidad de trabajar con registros enlazados abre las puertas al diseo de e


structuras de datos muy elaboradas que permiten efectuar ciertas operaciones muy eficie
ntemente. El precio a pagar es una mayor complejidad de nuestros programas C y, posibl
emente,
un mayor consumo de memoria (estamos almacenando valores y punteros, aunque slo
nos interesan los valores).
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
350
350
ramacin con C - UJI

Introduccin a la prog

c
UJI

Pero no has visto ms que el principio. En otras asignaturas de la carrera apr


enders
a utilizar estructuras de datos complejas, pero capaces de ofrecer tiempos de re
spuesta
mucho mejores que las listas que hemos estudiado o capaces de permitir implement
aciones
sencillas para operaciones que an no hemos estudiado. Te vamos a presentar unos p
ocos
ejemplos ilustrativos.
Las listas circulares, por ejemplo, son listas sin final. El nodo sigui
ente al que
parece el ltimo nodo es el primero. Ningn nodo est ligado a
.
info
sig

info
lista
2

sig

info

sig
3

Este tipo de estructura de datos es til, por ejemplo, para mantener una
lista de
tareas a las que hay que ir dedicando atencin rotativamente: cuando hemo
s hecho
una ronda, queremos pasar nuevamente al primer elemento. El campo sig d
el ltimo
elemento permite pasar directamente al primero, con lo que resulta senc
illo codificar
un bucle que recorre rotativamente la lista.
En muchas aplicaciones es preciso trabajar con matrices dispersas. Una
matriz dispersa es una matriz en la que muy pocos componentes presentan un valor
diferente
de cero. Esta matriz, por ejemplo, es dispersa:
0 0 2.5 0 0 1.2 0 0 0 0
0 0
0 0 0 0 0 0 0 0
0 3.7 0 0 0 0 0 0 0 0
0 0

0 0 0 0 0 0 0 0

0 0

0 0 0 0 0 0 0 0

0 1.3 8.1 0 0 0 0 0.2 0 0


0 0

0 0 0 0 0 0 0 0

0 0

0 0 0 0 0 0 0 0

0 0
0 0

0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

De los 100 componentes de esta matriz de 10 10, tan slo hay 6 no nulos.
Las
matrices dispersas pueden representarse con listas de listas para ahorr
ar memoria.
Una lista mantiene las filas que, a su vez, son listas de valores no nu
los. En estas
ltimas listas, cada nodo almacena la columna del valor no nulo y el prop

io valor. La
matriz dispersa del ejemplo se representara as (suponiendo que filas y co
lumnas
empiezan numerndose en 1, como es habitual en matemticas):
matriz
columna

sig

columna

sig
sig

fila
1

sig

sig

fila
3

columna

cols

3
valor
2.5

6
valor
1.2
sig

cols

columna
2
valor
3.7
columna

sig

columna

sig
2

8
sig
fila
valor
6
0.2

cols

valor
1.3

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
1
351

valor
8.1

35

Introduccin a la programacin con C -UJI


c

UJI

El ahorro de memoria es notabilsimo: si un float ocupa 8 bytes, hemos pa


sado
de 800 a 132 bytes consumidos. El ahorro es relativamente mayor cuanto
mayor
es la matriz. Eso s, la complejidad de los algoritmos que manipulan esa
estructura es tambin notabilsima. Imagina el procedimiento que permite multiplic
ar
eficientemente dos matrices dispersas representadas as!
Un rbol binario de bsqueda es una estructura montada con registros enlaza
dos,
pero no es una lista. Cada nodo tiene cero, uno o dos hijos: uno a su i
zquierda y
uno a su derecha. Los nodos que no tienen hijos se llaman hojas. El nod
o ms alto,
del que descienden todos los dems, se llama nodo raz. Los descendientes d
e un
nodo (sus hijos, nietos, biznietos, etc.) tienen una curiosa propiedad:
si descienden
por su izquierda, tienen valores ms pequeos que el de cualquier ancestro,
y si
descienden por su derecha, valores mayores. Aqu tienes un ejemplo de rbol
binario
de bsqueda:
raiz
der

info

izq
10
der
der

info

info

izq

izq
3

15
der
der

info
12

izq

info
der
1

izq
info

der

info

izq

izq
6

23

Una ventaja de los rboles binarios de bsqueda es la rapidez con que puede
n
resolver la pregunta pertenece un valor determinado al conjunto de valore
s del
rbol?. Hay un mtodo recursivo que recibe un puntero a un nodo y dice:
si el puntero vale
; la respuesta es no;
si el valor coincide con el del nodo apuntado, la respuesta es s;
si el valor es menor que el valor del nodo apuntado, entonces la r
espuesta la
conoce el hijo izquierdo, por lo que se le pregunta a l (recursiv
amente);
y si el valor es mayor que el valor del nodo apuntado, entonces la
respuesta
la conoce el hijo derecho, por lo que se le pregunta a l (recursi

vamente).
Ingenioso, no? Observa que muy pocos nodos participan en el clculo de la
respuesta. Si deseas saber, por ejemplo, si el 6 pertenece al rbol de la fi
gura, slo
hay que preguntarle a los nodos que tienen el 10, el 3 y el 6. El resto
de nodos
no se consultan para nada. Siempre es posible responder a una pregunta
de pertenencia en un rbol con n nodos visitando un nmero de nodos que es, a lo
sumo,
igual a 1 + log2 n. Rapidsimo. Qu costar, a cambio, insertar o borrar un no
do
en el rbol? Cabe pensar que mucho ms que un tiempo proporcional al nmero
de nodos, pues la estructura de los enlaces es muy compleja. Pero no es
as. Existen procedimientos sofisticados que consiguen efectuar esas operaciones
en tiempo
proporcional al logaritmo en base 2 del nmero de nodos!
Hay muchas ms estructuras de datos que permiten acelerar sobremanera los programas que gestionan grandes conjuntos de datos. Apenas hemos empezado a conocer
y aprendido a manejar las herramientas con las que se construyen los programas:
las
estructuras de datos y los algoritmos.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
352
352
-UJI

Introduccin a la programacin con C

cUJI

Me temo que s, seora dijo Alicia. No recuerdo las cosas como sola. . .
y no conservo el mismo tamao diez minutos seguidos!
LEWIS CARROLL, Alicia en el Pas de
las Maravillas.
Acabamos nuestra introduccin al lenguaje C con el mismo objeto de estudio con el
que finalizamos la presentacin del lenguaje Python: los ficheros. Los ficheros pe
rmiten
guardar informacin en un dispositivo de almacenamiento de modo que sta sobreviva
a la ejecucin de un programa. No te vendra mal repasar los conceptos introductorio
s a
ficheros antes de empezar.

Con Python estudiamos nicamente ficheros de texto. Con C estudiaremos dos tipos d
e
ficheros: ficheros de texto y ficheros binarios.

Ya conoces los ficheros de texto: contienen datos legibles por una persona y pue
des
generarlos o modificarlos desde tus propios programas o usando aplicaciones como
los
editores de texto. Los ficheros binarios, por contra, no estn pensados para facil
itar su
lectura por parte de seres humanos (al menos no directamente).
Pongamos que se desea guardar un valor de tipo entero en un fichero de text
o, por
ejemplo, el valor 12. En el fichero de texto se almacenar el dgito
(codi
ficado en
ASCII como el valor 49) y el dgito
(codificado en ASCII como el valor 50), e
s decir, dos
datos de tipo char. A la hora de leer el dato, podremos leerlo en cualquier vari
able de tipo
entero con capacidad suficiente para almacenar ese valor (un char, un unsigned c
har, un
int, un unsigned int, etc.). Esto es as porque la lectura de ese dato pasa por un
proceso
de interpretacin relativamente sofisticado: cuando se lee el carcter
, se
memoriza
el valor 1; y cuando se lee el carcter
, se multiplica por 10 el valor memor
izado y se
le suma el valor 2. As se llega al valor 12, que es lo que se almacena en la vari
able en
cuestin. Observa que, codificado como texto, 12 ocupa dos bytes, pero que si se a
lmacena
en una variable de tipo char ocupa 1 y en una variable de tipo int ocupa 4.
Un problema de los ficheros de texto es la necesidad de usar marcas de sepa
racin
entre sus diferentes elementos. Si, por ejemplo, al valor 12 ha de sucederle el
valor 100,
no podemos limitarnos a disponer uno a continuacin del otro sin ms, pues el ficher
o
contendra la siguiente secuencia de caracteres:

353
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI

353

1
0

Qu estamos representando exactamente? Un 12 seguido de un 100 o un 1 seguido de


un 2100? Y por qu no un 1210 seguido de un 0 o, sencillamente, el valor 12100, sin
ms?
Las marcas de separacin son caracteres que decide el programador, pero es cor
riente
que se trate de espacios en blanco, tabuladores o saltos de lnea. El valor 12 seg
uido del
valor 100 podra representarse, pues, con cualquiera de estas secuencias de caract
eres:
0

\t

\n

Usar caracteres separadores es fuente, naturalmente, de un coste adicional: un m


ayor
tamao de los ficheros.
Cuando los separadores son espacios en blanco, es frecuente permitir libertad
en
cuanto a su nmero:
1
1

\n

\n

Las herramientas con las que leemos los datos de ficheros de texto saben lidiar
con las
complicaciones que introducen estos separadores blancos repetidos.
Los ficheros de texto cuentan con la ventaja de que se pueden inspeccionar c
on ayuda
de un editor de texto y permiten as, por lo general, deducir el tipo de los difer
entes datos
que lo componen, pues stos resultan legibles.

Los ficheros binarios requieren una mayor precisin en la determinacin de la codifi


cacin
de la informacin. Si almacenamos el valor 12 en un fichero binario, hemos de deci
dir si
queremos almacenarlo como carcter con o sin signo, como entero con o sin signo, e
tc.
La decisin adoptada determinar la ocupacin de la informacin (uno o cuatro bytes) y
su codificacin (binario natural o complemento a dos). Si guardamos el 12 como un
char,
guardaremos un solo byte formado por estos 8 bits:

Pero si optamos por almacenarlo como un int, sern cuatro los bytes escritos:

Un mismo patrn de 8 bits, como

tiene dos interpretaciones posibles: el valor 255 si entendemos que es un dato d


e tipo
unsigned char o el valor 1 si consideramos ue codifica un dato de tipo char.1
1
Un fichero de texto no presentara esta ambigedad: el nmero se habra escrito com
o 1 o como 255.
S ue presentara, sin embargo, un punto de eleccin reservado al programador: aunue
1 lleva signo y
por tanto se almacenar en una variable de algn tipo con signo, ueremos almacenarlo
en una variable de
tipo char, una variable de tipo int o, por u no, en una variable de tipo float?

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
354
354

Introduccin a la programacin con C UJI


cUJI

Como puedes ver, la secuencia de bits ue escribimos en el fichero es exacta


mente la
misma ue hay almacenada en la memoria, usando la mismsima codificacin binaria. De
ah el nombre de ficheros binarios.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
310 Qu ocupa en un fichero de texto cada uno de estos datos?
a) 1

d)
g)

15

32768

b) 0

e) 128
h) 2147483647

c) 12

f) 32767
i)

2147483648

Y cunto ocupa cada uno de ellos si los almacenamos en un fichero binario como
valores de tipo int?
311 Cmo se interpreta esta secuencia de bytes en cada uno de los siguientes
supuestos?

a) Como cuatro datos de tipo char.


b) Como cuatro datos de tipo unsigned char.
c) Como un dato de tipo int.
d) Como un dato de tipo unsigned int.
...............................................................................
...
Escribir dos o ms datos de un mismo tipo en un fichero binario no requiere la
insercin de marcas separadoras: cada cierto nmero de bytes empieza un nuevo dato
(cada cuatro bytes, por ejemplo, empieza un nuevo int), as que es fcil decidir dnde
empieza y acaba cada dato.
La lectura de un fichero binario requiere un conocimiento exacto del tipo de
datos
de cada uno de los valores almacenados en l, pues de lo contrario la secuencia de
bits
carecer de un significado definido.
Los ficheros binarios no slo pueden almacenar escalares. Puedes almacenar tam
bin
registros y vectores pues, a fin de cuentas, no son ms que patrones de bits de ta
mao
conocido. Lo nico que no debe almacenarse en ficheros binarios son los punteros.
La
razn es sencilla: si un puntero apunta a una zona de memoria reservada con malloc
, su
valor es la direccin del primer byte de esa zona. Si guardamos ese valor en disco
y lo
recuperamos ms tarde (en una ejecucin posterior, por ejemplo), esa zona puede que
no
haya sido reservada. Acceder a ella provocar, en consecuencia, un error capaz de
abortar
la ejecucin del programa.
Por regla general, los ficheros binarios son ms compactos que los ficheros de
texto,
pues cada valor ocupa lo mismo que ocupara en memoria. La lectura (y escritura) d
e
los datos de ficheros binarios es tambin ms rpida, ya que nos ahorramos el proceso
de conversin del formato de texto al de representacin de informacin en memoria y

viceversa. Pero no todo son ventajas.

Los ficheros de texto se manipulan en C siguiendo el mismo protocolo que seguamos


en Python:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
355
355
ramacin con C - UJI

Introduccin a la prog

c
UJI

Portabilidad de ficheros
Los ficheros binarios presentan algunos problemas de portabilidad, pues
no todos los
ordenadores almacenan en memoria los valores numricos de la misma forma:
los ficheros
binarios escritos en un ordenador big-endian no son directamente legibles
en un
ordenador little-endian.
Los ficheros de texto son, en principio, ms portables, pues la tabla
ASCII es un
estndar ampliamente aceptado para el intercambio de ficheros de texto. No
obstante,
la tabla ASCII es un cdigo de 7 bits que slo da cobertura a los smbolos pro
pios de
la escritura del ingls y algunos caracteres especiales. Los caracteres ac
entuados, por
ejemplo, estn excluidos. En los ltimos aos se ha intentado implantar una fa
milia de
estndares que den cobertura a estos y otros caracteres. Como 8 bits resul
tan insuficientes para codificar todos los caracteres usados en la escritura de cualqu
ier lenguaje, hay
diferentes subconjuntos para cada una de las diferentes comunidades cult
urales. Las lenguas romnicas occidentales usan el estndar IsoLatin-1 (o ISO-8859-1), reci
entemente
ampliado con el smbolo del euro para dar lugar al IsoLatin-15 (o ISO-8859
-15). Los
problemas de portabilidad surgen cuando interpretamos un fichero de text
o codificado
con IsoLatin-1 como si estuviera codificado con otro estndar: no veremos
ms que un
galimatas de smbolos extraos all donde se usan caracteres no ASCII.

1.
Se abre el fichero en modo lectura, escritura, adicin, o cualquier otro
modo vlido.
2.
Se trabaja con l leyendo o escribiendo datos, segn el modo de apertura es
cogido.
Al abrir un fichero se dispone un cabezal de lectura o escritura en un pu
nto
definido del fichero (el principio o el final). Cada accin de lectura o
escritura
desplaza el cabezal de izquierda a derecha, es decir, de principio a fi
nal del fichero.
3.

Se cierra el fichero.

Bueno, lo cierto es que, como siempre en C, hay un paso adicional y previo a est
os
tres: la declaracin de una variable de tipo fichero. La cabecera
incluye
la
definicin de un tipo de datos llamado FILE y declara los prototipos de las funcio
nes de
manipulacin de ficheros. Nuestra variable de tipo fichero ha de ser un puntero a
FILE,
es decir, ha de ser de tipo FILE .
Las funciones bsicas con las que vamos a trabajar son:

fopen: abre un fichero. Recibe la ruta de un fichero (una cadena) y el


modo de
apertura (otra cadena) y devuelve un objeto de tipo FILE .
FILE

fopen char ruta

char modo

Los modos de apertura para ficheros de texto con los que trabajaremos s
on stos:

(lectura): El primer carcter ledo es el primero del ficher

o.

(escritura): Trunca el fichero a longitud 0. Si el fiche


ro no existe, se crea.

(adicin): Es un modo de escritura que preserva el contenido


original del
fichero. Los caracteres escritos se aaden al final del fichero.
Si el fichero no puede abrirse por cualquier razn, fopen devuelve el val
or .
(Observa que los modos se indican con cadenas, no con caracteres: debes
usar
comillas dobles.)
fclose: cierra un fichero. Recibe el FILE
to por una llamada previa a fopen.
int fclose FILE

devuel

fichero

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

356
356

Introduccin a la programacin con C -UJI


cUJI

Modos de apertura para lectura y escritura simultnea


Los modos
,
y
no son los nicos vlidos para los ficheros de t
exto. Puedes
usar, adems, stos otros:
,
y
. Todos ellos abren los fiche
ros en modo
de lectura y escritura a la vez. Hay, no obstante, matices que los difere
ncian:

: No se borra el contenido del fichero, que debe existir pre


viamente. El
cabezal de lectura/escritura se sita al principio del fichero.

: Si el fichero no existe, se crea, y si existe, se trunca


el contenido a longitud
cero. El cabezal de lectura/escritura se sita al principio del fic
hero.

: Si el fichero no existe, se crea. El cabezal de lectura/esc


ritura se sita
al final del fichero.
Una cosa es que existan estos mtodos y otra que te recomendemos su uso
. Te lo
desaconsejamos. Resulta muy difcil escribir en medio de un fichero de text
o a voluntad
sin destruir la informacin previamente existente en l, pues cada lnea puede
ocupar
un nmero de caracteres diferente.

El valor devuelto por fclose es un cdigo de error que nos advierte de si


hubo un fallo
al cerrar el fichero. El valor 0 indica xito y el valor
(predefinido
en
)
indica error. Ms adelante indicaremos cmo obtener informacin adicional ace
rca
del error detectado.
Cada apertura de un fichero con fopen debe ir acompaada de una llamada a
fclose
una vez se ha terminado de trabajar con el fichero.
fscanf : lee de un fichero. Recibe un fichero abierto con fopen (un FIL
E ), una
cadena de formato (usando las marcas de formato que ya conoces por scan
f ) y las
direcciones de memoria en las que debe depositar los valores ledos. La f
uncin
devuelve el nmero de elementos efectivamente ledos (valor que puedes usar
para
comprobar si la lectura se complet con xito).
int fscanf

FILE

fichero char formato

direccion

es
fprintf : escribe en un fichero. Recibe un fichero abierto con fopen (u
n FILE ), una
cadena de formato (donde puedes usar las marcas de formato que aprendis
te a
usar con printf ) y los valores que deseamos escribir. La funcin devuelv
e el nmero
de caracteres efectivamente escritos (valor que puedes usar para compro
bar si se

escribieron correctamente los datos).


int fprintf

FILE

fichero char formato

valores

feof : devuelve 1 si estamos al final del fichero y 0 en caso contrario


. El nombre de
la funcin es abreviatura de end of file (en espaol, fin de fichero). Ojo! Sl
tiene sentido consultar si se est o no al final de fichero tras efectuar
una lectura
de datos. (Este detalle complicar un poco las cosas.)
int feof

FILE

fichero

Como puedes ver no va a resultar muy difcil trabajar con ficheros de texto en
C. A
fin de cuentas, las funciones de escritura y lectura son bsicamente idnticas a pri
ntf y
scanf , y ya hemos aprendido a usarlas. La nica novedad destacable es la nueva fo
rma
de detectar si hemos llegado al final de un fichero o no: ya no se devuelve la c
adena
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

357
357

Introduccin a la programacin con C -UJI


cUJI

vaca como consecuencia de una lectura al final del fichero, como ocurra en Python,
sino
que hemos de preguntar explcitamente por esa circunstancia usando una funcin (feof
).
Nada mejor que un ejemplo para aprender a utilizar ficheros de texto en C. V
amos a
generar los 1000 primeros nmeros primos y a guardarlos en un fichero de texto. Ca
da
nmero se escribir en una lnea.
include
int es primo int n
int i
primo
for j
if

j primo
1
2 j n 2 j
n j
0
primo 0
break

return primo
int main void
FILE fp
int i n
fp fopen
i 1
n 0
while n 1000
if es primo i
fprintf fp
n

i
fclose fp
return 0
Hemos llamado a la variable de fichero fp por ser abreviatura del trmino file
pointer
(puntero a fichero). Es frecuente utilizar ese nombre para las variables de tipo
FILE .
Una vez compilado y ejecutado el programa
obtenemos un f
ichero de
texto llamado
del que te mostramos sus primeras y ltimas lneas (pue
des
comprobar la correccin del programa abriendo el fichero
con un e
ditor de
texto):

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

358
358

Aunque en pantalla lo vemos como una secuencia de lneas, no es ms que una secuenci
a
de caracteres:
1
1

\n
\n 7

\n

3
\n

\n

\n

...

Diseemos ahora un programa que lea el fichero


generado por el programa anterior y muestre por pantalla su contenido:
include
int main void
FILE
int i

fp

fp fopen
fscanf fp
i
while
feof fp
printf
i
fscanf fp
i
fclose fp
return 0
Observa que la llamada a fscanf se encuentra en un bucle que se lee as mientra
s no
se haya acabado el fichero. . . , pues feof averigua si hemos llegado al final de
l fichero.
La lnea 9 contiene una lectura de datos para que la consulta a feof tenga sentido
: feof
slo actualiza su valor tras efectuar una operacin de lectura del fichero. Si no te
gusta
la aparicin de dos sentencias fscanf , puedes optar por esta alternativa:
include
int main void
FILE
int i
fp fopen
while 1
fscanf fp

fp

Introduccin a la programacin
Andrs Marzal/Isabel
con- C
Gracia
ISBN: 978-84-693-0143-2

359
359
c
UJI

Introduccin a la programacin con C - UJI

if feof fp
printf

break
i

fclose fp
return 0
Y si deseas evitar el uso de break, considera esta otra:
include
int main void
FILE
int i

fp

fp fopen
do
fscanf fp
if feof fp
printf
while feof fp
fclose fp

i
i

return 0

Y si el fichero no existe?
Al abrir un fichero puede que detectes un error: fopen devuelve la direcc
in
. Hay
varias razones, pero una que te ocurrir al probar algunos de los programas
del texto es
que el fichero que se pretende leer no existe. Una solucin puede consistir
en crearlo
en ese mismo instante:
f fopen ruta
if f
f fopen ruta
fclose f
f fopen ruta
Si el problema era la inexistencia del fichero, este truco funcionar, pues
el modo
lo crea cuando no existe.
Es posible, no obstante, que incluso este mtodo falle. En tal caso, es
probable que
tengas un problema de permisos: tienes permiso para leer ese fichero?, tien
es permiso
para escribir en el directorio en el que reside o debe residir el fichero
? Ms adelante
prestaremos atencin a esta cuestin.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
312 Disea un programa que aada al fichero
los 100 siguientes nmeros
primos. El programa leer el contenido actual del fichero para averiguar cul es el l
timo
primo del fichero. A continuacin, abrir el fichero en modo adicin (
) y aadir 100
nuevos primos. Si ejecutsemos una vez
y, a continuacin, dos veces el
nuevo programa, el fichero acabara conteniendo los 1200 primeros primos.
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
360
360
ramacin con C - UJI

Introduccin a la prog

c
UJI

313 Disea un programa que lea de teclado una frase y escriba un fichero de texto
llamado
en el que cada palabra de la frase ocupa una lnea.
314 Disea un programa que lea de teclado una frase y escriba un fichero de texto
llamado
en el que cada lnea contenga un carcter de la frase.
315 Modifica el programa miniGalaxis para que gestione una lista de records. Un
fichero de texto, llamado
almacenar el nombre y nmero de
movimientos de los 5 mejores jugadores de todos los tiempos (los que completaron
el
juego usando el menor nmero de sondas).
316 Disponemos de dos ficheros: uno contiene un diccionario y el otro, un texto.
El
diccionario est ordenado alfabticamente y contiene una palabra en cada lnea. Disea
un programa que lea el diccionario en un vector de cadenas y lo utilice para det
ectar
errores en el texto. El programa mostrar por pantalla las palabras del texto que
no estn
en el diccionario, indicando los nmeros de lnea en que aparecen.
Supondremos que el diccionario contiene, a lo sumo, 1000 palabras y que la p
alabra
ms larga (tanto en el diccionario como en el texto) ocupa 30 caracteres.
(Si quieres usar un diccionario real como el descrito y trabajas en Unix, en
contrars
uno en ingls en
o
. Puedes averi
guar el
nmero de palabras que contiene con el comando
de Unix.)
317 Modifica el programa del ejercicio anterior para que el nmero de palabras del
vector que las almacena se ajuste automticamente al tamao del diccionario. Tendrs
que usar memoria dinmica.
Si usas un vector de palabras, puedes efectuar dos pasadas de lectura en el
fichero que
contiene el diccionario: una para contar el nmero de palabras y saber as cunta memo
ria
es necesaria y otra para cargar la lista de palabras en un vector dinmico. Natura
lmente,
antes de la segunda lectura debers haber reservado la memoria necesaria.
Una alternativa a leer dos veces el fichero consiste en usar realloc juicios
amente:
reserva inicialmente espacio para, digamos, 1000 palabras; si el diccionario con
tiene un
nmero de palabras mayor que el que cabe en el espacio de memoria reservada, dupli
ca
la capacidad del vector de palabras (cuantas veces sea preciso si el problema se
da ms
de una vez).
Otra posibilidad es usar una lista simplemente enlazada, pues puedes crearla
con
una primera lectura. Sin embargo, no es recomendable que sigas esta estrategia,
pues
no podrs efectuar una bsqueda dicotmica a la hora de determinar si una palabra est
incluida o no en el diccionario.
...............................................................................
...
Ya vimos en su momento que fscanf presenta un problema cuando leemos cadenas
:
slo lee una palabra, es decir, se detiene al llegar a un blanco. Aprendimos a usar
entonces una funcin, gets, que lea una lnea completa. Hay una funcin equivalente
para ficheros de texto:
char
fgets char cadena
int max tam FILE
f
ichero
Ojo con el prototipo de fgets! El parmetro de tipo FILE es el ltimo, no el primero!

Otra incoherencia de C. El primer parmetro es la cadena en la que se desea deposi


tar
el resultado de la lectura. El segundo parmetro, un entero, es una medida de segu
ridad:
es el mximo nmero de bytes que queremos leer en la cadena. Ese lmite permite evitar
peligrosos desbordamientos de la zona de memoria reservada para cadena cuando la
cadena leda es ms larga de lo previsto. El ltimo parmetro es, finalmente, el fichero
del que vamos a leer (previamente se ha abierto con fopen). La funcin se ocupa de
terminar correctamente la cadena leda con un
, pero respetando el salto d
e lnea
( ) si lo hubiera.2 En caso de querer suprimir el retorno de lnea, puedes invocar
una
funcin como sta sobre la cadena leda:
2
En esto se diferencia de gets.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

361
361

void quita fin de linea char linea


int i
for i 0 linea i
if linea i
linea i
break

La funcin fgets devuelve una cadena (un char ). En realidad, es un puntero a


la
propia variable cadena cuando todo va bien, y
cuando no se ha podido ef
ectuar la
lectura. El valor de retorno es til, nicamente, para hacer detectar posibles error
es tras
llamar a la funcin.
Hay ms funciones de la familia get. La funcin fgetc, por ejemplo, lee un carcte
r:
int fgetc FILE
fichero
No te equivoques: devuelve un valor de tipo int, pero es el valor ASCII de un ca
rcter.
Puedes asignar ese valor a un unsigned char, excepto cuando vale
(de end of
file),
que es una constante (cuyo valor es 1) ue indica ue no se pudo leer el carcter
reuerido porue llegamos al final del fichero.
Las funciones fgets y fgetc se complementan con fputs y fputc, ue en lugar
de leer
una cadena o un carcter, escriben una cadena o un carcter en un fichero abierto pa
ra
escritura o adicin. He au sus prototipos:
int fputs char cadena
FILE fichero
int fputc int caracter FILE fichero
Al escribir una cadena con fputs, el terminador
no se escribe en el fichero
. Pero no
te preocupes: fgets lo sabe y lo introduce automticamente en el vector de caractere
s
al leer del fichero.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
318 Hemos escrito este programa para probar nuestra comprensin de fgets y fputs
(presta atencin tambin a los blancos, que se muestran con el carcter ):
include
include
define

100

int main void


FILE f
char s
char aux
f fopen
fputs
fputs
fclose f

f
f

f fopen
aux fgets s
printf
aux fgets s
printf
fclose f

f
aux s
f
aux s

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
362
362
ramacin con C - UJI

Introduccin a la prog

c
UJI

return 0
Primera cuestin: Cuntos bytes ocupa el fichero prueba txt?
Al ejecutarlo, obtenemos este resultado en pantalla:

Segunda cuestin: Puedes explicar con detalle qu ha ocurrido? (El texto


es escrito automticamente por printf cuando se le pasa como cadena un puntero a
.)
...............................................................................
...

Lo aprendido nos permite ya disear programas capaces de escribir y leer coleccion


es
de datos en ficheros de texto.
Una agenda
Vamos a desarrollar un pequeo ejemplo centrado en las rutinas de entrada/salida p
ara
la gestin de una agenda montada con una lista simplemente enlazada. En la agenda,
que cargaremos de un fichero de texto, tenemos el nombre, la direccin y el telfono
de
varias personas. Cada entrada en la agenda se representar con tres lneas del fiche
ro
de texto. He aqu un ejemplo de fichero con este formato:

Nuestro programa podr leer en memoria los datos de un fichero como ste y tambin
escribirlos en fichero desde memoria.
Las estructuras de datos que manejaremos en memoria se definen as:
struct Entrada
char nombre
char direccion
char telefono
struct NodoAgenda
struct Entrada datos
struct NodoAgenda sig
typedef struct NodoAgenda

TipoAgenda

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
ntroduccin a la programacin con C - UJI

363
363

UJI
c

Al final del apartado presentamos el programa completo. Centrmonos ahora en l


as
funciones de escritura y lectura del fichero. La rutina de escritura de datos en
un fichero recibir la estructura y el nombre del fichero en el que guardamos la informacin
.
Guardaremos cada entrada de la agenda en tres lneas: una por cada campo.
void escribe agenda TipoAgenda agenda char nombre fichero
struct NodoAgenda
FILE fp

aux

fp fopen nombre fichero


for aux agenda aux
fprintf fp

aux aux sig


aux datos nombre
aux datos direccion
aux datos telefono

fclose fp
La lectura del fichero ser sencilla:
TipoAgenda lee agenda char nombre fichero
TipoAgenda agenda
struct Entrada entrada leida
FILE fp
char nombre
1 direccion
telefono 1
int longitud
agenda

crea agenda

fp fopen nombre fichero


while 1
fgets nombre
fp
if feof fp break
Si se acab el fichero, acabar la lectura.
quita fin de linea nombre
fgets direccion
fp
quita fin de linea direccion
fgets telefono
fp
quita fin de linea telefono
agenda

anyadir entrada agenda nombre direccion telefono

fclose fp
return agenda
La nica cuestin reseable es la purga de saltos de lnea innecesarios.
He aqu el listado completo del programa:
include
include
define

200

enum

Ver 1 Alta Buscar Salir

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

364
364

struct Entrada
char nombre
char direccion
char telefono
struct NodoAgenda
struct Entrada datos
struct NodoAgenda sig
typedef struct NodoAgenda

TipoAgenda

void quita fin de linea char linea


int i
for i 0 linea i
if linea i
linea i
break

void muestra entrada struct NodoAgenda e


Podramos haber pasado e por valor, pero resulta ms eficiente (y no much
o ms
incmodo) hacerlo por referencia: pasamos as slo 4 bytes en lugar de 12.
printf
printf
printf

e
e
e

void libera entrada struct NodoAgenda

datos nombre
datos direccion
datos telefono
e

int i
free
free
free
free

e
e
e
e

datos nombre
datos direccion
datos telefono

TipoAgenda crea agenda void


return
TipoAgenda anyadir entrada TipoAgenda agenda char nombre
char direccion char telefono
struct NodoAgenda

aux

Averiguar si ya tenemos una persona con ese nombre


if buscar entrada por nombre agenda nombre
return agenda
e

Si llegamos aqu, es porque no tenamos registrada a esa persona.


malloc sizeof struct NodoAgenda

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
65
365

Introduccin a la programacin con C -UJI


cUJI

e datos nombre malloc strlen nombre 1 sizeof char


strcpy e datos nombre nombre
e datos direccion malloc strlen direccion 1 sizeof char
strcpy e datos direccion direccion
e datos telefono malloc strlen telefono 1 sizeof char
strcpy e datos telefono telefono
e sig agenda
agenda e
return agenda
void muestra agenda TipoAgenda agenda
struct NodoAgenda

aux

for aux agenda aux


muestra entrada aux
struct NodoAgenda
a char nombre

aux

aux

sig

buscar entrada por nombre TipoAgenda agend

struct NodoAgenda

aux

for aux agenda aux


aux aux
if strcmp aux datos nombre nombre
return aux

sig
0

return
void libera agenda TipoAgenda agenda
struct NodoAgenda

aux

siguiente

aux agenda
while aux
siguiente aux sig
libera entrada aux
aux siguiente

void escribe agenda TipoAgenda agenda char nombre fichero


struct NodoAgenda
FILE fp

aux

fp fopen nombre fichero


for aux agenda aux
fprintf fp
fclose fp
TipoAgenda lee agenda char nombre fichero
TipoAgenda agenda
struct Entrada entrada leida

aux aux sig


aux datos nombre
aux datos direccion
aux datos telefono

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

366
366

FILE fp
char nombre
telefono 1
int longitud
agenda

direccion

crea agenda

fp fopen nombre fichero


while 1
fgets nombre
fp
if feof fp break
Si se acab el fichero, acabar la lectura.
quita fin de linea nombre
fgets direccion
fp
quita fin de linea direccion
fgets telefono
fp
quita fin de linea telefono
agenda

anyadir entrada agenda nombre direccion telefono

fclose fp
return agenda

Programa principal
int main void
TipoAgenda miagenda
struct NodoAgenda encontrada
int opcion
char nombre
1
char direccion
1
char telefono
1
char linea
1
miagenda

lee agenda

do
printf
printf
printf
printf
printf
printf
gets linea

sscanf linea

opcion

switch opcion
case Ver
muestra agenda miagenda
break
case Alta
printf

gets nombre

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

367
367

printf
printf
miagenda

gets direccion
gets telefono
anyadir entrada miagenda nombre direccion

telefono
break
case Buscar
printf
gets nombre
encontrada buscar entrada por nombre miagenda nombre
if encontrada
printf
nombre
else
muestra entrada encontrada
break
while opcion

Salir

escribe agenda miagenda


libera agenda miagenda
return 0

Entrada/salida de fichero para el programa de gestin de una coleccin de discos


Acabamos esta seccin dedicada a los ficheros de texto con una aplicacin prctica. Va
mos
a aadir funcionalidad al programa desarrollado en el apartado 4.11: el programa c
argar
la base de datos tan pronto inicie su ejecucin leyendo un fichero de texto y la gua
rdar
en el mismo fichero, recogiendo los cambios efectuados, al final.
En primer lugar, discutamos brevemente acerca del formato del fichero de tex
to. Podemos almacenar cada dato en una lnea, as:

Pero hay un serio problema: cmo sabe el programa dnde empieza y acaba cada disco?
El programa no puede distinguir entre el ttulo de una cancin, el de un disco o el
nombre
de un intrprete. Podramos marcar cada lnea con un par de caracteres que nos indique
n
qu tipo de informacin mantiene:

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
Introduccin a la programacin con C -UJI
cUJI

368
368

Con
indicamos ttulo de disco; con , intrprete; con , ao; y con , ttulo
de cancin. Pero esta solucin complica las cosas en el programa: no sabemos de qu
tipo es una lnea hasta haber ledo sus dos primeros caracteres. O sea, sabemos que
un
disco ha acabado cuando ya hemos ledo una lnea del siguiente. No es que no se
pueda trabajar as, pero resulta complicado. Como podemos definir libremente el fo
rmato,
optaremos por uno que preceda los ttulos de las canciones por un nmero que indique
cuntas canciones hay:

La lectura de la base de datos es relativamente sencilla:


void quita fin de linea char linea
int i
for i 0 linea i
if linea i
linea i
break

Introduccin
Andrs aMarzal/Isabel
la programacin con -CISBN: 978-84-693-0143-2
Gracia
cUJI
Introduccin a la programacin con C -UJI

369
369

TipoColeccion carga coleccion char nombre fichero


FILE f
char titulo disco
1 titulo cancion
terprete 1
char linea
1
int anyo
int numcanciones
int i
TipoColeccion coleccion
TipoListaCanciones lista canciones

in

coleccion crea coleccion


f fopen nombre fichero
while 1
fgets titulo disco
f
if feof f
break
quita fin de linea titulo disco
fgets interprete
f
quita fin de linea interprete
fgets linea
f sscanf linea
anyo
fgets linea
f sscanf linea
numcanciones
lista canciones crea lista canciones
for i 0 i numcanciones i
fgets titulo cancion
f
quita fin de linea titulo cancion
lista canciones anyade cancion lista canciones titulo cancion
coleccion
nyo lista canciones

anyade disco coleccion titulo disco interprete a

fclose f
return coleccion
Tan slo cabe resear dos cuestiones:
La deteccin del final de fichero se ha de hacer tras una lectura infruc
tuosa, por lo
que la hemos dispuesto tras el primer fgets del bucle.
La lectura de lneas con fgets hace que el salto de lnea est presente, as q
ue hay
que eliminarlo explcitamente.
Al guardar el fichero hemos de asegurarnos de que escribimos la informacin en
el
mismo formato:
void guarda coleccion TipoColeccion coleccion char nombre fichero
struct Disco disco
struct Cancion cancion
int numcanciones
FILE f
f fopen nombre fichero
for disco coleccion disco
disco
fprintf f
disco titulo
fprintf f
disco interprete

disco

sig

fprintf f

disco anyo

Introduccin a la programacin con C


Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
370
370
cUJI
Introduccin a la programacin con C -UJI

numcanciones 0
for cancion disco canciones cancion
cancion
cancion
sig
numcanciones
fprintf f
numcanciones
for cancion
cancion
fprintf f

disco
cancion

canciones cancion
sig
cancion titulo

fclose f
Observa que hemos recorrido dos veces la lista de canciones de cada disco: una p
ara
saber cuntas canciones contiene (y as poder escribir en el fichero esa cantidad) y
otra
para escribir los ttulos de las canciones.
Aqu tienes las modificaciones hechas al programa principal:
include
include
include
include

int main void


int opcion
TipoColeccion coleccion
char titulo disco
1 titulo cancion
1
interprete
char linea
1
int anyo
struct Disco undisco
TipoListaCanciones lista canciones
coleccion

carga coleccion

do
printf
printf
printf
printf
printf
printf
printf
printf
printf
inea

gets linea
opcion

guarda coleccion coleccion


coleccion libera coleccion coleccion
return 0

sscanf l

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
319 La gestin de ficheros mediante su carga previa en memoria puede resultar
problemtica al trabajar con grandes volmenes de informacin. Modifica el programa de
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
371
371
gramacin con C - UJI

Introduccin a la pro

c
UJI

la agenda para que no cargue los datos en memoria. Todas las operaciones (aadir d
atos
y consultar) se efectuarn gestionando directamente ficheros.
320 Modifica el programa propuesto en el ejercicio anterior para que sea posible
borrar entradas de la agenda. (Una posible solucin pasa por trabajar con dos fich
eros,
uno original y uno para copias, de modo que borrar una informacin sea equivalente
a
no escribirla en la copia.)
321 Modifica el programa de la agenda para que se pueda mantener ms de un
telfono asociado a una persona. El formato del fichero pasa a ser el siguiente:
Una lnea que empieza por la letra N contiene el nombre de una persona.
Una lnea que empieza por la letra D contiene la direccin de la persona c
uyo
nombre acaba de aparecer.
Una lnea que empieza por la letra T contiene un nmero de telfono asociado
a
la persona cuyo nombre apareci ms recientemente en el fichero.
Ten en cuenta que no se puede asociar ms de una direccin a una persona (y si eso
ocurre en el fichero, debes notificar la existencia de un error), pero s ms de un
telfono.
Adems, puede haber lneas en blanco (o formadas nicamente por espacios en blanco)
en el fichero. He aqu un ejemplo de fichero con el nuevo formato:

322 En un fichero matriz mat almacenamos los datos de una matriz de enteros con
el siguiente formato:
La primera lnea contiene el nmero de filas y columnas.
Cada una de las restantes lneas contiene tantos enteros (separados por
espacios)
como indica el nmero de columnas. Hay tantas lneas de este estilo como f
ilas
tiene la matriz.
Este ejemplo define una matriz de 3 4 con el formato indicado:

Escribe un programa que lea matriz mat efectuando las reservas de memoria dinm
ica
que corresponda y muestre por pantalla, una vez cerrado el fichero, el contenido
de la
matriz.
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

372
372

323 Modifica el programa del ejercicio anterior para que, si hay menos lneas con
valores de filas que filas declaradas en la primera lnea, se rellene el restante
nmero de
filas con valores nulos.
Aqu tienes un ejemplo de fichero con menos filas que las declaradas:

324 Disea un programa que facilite la gestin de una biblioteca. El programa permitir prestar libros. De cada libro se registrar al menos el ttulo y el autor. En c
ualquier
instante se podr volcar el estado de la biblioteca a un fichero y cargarlo de l.
Conviene que la biblioteca sea una lista de nodos, cada uno de los cuales re
presenta
un libro. Uno de los campos del libro podra ser una cadena con el nombre del pres
tatario.
Si dicho nombre es la cadena vaca, se entender que el libro est disponible.
...............................................................................
...
Permisos Unix
Los ficheros Unix llevan asociados unos permisos con los que es posible d
eterminar
qu usuarios pueden efectuar qu acciones sobre cada fichero. Las acciones so
n: leer,
escribir y ejecutar (esta ltima limitada a ficheros ejecutables, es decir,
resultantes de
una compilacin o que contienen cdigo fuente de un lenguaje interpretado y s
iguen
cierto convenio). Se puede fijar cada permiso para el usuario propietario d
el fichero,
para los usuarios de su mismo grupo o para todos los usuarios del sistema
.
Cuando ejecutamos el comando
con la opcin
, podemos ver l
os permisos
codificados con las letras
y el carcter :

El fichero
tiene permiso de lectura y escritura para el usuario
(caracteres 2 a
4), de slo lectura para los usuarios de su grupo (caracteres 5 a 7) y de sl
o lectura para
el resto de usuarios (caracteres 8 a 10). El fichero
puede ser ledo
, modificado y
ejecutado por el usuario. Los usuarios del mismo grupo pueden leerlo y ej
ecutarlo, pero
no modificar su contenido. El resto de usuarios no puede acceder al fiche
ro.
El comando Unix
permite modificar los permisos de un fiche
ro. Una forma
tradicional de hacerlo es con un nmero octal que codifica los permisos. Aq
u tienes un
ejemplo de uso:

El valor octal 0700 (que en binario es


), por ejemplo,
otorga permisos de
lectura, escritura y ejecucin al propietario del fichero, y elimina cualqu
ier permiso para
el resto de usuarios. De cada 3 bits, el primero fija el permiso de lectu
ra, el segundo el
de escritura y el tercero el de ejecucin. Los 3 primeros bits corresponden
al usuario,
los tres siguientes al grupo y los ltimos 3 al resto. As pues, 0700 equival
e a
en la notacin de
.
Por ejemplo, para que
sea tambin legible y ejecutable por par
te de cualquier
miembro del grupo del propietario puedes usar el valor 0750 (que equivale
a
).

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
Introduccin a la programacin con C - UJI
c
UJI

373
373

Hay tres ficheros de texto predefinidos y ya abiertos cuando se inicia un progra


ma: los
ficheros de consola. En realidad, no son ficheros, sino dispositivos:
stdin (entrada estndar): el teclado;
stdout (salida estndar): la pantalla;
stderr (salida estndar de error): ?
Qu es stderr? En principio es tambin la pantalla, pero podra ser, por ejemplo un
fichero en el que deseamos llevar un cuaderno de bitcora con las anomalas o errore
s
detectados durante la ejecucin del programa.
La funcin printf es una forma abreviada de llamar a fprintf sobre stdout y sc
anf encubre una llamada a fscanf sobre stdin. Por ejemplo, estas dos llamadas son equi
valentes:
printf
f printf stdout
El hecho de que,
lentes a
ficheros nos permite
utar un
programa cuyos datos
s
decidir la fuente de
rama,
por ejemplo, permite

en el fondo, Unix considere al teclado y la pantalla equiva


hacer ciertas cosas curiosas. Por ejemplo, si deseamos ejec
se deben leer de teclado o de fichero, segn convenga, podemo
entrada en el momento de la ejecucin del programa. Este prog
elegir al usuario entre leer de teclado o leer de fichero:

include
int main void
FILE fp
char dedonde 80
int n

nombre 80

printf
gets dedonde
if dedonde 0
printf
gets nombre
fp fopen nombre
else
fp

stdin

fscanf fp

Lee de fichero o teclado.

if fp
stdin
fclose fp
return 0
Existe otra forma de trabajar con fichero o teclado que es ms cmoda para el pr
ogramador: usando la capacidad de redireccin que facilita el intrprete de comandos Uni
x.

La idea consiste en desarrollar el programa considerando slo la lectura por tecla


do y,
cuando iniciamos la ejecucin del programa, redirigir un fichero al teclado. Ahora
vers
cmo. Fjate en este programa:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

374
374

De cadena a entero o flotante


Los ficheros de texto contienen eso, texto. No obstante, el texto se inter
preta en ocasiones
como si codificara valores enteros o flotantes. La funcin fscanf , por ejem
plo, es capaz
de leer texto de un fichero e interpretarlo como si fuera un entero o un f
lotante. Cuando
hacemos fscanf f
a , donde a es de tipo int, se leen caracter
es del fichero
y se interpretan como un entero. Pero hay un problema potencial: el texto
puede no
corresponder a un valor entero, con lo que la lectura no se efectuara corre
ctamente.
Una forma de curarse en salud es leer como cadena los siguientes caractere
s (con fscanf
y la marca de formato
o con gets, por ejemplo), comprobar que la se
cuencia de
caracteres leda describe un entero (o un flotante, segn convenga) y converti
r ese texto
en un entero (o flotante). Cmo efectuar la conversin? C nos ofrece en su bibl
ioteca
estndar la funcin atoi, que recibe una cadena y devuelve un entero. Has de i
ncluir la
cabecera
para usarla. Aqu tienes un ejemplo de uso:
include
include
int main void
char a
int b
b

atoi a

printf
a b
return 0
Si deseas interpretar el texto como un float, puedes usar atof en lugar de
atoi. As de
fcil.

include
int main void
int i n
for i 0 i 10 i
scanf
n
if n 2 0
printf

return 0
Si lo compilas para generar un programa pares, lo ejecutas e introduces los sigu
ientes
10 nmeros enteros, obtendrs este resultado en pantalla:

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
UJI
c

375
375

Cada vez que el ordenador ha detectado un nmero par, lo ha mostrado en pantalla e


ntre
corchetes.
Creemos ahora, con la ayuda de un editor de texto,
, un fichero d
e texto
con los mismos 10 nmeros enteros que hemos introducido por teclado antes:

Podemos llamar a pares as:

El carcter indica a Unix que lea del fichero


en lugar de leer de
l teclado.
El programa, sin tocar una sola lnea, pasa a leer los valores de
y
muestra
por pantalla los que son pares.
Tambin podemos redirigir la salida (la pantalla) a un fichero. Fjate:

Ahora el programa se ejecuta sin mostrar texto alguno por pantalla y el fiche
ro solopares txt acaba conteniendo lo que debiera haberse mostrado por pantalla.

Para redirigir la salida de errores, puedes usar el par de caracteres


s
eguido del
nombre del fichero en el que se escribirn los mensajes de error.
La capacidad de redirigir los dispositivos de entrada, salida y errores tien
e infinidad
de aplicaciones. Una evidente es automatizar la fase de pruebas de un programa d
urante
su desarrollo. En lugar de escribir cada vez todos los datos que solicita un pro
grama
para ver si efecta correctamente los clculos, puedes preparar un fichero con los d
atos
de entrada y utilizar redireccin para que el programa los lea automticamente.
Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
c
Introduccin a la programacin con C - UJI
UJI

376
376

Los peligros de gets. . . y cmo superarlos


Ya habrs podido comprobar que gets no es una funcin segura, pues siempre es
posible
desbordar la memoria reservada leyendo una cadena suficientemente larga.
Algunos
compiladores generan una advertencia cuando detectan el uso de gets. Cmo le
er,
pues, una lnea de forma segura? Una posibilidad consiste en escribir nuest
ra propia
funcin de lectura carcter a carcter (con ayuda de la funcin fgetc) e imponer
una
limitacin al nmero de caracteres ledos.
int lee linea char linea
int c nc
max lon

int max lon

0
Se reserva un carcter para el

while
c fgetc stdin
if c
break
if nc max lon
linea nc
c
if c
return

nc

linea nc
return nc
Para leer una cadena en un vector de caracteres con una capacidad mxima
de 100
caracteres, haremos:
lee linea cadena 100
El valor de cadena se modificar para contener la cadena leda. La cadena ms l
arga
leda tendr una longitud de 99 caracteres (recuerda que el
ocupa uno de
los 100).
Pero hay una posibilidad an ms sencilla: usar fgets sobre stdin:
fgets cadena 100 stdin
Una salvedad: fgets incorpora a la cadena leda el salto de lnea, cosa que g
ets no hace.
La primera versin, no obstante, sigue teniendo inters, pues te muestra
un esqueleto de funcin til para un control detallado de la lectura por teclado. Insp
irndote en
ella puedes escribir, por ejemplo, una funcin que slo lea dgitos, o letras,
o texto que
satisface alguna determinada restriccin.

Hemos aprendido a crear ficheros y a modificar su contenido. No sabemos, sin emb

argo,
cmo eliminar un fichero del sistema de ficheros ni cmo rebautizarlo. Hay dos funci
ones
de la librera estndar de C (accesibles al incluir
) que permiten efectuar e
stas
dos operaciones:
remove: elimina el fichero cuya ruta se proporciona.
int remove char ruta
La funcin devuelve 0 si se consigui eliminar el fichero y otro valor si s
e cometi
algn error. Ojo! No confundas borrar un fichero con borrar el contenido d
e un
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

377
377

La consulta de teclado
La funcin getc (o, para el caso, fgetc actuando sobre stdin) bloquea la e
jecucin del
programa hasta que el usuario teclea algo y pulsa la tecla de retorno. M
uchos programadores se preguntan cmo puedo saber si una tecla est pulsada o no sin qued
ar
bloqueado? Ciertas aplicaciones, como los videojuegos, necesitan efectua
r consultas al
estado del teclado no bloqueantes. Malas noticias: no es un asunto del l
enguaje C,
sino de bibliotecas especficas. El C estndar nada dice acerca de cmo efectu
ar esa
operacin.
En Unix, la biblioteca curses, por ejemplo, permite manipular los te
rminales y acceder
de diferentes modos al teclado. Pero no es una biblioteca fcil de (aprend
er a) usar. Y,
adems, presenta problemas de portabilidad, pues no necesariamente est disp
onible en
todos los sistemas operativos.
Cosa parecida podemos decir de otras cuestiones: sonido, grficos trid
imensionales,
interfaces grficas de usuario, etc. C, en tanto que lenguaje de programac
in estandarizado, no ofrece soporte. Eso s: hay bibliotecas para infinidad de campos
de aplicacin.
Tendrs que encontrar la que mejor se ajusta a tus necesidades y. . . estud
iar!

fichero. La funcin remove elimina completamente el fichero. Abrir un fi


chero en
modo escritura y cerrarlo inmediatamente elimina su contenido, pero el
fichero
sigue existiendo (ocupando, eso s, 0 bytes).
rename: cambia el nombre de un fichero.
int rename char ruta original

char nueva ruta

La funcin devuelve 0 si no hubo error, y otro valor en caso contrario.

La gestin de ficheros binarios obliga a trabajar con el mismo protocolo bsico:


1. abrir el fichero en el modo adecuado,
2. leer y/o escribir informacin,
3. y cerrar el fichero.
La funcin de apertura de un fichero binario es la misma que hemos usado para
los
ficheros de texto: fopen. Lo que cambia es el modo de apertura: debe contener la
letra .
Los modos de apertura bsicos3 para ficheros binarios son, pues:

(lectura): El primer byte ledo es el primero del fichero.


(escritura): Trunca el fichero a longitud 0. Si el fichero no
existe, se crea.
(adicin): Es un modo de escritura que preserva el contenido orig
inal del
fichero. Los datos escritos se aaden al final del fichero.
Si el fichero no puede abrirse por cualquier razn, fopen devuelve el valor
.
La funcin de cierre del fichero es fclose.
Las funciones de lectura y escritura s son diferentes:
3
Ms adelante te presentamos tres modos de apertura adicionales.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

378
378

fread: recibe una direccin de memoria, el nmero de bytes que ocupa un dat
o, el
nmero de datos a leer y un fichero. He aqu su prototipo4 :
int fread void

direccion int tam int numdatos FILE

fichero
Los bytes ledos se almacenan a partir de direccion. Devuelve el nmero de
datos
que ha conseguido leer (y si ese valor es menor que numdatos, es porque
hemos
llegado al final del fichero y no se ha podido efectuar la lectura comp
leta).
fwrite: recibe una direccin de memoria, el nmero de bytes que ocupa un da
to, el
nmero de datos a escribir y un fichero. Este es su prototipo:
int fwrite void

direccion int tam int numdatos FILE

fichero
Escribe en el fichero los tam por numdatos bytes existentes desde direc
cion en
adelante. Devuelve el nmero de datos que ha conseguido escribir (si vale
menos
que numdatos, hubo algn error de escritura).
Empezaremos a comprender cmo trabajan estas funciones con un sencillo ejemplo.
Vamos a escribir los diez primeros nmeros enteros en un fichero:
include
int main void
FILE
int i

fp

fp fopen
for i 0 i 10 i
fwrite i sizeof int
fclose fp

1 fp

return 0
Analicemos la llamada a fwrite. Fjate: pasamos la direccin de memoria en la que em
pieza
un entero (con i) junto al tamao en bytes de un entero (sizeof int , que vale 4)
y el
valor 1. Estamos indicando que se van a escribir los 4 bytes (resultado de multi
plicar 1
por 4) que empiezan en la direccin i, es decir, se va a guardar en el fichero una
copia
exacta del contenido de i.
Quiz entiendas mejor qu ocurre con esta otra versin capaz de escribir un vector
completo en una sola llamada a fwrite:
include
int main void
FILE fp

int i v 10
for i 0 i 10 i
v i i
fp fopen
fwrite v sizeof int

10 fp

4
Bueno, casi. El prototipo no usa el tipo int, sino size_t, que est definido
como unsigned int. Preferimos
presentarte una versin modificada del prototipo para evitar introducir nuevos con
ceptos.
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2

379
379

Introduccin a la programacin con C -UJI


cUJI

fclose fp
return 0
Ahora estamos pasando la direccin en la que empieza un vector (v es una direccin,
as
que no hemos de poner un delante), el tamao de un elemento del vector (sizeof int
)
y el nmero de elementos del vector (10). El efecto es que se escriben en el fiche
ro los 40
bytes de memoria que empiezan donde empieza v. Resultado: todo el vector se alma
cena
en disco con una sola operacin de escritura. Cmodo, no?
Ya te dijimos que la informacin de todo fichero binario ocupa exactamente el
mismo
nmero de bytes que ocupara en memoria. Hagamos la prueba. Veamos con
, desde
el intrprete de comandos de Unix, cunto ocupa el fichero:

Efectivamente, ocupa exactamente 40 bytes (el nmero que aparece en quinto lugar).
Si
lo mostramos con
, no sale nada con sentido en pantalla.

Por qu? Porque


interpreta el fichero como si fuera de texto, as que encuentra
la
siguiente secuencia binaria:
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000001
00000000
00000000
00000000
00000010
00000000
00000000
00000000
00000011
00000000
00000000
00000000
00000100
Los valores ASCII de cada grupo de 8 bits no siempre corresponden a caracteres v
isibles,
por lo que no se representan como smbolos en pantalla (no obstante, algunos bytes
s
tienen efecto en pantalla; por ejemplo, el valor 9 corresponde en ASCII al tabul
ador).
Hay una herramienta Unix que te permite inspeccionar un fichero binario:
(abreviatura de octal dump, es decir, volcado octal).

(La opcin
de
hace que muestre la interpretacin como enteros de grupos de
4
bytes.) Ah estn los nmeros! La primera columna indica (en hexadecimal) el nmero de
byte del primer elemento de la fila.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

325 Qu aparecer en pantalla si mostramos con el comando


el contenido del
fichero binario
programa?:

generado en este

include
int main void

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
380
380
ramacin con C - UJI

Introduccin a la prog

c
UJI

FILE fp
int i v 26
fp fopen
for i 97 i 123 i
v i 97 i
fwrite v sizeof int
fclose fp

26 fp

return 0
(Una pista: el valor ASCII del carcter
es 97.)
Y qu aparecer si lo visualizas con el comando
(la opcin indica que se
desea ver el fichero carcter a carcter e interpretado como secuencia de caracteres
).
...............................................................................
...
Ya puedes imaginar cmo se leen datos de un fichero binario: pasando la direcc
in
de memoria en la que queremos que se copie cierta cantidad de bytes del fichero.
Los
dos programas siguientes, por ejemplo, leen los diez valores escritos en los dos
ltimos
programas. El primero lee entero a entero (de 4 bytes en 4 bytes), y el segundo
con una
sola operacin de lectura (cargando los 40 bytes de golpe):
include
int main void
FILE fp
int i n
fp fopen
for i 0 i 10 i
fread n sizeof int
printf
n
fclose fp
return 0

include
int main void
FILE fd
int i v 10
fp fopen
fread v sizeof int 10 fp
for i 0 i 10 i
printf
v i
fclose fp

1 fp

return 0

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

381
381

En los dos programas hemos indicado explcitamente que bamos a leer 10 enteros,
pues sabamos de antemano que haba exactamente 10 nmeros en el fichero. Es fcil
modificar el primer programa para que lea tantos enteros como haya, sin conocer
a priori
su nmero:
include
int main void
FILE
int n

fp

fp fopen
fread n sizeof int 1 fp
while feof fp
printf
n
fread n sizeof int 1 fp
fclose fp
return 0
Lo cierto es que hay una forma ms idiomtica, ms comn en C de expresar lo mismo
:

include
int main void
FILE
int n
f fopen
while fread
printf
fclose fp

fp

n sizeof int
n

1 fp

return 0
En esta ltima versin, la lectura de cada entero se efecta con una llamada a fread e
n
la condicin del while.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
326 Disea un programa que genere un fichero binario
con los 1000
primeros nmeros primos.
327 Disea un programa que aada al fichero binario
(ver ejerc
icio
anterior) los 100 siguientes nmeros primos. El programa leer el contenido actual d
el
fichero para averiguar cul es el ltimo primo conocido. A continuacin, abrir el fiche
ro
en modo adicin y aadir 100 nuevos primos. Si ejecutsemos dos veces el programa, el

fichero acabara conteniendo los 1200 primeros primos.


...............................................................................
...
No slo puedes guardar tipos relativamente elementales. Tambin puedes almacenar
en disco tipos de datos creados por ti. Este programa, por ejemplo, lee de disco
un vector
de puntos, lo modifica y escribe en el fichero el contenido del vector:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
382
382
ramacin con C - UJI

Introduccin a la prog

c
UJI

include
include
struct Punto
float x
float y
int main void
FILE fp
struct Punto v 10
int i
Cargamos en memoria un vector de puntos.
fp fopen
fread v sizeof struct Punto 10 fp
fclose fp
Procesamos los puntos (calculamos el valor absoluto de cada coordena
da).
for i 0 i 10 i
v i x fabs v i x
v i y fabs v i y
Escribimos el resultado en otro fichero.
fp fopen
fwrite v sizeof struct Punto 10 fp
fclose fp
return 0
Esta otra versin no carga el contenido del primer fichero completamente en mem
oria
en una primera fase, sino que va leyendo, procesando y escribiendo punto a punto
:
include
include
struct Punto
float x
float y
int main void
FILE fp entrada
struct Punto p
int i

fp salida

fp entrada fopen
fp salida fopen
for i 0 i 10 i
fread p sizeof struct Punto
p x fabs p x
p y fabs p y

1 fp entrada

fwrite p sizeof struct Punto

1 fp salida

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

383
383

fclose fp entrada
fclose fp salida
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCICI
OS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
328 Los dos programas anteriores suponen que hay diez puntos en
.
Modifcalos para que procesen tantos puntos como haya en el fichero.
329 Implementa un programa que genere un fichero llamado
con 10
elementos del tipo struct Punto. Las coordenadas de cada punto se generarn aleato
riamente en el rango [10, 10]. Usa el ltimo programa para generar el fichero
.
Comprueba ue contiene el valor absoluto de los valores de
. Si es
necesario,
disea un nuevo programa ue muestre por pantalla el contenido de un fichero de pu
ntos
cuyo nombre suministra por teclado el usuario.
...............................................................................
...

Los ficheros binarios pueden utilizarse como vectores en disco y acceder directa
mente a cualuier elemento del mismo. Es decir, podemos abrir un fichero binario
en
modo lecturaescritura y, gracias a la capacidad de desplazarnos libremente por l,
leer/escribir cualuier dato. Es como si dispusieras del control de avance rpido
hacia
adelante y hacia atrs de un reproductor/grabador de cintas magnetofnicas. Con l pue

des ubicar el cabezal de lectura/escritura en cualuier punto de la cinta y pulsar
el
botn play para escuchar (leer) o el botn record para grabar (escribir).
Adems de los modos de apertura de ficheros binarios ue ya conoces, puedes us
ar
tres modos de lectura/escritura adicionales:
: No se borra el contenido del fichero, ue debe existir previam
ente. El ca
bezal de lectura/escritura se sita al principio del fichero.
: Si el fichero no existe, se crea, y si existe, se trunca el c
ontenido a longitud
cero. El cabezal de lectura/escritura se sita al principio del fichero.
: Si el fichero no existe, se crea. El cabezal de lectura/escritu
ra se sita al
final del fichero.
Para poder leer/escribir a voluntad en cualuier posicin de un fichero abiert
o en
alguno de los modos binarios necesitars dos funciones auxiliares: una ue te perm
ita
desplazarte a un punto arbitrario del fichero y otra ue te permita preguntar en

u
posicin te encuentras en un instante dado. La primera de estas funciones es fseek
, ue
desplaza el cabezal de lectura/escritura al byte ue indiuemos.
int fseek FILE
fp int desplazamiento int desde donde
El valor desde donde se fija con una constante predefinida ue proporciona una i
nter
pretacin distinta a desplazamiento:
: el valor de desplazamiento es un valor absoluto a contar
desde el prin
cipio del fichero. Por ejemplo, fseek fp 3
desplaza al c
uarto byte del
fichero fp. (La posicin 0 corresponde al primer byte del fichero.)

Introduccin a la programacin
Andrs Marzal/Isabel
con ISBN:
Gracia
C
9788469301432
384
384
c
UJI
gramacin con C  UJI

Introduccin a la pro

n ue nos

: el valor de desplazamiento es un valor relativo al lugar e


encontramos en un instante dado. Por ejemplo, si nos encontramos en el

cuarto
byte del fichero fp, la llamada fseek fp 2

nos desplazar al

segundo
byte, y fseek fp 2

al sexto.

: el valor de desplazamiento es un valor absoluto a contar d


esde el final
del fichero. Por ejemplo, fseek fp 1
nos desplaza al lti
mo byte de
fp: si a continuacin leysemos un valor, sera el del ltimo byte del fichero.
La
llamada fseek fp 0
nos situara fuera del fichero (en el
mismo punto
en el ue estamos si abrimos el fichero en modo de adicin).
La funcin devuelve el valor 0 si tiene xito, y un valor no nulo en caso contrario.
Has de tener siempre presente ue los desplazamientos sobre el fichero se in
dican en
bytes. Si hemos almacenado enteros de tipo int en un fichero binario, deberemos
tener la
precaucin de ue todos nuestros fseek tengan desplazamientos mltiplos de sizeof in
t .
Este programa, por ejemplo, pone a cero todos los valores pares de un ficher
o binario
de enteros:
include
int main void
FILE fp
int n bytes leidos cero

fp fopen
while fread n sizeof int 1 fp
0
if n 2
0
Si el ltimo valor ledo es par...
fseek fp sizeof int
... damos un paso atrs ...
fwrite cero sizeof int 1 fp
... y sobreescribimos su valo
r absoluto.
fclose fp
return 0
La segunda funcin ue te presentamos en este apartado es ftell. Este es su
prototipo:
int ftell FILE fp
El valor devuelto por la funcin es la posicin en la ue se encuentra el cabezal de
lectura/escritura en el instante de la llamada.
Veamos un ejemplo. Este programa, por ejemplo, crea un fichero y nos dice el
nmero
de bytes del fichero:

include
int main void
FILE fp
int i pos
fp fopen
for i 0 i 10 i
fwrite i sizeof int

1 fp

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

385
385

fclose fp
fp fopen
fseek fp 0
pos ftell fp
printf
fclose fp

pos

return 0
Fjate bien en el truco ue permite conocer el tamao de un fichero: nos situamos al
final del fichero con fseek indicando ue ueremos ir al primer byte desde el fin
al
(byte 0 con el modo
) y averiguamos a continuacin la posicin en la ue
nos
encontramos (valor devuelto por ftell).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
330 Disea una funcin de nombre rebobina que recibe un FILE y nos ubica al
inicio del mismo.
331 Disea una funcin que reciba un FILE (ya abierto) y nos diga el nmero de
bytes que ocupa. Al final, la funcin debe dejar el cursor de lectura/escritura en
el mismo
lugar en el que estaba cuando se la llam.
332 Disea un programa que calcule y muestre por pantalla el mximo y el mnimo
de los valores de un fichero binario de enteros.
333 Disea un programa que calcule el mximo de los enteros de un fichero binario
y lo intercambie por el que ocupa la ltima posicin.
334 Nos pasan un fichero binario
con una cantidad indeterminada
de
nmeros de tipo float. Sabemos, eso s, que los nmeros estn ordenados de menor a
mayor. Disea un programa que pida al usuario un nmero y determine si est o no est
en el fichero.
En una primera versin, implementa una bsqueda secuencial que se detenga tan
pronto ests seguro de que el nmero buscado est o no. El programa, en su versin final
,
deber efectuar la bsqueda dicotmicamente (en un captulo anterior se ha explicado
qu es una bsqueda dicotmica).
...............................................................................
...
Trabajar con ficheros binarios como si se tratara de vectores tiene ciertas
ventajas, pero
tambin inconvenientes. La ventaja ms obvia es la capacidad de trabajar con cantida
des
ingentes de datos sin tener que cargarlas completamente en memoria. El inconveni
ente
ms serio es la enorme lentitud con que se pueden ejecutar entonces los programas.
Ten en cuenta que desplazarse por un fichero con fseek obliga a ubicar el cabezal
de
lectura/escritura del disco duro, una operacin que es intrnsecamente lenta por com
portar
operaciones mecnicas, y no slo electrnicas.
Si en un fichero binario mezclas valores de varios tipos resultar difcil, cua
ndo no
imposible, utilizar sensatamente la funcin fseek para posicionarse en un punto ar
bitrario
del fichero. Tenemos un problema similar cuando la informacin que guardamos en un
fichero es de longitud intrnsecamente variable. Pongamos por caso que usamos un f
ichero

binario para almacenar una lista de palabras. Cada palabra es de una longitud, a
s que
no hay forma de saber a priori en qu byte del fichero empieza la n-sima palabra de
la
lista. Un truco consiste en guardar cada palabra ocupando tanto espacio como la
palabra
ms larga. Este programa, por ejemplo, pide palabras al usuario y las escribe en u
n fichero
binario en el que todas las cadenas miden exactamente lo mismo (aunque la longit
ud de
cada una de ellas sea diferente):

Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
386
386
ramacin con C - UJI

Introduccin a la prog

c
UJI

Ficheros binarios en Python


Python tambin permite trabajar con ficheros binarios. La apertura, lectura
/escritura y
cierre de ficheros se efecta con las funciones y mtodos de Python que ya co
noces:
open, read, write y close. Con read puedes leer un nmero cualquiera de car
acteres (de
bytes) en una cadena. Por ejemplo, f read 4 lee 4 bytes del fichero f (pr
eviamente
abierto con open). Si esos 4 bytes corresponden a un entero (en binario),
la cadena
contiene 4 caracteres que lo codifican (aunque no de forma que los podamo
s visualizar
cmodamente). Cmo asignamos a una variable el valor entero codificado en esa
cadena?
Python proporciona un mdulo con funciones que permiten pasar de binario a t
ipos
Python y viceversa: el mdulo struct. Su funcin unpack desempaqueta informacin
binaria de una cadena. Para desempaquetar un entero de una cadena almacenad
a
en una variable llamada enbinario la llamamos as: unpack
enbinario
. El primer
parmetro desempea la misma funcin que las cadenas de formato en scanf , slo q
ue
usa un juego de marcas de formato diferentes ( para el equivalente a un i
nt, para
float, para long long, etc.. Consulta el manual del mdulo struct para cono
cerlos.).
Aqu tienes un ejemplo de uso: un programa que lee y muestra los valores de
un fichero
binario de enteros:
from struct import unpack
f open
while 1
c f read 4
if c
break
v unpack
c
print v 0
f close
Fjate en que el valor devuelto por unpack no es directamente el entero, si
no una lista
(en realidad una tupla), por lo que es necesario indexarla para acceder a
l valor que nos
interesa. La razn de que devuelva una lista es que unpack puede desempaque
tar varios
valores a la vez. Por ejemplo, unpack
cadena desempaqueta dos en
teros y un
flotante de cadena (que debe tener al menos 16 bytes, claro est). Puedes a
signar los
valores devueltos a tres variables as: a b c unpack
cadena .
Hemos aprendido, pues, a leer ficheros binarios con Python. Cmo los esc
ribimos?
Siguiendo un proceso inverso: empaquetando primero nuestros valores Python
en
cadenas que los codifican en binario mediante la funcin pack y escribiendo
las con el
mtodo write. Este programa de ejemplo escribe un fichero binario con los nm
eros del

0 al 99:
from struct import pack
f open
for v in range 100
c pack
v
f write c
f close
Slo queda que aprendas a implementar acceso directo a los ficheros bina
rios con
Python. Tienes disponibles los modos de apertura
,
y
. Adems,
el mtodo
seek permite desplazarse a un byte cualquiera del fichero y el mtodo tell
indica en
qu posicin del fichero nos encontramos.

include
define

80

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

387
387

int main void


char palabra

seguir

1
FILE fp
fp fopen
do
printf

gets palab

ra
fwrite palabra sizeof char
printf

fp

gets seguir
while strcmp seguir
fclose fp

return 0
Fjate en que cada palabra ocupa siempre lo mismo, independientemente de su lo
ngitud: 80 bytes. Este otro programa es capaz ahora de mostrar la lista de palabr
as en
orden inverso, gracias a la ocupacin fija de cada palabra:
include
define

80

int main void


FILE fp
char palabra
int tam

primero, averiguar el tamao del fichero (en palabras)


fp fopen
fseek fp 0
tam ftell fp
y ya podemos
for i tam 1 i
fseek fp i
fread palabra

listarlas en orden inverso


0 i
sizeof char

p
printf

palabra

fclose fp
return 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
335 Los dos programas anteriores pueden plantear problemas cuando trabajan con
palabras que tienen 80 caracteres ms el terminador. Qu problemas? Cmo los solucionaras?
336 Disea un programa que lea una serie de valores enteros y los vaya escribiendo

en un fichero hasta que el usuario introduzca el valor 1 (ue no se escribir en el


fichero).
Tu programa debe, a continuacin, determinar si la secuencia de nmeros introducida
en
el fichero es palndroma.
337 Deseamos gestionar una coleccin de cmics. De cada cmic anotamos los siguientes datos:
Introduccin a la programacin
Andrs Marzal/Isabel
con- ISBN:
Gracia
C
978-84-693-0143-2
388
388
ramacin con C - UJI

Introduccin a la prog

c
UJI

Superhroe: una cadena de hasta 20 caracteres.


Ttulo: una cadena de hasta 200 caracteres.
Nmero: un entero.
Ao: un entero.
Editorial: una cadena de hasta 30 caracteres.
Sinopsis: una cadena de hasta 1000 caracteres.
El programa permitir:
1.

Dar de alta un cmic.

2.

Consultar la ficha completa de un cmic dado el superhroe y el nmero del ep

isodio.
3.
4.
rias.

Ver un listado por superhroe que muestre el ttulo de todas sus historias.
Ver un listado por ao que muestre el superhrore y ttulo de todas sus histo

Disea un programa que gestione la base de datos teniendo en cuenta que no queremo
s
cargarla en memoria cada vez que ejecutamos el programa, sino gestionarla direct
amente
sobre disco.
...............................................................................
...
Truncamiento de ficheros
Las funciones estndar de manejo de ficheros no permiten efectuar una oper
acin que
puede resultar necesaria en algunas aplicaciones: eliminar elementos de
un fichero. Una
forma de conseguir este efecto consiste en generar un nuevo fichero en e
l que escribimos
slo aquellos elementos que no deseamos eliminar. Una vez generado el nuev
o fichero,
borramos el original y renombramos el nuevo para que adopte el nombre de
l original.
Costoso.
En Unix puedes recurrir a la funcin truncate (disponible al incluir l
a cabecera
). El perfil de truncate es ste:
int truncate char nombre

int longitud

La funcin recibe el nombre de un fichero (que no debe estar abierto) y el


nmero de
bytes que deseamos conservar. Si la llamada tiene xito, la funcin hace que
en el
fichero slo permanezcan los longitud primeros bytes y devuelve el valor 0
. En caso
contrario, devuelve el valor 1. Observa ue slo puedes borrar los ltimos el
ementos
de un fichero, y no cualuiera de ellos. Por eso la accin de borrar parte

de un fichero
recibe el nombre de truncamiento.

Algunas de las operaciones con ficheros pueden resultar fallidas (apertura de un


fichero
cuya ruta no apunta a ningn fichero existente, cierre de un fichero ya cerrado, e
tc.).
Cuando as ocurre, la funcin llamada devuelve un valor ue indica ue se cometi un
error, pero ese valor slo no aporta informacin ue nos permita conocer el error co
metido.
La informacin adicional est codificada en una variable especial: errno (declar
ada en
). Puedes comparar su valor con el de las constantes predefinidas en
para averiguar u error concreto se ha cometido:
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

389
389

: permiso denegado,
: el fichero no existe,
: demasiados ficheros abiertos,
...
Como manejarte con tantas constantes (algunas con significados un tanto difcil
de
comprender hasta ue curses asignaturas de sistemas operativos) resulta complica
do,
puedes usar una funcin especial:
void perror char s
Esta funcin muestra por pantalla el valor de la cadena s, dos puntos y un mensaje
de
error ue detalla la causa del error cometido. La cadena s, ue suministra el pr
ogramador,
suele indicar el nombre de la funcin en la ue se detect el error, ayudando as a la
depuracin del programa.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

390
390

Esta tabla muestra el nombre de cada uno de los tipos de datos para valores ente
ros
(algunos tienen dos nombres vlidos), su rango de representacin y el nmero de bytes
(grupos de 8 bits) ue ocupan.
Tipo

Rango
Bytes

char

128 . . . 127
1

short int (o short)

32768 . . . 32767
2

int

2147483648 . . . 2147483647
4

long int (o long)

2147483648 . . . 2147483647
4
long long int (o long long)
9223372036854775808 . . . 922337203
6854775807
8
(Como ves, los tipos short int, long int y long long int pueden abreviarse, resp
ectivamente,
como short, long, y long long.)
Un par de curiosidades sobre la tabla de tipos enteros:
Los tipos int y long int ocupan lo mismo (4 bytes) y tienen el mismo ran
go. Esto
es as para el compilador
sobre un PC. En una muina distinta o con o
tro
compilador, podran ser diferentes: los int podran ocupar 4 bytes y los lon
g int, 8,
por ejemplo. En sistemas ms antiguos un int ocupaba 2 bytes y un long int
, 4.
El nombre del tipo char es abreviatura de carcter (character, en ingls) y, sin
embargo, hace referencia a los enteros de 8 bits, es decir, 1 byte. Los
valores de
tipo char son ambivalentes: son tanto nmeros enteros como caracteres.
Es posible trabajar con enteros sin signo en C, es decir, nmeros enteros posi
tivos. La
ventaja de trabajar con ellos es ue se puede aprovechar el bit de signo para au
mentar
el rango positivo y duplicarlo. Los tipos enteros sin signo tienen el mismo nomb
re ue
sus correspondientes tipos con signo, pero precedidos por la palabra unsigned, 
ue acta
como un adjetivo:
Tipo

Rango
Bytes

unsigned char

0. . . 255

1
unsigned short int (o unsigned short)
2
unsigned int (o unsigned)
5
4
unsigned long int (o unsigned long)
5
4
unsigned long long int (o unsigned long long)
73709551615
8
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la programacin con C  UJI

0. . . 65535
0. . . 429496729
0. . . 429496729
0. . . 184467440
391
391

Del mismo modo ue podemos marcar un tipo entero como sin signo con el adjetivo
unsigned, podemos hacer explcito ue tiene signo con el adjetivo signed. O sea, e
l tipo
int puede escribirse tambin como signed int: son exactamente el mismo tipo, slo u
e
en el segundo caso se pone nfasis en ue tiene signo, haciendo posible una mejora
en
la legibilidad de un programa donde este rasgo sea importante.

Puedes escribir nmeros enteros en notacin octal (base 8) o hexadecimal (base 16).
Un
nmero en notacin hexadecimal empieza por 0x. Por ejemplo, 0xff es 255 y 0x0 es 0.
Un
nmero en notacin octal debe empezar por un 0 y no ir seguido de una x. Por ejemplo
,
077 es 63 y 010 es 8.1
Puedes precisar ue un nmero entero es largo aadindole el sufijo (por Long).
Por ejemplo, 2L es el valor 2 codificado con 32 bits. El sufijo
(por long long)
indica
ue el nmero es un long long int. El literal 2L , por ejemplo, representa al nmero
entero 2 codificado con 64 bits (lo ue ocupa un long long int). El sufijo (comb
inado
opcionalmente con o ) precisa ue un nmero no tiene signo (la por unsigned).
Normalmente no necesitars usar esos sufijos, pues C hace conversiones automti

cas de tipo cuando conviene. S te har falta si uieres denotar un nmero mayor ue
2147483647 (o menor ue 2147483648), pues en tal caso el nmero no puede represen
tarse como un simple int. Por ejemplo, la forma correcta de referirse a 30000000
00 es con
el literal 3000000000L .
C resulta abrumador por la gran cantidad de posibilidades ue ofrece. Son m
uchas
formas diferentes de representar enteros, verdad? No te preocupes, slo en aplicaci
ones
muy concretas necesitars utilizar la notacin octal o hexadecimal o tendrs ue aadir
el sufijo a un literal para indicar su tipo.

Hay una marca de formato para la impresin o lectura de valores de cada tipo de en
tero:
Tipo

Marca

Tipo

Marca
char (nmero)
short
int
long
long long

unsigned
unsigned
unsigned
unsigned
unsigned

char
short
long
long long

Puedes mostrar los valores numricos en base octal o hexadecimal sustituyendo la (


o
la ) por una o una , respectivamente. Por ejemplo,
es una marca ue mue
stra un
entero largo en hexadecimal y
muestra un short en octal.
Son muchas, verdad? La ue usars ms frecuentemente es . De todos modos, por

si necesitas utilizar otras, he au algunas reglas mnemotcnicas:


base

significa decimal y alude a la base en ue se representa la informacin:


10. Por otra parte, y representan a hexadecimal y octal y aluden a las
bases 16 y 8.
significa unsigned, es decir, sin signo.

Lo cierto es ue tambin puede usar notacin octal o hexadecimal en Python, aun
ue en su momento
no lo contamos.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

392
392

significa mitad (por half), as ue es la mitad de un entero, o sea, un


short, y
es la mitad de la mitad de un entero, o sea, un char.
significa largo (por long), as ue
largo (un long) y
es un entero extralargo (un long long).

es un entero

Tambin en el caso de los flotantes tenemos dnde elegir: hay tres tipos diferentes.
En
esta tabla te mostramos el nombre, mximo valor absoluto y nmero de bits de cada un
o
de ellos:
Tipo

Mximo valor absoluto

Bytes
38
float

3.4028234710

4
double

1.797693134862315710308

8
long double

1.189731495357231765021263853031104932

12
Recuerda que los nmeros expresados en coma flotante presentan mayor resolucin
en la cercanas del 0, y que sta es tanto menor cuanto mayor es, en valor absoluto,
el
nmero representado. El nmero no nulo ms prximo a cero que puede representarse
con cada uno de los tipos se muestra en esta tabla:
Tipo

Mnimo valor absolut

o no nulo
float

1.1754943

51038
double

2.225073858507201

410308
long double

3.362103143112093506262677817321

8104932

Ya conoces las reglas para formar literales para valores de tipo float. Puedes aa
dir el
sufijo para precisar ue el literal corresponde a un double y el sufijo para ind
icar
ue se trata de un long double. Por ejemplo, el literal 3.2 es el valor 3.2 codi
ficado
como double. Al igual ue con los enteros, normalmente no necesitars precisar el
tipo
del literal con el sufijo , a menos ue su valor exceda del rango propio de los
float.

Veamos ahora las principales marcas de formato para la impresin de datos de tipos

flotantes:
Tipo

Notacin convencional

Notacin cientfica
float
double
long double
Observa ue tanto float como double usan la misma marca de formato para impr
esin
(o sea, con la funcin printf y similares).
No pretendemos detallar todas las marcas de formato para flotantes. Tenemos,
adems,
otras como , , , ,
,
,
y . Cada marca introduce ciertos m
atices
ue, en segn u aplicaciones, pueden venir muy bien. Necesitars un buen manual
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

393
393

de referencia a mano para controlar estos y otros muchos aspectos (no tiene sent
ido
memorizarlos) cuando ejerzas de programador en C durante tu vida profesional.2
Las marcas de formato para la lectura de datos de tipos flotantes presentan
alguna
diferencia:
Tipo

Notacin convenciona

l
float
double
long double
Observa ue la marca de impresin de un double es , pero la de lectura es
.
Es una incoherencia de C ue puede darte algn ue otro problema.

El tipo char, ue ya hemos presentado al estudiar los tipos enteros, es, a la ve
z el tipo
con el ue solemos representar caracteres y con el ue formamos las cadenas.

Los literales de carcter encierran entre comillas simples al carcter en cuestin o l


o
codifican como un nmero entero. Es posible utilizar secuencias de escape para ind
icar
el carcter ue se encierra entre comillas.

Los valores de tipo char pueden mostrarse en pantalla (o escribirse en ficheros


de texto)
usando la marca
o
. La primera marca muestra el carcter como eso mismo,
como
carcter; la segunda muestra su valor decimal (el cdigo ASCII del carcter).

C99 define tres nuevos tipos bsicos: el tipo lgico (o booleano), el tipo complejo
y el
tipo imaginario.

Las variables de tipo Bool pueden almacenar los valores 0 (falso) o 1 (cierto). Si
se incluye la cabecera
es posible usar el identificador de tipo bo
ol y las
constantes
y
para referirse al tipo Bool y a los valores 1 y 0, respec
tivamente.

C99 ofrece soporte para la aritmtica compleja a travs de los tipos Complex e Imagi
nary.
En Unix puedes obtener ayuda acerca de las funciones estndar con el manual en
lnea. Ejecuta
2
, por ejemplo, y obtendrs una pgina de manual sobre la funcin printf , in
cluyendo informacin
sobre todas sus marcas de formato y modificadores.

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con CISBN: 9788469301432
Introduccin a la programacin con C UJI
cUJI

394
394

Por u ofrece C tan gran variedad de tipos de datos para enteros y flotantes? Por
ue
C procura facilitar el diseo de programas eficientes proporcionando al programado
r
un juego de tipos ue le permita adoptar el compromiso adecuado entre ocupacin de
memoria y rango disponible. Por u iba un programador a uerer gastar 4 bytes en u
na
variable ue slo almacenar valores entre 0 y 255? Naturalmente, ofrecer ms control
no es gratis: a cambio hemos de tomar muchas ms decisiones. Ahorrar 3 bytes en un
a
variable puede no justificar el uebradero de cabeza, pero piensa en el ahorro 
ue se
puede producir en un vector ue contiene miles o cientos de miles de elementos 
ue
pueden representarse cada uno con un char en lugar de con un int.
Por otra parte, la aruitectura de tu ordenador est optimizada para realizar
clculos
con valores de ciertos tamaos. Por ejemplo, las operaciones con enteros suelen se
r
ms rpidas si trabajas con int (aunue ocupen ms bytes ue los char o short) y las
operaciones con flotantes ms eficientes trabajan con double.
Segn si valoras ms velocidad o consumo de memoria en una aplicacin, debers
escoger uno u otro tipo de datos para ciertas variables.

Andrs Marzal/Isabel Gracia  ISBN: 9788469301432


Introduccin a la programacin con C
troduccin a la programacin con C  UJI
c
UJI

395
395

In

scanf
La funcin scanf (y fscanf ) se comporta de un modo un tanto especial y puede desc
on
certarte en ocasiones. Veamos u hace exactamente scanf :
Empieza saltndose los blancos ue encuentra (espacios en blanco, tabulador
es y
saltos de lnea).
A continuacin, consume los caracteres no blancos mientra le sirvan para leer
un valor del tipo ue se indica con la marca de formato (por ejemplo, dgit
os si la
marca es ).
La lectura se detiene cuando el siguiente carcter a leer no sirve (por ejemp
lo,
una letra si estamos leyendo un entero). Dicho carcter no es consumido. Los
caracteres consumidos hasta este punto se interpretan como la representacin
de
un valor del tipo ue se indica con la correspondiente marca de formato,
as ue se
crea dicho valor y se escribe en la zona de memoria ue empieza en la dir
eccin
ue se indiue.
Un ejemplo ayudar a entender el comportamiento de scanf :
include
int main void
int a c
float b
printf
printf
printf
printf
a b
printf
c

scanf
scanf
scanf
El entero a es d el flotante b

a
b
c
es f

y el entero c es d

return 0
Ejecutemos el programa e introduzcamos los valores 20, 3.0 y 4 pulsando el retor
no de
carro tras cada uno de ellos.
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la programacin con C  UJI

396
396

Perfecto. Para ver u ha ocurrido paso a paso vamos a representar el texto u


e
escribe el usuario durante la ejecucin como una secuencia de teclas. En este grfic
o se
muestra u ocurre durante la ejecucin del primer scanf (lnea 8), momento en el ue
las
tres variables estn sin inicializar y el usuario acaba de pulsar las teclas , y r
etorno
de carro:
2

\n

a
b
c

El carcter a la derecha de la flecha es el siguiente carcter ue va a ser consumid


o.
La ejecucin del primer scanf consume los caracteres
y
, pues amb
os son
vlidos para formar un entero. La funcin detecta el blanco (salto de lnea) ue sigue
al
carcter
y se detiene. Interpreta entonces los caracteres ue ha ledo como el
valor
entero 20 y lo almacena en la direccin de memoria ue se la suministrado ( a):
&a
2
0
\n
a
20
b
c
En la figura hemos representado los caracteres consumidos en color gris. Fjate en
ue
el salto de lnea an no ha sido consumido.
La ejecucin del segundo scanf , el ue lee el contenido de b, empieza descart
ando
los blancos iniciales, es decir, el salto de lnea:
2

\n

20
b
c
Como no hay ms caracteres ue procesar, scanf ueda a la espera de ue el usuario
teclee algo con lo ue pueda formar un flotante y pulse retorno de carro. Cuando
el
usaurio teclea el 3.0 seguido del salto de lnea, pasamos a esta nueva situacin:
2

\n

\n

20
b
c
Ahora, scanf reanuda su ejecucin y consume el

, el

y el

. Como detec

ta ue
lo ue sigue no es vlido para formar un flotante, se detiene, interpreta los cara
cteres
ledos como el valor flotante 3.0 y lo almacena en la direccin de b:

Introduccin a la programacin con C

397
397
c

UJI
Andrs Marzal/Isabel Gracia  ISBN: 9788469301432
Introduccin a la programacin con C  UJI

2
a

\n

\n

20

&b
b 3.0
c
Finalmente, el tercer scanf entra en ejecucin y empieza por saltarse el salto de
lnea.
2
a

\n

\n

20

b 3.0
c
Acto seguido se detiene, pues no es necesario ue el usuario introduzca nuevo te
xto ue
procesar. Entonces el usuario escribe el y pulsa retorno:
2
a

\n

\n

\n

20

b 3.0
c
Ahora scanf prosigue consumiendo el y detenindose nuevamente ante el salto de
lnea. El carcter ledo se interpreta entonces como el entero y se almacena en la
direccin de memoria de c:
2
0
\n
3
.
0 \n 4 \n
a 20
b 3.0
&c
c

Como puedes apreciar, el ltimo salto de lnea no llega a ser consumido, pero eso im
porta
poco, pues el programa finaliza correctamente su ejecucin.
Vamos a estudiar ahora el poru de un efecto curioso. Imagina ue, cuando el p
ro
grama pide al usuario el primer valor entero, ste introduce tanto dicho valor com
o los
dos siguientes, separando los tres valores con espacios en blanco. He au el resu
ltado
en pantalla:

El programa ha ledo correctamente los tres valores, sin esperar a ue el usua
rio
introduzca tres lneas con datos: cuando tena ue detenerse para leer el valor de b
, no
lo ha hecho, pues saba ue ese valor era 3.0; y tampoco se ha detenido al leer el v
alor
de c, ya ue de algn modo saba ue era 4. Veamos paso a paso lo ue ha sucedido,
pues la explicacin es bien sencilla.
Durante la ejecucin del primer scanf , el usuario ha escrito el siguiente tex
to:
2

\n

a
b
c
Como su objetivo es leer un entero, ha empezado a consumir caracteres. El
y
el
le son ltiles, as ue los ha consumido. Entonces se ha detenido frente al espacio
en
blanco. Los caracteres ledos se interpretan como el valor entero 20 y se almacena
n en
a:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con CISBN: 9788469301432
398

398

Introduccin a la programacin con C  UJI


UJI
c

&a
2

\n

3
a

20

b
c
La ejecucin del siguiente scanf no ha detenido la ejecucin del programa, pues an ha
ba
caracteres pendientes de procesar en la entrada. Como siempre, scanf se ha salta
do el
primer blanco y ha ido encontrando caracteres vlidos para ir formando un valor de
l
tipo ue se le indica (en este caso, un flotante). La funcin scanf ha dejado de c
onsumir
caracteres al encontrar un nuevo blanco, se ha detenido y ha almacenado en b el
valor
flotante
. He au el nuevo estado:
2

\n

3
a

20

&b
b 3.0
c
Finalmente, el tercer scanf tampoco ha esperado nueva entrada de teclado: se ha
saltado
directamente el siguiente blanco, ha encontrado el carcter
y se ha detenido
porue
el carcter
ue le sigue es un blanco. El valor ledo (el entero 4) se almacena e
n c:
2

\n

3
a

20

b 3.0
&c
c
Tras almacenar en

el entero , el estado es ste:


2

\n

3
a

20

b 3.0
c

Cuando observes un comportamiento inesperado de scanf , haz un anlisis de lo


su
cedido como el ue te hemos presentado au y vers ue todo tiene explicacin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . EJERCIC
IOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
338 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos
de entrada en la ejecucin del programa?
2
3

\n

339 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos


de entrada en la ejecucin del programa?
2
3

\n

\n

340 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos


de entrada en la ejecucin del programa?
2
4

0
x

2
\n

Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2


399
Introduccin a la progra
macin con C - UJI
Introduccin a la programacin con C
399
c
UJI

341 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos


de entrada en la ejecucin del programa?
6

\n
(Prueba este ejercicio con el ordenador.)
...............................................................................
...
scanf
Vamos a estudiar ahora el comportamiento paso a paso de scanf cuando leemos una
cadena:
Se descartan los blancos iniciales (espacios en blanco, tabuladores o
saltos de
lnea).
Se leen los caracteres vlidos hasta el primer blanco y se almacenan en po
siciones de memoria consecutivas a partir de la que se suministra como argu
mento. Se
entiende por carcter vlido cualquier carcter no blanco (ni tabulador, ni
espacio
en blanco, ni salto de lnea. . . ).
Se aade al final un terminador de cadena.
Un ejemplo ayudar a entender qu ocurre ante ciertas entradas:
include
define

10

int main void


char a
printf
printf
printf
a b

1
scanf
scanf

a
b

return 0
Si ejecutas el programa y escribes una primera cadena sin blancos, pulsas el ret
orno de
carro, escribes otra cadena sin blancos y vuelves a pulsar el retorno, la lectur
a se efecta
como cabe esperar:

Estudiemos paso a paso lo ocurrido. Ante el primer scanf , el usuario ha esc


rito lo
siguiente:
u

\n

La funcin ha empezado a consumir los caracteres con los que ir formando la cadena
. Al
llegar al salto de lnea se ha detenido sin consumirlo. He aqu el nuevo estado de c
osas:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- ISBN:
C
978-84-693-0143-2
0
400

Introduccin a la programacin con C - UJI


c
UJI

40

u
o

\n

0
5

u n o \0

(Fjate en que scanf termina correctamente la cadena almacenada en a.) Acto seguid
o se
ha ejecutado el segundo scanf . La funcin se salta entonces el blanco inicial, es
decir, el
salto de lnea que an no haba sido consumido.
u
o

\n

0
5

u n o \0

Como no hay ms caracteres, scanf ha detenido la ejecucin a la espera de que el usu


ario
teclee algo. Entonces el usuario ha escrito la palabra y ha pulsado retorno de
carro:
u
\n

0
4

\n
1

u n o \0

Entonces scanf ha procedido a consumir los tres primeros caracteres:


u
\n

0
4

u n o \0
0
1

a
4

\n

d o s \0

Fjate en que scanf introduce automticamente el terminador pertinente al final


de
la cadena leda. El segundo scanf nos conduce a esta nueva situacin:
u
\n

n
0

\n
9

a
4

8
b

u n o \0
0
1

9
d o s \0

Compliquemos un poco la situacin. Qu ocurre si, al introducir las cadenas, metem


os
espacios en blanco delante y detrs de las palabras?

Recuerda que scanf se salta siempre los blancos que encuentra al principio y
que se
detiene en el primer espacio que encuentra tras empezar a consumir caracteres vli
dos.
Vemoslo paso a paso. Empezamos con este estado de la entrada:
Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
401
401
macin con C - UJI
UJI
c

Introduccin a la progra

\n

El primer scanf empieza saltndose los blancos inciales:


u

\n

A continuacin consume los caracteres


,
y
el blanco
que sigue:
u

y se detiene al detectar

\n
0

u n o \0

Cuando se ejecuta, el segundo scanf empieza saltndose los blancos iniciales, que
son
todos los que hay hasta el salto de lnea (includo ste):
u

\n
0

u n o \0

De nuevo, como no hay ms que leer, la ejecucin se detiene. El usuario teclea enton
ces
nuevos caracteres:
u
o
o

\n
s

\n
0

u n o \0

A continuacin, sigue saltndose los blancos:


u
o
o

\n
0

\n

u n o \0

Pasa entonces a consumir caracteres no blancos y se detiene ante el primer blanc


o:
o
s

n
o

\n
0
9

u n o \0
0
1
9

u
d

\n

d o s \0

Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- C
ISBN: 978-84-693-0143-2
402
402
Introduccin a la programacin con C - UJI
c
UJI

Ya est.
Imagina ahora que nuestro usuario quiere introducir en a la cadena
y en
b la cadena
. Aqu tienes lo que ocurre al ejecutar el programa

El programa ha finalizado sin darle tiempo al usuario a introducir la cadena


.
Es ms, la primera cadena vale
y la segunda
, con lo que ni siqu
iera se
ha conseguido el primer objetivo: leer la cadena
y depositarla ta
l cual en
a. Analicemos paso a paso lo sucedido. La entrada que el usuario teclea ante el
primer
scanf es sta:
u
o

\n

La funcin lee en a los caracteres ,


y se detiene al detectar un blanco. El
nuevo estado se puede representar as:

u
d

n
0

\n
1

9
a

u n o \0

El segundo scanf entra en juego entonces y aprovecha lo que an no ha sido procesado


,
as que empieza por descartar el blanco inicial y, a continuacin, consume los carac
teres
,
, :
u
d

n
0

9
a

\n

u n o \0
0
1

9
b

d o s \0

Ves? La consecuencia de este comportamiento es que con scanf slo podemos leer
palabras individuales. Para leer una lnea completa en una cadena, hemos de utiliz
ar una
funcin distinta: gets (por get string, que en ingls significa obtn cadena), disponible
incluyendo
en nuestro programa.

gets
scanf
Vamos a estudiar un caso concreto y analizaremos las causas del extrao comportami
ento
observado.
include
define

80

int main void


Andrs Marzal/Isabel
Introduccin
Gracia
a la programacin con- CISBN: 978-84-693-0143-2
403
403
JI
UJI
c

Introduccin a la programacin con C - U

char a
int i

printf
printf
printf
printf

gets a
scanf
gets b

a i b
return 0
Observa que leemos cadenas con gets y un entero con scanf . Vamos a ejecutar el
programa
introduciendo la palabra
en la primera cadena, el valor en el entero y la pal
abra
en la segunda cadena.

Qu ha pasado? No hemos podido introducir la segunda cadena: tan pronto hemos


escrito el retorno de carro que sigue al 2, el programa ha finalizado! Estudiemo
s paso a
paso lo ocurrido. El texto introducido ante el primer scanf es:
u
o

\n

El primer gets nos deja en esta situacin:


u

\n
0
4

9
a

u n o \0

A continuacin se ejecuta el scanf con el que se lee el valor de i. El usuario tec


lea lo
siguiente:
u
\n

n
0

\n
6

9
a

u n o \0
i 2

La funcin lee el
ueda el programa
es ste:

y encuentra un salto de lnea. El estado en el que q

\n

\n
0

9
a

u n o \0
i 2

Introduccin a la programacin
Andrs Marzal/Isabel
con- CISBN: 978-84-693-0143-2
Gracia
404
404

Introduccin a la programacin con C - UJI


UJI
c

Fjate bien en qu ha ocurrido: nos hemos quedado a las puertas de procesar el salto
de
lnea. Cuando el programa pasa a ejecutar el siguiente gets, lee una cadena vaca! Por
qu? Porque gets lee caracteres hasta el primer salto de lnea, y el primer carcter c
on
que nos encontramos ya es un salto de lnea. Pasamos, pues, a este nuevo estado:
u
\n

0
4

\n

u n o \0
i 2
0

\0

Cmo podemos evitar este problema? Una solucin posible consiste en consumir la
cadena vaca con un gets extra y una variable auxiliar. Fjate en este programa:
include
define

80

int main void


char a
1
int i
char findelinea
tenido no nos importa.

1
1

Cadena auxiliar. Su con

printf
printf
gets findelinea
printf
printf

gets a
scanf

gets b
a i b

return 0
Hemos introducido una variable extra, findelinea, cuyo nico objetivo es consumir
lo que
scanf no ha consumido. Gracias a ella, ste es el estado en que nos encontramos ju
sto
antes de empezar la lectura de b:
u
o

\n

\n
0

7
a

9
u n o \0

i 2
0
2

6
ndelinea

9
\0

El usuario escribe entonces el texto que desea almacenar en :

Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
405
405
ogramacin con C -UJI
cUJI

Introduccin a la pr

u
\n

\n

n
0

\n
1

9
a

u n o \0
i 2
0

ndelinea

\0

Ahora la lectura de
ado resultante:

tiene xito. Tras ejecutar gets, ste es el est


u

\n

\n

0
4

\n
1

9
a

u n o \0

i 2
4

8
ndelinea

\0
0

9
b

d o s \0

Perfecto! Ya te dijimos que aprender C iba a suponer enfrentarse a algunas dificu


ltades
de carcter tcnico. La nica forma de superarlas es conocer bien qu ocurre en las
entraas del programa.
Pese a que esta solucin funciona, facilita la comisin de errores. Hemos de rec
ordar
consumir el fin de lnea slo en ciertos contexto. Esta otra solucin es ms sistemtica:
leer siempre lnea a lnea con gets y, cuando hay de leerse un dato entero, flotante
, etc.,
hacerlo con sscanf sobre la cadena leda:
include
define

80

int main void


char a
int i
char linea
o nos importa.
printf

b
1

1
Cadena auxiliar. Su contenido n
gets a

printf
scanf linea
printf
printf

gets linea

i
gets b
a i b

return 0

406
Andrs aMarzal/Isabel
Introduccin
Gracia
la programacin con -CISBN: 978-84-693-0143-2
406
Introduccin a la programacin co
n C -UJI
UJI
c

Ah, ya s!, es un libro del Espejo, naturalmente! Si lo pongo delante de


un espejo, las palabras se vern otra vez al derecho.
Y ste es el poema que ley Alicia11 :
JERIGNDOR
Cocillaba el da y las tovas
giroscopaban y barrenaban en
Todos debirables estaban los
y silbramaban las alecas

agilimosas
el larde.
burgovos,
rastas.

11. [...] Carroll pasa a continuacin a interpretar las palabras de la ma


nera siguiente:
BRYLLIG [cocillaba] (der. del verbo BRYL o BROIL); hora de cocinar
la comida; es decir, cerca de la hora de comer.
SLYTHY [agilimosas] (voz compuesta por SLIMY y LITHE. Suave y acti
o.
TOVA. Especie de tejn. Tena suave pelo blanco, largas patas tras
eras y cuernos
cortos como de ciervo, se alimentaba principalmente de queso.
GYRE [giroscopar], verbo (derivado de GYAOUR o GIAOUR, perro). Araar
como
un perro.
GYMBLE [barrenar], (de donde viene GIMBLET [barrena]) hacer agujero
s en
algo.
WAVE [larde] (derivado del verbo to swab [fregar] o soak [empapar
Ladera de una colina (del hecho de empaparse por accin de la lluv
ia).
MIMSY (de donde viene MIMSERABLE y MISERABLE): infeliz.
BOROGOVE [burgovo], especie extinguida de loro. Careca de alas, t
ena el pico
hacia arriba, y anidaba bajo los relojes de sol: se alimentaba
de ternera.
MOME [aleca] (de donde viene SOLEMOME y SOLEMNE). Grave.
RATH [rasta]. Especie de tortuga de tierra. Cabeza erecta, boca
de tiburn,
patas anteriores torcidas, de manera que el animal caminaba so
bre sus rodillas;
cuerpo liso de color verde; se alimentaba de golondrinas y ost
ras.
OUTGRABE [silbramar]. Pretrito del verbo OUTGRIBE (emparentado co
n el antiguo TO GRIKE o SHRIKE, del que proceden SHREAK [chillar] y CREA
K
[chirriar]: chillaban.
Por tanto, el pasaje dice literalmente: Era por la tarde, y los tejones,
suaves y
activos, hurgaban y hacan agujeros en las laderas; los loros eran muy de
sdichados,
y las graves tortugas proferan chillidos.
ALICIA ANOTADA (EDICIN DE MARTIN GARDNER), Lew
is Carroll.

Andrs aMarzal/Isabel

Introduccin

Gracia
la programacin con -C ISBN: 978-84-693-0143-2
Introduccin a la programacin con C -UJI
cUJI

407
407

Você também pode gostar