Está en la página 1de 397

UNIVERSIDAD

POLITCNICA
DE CARTAGENA

rea de Lenguajes y Sistemas Informticos










FUNDAMENTOS DE INFORMTICA.
PROGRAMACIN EN C.
Pedro Mara Alcover Garau































Ingeniero Industrial
Ingeniero Tcnico Industrial
















FUNDAMENTOS DE INFORMTICA.
PROGRAMACIN EN C.

Pedro Mara Alcover Garau











rea de Lenguajes y Sistemas Informticos
Universidad Politcnica de Cartagena

Septiembre, 2005






















Pedro Mara Alcover Garau


Edita Universidad Politcnica de Cartagena
Segunda Edicin revisada y ampliada: Junio 2007.


ISBN: 8495781611

Depsito Legal MU18692005.

Imprime Morpi, S.L.




















A todos los alumnos
de la Universidad Politcnica de Cartagena
que estudian.
A muchos les he tenido en mis clases.
Doy gracias a Dios por haberles conocido.












PRESENTACIN

Despus de varios aos de dar clases de fundamentos de informtica y
de programacin en lenguaje C en algunas titulaciones de la Escuela de
Ingenieros Industriales de la UPCT, me he decidido a poner por escrito,
para gua del alumno, un manual que recoja toda la materia que se
imparte en esas asignaturas.
La informacin que aqu se recoge supera las posibilidades de docencia
que pueden alcanzarse en una asignatura cuatrimestral. He querido
extenderme ms all de lo que es posible ver en las aulas, porque creo
que vale la pena ofrecer la posibilidad de que alguien que adquiera este
manual para apoyo de una asignatura, pueda luego continuar
estudiando y ampliar sus conocimientos, y profundizar.
De nuevo he tenido el tiempo muy limitado para la revisin y ampliacin
de estas pginas. Ahora es urgente mandar estas pginas a imprenta,
porque el curso acadmico comienza hoy mismo.
Seguro que quien haga uso de este manual detectar errores y erratas.
Tambin apreciar modos de decir que podran mejorarse, y que
ofreceran una mayor claridad de exposicin.


Agradecer recibir todas las sugerencias, porque as se podr ofrecer, a
quienes vengan detrs, una versin del manual mejorada. Se puede
contactar conmigo a travs del correo electrnico. Mi direccin es
pedro.alcover@upct.es.
Muchas gracias.
Cartagena, 25 de septiembre de 2006

Con fecha 20 de junio de 2007 termino una revisin en la que he
incluido todas las modificaciones que los alumnos y profesores me han
sugerido. Es evidente que agradezco esta colaboracin; algunos errores
dificultaban la comprensin del texto.
A partir de algunas dudas que me han planteado los alumnos he
decidido introducir algunos nuevos epgrafes que no estaban en la
ltima versin del manual. No aade nada sustancial al texto anterior.





i








NDICE



PARTE I. Primeros pasos en C 1

CAPTULO 1
LENGUAJE C. 5
Introduccin 5
Entorno de programacin. 7
Estructura bsica de un programa en C. 9
Elementos lxicos 12
Sentencias simples y sentencias compuestas 13
Errores y depuracin 14
Recapitulacin 15
El entorno de Borland C++ 15

CAPTULO 2
TIPOS DE DATOS Y VARIABLES EN C 19
Declaracin de variables. 20
Tipos de datos primitivos en C: sus dominios. 22
Tipos de datos primitivos en C: sus operadores. 25
Operador asignacin. 26
Operadores aritmticos. 27
Operadores relacionales y lgicos. 30
Operadores a nivel de bit. 34


ii
Operadores compuestos. 39
Operador sizeof 40
Expresiones en las que intervienen variables de diferente
tipo. 41
Operador para forzar cambio de tipo. 43
Propiedades de los operadores. 45
Valores fuera de rango en una variable. 48
Constantes. Directiva #define. 50
Intercambio de valores de dos variables. 51
Ayudas On line. 52
Recapitulacin 52
Ejemplos y Ejercicios propuestos (del 1 al 15) 53

CAPTULO 3
FUNCIONES DE ENTRADA Y SALIDA POR
CONSOLA 65
Salida de datos. La funcin printf() 66
Entrada de datos. La funcin scanf() 74
Recapitulacin 76
Ejercicios (del 16 al 19) 77
ANEXO: Ficha resumen de la funcin printf() 83

CAPTULO 4
ESTRUCTURAS DE CONTROL 87
Introduccin 87
Conceptos previos 89
Estructuras de control condicionales 90
Estructura de seleccin mltiple: Sentencia switch 97
Un ejercicio planteado. 101
Estructuras de repeticin. Iteracin. 104
Sentencias de salto: break y continue 116
Palabra reservada goto 120
Variables de control de las iteraciones 120
Recapitulacin. 122
Ejercicios. (del 20 al 39) 122



iii
CAPTULO 5
MBITO Y VIDA DE LAS VARIABLES 147
mbito y Vida. 147
El almacenamiento de las variables en la memoria. 148
Variables Locales y Variables Globales 150
Variables estticas y dinmicas. 154
Variables en registro. 156
Variables extern 157
En resumen 158
Ejercicios (40) 160

CAPTULO 6
ARRAYS NUMRICOS: VECTORES Y MATRICES 163
Nocin y declaracin de array. 164
Nocin y declaracin de array de dimensin mltiple, o
matrices. 166
Ejercicios (del 41 al 47) 169

CAPTULO 7
CARACTERES Y CADENAS DE CARACTERES 181
Operaciones con caracteres. 182
Entrada de caracteres. 185
Cadena de caracteres. 186
Dar valor a una cadena de caracteres. 188
Operaciones con cadenas de caracteres. 191
Otras funciones de cadena. 196
Ejercicios. (del 48 al 51) 199

CAPTULO 8
PUNTEROS 203
Definicin y declaracin 204
Dominio y operadores para los punteros 205
Punteros y vectores 209
ndices y operatoria de punteros 212


iv
Puntero a puntero 215
Advertencia final 219
Ejercicios. (del 52 al 53) 221

CAPTULO 9
FUNCIONES 223
Definiciones 224
Funciones en C 227
Declaracin de la funcin. 228
Definicin de la funcin. 230
Llamada a la funcin 232
La sentencia return 233
mbito y vida de las variables 236
Recurrencia 239
Llamadas por valor y llamadas por referencia 243
Vectores y matrices como argumentos 246
Funciones de escape 249
Ejercicios. (del 54 al 63) 250

PARTE II. Profundizando en C 273

CAPTULO 10
ASIGNACIN DINMICA DE MEMORIA 277
Funcin malloc 279
Funcin free 283
Ejemplo: la Criba de Erastthenes. (Ejercicio 64) 283
Matrices en memoria dinmica 288
Ejercicios. (65) 292

CAPTULO 11
ALGUNOS USOS CON FUNCIONES 299
Punteros a funciones 300
Vectores de punteros a funciones 303


v
Funciones como argumentos 305
Ejemplo: la funcin qsort 308
Estudio de tiempos 312
Creacin de MACROS 315
Ejemplo de MACRO: la Criba de Erastthenes 316
Funciones con un nmero variable de argumentos 320
Argumentos de la lnea de rdenes 325
Ejercicios. (del 66 al 67) 328

CAPTULO 12
ESTRUCTURAS ESTTICAS DE DATOS Y
DEFINICIN DE TIPOS 329
Tipos de dato enumerados 330
Dar nombre a los tipos de dato 331
Estructuras de datos y tipos de dato estructurados 333
Estructuras de datos en C 334
Vectores y punteros a estructuras 339
Anidamiento de estructuras 341
Tipo de dato union 344
Ejercicios. (68) 348

CAPTULO 13
GESTIN DE ARCHIVOS 353
Tipos de dato con persistencia 354
Archivos y sus operaciones 356
Archivos de texto y binarios 359
Tratamiento de archivos en el lenguaje C 359
Archivos secuenciales con buffer. 361
Entrada y salida sobre archivos de acceso aleatorio 373
Ejercicios. (del 69 al 70) 375





vi








PARTE I:
Primeros pasos
en lenguaje C.
Fundamentos de informtica. Programacin en Lenguaje C


2
PARTE I: Primeros pasos en lenguaje C.






El lenguaje C es un lenguaje de alto o medio nivel.
Un lenguaje de programacin es un conjunto de palabras, de
smbolos y de reglas para combinar estos smbolos, que se usan para
expresar algoritmos y construir programas. Los lenguajes de
programacin, como todos los lenguajes, poseen un lxico (vocabulario
o conjunto de smbolos permitidos), una sintaxis (que recoge las reglas
que indican cmo realizar construcciones de lenguaje) y una semntica
(que indica el significado de cada construccin concreta).
A lo largo de esta primera parte del manual, veremos cmo programar
en C. Aprenderemos muchas de las palabras el lxico de C y las normas
sintcticas para construir sentencias y programas. Comenzaremos con
un breve captulo introductorio donde se presentan los conceptos
bsicos de los lenguajes de programacin y, en concreto, del lenguaje C.
Y captulo a captulo iremos aprendiendo a trabajar y programar en este
lenguaje. Mostraremos el modo en que se crean las variables que
almacenarn la informacin de nuestros programas y diferentes
caractersticas y propiedades de las variables creadas (Captulos 2 y 5);
La forma que tiene el lenguaje C de lograr comunicacin entre el
programa y el usuario: mostrar datos por pantalla o solicitar nueva
informacin por teclado (Captulo 3). El captulo 4 est enteramente
dedicado a las estructuras de control, que permiten modificar el orden
secuencial de ejecucin de instrucciones, o decidir la ejecucin de una
instruccin u otra alternativa: veremos cmo se expresan estas
estructuras en el lenguaje C. Tambin veremos cmo crear estructuras
sencillas de datos: agrupaciones de variables del mismo tipo, creadas

3
Fundamentos de informtica. Programacin en Lenguaje C

como cadenas de caracteres o como vectores o matrices numricas
(Captulos 6 y 7)
Al final de esta Primera Parte tenemos un primer capitulo introductorio
para las funciones (Captulo 9); le seguir luego otro de ampliacin de
nociones, incluido ya en la Tercera Parte del manual. Pero antes de
llegar a este ltimo captulo de esta Primera Parte, veremos, en el
Captulo 8, un aspecto de la programacin en C, ciertamente
controvertido, que aporta cierta complejidad y peligro a la programacin
en el lenguaje C, pero que ofrece tambin, a cambio, cierta capacidad
de dominio del programador sobre la gestin de memoria del programa:
los punteros.
Esta primera parte completa la informacin que el alumno de primero de
una carrera de industriales debe aprender. En la Segunda Parte
quedarn recogidos aquellos Captulos para un posterior estudio, por si
algn alumno desea o requiere saber ms sobre la programacin en C.
Esta manual es continuacin de otro de estudio previo titulado
Fundamentos de Informtica. Codificacin y Algoritmia, editado,
como ste, por la Universidad Politcnica de Cartagena, y del mismo
autor que el presente manual.

4







CAPTULO 1

LENGUAJE C.

Presentamos en este captulo una primera vista de la programacin en
lenguaje C. El objetivo ahora es mostrar los conceptos bsicos de un
entorno de programacin, y redactar, con el entorno que cada uno
quiera (a lo largo del curso emplearemos fundamentalmente el Turbo
C++, de la casa Borland), un primer programa en C, que nos servir
para conocer las partes principales de un programa.

Introduccin.
Los lenguajes de programacin estn especialmente diseados para
programar computadoras. Sus caractersticas fundamentales son:
1. Son independientes de la arquitectura fsica del ordenador.
Los lenguajes estn, adems, normalizados, de forma que queda
garantizada la portabilidad de los programas escritos en esos
lenguajes.
Fundamentos de informtica. Programacin en Lenguaje C.


6
2. Normalmente un mandato en un lenguaje de alto nivel da lugar, al
ser introducido, a varias instrucciones en lenguaje mquina.
3. Utilizan notaciones cercanas a las habituales, con sentencias y
frases semejantes al lenguaje matemtico o al lenguaje natural.
El lenguaje C se dise en 1969. El lenguaje, su sintaxis y su semntica,
as como el primer compilador de C fueron diseados y creados por
Dennis M. Ritchie y Ken Thompson, en los laboratorios Bell. Ms tarde,
en 1983, se defini el estndar ANSI C (que es el que aqu
presentaremos).
El lenguaje C tiene muy pocas reglas sintcticas, sencillas de aprender.
Su lxico es muy reducido: tan solo 32 palabras.
A menudo se le llama lenguaje de medio nivel, ms prximo al cdigo
mquina que muchos lenguajes de ms alto nivel. Es un lenguaje
apreciado en la comunidad cientfica por su probada eficiencia. Es el
lenguaje de programacin ms popular para crear software de sistemas,
aunque tambin se utiliza para implementar aplicaciones. Permite el uso
del lenguaje ensamblador en partes del cdigo, trabaja a nivel de bit, y
permite modificar los datos con operadores que manipulan bit a bit la
informacin. Tambin se puede acceder a las diferentes posiciones de
memoria conociendo su direccin.
El lenguaje C es un lenguaje del paradigma imperativo, estructurado.
Permite con facilidad la programacin modular, creando unidades que
pueden compilarse de forma independiente, que pueden posteriormente
enlazarse. As, se crean funciones o procedimientos, que se pueden
compilar y almacenar, creando bibliotecas de cdigo ya editado y
compilado que resuelve distintas operaciones. Cada programador puede
disear sus propias bibliotecas, que simplifican luego considerablemente
el trabajo futuro. El ANSI C posee una amplia coleccin de bibliotecas de
funciones estndar y normalizadas.

Captulo 1. Lenguaje C.


7
Entorno de programacin.
Para realizar la tarea de escribir el cdigo de una aplicacin en un
determinado lenguaje, y poder luego compilar y obtener un programa
que realiza la tarea planteada, se dispone de lo que se denomina un
entorno de programacin.
Un entorno de programacin es un conjunto de programas necesarios
para construir, a su vez, otros programas. Un entorno de programacin
incluye editores, compiladores, archivos para incluir, archivos de
biblioteca, enlazadores y depuradores (ya veremos todos estos
conceptos en el primer Captulo de este manual). Gracias a Dios existen
entornos de programacin integrados, de forma que en una sola
aplicacin quedan reunidos todos estos programas. Ejemplos de
entornos integrados de programacin en C son el programa Microsoft
Visual C++, o el Turbo C++ de Borland.
Un editor es un programa que permite construir ficheros de caracteres,
que el programador introduce a travs del teclado. Un programa no es
ms que archivo de texto. El programa editado en el lenguaje de
programacin se llama fichero fuente. Algunos de los editores facilitan
el correcto empleo de un determinado lenguaje de programacin, y
advierten de inmediato la insercin de una palabra clave, o de la
presencia de un error sintctico, marcando el texto de distintas formas.
Un compilador es un programa que compila, es decir, genera ficheros
objeto que entiende el ordenador. Un archivo objeto todava no es
una archivo ejecutable.
El entorno ofrece tambin al programador un conjunto de archivos para
incluir o archivos de cabecera. Esos archivos suelen incluir
abundantes parmetros que hacen referencia a diferentes caractersticas
de la mquina sobre la que se est trabajando. As, el mismo programa
en lenguaje de alto nivel, compilado en mquinas diferentes, logra
archivos ejecutables distintos. Es decir, el mismo cdigo fuente es as
Fundamentos de informtica. Programacin en Lenguaje C.


8
portable y vlido para mquinas diferentes.
Otros archivos son los archivos de biblioteca. Son programas
previamente compilados que realizan funciones especficas. Suele
suceder que muy diversos programas tienen idnticas o muy parecidas
muchas partes del cdigo. Ciertas partes que son ya conocidas porque
son comunes a la mayor parte de los programas estn ya escritas y
vienen recogidas y agrupadas en archivos que llamamos bibliotecas.
Ejemplos de estas funciones son muchas matemticas (trigonomtricas,
o numricas,) o funciones de entrada de datos desde teclado o de
salida de la informacin del programa por pantalla. Desde luego, para
hacer uso de una funcin predefinida, es necesario conocer su existencia
y tener localizada la biblioteca donde est precompilada; eso es parte
del aprendizaje de un lenguaje de programacin, aunque tambin se
disponen de grandes ndices de funciones, de fcil acceso para su
consulta.
Al compilar un programa generamos un archivo objeto. Habitualmente
los programas que compilemos harn uso de algunas funciones de
biblioteca; en ese caso, el archivo objeto no es an un fichero
ejecutable, puesto que le falta aadir el cdigo de esas funciones. Un
entorno de programacin que tenga definidas bibliotecas necesitar
tambin un enlazador que realice la tarea de juntar el archivo objeto
con las bibliotecas empleadas y llegar, as, al cdigo ejecutable.
La creacin e implementacin de un programa no suele terminar con
este ltimo paso descrito. Con frecuencia se encontrarn errores, bien
de compilacin porque haya algn error sintctico o de expresin y
manejo del lenguaje; bien de ejecucin, porque el programa no haga
exactamente lo que se deseaba. No siempre es sencillo encontrar los
errores de nuestros programas; un buen entorno de programacin
ofrece al programador algunas herramientas llamadas depuradores,
que facilitan esta tarea.
Podramos escribir el algoritmo que define la tarea de crear un
Captulo 1. Lenguaje C.

programa. Ese algoritmo podra tener el aspecto del recogido en el
flujograma de la Figura 1.1.
Escritura del programa
fuente (.cpp)
Compilacin
I

9
En el caso del lenguaje C, el archivo de texto donde se almacena el
cdigo tendr un nombre (el que se quiera) y la extensin .cpp (si
trabajamos con un entorno de programacin de C++), o .c. Al compilar
el fichero fuente (nombre.cpp) se llega al cdigo mquina, con el mismo
nombre que el archivo donde est el cdigo fuente, y con la extensin
.obj. Casi con toda probabilidad en cdigo fuente har uso de funciones
que estn ya definidas y precompiladas en las bibliotecas. Ese cdigo
precompilado est en archivos con la extensin .lib. Con el archivo .obj
y los necesarios .lib que se deseen emplear, se procede al linkado o
enlazado que genera un fichero ejecutable con la extensin .exe.

Estructura bsica de un programa en C.
Aqu viene escrito un sencillo programa en C. Quiz convenga ponerse
ahora delante del ordenador y, con el editor de C en la pantalla, escribir
estar lneas y ejecutarlas. Ms adelante se muestra cmo trabajar en un
entorno de programacin (se toma el Borland C++).
Errores de
compilacin
Obtencin del
programa objeto (.obj)
Enlace
Archivos de
biblioteca (.lib)
Programas
objeto del
usuario
Obtencin del
programa
ejecutable (.exe)
Errores de
ejecucin
S No
S No
F
Figura 1.1.: Fases de
desarrollo de un programa
Fundamentos de informtica. Programacin en Lenguaje C.


10

#i ncl ude <st di o. h>
/ * Est e es un pr ogr ama en C. */
/ / I un
void mai n( void)
mpr i me mensaj e en l a pant al l a del or denador
{
pr i nt f ( mi pr i mer pr ogr ama en C) ;
}
Todos los programas en C deben tener ciertos componentes fijos. Vamos
a ver los que se han empleado en este primer programa:
1. #include <stdio.h>: Los archivos .h son los archivos de cabecera
en C. Con esta lnea de cdigo se indica al compilador que se desea
emplear, en el programa redactado, alguna funcin que est
declarada en el archivo de biblioteca stdio.h. Esta archivo contiene
las declaraciones de una coleccin de programas de entrada y salida
por consola (pantalla y teclado).
Esta instruccin nos permite utilizar cualquiera de las funciones
declaradas en el archivo. Esta lnea de cdigo recoge el nombre del
archivo stdio.h, donde estn recogidos todos los prototipos de las
funciones de entrada y salida estndar. Todo archivo de cabecera
contiene identificadores, constantes, variables globales, macros,
prototipos de funciones, etc.
Toda lnea que comience por # se llama directiva de
preprocesador. A lo largo del libro se irn viendo diferentes
directivas.
2. main: Es el nombre de una funcin. Es la funcin principal y
establece el punto donde comienza la ejecucin del programa. La
funcin main es necesaria en cualquier programa de C que desee
ejecutar instrucciones. Un cdigo ser ejecutable si y slo si
dispone de la funcin main.
3. void main( void) : Los parntesis se encuentran siempre despus de
un identificador de funcin. Entre ellos se recogen los parmetros
Captulo 1. Lenguaje C.


11
que se pasan a la funcin al ser llamada. En este caso, no se recoge
ningn parmetro, y entre parntesis se indica el tipo void. Ya se
ver ms adelante qu significa esta palabra. Delante del nombre de
la funcin principal (main) tambin viene la palabra void, porque la
funcin principal que hemos implementado no devuelve ningn
valor.
4. / * comentarios * / : Smbolos opcionales. Todo lo que se encuentre
entre estos dos smbolos son comentarios al programa fuente y no
sern ledos por el compilador.
Los comentarios no se compilan, y por tanto no son parte del
programa; pero son muy necesarios para lograr unos cdigos
inteligibles, fcilmente interpretables tiempo despus de que hayan
sido redactados y compilados. Es muy conveniente, cuando se
realizan tareas de programacin, insertar comentarios con frecuencia
que vayan explicando el proceso que se est llevando en cada
momento. Un programa bien documentado es un programa que
luego se podr entender con facilidad y ser, por tanto, ms
fcilmente modificado y mejorado.
Tambin se pueden incluir comentarios precedindolos de la doble
barra //. En ese caso, el compilador no toma en consideracin lo
que est escrito desde la doble barra hasta el final de la presente
lnea.
5. ;: Toda sentencia en C termina con el punto y coma. En C, se
entiende por sentencia todo lo que tenga, al final, un punto y coma.
La lnea antes comentada (#include <stdio.h>) no termina con un
punto y coma porque no es una sentencia: es (ya lo hemos dicho)
una directiva de preprocesador.
6. {}: Indican el principio y el final de todo bloque de programa.
Cualquier conjunto de sentencias que se deseen agrupar, para
formar entre ellas una sentencia compuesta o bloque, irn marcadas
Fundamentos de informtica. Programacin en Lenguaje C.


12
por un par de llaves: una antes de la primera sentencia a agrupar; la
otra, de cierre, despus de la ltima sentencia. Una funcin es un
bloque de programa y debe, por tanto, llevarlas a su inicio y a su fin.

Elementos lxicos.
Entendemos por elemento lxico cualquier palabra vlida en el
lenguaje C. Sern elementos lxicos, o palabras vlidas, todas aquellas
palabras que formen parte de las palabras reservadas del lenguaje, y
todas aquellas palabras que necesitemos generar para la redaccin del
programa, de acuerdo con una normativa sencilla.
Para crear un identificador (un identificador es un smbolo empleado
para representar un objeto dentro de un programa) en el lenguaje C se
usa cualquier secuencia de una o ms letras (de la A a la Z, y de la
a a la z, excluida las letras y ), dgitos (del 0 al 9) o smbolo
subrayado (_). Un identificador es cualquier palabra vlida en C. Con
ellos podemos dar nombre a variables, constantes, tipos de dato,
nombres de funciones o procedimientos, etc. Tambin las palabras
propias del lenguaje C son identificadores; estas palabras se llaman
palabras clave o palabras reservadas.
Adems de la restriccin en el uso de caracteres vlidos para crear
identificadores, existen otras reglas bsicas para su creacin en el
lenguaje C:
1. Debe comenzar por una letra del alfabeto o por el carcter
subrayado. Un identificador no puede comenzar por un dgito.
2. El compilador slo reconoce los primeros 32 caracteres de un
identificador, pero ste puede tener cualquier otro tamao mayor.
Aunque no es nada habitual generar identificadores tan largos, si
alguna vez as se hace hay que evitar que dos de ellos tengan
iguales los 32 primeros caracteres, porque entonces para el
compilador ambos identificadores sern el mismo.
Captulo 1. Lenguaje C.


13
3. Las letras de los identificadores pueden ser maysculas y
minsculas. El compilador distingue entre unas y otras, y dos
identificadores que se lean igual y que se diferencien nicamente en
que una de sus letras es mayscula en uno y minscula en otro, son
distintos.
4. Un identificador no puede deletrearse igual y tener el mismo tipo de
letra (mayscula o minscula) que una palabra reservada o que una
funcin definida en una librera que se haya incluido en el programa
mediante una sentencia include.
Las palabras reservadas, o palabras clave, son identificadores
predefinidos que tienen un significado especial para el compilador de C.
Slo se pueden usar en la forma en que han sido definidos. El conjunto
de palabras clave o reservadas (que siempre van en minscula) en ANSI
C es muy reducido (un total de 32) y son las siguientes:

auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default ( goto) sizeof volatile
do if static while
A lo largo del manual se ver el significado de cada una de ellas. La
palabra goto viene recogida entre parntesis porque, aunque es una
palabra reservada en C y su uso es sintcticamente correcto, de hecho
no es una palabra permitida en un paradigma de programacin
estructurado como es el paradigma del lenguaje C.

Sentencias simples y sentencias compuestas.
Una sentencia simple es cualquier expresin vlida en la sintaxis de C
que termine con el carcter de punto y coma. Sentencia compuesta es
Fundamentos de informtica. Programacin en Lenguaje C.


14
una sentencia formada por una o varias sentencias simples.
La sentencia simple queda definida cuando el programador termina una
expresin vlida en C, con un punto y coma. La sentencia compuesta se
inicia con una llave de apertura ({) y se termina con una llave de
clausura (}).

Errores y depuracin.
No es extrao que, al terminar de redactar el cdigo de un programa, al
iniciar la compilacin, el compilador deba abortar su proceso y avisar de
que existen errores. El compilador ofrece algunos mensajes que
clarifican frecuentemente el motivo del error, y la correccin de esos
errores no comporta habitualmente demasiada dificultad. A esos errores
sintcticos los llamamos errores de compilacin. Ejemplo de estos
errores pueden ser que se haya olvidado terminar una sentencia con el
punto y coma, o que falte una llave de cierre de bloque de sentencias
compuestas, o sobre un parntesis, o se emplee un identificador mal
construido
Otras veces, el compilador no haya error sintctico alguno, y compila
correctamente el programa, pero luego, en la ejecucin, se producen
errores que acaban por abortar el proceso. A esos errores los llamamos
errores de ejecucin. Un clsico ejemplo de este tipo de errores es
forzar al ordenador a realizar una divisin por cero, o acceder a un
espacio de memoria para el que no estamos autorizados. Esos errores
tambin suelen ser sencillos de encontrar, aunque a veces, como no son
debidos a fallos sintcticos ni de codificacin del programa sino que
pueden estar ocasionados por el valor que en un momento concreto
adquiera una variable, no siempre son fcilmente identificables, y en
esos casos puede ser necesario utilizar los depuradores que muchos
entornos de programacin ofrecen.
Y puede ocurrir tambin que el cdigo no tenga errores sintcticos, y por
Captulo 1. Lenguaje C.


15
tanto el compilador termine su tarea y genere un ejecutable; que el
programa se ejecute sin contratiempo alguno, porque en ningn caso se
llega a un error de ejecucin; pero que el resultado final no sea el
esperado. Todo est sintcticamente bien escrito, sin errores de
compilacin ni de ejecucin, pero hay errores en el algoritmo que
pretende resolver el problema que nos ocupa. Esos errores pueden ser
ocasionados sencillamente por una errata a la hora de escribir el cdigo,
que no genera un error sintctico, ni aborta la ejecucin: por ejemplo,
teclear indebidamente el operador suma (+) cuando el que corresponda
era el operador resta (-).
Todo error requiere una modificacin del programa, y una nueva
compilacin. No todos los errores aparecen de inmediato, y no es
extrao que surjan despus de muchas ejecuciones.

Recapitulacin.
En este captulo hemos introducido los conceptos bsicos iniciales para
poder comenzar a trabajar en la programacin con el lenguaje C. Hemos
presentado el entorno habitual de programacin y hemos visto un
primer programa en C (sencillo, desde luego) que nos ha permitido
mostrar las partes bsicas del cdigo de un programa: las directivas de
preprocesador, los comentarios, la funcin principal, las sentencias
(simples o compuestas) y las llaves que agrupan sentencias. Y hemos
aprendido las reglas bsicas de creacin de identificadores.

El entorno de Borland C++.
Al ejecutar la aplicacin de Borland C++, aparece una pantalla gris. Lo
primero que se debe hacer es crear un nuevo documento donde
podremos escribir nuestro cdigo. Para esto basta seguir los pasos
indicados en la Figura 1.2.
Fundamentos de informtica. Programacin en Lenguaje C.


16
Figura 1.2.: Borland C++. Creacin de nuevo documento.
Una vez creado el documento, ya podemos escribir en l. Vamos a
copiar el texto del programa ejemplo de este captulo. En la figura 1.3.
vemos el cdigo copiado. Podemos ver cmo ha quedado escrito en
letras azules todo lo que es comentario. En letras verdes las directivas
de preprocesador. Y en letras tambin azules los textos recogidos entre
comillas. Todo este cdigo de colores (y otros colores de letra que se
irn viendo), y de tipos de letra (cursiva, negrita) ayudan en la
confeccin del programa.
Supongamos ahora que guardamos el archivo en una carpeta nueva
llamada PrimerPrograma. El archivo (con extensin cpp) ocupa 167
bytes aunque por exigencias del disco donde se almacena el archivo
emplea hasta un total de 4 Kbytes.
Al compilar el programa, aparecen en la carpeta otros tantos archivos:
uno con la extensin obj, y otro con la extensin exe, que es el
ejecutable. Los tamaos de ambos archivos pueden verse buscando las
propiedades de ambos.
Captulo 1. Lenguaje C.

Para compilar y ejecutar basta con elegir la opcin Men Debug Run
o, como indica esa misma opcin, pulsar simultneamente las teclas
Control y F9. Una tercera opcin es pulsar el botn en forma de rayo
amarillo situado en la sexta posicin de la barra de botones.
Figura 1.3.: Borland C++. Nuestro primer programa.

17
Fundamentos de informtica. Programacin en Lenguaje C.


18








CAPTULO 2

TIPOS DE DATOS Y VARIABLES EN C

Un tipo de dato define de forma explcita un conjunto de valores,
denominado dominio, sobre el cual se pueden realizar una serie de
operaciones. Un valor es un elemento del conjunto que hemos llamado
dominio. Una variable es un espacio de la memoria destinada al
almacenamiento de un valor de un tipo de dato concreto, referenciada
por un nombre. Son conceptos sencillos, pero muy necesarios para
saber exactamente qu se hace cuando se crea una variable en un
programa.
Un tipo de dato puede ser tan complejo como se quiera. Puede necesitar
un byte para almacenar cualquier valor de su dominio, o requerir de
muchos bytes.
Cada lenguaje ofrece una coleccin de tipos de datos, que hemos
llamado primitivos. Tambin ofrece herramientas para crear tipos de
dato distintos, ms complejos que los primitivos y ms acordes con el
tipo de problema que se aborde en cada momento.
Fundamentos de informtica. Programacin en Lenguaje C


20
En este captulo vamos a presentar los diferentes tipos de datos
primitivos que ofrece el lenguaje C. Veremos cmo se crean (declaran)
las variables, qu operaciones se pueden realizar con cada una de ellas,
y de qu manera se pueden relacionar unas variables con otras para
formar expresiones. Veremos las limitaciones en el uso de las variables
segn su tipo de dato.
Ya hemos dicho que un tipo de dato especifica un dominio sobre el que
una variable de ese tipo puede tomar sus valores; y unos operadores. A
lo largo del captulo iremos presentando los distintos operadores bsicos
asociados con los tipos de dato primitivos del lenguaje C. Es importante
entender la operacin que realiza cada operador y sobre qu dominio
este operador est definido.

Declaracin de variables.
Antes de ver los tipos de dato primitivos, conviene saber cmo se crea
una variable en C.
Toda variable debe ser declarada previa a su uso. Declarar una
variable es indicar al programa un identificador o nombre para esa
variable, y el tipo de dato para la que se crea esa variable.
La declaracin de variable tiene la siguiente sintaxis:
tipo var_ 1 [ =valor1, var_ 2 = valor_ 2, , var_ N = valor_ N] ;
Donde tipo es el nombre del tipo de variable que se desea crear, y
var_1, es el nombre o identificador de esa variable.
Aclaracin a la notacin: en las reglas sintcticas de un lenguaje de
programacin, es habitual colocar entre corchetes ([]) aquellas partes
de la sintaxis que son optativas.
En este caso tenemos que en una declaracin de variables se pueden
declarar una o ms variables del mismo tipo, todas ellas separadas por
el operador coma. Al final de la sentencia de declaracin de variables
Captulo 2. Tipos de datos y variables en C.


21
se encuentra, como siempre ser en cualquier sentencia, el operador
punto y coma.
En la declaracin de una variable, es posible asignarle un valor de inicio.
De lo contrario, la variable creada adquirir un valor cualquiera entre
todos los explicitados por el rango del tipo de dato, desconocido para el
programador.
Qu ocurre si una variable no es inicializada? En ese caso, al declararla
se dar orden de reservar una cantidad de memoria (la que exija el tipo
de dato indicado para la variable) para el almacenamiento de los valores
que pueda ir tomando esa variable creada. Esa porcin de memoria es
un elemento fsico y, como tal, deber tener un estado fsico. Cada uno
de los bits de esta porcin de memoria estar en el estado que se ha
llamado 1, o en el estado que se ha llamado 0. Y un estado de memoria
codifica una informacin concreta: la que corresponda al tipo de dato
para el que est reservada esa memoria.
Es conveniente remarcar esta idea. No es necesario, y tampoco lo exige
la sintaxis de C, dar valor inicial a una variable en el momento de su
declaracin. La casustica es siempre enorme, y se dan casos y
circunstancias en las que realmente no sea conveniente asignar a la
variable un valor inicial. Pero habitualmente es muy recomendable
inicializar las variables. Otros lenguajes lo hacen por defecto en el
momento de la declaracin de variables; C no lo hace. Otros lenguajes
detectan como error de compilacin (errar sintctico) el uso de una
variable no inicializada; C acepta esta posibilidad.
A partir del momento en que se ha declarado esa variable, puede ya
hacerse uso de ella. Tras la declaracin ha quedado reservado un
espacio de memoria para almacenar la informacin de esa variable.
Si declaramos tipo variable = valor; tendremos la variable <variable,
tipo, R, valor>, de la que desconocemos su direccin de memoria. Cada
vez que el programa trabaje con variable estar haciendo referencia a
Fundamentos de informtica. Programacin en Lenguaje C


22
esta posicin de memoria R. Y estar refirindose a uno o ms bytes, en
funcin del tamao del tipo de dato para el que se ha creado la variable.

Tipos de datos primitivos en C: sus dominios.
Los tipos de dato primitivos en C quedan recogidos en la tabla 2.1.
Las variables de tipo de dato carcter ocupan 1 byte. Aunque estn
creadas para almacenar caracteres mediante una codificacin como la
ASCII (que asigna a cada carcter un valor numrico codificado con esos
8 bits), tambin pueden usarse como variables numricas. En ese caso,
el rango de valores es el recogido en la tabla 2.1. En el caso de que se
traten de variables con signo, entonces el rango va desde
7
2 hasta

7
2 1.
RANGO DE VALORES
TIPOS SIGNO MENOR MAYOR

tipos de dato CARCTER: char

signed -128 +127
unsigned
char
0 +255

tipos de dato ENTERO: int

signed -32.768 +32.767
unsigned
short
0 +65.535
signed -2.147.483.648 +2.147.483.647
unsigned
long
0 4.294.967.295

tipos de dato CON COMA FLOTANTE

float -3.402923E+38 +3.402923E+38
double -1.7976931E+308 +1.7976931E+308
long double -1.2E+4932 +1.2E+4932

Tabla 2.1.: Tipos de datos primitivos en C.

Para crear una variable de tipo carcter en C, utilizaremos la palabra
clave char. Si la variable es con signo, entonces su tipo ser signed
char, y si no debe almacenar signo, entonces ser unsigned char. Por
Captulo 2. Tipos de datos y variables en C.


23
defecto, si no se especifica si la variable es con o sin signo, el lenguaje C
considera que se ha tomado la variable con signo, de forma que decir
char es lo mismo que decir signed char.
Lo habitual ser utilizar variables tipo char para el manejo de
caracteres. Los caracteres simples del alfabeto latino se representan
mediante este tipo de dato. El dominio de las variables char es un
conjunto finito ordenado de caracteres, para el que se ha definido una
correspondencia que asigna, a cada carcter del dominio, un cdigo
binario diferente de acuerdo con alguna normalizacin. El cdigo ms
extendido es el cdigo ASCII (American Standard Code for Information
Interchange).
Las variables tipo entero, en C, se llaman int. Dependiendo de que esas
variables sean de dos bytes o de cuatro bytes las llamaremos de tipo
short int (16 bits) de tipo long int (32 bits). Y para cada una de
ellas, se pueden crear con signo o sin signo: signed short int y signed
long int, unsigned short int y unsigned long int. De nuevo, si no
se especifica nada, C considera que la variable entera creada es con
signo, de forma que la palabra signed vuelve a ser opcional. En
general, se recomienda el uso de la palabra signed. Utilizar esa palabra
al declarar enteros con signo facilita la compresin del cdigo.
El tamao de la variable int depende del concepto, no introducido hasta
el momento, de longitud de la palabra. Habitualmente esta longitud
se toma mltiplo de 8, que es el nmero de bits del byte. De hecho la
longitud de la palabra viene definido por el mximo nmero de bits que
puede manejar el procesador, de una sola vez, cuando hace clculos con
enteros.
Si se declara una variable en un PC como de tipo int (sin determinar si
es short o long), el compilador de C considerar que esa variable es de
la longitud de la palabra: de 16 o de 32 bits. Es importante conocer ese
dato (que depende del compilador), o a cambio es mejor especificar
Fundamentos de informtica. Programacin en Lenguaje C


24
siempre en el programa si se desea una variable corta o larga, y no
dejar esa decisin al tamao de la palabra.
Una variable declarada como de tipo long se entiende que es long int.
Y una variable declarada como de tipo short, se entiende que es short
int. Muchas veces se toma como tipo de dato nicamente el modificador
de tamao, omitiendo la palabra clave int.
Los restantes tipos de dato se definen para codificar valores reales. Hay
que tener en cuenta que el conjunto de los reales es no numerable
(entre dos reales siempre hay un real y, por tanto, hay infinitos reales).
Los tipos de dato que los codifican ofrecen una codificacin finita s
numerable. Esos tipos de dato codifican subconjuntos del conjunto de
los reales; subconjuntos que, en ningn caso, pueden tomarse como un
intervalo del conjunto de los reales.
A esta codificacin de los reales se le llama de coma flotante. As
codifica el lenguaje C (y muchos lenguajes) los valores no enteros.
Tomando como notacin para escribir esos nmeros la llamada notacin
cientfica (signo, mantisa, base, exponente: por ejemplo, el numero de
Avogadro, +
23
6, 023 10 : signo positivo, mantisa 6,023, base decimal y
exponente 23), almacena en memoria, de forma normalizada (norma
IEEE754) el signo del nmero, su mantisa y su exponente. No es
necesario almacenar la base, que en todos los casos trabaja en la base
binaria.
Los tipos de dato primitivos en coma flotante que ofrece el lenguaje C
son tres: float, que reserva 4 bytes para su codificacin y que toma
valores en el rango sealado en la tabla 2.1.; double, que reserva 8
bytes; y long double, que reserva 10 bytes. Desde luego, en los tres
tipos de dato el dominio abarca tantos valores positivos como negativos.
Existe adems un tipo de dato que no reserva espacio en memoria: su
tamao es nulo. Es el tipo de dato void. No se pueden declarar
variables de ese tipo. Ms adelante se ver la necesidad y utilidad de
Captulo 2. Tipos de datos y variables en C.


25
tener definido un tipo de dato de estas caractersticas. Por ejemplo es
muy conveniente para el uso de funciones.
En C el carcter que indica el fin de la parte entera y el comienzo de la
parte decimal se escribe mediante el carcter punto. La sintaxis no
acepta interpretaciones de semejanza, y para el compilador el carcter
coma es un operador que nada tiene que ver con el punto decimal. Una
equivocacin en ese carcter causar habitualmente un error en tiempo
de compilacin.
El lenguaje C dedica nueve palabras para la identificacin de los tipos de
dato primitivos: void, char, int, float, double, short, long, signed e
unsigned. Ya se ha visto, por tanto ms de un 25 % del lxico total del
lenguaje C. Existen ms palabras clave para la declaracin y creacin de
variables. Se vern ms adelante.

Tipos de datos primitivos en C: sus operadores.
Ya se dijo que un tipo de dato explicita un conjunto de valores, llamado
dominio, sobre el que son aplicables una serie de operadores. No queda
del todo definido un tipo de dato presentando slo su dominio. Falta
indicar cules son los operadores que estn definidos para cada tipo de
dato.
Los operadores pueden aplicarse a una sola variable, a dos variables, e
incluso a varias variables. Llamamos operacin unaria a la que se
aplica a una sola variable. Una operacin unaria es, por ejemplo, el
signo del valor.
No tratamos ahora las operaciones que se pueden aplicar sobre una
variable creada para almacenar caracteres. Ms adelante hay un
captulo entero dedicado a este tipo de dato char.
Las variables enteras, y las char cuando se emplean como variables
enteras de pequeo rango, adems del operador unario del signo, tienen
Fundamentos de informtica. Programacin en Lenguaje C


26
definidos el operador asignacin, los operadores aritmticos, los
relacionales y lgicos y los operadores a nivel de bit.
Los operadores de las variables con coma flotante son el operador
asignacin, todos los aritmticos (excepto el operador mdulo o clculo
del resto de una divisin), y los operadores relacionales y lgicos. Las
variables float, double y long double no aceptan el uso de operadores
a nivel de bit.

Operador asignacin.
El operador asignacin permite al programador modificar los valores de
las variables y alterar, por tanto, el estado de la memoria del ordenador.
El carcter que representa al operador asignacin es el carcter =. La
forma general de este operador es
nombre_variable = expresin;
Donde expresin puede ser un literal, otra variable, o una combinacin
de variables, literales y operadores y funciones. Podemos definirlo como
una secuencia de operandos y operadores que unidos segn ciertas
reglas producen un resultado.
Este signo en C no significa igualdad en el sentido matemtico al que
estamos acostumbrados, sino asignacin. No puede llevar a equvocos
expresiones como la siguiente:
a = a + 1;
Ante esta instruccin, el procesador toma el valor de la variable a, lo
copia en un registro de la ALU donde se incrementa en una unidad, y
almacena (asigna) el valor resultante en la variable a, modificando por
ello el valor anterior de esa posicin de memoria. La expresin
comentada no es una igualdad de las matemticas, sino una orden para
incrementar en uno el valor almacenado en la posicin de memoria
reservada por la variable a.
Captulo 2. Tipos de datos y variables en C.


27
El operador asignacin tiene dos extremos: el izquierdo (que toma el
nombre Lvalue en mucho manuales) y el derecho (Rvalue). La
apariencia del operador es, entonces:
LValue = RValue;
Donde Lvalue slo puede ser el nombre de una variable, y nunca una
expresin, ni un literal. Expresiones como a + b = c; 3 = variable; son
errneas, pues ni se puede cambiar el valor del literal 3 que, adems,
no est en memoria porque es un valor literal; ni se puede almacenar
un valor en la expresin a + b, porque los valores se almacenan en
variables, y a + b no es variable alguna.
Un error de este estilo interrumpe la compilacin del programa. El
compilador dar un mensaje de error en el que har referencia a que el
Lvalue de la asignacin no es correcto.
Cuando se trabaja con variables enteras, al asignar a una variable un
valor mediante un literal (por ejemplo, v = 3;) se entiende que ese dato
viene expresado en base 10.
Pero en C es posible asignar valores en la base hexadecimal. Si se
quiere dar a una variable un valor en hexadecimal, entonces ese valor
va precedido de un cero y una letra equis. Por ejemplo, si se escribe v =
0x20, se est asignando a la variable v el valor 20 en hexadecimal, es
decir, el 32 en decimal, es decir, el valor 100000 en binario.

Operadores aritmticos.
Los operadores aritmticos son:
1. Suma. El identificador de este operador es el carcter +. Este
operador es aplicable sobre cualquier variable primitiva de C. Si el
operador + se emplea como operador unario, entonces es el
operador de signo positivo.
Fundamentos de informtica. Programacin en Lenguaje C


28
2. Resta. El identificador de este operador es el carcter . Este
operador es aplicable sobre cualquier variable primitiva de C. Si el
operador se emplea como operador unario, entonces es el
operador de signo negativo.
3. Producto. El identificador de este operador es el carcter *. Este
operador es aplicable sobre cualquier variable primitiva de C.
4. Cociente o Divisin. El identificador de este operador es el carcter
/. Este operador es aplicable sobre cualquier variable primitiva de
C.
Cuando el cociente se realiza con variables enteras el resultado ser
tambin un entero, y trunca el resultado al mayor entero menor que
el cociente. Por ejemplo, 5 dividido entre 2 es igual a 2. Y 3 dividido
entre 4 es igual a 0. Es importante tener esto en cuenta cuando se
trabaja con enteros.
Supongamos la expresin
sup = (1 / 2) * base * altura;
para el clculo de la superficie de un tringulo, y supongamos que
todas las variables que intervienen han sido declaradas enteras. As
expresada la sentencia o instruccin de clculo, el resultado ser
siempre el valor 0 para la variable sup, sea cual sea el valor actual
de las variable base o altura: y es que al calcular el valor de 1
dividido entre 2, el procesador ofrece como resultado el valor 0.
Cuando el cociente se realiza entre variables de coma flotante,
entonces el resultado es tambin de coma flotante.
Siempre se debe evitar el cociente en el que el denominador sea
igual a cero, porque en ese caso se dar un error de ejecucin y el
programa quedar abortado.
5. Mdulo. El identificador de este operador es el carcter %. Este
operador calcula el resto del cociente entero. Por su misma
Captulo 2. Tipos de datos y variables en C.


29
definicin, no tiene sentido su aplicacin entre variables no enteras:
su uso con variables de coma flotante provoca error de compilacin.
Como en el cociente, tampoco su divisor puede ser cero.
6. Incremento y decremento. Estos dos operadores no existen en
otros lenguajes. El identificador de estos operadores son los
caracteres ++ para el incremento, y -- para el decremento. Este
operador es vlido para todos los tipos de dato primitivos de C.
La expresin a++; es equivalente a la expresin a = a + 1;. Y la
expresin a--; es equivalente a la expresin a = a - 1;.
Estos operadores condensan, en uno sola expresin, un operador
asignacin, un operador suma (o resta) y un valor literal: el valor 1.
Y como se puede apreciar son operadores unarios: se aplican a una
sola variable.
Dnde se ubique el operador con respecto a la variable tiene su
importancia, porque vara su comportamiento dentro del total de la
expresin.
Por ejemplo, el siguiente cdigo
unsigned short int a, b = 2, c = 5;
a = b + c++;
modifica dos variables: por el operador asignacin, la variable a
tomar el valor resultante de sumar los contenidos de b y c; y por la
operacin incremento, que lleva consigo asociado otro operador
asignacin, se incrementa en uno el valor de la variable c.
Pero queda una cuestin abierta: Qu operacin se hace primero:
incrementar c y luego calcular b + c para asignar su resultado a la
variable a; o hacer primero la suma y slo despus incrementar la
variable c?
Eso lo indicar la posicin del operador. Si el operador incremento (o
decremento) precede a la variable, entonces se ejecuta antes de
evaluar el resto de la expresin; si se coloca despus de la variable,
Fundamentos de informtica. Programacin en Lenguaje C


30
entonces primero se evala la expresin donde est implicada la
variable a incrementar o decrementar y slo despus se incrementa
o decrementa esa variable.
En el ejemplo antes sugerido, el operador est ubicado a la derecha
de la variable c. Por lo tanto, primero se efecta la suma y la
asignacin sobre a, que pasa a valer 7; y luego se incrementa la
variable c, que pasa a valer 6. La variable b no modifica su valor.
Por completar el ejemplo, si la expresin hubiera sido
a = b + ++c;
entonces, al final tendramos que c vale 6 y que a vale 8, puesto que
no se realizara la suma y la asignacin sobre a hasta despus de
haber incrementado el valor de la variable c.
Los operadores incremento y decremento, y el juego de la
precedencia, son muy cmodos y se emplean mucho en los cdigos
escritos en lenguaje C.
Aunque hasta el tema siguiente no se va a ver el modo en que se
pueden recibir datos desde el teclado (funcin scanf( ) ) y el modo de
mostrar datos por pantalla (funcin printf( ) ), vamos a recoger a lo
largo de este captulo algunas cuestiones muy sencillas para
resolver. Por ahora lo importante no es entender el programa entero,
sino la parte que hace referencia a la declaracin y uso de las
variables.

Operadores relacionales y lgicos.
Los operadores relacionales y los operadores lgicos crean expresiones
que se evalan como verdaderas o falsas.
En muchos lenguajes existe un tipo de dato primitivo para estos valores
booleanos de verdadero o falso. En C ese tipo de dato no existe.
Captulo 2. Tipos de datos y variables en C.


31
El lenguaje C toma como falsa cualquier expresin que se evale como
0. Y toma como verdadera cualquier otra evaluacin de la expresin. Y
cuando en C se evala una expresin con operadores relacionales y/o
lgicos, la expresin queda evaluada a 0 si el resultado es falso; y a 1 si
el resultado es verdadero.
Los operadores relacionales son seis: igual que (==), distintos
(!=) , mayor que (>), mayor o igual que (>=), menor que (<)
y menor o igual que (<=).
Todos ellos se pueden aplicar a cualquier tipo de dato primitivo de C.
Una expresin con operadores relacionales sera, por ejemplo, a != 0,
que ser verdadero si a toma cualquier valor diferente al 0, y ser falso
si a toma el valor 0. Otras expresiones relacionales seran, por ejemplo,
a > b + 2;

x + y == z + t;
Con frecuencia interesar evaluar una expresin en la que se obtenga
verdadero o falso no solo en funcin de una relacin, sino de varias. Por
ejemplo, se podra necesitar saber (obtener verdadero o falso) si el valor
de una variable concreta est entre dos lmites superior e inferior. Para
eso necesitamos concatenar dos relacionales. Y eso se logra mediante
los operadores lgicos.
Un error frecuente (y de graves consecuencias en la ejecucin del
programa) al programar en C C++ es escribir el operador asignacin
(=), cuando lo que se pretenda escribir era el operador relacional
igual que (==). El C C++ la expresin variable = valor; ser
siempre verdadera si valor es distinto de cero. Si colocamos una
asignacin donde desebamos poner el operador relacional igual que,
tendremos dos consecuencias graves: se cambiar el valor de la variable
colocada a la izquierda del operador asignacin (cosa que no queramos)
Fundamentos de informtica. Programacin en Lenguaje C


32
y, si el valor de la variable de la derecha es distinto de cero, la
expresin se evaluar como verdadera al margen de cules fueran los
valores iniciales de las variables.
Los operadores lgicos son: AND, cuyo identificador est formado por el
carcter repetido &&; OR, con el identificador ||; y el operador
negacin, cuyo identificador es el carcter de admiracin final (!).

a b a && b a || b !a

F F F F V
F V F V V
V F F V F
V V V V F

Tabla 2.2.: Resultados de los
operadores lgicos.

Estos operadores binarios actan sobre dos expresiones que sern
verdaderas (o distintas de cero), o falsas (o iguales a cero), y devuelven
como valor 1 0 dependiendo de que la evaluacin haya resultado
verdadera o falsa.
La tabla de valores para conocer el comportamiento de estos operadores
est recogida en la tabla 2.2. En esa tabla se recoge el resultado de los
tres operadores en funcin del valor de cada una de las dos expresiones
que evalan.
Por ejemplo, supongamos el siguiente cdigo en C:
int a = 1 , b = 3 , x = 30 , y = 10;
int resultado;
resultado = a * x == b * y;
El valor de la variable resultado quedar igual a 1.
Y si queremos saber si la variable x guarda un valor entero positivo
menor que cien, escribiremos la expresin
(x > 0 && x < 100)
Captulo 2. Tipos de datos y variables en C.


33
Con estos dos grupos de operadores son muchas las expresiones de
evaluacin que se pueden generar. Quiz en este momento no adquiera
mucho sentido ser capaz de expresar algo as; pero ms adelante se
ver cmo la posibilidad de verificar sobre la veracidad y falsedad de
muchas expresiones permite crear estructuras de control condicionales,
o de repeticin.
Una expresin con operadores relacionales y lgicos admite varias
formas equivalentes Por ejemplo, la antes escrita sobre el intervalo de
situacin del valor de la variable x es equivalente a escribir
!(x < 0 || x >= 100)
Evaluar las siguientes expresiones.
short a = 0, b = 1, c = 5;
a; // FALSO
b; // VERDADERO
a < b; // VERDADERO
5 * (a + b) == c; // VERDADERO

float pi = 3.141596;
long x = 0, y = 100, z =1234;

3 * pi < y && (x + y) * 10 <= z / 2; // FALSO
3 * pi < y || (x + y) * 10 <= z / 2; // VERDADERO
3 * pi < y && !((x + y) * 10 <= z / 2); // VERDADERO

long a = 5, b = 25, c = 125, d = 625;
5 * a == b; // VERDADERO
5 * b == c; // VERDADERO
a + b + c + d < 1000; // VERDADERO
a > b || a = 10; // VERDADERO
La ltima expresin trae su trampa: Por su estructura se ve que se ha
pretendido crear una expresin lgica formada por dos sencillas
enlazadas por el operador OR. Pero al establecer que uno de los
extremos de la condicin es a = 10 (asignacin, y no operador relacional
igual que) se tiene que en esta expresin recogida la variable a pasa a
Fundamentos de informtica. Programacin en Lenguaje C


34
valer 10 y la expresin es verdadera puesto que el valor 10 es
verdadero (todo valor distinto de cero es verdadero).

Operadores a nivel de bit.
Ya se ha dicho en el captulo primero que el lenguaje C es de medio
nivel. Con eso se quiere decir que es un lenguaje de programacin que
tiene la capacidad de trabajar a muy bajo nivel, modificando un bit de
una variable, o logrando cdigos que manipulan la codificacin interna
de la informacin. Todos los operadores a nivel de bit estn definidos
nicamente sobre variables de tipo entero. No se puede aplicar sobre
una variable float, ni sobre una double, ni sobre una long double.
Los operadores a nivel de bit son seis:
1. Operador AND a nivel de bit. Su identificador es un solo carcter
&. Se aplica sobre variables del mismo tipo, con la misma longitud
de bits. Bit a bit compara los dos de cada misma posicin y asigna al
resultado un 1 en ese bit en esa posicin si los dos bits de las dos
variables sobre las que se opera valen 1; en otro caso asigna a esa
posicin del bit el valor 0.
2. Operador OR a nivel de bit. Su identificador es un solo carcter |.
Se aplica sobre variables del mismo tipo, con la misma longitud de
bits. Bit a bit compara los dos de cada misma posicin y asigna al
resultado un 1 en ese bit en esa posicin si alguno de los dos bits de
las dos variables sobre las que se opera valen 1; si ambos bits valen
cero, asigna a esa posicin del bit el valor 0.
Es frecuente en C y C++ el error de pretender escribir el operador
lgico and (&&), o el or (||) y escribir finalmente el operador
a nivel de bit (& |). Desde luego el significado de la sentencia o
instruccin ser completamente distinto e imprevisible. Ser un error
del programa de difcil deteccin.
Captulo 2. Tipos de datos y variables en C.


35
3. Operador OR EXCLUSIVO, XOR a nivel de bit. Su identificador
es un carcter ^. Se aplica sobre variables del mismo tipo, con la
misma longitud de bits. Bit a bit compara los dos de cada misma
posicin y asigna al resultado un 1 en ese bit en esa posicin si los
dos bits de las dos variables tienen valores distintos: el uno es 1 y el
otro 0, o viceversa; si los dos bits son iguales, asigna a esa posicin
del bit el valor 0.

variable binario hex. dec.

a 1010 1011 1100 1101 ABCD 43981
b 0110 0111 1000 1001 6789 26505
a_and_b 0010 0011 1000 1001 2389 9097
a_or_b 1110 1111 1100 1101 EFCD 61389
a_xor_b 1100 1100 0100 0100 CC44 52292

Tabla 2.3.: Valores del ejemplo en binario, hexadecimal y
decimal. Operadores a nivel de bit.


Por ejemplo, y antes de seguir con los otros tres operadores a nivel
de bit, supongamos que tenemos el siguiente cdigo:
unsigned short int a = 0xABCD, b = 0x6789;
unsigned short int a_and_b = a & b;
unsigned short int a_or_b = a | b;
unsigned short int a_xor_b = a ^ b;
La variable a vale, en hexadecimal ABCD, y en decimal 43981. La
variable b 6789, que en base diez es 26505. Para comprender el
comportamiento de estos tres operadores, se muestra ahora en la
tabla 2.3. los valores de a y de b en base dos, donde se puede ver
bit a bit de ambas variables, y veremos tambin el bit a bit de las
tres variables calculadas.
En la variable a_and_b se tiene un 1 en aquellas posiciones de bit
donde haba 1 en a y en b; un 0 en otro caso. En la variable a_or_b
se tiene un 1 en aquellas posiciones de bit donde haba al menos un
1 entre a y b; un 0 en otro caso. En la variable a_xor_b se tiene un 1
en aquellas posiciones de bit donde haba un 1 en a y un 0 en b, o
Fundamentos de informtica. Programacin en Lenguaje C


36
un 0 en a y un 1 en b; y un cero cuando ambos bits coincidan de
valor en esa posicin.
La tabla de valores de estos tres operadores queda recogida en la
tabla 2.4.

and or xor

0 0 0 0 0
0 1 0 1 1
1 0 0 1 1
1 1 1 1 0

Tabla 2.4.: Valores que
adoptan los tres operadores a
nivel de bit


4. Operador complemento a uno. Este operador unario devuelve el
complemento a uno del valor de la variable a la que se aplica. Su
identificador es el carcter ~. Si se tiene que a la variable x de tipo
short se le asigna el valor hexadecimal ABCD, entonces la variable
y, a la que se asigna el valor ~x valdr 5432. O si x vale 578D,
entonces y valdr A872. Puede verificar estos resultados calculando
los complementos a uno de ambos nmeros.
5. Operador desplazamiento a izquierda. Su identificador es la
cadena <<. Es un operador binario, que realiza un desplazamiento
de todos los bits de la variable o valor literal sobre la que se aplica
un nmero dado de posiciones hacia la izquierda. Los bits ms a la
izquierda (los ms significativos) se pierden; a la derecha se van
introduciendo tantos bits puestos a cero como indique el
desplazamiento.
Por ejemplo, si tenemos el siguiente cdigo:
short int var1 = 0x7654;
short int var2 = var1 << 3;
Captulo 2. Tipos de datos y variables en C.


37
La variable var2 tendr el valor de la variable var1 a la que se le
aplica un desplazamiento a izquierda de 3 bits.
Si la variable var1 tiene el valor, en base binaria
0111 0110 0101 0100 (estado de la variable var1)
entonces la variable var2, a la que se asigna el valor de la variable
var1 al que se han aadido tres ceros a su derecha y se le han
eliminado los tres dgitos ms a la izquierda queda:
1011 0010 1010 0000 (estado de la variable var2).
Es decir, var2 valdr, en hexadecimal, B2A0.
Una observacin sobre esta operacin. Introducir un cero a la
derecha de un nmero es lo mismo que multiplicarlo por la base.
En el siguiente cdigo
unsigned short int var1 = 12;
unsigned short int d = 1;
unsigned short int var2 = var1 << d;
var2 ser el doble que var1, es decir, 24. Y si d hubiera sido igual a
2, entonces var2 sera cuatro veces var1, es decir, 48. Y si d hubiera
sido igual a 3, entonces var2 sera ocho veces var1, es decir, 96.
Si llega un momento en que el desplazamiento obliga a perder algn
dgito 1 a la izquierda, entonces ya habremos perdido esa
progresin, porque la memoria no ser suficiente para albergar todos
sus dgitos y la cifra ser truncada.
Si las variables var1 y var2 estn declaradas como signed, y si la
variable var1 tiene asignado un valor negativo (por ejemplo, -7),
tambin se cumple que el desplazamiento equivalga a multiplicar por
dos. Es buen ejercicio de clculo de complementos y de codificacin
de enteros con signo verificar lo datos que a continuacin se
presentan:
var1 = -7;: estado de memoria FFF9
Fundamentos de informtica. Programacin en Lenguaje C


38
var2 = var1 << 1;: estado de memoria para la variable var2 ser
FFF2, que es la codificacin del entero -14.
6. Operador desplazamiento a derecha. Su identificador es la cadena
>>. Es un operador binario, que realiza un desplazamiento de
todos los bits de la variable o valor literal sobre la que se aplica un
nmero dado de posiciones hacia la derecha. Los bits ms a la
derecha (los menos significativos) se pierden; a la izquierda se van
introduciendo tantos bits como indique el desplazamiento. En esta
ocasin, el valor de los bits introducidos por la izquierda depender
del signo del entero sobre el que se aplica el operador
desplazamiento. Si el entero es positivo, se introducen tantos ceros
por la izquierda como indique el desplazamiento. Si el entero es
negativo, se introducen tantos unos por la izquierda como indique el
desplazamiento. Evidentemente, si el entero sobre el que se aplica el
desplazamiento a derecha est declarado como unsigned,
nicamente sern ceros lo que se introduzca por su izquierda, puesto
que en ningn caso puede codificar un valor negativo.
Si desplazar a la izquierda era equivalente a multiplicar por la base,
ahora, desplazar a la derecha es equivalente a dividir por la base
(divisin entera, sesgando el valor al entero mayor, menor que el
resultado de dicho cociente). Y el hecho de que el desplazamiento a
derecha considere el signo en el desplazamiento permite que, en los
valores negativos, siga siendo equivalente desplazar a derecha que
dividir por la base.
Por ejemplo, si tenemos el siguiente cdigo
signed short int var1 = -231;
signed short int var2 = var1 >> 1;
Entonces, el estado que codifica el valor de var1 es, expresado en
hexadecimal, FF19. Y el valor que codifica entonces var2, si lo hemos
desplazado un bit a la derecha, ser FF8C, que es la codificacin del
entero negativo -116.
Captulo 2. Tipos de datos y variables en C.


39
Los operadores a nivel de bit tienen una gran potencialidad. De todas
formas no son operaciones a las que se est normalmente habituado, y
eso hace que no resulte muchas veces evidente su uso. Los operadores
a nivel de bit operan a mucha mayor velocidad que, por ejemplo, el
operador producto o cociente. En la medida en que se sabe, quien
trabaja haciendo uso de esos operadores puede lograr programas
notablemente ms veloces en ejecucin.

Operadores compuestos.
Ya se ha visto que una expresin de asignacin en C trae, a su izquierda
(Lvalue), el nombre de una variable y, a su derecha (Rvalue) una
expresin a evaluar, o un literal, o el nombre de otra variable. Y ocurre
frecuentemente que la variable situada a la izquierda forma parte de la
expresin de la derecha. En estos casos, y si la expresin es sencilla,
todos los operadores aritmticos y los operadores a nivel de bit binarios
(exceptuando, por tanto, los operadores de signo, incremento,
decremento y complemento) pueden presentar otra forma, en la que se
asocia el operador con el operador asignacin. Son los llamados
operadores de asignacin compuestos:
+= x += y; es lo mismo que decir x = x + y;
= x -= y; es lo mismo que decir x = x y;
*= x *= y; es lo mismo que decir x = x * y;
/= x /= y; es lo mismo que decir x = x / y;
%= x %= y; es lo mismo que decir x = x % y;
>>= x >>= y; es lo mismo que decir x = x >> y;
<<= x <<= y; es lo mismo que decir x = x << y;
&= x &= y; es lo mismo que decir x = x & y;
|= x |= y; es lo mismo que decir x = x | y;
^= x ^= y; es lo mismo que decir x = x ^ y;
Puede parecer que estos operadores no facilitan la comprensin del
cdigo escrito. Quiz una expresin de la forma F*=n--; no tenga una
presentacin muy clara. Pero de hecho estos operadores compuestos se
usan frecuentemente y, quien se habita a ellos, agradece que se hayan
definido para el lenguaje C.
Fundamentos de informtica. Programacin en Lenguaje C


40
Por cierto, que la expresin del prrafo anterior es equivalente a escribir
estas dos lneas de cdigo: F = F * n; y n = n 1;.

Operador sizeof.
Ya sabemos el nmero de bytes que ocupan en memoria todas las
variables de tipo de dato primitivo en C: 1 byte las variables tipo char;
2 las de tipo short; 4 las de tipo long y float; 8 las de tipo double, y
10 las variables long double.
Pero ya se ha dicho que adems de estos tipos primitivos, C permite la
definicin de otros tipos diferentes, combinacin de esos primitivos. Y
los tamaos de esos tipos definidos pueden ser tan diversos como
diversas pueden ser las definiciones de esos nuevos tipos. no es extrao
trabajar con tipos cuyas variables ocupan 13 bytes, 1045, cualquier
otro tamao.
C ofrece un operador que devuelve la cantidad de bytes que ocupa una
variable o un tipo de dato concreto. El valor devuelto es tomado como
un entero, y puede estar presente en cualquier expresin de C. Es el
operador sizeof. Su sintaxis es:
sizeof(nombre_variable); sizeof(nombre_tipo_de_dato);
ya que se puede utilizar tanto con una variable concreta como
indicndole al operador el nombre del tipo de dato sobre el que
queramos conocer su tamao. No es vlido utilizar este operador
indicando entre parntesis el tipo de dato void: esa instruccin dara
error en tiempo de compilacin.
Con este operador aseguramos la portabilidad, al no depender la
aplicacin del tamao del tipo de datos de la mquina que se vaya a
usar. Aunque ahora mismo no se ha visto en este texto qu utilidad
puede tener en un programa conocer, como dato de clculo, el nmero
Captulo 2. Tipos de datos y variables en C.


41
de bytes que ocupa una variable, la verdad es que con frecuencia ese
dato es muy necesario.
Ejemplo: Podemos ver el tamao de los diferentes tipos de datos
primitivos de C. Basta teclear este cdigo en nuestro editor:
#include <stdio.h>
main()
{
printf("int => %d\n",sizeof(int));
printf("char => %d\n",sizeof(char));
printf("short => %d\n",sizeof(short));
printf("long => %d\n",sizeof(long));
printf("float => %d\n",sizeof(float));
printf("double => %d\n",sizeof(double));
}
(La funcin printf quedar presentada y explicada en el prximo
captulo.)

Expresiones en las que intervienen variables de
diferente tipo.
Hay lenguajes de programacin que no permiten realizar operaciones
con valores de tipos de dato distintos. Se dice que son lenguajes de
tipado fuerte, que fuerzan la comprobacin de la coherencia de tipos en
todas las expresiones, y lo verifican en tiempo de compilacin.
El lenguaje C NO es de esos lenguajes, y permite la compilacin de un
programa con expresiones que mezclan los tipos de datos.
Y aunque en C se pueden crear expresiones en las que intervengan
variables y literales de diferente tipo de dato, el procesador trabaja de
forma que todas las operaciones que se realizan en la ALU sean con
valores del mismo dominio.
Para lograr eso, cuando se mezclan en una expresin diferentes tipos de
dato, el compilador convierte todas las variables a un nico tipo
compatible; y slo despus de haber hecho la conversin se realiza la
operacin.
Fundamentos de informtica. Programacin en Lenguaje C


42
Esta conversin se realiza de forma que se no se pueda perder
informacin: en una expresin donde intervienen elementos de
diferentes dominios, todos los valores se codifican de acuerdo con el tipo
de dato de mayor rango.
La ordenacin de rango de los tipos de dato primitivos de C es, de
menor a mayor, la siguiente:
char short long float double long double
As, por ejemplo, si se presenta el siguiente cdigo:
char ch = 7;
short sh = 2;
long ln = 100, ln2;
double x = 12.4, y;
y = (ch * ln) / sh x;
La expresin para el clculo que se almacenar en la variable y va
cambiando de tipo de dato a medida que se va realizando: en el
producto de la variable char con la variable long, se fuerza el cambio
de la variable de tipo char, que se recodificar y as quedar para su
uso en la ALU, a tipo long. Esa suma ser por tanto un valor long.
Luego se realizar el cociente con la variable short, que deber
convertirse en long para poder dividir al resultado long antes obtenido.
Y, finalmente, el resultado del cociente se debe convertir en un valor
double, para poder restarle el valor de la variable x.
El resultado final ser pues un valor del tipo de dato double. Y as ser
almacenado en la posicin de memoria de la variable y de tipo double.
Si la ltima instruccin hubiese sido
ln2 = (ch * ln) / sh x;
todo hubiera sido como se ha explicado, pero a la hora de almacenar la
informacin en la memoria reservada para la variable long ln2, el
resultado final, que vena expresado en formato double, deber
recodificarse para ser guardado como long. Y es que, en el trasiego de
la memoria a los registros de la ALU, bien se puede hacer un cambio de
Captulo 2. Tipos de datos y variables en C.


43
tipo y por tanto un cambio de forma de codificacin y, especialmente, de
nmero de bytes empleados para esa codificacin. Pero lo que no se
puede hacer es que en una posicin de memoria como la del ejemplo,
que dedica 32 bits a almacenar informacin, se quiera almacenar un
valor de 64 bits, que es lo que ocupan las variables double.
Ante el operador asignacin, si la expresin evaluada, situada en la
parte derecha de la asignacin, es de un tipo de dato diferente al tipo de
dato de la variable indicada a la izquierda de la asignacin, entonces el
valor del lado derecho de la asignacin se convierte al tipo de dato del
lado izquierdo. En este caso el forzado de tipo de dato puede consistir
en llevar un valor a un tipo de dato de menor rango. Y ese cambio corre
el riesgo de perder truncar la informacin.

Operador para forzar cambio de tipo.
En el epgrafe anterior se ha visto el cambio o conversin de tipo de
dato que se realiza de forma implcita en el procesador cuando
encuentra expresiones que contienen diferentes tipos de dato. Tambin
existe una forma en que programador puede forzar un cambio de tipo de
forma explcita. Este cambio se llama cambio por promocin, o casting.
C dispone de un operador para forzar esos cambios.
La sintaxis de este operador unario es la siguiente:
( tipo) nombre_ variable;
El operador de promocin de tipo, o casting, precede a la variable. Se
escribe entre parntesis el nombre del tipo de dato hacia donde se
desea forzar el valor codificado en la variable sobre la que se aplica el
operador.
La operacin de conversin debe utilizarse con cautelas, de forma que
los cambios de tipo sean posibles y compatibles. No se puede realizar
cualquier cambio. Especialmente cuando se trabaja con tipos de dato
Fundamentos de informtica. Programacin en Lenguaje C


44
creados (no primitivos), que pueden tener una complejidad grande.
Tambin hay que estar vigilante a los cambios de tipo que fuerzan a una
disminucin en el rango: por ejemplo, forzar a que una variable float
pase a ser de tipo long. El rango de valores de una variable float es
muchos mayor, y si el valor de la variable es mayor que el valor mximo
del dominio de los enteros de 4 bytes, entonces el resultado del
operador forzar tipo ser imprevisible. Y tendremos entonces una
operacin que no ofrece problema alguno en tiempo de compilacin,
pero que bien puede llevar a resultados equivocados en tiempo de
ejecucin.

de tipo al tipo Posibles prdidas

char signed char Si el valor inicial es mayor de
127, entonces el nuevo valor
ser negativo.

short char Se pierden los 8 bits ms
significativos.

long int char Se pierden los 24 bits ms
significativos.

long int short Se pierden los 16 bits ms
significativos.

float int Se pierde la parte fraccional y
ms informacin.

double float Se pierde precisin. El
resultado se presenta
redondeado.

long double double Se pierde precisin. El
resultado se presenta
redondeado.


Tabla 2.5.: Prdidas de informacin en los cambios de tipo
con disminucin de rango.


No se pueden realizar conversiones del tipo void a cualquier otro tipo,
pero s de cualquier otro tipo al tipo void. Eso se entender mejor ms
adelante.
En la tabla 2.5. se muestran las posibles prdidas de informacin que se
pueden producir en conversiones forzadas de tipo de dato. Esas prdidas
Captulo 2. Tipos de datos y variables en C.


45
se darn tanto si la conversin de tipo de dato viene forzada por el
operador conversor de tipo, como si es debido a exigencias del operador
asignacin. Evidentemente, salvo motivos intencionados que rara vez se
han de dar, este tipo de conversiones hay que evitarlas. Los
compiladores no interrumpen el trabajo de compilacin cuando
descubren, en el cdigo, alguna conversin de esta ndole, pero s
advierten de aquellas expresiones donde puede haber un cambio
forzado de tipo de dato que conduzca a la prdida de informacin.

Propiedades de los operadores.
Al evaluar una expresin formada por diferentes variables y literales, y
por diversos operadores, hay que lograr expresar realmente lo que se
desea operar. Por ejemplo, la expresin a + b * c Se evala como el
producto de la suma de a y b, con c; o se evala como la suma del
producto de b con c, y a?
Para definir unas reglas que permitan una interpretacin nica e
inequvoca de cualquier expresin, se han definido tres propiedades en
los operadores:
1. su posicin. Establece dnde se coloca el operador con respecto a
sus operandos. Un operador se llamar infijo si viene a colocarse
entre sus operandos; y se llamar prefijo se el operador precede al
operando.
2. su precedencia. Establece el orden en que se ejecutan los distintos
operadores implicados en una expresin. Existe un orden de
precedencia perfectamente definido, de forma que en ningn caso
una expresin puede tener diferentes interpretaciones. Y el
compilador de C siempre entender las expresiones de acuerdo con
su orden de precedencia establecido.
3. su asociatividad. Esta propiedad resuelve la ambigedad en la
eleccin de operadores que tengan definida la misma precedencia.
Fundamentos de informtica. Programacin en Lenguaje C


46
En la prctica habitual de un programador, se acude a dos reglas para
lograr escribir expresiones que resulten correctamente evaluadas:
1. Hacer uso de parntesis. De hecho los parntesis son un operador
ms, que adems son los primeros en el orden de ejecucin. De
acuerdo con esta regla, la expresin antes recogida podra escribirse
(a + b) * c; a + (b * c); en funcin de cul de las dos se desea.
Ahora, con los parntesis, estas expresiones no llevan a equvoco
alguno.
2. Conocer y aplicar las reglas de precedencia y de asociacin por
izquierda y derecha. Este orden podr ser siempre alterado mediante
el uso de parntesis. Segn esta regla, la expresin antes recogida
se interpreta como a + (b * c).
Se considera buena prctica de programacin conocer esas reglas de
precedencia y no hacer uso abusivo de los parntesis. De todas
formas, cuando se duda sobre cmo se evaluar una expresin, lo
habitual es echar mano de los parntesis. A veces una expresin
adquiere mayor claridad si se recurre al uso de los parntesis.
Las reglas de precedencia son las que se recogen el la tabla 2.6. Cuanto
ms alto en la tabla est el operador, ms alta es su precedencia, y
antes se evala ese operador que cualquier otro que est ms abajo en
la tabla. Y para aquellos operadores que estn en la misma fila, es decir,
que tengan el mismo grado de precedencia, el orden de evaluacin, en
el caso en que ambos operadores intervengan en una expresin, viene
definido por la asociatividad: de derecha a izquierda o de izquierda a
derecha.
Existen 16 categoras de precedencia, y todos los operadores colocados
en la misma categora tienen igual precedencia que cualquiera otro de la
misma categora. Algunas de esas categoras tan solo tienen un
operador.
Captulo 2. Tipos de datos y variables en C.


47
Cuando un operador viene duplicado en la tabla, la primera ocurrencia
es como operador unario, la segunda como operador binario.
Cada categora tiene su regla de asociatividad: de derecha a izquierda
(anotada como D I), o de izquierda a derecha (anotada como I D).




() [] -> .
I D

! ~ ++ -- + - * &
D I

.* ->*
I D

* / %
I D

+ -
I D

<< >>
I D

> >= < <=
I D

== !=
I D

&
I D

^
I D

|
I D

&&
I D

||
D I

?:
I D
= += -= *= /= %= &= |= <<= >>= D I
, I D

Tabla 2.6.: Precedencia y Asociatividad de los operadores.

Por ejemplo, la expresin
a * x + b * y c / z:
se evala en el siguiente orden: primero los productos y el cociente, y
ya luego la suma y la resta. Todos estos operadores estn en categoras
con asociatividad de izquierda a derecha, por lo tanto, primero se
efecta el producto ms a la izquierda y luego el segundo, ms al centro
Fundamentos de informtica. Programacin en Lenguaje C


48
de la expresin. Despus se efecta el cociente; luego la suma y
finalmente la resta.
Todos los operadores de la tabla 2.6. que faltan por presentar en el
manual se emplean para vectores y cadenas y para operatoria de
punteros. Ms adelante se conocern todos ellos.

Valores fuera de rango en una variable.
Ya hemos dicho repetidamente que una variable es un espacio de
memoria, de un tamao concreto en donde la informacin se codifica de
una manera determinada por el tipo de dato que se vaya a almacenar
en esa variable.
Espacio de memoria limitado.
Es importante conocer los lmites de nuestras variables. Esos lmites ya
venan presentados en la tabla 2.1.
Cuando en un programa se pretende asignar a una variable un valor que
no pertenece al dominio, el resultado es habitualmente extrao. Se
suele decir que es imprevisible, pero la verdad es que la electrnica del
procesador acta de la forma para la que est diseada, y no son
valores aleatorios los que se alcanzan entonces, aunque s, muchas
veces, valores no deseados, o valores errneos.
Se muestra ahora el comportamiento de las variables ante un
desbordamiento (que as se le llama) de la memoria. Si la variable es
entera, ante un desbordamiento de memoria el procesador trabaja de la
misma forma que lo hace, por ejemplo, el cuenta kilmetros de un
vehculo. Si en un cuenta kilmetros de cinco dgitos, est marcado el
valor 99.998 kilmetros, al recorrer cinco kilmetros ms, el valor que
aparece en pantalla ser 00.003. Se suele decir que se le ha dado la
vuelta al marcador. Y algo similar ocurre con las variables enteras. En la
Captulo 2. Tipos de datos y variables en C.


49
tabla 2.7. se muestran los valores de diferentes operaciones con
desbordamiento.

signed short 32767 + 1 da el valor 32768
unsigned short 65535 + 1 da el valor 0
signed long 2147483647 +1 da el valor -2147483648
unsigned long 4294967295 + 1 da el valor 0


Tabla 2.7.: valores de desbordamiento en las variables de
tipo entero.


Si el desbordamiento se realiza por asignacin directa, es decir,
asignando a una variable un literal que sobrepasa el rango de su
dominio, o asignndole el valor de una variable de rango superior,
entonces se almacena el valor truncado. Por ejemplo, si a una variable
unsigned short se le asigna un valor que requiere 25 dgitos binarios,
nicamente se quedan almacenados los 16 menos significativos. A eso
hay que aadirle que, si la variable es signed short, al tomar los 16
dgitos menos significativos, interpretar el ms significativo de ellos
como el bit de signo, y segn sea ese bit, interpretar toda la
informacin codificada como entero negativo en complemento a la base,
o como entero positivo.
Hay situaciones y problemas donde jugar con las reglas de
desbordamiento de enteros ofrece soluciones muy rpidas y buenas.
Pero, evidentemente, en esos casos hay que saber lo que se hace.
Si el desbordamiento se realiza con variables en coma flotante el
resultado es mucho ms complejo de prever, y no resulta sencillo
pensar en una situacin en la que ese desbordamiento pueda ser
deseado.
Si el desbordamiento es por asignacin, la variable desbordada
almacenar un valor que nada tendr que ver con el original. Si el
desbordamiento tiene lugar por realizar operaciones en un tipo de dato
de coma flotante y en las que el valor final es demasiado grande para
Fundamentos de informtica. Programacin en Lenguaje C


50
ese tipo de dato, entonces el resultado es completamente imprevisible,
y posiblemente se produzca una interrupcin en la ejecucin del
programa. Ese desbordamiento se considera, sin ms, error de
programacin.

Constantes. Directiva #define.
Cuando se desea crear una variable a la que se asigna un valor inicial
que no debe modificarse, se la precede, en su declaracin, de la palabra
clave de C const.
const tipo var_ 1 = val_ 1[ ,var_ 2 = val_ 2, , var_ N = val_ N] ;
Se declara con la palabra reservada const. Pueden definirse constantes
de cualquiera de los tipos de datos simples.
const float DOS_PI = 6.28;
Como no se puede modificar su valor, la constante no puede ser un
Lvalue (excepto en el momento de su inicializacin). Si se intenta
modificar el valor de la constante mediante el operador asignacin el
compilador dar un error y no se crear el programa.
Otro modo de definir constantes es mediante la directiva de
preprocesador o de compilacin #define. Ya se ha visto en el primer
captulo otra directiva que se emplea para indicar al compilador de qu
bibliotecas se toman funciones ya creadas y compiladas: la directiva
#include.
#define DOS_PI 6.28
Como se ve, la directiva #define no termina en punto y coma. Eso es
debido a que las directivas de compilacin no son instrucciones de C,
sino rdenes que se dirigen al compilador. La directiva #define se
ejecuta previamente a la compilacin, y sustituye, en todas las lneas de
cdigo posteriores a la aparicin de la directiva, cada aparicin de la
Captulo 2. Tipos de datos y variables en C.


51
primera cadena de caracteres por la segunda cadena: en el ejemplo
antes presentado, la cadena DOS_PI por el valor 6.28.
Ya se ver cmo esta directiva pude resultar muy til en ocasiones.

Intercambio de valores de dos variables
Una operacin bastante habitual en un programa es el intercambio de
valores entre dos variables. Supongamos el siguiente ejemplo:
<variable1, int, R, 10> y <variable2, int, R, 20>
Si queremos que variable1 almacene el valor de variable2 y variable2 el
de variable1, es necesario acudir a una variable auxiliar. El proceso es
as:
auxiliar = variable1;
variable1 = variable2;
variable2 = auxiliar;
Porque no podemos copiar el valor de variable2 en variable1 sin perder
con esta asignacin el valor de variable1 que queramos guardar en
variable2.
Con el operador or exclusivo se puede hacer intercambio de valores sin
acudir, para ello, a una variable auxiliar. El procedimiento es el
siguiente:
variable1 = variable1 ^ variable2;
variable2 = variable1 ^ variable2;
variable1 = variable1 ^ variable2;
que, con operadores compuestos queda de la siguiente manera:
variable1 ^= variable2;
variable2 ^= variable1;
variable1 ^= variable2;
Veamos un ejemplo para comprobar que realmente realiza el
intercambio:
short int variable1 = 3579;
short int variable2 = 2468;
Fundamentos de informtica. Programacin en Lenguaje C


52
en base binaria el valor de las dos variables es:
variable1 0 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1
variable2 0 0 0 0 1 0 0 1 1 0 1 0 0 1 0 0
variable1 ^= variable2 0 0 0 0 0 1 0 0 0 1 0 1 1 1 1 1
variable2 ^= variable1 0 0 0 0 1 1 0 1 1 1 1 1 1 0 1 1
variable1 ^= variable2 0 0 0 0 1 0 0 1 1 0 1 0 0 1 0 0
Al final del proceso, el valor de variable1 es el que inicialmente tena
variable2, y al revs. Basta comparar valores. Para verificar que las
operaciones estn correctas (que lo estn) hay que tener en cuenta que
el proceso va cambiando los valores de variable1 y de variable2, y esos
cambios hay que tenerlos en cuenta en las siguientes operaciones or
exclusivo a nivel de bit.

Ayudas On line.
Muchos editores y compiladores de C cuentan con ayudas en lnea
abundante. Todo lo referido en este captulo puede encontrarse en ellas.
Es buena prctica de programacin saber manejarse por esas ayudas,
que llegan a ser muy voluminosas y que gozan de buenos ndices para
lograr encontrar el auxilio necesario en cada momento.

Recapitulacin.
Despus de estudiar este captulo, ya sabemos crear y operar con
nuestras variables. Tambin conocemos muchos de los operadores
definidos en C. Con todo esto podemos realizar ya muchos programas
sencillos.
Si conocemos el rango o dominio de cada tipo de dato, sabemos
tambin de qu tipo conviene que sea cada variable que necesitemos. Y
estaremos vigilantes en las operaciones que se realizan con esas
variables, para no sobrepasar ese dominio e incurrir en un overflow.
Tambin hemos visto las reglas para combinar, en una expresin,
variables y valores de diferente tipo de dato. Es importante conocer bien
Captulo 2. Tipos de datos y variables en C.


53
todas las reglas que gobiernan estas combinaciones porque con
frecuencia, si se trabaja sin tiento, se llegan a resultados errneos.
Con los ejercicios que se proponen a continuacin se pueden practicar y
poner a prueba nuestros conceptos adquiridos.


Ejemplos y ejercicios propuestos.

1. Escribir un programa que realice las operaciones de suma,
resta, producto, cociente y mdulo de dos enteros introducidos
por teclado.

#include <stdio.h>
void main(void)
{
signed long a, b;
signed long sum, res, pro, coc, mod;

printf("Introduzca el valor del 1er. operando ... ");
scanf("%ld",&a);
printf("Introduzca el valor del 2do. operando ... ");
scanf("%ld",&b);

// Clculos
sum = a + b;
res = a - b;
pro = a * b;
coc = a / b;
mod = a % b;

// Mostrar resultados por pantalla.
printf("La suma es igual a %ld\n", sum);
printf("La resta es igual a %ld\n", res);
printf("El producto es igual a %ld\n", pro);
printf("El cociente es igual a %ld\n", coc);
printf("El resto es igual a %ld\n", mod);
}
Fundamentos de informtica. Programacin en Lenguaje C


54
Observacin: cuando se realiza una operacin de cociente o de resto
es muy recomendable antes verificar que, efectivamente, el divisor no
es igual a cero. An no sabemos hacerlo, as que queda el programa un
poco cojo. Al ejecutarlo ser importante que el usuario no introduzca un
valor para la variable b igual a cero.

2. Repetir el mismo programa para nmeros de coma flotante.

#include <stdio.h>
void main(void)
{
float a, b;
float sum, res, pro, coc;

printf("Introduzca el valor del 1er. operando ... ");
scanf("%f",&a);
printf("Introduzca el valor del 2do. operando ... ");
scanf("%f",&b);

// Clculos
sum = a + b;
res = a - b;
pro = a * b;
coc = a / b;
// mod = a % b; : esta operacin no est permitida
// Mostrar resultados por pantalla.
printf("La suma es igual a %f\n", sum);
printf("La resta es igual a %f\n", res);
printf("El producto es igual a %f\n", pro);
printf("El cociente es igual a %f\n", coc);
}
En este caso se ha tenido que omitir la operacin mdulo, que no est
definida para valores del dominio de los nmeros de coma flotante. Al
igual que en el ejemplo anterior, se debera verificar (an no se han
presentado las herramientas que lo permiten), antes de realizar el
cociente, que el divisor era diferente de cero.

Captulo 2. Tipos de datos y variables en C.


55
3. Escriba un programa que resuelva una ecuacin de primer
grado ( + = 0 a x b ) . El programa solicita los valores de los
parmetros a y b y mostrar el valor resultante de la variable x.

#include <stdio.h>
void main(void)
{
float a, b;
printf("Introduzca el valor del parmetro a ... ");
scanf("%f",&a);
printf("Introduzca el valor del parmetro b ... ");
scanf("%f",&b);
printf("x = %f",-b / a);
}
De nuevo sera ms correcto el programa si, antes de realizar el
cociente, se verificase que la variable a es distinta de cero.

4. Escriba un programa que solicite un entero y muestre por
pantalla su valor al cuadrado y su valor al cubo.

#include <stdio.h>
void main(void)
{
short x;
long cuadrado, cubo;
printf("Introduzca un valor ... ");
scanf("%hi",&x);
cuadrado = (long)x * x;
cubo = cuadrado * x;
printf("El cuadrado de %hd es %li\n",x, cuadrado);
printf("El cubo de %hd es %li\n",x, cubo);
}
Es importante crear una presentacin cmoda para el usuario. No
tendra sentido comenzar el programa por la funcin scanf, porque en
ese caso el programa comenzara esperando un dato del usuario, sin
aviso previo que le indicase qu es lo que debe hacer. En el siguiente
Fundamentos de informtica. Programacin en Lenguaje C


56
tema se presenta con detalle las dos funciones de entrada y salida por
consola.
La variable x es short. Al calcular el valor de la variable cuadrado
forzamos el tipo de dato para que el valor calculado sea long y no se
pierda informacin en la operacin. En el clculo del valor de la variable
cubo no es preciso hacer esa conversin, porque ya la variable cuadrado
es de tipo long. En esta ltima operacin no queda garantizado que no
se llegue a un desbordamiento: cualquier valor de x mayor de 1290
tiene un cubo no codificable con 32 bits. Se puede probar qu ocurre
introduciendo valores mayores que ste indicado.

5. Escriba un programa que solicite los valores de la base y de la
altura de un tringulo y que imprima por pantalla el valor de la
superficie.

#include <stdio.h>
void main(void)
{
double b, h, S;
printf("Introduzca la base ... ");
scanf("%lf",&b);
printf("Introduzca la altura ... ");
scanf("%lf",&h);
S = b * h / 2;
printf("La superficie del triangulo de ");
printf("base %.2lf y altura %.2lf ",b,h);
printf("es %.2lf",S);
}
Las variables se han tomado double. As no se pierde informacin en la
operacin cociente. Puede probar a declarar las variables como de tipo
short, modificando tambin algunos parmetros de las funciones de
entrada y salida por consola:
void main(void)
{
short b, h, S;
Captulo 2. Tipos de datos y variables en C.


57
printf("Introduzca la base ... ");
scanf("%hd",&b);
printf("Introduzca la altura ... ");
scanf("%hd",&h);
S = b * h / 2;
printf("La superficie del triangulo de ");
printf("base %hd y altura %hd ",b,h);
printf("es %hd",S);
}
Si al ejecutar el programa el usuario introduce los valores 5 para la base
y 3 para la altura, el valor de la superficie que mostrar el programa al
ejecutarse ser ahora de 7, y no de 7,5.

6. Escriba un programa que solicite el valor del radio y muestre la
longitud de su circunferencia y la superficie del crculo inscrito
en ella.

#include <stdio.h>
#define PI 3.14159
void main(void)
{
signed short int r;
double l, S;
const double pi = 3.14159;
printf("Indique el valor del radio ... ");
scanf("%hd",&r);
printf("La longitud de la circunferencia");
printf(" cuyo radio es %hd",r);
l = 2 * pi * r;
printf(" es %lf. \n",l);
printf("La superficie de la circunferencia");
printf(" cuyo radio es %hd",r);
S = PI * r * r;
printf(" es %lf. \n",S);
}
En este ejemplo hemos mezclado tipos de dato. El radio lo tomamos
como entero. Luego, en el clculo de la longitud l, como la expresin
tiene el valor de la constante pi, que es double, se produce una
conversin implcita de tipo de dato, y el resultado final es double.
Fundamentos de informtica. Programacin en Lenguaje C


58
Para el clculo de la superficie, en lugar de emplear la constante pi se ha
tomado un identificador PI definido mediante la directiva #define. El
valor de PI tambin tiene forma de coma flotante, y el resultado ser
por tanto de este tipo.
A diferencia de los ejemplos anteriores, en ste hemos guardado los
resultados obtenidos en variables.

7. Escriba un programa que solicite el valor de la temperatura en
grados Fahrenheit y muestre por pantalla el equivalente en
grados Celsius. La ecuacin que define esta trasformacin es:
Celsius = ( 5 / 9) ( Fahrenheit 32) .

#include <stdio.h>
void main(void)
{
double fahr, cels;
printf("Temperatura en grados Fahrenheit ... ");
scanf("%lf",&fahr);
cels = (5 / 9) * (fahr - 32);
printf("La temperatura en grados Celsius ");
printf("resulta ... %lf.",cels);
}
Tal y como est escrito el cdigo parece que todo ha de ir bien. Si
ensayamos el programa con la entrada en grado Fahrenheit igual a 32,
entonces la temperatura en Celsius resulta 0, que es correcto puesto
que esas son las temperaturas, en las dos tablas, en las que se derrite
el hielo.
Pero, y si probamos con otra entrada?... Tambin da 0! Por qu?
Pues porque (5 / 9) es una operacin cociente entre dos enteros, cuyo
resultado es un entero: el truncado, es decir, el mayor entero menor
que el resultado de la operacin; es decir, 0.
Cmo se debera escribir la operacin?
Captulo 2. Tipos de datos y variables en C.


59
cels = (5 / 9.0) * (fahr - 32);
cels = (5.0 / 9) * (fahr - 32);
cels = ((double)5 / 9) * (fahr - 32);
cels = (5 / (double)9) * (fahr - 32);
cels = 5 * (fahr - 32) / 9;
Hay muchas formas: lo importante es saber en todo momento qu
operacin realizar la sentencia que escribimos. Hay que saber escribir
expresiones que relacionan valores de distinto tipo para no perder nunca
informacin por falta de rango al elegir mal uno de los tipos de alguno
de los literales o variables.
Es importante, en este tema de los lenguajes, no perder de vista la
realidad fsica que subyace en toda la programacin. Un lenguaje de
programacin no es una herramienta que relacione variables en el
sentido matemtico de la palabra. Relaciona elementos fsicos, llamados
posiciones de memoria. Y es importante, al programar, razonar de
acuerdo con la realidad fsica con la que trabajamos.

8. Rotaciones de bits dentro de un entero.

Una operacin que tiene uso en algunas aplicaciones de tipo
criptogrficos es la de rotacin de un entero. Rotar un nmero x
posiciones hacia la derecha consiste en desplazar todos sus bits hacia la
derecha esas x posiciones, pero sin perder los x bits menos
significativos, que pasarn a situarse en la parte ms significativa del
nmero. Por ejemplo, el nmero a = 1101 0101 0011 1110 rotado 4 bits
a la derecha queda 1110 1101 0101 0011, donde los cuatro bits menos
significativos (1110) se han venido a posicionar en la izquierda del
nmero.
Fundamentos de informtica. Programacin en Lenguaje C


60
La rotacin hacia la izquierda es anlogamente igual, donde ahora los x
bits ms significativos del nmero pasan a la derecha del nmero. El
nmero a rotado 5 bits a la izquierda quedara: 1010 0111 1101 1010.
Qu rdenes deberamos dar para lograr la rotacin de nmeros
a la izquierda:

unsigned short int a, b, despl;
a = 0xABCD;
despl = 5;
b = ((a << despl) | (a >> (8 * sizeof(short) - despl)));
Veamos cmo funciona este cdigo:
a: 1010 1011 1100 1101
despl: 5
a << despl: 0111 1001 1010 0000
8 * sizeof(short) despl: 8 * 2 5 = 11
a >> 11: 0000 0000 0001 0101
b: 0111 1001 1011 0101
Y para lograr la rotacin de nmeros a la derecha:
unsigned short int a, b, despl;
a = 0xABCD;
despl = 5;
b = ((a >> despl) | (a << (8 * sizeof(short) - despl)));

9. I ndicar el valor que toman las variables en las siguientes
asignaciones.

int a = 10, b = 5, c, d;
float x = 10.0 ,y = 5.0, z, t, v;
c = a/b;
d = x/y;
z = a/b;
t = (1 / 2) * x;
v = (1.0 / 2) * x;


Captulo 2. Tipos de datos y variables en C.


61
10. Escribir el siguiente programa y j ustificar la salida que ofrece
por pantalla.

#include <stdio.h>
void main(void)
{
char a = 127;
a++;
printf("%hd", a);
}
La salida que se obtiene con este cdigo es -128. Intente justificar por
qu ocurre. No se preocupe si an no conoce el funcionamiento de la
funcin printf(). Verdaderamente la variable a ahora vale -128. Por
qu?
Si el cdigo que se ejecuta es el siguiente, explique la salida obtenida.
Sabra adivinar qu va a salir por pantalla antes de ejecutar el
programa?
#include <stdio.h>
void main(void)
{
short sh = 0x7FFF;
long ln = 0x7FFFFFFF;
printf("\nEl valor inicial de sh es ... %hd", sh);
printf("\nEl valor inicial de ln es ... %ld", ln);
sh++;
ln++;
printf("\nEl valor final de sh es ... %hd", sh);
printf("\nEl valor final de ln es ... %ld", ln);
}

11. Escribir un programa que indique cuntos bits y bytes ocupa
una variable long.

#include <stdio.h>
void main(void)
{
Fundamentos de informtica. Programacin en Lenguaje C


62
short bits, bytes;
bytes = sizeof(long);
bits = 8 * bytes;
printf("BITS = %hd - BYTES = %hd.", bits, bytes);
}

12. Escribir el siguiente programa y explique las salidas que ofrece
por pantalla.

#include <stdio.h>

void main(void)
{
char a = 'X', b = 'Y';
printf("\nvalor (caracter) de a: %c", a);
printf("\nvalor (caracter) de b: %c", b);

printf("\nvalor de a (en base 10): %hd", a);
printf("\nvalor de b (en base 10): %hd", b);

printf("\nvalor (caracter) de a + b: %c", a + b);
printf("\nvalor (en base 10) de a + b: %hd", a + b);

printf("\nvalor (caracter) de a+b + 5: %c", a + b + 5);
printf("\nvalor (en base 10) de a+b+5: %hd", a+b + 5);
}
La salida que se obtiene por pantalla es:
valor (caracter) de a: X
valor (caracter) de b: Y
valor de a (en base 10): 88
valor de b (en base 10): 89
valor (caracter) de a + b:
valor (en base 10) de a + b: 177
valor (caracter) de a + b + 5:
valor (en base 10) de a + b + 5: 182

13. Escriba un programa que calcule la superficie y el volumen de
una esfera cuyo radio es introducido por teclado.
=
2
4 S r =
3
4 3 V r
Captulo 2. Tipos de datos y variables en C.


63

(Recuerde que, en C, la expresin 4 3 no significa el valor racional
resultante del cociente, sino un nuevo valor entero, que es
idnticamente igual a 1.)

14. El nmero ureo ( ) es aquel que verifica la propiedad de que
al elevarlo al cuadrado se obtiene el mismo valor que al
sumarle 1. Haga un programa que calcule y muestre por
pantalla el nmero ureo.

#include <stdio.h>
#include <math.h>
void main(void)
{
double aureo;
printf("Nmero AUREO: tal que x + 1 = x * x.\n");
// Clculo del nmero Aureo
// x^2 = x + 1 ==> x^2 - x - 1 = 0 ==> x = (1 + sqrt(5)) / 2.
aureo = (1 + sqrt(5)) / 2;
printf("El nmero AUREO es .. %lf\n",aureo);
printf("aureo + 1 ........... %lf\n",aureo + 1);
printf("aureo * aureo ....... %lf\n", aureo * aureo);
}
La funcin sqrt() est definida en la biblioteca math.h. Calcula el valor
de la raz cuadrada de un nmero. Espera como parmetro una variable
de tipo double, y devuelve el valor en este formato o tipo de dato.
El ejercicio es muy sencillo. La nica complicacin (si se le puede llamar
complicacin a esta trivialidad) es saber cmo se calcula el nmero
aureo a partir de la definicin aportada. Muchas veces el problema de la
programacin no est en el lenguaje, sino en saber expresar una
solucin viable de nuestro problema.

Fundamentos de informtica. Programacin en Lenguaje C


64
15. Escriba un programa que calcule a qu distancia caer un
proyectil lanzado desde un can. El programa recibe desde
teclado el ngulo inicial de salida del proyectil ( ) y su
velocidad inicial (
0
V ) .
Tenga en cuenta las siguientes ecuaciones que definen el
comportamiento del sistema descrito:
=
0
cos( )
x
V V =
0
( )
y
V V sen g t
=
0
cos( ) x V t =
2
0
1
( )
2
y V t sen g t








CAPTULO 3

FUNCIONES DE ENTRADA Y SALIDA
POR CONSOLA

Hasta el momento, hemos presentado las sentencias de creacin y
declaracin de variables. Tambin hemos visto multitud de operaciones
que se pueden realizar con las diferentes variables y literales. Pero an
no sabemos cmo mostrar un resultado por pantalla. Y tampoco hemos
aprendido todava a introducir informacin, para un programa en
ejecucin, desde el teclado.
El objetivo de este breve captulo es iniciar en la comunicacin entre el
programa y el usuario.
Lograr que el valor de una variable almacenada de un programa sea
mostrado por pantalla sera una tarea compleja si no fuese porque ya
ANSI C ofrece funciones que realizan esta tarea. Y lo mismo ocurre
cuando el programador quiere que sea el usuario quien teclee una
entrada durante la ejecucin del programa.
Fundamentos de informtica. Programacin en Lenguaje C


66
Estas funciones, de entrada y salida estndar de datos por consola,
estn declaradas en un archivo de cabecera llamado stdio.h. Siempre
que deseemos usar estas funciones deberemos aadir, al principio del
cdigo de nuestro programa, la directriz #include <stdio.h>.

Salida de datos. La funcin printf().
El prototipo de la funcin es el siguiente:
int printf( const char * cadena_ control[ , argumento, ...] ) ;
Qu es un prototipo de una funcin es cuestin que habr que explicar
en otro captulo. Sucintamente, diremos que el prototipo indica el modo
de empleo de la funcin: qu tipo de dato devuelve y qu valores espera
recibir cuando se hace uso de ella. El prototipo nos sirve para ver cmo
se emplea esta funcin.
La funcin printf devuelve un valor entero. Se dice que es de tipo int. La
funcin printf devuelve un entero que indica el nmero de bytes que ha
impreso en pantalla. Si, por la causa que sea, la funcin no se ejecuta
correctamente, en lugar de ese valor entero lo que devuelve es un valor
que significa error (por ejemplo un valor negativo). No descendemos a
ms detalles.
La funcin, como toda funcin, lleva despus del nombre un par de
parntesis. Entre ellos va redactado el texto que deseamos que quede
impreso en la pantalla. La cadena_control indica el texto que debe ser
impreso, con unas especificaciones que indican el formato de esa
impresin; es una cadena de caracteres recogidos entre comillas, que
indica el texto que se ha de mostrar por pantalla. A lo largo de este
captulo aprenderemos a crear esas cadenas de control que especifican
la salida y el formato que ha de mostrar la funcin printf.
Para comenzar a practicar, comenzamos por escribir en el editor de C el
siguiente cdigo. Es muy recomendable que a la hora de estudiar
Captulo 3. Funciones de entrada y salida por consola.


67
cualquier lenguaje de programacin, y ahora en concreto el lenguaje C,
se trabaje delante de un ordenador que tenga un editor y un compilador
de cdigo:
#i ncl ude <st di o. h>
void mai n( void)
{
pr i nt f ( Text o a most r ar en pant al l a) ;
}
Que ofrecer la siguiente salida por pantalla
Text o a most r ar en pant al l a
Y as, cualquier texto que se escriba entre las comillas aparecer en
pantalla.
Si introducimos ahora otra instruccin con la funcin printf a
continuacin y debajo de la otra, por ejemplo
pr i nt f ( Ot r o t ext o) ;
Entonces lo que tendremos en pantalla ser
Text o a most r ar en pant al l aOt r o t ext o
Y es que la funcin printf continua escribiendo donde se qued la vez
anterior.
Muchas veces nos va a interesar introducir, en nuestra cadena de
caracteres que queremos imprimir por pantalla, algn carcter de, por
ejemplo, salto de lnea. Pero si tecleamos la tecla intro en el editor de C
lo que hace el cursor en el editor es cambiar de lnea y eso que no
queda reflejado luego en el texto que muestra el programa en tiempo de
ejecucin.
Para poder escribir este carcter de salto de lnea, y otros que llamamos
caracteres de control, se escribe, en el lugar de la cadena donde
queremos que se imprima ese carcter especial, una barra invertida (\)
seguida de una letra. Cul letra es la que se debe poner depender de
qu carcter especial se desea introducir. Esos caracteres de control son
Fundamentos de informtica. Programacin en Lenguaje C


68
caracteres no imprimibles, o caracteres que tienen ya un significado
especial en la funcin printf.
Por ejemplo, el cdigo anterior quedara mejor de la siguiente forma:
#i ncl ude <st di o. h>
void mai n( void)
{
pr i nt f ( Text o a most r ar en pant al l a\ n) ;
pr i nt f ( Ot r o t ext o. )
}
que ofrecer la siguiente salida por consola:
Text o a most r ar en pant al l a
Ot r o t ext o
Ya que al final de la cadena del primer printf hemos introducido un
carcter de control salto de lnea: \n significa, dentro de la funcin
printf, salto de lnea.
Las dems letras con significado para un carcter de control en esta
funcin vienen recogidas en la tabla 3.1.

\a Carcter sonido. Emite un pitido breve.
\v Tabulador vertical.
\0 Carcter nulo.
\n Nueva lnea.
\t Tabulador horizontal.
\b Retroceder un carcter.
\r Retorno de carro.
\f Salto de pgina.
\ Imprime la comilla simple.
\ Imprime la comilla doble.
\\ Imprime la barra invertida \.
\xdd dd es el cdigo ASCII, en hexadecimal, del
carcter que se desea imprimir.


Tabla 3.1.: Caracteres de control en la cadena de
la funcin printf.


Muchas pruebas se pueden hacer ya en el editor de C, para compilar y
ver la ejecucin que resulta. Gracias a la ltima opcin de la tabla 3.1.
es posible imprimir todos los caracteres ASCII y los tres inmediatamente
Captulo 3. Funciones de entrada y salida por consola.


69
anteriores sirven para imprimir caracteres que tienen un significado
preciso dentro de la cadena de la funcin printf. Gracias a ellos podemos
imprimir, por ejemplo, un carcter de comillas dobles evitando que la
funcin printf interprete ese carcter como final de la cadena que se
debe imprimir.
El siguiente paso, una vez visto cmo imprimir texto prefijado, es
imprimir en consola el valor de una variable de nuestro programa.
Cuando en un texto a imprimir se desea intercalar el valor de una
variable, en la posicin donde debera ir ese valor se coloca el carcter
% seguido de algunos caracteres. Segn los caracteres que se
introduzcan, se imprimir un valor de un tipo de dato concreto, con un
formato de presentacin determinado. Ese carcter % y esos
caracteres que le sigan son los especificadores de formato. Al final de
la cadena, despus de las comillas de cierre, se coloca una coma y el
nombre de la variable que se desea imprimir.
Por ejemplo, el siguiente cdigo
#i ncl ude <st di o. h>
void mai n( void)
{
short int a = 5 , b = 10 , c;
c = a + b++;
pr i nt f ( Ahor a c val e %hd \ n, c) ;
pr i nt f ( y b val e ahor a %hd , b) ;
}
Que ofrece la siguiente salida por pantalla:
Ahor a c val e 15
y b val e ahor a 11
Una cadena de texto de la funcin printf puede tener tantos
especificadores de formato como se desee. Tantos como valores de
variables queramos imprimir por pantalla. Al final de la cadena, y
despus de una coma, se incluyen tantas variables, separadas tambin
por comas, como especificadores de formato se hayan incluido en la
cadena de texto. Cada grupo de caracteres encabezado por % en el
Fundamentos de informtica. Programacin en Lenguaje C


70
primer argumento de la funcin (la cadena de control) est asociado con
el correspondiente segundo, tercero, etc. argumento de esa funcin.
Debe existir una correspondencia biunvoca entre el nmero de
especificadores de formato y el nmero de variables que se recogen
despus de la cadena de control; de lo contrario se obtendrn resultados
imprevisibles y sin sentido.
El especificador de formato instruye a la funcin sobre la forma en que
deben ir impresos cada uno de los valores de las variables que
deseamos que se muestren por pantalla.
Los especificadores tienen la forma:
% [ flags] [ ancho campo] [ .precisin] [ F/ N/ h/ l/ L] type
Veamos los diferentes componentes del especificador de formato:
type: Es el nico argumento necesario. Consiste en una letra que indica
el tipo de dato a que corresponde al valor que se desea imprimir en
esa posicin. En la tabla 3.2. se recogen todos los valores que definen
tipos de dato. Esta tabla est accesible en las ayudas de editores y
compiladores de C.
[ F / N / h / l / L] : Estas cinco letras son modificadores de tipo y
preceden a las letras que indican el tipo de dato que se debe mostrar
por pantalla.
La letra h es el modificador short para valores enteros.
La letra l tiene dos significados: es el modificador long para valores
enteros. Y, precediendo a la letra f indica que all debe ir un valor de tipo
double.
La letra L precediendo a la letra f indica que all debe ir un valor de tipo
long double.
[ ancho campo] [ .precisin] : Estos dos indicadores opcionales deben ir
antes de los indicadores del tipo de dato. Con el ancho de campo, el
Captulo 3. Funciones de entrada y salida por consola.


71
especificador de formato indica a la funcin printf la longitud mnima
que debe ocupar la impresin del valor que all se debe mostrar.

%d Entero con signo, en base decimal.
%i Entero con signo, en base decimal.
%o Entero (con o sin signo) codificado en base
octal.

%u Entero sin signo, en base decimal.
%x Entero (con o sin signo) codificado en base
hexadecimal, usando letras minsculas.
Codificacin interna de los enteros.

%X Entero (con o sin signo) codificado en base
hexadecimal, usando letras maysculas.
Codificacin interna de los enteros.

%f Nmero real con signo.
%e Nmero real con signo en formato cientfico,
con el exponente e en minscula.

%E Nmero real con signo en formato cientfico,
con el exponente e en mayscula.

%g Nmero real con signo, a elegir entre formato e
f segn cul sea el tamao ms corto.

%G Nmero real con signo, a elegir entre formato E
f segn cul sea el tamao ms corto.

%c Un carcter. El carcter cuyo ASCII corresponda
con el valor a imprimir.

%s Cadena de caracteres.
%p Direccin de memoria.
%n No lo explicamos aqu ahora.
%% Si el carcter % no va seguido de nada,
entonces se imprime el carcter sin ms.


Tabla 3.2.: Especificadores de tipo de dato en la
funcin printf.


Para mostrar informacin por pantalla la funcin printf emplea un tipo
de letra de paso fijo. Esto quiere decir que cada carcter impreso
ocasiona el mismo desplazamiento del cursor hacia la derecha. Al decir
que el ancho de campo indica la longitud mnima se quiere decir que
este parmetro seala cuntos avances de cursor deben realizarse,
como mnimo, al imprimir el valor.
Por ejemplo, las instrucciones
Fundamentos de informtica. Programacin en Lenguaje C


72
long int a = 123, b = 4567, c = 135790;
pr i nt f ( La var i abl e a val e . . . %6l i . \ n, a) ;
pr i nt f ( La var i abl e b val e . . . %6l i . \ n, b) ;
pr i nt f ( La var i abl e c val e . . . %6l i . \ n, c) ;
tiene la siguiente salida:
La var i abl e a val e . . . 123.
La var i abl e b val e . . . 4567.
La var i abl e c val e . . . 135790.
donde vemos que los tres valores impresos en lneas diferentes quedan
alineados en sus unidades, decenas, centenas, etc. gracias a que todos
esos valores se han impreso con un ancho de campo igual a 6: su
impresin ha ocasionado tantos desplazamientos de cursos como indica
el ancho de campo.
Si la cadena o nmero es mayor que el ancho de campo indicado
ignorar el formato y se emplean tantos pasos de cursor como sean
necesarios para imprimir correctamente el valor.
Si se desea, es posible rellenar con ceros los huecos del avance de
cursor. Para ellos se coloca un 0 antes del nmero que indica el ancho
de campo
La instruccin
pr i nt f ( La var i abl e a val e . . . %06l i . \ n, a) ;
ofrece como salida la siguiente lnea en pantalla:
La var i abl e a val e . . . 000123.
El parmetro de precisin se emplea para valores con coma flotante.
Indica el nmero de decimales que se deben mostrar. Indica cuntos
dgitos no enteros se deben imprimir: las posiciones decimales. A ese
valor le precede un punto. Si el nmero de decimales del dato
almacenado en la variable es menor que la precisin sealada, entonces
la funcin printf completa con ceros ese valor. Si el nmero de
decimales del dato es mayor que el que se indica en el parmetro de
precisin, entonces la funcin printf trunca el nmero.
Captulo 3. Funciones de entrada y salida por consola.


73
Por ejemplo, el cdigo
double r ai z_2 = sqr t ( 2) ;
pr i nt f ( " A. Rai z de dos val e %l f \ n" , r ai z_2) ;
pr i nt f ( " B. Rai z de dos val e %12. 1l f \ n" , r ai z_2) ;
pr i nt f ( " C. Rai z de dos val e %12. 3l f \ n" , r ai z_2) ;
pr i nt f ( " D. Rai z de dos val e %12. 5l f \ n" , r ai z_2) ;
pr i nt f ( " E. Rai z de dos val e %12. 7l f \ n" , r ai z_2) ;
pr i nt f ( " F. Rai z de dos val e %12. 9l f \ n" , r ai z_2) ;
pr i nt f ( " G. Rai z de dos val e %12. 11l f \ n" , r ai z_2) ;
pr i nt f ( " H. Rai z de dos val e %5. 7l f \ n" , r ai z_2) ;
pr i nt f ( " I . Rai z de dos val e %012. 4l f \ n" , r ai z_2) ;
que ofrece la siguiente salida por pantalla:
A. Rai z de dos val e 1. 414214
B. Rai z de dos val e 1. 4
C. Rai z de dos val e 1. 414
D. Rai z de dos val e 1. 41421
E. Rai z de dos val e 1. 4142136
F. Rai z de dos val e 1. 414213562
G. Rai z de dos val e 1. 41421356237
H. Rai z de dos val e 1. 4142136
I . Rai z de dos val e 0000001. 4142
La funcin sqrt est declarada en el archivo de cabecera math.h. Esta
funcin devuelve la raz cuadrada (en formato double) del valor
(tambin double) que ha recibido como parmetro de entrada, entre
parntesis.
Por defecto, se toman seis decimales, sin formato alguno. Se ve en el
ejemplo el truncamiento de decimales. En el caso G., la funcin printf
hace caso omiso del ancho de campo pues se exige que muestre un
valor que tiene un carcter para la parte entera, otro para el punto
decimal y once para los decimales: en total 13 caracteres, y no 12 como
seala en ancho de campo. y es que el punto decimal es un carcter
ms dentro de la impresin por pantalla del valor.
[ flags] : Son caracteres que introducen unas ltimas modificaciones en
el modo en que se presenta el valor. Algunos de sus valores y
significados son:
carcter : el valor queda justificado hacia la izquierda.
Fundamentos de informtica. Programacin en Lenguaje C


74
carcter +: el valor se escribe con signo, sea ste positivo o negativo.
En ausencia de esta bandera, la funcin printf imprime el signo
nicamente si es negativo.
carcter en blanco: Si el valor numrico es positivo, deja un espacio en
blanco. Si es negativo imprime el signo.
Existen otras muchas funciones que muestran informacin por pantalla.
Muchas de ellas estn definidas en el archivo de cabecera stdio.h. Con
la ayuda a mano, es sencillo aprender a utilizar muchas de ellas.

Entrada de datos. La funcin scanf().
La funcin scanf de nuevo la encontramos declarada en el archivo de
cabecera stdio.h. Permite la entrada de datos desde el teclado. La
ejecucin del programa queda suspendida en espera de que el usuario
introduzca un valor y pulse la tecla de validacin (intro).
La ayuda de cualquier editor y compilador de C es suficiente para lograr
hacer un buen uso de ella. Presentamos aqu unas nociones bsicas,
suficientes para su uso ms habitual. Para la entrada de datos, al igual
que ocurra con la salida, hay otras funciones vlidas que tambin
pueden conocerse a travs de las ayudas de los distintos editores y
compiladores de C.
El prototipo de la funcin es:
int scanf( const char * cadena_ control[ ,direcciones,] ) ;
La funcin scanf puede leer del teclado tantas entradas como se le
indiquen. De todas formas, se recomienda usar una funcin scanf para
cada entrada distinta que se requiera.
El valor que devuelve la funcin es el del nmero de entradas diferentes
que ha recibido. Si la funcin ha sufrido algn error, entonces devuelve
un valor que significa error (por ejemplo, un valor negativo).
Captulo 3. Funciones de entrada y salida por consola.


75
En la cadena de control se indica el tipo de dato del valor que se espera
recibir por teclado. No hay que escribir texto alguno en la cadena de
control de la funcin scanf: nicamente el especificador de formato.
El formato de este especificador es similar al presentado en la funcin
printf: un carcter % seguido de una o dos letras que indican el tipo de
dato que se espera. Luego, a continuacin de la cadena de control, y
despus de una coma, se debe indicar dnde se debe almacenar ese
valor: la posicin de una variable que debe ser del mismo tipo que el
indicado en el especificador. El comportamiento de la funcin scanf es
imprevisible cuando no coinciden el tipo sealado en el especificador y el
tipo de la variable; en ese caso, habitualmente aborta la ejecucin del
programa.
Las letras que indican el tipo de dato a recibir se recogen en la tabla 3.3.
Los modificadores de tipo de dato son los mismos que para la funcin
printf.

%d Entero con signo, en base decimal.
%i Entero con signo, en base decimal.
%o Entero codificado en base octal.
%u Entero sin signo, en base decimal.
%x Entero codificado en base hexadecimal, usando
letras minsculas. Codificacin interna de los
enteros.

%X Entero codificado en base hexadecimal, usando
letras maysculas. Codificacin interna de los
enteros.

%f Nmero real con signo.
%e Nmero real con signo en formato cientfico,
con el exponente e en minscula.

%c Un carcter. El carcter cuyo ASCII corresponda
con el valor a imprimir.

%s Cadena de caracteres.
%p Direccin de memoria.
%n No lo explicamos aqu ahora.

Tabla 3.3.: Especificadores de tipo de dato en la
funcin scanf.


Fundamentos de informtica. Programacin en Lenguaje C


76
La cadena de control tiene otras especificaciones, pero no las vamos a
ver aqu. Se pueden obtener el la ayuda del compilador.
Adems de la cadena de control, la funcin scanf requiere de otro
parmetro: el lugar dnde se debe almacenar el valor introducido. La
funcin scanf espera, como segundo parmetro, el lugar donde
se aloja la variable, no el nombre. Espera la direccin de la variable.
As est indicado en su prototipo.
Para poder saber la direccin de una variable, C dispone de un operador
unario: &. El operador direccin, prefijo a una variable, devuelve la
direccin de memoria de esta variable. El olvido de este operador en la
funcin scanf es frecuente en programadores noveles. Y de
consecuencias desastrosas: siempre ocurre que el dato introducido no
se almacena en la variable que desebamos, alguna vez producir
alteraciones de otros valores y las ms de las veces llevar a la
inestabilidad del sistema y se deber finalizar la ejecucin del programa.

Recapitulacin.
Hemos presentado el uso de las funciones printf() y scanf(), ambas
declaradas en el archivo de cabecera stdio.h. Cuando queramos hacer
uno de una de las dos funciones, o de ambas, deberemos indicarle al
programa con la directiva de preprocesador #include <stdio.h>.
El uso de ambas funciones se aprende en su uso habitual. Los ejercicios
del captulo anterior pueden ayudar, ahora que ya las hemos
presentado, a practicar con ellas.




Captulo 3. Funciones de entrada y salida por consola.


77
Ejercicios.

16. Escribir un programa que muestre al cdigo ASCI I de un
carcter introducido por teclado.

#i ncl ude <st di o. h>

void mai n( void)
{
unsigned char ch;

pr i nt f ( " I nt r oduzca un car ct er por t ecl ado . . . " ) ;
scanf ( " %c" , &ch) ;

pr i nt f ( " El car ct er i nt r oduci do ha si do %c\ n" , ch) ;
pr i nt f ( " Su cdi go ASCI I es el %hd" , ch) ;
}
Primero mostramos el carcter introducido con el especificador de tipo
%c: as muestra el carcter por pantalla. Y luego mostramos el mismo
valor de la variable ch con el especificador %hd, es decir, como entero
corto, y entonces nos muestra el valor numrico de ese carcter.

17. Lea el programa siguiente, e intente explicar la salida que
ofrece por pantalla.

#i ncl ude <st di o. h>

void mai n( void)
{
signed long int sl i ;
signed short int ssi ;

pr i nt f ( " I nt r oduzca un val or negat i vo par a sl i . . . " ) ;
scanf ( " %l d" , &sl i ) ;

pr i nt f ( " I nt r oduzca un val or negat i vo par a ssi . . . " ) ;
scanf ( " %hd" , &ssi ) ;
Fundamentos de informtica. Programacin en Lenguaje C


78

pr i nt f ( " El val or sl i es %l d\ n" , sl i ) ;
pr i nt f ( " El val or ssi es %l d\ n\ n" , ssi ) ;

pr i nt f ( " El val or sl i como \ " %%l X\ " es %l X\ n" , sl i ) ;
pr i nt f ( " El val or ssi como \ " %%hX\ " es %hX\ n\ n" , ssi ) ;

pr i nt f ( " El val or sl i como \ " %%l u\ " es %l u\ n" , sl i ) ;
pr i nt f ( " El val or ssi como \ " %%hu\ " es %hu\ n\ n" , ssi ) ;

pr i nt f ( " El val or sl i como \ " %%hu\ " es %hu\ n" , sl i ) ;
pr i nt f ( " El val or ssi como \ " %%l u\ " es %l u\ n\ n" , ssi ) ;

pr i nt f ( " El val or sl i como \ " %%hi \ " es %hi \ n" , sl i ) ;
pr i nt f ( " El val or ssi como \ " %%l i \ " es %l i \ n\ n" , ssi ) ;
}
La salida que ha obtenido su ejecucin es la siguiente:
I nt r oduzca un val or negat i vo par a sl i . . . - 23564715
I nt r oduzca un val or negat i vo par a ssi . . . - 8942

El val or sl i es - 23564715
El val or ssi es - 8942

El val or sl i como " %l X" es FE986E55
El val or ssi como " %hX" es DD12

El val or sl i como " %l u" es 4271402581
El val or ssi como " %hu" es 56594

El val or sl i como " %hu" es 28245
El val or ssi como " %l u" es 4294958354

El val or sl i como " %hi " es 28245
El val or ssi como " %l i " es - 8942
Las dos primeras lneas no requieren explicacin alguna: recogen las
entradas que se han introducido por teclado cuando se han ejecutado
las instrucciones de la funcin scanf(). Las dos siguientes tampoco:
muestran por pantalla lo valores introducidos: el primero (sli) es long
int, y se muestra con el especificador de formato %ld %li; el segundo
(ssi) es short int, y se muestra con el especificador de formato %hd
%hi.
Las siguientes lneas de salida son:
El val or sl i como " %l X" es FE986E55
Captulo 3. Funciones de entrada y salida por consola.


79
El val or ssi como " %hX" es DD12
Que muestran los nmeros tal y como los tiene codificados el
ordenador: al ser enteros con signo, y ser negativos, codifica el bit ms
significativo (el bit 31 en el caso de sli, el bit 15 en el caso de ssi) a uno
porque es el bit del signo; y codifica el resto de los bits (desde el bit 30
al bit 0 en el caso de sli, desde el bit 14 hasta el bit 0 en el caso de ssi)
como el complemento a la base del valor absoluto del nmero
codificado.
El nmero =
10 16
(8942) (22EE) se codifica, como nmero negativo (dgito
15 a 1 y resto el valor en su complemento a la base), de la forma
DD12 . Y el nmero =
10 16
(23564715) (167 91AB) se codifica, como
nmero negativo, de la forma FE98 6E55 .
Las dos siguientes lneas son:
El val or sl i como " %l u" es 4271402581
El val or ssi como " %hu" es 56594
Muestra el contenido de la variable lsi que considera ahora como entero
largo sin signo. Y por tanto toma esos 32 bits, que ya no los considera
como un bit de signo y 31 de complemento a la base del nmero
negativo, sino 32 bits de valor positivo codificado en binario:
=
16 10
(FE98 6E55) (4.271.402.581) .
Y muestra el contenido de la variable ssi que considera ahora como
entero corto sin signo. Y por tanto toma esos 16 bits, que ya no los
considera como un bit de signo y 15 de complemento a la base del
nmero negativo, sino 16 bits de valor positivo codificado en binario:
=
16 10
(DD12) (56.594) .
Las dos siguientes lneas son:
El val or sl i como " %hu" es 28245
El val or ssi como " %l u" es 4294958354
La primera de ellas considera la variable sli como una variable corta de
16 bits. Por tanto lo que hace es tomar los 16 bits menos significativos
Fundamentos de informtica. Programacin en Lenguaje C


80
de la variable de 32 bits y los interpreta como entero corto sin signo:
=
16 10
(6E55) (28.245) .
La segunda lnea es de difcil interpretacin: en realidad muestra un
valor numrico que, expresado en base hexadecimal, es igual a
16
(FFFF DD12) : ha aadido, delante de la variable de 16 bits, otros 16
bits que ha encontrado, casualmente, codificados en todos los bits a
uno.
El ltimo bloque es fcilmente interpretable una vez explicadas las dos
lneas anteriores. Se deja al lector esa interpretacin.

18. Escriba el siguiente programa y compruebe cmo es la salida
que ofrece por pantalla.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>
void mai n( void)
{
double a = M_PI ;

pr i nt f ( " 1. El val or de Pi es . . . %20. 1l f \ n" , a) ;
pr i nt f ( " 2. El val or de Pi es . . . %20. 2l f \ n" , a) ;
pr i nt f ( " 3. El val or de Pi es . . . %20. 3l f \ n" , a) ;
pr i nt f ( " 4. El val or de Pi es . . . %20. 4l f \ n" , a) ;
pr i nt f ( " 5. El val or de Pi es . . . %20. 5l f \ n" , a) ;
pr i nt f ( " 6. El val or de Pi es . . . %20. 6l f \ n" , a) ;
pr i nt f ( " 7. El val or de Pi es . . . %20. 7l f \ n" , a) ;
pr i nt f ( " 8. El val or de Pi es . . . %20. 8l f \ n" , a) ;
pr i nt f ( " 9. El val or de Pi es . . . %20. 9l f \ n" , a) ;
pr i nt f ( " 10. El val or de Pi es . . . %20. 10l f \ n" , a) ;
pr i nt f ( " 11. El val or de Pi es . . . %20. 11l f \ n" , a) ;
pr i nt f ( " 12. El val or de Pi es . . . %20. 12l f \ n" , a) ;
pr i nt f ( " 13. El val or de Pi es . . . %20. 13l f \ n" , a) ;
pr i nt f ( " 14. El val or de Pi es . . . %20. 14l f \ n" , a) ;
pr i nt f ( " 15. El val or de Pi es . . . %20. 15l f \ n" , a) ;
}
La salida que ofrece por pantalla es la siguiente:
1. El val or de Pi es . . . 3. 1
2. El val or de Pi es . . . 3. 14
Captulo 3. Funciones de entrada y salida por consola.


81
3. El val or de Pi es . . . 3. 142
4. El val or de Pi es . . . 3. 1416
5. El val or de Pi es . . . 3. 14159
6. El val or de Pi es . . . 3. 141593
7. El val or de Pi es . . . 3. 1415927
8. El val or de Pi es . . . 3. 14159265
9. El val or de Pi es . . . 3. 141592654
10. El val or de Pi es . . . 3. 1415926536
11. El val or de Pi es . . . 3. 14159265359
12. El val or de Pi es . . . 3. 141592653590
13. El val or de Pi es . . . 3. 1415926535898
14. El val or de Pi es . . . 3. 14159265358979
15. El val or de Pi es . . . 3. 141592653589793
Donde hemos cambiado los espacios en blanco por puntos en la parte de
la impresin de los nmeros. Y donde el archivo de biblioteca math.h
contiene el valor del nmero pi, en la constante o identificador M_ PI .
Efectivamente, emplea 20 espacios de carcter de pantalla para mostrar
cada uno de los nmeros. Y cambia la posicin de la coma decimal, pues
cada lnea exigimos a la funcin printf() que muestre un decimal ms
que en la lnea anterior.

19. Escriba un programa que genere 20 nmeros de coma flotante
de forma aleatoria en un rango de valores entre 0 y 1 milln, y
los muestre, uno debaj o de otro, alineados por la coma
decimal.

#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>

void mai n( void)
{
r andomi ze( ) ;

for( int i = 1 ; i <= 20 ; i ++)
pr i nt f ( " %2d. %10. 5l f \ n" , i ,
( double) r andom( 1000000) / r andom( 10000) ) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


82
La funcin random(n) genera un entero aleatorio entre 0 y n 1. La
funcin randomize() debe invocarse siempre en aquellos programas que
luego se vaya a usar el generador de aleatorios y sirve para inicializar el
generador random(). Ambas funciones vienen declaradas en el archivo
de cabecera stdlib.h.
An no hemos llegado al captulo de las estructuras de control, pero
podemos entender por ahora, viendo el cdigo, que este programa
realice veinte veces la operacin de generar dos enteros, dividirlos en
cociente de coma flotante (por eso, previo al cociente, convertimos el
dividendo en double) y mostramos los distintos resultados, uno debajo
del otro, con el especificador de tipo de dato %10.5lf. As, si los
nmeros generados son menores que 100.000 quedarn en lnea, como
se ve en la salida que se muestra a continuacin:
1. 57. 21568
2. 62. 41973
3. 147. 16501
4. 120. 04998
5. 215. 02813
6. 52. 82802
7. 260. 75406
8. 721. 83456
9. 9. 85598
10. 150. 42073
11. 7. 11266
12. 78. 85494
13. 73. 41685
14. 196. 21048
15. 88. 43795
16. 192. 40674
17. 315. 92087
18. 50. 11689
19. 7. 16849
20. 187. 26519
Donde la coma decimal queda ubicada, en todos los nmeros, en la
misma columna. Y as tambin las unidades, decenas, y decimales.



Captulo 3. Funciones de entrada y salida por consola.


83
ANEXO: Ficha resumen de la funcin printf
printf <stdio.h>
int printf ( formato [ , argumento , ...] ) ;
Imprime los datos formateados a la salida estndar segn
especifica el argumento formato.
formato
Es una cadena de texto que contiene el texto que se va a imprimir.
Opcionalmente puede contener etiquetas que se caracterizan por ir
precedidas del carcter %. Estas etiquetas sern luego sustituidas
cuando se ejecute la funcin por un valor especificado en los
argumentos.
El nmero de etiquetas de formato debe ser el mismo que el de los
argumentos que se indiquen. Cada etiqueta indica la ubicacin donde se
debe insertar un valor en la cadena de texto, y el formato en que se
debe imprimir ese valor. Por cada etiqueta debe haber un argumento.
El formato de etiquetas sigue el siguiente prototipo:
% [ flags] [ ancho] [ .precisin] [ modificadores] tipo
donde tipo es imprescindible.
tipo Salida Ejemplo
c Carcter a
d o i Decimal entero con signo 392
u Decimal entero sin signo 7235
o Octal con signo 610
x Entero hexadecimal sin signo 7fa
X Entero hexadecimal sin signo (letras
maysculas)
7FA
f Decimal en punto flotante 392.65
e Notacin cientfica (mantisa/exponente) con
el carcter e
3.9265e2
E Notacin cientfica (mantisa/exponente) con
el carcter E
3.9265E2
g Usa el que sea ms corto entre %e y %f 392.65
G Usa el que sea ms corto entre %E y %f 392.65
s Cadena de caracteres sample
p Direccin apuntada por el argumento B800:0000
El resto de parmetros, esto es flags, ancho, .precisin y modificadores
son opcionales y siguen el siguiente formato:
Fundamentos de informtica. Programacin en Lenguaje C


84
modificador significado
h argumento se interpreta como un short int.
l argumento se interpreta como un long int cuando
precede a un entero (d, i, o, u, x, X) o double si
precede a un tipo de dato de coma flotante (f, g, G, e,
E).
L argumento se interpreta como un long double si
precede a un tipo de dato de coma flotante (f, g, G, e,
E).
ancho significado
num Especifica el nmero mnimo de caracteres que se
imprimen. Si el valor que se imprime es menor que este
num entonces el resultado es completado con ceros. El
valor nunca es truncado incluso si es mayor.
* El ancho no se especifica en el formato de la cadena
sino que se indica en el valor entero que precede al
argumento que tiene que formatearse.
.precisin significado
.num Para los tipos f, e, E, g, G: indica el nmero de dgitos
que se imprimen despus del punto decimal (por defecto
es 6).
Para el tipo s: indica el nmero mximo de caracteres
que se imprimen. (por defecto imprime hasta encontrar
el primer carcter null).
flags significado
- Alineacin a la izquierda con el ancho de campo dado
(por defecto alinea a la derecha). Este flag slo tiene
sentido cuando se especifica el ancho de campo.
+ Obliga a anteponer un signo al resultado (+ o -) si el
tipo es con signo. (por defecto slo el signo menos - se
imprime).
blanco Si el argumento es un valor positivo con signo, se
inserta un blanco antes del nmero.
0 Coloca tantos ceros a la izquierda del nmero como
sean necesarios para completar el ancho de campo
especificado.
# Usado con los tipos o, x o X el valor es precedido con 0,
0x o 0X respectivamente si no son cero.
Usado con e, E o f obliga a que el valor de salida
contenga el punto decimal incluso aunque slo sigan
ceros.
Usado con g o G el resultado es el mismo que con e o E
pero los ceros sobrantes no se eliminan.
Captulo 3. Funciones de entrada y salida por consola.


85
argumento(s)
Parmetro(s) opcional(es) que contiene(n) los datos que se insertarn
en el lugar de los % etiquetas especificados en los parmetros del
formato. Debe haber el mismo nmero de parmetros que de etiquetas
de formato.
Valor de retorno de printf.
Si tiene xito representa el nmero total de caracteres impresos. Si hay
un error, se devuelve un nmero negativo.
Ejemplo.
/* printf: algunos ejemplos de formato*/
#i ncl ude <st di o. h>

void mai n( void)
{
pr i nt f ( " Car act er es: %c %c \ n" , ' a' , 65) ;
pr i nt f ( " Deci mal es: %d %l d\ n" , 1977, 650000) ;
pr i nt f ( " Pr ecedi dos de bl ancos: %10d \ n" , 1977) ;
pr i nt f ( " Pr ecedi dos de cer os: %010d \ n" , 1977) ;
pr i nt f ( " For mat o: %d %x %o %#x %#o\ n" , 100, 100, 100, 100, 100) ;
pr i nt f ( " f l oat : %4. 2f %+. 0e %E\ n" , 3. 1416, 3. 1416, 3. 1416) ;
pr i nt f ( " Ancho: %*d \ n" , 5, 10) ;
pr i nt f ( " %s \ n" , " Mi mam me mi ma" ) ;
}
Y la salida:
Car act er es: a A
Deci mal es: 1977 650000
Pr ecedi dos con bl ancos: 1977
Pr ecedi dos con cer os: 0000001977
For mat o: 100 64 144 0x64 0144
f l oat : 3. 14 +3e+000 3. 141600E+000
Ancho: 10
Mi mam me mi ma
Fundamentos de informtica. Programacin en Lenguaje C


86








CAPTULO 4

ESTRUCTURAS DE CONTROL

El lenguaje C pertenece a la familia de lenguajes del paradigma de la
programacin estructurada. En este captulo quedan recogidas las reglas
de la programacin estructurada y, en concreto, las reglas sintcticas
que se exige en el uso del lenguaje C para el diseo de esas estructuras.
El objetivo del captulo es aprender a crear estructuras
condicionales y estructuras de iteracin. Tambin veremos una
estructura especial que permite seleccionar un camino de ejecucin de
entre varios establecidos. Y veremos las sentencias de salto que nos
permiten abandonar el bloque de sentencias iteradas por una estructura
de control.

Introduccin.
Las reglas de la programacin estructurada son:
1. Todo programa consiste en una serie de acciones o sentencias que
Fundamentos de informtica. Programacin en Lenguaje C


88
se ejecutan en secuencia, una detrs de otra.
2. Cualquier accin puede ser sustituida por dos o ms acciones en
secuencia. Esta regla se conoce como la de apilamiento.
3. Cualquier accin puede ser sustituida por cualquier estructura de
control; y slo se consideran tres estructuras de control: la
secuencia, la seleccin y la repeticin. Esta regla se conoce
como regla de anidamiento. Todas las estructuras de control de la
programacin estructurada tienen un solo punto de entrada y un solo
punto de salida.
4. Las reglas de apilamiento y de anidamiento pueden aplicarse tantas
veces como se desee y en cualquier orden.
Ya hemos visto cmo se crea una sentencia: con un punto y coma
precedido de una expresin que puede ser una asignacin, la llamada a
una funcin, una declaracin de una variable, etc. O, si la sentencia es
compuesta, agrupando entonces varias sentencias simples en un bloque
encerrado por llaves.
Los programas discurren, de instruccin a instruccin, una detrs de
otra, en una ordenacin secuencial y nunca dos al mismo tiempo, como
queda representado en la figura 4.1.
Pero un lenguaje de programacin no slo ha de poder ejecutar las
instrucciones en orden secuencial: es necesaria la capacidad para
modificar ese orden de ejecucin. Para ello estn las estructuras de
control. Al acabar este captulo, una vez conocidas las estructuras de
control, las posibilidades de resolver diferentes problemas mediante el
lenguaje de programacin C se habrn multiplicado enormemente.
Instruccin 1 Instruccin 2 Instruccin N
Figura 4.1.: Esquema de ordenacin secuencial de sentencias.
Captulo 4. Estructuras de control.


89
A lo largo del captulo iremos viendo abundantes ejemplos. Es
conveniente pararse en cada uno: comprender el cdigo que se propone
en el manual, o lograr resolver aquellos que se dejan propuestos. En
algunos casos ofreceremos el cdigo en C; en otros dejaremos apuntado
el modo de resolver el problema ofreciendo el pseudocdigo del
algoritmo o el flujograma. Muchos de los ejemplos que aqu se van a
resolver ya tienen planteado el flujograma o el pseudocdigo en el libro
Fundamentos de Informtica. Codificacin y Algoritmia.

Conceptos previos.
La regla 3 de la programacin estructurada habla de tres estructuras de
control: la secuencia, la seleccin y la repeticin. Nada nuevo hay ahora
que decir sobre la secuencia, que vendra esquematizada en la figura
4.1. En la figura 4.2. se esquematizan diferentes posibles estructuras de
seleccin; y en la figura 4.3. las dos estructuras bsicas de repeticin.
Las dos formas que rompen el orden secuencial de ejecucin de
sentencias son:
1. Instruccin condicional: Se evala una condicin y si se cumple
se transfiere el control a una nueva direccin indicada por la
instruccin.
2. Instruccin incondicional. Se realiza la transferencia a una nueva
direccin sin evaluar ninguna condicin (por ejemplo, llamada a una
funcin).
En ambos casos la transferencia del control se puede realizar con o sin
retorno: en el caso de que exista retorno, despus de ejecutar el bloque
de instrucciones de la nueva direccin se retorna a la direccin que
sucede a la que ha realizado el cambio de flujo.
Fundamentos de informtica. Programacin en Lenguaje C


90
Las estructuras de control que se van a ver en este captulo son
aquellas con trasfieren el control a una nueva direccin, de
acuerdo a una condicin evaluada.

Estructuras de control condicionales.
Las estructuras de control condicionales que se van a ver son la
bifurcacin abierta y la bifurcacin cerrada. Un esquema del flujo de
ambas estructuras ha quedado recogido en la Figura 4.2.
La bifurcacin abierta. La sentencia if.
La sentencia que est precedida por la estructura de control
condicionada se ejecutar si la condicin de la estructura de control es
verdadera; en caso contrario no se ejecuta la instruccin condicionada y
Condicin
Instruccin Instruccin
Instruccin
Condicin
Instruccin
Instruccin
Bifurcacin abierta Bifurcacin cerrada
Figura 4.2.: Estructuras de seleccin.
S S No No
Condicin
Instruccin
Instruccin
S No
Figura 4.3.: Estructuras de repeticin.
Condicin
Instruccin Instruccin
S No
Estructura while Estructura do - while
Captulo 4. Estructuras de control.


91
continua el programa con la siguiente instruccin. En la figura 4.2. se
puede ver un esquema del comportamiento de la bifurcacin abierta.
La sintaxis de la estructura de control condicionada abierta es la
siguiente:
if( condicin) sentencia;
Si la condicin es verdadera (distinto de cero en el lenguaje C), se
ejecuta la sentencia. Si la condicin es falsa (igual a cero en el lenguaje
C), no se ejecuta la sentencia.
Si en lugar de una sentencia, se desean condicionar varias de ellas,
entonces se crea una sentencia compuesta mediante llaves.
Ejemplo:
Programa que solicite dos valores enteros y muestre el cociente:
#i ncl ude <st di o. h>
void mai n( void)
{
short D, d;
pr i nt f ( " Pr ogr ama par a di vi di r dos ent er os. . . \ n" ) ;
pr i nt f ( " I nt r oduzca el di vi dendo . . . " ) ;
scanf ( " %hd" , &D) ;
pr i nt f ( " I nt r oduzca el di vi sor . . . " ) ;
scanf ( " %hd" , &d) ;
if( d ! = 0) pr i nt f ( " %hu / %hu = %hu" , D, d, D / d) ;
}
Se efectuar la divisin nicamente en el caso en que se verifique la
condicin de que d != 0.
La bifurcacin cerrada. La sentencia if else.
En una bifurcacin cerrada, la sentencia que est precedida por una
estructura de control condicionada se ejecutar si la condicin de la
estructura de control es verdadera; en caso contrario se ejecuta una
instruccin alternativa. Despus de la ejecucin de una de las dos
sentencias (nunca las dos), el programa contina la normal ejecucin de
las restantes sentencias que vengan a continuacin.
Fundamentos de informtica. Programacin en Lenguaje C


92
La sintaxis de la estructura de control condicionada cerrada es la
siguiente:
if( condicin) sentencia1;
else sentencia2;
Si la condicin es verdadera (distinto de cero en el lenguaje C), se
ejecuta la sentencia llamada sentencia1. Si la condicin es falsa (igual a
cero en el lenguaje C), se ejecuta la sentencia llamada sentencia2.
Si en lugar de una sentencia, se desean condicionar varias de ellas,
entonces se crea una sentencia compuesta mediante llaves.
Ejemplo:
El mismo programa anteriormente visto. Quedar mejor si se escribe de
la siguiente forma:
#i ncl ude <st di o. h>
void mai n( void)
{
short D, d;
pr i nt f ( " Pr ogr ama par a di vi di r dos ent er os. . . \ n" ) ;
pr i nt f ( " I nt r oduzca el di vi dendo . . . " ) ;
scanf ( " %hd" , &D) ;
pr i nt f ( " I nt r oduzca el di vi sor . . . " ) ;
scanf ( " %hd" , &d) ;
if( d ! = 0) pr i nt f ( " %hu / %hu = %hu" , D, d, D / d) ;
else pr i nt f ( No se puede r eal i zar di vi si on por cer o) ;
}
Se efectuar la divisin nicamente en el caso en que se verifique la
condicin de que d != 0. Si el divisor introducido es igual a cero,
entonces imprime en pantalla un mensaje de advertencia.
Anidamiento de estructuras condicionales.
Decimos que se produce anidamiento de estructuras de control cuando
una estructura de control aparece dentro de otra estructura de control
del mismo tipo.
Tanto en la parte if como en la parte else, los anidamientos pueden
llegar a cualquier nivel. De esa forma podemos elegir entre numerosas
sentencias estableciendo las condiciones necesarias.
Captulo 4. Estructuras de control.


93
Una estructura de anidamiento tiene, por ejemplo, la forma:

if( expr esi n_1) / * pr i mer i f */
{
if( expr esi n_2) / * segundo i f */
{
if( expr esi n_3) / * t er cer i f */
sent enci a_1;
else / * al t er nat i va al t er cer i f */
sent enci a_2;
}
else / * al t er nat i va al 2 i f */
sent enci a_3;
}
else / * al t er nat i va al pr i mer i f */
sent enci a_4;
(se puede ver el organigrama de este cdigo en la figura 4.4.)
Cada else se asocia al if ms prximo en el bloque en el que se
encuentre y que no tenga asociado un else. No est permitido (no
tendra sentido) utilizar un else sin un if previo. Y la estructura else
debe ir inmediatamente despus de la sentencia condicionada con su if.
Un ejemplo de estructura anidada sera, siguiendo con los ejemplos
anteriores, el caso de que, si el divisor introducido ha sido el cero, el
programa preguntase si se desea introducir un divisor distinto.
C1
C2
C3
S4 S3 S1 S2
F
C
Figura 4.4.: Ejemplo de
estructuras condicionales anidadas.
No S
No S
No S
Fundamentos de informtica. Programacin en Lenguaje C


94
#i ncl ude <st di o. h>
void mai n( void)
{
short D, d;
char opci on;
pr i nt f ( " Pr ogr ama par a di vi di r dos ent er os. . . \ n" ) ;
pr i nt f ( " I nt r oduzca el di vi dendo . . . " ) ;
scanf ( " %hd" , &D) ;
pr i nt f ( " I nt r oduzca el di vi sor . . . " ) ;
scanf ( " %hd" , &d) ;
if( d ! = 0)
pr i nt f ( " %hu / %hu = %hu" , D, d, D / d) ;
else
{
pr i nt f ( " No se puede di vi di r por cer o. \ n" ) ;
pr i nt f ( " I nt r oduci r ot r o denomi nador ( s/ n) ?" ) ;
opci on = get char ( ) ;
if( opci on == ' s' )
{
pr i nt f ( " \ nNuevo denomi nador . . . " ) ;
scanf ( " %hd" , &d) ;
if( d ! = 0)
pr i nt f ( " %hu / %hu = %hu" , D, d, D/ d) ;
else
pr i nt f ( " De nuevo ha i nt r oduci do 0. " ) ;
}
}
}
La funcin getchar() est definida en la biblioteca stdio.h. Esta funcin
espera a que el usuario pulse una tecla del teclado y, una vez pulsada,
devuelve el cdigo ASCII de la tecla pulsada.
En este ejemplo hemos llegado hasta un tercer nivel de anidacin.
Escala if - else if
Cuando se debe elegir entre una lista de opciones, y nicamente una de
ellas ha de ser vlida, se llega a producir una concatenacin de
condiciones de la siguiente forma:
if(condicin1) setencia1;
else
{
if(condicin2) sentencia2;
else
{
if(condicin3) sentencia3;
Captulo 4. Estructuras de control.


95
else sentencia4;
}
}
El flujograma recogido en la Figura 4.4. representara esta situacin sin
ms que intercambiar los caminos de verificacin de las condiciones C1,
C2 y C3 recogidas en l (es decir, intercambiando los rtulos de S y
de No).
Este tipo de anidamiento se resuelve en C con la estructura else if, que
permite una concatenacin de las condicionales. Un cdigo como el
antes escrito quedara:
if(condicin1) sentencia1;
else if (condicin2) sentencia2;
else if(condicin3) sentencia3;
else sentencia4;
Como se ve, una estructura as anidada se escribe con mayor facilidad y
expresa tambin ms claramente las distintas alternativas. No es
necesario que, en un anidamiento de sentencias condicionales,
encontremos un else final: el ltimo if puede ser una bifurcacin
abierta:
if(condicin1) sentencia1;
else if (condicin2) sentencia2;
else if(condicin3) sentencia3;
Un ejemplo de concatenacin podra ser el siguiente programa, que
solicita al usuario la nota de un examen y muestra por pantalla la
calificacin acadmica obtenida:
#i ncl ude <st di o. h>
void mai n( void)
{
float not a;
pr i nt f ( " I nt r oduzca l a not a del examen . . . " ) ;
scanf ( " %f " , &not a) ;
if( not a < 0 | | not a > 10) pr i nt f ( " Not a i ncor r ect a. " ) ;
else if( not a < 5) pr i nt f ( " Suspenso. " ) ;
else if( not a < 7) pr i nt f ( " Apr obado. " ) ;
else if( not a < 9) pr i nt f ( " Not abl e. " ) ;
else if( not a < 10) pr i nt f ( " Sobr esal i ent e. " ) ;
else pr i nt f ( " Mat r cul a de honor . " ) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


96
nicamente se evaluar un else if cuando no haya sido cierta la
condicin de ninguno de los anteriores ni del if inicial. Si todas las
condiciones resultan ser falsas, entonces se ejecutar (si existe) el
ltimo else.
La estructura condicional y el operador condicional
Existe un operador que selecciona entre dos opciones, y que realiza, de
forma muy sencilla y bajo ciertas limitaciones la misma operacin que la
estructura de bifurcacin cerrada. Es el operador interrogante, dos
puntos (?:).
La sintaxis del operador es la siguiente:
expresin_ 1 ? expresin_ 2 : expresin3;
Se evala expresin_1; si resulta ser verdadera, entonces se ejecutar
la sentencia recogida en expresin_2; y si es falsa, entonces se
ejecutar la sentencia recogida en expresin_3. Tanto expresin_2
como expresin_3 pueden ser funciones, o expresiones muy complejas,
pero siempre deben ser sentencias simples.
Es conveniente no renunciar a conocer algn aspecto de la sintaxis de
un lenguaje de programacin. Es cierto que el operador interrogante
dos puntos se puede siempre sustituir por la estructura de control
condicional if else. Pero el operador puede, en muchos casos,
simplificar el cdigo o hacerlo ms elegante. Y hay que tener en cuenta
que el resto de los programadores s hacen uso del operador y el cdigo
en C est lleno de ejemplos de su uso.
Por ejemplo, el cdigo:
if(x >= 0)
printf(Positivo\n);
else
printf(Negativo\n);
es equivalente a: printf(%s\n, x >= 0 ? Positivo: Negativo);

Captulo 4. Estructuras de control.


97
Estructura de seleccin mltiple: Sentencia switch.
La sentencia switch permite transferir el control de ejecucin del
programa a un punto de entrada etiquetado en un bloque de sentencias.
La decisin sobre a qu instruccin del bloque se trasfiere la ejecucin
se realiza mediante una expresin entera.
La forma general de la estructura switch es:
switch(variable_del_switch)
{
case expresionConstante1:
[sentencias;]
[break;]
case expresionConstante2:
[sentencias;]
[break;]
[]
case expresionConstanteN:
[sentencias;]
[break;]
[default
sentencias;]
}
El cuerpo de la sentencia switch se conoce como bloque switch y
permite tener sentencias prefijadas con las etiquetas case. Una etiqueta
case es una constante entera (variables de tipo char short long,
con o sin signo). Si el valor de la expresin de switch coincide con el
valor de una etiqueta case, el control se transfiere a la primera
sentencia que sigue a la etiqueta. No puede haber dos case con el
mismo valor de constante. Si no se encuentra ninguna etiqueta case
que coincida, el control se transfiere a la primera sentencia que sigue a
la etiqueta default. Si no existe esa etiqueta default, y no existe una
etiqueta coincidente, entonces no se ejecuta ninguna sentencia del
switch y se contina, si la hay, con la siguiente sentencia posterior a la
estructura.
Por ejemplo, para el cdigo que se muestra a continuacin, y cuyo
flujograma queda recogido en la figura 4.5.:
Fundamentos de informtica. Programacin en Lenguaje C


98
switch( a)
{
case 1: pr i nt f ( UNO\ t ) ;
case 2: pr i nt f ( DOS\ t ) ;
case 3: pr i nt f ( TRES\ t ) ;
default: pr i nt f ( NI NGUNO\ n) ;
}
Si el valor de a es, por ejemplo, 2, entonces comienza a ejecutar el
cdigo del bloque a partir de la lnea que da entrada el case 2:.
Producir la siguiente salida por pantalla:
DOS TRES NI NGUNO.
Una vez que el control se ha trasferido a la sentencia que sigue a una
etiqueta concreta, ya se ejecutan todas las dems sentencias del bloque
switch, de acuerdo con la semntica de dichas sentencias. El que
aparezca una nueva etiqueta case no obliga a que se dejen de ejecutar
las sentencias del bloque. Si se desea detener la ejecucin de sentencias
en el bloque switch, debemos transferir explcitamente el control al
exterior del bloque. Y eso se realiza utilizando la sentencia break.
Dentro de un bloque switch, la sentencia break transfiere el control a
la primera sentencia posterior al switch. Ese es el motivo por el que en
la sintaxis de la estructura switch se escriba (en forma opcional) las
sentencias break en las instrucciones inmediatamente anteriores a cada
una de las etiquetas.
= 1 a
C
F
UNO
= 2 a DOS
= 3 a TRES
NINGUNO
S No
S No
S No
Figura 4.5.: Flujograma del
programa ejemplo con switch, sin
sentencias break.
Captulo 4. Estructuras de control.


99
En el ejemplo anterior, si colocamos la sentencia break en cada case,
switch( a)
{
case 1: pr i nt f ( UNO) ;
break;
case 2: pr i nt f ( DOS) ;
break;
case 3: pr i nt f ( TRES) ;
break;
default: pr i nt f ( NI NGUNO) ;
}
Entonces la salida por pantalla, si la variable a tiene el valor 2 ser
nicamente:
DOS
(Puede verse el flujograma de este nuevo cdigo en la figura 4.6.)
La ejecucin de las instrucciones que siguen ms all de la siguiente
etiqueta case puede ser til en algunas circunstancias. Pero lo habitual
ser que aparezca una sentencia break al final del cdigo de cada
etiqueta case.
Una sola sentencia puede tener ms de una etiqueta case. Queda claro
en el siguiente ejemplo:
short int not a;
pr i nt f ( " I nt r oduzca l a not a del examen . . . " ) ;
scanf ( " %hd" , &not a) ;
= 1 a
C
F
UNO
= 2 a DOS
= 3 a TRES
NINGUNO
S No
S No
S No
Figura 4.6.: Flujograma del
programa ejemplo con switch,
con sentencias break.
break;
break;
break;
Fundamentos de informtica. Programacin en Lenguaje C


100
switch( not a)
{
case 1:
case 2:
case 3:
case 4: pr i nt f ( SUSPENSO) ;
break;
case 5:
case 6: pr i nt f ( APROBADO) ;
break;
case 7:
case 8: pr i nt f ( NOTABLE) ;
break;
case 9: pr i nt f ( SOBRESALI ENTE) ;
break;
case 10: pr i nt f ( MATR CULA DE HONOR) ;
break;
default: pr i nt f ( Not a i nt r oduci da er r nea. ) ;
}
No se puede poner una etiqueta case fuera de un bloque switch. Y
tampoco tiene sentido colocar instrucciones dentro del bloque switch
antes de aparecer el primer case: eso supondra un cdigo que jams
podra llegar a ejecutarse. Por eso, la primera sentencia de un bloque
switch debe estar ya etiquetada.
Se pueden anidar distintas estructuras switch.
El ejemplo de las notas, que ya se mostr al ejemplificar una anidacin
de sentencias if else if puede servir para comentar una caracterstica
importante de la estructura switch. Esta estructura no admite, en sus
distintas entradas case, ni expresiones lgicas o relacionales, ni
expresiones aritmticas, sino literales. La nica relacin aceptada es,
pues, la de igualdad. Y adems, el trmino de la igualdad es siempre
entre una variable o una expresin entera (la del switch) y valores
literales: no se puede indicar el nombre de una variable. El programa de
las notas, si la variable nota hubiese sido de tipo float, como de hecho
quedo definida cuando se resolvi el problema con los condicionales if
else if no tiene solucin posible mediante la estructura switch.
Captulo 4. Estructuras de control.


101
Y una ltima observacin: las sentencias de un case no forman un
bloque y no tiene porqu ir entre llaves. La estructura switch entera,
con todos sus cases, s es un bloque.

Un ejercicio planteado.
Planteamos ahora un ejercicio a resolver: solicite del usuario que
introduzca por teclado un da, mes y ao, y muestre entonces por
pantalla el da de la semana que le corresponde.
La resolucin de este problema es sencilla si se sabe el cmo. Sin un
correcto algoritmo que nos permita saber cmo procesar la entrada no
podemos hacer nada.
Por lo tanto, antes de intentar implementar un programa que resuelva
este problema, ser necesario preguntarse si somos capaces de
resolverlo sin programa. Porque si no sabemos hacerlo nosotros, menos
sabremos explicrselo a la mquina.
Buscando en Internet he encontrado lo siguiente: Para saber a qu da
de la semana corresponde una determinada fecha, basta aplicar la
siguiente expresin:
( )
= + + + +

26 2 10 4 4 2 mod7 d M D A A C C
Donde d es el da de la semana ( = 0 d es el domingo; = 1 d es el
lunes,, = 6 d es el sbado); D es el da del mes de la fecha; M es el
mes de la fecha; A es el ao de la fecha; y C es la centuria (es decir,
los dos primero dgitos del ao) de la fecha.
A esos valores hay que introducirle unas pequeas modificaciones: se
considera que el ao comienza en marzo, y que los meses de enero y
febrero son los meses 11 y 12 del ao anterior.
Hagamos un ejemplo a mano: Qu da de la semana fue el 15 de
febrero de 1975?:
Fundamentos de informtica. Programacin en Lenguaje C


102
= 15 D
= 12 M : hemos quedado que en nuestra ecuacin el mes de febrero
es el dcimo segundo mes del ao anterior.
= 74 A : hemos quedado que el mes de febrero corresponde al
ltimo mes del ao anterior.
= 19 C
Con todos estos valores, el da de la semana queda:
( )
= + + + +

26 12 2 10 15 74 74 4 19 4 2 19 mod7 d
que es igual a 6, es decir, sbado.
Slo queda hacer una ltima advertencia a tener en cuenta a la hora de
calcular nuestros valores de A y de C : Si queremos saber el da de la
semana del 1 de febrero de 2000, tendremos que = 12 M , que = 99 A y
que = 19 C : es decir, primero convendr hacer las rectificaciones al ao
y slo despus calcular los valores de A y de C . se da fue
( )
= + + + + =

26 12 2 10 1 99 99 4 19 4 2 19 mod7 2 d
es decir martes!
Queda ahora hacer el programa que nos d la respuesta al da de la
semana en el que estamos. Har falta emplear dos veces la estructura
de control condicional if y una vez el switch. El programa queda como
sigue:
#i ncl ude <st di o. h>

voi d mai n( voi d)
{
unsigned short D, mm, aaaa;
unsigned short M, A, C;

pr i nt f ( " I nt r oduzca l a f echa . . . \ n" ) ;
pr i nt f ( " D a . . . " ) ;
scanf ( " %hu" , &D) ;

pr i nt f ( " Mes . . . " ) ;
scanf ( " %hu" , &mm) ;

Captulo 4. Estructuras de control.


103
pr i nt f ( " Ao . . . " ) ;
scanf ( " %hu" , &aaaa) ;

/ / Val or es de l as var i abl es:

/ / El val or de D ya ha quedado i nt r oduci do por el usuar i o.
/ / Val or de M:

if( mm< 3)
{
M = mm+ 10;
A = ( aaaa - 1) %100;
C = ( aaaa - 1) / 100;
}
else
{
M = mm- 2;
A = aaaa %100;
C = aaaa / 100;
}

pr i nt f ( " El d a %2hu de %2hu de %4hu f ue " , D, mm, aaaa) ;
switch( ( 70+( 26*M- 2) / 10 + D + A + A/ 4 + C/ 4 - C*2 ) %7)
{
case 0: pr i nt f ( " DOMI NGO" ) ; break;
case 1: pr i nt f ( " LUNES" ) ; break;
case 2: pr i nt f ( " MARTES" ) ; break;
case 3: pr i nt f ( " MI RCOLES" ) ; break;
case 4: pr i nt f ( " J UEVES" ) ; break;
case 5: pr i nt f ( " VI ERNES" ) ; break;
case 6: pr i nt f ( " SBADO" ) ;
}
}
Si, por ejemplo, introducimos la fecha 25 de enero de 1956, la salida del
programa tendr el siguiente aspecto:
I nt r oduzca l a f echa . . .
D a . . . 25
Mes . . . 1
Ao . . . 1956
El d a 25 de 1 de 1956 f ue MI RCOLES
Falta aclarar por qu he sumado 70 al valor de la expresin que
calculamos en el switch. Veamos un ejemplo para justificar ese valor:
supongamos la fecha 2 de abril de 2001. Tendremos que = 2 D , = 2 M ,
= 1 A y = 20 C , y entonces el valor de d queda: 27%7 que es igual a
6 . Nuestro algoritmo trabaja con valores entre 0 y 6, y al salir
Fundamentos de informtica. Programacin en Lenguaje C


104
negativo el valor sobre el que se debe calcular el mdulo de 7, el
resultado nos sale fuera de ese rango de valores. Pero la operacin
mdulo establece una relacin de equivalencia entre el conjunto de los
enteros y el conjunto de valores comprendidos entre 0 y el valor del
mdulo menos 1. Le sumamos al valor calculado un mltiplo de 7
suficientemente grande para que sea cual sea el valor de las variables,
al final obtenga un resultado positivo. As, ahora, el valor obtenido ser
= = 70 27%7 43%7 1, es decir, lunes:
I nt r oduzca l a f echa . . .
D a . . . 2
Mes . . . 4
Ao . . . 2001
El d a 2 de 4 de 2001 f ue LUNES

Estructuras de repeticin. Iteracin.
Una estructura de repeticin o de iteracin es aquella que nos permite
repetir un conjunto de sentencias mientras que se cumpla una
determinada condicin.
Las estructuras de iteracin o de control de repeticin, en C, se
implementan con las estructuras do while, while y for. Todas ellas
permiten la anidacin de unas dentro de otras a cualquier nivel. Puede
verse un esquema de su comportamiento en la figura 4.3., en pginas
anteriores.
Estructura while.
La estructura while, tambin llamada condicional, o centinela, se
emplea en aquellos casos en que no se conoce por adelantado el
nmero de veces que se ha de repetir la ejecucin de una determinada
sentencia o bloque: ninguna, una o varias.
La sintaxis de la estructura while es la que sigue:
while( condicin) sentencia;
Captulo 4. Estructuras de control.


105
donde condicin es cualquier expresin vlida en C. Esta expresin se
evala cada vez, antes de la ejecucin de la sentencia iterada (o bloque
de sentencias si se desea iterar una sentencia compuesta). Puede por
tanto no ejecutarse nunca el bloque de sentencias de la estructura de
control. Las sentencias se volvern a ejecutar una y otra vez mientras
condicin siga siendo verdadero. Cuando la condicin resulta ser falsa,
entonces el contador de programa se sita en la inmediata siguiente
instruccin posterior a la sentencia gobernada por la estructura.
Veamos un ejemplo sencillo. Hagamos un programa que solicite un
entero y muestre entonces por pantalla la tabla de multiplicar de ese
nmero. El programa es muy sencillo gracias a las sentencias de
repeticin:
#i ncl ude <st di o. h>
void mai n( void)
{
short int n, i ;
pr i nt f ( " Tabl a de mul t i pl i car del . . . " ) ;
scanf ( " %hd" , &n) ;
i = 0;
while( i <= 10)
{
pr i nt f ( " %3hu * %3hu = %3hu\ n" , i , n, i * n) ;
i ++;
}
}
Despus de solicitar el entero, inicializa a 0 la variable i y entonces,
mientras que esa variable contador sea menor o igual que 10, va
mostrando el producto del entero introducido por el usuario con la
variable contador i. La variable i cambia de valor dentro del bucle de la
estructura, de forma que llega un momento en que la condicin deja de
cumplirse; cundo?: cuando la variable i tiene un valor mayor que 10.
Conviene asegurar que en algn momento va a dejar de cumplirse la
condicin; de lo contrario la ejecucin del programa podra quedarse
atrapada en un bucle infinito. De alguna manera, dentro de la
sentencia gobernada por la estructura de iteracin, hay que modificar
Fundamentos de informtica. Programacin en Lenguaje C


106
alguno de los parmetros que intervienen en la condicin. Ms adelante,
en este captulo, veremos otras formas de salir de la iteracin.
Este ltimo ejemplo ha sido sencillo. Veamos otro ejemplo que requiere
un poco ms de imaginacin. Supongamos que queremos hacer un
programa que solicite al usuario la entrada de un entero y que entonces
muestre por pantalla el factorial de ese nmero. Ya se sabe la definicin
de factorial: = ! ( 1) ( 2) ... 2 1 n n n n .
Antes de mostrar el cdigo de esta sencilla aplicacin, conviene volver a
una idea comentada captulos atrs. Efectivamente, habr que saber
decir en el lenguaje C cmo se realiza esta operacin. Pero previamente
debemos ser capaces de expresar el procedimiento en castellano. En el
captulo 4 de Fundamentos de Informtica. Codificacin y Algoritmia.
se encuentra extensamente documentado este y otros muchos
algoritmos de iteracin que veremos ahora implementados en C, en
estas pginas.
Veamos una posible solucin al programa del factorial:
#i ncl ude <st di o. h>
void mai n( void)
{
unsigned short n;
unsigned long Fact ;
pr i nt f ( " I nt r oduzca el ent er o . . . " ) ;
scanf ( " %hu" , &n) ;
pr i nt f ( " El f act or i al de %hu es . . . " , n) ;
Fact = 1;
while( n ! = 0)
{
Fact = Fact * n;
n = n - 1;
}
pr i nt f ( " %l u. " , Fact ) ;
El valor de la variable Fact se inicializa a uno antes de comenzar a
usarla. Efectivamente es muy importante no emplear esa variable sin
darle el valor inicial que a nosotros nos interesa. La variable n se
inicializa con la funcin scanf.
Captulo 4. Estructuras de control.


107
Mientras que n no sea cero, se ir multiplicando Fact (inicialmente a
uno) con n. En cada iteracin el valor de n se ir decrementando en
uno.
La tabla de los valores que van tomando ambas variables se muestra en
la tabla 4.1. (se supone que la entrada por teclado ha sido el nmero 5).
Cuando la variable n alcanza el valor cero termina la iteracin. En ese
momento se espera que la variable Fact tenga el valor que corresponde
al factorial del valor introducido por el usuario.
La iteracin se ha producido tantas veces como el cardinal del nmero
introducido. Por cierto, que si el usuario hubiera introducido el valor
cero, entonces el bucle no se hubiera ejecutado ni una sola vez, y el
valor de Fact hubiera sido uno, que es efectivamente el valor por
definicin ( = 0! 1).

n 5 4 3 2 1 0
Fact 5 20 60 120 120 120

Tabla 4.1.: Valores que van tomando las variables del
bucle del programa del clculo del factorial si la entrada
del usuario ha sido n = 5.


La estructura de control mostrada admite formas de presentacin ms
compacta y, de hecho, lo habitual ser que as se presente. Por
ejemplo:
while( n ! = 0)
{
Fact *= n;
n = n - 1;
}
donde todo es igual excepto que ahora se ha hecho uso del operador
compuesto en la primera sentencia del bloque. Pero an se puede
compactar ms:
1. La condicin de permanencia ser verdad siempre que n no sea cero.
Y por definicin de verdad en C (algo es verdadero cuando es
Fundamentos de informtica. Programacin en Lenguaje C


108
distinto de cero) se puede decir que la n != 0 es verdadero s y slo
si n es verdadero.
2. Las dos sentencias simples iteradas en el bloque pueden
condensarse en una sola: Fact *= n--;
As las cosas, la estructura queda:
while( n) Fact *= n- - ;
Hacemos un comentario ms sobre la estructura while. Esta estructura
permite iterar una sentencia sin cuerpo. Por ejemplo, supongamos que
queremos hacer un programa que solicite continuamente del usuario
que pulse una tecla, y que esa solicitud no cese hasta que ste
introduzca el carcter, por ejemplo, a. La estructura quedar tan
simple como lo que sigue:
while( ( ch = get char ( ) ) ! = a ) ;
Esta lnea de programa espera una entrada por teclado. Cuando sta se
produzca comprobar que hemos tecleado el carcter a minscula; de
no ser as, volver a esperar otro carcter.
Una forma ms sencilla fcil de ver el significado de esta ltima lnea
de cdigo vista sera expresarlo de la siguiente manera:
while( ch ! = a )
ch = get char ( ) ;
Un ltimo ejemplo clsico de uso de la estructura while. El clculo del
mximo comn divisor de dos enteros que introduce el usuario por
teclado.
No es necesario explicar el concepto de mximo comn divisor. S es
necesario en cambio explicar un mtodo razonable de plantear al
ordenador cmo se calcula ese valor: porque este ejemplo deja claro la
importancia de tener no slo conocimientos de lenguaje de
programacin, sino tambin de algoritmos vlidos.
Captulo 4. Estructuras de control.


109
Euclides, matemtico del siglo V a. de C. present un algoritmo muy
fcil de implementar, y de muy bajo coste computacional. El algoritmo
de Euclides dice que el mximo comn divisor de dos enteros
1
a y
1
b
(diremos
1 1
( , ) mcd a b ), donde
1
0 b es igual a
2 2
( , ) mcd a b donde
=
2 1
a b y donde =
2 1 1
% b a b , entendiendo por
1 1
% a b el resto de la
divisin de
1
a con
1
b . Y el proceso puede seguirse hasta llegar a unos
valores de
i
a y de
i
b que verifiquen que 0
i
a , 0
i
b y = % 0
i i
a b .
Entonces, el algoritmo de Euclides afirma que, llegado a estos valores el
valor buscado es =
1 1
( , )
i
mcd a b b .
Ahora falta poner esto en lenguaje C. Ah va:
#i ncl ude <st di o. h>
void mai n( void)
{
short int a, b, aux;
short int mcd;
pr i nt f ( " Val or de a . . . " ) ;
scanf ( " %hd" , &a) ;
pr i nt f ( " Val or de b . . . " ) ;
scanf ( " %hd" , &b) ;
pr i nt f ( " El mcd de %hd y %hd es . . . " , a, b) ;
while( b)
{
aux = a %b;
a = b;
b = aux;
}
pr i nt f ( " %hu" , a) ;
}
(De nuevo recordamos que se encuentran en el manual complementario
a ste, titulado Fundamentos de Informtica. Codificacin y Algoritmia.
explicaciones a este nuevo algoritmo y a otros muchos que veremos en
estas pginas.)
Hemos tenido que emplear una variable auxiliar, que hemos llamado
aux, para poder hacer el intercambio de variables: que a pase a valer el
valor de b y b el del resto de dividir a por b.
As como queda escrito el cdigo, se irn haciendo los intercambios de
valores en las variables a y b hasta llegar a un valor de b igual a cero;
Fundamentos de informtica. Programacin en Lenguaje C


110
entonces, el anterior valor de b (que est guardado en a) ser el
mximo comn divisor.
Estructura do while.
La estructura do while es muy similar a la anterior. La diferencia ms
sustancial est en que con esta estructura el cdigo de la iteracin se
ejecuta, al menos, una vez. Si despus de haberse ejecutado, la
condicin se cumple, entonces vuelve a ejecutarse, y as hasta que la
condicin no se cumpla. Puede verse un esquema de su comportamiento
en la figura 4.3., en pginas anteriores.
La sintaxis de la estructura es la siguiente:
do sentencia while( condicin) ;
Y si se desea iterar un bloque de sentencias, entonces se agrupan en
una sentencia compuesta mediante llaves.
Habitualmente, toda solucin a un problema resuelto con una
estructura, es tambin solventable, de forma ms o menos similar, por
cualquiera de las otras dos estructuras. Por ejemplo, la tabla de
multiplicar quedara:
#i ncl ude <st di o. h>
void mai n( void)
{
short int n, i = 0;
pr i nt f ( " Tabl a de mul t i pl i car del . . . " ) ;
scanf ( " %hd" , &n) ;
do
{
pr i nt f ( " %3hu * %3hu = %3hu\ n" , i , n, i * n) ;
i ++:
}while( i <= 10) ;
}
Una estructura muy habitual en un programa es ejecutar unas
instrucciones y luego preguntar al usuario, antes de terminar la
ejecucin de la aplicacin, si desea repetir el proceso. Supongamos, por
ejemplo, que queremos un programa que calcule el factorial de tantos
nmeros como desee el usuario, hasta que no quiera continuar. El
Captulo 4. Estructuras de control.


111
cdigo ahora requiere de otra estructura de repeticin, que vuelva a
ejecutar el cdigo mientras que el usuario no diga basta. Una posible
codificacin de este proceso sera (ver figura 4.7.):
#i ncl ude <st di o. h>
void mai n( void)
{
unsigned short n;
unsigned long Fact ;
char opci on;
do
{
Fact = 1;
pr i nt f ( " \ n\ nI nt r oduzca el ent er o . . . " ) ;
scanf ( " %hu" , &n) ;
pr i nt f ( " El f act or i al de %hu es . . . " , n) ;
while( n ! = 0) Fact *= n- - ;
pr i nt f ( " %l u. " , Fact ) ;
pr i nt f ( " \ n\ nCal cul ar ot r o f act or i al ( s/ n) " ) ;
}while( opci on = get char ( ) == ' s' ) ;
}
La estructura do while repetir el cdigo que calcula el factorial del
entero solicitado mientras que el usuario responda con una s a la
pregunta de si desea que se calcule otro factorial.
Podemos afinar un poco ms en la presentacin. Vamos a rechazar
cualquier contestacin que no sea o s o n: si el usuario responde s,
entonces se repetir la ejecucin del clculo del factorial; si responde n
el programa terminar su ejecucin; y si el usuario responde cualquier
otra letra, entonces simplemente ignorar la respuesta y seguir
esperando una contestacin vlida.
Figura 4.7.: Flujograma
para repeticin de proceso.
repetir
C
F
S No
PROCESO
Fundamentos de informtica. Programacin en Lenguaje C


112
El cdigo queda ahora de la siguiente manera:
#i ncl ude <st di o. h>
void mai n( void)
{
unsigned short n;
unsigned long Fact ;
char opci on;
do
{
Fact = 1;
pr i nt f ( " \ n\ nI nt r oduzca el ent er o . . . " ) ;
scanf ( " %hu" , &n) ;
pr i nt f ( " El f act or i al de %hu es . . . " , n) ;

while( n ! = 0) Fact *= n- - ;

pr i nt f ( " %l u. " , Fact ) ;
pr i nt f ( " \ n\ nCal cul ar ot r o f act or i al ( s/ n) " ) ;

do
opci on = get char ( ) ;
while ( opci on ! = s && opci on ! = n ) ;
}while( opci on = get char ( ) == ' s' ) ;
}
Ahora el valor de la variable opcion se ir pidiendo mientras que el
usuario no introduzca correctamente una de las dos respuestas vlidas:
o s (s), o no (n). El flujograma de esta nueva solucin queda recogido
en la figura 4.8.
C1
C
F
S No
Figura 4.8.: Flujograma
para repeticin de proceso.
PROCESO
opcion
C2
S No
1 ' ' | ' ' C opcion s opcion n
= 2 ' ' C opcion s
Captulo 4. Estructuras de control.


113
Estructura for.
Una estructura for tiene una sintaxis notablemente distinta a la indicada
para las estructuras while y do while. Pero la funcin que realiza es la
misma. Con la palabra reservada for podemos crear estructuras de
control que se dicen controladas por variable.
La sintaxis de la estructura for es la siguiente:
for( sentencias_ 1 , expresin ; sentencias_ 2) sentencia_ 3;
Donde sentencia3 es la sentencia que se itera, la que queda
gobernada por la estructura de control for.
Donde sentencias_ 1 es un grupo de sentencias que se ejecutan antes
que ninguna otra en una estructura for, y siempre se ejecutan una vez
y slo una vez. Son sentencias, separadas por el operador coma, de
inicializacin de variables.
Donde expresin es la condicin de permanencia en la estructura
for. Siempre que se cumpla expresin volver a ejecutarse la sentencia
iterada por la estructura for.
e1
C
F
S No
Figura 4.9.: Flujograma la
estructura de control
for(s1 ; e1 ; s2) s3;
s1
s3
s2
Fundamentos de informtica. Programacin en Lenguaje C


114
Donde sentencias_ 2 son un grupo de sentencias que se ejecutan
despus de la sentencia iterada (despus de sentencia_3).
El orden de ejecucin es, por tanto (ver flujograma en figura 4.9.):
1. Se inicializan variables segn el cdigo recogido en sentencias_1.
2. Se verifica la condicin de permanencia recogida en expresin. Si
expresin es verdadero se sigue en el paso 3; si es falso entonces se
sigue en el paso 6.
3. Se ejecuta la sentencia iterada llamada, en nuestro esquema de
sintaxis, sentencia_3.
4. Se ejecutan las sentencias recogidas en sentencias_2.
5. Vuelta al paso 2.
6. Fin de la iteracin.
As, una estructura for es equivalente a una estructura while de la
forma:
sentencias_ 1;
while( expresin)
{
sentencia_ 3;
sentencias_ 2;
}
Por ejemplo, veamos un programa que muestra por pantalla los enteros
pares del 1 al 100:
#i ncl ude <st di o. h>
void mai n( void)
{
short i ;
for( i = 2 ; i <= 100 ; i += 2)
pr i nt f ( " %5hd" , i ) ;
}
Si queremos mejorar la presentacin, podemos hacer que cada cinco
pares comience una nueva fila:
#i ncl ude <st di o. h>
void mai n( void)
Captulo 4. Estructuras de control.


115
{
short i ;
for( i = 2 ; i <= 100 ; i += 2)
{
pr i nt f ( " %5hd" , i ) ;
if( i %10 == 0) pr i nt f ( " \ n" ) ;
}
}
Ya hemos dicho que en cada uno de los tres espacios de la estructura
for destinados a recoger sentencias o expresiones, pueden consignarse
una expresin, o varias, separadas por comas, o ninguna. Y la sentencia
iterada mediante la estructura for puede tener cuerpo, o no. Veamos
por ejemplo, el clculo del factorial de un entero mediante una
estructura for:
for( Fact = 1 ; n ; Fact *= n- - ) ;
Esta estructura for no itera ms que la sentencia punto y coma. Toda la
trasformacin que deseamos realizar queda en la expresin del clculo
del factorial mediante la expresin Fact *= n--.
El punto y coma debe ponerse: toda estructura de control acta
sobre una sentencia. Si no queremos, con la estructura for, controlar
nada, entonces la solucin no es no poner nada, sino poner una
sentencia vaca.
Todos los ejemplos que hasta el momento hemos puesto en la
presentacin de las estructuras while y do while se pueden rehacer
con una estructura for. En algunos casos es ms cmodo trabajar con la
estructura for; en otros se hace algo forzado. Veamos algunos ejemplos
implementados ahora con la estructura for:
Cdigo para ver la tabla de multiplicar:
#i ncl ude <st di o. h>
void mai n( void)
{ short int n, i ;
pr i nt f ( " Tabl a de mul t i pl i car del . . . " ) ;
scanf ( " %hd" , &n) ;
for( i = 0 ; i <= 10 ; i ++)
pr i nt f ( " %3hu * %3hu = %3hu\ n" , i , n, i * n) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


116
Bloquear la ejecucin hasta que se pulse la tecla a:
for( ; ch ! = a ; ch = get char ( ) ) ;
Bsqueda del mximo comn divisor de dos enteros:
for( ; b ; )
{
aux = a %b;
a = b;
b = aux;
}
pr i nt f ( " %hu" , a) ;

Sentencias de salto: break y continue.
Hay dos sentencias que modifican el orden del flujo de instrucciones
dentro de una estructura de iteracin. Son las sentencias break y
continue. Ambas sentencias, en una estructura de iteracin, se
presentan siempre condicionadas.
Una sentencia break dentro de un bloque de instrucciones de una
estructura de iteracin interrumpe la ejecucin de las restantes
sentencias iteradas y abandona la estructura de control, asignando al
contador de programa la direccin de la siguiente sentencia posterior a
la llave que cierra el bloque de sentencias de la estructura de control.
Por ejemplo, podemos hacer un programa que solicite al usuario que
vaya introduciendo nmeros por teclado hasta que la entrada sea un
nmero par. En ese momento el programa abandonar la solicitud de
datos e indicar cuntos enteros ha introducido el usuario hasta
introducir uno que sea par. El cdigo podra quedar as:
#i ncl ude <st di o. h>
void mai n( void)
{
short i , num;
for( i = 1 ; ; i ++)
{
pr i nt f ( " I nt r oduzca un ent er o . . . " ) ;
scanf ( " %hd" , &num) ;
if( num%2 == 0) break;
Captulo 4. Estructuras de control.


117
}
pr i nt f ( " Ha i nt r oduci do el ent er o par %hd" , num) ;
pr i nt f ( " despus de %hd i mpar es. " , i - 1) ;
}
Se habrn introducido tantos enteros como indique la variable i. De
ellos, todos menos el ltimo habrn sido impares. Desde luego, el
cdigo hara lo mismo si la expresin que condiciona al break se
hubiese colocado en el segundo espacio que ofrece la sintaxis del for
(all donde se colocan las condiciones de permanencia) y se hubiese
cambiado en algo el cdigo; pero no resulta intuitivamente tan sencillo.
Si comparamos la estructura for vista arriba con otra similar, en la que
la condicin de salto queda recogida en el for tendremos:
for( i = 1 ; ; i ++)
{
pr i nt f ( " ent er o: " ) ;
scanf ( " %hd" , &num) ;
if( num%2 == 0) break;
}
for( i = 1 ; num%2 == 0 ; i ++)
{
pr i nt f ( " ent er o: " ) ;
scanf ( " %hd" , &num) ;
}
Aparentemente ambos cdigos hacen lo mismo. Pero si comparamos sus
flujogramas recogidos en la figura 4.10. veremos que se ha introducido
una diferencia no pequea.
Habitualmente esta sentencia break siempre podr evitarse con un
diseo distinto de la estructura de control. Segn en qu ocasiones, el
cdigo adquiere mayor claridad si se utiliza el break. El uso de la
sentencia de salto break es prctica habitual en la programacin
estructurada como es el caso del paradigma del lenguaje C.
Figura 4.10.: Salto con o sin break.
= 1 mod2 0 C num
C1
1 i
+ 1 i i
F
C
num
C1
1 i + 1 i i
F
C
num
Con break Sin break
Fundamentos de informtica. Programacin en Lenguaje C


118
Con la sentencia break es posible definir estructuras de control sin
condicin de permanencia, o con una condicin que es siempre
verdadera. Por ejemplo:
long int suma = 0, num;
do
{
pr i nt f ( I nt r oduzca un nuevo sumando . . . ) ;
scanf ( %l d, &num) ;
if( num%2 ! = 0) break;
suma += num;
}while( 1) ;
pr i nt f ( La suma es . . . %l d, suma) ;
En esta estructura, la condicin de permanencia es verdadera siempre,
por definicin, puesto que es un literal diferente de cero. Pero hay una
sentencia break condicionada dentro del bloque iterado. El cdigo ir
guardando en la variable suma la suma acumulada de todos los valores
introducidos por teclado mientras que esos valores sean enteros pares.
En cuanto se introduzca un entero impar se abandona la estructura de
iteracin y se muestra el valor sumado.
El diagrama de flujo de este ltimo ejemplo queda recogido en la figura
4.11.
Tambin se pueden tener estructuras for sin ninguna expresin recogida
entre sus parntesis. Por ejemplo:
for( ; ; )
{
ch = get char ( ) ;
Figura 4.11.: Otro ejemplo de salto con break.
C1
F
C
0 suma

+
suma
suma num
num
1 mod2 0 C num
suma
Captulo 4. Estructuras de control.


119
pr i nt f ( Est o es un bucl e i nf i ni t o\ n) ;
if( ch == a ) break;
}
que repetir la ejecucin del cdigo hasta que se pulse la tecla a, y que
ocasionar la ejecucin del break.
Una sentencia continue dentro de un bloque de instrucciones de una
estructura de iteracin interrumpe la ejecucin de las restantes
sentencias iteradas y vuelve al inicio de las sentencias de la estructura
de control, si es que la condicin de permanencia as lo permite.
Veamos, por ejemplo, el programa antes presentado que muestra por
pantalla los 100 primeros enteros pares positivos. Otro modo de
resolver ese programa podra ser el siguiente:
#i ncl ude <st di o. h>
void mai n( void)
{
short i ;
for( i = 1 ; i <= 100 ; i ++)
{
if( i %2) continue;
pr i nt f ( " %4hd\ t " , i ) ;
}
}
Cuando el resto de la divisin entera entre el contador i y el nmero 2
es distinto de cero, entonces el nmero es impar y se solicita que se
ejecute la sentencia continue. Entonces se abandona la ejecucin del
resto de las sentencias del bloque iterado y, si la condicin de
Figura 4.12.: Salto con continue.
C1
F
C
1 i
C2
+ 1 i i
i
1 100 C i
2 mod2 0 C i
Fundamentos de informtica. Programacin en Lenguaje C


120
permanencia en la iteracin as lo permite, vuelve a comenzar la
iteracin por su primera sentencia del bloque iterado. El diagrama de
flujo del cdigo escrito queda recogido en la figura 4.12.

Palabra reservada goto.
La palabra reservada de C goto no debe ser empleada.
La sentencia de salto goto no respeta las reglas de la programacin
estructurada. Todo cdigo que se resuelve empleando una sentencia
goto puede encontrar una solucin mejor con una estructura de
iteracin.
Si alguna vez ha programado con esta palabra, la recomendacin es que
se olvide de ella. Si nunca lo ha hecho, la recomendacin es que la
ignore.
Y, eso s: hay que acordarse de que esa palabra es clave en C: no se
puede generar un identificador con esa cadena de letras.

Variables de control de iteraciones.
Hasta el momento, hemos hecho siempre uso de las variables para
poder almacenar valores concretos. Pero pueden tener otros usos. Por
ejemplo, podemos hacer uso de ellas como chivatos o centinelas: su
informacin no es el valor concreto numrico que puedan tener, sino la
de una situacin del proceso.
Como vamos viendo en los distintos ejemplos que se presentan, el
diseo de bucles es tema delicado: acertar en la forma de resolver un
problema nos permitir llevar solucin a muy diversos problemas. Pero,
como estamos viendo, es importante acertar en un correcto control del
bucle: decidir bien cuando se ejecuta y cuando se abandona.
Existen dos formas habituales de controlar un bucle o iteracin:
Captulo 4. Estructuras de control.


121
1. Control mediante variable contador. En ellos una variable se
encarga de contar el nmero de veces que se ejecuta el cuerpo del
bucle. Esos contadores requieren una inicializacin previa, externa al
bucle, y una actualizacin en cada iteracin para llegar as
finalmente a un valor que haga falsa la condicin de permanencia.
Esa actualizacin suele hacerse al principio o al final de las
sentencias iteradas. Hay que garantizar, cuando se disea una
iteracin, que se llega a una situacin de salida.
2. Control por suceso. Este tipo de control acepta, a su vez,
diferentes modalidades:
2.1. Consulta explcita: El programa interroga al usuario si desea
continuar la ejecucin del bucle. La contestacin del usuario
normalmente se almacena en una variable tipo char o int. Es el
usuario, con su contestacin, quien decida la salida de la
iteracin.
2.2. Centinelas: la iteracin se termina cuando la variable de control
toma un valor determinado. Este tipo de control es usado
habitualmente para introducir datos. Cuando el usuario
introduce el valor que el programador ha considerado como
valor de fin de iteracin, entonces, efectivamente, se termina
esa entrada de datos. As lo hemos visto en el ejemplo de
introduccin de nmeros, en el programa del clculo de la
media, en el que hemos considerado que la introduccin del
valor cero era entendido como final de la introduccin de datos.
2.3. Banderas: Es similar al centinela, pero utilizando una variable
lgica que toma un valor u otro en funcin de determinadas
condiciones que se evalan durante la ejecucin del bucle. As se
puede ver, por ejemplo, en el ejercicio 7 planteado al final de
captulo: la variable que hemos llamado chivato es una variable
bandera.
Fundamentos de informtica. Programacin en Lenguaje C


122
Normalmente la bandera siempre se puede sustituir por un
centinela, pero se emplea en ocasiones donde la condicin de
terminacin resulta compleja y depende de varios factores que se
determinan en diferentes puntos del bucle.

Recapitulacin.
Hemos presentado las estructuras de control posibles en el lenguaje C.
Las estructuras condicionales de bifurcacin abierta o cerrada,
condicionales anidadas, operador interrogante, dos puntos, y sentencia
switch. Tambin hemos visto las posibles iteraciones creadas con las
estructuras for, while y do while, y las modificaciones a las
estructuras que podemos introducir gracias a las palabras reservadas
break y continue.
El objetivo final de este tema es aprender unas herramientas de enorme
utilidad en la programacin. Conviene ahora ejercitarse en ellas,
resolviendo ahora los diferentes ejercicios propuestos.


Ejercicios.
En todos los ejercicios que planteamos a continuacin quiz ser
conveniente que antes de abordar la implementacin se intente disear
un algoritmo en pseudocdigo o mediante un diagrama de flujo. Si en
algn caso el problema planteado supone especial dificultad quedar
recogido ese flujograma en estas pginas. En bastantes casos, puede
consultarse ste en las pginas del manual Fundamentos de
informtica. Codificacin y algoritmia.


Captulo 4. Estructuras de control.


123
20. Calcular la suma de los pares positivos menores o igual a 200.

#i ncl ude <st di o. h>
void mai n( void)
{
long suma = 0;
short i ;
for( i = 2 ; i <= 200 ; i += 2)
suma += i ;
pr i nt f ( " est a suma es . . . %l d. \ n" , suma) ;
}


21. Hacer un programa que calcule la media de todos los valores
que introduzca el usuario por consola. El programa debe dej ar
de solicitar valores cuando el usuario introduzca el valor 0.

#i ncl ude <st di o. h>
void mai n( void)
{
long suma;
short i , num;
for( i = 0, suma = 0 ; ; i ++)
{
pr i nt f ( " I nt r oduzca nmer o . . . " ) ;
scanf ( " %hd" , &num) ;
suma += num;
if( num== 0) break;
}
if( i == 0) pr i nt f ( " No se han i nt r oduci do ent er os. " ) ;
else pr i nt f ( " La medi a es . . . %. 2f . " , ( float) suma / i ) ;
}
En la estructura for se inicializan las variables i y suma. Cada vez que
se introduce un nuevo entero se suma al acumulado de todas las sumas
de los enteros anteriores, en la variable suma. La variable i lleva la
cuenta de cuntos enteros se han introducido; dato necesario para
calcular, cuando se termine de introducir enteros, el valor de la media.
Fundamentos de informtica. Programacin en Lenguaje C


124

22. Mostrar por pantalla los nmeros perfectos menores de 10000.
Se entiende por nmero perfecto aquel que es igual a la suma
de sus divisores. Por ej emplo, 6 = 1 + 2 + 3 que son,
efectivamente, sus divisores.

#i ncl ude <st di o. h>
void mai n( void)
{
short suma;
short i , num;
for( num= 2 ; num< 10000 ; num++)
{
for( i = 1, suma = 0 ; i <= num/ 2 ; i ++)
if( num%i == 0) suma += i ;
if( num== suma)
pr i nt f ( " En ent er o %hd es per f ect o. \ n" , num) ;
}
}
La variable num recorre todos los enteros entre 2 y 10000 en busca de
aquellos que sean perfectos. Esa bsqueda se realiza con el for ms
externo.
Para cada valor distinto de num, la variable suma, que se inicializa a
cero cada vez que se comienza de nuevo a ejecutar la estructura for
anidada, guarda la suma de sus divisores. Eso se realiza en el for
anidado.
Despus del clculo de cada suma, si su valor es el mismo que el entero
inicial, entonces ese nmero ser perfecto (esa es su definicin) y as se
mostrar por pantalla.
La bsqueda de divisores se hace desde el 1 (que siempre interviene)
hasta la mitad de num: ninguno de los divisores de num puede ser
mayor que su mitad. Desde luego, se podra haber inicializado la
variable suma al valor 1, y comenzar a buscar los divisores a partir del
2, porque, efectivamente, el entero 1 es divisor de todos los enteros.
Captulo 4. Estructuras de control.


125

23. Solicitar del usuario cuatro nmeros y mostrarlos por pantalla
ordenados de menor a mayor.

#i ncl ude <st di o. h>
void mai n( void)
{
unsigned short int a0, a1, a2, a3;
pr i nt f ( " I nt r oduzca cuat r o ent er os . . . \ n\ n" ) ;
pr i nt f ( " Pr i mer ent er o . . . " ) ;
scanf ( " %hu" , &a0) ;
pr i nt f ( " Segundo ent er o . . . " ) ;
scanf ( " %hu" , &a1) ;
pr i nt f ( " Ter cer ent er o . . . " ) ;
scanf ( " %hu" , &a2) ;
pr i nt f ( " Cuar t o ent er o . . . " ) ;
scanf ( " %hu" , &a3) ;
if( a0 > a1)
{
a0 ^= a1;
a1 ^= a0;
a0 ^= a1;
}
if( a0 > a2)
{
a0 ^= a2;
a2 ^= a0;
a0 ^= a2;
}
if( a0 > a3)
{
a0 ^= a3;
a3 ^= a0;
a0 ^= a3;
}
if( a1 > a2)
{
a1 ^= a2;
a2 ^= a1;
a1 ^= a2;
}
if( a1 > a3)
{
a1 ^= a3;
a3 ^= a1;
a1 ^= a3;
Fundamentos de informtica. Programacin en Lenguaje C


126
}
if( a2 > a3)
{
a2 ^= a3;
a3 ^= a2;
a2 ^= a3;
}
pr i nt f ( " \ nOr denados. . . \ n" ) ;
pr i nt f ( " %hu <= %hu <= %hu <= %hu. " , a0, a1, a2, a3) ;
}
Donde el cdigo que se ejecuta en cada estructura if intercambia los
valores de las dos variables, como ya vimos en un ejemplo de un tema
anterior.

24. Mostrar por pantalla todos los caracteres ASCI I .
(Antes de ejecutar el cdigo escrito, termine de leer todo el texto
recogido en este problema.)
#i ncl ude <st di o. h>
void mai n( void)
{
unsigned char a;
for( a = 0 ; a <= 255 ; a++)
pr i nt f ( " %3c - %hX - %hd\ n" , a, a, a) ;
}
Va mostrando todos los caracteres, uno por uno, y su cdigo en
hexadecimal y en decimal.
Si se desea ver la aparicin de todos los caracteres, se puede programar
para que se pulse una tecla cada vez que queramos que salga el
siguiente carcter por pantalla. Simplemente habra que modificar la
estructura for, de la siguiente forma:
for( a = 0 ; a <= 255 ; a++)
{
pr i nt f ( " %3c - %hX - %hd\ n" , a, a, a) ;
get char ( ) ;
}
Advertencia importante: De forma intencionada hemos dejado el
cdigo de este problema con un error grave. Aparentemente todo est
Captulo 4. Estructuras de control.


127
bien. De hecho no existe error sintctico alguno. El error se ver en
tiempo de ejecucin, cuando se compruebe que se ha cado en un bucle
infinito.
Para comprobarlo basta observar la condicin de permanencia en la
iteracin gobernada por la estructura for, mediante la variable que
hemos llamado a: a <= 255. Si se tiene en cuenta que la variable a es
de tipo unsigned char, no es posible que la condicin indicada llegue a
ser falsa, puesto que, en el caso de que a alcance el valor 255, al
incrementar en 1 su valor, incurrimos en overflow, y la variable cae en
el valor cero: =
10 2
(255) (11111111) ; si solo tenemos ocho dgitos,
entonces + =
2 2
(11111111 1) (00000000) , pues no existe el bit noveno,
donde debera haber quedado codificado un dgito 1.
Sirva este ejemplo para advertir de que a veces puede aparecer un
bucle infinito de la manera ms insospechada. La tarea de programar no
est exenta de sustos e imprevistos que a veces obligan a dedicar un
tiempo no pequeo a buscar la causa de un error.
Un modo correcto de codificar este bucle sera, por ejemplo:
for( a = 0 ; a < 255 ; a++)
pr i nt f ( " %3c - %hX - %hd\ n" , a, a, a) ;
pr i nt f ( " %3c - %hX - %hd\ n" , a, a, a) ;
Y as, cuando llegue al valor mximo codificable en la variable a,
abandona el bucle e imprime, ya fuera de la iteracin, una ltima lnea
de cdigo, con el valor ltimo.

25. Solicitar al usuario una valor entero por teclado y mostrar
entonces, por pantalla, el cdigo binario en la forma en que se
guarda el nmero en la memoria.

#i ncl ude <st di o. h>
void mai n( void)
{
Fundamentos de informtica. Programacin en Lenguaje C


128
signed long a;
unsigned long Test ;
char opci on;
do
{
Test = 0x80000000;
pr i nt f ( " \ n\ nI ndi que el ent er o . . . " ) ;
scanf ( " %l d" , &a) ;
while( Test )
{
Test & a ? pr i nt f ( " 1" ) : pr i nt f ( " 0" ) ;
Test >>= 1;
}
pr i nt f ( " \ nDesea i nt r oduci r ot r o ent er o? . . . " ) ;
do
opci on = get char ( ) ;
while ( opci on ! = ' s' && opci on ! = ' n' ) ;
}while( opci on == ' s' ) ;
}
La variable Test se inicializa al valor hexadecimal 80000000, es decir,
con un 1 en el bit ms significativo y en cero en el resto. Posteriormente
sobre esta variable se va operando un desplazamiento a derecha de un
bit cada vez. As, siempre tenemos localizado un nico bit en la
codificacin de la variable Test, hasta que este bit se pierda por la parte
derecha en un ltimo desplazamiento.
Antes de cada desplazamiento, se realiza la operacin and a nivel de bit
entre la variable Test, de la que conocemos donde est su nico bit, y la
variable de la que queremos conocer su cdigo binario.
En el principio, tendremos as las dos variables:
Test 1000 0000 0000 0000 0000 0000 0000 0000
a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
Test & a x000 0000 0000 0000 0000 0000 0000 0000
Tenemos certeza de que la operacin and dejar un cero en todos los
bits (menos el ms significativo) de Test & a, porque Test tiene todos
esos bits a cero. Todos menos el primero. Entonces la operacin dar
un valor distinto de cero nicamente si en ese bit se encuentra un 1 en
la variable a. Sino, el resultado de la operacin ser cero.
Captulo 4. Estructuras de control.


129
Al desplazar ahora Test un bit a la derecha tendremos la misma
situacin que antes, pero ahora el bit de a testeado ser el segundo por
la derecha.
Test 0100 0000 0000 0000 0000 0000 0000 0000
a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
Test & a 0x00 0000 0000 0000 0000 0000 0000 0000
Y as sucesivamente, imprimiremos un 1 cuando la operacin and sea
diferente de cero, e imprimiremos un 0 cuando la operacin and d un
valor igual a cero.
El programa que hemos presentado permite al usuario ver el cdigo de
tantos nmeros como quiera introducir por teclado: hay una estructura
do while que anida todo el proceso.

26. Escribir un programa que solicite al usuario un entero positivo
e indique si ese nmero introducido es primo o compuesto.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>
void mai n( void)
{
unsigned long int numer o, r ai z;
unsigned long int di v;
char chi vat o;
pr i nt f ( " Dame el numer o que vamos a t est ear . . . " ) ;
scanf ( " %l u" , &numer o) ;
chi vat o = 0;
r ai z = sqr t ( numer o) ;
for( di v = 2 ; di v <= r ai z ; di v++)
{
if( numer o %di v == 0)
{
chi vat o = 1;
break;
}
}
if( chi vat o == 1)
pr i nt f ( " El numer o %l u es compuest o" , numer o) ;
else pr i nt f ( " El numer o %l u es pr i mo" , numer o) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


130
Antes de explicar brevemente el algoritmo, conviene hacer una digresin
sencilla matemtica: todo entero verifica que, si tiene divisores distintos
del 1 y del mismo nmero (es decir, si es compuesto), al menos uno de
esos divisores es menor que su raz cuadrada. Eso es sencillo de
demostrar por reduccin al absurdo: supongamos que tenemos un
entero n y que tiene dos factores distintos de 1 y de n ; por ejemplo, a
y b , es decir, = n a b . Supongamos que ambos factores son mayores
(estrictamente mayores) que la raz cuadrada de n . Entonces
tendremos:
= < = < n a b n n n n n
En tal caso tendramos el absurdo de que n es estrictamente menor que
n . Por lo tanto, para saber si un entero n es primo o compuesto, basta
buscar divisores entre 2 y n . Si en ese rango no los encontramos,
entonces podemos concluir que el entero es primo.
En este programa vamos probando con todo los posibles enteros que
dividen al nmero estudiado, que sern todos los comprendidos entre el
2 y la raz cuadrada de ese nmero. En cuanto se encuentra un valor
que divide al entero introducido, entonces ya est claro que ese nmero
es compuesto y no es menester seguir buscando otros posibles
divisores. Por eso se ejecuta la sentencia break.
Al terminar la ejecucin de la estructura for no sabremos si hemos
salido de ella gracias a que hemos encontrado un divisor y nos ha
expulsado la sentencia break, o porque hemos terminado de testear
entre todos los posibles candidatos a divisores menores que la raz
cuadrada y no hemos encontrado ninguno porque el entero introducido
es primo. Por ello hemos usado la variable chivato, que se pone a 1,
antes de la sentencia break, en caso de que hayamos encontrado un
divisor.
Otro modo de saber si hemos salido del bucle por haber encontrado un
divisor o por haber terminado el recorrido de la variable de control de la
Captulo 4. Estructuras de control.


131
estructura for es verificar el valor de esa variable contador. Si la
variable div es mayor que raiz entonces est claro que hemos salido del
bucle por haber terminado la bsqueda de posibles divisores y numero
es primo. Si div es menor o igual que raiz, entonces est tambin claro
que hemos encontrado un divisor: es otra forma de hacer el programa,
sin necesidad de crear la variable chivato.
El cdigo, en ese caso, podra quedar de la siguiente manera:
#i ncl ude <st di o. h>
#i ncl ude <mat h. h>
void mai n( void)
{
unsigned long int n, r ai z;
unsigned long int di v;
pr i nt f ( " Dame el numer o que vamos a t est ear . . . " ) ;
scanf ( " %l u" , &n) ;
r ai z = sqr t ( n) ;
for( di v = 2 ; di v<=r ai z ; di v++) if( n%di v == 0) break;
pr i nt f ( %l u es %s, n, n > r ai z ? pr i mo: compuest o) ;
}

27. Escriba un programa que muestre por pantalla un trmino
cualquiera de la serie de Fibonacci.

La serie de Fibonacci est definida de la siguiente manera: el primer y el
segundo elementos son iguales a 1. A partir del tercero, cualquier otro
elemento de la serie es igual a la suma de sus dos elementos anteriores.
Escribir el cdigo es muy sencillo una vez se tiene el flujograma. Intente
hacer el flujograma, o consulte pgina 60 del manual Fundamentos de
Informtica. Codificacin y Algoritmia.
#i ncl ude <st di o. h>

void mai n( void)
{
unsigned long f i b1 = 1, f i b2 = 1, Fi b = 1;
unsigned short n, i = 3;

Fundamentos de informtica. Programacin en Lenguaje C


132
pr i nt f ( " I ndi que el el ement o que se debe most r ar . . . " ) ;
scanf ( " %hu" , &n) ;

while( i <= n)
{
Fi b = f i b1 + f i b2;
f i b1 = f i b2;
f i b2 = Fi b;
i ++;
}
pr i nt f ( " El t r mi no %hu de l a ser i e es %l u. \ n" , n, Fi b) ;
}

28. Escriba un programa que resuelva una ecuacin de segundo
grado. Tendr como entrada los coeficientes a , b y c de la
ecuacin y ofrecer como resultado las dos soluciones reales.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>

void mai n( void)
{
float a, b, c;
double r ;
/ / i nt r oducci n de par met r os. . .
pr i nt f ( " I nt r oduzca l os coef i ci ent es. . . \ n\ n" ) ;
pr i nt f ( " a - - > " ) ; scanf ( " %f " , &a) ;
pr i nt f ( " b - - > " ) ; scanf ( " %f " , &b) ;
pr i nt f ( " c - - > " ) ; scanf ( " %f " , &c) ;
/ / Ecuaci n de pr i mer gr ado. . .
if( a == 0)
{ / / No hay ecuaci n . . .
if( b == 0) pr i nt f ( " No hay ecuaci n. \ n" ) ;
else / / S hay ecuaci n de pr i mer gr ado
{
pr i nt f ( " Ecuaci n de pr i mer gr ado. \ n" ) ;
pr i nt f ( " Ti ene una ni ca sol uci n. \ n" ) ;
pr i nt f ( " x1 - - > %l f \ n" , - c / b) ;
}
}
/ / Ecuaci n de segundo gr ado. Sol uci ones i magi nar i as.
else if ( ( r = b * b - 4 * a * c) < 0)
{
pr i nt f ( " Ecuaci n si n sol uci ones r eal es. \ n" ) ;
r = sqr t ( - r ) ;
Captulo 4. Estructuras de control.


133
pr i nt f ( " x1: %l f + %l f * i \ n" , - b/ ( 2*a) , r / ( 2*a) ) ;
pr i nt f ( " x2: %l f + %l f * i \ n" , - b/ ( 2*a) , - r / ( 2*a) ) ;
}
/ / Ecuaci n de segundo gr ado. Sol uci ones r eal es.
else
{
pr i nt f ( " Las sol uci ones son: \ n" ) ;
r = sqr t ( r ) ;
pr i nt f ( " \ t x1 - - > %l f \ n" , ( - b + r ) / ( 2 * a) ) ;
pr i nt f ( " \ t x2 - - > %l f \ n" , ( - b - r ) / ( 2 * a) ) ;
}
}

29. Escriba un programa que muestre todos los divisores de un
entero que se recibe como entrada del algoritmo.

#i ncl ude <st di o. h>

void mai n( void)
{
long int numer o;
long int di v;

pr i nt f ( " Nmer o - - > " ) ;
scanf ( " %l d" , &numer o) ;

pr i nt f ( " \ n\ nLos di vi sor es de %l d son: \ n\ t " , numer o) ;
pr i nt f ( " 1, " ) ;

for( di v = 2 ; di v <= numer o / 2 ; di v++)
if( numer o %di v == 0)
pr i nt f ( " %l d, " , di v) ;

pr i nt f ( " %l d. " , numer o) ;
}
El cdigo ofrece, por ejemplo, la siguiente salida por pantalla:
Nmero --> 456
Los divisores de 456 son:
1, 2, 3, 4, 6, 8, 12, 19, 24, 38, 57, 76, 114, 152, 228, 456.

Fundamentos de informtica. Programacin en Lenguaje C


134
30. Escriba un programa que calcule el nmero , sabiendo que
este nmero verifica la siguiente relacin:


=
=

2
2
1
1
6
k
k
.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>

#def i ne LI MI TE 10000

void mai n( void)
{
double PI = 0;

for( int i = 1 ; i < LI MI TE ; i ++)
PI += 1. 0 / ( i * i ) ;
PI *= 6;
PI = sqr t ( PI ) ;
pr i nt f ( " El val or de PI es . . . %l f . " , PI ) ;
}
Que ofrece la siguiente salida por pantalla:
El valor de PI es ... 3.141497.
El programa va haciendo, en la iteracin gobernada por la estructura
for, el sumatorio de los inversos de los cuadrados. El numerador se
debe poner como 1.0 para que el resultado del cociente no sea un
entero igual a cero sino un valor double.

31. Escriba un programa que calcule el nmero , sabiendo que
este nmero verifica la siguiente relacin:
( )

=

=
+

0
1
4 2 1
k
k
k



Captulo 4. Estructuras de control.


135
#i ncl ude <st di o. h>
#def i ne LI MI TE 100000

void mai n( void)
{
double PI = 0;

for( int i = 1 , e = 4 ; i < LI MI TE ; i += 2 , e = - e)
PI += e / ( double) i ;

pr i nt f ( " El val or de PI es . . . %l f . " , PI ) ;
}
Que ofrece la siguiente salida por pantalla:
El valor de PI es ... 3.141573.
La variable PI se inicializa a cero. En cada iteracin ir almacenando la
suma de todos los valores calculados. En lugar de calcular 4 ,
calculamos directamente el valor de : por eso el numerador (variable
que hemos llamado e) no vara entre -1 y +1, sino entre -4 y +4. El
valor de la variable i aumenta de dos en dos, y va tomando los
diferentes valores del denominador + 2 1 k . Es necesario forzar el tipo
de la variable i a double, para que el resultado de la operacin cociente
no sea un entero, que a partir de i igual a cero dara como resultado el
valor cero.

32. Escriba un programa que calcule el nmero , sabiendo que
este nmero verifica la siguiente relacin:

=
2 2 4 4 6 6 8 8
...
2 1 3 3 5 5 7 7 9
.

De nuevo el clculo del valor del nmero pi. Estos ejercicios son muy
sencillos de buscar (Internet est llena de definiciones de propiedades
del nmero pi) y siempre es fcil comprobar si hemos realizado un buen
cdigo: basta ejecutarlo y comprobar si sale el famoso 3.14.
En esta ocasin, el cdigo podra tomar la siguiente forma:
Fundamentos de informtica. Programacin en Lenguaje C


136
#i ncl ude <st di o. h>
#def i ne LI MI TE 100000
void mai n( void)
{
double PI = 2;
for( int num= 2 , den = 1, i = 1 ;
i < LI MI TE ; i ++ , i %2 ? num+=2 : den+=2)
PI *= num/ ( double) den;
pr i nt f ( " El val or de PI es . . . %l f . " , PI ) ;
}
Por problemas de espacio se ha tenido que mostrar la estructura for
truncada en dos lneas. Esta forma de escribir es perfectamente vlida
en C. El compilador considera lo mismo un espacio en blanco que tres
lneas blancas. Para el compilador estos dos cdigos significan lo mismo:
short int a = 0 , b = 1, c = 2;
double x, y, z;
short int
a = 0,
b = 1,
c = 2,
double
x,
y, z;

33. Cinco marineros llegan, tras un naufragio, a una isla desierta
con un gran nmero de cocoteros y un pequeo mono. Dedican
el primer da a recolectar cocos, pero ej ecutan con tanto afn
este trabaj o que acaban agotados, por lo que deciden
repartirse los cocos al da siguiente.
Durante la noche un marinero se despierta y, desconfiando de
sus compaeros, decide tomar su parte. Para ello, divide el
montn de cocos en cinco partes iguales, sobrndole un coco,
que regala al mono. Una vez calculada su parte la esconde y se
vuelve a acostar.
Un poco ms tarde otro marinero tambin se despierta y vuelve
a repetir la operacin, sobrndole tambin un coco que regala
al mono. En el resto de la noche sucede lo mismo con los otros
Captulo 4. Estructuras de control.


137
tres marineros.
Al levantarse por la maana procedieron a repartirse los cocos
que quedaban entre ellos cinco, no sobrando ahora ninguno.
Cuntos cocos haban recogido inicialmente? Mostrar todas las
soluciones posibles menores de 1 milln de cocos.

Este programa es sencillo de implementar. Pero hay que saber resolver
el problema. Es un ejemplo de cmo saber un lenguaje no lo es todo en
programacin; ms bien podramos decir que saber un lenguaje es lo de
menos: lo importante es saber qu decir.
Este algoritmo est ya explicado en el otro manual, ya tantas veces
referenciado en ste. Aqu dejamos slo el cdigo resultante.
#i ncl ude <st di o. h>

void mai n( void)
{
unsigned long N = 6, n;
unsigned long sol uci ones = 0;

pr i nt f ( " Val or es posi bl es de N . . . \ n\ n" ) ;

while( N < 4000000)
{
unsigned short int i ;
N += 5;
n = N;
for( i = 0 ; i < 5 ; i ++)
{
if( ( n - 1) %5) break;
n = 4 * ( n - 1) / 5;
}
if( i == 5 && ! ( n %5) )
{
pr i nt f ( " ( %4l u) %- 8l u" , ++sol uci ones, N) ;
if( ! ( sol uci ones %5) ) pr i nt f ( " \ n" ) ;
}
}
}

Fundamentos de informtica. Programacin en Lenguaje C


138
34. El calendario j uliano ( debido a j ulio Cesar) consideraba que el
ao duraba 365.25 das, por lo que se estableci que los aos
tendran una duracin de 365 das y cada cuatro aos se
aadiese un da ms ( ao bisiesto) .
Sin embargo se comprob que en realidad el ao tiene
365.2422 das, lo que implica que el calendario j uliano llevase
un desfase de unos once minutos. Este error es relativamente
pequeo, pero, con el transcurrir del tiempo, el error
acumulado puede ser importante.
El papa Gregorio XI I , en 1582, propuso reformar el calendario
j uliano para evitar los errores arrastrados de aos anteriores.
Los acuerdos tomados entonces, que son por los que nos an
nos regimos, fueron los siguientes:
Para suprimir el error acumulado por el calendario j uliano,
se suprimieron diez das. De tal manera que el da siguiente
al 4 de octubre de 1582 fuel el da 15 del mismo mes.
La duracin de los aos sera de 365 das o 366 en caso de
ser bisiesto.
Sern bisiestos todos los aos que sean mltiplos de 4,
salvo los que finalizan en 00, que slo lo sern cuando
tambin sean mltiplos de 400, por ej emplo 1800 no fue
bisiesto y el 2000 s.
Con esta reforma el desfase existente entre el ao civil y el
ao real se reduce a menos de treinta segundos anuales.
Definir un algoritmo que solicite al usuario una fecha
introducida mediante tres datos: da, mes y ao; ese programa
debe validar la fecha: es decir comprobar que la fecha es
correcta cumpliendo las siguientes reglas:
El ao debe ser mayor que 0.
Captulo 4. Estructuras de control.


139
El mes debe ser un nmero entre uno y doce.
El da debe estar entre 1 y 30, 31,28 29 dependiendo el
mes de que se trate y si el ao es bisiesto o no.

Se deja propuesto. En otro lugar est recogido el flujograma del
algoritmo.

35. Calcule el valor del nmero e. sabiendo que verifica la siguiente
relacin:
= + + + +
1 1 1 1
...
0! 1! 2! 3!
e

#i ncl ude <st di o. h>
void mai n( void)
{
double p = 1, e = 1;
for( short n = 1 ; n < 100 ; n++)
{
p *= 1. 0 / n;
e += p;
}
pr i nt f ( " El numer o e es . . . %20. 17l f . " , e) ;
}

36. J uego de las 15 cerillas: Participan dos j ugadores.
I nicialmente se colocan 15 cerillas sobre una mesa y cada uno
de los dos j ugadores toma, alternativamente 1, 2 o 3 cerillas
pierde el j ugador que toma la ltima cerilla.
Buscar el algoritmo ganador para este j uego, generalizando:
inicialmente hay N cerillas y cada vez se puede tomar hasta un
mximo de k cerillas. Los valores N y k son introducidos por
Fundamentos de informtica. Programacin en Lenguaje C


140
teclado y decididos por un j ugador ( que ser el j ugador
perdedor) , el otro j ugador ( que ser el ordenador, y que
siempre debe ganar) decide quien empieza el j uego.

#i ncl ude <st di o. h>

void mai n( void)
{
/ / Con cunt as cer i l l as se va a j ugar .
unsigned short cer i l l as;
/ / Cunt as cer i l l as se pueden qui t ar cada vez.
unsigned short qui t ar ;
/ / Cer i l l as que qui t a el ganador y el per dedor .
unsigned short qp, qg;
char ganador ;

do
{
pr i nt f ( " \ n\ n\ nCon cunt as cer i l l as
se va a j ugar . . . " ) ;
scanf ( " %hu" , &cer i l l as) ;
if( cer i l l as == 0) break;
do
{
pr i nt f ( " \ Cunt as cer i l l as pueden
qui t ar se de una vez. . . " ) ;
scanf ( " %hu" , &qui t ar ) ;
if( qui t ar >= cer i l l as)
pr i nt f ( " No pueden qui t ar se t ant as
cer i l l as. \ n" ) ;
}while( qui t ar >= cer i l l as) ;

qg = ( cer i l l as - 1) %( qui t ar + 1) ;
/ / MOSTRAR CERI LLAS . . .
pr i nt f ( " \ n" ) ;
for( shor t i = 1 ; i <= cer i l l as ; i ++)
{
pr i nt f ( " | " ) ;
if( ! ( i %30) ) pr i nt f ( " \ n\ n" ) ;
}
pr i nt f ( " \ n" ) ;
/ / Fi n de MOSTRAR CERI LLAS
if( qg)
{
pr i nt f ( " \ nComi enza l a mqui na. . . " ) ;
pr i nt f ( " \ nMqui na Ret i r a %hu cer i l l as. . .
\ n" , qg) ;
Captulo 4. Estructuras de control.


141
cer i l l as - = qg;
/ / MOSTRAR CERI LLAS . . .
pr i nt f ( " \ n" ) ;
for( short i = 1 ; i <= cer i l l as ; i ++)
{
pr i nt f ( " | " ) ;
if( ! ( i %30) ) pr i nt f ( " \ n\ n" ) ;
}
pr i nt f ( " \ n" ) ;
}
/ / FI N DE MOSTRAR CERI LLAS
else pr i nt f ( " \ nComi enza el j ugador . . . " ) ;

while( cer i l l as ! = 1)
{
do
{
pr i nt f ( " \ nCer i l l as que r et i r a el
j ugador . . . " ) ;
scanf ( " %hu" , &qp) ;
if( qp > qui t ar )
pr i nt f ( " \ nNo puede qui t ar mas
de %hu. \ n" , qui t ar ) ;
}while( qp > qui t ar ) ;
cer i l l as - = qp;
/ / MOSTRAR CERI LLAS . . .
pr i nt f ( " \ n" ) ;
for( short i = 1 ; i <= cer i l l as ; i ++)
{
pr i nt f ( " | " ) ;
if( ! ( i %30) ) pr i nt f ( " \ n\ n" ) ;
}
pr i nt f ( " \ n" ) ;
/ / Fi n de MOSTRAR CERI LLAS
if( cer i l l as == 1)
{
ganador = ' j ' ;
break;
}
qg = qui t ar - qp + 1;
pr i nt f ( " \ nLa mqui na r et i r a %hu
cer i l l as. \ n" , qg) ;
cer i l l as - = qg;
/ / MOSTRAR CERI LLAS . . .
pr i nt f ( " \ n" ) ;
for( short i = 1 ; i <= cer i l l as ; i ++)
{
pr i nt f ( " | " ) ;
if( ! ( i %30) ) pr i nt f ( " \ n\ n" ) ;
}
pr i nt f ( " \ n" ) ;
Fundamentos de informtica. Programacin en Lenguaje C


142
/ / Fi n de MOSTRAR CERI LLAS
if( cer i l l as == 1) ganador = ' m' ;
}

if( ganador == ' j ' )
pr i nt f ( " \ nHa ganado el j ugador . . . " ) ;
else if( ganador == ' m' )
pr i nt f ( " \ nHe ganado yo, l a mqui na. . . " ) ;
}while( cer i l l as) ;
}
El programa ha quedado un poco ms largo que los anteriores. Se ha
dedicado un poco de cdigo a la presentacin en el momento de la
ejecucin. Ms adelante, cuando se haya visto el modo de definir
funciones, el cdigo quedar visiblemente reducido. Por ejemplo, en
cuatro ocasiones hemos repetido el mismo cdigo destinado a visualizar
por pantalla las cerillas que quedan por retirar.

37. El nmero ureo ( ) verifica muchas curiosas propiedades.
Por ej emplo:
= +
2
1 = 1 1 = +
3
( 1) ( 1)
= + 1 1 = + 1 y otras
La penltima expresin presentada ( = + 1 1 ) muestra un
camino curioso para el clculo del nmero ureo:
Si = + = + = +

+ +

1 1 1
1 1 1 ...
1 1
1 1
1
1

Y, entonces un modo de calcular el nmero ureo es: inicializar
al valor 1, e ir afinando en el clculo del valor del nmero
ureo a base de repetir muchas veces que = + 1 1 :
Captulo 4. Estructuras de control.


143
+
+
+
+
+

1
1
1
1
1
1
1
1 ...
1
1
1

Escriba un programa para calcular el nmero ureo mediante
este procedimiento haciendo, por ej emplo, la sustitucin
= + 1 1 mil veces. Mostrar luego el resultado por pantalla.

#i ncl ude <st di o. h>
#def i ne LI MI TE 1000

void mai n( void)
{
double au = 1;

for( int i = 0 ; i < LI MI TE ; i ++)
au = 1 + 1 / au;

pr i nt f ( " El nmer o ur eo es . . . . . %l f . \ n" , au) ;
pr i nt f ( " ur eo al cuadr ado es . . . %l f . \ n" , au * au) ;
}
No se ha hecho otra cosa que considerar lo que sugiere el enunciado:
inicializar el nmero ureo a 1, y repetir mil veces la iteracin
= + 1 1 . Al final mostramos tambin el cuadrado del nmero ureo:
es un modo rpido de verificar que, efectivamente, el nmero hallado es
el nmero buscado: el nmero ureo verifica que su cuadrado es igual al
nmero incrementado en uno.
La salida que ofrece este cdigo por pantalla es la siguiente:
El nmero ureo es ..... 1.618034.
ureo al cuadrado es ... 2.618034.

38. Siguiendo con el mismo enunciado, tambin podemos plantearnos
calcular el nmero ureo a partir de la relacin = + 1 . De nuevo
Fundamentos de informtica. Programacin en Lenguaje C


144
tenemos:
= + = + + = + + + = = + + + + + + 1 1 1 1 1 1 ... 1 1 1 1 1 ... 1

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>
#def i ne LI MI TE 100000

void mai n( void)
{
double au = 1;

for( i nt i = 0 ; i < LI MI TE ; i ++)
au = sqr t ( 1 + au) ;

pr i nt f ( " El nmer o ur eo es . . . . . %l f . \ n" , au) ;
pr i nt f ( " ur eo al cuadr ado es . . . %l f . \ n" , au * au) ;
}
Este programa ofrece una salida idntica a la anterior. Y el cdigo es
casi igual que el anterior: nicamente cambia la definicin de la
iteracin.

39. Muestre por pantalla todos los enteros primos comprendidos
entre dos enteros introducidos por teclado.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>

void mai n( void)
{
long a, b, di v;
pr i nt f ( " L mi t e i nf er i or . . . " ) ;
scanf ( " %l d" , &a) ;
pr i nt f ( " L mi t e super i or . . . " ) ;
scanf ( " %l d" , &b) ;

pr i nt f ( " Los pr i mos ent r e %l d y %l d son . . . \ n\ t " , a, b) ;
for( long num= a, pr i mos = 0 ; num<= b ; num++)
{
Captulo 4. Estructuras de control.


145
for( di v = 2 ; di v < sqr t ( num) ; di v++)
if( num%di v == 0) br eak;
if( num%di v == 0) cont i nue;
if( pr i mos ! = 0 && pr i mos %10 == 0)
pr i nt f ( " \ n\ t " ) ;
pr i mos++;
pr i nt f ( " %6l d, " , num) ;
}
}
El primer for recorre todos los enteros comprendidos entre los dos
lmites introducidos por teclado. El segundo for averigua si la variable
num codifica en cada iteracin del primer for un entero primo o
compuesto: si al salir del segundo for se tiene que num % div es igual a
cero, entonces num es compuesto y se ejecuta las sentencia continue
que vuelve a la siguiente iteracin del primer for. En caso contrario, el
valor de num es primo, y entonces sigue adelante con las sentencias del
primer for, que estn destinadas nicamente a mostrar por pantalla, de
forma ordenada, ese entero primo, al igual que habr mostrado
previamente todos los otros valores primos y mostrar los que siga
encontrando posteriormente.
La salida por pantalla del programa podra ser la siguiente:
Lmite inferior ... 123
Lmite superior ... 264
Los primos entre 123 y 264 son ...

127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263,
Para este ltimo programa se recomienda que se dibuje el diagrama de
flujo.


Fundamentos de informtica. Programacin en Lenguaje C


146









CAPTULO 5

MBITO Y VIDA DE LAS VARIABLES

Este breve captulo pretende completar algunos conceptos presentados
en el captulo 3 y que, una vez hemos visto algo de cdigo en el captulo
4, sern ahora ms sencillos de presentar y de comprender. Tambin
presentamos una breve descripcin de cmo se gestiona el
almacenamiento de los datos dentro del ordenador.

mbito y Vida.
Entendemos por mbito de una variable el lugar, dentro de un
programa, en el que esta variable tiene significado. Hasta el momento
todas nuestras variables han tenido como mbito todo el programa, y
quiz ahora no es sencillo hacerse una idea intuitiva de este concepto;
pero realmente, no todas las variables estn en activo a lo largo de
todo el programa.
Fundamentos de informtica. Programacin en Lenguaje C


148
Adems del mbito, existe otro concepto, que podramos llamar
extensin o tiempo de vida, que define el intervalo de tiempo en el
que el espacio de memoria reservado por una variable sigue en reserva;
cuando la variable muere, ese espacio de memoria vuelve a estar
disponible para otros usos que el ordenador requiera. Tambin este
concepto quedar ms aclarado a medida que avancemos en este breve
captulo.

El almacenamiento de las variables y la memoria.
Para comprender las diferentes formas en que se puede crear una
variable, es conveniente describir previamente el modo en que se
dispone la memoria de datos en el ordenador.
Hay diferentes espacios donde se puede ubicar una variable declarada
en un programa:
1. Registros. El registro es el elemento ms rpido de almacenamiento
y acceso a la memoria. La memoria de registro est ubicada
directamente dentro del procesador. Sera muy bueno que toda la
memoria fuera de estas caractersticas, pero de hecho el nmero de
registros en el procesador est muy limitado. El compilador decide
qu variables coloca en estas posiciones privilegiadas. El
programador no tiene baza en esa decisin. El lenguaje C permite
sugerir, mediante algunas palabras clave, la conveniencia o
inconveniencia de que una determinada variable se cree en este
espacio privilegiado de memoria.
2. La Pila. La memoria de pila reside en la memoria RAM (Random
Access Memory: memoria de acceso aleatorio) De la memoria RAM
es de lo que se habla cuando se anuncian los megas o gigas que
tiene la memoria de un ordenador.
El procesador tiene acceso y control directo a la pila gracias al
puntero de pila, que se desplaza hacia abajo cada vez que hay que
Captulo 5. mbito y vida de las variables.


149
reservar ms memoria para una nueva variable, y vuelve a
recuperar su posicin hacia arriba para liberar esa memoria. El
acceso a la memoria RAM es muy rpido, slo superado por el
acceso a registros. El compilador debe conocer, mientras est
creando el programa, el tamao exacto y la vida de todas y cada una
de las variables implicadas en el proceso que se va a ejecutar y que
deben ser almacenados en la pila: el compilador debe generar el
cdigo necesario para mover el puntero de la pila hacia abajo y hacia
arriba. Esto limita el uso de esta buena memoria tan rpida. Hasta el
captulo 10, cuando hablemos de la asignacin dinmica de la
memoria, todas las variables que empleamos pueden existir en la
pila, e incluso algunas de ellas en las posiciones de registro.
3. El montculo. Es un espacio de memoria, ubicada tambin en la
memoria RAM, donde se crean las variables de asignacin dinmica.
Su ventaja es que el compilador no necesita, al generar el programa,
conocer cunto espacio de almacenamiento necesita asignar al
montculo para la correcta ejecucin del cdigo compilado. Esta
propiedad ofrece una gran flexibilidad al cdigo de nuestros
programas. A cambio hay que pagar un precio con la velocidad: lleva
ms tiempo asignar espacio en el montculo que tiempo lleva hacerlo
en la pila.
4. Almacenamiento esttico. El almacenamiento esttico contiene
datos que estn disponibles durante todo el tiempo que se ejecuta el
programa. Ms adelante, en este captulo, veremos cmo se crean y
qu caractersticas tienen las variables estticas.
5. Almacenamiento constante. Cuando se define un valor constante,
ste se ubica habitualmente en los espacios de memoria reservados
para el cdigo del programa: lugar seguro, donde no se ha de poder
cambiar el valor de esa constante.

Fundamentos de informtica. Programacin en Lenguaje C


150
Variables Locales y Variables Globales
Una variable puede definirse fuera de la funcin principal: en el
programa, pero no en una funcin. Esas variables se llaman globales, y
son vlidas en todo el cdigo que se escriba en ese programa. Su
espacio de memoria queda reservado mientras el programa est en
ejecucin. Diremos que son variables globales, que su mbito es
todo el programa y que su vida perdura mientras el programa
est en ejecucin.
Veamos como ejemplo el siguiente cdigo:
long int Fact ;
#i ncl ude <st di o. h>
void mai n( void)
{
short int n;
pr i nt f ( " I nt r oduce el val or de n . . . " ) ;
scanf ( " %hd" , &n) ;
pr i nt f ( " El f act or i al de %hd es . . . " , n) ;
Fact = 1;
while( n) Fact *=n- - ;
pr i nt f ( " %l d" , Fact ) ;
}
La variable n es local: su mbito es nicamente el de la funcin principal
main. La variable Fact es global: su mbito se extiende a todo el
programa.
Advertencia: salvo para la declaracin de variables globales (y
declaracin de funciones, que veremos ms adelante), el lenguaje C no
admite ninguna otra sentencia fuera de una funcin.
Ahora mismo este concepto nos queda fuera de intuicin porque no
hemos visto an la posibilidad de crear y definir en un programa otras
funciones, aparte de la funcin principal. Pero esa posibilidad existe, y
en ese caso, si una variable es definida fuera de cualquier funcin,
entonces esa variable es accesible desde todas las funciones del
programa.
Captulo 5. mbito y vida de las variables.


151
No se requiere ninguna palabra clave del lenguaje C para indicar al
compilador que esa variable concreta es global.
Se recomienda, en la medida de lo posible, no hacer uso de variables
globales. Cuando una variable es manipulable desde cualquier mbito
de un programa es fcil sufrir efectos imprevistos.
Una variable ser local cuando se crea en un bloque del programa, que
puede ser una funcin, o un bloque interno de una funcin.
Por ejemplo:
long x = 12;
/ / Sl o x est di sponi bl e.
{
long y = 25;
/ / Tant o x como y est n di sponi bl es.
}
/ / La var i abl e y est f uer a de mbi t o. Ha t er mi nado su vi da.
El mbito de una variable local ser el del bloque en el que est
definida. En C, puede declararse una variable local, con un nombre
idntico al de una variable global; entonces, cuando en ese mbito local
se haga referencia al nombre de esa variable, se entender la variable
local: en ese mbito no se podr tener acceso a la variable global, cuyo
nombre ha sido robado por una local. Por ejemplo:
long x = 12;
/ / Sl o x est di sponi bl e.
{
long x = 25;
/ / En est e bl oque l a ni ca var i abl e x accesi bl e val e 25.
}
/ / La ni ca var i abl e x en est e mbi t o val e 12.
Tambin pueden definirse variables locales del mismo nombre en
mbitos diferentes y disjuntos, porque al no coincidir en mbito en
ninguna sentencia, no puede haber equvoco y cada variable, del mismo
nombre, existe slo en su propio mbito. Por ejemplo:
long x = 12;
/ / Sl o x est di sponi bl e.
{
long y = 25;
Fundamentos de informtica. Programacin en Lenguaje C


152
/ / Tant o x como y est n di sponi bl es.
}
/ / La var i abl e y est f uer a de mbi t o. Ha t er mi nado su vi da.
{
long y = 40;
/ / Tant o x como y est n di sponi bl es.
/ / Est a var i abl e y no es l a mi sma que l a ot r a.
/ * La decl ar aci n de l a var i abl e y es cor r ect a, puest o que
l a ant er i or decl ar aci n de una var i abl e con el mi smo
nombr e f ue en ot r o mbi t o. */
}
/ / La var i abl e y est f uer a de mbi t o. Ha t er mi nado su vi da.
Veamos un ejemplo sencillo de uso de diferentes variable locales:
unsigned short i ;
for( i = 2 ; i < 10000 ; i ++)
{
unsigned short suma = 1;
for( unsigned short j = 2 ; j <= i / 2 ; j ++)
if( i %j == 0) suma += j ;
if( suma == i ) pr i nt f ( %hu, i )
}
En este cdigo, que como vimos permite buscar los nmeros perfectos
entre los primeros 10000 enteros, declara dos variables (j y suma) en el
bloque de la estructura del primer for; la variable j est declarada an
ms local, en el interior del segundo for. Al terminar la ejecucin del for
gobernado por la variable i, esas dos variables dejan de existir; la
variable j muere nada ms se abandona el espacio de ejecucin de la
sentencia iterada por el segundo for. Si a su vez, la estructura for ms
externa estuviera integrada dentro de otra estructura de iteracin, cada
vez que se volviera a ejecutar ese for se volveran a crear esas dos
variables, que tendran el mismo nombre, pero no necesariamente las
mismas direcciones de memoria que antes.
Hay una palabra en C para indicar que la variable es local. Es la palabra
reservada auto. Esta palabra rara vez se utiliza, porque el compilador
descubre siempre el mbito de las variables gracias al lugar donde se
recoge la declaracin.
Un ejemplo muy simple puede ayudar a presentar estas ideas de forma
ms clara:
Captulo 5. mbito y vida de las variables.


153
#i ncl ude <st di o. h>

long b = 0, c = 0;
void mai n( void)
{
for( long b = 0 ; b < 10 ; b++) c++;
pr i nt f ( " El val or de b es %l d y el de c es %l d" , b, c) ;
}
Las variables b y c han sido declaradas globales. Y ambas han sido
inicializadas a cero. Luego, dentro de la funcin principal, se ha
declarado, local dentro del for, la variable b. Y dentro del for se han
variado los valores de las variables b y c.
Cul es la salida que ofrecer por pantalla este cdigo? Por lo que
respecta a la variable c no hay ninguna duda: se ha incrementado diez
veces, y su valor, despus de ejecutar la estructura for, ser 10. Pero,
y b? Esta variable ha sufrido tambin una variacin y ha llegado al
valor 10. Pero cul de los dos variables b ha cambiado?: la de mbito
ms local. Y como la sentencia que ejecuta la funcin printf ya est
fuera de la estructura for, y para entonces la variable local b ya ha
muerto, la variable b que muestra la funcin printf no es otra que la
global: la nica viva en este momento. La salida que mostrar el
programa es la siguiente: El valor de b es 0 y el de c es 10.
Una advertencia importante: ya se ha visto que se pueden declarar, en
mbitos ms reducidos, variables con el mismo nombre que otras que
ya existen en mbitos ms globales. Lo que no se puede hacer es
declarar, en un mismo mbito, dos variables con el mismo nombre. Ante
esa circunstancia, el compilador dar error y no compilar.
Una ltima observacin sobre las variables locales: El lenguaje C
requiere que todas las variables se definan al principio del
bloque donde tienen su mbito: esas declaraciones de variables
deben ser las primeras sentencias en cada bloque que tenga variables
locales en l. As, cuando el compilador crea el bloque, puede asignar el
espacio exacto requerido para esas variables en la pila de la memoria.
En C++ es posible diseminar las declaraciones de las distintas variables
Fundamentos de informtica. Programacin en Lenguaje C


154
a lo largo del bloque, definindolas en el momento en que el
programador requiere de su uso. Si se programa en un entorno de C++,
se podr por tanto diseminar esas declaraciones; pero eso es propiedad
de C++, y el programa generado no podra ser compilado en un
compilador de C.
Es conveniente por tanto, cuando se pretende aprender a programar en
C, imponerse la disciplina de agrupar todas las declaraciones al principio
de cada bloque, como es exigido en la sintaxis de C. Aunque el
programa compilase en un compilador de C++, el cdigo sera
sintcticamente errneo desde el punto de vista de un compilador de C.

Variables estticas y dinmicas.
Con respecto a la extensin o tiempo de vida, las variables pueden ser
estticas o dinmicas. Ser esttica aquella variable que una vez
definida, persiste hasta el final de la ejecucin del programa. Y ser
dinmica aquella variable que puede ser creada y destruida durante la
ejecucin del programa.
No se requiere ninguna palabra clave para indicar al compilador que una
variable creada es dinmica. S es en cambio necesario indicar al
compilador, mediante la palabra clave static, cuando queremos que una
variable sea creada esttica. Esa variable puede ser local, y en tal caso
su mbito ser local, y slo podr ser usada cuando se estn ejecutando
sentencias de su mbito; pero su extensin ser la misma que la del
programa, y siempre que se vuelvan a las sentencias de su mbito all
estar la variable, ya creada, lista para ser usada. Cuando terminen de
ejecutarse las sentencias de su mbito esas posiciones de memoria no
sern accesibles, porque estaremos fuera de mbito, pero tampoco
podr hacerse uso de esa memoria para otras variables, porque la
variable esttica seguir viva y esa posicin de memoria sigue
Captulo 5. mbito y vida de las variables.


155
almacenando el valor que qued de la ltima vez que se ejecutaron las
sentencias de su mbito.
Cuando se crea una variable local dentro de una bloque, o dentro de una
funcin, el compilador reserva espacio para esa variable cada vez que se
llama a la funcin: mueve en cada ocasin hacia abajo el puntero de pila
tanto como sea preciso para volver a crear esa variable. Si existe un
valor inicial para la variable, la inicializacin se realiza cada vez que se
pasa por ese punto de la secuencia.
Si se quiere que el valor permanezca durante la ejecucin del programa
entero, y no slo cada vez que se entra de nuevo en el mbito de esa
variable, entonces tenemos dos posibilidades: La primera consiste en
crear esa variable como global, extendiendo su mbito al mbito de todo
el programa (en este caso la variable no queda bajo control del bloque
donde queramos ubicarla, o bajo control nico de la funcin que la
necesita, sino que es accesible (se puede leer y se puede variar su
valor) desde cualquier sentencia del programa); La segunda consiste en
crear una variable static dentro del bloque o funcin. El
almacenamiento de esa variable no se lleva a cabo en la pila sino en el
rea de datos estticos del programa. La variable slo se inicializa una
vez la primera vez que se llama a la funcin, y retiene su valor entre
diferentes invocaciones.
Veamos el siguiente ejemplo, donde tenemos dos variables locales que
sufren las mismas operaciones: una esttica (la variable que se ha
llamado a) y la otra no (la que se ha llamado b):
#i ncl ude <st di o. h>

void mai n( void)
{
for( long i = 0 ; i < 3 ; i ++)
for( long j = 0 ; j < 4 ; j ++)
{
static l ong a = 0;
long b = 0;

for( long j = 0 ; j < 5 ; j ++, a++, b++) ;
Fundamentos de informtica. Programacin en Lenguaje C


156
pr i nt f ( " a = %3l d. b = %3l d. \ n" , a, b) ;
}
}
El programa ofrece la siguiente salida por pantalla:
a = 5. b = 5.
a = 10. b = 5.
a = 15. b = 5.
a = 20. b = 5.
a = 25. b = 5.
a = 30. b = 5.
a = 35. b = 5.
a = 40. b = 5.
a = 45. b = 5.
a = 50. b = 5.
a = 55. b = 5.
a = 60. b = 5.

Variables en registro.
Cuando se declara una variable, se reserva un espacio de memoria para
almacenar sus sucesivos valores. Cul sea ese espacio de memoria es
cuestin que no podemos gobernar del todo. Especialmente, como ya se
ha dicho, no podemos decidir cules son las variables que deben
ubicarse en los espacios de registro.
Pero el compilador, al traducir el cdigo, puede detectar algunas
variables empleadas de forma recurrente, y decidir darle esa ubicacin
preferente. En ese caso, no es necesario traerla y llevarla de la ALU a la
memoria y de la memoria a la ALU cada vez que hay que operar con
ella.
El programador puede tomar parte en esa decisin, e indicar al
compilador que alguna o algunas variables conviene que se ubiquen en
los registros de la ALU. Eso se indica mediante la palabra clave
register.
Si al declarar una variable, se precede a toda la declaracin la palabra
register, entonces esa variable queda creada en un registro de la ALU.
Captulo 5. mbito y vida de las variables.


157
Una variable candidata a ser declarada register es, por ejemplo, las
que actan de contadoras en estructuras for.
Tambin puede ocurrir que no se desee que una variable sea
almacenada en un registro de la ALU. Y quiz se desea indicar al
compilador que, sea cual sea su opinin, una determinada variable no
debe ser almacenada all sino en la memoria, como una variable
cualquiera normal. Para evitar que el compilador decida otra cosa se le
indica con la palabra volatile.
El compilador tomas las indicaciones de register a ttulo orientativo. Si,
por ejemplo, se ha asignado el carcter de register a ms variables que
permite la capacidad de la ALU, entonces el compilador resuelve el
conflicto segn su criterio, sin abortar el proceso de compilacin.

Variables extern
Aunque estamos todava lejos de necesitar este tipo de declaracin,
presentamos ahora esta palabra clave de C, que hace referencia al
mbito de las variables.
El lenguaje C permite trocear un problema en diferentes mdulos que,
unidos, forman una aplicacin. Estos mdulos muchas veces sern
programas independientes que despus se compilan por separado y
finalmente se linkan o se juntan. Debe existir la forma de indicar, a
cada uno de esos programas desarrollados por separado, la existencia
de variables globales comunes para todos ellos. Variables cuyo mbito
trasciende el mbito del programa donde se declaran, porque abarcan
todos los programas que luego, linkados, darn lugar a la aplicacin
final.
Se podran declarar todas las variables en todos los archivos. C en la
compilacin de cada programa por separado no dara error, y asignara
tanta memoria como veces estuvieran declaradas. Pero en el enlazado
dara error de duplicidad.
Fundamentos de informtica. Programacin en Lenguaje C


158
Para evitar ese problema, las variable globales que deben permanecer
en todos o varios de los mdulos de un programa se declaran como
extern en todos esos mdulos excepto en uno, donde se declara como
variable global sin la palabra extern. Al compilar entonces esos
mdulos, no se crear la variable donde est puesta la palabra extern,
y permitir la compilacin al considerar que, en alguno de los mdulos
de linkado, esa variable s se crea. Evidentemente, si la palabra extern
se coloca en todos los mdulos, entonces en ninguno se crea la variable
y se producir error en el linkado.
El identificador de una variable declarada como extern es conveniente
que no tenga ms de seis caracteres, pues en los procesos de linkado de
mdulos slo los seis primeros caracteres sern significativos.

En resumen
mbito:
El mbito es el lugar del cdigo donde las sentencias pueden hacer uso
de una variable.
Una variable local queda declarada en el interior de un bloque. Puede
indicarse ese carcter de local al compilador mediante la palabra auto.
De todas formas, la ubicacin de la declaracin ofrece suficientes pistas
al compilador para saber de la localidad de cada variable. Su mbito
queda localizado nicamente a las instrucciones que quedan dentro del
bloque donde ha sido creada la variable.
Una variable es global cuando queda declarada fuera de cualquier
bloque del programa. Su mbito es todo el programa: cualquier
sentencia de cualquier funcin del programa puede hacer uso de esa
variable global.
Extensin:
Captulo 5. mbito y vida de las variables.


159
La extensin es el tiempo en que una variable est viva, es decir, en que
esa variable sigue existiendo en la memoria.
Una variable global debe existir mientras el programa est en marcha,
puesto que cualquier sentencia del programa puede hacer uso de ella.
Una variable local slo existe en el intervalo de tiempo transcurrido
desde la ejecucin de la primera sentencia del bloque donde se ha
creado esa variable y hasta que se sale de ese bloque. Es, tras la
ejecucin de la ltima sentencia del bloque, el momento en que esa
variable desaparece. Si el bloque vuelve a ejecutarse entonces vuelve a
crearse una variable con su mismo nombre, que se ubicar donde antes,
o en otra direccin de memoria diferente: es, en todo caso, una variable
diferente a la anterior.
Se puede forzar a que una variable local exista durante toda la ejecucin
del programa. Eso puede hacerse mediante la palabra reservada de C
static. En ese caso, al terminar la ejecucin de la ltima instruccin del
bloque donde est creada, la variable no desaparece. De todas formas,
mientras no se vuelva a las sentencias de ese bloque, esa variable no
podr ser reutilizada, porque fuera de ese bloque, an estando viva,
est fuera de su mbito.
Ubicacin: Podemos indicar al compilador si queremos que una variable
sea creada en los registros de la ALU, utilizando la palabra reservada
register. Podemos indicarla tambin al compilador que una variable no
se cree en esos registros, mediante la palabra reservada volatile. Fuera
de esas indicaciones que da el programador, el compilador puede decidir
qu variables se crean en la ALU y cules en la memoria principal.
No se ha dicho nada en este captulo sobre la creacin de espacios de
memoria con valores constantes. Ya se present la forma de hacerlo en
el captulo 2 sobre tipos de datos y variables en C. Una variable
declarada como const quedar almacenada en el espacio de memoria
de las instrucciones. No se puede modificar (mediante el operador
Fundamentos de informtica. Programacin en Lenguaje C


160
asignacin) el valor de una variable definida como const. Por eso, al
crear una variable de esta forma hay que asignarle valor en su
declaracin.

Ejercicios

40. Haga un programa que calcule el mximo comn divisor de dos
enteros que el usuario introduzca por consola. El usuario podr
hacer tantos clculos como quiera, e interrumpir la bsqueda
de nuevos mximos comunes divisores cuando introduzca un
par de ceros.
El programa mostrar por pantalla, cada vez que ej ecute el
cdigo del bucle, un valor contador que se incrementa y que
indica cuntas veces se est ej ecutando a lo largo de toda la
aplicacin. Esa variable contador ser declarada como static.

#i ncl ude <st di o. h>
void mai n( void)
{
unsigned short a, b, mcd;
do
{
pr i nt f ( " Val or de a . . . " ) ;
scanf ( " %hu" , &a) ;
pr i nt f ( " Val or de b . . . " ) ;
scanf ( " %hu" , &b) ;
if( a == 0 && b == 0) br eak;
while( b)
{
static unsigned short cont = 0;
mcd = b;
b = a %b;
a = mcd;
cont ++;
pr i nt f ( " \ ncont = %hu" , cont ) ; }
pr i nt f ( " \ n\ nEl mcd es %hu. " , mcd) ;
}while( 1) ;
Captulo 5. mbito y vida de las variables.


161
}
Cada vez que se ejecuta el bloque de la estructura do while se
incrementa en uno la variable cont. Esta variable se inicializa a cero
nicamente la primera vez que se ejecuta la sentencia while de clculo
del mximo comn divisor.
Observacin: quiz podra ser interesante, que al terminar de ejecutar
todos los clculos que desee el usuario, entonces se mostrara por
pantalla el nmero de veces que se ha entrado en el bucle. Pero eso no
es posible tal y como est el cdigo, puesto que fuera del mbito de la
estructura while que controla el clculo del mximo comn divisor, la
variable cont, sigue viva, pero estamos fuera de mbito y el compilador
no reconoce ese identificador como variable existente.

Fundamentos de informtica. Programacin en Lenguaje C


162








CAPTULO 6

ARRAYS NUMRICOS: VECTORES Y
MATRICES

Hasta el momento hemos trabajado con variables, declaradas una a una
en la medida en que nos han sido necesarias. Pero pudiera ocurrir que
necesitsemos un bloque de variables grande, por ejemplo para definir
los valores de una matriz numrica, o para almacenar los distintos
valores obtenidos en un proceso de clculo del que se obtienen
numerosos resultados del mismo tipo. Supongamos, por ejemplo, que
deseamos hacer un programa que ordene mil valores enteros: habr
que declarar entonces mil variables, todas ellas del mismo tipo, y todas
ellas con un nombre diferente.
Es posible hacer una declaracin conjunta de un grupo de variables. Eso
se realiza cuando todas esas variables son del mismo tipo. Esa es,
intuitivamente, la nocin del array. Y ese es el concepto que
introducimos en el presente captulo
Fundamentos de informtica. Programacin en Lenguaje C


164

Nocin y declaracin de array.
Un array (tambin llamado vector) es una coleccin de variables del
mismo tipo todas ellas referenciadas con un nombre comn.
La sintaxis para la declaracin de un vector es la siguiente:
tipo nombre_ vector[ dimensin] ;
Donde tipo define el tipo de dato de todas las variables creadas, y
dimensin es un literal que indica cuntas variables de ese tipo se deben
crear. En ningn caso est permitido introducir el valor de la dimensin
mediante una variable. El compilador reserva el espacio necesario para
almacenar, de forma contigua, tantas variables como indique el literal
dimensin: reservar, pues, tantos bytes como requiera una de esas
variables, multiplicado por el nmero de variables a crear.
Por ejemplo, la sentencia short int mi _vect or [ 1000] ; reserva dos
mil bytes de memoria consecutivos para poder codificar mil variables de
tipo short.
Cada una de las variables de un array tiene un comportamiento
completamente independiente de las dems. Su nica relacin con todas
las otras variables del array es que estn situadas todas ellas de forma
correlativa en la memoria. Cada variable tiene su propio modo de ser
llamada: desde nombre_vector[0] hasta nombre_vector[dimensin 1].
En el ejemplo anterior, tendremos 1000 variables que van desde
mi_vector[0] hasta mi_vector[999].
C no comprueba los lmites del vector. Es responsabilidad del
programador asegurar que no se accede a otras posiciones de memoria
contiguas del vector. Por ejemplo, si hacemos referencia al elemento
mi_vector[1000], el compilador no dar como errneo ese nombre,
aunque de hecho no exista tal variable.
Captulo 6. Arrays numricos: vectores y matrices.


165
La variable mi_vector[0] est posicionada en una direccin de memoria
cualquiera. La variable mi_vector[1] est situada dos bytes ms
adelante, porque mi_vector es un array de tipo short y por tanto
mi_vector[0] ocupa 2 bytes (como todos los dems elementos del
vector), y porque mi_vector[1] es consecutiva en la memoria, a
mi_vector[0]. Esa sucesin de ubicaciones sigue en adelante, y la
variable mi_vector[999] estar 1998 bytes por encima de la posicin de
mi_vector[0]. Si hacemos referencia a la variable mi_vector[1000]
entonces el compilador considera la posicin de memoria situada 2000
bytes por encima de la posicin de mi_vector[0]. Y de all tomar valor o
escribir valor si as se lo indicamos. Pero realmente, en esa posicin el
ordenador no tiene reservado espacio para esta variable, y no sabemos
qu estaremos realmente leyendo o modificando. Este tipo de errores
son muy graves y a veces no se detectan hasta despus de varias
ejecuciones.
El recorrido del vector se puede hacer mediante ndices. Por ejemplo:
short mi _vect or [ 1000] , i ;
for( i = 0 ; i < 1000 ; i ++) mi _vect or [ i ] = 0;
Este cdigo recorre todo el vector e inicializa a cero todas y cada una de
sus variables.
Tngase cuidado, por ejemplo, con el recorrido del vector, que va desde
el elemento 0 hasta el elemento dimensin 1. Un error habitual es
escribir el siguiente cdigo:
short mi _vect or [ 1000] , i ;
for( i = 0 ; i <= 1000 ; i ++) mi _vect or [ i ] = 0;
Donde se har referencia a la posicin 1000 del vector, que no es vlida.
Existe otro modo de inicializar los valores de un vector o array, sin
necesidad de recorrerlo con un ndice. Se puede emplear para ello el
operador asignacin, dando, entre llaves y separados por comas, tantos
valores como dimensin tenga el vector. Por ejemplo;
short mi _vect or [ 10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
Fundamentos de informtica. Programacin en Lenguaje C


166
que es equivalente al siguiente cdigo:
short mi _vect or [ 10] , i ;
for( i = 0 ; i < 10 ; i ++) mi _vect or [ i ] = 10 * ( i + 1) ;
Cuando se inicializa un vector mediante el operador asignacin en su
declaracin, como hay que introducir entre llaves tantos valores como
sea la dimensin del vector creado, es redundante indicar la dimensin
entre corchetes y tambin en el cardinal del conjunto de valores
asignado. Por eso, se puede declarar ese vector sin especificar el
nmero de variables que se deben crear. Por ejemplo:
short mi _vect or [ ] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
Por lo dems, estas variables son exactamente iguales que todas las
vistas hasta el momento. Tambin ellas pueden ser declaradas globales
o locales, o static, o extern.

Nocin y declaracin de array de dimensin
mltiple, o matrices.
Es posible definir arrays de ms de una dimensin. El comportamiento
de esas variables vuelve a ser conceptualmente muy sencillo. La sintaxis
de esa declaracin es la siguiente:
tipo nombre_ matriz[ dim_ 1] [ dim_ 2] [ dim_ N] ;
Donde los valores de las dimensiones son todos ellos literales.
Por ejemplo podemos crear una matriz tres por tres:
float mat r i z[ 3] [ 3] ;
que reserva 9 bloques de cuatro bytes cada uno para poder almacenar
valores tipo float. Esas variables se llaman tambin con ndices, en este
caso dos ndices (uno para cada dimensin) que van desde el 0 hasta el
valor de cada dimensin menos uno.
Por ejemplo:
long matriz[ 5] [ 2] , i , j ;
Captulo 6. Arrays numricos: vectores y matrices.


167
for( i = 0 ; i < 5 ; i ++)
for( j = 0 ; j < 2 ; j ++)
mat r i z[ i ] [ j ] = 0;
donde tenemos una matriz de cinco filas y dos columnas, toda ella con
los valores iniciales a cero. Tambin se puede inicializar la matriz
mediante el operador asignacin y llaves. En este caso se hara lo
siguiente:
long int mat r i z[ 5] [ 2] = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};
que es lo mismo que escribir
long matriz[ 5] [ 2] , i , j , k;
for( i = 0 , k = 1; i < 5 ; i ++)
for( j = 0 ; j < 2 ; j ++)
{
mat r i z[ i ] [ j ] = k;
k++;
}
Y de nuevo hay que estar muy vigilante para no sobrepasar, al utilizar
los ndices, la dimensin de la matriz. Para comprender mejor cmo se
distribuyen las variables en la memoria, y el peligro de equivocarse en
los ndices que recorren la matriz, veamos el siguiente programa que
crea una matriz de 2 por 5 y muestra por pantalla la direccin de cada
uno de los 10 elementos:
#i ncl ude <st di o. h>
void mai n( void)
{
char m[ 2] [ 5] ;
short i , j ;
for( i = 0 ; i < 2 ; i ++)
for( j = 0 ; j < 5 ; j ++)
pr i nt f ( " &m[ %hd] [ %hd] = %p\ n" , i , j , &m[ i ] [ j ] ) ;
}
La salida por pantalla ha sido esta:
&m[ 0] [ 0] = 0012FF80
&m[ 0] [ 1] = 0012FF81
&m[ 0] [ 2] = 0012FF82
&m[ 0] [ 3] = 0012FF83
&m[ 0] [ 4] = 0012FF84
&m[ 1] [ 0] = 0012FF85
&m[ 1] [ 1] = 0012FF86
Fundamentos de informtica. Programacin en Lenguaje C


168
&m[ 1] [ 2] = 0012FF87
&m[ 1] [ 3] = 0012FF88
&m[ 1] [ 4] = 0012FF89
Donde vemos que los elementos van ordenados desde el m[0][0] hasta
el m[0][4], y a continuacin el m[1][0]: cuando termina la primera fila
comienza la segunda fila.
Si ahora, por equivocacin, escribiramos el siguiente cdigo
#i ncl ude <st di o. h>
void mai n( void)
{
char m[ 2] [ 5] ;
short i , j ;
for( i = 0 ; i < 2 ; i ++)
for( j = 0 ; j < 6 ; j ++)
pr i nt f ( " &m[ %hd] [ %hd] = %p\ n" , i , j , &m[ i ] [ j ] ) ;
}
(donde, como se ve, el ndice j recorre hasta el valor 5, y no slo hasta
el valor 4) tendramos la siguiente salida por pantalla:
&m[ 0] [ 0] = 0012FF80
&m[ 0] [ 1] = 0012FF81
&m[ 0] [ 2] = 0012FF82
&m[ 0] [ 3] = 0012FF83
&m[ 0] [ 4] = 0012FF84
&m[0][5] = 0012FF85
&m[ 1] [ 0] = 0012FF85
&m[ 1] [ 1] = 0012FF86
&m[ 1] [ 2] = 0012FF87
&m[ 1] [ 3] = 0012FF88
&m[ 1] [ 4] = 0012FF89
&m[1][5] = 0012FF8A
Donde el compilador se ha tragado que la matriz tiene el elemento
m[0][5] y el m[1][5]. No sabemos qu habr en la posicin de la
variable no existente m[1][5]. S sabemos en cambio qu hay en la
m[0][5]: si vemos la lista de la salida, tenemos que el compilador
considera que la variable m[0][5] estar a continuacin de la m[0][4].
Pero por otro lado, ella sabe que la segunda fila comienza en m[1][0] y
sabe dnde est ubicada. Si comparamos &m[0][5] y &m[1][0] veremos
que a ambos se les supone la misma direccin. Y es que m[0][5] no
ocupa lugar porque no existe. Pero cuando se escriba en el cdigo
Captulo 6. Arrays numricos: vectores y matrices.


169
m[0][5] = 0;
lo que estaremos poniendo a cero, quiz sin saberlo, es la variable
m[1][0].
Conclusin: mucho cuidado con los ndices al recorrer matrices y
vectores.

Ejercicios

41. Escriba el cdigo necesario para crear una matriz identidad
( todos sus valores a cero, excepto la diagonal principal) de
dimensin 3.

short i dent i dad[ 3] [ 3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
Otra forma de solventarlo:
short i dent i dad[ 3] [ 3] , i , j ;
for( i = 0 ; i < 3 ; i ++)
for( j = 0 ; j < 3 ; j ++)
i dent i dad[ i ] [ j ] = i == j ? 1 : 0;
El operador ?: se present al hablar de las estructuras de control
condicionales. Como el lenguaje C devuelve el valor 1 cuando una
expresin se evala como verdadera, hubiera bastando con que la
ltima lnea del cdigo presentado fuese
i dent i dad[ i ] [ j ] = i == j ;
Para mostrar la matriz por pantalla el cdigo es siempre ms o menos el
mismo:
for( i = 0 ; i < 3 ; i ++)
{
pr i nt f ( " \ n\ n" ) ;
for( j = 0 ; j < 3 ; j ++)
pr i nt f ( " %5hd" , i dent i dad[ i ] [ j ] ) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


170
42. Escriba un programa que solicite al usuario los valores de una
matriz de tres por tres y muestre por pantalla la traspuesta de
esa matriz introducida.

#i ncl ude <st di o. h>
void mai n( void)
{
short mat r i z[ 3] [ 3] ;
short i , j ;
for( i = 0 ; i < 3 ; i ++)
for( j = 0 ; j < 3 ; j ++)
{
pr i nt f ( " mat r i z[ %hd] [ %hd] = " , i , j ) ;
scanf ( " %hd" , &mat r i z[ i ] [ j ] ) ;
}
for( i = 0 ; i < 3 ; i ++)
{
pr i nt f ( " \ n\ n" ) ;
for( j = 0 ; j < 3 ; j ++)
pr i nt f ( " %5hd" , mat r i z[ i ] [ j ] ) ;
}
pr i nt f ( " \ n\ n\ n" ) ;
for( i = 0 ; i < 3 ; i ++)
{
pr i nt f ( " \ n\ n" ) ;
for( j = 0 ; j < 3 ; j ++)
pr i nt f ( " %5hd" , mat r i z[ j ] [ i ] ) ;
}
}
Primero muestra la matriz tal y como la ha introducido el usuario, y ms
abajo muestra su traspuesta.

43. Escriba un programa que solicite al usuario los valores de dos
matrices de tres por tres y muestre por pantalla cada una de
ellas, una al lado de la otra, y su suma, y cada una de ellas, una
al lado de la otra, y su producto.


Captulo 6. Arrays numricos: vectores y matrices.


171
#def i ne TAM 3
#i ncl ude <st di o. h>
void mai n( void)
{
short a[ TAM] [ TAM] ;
short b[ TAM] [ TAM] ;
short s[ TAM] [ TAM] ;
short p[ TAM] [ TAM] ;
short i , j , k;
/ / Ent r ada mat r i z a.
for( i = 0 ; i < TAM ; i ++)
for( j = 0 ; j < TAM ; j ++)
{
pr i nt f ( " a[ %hd] [ %hd] = " , i , j ) ;
scanf ( " %hd" , &a[ i ] [ j ] ) ;
}
/ / Ent r ada mat r i z b.
for( i = 0 ; i < TAM ; i ++)
for( j = 0 ; j < TAM ; j ++)
{
pr i nt f ( " b[ %hd] [ %hd] = " , i , j ) ;
scanf ( " %hd" , &b[ i ] [ j ] ) ;
}
/ / Cl cul o Suma.
for( i = 0 ; i < TAM ; i ++)
for( j = 0 ; j < TAM ; j ++)
s[ i ] [ j ] = a[ i ] [ j ] + b[ i ] [ j ] ;
/ / Cl cul o Pr oduct o.
/ / p[ i ] [ j ] =a[ i ] [ 0] *b[ 0] [ j ] +a[ i ] [ 1] *b[ 1] [ j ] +a[ i ] [ 2] *b[ 2] [ j ]
for( i = 0 ; i < TAM ; i ++)
for( j = 0 ; j < TAM ; j ++)
{
p[ i ] [ j ] = 0;
for( k = 0 ; k < TAM ; k++)
p[ i ] [ j ] += a[ i ] [ k] * b[ k] [ j ] ;
}
/ / Most r ar r esul t ados.
/ / SUMA
for( i = 0 ; i < TAM ; i ++)
{
pr i nt f ( " \ n\ n" ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , a[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , b[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , s[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


172
/ / PRODUCTO
pr i nt f ( " \ n\ n\ n" ) ;
for( i = 0 ; i < TAM ; i ++)
{
pr i nt f ( " \ n\ n" ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , a[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , b[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
for( j = 0 ; j < TAM ; j ++)
pr i nt f ( " %4hd" , p[ i ] [ j ] ) ;
pr i nt f ( " \ t " ) ;
}
}
En el manejo de matrices y vectores es frecuente utilizar siempre, como
estructura de control de iteracin, la opcin for. Y es que tiene una
sintaxis que permite manipular muy bien las iteraciones que tienen un
nmero prefijado de repeticiones. Ms, en el caso de las matrices, que
las mismas variables de control de la estructura son las que sirven para
los ndices que recorre la matriz.

44. Escriba un programa que solicite al usuario un conj unto de
valores ( tantos como quiera el usuario) y que al final, ordene
esos valores de menor a mayor. El usuario termina su entrada
de datos cuando introduzca el cero.

#i ncl ude <st di o. h>
void mai n( void)
{
short dat os[ 1000] ;
short i , j , nn;
/ / I nt r oducci n de dat os.
i = 0;
do
{
pr i nt f ( " Ent ada de nuevo dat o . . . " ) ;
scanf ( " %hi " , &dat os[ i ] ) ;
i ++;
Captulo 6. Arrays numricos: vectores y matrices.


173
}while( dat os[ i - 1] ! = 0 && i < 1000) ;
nn = i - 1; / / Tot al de dat os vl i dos i nt r oduci dos.
/ / Or denar dat os
for( i = 0 ; i <= nn ; i ++)
for( j = i + 1 ; j < nn ; j ++)
if( dat os[ i ] > dat os[ j ] )
{
dat os[ i ] ^= dat os[ j ] ;
dat os[ j ] ^= dat os[ i ] ;
dat os[ i ] ^= dat os[ j ] ;
}
/ / Most r ar dat os or denados por pant al l a
pr i nt f ( " \ n\ n" ) ;
for( i = 0 ; i < nn ; i ++)
pr i nt f ( " %l i < " , dat os[ i ] ) ;
pr i nt f ( " \ b\ b " ) ;
}
Introduccin de datos: va solicitando uno a uno todos los datos,
mediante una estructura de control do while. La entrada de datos
termina cuando la condicin es falsa: o cuando se haya introducido un
cero o cuando se hayan introducido tantos valores como enteros se han
creado en el vector. Se habrn introducido tantos datos como indique el
valor de la variable i, donde hay que tener en cuenta que ha sufrido un
incremento tambin cuando se ha introducido el cero, y ese ltimo valor
no nos interesa. Por eso ponemos la variable nn al valor i 1.
Ordenar datos: Tiene una forma parecida a la que se present para la
ordenacin de cuatro enteros (en el tema de las estructuras de control),
pero ahora para una cantidad desconocida para el programador
(recogida en la variable nn). Por eso se deben recorrer todos los valores
mediante una estructura de iteracin, y no como en el ejemplo de los
cuatro valores que adems no estaban almacenados en vectores, y por
lo tanto no se poda recorrer los distintos valores mediante ndices. Los
datos quedan almacenados en el propio vector, de menor a mayor.
Mostrar datos: Se va recorriendo el vector desde el principio hasta el
valor nn: esos son los elementos del vector que almacenan datos
introducidos por el usuario.

Fundamentos de informtica. Programacin en Lenguaje C


174
45. Escribir un programa que solicite al usuario un entero positivo
e indique si ese nmero introducido es primo o compuesto.
Adems, si el nmero es compuesto, deber guardar todos sus
divisores y mostrarlos por pantalla.

#i ncl ude <st di o. h>
#def i ne TAM 1000
void mai n( void)
{
unsigned long int numer o, mi t ad;
unsigned long int i ;
unsigned long int di v;
unsigned long int D[ TAM] ;
for( i = 0 ; i < TAM ; i ++) D[ i ] = 0;
D[ 0] = 1;
pr i nt f ( " Numer o que vamos a t est ear . . . " ) ;
scanf ( " %l u" , &numer o) ;
mi t ad = numer o / 2;
for( i = 1 , di v = 2 ; di v <= mi t ad ; di v++)
{
if( numer o %di v == 0)
{
D[ i ] = di v;
i ++;
if( i == TAM)
{
pr i nt f ( " Vect or mal di mensi onado. " ) ;
break;
}
}
}
if( i < TAM) D[ i ] = numer o;
if( i == 1) pr i nt f ( " \ n%l u es PRI MO. \ n" , numer o) ;
else
{
pr i nt f ( " \ n%l u es COMPUESTO. " , numer o) ;
pr i nt f ( " Sus di vi sor es son: \ n\ n" ) ;
for( i = 0 ; i < TAM && D[ i ] ! = 0; i ++)
pr i nt f ( " \ n%l u" , D[ i ] ) ;
}
}
Este programa es semejante a uno presentado en el captulo de las
estructuras de control. All se comenta el diseo de este cdigo. Ahora
aadimos que, cada vez que se encuentra un divisor, se almacena en
Captulo 6. Arrays numricos: vectores y matrices.


175
una posicin del vector D y se incrementa el ndice del vector (variable
i). Se inicia el contador al valor 1 porque a la posicin 0 del vector ya se
le ha asignado el valor 1.
La variable i hace de centinela y de chivato. Si despus de buscar todos
los divisores la variable i est al valor 1, entonces es seal de que no se
ha encontrado ningn divisor distinto del 1 y del mismo nmero, y por
tanto ese nmero es primo.
Para la dimensin del vector se utiliza una constante definida con la
directiva de procesador define. Si se desea cambiar ese valor, no ser
necesario revisar todo el cdigo en busca de las referencias a los lmites
de la matriz, sino que todo el cdigo est ya escrito sobre ese valor
prefijado. Basta cambiar el valor definido en la directiva para que se
modifiquen todas las referencias al tamao del vector.

46. Escribir un programa que defina un array de short de 32
elementos, y que almacene en cada uno de ellos los sucesivos
dgitos binarios de un entero largo introducido por pantalla.
Luego, una vez obtenidos todos los dgitos, el programa
mostrar esos dgitos.

Un posible cdigo que da solucin a este programa podra er el
siguiente:
#i ncl ude <st di o. h>

void mai n( voi d)
{
signed long N;
unsigned short bi t s[ 32] , i ;
unsigned long Test ;

do
{
pr i nt f ( " \ n\ nI nt r oduce un ent er o . . . " ) ;
Fundamentos de informtica. Programacin en Lenguaje C


176
scanf ( " %l i " , &N) ;

if( N == 0) br eak;

for( i =0, Test = 0x80000000 ; Test ; Test >>=1, i ++)
bi t s[ i ] = Test & N ? 1 : 0;

pr i nt f ( " \ nCodi f i caci n bi nar i a i nt er na . . . " ) ;
for( i = 0 ; i < si zeof ( l ong) * 8 ; i ++)
pr i nt f ( " %hu" , bi t s[ i ] ) ;

}while( 1) ;
}
Este cdigo permite introducir tantos enteros como quiera el usuario.
Cuando el usuario introduzca el valor cero entonces se termina la
ejecucin del programa. Ya qued explicado el funcionamiento de este
algoritmo en un tema anterior. Ahora simplemente hemos introducido la
posibilidad de que se almacenen los dgitos binarios en un array.
Una posible salida por pantalla de este programa sera la siguiente:
Introduce un entero ... 12
Codificacin binaria interna ... 00000000000000000000000000001100
Introduce un entero ... -12
Codificacin binaria interna ... 11111111111111111111111111110100
Introduce un entero ... 0

47. Un cuadro mgico es un reticulado de n filas y n columnas que
tiene la propiedad de que todas sus filas, y todas sus columnas,
y las diagonales principales, suman el mismo valor. Por
ej emplo:
6 1 8
7 5 3
2 9 4
La tcnica que se utiliza para generar cuadros mgicos
( que tienen siempre una dimensin impar: impar nmero
Captulo 6. Arrays numricos: vectores y matrices.


177
de filas y de columnas) es la siguiente:
a. Se comienza fij ando el entero 1 en el espacio central de
la primera fila.
b. Se van escribiendo los sucesivos nmeros ( 2, 3, ...)
sucesivamente, en las casillas localizadas una fila
arriba y una columna a la izquierda. Estos
desplazamientos se realizan tratando a la matriz como
si estuviera envuelta sobre s misma, de forma que
moverse una posicin hacia arriba desde la fila superior
lleva a la fila inferior, y moverse una posicin a la
izquierda desde la primera columna lleva a la columna
ms a la derecha del cuadro.
c. Si se llega a una posicin ya ocupada ( es decir, si
arriba a la izquierda ya est ocupado con un nmero
anterior) , entonces la posicin a rellenar cambia, que
ahora ser la inmediatamente debaj o de la ltima
casilla rellenada. Despus se contina el proceso tal y
como se ha descrito en el punto anterior.
Escriba un programa que genere el cuadro mgico de la
dimensin que el usuario desee, y lo muestre luego por
pantalla..

Para llegar a una solucin para este programa ofrecemos el flujograma
desglosado en partes. Est recogido en la figura 6.1. Con l se puede
implementar fcilmente el cdigo que imprima el cuadro mgico. El
primer paso (que el usuario introduzca el valor de la dimensin de la
matriz cuadrada) debera hacerse de tal manera que slo se acepta un
valor que sea impar; en caso contrario, el programa vuelve a solicitar
una dimensin: y as hasta que el usuario acierta a introducir un valor
impar.
Fundamentos de informtica. Programacin en Lenguaje C


178
Aparte del cdigo que cada uno pueda escribir de la mano del
flujograma, ofrecemos ahora otro que agiliza de forma notable la
bsqueda de la siguiente posicin del cuadro donde se ha de colocar el
siguiente valor de numero. En el cdigo se ha definido una macro
mediante la directiva #define. No es trivial verlo a la primera, pero
ayuda el ejemplo a comprender que a veces un cdigo bien pensado
facilita su comprensin.
El cdigo es el siguiente:
#i ncl ude <st di o. h>
#def i ne l r ( x, N) ( ( x) < 0 ? N+( x) %N : ( ( x) >=N ? ( x) %N : ( x) ) )

void mai n( void)
{
unsigned short magi co[ 17] [ 17] ;
unsigned short f i l , col , di m, num;

do
{
pr i nt f ( " \ nDi mensi n ( i mpar ent r e 3 y 17 ) : " ) ;
scanf ( " %hu" , &di m) ;
}while( di m%2 == 0) ;

pr i nt f ( " \ nCuadr o Mgi co de di mensi n %hu: \ n\ n" , di m) ;

/ / I ni ci al i zamos l a mat r i z a cer o

for( f i l = 0 ; f i l < di m; f i l ++)
for( col = 0 ; col < di m; col ++)
magi co[ f i l ] [ col ] = 0;

/ / Al gor i t mo de asi gnaci n de val or es. . .

for( f i l = di m/ 2 , col = 0 , num= 1 ; num< di m*di m; )
{
if( magi co[ f i l ] [ col ] == 0)
{
magi co[ f i l ] [ col ] = num++;
f i l = l r ( f i l + 1, di m) ;
col = l r ( col - 1, di m) ;
}
else
{
f i l = l r ( f i l - 1, di m) ;
col = l r ( col + 2, di m) ;
}
}
Captulo 6. Arrays numricos: vectores y matrices.


179

/ / Most r amos ahor a el cuadr ado mgi co por pant al l a

for( f i l = 0 ; f i l < di m; f i l ++)
{
pr i nt f ( " \ n\ n" ) ;
for( col = 0 ; col < di m; col ++)
pr i nt f ( " %5hu" , magi co[ f i l ] [ col ] ) ;
}
}




Fundamentos de informtica. Programacin en Lenguaje C


180

C
F
dim
Inicializar matriz
M
Rellenar matriz
M
Mostrar matriz
M
Proceso general Iniciar Matriz M
Rellenar Matriz M
Mostrar Matriz M
C
0 fil
C1
< 1 C fil dim
F
0 col
C2
< 2 C col dim
[ ][ ] 0 M fil col
S No
No S
C
0 fil
C1
< 1 C fil dim
F
0 col
C2
< 2 C col dim
S No
No S
[ ][ ] M fil col
Salto de lnea
C 2 fil dim
0 col
[ ][ ] 1 M fil col
2 numero
Buscar siguiente
posicin ( , ) fil col
[ , ] M fil col numero
+ 1 numero numero
C1
S No
F
<
2
1 C numero dim
C posf fil posc col
C1
S No
= 1 0 C fil
1 fil dim 1 fil fil
C2
S No
= 2 0 C col
1 col dim 1 col col
C3
No Si
3 [ ][ ] 0 C M fil col
+ 1 fil posf + 1 col posc [ ][ ] M fil col numero
F
Buscar siguiente
posicin ( , ) fil col
Figura 6.1.: Flujograma para la implementacin del cuadro mgico.







CAPTULO 7

CARACTERES Y CADENAS DE
CARACTERES

Ya hemos visto que un carcter se codifica en C mediante el tipo de dato
char. Es una posicin de memoria (la ms pequea que se puede
reservar en C: un byte) que codifica cada carcter segn un cdigo
arbitrario. El ms extendido en el mundo del PC y en programacin con
lenguaje C es el cdigo ASCII.
Hasta el momento, cuando hemos hablado de los operadores de una
variable char, hemos hecho referencia al uso de esa variable (que no se
ha recomendado) como entero de pequeo rango o dominio. Pero donde
realmente tiene uso este tipo de dato es en el manejo de caracteres y
en el de vectores o arrays declarados de este tipo.
A un array de tipo char se le suele llamar cadena de caracteres.
En este captulo vamos a ver las operaciones bsicas que se pueden
realizar con caracteres y con cadenas. No hablamos de operadores,
Fundamentos de informtica. Programacin en Lenguaje C


182
porque estos ya se han visto antes, en un captulo previo, y se reducen
a los aritmticos, lgicos, relacionales y a nivel de bit. Hablamos de
operaciones habituales cuando se manejan caracteres y sus cadenas:
operaciones que estn definidas como funciones en algunas bibliotecas
del ANSI C y que presentaremos en este captulo.

Operaciones con caracteres.
La biblioteca ctype.h contiene abundantes funciones para la
manipulacin de caracteres. En la ayuda on line que ofrece cualquier
editor de C se pueden encontrar indicaciones concretas y prcticas para
el uso de cada una de ellas.
La biblioteca ctype.h define funciones de manejo de caracteres de
forma individual, no concatenados formando cadenas.
Lo ms indicado ser ir viendo cada una de esas funciones y explicar
qu operacin realiza sobre el carcter y qu valores devuelve.
int isalnum( int c) ;
Recibe el cdigo ASCII de una carcter y devuelve el valor 1 si el
carcter que corresponde a ese cdigo ASCII es una letra o un dgito
numrico; en caso contrario devuelve un 0.
Ejemplo de uso:
if( i sal num( @ ) ) pr i nt f ( Al f anumr i co) :
else pr i nt f ( No al f anumr i co) ;
As podemos saber si el carcter @ es considerado alfabtico o
numrico. La respuesta ser que no lo es. Evidentemente, para hacer
uso de esta funcin y de todas las que se van a ver en este epgrafe,
hay que indicar al compilador la biblioteca en donde se encuentran
definidas estas bibliotecas: #define <ctype.h>.
int isalpha( int c) ;
Captulo 7. Caracteres y cadenas de caracteres.


183
Recibe el cdigo ASCII de una carcter y devuelve el valor 1 si el
carcter que corresponde a ese cdigo ASCII es una letra; en caso
contrario devuelve un 0.
Ejemplo de uso:
if( i sal num( 2 ) ) pr i nt f ( Al f abt i co) :
else pr i nt f ( No al f abt i co) ;
La respuesta ser que 2 no es alfabtico.
int iscntrl( int c) ;
Recibe el cdigo ASCII de una carcter y devuelve el valor 1 si el
carcter que corresponde a ese cdigo ASCII es el carcter borrado o un
carcter de control (ASCII entre 0 y 1F, y el 7F, en hexadecimal); en
caso contrario devuelve un 0.
Ejemplo de uso:
if( i scnt r l ( \ n ) ) pr i nt f ( Car ct er de cont r ol ) :
else pr i nt f ( No car ct er de cont r ol ) ;
La respuesta ser que el salto de lnea s es carcter de control.
Resumidamente ya, presentamos el resto de funciones de esta
biblioteca. Todas ellas reciben como parmetro el cdigo ASCII de un
carcter.
int isdigit( int c) ; Devuelve el valor 1 si el carcter es un dgito; en
caso contrario devuelve un 0.
int isgraph( int c) ; Devuelve el valor 1 si el carcter es un carcter
imprimible; en caso contrario devuelve un 0.
int isascii( int c) ; Devuelve el valor 1 si el cdigo ASCII del carcter es
menor de 128; en caso contrario devuelve un 0.
int islower( int c) ; Devuelve el valor 1 si el carcter es una letra
minscula; en caso contrario devuelve un 0.
int ispunct( int c) ; Devuelve el valor 1 si el carcter es signo de
puntuacin; en caso contrario devuelve un 0.
Fundamentos de informtica. Programacin en Lenguaje C


184
int isspace( int c) ; Devuelve el valor 1 si el carcter es el espacio en
blanco, tabulador vertical u horizontal, retorno de carro, salto de lnea, u
otro carcter de significado espacial en un texto; en caso contrario
devuelve un 0.
int isupper( int c) ; Devuelve el valor 1 si el carcter es una letra
mayscula; en caso contrario devuelve un 0.
int isxdigit( int c) ; Devuelve el valor 1 si el carcter es un dgito
hexadecimal (del 0 al 9, de la a a la f de la A a la F); en caso
contrario devuelve un 0.
int tolower( int ch) ; Si el carcter recibido es una letra mayscula,
devuelve el ASCII de su correspondiente minscula; en caso contrario
devuelve el mismo cdigo ASCII que recibi como entrada.
int toupper( int ch) ; Si el carcter recibido es una letra minscula,
devuelve el ASCII de su correspondiente mayscula; en caso contrario
devuelve el mismo cdigo ASCII que recibi como entrada.
Con estas funciones definidas se pueden trabajar muy bien muchas
operaciones que se pueden realizar con caracteres. Por ejemplo, veamos
un programa que solicita caracteres por consola, hasta que recibe el
carcter salto de lnea, y que nicamente muestra por pantalla los
caracteres introducidos que sean letras. Al final indicar cuntas veces
han sido pulsadas cada una de las cinco vocales.
#i ncl ude <st di o. h>
#i ncl ude <ct ype. h>
void mai n( void)
{
unsigned short int a = 0, e = 0, i = 0, o = 0, u = 0;
char ch;
do
{
ch = get char ( ) ;
if( i sal pha( ch) )
{
if( ch == ' a' ) a++;
else if( ch == ' e' ) e++;
else if( ch == ' i ' ) i ++;
else if( ch == ' o' ) o++;
Captulo 7. Caracteres y cadenas de caracteres.


185
else if( ch == ' u' ) u++;
}
else
pr i nt f ( " \ b " ) ;
}while( ch ! = 10) ;
pr i nt f ( " \ n\ nVocal a . . . %hu" , a) ;
pr i nt f ( " \ nVocal e . . . %hu" , e) ;
pr i nt f ( " \ nVocal i . . . %hu" , i ) ;
pr i nt f ( " \ nVocal o . . . %hu" , o) ;
pr i nt f ( " \ nVocal u . . . %hu" , u) ;
}
Si el carcter introducido es alfabtico, entonces simplemente verifica si
es una vocal y aumenta el contador particular para cada vocal. Si no lo
es, entonces imprime en pantalla un retorno de carro y un carcter
blanco, de forma que borra el carcter que haba sido introducido
mediante la funcin getchar y que no deseamos que salga en pantalla.
El carcter intro es el ASCII 13. As lo hemos sealado en la condicin
que regula la estructura do while.

Entrada de caracteres.
Hemos visto dos funciones que sirven bien para la introduccin de
caracteres.
La funcin scanf espera la entrada de un carcter ms el carcter intro.
No es muy cmoda cuando se espera nicamente un carcter.
La funcin getchar tambin est definida en la biblioteca stdio.h. De
todas formas, su uso no es siempre el esperado, por problemas del
buffer de teclado. Un buffer es como una cola o almacn que crea y
gestiona el sistema operativo. Nuestro programas nunca actan
directamente sobre la mquina, sino siempre a travs de los servicios
que ofrece ese sistema operativo. Con frecuencia ocurre que la funcin
getchar debera esperar la entrada por teclado de un carcter, pero no
lo hace porque ya hay caracteres a la espera en el buffer gestionado por
el sistema operativo.
Fundamentos de informtica. Programacin en Lenguaje C


186
Si se est trabajando con un editor en el sistema operativo Windows, se
puede hacer uso de la biblioteca conio.h y de algunas de sus funciones
de entrada por consola. Esta biblioteca no es estndar en ANSI C, pero
si vamos a trabajar en ese sistema operativo s resulta vlida.
En esa biblioteca vienen definidas dos funciones tiles para la entrada
por teclado, y que no dan los problemas que da, en Windows, la funcin
getchar.
Esas dos funciones son:
int getche( void) ; espera del usuario un pulso de teclado. Devuelve el
cdigo ASCII del carcter pulsado y muestra por pantalla ese carcter.
Al ser invocada esta funcin, no recibe valor o parmetro alguno: por
eso se define como de tipo void.
int getch( void) ; espera del usuario un pulso de teclado. Devuelve el
cdigo ASCII del carcter pulsado. Al ser invocada esta funcin, no
recibe valor o parmetro alguno: por eso se define como de tipo void.
Esta funcin no tiene eco en pantalla, y no se ve el carcter pulsado.

Cadena de caracteres.
Una cadena de caracteres es una formacin de caracteres. Es un vector
tipo char, cuyo ltimo elemento es el carcter nulo ( NULL, \0 se
escribe en C). Toda cadena de caracteres termina con el carcter
llamado carcter NULO de C. Este carcter indica donde termia la
cadena.
La declaracin de una cadena de caracteres se realiza de forma similar a
la de un vector de cualquier otro tipo:
char mi_ cadena[ dimensin] ;
donde dimensin es un literal que indica el nmero de bytes que se
deben reservar para la cadena (recuerde que una variable tipo char
ocupa un byte).
Captulo 7. Caracteres y cadenas de caracteres.


187
La asignacin de valores, cuando se crea una cadena, puede ser del
mismo modo que la asignacin de vectores:
char mi _cadena[ 4] = { h , o , l , a };
Y as hemos asignado a la variable mi_cadena el valor de la cadena
hola.
Y as, con esta operacin de asignacin, acabamos de cometer un error
importante. Repasemos qu es una cadena?: es un vector tipo char,
cuyo ltimo elemento es el carcter nulo. Pero si el ltimo elemento es
el carcter nulo no nos hemos equivocado en algo?: S.
La correcta declaracin de esta cadena sera:
char mi _cadena[ 5] = { h , o , l , a , \ 0 };
Faltaba el carcter nulo con el que debe terminar toda cadena. De todas
formas, la asignacin de valor a una cadena suele hacerse mediante
comillas dobles, de la siguiente manera:
char mi _cadena[ 5] = hol a;
Y ya en la cadena hola se recoge el quinto carcter: el carcter nulo.
Ya se encarga el compilador de C de introducir el carcter nulo al final
de la cadena.
Y es que hay que distinguir, por ejemplo, entre el carcter a y la
cadena a. En el primer caso tratamos del valor ASCII 97, que codifica
la letra a minscula; en el segundo caso tratamos de una cadena de
dos caracteres: el carcter a seguido del carcter nulo.
Tambin podramos haber hecho lo siguiente:
char mi _cadena[ 100] = hol a;
donde todos los bytes posteriores al carcter nulo tendrn valores
aleatorios, no asignados. Pero eso no importa, porque la cadena slo se
extiende hasta el carcter nulo: no nos interesa lo que pueda haber ms
all de ese carcter.
Fundamentos de informtica. Programacin en Lenguaje C


188
Y al igual que con los arrays, podemos inicializar la cadena, sin
necesidad de dimensionarla:
char mi _cadena[ ] = hol a;
Y entonces ya se encarga el compilador de reservar cinco bytes para la
variable mi_cadena.
Una forma de vaciar una cadena y asignarle el valor de cadena vaca es
el siguiente;
mi _cadena[ 0] = \ 0 ;
donde, ya lo hemos dicho, \0 es el carcter nulo.
Definimos longitud de la cadena como el nmero de caracteres
previos al carcter nulo. El carcter nulo no cuenta como parte para el
clculo de la longitud de la cadena. La cadena hola necesita cinco
variables char para ser almacenada, pero su longitud se dice que es
cuatro. Asignando al elemento de ndice 0 el valor nulo, tenemos una
cadena de longitud cero.
Cada elemento de la cadena se reconoce a travs del ndice entre
corchetes. Cuando se quiere hacer referencia a toda la cadena en su
conjunto, se emplea el nombre sin ningn corchete ni ndice.

Dar valor a una cadena de caracteres.
Para recibir cadenas por teclado disponemos, entre otras, de la funcin
scanf ya presentada en captulos anteriores. Esa funcin toma la cadena
introducida por teclado, pero la corta a partir de la primera entrada de
un carcter en blanco. Se puede evitar ese corte, pero en general, para
introducir por teclado una cadena que puede tener caracteres en blanco,
muchos compiladores de C recomiendan el uso de otra funcin, mucho
ms cmoda, que es la funcin gets.
Captulo 7. Caracteres y cadenas de caracteres.


189
La funcin gets est definida en la biblioteca stdio.h, y su prototipo es
el siguiente:
char * gets( char * s) ;
Esta funcin asigna a la cadena s todos los caracteres introducidos como
cadena. La funcin queda a la espera de que el usuario introduzca la
cadena de texto. Hasta que no se pulse la tecla intro, se supone que
todo lo que se teclee ser parte de la cadena de entrada. Al final de
todos ellos, como es natural, coloca el carcter nulo.
Hay que hacer una advertencia grave sobre el uso de esta funcin:
puede ocurrir que la cadena introducida por teclado sea de mayor
longitud que el nmero de bytes que se han reservado. Esa incidencia
no es vigilada por la funcin gets. Y si ocurre, entonces, adems de
grabar informacin de la cadena en los bytes reservados para ello, se
har uso de los bytes, inmediatamente consecutivos a los de la cadena,
hasta almacenar toda la informacin tecleada ms su carcter nulo final.
Esa violacin de memoria puede tener y de hecho habitualmente
tiene consecuencias desastrosas para el programa.
Ejemplo: programa que pregunta el nombre y, entonces saluda al
usuario.
#i ncl ude <st di o. h>
void mai n( void)
{
char nombr e[ 10] ;
pr i nt f ( " Cmo t e l l amas? " ) ;
get s( nombr e) ;
pr i nt f ( " Hol a, %s. " , nombr e) ;
}
El programa est bien construido. Pero hay que tener en cuenta que el
nombre que se introduce puede, fcilmente, superar los 10 caracteres.
Por ejemplo, si un usuario responde diciendo Jos Antonio, ya ha
introducido 13 caracteres: 4 por Jos, 7 por Antonio, 1 por el carcter
en blanco, y otro ms por el carcter nulo final. En ese caso, el
comportamiento del programa sera imprevisible.
Fundamentos de informtica. Programacin en Lenguaje C


190
Se puede hacer un programa que acepte la entrada por teclado de
carcter a carcter, y vaya mostrando la entrada por pantalla a la vez
que la va almacenando en la cadena. Veamos una posible
implementacin.
#def i ne TAM 30
#i ncl ude <st di o. h>
#i ncl ude <ct ype. h>
void mai n( void)
{
char nombr e[ TAM] ;
short int i ;
pr i nt f ( " Cmo t e l l amas? " ) ;
for( i = 0 ; i < TAM ; i ++)
nombr e[ i ] = NULL;
i = 0;
while( i < TAM)
{
nombr e[ i ] = get char ( ) ;
if ( nombr e[ i ] == 10)
{
nombr e[ i ] = NULL;
break;
}
else if( nombr e[ i ] == 8 && i > 0)
{
nombr e[ i ] = NULL;
pr i nt f ( " \ b \ b" ) ;
i - - ;
}
else if( i sgr aph( nombr e[ i ] ) | | nombr e[ i ] == 32)
pr i nt f ( " %c" , nombr e[ i ++] ) ;
}
pr i nt f ( " \ n\ nHol a, %s. " , nombr e) ;
}
Donde el valor ASCII 8 es el carcter borrar uno hacia detrs: eso
significa que hay que retroceder, escribir un blanco donde ya habamos
escrito otro carcter, y volver a retroceder; adems hay que reducir en
uno el valor del ndice i, puesto que hemos eliminado un carcter. Si
estbamos en el carcter 0, la operacin de borrado no surte efecto,
porque as lo hemos condicionado en el if correspondiente. El valor
ASCII 10 es el carcter intro, que vamos a entender como final de
entrada de la cadena: por eso se interrumpe el proceso de entrada de
caracteres y se cambia el valor de ese ltimo carcter que deja de ser
Captulo 7. Caracteres y cadenas de caracteres.


191
10 para ser el carcter nulo. Y el carcter 32 es el carcter espacio en
blanco, que no se resuelve como imprimible en la funcin isgraph.
Esta pequea aplicacin, e incluso alguna un poco ms desarrollada,
podran resolver el problema de la funcin gets, pero para nuestro
estudio ser mejor usar la funcin de stdio.h sin complicarse la vida.

Operaciones con cadenas de caracteres.
Todo lo visto en el captulo de vectores es perfectamente aplicable a las
cadenas: de hecho una cadena no es ms que un vector de tipo char.
De todas formas, las cadenas de caracteres merecen un tratamiento
diferente al presentado para el resto de vectores, ya que las operaciones
que se pueden realizar con cadenas son muy diferentes a las que se
realizan con vectores numricos: concatenar cadenas, buscar una
subcadena en una cadena dada, determinar cul de dos cadenas es
mayor alfabticamente, etc. Vamos a ver algunos programas bsicos de
manejo de cadena, y posteriormente presentaremos un conjunto de
funciones de biblioteca definidas para las cadenas, y que se encuentran
en la biblioteca string.h.
Copiar el contenido de una cadena en otra cadena:
#i ncl ude <st di o. h>
void mai n( void)
{
char or i gi nal [ 100] ;
char copi a[ 100] ;
short i nt i = 0;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
while( or i gi nal [ i ] ! = NULL)
{
copi a[ i ] = or i gi nal [ i ] ;
i ++;
}
copi a[ i ] = NULL;
pr i nt f ( " Or i gi nal : %s\ n" , or i gi nal ) ;
pr i nt f ( " Cop a: %s\ n" , copi a) ;
}
Fundamentos de informtica. Programacin en Lenguaje C


192
Mientras no se llega al carcter nulo de original, se van copiando uno a
uno los valores de las variables de la cadena en copia. Al final, cuando
ya se ha llegado al nulo en original, habr que cerrar tambin la cadena
en copia, mediante un carcter nulo.
El carcter nulo se escribe NULL, \0. Ambas formas son equivalentes.
Esta operacin tambin se puede hacer con una funcin de la biblioteca
string: la funcin
char * strcpy( char * dest, const char * src) ;
que recibe como parmetros las dos cadenas, origen y destino, y
devuelve la direccin de la cadena de destino. Con esta funcin, el
programa antes presentado queda de la siguiente forma:
#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>
void mai n( void)
{
char or i gi nal [ 100] ;
char copi a[ 100] ;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
st r cpy( copi a, or i gi nal ) ;
pr i nt f ( " Or i gi nal : %s\ n" , or i gi nal ) ;
pr i nt f ( " Cop a: %s\ n" , copi a) ;
}
Determinar la longitud de una cadena:
#i ncl ude <st di o. h>
void mai n( void)
{
char or i gi nal [ 100] ;
short int i = 0;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
while( or i gi nal [ i ] ! = NULL) i ++;
pr i nt f ( " %s t i ene l ongi t ud %hd\ n" , or i gi nal , i ) ;
}
El contador i se va incrementando hasta llegar al carcter nulo. As, en i,
tenemos la longitud de la cadena.
Captulo 7. Caracteres y cadenas de caracteres.


193
Esta operacin tambin se puede hacer con una funcin de la biblioteca
string: la funcin
size_ t strlen( const char * s) ;
que recibe como parmetro una cadena de caracteres y devuelve su
longitud. Una variable tipo size_ t es, para nosotros y ahora mismo, una
variable de tipo entero.
#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>
void mai n( void)
{
char or i gi nal [ 100] ;
short int i ;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
i = st r l en( or i gi nal ) ;
pr i nt f ( " %s t i ene l ongi t ud %hd\ n" , or i gi nal , i ) ;
}
Concatenar una cadena al final de otra:
#i ncl ude <st di o. h>
void mai n( void)
{
char or i gi nal [ 100] ;
char concat [ 100] ;
short int i = 0, j = 0;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
pr i nt f ( Cadena a concat enar . . . ) ;
get s( concat ) ;
while( or i gi nal [ i ] ! = NULL) i ++;
while( concat [ j ] ! = NULL)
{
or i gi nal [ i ] = concat [ j ] ;
i ++;
j ++;
}
or i gi nal [ i ] = NULL;
pr i nt f ( " Text o concat enado: %s\ n" , or i gi nal ) ;
}
Y de nuevo disponemos de una funcin en la biblioteca string que
realiza la concatenacin de cadenas:
char * strcat( char * dest, const char * src) ;
Fundamentos de informtica. Programacin en Lenguaje C


194
Que recibe como parmetros las cadenas destino de la concatenacin y
cadena fuente, y devuelve la direccin de la cadena que contiene la
cadena original ms la concatenada.
#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>
void mai n( void)
{
char or i gi nal [ 100] ;
char concat [ 100] ;
pr i nt f ( Cadena or i gi nal . . . ) ;
get s( or i gi nal ) ;
pr i nt f ( Cadena a concat enar . . . ) ;
get s( concat ) ;
st r cat ( or i gi nal , concat ) ;
pr i nt f ( " Text o concat enado: %s\ n" , or i gi nal ) ;
}
Tambin existe otra funcin, parecida a esta ltima, que concatena no
toda la segunda cadena, sino hasta un mximo de caracteres, fijado por
un tercer parmetro de la funcin:
char * strncat( char * dest, const char * src, size_ t maxlen) ;
Comparar dos cadenas e indicar cul de ellas es mayor, o si son
iguales:
#i ncl ude <st di o. h>
void mai n( void)
{
char cad01[ 100] ;
char cad02[ 100] ;
short int i = 0;
short int chi vat o = 0;
pr i nt f ( Pr i mer a Cadena . . . ) ;
get s( cad01) ;
pr i nt f ( Segunda Cadena . . . ) ;
get s( cad02) ;
while( cad01[ i ] ! = NULL && cad02[ i ] ! = NULL)
{
if( cad01[ i ] > cad02[ i ] )
{
chi vat o = 1;
break;
}
else if( cad01[ i ] < cad02[ i ] )
{
chi vat o = 2;
Captulo 7. Caracteres y cadenas de caracteres.


195
break;
}
i ++;
}
if( chi vat o == 1)
pr i nt f ( " cadena01 > cadena02" ) ;
else if( chi vat o == 2)
pr i nt f ( " cadena02 > cadena01" ) ;
else if( cad01[ i ] == NULL && cad02[ i ] ! = NULL)
pr i nt f ( " cadena02 > cadena01" ) ;
else if( cad01[ i ] ! = NULL && cad02[ i ] == NULL)
pr i nt f ( " cadena01 > cadena02" ) ;
else
pr i nt f ( " cadenas i gual es" ) ;
}
Y una vez ms, disponemos de una funcin en la biblioteca string que
realiza la comparacin de cadenas:
int strcmp( const char * s1, const char * s2) ;
Que recibe como parmetros las cadenas a comparar y devuelve un
valor negativo si s1 < s2; un valor positivo si s1 > s2; y un cero si
ambas cadenas son iguales
#i ncl ude <st r i ng. h>
#i ncl ude <st di o. h>
void mai n( void)
{
char c1[ 100] = " Text o de l a cadena pr i mer a" ;
char c2[ 100] = " Text o de l a cadena segunda" ;
short int comp;
pr i nt f ( Pr i mer a Cadena . . . ) ;
get s( c1) ;
pr i nt f ( Segunda Cadena . . . ) ;
get s( c2) ;
comp = st r cmp( c1, c2) ;
if( comp < 0) pr i nt f ( " cadena02 > cadena01" ) ;
else if( comp > 0) pr i nt f ( " cadena01 > cadena02" ) ;
else pr i nt f ( " Cadenas i gual es" ) ;
}
Tambin existe una funcin que compara hasta una cantidad de
caracteres sealado, es decir, una porcin de la cadena:
int strncmp( const char * s1, const char * s2, size_ t maxlen) ;
Fundamentos de informtica. Programacin en Lenguaje C


196
donde maxlen es el tercer parmetro, que indica hasta cuntos
caracteres se han de comparar.
Podramos seguir con otras muchas funciones de la biblioteca string.
Hemos mostrado algunas de las operaciones con cadenas, con su
funcin y sin ella, para presentar con ejemplos el modo habitual con el
que se manejan las cadenas de caracteres. Hay mucha informacin
sobre estas y otras funciones de la biblioteca string en cualquier ayuda
on line de cualquier editor de C. Se recomienda consultar esa ayuda
para obtener informacin sobre ellas.

Otras funciones de cadena.
Vamos a detenernos en la conversin de una cadena de caracteres,
todos ellos numricos, en la cantidad numrica, para poder luego operar
con ellos. Las funciones que veremos en este epgrafe se encuentran
definidas en otras bibliotecas: en la stdlib.h o en la biblioteca math.h.
Convertir una cadena de caracteres (todos ellos dgitos o signo decimal)
en un double.
double strtod( const char * s, char * * endptr) ;
Esta funcin convierte la cadena s en un valor double. La cadena s debe
ser una secuencia de caracteres que puedan ser interpretados como un
valor double. La forma genrica en que se puede presentar esa cadena
de caracteres es la siguiente:
[ws] [sn] [ddd] [.] [ddd] [fmt[sn]ddd]
donde [ws] es un espacio en blanco opcional; [sn] es el signo opcional
(+ ); [ddd] son dgitos opcionales; [fmt] es el formato exponencial,
tambin opcional, que se indica con las letras e E; finalmente, el [.]
es el carcter punto decimal, tambin opcional. Por ejemplo, valores
vlidos seran + 1231.1981 e-1; 502.85E2; + 2010.952.
Captulo 7. Caracteres y cadenas de caracteres.


197
La funcin abandona el proceso de lectura y conversin en cuanto llega
a un carcter que no puede ser interpretable como un nmero real. En
ese caso, se puede hacer uso del segundo parmetro para detectar el
error que ha encontrado: aqu se recomienda que para el segundo
parmetro de esta funcin se indique el valor nulo: en esta parte del
libro an no se tiene suficiente formacin en C para poder comprender y
emplear bien este segundo parmetro.
La funcin devuelve, si ha conseguido la transformacin, el nmero ya
convertido en formato double.
Vamos a ver un ejemplo.
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
void mai n( void)
{
char ent r ada[ 80] ;
double val or ;
pr i nt f ( " Nmer o deci mal . . . " ) ;
get s( ent r ada) ;
val or = st r t od( ent r ada, NULL) ;
pr i nt f ( " La cadena es %s " , ent r ada) ;
pr i nt f ( " y el nmer o es %l f \ n" , val or ) ;
}
De forma semejante se comporta la funcin atof, de la biblioteca
math.h. Su prototipo es:
double atof( const char * s) ;
Donde el formato de la cadena de entrada es el mismo que hemos visto
para la funcin strtod.
Consultando la ayuda del compilador se puede ver cmo se emplean las
funciones strtol y strtoul, de la biblioteca stdlib.h: la primera
convierte una cadena de caracteres en un entero largo; la segunda es lo
mismo pero el entero es siempre largo sin signo. Y tambin las
funciones atoi y atol, que convierte la cadena de caracteres a int y a
long int respectivamente.
Fundamentos de informtica. Programacin en Lenguaje C


198
Como ejemplo de todo lo dicho, veamos un programa que acepta como
entrada una cadena de la forma x + y = y muestra por pantalla el
resultado de la operacin. Desde luego, el desarrollo presentado es uno
de los muchos posibles; y como se ha dicho ya en otras ocasiones en
este manual, no necesariamente la mejor de las soluciones:
#i ncl ude <st di o. h>
#i ncl ude <ct ype. h>
#i ncl ude <st dl i b. h>
void mai n( void)
{
char e[ 80] ;
char n[ 20] ;
char op;
short i = 0, j = 0;
short est = 0, er r = 0;
long int a1, a2, r es;
pr i nt f ( " I nt r oduzca l a cadena de oper aci n. " ) ;
pr i nt f ( " \ nX + Y = o X - Y =. . . " ) ;
get s( e) ;
while( e[ i ] ! = NULL)
{
if( e[ i ] ! = 32 && ! i sdi gi t ( e[ i ] ) && e[ i ] ! = ' +' &&
e[ i ] ! = ' - ' && e[ i ] ! = ' =' )
{
er r = 1;
break;
}
else if( e[ i ] == 32 && ( est == 0 | | est == 2 | |
est == 3 | | est == 5) ) i ++;
else if( i sdi gi t ( e[ i ] ) && ( est == 0 | | est == 3) )
{
est ++;
n[ j ] = e[ i ] ;
i ++;
j ++;
}
else if( i sdi gi t ( e[ i ] ) && ( est == 1 | | est == 4) )
{
n[ j ] = e[ i ] ;
j ++;
i ++;
}
else if( e[ i ] == 32 && ( est == 1 | | est == 4) )
{
n[ j ] = NULL;
if( est == 1) a1 = at ol ( n) ;
else a2 = at ol ( n) ;
est ++;
Captulo 7. Caracteres y cadenas de caracteres.


199
j = 0;
i ++;
}
else if( ( e[ i ] == ' +' | | e[ i ] == ' - ' ) && est == 2)
{
op = e[ i ] ;
i ++;
est = 3;
}
else if( e[ i ] == ' =' && est == 5)
{
pr i nt f ( " %l u %c %l u = " , a1, op, a2) ;
pr i nt f ( " %l u" , op==' +' ? a1 + a2 : a1 - a2) ;
break;
}
else
{
er r = 1;
break;
}
}
if( er r == 1) pr i nt f ( " Er r or de ent r ada de dat os" ) ;
}

Ejercicios.

48. Escribir un programa que solicite del usuario la entrada de una
cadena y muestre por pantalla en nmero de veces que se ha
introducido cada una de las cinco vocales.

#i ncl ude <st di o. h>
void mai n( void)
{
char cadena[ 100] ;
short int a, e, i , o, u, cont ;
pr i nt f ( " I nt r oduzca una cadena de t ext o . . . \ n" ) ;
get s( cadena) ;
a = e = i = o = u = 0;
for( cont = 0 ; cadena[ cont ] ! = NULL ; cont ++)
{
if( cadena[ cont ] == ' a' ) a++;
else if( cadena[ cont ] == ' e' ) e++;
else if( cadena[ cont ] == ' i ' ) i ++;
Fundamentos de informtica. Programacin en Lenguaje C


200
else if( cadena[ cont ] == ' o' ) o++;
else if( cadena[ cont ] == ' u' ) u++;
}
pr i nt f ( " La cadena i nt r oduci da ha si do . . . \ n" ) ;
pr i nt f ( " %s\ n" , cadena) ;
pr i nt f ( " Y l as vocal es i nt r oduci das han si do . . . \ n" ) ;
pr i nt f ( " a . . . %hd\ n" , a) ;
pr i nt f ( " e . . . %hd\ n" , e) ;
pr i nt f ( " i . . . %hd\ n" , i ) ;
pr i nt f ( " o . . . %hd\ n" , o) ;
pr i nt f ( " u . . . %hd\ n" , u) ;
}
Una sola observacin al cdigo: la asignacin concatenada que pone a
cero las cinco variables de cuenta de vocales. Esta sintaxis es correcta y
est permitida en C.

49. Escribir un programa que solicite del usuario la entrada de una
cadena y muestre por pantalla esa misma cadena en
maysculas.

#i ncl ude <st di o. h>
#i ncl ude <ct ype. h>
void mai n( void)
{
char cadena[ 100] ;
short int cont ;
pr i nt f ( " I nt r oduzca una cadena de t ext o . . . \ n" ) ;
get s( cadena) ;
for( cont = 0 ; cadena[ cont ] ! = NULL ; cont ++)
cadena[ cont ] = t oupper ( cadena[ cont ] ) ;
pr i nt f ( " La cadena i nt r oduci da ha si do . . . \ n" ) ;
pr i nt f ( " %s\ n" , cadena) ;
}

50. Escribir un programa que solicite del usuario la entrada de una
cadena y luego la imprima habiendo eliminado de ella los
espacios en blanco.
Captulo 7. Caracteres y cadenas de caracteres.


201

#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>
voi d mai n( voi d)
{
char cadena[ 100] ;
short int i , j ;
pr i nt f ( " I nt r oduzca una cadena de t ext o . . . \ n" ) ;
get s( cadena) ;
for( i = 0 ; cadena[ i ] ! = NULL ; )
if( cadena[ i ] == 32)
for( j = i ; cadena[ j ] ! = NULL ; j ++)
cadena[ j ] = cadena[ j + 1] ;
else i ++;
pr i nt f ( " La cadena i nt r oduci da ha si do . . . \ n" ) ;
pr i nt f ( " %s\ n" , cadena) ;
}
Si el carcter i es el carcter blanco (ASCII 32) entonces no se
incrementa el contador sino que se adelantan una posicin todos los
caracteres hasta el final. Si el carcter no es el blanco, entonces
simplemente se incrementa el contador y se sigue rastreando la cadena
de texto.
Una observacin: la estructura for situada inmediatamente despus de
la funcin gets, controla una nica sentencia simple, que est controlado
por una estructura if else, que a su vez controla otra sentencia for,
tambin con una sola sentencia simple. No es necesario utilizar ninguna
llave en todo ese cdigo porque no hay ni una sola sentencia
compuesta.

51. Escribir un programa que solicite del usuario la entrada de una
cadena y la entrada de un desplazamiento. Luego debe volver a
imprimir la cadena con todos los caracteres alfabticos
desplazados tantas letras en el alfabeto como indique el
desplazamiento. Si en ese desplazamiento se llega ms all de
la letra Z, entonces se contina de nuevo con la A. Se tienen
26 letras en el alfabeto ASCI I .
Fundamentos de informtica. Programacin en Lenguaje C


202

#i ncl ude <ct ype. h>
#i ncl ude <st di o. h>
void mai n( void)
{
char c[ 100] ;
short int i ;
short int d;
pr i nt f ( " I nt r oduzca una cadena de t ext o . . . \ n" ) ;
get s( c) ;
for( i = 0 ; c[ i ] ! = NULL ; i ++)
c[ i ] = t oupper ( c[ i ] ) ;
pr i nt f ( " I nt r oduzca el despl azami ent o . . . \ n" ) ;
scanf ( " %hd" , &d) ;
d %= 26; / * Si d = 26 * k + d' , donde d' < 26
ent onces despl azar d en el
abecedar i o es l o mi smo que despl azar d' . */
for( i = 0 ; c[ i ] ! = NULL ; i ++)
{
if( i sal pha( c[ i ] ) )
{
c[ i ] += d;
if( c[ i ] > ' Z' ) c[ i ] = ' A' + c[ i ] - ' Z' - 1;
}
}
pr i nt f ( " La cadena t r ansf or mada queda . . . \ n" ) ;
pr i nt f ( " %s" , c) ;
}
Una nica observacin a este cdigo y, en general, a todos los que se
presentan como ejemplo en este manual. Si bien vienen resueltos,
cuando de verdad se aprende a programar es en el momento en que
uno se pone delante de la pantalla y comienza a echar lneas de cdigo
en busca de SU solucin. Este programa, como la mayora, tiene
infinidad de formas diferentes de solventarlo. La que aqu se muestra no
es la mejor, simplemente es la que se ha ocurrido a quien esto escribe
en el tiempo que ha tardado en redactar esa solucin. Si hubiera
resuelto el programa maana, el libro tendra una solucin distinta.
Y es que copiar el cdigo en un editor de C y compilarlo no sirve para
aprender. Que este manual es de C, y no de mecanografa.








CAPTULO 8

PUNTEROS

La memoria puede considerarse como una enorme cantidad de
posiciones de almacenamiento de informacin perfectamente ordenadas.
Cada posicin es un octeto o byte de informacin. Cada posicin viene
identificada de forma inequvoca por un nmero que suele llamarse
direccin de memoria. Cada posicin de memoria tiene una direccin
nica. Los datos de nuestros programas se guardan en esa memoria.
La forma en que se guardan los datos en la memoria es mediante el uso
de variables. Una variable es un espacio de memoria reservado para
almacenar un valor: valor que pertenece a un rango de valores posibles.
Esos valores posibles los determina el tipo de dato de esa variable.
Dependiendo del tipo de dato, una variable ocupar ms o menos bytes
de la memoria, y codificar la informacin de una u otra manera.
Si, por ejemplo, creamos una variable de tipo float, estaremos
reservando cuatro bytes de memoria para almacenar sus posibles
valores. Si, por ejemplo, el primero de esos bytes es el de posicin
Fundamentos de informtica. Programacin en Lenguaje C


204
ABD0:FF31 (es un modo de escribir: en definitiva estamos dando 32 bits
para codificar las direcciones de la memoria), el segundo byte ser el
ABD0:FF32, y luego el ABD0:FF33 y finalmente el ABD0:FF34. La
direccin de memoria de esta variable es la del primero de sus bytes; en
este caso, diremos que toda la variable float est almacenada en
ABD0:FF31. Ya se entiende que al hablar de variables float, se emplean
un total de 4 bytes.
Ese es el concepto habitual cuando se habla de la posicin de memoria o
de la direccin de una variable.
Adems de los tipos de dato primitivos ya vistos en un tema anterior,
existe un C un tipo de dato especial, que ofrece muchas posibilidades y
confiere al lenguaje C de una filosofa propia. Es el tipo de dato puntero.
Mucho tiene que ver ese tipo de dato con la memoria de las variables.
Este captulo est dedicado a su presentacin.

Definicin y declaracin.
Una variable tipo puntero es una variable que contiene la
direccin de otra variable.
Para cada tipo de dato, primitivo o creado por el programador, permite
la creacin de variables puntero hacia variables de ese tipo de dato.
Existen punteros a char, a long, a double, etc. Son nuevos tipos de
dato: puntero a char, puntero a long, puntero a double,
Y como tipos de dato que son, habr que definir para ellos un dominio y
unos operadores.
Para declarar una variable de tipo puntero, la sintaxis es similar a la
empleada para la creacin de las otras variables, pero precediendo al
nombre de la variable del carcter asterisco (*).
tipo *nombre_puntero;
Por ejemplo:
Captulo 8. Punteros.


205
short int *p;
Esa variable p as declarada ser una variable puntero a short, que no
es lo mismo que puntero a float, etc.
En una misma instruccin, separados por comas, pueden declararse
variables puntero con otras que no lo sean:
long a, b, *c;
Se han declarado dos variables de tipo long y una tercera que es
puntero a long.

Dominio y operadores para los punteros
El dominio de una variable puntero es el de las direcciones de memoria.
En un PC las direcciones de memoria se codifican con 32 bits (4 bytes),
y toman valores desde 0 hasta FFFFFFFF, en base hexadecimal.
El operador sizeof, aplicado a cualquier variable de tipo puntero,
devuelve el valor 4.
Una observacin importante: si un PC tiene direcciones de 32 bytes
cunta memoria puede llegar a direccionar?: Pues con 32 bits es
posible codificar hasta
32
2 bytes, es decir, hasta
30
4 2 bytes, es decir 4
Giga bytes de memoria.
Pero sigamos con los punteros. Ya tenemos el dominio. Codificar, en un
formato similar al de los enteros largos sin signo, las direcciones de toda
nuestra memoria. Ese ser su dominio de valores.
Los operadores son los siguientes:
Operador direccin (&): Este operador se aplica a cualquier variable,
y devuelve la direccin de memoria de esa variable. Por ejemplo, se
puede escribir:
long x, *px;
px = &x;
Fundamentos de informtica. Programacin en Lenguaje C


206
Y as se ha creado una variable puntero a long llamada px, que servir
para almacenar direcciones de variables de tipo long. Mediante la
segunda instruccin asignamos a ese puntero la direccin de la variable
x. Habitualmente se dice que px apunta a x.
El operador direccin no es propio de los punteros, sino de todas las
variables. Pero no hemos querido presentarlo hasta el momento en que
por ser necesario creemos que tambin ha de ser fcilmente
comprendido. De hecho este operador ya lo usbamos, por ejemplo, en
la funcin scanf, cuando se le indica a esa funcin dnde queremos
que almacene el dato que introducir el usuario por teclado: por eso, en
esa funcin precedamos el nombre de la variable cuyo valor se iba a
recibir por teclado con el operador &.
Hay una excepcin en el uso de este operador: no puede aplicarse sobre
una variable que haya sido declarada register. El motivo es claro: al ser
una variable register, le hemos indicado al compilador que no almacene
su informacin en la memoria sino en un registro de la ALU. Y si la
variable no est en memoria, no tiene sentido que le solicitemos la
direccin de donde no est.
Operador indireccin (*): Este operador slo se aplica a los punteros.
Al aplicar a un puntero el operador indireccin, se obtiene el contenido
de la posicin de memoria apuntada por el puntero. Supongamos:
float pi = 3.14, *pt;
pt = &pi;
Con la primera instruccin, se han creado dos variables:
<pi, float,
1
R , 3.14> y <pt, float*,
2
R , ? >
Con la segunda instruccin damos valor a la variable puntero:
<pt, float*,
2
R ,
1
R >
Ahora la variable puntero pt vale
1
R , que es la direccin de memoria de
la variable pi.
Captulo 8. Punteros.


207
Hablando ahora de la variable pt, podemos hacernos tres preguntas,
todas ellas referidas a direcciones de memoria.
1. Dnde est pt? Porque pt es una variable, y por tanto est ubicada
en la memoria y tendr una direccin. Para ver esa direccin, basta
aplicar a pt el operador direccin &. pt est en &pt.
2. Qu vale pt? Y como pt es un puntero, pt vale o codifica una
determinada posicin de memoria. Su valor pertenece al dominio de
direcciones y est codificado mediante 4 bytes. En concreto, pt vale
la direccin de la variable pi. pt vale &pi.
3. Qu valor est almacenado en esa direccin de memoria a donde
apunta pt? Esta es una pregunta muy interesante, donde se muestra
la gran utilidad que tienen los punteros. Podemos llegar al valor de
cualquier variable tanto si disponemos de su nombre como si
disponemos de su direccin. Podemos llegar al valor de la posicin
de memoria apuntada por pt, que como es un puntero a float, desde
el puntero tomar ese byte y los tres siguientes como el lugar donde
se aloja una variable float. Y para llegar a ese valor, disponemos del
operador indireccin. El valor codificado en la posicin almacenada
en pt es el contenido de pi: *pt es 3.14.
Al emplear punteros hay un peligro de confusin, que desconcierta al
principiante: al hablar de la direccin del puntero es fcil no entender si
nos referimos a la direccin que trae codificada en sus cuatro bytes, o la
posicin de memoria dnde estn esos cuatro bytes del puntero que
codifican direcciones.
Es muy importante que las variables puntero estn correctamente
direccionadas. Trabajar con punteros a los que no se les ha asignado
una direccin concreta conocida (la direccin de una variable) es muy
peligroso. En el caso anterior de la variable pi, se puede escribir:
*pt = 3.141596;
Fundamentos de informtica. Programacin en Lenguaje C


208
y as se ha cambiado el valor de la variable pi, que ahora tiene algunos
decimales ms de precisin. Pero si la variable pt no estuviera
correctamente direccionada mediante una asignacin previa en qu
zona de la memoria se hubiera escrito ese nmero 3.141596? Pues en la
posicin que, por defecto, hubiera tenido esos cuatro bytes que codifican
el valor de la variable pt: quiz una direccin de otra variable, o a mitad
entre una variable y otra; o en un espacio de memoria no destinado a
almacenar datos, sino instrucciones, o el cdigo del sistema operativo,
En general, las consecuencias de usar punteros no inicializados, son
catastrficas para la buena marcha de un ordenador. Detrs de un
programa que cuelga al ordenador, muchas veces hay un puntero no
direccionado.
Pero no slo hay que inicializar las variables puntero: hay que
inicializarlas bien, con coherencia. No se puede asignar a un puntero a
un tipo de dato concreto la direccin de una variable de un tipo de dato
diferente. Por ejemplo:
float x, px;
long y;
px = &y;
Si ahora hacemos referencia a *px trabajaremos la informacin de la
variable y como long, o como float? Y peor todava:
float x, px;
char y;
px = &y;
Al hacer referencia a *px leemos la informacin del byte cuya
direccin es la de la variable y, o tambin se va a tomar en
consideracin los otros tres bytes consecutivos? Porque la variable px
considera que apunta a variables de 4 bytes, que pasa eso es un
puntero a float. Pero la posicin que le hemos asignado es la de una
variable tipo char, que nicamente ocupa un byte.
El error de asignar a un puntero la direccin de una variable de tipo de
dato distinto al puntero est, de hecho, impedido por el compilador, y si
Captulo 8. Punteros.


209
encuentra una asignacin de esas caractersticas, aborta el proceso de
compilacin.

Punteros y vectores
Los punteros sobre variables simples tienen una utilidad clara en las
funciones. All los veremos con detenimiento. Lo que queremos ver
ahora es el uso de punteros sobre arrays.
Un array, o vector, es una coleccin de variables, todas del mismo tipo,
y colocadas en memoria de forma consecutiva. Si creo una array de
cinco variables float, y el primero de los elementos queda reservado en
la posicin de memoria FF54:AB10, entonces no cabe duda que el
segundo estar en FF54:AB14, y el tercero en FF54:AB18, y el cuarto en
FF54:AB1C y el ltimo en FF54:AB20.
Supongamos la siguiente situacin:
long a[10];
long *pa;
pa = &a[0];
Y con esto a la vista, pasamos ahora a presentar otros dos operadores,
muy usados con los punteros.
Operador incremento (++) y decremento (): Estos operadores
no son nuevos, y ya los conocemos. Todas sus propiedades que se
vieron con los enteros siguen vigentes ahora con las direcciones de
memoria.
Desde luego, si el incremento es sobre el contenido de la variable
apuntada sobre el puntero (*pa)++;, no hay nada que aadir: se
incrementar el contenido de esa variable apuntada, de la misma forma
que si el operador estuviera aplicado sobre la variable apuntada misma:
es lo mismo que escribir a[0]++;.
Nos referimos a incrementar el valor del puntero. Qu sentido tiene
incrementar en 1 una direccin de memoria? El sentido ser el de acudir
Fundamentos de informtica. Programacin en Lenguaje C


210
al siguiente valor situado en la memoria. Y si el puntero es de tipo long,
y apunta a variables long, entonces lo que se espera cuando se
incremente un 1 ese puntero es que su valor codificado se incremente
en 4: porque 4 es el nmero de bytes que deberemos saltar para pasar
de apuntar a una variable long a pasar a apuntar a otra variable del
mismo tipo almacenada de forma consecutiva.
Es decir, que si pa contiene la direccin de a[0], entonces pa + 1 es la
direccin del elemento a[1], y pa + 9 es la direccin del elemento a[9].
Y en el siguiente ejemplo, tenemos:
long a[10], *pa;
short b[10], *pb;
pa = &a[0];
pb = &b[0];

a b
F6C3:9870 pa pb F6C3:9870
F6C3:9871 F6C3:9871
b[0]
F6C3:9872 pb + 1 F6C3:9872
a[0]
F6C3:9873 F6C3:9873
b[1]
F6C3:9874 pa + 1 pb + 2 F6C3:9874
F6C3:9875 F6C3:9875
b[2]
F6C3:9876 pb + 3 F6C3:9876
a[1]
F6C3:9877 F6C3:9877
b[3]
F6C3:9878 pa + 2 pb + 4 F6C3:9878
F6C3:9879 F6C3:9879
b[4]
F6C3:987A pb + 5 F6C3:987A
a[2]
F6C3:987B F6C3:987B
b[5]
F6C3:987C pa + 3 pb + 6 F6C3:987C

F6C3:987D F6C3:987D


Al ir aumentando el valor del puntero, nos vamos desplazando por los
distintos elementos del vector, de tal manera que hablar de a[0] es lo
mismo que hablar de *pa; y hablar de a[1] es lo mismo que hablar de
*(pa + 1); y, en general, hablar de a[i] es lo mismo que hablar de *(pa
+ i). Y lo mismo si comentamos el ejemplo de las variables de tipo
short.
Captulo 8. Punteros.


211
La operatoria o aritmtica de punteros tiene en cuenta el tamao de las
variables que se recorren. En el siguiente programa, y en la salida que
ofrece por pantalla, se puede ver este comportamiento de los punteros.
Sea cual sea el tipo de dato del puntero y de la variable a la que apunta,
si calculo la resta entre dos punteros situados uno al primer elemento de
un array y el otro al ltimo, esa diferencia ser la misma, porque la
resta de direcciones indica cuntos elementos de este tipo hay (caben)
entre esas dos direcciones. En nuestro ejemplo, todas esas diferencias
valen 9. Pero si lo que se calcula es el nmero de bytes entre la ltima
posicin (apuntada por el segundo puntero) y la primera (apuntada por
el primer puntero), entonces esa diferencia s depender del tamao de
la variable del array.
#include <stdio.h>
void main(void)
{
char c[10], *pc1, *pc2;
short h[10], *ph1, *ph2;
float f[10], *pf1, *pf2;
double d[10], *pd1, *pd2;
long double ld[10], *pld1, *pld2;

pc1 = &c[0]; pc2 = &c[9];
ph1 = &h[0]; ph2 = &h[9];
pf1 = &f[0]; pf2 = &f[9];
pd1 = &d[0]; pd2 = &d[9];
pld1 = &ld[0]; pld2 = &ld[9];

printf(" pc2(%p) - pc1(%p) = %hd\n",pc2,pc1,pc2 - pc1);
printf(" ph2(%p) - ph1(%p) = %hd\n",ph2,ph1,ph2 - ph1);
printf(" pf2(%p) - pf1(%p) = %hd\n",pf2,pf1,pf2 - pf1);
printf(" pd2(%p) - pd1(%p) = %hd\n",pd2,pd1,pd2 - pd1);
printf("pld2(%p) - pld1(%p) = %hd\n",pld2,pld1,pld2 - pld1);
printf("\n\n");
printf("(long)pc2-(long)pc1=%3ld\n",(long)pc2-(long)pc1);
printf("(long)ph2-(long)ph1=%3ld\n",(long)ph2-(long)ph1);
printf("(long)pf2-(long)pf1=%3ld\n",(long)pf2-(long)pf1);
printf("(long)pd2-(long)pd1=%3ld\n",(long)pd2-(long)pd1);
printf("(long)pld2-(long)pld1=%3ld\n",(long)pld2-(long)pld1);
}
Que ofrece, por pantalla, el siguiente resultado:
pc2(0012FF89) - pc1(0012FF80) = 9
ph2(0012FF62) - ph1(0012FF50) = 9
Fundamentos de informtica. Programacin en Lenguaje C


212
pf2(0012FF4C) - pf1(0012FF28) = 9
pd2(0012FF20) - pd1(0012FED8) = 9
pld2(0012FECE) - pld1(0012FE74) = 9

(long)pc2 - (long)pc1 = 9
(long)ph2 - (long)ph1 = 18
(long)pf2 - (long)pf1 = 36
(long)pd2 - (long)pd1 = 72
(long)pld2 - (long)pld1 = 90
Que hemos de interpretar bien y entender.
Repetimos: al calcular la diferencia entre el puntero que apunta al
noveno elemento de la matriz y el que apunta al elemento cero, en
todos los casos el resultado ha de ser 9: porque en la operatoria de
punteros, independientemente del tipo del puntero, lo que se obtiene es
el nmero de elementos que hay entre las dos posiciones de memoria
sealadas.
Al convertir las direcciones en valores tipo long, ya no estamos
calculando cuntas variables hay entre ambas direcciones, sino la
diferencia entre el valor que codifica la ltima posicin del vector y el
valor que codifica la primera direccin. Y en ese caso, el valor ser
mayor segn sea mayor el nmero de bytes que emplee el tipo de dato
referenciado por el puntero. Si es un char, entre la posicin ltima y la
primera hay, efectivamente, 9 elementos; y el nmero de bytes entre
esas dos direcciones tambin es 9. Si es un float, entre la posicin
ltima y la primera hay, efectivamente y de nuevo, 9 elementos; pero
ahora el nmero de bytes entre esas dos direcciones es 36, porque cada
uno de los nueve elementos ocupa cuatro bytes de memoria.

ndices y operatoria de punteros
Se puede recorrer un vector, o una cadena de caracteres mediante
ndices. Y tambin, de forma equivalente, mediante operatoria de
punteros.
Captulo 8. Punteros.


213
Pero adems, los arrays y cadenas tienen la siguiente propiedad: Si
declaramos ese array o cadena de la siguiente forma:
tipo nombre_array[dimensin];
El nombre del vector o cadena es nombre_array. Para hacer uso de cada
una de las variables, se utiliza el nombre del vector o cadena seguido,
entre corchetes, del ndice del elemento al que se quiere hacer
referencia: nombre_array[ndice].
Y ahora introducimos otra novedad: el nombre del vector o cadena
recoge la direccin de la cadena, es decir, la direccin del primer
elemento de la cadena: decir nombre_array es lo mismo que decir
&nombre_array[0].
Y por tanto, y volviendo al cdigo anteriormente visto:
long a[10], *pa;
short b[10], *pb;
pa = &a[0];
pb = &b[0];
Tenemos que *(pa + i) es lo mismo que a[i]. Y como decir a es
equivalente a decir &a[0] entonces, decir pa = &a[0] es lo mismo que
decir pa = a, y trabajar con el valor *(pa + i) es lo mismo que trabajar
con el valor *(a + i).
Y si podemos considerar que dar el nombre de un vector es equivalente
a dar la direccin del primer elemento, entonces podemos considerar
que ese nombre funciona como un puntero constante, con quien se
pueden hacer operaciones y formar parte de expresiones, mientras no
se le coloque en la parte Lvalue de un operador asignacin.
Y muchos programadores, en lugar de trabajar con ndices, recorren
todos sus vectores y cadenas mediante la operatoria o aritmtica de
punteros.
Veamos un programa sencillo, resuelto mediante ndices de vectores y
mediante la operatoria de punteros. Por ejemplo, un programa que
solicite al usuario una cadena de caracteres y luego la copie en otra
Fundamentos de informtica. Programacin en Lenguaje C


214
cadena en orden inverso: primero el ltimo carcter, luego el penltimo,
etc.
Con ndices:
#include <stdio.h>
#include <string.h>
void main(void)
{
char orig[100], copia[100];
short i, l;
printf("Introduzca la cadena ... \n");
gets(orig);
l = strlen(orig);
for(i = 0 ; i < l ; i++)
copia[l - i - 1] = orig[i];
copia[i] = NULL;
printf("Cadena original: %s\n",orig);
printf("Cadena copia: %s\n",copia);
}
Con operatoria de punteros:
#include <stdio.h>
#include <string.h>
void main(void)
{
char orig[100], copia[100];
short i, l;

printf("Introduzca la cadena ... \n");
gets(orig);
l = strlen(orig);
for(i = 0 ; i < l ; i++)
*(copia + l - i - 1) = *(orig + i);
*(copia + i) = NULL;
printf("Cadena original: %s\n",orig);
printf("Cadena copia: %s\n",copia);
}
Desde luego, ambas formas de referirse a los distintos elementos del
vector son vlidas.
En el captulo en que hemos presentado los arrays hemos indicado que
es competencia del programador no recorrer el vector ms all de las
posiciones reservadas. Si se llega, mediante operatoria de ndices o
mediante operatoria de punteros a una posicin de memoria que no
pertenece realmente al vector, el compilador no detectar error alguno,
Captulo 8. Punteros.


215
e incluso puede que tampoco se produzca un error en tiempo de
ejecucin, pero estaremos accediendo a zona de memoria que quiz se
emplea para almacenar otra informacin. Y entonces alteraremos esos
datos de forma inconsiderada, con las consecuencias desastrosas que
eso pueda llegar a tener para el buen fin del proceso. Cuando en un
programa se llega equivocadamente, mediante operatoria de punteros o
de ndices, ms all de la zona de memoria reservada, se dice que se ha
producido o se ha incurrido en una violacin de memoria.

Puntero a puntero
Un puntero es una variable que contiene la direccin de otra variable.
Segn sea el tipo de variable que va a ser apuntada, as, de ese tipo,
debe ser declarado el puntero. Ya lo hemos dicho.
Pero un puntero es tambin una variable. Y como variable que es, ocupa
una porcin de memoria: tiene una direccin.
Se puede, por tanto, crear una variable que almacene la direccin de
esa variable puntero. Sera un puntero que almacenara direcciones de
tipo de dato puntero. Un puntero a puntero.
Por ejemplo:
float F, *pF, **ppF;
Acabamos de crear tres variables: una, de tipo float, llamada F. Una
segunda variable, de tipo puntero a float, llamada pF. Y una tercera
variable, de tipo puntero a puntero float, llamada ppF.
Y eso no es un rizo absurdo. Tiene mucha aplicacin en C. Igual que se
puede hablar de un puntero a puntero a puntero a puntero a float.
Y as como antes hemos visto que hay una relacin directa entre
punteros a un tipo de dato y vectores de este tipo de dato, tambin
veremos ahora que hay una relacin directa entre punteros a punteros y
matrices de dimensin 2. Y entre punteros a punteros a punteros y
Fundamentos de informtica. Programacin en Lenguaje C


216
matrices de dimensin 3. Y si es conveniente trabajar con matrices de
dimensin n , entonces tambin lo es trabajar con punteros a punteros a
punteros
Vemoslo con un ejemplo. Supongamos que creamos una matriz de
dimensin 2:
double m[4][6];
Antes hemos dicho que al crea un array, al hacer referencia a su nombre
estamos indicando la direccin del primero de sus elementos. Ahora, al
crear esta matriz, la direccin del elemento m[0][0] la obtenemos con el
nombre de la matriz: Es equivalente decir m que decir &m[0][0].
Pero la estructura que se crea al declarar una matriz es algo ms
compleja que una lista de posiciones de memoria. En el ejemplo
expuesto de la matriz double, se puede considerar que se han creado
cuatro vectores de seis elementos cada uno y colocados en la memoria
uno detrs del otro de forma consecutiva. Y cada uno de esos vectores
tiene, como todo vector, la posibilidad de ofrecer la direccin de su
primer elemento. El cuadro 8.1. presenta un esquema de esta
construccin. Desde luego, no existen los punteros m, ni ninguno de los
*(m + i). Pero si empleamos el nombre de la matriz de esta forma,
entonces trabajamos con sintaxis de punteros.
De hecho, si ejecutamos el siguiente programa:
#include <stdio.h>
void main(void)
{
double m[4][6];
short i;
printf("m = %p\n",m);
for(i = 0 ; i < 4 ; i++)
{
printf("*(m + %hd) = %p\t",i, *(m + i));
printf("&m[%hd][0] = %p\n",i, &m[i][0]);
}
}
Obtenemos la siguiente salida:
Captulo 8. Punteros.


217
m = 0012FECC
*(m + 0) = 0012FECC &m[0][0] = 0012FECC
*(m + 1) = 0012FEFC &m[1][0] = 0012FEFC
*(m + 2) = 0012FF2C &m[2][0] = 0012FF2C
*(m + 3) = 0012FF5C &m[3][0] = 0012FF5C


m *(m+0) m[0][0] m[0][1] m[0][2] m[0][3] m[0][4] m[0][5]
*(m+1) m[1][0] m[1][1] m[1][2] m[1][3] m[1][4] m[1][5]
*(m+2) m[2][0] m[2][1] m[2][2] m[2][3] m[2][4] m[2][5]
*(m+3) m[3][0] m[3][1] m[3][2] m[3][3] m[3][4] m[3][5]

Cuadro 8.1.: Distribucin de la memoria en la matriz
double m[4][6];


Tenemos que m vale lo mismo que *(m + 0); su valor es la direccin del
primer elemento de la matriz: m[0][0]. Despus de l, vienen todos los
dems, uno detrs de otro: despus de m[0][5] vendr el m[1][0], y
esa direccin la podemos obtener con *(m + 1); despus de m[1][5]
vendr el m[2][0], y esa direccin la podemos obtener con *(m + 2);
despus de m[2][5] vendr el m[3][0], y esa direccin la podemos
obtener con *(m + 3); y despus de m[3][5] se termina la cadena de
elementos reservados.
Es decir, en la memoria del ordenador, no se distingue entre un vector
de 24 variables tipo double y una matriz 4 * 6 de variables tipo
double. Es el lenguaje el que sabe interpretar, mediante una operatoria
de punteros, una estructura matricial donde slo se dispone de una
secuencia lineal de elementos. Esos punteros no existen en realidad, y si
se imprime la posicin que ocupa m, *m la pantalla nos mostrar el
mismo valor que al solicitarle que nos muestre la direccin de m[0][0].
No existen, pero el lenguaje C admite esa sintaxis, y podemos trabajar
como si de punteros constantes se trataran.
Fundamentos de informtica. Programacin en Lenguaje C


218
Y as como antes hemos podido trabajar un programa con un array
mediante operatoria de punteros, ahora vamos a hacer lo mismo con un
programa que emplee matrices.
Veamos un programa que calcula el determinante de una matriz de tres
por tres.
Con ndices:
#include <stdio.h>
void main(void)
{
double m[3][3];
double det;
short i,j;
for(i = 0 ; i < 3 ; i++)
for(j = 0 ; j < 3 ; j++)
{
printf("m[%hd][%hd] = ", i, j);
scanf("%lf",&m[i][j]);
}
det = 0;
det += (m[0][0] * m[1][1] * m[2][2]);
det += (m[0][1] * m[1][2] * m[2][0]);
det += (m[0][2] * m[1][0] * m[2][1]);
det -= (m[0][2] * m[1][1] * m[2][0]);
det -= (m[0][1] * m[1][0] * m[2][2]);
det -= (m[0][0] * m[1][2] * m[2][1]);
printf("El determinante ... \n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n | ");
for(j = 0 ; j < 3 ; j++)
printf("%8.2lf",m[i][j]);
printf(" | ");
}
printf("\n\n es ... %lf",det);
}
Con operatoria de punteros:
#include <stdio.h>
void main(void)
{
double m[3][3];
double det;
short i,j;
for(i = 0 ; i < 3 ; i++)
for(j = 0 ; j < 3 ; j++)
{
Captulo 8. Punteros.


219
printf("m[%hd][%hd] = ", i, j);
scanf("%lf",*(m + i) + j);
}
det = 0;
det += *(*(m + 0) + 0) * *(*(m + 1) + 1) * *(*(m + 2) + 2);
det += *(*(m + 0) + 1) * *(*(m + 1) + 2) * *(*(m + 2) + 0);
det += *(*(m + 0) + 2) * *(*(m + 1) + 0) * *(*(m + 2) + 1);
det -= *(*(m + 0) + 2) * *(*(m + 1) + 1) * *(*(m + 2) + 0);
det -= *(*(m + 0) + 1) * *(*(m + 1) + 0) * *(*(m + 2) + 2);
det -= *(*(m + 0) + 0) * *(*(m + 1) + 2) * *(*(m + 2) + 1);
printf("El determinante ... \n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n | ");
for(j = 0 ; j < 3 ; j++)
printf("%8.2lf",*(*(m + i) + j));
printf(" | ");
}
printf("\n\n es ... %lf",det);
Desde luego, la operatoria de punteros con matrices resulta algo
farragosa en un primer momento. Pero no encierra dificultad de
concepto.

Advertencia final
El uso de puntero condiciona el modo de programar. El puntero es una
herramienta muy poderosa y muy arriesgada tambin. Es necesario
saber bien qu se hace, porque jugando con punteros se pueden
burlar muchas seguridades de C.
Veamos un ejemplo. Primero presentamos el siguiente cdigo, donde se
emplea una variable static: una variable que se extiende a la duracin
de todo el programa, pero cuyo mbito queda reducido al del bloque en
el que se ha definido. Esa variable, que en el ejemplo llamamos local, se
inicializa a cero la primera vez que se entra en el bloque donde est
definida, y posteriormente se va aumentando de uno en uno cada vez
que se ejecuta su bloque.
#include <stdio.h>
void main(void)
{
short a, b = 0;
Fundamentos de informtica. Programacin en Lenguaje C


220
do
{
a = 0;
do
{
static short local = 0;
local++;
printf("local = %2hd\n",local);
a++;
}while (a < 5);
printf("\n");
b++;
}while(b < 5);
}
La salida de este programa es la que sigue:
local = 1 local = 2 local = 3 local = 4 local = 5
local = 6 local = 7 local = 8 local = 9 local = 10
local = 11 local = 12 local = 13 local = 14 local = 15
local = 16 local = 17 local = 18 local = 19 local = 20
local = 21 local = 22 local = 23 local = 24 local = 25
Cada vez que se entra en el mbito de la variable local, se vuelve a
poder trabajar sobre ella y se sigue incrementando desde el valor en
que quedo despus de la ltima modificacin.
Pero si ahora introducimos en el programa los siguientes cambios:
#include <stdio.h>
void main(void)
{
short a, b = 0;
short *c;
do
{
a = 0;
do
{
static short local = 0;
c = &local;
local++;
printf("local = %hd\t",local);
a++;
}while (a < 5);
printf("\n");
b++;
*c = 0;
}while(b < 5);
Entonces la salida es:
Captulo 8. Punteros.


221
local = 1 local = 2 local = 3 local = 4 local = 5
local = 1 local = 2 local = 3 local = 4 local = 5
local = 1 local = 2 local = 3 local = 4 local = 5
local = 1 local = 2 local = 3 local = 4 local = 5
local = 1 local = 2 local = 3 local = 4 local = 5
Y es que con el puntero, y desde fuera del mbito de la variable local, le
hemos cambiado su valor y la hemos inicializado a cero una y otra vez.
Y eso es peligroso, porque podemos violar las reglas de validez de las
variables. Es mejor evitar operaciones de este estilo. No se debe jugar
en contra de las reglas de la sintaxis del lenguaje C. Es mejor programar
de acuerdo con esas reglas.

Ejercicios

52. Leer el siguiente cdigo y completar la salida que ofrece por
pantalla: (No est resuelto).

#include <stdio.h>
void main(void)
{
char c[3];
short i[3], cont;
float f[3];
printf("Las direcciones de memoria son:\n");
for(cont = 0 ; cont < 3 ; cont++)
{
printf("&c[%2d] = %10p\t",cont,c + cont);
printf("&i[%2d] = %10p\t",cont,i + cont);
printf("&f[%2d] = %10p\n\n",cont,f + cont);
}
}
Las direcciones de memoria son:
&c[ 0] = 0064FE01 &i[ 0] = 0064FDF8 &f[ 0] = 0064FDEC
&c[ 1] = &i[ 1] = &f[ 1] =
&c[ 2] = &i[ 2] = &f[ 2] =
Fundamentos de informtica. Programacin en Lenguaje C


222
53. Leer el siguiente cdigo y completar la salida que ofrece por
pantalla.

#include <stdio.h>
main()
{
char c[20],*pc1,*pc2;
short int i[20],*pi1,*pi2;
float f[20],*pf1,*pf2;

pc1 = &c[0];
pc2 = &c[19];
pi1 = &i[0];
pi2 = &i[19];
pf1 = &f[0];
pf2 = &f[19];

printf("(int)pc2-(int)pc1 es %d\n",(int)pc2-(int)pc1);
printf("pc2 - pc1 es %d\n\n",pc2 - pc1);

printf("(int)pi2-(int)pi1 es %d\n",(int)pi2-(int)pi1);
printf("pi2 - pi1 es %d\n\n",pi2 - pi1);

printf("(int)pf2-(int)pf1 es %d\n",(int)pf2-(int)pf1);
printf("pf2 - pf1 es %d\n\n",pf2 - pf1);
}

(int)pc2 - (int)pc1 es 19
pc2 - pc1 es 19
(int)pi2 - (int)pi1 es 38
pi2 - pi1 es 19
(int)pf2 - (int)pf1 es 76
pf2 - pf1 es 19

En general una buena forma de aprendere a manejar punteros es
intentar rehacer todos los ejercicios ya resueltos en los dos captulos
anteriores empleando ahora operatoria de punteros y recorriendo los
vectores y matrices mediante la indireccin.







CAPTULO 9

FUNCIONES

Hemos llegado a las funciones.
Al principio del tema de estructuras de control sealbamos que haba
dos maneras de romper el flujo secuencial de sentencias. Y hablbamos
de dos tipos de instrucciones que rompen el flujo: las instrucciones
condicionales y de las incondicionales. Las primeras nos dieron pie a
hablar largamente de las estructuras de control: condicionales y de
iteracin. Ahora toca hablar de las instrucciones incondicionales que
realizan la transferencia a una nueva direccin del programa sin evaluar
condicin alguna.
De hecho, ya hemos visto muchas funciones. Y las hemos utilizado.
Cuando hemos querido mostrar por pantalla un mensaje hemos acudido
a la funcin printf de la biblioteca stdio.h. Cuando hemos querido saber
la longitud de una cadena hemos utilizado la funcin strlen, de la
biblioteca string.h. Y cuando hemos querido hallar la funcin seno de
un valor concreto, hemos acudido a la funcin sin, de math.h.
Fundamentos de informtica. Programacin en Lenguaje C


224
Y ya hemos visto que, sin saber cmo, hemos echado mano de una
funcin estndar programada por ANSI C que nos ha resuelto nuestro
problema. Cmo se ha logrado que se vea en pantalla un texto, o el
valor de una variable? Qu desarrollo de Taylor se ha aplicado para
llegar a calcular el seno de un ngulo dado? No lo sabemos. Dnde est
el cdigo que resuelve nuestro problema? Tampoco lo sabemos. Pero
cada vez que hemos invocado a una de esas funciones, lo que ha
ocurrido es que el contador de programa ha abandonado nuestra
secuencia de sentencias y se ha puesto con otras sentencias, que son
las que codifican las funciones que hemos invocado.
De forma incondicional, cada vez que se invoca una funcin, se
transfiere el control de ejecucin a otra direccin de memoria, donde se
encuentran codificadas otras sentencias, que resuelven el problema para
el que se ha definido, editado y compilado esa funcin.
Son transferencias de control con retorno. Porque cuando termina la
ejecucin de la ltima de las sentencias de la funcin, entonces regresa
el control a la sentencia inmediatamente posterior a aquella que invoc
esa funcin.
Quiz ahora, cuando vamos a abordar la teora de creacin, diseo e
implementacin de funciones, ser buen momento para releer lo que
decamos en el tema 4 al tratar de la modularidad. Y recordar tambin
las tres propiedades que deban tener los distintos mdulos:
independencia funcional, comprensibilidad, adaptabilidad. No lo vamos a
repetir ahora: all se trat.

Definiciones
Abstraccin Modularidad Programacin estructurada.
Esas eran las tres notas bsicas que presentamos al presentar el
lenguaje de programacin C. De la programacin estructurada ya hemos
hablado, y lo seguiremos haciendo en este captulo. La abstraccin es el
Captulo 9. Funciones.


225
paso previo de toda programacin: conocer el sistema e identificar los
ms significativos elementos que dan con la esencia del problema a
resolver. Y la modularidad es la capacidad de dividir el sistema
estudiado en partes diferenciadas.
Eso que hemos llamado mdulo, en un lenguaje de programacin se
puede llamar procedimiento o se puede llamar funcin. Las funciones y
los procedimientos permiten crear programas complejos, mediante un
reparto de tareas que permite construir el programa de forma
estructurada y modular.
Desde un punto de vista acadmico, se entiende por procedimiento el
conjunto de sentencias a las que se asocia un identificador (un nombre),
y que realiza una tarea que se conoce por los cambios que ejerce sobre
el conjunto de variables. Y entendemos por funcin el conjunto de
sentencias a las que se asocia un identificador (un nombre) y que
genera un valor nuevo, calculado a partir de los argumentos que recibe.
Los elementos que componen un procedimiento o funcin son, pues:
1. Un identificador, que es el nombre que sirve para invocar a esa
funcin o a ese procedimiento.
2. Una lista de parmetros, que es el conjunto de variables que se
facilitan al procedimiento o funcin para que realice su tarea
modularizada. Al hacer la abstraccin del sistema, y modularlo en
partes ms accesibles, hay que especificar los parmetros formales
que permiten la comunicacin y definen el dominio (tipo de dato) de
los datos de entrada. Esa lista de parmetros define el modo en que
podrn comunicarse el programa que utiliza a la funcin y la funcin
usada.
3. Un cuerpo o conjunto de sentencias. Las necesarias para poder
realizar la tarea para la que ha sido definida la funcin o el
procedimiento.
Fundamentos de informtica. Programacin en Lenguaje C


226
4. Un entorno. Entendemos por entorno el conjunto de variables
globales, y externas por tanto al procedimiento o funcin, que
pueden ser usadas y modificadas dentro del mbito de la funcin.
Esas variables, por ser globales y por tanto definidas en un mbito
ms amplio al mbito local de la funcin, no necesitan ser
explicitadas en la lista de parmetros de la funcin.
Es una prctica desaconsejable trabajar con el entorno de la funcin
desde el mbito local de la funcin. Hacerlo lleva consigo que esa
funcin deja de ser independiente de ese entorno y, por tanto, deja
de ser exportable. Perderamos entonces el valor de la
independencia funcional, que es una de las propiedades de la
programacin por mdulos.
Podemos pues concluir que el uso de variables globales dentro del
cuerpo de un procedimiento o funcin es altamente desaconsejable.
En el lenguaje C no se habla habitualmente de procedimientos, sino slo
de funciones. Pero de hecho existen de las dos cosas. Procedimientos
seran, por ejemplo, la funcin printf no se invoca para calcular valores
nuevos, sino para realizar una tarea sobre las variables. Ms claro se ve
con la funcin scanf que, efectivamente, realiza una tarea que se conoce
por los cambios que ejerce sobre una variable concreta. Y funciones
seran, por ejemplo, la funcin strlen, que a partir de una cadena de
caracteres que recibe como parmetro de entrada calcula un valor, que
es la longitud de esa cadena; o la funcin sin, que a partir de un ngulo
que recibe como valor de entrada, calcula el seno de ese ngulo como
valor de salida.
En definitiva, una funcin es una porcin de cdigo, identificada con un
nombre concreto (su identificador), que realiza una tarea concreta, que
puede ser entendida de forma independiente al resto del programa, y
que tiene muy bien determinado cmo se hace uso de ella, con qu
parmetros se la invoca y bajo qu condiciones puede ser usada, cul es
la tarea que lleva a cabo, y cul es el valor que calcula y devuelve.
Captulo 9. Funciones.


227
Tanto los procedimientos como las funciones pueden ser vistos como
cajas negras: un cdigo del que desconocemos sus sentencias, al que se
le puede suministrar unos datos de entrada y obtener modificaciones
para esos valores de entrada y/o el clculo de un nuevo valor, deducido
a partir de los valores que ha recibido como entrada.
Con eso se consigue programas ms cortos; que el cdigo pueda ser
usado ms de una vez; mayor facilidad para gestionar un correcto orden
de ejecucin de sentencias; que las variables tengan mayor carcter
local, y no puedan ser manipuladas fuera del mbito para el que han
sido creadas.

Funciones en C
Una funcin, en C, es un segmento independiente de cdigo fuente,
diseado para realizar una tarea especfica.
Las funciones son los elementos principales de un programa en C. Cada
una de las funciones de un programa constituye una unidad, capaz de
realizar una tarea determinada. Quiz se podra decir que un programa
es simplemente un conjunto de definiciones de distintas funciones,
empleadas luego de forma estructurada.
La primera funcin que aparece en todo programa C es la funcin
principal, o funcin main. Todo programa ejecutable tiene una, y slo
una, funcin main. Un programa sin funcin principal no genera un
ejecutable. Y si todas las funciones se crean para poder ser utilizadas, la
funcin principal es la nica que no puede ser usada por nadie: nadie
puede invocar a la funcin principal de un programa. Tampoco puede
llamarse a s misma (aunque este concepto de autollamada,
denominado recurrencia, lo trataremos ms adelante).
Adems de la funcin principal, en un programa se pueden encontrar
otras funciones: o funciones creadas y diseadas por el programador
para esa aplicacin, o funciones ya creadas e implementadas y
Fundamentos de informtica. Programacin en Lenguaje C


228
compiladas en libreras: de creacin propia o adquirida o pertenecientes
al estndar de ANSI C.
Las funciones estndar de ANSI C se encuentran clasificadas en distintas
libreras de acuerdo con las tareas que desarrollan. Al montar un
programa en C, se buscan en las libreras las funciones que se van a
necesitar, que se incluyen en el programa y se hacen as parte del
mismo.
Tambin se pueden crear las propias funciones en C. As, una vez
creadas y definidas, ya pueden ser invocadas tantas veces como se
quiera. Y as, podemos ir creando nuestras propias bibliotecas de
funciones.
Siempre que hemos hablado de funciones hemos utilizado dos verbos,
uno despus del otro: creacin y definicin de la funcin. Y es que en
una funcin hay que distinguir entre su declaracin o prototipo (creacin
de la funcin), su definicin (el cuerpo de cdigo que recoge las
sentencias que debe ejecutar la funcin para lograr llevar a cabo su
tarea) y, finalmente, su invocacin o llamada: una funcin creada y
definida slo se ejecuta si otra funcin la invoca o llama. Y en definitiva,
como la nica funcin que se ejecuta sin ser invocada (y tambin la
nica funcin que no permite ser invocada) es la funcin main,
cualquier funcin ser ejecutada nicamente si es invocada por la
funcin main o por alguna funcin que ha sido invocada por la funcin
main o tiene en su origen, en una cadena de invocacin, una llamada
desde la funcin main.

Declaracin de la funcin.
La declaracin de una funcin se realiza a travs de su prototipo. Un
prototipo tiene la forma:
tipo_ funcion nombre_ funcion( [ tipo1 [ var1] [ , tipoN [ varN] ] ) ;
Captulo 9. Funciones.


229
Donde tipo_ funcion declara de qu tipo es el valor que devolver la
funcin. Una funcin puede devolver valores de cualquier tipo de dato
vlido en C, tanto primitivo como diseado por el programador (se ver
la forma de crear tipos de datos en unos temas ms adelante). Si no
devuelve ningn valor, entonces se indica que es de tipo void.
Donde tipo1,, tipoN declara de qu tipo es cada uno de los valores
que la funcin recibir como parmetros al ser invocada. En la
declaracin del prototipo es opcional indicar el nombre que tomarn las
variables que recibirn esos valores y que se comportarn como
variables locales de la funcin. Sea como sea, ese nombre s deber
quedar recogido en la definicin de la funcin. Pero eso es adelantar
acontecimientos.
Al final de la declaracin viene el punto y coma. Y es que la declaracin
de una funcin es una sentencia en C. Una sentencia que se consigna
fuera de cualquier funcin. La declaracin de una funcin tiene carcter
global dentro de programa donde se declara. No se puede declarar, ni
definir, una funcin dentro de otra funcin: eso siempre dar error de
compilacin.
Toda funcin que quiera ser definida e invocada debe haber sido
previamente declarada. El prototipo de la funcin presenta el modo en
que esa funcin debe ser empleada. Es como la definicin de su
interface, de su forma de comunicacin: qu valores, de qu tipo y en
qu orden debe recibir la funcin como argumentos al ser invocada. El
prototipo permite localizar cualquier conversin ilegal de tipos entre los
argumentos utilizados en la llamada de la funcin y los tipos definidos
en los parmetros, entre los parntesis del prototipo. Adems, controla
que el nmero de argumentos usados en una llamada a una funcin
coincida con el nmero de parmetros de la definicin.
Existe una excepcin a esa regla: cuando una funcin es de tipo int,
puede omitirse su declaracin. Pero es recomendable no hacer uso de
esa excepcin. Si en una expresin, en una sentencia dentro del cuerpo
Fundamentos de informtica. Programacin en Lenguaje C


230
de una funcin, aparece un nombre o identificador que no ha sido
declarado previamente, y ese nombre va seguido de un parntesis de
apertura, el compilador supone que ese identificador corresponde al
nombre de una funcin de tipo int.
Todas las declaraciones de funcin deben preceder a la definicin del
cuerpo de la funcin main.

Definicin de la funcin.
Ya tenemos la funcin declarada. Con el prototipo ha quedado definido
el modo en que podemos utilizarla: cmo nos comunicamos nosotros
con ella y qu resultado nos ofrece.
Ahora queda la tarea de definirla.
Hay que escribir el cdigo, las sentencias, que van a realizar la tarea
para la que ha sido creada la funcin.
La forma habitual que tendr la definicin de una funcin la conocemos
ya, pues hemos visto ya muchas: cada vez que hacamos un programa,
y escribamos la funcin principal, estbamos definiendo esa funcin
main. Esa forma es:
tipo_ funcion nombre_ funcion( [ tipo1 var1] [ , tipoN varN] )
{
[ declaracin de variables locales]
[ cuerpo de la funcin: grupo de sentencias]
[ return( parmetro) ;]
}
Donde el tipo_funcin debe coincidir con el de la declaracin, lo mismo
que nombre_funcin y lo mismo que la lista de parmetros. Ahora, en la
definicin, los parmetros de la funcin siguen recogiendo el tipo de
dato y el nombre de la variable: pero ahora ese nombre NO es opcional.
Debe ponerse, porque esos nombres sern los identificadores de las
variables que recojan los valores que se le pasan a la funcin cuando se
la llama o invoca. A esas variables se las llama parmetros formales:
Captulo 9. Funciones.


231
son variables locales a la funcin: se crean cuando la funcin es
invocada y se destruyen cuando se termina la ejecucin de la funcin.
La lista de parmetros puede ser una lista vaca porque no se le quiera
pasar ningn valor a la funcin: eso es frecuente. En ese caso, tanto en
el prototipo como en la definicin, entre los parntesis que siguen al
nombre de la funcin se coloca la palabra clave void.
tipo_ funcin nombre_ funcin( void) ; / / declaracin del prototipo
Si la funcin no devuelve valor alguno, entonces se indica como de tipo
void, al igual que ya se hizo en la definicin del prototipo. Una funcin
declarada como de tipo void no puede ser usada como operando en una
expresin de C, porque esa funcin no tiene valor alguno. Una funcin
de tipo void puede mostrar datos por pantalla, escribir o leer ficheros,
etc.
El bloque de la funcin tiene tres partes: la declaracin de las variables
locales, el cuerpo de la funcin, donde estn las sentencias que llevarn
a cabo la tarea para la que ha sido creada y definida la funcin, y la
sentencia return, de la que hablaremos enseguida.
El bloque de la funcin viene recogido entre llaves. Aunque la funcin
tenga una sola sentencia, es obligatorio recoger esa sentencia nica
entre las llaves de apertura y de cerrado.
Las variables creadas en el cuerpo de la funcin sern locales a ella. Se
pueden usar identificadores idnticos para nombrar distintas variables
de diferentes funciones, porque cada variable de cada funcin pertenece
a un mbito completamente disjunto al mbito de otra funcin, y no hay
posibilidad alguna de confusin. Cada variable tendr su direccin y su
mbito distintos.
Aunque ya se ha dicho anteriormente, recordamos que todas las
funciones en C, sin excepcin alguna, estn en el mismo nivel de
mbito, es decir, no se puede declarar ninguna funcin dentro de otra
Fundamentos de informtica. Programacin en Lenguaje C


232
funcin, y no se puede definir una funcin como bloque interno en el
cuerpo de otra funcin.

Llamada a la funcin
La llamada a una funcin es una sentencia habitual en C. Ya la hemos
usado con frecuencia, invocando hasta el momento nicamente
funciones de biblioteca. Pero la forma de invocar es la misma para
cualquier funcin.
nombre_ funcin( [ argumento1] [ , , argumentoN] ) ;
La sentencia de llamada est formada por el nombre de la funcin y sus
argumentos (los valores que se le pasan) que deben ir recogidos en el
mismo orden que la secuencia de parmetros del prototipo y entre
parntesis. Si la funcin no recibe parmetros (porque as est definida),
entonces se coloca despus de su nombre los parntesis de apertura y
cerrado sin ninguna informacin entre ellos. Si no se colocan los
parntesis, se produce un error de compilacin.
El paso de parmetros en la llamada exige una asignacin para cada
parmetro. El valor del primer argumento introducido en la llamada a la
funcin queda asignado en la variable del primer parmetro formal de la
funcin; el segundo valor de argumento queda asignado en el segundo
parmetro formal de la funcin; y as sucesivamente. Hay que asegurar
que el tipo de dato de los parmetros formales es compatible en cada
caso con el tipo de dato usado en lista de argumentos en la llamada de
la funcin. El compilador de C no dar error si se fuerzan cambios de
tipo de dato incompatibles, pero el resultado ser inesperado
totalmente.
La lista de argumentos estar formada por nombres de variables que
recogen los valores que se desean pasar, o por literales. No es necesario
(ni es lo habitual) que los identificadores de los argumentos que se
Captulo 9. Funciones.


233
pasan a la funcin cuando es llamada coincidan con los identificadores
de los parmetros formales.
Las llamadas a las funciones, dentro de cualquier funcin, pueden
realizarse en el orden que sea necesario, y tantas veces como se quiera,
independientemente del orden en que hayan sido declaradas o definidas.
Incluso se da el caso, bastante frecuente como veremos ms adelante,
que una funcin pueda llamarse a s misma. Esa operacin de
autollamada se llama recurrencia.
Si la funcin debe devolver un valor, con cierta frecuencia interesar
que la funcin que la invoca almacene ese valor en una variable local
suya. En ese caso, la llamada a la funcin ser de la forma:
variable = nombre_ funcin( [ argumento1] [ , , argumentoN] ) ;
Aunque eso no siempre se hace necesario, y tambin con frecuencia
encontraremos las llamadas a las funciones como partes de una
expresin.

La sentencia return
Hay dos formas ordinarias de terminar la ejecucin de una funcin.
1. Llegar a la ltima sentencia del cuerpo, antes de la llave que cierra el
bloque de esa funcin.
2. Llegar a una sentencia return. La sentencia return fuerza la salida
de la funcin, y devuelve el control del programa a la funcin que la
llam, en la sentencia inmediatamente posterior a la de la llamada a
la funcin.
Si la funcin es de un tipo de dato distinto de void, entonces en el
bloque de la funcin debe recogerse, al menos, una sentencia return.
En ese caso, adems, en esa sentencia y a continuacin de la palabra
return, deber ir el valor que devuelve la funcin: o el identificador de
Fundamentos de informtica. Programacin en Lenguaje C


234
una variable o un literal, siempre del mismo tipo que el tipo de la
funcin o de otro tipo compatible.
Una funcin tipo void no necesariamente tendr la sentencia return. En
ese caso, la ejecucin de la funcin terminar con la sentencia ltima
del bloque. Si una funcin de tipo void hace uso de sentencias return,
entonces en ningn caso debe seguir a esa palabra valor alguno: si as
fuera, el compilador detectar un error y no compilar el programa.
La sentencia return puede encontrarse en cualquier momento del
cdigo de una funcin. De todas formas, no tendra sentido recoger
ninguna sentencia ms all de una sentencia return que no estuviera
condicionada, pues esa sentencia jams llegara a ejecutarse.
En resumen, la sentencia return realiza bsicamente dos operaciones:
1. Fuerza la salida inmediata del cuerpo de la funcin y se vuelve a la
siguiente sentencia despus de la llamada.
2. Si la funcin no es tipo void, entonces adems de terminar la
ejecucin de la funcin, devuelve un valor a la funcin que la llam.
Si esa funcin llamante no recoge ese valor en una variable, el valor
se pierde, con todas las variables locales de la funcin abandonada.
La forma general de la sentencia return es:
return [ expresin] ;
Muchos programadores habitan a colocar la expresin del return entre
parntesis. Es opcional, como lo es en la redaccin de cualquier
expresin.
Si el tipo de dato de la expresin del return no coincide con el tipo de la
funcin entonces, de forma automtica, el tipo de dato de la expresin
se convierte en el tipo de dato de la funcin.
Ha llegado el momento de ver algunos ejemplos. Veamos primero una
funcin de tipo void: una que muestre un mensaje por pantalla.
Captulo 9. Funciones.


235
Declaracin: void most r ar ( short) ;
Definicin:
void most r ar ( short x)
{
pr i nt f ( El val or r eci bi do es %hd. , x) ;
}
Llamada: most r ar ( 10) ;
que ofrece la siguiente salida por pantalla:
El val or r eci bi do es 10.
Otro ejemplo: Una funcin que reciba un entero y devuelva el valor de
su cuadrado.
Declaracin: unsigned long int cuadr ado( short) ;
Definicin:
unsigned long int cuadr ado( short x)
{
return x * x;
}
Una posible llamada:
pr i nt f ( El cuadr ado de %hd es %l d. \ n, a, cuadr ado( a) ) ;
Un tercer ejemplo, ahora con dos sentencias return: una funcin que
reciba como parmetros formales dos valores enteros y devuelve el
valor del mayor de los dos:
Declaracin: short mayor ( short, short) ;
Definicin:
short mayor ( short x, short y)
{
if( x > y) return x;
else return y;
}
Desde luego la palabra else podra omitirse, porque jams se llegar a
ella si se ejecuta el primer return, y si la condicin del if es falsa,
entonces se ejecuta el segundo return.
Fundamentos de informtica. Programacin en Lenguaje C


236
Otra posible definicin:
short mayor ( short x, short y)
{
x > y ? return( x) : return( y) ;
}
Llamada:
A = mayor ( a, b) ;
Donde la variable A guardar el mayor de los dos valores entre a y b.
Una ltima observacin: el tipo de la funcin puede ser un tipo de dato
puntero. En ese caso el valor que devuelve la funcin ser una direccin
de memoria donde se aloja un valor, o un vector, o una matriz. Ms
adelante, en los siguientes captulos, veremos algn ejemplo donde
este tipo de funcin resulta de gran utilidad.

mbito y vida de las variables
Ya conocemos el concepto de mbito de la variable. Y ahora que ya
sabemos algo de las funciones, es conveniente presentar cundo se
puede acceder a cada variable, cundo diremos que est viva, etc.
Veamos un programa ya conocido, el del clculo del factorial de un
entero, resuelto ahora mediante funciones:
#i ncl ude <st di o. h>
long Fact or i al ( short) ;
void mai n( void)
{
short n;
pr i nt f ( " I nt r oduzca el val or de n . . . " ) ;
scanf ( " %hd" , &n) ;
pr i nt f ( " El f act or i al de %hd " , n) ;
pr i nt f ( " es %l d" , Fact or i al ( n) ) ;
}

long Fact or i al ( short a)
{
long F = 1;
while( a) F *= a- - ;
return F;
}
Captulo 9. Funciones.


237
En este programa, la funcin principal main tiene definida una variable
de tipo short, a la que hemos llamado n. En esa funcin, esa variable es
local, y podemos recoger sus caractersticas en la cudrupla:
< > , , ,
n n
n R V short
La variable, de tipo short, n se almacena en la direccin de memoria
n
R
y guardar el valor que reciba de la funcin scanf.
La funcin main invoca a la funcin Factorial. En la llamada se pasa
como parmetro el valor de la variable n. En esa llamada, el valor de la
variable n se copia en la variable a de Factorial:
< > , , ,
a n
a R V short
Desde el momento en que se produce la llamada a la funcin Factorial,
abandonamos el mbito de la funcin main. En este momento, la
variable n est fuera de mbito y no puede, por tanto hacerse uso de
ella. No ha quedado eliminada: estamos en el mbito de Factorial pero
an no han terminado todas las sentencias de main. En el clculo
dentro de la funcin Factorial se ve modificado el valor de la variable
local a. Pero esa modificacin para nada influye en la variable n, que
est definida en otra posicin de memoria distinta.
Cuando se termina la ejecucin de la funcin Factorial, el control del
programa vuelve a la funcin main. La variable a y la variable F
mueren, pero el valor de la variable F ha sido recibido como parmetro
en la funcin printf, y as podemos mostrarlo por pantalla. Ahora, de
nuevo en la funcin principal, volvemos al mbito de la variable n, de la
que podramos haber hecho uso si hubiera sido necesario.
Veamos ahora otro ejemplo, con un programa que calcule el mximo
comn divisor de dos enteros. De nuevo, resolvemos el problema
mediante funciones:
#i ncl ude <st di o. h>
long eucl i des( long, long) ;
void mai n( void)
{
Fundamentos de informtica. Programacin en Lenguaje C


238
long n1, n2;
do
{
pr i nt f ( " I nt r oduzca el val or de n1 . . . " ) ;
scanf ( " %l d" , &n1) ;
pr i nt f ( " I nt r oduzca el val or de n2 . . . " ) ;
scanf ( " %l d" , &n2) ;
if( n2 ! = 0)
pr i nt f ( " \ nEl mcd de %l d y %l d , n1, n2) ;
pr i nt f ( es %l d\ n" , eucl i des( n1, n2) ) ;
}while( n2 ! = 0) ;
}

long eucl i des( long a, long b)
{
static short cont = 0;
long mcd;
while( b)
{
mcd = b;
b = a %b;
a = mcd;
}
pr i nt f ( " I nvocaci ones a l a f unci n . . . %hd\ n" , ++cont ) ;
return mcd;
}
En esta ocasin, adems, hemos incluido una variable static en la
funcin euclides. Esta variable nos informar de cuntas veces se ha
ejecutado la funcin.
Las variables n1 y n2, de main, dejan de estar accesibles cuando se
invoca a la funcin euclides. En ese momento se copian sus valores en
las variables a y b que comienzan a existir precisamente en el momento
de la invocacin de la funcin. Adems de esas variables locales, y de la
variable local mcd, se ha creado otra, llamada cont, que es tambin
local a euclides pero que, a diferencia de las dems variables locales, no
desaparecer cuando se ejecute la sentencia return y se devuelva el
control de la aplicacin a la funcin main: es una variable declarada
static. Cuando eso ocurra, perder la variable cont su mbito, y no
podr ser accedida, pero en cuanto se invoque de nuevo a la funcin
euclides, all estar la variable, ya creada, accesible para cuando la
funcin la requiera.
Captulo 9. Funciones.


239

Recurrencia
Ya lo hemos comentado antes. Una funcin decimos que es recurrente si
existe una llamada a s misma en el cuerpo de su definicin.
Conceptualmente la recurrencia no ofrece mucha complicacin. El quid
de la recurrencia est en saber utilizarla. Hay problemas donde la
recurrencia cabe perfectamente, y agiliza mucho los algoritmos de
solucin. Implementar una funcin mediante recurrencia es sencillo en
C. El problema no est en el C, sino en llegar al algoritmo que haga uso
de ella.
Lo ms adecuado para explicar la recurrencia es ver algunos ejemplos.
Veremos la solucin de los dos programas del epgrafe anterior,
solventados ahora mediante recurrencia. Comenzamos por el clculo del
factorial.
Por definicin, el factorial de un entero positivo es igual al producto de
ese entero por el factorial del entero inmediatamente inferior:
= ! ( 1)! n n n
Esta forma de ver el factorial lleva directamente a la recurrencia:
efectivamente, en la definicin de factorial nos encontramos que hemos
recurrido al concepto de factorial. La implementacin de la funcin
podra ser la siguiente:
long Fact or i al ( short A)
{
if( A == 0) return 1;
else return a * Fact or i al ( A - 1) ;
}
Que tambin podra haberse escrito de la siguiente forma:
long Fact or i al ( short A)
{
return A ? A * Fact or i al ( A - 1) : 1;
}
Fundamentos de informtica. Programacin en Lenguaje C


240
Un comentario a la funcin. Cada vez que la funcin es invocada por s
misma, se crea de nuevo una variable A, distinta de la variable A creada
en la anterior invocacin. Para cada llamada creamos un juego de
variables cuyo mbito es el de esta llamada, y su vida el tiempo que se
tarde en ejecutar la ltima sentencia del bloque de la funcin.
Supongamos que queremos conocer el valor del factorial de 3.
Invocamos a la funcin Factorial con ese valor como argumento.
pr i nt f ( El f act or i al de %hd es %l d. \ n, 3, Fact or i al ( 3) ) ;
Primera llamada: se crea la variable < >
1
, , ,3 A R short . Como A es
distinto de cero, no se devuelve el entero 1, sino el producto de A por el
Factorial de (A 1). Entonces, antes de terminar la ejecucin de la
funcin Factorial y eliminar la variable A localizada en
1
R necesitamos
recibir el valor de Factorial de (A 1).
Segunda llamada: se crea la variable < >
2
, , ,2 A R short . Con el mismo
nombre que en la llamada anterior, son variables diferentes, ubicadas
en posiciones de memoria diferentes. En este momento, la variable en
mbito es la ubicada en
2
R ; la ubicada en
1
R no es accesible: siempre
que en esta segunda ejecucin de la funcin Factorial hagamos
referencia a la variable A, se entiende que nos referimos a la ubicada en
2
R . Como esta variable A no vale 0, entonces la funcin devuelve el
valor del producto de A (la de
2
R ) por Factorial(A 1). Y de nuevo,
antes de terminar la ejecucin de la funcin Factorial y eliminar la
variable A localizada en
2
R necesitamos recibir el valor de Factorial(A
1).
Tercera llamada: se crea la variable < >
3
, , ,1 A R short . Con el mismo
nombre que en las dos llamadas anteriores, son variables diferentes,
ubicadas en posiciones de memoria diferentes. En este momento, la
variable en mbito es la ubicada en
3
R ; las ubicadas en
1
R y
2
R no son
accesibles: siempre que en esta tercera ejecucin de la funcin Factorial
hagamos referencia a la variable A, se entiende que nos referimos a la
Captulo 9. Funciones.


241
ubicada en
3
R . Como esta variable A no vale 0, entonces la funcin
devuelve el valor del producto de A (la de
3
R ) por Factorial(A 1). Y de
nuevo, antes de terminar la ejecucin de la funcin Factorial y eliminar
la variable A localizada en
3
R necesitamos recibir el valor de Factorial(A
1).
Cuarta llamada: se crea la variable < >
4
, , , 0 A R short . Con el mismo
nombre que en las tres llamadas anteriores, son variables diferentes,
ubicadas en posiciones de memoria diferentes. En este momento, la
variable en mbito es la ubicada en
4
R ; las ubicadas en
1
R ,
2
R y
3
R no
son accesibles: siempre que en esta cuarta ejecucin de la funcin
Factorial hagamos referencia a la variable A, se entiende que nos
referimos a la ubicada en
4
R . El valor de esta variable es 0 por lo que la
funcin devuelve el valor 1 y termina su ejecucin. La variable A ubicada
en
4
R termina su existencia y el control del programa vuelve a quien
llam a la funcin.
Quien llam a la funcin fue la propia funcin Factorial, en su tercera
llamada. Estaba pendiente, para cerrarse y devolver un valor, a recibir
un valor de la funcin Factorial. Y ha recibido el valor 1, que multiplica al
valor de A que tambin es 1, y devuelve a quien la llam. La variable A
ubicada en
3
R termina su existencia y el control del programa vuelve a
quien llam a la funcin.
Y quien llam a la funcin fue la propia funcin Factorial, en su segunda
llamada. Estaba pendiente, para cerrarse y devolver un valor, a recibir
un valor de la funcin Factorial. Y ha recibido el valor 1, que multiplica al
valor de A que es 2, y devuelve a quien la llam. La variable A ubicada
en
2
R termina su existencia y el control del programa vuelve a quien
llam a la funcin.
Y quien llam a la funcin fue la propia funcin Factorial, en su primera
llamada. Estaba pendiente, para cerrarse y devolver un valor, a recibir
un valor de la funcin Factorial. Y ha recibido el valor 2, que multiplica al
valor de A que es 3, y devuelve a quien la llam. La variable A ubicada
Fundamentos de informtica. Programacin en Lenguaje C


242
en
1
R termina su existencia y el control del programa vuelve a quien
llam a la funcin.
Y quien llam a la funcin Factorial fue la funcin principal, que vuelve a
recuperar el control de ejecucin del programa y que recibe el valor
devuelto por la funcin que se lo pasa como parmetro a la funcin
printf para que muestre ese valor por pantalla:
El f act or i al de 3 es 6.
Cuatro ejecuciones, cuatro mbitos, cuatro variables distintas, cuatro
vidas distintas.
Veamos ahora el segundo ejemplo: el de la funcin euclides. En esta
funcin la recurrencia es an ms clara, puesta la propia definicin del
algoritmo de Euclides es autodefinida. El cdigo de la funcin podra
quedar as:
long eucl i des( long a, long b)
{
return b ? eucl i des( b, a %b) : a;
}
No vamos a comentar su ejecucin con tanta largueza como en la
funcin Factorial. Pero as escrita, la funcin euclides se comporta tal y
como qued definido el algoritmo en el captulo 8. All decamos:
Euclides, matemtico del siglo V a. de C. present un algoritmo muy
fcil de implementar, y de muy bajo coste computacional. El algoritmo
de Euclides dice que el mximo comn divisor de dos enteros
1
a y
1
b
(diremos
1 1
( , ) mcd a b ), donde
1
0 b es igual a
2 2
( , ) mcd a b donde
=
2 1
a b y donde =
2 1 1
% b a b , entendiendo por
1 1
% a b el resto de la
divisin de
1
a con
1
b . Y el proceso puede seguirse hasta llegar a unos
valores de
i
a y de
i
b que verifiquen que 0
i
a , 0
i
b y = % 0
i i
a b .
Entonces, el algoritmo de Euclides afirma que, llegado a estos valores el
valor buscado es =
1 1
( , )
i
mcd a b b . Y eso es precisamente lo que se ha
implementado en esta funcin.

Captulo 9. Funciones.


243
Llamadas por valor y llamadas por referencia
Estos dos nuevos conceptos son tradicionales al hablar de funciones. Y
muy importantes. Hacen referencia al modo en que la funcin recibe los
parmetros.
Hasta ahora, en todos los ejemplos previos presentados, hemos
trabajado haciendo llamadas por valor. Decimos que una funcin es
llamada por valor cuando se copia el valor del argumento en el
parmetro formal de la funcin. Una variable est en la funcin que
llama; y otra variable, distinta, es la que recibe el valor en la funcin
llamada. La funcin llamada no puede alterar el valor del argumento
original de la funcin que llama. nicamente puede cambiar el valor de
su variable local que ha recibido por asignacin el valor de esa variable
en el momento en que se realiz la llamada a la funcin. As, en la
funcin llamada, cada argumento es efectivamente una variable local
inicializada con el valor con que se llam a la funcin.
Pero supongamos que necesitamos en nuestro programa realizar con
mucha frecuencia la tarea de intercambiar el valor de dos variables. Ya
sabemos cmo se hace, y lo hemos visto resuelto tanto a travs de una
variable auxiliar como gracias al operador or exclusivo. Sera muy
conveniente disponer de una funcin a la que se le pudieran pasar, una
y otra vez, el par de variables de las que deseamos intercambiar sus
valores. Pero cmo lograr hacer ese intercambio a travs de una
funcin si todo lo que se realiza en la funcin llamada muere cuando
termina su ejecucin? Cmo lograr que en la funcin que invoca ocurra
realmente el intercambio de valores entre esas dos variables?
La respuesta no es trivial: cuando invocamos a la funcin (que
llamaremos en nuestro ejemplo intercambio), las variables que
deseamos intercambiar dejan de estar en su mbito y no llegamos a
ellas. Toda operacin en memoria que realice la funcin intercambio
morir con su ltima sentencia: su nico rastro ser, si acaso, la
Fundamentos de informtica. Programacin en Lenguaje C


244
obtencin de un resultado, el que logra sobrevivir de la funcin gracias a
la sentencia return.
Y aqu llegamos a la necesidad de establecer otro tipo de llamadas a
funciones. las llamadas por referencia. En este tipo de llamada, lo
que se transfiere a la funcin no es el valor del argumento, sino la
direccin de memoria de la variable argumento. Se copia la direccin del
argumento en el parmetro formal, y no su valor.
Evidentemente, en ese caso, el parmetro formal deber ser de tipo
puntero. En ese momento, la variable argumento quedar fuera de
mbito, pero a travs del puntero correspondiente en los parmetros
formales podr llegar a ella, y modificar su valor.
La funcin intercambio podra tener el siguiente prototipo:
void i nt er cambi o( long*, long*) ;
Y su definicin podra ser la siguiente:
void i nt er cambi o( long*a, long*b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
O tambin
void i nt er cambi o( long*a, long*b)
{
short aux;
aux = *b;
*b = *a;
*a = aux;
}
Supongamos que la funcin que llama a la funcin intercambio lo hace
de la siguiente forma:
i nt er cambi o( &x, &y) ;
Donde lo que le pasa son las direcciones (no los valores) de las dos
variables de las que se desea intercambiar sus valores.
Captulo 9. Funciones.


245
En la funcin llamante tenemos:
< > , , ,
x x
x R V short y < > , , ,
y y
y R V short
En la funcin intercambio tenemos:
< > , , ,
a x
a R R short* y < > , , ,
b y
b R R short*
Es decir, dos variables puntero cuyos valores que se le van asignar
sern las posiciones de memoria de cada una de las dos variables
usadas como argumento, y que son con las que se desea realizar el
intercambio de valores.
La funcin trabaja sobre los contenidos de las posiciones de memoria
apuntadas por los dos punteros. Y cuando termina la ejecucin de la
funcin, efectivamente, mueren las dos variables puntero a y b creadas.
Pero ya han dejado hecha la faena en las direcciones que recibieron al
ser creadas: en
x
R ahora queda codificado el valor
y
V ; y en
y
R queda
codificado el valor
x
V . Y en cuanto termina la ejecucin de intercambio
regresamos al mbito de esas dos variables x e y: y nos las
encontramos con los valores intercambiados.
Muchos son los ejemplos de funciones que, al ser invocadas, reciben los
parmetros por referencia. La funcin scanf recibe el parmetro de la
variable sobre la que el usuario deber indicar su valor con una llamada
por referencia. Tambin lo hemos visto en la funcin gets, que recibe
como parmetro la direccin de la cadena de caracteres donde se
almacenar la cadena que introduzca el usuario.
Por otro lado, siempre que deseemos que una funcin nos devuelva ms
de un valor tambin ser necesario utilizar llamadas por referencia: uno
de los valores deseamos podremos recibirlo gracias a la sentencia
return de la funcin llamada; los dems podrn quedar en los
argumentos pasados como referencia: entregamos a la funcin sus
direcciones, y ella, al terminar, deja en esas posiciones de memoria los
resultados deseados.
Fundamentos de informtica. Programacin en Lenguaje C


246

Vectores y matrices como argumentos
Y si podemos pasar la direccin de una variable, entonces tambin
podemos pasar la direccin de un array, o de una matriz, o de una
cadena de caracteres.
As, cuando queremos pasar como argumento un vector, no es necesario
hacer copia de todo l en la lista de parmetros: basta pasar como
parmetro la direccin del primer elemento del vector. La funcin podr
acceder a todos los elementos del vector mediante operatoria de
punteros o mediante ndices.
Habitualmente, al pasar un array o matriz, ser necesario pasar, como
otros parmetros, la dimensin o dimensiones de ese array o matriz.
La llamada de la funcin usar el nombre del vector como argumento,
ya que como dijimos al presentar los arrays y las cadenas, el nombre de
un array o cadena, en C, indica su direccin: decir nombre_vector es lo
mismo que decir &nombre_vector[0].
Evidentemente, y como siempre, el tipo de dato puntero del parmetro
formal debe ser compatible con el tipo de dato del vector argumento.
Existen tres formas de declarar un parmetro formal que va a recibir un
puntero a un vector:
tipo nombre( tipo vector[ dimensin] ) ; es decir, declarando el
parmetro como un vector dimensionado.
tipo nombre( tipo vector[ ] ) ; es decir, declarando el parmetro como
un vector sin tamao determinado.
tipo nombre( tipo* ) ; es decir, declarando el parmetro como un
puntero.
Veamos un ejemplo. Hagamos una aplicacin que reciba un array de
variables tipo float y nos indique cul es el menor de sus valores y cul
Captulo 9. Funciones.


247
el mayor. Entre los parmetros de la funcin ser necesario indicar
tambin la dimensin del vector.
Lo primero que hemos de pensar es cmo pensamos devolver, a la
funcin que llame a nuestra funcin, los dos valores solicitados. Repito:
DOS valores solicitados. No podremos hacerlo mediante un return,
porque as slo podramos facilitar uno de los dos. Por eso, entre los
parmetros de la funcin tambin necesitamos dos que sean la direccin
donde deberemos dejar recogido el mayor de los valores y la direccin
donde deber ir recogido el menor de ellos.
Entonces la funcin podr tener el siguiente prototipo:
void ext r emos( float*v, unsinged short d, float*M, float*m) ;
El primer parmetro es la direccin del array donde se recogen todos los
valores. En segundo parmetro la dimensin del array. El tercero y el
cuarto las direcciones donde se consignarn los valores mayor (variable
M) y menor (variable m) del array.
El cdigo de la funcin podra ser el siguiente:
void ext r emos( float*v, unsigned short d, float*M, float*m)
{
short int i ;
*M = *v;
*m= *v;
for( i = 0 ; i < d ; i ++)
{
if( *M < *( v + i ) ) *M = *( v + i ) ;
if( *m> *( v + i ) ) *m= *( v + i ) ;
}
}
Inicialmente hemos puesto como menor y como mayor el primero de los
elementos del vector. Y luego lo hemos recorrido, y siempre que hemos
encontrado un valor mayor que el que tenamos consignado como
mayor, hemos cambiado y hemos guardado ese nuevo valor como el
mayor; y lo mismo hemos hecho con el menor. Y al terminar de recorrer
el vector, ya han quedado esos dos valores guardados en las direcciones
de memoria que hemos recibido como parmetros.
Fundamentos de informtica. Programacin en Lenguaje C


248
Para llamar a esta funcin bastar la siguiente sentencia:
ext r emos( vect or , di mensi on, &mayor , &menor ) ;
La recepcin de un vector como parmetro formal no necesariamente
debe hacerse desde el primer elemento del vector. Supongamos que al
implementar la funcin extremos exigimos, como especificacin tcnica
de esa funcin, que la matriz tenga una dimensin impar. Y diremos que
reciba como parmetros los mismos que antes. Pero ahora la direccin
de memoria del vector ser la del elemento que est a la mitad del
vector. Si la dimensin es n , el usuario de la funcin deber pasar como
argumento la direccin del elemento de ndice ( 1) / 2 n . Y el argumento
segundo deber ser, en lugar de la dimensin del vector, el valor
indicado ( 1) / 2 n .
El prototipo de la funcin es exactamente el mismo que antes:
void ext r emos2( float*v, unsinged short d, float*M, float*m) ;
Y su definicin podra ser la siguiente:
void ext r emos2( float*v, unsigned short d, float*M, float*m)
{
short int i ;
*M = *v;
*m= *v;
for( i = 0 ; i < d ; i ++)
{
if( *M < *( v + i ) ) *M = *( v + i ) ;
if( *M < *( v - i ) ) *M = *( v - i ) ;
if( *m> *( v + i ) ) *m= *( v + i ) ;
if( *M < *( v - i ) ) *M = *( v - i ) ;
}
}
Donde as logramos la misma operacin de bsqueda y hemos reducido
a la mitad los incrementos de la variable contador i. No entramos ahora
en analizar la oportunidad de esta nueva versin de la funcin.
Queremos sealar simplemente que el cdigo puede hacer lo que a
nosotros nos convenga ms. Lo importante en este caso es dejar bien
especificadas las condiciones para el correcto uso de la funcin. Y vigilar
Captulo 9. Funciones.


249
cules son los lmites del vector y no permitir que se acceda a posiciones
de memoria que estn fuera de la dimensin del vector.

Funciones de escape
Existen ocasiones en que lo mejor que se puede hacer es abortar a
ejecucin de una aplicacin antes de llegar a consecuencias ms
desastrosas si se continuara la ejecucin del programa. A veces ms
vale abortar misin intentando salvar la mayor parte de los muebles,
que dejar que una situacin irresoluble arruine la lnea de ejecucin y
entonces se produzca la interrupcin de la ejecucin de una forma no
controlada por nosotros.
Para realizar esas operaciones de salida inmediata disponemos de dos
funciones, definidas en la biblioteca stdlib.h: la funcin exit y la funcin
abort.
La funcin exit nos permite abandonar la ejecucin de un programa
antes de su finalizacin, volviendo el control al sistema operativo. Antes
de regresar ese control, realiza algunas tareas importantes: por
ejemplo, si el programa tiene abiertos algunos archivos, la funcin los
cierra antes de abandonar la ejecucin de la aplicacin, y as esos
archivos no se corrompen. Esta funcin se utiliza cuando no se cumple
una condicin, imprescindible para la correcta ejecucin del programa.
El prototipo de la funcin es
void exit( int status) ;
El parmetro status indica el modo en que se debe realizar la operacin
de finalizacin inmediata del programa. El valor habitual es el cero, e
indica que se debe realizar una salida inmediata llamada normal.
Ahora mismo no vamos a poner ejemplos de uso de esta funcin. Pero
ms adelante, en el prximo tema, se vern ocasiones claras en que su
uso es muy necesario.
Fundamentos de informtica. Programacin en Lenguaje C


250
La funcin abort es semejante. Su prototipo es:
void abort( void)
Y nos permite abandonar de forma anormal la ejecucin del programa,
antes de su finalizacin. Escribe el mensaje Abnormal program
termination y devuelve el control al sistema operativo.

Ejercicios

54. Escribir un programa que solicite al usuario dos enteros y
calcule, mediante una funcin, el mximo comn divisor.
Definir otra funcin que calcule el mnimo comn mltiplo,
teniendo en cuenta que siempre se verifica que
a b = mcd( a, b) mcm( a, b) .

#i ncl ude <st di o. h>

/ / Decl ar aci n de l as f unci ones . . .
short mcd( short, short) ;
long mcm( short, short) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
short a, b;

pr i nt f ( " I nt r oduzca el val or de a . . . " ) ;
scanf ( " %hd" , &a) ;

pr i nt f ( " I nt r oduzca el val or de b . . . " ) ;
scanf ( " %hd" , &b) ;

pr i nt f ( " El mxi mo comn di vi sor de %hd y %hd " , a, b) ;
pr i nt f ( " es %hd" , mcd( a, b) ) ;
pr i nt f ( " \ ny el m ni mo comn ml t i pl o %l d. " , mcm( a, b) ) ;

f l ushal l ( ) ;
get char ( ) ;
}
Captulo 9. Funciones.


251

/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ * Funci n cl cul o del mxi mo comn di vi sor . - - - - - - - - - - - - - */
short mcd( short a, short b)
{
short m;
while( b)
{
m= a %b;
a = b;
b = m;
}
return a;
}


/ * Funci n cl cul o del m ni mo comn ml t i pl o. - - - - - - - - - - - - */
long mcm( short a, short b)
{
return a * ( long) b / mcd( a, b) ;
}

55. Haga un programa que calcule el trmino n (a determinar en la
ejecucin del programa) de la serie de Fibonacci. El programa
deber utilizar una funcin, llamada fibonacci, cuyo prototipo
sea
short fibonacci( short) ;
Que recibe como parmetro el valor de n, y devuelve el trmino
n-simo de la Serie.

#i ncl ude <st di o. h>
#i ncl ude <coni o. h>

unsigned long f i bonacci ( short) ;

void mai n( void)
{
short N;
pr i nt f ( " I ndi que el t r mi no de l a ser i e: " ) ;
Fundamentos de informtica. Programacin en Lenguaje C


252
scanf ( " %hd" , &N) ;

pr i nt f ( " \ nEl t r mi no %hd es %l u. " , N, f i bonacci ( N) ) ;
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ * Funci n Fi bonacci . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
unsigned long f i bonacci ( short x)
{
unsigned long f i b1 = 1 , f i b2 = 1, Fi b = 1;

while( x > 2)
{
Fi b = f i b1 + f i b2;
f i b1 = f i b2;
f i b2 = Fi b;
x- - ;
}
return Fi b;
}

56. Escriba un programa que solicite al usuario un entero y
devuelva el factorial de ese entero. Utilice una funcin para el
clculo del factorial.

#i ncl ude <st di o. h>

/ / Decl ar aci n de l a f unsi n Fact or i al . . .
unsigned long f act or i al ( short) ;
/ / Funci n pr i nci pal . . .
void mai n( void)
{
short N;
pr i nt f ( " I ndi que un ent er o menor que 13: " ) ;
scanf ( " %hd" , &N) ;

pr i nt f ( " \ nEl f act or i al de %hd, N) ;
pr i nt f ( es %l u. " , f act or i al ( N) ) ;
}

/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Captulo 9. Funciones.


253
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ * Funci n Fact or i al . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
unsigned long f act or i al ( short x)
{
unsigned long F = 1;
while( x) F *= x- - ;
return F;
}

57. Escriba una funcin que reciba como parmetros un vector de
enteros y un entero que recoj a la dimensin del vector, y
devuelva ese vector, con los valores enteros ordenados de
menor a mayor.
Un posible programa que utilice esta funcin que presentamos podra
ser el siguiente:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>

#def i ne TAM 100

/ / Decl ar aci n de l as f unci ones . . .
void or denar ( long *, short) ;
void most r ar ( long *, short) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
long vect or [ TAM] ;

r andomi ze( ) ;
for( int i = 0 ; i < TAM ; i ++)
vect or [ i ] = r andom( 1000) ;

most r ar ( vect or , TAM) ; / / Ant es de or denar .
or denar ( vect or , TAM) ; / / Se or dena el vect or .
most r ar ( vect or , TAM) ; / / Despes de or denar .
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
Fundamentos de informtica. Programacin en Lenguaje C


254

/ * Funci n de or denaci n. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void or denar ( long *v , short d)
{
for( short i = 0 ; i < d ; i ++)
for( short j = i + 1 ; j < d ; j ++)
if( *( v + i ) > *( v + j ) )
{
*( v + i ) ^= *( v + j ) ;
*( v + j ) ^= *( v + i ) ;
*( v + i ) ^= *( v + j ) ;
}
}

/ * Funci n que muest r a el vect or que r eci be como par met r o. */
void most r ar ( long *v , short d)
{
pr i nt f ( " \ n\ n" ) ;
for( short i = 0 ; i < d ; i ++)
pr i nt f ( " %5l d" , *( v + i ) ) ;
}

Hewmos definido dos funciones: una muestra las valores de un vector
que recibe como primer parmetro, cuya dimensin queda indicada por
el segundo parmetro.
Las dos funciones reciben como parmetros el valor de la dimensin,
que es un valor tomado de una directiva define, prefectamente accesible
en cualquier punto del programa. Y es que aunque el valor de la macro
TAM es accesible desde ambas funciones, stas, como es exigido en un
correcto diseo de funciones, no dependen para nada del entorno en el
que estn definidas. Tal y como estn implementadas en nuestro
ejercicio pueden ser exportadas a cualquier sitio, pues para nada
depende su correcta ejecucin de ningn valor o parmetro, o macro,
definido en el entorno en el que se han definido aqu y ahora ambas
funciones.
En todo momento se ha utilizado en las fucniones la aritmtica de
punteros y el operador indireccin. Evidentemente se podra hacer lo
mismo con los ndices del vector. De hecho la expresin *(v + i) es
siempre intercambiable por la expresin v[i].
Captulo 9. Funciones.


255

58. Escriba una aplicacin que reciba un entero y busque todos sus
divisores, dej ndolos en un vector.
La aplicacin debe tener una funcin que se encargar de
buscar los divisores. Esta funcin recibir como parmetros.el
entero sobre el que hay que buscar sus divisores, el vector
donde debe dej ar los enteros, y la dimensin del vector.
La funcin devuelve un valor positivo igual al nmero de
divisores hallados si el proceso termina correctamente. Deber
devolver un valor negativo si falla en el proceso de bsqueda
de los divisores.

#i ncl ude <st di o. h>

#def i ne TAM 100

/ / Decl ar aci n de l as f unci ones . . .
short di vi sor es( long, long*, short) ;
void most r ar ( long *, short) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
long N, vect or [ TAM] ;
short di v;

pr i nt f ( " I nt r oduzca un ent er o . . . " ) ;
scanf ( " %l d" , &N) ;

if( ( di v = di vi sor es( N, vect or , TAM) ) > 0)
most r ar ( vect or , di v) ;
else
pr i nt f ( " No se ha podi do r eal i zar l a oper aci n" ) ;
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

Fundamentos de informtica. Programacin en Lenguaje C


256
/ * Funci n de bsqueda de di vi sor es. - - - - - - - - - - - - - - - - - - - - - */
short di vi sor es( long N, long *v , short d)
{
short cont = 1;
v[ 0] = 1;
for( i nt di v = 2 ; di v <= N / 2 ; di v++)
if( N %di v == 0)
{
v[ cont ] = di v;
cont ++;
/ / Si no caben ms di vi sor es, se abor t a l a oper aci n.
if( cont >= d) return - 1;
}
v[ cont ] = N;
return cont + 1;
}

/ * Funci n que muest r a el vect or que r eci be como par met r o. */
void most r ar ( long *v , short d)
{
pr i nt f ( " \ n\ n" ) ;
for( short i = 0 ; i < d ; i ++)
pr i nt f ( " %5l d" , *( v + i ) ) ;
}

59. Escriba una funcin, y un programa que le use, que calcule el
mximo comn divisor de un conj unto de enteros que recibe en
un vector como parmetro Adems, la funcin recibe otro
parmetro que es el nmero de enteros recogidos en ese
vector.

#i ncl ude <st di o. h>
#def i ne TAM 100

/ / Decl ar aci n de f unci ones . . .
long mcd( long, long) ;
long MCD( long*, short) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
long numer os[ 100] ;
long m;
short i ;
Captulo 9. Funciones.


257

/ / I nt r oducci n de val or es . . .

pr i nt f ( " I nt r oduzca val or es. " ) ;
pr i nt f ( " Al t er mi nar i nt r oduzca en val or cer o. " ) ;
for( i = 0 ; i < TAM ; i ++)
{
pr i nt f ( " \ n\ nnumer os[ %3hu] - > " , i ) ;
scanf ( " %l u" , numer os + i ) ;
if( *( numer os + i ) == 0) break;
}

/ / Cl cul o del mxi mo comn di vi sor . . .
m= MCD( numer os, i ) ;
pr i nt f ( " El maxi mo comun di vi sor es . . . %l u" , m) ;
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ / Funci n cl cul o mcd de dos ent er os . . .
long mcd( long a, long b) { return b ? mcd( b, a %b) : a; }

/ / Funci n cl cul o mcd de var i os ent er os . . .
long MCD( l ong*n, shor t d)
{
short i ;
long m;

/ / Si al gn ent er o es 0, el mcd l o ponemos a cer o.
for( i = 0 ; i < d ; i ++)
if( ! *( n + i ) ) return 0;
/ / Si sol o hay un ent er o, el mcd es ese ent er o.
if( d == 1) return *( n + 0) ;

i = 2;
m= mcd( *( n + 0) , *( n + 1) ) ;
while( i < d) m= mcd( m, *( n + i ++) ) ;

return m;
}

60. Escriba una funcin que reciba un entero y diga si es o no es
perfecto ( devuelve 1 si lo es; 0 si no lo es) . Utilice esa funcin
para mostrar los nmeros perfectos entre dos enteros
Fundamentos de informtica. Programacin en Lenguaje C


258
introducidos por el usuario.

#i ncl ude <st di o. h>
#i ncl ude <coni o. h>

/ / Decl ar aci n de l a f unci n per f ect o . . .
short per f ect o( long) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
long a, b;

pr i nt f ( " Li mi t e i nf er i or . . . " ) ;
scanf ( " %l d" , &a) ;

pr i nt f ( " Li mi t e super i or . . . " ) ;
scanf ( " %l d" , &b) ;

for ( long i = a ; i <= b; i ++)
if( per f ect o( i ) == 1) pr i nt f ( " %6l d" , i ) ;
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ / Funci n per f ect o . . .
short per f ect o( long x)
{
long suma = 1;
for( long di v = 2 ; di v <= x / 2 ; di v++)
if( x %di v == 0) suma += di v;

return suma == x ? 1 : 0;
}

61. Torres de Hanoi. Escriba un programa que solicite al usuario
con cuntos aros de la torre de Hanoi desea j ugar, y que el
programa muestre por pantalla todos los movimientos
necesarios para trasladoar todos los aros del primer al tercer
soporte o varilla.
Captulo 9. Funciones.


259
(Consultar Fundamentos de Informtica. Codificacin y algoritmia,
Captulo 6 sobre Recursividad.)

#i ncl ude <st di o. h>

/ / Decl ar aci n de l a f unci n Hanoi . . .
void Hanoi ( short, short, short, short) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
short di scos;

pr i nt f ( " I ndi que con cunt os di scos desea j ugar : " ) ;
scanf ( " %hd" , &di scos) ;

Hanoi ( 1, di scos, 1, 3) ;
}

/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Funci n Hanoi . */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void Hanoi ( short D1, short D2, short i , short j )
{
if( D1 == D2)
pr i nt f ( " Di sco %hu de %hu a %hu\ n" , D1, i , j ) ;
else
{
Hanoi ( D1 , D2 - 1 , i , 6 - i - j ) ;
Hanoi ( D2 , D2 , i , j ) ;
Hanoi ( D1 , D2 - 1 , 6 - i - j , j ) ;
}
}

62. Serie de Fibonacci. Fibonacci fue un matemtico italiano del
siglo XI I I que descubri la serie que lleva su nombre. Cada
nmero de esa serie es el resultado de la suma de los dos
anteriores:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
Haga un programa que calcule la serie de Fibonacci y guarde
Fundamentos de informtica. Programacin en Lenguaje C


260
los 40 primeros elementos de la serie en un array de tipo
unsigned long. ( Tenga en cuenta que el elemento 46 de la serie
de Fibonacci es el mayor elemento de la serie codificable en un
entero sin signo de 32 bits: si calcula valores ms all de esta
posicin, debera definir un tipo de dato nuevo que albergara
enteros ms largos.)
Esta serie goza de una serie de propiedades curiosas. Por
ej emplo:
La suma de los n primeros trminos verifica que:
+
=
=
2
1
1
n
i n
i
f f
La suma de los n primeros trminos pares verifica que:
+
=
=
2 2 1
1
1
n
i n
i
f f
La suma de los n primeros trminos impares verifica que:

=
=
2 1 2
1
n
i n
i
f f
La suma de los cuadrados de los n primeros trminos
verifica que:
+
=
=

2
1
1
n
i n n
i
f f f
Si n es divisible por m, entonces
n
f es divisible por
m
f .
Cualesquiera dos elementos consecutivos de la serie de
Fibonacci son primos entre s.
El cociente de dos nmeros consecutivos de la serie se
aproxima al nmero ureo:
+

1 n n
f f cuando n .
Contine el programa anterior de forma que el usuario pueda
solicitar mediante un men de opciones, la comprobacin de
Captulo 9. Funciones.


261
cada una de estas propiedades...

Antes de desanimarse ante la longitud de este ejercicio propuesto y
dejarlo estar, vale la pena que al menos considere que es un ejemplo
muy sencillo, con funciones simples, todas ellas muy parecidas. Y que si
bien es cierto que es mucho cdigo para copiar en un ordenador y ver
cmo funciona, s se puede detener en una o dos de las 9 funciones
definidas, y verlas en funcionamiento.
Un posible cdigo que resuelve el problema planteado podra ser el
siguiente:

/ * ======================================================= */
/ * PROGRAMA DE LA SERI E DE FI BONACCI . */
/ * ======================================================= */

#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <mat h. h>

#def i ne RANGO 40

/ / Decl ar aci n de l as f unci ones. . .
char menu( void) ;
void f i b01( unsigned long*) ;
void f i b02( unsigned long*) ;
void f i b03( unsigned long*) ;
void f i b04( unsigned long*) ;
void f i b05( unsigned long*) ;
void f i b06( unsigned long*) ;
long mcd( long, long) ;
void f i b07( unsigned long*) ;
void f i b08( unsigned long*) ;

/ / Funci n pr i nci pal . . .
void mai n( void)
{
unsigned long f i b[ RANGO + 1] ;
/ / No ut i l i zar emos el el ement o 0.
unsigned short i ;
char opci on;

f i b[ 1] = 1;
Fundamentos de informtica. Programacin en Lenguaje C


262
f i b[ 2] = 1;
/ / Ser i e de f i bonacci . . .
for( i = 3 ; i <= RANGO ; i ++)
f i b[ i ] = f i b[ i - 1] + f i b[ i - 2] ;

do
{
/ / Men de opci ones . . .
opci on = menu( ) ;
switch( opci on)
{
case ' 1' : f i b01( f i b) ; break;
case ' 2' : f i b02( f i b) ; break;
case ' 3' : f i b03( f i b) ; break;
case ' 4' : f i b04( f i b) ; break;
case ' 5' : f i b05( f i b) ; break;
case ' 6' : f i b06( f i b) ; break;
case ' 7' : f i b07( f i b) ; break;
case ' 8' : f i b08( f i b) ; break;
case ' 0' : pr i nt f ( " \ nFi n del pr ogr ama" ) ;
pr i nt f ( " \ nPul se t ecl a par a t er mi nar " ) ;
f l ushal l ( ) ;
get char ( ) ;
break;
default: pr i nt f ( " opci n no def i ni da . . . " ) ;
}
}while( opci on ! = ' 0' ) ;
}


/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ * Funci n que muest r a el men de opci ones */
char menu( void)
{
char opci on;

cl r scr ( ) ;

pr i nt f ( " \ n\ n\ t PROPI EDADES DE LA SERI E DE FI BONACCI " ) ;
pr i nt f ( " \ n\ n\ t 0. Sal i r de pr ogr ama. " ) ;
pr i nt f ( " \ n\ t 1. La suma de l os n pr i mer os t er mi nos" ) ;
pr i nt f ( " \ n\ t es i gual a f [ n + 2] - 1. " ) ;

pr i nt f ( " \ n\ t 2. La suma de l os n pr i mer os
t er mi nos par es ( de 2 a 2*n) " ) ;
pr i nt f ( " \ n\ t es i gual a f [ 2 * n + 1] - 1. " ) ;

pr i nt f ( " \ n\ t 3. La suma de l os n pr i mer os
Captulo 9. Funciones.


263
t er mi nos i mpar es ( de 1 a 2*n- 1) " ) ;
pr i nt f ( " \ n\ t es i gual a f [ n + 2] . " ) ;

pr i nt f ( " \ n\ t 4. La suma de l os cuadr ados
de l os n pr i mer os t er mi nos" ) ;
pr i nt f ( " \ n\ t es i gual a f [ n] * f [ n + 1] . " ) ;

pr i nt f ( " \ n\ t 5. Si n es di vi si bl e por m,
ent onces f [ n] " ) ;
pr i nt f ( " \ n\ t es di vi si bl e por f [ m] . " ) ;

pr i nt f ( " \ n\ t 6. Cual qui er par consecut i vo
de f i bonacci " ) ;
pr i nt f ( " \ n\ t son pr i mos ent r e si . " ) ;

pr i nt f ( " \ n\ t 7. El coci ent e de dos el ement os
consecut i vos de l a ser i e" ) ;
pr i nt f ( " \ n\ t se apr oxi ma al numer o aur eo. " ) ;

pr i nt f ( " \ n\ t 8. Most r ar l os 40 pr i mer os
el ement os de l a ser i e de Fi bonacci . " ) ;

pr i nt f ( " \ n\ n\ n\ t El i j a una opci on . . . " ) ;

do
opci on = get char ( ) ;
while( opci on < ' 0' | | opci on > ' 8' ) ;
return opci on;
}

/ / Funci n f i b01
void f i b01( unsigned long*f )
{
unsigned short n, i ;
unsigned long S = 0;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 1. " ) ;
pr i nt f ( " \ n\ n\ t La suma de l os n pr i mer os t er mi nos" ) ;
pr i nt f ( " \ n\ t es i gual a f [ n + 2] - 1. " ) ;

pr i nt f ( " \ n\ nI ndi que el i ndi ce n que qui er e
ver i f i car ( ent r e 1 y 38) . . . " ) ;
scanf ( " %hu" , &n) ;
if( n > RANGO - 2)
pr i nt f ( " No se di sponen de suf i ci ent es
el ement os. " ) ;
else if( n == 0) pr i nt f ( " No val i do" ) ;
else
{
for( i = 1 ; i <= n ; i ++) S += *( f + i ) ;
Fundamentos de informtica. Programacin en Lenguaje C


264
pr i nt f ( " \ n\ nSUMA f [ 1] . . . f [ %2hu] = %l u\ n" , n, S) ;
pr i nt f ( " f [ %hu] - 1 = %l u\ n" , n + 2, *( f + n + 2) - 1) ;
}

pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b02
void f i b02( unsigned long*f )
{
unsigned short n, i ;
unsigned long S = 0;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 2. " ) ;
pr i nt f ( " \ n\ t La suma de l os n pr i mer os
t er mi nos par es ( 2, 4, . . . , 2 * n) " ) ;
pr i nt f ( " \ n\ t par es es i gual a f [ 2 * n + 1] - 1. " ) ;

pr i nt f ( " \ n\ nI ndi que el i ndi ce n que qui er e
ver i f i car ( ent r e 1 y 19) . . . " ) ;
scanf ( " %hu" , &n) ;

/ / n no puede ser t al que 2 * n + 1 sea mayor que RANGO. . .
if( n > ( RANGO - 1) / 2)
pr i nt f ( " No se di sponen de suf i ci ent es
el ement os. " ) ;
else if( n == 0) pr i nt f ( " No val i do" ) ;
else
{
for( i = 2 ; i <= 2 * n ; i += 2) S += *( f + i ) ;
/ / Most r amos r esul t ados. . .
pr i nt f ( " \ n\ nSUMA f [ 2] . . . f [ %2hu] =
%l u\ n" , 2 * n, S) ;
pr i nt f ( " f [ %hu] - 1 = %l u\ n" ,
2 * n + 1, *( f + 2 * n + 1) - 1) ;
}

pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b03
void f i b03( unsigned long*f )
{
unsigned short n, i ;
unsigned long S = 0;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 2. " ) ;
Captulo 9. Funciones.


265
pr i nt f ( " \ n\ t La suma de l os n pr i mer os t er mi nos
i mpar es ( de 1 a 2*n- 1) " ) ;
pr i nt f ( " \ n\ t es i gual a f [ n + 2] . " ) ;

pr i nt f ( " \ n\ nI ndi que el i ndi ce n que qui er e
ver i f i car ( ent r e 1 y 20) . . . " ) ;
scanf ( " %hu" , &n) ;

/ / n no puede ser t al que 2 * n - 1 sea mayor que RANGO. . .
if( n > ( RANGO + 1) / 2)
pr i nt f ( " No se di sponen de suf i ci ent es
el ement os. " ) ;
else if( n == 0) pr i nt f ( " No val i do" ) ;
else
{
for( i = 1 ; i <= 2 * n - 1 ; i += 2)
S += *( f + i ) ;
/ / Most r amos r esul t ados. . .
pr i nt f ( " \ n\ nSUMA f [ 2] . . . f [ %2hu] = %l u\ n" ,
2 * n, S) ;
pr i nt f ( " f [ %hu] = %l u\ n" , 2 * n, *( f + 2 * n) ) ;
}

pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b04
void f i b04( unsigned long*f )
{
unsigned short n, i ;
unsigned long S = 0;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 3. " ) ;
pr i nt f ( " \ n\ t La suma de l os cuadr ados de l os
n pr i mer os t er mi nos" ) ;
pr i nt f ( " \ n\ t es i gual a f [ n] * f [ n + 1] . " ) ;

pr i nt f ( " \ n\ nI ndi que el i ndi ce n que qui er e ver i f i car
( ent r e 1 y 39) . . . " ) ;
scanf ( " %hu" , &n) ;
if( n > RANGO - 1)
pr i nt f ( " No se di sponen de suf i ci ent es
el ement os. " ) ;
else if( n == 0) pr i nt f ( " No val i do" ) ;
else
{
for( i = 1 ; i <= n ; i ++)
S += *( f + i ) * *( f + i ) ;

Fundamentos de informtica. Programacin en Lenguaje C


266
pr i nt f ( " \ n\ nSUMA f [ 1] ^2. . . f [ %2hu] ^2 = %l u\ n" ,
n, S) ;
pr i nt f ( " f [ %hu] * f [ %hu] = %l u\ n" ,
n, n + 1, *( f + n) * *( f + n + 1) ) ;
}

pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b05
void f i b05( unsigned long*f )
{
unsigned short n, m;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 5. " ) ;
pr i nt f ( " \ n\ t Si n es di vi si bl e por m, ent onces f [ n] " ) ;
pr i nt f ( " \ n\ t es di vi si bl e por f [ m] . \ n\ n" ) ;

for( n = 3 ; n < RANGO / 2 ; n++)
/ / Todos l os ml t i pl os de i . . .
{
/ * Comenzamos con n = 3, por que f [ 2] = 1, y t odos ser n
ml t i pl os de 1. */
pr i nt f ( " \ n\ n** n = %2hu\ t f [ %2hu] = %10l u ** \ n" ,
n, n, *( f + n) ) ;
for( m= 2 * n ; m< RANGO ; m+= n)
{
pr i nt f ( " m= %2hu\ t f [ %2hu] = %10l u\ t " ,
m, m, *( f + m) ) ;
pr i nt f ( " f [ %2hu] %%f [ %2hu] = %l u" ,
m, n, *( f + m) %*( f + n) ) ;
if( *( f + m) %*( f + n) == 0)
pr i nt f ( " \ t [ DI VI SI BLE] \ n" ) ;
}
pr i nt f ( " \ n\ n\ t Pul se una t ecl a par a ver si gui ent e
ser i e de mul t i pl os . . . " ) ;
get char ( ) ;
}
pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b06
void f i b06( unsigned long*f )
{
unsigned short n;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 6. " ) ;
Captulo 9. Funciones.


267
pr i nt f ( " \ n\ t Cual qui er par consecut i vo de f i bonacci " ) ;
pr i nt f ( " \ n\ t son pr i mos ent r e si . \ n\ n" ) ;

for( n = 3 ; n < RANGO ; n++)
{
pr i nt f ( " f [ %2hu] = %10l u\ t " , n, *( f + n) ) ;
pr i nt f ( " f [ %2hu] = %10l u\ t " , n + 1, *( f + n + 1) ) ;
pr i nt f ( " MCD = %l u\ t " , mcd( *( f + n) , *( f + n + 1) ) ) ;
if( mcd( *( f + n) , *( f + n + 1) ) == 1)
pr i nt f ( " [ COPRI MOS] \ n" ) ;
}
pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n mcd, def i ni da par a uso de f i b06
long mcd( long a, long b)
{
return b ? mcd( b, a %b) : a;
}

/ / Funci n f i b07
void f i b07( unsigned long*f )
{
unsigned short n;
const double or o = ( 1 + sqr t ( 5) ) / 2;
char bl ancos[ ] = " " ;

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 7. " ) ;
pr i nt f ( " \ n\ t El coci ent e de dos el ement os
consecut i vos de l a ser i e" ) ;
pr i nt f ( " \ n\ t se apr oxi ma al numer o aur eo. \ n\ n" ) ;

for( n = 1 ; n < RANGO ; n++)
{
pr i nt f ( " f [ %2hu] = %10l u\ t " , n, *( f + n) ) ;
pr i nt f ( " f [ %2hu] = %10l u\ t " , n + 1, *( f + n + 1) ) ;
pr i nt f ( " COCI ENTE = %20. 18l f \ n"
( doubl e) *( f + n + 1) / *( f + n) ) ;
}
pr i nt f ( " \ n%sNumer o de or o ( 1+sqr t ( 5) ) / 2 - > %20. 18l f " ,
bl ancos, or o) ;
pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

/ / Funci n f i b08
void f i b08( unsigned long*f )
{
unsigned short n;
Fundamentos de informtica. Programacin en Lenguaje C


268

cl r scr ( ) ;
pr i nt f ( " \ n\ n\ n\ t OPCI ON SELECCI ONADA 8. " ) ;
pr i nt f ( " \ n\ t Most r ar l os 40 pr i mer os el ement os
de l a ser i e de Fi bonacci . \ n\ n" ) ;

for( n = 1 ; n <= RANGO ; n++)
pr i nt f ( " f [ %2hu] = %10l u%s" ,
n, *( f + n) , n %2 ? " \ t " : " \ n" ) ;

pr i nt f ( " \ n\ nPul se i nt r o par a vol ver a menu pr i nci pal " ) ;
get char ( ) ;
}

63. Haga un programa que solicite al usuario un valor entero entre
1 y 999.999 y escriba por pantalla la cantidad numrica
introducida escrito con letras. Por ej emplo, si el usuario
introduce 56343, el programa deber escribir por pantalla
Cincuenta y seis mil trescientos cuarenta y tres.

El programa planteado es bastante largo y el enunciado desanima,
porque de entrada se vislumbran tantas posibilidades que aburre. Sin
embargo su lgica es sencilla y gracias a las funciones es tambin fcil
de entender.
Han quedado definidas tres funciones. Una llamada unidades, otra
decenas, y otra centenas. Al obtener el nmero a leer en formato texto,
lo primero que hace la funcin principal es determinar si, efectivamente,
ese nmero introducifo tiene unidades, decenas, centenas, unidades de
millar, decenas de millar y centenas de millar. Segn las tenga o no se
invocar a las funciones que expresan las unidades, o las decenas, o las
centenas.
La funcin de las decenas es algo ms complicada porque ha de
contemplar la forma de expresar la numeracin entre el once y el
quince, distinto al resto de decenas. Y adems ha de tener en cuenta
Captulo 9. Funciones.


269
que si no hay unidades, entonces las decenas no aaden, al final, la
cpula y.
#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>

/ / Decl ar aci n de l as f unci ones
char* uni dades( unsigned short, unsigned short) ;
char* decenas( unsigned short, unsigned short) ;
char* cent enas( unsigned short, unsigned short, unsigned short) ;

/ Funci n pr i nci pal
void mai n( void)
{
char l ei do[ 200] , l ei doM[ 200] ;
unsigned long i nt n, naux;
unsigned short C, D, U, c, d, u;

do
{
/ / I ni ci al i zamos l as cadenas de car act er es.
l ei do[ 0] = ' \ 0' ;
l ei doM[ 0] = ' \ 0' ;

pr i nt f ( " \ n\ n\ nI nt r oduzca ent er o a . . . " ) ;
scanf ( " %l u" , &naux) ;
/ / Det er mi namos uni dades, decenas, cent enas. . .
/ / y uni dades, decenas y cent enas de mi l
n = naux;
u = naux %10;
naux / = 10;
d = naux %10;
naux / = 10;
c = naux %10;
naux / = 10;
U = naux %10;
naux / = 10;
D = naux %10;
naux / = 10;
C = naux %10;

if( U | | D | | C)
{
/ / Si n el nmer o es mayor que 999:
st r cat ( l ei doM, cent enas( C, D, U) ) ;
st r cat ( l ei doM, decenas( U, D) ) ;
st r cat ( l ei doM, uni dades( U, D) ) ;
st r cat ( l ei doM, " mi l " ) ;
}
st r cat ( l ei do, cent enas( c, d, u) ) ;
st r cat ( l ei do, decenas( u, d) ) ;
Fundamentos de informtica. Programacin en Lenguaje C


270
st r cat ( l ei do, uni dades( u, d) ) ;
st r cat ( l ei doM, l ei do) ;

pr i nt f ( " El numer o %l u se l ee . . . " , n) ;
pr i nt f ( " \ n%s" , l ei doM) ;
}while( n) ; / / Ter mi na l a apl i caci n cuando el usuar i o
/ / qui er a l eer el nmer o cer o.
}

/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/ * Def i ni ci n de l as f unci ones */
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/ * Funci n par a l as uni dades. - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
char* uni dades( unsigned short u, unsigned short d)
{
if( d == 1 && ( u == 0 | | u == 1 | | u == 2 | |
u == 3 | | u == 4 | | u == 5) )
return " " ;

switch( u)
{
case 0: return " " ;
case 1: return " uno " ;
case 2: return " dos " ;
case 3: return " t r es " ;
case 4: return " cuat r o " ;
case 5: return " ci nco " ;
case 6: return " sei s " ;
case 7: return " si et e " ;
case 8: return " ocho " ;
case 9: return " nueve " ;
}
}

/ * Funci n par a l as decenas. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
char* decenas( unsigned short u, unsigned short d)
{
if( d == 1 && ( u == 0 | | u == 1 | | u == 2 | |
u == 3 | | u == 4 | | u == 5) )
switch( u)
{
case 0: return " di ez " ;
case 1: return " once " ;
case 2: return " doce " ;
case 3: return " t r ece " ;
case 4: return " cat or ce " ;
case 5: return " qui nce " ;
}
switch( d)
{
Captulo 9. Funciones.


271
case 0: return " " ;
case 1: return " di eci " ;
case 2: if( u) return " vei nt i " ;
else return " vei nt e" ;
case 3: if( u) return " t r ei nt a y " ;
else return " t r ei nt a " ;
case 4: if( u) return " cuar ent a y " ;
else return " cuar ent a " ;
case 5: if( u) return " ci ncuent a y " ;
else return " ci ncuent a" ;
case 6: if( u) return " sesent a y " ;
else return " sesent a " ;
case 7: if( u) return " set ent a y " ;
else return " set ent a " ;
case 8: if( u) return " ochent a y " ;
else return " ochent a " ;
case 9: if( u) return " novent a y " ;
else return " novent a " ;
}
}

/ * Funci n par a l as cent enas. - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
char* cent enas( unsigned short c, unsigned short d,
unsigned short u)
{
switch( c)
{
case 0: r et ur n " " ;
case 1: if( ! u && ! d) return " ci en " ;
else return " ci ent o " ;
case 2: return " dosci ent os " ;
case 3: return " t r esi ent os " ;
case 4: return " cuat r oci ent os " ;
case 5: return " qi ni ent os " ;
case 6: return " sei sci ent os " ;
case 7: return " set eci ent os " ;
case 8: return " ochoci ent os " ;
case 9: return " noveci ent os " ;
}
}

Fundamentos de informtica. Programacin en Lenguaje C


272








PARTE II:
Profundizando
en C.
Fundamentos de informtica. Programacin en Lenguaje C


274
PARTE II: Profundizando en C.


275





Para el estudio de esta segunda parte del manual es muy conveniente
estar convencidos de que se domina la primera parte.
Ya hemos visto muchos conceptos sobre el modo de programar en C.
Pero son muchsimos ms los que quedan por conocer. Desde luego, no
se pretende en este manual abordarlos todos. Por ejemplo, no se dice
una sola palabra sobre la programacin en un entorno grfico.
En esta segunda parte vamos a hablar, en cuatro captulos, de la
asignacin dinmica de la memoria, y de cmo disponer de arrays y
matrices de dimensin definida en tiempo de ejecucin. Tambin
hablaremos de otras muchas posibilidades que se pueden abordar
mediante las funciones, y tcnicas de manejar las funciones: punteros a
funciones, funciones como argumentos de otra funcin, funciones con un
nmero variable de argumentos, macros, etc. En un tercer captulo
mostraremos tcnicas para crear nuevos tipos de datos distintos de los
tipos de dato primitivos. Y terminamos esta segunda parte y el manual
hablando del acceso a disco.


Fundamentos de informtica. Programacin en Lenguaje C


276








CAPTULO 10

ASIGNACIN DINMICA DE
MEMORIA

Cuando hemos hablado de los vectores, hemos remarcado la idea de
que la dimensin indicada en su declaracin debe ser un literal:
long numeros[100];
Con esa sentencia creamos un vector de 100 enteros largos: acabamos
de reservar 400 bytes para codificar valores de este tipo de dato.
En muchas ocasiones nos podra interesar que la declaracin de este
vector se hiciera mediante una variable, y poder as reservar el espacio
de memoria que realmente necesitramos. Por ejemplo, al crear unas
matrices en un programa de operatoria de matrices, vendra bien poder
codificar algo del siguiente estilo:
long f, c;
printf(Nmero de filas de su matriz A ... );
scanf(%ld,&f);
printf(Nmero de columnas de su matriz A ... );
scanf(%ld,&c);
Fundamentos de informtica. Programacin en Lenguaje C


278
long A[f][c];
Y as, quedara creada la matriz de acuerdo con la dimensin deseada.
Pero este cdigo es errneo.
Esta forma de crear matrices no puede llevarse a cabo bajo ningn
concepto, porque la reserva de memoria de una matriz o de un vector
se realiza en tiempo de compilacin: el compilador, antes de ejecucin
alguna, debe saber cunta memoria se debe reservar y para qu
dominio de valores.
Eso es un serio problema. Si deseamos hacer un programa que necesite
manejar informacin con arrays o matrices, siempre habr que
dimensionar esos espacios de memoria de manera que consideren el
caso ms desfavorable: para la ocasin en que se necesite un mayor
tamao. Y luego recorrer ese array o esa matriz acotando el uso a la
zona que nos interesa.
Un ejemplo simple: si queremos crear una matriz cuadrada, y su
dimensin puede estar en un valor entre 2 y 100, el programador
deber hacer lo siguiente:
long matriz[100][100];
short int dimension;
short int i, j;
printf(Indique la dimensin de la matriz ... );
scanf(%hd,&dimension);
// Introduccin de valores...
for( i = 0 ; i < dimension ; i++)
for(j = 0 ; j < dimension j++)
{
printf(matriz[%hd][%hd] = , i, j);
scanf(%ld,&matriz[i][j]);
}
Y as, si el usuario introduce el valor 3, entonces emplearemos 36 bytes
para el manejo de nuestra matriz de 9 enteros largos. Pero la memoria
reservada ser de cuarenta mil bytes.
C dispone de un modo para lograr que la reserva de memoria quede
optimizada, haciendo esa reserva en tiempo de ejecucin, cuando el
Captulo 10. Asignacin dinmica de memoria.


279
programa se ejecuta y ya sabe con exactitud la memoria que necesita:
mediante la asignacin dinmica de memoria. Con ello se logra ajustar
la cantidad de memoria que utiliza el programa al tamao que
realmente es necesario para cada ejecucin concreta
Disponemos de algunas funciones que nos permiten reservar y liberar
esa memoria de una forma dinmica, es decir, en tiempo de ejecucin.

Funcin malloc
Esta funcin malloc queda definida en la biblioteca stdlib.h. Tambin la
encontramos en la biblioteca alloc.h. Recomendamos hacer uso siempre
de la definicin recogida en stdlib.
Su prototipo es el siguiente:
void *malloc(size_t size);
Donde el tipo de dato size_t lo consideraremos ahora nosotros
simplemente como un tipo de dato long.
La funcin reserva tantos bytes consecutivos como indique el valor de la
variable size y devuelve la direccin del primero de esos bytes. Esa
direccin debe ser recogida por una variable puntero: si no se recoge,
tendremos la memoria reservada pero no podremos acceder a ellas
porque no sabremos dnde ha quedado hecha esa reserva.
Como se ve, la direccin de devuelve con el tipo de dato void*. La
funcin malloc as lo hace porque est definida para hacer reserva de
bytes, al margen de para qu se reservan esos bytes. Pero no tendra
sentido trabajar con direcciones de tipo de dato void. Por eso, en la
asignacin al puntero que debe recoger la direccin del array, se debe
indicar, mediante el operador forzar tipo, el tipo e la direccin.
Veamos un ejemplo:
long dim;
float *vector;
printf(Dimensin de su vector ...);
Fundamentos de informtica. Programacin en Lenguaje C


280
scanf(%ld,&dim);
vector = (float*)malloc(4 * dim);
En el ejemplo, hemos reservado tantos bytes como hace falta para
reservar memoria para un array de float de la dimensin indicada por el
usuario. Como cada variable float ocupa cuatro bytes hemos
multiplicado la dimensin por cuatro. Como se ve, en la asignacin, a la
direccin de memoria que devuelve la funcin malloc, le hemos indicado
que deber comportarse como direccin de float. De ese tipo es adems
el puntero que la recoge. A partir de este momento, desde el puntero
vector y con operatoria de punteros podemos manejarnos por el array
creado en tiempo de ejecucin.
Tambin se puede recorrer el array con ndices, como si de un vector
normal se tratase: la variable vector[i] es la misma que la variable
*(vector + i).
Es responsabilidad del programador reservar un nmero de bytes que
sea mltiplo del tamao del tipo de dato para el que hacemos la
reserva. Si, por ejemplo, en una reserva para variables float, el nmero
de bytes no es mltiplo de 4, el compilador no interrumpir su trabajo, y
generar el ejecutable; pero existe el peligro, en un momento dado, de
incurrir en una violacin de memoria.
De forma habitual al invocar a la funcin malloc se hace utilizando el
operador sizeof. La sentencia anterior en la que reservbamos espacio
para nuestro vector de tipo float quedara mejor de la siguiente forma:
vector = (float*)malloc(sizeolf(float) * dim);
Y una ltima observacin sobre la reserva de la memoria. La funcin
malloc busca un espacio de memoria (en la memoria heap o montn:
pero no es ese tema que ahora vaya a ocuparnos) de la longitud que se
le indica en el argumento. Gracias a esta asignacin de memoria se
podr trabajar con una serie de valores, y realizar clculos necesarios
para nuestra aplicacin. Pero y si no hay en la memoria un espacio
suficiente de bytes consecutivos, libres y disponibles para satisfacer la
Captulo 10. Asignacin dinmica de memoria.


281
demanda? En ese caso, no podramos realizar ninguna de las tareas que
el programa tiene determinadas, simplemente porque no tendramos
memoria.
Cuando la funcin malloc lo logra satisfacer la demanda, devuelve el
puntero nulo. Es importante, siempre que se crea un espacio de
memoria con esta funcin, y antes de comenzar a hacer uso de ese
espacio, verificar que s se ha logrado hacer buena reserva. En caso
contrario, habitualmente lo que habr que hacer es abortar la ejecucin
del programa, porque sin memoria, no hay datos ni capacidad de operar
con ellos.
Esta verificacin se realiza de la siguiente manera:
vector = (float*)malloc(dimension * sizeof(float));
if(vector == NULL)
{
printf("\nNo hay memoria disponible.");
printf("\nEl programa va a terminar.");
printf("\nPulse cualquier tecla ... ");
exit(1);
}
Y as, nunca trabajaremos con un puntero cuya direccin es nula. Si no
hiciramos esa verificacin, en cuanto se echase mano del puntero
vector para recorrer nuestra memoria inexistente, el programa abortara
inmediatamente. Es mejor tomar nosotros la iniciativa, mediante la
funcin exit, y decidir nosotros el modo de terminacin del programa en
lugar de que lo decida un error fatal de ejecucin.
Existen otras funciones muy similares de reserva de memoria dinmica.
Por ejemplo, la funcin calloc, que tiene el siguiente prototipo:
void *calloc(size_t nitems, size_t size);
Que recibe como parmetros el nmero de elementos que se van a
reservar y el nmero de bytes que ocupa cada elemento. La sentencia
anterior
vector = (float*)malloc(dimension * sizeof(float));
Fundamentos de informtica. Programacin en Lenguaje C


282
ahora, con la funcin calloc, quedara:
vector = (float*)calloc(dimension, sizeof(float));
Como se ve, en sustancia, ambas funciones tienen un comportamiento
muy similar. Tambin en este caso, obviamente, ser conveniente hacer
siempre la verificacin de que la memoria ha sido felizmente reservada.
Lo mejor es acudir a las ayudas de los compiladores para hacer buen
uso de las especificaciones de cada funcin.
Una ltima funcin de asignacin dinmica de la memoria es la funcin
realloc. Su prototipo es el siguiente:
void *realloc(void *block, size_t size);
Esta funcin sirve para reasignar una zona de memoria sobre un
puntero. El primer parmetro es el del puntero sobre el que se va a
hacer el realojo; el segundo parmetro recoge el nuevo tamao que
tendr la zona de memoria reservada.
Si el puntero block ya tiene memoria asignada, mediante una funcin
malloc o calloc, o reasignada por otra llamada anterior realloc, entonces
la funcin vara el tamao de la posicin de memoria al nuevo tamao
que indica la variable size. Si el tamao es menor que el que haba,
simplemente deja liberada para nuevos usos la memoria de ms que
antes disponamos y de la que hemos decidido prescindir. Si el nuevo
tamao es mayor, entonces procura prolongar ese espacio reservado
con los bytes siguientes; si eso no es posible, entonces busca en la
memoria un espacio libre del tamao indicado por size, y copia los
valores asignados en el tramo de memoria anterior en la nueva
direccin. La funcin devuelve la direccin donde queda ubicada toda la
memoria reservada. Es importante recoger esa direccin de memoria
que devuelve la funcin realloc, porque en su ejecucin, la funcin
puede cambiar la ubicacin del vector. La llamada podra ser as:
vector = (float*)realloc(vector, new_dim * sizeof(float));
Captulo 10. Asignacin dinmica de memoria.


283
Si el puntero block tiene el valor nulo, entonces realloc funciona de la
misma forma que la funcin malloc.
Si el valor de la variable size es cero, entonces la funcin realloc libera
el puntero block, que queda nulo. En ese caso, el comportamiento de la
funcin realloc es semejante al de la funcin free que vemos a
continuacin.

Funcin free
Esta funcin viene tambin definida en la biblioteca stdlib.h. Su
cometido es liberar la memoria que ha sido reservada mediante la
funcin malloc, o calloc, o realloc. Su sintaxis es la siguiente:
void free(void *block);
Donde block es un puntero que tiene asignada la direccin de memoria
de cualquier bloque de memoria que haya sido reservada previamente
mediante una funcin de memoria dinmica, como por ejemplo la
funcin malloc ya presentada y vista en el epgrafe anterior.
La memoria que reserva el compilador ante una declaracin de un
vector se ubica en la zona de variables de la memoria. La memoria que
se reserva mediante la funcin malloc queda ubicada en otro espacio de
memoria distinto, que se llama habitualmente heap. En ningn caso se
puede liberar, mediante la funcin free, memoria que haya sido creada
esttica, es decir, vectores declarados como tales en el programa y que
reservan la memoria en tiempo de ejecucin. Es esa memoria del heap
la que libera la funcin free. Si se aplica esa funcin sobre memoria
esttica se produce un error en tiempo de compilacin.

Ejemplo: la Criba de Erastthenes
Supongamos que deseamos hacer un programa que almacene en un
vector todos los nmeros primos menores que un milln. Para esa
Fundamentos de informtica. Programacin en Lenguaje C


284
bsqueda utilizaremos el algoritmo de la criba de Eraststhenes. Es un
algoritmo que permite encontrar todos los Nmeros Primos menores o
iguales a un entero dado.
Se comienza generando una tabla con todos los nmeros desde 1 hasta
el lmite superior (en nuestro caso hemos quedado que un milln).
Tomamos el nmero 1 como primo por definicin. A continuacin se
pasa al siguiente nmero, que es el 2, que ya desde ese momento se
considerar primo, y se procede a marcar en la tabla como enteros
compuestos (es decir, no primos) a todos los nmeros posteriores a 2 y
mltiplos de 2.
A continuacin pasamos al siguiente nmero que no est marcado como
compuesto, que resulta ser el 3, que queda considerado ahora como
primo, y procedemos a marcar como compuestos en nuestra tabla todos
los nmeros posteriores a l y mltiplos suyos.
Ya hemos marcado como compuestos todos los mltiplos de 3. Ahora
buscamos el siguiente nmero no marcado como compuesto. El 4 es
mltiplo de 2 y ya ha quedado marcado como compuesto, as que nos lo
saltamos y llegamos al 5 que no es mltiplo ni de 2 ni de 3. El 5 queda
considerado primo y ahora procedemos a marcar como compuestos
todos los mltiplos de 5.
El proceso se va repitiendo hasta llegar al ltimo nmero menor que el
lmite superior marcado. Todos los nmeros que no hayan sido
marcados como compuestos sern primos.
En realidad no es necesario realizar la criba hasta llegar al ltimo
nmero menor que el lmite superior fijado: basta llegar hasta la raz
cuadrada de ese lmite. La razn es que si un nmero no tiene un divisor
menor que su raz cuadrada, entonces tampoco lo puede tener mayor
que su raz cuadrada y, por tanto, si al llegar a ese limite no se ha
encontrado un divisor, entonces ese nmero es primo con total certeza.
Captulo 10. Asignacin dinmica de memoria.


285
Una vez tenemos claro el algoritmo que nos permitir llegar a crear la
tabla de los primos, el siguiente paso ser implementar el programa que
nos haga este proceso.

64. Escriba un programa que cree un vector con todos los primos
menores que un milln Utilice, para la bsqueda de los primos,
la criba de Erasthtenes.

Vamos a definir para ellos dos funciones ms la funcin principal:
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define MAX 1000000

long Criba(char*, long);
void TablaPrimos(char*, long*, long);

void main(void)
{
char *num;
long *primos;
long pr, i;

num = (char*)malloc((MAX + 1) * sizeof(char));
if(num == NULL)
{
printf("\nNo hay memoria disponible.");
printf("\nEl programa va a terminar.");
printf("\nPulse cualquier tecla ... ");
exit(1);
}
pr = Criba(num, MAX + 1);
// Ya est hecha la criba. Tenemos pr primos.
// Creamos ahora el vector que contendr a los primos.
// Reservamos memoria para este vector:
primos = (long*)malloc((pr + 1) * sizeof(long));
if(primos == NULL)
{
printf("\nNo hay memoria disponible.");
printf("\nEl programa va a terminar.");
printf("\nPulse cualquier tecla ... ");
exit(1);
Fundamentos de informtica. Programacin en Lenguaje C


286
}
TablaPrimos(num, primos, MAX + 1);
free(num);

// Mostramos los primos por pantalla ...
printf("Primos menores que %ld... \n\n",MAX);
for(i = 0 ; *(primos + i) != 0 ; i++)
printf("%10ld", *(primos + i));
free(primos);
}

long Criba(char* num, long rango)
{
long i, j;
// En principio marcamos todos los elementos como PRIMOS
for(i = 0 ; i < rango; i++)
num[i] = 'p';
for(i = 2 ; i < sqrt(rango) ; i++)
if(num[i] == 'p')
for(j = 2 * i ; j < rango ; j += i)
num[j] = 'c';
for( i = 1 , j = 0 ; i < rango ; i++)
if(num[i] == 'p') j++;
return j;
}

void TablaPrimos(char* num, long* primos, long rango)
{
long i, j;
for(i = 1 , j = 0 ; i < rango ; i++)
if(num[i] == 'p')
{
*(primos + j) = i;
j++;
}
*(primos + j) = 0;
}
Vamos viendo este programa, resuelto mediante funciones. Primero
reservamos, en la funcin main, un espacio de memoria suficiente para
albergar tantas variables char (de un bytes cada una) como indica MAX
+ 1. MAX es la macro que recoge el lmite superior sobre el que se van a
buscar todos los primos inferiores. A esa memoria, que manejaremos
desde un puntero tipo char* que hemos llamado num, le asignamos a
todas sus posiciones el valor p, que vamos a entender que significa
primo. Y es sobre ese vector sobre quien se hace la criba de
Captulo 10. Asignacin dinmica de memoria.


287
Erastthenes, definida en la funcin Criba, que devuelve el nmero de
primos que hay en ese rango entre 1 y MAX (la funcin principal recoge
ese valor en la variable pr) y que deja modificada la memoria recogida
por el puntero num: una vez ejecutada la funcin Criba, cada posicin
valdr c p segn que su ndice en el vector sea compuesto o primo:
num[i] valdr p si i es primo, y valdr c si i en compuesto.
Como ya sabemos cuantos primos hay en nuestro intervalo, ahora
creamos un espacio de memoria para almacenar enteros largos: tantos
como indica la variable pr (en realidad reservamos uno ms que pr, por
el motivo que se explica enseguida). Y llamando a la funcin
TablaPrimos se le asigna a cada posicin de esa memoria cada uno de
los pr primos del intervalo.
El ltimo valor (ese espacio en memoria de ms que acabamos de
reservar) de nuestro vector de primos lo ponemos a cero: as el
recorrido de nuestro vector no se rige por un ndice, sino por un valor
que hace de fin de lista: se podr recorrer el vector de enteros primos
mientras que no se llegue a un valor cero. As se ha recorrido al final de
funcin principal, cuando se muestra la lista de primos por pantalla.
Si en el programa se quiere aumentar el rango de primos bastar
modificar la definicin de la directiva define.
Al final del programa, y antes de terminar su ejecucin liberamos la
memoria de la tabla de primos. Antes, ya habamos liberado la memoria
recogida por el puntero num. Y eso es algo interesante a destacar de la
memoria dinmica: se crea cuando se necesita, y se libera cuando ya no
se necesita. Y en ese aspecto, esta memoria tiene un rgimen de vida y
de mbito diferente al de las variables creadas por declaracin. La
variable puede dejar de existir antes de finalizar el bloque en el que ha
sido creada; y puede comenzar a existir pasado un tiempo al inicio de la
ejecucin de su bloque.

Fundamentos de informtica. Programacin en Lenguaje C


288
Matrices en memoria dinmica
Para crear matrices, podemos trabajar de dos maneras diferentes. La
primera es crear una estructura similar a la indicada en el cuadro 12.1.
Presentamos un cdigo que genera una matriz de tipo float. El
programa solicita al usuario las dimensiones de la matriz (el nmero de
filas y el nmero de columnas). A continuacin la aplicacin reserva un
array de tantos punteros como filas tenga la matriz; y sobre cada uno
de esos punteros hace una reserva de tantos elementos float como
columnas se deseen en la matriz.
Luego, para ver cmo se puede hacer uso de esa matriz creada
mediante memoria dinmica, solicitamos al usuario que d valor a cada
elemento de la matriz y, finalmente, mostramos por pantalla todos los
elementos de la matriz.
Por ltimo, se realiza la liberacin de la memoria. Para ello primero
habr que liberar la memoria asignada a cada uno de los vectores
creados en memoria dinmica sobre el puntero p, y posteriormente
liberar al puntero p del array de punteros.
El cdigo es el siguiente:
#include <stdlib.h>
#include <stdio.h>

void main(void)
{
float **p;
short f, c;
short i, j;
// Introducir las dimensiones ...
printf("Indique filas del vector ... ");
scanf("%hd",&f);
printf("Indique columnas del vector ... ");
scanf("%hd",&c);
// Creacin de las filas ...
p = (float**)malloc(f * sizeof(float*));
if(p == NULL)
{
printf("Memoria insuficiente.\n");
printf("La ejeccin se interrumpir.\n");
printf("Pulse una tecla para terminar ... ");
Captulo 10. Asignacin dinmica de memoria.


289
getchar();
exit(0);
}
// Creacin de las columnas ...
for( i = 0 ; i < f ; i++)
{
*(p + i) = (float*)malloc(c * sizeof(float));
if(*(p + i) == NULL)
{
printf("Memoria insuficiente.\n");
printf("La ejeccin se interrumpir.\n");
printf("Pulse una tecla para terminar...");
getchar();
exit(0);
}
}
// Asignacin de valores ...
for(i = 0 ; i < f ; i++)
for(j = 0 ; j < c ; j++)
{
printf("matriz[%2hd][%2hd] = ", i, j);
scanf("%f",*(p + i) + j);
}
// Mostrar la matriz por pantalla ...
for(i = 0 ; i < f ; i++)
{
printf("\n");
for(j = 0 ; j < c ; j++)
printf("%6.2f\t",*(*(p + i) + j));
}
// Liberar la memoria ...
for(i = 0 ; i < f ; i++)
free(*(p + i));
free(p);
}
Desde luego, hemos trabajado con operatoria de punteros. Ya se explic
que hablar de *(*(p + i) + j) es lo mismo que hablar de p[i][j]. Y que
hablar de (*(p + i) + j) es hablar de &p[i][j].
Y as se podra haber codificado. Veamos algunas de las lneas del
cdigo anterior, recogidas con operatoria de ndices:
// Asignacin de valores ...
for(i = 0 ; i < f ; i++)
for(j = 0 ; j < c ; j++)
{
printf("matriz[%2hd][%2hd] = ", i, j);
scanf("%f",&p[i][j]);
}
Fundamentos de informtica. Programacin en Lenguaje C


290
// Mostrar la matriz por pantalla ...
for(i = 0 ; i < f ; i++)
{
printf("\n");
for(j = 0 ; j < c ; j++)
printf("%6.2f\t",p[i][j]);
}
Una ltima observacin a este cdigo presentado: el puntero p es (debe
ser as) puntero a puntero. Y, efectivamente, l apunta a un array de
punteros que, a su vez, apuntan a un array de elementos float.
Decamos que haba dos formas de crear una matriz por asignacin
dinmica de memoria. La segunda es crear un solo array, de longitud
igual al producto de filas por columnas. Y si la matriz tiene filas y
columnas, considerar los primeros elementos del vector como la
primera fila de la matriz; y los segundos elementos, como la segunda
fila, y as, hasta llegar a la ltima fila.
El cdigo de esta nueva forma de manejar matrices podra ser:
#include <stdlib.h>
#include <stdio.h>
void main(void)
{
float *p;
short f, c;
short i, j;
// Introducir las dimensiones ...
printf("Indique filas del vector ... ");
scanf("%hd",&f);
printf("Indique columnas del vector ... ");
scanf("%hd",&c);
// Creacin de la matriz ...
p = (float*)malloc(f * c * sizeof(float*));
if(p == NULL)
{
printf("Memoria insuficiente.\n");
printf("La ejeccin se interrumpir.\n");
printf("Pulse una tecla para terminar ... ");
getchar();
exit(0);
}
// Asignacin de valores ...
for(i = 0 ; i < f ; i++)
for(j = 0 ; j < c ; j++)
{
Captulo 10. Asignacin dinmica de memoria.


291
printf("matriz[%2hd][%2hd] = ", i, j);
scanf("%f",p + (i * c + j));
}
// Mostrar la matriz por pantalla ...
for(i = 0 ; i < f ; i++)
{
printf("\n");
for(j = 0 ; j < c ; j++)
printf("%6.2f\t",*(p + (i * c + j)));
}
// Mostrar los datos como vector lineal ...
printf("\n\n");
for(i = 0 ; i < f * c ; i++)
printf("%6.2f\t",*(p + i));
// Liberar la memoria ...
free(p);
}
Ahora el puntero es un simple puntero a float. Y jugamos con los valores
de los ndices para avanzar ms o menos en el array. Cuando hablamos
de *(p + (i * c + j)), donde p es el puntero, i es el contador de filas, j el
contador de columnas, y c la variable que indica cuntas columnas hay,
estamos recorriendo el array de la siguiente manera: si queremos ir, por
ejemplo, a la fila 2 (i = 2) y a la columna 5 (j = 5), y suponiendo que la
matriz tiene, por ejemplo, 8 columnas (c = 8) entonces, ese elemento
del vector (2, 5) est ubicado en la posicin 2 * 8 + 5. es decir, en la
posicin 21.
Con el valor i = 0 tenemos los elementos de la primera fila, situados en
el vector desde su posicin 0 hasta el su posicin c 1. Con el valor i =
1 tenemos los elementos de la segunda fila, situados en el vector desde
su posicin c hasta su posicin 2 * c 1. Y en general, la fila i se sita
en el vector desde la posicin i * c hasta la posicin (i + 1) * c 1.
De nuevo, podremos trabajar con operatoria de ndices: hablar de *(p +
(i * c + j)) es lo mismo que hablar del elemento del vector p[i * c + j].



Fundamentos de informtica. Programacin en Lenguaje C


292
Ejercicios.

65. Un cuadro mgico es un reticulado de n filas y n columnas que
tiene la propiedad de que todas sus filas, y todas sus columnas,
y las diagonales principales, suman el mismo valor. Por
ejemplo:
6 1 8
7 5 3
2 9 4
La tcnica que se utiliza para generar cuadros mgicos (que
tienen siempre una dimensin impar: impar nmero de filas y
de columnas) es la siguiente:
a. Se comienza fijando el entero 1 en el espacio central de la
primera fila.
b. Se van escribiendo los sucesivos nmeros (2, 3, ...)
sucesivamente, en las casillas localizadas una fila arriba y
una columna a la izquierda. Estos desplazamientos se
realizan tratando a la matriz como si estuviera envuelta
sobre s misma, de forma que moverse una posicin hacia
arriba desde la fila superior lleva a la fila inferior, y moverse
una posicin a la izquierda desde la primera columna lleva a
la columna ms a la derecha del cuadro.
c. Si se llega a una posicin ya ocupada (es decir, si arriba a la
izquierda ya est ocupado con un nmero anterior),
entonces la posicin a rellenar cambia, que ahora ser la
inmediatamente debajo de la ltima casilla rellenada.
Despus se contina el proceso tal y como se ha descrito en
Captulo 10. Asignacin dinmica de memoria.


293
el punto anterior.
Escriba un programa que genere el cuadro mgico de la
dimensin que el usuario desee, y lo muestre luego por
pantalla.

/* ===== PROGRAMA DEL CUADRO MGICO.====================== */

#include <stdio.h>
#include <stdlib.h>

// Para comprender estas cuatro instrucciones consultar
// captulo 11.

typedef unsigned long int uli;
typedef unsigned short int usi;
typedef signed long int sli;
typedef signed short int ssi;

usi Dimension(void);
void CuadroACero(uli**,usi);
uli** AsignarMemoria(uli**,usi);
void CrearCuadro(uli**,usi);
void MostrarCuadro(uli**, usi);


void main(void)
{
usi dim;
uli **cuadro;
usi i;

do
{
// Valor de la dimensin ...
dim = Dimension();
if(!dim) break;

// Asignacin de memoria ...
cuadro = AsignarMemoria(cuadro,dim);
if(cuadro == NULL) break;
// Inicializamos la matriz a cero...
CuadroACero(cuadro,dim);

// Asignar valores a los elementos del cuadro mgico...
CrearCuadro(cuadro,dim);

Fundamentos de informtica. Programacin en Lenguaje C


294
// Mostrar el cuadro mgico...
MostrarCuadro(cuadro,dim);
printf("\n\nPulse una tecla para mostrar
otro cuadro ... ");
getchar();

// Liberar la memoria reservada

for(i = 0 ; i < dim ; i++) free(*(cuadro + i));
free(cuadro);
}while(dim);
}


/* ------------------------------------------------------- */
/* Funcin Dimension() */
/* ------------------------------------------------------- */

usi Dimension(void)
{
usi d;
do
{
clrscr();
printf("Dimension del cuadro. Debe ser un
valor IMPAR ... ");
printf("\nIndique dimension CERO si
desea terminar la aplicacion -> ");
scanf("%hu",&d);
}while(!(d % 2) && d);
return d;
}


/* ------------------------------------------------------- */
/* Funcin CuadroACero() */
/* ------------------------------------------------------- */

void CuadroACero(uli**C,usi d)
{
usi i, j;
for(i = 0 ; i < d ; i++)
for(j = 0 ; j < d ; j++)
*(*(C + i) + j) = 0;
}


/* ------------------------------------------------------- */
/* Funcin AsignarMemoria() */
/* ------------------------------------------------------- */

Captulo 10. Asignacin dinmica de memoria.


295
uli** AsignarMemoria(uli**C,usi d)
{
usi i;
if((C = (uli**)malloc(d * sizeof(uli*))) == NULL)
{
printf("\nError (1) de asignacion de
memoria.");
printf("\nLa ejecucion del programa no
puede continuar.");
printf("\nPulse cualquier tecla para terminar
la aplicacion");
getchar();
return C;
}

for(i = 0 ; i < d ; i++)
if((*(C + i)=(uli*)malloc(d*sizeof(uli)))==NULL)
{
printf("\nError (2) de asignacin de
memoria.");
printf("\nLa ejecucin del programa no
puede continuar.");
printf("\nPulse cualquier tecla para
terminar la aplicacion");
getchar();
C = NULL;
}

return(C);
}

/* ------------------------------------------------------- */
/* Funcin CrearCuadro() */
/* ------------------------------------------------------- */

void CrearCuadro(uli**C,usi d)
{
usi posX, posY, antX, antY, elem;

// Posicin inicial: el centro de la primera fila...
posX = d / 2;
posY = 0;
elem = 1;

while(elem <= d * d)
{
*(*(C + posX) + posY) = elem;
// Nueva posicin X ...
antX = posX;
posX = posX ? posX - 1 : d - 1;
// Nueva posicion Y ...
Fundamentos de informtica. Programacin en Lenguaje C


296
antY = posY;
posY = posY ? posY - 1 : d - 1;
// Si la casilla ya ha sido ocupada ...
if(*(*(C + posX) + posY))
{
posX = antX;
posY = antY == d - 1 ? 0 : antY + 1;
}
elem++;
}
}

/* ------------------------------------------------------- */
/* Funcin MostrarCuadro() */
/* ------------------------------------------------------- */

void MostrarCuadro(uli**C, usi d)
{
uli *sumaf,*sumac,sumad[2];
usi i, j;

sumac = (uli*)malloc(d * sizeof(uli));
sumaf = (uli*)malloc(d * sizeof(uli));

if(sumaf != NULL)
{
for(i = 0 ; i < d ; i++)
{
*(sumaf + i) = 0;
for(j = 0 ; j < d ; j++)
*(sumaf + i) += *(*(C + i) + j);
}
}

if(sumac != NULL)
{
for(i = 0 ; i < d ; i++)
{
*(sumac + i) = 0;
for(j = 0 ; j < d ; j++)
*(sumac + i) += *(*(C + j) + i);
}
}

sumad[0] = sumad[1] = 0;
for(i = 0 ; i < d ; i++)
sumad[0] += *(*(C + i) + i);
for(i = 1 ; i <= d ; i++)
sumad[1] += *(*(C + d - i) + i - 1);

for(i = 0 ; i < d ; i++)
Captulo 10. Asignacin dinmica de memoria.


297
{
printf("\n");
for(j = 0 ; j < d ; j++)
printf("%5hu",*(*(C + j) + i));
printf(" -> %lu\n",*(sumaf + i));
}

printf("\n");
for(i = 0 ; i < d ; i++)
printf(" |");
printf("\n");
for(i = 0 ; i < d ; i++)
printf(" V");
printf("\n\n");
for(i = 0 ; i < d ; i++)
printf("%5lu",*(sumac + i));

printf("\n\nSuma Diagonal principal .... %5lu",
sumad[0]);
printf("\n\nSuma Diagonal secundaria ... %5lu",
sumad[1]);

}

Fundamentos de informtica. Programacin en Lenguaje C


298








CAPTULO 11

ALGUNOS USOS CON FUNCIONES

En un captulo anterior hemos visto lo bsico sobre funciones. Con todo
lo dicho en ese tema se puede trabajar perfectamente en C, e
implementar multitud de programas, con buena modularidad.
En este tema queremos presentar muy brevemente algunos usos ms
avanzados de las funciones: distintas maneras en que pueden ser
invocadas. Punteros a funciones, vectores de punteros a funciones, el
modo de pasar una funcin como argumento de otra funcin. Son
modos de hacer sencillos, que aaden, a todo lo dicho en el tema
anterior, posibilidades de diseo de programas.
Otra cuestin que abordaremos en este tema es cmo definir aquellas
funciones de las que desconozcamos a priori el nmero de parmetros
que han de recibir. De hecho, nosotros ya conocemos algunas de esas
funciones: la funcin printf puede ser invocada con un solo parmetro
(la cadena de caracteres que no imprima ningn valor) o con tantos
como se quiera: tantos como valores queramos que se impriman en
Fundamentos de informtica. Programacin en Lenguaje C


300
nuestra cadena de caracteres. Veremos tambin aqu la manera de
definir funciones con estas caractersticas.

Punteros a funciones
En los primeros temas de este manual hablbamos de que toda la
informacin de un ordenador se guarda en memoria. No slo los datos.
Tambin las instrucciones tienen su espacio de memoria donde se
almacenan y pueden ser ledas. Todo programa debe ser cargado sobre
la memoria principal del ordenador antes de comenzar su ejecucin.
Y si una funcin cualquiera tiene una ubicacin en la memoria, entonces
podemos hablar de la direccin de memoria de esa funcin. Desde
luego, una funcin ocupar ms de un byte, pero se puede tomar como
direccin de memoria de una funcin aquella donde se encuentra la
entrada de esa funcin.
Y si tengo definida la direccin de una funcin No podr definir un
puntero que almacene esa direccin? La respuesta es que s, y ahora
veremos cmo poder hacerlo. Por tanto, podremos usar un puntero para
ejecutar una funcin. Ese puntero ser el que tambin nos ha de
permitir poder pasar una funcin como argumento de otra funcin.
La declaracin de un puntero a una funcin es la declaracin de una
variable. sta puede ser local, y de hecho, como siempre, ser lo
habitual. Cuando se declara un puntero a funcin para poder asignarle
posteriormente la direccin de una o u otra funcin, la declaracin de
ese puntero a funcin debe tener un prototipo coincidente con las
funciones a las que se desea apuntar.
Supongamos que tenemos las siguientes funciones declaradas al inicio
de un programa:
tipo_funcin nombre_funcin_1 (tipo1, , tipoN);
tipo_funcin nombre_funcin_2 (tipo1, , tipoN);
Captulo 11. Algunos usos con funciones.


301
Y supongamos ahora que queremos declarar, por ejemplo en la funcin
principal, un puntero que pueda recoger la direccin de estas dos
funciones. La declaracin del puntero ser la siguiente:
tipo_ funcin ( * puntero_ a_ funcion) ( tipo1,,tipoN) ;
De esta declaracin podemos hacer las siguientes importantes
observaciones:
1. tipo_funcin debe coincidir con el tipo de la funcin a la que va a
apuntar el puntero a funcin. De la misma manera la lista de
argumentos debe ser coincidente, tanto en los tipos de dato que
intervienen como en el orden. En definitiva, los prototipos de la
funcin y de puntero deber ser idnticos.
2. Si *puntero_a_funcin NO viniese recogido entre parntesis
entonces no estaramos declarando un puntero a funcin, sino una
funcin normal que devuelve un tipo de dato puntero: un puntero
para una recoger la direccin de una variable de tipo tipo_funcin.
Por eso los parntesis no son opcionales.
Una vez tenemos declarado el puntero, el siguiente paso ser siempre
asignarle una direccin de memoria. En ese caso, la direccin de una
funcin. La sintaxis para esta asignacin es la siguiente:
puntero_ a_ funcin = nombre_ funcin_ 1;
Donde nombre_funcin_1 puede ser el nombre de cualquier funcin
cuyo prototipo coincide con el del puntero.
Una observacin importante: al hacer la asignacin de la direccin de la
funcin, hacemos uso del identificador de la funcin: no se emplea el
operador &; tampoco se ponen los parntesis al final del identificador de
la funcin.
Al ejecutar puntero_a_funcion obtendremos un comportamiento idntico
al que tendramos si ejecutramos directamente la funcin. La sintaxis
para invocar a la funcin desde el puntero es la siguiente:
Fundamentos de informtica. Programacin en Lenguaje C


302
resultado = ( * puntero_ a_ funcin) ( var_ 1, ,var_ N) ;
Y as, cuando en la funcin principal se escriba esta sentencia tendremos
el mismo resultado que si se hubiera consignado la sentencia
resultado = nombre_ a_ funcin_ 1( var_ 1, ,var_ N) ;
Antes de ver algunos ejemplos, hacemos una ltima observacin. El
puntero funcin es una variable local en una funcin. Mientras estemos
en el mbito de esa funcin podremos hacer uso de ese puntero. Desde
luego toda funcin trasciende el mbito de cualquier otra funcin; pero
no ocurre as con los punteros.
Veamos algn ejemplo. Hacemos un programa que solicita al usuario
dos operandos y luego si desea sumarlos, restarlos, multiplicarlos o
dividirlos. Entonces muestra el resultado de la operacin. Se definen
cuatro funciones, para cada una de las cuatro posibles operaciones a
realizar. Y un puntero a funcin al que se le asignar la direccin de la
funcin que ha de realizar esa operacin seleccionada.
El cdigo podra quedar como sigue:
#i ncl ude <st di o. h>

float sum( f l oat , f l oat ) ;
float r es( f l oat , f l oat ) ;
float pr o( f l oat , f l oat ) ;
float di v( f l oat , f l oat ) ;

void mai n( void)
{
float a, b;
unsigned char op;
float ( *oper aci on) ( f l oat , f l oat ) ;

pr i nt f ( " Pr i mer oper ador . . . " ) ;
scanf ( " %f " , &a) ;
pr i nt f ( " Segundo oper ador . . . " ) ;
scanf ( " %f " , &b) ;
pr i nt f ( " Oper aci n ( + , - , * , / ) . . . " ) ;
do
op = get char ( ) ;
while( op ! =' +' && op ! =' - ' && op ! =' *' && op ! =' / ' ) ;

switch( op)
Captulo 11. Algunos usos con funciones.


303
{
case ' +' : oper aci on = sum; break;
case ' - ' : oper aci on = r es; break;
case ' *' : oper aci on = pr o; break;
case ' / ' : oper aci on = di v;
}

pr i nt f ( " \ n%f %c %f = %f " , a, op, b, ( *oper aci on) ( a, b) ) ;
}

float sum( float x, float y)
{ return x + y; }

float r es( float x, float y)
{ return x - y; }

float pr o( float x, float y)
{ return x * y; }

float di v( float x, float y)
{ return y ? x / y : 0; }
La definicin de las cuatro funciones no requiere a estas alturas
explicacin alguna. El puntero operacin queda definido como variable
local dentro de main. Dependiendo del valor de la variable op al puntero
se le asignar la direccin de una de las cuatro funciones, todas ellos
con idntico prototipo, igual a su vez al prototipo del puntero.
Evidentemente, esto es slo un ejemplo. Hay otras muchas formas de
resolver el problema, y quiz alguno piense que es ms complicado el
uso del puntero, y que podra hacerse recogido en cada case de la
estructura switch la llamada a la funcin correspondiente. Y no le
faltar razn. Ya hemos dicho muchas veces que aqu tan hay tantas
soluciones vlidas como programadores. Pero desde luego las
posibilidades de implementacin que ofrece el puntero a funcin son
claras.

Vectores de punteros a funciones
No aportamos aqu ningn concepto nuevo, sino una reflexin sobre otra
posibilidad que ofrece el tener punteros a funciones.
Fundamentos de informtica. Programacin en Lenguaje C


304
Como todo puntero, un puntero a funcin puede formar parte de un
array. Y como podemos definir arrays de todos los tipos que queramos,
entonces podemos definir un array de tipo de dato punteros a funciones.
Todos ellos sern del mismo tipo, y por tanto del mismo prototipo de
funcin. La sintaxis de definicin ser la siguiente:
tipo_ funcin ( * puntero_ a_ funcin[ dimensin] ) ( tipo1, tipoN) ;
Y la asignacin puede hacerse directamente en la creacin del puntero,
o en cualquier otro momento:
tipo_ funcin ( * puntero_ a_ funcin[ n] ) ( tipo1, tipoN) =
{ funcion_ 1, funcin_ 2, , funcin_ n }
Donde deber haber tantos nombres de funcin, todas ellas del mismo
tipo, como indique la dimensin del vector. Como siempre, cada una de
las funciones deber quedar declarada y definida en el programa.
El vector de funciones se emplea de forma anloga a cualquier otro
vector. Se puede acceder a cada una de esas funciones mediante
ndices, o por operatoria de punteros.
Podemos continuar con el ejemplo del epgrafe anterior. Supongamos
que la declaracin del puntero queda transformada en la declaracin de
una array de dimensin 4:
float( *oper aci on[ 4] ) ( float, float) = {sum, r es, pr o, di v};
Con esto hemos declarado cuatro punteros, cada uno de ellos apuntando
a cada una de las cuatro funciones definidas. A partir de ahora ser lo
mismo invocar a la funcin sumaf que invocar a la funcin apuntada por
el primer puntero del vector.
Si incorporamos en la funcin main la declaracin de una variable i de
tipo entero, la estructura switch puede quedar ahora como sigue
switch( op)
{
case ' +' : i = 0; break;
case ' - ' : i = 1; break;
case ' *' : i = 2; break;
case ' / ' : i = 3;
Captulo 11. Algunos usos con funciones.


305
}
Y ahora la ejecucin de la funcin ser como sigue:
pr i nt f ( \ n\ n%f %c %f = %f , a, op, b, ( *oper aci n[ i ] ) ( a, b) ) ;

Funciones como argumentos
Se trata ahora de ver cmo hemos de definir un prototipo de funcin
para que pueda recibir a otras funciones como parmetros. Un programa
que usa funciones como argumentos suele ser difcil de comprender y de
depurar, pero se adquiere a cambio una gran potencia en las
posibilidades de C.
La utilidad de pasar funciones como parmetro en la llamada a otra
funcin est en que se puede hacer depender cul sea la funcin a
ejecutar del estado a que se haya llegado a lo largo de la ejecucin.
Estado que no puede prever el programador, porque depender de cada
ejecucin concreta. Y as, una funcin que recibe como parmetro la
direccin de una funcin, tendr un comportamiento u otro segn reciba
la direccin de una u otra de las funciones declaradas y definidas.
La sintaxis del prototipo de una funcin que recibe como parmetro la
direccin de otra funcin es la habitual: primero el tipo de la funcin,
seguido de su nombre y luego, entre parntesis, la lista de parmetros.
Y entre esos parmetros uno o algunos pueden ser punteros a
funciones. La forma en que se indica ese parmetro en la lista de
parmetros es la siguiente:
tipo_ funcin ( * puntero_ a_ funcion) ( parmetros)
(Lo que queda aqu recogido no es el prototipo de la funcin, sino el
modo en que se consigna el parmetro puntero a funcin dentro de una
lista de parmetros en un prototipo de funcin que recibe, entre sus
argumentos, la direccin de una funcin.)
Fundamentos de informtica. Programacin en Lenguaje C


306
Supongamos que este parmetro pertenece al prototipo de la funcin
nombre_funcin. Entonces cuando se compile nombre_funcin el
compilador slo sabr que esta funcin recibir como argumento, entre
otras cosas, la direccin de una funcin que se ajusta al prototipo
declarado como parmetro. Cul sea esa funcin es cuestin que no se
conocer hasta el momento de la ejecucin y de la invocacin a esa
funcin.
La forma en que se llamar a la funcin ser la lgica de acuerdo con
estos parmetros. El nombre de la funcin y seguidamente, entre
parntesis, todos sus parmetros en el orden correcto. En el momento
de recoger el argumento de la direccin de la funcin se har de la
siguiente forma:
* puntero_ a_ funcin( parmetros)
De nuevo ser conveniente seguir con el ejemplo anterior, utilizando
ahora una quinta funcin para realizar la operacin y mostrar por
pantalla su resultado:
#i ncl ude <st di o. h>
#i ncl ude <coni o. h>

float sum( float, float) ;
float r es( float, float) ;
float pr o( float, float) ;
float di v( float, float) ;
void most r ar ( float, char, float, float( *f ) ( float, float) ) ;

void mai n( void)
{
float a, b;
unsigned char op;
float ( *oper aci on[ 4] ) ( float, float) ={sum, r es, pr o, di v};
do
{
pr i nt f ( " \ n\ nPr i mer oper ador . . . " ) ;
scanf ( " %f " , &a) ;
pr i nt f ( " Segundo oper ador . . . " ) ;
scanf ( " %f " , &b) ;
pr i nt f ( " Oper aci n . . . \ n) " ) ;
pr i nt f ( " \ n\ n1. Suma\ n2. Rest a) ;
pr i nt f ( \ n3. Pr oduct o\ n4. Coci ent e" ) ;
pr i nt f ( " \ n\ n\ t Su opci n ( 1 , 2 , 3 , 4) . . . " ) ;
Captulo 11. Algunos usos con funciones.


307
do
op = get che( ) ;
while( op - ' 0' < 1 | | op - ' 0' > 4 ) ;
most r ar ( a, op, b, oper aci on[ ( short) ( op - ' 1' ) ] ) ;
pr i nt f ( " \ n\ nOt r a oper aci n ( s / n) . . . " ) ;
do
op = get che( ) ;
while( op ! = ' s' && op ! = ' n' ) ;
}while( op == ' s' ) ;
}

float sum( float x, float y)
{ return x + y; }

float r es( float x, float y)
{ return x - y; }

float pr o( float x, float y)
{ return x * y; }

float di v( float x, float y)
{ return y ? x / y : 0; }

void most r ar ( float x, char c, float y, float( *f ) ( float, float) )
{
if( c == ' 1' ) c = ' +' ;
else if( c == ' 2' ) c = ' - ' ;
else if( c == ' 3' ) c = ' *' ;
else c = ' / ' ;

pr i nt f ( " \ n\ n%f %c %f = " , x, c, y) ;
pr i nt f ( " %f . " , ( *f ) ( x, y) ) ;
}
Vamos viendo poco a poco el cdigo. Primero aparecen las declaraciones
de cinco funciones: las encargadas de realizar suma, resta, producto y
cociente de dos valores float. Y luego, una quinta funcin, que hemos
llamado mostrar, que tiene como cuarto parmetro un puntero a
funcin. La declaracin de este parmetro es como se dijo: el tipo del
puntero de funcin, el nombre del puntero, recogido entre parntesis y
precedido de un asterisco, y luego, tambin entre parntesis, la lista de
parmetros del puntero a funcin. As ha quedado declarada.
Y luego comienza la funcin principal, main, donde viene declarado un
vector de cuatro punteros a funcin. A cada uno de ellos le hemos
asignado una de las cuatro funciones.
Fundamentos de informtica. Programacin en Lenguaje C


308
Y hemos recogido el cdigo de toda la funcin main en un bloque do
while, para que se realicen tantas operaciones como se deseen. Cada
vez que se indique una operacin se har una llamada a la funcin
mostrar que recibir como parmetro una de las cuatro direcciones de
memoria de las otras cuatro funciones. La llamada es de la forma:
most r ar ( a, op, b, oper aci on[ ( short) ( op - ' 1' ) ] ) ;
Donde el cuarto parmetro es la direccin de la operacin
correspondiente. operacion[0] es la funcin sum; operacion[1] es la
funcin res; operacion[2] es la funcin pro; y operacion[3] es la funcin
div. El valor de op 1 ser 0 si op es el carcter 1; ser 1 si es el
carcter 2; ser 2 si es el carcter 3; y ser 3 si es el carcter 4.
Y ya estamos en la funcin mostrar, que simplemente tiene que ejecutar
el puntero a funcin y mostrar el resultado por pantalla.

Ejemplo: la funcin qsort
Hay ejemplos de uso de funciones pasadas como parmetros muy
utilizados, como por ejemplo la funcin qsort, de la biblioteca stdlib.h.
Esta funcin es muy eficaz en la ordenacin de grandes cantidades de
valores. Su prototipo es:
void qsort( void * base, size_ t nelem, size_ t width, int
( * fcmp) ( const void* , const void* ) ) ;
Es una funcin que no devuelve valor alguno. Recibe como parmetros
el puntero base que es quien recoge la direccin del array donde estn
los elementos a ordenar; nelem, que es un valor entero que indica la
dimensin del vector pasado como primer parmetro; width es el tercer
parmetro, que indica el tamao que tiene cada uno de los elementos
del array; y por fin, el cuarto parmetro, es una funcin que devuelve
un valor entero y que recibe como parmetros dos direcciones de dos
variables. La funcin que se pase como parmetro en este puntero debe
devolver un 1 si su primer parmetro apunta a un valor mayor que el
Captulo 11. Algunos usos con funciones.


309
segundo parmetro; el valor -1 si es al contrario; el valor 0 si el valor de
ambos parmetros son iguales.
Hay que explicar porqu los tipos que recoge el prototipo son siempre
void. El motivo es porque la funcin qsort est definida para ser capaz
de ordenar un array de cualquier tipo. Puede ordenar enteros, reales,
letras, u otros tipos de dato mucho ms complejos, que se pueden crear
y que veremos en un captulo posterior. La funcin no tiene en cuenta el
tipo de dato: simplemente quiere saber dos cosas:
1. El tamao del tipo de dato; y eso se le facilita a la funcin a travs
del tercer parmetro, width.
2. Cmo se define la ordenacin: como a priori no se sabe el tipo de
dato, tampoco puede saber la funcin qsort con qu criterio decidir
qu valores del dominio del tipo de dato son mayores, o iguales, o
menores. Por eso, la funcin qsort requiere que el usuario le facilite,
mediante una funcin muy simple que debe implementar cada
usuario de la funcin qsort, ese criterio de ordenacin.
Actualmente el algoritmo que da soporte a la funcin qsort es el ms
eficaz en las tcnicas de ordenacin de grandes cantidades de valores.
Vamos a ver un ejemplo de uso de esta funcin. Vamos a hacer un
programa que ordene un vector bastante extenso de valores enteros
que asignaremos de forma aleatoria. Para ello deberemos emplear
tambin alguna funcin de generacin de aleatorios. Pero esa es
cuestin muy sencilla que aclaramos antes de mostrar el cdigo de la
funcin que hemos sugerido.
Existe una funcin en stdlib.h llamada random. Esa funcin pretende
ser un generador de nmeros aleatorios. Es un generador bastante
malo, pero para nuestros propsitos sirve. Su prototipo es:
int random( int num) ;
Fundamentos de informtica. Programacin en Lenguaje C


310
Es una funcin que devuelve un entero aleatorio entre 0 y ( 1) num . El
valor de num que se le pasa a la funcin como parmetro tambin debe
ser un valor entero.
Cuando en una funcin se hace uso de la funcin random, antes debe
ejecutarse otra funcin previa: la funcin randomize. Esta funcin
inicializa el generador de aleatorios con un valor inicial tambin
aleatorio. Su prototipo es:
void randomize( void) ;
Y se ejecuta en cualquier momento del programa, pero siempre antes de
la primera vez que se ejecute la funcin random.
Una vez presentadas las funciones necesarias para general aleatorios,
veamos como queda un posible programa que genera una serie de
enteros aleatorios y que los ordena de menor a mayor, haciendo uso de
la funcin qsort:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>

#def i ne TAM 10
#def i ne RANGO 1000
int or denar ( void*, void*) ;

void mai n( void)
{
long numer os[ TAM] ;
long i ;
r andomi ze( ) ;
for( i = 0 ; i < TAM ; i ++)
numer os[ i ] = r andom( RANGO) ;

/ / Vamos a or denar esos numer os . . .

qsor t ( ( void*) numer os, TAM, sizeof( long) , or denar ) ;

/ / Most r amos r esul t ados

for( i = 0 ; i < TAM ; i ++)
pr i nt f ( " numer os[ %4l d] = %l d\ n" , i , numer os[ i ] ) ;
}


Captulo 11. Algunos usos con funciones.


311
/ / La f unci n de or denaci n . . .
int or denar ( void *a, void *b)
{
if( *( long*) a > *( long*) b)
return 1;
else if( *( long*) a < *( long*) b)
return - 1;
return 0;
}
Hemos definido la funcin ordenar con un prototipo idntico al exigido
por la funcin qsort. Recibe dos direcciones de memoria (nosotros
queremos que sea de enteros largos, pero eso no se le puede decir a
qsort) y resuelve cmo discernir la relacin mayor que, menor que, o
identidad entre dos cualesquiera de esos valores que la funcin recibir
como parmetros.
La funcin trata a las dos direcciones de memoria como de tipo de dato
void. El puntero a void ni siquiera sabe qu cantidad de bytes ocupa la
variable a la que apunta. Toma la direccin del byte primero de nuestra
variable, y no hace ms. Dentro de la funcin, el cdigo ya especifica,
mediante el operador forzar tipo, que la variable apuntada por esos
punteros ser tratada como una variable long. Es dentro de nuestra
funcin donde especificamos el tipo de dato de los elementos que vamos
a ordenar. Pero la funcin qsort van a poder usarla todos aquellos que
tengan algo que ordenar, independientemente de qu sea ese algo:
porque todo el que haga uso de qsort le explicar a esa funcin, gracias
al puntero a funciones que recibe como parmetro, el modo en que se
decide quien va antes y quien va despus. Lo que aporta qsort es la
rapidez en poner en orden una cantidad ingente de valores del mismo
tipo.
Y, efectivamente, hay muchas formas de resolver los problemas y de
implementarlos. Y el uso de punteros a funciones, o la posibilidad de
pasar como parmetro de una funcin la direccin de memoria de otra
funcin es una posibilidad que ofrece enormes ventajas y posibilidades.

Fundamentos de informtica. Programacin en Lenguaje C


312
Estudio de tiempos
A veces es muy ilustrativo poder estudiar la velocidad de algunas
aplicaciones que hayamos implementado en C.
En algunos programas de ejemplo de captulos anteriores habamos
presentado un programa que ordenaba cadenas de enteros. Aquel
programa, que ahora mostraremos de nuevo, estaba basado en un
mtodo de ordenacin llamado mtodo de la burbuja: consiste en ir
pasando para arriba aquellos enteros menores, de forma que van
quedando cada vez ms abajo, o ms atrs (segn se quiera) los
enteros mayores. Por eso se llama el mtodo de la burbuja: porque lo
liviano sube.
Vamos a introducir una funcin que controla el tiempo de ejecucin. Hay
funciones bastante diversas para este estudio. Nosotros nos vamos
ahora a centrar en una funcin, disponible en la biblioteca time.h,
llamada clock, cuyo prototipo es:
clock_ t clock( void) ;
Vamos a considerar por ahora que el tipo de dato clock_ t es
equivalente a tipo de dato long (de hecho as es). Esta funcin est
recomendada para medir intervalos de tiempo. El valor que devuelve es
proporcional al tiempo trascurrido desde el inicio de ejecucin del
programa en la que se encuentra esa funcin. Ese valor devuelto ser
mayor cuanto ms tarde se ejecute esta funcin clock, que no realiza
tarea alguna ms que devolver el valor actualizado del contador de
tiempo. Cada breve intervalo de tiempo (bastantes veces por segundo:
no vamos ahora a explicar este aspecto de la funcin) ese contador que
indica el intervalo de tiempo transcurrido desde el inicio de la ejecucin
del programa, se incrementa en uno.
Un modo de estudiar el tiempo trascurrido en un proceso ser el
siguiente:
t i me_t t 1, t 2;
t 1 = cl ock( ) ;
Captulo 11. Algunos usos con funciones.


313
( pr oceso a est udi ar su t i empo)
t 2 = cl ock( ) ;
pr i nt f ( I nt er val o t r anscur r i do: %l d, t 2 t 1) ;
El valor que imprimir este cdigo ser proporcional al tiempo invertido
en la ejecucin del proceso del que estudiamos su ejecucin. Si esa
ejecucin es muy rpida posiblemente el resultado sea cero.
Veamos ahora dos programas de ordenacin. El primero mediante la
tcnica de la burbuja. Como se ve, en esta ocasin trabajamos con un
vector de cien mil valores para ordenar:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <t i me. h>

#def i ne TAM 100000
#def i ne RANGO 10000

int cambi ar ( long*, long*) ;

void mai n( void)
{
long numer os[ TAM] ;
long i , j ;
t i me_t t 1, t 2;
r andomi ze( ) ;
for( i = 0 ; i < TAM ; i ++)
numer os[ i ] = r andom( RANGO) ;
/ / Vamos a or denar esos numer os . . .
/ / Mt odo de l a bur buj a . . .
t 1 = cl ock( ) ;
for( i = 0 ; i < TAM ; i ++)
for( j = i ; j < TAM ; j ++)
if( numer os[ i ] > numer os[ j ] )
cambi ar ( numer os + i , numer os + j ) ;
t 2 = cl ock( ) ;
pr i nt f ( " t 2 - t 1 = %l d. \ n" , t 2 - t 1) ;
}

int cambi ar ( long *a, long *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
Fundamentos de informtica. Programacin en Lenguaje C


314
Si lo ejecuta en su ordenador, le aparecer por pantalla (quiz tarde
unos segundos: depende de lo rpido que sea su ordenador) una
nmero. Si es cero, porque la ordenacin haya resultado muy rpida,
simplemente aumente el valor de TAM y vuelva a compilar y ejecutar el
programa.
Ahora escriba este otro programa, que ordena mediante la funcin
qsort. Es el que hemos visto antes, algo modificado para hacer la
comparacin de tiempos:
#i ncl ude <st di o. h>
#i ncl ude <st dl i b. h>
#i ncl ude <t i me. h>
#def i ne TAM 100000
#def i ne RANGO 10000
int or denar ( void*, void*) ;

void mai n( void)
{
long numer os[ TAM] ;
long i , j ;
t i me_t t 1, t 2;
r andomi ze( ) ;
for( i = 0 ; i < TAM ; i ++)
numer os[ i ] = r andom( RANGO) ;

/ / Vamos a or denar esos numer os . . .
/ / Medi ant e l a f unci n qsor t . . .
t 1 = cl ock( ) ;
qsor t ( ( voi d*) numer os, TAM, si zeof ( long) , or denar ) ;
t 2 = cl ock( ) ;
pr i nt f ( " t 2 - t 1 = %l d. " , t 2 - t 1) ;
}

int or denar ( void *a, void *b)
{
if( *( long*) a > * ( long*) b)
return 1;
else i f ( *( long*) a < *( long*) b)
return - 1;
return 0;
}
Si ejecuta ahora este programa, obtendr igualmente la ordenacin de
los elementos del vector. Pero ahora el valor que saldr por pantalla es
del orden de 500 veces ms bajo.
Captulo 11. Algunos usos con funciones.


315
El algoritmo de ordenacin de la burbuja es muy cmodo de
implementar, y es eficaz para la ordenacin de unos pocos centenares
de enteros. Pero cuando hay que ordenar grandes cantidades, no es
suficiente con que el procedimiento sea tericamente vlido: adems
debe ser eficiente.
Programar no es slo poder en un lenguaje una serie de instrucciones.
Adems de saber lenguajes de programacin es conveniente conocer de
qu algoritmos se disponen para la solucin de nuestros problemas. O
echar mano de soluciones ya adoptadas, como en este caso, la
implementacin de la funcin qsort.

Creacin de MACROS
La directiva #define, que ya hemos visto, permite la definicin de
macros.
Una macro es un bloque de sentencias a las que se les ha asignado un
nombre o identificador.
Una macro no es una funcin. Es cdigo que se inserta all donde
aparece su identificador.
Veamos un ejemplo sencillo:
#i ncl ude <st di o. h>
/ / Def i ni ci n de l a macr o . . .
#def i ne cuadr ado( x) x * x
void mai n( void)
{
short a;
unsigned long b;
pr i nt f ( " I nt oduzca el val or de a . . . " ) ;
scanf ( " %hd" , &a) ;
pr i nt f ( " El cuadr ado de %hd es %l u" , a, cuadr ado( a) ) ;
}
cuadrado NO es una funcin, aunque su invocacin tenga la misma
apariencia. En el cdigo no aparece ni un prototipo con ese nombre, ni
Fundamentos de informtica. Programacin en Lenguaje C


316
su definicin. Es una macro: el cdigo x * x aparecer all donde en
nuestro programa se ponga cuadrado(x).
#define es una directiva del compilador. Antes de compilar, se busca en
todo el texto todas las veces donde venga escrita la cadena
cuadrado(expresin). Y en todas ellas sustituye esa cadena por la
segunda parte de la directiva define: en este caso, lo sustituye por la
cadena expresin * expresin. En nuestro ejemplo hemos calculado el
cuadrado de la variable a; en general, se puede calcular el cuadrado de
cualquier expresin.
En definitiva una macro es un bloque de cdigo que se va a insertar,
previamente a la compilacin, en todas aquellas partes de nuestro
programa donde se encuentre su identificador.
Una macro puede hacer uso de otra macro. Por ejemplo:
#def i ne cuadr ado( x) x * x
#def i ne ci r cul o( r ) 3. 141596 * cuadr ado( r )
La macro circulo calcula la superficie de una circunferencia de radio r.
Para realizar el clculo, hace uso de la macro cuadrado, que calcula el
cuadrado del radio. La definicin de una macro debe preceder siempre a
su uso. No se podra definir la macro circulo como se ha hecho si,
previamente a su definicin, no estuviera recogida la definicin de la
macro cuadrado.
Las macros pueden llegar a ser muy extensas. Vamos a rehacer el
cdigo del programa del tema anterior sobre la criba de Erastthenes,
usando macros en lugar de funciones.

Ejemplo de MACRO: la Criba de Erastthenes
El cdigo mediante funciones que resuelve la criba de Erastthenes ha
quedado resuelto al final de un tema anterior. El propsito ahora es
rehacer toda la aplicacin sin hacer uso de funciones: definiendo
macros.
Captulo 11. Algunos usos con funciones.


317
Para poder ver la diferencia entre utilizar macros y utilizar funciones
convendr presentar de nuevo las dos funciones que se haban definido
para la aplicacin, y poder comparar cmo se rehacen mediante una
directiva de procesador.
Las dos funciones eran la funcin Criba y la funcin TablaPrimos. La
primera era:
long Cr i ba( char* num, long r ango)
{
long i , j ;
/ / En pr i nci pi o mar camos t odos l os el ement os como PRI MOS
for( i = 0 ; i < r ango; i ++)
num[ i ] = ' p' ;
for( i = 2 ; i < sqr t ( r ango) ; i ++)
if( num[ i ] == ' p' )
for( j = 2 * i ; j < r ango ; j += i )
num[ j ] = ' c' ;
for( i = 1 , j = 0 ; i < r ango ; i ++)
if( num[ i ] == ' p' ) j ++;
return j ;
}
Ahora, con la macro, que hemos llamado _ _ Criba, queda de la
siguiente forma:
#def i ne __Cr i ba( _num, _pr ) \
{ \
l ong _i , _j ; \
f or ( _i = 0 ; _i < MAX; _i ++) _num[ _i ] = ' p' ; \
f or ( _i = 2 ; _i < sqr t ( MAX) ; _i ++) \
i f ( _num[ _i ] == ' p' ) \
f or ( _j = 2 * _i ; _j < MAX ; _j += _i ) \
_num[ _j ] = ' c' ; \
f or ( _i = 1 , _j = 0 ; _i < MAX ; _i ++) \
i f ( _num[ _i ] == ' p' ) _j ++; \
_pr = _j ; \
}
Las barras invertidas al final de cada lnea indican que aunque hay un
salto de lnea en el editor, el texto contina en la lnea siguiente. Deben
ponerse tal cual, sin espacio en blanco alguno posteriormente a ellas.
Las diferencias principales con respecto al cdigo de la funcin se basan
en las siguientes peculiaridades de las macros:
Fundamentos de informtica. Programacin en Lenguaje C


318
1. El nombre: mucha gente habita a preceder al nombre de las macros
uno o varios caracteres subrayado; nosotros hemos utilizado dos. Es
cuestin de criterio personal, y es muy conveniente usar un criterio
de creacin de identificadores especial para las macros. Si se decide
que las macros comienzan con dos caracteres subrayado, y tenemos
la disciplina de trabajo de no crear jams, en cdigo normal, un
identificador con ese inicio, entonces es imposible que la macro
pueda generar confusin. Dentro de las macros, es habitual tambin
darle un formato especial a los nombres de las variables que se
definan en ellas: en el ejemplo se han tomado todas las variables, y
todos los parmetros de la macro con un carcter subrayado al
principio. Es muy importante dar nombres especiales a esas
variables: hay que tener en cuenta que el cdigo se inserta tal cual
en la funcin que invoca a la macro: en nuestro ejemplo, si las
variables de la macro se hubieran llamado i y j en lugar de _i y _j,
tendramos un error de compilacin, porque esas variables, con el
identificador i y con el identificador j ya estn creadas en la funcin
principal que utiliza las macros.
2. Hay que considerar que la macro lo que hace es insertar el cdigo en
el lugar donde se coloca el identificador: si necesitamos crear
variables, habr que definir la macro como un bloque (comenzar y
terminar con llaves) para no tener que arrastrar luego todas esas
variables en el mbito de la funcin que llama a la macro. Si la
funcin que convertimos en macro devolva un valor, ahora habr
que ver la manera de que ese valor quede recogido al final de la
macro: lo habitual ser que se pase como parmetro la variable
donde iba a quedar almacenado el valor que devolva la funcin. En
nuestro caso hemos pasado como parmetro la variable pr. La
variable que en la funcin se llamaba rango ha quedado eliminada
porque si trabajamos con macros podemos hacer uso de la que
define el valor de MAX, cosa que evitbamos al redactar la funcin:
no queramos que la funcin tuviera un valor dependiente de una
Captulo 11. Algunos usos con funciones.


319
macro definida en la aplicacin donde se defina la funcin, para
permitir que la funcin fuese lo ms transportable posible, y no
dependiente de un valor ajeno a su propia definicin.
La segunda funcin era:
void Tabl aPr i mos( char* num, long* pr i mos, long r ango)
{
long i , j ;
for( i = 1 , j = 0 ; i < r ango ; i ++)
if( num[ i ] == ' p' )
{
*( pr i mos + j ) = i ;
j ++;
}
*( pr i mos + j ) = 0;
}
Y ahora, con la macro, que hemos llamado _ _ TablaPrimos, queda de
la siguiente forma:
#def i ne __Tabl aPr i mos( _num, _pr i mos) \
{ \
l ong _i , _j ; \
f or ( _i = 1 , _j = 0 ; _i < MAX ; _i ++) \
i f ( _num[ _i ] == ' p' ) \
{ *( _pr i mos + _j ) = _i ; \
_j ++; } \
*( _pr i mos + _j ) = 0; \
}
Donde de nuevo hemos eliminado el uso del tercer parmetro que se
recoga en una variable llamada rango. Hemos mantenido el criterio
para la asignacin de nombres. Hemos recibido como parmetros los
dos punteros: en la macro se llaman con una carcter subrayado previo:
cuando se sustituye el cdigo de la macro en el programa, antes de la
compilacin, el nombre que se recoge es el que se haya consignado
entre parntesis en la llamada de la macro: y ah los nombres van sin
esos caracteres subrayado.
Para un usuario que no haya definido la macro, el que un bloque de
cdigo sea macro o sea funcin es algo que no ha de saber. El modo de
invocacin es el mismo (cambiando los parmetros). Muchas de las
Fundamentos de informtica. Programacin en Lenguaje C


320
funciones estndares de C hacen uso de macros; otras no son realmente
tales, sino que son macros.
La ventaja de la macro es que el cdigo queda insertado en la aplicacin
antes de la compilacin, de forma que su uso no exige la llamada a una
funcin, el apile en memoria de las variables que se deben guardar
mientras salimos de un mbito para meternos en el mbito de la nueva
funcin en ejecucin, etc. El uso de macros reduce los tiempos de
ejecucin de las aplicaciones notablemente. Una macro consume, de
media, un 20 % menos del tiempo total que tardara, en hacer lo
mismo, el cdigo definido en forma de funcin.

Funciones con un nmero variable de argumentos
Hasta el momento hemos visto funciones que tienen definido un nmero
de parmetros concreto. Y son, por tanto, funciones que al ser
invocadas se les debe pasar un nmero concreto y determinado de
parmetros.
Sin embargo no todas las funciones que hemos utilizado son realmente
as de rgidas. Por ejemplo, la funcin printf, tantas veces invocada en
nuestros programas, no tiene un nmero prefijado de parmetros:
pr i nt f ( Aqu sol o hay un par amet r o. ) ; / / Un par met r o
pr i nt f ( Aqu hay %l d par met r os. , 2) ; / / Dos par met r os
pr i nt f ( Y ahor a %l d%c, 3, . ) ; / / Tr es par met r os
Vamos a ver en este epgrafe cmo lograr definir una funcin en la que
el nmero de parmetros sea variable, en funcin de las necesidades
que tenga el usuario en cada momento.
Existen una serie de macros que permiten definir una funcin como si
tuviera una lista variable de parmetros. Esas macros, que ahora
veremos, estn definidas en la biblioteca stdarg.h.
Captulo 11. Algunos usos con funciones.


321
El prototipo de las funciones con un nmero variable de parmetros es
el siguiente:
tipo nombre_ funcion( tipo_ 1,[ ..., tipo_ N] , ...) ;
Primero se recogen todos los parmetros de la funcin que son fijos, es
decir, aquellos que siempre debern aparecer como parmetros en la
llamada a la funcin. En el caso de la funcin printf, siempre debe
aparecer, al principio, una cadena de caracteres, que viene recogida
entre comillas. Si falta esta cadena en la funcin printf tendremos error
en tiempo de compilacin.
Y despus de los parmetros fijos y obligatorios (como veremos ms
adelante, toda funcin que admita un nmero variable de parmetros, al
menos deber tener un parmetro fijo) vienen tres puntos suspensivos.
Esos puntos deben ir al final de la lista de argumentos conocidos, e
indican que la funcin puede tener ms argumentos, de los que no
sabemos ni cuntos ni de qu tipo de dato.
La funcin que tiene un nmero indeterminado de parmetros, deber
averiguar cules recibe en cada llamada. La lista de parmetros deber
ser recogida por la funcin, que deber deducir de esa lista cules son
los parmetros recibidos. Para almacenar y operar con esta lista de
argumentos, est definido, en la biblioteca stdarg.h, un nuevo tipo de
dato de C, llamado va_ list (podramos decir que es el tipo de lista de
argumentos). En esa biblioteca viene definido el tipo de dato y las tres
macros empleadas para operar sobre objetos de tipo lista de
argumentos. Este tipo de dato tendr una forma similar a una cadena de
caracteres.
Toda funcin con un nmero de argumentos variable deber tener
declarada, en su cuerpo, una variable de tipo de dato va_ list.
t i po nombr e_f unci on ( t i po_1, [ . . . , t i po_N] , . . . )
{
va_l i st ar gument os / * l i st a de ar gument os */
Fundamentos de informtica. Programacin en Lenguaje C


322
Lo primero que habr que hacer con esta variable de tipo va_ list ser
inicializarla con la lista de argumentos variables recibida en la llamada a
la funcin.
Para inicializar esa variable se emplea una de las tres macros definidas
en la biblioteca stdarg.h: la macro va_ start, que tiene la siguiente
sintaxis:
void va_ start( va_ list ap, lastfix) ;
Donde ap es la variable que hemos creado como de tipo de dato
va_ list, y donde lastfix es el ltimo argumento fijo de la lista de
argumentos.
La macro va_ start asigna a la variable ap la direccin del primer
argumento variable que ha recibido la funcin. Necesita, para esta
operacin, recibir el nombre del ltimo parmetro fijo que recibe la
funcin como argumento. Se guarda en la variable ap la direccin del
comienzo de la lista de argumentos variables. Esto obliga a que en
cualquier funcin con nmero de argumentos variable exista al menos
un argumento de los llamados aqu fijos, con nombre en la definicin de
la funcin. En caso contrario, sera imposible obtener la direccin del
comienzo para una lista de los restantes argumentos.
Ya tenemos localizada la cadena de argumentos variables. Ahora ser
necesario recorrerla para extraer todos los argumentos que ha recibido
la funcin en su actual llamada. Para eso est definida una segunda
macro de stdarg.h: la macro va_ arg. Esta rutina o macro extrae el
siguiente argumento de la lista.
Su sintaxis es:
tipo va_ arg( va_ list ap, tipo) ;
Donde el primer argumento es, de nuevo, nuestra lista de argumentos,
llamada ap, que ya ha quedado inicializada con la macro va_ start. Y
tipo es el tipo de dato del prximo parmetro que se espera encontrar.
Captulo 11. Algunos usos con funciones.


323
Esa informacin es necesaria: por eso, en la funcin printf, indicamos en
el primer parmetro (la cadena que ha de ser impresa) los
especificadores de formato.
La rutina va extrayendo uno tras otro los argumentos de la lista variable
de argumentos. Para cada nuevo argumento se invoca de nuevo a la
macro. La macro extrae de la lista ap el siguiente parmetro (que ser
del tipo indicado) y avanza el puntero al siguiente parmetro de la lista.
La macro devuelve el valor extrado de la lista. Para extraer todos los
elementos de la lista habr que invocar a la macro va_ arg tantas veces
como sea necesario. De alguna manera la funcin deber detectar que
ha terminado ya de leer en la lista de variables. Por ejemplo, en la
funcin printf, se invocar a la macro va_ arg tantas veces como veces
haya aparecido en la primera cadena un especificador de formato: un
carcter % no precedido del carcter \.
Si se ejecuta la macro va_ arg menos veces que parmetros se hayan
pasado en la actual invocacin, la ejecucin no sufre error alguno:
simplemente dejarn de leerse esos argumentos. Si se ejecuta ms
veces que parmetros variables se hayan pasado, entones el resultado
puede ser imprevisible.
Si, despus de la cadena de texto que se desea imprimir, la funcin
printf recoge ms expresiones (argumentos en la llamada) que
caracteres % ha consignado en la cadena (primer argumento de la
llamada), no pasar percance alguno: simplemente habr argumentos
que no se imprimirn y ni tan siquiera sern extrados de la lista de
parmetros. Pero si hay ms caracteres % que variables e nuestra lista
variable de argumentos, entonces la funcin printf ejecutar la macro
va_ arg en busca de argumentos no existentes. En ese caso, el
resultado ser completamente imprevisible.
Y cuando ya se haya recorrido completa la lista de argumentos,
entonces deberemos ejecutar una tercera rutina que restablece la pila
de llamada a funciones. Esta macro es necesaria para permitir la
Fundamentos de informtica. Programacin en Lenguaje C


324
finalizacin correcta de la funcin y que pueda volver el control de
programa a la sentencia inmediatamente posterior a la de la llamada de
la funcin de argumentos variables.
Su sintaxis es:
void va_ end( va_ list ap) ;
Veamos un ejemplo. Hagamos un programa que calcule la suma de una
serie de variables double que se reciben. El primer parmetro de la
funcin indicar cuntos valores intervienen en la suma; los dems
parmetros sern esos valores. La funcin devolver la suma de todos
ellos:
#i ncl ude <st di o. h>
#i ncl ude <st dar g. h>

double sum( long, . . . ) ;

void mai n( void)
{
double S;
S = sum( 7, 1. 0, 2. 0, 3. 0, 4. 0, 5. 0, 6. 0, 7. 0) ;
pr i nt f ( " %f " , S) ;
}

double sum( long v, . . . )
{
double suma = 0;
long i ;
va_l i st sumandos;
va_st ar t ( sumandos, v) ;
for( i = 0 ; i < v ; i ++)
suma += va_ar g( sumandos, doubl e) ;
va_end( sumandos) ;
return suma;
}
La funcin sumaf recibe un nico parmetro fijo, que es el que indica
cuntas variables ms va a recibir la funcin. As cuando alguien quiere
sumar varios valores, lo que hace es invocar a la funcin sumaf
indicndole en un primer parmetro el nmero de sumandos y, a
continuacin, esos sumandos que se ha indicado.
Captulo 11. Algunos usos con funciones.


325
Despus de inicializar la variable sumandos de tipo va_ list mediante la
macro va_ start, se van sumando todos los argumentos recibidos en la
variable suma. Cada nuevo sumando se obtiene de la cadena sumandos
gracias a una nueva invocacin de la macro va_ arg. Tendr tantas
sumas como indique el parmetro fijo recibido en la variable v.
Al final, y antes de la sentencia return, ejecutamos la macro que
restaura la pila de direcciones de memoria (va_ end), de forma que al
finalizar la ejecucin de la funcin el programa lograr transferir el
control a la siguiente sentencia posterior a la que invoc la funcin de
parmetros variables.
Observacin: las funciones con parmetros variables presentan
dificultades cuando deben cargar, mediante la macro va_ arg, valores
de tipo char, unsigned char y valores float. Hay problemas de
promocin de variables y los resultados no son finalmente los
esperados.

Argumentos de la lnea de rdenes
Ya hemos explicado que en todo programa, la nica funcin ejecutable
es la funcin principal: la funcin main. Un programa sin funcin
principal puede ser compilable, pero no llegar a generar un programa
ejecutable, porque no tiene la funcin de arranque.
As, vemos que todas las funciones de C pueden definirse con
parmetros: es decir, pueden ejecutarse con unos valores de arranque,
que sern diferentes cada vez que esa funcin sea invocada.
Tambin se puede hacer eso con la funcin main. En ese caso, quien
debe pasar los parmetros de arranque a la funcin principal ser el
usuario del programa compilado en el momento en que indique al
sistema operativo el inicio de esa ejecucin de programa.
Fundamentos de informtica. Programacin en Lenguaje C


326
En muchos sistemas operativos (UNIX especialmente) es posible,
cuando se ejecuta un programa compilado de C, pasar parmetros a la
funcin main. Esos parmetros se pasan en la lnea de comandos que
lanza la ejecucin del programa. Para ello, en esa funcin principal se
debe haber incluido los siguientes parmetros:
tipo main( int argc, char * argv[ ] )
Donde argc recibe el nmero de argumentos de la lnea de comandos,
y argv es un array de cadenas de caracteres donde se almacenan los
argumentos de la lnea de comandos.
Los usos ms comunes para los argumentos pasados a la funcin
principal son el pasar valores para la impresin, el paso de opciones de
programa (muy empleado eso en unix, o en DOS), el paso de nombres
de archivos donde acceder a informacin en disco o donde guardar la
informacin generada por el programa, etc.
La funcin main habr recibido tantos argumentos como diga la variable
argc. El primero de esos argumentos es siempre el nombre del
programa. Los dems argumentos deben ser valores esperados, de
forma que la funcin principal sepa qu hacer con cada uno de ellos.
Alguno de esos argumentos puede ser una cadena de control que
indique la naturaleza de los dems parmetros, o al menos de alguno de
ellos.
Desde luego, los nombres de las variables argc y argv son mera
convencin: cualquier identificador que se elija servir de la misma
manera.
Y un ltimo comentario. Hasta el momento, siempre que hemos definido
la funcin main, la hemos declarado de tipo void. Realmente esta
funcin puede ser de cualquier tipo, y en tal caso podra disponer de una
sentencia return que devolviese un valor de ese tipo de dato.
Veamos un ejemplo. Hacemos un programa que al ser invocado se le
pueda facilitar una serie de datos personales, y los muestre por pantalla.
Captulo 11. Algunos usos con funciones.


327
El programa espera del usuario que introduzca su nombre, su profesin
y/o su edad. Si el usuario quiere introducir el nombre, debe precederlo
con la cadena -n; si quiere introducir la edad, deber precederla la
cadena -e; y si quiere introducir la profesin, deber ir precedida de la
cadena -p.
#i ncl ude <st di o. h>
#i ncl ude <st r i ng. h>
void mai n( int ar gc, char*ar gv[ ] )
{
char nombr e[ 30] ;
char edad[ 5] ;
char pr of esi on[ 30] ;
nombr e[ 0] = pr of esi on[ 0] = edad[ 0] = ' \ 0' ;
long i ;
for( i = 0 ; i < ar gc ; i ++)
{
if( st r cmp( ar gv[ i ] , " - n" ) == 0)
{
i ++;
if( i < ar gc) st r cpy( nombr e, ar gv[ i ] ) ;
}
else if( st r cmp( ar gv[ i ] , " - p" ) == 0)
{
i ++;
if( i < ar gc) st r cpy( pr of esi on, ar gv[ i ] ) ;
}
else if( st r cmp( ar gv[ i ] , " - e" ) == 0)
{
i ++;
if( i < ar gc) st r cpy( edad, ar gv[ i ] ) ;
}
}
pr i nt f ( " Nombr e: %s\ n" , nombr e) ;
pr i nt f ( " Edad: %s\ n" , edad) ;
pr i nt f ( " Pr of esi on: %s\ n" , pr of esi on) ;
}
Si ha entrado la cadena n, entonces la siguiente cadena deber ser el
nombre: y as se almacena. Si se ha entrado la cadena p, entonces la
siguiente cadena ser la profesin: y as se guarda. Y lo mismo ocurre
con la cadena e y la edad.
Al compilar el programa, quedar el ejecutable en algn directorio: en
principio en el mismo en donde se haya guardado el documento .cpp. Si
Fundamentos de informtica. Programacin en Lenguaje C


328
ejecutamos ese programa (supongamos que le hemos llamado datos)
con la siguiente lnea de comando:
dat os p est udi ant e e 21 n I sabel
Aparecer por pantalla la siguiente informacin:
Nombr e: I sabel
Edad: 21
Pr of esi on: est udi ant e

Ejercicios

66. Una vez ha creado un vector que contiene todos los primos
menores que un lmite superior dado, declare y defina una
funcin que busque los primos enlazados. Se llaman primos
enlazados a aquellos primos cuya diferencia es igual a dos, es
decir, que son dos impares consecutivos: por ej emplo 11 t 13;
17 y 19, etc. (Sin resolver)

67. Lea el siguiente cdigo y muestre la salida que ofrecer por
pantalla.

#i ncl ude <st di o. h>
#i ncl ude <mat h. h>
#def i ne CUADRADO( x) x*x
#def i ne SUMA( x, y) CUADRADO( x) + CUADRADO( y)
#def i ne RECTO( x, y) sqr t ( SUMA( x, y) )

void mai n( void)
{
pr i nt f ( " %2. 1f " , RECTO( 3, 4) ) ;
}








CAPTULO 12

ESTRUCTURAS ESTTICAS DE
DATOS Y DEFINICIN DE TIPOS

En uno de los primeros temas hablamos largamente de los tipos de
dato. Decamos que un tipo de dato determina un dominio (conjunto de
valores posibles) y unos operadores definidos sobre esos valores.
Hasta el momento hemos trabajado con tipos de dato estndar en C.
Pero con frecuencia hemos hecho referencia a que se pueden crear otros
diversos tipos de dato, ms acordes con las necesidades reales de
muchos problemas concretos que se abordan con la informtica.
Hemos visto, de hecho, ya diferentes tipos de dato a los que por ahora
no hemos prestado atencin alguna, pues an no habamos llegado a
este captulo: tipo de dato size_t, time_t: cada vez que nos los
hemos encontrado hemos despejado con la sugerencia de que se
considerasen, sin ms, tipos de dato iguales a long.
En este tema vamos a ver cmo se pueden definir nuevos tipos de dato.
Fundamentos de informtica. Programacin en Lenguaje C


330

Tipos de dato enumerados
La enumeracin es el modo ms simple de crear un nuevo tipo de dato.
Cuando definimos un tipo de dato enumerado lo que hacemos es definir
de forma explcita cada uno de los valores que formarn parte del
dominio de ese nuevo tipo de dato: es una definicin por extensin.
La sintaxis de creacin de un nuevo tipo de dato enumerado es la
siguiente:
enum identificador {id_1[, id_2, ..., id_N]};
Donde enum es una de las 32 palabras reservadas de C. Donde
identificador es el nombre que va a recibir el nuevo tipo de dato. Y
donde id_1, etc. son los diferentes identificadores de cada uno de los
valores del nuevo dominio creado con el nuevo tipo de dato.
Mediante la palabra clave enum se logran crear tipos de dato que son
subconjunto de los tipos de dato int. Los tipos de dato enumerados
tienen como dominio un subconjunto del dominio de int. De hecho las
variables creadas de tipo enum son tratadas, en todo momento, como
si fuesen de tipo int. Lo que hace enum es mejorar la legibilidad del
programa. Pero a la hora de operar con sus valores, se emplean todos
los operadores definidos para int.
Veamos un ejemplo:
enum semaforo {verde, amarillo, rojo};
Al crear un tipo de dato as, acabamos de definir:
1. Un dominio: tres valores definidos, con los literales verde,
amarillo y rojo. En realidad el ordenador los considera valores 0,
1 y 2.
2. Una ordenacin intrnseca a los valores del nuevo dominio: verde
menor que amarillo, y amarillo menor que rojo.
Captulo 12. Estructuras estticas de datos y definicin de tipos.


331
Acabamos, pues, de definir un conjunto de valores (subconjunto de los
enteros), con identificadores propios y nicos, que presenta la propiedad
de ordenacin.
Luego, en la funcin que sea necesario utilizar ese tipo de dato, se
pueden declarar variables con la siguiente sintaxis:
enum identificador nombre_variable;
En el caso del tipo de dato semforo, podemos definir en una funcin
una variable de la siguiente forma:
enum semaforo cruce;
Veamos otro ejemplo
enum judo {blanco,amarillo,naranja,verde,azul,marron,negro};

void main(void)
{
enum judo c;
printf(Los colores definidos son ... \n);
for(c = blanco ; c <= negro ; c++)
printf("%d\t",c);
}
La funcin principal mostrar por pantalla los valores de todos los
colores definidos: el menor es el banco, y el mayor el negro. Por
pantalla aparecer la siguiente salida:
Los colores definidos son ...
0 1 2 3 4 5 6

Dar nombre a los tipos de dato
A lo largo del presente captulo veremos la forma de crear diferentes
estructuras de datos que den lugar a tipos de dato nuevos. Luego, a
estos tops de dato se les puede asignar un identificador o un nombre
para poder hacer referencia a ellos a la hora de crear nuevas variables.
La palabra clave typedef, de C, permite crear nuevos nombres para los
tipos de dato creados. Una vez se ha creado un tipo de dato y se ha
Fundamentos de informtica. Programacin en Lenguaje C


332
creado el nombre para hacer referencia a l, ya podemos usar ese
identificador en la declaracin de variables, como si fuese un tipo de
dato estndar en C.
En sentido estricto, las sentencias typedef no crean nuevos tipos de
dato: lo que hacen es asignar un identificador definitivo para esos tipos
de dato.
La sintaxis para esa creacin de identificadores es la siguiente:
typedef tipo nombre_tipo;
As, se pueden definir los tipos de dato estndar con otros nombres, que
quiz convengan por la ubicacin en la que se va a hacer uso de esos
valores definidos por el tipo de dato. Y as tenemos:
typedef unsinged size_t;
typedef long time_t;
O podemos nosotros mismos reducir letras:
typedef unsigned long int uli;
typedef unsigned short int usi;
typedef signed short int ssi;
typedef signed long int sli;
O tambin:
typedef char* CADENA;
Y as, a partir de ahora, en todo nuestro programa, nos bastar declarar
las variables enteras como uno de esos nuevos cuatro tipos. O declarar
una cadena de caracteres como una variable de tipo CADENA. Es
evidente que con eso no se ha creado un nuevo tipo de dato, sino
simplemente un nuevo identificador para un tipo de dato ya existente.
Tambin se puede dar nombre a los nuevos tipos de dato creados. En el
ejemplo del tipo de dato enum llamado semaforo, podramos hacer:
typedef enum {verde, amarillo, rojo} semaforo;
Captulo 12. Estructuras estticas de datos y definicin de tipos.


333
Y as, el identificador semforo quedara definitivamente como nuevo
tipo de dato. Luego, en una funcin que necesitase un tipo de dato de
este tipo, ya no diramos:
enum semaforo cruce;
Sino simplemente
semaforo cruce;
Ya que el identificador semaforo ya ha quedado como identificador
vlido de tipo de dato en C.

Estructuras de datos y tipos de dato estructurados
Comenzamos ahora a tratar de la creacin de verdaderos nuevos tipos
de dato. En C, adems de los tipos de dato primitivos, se pueden utilizar
otros tipos de dato definidos por el usuario. Son tipos de dato que
llamamos estructurados, que se construyen mediante componentes de
tipos ms simples previamente definidos o tipos de dato primitivos, que
se denominan elementos de tipo constituyente. Las propiedades que
definen un tipo de dato estructurado son el nmero de componentes que
lo forman (que llamaremos cardinalidad), el tipo de dato de los
componentes y el modo de referenciar a cada uno de ellos.
Un ejemplo de tipo de dato estructurado ya lo hemos definido y utilizado
de hecho: las matrices y los vectores. No hemos considerado esas
construcciones como una creacin de un nuevo tipo de dato sino como
una coleccin ordenada y homognea de una cantidad fija de elementos,
todos ellos del mismo tipo, y referenciados uno a uno mediante ndices.
Pero existe otro modo, en C, de crear un tipo de dato estructurado. Y a
ese nos queremos referir cuando decimos que creamos un nuevo tipo de
dato, y no solamente una coleccin ordenada de elementos del mismo
tipo. Ese tipo de dato se llama registro, y est formado por
yuxtaposicin de elementos que contienen informacin relativa a una
Fundamentos de informtica. Programacin en Lenguaje C


334
misma entidad. Por ejemplo, el tipo de dato asignatura puede tener
diferentes elementos, todos ellos relativos a la entidad asignatura, y no
todos ellos del mismo tipo. Y as, ese tipo de dato registro que hemos
llamado asignatura tendra un elemento que llamaramos clave y que
podra ser de tipo long; y otro campo se llamara descripcin y sera de
tipo char*; y un tercer elemento sera el nmero de crditos y sera de
tipo float, etc. A cada elemento de un registro se le llama campo.
Un registro es un tipo de dato estructurado heterogneo, donde no
todos los elementos (campos) son del mismo tipo. El dominio de este
tipo de dato est formado por el producto cartesiano de los diferentes
dominios de cada uno de los componentes. Y el modo de referenciar a
cada campo dentro del registro es mediante el nombre que se le d a
cada campo.
En C, se dispone de una palabra reservada para la creacin de registros:
la palabra struct.

Estructuras de datos en C
Una estructura de datos en C es una coleccin de variables, no
necesariamente del mismo tipo, que se referencian con un nombre
comn. Lo normal ser crear estructuras formadas por variables que
tengan alguna relacin entre s, de forma que se logra compactar la
informacin, agrupndola de forma cabal. Cada variable de la estructura
se llama, en el lenguaje C, elementos de la estructura. Este concepto es
equivalente al presentado antes al hablar de campos.
La sintaxis para la creacin de estructuras presenta diversas formas.
Empecemos viendo una de ellas:
struct nombre_estructura
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
Captulo 12. Estructuras estticas de datos y definicin de tipos.


335
};
La definicin de la estructura termina, como toda sentencia de C, en un
punto y coma.
Una vez se ha creado la estructura, y al igual que hacamos con las
uniones, podemos declarar variables del nuevo tipo de dato dentro de
cualquier funcin del programa donde est definida la estructura:
struct nombre_estructura variable_estructura;
Y el modo en que accedemos a cada uno de los elementos (o campos)
de la estructura (o registro) ser mediante el operador miembro, que
se escribe con el identificador punto (.):
variable_estructura.identificador_1
Y, por ejemplo, para introducir datos en la estructura haremos:
variable_estructura.identificador_1 = valor_1;
La declaracin de una estructura se hace habitualmente fuera de
cualquier funcin, puesto que el tipo de dato trasciende el mbito de
una funcin concreta. De todas formas, tambin se puede crear el tipo
de dato dentro de la funcin, cuando ese tipo de dato no va a ser
empleado ms all de esa funcin. En ese caso, quiz no sea necesario
siquiera dar un nombre a la estructura, y se pueden crear directamente
las variables que deseemos de ese tipo, con la siguiente sintaxis:
struct
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
}nombre_variable;
Y as queda definida la variable nombre_variable, de tipo de dato
struct. Evidentemente, esta declaracin puede hacerse fuera de
cualquier funcin, de forma que la variable que generemos sea de
mbito global.
Fundamentos de informtica. Programacin en Lenguaje C


336
Otro modo de generar la estructura y a la vez declarar las primeras
variables de ese tipo, ser la siguiente sintaxis:
struct nombre_estructura
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
}variable_1, ..., variable_N;
Y as queda definido el identificador nombre_estructura y quedan
declaradas las variables variable_1, , variable_N, que sern locales o
globales segn se hay hecho esta declaracin en uno u otro mbito.
Lo que est claro es que si la declaracin de la estructura se realiza
dentro de una funcin, entonces nicamente dentro de su mbito el
identificador de la estructura tendr el significado de tipo de dato, y
solamente dentro de esa funcin se podrn utilizar variables de ese tipo
estructurado.
El mtodo ms cmodo para la creacin de estructuras en C es
mediante la combinacin de la palabra struct de la palabra typedef. La
sintaxis de esa forma de creacin es la siguiente:
typedef struct
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
} nombre_estructura;
Y as, a partir de este momento, en cualquier lugar del mbito de esta
definicin del nuevo tipo de dato, podremos crear variables con la
siguiente sintaxis:
nombre_estructura nombre_variable;
Veamos algn ejemplo: podemos necesitar definir un tipo de dato que
podamos luego emplear para realizar operaciones en variable compleja.
Captulo 12. Estructuras estticas de datos y definicin de tipos.


337
Esta estructura, que podramos llamar complejo, tendra la siguiente
forma:
typedef struct
{
double real;
double imag;
}complejo;
Y tambin podramos definir una serie de operaciones, mediante
funciones: por ejemplo, la suma, la resta y el producto de complejos. El
programa completo podra tener la siguiente forma:
#include <stdio.h>
typedef struct
{
double real;
double imag;
}complejo;

complejo sumac(complejo, complejo);
complejo restc(complejo, complejo);
complejo prodc(complejo, complejo);
void mostrar(complejo);

void main (void)
{
complejo A, B, C;
printf("Introduccin de datos ... \n");
printf("Parte real de A ......... ");
scanf("%lf",&A.real);
printf("Parte imaginaria de A ... ");
scanf("%lf",&A.imag);
printf("Parte real de B ......... ");
scanf("%lf",&B.real);
printf("Parte imaginaria de B ... ");
scanf("%lf",&B.imag);
// SUMA ...
printf("\n\n");
mostrar(A);
printf(" + ");
mostrar(B);
C = sumac(A,B);
mostrar(C);

// RESTA ...
printf("\n\n");
mostrar(A);
printf(" - ");
mostrar(B);
Fundamentos de informtica. Programacin en Lenguaje C


338
C = restc(A,B);
mostrar(C);

// PRODUCTO ...
printf("\n\n");
mostrar(A);
printf(" * ");
mostrar(B);
C = prodc(A,B);
mostrar(C);
}

complejo sumac(complejo c1, complejo c2)
{
c1.real += c2.real;
c1.imag += c2.imag;
return c1;
}

complejo restc(complejo c1, complejo c2)
{
c1.real -= c2.real;
c1.imag -= c2.imag;
return c1;
}

complejo prodc(complejo c1, complejo c2)
{
complejo S;
S.real = c1.real + c2.real; - c1.imag * c2.imag;
S.imag = c1.real + c2.imag + c1.imag * c2.real;
return S;

}

void mostrar(complejo X)
{
printf("(% .2lf%s%.2lf * i) ", X.real, X.imag > 0 ? "
+" : " " , X.imag);
}
As podemos ir definiendo un nuevo tipo de dato, con un dominio que es
el producto cartesiano del dominio de los double consigo mismo, y con
unos operadores definidos mediante funciones.
Las nicas operaciones que se pueden hacer sobre la estructura (aparte
de las que podamos definir mediante funciones) son las siguientes:
operador direccin (&), porque toda variable, tambin las estructuradas,
Captulo 12. Estructuras estticas de datos y definicin de tipos.


339
tienen una direccin en la memoria; operador seleccin (.) mediante el
cual podemos acceder a cada uno de los elementos de la estructura; y
operador asignacin, que slo puede de forma que los dos extremos de
la asignacin (tanto el Lvalue como el Rvalue) sean variables objeto del
mismo tipo. Por ejemplo, se puede hacer la asignacin:
complejo A, B;
A.real = 2;
A.imag = 3;
B = A;
Y as, las dos variables valen lo mismo: al elemento real de B se le
asigna el valor consignado en la parte real de A; y al elemento imag de
B se le asigna el valor del elemento imag de A.
Otro ejemplo de estructura podra ser el que antes hemos iniciado, al
hablar de los registros: una estructura para definir un tipo de dato que
sirva para el manejo de asignaturas:
typedef struct
{
long clave;
char descripcion[50];
float creditos;
}asignatura;

Vectores y punteros a estructuras
Una vez hemos creado un nuevo tipo de dato estructurado, no resulta
extrao que podamos crear vectores y matrices de este nuevo tipo de
dato.
Si, por ejemplo, deseamos hacer un inventario de asignaturas, ser
lgico que creemos un array de tantas variables asignatura como sea
necesario.
asignatura curricula[100];
Y as, tenemos 100 variables del tipo asignatura, distribuidas en la
memoria de forma secuencial, una despus de la otra. El modo en que
Fundamentos de informtica. Programacin en Lenguaje C


340
accederemos a cada una de las variables ser, como siempre mediante
la operatoria de ndices: curricula[i]. Y si queremos acceder a algn
miembro de una variable del tipo estructurado, utilizaremos de nuevo el
operador de miembro: curricula[i].descripcin.
Tambin podemos trabajar con operatoria de punteros. As como antes
hemos hablado de curricula[i], tambin podemos llegar a esa variable
del array con la expresin *(curricula + i). De nuevo, todo es igual.
Donde hay un cambio es en el operador de miembro: si trabajamos con
operatoria de punteros, el operador de miembro ya no es el punto,
sino que est formado por los caracteres ->. Si queremos hacer
referencia al elemento o campo descripcion de una variable del tipo
asignatura, la sintaxis ser: *(curricula + i)->descripcion.
Y tambin podemos trabajar con asignacin dinmica de memoria. En
ese caso, se declara un puntero del tipo estructurado, y luego se le
asigna la memoria reservada mediante la funcin malloc. Si creamos un
array de asignaturas en memoria dinmica, un programa de gestin de
esas asignaturas podra ser el siguiente:
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
long clave;
char descr[50];
float cred;
}asig;

void main(void)
{
asig *curr;
short n, i;
printf("Indique n de asignaturas de su CV ... ");
scanf("%hd",&n);
/* La variable n recoge el nmero de elementos de tipo
asignatura que debe tener nuestro array. */
curr = (asig*)malloc(n * sizeof(asig));
if(curr == NULL)
{
printf("Memoria insuficiente.\n");
printf("La ejecucion se interrumpira.\n");
Captulo 12. Estructuras estticas de datos y definicin de tipos.


341
printf("Pulse una tecla para terminar ... ");
getchar();
exit(0);
}
for(i = 0 ; i < n ; i++)
{
printf("\n\nAsignatura %hd ... \n",i + 1);
printf("clave ......... ");
scanf("%ld",&(curr + i)->clave);
printf("Descripcion ... ");
gets((curr + i)->descr);
printf("creditos ...... ");
scanf("%f",&(curr + i)->cred);
}
// Listado ...
for(i = 0 ; i < n ; i++)
{
printf("(%10ld)\t",(curr + i)->clave);
printf("%s\t",(curr + i)->descr);
printf("%4.1f creditos\n",(curr + i)->cred);
}
}
Observamos que (curr + i) es la direccin de la posicin i-sima del
vector curr. Es, pues, una direccin. Y (curr + i)->clave es el valor del
campo clave de la variable que est en la posicin i-sima del vector
curr. Es, pues, un valor: no es una direccin. Y (curr + i)->descr es la
direccin de la cadena de caracteres que forma el campo descr de la
variable que est en la posicin i-sima del vector curr. Es, pues, una
direccin, porque direccin es el campo descr: un array de caracteres.
Que accedamos a la variable estructura a travs de un puntero o a
travs de su identificador influye nicamente en el operador de miembro
que vayamos a utilizar. Una vez tenemos referenciado a travs de la
estructura un campo o miembro concreto, ste ser tratado como
direccin o como valor dependiendo de que el miembro se haya
declarado como puntero o como variable de dato.

Anidamiento de estructuras
Podemos definir una estructura que tenga entre sus miembros una
variable que sea tambin de tipo estructura. Por ejemplo:
Fundamentos de informtica. Programacin en Lenguaje C


342
typedef struct
{
unsigned short dia;
unsigned short mes;
unsigned short anyo;
}fecha;

typedef struct
{
unsigned long clave;
char descripcion[50];
double creditos;
fecha convocatorias[3];
}asignatura;
Ahora a la estructura de datos asignatura le hemos aadido un vector de
tres elementos para que pueda consignar sus fechas de exmenes en
las tres convocatorias. EL ANSI C permite hasta 15 niveles de
anidamiento de estructuras.
El modo de llegar a cada campo de la estructura fecha es, como
siempre, mediante los operadores de miembro. Por ejemplo, si
queremos que la primera convocatoria se realice el 15 de enero del
presente ao, la segunda convocatoria el 21 de junio y la tercera el 1 de
septiembre, las rdenes debern ser:
#include <time.h>
#include <stdio.h>
#include <string.h>

typedef struct
{
unsigned short dia;
unsigned short mes;
unsigned short anyo;
}fecha;

typedef struct
{
unsigned long clave;
char descripcion[50];
double creditos;
fecha c[3];
}asignatura;

void main(void)
{
Captulo 12. Estructuras estticas de datos y definicin de tipos.


343
asignatura asig;
time_t bloquehoy;
struct tm *hoy;
bloquehoy = time(NULL);
hoy = localtime(&bloquehoy);

asig.clave = 10102301;
*asig.descripcion = '\0';
strcat(asig.descripcion,"fundamentos de informtica");
asig.creditos = 7.5;

asig.c[0].dia = 15;
asig.c[0].mes = 1;
asig.c[0].anyo = hoy->tm_year - 100;

asig.c[1].dia = 21;
asig.c[1].mes = 6;
asig.c[1].anyo = hoy->tm_year - 100;

asig.c[2].dia = 1;
asig.c[2].mes = 9;
asig.c[2].anyo = hoy->tm_year - 100;

printf("Asignatura %10ld\n",asig.clave);
printf("%s\t",asig.descripcion);
printf("%4.1lf\n", asig.creditos);
printf("\npriemra convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[0].dia,
asig.c[0].mes,asig.c[0].anyo);
printf("\nsegunda convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[1].dia,
asig.c[1].mes,asig.c[1].anyo);
printf("\ntercera convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[2].dia,
asig.c[2].mes,asig.c[2].anyo);
}
Hemos asignado a cada uno de los tres elementos del vector c los
valores de da, mes y ao correspondientes a cada una de las tres
convocatorias. Hemos utilizado ndices de vectores para referenciar cada
una de las tres fechas. Podramos haber trabajado tambin con
operatoria de punteros. Por ejemplo, la referencia al da de la segunda
convocatoria es, con operatoria de ndices
asig.c[1].dia = 21;
y mediante operatoria de punteros:
Fundamentos de informtica. Programacin en Lenguaje C


344
(asig.c + 1)->dia = 21;
Donde asig.c es la direccin del primer elemento del vector c.
Y donde (asig.c + 1) es la direccin del segundo elemento del vector c.
Y donde (asig.c + 1)->dia es el valor del campo da de la variable de
tipo fecha cuya direccin es (asig.c + 1).
Respecto a la funcin localtime, el tipo de dato estructurado tm, y sus
campos (entre ellos el campo tm_year) puede encontrarse abundante
informacin sobre todo ello en la ayuda on line que ofrece cualquier
compilador. Son definiciones que vienen recogidas en la biblioteca
time.h y son estndares de ANSI C.

Tipo de dato union
Adems de las estructuras, el lenguaje C permite otra forma de creacin
de un nuevo tipo de dato: mediante la creacin de una unin (que se
define mediante la palabra clave en C union: por cierto, con sta,
acabamos de hacer referencia en este manual a la ltima de las 32
palabras del lxico del lenguaje C).
Una unin es una posicin de memoria compartida por dos o ms
variables diferentes, y en general de distinto tipo. Es una regin de
memoria que, a lo largo del tiempo, puede contener objetos de diversos
tipos. Una unin permite almacenar tipos de dato diferentes en el mismo
espacio de memoria. Como las estructuras, las uniones tambin tienen
miembros; pero a diferencia de las estructuras, donde la memoria que
ocupan es igual a la suma del tamao de cada uno de sus campos, la
memoria que emplea una variable de tipo unin es la necesaria para el
miembro de mayor tamao dentro de la unin. La unin almacena
nicamente uno de los valores definidos en sus miembros.
La sintaxis para la creacin de una unin es muy semejante a la
empleada para la creacin de una estructura:
Captulo 12. Estructuras estticas de datos y definicin de tipos.


345
typedef union
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
} nombre_union;
O en cualquiera otra de las formas que hemos visto para la creacin de
estructuras.
Es responsabilidad del programador mantener la coherencia en el uso de
esta variable: si la ltima vez que se asign un valor a la unin fue
sobre un miembro de un determinado tipo, luego, al acceder a la
informacin de la unin, debe hacerse con referencia a un miembro de
un tipo de dato adecuado y coherente con el ltimo que se emple. No
tendra sentido almacenar un dato de tipo float de uno de los campos
de la unin y luego querer leerlo a travs de un campo de tipo char. El
resultado de una operacin de este estilo es imprevisible.
Veamos un ejemplo, y comparmoslo con una estructura de definicin
similar:
#include <stdio.h>
#include <string.h>
typedef union
{
long dni; // nmero de dni.
char ss[30]; // nmero de la seg. social.
}ident1;

typedef struct
{
long dni; // nmero de dni.
char ss[30]; // nmero de la seg. social.
}ident2;

void main(void)
{
ident1 id1;
ident2 id2;

printf("tamao de la union: %ld\n",sizeof(ident1));
printf("tamao de la estructura:%ld\n",sizeof(ident2));

// Datos de la estructura ...
Fundamentos de informtica. Programacin en Lenguaje C


346
id2.dni = 44561098;
*(id2.ss + 0) = NULL;
strcat(id2.ss,"12/0324/5431890");
printf("\nid2.dni = %ld\n",id2.dni);
printf("id2.ss = %s\n",id2.ss);

// Datos de la unin ...
*(id1.ss + 0) = NULL;
strcat(id1.ss,"12/0324/5431890");
printf("\nid1.dni = %ld (mal)\n",id1.dni);// Mal.
printf("id1.ss = %s\n",id1.ss);
id1.dni = 44561098;
printf("\nid1.dni = %ld\n",id1.dni);
printf("id1.ss = %s(mal)\n",id1.ss); // Mal.
}
El programa ofrece la siguiente salida por pantalla;
tamao de la union: 30
tamao de la estructura:34

id2.dni = 44561098
id2.ss = 12/0324/5431890

id1.dni = 808399409 (mal)
id1.ss = 12/0324/5431890

id1.dni = 44561098
id1.ss = 324/5431890 (mal)
El tamao de la estructura es la suma del tamao de sus miembros. El
tamao de la unin es el tamao del mayor de sus miembros.
En la estructura se tienen espacios disjuntos para cada miembro: por un
lado se almacena el valor long de la variable dni y por otro la cadena de
caracteres ss. En la unin, si la ltima asignacin se ha realizado sobre
la cadena, no tiene sentido que se pretenda obtener el valor del
miembro long dni; Y si la ltima asignacin se ha realizado sobre el
campo dni, tampoco tiene sentido pretender leer el valor de la cadena
ss.
Una buena prueba de que la unin comparte la memoria la tenemos en
el ejemplo donde ha quedado como mal uso la impresin del dni. Si
vemos el valor impreso (808399409) y lo pasamos a hexadecimal
tenemos 302F3231. Y si separamos esa cifra en pares, tendremos
Captulo 12. Estructuras estticas de datos y definicin de tipos.


347
cuatro pares: el 30 (que es el cdigo ASCII del carcter 0); el 2F (que
es el cdigo ASCII del carcter /); el 32 (que es el cdigo ASCII del
carcter 2); y el 31, que es el cdigo ASCII del carcter 1). Y si vemos
el valor de los cuatro elementos de la cadena, tenemos que son 12/0.
Precisamente los cuatro que antes hemos reconocido en la codificacin
del mal ledo entero. Y es que hemos ledo los cuatro primeros
caracteres de la cadena como si de un entero se tratase.
En este aspecto, una buena utilidad del mal uso de las uniones (en ese
caso no sera mal uso: sera una treta del programador) ser el poder
obtener el cdigo interno de la informacin de los valores de tipo float.
Veamos como podramos hacerlo:
#include <stdio.h>
typedef union
{
float fvalor;
unsigned long lvalor;
}codigo;

void main(void)
{
unsigned long Test = 0x80000000;
codigo a;

printf("Introduzca float del que desea ");
printf("conocer su codificacion binaria ... ");
scanf("%f", &a.fvalor);

while(Test)
{
printf("%c",a.lvalor & Test ? '1' : '0');
Test >>= 1;
}
}
La funcin principal lee el valor y lo almacena en el campo de tipo float.
Pero luego lo lee como si fuese un dato de tipo long. Y si a ese valor
long le aplicamos el operador and a nivel de bit (permitido en las
variables long, y no permitido en las float) llegamos a poder obtener la
codificacin interna de los valores en coma flotante. No hemos explicado
en este manual la norma IEEE 754 que emplean los PC para codificar
Fundamentos de informtica. Programacin en Lenguaje C


348
esa clase de valores, pero quien quiera conocerla y cotejarla bien puede
hacerlo: la norma est fcilmente accesible; y el programa que
acabamos de presentar permite la visualizacin de esa codificacin.

Ejercicios

68. Definir un tipo de dato entero, de longitud tan grande como se
quiera, y definir tambin los operadores bsicos de ese nuevo
tipo de dato mediante la definicin y declaracin de las
funciones que sean necesarias.

La estructura que define el nuevo tipo de dato podra ser como la que
sigue:
#define Byte4 32
typedef unsigned long int UINT4;

typedef struct
{
/* nmero de elementos UINT4 reservados. */
UINT4 D;
/* nmero de elementos UINT4 utilizados actualmente. */
UINT4 T;
/* nmero de bits significativos. */
UINT4 B;
/* El nmero, que tendr tantos elementos como indique D. */
UINT4 *N;
}NUMERO;
Que tiene cuatro elementos, que servirn para conocer bien las
propiedades del nmero (nuevo tipo de dato entero) y que nos
permitirn agilizar luego numerosas operaciones con ellos. El puntero N
recoger un array (en asignacin dinmica) donde se codificarn los
numeros; a este puntero se le asignan tantos elementos enteros largos
consecutivos como indique el campo D. Y una vez creado el nmero
(reservada la memoria), siempre convendr mantener actualizado el
Captulo 12. Estructuras estticas de datos y definicin de tipos.


349
valor de T y de B: el nmero de elementos enteros largos que realmente
se emplean en cada momento para la codificacin del entero largo; y el
nmero de bits empleados en la codificacin del nmero actual.
Podemos ahora definir una serie de operadores, empleando funciones.
Lo primero ser definir la funcin que asigne memoria al puntero N y
aquella que ponga a cero el nuevo entero creado. A una la llamamos
PonerACero, y a la otra CrearNumero:
void PonerACero(NUMERO*n)
{
/* Esta funcin pone a cero los campos B y T de la variable
NUMERO recibida por referencia. Y deja tambin a cero todos
los dgitos del nmero. Considera que el campo T viene
correctamente actualizado. */
n->B = 0;
while(n->T) *(n->N + --n->T) = 0x0;
}

#define msg01_001 \
"01_001: Error de asignacin de memoria en CrearNumero()\n"
void CrearNumero(NUMERO*n)
{
/* Con esta funcin asignamos una cantidad de memoria a n->N
(tantos elementos UINT4 como indique n->D) */

n->N = (UINT4*)malloc(n->D * sizeof(UINT4));
if(n->N == NULL)
{
printf(msg01_001);
exit(1);
}
n->T = n->D;
PonerACero(n);
}
Y podemos definir ahora otras funciones, necesarias para trabajar
cmodamente en este nuevo tipo de dato. Vamos introducindolas una a
una:
Funcin diseada para copiar el valor de un NUMERO en otro
NUMERO. No bastara hacer copia mediante el asignacin direccin,
porque en ese caso se copiara la direccin de N de uno a otro pero lo
Fundamentos de informtica. Programacin en Lenguaje C


350
que nos interesa es que sean variables diferentes con direcciones
diferentes, que codifiquen el mismo nmero; y se copiaran los valores
del campo D, y eso no nos interesa porque el campo D indica cuntos
elemento de tipo UINT4 se han reservado en el puntero, que depende
de cada NUMERO. La funcin queda:
#define msg01_002 \
"01_002: No se puede hacer copia de la variable. Error en
CopiarNumero()\n"
void CopiarNumero(NUMERO*original,NUMERO*copia)
{
/* Esta funcin copia el valor de nmero grande original->N
en copia->N. Previo a esta operacin, verifica que el tamao
del original no supera la capacidad (copia->D) de la copia.*/

UINT4 c;
if(original->T > copia->D)
{
printf(msg01_002);
exit(1);
}
/* Si el original y la copia no son la misma variable ... */
if(original->N != copia->N)
{
PonerACero(copia);
for(c = 0 ; c < original->T ; c++)
*(copia->N + c) = *(original->N + c);
copia->T = original->T;
copia->B = original->B;
}
}
Funcin que actualice los valores de los campos B y T:
void longitud(NUMERO*n)
{
/* De entrada se le supone el tamao de la dimensin ----- */
n->T = n->D;
while(*(n->N + n->T - 1) == 0 && n->T != 0)
(n->T)--;
/* Una vez tenemos determinado el nmero de elementos UINT4
que forman el nmero, vamos ahora a calcular el nmero de
bits a partir del ms significativo. */
n->B = Byte4 * n->T;
if(n->B)
{
UINT4 Test = 0x80000000;
while(!(*(n->N + n->T - 1) & Test))
{
Captulo 12. Estructuras estticas de datos y definicin de tipos.


351
Test >>= 1;
n->B--;
}
}
}
La siguiente funcin lo que hace es intercambiar los valores de
dos variables NUMERO:
#define msg01_003 \
"01_003: No se pueden intercambiar valores. Error en
inv_v()\n"
void inv_v(NUMERO*n1,NUMERO*n2)
{
/* Si los dos tamaos de los arrays son iguales, entonces
intercambiamos los punteros de los nmeros. */
if(n1->D == n2->D)
{
UINT4*aux;
aux = n1->N;
n1->N = n2->N;
n2->N = aux;
}
/* En caso contrario ... */
else
{
UINT4 L;
L = (n1->T >= n2->T) ? n1->T : n2->T;
if(n1->T > n2->D || n2->T > n1->D)
{
printf(msg01_003);
exit(1);
}
/* Intercambiamos cada uno de los dgitos */
while(L)
{
L--;
*(n1->N + L) ^= *(n2->N + L);
*(n2->N + L) ^= *(n1->N + L);
*(n1->N + L) ^= *(n2->N + L);
}
}
/* Intercambiamos los valores de los tamaos. */
n1->T ^= n2->T;
n2->T ^= n1->T;
n1->T ^= n2->T;
/* Intercambiamos los valores del nmero de bits. */
n1->B ^= n2->B;
n2->B ^= n1->B;
n1->B ^= n2->B;
Fundamentos de informtica. Programacin en Lenguaje C


352
/* Evidentemente, no podemos intercambiar las dimensiones en
que han sido definidos cada uno de los dos nmeros grandes.*/
}
Y la siguiente funcin determina cul de los dos NUMEROs que
se reciben como parmetro es mayor y cul menor:
typedef signed short int SINY2;
typedef signed long int SINT4;
SINT2 orden(NUMERO*n1,NUMERO*n2)
{
/* Devuelve +1 si n1 > n2.
-1 si n1 < n2.
0 si n1 == n2. */
SINT4 c;
if(n1->B < n2->B)
return -1; /* Es decir, n1 < n2. */
if(n1->B > n2->B)
return +1; /* Es decir, n1 > n2. */
/* LLegados aqu tenemos que *n1->T es igual a n2->T... */
for(c = n1->T - 1 ; c >= 0 ; c--)
{
if(*(n1->N + c) < *(n2->N + c))
return -1; /* Es decir, n1 < n2. */
if(*(n1->N + c) > *(n2->N + c))
return +1; /* Es decir, n1 > n2. */
}
return 0;
}
Podemos seguir definiendo funciones auxiliares, como desplazamiento a
izquierda o desplazamiento a derecha, etc. Lo dejamos como ejercicio
para resolver. Tambin queda pendiente trabajar las operaciones
aritmticas de suma, resta, producto, cociente, mdulo, etc. No es
objeto del libro mostrar todo ese cdigo. Hemos mostrado el que
precede porque est formado en su mayor parte por funciones sencillas
y fciles de implementar y porque todas ellas trabajan con estructuras.









CAPTULO 13

GESTIN DE ARCHIVOS

Hasta el momento, toda la informacin (datos) que hemos sido capaces
de gestionar, la hemos tomado de dos nicas fuentes: o eran datos del
programa, o eran datos que introduca el usuario desde el teclado. Y
hasta el momento, siempre que un programa ha obtenido un resultado,
lo nico que hemos hecho ha sido mostrarlo en pantalla.
Y, desde luego, sera muy interesante poder almacenar la informacin
generada por un programa, de forma que esa informacin pudiera luego
ser consultada por otro programa, o por el mismo u otro usuario. O
sera muy til que la informacin que un usuario va introduciendo por
consola quedase almacenada para sucesivas ejecuciones del programa o
para posibles manipulaciones de esa informacin.
En definitiva, sera muy conveniente poder almacenar en algn soporte
informtico (por ejemplo, en el disco del ordenador) esa informacin, y
poder luego acceder a ese disco para volver a tomarla, para actualizar la
Fundamentos de informtica. Programacin en Lenguaje C


354
informacin almacenada, para aadir o para eliminar todo o parte de
ella.
Y eso es lo que vamos a ver en este tema: la gestin de archivos.
Comenzaremos con una breve presentacin de carcter terico sobre los
archivos y pasaremos a ver despus el modo en que podemos emplear
los distintos formatos de archivo.

Tipos de dato con persistencia
Entendemos por tipo de dato con persistencia, o archivo, o fichero
aquel cuyo tiempo de vida no est ligado al de ejecucin del programa
que lo crea o lo maneja. Es decir, se trata de una estructura de datos
externa al programa, que lo trasciende. Un archivo existe desde que un
programa lo crea y mientras que no sea destruido por este u otro
programa.
Un archivo est compuesto por registros homogneos que llamamos
registros de archivo. La informacin de cada registro viene recogida
mediante campos.
Es posible crear ese tipo de dato con persistencia porque esa
informacin queda almacenada sobre una memoria externa. Los
archivos se crean sobre dispositivos de memoria masiva. El lmite de
tamao de un archivo viene condicionado nicamente por el lmite de los
dispositivos fsicos que lo albergan.
Los programas trabajan con datos que residen en la memoria principal
del ordenador. Para que un programa manipule los datos almacenados
en un archivo y, por tanto, en un dispositivo de memoria masiva, esos
datos deben ser enviados desde esa memoria externa a la memoria
principal mediante un proceso de extraccin. Y de forma similar,
cuando los datos que manipula un programa deben ser concatenados
con los del archivo se utiliza el proceso de grabacin.
Captulo 13. Gestin de archivos.


355
De hecho, los archivos se conciben como estructuras que gozan de las
siguientes caractersticas:
1. Capaces de contener grandes cantidades de informacin.
2. Capaces de y sobrevivir a los procesos que lo generan y utilizan.
3. Capaces de ser accedidos desde diferentes procesos o programas.
Desde el punto de vista fsico, o del hardware, un archivo tiene una
direccin fsica: en el disco toda la informacin se guarda (grabacin) o
se lee (extraccin) en bloques unidades de asignacin o clusters
referenciados por un nombre de unidad o disco, la superficie a la que se
accede, la pista y el sector: todos estos elementos caracterizan la
direccin fsica del archivo y de sus elementos. Habitualmente, sin
embargo, el sistema operativo simplifica mucho esos accesos al archivo,
y el programador puede trabajar con un concepto simplificado de
archivo o fichero: cadena de bytes consecutivos terminada por un
carcter especial llamado EOF (End Of File); ese carcter especial
(EOF) indica que no existen ms bytes de informacin ms all de l.
Este segundo concepto de archivo permite al usuario trabajar con datos
persistentes sin tener que estar pendiente de los problemas fsicos de
almacenamiento. El sistema operativo posibilita al programador trabajar
con archivos de una forma sencilla. El sistema operativo hace de interfaz
entre el disco y el usuario y sus programas.
1. Cada vez que accede a un dispositivo de memoria masiva para leer o
paras grabar, el sistema operativo transporta, desde o hasta la
memoria principal, una cantidad fija de informacin, que se llama
bloque o registro fsico y que depende de las caractersticas fsicas
del citado dispositivo. En un bloque o registro fsico puede haber
varios registros de archivo, o puede que un registro de archivo ocupe
varios bloques. Cuantos ms registros de archivo quepan en cada
bloque menor ser el nmero de accesos necesarios al dispositivo de
Fundamentos de informtica. Programacin en Lenguaje C


356
almacenamiento fsico para lograr procesar toda la informacin del
archivo.
2. El sistema operativo tambin realiza la necesaria transformacin de
las direcciones: porque una es la posicin real o efectiva donde se
encuentra el registro dentro del soporte de informacin (direccin
fsica o direccin hardware) y otra distinta es la posicin relativa
que ocupa el registro en nuestro archivo, tal y como es visto este
archivo por el programa que lo manipula (direccin lgica o
simplemente direccin).
3. Un archivo es una estructura de datos externa al programa.
Nuestros programas acceden a los archivos para leer, modificar,
aadir, o eliminar registros. El proceso de lectura o de escritura
tambin lo gobierna el sistema operativo. Al leer un archivo desde un
programa, se transfiere la informacin, de bloque en bloque, desde
el archivo hacia una zona reservada de la memoria principal llamada
buffer, y que est asociada a las operaciones de entrada y salida de
archivo. Tambin se acta a travs del buffer en las operaciones de
escritura sobre el archivo.

Archivos y sus operaciones
Antes de abordar cmo se pueden manejar los archivos en C, ser
conveniente hacer una breve presentacin sobre los archivos con los
que vamos a trabajar: distintos modos en que se pueden organizar, y
qu operaciones se pueden hacer con ellos en funcin de su modo de
organizacin.
Hay diferentes modos de estructurar o de organizar un archivo. Las
caractersticas del archivo y las operaciones que con l se vayan a poder
realizar dependen en gran medida de qu modo de organizacin se
adopte. Las dos principales formas de organizacin que vamos a ver en
este manual son:
Captulo 13. Gestin de archivos.


357
1. Secuencial. Los registros se encuentran en un orden secuencial, de
forma consecutiva. Los registros deben ser ledos, necesariamente,
segn ese orden secuencial. Es posible leer o escribir un cierto
nmero de datos comenzando siempre desde el principio del archivo.
Tambin es posible aadir datos a partir del final del archivo. El
acceso secuencial es una forma de acceso sistemtico a los datos
poco eficiente si se quiere encontrar un elemento particular.
2. Indexado. Se dispone de un ndice para obtener la ubicacin de
cada registro. Eso permite localizar cualquier registro del archivo sin
tener que leer todos los que le preceden.
La decisin sobre cul de las dos formas de organizacin tomar
depender del uso que se d al archivo.
Para poder trabajar con archivos secuenciales, se debe previamente
asignar un nombre o identificador a una direccin de la memoria externa
(a la que hemos llamado antes direccin hardware). Al crear ese
identificador se define un indicador de posicin de archivo que se coloca
en esa direccin inicial. Al iniciar el trabajo con un archivo, el indicador
se coloca en el primer elemento del archivo que coincide con la direccin
hardware del archivo.
Para extraer un registro del archivo, el indicador debe previamente estar
ubicado sobre l; y despus de que ese elemento es ledo o extrado, el
indicador se desplaza automticamente al siguiente registro de la
secuencia.
Para aadir nuevos registros primero es necesario que el indicador se
posicione o apunte al final del archivo. Conforme el archivo va creciendo
de tamao, a cada nuevo registro se le debe asignar nuevo espacio en
esa memoria externa.
Y si el archivo est realizando acceso de lectura, entonces no permite el
de escritura; y al revs: no se pueden utilizar los dos modos de acceso
(lectura y escritura) de forma simultnea.
Fundamentos de informtica. Programacin en Lenguaje C


358
Las operaciones que se pueden aplicar sobre un archivo secuencial son:
1. Creacin de un nuevo archivo vaco. El archivo es una secuencia
vaca: ().
2. Adicin de registros mediante buffer. La adicin almacenar un
registro nuevo concatenado con la secuencia actual. El archivo pasa
a ser la secuencia (secuencia inicial + buffer). La informacin en un
archivo secuencial solo es posible aadirla al final del archivo.
3. Inicializacin para comenzar luego el proceso de extraccin. Con
esta operacin se coloca el indicador sobre el primer elemento de la
secuencia, dispuesto as para comenzar la lectura de registros. El
archivo tiene entonces la siguiente estructura: Izquierda = ();
Derecha = (secuencia); Buffer = primer elemento de (secuencia).
4. Extraccin o lectura de registros. Esta operacin coloca el indicador
sobre el primer elemento o registro de la parte derecha del archivo y
concatena luego el primer elemento de la parte derecha al final de la
parte izquierda. Y eso de forma secuencial: para leer el registro n en
el archivo es preciso leer previamente todos los registros desde el 1
hasta el n 1. Durante el proceso de extraccin hay que verificar,
antes de cada nueva lectura, que no se ha llegado todava al final del
archivo y que, por tanto, la parte derecha an no es la secuencia
vaca.
Y no hay ms operaciones. Es decir, no se puede definir ni la operacin
insercin de registro, ni la operacin modificacin de registro, ni la
operacin borrado de registro. Al menos diremos que no se realizan
fcilmente. La operacin de insercin se puede realizar creando de
hecho un nuevo archivo. La modificacin se podr hacer si al realizar la
modificacin no se aumenta la longitud del registro. Y el borrado no es
posible y, por tanto, en los archivos secuenciales se define el borrado
lgico: marcar el registro de tal forma que esa marca se interprete como
elemento borrado.
Captulo 13. Gestin de archivos.


359

Archivos de texto y binarios
Decamos antes que un archivo es un conjunto de bytes secuenciales,
terminados por el carcter especial EOF.
Si nuestro archivo es de texto, esos bytes sern interpretados como
caracteres. Toda la informacin que se puede guardar en un archivo de
texto son caracteres. Esa informacin podr por tanto ser visualizada
por un editor de texto.
Si se desean almacenar los datos de una forma ms eficiente, se puede
trabajar con archivos binarios. Los nmeros, por ejemplo, no se
almacenan como cadenas de caracteres, sino segn la codificacin
interna que use el ordenador. Esos archivos binarios no pueden
visualizarse mediante un editor de texto.
Si lo que se desea es que nuestro archivo almacene una informacin
generada por nuestro programa y que luego esa informacin pueda ser,
por ejemplo, editada, entonces se deber trabajar con ficheros de
caracteres o de texto. Si lo que se desea es almacenar una informacin
que pueda luego ser procesada por el mismo u otro programa, entonces
es mejor trabajar con ficheros binarios.

Tratamiento de archivos en el lenguaje C
Ya hemos visto todas las palabras reservadas de C. Ninguna de ellas
hace referencia a operacin alguna de entrada o salida de datos. Todas
las operaciones de entrada y salida estn definidas mediante funciones
de biblioteca estndar.
Para trabajar con archivos con buffer, las funciones, tipos de dato
predefinidos y constantes estn recogidos en la biblioteca stdio.h. Para
trabajar en entrada y salida de archivos sin buffer estn las funciones
definidas en io.h.
Fundamentos de informtica. Programacin en Lenguaje C


360
Todas las funciones de stdio.h de acceso a archivo trabajan mediante
una interfaz que est localizada por un puntero. Al crear un archivo, o al
trabajar con l, deben seguirse las normas que dicta el sistema
operativo. De trabajar as se encargan las funciones ya definidas, y esa
gestin es transparente para el programador.
Esa interfaz permite que el trabajo de acceso al archivo sea
independiente del dispositivo final fsico donde se realizan las
operaciones de entrada o salida. Una vez el archivo ha quedado abierto,
se puede intercambiar informacin entre ese archivo y el programa. El
modo en que la interfaz gestiona y realiza ese trfico es algo que no
afecta para nada a la programacin.
Al abrir, mediante una funcin, un archivo que se desee usar, se indica,
mediante un nombre, a qu archivo se quiere acceder; y esa funcin de
apertura devuelve al programa una direccin que deber emplearse en
las operaciones que se realicen con ese archivo desde el programa. Esa
direccin se recoge en un puntero, llamado puntero de archivo. Es un
puntero a una estructura que mantiene informacin sobre el archivo: la
direccin del buffer, el cdigo de la operacin que se va a realizar, etc.
De nuevo el programador no se debe preocupar de esos detalles:
simplemente debe declarar en su programa un puntero a archivo, como
ya veremos ms adelante.
El modo en que las funciones estndar de ANSI C gestionan todo el
acceso a disco es algo transparente al programador. Cmo trabaja
realmente el sistema operativo con el archivo sigue siendo algo que no
afecta al programador. Pero es necesario que de la misma manera que
una funcin de ANSI C ha negociado con el sistema operativo la
apertura del archivo y ha facilitado al programador una direccin de
memoria, tambin sea una funcin de ANSI C quien cierre al final del
proceso los archivos abiertos, de forma tambin transparente para el
programador. Si se interrumpe inesperadamente la ejecucin de un
programa, o ste termina sin haber cerrado los archivos que tiene
Captulo 13. Gestin de archivos.


361
abiertos, se puede sufrir un dao irreparable sobre esos archivos, y
perderlos o perder parte de su informacin.
Tambin es transparente al programador el modo en que se accede de
hecho a la informacin del archivo. El programa no accede nunca al
archivo fsico, sino que acta siempre y nicamente sobre la memoria
intermedia o buffer, que es el lugar de almacenamiento temporal de
datos. nicamente se almacenan los datos en el archivo fsico cuando la
informacin se transfiere desde el buffer hasta el disco. Y esa
transferencia no necesariamente coincide con la orden de escritura o
lectura que da el programador. De nuevo, por tanto, es muy importante
terminar los procesos de acceso a disco de forma regular y normalizada,
pues de lo contrario, si la terminacin del programa se realiza de forma
anormal, es muy fcil que se pierdan al menos los datos que estaban
almacenados en el buffer y que an no haban sido, de hecho,
transferidos a disco.

Archivos secuenciales con buffer.
Antes de utilizar un archivo, la primera operacin, previa a cualquier
otra, es la de apertura.
Ya hemos dicho que cuando abrimos un archivo, la funcin de apertura
asignar una direccin para ese archivo. Debe por tanto crearse un
puntero para recoger esa direccin.
En la biblioteca stdio.h est definido el tipo de dato FILE, que es tipo
de dato puntero a archivo. Este puntero nos permite distinguir entre los
diferentes ficheros abiertos en el programa. Crea la secuencia o interfaz
que nos permite la transferencia de informacin con el archivo
apuntado.
La sintaxis para la declaracin de un puntero a archivo es la siguiente:
FILE *puntero_a_archivo;
Fundamentos de informtica. Programacin en Lenguaje C


362
Vamos ahora a ir viendo diferentes funciones definidas en stdio.h para
la manipulacin de archivos.
Apertura de archivo.
La funcin fopen abre un archivo y devuelve un puntero asociado al
mismo, que puede ser utilizado para que el resto de funciones de
manipulacin de archivos accedan a este archivo abierto.
Su prototipo es:
FILE *fopen(const char*nombre_archivo, const char
*modo_apertura);
Donde nombre_archivo es el nombre del archivo que se desea abrir.
Debe ir entre comillas dobles, como toda cadena de caracteres. El
nombre debe estar consignado de tal manera que el sistema operativo
sepa identificar el archivo de qu se trata.
Y donde modo_apertura es el modo de acceso para el que se abre el
archivo. Debe ir en comillas dobles. Los posibles modos de apertura de
un archivo secuencial con buffer son:
r Abre un archivo de texto para lectura. El archivo debe existir.
w Abre un archivo de texto para escritura. Si existe ese archivo,
lo borra y lo crea de nuevo. Los datos nuevos se escriben
desde el principio.
a Abre un archivo de texto para escritura. Los datos nuevos se
aaden al final del archivo. Si ese archivo no existe, lo crea.
r+ Abre un archivo de texto para lectura/escritura. Los datos se
escriben desde el principio. El fichero debe existir.
w+ Abre un archivo de texto para lectura/escritura. Los datos se
escriben desde el principio. Si el fichero no existe, lo crea.
Captulo 13. Gestin de archivos.


363
rb Abre un archivo binario para lectura. El archivo debe existir.
wb Abre un archivo binario para escritura. Si existe ese archivo, lo
borra y lo crea de nuevo. Los datos nuevos se escriben desde
el principio.
ab Abre un archivo binario para escritura. Los datos nuevos se
aaden al final del archivo. Si ese archivo no existe, lo crea.
r+b Abre un archivo binario para lectura/escritura. Los datos se
escriben desde el principio. El fichero debe existir.
w+b Abre un archivo binario para lectura/escritura. Los datos se
escriben desde el principio. Si el fichero no existe, lo crea.
Ya vemos que hay muy diferentes formas de abrir un archivo. Queda
claro que de todas ellas destacan dos bloques: aquellas que abren el
archivo para manipular una informacin almacenada en binario, y otras
que abren el archivo para poder manipularlo en formato texto. Ya
iremos viendo ambas formas de trabajar la informacin a medida que
vayamos presentando las distintas funciones.
La funcin fopen devuelve un puntero a una estructura que recoge las
caractersticas del archivo abierto. Si se produce algn error en la
apertura del archivo, entonces la funcin fopen devuelve un puntero
nulo.
Ejemplos simples de esta funcin seran:
FILE *fichero;
fichero = fopen(datos.dat,w);
Que deja abierto el archivo datos.dat para escritura. Si ese archivo ya
exista, queda eliminado y se crea otro nuevo y vaco.
El nombre del archivo puede introducirse mediante variable:
char nombre_archivo[80];
printf(Indique el nombre del archivo ... );
Fundamentos de informtica. Programacin en Lenguaje C


364
gets(nombre_archivo);
fopen(nombre_archivo, w);
Y ya hemos dicho que si la funcin fopen no logra abrir el archivo,
entonces devuelve un puntero nulo. Es muy conveniente verificar
siempre que el fichero ha sido realmente abierto y que no ha habido
problemas:
FILE *archivo;
if(archivo = fopen(datos.dat, w) == NULL)
printf(No se puede abrir el archivo\n);
Dependiendo del compilador se podrn tener ms o menos archivos
abiertos a la vez. En todo caso, siempre se podrn tener, al menos ocho
archivos abiertos simultneamente.
Cierre del archivo abierto.
La funcin fclose cierra el archivo que ha sido abierto mediante fopen.
Su prototipo es el siguiente:
int fclose(FILE *nombre_archivo);
La funcin devuelve el valor cero si ha cerrado el archivo correctamente.
Un error en el cierre de un archivo puede ser fatal y puede generar todo
tipo de problemas. El ms grave de ellos es el de la prdida parcial o
total de la informacin del archivo.
Cuando una funcin termina normalmente su ejecucin, cierra de forma
automtica todos sus archivos abiertos. De todas formas es conveniente
cerrar los archivos cuando ya no se utilicen dentro de la funcin, y no
mantenerlos abiertos en espera de que se finalice su ejecucin.
Escritura de un carcter en un archivo.
Existen dos funciones definidas en stdio.h para escribir un carcter en
el archivo. Ambas realizan la misma funcin y ambas se utilizan
indistintamente. La duplicidad de definicin es necesaria para preservar
la compatibilidad con versiones antiguas de C.
Los prototipos de ambas funciones son:
Captulo 13. Gestin de archivos.


365
int putc(int c, FILE *nombre_archivo);
int fputc(int c, FILE * nombre_archivo);
Donde nombre_archivo recoge la direccin que ha devuelto la funcin
fopen. El archivo debe haber sido abierto para escritura y en formato
texto. Y donde la variable c es el carcter que se va a escribir. Por
razones histricas, ese carcter se define como un entero, pero de esos
dos o cuatro bytes (dependiendo de la longitud de la palabra) slo se
toma en consideracin el menos significativo.
Si la operacin de escritura se realiza con xito, la funcin devuelve el
mismo carcter escrito.
Vamos a hacer un programa que solicite al usuario su nombre y
entonces guarde ese dato en un archivo que llamaremos nombre.dat.
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
char nombre[80];
short int i;
FILE *archivo;

printf("Su nombre ... ");
gets(nombre);

archivo = fopen("nombre.dat", "w");
if(archivo == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
i = 0;
while(nombre[i] != NULL)
{
fputc(nombre[i],archivo);
i++;
}
fclose(archivo);
}
Fundamentos de informtica. Programacin en Lenguaje C


366
Una vez ejecutado el programa, y si todo ha ido correctamente, se
podr abrir el archivo nombre.dat con un editor de texto y comprobar
que realmente se ha guardado el nombre en ese archivo.
Lectura de un carcter desde un archivo.
De manera anloga a las funciones de escritura, existen tambin
funciones de lectura de caracteres desde un archivo. De nuevo hay dos
funciones equivalentes, cuyos prototipos son:
int fgetc(FILE *nombre_archivo);
int getc(FILE * nombre_archivo);
Que reciben como parmetro el puntero devuelto por la funcin fopen al
abrir el archivo y devuelven el carcter, de nuevo como un entero. El
archivo debe haber sido abierto para lectura y en formato texto. Cuando
ha llegado al final del archivo, la funcin fgetc, o getc, devuelve una
marca de fin de archivo que se codifica como EOF.
El cdigo para leer el nombre desde el archivo donde ha quedado
almacenado en el programa anterior sera:
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
char nombre[80];
short int i;
FILE *archivo;

archivo = fopen("nombre.dat", "r");
if(archivo == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
i = 0;
while((nombre[i++] = fgetc(archivo)) != EOF);
/* El ltimo elemento de la cadena ha quedado igual
a EOF. Se cambia al carcter fin de cadena, NULL */
nombre[--i] = NULL;
fclose(archivo);
Captulo 13. Gestin de archivos.


367
printf("Su nombre ... %s", nombre);
}
Este cdigo mostrar por pantalla el nombre almacenado en el archivo
nombre.dat.
Lectura y escritura de una cadena de caracteres.
Las funciones fputs y fgets escriben y leen, respectivamente, cadenas
de caracteres sobre archivos de disco.
Sus prototipos son:
int fputs(const char *s, FILE *nombre_archivo);
char *fgets(char *s, int n, FILE * nombre_archivo);
La funcin fputs escribe la cadena s en el archivo indicado por el
puntero nombre_archivo. Si la operacin ha sido correcta, devuelve un
valor no negativo. El archivo debe haber sido abierto en formato texto y
para escritura o para lectura, dependiendo de la funcin que se emplee.
La funcin fgets lee del archivo indicado por el puntero nombre_archivo
una cadena de caracteres. Lee los caracteres desde el inicio hasta un
total de n, que es el valor que recibe como segundo parmetro. Si antes
del carcter n-simo ha terminado la cadena, tambin termina la lectura
y cierra la cadena con un carcter nulo.
En el programa que vimos para la funcin fputc podramos eliminar la
variable i y cambiar la estructura while por la sentencia:
fputs(nombre,archivo);
Y en el programa que vimos para la funcin fgetc, la sentencia podra
quedar sencillamente:
fgets(nombre, 80, archivo);
Lectura y escritura formateada.
Fundamentos de informtica. Programacin en Lenguaje C


368
Las funciones fprintf y fscanf de entrada y salida de datos por disco
tienen un uso semejante a las funciones printf y scanf, de entrada y
salida por consola.
Sus prototipos son:
int fprintf(FILE *nombre_archivo, const char *cadena_formato [,
argumento, ...]);
int fscanf(FILE *nombre_archivo, const char *cadena_formato [,
direccin, ...]);
Donde nombre_archivo es el puntero a archivo que devuelve la
funcin fopen. Los dems argumentos de estas dos funciones ya los
conocemos, pues son los mismos que las funciones de entrada y salida
por consola. La funcin fscanf devuelve el carcter EOF si ha llegado al
final del archivo. El archivo debe haber sido abierto en formato texto y
para escritura o para lectura, dependiendo de la funcin que se emplee.
Veamos un ejemplo de estas dos funciones. Hagamos un programa que
guarde en un archivo (que llamaremos numeros.dat) los valores que
previamente se han asignado de forma aleatoria a un vector de
variables float. Esos valores se almacenan dentro de una cadena de
texto. Y luego, el programa vuelve a abrir el archivo para leer los datos
y cargarlos en otro vector y los muestra en pantalla.
#include <stdio.h>
#include <stdlib.h>
#define TAM 10
void main(void)
{
float or[TAM], cp[TAM];
short i;
FILE *ARCH;
char c[100];

randomize();
for(i = 0 ; i < TAM ; i++)
or[i] = (float)random(1000) / random(100);

ARCH = fopen("numeros.dat", "w");
if(ARCH == NULL)
Captulo 13. Gestin de archivos.


369
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
for(i = 0 ; i < TAM ; i++)
fprintf(ARCH,"Valor %04hi-->%12.4f\n",i,or[i]);

fclose(ARCH);

ARCH = fopen("numeros.dat", "r");
if(ARCH == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}

printf("Los valores guardados en el archivo son:\n");
i = 0;
while(fscanf(ARCH,"%s%s%s%f",c,c,c,cp + i++)!= EOF);
for(i = 0 ; i < TAM ; i++)
printf("Valor %04hd --> %12.4f\n",i,cp[i]);
fclose(ARCH);
}
El archivo contiene (en una ejecucin cualquiera: los valores son
aleatorios, y en cada ejecucin llegaremos a valores diferentes) la
siguiente informacin:
Valor 0000 --> 9.4667
Valor 0001 --> 30.4444
Valor 0002 --> 12.5821
Valor 0003 --> 0.2063
Valor 0004 --> 16.4545
Valor 0005 --> 28.7308
Valor 0006 --> 9.9574
Valor 0007 --> 0.1039
Valor 0008 --> 18.0000
Valor 0009 --> 4.7018
Hemos definido la variable c para que vaya cargando desde el archivo
los tramos de cadena de caracteres que no nos interesan para la
obtencin, mediante la funcin fscanf, de los sucesivos valores float
generados. Con esas tres lecturas de cadena la variable c va leyendo las
cadenas Valor; la cadena de caracteres que recoge el ndice i; la
Fundamentos de informtica. Programacin en Lenguaje C


370
cadena-->. La salida por pantalla tendr la misma apariencia que la
obtenida en el archivo.
Desde luego, con la funcin fscanf es mejor codificar bien la informacin
del archivo, porque de lo contrario la lectura de datos desde el archivo
puede llegar a hacerse muy incmoda.
Lectura y escritura en archivos binarios.
Ya hemos visto las funciones para acceder a los archivos secuenciales de
tipo texto. Vamos a ver ahora las funciones de lectura y escritura en
forma binaria.
Si en todas las funciones anteriores hemos requerido que la apertura del
fichero o archivo se hiciera en formato texto, ahora desde luego, para
hacer uso de las funciones de escritura y lectura en archivos binarios, el
archivo debe hacer sido abierto en formato binario.
Las funciones que vamos a ver ahora permiten la lectura o escritura de
cualquier tipo de dato.
Los prototipos son los siguientes:
size_t fread(void *buffer, size_t n_bytes, size_t contador, FILE
*nombre_archivo);
size_t fwrite(const void *buffer, size_t n_bytes, size_t contador,
FILE *nombre_archivo);
Donde buffer es un puntero a la regin de memoria donde se van a
escribir los datos ledos en el archivo, o el lugar donde estn los datos
que se desean escribir en el archivo. Habitualmente ser la direccin de
una variable. n_bytes es el nmero de bytes que ocupa cada dato que
se va a leer o grabar, y contador indica el nmero de datos de ese
tamao que se van a leer o grabar. El ltimo parmetro es el de la
direccin que devuelve la funcin fopen cuando se abre el archivo.
Captulo 13. Gestin de archivos.


371
Ambas funciones devuelven el nmero de elementos escritos o ledos.
Ese valor debe ser el mismo que el de la variable contador, a menos que
haya ocurrido un error.
Estas dos funciones son tiles para leer y escribir cualquier tipo de
informacin. Es habitual emplearla junto con el operador sizeof, para
determinar as la longitud (n_bytes) de cada elemento a leer o escribir.
El ejemplo anterior puede servir para ejemplificar ahora el uso de esas
dos funciones. El archivo numeros.dat ser ahora de tipo binario. El
programa cargar en forma binaria esos valores y luego los leer para
calcular el valor medio de todos ellos y mostrarlos por pantalla:
#include <stdio.h>
#include <stdlib.h>
#define TAM 10
void main(void)
{
float or[TAM], cp[TAM];
double suma = 0;
short i;
FILE *ARCH;

randomize();
for(i = 0 ; i < TAM ; i++)
or[i] = (float)random(1000) / random(100);

ARCH = fopen("numeros.dat", "wb");
if(ARCH == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
fwrite(or,sizeof(float),TAM,ARCH);
fclose(ARCH);

ARCH = fopen("numeros.dat", "rb");
if(ARCH == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
fread(cp,sizeof(float),TAM,ARCH);
Fundamentos de informtica. Programacin en Lenguaje C


372
fclose(ARCH);

for(i = 0 ; i < TAM ; i++)
{
printf("Valor %04hd --> %12.4f\n",i,cp[i]);
suma += *(cp + i);
}
printf("\n\nLa media es ... %lf", suma / TAM);
}
Otras funciones tiles en el acceso a archivo
Funcin feof: Esta funcin (en realidad es una macro) determina el final
de archivo. Es conveniente usarla cuando se trabaja con archivos
binarios, donde se puede inducir a error y tomar como carcter EOF un
valor entero codificado.
Su prototipo es:
int feof(FILE *nombre_archivo);
que devuelve un valor diferente de cero si en la ltima operacin de
lectura se ha detectado el valor EOF. en caso contrario devuelve el valor
cero.
Funcin ferror: Esta funcin (en realidad es una macro) determina si se
ha producido un error en la ltima operacin sobre el archivo. Su
prototipo es:
int ferror(FILE * nombre_archivo);
Si el valor devuelto es diferente de cero, entonces se ha producido un
error; si es igual a cero, entonces no se ha producido error alguno.
Si deseamos hacer un programa que controle perfectamente todos los
accesos a disco, entonces convendr ejecutar esta funcin despus de
cada operacin de lectura o escritura.
Funcin remove: Esta funcin elimina un archivo. El archivo ser
cerrado si estaba abierto y luego ser eliminado. Quiere esto decir que
el archivo quedar destruido, que no es lo mismo que quedarse vaco.
Su prototipo es:
Captulo 13. Gestin de archivos.


373
int remove(const char * nombre_archivo);
Donde nombre_archivo es el nombre del archivo que se desea borrar.
En ese nombre, como siempre, debe ir bien consignada la ruta completa
del archivo. Un archivo as eliminado no es recuperable.
Por ejemplo, en nuestro ejemplos anteriores, despus de haber hecho la
transferencia de datos al vector de float, podramos ya eliminar el
archivo de nuestro disco. Hubiera abastado poner la sentencia:
remove("numeros.dat");
Si el archivo no ha podido ser eliminado (por denegacin de permiso o
porque el archivo no existe en la ruta y nombre que ha dado el
programa) entonces la funcin devuelve el valor -1. Si la operacin de
eliminacin del archivo ha sido correcta, entonces devuelve un cero.
En realidad, la macro remove lo nico que hace es invocar a la funcin
de borrado definida en io.h: la funcin unlink, cuyo prototipo es:
int unlink(const char *filename);
Y cuyo comportamiento es idntico al explicado para la macro remove.

Entrada y salida sobre archivos de acceso aleatorio
Disponemos de algunas funciones que permiten acceder de forma
aleatoria a una u otra posicin del archivo.
Ya dijimos que un archivo, desde e punto de vista del programador es
simplemente un puntero a la posicin del archivo (en realidad al buffer)
donde va a tener lugar el prximo acceso al archivo. Cuando se abre el
archivo ese puntero recoge la direccin de la posicin cero del archivo,
es decir, al principio. Cada vez que el programa indica escritura de
datos, el puntero termina ubicado al final del archivo.
Pero tambin podemos, gracias a algunas funciones definidas en io.h,
hacer algunos accesos aleatorios. En realidad, el nico elemento nuevo
Fundamentos de informtica. Programacin en Lenguaje C


374
que se incorpora al hablar de acceso aleatorio es una funcin capaz de
posicionar el puntero del archivo devuelto por la funcin fopen en
distintas partes del fichero y poder as acceder a datos intermedios.
La funcin fseek puede modificar el valor de ese puntero, llevndolo
hasta cualquier byte del archivo y logrando as un acceso aleatorio. Es
decir, que las funciones estndares de ANSI C logran hacer accesos
aleatorios nicamente mediante una funcin que se aade a todas las
que ya hemos visto para los accesos secuenciales.
El prototipo de la funcin, definida en la biblioteca stdio.h es el
siguiente:
int fseek(FILE *nomnre_archivo, long despl, int modo);
Donde nombre_archivo es el puntero que ha devuelto la funcin fopen
al abrir el archivo; donde despl es el desplazamiento, en bytes, a
efectuar; y donde modo es el punto de referencia que se toma para
efectuar el desplazamiento. Para esa definicin de modo, stdio.h define
tres constantes diferentes:
SEEK_SET, que es valor 0.
SEEK_CUR, que es valor 1,
SEEK_END, que es valor 2.
El modo de la funcin fseek puede tomar como valor cualquiera de las
tres constantes. Si tiene la primera (SEEK_SET), el desplazamiento se
har a partir del inicio del fichero; si tiene la segunda (SEEK_CUR), el
desplazamiento se har a partir de la posicin actual del puntero; si
tiene la tercera (SEEK_END), el desplazamiento se har a partir del final
del fichero.
Para la lectura del archivo que habamos visto para ejemplificar la
funcin fscanf, las sentencias de lectura quedaran mejor si se hiciera
as:
printf("Los valores guardados en el archivo son:\n");
i = 0;
Captulo 13. Gestin de archivos.


375
while(!feof(ARCH))
{
fseek(ARCH,16,SEEK_CUR);
fscanf(ARCH,"%f",cp + i++);
}
Donde hemos indicado 16 en el desplazamiento en bytes, porque 16 son
los caracteres que no deseamos que se lean en cada lnea.
Los desplazamientos en la funcin fseek pueden ser positivos o
negativos. Desde luego, si los hacemos desde el principio lo razonable
es hacerlos positivos, y si los hacemos desde el final hacerlos negativos.
La funcin acepta cualquier desplazamiento y no produce nunca un
error. Luego, si el desplazamiento ha sido errneo, y nos hemos
posicionado en medio de ninguna parte o en un byte a mitad de dato,
entonces la lectura que pueda hacer la funcin que utilicemos ser
imprevisible.
Una ltima funcin que presentamos en este captulo es la llamada
rewind, cuyo prototipo es:
void rewind(FILE *nombre_archivo);
Que rebobina el archivo, devolviendo el puntero a su posicin inicial, al
principio del archivo.

Ejercicios

69. Descargar desde Internet un archivo con el texto completo de
El Quijote. Almacenarlo en formato texto. Darle a este archivo
el nombre quijote.txt. Y hacer entonces un programa que vaya
leyendo uno a uno los caracteres del archivo y vaya contando
cuntas veces aparece cada una de las letras del abecedario.
Mostrar al final en pantalla las veces que han aparecido cada
una de las letras y tambin el porcentaje de aparicin respecto
Fundamentos de informtica. Programacin en Lenguaje C


376
al total de todas las letras aparecidas.

Vamos a ofrecer dos soluciones a este programa. La primera es la ms
trivial:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
void main(void)
{
long letra[27];
short caracter;
long suma = 0;
short int i;
FILE *archivo;

for(i = 0 ; i < 26 ; i++) letra[i] = 0;

archivo = fopen("quijote.txt", "r");
if(archivo == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
while((caracter = fgetc(archivo)) != EOF)
{
if(isalpha(caracter))
{
i = (short)tolower(caracter) - (short)'a';
if(i >= 0 && i < 26) letra[i]++;
}
}

fclose(archivo);
for(i = 0 ; i < 26 ; i++) suma += letra[i];
for(i = 0 ; i < 26 ; i++)
{
printf("[ %c ]= %10ld\t",(char)(i+A),letra[i]);
printf("%7.2lf\n",((float)letra[i]/suma)*100);
}

printf("\n\nTotal letras ... %ld",suma);
}
Captulo 13. Gestin de archivos.


377
Esta es la solucin primera y sencilla. El vector letras tiene 26
elementos: tantos como letras tiene el abecedario ASCII. Pasamos
siempre la letra a minscula porque as no hemos de verificar que nos
venga el carcter en mayscula, y nos ahorramos muchas
comparaciones. El vector letra se indexa siempre por medio de la
variable i.
La pega es que con este cdigo no sumamos las veces que aparecen las
vocales con acento, o la letra u con diresis. Y, desde luego, no
calculamos cuntas veces aparece la letra la letra . Para poder
hacer esos clculos, deberemos modificar el programa aadiendo
algunas instrucciones:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
void main(void)
{
long letra[27];
short caracter;
long suma = 0;
short int i;
FILE *archivo;

for(i = 0 ; i < 27 ; i++) letra[i] = 0;

archivo = fopen("quijote.txt", "r");
if(archivo == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
while((caracter = fgetc(archivo)) != EOF)
{
if(caracter == 209 || caracter == 241)
letra[26]++; // letras y
else if(caracter == 225 || caracter == 193)
letra['a' - 'a']++; // letras y
else if(caracter == 233 || caracter == 201)
letra['e' - 'a']++; // letras y
else if(caracter == 237 || caracter == 205)
letra['i' - 'a']++; // letras e
else if(caracter == 243 || caracter == 211)
letra['o' - 'a']++; // letras y
Fundamentos de informtica. Programacin en Lenguaje C


378
else if(caracter == 250 || caracter == 218)
letra['u' - 'a']++; // letras y
else if(caracter == 252 || caracter == 220)
letra['u' - 'a']++; // letras y
else if(isalpha(caracter))
{
i = (short)tolower(caracter) - (short)'a';
if(i >= 0 && i < 26) letra[i]++;
}
}

fclose(archivo);
for(i = 0 ; i < 27 ; i++) suma += letra[i];
for(i = 0 ; i < 26 ; i++)
{
printf("[ %c ]= %10ld\t",(char)(i+A),letra[i]);
printf("%7.2lf\n",((float)letra[i]/suma)*100);
}
printf("[ %c ] = %10ld\t", 165,letra[26]);
printf("%7.2lf\n",((float)letra[26] / suma) * 100);

printf("\n\nTotal letras ... %ld",suma);
}

70. Implementar una base de datos de asignaturas. El programa
ser muy sencillo, y simplemente debe definir una estructura
como la que ya estaba definida en un tema anterior. El
programa almacenar en disco y aadir al final de archivo
cada una de las nuevas asignaturas que se aadan. La
informacin se guardar en binario. Se ofrecer la posibilidad
de realizar un listado de todas las asignaturas por pantalla o
grabando ese listado en disco, creando un documento que se
pueda luego tratar con un programa editor de texto.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct
{
unsigned long clave;
Captulo 13. Gestin de archivos.


379
char descr[50];
double cred;
}asignatura;

short mostrar_opciones(void);
void error(void);
short anyadir(char*);
short pantalla(char*);
short impresora(char*);

void main(void)
{
char nombre_archivo[80];
short opcion;
short oK;
printf("Nombre del archivo de asignaturas ... ");
gets(nombre_archivo);
do
{
opcion = mostrar_opciones();
switch(opcion)
{
case '1': oK = anyadir(nombre_archivo);
if(oK) error();
break;
case '2': oK = pantalla(nombre_archivo);
if(oK) error();
break;
case '3': oK = impresora(nombre_archivo);
if(oK) error();
break;
case '4': exit(1);
}
}while(1);
}

short mostrar_opciones(void)
{
char opcion;
clrscr();
printf("\n\n\t\tOpciones y Tareas");
printf("\n\n\t1. Aadir nueva asignatura.");
printf("\n\t2. Mostrar listado por pantalla.");
printf("\n\t3. Mostrar listado en archivo.");
printf("\n\t4. Salir del programa.");
printf("\n\n\t\t\tElegir opcion ... ");
do opcion = getchar(); while(opcion <'0'&&opcion >'4');
return opcion;
}

void error(void)
Fundamentos de informtica. Programacin en Lenguaje C


380
{
printf("Error en la operacion de acceso disco.\n");
printf("Pulse una tecla para terminar ... \n");
getchar();
exit(1);
}

short anyadir(char archivo[])
{
FILE *ARCH;
asignatura asig;
printf("\n\n\nDATOS DE LA NUEVA ASIGNATURA.\n\n");
printf("clave de la asignatura ... ");
scanf("%lu",&asig.clave);
printf("\nDescripcion ... ");
flushall();
gets(asig.descr);
printf("\nCreditos ...... ");
scanf("%lf",&asig.cred);
ARCH = fopen(archivo,"ab");
fwrite(&asig,sizeof(asig),1,ARCH);
printf("\n\n\tPulsar una tecla para continuar ... ");
getchar();
if(ferror(ARCH)) return 1;
fclose(ARCH);
return 0;
}

short pantalla(char archivo[])
{
FILE *ARCH;
asignatura asig;

ARCH = fopen(archivo,"a+b");
rewind(ARCH);
while(fread(&asig,sizeof(asig),1,ARCH) == 1)
{
printf("\n\nClave ......... %lu",asig.clave);
printf("\nDescripcion ... %s",asig.descr);
printf("\nCreditos ...... %6.1lf",asig.cred);
}
printf("\n\n\tPulsar una tecla para continuar ... ");
getchar();
if(ferror(ARCH)) return 1;
fclose(ARCH);
return 0;
}

short impresora(char archivo[])
{
FILE *ARCH1, *ARCH2;
Captulo 13. Gestin de archivos.


381
asignatura asig;

ARCH1 = fopen(archivo,"rb");
ARCH2 = fopen("impresora","w");
while(fread(&asig,sizeof(asig),1,ARCH1) == 1)
{
fprintf(ARCH2,"\n\nClave\t%lu", asig.clave);
fprintf(ARCH2,"\nDescripcion \t%s", asig.descr);
fprintf(ARCH2,"\nCreditos\t%6.1lf", asig.cred);
}
printf("\n\n\tPulsar una tecla para continuar ... ");
getchar();
if(ferror(ARCH1)) return 1;
fclose(ARCH1);
fclose(ARCH2);
return 0;
}
La funcin principal presenta nicamente una estructura switch que
gestiona cuatro posibles valores para la variable opcion. Esos valores se
muestran en la primera de las funciones, la funcin mostrar_opciones,
que imprime en pantalla las cuatro posibles opciones del programa,
(aadir registros, mostrarlos por pantalla, crear un archivo de
impresin, y salir del programa) y devuelve a la funcin principal el
valor de la opcin elegida.
La funcin anyadir recoge los valores de una nueva asignatura y guarda
la informacin, mediante la funcin fwrite, en el archivo que ha indicado
el usuario al comenzar la ejecucin del programa. El archivo se abre
para aadir y para codificacin binaria: ab. En esta funcin se invoca a
otra, llamada flushall. Esta funcin, de la biblioteca stdio.h, vaca
todos los buffers de entrada. La ejecutamos antes de la funcin gets
para variar el buffer de teclado. A veces ese buffer contiene algn
carcter, o el carcter intro pulsado desde la ltima entrada de datos
por teclado, y el sistema operativo lo toma como entrada de a funcin
gets, que queda ejecutada sin intervencin del usuario.
La funcin pantalla muestra por pantalla un listado de las asignaturas
introducidas hasta el momento y guardadas en el archivo. Abre el
archivo para lectura en formato binario: a+b. No lo hemos abierto
Fundamentos de informtica. Programacin en Lenguaje C


382
como rb para evitar el error en caso de que el usuario quiera leer un
archivo inexistente.
La funcin impresora hace lo mismo que pantalla, pero en lugar de
mostrar los datos por la consola los graba en un archivo de texto. Por
eso esa funcin abre dos archivos y va grabando el texto en el archivo
abierto como w. Si el archivo impresora ya existe, entonces es
eliminado y crea otro en su lugar.
Se puede completar el programa con nuevas opciones. Se podra
modificar la funcin mostrar_opciones y la funcin main incorporando
esas opciones nuevas. Y se crearan las funciones necesarias para esas
nuevas tareas. Por ejemplo: eliminar el archivo de asignaturas; hacer
una copia de seguridad del archivo; buscar una asignatura en el archivo
cuya clave sea la que indique el usuario y, si la encuentra, entonces
muestre por pantalla la descripcin y el nmero de crditos; etc.

También podría gustarte