Curso de C para Dummies
Curso de C para Dummies
Curso de C para Dummies
Se ha revisado con la intención que su nivel sea el razonable para una asignatura de
“Fundamentos de programación” o similar, aunque quizá algunos centros (especialmente
universitarios) exijan un nivel más alto que el que se cubre.
Aun así, este texto no pretende “sustituir a un profesor”, sino servir de apoyo para que los
alumnos no pierdan tiempo en tomar apuntes. Pero es trabajo del profesor aclarar las dudas
que surjan y proponer muchos más ejercicios que los que figuran aquí.
Este texto se distribuye "tal cual", sin garantía de ningún tipo, implícita ni explícita. Aun así, mi
intención es que resulte útil, así que le rogaría que me comunique cualquier error que
encuentre.
$ %
& ' (
) *
!
+ , " -
" # $ % & !
' $ !
( $ $ )
( , . /
%
0 - .
1
* % + , - +
. / % 0 1 & 2 &
3 # / 4 5% 0 ,6 0 , 0
3 # / 4 5% 7
7 8 % 98
8 % 98 )
! 8 % 98 - :
) ; % ,-
: <
= 0
$ % >? 7
( # % ,$ 7
1
8 0
( &
) 2 - 3 . #
+ ) -4 5 /
( 1 * +
7 8 %@ 0
7 #
+
#
#0
% A, A?, B, B?, ??, C?
#9
Revisión 0.90– Página 2
7 % DD, EE, C
# F #G
! H F #G !
) # I 7=
: %J 7
= - 7
(+
- 7
- 77
# 7
6 7- (/
+ 6 -. 3 "
( 6
&* . 0 %8
9 " (
+ .
+ . /
+ 8
++ 8
! " # #
( % *
7 " # 0 !
7 K !
7 < !
( % (
7 " # !7
7 L # !
7 !!
7 H + % , !)
7 7 ' $ % 0, 02 !)
7 % )=
7 ! # % #, #, ,M )
7 ) K )
( 1 #+
(+ : #(
(( #(
77 " # 0 )7
77 ' 0 )!
77 H )!
( ##
( ) /
$ % & ' (
. 8 $ /+
. 8 $ /(
8 . . 8 /
+ ; 8 /
El problema es que los lenguajes que realmente entienden los ordenadores resultan difíciles
para nosotros, porque son muy distintos de los que nosotros empleamos habitualmente para
hablar. Escribir programas en el lenguaje que utiliza internamente el ordenador (llamado
“ ” o “código máquina”) es un trabajo duro, tanto a la hora de crear el
programa como (especialmente) en el momento de corregir algún fallo o mejorar lo que se
hizo.
Por eso, en la práctica se emplean lenguajes más parecidos al lenguaje humano, llamados
“lenguajes de ”. Normalmente, estos son muy parecidos al idioma inglés, aunque
siguen unas reglas mucho más estrictas.
Vamos a ver en primer lugar algún ejemplo de lenguaje de alto nivel, para después comparar
con lenguajes de bajo nivel, que son los más cercanos al ordenador.
Uno de los lenguajes de alto nivel más sencillos es el lenguaje BASIC. En este lenguaje, escribir
el texto Hola en pantalla, sería tan sencillo como usar la orden
Otros lenguajes, como Pascal, nos obligan a ser algo más estrictos, pero a cambio hacen más
fácil descubrir errores:
El equivalente en lenguaje C resulta algo más difícil de leer, por motivos que iremos
comentando un poco más adelante:
Los lenguajes de bajo nivel son más cercanos al ordenador que a los lenguajes humanos. Eso
hace que sean más difíciles de aprender y también que los fallos sean más difíciles de descubrir
y corregir, a cambio de que podemos optimizar al máximo la velocidad (si sabemos cómo), e
Revisión 0.90– Página 6
incluso llegar a un nivel de control del ordenador que a veces no se puede alcanzar con otros
lenguajes. Por ejemplo, escribir Hola en lenguaje ensamblador de un ordenador equipado con
el sistema operativo MsDos y con un procesador de la familia Intel x86 sería algo como
"
# $%%
*
!! & "
+$
,-%%
+$
Resulta bastante más difícil de seguir. Pero eso todavía no es lo que el ordenador entiende,
aunque tiene una equivalencia casi directa. Lo que el ordenador realmente es capaz de
comprender son secuencias de ceros y unos. Por ejemplo, las órdenes “mov ds, ax” y “mov ah,
9” (en cuyo significado no vamos a entrar) se convertirían a lo siguiente:
(Nota: los colores de los ejemplos anteriores son una ayuda que nos dan algunos entornos de
programación, para que nos sea más fácil descubrir errores).
Está claro entonces que las órdenes que nosotros hemos escrito (lo que se conoce como
“programa ”) deben convertirse a lo que el ordenador comprende (obteniendo el
“programa ”).
Si elegimos un lenguaje de bajo nivel, como el ensamblador (en inglés Assembly, abreviado
como Asm), la traducción es sencilla, y de hacer esa traducción se encargan unas herramientas
llamadas (en inglés Assembler).
Cuando el lenguaje que hemos empleado es de alto nivel, la traducción es más complicada, y a
veces implicará también recopilar varios fuentes distintos o incluir posibilidades que se
encuentran en bibliotecas que no hemos preparado nosotros. Las herramientas encargadas de
todo esto son los .
Por ejemplo, en el caso de Windows (y de MsDos), y del programa que nos saluda en lenguaje
Pascal, tendríamos un fichero fuente llamado SALUDO.PAS. Este fichero no serviría de nada en
un ordenador que no tuviera un compilador de Pascal. En cambio, después de compilarlo
obtendríamos un fichero SALUDO.EXE, capaz de funcionar en cualquier otro ordenador que
tuviera el mismo sistema operativo, aunque no tenga un compilador de Pascal instalado.
Para algunos lenguajes, es frecuente encontrar compiladores pero no suele existir intérpretes.
Es el caso del lenguaje C, de Pascal y de C++, por ejemplo. En cambio, para otros lenguajes, lo
habitual es trabajar con intérpretes y no con compiladores, como ocurre con Python, Ruby y
PHP.
Además, hoy en día existe algo que parece intermedio entre un compilador y un intérprete:
Existen lenguajes que no se compilan para obtener un ejecutable para un ordenador concreto,
sino un ejecutable “genérico”, que es capaz de funcionar en distintos tipos de ordenadores, a
condición de que en ese ordenador exista una “ ” capaz de entender esos
ejecutables genéricos. Esta es la idea que se aplica en Java: los fuentes son ficheros de texto,
con extensión “.java”, que se compilan a ficheros “.class=”. Estos ficheros “.class=” se podrían
llevar a cualquier ordenador que tenga instalada una “máquina virtual Java” (las hay para la
mayoría de sistemas operativos). Esta misma idea se sigue en el lenguaje C#, que se apoya en
una máquina virtual llamada “Dot Net Framework” (algo así como “armazón punto net”).
A pesar de que los lenguajes de alto nivel se acercan al lenguaje natural, que nosotros
empleamos, es habitual no usar ningún lenguaje de programación concreto cuando queremos
plantear los pasos necesarios para resolver un problema, sino emplear un lenguaje de
programación ficticio, no tan estricto, muchas veces escrito incluso en español. Este lenguaje
recibe el nombre de
Por ejemplo, un algoritmo que controlase los pagos que se realizan en una tienda con tarjeta de
crédito, escrito en pseudocódigo, podría ser:
- ' & 2
& 2 &
5 6
7
8
8
8
, el lenguaje C tiene que es más difícil de aprender que otros y que puede
resultar difícil de leer (por lo que ciertos errores pueden tardar más en encontrarse).
La mayoría de los compiladores actuales permiten dar todos estos pasos desde un único
, en el que escribimos nuestros programas, los compilamos, y los depuramos en caso
de que exista algún fallo.
Y la cosa no acaba aquí. Aún queda más miga de la que parece en este programa, pero
cuando ya vayamos practicando un poco, iremos concretando más alguna que otra cosa de las
que aquí han quedado poco detalladas.
1
Hay que recordar que un ordenador no es sólo lo que acostumbramos a tener sobre nuestra mesa, con
pantalla y teclado. Existen otros muchos tipos de ordenadores que realizan tareas complejas y no
necesitan una pantalla durante su funcionamiento normal, como el ordenador que controla el ABS de un
coche.
Revisión 0.90– Página 11
El C es un lenguaje de , de modo que puede haber varias órdenes en
una misma línea, u órdenes separadas por varias líneas o espacios entre medias. Lo
que realmente indica dónde termina una orden y donde empieza la siguiente son los
puntos y coma. Por ese motivo, el programa anterior se podría haber escrito también
así (aunque no es aconsejable, porque puede resultar menos legible):
(esta es la forma que yo emplearé en este texto cuando estemos trabajando con
fuentes de mayor tamaño, para que ocupe un poco menos de espacio).
Los sistemas operativos de la familia Unix, como Linux, suelen incluir un compilador de C, de
modo que será fácil probar nuestros programas. Supondremos que se trata de una versión de
Linux de los últimos años, que tenga entorno gráfico. Podríamos usar el editor del texto del
entorno gráfico, teclear el fuente y guardarlo en nuestra carpeta personal, por ejemplo con el
nombre ejemplo001.c (lo que sí es importante es que termine en “.c”):
Después abriríamos una ventana de terminal y teclearíamos la siguiente orden para compilar
nuestro fuente:
0 %%$ 9 "
Donde:
“cc” es la orden que se usa para poner el compilador en marcha.
“ejemplo001.c” es el nombre del fuente que queremos compilar.
La opción “Vo” se usa para indicar el nombre que queremos que tenga el fichero
ejecutable resultante (la “o” viene de “output”, salida).
“miprograma” es el nombre que tendrá el programa ejecutable.
: "
Para crear nuestro programa, en el menú "Archivo" escogemos "Nuevo / Código fuente", y nos
aparece un editor vacío en el que ya podemos empezar a teclear. Si queremos nuestro
programa en funcionamiento, entraríamos al menú “Ejecutar” y usaríamos la opción “Compilar y
ejecutar”:
Puede ocurrir que se muestre el texto en pantalla, pero la ventana desaparezca tan rápido que
no tengamos tiempo de leerlo. Si es nuestro caso, tenemos dos opciones:
!
"
" #
Cuando queremos escribir un texto “tal cual”, como en el ejemplo anterior, lo encerramos entre
comillas. Pero no siempre querremos escribir textos prefijados. En muchos casos, se tratará de
algo que habrá que calcular. El caso más sencillo es el de una operación matemática. En
principio, podríamos pensar en intentar algo así (que está ):
! ;<,
Revisión 0.90– Página 15
En muchos lenguajes de programación esto es perfectamente válido, pero no en C. La función
“printf” nos obliga a que lo que escribamos en primer lugar sea un texto, indicado entre
comillas. Eso que le indicamos entre comillas es realmente un . Dentro de
ese código de formato podemos tener caracteres especiales, con los que le indicamos dónde y
cómo queremos que aparezca un número (u otras cosas). Esto lo veremos con detalle un poco
más adelante, pero de momento podemos anticipar que “' ” sirve para decir “quiero que aquí
aparezca un número entero”. ¿Qué número? El que le indicamos a continuación, separado por
una coma:
! = > ;<,
Este ejemplo mostraría en pantalla un número entero (%d) que sea el resultado de suma 3 y 4.
Podemos escribir entre las comillas más detalles sobre lo que estamos haciendo:
! ? ; 1 , = > ;<,
$ %
Está claro que el símbolo de la suma será un +, y podemos esperar cual será el de la resta,
pero alguna de las operaciones matemáticas habituales tiene símbolos menos intuitivos.
Veamos cuales son los más importantes:
<
@ > " 3
A B 3
: 7 6 3
= 6 3 C 3 D
Sencillo:
En primer lugar se realizarán las operaciones indicadas entre paréntesis.
Luego la negación.
Después las multiplicaciones, divisiones y el resto de la división.
Finalmente, las sumas y las restas.
En caso de tener igual prioridad, se analizan de izquierda a derecha.
El espacio del que disponemos para almacenar los números es limitado. Si el resultado de una
operación es un número “demasiado grande”, obtendremos un resultado erróneo. Por eso en
los primeros ejemplos usaremos números pequeños. Más adelante veremos a qué se debe
realmente este problema y cómo evitarlo.
& ' (
Las son algo que no contiene un valor predeterminado, un espacio de memoria al
que nosotros asignamos un nombre y en el que podremos almacenar datos.
El primer ejemplo nos permitía escribir “Hola”. El segundo nos permitía sumar dos números que
habíamos prefijado en nuestro programa. Pero esto tampoco es “lo habitual”, sino que esos
números dependerán de valores que haya tecleado el usuario o de cálculos anteriores.
Por eso necesitaremos usar variables, en las que guardemos los datos con los que vamos a
trabajar y también los resultados temporales. Vamos a ver como ejemplo lo que haríamos para
sumar dos números enteros que fijásemos en el programa.
! " # $ % &
Para usar una cierta variable primero hay que : indicar su nombre y el tipo de datos
que querremos guardar.
El primer tipo de datos que usaremos serán números enteros (sin decimales), que se indican
con “int” (abreviatura del inglés “integer”). Después de esta palabra se indica el nombre que
tendrá la variable:
Esa orden reserva espacio para almacenar un número entero, que podrá tomar distintos
valores, y al que nos referiremos con el nombre “primerNumero”.
! ' $
Podemos darle un valor a esa variable durante el programa haciendo
E+;,
O también podemos darles un valor inicial (“inicializarlas=”) antes de que empiece el programa,
en el mismo momento en que las definimos:
E+;,
Después ya podemos hacer operaciones con las variables, igual que las hacíamos con los
números:
E < "
! ( $ $
Una vez que sabemos cómo mostrar un número en pantalla, es sencillo mostrar el valor de una
variable. Para un número hacíamos cosas como
! ? = > ;<,
! ? = >
Ya sabemos todo lo suficiente para crear nuestro programa que sume dos números usando
variables:
"
+;,
" FGH
"
! =
Nota: las variables las podemos declarar dentro del cuerpo del programa (main) o fuera de él.
En programas tan sencillos no habrá diferencia. Más adelante veremos que en ciertos casos sí
se comportarán de forma distinta según donde las hayamos declarado.
Podemos resumir un poco este fuente, si damos los valores a las variables al inicializarlas:
+;,
" FGH
"
! =
) ' *
Estos nombres de variable (lo que se conoce como “ ”) pueden estar formados
por letras, números o el símbolo de subrayado (_) y deben comenzar por letra o subrayado. No
deben tener espacios entre medias, y hay que recordar que las vocales acentuadas y la eñe son
problemáticas, porque no son letras "estándar" en todos los idiomas. Algunos compiladores
permiten otros símbolos, como el $, pero es aconsejable no usarlos, de modo que el programa
sea más portable (funcione con facilidad en distintos sistemas).
5K $ K
BL 7 6
De momento, intentaremos usar nombres de variables que a nosotros nos resulten claros, y
que no parezca que puedan ser alguna orden de C.
E %
E %
o cualquier variación similar, el compilador protestará y nos dirá que no conoce esa variable,
porque la habíamos declarado como
El & que puede tener un "identificador" (el nombre de una variable, por
ejemplo) depende del compilador que usemos. Es frecuente que permitan cualquier longitud,
pero que realmente sólo se fijen en unas cuantas letras (por ejemplo, en las primeras 8 o en las
primeras 32). Eso quiere decir que puede que algún compilador considerase como iguales las
variables NumeroParaAnalizar1 y NumeroParaAnalizar2, porque tienen las primeras 18 letras
iguales. El C estándar (ANSI C) permite cualquier longitud, pero sólo considera las primeras 31.
+ !
Podemos escribir comentarios, que el compilador ignora, pero que pueden servir para
aclararnos cosas a nosotros. Se escriben entre /* y */:
:A M " / 6 L A:
Es conveniente escribir comentarios que aclaren la misión de las partes de nuestros programas
que puedan resultar menos claras a simple vista. Incluso suele ser aconsejable que el
programa comience con un comentario, que nos recuerde qué hace el programa sin que
necesitemos mirarlo de arriba a abajo. Un ejemplo casi exagerado:
:A @@@@ ?0 -4 J ! 0 @@@@ A:
+;,
" FGH
:A M " / 6 L A:
:A A:
"
:A N / 6 A:
! =
:A ?
M
L O
A:
! = > P
Con ese “%d” indicamos que esperamos leer un número entero (igual que para “printf”) y con
&primerNumero decimos que queremos que ese valor leído se guarde en la variable llamada
“primerNumero”. La diferencia está en ese símbolo & que nos obliga scanf a poner antes del
nombre de la variable. Más adelante veremos qué quiere decir ese símbolo y en qué otros casos
se usa.
Un ejemplo de programa que sume dos números tecleados por el usuario sería:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F4 A:
:A %%F - A:
:A A:
:A . 6 A:
:A 6 ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A - " A:
" :A 6 ' A:
! J
! =
! " J
! = "
"
! =
:
1. Multiplicar dos números tecleados por usuario
2. El usuario tecleará dos números (x e y), y el programa deberá calcular cual es el
resultado de su división y el resto de esa división.
3. El usuario tecleará dos números (a y b), y el programa mostrar el resultado de la
operación (a+b)*(aVb) y el resultado de la operación a2Vb2.
Empieza a ser el momento de refinar, de dar más detalles. El primer “matiz” importante es el
signo de los números: hemos hablado de números enteros (sin decimales), pero no hemos
detallado si esos números son positivos, negativos o si podemos elegirlo nosotros.
Pues es sencillo: si no decimos nada, se da por sentado que el número puede ser negativo o
positivo. Si queremos dejarlo más claro, podemos añadir la palabra “ ” (con signo) antes
de “int”. Este es uno de los “ ” que podemos emplear. Otro modificador es
“ ” (sin signo), que nos sirve para indicar al compilador que no vamos a querer
guardar números negativos, sólo positivos. Vamos a verlo con un ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G4 A:
:A %%G - A:
:A A:
:A 1 A:
:A " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A - " A:
"
!$
" !+
;
! ? = >
! " = > "
! =
¿Y sí hubiéramos escrito “tercerNumero=V3” después de decir que va a ser un entero sin signo,
pasaría algo? No, el programa mostraría un –3 en la pantalla. El lenguaje C nos deja ser tan
La pregunta que puede surgir ahora es: ¿resulta útil eso de no usar números negativos? Sí,
porque entonces podremos usar números positivos de mayor tamaño (dentro de poco veremos
por qué ocurre esto).
De igual modo que detallamos si queremos que un número pueda ser negativo o no, tenemos
disponible otro modificador que nos permite decir que queremos más espacio, para poder
almacenar números más grandes. Un “int” normalmente nos permite guardar números
inferiores al 2.147.483.647, pero si usamos el modificador “ ”, ciertos sistemas nos
permitirán usar números mucho mayores, o bien con el modificador “ ! ” podremos usar
números menores (sólo hasta 32.767, en caso de que necesitemos optimizar la cantidad de
memoria que utilizamos).
, % - . & /
&
El primer problema a tener en cuenta es que si asignamos a una variable “demasiado pequeña”
un valor más grande del que podría almacenar, podemos obtener valores incorrectos. Un caso
típico es intentar asignar un valor “long” a una variable “short”:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H4 A:
:A %%H - A:
:A A:
:A A:
:A " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A - " A:
"
!$
" ;;%%%
$+;,FG
! ? = >
! " = > "
! =
Y un problema similar lo podríamos tener si asignamos valores de un número sin signo a uno
con signo (o viceversa).
¿Por qué? Por que este último ejemplo lo hemos probado con un compilador ) * . Se
trata de un sistema operativo más antiguo, de 16 bits, capaz de manejar números de menor
tamaño. En estos sistemas, los “int” llegaban hasta 32.767 (lo que equivale a un short en los
sistemas modernos de 32 bits) y los “short” llegaban sólo hasta 127. En los sistemas de 64 bits
(poco frecuentes todavía) existen “int” de mayor tamaño.
Para entender por qué ocurre esto, vamos a hablar un poco sobre unidades de medida
utilizadas en informática y sobre sistemas de numeración.
0 # 1 2% - *3 - *
-
En informática, la unidad básica de información es el % . En la práctica, podemos pensar que
un byte es el equivalente a una . Si un cierto texto está formado por 2000 letras, podemos
esperar que ocupe unos 2000 bytes de espacio en nuestro disco.
Eso sí, suele ocurrir que realmente un texto de 2000 letras que se guarde en el ordenador
ocupe más de 2000 bytes, porque se suele incluir información adicional sobre los tipos de letra
que se han utilizado, cursivas, negritas, márgenes y formato de página, etc.
Un byte se queda corto a la hora de manejar textos o datos algo más largos, con lo que se
recurre a un múltiplo suyo, el + % , que se suele abreviar , o K.
En teoría, el prefijo kilo querría decir “mil”, luego un kilobyte debería ser 1000 bytes, pero en
los ordenadores conviene buscar por comodidad una potencia de 2 (pronto veremos por qué),
por lo que se usa 210 =1024. Así, la equivalencia exacta es 1 K = 1024 bytes.
Los K eran unidades típicas para medir la memoria de ordenadores: 640 K ha sido mucho
tiempo la memoria habitual en los IBM PC y similares. Por otra parte, una página
mecanografiada suele ocupar entre 2 K (cerca de 2000 letras) y 4 K.
Cuando se manejan datos realmente extensos, se pasa a otro múltiplo, el % o Mb, que
es 1000 K (en realidad 1024 K) o algo más de un millón de bytes. Por ejemplo, en un diskette
“normal” caben 1.44 Mb, y en un Compact Disc para ordenador (CdVRom) se pueden almacenar
Para estas unidades de gran capacidad, su tamaño no se suele medir en megabytes, sino en el
múltiplo siguiente: en % , con la correspondencia 1 Gb = 1024 Mb. Así, son cada vez
más frecuentes los discos duros con una capacidad de 120, 200 o más gigabytes.
Y todavía hay unidades mayores, pero que aún se utilizan muy poco. Por ejemplo, un %
son 1024 gigabytes.
Pero por debajo de los bytes también hay unidades más pequeñas...
:
• ¿Cuántas letras se podrían almacenar en una agenda electrónica que tenga 32 Kb de
capacidad?
• Si suponemos que una canción típica en formato MP3 ocupa cerca de 3.500 Kb,
¿cuántas se podrían guardar en un reproductor MP3 que tenga 256 Mb de capacidad?
• ¿Cuántos diskettes de 1,44 Mb harían falta para hacer una copia de seguridad de un
ordenador que tiene un disco duro de 6,4 Gb? ¿Y si usamos compact disc de 700 Mb,
cuántos necesitaríamos?
• ¿A cuantos CD de 700 Mb equivale la capacidad de almacenamiento de un DVD de 4,7
Gb? ¿Y la de uno de 8,5 Gb?
! 0 # 1 2%
Dentro del ordenador, la información se debe almacenar realmente de alguna forma que a él le
resulte "cómoda" de manejar. Como la memoria del ordenador se basa en componentes
electrónicos, la unidad básica de información será que una posición de memoria esté usada o
no (totalmente llena o totalmente vacía), lo que se representa como un 1 o un 0. Esta unidad
recibe el nombre de .
Un bit es demasiado pequeño para un uso normal (recordemos: sólo puede tener dos valores: 0
ó 1), por lo que se usa un conjunto de ellos, 8 bits, que forman un % . Las matemáticas
elementales (combinatoria) nos dicen que si agrupamos los bits de 8 en 8, tenemos 256
posibilidades distintas (variaciones con repetición de 2 elementos tomados de 8 en 8: VR2,8):
%%%%%%%%
%%%%%%%$
%%%%%%$%
%%%%%%$$
%%%%%$%%
Por tanto, si en vez de tomar los bits de 1 en 1 (que resulta cómodo para el ordenador, pero no
para nosotros) los utilizamos en grupos de 8 (lo que se conoce como un byte), nos
encontramos con 256 posibilidades distintas, que ya son más que suficientes para almacenar
una letra, o un signo de puntuación, o una cifra numérica o algún otro símbolo.
Por ejemplo, se podría decir que cada vez que encontremos la secuencia 00000010 la
interpretaremos como una letra A, y la combinación 00000011 como una letra B, y así
sucesivamente.
También existe una correspondencia entre cada grupo de bits y un número del 0 al 255: si
usamos el sistema binario de numeración (que aprenderemos dentro de muy poco), en vez del
sistema decimal, tenemos que:
Aun así, hay un inconveniente con el código ASCII: sólo los primeros 127 números son
estándar. Eso quiere decir que si escribimos un texto en un ordenador y lo llevamos a otro, las
letras básicas (A a la Z, 0 al 9 y algunos símbolos) no cambiarán, pero las letras internacionales
(como la Ñ o las vocales con acentos) puede que no aparezcan correctamente, porque se les
asignan números que no son estándar para todos los ordenadores.
( Eso de que realmente el ordenador trabaja con ceros y unos, por lo que le resulta más
fácil manejar los números que son potencia de 2 que los números que no lo son, es lo que
explica que el prefijo kilo no quiera decir “exactamente mil”, sino que se usa la potencia de 2
más cercana: 210 =1024. Por eso, la equivalencia exacta es - , . -/01 % .
4 5 % 65
Nosotros normalmente utilizamos el de numeración: todos los números se
expresan a partir de potencias de 10, pero normalmente lo hacemos sin pensar.
(aunque realmente nosotros lo hacemos automáticamente: no nos paramos a pensar este tipo
de cosas cuando sumamos o multiplicamos dos números).
Para los ordenadores no es cómodo contar hasta 10. Como partimos de “casillas de memoria”
que están completamente vacías (0) o completamente llenas (1), sólo les es realmente cómodo
contar con 2 cifras: 0 y 1.
Por eso, dentro del ordenador cualquier número se deberá almacenar como ceros y unos, y
entonces los números se deberán desglosar en potencias de 2 (el llamado “ ”):
13 = 1 · 8 + 1 · 4 + 0 · 2 + 1 · 1
13 = 1 · 2 3 + 1 · 2 2 + 0 · 2 1 + 1 · 2 0
Convertir un número de decimal a binario resulta algo menos intuitivo. Una forma sencilla es ir
dividiendo entre las potencias de 2, y coger todos los cocientes de las divisiones:
Si “juntamos” los cocientes que hemos obtenido, aparece el número binario que buscábamos:
109 decimal = 0110 1101 binario
(Nota: es frecuente separar los números binarios en grupos de 4 cifras Vmedio byteV para
mayor legibilidad, como yo he hecho en el ejemplo anterior; a un grupo de 4 bits se le llama
).
Revisión 0.90– Página 27
Otra forma sencilla de convertir de decimal a binario es dividir consecutivamente entre 2 y
coger los restos que hemos obtenido, pero en orden inverso:
Si leemos esos restos de abajo a arriba, obtenemos el número binario: 1101101 (7 cifras, si
queremos completarlo a 8 cifras rellenamos con ceros por la izquierda: 01101101).
¿Y se pueden hacer operaciones con números binarios? Sí, casi igual que en decimal:
:
1. Expresar en sistema binario los números decimales 17, 101, 83, 45.
2. Expresar en sistema decimal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Sumar los números 01100110+10110010, 11111111+00101101. Comprobar el
resultado sumando los números decimales obtenidos en el ejercicio anterior.
4. Multiplicar los números binarios de 4 bits 0100·1011, 1001·0011. Comprobar el
resultado convirtiéndolos a decimal.
7 5 % 65
Hemos visto que el sistema de numeración más cercano a como se guarda la información
dentro del ordenador es el sistema binario. Pero los números expresados en este sistema de
numeración "ocupan mucho". Por ejemplo, el número 254 se expresa en binario como
11111110 (8 cifras en vez de 3).
Por eso, se han buscado otros sistemas de numeración que resulten más "compactos" que el
sistema binario cuando haya que expresar cifras medianamente grandes, pero que a la vez
mantengan con éste una correspondencia algo más sencilla que el sistema decimal. Los más
usados son el sistema octal y, sobre todo, el hexadecimal.
254 = 3 · 8 2 + 7 · 8 1 + 6 · 8 0
o bien
Hemos conseguido otra correspondencia que, si bien nos resulta a nosotros más incómoda que
usar el sistema decimal, al menos es más compacta: el número 254 ocupa 3 cifras en decimal,
y también 3 cifras en octal, frente a las 8 cifras que necesitaba en sistema binario.
Pero además existe una correspondencia muy sencilla entre el sistema octal y el sistema
binario: si agrupamos los bits de 3 en 3, el paso de es rapidísimo
de modo que
o bien
:
1. Expresar en sistema octal los números decimales 17, 101, 83, 45.
2. Expresar en sistema octal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Expresar en el sistema binario los números octales 171, 243, 105, 45.
4. Expresar en el sistema decimal los números octales 162, 76, 241, 102.
8 5 % 65 +
El sistema octal tiene un inconveniente: se agrupan los bits de 3 en 3, por lo que convertir de
binario a octal y viceversa es muy sencillo, pero un byte está formado por 8 bits, que no es
múltiplo de 3.
Pero hay una dificultad: estamos acostumbrados al sistema decimal, con números del 0 al 9, de
modo que no tenemos cifras de un solo dígito para los números 10, 11, 12, 13, 14 y 15, que
utilizaremos en el sistema hexadecimal. Para representar estas cifras usaremos las letras de la
A a la F, así:
% E % 2
$ E $ 2
+ E + 2
; E ; 2
, E , 2
F E F 2
G E G 2
H E H 2
R E R 2
* E * 2
$% E 5 2
$$ E S 2
$+ E - 2
$; E 7 2
$, E ? 2
$F E 8 2
de modo que
254 = 15 · 16 1 + 14 · 16 0
o bien
de modo que
o bien
254 = 13 · 16 3 + 4 · 16 2 + 3 · 16 1 + 11 · 16 0
es decir
Revisión 0.90– Página 30
54331 (decimal) = D43B (hexadecimal)
% 2 E % E %%%% '
$ 2 E $ E %%%$ '
+ 2 E + E %%$% '
; 2 E ; E %%$$ '
, 2 E , E %$%% '
F 2 E F E %$%$ '
G 2 E G E %$$% '
H 2 E H E %$$$ '
R 2 E R E $%%% '
* 2 E * E $%%$ '
5 2 E $% E $%$% '
S 2 E $$ E $%$$ '
- 2 E $+ E $$%% '
7 2 E $; E $$%$ '
? 2 E $, E $$$% '
8 2 E $F E $$$$ '
110010100100100101010100111 =>
0110 0101 0010 0100 1010 1010 0111 = 6524AA7
:
1. Expresar en sistema hexadecimal los números decimales 18, 131, 83, 245.
2. Expresar en sistema hexadecimal los números binarios de 8 bits: 01100110, 10110010,
11111111, 00101101
3. Expresar en el sistema binario los números hexadecimales 2F, 37, A0, 1A2.
4. Expresar en el sistema decimal los números hexadecimales 1B2, 76, E1, 2A.
9 : % *+
En C tenemos la posibilidad de dar un valor a una variable usando el sistema decimal, como
hemos hecho hasta ahora, pero también podemos usar el sistema octal si ponemos un 0 a la
izquierda del número, o el sistema hexadecimal, si usamos 0x (pero no existe una forma directa
de trabajar con números en binario):
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R4 A:
:A - " A:
"
$F :A 7 A:
" %$F :A T 4 R<FE$; A:
%2$F :A 2 4 $G<FE+$ A:
! ? = >
! " = > "
! =
; <
Ahora que ya sabemos cómo se representa un número en sistema binario, podemos detallar un
poco más cómo se almacenan los números enteros en la memoria del ordenador, lo que nos
ayudará a entender por qué podemos tener problemas al asignar valores entre variables que no
sean exactamente del mismo tipo.
En principio, los números positivos se almacenan como hemos visto cuando hemos hablado del
sistema binario. El único matiz que falta es indicar cuantos bits hay disponibles para cada
número. Lo habitual es usar 16 bits para un “int” si el sistema operativo es de 16 bits (como
MsDos) y 32 bits para los sistemas operativos de 32 bits (como la mayoría de las versiones de
Windows y de Linux –o sistemas Unix en generalV).
En cuanto a los “short” y los “long”, depende del sistema. Vamos a verlo con un ejemplo:
Turbo C 2.01 (MsDos) GCC 3.4.2 (Windows 32b) GCC 3.4.2 (Linux 64b)
int: bits 16 32 32
int: valor máximo 32.767 2.147.483.647 2.147.483.647
short: bits 16 16 16
short: valor máximo 32.767 32.767 32.767
long: bits 32 32 64
Revisión 0.90– Página 32
long: valor máximo 2.147.483.647 2.147.483.647 9·1018
Es un método muy sencillo, pero que tiene el inconveniente de que las operaciones en
las que aparecen números negativos no se comportan correctamente. Vamos a ver un
ejemplo, con números de 8 bits:
También es un método sencillo, en el que las operaciones con números negativos salen
bien, y que sólo tiene como inconveniente que hay dos formas de expresar el número 0
(0000 0000 o 1111 1111), lo que complica algunos trabajos internos del ordenador.
0: para los negativos, se cambian los ceros por unos y se suma uno
al resultado.
En general, todos los formatos que permiten guardar números negativos usan el primer bit para
el signo. Por eso, si declaramos una variable como “unsigned”, ese primer bit se puede utilizar
como parte de los datos, y podemos almacenar números más grandes. Por ejemplo, un
“unsigned int” en MsDos podría tomar valores entre 0 y 65.535, mientras que un “int” (con
signo) podría tomar valores entre +32.767 y –32.768.
-
Hay una operación que es muy frecuente cuando se crean programas, pero que no tiene un
símbolo específico para representarla en matemáticas. Es incrementar el valor de una variable
en una unidad:
E <$
Pues bien, en C, existe una notación más compacta para esta operación, y para la opuesta (el
decremento):
<< M E <$
@@ M E @$
Pero esto tiene más misterio todavía del que puede parecer en un primer vistazo: podemos
distinguir entre "preincremento" y "postincremento". En C es posible hacer asignaciones como
' E <<
Así, si "a" valía 2, lo que esta instrucción hace es dar a "b" el valor de "a" y aumentar el valor
de "a". Por tanto, al final tenemos que b=2 y a=3 ( : se incrementa "a" tras
asignar su valor).
En cambio, si escribimos
' E <<
y "a" valía 2, primero aumentamos "a" y luego los asignamos a "b" ( ), de modo
que a=3 y b=3.
:
Crear un programa que use tres variables x,y,z. Sus valores iniciales serán 15, V10,
2.147.483.647. Se deberá incrementar el valor de estas variables. ¿Qué valores esperas
que se obtengan? Contrástalo con el resultado obtenido por el programa.
Revisión 0.90– Página 34
¿Cuál sería el resultado de las siguientes operaciones? a=5; b=++a; c=a++; b=b*5;
a=a*2;
Y ya que estamos hablando de las asignaciones, hay que comentar que en C es posible hacer
& :
E ' E E $
$ % =>
Pero aún hay más. Tenemos incluso formas reducidas de escribir cosas como "a = a+5". Allá
van
<E ' M E <'
@E ' M E @'
AE ' M E A'
:E ' M E :'
=E ' M E ='
:
Crear un programa que use tres variables x,y,z. Sus valores iniciales serán 15, V10, 214.
Se deberá incrementar el valor de estas variables en 12, usando el formato abreviado.
¿Qué valores esperas que se obtengan? Contrástalo con el resultado obtenido por el
programa.
¿Cuál sería el resultado de las siguientes operaciones? a=5; b=a+2; bV=3; c=V3;
c*=2; ++c; a*=b;
( # % *$
Podemos encontrarnos con variables cuyo valor realmente no varíe durante el programa.
Entonces podemos usar el modificador “ ” para indicárselo a nuestro compilador, y
entonces ya no nos dejará modificarlas por error.
B5U BT E $%
si luego intentamos
B5U BT E $%%
obtendríamos un mensaje de error que nos diría que no podemos modificar una constante.
También podemos encontrarnos (aunque es poco frecuente) con el caso contrario: una variable
que pueda cambiar de valor sin que nosotros modifiquemos (porque accedamos a una valor
que cambie “solo”, como el reloj interno del ordenador, o porque los datos sean compartidos
con otro programa que también pueda modicarlos, por ejemplo). En ese caso, usaremos el
modificador “volatile”, que hace que el compilador siempre compruebe el valor más reciente de
la variable antes de usarlo, por si hubiera cambiado:
.
Cuando queremos almacenar datos con decimales, no nos sirve el tipo de datos “int”.
Necesitamos otro tipo de datos que sí esté preparado para guardar números “reales=” (con
decimales). En el mundo de la informática hay dos formas de trabajar con números reales:
5 -
Tenemos dos tamaños para elegir, según si queremos guardar números con mayor cantidad de
cifras o con menos. Para números con pocas cifras significativas (un máximo de 6) existe el tipo
“float” y para números que necesiten más precisión (unas 10) tenemos el tipo “double”:
float double
Tamaño en bits 32 64
Valor máximo V3,4·10V38 V1,7·10V308
Valor mínimo 3,4·1038 1,7·10308
Cifras significativas 6 o más 10 o más
En algunos sistemas existe un tipo “long double”, con mayor precisión todavía (40 bits o incluso
128 bits).
"# 2
o bien, si queremos dar un valor inicial en el momento de definirlos (recordando que para las
cifras decimales no debemos usar una coma, sino un punto):
"# 2 $+ FG
( &
En principio es sencillo: usaremos “printf”, al que le indicaremos “%f” como código de formato:
Pero también podemos detallar la anchura, indicando el número de cifras totales y el número
de cifras decimales:
! ? 6 2 =F +! 2 :A ? ' O $+ FG A:
Si indicamos una anchura mayor que la necesaria, se rellena con espacios al principio (queda
alineado a la derecha)
! ? 6 2 =H +! 2 :A ? ' O C $+ FGD A:
Si quisiéramos que quede alineado a la izquierda (con los espacios de sobra al final), debemos
escribir la anchura como un número negativo
! ? 6 2 =, $! 2 :A ? ' O $+ G A:
Y si indicamos menos cifras enteras que las necesarias, no se nos hará caso y el número se
escribirá con la cantidad de cifras que sea necesario usar
! ? 6 2 =$ %! 2 :A ? ' O $; A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *4 A:
:A %%* A:
:A A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"# 2 $+ FG
! ? 6 2 =! 2
! ' + =F +! 2
! =F $! 2
! H ! =H $! 2
! IM =@H $! 2
! =+ %! 2
! ! =$ %! 2
El resultado sería
:
El usuario de nuestro programa podrá teclear dos números de hasta 8 cifras
significativas. El programa deberá mostrar el resultado de dividir el primer número
entre el segundo, utilizando tres cifras decimales.
Crear un programa que use tres variables x,y,z. Las tres serán números reales, y nos
bastará con dos cifras decimales. Deberá pedir al usuario los valores para las tres
variables y mostrar en pantalla cual es el mayor de los tres números tecleados.
$ / ( 0 *
Hemos comentado lo que habitualmente ocupa una variable de tipo int, de tipo long int, de tipo
float... Pero tenemos una forma de saber exactamente lo que ocupa: un operador llamado
“sizeof” (tamaño de). Veamos un ejemplo de su uso
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $%4 A:
:A %$% A:
:A A:
:A K 6 ' A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"# !
! ? K ! = $ " !
! 1 ! = $ " "#
! = $ "
? K ! , 1 ! , +
Como se puede ver, hay una : si quiero saber lo que ocupa un tipo de datos,
tengo que indicarlo entre paréntesis: sizeof(float), pero si se trata de una variable, puedo no
usar paréntesis: sizeof i. Eso sí, el compilador no dará ningún mensaje de error si uso un
paréntesis cuando sea una variable sizeof(i), así que puede resultar cómodo poner siempre el
paréntesis, sin pararse a pensar si nos lo podríamos haber ahorrado.
& $ (1 2
Si tenemos dos números enteros y hacemos su división, el resultado que obtenemos es otro
número entero, sin decimales:
Esto se debe a que la operación se realiza entre números enteros, se obtiene un resultado que
es un número entero, y ese valor obtenido se asigna a la variable “float”... pero ya es
demasiado tarde.
Para evitar ese tipo de problemas, podemos indicar que queremos convertir esos valores a
numeros reales. Cuando son números, basta con que indiquemos algún decimal:
y si son variables, añadiremos antes de ellas “(float)” para que las considere como números
reales antes de trabajar con ellas:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $$4 A:
:A %$$ A:
:A A:
:A - 6 3 A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
$ F + +
"# 6 $ 6 +
! B J = 1 = $ +
6 $ $% +
! 1 6 =! 6 $
6 + "# $ % "# +
! 6 ! 4 =! 6 +
B J F 1 + 1 6 + %%%%%% 6 ! 4 + F%%%%%
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $+4 A:
:A %$+ A:
:A A:
:A - 6 3 ! A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"# 2 F 1 ; F
"#
! B J =; $! 1 =; $! 2 1
2&1
! 1 =; $!
! > O =
que daría
B J F % 1 ; F 1 $H F> O $H
) . %
También tenemos un tipo de datos que nos permite almacenar una única letra (ya veremos que
manipular una cadena de texto completa es relativamente complicado). Es el tipo “char”:
(hay que destacar que se usa una comilla simple en vez de comillas dobles). Mostrarlos en
pantalla también es fácil:
! =
Así, un programa que leyera una letra tecleada por el usuario, fijara otra y mostrara ambas
podría ser:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $;4 A:
:A %$; A:
:A A:
:A A:
$ +
!
! = $
+
! . M = 1 ! 0 =
$ +
4 5 %? -
Al igual que ocurría con expresiones como %d, que tenían un significado especial, ocurre lo
mismo con ciertos caracteres, que nos permiten hacer cosas como bajar a la línea siguiente o
mostrar las comillas en pantalla.
\a Emite un pitido
\b Retroceso (permite borrar el último carácter)
\f Avance de página (expulsa una hoja en la impresora)
\n Avanza de línea (salta a la línea siguiente)
\r Retorno de carro (va al principio de la línea)
\t Salto de tabulación horizontal
\v Salto de tabulación vertical
\' Muestra una comilla simple
\" Muestra una comilla doble
\\ Muestra una barra invertida
\0 Carácter nulo (NULL)
\7 Emite un pitido (igual que \a)
\ddd Un valor en octal
\xddd Un valor en hexadecimal
: Crear un programa que pida al usuario que teclee cuatro letras y las
muestre en pantalla juntas, pero en orden inverso, y entre comillas dobles. Por ejemplo si las
letras que se teclean son a, l, o, h, escribiría "hola".
4 #
En el lenguaje C, no existe un tipo de datos para representar una cadena de texto. Eso supone
que su manejo no sea tan sencillo como el de los números enteros, numeros reales y las letras.
Deberemos tratarla como un bloque de varias letras. Por eso lo veremos más adelante.
#
Vamos a ver cómo podemos comprobar si se cumplen condiciones. La primera construcción
que usaremos será 2 .". El formato en C es
! 3
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $,4 A:
:A %$, A:
:A A:
:A - ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" '% ! ? J 6 W
Nota: para comprobar si un valor numérico es mayor que otro, usamos el símbolo “>”, como se
ve en este ejemplo. Para ver si dos valores son iguales, usaremos dos símbolos de “igual”: !
EE% . Las demás posibilidades las veremos algo más adelante. En todos los casos, la
condición que queremos comprobar deberá indicarse entre paréntesis.
:
Crear un programa que pida al usuario un número entero y diga si es par (pista: habrá
que comprobar si el resto que se obtiene al dividir entre dos es cero: if (x % 2 == 0)
…).
Crear un programa que pida al usuario dos números enteros y diga cual es el mayor de
ellos.
Crear un programa que pida al usuario dos números enteros y diga si el primero es
múltiplo del segundo (pista: igual que antes, habrá que ver si el resto de la división es
cero: a % b == 0).
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $F4 A:
:A %$F A:
:A A:
:A - ! + A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" '%
! ? J 6 W
! M ' / " 6 W
:A 5M ' ! A:
:A 5M ' " A:
En este caso, si el número es positivo, se hacen dos cosas: escribir un texto y luego... ¡escribir
otro! (Claramente, en este ejemplo, esos dos “printf” podrían ser uno solo; más adelante
iremos encontrando casos en lo que necesitemos hacer cosas “más serias=” dentro de una
sentencia compuesta).
3 3
< Menor que
> Mayor que
<= Menor o igual que
>= Mayor o igual que
== Igual a
!= No igual a (distinto de)
Y un ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $G4 A:
:A %$G A:
! ? ' J 4
! =
" ( % ! ? J W
Los operadores sólo se pueden usar tal y como aparecen en esa tabla. Por ejemplo, no es un
operador válido “!<” para expresar que un número no es menor que otro.
:
• Crear un programa que multiplique dos números enteros de la siguiente forma: pedirá
al usuario un primer número entero. Si el número que se que teclee es 0, escribirá en
pantalla “El producto de 0 por cualquier número es 0”. Si se ha tecleado un número
distinto de cero, se pedirá al usuario un segundo número y se mostrará el producto de
ambos.
• Crear un programa que pida al usuario dos números reales. Si el segundo no es cero,
mostrará el resultado de dividir entre el primero y el segundo. Por el contrario, si el
segundo número es cero, escribirá “Error: No se puede dividir entre cero”.
! #6
Podemos indicar lo que queremos que ocurra en caso de que no se cumpla la condición, usando
la orden “else” (en caso contrario), así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $H4 A:
:A %$H A:
:A A:
:A - ! , A:
:A V A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" '% ! ? J 6 W
# ! ? J " 6 W
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $R4 A:
:A %$R A:
:A A:
:A - ! F A:
:A ? M 6 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" '% ! ? J 6 W
" ) % ! ? J " 6 W
Podemos enlazar los “if” usando “else”, para decir “si no se cumple esta condición, mira a ver si
se cumple esta otra”:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $*4 A:
:A %$* A:
:A A:
:A - ! G A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" ) %
! ? J " 6 W
#
" %
! ? J W
#
! ? J 6 W
• Mejorar la solución a los dos ejercicios del apartado anterior, usando “else”.
4 % DD* EE* C
Estas condiciones se puede con “y”, “o”, etc., que se indican de la siguiente forma
3
&& Y
|| O
! No
" $ +
" $ ** ;
" ( - ** ? -
La siguiente forma de escribir una condición es incorrecta (y es un error muy frecuente en los
que empiezan a programar en C):
" $ ** + ;
porque la forma correcta de comprobar si la variable “opcion1” o bien “opcion2” valen “3” sería
ésta:
" $ ; ** + ;
:
Crear un programa que pida una letra al usuario y diga si se trata de una vocal.
Crear un programa que pida al usuario dos números enteros y diga “Uno de los
números es positivo”, “Los dos números son positivos=” o ien
b “Ninguno de los números
es positivo”, según corresponda.
Crear un programa que pida al usuario tres números reales y muestre cuál es el mayor
de los tres.
Crear un programa que pida al usuario dos números enteros cortos y diga si son iguales
o, en caso contrario, cuál es el mayor de ellos.
7 # F #G
Como suele ocurrir en C, lo que hemos visto tiene más miga de la que parece: una condición
cuyo resultado sea “falso” nos devolverá un 0, y otra cuyo resultado sea “verdadero” devolverá
el valor 1:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
Revisión 0.90– Página 46
:A ?0 - Q +%4 A:
:A %+% A:
:A A:
:A - ! H A:
:A X 6 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! +EE; 6 = W + ; :A ? ' % A:
! +YE; 6 = W +( ; :A ? ' $ A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +$4 A:
:A %+$ A:
:A A:
:A - ! R A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" ( % :A - 3 A:
! ? J W
" :A - 3 C D A:
! N " W
En general, es preferible evitar este tipo de construcciones. Resulta mucho más legible algo
como “if (numero!=0)” que “if(numero)”.
8 @ F #G
Cuidado con el operador de : hay que recordar que el formato es if (a==b) ... Si no
nos damos cuenta y escribimos if (a=b) estamos asignando a “a” el valor de “b”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ++4 A:
:A %++ A:
:A A:
:A - ! * A:
:A - A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? ' J 4
! =
" ) %
! ? J " 6 W
#
" %
! ? J W
#
! ? J 6 W
Y si esto es un error, ¿por qué el compilador “avisa” en vez de parar y dar un error “serio”?
Pues porque no tiene por qué ser necesariamente un error: podemos hacer
E '
! +
o bien
! E' +
Es decir, en la misma orden asignamos el valor y comparamos (algo parecido a lo que hacíamos
con “b = ++a”, por ejemplo). En este caso, la asignación dentro del “if” sería correcta.
2 E F ! 2EEF ! = > 2
2 E F ! 2 ! !
2 E % ! 2EF ! !
Respuesta: no hemos escrito una comparación dentro de “if”, sino una asignación. Por
tanto, lo que hay escrito dentro del paréntesis se evalúa como verdadero (distinto de
cero) y se escribe Si.
2 E F ! 2E% ! !
Respuesta: de nuevo, no hemos escrito una comparación dentro de “if”, sino una
asignación. Por tanto, lo que hay escrito dentro del paréntesis se evalúa como falso
(cero) y se escribe No.
2 E % ! 2EEF ! !
Para ayudarnos a centrarnos en el problema, existen notaciones gráficas, como los diagramas
de flujo, que nos permiten ver mejor qué se debe hacer y cuando.
En primer lugar, vamos a ver los 4 elementos básicos de un diagrama de flujo, y luego los
aplicaremos a un caso concreto.
El inicio o el final del programa se indica dentro de un círculo. Los procesos internos, como
realizar operaciones, se encuadran en un rectángulo. Las entradas y salidas (escrituras en
pantalla y lecturas de teclado) se indican con un paralelogramo que tenga su lados superior e
inferior horizontales, pero no tenga verticales los otros dos. Las decisiones se indican dentro de
un rombo.
(
Crear el diagrama de flujo y la versión en C de un programa que dé al usuario tres
oportunidades para adivinar un número del 1 al 10.
Crear el diagrama de flujo para el programa que pide al usuario dos números y dice si
uno de ellos es positivo, si lo son los dos o si no lo es ninguno.
Crear el diagrama de flujo para el programa que pide tres números al usuario y dice
cuál es el mayor de los tres.
Z 6 $ 4 6 +
y equivale a decir “si se cumple la condición, toma el valor v1; si no, toma el valor v2”. Un
ejemplo de cómo podríamos usarlo sería
B 1 E ' Z 4 '
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +;4 A:
:A %+; A:
:A A:
:A ? A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' 1
! ? ' J 4
! =
! ? ' 4
! = '
1 '' + , '
! ? 1 J = W 1
Un segundo ejemplo, que sume o reste dos números según la opción que se escoja, sería:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +,4 A:
:A %+, A:
:A A:
:A T @ + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
! ? ' J 4
! =
! ? ' 4
• Crear un programa que use el operador condicional para mostrar un el valor absoluto
de un número de la siguiente forma: si el número es positivo, se mostrará tal cual; si es
negativo, se mostrará cambiado de signo.
• Crear un programa que use el operador condicional para dar a una variable llamada
“iguales=” (entera) el valor 1 si los dos números que hatecleado el usuario son iguales,
o el valor 0 si son distintos.
• Usar el operador condicional para calcular el mayor de dos números.
+
Si queremos ver , sería muy pesado tener que hacerlo con muchos
“if” seguidos o encadenados. La alternativa es la orden “switch”, cuya sintaxis es
2 3
6 $, $
-
6 +, +
+'
-
6 ,
-
" # ,
Es decir, se escribe tras “switch” la expresión a analizar, entre paréntesis. Después, tras varias
órdenes “case” se indica cada uno de los valores posibles. Los pasos (porque pueden ser
varios) que se deben dar si se trata de ese valor se indican a continuación, terminando con
“break”. Si hay que hacer algo en caso de que no se cumpla ninguna de las condiciones, se
detalla tras “default”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +F4 A:
:A %+F A:
:A A:
:A . C [ D A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
, ! ? W
-
$ ,
+ ,
; ,
, ,
F ,
G ,
H ,
R ,
* ,
% , ! 7O" W
-
" # , ! O" W
(
Crear un programa que lea una letra tecleada por el usuario y diga si se trata de una
vocal, una cifra numérica o una consonante.
Crear un programa que lea una letra tecleada por el usuario y diga si se trata de un
signo de puntuación, una cifra numérica o algún otro carácter.
Repetir los dos programas anteriores, empleando “if” en lugar de “switch”.
Hemos visto cómo comprobar condiciones, pero no cómo hacer que una cierta parte de un
programa se repita un cierto número de veces o mientras se cumpla una condición (lo que
llamaremos un “ ”). En C tenemos varias formas de conseguirlo.
+
Si queremos hacer que una sección de nuestro programa se repita mientras se cumpla una
cierta condición, usaremos la orden “while”. Esta orden tiene dos formatos distintos, según
comprobemos la condición al principio o al final.
# 3
Un ejemplo que nos diga si cada número que tecleemos es positivo o negativo, y que pare
cuando tecleemos el número 0, podría ser:
! J % 4
! =
# ( %
" ' % ! ? 6 W
# ! ? " 6 W
! J % 4
! =
Nota: si recordamos que una condición falsa se evalúa como el valor 0 y una condición
verdadera como una valor distinto de cero, veremos que ese “while (numero != 0)” se podría
abreviar como “while (numero)”.
(
• Crear un programa que pida al usuario su contraseña (numérica). Deberá terminar
cuando introduzca como contraseña el número 4567, pero volvérsela a pedir tantas
veces como sea necesario.
• Crea un programa que escriba en pantalla los números del 1 al 10, usando “while”.
• Crea un programa que escriba en pantalla los números pares del 26 al 10 (descenV
diendo), usando “while”.
• Crear un programa calcule cuantas cifras tiene un número entero positivo (pista: se
puede hacer dividiendo varias veces entre 10).
+
Este es el otro formato que puede tener la orden “while”: la condición se comprueba .
El punto en que comienza a repetirse se indica con la orden “do”, así:
# 3
Al igual que en el caso anterior, si queremos que se repitan varias órdenes (es lo habitual),
deberemos encerrarlas entre llaves.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +H4 A:
:A %+H A:
:A A:
:A . C [ D A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
6 H$$
6
! I 6 / 4
! = 6
" 6 ( 6 ! 6L YW
# 6 ( 6
! 5 W
En este caso, se comprueba la condición al final, de modo que se nos preguntará la clave al
menos una vez. Mientras que la respuesta que demos no sea la correcta, se nos vuelve a
preguntar. Finalmente, cuando tecleamos la clave correcta, el ordenador escribe “Aceptada” y
termina el programa.
(
• Crear un programa que pida números positivos al usuario, y vaya calculando la suma
de todos ellos (terminará cuando se teclea un número negativo o cero).
• Crea un programa que escriba en pantalla los números del 1 al 10, usando "do..while".
• Crea un programa que escriba en pantalla los números pares del 26 al 10 (descenV
diendo), usando "do..while".
• Crea un programa que pida al usuario su código de usuario (un número entero) y su
contraseña numérica (otro número entero), y no le permita seguir hasta que introduzca
como código 1024 y como contraseña 4567.
#
Ésta es la orden que usaremos habitualmente para crear partes del programa que
un cierto número de veces. El formato de “for” es
" 6 - 3 3
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q +R4 A:
:A %+R A:
:A A:
:A V 'L C! D A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
" $ ) $%
! =
(
Crear un programa que muestre los números del 15 al 5, descendiendo (pista: en cada
pasada habrá que descontar 1, por ejemplo haciendo @@).
Crear un programa que muestre los primeros ocho números pares (pista: en cada
pasada habrá que aumentar de 2 en 2, o bien mostrar el doble del valor que hace de
contador).
En un “for”, realmente, la parte que hemos llamado “Incremento” no tiene por qué incrementar
la variable, aunque ése es su uso más habitual. Es simplemente una orden que se ejecuta
cuando se termine la “Sentencia” y antes de volver a comprobar si todavía se cumple la
condición de repetición.
! E$ E$%
Un caso todavía más exagerado de algo a lo que se entra y de lo que no se sale sería la
siguiente orden:
Los bucles “for” se pueden (incluir uno dentro de otro), de modo que podríamos escribir
las tablas de multiplicar del 1 al 5 con:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' >
En estos ejemplos que hemos visto, después de “for” había una única sentencia. Si queremos
que se hagan varias cosas, basta definirlas como un (una sentencia compuesta)
encerrándolas entre llaves. Por ejemplo, si queremos mejorar el ejemplo anterior haciendo que
deje una línea en blanco entre tabla y tabla, sería:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;%4 A:
:A -;% - A:
:A A:
:A C! D + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' >
" $ ) $%
! = = = W ' ' &
! W
Para “contar” no necesariamente hay que usar & . Por ejemplo, podemos contar con
letras así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;$4 A:
:A %;$ A:
:A A:
:A ! M A:
:A A:
:A - -> A:
:A - ' A:
" ) I
! =
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;+4 A:
:A %;+ A:
:A A:
:A C! D M A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
" I ' ! +
! =
(
Crear un programa que muestre las letras de la Z (mayúscula) a la A (mayúscula,
descendiendo).
Crear un programa que escriba en pantalla la tabla de multiplicar del 5.
Crear un programa que escriba en pantalla los números del 1 al 50 que sean múltiplos
de 3 (pista: habrá que recorrer todos esos números y ver si el resto de la división entre
3 resulta 0).
3 4(
Podemos salir de un bucle “for” antes de tiempo con la orden “ +”:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;;4 A:
:A %;; A:
:A A:
:A C! D A:
:A C' #D A:
:A A:
:A - -> A:
" % ) $%
" F -
! =
% $ + ; ,
(en cuanto se llega al valor 5, se interrumpe el “for”, por lo que no se alcanza el valor 10).
& 3 (* 0
Podemos saltar alguna repetición de un bucle con la orden “ ”:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;,4 A:
:A %;, A:
:A A:
:A C! D A:
:A C D A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
" % ) $%
" F
! =
% $ + ; , G H R * $%
! E$ , << ! = >
Respuesta: los números del 1 al 3 (se empieza en 1 y se repite mientras sea menor que
4).
! E$ , << ! = >
! E$ E, << ! = >
Respuesta: escribe un 5, porque hay un punto y coma después del “for”, de modo que
repite cuatro veces una orden vacía, y cuando termina, “i” ya tiene el valor 5.
! E$ , ! = >
! E$ << ! = >
! E % E , << \
! EE + ! = > ]
! E % E , << \
! EE + ' # ! = > ]
! E % E , << \
! EE $% ! = > ]
Respuesta: escribe los números del 0 al 4, porque la condición del “continue” nunca se
llega a dar.
! E % E , <<
! EE + ! = >
Respuesta: escribe 5, porque no hay llaves tras el “for”, luego sólo se repite la orden
“if”.
) 3
El formato de “goto” es
"
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;F4 A:
:A %;F A:
:A A:
:A ! 1 " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
" % ) F
" 0 % 0) +% 0 +
" $ 0' H
! 6 = 1 0 6 = W 0
,
! 8 " W
6 % 1 0 6 %
6 % 1 0 6 +
6 % 1 0 6 ,
6 % 1 0 6 G
6 % 1 0 6 R
6 % 1 0 6 $%
6 % 1 0 6 $+
6 % 1 0 6 $,
6 % 1 0 6 $G
6 % 1 0 6 $R
6 % 1 0 6 +%
6 $ 1 0 6 %
6 $ 1 0 6 +
6 $ 1 0 6 ,
6 $ 1 0 6 G
8 "
+ "% * - !5
Cuando comenzamos el tema, vimos cómo ayudarnos de los diagramas de flujo para plantear lo
que un programa debe hacer. Si entendemos esta herramienta, el paso a C (o a casi cualquier
otro lenguaje de programación es sencillo). Pero este tipo de diagramas es antiguo, no tiene en
cuenta todas las posibilidades del lenguaje C (y de muchos otros lenguajes actuales). Por
ejemplo, no existe una forma clara de representar una orden “switch”, que equivaldría a varias
condiciones encadenadas.
Por su parte, un bucle “while” se vería como una condición que hace que algo se repita (una
flecha que vuelve hacia atrás, al punto en el que se comprobaba la condición):
Aun así, existen otras notaciones más modernas y que pueden resultar más cómodas. Sólo
comentaremos una: los diagramas de Chapin. En ellos se representa cada orden dentro de una
caja:
Y las condiciones repetitivas se indican dejando una barra a la izquierda, que marca qué es lo
que se repite, tanto si la condición se comprueba al final (do..while):
Abrir fichero
Mientras haya datos en fichero
Leer dato
Mostrar dato
Cerrar fichero
, 6
En general, nos interesará usar “5! ” cuando puede que la parte repetitiva no se
llegue a repetir nunca (por ejemplo: cuando leemos un fichero, si el fichero está vacío,
no habrá datos que leer).
De igual modo, “ 5! ” será lo adecuado cuando debamos repetir al menos una
vez (por ejemplo, para pedir una clave de acceso, se le debe preguntar al menos una
vez al usuario, o quizá más veces, si la teclea correctamente).
En cuanto a “ ”, es equivalente a un “while”, pero la sintaxis habitual de la oren “for”
hace que sea especialmente útil cuando sabemos exactamente cuantas veces queremos
que se repita (por ejemplo: 10 veces sería “! E$ E$% << ”).
& *
Ya conocemos el manejo básico de la orden “printf”:
(el corchete indica que la cadena de texto de formato debe existir siempre, pero los datos
adicionales son opcionales, pueden no aparecer).
Esta orden muestra un texto formateado en pantalla. Para usarla es necesario incluir <stdio.h>
al principio de nuestro programa. Hemos visto cómo emplearla para mostrar número enteros,
números reales y caracteres. Ahora vamos a ver más detalles sobre qué podemos mostrar y
cómo hacerlo:
c Un único carácter
d Un número entero decimal (en base 10) con signo
f Un número real (coma flotante)
e Un número real en notación exponencial, usando la “e” minúscula
E Un número real en notación exponencial, usando la “E” mayúscula
g Usa “e” o “f” (el más corto), con “e” minúscula
G Usa “e” o “f” (el más corto), con “E” mayúscula
i Un número entero decimal con signo
u Un número entero decimal sin signo (unsigned)
h Corto (modificador para un entero)
l Largo (modificador para un entero)
x Un número entero decimal sin signo en hexadecimal (base 16)
X Un número entero decimal sin signo en hexadecimal en mayúsculas
o Un número entero decimal sin signo en octal (base 8)
s Una cadena de texto (que veremos en el próximo tema)
Queda alguna otra posibilidad que todavía es demasiado avanzada para nosotros, y que
comentaremos mucho más adelante, cuando hablemos de “punteros=”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;G4 A:
:A %;G A:
:A A:
:A 7 ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
$+;,
" !$+;,
"# +;, FGH
?
! ? J 6 = 3 >W
! 1 = 3 >W
! 1 =2 3 2 >W
! 1 =U 3 2 1J >W
! 1 = M M " >W
! 1 =$% ' " >W
! 1 =@$% 0 IM W
! ? " 6 6 = W "
! 1 M M 6 4 = W
"
! ? J 6 =! 3 W
! 1 =F +! >W
! 1 = 3 O! 2 W
! . = 1 2 = W
! W W 4 F%== W
? J 6 $+;, 3 >
1 +;++ 3 >
1 , + 3 2 >
1 ,7+ 3 2 1J >
1 $+;, M M " >
1 $+;, ' " >
1 $+;, 0 IM
? " 6 6 @$+;,
1 M M 6 4 ,+*,*GG%G+
? J 6 +;, FGH%%$ 3
Revisión 0.90– Página 68
1 +;, FH >
1 + ;,FGH% <%%+ 3 O! 2
. ? 1 2
4 F%=
Casi todo el fácil de seguir, pero aun así hay vemos alguna cosa desconcertante...
Por ejemplo, ¿por qué el número real aparece como 234.567001, si nosotros lo hemos definido
como 234.567? Hay que recordar que los números reales se almacenan con una cierta
. En un “float” sólo se nos garantiza que unas 6 cifras sean correctas. Si
mostramos el número con más de 6 cifras (estamos usando 9), puede que el resultado no sea
totalmente correcto. Si esta pérdida de precisión es demasiado grande para el uso que
queramos darle, deberemos usar otro tipo de datos, como .
Lo de que el número negativo quede mal cuando lo intentamos escribir como positivo, también
nos suena conocido: si el primer bit de un número con signo es uno, indica que es un número
negativo, mientras que en un número positivo el primer bit es la cifra binaria más grande (lo
que se conoce como “el bit más significativo”). Por eso, tanto el número V1234 como el
4294966062 se traducen en la % , que la sentencia “printf”
interpreta de una forma u otra según le digamos que el número el positivo o negativo.
Como curiosidad, ese número 4294966062 sería un valor distinto (64302) si usáramos un
compilador de 16 bits en vez de uno de 32, porque sería una secuencia de 16 ceros y unos, en
vez de una secuencia de 32 ceros y unos.
Otra opción más avanzada es que si usamos un asterisco (*) para indicar la anchura o la
precisión, los primeros datos de la lista serán los valores que se tomen para indicar la anchura y
la precisión real que se usarán:
E F
L2 E $%
! =A A > > 2 > 0
(
• Un programa que pida al usuario un número entero y muestre sus equivalentes en
formato hexadecimal y en octal. Deberá seguir pidiendo (y convirtiendo) números hasta
que se introduzca 0.
• Un programa que pida al usuario 2 números reales y muestre su división con 2
decimales (excepto si el segundo es 0; en ese caso, deberá decir “no se puede dividir”).
& *
Como ya sabemos, el uso de “scanf” recuerda mucho al de “printf”, salvo porque hay que
añadir el símbolo & antes de los nombres de las variables en las que queremos almacenar los
datos. Aun así, los códigos de formato no son exactamente los mismos. Vamos a ver los más
importantes:
Como vemos, la diferencia más importante es que si en vez de un entero queremos un entero
largo, se suele usar el mismo carácter escrito en %& .
Al igual que en “printf”, se puede indicar un número antes del identificador, que nos serviría
para indicar la $ de caracteres a leer. Por ejemplo, “scanf("%10s", &texto)”
nos permitiría leer un texto de un tamaño máximo de 10 letras.
Aun así, “scanf” a veces da resultados desconcertantes, por lo que no es la orden más
adecuada cuando se pretende crear un entorno amigable. Más adelante veremos formas
alternativas de leer del teclado.
(
• Un programa que pida al usuario un número hexadecimal y muestre su equivalente en
base 10.
• Un programa que pida al usuario un número octal y muestre su equivalente en base 10.
• Un programa que pida al usuario una letra, después le pida una segunda letra, y las
muestre en el orden contrario al que se introdujeron.
& 5
Es una forma sencilla de escribir un único carácter en la pantalla:
&& 5
Lee el siguiente carácter que esté disponible en el buffer del teclado (una memoria intermedia
que almacena todas las pulsaciones de teclas que hagamos):
"
Si no quedaran más letras por leer, el valor que nos daría es EOF, una constante predefinida
que nos indicará el final de un fichero (End Of File) o, en este caso, de la entrada disponible por
teclado. Se usaría así:
"
" ?T8 ! 1 L
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;H4 A:
:A %;H A:
:A A:
:A " 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
$ +
! 1 4
$ "
+ "
! 4
$
! 1 ' / = +
Vemos que aunque “getchar” lea tecla a tecla, no se analiza lo que hemos tecleado hasta que
se pulsa Intro. Si tecleamos varias letras, la primera vez que usemos getchar nos dirá cual era
la primera, la siguiente vez que usemos getchar nos dirá la segunda letra y así sucesivamente.
En este ejemplo sólo leemos dos letras. Si se teclean tres o más, las últimas se pierden. Si se
teclea una letra y se pulsa Intro, “letra1” será la letra tecleada... ¡y “letra2” será el Intro (el
carácter ‘\n’ de avance de línea)!
Una alternativa sería una segunda orden “getchar” para absorber la pulsación de la tecla Intro:
E " "
O bien leer dos caracteres con “scanf” (la letra esperada y el Intro que queremos absorber):
! = = > P
(
• Un programa que pida al usuario 4 letras (usando “getchar”) y las muestre en orden
inverso (por ejemplo, si las letras son “h o l a”, escribiría “aloh”).
0 ^,_
Podemos acceder a cada uno de los valores individuales indicando su nombre (ejemplo) y el
número de elemento que nos interesa, pero con una precaución: se empieza a numerar desde
0, así que en el caso anterior tendríamos 4 elementos, que serían ejemplo[0], ejemplo[1],
ejemplo[2], ejemplo[3].
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;R4 A:
:A %;R A:
:A A:
:A 0 ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
.F/ :A V 1 F J A:
:A V M L A:
.%/ +%% :A . 6 A:
.$/ $F%
.+/ $%%
.;/ !F%
.,/ ;%%
.%/ :A N A:
.$/ .+/ .;/ .,/
! =
:A 4 ! L ! 3 A:
:A N 0 A:
(
• Un programa que pida al usuario 4 números, los memorice (utilizando una tabla),
calcule su media aritmética y después muestre en pantalla la media y los datos
tecleados.
4 J
Al igual que ocurría con las variables “normales”, podemos dar valor a los elementos de una
tabla al principio del programa. Será más cómodo que dar los valores uno por uno, como
hemos hecho antes. Esta vez los indicaremos todos entre llaves, separados por comas:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ;*4 A:
:A %;* A:
:A A:
:A " 0 A:
:A ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
.F/ :A V 1 F J A:
+%% $F% $%% !F% ;%%
:A V M L A:
.%/ :A N A:
.$/ .+/ .;/ .,/
! =
:A 4 ! " " > 6O A:
:A L ' . " 0 A:
(
• Un programa que almacene en una tabla el número de días que tiene cada mes (suV
pondremos que es un año no bisiesto), pida al usuario que le indique un mes (1=enero,
12=diciembre) y muestre en pantalla el número de días que tiene ese mes.
• Un programa que almacene en una tabla el número de días que tiene cada mes (año
no bisiesto), pida al usuario que le indique un mes (ej. 2 para febrero) y un día (ej. el
día 15) y diga qué número de día es dentro del año (por ejemplo, el 15 de febrero sería
el día número 46, el 31 de diciembre sería el día 365).
4 <
Es de esperar que exista una forma más cómoda de acceder a varios elementos de un array,
sin tener siempre que repetirlos todos, como hemos hecho en
El “truco” consistirá en emplear cualquiera de las estructuras repetitivas que ya hemos visto
(while, do..while, for), por ejemplo así:
En este caso, que sólo sumábamos 5 números, no hemos escrito mucho menos, pero si
trabajásemos con 100, 500 o 1000 números, la ganancia en comodidad sí que está clara.
(
• A partir del programa propuesto en 5.1.2, que almacenaba en una tabla el número de
días que tiene cada mes, crear otro que pida al usuario que le indique la fecha,
detallando el día (1 al 31) y el mes (1=enero, 12=diciembre), como respuesta muestre
en pantalla el número de días que quedan hasta final de año.
• Crear un programa que pida al usuario 10 números y luego los muestre en orden
inverso (del último al primero).
• Crear un programa que pida al usuario 10 números, calcule su media y luego muestre
los que están por encima de la media.
) !
4 " #
Para las $ , la situación se complica un poco: se crean como “arrays=” de
caracteres. Están formadas por una sucesión de caracteres
(6/), de modo que tendremos que reservar una letra más de las que necesitamos. Por ejemplo,
para guardar el texto “Hola” usaríamos “char saludo[5]”.
Este carácter nulo lo utilizarán todas las órdenes estándar que tienen que ver con manejo de
cadenas: las que las muestran en pantalla, las que comparan cadenas, las que dan a una
cadena un cierto valor, etc. Por tanto, si no queremos usar esas funciones y sólo vamos a
acceder letra a letra (como hemos hecho con los números en los últimos ejemplos) nos bastaría
con “char saludo[4]”, pero si queremos usar cualquiera de esta órdenes estándar (será lo
habitual), deberemos tener la prudencia de reservar una letra más de las “necesarias=”, para ese
carácter nulo, que indica el final de la cadena, y que todas esas órdenes utilizan para saber
cuando deben terminar de manipular la cadena.
Un primer ejemplo que nos pidiese nuestro nombre y nos saludase sería:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,%4 A:
:A %,% A:
:A A:
:A 0 A:
:A 2 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ' 4
! = 2
! > = W 2
Dos comentarios:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,$4 A:
:A %,$ A:
:A A:
:A " 0 A:
:A 2 4 ! A:
:A P A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
2 .,%/ :A " ;* A:
! ' 4
! = 2
! > = W 2
4 K #
Podemos leer (o modificar) una de las letras de una cadena de igual forma que leemos o
modificamos los elementos de cualquier tabla: el primer elemento será texto[0], el segundo
será texto[1] y así sucesivamente:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,+4 A:
:A %,+ A:
:A A:
:A 0 A:
:A 2 4 A:
:A A:
:A A:
2 .,%/ :A " ;* A:
! ' 4
! = 2
! > = = W 2 > 2 ^%_
4
En una cadena que definamos como “char texto[40]” lo habitual es que realmente no
ocupemos las 39 letras que podríamos llegar a usar. Si guardamos 9 letras (y el carácter nulo
que marca el final), tendremos 30 posiciones que no hemos usado. Pero estas 30 posiciones
generalmente contendrán “basura”, lo que hubiera previamente en esas posiciones de
memoria, porque el compilador las reserva para nosotros pero no las “limpia”. Si queremos
saber cual es la longitud real de nuestra cadena tenemos dos opciones:
Podemos leer la cadena carácter por carácter desde el principio hasta que encontremos
el carácter nulo (\0) que marca el final.
Hay una orden predefinida que lo hace por nosotros, y que nos dice cuantas letras
hemos usado realmente en nuestra cadena. Es “ ”, que se usa así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,;4 A:
:A %,; A:
:A A:
:A . " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
2 .,%/
! ' 4
! = 2
! = 2
Como es de esperar, si escribimos “Hola”, esta orden nos dirá que hemos tecleado 4 letras
(no cuenta el \0 que se añade automáticamente al final).
(
• Un programa que te pida tu nombre y lo muestre en pantalla separando cada letra de
la siguiente con un espacio. Por ejemplo, si tu nombre es “Juan”, debería aparecer en
pantalla “J u a n”.
• Un programa que te pida tu nombre y lo muestre en pantalla separando al revés. Por
ejemplo, si tu nombre es “Juan”, debería aparecer en pantalla “nauJ”.
4 ! @ ) % *
Hemos visto que si leemos una cadena de texto con “scanf”, se paraba en el primer espacio en
blanco y no seguía leyendo a partir de ese punto. Existen otras órdenes que están diseñadas
específicamente para manejar cadenas de texto, y que nos podrán servir en casos como éste.
Para leer una cadena de texto (completa, sin parar en el primer espacio), usaríamos la orden
“gets=”, así:
" 2
De igual modo, para escribir un texto en pantalla podemos usar “puts=”, que muestra la cadena
de texto y avanza a la línea siguiente:
! = W > 2
(
• Un programa que te pida una frase y la muestre en pantalla sin espacios. Por ejemplo,
si la frase es “Hola, como estás=”, debería aparecer en antalla
p “Hola,comoestás=”.
4 4 ' $ % -* -/
Cuando queremos dar a una variable el valor de otra, normalmente usamos construcciones
como a =2, o como a = b. Pero en el caso de las cadenas de texto, esta NO es la forma
correcta, no podemos hacer algo como saludo="hola" ni algo como texto1=texto2. Si hacemos
algo así, haremos que las dos cadenas estén en la misma posición de memoria, y que los
cambios que hagamos a una de ellas se reflejen también en la otra. La forma correcta de
guardar en una cadena de texto un cierto valor es:
1 > "
Es decir, debemos usar una función llamada “ %” (string copy, copiar cadena), que se
encuentra también en “string.h”. Vamos a ver dos ejemplos de su uso:
1 >
Para evitar este problema, tenemos una forma de indicar que queremos copiar sólo los
primeros % de origen, usando la función “ %”, así:
Vamos a ver un ejemplo, que nos pida que tecleemos una frase y guarde en otra variable sólo
las 4 primeras letras:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,,4 A:
:A %,, A:
:A A:
:A , A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
! ! 4
" 2 $
1 2 + 2 $
! V 2 = W 2 +
1 2 ; 2 $ ,
! N , = W 2 ;
Finalmente, existe otra orden relacionada con estas dos: podemos : una cadena al final
de otra (concatenarla), con
> "
Vamos a ver un ejemplo de su uso, que nos pida nuestro nombre, nuestro apellido y cree una
nueva cadena de texto que contenga los dos, separados por un espacio:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,F4 A:
:A %,F A:
:A A:
:A - A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
Revisión 0.90– Página 79
"
! ' 4
" 2 $
! 4
" 2 +
2 $ :A 5K ' A:
2 $ 2 + :A N " A:
! = W 2 $
(
• Un programa que te pida una palabra, y la almacene en la variable llamada “texto”.
Luego deberá pedir una segunda palabra, y añadirla al final de “texto”. Finalmente,
deberá pedir una tercera palabra, y guardarla en la variable “texto” y en otra variable
llamada “texto2”.
4 7 %
Para dos cadenas alfabéticamente (para ver si son iguales o para poder ordenarlas,
por ejemplo), usamos
$> +
Hay que tener cuidado, porque las cadenas se comparan como en un diccionario, pero hay que
tener en cuenta ciertas cosas:
Al igual que en un diccionario, todas las palabras que empiecen por B se consideran
“mayores=” que las que empiezan por A.
Si dos cadenas empiezan por la misma letra (o las mismas letras), se ordenan
basándose en la primera letra diferente, también al igual que en el diccionario.
La primera diferencia está que en que se distingue entre mayúsculas y minúsculas. Para
más detalles, en el código ASCII las mayúsculas aparecen antes que las minúsculas, así
que las palabras escritas en mayúsculas se consideran “menores=” que las palabras
escritas en minúsculas. Por ejemplo, “ala” es menor que “hola”, porque una empieza
por “a” y la otra empieza por “h”, pero “Hola” es menor que “ala” porque la primera
empieza con una letra en mayúsculas y la segunda con una letra en minúsculas.
La segunda diferencia es que el código ASCII estándar no incluye eñe, vocales
acentuadas ni caracteres internacionales, así que estos caracteres “extraños=” aparecen
Revisión 0.90– Página 80
después de los caracteres “normales=”, de modo que “adiós=” se considera “mayor” que
“adiposo”, porque la o acentuada está después de todas las letras del alfabeto inglés.
Vamos a ver un primer ejemplo que nos pida dos palabras y diga si hemos tecleado la misma
las dos veces:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,G4 A:
:A %,G A:
:A A:
:A - A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
2 $.,%/ 2 +.,%/
! ' 4
" 2 $
! ' 4
" 2 +
" 2 $ 2 + %
! " W
#
! W
Podemos mejorarlo ligeramente para que nos diga qué palabra es “menor” de las dos:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,H4 A:
:A %,H A:
:A A:
:A - + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
2 $.,%/ 2 +.,%/
! ' 4
" 2 $
2 $ 2 +
" %
! " W
# " '%
! . ' 1 W
#
! . " ' 1 W
(
• Crear un programa que pida al usuario su contraseña. Deberá terminar cuando
introduzca como contraseña la palabra "clave", pero volvérsela a pedir tantas veces
como sea necesario.
• Crear un programa que pida al usuario su nombre y su contraseña, y no le permita
seguir hasta que introduzca como nombre "Pedro" y como contraseña "Peter".
4 8 # % #* #* *L
Hay dos posibilidades más de las cadenas de texto que merece la pena comentar. Son las que
nos ofrecen las funciones “sprintf” y “sscanf”:
La funcion " # crea una cadena de texto a partir de una especificación de formato y unos
ciertos parámetros, al igual que hace “printf”, pero la diferencia está en que “printf” manda su
salida a la pantalla, mientras que “sprintf” la deja guardada en una cadena de texto.
? J F% + 6 $%%
Pues bien, si tenemos una cadena de texto que hayamos definido (por ejemplo) como char
cadena[100] y escribimos
Esta vez en pantalla no aparece nada escrito, sino que “cadena” pasa a contener el texto que
antes habíamos mostrado. Ahora ya podríamos escribir este texto con:
o bien con
! D= D>
¿Qué utilidad tiene esta orden? Nos puede resultar cómoda cuando queramos formatear texto
que no vaya a aparecer directamente en pantalla de texto, sino que lo vayamos a enviar a un
Por otra parte " # es similar a “scanf”, con la diferencia de que los valores para las
variables no se leen desde el teclado, sino desde una cadena de texto
1 +% ;%
! = = "
Nota: sscanf devuelve el número de valores que realmente se han detectado, de modo que
podemos comprobar si ha tomado todos los que esperábamos o alguno menos (porque el
usuario haya tecleado menos de los que esperábamos o porque alguno esté tecleado
incorrectamente).
" ! = = " )+
! 7 '
Una tercera orden que puede resultar útil más de una vez es “ ”. Permite comprobar si
una cadena contiene un cierto texto. Devuelve NULL (un valor especial, que nos encontraremos
cada vez más a partir de ahora) si no la contiene, y otro valor (no daremos más detalles por
ahora sobre qué tipo de valor ni por qué) en casi de que sí la contenga:
" ! V..
! '
( estas no son todas las posibilidades que tenemos para manipular cadenas, pero
posiblemente sí son las más habituales. Hay otras que nos permiten buscar una letra dentro de
una cadena (strchr), una cadena dentro de otra cadena (strstr), “dar la vuelta” a una cadena
(strrev), etc. Según el compilador que usemos, podemos tener incluso funciones ya preparadas
para convertir una cadena a %& (strupr) o a minúsculas (strlwr).
4 9 J
Podemos dar un valor inicial a una cadena de texto, usando dos formatos distintos:
' .F%/ `
' .F%/ `
) .
Podemos declarar tablas de . Por ejemplo, si queremos guardar datos
de dos grupos de alumnos, cada uno de los cuales tiene 20 alumnos, tenemos dos opciones:
En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves, igual que si
fuera una tabla de una única dimensión. Vamos a verlo con un ejemplo de su uso:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,R4 A:
:A %,R A:
:A A:
:A 5 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
.+/.$%/
$ + ; , F G H R * $%
$$ $+ $; $, $F $G $H $R $* +%
! . " $ =
.%/.+/
Este tipo de tablas son las que se usan también para guardar matrices, cuando hay que
resolver problemas matemáticos más complejos.
También podemos usar arrays de dos dimensiones si queremos guardar una lista de cadenas
de texto, como en este ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q ,*4 A:
:A %,* A:
:A A:
:A 5 1 A:
:A A:
Revisión 0.90– Página 84
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
0 ? .F/.R%/
8
? ! '
? ! L 6 O
? !
? ! L
! ? " 0 4 =
0 ? .$/
:
• Un programa guarde los nombres de los meses. El usuario deberá indicar un número de
mes (por ejemplo, 3) y se le mostrará el nombre de dicho mes (por ejemplo, Marzo).
• Usar un array de 3 dimensiones para guardar los nombres de los meses en español e
inglés. El usuario deberá indicar un número de mes (por ejemplo, 3) y se le mostrará el
nombre de dicho mes en español (Marzo) y en inglés (March).
)& 7
Si damos un valor inicial a un array, no será necesario que indiquemos su tamaño, porque el
compilador lo puede saber contando cuantos valores hemos detallado, así:
(
• Un programa que pida 10 nombres y los memorice. Después deberá pedir que se teclee
un nombre y dirá si se encuentra o no entre los 10 que se han tecleado antes. Volverá
a pedir otro nombre y a decir si se encuentra entre ellos, y así sucesivamente hasta que
se teclee “fin”.
• Un programa que prepare espacio para un máximo de 100 nombres (de un máximo de
80 letras cada uno). El usuario deberá ir introduciendo un nombre cada vez, hasta que
se pulse Intro sin teclear nada, momento en el que dejarán de pedirse más nombres y
se mostrará en pantalla la lista de los nombres que se han introducido hasta entonces.
))
44 " # -
Un es una agrupación de datos, los cuales no necesariamente son del mismo tipo. Se
definen con la palabra “ ”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F%4 A:
:A %F% A:
:A A:
:A " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"#
`
+%
H F
! . =
Como es habitual en C, para declarar la variable hemos indicado primero el tipo de datos (struct
{ ...} ) y después el nombre que tendrá esa variable (persona).
También podemos declarar primero cómo van a ser nuestros registros, y más adelante definir
variables de ese tipo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F$4 A:
:A %F$ A:
:A A:
:A " + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"#
(
• Un “struct” que almacene datos de una canción en formato MP3: Artista, Título,
Duración (en segundos), Tamaño del fichero (en KB). Un programa debe pedir los
datos de una canción al usuario, almacenarlos en dicho “struct” y después mostrarlos
en pantalla.
44 ' -
Hemos guardado varios datos de una persona. Se pueden almacenar los de
si combinamos el uso de los “struct” con las tablas (arrays) que vimos anteriormente. Por
ejemplo, si queremos guardar los datos de 100 alumnos podríamos hacer:
"#
.$%%/
La inicial del primer alumno sería “alumnos[0].inicial”, y la edad del último sería
“alumnos[99].edad”.
(
• Ampliar el programa del apartado 5.5.1, para que almacene datos de hasta 100
canciones. Deberá tener un menú que permita las opciones: añadir una nueva canción,
mostrar el título de todas las canciones, buscar la canción que contenga un cierto texto
(en el artista o en el título).
• Un programa que permita guardar datos de "imágenes" (ficheros de ordenador que
contengan fotografías o cualquier otro tipo de información gráfica). De cada imagen se
debe guardar: nombre (texto), ancho en píxeles (por ejemplo 2000), alto en píxeles
(por ejemplo, 3000), tamaño en Kb (por ejemplo 145,6). El programa debe ser capaz
de almacenar hasta 700 imágenes (deberá avisar cuando su capacidad esté llena).
Debe permitir las opciones: añadir una ficha nueva, ver todas las fichas (número y
nombre de cada imagen), buscar la ficha que tenga un cierto nombre.
44 @
Podemos encontrarnos con un registo que tenga varios datos, y que a su vez ocurra que uno de
esos datos esté formado por varios datos más sencillos. Para hacerlo desde C, incluiríamos un
“struct” dentro de otro, así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F+4 A:
:A %F+ A:
:A A:
Revisión 0.90– Página 87
:A " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! 7
"#
7 R
H F
! . =!
(
• Ampliar el programa del primer apartado de 5.5.2, para que el campo “duración” se
almacene como minutos y segundos, usando un “struct” anidado que contenga a su vez
estos dos campos.
)+
Vamos a hacer un ejemplo completo que use tablas (“arrays=”), registros (“struct”) y que
además manipule cadenas.
La idea va a ser la siguiente: Crearemos un programa que pueda almacenar datos de hasta
1000 ficheros (archivos de ordenador). Para cada fichero, debe guardar los siguientes datos:
Nombre del fichero (max 40 letras), Tamaño (en KB, número de 0 a 2.000.000.000). El
programa mostrará un menú que permita al usuario las siguientes operaciones:
No debería resultar difícil. Vamos a ver directamente una de las formas en que se podría
plantear y luego comentaremos alguna de las mejoras que se podría (incluso se debería) hacer.
El resto del programa no es difícil: sabemos leer y comparar textos y números. Sólo haremos
tres consideraciones:
• Los textos (nombre del fichero, por ejemplo) pueden contener espacios, por lo que
usaremos “gets” en vez de “scanf”.
• Es " # ; " # % " #: si leemos un número con
“scanf”, la pulsación de la tecla “Intro” posterior se queda en el buffer del teclado, lo
que puede provocar que después intentemos leer con “gets=” un texto, pero sólo leamos
esa pulsación de la tecla “Intro”. Para evitarlo, los números los leeremos “en dos
etapas=”: primero leeremos una cadena con “gets=” y go lue la convertiremos a número
con “sscanf”.
• Hemos limitado el número de fichas a 1000, así que, si nos piden añadir, deberíamos
asegurarnos antes de que todavía tenemos hueco disponible.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F;4 A:
:A %F; A:
:A A:
:A ' A:
:A 1 0 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
8 % :A J ! M 1 A:
:A ' A:
:A . M 0 A:
:A B A:
! ? 0 3 4W
! $ @ 5K 6 ! W
! + @ B ' ! W
! ; @ B ! M K W
! , @ X ! W
! F @ W
:A 6 ' >
" 1 " ! ! A:
" 2
! 2 =
:A "J 3 " A:
$, :A 5K 6 A:
" 8 ) $%%% :A M A:
! ' ! 4
" ! . 8 / ' 8
! K aS4
" 2
! 2 = ! . 8 / 1
:A N 1 ! L A:
8
# :A 1 L ! > 6 A:
! BL2 ! I $%%% YW
-
+, :A B A:
" % ) 8
! ' 4 = K 4 = a'W
! . / ' 8 ! . / 1
-
;, :A B "J K A:
! b5 M K M M Z
" 2
! 2 =
" % ) 8
" ! . / 1 '
! ' 4 = K 4 = a'W
! . / ' 8 ! . / 1
-
,, :A X ! A:
! b7 M / ! M 6 Z
" 2
" % ) 8
" ! . / ' 8 2 %
! ' 4 = K 4 = a'W
! . / ' 8 ! . / 1
-
F, :A 4 6 M A:
! 8 " W
-
" # , :A T 4 6L A:
! T 3 YW
-
# ( F :A F> A:
(
• Un programa que pida el nombre, el apellido y la edad de una persona, los almacene
en un “struct” y luego muestre los tres datos en una misma línea, separados por
comas.
• Un programa que pida datos de 8 personas: nombre, dia de nacimiento, mes de
nacimiento, y año de nacimiento (que se deben almacenar en una tabla de structs).
Después deberá repetir lo siguiente: preguntar un número de mes y mostrar en
pantalla los datos de las personas que cumplan los años durante ese mes. Terminará
de repetirse cuando se teclee 0 como número de mes.
• Un programa que sea capaz de almacenar los datos de hasta 50 personas: nombre,
dirección, teléfono, edad (usando una tabla de structs). Deberá ir pidiendo los datos
uno por uno, hasta que un nombre se introduzca vacío (se pulse Intro sin teclear
nada). Entonces deberá aparecer un menú que permita:
Mostrar la lista de todos los nombres.
Mostrar las personas de una cierta edad.
Mostrar las personas cuya inicial sea la que el usuario indique.
Salir del programa
(lógicamente, este menú debe repetirse hasta que se escoja la opción de “salir”).
• Mejorar la base de datos de ficheros (ejemplo 53) para que no permita introducir
tamaños incorrectos (números negativos) ni nombres de fichero vacíos.
• Ampliar la base de datos de ficheros (ejemplo 53) para que incluya una opción de
búsqueda parcial, en la que el usuario indique parte del nombre y se muestre todos los
ficheros que contienen ese fragmento (usando “strstr”).
• Ampliar la base de datos de ficheros (ejemplo 53) para que se pueda borrar un cierto
dato (habrá que “mover hacia atrás=” todos los datos quehabía después de ese, y
disminuir el contador de la cantidad de datos que tenemos).
• Mejorar la base de datos de ficheros (ejemplo 53) para que se pueda modificar un
cierto dato a partir de su número (por ejemplo, el dato número 3). En esa modificación,
se deberá permitir al usuario pulsar Intro sin teclear nada, para indicar que no desea
modificar un cierto dato, en vez de reemplazarlo por una cadena vacía.
• Ampliar la base de datos de ficheros (ejemplo 53) para que se permita ordenar los
datos por nombre. Para ello, deberás buscar información sobre algún método de
ordenación sencillo, como el "método de burbuja" (en el siguiente apartado tienes
algunos), y aplicarlo a este caso concreto.
Existen ligeras mejoras (por ejemplo, cambiar uno de los “for” por un “while”, para no repasar
todos los datos si ya estaban parcialmente ordenados), así como métodos claramente más
efectivos, pero más difíciles de programar, alguno de los cuales veremos más adelante.
Veremos tres de estos métodos simples de ordenación, primero mirando la apariencia que tiene
el algoritmo, y luego juntando los tres en un ejemplo que los pruebe:
)
(Intercambiar cada pareja consecutiva que no esté ordenada)
(Nota: algunos autores hacen el bucle exterior creciente y otros decreciente, así:)
(Comparar cada elemento con los anteriores Vque ya están ordenadosV y desplazarlo hasta su
posición correcta).
(Es mejorable, no intercambiando el dato que se mueve con cada elemento, sino sólo al final de
cada pasada, pero no entraremos en más detalles).
(
• Un programa que cree un array de 7 números enteros y lo ordene con cada uno de
estos tres métodos, mostrando el resultado de los pasos intermedios.
• Abrir el fichero.
• Leer datos de él o escribir datos en él.
• Cerrar el fichero.
Eso sí, no siempre podremos realizar esas operaciones, así que además tendremos que
comprobar los posibles errores. Por ejemplo, puede ocurrir que intentemos abrir un fichero que
realmente no exista, o que queramos escribir en un dispositivo que sea sólo de lectura.
Vamos a ver un ejemplo, que cree un fichero de texto y escriba algo en él:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q FF4 A:
:A %FF A:
:A A:
:A ? ! A:
:A 2 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
8 .?& !
! ! ' 2 [
! ? O W !
! ? !
! 1 3 W !
! !
(
• Crea un programa que vaya leyendo las frases que el usuario teclea y las guarde en un
fichero de texto llamado “registroDeUsuario.txt”. Terminará cuando la frase introducida
sea “fin” (esa frase no deberá guardarse en el fichero).
+ * 5
Si queremos ! , los pasos son muy parecidos, sólo que lo abriremos para
lectura (el modo de escritura tendrá una “r”, de “read”, en lugar de “w”), y leeremos con
“fgets=”:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q FG4 A:
:A %FG A:
:A A:
:A . ! A:
:A 2 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
8 .?& !
' .R%/ 4WW 2 '
.R$/
! ! '
" ! V..
! 2 ! YW
2 $
!" R% !
! !
• En el nombre del fichero, hemos indicado un nombre algo más complejo. En estos
casos, hay que recordar que si aparece alguna barra invertida (\), deberemos
duplicarla, porque la barra invertida se usa para indicar ciertos códigos de control. Por
ejemplo, \n es el código de avance de línea y \a es un pitido. El modo de lectura en
este caso es “r” para indicar que queremos leer (read) del fichero, y “t” avisa de que es
un fichero de texto.
Revisión 0.90– Página 95
• Para del fichero y usaremos “fgets”, que se parece mucho a “gets=”, pero podemos
limitar la longitud del texto que leemos (en este ejemplo, a 80 caracteres) desde el
fichero. Esta cadena de texto los caracteres de avance de línea.
• Si abrir el fichero, se nos devolverá un valor especial llamado NULL
(que también veremos con mayor detalle más adelante, cuando hablemos de
punteros).
• La orden “ $ ” es la que nos permite abandonar el programa en un punto. La veremos
con más detalle un poco más adelante.
(
• Crea un programa que lea las tres primeras líneas del fichero creado en el apartado
anterior y las muestre en pantalla. Si el fichero no existe, se deberá mostrar un aviso.
+ 5 * * 5
Normalmente no querremos leer sólo una frase del fichero, sino procesar todo su contenido.
Para ayudarnos, tenemos una orden que nos permite saber si ya hemos llegado al final del
fichero. Es “feof” (EOF es la abreviatura de End Of File, fin de fichero).
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q FH4 A:
:A %FH A:
:A A:
:A . ! A:
:A ! 2 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
8 .?& !
' .R%/ 4WW 2 '
.R$/
! ! '
" ! V..
! 2 ! YW
2 $
# ( ! ! !
!" R% !
! !
Esa será la estructura básica de casi cualquier programa que deba leer un fichero completo, de
principio a fin: abrir, comprobar que se ha podido acceder correctamente, leer con “while
!(feof(…))” y cerrar.
Revisión 0.90– Página 96
(
• Un programa que pida al usuario que teclee frases, y las almacene en el fichero
“frases.txt”. Acabará cuando el usuario pulse Intro sin teclear nada. Después deberá
mostrar el contenido del fichero.
• Un programa que pregunte un nombre de fichero y muestre en pantalla el contenido de
ese fichero, haciendo una pausa después de cada 25 líneas, para que dé tiempo a
leerlo. Cuando el usuario pulse intro, se mostrarán las siguientes 25 líneas, y así hasta
que termine el fichero. (Pista: puedes usar un contador, volverlo a poner a cero tras
cada 25 líneas o bien comprobar si has avanzado otras 25 líneas usando la operación
“resto de la división”, y hacer la pausa con “getchar()”).
+& 8 5
Es frecuente que los ficheros que queramos manejar no sean de texto, pero que aun así tengan
un formato bastante definido. Por ejemplo, podemos querer crear una agenda, en la que los
datos de cada persona estén guardados en un “struct”. En este caso, podríamos guardar los
datos usando “fprintf” y “fscanf”, análogos a “printf” y “scanf” que ya conocemos.
Como se puede ver en este ejemplo, suele ser recomendable indicar la anchura que debe tener
cada dato cuando guardamos con “fprintf”, para que se pueda recuperar después de la misma
forma con “fscanf”.
Aun así, “fscanf” tiene el mismo problema que “scanf”: si leemos una cadena de texto, la
considera terminada después del primer espacio en blanco, y lo que haya a continuación lo
asignará a la siguiente cadena. Por eso, cuando manejemos textos con espacios, será preferible
usar “fgets=” o bien otras dos órdenes para manejo de fic heros que veremos un poco más
adelante.
(
• Crear un “struct” que almacene los siguientes datos de una persona: nombre, edad,
ciudad de residencia. Pedir al usuario esos datos de una persona y guardarlos en un
fichero llamado “gente.dat”. Cerrar el fichero, volverlo a abrir para lectura y mostrar los
datos que se habían guardado.
• Ampliar el programa anterior para que use un “array de structs=”, de forma que se
puedan tener datos de 10 personas. Se deberá pedir al usuario los datos de las 10
personas y guardarlos en el fichero. Después se pedirá al usuario un número del 1 al 10
y se mostrarán los datos de la persona indicada por ese número, que se deberán leer
de fichero (1 será la primera ficha, y 10 será la última). Por ejemplo, si el usuario indica
que quiere ver los datos de la persona 3 (tercera), se deberá leer las dos primeras,
ignorando su contenido, y después leer la tercera, que sí se deberá mostrar.
• Una agenda que maneje los siguientes datos: nombre, dirección, tlf móvil, email, y día,
mes y año de nacimiento (estos tres últimos datos deberán ser números enteros
Revisión 0.90– Página 97
cortos). Deberá tener capacidad para 100 fichas. Se deberá poder añadir un dato
nuevo, visualizar los nombres de las fichas existentes, o mostrar todos los datos de una
persona (se preguntará al usuario cual es el nombre de esa persona que quiere
visualizar). Al empezar el programa, leerá los datos de un fichero llamado “agenda.dat”
(si existe). Al terminar, guardará todos los datos en ese fichero.
+)
Si queremos leer o escribir , tenemos las órdenes "fgetc" y "fputc", que se usan:
E !" !
! > !
++"
Antes de seguir, vamos a ver las letras que pueden aparecer en el del
fichero, para poder añadir datos a un fichero ya existente:
>
r Abrir sólo para lectura.
w Crear para escribir. Sobreescribe el fichero si existiera ya (borrando el
original).
a Añade al final del fichero si existe, o lo crea si no existe.
+ Se escribe a continuación de los modos anteriores para indicar que
también queremos modificar. Por ejemplo: r+ permite leer y modificar el
fichero.
t Abrir en modo de texto.
b Abrir en modo binario.
(
• Un programa que pida al usuario que teclee frases, y las almacene en el fichero
“registro.txt”, que puede existir anteriormente (y que no deberá borrarse, sino añadir al
final de su contenido). Cada sesión acabará cuando el usuario pulse Intro sin teclear
nada.
• Crear un programa que pida al usuario pares de números enteros y escriba su suma
(con el formato “20 + 3 = 23”) en pantalla y en un fichero llamado “sumas.txt”, que se
encontrará en un subdirectorio llamado “resultados”. Cada vez que se ejecute el
programa, deberá añadir los nuevos resultados a continuación de los resultados de las
ejecuciones anteriores.
+,8 5
Hasta ahora nos hemos centrado en los ficheros de texto, que son sencillos de crear y de leer.
Pero también podemos manejar ficheros que contengan información de cualquier tipo.
Por ejemplo, para leer 10 números enteros de un fichero (cada uno de los cuales ocuparía 4
bytes, si estamos en un sistema operativo de 32 bits), haríamos
^$%_
! P > ,> $%> !
! $%
! 'O $% Y
Al igual que ocurría con “scanf”, la variable en la que guardemos los datos se deberá indicar
precedida del símbolo &, por motivos con detalle que veremos cuando hablemos sobre
punteros. También al igual que pasaba con “scanf”, si se trata de una cadena de caracteres
(bien porque vayamos a leer una cadena de texto, o bien porque queramos leer datos de
cualquier tipo pero con la intención de manejarlos byte a byte), como ^F%%_ no será
necesario indicar ese símbolo &, como en este ejemplo:
' ^,%_
! ' > $> ,%> !
! ,%
! 8 ! > L ' Y
+9 ( * 5
Vamos a ver un ejemplo, que duplique un fichero de cualquier tipo (no necesariamente de
texto), y después veremos las novedades:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q FR4 A:
:A %FR A:
:A A:
:A - ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 5 ! " A:
! I ' ! T " 4
! = ' T "
" ! T " ! ' T " ' V..
! 2 ! " YW
2 $
:A N A:
! I ' ! 7 4
! = ' 7
" ! 7 ! ' 7 [' V..
! ! YW
2 $
:A B M " M A:
# ( ! ! ! T "
:A - ! A:
! ! T "
! ! 7
Los cambios con relación a lo que conocíamos de ficheros de texto son los siguientes:
• Los ficheros pueden no ser de texto, de modo que leemos uno como fichero binario
(con “rb”) y escribimos el otro también como fichero binario (con “wb”).
• Definimos un buffer de 2048 bytes (2 K), para ir leyendo la información por bloques (y
guardando después cada bloque en el otro fichero).
Esto puede resultar menos legible que hacerlo en dos líneas separadas, como hemos
hecho hasta ahora, pero es más compacto, y, sobre todo, muy frecuente encontrarlo en
“fuentes ajenos” más avanzados, como los que se puedan encontrar en Internet o
cuando se programe en grupo con otras personas, de modo que he considerado
adecuado incluirlo.
• A “fread” le decimos que queremos leer 2048 datos de 1 byte cada uno, y él nos
devuelve la cantidad de bytes que ha leído realmente. Para que el fuente sea más fácil
Revisión 0.90– Página 100
de aplicar a otros casos en los que no sean bloques de 2048 bytes exactamente, suele
ser preferible indicar que queremos leer el tamaño del bloque, usando “sizeof”:
Cuando la cantidad leida sea menos de 2048 bytes, es que el fichero se ha acabado (lo
podemos comprobar mirando esta cantidad o con “feof”).
• “fwrite” se maneja igual que fread: se le indica dónde están los datos, el tamaño de
cada dato, cuantos datos hay que escribir y en qué fichero almacenarlos. En nuestro
ejemplo, el número de bytes que debe escribir será el que haya leido:
![ ' !! $ ! 7
(
• Mejorar la agenda anterior, para guardar y leer cada “ficha” (struct) de una vez, usando
fwrite/fread y sizeof, como en el último ejemplo.
• Crear un “struct” que almacene los siguientes datos de una persona: nombre, edad,
ciudad de residencia. Pedir al usuario esos datos de una persona y guardarlos en un
fichero llamado “gente.dat”, usando “fwrite”. Cerrar el fichero, volverlo a abrir para
lectura y mostrar los datos que se habían guardado, que se deben leer con “fread”.
• Ampliar el programa anterior para que use un “array de structs=”, de forma que se
puedan tener datos de 10 personas. Se deberá pedir al usuario los datos de las 10
personas y guardarlos en el fichero, usando “fwrite”. Después se pedirá al usuario un
número del 1 al 10 y se mostrarán los datos de la persona indicada por ese número,
que se deberán leer de fichero (1 será la primera ficha, y 10 será la última). Por
ejemplo, si el usuario indica que quiere ver los datos de la persona 3 (tercera), se
deberá leer las dos primeras (con “fread”), ignorando su contenido, y después leer la
tercera, que sí se deberá mostrar.
+:7 ; * 5
Cuando trabajamos con un fichero, es posible que necesitemos directamente a una
cierta posición del mismo. Para ello usamos “ +”, que tiene el formato:
01123014 % 4
01123567 $ 4 5
01123189 + 4 8
Esta orden nos permite saber también la de un fichero: nos posicionamos primero al
final con “fseek” y luego comprobamos con “ftell” en qué posición estamos:
! # ! % ??a&? 7
" ! !
:
• Ampliar el programa anterior (el “array de structs” con 10 personas) para que el dato
que indique el usuario se lea sin leer y descartar antes los que le preceden, sino que se
salte directamente a la ficha deseada usando “fseek”.
+ ( * * 5 <"
Ahora vamos a ver un ejemplo un poco más sofisticado: vamos a abrir un fichero que sea una
imagen en formato BMP y a mostrar en pantalla si está comprimido o no.
Para eso necesitamos antes saber cómo se guarda la información en un fichero BMP, pero esto
es algo fácil de localizar:
< ? @3 A)B
Un fichero BMP está compuesto por las siguientes partes: una cabecera de
fichero, una cabecera del bitmap, una tabla de colores y los bytes que
definirán la imagen.
Con esta información nos basta para nuestro propósito: la compresión se indica en la posición
30 del fichero, ocupa 4 bytes (lo mismo que un “int” en los sistemas operativos de 32 bits), y si
es un 0 querrá decir que la imagen no está comprimida.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q F*4 A:
:A %F* A:
:A A:
:A ! 3 ' A:
:A ! SB $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' .G%/
8 .?& !
- ' L" SB W
! 7 ' ! 4
" '
! ! ' '
" ! V..
W
#
! # ! ;% ??a& ?
! $ , !
! !
" EE %
3
#
SB -
Ya que estamos, podemos mejorarlo un poco para que además nos muestre el ancho y el alto
de la imagen, y que compruebe antes si realmente se trata de un fichero BMP:
Revisión 0.90– Página 103
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G%4 A:
:A %G% A:
:A A:
:A ! 3 ' A:
:A ! SB + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' .G%/
8 .?& !
$ +
- ' L" SB W
! 7 ' ! 4
" '
! ! ' '
" ! V..
W
#
$ !" ! :A . '1 A:
+ !" !
" $ S + B :A SB A:
! B ! 4 = = W
$ +
! # ! $R ??a& ? :A 3 $R4 A:
! $ , !
! 5 4 = W
! $ , ! :A " 4 A:
! 5 4 = W
! # ! , ??a&-V :A , '1 / 4 3 A:
! $ , !
! !
%, 3 -
$, - 3 .? R ' -
+, - 3 .? , ' -
#
! ! SB W :A SB A:
(
• Mejorar la última versión de la agenda anterior (la que usa fwrite, fread y sizeof) para
que no lea todas las fichas a la vez, sino que lea una única ficha del disco cada vez que
lo necesite, saltando a la posición en que se encuentra dicha ficha con “fseek”.
• Hacer un programa que muestre información sobre una imagen en formato GIF (se
deberá localizar en Internet los detalles sobre dicho formato): versión, ancho de la
imagen (en píxeles), alto de la imagen y cantidad de colores.
• Hacer un programa que muestre información sobre una imagen en formato PCX: ancho
de la imagen (en píxeles), alto de la imagen y cantidad de colores.
Revisión 0.90– Página 104
• Mejorar la base de datos de ficheros (ejemplo 53) para que los datos se guarden en
disco al terminar la sesión de uso, y se lean de disco cuando comienza una nueva
sesión.
• Mejorar la base de datos de ficheros (ejemplo 53) para que cada dato introducido se
guarde inmediatamente en disco, sin esperar a que termine la sesión de uso. En vez de
emplear un “array de structs=”, debe existir un solo “str uct” en memoria cada vez, y
para las búsquedas se recorra todo el contenido del fichero.
• Mejorar el ejercicio anterior (ejemplo 53 ampliado con ficheros, que se manejan ficha a
ficha) para que se pueda modificar un cierto dato a partir de su número (por ejemplo,
el dato número 3). En esa modificación, se deberá permitir al usuario pulsar Intro sin
teclear nada, para indicar que no desea modificar un cierto dato, en vez de
reemplazarlo por una cadena vacía.
+ 8 5 (
Mandar algo a impresora desde C no es difícil (al menos en principio): en muchos sistemas
operativos, la impresora es un dispositivo al que se puede acceder a través como si se tratara
de un fichero.
N ? 75 T U
N ? 75 T U 4
De igual manera, desde C podríamos crear un programa que mandara información al fichero
ficticio PRN: para escribir en impresora, así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G$4 A:
:A %G$ A:
:A A:
:A ? A:
:A B 7 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
8 .?&
! 4 [
! ? 6 W
!
En Linux la idea sería la misma, pero el nombre de dispositivo sería “/dev/lp”. Como
inconveniente, normalmente sólo puede escribir en este dispositivo el administrador y los
usuarios que pertenezcan a su grupo. Si pertenecemos a ese grupo, haríamos:
! : 6: [
+ 8 5 (
Hemos comentado que en muchos sistemas operativos se puede usar el símbolo “>” para
redirigir hacia “otro sitio” (la impresora o un fichero de texto, por ejemplo) la información que
iba destinada originalmente a la pantalla. Esto funciona, entre otros, en Windows, MsDos y toda
la familia de sistemas operativos Unix (incluido Linux).
Pero en el caso de Linux (y los Unix en general) podemos redirigir además los mensajes de
error hacia otro sitio distinto del resto de mensajes (que iban destinados a pantalla). Esto se
consigue con el símbolo “2>” :
6 2 + 2
Esta política de separar los mensajes de información y los mensajes de error es fácil de llevar a
nuestros programas. Basta con que los mensajes de error no los mandemos a pantalla con
órdenes como “printf”, sino que los mandemos a un fichero especial llamado “stderr” (salida
estándar de errores).
! ! 0 2
" ! V..
! ! > 8 YW
#
! 5 ! W
Si el usuario de nuestro programa no usa “2>”, los mensajes de error le aparecerían en pantalla
junto con cualquier otro mensaje, pero si se trata de un usuario avanzado, le estamos dando la
posibilidad de analizar los errores cómodamente.
+ = ( .7> "
Los ficheros de sonido en formato MP3 pueden contener información sobre el autor, el título,
etc. Si la contienen, se encontraría a 128 bytes del final del fichero. Los primeros 3 bytes de
esos 128 deberían ser las letras TAG. A continuación, tendríamos otros 30 bytes que serían el
Revisión 0.90– Página 106
título de la canción, y otros 30 bytes que serían el nombre del autor. Con esto ya podríamos
crear un programa que lea esa información de un fichero MP3 (si la contiene) e incluso que la
modifique.
Estos textos (título, autor y otros) deberían estar rellenos con caracteres nulos al final, pero es
algo de lo que no tenemos la certeza, porque algunas aplicaciones lo rellenan con espacios (es
el caso de alguna versión de WinAmp). Por eso, leeremos los datos con “fread” y añadiremos
un carácter nulo al final de cada uno.
Además, haremos que el programa nos muestre la información de varios ficheros: nos pedirá
un nombre, y luego otro, y así sucesivamente hasta que pulsemos Intro sin teclear nada más.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G+4 A:
:A %G+ A:
:A A:
:A . 1 A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
8 .?& !
.;$/
:A ' ! A:
! W ? ' ' ! B ; ' 4
"
:A > / A:
" %
W 5 ! I
:A 2 ' > ' A:
# " ! ! <' ( V..
:A ' > ' A:
! ' ! 4 = W
:A B K ! A:
! # ! % ??a&? 7
! K 4 = W ! !
:A 5 $+R '1 L 5c A:
! # ! !$+R ??a&? 7
! ; $ !
:A ; > K ! A:
.;/ W%
" 5c ( %
3 ! 3 6L
#
:A 2 > A:
:A > ;% A:
! ;% $ !
. / W%
! 4 = W
:A . " ;% A:
, - / (-
Hasta ahora hemos estado pensando los pasos que deberíamos dar para resolver un cierto
problema, y hemos creado programas a partir de cada uno de esos pasos. Esto es razonable
cuando los problemas son sencillos, pero puede no ser la mejor forma de actuar cuando se
trata de algo más complicado.
A partir de ahora vamos a empezar a intentar descomponer los problemas en trozos más
pequeños, que sean más fáciles de resolver. Esto nos puede suponer varias ventajas:
• Cada “trozo de programa” independiente será más fácil de programar, al realizar una
función breve y concreta.
• El “programa principal” será más fácil de leer, porque no necesitará contener todos los
detalles de cómo se hace cada cosa.
• Podremos repartir el trabajo, para que cada persona se encargue de realizar un “trozo
de programa”, y finalmente se integrará el trabajo individual de cada persona.
Esos “trozos=” de programa son lo que suele llamar “subrut inas”, “procedimientos=” o
“funciones”. En el lenguaje C, el nombre que más se usa es el de .
, ! % *
En C, todos los “trozos de programa” son funciones, incluyendo el propio cuerpo de programa,
. De hecho, la forma básica de una función será indicando su nombre seguido de
unos paréntesis vacíos, como hacíamos con “main”. Después, entre llaves indicaremos todos los
pasos que queremos que dé ese “trozo de programa”.
Por ejemplo, podríamos crear una función llamada “saludar”, que escribiera varios mensajes en
la pantalla:
! S 6 " W
! 0 W
! S 6 " W
Ahora desde dentro del cuerpo de nuestro programa, podríamos “ ” a esa función:
Así conseguimos que nuestro programa sea más fácil de leer. Como ejemplo, la parte principal
de nuestra agenda podría ser simplemente:
B
T
$, ' 7 -
+, ! 7 -
;, 7 -
d
, % *
Es muy frecuente que nos interese además indicarle a nuestra función ciertos datos especiales
con los que queremos que trabaje. Por ejemplo, si escribimos en pantalla números reales con
frecuencia, nos puede resultar útil que nos los muestre con el formato que nos interese. Lo
podríamos hacer así:
' "#
! =, +!
"# 2
2 F $
! ? 4
' 2
! 1 4
' + ;
Estos datos adicionales que indicamos a la función es lo que llamaremos sus “parámetros=”.
Como se ve en el ejemplo, tenemos que indicar un nombre para cada parámetro (puede haber
varios) y el tipo de datos que corresponde a ese parámetro. Si hay más de un parámetro,
deberemos indicar el tipo y el nombre para cada uno de ellos:
2 1
,& ? *
También es habitual que queramos que nuestra función realice una serie de cálculos y nos
“devuelva” el resultado de esos cálculos, para poderlo usar desde cualquier otra parte de
nuestro programa. Por ejemplo, podríamos crear una función para elevar un número entero al
cuadrado así:
&
! ? =
! 1 ; = ;
Podemos hacer una función que nos diga cual es el mayor de dos números reales así:
(
• Crear una función que borre la pantalla dibujando 25 líneas en blanco. No debe
devolver ningún valor.
• Crear una función que calcule el cubo de un número real (float). El resultado deberá ser
otro número real. Probar esta función para calcular el cubo de 3.2 y el de 5.
• Crear una función que calcule cual es el menor de dos números enteros. El resultado
será otro número entero.
• Crear una función llamada “signo”, que reciba un número real, y devuelva un número
entero con el valor: V1 si el número es negativo, 1 si es positivo o 0 si es cero.
• Crear una función que devuelva la primera letra de una cadena de texto. Probar esta
función para calcular la primera letra de la frase “Hola”
• Crear una función que devuelva la última letra de una cadena de texto. Probar esta
función para calcular la última letra de la frase “Hola”.
,) @ A @ A
Cuando queremos dejar claro que una función no tiene que devolver ningún valor, podemos
hacerlo indicando al principio que el tipo de datos va a ser “void” (nulo). Por ejemplo, nuestra
función “saludar”, que se limitaba a escribir varios textos en pantalla, quedaría más correcta si
fuera así:
! S 6 " W
! 0 W
! S 6 " W
Hay que tener en cuenta que si no indicamos tipo de datos, el lenguaje C no supondrá que no
vaya a devolver ningún valor, sino que devolverá un valor entero (int). De hecho, la forma
habitual de declarar el cuerpo de un programa (“main”) sería ésta, que es equivalente a la que
hemos estado usando:
Eso quiere decir que “main” también puede devolver un valor, que se leerá desde fuera de
nuestro programa. Lo habitual es devolver 0 si todo ha funcionado correctamente
%
]
8 .?& !
! ! ' 2
" ! V.. $
Este valor se podría comprobar desde el sistema operativo. Por ejemplo, en MsDos y Windows
se lee con “IF ERRORLEVEL”, así:
(
• Crear una función que borre la pantalla dibujando 25 líneas en blanco. No debe
devolver ningún valor.
• Crear una función que reciba un número y muestre en pantalla el perímetro y la
superficie de un cuadrado que tenga como lado el número que se ha indicado como
parámetro.
,+ ?
Hasta ahora, hemos declarado las variables antes de “main”. Ahora nuestros programas tienen
varios “bloques=”, así que se comportarán de forma distin ta según donde declaremos las
variables.
Por el contrario, si declaramos una variable al comienzo del programa, fuera de todos los
“bloques” de programa, será una “ ”, a la que se podrá acceder desde cualquier
parte.
Vamos a verlo con un ejemplo. Crearemos una función que calcule la potencia de un número
entero (un número elevado a otro), y el cuerpo del programa que la use.
; 6 F E ; e ; e ; e ; e ;
(multiplicamos 5 veces el 3 por sí mismo). En general, como nos pueden pedir cosas como "6
elevado a 100" (o en general números que pueden ser grandes), usaremos la orden "for" para
multiplicar tantas veces como haga falta:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G;4 A:
:A %G; A:
:A A:
:A ?0 ! 3 A:
:A 6 ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
' 2
$ :A X M 6 1 A:
:A ' A:
" $ ) 2 :A B 6 A:
& ' :A N 6 A:
:A > A:
:A ' " 6 M ' ' A:
$ +
! I ' 4
! = $
! I 2 4
! = +
! = 6 = 6 = $ + $ +
En este caso, las variables “temporal” e “i” son locales a la función “potencia”: para “main” no
existen. Si en “main” intentáramos hacer i=5; obtendríamos un mensaje de error.
De igual modo, “num1” y “num2” son locales para “main”: desde la función “potencia” no
podemos acceder a su valor (ni para leerlo ni para modificarlo), sólo desde “main”.
:
• Crear una función “pedirEntero”, que reciba como parámetros el texto que se debe
mostrar en pantalla, el valor mínimo aceptable y el valor máximo aceptable. Deberá
pedir al usuario que introduzca el valor tantas veces como sea necesario, volvérselo a
pedir en caso de error, y devolver un valor correcto. Probarlo con un programa que
pida al usuario un año entre 1800 y 2100.
• Crear una función “escribirTablaMultiplicar”, que reciba como parámetro un número
entero, y escriba la tabla de multiplicar de ese número (por ejemplo, para el 3 deberá
llegar desde 3x0=0 hasta 3x10=30).
• Crear una función “esPrimo”, que reciba un número y devuelva el valor 1 si es un
número primo o 0 en caso contrario.
• Crear una función que reciba una cadena y una letra, y devuelva la cantidad de veces
que dicha letra aparece en la cadena. Por ejemplo, si la cadena es "Barcelona" y la letra
es 'a', debería devolver 2 (aparece 2 veces).
• Crear una función que reciba un número cualquiera y que devuelva como resultado la
suma de sus dígitos. Por ejemplo, si el número fuera 123 la suma sería 6.
• Crear una función que reciba una letra y un número, y escriba un “triángulo” formado
por esa letra, que tenga como anchura inicial la que se ha indicado. Por ejemplo, si la
letra es * y la anchura es 4, debería escribir
****
***
**
*
,, *
¿Qué ocurre si damos el mismo nombre a dos variables locales? Vamos a comprobarlo con un
ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G,4 A:
:A %G, A:
:A A:
:A 7 6 ' A:
:A ' A:
:A A:
:A - -> A:
:A - ' A:
& +
F
! 6 = W
! 5 6 = W
6 F
5 6 F
¿Por qué? Sencillo: tenemos una variable local dentro de “duplica” y otra dentro de “main”. El
hecho de que las dos tengan el mismo nombre no afecta al funcionamiento del programa,
siguen siendo distintas. El programa se comporta como si “duplica” fuera así:
2
2 2 & +
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q GF4 A:
:A %GF A:
:A A:
:A X ' 1 A:
:A " ' A:
:A ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
& +
! 6 = W
! 5 6 = W
¿Y si queremos que se pueda modificar un dato indicado como parámetro? Todavía no sabemos
como hacerlo (lo veremos en el próximo tema). Por ahora sólo sabemos hacerlo devolviendo el
nuevo valor con “return”, con lo que nuestro último ejemplo quedaría así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q GG4 A:
:A %GG A:
:A A:
:A B ! 6 ' A:
:A L 4 A:
:A 6 6 6 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
& +
F
! 6 = W
! 5 6 = W
,9
En general, una función debe estar declarada antes de usarse. Por ejemplo, este fuente daría
un error en muchos compiladores, porque dentro de “main” intentamos usar algo llamado
“duplica”, que no se ha mencionado antes:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q GH4 A:
:A %GH A:
:A A:
:A 8 3 4 A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"# F
! 6 =!W
! 5 6 =!W
La forma de evitarlo es colocar la definición de las funciones antes de usarlas (si se puede) o
bien incluir al menos su “ ”, la cabecera de la función sin incluir los detalles de cómo
trabaja internamente, así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q GR4 A:
:A %GR A:
:A A:
:A ! 3 A:
:A M A:
:A ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"# "#
"# F
! 6 =!W
! 5 6 =!W
"# "#
& +
F
! 6 = W
! 5 6 = W
& +
,: 7 * #
8; M&
En un programa de gestión o una utilidad que nos ayuda a administrar un sistema, no es
habitual que podamos permitir que las cosas ocurran al azar. Pero los juegos se encuentran
muchas veces entre los ejercicios de programación más completos, y para un juego sí suele ser
Revisión 0.90– Página 117
conveniente que haya algo de azar, para que una partida no sea exactamente igual a la
anterior.
Pero todavía nos queda un detalle para que los números aleatorios que obtengamos sean
“razonables=”: los números que genera un ordenador no so n realmente al azar, sino “pseudoV
aleatorios=”, cada uno calculado a partir del siguient
e. Podemos elegir cual queremos que sea el
primer número de esa serie (la “semilla”), pero si usamos uno prefijado, los números que se
generarán serán siempre los mismos. Por eso, será conveniente que el primer número se base
en el reloj interno del ordenador: como es casi imposible que el programa se ponga en marcha
dos días exactamente a la misma hora (incluyendo milésimas de segundo), la serie de números
al azar que obtengamos será distinta cada vez.
La “semilla” la indicamos con “srand”, y si queremos basarnos en el reloj interno del ordenador,
lo que haremos será % antes de hacer ninguna llamada a “rand()”.
Para usar “rand()” y “srand()”, deberíamos añadir otro fichero a nuestra lista de “includes”, el
llamado “stdlib”:
'
Si además queremos que la semilla se tome a partir del reloj interno del ordenador (que es lo
más razonable), deberemos incluir también “time”:
Vamos a ver un ejemplo, que muestre en pantalla un número al azar entre 1 y 10:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G*4 A:
:A %G* A:
:A A:
:A T' J A:
:A I A:
:A A:
:A - -> A:
:A - ' A:
Revisión 0.90– Página 118
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
%
: $% $
! V J $ 1 $%4 = W
(
• Crear un programa que genere un número al azar entre 1 y 100. El usuario tendrá 6
oportunidades para acertarlo.
8; :
Dentro del fichero de cabecera “ ! !” tenemos acceso a muchas funciones matemáticas
predefinidas en C, como:
(
• Crear un programa que halle cualquier raíz de un número. El usuario deberá indicar el
número (por ejemplo, 2) y el índice de la raiz (por ejemplo, 3 para la raíz cúbica). Pista:
hallar la raíz cúbica de 2 es lo mismo que elevar 2 a 1/3.
• Crear un programa que resuelva ecuaciones de segundo grado, del tipo ax2 + bx + c =
0 El usuario deberá introducir los valores de a, b y c. Pista: la solución se calcula con
x = ± raíz (b2 – 4·a·c) / 2·a
8; , # L
Pero en C hay muchas más funciones de lo que parece. De hecho, casi todo lo que hasta ahora
hemos llamado “órdenes=”, son realmente “funciones=”, ay mayoría
l de ellas incluso devuelven
algún valor, que hasta ahora habíamos despreciado en muchos casos.
Vamos a hacer un repaso rápido a las funciones que ya conocemos y el valor que devuelven:
Por el contrario, las siguientes “órdenes=” no son funci ones, sino “palabras reservadas=” del
lenguaje C: if, else, do, while, for, switch, case, default, break, int, char, float, double, struct.
, 6
Una función recursiva es aquella que se define a partir de ella misma. Dentro de las
matemáticas tenemos varios ejemplos. Uno clásico es el "factorial de un número":
Y E e @$ e @+ e e ; e + e $
@$ Y E @$ e @+ e @; e e ; e + e $
Entonces podemos escribir el factorial de un número a partir del factorial del siguiente número:
Y E e @$ Y
Esta es la definición recursiva del factorial, ni más ni menos. Esto, programando, se haría:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H%4 A:
:A %H% A:
:A A:
:A 8 6 4 A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
# !
" $ :A 5 " M A:
$
& ! !$ :A $> " 3 A:
Revisión 0.90– Página 121
! I J 4
! =
! ! 4 = W !
¿Qué utilidad tiene esto? Pues más de la que parece: muchos problemas complicados se
pueden expresar a partir de otro más sencillo. En muchos de esos casos, ese problema se
podrá expresar de forma recursiva. Más adelante veremos algún otro ejemplo.
(
• Crear una función que calcule el valor de elevar un número entero a otro número
entero (por ejemplo, 5 elevado a 3 = 53 = 5 ·5 · 3 = 125). Esta función se debe crear
de forma recursiva.
• Como alternativa, crear una función que calcule el valor de elevar un número entero a
otro número entero de forma NO recursiva (lo que llamaremos “de forma iterativa”),
usando la orden “for”.
• Crear un programa que emplee recursividad para calcular un número de la serie
Fibonacci (en la que los dos primeros elementos valen 1, y para los restantes, cada
elemento es la suma de los dos anteriores).
• Crear un programa que emplee recursividad para calcular la suma de los elementos de
un vector.
• Crear un programa que emplee recursividad para calcular el mayor de los elementos de
un vector.
• Crear un programa que emplee recursividad para dar la vuelta a una cadena de
caracteres (por ejemplo, a partir de "Hola" devolvería "aloH").
• Crear, tanto de forma recursiva como de forma iterativa, una función diga si una
cadena de caracteres es simétrica (un palíndromo). Por ejemplo,
"DABALEARROZALAZORRAELABAD" es un palíndromo.
• Crear un programa que encuentre el máximo común divisor de dos números usando el
algoritmo de Euclides: Dados dos números enteros positivos m y n, tal que m > n, para
encontrar su máximo común divisor, es decir, el mayor entero positivo que divide a
ambos: V Dividir m por n para obtener el resto r (0 ≤ r < n) ; V Si r = 0, el MCD es n.; V
Si no, el máximo común divisor es MCD(n,r).
2 $
Es decir, entre paréntesis indicamos un cierto código, que suele ser (por convenio) un 0 si no
ha habido ningún error, u otro código distinto en caso de que sí exista algún error, al igual que
vimos con el valor de retorno de “main” (y el uso de este código es el que vimos entonces).
Si queremos utilizar “exit”, deberíamos añadir otro fichero a nuestra lista de “includes”, el
llamado “stdlib”:
'
(Aun así, al igual que ocurría con “stdio.h”, algunos compiladores permitirán que nuestro
programa funcione correctamente aunque no tenga esta línea).
Para eliminar esos fallos que hacen que un programa no se comporte como debería, se usan
unas herramientas llamadas “depuradores”. Estos nos permiten avanzar paso a paso para ver
cómo avanza realmente nuestro programa, y también nos dejan ver los valores de las variables.
Como nuestros conocimientos ya nos permiten hacer programas de una cierta complejidad, es
el momento de ver formas de descubrir dónde están los posibles errores. Lo haremos desde
varios entornos distintos.
9
> es un compilador antiguo, pero sencillo de manejar, y la depuración también es
sencilla con él:
“Trace into” va paso a paso por todas las órdenes del programa. Si hay una llamada a
una función, también sigue paso a paso por las órdenes que forma esa función.
“Step over” es similar, salvo que cuando haya una llamada a una función, ésta se
tomará como una única orden, sin ver los detalles de dicha función.
En cualquiera de ambos casos, se nos muestra con una línea azul por dónde va avanzando la
depuración.
" 9" 0 9 0
" ' 0
' #
Si usamos otra herramienta de desarrollo, como , la depuración puede ser mucho más
sencilla, similar al caso de Turbo C y al de DevC++.
Entonces ya podemos
construir nuestro ejecutable
normalmente, e iniciar la depuración desde el menú “Depurador”.
Este tipo de variables son sencillas de usar y rápidas... si sólo vamos a manejar estructuras de
datos que no cambien, pero resultan poco eficientes si tenemos estructuras cuyo tamaño no
sea siempre el mismo.
Es el caso de una agenda: tenemos una serie de fichas, e iremos añadiendo más. Si reservamos
espacio para 10, no podremos llegar a añadir la número 11, estamos limitando el máximo. Una
solución sería la de trabajar siempre en el disco: no tenemos límite en cuanto a número de
fichas, pero es muchísimo más lento.
Lo ideal sería aprovechar mejor la memoria que tenemos en el ordenador, para guardar en ella
todas las fichas o al menos todas aquellas que quepan en memoria.
Una solución “típica” (pero mala) es sobredimensionar: preparar una agenda contando con
1000 fichas, aunque supongamos que no vamos a pasar de 200. Esto tiene varios
inconvenientes: se desperdicia memoria, obliga a conocer bien los datos con los que vamos a
trabajar, sigue pudiendo verse sobrepasado, etc.
Las . Como una pila de libros: vamos apilando cosas en la cima, o cogiendo de la
cima.
Las . Como las del cine (en teoría): la gente llega por un sitio (la cola) y sale por
el opuesto (la cabeza).
Las , en las que se puede añadir elementos, consultarlos o borrarlos en cualquier
posición.
Y la cosa se va complicando: en los cada elemento puede tener varios sucesores, etc.
Todas estas estructuras tienen en común que, si se programan bien, pueden ir creciendo o
decreciendo según haga falta, al contrario que un array, que tiene su tamaño prefijado.
En todas ellas, lo que vamos haciendo es reservar un poco de memoria para cada
que nos haga falta, y enlazarlo a los que ya teníamos. Cuando queramos borrar un
elemento, enlazamos el anterior a él con el posterior a él (para que no “se rompa” nuestra
estructura) y liberamos la memoria que estaba ocupando.
:A J A:
A :A 3
M " A:
Es decir, pondremos un asterisco entre el tipo de datos y el nombre de la variable. Ese asterisco
puede ir junto a cualquiera de ambos, también es correcto escribir
Esta nomenclatura ya la habíamos utilizado aun sin saber que era eso de los punteros. Por
ejemplo, cuando queremos acceder a un fichero, hacemos
8 .?A !
Antes de entrar en más detalles, y para ver la diferencia entre trabajar con “arrays=” o con
punteros, vamos a hacer dos programas que pidan varios números enteros al usuario y
muestren su suma. El primero empleará un “array” (una tabla, de tamaño predefinido) y el
segundo empleará memoria que reservaremos durante el funcionamiento del programa.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H$4 A:
:A %H$ A:
:A A:
:A 6 A:
:A X $4 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
.$%%/ :A $%% A:
:A " A:
:A ' A:
# % :A . > A:
! - Z
! =
" '$%% :A $%% A:
! 7 $%%
# '$%% :A > 0 A:
:A - A:
" % )
. /
! 4 = W
Los más avispados se pueden dar cuenta de que si sólo quiero calcular la suma, lo podría hacer
a medida que leo cada dato, no necesitaría almacenar todos. Vamos a suponer que sí
necesitamos guardarlos (en muchos casos será verdad, si los cálculos son más complicados).
Entonces nos damos cuenta de que lo que hemos estado haciendo hasta ahora
:
• Si quiero sumar 1000 datos, o 500, o 101, no puedo. Nuestro límite previsto era de
100, así que no podemos trabajar con más datos.
• Si sólo quiero sumar 3 números, desperdicio el espacio de 97 datos que no uso.
• Y el problema sigue: si en vez de 100 números, reservamos espacio para 5000, es más
difícil que nos quedemos cortos pero desperdiciamos muchísima más memoria.
La solución es reservar espacio estrictamente para lo que necesitemos, y eso es algo que
podríamos hacer así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H+4 A:
:A %H+ A:
:A A:
:A 6 A:
:A X +4 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
& :A 6 A:
:A " A:
:A ' A:
# % :A . > A:
! - Z
! =
& & $ "
" V.. :A 1 > 6 A:
! '
# V.. :A > 0 A:
:A 1 A:
" % )
! I J = 4 $
Revisión 0.90– Página 129
! =
:A - A:
" % )
&
! 4 = W
!
Este fuente es más difícil de leer, pero a cambio es mucho más eficiente: funciona
perfectamente si sólo queremos sumar 5 números, pero también si necesitamos sumar 120.000
(y si caben tantos números en la memoria disponible de nuestro equipo, claro).
En primer lugar, lo que antes era .$%%/ que quiere decir “a partir de la posición de
memoria que llamaré datos, querré espacio para a guardar 100 números enteros=”,se ha
convertido en & que quiere decir “a partir de la posición de memoria que llamaré
datos voy a guardar varios números enteros (pero aún no sé cuantos)”.
• “malloc” es la orden que usaremos para reservar memoria cuando la necesitemos (es la
abreviatura de las palabra “memory” y “allocate”).
• Como parámetro, le indicamos cuanto espacio queremos reservar. Para 100 números
enteros, sería “100*sizeof(int)”, es decir, 100 veces el tamaño de un entero. En nuestro
caso, no son 100 números, sino el valor de la variable “cuantos”. Por eso hacemos
“malloc (cuantos*sizeof(int))”.
• Para terminar, ese es el espacio que queremos reservar para nuestra variable “datos=”.
Y esa variable es de tipo “int *” (un puntero a datos que serán números enteros). Para
que todo vaya bien, debemos “convertir” el resultado de “malloc” al tipo de datos
correcto, y lo hacemos forzando una conversión como vimos en el apartado 2.4
(operador “molde”), con lo que nuestra orden está completa:
datos = (int *) malloc (cuantos * sizeof(int));
• Si “malloc” nos devuelve NULL como resultado (un “puntero nulo”), quiere decir que no
ha encontrado ninguna posición de memoria en la que nos pudiera reservar todo el
espacio que le habíamos solicitado.
• Para usar “malloc” deberemos incluir “stdlib.h” al principio de nuestro fuente.
La forma de guardar los datos que teclea el usuario también es distinta. Cuando trabajábamos
con un “array”, hacíamos ! = . / (“el dato número i”), pero con punteros
usaremos ! = (en la posición datos + i). Ahora ya no necesitamos el símbolo
" # G7H. Este símbolo se usa para indicarle a C en qué posición de memoria debe
almacenar un dato. Por ejemplo, ! 2 es una variable que podremos usar para guardar un
número real. Si lo hacemos con la orden “scanf”, esta orden no espera que le digamos en qué
variable deber guardar el dato, sino en qué posición de memoria. Por eso hacemos ! =! >
Finalmente, la forma de acceder a los datos también cambia. Antes leíamos el primer dato
como datos[0], el segundo como datos[1], el tercero como datos[2] y así sucesivamente. Ahora
usaremos el asterisco (*) para indicar que queremos saber el valor que hay almacenado en una
cierta posición: el primer dato será *datos, el segundo *(datos+1), el tercero será *(datos+2) y
así en adelante. Por eso, donde antes hacíamos . / ahora usamos
&
También aparece otra orden nueva: . Hasta ahora, teníamos la memoria reservada
estáticamente, lo que supone que la usábamos (o la desperdiciábamos) durante todo el tiempo
que nuestro programa estuviera funcionando. Pero ahora, igual que reservamos memoria justo
en el momento en que la necesitamos, y justo en la cantidad que necesitamos, también
podemos volver a dejar disponible esa memoria cuando hayamos terminado de usarla. De eso
se encarga la orden “free”, a la que le debemos indicar qué puntero es el que queremos liberar.
: 6
El ejemplo anterior era “un caso real”. Generalmente, los casos reales son más aplicables que
los ejemplos puramente académicos, pero también más difíciles de seguir. Por eso, antes de
seguir vamos a ver un ejemplo más sencillo que nos ayude a asentar los conceptos:
Reservaremos espacio para un número real de forma estática, y para dos números reales de
forma dinámica, daremos valor a dos de ellos, guardaremos su suma en el tercer número y
mostraremos en pantalla los resultados.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H;4 A:
:A %H; A:
:A A:
:A B 0 'L A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
"# $ :A J > L A:
"# & + & :A . J A:
$ F % :A 7 6 ! 0 $ A:
+ "# & $ " "# :A 6 + A:
& + G H :A X ! 0 + A:
! ? 6 ! 0 =, +!W
&
! " J
! =! + :A X + A:
& $ & + :A - 6 A:
! 5 =, +!W &
! + :A . ' 6 A:
!
(En este ejemplo, no hemos comprobado si el resultado de “malloc” era NULL, porque sólo
pedíamos espacio para dos variables, y hemos dado por sentado que sí habría memoria
disponible suficiente para almacenarlas; en un caso general, deberemos asegurarnos siempre
de que se nos ha concedido ese espacio que hemos pedido).
:& 7
Si declaramos una variable como EF y posteriormente hacemos <<, debería resultar claro
que lo que ocurre es que aumenta en una unidad el valor de la variable n, pasando a ser 6.
Pero ¿qué sucede si hacemos esa misma operación sobre un puntero?
&
& $ "
& ;
&
En cambio, nosotros hemos aumentado el valor de “n”. Como “n” es un puntero, estamos
modificando una dirección de memoria. Por ejemplo, si “n” se refería a la posición de memoria
¿Y por qué “por ejemplo”? Porque, como ya sabemos, el espacio que ocupa una variable en C
depende del sistema operativo. Así, en un sistema operativo de 32 bits, un “int” ocuparía 4
bytes, de modo que la operación
haría que pasáramos de mirar la posición 10.000 a la 10.004. Generalmente no es esto lo que
querremos, sino modificar el valor que había almacenado en esa posición de memoria. Olvidar
ese * que indica que queremos cambiar el dato y no la posición de memoria puede dar lugar a
fallos muy difíciles de descubrir (o incluso a que el programa se interrumpa con un aviso de
“Violación de segmento” porque estemos accediendo a zonas de memoria que no hemos
reservado).
:) * ( % *
Hasta ahora no sabíamos cómo modificar los parámetros que pasábamos a una función.
Recordemos el ejemplo 64:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q G,4 A:
:A %G, A:
:A A:
:A 7 6 ' A:
:A ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
& +
F
! 6 = W
! 5 6 = W
Cuando poníamos este programa en marcha, el valor de n que se mostraba era un 5, porque
los cambios que hiciéramos dentro de la función se perdían al salir de ella. Esta forma de
trabajar (la única que conocíamos hasta ahora) es lo que se llama “pasar
”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H,4 A:
:A %H, A:
:A A:
:A B ! 6 A:
:A L A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
&2
&2 &2 & +
F
! 6 = W
! 5 6 = W
Esto permite que podamos obtener más de un valor a partir de una función. Por ejemplo,
podemos crear una función que intercambie los valores de dos variables enteras así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q HF4 A:
:A %HF A:
:A A:
:A ' 6 A:
:A L A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
F
' $+
' '
! 5 = 1 ' = W '
Este programa escribirá en pantalla que a vale 12 y que b vale 5. Dentro de la función
“intercambia”, nos ayudamos de una variable auxiliar para memorizar el valor de x antes de
cambiarlo por el valor de y.
Revisión 0.90– Página 134
: Crear una función que calcule las dos soluciones de una ecuación de
2
segundo grado (Ax + Bx + C = 0) y devuelva las dos soluciones como parámetros.
:+
En C hay muy poca diferencia “interna” entre un puntero y un array. En muchas ocasiones,
podremos declarar un dato como array (una tabla con varios elementos iguales, de tamaño
predefinido) y recorrerlo usando punteros. Vamos a ver un ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q HG4 A:
:A %HG A:
:A A:
:A 5 1 1 $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
.$%/
:A 7 6 A:
" % )$%
. / &+
:A A:
" % )$%
! = &
Pero también podremos hacer lo contrario: declarar de forma dinámica una variable usando
“malloc” y recorrerla como si fuera un array:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q HH4 A:
:A %HH A:
:A A:
:A 5 1 1 + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
&
:A 6 A:
& +%& $ "
:A 7 6 A:
:, 7
Igual que creamos “arrays” para guardar varios datos que sean números enteros o reales,
podemos hacerlo con punteros: podemos reservar espacio para “20 punteros a enteros=”
haciendo
& .+%/
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q HR4 A:
:A %HR A:
:A A:
:A 5 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
& 0 ? .;/ 8
'
8
! ? 0 4 = W
0 ? .%/
! ? " 0 4 = W
0 ? .$/
! ? 0 4 = W
0 ? .+/
Eso sí, la forma de acceder a los datos en un struct cambiará ligeramente. Para un dato que sea
un número entero, ya sabemos que lo declararíamos con A y cambiaríamos su valor
haciendo algo como A E+, de modo que para un struct podríamos esperar que se hiciera algo
como A E +%. Pero esa no es la sintaxis correcta: deberemos utilizar el nombre de la
variable y el del campo, con una flecha (V>) entre medias, así: @ E +%. Vamos a
verlo con un ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q H*4 A:
:A %H* A:
:A A:
:A 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ! A:
' .;%/
.+F/
:A . L L A:
$
:A . " L L A:
& +
:A 7 6 L A:
1 $ ' `
1 $ 0)0 0
$ +%
:A 5 L A:
+ &
$ "
1 +!' '
1 +!' )
+!' +$
:A B 1 ' A:
! 4 = > = > = W
$ ' $ $
! " 4 = > = > = W
+!' ' +!' +!'
! +
:: $ E ( %
@ A
Es muy frecuente que un programa que usamos desde la “línea de comandos” tenga ciertas
opciones que le indicamos como argumentos. Por ejemplo, bajo Linux o cualquier otro sistema
operativo de la familia Unix, podemos ver la lista detallada de ficheros que terminan en .c
haciendo
9 A
Pues bien, estas opciones que se le pasan al programa se pueden leer desde C. La forma de
hacerlo es con dos parámetros. El primero (que por convenio se suele llamar “argc”) será un
número entero que indica cuantos argumentos se han tecleado. El segundo (que se suele
llamar “argv”) es una tabla de cadenas de texto, que contiene cada uno de esos argumentos.
Por ejemplo, si bajo Windows o MsDos tecleamos la orden “DIR *.EXE P”, tendríamos que:
Un fuente en C de ejemplo, que mostrara todos los parámetros que se han tecleado sería:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R%4 A:
:A %R% A:
:A A:
:A 5 " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:
• Crear un programa llamado “suma”, que calcule (y muestre) la suma de dos números
que se le indiquen como parámetro. Por ejemplo, si se teclea “suma 2 3” deberá
responder “5”, y si se teclea “suma 2” deberá responder “no hay suficientes datos.
• Crear una calculadora básica, llamada “calcula”, que deberá sumar, restar, multiplicar o
dividir los dos números que se le indiquen como parámetros. Ejemplos de su uso sería
“calcula 2 + 3” o “calcula 5 * 60”.
: % 5 (
0
Ahora vamos a ver dos tipos de estructuras totalmente dinámicas (que puedan aumentar o
disminuir realmente de tamaño durante la ejecución del programa). Primero veremos las ,
y más adelante los árboles binarios. Hay otras muchas estructuras, pero no son difíciles de
desarrollar si se entienden bien estas dos.
Ahora “el truco” consistirá en que dentro de cada dato almacenaremos todo lo que nos
interesa, pero también una referencia que nos dirá dónde tenemos que ir a buscar el siguiente.
3 4 $%+;
' 4 - '
f ' 4 [[[ '
" 7 4 $,;%
Este dato está almacenado en la posición de memoria número 1023. En esa posición
guardamos el nombre y la dirección (o lo que nos interese) de esta persona, pero también una
información extra: la siguiente ficha se encuentra en la posición 1430.
Así, es muy cómodo recorrer la lista de forma , porque en todo momento sabemos
dónde está almacenado el siguiente dato. Cuando lleguemos a uno para el que no esté
definido cual es el siguiente dato, quiere decir que se ha acabado la lista.
Por tanto, en cada dato tenemos un enlace con el dato siguiente. Por eso este tipo de
estructuras recibe el nombre de “listas simplemente enlazadas=” o . Si
tuvieramos enlaces hacia el dato siguiente y el posterior, se trataría de una “lista doblemente
enlazada” o , que pretende hacer más sencillo el recorrido hacia delante o hacia
atrás.
Con este tipo de estructuras de información, hemos perdido la ventaja del acceso directo: ya no
podemos saltar directamente a la ficha número 500. Pero, por contra, podemos tener tantas
Para añadir una ficha, no tendríamos más que reservar la memoria para ella, y el compilador de
C nos diría “le he encontrado sitio en la posición 4079”. Entonces nosotros iríamos a la última
ficha y le diríamos “tu siguiente dato va a estar en la posición 4079”.
! :A ? M " 4 A:
' .;%/ :A ' > ;% A:
.F%/ :A 7 > F% A:
:A ? > +FF A:
!& " :A N 3 " A:
La diferencia con un “struct” normal está en el campo “siguiente” de nuestro registro, que es el
que indica donde se encuentra la ficha que va después de la actual, y por tanto será otro
puntero a un registro del mismo tipo, un “struct f *”.
Un puntero que “no apunta a ningún sitio” tiene el valor I== (realmente este identificador es
una constante de valor 0), que nos servirá después para comprobar si se trata del final de la
lista: todas las fichas “apuntarán” a la siguiente, menos la última, que “no tiene siguiente”, y
apuntará a NULL.
! & $ :A X ! A:
$ !& $ " ! :A 6 A:
1 $!' ' :A c ' > A:
1 $!' :A 3 A:
$!' ,% :A A:
$!' " V.. :A 1 1 " L A:
(No debería haber anada nuevo: ya sabemos cómo reservar memoria usando “malloc” y como
acceder a los campos de una estructura dinámica usando V>).
Ahora que ya tenemos una ficha, podríamos : otra ficha detrás de ella. Primero
guardamos espacio para la nueva ficha, como antes:
! & +
+ !& $ " ! :A 6 A:
1 +!' ' ` :A c ' > A:
1 +!' / :A 3 A:
+!' ;F :A A:
+!' " V.. :A 1 1 " L A:
$!' " +
! & ;
; !& $ " ! :A . A:
1 ;!' ' -
1 ;!' O
;!' $,
;!' " + :A I " A:
$!' " ; :A 1 A:
! . 4W
7 $ @ 7 ; @ 7 + @ V..
Gráficamente:
Es decir: cada ficha está enlazada con la siguiente, salvo la última, que no está enlazada con
ninguna (apunta a NULL).
$!' " + :A S ;4 ? I 7 $ 1 7 + A:
! ; :A . ' M 3 7 ; A:
Hemos empleado tres variables para guardar tres datos. Si tenemos 20 datos, ¿necesitaremos
20 variables? ¿Y 3000 variables para 3000 datos?
Sería tremendamente ineficiente, y no tendría mucho sentido. Es de suponer que no sea así.
En la práctica, basta con dos variables, que nos indicarán el principio de la lista y la posición
actual, o incluso sólo una para el principio de la lista.
Por ejemplo, una rutina que toda la lista se podría hacer de forma
recursiva así:
B . ! &
Antes de seguir, vamos a juntar todo esto en un programa, para comprobar que realmente
funciona: añadimos los 3 datos y decimos que los muestre desde el primero; luego borramos el
del medio y los volvemos a mostrar:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R$4 A:
:A %R$ A:
:A A:
:A 0 A:
:A I A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
! :A ? M " 4 A:
' .;%/ :A ' > ;% A:
.F%/ :A 7 > F% A:
:A ? > +FF A:
!& " :A N 3 " A:
! & $ :A X ! A:
! & + :A T ! A:
! & ; :A N L A:
B . ! &
" ( V.. :A 1 A:
! ' 4 = W !' '
! 7 3 4 = W !'
! ? 4 = W W !'
B . !' " :A N " A:
$ !& $ " ! :A 6 A:
1 $!' ' :A c ' > A:
1 $!' :A 3 A:
$!' ,% :A A:
$!' " V.. :A 1 1 " L A:
+ !& $ " ! :A 6 A:
1 +!' ' ` :A c ' > A:
1 +!' / :A 3 A:
+!' ;F :A A:
+!' " V.. :A 1 1 " L A:
$!' " + :A ? I A:
B . $
$!' " + :A S ;4 ? I 7 $ 1 7 + A:
! ; :A . ' M 3 7 ; A:
! N ' ;4W W
B . $
%
Vamos a ver otro ejemplo, que cree una lista de números, y vaya insertando en ella varios
valores . Ahora tendremos una función para mostrar datos, otra para crear la lista
insertando el primer dato, y otra que inserte un dato ordenado
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R+4 A:
:A %R+ A:
:A A:
:A " 0 A:
:A I A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
:A A:
:A " A:
& " :A N " A:
& - . 6 :A - > A:
& :A X ' 2 A:
&
$ " :A 6 A:
!' 6 :A c 6 A:
!' " V.. :A 1 " A:
:A - A A:
B . &
" :A 1 A:
! = W !' :A ? ' 6 A:
B . !' " :A N " A:
. && 6
& :A X ' 2 > 6 A:
& :A T 2 > A:
&
" :A 1 A:
" !' ) 6 :A 1 6O A:
Revisión 0.90– Página 143
. !' " 6 :A " 3 A:
# :A 1 1 A:
- . 6 :A " A:
!' " :A A:
& :A N M 6 A:
# :A 1 > 1 M A:
- . 6
& :A 1 1 M ' I A:
& :A . M A:
- . F :A - F A:
. ; :A ; A:
. + :A + A:
. G :A G A:
B . :A B A:
% :A '3 A:
No es un fuente fácil de leer (en general, no lo serán los que manejen punteros), pero aun así
los cambios no son grandes:
• Cuando después de ese dato debemos enlazar datos existentes, lo hacemos en dos
pasos: - . 6 !' "
• Por otra parte, también hemos abreviado un poco alguna expresión: " es lo
mismo que " ( V.. (recordemos que NULL es una constante que vale 0).
Finalmente, hay varios casos particulares que resultan más sencillos que una lista “normal”.
Vamos a comentar los más habituales:
: Las listas simples, tal y como las hemos tratado, tienen la ventaja de
que no hay limitaciones tan rígidas en cuanto a tamaño como en las variables estáticas, ni hay
por qué saber el número de elementos desde el principio. Pero siempre hay que recorrerlas
desde DELANTE hacia ATRAS, lo que puede resultar lento. Una mejora relativamente evidente
es lo que se llama una o lista doblemente enlazada: si guardamos punteros al dato
anterior y al siguiente, en vez de sólo al siguiente, podremos avanzar y retroceder con
comodidad. Implementa una lista doble enlazada que almacene números enteros.
: % 5 ( %
En las listas, después de cada elemento había otro, el “siguiente” (o ninguno, si habíamos
llegado al final).
Pero también nos puede interesar tener varias posibilidades después de cada elemento, varios
“hijos=”, por ejemplo 3. De cada uno de estos 3 “hijos=”saldrían otros 3, y así sucesivamente.
Obtendríamos algo que recuerda a un árbol: un tronco del que nacen 3 ramas, que a su veces
se subdividen en otras 3 de menor tamaño, y así sucesivamente hasta llegar a las hojas.
Pues eso mismo será un árbol: una estructura dinámica en la que cada nodo (elemento) puede
tener más de un “siguiente”. Nos centraremos en los , en los que cada nodo
puede tener un hijo izquierdo, un hijo derecho, ambos o
ninguno (dos hijos como máximo).
Para que se entienda mejor, vamos a introducir en un árbol binario de búsqueda los datos
5,3,7,2,4,8,9
J 4 F
" J 4 ; M F
J 4 H 1 M F
F
: W
; H
- 4 + M F> M ;
F
: W
; H
:
+
g 4 , M F> 1 M ;
F
: W
; H
: W
+ ,
2 4 R 1 M F> 1 M H
F
: W
; H
: W W
+ , R
/ 4 * 1 M F> 1 M H> 1 M R
F
: W
; H
: W W
+ , R
W
*
¿Qué tiene esto? La rapidez: tenemos 7 elementos, lo que en una lista supone que si
buscamos un dato que casualmente está al final, haremos 7 comparaciones; en este árbol,
tenemos 4 alturas => 4 comparaciones como máximo.
No vamos a ver cómo se hace eso de los “equilibrados=”, ue q sería propio de un curso de
programación más avanzado, pero sí vamos a empezar a ver rutinas para manejar estos árboles
binarios de búsqueda.
' :A ? ' O4 A:
:A @ A:
' & 0 IM :A @ 0 IM A:
' & 0 7 :A @ 0 A:
Y las rutinas de inserción, búsqueda, escritura, borrado, etc., podrán ser recursivas. Como
primer ejemplo, la de de todo el árbol en orden sería:
" :A " 0 A:
? ' !' 0 IM :A B IM 6 A:
! = !' :A ? ' A:
? ' !' 0 7 :A N " A:
Quien no se crea que funciona, debería coger lápiz y papel comprobarlo con el árbol que hemos
visto antes como ejemplo. Es muy importante que esta función quede clara antes de seguir
leyendo, porque los demás serán muy parecidos.
La rutina de sería parecida, aunque algo más "pesada" porque tenemos que pasar el
puntero por referencia, para que se pueda modificar el puntero:
' && 6
# :A 0 A:
" !' ' 6 :A N 1 A:
!' 0 IM 6 :A B IM A:
# :A ? A:
!' 0 7 6 :A B A:
Y finalmente, la de de todo el árbol, casi igual que la de escritura, sólo que en vez de
borrar la izquierda, luego el nodo y luego la derecha, borraremos primero las dos ramas y en
último lugar el nodo, para evitar incongruencias (intentar borrar el hijo de algo que ya no
existe):
" :A " 0 A:
S !' 0 IM :A X IM 6 A:
S !' 0 7 :A N " A:
! :A 8 > ' M A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R;4 A:
:A %R; A:
:A A:
:A 5 ' ' A:
:A 'J M A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
' :A ? ' O4 A:
:A @ A:
' & 0 IM :A @ 0 IM A:
' & 0 7 :A @ 0 A:
" :A " 0 A:
? ' !' 0 IM :A B IM 6 A:
! = !' :A ? ' A:
? ' !' 0 7 :A N " A:
' && 6
# :A 0 A:
" !' ' 6 :A N 1 A:
!' 0 IM 6 :A B IM A:
# :A ? A:
!' 0 7 6 :A B A:
:A - " A:
: ' #
Lo que estamos haciendo mediante los punteros es algo que técnicamente se conoce como
“direccionamiento indirecto”: cuando hacemos A , generalmente no nos va a interesar el
valor de n, sino que n es una dirección de memoria a la que debemos ir a mirar el valor que
buscamos.
Pues bien, podemos hacer que ese direccionamiento sea todavía menos directo que en el caso
normal: algo como AA se referiría a que n es una dirección de memoria, en la que a su vez
se encuentra como dato otra dirección de memoria, y dentro de esta segunda dirección de
memoria es donde se encuentra el dato. Es decir, n sería un “puntero a puntero a entero”.
: = ( * 5
Como ejemplo de un fuente en el que se apliquen algunas de las ideas más importantes que
hemos visto, vamos a crear un copidor de ficheros, que intente copiar todo el fichero de origen
en una única pasada: calculará su tamaño, intentará reservar la memoria suficiente para
almacenar todo el fichero a la vez, y si esa memoria está disponible, leerá el fichero completo y
lo guardará con un nuevo nombre.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R,4 A:
:A %R, A:
:A A:
:A - ! A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 5 ! " A:
! I ' ! T " 4
! = ' T "
" ! T " ! ' T " ' V..
! 2 ! " YW
2 $
:A N A:
! I ' ! 7 4
! = ' 7
" ! 7 ! ' 7 [' V..
! ! YW
2 +
:A B " ! " A:
! # ! T " % ??a&? 7
" ! ! T "
! # ! T " % ??a& ?
:A 6 A:
' !! & "
" ' !! V..
! 6 YW
2 ;
:A . 6 I A:
! ' !! $ " ! T "
:A ? ' 1 O A:
![ ' !! $ ! 7
" ( "
! - 4 W
:A - ! A:
! ! T "
! ! 7
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q RF4 A:
:A %RF A:
:A A:
:A . 6 A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
! ? 4W
1
. 0
Si lo que queremos es hacer una pausa en un programa, en ocasiones tendremos funciones
que nos permitan hacer una pausa de ciertas milésimas de segundo, como la función “delay” de
Turbo C.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q RG4 A:
:A %RG A:
:A A:
:A ' - A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! X + "
1 +%%%
! ? W
Revisión 0.90– Página 151
%
Si queremos comprobar la fecha y hora del sistema, lo podemos hacer con las funciones
disponibles en “time.h”, que sí son parte del estandar ANSI C, por lo que deberían estar
disponibles para casi cualquier compilador:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q RH4 A:
:A %RH A:
:A A:
:A . ! 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
& "
&!
" V..
! 4 = W "
! " "
! - 2 4 = W !
Dentro de “time.h”, tenemos definido un tipo llamado “time_t” que representará a una cierta
fecha (incluyendo hora). La mayoría de los sistemas lo representan internamente como un
entero largo (el número de segundos desde cierta fecha), aunque es algo que a nosotros no
nos debería hacer falta saber si usamos directamente ese tipo “time_t”.
Tenemos también un tipo de registro (struct) predefinido, llamdo “struct tm”, que guarda la
información desglosada del día, el mes, el año, la hora, etc. Los principales campos que
contiene son éstos:
& :A % @ +;A:
& 1 :A 7 $ @ ;$ A:
& :A B % @ F* A:
& :A B % @ $$ 4 % E ? A:
& :A " % @ F* A:
&[ 1 :A 7 % @ G 4 % E 7 " A:
&1 1 :A 7 K % @ ;GF A:
&1 :A 5K $*%% A:
Como hemos visto en este ejemplo, tenemos varias funciones para manipular la fecha y la
hora:
• “time” devuelve el número de segundos que han pasado desde el 1 de enero de 1970.
Su uso habitual es V..
• “gmtime” convierte ese número de segundos que nos indica “time” a una variable de
tipo “struct tm *” para que podamos conocer detalles como la hora, el minuto o el mes.
En la conversión, devuelve la hora universal (UTC o GMT, hora en Greenwich), que
puede no coincidir con la hora local.
• “localtime” es similar, pero devuelve la hora local, en vez de la hora universal (el
sistema debe saber correctamente en qué zona horaria nos encontramos).
• “asctime” convierte un dato horario de tipo “struct tm *” a una cadena de texto que
representa fecha, hora y día de la semana, siguiendo el formato B 1 +% $F4+$4F$
+%%% (día de la semana en inglés abreviado a 3 letras, mes en inglés abreviado a 3
letras, número de día, horas, minutos, segundos, año).
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q RR4 A:
:A %RR A:
:A A:
:A C #D A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
"
#& 8
8 # " & -.T-a & ? & ?-
# # ) 8
! - I W
! YW
%
Nota: en Turbo C no existe la constante CLOCKS_PER_SEC, sino una llamada CLK_TCK con el
mismo significado (“ticks” del reloj en cada segundo, para poder convertir a segundos el valor
que nos indica “clock()”).
. !
La familia Turbo C / Turbo C++ / Borland C++ incluye una serie de compiladores creados por
Borland para Dos y para Windows. Con ellos se podía utilizar ciertas órdenes para escribir en
cualquier posición de la pantalla, para usar colores, para comprobar qué tecla se había pulsado,
etc. Eso sí, estas órdenes no son C estándar, así que lo más habitual es que no se encuentren
disponibles para otros o para otros sistemas operativos.
Aun así, como primer acercamiento al control de estos dispositivos desde Linux, puede ser
interesante conocer lo que ofrecía la familia de Turbo C y posteriores, porque sientan muchas
de las bases que después utilizaremos, pero a la vez se trata de funciones muy sencillas.
• getch V Espera hasta que se pulse una tecla, pero no la muestra en pantalla.
• getche V Espera hasta que se pulse una tecla, y la muestra en pantalla.
• kbhit V Comprueba si se ha pulsado alguna tecla (pero no espera).
Todas ellas se encuentran definidas en el fichero de cabecera “conio.h”, que deberemos incluir
en nuestro programa.
Los colores de la pantalla se indican por números. Por ejemplo: 0 es el negro, 1 es el azul
oscuro, 2 el verde, 3 el azul claro, 4 el rojo, etc. Aun así, para no tener que recordarlos,
tenemos definidas constantes con su nombre en inglés:
S.5-a> S.V?> c ?? > -N5 > ?7> B5c? 5> S Tf > . c c 5N> 75 ac 5N>
. c S.V?> . c c ?? > . c -N5 > . c ?7> . c B5c? 5> N?..Tf> f ?
no obtendremos los resultados esperados, sino que será como si hubiésemos utilizado el color
equivalente en el rango de 0 a 7:
Para usarlas, tenemos que incluir “ !”. Vamos a ver un ejemplo que emplee la mayoría de
ellas:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q R*4 A:
:A %R* A:
:A A:
:A 1 A:
:A ' - A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ! A:
0 :A ' ! A:
2 ' #" S.V? :A 8 I A:
:A S A:
" % ) $ :A 7 A:
" 0 % 0) $F 0 :A . $G A:
" 21 $% ,%& ; 0 :A - A:
2 0 :A ? 0 A:
" 0 % :A % " A:
2 ' #" . c c 5N :A 0 ! " A:
# :A A:
2 ' #" S.5-a :A 0 ! " A:
! ? = 0 :A ? ' A:
" :A 8 4 M > A:
& 7 ( 5
En Linux, hay una biblioteca llamada "curses", que es la que deberemos incluir si queremos
usar algo más que el simple "printf": nos permitirá borrar la pantalla, escribir en unas ciertas
coordenadas, cambiar colores o apariencias del texto, dibujar recuadros, etc.
Eso sí, el manejo es más complicado que en el caso de Turbo C: hay que tener en cuenta que
en Linux (y en los Linux en general) podemos encontrarnos con que nuestro programa se use
desde un terminal antiguo, que no permita colores, sino sólo negrita y subrayado, o que no
actualice la pantalla continuamente, sino sólo en ciertos momentos. Es algo cada vez menos
frecuente, pero si queremos que nuestro programa funciones siempre, deberemos llevar ciertas
precauciones.
Por eso, el primer paso será activar el acceso a pantalla con “ ” y terminar con
“ 5 ”. Además, cuando queramos asegurarnos de que la información aparezca en pantalla,
deberíamos usar “ !”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *%4 A:
:A %*% A:
:A A:
:A - $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
6 $% $%
[
!
"
[
0 9 9 0
• En la inicialización, es frecuente que nos interese comprobar el teclado cada vez que se
pulse una tecla, sin esperar a que se complete con Intro, y de asegurarse de eso se
encarga la orden “cbreak”.
• También es frecuente que queramos que cuando se pulse una tecla, ésta no aparezca
en pantalla. Eso lo hacemos con “noecho”.
• Podemos desplazarnos a una cierta posición y escribir un texto, todo ello con la misma
orden, si usamos “mvaddstr”.
• Podemos cambiar la apariencia del texto si le aplicamos ciertos atributos, usando la
orden “attron”. Los atributos más habituales en un terminal Unix serían la negrita
(A_BOLD) y el subrayado (A_UNDERLINE). Estos atributos se desactivarían con
“attroff”.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *$4 A:
:A %*$ A:
:A A:
:A - + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A I 3 A:
' # :A A:
:A M " ' A:
:A S A:
5&ST.7 :A 5 6 " ' A:
6 + $% :A ? ' A:
!! 5&ST.7 :A 7 6 " A:
! :A 5 I ! A:
" :A ? M A:
[ :A '3 A:
Finalmente, si queremos escribir en pantalla usando colores, tendremos que decir que
queremos comenzar el modo de color con & durante la inicialización. Eso sí, antes
Revisión 0.90– Página 157
deberíamos comprobar con & si nuestro terminal puede manejar colores (tenemos
definida una constante FALSE para poder comprobarlo).
Entonces podríamos definir nuestros pares de color de fondo y de primer plano, con “init_pair”,
así: & $ -T.T &-N5 -T.T &S.5-a (nuestro par de colores 1 será texto azul claro sobre
fondo negro).
Para usar estos colores, sería muy parecido a lo que ya conocemos: -T.T & 5 $
"
" a?N&.?8
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *+4 A:
:A %*+ A:
:A A:
:A - ; A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
[
! YW
2 $
&
' #
# 1 V?
:
• Crea un menú para MsDos que muestre varias opciones en el centro de la pantalla, y el
reloj en la parte superior derecha de la pantalla. Mientras el usuario no pulse una tecla,
el reloj debe actualizarse continuamente.
• Crea, tanto para MsDos como para Linux, un “protector de pantalla” que muestre tu
nombre rebotando en los laterales de la pantalla. Deberá avanzar de posición cada
segundo.
) F * ( 3-
4 " H # - H
Existen distintas bibliotecas que permiten crear gráficos desde el lenguaje C. Unas son
específicas para un sistema, y otras están diseñadas para ser portables de un sistema a otro.
Por otra parte, unas se centran en las funciones básicas de dibujo (líneas, círculos, rectángulos,
etc), y otras se orientan más a la representación de imágenes que ya existan como fichero.
Nosotros veremos las nociones básicas del uso de SDL, que es una de las bibliotecas más
adecuadas para crear juegos, porque es multiplataforma (existe para Windows, Linux y otros
sistemas) y porque esta orientada a la manipulación de imágenes, que es algo más frecuente
en juegos que el dibujar líneas o polígonos.
No veremos detalles de su instalación, porque en los sistemas Linux debería bastar un instalar
el paquete SDLVDev (que normalmente tendrá un nombre como libsdl1.2Vdev), y en Windows
hay entornos que permiten crear un “proyecto de SDL” tan sólo con dos clics, como
CodeBlocks.
Ya dentro del cuerpo del programa, el primer paso sería tratar de inicializar la biblioteca SDL, y
abandonar el programa en caso de no conseguirlo:
" 7.& 7.& &X 7?T ) %
! I 7.4 = W 7.&c ?
2 $
Para mostrar una imagen en pantalla: deberemos declararla del tipo SDL_Surface, cargarla con
SDL_LoadBMP, y volcarla con SDL_BlitSurface usando como dato auxiliar la posición de destino,
que será de tipo SDL_Rect:
7.& ! & "
" 7.&. SB " '
7.&
2 ;+%
1 ,%%
7.&S ! " 86;;
Y para esperar 5 segundos y que nos dé tiempo a comprobar que todo ha funcionado,
utilizaríamos:
7.&7 1 F%%%
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - 7. A:
:A %$ A:
:A A:
:A ?0 7. $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
'
7.: 7.
7.& ! &
7.& ! &!
7.& ! & "
7.&
0
:A I ' ' 7. A:
" 7.& 7.& &X 7?T ) %
! I 7.4 = W 7.&c ?
2 $
:A " A:
:A 6 A:
7.&fB& - ' $ 7. ' $ 7.
:A 7 ' 0 " ! A:
2 %
1 %
7.&S ! ! 86;;
:A 7 ' 0 " A:
2 ;+%
1 ,%%
7.&S ! " 86;;
:A 5 I A:
7.&8
:A N A:
7.&7 1 F%%%
:A 8 > A:
7.&g
%
En principio, si sólo usamos SDL, las imágenes tendrán que ser en formato BMP, pero hay otras
bibliotecas adicionales, como SDL_Image, que permiten mostrar también imágenes en formatos
PNG, JPG, etc.
El tipo SDL_Rect representa un rectángulo, y se trata de un registro (struct), que tiene como
campos:
* x: posición horizontal
* y: posición vertical
* w: anchura (width)
* h: altura (height)
(nosotros no hemos usado la anchura ni la altura, pero podrían ser útiles si no queremos volcar
toda la imagen, sino sólo parte de ella).
En Windows (con entornos como CodeBlocks), bastaría con pulsar el botón “compilar el
proyecto” de nuestro entorno de desarrollo.
:
• Crea (o busca en Internet) una imagen de fondo de tamaño 800x600 que represente
un cielo negro con estrellas, y tres imágenes de planetas con un tamaño menor
(cercano a 100x100, por ejemplo). Entra a modo gráfico 800x600 con 24 bits de color
usando SDL, dibuja la imagen de fondo, y sobre ella, las de los tres planetas. El título
de la ventana deberá ser “Planetas=”. Las imágenes debe rán mostrarse durante 7
segundos.
4 0 H $
Para poder mover a ese protagonista por encima del fondo de forma “no planificada”
necesitamos poder comprobar qué teclas se están pulsando. Lo podemos conseguir haciendo:
7.&c a 1 86;;
" . 7.a&V /
N ! +
Donde la variable "teclas", será un array de "unsigned int". La forma normal de declararla será:
V R&
Eso sí, antes de poner acceder al estado de cada tecla, deberemos poner en marcha todo el
sistema de comprobación de sucesos ("events", en inglés). Al menos deberemos comprobar si
hay alguna petición de abandonar el programa (por ejemplo, pulsando la X de la ventana), a lo
que corresponde el suceso de tipo SDL_QUIT. De paso, podríamos comprobar en este mismo
paso si se ha pulsado la tecla ESC, que es otra forma razonable de indicar que queremos
terminar el programa:
# 7.& ?6
" 1 7.&gV $
" 1 7.&a?N7Tf
" # 1 # 1 1 1 7.a&? -5 ? $
7.&?6
4 * - H
Hemos visto cómo dibujar imágenes en pantalla, y cómo comprobar qué teclas se han pulsado,
para hacer que una imagen se mueva sobre la otra.
Pero tenía un defecto, que hacía que no quedara vistoso: si la imagen del “protagonista” tiene
un recuadro negro alrededor, al moverse tapará parte del fondo. Esto se debe evitar: una
imagen que se mueve en pantalla (lo que se suele llamar un “sprite”) debería tener zonas
, a través de las que se vea el fondo.
Es algo fácil de conseguir con SDL: podemos hacer que un color se considere transparente,
usando
donde "surface" es la superficie que queremos que tenga un color transparente (por ejemplo,
nuestro protagonista), y "r, g, b" son las componentes roja, verde y azul del color que
queremos que se considere transparente (si es el color negro el que queremos que no sea vea,
serían 0,0,0).
:A " A:
! 7.&. SB ! '
" 7.&. SB " '
:A ? " ' A:
7.& - a 1 " 7.& --T.T a?N
7.&B cS " !'! % % %
Si usamos la biblioteca adicional SDL_Image, podremos usar imágenes PNG con transparencia,
y entonces no necesitaríamos usar esta orden.
Escribir $ con SDL no es algo “trivial”: A no ser que empleemos la biblioteca adicional
SDL_TTF, no tendremos funciones predefinidas que muestren una frase en ciertas coordenadas
de la pantalla.
Pero podríamos hacerlo “a mano”: preparar una imagen que tenga las letras que queremos
mostrar (o una imagen para cada letra), y tratarlas como si fueran imágenes. Podemos
crearnos nuestras propias funciones para escribir cualquier texto. Podríamos comenzar por una
función “escribirLetra(int x, int y, char letra)”, y apoyándonos en ella, crear otra
“escribirFrase(int x, int y, char *frase)”
:
• Amplía el ejercicio anterior, para que la imagen de la nave espacial tenga el contorno
transparente (y quizá también alguna zona interior).
• Crea una imagen que contenga varias letras (al menos H, O, L, A), y úsala para escribir
las frases HOLA y OH en modo gráfico con SDL.
4! @ ##
Si intentamos mover varias imágenes a la vez en pantalla, es probable que el resultado
parpadee.
El motivo es que mandamos información a la pantalla en distintos instantes, por lo que es fácil
que alguno de todos esos bloques de información llegue en un momento que no coincida con el
barrido de la pantalla (el movimiento del haz de electrones que redibuja la información que
vemos).
Una solución habitual es preparar toda la información, trozo a trozo, en una “imagen oculta”, y
sólo volcar a la pantalla visible cuando la imagen está totalmente preparada. Esta técnica es la
que se conoce como “usar un doble buffer”.
El segundo paso es sincronizar con el barrido, algo que en la mayoría de bibliotecas hace una
función llamada “retrace” o “sync”, y que en SDL se hace automáticamente cuando volcamos la
información con “SDL_Flip”.
A la hora de dibujar, no lo hacemos directamente sobre “screen”, sino sobre una superficie
(“surface”) auxiliar. Cuando toda esta superficie está lista, es cuando la volcamos a la pantalla,
así:
7.&S ! ! 86;; T
7.&S ! " 86;; T
7.&S ! T 86;;
7.&8
Sólo queda un detalle: ¿cómo reservamos espacio para esa pantalla oculta?
! 7.&. SB ! '
Si no es el caso (por ejemplo, porque el fondo se forme repitiendo varias imágenes de pequeño
tamaño), podemos usar "SDL_CreateRGBSurface", que reserva el espacio para una superficie
de un cierto tamaño y con una cierta cantidad de colores, así:
(el parámetro SDL_SWSURFACE indica que no se trabaje en memoria física de la tarjeta, sino
en memoria del sistema; 640x480 es el tamaño de la superficie; 16 es la cantidad de color V
16bpp = 655356 coloresV; los otros 0,0,0,0 se refieren a la cantidad de rojo, verde, azul y
transparencia ValphaV de la imagen).
:
• Amplía el ejercicio de la nave, para que emplee un doble buffer que permita evitar
parpadeos.
44 @ H 1 2
Para abordar un juego completo, es frecuente no tener claro cómo debería ser la estructura del
juego: qué repetir y cuando.
Esta parte que se repite es lo que se suele llamar el “bucle de juego” (en inglés, “game loop”).
Su apariencia exacta depende de cada juego, pero en la mayoría podríamos encontrar algo
parecido a:
I
B
- ' : 3 : 0 1 #
7 ' 0 !
5 I 3 0
- ' 1 0 "
- " 0 "J 3
B
Revisión 0.90– Página 165
5 6 >
8 B
. '
El orden no tiene por qué ser exactamente éste, pero habitualmente será parecido. Vamos a
detallarlo un poco más:
:
• Amplía el ejercicio de la nave, para que emplee tenga una función “buclePrincipal”, que
siga la apariencia de un bucle de juego clásico, llamando a funciones con los nombres
“comprobarTeclas”, “dibujarElementos=”, “moverEstrella s=”, “mostrarPantallaOculta”.
Existirán algunas estrellas, cuya posición cambiará cuando se llame a “moverEstrellas=” y
que se dibujarán, junto con los demás componentes, al llamar a en “dibujarElementos=”.
Al final de cada “pasada” por el bucle principal habrá una pausa de 20 milisegundos,
para que la velocidad del “juego” no dependa del ordenador en que se ejecute.
• Ampliar el ejercicio anterior, para que también exista un “enemigo” que se mueva de
lado a lado de la pantalla.
• Ampliar el ejercicio anterior, para que haya 20 “enemigos=” (un array) que se muevan
de lado a lado de la pantalla.
• Ampliar el ejercicio anterior, para añadir la posibilidad de que nuestro personaje
“dispare”.
• Ampliar el ejercicio anterior, para que si un “disparo” toca a un “enemigo”, éste deje de
dibujarse.
• Ampliar el ejercicio anterior, para que la partida termine si un “enemigo” toca a nustro
personaje.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *;4 A:
:A %*; A:
:A A:
:A T ' A:
:A J A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
GH
' ;;
! . 6 ' 6 = W
! 1 ' 6 = W '
! ? 4 = W <
! ? 3" 1 ' 4 = W '
! 3" 4 = W *'
! 3" 2 6 4 = W ='
! 7 IM 4 = W )) $
! 7 4 = W '' $
%
. 6 ' 6 GH
1 ' 6 ;;
? 4 @GR
? 3" 1 ' 4 $
3" 4 **
3" 2 6 4 *R
7 IM 4 $;,
7 4 ;;
Revisión 0.90– Página 167
Para comprobar que es correcto, podemos convertir al sistema binario esos dos números y
seguir las operaciones paso a paso:
GH E %$%% %%$$
;; E %%$% %%%$
Después hacemos el producto lógico de A y B, multiplicando cada bit, de modo que 1*1 = 1,
1*0 = 0, 0*0 = 0
%%%% %%%$ E $
Después hacemos su suma lógica, sumando cada bit, de modo que 1+1 = 1, 1+0 = 1, 0+0 =
0
%$$% %%$$ E **
La suma lógica exclusiva devuelve un 1 cuando los dos bits son distintos: 1^1 = 0, 1^0 = 1,
0^0 = 0
%$$% %%$% E *R
Desplazar los bits una posición a la izquierda es como multiplicar por dos:
Desplazar los bits una posición a la derecha es como dividir entre dos:
%%$% %%%$ E ;;
¿Qué utilidades puede tener todo esto? Posiblemente, más de las que parece a primera vista.
Por ejemplo: desplazar a la izquierda es una forma muy rápida de multiplicar por potencias de
dos; desplazar a la derecha es dividir por potencias de dos; la suma lógica exclusiva (xor) es un
método rápido y sencillo de cifrar mensajes; el producto lógico nos permite obligar a que
ciertos bits sean 0 (algo que se puede usar para comprobar máscaras de red); la suma lógica,
por el contrario, puede servir para obligar a que ciertos bits sean 1...
2 <E +
2 E +
2 PE +
2 iE +
...
-
Desde el principio hemos estado manejando cosas como
Aunque “include” es la directiva que ya conocemos, vamos a comenzar por otra más sencilla, y
que nos resultará útil cuando lleguemos a ésta.
%Q #
La directiva “define” permite crear “constantes simbólicas=”. Podemos crear una constante
haciendo
! B5U ? T $%
! 5 E B5U ? T
El primer paso que hace nuestro compilador es reemplazar esa “falsa constante” por su valor,
de modo que la orden que realmente va a analizar es
! 5 E $%
pero a cambio nosotros tenemos el valor numérico sólo al principio del programa, por lo que es
muy fácil de modificar, mucho más que si tuviéramos que revisar el programa entero buscando
dónde aparece ese 10.
Vamos a ver un ejemplo completo, que pida varios números y muestre su suma y su media:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *,4 A:
:A %*, A:
:A A:
:A ?0 ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! -5 757 VB? T F
Las constantes simbólicas se suelen escribir en mayúsculas por convenio, para que sean más
fáciles de localizar. De hecho, hemos manejado hasta ahora muchas constantes simbólicas sin
saberlo. La que más hemos empleado ha sido NULL, que es lo mismo que un 0, está declarada
así:
! V.. %
Pero también en casos como la pantalla en modo texto con Turbo C aparecían otras constantes
simbólicas, como éstas
! S.V? $
! N?..Tf $,
Y al acceder a ficheros teníamos otras constantes simbólicas como SEEK_SET (0), SEEK_CUR
(1), SEEK_END (2).
A “define” también se le puede dar también un uso más avanzado: se puede crear “macros”,
que en vez de limitarse a dar un valor a una variable pueden comportarse como pequeñas
órdenes, más rápidas que una función. Un ejemplo podría ser:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *F4 A:
:A %*F A:
:A A:
:A ?0 ! + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
$ +
Revisión 0.90– Página 170
! I 4
! = $
! I " 4
! = +
! = W VB5 $ +
%
# + %Q
Ya nos habíamos encontrado con esta directiva. Lo que hace es que cuando llega el momento
de que nuestro compilador compruebe la sintaxis de nuestro fuente en C, ya no existe ese
“include”, sino que en su lugar el compilador ya ha insertado los ficheros que le hemos
indicado.
¿Y eso de por qué se escribe <stdio.h>, entre < y >? No es la única forma de usar #include.
Podemos encontrar líneas como
'
y como
El primer caso es un fichero de cabecera del compilador. Lo indicamos entre < y > y
así el compilador sabe que tiene que buscarlo en su directorio (carpeta) de “includes”. El
segundo caso es un fichero de cabecera que hemos creado , por lo que lo indicamos
entre comillas, y así el compilador sabe que no debe buscarlo entre sus directorios, sino en el
mismo directorio en el que está nuestro programa.
Vamos a ver un ejemplo: declararemos una función “suma” dentro de un fichero “.h” y lo
incluiremos en nuestro fuente para poder utilizar esa función “suma” sin volver a definirla. Wl
fichero de cabecera sería así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *G 4 A:
:A %*G A:
:A A:
:A ?0 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
2 1
2 1
(Nota: si somos puristas, esto no es correcto del todo. Un fichero de cabecera no debería
contener los detalles de las funciones, sólo su “cabecera”, lo que habíamos llamado el
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *G ' 4 A:
:A %*G A:
:A A:
:A ?0 + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
%*G
$ +
! I 4
! = $
! I " 4
! = +
! = W $ +
%
%Q# #* Q #
Hemos utilizado #define para crear constantes simbólicas. Desde dentro de nuestro fuente,
podemos comprobar si está definida una constante, tanto si ha sido creada por nosotros como
si la ha creado el sistema.
Cuando queramos que nuestro fuente funcione en varios sistemas distintos, podemos hacer que
se ejecuten unas órdenes u otras según de qué compilador se trate, empleando #ifdef (o #if) al
principio del bloque y #endif al final. Por ejemplo,
! ! &c--&
8 # " & -.T-a & ? & ?-
!
! ! & V ST-&
8 # " & -.a& -a
!
Los programas que utilicen esta idea podrían compilar sin ningún cambio en distintos sistemas
operativos y/ distintos compiladores, y así nosotros esquivaríamos las incompatibilidades que
pudieran existir entre ellos (a cambio, necesitamos saber qué constantes simbólicas define cada
sistema).
Esta misma idea se puede aplicar a nuestros programas. Uno de los usos más frecuentes es
hacer que ciertas partes del programa se pongan en funcionamiento durante la fase de
depuración, pero no cuando el programa esté terminado.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *H4 A:
:A %*H A:
:A A:
:A - A:
:A ! A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! -5 757 VB? T F
! 7? V 5 7T $
! = W
! =, +!W "# %-5 757 VB? T
Este fuente tiene intencionadamente un error: no hemos dado un valor inicial a la suma, con lo
que contendrá basura, y obtendremos un resultado incorrecto.
I $4 +
I +4 ;
I ;4 ,
I ,4 F
I F4 H
X 4 +%%*%FF*H$
X 4 +%%*%FF*H;
X 4 +%%*%FF*HG
X 4 +%%*%FF*R%
X 4 +%%*%FF*RF
+%%*%FF**+
,%$R$$$*R ,%
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *H'4 A:
:A %*H' - A:
:A A:
:A - A:
:A ! + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! -5 757 VB? T F
:A ! 7? V 5 7T $A:
! = W
! =, +!W "# %-5 757 VB? T
También tenemos otra alternativa: en vez de comentar la línea de #define, podemos anular la
definición con la directiva #undef:
! 7? V 5 7T
Por otra parte, también tenemos una directiva #ifndef para indicar qué hacer si no está definida
una contante simbólico, y un #else, al igual que en los “if” normales, para indicar qué hacer si
no se cumple la condición, y una directiva #elif (abreviatura de “else if”), por si queremos
encadenar varias condiciones.
! $
Las que hemos comentado son las directivas más habituales, pero también existen otras. Una
de las que son frecuentes (pero menos estándar que las anteriores) es #pragma, que permite
indicar opciones avanzadas específicas de cada compilador.
Revisión 0.90– Página 174
No veremos detalles de su uso en ningún compilador concreto, pero sí un ejemplo de las cosas
que podemos encontrar su manejamos fuentes creados por otros programadores:
! ! -& XTa?7
" # &&7?85. c
!
*
O
Es bastante frecuente que un programa no esté formado por un único fuente, sino por varios.
Puede ser por hacer el programa más modular debido a su complejidad, por reparto de trabajo
entre varias personas, etc.
En cualquier caso, la gran mayoría de los compiladores de C serán capaces de juntar varios
fuentes independientes y crear un único ejecutable a partir de todos ellos. Vamos a ver cómo
conseguirlo.
Empezaremos por el caso más sencillo: supondremos que tenemos un programa principal, y
otros dos módulos auxiliares en los que hay algunas funciones que se usarán desde el
programa principal.
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *R 4 A:
:A A:
:A A:
:A " A:
:A 6 ! $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! 8 3 W
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *R'4 A:
:A A:
:A A:
:A " A:
:A 6 ! + A:
:A A:
:A - -> A:
Revisión 0.90– Página 175
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! 8 3 > L = W
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *R 4 A:
:A A:
:A A:
:A " A:
:A 6 ! ; A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! ? " W
;
%
Para compilar los tres fuentes y crear un único ejecutable, desde la mayoría de los
compiladores de Dos o Windows bastaría con acceder a la línea de comandos, y teclear el
nombre del compilador seguido por los de los tres fuentes:
-- V T 7T ?
(TCC es el nombre del compilador, en el caso de Turbo C y de Turbo C++; sería BCC para el
caso de Borland C++, SCC para Symantec C++, etc.).
Entonces el compilador convierte los tres ficheros fuente (.C) a ficheros objeto (.OBJ), los
enlaza y crea un único ejecutable, que se llamaría UNO.EXE (porque UNO es el nombre del
primer fuente que hemos indicado al compilador), y cuya salida en pantalla sería:
? "
8 3
8 3 > L ;
En el caso de GCC para Linux (o de alguna de sus versiones para Windows, como MinGW, o
DevC++, que se basa en él), tendremos que indicarle el nombre de los ficheros de entrada (con
extensión) y el nombre del fichero de salida, con la opción “Vo”:
" @
! ? " W
;
%
el compilador nos dé un mensaje de error, diciendo que no conoce las funciones "uno()" y
"dos=()". No debería ser nuestro caso, si al compilar lehemos indicado los fuentes en el orden
correcto (TCC UNO DOS TRES), pero puede ocurrir si se los indicamos en otro orden, o bien si
tenemos muchos fuentes, que dependan los unos de los otros.
La forma de evitarlo sería indicándole que esas funciones existen, y que ya le llegarán más
tarde los detalles en concreto sobre cómo funcionan.
Para decirle que existen, lo que haríamos sería incluir en el programa principal los
de las funciones (las cabeceras, sin el desarrollo) que se encuentran en otros módulos, así:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q *R 4 A:
:A A:
:A A:
:A " A:
:A 6 ! ;' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ! 2 A:
:A - " A:
! ? " W
;
%
Esta misma solución de poner los prototipos al principio del programa nos puede servir para
casos en los que teniendo un único fuente queramos declarar el cuerpo del programa antes
que las funciones auxiliares(
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q **4 A:
:A %** A:
:A A:
:A 8 / A:
:A ! A:
:A - " A:
! ? " W
;
%
! 8 3 W
! 8 3 > L = W
En ciertos compiladores puede que tengamos problemas con este programa si no incluimos los
prototipos al principio, porque en "main()" se encuentra la llamada a "uno()", que no hemos
declarado. Al poner los prototipos antes, el compilador ya sabe qué tipo de función es "uno()"
(sin parámetros, no devuelve ningún valor, etc.), y que los datos concretos los encontrará más
adelante.
De hecho, si quitamos esas dos líneas, este programa no compila en Turbo C++ 1.01 ni en
Symantec C++ 6.0, porque cuando se encuentra en "main()" la llamada a "uno()", da por
supuesto que va a ser una función de tipo "int". Como después le decimos que es "void",
protesta. (En cambio, GCC, que suele ser más exigente, en este caso se limita a avisarnos,
pero compila el programa sin problemas).
La solución habitual en estos casos en que hay que declarar prototipos de funciones
(especialmente cuando se trata de funciones compartidas por varios fuentes) suele ser agrupar
estos fuentes en "ficheros de cabecera". Por ejemplo, podríamos crear un fichero llamado
EJEMPLO.H que contuviese:
:A ?`?B .T A:
:A ! A:
0
Revisión 0.90– Página 178
! ? " W
;
%
Aquí es importante recordar la diferencia en la forma de indicar los dos ficheros de cabecera:
<stdio.h> Se indica entre corchetes angulares porque el fichero de cabecera es propio del
compilador (el ordenador lo buscará en los directorios del compilador).
"ejemplo.h" Se indica entre comillas porque el fichero H es nuestro (el ordenador lo buscará
en el mismo directorio que están nuestros fuentes).
Finalmente, conviene hacer una consideración: si varios fuentes distintos necesitaran acceder a
EJEMPLO.H, deberíamos evitar que este fichero se incluyese varias veces. Esto se suele
conseguir definiendo una variable simbólica la primera vez que se enlaza, de modo que
podamos comprobar a partir de entonces si dicha variable está definida, con #ifdef, así:
:A ?`?B .T 0 A:
! ! &?`?B .T&
! &?`?B .T&
:A ! A:
+ ( 3
Make es una herramienta que muchos compiladores incorporan y que nos puede ser de utilidad
cuando se trata de proyectos de un cierto tamaño, o al menos creados a partir de bastantes
fuentes.
Su uso normal consiste simplemente en teclear MAKE. Entonces esta herramienta buscará su
fichero de configuración, un fichero de texto que deberá llamarse "makefile" (podemos darle
otro nombre; ya veremos cómo). Este fichero de configuración le indica las dependencias entre
ficheros, es decir, qué ficheros es necesario utilizar para crear un determinado "objetivo". Esto
permite que no se recompile todos y cada uno de los fuentes si no es estrictamente necesario,
sino sólo aquellos que se han modificado desde la última compilación.
'0 6 4
3
Vamos a crear un ejecutable llamado PRUEBA.EXE a partir de cuatro ficheros fuente llamados
UNO.C, DOS.C, TRES.C, CUATRO.C, usando Turbo C++.
Así, nuestro primer “makefile” podría ser un fichero que contuviese el siguiente texto:
Es decir: nuestro objetivo es conseguir un fichero llamado PRUEBA.EXE, que queremos crear a
partir de varios ficheros llamados UNO.C, DOS.C, TRES.C y CUATRO.C. La orden que queremos
dar es la que aparece en la segunda línea, y que permite, mediante el compilador TCC, crear un
ejecutable llamado PRUEBA.EXE a partir de cuatro fuentes con los nombres anteriores. (La
opción "Ve" de Turbo C++ permite indicar el nombre que queremos que tenga el ejecutable; si
no, se llamaría UNO.EXE, porque tomaría su nombre del primero de los fuentes).
¿Para qué nos sirve esto? De momento, nos permite ! : cada vez que tecleamos
MAKE, se lee el fichero MAKEFILE y se compara la fecha (y hora) del objetivo con la de las
dependencias; si el fichero objetivo no existe o si es más antiguo que alguna de las
dependencias, se realiza la orden que aparece en la segunda línea (de modo que evitamos
escribirla completa cada vez).
En nuestro caso, cada vez que tecleemos MAKE, ocurrirá una de estas tres posibilidades
Eso sí, estamos dando por supuesto varias cosas “casi evidentes=”:
- ! 0 '
- ! '0
'04
@
'04
@
'04
@
Estamos detallando los pasos que normalmente se dan al compilar, y que muchos compiladores
realizan en una única etapa, sin que nosotros nos demos cuenta: primero se convierten los
ficheros fuente (ficheros con extensión C) a código máquina (código objeto, ficheros con
extensión OBJ) y finalmente los ficheros objeto se enlazan entre sí (y con las bibliotecas propias
del lenguaje) para dar lugar al programa ejecutable (en MsDos y Windows normalmente serán
ficheros con extensión EXE).
Así conseguimos que cuando modifiquemos un único fuente, se recompile sólo este (y no todos
los demás, que pueden ser muchos) y después se pase directamente al proceso de enlazado,
con lo que se puede ganar mucho en velocidad si los cambios que hemos hecho al fuente son
pequeños.
(Nota: la opción "Vc" de Turbo C++ es la que le indica que sólo compile los ficheros de C a OBJ,
pero sin enlazarlos después).
Si tenemos varios MAKEFILE distintos (por ejemplo, cada uno para un compilador diferente, o
para versiones ligeramente distintas de nuestro programa), nos interesará poder utilizar
nombres distintos.
Esto se consigue con la opción "Vf" de la orden MAKE, por ejemplo si tecleamos
Podemos mejorar más aún estos ficheros de configuración. Por ejemplo, si precedemos la
orden por @, dicha orden no aparecerá escrita en pantalla
Y si precedemos la orden por & , se repetirá para los ficheros indicados como "dependencias".
Hay que usarlo en conjunción con la macro $**, que hace referencia a todos los ficheros
dependientes, o $?, que se refiere a los ficheros que se hayan modificado después que el
objetivo.
" 4
P 1 (AA 4W!
Una última consideración: podemos crear nuestras propias macros, con la intención de que
nuestro MAKEFILE resulte más fácil de leer y de mantener, de modo que una versión más
legible de nuestro primer fichero sería:
8V? ? E
-TB . E
' 2 4 ( 8V? ?
( -TB . @ ' 2 ( 8V? ?
Pero todavía hay más que no hemos visto. Las herramientas MAKE suelen permitir otras
posibilidades, como la comprobación de condiciones (con las directivas "!if", "!else" y similares)
o la realización de operaciones (con los operadores estándar de C: +, *, %, >>, etc). Quien
quiera profundizar en estos y otros detalles, puede recurrir al manual de la herramienta MAKE
que incorpore su compilador.
¿Alguna diferencia en Linux? Pocas. Sólo hay que recordar que en los sistemas Unix se
distingue entra mayúsculas y minúsculas, por lo que la herramienta se llama “make”, y el
fichero de datos “Makefile” o “makefile” (preferible la primera nomenclatura, con la primera
letra en mayúsculas). De igual modo, el nombre del compilador y los de los fuentes se deben
F - G
En la gran mayoría de los compiladores que incorporan un “entorno de desarrollo”, existe la
posibilidad de conseguir algo parecido a lo que hace la
herramienta MAKE, pero desde el propio entorno. Es lo
que se conoce como “crear un proyecto”.
Desde esta primera ventana también le daremos ya un nombre al proyecto (será el nombre que
tendrá el fichero ejecutable), y también desde aquí podemos añadir los fuentes que formarán
parte del proyecto, si los tuviéramos creados desde antes (suele ser algo como “Añadir fichero”,
o en inglés “Add Item”).
&=
Conocemos lo que es un struct: un dato formado por varios “trozos” de información de distinto
tipo. Pero C también tiene dos tipos especiales de “struct”, de manejo más avanzado. Son las
uniones y los campos de bits.
Una recuerda a un “struct” normal, con la diferencia de que sus “campos=” comparten el
mismo espacio de memoria:
\
:A $ '1 A:
:A , '1 A:
] 0
En este caso, la variable “ejemplo” ocupa 4 bytes en memoria (suponiendo que estemos
trabajando en un compilador de 32 bits, como lo son la mayoría de los de Windows y Linux). El
primer byte está compartido por “letra” y por “numero”, y los tres últimos bytes sólo
pertenecen a “numero”.
Si hacemos
0 E +F
0 E F%
! = > 0
& &' \
' &$ 4 $
' &+& &F 4 ,
' &G 4 $
' &H& &$G 4 $%
] 6 ' 7 S
Esta variable ocuparía 1+4+1+10 = 16 bits (2 bytes). Los campos de bits pueden ser
interesantes cuando queramos optimizar al máximo el espacio ocupado por nuestro datos.
)
Cuando vimos la orden “for”, siempre usábamos una única variable como contador, pero esto
no tiene por qué ser siempre así. Vamos a verlo con un ejemplo:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $%%4 A:
:A $%% A:
:A A:
:A T $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
" % 0 $ ) F 0) ;% 0 +
! 6 = 1 0 6 = W 0
El único problema está en saber cúando terminará el bucle: si se parará en cuanto se cumpla
una de las dos condiciones o si tendrán que cumplirse las dos.
Como podemos observar, llega un momento en que deja de cumplise que i<=5, pero el
programa sigue avanzando: no se sale del bucle “for” hasta que se cumplen
(realmente, hasta que se cumple la segunda).
La idea es que, en general, si se usa el operador coma para separar dos expresiones, nuestro
compilador evalúa la primera expresión, luego la segunda, y devuelve como valor el resultado
de la segunda. Veámoslo con un segundo ejemplo algo más rebuscado
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $%$4 A:
:A $%$ A:
:A A:
:A T + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
0 #
# F 0 G
Aun así, en la práctica, el único uso habitual del operador coma es el primero que hemos visto:
utilizar dos condiciones simultáneas para controlar un bucle “for”.
+
Cuando tenemos varias constantes, cuyos valores son números enteros, y especialmente si son
números enteros consecutivos, tenemos una forma abreviada de definirlos. Se trata de
:
(Al igual que las constantes de cualquier otro tipo, se suele escribir en mayúsculas para
recordar en cualquier parte que se sepa "de un vistazo" que son constantes, no variables)
Si queremos que los valores no sean exactamente estos, podemos dar valor a cualquiera de las
contantes, y las siguientes irán aumentando de uno en uno. Por ejemplo, si escribimos
.V ? $ B5 ? B ? -T.? `V?X? G X ? ?
5S57T $% 7TB cT
, - *
El tipo de una variable nos indica el rango de valores que puede tomar. Tenemos creados para
nosotros los tipos básicos, pero puede que nos interese crear nuestros propios tipos de
variables, para lo que usamos “ % ”. El formato es
1 ! '
Lo que hacemos es darle un nuevo nombre a un tipo de datos. Puede ser cómodo para hacer
más legible nuestros programas. Por ejemplo, alguien que conozca Pascal o Java, puede echar
en falta un tipo de datos “boolean”, que permita hacer comparaciones un poco más legibles,
siguiendo esta estructura:
" 85. ?
o bien como
" - V?
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $%+4 A:
:A $%+ A:
:A A:
:A 7 ! 3 $ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
! X5. 75 H$$ :A - 6 A:
! V? $ :A " ' A:
! 85. ? %
" 6 :A N 6 ' A:
'
! I 6 / 4
! = 6
6 X5. 75
" 85. ? ! 6L YW
# ( V?
! 5 W
%
También podemos usar "typedef" para dar un nombre corto a todo un struct:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A ?0 - Q $%;4 A:
:A $%; A:
:A A:
:A 7 ! 3 + A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@@@A:
> " :A 7 ! A:
6
"#
!
0 :A N 6 ' A:
0 6 ;,
0 $+%% F
0 ! 5
! ? 6 = 0 6
%
0.90, de 06VEneV10. Añadido un apartado sobre SDL (10.5). Ligeramente ampliados los apartados
0.2, 1.1. Algún párrafo reescrito en 1.2. Ligeramente reescrito algún párrafo del tema 2, añadido
un párrafo en 2.1.12, reescrito un párrafo en 4.1. Ampliado el contenido del apartado 4.4.
Añadidos ejercicios propuestos adicionales en 3.1.3 (1), 3.1.4 (2), 3.1.5 (2), 3.1.8 (1), 3.1.9 (2),
3.1.10 (2), 3.2.1 (2), 3.2.3 (3), 3.7 (2), 4.1 (2), 4.2 (3), 4.3 (1), 4.4 (1), 5.1.1 (2), 5.1.2 (1),
5.1.4 (2), 5.2.3 (2), 5.2.4 (1), 5.2.5 (1), 5.2.6 (2), 5.3 (2), 5.5.1 (1), 5.5.2 (1), 5.5.3 (1), 5.6 (5),
5.7 (1), 6.1 (1), 6.2 (1), 6.4 (2), 6.6 (2), 6.8 (2), 6.10 (4), 7.4 (3), 7.5 (1), 7.6 (6), 7.10 (6).
Incluidos ejemplos de posibles soluciones a los ejercicios propuestos en 1.2 (199 páginas, 126
ejemplos, 145 ejercicios propuestos V15 de ellos resueltosV).
0.23, de 11VEneV07. Más detallado el apartado 5.2.7 y el 7.8. Añadido un ejemplo más a 7.3.
Revisados muchos fuentes para usar variables locales, en vez de globales, siempre que fuera
posible. En el tema 5.6 había dos ejemplos de agenda muy parecidos, eliminado uno de ellos.
(174 páginas).
0.22, de 31VOctV06. Añadidos al tema 3 varios ejercicios resueltos (14), relacionados con fallos
frecuentes de los alumnos. Ligera revisión ortográfica. (175 páginas).
0.21, de 07VJulV06. Incluye hasta el apartado 11.7 (172 páginas).
0.20, de 09VJunV06. Incluye hasta el apartado 11.5 (169 páginas).
0.19, de 08VMayV06. Incluye hasta el apartado 11.3.2 (160 páginas).
0.18, de 27VAbrV06. Incluye hasta el apartado 11.2.3 (152 páginas).
0.17, de 04VAbrV06. Completado el tema de “punteros y memoria dinámica” (tema 9) y el de
“bibliotecas útiles” (tema 10) (145 páginas).
0.16, de 21VMarV06. Ampliado el tema de “punteros y memoria dinámica”, hasta el apartado 9.10
(132 páginas).
0.15, de 19VMarV06. Añadido un tema sobre depuración (nuevo tema 8, el tema de “punteros”
pasa a ser el tema 9). Ampliado el tema de “punteros y memoria dinámica” con un ejemplo
básico, información sobre parámetros por valor y por referencia, aritmética de punteros y la
equivalencia entre punteros y arrays. Ampliado el apartado de “funciones” para hablar de el
orden de las funciones (tema 7.8, renumerados los posteriores) (123 páginas).
0.14, de 16VFebV06. Ampliado el apartado de “funciones” para hablar de números aleatorios y de
funciones matemáticas. Ampliado el apartado de “punteros y memoria dinámica” con un primer
ejemplo de su uso (114 páginas).
0.13, de 05VFebV06. Ampliado el apartado de “funciones” y comenzado el apartado de “punteros
y memoria dinámica”. Añadido el apartado 6.13, con un ejemplo de lectura y escritura en un
fichero. Añadidos ejercicios en los apartados 2.1.3, 2.1.7, 2.1.10, 2.2, 2.3, 3.1.5, 3.2.3...
Corregida una errata en el formato de “fread” y “fwrite”. (108 páginas, cambia la numeración a
partir de la página 27 por la inclusión de esos ejercicios básicos).
0.12, de 25VEneV06. Completado el capítulo de “ficheros” y comenzado el de “funciones” (hasta el
apartado 7.6, 100 páginas).
0.11, de 11VEneV06. Añadido un ejemplo de “array” para almacenar muchos registros. Más
ejemplos sobre ficheros: cómo mostrar datos de una imagen BMP (89 páginas, cambia la
numeración a partir de la página 79 por incluir el ejemplo de registros).
0.10, de 12VDicV05. Más información sobre ficheros: binarios, lectura y escritura, acceso directo...
(86 páginas).
0.09, de 30VNovV05. Añadida información sobre “structs” y sobre lectura y escritura de ficheros
de texto (tema 6.2). Corregidos alguno de los formatos de “scanf”, que el procesador de textos
había convertido a mayúsculas incorrectamente (82 páginas).
0.08, de 14VNovV05. Incluye hasta el tema 5.4 (76 páginas).
0.07, de 30VOctV05. Incluye hasta el tema 5.2.4 (70 páginas).
0.06, de 23VOctV05. Incluye todo el tema 3 (60 páginas).
0.05, de 10VOctV05. Incluye hasta el tema 3.3 (56 páginas).
Soluciones incluidas en esta versión del curso: 15 (de 145 ejercicios propuestos totales).
\
! . $$R 1 FG = > $$R<FG
]
\
! @+ < ; A F E = ? > :: 4 $;
@+ < ; A F
! +%<F == G E = ? > :: 4 $
+%<F = G
! $F < @FAG : $% E = ? > :: 4 $+
$F < @FAG : $%
! + < $% : F A + @ H == $ E = ? > :: 4 G
+ < $% : F A + @ H = $
]
( ) ! * +
\
E $+$
' E $;+
E A'
! ? = >
]
, )
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A $ H $ A:
:A %$%H%$ A:
:A A:
:A B A:
Revisión 0.90– Página 191
:A 6 ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
> '>
!
! = > P
! "
! = > P'
E A'
! = >
]
- . ) / * .
+ 0 + 0
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A $ H $' A:
:A %$%H%$' A:
:A A:
:A 7 6 1 + A:
:A 6 ' A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
2> 1
6 >
!
! = > P2
! "
! = > P1
6 E 2:1
E 2:1
! 6 = > 6
! ? 6 = >
]
- . ) *
0 " 0
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
Revisión 0.90– Página 192
:A 0 A:
:A $ H $ A:
:A %$%H%$ A:
:A A:
:A - <' @' A:
:A +@'+ A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
> '
!
! = > P
! "
! = > P'
, ) 1 2 * 2 3 4
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + $ $ A:
:A %+%$%$ A:
:A A:
:A B A:
:A , ! > " A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
" > '
!
! = > P
! "
! = > P'
! 4 = > A'
]
+ /* *5 6 + . * $*
1' 1 ! 1' 6 . + + 78 +
9 .
\
2E$F> 1E@$%> IE+$,H,R;G,H
2<<
1<<
I<<
7 . : 9 ; # ; # ; # ; " #
; " #
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + $ $%' A:
:A %+%$$%' A:
:A A:
:A 6 A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
> '>
EF
'E<<
E <<
'E'AF
E A+
+ /* *5 6 + . * $*
1 6 . + + * 2 + 78 +
9 .
Revisión 0.90– Página 194
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + $ $$ A:
:A %+%$$$ A:
:A A:
:A ?0 <E A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
2E$F> 1E@$%> IE+$,
2 <E $+
1 <E $+
I <E $+
7 . : 9 ; # ; # ;!# ; !# "; #
# "; #
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + $ $$' A:
:A %+%$$$' A:
:A A:
:A 6 A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
> '>
EF
'E <+
'@E;
E@;
AE+
<<
AE'
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + + + A:
:A %+%+%+ A:
:A A:
:A 7 6 A:
:A ' 3 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
' > '
!
! = ! > P
! "
! = ! > P'
! 6 3 4 = ; ! > :'
]
+ /* *5 = . ) *
. 2 > . + +
)
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + + +' A:
:A %+%+%+' A:
:A A:
:A 2>1>I > A:
:A 3 > 1 A:
:A 1 A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
! 2> 1> I
! 1
!
! =! > P2
! "
! =! > P1
!
! =! > PI
! ? 1 4 =! > 1
]
> 3 4 +
5
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + ; A:
:A %+%; A:
:A A:
:A ? A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
! ? 4 = > I !
]
? * + * @ ? * * *
<* : A< A
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
:A 0 A:
:A + F $ A:
:A %+%F%$ A:
:A A:
:A , A:
:A > A:
:A 6 1 A:
:A A:
:A A:
:A - -> A:
:A - ' A:
:A@@@@@@@@@@@@@@@@@@@@@@@@@A:
\
$> +> ;> ,
! 4
! = = > P $
! " 4
! = = > P +
! 4
Revisión 0.90– Página 197
! = = > P ;
! 4
! = = > P ,
! 5 6 M ?@= = = = ?@ >
,> ;> +> $
]