Sie sind auf Seite 1von 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.

NDICE
PARTE I. Primeros pasos en C

CAPTULO 1
LENGUAJE C.

Introduccin
Entorno de programacin.
Estructura bsica de un programa en C.
Elementos lxicos
Sentencias simples y sentencias compuestas
Errores y depuracin
Recapitulacin
El entorno de Borland C++

5
7
9
12
13
14
15
15

CAPTULO 2
TIPOS DE DATOS Y VARIABLES EN C

19

Declaracin de variables.
Tipos de datos primitivos en C: sus dominios.
Tipos de datos primitivos en C: sus operadores.
Operador asignacin.
Operadores aritmticos.
Operadores relacionales y lgicos.
Operadores a nivel de bit.

20
22
25
26
27
30
34

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

39
40
41
43
45
48
50
51
52
52
53

CAPTULO 3
FUNCIONES DE ENTRADA Y SALIDA POR
CONSOLA

65

Salida de datos. La funcin printf()


Entrada de datos. La funcin scanf()
Recapitulacin
Ejercicios (del 16 al 19)
ANEXO: Ficha resumen de la funcin printf()

66
74
76
77
83

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

ii

87
87
89
90
97
101
104
116
120
120
122
122

CAPTULO 5
MBITO Y VIDA DE LAS VARIABLES

147

mbito y Vida.
El almacenamiento de las variables en la memoria.
Variables Locales y Variables Globales
Variables estticas y dinmicas.
Variables en registro.
Variables extern
En resumen
Ejercicios (40)

147
148
150
154
156
157
158
160

CAPTULO 6
ARRAYS NUMRICOS: VECTORES Y MATRICES

163

Nocin y declaracin de array.


Nocin y declaracin de array de dimensin mltiple, o
matrices.
Ejercicios (del 41 al 47)

164
166
169

CAPTULO 7
CARACTERES Y CADENAS DE CARACTERES

181

Operaciones con caracteres.


Entrada de caracteres.
Cadena de caracteres.
Dar valor a una cadena de caracteres.
Operaciones con cadenas de caracteres.
Otras funciones de cadena.
Ejercicios. (del 48 al 51)

182
185
186
188
191
196
199

CAPTULO 8
PUNTEROS

203

Definicin y declaracin
Dominio y operadores para los punteros
Punteros y vectores
ndices y operatoria de punteros

204
205
209
212

iii

215
219
221

Puntero a puntero
Advertencia final
Ejercicios. (del 52 al 53)

CAPTULO 9
FUNCIONES

223

Definiciones
Funciones en C
Declaracin de la funcin.
Definicin de la funcin.
Llamada a la funcin
La sentencia return
mbito y vida de las variables
Recurrencia
Llamadas por valor y llamadas por referencia
Vectores y matrices como argumentos
Funciones de escape
Ejercicios. (del 54 al 63)

224
227
228
230
232
233
236
239
243
246
249
250

PARTE II. Profundizando en C

273

CAPTULO 10
ASIGNACIN DINMICA DE MEMORIA

277

Funcin malloc
Funcin free
Ejemplo: la Criba de Erastthenes. (Ejercicio 64)
Matrices en memoria dinmica
Ejercicios. (65)

279
283
283
288
292

CAPTULO 11
ALGUNOS USOS CON FUNCIONES

299

Punteros a funciones
Vectores de punteros a funciones

300
303

iv

Funciones como argumentos


Ejemplo: la funcin qsort
Estudio de tiempos
Creacin de MACROS
Ejemplo de MACRO: la Criba de Erastthenes
Funciones con un nmero variable de argumentos
Argumentos de la lnea de rdenes
Ejercicios. (del 66 al 67)

305
308
312
315
316
320
325
328

CAPTULO 12
ESTRUCTURAS ESTTICAS DE DATOS Y
DEFINICIN DE TIPOS

329

Tipos de dato enumerados


Dar nombre a los tipos de dato
Estructuras de datos y tipos de dato estructurados
Estructuras de datos en C
Vectores y punteros a estructuras
Anidamiento de estructuras
Tipo de dato union
Ejercicios. (68)

330
331
333
334
339
341
344
348

CAPTULO 13
GESTIN DE ARCHIVOS

353

Tipos de dato con persistencia


Archivos y sus operaciones
Archivos de texto y binarios
Tratamiento de archivos en el lenguaje C
Archivos secuenciales con buffer.
Entrada y salida sobre archivos de acceso aleatorio
Ejercicios. (del 69 al 70)

354
356
359
359
361
373
375

vi

PARTE I:

Primeros pasos
en lenguaje C.

Fundamentos de informtica. Programacin en Lenguaje C

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

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

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

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.

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.

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.

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

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.

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

No

Errores de
compilacin

Obtencin del
programa objeto (.obj)

Obtencin del
programa
ejecutable (.exe)
S

Errores de
ejecucin

Figura 1.1.: Fases de


desarrollo de un programa

Enlace

No

Programas
objeto del
usuario

Archivos de
biblioteca (.lib)

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

Fundamentos de informtica. Programacin en Lenguaje C.

#include <stdio.h>
/* Este es un programa en C. */
// Imprime un mensaje en la pantalla del ordenador
void main(void)
{
printf(mi primer programa 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

10

Captulo 1. Lenguaje C.

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

11

Fundamentos de informtica. Programacin en Lenguaje C.

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.

12

Captulo 1. Lenguaje C.

3. Las

letras

de

los

identificadores

pueden

ser

maysculas

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,

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
break
case
char
const
continue
default
do

double
else
enum
extern
float
for
(goto)
if

int
long
register
return
short
signed
sizeof
static

struct
switch
typedef
union
unsigned
void
volatile
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

13

Fundamentos de informtica. Programacin en Lenguaje C.

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

14

Captulo 1. Lenguaje C.

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.

15

Fundamentos de informtica. Programacin en Lenguaje C.

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.

16

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

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

20

Captulo 2. Tipos de datos y variables en C.

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

21

Fundamentos de informtica. Programacin en Lenguaje C

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 27

hasta

2 1.
7

TIPOS

RANGO DE VALORES
MENOR
MAYOR

SIGNO

tipos de dato CARCTER: char


signed
unsigned

-128
0

+127
+255

-32.768
0
-2.147.483.648
0

+32.767
+65.535
+2.147.483.647
4.294.967.295

char

tipos de dato ENTERO: int


signed
unsigned
signed
unsigned

short
long

tipos de dato CON COMA FLOTANTE


float
double
long double

-3.402923E+38
-1.7976931E+308
-1.2E+4932

+3.402923E+38
+1.7976931E+308
+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

22

Captulo 2. Tipos de datos y variables en C.

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

23

Fundamentos de informtica. Programacin en Lenguaje C

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, +6, 023 1023 : 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

24

Captulo 2. Tipos de datos y variables en C.

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

25

Fundamentos de informtica. Programacin en Lenguaje C

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.

26

Captulo 2. Tipos de datos y variables en C.

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.

27

Fundamentos de informtica. Programacin en Lenguaje C

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

28

Captulo 2. Tipos de datos y variables en C.

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,

29

Fundamentos de informtica. Programacin en Lenguaje C

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.

30

Captulo 2. Tipos de datos y variables en C.

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)

31

Fundamentos de informtica. Programacin en Lenguaje C

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

a && b

a || b

!a

F
F
V
V

F
V
F
V

F
F
F
V

F
V
V
V

V
V
F
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)

32

Captulo 2. Tipos de datos y variables en C.

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;
3 * pi < y || (x + y) * 10 <= z / 2;
3 * pi < y && !((x + y) * 10 <= z / 2);

// FALSO
// VERDADERO
// 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

33

Fundamentos de informtica. Programacin en Lenguaje C

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.

34

Captulo 2. Tipos de datos y variables en C.

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

a
b
a_and_b
a_or_b
a_xor_b

1010
0110
0010
1110
1100

1011
0111
0011
1111
1100

1100
1000
1000
1100
0100

1101
1001
1001
1101
0100

hex.

dec.

ABCD
6789
2389
EFCD
CC44

43981
26505
9097
61389
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
unsigned
unsigned
unsigned

short
short
short
short

int
int
int
int

a = 0xABCD, b = 0x6789;
a_and_b = a & b;
a_or_b = a | b;
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

35

Fundamentos de informtica. Programacin en Lenguaje C

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.

0
0
1
1

and

or

xor

0
0
0
1

0
1
1
1

0
1
1
0

0
1
0
1

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

cero

desplazamiento.
Por ejemplo, si tenemos el siguiente cdigo:
short int var1 = 0x7654;
short int var2 = var1 << 3;

36

como

indique

el

Captulo 2. Tipos de datos y variables en C.

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

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

37

Fundamentos de informtica. Programacin en Lenguaje C

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

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.

38

Captulo 2. Tipos de datos y variables en C.

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

+= y;
-= y;
*= y;
/= y;
%= y;
>>= y;
<<= y;
&= y;
|= y;
^= y;

es
es
es
es
es
es
es
es
es
es

lo
lo
lo
lo
lo
lo
lo
lo
lo
lo

mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo

que
que
que
que
que
que
que
que
que
que

decir
decir
decir
decir
decir
decir
decir
decir
decir
decir

x
x
x
x
x
x
x
x
x
x

=
=
=
=
=
=
=
=
=
=

x
x
x
x
x
x
x
x
x
x

+ y;
y;
* y;
/ y;
% y;
>> y;
<< y;
& y;
| y;
^ 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.

39

Fundamentos de informtica. Programacin en Lenguaje C

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

40

