Professional Documents
Culture Documents
1
Introduccin a la programacin con C 18 c UJI
19
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 los
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 int oat Pedir lmites inferior y superior. printf
scanf while 0 printf
printf scanf
printf scanf while printf
printf scanf
Calcular el sumatorio de la raz cuadrada de para entre
y . 0.0 for sqrt Mostrar
el resultado printf printf
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 utilizando llaves
slo donde resultan necesarias y, probablemente, utilizando unas pocas lneas menos.
Estudia las diferencias entre la primera versin de sumatorio y esta otra:
include
include
int main void
int
oat
Pedir lmites inferior y superior.
printf scanf
while 0
printf scanf
printf scanf
while
printf scanf
Calcular el sumatorio de la raz cuadrada de para entre y .
0.0
for sqrt
Mostrar el resultado.
printf
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.
Introduccin a la programacin con C 19 c UJI
20
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
International Obfuscated C Code Contest
Es posible escribir programas ilegibles en C, hasta tal punto que hay un concurso inter-
nacional de programas ilegibles!: el International Obfuscated C Code Contest (IOCCC).
Este programa (en K&R C, ligeramente modicado 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 palabras
del anagrama (precedido por un guin) y el texto del que se desea obtener un anagrama.
Si compilas el programa y lo ejecutas as descubrirs algunos anagramas curiosos
2
+ 1. (Recuerda que en C no hay operador de exponenciacin.)
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
2147483647
printf
printf
return 0
Al compilarlo y ejecutarlo hemos obtenido la siguiente salida por pantalla:
Introduccin a la programacin con C 45 c UJI
46
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Qu ha ocurrido?
25 Disea un programa C que solicite el radio de una circunferencia y muestre
por pantalla su permetro (2) y su rea (
2
).
26 Si es una variable de tipo char con el valor 127, qu vale ? Y qu vale
? Y si es una variable de tipo unsigned int con el valor 2147483647, qu vale ?
Y qu vale ?
27 Qu resulta de evaluar cada una de estas dos expresiones?
a) 1 0 1 0 1
b) 1 0 1 0 1
28 Por qu si es una variable entera 2 proporciona el mismo resultado que
1? Con qu operacin de bits puedes calcular 2? Y 32? Y 128?
29 Qu hace este programa?
include
int main void
unsigned char
printf scanf
printf scanf
printf
printf
return 0
(Nota: la forma en que hace lo que hace viene de un viejo truco de la programacin
en ensamblador, donde hay ricos juegos de instrucciones para la manipulacin de datos
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 cuenta
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
int
oat
Introduccin a la programacin con C 46 c UJI
47
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
5 3 2?
Recuerda que en Python podamos combinar operadores de comparacin para formar
expresiones como 5 3 2. Esa, en particular, se evala a True, pues 5 es mayor que 3 y
3 es mayor que 2. C tambin acepta esa expresin, pero con un signicado completamente
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 es
1 (valor por defecto) y 1 no es mayor que 2, el resultado de la evaluacin es 0, o sea,
falso.
Ojo con la interferencia entre ambos lenguajes! Problemas como ste surgirn con
frecuencia cuando aprendas nuevos lenguajes: construcciones que signican algo en el
lenguaje que conoces bien tienen un signicado diferente en el nuevo.
Si asignas a un entero int el valor de un entero ms corto, como un char, el entero
corto promociona a un entero int automticamente. Es decir, es posible efectuar esta
asignacin sin riesgo alguno:
Podemos igualmente asignar un entero int a un char. C se encarga de hacer la conversin
de tipos pertinente:
Pero, cmo? En un byte (lo que ocupa un char) no caben cuatro (los que ocupa un int)!
C toma los 8 bits menos signicativos de y los almacena en , sin ms. La conversin
funciona correctamente, es decir, preserva el valor, slo si el nmero almacenado en est
comprendido entre 128 y 127.
Observa este programa:
include
int main void
int
char
512
127
printf
return 0
Produce esta salida por pantalla:
Por qu el primer resultado es 0? El valor 512, almacenado en una variable de tipo
int, se representa con este patrn de bits: . Sus
8 bits menos signicativos se almacenan en la variable al ejecutar la asignacin ,
es decir, almacena el patrn de bits , que es el valor decimal 0.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30 Qu mostrar por pantalla este programa?
Introduccin a la programacin con C 47 c UJI
48
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
include
int main void
int
char
unsigned char
384
256
printf
printf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Si asignamos un entero a una variable otante, el entero promociona a su valor
equivalente en coma otante. Por ejemplo, esta asignacin almacena en el valor 2.0 (no
el entero 2).
2
Si asignamos un valor otante a un entero, el otante se convierte en su equivalente
entero (si lo hay!). Por ejemplo, la siguiente asignacin almacena el valor 2 en (no el
otante 2.0).
2.0
Y esta otra asignacin almacena en el valor :
0.1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31 Qu valor se almacena en las variables (de tipo int) y (de tipo oat) tras
ejecutar cada una de estas sentencias?
a) 2
b) 1 2
c) 2 4
d) 2.0 4
e) 2.0 4.0
f ) 2.0 4
g) 2 4
h) 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:
int 2.3
En este ejemplo da igual poner int que no ponerlo: C hubiera hecho la conversin impl-
citamente. El trmino int es el operador de conversin a enteros de tipo int. Hay un
operador de conversin para cada tipo: char , unsigned int oat , etc. . . Recuerda
que el smbolo tipo es un operador unario conocido como operador de coercin o
conversin de tipos.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32 Qu valor se almacena en las variables (de tipo int) y (de tipo oat) tras
ejecutar estas sentencias?
Introduccin a la programacin con C 48 c UJI
49
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
a) oat 2
b) 1 oat 2
c) int 2 4
d) int 2 oat 4
e) 2.0 int 4.0
f ) int 2.0 4
g) int 2.0 4
h) 2 oat 4
i) oat 1 2
j) 1 oat 2
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 controla 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 Compilador
El preprocesador es un programa independiente, aunque es infrecuente invocarlo direc-
tamente. El preprocesador del compilador se llama .
Las directivas son analizadas e interpretadas por el preprocesador. La directiva include
seguida del nombre de un chero (entre los caracteres y ) hace que el preprocesador
sustituya la lnea en la que aparece por el contenido ntegro del chero (en ingls inclu-
de signica incluye). El compilador, pues, no llega a ver la directiva, sino el resultado
de su sustitucin.
Nosotros slo estudiaremos, de momento, dos directivas:
dene, que permite denir constantes,
e include, que permite incluir el contenido de un chero y que se usa para importar
funciones, variables, constantes, etc. de bibliotecas.
dene
Una diferencia de C con respecto a Python es la posibilidad que tiene el primero de
denir constantes. Una constante es, en principio
15
, una variable cuyo valor no puede ser
modicado. Las constantes se denen con la directiva dene. As:
dene valor
Cada lnea dene slo puede contener el valor de una constante.
Por ejemplo, podemos denir los valores aproximados de y del nmero as:
dene 3.1415926535897931159979634685442
dene 2.7182818284590450907955982984276
15
Lo de en principio est justicado. No es cierto que las constantes de C sean variables. Lee el cuadro
titulado El preprocesador y las constantes para saber qu son exactamente.
Introduccin a la programacin con C 49 c UJI
50
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Intentar asignar un valor a o a en el programa produce un error que detecta el
compilador
16
.
Observa que no hay operador de asignacin entre el nombre de la constante y su
valor y que la lnea no acaba con punto y coma
17
. Es probable que cometas ms de una
vez el error de escribir el operador de asignacin o el punto y coma.
No es obligatorio que el nombre de la constante se escriba en maysculas, pero s
un convenio ampliamente adoptado.
const
C99 propone una forma alternativa de denir constantes mediante una nueva palabra
reservada: const. Puedes usar const delante del tipo de una variable inicializada en la
declaracin para indicar que su valor no se modicar nunca.
include
int main void
const oat pi 3.14
oat
printf
scanf
pi
printf
return 0
Pero la posibilidad de declarar constantes con const no nos libra de la directiva
dene, pues no son de aplicacin en todo lugar donde conviene usar una constante.
Ms adelante, al estudiar la declaracin de vectores, nos referiremos nuevamente a esta
cuestin.
Es frecuente denir una serie de constantes con valores consecutivos. Imagina una apli-
cacin en la que escogemos una opcin de un men como ste:
Cuando el usuario escoge una opcin, la almacenamos en una variable (llammosla
opcion) y seleccionamos las sentencias a ejecutar con una serie de comparaciones como
las que se muestran aqu esquemticamente
18
:
16
Has ledo ya el cuadro El preprocesador y las constantes?
17
A qu esperas para leer el cuadro El preprocesador y las constantes?
18
Ms adelante estudiaremos una estructura de seleccin que no es if y que se usa normalmente para
especicar este tipo de acciones.
Introduccin a la programacin con C 50 c UJI
51
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
El preprocesador y las constantes
Como ya dijimos, el compilador no lee directamente nuestros cheros con extensin .
Antes son tratados por otro programa al que se conoce como preprocesador. El prepro-
cesador (que suele ser el programa cpp, por C preprocessor) procesa las denominadas
directivas (lneas que empiezan con ). Cuando el preprocesador encuentra la directiva
dene, la elimina, pero recuerda la asociacin establecida entre un identicador y un
texto; cada vez que encuentra ese identicador en el programa, lo sustituye por el texto.
Un ejemplo ayudar a entender el porqu de algunos errores misteriosos de C cuando
se trabaja con constantes. Al compilar este programa:
dene 3.14
int main void
int PI
return 0
el preprocesador lo transforma en este otro programa (sin modicar nuestro chero).
Puedes comprobarlo invocando directamente al preprocesador:
El resultado es esto:
int main void
int 3.14
return 0
Como puedes ver, una vez preprocesado, no queda ninguna directiva en el programa y
la aparicin del identicador ha sido sustituida por el texto . Un error tpico es
confundir un dene con una declaracin normal de variables y, en consecuencia, poner
una asignacin entre el identicador y el valor:
dene 3.14
int main void
int PI
return 0
El programa resultante es incorrecto. Por qu? El compilador ve el siguiente programa
tras ser preprocesado:
int main void
int 3.14
return 0
La tercera lnea del programa resultante no sigue la sintaxis del C!
if opcion 1
Cdigo para cargar registros
Introduccin a la programacin con C 51 c UJI
52
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
else if opcion 2
Cdigo para guardar registros
else if opcion 3
El cdigo resulta un tanto ilegible porque no vemos la relacin entre los valores numricos
y las opciones de men. Es ms legible recurrir a constantes:
dene 1
dene 2
dene 3
dene 4
dene 5
dene 6
dene 7
if opcion
Cdigo para cargar registros
else if opcion
Cdigo para guardar registros
else if opcion
Puedes ahorrarte la retahla de denes con los denominados tipos enumerados. Un
tipo enumerado es un conjunto de valores con nombre. Fjate en este ejemplo:
enum Cargar 1 Guardar Anyadir Borrar Modicar Buscar Finalizar
if opcion Cargar
Cdigo para cargar registros
else if opcion Guardar
Cdigo para guardar registros
else if opcion Anyadir
La primera lnea dene los valores Cargar, Guardar, . . . como una sucesin de valores
correlativos. La asignacin del valor 1 al primer elemento de la enumeracin hace que la
sucesin empiece en 1. Si no la hubisemos escrito, la sucesin empezara en 0.
Es habitual que los enum aparezcan al principio, tras los include y dene.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33 Qu valor tiene cada identicador de este tipo enumerado?
enum Primera Segunda Tercera Penultima Ultima
(No te hemos explicado qu hace la segunda asignacin. Comprueba que la explica-
cin que das es correcta con un programa que muestre por pantalla el valor de cada
identicador.)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 52 c UJI
53
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Los tipos enumerados sirven para algo ms que asignar valores a opciones de men.
Es posible denir identicadores con diferentes valores para series de elementos 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
include
En C, los mdulos reciben el nombre de bibliotecas (o libreras, como traduccin fonti-
camente 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, varia-
bles, tipos de datos y constantes estn declaradas en el chero , que es abre-
viatura de standard input/output (entrada/salida estndar). En particular, el programa
usa las funciones printf y scanf de . Los cheros con extensin
se denominan cheros cabecera (la letra es abreviatura de header, que en ingls
signica cabecera).
A diferencia de Python, C no permite importar un subconjunto de las funciones pro-
porcionadas por una biblioteca. Al hacer include de una cabecera se importan todas
sus funciones, tipos de datos, variables y constantes. Es como si en Python ejecutaras la
sentencia from mdulo import .
Normalmente no basta con incluir un chero de cabecera con include para poder
compilar un programa que utiliza bibliotecas. Es necesario, adems, compilar con opciones
especiales. Abundaremos sobre esta cuestin inmediatamente, al presentar la librera
matemtica.
Podemos trabajar con funciones matemticas incluyendo en nuestros programas.
La tabla 1.1 relaciona algunas de las funciones que ofrece la biblioteca matemtica.
Todos los argumentos de las funciones de son de tipo otante.
19
La biblioteca matemtica tambin ofrece algunas constantes matemticas predenidas.
Te relacionamos algunas en la tabla 1.2.
No basta con escribir include para poder usar las funciones matemticas:
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 cada fun-
cin y cmo lo hace (con qu instrucciones concretas). Si compilas sin , el compilador
se quejar:
19
Lo cierto es que son de tipo double (vase el apndice A), pero no hay problema si las usas con valores
y variables de tipo oat, ya que hay conversin automtica de tipos.
Introduccin a la programacin con C 53 c UJI
54
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Funcin C Funcin matemtica
sqrt raz cuadrada de
sin seno de
cos coseno de
tan tangente de
asin arcoseno de
acos arcocoseno de
atan arcotangente de
exp el nmero elevado a
exp10 10 elevado a
log logaritmo en base de
log10 logaritmo en base 10 de
log2 logaritmo en base 2 de
pow elevado a
fabs valor absoluto de
round redondeo al entero ms prximo a
ceil redondeo superior de
oor redondeo inferior de
Tabla 1.1: Algunas funciones matemticas disponibles en la biblioteca .
Constante Valor
una aproximacin del nmero
una aproximacin del nmero
una aproximacin de /2
una aproximacin de /4
una aproximacin de 1/
una aproximacin de
2
una aproximacin de log
2
una aproximacin de log
10
Tabla 1.2: Algunas constantes disponibles en la biblioteca .
El mensaje advierte de que hay una referencia indenida a sqrt. En realidad no
se est quejando el compilador, sino otro programa del que an no te hemos dicho
nada: el enlazador (en ingls, linker). El enlazador es un programa que detecta en un
programa las llamadas a funcin no denidas en un programa C y localiza la denicin
de las funciones (ya compiladas) en bibliotecas. El chero que inclumos con
dene contiene la cabecera de las funciones matemticas, pero no su cuerpo. El cuer-
po de dichas funciones, ya compilado (es decir, en cdigo de mquina), reside en otro
chero: . Para qu vale el chero si no tiene el cuerpo de
las funciones? Para que el compilador compruebe que estamos usando correctamente las
funciones (que suministramos el nmero de argumentos adecuado, que su tipo es el que
debe ser, etc.). Una vez que se comprueba que el programa es correcto, se procede a ge-
nerar el cdigo de mquina, y ah es necesario pegar (enlazar) el cdigo de mquina
de las funciones matemticas que hemos utilizado. El cuerpo ya compilado de sqrt, por
ejemplo, se encuentra en ( es abreviatura de math library). El
enlazador es el programa que enlaza el cdigo de mquina de nuestro programa con el
cdigo de mquina de las bibliotecas que usamos. Con la opcin le indicamos al en-
lazador que debe resolver las referencias indenidas a funciones matemticas utilizando
.
Introduccin a la programacin con C 54 c UJI
55
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
La opcin evita tener que escribir al nal. Estas dos invoca-
ciones del compilador son equivalentes:
( )( )( ), donde
= ( + + )/2.).
Compila y ejecuta el programa.
35 Disea un programa C que solicite el radio de una circunferencia y muestre
por pantalla su permetro (2) y su rea (
2
). Utiliza la aproximacin a predenida
en la biblioteca matemtica.
Compila y ejecuta el programa.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Las estructuras de control de C son parecidas a las de Python. Bueno, hay alguna ms
y todas siguen unas reglas sintcticas diferentes. Empecemos estudiando las estructuras
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 parntesis que encierran a la condicin son obligatorios. Como en Python no lo son,
es fcil que te equivoques por no ponerlos. Si el bloque de sentencias consta de una sola
sentencia, no es necesario encerrarla entre llaves:
if condicin
sentencia
Introduccin a la programacin con C 55 c UJI
56
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 puedes eliminar las
llaves:
if condicin
sentencia si
else
sentencias no
if condicin
sentencias si
else
sentencia no
if condicin
sentencia si
else
sentencia no
Ojo: la indentacin no signica nada para el compilador. La ponemos nicamente
para facilitar la lectura. Pero si la indentacin no signica nada nos enfrentamos a un
problema de ambigedad con los if anidados:
if condicin
if otra condicin
sentencias si
else
???
???
sentencias no
A cul de los dos if pertenece el else? Har el compilador de C una interpretacin
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 rompe la ambigedad trabajando con esta sencilla regla: el else pertenece al if
libre ms cercano. Si quisiramos expresar la primera estructura, deberamos aadir
llaves para determinar completamente qu bloque est dentro de qu otro:
if condicin
if otra condicin
sentencias si
Introduccin a la programacin con C 56 c UJI
57
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
else
sentencias no
A pesar de que el if externo contiene una sola sentencia (otro y, por tanto, las llaves
son redundantesif ), las llaves son necesarias para indicar que el else va asociado a la
condicin exterior.
No hay sentencia elif : la combinacin else if
C no tiene una estructura elif como la de Python, pero tampoco la necesita. Puedes usar
else if donde hubieras puesto un elif en Python:
if condicin
sentencias si
else if condicin2
sentencias si2
else if condicin3
sentencias si3
else
sentencias no
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 + = 0 solicitando por teclado el valor de y (ambos de tipo oat).
El programa detectar si la ecuacin no tiene solucin o si tiene innitas 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 innitas 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 mostrar por
pantalla el siguiente resultado:
Introduccin a la programacin con C 57 c UJI
58
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin 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 ejemplo, de
1 euro), no muestra el mensaje correspondiente.
41 Disea un programa C que lea un carcter cualquiera desde el teclado, y muestre el
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 ml-
tiple. 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 c UJI
59
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 enumerados:
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 nal de
cada opcin. Este programa, por ejemplo, presenta un comportamiento curioso:
E E
include
enum Saludar 1 Despedirse
int main void
int opcion
printf
printf
scanf opcion
switch opcion
case Saludar
Introduccin a la programacin con C 59 c UJI
60
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 ujo de control que entra en un case ejecuta las
acciones asociadas al siguiente case, y as hasta encontrar un break o salir del switch
por la ltima de sus lneas.
El compilador de C no seala la ausencia de break como un error porque, de hecho,
no lo es. Hay casos en los que puedes explotar a tu favor este curioso comportamiento
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 a la programacin con C 60 c UJI
61
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Nuevamente, los parntesis son obligatorios y las llaves pueden suprimirse si el bloque
contiene una sola sentencia.
Veamos un ejemplo de uso: un programa que calcula
para y enteros:
include
int main void
int
printf scanf
printf scanf
1
0
while
printf
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 seguro
que ste se ejecuta al menos una vez. Podramos reescribir para usar un
bucle do while:
include
include
int main void
int
oat
Pedir lmites inferior y superior.
do
printf scanf
if 0 printf
while 0
do
printf scanf
if printf
while
Calcular el sumatorio de la raz cuadrada de i para i entre a y b.
0.0
for sqrt
Introduccin a la programacin con C 61 c UJI
62
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Mostrar el resultado.
printf
return 0
Los bucles do while no aaden potencia al lenguaje, pero s lo dotan de mayor expre-
sividad. Cualquier cosa que puedas hacer con bucles do while, puedes hacerla tambin
con slo bucles while y la ayuda de alguna sentencia condicional if , pero probablemente
requerirn mayor esfuerzo por tu parte.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 continua-
cin, 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 mitad;
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 cdigo:
inicializacin
while condicin
sentencias
incremento
Una forma habitual de utilizar el bucle for es la que se muestra en este ejemplo, que
imprime por pantalla los nmeros del 0 al 9 y en el que suponemos que es de tipo int:
for 0 10
printf
Es equivalente, como decamos, a este otro fragmento de programa:
Introduccin a la programacin con C 62 c UJI
63
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Comparaciones y asignaciones
Un error frecuente es sustituir el operador de comparacin de igualdad por el de asig-
nacin en una estructura if o while. Analiza este par de sentencias:
0
if 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 recibe el valor 0 y que ese 0,
devuelto por el operador de asignacin, se considera la representacin del valor falso.
Lo correcto hubiera sido:
0
if 0 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 escribiendo siempre
la variable en la parte derecha:
0
if 0 Correcto.
De ese modo, si se confunden y usan en lugar de , se habr escrito una expresin
incorrecta y el compilador detendr el proceso de traduccin a cdigo de mquina:
0
if 0 Mal: error detectable por el compilador.
0
while 10
printf
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46 Implementa el programa de clculo de
=
!
( )! !
.
(Prueba tu programa sabiendo que 10 000 euros al 4.5% de inters anual se convierten
en 24 117.14 euros al cabo de 20 aos.)
55 Un vector en un espacio tridimensional es una tripleta de valores reales ( ).
Deseamos confeccionar un programa que permita operar con dos vectores. El usuario 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 diferente,
el programa advertir al usuario de su error y el men reaparecer.
Las opciones 4 y 5 pueden proporcionar resultados distintos en funcin del orden
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 mostrar el
siguiente men:
Nuevamente, si el usuario se equivoca, se le advertir del error y se le permitir
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 realizar.
Quiz la siguiente tabla te sea de ayuda:
Operacin Clculo
Suma: (
1
1
1
) + (
2
2
2
) (
1
+
2
1
+
2
1
+
2
)
Diferencia: (
1
1
1
) (
2
2
2
) (
1
2
1
2
1
2
)
Producto escalar: (
1
1
1
) (
2
2
2
)
1
2
+
1
2
+
1
2
Producto vectorial: (
1
1
1
) (
2
2
2
) (
1
2
1
2
1
2
)
ngulo entre (
1
1
1
) y (
2
2
2
)
180
arccos
2
+
1
2
+
1
2
1
+
2
1
+
2
1
2
2
+
2
2
+
2
2
Longitud de ( )
2
+
2
+
2
Ten en cuenta que tu programa debe contemplar toda posible situacin excepcional:
divisiones por cero, races con argumento negativo, etc..
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 66 c UJI
67
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
La sentencia break tambin est disponible en C. De hecho, ya hemos visto una aplicacin
suya en la estructura de control switch. Con ella puedes, adems, abortar al instante la
ejecucin de un bucle cualquiera (while, do while o for).
Otra sentencia de C que puede resultar til es continue. Esta sentencia naliza la
iteracin actual, pero no aborta la ejecucin del bucle.
Por ejemplo, cuando en un bucle while se ejecuta continue, la siguiente sentencia a
ejecutar es la condicin del bucle; si sta se cumple, se ejecutar una nueva iteracin del
bucle.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56 Qu muestra por pantalla este programa?
include
int main void
int
0
while 10
if 2 0
continue
printf
for 0 10
if 2 0
continue
printf
return 0
57 Traduce a C este programa Python.
car raw input
if car lower or car
print
else
if not car or car
print
print
else
print
58 Traduce a C este programa Python.
from math import pi
radio oat raw input
opcion
while opcion and opcion and opcion
print
print
print
Introduccin a la programacin con C 67 c UJI
68
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
print
opcion raw input
if opcion
diametro 2 radio
print diametro
elif opcion
perimetro 2 pi radio
print perimetro
elif opcion
area pi radio 2
print area
else
print opcion
59 Traduce a C este programa Python.
anyo int raw input
if anyo 4 0 and anyo 100 0 or anyo 400 0
print anyo
else
print anyo
60 Traduce a C este programa Python.
limite 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 y asegurndose de que sea
mayor o igual que . A continuacin, muestra por pantalla el valor de
=
1/.
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 con C 68 c UJI
69
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con 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!.
iwis C/oii, Alicia en el Pas de las Maravillas.
En este captulo vamos a estudiar algunas estructuras que agrupan varios datos, pero cuyo
tamao resulta conocido al compilar el programa y no sufre modicacin alguna durante
su ejecucin. Empezaremos estudiando los vectores, estructuras que se pueden asimilar a
las listas Python. En C, las cadenas son un tipo particular de vector. Manejar cadenas en
C resulta ms complejo y delicado que manejarlas en Python. Como contrapartida, es ms
fcil denir 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 denir nuevos tipos como agrupaciones de datos de tipos no necesariamente
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 asimilarse
a las listas Python, pero con una limitacin fundamental: todos los elementos del vector
han de tener el mismo tipo. Podemos denir vectores de enteros, vectores de otantes,
etc., pero no podemos denir vectores que, por ejemplo, contengan a la vez enteros y
otantes. El tipo de los elementos de un vector se indica en la declaracin del vector.
C nos permite trabajar con vectores estticos y dinmicos. En este captulo nos ocu-
pamos nicamente de los denominados vectores estticos, que son aquellos que tienen
tamao jo 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 explcito me-
diante una expresin que podamos evaluar examinando nicamente el texto del programa.
Un vector de 10 enteros de tipo int se declara as:
int 10
69
70
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Sin cortes
Los vectores C son mucho ms limitados que las listas Python. A los problemas re-
lacionados con el tamao jo 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 ejemplo, no
existe en C. Cuando en Python desebamos extraer una copia de los elementos entre i
y de un vector escribamos 1 . En C no hay operador de corte. . . ni operador
de concatenacin o repeticin, ni sentencias de borrado de elementos, ni se entienden
como accesos desde el nal los ndices negativos, ni hay operador de pertenencia, etc.
Echaremos de menos muchas de las facilidades propias de Python.
El vector comprende los elementos 0 , 1 , 2 , . . . , 9 , todos de tipo 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 declaran dos
vectores de oat, uno con 20 componentes y otro con 100:
oat 20 100
Tambin es posible mezclar declaraciones de vectores y escalares en una misma
lnea. En este ejemplo se declaran las variables y como vectores de 80 caracteres y
la variable como escalar de tipo carcter:
char 80 80
Se considera mal estilo declarar la talla de los vectores con literales de entero. Es
preferible utilizar algn identicador para la talla, pero teniendo en cuenta que ste debe
corresponder a una constante:
dene 80
char
Esta otra declaracin es incorrecta, pues usa una variable para denir la talla del
vector
1
:
int talla 80
char talla
!
No siempre es vlido!
Puede que consideres vlida esta otra declaracin que prescinde de constantes de-
nidas con dene y usa constantes declaradas con const, pero no es as:
const int talla 80
char 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 error suponer
que los valores del vector son nulos tras su creacin. Si no lo crees, fjate en este 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 consideraremos incorrecto.
Introduccin a la programacin con C 70 c UJI
71
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
include
dene 5
int main void
int
for 0
printf
return 0
Observa que el acceso a elementos del vector sigue la misma notacin de Python: usamos
el identicador del vector seguido del ndice encerrado entre corchetes. En una ejecucin
del programa obtuvimos este resultado en pantalla (es probable que obtengas resultados
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
dene 10
int main void
int
for 0
0
for 0
printf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67 Declara e inicializa un vector de 100 elementos de modo que los componentes de
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 inverso.
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 nmero
y dir si es o no es uno de los 50 primeros nmeros de Fibonacci.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 71 c UJI
72
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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? Porque la talla
del vector puede aparecer en diferentes puntos del programa y es posible que algn da
modiquemos el programa para trabajar con una talla diferente. En tal caso, deberamos
editar muchas lneas diferentes del programa (quiz decenas o cientos). Bastara que
olvidsemos modicar una o que modicsemos una de ms para que el programa fuera
errneo. Fjate en este programa C:
include
int main void
int 10 10
for 0 10 0
for 0 10 0
for 0 10 printf
for 0 10 printf
return 0
Las tallas de y aparecen en seis lugares. Imagina que deseas modicar el programa
para que pase a tener 20 enteros: tendrs que modicar slo tres de esos dieces. 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 . Complicado. Estudia esta versin:
include
dene 10
dene 10
int main void
int TALLA A TALLA B
for 0 TALLA A 0
for 0 TALLA B 0
for 0 TALLA A printf
for 0 TALLA B printf
return 0
Si ahora has de modicar 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 errores.
Por qu en Python no nos preocup esta cuestin? Recuerda que en Python no haba
declaracin de variables, que las listas podan modicar su longitud durante la ejecucin
de los programas y que podas consultar la longitud de cualquier secuencia de valores
con la funcin predenida len. Python ofrece mayores facilidades al programador, pero
a un precio: menos velocidad de ejecucin y ms consumo de memoria.
Hay una forma alternativa de inicializar vectores. En este fragmento se denen e
inicializan dos vectores, uno con todos sus elementos a 0 y otro con una secuencia
ascendente de nmeros:
dene 5
int 0 0 0 0 0
Introduccin a la programacin con C 72 c UJI
73
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int 1 2 3 4 5
Ten en cuenta que, al declarar e inicializar simultneamente un vector, debes indicar
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 0 0 0 0 0
int 1 2 3 4 5
El compilador deduce que la talla del vector es 5, es decir, el nmero de valores que
aparecen a la derecha del igual. Te recomendamos que, ahora que ests aprendiendo,
no uses esta forma de declarar vectores: siempre que puedas, opta por una que 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 ltimo
elementos de un vector de talla 10:
int 0 0 9 0
Vamos a ilustrar lo aprendido desarrollando un sencillo programa que calcule y muestre
los nmeros primos menores que , para un valor de jo y determinado en el propio
programa. Usaremos un mtodo denominado la criba de Eratstenes, uno de los algoritmos
ms antiguos y que debemos a un astrnomo, gegrafo, matemtico y lsofo de la antigua
Grecia. El mtodo utiliza un vector de valores booleanos (unos o ceros). Si la celda de
ndice contiene el valor 1, consideramos que 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 ndice
de esta casilla. Y as sucesivamente. Cuando se ha recorrido completamente el vector, las
casillas cuyo ndice es primo contienen un 1.
Vamos con una primera versin del programa:
include
dene 100
int main void
int criba
Inicializacin
criba 0 0
for 1
criba 1
Criba de Eratstenes
for 2
Introduccin a la programacin con C 73 c UJI
74
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
if criba
for 2
criba 0
Mostrar los resultados
for 0
if criba
printf
return 0
Observa que hemos tenido que decidir qu valor toma , pues el vector criba debe tener un
tamao conocido en el momento en el que se compila el programa. Si deseamos conocer
los, digamos, primos menores que 200, tenemos que modicar la lnea 3.
Mejoremos el programa. Es necesario utilizar 4 bytes para almacenar un 0 o un 1?
Estamos malgastando memoria. Esta otra versin reduce a una cuarta parte el tamao
del vector criba:
include
dene 100
int main void
char criba
int
Inicializacin
criba 0 0
for 1
criba 1
Criba de Eratstenes
for 2
if criba
for 2
criba 0
Mostrar los resultados
for 0
if criba
printf
return 0
Mejor as.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70 Puedes ahorrar tiempo de ejecucin haciendo que tome valores entre 2 y la raz
cuadrada de . Modica 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 con C 74 c UJI
75
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Optimiza, pero no te pases
C permite optimizar mucho los programas y hacer que estos consuman la menor 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 podemos
reducir ms el consumo de memoria: para representar un 1 o un 0 basta un solo bit.
Como en un char caben 8 bits, podemos proponer esta otra solucin:
include
include
dene 100
int main void
char criba 8 1 Ocupa unas 8 veces menos que la versin anterior.
int
Inicializacin
criba 0 254 Pone todos los bits a 1 excepto el primero.
for 1 8
criba 255 Pone todos los bits a 1.
Criba de Eratstenes
for 2
if criba 8 1 8 Pregunta si el bit en posicin vale 1.
for 2
criba 8 1 8 Pone a 0 el bit en posicin .
Mostrar los resultados
for 0
if criba 8 1 8
printf
return 0
Buf! La legibilidad deja mucho que desear. Y no slo eso: consultar si un determinado
bit vale 1 y jar un determinado bit a 0 resultan ser operaciones ms costosas que
consultar si el valor de un char es 1 o, respectivamente, jar el valor de 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 programa
pierde legibilidad y, adems, resulta ms lento? No hay una respuesta denitiva a esta
pregunta. La nica respuesta es: depende. En segn qu aplicaciones, puede resultar
necesario, en otras no. Lo que no debes hacer, al menos de momento, es obsesionarte
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 elementos son:
=
=1
=1
(
)
2
Introduccin a la programacin con C 75 c UJI
76
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
donde
2
+
3
3
+ +
10
10
puede representarse con un
vector en el que se almacenan sus 11 coecientes (
0
,
1
, . . . ,
10
). Vamos a construir un
programa C que permita leer por teclado dos polinomios () y () y, una vez ledos,
calcule los polinomios () = () + () y () = () ().
Empezaremos deniendo dos vectores y que han de poder contener 11 valores en
coma otante:
include
dene 11
int main void
oat
Como leer por teclado 11 valores para y 11 ms para 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 nicamente
el valor de los coecientes de grado menor o igual que el indicado:
E E
include
dene 11
int main void
oat
int grado
int
Lectura de
do
printf 1
scanf grado
while grado 0 grado
for 0 grado
printf scanf
Lectura de
do
printf 1
scanf grado
while grado 0 grado
for 0 grado
printf scanf
return 0
El programa presenta un problema: no inicializa los coecientes que correponden a
los trminos
, para mayor que el grado del polinomio. Como dichos valores deben
Introduccin a la programacin con C 85 c UJI
86
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
ser nulos, hemos de inicializarlos explcitamente (en aras de la brevedad mostramos
nicamente la inicializacin de los coecientes de ):
int main void
oat
int grado
int
Lectura de p
do
printf 1
scanf grado
while grado 0 grado
for 0 grado
printf scanf
for grado 1
0.0
return 0
Ahora que hemos ledo los polinomios, calculemos la suma. La almacenaremos en un
nuevo vector llamado . La suma de dos polinomios de grado menor que
es un polinomio de grado tambin menor que , as que el vector tendr
talla .
int main void
oat
El procedimiento para calcular la suma de polinomios es sencillo. He aqu el clculo
y la presentacin del resultado en pantalla:
include
dene 11
int main void
oat
int grado
int
Lectura de
do
printf 1
scanf grado
while grado 0 grado
for 0 grado
printf scanf
for grado 1
Introduccin a la programacin con C 86 c UJI
87
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
0.0
Lectura de
do
printf 1
scanf grado
while grado 0 grado
for 0 grado
printf scanf
for grado 1
0.0
Clculo de la suma
for 0
Presentacin del resultado
printf 0
for 1
printf
printf
return 0
Aqu tienes un ejemplo de uso del programa con los polinomios () = 5 +3 +5
2
+
3
y () = 4 4 5
2
+
3
:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81 Modica el programa anterior para que no se muestren los coecientes nulos.
82 Tras efectuar los cambios propuestos en el ejercicio anterior no aparecer nada
por pantalla cuando todos los valores del polinomio sean nulos. Modica el programa
para que, en tal caso, se muestre por pantalla .
83 Tras efectuar los cambios propuestos en los ejercicios anteriores, el polinomio
empieza con un molesto signo positivo cuando
0
es nulo. Corrige el programa para que
el primer trmino del polinomio no sea precedido por el carcter .
84 Cuando un coeciente es negativo, por ejemplo 1, el programa anterior muestra
su correspondiente trmino en pantalla as: . Modica el programa ante-
rior para que un trmino con coeciente negativo como el del ejemplo se muestre as:
.
Introduccin a la programacin con C 87 c UJI
88
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Nos queda lo ms difcil: el producto de los dos polinomios. Lo almacenaremos en un
vector llamado n. Como el producto de dos polinomios de grado menor o igual que i es un
polinomio de grado menor o igual que 2i, la talla del vector n no es :
int main void
oat p j
oat n 2 1
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
85 Entiendes por qu hemos reservado 2 1 elementos para n y
no 2 ?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
El coeciente n
i
, para valores de i entre 0 y el grado mximo de n(x), es decir, entre
los enteros 0 y 2 2, se calcula as:
n
i
=
i
=0
p
j
i
10
, que son nulos. Modica el programa para que, con la ayuda de variables enteras,
recuerde el grado de los polinomios () y () en sendas variables talla p y talla q y
use esta informacin en los clculos de modo que se opere nicamente con los coecientes
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 que
efecta estadsticas de una serie de edades), necesitas alguna variable auxiliar que
te permita saber en todo momento cuntas de las celdas contienen informacin. Si
aades un elemento, has de incrementar t mismo el valor de esa variable.
Ya sabes lo suciente sobre vectores para poder hacer frente a estos ejercicios:
Introduccin a la programacin con C 89 c UJI
90
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 pronto sea
posible, si introduce un nmero repetido y solicitar nuevamente el nmero hasta que sea
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 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.
El da y hora en que se registr la mayor temperatura.
89 La cabecera incluye la declaracin de funciones para generar nmeros
aleatorios. La funcin rand, que no tiene parmetros, devuelve un entero positivo alea-
torio. Si deseas generar nmeros aleatorios entre 0 y un valor dado , puedes evaluar
rand 1 . Cuando ejecutas un programa que usa rand, la semilla del generador 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 elementos
con nmeros enteros entre 0 y 4. Muestra por pantalla el resultado. Detecta y muestra, a
continuacin, el tamao de la sucesin ms larga de nmeros consecutivos iguales.
(Ejemplo: si los nmeros generados son , el tramo ms largo
formado por nmeros iguales es de talla 3 (los tres doses al nal de la secuencia), as
que por pantalla aparecer el valor 3.)
90 Modica 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.
Introduccin a la programacin con C 90 c UJI
91
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con 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 introduzca
una serie de nmeros enteros y nos dir si cada uno de ellos est o no en el vector. El
programa naliza 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 introduzca
una serie de nmeros enteros y nos dir si cada uno de ellos est o no en el vector. El
programa naliza 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, utiliza
el algoritmo de bsqueda dicotmica.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Es importante que conozcas bien cmo se disponen los vectores en memoria. Cuando se
encuentra esta declaracin en un programa:
int 5
el compilador reserva una zona de memoria contigua capaz de albergar 5 valores de tipo
int. Como una variable de tipo int ocupa 4 bytes, el vector ocupar 20 bytes.
Podemos comprobarlo con este programa:
include
dene 5
int main void
int
printf sizeof 0
printf sizeof
return 0
El resultado de ejecutarlo es ste:
Cada byte de la memoria tiene una direccin. Si, pongamos por caso, el vector
empieza en la direccin 1000, 0 se almacena en los bytes 10001003, 1 en los
bytes 10041007, y as sucesivamente. El ltimo elemento, 4 , ocupar los bytes 1016
1019:
996:
1000: a[0]
1004: a[1]
1008: a[2]
1012: a[3]
1016: a[4]
Introduccin a la programacin con C 91 c UJI
92
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Big-endian y little-endian
Lo bueno de los estndares es. . . que hay muchos donde elegir. No hay forma de ponerse
de acuerdo. Muchos ordenadores almacenan los nmeros enteros de ms de 8 bits
disponiendo los bits ms signicativos en la direccin de memoria ms baja y otros, en
la ms alta. Los primeros se dice que siguen la codicacin big-endian y los segundos,
little-endian.
Pongamos un ejemplo. El nmero 67586 se representa en binario con cuatro 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 bajo
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 little-
endian 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 signicativo a la izquierda.
La diferente codicacin de unas y otras plataformas plantea serios problemas a la
hora de intercambiar informacin en cheros binarios, es decir, cheros que contienen
volcados de la informacin en memoria. Nos detendremos nuevamente sobre esta cuestin
cuando estudiamos cheros.
Por cierto, lo de little-endian y big-endian viene de Los viajes de Gulliver,
la novela de Johnathan Swift. En ella, los liliputienses debaten sobre una importante
cuestin poltica: deben abrirse los huevos pasados por agua por su extremo grande,
como deende el partido Big-Endian, o por su extremo puntiagudo, como mantiene 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, 2 es la direccin de memoria en
la que empieza 2 , es decir, la direccin 1008 en el ejemplo.
Veamos qu direccin ocupa cada elemento de un vector cuando ejecutamos un pro-
grama sobre un computador real:
include
dene 5
int main void
int
for 0
printf unsigned int
return 0
Introduccin a la programacin con C 92 c UJI
93
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Al ejecutar el programa obtenemos en pantalla lo siguiente (puede que obtengas un
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 se diferencia de la siguiente en
4 unidades.
Recuerda que la funcin de lectura de datos por teclado scanf modica el valor 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. Por esa
razn precedamos los identicadores de las variables con el operador . Este programa,
por ejemplo, lee por teclado el valor de todos los componentes de un vector utilizando el
operador para conocer la direccin de memoria de cada uno de ellos:
include
dene 5
int main void
int
for 0
printf scanf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
98 Qu problema presenta esta otra versin del mismo programa?
include
dene 5
int main void
int
for 0
printf scanf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Analiza este programa:
include
dene 5
Introduccin a la programacin con C 93 c UJI
94
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int main void
int
for 0
printf unsigned int
printf unsigned int
return 0
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 0
es equivalente a esta otra:
printf unsigned int
As pues, expresa una direccin de memoria (la de su primer elemento), es decir, es
un puntero o referencia a memoria y es equivalente a 0 . La caracterstica de que
el identicador 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 represent-
bamos las listas en Python:
a
0
0
0
1
0
2
0
3
0
4
Fjate en que el grco pone claramente de maniesto que es un puntero, pues se le
representa con una echa 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 no es exactamente un puntero, aunque funciona como tal. Sera ms
justo representar la memoria as:
0
0
0
1
0
2
0
3
0
4
Pero, por el momento, conviene que consideres vlida la representacin en la que es
un puntero. Cuando estudiemos la gestin de memoria dinmica abundaremos en esta
cuestin.
Introduccin a la programacin con C 94 c UJI
95
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Recuerda que el operador obtiene la direccin de memoria en la que se encuentra un
valor. En esta gura te ilustramos 0 y 2 como sendos punteros a sus respectivas
celdas en el vector.
&a[2]
a
0
0
0
1
0
2
0
3
0
4
&a[0]
Cmo encuentra C la direccin de memoria de un elemento del vector cuando acce-
demos 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 2 , por ejemplo, se entiende como
accede al valor de tipo int que empieza en la direccin con un desplazamiento de
2 4 bytes. Una sentencia de asignacin como 2 0 se interpreta como almacena
el valor 0 en el entero int que empieza en la direccin de memoria de ms 24 bytes.
Aqu tienes un programa con un resultado que puede sorprenderte:
include
dene 3
int main void
int
for 0
10
printf
printf
printf
printf unsigned int
printf
printf unsigned int 0 0
printf unsigned int 1 1
printf unsigned int 2 2
printf
printf unsigned int 0 0
printf unsigned int 1 1
printf unsigned int 2 2
printf
printf unsigned int 2 2
printf unsigned int 3 3
printf unsigned int 4 4
printf unsigned int 5 5
printf unsigned int 1 1
printf unsigned int 5 5
printf
return 0
Introduccin a la programacin con C 95 c UJI
96
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Aqu tienes el resultado de su ejecucin
3
:
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 objeto
4
y
la tercera muestra el valor almacenado en dicho objeto. A la vista de las direcciones
de memoria de los objetos , 0 , 1 , 2 , 0 , 1 y 2 , el compilador ha
reservado la memoria de estas variables as:
3221222636: i 3
3221222640: w[0] 10
3221222644: w[1] 11
3221222648: w[2] 12
3221222652:
3221222656: v[0] 0
3221222660: v[1] 1
3221222664: v[2] 2
Fjate en que las seis ltimas las de la tabla corresponden a accesos a y con
ndices fuera de rango. Cuando tratbamos de acceder a un elemento inexistente en una
lista Python, el intrprete generaba un error de tipo (error de ndice). Ante una situacin
similar, C no detecta error alguno. Qu hace, pues? Aplica la frmula de indexacin, sin
ms. Estudiemos con calma el primer caso extrao: 2 . C lo interpreta como: acceder
al valor almacenado en la direccin que resulta de sumar 3221222656 (que es donde
empieza el vector ) 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 ocupa
el valor de 2 ! Esa es la razn de que se muestre el valor 12. En la ejecucin del
3
Nuevamente, una advertencia: puede que obtengas un resultado diferente al ejecutar el programa en
tu ordenador. La asignacin de direcciones de memoria a cada objeto de un programa es una decisin que
adopta el compilador con cierta libertad.
4
Si ejecutas el programa en tu ordenador, es probable que obtengas valores distintos para las direcciones
de memoria. Es normal: en cada ordenador y con cada ejecucin se puede reservar una zona de memoria
distinta para los datos.
Introduccin a la programacin con C 96 c UJI
97
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
programa, 2 y 2 son exactamente lo mismo. Encuentra t mismo una explicacin
para los restantes accesos ilcitos.
Ojo! Que se pueda hacer no signica que sea aconsejable hacerlo. En absoluto. Es
ms: debes evitar acceder a elementos con ndices de vector fuera de rango. Si no conviene
hacer algo as, por qu no comprueba C si el ndice est en el rango correcto antes de
acceder a los elementos y, en caso contrario, nos seala un error? Por eciencia. Un pro-
grama que maneje vectores acceder a sus elementos, muy probablemente, en numerosas
ocasiones. Si se ha de comprobar si el ndice est en el rango de valores vlidos, cada
acceso se penalizar con un par de comparaciones y el programa se ejecutar ms lenta-
mente. C sacrica seguridad por velocidad, de ah que tenga cierta fama (justicadsima)
de lenguaje peligroso.
Este programa pretende copiar un vector en otro, pero es incorrecto:
E E
dene 10
int main void
int original 1 2 3 4 5 6 7 8 9 10
int copia
copia original
return 0
Si compilas el programa, obtendrs un error en la lnea 8 que te impedir obtener un
ejecutable: . El mensaje de error nos indica que
no es posible efectuar asignaciones entre tipos vectoriales.
Nuestra intencin era que antes de ejecutar la lnea 8, la memoria presentara este
aspecto:
original
1
0
2
1
3
2
4
3
5
4
6
5
7
6
8
7
9
8
10
9
copia
0 1 2 3 4 5 6 7 8 9
y, una vez ejecutada la lnea 8 llegar a una de estas dos situaciones:
1. obtener en copia una copia del contenido de original:
original
1
0
2
1
3
2
4
3
5
4
6
5
7
6
8
7
9
8
10
9
copia
1
0
2
1
3
2
4
3
5
4
6
5
7
6
8
7
9
8
10
9
2. o conseguir que, como en Python, copia apunte al mismo lugar que original:
original
1
0
2
1
3
2
4
3
5
4
6
5
7
6
8
7
9
8
10
9
copia
0 1 2 3 4 5 6 7 8 9
Introduccin a la programacin con C 97 c UJI
98
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Violacin de segmento
Los errores de acceso a zonas de memoria no reservada se cuentan entre los peores. En
el ejemplo, hemos accedido a la zona de memoria de un vector salindonos del rango
de indexacin vlido de otro, lo cual ha producido resultados desconcertantes.
Pero podra habernos ido an peor: si tratas de escribir en una zona de memoria
que no pertenece a ninguna de tus variables, cosa que puedes hacer asignando un valor
a un elemento de vector fuera de rango, es posible que se genere una excepcin du-
rante la ejecucin del programa: intentar escribir en una zona de memoria que no ha
sido asignada a nuestro proceso dispara, en Unix, una seal de violacin de segmen-
to (segmentation violation) que provoca la inmediata nalizacin de la ejecucin del
programa. Fjate en este programa:
E E
include
int main void
int 10
10000 1
return 0
Cuando lo ejecutamos en un ordenador bajo Unix, obtenemos este mensaje por pantalla:
El programa ha nalizado abruptamente al ejecutar la asignacin de la lnea 7.
Estos errores en la gestin de memoria se maniestan de formas muy variadas: pue-
den producir resultados extraos, nalizar la ejecucin incorrectamente o incluso blo-
quear al computador. Bloquear al computador? S, en sistemas operativos poco robustos,
como Microsoft Windows, el ordenador puede quedarse bloqueado. (Probablemente has
experimentado la sensacin usando algunos programas comerciales en el entorno Mi-
crosoft Windows.) Ello se debe a que ciertas zonas de memoria deberan estar fuera del
alcance de los programas de usuario y el sistema operativo debera prohibir accesos
ilcitos. Unix mata al proceso que intenta efectuar accesos ilcitos (de ah que terminen
con mensajes como Violacin de segmento). Microsoft Windows no tiene la precaucin
de protegerlas, as que las consecuencias son mucho peores.
Pero casi lo peor es que tu programa puede funcionar mal en unas ocasiones y bien
en otras. El hecho de que el programa pueda funcionar mal algunas veces y bien el
resto es peligrossimo: como los errores pueden no manifestarse durante el desarrollo
del programa, cabe la posibilidad de que no los detectes. Nada peor que dar por bueno
un programa que, en realidad, es incorrecto.
Tenlo siempre presente: la gestin de vectores obliga a estar siempre pendiente de
no rebasar la zona de memoria reservada.
Pero no ocurre ninguna de las dos cosas: el identicador de un vector esttico se considera
un puntero inmutable. Siempre apunta a la misma direccin de memoria. No puedes
asignar un vector a otro porque eso signicara cambiar el valor de su direccin. (Observa,
adems, que en el segundo caso, la memoria asignada a copia quedara sin puntero que
la referenciara.)
Si quieres copiar el contenido de un vector en otro debes hacerlo elemento a elemento:
dene 10
Introduccin a la programacin con C 98 c UJI
99
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int main void
int original 1 2 3 4 5 6 7 8 9 10
int copia
int
for 0
copia original
return 0
En Python podamos comparar listas. Por ejemplo, 1 2 3 1 1 1 3 devolva True.
Ya lo habrs adivinado: C no permite comparar vectores. Efectivamente.
Si quieres comparar dos vectores, has de hacerlo elemento a elemento:
dene 3
int main void
int original 1 2 3
int copia 1 1 1 3
int son iguales
son iguales 1 Suponemos que todos los elementos son iguales dos a dos.
0
while son iguales
if copia original Pero basta con que dos elementos no sean iguales...
son iguales 0 ... para que los vectores sean distintos.
if son iguales
printf
else
printf
return 0
Las cadenas son un tipo de datos bsico en Python, pero no en C. Las cadenas de C
son vectores de caracteres (elementos de tipo char) con una peculiaridad: el texto de la
cadena termina siempre en un carcter nulo. El carcter nulo tiene cdigo ASCII 0 y
podemos representarlo 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
primero vale 0 y el segundo vale 48.
Las cadenas estticas en C son, a diferencia de las cadenas Python, mutables. Eso
signica que puedes modicar el contenido de una cadena durante la ejecucin de un
programa.
Introduccin a la programacin con C 99 c UJI
100
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Las cadenas se declaran como vectores de caracteres, as que debes proporcionar el
nmero mximo de caracteres que es capaz de almacenar: su capacidad. Esta cadena, por
ejemplo, se declara con capacidad para almacenar 10 caracteres:
char 10
Puedes inicializar la cadena con un valor en el momento de su declaracin:
char 10
Hemos declarado como un vector de 10 caracteres y lo hemos inicializado asignndole la
cadena . Fjate: hemos almacenado en una cadena de menos de 10 caracteres.
No hay problema: la longitud de la cadena almacenada en es menor que la capacidad
de .
A simple vista, ocupa 6 bytes, pues contamos en ella 6 caracteres, pero no es
as. En realidad, ocupa 7 bytes: los 6 que corresponden a los 6 caracteres que
ves ms uno correspondiente a un carcter nulo al nal, que se denomina terminador de
cadena y es invisible.
Al declarar e inicializar una cadena as:
char 10
la memoria queda de este modo:
a
c
0
a
1
d
2
e
3
n
4
a
5
\0
6 7 8 9
Es decir, es como si hubisemos inicializado la cadena de este otro modo equivalente:
char 10
Recuerda, pues, que hay dos valores relacionados con el tamao de una cadena:
su capacidad, que es la talla del vector de caracteres;
su longitud, que es el nmero de caracteres que contiene, sin contar el terminador
de la cadena. La longitud de la cadena debe ser siempre estrictamente menor que
la capacidad del vector para no desbordar la memoria reservada.
Y por qu toda esta complicacin del terminador de cadena? Lo normal al trabajar
con una variable de tipo cadena es que su longitud vare conforme evoluciona la ejecucin
del programa, pero el tamao de un vector es jo. Por ejemplo, si ahora tenemos en el
texto y ms tarde decidimos guardar en ella el texto , que tiene un
carcter menos, estaremos pasando de esta situacin:
a
c
0
a
1
d
2
e
3
n
4
a
5
\0
6 7 8 9
a esta otra:
a
t
0
e
1
x
2
t
3
o
4
\0
5 6 7 8 9
Introduccin a la programacin con C 100 c UJI
101
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Una cadena de longitud uno no es un carcter
Hemos dicho en el captulo anterior que una cadena de un slo carcter, por ejemplo
, no es lo mismo que un carcter, por ejemplo . Ahora puedes saber por qu: la
diferencia estriba en que ocupa dos bytes, el que corresponde al carcter y el
que corresponde al carcter nulo , mientras que ocupa un solo byte.
Fjate en esta declaracin de variables:
char
char 2
He aqu una representacin grca de las variables y su contenido:
y a
b y
0
\0
1
Recuerda:
Las comillas simples denen un carcter y un carcter ocupa un solo byte.
Las comillas dobles denen una cadena. Toda cadena incluye un carcter nulo
invisible al nal.
Fjate en que la zona de memoria asignada a sigue siendo la misma. El truco del
terminador ha permitido que la cadena decrezca. Podemos conseguir tambin que crezca
a voluntad. . . pero siempre que no se rebase la capacidad del vector.
Hemos representado las celdas a la derecha del terminador como cajas vacas, pero
no es cierto que lo estn. Lo normal es que contengan valores arbitrarios, aunque eso no
importa mucho: el convenio de que la cadena termina en el primer carcter nulo hace que
el resto de caracteres no se tenga en cuenta. Es posible que, en el ejemplo anterior, la
memoria presente realmente este aspecto:
a
t
0
e
1
x
2
t
3
o
4
\0
5
a
6
u
7
\0
8
x
9
Por comodidad representaremos las celdas a la derecha del terminador con cajas vacas,
pues no importa en absoluto lo que contienen.
Qu ocurre si intentamos inicializar una zona de memoria reservada para slo 10
chars con una cadena de longitud mayor que 9?
char 10
!
Mal!
Estaremos cometiendo un gravsimo error de programacin que, posiblemente, no detecte
el compilador. Los caracteres que no caben en se escriben en la zona de memoria que
sigue a la zona ocupada por .
a
s
0
u
1
p
2
e
3
r
4
c
5
a
6
l
7
i
8
f
9
r a g i l i s t i c o e s p i a l i d o s o \0
Ya vimos en un apartado anterior las posibles consecuencias de ocupar memoria que no
nos ha sido reservada: puede que modiques el contenido de otras variables o que trates
de escribir en una zona que te est vetada, con el consiguiente aborto de la ejecucin
del programa.
Introduccin a la programacin con C 101 c UJI
102
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Como resulta que en una variable con capacidad para, por ejemplo, 80 caracteres
slo caben realmente 79 caracteres aparte del nulo, adoptaremos una curiosa prctica al
declarar variables de cadena que nos permitir almacenar los 80 caracteres (adems del
nulo) sin crear una constante confusin con respecto al nmero de caracteres que caben
en ellas:
include
dene 80
int main void
char cadena 1 Reservamos 81 caracteres: 80 caracteres ms el terminador
return 0
Las cadenas se muestran con printf y la adecuada marca de formato sin que se presenten
dicultades especiales. Lo que s resulta problemtico es leer cadenas. La funcin scanf
presenta una seria limitacin: slo puede leer palabras, no frases. Ello nos obligar
a presentar una nueva funcin (gets). . . que se lleva fatal con scanf .
Salida con printf
Empecemos por considerar la funcin printf , que muestra cadenas con la marca de formato
. Aqu tienes un ejemplo de uso:
include
dene 80
int main void
char cadena 1
printf cadena
return 0
Al ejecutar el programa obtienes en pantalla esto:
Puedes alterar la presentacin de la cadena con modicadores:
include
dene 80
int main void
char cadena 1
printf cadena
Introduccin a la programacin con C 102 c UJI
103
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
printf cadena
printf cadena
return 0
Y si deseamos mostrar una cadena carcter a carcter? Podemos hacerlo llamando a
printf sobre cada uno de los caracteres, pero recuerda que la marca de formato asociada
a un carcter es :
include
dene 80
int main void
char cadena 1
int
0
while cadena
printf cadena
return 0
Este es el resultado de la ejecucin:
Entrada con scanf
Poco ms hay que contar acerca de printf . La funcin scanf es un reto mayor. He aqu
un ejemplo que pretende leer e imprimir una cadena en la que podemos guardar hasta
80 caracteres (sin contar el terminador nulo):
include
dene 80
int main void
Introduccin a la programacin con C 103 c UJI
104
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
char cadena 1
scanf cadena
printf cadena
return 0
Ojo! No hemos puesto el operador delante de cadena! Es un error? No. Con las
cadenas no hay que poner el carcter del identicador al usar scanf . Por qu? Porque
scanf espera una direccin de memoria y el identicador, por la dualidad vector-puntero,
es una direccin de memoria!
Recuerda: cadena 0 es un char, pero cadena, sin ms, es la direccin de memoria
en la que empieza el vector de caracteres.
Ejecutemos el programa e introduzcamos una palabra:
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
99 Es vlida esta otra forma de leer una cadena? Prubala en tu ordenador.
include
dene 80
int main void
char cadena 1
scanf cadena 0
printf cadena
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cuando scanf recibe el valor asociado a cadena, recibe una direccin de memoria 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 ejecucin.
Y por qu printf no muestra por pantalla una simple direccin de memoria cuando
ejecutamos la llamada printf cadena ? Si es 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
dene 80
Introduccin a la programacin con C 104 c UJI
105
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int main void
char cadena 1
scanf cadena
printf cadena
return 0
Si al ejecutar el programa tecleamos un par de palabras, slo se muestra la primera:
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 caracteres
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 se
aade un terminador.
Aqu tienes un ejemplo:
include
dene 11
int main void
char 1 1
printf gets
printf gets
printf
return 0
Ejecutemos el programa:
!
Mal!
return 0
Si deseas asignar un literal de cadena, tendrs que hacerlo con la ayuda de strcpy:
include
dene 10
int main void
char 1
strcpy
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 tabla
Introduccin a la programacin con C 109 c UJI
110
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 asegurarte 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
dene 10
int main void
char original 1
char copia 1
strncpy copia original 1 Copia, a lo sumo, 1 caracteres.
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 carcter,
por si acaso:
include
dene 10
int main void
char original 1
char copia 1
strncpy copia original 1
copia
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 ma-
ysculas y copie en otra una versin de la misma cuyas letras sean todas minsculas. No
uses la funcin strcpy.
103 Disea un programa que lea una cadena que posiblemente contenga letras ma-
ysculas 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 minsculas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 110 c UJI
111
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
El convenio de terminar una cadena con el carcter nulo permite conocer fcilmente la
longitud de una cadena:
include
dene 80
int main void
char 1
int
printf
gets
0
while
printf
return 0
Calcular la longitud de una cadena es una operacin frecuentemente utilizada, as
que est predenida en la biblioteca de tratamiento de cadenas. Si inclumos la cabecera
, podemos usar la funcin strlen (abreviatura de string length):
include
include
dene 80
int main void
char 1
int
printf
gets
strlen
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. Has de
estar al tanto, pues, de la fuente de ineciencia que puede suponer utilizar directamente
strlen en lugares crticos como los bucles. Por ejemplo, esta funcin cuenta las vocales
minsculas de una cadena leda por teclado:
include
include
dene 80
int main void
char 1
Introduccin a la programacin con C 111 c UJI
112
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
El estilo C
El programa que hemos presentado para calcular la longitud de una cadena es 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
dene 80
int main void
char 1
int
printf
gets
0
while Observa que no hay sentencia alguna en el while.
printf 1
return 0
El operador de postincremento permite aumentar en uno el valor de justo despus de
consultar el valor de . Eso s, hemos tenido que modicar el valor mostrado como
longitud, pues ahora acaba valiendo uno ms.
Es ms, ni siquiera es necesario efectuar comparacin alguna. El bucle se puede
sustituir por este otro:
0
while
El bucle funciona correctamente porque el valor signica falso cuando se inter-
preta 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 5 0 0 0 0 0
1
Hay dos posibles interpretaciones:
Se evala primero la parte derecha de la asignacin, as que pasa a valer 2 y
se asigna ese valor en 2 .
Se evala primero la asignacin, con lo que se asigna el valor 1 en 1 y,
despus, se incrementa el valor de , que pasa a valer 2.
Qu hace C? No se sabe. La especicacin del lenguaje estndar indica que el resultado
est indenido. Cada compilador elige qu hacer, as que ese tipo de sentencias pueden
dar problemas de portabilidad. Conviene, pues, evitarlas.
int contador
Introduccin a la programacin con C 112 c UJI
113
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
while o for
Los bucles while pueden sustituirse muchas veces por bucles for equivalentes, bastante
ms compactos:
include
dene 80
int main void
char 1
int
printf
gets
for 0 Tampoco hay sentencia alguna en el for.
printf
return 0
Tambin aqu es superua la comparacin:
for 0
Todas las versiones del programa que hemos presentado son equivalentes. Escoger
una u otra es cuestin de estilo.
printf
gets
contador 0
for 0 strlen
if
contador
printf contador
return 0
Pero tiene un problema de eciencia. 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 tiene,
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
dene 80
int main void
char 1
int longitud contador
printf
gets
Introduccin a la programacin con C 113 c UJI
114
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
longitud strlen cadena
contador 0
for 0 longitud
if
contador
printf contador
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 en
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 segunda
cadena no est contenida en la primera, el programa nos lo har saber.
(Ejemplo: si la primera cadena es y la segunda 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 cadena.
Si la segunda cadena no est contenida en la primera, el programa nos lo har saber.
(Ejemplo: si la primera cadena es y la segunda 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 nal 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 resultado
en pantalla. El programa nalizar cuando el usuario escriba una lnea en blanco.
111 Disea un programa C que lea un entero y una cadena y muestre por pantalla
el valor (en base 10) de la cadena si se interpreta como un nmero en base . El valor
de debe estar comprendido entre 2 y 16. Si la cadena contiene un carcter que no
corresponde a un dgito en base , noticar el error y no efectuar clculo alguno.
Ejemplos:
si es y es 16, se mostrar el valor 255;
si es y es 15, se noticar un error:
;
si es y 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 con C 114 c UJI
115
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con 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 bucles.
Este programa, por ejemplo, pide dos cadenas y concatena la segunda a la primera:
include
dene 80
int main void
char 1 1
int longa longb
int
printf gets
printf gets
longa strlen
longb strlen
for 0 longb
longa
longa longb
printf
return 0
Pero es mejor usar la funcin de librera strcat (por string concatenate):
include
include
dene 80
int main void
char 1 1
printf
gets
printf
gets
strcat Equivale a la asignacin Python
printf
return 0
Si quieres dejar el resultado de la concatenacin en una variable distinta, debers
actuar en dos pasos:
include
include
dene 80
int main void
char 1 1 1
Introduccin a la programacin con C 115 c UJI
116
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
printf
gets
printf
gets
strcpy sta seguida de...
strcat ... sta equivale a la sentencia Python
printf
return 0
Recuerda que es responsabilidad del programador asegurarse de que la cadena que
recibe la concatenacin dispone de capacidad suciente para almacenar la cadena resul-
tante.
Por cierto, el operador de repeticin de cadenas que encontrbamos en Python (ope-
rador ) no est disponible en C ni hay funcin predenida que lo proporcione.
Un carcter no es una cadena
Un error frecuente es intentar aadir un carcter a una cadena con strcat o asignrselo
como nico carcter con strcpy:
char linea 10
char caracter
strcat linea caracter
!
Mal!
strcpy linea
!
Mal!
Recuerda: los dos datos de strcat y strcpy han de ser cadenas y no es aceptable que
uno de ellos sea un carcter.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
113 Escribe un programa C que lea el nombre y los dos apellidos de una persona en
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, y ,
y devuelve un entero. El entero que resulta de efectuar la llamada strcmp codica
el resultado de la comparacin:
Introduccin a la programacin con C 116 c UJI
117
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
es menor que cero si la cadena es menor que ,
es 0 si la cadena es igual que , y
es mayor que cero si la cadena es mayor que .
Naturalmente, menor signica que va delante en orden alfabtico, y mayor que va detrs.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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, y (con capacidad para 80 carac-
teres), y muestre por pantalla 1 si es menor que , 0 si es igual que , y 1 si es
mayor que . Est prohibido que utilices la funcin strcmp.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
No slo contiene funciones tiles para el tratamiento de cadenas. En
encontrars unas funciones que permiten hacer cmodamente preguntas acerca de los
caracteres, como si son maysculas, minsculas, dgitos, etc:
isalnum carcter : devuelve cierto (un entero cualquiera distinto de cero) si carcter
es una letra o dgito, y falso (el valor entero 0) en caso contrario,
isalpha carcter : devuelve cierto si carcter es una letra, y falso en caso contrario,
isblank carcter : devuelve cierto si carcter es un espacio en blanco o un tabu-
lador,
isdigit carcter devuelve cierto si carcter es un dgito, y falso en caso contrario,
isspace carcter : devuelve cierto si carcter es un espacio en blanco, un salto de
lnea, un retorno de carro, un tabulador, etc., y falso en caso contrario,
islower carcter : devuelve cierto si carcter es una letra minscula, y falso en
caso contrario,
isupper carcter : devuelve cierto si carcter es una letra mayscula, y falso en
caso contrario.
Tambin en encontrars un par de funciones tiles para convertir caracteres de
minscula a mayscula y viceversa:
toupper carcter : devuelve la mayscula asociada a carcter, si la tiene; si no,
devuelve el mismo carcter,
tolower carcter : devuelve la minscula asociada a carcter, si la tiene; si no,
devuelve el mismo carcter.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
118 Qu problema presenta este programa?
Introduccin a la programacin con C 117 c UJI
118
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
include
include
int main void
char 2
if isalpha
printf
else
printf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
sprintf
Hay una funcin que puede simplicar notablemente la creacin de cadenas cuyo con-
tenido se debe calcular a partir de uno o ms valores: sprintf , disponible incluyendo la
cabecera (se trata, en cierto modo, de la operacin complementaria de sscanf ).
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
dene 80
int main void
char 1
char 1
char 1
sprintf
printf
return 0
Si ejecutas el programa aparecer lo siguiente en pantalla:
Como puedes ver, se ha asignado a el valor de seguido de un espacio en blanco y
de la cadena . Podramos haber conseguido el mismo efecto con llamadas a strcpy ,
strcat y strcat , pero sprintf resulta ms legible y no cuesta mucho apren-
der a usarla, pues ya sabemos usar printf . No olvides que t eres responsable de que la
informacin que se almacena en 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 haber
escrito en Python as:
Ojo: programa Python
.
include
dene 10
int main void
oat o l
oat p
Introduccin a la programacin con C 125 c UJI
126
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int las a columnas a las b columnas b
int las s columnas s las p columnas p
int
Lectura de la matriz
printf scanf las a
printf scanf columnas a
for 0 las a
for 0 columnas a
printf scanf
Lectura de la matriz
printf scanf las b
printf scanf columnas b
for 0 las b
for 0 columnas b
printf scanf
Clculo de la suma
if las a las b columnas a columnas b
las s las a
columnas s columnas a
for 0 las s
for 0 las s
Clculo del producto
if columnas a las b
las p las a
columnas p columnas b
for 0 las p
for 0 columnas p
0.0
for 0 columnas a
Impresin del resultado de la suma
if las a las b columnas a columnas b
printf
for 0 las s
for 0 columnas s
printf
printf
else
printf
Impresin del resultado del producto
if columnas a las b
printf
for 0 las p
for 0 columnas p
printf
Introduccin a la programacin con C 126 c UJI
127
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
printf
else
printf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
129 Extiende el programa de calculadora matricial para efectuar las siguientes ope-
raciones:
Producto de una matriz por un escalar. (La matriz resultante tiene la misma di-
mensin que la original y cada elemento se obtiene multiplicando el escalar por la
celda correspondiente de la matriz original.)
Transpuesta de una matriz. (La transpuesta de una matriz de es una matriz
de en la que el elemento de la la y columna tiene el mismo valor que
el que ocupa la celda de la la y columna en la matriz original.)
130 Una matriz tiene un valle si el valor de una de sus celdas es menor que el de
cualquiera de sus 8 celdas vecinas. Disea un programa que lea una matriz (el usuario
te indicar de cuntas las y columnas) y nos diga si la matriz tiene un valle o no. En
caso armativo, 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 las 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
), la recta de ajuste
= + que minimiza el cuadrado de la distancia vertical de todos los puntos a la
Introduccin a la programacin con C 141 c UJI
142
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
recta se puede obtener efectuando los siguientes clculos:
n =
i
i=1
x
i
i
i=1
i
i=1
x
i
i
i=1
x
i
2
i
i
i=1
x
2
i
,
l =
i
i=1
i
i=1
x
2
i
i
i=1
x
i
i
i=1
x
i
i
i=1
x
2
i
i
i=1
x
i
2
Las frmulas asustan un poco, pero no contienen ms que sumatorios. El programa que
vamos a escribir lee una serie de puntos (con un nmero mximo de, pongamos, 1000), y
muestra los valores de n y l.
Modelaremos los puntos con un registro:
struct Punto
oat x
El vector de puntos, al que en principio denominaremos p, tendr talla 1000:
dene 1000
struct Punto p
Pero 1000 es el nmero mximo de puntos. El nmero de puntos disponibles efectivamente
ser menor o igual y su valor deber estar accesible en alguna variable. Olvidmonos del
vector p: nos conviene denir un registro en el que se almacenen vector y talla real del
vector.
struct ListaPuntos
struct Punto punto
int talla
Observa que estamos anidando structs.
Necesitamos ahora una variable del tipo que hemos denido:
include
dene 1000
struct Punto
oat x
struct ListaPuntos
struct Punto punto
int talla
int main void
struct ListaPuntos lista
Reexionemos brevemente sobre cmo podemos acceder a la informacin de la variable
lista:
Introduccin a la programacin con C 142 c UJI
143
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Expresin Tipo y signicado
lista Es un valor de tipo struct ListaPuntos. Contiene un vector
de 1000 puntos y un entero.
lista talla Es un entero. Indica cuntos elementos del vector contie-
nen informacin.
lista punto Es un vector de 1000 valores de tipo struct Punto.
lista punto 0 Es el primer elemento del vector y es de tipo struct Punto,
as que est compuesto por dos otantes.
lista punto 0 Es el campo del primer elemento del vector. Su tipo es
oat.
lista punto lista talla 1 Es el campo del ltimo elemento con informacin del
vector. Su tipo es oat.
lista punto Error! Si lista punto es un vector, no podemos acceder
al campo .
lista punto 0 Error! Si lo anterior era incorrecto, sto lo es an ms.
lista punto 0 Error! Qu hace un punto antes del operador de inde-
xacin?
lista 0 punto Error! La variable lista no es un vector, as que no puedes
aplicar el operador de indexacin sobre ella.
Ahora que tenemos ms claro cmo hemos modelado la informacin, vamos a resolver
el problema propuesto. Cada uno de los sumatorios se precalcular cuando se hayan ledo
los puntos. De ese modo, simplicaremos signicativamente las expresiones de clculo
de y . Debes tener en cuenta que, aunque en las frmulas se numeran los puntos
empezando en 1, en C se empieza en 0.
Veamos el programa completo:
include
dene 1000
struct Punto
oat
struct ListaPuntos
struct Punto punto
int talla
int main void
struct ListaPuntos lista
oat sx sy sxy sxx
oat
int
Lectura de puntos
printf scanf lista talla
for 0 lista talla
printf scanf lista punto
printf scanf lista punto
Introduccin a la programacin con C 143 c UJI
144
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Clculo de los sumatorios
sx 0.0
for 0 lista talla
sx lista punto
sy 0.0
for 0 lista talla
sy lista punto
sxy 0.0
for 0 lista talla
sxy lista punto lista punto
sxx 0.0
for 0 lista talla
sxx lista punto lista punto
Clculo de m y b e impresin de resultados
if sx sx lista talla sxx 0
printf
else
sx sy lista talla sxy sx sx lista talla sxx
printf
sy sxx sx sxy lista talla sxx sx sx
printf
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 denas) llamada representantes. A
continuacin, ir leyendo nuevos puntos hasta que se introduzca el punto de coordenadas
(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 distancia
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). Dene 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 realmente el
valor almacenado con un campo del registro. Escribe un programa que use dos variables
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 resultado).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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
chero). 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 144 c UJI
145
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
la duracin (en minutos y segundos),
el ao de publicacin.
Deniremos un registro para almacenar los datos de un CD y otro para representar la
duracin, ya que sta cuenta con dos valores (minutos y segundos):
dene 80
dene 40
struct Tiempo
int minutos
int segundos
struct CompactDisc
char titulo 1
char interprete 1
struct Tiempo duracion
int anyo
Vamos a usar un vector para almacenar la coleccin, deniremos un mximo nmero de
CDs: 1000. Eso no signica que la coleccin tenga 1000 discos, sino que puede tener a lo
sumo 1000. Y cuntos tiene en cada instante? Utilizaremos una variable para mantener el
nmero de CDs presente en la coleccin. Mejor an: deniremos un nuevo tipo de registro
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 vector.
He aqu la denicin de la estructura y la declaracin de la coleccin de CDs:
dene 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 cheros en C, pues
al nalizar cada ejecucin se pierde toda la informacin registrada.)
He aqu el programa completo:
include
include
dene 80
Introduccin a la programacin con C 145 c UJI
146
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
dene 1000
dene 80
dene 40
enum Anyadir 1 ListadoCompleto ListadoPorInterprete Suprimir Salir
struct Tiempo
int minutos
int segundos
struct CompactDisc
char titulo 1
char interprete 1
struct Tiempo duracion
int anyo
struct Coleccion
struct CompactDisc cd
int cantidad
int main void
struct Coleccion mis cds
int opcion
char titulo 1 interprete 1
char linea Para evitar los problemas de 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 opcion
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
printf
Introduccin a la programacin con C 146 c UJI
147
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
gets linea
sscanf linea mis cds cd mis cds cantidad duracion minutos
printf
gets linea
sscanf linea mis cds cd mis cds cantidad duracion segundos
printf
gets linea
sscanf linea mis cds cd mis cds cantidad anyo
mis cds cantidad
break
case ListadoCompleto Listar todo.
for 0 mis cds cantidad
printf
mis cds cd titulo
mis cds cd interprete
mis cds cd duracion minutos
mis cds cd duracion segundos
mis cds cd anyo
break
case ListadoPorInterprete Listar por intrprete.
printf gets interprete
for 0 mis cds cantidad
if strcmp interprete mis cds cd interprete 0
printf
mis cds cd titulo
mis cds cd interprete
mis cds cd duracion minutos
mis cds cd duracion segundos
mis cds cd anyo
break
case Suprimir Suprimir CD.
printf gets titulo
printf gets interprete
for 0 mis cds cantidad
if strcmp titulo mis cds cd titulo 0
strcmp interprete mis cds cd interprete 0
break
if mis cds cantidad
for 1 mis cds cantidad
mis cds cd 1 mis cds cd
mis cds cantidad
break
while opcion Salir
printf
return 0
En nuestro programa hemos separado la denicin del tipo struct Coleccion de la
declaracin de la variable mis cds. No es necesario. Podemos denir el tipo y declarar
la variable en una sola sentencia:
struct Coleccion
Introduccin a la programacin con C 147 c UJI
148
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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
denir un nuevo registro: el que modela una cancin. De cada cancin nos interesa el
ttulo, el autor y la duracin:
struct Cancion
char titulo 1
char autor 1
struct Tiempo duracion
Hemos de modicar el registro struct CompactDisc para que almacene hasta, digamos,
20 canciones:
dene 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 modicada,
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 canciones
while mis cds cd mis cds cantidad canciones
for 0 mis cds cd mis cds cantidad canciones
printf
gets mis cds cd mis cds cantidad cancion titulo
printf
gets mis cds cd mis cds cantidad cancion autor
printf
Introduccin a la programacin con C 148 c UJI
149
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
gets linea
sscanf linea
mis cds cd mis cds cantidad cancion duracion minutos
printf
gets linea
sscanf linea
mis cds cd mis cds cantidad cancion duracion segundos
segundos 0
for 0 mis cds cd mis cds cantidad canciones
segundos 60 mis cds cd mis cds cantidad cancion duracion minutos
mis cds cd mis cds cantidad cancion duracion segundos
mis cds cd mis cds cantidad duracion minutos segundos 60
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.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 progra-
ma 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, borrar
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 palabra struct.
C permite denir nuevos nombres para los tipos existentes con la palabra clave typedef .
He aqu un posible uso de typedef :
dene 80
dene 40
struct Tiempo
int minutos
int segundos
typedef struct Tiempo TipoTiempo
struct Cancion
char titulo 1
char autor 1
TipoTiempo duracion
Introduccin a la programacin con C 149 c UJI
150
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 denir un nuevo tipo a partir de un registro:
dene 80
dene 40
typedef struct
int minutos
int segundos
TipoTiempo
typedef struct
char titulo 1
char autor 1
TipoTiempo duracion
TipoCancion
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 nombres
de tipos de datos (denidos con typedef y struct o slo con struct). Es un buen convenio
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 renombramiento
de tipos no slo sirve para eliminar la molesta palabra clave struct, tambin permite
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 denimos un nuevo
tipo, el programa es ms legible:
Introduccin a la programacin con C 150 c UJI
151
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 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 la
lnea que empieza por typedef , aunque hayamos denido decenas de variables del tipo
TipoEdad:
typedef int TipoEdad
TipoEdad edad
Los cambios de tipos y sus consecuencias
Hemos dicho que typedef dene nuevos tipos y facilita sustituir un tipo por otro en dife-
rentes versiones de un programa. Es cierto, pero problemtico. Considera que denimos
un tipo edad como carcter sin signo y que denimos una variable de dicho tipo cuyo
valor leemos de teclado:
include
typedef unsigned char TipoEdad
int main void
TipoEdad mi edad
scanf mi edad
printf mi edad
return 0
Qu pasa si, posteriormente, decidimos que el TipoEdad debiera ser un entero de
32 bits? He aqu una versin errnea del programa:
include
typedef int TipoEdad
int main void
TipoEdad mi edad
printf
scanf mi edad
!
Mal!
printf mi edad
!
Mal!
return 0
Y por qu es errneo? Porque debiramos haber modicado 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 modicaciones. Otros lenguajes, como C soportan
de forma mucho ms exible la posibilidad de cambiar tipos de datos, ya que no obligan
al programador a modicar un gran nmero de lneas del programa.
Introduccin a la programacin con C 151 c UJI
152
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Un momento despus, Alicia atravesaba el cristal, y saltaba gilmente a la
habitacin del Espejo.
iwis C/oii, Alicia a travs del espejo.
Vamos a estudiar la denicin y uso de funciones en C. El concepto es el mismo que ya
estudiaste al aprender Python: una funcin es un fragmento de programa parametrizado
que efecta unos clculos y, o devuelve un valor como resultado, o tiene efectos laterales
(modicacin 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 parmetros.
En este aspecto, C presenta ciertas limitaciones frente a Python, pero tambin ciertas
ventajas. Entre las limitaciones tenemos la necesidad de dar un tipo a cada parmetro
y al valor de retorno, y entre las ventajas, la posibilidad de pasar variables escalares y
modicar su valor en el cuerpo de la funcin (gracias al uso de punteros).
Estudiaremos tambin la posibilidad de declarar y usar variables locales, y volveremos
a tratar la recursividad. Adems, veremos cmo implementar nuestros propios mdulos
mediante las denominadas unidades de compilacin y la creacin de cheros de cabecera.
Finalmente, estudiaremos la denicin y el uso de macros, una especie de pseudo-
funciones que gestiona el preprocesador de C.
En C no hay una palabra reservada (como def en Python) para iniciar la denicin de
una funcin. El aspecto de una denicin de funcin en C es ste:
tipo de retorno identicador parmetros
cuerpo de la funcin
El cuerpo de la funcin puede contener declaraciones de variables locales (tpicamente
en sus primeras lneas).
Aqu tienes un ejemplo de denicin de funcin: una funcin que calcula el logaritmo
en base (para entero) de un nmero . La hemos denido de un modo menos compacto
de lo que podemos hacer para ilustrar los diferentes elementos que puedes encontrar en
una funcin:
oat logaritmo oat int
oat logbase resultado
152
153
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
logbase log10
resultado log10 logbase
return resultado
Detengmonos a analizar brevemente cada uno de los componentes de la denicin
de una funcin e identiqumoslos 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 denir procedimientos, es decir, funciones sin
valor de retorno). Puedes considerar esto como una limitacin frente a Python: en
C, cada funcin devuelve valores de un nico tipo. No podemos denir una funcin
que, segn convenga, devuelva un entero, un otante o una cadena, como hicimos
en Python cuando nos convino.
En nuestro ejemplo, la funcin devuelve un valor de tipo oat.
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
El identicador es el nombre de la funcin y, para estar bien formado, debe observar
las mismas reglas que se siguen para construir nombres de variables. Eso s, no
puedes denir una funcin con un identicador que ya hayas usado para una
variable (u otra funcin).
El identicador de nuestra funcin de ejemplo es logaritmo:
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
Entre parntesis aparece una lista de declaraciones de parmetros separadas por
comas. Cada declaracin de parmetro indica tanto el tipo del mismo como su
identicador
1
.
Nuestra funcin tiene dos parmetros, uno de tipo oat y otro de tipo int.
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
1
Eso en el caso de parmetros escalares. Los parmetros de tipo vectorial se estudiarn ms adelante.
Introduccin a la programacin con C 153 c UJI
154
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 que sigue
una o ms sentencias C. La sentencia return permite nalizar 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 naliza 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 declaracin de
variables (locales) y est formado por tres sentencias, dos de asignacin y una de
devolucin de valor:
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
La sentencia (o sentencias) de devolucin de valor forma(n) parte del cuerpo y
empieza(n) con la palabra return. Una funcin puede incluir ms de una sentencia
de devolucin de valor, pero debes tener en cuenta que la ejecucin de la funcin
naliza con la primera ejecucin de una sentencia return.
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
La funcin logaritmo se invoca como una funcin cualquiera de :
include
include
oat logaritmo oat int
oat logbase resultado
logbase log10
resultado log10 logbase
return resultado
int main void
oat
logaritmo 128.0 2
printf
Introduccin a la programacin con C 154 c UJI
155
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
return 0
Si ejecutamos el programa tenemos:
Es necesario que toda funcin se dena en el programa antes de la primera lnea
en que se usa. Por esta razn, todas nuestras funciones se denen delante de la funcin
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 programa, ya
que usamos la funcin log10. Recuerda, adems, que al compilar se debe enlazar con la
biblioteca matemtica, es decir, se debe usar la opcin de .
Esta ilustracin te servir para identicar los diferentes elementos de la denicin
de una funcin y de su invocacin:
oat logaritmo oat int
oat logbase resultado
logbase log10 ;
resultado log10 logbase
return resultado
.
.
.
int main void
oat
.
.
.
logaritmo 128.0 2
.
.
.
Cabecera
Parmetros formales (o simplemente parmetros)
Tipo de retorno
Cuerpo
Identicador
Declaracin de variables locales
Sentencia de devolucin de valor
Llamada, invocacin o activacin
Identicador
Argumentos o parmetros reales
Ah! Te hemos dicho antes que la funcin logaritmo no es muy compacta. Podramos
haberla denido as:
oat logaritmo oat int
return log10 log10
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
147 Dene una funcin que reciba un int y devuelva su cuadrado.
2
Nuevamente hemos de matizar una armacin: en realidad slo es necesario que se haya declarado el
prototipo de la funcin. Ms adelante daremos ms detalles.
Introduccin a la programacin con C 155 c UJI
156
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
148 Dene una funcin que reciba un oat y devuelva su cuadrado.
149 Dene una funcin que reciba dos oat y devuelva 1 (cierto) si el primero es
menor que el segundo y 0 (falso) en caso contrario.
150 Dene una funcin que calcule el volumen de una esfera a partir de su radio :.
(Recuerda que el volumen de una esfera de radio : es 4;3:
3
.)
151 El seno hiperblico de x es
sinh =
c
x
c
x
2
Disea una funcin C que efecte el calculo de senos hiperblicos. (Recuerda que c
x
se
puede calcular con la funcin exp, disponible incluyendo y enlazando el programa
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 (x
0
,
0
) a una recta ^x +B +C = 0 viene dada por
J =
^x
0
+B
0
+C
^
2
+B
2
Disea una funcin que reciba los valores que denen una recta y los valores que denen
un punto y devuelva la distancia del punto a la recta.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Veamos otro ejemplo de denicin de funcin:
int minimo int o int l int t
if o l
if o t
return o
else
return t
else
if l t
return l
else
return t
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 ejecuta,
naliza la ejecucin de la funcin y se devuelve el valor indicado.
Observa que main es una funcin. Su cabecera es int main void . Qu signica
void? Signica que no hay parmetros. Pero no nos adelantemos. En este mismo captulo
hablaremos de funciones sin parmetros.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
154 Dene una funcin que, dada una letra minscula del alfabeto ingls, devuelva
su correspondiente letra mayscula. Si el carcter recibido como dato no es una letra
minscula, la funcin la devolver inalterada.
155 Qu error encuentras en esta funcin?
Introduccin a la programacin con C 156 c UJI
157
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int minimo int
if
return
if
return
return
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cada funcin puede denir sus propias variables locales denindolas en su cuerpo. C
permite, adems, denir variables fuera del cuerpo de cualquier funcin: son las variables
globales.
Las variables que declaramos justo al principio del cuerpo de una funcin son variables
locales. Este programa, por ejemplo, declara dos variables locales para calcular el suma-
torio
=
. La variable local a sumatorio con identicador nada tiene que ver con la
variable del mismo nombre que es local a main:
include
int sumatorio int int
int Variables locales a sumatorio.
0
for
return
int main void
int Variable local a main.
for 1 10
printf sumatorio 1
return 0
Las variables locales y de sumatorio slo viven durante las llamadas a sumatorio.
La zona en la que es visible una variable es su mbito. Las variables locales slo son
visibles en el cuerpo de la funcin en la que se declaran; se es su mbito.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
156 Disea una funcin que calcule el factorial de un entero .
157 Disea una funcin que calcule
1 + +
2
2!
+
3
3!
+
4
4!
+
Disea una funcin que aproxime el valor de
, aunque hay
formas ms ecientes de calcular /1!,
2
/2!,
3
/3!, . . . , sabes cmo? Plantate cmo
generar un trmino de la forma
num 2
while num 0
return
int main void
unsigned int numero
int bitsnumero
printf scanf numero
bitsnumero bits numero
printf bitsnumero numero
return 0
Al ejecutar el programa y teclear el nmero 128 se muestra por pantalla lo siguiente:
Como puedes ver, el valor de numero permanece inalterado tras la llamada a bits,
aunque en el cuerpo de la funcin se modica el valor del parmetro num (que toma el
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 pues, 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 pila. Cada
funcin activada se representa en la pila mediante un registro de activacin o trama de
Introduccin a la programacin con C 163 c UJI
164
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
activacin. Se trata de una zona de memoria en la que se almacenan las variables locales
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 automticamente
la funcin main. En lla tenemos dos variables locales: numero y bitsnumero.
main
numero
bitsnumero
Si el usuario teclea el valor 128, ste se almacena en numero:
main
128 numero
bitsnumero
Cuando se produce la llamada a la funcin bits, se crea una nueva trama de activacin:
llamada desde lnea 21
main
128 numero
bitsnumero
bits
num
b
El parmetro num recibe una copia del contenido de numero y se inicializa la variable
local con el valor 0:
llamada desde lnea 21
main
128 numero
bitsnumero
bits
128 num
0 b
Tras ejecutar el bucle de bits, la variable vale 8. Observa que aunque num ha modicado
su valor y ste provena originalmente de numero, el valor de numero no se altera:
llamada desde lnea 21
main
128 numero
bitsnumero
bits
0 num
8 b
La trama de activacin de bits desaparece ahora, pero dejando constancia del valor de-
vuelto por la funcin:
Introduccin a la programacin con C 164 c UJI
165
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
main
128 numero
bitsnumero
8 return
Y, nalmente, el valor devuelto se copia en bitsnumero:
main
128 numero
8 bitsnumero
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 modicamos
el valor de un parmetro en el cuerpo de la funcin, estamos modicando el valor de la
copia, no el de la variable original.
Este otro programa declara numero como una variable global y trabaja directamente
con dicha variable:
include
unsigned int numero
int bits void
int 0
do
numero 2
while numero 0
return
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 accesibles
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:
main bitsnumero
variables globales
numero
Introduccin a la programacin con C 165 c UJI
166
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
En main se da valor a la variable global numero:
main bitsnumero
variables globales
128 numero
Y se llama a continuacin a bits sin argumento alguno:
bits
b
llamada desde lnea 22
main bitsnumero
variables globales
128 numero
El clculo de bits modica el valor de numero. Tras la primera iteracin del bucle while,
sta es la situacin:
bits
1 b
llamada desde lnea 22
main bitsnumero
variables globales
64 numero
Cuando naliza la ejecucin de bits tenemos:
8 return
main bitsnumero
variables globales
0 numero
Entonces se copia el valor devuelto en bitsnumero:
main 8 bitsnumero
variables globales
0 numero
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 justicado acceder a variables globales, especialmente cuando ests aprendiendo.
Evtalas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
168 Estudia este programa y muestra grcamente el contenido de la memoria cuando
se van a ejecutar por primera vez las lneas 24, 14 y 5.
Introduccin a la programacin con C 166 c UJI
167
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
include
int cuadrado int
return
int sumatorio int int
int
0
for
cuadrado
return
int main void
int
10
20
printf sumatorio
return 0
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 ulti-
moprimo 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). Muestra 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 0
int siguienteprimo void
int esprimo
do
ultimoprimo
esprimo 1
for 2 ultimoprimo 2
if ultimoprimo 0
esprimo 0
break
while esprimo
return ultimoprimo
int main void
int
Introduccin a la programacin con C 167 c UJI
168
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
printf
for 0 10
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 cierta
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
dene 10
int cuenta primos int Cuenta el nmero de primos entre 1 y .
char criba
int numprimos
Comprobemos que el argumento es vlido
if
return 1 Devolvemos 1 si no es vlido.
Inicializacin
criba 0 0
for 1
criba 1
Criba de Eratstenes
for 2
if criba
for 2
criba 0
Conteo de primos
numprimos 0
for 0
if criba
numprimos
return numprimos
int main void
int hasta cantidad
printf scanf hasta
cantidad cuenta primos hasta
if cantidad 1
printf 1
else
printf hasta cantidad
return 0
Introduccin a la programacin con C 168 c UJI
169
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Cuando el programa inicia su ejecucin, se crea una trama de activacin en la que se
albergan las variables hasta y cantidad. Supongamos que cuando se solicita el valor de
hasta el usuario introduce el valor 6. He aqu el aspecto de la memoria:
main
cantidad
6 hasta
Se efecta entonces (lnea 40) la llamada a cuenta primos, con lo que se crea una
nueva trama de activacin. En ella se reserva memoria para todas las variables locales
de cuenta primos:
llamada desde lnea 40
main
cuenta_primos
cantidad
6 hasta
numprimos
i
j
criba
0 1 2 3 4 5 6 7 8 9
n
Observa que el vector criba ocupa memoria en la propia trama de activacin. Completa
t mismo el resto de acciones ejecutadas por el programa ayudndote de una traza de la
pila de llamadas a funcin con grcos como los mostrados.
Te hemos dicho en el apartado 2.1 que los vectores han de tener talla conocida en tiempo
de compilacin. Es hora de matizar esa armacin. Los vectores locales a una funcin
pueden determinar su talla en tiempo de ejecucin. Veamos un ejemplo:
include
int cuenta primos int Cuenta el nmero de primos entre 1 y .
char criba
int numprimos
Inicializacin
criba 0 0
for 1
criba 1
Criba de Eratstenes
for 2
Introduccin a la programacin con C 169 c UJI
170
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
if criba
for 2
criba 0
Conteo de primos
numprimos 0
for 0
if criba
numprimos
return numprimos
int main void
int hasta cantidad
printf scanf hasta
cantidad cuenta primos hasta
printf hasta cantidad
return 0
Fjate en cmo hemos denido el vector criba: la talla no es un valor constante, sino
, un parmetro cuyo valor es desconocido hasta el momento en que se ejecute la funcin.
Esta es una caracterstica de C99 y supone una mejora interesante del lenguaje.
Este programa ilustra el modo en que podemos declarar y pasar parmetros vectoriales
a una funcin:
include
dene 3
void incrementa int
int
for 0
int main void
int
printf
for 0
printf
incrementa
printf
Introduccin a la programacin con C 170 c UJI
171
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
for 0
printf
return 0
Fjate en cmo se indica que el parmetro es un vector de enteros: aadiendo un par
de corchetes a su identicador. En la lnea 23 pasamos a incrementa el vector . Qu
ocurre cuando modicamos componentes del parmetro vectorial en la lnea 10?
Si ejecutamos el programa obtenemos el siguiente texto en pantalla:
El contenido de se ha modicado! Ocurre lo mismo que ocurra en Python: los
vectores s modican su contenido cuando se altera el contenido del respectivo parmetro
en las llamadas a funcin.
Cuando se pasa un parmetro vectorial a una funcin no se efecta una copia de
su contenido en la pila: slo se copia la referencia a la posicin de memoria en la
que empieza el vector. Por qu? Por eciencia: no es infrecuente que los programas
manejen vectores de tamao considerable; copiarlos cada vez en la pila supondra invertir
una cantidad de tiempo que, para vectores de tamao medio o grande, podra ralentizar
drsticamente la ejecucin del programa. La aproximacin adoptada por C hace que slo
sea necesario copiar en la pila 4 bytes, que es lo que ocupa una direccin de memoria. Y
no importa cun grande o pequeo sea un vector: la direccin de su primer valor siempre
ocupa 4 bytes.
Veamos grcamente, pues, qu ocurre en diferentes instantes de la ejecucin del
programa. Justo antes de ejecutar la lnea 23 tenemos esta disposicin de elementos en
memoria:
main
3 i
v
0
0
1
1
2
2
En el momento de ejecutar la lnea 10 por primera vez, en la funcin incrementa, la
memoria presenta este aspecto:
main
3 i
v
0
0
1
1
2
2
llamada desde lnea 23
incrementa
0 i
a
Introduccin a la programacin con C 171 c UJI
172
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Ves? El parmetro apunta a . Los cambios sobre elementos del vector que tienen
lugar al ejecutar la lnea 10 tienen efecto sobre los correspondientes elementos de ,
as que reeja los cambios que experimenta . Tras ejecutar el bucle de incrementa,
tenemos esta situacin:
main
3 i
v
1
0
2
1
3
2
llamada desde lnea 23
incrementa
3 i
a
Y una vez ha nalizado la ejecucin de incrementa, sta otra:
main
3 i
v
1
0
2
1
3
2
Y qu ocurre cuando el vector es una variable global? Pues bsicamente lo mismo:
las referencias no tienen por qu ser direcciones de memoria de la pila. Este programa
es bsicamente idntico al anterior, slo que es ahora una variable global:
include
dene 3
int
void incrementa int
int
for 0
int main void
int
printf
for 0
printf
incrementa
printf
for 0
printf
return 0
Introduccin a la programacin con C 172 c UJI
173
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Analicemos qu ocurre en diferentes instantes de la ejecucin del programa. Justo
antes de ejecutar la lnea 24, existen las variables locales a main y las variables globales:
main 3 i
variables globales
v
0
0
1
1
2
2
Al llamar a incrementa se suministra un puntero a la zona de memoria de variables
globales, pero no hay problema alguno: el parmetro es un puntero que apunta a esa
direccin.
main 3 i
llamada desde lnea 24
incrementa 0 i
a
variables globales
v
0
0
1
1
2
2
Los cambios al contenido de se maniestan en :
main 3 i
llamada desde lnea 24
incrementa 3 i
a
variables globales
v
1
0
2
1
3
2
Y una vez ha nalizado la ejecucin de incrementa, el contenido de queda modicado:
main 3 i
variables globales
v
1
0
2
1
3
2
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
170 Disea un programa C que manipule polinomios de grado menor o igual que
10. Un polinomio se representar con un vector de oat de tamao 11. Si es un vector
que representa un polinomio, es el coeciente del trmino de grado . Disea un
procedimiento suma con el siguiente perl:
void suma oat oat oat
El procedimiento modicar para que contenga el resultado de sumar los polinomios
y .
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 hay
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 valor de
la talla de un vector tienes dos posibilidades:
Introduccin a la programacin con C 173 c UJI
174
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
1. saberlo de antemano,
2. o proporcionarlo como parmetro adicional.
Estudiemos la primera alternativa. Fjate en este fragmento de programa:
include
dene 20
dene 10
void inicializa int
int
for 0
0
void imprime int
int
for 0
printf
printf
int main void
int
int
inicializa
inicializa
!
Ojo!
imprime
imprime
!
Ojo!
return 0
Siguiendo esta aproximacin, la funcin inicializa slo se puede utilizar con vectores de
int de talla , como . No puedes llamar a inicializa con : si lo haces (y C te deja
hacerlo!) cometers un error de acceso a memoria que no te est reservada, pues el bucle
recorre componentes, aunque slo tenga . Ese error puede abortar la
ejecucin del programa o, peor an, no hacindolo pero alterando la memoria de algn
modo indenido.
Este es el resultado obtenido en un ordenador concreto:
El programa no ha abortado su ejecucin, pero ha mostrado 20 valores del vector ,
que slo tiene 10.
Cmo podemos disear una funcin que pueda trabajar tanto con el vector como
con el vector ? Siguiendo la segunda aproximacin propuesta, es decir, pasando como
parmetro adicional la talla del vector en cuestin:
Introduccin a la programacin con C 174 c UJI
175
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 de la zona
reservada para el vector. Cuando pasamos una variable escalar por referencia tambin
pasamos una direccin de memoria: aquella en la que empieza la zona reservada para
el valor escalar. Qu diferencia hay entre una y otra direccin? Ninguna: un puntero
siempre es un puntero. Fjate en este programa:
include
dene 10
void procedimiento int int
printf 0
printf 0
!
Ojo!
int main void
int 10
for 0 1
printf
procedimiento
printf
procedimiento
printf
procedimiento 0 1
return 0
Esta es la salida resultante de su ejecucin:
Observa qu ha ocurrido: en procedimiento se puede usar y como si fueran
vectores o escalares pasados por referencia. Y podemos pasar a procedimiento tanto
la direccin de un vector de ints como la de un escalar de tipo int. La conclusin es
clara: int e int son sinnimos cuando se declara un parmetro, pues en
ambos casos se describen punteros a direcciones de memoria en las que residen sendos
valores enteros (o donde empieza una serie de valores enteros). Aunque sean 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 direccin
de un entero y int cuando quieras pasar la direccin de un vector de enteros.
include
Introduccin a la programacin con C 175 c UJI
176
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
dene 20
dene 10
void inicializa int int talla
int
for 0 talla
0
void imprime int int talla
int
for 0 talla
printf
printf
int main void
int
int
inicializa
inicializa
imprime
imprime
return 0
Ahora puedes llamar a la funcin inicializa con inicializa o iniciali-
za . Lo mismo ocurre con imprime. El parmetro talla toma el valor apropiado
en cada caso porque t se lo ests pasando explcitamente.
ste es el resultado de ejecutar el programa ahora:
Correcto.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
172 Disea un procedimiento ordena que ordene un vector de enteros. El procedi-
miento 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 parmetro
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 adicional.
Introduccin a la programacin con C 176 c UJI
177
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin 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 con
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 oat y muestre todos sus
componentes en pantalla. Cada componente se representar separado del siguiente con
una coma. Cada 6 componentes aparecer un salto de lnea. La talla del vector se indicar
con un parmetro adicional.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
C permite modicar el valor de variables escalares en una funcin recurriendo a sus
direcciones de memoria. Analicemos el siguiente ejemplo:
include
void incrementa int
1
int main void
int
1
printf
incrementa
printf
return 0
Al ejecutarlo, aparece en pantalla el siguiente texto:
Efectivamente, ha modicado su valor tras la llamada a incrementa. Observa la
forma en que se ha declarado el nico parmetro de incrementa: int . O sea, es del
tipo int . Un tipo de la forma tipo signica puntero a valor de tipo tipo. Tenemos,
por tanto, que 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 . Estamos pasando la direccin de memoria de
(que es lo que proporciona el operador ) y no el valor de . Todo correcto, ya que hemos
dicho que la funcin espera la direccin de memoria de un entero.
Al principio de la ejecucin de incrementa tendremos esta situacin:
main 1 b
llamada desde lnea 14
incrementa
a
Introduccin a la programacin con C 177 c UJI
178
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
El parmetro es un puntero que apunta a . Fjate ahora en la sentencia que
incrementa el valor apuntado por (lnea 5):
1
El asterisco que precede 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 direccin
de memoria de una variable.) O sea, C interpreta como accede a la variable apuntada
por , que es , as que 1 equivale a 1 e incrementa el contenido de la variable
.
El & de los parmetros de scanf
Ahora ya puedes entender bien por qu las variables escalares que suministramos a
scanf para leer su valor por teclado van precedidas por el operador : como scanf debe
modicar su valor, ha de saber en qu direccin de memoria residen. No ocurre lo mismo
cuando vamos a leer una cadena, pero eso es porque el identicador de la variable ya
es, en ese caso, una direccin de memoria.
Qu pasara si en lugar de 1 hubisemos escrito 1? Se hubiera incre-
mentado la direccin de memoria a la que apunta el puntero, nada ms.
Y si hubisemos escrito ? Lo mismo: hubisemos incrementado el valor de la
direccin almacenada en . Y ?, funcionara? A primera vista diramos que s, pero
no funciona como esperamos. El operador postjo tiene mayor nivel de precedencia
que el operador unario , as que (post)incrementa la direccin 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/decremento,
tendrs que utilizar parntesis para que los operadores se apliquen en el orden deseado:
.
Naturalmente, no slo puedes acceder as a variables locales, tambin las variables
globales son accesibles mediante punteros:
include
int Variable global.
void incrementa int
1
int main void
1
printf
incrementa
printf
return 0
El aspecto de la memoria cuando empieza a ejecutarse la funcin incrementa es ste:
Introduccin a la programacin con C 178 c UJI
179
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
main
llamada desde lnea 14
incrementa
a
variables globales
1 b
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
178 Disea un procedimiento que modique el valor del parmetro de tipo oat para
que valga la inversa de su valor cuando ste sea distinto de cero. Si el nmero es cero,
el procedimiento dejar intacto el valor del parmetro.
Si vale 2.0, por ejemplo, inversa har que valga 0.5.
179 Disea un procedimiento que intercambie el valor de dos nmeros enteros.
Si y valen 1 y 2, respectivamente, la llamada intercambia har que
pase a valer 2 y pase a valer 1.
180 Disea un procedimiento que intercambie el valor de dos nmeros oat.
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 radianes) y un radio , calcule el
valor de = cos() e = sin():
No podemos disear una funcin que devuelva los dos valores. Hemos de disear un pro-
cedimiento que devuelva los valores resultantes como parmetros pasados por referencia:
include
include
void calcula xy oat alfa oat radio oat oat
radio cos alfa
radio sin alfa
Introduccin a la programacin con C 179 c UJI
180
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
En C slo hay paso por valor
Este apartado intenta que aprendas a distinguir el paso de parmetros por valor y por
referencia. Pero la realidad es que C slo tiene paso de parmetros por valor! Cuando
pasas una referencia, ests pasando explcitamente una direccin de memoria gracias
al operador , y lo que hace C es copiar dicha direccin en la pila, es decir, pasa por
valor una direccin para simular el paso de parmetros por referencia. La extraa forma
de pasar el parmetro hace que tengas que usar el operador cada vez que deseas
acceder a l en el cuerpo de la funcin.
En otros lenguajes, como Pascal, es posible indicar que un parmetro se pasa por
referencia sin que tengas que usar un operador (equivalente a) al efectuar el paso
o un operador (equivalente a) cuando usas el parmetro en el cuerpo de la funcin.
Por ejemplo, este programa Pascal incluye un procedimiento que modica el valor de
su parmetro:
C es una extensin de C que permite el paso de parmetros por referencia. Usa para
ello el carcter en la declaracin del parmetro:
include
void incrementa int
1
int main void
int
1
printf
incrementa
printf
return 0
(Aunque no venga a cuento, observa lo diferente que es C de Pascal (y aun as, lo
semejante que es) y cmo el programa C presenta un aspecto muy semejante a uno
equivalente escrito en C.)
Y cmo llamamos al procedimiento? Aqu tienes un ejemplo de uso:
Introduccin a la programacin con C 180 c UJI
181
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int main void
oat angulo horizontal vertical
printf scanf angulo
printf scanf
calcula xy angulo horizontal vertical
printf horizontal vertical
return 0
Ves? Las variables horizontal y vertical no se inicializan en main: reciben valores como
resultado de la llamada a calcula xy.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 e .
185 Disea una funcin que reciba dos nmeros enteros y y devuelva, simult-
neamente, el menor y el mayor de ambos. La funcin tendr esta cabecera:
void minimax int int 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
dene 6
Dene aqu la funcin
int main void
int distancia encontrado buscado
for 0
printf
scanf
printf
scanf buscado
encontrado busca buscado distancia
if encontrado
printf buscado
else
Introduccin a la programacin con C 181 c UJI
182
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
printf distancia
printf
scanf buscado
encontrado busca buscado distancia
if encontrado
printf buscado
else
printf distancia
return 0
Al ejecutar el programa obtenemos esta salida por pantalla:
187 Modica la funcin del ejercicio anterior para que, adems de la distancia al
elemento ms prximo, devuelva el valor del elemento ms prximo.
188 Modica 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 re-
gistros. El paso de registros es por valor, o sea, copiando el contenido en la pila, a menos
que t mismo pases un puntero a su direccin de memoria.
Este programa, por ejemplo, dene 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
oat
oat distancia struct Punto
return sqrt
int main void
struct Punto pto
Introduccin a la programacin con C 182 c UJI
183
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
pto 1
pto 1
pto 1
printf distancia pto
return 0
Al pasar un registro a la funcin, C copia en la pila cada uno de los valores de sus
campos. Ten en cuenta que una variable de tipo struct Punto ocupa 24 bytes (contiene 3
valores de tipo oat). Variables de otros tipos registro que denas pueden ocupar cientos
o incluso miles de bytes, as que ve con cuidado: llamar a una funcin pasando registros
por valor puede resultar ineciente. Por cierto, no es tan extrao que un registro 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 modicaciones del valor de un campo en
el cuerpo de la funcin no tendrn efectos perceptibles fuera de la funcin.
Como te hemos anticipado, tambin puedes pasar registros por referencia. En tal caso
slo se estar copiando en la pila la direccin de memoria en la que empieza el registro
(y eso son 4 bytes), mida lo que mida ste. Se trata, pues, de un paso de parmetros ms
eciente. Eso s, has de tener en cuenta que los cambios que efectes a cualquier campo
del parmetro se reejarn en el campo correspondiente de la variable que suministraste
como argumento.
Esta funcin, por ejemplo, dene dos parmetros: uno que se pasa por referencia y
otro que se pasa por valor. La funcin traslada un punto en el espacio (modicando los
campos del punto original) de acuerdo con el vector de desplazamiento que se indica con
otro punto (traslacion):
void traslada struct Punto struct Punto traslacion
traslacion
traslacion
traslacion
Observa cmo hemos accedido a los campos de . Ahora es una direccin de memoria
(es de tipo struct Punto ), y es la variable apuntada por (y por tanto, es de tipo
struct Punto). El campo es accedido con : primero se accede al contenido de la
direccin de memoria apuntada por , y luego al campo del registro , de ah que
usemos parntesis.
Es tan frecuente la notacin que existe una forma compacta equivalente:
void traslada struct Punto struct Punto traslacion
traslacion
traslacion
traslacion
La forma es absolutamente equivalente a .
Recuerda, pues, que dentro de una funcin se accede a los campos de forma distinta
segn se pase un valor por copia o por referencia:
1. con el operador punto, como en traslacion , si la variable se ha pasado por valor;
2. con el operador echa, como en , si la variable se ha pasado por referencia
(equivalentemente, puedes usar la notacin ).
Introduccin a la programacin con C 183 c UJI
184
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Acabemos este apartado mostrando una rutina que pide al usuario que introduzca las
coordenadas de un punto:
void lee punto struct Punto
printf scanf
printf scanf
printf scanf
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
189 Este ejercicio y los siguientes de este bloque tienen por objeto construir una serie
de funciones que permitan efectuar transformaciones anes sobre puntos en el plano. Los
puntos sern variables de tipo struct Punto, que denimos as:
struct Punto
oat
Disea un procedimiento muestra punto que muestre por pantalla un punto. Un punto
tal que vale 2.0 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 ( ) a
( + + ), siendo el desplazamiento ( ) un vector (que representamos con otro
punto). Implementa una funcin que reciba dos parmetros de tipo punto y modique el
primero de modo que se traslade lo que indique el vector.
191 La operacin de escalado transforma un punto ( ) en otro ( ), donde es
un factor de escala (real). Implementa una funcin que escale un punto de acuerdo con el
factor de escala que se suministre como parmetro (un oat).
192 Si rotamos un punto ( ) una cantidad de radianes alrededor del origen,
obtenemos el punto
( cos sin sin + cos )
Dene una funcin que rote un punto la cantidad de grados que se especique.
193 La rotacin de un punto ( ) una cantidad de radianes alrededor de un punto
( ) se puede efectuar con una traslacin con el vector ( ), una rotacin de
radianes con respecto al origen y una nueva traslacin con el vector ( ). Disea una
funcin que 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 denido un tipo registro para representar complejos as:
struct Complejo
oat real
oat imag
Disea e implementa los siguientes procedimientos para su manipulacin:
leer un complejo de teclado;
mostrar un complejo por pantalla;
Introduccin a la programacin con C 184 c UJI
185
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
el mdulo de un complejo (| +| =
2
+
2
);
el opuesto de un complejo (( +) = );
el conjugado de un complejo ( + = );
la suma de dos complejos (( +) + ( +) = ( +) + ( +));
la diferencia de dos complejos (( +) ( +) = ( ) + ( ));
el producto de dos complejos (( +) ( +) = ( ) + ( +));
la divisin de dos complejos (
+
+
=
+
2
+
2
+
2
+
2
).
196 Dene un tipo registro y una serie de funciones para representar y manipular
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, devolviendo los valores
1, 0 o 1 respectivamente,
comprobar si una fecha existe (por ejemplo, el 29 de febrero de 2002 no existe):
calcular la diferencia de das entre dos fechas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
El paso de vectores multidimensionales no es una simple extensin del paso de vectores
unidimensionales. Veamos. Aqu tienes un programa incorrecto en el que se dene una
funcin que recibe una matriz y devuelve su elemento mximo:
E E
include
dene 3
int maximo int
int
0 0
for 0
for 0
if
return
Introduccin a la programacin con C 185 c UJI
186
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int main void
int matriz
int
for 0
for 0
matriz
printf maximo matriz
return 0
El compilador no acepta ese programa. Por qu? Fjate en la declaracin del parmetro.
Qu hay de malo? C no puede resolver los accesos de la forma . Si recuerdas,
signica accede a la celda cuya direccin se obtiene sumando a la direccin
el valor + j, donde es el nmero de columnas de la matriz
(en nuestro caso, sera ). Pero, cmo sabe la funcin cuntas columnas tiene ?
No hay forma de saberlo viendo una denicin del estilo int !
La versin correcta del programa debe indicar explcitamente cuntas columnas tiene
la matriz. Hela aqu:
include
dene 3
int maximo int
int
0 0
for 0
for 0
if
return
int main void
int matriz
int
for 0
for 0
matriz
printf maximo matriz
return 0
No ha sido necesario indicar cuntas las tiene la matriz (aunque somos libres de hacerlo).
La razn es sencilla: el nmero de las no hace falta para calcular la direccin en la que
reside el valor .
As pues, en general, es necesario indicar explcitamente el tamao de cada una de las
dimensiones del vector, excepto el de la primera (que puedes declarar o no, a voluntad).
Introduccin a la programacin con C 186 c UJI
187
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Slo as obtiene C informacin suciente para calcular los accesos a elementos del vector
en el cuerpo de la funcin.
Una consecuencia de esta restriccin es que no podremos denir funciones capaces
de trabajar con matrices de tamao arbitrario. Siempre hemos de denir explcitamente
el tamao de cada dimensin excepto de la primera. Habr una forma de superar este
inconveniente, pero tendremos que esperar al siguiente captulo para poder estudiarla.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 blanco
representar una casilla vaca; el carcter representar una casilla ocupada con un
crculo y el carcter representar una casilla marcada con una 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 modique el
tablero adecuadamente. La funcin debe detectar si la jugada es vlida o no.
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, modica 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 vivas o
muertas. La matriz se modica a partir de su estado siguiendo unas sencilla reglas 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 superpoblacin.
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 representa
celda muerta y el valor 1 representa celda viva. La funcin modicar la matriz de
acuerdo con las reglas del juego de la vida. (Avisos: Necesitars una matriz auxiliar. Las
celdas de los bordes no tienen 8 vecinos, sino 3 o 5.)
A continuacin, monta un programa que permita al usuario introducir una disposicin
inicial de celdas y ejecutar el juego de la vida durante ciclos, siendo un valor
introducido por el usuario.
Aqu tienes un ejemplo de partida de 3 ciclos con una conguracin inicial curiosa:
Introduccin a la programacin con C 187 c UJI
188
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 casillas
que no contienen bomba. Con cada jugada, el usuario descubre una casilla (a partir de
sus coordenadas, un par de letras). Si la casilla contiene una bomba, la partida naliza
con la derrota del usuario. Si la casilla est libre, el usuario es informado de cuntas
bombas hay en las (como mucho) 8 casillas vecinas.
Este tablero representa, en un terminal, el estado actual de una partida sobre un
tablero de 8 8:
Las casillas con un punto no han sido descubiertas an. Las casillas con un nmero han
sido descubiertas y sus casillas vecinas contienen tantas bombas como se indica en el
Introduccin a la programacin con C 188 c UJI
189
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
nmero. Por ejemplo, la casilla de coordenadas ( , ) tiene 3 bombas en la vecindad
y la casilla de coordenadas ( , ), ninguna.
Implementa un programa que permita seleccionar el nivel de dicultad y, una vez
escogido, genere un tablero y permita jugar con l al jugador.
Los niveles de dicultad 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, modicar 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 vectores
3
. La razn es simple: la asignacin funciona con valores escalares y
registros, pero no con vectores.
Ya hemos visto cmo devolver valores escalares. A ttulo ilustrativo te presentamos un
ejemplo de denicin de registro y denicin de funcin que recibe como parmetros un
punto ( ) y un nmero y devuelve un nuevo punto cuyo valor es ( ):
struct Punto
oat
struct Punto escala struct Punto oat
struct Punto
return
Eso es todo. . . por el momento. Volveremos a la cuestin de si es posible devolver vectores
cuando estudiemos la gestin de memoria dinmica.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
200 Vuelve a implementar las funciones de manipulacin de puntos en el plano
(ejercicios 189194) para que no modiquen 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
Introduccin a la programacin con C 189 c UJI
190
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
201 Implementa nuevamente las funciones del ejercicio 195, pero devolviendo un nuevo
complejo con el resultado de operar con el complejo o complejos que se suministran como
parmetros.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pongamos en prctica lo aprendido diseando una versin simplicada de un juego de
rescate espacial (Galaxis)
4
al que denominaremos miniGalaxis.
MiniGalaxis se juega con un tablero de 9 las 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 tablero. La
sonda dispara una seal en las cuatro direcciones cardinales que es devuelta por unos
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 sobre
nuestra conciencia.
Lo mejor ser que te hagas una idea precisa del juego jugando. Al arrancar aparece
esta informacin en pantalla:
El tablero se muestra como una serie de casillas. Arriba tienes letras para identicar
las columnas y a la izquierda nmeros para las las. 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 coorde-
nadas 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. He 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 Christian Franz y escrito para
el Apple Macintosh. Ms tarde, Eric Raymond lo reescribi para que fuera ejecutable en Unix.
Introduccin a la programacin con C 190 c UJI
191
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 con
la sonda. Mala suerte. Las casillas que ahora aparecen con un punto son las exploradas
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:
Dos nufragos detectados. Parece probable que uno de ellos est en la columna .
Lancemos otra sonda en esa columna. Probemos con 2 :
= 1
= 1
1
1
Mergesort y el estilo C
Los programadores C tienden a escribir los programas de una forma muy compacta.
Estudia esta nueva versin de la funcin merge:
void merge int int inicio1 int nal1 int nal2
int
int nal2 inicio1 1
for inicio1 nal1 1 0 nal1 nal2
while nal1
while nal2
for 0 nal2 inicio1 1 inicio1
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 mismo: te
prevenimos para que ests preparado cuando te enfrentes a la lectura de programas 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 celda del vector.
Es una prctica frecuente y da lugar, una vez acostumbrado, a programas bastante
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 denir la funcin antes de su uso, pero no siempre
es posible. Imagina que una funcin necesita llamar a una funcin y que , a su
vez, necesita llamar a (recursin indirecta). Cul ponemos delante? La solucin es
fcil: da igual, la que quieras, pero debes hacer una declaracin anticipada de la funcin
que denes en segundo lugar. La declaracin anticipada no incluye el cuerpo de la
funcin: consiste en la declaracin del tipo de retorno, identicador de funcin y lista de
parmetros con su tipo, es decir, es un prototipo o perl de la funcin en cuestin.
Estudia este ejemplo
6
:
int impar int
int par int
if 0
return 1
else
return impar 1
6
El ejemplo es meramente ilustrativo: hay formas mucho ms ecientes de saber si un nmero es par o
impar.
Introduccin a la programacin con C 216 c UJI
217
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int impar int
if 0
return 0
else
return par 1
La primera lnea es una declaracin anticipada de la funcin impar, pues se usa antes
de haber sido denida. Con la declaracin anticipada hemos adelantado la informacin
acerca de qu tipo de valores aceptar y devolver la funcin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
211 Dibuja el estado de la pila cuando se llega al caso base en la llamada recursiva
impar 7 .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
La declaracin anticipada resulta necesaria para programas con recursin indirecta,
pero tambin la encontrars (o usars) en programas sin recursin. A veces conviene
denir funciones en un orden que facilite la lectura del programa, y es fcil que se dena
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 denir primero mergesort y despus merge pues, a n de cuentas, las hemos
desarrollado en ese orden. De denirlas as, necesitaramos declarar anticipadamente
merge:
include
dene 100
void merge int int inicio1 int nal1 int nal2 Declaracin anticipada.
void mergesort int int inicio int nal
if nal inicio 0
return
else
mergesort inicio inicio nal 2
mergesort inicio nal 2 1 nal
merge inicio inicio nal 2 nal Podemos usarla: se ha declarado antes.
void merge int int inicio1 int nal1 int nal2 Y ahora se dene.
El preprocesador permite denir 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 las
llamadas no se traducen en verdaderas llamadas a funcin. Ahora vers por qu.
Vamos con un ejemplo:
Introduccin a la programacin con C 217 c UJI
218
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Prototipo por defecto y declaracin anticipada
Si usas una funcin antes de denirla y no has preparado una declaracin anticipada,
C deduce el tipo de cada parmetro a partir de la forma en la que se le invoca. Este
truco funciona a veces, pero es frecuente que sea fuente de problemas. Considera este
ejemplo:
int int
return 1
oat oat
return
En la lnea 3 se usa y an no se ha denido. Por la forma de uso, el compilador
deduce que su perle es int int . Pero, al ver la denicin, detecta un conicto.
El problema se soluciona alterando el orden de denicin de las funciones o, si se
preere, mediante una declaracin anticipada:
oat oat
int int
return 1
oat oat
return
dene
La directiva con la que se dene una macro es dene, la misma con la que declarbamos
constantes. La diferencia est en que la macro lleva uno o ms parmetros (separados
por comas) encerrados entre parntesis. Este programa dene y usa la macro :
include
dene
int main void
printf 2 2
return 0
El compilador no llega a ver nunca la llamada a . La razn es que el prepro-
cesador la sustituye por su cuerpo, consiguiendo que el compilador vea esta otra versin
del programa:
include
Introduccin a la programacin con C 218 c UJI
219
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
int main void
printf 2 2 2
return 0
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 argumentos
en pila y el salto/retorno a otro lugar del programa.
No obligan a dar informacin de tipo acerca de los parmetros ni del valor de
retorno. Por ejemplo, esta macro devuelve el mximo de dos nmeros, sin importar
que sean enteros o otantes:
dene
Pero tienen serios inconvenientes:
La denicin de la macro debe ocupar, en principio, una sola lnea. Si ocupa ms
de una lnea, hemos de nalizar todas menos la ltima con el carcter justo
antes del salto de lnea. Incmodo.
No puedes denir variables locales.
7
No admiten recursin.
Son peligrossimas. Qu crees que muestra por pantalla este programa?:
include
dene
int main void
printf 3 3
return 0
36?, es decir, el cuadrado de 6? Pues no es eso lo que obtienes, sino 15. 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 evitar este
problema usando parntesis:
include
dene
main void
printf 3 3
return 0
7
No del todo cierto, pero no entraremos en detalles.
Introduccin a la programacin con C 219 c UJI
220
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 estando
mal. Qu esperas que calcule 1.0 3 3 ?, el valor de 1/36, es de-
cir, 0.02777. . . ? Te equivocas. La expresin 1.0 3 3 se convierte 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:
include
dene
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:
3
la variable se incrementa 2 veces, y no una sla. Ten en cuenta que el compilador traduce
lo que ve, y ve esto:
3
Y este problema no se puede solucionar.
Recuerda! Si usas macros, toda precaucin es poca.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 otantes.
214 Disea una macro que calcule el valor absoluto de un nmero, sin importar si
es entero o otante.
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 soporte a
las macros slo por compatibilidad con C, pero ofrecen alternativas mejores. Por ejemplo,
puedes denir 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 los
valores que se suministren para los parmetros. Las funciones inline presentan muchas
ventajas frente a la macros. Entre ellas, la posibilidad de utilizar variables locales o la
no necesidad de utilizar parntesis alrededor de toda aparicin de un parmetro.
Las funciones inline son tan tiles que compiladores como las integran desde
hace aos como extensin propia del lenguaje C y han pasado a formar parte del lenguaje
C99. Al compilar un programa C99 como ste:
Introduccin a la programacin con C 220 c UJI
221
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
include
inline int doble int
return 2
int main void
int
for 0 10
printf doble 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 programa
equivalente:
include
int main void
int
for 0 10
printf 1 2
return 0
Hay ocasiones, no obstante, en las que el compilador no puede efectuar la sustitucin
de la llamada a funcin por su cuerpo. Si la funcin es recursiva, por ejemplo, la sustitucin
es imposible. Pero aunque no sea recursiva, el compilador puede juzgar que una funcin
es excesivamente larga o compleja para que compense efectuar la sustitucin. Cuando 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 static es invisible
fuera de la funcin, como cualquier otra variable local, pero recuerda su valor entre
diferentes ejecuciones de la funcin en la que se declara.
Veamos un ejemplo:
include
int turno void
static int contador 0
return contador
int main void
Introduccin a la programacin con C 221 c UJI
222
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int
for 0 10
printf turno
return 0
Si ejecutas el programa aparecern por pantalla los nmeros del 0 al 9. Con cada llamada,
contador devuelve su valor y se incrementa en una unidad, sin olvidar su valor entre
llamada y llamada.
La inicializacin de las variables static es opcional: el compilador asegura que em-
piezan valiendo 0.
Vamos a volver a escribir el programa que presentamos en el ejercicio 169 para generar
nmeros primos consecutivos. Esta vez, vamos a hacerlo sin usar una variable global que
recuerde el valor del ltimo primo generado. Usaremos en su lugar una variable local
static:
include
int siguienteprimo void
static int ultimoprimo 0
int esprimo
int
do
ultimoprimo
esprimo 1
for 2 ultimoprimo 2
if ultimoprimo 0
esprimo 0
break
while esprimo
return ultimoprimo
int main void
int
printf
for 0 10
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 denen sendas funciones C
que aproximan numricamente la integral denida en un intervalo para las funciones
matemticas () =
2
y () =
3
, respectivamente:
Introduccin a la programacin con C 222 c UJI
223
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
oat integra cuadrado oat oat int
int
oat
0.0
for 0
return
oat integra cubo oat oat int
int
oat
0.0
for 0
return
Las dos funciones que hemos denido son bsicamente iguales. Slo dieren en su iden-
ticador 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:
oat integra oat oat int oat oat
int
oat
0.0
for 0
return
Hemos declarado un cuarto parmetro que es de tipo puntero a funcin. Cuando llamamos
a integra, el cuarto parmetro puede ser el identicador de una funcin que reciba un
oat y devuelva un oat:
include
oat integra oat oat int oat oat
int
oat
0.0
Introduccin a la programacin con C 223 c UJI
224
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
x o
for i 0 i i i
| x l o i
x l o i
return
oat cuadrado oat x
return x x
oat cubo oat x
return x x x
int main void
printf integra 0.0 1.0 10 cuadrado
printf integra 0.0 1.0 10 cubo
return 0
La forma en que se declara un parmetro del tipo puntero a funcin resulta un tanto
complicada. En nuestro caso, lo hemos declarado as: oat | oat . El primer oat
indica que la funcin devuelve un valor de ese tipo. El | indica que el parmetro |
es un puntero a funcin. Y el oat entre parntesis indica que la funcin trabaja con un
parmetro de tipo oat. Si hubisemos necesitado trabajar con una funcin que recibe un
oat y un int, hubisemos escrito oat | oat int en la declaracin del parmetro.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
216 Puedes usar la funcin integra para calcular la integral denida de la funcin
matemtica sin(x)? Cmo?
217 Disea una funcin C capaz de calcular
l
i=o
| (i),
siendo | una funcin matemtica cualquiera que recibe un entero y devuelve un entero.
218 Disea una funcin C capaz de calcular
l
i=o
J
=t
| (i, ),
siendo | una funcin matemtica cualquiera que recibe dos enteros y devuelve un entero.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Cuando te enfrentas a la escritura de un programa largo, individualmente o en equipo,
te resultar virtualmente imposible escribirlo en un nico chero de texto. Resulta ms
prctico agrupar diferentes partes del programa en cheros independientes. Cada chero
Introduccin a la programacin con C 224 c UJI
225
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
puede, por ejemplo, agrupar las funciones, registros y constantes propias de cierto tipo
de clculos.
Proceder as tiene varias ventajas:
Mejora la legibilidad del cdigo (cada chero es relativamente breve y agrupa
temticamente las funciones, registros y constantes).
La compilacin es ms rpida (cuando se modica un chero, slo es necesario
compilar ese chero).
Y, quiz lo ms importante, permite reutilizar cdigo. Es un benecio a medio y
largo plazo. Si, por ejemplo, te dedicas a programar videojuegos tridimensionales,
vers que todos ellos comparten ciertas constantes, registros y funciones denidas
por t o por otros programadores: tipos de datos para modelar puntos, polgonos,
texturas, etctera; funciones que los manipulan, visualizan, leen/escriben en disco,
etctera. Puedes denir estos elementos en un chero y utilizarlo en cuantos pro-
gramas desees. Alternativamente, podras copiar-y-pegar las funciones, constantes
y registros que uno necesita en cada programa, pero no es conveniente en 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 chero, basta con corregir la denicin 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 deniciones de
variables, tipos, constantes y funciones orientados a resolver cierto tipo de problemas.
Puedes compilar independientemente cada unidad de compilacin (de ah el nombre) de
modo que el compilador genere un chero binario para cada una de ellas. El enlazador
se encarga de unir en una ltima etapa todas las unidades compiladas para crear un
nico chero ejecutable.
Lo mejor ser que aprendamos sobre unidades de compilacin escribiendo una muy
sencilla: un mdulo en el que se dene una funcin que calcula el mximo de dos nmero
enteros. El chero que corresponde a esta unidad de compilacin se llamar .
He aqu su contenido:
int maximo int int
if
return
else
return
El programa principal se escribir en otro chero llamado . Dicho programa
llamar a la funcin maximo:
E E
include
int main void
int
printf
scanf
printf
scanf
printf maximo
Introduccin a la programacin con C 225 c UJI
226
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
return 0
Hemos marcado el programa como incorrecto. Por qu? Vers, estamos usando una fun-
cin, maximo, que no est denida en el chero . 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 llamar
a una funcin de la que no sabe nada. Mala cosa.
Cmo se resuelve el problema? Puedes declarar la funcin sin denirla, es decir,
puedes declarar el aspecto de su cabecera (lo que denominamos su prototipo) e indicar
que es una funcin denida externamente:
include
extern int maximo int int
int main void
int
printf
scanf
printf
scanf
printf maximo
return 0
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 la
declaracin del prototipo de la funcin maximo empieza con la palabra clave extern. Con
ella se indica al compilador que maximo est denida en algn mdulo externo. Tambin
puedes indicar con extern que una variable se dene en otro mdulo.
Puedes compilar el programa as:
La compilacin necesita tres pasos: uno por cada unidad de compilacin y otro para
enlazar.
1. El primer paso ( ) traduce a cdigo de mquina el chero
o unidad de compilacin . La opcin indica al compilador que
es un mdulo y no dene a la funcin main. El resultado de la com-
pilacin se deja en un chero llamado . La extensin abrevia el
trmino object code, es decir, cdigo objeto. Los cheros con extensin
contienen el cdigo de mquina de nuestras funciones
8
, pero no es directamente
ejecutable.
2. El segundo paso ( ) es similar al primero y genera el chero
a partir de .
8
. . . pero no slo eso: tambin contienen otra informacin, como la denominada tabla de smbolos.
Introduccin a la programacin con C 226 c UJI
227
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
3. El tercer paso ( ) es especial. El
compilador recibe dos cheros con extensin y genera un nico chero ejecu-
table, llamado principal. Este ltimo paso se encarga de enlazar las dos unidades
compiladas para generar el chero ejecutable.
Por enlazar entendemos que las llamadas a funciones cuyo cdigo de mquina
era desconocido (estaba en otra unidad de compilacin) se traduzcan en saltos
a las direcciones en las que se encuentran los subprogramas de cdigo mquina
correspondientes (y que ahora se conocen).
Aqu tienes un diagrama que ilustra el proceso:
Enlazador principal
Paso 3
Compilador
Paso 1
Compilador
Paso 2
Puedes ahorrarte un paso fundiendo los dos ltimos en uno slo. As:
Este diagrama muestra todos los pasos del proceso a los que aludimos:
Compilador Enlazador principal
Paso 2
Compilador
Paso 1
Para conseguir un programa ejecutable es necesario que uno de los mdulos (pero
slo uno de ellos!) dena una funcin main. Si ningn mdulo dene main o si main se
dene en ms de un mdulo, el enlazador protestar y no generar chero ejecutable
alguno.
Hemos resuelto el problema de gestionar diferentes unidades de compilacin, pero la
solucin de tener que declarar el prototipo de cada funcin en toda unidad de compilacin
que la usa no es muy buena. Hay una mejor: denir un chero de cabecera. Los cheros
de cabecera agrupan las declaraciones de funciones (y cualquier otro elemento) denidos
en un mdulo. Las cabeceras son cheros con extensin (es un convenio: la h es
abreviatura de header).
Nuestra cabecera ser este chero:
extern int maximo int int
Para incluir la cabecera en nuestro programa, escribiremos una nueva directiva include:
Introduccin a la programacin con C 227 c UJI
228
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Documentacin y cabeceras
Es importante que documentes bien los cheros de cabecera, pues es frecuente que los
programadores que usen tu mdulo lo consulten para hacerse una idea de qu ofrece.
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 int
Calcula el mximo de dos nmero enteros y .
Y por qu los programadores no miran directamente el chero en lugar del
cuando quieren consultar algo? Por varias razones. Una de ellas es que, posiblemente,
el no est accesible. Si el mdulo es un producto comercial, probablemente slo les
hayan vendido el mdulo ya compilado (el chero ) y el chero de cabecera. Pero
incluso si se tiene acceso al , puede ser preferible ver el . El chero puede
estar plagado de detalles de implementacin, funciones auxiliares, variables para uso
interno, etc., que hacen engorrosa su lectura. El chero de cabecera contiene 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
printf
scanf
printf
scanf
printf maximo
return 0
La nica diferencia con respecto a otros include que ya hemos usado estriba en el uso de
comillas dobles para encerrar el nombre del chero, en lugar de los caracteres y .
Con ello indicamos al preprocesador que el chero se encuentra en nuestro
directorio activo. El preprocesador se limita a sustituir la lnea en la que aparece include
por el contenido del chero. En un ejemplo tan sencillo no hemos ganado
mucho, pero si el mdulo contuviera muchas funciones, con slo una lnea
habramos conseguido importarlas todas.
Introduccin a la programacin con C 228 c UJI
229
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Aqu tienes una actualizacin del grco que muestra el proceso completo de compi-
lacin:
Preprocesador Compilador Enlazador principal
Compilador
No slo puedes declarar funciones en los cheros de cabecera. Tambin puedes denir
constantes, variables y registros.
Poco hay que decir sobre las constantes. Basta con que las denas con dene en
el chero 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, tendremos
que incluir su declaracin en la cabecera. Cmo? Una primera idea es poner, directamente,
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 de-
niendo una nueva variable con el mismo identicador que otra. Y declarar dos variables
con el mismo identicador es un error.
Quien detecta el error es el enlazador: cuando vaya a generar el programa ejecutable,
encontrar que hay dos objetos que tienen el mismo identicador, y eso est prohibido.
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 compilador
sabe que variable es de tipo int y que est denida 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 denicin de
registros: basta con que pongas su declaracin en la cabecera, sin ms. Pero si tu programa
incluye dos cabeceras que, a su vez, incluyen ambas a una tercera donde se denen
constantes o registros, puedes tener problemas. Un ejemplo ilustrar mejor el tipo de
dicultades al que nos enfrentamos. Supongamos que un chero dene un registro:
Introduccin a la programacin con C 229 c UJI
230
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Bibliotecas
Ya has usado funciones y datos predenidos, como las funciones y las constantes ma-
temticas. 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 cheros con extensin , conviene empaquetarlos
en uno solo con extensin (por archive). Los cheros con extensin son
similares a los cheros con extensin : meras colecciones de cheros. De hecho,
tar (tape archiver) es una evolucin de (por archiver), el programa con el que
se manipulan los cheros con extensin .
La biblioteca matemtica, por ejemplo, agrupa un montn de mdulos. En un sistema
Linux se encuentra en el chero y puedes consultar su contenido
con esta orden:
Como puedes ver, hay varios cheros con extensin en su interior. (Slo te
mostramos el principio y el nal del resultado de la llamada, pues hay un total de 395
cheros!)
Cuando usas la biblioteca matemtica compilas as:
o, equivalentemente, as:
En el segundo caso hacemos explcito el nombre de la biblioteca en la que se
encuentran las funciones matemticas. El enlazador no slo sabe tratar cheros con
extensin : tambin sabe buscarlos en los de extensin .
En cualquier caso, sigue siendo necesario que las unidades de compilacin conozcan
el perl de las funciones que usan y estn denidas en otros mdulos o bibliotecas. Por
eso inclumos, cuando conviene, el chero en nuestros programas.
Hay innidad de bibliotecas que agrupan mdulos con utilidades para diferentes
campos de aplicacin: resolucin de problemas matemticos, diseo de videojuegos,
reproduccin de msica, etc. Algunas son cdigo abierto, en cuyo caso se distribuyen
con los cheros de extensin , los cheros de extensin y alguna utilidad
para facilitar la compilacin (un makele). Cuando son comerciales es frecuente que se
mantenga el cdigo fuente en privado. En tal caso, se distribuye el chero con extensin
(o una coleccin de cheros con extensin ) y uno o ms cheros con extensin
.
Cabecera
struct
int
Fin de cabecera
Ahora, los cheros y incluyen a y declaran la existencia de sendas funciones:
Introduccin a la programacin con C 230 c UJI
231
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Cabecera
include
int funcion de b punto h int
Fin de cabecera
Cabecera
include
int funcion de c punto h int
Fin de cabecera
Y, nalmente, 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
Fin de cabecera .
int funcion de b punto h int
Fin de cabecera .
Cabecera .
Cabecera .
struct
int
Fin de cabecera .
int funcion de c punto h int
Fin de cabecera .
int main void
Introduccin a la programacin con C 231 c UJI
232
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
El compilador encuentra, por tanto, la denicin de struct por duplicado, y nos avisa
del error. No importa que las dos veces se declare de la misma forma: C lo considera
ilegal. El problema puede resolverse reescribiendo (y, en general, cualquier chero
cabecera) as:
Cabecera de
ifndef
dene
struct
int
endif
Fin de cabecera de
Las directivas ifndef / endif marcan una zona de cdigo condicional. Se interpretan
as: si la constante no est denida, entonces incluye el fragmento hasta el endif ,
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. No
debes confundir estas directivas con una sentencia if : no lo son. La sentencia if 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 tiempo de
compilacin.
Observa que lo primero que se hace en ese fragmento de programa es denir la
constante (lnea 3). La primera vez que se incluya la cabecera no estar an
denida , as que se incluirn las lneas 38. Uno de los efectos ser que pasar
a estar denida. La segunda vez que se incluya la cabecera , ya estar denida,
as que el compilador no ver por segunda vez la denicin de struct .
El efecto nal es que la denicin de struct slo se ve una vez. He aqu lo que
resulta de tras su paso por el preprocesador:
include
Cabecera .
Cabecera .
struct
int
Fin de cabecera .
int funcion de b punto h int
Fin de cabecera .
Cabecera .
Cabecera .
Fin de cabecera .
int funcion de c punto h int
Fin de cabecera .
int main void
Introduccin a la programacin con C 232 c UJI
233
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
La segunda inclusin de no ha supuesto el copiado del texto guardado entre directivas
ifndef endif . Ingenioso, no?
Introduccin a la programacin con C 233 c UJI
234
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
La Reina se puso congestionada de furia, y, tras lanzarle una mirada felina,
empez a gritar: Que le corten la cabeza! Que le corten. . . !.
iwis C/oii, Alicia en el Pas de las Maravillas.
Vimos en el captulo 2 que los vectores de C presentaban un serio inconveniente con
respecto a las listas de Python: su tamao deba ser jo y conocido en tiempo de com-
pilacin, 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 estruc-
turas 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 ja en tiempo de ejecucin,
y mediante registros enlazados, tambin conocidos como listas enlazadas (o, sim-
plemente, listas).
Ambas aproximaciones se basan en el uso de punteros y cada una de ellas presenta
diferentes ventajas e inconvenientes.
Sabemos denir vectores indicando su tamao en tiempo de compilacin:
dene 10
int
Pero, y si no sabemos a priori cuntos elementos debe albergar el vector?
1
Por lo
estudiado hasta el momento, podemos denir 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 intolerable
para situaciones normales? Imagina una aplicacin de agenda telefnica personal que,
1
En la seccin 3.5.3 vimos cmo denir vectores locales cuya talla se decide al ejecutar una funcin:
lo que denominamos vectores de longitud variable. Nos proponemos dos objetivos: por una parte, poder
redimensionar vectores globales; y, por otro, vamos a permitir que un vector crezca y decrezca en tamao
cuantas veces queramos. Los vectores de longitud variable que estudiamos en su momento son inapropiados
para cualquiera de estos dos objetivos.
234
235
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
por si acaso, reserva 100000 entradas en un vector. Lo ms probable es que un usuario
convencional no gaste ms de un centenar. Estaremos desperdiciando, pues, unas 99900
celdas del vector, cada una de las cuales puede consistir en un centenar de bytes. Si
todas las aplicaciones del ordenador se disearan as, la memoria disponible se agotara
rapidsimamente.
malloc free
Afortunadamente, podemos denir, durante la ejecucin del programa, vectores cuyo ta-
mao es exactamente el que el usuario necesita. Utilizaremos para ello dos funciones de
la biblioteca estndar (disponibles incluyendo la cabecera ):
malloc (abreviatura de memory allocate, que podemos traducir por reservar me-
moria): solicita un bloque de memoria del tamao que se indique (en bytes);
free (que en ingls signica 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
int talla
printf scanf talla
malloc talla sizeof int
for 0 talla
free
return 0
Fjate en cmo se ha denido el vector (lnea 6): como int , es decir, como puntero
a entero. No te dejes engaar: no se trata de un puntero a un entero, sino de un puntero
a una secuencia de enteros. Ambos conceptos son equivalentes en C, pues ambos son
meras direcciones de memoria. La variable 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 , 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 la direccin
de memoria en la que empiezan esos enteros. La funcin malloc presenta un prototipo
similar a ste:
void malloc int bytes
Es una funcin que devuelve un puntero especial, del tipo de datos void . Qu signica
void ? Signica puntero a cualquier tipo de datos, o sea, direccin de memoria,
Introduccin a la programacin con C 235 c UJI
236
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 ahora 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 valores
de tipo int, hemos de solicitar memoria para talla sizeof int bytes. Recuerda que
sizeof int es la ocupacin en bytes de un dato de tipo int (y que estamos asumiendo
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:
a
0 1 2 3 4
Es decir, se reserva suciente memoria para albergar 5 enteros.
Como puedes ver, las lneas 1112 tratan a como si fuera un vector de enteros
cualquiera. Una vez has reservado memoria para un vector dinmico, no hay diferencia
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 del
mismo tipo base.
Aritmtica de punteros
Una curiosidad: el acceso indexado 0 es equivalente a . En general, es
equivalente a , es decir, ambas son formas de expresar el concepto accede al
contenido de la direccin con un desplazamiento de veces el tamao del tipo base.
La sentencia de asignacin podra haberse escrito como . En C es
posible sumar o restar un valor entero a un puntero. El entero se interpreta 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 sus detrac-
tores: 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 guarda
en 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 memoria 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 disponible para
otras posibles llamadas a malloc. Es como cerrar un chero: si no necesito un recurso, lo
libero para que otros lo puedan aprovechar.
2
Puedes aprovechar as la memoria de forma
ptima.
Recuerda: tu programa debe efectuar una llamada a free por cada llamada a malloc.
Es muy importante.
Conviene que despus de hacer free asignes al puntero el valor , especialmente
si la variable sigue viva durante bastante tiempo. es una constante denida en
. Si un puntero vale , se entiende que no apunta a un bloque de memoria.
Grcamente, un puntero que apunta a se representa as:
2
Y, como en el caso de un chero, si no lo liberas t explcitamente, se libera automticamente al nalizar
la ejecucin del programa. An as, te exigimos disciplina: oblgate a liberarlo t mismo tan pronto dejes de
necesitarlo.
Introduccin a la programacin con C 236 c UJI
237
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
a
Liberar memoria no cambia el valor del puntero
La llamada a free libera la memoria apuntada por un puntero, pero no modica 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 de tipo int , es decir,
imagina que vale 1000. Cuando ejecutamos free , ese bloque se libera y pasa a
estar disponible para eventuales llamadas a malloc, pero sigue valiendo 1000! Por
qu? Porque se ha pasado a free por valor, no por referencia, as que free no tiene
forma de modicar el valor de . Es recomendable que asignes a el valor despus
de una llamada a free, pues as haces explcito que la variable no apunta a nada.
Recuerda, pues, que es responsabilidad tuya y que conviene hacerlo: asigna expl-
citamente el valor a todo puntero que no apunte a memoria reservada.
La funcin malloc puede fallar por diferentes motivos. Podemos saber cundo ha falla-
do porque malloc lo notica 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 memoria
solicitada y, en caso contrario, tratar el error.
malloc talla sizeof int
if
printf
else
Es posible (y una forma de expresin idiomtica de C) solicitar la memoria y comprobar si
se pudo obtener en una nica lnea (presta atencin al uso de parntesis, es importante):
if malloc talla sizeof int
printf
else
Nuestros programas, sin embargo, no incluirn esta comprobacin. Estamos aprendiendo
a programar y sacricaremos las comprobaciones como sta en aras de la legibilidad
de los programas. Pero no lo olvides: los programas con un acabado profesional deben
comprobar y tratar posibles excepciones, como la no existencia de suciente memoria.
Tambin puedes usar para inicializar punteros y dejar explcitamente claro que
no se les ha reservado memoria.
include
include
int main void
int
Introduccin a la programacin con C 237 c UJI
238
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Fragmentacin de la memoria
Ya hemos dicho que malloc puede fracasar si se solicita ms memoria de la disponible 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 megabytes,
podamos solicitar un bloque de hasta 48 megabytes. Pero eso no est garantizado.
Imagina que los 16 megabytes ya ocupados no estn dispuestos contiguamente en la
memoria sino que, por ejemplo, se alternan con fragmentos de memoria libre de modo
que, de cada cuatro megabytes, uno est ocupado y tres estn libres, como muestra esta
gura:
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 alternancia de bloques
libres y ocupados que limita su disponibilidad. La fragmentacin no slo limita el mximo
tamao de bloque que puedes solicitar, adems, afecta a la eciencia con la que se
ejecutan las llamadas a malloc y free.
int talla
printf scanf talla
malloc talla sizeof int
for 0 talla
free
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 aquellos
cuyo valor es par y los devuelve en un nuevo vector cuya memoria se solicita dinmica-
mente.
int selecciona pares int int talla
int numpares 0
int pares
Primero hemos de averiguar cuntos elementos pares hay en .
for 0 talla
if 2 0
numpares
Ahora podemos pedir memoria para ellos.
pares malloc numpares sizeof int
Introduccin a la programacin con C 238 c UJI
239
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Aritmtica de punteros y recorrido de vectores
La aritmtica de punteros da lugar a expresiones idiomticas de C que deberas saber
leer. Fjate en este programa:
include
include
int main void
int
int talla
int
printf scanf talla
malloc talla sizeof int
for 0 talla
free
return 0
El efecto del bucle es inicializar el vector con la secuencia 0, 1, 2. . . El puntero empieza
apuntando a donde , o sea, al principio del vector. Con cada autoincremento, , pasa
a apuntar a la siguiente celda. Y la sentencia asigna al lugar apuntado por el
valor .
Y, nalmente, copiar los elementos pares en la zona de memoria solicitada.
0
for 0 talla
if 2 0
pares
return pares
Observa que devolvemos un dato de tipo int , es decir, un puntero a entero; bueno, en
realidad se trata de un puntero a una secuencia de enteros (recuerda que son conceptos
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
dene 10
int main void
int vector
Introduccin a la programacin con C 239 c UJI
240
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int seleccion
Llenamos el vector con valores aleatorios.
srand time 0
for 0
vector rand
Se efecta ahora la seleccin de pares.
seleccion selecciona pares vector
La variable seleccion apunta ahora a la zona de memoria con los elementos pares.
S, pero,
?
cuntos elementos pares hay?
for 0
printf seleccion
free seleccion
seleccion
return 0
Tenemos un problema al usar selecciona pares: no sabemos cuntos valores ha se-
leccionado. Podemos modicar la funcin para que modique el valor de un parmetro
que pasamos por referencia:
int selecciona pares int int talla int numpares
int
int pares
Contamos los elementos pares en el parmetro numpares, pasado por referencia.
numpares 0
for 0 talla
if 2 0
numpares
pares malloc numpares sizeof int
0
for 0 talla
if 2 0
pares
return pares
Ahora podemos resolver el problema:
include
include
include
dene 10
int selecciona pares int int talla int numpares
int
int pares
Introduccin a la programacin con C 240 c UJI
241
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Contamos los elementos pares en el parmetro numpares, pasado por referencia.
numpares 0
for 0 talla
if 2 0
numpares
pares malloc numpares sizeof int
0
for 0 talla
if 2 0
pares
return pares
int main void
int vector
int seleccion seleccionados
Llenamos el vector con valores aleatorios.
srand time 0
for 0
vector 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 elementos pares.
Adems, la variable seleccionados contiene el nmero de pares.
Ahora los mostramos en pantalla.
for 0 seleccionados
printf seleccion
free seleccion
seleccion
return 0
Por cierto, el prototipo de la funcin, que es ste:
int selecciona pares int int talla int seleccionados
puede cambiarse por este otro:
int selecciona pares int int talla int seleccionados
Conceptualmente, es lo mismo un parmetro declarado como int que como int :
ambos son, en realidad, punteros a enteros
3
. No obstante, es preferible utilizar la primera
forma cuando un parmetro es un vector de enteros, ya que as lo distinguimos fcilmente
de un entero pasado por referencia. Si ves el ltimo prototipo, no hay nada que te permita
saber si 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 hace que sea un puntero inmutable,
mientras que int permite modicar la direccin apuntada por haciendo, por ejemplo, . De todos
modos, no haremos uso de esa diferencia en este texto.
Introduccin a la programacin con C 241 c UJI
242
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 10
for 0 10
1
return
La funcin devuelve, a n de cuentas, una direccin de memoria en la que empieza 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 se
libera tan pronto naliza la ejecucin de la funcin. Este intento de uso de la funcin,
por ejemplo, trata de acceder ilegalmente a memoria:
int main void
int
primeros
printf No existe .
Recuerda: si devuelves un puntero, ste no puede apuntar a datos locales.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 devolver
dos datos: un puntero al nuevo vector de enteros positivos y su longitud. El puntero
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 oat
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 oat cuyo tamao se
solicitar previamente al usuario. Una vez ledos los componentes del vector, el programa
copiar sus valores en otro vector distinto que ordenar con el mtodo de la burbuja.
Recuerda liberar toda memoria dinmica solicitada antes de nalizar el programa.
222 Escribe una funcin que lea por teclado un vector de oat cuyo tamao se
solicitar previamente al usuario. Escribe, adems, una funcin que reciba un vector como
el ledo en la funcin anterior y devuelva una copia suya con los mismos valores, pero
ordenados de menor a mayor (usa el mtodo de ordenacin de la burbuja o cualquier otro
que conozcas).
Disea un programa que haga uso de ambas funciones. Recuerda que debes liberar
toda memoria dinmica solicitada antes de nalizar la ejecucin del programa.
223 Escribe una funcin que reciba un vector de enteros y devuelva otro con sus
mayores valores, siendo un nmero menor o igual que la talla del vector original.
224 Escribe una funcin que reciba un vector de enteros y un valor . Si es menor
o igual que la talla del vector, la funcin devolver un vector con las primeras celdas
Introduccin a la programacin con C 242 c UJI
243
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
del vector original. En caso contrario, devolver un vector de 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 la vez,
mediante parmetros pasados por referencia. Una posibilidad es usar nicamente valores
pasados por referencia:
include
include
include
dene 10
void selecciona pares int int talla int pares int numpares
int
numpares 0
for 0 talla
if 2 0
numpares
pares malloc numpares sizeof int
0
for 0 talla
if 2 0
pares
int main void
int vector
int seleccion seleccionados
srand time 0
for 0
vector rand
selecciona pares vector seleccion seleccionados
for 0 seleccionados
printf seleccion
free seleccion
seleccion
return 0
Fjate en la declaracin del parmetro pares en la lnea 7: es un puntero a un vector
de enteros, o sea, un vector de enteros cuya direccin se suministra a la funcin. Por qu?
Porque a resultas de llamar a la funcin, la direccin apuntada por pares ser una nueva
direccin (la que obtengamos mediante una llamada a malloc). La lnea 16 asigna un valor
a pares. Resulta interesante que veas cmo se asigna valores al vector apuntado por
pares en la lnea 21 (los parntesis alrededor de pares son obligatorios). Finalmente,
observa que seleccion se declara en la lnea 27 como un puntero a entero y que se pasa
Introduccin a la programacin con C 243 c UJI
244
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 un
puntero de enteros. La cabecera de la funcin selecciona pares podra haberse denido
as:
void selecciona pares int int talla int pares int numpares
Ves cmo usamos un doble asterisco?
Ms elegante resulta denir un registro vector dinmico de enteros que almacene
conjuntamente tanto el vector de elementos propiamente dicho como el tamao del vector
4
:
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 elementos
pares del primero.
int
struct VectorDinamicoEnteros pares
pares talla 0
for 0 entrada talla
if entrada elementos 2 0
pares talla
pares elementos malloc pares talla sizeof int
0
for 0 entrada talla
if entrada elementos 2 0
pares elementos entrada elementos
return pares
int main void
int
struct VectorDinamicoEnteros vector seleccionados
vector talla 10
vector elementos malloc vector talla sizeof int
srand time 0
for 0 vector talla
vector elementos rand
4
Aunque recomendemos este nuevo mtodo para gestionar vectores de tamao variable, has de saber,
cuando menos, leer e interpretar correctamente parmetros con tipos como int , int , int o int ,
pues muchas veces tendrs que utilizar bibliotecas escritas por otros programadores o leer cdigo fuente de
programas cuyos diseadores optaron por estos estilos de paso de parmetros.
Introduccin a la programacin con C 244 c UJI
245
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Valores de retorno como aviso de errores
Es habitual que aquellas funciones C que pueden dar lugar a errores nos adviertan
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 , la funcin devuelve el valor 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 , y si devuelve el valor 0, no consigui leer ninguno de
los dos. Un programa robusto debe comprobar el valor devuelto siempre que se efecte
una llamada a scanf ; as:
if scanf 2
printf
else
Situacin normal.
Las rutinas que nosotros diseamos deberan presentar un comportamiento similar. La
funcin selecciona pares, por ejemplo, podra implementarse as:
int selecciona pares int int talla int pares int numpares
int
numpares 0
for 0 talla
if 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.
0
for 0 talla
if 2 0
pares
return 1 Si llegamos aqu, todo fue bien, as que avisamos de ello con el valor 1.
Aqu tienes un ejemplo de uso de la nueva funcin:
if selecciona pares vector seleccion seleccionados
Todo va bien.
else
Algo fue mal.
Hay que decir, no obstante, que esta forma de aviso de errores empieza a quedar
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
Introduccin a la programacin con C 245 c UJI
246
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
for 0 seleccionados talla
printf seleccionados elementos
free seleccionados elementos
seleccionados elementos
seleccionados talla 0
return 0
El nico problema de esta aproximacin es la potencial fuente de ineciencia que
supone devolver una copia de un registro, pues podra ser de gran tamao. No es nuestro
caso: un struct VectorDinamicoEnteros ocupa slo 8 bytes. Si el tamao fuera un problema,
podramos usar una variable de ese tipo como parmetro pasado por referencia. Usaramos
as slo 4 bytes:
include
include
struct VectorDinamicoEnteros
int elementos
int talla
void selecciona pares struct VectorDinamicoEnteros entrada
struct VectorDinamicoEnteros pares
int
pares talla 0
for 0 entrada talla
if entrada elementos 2 0
pares talla
pares elementos malloc pares talla sizeof int
0
for 0 entrada talla
if entrada elementos 2 0
pares elementos entrada elementos
int main void
int
struct VectorDinamicoEnteros vector seleccionados
vector talla 10
vector elementos malloc vector talla sizeof int
for 0 vector talla
vector elementos rand
selecciona pares vector seleccionados
for 0 seleccionados talla
printf seleccionados elementos
free seleccionados elementos
seleccionados elementos
Introduccin a la programacin con C 246 c UJI
247
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
seleccionados talla 0
return 0
Como ves, tienes muchas soluciones tcnicamente diferentes para realizar lo mismo.
Debers elegir en funcin de la elegancia de cada solucin y de su eciencia.
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 0 5 o
0 0 0 0 0 , ests reservando espacio en memoria para 5 elementos y asig-
nndole a cada elemento el valor 0. La variable puede verse como un simple 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 ), Python se encarga de detectar automticamente que la lista ya no es apuntada 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 calcule
su permetro. Empezaremos por crear un tipo de datos para almacenar los puntos de un
polgono. Nuestro tipo de datos se dene as:
struct Punto
oat
struct Poligono
struct Punto
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 para
guardar dichos puntos en el campo (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
struct Poligono pol
printf scanf pol puntos
pol malloc pol puntos sizeof struct Punto
for 0 pol puntos
printf
printf scanf pol
printf scanf pol
return pol
Introduccin a la programacin con C 247 c UJI
248
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Es interesante la forma en que solicitamos memoria para el vector de puntos:
pol 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
pol
pol puntos 0
Vamos ahora a denir una funcin que calcula el permetro de un polgono:
oat perimetro poligono struct Poligono pol
int
oat perim 0.0
for 1 pol puntos
perim sqrt pol pol 1
pol pol 1
pol pol 1
pol pol 1
perim sqrt pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
return perim
Es importante que entiendas bien expresiones como pol . Esa, en particular, signi-
ca: del parmetro pol, que es un dato de tipo struct Poligono, accede al componente del
campo , que es un vector de puntos; dicho componente es un dato de tipo struct Punto,
pero slo nos interesa acceder a su campo (que, por cierto, es de tipo oat).
Juntemos todas las piezas y aadamos un sencillo programa principal que invoque a
las funciones desarrolladas:
include
include
struct Punto
oat
struct Poligono
struct Punto
int puntos
struct Poligono lee poligono void
int
struct Poligono pol
Introduccin a la programacin con C 248 c UJI
249
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
printf scanf pol puntos
pol malloc pol puntos sizeof struct Punto
for 0 pol puntos
printf
printf scanf pol
printf scanf pol
return pol
void libera poligono struct Poligono pol
free pol
pol
pol puntos 0
oat perimetro poligono struct Poligono pol
int
oat perim 0.0
for 1 pol puntos
perim sqrt pol pol 1
pol pol 1
pol pol 1
pol pol 1
perim sqrt pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
return perim
int main void
struct Poligono un poligono
oat 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
oat
struct Poligono
struct Punto
Introduccin a la programacin con C 249 c UJI
250
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int puntos
void lee poligono struct Poligono pol
int
printf scanf pol puntos
pol malloc pol puntos sizeof struct Punto
for 0 pol puntos
printf
printf scanf pol
printf scanf pol
void libera poligono struct Poligono pol
free pol
pol
pol puntos 0
oat perimetro poligono const struct Poligono pol
int
oat perim 0.0
for 1 pol puntos
perim sqrt pol pol 1
pol pol 1
pol pol 1
pol pol 1
perim sqrt pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
pol pol puntos 1 pol 0
return perim
int main void
struct Poligono un poligono
oat 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 copia de su
contenido. Hay una razn para hacerlo: la eciencia. Cada dato de tipo struct Poligono
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 bytes.
Si, por contra, pasamos su direccin de memoria, slo hay que pasar 4 bytes. En este
Introduccin a la programacin con C 250 c UJI
251
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
caso particular no hay una ganancia extraordinaria, pero en otras aplicaciones manejars
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 calicando el parmetro de perimetro poligono.
Su uso es opcional y sirve para indicar que, aunque es posible modicar la informacin
apuntada por pol, no lo haremos. En realidad suministramos el puntero por cuestin de
eciencia, no porque deseemos modicar el contenido. Con esta indicacin conseguimos
dos efectos: si intentsemos modicar accidentalmente el contenido, el compilador nos
advertira del error; y, si fuera posible, el compilador efectuara optimizaciones que no
podra aplicar si la informacin apuntada por pol pudiera modicarse en la funcin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
225 Funciona esta otra implementacin de perimetro poligono?
oat perimetro poligono struct Poligono pol
int
oat perim 0.0
for 1 pol puntos 1
perim sqrt pol pol puntos pol 1
pol pol puntos pol 1
pol pol puntos pol 1
pol pol puntos pol 1
return perim
226 Disea una funcin que cree un polgono regular de lados inscrito en una
circunferencia de radio . Esta gura muestra un pentgono inscrito en una circunferencia
de radio y las coordenadas de cada uno de sus vrtices:
( cos(0) sin(0))
( cos(2/5) sin(2/5))
( cos(2 2/5) sin(2 2/5))
( cos(2 3/5) sin(2 3/5))
( cos(2 4/5) sin(2 4/5))
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 oat . Dos asteriscos, no uno. Eso es porque se trata de un puntero 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 las y columnas. En la
lnea 13 encontramos una peticin de memoria. Se solicita espacio para un nmero las
de punteros a oat. Supongamos que las vale 4. Tras esa peticin, tenemos la siguiente
asignacin de memoria para :
Introduccin a la programacin con C 254 c UJI
255
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
m
0
1
2
3
El vector es un vector dinmico cuyos elementos son punteros (del tipo oat ). De
momento, esos punteros no apuntan a ninguna zona de memoria reservada. De ello se
encarga la lnea 15. Dicha lnea est en un bucle, as que se ejecuta para 0 , 1 ,
2 , . . . El efecto es proporcionar un bloque de memoria para cada celda de . He aqu
el efecto nal:
m
0
0 1 2 3 4
1
0 1 2 3 4
2
0 1 2 3 4
3
0 1 2 3 4
Acceso a las y elementos
Bien. Y cmo se usa ahora? Como cualquier matriz! Pensemos en qu ocurre cuando
accedemos a 1 2 . Analicemos 1 2 de izquierda a derecha. Primero tenemos
a , que es un puntero (tipo oat ), o sea, un vector dinmico a elementos del tipo
oat . El elemento 1 es el segundo componente de . Y de qu tipo es? De tipo
oat , un nuevo puntero o vector dinmico, pero a valores de tipo oat. Si es un vector
dinmico, lo podemos indexar, as que es vlido escribir 1 2 . Y de qu tipo es eso?
De tipo oat. Fjate:
es de tipo oat ;
1 es de tipo oat ;
1 2 es de tipo oat.
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 . Si empezsemos liberando ,
cometeramos un grave error: si liberamos antes que todos los , perderemos el
puntero que los referencia y, en consecuencia, no podremos liberarlos!
free
return 0
En la siguiente gura se muestra el estado de la memoria tras la inicializacin de la
matriz tridimensional:
Introduccin a la programacin con C 267 c UJI
268
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
a
0
0 1 2
1
0 1 2
2
0 1 2
0
0
1
1
2
2
1
0
2
1
3
2
2
0
3
1
4
2
1
0
2
1
3
2
2
0
3
1
4
2
3
0
4
1
5
2
2
0
3
1
4
2
3
0
4
1
5
2
4
0
5
1
6
2
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 nalmente. Podemos jar
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 solicitado
previamente con malloc crezca en funcin de las necesidades. Se usa para ello la funcin
realloc, cuyo prototipo es (similar a) ste:
void realloc void puntero int bytes
Aqu tienes un ejemplo de uso:
include
int main void
int
malloc 10 sizeof int Se pide espacio para 10 enteros.
realloc 20 sizeof int Ahora se ampla para que quepan 20.
realloc 5 sizeof int Y ahora se reduce a slo 5 (los 5 primeros).
free
return 0
Introduccin a la programacin con C 268 c UJI
269
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
La funcin realloc recibe como primer argumento el puntero que indica la zona de me-
moria 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 asignada. Si
las posiciones de memoria que siguen a las que ocupa en ese instante estn
libres, se las asigna a , sin ms. En caso contrario, solicita una nueva zona de
memoria, copia el contenido de la vieja zona de memoria en la nueva, libera 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. Un 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 parmetro,
realloc se comporta como si se llamara a malloc.
Como puedes imaginar, un uso constante de realloc puede ser una fuente de ine-
ciencia. Si tienes un vector que ocupa un 1 megabyte y usas realloc para que ocupe 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 suciente para efectuar la
nueva reserva, pues durante un instante (mientras se efecta la copia) estars usando 2.1
megabytes.
Desarrollemos un ejemplo para ilustrar el concepto de reasignacin o redimensio-
namiento 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 denir el tipo de datos para un diccionario.
struct Diccionario
char palabra
int palabras
Aqu tienes un ejemplo de diccionario que contiene 4 palabras:
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
Un diccionario vaco no tiene palabra alguna ni memoria reservada. Esta funcin crea
un diccionario:
struct Diccionario crea diccionario void
struct Diccionario
palabra
Introduccin a la programacin con C 269 c UJI
270
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
palabras 0
return
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 char pal
int
for 0 palabras
if strcmp palabra pal 0
return
Si la palabra no est, hemos de aadirla:
void inserta palabra en diccionario struct Diccionario char pal
int
for 0 palabras
if strcmp palabra pal 0
return
palabra realloc palabra palabras 1 sizeof char
palabra palabras malloc strlen pal 1 sizeof char
strcpy palabra palabras pal
palabras
Podemos liberar la memoria ocupada por un diccionario cuando no lo necesitamos
ms llamando a esta otra funcin:
void libera diccionario struct Diccionario
int
if palabra
for 0 palabras
free palabra
free palabra
palabra
palabras 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 prejo dado (una cadena).
Introduccin a la programacin con C 270 c UJI
271
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
No es lo mismo un puntero que un vector
Aunque C permite considerarlos una misma cosa en muchos contextos, hay algunas
diferencias entre un puntero a una serie de enteros, por ejemplo, y un vector de enteros.
Consideremos un programa con estas declaraciones:
int vector 10
int escalar
int puntero
int 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 al que puede
apuntar el puntero: puntero escalar;
a la direccin de memoria en la que empieza un vector: puntero 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: punte-
ro 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 redimensionarlos.
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 simplicacin, 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 sujo dado (una cadena).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
La funcin que determina si una palabra pertenece o no a un diccionario requiere
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 modicar la funcin de insercin de
palabras en el diccionario:
void inserta palabra en diccionario struct Diccionario char pal
int
for 0 palabras
if strcmp palabra pal 0 Si ya est, no hay nada que hacer.
return
if strcmp palabra pal 0 Encontramos su posicin (orden alfabtico)
break
Introduccin a la programacin con C 271 c UJI
272
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Si llegamos aqu, la palabra no est y hay que insertarla en la posicin , desplazando
antes el resto de palabras.
Reservamos espacio en la lista de palabras para una ms.
palabra realloc palabra palabras 1 sizeof char
Desplazamos el resto de palabras para que haya un hueco en el vector.
for palabras
palabra malloc strlen palabra 1 1 sizeof char
strcpy palabra palabra 1
free palabra 1
Y copiamos en su celda la nueva palabra
palabra malloc strlen pal 1 sizeof char
strcpy palabra pal
palabras
Buf! Las lneas 2022 no hacen ms que asignar a una palabra el contenido de otra
(la que ocupa la posicin recibe una copia del contenido de la que ocupa la posicin
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 2022:
for palabras
palabra palabra 1
Y copiamos en su celda la nueva palabra
palabra malloc strlen pal 1 sizeof char
strcpy palabra pal
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 cadena
a otra. Cmo es posible? Antes hemos de comentar qu signica una asignacin como
sta:
palabra palabra 1
Signica que palabra apunta al mismo lugar al que apunta palabra 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, es
decir, que ambos apunten al mismo lugar.
Veamos qu pasa estudiando un ejemplo. Imagina un diccionario en el que ya hemos
insertado las palabras anual, dadivoso, mano y taco y que vamos a insertar ahora
la palabra feliz. Partimos, pues, de esta situacin:
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
Una vez hemos redimensionado el vector de palabras, tenemos esto:
Introduccin a la programacin con C 272 c UJI
273
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
4
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
La nueva palabra debe insertarse en la posicin de ndice 2. El bucle ejecuta la asigna-
cin para tomando los valores 4 y 3. Cuando se
ejecuta la iteracin con igual a 4, tenemos:
4
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
La ejecucin de la asignacin ha hecho que palabra 4 apunte al mismo lugar que
palabra 3 . No hay problema alguno en que dos punteros apunten a un mismo bloque
de memoria. En la siguiente iteracin pasamos a esta otra situacin:
4
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
Podemos reordenar grcamente los elementos, para ver que, efectivamente, estamos ha-
ciendo hueco para la nueva palabra:
4
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
El bucle ha acabado. Ahora se pide memoria para el puntero palabra (siendo
igual a 2). Se piden 6 bytes (feliz tiene 5 caracteres ms el terminador nulo):
Introduccin a la programacin con C 273 c UJI
274
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
4
3
2
1
0
t a c o
m a n o
d a d i v o s o
a n u a l
palabra
palabras
4
Y, nalmente, se copia en palabra el contenido de pal con la funcin strcpy y se
incrementa el valor de palabras:
4
3
2
1
0
t a c o
m a n o
f e l i z
d a d i v o s o
a n u a l
palabra
palabras
5
Introduccin a la programacin con C 274 c UJI
275
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Podemos ahora implementar una funcin de bsqueda de palabras ms eciente.
Una primera idea consiste en buscar desde el principio y parar cuando se encuentre
la palabra buscada o cuando se encuentre una palabra mayor (alfabticamente) que la
buscada. En este ltimo caso sabremos que la palabra no existe. Pero an hay una forma
ms eciente de saber si una palabra est o no en una lista ordenada: mediante una
bsqueda dicotmica.
int buscar en diccionario struct Diccionario char pal
int izquierda centro derecha
izquierda 0
derecha palabras
while izquierda derecha
centro izquierda derecha 2
if strcmp pal palabra centro 0
return 1
else if strcmp pal palabra centro 0
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 char pal
int izquierda centro derecha comparacion
izquierda 0
derecha palabras
while izquierda derecha
centro izquierda derecha 2
comparacion strcmp pal 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
dene 80
struct Diccionario
char palabra
Introduccin a la programacin con C 275 c UJI
276
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
int palabras
struct Diccionario crea diccionario void
struct Diccionario
palabra
palabras 0
return
void libera diccionario struct Diccionario
int
if palabra
for 0 palabras
free palabra
free palabra
palabra
palabras 0
void inserta palabra en diccionario struct Diccionario char pal
int
for 0 palabras
if strcmp palabra pal 0 Si ya est, no hay nada que hacer.
return
if strcmp palabra pal 0 Aqu hemos encontrado su posicin (orden alfabtico)
break
Si llegamos aqu, la palabra no est y hay que insertarla en la posicin , desplazando
antes el resto de palabras.
Reservamos espacio en la lista de palabras para una ms.
palabra realloc palabra palabras 1 sizeof char
Desplazamos el resto de palabras para que haya un hueco en el vector.
for palabras
palabra malloc strlen palabra 1 1 sizeof char
strcpy palabra palabra 1
free palabra 1
Y copiamos en su celda la nueva palabra
palabra malloc strlen pal 1 sizeof char
strcpy palabra pal
palabras
int buscar en diccionario struct Diccionario char pal
int izquierda centro derecha comparacion
izquierda 0
derecha palabras
while izquierda derecha
Introduccin a la programacin con C 276 c UJI
277
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
centro izquierda derecha 2
comparacion strcmp pal 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 0
libera diccionario mi diccionario
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
243 Cuntas comparaciones se hacen en el peor de los casos en la bsqueda dico-
tmica de una palabra cualquiera en un diccionario con 8 palabras? Y en un diccionario
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 2
20
.)
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 nal.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 277 c UJI
278
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Vamos a desarrollar un ejemplo completo: una agenda telefnica. Utilizaremos vectores
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 redi-
mensionamiento, 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 usare-
mos redimensionamiento para gestionar el vector de entradas de la agenda: gastaremos
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 grca de
las estructuras de datos con las que representamos la agenda del ejemplo:
persona
personas
4
nombre
telefono
telefonos
2
nombre
telefono
telefonos
3
nombre
telefono
telefonos
0
nombre
telefono
telefonos
1
P
0
e
1
p
2
e
3 4
P
5
e
6
r
7
e
8
z
9
\0
10
A
0
n
1
a
2 3
G
4
a
5
r
6
c
7
8
a
9
\0
10
J
0
u
1
a
2
n
3 4
G
5
i
6
l
7
\0
8
M
0
a
1
r
2
3
a
4 5
P
6
a
7
z
8
\0
9
0
0 1 2 3 4 5 6 7 8 9 10
0
0 1 2 3 4 5 6 7 8 9 10
1
0 1 2 3 4 5 6 7 8 9 10
2
0 1 2 3 4 5 6 7 8 9 10
0
0 1 2 3 4 5 6 7 8 9
1
0 1 2 3 4 5 6 7 8 9
Empezaremos proporcionando soporte para el tipo de datos entrada: un nombre
y un listado de telfonos (un vector dinmico).
include
include
Introduccin a la programacin con C 278 c UJI
279
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Entradas
struct Entrada
char nombre Nombre de la persona.
char telefono Vector dinmico de nmeros de telfono.
int telefonos Nmero de elementos en el anterior vector.
void crea entrada struct Entrada char nombre
Inicializa una entrada con un nombre. La lista de telfonos se pone a .
Reservamos memoria para el nombre y efectuamos la asignacin.
nombre malloc strlen nombre 1 sizeof char
strcpy nombre nombre
telefono
telefonos 0
void anyadir telefono a entrada struct Entrada char telefono
telefono realloc telefono telefonos 1 sizeof char
telefono telefonos malloc strlen telefono 1 sizeof char
strcpy telefono telefonos telefono
telefonos
void muestra entrada struct Entrada
Podramos haber pasado por valor, pero resulta ms eciente (y no mucho ms
incmodo) hacerlo por referencia: pasamos as slo 4 bytes en lugar de 12.
int
printf nombre
for 0 telefonos
printf 1 telefono
void libera entrada struct Entrada
int
free nombre
for 0 telefonos
free telefono
free telefono
nombre
telefono
telefonos 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
246 Modica 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 279 c UJI
280
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Ya tenemos resuelta la gestin de entradas. Ocupmonos ahora del tipo agenda y de
su gestin.
Agenda
struct Agenda
struct Entrada persona Vector de entradas
int personas Nmero de entradas en el vector
struct Agenda crea agenda void
struct Agenda
persona
personas 0
return
void anyadir persona struct Agenda char nombre
int
Averiguar si ya tenemos una persona con ese nombre
for 0 personas
if strcmp persona nombre nombre 0
return
Si llegamos aqu, es porque no tenamos registrada a esa persona.
persona realloc persona personas 1 sizeof struct Entrada
crea entrada persona personas nombre
personas
void muestra agenda struct Agenda
Pasamos as por eciencia.
int
for 0 personas
muestra entrada persona
struct Entrada buscar entrada por nombre struct Agenda char nombre
int
for 0 personas
if strcmp persona nombre nombre 0
return persona
Si llegamos aqu, no lo hemos encontrado. Devolvemos para indicar que no
conocemos a esa persona.
return
Introduccin a la programacin con C 280 c UJI
281
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
void libera agenda struct Agenda
int
for 0 personas
libera entrada persona
free persona
persona
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 esa persona.
Ello nos permite, por ejemplo, mostrarla a continuacin llamando a la funcin que muestra
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 cadena ha de
ser explcitamente controlada por el programador y las operaciones que podemos hacer
con ellas son, en principio, primitivas. Python, por contra, libera al programador de
innumerables preocupaciones cuando trabaja con objetos como las cadenas o los vectores
dinmicos (sus listas). Adems, ofrece de fbrica numerosas utilidades para manipular
cadenas (cortes, funciones del mdulo string, etc.) y listas (mtodo append, sentencia del,
cortes, ndices negativos, etc.). Por qu no usamos siempre Python? Por eciencia. C
permite disear, por regla general, programas mucho ms ecientes que sus equivalentes
en Python. La mayor exibilidad de Python tiene un precio.
Antes de programar una aplicacin, hemos de preguntarnos si la eciencia es un
factor crtico. Un programa con formularios (con un interfaz grco) y/o accesos 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 (lenta) velocidad
de introduccin de datos y/o el sistema de base de datos al acceder a la informacin.
En tal caso, parece sensato escoger el lenguaje ms exible, el que permita desarrollar
la aplicacin con mayor facilidad. Un programa de clculo matricial, un sistema 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 implementacin.
Hay lenguajes de programacin que combinan la eciencia de C con parte de la
exibilidad de Python y pueden suponer una buena solucin de compromiso en muchos
casos. Entre ellos hemos de destacar el lenguaje C , que estudiars el prximo curso.
Y hay una opcin adicional: implementar en el lenguaje eciente las rutinas de
clculo pesadas y usarlas desde un lenguaje de programacin exible. Python ofrece un
interfaz que permite el uso de mdulos escritos en C o C . Su uso no es trivial, pero
hay herramientas como SWIG o Boost.Python que simplican enormemente estas
tareas.
Ya podemos escribir el programa principal.
include
include
dene 200
dene 40
dene 80
Introduccin a la programacin con C 281 c UJI
282
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 miagenda
break
case AltaPersona
printf
gets nombre
anyadir persona miagenda nombre
break
case AnyadirTelefono
printf
gets nombre
encontrada buscar entrada por nombre miagenda nombre
if encontrada
printf nombre
printf nombre
else
printf
gets telefono
anyadir telefono a entrada encontrada telefono
break
case Buscar
printf
Introduccin a la programacin con C 282 c UJI
283
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
gets nombre
encontrada buscar entrada por nombre miagenda nombre
if encontrada
printf nombre
else
muestra entrada encontrada
break
while opcion Salir
libera agenda miagenda
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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. Modica 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 ine-
ciencia, 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 con-
sumo 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 manera 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 siguiente
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 grco para ir captando
la idea:
lista
info sig info sig info sig
Conceptualmente, es lo mismo que se ilustra en este grco:
lista 3
0
8
1
2
2
Pero slo conceptualmente. En la implementacin, el nivel de complejidad al que nos en-
frentamos es mucho mayor. Eso s, a cambio ganaremos en exibilidad y podremos ofrecer
versiones ecientes de ciertas operaciones sobre listas de valores que implementadas 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.
Introduccin a la programacin con C 283 c UJI
284
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Redimensionamiento con holgura
Utilizamos realloc para aumentar celda a celda la reserva de memoria de los vectores que
necesitan crecer. Estos redimensionamientos, tan ajustados a las necesidades exactas
de cada momento, pasan factura: cada realloc es potencialmente lento, pues 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 celdas 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:
a
0 1 2 3 4
Cuando necesitamos escribir un nuevo dato en una celda adicional, comprobamos
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 salud y redi-
mensionamos pidiendo memoria para, pongamos, 10 celdas ms (y, consecuentemente,
incrementamos capacidad en 10 unidades). As reducimos las llamadas a realloc a la
dcima parte. Incrementar un nmero jo de celdas no es la nica estrategia posible.
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
struct crea VDH void
struct vdh
vdh dato malloc 1 sizeof int vdh talla 0 vdh capacidad 1
return vdh
void anyade dato struct vdh int undato
if vdh talla vdh capacidad
vdh capacidad 2
vdh dato realloc vdh dato vdh capacidad sizeof int
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 que
puede ser tolerable para nuestra aplicacin. Python usa internamente una variante de
esta tcnica al modicar la talla de una lista con el mtodo append.
Introduccin a la programacin con C 284 c UJI
285
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Para ir aprendiendo a usar estas tcnicas, gestionaremos ahora una lista con registros
enlazados. La manejaremos directamente, desde un programa principal, y nos centraremos
en la realizacin de una serie de acciones que parten de un estado de la lista y la dejan
en otro estado diferente. Ilustraremos cada una de las acciones mostrando con todo 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 denir el tipo de los registros 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 hay cierto
nivel de recursin o autoreferencia en la denicin: un struct Nodo contiene un
puntero a un struct Nodo. Por esta razn, las estructuras que vamos a estudiar se
denominan a veces estructuras recursivas.)
Si quisiramos manejar una lista de puntos en el plano, tendramos dos opciones:
1. denir un registro con varios campos para la informacin relevante:
struct Nodo
oat
oat
struct Nodo sig
lista
1.1
7.1
sig
0.2
0.1
sig
3.7
2.1
sig
2. denir un tipo adicional y utilizar un nico campo de dicho tipo:
struct Punto
oat
oat
struct Nodo
struct Punto info
struct Nodo sig
lista
11
71
info sig
02
01
info sig
37
21
info sig
Introduccin a la programacin con C 285 c UJI
286
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 maestro,
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 anterior
a esta otra:
lista
8
info sig
Cmo creamos el nuevo registro? Con malloc:
int main void
struct Nodo lista
lista malloc sizeof struct Nodo
ste es el resultado:
lista
info sig
Ya tenemos el primer nodo de la lista, pero sus campos an no tienen los valores que
deben tener nalmente. Lo hemos representado grcamente dejando el campo info en
blanco y sin poner una echa 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 malloc sizeof struct Nodo
lista info 8
lista sig
Introduccin a la programacin con C 286 c UJI
287
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 operador permite
acceder al campo de un registro apuntado por un puntero. He aqu el resultado:
lista
8
info sig
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 ubicaremos
justo al principio de la lista, delante del nodo que contiene el valor 8. O sea, partimos de
esta situacin:
lista
8
info sig
y queremos llegar a esta otra:
lista
3
info sig
8
info sig
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 valor 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 malloc sizeof struct Nodo
lista info 3
lista 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:
lista
3
info sig
8
info sig
No somos capaces de acceder al nodo que contiene el valor 8! Es lo que denominamos
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 pedido
con malloc, tampoco podremos liberarlo luego con free. Cuando se produce una prdida
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 memoria
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.
Introduccin a la programacin con C 287 c UJI
288
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 identicador de cada puntero. En una
lnea de declaracin que empieza por la palabra int puedes declarar punteros a enteros y
enteros, segn precedas los respectivos identicadores con asterisco o no. Detengmonos
un momento para considerar el estado de la memoria justo despus de ejecutarse la lnea
6, que reza aux lista:
aux
lista
8
info 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 apunten
al mismo lugar.
Sigamos con nuestra traza. Veamos cmo queda la memoria justo despus de ejecutar
la lnea 7, que dice lista malloc sizeof struct Nodo :
aux
lista
info sig
8
info sig
La lnea 8, que dice lista info 3, asigna al campo info del nuevo nodo (apuntado
por lista) el valor 3:
aux
lista
3
info sig
8
info sig
La lista an no est completa, pero observa que no hemos perdido la referencia al
ltimo fragmento de la lista. El puntero aux la mantiene. Nos queda por ejecutar la 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
lista
3
info sig
8
info sig
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 lista
que queramos construir. No importa cmo quedan los punteros auxiliares: una vez han
desempeado su funcin en la construccin de la lista, son supruos. Si te quedas ms
tranquilo, puedes aadir una lnea con aux al nal del programa para que aux no
quede apuntando a un nodo de la lista, pero, repetimos, es innecesario.
Introduccin a la programacin con C 288 c UJI
289
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Marqumonos un nuevo objetivo. Intentemos aadir un nuevo nodo al nal de la lista. Es
decir, partiendo de la ltima lista, intentemos obtener esta otra:
lista
3
info sig
8
info sig
2
info sig
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 apunte
al nuevo nodo. Finalmente, haremos que el nuevo nodo contenga el valor 2 y que tenga
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:
aux
info sig
lista
3
info sig
8
info sig
O sea, la lista que cuelga de lista sigue igual, pero ahora aux apunta a un nue-
vo nodo. Pasemos a estudiar la lnea 7, que parece complicada porque contiene varias
aplicaciones del operador . Esa lnea reza as: lista sig sig aux. Vamos a ver qu
signica 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 campo sig
del segundo nodo, que es otro puntero. Si a ese puntero le asignamos aux, el campo sig
del segundo nodo apunta a donde apuntar aux. Aqu tienes el resultado:
aux
info sig
lista
3
info sig
8
info sig
An no hemos acabado. Una vez hayamos ejecutado las lneas 8 y 9, el trabajo estar
completo:
aux
2
info sig
lista
3
info sig
8
info sig
Introduccin a la programacin con C 289 c UJI
290
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Y es so lo que buscbamos? S. Reordenemos grcamente los diferentes compo-
nentes para que su disposicin en la imagen se asemeje ms a lo que esperbamos:
aux
lista
3
info sig
8
info sig
2
info sig
Ahora queda ms claro que, efectivamente, hemos conseguido el objetivo. Esta gura
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 nal muy poco ex-
tensible. 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 qui-
siramos 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 nal! No
podemos expresar la idea aadir por el nal 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 puntero
auxiliar, digamos aux;
aux
lista
3
info sig
8
info sig
2. pedir un nodo nuevo y mantenerlo apuntado con otro puntero auxiliar, digamos
nuevo;
aux
lista
nuevo
info sig
3
info sig
8
info sig
3. escribir en el nodo apuntado por nuevo el nuevo dato y hacer que su campo sig
apunte a ;
aux
lista
nuevo
2
info sig
3
info sig
8
info sig
4. hacer que el nodo apuntado por aux tenga como siguiente nodo al nodo apuntado
por nuevo.
aux
lista
nuevo
2
info sig
3
info sig
8
info sig
Introduccin a la programacin con C 290 c UJI
291
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Lo que es equivalente a este otro grco en el que, sencillamente, hemos reorga-
nizado la disposicin de los diferentes elementos:
aux
lista
nuevo
3
info sig
8
info sig
2
info sig
Modiquemos 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 lo
mantienen apuntado con aux. El ltimo nodo se distingue porque al llegar a l, aux sig
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
lista
nuevo
3
info sig
8
info sig
Las lneas 911 dejan el estado de la memoria as:
aux
lista
nuevo
2
info sig
3
info sig
8
info sig
Finalmente, la lnea 12 completa la adicin del nodo:
aux
lista
nuevo
2
info sig
3
info sig
8
info sig
5
Aunque falla en un caso: si la lista est inicialmente vaca. Estudiaremos este problema y su solucin
ms adelante.
Introduccin a la programacin con C 291 c UJI
292
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 aux sig
nuevo malloc sizeof struct Nodo
nuevo info 2
nuevo sig
aux sig nuevo
return 0
Observa que el punto y coma que aparece al nal 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 nal es ms propia de la
programacin C, ms idiomtica.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
249 Hemos diseado un mtodo (que mejoraremos en el siguiente apartado) que
permite insertar elementos por el nal 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?:
lista
1
info sig
3
info sig
8
info sig
2
info sig
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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:
lista
3
info sig
8
info sig
2
info sig
llegar a esta otra:
lista
8
info sig
2
info sig
Como lo que deseamos es que lista pase a apuntar al segundo elemento de la lista,
podramos disear una aproximacin directa modicando el valor de lista:
int main void
struct Nodo lista aux nuevo
lista lista sig
!
Mal! Se pierde la referencia a la cabeza original de la lista.
return 0
Introduccin a la programacin con C 292 c UJI
293
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
El efecto obtenido por esa accin es ste:
lista
3
info sig
8
info sig
2
info sig
Fugas de memoria, colapsos y recogida de basura
Muchos programas funcionan correctamente. . . durante un rato. Cuando llevan un tiempo
ejecutndose, sin embargo, el ordenador empieza a ralentizarse sin explicacin aparente
y la memoria del ordenador se va agotando. Una de las razones para que esto ocurra
son las fugas de memoria. Si el programa pide bloques de memoria con malloc y no
los libera con free, ir consumiendo ms y ms memoria irremediablemente. Llegar un
momento en que no quede apenas memoria libre y la que quede, estar muy fragmentada,
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 fuga acabar
colapsando al ordenador y, en algunos sistemas operativos, obligando a reiniciar la
mquina.
El principal problema con las fugas de memoria es lo difciles de detectar 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 bueno 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 memoria propor-
cionando recogida de basura (del ingls garbage collection) automtica. Los sistemas 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 llamadas
a malloc (o la funcin/mecanismo equivalente en su lenguaje) y el sistema se encarga
de marcar como disponibles los bloques de memoria no referenciados. Lenguajes 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 preten-
damos, pero hemos perdido la referencia a un nodo (el que hasta ahora era el primero)
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 modicar lista:
int main void
struct Nodo lista aux nuevo
free lista
lista lista sig
!
Mal! lista no apunta a una zona de memoria vlida.
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 memoria:
lista
8
info sig
2
info sig
Introduccin a la programacin con C 293 c UJI
294
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 indenido. 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 nuevo
aux lista sig
free lista
lista aux
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
lista
3
info sig
8
info sig
2
info sig
Al ejecutar free lista , pasamos a esta otra situacin:
aux
lista
8
info sig
2
info sig
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
lista
8
info sig
2
info sig
Vas viendo ya el tipo de problemas al que nos enfrentamos con la gestin de listas?
Los siguientes apartados te presentan funciones capaces de inicializar listas, de insertar,
borrar y encontrar elementos, de mantener listas ordenadas, etc. Cada apartado te presen-
tar una variante de las listas enlazadas con diferentes prestaciones que permiten elegir
soluciones de compromiso entre velocidad de ciertas operaciones, consumo de memoria y
complicacin de la implementacin.
Vamos a desarrollar un mdulo que permita manejar listas de enteros. En el chero de
cabecera declararemos los tipos de datos bsicos:
struct Nodo
int info
struct Nodo sig
Introduccin a la programacin con C 294 c UJI
295
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Como ya dijimos, este tipo de nodo slo alberga un nmero entero. Si necesitsemos una
lista de oat 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 denido previamente
por nosotros.
Una lista es un puntero a un struct Nodo, pero cuesta poco denir 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, indistintamente.
Por claridad, nos referiremos al tipo de una lista con TipoLista y al de un puntero a un
nodo cualquiera con struct Nodo , pero no olvides que ambos tipos son equivalentes.
Denicin de struct con typedef
Hay quienes, para evitar la escritura repetida de la palabra struct, recurren a la inme-
diata creacin de un nuevo tipo tan pronto se dene 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 denir la estructura
as:
typedef struct Nodo
int info
TipoNodo sig
!
Mal!
TipoNodo
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 denicin.
Nuestra primera funcin crear una lista vaca. El prototipo de la funcin, que declaramos
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
Introduccin a la programacin con C 295 c UJI
296
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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 lista, 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 la
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 es 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 vacia
lista inserta por cabeza lista 2
lista inserta por cabeza lista 8
lista inserta por cabeza lista 3
return 0
Introduccin a la programacin con C 296 c UJI
297
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
o, equivalentemente, esta otra:
include
int main void
TipoLista lista
lista inserta por cabeza inserta por cabeza
inserta por cabeza lista vacia 2 8 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 malloc sizeof struct Nodo
nuevo info 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 sig
con lo que era la lista anteriormente. Hemos perdido la referencia a la lista original.
Vemoslo con un ejemplo. Imagina una lista como sta:
lista
8
info sig
2
info sig
La ejecucin de la funcin (incompleta) con valor igual a 3 nos lleva a esta otra situacin:
nuevo
3
info sig
lista
8
info sig
2
info sig
Hemos perdido la referencia a la vieja lista. Una solucin sencilla consiste en, antes
de modicar lista, asignar a nuevo sig el valor de lista:
Introduccin a la programacin con C 297 c UJI
298
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
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:
nuevo
info sig
lista
8
info sig
2
info sig
Las lneas 5 y 6 modican los campos del nodo apuntado por nuevo:
nuevo
3
info sig
lista
8
info sig
2
info sig
Finalmente, la lnea 7 hace que lista apunte a donde nuevo apunta. El resultado nal
es ste:
nuevo
3
info sig
lista
8
info sig
2
info sig
Slo resta redisponer grcamente la lista para que no quepa duda de la correccin de
la solucin:
nuevo
lista
3
info sig
8
info sig
2
info sig
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 que
siempre deberemos considerar la validez de nuestros mtodos.
Hagamos una comprobacin grca. Si partimos de esta lista:
lista
y ejecutamos la funcin (con valor igual a 10, por ejemplo), pasaremos momentneamente
por esta situacin:
nuevo
10
info sig
lista
y llegaremos, al nal, a esta otra:
Introduccin a la programacin con C 298 c UJI
299
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
nuevo
lista
10
info sig
Ha funcionado correctamente. No tendremos tanta suerte con todas las funciones que
vamos a disear.
Nos interesa conocer ahora la longitud de una lista. La funcin que disearemos recibe
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 un
puntero hasta llegar a . Con cada salto de nodo a nodo, incrementaremos un contador
cuyo valor nal ser devuelto por la funcin:
int longitud lista TipoLista lista
struct Nodo aux
int contador 0
for aux lista aux aux aux sig
contador
return contador
Hagamos una pequea traza. Si recibimos esta lista:
lista
3
info sig
8
info sig
2
info sig
la variable contador empieza valiendo 0 y el bucle inicializa aux haciendo que apunte al
primer elemento:
aux
lista
3
info sig
8
info sig
2
info sig
En la primera iteracin, contador se incrementa en una unidad y aux pasa a apuntar al
segundo nodo:
aux
lista
3
info sig
8
info sig
2
info sig
Acto seguido, en la segunda iteracin, contador pasa a valer 2 y aux pasa a apuntar al
tercer nodo:
aux
lista
3
info sig
8
info sig
2
info sig
Introduccin a la programacin con C 299 c UJI
300
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Finalmente, contador vale 3 y aux pasa a apuntar a :
aux
lista
3
info sig
8
info sig
2
info sig
Ah acaba el bucle. El valor devuelto por la funcin es 3, el nmero de nodos de la lista.
Observa que longitud lista tarda ms cuanto mayor es la lista. Una lista con nodos
obliga a efectuar 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
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 procedi-
miento 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 aux
for aux lista aux aux aux sig
printf aux info
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253 Disea un procedimiento que muestre el contenido de una lista al estilo Python.
Por ejemplo, la lista de la ltima gura se mostrar como 3 8 2 . Fjate en que la
coma slo aparece separando a los diferentes valores, no despus de todos los nmeros.
Introduccin a la programacin con C 300 c UJI
301
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con 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 representar as:
(La barra vertical representa a .)
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Diseemos ahora una funcin que inserte un nodo al nal 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 elemento
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 aux
for aux lista aux sig aux aux sig
Analicemos paso a paso el bucle con un ejemplo. Imagina que la lista que nos sumi-
nistran en lista ya tiene tres nodos:
lista
3
info sig
8
info sig
2
info sig
La primera iteracin del bucle hace que aux apunte al primer elemento de la lista:
aux
lista
3
info sig
8
info sig
2
info sig
Habr una nueva iteracin si aux sig es distinto de , es decir, si el nodo apun-
tado 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
lista
3
info sig
8
info sig
2
info sig
Sigue siendo cierto que aux sig es distinto de ? S. Avanzamos aux un nodo ms
a la derecha:
aux
lista
3
info sig
8
info sig
2
info sig
Introduccin a la programacin con C 301 c UJI
302
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Y ahora? Es cierto que aux sig es distinto de ? No, es igual a . Ya hemos
llegado al ltimo nodo de la lista. Fjate en que hemos parado un paso antes que cuando
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 enlazarlo
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 aux sig
nuevo malloc sizeof struct Nodo
nuevo info valor
nuevo sig
aux sig nuevo
return lista
El efecto de la ejecucin de las nuevas lneas, suponiendo que el valor es 10, es ste:
nuevo
10
info sig
aux
lista
3
info sig
8
info sig
2
info sig
Est claro que ha funcionado correctamente, no? Tal vez resulte de ayuda ver la misma
estructura reordenada as:
lista
3
info sig
8
info sig
2
info sig
10
info sig
aux nuevo
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 sig 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 por 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 maniestan
en tiempo de ejecucin y, normalmente, con consecuencias desastrosas
6
, especialmente
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 abortar inmediata-
mente la ejecucin del programa. En Microsoft Windows es frecuente que el ordenador se cuelgue.
Introduccin a la programacin con C 302 c UJI
303
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
lista sig
else
for aux lista aux sig aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info valor
nuevo sig
aux sig nuevo
return lista
Como puedes ver, el tratamiento de la lista vaca es muy sencillo, pero especial. Ya te
lo advertimos antes: comprueba siempre si tu funcin se comporta adecuadamente en
situaciones extremas. La lista vaca es un caso para el que siempre deberas comprobar
la validez de tu aproximacin.
La funcin puede retocarse factorizando acciones comunes a los dos bloques del
if else:
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 aux sig
aux sig nuevo
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, ello
conseguira que, en principio, los nodos que cuelgan de lista formaran una lista correcta,
pero estaramos provocando una fuga de memoria al no liberar con free el nodo de la
cabeza (ya lo vimos cuando introdujimos las listas enlazadas):
lista
3
info sig
8
info sig
2
info sig
Tampoco podemos empezar haciendo free lista para liberar el primer nodo, pues
entonces perderamos la referencia al resto de nodos. La memoria quedara as:
Introduccin a la programacin con C 303 c UJI
304
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
lista
3
info sig
8
info sig
2
info sig
Quin apuntara entonces al primer nodo de la lista?
La solucin requiere utilizar un puntero auxiliar:
E 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 solucin
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 aux
if lista
aux lista sig
free lista
lista aux
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. localizar el ltimo nodo de la lista para liberar la memoria que ocupa,
2. y hacer que el hasta ahora penltimo nodo tenga como valor de su campo sig a
.
La primera fase consistir bsicamente en esto:
E E
TipoLista borra cola TipoLista lista
struct Nodo aux
Introduccin a la programacin con C 304 c UJI
305
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
for aux lista aux sig aux aux sig
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 solucin:
no tiene sentido borrar nada de una lista vaca.
E E
TipoLista borra cola TipoLista lista
struct Nodo aux
if lista
for aux lista aux sig aux aux sig
return lista
Ahora el bucle solo se ejecuta con listas no vacas. Si partimos de esta lista:
aux
lista
3
info sig
8
info sig
2
info sig
el bucle hace que aux acabe apuntando al ltimo nodo:
aux
lista
3
info sig
8
info sig
2
info sig
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 memoria:
E E
TipoLista borra cola TipoLista lista
struct Nodo aux
if lista
for aux lista aux sig aux aux sig
free aux
Llegamos as a esta situacin:
aux
lista
3
info sig
8
info sig
Introduccin a la programacin con C 305 c UJI
306
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Fjate: slo nos falta conseguir que el nuevo ltimo nodo (el de valor igual a 8) tenga
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 modicar 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 aux lista aux sig atras aux aux aux sig
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 permiten
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 toma valores
crecientes y otra que toma valores decrecientes:
for 0 10 3
printf
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 pantalla
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
lista
3
info sig
8
info sig
2
info sig
Y en la segunda iteracin:
atras aux
lista
3
info sig
8
info sig
2
info sig
Y en la tercera:
atras aux
lista
3
info sig
8
info sig
2
info sig
Introduccin a la programacin con C 306 c UJI
307
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Ves? No importa cun larga sea la lista; el puntero atras siempre va un paso por detrs
del puntero aux. En nuestro ejemplo ya hemos llegado al nal de la lista, as que ahora
podemos liberar el nodo apuntado por aux:
atras aux
lista
3
info sig
8
info sig
Ahora podemos continuar: ya hemos borrado el ltimo nodo, pero esta vez s que sabemos
cul es el nuevo ltimo nodo.
E E
TipoLista borra cola TipoLista lista
struct Nodo aux atras
if lista
for atras aux lista aux sig atras aux aux aux sig
free aux
atras sig
Tras ejecutar la nueva sentencia, tenemos:
atras aux
lista
3
info sig
8
info sig
An no hemos acabado. La funcin borra cola trabaja correctamente con la lista vaca,
pues no hace nada en ese caso (no hay nada que borrar), pero, funciona correctamente
cuando suministramos una lista con un nico elemento? Hagamos una traza.
Tras ejecutar el bucle que busca a los elementos ltimo y penltimo, los punteros
atras y aux quedan as:
atras aux
lista
3
info sig
Ahora se libera el nodo apuntado por aux:
atras aux
lista
Y, nalmente, hacemos que atras sig sea igual . Pero, eso es imposible! 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 detectarlo?
Viendo si atras vale . Y qu hemos de hacer entonces? Hemos de hacer que lista
pase a valer , sin ms.
TipoLista borra cola TipoLista lista
Introduccin a la programacin con C 307 c UJI
308
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
struct Nodo aux atras
if lista
for atras aux lista aux sig atras aux aux aux sig
free aux
if atras
lista
else
atras 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 explcitamente
es el de la lista compuesta por un solo elemento.
Insistimos en que debes seguir una sencilla regla en el diseo de funciones con
punteros: si accedes a un campo de un puntero ptr, por ejemplo, ptr sig o ptr info,
pregntate siempre si cabe alguna posibilidad de que ptr sea ; si es as, tienes un
problema que debes solucionar.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256 Funcionan correctamente las funciones que hemos denido antes (clculo de la
longitud, insercin por cabeza y por cola y borrado de cabeza) cuando se suministra una
lista compuesta por un nico elemento?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Vamos a disear ahora una funcin que no modica la lista. Se trata de una funcin que
nos indica si un valor entero pertenece a la lista o no. El prototipo de la funcin 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: re-
correr cada uno de sus elementos y, si encontramos uno con el valor buscado, devolver
inmediatamente el valor 1; si llegamos al nal 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 sig
if aux info valor
return 1
return 0
Introduccin a la programacin con C 308 c UJI
309
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
sta ha sido fcil, no?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
257 Funciona correctamente pertenece cuando se suministra como valor de
lista, es decir, cuando se suministra una lista vaca? Y cuando se suministra una lista
con un nico elemento?
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
El problema que abordamos ahora es el diseo de una funcin que recibe una lista y un
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 valor no est
en la lista, el problema se resuelve de forma trivial: se devuelve la lista intacta 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 mar-
cada con puntos suspensivos. En esta lista hemos buscado el valor 8, as que podemos
representar la memoria as:
aux
lista
3
info sig
8
info sig
2
info sig
Nuestro objetivo ahora es, por una parte, efectuar el siguiente empalme entre nodos:
aux
lista
3
info sig
8
info sig
2
info sig
y, por otra, eliminar el nodo apuntado por aux:
aux
lista
3
info sig
2
info sig
Introduccin a la programacin con C 309 c UJI
310
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 aux lista aux atras aux aux aux sig
if aux info valor
atras sig aux sig
return lista
El puntero atras empieza apuntando a y siempre va un paso por detrs de aux.
atras aux
lista
3
info sig
8
info sig
2
info sig
Es decir, cuando aux apunta a un nodo, atras apunta al anterior. La primera iteracin
cambia el valor de los punteros y los deja en este estado:
atras aux
lista
3
info sig
8
info sig
2
info sig
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 atras
es cuando el elemento encontrado ocupa la primera posicin. Fjate en este ejemplo
en el que queremos borrar el elemento de valor 3:
atras aux
lista
3
info sig
8
info sig
2
info sig
El empalme procedente en este caso es ste:
atras aux
lista
3
info sig
8
info sig
2
info sig
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo aux atras
for atras aux lista aux atras aux aux aux sig
if aux info valor
if atras
Introduccin a la programacin con C 310 c UJI
311
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
lista aux sig
else
atras sig aux sig
return lista
Ahora podemos borrar el elemento apuntado por aux con tranquilidad y devolver la lista
modicada:
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo aux atras
for atras aux lista aux atras aux aux aux sig
if aux info valor
if atras
lista aux sig
else
atras sig aux sig
free aux
return lista
return lista
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 complicado,
aunque hay una idea que conduce a una solucin trivial: llamar tantas veces a la funcin
que hemos diseado en el apartado anterior como elementos haya originalmente en la
lista. Pero, como comprenders, se trata de una aproximacin muy ineciente: si la lista
tiene nodos, llamaremos veces a una funcin que, en el peor de los casos, recorre la
lista completa, es decir, da pasos para completarse. Es ms eciente borrar todos los
elementos de una sola pasada, en tiempo directamente proporcional a .
Supn que recibimos esta lista:
lista
3
info sig
8
info sig
2
info sig
8
info sig
1
info sig
Introduccin a la programacin con C 311 c UJI
312
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con 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 punteros auxi-
liares en un estado tal que podamos seguir iterando para encontrar y borrar el siguiente
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
lista
3
info sig
8
info sig
2
info sig
8
info sig
1
info sig
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 (siempre
ha de ir un paso por detrs de aux):
atras aux
lista
3
info sig
2
info sig
8
info sig
1
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 solucin:
TipoLista borra valor TipoLista lista int valor
struct Nodo aux atras
atras
aux lista
while aux
if aux info valor
if atras
lista aux sig
else
atras sig aux sig
free aux
if atras
aux lista
else
aux atras sig
else
atras aux
aux aux sig
return lista
Hemos optado por un bucle while en lugar de un bucle for porque necesitamos un mayor
control de los punteros auxiliares (con el for, en cada iteracin avanzamos ambos punteros
y no siempre queremos que avancen).
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 buscamos?
Si falla en alguno de estos casos, corrige la funcin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 312 c UJI
313
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con 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 permite emular a
cualquier bucle while. Aqu tienes una versin de borra valor (eliminacin de todos los
nodos con un valor dado) que usa un bucle for:
TipoLista borra valor TipoLista lista int valor
struct Nodo aux atras
for atras aux lista aux
if aux info valor
if atras
lista aux sig
else
atras sig aux sig
free aux
if atras
aux lista
else
aux atras sig
else
atras aux
aux aux sig
return lista
Observa que en el bucle for hemos dejado en blanco la zona que indica cmo modicar
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:
lista
3
info sig
8
info sig
2
info sig
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
Introduccin a la programacin con C 313 c UJI
314
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
struct Nodo aux atras nuevo
int
nuevo malloc sizeof struct Nodo
nuevo info valor
for 0 atras aux lista pos aux atras aux aux aux sig
nuevo sig aux
if atras
lista nuevo
else
atras sig nuevo
return lista
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
261 Modica 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 nodos
estn dispuestos en un orden arbitrario. Es posible mantener listas ordenadas si las
inserciones se realizan utilizando siempre una funcin que respete el orden.
La funcin que vamos a desarrollar, por ejemplo, inserta un elemento en una lista or-
denada de menor a mayor de modo que la lista resultante sea tambin una lista ordenada
de menor a mayor.
TipoLista inserta en orden TipoLista lista int valor
struct Nodo aux atras nuevo
nuevo malloc sizeof struct Nodo
nuevo 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 nal de la lista.
nuevo sig
if atras
lista nuevo
else
atras sig nuevo
return lista
Introduccin a la programacin con C 314 c UJI
315
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
262 Haz una traza de la insercin del valor 7 con inserta en orden en cada una de
estas listas:
a)
lista
1
info sig
3
info sig
8
info sig
b)
lista
12
info sig
15
info sig
23
info sig
c)
lista
1
info sig
7
info sig
9
info sig
d)
lista
e)
lista
1
info sig
f )
lista
10
info sig
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 una
lista ordenada efectuando el menor nmero posible de comparaciones y desplazamientos
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 est
ordenada o desordenada.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduccin a la programacin con C 315 c UJI
316
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
La funcin que disearemos ahora recibe dos listas y devuelve una nueva lista que resulta
de concatenar (una copia de) ambas.
TipoLista concatena listas TipoLista TipoLista
TipoLista
struct Nodo aux nuevo anterior
for aux aux aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
nuevo
anterior nuevo
for aux aux aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
nuevo
anterior nuevo
if anterior
anterior sig
return
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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 cada uno
de sus nodos. A estas alturas no debera resultarte muy difcil de entender:
TipoLista libera lista TipoLista lista
struct Nodo aux otroaux
aux lista
Introduccin a la programacin con C 316 c UJI
317
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
while aux
otroaux aux sig
free aux
aux otroaux
return
Alternativamente podramos denir la rutina de liberacin como un procedimiento:
void libera lista TipoLista lista
struct Nodo aux otroaux
aux lista
while aux
otroaux aux sig
free aux
aux otroaux
lista
De este modo nos aseguramos de que el puntero lista ja su valor a .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
271 Disea una funcin que devuelva un corte de la lista. Se proporcionarn como
parmetros dos enteros y y se devolver una lista con una copia de los nodos que
ocupan las posiciones a 1, ambas includas.
272 Disea una funcin que elimine un corte de la lista. Se proporcionarn como
parmetros dos enteros y y se eliminarn los nodos que ocupan las posiciones a
1, ambas includas.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Te ofrecemos, a modo de resumen, todas las funciones que 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 sig
typedef struct Nodo TipoLista
extern TipoLista lista vacia void
extern int es lista vacia TipoLista lista
extern TipoLista inserta por cabeza TipoLista lista int valor
extern TipoLista inserta por cola TipoLista lista int valor
extern TipoLista borra cabeza TipoLista lista
extern TipoLista borra cola TipoLista lista
extern int longitud lista TipoLista lista
extern void muestra lista TipoLista lista
Introduccin a la programacin con C 317 c UJI
318
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
extern int pertenece TipoLista lista int valor
extern TipoLista borra primera ocurrencia TipoLista lista int valor
extern TipoLista borra valor TipoLista lista int valor
extern TipoLista inserta en posicion TipoLista lista int pos int valor
extern TipoLista inserta en orden TipoLista lista int valor
extern TipoLista concatena listas TipoLista TipoLista
extern 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 aux sig
aux sig nuevo
return lista
TipoLista borra cabeza TipoLista lista
struct Nodo aux
if lista
aux lista sig
free lista
lista aux
Introduccin a la programacin con C 318 c UJI
319
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
return lista
TipoLista borra cola TipoLista lista
struct Nodo aux atras
if lista
for atras aux lista aux sig atras aux aux aux sig
free aux
if atras
lista
else
atras sig
return lista
int longitud lista TipoLista lista
struct Nodo aux
int contador 0
for aux lista aux aux aux sig
contador
return contador
void muestra lista TipoLista lista
Como la solucin al ejercicio 254, no como lo vimos en el texto.
struct Nodo aux
printf
for aux lista aux aux aux sig
printf aux info
printf
int pertenece TipoLista lista int valor
struct Nodo aux
for aux lista aux aux aux sig
if aux info valor
return 1
return 0
TipoLista borra primera ocurrencia TipoLista lista int valor
struct Nodo aux atras
for atras aux lista aux atras aux aux aux sig
if aux info valor
if atras
lista aux sig
else
atras sig aux sig
free aux
Introduccin a la programacin con C 319 c UJI
320
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
return lista
return lista
TipoLista borra valor TipoLista lista int valor
struct Nodo aux atras
atras
aux lista
while aux
if aux info valor
if atras
lista aux sig
else
atras sig aux sig
free aux
if atras
aux lista
else
aux atras sig
else
atras aux
aux aux sig
return lista
TipoLista inserta en posicion TipoLista lista int pos int valor
struct Nodo aux atras nuevo
int
nuevo malloc sizeof struct Nodo
nuevo info valor
for 0 atras aux lista pos aux atras aux aux aux sig
nuevo sig aux
if atras
lista nuevo
else
atras sig nuevo
return lista
TipoLista inserta en orden TipoLista lista int valor
struct Nodo aux atras nuevo
nuevo malloc sizeof struct Nodo
nuevo 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
Introduccin a la programacin con C 320 c UJI
321
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
if atras
lista nuevo
else
atras sig nuevo
Y como ya est insertado, acabamos.
return lista
Si llegamos aqu, es que nuevo va al nal de la lista.
nuevo sig
if atras
lista nuevo
else
atras sig nuevo
return lista
TipoLista concatena listas TipoLista TipoLista
TipoLista
struct Nodo aux nuevo anterior
for aux aux aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
nuevo
anterior nuevo
for aux aux aux aux sig
nuevo malloc sizeof struct Nodo
nuevo info aux info
if anterior
anterior sig nuevo
else
nuevo
anterior nuevo
if anterior
anterior sig
return
TipoLista libera lista TipoLista lista
struct Nodo aux otroaux
aux lista
while aux
otroaux aux sig
free aux
aux otroaux
return
Introduccin a la programacin con C 321 c UJI
322
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
include
include
int main void
TipoLista 2 3
printf
lista vacia
muestra lista
printf es lista vacia
printf
inserta por cabeza 2
inserta por cabeza 8
inserta por cabeza 3
muestra lista
printf longitud lista
printf
inserta por cola 1
inserta por cola 5
inserta por cola 10
muestra lista
printf
borra cabeza
muestra lista
printf
borra cola
muestra lista
printf pertenece 5
printf pertenece 7
printf
inserta por cola 1
muestra lista
printf
borra primera ocurrencia 1
muestra lista
printf
borra primera ocurrencia 1
muestra lista
printf
borra primera ocurrencia 1
muestra lista
printf
inserta por cola 2
inserta por cabeza 2
muestra lista
Introduccin a la programacin con C 322 c UJI
323
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
printf
borra valor 2
muestra lista
printf
borra valor 8
muestra lista
printf
inserta en posicion 0 1
muestra lista
printf
inserta en posicion 2 10
muestra lista
printf
inserta en posicion 1 3
muestra lista
printf
inserta en orden 4
inserta en orden 0
inserta en orden 20
inserta en orden 5
muestra lista
printf
2 lista vacia
2 inserta por cola 2 30
2 inserta por cola 2 40
2 inserta por cola 2 50
muestra lista 2
printf
3 concatena listas 2
muestra lista 3
printf
libera lista
2 libera lista 2
3 libera lista 3
muestra lista
muestra lista 2
muestra lista 3
return 0
Recuerda que debes compilar estos programas en al menos dos pasos:
lineal
lineal
lineal
0 0 25 0 0 12 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 37 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 13 81 0 0 0 0 02 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 ahorrar memoria.
Una lista mantiene las las que, a su vez, son listas de valores no nulos. En estas
ltimas listas, cada nodo almacena la columna del valor no nulo y el propio valor. La
matriz dispersa del ejemplo se representara as (suponiendo que las y columnas
empiezan numerndose en 1, como es habitual en matemticas):
matriz
1
sig la cols
3
2.5
columna
valor
sig
6
1.2
columna
valor
sig
3
sig la cols
2
3.7
columna
valor
sig
6
sig la cols
2
1.3
columna
valor
sig
3
8.1
columna
valor
sig
8
0.2
columna
valor
sig
Introduccin a la programacin con C 351 c UJI
352
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
El ahorro de memoria es notabilsimo: si un oat ocupa 8 bytes, hemos pasado
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 estruc-
tura es tambin notabilsima. Imagina el procedimiento que permite multiplicar
ecientemente dos matrices dispersas representadas as!
Un rbol binario de bsqueda es una estructura montada con registros enlazados,
pero no es una lista. Cada nodo tiene cero, uno o dos hijos: uno a su izquierda y
uno a su derecha. Los nodos que no tienen hijos se llaman hojas. El nodo ms alto,
del que descienden todos los dems, se llama nodo raz. Los descendientes de 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
10
der info izq
3
der info izq
15
der info izq
1
der info izq
6
der info izq
12
der info izq
23
der info izq
Una ventaja de los rboles binarios de bsqueda es la rapidez con que pueden
resolver la pregunta pertenece un valor determinado al conjunto de valores 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 respuesta la
conoce el hijo izquierdo, por lo que se le pregunta a l (recursivamente);
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 (recursivamente).
Ingenioso, no? Observa que muy pocos nodos participan en el clculo de la res-
puesta. Si deseas saber, por ejemplo, si el 6 pertenece al rbol de la 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 per-
tenencia en un rbol con nodos visitando un nmero de nodos que es, a lo sumo,
igual a 1 + log
2
. Rapidsimo. Qu costar, a cambio, insertar o borrar un nodo
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. Exis-
ten procedimientos sosticados 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 pro-
gramas 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.
Introduccin a la programacin con C 352 c UJI
353
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Me temo que s, seora dijo Alicia. No recuerdo las cosas como sola. . .
y no conservo el mismo tamao diez minutos seguidos!
iwis C/oii, Alicia en el Pas de las Maravillas.
Acabamos nuestra introduccin al lenguaje C con el mismo objeto de estudio con el
que nalizamos la presentacin del lenguaje Python: los cheros. Los cheros permiten
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 introductorios a
cheros antes de empezar.
Con Python estudiamos nicamente cheros de texto. Con C estudiaremos dos tipos de
cheros: cheros de texto y cheros binarios.
Ya conoces los cheros de texto: contienen datos legibles por una persona y puedes
generarlos o modicarlos desde tus propios programas o usando aplicaciones como los
editores de texto. Los cheros binarios, por contra, no estn pensados para facilitar su
lectura por parte de seres humanos (al menos no directamente).
Pongamos que se desea guardar un valor de tipo entero en un chero de texto, por
ejemplo, el valor 12. En el chero de texto se almacenar el dgito (codicado en
ASCII como el valor 49) y el dgito (codicado en ASCII como el valor 50), es decir, dos
datos de tipo char. A la hora de leer el dato, podremos leerlo en cualquier variable de tipo
entero con capacidad suciente para almacenar ese valor (un char, un unsigned char, un
int, un unsigned int, etc.). Esto es as porque la lectura de ese dato pasa por un proceso
de interpretacin relativamente sosticado: cuando se lee el carcter , se memoriza
el valor 1; y cuando se lee el carcter , se multiplica por 10 el valor memorizado y se
le suma el valor 2. As se llega al valor 12, que es lo que se almacena en la variable en
cuestin. Observa que, codicado como texto, 12 ocupa dos bytes, pero que si se almacena
en una variable de tipo char ocupa 1 y en una variable de tipo int ocupa 4.
Un problema de los cheros de texto es la necesidad de usar marcas de separacin
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 chero
contendra la siguiente secuencia de caracteres:
353
354
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
1 2 1 0 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 corriente
que se trate de espacios en blanco, tabuladores o saltos de lnea. El valor 12 seguido del
valor 100 podra representarse, pues, con cualquiera de estas secuencias de caracteres:
1 2 1 0 0
1 2 \t 1 0 0
1 2 \n 1 0 0
Usar caracteres separadores es fuente, naturalmente, de un coste adicional: un mayor
tamao de los cheros.
Cuando los separadores son espacios en blanco, es frecuente permitir libertad en
cuanto a su nmero:
1 2 \n 1 0 0 \n
Las herramientas con las que leemos los datos de cheros de texto saben lidiar con las
complicaciones que introducen estos separadores blancos repetidos.
Los cheros de texto cuentan con la ventaja de que se pueden inspeccionar con ayuda
de un editor de texto y permiten as, por lo general, deducir el tipo de los diferentes datos
que lo componen, pues stos resultan legibles.
Los cheros binarios requieren una mayor precisin en la determinacin de la codicacin
de la informacin. Si almacenamos el valor 12 en un chero binario, hemos de decidir si
queremos almacenarlo como carcter con o sin signo, como entero con o sin signo, etc.
La decisin adoptada determinar la ocupacin de la informacin (uno o cuatro bytes) y
su codicacin (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 de tipo
unsigned char o el valor 1 si consideramos que codica un dato de tipo char.
1
1
Un chero de texto no presentara esta ambigedad: el nmero se habra escrito como 1 o como 255.
S que presentara, sin embargo, un punto de eleccin reservado al programador: aunque 1 lleva signo y
por tanto se almacenar en una variable de algn tipo con signo, queremos almacenarlo en una variable de
tipo char, una variable de tipo int o, por qu no, en una variable de tipo oat?
Introduccin a la programacin con C 354 c UJI
355
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Como puedes ver, la secuencia de bits que escribimos en el chero es exactamente la
misma que hay almacenada en la memoria, usando la mismsima codicacin binaria. De
ah el nombre de cheros binarios.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
310 Qu ocupa en un chero de texto cada uno de estos datos?
a) 1
b) 0
c) 12
d) 15
e) 128
f ) 32767
g) 32768
h) 2147483647
i) 2147483648
Y cunto ocupa cada uno de ellos si los almacenamos en un chero 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 chero 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 chero 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 signicado denido.
Los cheros binarios no slo pueden almacenar escalares. Puedes almacenar tambin
registros y vectores pues, a n de cuentas, no son ms que patrones de bits de tamao
conocido. Lo nico que no debe almacenarse en cheros 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 cheros binarios son ms compactos que los cheros de texto,
pues cada valor ocupa lo mismo que ocupara en memoria. La lectura (y escritura) de
los datos de cheros 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 cheros de texto se manipulan en C siguiendo el mismo protocolo que seguamos
en Python:
Introduccin a la programacin con C 355 c UJI
356
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Portabilidad de cheros
Los cheros binarios presentan algunos problemas de portabilidad, pues no todos los
ordenadores almacenan en memoria los valores numricos de la misma forma: los cheros
binarios escritos en un ordenador big-endian no son directamente legibles en un
ordenador little-endian.
Los cheros de texto son, en principio, ms portables, pues la tabla ASCII es un
estndar ampliamente aceptado para el intercambio de cheros de texto. No obstante,
la tabla ASCII es un cdigo de 7 bits que slo da cobertura a los smbolos propios de
la escritura del ingls y algunos caracteres especiales. Los caracteres acentuados, por
ejemplo, estn excluidos. En los ltimos aos se ha intentado implantar una familia de
estndares que den cobertura a estos y otros caracteres. Como 8 bits resultan insucien-
tes para codicar todos los caracteres usados en la escritura de cualquier lenguaje, hay
diferentes subconjuntos para cada una de las diferentes comunidades culturales. Las len-
guas romnicas occidentales usan el estndar IsoLatin-1 (o ISO-8859-1), recientemente
ampliado con el smbolo del euro para dar lugar al IsoLatin-15 (o ISO-8859-15). Los
problemas de portabilidad surgen cuando interpretamos un chero de texto codicado
con IsoLatin-1 como si estuviera codicado con otro estndar: no veremos ms que un
galimatas de smbolos extraos all donde se usan caracteres no ASCII.
1. Se abre el chero en modo lectura, escritura, adicin, o cualquier otro modo vlido.
2. Se trabaja con l leyendo o escribiendo datos, segn el modo de apertura escogido.
Al abrir un chero se dispone un cabezal de lectura o escritura en un punto
denido del chero (el principio o el nal). Cada accin de lectura o escritura
desplaza el cabezal de izquierda a derecha, es decir, de principio a nal del chero.
3. Se cierra el chero.
Bueno, lo cierto es que, como siempre en C, hay un paso adicional y previo a estos
tres: la declaracin de una variable de tipo chero. La cabecera incluye la
denicin de un tipo de datos llamado FILE y declara los prototipos de las funciones de
manipulacin de cheros. Nuestra variable de tipo chero 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 chero. Recibe la ruta de un chero (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 cheros de texto con los que trabajaremos son stos:
(lectura): El primer carcter ledo es el primero del chero.
(escritura): Trunca el chero a longitud 0. Si el chero no existe, se crea.
(adicin): Es un modo de escritura que preserva el contenido original del
chero. Los caracteres escritos se aaden al nal del chero.
Si el chero no puede abrirse por cualquier razn, fopen devuelve el valor .
(Observa que los modos se indican con cadenas, no con caracteres: debes usar
comillas dobles.)
fclose: cierra un chero. Recibe el FILE devuelto por una llamada previa a fopen.
int fclose FILE chero
Introduccin a la programacin con C 356 c UJI
357
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Modos de apertura para lectura y escritura simultnea
Los modos , y no son los nicos vlidos para los cheros de texto. Puedes
usar, adems, stos otros: , y . Todos ellos abren los cheros en modo
de lectura y escritura a la vez. Hay, no obstante, matices que los diferencian:
: No se borra el contenido del chero, que debe existir previamente. El
cabezal de lectura/escritura se sita al principio del chero.
: Si el chero no existe, se crea, y si existe, se trunca el contenido a longitud
cero. El cabezal de lectura/escritura se sita al principio del chero.
: Si el chero no existe, se crea. El cabezal de lectura/escritura se sita
al nal del chero.
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 chero de texto 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 chero. El valor 0 indica xito y el valor (predenido en )
indica error. Ms adelante indicaremos cmo obtener informacin adicional acerca
del error detectado.
Cada apertura de un chero con fopen debe ir acompaada de una llamada a fclose
una vez se ha terminado de trabajar con el chero.
fscanf : lee de un chero. Recibe un chero abierto con fopen (un FILE ), una
cadena de formato (usando las marcas de formato que ya conoces por scanf ) y las
direcciones de memoria en las que debe depositar los valores ledos. La funcin
devuelve el nmero de elementos efectivamente ledos (valor que puedes usar para
comprobar si la lectura se complet con xito).
int fscanf FILE chero char formato direcciones
fprintf : escribe en un chero. Recibe un chero abierto con fopen (un FILE ), una
cadena de formato (donde puedes usar las marcas de formato que aprendiste a
usar con printf ) y los valores que deseamos escribir. La funcin devuelve el nmero
de caracteres efectivamente escritos (valor que puedes usar para comprobar si se
escribieron correctamente los datos).
int fprintf FILE chero char formato valores
feof : devuelve 1 si estamos al nal del chero y 0 en caso contrario. El nombre de
la funcin es abreviatura de end of le (en espaol, n de chero). Ojo! Slo
tiene sentido consultar si se est o no al nal de chero tras efectuar una lectura
de datos. (Este detalle complicar un poco las cosas.)
int feof FILE chero
Como puedes ver no va a resultar muy difcil trabajar con cheros de texto en C. A
n de cuentas, las funciones de escritura y lectura son bsicamente idnticas a printf y
scanf , y ya hemos aprendido a usarlas. La nica novedad destacable es la nueva forma
de detectar si hemos llegado al nal de un chero o no: ya no se devuelve la cadena
Introduccin a la programacin con C 357 c UJI
358
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
vaca como consecuencia de una lectura al nal del chero, 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 cheros de texto en C. Vamos a
generar los 1000 primeros nmeros primos y a guardarlos en un chero de texto. Cada
nmero se escribir en una lnea.
include
int es primo int
int primo
primo 1
for 2 2
if 0
primo 0
break
return primo
int main void
FILE fp
int
fp fopen
1
0
while 1000
if es primo
fprintf fp
fclose fp
return 0
Hemos llamado a la variable de chero fp por ser abreviatura del trmino le pointer
(puntero a chero). Es frecuente utilizar ese nombre para las variables de tipo FILE .
Una vez compilado y ejecutado el programa obtenemos un chero de
texto llamado del que te mostramos sus primeras y ltimas lneas (puedes
comprobar la correccin del programa abriendo el chero con un editor de
texto):
Introduccin a la programacin con C 358 c UJI
359
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Aunque en pantalla lo vemos como una secuencia de lneas, no es ms que una secuencia
de caracteres:
1 \n 2 \n 3 \n 5 \n
. . .
7 9 0 1 \n 7 9 0 7 \n
Diseemos ahora un programa que lea el chero generado por el pro-
grama anterior y muestre por pantalla su contenido:
include
int main void
FILE fp
int
fp fopen
fscanf fp
while feof fp
printf
fscanf fp
fclose fp
return 0
Observa que la llamada a fscanf se encuentra en un bucle que se lee as mientras no
se haya acabado el chero. . . , pues feof averigua si hemos llegado al nal del chero.
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 chero. Si no te gusta
la aparicin de dos sentencias fscanf , puedes optar por esta alternativa:
include
int main void
FILE fp
int
fp fopen
while 1
fscanf fp
Introduccin a la programacin con C 359 c UJI
360
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
if feof fp break
printf
fclose fp
return 0
Y si deseas evitar el uso de break, considera esta otra:
include
int main void
FILE fp
int
fp fopen
do
fscanf fp
if feof fp
printf
while feof fp
fclose fp
return 0
Y si el chero no existe?
Al abrir un chero puede que detectes un error: fopen devuelve la direccin . Hay
varias razones, pero una que te ocurrir al probar algunos de los programas del texto es
que el chero que se pretende leer no existe. Una solucin puede consistir en crearlo
en ese mismo instante:
fopen ruta
if
fopen ruta
fclose
fopen ruta
Si el problema era la inexistencia del chero, 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 chero?, tienes permiso
para escribir en el directorio en el que reside o debe residir el chero? Ms adelante
prestaremos atencin a esta cuestin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
312 Disea un programa que aada al chero los 100 siguientes nmeros
primos. El programa leer el contenido actual del chero para averiguar cul es el ltimo
primo del chero. A continuacin, abrir el chero en modo adicin ( ) y aadir 100
nuevos primos. Si ejecutsemos una vez y, a continuacin, dos veces el
nuevo programa, el chero acabara conteniendo los 1200 primeros primos.
Introduccin a la programacin con C 360 c UJI
361
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
313 Disea un programa que lea de teclado una frase y escriba un chero 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 chero de texto
llamado en el que cada lnea contenga un carcter de la frase.
315 Modica el programa miniGalaxis para que gestione una lista de records. Un
chero 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 cheros: 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 detectar
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 palabra
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, encontrars
uno en ingls en o . Puedes averiguar el
nmero de palabras que contiene con el comando de Unix.)
317 Modica 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 chero que
contiene el diccionario: una para contar el nmero de palabras y saber as cunta memoria
es necesaria y otra para cargar la lista de palabras en un vector dinmico. Naturalmente,
antes de la segunda lectura debers haber reservado la memoria necesaria.
Una alternativa a leer dos veces el chero consiste en usar realloc juiciosamente:
reserva inicialmente espacio para, digamos, 1000 palabras; si el diccionario contiene un
nmero de palabras mayor que el que cabe en el espacio de memoria reservada, duplica
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 cheros de texto:
char fgets char cadena int max tam FILE chero
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 depositar
el resultado de la lectura. El segundo parmetro, un entero, es una medida de seguridad:
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, nalmente, el chero
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 de 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.
Introduccin a la programacin con C 361 c UJI
362
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
void quita n de linea char linea
int
for 0 linea
if linea
linea
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 efectuar la
lectura. El valor de retorno es til, nicamente, para hacer detectar posibles errores tras
llamar a la funcin.
Hay ms funciones de la familia get. La funcin fgetc, por ejemplo, lee un carcter:
int fgetc FILE chero
No te equivoques: devuelve un valor de tipo int, pero es el valor ASCII de un carcter.
Puedes asignar ese valor a un unsigned char, excepto cuando vale (de end of le),
que es una constante (cuyo valor es 1) que indica que no se pudo leer el carcter
requerido porque llegamos al nal del chero.
Las funciones fgets y fgetc se complementan con fputs y fputc, que en lugar de leer
una cadena o un carcter, escriben una cadena o un carcter en un chero abierto para
escritura o adicin. He aqu sus prototipos:
int fputs char cadena FILE chero
int fputc int caracter FILE chero
Al escribir una cadena con fputs, el terminador no se escribe en el chero. Pero no
te preocupes: fgets lo sabe y lo introduce automticamente en el vector de caracteres
al leer del chero.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
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
dene 100
int main void
FILE
char 1
char aux
fopen
fputs
fputs
fclose
fopen
aux fgets
printf aux
aux fgets
printf aux
fclose
Introduccin a la programacin con C 362 c UJI
363
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
return 0
Primera cuestin: Cuntos bytes ocupa el chero 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 colecciones
de datos en cheros de texto.
Una agenda
Vamos a desarrollar un pequeo ejemplo centrado en las rutinas de entrada/salida para
la gestin de una agenda montada con una lista simplemente enlazada. En la agenda,
que cargaremos de un chero de texto, tenemos el nombre, la direccin y el telfono de
varias personas. Cada entrada en la agenda se representar con tres lneas del chero
de texto. He aqu un ejemplo de chero con este formato:
Nuestro programa podr leer en memoria los datos de un chero como ste y tambin
escribirlos en chero desde memoria.
Las estructuras de datos que manejaremos en memoria se denen as:
struct Entrada
char nombre
char direccion
char telefono
struct NodoAgenda
struct Entrada datos
struct NodoAgenda sig
typedef struct NodoAgenda TipoAgenda
Introduccin a la programacin con C 363 c UJI
364
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
Al nal del apartado presentamos el programa completo. Centrmonos ahora en las
funciones de escritura y lectura del chero. La rutina de escritura de datos en un che-
ro recibir la estructura y el nombre del chero 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 chero
struct NodoAgenda aux
FILE fp
fp fopen nombre chero
for aux agenda aux aux aux sig
fprintf fp aux datos nombre
aux datos direccion
aux datos telefono
fclose fp
La lectura del chero ser sencilla:
TipoAgenda lee agenda char nombre chero
TipoAgenda agenda
struct Entrada entrada leida
FILE fp
char nombre 1 direccion 1 telefono 1
int longitud
agenda crea agenda
fp fopen nombre chero
while 1
fgets nombre fp
if feof fp break Si se acab el chero, acabar la lectura.
quita n de linea nombre
fgets direccion fp
quita n de linea direccion
fgets telefono fp
quita n 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
dene 200
enum Ver 1 Alta Buscar Salir
Introduccin a la programacin con C 364 c UJI
365
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
struct Entrada
char nombre
char direccion
char telefono
struct NodoAgenda
struct Entrada datos
struct NodoAgenda sig
typedef struct NodoAgenda TipoAgenda
void quita n de linea char linea
int
for 0 linea
if linea
linea
break
void muestra entrada struct NodoAgenda
Podramos haber pasado por valor, pero resulta ms eciente (y no mucho ms
incmodo) hacerlo por referencia: pasamos as slo 4 bytes en lugar de 12.
printf datos nombre
printf datos direccion
printf datos telefono
void libera entrada struct NodoAgenda
int
free datos nombre
free datos direccion
free datos telefono
free
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
Si llegamos aqu, es porque no tenamos registrada a esa persona.
malloc sizeof struct NodoAgenda
Introduccin a la programacin con C 365 c UJI
366
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
datos nombre malloc strlen nombre 1 sizeof char
strcpy datos nombre nombre
datos direccion malloc strlen direccion 1 sizeof char
strcpy datos direccion direccion
datos telefono malloc strlen telefono 1 sizeof char
strcpy datos telefono telefono
sig agenda
agenda
return agenda
void muestra agenda TipoAgenda agenda
struct NodoAgenda aux
for aux agenda aux aux aux sig
muestra entrada aux
struct NodoAgenda buscar entrada por nombre TipoAgenda agenda char nombre
struct NodoAgenda aux
for aux agenda aux aux aux sig
if strcmp aux datos nombre nombre 0
return aux
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 chero
struct NodoAgenda aux
FILE fp
fp fopen nombre chero
for aux agenda aux aux aux sig
fprintf fp aux datos nombre
aux datos direccion
aux datos telefono
fclose fp
TipoAgenda lee agenda char nombre chero
TipoAgenda agenda
struct Entrada entrada leida
Introduccin a la programacin con C 366 c UJI
367
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
FILE fp
char nombre 1 direccion 1 telefono 1
int longitud
agenda crea agenda
fp fopen nombre chero
while 1
fgets nombre fp
if feof fp break Si se acab el chero, acabar la lectura.
quita n de linea nombre
fgets direccion fp
quita n de linea direccion
fgets telefono fp
quita n 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
Introduccin a la programacin con C 367 c UJI
368
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
printf gets direccion
printf gets telefono
miagenda 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 chero para el programa de gestin de una coleccin de discos
Acabamos esta seccin dedicada a los cheros de texto con una aplicacin prctica. Vamos
a aadir funcionalidad al programa desarrollado en el apartado 4.11: el programa cargar
la base de datos tan pronto inicie su ejecucin leyendo un chero de texto y la guardar
en el mismo chero, recogiendo los cambios efectuados, al nal.
En primer lugar, discutamos brevemente acerca del formato del chero de texto. Po-
demos 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 indiquen
qu tipo de informacin mantiene:
Introduccin a la programacin con C 368 c UJI
369
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 denir libremente el formato,
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 n de linea char linea
int
for 0 linea
if linea
linea
break
Introduccin a la programacin con C 369 c UJI
370
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
TipoColeccion carga coleccion char nombre chero
FILE
char titulo disco 1 titulo cancion 1 interprete 1
char linea 1
int anyo
int numcanciones
int
TipoColeccion coleccion
TipoListaCanciones lista canciones
coleccion crea coleccion
fopen nombre chero
while 1
fgets titulo disco
if feof
break
quita n de linea titulo disco
fgets interprete
quita n de linea interprete
fgets linea sscanf linea anyo
fgets linea sscanf linea numcanciones
lista canciones crea lista canciones
for 0 numcanciones
fgets titulo cancion
quita n de linea titulo cancion
lista canciones anyade cancion lista canciones titulo cancion
coleccion anyade disco coleccion titulo disco interprete anyo lista canciones
fclose
return coleccion
Tan slo cabe resear dos cuestiones:
La deteccin del nal de chero se ha de hacer tras una lectura infructuosa, 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 que hay
que eliminarlo explcitamente.
Al guardar el chero hemos de asegurarnos de que escribimos la informacin en el
mismo formato:
void guarda coleccion TipoColeccion coleccion char nombre chero
struct Disco disco
struct Cancion cancion
int numcanciones
FILE
fopen nombre chero
for disco coleccion disco disco disco sig
fprintf disco titulo
fprintf disco interprete
fprintf disco anyo
Introduccin a la programacin con C 370 c UJI
371
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
numcanciones 0
for cancion disco canciones cancion cancion cancion sig
numcanciones
fprintf numcanciones
for cancion disco canciones cancion cancion cancion sig
fprintf cancion titulo
fclose
Observa que hemos recorrido dos veces la lista de canciones de cada disco: una para
saber cuntas canciones contiene (y as poder escribir en el chero esa cantidad) y otra
para escribir los ttulos de las canciones.
Aqu tienes las modicaciones hechas al programa principal:
include
include
include
include
int main void
int opcion
TipoColeccion coleccion
char titulo disco 1 titulo cancion 1 interprete 1
char linea 1
int anyo
struct Disco undisco
TipoListaCanciones lista canciones
coleccion carga coleccion
do
printf
printf
printf
printf
printf
printf
printf
printf
printf gets linea sscanf linea opcion
guarda coleccion coleccion
coleccion libera coleccion coleccion
return 0
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
319 La gestin de cheros mediante su carga previa en memoria puede resultar
problemtica al trabajar con grandes volmenes de informacin. Modica el programa de
Introduccin a la programacin con C 371 c UJI
372
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
la agenda para que no cargue los datos en memoria. Todas las operaciones (aadir datos
y consultar) se efectuarn gestionando directamente cheros.
320 Modica el programa propuesto en el ejercicio anterior para que sea posible
borrar entradas de la agenda. (Una posible solucin pasa por trabajar con dos cheros,
uno original y uno para copias, de modo que borrar una informacin sea equivalente a
no escribirla en la copia.)
321 Modica el programa de la agenda para que se pueda mantener ms de un
telfono asociado a una persona. El formato del chero 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 cuyo
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 chero.
Ten en cuenta que no se puede asociar ms de una direccin a una persona (y si eso
ocurre en el chero, debes noticar 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 chero. He aqu un ejemplo de chero con el nuevo formato:
322 En un chero matriz mat almacenamos los datos de una matriz de enteros con
el siguiente formato:
La primera lnea contiene el nmero de las 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 las
tiene la matriz.
Este ejemplo dene una matriz de 3 4 con el formato indicado:
Escribe un programa que lea matriz mat efectuando las reservas de memoria dinmica
que corresponda y muestre por pantalla, una vez cerrado el chero, el contenido de la
matriz.
Introduccin a la programacin con C 372 c UJI
373
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
323 Modica el programa del ejercicio anterior para que, si hay menos lneas con
valores de las que las declaradas en la primera lnea, se rellene el restante nmero de
las con valores nulos.
Aqu tienes un ejemplo de chero con menos las que las declaradas:
324 Disea un programa que facilite la gestin de una biblioteca. El programa per-
mitir prestar libros. De cada libro se registrar al menos el ttulo y el autor. En cualquier
instante se podr volcar el estado de la biblioteca a un chero y cargarlo de l.
Conviene que la biblioteca sea una lista de nodos, cada uno de los cuales representa
un libro. Uno de los campos del libro podra ser una cadena con el nombre del prestatario.
Si dicho nombre es la cadena vaca, se entender que el libro est disponible.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Permisos Unix
Los cheros Unix llevan asociados unos permisos con los que es posible determinar
qu usuarios pueden efectuar qu acciones sobre cada chero. Las acciones son: leer,
escribir y ejecutar (esta ltima limitada a cheros ejecutables, es decir, resultantes de
una compilacin o que contienen cdigo fuente de un lenguaje interpretado y siguen
cierto convenio). Se puede jar cada permiso para el usuario propietario del chero,
para los usuarios de su mismo grupo o para todos los usuarios del sistema.
Cuando ejecutamos el comando con la opcin , podemos ver los permisos
codicados con las letras y el carcter :
El chero 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 slo lectura para
el resto de usuarios (caracteres 8 a 10). El chero puede ser ledo, modicado y
ejecutado por el usuario. Los usuarios del mismo grupo pueden leerlo y ejecutarlo, pero
no modicar su contenido. El resto de usuarios no puede acceder al chero.
El comando Unix permite modicar los permisos de un chero. Una forma
tradicional de hacerlo es con un nmero octal que codica los permisos. Aqu tienes un
ejemplo de uso:
El carcter indica a Unix que lea del chero en lugar de leer del 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 chero. Fjate:
Ahora el programa se ejecuta sin mostrar texto alguno por pantalla y el chero solo-
pares txt acaba conteniendo lo que debiera haberse mostrado por pantalla.
Para redirigir la salida de errores, puedes usar el par de caracteres seguido del
nombre del chero en el que se escribirn los mensajes de error.
La capacidad de redirigir los dispositivos de entrada, salida y errores tiene innidad
de aplicaciones. Una evidente es automatizar la fase de pruebas de un programa durante
su desarrollo. En lugar de escribir cada vez todos los datos que solicita un programa
para ver si efecta correctamente los clculos, puedes preparar un chero con los datos
de entrada y utilizar redireccin para que el programa los lea automticamente.
Introduccin a la programacin con C 376 c UJI
377
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 sucientemente larga. Algunos
compiladores generan una advertencia cuando detectan el uso de gets. Cmo leer,
pues, una lnea de forma segura? Una posibilidad consiste en escribir nuestra 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 max lon
int nc 0
max lon Se reserva un carcter para el
while fgetc stdin
if
break
if nc max lon
linea nc
if nc 0
return
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 modicar para contener la cadena leda. La cadena ms larga
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 gets no hace.
La primera versin, no obstante, sigue teniendo inters, pues te muestra un esque-
leto de funcin til para un control detallado de la lectura por teclado. Inspirndote 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 cheros y a modicar su contenido. No sabemos, sin embargo,
cmo eliminar un chero del sistema de cheros ni cmo rebautizarlo. Hay dos funciones
de la librera estndar de C (accesibles al incluir ) que permiten efectuar estas
dos operaciones:
remove: elimina el chero cuya ruta se proporciona.
int remove char ruta
La funcin devuelve 0 si se consigui eliminar el chero y otro valor si se cometi
algn error. Ojo! No confundas borrar un chero con borrar el contenido de un
Introduccin a la programacin con C 377 c UJI
378
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
La consulta de teclado
La funcin getc (o, para el caso, fgetc actuando sobre stdin) bloquea la ejecucin del
programa hasta que el usuario teclea algo y pulsa la tecla de retorno. Muchos progra-
madores se preguntan cmo puedo saber si una tecla est pulsada o no sin quedar
bloqueado? Ciertas aplicaciones, como los videojuegos, necesitan efectuar consultas al
estado del teclado no bloqueantes. Malas noticias: no es un asunto del lenguaje C,
sino de bibliotecas especcas. El C estndar nada dice acerca de cmo efectuar esa
operacin.
En Unix, la biblioteca curses, por ejemplo, permite manipular los terminales y acceder
de diferentes modos al teclado. Pero no es una biblioteca fcil de (aprender a) usar. Y,
adems, presenta problemas de portabilidad, pues no necesariamente est disponible en
todos los sistemas operativos.
Cosa parecida podemos decir de otras cuestiones: sonido, grcos tridimensionales,
interfaces grcas de usuario, etc. C, en tanto que lenguaje de programacin estandari-
zado, no ofrece soporte. Eso s: hay bibliotecas para innidad de campos de aplicacin.
Tendrs que encontrar la que mejor se ajusta a tus necesidades y. . . estudiar!
chero. La funcin remove elimina completamente el chero. Abrir un chero en
modo escritura y cerrarlo inmediatamente elimina su contenido, pero el chero
sigue existiendo (ocupando, eso s, 0 bytes).
rename: cambia el nombre de un chero.
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 cheros binarios obliga a trabajar con el mismo protocolo bsico:
1. abrir el chero en el modo adecuado,
2. leer y/o escribir informacin,
3. y cerrar el chero.
La funcin de apertura de un chero binario es la misma que hemos usado para los
cheros de texto: fopen. Lo que cambia es el modo de apertura: debe contener la letra .
Los modos de apertura bsicos
3
para cheros binarios son, pues:
(lectura): El primer byte ledo es el primero del chero.
(escritura): Trunca el chero a longitud 0. Si el chero no existe, se crea.
(adicin): Es un modo de escritura que preserva el contenido original del
chero. Los datos escritos se aaden al nal del chero.
Si el chero no puede abrirse por cualquier razn, fopen devuelve el valor .
La funcin de cierre del chero es fclose.
Las funciones de lectura y escritura s son diferentes:
3
Ms adelante te presentamos tres modos de apertura adicionales.
Introduccin a la programacin con C 378 c UJI
379
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
fread: recibe una direccin de memoria, el nmero de bytes que ocupa un dato, el
nmero de datos a leer y un chero. He aqu su prototipo
4
:
int fread void direccion int tam int numdatos FILE chero
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 nal del chero y no se ha podido efectuar la lectura completa).
fwrite: recibe una direccin de memoria, el nmero de bytes que ocupa un dato, el
nmero de datos a escribir y un chero. Este es su prototipo:
int fwrite void direccion int tam int numdatos FILE chero
Escribe en el chero los tam por numdatos bytes existentes desde direccion 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 chero:
include
int main void
FILE fp
int
fp fopen
for 0 10
fwrite sizeof int 1 fp
fclose fp
return 0
Analicemos la llamada a fwrite. Fjate: pasamos la direccin de memoria en la que empieza
un entero (con ) 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 multiplicar 1
por 4) que empiezan en la direccin , es decir, se va a guardar en el chero una copia
exacta del contenido de .
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 10
for 0 10
fp fopen
fwrite sizeof int 10 fp
4
Bueno, casi. El prototipo no usa el tipo int, sino size_t, que est denido como unsigned int. Preferimos
presentarte una versin modicada del prototipo para evitar introducir nuevos conceptos.
Introduccin a la programacin con C 379 c UJI
380
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
fclose fp
return 0
Ahora estamos pasando la direccin en la que empieza un vector ( 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 chero los 40
bytes de memoria que empiezan donde empieza . Resultado: todo el vector se almacena
en disco con una sola operacin de escritura. Cmodo, no?
Ya te dijimos que la informacin de todo chero 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 chero:
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 chero 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 visibles,
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 tabulador).
Hay una herramienta Unix que te permite inspeccionar un chero binario: (abre-
viatura de octal dump, es decir, volcado octal).
return 0
Ejecutemos el programa e introduzcamos los valores 20, 3.0 y 4 pulsando el retorno de
carro tras cada uno de ellos.
396
397
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Perfecto. Para ver qu ha ocurrido paso a paso vamos a representar el texto que
escribe el usuario durante la ejecucin como una secuencia de teclas. En este grco se
muestra qu ocurre durante la ejecucin del primer scanf (lnea 8), momento en el que las
tres variables estn sin inicializar y el usuario acaba de pulsar las teclas , y retorno
de carro:
2 0 \n
a
b
c
El carcter a la derecha de la echa es el siguiente carcter que va a ser consumido.
La ejecucin del primer scanf consume los caracteres y , pues ambos son
vlidos para formar un entero. La funcin detecta el blanco (salto de lnea) que sigue al
carcter y se detiene. Interpreta entonces los caracteres que ha ledo como el valor
entero 20 y lo almacena en la direccin de memoria que se la suministrado ( ):
2 0 \n
&a
20 a
b
c
En la gura hemos representado los caracteres consumidos en color gris. Fjate en que
el salto de lnea an no ha sido consumido.
La ejecucin del segundo scanf , el que lee el contenido de , empieza descartando
los blancos iniciales, es decir, el salto de lnea:
2 0 \n
20 a
b
c
Como no hay ms caracteres que procesar, scanf queda a la espera de que el usuario
teclee algo con lo que pueda formar un otante y pulse retorno de carro. Cuando el
usaurio teclea el 3.0 seguido del salto de lnea, pasamos a esta nueva situacin:
2 0 \n 3 . 0 \n
20 a
b
c
Ahora, scanf reanuda su ejecucin y consume el , el y el . Como detecta que
lo que sigue no es vlido para formar un otante, se detiene, interpreta los caracteres
ledos como el valor otante 3.0 y lo almacena en la direccin de :
Introduccin a la programacin con C 397 c UJI
398
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
2 0 \n 3 . 0 \n
20 a
&b
3.0 b
c
Finalmente, el tercer scanf entra en ejecucin y empieza por saltarse el salto de lnea.
2 0 \n 3 . 0 \n
20 a
3.0 b
c
Acto seguido se detiene, pues no es necesario que el usuario introduzca nuevo texto que
procesar. Entonces el usuario escribe el y pulsa retorno:
2 0 \n 3 . 0 \n 4 \n
20 a
3.0 b
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 :
2 0 \n 3 . 0 \n 4 \n
20 a
3.0 b
&c
4 c
Como puedes apreciar, el ltimo salto de lnea no llega a ser consumido, pero eso importa
poco, pues el programa naliza correctamente su ejecucin.
Vamos a estudiar ahora el porqu de un efecto curioso. Imagina que, cuando el pro-
grama pide al usuario el primer valor entero, ste introduce tanto dicho valor como los
dos siguientes, separando los tres valores con espacios en blanco. He aqu el resultado
en pantalla:
El programa ha ledo correctamente los tres valores, sin esperar a que el usuario
introduzca tres lneas con datos: cuando tena que detenerse para leer el valor de , no
lo ha hecho, pues saba que ese valor era 3.0; y tampoco se ha detenido al leer el valor
de , ya que de algn modo saba que era 4. Veamos paso a paso lo que ha sucedido,
pues la explicacin es bien sencilla.
Durante la ejecucin del primer scanf , el usuario ha escrito el siguiente texto:
2 0 3 . 0 4 \n
a
b
c
Como su objetivo es leer un entero, ha empezado a consumir caracteres. El y el
le son ltiles, as que los ha consumido. Entonces se ha detenido frente al espacio en
blanco. Los caracteres ledos se interpretan como el valor entero 20 y se almacenan en
:
Introduccin a la programacin con C 398 c UJI
399
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
2 0 3 . 0 4 \n
&a
20 a
b
c
La ejecucin del siguiente scanf no ha detenido la ejecucin del programa, pues an haba
caracteres pendientes de procesar en la entrada. Como siempre, scanf se ha saltado el
primer blanco y ha ido encontrando caracteres vlidos para ir formando un valor del
tipo que se le indica (en este caso, un otante). La funcin scanf ha dejado de consumir
caracteres al encontrar un nuevo blanco, se ha detenido y ha almacenado en el valor
otante . He aqu el nuevo estado:
2 0 3 . 0 4 \n
20 a
&b
3.0 b
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 porque
el carcter que le sigue es un blanco. El valor ledo (el entero 4) se almacena en c:
2 0 3 . 0 4 \n
20 a
3.0 b
&c
4 c
Tras almacenar en el entero , el estado es ste:
2 0 3 . 0 4 \n
20 a
3.0 b
4 c
Cuando observes un comportamiento inesperado de scanf , haz un anlisis de lo su-
cedido como el que te hemos presentado aqu y vers que todo tiene explicacin.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
338 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos
de entrada en la ejecucin del programa?
2 0 3 . 1 2 3 4 5 5 \n
339 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos
de entrada en la ejecucin del programa?
2 0 3 . 1 2 3 \n 4 5 5 \n
340 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos
de entrada en la ejecucin del programa?
2 0 2 4 5 x \n
Introduccin a la programacin con C 399 c UJI
400
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
341 Qu pasa si el usuario escribe la siguiente secuencia de caracteres como datos
de entrada en la ejecucin del programa?
6 x 2 \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 posicio-
nes de memoria consecutivas a partir de la que se suministra como argumento. Se
entiende por carcter vlido cualquier carcter no blanco (ni tabulador, ni espacio
en blanco, ni salto de lnea. . . ).
Se aade al nal un terminador de cadena.
Un ejemplo ayudar a entender qu ocurre ante ciertas entradas:
include
dene 10
int main void
char 1 1
printf scanf
printf scanf
printf
return 0
Si ejecutas el programa y escribes una primera cadena sin blancos, pulsas el retorno de
carro, escribes otra cadena sin blancos y vuelves a pulsar el retorno, la lectura se efecta
como cabe esperar:
Estudiemos paso a paso lo ocurrido. Ante el primer scanf , el usuario ha escrito lo
siguiente:
u n o \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 cosas:
Introduccin a la programacin con C 400 c UJI
401
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
u n o \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
(Fjate en que scanf termina correctamente la cadena almacenada en .) Acto seguido 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 n o \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
Como no hay ms caracteres, scanf ha detenido la ejecucin a la espera de que el usuario
teclee algo. Entonces el usuario ha escrito la palabra y ha pulsado retorno de carro:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
Entonces scanf ha procedido a consumir los tres primeros caracteres:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
b d
0
o
1
s
2
\0
3 4 5 6 7 8 9
Fjate en que scanf introduce automticamente el terminador pertinente al nal de
la cadena leda. El segundo scanf nos conduce a esta nueva situacin:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
b d
0
o
1
s
2
\0
3 4 5 6 7 8 9
Compliquemos un poco la situacin. Qu ocurre si, al introducir las cadenas, metemos
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 vlidos.
Vemoslo paso a paso. Empezamos con este estado de la entrada:
Introduccin a la programacin con C 401 c UJI
402
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
u n o \n
El primer scanf empieza saltndose los blancos inciales:
u n o \n
A continuacin consume los caracteres , y y se detiene al detectar el blanco
que sigue:
u n o \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
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 o \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
De nuevo, como no hay ms que leer, la ejecucin se detiene. El usuario teclea entonces
nuevos caracteres:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
A continuacin, sigue saltndose los blancos:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
Pasa entonces a consumir caracteres no blancos y se detiene ante el primer blanco:
u n o \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
b d
0
o
1
s
2
\0
3 4 5 6 7 8 9
Introduccin a la programacin con C 402 c UJI
403
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
Ya est.
Imagina ahora que nuestro usuario quiere introducir en la cadena y en
la cadena . Aqu tienes lo que ocurre al ejecutar el programa
El programa ha nalizado sin darle tiempo al usuario a introducir la cadena .
Es ms, la primera cadena vale y la segunda , con lo que ni siquiera se
ha conseguido el primer objetivo: leer la cadena y depositarla tal cual en
. Analicemos paso a paso lo sucedido. La entrada que el usuario teclea ante el primer
scanf es sta:
u n o d o s \n
La funcin lee en los caracteres , y y se detiene al detectar un blanco. El
nuevo estado se puede representar as:
u n o d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
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 caracteres
, , :
u n o d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
b d
0
o
1
s
2
\0
3 4 5 6 7 8 9
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 utilizar una
funcin distinta: gets (por get string, que en ingls signica obtn cadena), disponible
incluyendo en nuestro programa.
gets
scanf
Vamos a estudiar un caso concreto y analizaremos las causas del extrao comportamiento
observado.
include
dene 80
int main void
Introduccin a la programacin con C 403 c UJI
404
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
char 1 1
int
printf gets
printf scanf
printf gets
printf
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 palabra
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 nalizado! Estudiemos paso a
paso lo ocurrido. El texto introducido ante el primer scanf es:
u n o \n
El primer gets nos deja en esta situacin:
u n o \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
A continuacin se ejecuta el scanf con el que se lee el valor de . El usuario teclea lo
siguiente:
u n o \n 2 \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
La funcin lee el y encuentra un salto de lnea. El estado en el que queda el programa
es ste:
u n o \n 2 \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
Introduccin a la programacin con C 404 c UJI
405
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 con
que nos encontramos ya es un salto de lnea. Pasamos, pues, a este nuevo estado:
u n o \n 2 \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
b \0
0 1 2 3 4 5 6 7 8 9
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
dene 80
int main void
char 1 1
int
char ndelinea 1 Cadena auxiliar. Su contenido no nos importa.
printf gets
printf scanf gets ndelinea
printf gets
printf
return 0
Hemos introducido una variable extra, ndelinea, cuyo nico objetivo es consumir lo que
scanf no ha consumido. Gracias a ella, ste es el estado en que nos encontramos justo
antes de empezar la lectura de :
u n o \n 2 \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
ndelinea
\0
0 1 2 3 4 5 6 7 8 9
El usuario escribe entonces el texto que desea almacenar en :
Introduccin a la programacin con C 405 c UJI
406
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2
Introduccin a la programacin con C - UJI
u n o \n 2 \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
ndelinea
\0
0 1 2 3 4 5 6 7 8 9
Ahora la lectura de tiene xito. Tras ejecutar gets, ste es el estado resultante:
u n o \n 2 \n d o s \n
a
u
0
n
1
o
2
\0
3 4 5 6 7 8 9
2 i
ndelinea
\0
0 1 2 3 4 5 6 7 8 9
b d
0
o
1
s
2
\0
3 4 5 6 7 8 9
Perfecto! Ya te dijimos que aprender C iba a suponer enfrentarse a algunas dicultades
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 recordar
consumir el n 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, otante, etc.,
hacerlo con sscanf sobre la cadena leda:
include
dene 80
int main void
char 1 1
int
char linea 1 Cadena auxiliar. Su contenido no nos importa.
printf gets
printf gets linea scanf linea
printf gets
printf
return 0
Introduccin a la programacin con C 406 c UJI
407
Andrs Marzal/Isabel Gracia - ISBN: 978-84-693-0143-2 Introduccin a la programacin con C - UJI
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 Alicia
11
:
JERIGNDOR
Cocillaba el da y las tovas agilimosas
giroscopaban y barrenaban en el larde.
Todos debirables estaban los burgovos,
y silbramaban las alecas rastas.
11. [...] Carroll pasa a continuacin a interpretar las palabras de la manera siguiente:
Biiic [cocillaba] (der. del verbo Bi o Boii); hora de cocinar la co-
mida; es decir, cerca de la hora de comer.
'iu [agilimosas] (voz compuesta por 'iiV y iui. Suave y activo.
1ov/. Especie de tejn. Tena suave pelo blanco, largas patas traseras y cuernos
cortos como de ciervo, se alimentaba principalmente de queso.
Ci [giroscopar], verbo (derivado de C/o. o Ci/o., perro). Araar como
un perro.
CVuii [barrenar], (de donde viene CiVuii [barrena]) hacer agujeros en
algo.
\/vi [larde] (derivado del verbo to swab [fregar] o soak [empapar]).
Ladera de una colina (del hecho de empaparse por accin de la lluvia).
\iVs (de donde viene \iVsi/uii y \isi/uii): infeliz.
Boocovi [burgovo], especie extinguida de loro. Careca de alas, tena el pico
hacia arriba, y anidaba bajo los relojes de sol: se alimentaba de ternera.
\oVi [aleca] (de donde viene 'oiiVoVi y 'oiiVi). Grave.
R/u [rasta]. Especie de tortuga de tierra. Cabeza erecta, boca de tiburn,
patas anteriores torcidas, de manera que el animal caminaba sobre sus rodillas;
cuerpo liso de color verde; se alimentaba de golondrinas y ostras.
O.c/ui [silbramar]. Pretrito del verbo O.ciui (emparentado con el an-
tiguo o Ciki o 'uiki, del que proceden 'ui/k [chillar] y Ci/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 desdichados,
y las graves tortugas proferan chillidos.
/iici/ /o/n/ (nicio ni \/i C/ni), Lewis Carroll.
Introduccin a la programacin con C 407 c UJI