Captulo 2. Tipos de datos y variables en C.

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
printf("char
printf("short
printf("long
printf("float
printf("double
}

=>
=>
=>
=>
=>
=>

%d\n",sizeof(int));
%d\n",sizeof(char));
%d\n",sizeof(short));
%d\n",sizeof(long));
%d\n",sizeof(float));
%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.

41

Fundamentos de informtica. Programacin en Lenguaje C

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

42

Captulo 2. Tipos de datos y variables en C.

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

43

Fundamentos de informtica. Programacin en Lenguaje C

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

short

char

long int

char

long int

short

float

int

double

float

long double

double

Si el valor inicial es mayor de


127, entonces el nuevo valor
ser negativo.
Se pierden los 8 bits ms
significativos.
Se pierden los 24 bits ms
significativos.
Se pierden los 16 bits ms
significativos.
Se pierde la parte fraccional y
ms informacin.
Se pierde precisin. El
resultado se presenta
redondeado.
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

44

Captulo 2. Tipos de datos y variables en C.

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,

compiladores

este
no

tipo

de

interrumpen

conversiones
el

trabajo

hay que evitarlas.


de

compilacin

Los

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.

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.

45

Fundamentos de informtica. Programacin en Lenguaje C

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.

46

Captulo 2. Tipos de datos y variables en C.

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

()
!
.*

[]
~

>
==

++

ID

.
--

DI

&

ID

->*

<<

->

ID

ID
ID

>>
>=

<

ID

<=

ID

!=

&

ID

ID

ID

&&

ID

||

DI

?:

ID

+=

-=

*=

/=

%=

&=

|=

<<=

>>=

DI
ID

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

47

Fundamentos de informtica. Programacin en Lenguaje C

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

48

Captulo 2. Tipos de datos y variables en C.

tabla 2.7. se muestran los valores de diferentes operaciones con


desbordamiento.
signed short
unsigned short
signed long
unsigned long

32767 + 1 da el valor 32768


65535 + 1 da el valor 0
2147483647 +1 da el valor -2147483648
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

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

49

Fundamentos de informtica. Programacin en Lenguaje C

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

50

Captulo 2. Tipos de datos y variables en C.

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

intercambio:
short int variable1 = 3579;
short int variable2 = 2468;

51

que

realmente

realiza

el

Fundamentos de informtica. Programacin en Lenguaje C

en base binaria el valor de las dos variables es:


variable1
variable2
variable1 ^= variable2
variable2 ^= variable1
variable1 ^= variable2

0
0
0
0
0

0
0
0
0
0

0
0
0
0
0

0
0
0
0
0

1
1
0
1
1

1
0
1
1
0

0
0
0
0
0

1
1
0
1
1

1
1
0
1
1

1
0
1
1
0

1
1
0
1
1

1
0
1
1
0

1
0
1
1
0

0
1
1
0
1

1
0
1
1
0

1
0
1
1
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

52

Captulo 2. Tipos de datos y variables en C.

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 =
res =
pro =
coc =
mod =

a
a
a
a
a

+
*
/
%

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

53

Fundamentos de informtica. Programacin en Lenguaje C

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.

54

Captulo 2. Tipos de datos y variables en C.

3.

Escriba un programa que resuelva una ecuacin de primer


grado ( a x + b = 0 ). 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

55

Fundamentos de informtica. Programacin en Lenguaje C

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;

56

Captulo 2. Tipos de datos y variables en C.

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.

57

Fundamentos de informtica. Programacin en Lenguaje C

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?

58

Captulo 2. Tipos de datos y variables en C.

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

posiciones,

pero

sin

perder

los

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.

59

Fundamentos de informtica. Programacin en Lenguaje C

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:
despl:
a << despl:
8 * sizeof(short) despl:
a >> 11:
b:

1010 1011
5
0111 1001
8 * 2 5
0000 0000
0111 1001

1100 1101
1010 0000
= 11
0001 0101
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.

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

60

Captulo 2. Tipos de datos y variables en C.

10.

Escribir el siguiente programa y justificar 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)
{

61

Fundamentos de informtica. Programacin en Lenguaje C

12.

short bits, bytes;


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

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

13.

(caracter) de a: X
(caracter) de b: Y
de a (en base 10): 88
de b (en base 10): 89
(caracter) de a + b:
(en base 10) de a + b: 177
(caracter) de a + b + 5:
(en base 10) de a + b + 5: 182

Escriba un programa que calcule la superficie y el volumen de


una esfera cuyo radio es introducido por teclado.
S = 4 r2

V = 4 3 r3

62

Captulo 2. Tipos de datos y variables en C.

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

63

Fundamentos de informtica. Programacin en Lenguaje C

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 ( V0 ).
Tenga en cuenta las siguientes ecuaciones que definen el
comportamiento del sistema descrito:
Vx = V0 cos( )

Vy = V0 sen( ) g t

x = V0 t cos( )

y = V0 t sen( )

64

1
g t2
2

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

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

66

Captulo 3. Funciones de entrada y salida por consola.

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:
#include <stdio.h>
void main(void)
{
printf(Texto a mostrar en pantalla);
}
Que ofrecer la siguiente salida por pantalla
Texto a mostrar en pantalla
Y as, cualquier texto que se escriba entre las comillas aparecer en
pantalla.
Si

introducimos

ahora

otra

instruccin

con

la

funcin

printf

continuacin y debajo de la otra, por ejemplo


printf(Otro texto);
Entonces lo que tendremos en pantalla ser
Texto a mostrar en pantallaOtro texto
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

67

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
void main(void)
{
printf(Texto a mostrar en pantalla\n);
printf(Otro texto.)
}
que ofrecer la siguiente salida por consola:
Texto a mostrar en pantalla
Otro texto
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
\v
\0
\n
\t
\b
\r
\f
\
\
\\
\xdd

Carcter sonido. Emite un pitido breve.


Tabulador vertical.
Carcter nulo.
Nueva lnea.
Tabulador horizontal.
Retroceder un carcter.
Retorno de carro.
Salto de pgina.
Imprime la comilla simple.
Imprime la comilla doble.
Imprime la barra invertida \.
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

68

Captulo 3. Funciones de entrada y salida por consola.

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

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
#include <stdio.h>
void main(void)
{
short int a = 5 , b = 10 , c;
c = a + b++;
printf(Ahora c vale %hd \n,c);
printf(y b vale ahora %hd ,b);
}
Que ofrece la siguiente salida por pantalla:
Ahora c vale 15
y b vale ahora 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

69

Fundamentos de informtica. Programacin en Lenguaje C

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

70

Captulo 3. Funciones de entrada y salida por consola.

especificador de formato indica a la funcin printf la longitud mnima


que debe ocupar la impresin del valor que all se debe mostrar.
%d
%i
%o
%u
%x
%X
%f
%e
%E
%g
%G
%c
%s
%p
%n
%%

Entero con signo, en base decimal.


Entero con signo, en base decimal.
Entero (con o sin signo) codificado en base
octal.
Entero sin signo, en base decimal.
Entero (con o sin signo) codificado en base
hexadecimal, usando letras minsculas.
Codificacin interna de los enteros.
Entero (con o sin signo) codificado en base
hexadecimal, usando letras maysculas.
Codificacin interna de los enteros.
Nmero real con signo.
Nmero real con signo en formato cientfico,
con el exponente e en minscula.
Nmero real con signo en formato cientfico,
con el exponente e en mayscula.
Nmero real con signo, a elegir entre formato e
f segn cul sea el tamao ms corto.
Nmero real con signo, a elegir entre formato E
f segn cul sea el tamao ms corto.
Un carcter. El carcter cuyo ASCII corresponda
con el valor a imprimir.
Cadena de caracteres.
Direccin de memoria.
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

71

Fundamentos de informtica. Programacin en Lenguaje C

long int a
printf(La
printf(La
printf(La

= 123, b
variable
variable
variable

=
a
b
c

4567, c = 135790;
vale ... %6li.\n,a);
vale ... %6li.\n,b);
vale ... %6li.\n,c);

tiene la siguiente salida:


La variable a vale ...
123.
La variable b vale ...
4567.
La variable c vale ... 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
printf(La variable a vale ... %06li.\n,a);
ofrece como salida la siguiente lnea en pantalla:
La variable a vale ... 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.

72

Captulo 3. Funciones de entrada y salida por consola.

Por ejemplo, el cdigo


double raiz_2 =
printf("A. Raiz
printf("B. Raiz
printf("C. Raiz
printf("D. Raiz
printf("E. Raiz
printf("F. Raiz
printf("G. Raiz
printf("H. Raiz
printf("I. Raiz

sqrt(2);
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale

%lf\n",raiz_2);
%12.1lf\n",raiz_2);
%12.3lf\n",raiz_2);
%12.5lf\n",raiz_2);
%12.7lf\n",raiz_2);
%12.9lf\n",raiz_2);
%12.11lf\n",raiz_2);
%5.7lf\n",raiz_2);
%012.4lf\n",raiz_2);

que ofrece la siguiente salida por pantalla:


A.
B.
C.
D.
E.
F.
G.
H.
I.

Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz

de
de
de
de
de
de
de
de
de

dos
dos
dos
dos
dos
dos
dos
dos
dos

vale
vale
vale
vale
vale
vale
vale
vale
vale

1.414214
1.4
1.414
1.41421
1.4142136
1.414213562
1.41421356237
1.4142136
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.

73

Fundamentos de informtica. Programacin en Lenguaje C

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

74

Captulo 3. Funciones de entrada y salida por consola.

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
%i
%o
%u
%x
%X
%f
%e
%c
%s
%p
%n

Entero con signo, en base decimal.


Entero con signo, en base decimal.
Entero codificado en base octal.
Entero sin signo, en base decimal.
Entero codificado en base hexadecimal, usando
letras minsculas. Codificacin interna de los
enteros.
Entero codificado en base hexadecimal, usando
letras maysculas. Codificacin interna de los
enteros.
Nmero real con signo.
Nmero real con signo en formato cientfico,
con el exponente e en minscula.
Un carcter. El carcter cuyo ASCII corresponda
con el valor a imprimir.
Cadena de caracteres.
Direccin de memoria.
No lo explicamos aqu ahora.

Tabla 3.3.: Especificadores de tipo de dato en la


funcin scanf.

75

Fundamentos de informtica. Programacin en Lenguaje C

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.

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,

presentado, a practicar con ellas.

76

ahora

que

ya

las

hemos

Captulo 3. Funciones de entrada y salida por consola.

Ejercicios.

16.

Escribir un programa que muestre al cdigo ASCII de un


carcter introducido por teclado.

#include <stdio.h>
void main(void)
{
unsigned char ch;
printf("Introduzca un carcter por teclado ... ");
scanf("%c",&ch);
printf("El carcter introducido ha sido %c\n",ch);
printf("Su cdigo ASCII 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.

#include <stdio.h>
void main(void)
{
signed long int sli;
signed short int ssi;
printf("Introduzca un valor negativo para sli ... ");
scanf("%ld",&sli);
printf("Introduzca un valor negativo para ssi ... ");
scanf("%hd",&ssi);

77

Fundamentos de informtica. Programacin en Lenguaje C

printf("El valor sli es %ld\n",sli);


printf("El valor ssi es %ld\n\n",ssi);
printf("El valor sli como \"%%lX\" es %lX\n",sli);
printf("El valor ssi como \"%%hX\" es %hX\n\n",ssi);
printf("El valor sli como \"%%lu\" es %lu\n",sli);
printf("El valor ssi como \"%%hu\" es %hu\n\n",ssi);
printf("El valor sli como \"%%hu\" es %hu\n",sli);
printf("El valor ssi como \"%%lu\" es %lu\n\n",ssi);
printf("El valor sli como \"%%hi\" es %hi\n",sli);
printf("El valor ssi como \"%%li\" es %li\n\n",ssi);
}
La salida que ha obtenido su ejecucin es la siguiente:
Introduzca un valor negativo para sli ... -23564715
Introduzca un valor negativo para ssi ... -8942
El valor sli es -23564715
El valor ssi es -8942
El valor sli como "%lX" es FE986E55
El valor ssi como "%hX" es DD12
El valor sli como "%lu" es 4271402581
El valor ssi como "%hu" es 56594
El valor sli como "%hu" es 28245
El valor ssi como "%lu" es 4294958354
El valor sli como "%hi" es 28245
El valor ssi como "%li" 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 valor sli como "%lX" es FE986E55

78

Captulo 3. Funciones de entrada y salida por consola.

El valor 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 (8942)10 = (22EE)16 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 (23564715)10 = (167 91AB)16 se codifica, como

nmero negativo, de la forma FE98 6E55 .


Las dos siguientes lneas son:
El valor sli como "%lu" es 4271402581
El valor 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:

(FE98 6E55)16 = (4.271.402.581)10 .

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:
(DD12)16 = (56.594)10 .

Las dos siguientes lneas son:


El valor sli como "%hu" es 28245
El valor ssi como "%lu" 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

79

Fundamentos de informtica. Programacin en Lenguaje C

de la variable de 32 bits y los interpreta como entero corto sin signo:


(6E55)16 = (28.245)10 .

La segunda lnea es de difcil interpretacin: en realidad muestra un


valor numrico que, expresado en base hexadecimal, es igual a
(FFFF DD12)16 : 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.

#include <stdio.h>
#include <math.h>
void main(void)
{
double a = M_PI;
printf(" 1.
printf(" 2.
printf(" 3.
printf(" 4.
printf(" 5.
printf(" 6.
printf(" 7.
printf(" 8.
printf(" 9.
printf("10.
printf("11.
printf("12.
printf("13.
printf("14.
printf("15.

El
El
El
El
El
El
El
El
El
El
El
El
El
El
El

valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor

de
de
de
de
de
de
de
de
de
de
de
de
de
de
de

Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi

es
es
es
es
es
es
es
es
es
es
es
es
es
es
es

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

%20.1lf\n",a);
%20.2lf\n",a);
%20.3lf\n",a);
%20.4lf\n",a);
%20.5lf\n",a);
%20.6lf\n",a);
%20.7lf\n",a);
%20.8lf\n",a);
%20.9lf\n",a);
%20.10lf\n",a);
%20.11lf\n",a);
%20.12lf\n",a);
%20.13lf\n",a);
%20.14lf\n",a);
%20.15lf\n",a);

}
La salida que ofrece por pantalla es la siguiente:
1. El valor de Pi es ... 3.1
2. El valor de Pi es ... 3.14

80

Captulo 3. Funciones de entrada y salida por consola.

3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.

El
El
El
El
El
El
El
El
El
El
El
El
El

valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor

de
de
de
de
de
de
de
de
de
de
de
de
de

Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi

es
es
es
es
es
es
es
es
es
es
es
es
es

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

3.142
3.1416
3.14159
3.141593
3.1415927
3.14159265
3.141592654
3.1415926536
3.14159265359
3.141592653590
3.1415926535898
3.14159265358979
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 debajo de otro, alineados por la coma
decimal.

#include <stdio.h>
#include <stdlib.h>
void main(void)
{
randomize();
for(int i = 1 ; i <= 20 ; i++)
printf("%2d. %10.5lf\n",i,
(double)random(1000000) / random (10000));
}

81

Fundamentos de informtica. Programacin en Lenguaje C

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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.

57.21568
62.41973
147.16501
120.04998
215.02813
52.82802
260.75406
721.83456
9.85598
150.42073
7.11266
78.85494
73.41685
196.21048
88.43795
192.40674
315.92087
50.11689
7.16849
187.26519

Donde la coma decimal queda ubicada, en todos los nmeros, en la


misma columna. Y as tambin las unidades, decenas, y decimales.

82

Captulo 3. Funciones de entrada y salida por consola.

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

Carcter

doi

Decimal entero con signo

392

Decimal entero sin signo

7235

Octal con signo

610

Entero hexadecimal sin signo

7fa

Entero hexadecimal sin signo (letras


maysculas)

7FA

Decimal en punto flotante

392.65

Notacin cientfica (mantisa/exponente) con


el carcter e

3.9265e2

Notacin cientfica (mantisa/exponente) con


el carcter E

3.9265E2

Usa el que sea ms corto entre %e y %f

392.65

Usa el que sea ms corto entre %E y %f

392.65

Cadena de caracteres

sample

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:

83

Fundamentos de informtica. Programacin en Lenguaje C

modificador

significado

argumento se interpreta como un short int.

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

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.

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.

84

Captulo 3. Funciones de entrada y salida por consola.

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*/
#include <stdio.h>
void main(void)
{
printf("Caracteres: %c %c \n", 'a', 65);
printf("Decimales: %d %ld\n", 1977, 650000);
printf("Precedidos de blancos: %10d \n", 1977);
printf("Precedidos de ceros: %010d \n", 1977);
printf("Formato: %d %x %o %#x %#o\n",100,100,100,100,100);
printf("float: %4.2f %+.0e %E\n", 3.1416, 3.1416, 3.1416);
printf("Ancho: %*d \n", 5, 10);
printf("%s \n", "Mi mam me mima");
}
Y la salida:
Caracteres: a A
Decimales: 1977 650000
Precedidos con blancos:
1977
Precedidos con ceros: 0000001977
Formato: 100 64 144 0x64 0144
float: 3.14 +3e+000 3.141600E+000
Ancho:
10
Mi mam me mima

85

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

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

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.

Instruccin 1

Instruccin 2

Instruccin N

Figura 4.1.: Esquema de ordenacin secuencial de sentencias.


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.

88

Captulo 4. Estructuras de control.

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.

89

Fundamentos de informtica. Programacin en Lenguaje C

No

Condicin

No

Instruccin

Instruccin

Instruccin

Condicin

Instruccin

Instruccin

Bifurcacin abierta

Bifurcacin cerrada

Figura 4.2.: Estructuras de seleccin.

No

Condicin

Instruccin

Instruccin

S
No

Instruccin

Condicin

Instruccin
Estructura while

Estructura do - while

Figura 4.3.: Estructuras de repeticin.


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

90

Captulo 4. Estructuras de control.

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:
#include <stdio.h>
void main(void)
{
short D, d;
printf("Programa para dividir dos enteros...\n");
printf("Introduzca el dividendo ... ");
scanf("%hd",&D);
printf("Introduzca el divisor ... ");
scanf("%hd",&d);
if(d != 0) printf("%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.

91

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
void main(void)
{
short D, d;
printf("Programa para dividir dos enteros...\n");
printf("Introduzca el dividendo ... ");
scanf("%hd",&D);
printf("Introduzca el divisor ... ");
scanf("%hd",&d);
if(d != 0) printf("%hu / %hu = %hu", D, d, D / d);
else printf(No se puede realizar division por cero);
}
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.

92

Captulo 4. Estructuras de control.

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


if(expresin_1)
/*
{
if(expresin_2)
/*
{
if(expresin_3)
/*
sentencia_1;
else
/*
sentencia_2;
}
else
/*
sentencia_3;
}
else
/*
sentencia_4;

primer if */
segundo if */
tercer if */
alternativa al tercer if */

alternativa al 2 if */

alternativa al primer if */

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

C
S
S
S

S1

C3

C2

C1

No

No

No

S2

S3

Figura 4.4.: Ejemplo de


estructuras condicionales anidadas.

S4

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.

93

Fundamentos de informtica. Programacin en Lenguaje C

#include <stdio.h>
void main(void)
{
short D, d;
char opcion;
printf("Programa para dividir dos enteros...\n");
printf("Introduzca el dividendo ... ");
scanf("%hd",&D);
printf("Introduzca el divisor ... ");
scanf("%hd",&d);
if(d != 0)
printf("%hu / %hu = %hu", D, d, D / d);
else
{
printf("No se puede dividir por cero.\n");
printf("Introducir otro denominador (s/n)?");
opcion = getchar();
if(opcion == 's')
{
printf("\nNuevo denominador ... ");
scanf("%hd",&d);
if(d != 0)
printf("%hu / %hu = %hu", D, d, D/d);
else
printf("De nuevo ha introducido 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;

94

Captulo 4. Estructuras de control.

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:
#include <stdio.h>
void main(void)
{
float nota;
printf("Introduzca la nota del examen ... ");
scanf("%f",&nota);
if(nota < 0 || nota > 10) printf("Nota incorrecta.");
else if(nota < 5) printf("Suspenso.");
else if(nota < 7) printf("Aprobado.");
else if(nota < 9) printf("Notable.");
else if(nota < 10) printf("Sobresaliente.");
else printf("Matrcula de honor.");
}

95

Fundamentos de informtica. Programacin en Lenguaje C

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

96

Captulo 4. Estructuras de control.

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

97

Fundamentos de informtica. Programacin en Lenguaje C

switch(a)
{
case 1:
case 2:
case 3:
default:
}

printf(UNO\t);
printf(DOS\t);
printf(TRES\t);
printf(NINGUNO\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

NINGUNO.

C
No
No
No

a=3

a=2

a=1

UNO

DOS

TRES
NINGUNO

Figura 4.5.: Flujograma del


programa ejemplo con switch, sin
sentencias break.

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.

98

Captulo 4. Estructuras de control.

En el ejemplo anterior, si colocamos la sentencia break en cada case,


switch(a)
{
case 1: printf(UNO);
break;
case 2: printf(DOS);
break;
case 3: printf(TRES);
break;
default: printf(NINGUNO);
}
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.)

C
No
No
No

a=3

a=2

a=1

Figura 4.6.: Flujograma del


programa ejemplo con switch,
con sentencias break.

UNO

break;

DOS

break;

TRES

break;

NINGUNO
F

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 nota;
printf("Introduzca la nota del examen ... ");
scanf("%hd",&nota);

99

Fundamentos de informtica. Programacin en Lenguaje C

switch(nota)
{
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
default:

printf(SUSPENSO);
break;
printf(APROBADO);
break;
printf(NOTABLE);
break;
printf(SOBRESALIENTE);
break;
printf(MATRCULA DE HONOR);
break;
printf(Nota introducida errnea.);

}
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 ifelseif 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
elseif no tiene solucin posible mediante la estructura switch.

100

Captulo 4. Estructuras de control.

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:
d = (26 M 2 ) 10 + D + A + A 4 + C 4 2 C mod7

Donde d es el da de la semana ( d = 0 es el domingo; d = 1 es el


lunes,, d = 6 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?:

101

Fundamentos de informtica. Programacin en Lenguaje C

D = 15

M = 12 : hemos quedado que en nuestra ecuacin el mes de febrero

es el dcimo segundo mes del ao anterior.

A = 74 : hemos quedado que el mes de febrero corresponde al

ltimo mes del ao anterior.

C = 19

Con todos estos valores, el da de la semana queda:


d = (26 12 2 ) 10 + 15 + 74 + 74 4 + 19 4 2 19 mod7

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 M = 12 , que A = 99 y
que C = 19 : es decir, primero convendr hacer las rectificaciones al ao
y slo despus calcular los valores de A y de C . se da fue
d = (26 12 2 ) 10 + 1 + 99 + 99 4 + 19 4 2 19 mod7 = 2

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:
#include <stdio.h>
void main(void)
{
unsigned short D, mm, aaaa;
unsigned short M, A, C;
printf("Introduzca la fecha ... \n");
printf("Da ... ");
scanf("%hu", &D);
printf("Mes ... ");
scanf("%hu", &mm);

102

Captulo 4. Estructuras de control.

printf("Ao ... ");


scanf("%hu", &aaaa);
// Valores de las variables:
// El valor de D ya ha quedado introducido por el usuario.
// Valor de M:
if(mm< 3)
{
M =
A =
C =
}
else
{
M =
A =
C =
}

mm + 10;
(aaaa - 1) % 100;
(aaaa - 1) / 100;

mm - 2;
aaaa % 100;
aaaa / 100;

printf("El da %2hu de %2hu de


switch((70+(26*M-2)/10 + D + A
{
case 0: printf("DOMINGO");
case 1: printf("LUNES");
case 2: printf("MARTES");
case 3: printf("MIRCOLES");
case 4: printf("JUEVES");
case 5: printf("VIERNES");
case 6: printf("SBADO");
}

%4hu fue ",D, mm, aaaa);


+ A/4 + C/4 - C*2 ) % 7)
break;
break;
break;
break;
break;
break;

Si, por ejemplo, introducimos la fecha 25 de enero de 1956, la salida del


programa tendr el siguiente aspecto:
Introduzca la fecha ...
Da ... 25
Mes ... 1
Ao ... 1956
El da 25 de 1 de 1956 fue MIRCOLES

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 D = 2 , M = 2 ,
A = 1 y C = 20 , 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

103

Fundamentos de informtica. Programacin en Lenguaje C

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:
Introduzca la fecha ...
Da ... 2
Mes ... 4
Ao ... 2001
El da 2 de 4 de 2001 fue 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 dowhile, 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;

104

Captulo 4. Estructuras de control.

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:
#include <stdio.h>
void main(void)
{
short int n,i;
printf("Tabla de multiplicar del ... ");
scanf("%hd",&n);
i = 0;
while(i <= 10)
{
printf("%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

105

Fundamentos de informtica. Programacin en Lenguaje C

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: n ! = n (n 1) (n 2) ... 2 1 .
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

otros

muchos

algoritmos de iteracin que veremos ahora implementados en C, en


estas pginas.
Veamos una posible solucin al programa del factorial:
#include <stdio.h>
void main(void)
{
unsigned short n;
unsigned long Fact;
printf("Introduzca el entero ... ");
scanf("%hu",&n);
printf("El factorial de %hu es ... ", n);
Fact = 1;
while(n != 0)
{
Fact = Fact * n;
n = n - 1;
}
printf("%lu.", 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.

106

Captulo 4. Estructuras de control.

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
Fact

5
5

4
20

3
60

2
120

1
120

0
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

107

Fundamentos de informtica. Programacin en Lenguaje C

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 = getchar()) != 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 = getchar();

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

programacin, sino tambin de algoritmos vlidos.

108

de

lenguaje

de

Captulo 4. Estructuras de control.

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 a1 y b1
(diremos mcd(a1 , b1 ) ), donde b1 0 es igual a mcd(a2 , b2 ) donde
a2 = b1 y donde b2 = a1 %b1 , entendiendo por a1 %b1 el resto de la

divisin de a1 con b1 . Y el proceso puede seguirse hasta llegar a unos


valores de ai y de bi que verifiquen que ai 0 , bi 0 y ai %bi = 0 .
Entonces, el algoritmo de Euclides afirma que, llegado a estos valores el
valor buscado es mcd(a1 , b1 ) = bi .
Ahora falta poner esto en lenguaje C. Ah va:
#include <stdio.h>
void main(void)
{
short int a, b, aux;
short int mcd;
printf("Valor de a ... ");
scanf("%hd",&a);
printf("Valor de b ... ");
scanf("%hd",&b);
printf("El mcd de %hd y %hd es ... ", a, b);
while(b)
{
aux = a % b;
a = b;
b = aux;
}
printf("%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;

109

Fundamentos de informtica. Programacin en Lenguaje C

entonces, el anterior valor de b (que est guardado en a) ser el


mximo comn divisor.

Estructura dowhile.

La estructura dowhile 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

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:
#include <stdio.h>
void main(void)
{
short int n,i = 0;
printf("Tabla de multiplicar del ... ");
scanf("%hd",&n);
do
{
printf("%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

110

Captulo 4. Estructuras de control.

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.):
#include <stdio.h>
void main(void)
{
unsigned short n;
unsigned long Fact;
char opcion;
do
{
Fact = 1;
printf("\n\nIntroduzca el entero ... ");
scanf("%hu",&n);
printf("El factorial de %hu es ... ", n);
while(n != 0) Fact *= n--;
printf("%lu.", Fact);
printf("\n\nCalcular otro factorial (s/n) ");
}while(opcion = getchar() == 's');
}

PROCESO
S

repetir

No

Figura 4.7.: Flujograma


para repeticin de proceso.

La estructura dowhile 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.

111

Fundamentos de informtica. Programacin en Lenguaje C

El cdigo queda ahora de la siguiente manera:


#include <stdio.h>
void main(void)
{
unsigned short n;
unsigned long Fact;
char opcion;
do
{
Fact = 1;
printf("\n\nIntroduzca el entero ... ");
scanf("%hu",&n);
printf("El factorial de %hu es ... ", n);
while(n != 0) Fact *= n--;
printf("%lu.", Fact);
printf("\n\nCalcular otro factorial (s/n) ");
do
opcion = getchar();
while (opcion != s && opcion != n);
}while(opcion = getchar() == '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 opcion ' s ' | opcion ' n '


C 2 opcion = ' s '
PROCESO

opcion
S
S

C2

C1

No

No

Figura 4.8.: Flujograma


para repeticin de proceso.

112

Captulo 4. Estructuras de control.

Estructura for.

Una estructura for tiene una sintaxis notablemente distinta a la indicada


para las estructuras while y dowhile. Pero la funcin que realiza es la
misma. Con la palabra reservada for podemos crear estructuras de
control que se dicen controladas por variable.

s1

e1

No

s3
F
s2

Figura 4.9.: Flujograma la


estructura de control
for(s1 ; e1 ; s2) s3;

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.

113

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
void main(void)
{
short i;
for(i = 2 ; i <= 100 ; i += 2)
printf("%5hd",i);
}

Si queremos mejorar la presentacin, podemos hacer que cada cinco


pares comience una nueva fila:
#include <stdio.h>
void main(void)

114

Captulo 4. Estructuras de control.

{
short i;
for(i = 2 ; i <= 100 ; i += 2)
{
printf("%5hd",i);
if(i % 10 == 0) printf("\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:
#include <stdio.h>
void main(void)
{
short int n,i;
printf("Tabla de multiplicar del ... ");
scanf("%hd",&n);
for(i = 0 ; i <= 10 ; i++)
printf("%3hu * %3hu = %3hu\n",i,n,i * n);
}

115

Fundamentos de informtica. Programacin en Lenguaje C

Bloquear la ejecucin hasta que se pulse la tecla a:


for( ; ch != a ; ch = getchar());

Bsqueda del mximo comn divisor de dos enteros:


for( ; b ; )
{
aux = a % b;
a = b;
b = aux;
}
printf("%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:
#include <stdio.h>
void main(void)
{
short i, num;
for(i = 1 ; ; i++)
{
printf("Introduzca un entero ... ");
scanf("%hd",&num);
if(num % 2 == 0) break;

116

Captulo 4. Estructuras de control.

}
printf("Ha introducido el entero par %hd",num);
printf(" despus de %hd impares. ",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++)
{
printf("entero: ");
scanf("%hd",&num);
if(num % 2 == 0) break;
}

for(i = 1 ; num%2 == 0 ; i++)


{
printf("entero: ");
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.

i 1

num

C1 num mod2 = 0

i i +1

C1

i 1

i i +1

num

C1

Con break

Sin break

Figura 4.10.: Salto con o sin break.

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.

117

Fundamentos de informtica. Programacin en Lenguaje C

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
{
printf(Introduzca un nuevo sumando ... );
scanf(%ld,&num);
if(num % 2 != 0) break;
suma += num;
}while(1);
printf(La suma es ... %ld, 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.

C1 nummod2 0
suma 0

suma

num

suma + num

suma

C1

Figura 4.11.: Otro ejemplo de salto con break.

Tambin se pueden tener estructuras for sin ninguna expresin recogida


entre sus parntesis. Por ejemplo:
for( ; ; )
{
ch = getchar();

118

Captulo 4. Estructuras de control.

printf(Esto es un bucle infinito\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:
#include <stdio.h>
void main(void)
{
short i;
for(i = 1 ;i <= 100 ; i++)
{
if(i % 2) continue;
printf("%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
i 1
C
C1

i i +1

C2

C1 i 100
C 2 i mod2 0

Figura 4.12.: Salto con continue.

119

Fundamentos de informtica. Programacin en Lenguaje C

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:

120

Captulo 4. Estructuras de control.

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.

121

Fundamentos de informtica. Programacin en Lenguaje C

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

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

informtica. Codificacin y algoritmia.

122

del

manual

Fundamentos

de

Captulo 4. Estructuras de control.

20.

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

#include <stdio.h>
void main(void)
{
long suma = 0;
short i;
for(i = 2 ; i <= 200 ; i += 2)
suma += i;
printf("esta suma es ... %ld.\n",suma);
}

21.

Hacer un programa que calcule la media de todos los valores


que introduzca el usuario por consola. El programa debe dejar
de solicitar valores cuando el usuario introduzca el valor 0.

#include <stdio.h>
void main(void)
{
long suma;
short i, num;
for(i = 0, suma = 0 ; ; i++)
{
printf("Introduzca nmero ... ");
scanf("%hd",&num);
suma += num;
if(num == 0) break;
}
if(i == 0) printf("No se han introducido enteros.");
else printf("La media 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.

123

Fundamentos de informtica. Programacin en Lenguaje C

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 ejemplo, 6 = 1 + 2 + 3 que son,
efectivamente, sus divisores.

#include <stdio.h>
void main(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)
printf("En entero %hd es perfecto.\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.

124

Captulo 4. Estructuras de control.

23.

Solicitar del usuario cuatro nmeros y mostrarlos por pantalla


ordenados de menor a mayor.

#include <stdio.h>
void main(void)
{
unsigned short int a0,a1,a2,a3;
printf("Introduzca cuatro enteros ... \n\n");
printf("Primer entero ... ");
scanf("%hu",&a0);
printf("Segundo entero ... ");
scanf("%hu",&a1);
printf("Tercer entero ... ");
scanf("%hu",&a2);
printf("Cuarto entero ... ");
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;

125

Fundamentos de informtica. Programacin en Lenguaje C

}
if(a2 > a3)
{
a2 ^= a3;
a3 ^= a2;
a2 ^= a3;
}
printf("\nOrdenados... \n");
printf("%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 ASCII.

(Antes de ejecutar el cdigo escrito, termine de leer todo el texto


recogido en este problema.)
#include <stdio.h>
void main(void)
{
unsigned char a;
for(a = 0 ; a <= 255 ; a++)
printf("%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++)
{
printf("%3c - %hX - %hd\n",a,a,a);
getchar();
}
Advertencia importante: De forma intencionada hemos dejado el

cdigo de este problema con un error grave. Aparentemente todo est

126

Captulo 4. Estructuras de control.

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: (255)10 = (11111111)2 ; si solo tenemos ocho dgitos,
entonces (11111111 + 1)2 = (00000000)2 , 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++)
printf("%3c - %hX - %hd\n",a,a,a);
printf("%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.

#include <stdio.h>
void main(void)
{

127

Fundamentos de informtica. Programacin en Lenguaje C

signed long a;
unsigned long Test;
char opcion;
do
{
Test = 0x80000000;
printf("\n\nIndique el entero ... ");
scanf("%ld", &a);
while(Test)
{
Test & a ? printf("1") : printf("0");
Test >>= 1;
}
printf("\nDesea introducir otro entero? ... ");
do
opcion = getchar();
while (opcion != 's' && opcion != 'n');
}while(opcion == '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
a
Test & a

1000 0000 0000 0000 0000 0000 0000 0000


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

128

Captulo 4. Estructuras de control.

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
a
Test & a

0100 0000 0000 0000 0000 0000 0000 0000


xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
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
dowhile 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.

#include <stdio.h>
#include <math.h>
void main(void)
{
unsigned long int numero, raiz;
unsigned long int div;
char chivato;
printf("Dame el numero que vamos a testear ... ");
scanf("%lu", &numero);
chivato = 0;
raiz = sqrt(numero);
for(div = 2 ; div <= raiz ; div++)
{
if(numero % div == 0)
{
chivato = 1;
break;
}
}
if(chivato == 1)
printf("El numero %lu es compuesto",numero);
else printf("El numero %lu es primo",numero);
}

129

Fundamentos de informtica. Programacin en Lenguaje C

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 =nn<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

130

Captulo 4. Estructuras de control.

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:
#include <stdio.h>
#include <math.h>
void main(void)
{
unsigned long int n, raiz;
unsigned long int div;
printf("Dame el numero que vamos a testear ... ");
scanf("%lu", &n);
raiz = sqrt(n);
for(div = 2 ; div<=raiz ; div++) if(n%div == 0) break;
printf(%lu es %s, n,n > raiz ? primo:compuesto);
}

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.
#include <stdio.h>
void main(void)
{
unsigned long fib1 = 1, fib2 = 1, Fib = 1;
unsigned short n, i = 3;

131

Fundamentos de informtica. Programacin en Lenguaje C

printf("Indique el elemento que se debe mostrar ... ");


scanf("%hu",&n);
while(i <= n)
{
Fib = fib1 + fib2;
fib1 = fib2;
fib2 = Fib;
i++;
}
printf("El trmino %hu de la serie es %lu.\n",n,Fib);
}

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.

#include <stdio.h>
#include <math.h>
void main(void)
{
float a, b, c;
double r;
// introduccin de parmetros...
printf("Introduzca los coeficientes...\n\n");
printf("a --> "); scanf("%f",&a);
printf("b --> "); scanf("%f",&b);
printf("c --> "); scanf("%f",&c);
// Ecuacin de primer grado...
if(a == 0)
{
// No hay ecuacin ...
if(b == 0) printf("No hay ecuacin.\n");
else // S hay ecuacin de primer grado
{
printf("Ecuacin de primer grado.\n");
printf("Tiene una nica solucin.\n");
printf("x1 --> %lf\n", -c / b);
}
}
// Ecuacin de segundo grado. Soluciones imaginarias.
else if ((r = b * b - 4 * a * c) < 0)
{
printf("Ecuacin sin soluciones reales.\n");
r = sqrt(-r);

132

Captulo 4. Estructuras de control.

printf("x1: %lf + %lf * i\n",-b/(2*a), r/(2*a));


printf("x2: %lf + %lf * i\n",-b/(2*a),-r/(2*a));
}
// Ecuacin de segundo grado. Soluciones reales.
else
{
printf("Las soluciones son:\n");
r = sqrt(r);
printf("\tx1 --> %lf\n", (-b + r) / (2 * a));
printf("\tx2 --> %lf\n", (-b - r) / (2 * a));
}
}

29.

Escriba un programa que muestre todos los divisores de un


entero que se recibe como entrada del algoritmo.

#include <stdio.h>
void main(void)
{
long int numero;
long int div;
printf("Nmero --> ");
scanf("%ld",&numero);
printf("\n\nLos divisores de %ld son:\n\t", numero);
printf("1, ");
for(div = 2 ; div <= numero / 2 ; div++)
if(numero % div == 0)
printf("%ld, ",div);
printf("%ld.",numero);
}

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.

133

Fundamentos de informtica. Programacin en Lenguaje C

30.

Escriba un programa que calcule el nmero , sabiendo que


este nmero verifica la siguiente relacin:

2
6

2.
k =1 k

#include <stdio.h>
#include <math.h>
#define LIMITE 10000
void main(void)
{
double PI = 0;
for(int i = 1 ; i < LIMITE ; i++)
PI += 1.0 / (i * i);
PI *= 6;
PI = sqrt(PI);
printf("El valor de PI es ... %lf.",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:

( 1)

2k +1

k =0

134

Captulo 4. Estructuras de control.

#include <stdio.h>
#define LIMITE 100000
void main(void)
{
double PI = 0;
for(int i = 1 , e = 4 ; i < LIMITE ; i += 2 , e = -e)
PI += e / (double)i;
printf("El valor de PI es ... %lf.",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 k + 1 . 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
... .
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:

135

Fundamentos de informtica. Programacin en Lenguaje C

#include <stdio.h>
#define LIMITE 100000
void main(void)
{
double PI = 2;
for(int num = 2 , den = 1, i = 1 ;
i < LIMITE ; i++ , i % 2 ? num+=2 : den+=2)
PI *= num / (double)den;
printf("El valor de PI es ... %lf.",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 ,
double x, y, z;
short int
a = 0,
b = 1,
c = 2,
double
x,
y, z;

33.

b = 1, c = 2;

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 ejecutan con tanto afn
este

trabajo

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

136

Captulo 4. Estructuras de control.

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.
#include <stdio.h>
void main(void)
{
unsigned long N = 6, n;
unsigned long soluciones = 0;
printf("Valores posibles 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))
{
printf("(%4lu) %-8lu",++soluciones, N);
if(!(soluciones % 5)) printf("\n");
}
}
}

137

Fundamentos de informtica. Programacin en Lenguaje C

34.

El calendario juliano (debido a julio 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 juliano 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 XII, en 1582, propuso reformar el calendario
juliano para evitar los errores arrastrados de aos anteriores.
Los acuerdos tomados entonces, que son por los que nos an
nos regimos, fueron los siguientes:
D Para suprimir el error acumulado por el calendario juliano,
se suprimieron diez das. De tal manera que el da siguiente
al 4 de octubre de 1582 fuel el da 15 del mismo mes.
D La duracin de los aos sera de 365 das o 366 en caso de
ser bisiesto.
D 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 ejemplo 1800 no fue
bisiesto y el 2000 s.
D 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:
D El ao debe ser mayor que 0.

138

Captulo 4. Estructuras de control.

D El mes debe ser un nmero entre uno y doce.


D 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:

e=

1
1
1
1
+
+
+
+ ...
0! 1! 2! 3!

#include <stdio.h>
void main(void)
{
double p = 1, e = 1;
for(short n = 1 ; n < 100 ; n++)
{
p *= 1.0 / n;
e += p;
}
printf("El numero e es ... %20.17lf.",e);
}

36.

Juego

de

las

15

cerillas:

Participan

dos

jugadores.

Inicialmente se colocan 15 cerillas sobre una mesa y cada uno


de los dos jugadores toma, alternativamente 1, 2 o 3 cerillas
pierde el jugador que toma la ltima cerilla.
Buscar el algoritmo ganador para este juego, 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

139

Fundamentos de informtica. Programacin en Lenguaje C

teclado y decididos por un jugador (que ser el jugador


perdedor), el otro jugador (que ser el ordenador, y que
siempre debe ganar) decide quien empieza el juego.

#include <stdio.h>
void main(void)
{
// Con cuntas cerillas se va a jugar.
unsigned short cerillas;
// Cuntas cerillas se pueden quitar cada vez.
unsigned short quitar;
// Cerillas que quita el ganador y el perdedor.
unsigned short qp, qg;
char ganador;
do
{
printf("\n\n\nCon cuntas cerillas
se va a jugar ... ");
scanf("%hu",&cerillas);
if(cerillas == 0) break;
do
{
printf("\Cuntas cerillas pueden
quitarse de una vez... ");
scanf("%hu",&quitar);
if(quitar >= cerillas)
printf("No pueden quitarse tantas
cerillas.\n");
}while(quitar >= cerillas);
qg = (cerillas - 1) % (quitar + 1);
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");
// Fin de MOSTRAR CERILLAS
if(qg)
{
printf("\nComienza la mquina...");
printf("\nMquina Retira %hu cerillas...
\n", qg);

140

Captulo 4. Estructuras de control.

cerillas -= qg;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");
}
// FIN DE MOSTRAR CERILLAS
else printf("\nComienza el jugador...");
while(cerillas != 1)
{
do
{
printf("\nCerillas que retira el
jugador ... ");
scanf("%hu",&qp);
if(qp > quitar)
printf("\nNo puede quitar mas
de %hu.\n",quitar);
}while(qp > quitar);
cerillas -= qp;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30)) printf("\n\n");
}
printf("\n");
// Fin de MOSTRAR CERILLAS
if(cerillas == 1)
{
ganador = 'j';
break;
}
qg = quitar - qp + 1;
printf("\nLa mquina retira %hu
cerillas.\n",qg);
cerillas -= qg;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");

141

Fundamentos de informtica. Programacin en Lenguaje C

// Fin de MOSTRAR CERILLAS


if(cerillas == 1) ganador = 'm';
}
if(ganador == 'j')
printf("\nHa ganado el jugador...");
else if(ganador == 'm')
printf("\nHe ganado yo, la mquina...");
}while(cerillas);
}

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 ejemplo:
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+

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 :

142

Captulo 4. Estructuras de control.

1+

1+
1+

1
1 + ...

1
1+

1
1

Escriba un programa para calcular el nmero ureo mediante


este

procedimiento

haciendo,

por

ejemplo,

la

sustitucin

= 1 + 1 mil veces. Mostrar luego el resultado por pantalla.

#include <stdio.h>
#define LIMITE 1000
void main(void)
{
double au = 1;
for(int i = 0 ; i < LIMITE ; i++)
au = 1 + 1 / au;
printf("El nmero ureo es ..... %lf.\n",au);
printf("ureo al cuadrado es ... %lf.\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

143

Fundamentos de informtica. Programacin en Lenguaje C

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

#include <stdio.h>
#include <math.h>
#define LIMITE 100000
void main(void)
{
double au = 1;
for(int i = 0 ; i < LIMITE ; i++)
au = sqrt(1 + au);
printf("El nmero ureo es ..... %lf.\n",au);
printf("ureo al cuadrado es ... %lf.\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.

#include <stdio.h>
#include <math.h>
void main(void)
{
long a, b, div;
printf("Lmite inferior ... ");
scanf("%ld",&a);
printf("Lmite superior ... ");
scanf("%ld",&b);
printf("Los primos entre %ld y %ld son ...\n\t", a, b);
for(long num = a, primos = 0 ; num <= b ; num++)
{

144

Captulo 4. Estructuras de control.

for(div = 2 ; div < sqrt(num) ; div++)


if(num % div == 0) break;
if(num % div == 0) continue;
if(primos != 0 && primos % 10 == 0)
printf("\n\t");
primos++;
printf("%6ld,",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,
179,
233,

131,
181,
239,

137,
191,
241,

139,
193,
251,

149,
197,
257,

151,
199,
263,

157,
211,

163,
223,

167,
227,

173,
229,

Para este ltimo programa se recomienda que se dibuje el diagrama de


flujo.

145

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

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

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

148

Captulo 5. mbito y vida de las variables.

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.

149

Fundamentos de informtica. Programacin en Lenguaje C

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;
#include <stdio.h>
void main(void)
{
short int n;
printf("Introduce el valor de n ... ");
scanf("%hd",&n);
printf("El factorial de %hd es ... ",n);
Fact = 1;
while(n) Fact *=n--;
printf("%ld",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.

150

Captulo 5. mbito y vida de las variables.

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;
// Slo x est disponible.
{
long y = 25;
// Tanto x como y estn disponibles.
}
// La variable y est fuera de mbito. Ha terminado su vida.
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;
// Slo x est disponible.
{
long x = 25;
// En este bloque la nica variable x accesible vale 25.
}
// La nica variable x en este mbito vale 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;
// Slo x est disponible.
{
long y = 25;

151

Fundamentos de informtica. Programacin en Lenguaje C

// Tanto x como y estn disponibles.


}
// La variable y est fuera de mbito. Ha terminado su vida.
{
long y = 40;
// Tanto x como y estn disponibles.
// Esta variable y no es la misma que la otra.
/* La declaracin de la variable y es correcta, puesto que
la anterior declaracin de una variable con el mismo
nombre fue en otro mbito. */
}
// La variable y est fuera de mbito. Ha terminado su vida.
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)printf(%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:

152

Captulo 5. mbito y vida de las variables.

#include <stdio.h>
long b = 0, c = 0;
void main(void)
{
for(long b = 0 ; b < 10 ; b++) c++;
printf("El valor de b es %ld y el de c es %ld", 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

153

Fundamentos de informtica. Programacin en Lenguaje C

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

154

Captulo 5. mbito y vida de las variables.

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

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):
#include <stdio.h>
void main(void)
{
for(long i = 0 ; i
for(long j =
{
static
long b

< 3 ; i++)
0 ; j < 4 ; j++)
long a = 0;
= 0;

for(long j = 0 ; j < 5 ; j++, a++, b++);

155

Fundamentos de informtica. Programacin en Lenguaje C

printf("a = %3ld.

b = %3ld.\n", a, b);

}
}
El programa ofrece la siguiente salida por pantalla:
a
a
a
a
a
a
a
a
a
a
a
a

=
=
=
=
=
=
=
=
=
=
=
=

5.
10.
15.
20.
25.
30.
35.
40.
45.
50.
55.
60.

b=
b=
b=
b=
b=
b=
b=
b=
b=
b=
b=
b=

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

156

Captulo 5. mbito y vida de las variables.

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.

157

Fundamentos de informtica. Programacin en Lenguaje C

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:

158

Captulo 5. mbito y vida de las variables.

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

159

Fundamentos de informtica. Programacin en Lenguaje C

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 ejecute el
cdigo del bucle, un valor contador que se incrementa y que
indica cuntas veces se est ejecutando a lo largo de toda la
aplicacin. Esa variable contador ser declarada como static.

#include <stdio.h>
void main(void)
{
unsigned short a, b, mcd;
do
{
printf("Valor de a ... ");
scanf("%hu",&a);
printf("Valor de b ... ");
scanf("%hu",&b);
if(a == 0 && b == 0) break;
while(b)
{
static unsigned short cont = 0;
mcd = b;
b = a % b;
a = mcd;
cont++;
printf("\ncont = %hu", cont);
printf("\n\nEl mcd es %hu.", mcd);
}while(1);

160

Captulo 5. mbito y vida de las variables.

}
Cada vez que se ejecuta el bloque de la estructura dowhile 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.

161

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

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_vector[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.

164

Captulo 6. Arrays numricos: vectores y matrices.

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_vector[1000], i;
for(i = 0 ; i < 1000 ; i++) mi_vector[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_vector[1000], i;
for(i = 0 ; i <= 1000 ; i++) mi_vector[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_vector[10] = {10,20,30,40,50,60,70,80,90,100};

165

Fundamentos de informtica. Programacin en Lenguaje C

que es equivalente al siguiente cdigo:


short mi_vector[10], i;
for(i = 0 ; i < 10 ; i++) mi_vector[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_vector[] = {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 matriz[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;

166

Captulo 6. Arrays numricos: vectores y matrices.

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


for(j = 0 ; j < 2 ; j++)
matriz[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 matriz[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++)
{
matriz[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:
#include <stdio.h>
void main(void)
{
char m[2][5];
short i, j;
for(i = 0 ; i < 2 ; i++)
for(j = 0 ; j < 5 ; j++)
printf("&m[%hd][%hd] = %p\n",i,j,&m[i][j]);
}
La salida por pantalla ha sido esta:
&m[0][0]
&m[0][1]
&m[0][2]
&m[0][3]
&m[0][4]
&m[1][0]
&m[1][1]

=
=
=
=
=
=
=

0012FF80
0012FF81
0012FF82
0012FF83
0012FF84
0012FF85
0012FF86

167

Fundamentos de informtica. Programacin en Lenguaje C

&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
#include <stdio.h>
void main(void)
{
char m[2][5];
short i, j;
for(i = 0 ; i < 2 ; i++)
for(j = 0 ; j < 6 ; j++)
printf("&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]
&m[0][1]
&m[0][2]
&m[0][3]
&m[0][4]
&m[0][5]
&m[1][0]
&m[1][1]
&m[1][2]
&m[1][3]
&m[1][4]
&m[1][5]

=
=
=
=
=
=
=
=
=
=
=
=

0012FF80
0012FF81
0012FF82
0012FF83
0012FF84
0012FF85
0012FF85
0012FF86
0012FF87
0012FF88
0012FF89
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

168

Captulo 6. Arrays numricos: vectores y matrices.

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 identidad[3][3] = {{1,0,0},{0,1,0},{0,0,1}};


Otra forma de solventarlo:
short identidad[3][3], i, j;
for(i = 0 ; i < 3 ; i++)
for(j = 0 ; j < 3 ; j++)
identidad[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
identidad[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++)
{
printf("\n\n");
for(j = 0 ; j < 3 ; j++)
printf("%5hd",identidad[i][j]);
}

169

Fundamentos de informtica. Programacin en Lenguaje C

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.

#include <stdio.h>
void main(void)
{
short matriz[3][3];
short i, j;
for(i = 0 ; i < 3 ; i++)
for(j = 0 ; j < 3 ; j++)
{
printf("matriz[%hd][%hd] = ", i, j);
scanf("%hd",&matriz[i][j]);
}
for(i = 0 ; i < 3 ; i++)
{
printf("\n\n");
for(j = 0 ; j < 3 ; j++)
printf("%5hd",matriz[i][j]);
}
printf("\n\n\n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n\n");
for(j = 0 ; j < 3 ; j++)
printf("%5hd",matriz[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.

170

Captulo 6. Arrays numricos: vectores y matrices.

#define TAM 3
#include <stdio.h>
void main(void)
{
short a[TAM][TAM];
short b[TAM][TAM];
short s[TAM][TAM];
short p[TAM][TAM];
short i, j, k;
// Entrada matriz a.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
{
printf("a[%hd][%hd] = ", i, j);
scanf("%hd",&a[i][j]);
}
// Entrada matriz b.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
{
printf("b[%hd][%hd] = ", i, j);
scanf("%hd",&b[i][j]);
}
// Clculo Suma.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
s[i][j] = a[i][j] + b[i][j];
// Clculo Producto.
// 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];
}
// Mostrar resultados.
// SUMA
for(i = 0 ; i < TAM ; i++)
{
printf("\n\n");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",a[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",b[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",s[i][j]);
printf("\t");
}

171

Fundamentos de informtica. Programacin en Lenguaje C

// PRODUCTO
printf("\n\n\n");
for(i = 0 ; i < TAM ; i++)
{
printf("\n\n");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",a[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",b[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",p[i][j]);
printf("\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 conjunto 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.

#include <stdio.h>
void main(void)
{
short datos[1000];
short i, j, nn;
// Introduccin de datos.
i = 0;
do
{
printf("Entada de nuevo dato ... ");
scanf("%hi",&datos[i]);
i++;

172

Captulo 6. Arrays numricos: vectores y matrices.

}while(datos[i - 1] != 0 && i < 1000);


nn = i - 1; // Total de datos vlidos introducidos.
// Ordenar datos
for(i = 0 ; i <= nn ; i++)
for(j = i + 1 ; j < nn ; j++)
if(datos[i] > datos[j])
{
datos[i] ^= datos[j];
datos[j] ^= datos[i];
datos[i] ^= datos[j];
}
// Mostrar datos ordenados por pantalla
printf("\n\n");
for(i = 0 ; i < nn ; i++)
printf("%li < ", datos[i]);
printf("\b\b ");
}
Introduccin de datos: va solicitando uno a uno todos los datos,
mediante una estructura de control dowhile. 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.

173

Fundamentos de informtica. Programacin en Lenguaje C

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.

#include <stdio.h>
#define TAM 1000
void main(void)
{
unsigned long int numero, mitad;
unsigned long int i;
unsigned long int div;
unsigned long int D[TAM];
for(i = 0 ; i < TAM ; i++) D[i] = 0;
D[0] = 1;
printf("Numero que vamos a testear ... ");
scanf("%lu", &numero);
mitad = numero / 2;
for(i = 1 , div = 2 ; div <= mitad ; div++)
{
if(numero % div == 0)
{
D[i] = div;
i++;
if(i == TAM)
{
printf("Vector mal dimensionado.");
break;
}
}
}
if(i < TAM) D[i] = numero;
if(i == 1) printf("\n%lu es PRIMO.\n",numero);
else
{
printf("\n%lu es COMPUESTO. ", numero);
printf("Sus divisores son:\n\n");
for(i = 0 ; i < TAM && D[i] != 0; i++)
printf("\n%lu", 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

174

Captulo 6. Arrays numricos: vectores y matrices.

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:
#include <stdio.h>
void main(void)
{
signed long N;
unsigned short bits[32], i;
unsigned long Test;
do
{
printf("\n\nIntroduce un entero ... ");

175

Fundamentos de informtica. Programacin en Lenguaje C

scanf("%li",&N);
if(N == 0) break;
for(i=0,Test = 0x80000000 ; Test ; Test >>=1,i++)
bits[i] = Test & N ? 1 : 0;
printf("\nCodificacin binaria interna ... ");
for(i = 0 ; i < sizeof(long) * 8 ; i++)
printf("%hu", bits[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
ejemplo:
6

La tcnica que se utiliza para generar cuadros mgicos


(que tienen siempre una dimensin impar: impar nmero

176

Captulo 6. Arrays numricos: vectores y matrices.

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

una

columna

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

177

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#define lr(x, N) ((x)< 0 ? N+(x)%N : ((x)>=N ? (x)%N : (x) ))
void main(void)
{
unsigned short magico[17][17];
unsigned short fil, col, dim, num;
do
{
printf( "\nDimensin ( impar entre 3 y 17 ): ");
scanf("%hu", &dim);
}while(dim % 2 == 0);
printf( "\nCuadro Mgico de dimensin %hu:\n\n", dim);
//Inicializamos la matriz a cero
for(fil = 0 ; fil < dim ; fil++)
for(col = 0 ; col < dim ; col++)
magico[fil][col] = 0;
// Algoritmo de asignacin de valores...
for(fil = dim/2 , col = 0 , num = 1 ; num < dim*dim;)
{
if(magico[fil][col] == 0)
{
magico[fil][col] = num++;
fil = lr(fil + 1, dim);
col = lr(col - 1, dim);
}
else
{
fil = lr(fil - 1, dim);
col = lr(col + 2, dim);
}
}

178

Captulo 6. Arrays numricos: vectores y matrices.

// Mostramos ahora el cuadrado mgico por pantalla


for(fil = 0 ; fil < dim ; fil++)
{
printf("\n\n");
for(col = 0 ; col < dim ; col++)
printf("%5hu", magico[fil][col]);
}
}

179

Fundamentos de informtica. Programacin en Lenguaje C

dim

fil 0

C
fil 0
Salto de lnea

C1 fil < dim

Inicializar matriz
M

C1

No

col 0

Rellenar matriz
M

C1 fil < dim


S
F

No

C1

col 0

M[fil ][col ] 0
M[fil ][col ]

Mostrar matriz
M

No

C2

No

C 2 col < dim

C2

C 2 col < dim

Proceso general

Mostrar Matriz M

Iniciar Matriz M

fil dim 2

M[fil ][col ] 1

col 0

numero 2

numero numero + 1

M[fil , col ] numero

S
Buscar siguiente
posicin (fil , col )

Rellenar Matriz M
C

posf fil

No
C1 numero < dim2

posc col
S

Buscar siguiente
posicin (fil , col )

C1

fil dim 1

C1

C1 fil = 0
No

fil fil 1

C2

C 2 col = 0
No

col dim 1

No

col col 1

C3

Si

C 3 M[fil ][col ] 0
M[fil ][col ] numero

col posc + 1

fil posf + 1

Figura 6.1.: Flujograma para la implementacin del cuadro mgico.

180

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

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(isalnum(@)) printf(Alfanumrico):
else printf(No alfanumrico);
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);

182

Captulo 7. Caracteres y cadenas de caracteres.

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(isalnum(2)) printf(Alfabtico):
else printf(No alfabtico);
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(iscntrl(\n)) printf(Carcter de control):
else printf(No carcter de control);
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.

183

Fundamentos de informtica. Programacin en Lenguaje C

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.
#include <stdio.h>
#include <ctype.h>
void main(void)
{
unsigned short int a = 0,
char ch;
do
{
ch = getchar();
if(isalpha(ch))
{
if(ch == 'a')
else if(ch ==
else if(ch ==
else if(ch ==

e = 0, i = 0, o = 0, u = 0;

a++;
'e') e++;
'i') i++;
'o') o++;

184

Captulo 7. Caracteres y cadenas de caracteres.

else if(ch == 'u') u++;


}
else
printf("\b ");
}while(ch != 10);
printf("\n\nVocal a ... %hu",a);
printf("\nVocal e ... %hu",e);
printf("\nVocal i ... %hu",i);
printf("\nVocal o ... %hu",o);
printf("\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 dowhile.

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.

185

Fundamentos de informtica. Programacin en Lenguaje C

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

186

Captulo 7. Caracteres y cadenas de caracteres.

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] = hola;
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] = hola;
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.

187

Fundamentos de informtica. Programacin en Lenguaje C

Y al igual que con los arrays, podemos inicializar la cadena, sin


necesidad de dimensionarla:
char mi_cadena[] = hola;
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.

188

Captulo 7. Caracteres y cadenas de caracteres.

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.
#include <stdio.h>
void main(void)
{
char nombre[10];
printf("Cmo te llamas? ");
gets(nombre);
printf("Hola, %s.", nombre);
}
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.

189

Fundamentos de informtica. Programacin en Lenguaje C

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.
#define TAM 30
#include <stdio.h>
#include <ctype.h>
void main(void)
{
char nombre[TAM];
short int i;
printf("Cmo te llamas? ");
for(i = 0 ; i < TAM ; i++)
nombre[i] = NULL;
i = 0;
while(i < TAM)
{
nombre[i] = getchar();
if (nombre[i] == 10)
{
nombre[i] = NULL;
break;
}
else if(nombre[i] == 8 && i > 0)
{
nombre[i] = NULL;
printf("\b \b");
i--;
}
else if(isgraph(nombre[i]) || nombre[i] == 32)
printf("%c",nombre[i++]);
}
printf("\n\nHola, %s.", nombre);
}
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

190

Captulo 7. Caracteres y cadenas de caracteres.

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:


#include <stdio.h>
void main(void)
{
char original[100];
char copia[100];
short int i = 0;
printf(Cadena original ... );
gets(original);
while(original[i] != NULL)
{
copia[i] = original[i];
i++;
}
copia[i] = NULL;
printf("Original: %s\n",original);
printf("Copa: %s\n",copia);
}

191

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#include <string.h>
void main(void)
{
char original[100];
char copia[100];
printf(Cadena original ... );
gets(original);
strcpy(copia, original);
printf("Original: %s\n",original);
printf("Copa: %s\n",copia);
}

Determinar la longitud de una cadena:


#include <stdio.h>
void main(void)
{
char original[100];
short int i = 0;
printf(Cadena original ... );
gets(original);
while(original[i] != NULL) i++;
printf("%s tiene longitud %hd\n",original,i);
}
El contador i se va incrementando hasta llegar al carcter nulo. As, en i,
tenemos la longitud de la cadena.

192

Captulo 7. Caracteres y cadenas de caracteres.

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.
#include <stdio.h>
#include <string.h>
void main(void)
{
char original[100];
short int i;
printf(Cadena original ... );
gets(original);
i = strlen(original);
printf("%s tiene longitud %hd\n",original,i);
}

Concatenar una cadena al final de otra:


#include <stdio.h>
void main(void)
{
char original[100];
char concat[100];
short int i = 0, j = 0;
printf(Cadena original ... );
gets(original);
printf(Cadena a concatenar ... );
gets(concat);
while(original[i] != NULL) i++;
while(concat[j] != NULL)
{
original[i] = concat[j];
i++;
j++;
}
original[i] = NULL;
printf("Texto concatenado: %s\n",original);
}
Y de nuevo disponemos de una funcin en la biblioteca string que
realiza la concatenacin de cadenas:
char *strcat(char *dest, const char *src);

193

Fundamentos de informtica. Programacin en Lenguaje C

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.
#include <stdio.h>
#include <string.h>
void main(void)
{
char original[100];
char concat[100];
printf(Cadena original ... );
gets(original);
printf(Cadena a concatenar ... );
gets(concat);
strcat(original, concat);
printf("Texto concatenado: %s\n",original);
}
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:
#include <stdio.h>
void main(void)
{
char cad01[100];
char cad02[100];
short int i = 0;
short int chivato = 0;
printf(Primera Cadena ... );
gets(cad01);
printf(Segunda Cadena ... );
gets(cad02);
while(cad01[i] != NULL && cad02[i] != NULL)
{
if(cad01[i] > cad02[i])
{
chivato = 1;
break;
}
else if(cad01[i] < cad02[i])
{
chivato = 2;

194

Captulo 7. Caracteres y cadenas de caracteres.

break;
}
i++;
}
if(chivato == 1)
printf("cadena01 > cadena02");
else if(chivato == 2)
printf("cadena02 > cadena01");
else if(cad01[i] == NULL && cad02[i] != NULL)
printf("cadena02 > cadena01");
else if(cad01[i] != NULL && cad02[i] == NULL)
printf("cadena01 > cadena02");
else
printf("cadenas iguales");
}
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
#include <string.h>
#include <stdio.h>
void main(void)
{
char c1[100] = "Texto de la cadena primera";
char c2[100] = "Texto de la cadena segunda";
short int comp;
printf(Primera Cadena ... );
gets(c1);
printf(Segunda Cadena ... );
gets(c2);
comp = strcmp(c1,c2);
if(comp < 0) printf("cadena02 > cadena01");
else if(comp > 0) printf("cadena01 > cadena02");
else printf("Cadenas iguales");
}
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);

195

Fundamentos de informtica. Programacin en Lenguaje C

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.

196

Captulo 7. Caracteres y cadenas de caracteres.

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.
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
char entrada[80];
double valor;
printf("Nmero decimal ... ");
gets(entrada);
valor = strtod(entrada, NULL);
printf("La cadena es %s ", entrada);
printf("y el nmero es %lf\n", valor);
}
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.

197

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
void main(void)
{
char e[80];
char n[20];
char op;
short i = 0, j = 0;
short est = 0, err = 0;
long int a1, a2, res;
printf("Introduzca la cadena de operacin.");
printf("\nX + Y = o X - Y =... ");
gets(e);
while(e[i] != NULL)
{
if(e[i] != 32 && !isdigit(e[i]) && e[i] != '+' &&
e[i] != '-' && e[i] != '=')
{
err = 1;
break;
}
else if(e[i] == 32 && (est == 0 || est == 2 ||
est == 3 || est == 5)) i++;
else if(isdigit(e[i]) && (est == 0 || est == 3))
{
est++;
n[j] = e[i];
i++;
j++;
}
else if(isdigit(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 = atol(n);
else a2 = atol(n);
est++;

198

Captulo 7. Caracteres y cadenas de caracteres.

j = 0;
i++;
}
else if((e[i] == '+' || e[i] == '-') && est == 2)
{
op = e[i];
i++;
est = 3;
}
else if(e[i] == '=' && est == 5)
{
printf("%lu %c %lu = ",a1,op,a2);
printf("%lu", op=='+' ? a1 + a2 : a1 - a2);
break;
}
else
{
err = 1;
break;
}
}
if(err == 1) printf("Error de entrada de datos");
}

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.

#include <stdio.h>
void main(void)
{
char cadena[100];
short int a, e, i, o, u, cont;
printf("Introduzca una cadena de texto ... \n");
gets(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++;

199

Fundamentos de informtica. Programacin en Lenguaje C

else if(cadena[cont] == 'o') o++;


else if(cadena[cont] == 'u') u++;
}
printf("La cadena introducida ha sido ...\n");
printf("%s\n",cadena);
printf("Y las vocales introducidas han sido ... \n");
printf("a ... %hd\n",a);
printf("e ... %hd\n",e);
printf("i ... %hd\n",i);
printf("o ... %hd\n",o);
printf("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

muestre

por

pantalla

esa

misma

cadena

en

maysculas.

#include <stdio.h>
#include <ctype.h>
void main(void)
{
char cadena[100];
short int cont;
printf("Introduzca una cadena de texto ... \n");
gets(cadena);
for(cont = 0 ; cadena[cont] != NULL ; cont++)
cadena[cont] = toupper(cadena[cont]);
printf("La cadena introducida ha sido ...\n");
printf("%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.

200

Captulo 7. Caracteres y cadenas de caracteres.

#include <stdio.h>
#include <string.h>
void main(void)
{
char cadena[100];
short int i, j;
printf("Introduzca una cadena de texto ... \n");
gets(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++;
printf("La cadena introducida ha sido ...\n");
printf("%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 ifelse, 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 ASCII.

201

Fundamentos de informtica. Programacin en Lenguaje C

#include <ctype.h>
#include <stdio.h>
void main(void)
{
char c[100];
short int i;
short int d;
printf("Introduzca una cadena de texto ... \n");
gets(c);
for(i = 0 ; c[i] != NULL ; i++)
c[i] = toupper(c[i]);
printf("Introduzca el desplazamiento ... \n");
scanf("%hd",&d);
d %= 26; /* Si d = 26 * k + d', donde d' < 26
entonces desplazar d en el
abecedario es lo mismo que desplazar d'. */
for(i = 0 ; c[i] != NULL ; i++)
{
if(isalpha(c[i]))
{
c[i] += d;
if(c[i] > 'Z') c[i] = 'A' + c[i] - 'Z' - 1;
}
}
printf("La cadena transformada queda ... \n");
printf("%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.

202

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

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:

204

Captulo 8. Punteros.

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 232 bytes, es decir, hasta 4 230 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;

205

Fundamentos de informtica. Programacin en Lenguaje C

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, R1 , 3.14> y <pt, float*, R2 , ? >
Con la segunda instruccin damos valor a la variable puntero:
<pt, float*, R2 , R1 >
Ahora la variable puntero pt vale R1 , que es la direccin de memoria de
la variable pi.

206

Captulo 8. Punteros.

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;

207

Fundamentos de informtica. Programacin en Lenguaje C

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

208

Captulo 8. Punteros.

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

209

Fundamentos de informtica. Programacin en Lenguaje C

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[0]

a[1]

a[2]

a
F6C3:9870
F6C3:9871
F6C3:9872
F6C3:9873
F6C3:9874
F6C3:9875
F6C3:9876
F6C3:9877
F6C3:9878
F6C3:9879
F6C3:987A
F6C3:987B
F6C3:987C
F6C3:987D

pa

pa + 1

pa + 2

pa + 3

pb

pb + 1

pb + 2

pb + 3

pb + 4

pb + 5

pb + 6

b
F6C3:9870
F6C3:9871
F6C3:9872
F6C3:9873
F6C3:9874
F6C3:9875
F6C3:9876
F6C3:9877
F6C3:9878
F6C3:9879
F6C3:987A
F6C3:987B
F6C3:987C
F6C3:987D

b[0]
b[1]
b[2]
b[3]
b[4]
b[5]

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.

210

Captulo 8. Punteros.

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];
ph1 =
&h[0];
pf1 =
&f[0];
pd1 =
&d[0];
pld1 = &ld[0];

pc2 =
&c[9];
ph2 =
&h[9];
pf2 =
&f[9];
pd2 =
&d[9];
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) ph2(0012FF62) -

pc1(0012FF80) = 9
ph1(0012FF50) = 9

211

Fundamentos de informtica. Programacin en Lenguaje C

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

- (long)pc1
- (long)ph1
- (long)pf1
- (long)pd1
- (long)pld1

=
=
=
=
=

9
18
36
72
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.

212

Captulo 8. Punteros.

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

213

Fundamentos de informtica. Programacin en Lenguaje C

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,

214

Captulo 8. Punteros.

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

215

Fundamentos de informtica. Programacin en Lenguaje C

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:

216

Captulo 8. Punteros.

m =
*(m
*(m
*(m
*(m

0012FECC
+ 0) = 0012FECC
+ 1) = 0012FEFC
+ 2) = 0012FF2C
+ 3) = 0012FF5C

&m[0][0]
&m[1][0]
&m[2][0]
&m[3][0]

=
=
=
=

0012FECC
0012FEFC
0012FF2C
0012FF5C

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


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

m[0][5]
m[1][5]
m[2][5]
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.

217

Fundamentos de informtica. Programacin en Lenguaje C

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++)
{

218

Captulo 8. Punteros.

printf("m[%hd][%hd] = ", i, j);


scanf("%lf",*(m + i) + j);

}
det = 0;
det += *(*(m + 0) + 0) * *(*(m + 1)
det += *(*(m + 0) + 1) * *(*(m + 1)
det += *(*(m + 0) + 2) * *(*(m + 1)
det -= *(*(m + 0) + 2) * *(*(m + 1)
det -= *(*(m + 0) + 1) * *(*(m + 1)
det -= *(*(m + 0) + 0) * *(*(m + 1)
printf("El determinante ... \n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n | ");
for(j = 0 ; j < 3 ; j++)
printf("%8.2lf",*(*(m +
printf(" | ");
}
printf("\n\n es ... %lf",det);

+
+
+
+
+
+

1)
2)
0)
1)
0)
2)

*
*
*
*
*
*

*(*(m
*(*(m
*(*(m
*(*(m
*(*(m
*(*(m

+
+
+
+
+
+

2)
2)
2)
2)
2)
2)

+
+
+
+
+
+

2);
0);
1);
0);
2);
1);

i) + j));

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;

219

Fundamentos de informtica. Programacin en Lenguaje C

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

= 1
= 6
= 11
= 16
= 21

local
local
local
local
local

= 2
= 7
= 12
= 17
= 22

local
local
local
local
local

= 3
= 8
= 13
= 18
= 23

local
local
local
local
local

= 4
= 9
= 14
= 19
= 24

local
local
local
local
local

=
=
=
=
=

5
10
15
20
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:

220

Captulo 8. Punteros.

local
local
local
local
local

=
=
=
=
=

1
1
1
1
1

local
local
local
local
local

=
=
=
=
=

2
2
2
2
2

local
local
local
local
local

=
=
=
=
=

3
3
3
3
3

local
local
local
local
local

=
=
=
=
=

4
4
4
4
4

local
local
local
local
local

=
=
=
=
=

5
5
5
5
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
for(cont = 0 ; cont < 3
{
printf("&c[%2d] =
printf("&i[%2d] =
printf("&f[%2d] =
}
}

de memoria son:\n");
; cont++)
%10p\t",cont,c + cont);
%10p\t",cont,i + cont);
%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] =

221

Fundamentos de informtica. Programacin en Lenguaje C

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
pc2
pi1
pi2
pf1
pf2

=
=
=
=
=
=

&c[0];
&c[19];
&i[0];
&i[19];
&f[0];
&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.

222

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

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

224

Captulo 9. Funciones.

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.

225

Fundamentos de informtica. Programacin en Lenguaje C

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.

226

Captulo 9. Funciones.

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

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

227

Fundamentos de informtica. Programacin en Lenguaje C

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

228

Captulo 9. Funciones.

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

229

Fundamentos de informtica. Programacin en Lenguaje C

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:

230

Captulo 9. Funciones.

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

231

Fundamentos de informtica. Programacin en Lenguaje C

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

232

Captulo 9. Funciones.

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

233

Fundamentos de informtica. Programacin en Lenguaje C

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.

234

Captulo 9. Funciones.

Declaracin: void mostrar(short);


Definicin:
void mostrar(short x)
{
printf(El valor recibido es %hd., x);
}
Llamada: mostrar(10);
que ofrece la siguiente salida por pantalla:
El valor recibido es 10.
Otro ejemplo: Una funcin que reciba un entero y devuelva el valor de
su cuadrado.
Declaracin: unsigned long int cuadrado(short);
Definicin:
unsigned long int cuadrado(short x)
{
return x * x;
}
Una posible llamada:
printf(El cuadrado de %hd es %ld.\n, a, cuadrado(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.

235

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
long Factorial(short);
void main(void)
{
short n;
printf("Introduzca el valor de n ... ");
scanf("%hd", &n);
printf("El factorial de %hd ",n);
printf("es %ld",Factorial(n));
}
long Factorial(short a)
{
long F = 1;
while(a) F *= a--;
return F;
}

236

Captulo 9. Funciones.

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, short , Rn , Vn >

La variable, de tipo short, n se almacena en la direccin de memoria Rn


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, short , Ra , Vn >

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:
#include <stdio.h>
long euclides(long, long);
void main(void)
{

237

Fundamentos de informtica. Programacin en Lenguaje C

long n1, n2;


do
{
printf("Introduzca el valor de n1 ... ");
scanf("%ld", &n1);
printf("Introduzca el valor de n2 ... ");
scanf("%ld", &n2);
if(n2 != 0)
printf("\nEl mcd de %ld y %ld ,n1, n2);
printf(es %ld\n", euclides(n1,n2));
}while(n2 != 0);
}
long euclides(long a, long b)
{
static short cont = 0;
long mcd;
while(b)
{
mcd = b;
b = a % b;
a = mcd;
}
printf("Invocaciones a la funcin ... %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.

238

Captulo 9. Funciones.

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:
n ! = n (n 1)!

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 Factorial(short A)
{
if(A == 0) return 1;
else return a * Factorial(A - 1);
}
Que tambin podra haberse escrito de la siguiente forma:
long Factorial(short A)
{
return A ? A * Factorial(A - 1) : 1;
}

239

Fundamentos de informtica. Programacin en Lenguaje C

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.
printf(El factorial de %hd es %ld.\n,3, Factorial(3));
Primera llamada: se crea la variable < A, short , R1 , 3 > . 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 R1 necesitamos
recibir el valor de Factorial de (A 1).
Segunda llamada: se crea la variable < A, short , R2 , 2 > . 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 R2 ; la ubicada en R1 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
R2 . Como esta variable A no vale 0, entonces la funcin devuelve el

valor del producto de A (la de R2 ) por Factorial(A 1). Y de nuevo,


antes de terminar la ejecucin de la funcin Factorial y eliminar la
variable A localizada en R2 necesitamos recibir el valor de Factorial(A
1).
Tercera llamada: se crea la variable < A, short , R3 ,1 > . 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 R3 ; las ubicadas en R1 y R2 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

240

Captulo 9. Funciones.

ubicada en R3 . Como esta variable A no vale 0, entonces la funcin


devuelve el valor del producto de A (la de R3 ) por Factorial(A 1). Y de
nuevo, antes de terminar la ejecucin de la funcin Factorial y eliminar
la variable A localizada en R3 necesitamos recibir el valor de Factorial(A
1).
Cuarta llamada: se crea la variable < A, short , R4 , 0 > . 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 R4 ; las ubicadas en R1 , R2 y R3 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 R4 . 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 R4 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 R3 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 R2 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

241

Fundamentos de informtica. Programacin en Lenguaje C

en R1 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 factorial 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 euclides(long a, long b)
{
return b ? euclides(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 a1 y b1
(diremos mcd(a1 , b1 ) ), donde b1 0 es igual a mcd(a2 , b2 ) donde
a2 = b1 y donde b2 = a1 %b1 , entendiendo por a1 %b1 el resto de la

divisin de a1 con b1 . Y el proceso puede seguirse hasta llegar a unos


valores de ai y de bi que verifiquen que ai 0 , bi 0 y ai %bi = 0 .
Entonces, el algoritmo de Euclides afirma que, llegado a estos valores el
valor buscado es mcd(a1 , b1 ) = bi . Y eso es precisamente lo que se ha
implementado en esta funcin.

242

Captulo 9. Funciones.

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

243

Fundamentos de informtica. Programacin en Lenguaje C

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 intercambio(long*,long*);
Y su definicin podra ser la siguiente:
void intercambio(long*a,long*b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
O tambin
void intercambio(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:
intercambio(&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.

244

Captulo 9. Funciones.

En la funcin llamante tenemos:


< x, short , Rx , Vx > y < y , short , Ry , Vy >

En la funcin intercambio tenemos:


< a, short*, Ra , Rx > y < b, short*, Rb , Ry >

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 Rx ahora queda codificado el valor Vy ; y en Ry queda
codificado el valor Vx . 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.

245

Fundamentos de informtica. Programacin en Lenguaje C

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

246

Captulo 9. Funciones.

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 extremos(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 extremos(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.

247

Fundamentos de informtica. Programacin en Lenguaje C

Para llamar a esta funcin bastar la siguiente sentencia:


extremos(vector, dimension, &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 (n 1) / 2 . Y el argumento
segundo deber ser, en lugar de la dimensin del vector, el valor
indicado (n 1) / 2 .
El prototipo de la funcin es exactamente el mismo que antes:
void extremos2(float*v, unsinged short d, float*M, float*m);

Y su definicin podra ser la siguiente:


void extremos2(float*v,
{
short int i;
*M = *v;
*m = *v;
for(i = 0 ; i < d
{
if(*M < *(v
if(*M < *(v
if(*m > *(v
if(*M < *(v
}
}

unsigned short d, float*M, float*m)

; i++)
+
+
-

i))
i))
i))
i))

*M
*M
*m
*M

=
=
=
=

*(v
*(v
*(v
*(v

+
+
-

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

248

Captulo 9. Funciones.

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.

249

Fundamentos de informtica. Programacin en Lenguaje C

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

#include <stdio.h>
// Declaracin de las funciones ...
short mcd(short, short);
long mcm(short, short);
// Funcin principal...
void main(void)
{
short a, b;
printf("Introduzca el valor de a ... ");
scanf("%hd",&a);
printf("Introduzca el valor de b ... ");
scanf("%hd",&b);
printf("El mximo comn divisor de %hd y %hd ", a, b);
printf("es %hd", mcd(a,b));
printf("\ny el mnimo comn mltiplo %ld.", mcm(a,b));
flushall();
getchar();
}

250

Captulo 9. Funciones.

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin clculo del mximo comn divisor. ------------- */
short mcd(short a, short b)
{
short m;
while(b)
{
m = a % b;
a = b;
b = m;
}
return a;
}

/* Funcin clculo del mnimo comn mltiplo. ------------ */


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.

#include <stdio.h>
#include <conio.h>
unsigned long fibonacci(short);
void main(void)
{
short N;
printf("Indique el trmino de la serie: ");

251

Fundamentos de informtica. Programacin en Lenguaje C

scanf("%hd", &N);
printf("\nEl trmino %hd es %lu.", N, fibonacci(N));
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin Fibonacci. ------------------------------------ */
unsigned long fibonacci(short x)
{
unsigned long fib1 = 1 , fib2 = 1, Fib = 1;
while(x > 2)
{
Fib = fib1 + fib2;
fib1 = fib2;
fib2 = Fib;
x--;
}
return Fib;
}

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.

#include <stdio.h>
// Declaracin de la funsin Factorial ...
unsigned long factorial(short);
// Funcin principal ...
void main(void)
{
short N;
printf("Indique un entero menor que 13: ");
scanf("%hd", &N);
printf("\nEl factorial de %hd, N);
printf( es %lu.", factorial(N));
}
/* ------------------------------------------------------- */

252

Captulo 9. Funciones.

/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin Factorial. ------------------------------------ */
unsigned long factorial(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 recoja 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:
#include <stdio.h>
#include <stdlib.h>
#define TAM 100
// Declaracin de las funciones ...
void ordenar(long *, short);
void mostrar(long *, short);
// Funcin principal ...
void main(void)
{
long vector[TAM];
randomize();
for(int i = 0 ; i < TAM ; i++)
vector[i] = random(1000);
mostrar(vector, TAM); // Antes de ordenar.
ordenar(vector, TAM); // Se ordena el vector.
mostrar(vector, TAM); // Despes de ordenar.
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */

253

Fundamentos de informtica. Programacin en Lenguaje C

/* Funcin de ordenacin. -------------------------------- */


void ordenar(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);
}
}
/* Funcin que muestra el vector que recibe como parmetro.*/
void mostrar(long *v , short d)
{
printf("\n\n");
for(short i = 0 ; i < d ; i++)
printf("%5ld", *(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].

254

Captulo 9. Funciones.

58.

Escriba una aplicacin que reciba un entero y busque todos sus


divisores, dejndolos 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 dejar 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.

#include <stdio.h>
#define TAM 100
// Declaracin de las funciones ...
short divisores(long, long*, short);
void mostrar(long *, short);
// Funcin principal ...
void main(void)
{
long N, vector[TAM];
short div;
printf("Introduzca un entero ... ");
scanf("%ld",&N);
if((div = divisores(N,vector, TAM)) > 0)
mostrar(vector, div);
else
printf("No se ha podido realizar la operacin");
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */

255

Fundamentos de informtica. Programacin en Lenguaje C

/* Funcin de bsqueda de divisores. --------------------- */


short divisores(long N, long *v , short d)
{
short cont = 1;
v[0] = 1;
for(int div = 2 ; div <= N / 2 ; div++)
if(N % div == 0)
{
v[cont] = div;
cont++;
// Si no caben ms divisores, se aborta la operacin.
if(cont >= d) return -1;
}
v[cont] = N;
return cont + 1;
}
/* Funcin que muestra el vector que recibe como parmetro.*/
void mostrar(long *v , short d)
{
printf("\n\n");
for(short i = 0 ; i < d ; i++)
printf("%5ld", *(v + i));
}

59.

Escriba una funcin, y un programa que le use, que calcule el


mximo comn divisor de un conjunto 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.

#include <stdio.h>
#define TAM 100
// Declaracin de funciones ...
long mcd(long, long);
long MCD(long*,short);
// Funcin principal ...
void main(void)
{
long numeros[100];
long m;
short i;

256

Captulo 9. Funciones.

// Introduccin de valores ...


printf("Introduzca valores.");
printf("Al terminar introduzca en valor cero.");
for(i = 0 ; i < TAM ; i++)
{
printf("\n\nnumeros[%3hu] -> ",i);
scanf("%lu",numeros + i);
if(*(numeros + i) == 0) break;
}
// Clculo del mximo comn divisor ...
m = MCD(numeros,i);
printf("El maximo comun divisor es ... %lu",m);
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
// Funcin clculo mcd de dos enteros ...
long mcd(long a, long b) {
return b ? mcd(b,a % b) : a; }
// Funcin clculo mcd de varios enteros ...
long MCD(long*n,short d)
{
short i;
long m;
// Si algn entero es 0, el mcd lo ponemos a cero.
for(i = 0 ; i < d ; i++)
if(!*(n + i)) return 0;
// Si solo hay un entero, el mcd es ese entero.
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

257

entre

dos

enteros

Fundamentos de informtica. Programacin en Lenguaje C

introducidos por el usuario.

#include <stdio.h>
#include <conio.h>
// Declaracin de la funcin perfecto ...
short perfecto(long);
// Funcin principal ...
void main(void)
{
long a, b;
printf("Limite inferior ... ");
scanf("%ld",&a);
printf("Limite superior ... ");
scanf("%ld",&b);
for(long i = a ; i <= b; i++)
if(perfecto(i) == 1) printf("%6ld", i);
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
// Funcin perfecto ...
short perfecto(long x)
{
long suma = 1;
for(long div = 2 ; div <= x / 2 ; div++)
if(x % div == 0) suma += div;
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 jugar, y que el
programa

muestre

por

pantalla

todos

los

movimientos

necesarios para trasladoar todos los aros del primer al tercer


soporte o varilla.

258

Captulo 9. Funciones.

(Consultar Fundamentos de Informtica. Codificacin y algoritmia,


Captulo 6 sobre Recursividad.)

#include <stdio.h>
// Declaracin de la funcin Hanoi ...
void Hanoi(short, short, short, short);
// Funcin principal ...
void main(void)
{
short discos;
printf("Indique con cuntos discos desea jugar: ");
scanf("%hd",&discos);
Hanoi(1, discos, 1, 3);
}
/* ------------------------------------------------------- */
/* Funcin Hanoi.
*/
/* ------------------------------------------------------- */
void Hanoi(short D1, short D2, short i, short j)
{
if(D1 == D2)
printf("Disco %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 XIII 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

259

Fundamentos de informtica. Programacin en Lenguaje C

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
ejemplo:
D La suma de los n primeros trminos verifica que:
n

f
i =1

= fn+2 1

D La suma de los n primeros trminos pares verifica que:


n

f
i =1

2i

= f2n+1 1

D La suma de los n primeros trminos impares verifica que:


n

f
i =1

2i 1

= f2n

D La suma de los cuadrados de los n primeros trminos

verifica que:
n

f
i =1

= fn fn+1

D Si n es divisible por m, entonces fn es divisible por fm .


D Cualesquiera dos elementos consecutivos de la serie de

Fibonacci son primos entre s.


D El cociente de dos nmeros consecutivos de la serie se

aproxima al nmero ureo: fn+1 fn cuando n .


Contine el programa anterior de forma que el usuario pueda
solicitar mediante un men de opciones, la comprobacin de

260

Captulo 9. Funciones.

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 SERIE DE FIBONACCI.
*/
/* ======================================================= */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define RANGO 40
// Declaracin de las funciones...
char menu(void);
void fib01(unsigned long*);
void fib02(unsigned long*);
void fib03(unsigned long*);
void fib04(unsigned long*);
void fib05(unsigned long*);
void fib06(unsigned long*);
long mcd(long, long);
void fib07(unsigned long*);
void fib08(unsigned long*);
// Funcin principal...
void main(void)
{
unsigned long fib[RANGO + 1];
// No utilizaremos el elemento 0.
unsigned short i;
char opcion;
fib[1] = 1;

261

Fundamentos de informtica. Programacin en Lenguaje C

fib[2] = 1;
// Serie de fibonacci...
for(i = 3 ; i <= RANGO ; i++)
fib[i] = fib[i - 1] + fib[i - 2];
do
{
// Men de opciones ...
opcion = menu();
switch(opcion)
{
case '1': fib01(fib); break;
case '2': fib02(fib); break;
case '3': fib03(fib); break;
case '4': fib04(fib); break;
case '5': fib05(fib); break;
case '6': fib06(fib); break;
case '7': fib07(fib); break;
case '8': fib08(fib); break;
case '0': printf("\nFin del programa");
printf("\nPulse tecla para terminar");
flushall();
getchar();
break;
default: printf("opcin no definida ... ");
}
}while(opcion != '0');
}

/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin que muestra el men de opciones
char menu(void)
{
char opcion;
clrscr();
printf("\n\n\tPROPIEDADES
printf("\n\n\t0. Salir de
printf("\n\t1. La suma de
printf("\n\t
es igual a

DE LA SERIE DE FIBONACCI");
programa.");
los n primeros terminos");
f[n + 2] - 1.");

printf("\n\t2. La suma de los n primeros


terminos pares (de 2 a 2*n)");
printf("\n\t
es igual a f[2 * n + 1] - 1.");
printf("\n\t3. La suma de los n primeros

262

*/

Captulo 9. Funciones.

terminos impares (de 1 a 2*n-1)");


printf("\n\t
es igual a f[n + 2].");
printf("\n\t4. La suma de los cuadrados
de los n primeros terminos");
printf("\n\t
es igual a f[n] * f[n + 1].");
printf("\n\t5. Si n es divisible por m,
entonces f[n]");
printf("\n\t
es divisible por f[m].");
printf("\n\t6. Cualquier par consecutivo
de fibonacci");
printf("\n\t
son primos entre si.");
printf("\n\t7. El cociente de dos elementos
consecutivos de la serie");
printf("\n\t
se aproxima al numero aureo.");
printf("\n\t8. Mostrar los 40 primeros
elementos de la serie de Fibonacci.");
printf("\n\n\n\tElija una opcion ... ");
do
opcion = getchar();
while(opcion < '0' || opcion > '8');
return opcion;
}
// Funcin fib01
void fib01(unsigned long*f)
{
unsigned short n, i;
unsigned long S = 0;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 1.");
printf("\n\n\tLa suma de los n primeros terminos");
printf("\n\tes igual a f[n + 2] - 1.");
printf("\n\nIndique el indice n que quiere
verificar (entre 1 y 38) ... ");
scanf("%hu",&n);
if(n > RANGO - 2)
printf("No se disponen de suficientes
elementos.");
else if(n == 0) printf("No valido");
else
{
for(i = 1 ; i <= n ; i++) S += *(f + i);

263

Fundamentos de informtica. Programacin en Lenguaje C

printf("\n\nSUMA f[ 1]... f[%2hu] = %lu\n",n,S);


printf("f[%hu]-1 = %lu\n",n + 2,*(f + n + 2)-1);
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib02
void fib02(unsigned long*f)
{
unsigned short n, i;
unsigned long S = 0;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 2.");
printf("\n\tLa suma de los n primeros
terminos pares (2, 4, ..., 2 * n)");
printf("\n\tpares es igual a f[2 * n + 1] - 1.");
printf("\n\nIndique el indice n que quiere
verificar (entre 1 y 19) ... ");
scanf("%hu",&n);
// n no puede ser tal que 2 * n + 1 sea mayor que RANGO...
if(n > (RANGO - 1) / 2)
printf("No se disponen de suficientes
elementos.");
else if(n == 0) printf("No valido");
else
{
for(i = 2 ; i <= 2 * n ; i += 2) S += *(f + i);
// Mostramos resultados...
printf("\n\nSUMA f[ 2]... f[%2hu] =
%lu\n",2 * n,S);
printf("f[%hu] - 1 = %lu\n",
2 * n + 1,*(f + 2 * n + 1) - 1);
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib03
void fib03(unsigned long*f)
{
unsigned short n, i;
unsigned long S = 0;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 2.");

264

Captulo 9. Funciones.

printf("\n\tLa suma de los n primeros terminos


impares (de 1 a 2*n-1)");
printf("\n\tes igual a f[n + 2].");
printf("\n\nIndique el indice n que quiere
verificar (entre 1 y 20) ... ");
scanf("%hu",&n);
// n no puede ser tal que 2 * n - 1 sea mayor que RANGO...
if(n > (RANGO + 1) / 2)
printf("No se disponen de suficientes
elementos.");
else if(n == 0) printf("No valido");
else
{
for(i = 1 ; i <= 2 * n - 1 ; i += 2)
S += *(f + i);
// Mostramos resultados...
printf("\n\nSUMA f[ 2]... f[%2hu] = %lu\n",
2 * n,S);
printf("f[%hu] = %lu\n",2 * n,*(f + 2 * n));
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib04
void fib04(unsigned long*f)
{
unsigned short n, i;
unsigned long S = 0;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 3.");
printf("\n\tLa suma de los cuadrados de los
n primeros terminos");
printf("\n\tes igual a f[n] * f[n + 1].");
printf("\n\nIndique el indice n que quiere verificar
(entre 1 y 39) ... ");
scanf("%hu",&n);
if(n > RANGO - 1)
printf("No se disponen de suficientes
elementos.");
else if(n == 0) printf("No valido");
else
{
for(i = 1 ; i <= n ; i++)
S += *(f + i) * *(f + i);

265

Fundamentos de informtica. Programacin en Lenguaje C

printf("\n\nSUMA f[ 1]^2... f[%2hu]^2 = %lu\n",


n,S);
printf("f[%hu] * f[%hu] = %lu\n",
n,n + 1,*(f + n) * *(f + n + 1));
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib05
void fib05(unsigned long*f)
{
unsigned short n, m;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 5.");
printf("\n\tSi n es divisible por m, entonces f[n]");
printf("\n\tes divisible por f[m].\n\n");
for(n = 3 ; n < RANGO / 2 ; n++)
// Todos los mltiplos de i ...
{
/* Comenzamos con n = 3, porque f[2] = 1, y todos sern
mltiplos de 1. */
printf("\n\n** n = %2hu\tf[%2hu] = %10lu ** \n",
n,n,*(f + n));
for(m = 2 * n ; m < RANGO ; m += n)
{
printf("
m = %2hu\tf[%2hu] = %10lu\t",
m, m, *(f + m));
printf("f[%2hu] %% f[%2hu] = %lu",
m,n,*(f + m) % *(f + n));
if(*(f + m) % *(f + n) == 0)
printf("\t[DIVISIBLE]\n");
}
printf("\n\n\tPulse una tecla para ver siguiente
serie de multiplos ... ");
getchar();
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib06
void fib06(unsigned long*f)
{
unsigned short n;
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 6.");

266

Captulo 9. Funciones.

printf("\n\tCualquier par consecutivo de fibonacci");


printf("\n\tson primos entre si.\n\n");
for(n = 3 ; n < RANGO ; n++)
{
printf("f[%2hu] = %10lu\t",n,*(f + n));
printf("f[%2hu] = %10lu\t",n + 1, *(f + n + 1));
printf("MCD = %lu\t",mcd(*(f + n),*(f + n + 1)));
if(mcd(*(f + n),*(f + n + 1)) == 1)
printf("[COPRIMOS]\n");
}
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin mcd, definida para uso de fib06
long mcd(long a, long b)
{
return b ? mcd(b,a % b) : a;
}
// Funcin fib07
void fib07(unsigned long*f)
{
unsigned short n;
const double oro = (1 + sqrt(5)) / 2;
char blancos[] = "

";

clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 7.");
printf("\n\tEl cociente de dos elementos
consecutivos de la serie");
printf("\n\tse aproxima al numero aureo.\n\n");
for(n = 1 ; n < RANGO ; n++)
{
printf("f[%2hu] = %10lu\t",n,*(f + n));
printf("f[%2hu] = %10lu\t",n + 1, *(f + n + 1));
printf("COCIENTE = %20.18lf\n"
(double)*(f + n + 1) / *(f + n));
}
printf("\n%sNumero de oro (1+sqrt(5)) / 2 -> %20.18lf",
blancos,oro);
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib08
void fib08(unsigned long*f)
{
unsigned short n;

267

Fundamentos de informtica. Programacin en Lenguaje C

clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 8.");
printf("\n\tMostrar los 40 primeros elementos
de la serie de Fibonacci.\n\n");
for(n = 1 ; n <= RANGO ; n++)
printf("f[%2hu] = %10lu%s",
n,*(f + n),n % 2 ? "\t" : "\n");
printf("\n\nPulse intro para volver a menu principal");
getchar();
}

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

268

Captulo 9. Funciones.

que si no hay unidades, entonces las decenas no aaden, al final, la


cpula y.
#include <stdio.h>
#include <string.h>
// Declaracin de las funciones
char* unidades(unsigned short, unsigned short);
char* decenas(unsigned short, unsigned short);
char* centenas(unsigned short,unsigned short,unsigned short);
/Funcin principal
void main(void)
{
char leido[200], leidoM[200];
unsigned long int n, naux;
unsigned short C, D, U, c, d, u;
do
{
// Inicializamos las cadenas de caracteres.
leido[0] = '\0';
leidoM[0] = '\0';
printf("\n\n\nIntroduzca entero a ... ");
scanf("%lu",&naux);
// Determinamos unidades, decenas, centenas...
// y unidades, decenas y centenas de mil
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)
{
// Sin el nmero es mayor que 999:
strcat(leidoM,centenas(C,D,U));
strcat(leidoM,decenas(U,D));
strcat(leidoM,unidades(U,D));
strcat(leidoM, "mil ");
}
strcat(leido,centenas(c,d,u));
strcat(leido,decenas(u,d));

269

Fundamentos de informtica. Programacin en Lenguaje C

strcat(leido,unidades(u,d));
strcat(leidoM,leido);
printf("El numero %lu se lee ... ",n);
printf("\n%s",leidoM);
}while(n); // Termina la aplicacin cuando el usuario
// quiera leer el nmero cero.
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin para las unidades. ---------------------------- */
char* unidades(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:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
}

return
return
return
return
return
return
return
return
return
return

"";
"uno ";
"dos ";
"tres ";
"cuatro ";
"cinco ";
"seis ";
"siete ";
"ocho ";
"nueve ";

}
/* Funcin para las 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 "diez ";
case 1:
return "once ";
case 2:
return "doce ";
case 3:
return "trece ";
case 4:
return "catorce ";
case 5:
return "quince ";
}
switch(d)
{

270

Captulo 9. Funciones.

case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:

return "";
return "dieci";
if(u) return "veinti";
else return "veinte";
if(u) return "treinta y ";
else return "treinta ";
if(u) return "cuarenta y ";
else return "cuarenta ";
if(u) return "cincuenta y ";
else return "cincuenta";
if(u) return "sesenta y ";
else return "sesenta ";
if(u) return "setenta y ";
else return "setenta ";
if(u) return "ochenta y ";
else return "ochenta ";
if(u) return "noventa y ";
else return "noventa ";

}
}
/* Funcin para las centenas. ---------------------------- */
char* centenas(unsigned short c,unsigned short d,
unsigned short u)
{
switch(c)
{
case 0:
return "";
case 1:
if(!u && !d) return "cien ";
else return "ciento ";
case 2:
return "doscientos ";
case 3:
return "tresientos ";
case 4:
return "cuatrocientos ";
case 5:
return "qinientos ";
case 6:
return "seiscientos ";
case 7:
return "setecientos ";
case 8:
return "ochocientos ";
case 9:
return "novecientos ";
}
}

271

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.

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.

275

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

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

278

Captulo 10. Asignacin dinmica de memoria.

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

279

Fundamentos de informtica. Programacin en Lenguaje C

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

280

Captulo 10. Asignacin dinmica de memoria.

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

281

Fundamentos de informtica. Programacin en Lenguaje C

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

282

Captulo 10. Asignacin dinmica de memoria.

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

283

Fundamentos de informtica. Programacin en Lenguaje C

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.

284

Captulo 10. Asignacin dinmica de memoria.

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

285

Fundamentos de informtica. Programacin en Lenguaje C

}
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

286

Captulo 10. Asignacin dinmica de memoria.

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.

287

Fundamentos de informtica. Programacin en Lenguaje C

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

288

Captulo 10. Asignacin dinmica de memoria.

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

289

Fundamentos de informtica. Programacin en Lenguaje C

// 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++)
{

290

Captulo 10. Asignacin dinmica de memoria.

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

291

Fundamentos de informtica. Programacin en Lenguaje C

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

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

292

Captulo 10. Asignacin dinmica de memoria.

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

unsigned long int uli;


unsigned short int usi;
signed long int sli;
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);

293

Fundamentos de informtica. Programacin en Lenguaje C

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

294

Captulo 10. Asignacin dinmica de memoria.

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

295

Fundamentos de informtica. Programacin en Lenguaje C

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

296

Captulo 10. Asignacin dinmica de memoria.

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

297

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

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

300

Captulo 11. Algunos usos con funciones.

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:

301

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
float
float
float
float

sum(float,
res(float,
pro(float,
div(float,

float);
float);
float);
float);

void main(void)
{
float a, b;
unsigned char op;
float (*operacion)(float, float);
printf("Primer operador ... ");
scanf("%f",&a);
printf("Segundo operador ... ");
scanf("%f",&b);
printf("Operacin ( + , - , * , / ) ... ");
do
op = getchar();
while(op !='+' && op !='-' && op !='*' && op !='/');
switch(op)

302

Captulo 11. Algunos usos con funciones.

{
case
case
case
case
}

'+':
'-':
'*':
'/':

operacion
operacion
operacion
operacion

=
=
=
=

sum;
res;
pro;
div;

break;
break;
break;

printf("\n%f %c %f = %f",a, op, b, (*operacion)(a, b));


}
float sum(float x, float y)
{ return x + y; }
float res(float x, float y)
{ return x - y; }
float pro(float x, float y)
{ return x * y; }
float div(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.

303

Fundamentos de informtica. Programacin en Lenguaje C

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(*operacion[4])(float,float)= {sum,res,pro,div};
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 '+':
case '-':
case '*':
case '/':

i
i
i
i

=
=
=
=

0;
1;
2;
3;

break;
break;
break;

304

Captulo 11. Algunos usos con funciones.

}
Y ahora la ejecucin de la funcin ser como sigue:
printf(\n\n%f %c %f = %f, a, op, b, (*operacin[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.)

305

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#include <conio.h>
float sum(float, float);
float res(float, float);
float pro(float, float);
float div(float, float);
void mostrar(float, char, float, float(*f)(float, float));
void main(void)
{
float a, b;
unsigned char op;
float (*operacion[4])(float, float) ={sum,res,pro,div};
do
{
printf("\n\nPrimer operador ... ");
scanf("%f",&a);
printf("Segundo operador ... ");
scanf("%f",&b);
printf("Operacin ... \n)");
printf("\n\n1. Suma\n2. Resta);
printf(\n3. Producto\n4. Cociente");
printf("\n\n\tSu opcin (1 , 2 , 3 , 4) ... ");

306

Captulo 11. Algunos usos con funciones.

do
op = getche();
while(op - '0' < 1 || op - '0' > 4 );
mostrar(a,op,b,operacion[(short)(op - '1')]);
printf("\n\nOtra operacin (s / n) ... ");
do
op = getche();
while(op != 's' && op != 'n');
}while(op == 's');
}
float sum(float x, float y)
{ return x + y; }
float res(float x, float y)
{ return x - y; }
float pro(float x, float y)
{ return x * y; }
float div(float x, float y)
{ return y ? x / y : 0; }
void mostrar(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 = '/';
printf("\n\n%f %c %f = ", x, c, y);
printf("%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.

307

Fundamentos de informtica. Programacin en Lenguaje C

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:
mostrar(a,op,b,operacion[(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

308

Captulo 11. Algunos usos con funciones.

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

309

Fundamentos de informtica. Programacin en Lenguaje C

Es una funcin que devuelve un entero aleatorio entre 0 y (num 1) . 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:
#include <stdio.h>
#include <stdlib.h>
#define TAM 10
#define RANGO 1000
int ordenar(void*,void*);
void main(void)
{
long numeros[TAM];
long i;
randomize();
for(i = 0 ; i < TAM ; i++)
numeros[i] = random(RANGO);
// Vamos a ordenar esos numeros ...
qsort((void*)numeros, TAM, sizeof(long), ordenar);
// Mostramos resultados
for(i = 0 ; i < TAM ; i++)
printf("numeros[%4ld] = %ld\n", i, numeros[i]);
}

310

Captulo 11. Algunos usos con funciones.

// La funcin de ordenacin ...


int ordenar(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.

311

Fundamentos de informtica. Programacin en Lenguaje C

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:
time_t t1, t2;
t1 = clock();

312

Captulo 11. Algunos usos con funciones.

(proceso a estudiar su tiempo)


t2 = clock();
printf(Intervalo transcurrido: %ld, t2 t1);
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:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TAM 100000
#define RANGO 10000
int cambiar(long*, long*);
void main(void)
{
long numeros[TAM];
long i, j;
time_t t1, t2;
randomize();
for(i = 0 ; i < TAM ; i++)
numeros[i] = random(RANGO);
// Vamos a ordenar esos numeros ...
// Mtodo de la burbuja ...
t1 = clock();
for( i = 0 ; i < TAM ; i++)
for(j = i ; j < TAM ; j++)
if(numeros[i] > numeros[j])
cambiar(numeros + i, numeros + j);
t2 = clock();
printf("t2 - t1 = %ld.\n", t2 - t1);
}
int cambiar(long *a, long *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}

313

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define TAM 100000
#define RANGO 10000
int ordenar(void*,void*);
void main(void)
{
long numeros[TAM];
long i, j;
time_t t1, t2;
randomize();
for(i = 0 ; i < TAM ; i++)
numeros[i] = random(RANGO);
// Vamos a ordenar esos numeros ...
// Mediante la funcin qsort ...
t1 = clock();
qsort((void*)numeros, TAM, sizeof(long), ordenar);
t2 = clock();
printf("t2 - t1 = %ld.", t2 - t1);
}
int ordenar(void *a, void *b)
{
if(*(long*)a > * (long*)b)
return 1;
else if(*(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.

314

Captulo 11. Algunos usos con funciones.

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:
#include <stdio.h>
// Definicin de la macro ...
#define cuadrado(x) x * x
void main(void)
{
short a;
unsigned long b;
printf("Intoduzca el valor de a ... ");
scanf("%hd",&a);
printf("El cuadrado de %hd es %lu", a, cuadrado(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

315

Fundamentos de informtica. Programacin en Lenguaje C

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:
#define cuadrado(x) x * x
#define circulo(r) 3.141596 * cuadrado(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.

316

Captulo 11. Algunos usos con funciones.

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 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;
}
Ahora, con la macro, que hemos llamado __Criba, queda de la
siguiente forma:
#define __Criba(_num, _pr)
{
long _i, _j;
for(_i = 0 ; _i < MAX; _i++) _num[_i] = 'p';
for(_i = 2 ; _i < sqrt(MAX) ; _i++)
if(_num[_i] == 'p')
for(_j = 2 * _i ; _j < MAX ; _j += _i)
_num[_j] = 'c';
for(_i = 1 , _j = 0 ; _i < MAX ; _i++)
if(_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:

317

Fundamentos de informtica. Programacin en Lenguaje C

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

318

Captulo 11. Algunos usos con funciones.

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 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;
}
Y ahora, con la macro, que hemos llamado __TablaPrimos, queda de
la siguiente forma:
#define __TablaPrimos(_num, _primos)
{
long _i, _j;
for(_i = 1 , _j = 0 ; _i < MAX ; _i++)
if(_num[_i] == 'p')
{ *(_primos + _j) = _i;
_j++;}
*(_primos + _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

319

Fundamentos de informtica. Programacin en Lenguaje C

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:
printf(Aqu solo hay un parametro.);

// Un parmetro

printf(Aqu hay %ld parmetros., 2);

// Dos parmetros

printf(Y ahora %ld%c, 3, .);

// Tres parmetros

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.

320

Captulo 11. Algunos usos con funciones.

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.
tipo nombre_funcion (tipo_1,[..., tipo_N], ...)
{
va_list argumentos /* lista de argumentos */

321

Fundamentos de informtica. Programacin en Lenguaje C

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.

322

Captulo 11. Algunos usos con funciones.

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

323

Fundamentos de informtica. Programacin en Lenguaje C

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:
#include <stdio.h>
#include <stdarg.h>
double sum(long, ...);
void main(void)
{
double S;
S = sum(7, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
printf("%f",S);
}
double sum(long v,...)
{
double suma = 0;
long i;
va_list sumandos;
va_start(sumandos, v);
for(i = 0 ; i < v ; i++)
suma += va_arg(sumandos,double);
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.

324

Captulo 11. Algunos usos con funciones.

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.

325

Fundamentos de informtica. Programacin en Lenguaje C

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.

326

Captulo 11. Algunos usos con funciones.

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.
#include <stdio.h>
#include <string.h>
void main(int argc, char*argv[])
{
char nombre[30];
char edad[5];
char profesion[30];
nombre[0] = profesion[0] = edad[0] = '\0';
long i;
for(i = 0 ; i < argc ; i++)
{
if(strcmp(argv[i],"-n") == 0)
{
i++;
if(i < argc) strcpy(nombre, argv[i]);
}
else if(strcmp(argv[i],"-p") == 0)
{
i++;
if(i < argc) strcpy(profesion, argv[i]);
}
else if(strcmp(argv[i],"-e") == 0)
{
i++;
if(i < argc) strcpy(edad, argv[i]);
}
}
printf("Nombre: %s\n", nombre);
printf("Edad: %s\n", edad);
printf("Profesion: %s\n", profesion);
}
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

327

Fundamentos de informtica. Programacin en Lenguaje C

ejecutamos ese programa (supongamos que le hemos llamado datos)


con la siguiente lnea de comando:
datos p estudiante e 21 n Isabel
Aparecer por pantalla la siguiente informacin:
Nombre: Isabel
Edad: 21
Profesion: estudiante

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 ejemplo 11 t 13;
17 y 19, etc. (Sin resolver)

67.

Lea el siguiente cdigo y muestre la salida que ofrecer por


pantalla.

#include <stdio.h>
#include <math.h>
#define CUADRADO(x) x*x
#define SUMA(x,y) CUADRADO(x) + CUADRADO(y)
#define RECTO(x,y) sqrt(SUMA(x,y))
void main(void)
{
printf("%2.1f",RECTO(3,4));
}

328

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

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.

330

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

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

331

Fundamentos de informtica. Programacin en Lenguaje C

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

unsigned long int uli;


unsigned short int usi;
signed short int ssi;
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;

332

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

333

Fundamentos de informtica. Programacin en Lenguaje C

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;

334

Captulo 12. Estructuras estticas de datos y definicin de tipos.

};
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.

335

Fundamentos de informtica. Programacin en Lenguaje C

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.

336

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

337

\n");
");
");
");
");

Fundamentos de informtica. Programacin en Lenguaje C

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,

338

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

339

Fundamentos de informtica. Programacin en Lenguaje C

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

340

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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:

341

Fundamentos de informtica. Programacin en Lenguaje C

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

342

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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:

343

Fundamentos de informtica. Programacin en Lenguaje C

(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:

344

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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;
char ss[30];
}ident1;
typedef struct
{
long dni;
char ss[30];
}ident2;

// nmero de dni.
// nmero de la seg. social.

// nmero de dni.
// nmero de la seg. social.

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

345

Fundamentos de informtica. Programacin en Lenguaje C

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

346

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

347

Fundamentos de informtica. Programacin en Lenguaje C

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

348

Captulo 12. Estructuras estticas de datos y definicin de tipos.

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

349

Fundamentos de informtica. Programacin en Lenguaje C

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

350

Captulo 12. Estructuras estticas de datos y definicin de tipos.

}
}

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;

351

Fundamentos de informtica. Programacin en Lenguaje C

/* 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
for(c = n1->T - 1 ; c >= 0 ; c--)
{
if(*(n1->N + c) < *(n2->N + c))
return -1; /* Es decir, n1
if(*(n1->N + c) > *(n2->N + c))
return +1; /* Es decir, n1
}
return 0;
}

*/
*/
a n2->T... */

< n2. */
> n2. */

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.

352

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

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.

354

Captulo 13. Gestin de archivos.

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

355

Fundamentos de informtica. Programacin en Lenguaje C

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:

356

Captulo 13. Gestin de archivos.

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.

357

Fundamentos de informtica. Programacin en Lenguaje C

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.

358

Captulo 13. Gestin de archivos.

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.

359

Fundamentos de informtica. Programacin en Lenguaje C

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

independiente

permite
del

que

el

dispositivo

trabajo
final

de

fsico

acceso

al

archivo

sea

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

360

Captulo 13. Gestin de archivos.

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;

361

Fundamentos de informtica. Programacin en Lenguaje C

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.

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.

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.

362

Captulo 13. Gestin de archivos.

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

363

Fundamentos de informtica. Programacin en Lenguaje C

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:

364

Captulo 13. Gestin de archivos.

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

365

Fundamentos de informtica. Programacin en Lenguaje C

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

366

Captulo 13. Gestin de archivos.

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.

367

Fundamentos de informtica. Programacin en Lenguaje C

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)

368

Captulo 13. Gestin de archivos.

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

0000
0001
0002
0003
0004
0005
0006
0007
0008
0009

-->
-->
-->
-->
-->
-->
-->
-->
-->
-->

9.4667
30.4444
12.5821
0.2063
16.4545
28.7308
9.9574
0.1039
18.0000
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

369

Fundamentos de informtica. Programacin en Lenguaje C

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.

370

Captulo 13. Gestin de archivos.

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

371

Fundamentos de informtica. Programacin en Lenguaje C

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:

372

Captulo 13. Gestin de archivos.

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

373

Fundamentos de informtica. Programacin en Lenguaje C

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;

374

Captulo 13. Gestin de archivos.

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

375

Fundamentos de informtica. Programacin en Lenguaje C

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

376

Captulo 13. Gestin de archivos.

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,

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

377

Fundamentos de informtica. Programacin en Lenguaje C

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

70.

printf("\n\nTotal letras ... %ld",suma);

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;

378

Captulo 13. Gestin de archivos.

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)

379

Fundamentos de informtica. Programacin en Lenguaje C

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;

380

Captulo 13. Gestin de archivos.

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

381

Fundamentos de informtica. Programacin en Lenguaje C

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.

382

Das könnte Ihnen auch gefallen