Sei sulla pagina 1di 433

De C a C++

De la programacin procedural al objeto

Este libro se dirige a desarrolladores principiantes que desean dominar el diseo de algoritmos usando
el lenguaje C y el lenguaje C++. La eficacia y la complementariedad as como la generalidad de estos
lenguajes permitirn al lector adquirir una experiencia fundamental en la programacin informtica
para a continuacin evolucionar fcilmente con otros lenguajes de programacin. El autor ha logrado
no slo exponer el funcionamiento de las herramientas fundamentales de ambos lenguajes sino tambin
proporcionar los medios para ponerlos en prctica. De este modo, cada seccin terica va seguida de
una seccin de "puesta en prctica", compuesta de variados ejercicios.

El conjunto de fundamentos de programacin y de diseo algortmico se organiza a partir de la nocin


de estructuras de datos, siguiendo un recorrido que va de las ms simples a acabando con la ms
compleja: el objeto. Los cuatro primeros captulos estn dedicados al aprendizaje del lenguaje C y al
uso de las primeras tcnicas en algortmica, de las variables simples, pasando por tablas y las tuplas
hasta llegar a los punteros. A continuacin, siempre en lenguaje C, se tratan estructuras de datos
complejas: listas enlazadas dinmicas y estticas, as como pilas, colas y rboles, bsicamente rboles
binarios. Para ello, un captulo explica las claves de la recursividad, elemento imprescindible para la
implementacin de rboles en C y C++. Estas estructuras de datos se presentan entre el lenguaje C y el
lenguaje C++ para ilustrar el paso que pueden representar entre el mundo sin objetos del lenguaje C y
el mundo de los objetos de C++.

La ltima parte presenta las caractersticas y el paradigma orientado a objetos del lenguaje C++. Se
tratan todos los puntos importantes con pequeos programas de ejemplo. El objetivo es proporcionar al
lector un sendero consecuente hacia el mundo orientado a objetos y a otros lenguajes orientados a
objetos y darle las claves para pasar a Java o C#, por ejemplo.

Los captulos del libro:


Introduccin - Variables simples - Los controles de flujo - Variables de conjunto (tuplas y tablas) -
Variables puntero - Recursividad - Estructuras de datos y algoritmos - Variables objeto, descubrir C++ -
Anexo
Frdric DROUILLON
Frdric DROUILLON es profesor, investigador y diseador en el mundo de la informtica y de las
artes digitales. Trata la programacin como una tecnologa de expresin y de formulacin de ideas que
requiere investigacin y creatividad. El objetivo de sus lecciones, y por lo tanto de este libro, es
permitir al lector adquirir una experiencia fundamental en programacin para poder moverse lo ms
fcilmente posible en las diferentes tecnologas y lenguajes de programacin.

Objetivo: aprender a programar

El objetivo es aprender a programar, lo que significa dominar el diseo de algoritmos con al menos un
lenguaje de programacin. Hemos escogido los lenguajes C y C++ por su eficacia y su
complementariedad, pero tambin por su genericidad. En efecto, el objetivo es adquirir un
conocimiento fundamental en programacin que permitir al lector moverse con relativa facilidad en
otras tcnicas y lenguajes de programacin.
Contenido del curso

Es un recorrido por las estructuras de datos primitivas comunes en la prctica de la programacin


algortmica. Se estudian las estructuras de datos de cara a una evolucin en tres fases principales:

Los tipos primitivos nativos del lenguaje C, puestos en prctica con la escritura y la bsqueda de
algoritmos.
La exploracin de modelos de datos complejos (incluyendo rboles binarios), no nativos en el lenguaje
C, con la experimentacin de un amplio abanico de algoritmos para trabajar con ellos.
La introduccin a las nociones de clase y objeto y a los conceptos esenciales que intervienen en C++.

A quin va dirigido?

Actualmente este curso se da ntegramente en primer ciclo de Informtica. Cubre los tres primeros
semestres de la carrera. Los dos primeros semestres se dedican al dominio de los fundamentos bsicos
incluidos en el lenguaje C (captulos Variables simples, Control del flujo, Variables complejas
[estructuras y tablas], Variables puntero). El tercer semestre se dedica a la experimentacin con listas
(captulos Recursividad y Estructuras de datos y algoritmos) y a la introduccin a las nociones de clase
y objeto en el lenguaje C++ (captulo Variables objeto, descubriendo C++).

Estos tres grandes mdulos pueden asociarse y adaptarse segn convenga al lector. Por ejemplo, la
parte sobre C se ha utilizado en escuelas de ingenieros para asignaturas de programacin de nivel
bsico o avanzado. Tambin se ha utilizado en un mster multimedia para una formacin sobre la
algortmica con una reflexin acerca de la escritura y la creacin de software. La parte sobre C++
tambin se ha utilizado en otros mbitos, como por ejemplo en una asignatura de ltimo ao de carrera
o en un primer ao de mster en informtica. La experimentacin con estructuras de datos aparece
sobre todo como un mdulo de especializacin. Representan una cantidad de conocimientos de
informtica esencial, tiles cuando se involucran proyectos en C o C++.

Cmo aprender a programar?

Desde un punto de vista pedaggico, la respuesta en la que insistimos es: practicando. En este mbito,
el conocimiento terico requiere la experimentacin repetida mediante la realizacin de prcticas
distintas y variadas de distintos niveles. Con el tiempo, solo la prctica permite adquirir una intuicin
especfica que permite detectar o plantear problemas y, a continuacin, encontrar ms fcilmente la
solucin.

1. Comprender no es saber

Cuando se comienza a aprender un lenguaje de programacin, generalmente comprender no es


suficiente. Tambin hay que saber hacer, y comprender no es saber hacer. Por supuesto hay que saber,
es decir, conocer las herramientas disponibles y los conceptos bsicos asociados, pero tambin es
necesario ser capaz de ponerlos en prctica para escribir un programa. Escribir un programa requiere un
buen dominio de herramientas, pero tambin crear hiptesis de realizacin, es decir, poder identificar y
plantear los problemas subyacentes con objeto de ser capaz de crear soluciones para resolverlos. Se
trata de un proceso de elaboracin y de creacin que requiere investigacin y creatividad. Las
soluciones, a excepcin de lo disponible en libreras de clases o funciones, no existen. Encontrar la
solucin es a menudo crear la solucin.
Para ponerse en situacin, es similar a aprender a tocar un instrumento. Si alguien le explica cmo
tocar la guitarra, podr comprender las explicaciones, pero ello no le convertir en un guitarrista ni en
un compositor. Ser guitarrista requiere muchas horas de trabajo con su instrumento. Ser compositor y
tener ideas de canciones o de arreglos musicales tambin requiere muchas preguntas y grandes
cantidades de trabajo y dedicacin. Sucede lo mismo con la programacin y el diseo informtico.

Con el objetivo de adquirir este tacto, no solo hemos pensado en explicar el funcionamiento de las
herramientas bsicas del lenguaje, sino que tambin hemos pensado en proporcionarle los medios
necesarios para ponerlas en prctica. De este modo, cada seccin incorpora un apartado Puesta en
prctica. Son ejercicios que hemos intentado diversificar. Cada ejercicio es una invitacin. No se trata
necesariamente de responder a todo. Lo importante es plantearse la pregunta para iniciarse en la
aplicacin de los conocimientos adquiridos. Si una idea de programa le viene a la mente durante la
parte terica o durante la realizacin de un ejercicio, sobre todo implemntela!

2. Tres niveles de dificultad

Durante el aprendizaje, aparecen tres niveles de dificultad: el dominio de las herramientas, la


resolucin de problemas y el diseo de programas. En cada nivel, se alcanzan los objetivos especficos
con ejercicios variados, y repetidos para los puntos ms difciles.

a. Dominar las herramientas

Lo que llamamos herramientas son los conceptos bsicos de programacin y algortmica, tal y como se
implementan en el lenguaje C. No son muchos; sin entrar en detalles, hemos definido ocho en total. Se
trata de las variables simples, las operaciones, los saltos (if, else), las disyunciones (switch), los bucles
(while, do-while, for), las funciones, las variables complejas (estructuras y tablas), las variables
puntero, y esto es todo. Con ellos, se puede escribir lo que se desee, incluido un sistema operativo
como Unix (que es el origen del lenguaje C).

A este nivel de herramientas bsicas, proponemos los siguientes objetivos:

Comprender estas herramientas.


Conocer su funcionamiento y ser capaz de usarlas mentalmente para poderse anticipar a su
comportamiento en el programa.
Saber leer e interpretar el cdigo donde aparecen.
Saber escribir el cdigo para usarlas.

En este primer nivel, no hay realmente ningn tipo de diseo. Se trata, por ejemplo, de hacer un bucle
en un programa de prueba para ver cmo se desarrolla y comprender su funcionamiento.

b. Resolver un problema

En el siguiente nivel, hay que entrar en una dinmica de resolucin de problemas y de diseo
algortmico. Ya no bastar con comprender las herramientas. Hay que ser capaz de utilizarlas incluso
para hacer lo ms trivial. Ahora que tiene un martillo, un destornillador, una sierra, clavos, tornillos, un
lpiz, una regla y varias tablas, hay que hacer un mueble. Por ejemplo, un armario.

En este nivel de introduccin al diseo, establecemos los siguientes objetivos:


Adquirir la capacidad de leer y comprender un algoritmo.
Ser capaz de recurrir a una estructura de datos adecuada.
Ser capaz de inventar y escribir un algoritmo.

Todo lo que se escribe y que la mquina ejecuta requiere la elaboracin de un algoritmo, que es una
sucesin finita de operaciones cuyo objetivo es realizar una tarea. Es decir, una serie de instrucciones
para que la mquina haga algo. Cabe destacar que la algortmica da lugar a descubrimientos genuinos
y referencias cientficas. Hay una gran cantidad de algoritmos conocidos y algunos tienen el nombre de
sus autores; por ejemplo, el algoritmo de Bresenham para dibujar rectas en dispositivos de grficos
rasterizados. En este nivel la dificultad puede ser muy grande. Sin embargo, afortunadamente, hay
problemas muy accesibles y, segn nuestra filosofa, el lector har lo que buenamente pueda con los
desafos que se marque y los ejercicios elegidos.

c. Disear un programa

El ltimo nivel que afrontaremos es la composicin de un programa completo que comprenda varios
miles de lneas de cdigo y que rena varias resoluciones de enigmas, muchos algoritmos. Por ejemplo,
puede ser un videojuego clsico o cualquier otra creacin. Se trata de realizar un proyecto consecuente,
que permita:

Comprender un programa completo.


Saber disear un programa completo.

En este nivel, los programas pueden componerse de muchos archivos fuente y realizar llamadas a
muchas libreras. Surgen muchas cuestiones metodolgicas en la escritura de cdigo y en la gestin de
un proyecto que quizs se realice en equipo. Aparece una serie de etapas de trabajo. Estas estn
relacionadas con la idea inicial del programa real que funciona ms o menos sin errores y hace ms o
menos lo que se espera de l con mayor o menor satisfaccin del cliente. En esto consiste la direccin y
gestin de proyectos de TI. Nuestro objetivo en este caso no es entrar en este tipo de cuestiones, sino
que esperamos prepararle para comprender los fundamentos y los entresijos.

3. Un aprendizaje no lineal

De forma general, el aprendizaje no se reduce a una mecnica simple. La claridad va apareciendo poco
a poco, como si de una niebla que se va disipando se tratase. Al comienzo no se distingue nada y
progresivamente, a medida que se disipa la niebla, van apareciendo formas cada vez ms claras y
precisas. A veces, algunos se pierden al inicio como si estuvieran delante de una muro infranqueable.
Tal vez, puede ser la misma impresin que estar en un pas extranjero escuchando un idioma que no se
domina pero que se esfuerza en comprender. Por lo tanto, el aprendizaje no es frontal, directo y fcil de
planificar. Con el tiempo y la realizacin de ejercicios, los polos de la claridad, de la comprensin y de
la aplicacin del conocimiento se reafirman y se van conectando suavemente entre s. Y si bien
persisten las zonas grises, con la perseverancia siempre se termina llegando al final. Las fases de
adquisicin de competencias para disear e implementar un programa informtico dependen de las
personas y del trabajo alcanzado. El talento en este caso no tiene por qu ser brillante. Depende ms del
placer de programar, porque quien disfruta programando pasa ms tiempo que los dems dedicndose a
ello y mejora por ese mismo motivo.
Si le invade el desnimo, piense que es un universo finito. El nmero de herramientas bsicas es
limitado. Nunca aumentar. Es un detalle tranquilizador. Adems, el lenguaje C le brinda un pasaporte
prometedor al resto de los lenguajes. Este aprendizaje es una buena inversin.

Organizacin del libro

1. Captulos

La parte que concierne al aprendizaje del lenguaje C y las primeras herramientas en algortmica es la
que ms se ha desarrollado. Se explica en los primeros cuatro captulos.

El primer captulo permite dominar la escritura y la organizacin de instrucciones simples a partir de


variables bsicas y operaciones realizadas sobre ellas. En este nivel, son necesarios muchos detalles y
poco diseo es posible.

El segundo captulo introduce la nocin de bloque de instrucciones, es decir, un nivel superior de


organizacin de instruccin con las posibilidades bsicas de salto, disyunciones, bucles y escritura de
funciones. En este nivel, se pueden plantear problemas algortmicos y cuestionarse el estilo de la
escritura y el diseo de un programa.

El tercer captulo expone los tipos de datos ms potentes: las estructuras y las tablas. Estos nuevos tipos
de datos permiten acceder a niveles de diseo ms elaborados. Con ellos, es posible escribir programas
reales de varias lneas de cdigo. En este nivel, la dificultad radica en cuestiones de modelizacin, de
idea general, de investigacin y de seguimiento y gestin de proyectos. En el contexto de consecucin
de una licenciatura en informtica, este captulo termina el primer semestre de trabajo y viene
acompaado de la realizacin de un proyecto. En la mayora de los casos, se trata de la realizacin de
un videojuego en modo consola. Para ello, hay dos libreras de desarrollo de juegos en modo consola
en Windows que son accesibles por Internet. La primera la ha desarrollado un estudiante
particularmente avanzado, Julien Batonnet. La segunda se usa para garantizar el desarrollo de
proyectos interesantes. Esta librera se presenta en el anexo.

El cuarto captulo proporcionar sobre todo la potencia para programar usando punteros que permitan
la asignacin de memoria dinmica y la elaboracin de estructuras de datos no nativas en el lenguaje,
como las listas encadenadas. Dada la dificultad generalmente encontrada en este tema, se le dedica todo
el segundo semestre. Se le asocia un mdulo complementario de programacin grfica con la librera
ALLEGRO para poder realizar programas en modo grfico. Esta librera, con su documentacin, es
accesible en diferentes sitios web. El segundo semestre siempre termina con un nuevo proyecto en
modo grfico.

En estos cuatro primeros captulos que forman la primera parte, la mayora de los puntos importantes se
resumen con pequeos programas comentados llamados experimentos. Los experimentos pueden ser
muy prcticos para condensar el curso con el fin de, por ejemplo, presentarlo a un pblico ya formado
que tenga la necesidad de recordar todos los puntos importantes del lenguaje C.

La siguiente parte agrupa los captulos Recursividad y Estructuras de datos complejas y algoritmos y
trata principalmente de listas. El captulo Estructuras de datos presenta las listas encadenadas
dinmicas, las pilas y las colas, as como los rboles (esencialmente los rboles binarios). Para ello, el
captulo Recursividad proporciona las claves de la recursividad, elemento ineludible para la
implementacin de rboles en C y en C++. Estas estructuras de datos se presentan desde el punto de
vista de sus implementaciones en C y de su funcionamiento. A menudo, estos temas se tratan aparte
bajo el ttulo estructuras de datos y algoritmos. Hemos elegido presentarlas aqu entre el lenguaje C y
el lenguaje C++ porque estos lenguajes tienen un tipo particular de estructura de datos: estructuras de
datos que hacen llamadas a construcciones y tratamientos algortmicos, estructuras de datos que por s
mismas constituyen un pequeo programa y que se encuentran naturalmente en las libreras de clases
del mundo objeto... Por este motivo nos pareci interesante crear una pasarela entre el mundo sin
objetos de C y el mundo con objetos de C++ afirmando que ambos aspectos (saber utilizar, por un lado,
y saber implementar, por el otro) siguen siendo de actualidad.

La ltima parte se compone del captulo Variables objeto, descubriendo C++, que presenta las
funcionalidades y el paradigma de orientacin a objetos del lenguaje C++. Todos los puntos
importantes se tratan con pequeos programas de ilustracin. La idea es hacer un avance hacia el
mundo orientado a objetos y de otros lenguajes orientados a objetos. En general, al final del captulo no
debera haber problemas para pasar a Java o C#, por ejemplo.

2. Soluciones de los ejercicios

Las soluciones de los ejercicios no se incluyen en el libro, sino que pueden obtenerse desde el sitio web
de Ediciones ENI. De todos modos, no estn disponibles todas las soluciones debido al gran nmero de
ejercicios presentados. Algunos, en efecto, son sujetos de proyectos, y sera una lstima, llegado el
caso, no tenerlos. Pero para los ejercicios que tienen solucin, asegrese de que utiliza esta de forma
correcta. Porque lo principal es comenzar plantendose el problema. Sin problema no hay solucin, y
encontrarse una solucin sin que se haya planteado seriamente el problema, ni se haya buscado
activamente, no sirve de nada. Es peor que no tener las soluciones de ningn problema. La ilusin
ficticia de haber comprendido el problema puede hacer caer al lector en la creencia de que domina el
temario y puede que no sea as. Hay que enfrentarse al problema, buscar una solucin y adquirir las
habilidades necesarias para encontrarla por uno mismo. El diseador siempre termina adquiriendo la
tcnica necesaria para lograr lo que l mismo ha decidido llevar a cabo.

3. Anexos

En el anexo se encuentra una tabla sobre las prioridades de los operadores y sobre todo una
introduccin a la librera CREACO, publicada como complemento del libro en el sitio web de
Ediciones ENI o en la pgina http://fdrouillon.free.fr. Esta librera proporciona un cierto nmero de
funciones para realizar juegos en modo consola, es decir, con letras y diecisis colores. Actualmente, la
librera gestiona el ratn, el teclado, permite redimensionar la ventana y posibilita una visualizacin
extremadamente rpida para obtener animaciones fluidas. El desarrollo de esta librera es en cdigo
abierto (open source). Lo que se explica es tan solo la punta del iceberg de lo que puede llegar a hacer
y, adems, resulta muy interesante. No dude en completar su estudio.

La librera original es la obra del estudiante Julien Batonnet. Su librera, siempre en cdigo abierto, est
ms actualizada en la rama de desarrollo, tambin accesible por Internet en
http://libconlib.googlecode.com/svn/trunk/.

Entornos de desarrollo

Todo el cdigo de este libro es tericamente portable. Solo el uso de libreras especficas como
Windows.h puede suponer un problema. Por tanto, hay que encontrar una librera equivalente en Mac y
Linux. Pero, en lo que concierne a los lenguajes C y C++, es totalmente estndar y sigue las normas.

El trabajo ha sido realizado con el compilador Mingw32 y el IDE CodeBlocks. Este compilador y este
entorno de desarrollo son prcticos y fciles de instalar. Puede obtenerlos en
http://www.codeblocks.org/.

Tambin se ha utilizado el compilador Microsoft visual C++; especialmente cuando se ha necesitado un


depurador ms potente. Microsoft publica versiones gratuitas de estas herramientas.

Agradecimientos

Antes de finalizar, querra dar las gracias en particular a todas las personas que me han dado la
posibilidad de ensear. Mis agradecimientos ms sinceros son, por tanto, para la Universidad Pars 8
Vincennes Saint-Denis, y para Ghislaine Azmard y Pierre Audibert, del UFR MITSIC (Matemticas,
Informtica, Tecnologas, Ciencias de la Informacin y de la Comunicacin).

Quiero mostrar mi ms sincero agradecimiento a Frdric Ravaut, profesor e investigador y jefe de


estudios de Informtica, y a Jean-Pierre Segado, profesor adjunto responsable de estudios de
Informtica del ciclo L, ambos de la Escuela Central de Electrnica (ECE). Tambin quiero dar las
gracias a Philippe Brunet, de la direccin pedaggica del Instituto Superior del Automvil y de
Transportes de Nevers (ISAT). Mi agradecimiento tambin para Bernard Buffire, antiguo director
pedaggico de la Escuela Superior de Ingeniera Informtica de Nevers (CS2I-Bourgogne), donde
ejerzo en la actualidad, y para su sucesor actual, David Aurousseau.

No hace falta decir que tambin doy las gracias a cada uno de los estudiantes que he conocido, con el
conjunto de los cuales he compartido esta formidable pasin de crear y escribir programas.

Conclusin: la programacin como escritura

La programacin aqu se encara como una escritura que permite expresar de forma especfica ideas y
puntos de vista prcticamente en todas las materias.

Si hay amantes de los videojuegos, desde el punto de vista de la programacin, el juego es programar el
videojuego. La resolucin de enigmas es verdaderamente apasionante y, cuanto ms se eleva su nivel,
ms difcil e interesante se vuelve. El ordenador es un robot, y usted es su cerebro. Se debe poner en la
piel de la mquina para conseguir que haga lo que usted desea. Esto supone:

Comprender la mquina.
Tener ideas, voluntad y objetivos que desea que cumpla.
Poder traducir sus deseos en elementos ejecutables para la mquina mediante una reflexin metdica,
el dominio de un lenguaje de programacin, la escritura del programa (incluyendo las distintas etapas
de diseo).
El informtico que escribe programas no se limita a las tcnicas que domina. Hace propuestas en las
reas y situaciones que aborda; la tcnica por s sola no es suficiente para conseguir el xito. Tambin
tiene que ser capaz de abrirse a otros dominios, de desarrollar conocimientos como un consultor o un
investigador y de tener incluso inspiracin, lo que nos dirige a una cultura abierta y viva.
Introduccin a la programacin en C

1. Qu es programar?

Un programa es una serie finita de operaciones agrupadas y organizadas en un orden adecuado para la
consecucin de una o varias tareas que el ordenador realiza cuando se ejecuta dicho programa. Estas
operaciones se llaman instrucciones. Se escriben y se definen con un lenguaje de programacin. Hay
miles de lenguajes de programacin. En cada lenguaje las instrucciones utilizan datos, que son
informacin almacenada en memoria usando un formato denominado tipo o estructura de datos.

Una sucesin finita de operaciones agrupadas y organizadas en un orden adecuado para la consecucin
de una o varias tareas es tambin la definicin de un algoritmo. Un programa es, por tanto, un
algoritmo o un conjunto de algoritmos agrupados entre ellos por... un algoritmo. Cualquier
combinacin de instrucciones para la realizacin de una tarea es un algoritmo.

Por tanto, programar consiste en definir y ordenar en un lenguaje de programacin las instrucciones
que la mquina deber ejecutar, es decir, encontrar el algoritmo y las estructuras de datos adecuadas
para alcanzar un determinado objetivo.

2. El posicionamiento en relacin con el ordenador

El ordenador se construye con varios niveles superpuestos y solidarios entre ellos. Cada nivel se
sustenta en el nivel anterior. Ello reduce la complicacin y permite realizar operaciones ms complejas.
Podemos esquematizarlo del siguiente modo:
3. Los elementos de la programacin

a. El compilador

El compilador es un traductor que lee un programa escrito en un primer lenguaje (el lenguaje
origen/fuente) y lo traduce a un programa equivalente en otro lenguaje (el lenguaje destino). Durante el
proceso, indica si hay errores en el programa origen. Cuando escribimos un programa en C, el lenguaje
C es el fuente y la compilacin realiza su traduccin al lenguaje mquina, es decir, un ejecutable.
En general, el compilador viene acompaado de una interfaz de tratamiento de texto que permite
escribir programas en el lenguaje origen.

b. El IDE: entorno de desarrollo

Para ser usable, el compilador se completa con un entorno de desarrollo: EDI, de entorno de desarrollo
integrado, o en ingls IDE, de Integrated Development Environment. Es un programa o un conjunto de
programas relacionados entre ellos. Incluye un procesador de textos especfico para el cdigo fuente
con opciones especficas automatizadas (indentacin automtica, coloracin de palabras clave,
bsqueda y reemplazo de expresiones, etc.), herramientas automticas de configuracin, diversas
opciones y, a menudo, un depurador. Por ejemplo, el compilador libre MingW puede usarse con varios
IDE: CodeBlocks, NetBeans. A menudo el entorno de desarrollo viene acompaado con el compilador,
pero no siempre es as.

c. Instalacin

Depende directamente del compilador y del entorno de desarrollo seleccionados.

4. Los pilares de un programa

a. Datos e instrucciones

Para escribir un programa, disponemos en todos los lenguajes de dos grandes categoras de
herramientas:

La relativa al almacenamiento y la memoria del ordenador. Es lo que se denominan estructuras


de datos. Se basan en distintas especies de variables. Cada tipo de variable viene determinado
por una palabra clave. En C, una variable puede ser de los tipos siguientes: char, short, int, long,
float, double, struct, tabla y puntero.

La relativa a los tratamientos (clculos) aplicados a los datos, es decir, las instrucciones. Como
cada programa es una creacin, no podemos saber por adelantado cules sern los tratamientos
a realizar. El lenguaje de programacin proporciona, por tanto, instrucciones bsicas a partir de
las cuales podemos construir nuestras propias instrucciones y nuestras funciones. Las
instrucciones nativas en un lenguaje se indican mediante palabras clave. C dispone de las
instrucciones siguientes: if, if-else, if-else if-else, switch, while, do-while, for, as como
diferentes variedades de operadores: asignacin, aritmticos, bit a bit, comparaciones, punteros,
tablas, estructuras, funcin.

En definitiva, un lenguaje dispone de pocos elementos, pero con ellos puede realizar una infinidad de
tareas. Es un poco como en msica: con trece notas podemos inventar una infinidad de composiciones.

b. Libreras de funciones

Por otro lado, el programador no reinventa la rueda en cada nuevo programa. Se pueden utilizar las
mismas instrucciones sofisticadas ya escritas para operaciones frecuentes, tales como operaciones con
archivos, operaciones de visualizacin, etc. En C, estas instrucciones se presentan en forma de
funciones y tenemos a nuestra disposicin de manera estndar unas libreras de funciones clasificadas
por temas: entrada/salida, tratamientos de cadena de caracteres, utilidades, funciones matemticas, etc.
Hay muchas otras posibilidades de libreras de funcin, especialmente para la creacin de videojuegos,
el tratamiento de imgenes, el tratamiento de sonido... Algunas, en cdigo abierto, son libres de
derechos de uso. Otras, propietarias, pueden llegar a venderse muy caras.

De cara a la realizacin de un proyecto, creamos nuestros propias guas.

5. Primer programa

a. La funcin main(): punto de entrada del programa

Para el sistema operativo, un ejecutable se traduce a una pila de instrucciones con una entrada y un
inicio, que se llama cabecera del programa. Para el programador, es la funcin main(). De este modo,
todos los programas empiezan por la funcin main().

En un programa, una funcin siempre se designa con el nombre y el parntesis a la derecha. Por
ejemplo, la funcin miFunc: miFunc() o la funcin main: main().

Segn el sistema operativo o el entorno de desarrollo, main() puede tener caractersticas especficas. A
continuacin se muestra un ejemplo de programa con un main() estndar. Este programa es completo;
una vez compilado funcionar, pero no har nada:

int main() // punto de entrada del programa,


{ // apertura del bloque de instrucciones del programa

/* valor de retorno de la funcin main que indica un buen


funcionamiento del programa. */
return 0;

} // cierre del bloque de instrucciones del programa

Los comentarios // y /*...*/

El smbolo // en un programa indica que todo lo que sigue en la misma lnea (y solo en esa lnea) es un
comentario y no ser tenido en cuenta durante la compilacin, es decir, durante la construccin del
ejecutable en lenguaje mquina. Permite introducir explicaciones en el cdigo fuente para hacerlo ms
claro y comprensible.

De igual modo, todo el texto o el cdigo que se encuentre entre /* y */ es considerado como un
comentario.

b. Visualizar texto con la funcin printf()

La funcin printf() se encuentra en la librera en <stdio.h>. Para poderla usar, hay que incluir la librera
en nuestro proyecto. Para ello, utilizamos la instruccin #include antes del comienzo del programa,
encima del main(). Ejemplo de uso en el programa:

#include <stdio.h>
Los smbolos mayor que y menor que indican a la mquina que el archivo correspondiente se encuentra
en el directorio include del compilador. Si no fuera as, aparecera un mensaje de error en la
compilacin.

La funcin printf() permite mostrar texto en una ventana de consola. Una o varias palabras, una o
varias frases: el texto que se muestra debe indicarse entre los parntesis a la derecha de la funcin. Por
ejemplo, para mostrar el texto hoy hace buen da se deber escribir en el cdigo:

printf("hoy hace un buen da");


El texto entre parntesis tiene que estar obligatoriamente entre comillas dobles: el texto que quiero
mostrar, y despus el parntesis que cierra los parmetros y el punto y coma.

El texto entre comillas dobles se denomina cadena de caracteres y el punto y coma indica una
instruccin.

#include <stdio.h> // para tener acceso a la funcin printf()

int main() // entrada del programa,


{ // apertura del bloque de instrucciones

/* llamada a la funcin printf() que muestra la cadena


de caracteres pasada por parmetro */
printf("hola");

/* detener el programa para tener el tiempo de leer


el resultado */
system("PAUSE");

/* valor de retorno de la funcin main que indica


un buen funcionamiento del programa.*/
return 0;

} // cierre del bloque de instrucciones

6. Puesta en prctica: descubriendo el compilador

Ejercicio 1

Instalar el compilador e IDE codeBlocks si todava no se ha instalado. El archivo de instalacin se


puede descargar en http://fdrouillon.free.fr o en http://www.codeblocks.org.

Una vez finaliza la instalacin, localice en el disco la ubicacin de las carpetas include y lib del
compilador. Tenga especial cuidado en no modificar nada. Inicie el programa codeBlocks.

Ejercicio 2

Para ver las etapas de la creacin de un proyecto, hacer un proyecto console application (encontrar
los comandos adecuados en el IDE). Especificar una carpeta en el disco duro para el proyecto y
asignarle un nombre.
Ejercicio 3

Descubriendo el main(): compilar un programa que no hace nada.

Ejercicio 4

Primer programa: mostrar lo que quiera con la funcin printf().

Volver al programa y modificar alguna cosa cada vez, por ejemplo:

Aadir \n al final del mensaje, acentos , , , , , etc.


Eliminar el punto y coma, una o dos comillas dobles, un parntesis...

En qu casos hay un mensaje de error y cul es?

Ejercicio 5

Crear un dibujo usando varias veces la funcin printf() y varias impresiones de pantalla seguidas de
cadenas de caracteres.
COMPLETAR!!! PAGINAS 42 Y 43
COMPLETAR PAGINAS 49 Y 50
Para tener nmeros con signo, es decir, con valores positivos y negativos, la solucin es tener un bit
dedicado al signo, el de ms a la izquierda, el bit 8 en un byte.

Si este bit es 1 el nmero es negativo; si no, es positivo. Pero esto no es suficiente, ya que entonces hay
dos 0 (uno en negativo y otro en positivo) y las operaciones de suma y resta entre nmeros negativos y
positivos se complican un poco (si x e y son de signo diferente, el signo de x+y es el del ms grande en
valor absoluto. Por tanto, hay que comenzar por saber cul de ambos es el mayor para averiguar el
signo del resultado y, continuacin, restar al ms grande el ms pequeo).

El mtodo que se usa generalmente es del complemento a dos. Este mtodo permite transformar una
resta en una suma. Recordemos que, entre el nmero limitado de instrucciones simples que el
microprocesador realiza, est la suma de bits. Es una solucin rpida.

Principios del complemento a dos en binario

El complemento a 2 de un nmero binario es su inverso + 1, por ejemplo:


Nmero en binario 11010011
Inverso 00101100
Complemento a dos 00101101

Complemento a dos en un sistema de signos

Si el nmero es negativo (bit de signo a 1), da el nmero negativo del complemento a dos de ese
nmero. Por ejemplo, en un byte:

nmero en binario 10000000 | 10000001 | 10001111


valor sin signo 128 | 129 | 143
complemento a dos 10000000 | 01111111 | 01110001
traduccin con signo -128 | -127 | -113

En un byte los valores positivos van de 0 a 127 y todos los valores de 128 a 255 son interpretados como
valores negativos de -128 a 0.

Para n bits, el rango posible de valores con signo cubre la horquilla:

- (2n - 1) a + (2n - 1 -1)


El problema de desbordamiento de la capacidad todava persiste. Por ejemplo, en un byte con signo no
se pueden codificar nmeros inferiores a -128 ni superiores a 127. Para un byte con signo, sumar 120 y
9 da -127.

7. Puesta en prctica: codificacin de datos numricos

Ejercicio 1

Cuntos datos se pueden codificar en 2 bits? Y en 4? Y en 8?

Cules son los cdigos binarios asociados a estos datos?

Cules son los valores decimales asociados a estos cdigos con signo? Y sin signo?

Cuntos bits son necesarios para codificar el alfabeto chino de 4344 smbolos? Cuntos bytes?

Ejercicio 2

Dar la codificacin en binario de los nmeros -32, -77, 104, 258 en 8 bits.

Ejercicio 3

Si en un programa:

asigno el valor 266 a un char y lo muestro, cul es el resultado?


asigno el valor 384 a un char y lo muestro, cul es el resultado?
asigno el valor 768 a un char y lo muestro, cul es el resultado?
asigno el valor 23277 a un char y lo muestro, cul es el resultado?
cunto da en un char -120+250?
cunto da en un char -120-120?

Comprobar o encontrar las respuestas con un programa y explicar los resultados.

8. Experimento: variables simples, declaracin, asignacin, visualizacin y entrada de datos

// libreras de funciones
#include <stdio.h>
#include <stdlib.h>

int main()
{
//--------------------------------------------------------
//declarar variables: <tipo> <identificador> < ; >
//el tipo define el tamao y sus propiedades (con coma flotante o sin)

// las instrucciones
int foo; // reserva un espacio de memoria (una direccin) para un int
foo = 10; // asigna el valor 10 a la variable foo,

// recordatorio:
// expresin (int foo, foo=10) e instruccin (int foo; foo=10;)

//--------------------------------------------------------
// los otros tipos en variables simples en C
char c = A; // 1 byte, codificacin ascii de caracteres
short s = 10; // 2 bytes
int i = 20; // 2 o 4 bytes
long l = 456; // 4 bytes
float f = 3.89; // 4 bytes
double d = 45678890.876543;// 8 bytes

//--------------------------------------------------------
// para mostrar un valor:
// cada tipo en su formato:
// char %c, short, int, long %d, float %f, double %lf,
// direccin de memoria (variable compleja de tipo puntero) %p
printf("i vale: %d\n", i);

//--------------------------------------------------------
// codificacin ascii de caracteres: a un valor numrico
// le corresponde un carcter
printf("%c: %d\n", 112,112);

//--------------------------------------------------------
// tamao en memoria de una variable: operador sizeof
printf("double: %d, int: %d\n",sizeof(double), sizeof(i));
//--------------------------------------------------------
// obtener la direccin en memoria de una variable: operador &
printf("%p, %p, %p, %p\n", &i, &l,&f,&d);

//--------------------------------------------------------
// entrada del usuario: scanf
printf ("introduzca un valor entero\n");
scanf("%d",&i);
printf("i vale: %d\n",i);

//---------------------------------

Operaciones

1. Nocin de expresin

En el cdigo informtico, a todo elemento o conjunto de elementos que son objeto de una evaluacin
numrica se le llama expresin:

a + b, a / b, a = b, &b, !b son expresiones.

En estas expresiones +, /, =, & y ! son operadores y las variables a y b son los operandos.

Estas combinaciones de variables con operadores se llaman expresiones ms que operaciones, ya que
no solo hay operadores aritmticos. Adems, un valor constante, una variable o una llamada a una
funcin por s solos tambin se consideran expresiones. Son expresiones elementales. A las expresiones
que usan operadores y varios argumentos se les llama expresiones compuestas.

El valor numrico de una expresin, compuesta o elemental, es el resultado de la expresin en s


misma. Si se trata de una operacin aritmtica o de una asignacin, el valor numrico de la expresin es
el resultado de esta operacin. Si se trata de una llamada a una funcin, es el valor de retorno de la
funcin. La expresin vale este valor, es este valor y, como tal, una expresin puede integrarse dentro
de otra ms grande, en una asignacin o como un operando. Por ejemplo, el programa:

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

int main()
{
int a, b=20;
printf("%d--",a=10); // asignacin
printf("%d--",a=a*b); // multiplicacin
printf("%d\n",a%b); // mdulo (resto de la divisin)
return 0;
}
Imprime:

10200--0

2. Operaciones aritmticas

a. Operadores +, -, *, /, %

Los operadores aritmticos son los siguientes:

+ ms : el resultado de la expresin a+b es la suma

- menos : el resultado de la expresin a-b es la resta

* multiplicar : el resultado de la expresin a*b es la multiplicacin

/ dividir : el resultado de la expresin a/b es la divisin

% mdulo : el resultado de la expresin a%b es el resto de la divisin de a entre b

Qu resultado devuelve el programa siguiente?

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

int main()
{
int a=10,b=20,c=0; // 1
c=a+b; // 2
a=b/c; // 3
b=a*c; // 4
printf("a=%d, b=%d, c=%d\n",a,b,c); // 5
printf("res=%d\n",c%4); // 6
printf("c=%d\n",c); // 7
return 0;
}
lnea 1: a vale 10, b vale 20, c vale 0

lnea 2: a vale 10, b vale 20, c vale 30

lnea 3: a vale 0, b vale 20, c vale 30

lnea 4: a vale 0, b vale 0, c vale 30

lnea 5: visualizacin: a=0, b=0, c=30


lnea 6: visualizacin: res=2

lnea 7: visualizacin: c=30

Atencin! En la lnea 6 el resultado de la operacin se muestra, pero no hay asignacin que modifique
el valor de c y c conserva su valor, que es el que se muestra en la lnea 7.

b. Asignaciones combinadas

Los operadores siguientes permiten asociar una operacin con una asignacin; la operacin se realiza
antes que la asignacin:

a += b es una contraccin de a = a+b


a -= b es una contraccin de a = a-b
a *= b es una contraccin de a = a*b
a /= b es una contraccin de a = a/b
a %= b es una contraccin de a = a%b
Qu imprime el programa siguiente?

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

int main()
{
int a=7,b=232,c=4; // 1

c+=a; // 2
b-=c; // 3
a%=10; // 4
printf("a=%d, b=%d, c=%d\n",a,b,c); // 5
c+=(b-a); // 6
printf("c=%d\n",c); // 7
}
lnea 1: a vale 7, b vale 232, c vale 4

lnea 2: c vale 11

lnea 3: b vale 221

lnea 4: a vale 7

lnea 5: visualizacin: a=7 b=221 c=11

lnea 6: c vale 225

lnea 7: visualizacin: c=225


c. Pre y posincrementos o decrementos

Tambin hay que dominar los operadores ++ y --. Se utilizan muy a menudo. Se usan a la izquierda o a
la derecha de su operando.

Sea, por ejemplo, un entero i:

i++ y ++i son equivalentes a i = i+1, tambin


i-- y --i son equivalentes a i = i-1

Pero atencin! Ambos operadores se comportan distinto si se colocan delante de la variable o se


colocan detrs. La diferencia es visible cuando i++ o ++i se utiliza en expresiones, como por ejemplo
en la llamada de una funcin o en una operacin:

printf("%d %d",++i, i++);


Si el operador ++ se coloca delante del operando: ++i, entonces, en una expresin, el valor de i es el
valor de i+1, es decir, i se incrementa en 1 antes de usarse en la expresin.

En cambio, si ++ se coloca detrs del operando: i++, entonces, en una expresin, el valor de i sigue
siendo i y se incrementa en 1 despus de que la expresin haya sido evaluada.

Por ejemplo, qu muestra la siguiente serie de comandos?

int i=0;
printf("%d",i++); // el resultado da 0
printf("%d",i); // pero i se incrementa despus de usarse
// y vale 1 despus
printf("%d",++i); // i se incrementa antes de su uso,
// el resultado da 2
El principio es el mismo con el operador --. Si se coloca antes de la variable, se realizar la resta de 1
antes de que se use la variable en la expresin. Si se coloca despus de la variable, la variable se usar
sin modificarse en la expresin y despus se decrementar en 1.

d. Operaciones entre tipo distintos, operador de cast

Qu sucede si se realizan operaciones con variables de tipos distintos? Por ejemplo:

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

int main()
{
int entero = 10;
float flotante = 34.9;
int eRes;
float fRes;
eRes=entero+flotante;
fRes=entero+flotante;
printf("eRes: %d, fRes: %f\n", eRes, fRes); // 1
eRes=entero / (entero+5);
fRes=entero / (entero+5);
printf("eRes: %d, fRes: %f\n", eRes, fRes); // 2

eRes=flotante / entero;
fRes=flotante / entero;
printf("eRes: %d, fRes: %f\n", eRes, fRes); // 3
return 0;
}

A continuacin, la regla que debe saber:

La operacin aritmtica se realiza con el tipo del operando ms fuerte. La asignacin se realiza con el
tipo del miembro de la izquierda, el de la variable que recibe el valor.

1) La primera impresin muestra eRes: 44, fRes: 44.900000.

La operacin se hace en float, el tipo ms fuerte. Con la asignacin, el resultado se transforma a tipo int
para eRes y sigue siendo un float con fRes.

2) La segunda impresin muestra eRes: 0, fRes: 0.000000.

La operacin se realiza con el tipo int, el ms fuerte. Por lo tanto, no hay cifras decimales y el resultado
es 0 en ambos casos.

3) La tercera impresin muestra eRes: 3, fRes: 3.490000.

La operacin se realiza en float, el resultado se convierte a int en el primer caso y sigue siendo un float
en el segundo.

Operador de cast

A veces es necesario forzar una operacin para que trabaje con un tipo en particular. Para ello, existe un
operador que permite convertir el tipo de una expresin a otro tipo. Es el operador llamado cast. Por
ejemplo, en el caso //2, mostrado a continuacin:

fRes = entero / (entero+5);


si se necesita el resultado en float de una operacin hay que forzar esta operacin a que se realice en
float. Para ello basta con especificar entre parntesis el tipo deseado a la izquierda de alguna de las dos
variables:

fRes = (float)entero / (entero+5);


o
fRes = entero / (float)(entero+5);
Atencin: no es el resultado lo que se convierte, sino una de las variables para que la operacin se
realice en un tipo ms fuerte. Pero tambin funciona a la inversa, si se desea, por ejemplo, forzar una
operacin realizada en float para que se haga en tipo int:
fRes= (int)flotante / entero;

e. Prioridades entre operadores

La mquina siempre ejecuta las instrucciones una tras otra y nunca ejecuta dos operaciones a la vez.
Para poder realizar una operacin compuesta de varios clculos, hay un orden de prioridad entre
operadores. Por ejemplo, el operador * es prioritario en relacin con el operador +, y una expresin
como:

a+b*c
se interpreta como:

a + (b * c)
A continuacin se muestra una tabla de prioridades para los operadores que hemos estudiado hasta
ahora (la tabla completa que incluye todos los operadores de C est en el anexo y haremos referencia a
ella en los captulos siguientes). Cada fila corresponde a un nivel. Tenemos cuatro niveles y estn
clasificados en orden decreciente de prioridad, de la prioridad ms alta a la ms baja.

Pero qu sucede si los operadores tienen la misma prioridad? Hay una regla de asociatividad que
determina el orden de evaluacin de la expresin y de sus subexpresiones si las hay. Por ejemplo, * y /
son asociativos a la izquierda, es decir, que el clculo se descompone empezando por la izquierda:

a*b/c
se interpreta como

(a * b) / c
No es obligatorio basarse exclusivamente en las prioridades predefinidas con los operadores. Tambin
se puede forzar una prioridad aadiendo parntesis; por ejemplo:
(a + b) * c
a + b ser lo que se calcular primero y, a continuacin, la multiplicacin por c.

En caso de duda, siempre es bueno clarificar la expresin con los parntesis apropiados.

Qu imprime el programa siguiente?

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

int main()
{
int x,y,z;

x= -3+4*5-6;
printf("%d\n",x);

x= 3+4%5-6;
printf("%d\n",x);

x=-3*4%-6/5;
printf("%d\n",x);

x= (7+6)%5/2;
printf("%d\n",x);

x=2;
x*=3+2;
printf("%d\n",x);

x*=y=z=4;
printf("%d\n",x);
}
Muestra:

11
1
0
1
10
40
Para averiguarlo, lo ms sencillo es aadir los parntesis correspondientes a las prioridades e ir
descomponiendo progresivamente la expresin en el orden ejecutado por la mquina.

Descomposicin del primer ejemplo:

x = -3+4*5-6
x = (-3)+4*5-6 - como signo
x = (-3)+(4*5)-6 * multiplicar
x = ((-3)+(4*5))-6 + suma debido a la asociatividad izquierda
x = (((-3)+(4*5))-6) - menos antes de la asignacin
(x = (((-3)+(4*5))-6)) = asignacin

lo que resulta en:


(x= ((-3+20)-6))
(x= (17-6))
(x= 11)
11
Descomposicin del segundo ejemplo:

x = 3+4%5-6
x = 3+(4%5)-6
x = (3+(4%5))-6
x = ((3+(4%5))-6)
(x = ((3+(4%5))-6))
Descomposicin del tercer ejemplo:

x = -3*4%-6/5
x = (-3)*4%(-6)/5
x = ((-3)*4)%(-6)/5
x = (((-3)*4)%(-6))/5
x = ((((-3)*4)%(-6))/5)
(x = ((((-3)*4)%(-6))/5))
Descomposicin del cuarto ejemplo:

x = (7+6)%5/2 // el clculo entre parntesis se realiza primero


x = ((7+6)%5)/2
x = (((7+6)%5)/2)
(x = (((7+6)%5)/2))
Descomposicin del quinto ejemplo:

x vale 2
x *= 3+2
x *= (3+2)
(x *= (3+2))
Descomposicin del sexto ejemplo:

x vale 10
x *= y = z = 4
x *= y = (z = 4) asociatividad a partir de la derecha
x *= (y = (z = 4))
(x *= (y = (z = 4)))
3. Puesta en prctica: operaciones aritmticas, cast

Ejercicio 1

Sean las declaraciones:


char c =\x01 ; /* \xhh para un nmero hexadecimal
(0...9, a...f o A...F) en un byte mx*/
short p=10;
Cules son los tipos y los valores de cada una de las expresiones siguientes?

p+3
c+1
p+c
3*p+5*c
6.8 /7 + a * 560
Ejercicio 2

Sean las declaraciones:

char c=\x05;
int n=5;
long p=1000;
float x=1.25;
double z=5.5;
Cules son los tipos y los valores de cada una de las expresiones siguientes?

n+c+p
2*x+c
(char) n + c
(float) z + n / 2
Ejercicio 3

Escribir un programa que, tras introducir un nmero entero, muestre su opuesto y su mitad exacta.

Ejercicio 4

Crear un conversor pesetas/euro... (un euro = 166,386 pesetas)

cules son las fases del programa?


programar
Ejercicio 5

Para convertir los grados Fahrenheit en grados Celsius, se usa la siguiente frmula:

C= 5/9 * (F-32)
Para convertir los grados Celsius en grados Fahrenheit, se usa esta otra:

F= ( (9*C ) / 5)+32
donde F es una temperatura en grados Fahrenheit y C es la temperatura correspondiente en grados
Celsius.

Escribir un programa que convierta en grados Celsius una temperatura entrada por el teclado en grados
Fahrenheit.
Hacer lo mismo con una temperatura expresada en grados Celsius para convertirla a grados Fahrenheit.
Ejercicio 6

Escribir un programa para comprobar todos los casos de la divisin en C con el objetivo de ilustrar el
problema de los cast:

Divisin de un int entre un int, guardada en un int mostrado con %d


Divisin de un int entre un int, guardada en un float mostrado con %f
Divisin de un int entre un float, guardada en un float y mostrado con %d y %f
En qu casos es necesario? Comprobar con cast.

Atencin, es importante distinguir por un lado la divisin y su resultado, y por otro la visualizacin del
resultado segn un formato u otro. Son dos problemas distintos.

Ejercicio 7

Una tienda de informtica anuncia unas rebajas del 10% en ordenadores porttiles. Escribir un
programa que lea el precio de un ordenador entrado por el teclado y muestre el nuevo precio con la
rebaja.

Ejercicio 8

Sea una funcin matemtica f cuya definicin es f(x) = (2x+3)(3x2+2).

Escribir el programa que calcule la imagen de f de un nmero introducido por el teclado.

Ejercicio 9

Escribir un programa que lea un nmero del teclado y muestre 1 si es par y 0 si es impar.

Ejercicio 10

Escribir un programa que muestre el nmero de las decenas, el de las centenas y el de los millares de
un nmero introducido por el teclado. Por ejemplo, para 31345, el nmero de las decenas es el 4, el de
las centenas es el 3 y el de los millares es el 1.

Ejercicio 11

Escribir un programa que redondee un nmero real entrado por el teclado a dos cifras decimales.

Ejercicio 12

Escribir un programa que lea un valor introducido por el usuario en una variable y que muestre i, i++ e
++i. Qu es lo que muestra? Por qu?

Ejercicio 13

Qu resultados da el programa siguiente?

#include <stdio.h>
int main()
{
int i, j, n;
i=0;
n=i++;
printf("A : i = %d, n = %d \n", i, n);

i=10;
n=++i;
printf("B : i = %d, n = %d \n", i, n);

i=20;
j=5;
n=i++ * ++j;
printf("C : i = %d, j = %d, n = %d \n", i, j, n);

i=15;
n= i += 3;
printf("D : i = %d, n = %d \n", i, n);

i = 3;
j = 5;
n = i *= --j;
printf("E : i = %d, j = %d, n = %d \n", i, j, n);
return 0;
}

4. Obtener valores aleatorios

a. Principio de pseudoaleatoriedad

El concepto de azar es un problema en matemticas; la aleatoriedad no existe en un ordenador. Sin


embargo, siempre es posible simular la aleatoriedad con un ordenador produciendo valores
imprevisibles.

El principio es el de construir una serie de nmeros. Se toma un nmero como punto de partida, al que
se le aplica un extrao clculo que produce un valor imposible de predecir y aplicando cada vez el
mismo clculo al resultado obtenido. En funcin del punto de partida y del clculo, se obtiene una serie
impredecible de nmeros como si se produjeran por azar. Por ejemplo, una serie poco previsible puede
obtenerse con el clculo siguiente: (x+3,14159)8

En cada paso, hay un nmero intuitivamente improbable. Pero la serie ser siempre la misma con un
nmero de partida y un clculo idntico. Para tener series distintas, el problema es poder obtener un
punto de partida que vaya cambiando.

Para obtener una serie de nmeros (pseudo)aleatorios e inicializar el valor de partida, la librera
estndar stdlib.h proporciona dos funciones:

La funcin rand() se dedica a realizar el clculo y devuelve un nmero impredecible.


La funcin srand() inicializa el valor de partida.

b. La funcin rand()

El clculo operado por la funcin rand() es el siguiente, a partir de la variable de paso de tipo unsigned
long (o int en 32 bits en 4 bytes) y por defecto inicializada a 1 de partida:

paso = paso * 1103515245 + 12345


paso = (paso / 65536) % 32768

Cada nueva llamada a la funcin realiza un paso de ms en la serie a partir del valor albergado en la
variable paso. El valor de la variable paso se devuelve al contexto de la llamada de la funcin, es decir,
que hay que recuperar este valor en una variable de la forma siguiente:

int prueba;

(...)

prueba=rand(); // en cualquier parte del programa

En el clculo, el mdulo 32768 hace que el valor mximo posible sea 32767. Este valor tambin se ha
definido en la librera por la macro constante RAND_MAX (una macro constante es un valor fijo que
se reemplaza por un texto; este tema se explica en el captulo de Variables conjunto [estructuras y
tablas]).

El programa siguiente prueba los cinco primeros valores devueltos por la funcin rand():

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

int main()
{
int prueba;

prueba=rand();
printf("paso 1: %d\n",prueba);
prueba=rand();
printf("paso 2: %d\n",prueba);
prueba=rand();
printf("paso 3: %d\n",prueba);
prueba=rand();
printf("paso 4: %d\n",prueba);
prueba=rand();
printf("paso 5: %d\n",prueba);
return 0;
}
En cada nueva ejecucin del programa, siempre salen los mismos nmeros:

41
18467
6334
26500
19169

c. La funcin srand()

Para evitar tener siempre la misma serie, hay que comenzar la serie con nmeros distintos. Este es el
papel de la funcin srand(), inicializar el primer valor de la serie. Para ello, hay que pasar a srand() el
valor deseado para iniciar la serie. Por ejemplo, para generar la serie a partir del nmero 2, la funcin
se debe llamar de la siguiente forma:

srand(2);

En un programa, con un nmero cualquiera sera:

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

int main()
{
int val;

srand(689); // inicializacin de partida

val = rand();
printf("val=%d\n",val); // clculo del primer nmero
return 0;
}

Pero en cada ejecucin el punto de partida ser siempre el mismo: 689, en este ejemplo... Cmo se
puede inicializar la serie con un valor distinto en cada ejecucin?

La idea es capturar la hora con la funcin de la librera time.h, la funcin time(). Esta funcin devuelve
el valor de tiempo segn una codificacin que vara de un sistema a otro, o el valor -1 si esta
funcionalidad no est disponible. En Windows la hora se devuelve en milisegundos. La llamada a la
funcin se realiza de la siguiente forma en el programa:

int partida;
partida=time(NULL);

Es necesario indicar el valor NULL entre los parntesis, como parmetro de la funcin. A continuacin
solo falta pasar esta variable a la funcin srand().

srand(partida);
En un programa se utilizara del siguiente modo:

#include <stdio.h> // para utilizar la funcin printf()


#include <stdlib.h> // para utilizar las funciones rand() y srand()
#include <time.h> // para utilizar la funcin time()

int main()
{
int partida=time(NULL); // inicializacin en la declaracin de la variable
srand(partida);

(...)

printf("primer valor: %d\n",rand());


printf("segundo valor: %d\n",rand());
printf("tercer valor: %d\n",rand());
return 0;
}
Con este programa, la serie ser diferente en cada nueva ejecucin del programa.

La variable partida no es necesaria; puede colocar directamente la llamada a la funcin time() como
parmetro de la funcin srand(), quedando de este modo: srand(time(NULL));

d. Valores aleatorios en rangos definidos

Obtener valores enteros en un rango

El operador mdulo devuelve el resto de una divisin entera, a%b vale el resto de la divisin de a entre
b, siendo un valor entero que va de 0 a b-1. Por ejemplo, el programa siguiente permite visualizar dos
valores aleatorios entre 0 y 9:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()

{
int prueba;
srand(time(NULL));

prueba=rand();
prueba%=10; // equivalente a prueba = prueba%10;
printf("prueba=%d\n",prueba);

// se puede escribir directamente


prueba=rand()%10;
printf("prueba=%d\n",prueba);
return 0;
}
Para obtener un valor en un rango con mximo max y con un valor mnimo min distinto de 0,
basta con extraer un valor aleatorio dentro del intervalo max-min y sumar min. Por ejemplo, para un
valor entre 20 y 49, deberamos escribir:

int prueba;

prueba= (rand()%(50-20))+20;
// es decir:
prueba = 20 + rand()%30;
Si rand() %30 da 0, entonces 20 es el valor ms pequeo y si rand() devuelve 29, 49 es exactamente el
valor ms grande.

Obtener valores en coma flotante en un rango

La funcin rand() devuelve un valor entero de tipo int y el valor ms grande de los posibles es el valor
RAND_MAX de tipo int, definido como macro constante en la librera stdlib.h. Para obtener un valor
en coma flotante entre 0 y 1, basta con dividir el valor devuelto por rand() por el valor mximo posible
RAND_MAX, sin olvidar de realizar un casting a float, ya que, si no se se lleva a cabo, se calcular en
el tipo int (int dividido entre int). Obtendremos el cdigo siguiente:

float prueba; // atencin, usar un float!


prueba= (float) rand() / RAND_MAX;
Para tener un nmero entre 0 y 50, basta con multiplicar el nmero obtenido entre 0 y 1 por 50. El
extremo inferior es 0, con 0*50, y el extremo superior es 50, con 1*50. Cualquier otro valor estar
comprendido entre ambos extremos.

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

int main()
{
float prueba;
srand(time(NULL));

prueba=(float) rand() / RAND_MAX; // entre 0 y 1


printf("prueba1=%f\n",prueba);

prueba=((float) rand() / RAND_MAX)*50; // entre 0 y 50


printf("prueba2=%f\n",prueba);

prueba=20+ ((float) rand() / RAND_MAX)*30; // entre 20 y 50


printf("prueba3=%f\n",prueba);

return 0;
}
5. Puesta en prctica: operaciones y nmeros aleatorios

Ejercicio 1

Elegir cara o cruz mentalmente, y ejecutar un programa que escriba al azar 0 para cara y 1 para cruz...
gan?

Ejercicio 2

Elegir mentalmente un nmero entre 2 y 12 y ejecutar un programa que simule una tirada con dos
dados de seis caras... gan?

Ejercicio 3

Escribir un programa que genere y muestre 7 nmeros aleatorios segn las restricciones siguientes:

Mostrar un nmero aleatorio segn el rango mximo del generador aleatorio.


Mostrar un nmero aleatorio comprendido entre 0 y 367.
Mostrar un nmero aleatorio comprendido entre 0 y un valor umbral alto entrado por el usuario.
Mostrar un nmero aleatorio comprendido entre 678 y 7354.
Mostrar un nmero aleatorio comprendido entre un valor umbral bajo y un valor umbral alto entrados
por el usuario.
Mostrar un nmero aleatorio comprendido entre 0 y 1.
Mostrar un nmero aleatorio con decimales comprendido entre 0 y 50.

6. Operaciones bit a bit

En C tenemos seis operadores que permiten realizar operaciones a nivel de bit. Se usan nicamente con
los tipos enteros con signo y con char, short, int y long sin signo.

a. Y - operador &

El operador Y funciona bit a bit de la siguiente forma:

i & 0 siempre devuelve 0


i & 1 devuelve i, es decir, o 0 o 1
Por ejemplo, el resultado de 12 & 10 devuelve 8:

1 1 0 0 (12 en binario)
& 1 0 1 0 (10 en binario)
devuelve 1 0 0 0 (8 en binario)
Sirve para acceder a uno o a varios bits concretos de un entero. Para ello, consideremos un valor de
mscara en la que solo los bits a 1 son visibles. Por ejemplo:

Para saber el valor del primer bit de un entero, mscara 1:

10111001
& 00000001
devuelve 00000001
Para conocer el valor del segundo bit, mscara 2:

10111001
& 00000010
devuelve 00000000
Para conocer el valor del tercer bit, mscara 4:

10111001
& 00000100
devuelve 00000000
Para conocer el valor del cuarto bit, mscara 8:

10111001
& 00001000
devuelve 00001000
Para conocer el valor resultante de los cuatro primeros bits del byte:

10111001
& 00001111
devuelve 00001001
Para conocer el valor resultante de los cuatro ltimos bits del byte:

10111001
& 11110000
devuelve 10110000
Lo interesante es poder tener en un solo entero varios interruptores booleanos distintos o poderlo
descomponer en varios rangos de bits.

b. O exclusivo - operador

Este operador devuelve 1 si sus operandos son distintos y 0 si son iguales:

00 devuelve 0
10 devuelve 1
01 devuelve 1
11 devuelve 0
Se utiliza en diferentes casos; por ejemplo, en el programa siguiente, los valores de ambas variables se
intercambian:

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

int main()
{
int a=345, b=678;

printf("a: %d, b: %d\n,a,b);


a=b;
b=a;
a=b;
printf("a: %d, b: %d\n,a,b);
return 0;
}
c. O inclusivo - operador |

Devuelve 1 si algn operando vale 1:

0|0 devuelve 0
1|0 devuelve 1
0|1 devuelve 1
1|1 devuelve 1
Permite acumular bits a 1 en una variable. Se utiliza especialmente en algunas funciones para
especificar con una sola variable qu tratamientos se deben aplicar (bits a 1) y cules ignorar (bits a 0).
En tal implementacin, cada bit tiene asociado simblicamente un tratamiento; si est a 1, el
tratamiento deber realizarse, y si est a 0, el tratamiento correspondiente no debe realizarse.

d. COMPLEMENTO - operador ~

El complemento es un operador unario (con un solo operando a la derecha) que invierte el valor de los
bits de una variable:

~1100 devuelve 0011


Pone a 1 los bits que valen 0 y a 0 los que valen 1.

e. Operadores de desplazamiento a izquierda y derecha - operadores >> y <<

Los desplazamientos permiten mover los bits de una variable hacia la izquierda o hacia la derecha. Por
ejemplo, un desplazamiento a la derecha de 2 bits:

10110011 >> 2 devuelve 00101100


Los dos bits a 1 de la derecha han salido y han sido reemplazados por 0, que han entrado por la
izquierda. Esta operacin es equivalente a una divisin entre 22. n bits desplazamientos a la derecha es
una divisin entre 2n.

En el otro sentido, un desplazamiento de 4 bits:

11110011 << 4 devuelve 00110000


Los cuatro bits a 1 de la izquierda han salido y han sido reemplazados por 0, que han entrado por la
derecha. Esta operacin equivale a una multiplicacin por 24. n bits desplazamientos a la izquierda es
una multiplicacin por 2n.

f. Prioridades de los operadores bit a bit


Su nivel de prioridad se sita entre el de la asignacin y el de la suma y la resta. No tienen todos la
misma prioridad (ver Anexo 1 Prioridad y asociatividad de los operadores).

7. Puesta en prctica: operaciones bit a bit

Ejercicio 1

Que imprime por pantalla el programa siguiente?

#include <stdio.h>

int main()
{
int x,y,y;

x = 3;
y = 2;
z=1
printf("%d\n", x | y & z);
printf("%d\n", x | y & ~z);
printf("%d\n", x y & ~z);
printf("%d\n", x & y & z);

x = 1;
y = -1;
printf("%d\n", ~x | x);
printf("%d\n", x & ~x);
printf("%d\n", x x);
x <<= 3;
y <<= 3;
y >>= 3;
}
8. Experimento: operaciones aritmticas, valores aleatorios

/*
2. OPERACIONES ARITMTICAS
*/
// libreras de funciones
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
int a=90, b=10;

// los operadores aritmticos son +, -, *, /, %


//--------------------------------------------------------
// a+b es una expresin
// el valor numrico de una expresin es el resultado
// de la operacin (aritmtica o no)
printf("%d\n",a+b);

//--------------------------------------------------------
// prioridad de los operadores aritmticos:
printf("%d\n",a/2+b*8%3); // (a/2)+((b*8)%3)

//--------------------------------------------------------
// tipo de una expresin aritmtica:
// si los operandos son de tipos distintos, la expresin
// es del tipo del operando ms fuerte
char c=12, res2;
float f=4.5, res1;
printf("%f\n",c+f);

//--------------------------------------------------------
// obtener un resultado: la asignacin se realiza con el tipo
// de la variable que recibe el valor, posible truncamiento
res1=c+f;
printf("%f\n",res1);

res2=c+f;
printf("%d\n",res2);

//--------------------------------------------------------
// La operacin se realiza con el tipo ms fuerte:
int i1=10, i2=5;

i1=i2/i1;
printf("res i1 : %d\n",i1);

float res;
i1=10; // (atencin div entre 0)
res=i2/i1;
printf("res float : %f\n",res);

//--------------------------------------------------------
// forzar una operacin para que se realice en otro tipo que
// el de los operandos: operador de cast
res= (float)i2/i1;
printf("res float : %f\n",res);

//--------------------------------------------------------
//posibilidad de combinar la asignacin y una operacin aritmtica
// +=, -=, *=, /=, %=
res+=10; // equivalente a res= res+10
printf("res float : %f\n",res);

//--------------------------------------------------------
// operadores de incremento/decremento
int i=0;
printf("%d\n",i++); // posincremento
printf("%d\n",++i); // preincremento

//--------------------------------------------------------
// obtener un valor aleatorio entero: funcin rand()
i=rand();
printf("%d\n",i);

// albergado en un rango
i=rand()%10; // comprendido entre 0 y 9
printf("%d\n",i);

i=5+rand()%10;// entre 5 y 14

//--------------------------------------------------------
// inicializar la serie de nmeros aleatorios:
srand(time(NULL));

//--------------------------------------------------------
// obtener un valor aleatorio entre 0 y 1
res= (float)rand()/RAND_MAX;
printf("%f\n",res);

// obtener un valor aleatorio en coma flotante en un rango


res= ((float)rand()/RAND_MAX)*10; // comprendido entre 0 y 10
printf("%f\n",res);

// fin prog
return 0;

Bloques de instrucciones y condiciones


1. Qu es un bloque de instrucciones?

a. Definicin

Un bloque de instrucciones es UNA instruccin compuesta de varias instrucciones encadenadas.


En C (y todos los lenguajes derivados) se delimita con los operadores { } (llave de apertura y de
cierre).
Un bloque puede contener otros bloques anidados.
En un archivo fuente, no puede haber una instruccin fuera de un bloque (excepto las directivas de
macro y declaraciones de variables globales o de funciones). Para ser vlidas, todas las instrucciones
tienen que estar dentro de un bloque. El bloque superior es el de la funcin main(), que es el que al
final rene todas las instrucciones del programa.
b. Ejemplo

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

int main()
{ //------------------------- apertura de bloque main B1
int x,pasx; // declaraciones de variables locales al bloque main
// son visibles (accesibles) en este bloque y todos
// los subbloques

{ //----------apertura B2
int c;
x=0;
c=rand()%256;
pasx=rand()%5;
} // ---------cierre B2
x=640;

{ // ---------apertura B3
//c=10; // provoca error, c no es visible en este bloque
x/=2;
pasx=15;

} // ---------cierre B3
x+=pasx;
printf("x vale : %d\n",x); // resultado?

Return 0;

} //-------------------------cierre bloc main


Los bloques plantean la cuestin de la visibilidad de variables. De hecho, el tiempo de vida de las
variables en general se asocia al del bloque en el que se han declarado. Debido a ello, son visibles en
todos los subbloques anidados y, en cambio, son invisibles en los bloques de nivel superior o en los del
mismo nivel pero separados.

c. Utilidad de un bloque de instrucciones

En el ejemplo anterior los bloques son intiles: no alteran en ningn caso el desarrollo lineal del
programa, no hay ninguna modificacin en la sucesin de operaciones y si se eliminan todo seguir
igual, excepto la declaracin de la variable c.

La utilidad de los bloques es permitir romper esta linealidad e introducir, gracias a las instrucciones
proporcionadas por el lenguaje:

Saltos de bloque, con las tres instrucciones if if-else, if -else if -else.


Caminos alternativos, con la instruccin switch.
Y repeticiones, con las tres instrucciones de bucle while, do-while y for.
Los bloques tambin permiten generalizar partes del cdigo que se repiten en un programa con
posiblemente valores distintos. Es lo que llamamos una funcin. Una funcin es un bloque de
instrucciones dotado de una entrada de valores y de una salida de valores.

2. Definir una condicin

a. Por qu una condicin?

Los saltos condicionales permiten ejecutar o no ejecutar un bloque de instrucciones segn se cumpla o
no una condicin. Incluso para los bucles, su bloque de instrucciones se repite mientras se cumpla una
condicin definida para su repeticin.

Por ejemplo, en el caso de la instruccin if, primera forma de salto condicional, la ejecucin del bloque
de cdigo est sujeta a una condicin de la siguiente forma:

Si (condicin verdadera) Entonces


ejecutar bloque de instrucciones
FinSi
Si y solo si se cumple la condicin, es decir, es verdadera, entonces las instrucciones del bloque que
depende de esta se ejecutarn.

Una condicin verdadera es una expresin cuyo valor es distinto de 0. De igual modo, una condicin se
considera falsa si la expresin vale 0.

b. Cmo definir una condicin?

Definir una condicin es escribir una expresin que se evaluar como verdadera o falsa. La mayora de
las condiciones se elaboran a partir de comparaciones de variables. Las comparaciones bit a bit tambin
se usan a veces.

c. Los operadores de comparacin

Se pueden comparar variables o cualquier tipo de expresin desde el punto de vista de sus valores. Sean
dos expresiones a y b (variables u operaciones), cada una de ellas con un valor. Las comparaciones
posibles son las siguientes:

a > b a estrictamente mayor que b


a < b a estrictamente menor que b
a >= b a mayor o igual que b
a <= b a menor o igual que b
a == b a igual a b (prueba de igualdad)
a != b a diferente de b (prueba de desigualdad)
El resultado de cada expresin es:

0 si es falsa
1 si es verdadera
Segn su punto de vista, qu imprime el programa siguiente?

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

int main()
{
int a=10, b=5;
printf("%d ",a < b);
printf("%d ",a+b > 12);

a = 5;
printf("%d ",a >= b);
printf("%d ",a == b);

b = 4;
printf("%d ",a <= b);
printf("%d ",a == b);
printf("%d ",a+3 != b*2);
return 0;
}
Respuesta:

0111000
Otro ejemplo:

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

int main()
{
int a,b;
printf("%d ",56 < rand() ); // 1 si verdadero, 0 si falso

printf("%d ",a=rand() + b=rand() >= 1000 );


// 1 si verdadero, 0 si falso
printf("%d ",a*b == 45); // 1 si s, 0 si no
return 0;
}
Los resultados dependen de los nmeros devueltos por la funcin rand().

d. El operador unario de negacin !


El operador ! colocado a la izquierda de una expresin devuelve verdadero si la expresin vale 0 (falso)
y falso si la expresin es diferente de 0 (verdadero). Por ejemplo:

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

int main()
{
int a=10, b=20;
printf("%d ", !(a < b)); // muestra 0
printf("%d ", !(a > b)); // muestra 1
return 0;
}
A menudo se utiliza en condiciones, sustituyendo una igualdad a 0, por ejemplo:

int a=10;

printf("%d ", a==0);// muestra 0

// puede ser reemplazada por la expresin:

printf("%d ", !a); // que tambin muestra 0


e. Prioridades de los operadores de negacin y de comparacin

Todas las operaciones aritmticas, as como desplazamientos, cast, sizeof, NO y complemento a 1 son
prioritarias respecto a las comparaciones. En cambio, las comparaciones son prioritarias sobre Y y O
tanto inclusivos como exclusivos y asignaciones combinadas (Anexo 1: Prioridad y asociatividad de
operadores).

Por ejemplo:

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

int main()
{
int a=10, b=0, c=2;
printf("a+b < c*10 vale %d\n", a+b < c*10);
printf("c*10 >= 66 vale %d\n", c*10 >= 66);
printf("a+b < c*10 >= 66 == 80 vale %d\n",
a+b < c*10 >= 66 == 80);
return 0;
}
La primera lnea devuelve 1
la segunda 0
la tercera 0
3. Puesta en prctica: operadores de comparacin y negacin
Ejercicio 1

Implementar un programa que muestre los resultados del siguiente bloque de cdigo:

int a, b, c;
srand(5);
Imprimir los resultados de:
a=rand()%256;
b=rand()%256;
c=rand()%256;

a<128
b>128
c==223
a < b >= rand()%2 == 1
a= b !=c +rand()%50;
b= a==c
c= rand()%10 < rand()%10 >= rand()%10 != rand()%10 ==rand()%10
Ejercicio 2

Qu muestra el siguiente programa?

#include <stdio.h>

int main()
{
int a=10, b=0, c=2;
printf("a+b < c*10 vale %d\n", a+b < c*10);
printf("c*10 >= 15 vale %d\n", c*10 >= 15);
printf("a+b < c*10 >= 15 == 1 vale %d\n",
a+b < c*10 >= 15 == 1);
printf("a+b < c*10 <= 15 == 1 vale %d\n",
a+b < c*10 <= 15 == 1);
return 0;
}
Saltos condicionales

1. La instruccin if

Si y solo si la expresin es verdadera, ENTONCES el bloque de instrucciones asociado al if se ejecuta.


Solo puede haber un bloque de cdigo asociado al if.

if ( expresin verdadera){
bloque instrucciones;
}
Por ejemplo, sea una variable a, en un programa se puede escribir:

if ( a >=100 ){ // prueba
printf( "a es mayor o igual que 100 \n"); // instruccin
}
Despus de un if solo puede haber un bloque de cdigo, es decir, una sola instruccin. Si hay ms
instrucciones, hay que abrir y cerrar el bloque, pero si solo hay una, no es necesario. El ejemplo
anterior puede escribirse sin errores de compilacin del siguiente modo:

if ( a >=100 )
printf( "a es mayor o igual que 100 \n");
Atencin a la indentacin: es del todo intil para la mquina, pero en cambio es fundamental
profesionalmente para que el cdigo sea legible.

En el caso de una sucesin de if, cada uno hace una evaluacin:

if ( expresin1 verdadera){
bloque instrucciones 1;
}
if ( expresin2 verdadera){
bloque instrucciones 2;
}
if ( expresin3 verdadera){
bloque instrucciones 3;
}
Solamente los bloques cuya condicin se evale como verdadera se ejecutarn: ninguno, algunos o
todos.

Los if pueden anidarse; en este caso puede que no se ejecuten todos, la serie se detendr en la primera
condicin falsa:

if ( expresin1 verdadera){
bloque instrucciones 1;
if ( expresin2 verdadera){
bloque instrucciones 2;
if ( expresin3 verdadera){
bloque instrucciones 3;
}
}
}
Si (y solo si) la expresin1 es verdadera, entonces las instrucciones del bloque de instrucciones 1 se
ejecutarn y se evaluar la expresin2.

Si (y solo si) la expresin2 es verdadera, entonces las instrucciones del bloque de instrucciones 2 se
ejecutarn y se evaluar la expresin3.

Si (y solo si) la expresin3 es verdadera, entonces las instrucciones del bloque de instrucciones 3 se
ejecutarn.

2. La pareja de instrucciones if-else

SI la expresin es verdadera, se ejecutar el bloque de instrucciones asociado al if; SI NO, es el


siguiente bloque de instrucciones el que se ejecutar. El else tiene que colocarse al final de un bloque
if. No puede haber instrucciones entre ambos:

if (expresin1 verdadera){
bloque instrucciones 1;
}
else{
bloque instrucciones 2;
}
Si la expresin1 es verdadera (valor diferente de 0), entonces se ejecuta el bloque de instrucciones 1.

Pero si la expresin1 es falsa (vale 0), entonces se ejecuta el bloque de instrucciones 2.

Sea cual sea el valor de la expresin1, se ejecutar un bloque de instrucciones, o bien el primero o bien
el segundo. Por ejemplo:

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

int main()
{
int a=rand()%100;

if (a<50)
printf("a menor que 50\n");
else
printf("a mayor o igual que 50\n");
return 0;
}
Un valor se selecciona al azar entre 0 y 100. El programa indica si el valor es menor o mayor o igual
que 50.

Como sucede con el if solo, puede haber una sucesin de if-else, o if-else dentro de if, if-else... Los else
se refieren siempre al bloque anterior, al inmediatamente superior.

if (expresin1 verdadera){
bloque de instrucciones1;
if (expresin2 verdadera){
bloque de instrucciones2;
}
}
else{
bloque de instrucciones3;
}
Si la expresion1 es verdadera, el bloque de instrucciones1 se ejecuta y si la expresin2 es verdadera, se
ejecuta el bloque de instrucciones2 y finaliza. Si no, es decir, si la expresin1 es falsa, entonces es el
bloque de instrucciones3 el que se ejecuta.

3. La forma compacta de if-else, operador condicional ?

Sea por ejemplo la siguiente secuencia de instrucciones:

int a, b;

a=rand()%10;
b=rand()%10;

if (a<b)
a*=2;
else
b*=2;
Ambas variables (a y b) se inicializan con valores al azar comprendidos entre 0 y 9. Las dos variables
se comparan y la menor se multiplica por dos.

La comparacin puede realizarse de la siguiente forma:

(a < b) ? (a*=2) : (b*=2);


La forma de leerlo es as:

es a < b? en caso afirmativo ejecutar a*=2, si no ejecutar b*=2


La forma general es:

(expresin prueba) ? si verdadera instruccin1 : si falsa instruccin2 ;


4. Instrucciones if-else if-else en cascada

Cuando se tiene una serie de if simples, se evalan todas las condiciones y, si son todas verdaderas,
todas las instrucciones pueden ejecutarse. En el caso de un if seguido de un else, ser o un bloque o el
otro el que se ejecutar, nunca ambos.

Siguiendo el principio exclusivo del if-else, es posible alargar la lista de condiciones gracias a la
instruccin else if; es el tercer principio del salto:

if(expresin1 verdadera){
instrucciones1;
}
else if (expresin2 verdadera){
instrucciones2;
}
else if (expresin3 verdadera){
instrucciones3;
}
else{
instrucciones4;
}
Si la expresin1 es verdadera,

se ejecuta el bloque de instrucciones1,

si no, si la expresin2 es verdadera,

es el bloque de instrucciones2 el que se ejecuta,

si no, si la expresin3 es verdadera,

es el bloque de instrucciones3 el que se ejecuta,

y si no

es el bloque de instrucciones4 el que se ejecuta.

Solo puede haber un bloque ejecutado. Una vez que una de las condiciones se evala como verdadera,
el proceso de evaluacin finaliza. Si ninguna condicin ha dado verdadero, entonces es el bloque del
else el que se ejecuta.

5. Experimento: los saltos condicionales (los tres if)

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

******************************************************************
2 : SALTOS CONDICIONALES DE BLOQUES / 3 posibilidades

Forma 1:

Si (condicin verdadera) Entonces


Ejecutar bloque de instrucciones
FinSi

CMO DEFINIR UNA CONDICIN?


-> los operadores de comparacin: >, >=, <, <=, ==, !=
-> las expresiones se evalan como VERDADERAS (valor 1) o FALSAS
(valor 0)
OBSERVACIN:
En C
- cualquier expresin que vale 0 puede considerarse como falsa
- y cualquier expresin que tenga un valor distinto de 0 puede
considerarse como verdarera

*****************************************************************/

int main()
{
int d1,d2;
srand(time(NULL));
// tirada de dados
d1=1+rand()%6;
d2=1+rand()%6;
if (d1>d2)
printf("el dado 1 gana: d1=%d, d2=%d\n",d1,d2);
if (d2>d1)
printf("el dado 2 gana: d1=%d, d2=%d\n",d1,d2);

// para verlo, se muestra el valor de la condicin


printf("d1>d2 vale %d,d1<d2 vale %d\n",d1>d2, d1<d2);

//OBSERVACIN:
//si solo hay una instruccin, las { } no son necesarias

return 0;
}

/*****************************************************************
SALTOS CONDICIONALES DE BLOQUES

Forma 2:

Si (condicin verdadera) Entonces


ejecutar bloque de instrucciones 1
Si no
ejecutar bloque de instrucciones 2
FinSi

*****************************************************************/
/*
int main()
{
int d1,d2;
srand(time(NULL));
// tirar dados
d1=1+rand()%6;
d2=1+rand()%6;
if (d1>d2)
printf("el dado 1 gana: d1=%d, d2=%d\n",d1,d2);
else
printf("el dado 2 gana: d1=%d, d2=%d\n",d1,d2);

return 0;
}
*/
/*****************************************************************
SALTOS CONDICIONALES DE BLOQUES

Forma 3:

Si (condicin 1 veradera) Entonces


ejecutar bloque de instrucciones 1
si no Si (condicin 2 verdadera) Entonces
ejecutar bloque de instrucciones 2
si no Si (condicin n verdadera) Entonces
ejecutar bloque de instrucciones n
Si no
ejecutar bloque de instrucciones por defecto
FinSi

*****************************************************************/
/*
//TEST 1
int main()
{
int d1,d2;
srand(time(NULL));
// tirar los dados
d1=1+rand()%6;
d2=1+rand()%6;
if (d1==d2)
printf("el dado 1 y el dado 2 son iguales: d1=%d, d2=%d\n",d1,d2);
else if (d1>d2)
printf("el dado 1 gana: d1=%d, d2=%d\n",d1,d2);
else
printf("el dado 2 gana: d1=%d, d2=%d\n",d1,d2);

return 0;
}
*/
/*
// TEST 2
int main()
{
int d;
srand(time(NULL));
// tirada de dados
d=1+rand()%6;

if (d==1)
printf("d vale 1: %d\n",d);
else if (d==2)
printf("d vale 2: %d\n",d);
else if (d==3)
printf("d vale 3: %d\n",d);
else if (d==4)
printf("d vale 4: %d\n",d);
else
printf("d vale 5 o 6: %d\n",d);

return 0;
}
*/
6. Puesta en prctica: saltos condicionales

Ejercicio 1

Escribir un programa que solicite dos enteros e indique cul es el ms pequeo y cul el ms grande.

Ejercicio 2

Implementar un programa que lea dos variables del teclado y las muestre en orden creciente.

Ejercicio 3

Implementar un programa que solicite un entero e indique si es par o impar.

Ejercicio 4

Mostrar cada una de las tres secuencias siguientes para:

a igual a 100,

a igual a 1

Secuencia 1
Secuencia 2
Secuencia 3
if ( a < 50 )
printf("1");

if ( a < 40 )
printf("2");
if ( a < 30 )
printf("3");

if ( a < 20 )
printf("4");

if ( a < 10 )
printf("5");
if ( a < 50 )
printf("1");
else
printf("2");

if ( a < 30 )
printf("3");
else
printf("4");
if ( a < 50 )
printf("1");
else if ( a < 40 )
printf("2");
else if ( a < 30 )
printf("3");
else if ( a < 20 )
printf("4");
else if ( a < 10 )
printf("5");
else
printf("0");
Ejercicio 5

Implementar un programa para jugar a cara o cruz. Al inicio el usuario elige cara o cruz, el programa
lanza la moneda y devuelve el resultado de si ha ganado o ha perdido.

Ejercicio 6

Escribir un juego del nmero oculto: el jugador intenta adivinarlo. El programa indica si ha ganado o
por cunto se ha pasado o se ha quedado corto.

Ejercicio 7

Dos criaturas furiosas poseen un mismo poder, pero cuando se encuentran la que tiene ms destruye a
la otra. Imaginar una solucin en C e implementar un programa de ejemplo.

Ejercicio 8

El usuario elige dos signos entre + y - y el programa indica el signo de su producto (el que dara si se
multiplicaran dos nmeros con los signos elegidos). Implementar el programa.
Ejercicio 9

Escribir un programa que lee tres variables del teclado y muestra el mximo de las tres.

Ejercicio 10

Una empresa vende dos tipos de productos. Los productos de tipo A, que tienen un IVA del 10%, y los
productos de tipo B, que tienen un IVA del 21%. Escribir un programa que lea, introducido por teclado,
el precio sin impuestos de un producto y el tipo de producto, y muestre el porcentaje de IVA y el precio
IVA incluido del producto.

Ejercicio 11

Un personaje llega a una puerta. Un guardin est delante de la entrada. El guardin le formula una
pregunta. Si el personaje acierta con la respuesta, puede pasar; si no, es eliminado.

Implementar un programa que traduzca esta situacin en lenguaje C.

Caminos alternativos

1. Seleccin de caminos alternativos: switch, case y break

Una serie de if, else if, else como:

if (i==0){
instrucciones0;
}
else if (i==1){
instrucciones1;
}
else if (i==7){
instrucciones7;
}
else if (i==55){
instrucciones55;
}
else{
instrucciones_n;
}
puede reemplazarse por un switch que es un conmutador. Funciona de la siguiente forma:

switch(valor_expresin){

case expresin_constante_1:
instrucciones1;
break;

case expresin_constante_2:
instrucciones2;
break;

case expresin_constante_3:
instrucciones3;
break;

default:
instrucciones_n;
break;
}
El bloque de instrucciones que se ejecutar se decide a partir del valor de la expresin del parmetro
del switch:

Si este parmetro vale expresin_constante_1, se ejecutarn las instrucciones1.


Si este parmetro vale expresin_constante_2, se ejecutarn las instrucciones2.
Si este parmetro vale expresin_constante_3, se ejecutarn las instrucciones3.
Si este parmetro tiene un valor diferente a los casos propuestos, se ejecutarn las instrucciones_n.
No hay lmite al nmero de casos posibles. Pero cada caso se identifica por un valor constante, es decir,
no puede haber variables despus de la palabra clave case.

La instruccin break provoca una salida del bloque switch. Sin el break, todas las instrucciones de los
casos siguientes al caso que cumple la igualdad tambin se ejecutaran.

Nuestra serie de if, else if, else puede escribirse:

switch(i){

case 0:
instrucciones0;
break;
case 7:
instrucciones7;
break;
case 55:
instrucciones55;
break;
default:
instrucciones_n;
}
Segn el valor de la variable i, se ejecutarn las instrucciones del caso correspondiente hasta el break.
Si i no se corresponde con ninguno de los casos, entonces es el caso por defecto el que se ejecuta, y si
no hay caso por defecto, no se ejecuta nada.

Cada caso puede tener varias instrucciones y corresponder a una serie de instrucciones. De hecho, no es
un bloque de instrucciones (que la mquina considera como una instruccin nica, compuesta) y las
llaves son innecesarias.

Un clarsimo ejemplo es el que se muestra a continuacin. Se trata de una interfaz para una base de
datos comercial que ofrece realizar cuatro acciones: a para visualizar la lista de clientes, b para
visualizar los datos de un cliente, c para introducir datos de un cliente y q para salir. El programa
sera el siguiente:

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

int main()
{
int eleccion;

// men de usuario
printf( "a: Visualizar la lista de clientes\n"
"b: Visualizar los datos de un cliente\n"
"c: Introducir datos de un cliente\n"
"q: salir\n");
// recuperacin de la eleccin del usuario
scanf("%c",&eleccion);

// conmutacin de las instrucciones segn la eleccin


switch(eleccion){
case a:
printf("Visualizar la lista de clientes\n");
/* poner aqu el cdigo para la visualizacin de todos los clientes */
break;

case b :
printf("Visualizar los datos de un cliente\n");
printf("Nombre del cliente?\n);
/* poner aqu el cdigo para la visualizacin de informacin de un cliente */
break;

case c :
printf("Introducir datos de un cliente\n");
/* poner aqu el cdigo para la introduccin de datos de un cliente */
break;

case q : // instruccin de salir


// (es necesario un bucle)
break;
default : printf("Comando de entrada incorrecto\n");
}
return 0;
}
2. Ruptura de la secuenciacin: goto con etiqueta

El goto es una instruccin que permite realizar un salto directo, sin condicin, desde cualquier lugar a
una etiqueta. La etiqueta de referencia debe figurar en la misma funcin que el goto.
Por ejemplo, se podra tener en una funcin:

{
(...)
if (error_fatal==1){
goto error;
(...)
}

error:
printf( "Error no recuperable\n");
exit(EXIT_FAILURE);

}
"error:" es una etiqueta colocada en algn sitio dentro del bloque de la funcin. La instruccin goto
error provoca un salto hasta este mismo lugar de la funcin y, a partir de ah, las instrucciones que se
encuentren se ejecutarn.

Atencin! En principio, un programa bien estructurado no tiene nunca la necesidad de utilizar un goto
y se desaconseja rotundamente su uso porque debilita la estructuracin y la organizacin del programa.
La lectura y la escritura del programa pueden volverse incluso confusas. Siempre debe reemplazarse
por una forma ms estructurada. El nico caso en el que quiz en C se considere admisible es en un
control de error, cuando permite salir de una situacin desesperada.

3. Experimento: seleccin de caminos alternativos con switch

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

/*****************************************************************
CONMUTACIN

La Forma if - else if - else puede reemplazarse por:

Segn (valor)
caso 1: ejecutar bloque de instrucciones 1
caso 2: ejecutar bloque de instrucciones 2
caso n: ejecutar bloque de instrucciones n
si no: ejecutar bloque de instrucciones por defecto
FinSegn

*****************************************************************/
// Por ejemplo este programa:
int main()
{
int d;
srand(time(NULL));
// tirada de dados
d=1+rand()%6;

if (d==1)
printf("d vale 1: %d\n",d);
else if (d==2)
printf("d vale 2: %d\n",d);
else if (d==3)
printf("d vale 3: %d\n",d);
else if (d==4)
printf("d vale 4: %d\n",d);
else
printf("d vale 5 o 6: %d\n",d);
return 0;
}

// puede escribirse:

/*
int main()
{
int d;
srand(time(NULL));
// tirada de dados
d=1+rand()%6;

switch(d){
case 1: printf("d vale 1: %d\n",d); // break;
case 2: printf("d vale 2: %d\n",d); // break;
case 3: printf("d vale 3: %d\n",d); // break;
case 4: printf("d vale 4: %d\n",d); // break;
default:printf("d vale 5 o 6: %d\n",d);
}
// OBSERVACIN: la instruccin break; provoca la salida inmediata
// del bloque switch; sin break los casos siguientes tambin se ejecutan

return 0;
}
*/
4. Puesta en prctica: caminos alternativos con switch

Ejercicio 1

Qu imprime la secuencia siguiente

scanf("%d", &elec);
switch( elec ){
case 1 : printf ("buenos das"); break;
case 2 : printf ( "buenas tardes"); break;
default : printf ( "hola") ; break;
}
para elec igual a 0, 1 y 3?

Ejercicio 2

Implementar un programa que muestre una frase diferente en cada ejecucin.

Ejercicio 3

De saber si la suma de a+b es par.


De saber si el producto de a*b es par.
De averiguar el signo de a-b.
De averiguar el signo de a*b.
Ejercicio 4

Al inicio, el programa solicita al usuario situarse con relacin al nivel de aprendizaje de C: excelente,
bueno, regular, malo, horrible. En funcin de su respuesta, el programa le propone una solucin o le da
un consejo. Implementar el programa.

Ejercicio 5

Escribir un programa que muestre un men ofreciendo al usuario jugar con uno, dos, tres o cuatro
dados. Segn la eleccin, el programa lanza los dados. Los dados idnticos se relanzan y se van
acumulando los puntos. El usuario gana si el total es mayor que las dos tiradas mximas (con dos dados
es 8: (12/3)*2). El programa indica cunto le falta para ganar o cunto tiene de ms.

Ejercicio 6

Un personaje llega a una puerta. Un guardin est delante de la entrada. El guardin le formula una
pregunta. Si el personaje acierta con la respuesta, puede pasar; si no, es eliminado.

Implementar un programa que traduzca esta situacin en lenguaje C.

Ejercicio 7

Implementar un programa que determine cul es su horscopo numrico. El usuario introduce su fecha
de nacimiento y el programa calcula el nmero correspondiente. Por ejemplo, sea la fecha de
nacimiento 09051969; hay que sumar todas las cifras hasta tener solo una: 0+9+0+5+1+9+6+9 da 39,
3+9 da 12, 1+2 da 3, el nmero correspondiente a la fecha de nacimiento es 3. A partir del nmero
obtenido, el programa da el horscopo para el ao actual.
Evaluacin de multicondiciones (Y/O)

1. Conjuncin Y, operador &&

a. Y con dos expresiones miembro

Sean dos expresiones, E1, E2.

la expresin E1 && E2:


es verdadera (vale 1) si E1 Y E2 son verdaderas
es falsa en caso contrario (vale 0)
Ejemplo:

int a = rand()%300;
if (a >= 100 && a<=200)
printf("a est comprendida entre 100 y 200\n");
La comprobacin vale 1 si el valor de a est comprendido en el rango 100-200 (extremos incluidos) y
vale 0 en el resto de los casos.

Otro ejemplo, en la aduana:

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

int main()
{
int papel, declarar;

printf("Lleva sus papeles? (s/n)\n");


scanf("%c",&papel);
// cuando hay varias llamadas sucesivas de scanf() hay que
// reiniciar el buffer de entrada (stdin) con la funcin
// rewind()
rewind(stdin);

printf("Alguna cosa que declarar? (s/n)\n");


scanf("%c",&declarar);

if( papel==s && declarar==n)


printf ("Est bien, puede pasar\n");
else
printf("Espere aqu, por favor\n");

return 0;
}
Si el viajero tiene sus papeles en orden y no tiene nada que declarar, puede pasar.

b. Y con ms de dos expresiones miembro


Si hay ms de 2 expresiones miembro:

E1 && E2 && E3 && E4 es verdadera si TODAS son verdaderas, falsa en caso contrario
Ejemplo:

if (x>0 && x<1024 && y>0 && y<768)


printf("(x,y) en pantalla de 1024x768\n");

Cuando se encuentra una expresin miembro falsa, la expresin entera es falsa y no se evalan las
expresiones restantes.

Otro ejemplo, receta de crepes:

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

int main()
{
char harina, sal, huevos, leche, gruyere;

printf("En el frigorfico hay huevos (s/n), leche (s/n)"


"gruyere (s/n)\n ?");
scanf("%c%c%c",&huevos, &leche, &gruyere);
rewind(stdin);

printf("En el armario hay harina (s/n)"


"y sal (o/n) ?\n");
scanf("%c%c",&harina, &sal);

if( huevos==s && leche==s && gruyere==s &&


harina==s && sal==s)
printf ("Genial! Cocinemos crepes!\n");
else
printf("No hay crepes para cenar.\n");
return 0;
}
Si disponemos de todos los ingredientes, podremos hacer crepes.

2. Disyuncin O, operador ||

a. O con dos expresiones miembro

Sean dos expresiones, E1, E2,

la expresin E1 || E2:
es verdadera (vale 1) si E1 O E2 son verdaderas
es falsa si las dos son falsas (vale 0)
Ejemplo:
int a = rand()%300;
if (a <= 100 || a>=200)
printf("a no est en el rango 100-200\n");
La comprobacin es verdadera si a est fuera del rango 100-200.

Otro ejemplo, en la aduana:

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

int main() // En la aduana versin O


{
int papel=0,declarar=0;

printf("Dispone de sus papeles? (s/n)\n");


scanf("%c",&papel);
rewind(stdin);

printf("Alguna cosa que declarar?(s/n)\n");


scanf("%c",&declarar);
rewind(stdin);

if (papel == n || declarar == s)
printf("espere, por favor\n");
else
printf("est bien, puede pasar\n");

return 0;
}
Si el viajero no tiene sus papeles o si hay algo que declarar, debe esperarse.

b. O con ms de dos expresiones miembro

Si hay ms de 2 expresiones miembro:

E1 || E2 || E3 || E4
es verdadera si al menos una es verdadera
es falsa si todas son falsas
Ejemplo:

if (x<0 || x>1024 || y<0 || y>768)


printf("(x,y) no est en una pantalla de 1024x768\n");
Si la comprobacin es verdadera, el punto de coordenadas (x,y) no est en la pantalla.

Otro ejemplo: puedo preparar un sandwich de jamn?

#include <stdio.h>
#include <stdlib.h>
int main()
{
char pan, mantequilla, jamon, pepinillo, ensalada;

printf("En el frigorfico hay:\n"


"mantequilla (s/n), jamon (s/n), pepinillos (s/n), "
"ensalada (s/n)\n"
"y tengo pan de molde (s/n)\n ?");
scanf("%c%c%c%c%c", &pan, &mantequilla, &jamon, &pepinillo,
&ensalada);

if(pan==n||mantequilla==n||jamon==n||
pepinillo==n||ensalada==n)
printf ("No hay sandwich de jamn!\n");
else
printf("am! Un buen sandwich\n");
return 0;
}
3. Y prioritario sobre O

Si hay ms de 2 expresiones miembro con Y y O:

E1 && E2 || E3 && E4 || E5 entonces && es prioritario sobre ||

lo que da : (E1 && E2) || (E3 && E4) || E5


equivalente a : E6 || E7 || E5

si E6 o E7 o E5 son verdaderas la expresin es verdadera. Es falsa en caso contrario


4. Prioridad respecto al resto de los operadores

La prioridad ms baja est justo por debajo de la del operador condicional y de las asignaciones (ver
anexo 1).

5. Puesta en prctica: operadores lgicos Y, O

Ejercicio 1

El juego de la ruleta a tres bandas. Tres ruletas se ponen en paralelo, cada una de ellas tiene sus valores
de letras o nmeros. Al inicio se las hace rodar con velocidades distintas. Cuando se detienen se alinean
tres letras o nmeros: A5R o 67T, por ejemplo. Se gana cuando algunas combinaciones aparecen, como
por ejemplo: AAA, BCD, OIO...

Implementar un programa para probar suerte una sola vez.

Ejercicio 2

X, Y y Z son variables numricas. Se consideran las dos siguientes secuencias algortmicas S1 y S2:

Secuencia S1
si (X<5 o Y>2) y Z>3 entonces X=1
si (Z-Y) > 0 entonces Z=0
finsi
Y = Y+Z
si no
X=2
Z = Y+Z
finsi
Secuencia S2

si X<5 o (Y>2 y Z>3) entonces X =1


si (Z-Y) > 0 entonces Z = 0
finsi
Y = Y+Z
si no
X=2
Z = Y+Z
finsi
1) Para cada una de ambas secuencias, dar los valores, despus de la ejecucin, de X, Y y Z, si se
supone que en el estado inicial estas tres variables tienen los valores:

a) X =4 Y =1 Z =4
b) X =4 Y =5 Z =4
c) X =1 Y =3 Z =1
2) Implementar un programa para comprobar sus respuestas con las dos secuencias.

Ejercicio 3

En un algoritmo que analiza resultados de examen, 4 variables permiten describir el entorno:

Las variables numricas Nle, Nc, Nm, Nf, que indican respectivamente, para un candidato, las notas de
letras: de lengua extranjera (Nle), de castellano (Nc), y las notas de ciencias: matemticas (Nm) y fsica
(Nf). Se supone que las notas se calculan sobre 10 y que todas tienen el mismo coeficiente.

Formar expresiones lgicas (y solo las expresiones) correspondientes a las situaciones siguientes:

1) la media de las cuatro notas es superior a 5


2) las notes de matemticas y de castellano son superiores a la media de
las cuatro notas
3) hay al menos una nota mayor que 5
4) todas las notas son mayores que 5
5) la media (5) se obtiene por una de las dos ramas (letras
y ciencias)
6) la media de las cuatro notas es mayor o igual a 5 y la
media (5) se obtiene por una de las dos ramas
Ejercicio 4

Escribir el algoritmo que determina el nmero de valores distintos entre tres variables introducidas por
el usuario (p. ej.: 8, 8 y 8 entrados por el usuario dan 1 valor distinto).

Cmo hacerlo con 4 variables?

Ejercicio 5

Estamos en un videojuego con una resolucin de pantalla de 640x480 pxeles. En el centro hay una
zona rectangular de 580x420. Esta zona se divide en cuatro partes iguales (z1,z2,z3,z4). Cada parte es
de hecho un botn que se puede pulsar con el ratn:

images/02ri01.png
Implementar un programa que:

Inicialice dos variables x e y con una posicin elegida al azar en la pantalla.


Mostrar en qu zona se encuentra esta posicin.
Ejercicio 6

Un personaje dispone de varias cualidades: valor, fuerza, paciencia, perseverancia, resistencia a la


magia. Cada cualidad es un valor entre 0 y 100. Segn las pruebas, puede tener miedo, volverse dbil,
perder la paciencia, estar tentado de dejarlo todo. Implementar un programa que responda a las
preguntas y situaciones siguientes:

Si la paciencia es menor que 50, la perseverancia baja varios puntos.


Tiene tanto valor como paciencia, y tanta fuerza como perseverancia?
Tiene ms paciencia que fuerza, valor y perseverancia juntos?
Es su resistencia a la magia igual a la media de los otros talentos menos 1/10?
Para salir de una prueba mgica de nivel 1, hace que su valor y su fuerza estn comprendidos entre 30 y
70 o que su paciencia y su perseverancia sean superiores a 40. Puede realizar tal prueba?
Morira en una prueba de supervivencia de nivel 5 si su fuerza no es al menos superior a 50. Tiene
posibilidades de sobrevivir si la realiza ahora?
Un hechizo de aniquilacin lo reduce a la sombra si la fuerza del sortilegio es mayor que la suma de sus
cualidades multiplicada por su resistencia a la magia. Probar un hechizo de fuerza aleatoria.
Cuando tiene tanto valor como fuerza, paciencia y perseverancia, su resistencia a la magia aumenta en
20 puntos. Aumenta su magia ahora?
Ejercicio 7

Estamos en un extrao mundo de hongos: cuando tres entidades de la misma especie se tocan,
producen una cuarta de la misma especie y se dispersan. En cambio si solo hay dos que sean de la
misma especie, la tercera es destruida y las otras dos permanecen juntas. Si todas son de especies
diferentes no pasa nada.

Implementar un programa que simule el principio de esta interaccin.


Bucles

1. Bucle MIENTRAS QUE: while

El bucle while tiene el aspecto siguiente:

while (expresin verdadera){


instrucciones;
}
Mientras que el valor de la expresin sea verdadero, es decir, no nulo y diferente de 0, las instrucciones
del bloque asociado al bucle se irn repitiendo. Para que el bucle pueda finalizar, la expresin tiene que
evaluarse falsa y, para ello, uno de los componentes de la expresin de la condicin debe modificarse
en el bloque de instrucciones. Por ejemplo:

int a=3,i=0;
while (i<a){
i++;
printf("i vale %d\n",i);
}
printf("Fin del bucle con i=%d \n",i);
Mientras i es menor que a, las instrucciones se ejecutan:

al inicio a vale 3 e i vale 0:

i < a: la comprobacin devuelve verdadero, las instrucciones se


ejecutarn, i aumenta en 1 y ahora vale 1.

i < a: la comprobacin devuelve verdadero, las instrucciones se


ejecutarn, i aumenta en 1 y ahora vale 2.

i < a : la comprobacin devuelve verdadero, las instrucciones se


ejecutarn, i aumenta en 1 y ahora vale 3.

i < a : la comprobacin devuelve falso, fin del bucle, la ejecucin contina con las instrucciones que
siguen al bloque del bucle.
La comprobacin puede ser falsa desde el inicio y en este caso las instrucciones del bloque del bucle no
se ejecutan; por ejemplo:

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

int main()
{
int a;
printf("Introduzca un valor entre 50 y 100\n");
scanf("%d",&a);
while( a < 50 || a>100 ){
printf("el nmero debe estar comprendido entre 50 y 100\n");
scanf("%d",&a);
rewind(stdin);
}
printf("valor entrado: %d\n",a);
return 0;
}
Si el usuario introduce inicialmente un valor entre 50 y 100, la comprobacin del bucle es falsa y no se
entrar en l. Se entrar en el bucle cuando el valor introducido por el usuario sea menor que 50 o
mayor que 100.

2. Bucle HACER {} MIENTRAS QUE: do-while

El bucle do-while tiene la siguiente forma:

do {
instrucciones;
} while (expresin) ; // atencin al punto y coma
// despus de la instruccin de bucle
A diferencia del bucle while, las instrucciones preceden la evaluacin de la condicin de parada y por
ello las instrucciones del bucle siempre se ejecutarn al menos una vez. Qu imprime el programa
siguiente?

int a=0, i=10;


do{
i++;
printf("i vale %d\n",i);
}while(i<a);
Respuesta:

i vale 11
La entrada de un nmero entre 50 y 100 del ejemplo con while anterior se puede escribir con un bucle
do-while justamente porque se requiere al menos una vez la llamada a la funcin scanf():

int a;
do{
scanf("%d",&a);
rewind(stdin);
if (a<50 || a>100 )
printf("el nmero debe estar comprendido entre 50 y 100\n\n");
}while( a<50 || a>100);
printf("valor de entrada: %d\n",a);
3. Bucle PARA: for

El bucle for tiene la forma siguiente:

for (expr1; expr2; expr3 ){


instrucciones;
}
con:
expr1: es una asignacin que se realiza solo una vez al comienzo del bucle

expr2: es la comprobacin de parada que se evala al comienzo de cada iteracin

expr3: es una modificacin de variable presente en la comprobacin para


terminar el bucle.
Como en todos los bucles, mientras la comprobacin sea verdadera, las instrucciones del bloque se
ejecutarn. Por ejemplo:

int i;
for (i=0; i<3; i++)
printf("i=%d\n",i);
El orden de ejecucin se descompone en:

for ( i=0; // 1 inicializacin


i<3; // 2 comprobacin
i++) // 4 incremento
printf("i=%d\n",i); // 3 bloque de instrucciones
Primera ejecucin del bloque:

1) i se inicializa a 0, a continuacin:
2) i se compara con 3, la comprobacin es verdadera
3) entonces se ejecutan las instrucciones del bloque
4) y se incrementa i en 1, i igual a 1,
Segunda ejecucin del bloque:

2) i es menor que 3,
3) se ejecutan las instrucciones del bloque
4) i se incrementa en 1, i igual a 2,
Tercera ejecucin del bloque:

2) i es menor que 3,
3) se ejecutan las instrucciones del bloque
4) i se incrementa en 1, i igual a 3,
Parada del bucle:

2) i no es menor que 3, la comprobacin devuelve falso

termina el bucle. El programa pasa a las instrucciones


que siguen al bucle.
Con un bucle while, el bucle anterior es equivalente a:

i=0;
while( i<3 ){
printf("i=%d\n",i);
i++;
}
Cada uno de los tres campos del bucle (inicializacin; comprobacin; incremento) puede ser una serie
de expresiones. Tienen que ir todas separadas por comas. Por ejemplo:
for ( x=0, y=45, z=rand()%100;
(x<500 && z < 400);
x+=10, y+=5, z +=9
)
printf("x=%d, y=%d, z=%d",x,y,z);
Este for usa tres variables. El bucle se desarrolla en el orden siguiente:

1) se inicializan al principio,

2) la comprobacin solo afecta a las variables x y z,

3) solo hay una instruccin en el bloque: mostrar los valores


de x, y, z

4) el incremento se realiza para las tres variables.


Todos los campos de un bucle for son opcionales, pero los dos punto y coma son obligatorios. Por
ejemplo, un bucle infinito puede escribirse:

for ( ; ; )
instrucciones;
En este ltimo caso, la salida del bucle deber forzarse con una instruccin de ruptura, como break,
usndola en el bloque de instrucciones (ver la seccin sobre salida y salto forzados en un bucle).

4. Bucles anidados

Por supuesto, es posible tener varios bucles anidados, por ejemplo:

while (expresin1 verdadera){


instrucciones1;
while (expresin2 verdadera){
instrucciones2;
}
}
o incluso:

for( expr1 ; expr2 verdadera ; expr 3){


instrucciones1;
while (expresin2 verdadera){
instrucciones2;
}
}
Cuando expliquemos el uso de tablas de dos dimensiones, nos encontraremos frecuentemente con
construcciones del tipo:

for( expr1 ; expr2 verdadera ; expr 3){


for( expr4 ; expr5 verdadera ; expr 6){
instrucciones;
}
}
5. Salida y salto forzados en un bucle

a. Salir con la instruccin break

Puede ser especialmente til salir de un bucle de otra manera distinta a la comprobacin de inicio o fin
de iteracin. Ya hemos visto que la instruccin break puede provocar la salida instantnea del switch
inmediatamente superior. break tambin puede provocar la salida instantnea del bucle inmediatamente
superior. Por ejemplo, en el caso de un bucle infinito se puede encontrar:

for ( ; ; ){
if(...)
break;
if(...)
break;
if(...)
break;
}
Si una de las tres comprobaciones es verdadera, break provoca la salida del bucle for.

Atencin: en el caso de bucles anidados, la salida solo afecta al bucle inmediatamente superior, no a
todos:

int toto=1000;
while (1){ // atencin
for ( ; ; toto=rand())
if (toto<500)
break ; // provoca la salida nicamente del for
}
En este ejemplo, si toto es menor que 500, la instruccin break provoca la salida del bucle for, pero no
del bucle while. Por este motivo, el bucle for volver a comenzar inmediatamente despus de su
interrupcin y no hay salida posible del bucle while.

b. Pasar a la siguiente iteracin con la instruccin continue

La instruccin continue no se suele usar a menudo. Su funcin no es finalizar el bucle, sino iniciar de
forma inmediata la siguiente iteracin del bucle while, do-while o for en la que se encuentra. En el caso
del while y del do-while, desencadena la reevaluacin de la condicin de parada. En el caso de los
bucles for, desencadena el paso a la etapa de incremento.

Si por ejemplo se escribe:

for (i=0 ; i<100 ; i++){


if (i>45 && i<60)
continue;
// instrucciones siguientes
(...)
}
Para valores de i de 46 a 59 no se ejecutarn las instrucciones siguientes.
c. Salir de uno o varios bucles anidados con la instruccin goto

Como ya hemos mencionado previamente, para el control de errores, un goto puede permitir salir de un
bucle o de varios bucles anidados:

for (...)
for (...){
if (catastrofe)
goto error;
}

...
error :
reparar los daos
Sin embargo, aparte del control de errores y de algunos casos precisos, se desaconseja el uso de la
instruccin goto.

6. Puesta en prctica: bucles while, do-while y for

Ejercicio 1

Implementar un programa que muestre, para cada valor de la tabla ascii (de 0 a 255), el carcter
correspondiente.

Ejercicio 2

Hacer un programa que permita:

Mostrar todos los nmeros de 0 a 1000.


Mostrar todos los nmeros de 0 a 100 en 10 columnas.
Mostrar la tabla de multiplicacin.
Mostrar la tabla de sumas.
Ejercicio 3

Escribir el cdigo que permite obtener: AAAAAAABBBBBZZZZZZZZTTT con nicamente cuatro


llamadas a la funcin putchar().
Escribir el cdigo que permite tener 10 veces la secuencia de cdigo anterior.
Cuntos bucles hay que usar para obtener la siguiente impresin de pantalla?
00000100000
00001110000
00011111000
00111111100
01111111110
11111111111
Escribir el cdigo correspondiente.
Probarlo todo en un programa.
Ejercicio 4
Es posible con scanf() implementar un programa que muestre ceros continuamente y, si se pulsa una
tecla, un nmero aleatorio de veces la letra o la cifra pulsada? Hacer que sea posible.

Ejercicio 5

Qu imprime el cdigo siguiente:

int i;
for (i=0; i<B ; i++)
printf("%d", B);

putchar(\n);

i--;
while (i++ < B)
printf("A");

do{
printf("C");
}while (i < B);
para B=5, B= 1, B=0?

Ejercicio 6

Sea un segmento horizontal [10, 100]; implementar un programa que:

Muestre todas las posiciones con saltos de uno en uno.


Muestre todas las posiciones con saltos de dos en dos.
Muestre todas las posiciones con saltos de un nmero definido por el usuario.
Ejercicio 7

Sea un rectngulo de posicin:

images/02ri02.png
Mostrar todas las posiciones del rectngulo numerndolas a partir de 0 y aumentando de 1 en 1 para
cada posicin (visualizar con la funcin printf(); el rectngulo est pegado al margen de la consola).

Ejercicio 8

En un programa:

Mostrar un nmero aleatorio de veces la palabra hola.


Mostrar repito el bloque tantas veces como el usuario lo solicite (se detiene cuando el usuario ya no
quiere ms).
Obligar al usuario a entrar un nmero par entre 100 y 1000.
Ejercicio 9

Simulacin de una carrera de caracoles.


En la salida 4 o 5 caracoles en el borde izquierdo de la consola. La llegada est a una distancia de 60
caracteres a la derecha (se puede dibujar con |). Al final de la carrera, el programa muestra quin es el
ganador.

Ejercicio 10

Tirada de dados. Escribir un programa que simule un nmero n, entrado por el usuario, de tiradas con 6
dados. El programa cuenta y muestra al final el nmero de veces en el que todos los dados tienen el
mismo valor.

Ejercicio 11

Dicotoma. Escribir un programa que genere aleatoriamente un nmero que el usuario tendr que
descubrir. En cada intento, el programa dir si el nmero es ms pequeo o ms grande. Si el usuario
encuentra el nmero, el programa le dir que ha ganado y le mostrar el nmero de intentos que ha
necesitado.

Ejercicio 12

Fabricar un dado trucado. Por ejemplo la cara 6 aparece ms frecuentemente. Probar el dado trucado
para demostrar su eficacia. Mejorar el dado haciendo que sean ms frecuentes 3 caras, por ejemplo, 1, 3
y 6. Probar si funciona.

Ejercicio 13

Implementar un programa en el que es el ordenador el que truca un dado de seis caras. Un valor se
vuelve el ms probable y no sabemos cul es. Despus de varias tiradas, gana si descubre cul es la
cara trucada. Puede ayudarse con un contador de las caras que han salido. Implementar el mismo
programa con esta vez tres caras trucadas.

Ejercicio 14

Implementar un generador de 100 palabras imaginarias pronunciables en espaol y producidas por la


mquina; por ejemplo: manata, ejole, popelepe... Las palabras se mostrarn por pantalla unas debajo de
otras.

Ejercicio 15

Escribir un programa que convierta un entero natural en cifras romanas, usando la antigua notacin.

Ejemplo: 4 (IIII), 9 (VIIII), 900 (DCCCC)

Recordemos los elementos base:

I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000.

Ejercicio 16

Inventar un sistema numeral inspirado en el de los romanos e implementar un programa que convierta
enteros naturales entrados por el usuario.

Ejercicio 17

Escribir un programa en C que proponga al usuario dibujar por pantalla algunas figuras compuestas de
asteriscos (tringulo, cuadrado, cono), de letras...

Las figuras se propondrn mediante un men (tringulo, cuadrado, cono...). La altura de la figura la
introducir el usuario. Ejemplo:

altura=4

images/ri02p47.png

Uso tpico de bucles

1. Crear un men de usuario

Un programa que finaliza con una accin del usuario siempre se basa en un bucle, es la base de su
dinmica. Para crear un men, el principio es el de dar una eleccin de comandos al usuario mediante
una interfaz y poner fin al bucle si el usuario lo solicita con un comando especfico. La interfaz simple
que proponemos es ofrecer al usuario cuatro elecciones:

printf ( "1: Escribir hola\n"


"2: Escribir hace buen da\n"
"3: Introducir un nmero entero\n"
"0: Salir\n");
A continuacin el programa captura la eleccin del usuario y aplica los tratamientos correspondientes.
Si el usuario entra cualquier otro nmero distinto de 0, 1, 2 o 3, el programa le indica que la seleccin
realizada no corresponde a ningn comando:

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

int main()
{
int selec, res;
do{ // 1

printf ( "1: Escribir hola\n" // 2


"2: Escribir hace buen da\n"
"3: Introducir un nmero entero\n"
"0: Salir\n");

scanf("%d",&selec); // 3
rewind(stdin);

switch(selec){ // 4
case 0 :
break;
case 1 :
printf("hola\n");
break;

case 2 :
printf("hace buen da\n");
break;

case 3 :
scanf("%d",&res);
rewind(stdin);
printf("el nmero es: %d\n",res);
break;

default :
printf("No es ningn comando: %d\n",choix);
break;
}

}while(selec !=0); // 5

return 0;
}
(1) (5) Instruccin de bucle do-while. El bucle se itera mientras la variable selec es diferente de 0 y
termina cuando la variable selec es igual a 0.

(2) Se imprime por pantalla el men de usuario.

(3) Primera instruccin ejecutada al menos una vez; el usuario entra un valor para la variable selec. La
llamada de la funcin scanf() prosigue con una llamada a la funcin rewind() para reinicializar el buffer
de entrada de datos por el teclado stdin y evitar un bucle infinito en caso de error.

(4) A partir del valor capturado en la variable selec, un switch orienta el flujo del programa hacia el
bloque de instrucciones correspondiente. Si la variable selec contiene un valor distinto de 0, 1, 2 o 3,
por defecto se muestra el mensaje de que no hay ningn comando asociado a ese nmero.

Si el usuario entra un valor errneo o una letra, es el caso por defecto el que se ejecuta antes de la
reimpresin del men. Para salir hay que seleccionar explcitamente la opcin salir.

2. Bucle de eventos en un videojuego

En la misma lnea, a continuacin se muestra otra versin de bucle de eventos adaptado para un
videojuego. La diferencia es poder capturar las entradas del usuario sin por ello detener el programa,
como sucede con la funcin scanf() del ejemplo anterior. En nuestro ejemplo, las entradas del usuario
se limitan al teclado, pero en modo grfico con una librera adaptada podemos tener entradas de ratn,
joystick, sensores, etc.
a. Obtener las entradas de teclado (kbhit() y getch())

La funcin scanf() detiene el programa y espera una entrada. Nada ms ocurrir hasta que el usuario
introduzca algo. Para evitarlo, hay que utilizar otras dos funciones:

int kbhit()
int getch();
La funcin kbhit() (abreviacin de keyboard hit) indica si se ha pulsado una tecla y devuelve un
valor verdadero o falso.

La funcin getch() (abreviacin de get char) devuelve el valor numrico ascii de la tecla pulsada.

Estas dos funciones no forman parte de la librera estndar de C. En general, estn asociadas a una
librera llamada conio.h (abreviacin de console in out).

b. Bucle de eventos simple

El siguiente bucle de eventos permite mostrar la tecla que se ha pulsado en modo letra (el carcter) y en
modo numrico ascii (el nmero que corresponde al cdigo del carcter) y, si ninguna tecla se ha
pulsado, se muestran ceros continuamente uno tras otro:

#include <stdio.h>
#include <conio.h> // no olvidar incluir la librera conio

int main()
{
int res;
while (res!=q){
if (kbhit()){
res=getch();
printf("tecla %c pulsada, valor ascii: %d\n",res,res);
}
putchar(0);
}
return 0;
}
La funcin kbhit() indica si se ha pulsado una tecla del teclado. En caso afirmativo, recuperamos el
valor ascii de la tecla con la funcin getch() y mostramos el resultado. Pero mientras no se pulse
ninguna tecla, las instrucciones que siguen al bloque if se ejecutan: la pgina se llena de ceros.

Problema: es muy rpido y no se ve casi nada. Para solucionarlo, aadiremos un pequeo timer para
ralentizar la impresin de ceros.

c. Controlar el tiempo de ejecucin

La funcin clock() devuelve el tiempo transcurrido en milisegundos (o en ciclos de reloj, depende del
entorno) desde el inicio del programa. A continuacin vemos cmo aadir al programa anterior un
pequeo control de tiempo.#include <stdio.h>
#include <conio.h>
#include <time.h>

int main()
{
int res, top=0;

while (res!=q){
if (kbhit()){
res=getch();
printf("tecla %c pulsada, valor ascii: %d\n",res,res);
}
if(clock()>top+30){ // controlar el tiempo
top=clock();
putchar(0);
}
}
return 0;
}
Ahora en el bucle while, si y solo si el tiempo sobrepasa el ltimo top registrado en ms de 30
milisegundos, se mostrar un cero y se guardar el nuevo top. Si no, no suceder nada. Por lo tanto,
solo hay una impresin de un cero cada 30 milisegundos.

if(clock()>top+30){ // 1
top=clock(); // 2
putchar(0);
}
(1) clock() es el tiempo que pasa, top es el tiempo de partida y +30 es para el siguiente top a 30
milisegundos.

(2) Cuando el tiempo transcurrido llega a top +30, top adquiere el nuevo valor de partida y se imprime
un 0. Un 0 se muestra cada 30 milisegundos.

3. Comenzar la creacin de un juego en modo consola

Con un bucle de eventos no bloqueante, se pueden escribir juegos. A continuacin se muestra un


programa que desplaza una letra por una zona de juego. Para hacerlo, necesitamos una funcin
adicional:

void gotoxy(int x, int y);


Esta funcin se escribe antes del main(). Necesita la inclusin del archivo de cabeceras windows.h.
Desplaza el cursor de escritura a la posicin (x, y) que se desee en la ventana de la consola. Podemos
usarla para desplazar una letra con las teclas de flecha.

Las flechas tienen una codificacin particular, pero obedecen a los valores: 72 para arriba, 77 para
derecha, 80 para abajo y 75 para izquierda. De hecho, pulsar una tecla es pulsar dos teclas: la tecla a
(valor 224) y una de las letras correspondientes a los valores ascii 72, 77, 80, 75.

#include <stdio.h> // para el uso de la funcin de impresin por pantalla


#include <stdlib.h> // para la funcin srand() y rand()
#include <time.h> // para inicializar srand() con time()
#include <conio.h> // para las funciones kbhit() y getch()
#include <windows.h> // para escribir la funcin gotoxy mostrada a continuacin

void gotoxy(int x, int y)


{
COORD c;

c.X = x;
c.Y = y;
SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), c);
}

int main()
{
int fin=0, res, x, y; //1
const int TX=20, TY=15;

srand(time(NULL));
x=rand()%TX;
y=rand()%TY;
gotoxy(x,y);
putchar(X);

while (! fin){ // equivalente a fin==0 //2

if( kbhit()){ //3

gotoxy(x,y); //4
putchar( );

res=getch(); //5
switch(res){
case 72 : y--; break; // arriba
case 77 : x++; break; // derecha
case 80 : y++; break; // abajo
case 75 : x--; break; // izquierda
case 224 : break; // evacuar la tecla combinada
default : fin=1; break; // otra tecla:
//poner fin al prg
}
if (x<0) //6
x=TX;
if (x>TX)
x=0;
if (y<0)
y=TY;
if (y>TY)
y=0;
gotoxy(x,y); //7
putchar(X);

}
}

return 0;
}
(1) Tenemos cuatro variables. La variable fin, inicializada a 0, controla el bucle. Cuando fin no sea
igual a 0, el bucle finalizar. La variable res sirve para recuperar las entradas del teclado. Las variables
x e y memorizan las posiciones horizontal y vertical de la letra que vamos a desplazar. TX y TY son
dos valores constantes, es decir, que no se pueden modificar en el resto del programa. Se puede atribuir
esta caracterstica mediante la palabra clave const antes del tipo. TX se inicializa a 20 y TY se inicializa
a 15. Ambos valores corresponden al tamao de la zona de juego.

En primer lugar, la funcin srand() inicializa la serie de nmeros pseudoaleatorios en el reloj interno. A
continuacin, se inicializan las variables x e y. La posicin horizontal x es aleatoria entre 0 y TX, y la
posicin vertical, entre 0 y TY. La llamada a la funcin gotoxy() coloca el cursor de escritura en esta
posicin inicial (x,y) y putchar() imprime la letra X en esta posicin.

(2) Mientras la variable fin sea igual a 0, la expresin sigue siendo verdadera y las instrucciones del
bloque se repiten.

(3) La primera instruccin es una comprobacin del valor de retorno de una llamada a la funcin
kbhit(). En vez de escribir (kbhit()==1), la comprobacin se ha abreviado a if (kbhit()), lo que viene a
ser lo mismo debido a que es el valor de retorno de la llamada a la funcin kbhit() el que da la respuesta
a la comprobacin. Si kbhit() devuelve 0 (ninguna tecla pulsada), la comprobacin es falsa. Si kbhit ()
devuelve 1, la comprobacin es verdadera y se ejecutan las instrucciones del bloque.

(4) El objetivo es mover una letra con las flechas. Para ello, hay que:

1: borrar la letra de su posicin actual.

2: modificar su posicin en funcin de la tecla pulsada.

3: controlar que la nueva posicin permanezca en la zona de juego.

4: mostrar la letra en su nueva posicin.

En el bloque del if (kbhit()), la primera instruccin consiste en ubicar el cursor en la posicin actual de
la letra. La segunda instruccin elimina esta letra mostrando en su lugar un espacio en blanco.

(5) Ahora hay que modificar la posicin de la letra en funcin de la tecla pulsada.

El valor de la tecla pulsada es devuelto por la funcin getch(). Este valor se asigna a la variable res y
sirve para elegir un caso del switch.

La referencia (0,0) de la pantalla del ordenador es siempre la esquina superior izquierda. As que:
Subir significa disminuir el valor de y, que se aproxima a 0.
Bajar significa aumentar el valor de y.
Ir a la izquierda significa disminuir el valor de x.
Ir a la derecha significa aumentar el valor de x.
Si se trata de una flecha hacia arriba, la letra sube e y disminuye en 1.

Si se trata de una flecha hacia la derecha, la letra avanza hacia la derecha y x aumenta en 1.

Si se trata de una flecha hacia abajo, la letra baja e y aumenta en 1.

Si se trata de una flecha hacia la izquierda, la letra avanza hacia la izquierda y x disminuye en 1.

El resto de las teclas pulsadas desencadenan la asignacin a 1 de la variable fin, es decir, el fin del
bucle while en la prxima iteracin, justo despus de finalizar las instrucciones del bloque.

Debido a la codificacin de las flechas, el valor 224 se aade cada vez que se pulsa una flecha,
exactamente como si se pulsaran las dos teclas. Por este motivo, el valor 224 desencadenara el
comando por defecto: finalizacin del programa. Para evitarlo, el caso 224 se aade y no realiza nada.

(6) Si las posiciones vertical u horizontal de la letra se han modificado, puede que la letra se salga de la
zona de juego. Esta zona va de 0 a TX para la horizontal y de 0 a TY para la vertical.

Para controlar los lmites, hay que tomar una decisin: o bien la letra se bloquea al llegar a los bordes, o
bien reaparece en el borde opuesto. Esta ltima solucin es la que hemos elegido. De este modo:

Si x es menor que 0, x sale por la izquierda y reaparece por la derecha, x ahora vale TX.
Si x es mayor que TX, x sale por la derecha y reaparece por la izquierda, x ahora vale 0.
Si y es menor que 0, y sale por la arriba y reaparece por abajo, con el valor TY.
Si y es mayor que TY, y sale por abajo y reaparece por arriba con el valor 0.
(7) Ahora que se ha calculado la posicin, el cursor se desplaza a esta nueva posicin con una llamada a
gotoxy() y la funcin putchar() muestra la letra en su nueva posicin. El bloque if (kbhit()) ha
finalizado. El bucle contina si la variable fin es siempre igual a 0.

4. Puesta en prctica: mens y bucles de eventos

Ejercicio 1

Retomando el cdigo de la leccin:

Aadir un tesoro y, cuando el usuario lo encuentre, ganar un punto. Mostrar la puntuacin.


Aadir un enemigo mvil. Si el usuario lo toca, pierde.
Aadir una pelota para el jugador. Si el jugador se aproxima, puede chutarla y conducirla.
Ejercicio 2

Implementar un pequeo convertidor pesetas-euros. El programa ofrece un men con las dos
posibilidades; al finalizar una conversin ofrece al usuario volver a comenzar o salir (1 euro son
166,386 pesetas).
Ejercicio 3

Implementar el programa que muestra ceros continuamente y, si se pulsa una tecla, un nmero aleatorio
de veces la letra o cifra correspondiente.

Ejercicio 4

Imaginar una mquina distribuidora de bebidas.

Cmo funciona la mquina: interfaz, eleccin, moneda, aprovisionamiento? Qu variables son


necesarias para su realizacin? Escribir el algoritmo y programar una solucin con la entrada/salida
scanf() y printf().

Ejercicio 5

El jugador se desplaza por la pantalla con las flechas. En la zona de movimiento, hay tesoros por
descubrir. El jugador anota puntos en funcin de lo que recolecta en el juego. Implementar el programa.

Ejercicio 6

Dos letras se desplazan en un rea de juego de forma ms o menos aleatoria. Cuando llegan al borde
rebotan y salen en otra direccin. Implementar el programa. A continuacin, modificarlo para que
cuando las dos letras se encuentren, se muestre una frase.

Ejercicio 7

Juego. Se dispone de una decena de letras. A cada letra se le ha asignado un factor de inestabilidad. Por
ejemplo, a la letra M, muy estable, se le ha asignado el factor de inestabilidad 0 y, en cambio, a la
letra V se le ha asignado un factor de inestabilidad 9.

Pulsando la tecla espacio, se apilan verticalmente las letras, elegidas de forma aleatoria por el
programa. El objetivo es llegar a una altura determinada, pero si la suma de factores es mayor o igual a
50, la torre se derrumba. Programarlo.

Ejercicio 8

Un hombrecillo sube por una escalera que da al vaco. Cuando llega a arriba, avanza y se cae al suelo.
En suelo se queja ay!. El contador cuenta un punto. Vuelve a la escalera, la vuelve a subir y vuelve a
caer diciendo ay! y gana otro punto, etc. Programar.

Ejercicio 9

Juego de dados con dos jugadores. Cada jugador lanza el dado en su turno tantas veces como quiera.
Acumula todos los puntos que ha ido obteniendo, a excepcin de si aparece un 1, en cuyo caso no se
anotar ningn punto y est obligado a ceder el turno al otro jugador. El ganador es el primero que llega
a 100.

Implementar un programa para jugar contra el ordenador. El dado no debe estar trucado. El ordenador
debe jugar sin hacer trampas cuando es su turno y debe lanzar el dado cuando es el turno del jugador.
Siempre muestra el resultado de cada tirada y en el turno del jugador, despus de cada tirada, pregunta
si se quiere continuar. Cuando la partida termina, ofrece la posibilidad de jugar otra vez o de salir.

La programacin de este juego supone disear una estrategia para el ordenador. Cundo y cmo
decide plantarse?

Ejercicio 10

Master_Mind. Se juega con varios colores. El jugador debe adivinar una combinacin oculta realizada
con estos colores. Por ejemplo, con 4 colores: ROJO, VERDE, AZUL y AMARILLO, tenemos esta
combinacin AZUL, AMARILLO, AMARILLO y ROJO. El orden es importante.

Para jugar, el jugador propone una combinacin. Por ejemplo, ROJO, VERDE, AMARILLO y AZUL.

En respuesta, el ordenador indica cuntos colores de los expuestos en la combinacin Y estn en la


posicin correcta. En este caso, 1, el AMARILLO en la tercera posicin.

Tambin indica el total de colores acertados, en este caso 2 colores: rojo y amarillo.

Si el jugador acierta, el ordenador le dice el nmero de intentos que ha necesitado y le ofrece la


posibilidad de jugar otra partida o finalizar.

En cada partida el ordenador crea una nueva combinacin oculta y responde a los intentos del jugador.
Escribir el programa.

Ejercicio 11

Implementar el juego de la rana que atraviesa una carretera:

r
.....v........................v......> ... r, la rana, tiene que llegar al otro
...v...........v..................> lado de la carretera sin que ningn
<......v................v........... vehculo v la toque. Hay cuatro
<........v.........v................ carriles con un mximo de dos
vehculos por carril.

Descomponer la realizacin del programa en varias etapas. Por ejemplo: hacer la rana, hacer la
carretera de un carril con un coche, aadir otro coche, aadir un carril y dos coches, aadir dos carriles
en sentido inverso y cuatro coches.
Funciones

1. Qu es una funcin?

Una funcin es una unidad de tratamiento til para el cumplimiento de una accin o de una etapa de la
accin. El diseo de programas de mltiples acciones se basar en un buen desacople en mltiples
funciones.
Permite factorizar (generalizar) el cdigo que se repite. Cuando en un programa una secuencia de
accin se repite, en general se puede escribir una funcin para dejar el cdigo ms claro y ligero.
Tcnicamente la funcin es un bloque de instrucciones dotado de un nombre y dos mecanismos:
Un mecanismo de entrada de valores: son los parmetros de entrada.
Un mecanismo de salida de valores: el valor de retorno (nico).
Una vez se ha escrito la funcin, esta puede ser llamada. La llamada a una funcin permite insertar el
bloque de instrucciones de la funcin en el punto del desarrollo del programa en el que se invoca. En
ese instante, se le proporcionan para su ejecucin unos valores a los parmetros y, si procede, tambin
podemos obtener el valor de retorno.
Una funcin tiene la siguiente forma:

<tipo de retorno> <nombre funcin> < ( lista de parmetros ) >

{ apertura bloque

(...) // las instrucciones del bloque

return (...) //instruccin de retorno si devuelve un valor

} cierre del bloque


La primera lnea constituye lo que se denomina la cabecera de la funcin. El bloque de instrucciones
es el cuerpo de la funcin.

La cabecera incluye el tipo del valor de retorno, el nombre de la funcin y la lista de parmetros entre
parntesis. Los parmetros son simplemente variables locales a la funcin que tienen la particularidad
de poder recibir valores cuando se llama a esta. Si no hay parmetros, la lista queda vaca. El valor de
retorno tampoco es obligatorio. Si no hay valor de retorno, el tipo de retorno es void. A una funcin que
no devuelve valores tambin se la llama procedimiento.

2. Escribir una funcin

a. Dnde se escriben las funciones?

Las funciones se escriben en archivos fuente de C, antes del mtodo main() o de cualquier otro.

b. Condiciones que se deben cumplir

Para escribir una funcin, hay que:

Definir el objetivo de la funcin: Para qu sirve la funcin? Qu hace?


Dar un nombre a la funcin. Los nombres de funciones deben respetar las mismas restricciones que los
nombres de las variables: no pueden ser palabras clave, ni operadores, ni empezar con un nmero y, si
el nombre est formado por varias palabras, tampoco hay que usar el espacio.
Indicar el tipo de valor de retorno. A la izquierda del nombre se especifica OBLIGATORIAMENTE el
tipo del valor de retorno. Es necesariamente un tipo de variable simple (char, short, int, long, float,
double, puntero) o una estructura (ver captulo Variables de conjunto). Si no hay valor de retorno, es el
tipo void (nada) el que debe especificarse.
Indicar la lista de parmetros. Los parmetros son simplemente variables locales a la funcin
declarados entre los parntesis a la derecha del nombre de esta. Lo que los distingue del resto de las
variables locales es que inicializan sus valores en el momento de la invocacin de la funcin.
c. Ejemplo de funcin sin retorno ni parmetros

Escribir una funcin que muestre hola un nmero aleatorio de veces.

La funcin se llama hola1, no devuelve valores ni tiene parmetros:

void hola1()
{
int num;
num=1+rand()%10; // un valor entre 1 y 10 incluidos
printf("hola1() imprimir %d veces hola:\n",num);
while(num--)
printf("hola\n");
}
La llamada a esta funcin se detalla en la seccin Utilizar una funcin.

d. Ejemplo de funcin con retorno, pero sin parmetros

Queremos escribir una funcin que muestre un nmero aleatorio de veces la palabra hola y que
devuelva el nmero de veces que se ha impreso el saludo por pantalla. La funcin se llama hola2,
devuelve un valor de tipo int y no tiene parmetros:

int hola2()
{
int num, i;

num=1+rand()%10;
printf("hola2():\n");

// modificar el bucle para no perder el valor de num


for (i=0; i<num; i++)
printf("hola\n");
// instruccin para devolver el valor de num
return num;
}
La instruccin return permite devolver un valor al contexto de la llamada de la funcin. Esta
instruccin provoca una salida inmediata de la funcin y puede usarse en algunos casos para salir
prematuramente de una funcin.

La llamada a esta funcin se detalla en la seccin Utilizar la funcin.


e. Ejemplo de funcin sin retorno con un parmetro

Queremos escribir una funcin que muestre por pantalla hola tantas veces como el valor dado por
parmetro. La funcin se llama hola3, no devuelve valores y tiene un parmetro de tipo int:

void hola3(int num)


{
printf("hola3() mostrar %d hola:\n",num);
while (num--)
printf("hola\n");
}
La llamada de esta funcin se detalla en la seccin Utilizar la funcin.

f. Ejemplo de funcin con retorno y un parmetro

Queremos escribir una funcin que muestre hola un nmero determinado de veces y devuelva este
nmero. Por parmetro se facilita el nmero mnimo de impresiones por pantalla. Este nmero mnimo
aumenta con un valor aleatorio en la funcin y el nmero total de impresiones por pantalla se devuelve
en el contexto de la llamada.

int hola4(int min)


{
int max,i;
printf("hola4() :\n");
max=min+rand()%5;
for (i=0; i<max; i++)
printf("hola\n");
return max;
}
La llamada a esta funcin se detalla en la seccin Utilizar la funcin.

g. Conclusin: cuatro casos de escritura de funciones

Una funcin puede o no devolver un valor y puede o no tener parmetros:

Funciones
Retorno
Parmetro(s)
caso 1
NO
NO
caso 2
S
NO
caso 3
NO
S
caso 4
S
S
3. Utilizar la funcin

a. Llamada a la funcin

Una vez se ha escrito la funcin, solo falta invocarla en el punto donde queremos que se ejecute dentro
del programa. A continuacin se muestra un programa que llama a la funcion1() un nmero (num) de
veces entrado por el usuario:

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

int main()
{
int num,i;

srand(time(NULL));
printf("entrar el nmero de repeticiones: \n");
scanf("%d",&num);

for(i=0; i<num; i++){


printf("llamada %d :\n",i);
hola1(); // llamada a hola1()
}

return 0;
}
b. Recuperacin del valor de retorno

Para recuperar el valor de retorno, basta con asignar, a una variable del contexto de llamada, la llamada
a la funcin de la forma siguiente:

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

int main()
{
int val;

srand(time(NULL));

val = hola2(); // asignacin del retorno a val

printf ("retorno de hola2(): %d\n",val);


return 0;
}
El valor numrico de la llamada a la funcin es el valor de retorno; para asignar el valor de retorno sin
guardarlo, podemos escribirlo as:
#include <stdio.h>
#include <stdlib.h>

int main()
{
srand(time(NULL));
printf ("retorno de hola2() : %d\n",hora2());
return 0;
}

La recuperacin del valor de retorno no es obligatoria. Pero si el valor devuelto no se recupera, se


pierde.

c. Paso de valores a los parmetros

Para pasar valores por parmetro, basta con especificar para cada parmetro el valor que deseemos
asignarle en la llamada a la funcin, por ejemplo:

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

int main()
{
int num;

srand(time(NULL));

printf("prueba 1 ----------------------\n");
hola3(5);

printf("prueba 2 ----------------------\n");
hola3(rand()%2);

printf("prueba 3 ----------------------\n");
printf("entrar el nmero de repeticiones:\n");
scanf("%d",&num);
hola3(num);

printf("prueba 4 ----------------------\n");
num=hola4(num);
printf("retorno hola4: %d\n",num);
return 0;
}
En este ejemplo pasamos un 5 en la primera llamada a la funcin hola3(), o 0 o 1 en la segunda, y un
valor entrado por el usuario en la tercera.

La llamada a hola4() toma el valor de num que ha entrado el usuario. Este mismo valor se devuelve
modificado al final de su ejecucin a la variable num del contexto de la llamada.
d. Precisin sobre el paso por valor

Sea la funcin:

void modif (int a, int b)


{
a=1000;
b=2000;
}
y su llamada en el programa:

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

int main()
{
int a=0, b=0;

modif(a,b);
printf("a=%d, b=%d\n",a,b);

return 0;
}
Segn usted, qu imprime el programa por pantalla?

En el momento de su llamada, los valores 0 y 0 se asignan a los parmetros a y b de la funcin modif().


Son valores locales a la funcin. Solo existen dentro del bloque de la funcin. La funcin modifica sus
valores, que pasan a ser 1000 y 2000.

A continuacin, volvemos al contexto de la llamada. Cunto valen a y b? Ambas variables (a y b) son


locales al bloque del main(). No han sido modificadas. No son las mismas que las de la funcin. Por lo
tanto, printf() imprime por pantalla:

a=0, b=0

Como podemos ver en este ejemplo y en los anteriores, las variables declaradas en un bloque son
locales al bloque donde se han declarado y desaparecen al final de la ejecucin del bloque. Podemos
mover valores en el programa gracias a las entradas y salidas de las funciones.

e. Visibilidad y declaracin de funciones

Una funcin es global, es decir, visible para todo el programa independientemente de su posicin en los
archivos de cdigo fuente que lo componen, a condicin de que se d a conocer antes de su primera
llamada en tiempo de compilacin.

En efecto, hay un error de compilacin si, por ejemplo, una funcin definida despus del main() se
invoca en el main(). Para evitar este problema hay dos soluciones:

Definir la funcin antes del main(), arriba. Est muy bien para programas pequeos que tengan pocas
funciones y con un solo archivo de cdigo fuente.
Declarar la funcin. Es metodolgicamente mejor incluso para programas pequeos e indispensable
para programas ms grandes.
Cmo declarar la funcin?

Es muy sencillo, basta con copiar la cabecera de la funcin, pegarla en el sitio en el que se desee poner
la declaracin (por ejemplo, encima del main()) y aadirle un punto y coma. Si no tiene parmetros,
hay que especificar la palabra void entre los parntesis.

A continuacin se muestra un ejemplo con la declaracin de nuestras cuatro funciones hola():

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

// declaracin de funciones:
// <tipo de retorno> <nombre> <(parmetros)> <;>
void hola1 (void);
int hola2 (void);
void hola3 (int num);
int hola4 (int min);

// el programa
int main()
{
hola1();
hola2();
hola3(5);
hola4(5);
return 0;
}
// y las funciones se definen despus del main()
(...)
Para programas ms grandes, las declaraciones de funcin se agrupan en una o varias libreras. Estas
libreras se incluyen con la directiva #include en los archivos de cdigo fuente que utilizan estas
funciones (ver captulo Variables de conjunto - Estructuracin de un programa, estudio de un autmata
celular).

4. Ejemplo de funciones con una lista variable de parmetros

Tambin es posible en C escribir funciones que tengan un nmero variable de parmetros. Es el caso de
las funciones printf() y scanf(), por ejemplo. Pero resulta bastante raro, ya que es poco prctico en este
lenguaje.

Este tipo de funciones deben tener al menos un parmetro fijo que preceda a la lista de parmetros
variables. Esta lista se representa con tres puntos ... en la cabecera, donde van los parmetros. Los
parmetros pueden ser del mismo tipo o de tipos diferentes. En ambos casos, se trata de recuperar una
lista de parmetros y de saber su final. En el segundo caso, adems, hay que encontrar algn mtodo
para obtener el tipo de cada parmetro.
La lista de parmetros se gestiona con cuatro instrucciones definidas en <stdarg.h>. Denominamos
UPF al ltimo parmetro fijo, el que precede a la lista de parmetros variables y determina el punto de
partida. Las cuatro instrucciones son:

va_list
es el tipo de la lista de recuperacin.
va_start (va_list lista, UPF)
es la funcin de inicializacin de la lista.
va_arg (va_list lista, type param)
es la funcin para la recuperacin de cada elemento de la lista.
va_end(va_list lista)
es la funcin que libera memoria de la lista formada de manera dinmica (la asignacin de memoria
dinmica se trata en el captulo Variables puntero).
a. Lista variable de parmetros del mismo tipo

Para comprobar una lista de parmetros del mismo tipo, a continuacin se muestra una funcin que
efecta la suma de los parmetros enteros que se le pasen:

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

int suma(int num,...)// 1 cabecera de la funcin con lista


{
va_list lparam; // 2 declarar una lista
int res=0,n;
va_start(lparam,num); // 3 inicializar la lista

for(n=0; n<num; n++) // 4


res+= va_arg(lparam,int);// 5 recuperacin del valor
// de cada parmetro

va_end(lparam); // 6 liberar la memoria de la lista


// que acabamos de construir
return res;

int main(int argc, char*argv[])


{
printf("%d\n",suma(1,1)); // muestra 1
printf("%d\n",suma(3,1,2,3)); // muestra 6
printf("%d\n",suma(5,1,2,3,4,5)); // muestra 15
return 0;
}
(1) La cabecera de la funcin. El ltimo parmetro fijo es int num. A continuacin, los tres puntos
indican que se trata de una lista variable de parmetros.
(2) Para empezar, declarar una variable va_lista.

(3) A continuacin, llamar a la funcin va_start, que inicializa la variable va_lista.

(4) El ltimo parmetro fijo (UPF) se usa para obtener el nmero de parmetros en el momento de la
invocacin. Es el que informa el final de la lista.

(5) La llamada de la funcin va_arg() devuelve el valor de cada parmetro; en este caso, se van
sumando a medida que se van obteniendo.

(6) Al final, hay que liberar la memoria asignada.

b. Lista variable de parmetros de tipo distinto

Si el tipo de los parmetros es diferente, hay que implementar un mecanismo para el reconocimiento de
tipos de parmetros. Por ejemplo, cada tipo de parmetro puede identificarse con un cdigo, un valor
entero, siendo por ejemplo el 0 para int, el 1 para double, el 2 para cadena de caracteres, etc. A
continuacin, en el momento de la llamada, cada parmetro es precedido por un cdigo que
corresponde a su tipo. Es lo que hace la siguiente funcin test():

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

enum{t_int,t_double, t_string,t_end}; // 1 una enumeracin

void test(int tipo,...) // 2


{
va_list lparam; // 3
int fin=0;
int i;
double f;
char*s;

va_start(lparam,tipo); // 4

while(!fin){
switch(tipo){ // 5
case t_int :
i=va_arg(lparam,int);
printf("int %d\n",i);
break;

case t_double :
f=va_arg(lparam,double);
printf("double %lf\n",f);
break;

case t_string :
s=va_arg(lparam,char*);
printf("int %s\n",s);
break;

case t_end :
fin=1;
break;
}
tipo=va_arg(lparam,int); // 6
}
va_end(lparam); // 7
}

int main()
{
test( t_int, 10, // 8
t_int,20,
t_string, "hola",
t_double, 3.5,
t_end);

return 0;
}
(1) Todos los cdigos se agrupan en una enumeracin. Este tipo de datos se explica en el captulo
Variables conjunto (estructuras y tablas), que es justamente el siguiente, pero no tiene la menor
dificultad. La instruccin enum siempre viene acompaada de un bloque que contiene una lista de
nombres. Cada nombre se corresponde con un valor constante. Por defecto, el primero vale 0 y todos
los dems se incrementan de uno en uno, por ejemplo:

enum{ va, ve, vi, vo };


significa que:
va vale 0
ve vale 1
vi vale 2
vo vale 3
De este modo, en nuestro ejemplo:

t_int vale 0
t_double vale 1
t_string vale 2
t_end vale 3
(2) La cabecera de la funcin test(), que declara una lista variable de parmetros.

(3) Declaracin de la lista lparam.

(4) Inicializacin de la lista que comienza con el parmetro int tipo.

(5) Conmutacin, segn el valor de tipo; se tratar un caso u otro, el parmetro correspondiente se
retirar de la lista.
(6) Recuperacin del tipo para el caso siguiente. Cuando se encuentre t_end, el bucle finalizar.

(7) Al final, cuando se han tratado todos los elementos, se destruye la lista.

(8) Llamada a la funcin test() con 9 parmetros, 4 cdigos de tipo y 4 valores, uno por cada tipo ms
el cdigo especfico para el final de lista t_end. La llamada produce la impresin por pantalla de:

10
20
hola
3,5
c. Transformar printf()

Se trata de transformar la funcin printf(), de forma que pueda escribir en cualquier sitio de la ventana
de la consola y en un color determinado. La posicin y el color se obtienen con las funciones gotoxy()
y textcolor().

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <windows.h> // para las funciones gotoxy()
// y textcolor()

// declaracin de funciones
void G_printf(int x, int y, int color, char*format,...);
void gotoxy(int x, int y);
void textcolor(int color);

int main()
{
G_printf(5,5,12,"nm: %d",2341 ); // 0
return 0;
}
void G_printf(int x, int y, int color, char*format,...) //1
{
va_list lparam;
char stock[10000]; // 2

va_start(lparam,format);
vsprintf(stock,format,lparam); // 3
va_end(lparam);

// mostrar en la posicin adecuada con el color adecuado // 4


gotoxy(x,y);
textcolor(color);
printf("%s",stock);
}
void gotoxy(int x, int y)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(h,c);
}
void textcolor(int color)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(h,color);
}
(0) La llamada a la funcin G_printf() muestra "nm: 2341" en la posicin 5,5 de la ventana de la
consola y con el color 12 (rojo).

(1) La cabecera de la funcin, que tiene tres parmetros fijos y una lista variable de parmetros.

(2) La cadena de formato se obtiene con los valores asociados a los formatos integrados, en la tabla de
caracteres stock (las tablas se estudian en el captulo siguiente).

(3) La funcin estndar vsprintf() permite recuperar, en una tabla de caracteres, una cadena con formato
como la de printf(), con el formato aplicado de una va_list. Despus de esta llamada, la cadena se
almacena en la tabla stock (los formatos se reemplazan por los valores efectivos representados con
caracteres).

(4) Una vez se ha construido la cadena, solo hay que mostrarla en la posicin facilitada en x e y y con
el color de la variable color.

5. Puesta en prctica: funciones

a. Identificar los componentes de una funcin

Ejercicio 1

Sea la funcin siguiente:

void wait(int tmps)


{
int start=clock();
while (clock()<start+tmps){}
}
Determinar el nmero y el tipo de los parmetros, el nombre de la funcin, el tipo del valor de retorno y
las variables utilizadas. Qu hace la funcin?

Ejercicio 2

Escribir la cabecera de una funcin que sirva para calcular la distancia entre dos puntos (la distancia se
calculara usando el teorema de Pitgoras). Determinar el conjunto de recursos necesarios para escribir
la funcin.
b. Declaracin de funciones

Ejercicio 3

Cules son los mensajes de error producidos por el programa siguiente?

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

int main()
{
mostrar_hola(5);
return 0;
}

void mostrar_hola (int num)


{
int i;

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


puts("hola");
}
Modificar el programa para que funcione. Dar dos soluciones.

c. Procedimientos sin parmetro

Ejercicio 4

Para probar la funcin rand() estadsticamente, implementar una funcin que obtenga 100000 veces un
valor comprendido entre 0 y 5, cuente las veces que se da cada resultado y muestre el porcentaje de
cada resultado. Llamar a esta funcin en un programa.

Ejercicio 5

Implementar una funcin que muestre un tablero de ajedrez de 20 por 15 por pantalla. El tablero tiene
un marco verde. Probar la funcin en un programa.

Ejercicio 6

Implementar una funcin que muestre el alfabeto en bucle en un rectngulo de 20 lneas por 40
columnas. Cada alfabeto tendr sus propios colores. Probar el programa.

Ejercicio 7

Implementar una funcin que genere automticamente y muestre una frase simple distinta en cada
llamada. Probar en un programa que muestra una frase cada vez que se pulsa una tecla del teclado. El
programa finaliza si se pulsa la letra q.
Ejercicio 8

En un programa, un men ofrece las acciones siguientes:

Bombardeo de un nmero aleatorio de letras multicolor en la ventana de la consola.


Bombardeo de un nmero aleatorio de lneas verticales, horizontales o diagonales. Los tamaos son
siempre aleatorios. Se aconseja implementar una funcin por cada tipo de lnea.
Dibujar un rectngulo de tamao aleatorio en la ventana de la consola.
Bombardeo aleatorio de letras, lneas o rectngulos.
Implementar el programa. El programa finaliza si as lo solicita el usuario.

d. Funciones sin parmetros

Ejercicio 9

Escribir una funcin run() que permita mover una letra controlada por las flechas del teclado. La
funcin devuelve un valor cuya interpretacin permite finalizar el programa.

e. Funciones con parmetros

Ejercicio 10

Implementar una funcin que muestre el carcter (tabla ASCII) correspondiente a un valor numrico
decimal entre 0 y 255. A partir de esta funcin, implementar una segunda funcin que muestre toda la
tabla ASCII.

Ejercicio 11

Implementar una funcin que devuelva la media de tres nmeros, sean cuales sean (en coma flotante o
no). Probarla en un programa. El programa finaliza solo cuando el usuario lo solicita.

Ejercicio 12

Escribir una primera funcin que indique si un nmero entero es mltiplo de 2 o no.

Escribir una segunda funcin que indique si un nmero entero es mltiplo de 3 o no.

Utilizar estas dos funciones en un programa que lea un nombre entero y determine si es par, mltiplo de
3 y/o mltiplo de 6. El programa solo finaliza cuando el usuario lo pide.

Ejemplo de ejecucin:

Introducir un nmero: 9

es mltiplo de 3

Volver a empezar? (s/n)


Otro ejemplo:
Introducir un nmero: 12

es par, mltiplo de 3 y es divisible por 6

Volver a empezar? (s/n)


Ejercicio 13

Sea un plan de impuestos definido de la siguiente manera: para un hogar X con un ingreso total R y un
nmero de n miembros en el hogar, el impuesto queda definido por:

10% de R si R/n <500 euros

20% de R si R/n >=500 euros

Escribir una funcin que calcule la cantidad de importe en funcin de R y n.


Escribir una funcin que calcule los ingresos netos de un hogar despus del pago del impuesto en
funcin de R y n. Probar en un programa. R y n son introducidos por el usuario y se muestra la suma
del importe y de los ingresos netos del hogar.
El programa solamente finaliza si el usuario lo solicita.

Ejercicio 14

Escribir una funcin que recibe por parmetro dos nmeros en coma flotante y un carcter que&ensp;se
corresponde con una operacin (+, -, /, *, %). La funcin devuelve el resultado de la operacin
especificada por el carcter. El programa solo finaliza cuando el usuario lo solicita.

Ejercicio 15

Sea un rea de 24 lneas por 79 columnas mximo. Escribir para empezar una funcin que muestre un
carcter en una posicin dada y con un color dado. A continuacin:

Escribir una funcin que bombardee num veces la pantalla con letras de forma aleatoria. El nmero de
repeticiones es introducido por el usuario.
Escribir una funcin que rellene el rea rectangular con una letra distinta para cada iteracin.
Escribir una funcin que pueda mostrar un rectngulo de letras de un color determinado y un tamao
determinado en el rea.
Escribir una funcin que bombardee num veces el rea con rectngulos (tener especial cuidado de no
escribir fuera del rea permitida).
Escribir una funcin que muestre una espiral rectangular cuyo tamao mximo corresponda a un valor
entrado por parmetro.
En un programa de prueba, ofrecerle el men al usuario. El programa finaliza cuando el usuario lo
solicita.

Ejercicio 16

Sea el programa que se muestra a continuacin:

Describir qu hace y aportar comentarios.


Reemplazar las secuencias de cdigo que se repiten por funciones.
Escribir una funcin que permita dibujar un tringulo en la posicin y con el tamao y el color
facilitados por el usuario.
En qu sitio se puede invocar esta funcin, y con qu parmetros, en el cdigo siguiente?
Escribir una funcin para ralentizar el proceso.
Probar el programa.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
#include <time.h>

/*****************************************************
*****************************************************/
void gotoxy (int x, int y);
void textcolor (int color);

/*****************************************************
*****************************************************/
int main()
{
const int TX = 40;
const int TY = 20;
int x,y,res,i;

int fin=0;

srand(time(NULL));

// comentario 1
for(i=0; i<=TX; i++){
gotoxy(i,0);
textcolor(12*16);
putchar( );
}
// com 2
for(i=0; i<=TX; i++) {
gotoxy(i,TY);
textcolor(12*16);
putchar( );
}
// com 3
for(i=0; i<=TY; i++){
gotoxy(0,i);
textcolor(12*16);
putchar( );
}
// com 4
for(i=0; i<=TY; i++){
gotoxy(TX,i);
textcolor(12*16);
putchar( );
}

// com 5
x=rand()%TX;
y=rand()%TY;

// com 6
gotoxy(x,y);
textcolor(10);
putchar(P);

// com 7
while (!fin){

// com 8
if (kbhit()){

// com 9
gotoxy(x,y);
textcolor(0);
putchar(P);

// com 10
res=getch();
switch(res){
case 72 : y--; break; // 11
case 77 : x++; break; // 12
case 80 : y++; break; // 13
case 75 : x--; break; // 14
case 224 : break;
default : fin = 1; // 15
}

// com 16
if (x < 1)
x = TX-1;
if (x>=TX)
x = 1;
if (y < 1)
y = TY-1;
if (y>=TY)
y = 1;

// com 17
gotoxy(x,y);
textcolor(10);
putchar(P);
}
}
gotoxy(0,TY+2);
return 0;
}
/*****************************************************
*****************************************************/
void gotoxy(int x, int y)
{
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),c);
}
/*****************************************************
*****************************************************/
void textcolor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),color);
}
/*****************************************************
*****************************************************/

Estilo, comentarios e indentacin

1. Por qu tratar el estilo?

El objetivo del estilo de la programacin es asegurar que el cdigo ser fcil de leer y de compartir en
un equipo. Es indispensable un buen estilo en toda buena programacin.

Un programa sin rigor es difcil de leer, poco fiable, difcil de corregir y de comprender. Rpidamente
se vuelve imposible de programar y al final se congela en su incomprensibilidad. Cuanto ms
incomprensible es un programa por la ausencia de rigor en su presentacin, menos posible es continuar
con su desarrollo, aportar modificaciones o nuevas funcionalidades o simplemente depurar. El estilo
est relacionado con la eficacia del diseo. Hoy en da el estilo se ha normalizado y hay asociados una
serie de consejos y recomendaciones que varan muy poco de una comunidad de desarrollo a otra. El
estilo no es un tema secundario; es una cuestin de disciplina, de rigor y eficacia en la realizacin de un
trabajo. Muestre su cdigo y simplemente con su estilo todo contratista sabr si es un buen profesional
o no.

El creador del lenguaje C, Brian Kernighan, nos dice a propsito del estilo: El cdigo ser sencillo y
limpio -lgica clara y evidente, expresiones naturales, uso de un lenguaje convencional, nombres
comprensibles y con significado, formato impecable, comentarios tiles- y evitar las especificidades
de tipo truco, as como las construcciones inslitas. Es importante ser coherente, ya que terceras
personas encontrarn de este modo su cdigo ms legible -y lo mismo le pasar a usted con el trabajo
de otros- si realiza todo el trabajo siguiendo un mismo patrn. (KERNIGHAN, La prctica de la
programacin, p. 2).
2. Tipografa y eleccin de nombres

Lo ideal es un nombre conciso, fcil de recordar, si es posible pronunciable y que d informacin til
sobre una variable, una funcin o cualquier objeto que cree en el programa. De forma coherente, d a
los objetos parecidos nombres similares, con una posible precisin sobre las relaciones que existen
entre ambos o sus diferencias. Evite nombres que puedan prestarse a la confusin o ser ambiguos en el
programa.

Los nombres de las variables globales se escriben en maysculas, los de las variables locales, en
minsculas. Si fuera necesario, los nombres pueden estar formados de varias palabras, cada una
empezando con una letra mayscula:

int posHorizontalJugador;
int posVerticalJugador;
Este formato de los nombres se usa mucho en la comunidad Java y en Microsoft. Pero, en este ejemplo,
ambos nombres son demasiado largos y es mejor darle prioridad a la claridad y a la concisin:

int playerx;
int playery;
Las notaciones del tipo "m_tipo" se consideran "_muy_deficientes" y se deben evitar. Lo ideal es una
economa en el trmino sin perder claridad.

3. Indentacin rigurosa y llaves

Es esencial indentar bien los programas para mostrar la estructura jerrquica de las instrucciones. Esta
reparticin grfica del cdigo en el texto es necesaria para su legibilidad, su claridad y su comprensin
cuando se trata de encontrar un error.

Una indentacin es un avance de 3 o 4 caracteres como mximo en el comienzo de la lnea. La


indentacin sigue la jerarqua de bloques, cada anidamiento implica una indentacin.

La llave de apertura se coloca inmediatamente a la derecha de la condicin. La llave de cierre est


siempre al mismo nivel que la instruccin anterior a la condicin (if, while, etc.). No hay nada en la
lnea despus de las llaves.

Los parntesis de la condicin se separan con un espacio de la instruccin if, while, etc.:

while (!fin){

if (kbhit()){
switch (getch()){
case 1 :
break;

}
}
}
Tambin est muy extendida la costumbre de poner la llave de apertura justo debajo de la instruccin,
antes de la condicin de la forma siguiente (este es el modelo por defecto en Visual Studio):
while (! fin)
{

if (kbhit())
{
switch (getch())
{
case 1 :
break;
}
}
}
Para evitar olvidarse de una llave en cualquier parte del programa, hay que seguir esta sencilla regla:
una vez que abra un bloque, cirrelo inmediatamente y a continuacin aada las lneas necesarias para
escribir el cdigo (correctamente indentado) entre ambas llaves.

Para las instrucciones de condicin, este es el resultado:

if (condicin) {
/*instrucciones*/
}
else {
/*instrucciones*/
}
El nico caso en el que hay texto despus de la llave de cierre es con un do-while:

do {
/* instrucciones*/
} while (condicin);
El switch con este patrn:

switch (expresin) {
case n:
/*instrucciones*/
break;
default:
/*instrucciones*/
break;
}
Las separaciones no son mayores que un espacio. En general, como en un texto normal, hay un espacio
despus de una coma o un punto y coma. El espacio entre la llave de apertura y la condicin no es
obligatorio. A continuacin, algunos ejemplos de espacios y separaciones:

int test;
if (condicin) {

}
for (i=0; i<10; i++) {
}
res = rand()%20;
De forma general, conserve el estilo grfico elegido para las llaves y los espacios a lo largo del
programa o de todos los programas.

4. Parntesis para eliminar ambigedades

Una expresin como:

if (x >0 && x <100 || y >0 && y<100)


puede ser confusa. Incluso para un programador experimentado, es fcil equivocarse a veces con la
prioridad de los operadores y las reglas de asociatividad. Para evitar cualquier ambigedad, lo mejor es
detallar los grupos deseados:

if ( ((x >0) && (x <100)) || ((y >0) && (y<100)) )


5. Comentarios pertinentes

El objetivo de un comentario en el cdigo es hacer el cdigo ms comprensible, por ejemplo


explicando brevemente un detalle o dando informacin sobre el algoritmo y la seccin en cuestin.

Evite repetir con un comentario lo que ya es evidente con el cdigo. Evite comentarios intiles para
cosas evidentes del tipo:

// por defecto
default :
break;

/* se devuelve cero */
return 0;

contador++: // se incrementa el contador

// inicializar x con un valor aleatorio


x=rand();
Estos comentarios son intiles, es mejor eliminarlos porque no aportan informacin sobre el cdigo.
Solo ocupan espacio y entorpecen la lectura del cdigo.

No hay que perder de vista que el comentario est para ayudar a la comprensin del cdigo. Sin
embargo, si el cdigo no es correcto, es mejor reescribirlo que comentarlo.

Atencin a los comentarios que se vuelven contradictorios con el cdigo: al inicio escribe cdigo y crea
un comentario y ms tarde cambia el cdigo y olvida actualizar el comentario. El resultado es que
cuando alguien lee el cdigo el comentario le pone las cosas ms difciles.

La regla es, en la medida de lo posible, dejar siempre claras las cosas y sobre todo no hacerlas ms
confusas.

6. Puesta en prctica: estilo, indentacin, comentarios


Ejercicio 1

Qu hace el programa siguiente?

int main(int argc, char *argv[]){int elec,res;printf ( " 1:


Decir hola \n"" 2: Decir hace buen da\n"" 3: Introducir un
nmero entero\n"" 0: Salir
\n");do{scanf("%d",&elec);rewind(stdin);switch(elec){case
0:break;case 1:printf(" hola\n ");break;case 2:printf("hace
buen da\n ");break;case 3:printf("Introducir un nmero
:\n");scanf("%d",&res); printf ("el nmero es:
%d",res);break ;default:printf("No es un comando
%d\n",elec);break;}}while(elec !=0);return 0;}
No hay errores de sintaxis. Aparte de algunos saltos de lnea, el programa funciona. Reescribirlo
completamente dndole un estilo. A continuacin, explicar lo que hace en un archivo aparte y, para
acabar, probar el cdigo.

Ejercicio 2

Qu hace el programa siguiente?

int main(int argc, char *argv[]){int res ;printf("pulse teclas


: (q para salir) \n");while ( res!=q){if (kbhit())
{res=getch() ;printf(" tecla %c pulsada, valor ascii:
%d\n",res,res) ;}printf("el bucle vuelve a iterar\n");}return 0;}
Rehacer el estilo de este cdigo. Explicar qu hace. Probarlo.

Ejercicio 3

Qu hace el programa siguiente?

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


<conio.c>
int main(int argc, char *argv[]){const int aborigen=20;const int
castillo=10;int
porque=0,marcial,manzana,level;srand(time(NULL));manzana=1+rand()
%aborigen;level=1+rand()%castillo; gotoxy(manzana,level);
putchar(o);while(!porque){if(kbhit())
{gotoxy(manzana,level);putchar( );marcial=getch();switch(marcial)
{case 72:level--;break;case 77: manzana++; break;case 80:level+
+;break;case75:manzana--;break;default:porque=1; break ; }if
(manzana<1)manzana=aborigen-1;if (manzana>aborigen-1)manzana=1;
if(level<1)level=castillo-1;if(level>castillo-
1)level=1;gotoxy(manzana,level) ;putchar(o);}}return 0;}
Rehacer el estilo de este cdigo. Explicar qu hace, renombrar las variables consecuentemente. Probar
y encontrar el error.
Tupla
1. Qu es una tupla?

En programacin, una tupla es un tipo de datos que permite agrupar en una sola unidad diversas
variables, frecuentemente de tipos distintos. Se trata de UNA variable que es un conjunto de variables.
Es muy til para componer objetos complejos que tienen diferentes facetas. Por ejemplo, si alguien
quiere hacer un registro de reuniones, cada encuentro implica manejar informacin del tipo:

Un nombre.
Un lugar.
Una fecha.
Una hora de inicio.
Una hora de finalizacin.
Una categora (oficina, personal, ocio, etc.).
Etc.
Todos estos datos pueden agruparse en el interior de una tupla, y con varias tuplas de este tipo se
pueden albergar varias citas. Lo interesante es poder tener un nmero ilimitado de citas.

Otro ejemplo: en un videojuego 2D, es un enemigo, definido por una posicin, un desplazamiento, un
tipo (escalador, rpido, serpenteante, pesado, violento), un factor de agresividad, un color, una o varias
imgenes, etc. Para cada uno de estos datos, primeramente se trata de elegir un tipo de variable
apropiado y a continuacin agruparlos en el interior de una tupla llamada enemigo para poder tener
uno o varios enemigos en el juego.

Cada elemento de la tupla se llama campo. Nuestra tupla reunin tiene al menos 6 campos y la
tupla enemigo tiene al menos 8 campos (posicin y desplazamiento horizontales y verticales, tipo...).

2. Poder usar una tupla en un programa

a. Definir un tipo de tupla

Para definir una tupla, se utiliza la palabra clave struct seguida de una lista de declaraciones de
variables (sin inicializar) entre llaves y un punto y coma:

struct {
float x,y; // atencin, no se pueden inicializar las
float dx,dy; // variables en la definicin de la
int color // tupla
char letra;
};
El ejemplo anterior es una tupla perfectamente vlida, pero, como no tiene nombre, no se puede utilizar
en todo el programa. Para dar nombre a su tipo de tupla, simplemente basta con aadir el nombre
deseado despus de la palabra clave struct:

struct entidad { // nombre de su tupla


float x,y;
float dx,dy;
int color
char letra;
};
Acabamos de definir el tipo de tupla struct entidad. Atencin, es un nombre de tipo y no interfiere
con los nombres de variable.

b. Declarar las variables de la tupla

Despus de haber definido un tipo de tupla, para crear variables de este tipo se sigue el mismo
procedimiento que con variables de otros tipos. Hay que indicar el tipo, dar un nombre a la variable y
finalizar con un punto y coma.

Para tener variables del tipo struct, entidad que hemos definido anteriormente, escribimos en algn sitio
en el programa:

struct entidad e1, e2;


e1 y e2 son dos variables de tipo struct entidad.

Observacin:

El nombre del tipo de la tupla es independiente del de la variable de este tipo, y se puede tener una
variable con el mismo nombre:

struct entidad entidad; // funciona


Adems, se puede combinar una definicin de tipo de tupla y las variables de este tipo de tupla. A la
definicin de la tupla pix que mostramos a continuacin, se le puede aadir al final una serie de
declaraciones de variables:

struct pix{
int x, y;
int color;
}P1, P2, toto;
La definicin de la estructura pix va seguida de las declaraciones de variables P1, P2 y toto, que son
tres tuplas de tipo struct pix.

3. Utilizar una tupla

a. Acceso a los elementos con el operador punto

Todas las variables de una tupla, es decir, todos los campos de la tupla, son accesibles a travs del
operador . llamado punto, de la forma siguiente:

struct pix p;

p.x = 0;
p.y = 50;
p.color = 255;
En la primera lnea se declara una tupla de tipo struct pix llamada p. Las tres lneas siguientes
inicializan cada uno de los campos de la variable p. El campo x toma el valor 0, el campo y toma el
valor 50 y el campo color, el valor 255.
b. Prioridad del operador punto

Tiene la prioridad ms alta. El resto de los operadores se procesan despus en la evaluacin de una
expresin (ver Anexo - Prioridad y asociatividad de operadores).

c. Una tupla como campo de otra tupla

Se pueden anidar tuplas. Una tupla puede tener otra tupla como campo, con la condicin de que su tipo
ya sea conocido en el momento de la definicin de la tupla. Por ejemplo:

struct rgb{ // valor de rojo, verde y azul


int r,g,b; // para tener un color
};

struct coord{
int x, y; // para almacenar una posicin en 2D
};

struct rect{
struct coord si; // coord de la esquina superior-izquierda
struct coord id; // coord de la esquina inferior-derecha
struct rgb color; // color del rectngulo
};
La tupla rect permite almacenar la informacin de rectngulos coloreados. La tupla rgb concentra los
datos del color con tres campos: un entero para el rojo, otro para el verde y otro para el azul (en RGB
-red, green, blue- todo color se obtiene con una mezcla de rojo, verde y azul). La tupla coord permite
almacenar coordenadas de un punto en el espacio bidimensional. Para disear un rectngulo
necesitamos dos puntos, el punto de arriba a la izquierda y el punto de abajo a la derecha:

images/03ri01.png
La tupla final para almacenar los datos de un rectngulo incluye una tupla rgb para su color y dos tuplas
coord para cada uno de los puntos superior-izquierdo (si) e inferior-derecho (id).

En este ejemplo, las tuplas rgb y coord deben haberse definido completamente antes que la tupla rect
para ser conocidas cuando se usan en la definicin. En caso contrario, se producir un error de
compilacin.

El acceso a los campos de una tupla se realiza con el operador . (punto) y nada cambia si el campo es
en s mismo una tupla. El operador punto se volver a usar de nuevo para acceder a los campos de la
tupla elemento de la siguiente forma:

struct rect r1;


r1.si.x = 0;
r1.si.y = 0;
r1.id.x = 100;
r1.id.y = 50;
r1.color.r = 255;
r1.color.g = r1.color.b = 0;
Por ejemplo, el tipo de la expresin (r1.si) es una tupla de tipo struct coord y se accede a cada uno de
sus campos usando de nuevo el operador punto; se obtienen las expresiones (r1.si).x y (r1.si).y. En las
dos expresiones anteriores, los parntesis carecen de utilidad.

d. Inicializar una tupla en la declaracin

Una tupla puede inicializarse en la declaracin asignndole una serie de valores entre llaves y
finalizando la instruccin con un punto y coma. Cada valor se asigna al campo correspondiente en el
orden que se ha declarado y se debe respetar el tipo. Sea la estructura test:

struct test {
float x, y;
int px, py;
char letra;
};
y una declaracin seguida de una inicializacin en el programa:

struct test t1={ 1.5, 2.0, 150,300,A};


Para la variable t1 de tipo struct test, 1.5 se asigna al campo x, 2.0 se asigna al campo y, 150 se asigna
al campo px, 300 al campo py y al campo letra se le asigna el valor A.

Si hay tuplas anidadas, se puede elegir entre asignar una sola lista para los valores asignados o detallar
entre llaves cada tupla. Por ejemplo:

struct fecha{
int dia, mes, ejercicio;
};
struct hora{
int hora, minuto, segundo;
};
struct momento{
struct fecha fecha;
struct hora hora;
};
En el programa, este sera un ejemplo vlido de declaracin con inicializacin:

struct momento m1={ 25,12,2006,13,45,30};


Los campos de m1 valen respectivamente 25 para da, 12 para mes, 2006 para ejercicio, 13 para hora,
45 para minuto y 30 para segundo.

Tambin se puede detallar el contenido de cada estructura anidada de la siguiente manera:

struct momento m1={ {25,12,2006},


{13,45,30}
};
Si el nmero de valores de la lista es mayor que el nmero de campos, se produce un error de
compilacin. Sin embargo, si el nmero de valores de la lista es menor, los campos restantes se
inicializan a 0. Esta prueba permite comprobarlo:

int main()
{
struct momento m={{1}, {3,4}};

printf("m.fecha.dia=%d\n",m.fecha.dia);
printf("m.fecha.mes=%d\n",m.fecha.mes);
printf("m.fecha.ejercicio=%d\n",m.fecha.ejercicio);
printf("m.hora.hora=%d\n",m.hora.hora);
printf("m.hora.minuto=%d\n",m.hora.minuto);
printf("m.hora.segundo=%d\n",m.hora.segundo);
return 0;
}
Resultado:

m.fecha.dia=1
m.fecha.mes=0
m.fecha.ejercicio=0
m.hora.hora=3
m.hora.minuto=4
m.hora.segundo=0
e. Copiar una tupla

Las copias y las asignaciones de tupla a tupla del mismo tipo estn permitidas. Por ejemplo:

struct coord pt1, pt2;


pt1.x=100;
pt1.y=200;

pt2=pt1;
La tupla pt1 se inicializa con los valores 100 y 200. A continuacin la tupla pt1 se asigna a la tupla del
mismo tipo pt2. Los valores de pt1 se copian en pt2 (las tuplas se llaman lvalues (por leftvalue)).

4. Puesta en prctica: definir, declarar, inicializar tuplas

Ejercicio 1

Una carpintera industrial gestiona un stock de paneles de madera. Cada panel se caracteriza por su
anchura, su altura y su grosor en milmetros y su tipo de madera. Hay tres tipos de madera: pino
(cdigo 0), roble (cdigo 1) y haya (cdigo 2).

Definir la tupla panel que contenga toda esta informacin relativa a un panel de madera.
En un programa, inicializar dos paneles en su declaracin con valores aleatorios. Mostrar los valores.
Ejercicio 2

Un mayorista de componentes electrnicos vende cuatro tipos de producto:

Placas base (cdigo 1).


Procesadores (cdigo 2).
Mdulos de memoria (cdigo 3).
Tarjetas grficas (cdigo 4).
Cada producto tiene una referencia, que es un nmero entero, un precio en euros y una cantidad
disponible. Definir la tupla producto y en un programa inicializar dos productos en su declaracin y dos
productos con valores entrados por el usuario. Mostrar los valores y clonar el producto ms caro.

Ejercicio 3

Sea un programa que muestra copos de nieve cayendo por la pantalla. Cada copo de nieve es una letra.
Crear una estructura de datos que permita codificar un copo de nieve. En un programa declarar cuatro
copos de nieve. Inicializar uno en la declaracin y otro con valores aleatorios. Copiar cada copo de
nieve y mostrar los cuatro copos de nieve de forma clara.

Ejercicio 4

El mundo en el que se encuentra el jugador est repleto de enemigos. Dar las caractersticas bsicas de
un enemigo imaginario y la estructura de datos mejor adaptada a ellas. Inicializar un enemigo en la
declaracin y otro con valores aleatorios. Mostrar los valores de este enemigo. Hacer un clon.

Tuplas y funciones

1. Devolver una tupla

Como la copia de tuplas est permitida, las tuplas se pueden considerar como valores de retorno. Sea la
estructura coord:

struct coord{
int x, y;
};
A continuacin se muestra una funcin tpica que permite inicializar una tupla despus de su
declaracin sin importar en qu parte del programa se encuentra:

struct coord inicializa_punto1()


{
struct coord t0;
t0.x = rand()%1024;
t0.y = rand()%768;
return t0;
}
En la funcin:

Se declara una tupla del tipo deseado.


Cada uno de los campos se inicializa con valores aleatorios o calculados.
Para finalizar, se devuelve la tupla resultante al contexto de la llamada.
Por supuesto, los valores pueden ser pasados por parmetro, tal y como muestra el siguiente ejemplo:

struct coord inicializa_punto2(int x, int y)


{
struct coord t0;
t0.x = x;
t0.y = y;
return t0;
}
Y en el programa la tupla devuelta por la funcin se asigna a una tupla del contexto de la llamada:

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

int main()
{
struct coord pt1, pt2;

pt1 = incializa_punto1();
pt2 = inicializa_punto2(40, 40);
printf("pt1.x=%d, pt1.y=%d\n"
"pt2.x=%d, pt2.y=%d\n",pt1.x,pt1.y,pt2.x, pt2.y);
return 0;
}
La llamada a inicializa_punto1() devuelve una tupla coord inicializada con valores aleatorios y se
asigna a la variable coord pt1. La llamada a inicializa_punto2() devuelve otra tupla coord inicializada a
(40, 40) y se asigna a la variable coord pt2.

2. Tuplas como parmetros de funcin

Una tupla se pasa como parmetro del mismo tipo a una funcin por valor, como cualquier otra
variable de cualquier otro tipo. La estructura pasada se copia en el parmetro de la funcin.

Atencin!

En el siguiente ejemplo, la funcin permite incrementar en 1 la posicin horizontal de un punto. Si la


posicin excede el valor 1000, se vuele a posicionar en 0:

void modif(struct coord pt)


{
pt.x++;
if (pt.x>1000)
pt.x=0;

}
En un programa, la llamada es la siguiente:

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

int main()
{
struct coord p={ 10,20};
printf("p.x=%d, p.y=%d\n", p.x, p.y);
modif(p);
printf("p.x=%d, p.y=%d\n", p.x, p.y);
return 0;
}
Qu imprime el programa?

p.x=10, p.y=20
p.x=10, p.y=20
No hay modificacin de la tupla p. La tupla por parmetro de la funcin modif() es local a la funcin.
Es otra variable. En el momento de la invocacin, se realiza una asignacin implcita:

modif(pt = p);
Pero, al finalizar la funcin, la tupla p en el contexto de la llamada no se modifica.

Para tener una funcin de modificacin de los valores albergados en una tupla, hay que pasar la tupla
como argumento y devolver la tupla modificada, lo que da:

struct coord modif(struct coord pt)


{
pt.x++;
if (pt.x>1000)
pt.x=0;
return pt;
}
con la llamada:

int main()
{
struct coord p={10,20};;
printf("p.x=%d, p.y=%d\n", p.x, p.y);
p=modif(p);
printf("p.x=%d, p.y=%d\n", p.x, p.y);
return 0;
}
Esta vez, la copia ha sido modificada en la funcin y se ha asignado de nuevo a la tupla p local del
mtodo main(). El programa imprime:

p.x=10, p.y=20
p.x=11, p.y=20
3. Experimento: una entidad mvil por la pantalla

/****************************************************************
El objetivo es crear UNA entidad que se desplace por la pantalla
****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
// definicin y declaracin del tipo de mi tupla en global
struct entidad{
float x,y; // posicin de la entidad
float dx,dy; // su valor de desplazamiento
int color; // su color
char letra; // su apariencia
};

// constantes globales para los lmites de la zona de juego


const int TX = 40;
const int TY = 24;

// mis declaraciones de funciones


struct entidad init (void);
void mostrar (struct entidad e,int color);
struct entidad mover (struct entidad e);
int top (int*start,int dur);
void gotoxy (int x, int y);
void textcolor (int color);
/****************************************************************
PRUEBA 1: UNA SOLA ENTIDAD
****************************************************************/
int main()
{
struct entidad e;
int start=0;

// inicializacin generador aleatorio


srand(time(NULL));

// inicializacin tupla
e=init();

// bucle del juego


while (!kbhit()){

// la accin se ejecuta una vez cada 75 milisegundos


if (top(&start,75 )){

// borrar la posicin actual


mostrar(e,0);

// avanzar
e=mover(e);

// mostrar
mostrar(e,e.color);
}
}
return 0;
}
/****************************************************************
El valor de retorno de la tupla se comporta como una variable
normal
****************************************************************/
struct entidad init()
{
struct entidad e;
e.x=rand()%TX;
e.y=rand()%TY;
e.dx = ((float)rand()/RAND_MAX)*4 - 2; // valor float entre
// -2 y 2
e.dy = ((float)rand()/RAND_MAX)*4 - 2;
e.color= rand()%256;
e.letra=A+rand()%26;
return e;
}
/***************************************************************
Como parmetro de funcin la tupla se comporta como una
variable normal. Se trata de un paso por valor.
***************************************************************/
void mostrar(struct entidad e,int color)
{
gotoxy(e.x,e.y);
textcolor(color);
putchar(e.letra);
}
/***************************************************************
***************************************************************/
struct entidad mover(struct entidad e)
{
// avance
e.x+=e.dx;
e.y+=e.dy;

// control de los lmites


if( e.x<0 || e.x>TX)
e.dx*=-1;
if( e.y<0 || e.y>TY)
e.dy*=-1;

return e;
}
/***************************************************************
***************************************************************/
int top(int*start,int dur)
{
int res=0;
if (clock()> *start+dur){
res=1;
*start=clock();
}
return res;
}
/***************************************************************
***************************************************************/
void gotoxy(int x, int y)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(h,c);
}
/***************************************************************
***************************************************************/
void textcolor(int color)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(h,color);
}
/***************************************************************
***************************************************************/
4. Puesta en prctica: tuplas y funciones

Ejercicio 1

Una carpintera industrial gestiona un stock de paneles de madera. Cada panel se caracteriza por su
anchura, su altura y su grosor en milmetros, un volumen y un tipo de madera. Hay tres tipos de
madera: pino (cdigo 0), roble (cdigo 1) y haya (cdigo 2).

Definir una tupla llamada panel que contenga todos los datos relativos a un panel de madera.
Escribir las funciones de entrada de paneles de madera.
Escribir una funcin de visualizacin de un panel de madera.
Escribir una funcin que calcule el volumen en metros cbicos de un panel.
Escribir una funcin que muestre el volumen de un panel de madera.
Ejercicio 2

Un mayorista de componentes informticos vende cuatro tipos de productos:

Placas base (cdigo 1).


Procesadores (cdigo 2).
Mdulos de memoria (cdigo 3).
Tarjetas grficas (cdigo 4).
Cada producto tiene una referencia, que es un nmero entero, un precio en euros y una cantidad
disponible.
Definir una tupla llamada producto que represente un producto.
Escribir una funcin de entrada de datos de un producto.
Escribir una funcin de visualizacin de datos de un producto.
Escribir una funcin que permita a un usuario introducir el pedido de un producto.
El usuario identifica el producto e introduce la cantidad solicitada. El ordenador muestra todos los
datos del pedido incluyendo el precio.

Ejercicio 3

Sean dos tipos de tupla: fecha y persona:

struct fecha{
int dia, mes, anyo;
};
struct persona{
int identificador;
struct fecha fecha_contratacion;
struct fecha fecha_puesto;
};
Escribir una funcin para inicializar una tupla de tipo persona.
Escribir una funcin de visualizacin de forma que se obtenga o bien una u otra de las dos
visualizaciones siguientes:
Identificador: 1224
fecha contratacin (dd/mm/aaaa): 16/01/2008
fecha del puesto = fecha de contratacin? (S/N): S

Identificador: 1224
fecha contratacin (dd/mm/aaaa): 16/01/2008
fecha del puesto = fecha de contratacin? (S/N): N
fecha del puesto (dd/mm/aaaa): 01/09/2008
Ejercicio 4

Sea una entidad en un videojuego en modo consola. Esta entidad se define mediante una posicin, un
desplazamiento, un tipo (escalador, rpido, serpenteante, pesado, violento) y un color. La entidad
tambin tiene un nombre (una letra) que sirve para visualizarla y una serie de factores: factor de
agresividad, de ira, de codicia, de hambre y de miedo.

Definir la estructura de datos para codificar la entidad. Escribir una funcin de inicializacin, una
funcin de actualizacin a 0 (reset) y una funcin de visualizacin. Comprobar en un programa con un
men: salir, ver, inicializar, reset.

Typedef, enum y #define

1. Utilizar un typedef

El lenguaje C ofrece la posibilidad de definir sus propios tipos a partir de los tipos base. Para ello, hay
que utilizar el comando typedef. El principio es sencillo: declarar una variable del tipo deseado, aadir
typedef delante... y el nombre de la variable es un sinnimo del tipo de esta variable. Por ejemplo:
typedef int toto;
Permite definir el tipo toto en un programa; toto se convierte en sinnimo de int y puede usarse como
tal en todo el programa:

typedef int toto;

int main()
{
toto prueba=80;
printf("la variable prueba vale: %d\n",prueba);
return 0;
}
Lo interesante de poder rebautizar sus tipos depende de las situaciones. Por ejemplo, para crear un
lenguaje en espaol. Pero se utiliza casi automticamente con las tuplas para hacer desaparecer la
palabra clave struct.

Sea por ejemplo el tipo struct enemigo en un programa:

struct enemigo{
char tipo; // tipo del enemigo (una letra)
float molestia; // potencial de molestia
float x,y,px,py; // posicin y desplazamiento
int fuerza; // indicador de la fuerza de combate
};
Aadiendo la palabra clave typedef delante, se puede definir un sinnimo de struct enemigo de la
forma siguiente:

typedef struct enemigo{


char tipo;
float molestia;
float x,y,px,py;
int fuerza;
}t_enemigo;
y de forma ms concisa se puede definir su tipo de tupla:

typedef struct{
char tipo;
float molestia;
float x,y,px,py;
int fuerza;
}t_enemigo;
t_enemigo se convierte en un nombre de tipo para la tupla, en ambos casos por el uso de typedef, y
t_enemigo puede usarse en cualquier parte del programa:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
t_enemigo E;
int i;
srand(time(NULL));
E=inicializa_enemigo();
muestra_enemigo(E);
return 0;
}
El prefijo t_ es prctico. Permite detallar que se trata de un tipo, y no de un nombre de variable. No es
obligatorio usar esta nomenclatura.

2. Utilizar un enum

La palabra clave enum, de enumeracin, permite definir una serie de valores enteros constantes
identificados por nombres. La numeracin se realiza automticamente. Por defecto empieza por 0 para
el primer nombre y se incrementa de uno en uno con cada nombre, por ejemplo:

enum { NO, SI, PUEDE_SER };


Para este enum NO vale 0, SI vale 1 y PUEDE_SER vale 2.

Se puede especificar un valor para cada elemento de la lista; este elemento adquiere este valor y los
siguientes elementos tienen como valor los valores incrementados de uno en uno a partir de ese valor,
por ejemplo:

enum { arrancar=77, correr, acelerar, frenar=-20, parar, marcha_atras};


Arrancar vale 77, correr vale 78, acelerar vale 79, frenar vale -20, parar vale -19 y marcha_atras vale
-18.

Si fuera necesario, el enum puede tener un nombre y entonces se puede usar como tipo para declarar
variables enteras. Lo interesante es la semntica y la metodologa. Por ejemplo, supongamos que, a
continuacin, t1 adquirir los valores correspondientes al enum test:

enum test { arrancar, correr, acelerar, frenar=100, marcha_atras, parar};

int main()
{
enum test t1;
int i;
for (i=0; i<=stop; i++){
t1=rand();
switch(t1){
case arrancar: /*instrucciones*/ break;
case correr: /*instrucciones*/ break;
(...)
case parar: /*instrucciones*/ break;
default: /*instrucciones*/ break;
}
}
return 0;
}
Pero en C no hay control sobre el valor de t1, que se comporta como un int cualquiera. El uso ms
interesante y ms importante de enum reside en poder organizar conjuntos de constantes enteras
relacionadas entre ellas metodolgicamente para tener significado. Por ejemplo, en el juego:

typedef enum{ NORTE, ESTE, SUR, OESTE} direccion;


permite definir cuatro constantes enteras para identificar las direcciones que puede tomar un personaje.
direccion es una variable de tipo enum y con el typedef se convierte en sinnimo de este enum. A
continuacin podemos usar direccion como nombre de tipo para el enum:

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

int main()
{
direccion d;
srand(time(NULL));
d=rand()%(OESTE+1);
printf("%d\n", d);
return 0;
}
3. Utilizar un #define

En el lenguaje C, #define es lo se denomina una macro, una directiva del preprocesador. #include,
#if, #ifndef, #ifdef, #else, #endif, #undef son otras directivas. Todas se introducen por # al comienzo de
la lnea y terminan con un salto de lnea.

Desde el punto de vista de la compilacin, las directivas ests asociadas a un pretratamiento por el
preprocesador cuyo objetivo es el de preparar el cdigo fuente para el compilador. Es una primera etapa
de la compilacin. Proporciona porciones de cdigo al compilador y tambin elimina todos los
comentarios del cdigo fuente del programa.

La palabra clave define define un nombre simblico que representa un valor constante, o una
funcin, cualquier tipo de texto de reemplazo. Se escribe de la siguiente forma:

#define nombre texto de reemplazo


En el programa, cada vez que se usa nombre, este es sustituido por texto de reemplazo. La sintaxis de
texto de reemplazo es, de hecho, independiente al resto del lenguaje, lo que permite usos variados y a
veces sagaces y sorprendentes. Sin embargo, despus del reemplazo del smbolo nombre por texto de
reemplazo en el programa, el cdigo resultante deber ser sintcticamente correcto. Si texto de
reemplazo contiene cdigo incompleto, deber completarse alrededor de nombre, en el momento en
que se use nombre.

Si fuera necesario, una contrabarra \, aadida como ltimo carcter de la lnea, indica que la lnea
contina en la lnea siguiente.

Tpicamente #define se usa en C para identificar valores constantes. Por ejemplo, en este caso el
tamao del rea de juego en modo consola:

#define TX 80
#define TY 24
Esta expresin significa que TX es equivalente a 80, TY a 24. A partir de ese instante TX y TY se
pueden usar en todo el programa en vez de los valores efectivos. Por ejemplo, en bucles:

#include <stdio.h>

int main()
{
int x, y;
for (y=0; y<TY; y++)
for (x=0; x<TX; x++)
printf("%c", A+((y*TY+x)%26));
return 0;
}
Lo interesante de su uso es que resulta muy fcil modificar el programa para que cambie a otros
valores. Basta con modificar el valor de reemplazo de #define y no hay que tocar ms cdigo. Por
ejemplo, en el programa anterior, si se escribe:

#define TX 40
#define TY 12
y se recompila, el programa funciona a partir de ahora con los valores 40 y 12.

El uso de los #define en C est un poco en controversia hoy en da. El mismo Brian Kernighan, creador
del lenguaje C junto con Dennis Ritchie, declara el preprocesador C es una herramienta potente, pero
sin embargo ha quedado un poco obsoleto, y las macros constituyen un mtodo de programacin
peligroso, ya que modifican la estructura lxica del programa de forma subyacente. (KERNIGHAN,
"La prctica de la programacin", pg. 23]).

4. Puesta en prctica: typedef, enum y #define

Ejercicio 1

Definir un tipo para la tupla: struct pix { int x; int y; int color }.

Escribir una funcin que bombardee de pix la ventana de la consola.

Ejercicio 2

Modificar el cdigo del experimento (seccin Experimento: una entidad mvil por pantalla) de forma
que se pueda desplazar la entidad con las flechas del teclado. Definir un enum para las direcciones
(NORTE, ESTE, SUR, OESTE), un tipo para la tupla y dos define para el tamao del rea de juego.

Ejercicio 3

Crear un jugador de ftbol que juega solo con el baln.


Tablas estticas

1. Qu es una tabla?

Una tabla es un conjunto de objetos del mismo tipo: una tabla de int, de float, de double, de tuplas del
mismo tipo, etc.

El hecho de que sea esttica significa que no es dinmica, es decir, que su tamao es fijo y que su
espacio en memoria lo asigna la mquina en la declaracin.

Cada elemento de la tabla est numerado del primero, que es 0, al ltimo, que es el nmero de
elementos de la tabla menos uno. Al nmero del elemento se le llama ndice. El ndice, asociado al
operador corchete [ ], permitir acceder al elemento correspondiente.

Por ejemplo, en un programa, una tabla de 10 int es tener un agrupamiento de 10 int en una sola
variable de tipo tabla de int. Los ndices de los elementos van de 0 para el primer elemento a 9 para el
ltimo. Cada int de la tabla tiene su propio valor. Podemos representarlo de la siguiente forma:

images/03ri02.png
El entero n.4 vale 12, el entero n.1 vale 32, el entero n.8 vale 123, el entero n.9 vale 9, etc.

2. Crear una tabla esttica en un programa

a. Definir y declarar una tabla

Para definir y declarar una tabla en un programa, hay que proporcionar:

El tipo de los elementos.


Un nombre para la tabla.
El nmero de elementos entre corchetes.
Un punto y coma
Sea el formalismo:

<tipo> <nombre> <[constante entera] > < ; >


Por ejemplo:

int tab [10]; // declaracin de una tabla de 10 int


float f[90] // declaracin de una tabla de 90 float
La declaracin puede hacerse en cualquier bloque de instrucciones del programa y las reglas de
visibilidad son las mismas que las de las variables simples.

Atencin! El nmero de elementos siempre debe ser un valor entero y constante. No puede ser una
variable. Para tener un nmero variable de elementos, hay que usar una tabla dinmica, es decir, utilizar
punteros (ver captulo Variables puntero).

b. Utilizar #define para los tamaos


Para el tamao de la tabla, es mejor utilizar una macro constante #define o una constante de enum. Por
ejemplo:

#define NUMMAX 50

int main()
{
int test[NUMMAX];

(...)

return 0;
}
En efecto, muchas operaciones sobre tablas se refieren a su tamao, especialmente los bucles que
permiten recorrer cada elemento de la tabla. Para poder probar su programa con diferentes tamaos de
tabla, basta con modificar el valor del #define, recompilar y ejecutar el programa. No es necesario
buscar todas las operaciones aplicadas sobre la tabla y en cada una de ellas modificar el tamao.

3. Utilizar una tabla

a. Acceder a los elementos de la tabla con el operador corchete [ ]

Para acceder a un elemento de la tabla, hay que utilizar el operador corchete y el ndice del elemento
deseado, por ejemplo:

int toto[5]; // los ndices son 0, 1, 2, 3, 4. El tamao (5)


// no es un ndice de la tabla

toto[0] = 50; // actualizacin a 50 del primer elemento de ndice 0


toto[1] = 60; // actualizacin a 60 del segundo elemento de ndice 1
toto[4] = 100; // actualizacin a 100 del ltimo elemento de ndice 4

toto[2]=toto[0]+toto[1]; // actualizacin a 110 del tercer elemento de


// ndice 2
b. Prioridad del operador corchete

La prioridad del operador [] es muy elevada (ver en el anexo - seccin Prioridad y asociatividad de
operadores) y permite hacer todas las operaciones posibles (aritmticas, bit a bit, comparacin) con
elementos de tabla. En el ejemplo anterior, cada elemento de la tabla es un int y se comporta como un
int en cualquier operacin.

c. Desbordamiento de tabla

Atencin!

Generalmente, est prohibido escribir en un espacio de memoria no reservado. Un desbordamiento de


tabla, es decir, un intento de acceso a memoria en una posicin que no forme parte de la tabla usando la
tabla provoca un error en el funcionamiento del programa. El resultado depende de la gestin de la
memoria: un error curioso, la salida inmediata del programa, una cada del sistema, un bloqueo, etc.
Desgraciadamente, los desbordamientos de tablas no se pueden detectar en la compilacin del
programa.

Por ejemplo:

int main()
{
float test[5];

test[-1] = 9.5; // provoca un bug, error o cada del sistema


test[5] = 10.0; // provoca un bug, error o cada del sistema
return 0 ;
}
d. Inicializar una tabla en la declaracin

Se puede inicializar una tabla en el momento de su declaracin con valores constantes. A continuacin
se muestran los distintos casos posibles:

int tab[7]={456, 76, 82, 12, 4, 3, 0};


char msg[3]={h,o,l,a,!,! }; // error!
float f[50]={1.5, 1.67, 2.33}; // completado con 0
short sh[ ] ={12, 34,5678}; // adquiere tamao 3
Si el nmero de constantes de la inicializacin es mayor que el tamao de la tabla, se produce un error
de compilacin.

Si el nmero de valores es menor que el tamao de la tabla, el resto de la tabla se completa con ceros.

Una tabla inicializada en su definicin no requiere tener un tamao explcito; en este caso, tendr el
tamao del nmero de elementos dados en su inicializacin. La tabla sh tiene un tamao de tres.

e. Recorrer una tabla con un bucle for

Cmo inicializar una tabla con varios miles de elementos?

En la inicializacin es tedioso. Por ejemplo, en una tabla de 5000 enteros, para asignar a todos sus
elementos el valor 10:

int titi[5000]={10,10,10,10,...(5000 elementos!) ..., 10,10};


O, peor an, con un valor aleatorio:

titi[0] = rand();
titi[1] = rand();
... 5000 elementos!
El bucle for permite fcilmente recorrer todos los ndices de una tabla y acceder a cada elemento, sea
cual sea el total:

int i;
for (i=0; i<5000; i++)
titi[i] = rand();
La variable i adquiere sucesivamente todos los valores de 0 a 4999. Para cada valor, i sirve de ndice de
tabla y, para cada ndice, se ejecutan las instrucciones del bloque. En este ejemplo, se ha asignado un
valor aleatorio a cada entero de la tabla.

Se aconseja el uso de un #define para determinar el tamao de la tabla, con lo que obtenemos, por
ejemplo, el siguiente programa completo:

#define NUMMAX 5000


#include <stdio.h>

int main()
{
int tab[NUMMAX];
int i;
for (i=0; i<NUMMAX; i++){
tab[i]=rand()%256;
printf("tab[%4d]=%4d\n",itab[i]);
}
return 0;
}
f. Ordenar una tabla

La necesidad de organizar los datos segn un orden u otro es muy comn en el mundo informtico y
existen gran cantidad de algoritmos de ordenacin (ver, por ejemplo, la obra de Robert Sedgewick,
Algorithms in C ). A continuacin se muestra un algoritmo de ordenacin elemental, la ordenacin por
seleccin: ordena crecientemente todos los valores de una tabla de enteros.

Principio

A partir de una posicin i en la tabla (en el inicio i = 0), se mira en la continuacin de la tabla para cada
posicin j (al inicio j = i + 1) si hay algn valor menor. En caso afirmativo, se permutan los valores de
las posiciones i y j. A continuacin se avanza en 1 y se vuelve a comenzar a partir de la nueva posicin
j a j+1:

images/03ri03.png
Algoritmo en C

Para que funcione en un programa el algoritmo siguiente, hay que inicializar en primer lugar su tabla,
ordenar y volver a imprimir el resultado.

#define N 50

int i,j, tmp, tab[N];

//... inicializar la tabla antes ... y visualizarla


for (i=0; i<N; i++) // para cada i
for (j=i+1; j<N; j++) // mirar a partir de i+1
if (tab[j]<tab[i]){ // si elemento menor
tmp=tab[i]; // si es as, permutar
tab[i]=tab[j];
tab[j]=tmp;
}

// ... visualizar el resultado de la tabla ordenada


Hay medios para optimizar un poco este mtodo (ver ejercicios). Puede ser especialmente interesante
evitar tener tantas permutaciones en un mismo recorrido de j, ya que ralentiza un poco el tratamiento.

4. Tablas de varias dimensiones

a. Declarar una tabla de varias dimensiones

Se puede tener una tabla de varias dimensiones. Basta con especificar, para cada dimensin, su tamao
con el operador corchetes.

Matriz de dos dimensiones

A una tabla de dos dimensiones tambin se le llama matriz. A continuacin se muestra un ejemplo de
matriz de float:

float mat[3][4]; // es una tabla de float de dos dimensiones, lo que


// es lo mismo que tener 3 tablas de 4 float
Como especifica Kernighan, creador del lenguaje C, en C, una tabla de dos dimensiones es, de hecho,
una tabla de una dimensin en la que cada elemento es una tabla. Una matriz es una tabla de tablas.

Para localizar un elemento en una tabla de dos dimensiones, hay que usar dos ndices. El primero para
decir en qu tabla se encuentra y el segundo para indicar su posicin en esa tabla. Por ejemplo:

mat[1][3]=0;
designa al elemento n. 3 de la tabla n. 1.

La primera dimensin corresponde al nmero de filas y la segunda, al de columnas. El total de


elementos es filas*columnas. La organizacin en filas y columnas se puede ver as:

images/03ri04.png
En negro, la posicin mat[3][5].

En gris, la posicin mat[0][0].

El recorrido de los ndices de la derecha (columnas) es consecutivo en memoria. Por esta razn es el
ms rpido. El recorrido de los ndices de la izquierda (filas) requiere saltos en memoria. En este
ejemplo seran saltos de 7 int. Por este motivo el recorrido por filas es ms lento.

Tablas de N dimensiones

Se pueden tener tablas con cualquier nmero de dimensiones:

char tata[3][3][3];
float titi[5][7][8][23][45][56][123];
etc.
Pero ser necesario usar tantos ndices como dimensiones tenga la tabla para localizar un elemento.

Desplegar todas las posiciones de un cubo de 3*3*3 elementos genera, por ejemplo:

tata[0][0][0] tata[0][0][1] tata[0][0][2]


tata[0][1][0] tata[0][1][1] tata[0][1][2]
tata[0][2][0] tata[0][2][1] tata[0][2][2]
tata[1][0][0] tata[1][0][1] tata[1][0][2]
tata[1][1][0] tata[1][1][1] tata[1][1][2]
tata[1][2][0] tata[1][2][1] tata[1][2][2]
tata[2][0][0] tata[2][0][1] tata[2][0][2]
tata[2][1][0] tata[2][1][1] tata[2][1][2]
tata[2][2][0] tata[2][2][1] tata[2][2][2]
Para desplegar una tabla de siete dimensiones, es el mismo principio, pero con siete ndices a, b, c, d, e,
f, g que respeten los tamaos mximos de cada dimensin:

titi[0] [0] [0] [0] [0] [0] [0] = rand();


titi[0] [0] [0] [0] [0] [0] [1] = rand();
etc.
titi[a] [b] [c] [d] [e] [f] [g] = rand();
b. Inicializar en la declaracin

Una matriz puede inicializarse o bien de forma continua, o bien fila por fila. Por ejemplo:

int mat[4][2]={1,2,3,0,7,6,5,4};
es equivalente a:

int mat[4][2]={ {1,2},


{3,0},
{7,6},
{5,4} };
Como para las tablas de una dimensin, si el nmero de valores es inferior al tamao de la tabla, la
inicializacin de la tabla se completa con ceros:

float mat[3][3]={ {1,2},


{5.666} };
es equivalente a:

float mat[3][3]={ {1, 2, 0},


{5.666, 0, 0},
{0, 0, 0} };
c. Recorrer una tabla con mltiples dimensiones

Para recorrer una tabla de varias dimensiones, hay que hacer un bucle para cada dimensin, con todos
los bucles anidados. Por ejemplo, para inicializar una matriz, habra que:

1) pasar por cada fila y


2) en cada fila, recorrer todas las columnas.

Por lo tanto, habra dos bucles, uno para las filas y otro para las columnas, lo que dara como resultado,
por ejemplo:

int mat[45][75];
int y,x;

for (y=0; y<45; y++) // para cada fila, examinar


for (x=0; x<75; x++) // cada elemento de la fila en curso
mat[y][x]=rand()%2;
El principio es el mismo para un nmero cualquiera de dimensiones. Por ejemplo, con un cubo de
10*10*10:

int cubo[10][10][10];
int y,x,z;

for (z=0; z<10; z++) // para cada plano 2D


for (y=0; y<10; y++) // para cada fila del plano 2D
for (x=0; x<10; x++) // cada elemento de la fila del
// plano 2D
cubo[z][y][x]=rand()%20;
5. Experimento: tablas estticas

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

/*****************************************************************
Qu es una tabla?
- una tabla es un conjunto de objetos del mismo tipo:
una tabla de int, de float, de double, etc.

Qu es una tabla esttica?


- el hecho de que sea esttica significa que no es dinmica,
es decir, que su tamao es fijo y que la mquina asigna su
espacio en memoria en la declaracin.

Cmo crear una tabla esttica en un programa?


Para definir y declarar una tabla hay que dar:
- el tipo de los elementos
- un nombre para la tabla
- el nmero de elementos con el operador corchetes (entre corchetes)
siguiendo el patrn: <tipo> <nombre> < [ constante entera ] >

el nmero de elementos debe ser siempre un valor entero


constante (no una variable)

La declaracin es la definicin cerrada por un punto y coma


Por ejemplo:
int tab[50]; // declaracin de una tabla de 50 int
float f[90]; // declaracin de una tabla de 90 float

Cmo acceder a cada elemento de la tabla?

Todos los elementos de una tabla estn numerados desde el primero (0)
hasta el ltimo (tamao de la tabla-1). Este nmero es el ndice del
elemento. Indica la ubicacin del elemento en la tabla. (Atencin: el
primer elemento de la tabla se encuentra en el ndice 0).

A continuacin, para acceder a un elemento de la tabla, hay que usar


el operador corchetes [ ], por ejemplo:
int toto[5]; // los ndices son 0, 1, 2, 3, 4. (El tamao no es
// un ndice de la tabla).

toto[2] = 123; // asignacin a 123 del tercer int de la tabla


toto[0] = 10; // asignacin a 10 del primer int de la tabla
toto[4] = 678; // asignacin a 678 del quinto int de la tabla

*****************************************************************/

#define NUMMAX 50

int main()
{

int toto[5];
toto[2] = 123;
toto[0] = 10;
toto[4] = 678;

printf("toto[%d]=%d\n",2, toto[2]);
printf("toto[%d]=%d\n",0, toto[0]);
printf("toto[%d]=%d\n",4, toto[4]);

/*
//-------------------------------------------------------------
// posibilidad de inicializar una tabla en la declaracin.
// Lo que no se especifique se inicializa a 0
int toto[10]={1,2,3};
printf("toto[%d]=%d\n",0, toto[0]);
printf("toto[%d]=%d\n",1, toto[1]);
printf("toto[%d]=%d\n",2, toto[2]);
printf("toto[%d]=%d\n",3, toto[3]);
printf("toto[%d]=%d\n",4, toto[5]);
*/
//------------------------------------------------------------
// Cmo inicializar una tabla de varios miles de elementos?
/*
int tab[5000];

// el bucle for permite fcilmente pasar por todos los ndices


// y as acceder a cada elemento, sea cual sea la cantidad:
int i;
for (i=0; i<5000; i++){
tab[i]=rand()%256;
printf("tab[%4d]=%4d\n",i,tab[i]);
}
*/
//-------------------------------------------------------------
// Utilizar una macro constante para el tamao.
// Para el tamao de la tabla se recomienda usar una macro
// constante global de la forma:
// #define NUMMAX 50
// antes del main() o en una librera personal
/*
int tab[NUMMAX];
int i;

// inicializacin del generador aleatorio


srand(time(NULL));

for (i=0; i<NUMMAX; i++){


tab[i]=rand()%256;
printf("tab[%4d]=%4d\n",i,tab[i]);
}

//-------------------------------------------------------------
// Ordenar una tabla. Existen una gran cantidad de algoritmos
// de ordenacin de tablas; por ejemplo, para ordenar sus
// valores en orden creciente:
int j,tmp;
for(i=0; i<NUMMAX; i++)
for (j=i+1; j<NUMMAX; j++)
if (tab[j]<tab[i]){
tmp=tab[i];
tab[i]=tab[j];
tab[j]=tmp;
}
for(i=0; i<NUMMAX;i++)
printf("%4d\n",tab[i]);
*/
//------------------------------------------------------------
// Tablas de varias dimensiones
// se puede tener una tabla de varias dimensiones. Para ello,
// basta con especificar, para cada dimensin, su tamao.
// Por ejemplo:
/*
float f[3][4];// tabla de float de 2 dimensiones (una matriz)
// es como tener 3 tablas de 4 float.

// el acceso a los elementos se hace siempre con el operador


// corchetes [ ], una pareja de corchetes (abrir y cerrar)
// por dimensin:
f[0][0]=0;
f[0][1]=1;
f[0][2]=2;
f[0][3]=3;
f[1][0]=4;
//(...)
f[2][3]=11;

// el uso de un bucle requiere una variable y un bucle por


// dimensin:
int y, x,cmpt;

// inicializacin
for (cmpt=0,y=0; y<3; y++)
for (x=0; x<4; x++,cmpt++)
f[y][x]=cmpt;

// visualizacin
for (cmpt=0,y=0; y<3; y++)
for (x=0; x<4; x++,cmpt++)
printf("f[%d][%d]=%0.1f\n",y,x,f[y][x]);
*/
/*
//-------------------------------------------------------------
// posibilidad tambin de inicializar una tabla de varias
// dimensiones en la declaracin, 2 soluciones:
int tab1[4][2]={ 1,2,3,4,5,6,7,8};
int i,j;
printf("tab1 :\n");
for (i=0; i<4; i++){
for (j=0; j<2; j++)
printf("%2d",tab1[i][j]);
putchar(\n);
}

int tab2[4][2]={ {1,2},


{3,4},
{5,6},
{7,8} };
printf("tab2 :\n");
for (i=0; i<4; i++){
for (j=0; j<2; j++)
printf("%2d",tab2[i][j]);
putchar(\n);
}
// Observacin: probar pasando el tamao de la dimensin 2 a 3
// (no olvidarse de modificar la condicin del bucle 2)
// y ver el resultado
*/
return 0;
}
6. Puesta en prctica: operaciones bsicas con tablas estticas (no dinmicas)

a. Declaracin de tablas, acceso a los elementos

Ejercicio 1

Sea una tabla de 5 enteros. Aplicar las instrucciones siguientes:

1) Inicializar la tabla con valores aleatorios comprendidos entre 0 y 20.

2) Visualizar la tabla de forma clara y legible.

3) Si el primer valor es mayor que el segundo, permutar ambos valores,

si el segundo es mayor que el tercero, volver a permutar,

si el tercero es mayor que el cuarto, volver a permutar,

si el cuarto es mayor que el quinto, volver a permutar,

En qu posicin se encuentra el valor ms grande?

4) Visualizar la tabla para comprobar los cambios realizados.

Ejercicio 2

Sea una tabla de 3 enteros y otra tabla de 5 floats.

1) Inicializar estas tablas con valores aleatorios.

2) Para cada ndice, comparar los valores de ambas tablas y almacenar la diferencia exacta en una
tercera tabla.

3) Comprobar los resultados visualizndolos.

b. Inicializacin de tablas en la declaracin

Ejercicio 3

Escribir un programa que declare una tabla de 10 enteros inicializada con los valores del ndice.
Mostrar tres posiciones elegidas al azar. En estas posiciones, asignar un valor comprendido entre 0 y
256, mostrar el nuevo valor de estas posiciones.

Ejercicio 4

Sin detallar el nmero de letras que contiene, declarar una tabla de char inicializada con un mensaje
simple. Elegir tres posiciones aleatorias y modificar las letras de estas posiciones.

Asignar el smbolo \0 (contrabarra cero) a la ltima posicin de la tabla y mostrar el mensaje con la
funcin printf() y el formato %s.

Ejercicio 5

Programacin de un tetris (eso se dice pronto): codificar las formas T, S y Z, O-cuadrada, I, J y L


sabiendo que para cada una hay cuatro posiciones para las rotaciones hacia la izquierda o la derecha, y
que el tamao de cada forma ser de 4 sobre 4.

Ejercicio 6

Escribir un programa que declare una tabla de 543*896*531 elementos de un tipo a su eleccin.

Probar. Cul es el resultado?

Modificar el tamao de la tabla de forma que funcione. Con qu tamaos funciona?

La tabla se inicializa en la declaracin (dos o tres valores nicamente). Mostrar tres posiciones elegidas
al azar.

c. Tablas de varias dimensiones

Ejercicio 7

Escribir un programa que declare una tabla de 3 dimensiones de 2*2*2 e:

Inicializar esta tabla almacenando en cada posicin el nmero de la posicin (1, 2, 3...).
Mostrar el resultado.
Ejercicio 8

Escribir un programa que declare dos matrices de enteros de 2*3 e:

Inicializar estas matrices con valores aleatorios entre 0 y 20.


Para cada posicin, comparar los valores almacenados y guardar el mayor en una tercera matriz.
Mostrar las tres matrices (en 6 filas solamente).
Ejercicio 9

Escribir un programa que declare una matriz de 100*450 floats:

Inicializar 3 posiciones diferentes elegidas al azar en la matriz con valores entre 0 y 2.


Mostrar las tres posiciones y los valores en las tres posiciones.
d. Bucles y tablas

Ejercicio 10

Sea una tabla de 2000 floats:

Inicializarla con valores aleatorios comprendidos entre 0 y 10.


Mostrar la tabla con nicamente 2 cifras decimales.
Ejercicio 11

Sea una tabla de 150 int:

Inicializarla aleatoriamente con nicamente ceros o unos.


Mostrar la tabla en 10 columnas de 3 caracteres y 15 filas. El tamao de las columnas se especifica
entre el % y la d del formato de la forma siguiente %3d.
Ejercicio 12

Sea una matriz de 15 filas por 10 columnas:

Inicializar la tabla con valores crecientes aleatorios.


Mostrar el resultado. El tamao de las columnas se especifica como en el ejercicio anterior, con una
separacin de un carcter entre cada nmero.
Ejercicio 13

Sea una matriz de 15*15. Hacer un tablero de ajedrez y mostrar el resultado.

Ejercicio 14

Rellenar una tabla con valores aleatorios y operar una rotacin de valores para que lo que se encuentre
en la posicin 0 pase a la posicin 1, y lo que est en la posicin 1 pase a la posicin 2, y as hasta el
ltimo, que ir a la posicin 0.

Hacer el mismo ejercicio con una matriz: el valor de cada posicin pasa a la posicin siguiente hasta el
ltimo, que va a la primera posicin.

Ejercicio 15

Rellenar una tabla con valores aleatorios, mostrar la tabla y despus:

Mostrar los dos valores ms grandes.


Mostrar el valor ms pequeo y su posicin en la tabla.
Mostrar los valores de la tabla en orden creciente sin modificar la tabla.
Modificar la tabla para que todos sus valores estn ordenados decrecientemente y mostrar el resultado.
Ejercicio 16

Sea un grupo de nios en el patio de una escuela. Quieren jugar al pilla, pilla. Para designar al que la
para utilizan el mtodo "pito-pito-gorgo-rito-donde-vas-tutan-bonito-ala-era-verda-dera-pim-pam-fu-e-
ra...". Repitiendo de forma rtmica esta frase uno de ellos va designando miembros del grupo, haciendo
coincidir cada slaba con una persona del grupo, siempre en el mismo orden y girando siempre en el
mismo sentido. La persona designada con la ltima slaba no ser quien la pare. La operacin se va
repitiendo hasta que solo quede uno, que ser el gato.

Escribir un programa en el que el grupo de nios se represente con una tabla y utilizar este mtodo para
designar el que la para, que corresponder al ltimo ndice restante de la tabla.

Ejercicio 17

Un poeta maldito vive en un tico. Faltan dos tejas en el tejado y est lloviendo. El poeta ha colocado
una palangana en cada agujero. Las palanganas son del mismo tamao y cada una se llena gota a gota
en funcin de un tiempo aleatorio. Cul se llena antes y en cunto tiempo? Hacer una simulacin
tomando como palangana dos tablas; se almacena para cada gota el tiempo transcurrido desde la gota
anterior de la misma palangana.

Ejercicio 18

El lavavajillas est estropeado y se van acumulando los cubiertos y los platos sucios. Se pone una
persona a lavarlo todo a mano, intenta limpiar los platos y otros objetos a medida que llegan, pero
llegan tan rpido que se acumulan a su lado. Definir cuatro tipos de objetos (por ejemplo: plato llano,
plato hondo, de postre y bandeja). Con una tabla, simular la situacin de las vajillas apiladas. Cuando
se pulsa en una tecla del teclado, se apilan ms objetos y, cuando se pulsa otra, los objetos se retiran de
la pila. Cuando se pulsa [Enter], el contenido de la pila de objetos se muestra por pantalla.

Ejercicio 19

Estamos en una encrucijada en la carretera, hay un semforo que est rojo o verde. Hacer una
simulacin simplificada de la creacin de una fila de coches en uno de los semforos. Cuando est rojo,
los coches se sitan a la cola de la fila. Cuando pasa a estar verde, los coches del principio de la fila
arrancan y se van, dejando su sitio libre. Implementar un sistema de cola usando una tabla.

Ejemplos de uso de tablas

1. Cadenas de caracteres

Una cadena de caracteres (string en ingls) es una serie de caracteres almacenados en una tabla de char
y terminada por el carcter \0.

El \0 marca el fin de la cadena, sea cual sea el tamao de la tabla, pero el nmero total de caracteres
en la cadena nunca puede sobrepasar el tamao de la tabla.

Todas las funciones de tratamiento de cadenas se basan en el \0 final para encontrar el final de la
cadena, y nunca en el tamao de la tabla.

Una tabla de char puede inicializarse con una cadena de caracteres, por ejemplo:

char s[100]="hola!\n"; // 7 caracteres en la cadena: \0 implcito


// el resto de la tabla no se usa
printf(s);
s[0]=s;
s[3]=e;
printf(s);
La cadena "hola!\n" es una cadena de caracteres constante (tal cual es, no puede modificarse). La
propia mquina aade un \0 a todas las cadenas de caracteres constantes. Es por ello por lo que hay en
esta cadena 7 caracteres en total: 4 letras, un smbolo de admiracin (!), un retorno de carro y el \0
final. La tabla s se inicializa con esta cadena de 7 caracteres, pero es este el nico caso en que el
operador de asignacin se puede usar en C para copiar una cadena de caracteres. En el resto de los
casos hay que usar la funcin strcpy().

En caso de olvidarse el \0 final, el resultado es inesperado. El programa se cuelga o bien hay una
visualizacin extraa con caracteres inesperados (lo que hay en memoria antes de encontrarse con un
\0). Puede probar, por ejemplo:

char s1[]={a,b,c,d}; // falta el \0 final: resultado


// incierto
printf("prueba sin \\0 : %s\n",s1);
La tabla s1 es una tabla de cuatro caracteres, pero no es una cadena de caracteres porque le falta el \0
final.

Hay muchas funciones de tratamiento de cadenas de caracteres proporcionadas de forma estndar por la
librera string.h del lenguaje C. Especialmente la funcin fgets(), que permite al usuario entrar una
cadena de caracteres durante el funcionamiento del programa:

#include <stdio.h>

int main()
{
char recup[100];
printf("entre una frase:\n");
fgets(recup,100,stdin);
printf("frase introducida: %s\n",recup);
return 0;
}
La funcin fgets() espera como parmetro 1 la tabla en la que se guarda la cadena, como parmetro 2 el
tamao mximo de la tabla y como parmetro 3 el archivo en el que la entrada se produce (stdin es un
archivo creado y abierto automticamente en modo consola para todas las entradas del usuario).

Para copiar dos cadenas de caracteres, hay que utilizar la funcin strcpy() :

#include <stdio.h>

int main()
{
char recup[100], copia[100];

printf("entre una frase:\n");


fgets(recup,100,stdin);
strcpy(copia,recup);
printf("copia: %s\n",copia);
return 0;
}
El primer parmetro de strcpy() es la cadena destino de la copia, y el segundo es la cadena origen.

2. Imagen de mapa de bits

Una imagen digital es una tabla de dos dimensiones de nmeros. Cada posicin de la matriz se llama
pxel de la imagen y su valor corresponde a la codificacin de un color.

El nmero total de colores accesibles para la imagen, as como el tamao de la imagen en memoria
dependern del nmero de bits utilizados para la codificacin del color (8, 15, 16, 24, 32 bits).

images/03ri05.png
En negro, un pxel en una posicin de la matriz de la imagen y al cual le corresponde un color
codificado por un entero. Generalmente, el 0 designa el color negro.

3. Almacenar datos localizados

El viajero en el tren

Imaginemos que debemos enumerar la lista de los nombres de los pasajeros sentados en los trenes de
varias estaciones sin perder la informacin que permita localizar a cada uno de ellos. Supongamos que
tenemos 4 estaciones, un mximo de 100 trenes por estacin, un mximo de 25 vagones por tren y un
mximo de 60 plazas por vagn. Una estructura de datos podra ser:

char stock_nombres[4][100][25][60][100];
Yo cojo mi tren en la estacin 3; es el tren n. 33, el vagn n. 17, el asiento n. 42. La asignacin no es
posible con cadenas de caracteres. Por lo tanto, hay que almacenar el nombre copindolo con la funcin
strcpy():

strcpy(stock_nombres[3][33][17][42], "Federico Trotamundos");


En cada estacin, se accede a cada uno de los trenes, en cada tren se accede a cada uno de sus vagones
y en cada vagn se accede a cada uno de sus asientos.

Observacin:

En este ejemplo podramos usar macros constantes para los nombres de las estaciones (aqu usamos las
cuatro grandes estaciones de Barcelona: Sants, Catalunya, Triomf y Arenal):

#define SANTS 0
#define CATALUNYA 1
#define TRIOMF 2
#define ARENAL 3
Lo que permite escribir a continuacin:

strcpy(stock_nombres[ARENAL][33][17][42],"Federico Trotamundos");
o incluso un enum, lo que permite tambin agruparlos todos:
enum pasajeros{
SANTS,
CATALUNYA,
TRIOMF,
ARENAL,
NUM_ESTACIONES,
NUM_TRENES=100,
NUM_VAGONES=25,
NUM_ASIENTOS=60,
LONGITUD_NOMBRE=100
};

char stock_nombres[NUM_ESTACIONES][NUM_TRENES][NUM_VAGONES]
[NUM_ASIENTOS][LONGITUD_NOMBRE];
4. Experimento: utilizacin de cadenas de caracteres

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

/*****************************************************************
UTILIZACIN CLSICA DE TABLAS: cadenas de caracteres

Qu es una cadena de caracteres?


Una serie de caracteres almacenada en una tabla de char y
que acaba con el carcter \0
El \0 marca el final de la cadena de caracteres y es imprescindible.
Todas las funciones de tratamiento de cadenas de caracteres se basan
en esta marca de final de cadena para ello. Si se omite, se
corre el riesgo de tener un desbordamiento de tabla y el programa se
puede colgar o mostrar un comportamiento inesperado. Sin el \0 final,
no se trata de una cadena de caracteres, sino solo de una tabla de
caracteres.

*****************************************************************/
int main()
{
char s[100]="hola!\n"; // 7 caracteres en la cadena: \0 implcito
// el resto de la tabla no se usa
printf(s);
s[1]=s;
s[3]=i;
printf(s);
/*
//-------------------------------------------------------------
// Si no hay \0 resultado inesperado, cuelgue o aleatorio
char s1[]={a,b,c,d};
printf("prueba sin \\0: %s\n",s1);
*/
/*
//-------------------------------------------------------------
// el tamao de la cadena definida en la inicializacin de la
// declaracin
char s2[]="Pedro practica bicicleta"; // 24 caracteres + \0

// la funcin strlen devuelve el nmero de caracteres de una


// cadena sin contar el \0 final:
printf("s2 tiene %d caracteres\n",strlen(s2));
*/
/*
//-------------------------------------------------------------
// para introducir una cadena de caracteres:
// funcin fgets(la tabla para obtener la cadena, tamao mx, el
// archivo fuente)
char recup[100];
printf("entre una frase:\n");
fgets(recup,100,stdin);
printf("frase introducida: %s\n",recup);
*/
/*
//-------------------------------------------------------------
// para copiar una cadena de caracteres:
// funcin strcpy(tabla destino, tabla origen)
char copia[100];
strcpy(copia, recup);
printf("aqu la copia: %s\n",copia);
*/
return 0;
}
5. Puesta en prctica: tablas

a. Cadenas de caracteres

Ejercicio 1

Usando un men que se muestra al usuario, escribir el programa siguiente:

Introducir una cadena de caracteres.


Calcular la longitud de una cadena.
Convertir la cadena entrada en maysculas. Solo los caracteres en minsculas se modificarn, el resto
no se tratarn.
Convertir la cadena introducida en minsculas.
Comparar 2 cadenas introducidas. Se trata de indicar si son idnticas y cul es la primera en orden
lexicogrfico.
Concatenar 2 cadenas en la primera (atencin a los desbordamientos).
Encriptar una cadena aplicando un desplazamiento circular cuyo valor sea entrado por el usuario.
Desencriptar una cadena de caracteres encriptada aplicando un desplazamiento inverso a partir de un
valor entrado por el usuario.
Ejercicio 2

Llega el siguiente pedido por correo:

Hola:

Deseara un generador de contraseas aleatorias con las siguientes posibilidades:

Elegir el nmero de contraseas que se desean generar.


Elegir la longitud de la contrasea (de 6 a 30 caracteres).
Elegir los tipos de caracteres utilizados (minsculas, maysculas, cifras y smbolos).
Comprobacin del uso de todos los tipos de caracteres seleccionados.
Excluir los caracteres O e I porque pueden llevar a la confusin.
...Gracias, ...Cordialmente, ...

Implementar una propuesta.

Ejercicio 3

M.A. es una letra que se desplaza por un rea de juego definida. A su alrededor se han ubicado al azar
letras de colores que M.A. puede recoger. De hecho, una frase (o una palabra) se ha dispersado por el
rea y debe reconstruirla. Cada letra buena pasa a engancharse a su cola, de forma que, a medida que
va recogiendo las letras, va reconstruyendo la frase. Se trata de reconstruir la frase entera. El programa
podr tener reservadas varias frases para poder jugar ms partidas. La eleccin de las frases es
aleatoria. Programar.

Ejercicio 4

El juego del ahorcado. El programa muestra una palabra de siete letras y el jugador debe encontrar esta
palabra. Para ello propone una palabra. Si hay letras que coincidan entre la palabra propuesta y la
palabra que se debe adivinar, mostrar en su lugar correcto (las letras no encontradas se reemplazan por
guiones). El objetivo es encontrar la palabra con el mnimo de intentos.

Ejercicio 5

Realizar un generador automtico de texto. El principio es el de crear modelos de frases. Es decir, crear
moldes de frases, que se llaman protofrases. Una protofrase se forma encadenando categoras de
palabras o de expresiones. La protofrase bsica es sencilla, del tipo: Nombre / Adjetivo / Verbo /
Nombre. Cada categora es un conjunto de palabras del tipo correspondiente. Para crear una frase basta
con hacer una seleccin aleatoria de cada categora, segn el modelo inicial, y juntarlas para montar el
resultado.

Para cada categora existe una librera de palabras o expresiones disponibles en una tabla directamente
en el cdigo fuente del programa. Por supuesto, se pueden aadir palabras fijas en la protofrase para
aumentar las posibilidades expresivas; por ejemplo: Nombre / Verbo / CON / Nombre / EN / Nombre.
Las palabras con y en son fijas en la protofrase.

Ejercicio 6
Rellenar una matriz de letras y, a partir de una lista de palabras a su eleccin, mirar si hay palabras
presentes por casualidad. El programa indica las palabras existentes. Atencin: las letras de la palabra
pueden ser consecutivas, pero es ms probable que estn dispersas. En cambio, tienen que estar en
orden. Por ejemplo, la palabra TATA est en la lnea siguiente:

resdarTferdorAafdcvmTnfertuhgfdsqArfdyui

Para la visualizacin, utilizar las funciones proporcionadas en el anexo.

Ejercicio 7

En un programa, hacer dos listas de palabras. Elegir aleatoriamente palabras en ambas listas tambin
seleccionadas aleatoriamente y copiarlas una tras otra en una matriz de letras. A continuacin, recorrer
la matriz y, para cada palabra encontrada, decir si formaba parte de la lista 1 o de la lista 2 y reconstruir
la alternancia de la seleccin entre ambas listas.

b. Imagen, terreno de juego

Ejercicio 8

En un nuevo programa, declarar una matriz de 10 filas por 20 columnas.

Vamos a considerar una imagen en 256 colores, es decir, para la que hay un mximo de 256 colores.

1) Rellenar la imagen con valores aleatorios comprendidos entre 0 y 255 (extremos incluidos).

2) Visualizar esta imagen por pantalla respetando su aspecto rectangular. Se puede utilizar la funcin
gotoxy() y las funciones para el color proporcionadas en el anexo.

3) Calcular el histograma de la imagen.

El histograma de una imagen estudia el reparto estadstico de cada valor de color en una imagen de 256
colores. Su principio consiste, en una tabla de histograma de 256 casillas de tipo entero, en contar
cuntas veces aparece el color codificado como 0 y guardar ese valor en la casilla 0 de la tabla, en
contar cuntas veces aparece el color codificado como 1 y guardar ese nmero en la casilla 1 de la tabla
del histograma... y seguir as hasta llegar al color codificado como 255, incluido.

Una vez se haya calculado el histograma, mostrar el resultado de forma legible por pantalla.

4) Binarizacin de la imagen.

La binarizacin de una imagen consiste en poner a 0 todos los pxeles de la imagen original inferiores a
un valor umbral entrado por el usuario, y poner a 255 todos los pxeles de la imagen original que sean
mayores o iguales a este valor umbral, utilizando una imagen secundaria para no modificar la imagen
original. Una vez se haya efectuado esta operacin, mostrar el resultado.

Ejercicio 9
En un videojuego, la tcnica del tile map sirve para construir terrenos de juego, paisajes o diferentes
niveles. El principio es sencillo. En la base de datos se guardan pequeas imgenes elementales: por
ejemplo, csped, pared, agua y fuego. Cada pequea imagen se corresponde con un nmero; por
ejemplo, 0 para csped, 1 para pared, 2 para agua y 3 para fuego. A continuacin, en una matriz se
realiza un diseo a partir de estos nmeros. En el juego, cuando se necesita el terreno, la imagen se
forma a partir de la matriz y se muestra por pantalla.

Inicializar una matriz para realizar un terreno de juego de 10 por 15.


Mostrar este terreno con la funcin gotoxy() y las funciones para colorear proporcionadas en el anexo.
Colocar objetos de forma aleatoria en el terreno. Los objetos no pueden estar sobre las paredes, solo
pueden colocarse en el csped. Atencin: los objetos no se almacenan en la matriz del terreno de juego.
A continuacin, mostrar estos objetos.
Despus de un tiempo apropiado, el objeto cambia de lugar. En un bucle de eventos, cambiar la
ubicacin de los objetos, cada uno a su ritmo.
Ejercicio 10

Se acerca la Navidad, tal vez? Escribir un programa que simule una guirnalda elctrica con varios
destellos de luz de distintos colores.

Ejercicio 11

Una quincena de pequeos cuadrados (obtenidos con la visualizacin de un espacio cuyo color de
fondo se especifica) se desplazan a distinta velocidad por un espacio estrictamente delimitado. Cuando
llegan a un borde, rebotan y cambian de direccin.

Ejercicio 12

En un terreno construido a partir de una matriz, desplazar un jugador. El jugador debe respetar los
muros. Si hay objetos en el terreno, el jugador los recoge y gana puntos.

Ejercicio 13

En un rea de juego, una oruga avanza controlada por las teclas de las flechas. Su cuerpo se alarga con
una letra ms cada 5 giros. Escribir el programa. Completar el programa de forma que no se pueda
pisar a s misma.

Ejercicio 14

Usando la funcin gotoxy(), escribir un programa que haga caer nieve de arriba abajo de la pantalla.
Encontrar una solucin para la llegada de los copos: se funden? Se acumulan? Si se acumulan, hasta
dnde?

c. Localizacin de datos con varias dimensiones

Ejercicio 15

Mediciones en un laboratorio. Para estudiar los efectos de una molcula, un laboratorio organiza un
experimento con 150 personas. Estas personas se reparten en grupos de 50; el experimento dura 3
meses con una serie de medidas especficas para cada mes y para cada grupo:
El grupo 1 sirve de referencia y no recibe el tratamiento.
El grupo 2 recibe un placebo sin principio activo biolgico.
El grupo 3 recibe el principio activo.
Hay cuatro mediciones diferentes cada 60 segundos y cada 24 horas, y la fecha debe guardarse (mes,
da). Las mediciones son la temperatura, la tasa de linfocitos (glbulos blancos), la tasa de hemates
(glbulos rojos) y el tiempo exacto en el que se realiz la medicin. Cmo almacenar los resultados?
Escribir el programa que permita entrar cada una de estas mediciones.

Tablas y tuplas

1. Tabla como campo de una tupla

Una tupla puede contener tablas. El acceso a una tabla se efecta como para cualquier otro campo, con
el operador punto. Por ejemplo, vase la tupla siguiente:

typedef struct{
char nombre[80];
float calc;
int stock[10];
}test;
En el siguiente programa, se inicializa y se muestra una struct test:

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

int main()
{
test t1;
int i;

strcpy(t1.nom,"Miguel");
printf("Nombre: %s\n",t1.nombre);
t1.calc=(float)rand() / RAND_MAX;
printf("Clculo: %f\n",t1.calc);

for (i=0; i<10; i++){


t1.stock[i]=rand()%256;
printf("Valores %d almacenados: %d\n", i, t1.stock[i];
}
return 0;
}
Al inicio, se declara la tupla t1 y a continuacin:

Se inicializa y se muestra el campo nombre.


Se inicializa el campo calc con un valor en coma flotante aleatorio entre 0 y 1 y se muestra el valor.
Se inicializa el campo stock, una tabla de 10 enteros con valores aleatorios y se muestra la tabla. Se usa
un bucle para acceder a los elementos de la tabla.
2. Tabla de tuplas

Se puede tener una tabla en una tupla y tambin es posible tener una tabla de tuplas. Sea la siguiente
definicin y declaracin en global:

struct pix{
int x, y, color;
};
En un programa, declaramos una struct pix y una tabla de tres struct pix. A continuacin, se inicializan
y se muestran por pantalla, primero la tupla sola y despus la tabla de tuplas:

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

int main()
{
struct pix p, tab[3];
int i;
// inicializacin de la tupla sola
p.x=0;
p.y=50;
p.color=255;
// visualizacin de la tupla sola
printf("x=%d,y=%d,color=%d",p.x,p.y,p.color);

// inicializacin de la tabla de tuplas


for (i=0; i<3; i++){
tab[i].x=0;
tab[i].y=50;
tab[i].color=255;
// visualizacin de cada tupla
printf("tab[%d].x=%d,y=%d,color=%d",
i,tab[i].x,tab[i].y,tab[i].color);

(. . .)

}
Se usa un bucle for para acceder a cada tupla de la tabla tab. En este ejemplo, las tres tuplas en la tabla
se inicializan con los mismos valores, los campos x se asignan a 0, los campos y a 50 y color a 255.

Otro ejemplo: en un videojuego, la siguiente tupla caracteriza a los enemigos:

typedef struct{
char nombre[80]; // nombre y tipo de enemigo
float molestia; // potencial de molestia
float x,y,px,py; // posicin y desplazamiento
int fuerza[10]; // 10 posibilidades de fuerza para los combates
}t_enemigo;
Se define la siguiente funcin de inicializacin:

t_enemigo init_enemigo(void)
{
char* nombres[4]={"Bretelgunch","Athans","Belzebn",
"Trolmpico"};
t_enemigo e;
int i;
// un nombre al azar de entre los cuatro
strcpy(e.nombre, nombres[rand()%4]);
e.molestia= rand()/(float)RAND_MAX;
e.x = (rand()/(float)RAND_MAX) * 1024; // x de 0 a 1024
e.y = (rand()/(float)RAND_MAX) * 768; // y de 0 a 768
e.px = ((rand()/(float)RAND_MAX) * 10)-5; // de -5 a +5
e.py = ((rand()/(float)RAND_MAX) * 10)-5; // de -5 a +5
for (i=0; i<10; i++)
// los 10 indicadores de fuerza entre 0 y 100
e.fuerza[i]=rand()%100;
return e;
}
y la funcin de visualizacin:

void ver_enemigo (t_enemigo e, int num)


{
int i;
printf("Enemigo %d:\n", num);
printf("\t- nombre : %s \n", e.nombre);
printf("\t- molestia : %.2f \n", e.molestia);
printf("\t- pos horizontal : %.2f \n", e.x);
printf("\t- pos vertical : %.2f \n", e.y);
printf("\t- velocidad horizontal : %.2f \n", e.px);
printf("\t- velocidad vertical : %.2f \n", e.py);
for(i=0; i<10; i++)
printf(fuerza[%d]=%d\n",e.fuerza[i]);
}
El siguiente programa declara una tabla struct enemigo y la inicializa:

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

#define NUM_ENEMIGOS 100

// definicin de la tupla enemigo aqu.

// declaracin de las funciones aqu.

int main()
{
t_enemigo E[NUM_ENEMIGOS];
int i;
srand(time(NULL));
for (i=0; i<NUM_ENEMIGOS; i++){
E[i]=init_enemigo();
ver_enemigo(E[i],i);
}
return 0;
}

// las funciones de inicializacin y de visualizacin se definen aqu.


3. Diferencias entre tablas y tuplas

Hay tres diferencias entre una tabla y una tupla:

1) La tabla es un conjunto indexado de objetos del mismo tipo, se accede a ellos con el operador [ ]. La
tupla es un conjunto de objetos que pueden ser de tipos distintos, que no estn indexados y se accede a
sus elementos con el operador . punto.

2) La tabla no es un left value, es decir, que no se puede asignar cualquier cosa a una tabla. La tupla
es un left value y se le puede asignar a una tupla otra tupla del mismo tipo (copia).

3) La tabla como parmetro de funcin se transforma automticamente en puntero (este punto se trata
en la seccin Tablas y funciones) y, en el momento de la llamada a la funcin, este puntero adquiere la
direccin en memoria de la tabla pasada como parmetro. Este es un paso de parmetro por referencia a
la direccin de memoria. La tupla como parmetro de funcin es simplemente una variable local a la
funcin a la que se ha asignado un valor en el momento de la llamada; es un paso por valor sin
referencia a una direccin de memoria.

4. Puesta en prctica: tablas y tuplas

Ejercicio 1

Sean las dos tuplas:

struct fecha {
int dia, mes, anyo;
};
struct persona {
char nombre[80];
struct fecha fecha_contratacion;
struct fecha fecha_puesto;
};
Escribir una funcin para inicializar una struct persona.
Escribir una funcin de visualizacin para obtener:
nombre: TOTO
fecha contratacin (dd/mm/aaaa): 09/09/2010
fecha del puesto = fecha de contratacin? (S/N): S
O

nombre: TOTO
fecha contratacin (dd/mm/aaaa): 09/09/2010
fecha del puesto = fecha de contratacin? (S/N): N
fecha del puesto (dd/mm/aaaa): 01/01/2011
Ejercicio 2

Sea una entidad en un videojuego en modo consola. Se define por su posicin, su velocidad, un tipo
(escalador, rpido, serpenteante, pesado, violento), un color y una letra. La entidad tambin tiene un
nombre y una serie de factores: factor de agresividad, factor de ira, factor de codicia, factor de hambre
y factor de miedo.

Definir la estructura de datos para poder codificar una entidad. Cmo tener 100 entidades? Escribir
una funcin de inicializacin, una funcin de actualizacin a 0 (reset) y una funcin de visualizacin.
Comprobar en un programa con un men: salir, ver, inicializar, reset. Aadir una funcin para animar
entidades (utilizar la funcin void gotoxy(int x, int y) proporcionada en el anexo) y algunas funciones
que permitan a las entidades establecer relaciones entre ellas.

Ejercicio 3

Realizar una agenda de NUMMAX citas. Una cita se definir por:

Un nombre.
Un lugar de encuentro.
Una fecha (elegir un formato que permita ordenar).
Una hora de inicio.
Una hora de finalizacin.
Una categora (trabajo, personal, ocio, etc.).
Cualquier otro campo que pueda parecer til.
1) Definir el tipo o los tipos necesarios para una estructura de datos adecuada.

2) En un men, ofrecer al usuario las siguientes opciones:

Introducir una cita.


Visualizar todas las citas.
Mostrar una categora de citas.
Eliminar una cita.
Eliminar automticamente citas duplicadas.
Disponer en orden cronolgico las citas.
Salir.
Ejercicio 4

Sea la tupla siguiente:

struct punto {
int x, y, color;
};
En un programa, declarar una tabla de NUM_PUNTOS (por ejemplo, 10).

Escribir una funcin de inicializacin. Esta funcin inicializa cada punto o bien al azar, o bien con
valores entrados por el usuario.
Escribir una funcin de visualizacin.
Escribir una funcin que permita modificar los valores de uno de los puntos de la tabla.
Escribir una funcin para poner a cero todos los valores de una tabla.
Implementar un men de usuario con los comandos: salir, inicializar, ver, modificar, poner a 0.
Ejercicio 5

Escribir un programa que permita visualizar el signo del zodaco a partir de un da y un mes de
nacimiento. El da se entrar como un nmero, y el mes, como una cadena de caracteres. A
continuacin se muestran los signos con su periodo:

Capricornio
Acuario
Piscis
Aries
Tauro
Gminis
Cncer
Leo
Virgo
Libra
Escorpio
Sagitario
23 diciembre - 19 enero
20 enero - 19 febrero
20 febrero - 20 marzo
21 marzo - 19 abril
20 abril - 20 mayo
21 mayo - 20 junio
21 junio - 21 julio
22 julio - 22 agosto
23 agosto - 22 septiembre
23 septiembre - 22 octubre
23 octubre - 21 noviembre
22 noviembre - 22 diciembre
Ejemplo de ejecucin:

Escriba su da y su mes de nacimiento:


11 july
*** error en el nombre del mes ***
Escriba su da y su mes de nacimiento:
16 enero
usted est bajo el signo de: capricornio
Ejercicio 6

Escribir un programa que traduzca una frase entrada por el teclado en morse. Los caracteres
susceptibles de ser codificados son las 26 letras del abecedario, las cifras de 0 a 9 y el punto.

Si el texto contiene otros caracteres distintos a estos, el programa mostrar signos de interrogacin en
vez del cdigo morse. A continuacin, el alfabeto morse:

A
.-
B
-...
C
-.-.
D
-..
E
.
F
..-.
G
--.
H
....
I
..
J
.---
K
-.-
L
.-..
M
--
N
-.
O
---
P
.--.
Q
--.-
R
.-.
S
...
T
-
U
..-
V
...-
W
.--
X
-..-
Y
-.--
Z
--..
0
-----
1
.----
2
..---
3
...--
4
....-
5
.....
6
-....
7
--...
8
---..
9
----.
. punto
.-.-.-
Cada cdigo finaliza con un o dos espacios; un espacio se representa con smbolos de interrogacin,
por ejemplo: el lenguaje C da:

. .-.. ????? .-.. . -. --. ..- .- .--- . ????? -.-.

El programa sale automticamente si el usuario lo desea.

Ejercicio 7

A partir de los datos del ejercicio anterior, realizar un traductor hacia el otro sentido: del morse al
lenguaje normal. El programa mostrar un men: entrar un mensaje en morse, generar un
pseudomensaje en morse (generacin aleatoria del mensaje), salir.

Cada mensaje se traducir y se mostrar.

Ejercicio 8

Implementar un programa que cree una factura para un pedido de varios artculos. Para cada artculo
del pedido, el usuario proporciona la cantidad y el nmero de cdigo a partir del cual el programa
encuentra a la vez el nombre y el precio unitario. El programa rechaza los cdigos inexistentes.
Finalmente, muestra un resumen, que es la factura.

Los datos relativos a los distintos artculos se definen en global en el cdigo fuente del programa.
Utilizar un catlogo de supermercado para obtener la lista e inventar los cdigos.

1) El programa mostrar un men con:

Nuevo pedido.
Mostrar la lista de artculos disponibles.
Crear una funcin de bsqueda de datos relativos a un artculo a partir de su nmero de cdigo y una
funcin de visualizacin de la factura resumen.

Ejemplo de ejecucin:

ARTICULO CANTIDAD P. UNITARIO TOTAL

licuadora 33 47.29 1560.57


tostadora 12 35.84 430.08
Rasqueta de horno 6p 6 51.33 307.98

TOTAL 2298.63
2) En el momento de realizar el pedido, el programa verifica que el nmero de artculos solicitados est
disponible. En caso contrario, el usuario es invitado a modificar su pedido. El stock disponible se
actualiza despus de cada pedido.

Tablas y funciones

1. Utilizar una tabla declarada en global

El uso de tablas con funciones remite a la visibilidad (accesibilidad) de variables en un programa: la


tabla podr declararse en global con funciones escritas solamente para ella y sin parmetro, y tambin
podr declararse en local, por ejemplo en el main(), con funciones genricas que podrn usarse con
distintas tablas.

A modo de recordatorio, todas las variables pueden declararse en local o en global:

A las variables se las llama locales en la funcin en la que se han declarado. Es decir, que son
visibles (accesibles) nicamente en el bloque de la funcin y en todos los subbloques anidados. En este
caso, los valores de las variables pueden trasladarse gracias a los parmetros y al mecanismo de retorno
(return) de las funciones.
Pero se pueden declarar variables a nivel del archivo, fuera de todo bloque, encima del main(). En este
caso, la variable es accesible desde todos los bloques y todas las funciones del archivo, sin tener que
usar los parmetros de entrada o el valor de retorno. La declaracin global se usa para las definiciones
de tipo, especialmente las tuplas, para valores constantes (#define, enum...), para declaraciones de
funciones y para algunas variables esenciales que simplifican la escritura de pequeos programas
(menos de 500 lneas de cdigo). No debe usarse esta caracterstica en ningn caso si la globalidad
de la variable no est justificada. Con un mal uso, esta propiedad corre el riesgo de perjudicar el
desarrollo (en general el desarrollo se encuentra rpidamente paralizado ms all de las 1000 lneas de
cdigo).
El programa siguiente ilustra cmo utilizar esta propiedad para una tabla de datos en un programa:

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

// valor constante definido en global


#define NUMMAX 50

// tabla declarada en global, accesible desde cualquier parte del programa


int tabGlo[NUMMAX];

// declaracin de funciones definidas debajo del main()


void init_tabGlobal (void);
void ver_tabGlobal (void);

/********************************************************
********************************************************/
int main()
{
init_tabGlobal(); // llamada a la funcin de inicializacin sin parmetro
ver_tabGlobal(); // llamada a la funcin de visualizacin sin parmetro
return 0;
}
/********************************************************
Ambas funciones pueden realizar el tratamiento sobre la tabla tabGlo sin
pasarla por parmetro porque est declarada en global.
En cambio, solo pueden trabajar con esta tabla.
*********************************************************/
void init_tabGlobal()
{
int i;
for (i=0; i<NUMMAX; i++)
tabGlo[i]=rand()%256; // inicializacin de la tabla con
// valores aleatorios comprendidos
// entre 0 y 255
}
/********************************************************
********************************************************/
void ver_tabGlobal()
{
int i;
for (i=0; i<NUMMAX; i++){ // visualizacin de la tabla
printf("%4d",tabGlo[i]);
if (i%10==9) // en filas de 10
putchar(\n);
}
}
2. Tabla como parmetro de funcin

a. Precisin sobre el tipo tabla

Sea, por ejemplo, en un programa la declaracin:

int tab[50];
Cul es el valor numrico de tab? Qu es tab para la mquina?

tab es la direccin de memoria del bloque asignado para la tabla, es decir, la direccin del primer
elemento de la tabla, y todos los elementos son consecutivos (no hay fragmentacin en el bloque).
Pasar una tabla por parmetro a una funcin supone pasar una direccin de memoria por parmetro.
El tipo de variable que toma por valor direcciones de memoria es el tipo puntero.
Por lo tanto, para pasar una tabla a una funcin por parmetro habr que tener un puntero definido
como parmetro en la funcin.
b. La variable puntero

El puntero es simplemente una variable que toma por valor direcciones de memoria. De forma general,
en un programa una variable puntero se especifica con el operador asterisco de la forma siguiente: si T
es un tipo, T* es el tipo puntero de T. Por ejemplo:

char* s;
es un puntero a carcter, esta variable puede contener direcciones
de memoria de variables de tipo char

int*t;
es un puntero a entero, esta variable puede contener direcciones
de memoria de variables de tipo int

float*f;
es un puntero a float, esta variable puede contener direcciones
de memoria de variables de tipo float
A partir de estos punteros, se podran tener las asignaciones siguientes:

// sean 4 variables declaradas como sigue:


int toto=70;
char tata=A;
float titi=7.5;
int tab[10];

// recuperamos la direccin de cada una de las variables con


// el operador & y asignamos esta direccin a un puntero:
s=&tata;
f=&titi;
t=&toto;

// en el caso de las tablas, tab es ya una direccin, con lo


// que no necesitamos el operador & y podemos escribir:
t=tab; // t (un int*) adquiere como valor la direccin de la
// tabla (tabla de enteros, direccin del primer elemento)
c. En parmetro, conversin de tabla a puntero

Como un puntero permite recuperar una direccin de memoria y una tabla es una direccin de
memoria, las tablas como parmetros de funcin son punteros.

Tabla de una dimensin

Como parmetro de funcin, un puntero destinado a recibir la direccin de memoria de una tabla puede
escribirse de tres formas. Sea T un tipo y tab el nombre de la tabla; se puede escribir:

T *tab
T tab[ ]
T tab[NUM_ELEMENTOS]
Por ejemplo:

void init (int*t)


{
int i;
for (i=0; i<10; i++){
t[i]=rand()%256;
printf("t[%d]=%d\n",i,t[i]);
}
putchar(\n);
}
tambin puede escribirse:

void init (int t[] )


{
int i;
for (i=0; i<10; i++){
t[i]=rand()%256;
printf("t[%d]=%d\n",i,t[i]);
}
putchar(\n);
}
o tambin:

void init (int t[NUM_ELEMENTOS])


{
int i;
for (i=0; i<10; i++){
t[i]=rand()%256;
printf("t[%d]=%d\n",i,t[i]);
}
putchar(\n);
}
En los tres casos, el parmetro puntero de la funcin init() recibir en el momento de la invocacin la
direccin de la tabla, que ser pasada por parmetro.

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

int main()
{
int tab[10];

init(tab) ; // La asignacin implcita es la siguiente:


// init (t = &tab[0]) ;
// es equivalente a
// init (t = tab);
return 0;
}
Tablas de varias dimensiones

En el caso de tablas estticas, sea cual sea el nmero de dimensiones de una tabla, solo la primera
dimensin de la tabla se convierte a puntero. Por ejemplo: si se trata de una matriz de enteros, una tabla
de dos dimensiones, la matriz se convierte en un puntero a tabla de enteros. Por este motivo, no es
necesario saber el tamao de la primera dimensin de la tabla, sino el tamao del resto de las
dimensiones, que debe especificarse de forma obligada.

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

void init_matriz(int m[][10], int ty,int tx)


{
int x,y;
for(y=0; y<ty; y++){
for (x=0; x<tx; x++){
m[y][x]=rand()%256;
printf("%4d",m[y][x]);
}
putchar(\n);
}
}

int main()
{
int mat[25][10]; // 25 filas por 10 columnas
init_matriz(mat,25,10);
return 0;
}
El parmetro tambin puede escribirse:

int (*m)[10] // puntero a tabla de 10 int


d. Eleccin para escribir tablas como parmetros de funcin
Entre las tres formas posibles de escritura para el puntero como parmetro destinado a recibir una tabla,
las dos ltimas formas, int [ ] e int [NUM_ELEMENTOS], son preferibles y mejores que int * t desde
el punto de vista del significado que aportan.

int t[ ] especifica claramente con los corchetes que es una tabla lo que se espera por parmetro en el
momento de la llamada.

Por lo que respecta a int t[10], esta es equivalente a int*t o int t[ ], y el tamao no le sirve para nada a la
mquina. Se puede pasar una tabla de otro tamao sin problemas. Sin embargo, esta indicacin puede
ser til para la comprensin del cdigo o para recordar en la funcin que es una tabla de 10 int lo que se
espera, lo que explica por qu el bucle va de 0 a 10.

Otra solucin es pasar explcitamente por parmetro el tamao de la tabla, lo que permite utilizar la
funcin para tablas de cualquier tamao:

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

void init(int t[], int num_elementos)


{
int i;
for (i=0; i<num_elementos; i++){
t[i]=rand()%256;
printf("t[%d]=%d\n",i,t[i]);
}
putchar(\n);
}
//--------------------------------
int main()
{
int t1[10];
int t2[20];

init(t1, 10); // llamada para inicializar t1


init(t2, 20); // llamada para inicializar t2
return 0;
}
e. Modificacin de datos mediante un paso de parmetros por referencia

Qu imprime el siguiente cdigo?

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

void init(int t[], int num_elementos)


{
int i;
for (i=0; i<num_elementos; i++)
t[i]=1+rand()%9;
}

void mostrar(int t[], int num_elementos)


{
int i;
for (i=0; i<num_elementos; i++)
printf("t[%d]=%d\n",i,t[i]);

putchar(\n);
}

int main()
{
int tab[4]={0};

mostrar(tab,4);
init(tab, 4);
mostrar(tab,4);
return 0;
}
La tabla tab se inicializa en la declaracin con ceros. La primera llamada a la funcin mostrar() muestra
una lista de cuatro 0:

tab[0]=0
tab[1]=0
tab[2]=0
tab[3]=0
La segunda llamada a la funcin mostrar() muestra esta vez la tabla con valores inicializados de 1 a 9:

tab[0]=6
tab[1]=9
tab[2]=8
tab[3]=5
Por qu la tabla tab, que es una variable local en el contexto que llama a la funcin, se modifica
despus de la ejecucin de la funcin?

Porque el valor transmitido al parmetro int t[ ] de la funcin init() es una direccin de memoria.
Cuando la funcin recibe la tabla tab por parmetro, implcitamente se hace:

init(t=&tab[0], 4);
Lo que significa que la variable puntero t[ ] de la funcin init() contiene a continuacin la misma
direccin de memoria que la de la tabla tab declarada en el main(). De este modo, todas las operaciones
que se hagan en t[ ] en la funcin se llevan a cabo en tab, que est en el main(), ya que es el mismo sitio
en memoria. Es por este motivo por el que tab ha sido modificada despus de la llamada a la funcin
init().

Gracias al uso de direcciones de memoria, la tabla que se ha pasado por parmetro puede modificarse
en la funcin. A continuacin se muestra otro ejemplo ilustrativo de esta caracterstica:
Sea la funcin siguiente:

void test (int t1[], int num1, int t2[], int num2)
{
init(t1,num1);
init(t2,num2);
mostrar(t1,num1)
init(t1, num1);
mostrar(t2, num2);
}
y la llamada en un main()

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

int main()
{
int tab[5];
test(tab, 5, tab, 5);
return 0;
}
La tabla tab se pasa por parmetro en los dos parmetros de la funcin test. Sabe que suceder?

Se puede traducir la situacin en la funcin test() de la siguiente manera:

1) tab mediante su direccin se inicializa con valores aleatorios entre 1 y 9.

2) tab mediante su direccin se inicializa con valores aleatorios entre 1 y 9.

3) tab mediante su direccin se muestra.

4) tab mediante su direccin se muestra.

5) tab mediante su direccin se inicializa con valores aleatorios entre 1 y 9.

6) tab mediante su direccin se muestra.

Las variables t1[ ] y t2[ ] son diferentes, pero, como es la misma direccin de memoria la que se asigna
a cada una de ellas en el momento de la llamada, ambas trabajan con la misma ubicacin en memoria,
es decir, con la tabla tab.

Atencin!

No se puede utilizar el mecanismo de retorno para devolver una tabla esttica que se haya declarado en
la funcin. Como variable local de la funcin, el bloque de memoria que se le ha asignado se libera al
final de la funcin y la direccin de memoria de la tabla ya no es una direccin vlida para acceder en
escritura. Esta direccin se transmite al contexto de llamada, pero escribir en esta direccin provocar
que el programa finalice o se cuelgue (este punto se detalla en el mdulo sobre punteros y asignacin
dinmica de tablas).

3. Algunas funciones de tratamiento de cadenas de caracteres

a. Recuperar una cadena de caracteres entrada por el usuario

char* fgets (char*, int, FILE*) ------- <stdio.h>


Esta funcin lee una cadena entrada en el descriptor de ficheros especificado en el parmetro p3 (en el
caso de la entrada estndar, este descriptor ser stdin) hasta que se encuentre con un \n o hasta que p2-
1 caracteres hayan sido ledos, el \n final incluido. Todos los caracteres ledos se ubican a partir de la
direccin de memoria especificada en el parmetro p1. Un \0 se aade como ltimo carcter despus
del \n.

#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[100];
printf("entrar una cadena:\n");
fgets(buf,100,stdin);
printf("%s",buf);
return 0;
}
b. Obtener el tamao de una cadena

size_t strlen(const char*s) ------- <string.h>


Esta funcin devuelve la longitud de la cadena pasada por parmetro sin contar el \0 final (si p apunta
a una cadena vaca "" strlen devuelve 0). Por este motivo, el tamao para codificar una cadena
completa es strlen(s)+1.

char buf[100];
int L;
printf("entrar una cadena: ");
fgets(buf,100,stdin);
L=strlen(buf)
printf ("longitud de la cadena %s: %d\n",buf,L);
Atencin, fgets() aade un \n cuando se pulsa [Enter], y este carcter forma parte de la cadena y
cuenta como uno. Se puede ver si se muestra la cadena porque el cursor de escritura pasar a la
siguiente lnea.

c. Copiar una cadena

char* strcpy(char*, const char*) ------- <string.h>


Esta funcin copia la cadena de caracteres p2 en la direccin p1 y devuelve p1. Atencin: la zona de
memoria a la que apunta p1 debe ser suficientemente grande y estar correctamente asignada, accesible
en escritura.

char buf[100];
char cpi[100];
printf("entrar una cadena:\n");
fgets(buf,100,stdin);
strcpy(cpi, buf);
printf("copia: %s",cpi);
d. Comparar dos cadenas

int strcmp(const char*, const char*) ------- <string.h>


Esta funcin compara las cadenas de caracteres p1 y p2 y devuelve un valor negativo, 0 o positivo
segn si la primera es menor, igual o mayor que la segunda en orden lexicogrfico.

char buf[100];
char cpi[100];
int i;
printf("entrar dos cadenas:\n");
fgets(buf,100,stdin);
buf[strlen(buf)-1]=\0; // eliminacin de \n antes del final

fgets(cpi,100,stdin);
cpi[strlen(cpi)-1]=\0; // eliminacin de \n antes del final

if ( (res=strcmp(cpi,buf))==0)
printf ("las cadenas son iguales\n");
else if(res<0)
printf("la cadena %s es menor que la cadena %s\n",cpi,buf);
else
printf("la cadena %s es mayor que la cadena %s\n",cpi,buf);
e. Concatenar dos cadenas

char* strcat(char*, const char*) ------- <string.h>


Esta funcin concatena la cadena de caracteres p2 a continuacin de la cadena de caracteres p1 y
devuelve p1. La zona de memoria a la que apunta p1 debe ser suficientemente grande y accesible en
escritura.

char buf[100];
char cpi[100];
int i;
printf("entrar una cadena:\n");
fgets(buf,100,stdin);
// eliminacin de \n antes del ltimo carcter
buf[strlen(buf)-1]=\0;
// la cadena se pone 3 veces seguidas en cpi
for (i=0; i<3;i++)
strcat(cpi, buf);
printf("concatenacin: %s\n",cpi);
4. Experimento: tablas y funciones

/*****************************************************************
TABLA y FUNCIN
- Remite a la visibilidad (accesibilidad) de variables en un programa:
VARIABLES LOCALES:
- Las variables son locales a la funcin en la que se declaran.
Es decir, que son visibles (accesibles) solamente en el bloque de
la funcin y en todos sus subbloques anidados.

- Los valores de las variables pueden circular gracias a los


parmetros de entrada y al mecanismo de retorno (return).

VARIABLES GLOBALES:
- Sin embargo, se pueden declarar variables a nivel del archivo
fuera de todo bloque, encima del main(). La variable es accesible
entonces desde todos los bloques y todas las funciones del
archivo, sin tener que usar los parmetros o el return.

- Esta propiedad se usa para las definiciones de tipo (typedef,


tuplas) y valores constantes (#define, enum...).

- Es til para simplificar la escritura de programas pequeos, de


menos de 500 lneas de cdigo. Solo se aplica para variables clave
del programa.

- Utilizada sin conocimiento de causa, esta propiedad presenta el


riesgo de que puede daar el desarrollo (en general el desarrollo se
paraliza rpidamente ms all de las 1000 lneas de cdigo).

*****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// valor constante definido en global


#define NUMMAX 50

// tabla definida y declarada en global, accesible desde cualquier


// parte del programa
int tabGlo[NUMMAX];

// declaracin de funciones definidas debajo del main() (sin


// definir todava en el momento de la llamada)
void init_tabGlobal (void);
void mostrar_tabGlobal (void);

///********************************************************
///********************************************************
int main()
{
init_tabGlobal();
mostrar_tabGlobal();
return 0;
}
///********************************************************
/// la funcin solo puede tratar tabGlo
///********************************************************
void init_tabGlobal()
{
int i;
for (i=0; i<NUMMAX; i++)
tabGlo[i]=rand()%256;
}
///********************************************************
/// la funcin solo puede tratar tabGlo
///********************************************************
void mostrar_tabGlobal()
{
int i;
for (i=0; i<NUMMAX; i++){
printf("%4d",tabGlo[i]);
if (i%10==9)
putchar(\n);
}
}
/*****************************************************************
TABLA LOCAL COMO PARMETRO DE FUNCIN

Cul es el valor numrico de una tabla?

- Sea por ejemplo la declaracin:

int tab[10]; // Cunto vale tab?


// Qu es tab para la mquina?

tab es la direccin de memoria del bloque asignado para la tabla,


es decir, la direccin del primer elemento de la tabla, y todos los
elementos son consecutivos. Pasar una tabla como parmetro de
funcin supone pasar una direccin de memoria al parmetro.
El tipo de variable que adquiere como valor direcciones de memoria
es el tipo puntero.
Por lo tanto, para pasar una tabla a una funcin habr que tener
un puntero como parmetro.

*****************************************************************/
/*
#define NUMMAX_1 20
#define NUMMAX_2 NUMMAX_1*2

void inicializa (int tab[], int tam);


void muestra (int tab[], int tam);
///********************************************************
///********************************************************
int main()
{
int tab[NUMMAX_1]; // tabla local al main()

printf("---------inicializa y muestra tab:\n");


inicializa(tab, NUMMAX_1);
muestra(tab,NUMMAX_1);

int tab2[NUMMAX_2];
printf("---------inicializa y muestra tab2 :\n");
inicializa(tab2, NUMMAX_2);
muestra(tab2,NUMMAX_2);

return 0;
}
///********************************************************
/// la funcin puede tratar cualquier tabla de enteros.
///
/// IMPORTANTE:
/// Debido a que se le ha escrito a una direccin de
/// memoria, las modificaciones realizadas en la funcin
/// repercuten en la variable del contexto de la llamada,
/// de la que se ha dado su direccin (lo que se denomina
/// un paso de parmetro por referencia):
///
///********************************************************
void inicializa(int tab[], int tam) // o int *tab
{
int i;
for (i=0; i<tam; i++)
tab[i]=rand()%256;
}
///********************************************************
/// la funcin puede tratar cualquier tabla de enteros de
/// una dimensin
///********************************************************
void muestra(int tab[], int tam) // o int *tab
{
int i;

for (i=0; i<tam; i++){


printf("%4d",tab[i]);
if (i%10==9)
putchar(\n);
}
}
*/
///********************************************************
///
/// IMPORTANTE:
///
/// El retorno de una tabla esttica local a una funcin
/// es imposible: como para todas las variables, la memoria
/// asignada por la mquina se libera al finalizar la
/// ejecucin del bloque de la funcin.
/// Si se devuelve la direccin de una tabla local, esta
/// ya no estar reservada en las siguientes operaciones
/// y escribir en una memoria no reservada provoca un
/// cuelgue o un comportamiento anmalo del programa.
///
/// Para que funcione, hay que asignar dinmicamente su
/// tabla, es decir, asignarse uno mismo la memoria mediante
/// un puntero y una funcin de asignacin (malloc(),calloc()
/// realloc())
///
///
///********************************************************
/*
int * init()
{
int tab[10];
int i;
for (i=0; i<10; i++)
tab[i]=rand()%256;
return tab; // ERROR: returns address of local variable
}
*/
/*****************************************************************
TABLA LOCAL ESTTICA DE 2 A n DIMENSIONES COMO PARMETRO DE FUNCIN:

- solo la primera dimensin se convierte en puntero. Para el resto,


hay que especificar el tamao

*****************************************************************/
/*
#define DIM1 10
#define DIM2 15

void inicializa (int tab[][DIM2]);


void muestra (int tab[][DIM2]);

///********************************************************
///********************************************************
int main()
{
int dd[DIM1][DIM2]; // tabla 2D local a main()

printf("---------inicializa y muestra tab 2D:\n");


inicializa(dd);
muestra(dd);

return 0;
}
///********************************************************
///********************************************************
void inicializa(int t[][DIM2])
{
int i,j;
for (i=0; i<DIM1; i++)
for (j=0; j<DIM2; j++)
t[i][j]=rand()%256;
}
///********************************************************
///********************************************************
void muestra(int t[][DIM2])
{
int i,j;
for (i=0; i<DIM1; i++){
for (j=0; j<DIM2; j++)
printf("%4d",t[i][j]);
putchar(\n);
}
}
*/
5. Puesta en prctica: tablas y funciones

a. Llamadas a funciones, tablas por parmetro

Ejercicio 1

Escribir una funcin que muestre una de cada dos letras de una cadena de caracteres pasada por
parmetro. Probar la funcin en un programa.

Ejercicio 2

Escribir una funcin que reconozca y muestre letras comunes en dos palabras o frases introducidas por
el teclado. La entrada es ella misma una funcin. Probar en un programa que se detenga cuando lo
solicite el usuario.

Ejercicio 3

Escribir una funcin que cuente el nmero de repeticiones de letras en una palabra o una frase entrada
por el usuario. El resultado se muestra con una funcin distinta. Probar en un programa que se detenga
cuando el usuario lo solicite.
Ejercicio 4

Escribir una funcin que permita examinar la distribucin de los valores aleatorios obtenidos con la
funcin rand() para un nmero de n pruebas entrado por el usuario y que sean valores comprendidos
entre 0 y 9. Probar en un programa que se detenga cuando lo solicite el usuario.

Ejercicio 5

Hay un mtodo para obtener nmeros primos llamado Criba de Eratstenes. Permite obtener todos
los nmeros primos inferiores a un determinado valor n. El mtodo consiste en establecer la lista de
todos los enteros de 1 a n e ir tachando sucesivamente todos los mltiplos de enteros.

El mtodo es:

1) Tachar 1, que no es primo.

2) Pasar al siguiente no tachado (al empezar es el 2) e ir tachando todos sus mltiplos (y solamente sus
mltiplos).

3) Volver a empezar, buscar el siguiente nmero no tachado y tachar sus mltiplos, hasta llegar al final
de la lista.

4) Mostrar la lista de nmeros primos.

Implementar tres funciones, una en la que el usuario especifique el nmero n de enteros de la lista, otra
para determinar los nmeros enteros de la lista y finalmente otra para mostrar la lista. El programa
finaliza si el usuario as lo solicita.

Ejercicio 6

Master mind. Escribir un programa que elija al azar una combinacin de cinco cifras comprendidas
entre 1 y 8 y que solicite al usuario que la adivine. Para cada intento del usuario, el programa
determina:

El nmero de cifras que estn en el lugar correcto.


El nmero de cifras que NO estn en el lugar correcto.
Los intentos del usuario se entran escribiendo cinco cifras (sin separador). La entrada del usuario debe
estar blindada, es decir, que gestione errores: entrada de letras, respuesta demasiado corta o
demasiado larga, cifras incorrectas (no comprendidas entre 1 y 8).

Se establece un nmero mximo de intentos por partida. Este nmero se muestra y disminuye en cada
intento. Si se han agotado todos los intentos, el usuario ha perdido y el programa da la respuesta. A
continuacin, empieza una nueva partida.

Ejercicio 7

Mostrar al azar un cierto nmero de asteriscos (*) en un rectngulo. El nmero de asteriscos y el


tamao del rectngulo son introducidos por el usuario, sabiendo que el tamao mximo es de 25 * 80.
Atencin, no puede haber asteriscos superpuestos.

Ejercicio 8

Sea el siguiente extracto de cdigo:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.c>

#define TX 30 // tamao del rea de juego en x


#define TY 20 // " en y
#define DECX 5 // desplazamiento con relacin al borde izquierdo
#define DECY 5 // desplazamiento con relacin al borde de arriba

// el pas de la tierra media


#define BORDE 1 // en la matriz significa borde
#define MURO 2 // en la matriz significa muro

int jug[2]; // las coordenadas del jugador, 0 para x y 1 para y


int level[TY][TX]={
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,2,2,2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,2,2,2,2,2,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,2,2,2,1,
1,0,0,2,2,2,2,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,2,2,1,
1,0,0,2,2,2,2,2,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,2,2,2,2,2,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,
1,0,0,2,2,2,2,2,2,2,0,0,2,0,0,0,0,0,0,0,0,0,2,2,0,2,2,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
};
El objetivo es tener un jugador que se desplace con el teclado por el mundo representado por la matriz
level. Para ello, hay que escribir cuatro funciones:

Una funcin que permita visualizar a una posicin dada, un carcter dado de un color dado.
Una funcin de inicializacin del terreno a partir de la matriz level.
Una funcin de inicializacin del jugador: adquiere posicin en su mundo respetando el terreno y estos
muros.
Una funcin que gestione las entradas del teclado para el desplazamiento del jugador y el final del
programa.
Ejercicio 9

En el juego de Buscaminas, el terreno se especifica con una matriz de nmeros. Implementar dos
funciones, una para la inicializacin con un nmero de minas repartidas aleatoriamente en el terreno y
otra para indicar cuntas minas hay alrededor de cada posicin en el rea de juego. Escribir un
programa de prueba que muestre las minas en rojo y para cada posicin el nmero de minas que hay
alrededor (si no, no se muestra nada).

b. Operaciones con cadenas

Ejercicio 10

Escribir sus propias versiones de las funciones siguientes:

fgets() / introducir una cadena.


strlen() / contar la longitud de la cadena y devolver el resultado.
strcpy() / copiar una cadena en una segunda.
strcat() / concatenar dos cadenas, la segunda a continuacin de la primera.
strcmp() / comparar dos cadenas y devolver el orden lexicogrfico.
A continuacin, en un programa, comprobar la diferencia de eficiencia de ejecucin entre su versin y
la de la librera estndar (obtener ambas velocidades). Crear un men que permita elegir la funcin que
se desea probar.

Ejercicio 11

La librera estndar <string.h> proporciona otras funciones para el tratamiento de cadenas. Por
ejemplo, las tres funciones: strncat(), strncmp(), strncpy(). Encontrar cmo utilizar estas funciones y, en
un programa, implementar un ejemplo de cdigo que funcione para cada una de las tres.

Ejercicio 12

Escribir una funcin que cuente el nmero de palabras en un texto (el texto puede estar hardcoded en
el programa o puede ser una frase suficientemente larga entrada por el usuario). Probar en un programa
que finalice cuando lo desee el usuario. Escribir una segunda funcin que cuente el nmero de
segmentos obtenidos a partir de dos separadores cualesquiera entrados por el usuario.

Ejercicio 13

Escribir un programa con un men de usuario que permita las operaciones siguientes mientras el
usuario lo desee; cada operacin es una funcin:

Introducir una cadena.


Contar el nmero de vocales en una cadena por parmetro y devolver el resultado.
Contar el nmero de consonantes y devolver el resultado.
Eliminar todas las consonantes de una cadena y modificar la cadena consecuentemente.
Eliminar todas las vocales de una cadena sin modificar la original.
Invertir el orden de las letras de la cadena (buenos das -> sad soneub).
Indicar si una cadena es un palndromo (abccba, anna, azertytreza son palndromos).
Encriptar una cadena con un desplazamiento circular. El valor de encriptacin lo introduce el usuario.
Desencriptar la cadena si est encriptada.
Ejercicio 14

En un programa, escribir una funcin que muestre la conjugacin del presente de indicativo de un
verbo de la 1. conjugacin:

yo canto
t cantas
l/ella canta
nosotros cantamos
vosotros cantis
ellos/ellas cantan
El verbo que se debe conjugar es entrado por el usuario y pasado por parmetro a la funcin.

Al inicio, asegurarse de que el verbo facilitado termine por ar. Se supone que se trata de un verbo
regular.

Ejercicio 15

Escribir una funcin que cuente el nmero de repeticiones de las letras usadas en un pequeo texto. El
texto se pasa por parmetro a la funcin. El resultado se muestra en el contexto de la llamada, despus
de la ejecucin de la funcin. Comprobar en un programa.

Ejercicio 16

Problema: mi teclado se ha estropeado. Cada vez que pulso s, j o g, escribe ch.

En un programa, probar una funcin que reemplace todas las letras s, j y g de un pequeo texto pasado
por parmetro por ch: supone pasa a ser chupone, judas pasa a ser chudach, geranio pasa
a ser cheranio, etc.

A continuacin, integrar en un programa el hecho de que el usuario pueda elegir qu transformacin


aplicar. Escribir una segunda funcin que tenga dos parmetros ms, uno para la letra en la que se
transformar y otro para la letra o conjunto de letras que se transforman.

Gestin de variables

1. Visibilidad de variables

La visibilidad y el tiempo de vida de una variable es relativa al lugar de su declaracin en el programa.


A nivel de anidamiento del bloque, se denomina profundidad de la declaracin.

a. Profundidad de la declaracin
La declaracin se dice que es de profundidad 0 cuando est fuera de todo bloque de instrucciones, y de
profundidad n con n mayor o igual que 1 cuando est en un bloque: n corresponde al nivel de anidacin
del bloque en cuestin. Por ejemplo:

#include<stdlib.h> // nivel 0 en global, fuera de bloque


int x=0;
void test(int a);

int main()
{ // bloque de nivel 1
int i=0;
{ // bloque de nivel 2
int y=9;
{ // bloque de nivel 3
int w=rand();
test(w);
}
}
test(i);
}

void test(int a)
{ // otro bloque de nivel 1
int b;
...// instrucciones de la funcin
}
El parmetro int de la funcin test() se considera de nivel 1, local a la funcin test().

b. mbito de las variables

Sea cual sea su tipo (char, short, int, float, double, struct, tabla, puntero), una variable es visible en el
bloque en el que se declara a partir de su declaracin y en todos los subbloques (nivel de anidacin
superior). En cambio, no es accesible en los bloques superiores (nivel de anidacin inferior) u otros
bloques separados del mismo nivel. Si la variable se declara fuera de todo bloque, es decir, nivel 0 del
archivo, es visible en todo el archivo a partir de su declaracin. Es lo que se denomina una variable
global.

c. Enmascaramiento de una variable

En C todas las declaraciones de variables se agrupan al comienzo del bloque para cada funcin. Es raro
declarar variables en bloques anidados, y las variables globales se usan poco y estn muy controladas
(una variable se declara en global por una razn seria) para evitar, por ejemplo, este tipo de problemas:

#include <stdlib.h>
int x=1000;
int test(int x);

int main()
{
int x;
x=1;
printf ("%d, ",x);
{
int x;
x=2;
printf ("%d, ",x);
{
int x;
x=3;
printf ("%d, ",x);
}
{
int x;
x=33;
printf ("%d, ",x);
}
printf ("%d, ",x);
}
x++;
printf ("%d, ",x);
x=test(x);
printf("%d.",x);
return 0;
}

int test(int x)
{
x+=50;
return x;
}
Qu imprime el programa anterior? Las cinco variables del programa tienen todas el mismo nombre,
pero hay una prioridad entre las variables: una declaracin de variable local en un bloque de nivel n,
parmetros incluidos, enmascara el resto de las variables del mismo nombre de niveles anteriores.

El programa anterior imprime: 1, 2, 3, 33, 2, 2, 52.

La variable x declarada en global fuera de todo bloque antes del main() no se modifica, se encuentra en
este ejemplo siempre enmascarada. En la funcin test() est enmascarada por el parmetro int x de la
funcin. Las variables enmascaradas no se tocan y conservan su valor. Vuelven a ser accesibles en la
salida del bloque, cuando dejan de estar enmascaradas.

2. Tiempo de vida de las variables

a. Variables globales

Una variable global, es decir, declarada fuera de todo bloque, existe durante toda la ejecucin del
programa a partir de su declaracin.
b. Variables locales (auto)

En cambio, el tiempo de vida de una variable local a un bloque tiene el tiempo de vida del mismo
bloque. Dura en el programa el mismo tiempo del bloque en el que se ha declarado. Por ejemplo:

void tatatata()
{
int x, y ;
// instrucciones
}
Las variables x e y son variables llamadas automticas a las que se aade implcitamente la palabra
clave auto en la compilacin. Aparecen con sus declaraciones al comienzo del bloque y desaparecen
automticamente al final de este. Se les asigna automticamente un espacio en memoria a la entrada del
bloque y este espacio de memoria se libera automticamente al finalizar la ejecucin del bloque.

c. Variables static

Sin embargo, existe otro tipo de asignacin de variables; es el tipo de variables llamado static. Una
variable declarada static existe durante toda la vida del programa en el bloque en el que se ha
declarado. Su ubicacin en memoria no se modifica. Si en una funcin se inicializa en la declaracin, la
inicializacin tiene lugar una sola vez, en la primera llamada a la funcin. Por ejemplo:

void test( )
{
static int v=0;
printf("toto=%d\n",v);
v++;
}
Cada llamada a esta funcin test() mostrar el valor de la variable y a continuacin la incrementar en
1. En el siguiente programa, se llama a la funcin en cada pulsacin de cualquier tecla del teclado:

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

int main()
{
int fin=0;
while (fin!=q){
if (kbhit()){
fin=getch();
test();
}
}
return 0;
}
La primera vez que el usuario pulse una tecla, se mostrar el nmero 0. Las siguientes veces se
mostrar 1, 2, 3, etc.
Si la variable es adems global, la palabra clave static sirve para limitar su mbito solo al archivo en el
que se ha declarado. Solo ser visible en el archivo de su declaracin y en ningn caso se podr usar
desde otro archivo C del programa. Esta propiedad es importante para evitar cualquier confusin entre
variables globales en programas grandes.

3. Eleccin de metodologas

El enmascaramiento, declaracin sucesiva de variables con el mismo nombre a niveles diferentes, se


evita en C porque genera un cdigo fuente confuso que ofusca la legibilidad de un programa.

Las variables locales siempre se definen al inicio del bloque, en general el nivel 1 a la entrada del main
y a las entradas de las funciones.

Una variable local se declara static solo cuando el algoritmo lo justifica.

En el caso de las variables globales, lo importante es tener una razn seria para ponerlas en ese mbito
y dominar su uso adecuadamente. Todas se agrupan al comienzo del archivo C que las utiliza y nunca
se reparten en el archivo. Si hay varios archivos, la declaracin de la variable global debe exportarse
con la palabra clave extern en el resto de los archivos que la necesiten. Pero se desaconseja su uso; es
mejor declarar la variable extern en una librera personal e incluir esta librera en los archivos en que
se necesite (ver la seccin Integrar una librera personal).

En programas de tamao grande, se puede limitar a la inversa la zona de influencia de una variable
global declarndola static a un archivo.

4. Puesta en prctica: gestin de variables

Ejercicio 1

Mentalmente: qu resultado da el siguiente programa?

#include<stdio.h>

int i=0;
int main()
{
int i=1;
printf("i=%d\n",i);
{
int i=2;
printf("i=%d\n",i);
{
i+=1;
printf("i=%d\n",i);
}
printf("i=%d\n",i);
}
printf("i=%d\n",i);
}
Ejercicio 2

Mentalmente: qu resultado da el siguiente programa?

#include<stdio.h>

int f1(int);
void f2(void);

int n=10, q=2;

int main()
{
int n=0, p=5;
n=f1(p);
printf("A: en main, n=%d, p=%d, q=%d\n",n,p,q);
f2();
return 0;
}

int f1(int p)
{
int q;
q=2*p+n;
printf("B: en f1, n=%d, p=%d, q=%d\n",n,p,q);
return q;
}

void f2(void)
{
int p=q*n;
printf("C: en f2, n=%d, p=%d, q=%d\n",n,p,q);

}
Ejercicio 3

Mentalmente: qu resultado da el siguiente programa?

##include<stdio.h>
#define LOW 0
#define HIGH 5
#define CHANGE 2

void workover(int i);


int reset(int i);
int i=LOW;

int main()
{
int i=HIGH;
reset(i/2);
printf("i=%d\n",i);
reset (i=i/2);
printf("i=%d\n",i);
i=reset(i/2);
printf("i=%d\n",i);

workover(i);
printf("i=%d\n",i);
}

void workover(int i)
{
i=(i%i) * ((i*i)/(2*i) +4);
printf("i=%d\n",i);
}

int reset (int i)


{
if (i<=CHANGE)
i=HIGH;
else
i=LOW;
return i;
}
Ejercicio 4

Sea la funcin:

void wait(int tmp)


{
int start=clock();
while (clock()<start+tmp){}
}
Modificar esta funcin para desencadenar una accin cada num milisegundos sin parar el programa.
Probar en un programa.

Ejercicio 5

Escribir dos funciones diferentes que se invoquen en un mismo bucle. Cada funcin se ralentiza con un
valor diferente y cada una muestra en cada llamada el nmero total de veces que se ha llamado. Probar
en un programa.

Ejercicio 6

Sea una tala de enteros declarada en global. Escribir dos funciones de inicializacin (valores
aleatorios), una con parmetro y la otra sin. Escribir, tambin, dos funciones de visualizacin, una con
parmetro, la otra sin. Probar en un programa.
0
(x+1, y+1)
con valor
0
( x, y ) en el centro es la posicin actual. x se usa para la posicin horizontal e y para la posicin
vertical. Hay 3 posiciones adyacentes que valen 1.

El valor de la posicin actual evolucionar o se quedar tal y como est, en funcin de una regla de
transicin. La regla de transicin se basa en los valores encontrados en las posiciones colindantes. En el
ejemplo anterior, la posicin actual tiene contabilizados tres 1, encontrados en las celdas adyacentes.
De algn modo, hay 3 vecinos. La regla de transicin se basa en este nmero de vecinos.

Por ejemplo, se tendr una regla del tipo: si el nmero de vecinos es menor que dos o si el nmero de
vecinos es mayor que tres, es decir, si el nmero de vecinos es distinto de 2 o de 3, la posicin actual
adquiere el valor uno; si no, adquiere el valor cero.

Se puede imaginar otras reglas; de forma general, el principio es: para la posicin actual, si el nmero
de vecinos es menor que un lmite inferior o si el nmero de vecinos es mayor que un lmite
superior, entonces la posicin actual adquiere el valor booleano v1; si no, adquiere el valor
booleano complementario v2.

Es conveniente precisar que el clculo se realiza segn las posiciones del plano de cada etapa sin los
resultados. Para cada posicin, los resultados se almacenan en un plano equivalente, copia del primero.
Una vez que los valores de cada posicin se han calculado y se han guardado en el plano reservado a
los resultados, este ltimo se copia al plano inicial y se vuelve a empezar la operacin:

images/03ri06.png
2. Encontrar una estructura de datos adecuada

Ya tenemos el principio del autmata, pero cmo implementar un programa? Cmo traducir este
proceso en una serie de instrucciones en un lenguaje para la mquina?

Una vez se ha planteado la idea de la que se partir, el primer punto que se debe tratar es reflexionar
sobre la posibilidad de una estructura de datos. En qu se basar el motor del autmata para
funcionar? Es decir, qu tipo de datos se pueden escoger para codificar el autmata? Variables
simples? Tuplas? Tablas?

En este caso, es evidente y no resulta muy complicado. Bsicamente, hay dos superficies 2D de
booleanos. Dos matrices de enteros pueden sernos realmente tiles, una para el plano inicial y otra para
el resultante. El tamao de ambas se definir con macros constantes, con lo que obtenemos:

#define TX 80
#define TY 50

int MAT[TY][TX];
int SAV[TY][TX];
3. Identificar las funciones principales
Ahora que ya sabemos sobre qu trabajar, nuestras dos matrices, cules son las operaciones que
debemos realizar para implementar el principio de funcionamiento que hemos explicado? Es decir,
cules son las operaciones que se deben realizar y en qu orden?

En este caso, se trata de basarnos en nuestras dos matrices para encontrar y disear un algoritmo
eficiente para el proyecto.

1) A priori, la primera etapa es una etapa de inicializacin:

Al comienzo, ambas matrices se inicializan a 0. Algunas posiciones del plano inicial, la matriz MAT, se
ponen a 1. Escribir una funcin de inicializacin.
2) A continuacin, el motor ejecuta un bucle y en cada iteracin debe:

Mostrar el plano inicial.


Calcular la transicin para cada posicin y guardar el resultado en SAV.
Copiar la matriz SAV en el plano inicial MAT.
Ya se ve claramente que se han de escribir cuatro funciones:

Una funcin de inicializacin.


Una funcin de visualizacin del plano.
Una funcin de clculo.
Una funcin de copia.
Inicializacin, visualizacin y copia son sencillas. La funcin de clculo requiere un pequeo zoom:

Calcular es recorrer (bucle) todas las posiciones del plano inicial y:

1-para cada posicin contar el nmero de posiciones adyacentes a 1.


2-aplicar la ley de transicin en funcin del nmero.
3-almacenar el resultado en la matriz SAV.
En estas tres fases del algoritmo, la primera, contar el nmero de posiciones adyacentes, puede ser
objeto de una funcin aparte. Esta funcin podra recibir por parmetro la posicin actual en la matriz
inicial y devolver el nmero de posiciones adyacentes a 1.

La segunda etapa podra tambin ser objeto de una funcin aparte. Por ejemplo, si el motor estaba
destinado a funcionar con diferentes parmetros de leyes de transicin. Estos parmetros podran
entonces pasarse por parmetro.

Pero por ahora el motor solo tendr un tipo de ley de transicin. El tratamiento en la funcin de clculo
solo requerir contar las posiciones adyacentes a 1: una quinta funcin, la funcin de contar vecinos.

4. Elegir el nivel de las variables fundamentales

Ahora, tenemos una hiptesis de estructura de datos y una hiptesis de funciones que se deben
implementar. Solo nos falta un punto por decidir: las matrices son variables globales visibles para
todas las funciones, o se encapsulan localmente en el main(), lo que requerira una transmisin
mediante los parmetros?

Cul es la diferencia desde el punto de vista del desarrollo del proyecto? Cules son las ventajas o
inconvenientes de una alternativa u otra?

Si nuestras matrices son globales, no necesitamos pasarlas por parmetro y, con ello, aligeramos un
poco la escritura de funciones. Es un programa pequeo que usa un solo archivo. No hay confusin
alguna con otros procesos en ejecucin en el mismo programa. Un problema podra surgir en el futuro
si el desarrollo del proyecto llegara a ejecutar mltiples autmatas y gestionar varios planos iniciales.
En este caso, sera preferible poder pasar al menos el plano por parmetro.

Sin embargo, habra tambin la posibilidad de usar una tabla de matrices declarada en global y
modificar un poco cada funcin aadiendo un bucle para NUM matrices:

#define NUM 10
int NUMMAT[NUM][TY][TX]; // 10 matrices de TY por TX
Pero, a priori, para lo que queremos hacer ahora, no hay ningn inconveniente en usar dos matrices en
global.

Ambas matrices y las macros se declaran al comienzo del archivo, despus de las directivas include
necesarias y antes del main():

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

#define TX 80
#define TY 50

int MAT[TY][TX];
int SAV[TY][TX];

En general, todas las variables globales se especifican con los nombres en maysculas por convencin.

5. Escribir las funciones

a. Funcin de inicializacin

La funcin de inicializacin es sencilla:

Poner a 0 ambas matrices.


Poner a 1 algunas casillas.
void inicializar_matrices(void)
{
int i,j;

// todas las posiciones se ponen a 0


for (j=0; j<TY; j++){
for (i=0 ;i<TX ;i++){
MAT[j][i]=0 ;
SAV[j][i]=0;
}
}
// excepto cuatro posiciones en el centro que se ponen a 1
MAT[TY/2][TX/2]=1;
MAT[TY/2+1][TX/2]=1;
MAT[TY/2][TX/2+1]=1;
MAT[TY/2+1][TX/2+1]=1;
}
El hecho de poner ambas matrices a 0 al comienzo nos permitir llamar varias veces a la inicializacin
de matrices durante el funcionamiento del programa y volver a ejecutar el proceso de propagacin.

A continuacin, la misma funcin, con la posibilidad de transmisin de distintas matrices del mismo
tamao (TY y TX) por parmetro:

void inicializar_matrices(int M[][TX],int S[][TX])


{
int i,j;

for (j=0; j<TY; j++){


for (i=0 ;i<TX ;i++){
M[j][i]=0 ;
S[j][i]=0;
}
}
M[TY/2][TX/2]=1;
M[TY/2+1][TX/2]=1;
M[TY/2][TX/2+1]=1;
M[TY/2+1][TX/2+1]=1;
}
b. Funcin de visualizacin

La funcin de visualizacin utiliza las funciones gotoxy() y textcolor (ver anexo sobre funciones
consola in-out). El principio es simple: recorrer la matriz y, para cada posicin, si el valor es 1,
colorearlo en rojo y, si el valor es 0, colorearlo en azul y en ambos casos mostrar un espacio.

void visualizar(void)
{
int x,y ;

for (y=0; y<TY; y++){ // para cada posicin


for (x=0 ;x<TX ;x++){
gotoxy(x,y); // desplazar el cursor a la posicin
if(MAT[y][x]==1) // si el valor es igual a 1
textcolor(192); // color rojo de fondo
else
textcolor(16); // si no, color azul de fondo
putchar( ); // mostrar un espacio
}
}
}
/****************************************************************
****************************************************************/
void gotoxy(int x, int y)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(h,c);
}
/****************************************************************
*****************************************************************/
void textcolor(int color)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(h,color);
}
/****************************************************************
*****************************************************************/
c. Funcin de clculo

La funcin de clculo invoca a la funcin que cuenta los vecinos que requiere la posicin actual por
parmetro y devuelve el nmero de vecinos encontrados (las posiciones que estn a 1). Para cada
posicin, se aplica la ley de transicin y devuelve 0 si el nmero de vecinos es menor que 2 o mayor
que 3 (si el nmero de vecinos es distinto de 2 y de 3), y devuelve 1 en el resto de los casos.

void calcular(void)
{
int x,y,num_vecinos;

// para cada posicin de la matriz (sin contar el


// permetro debido a la bsqueda de vecinos: de 1 a TY-1
// y no de 0 a TY, dem para x)
for (y=1; y<TY-1; y++){
for (x=1 ;x<TX-1 ;x++){

// recuperacin del nmero de vecinos a 1


num_vecinos = contar_vecinos(y,x);

// aplicacin de la ley de transicin bsica y


// almacenamiento del resultado en la matriz SAV
if (num_vecinos <2 || num_vecinos>3)
SAV[y][x]=0;
else
SAV[y][x]=1;
}
}
}
La funcin de clculo puede ser un poco ms sofisticada; puede tener en cuenta el valor de cada
posicin y variar la ley de transicin:

// variante de la ley de transicin:


if (MAT[y][x]==1){ // por ejemplo, tener en cuenta el estado de la
// posicin actual

if (num_vecinos <2 || num_vecinos>3 ) // Tambin se puede


// modificar 2 y 3
SAV[y][x]=0;
else
SAV[y][x]=1;
}
if (MAT[y][x]==0){ // pequea variacin
if (num_vecinos!=0 && (num_vecinos <2 || num_vecinos>3) )
SAV[y][x]=1;
else
SAV[y][x]=0;
}
d. Funcin para contar vecinos

Dada una posicin determinada, la funcin devuelve el nmero de posiciones adyacentes a 1:

// en parmetro una posicin determinada


int contar_vecinos(int y, int x)
{
int num=0; // por defecto 0 vecinos a 1

if (MAT[y][x+1]==1) // examen de cada posicin adyacente


num++; // si 1, incrementar la cuenta
if (MAT[y-1][x+1]==1) // los valores y-1, y+1, x-1, x+1
num++; // tienen que estar siempre en la matriz,
if (MAT[y-1][x]==1) // ojo a los desbordamientos!
num++;
if (MAT[y-1][x-1]==1)
num++;
if (MAT[y][x-1]==1)
num++;
if (MAT[y+1][x-1]==1)
num++;
if (MAT[y+1][x]==1)
num++;
if (MAT[y+1][x+1]==1)
num++;

return num; // al final se devuelve el nmero encontrado


}
e. Funcin de copia
Hay dos posibilidades de copia: copiar mediante un bucle anidado o utilizar la funcin de la librera
estndar string.h, probablemente ms rpida y en una sola lnea.

void copia(void)
{
int i,j ;
/*
for (j=0; j<TY; j++){ // mtodo bsico
for (i=0 ;i<TX ;i++){
MAT[j][i]= SAV[j][i];
}
}*/
// Con la funcin memcpy de string.h
memcpy(MAT,SAV,sizeof(int)*TX*TY);
}
La funcin memcpy recibe en el primer parmetro la tabla destino, en el segundo la tabla origen y en el
tercer parmetro el tamao en bytes del bloque que se desea copiar. El primer parmetro debe tener
espacio suficiente y estar ya asignado.

f. Montaje en el main()

Ya solo falta montar el bucle principal del programa en el main(). Hay varias posibilidades. Hemos
elegido hacer que avance el programa cada vez que se pulse una tecla. El programa finaliza si se pulsa
la tecla q.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>

#define TX 80
#define TY 50

extern int MAT[TY][TX];


extern int SAV[TY][TX];

void inicializar_matrices(void);
int contar_vecinos (int x, int y);
void calcular (void);
void copia (void);
void visualizar (void);
void gotoxy (int x, int y);
void textcolor (int color);

int main(int argc, char *argv[])


{
int fin=0;
printf("Accin: pulsar sobre cualquier tecla\n"
"Para salir: q");

inicializar_matrices(); // inicializacin una vez al inicio

while(fin!=q){ // bucle principal

if(kbhit()){ // si se pulsa una tecla, recuperar


fin=getch(); // la tecla pulsada para controlar
// el fin del motor de organizacin
visualizar();
calcular();
copia();
}
}
return 0;
}
// poner las funciones declaradas anteriormente a continuacin
// para tener el programa completo
6. Integrar una librera personal

Una librera personal es un archivo .h, tambin llamado archivo de cabecera o header en ingls. Con
vistas al uso de programas de gran tamao, es til transportar toda la cabecera del archivo, es decir, los
include, las macros, las variables globales, las definiciones de tipo si las hay y las declaraciones de
funciones a una librera personal. A continuacin, basta con hacer un include de esta librera en cada
archivo C que utilice los datos que contenga.

El programa que acabamos de presentar es un pequeo programa que no necesita libreras. Sin
embargo, nada nos impide aadirle una. Los pasos que es preciso realizar sern los mismos sea cual sea
el tamao del programa.

Segn el entorno de desarrollo utilizado, cree un archivo .h, si es posible con el mismo nombre que el
archivo .c al que corresponde o con el nombre del programa si solo hay un .h para el programa. A
continuacin, corte lo que hay en la cabecera del archivo .c y pguelo en el archivo .h.

// retirado de autocelular.c y puesto en autocelular.h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>

#define TX 80
#define TY 50

int MAT[TY][TX];
int SAV[TY][TX];
void inicializar_matrices(void);
int contar_vecinos (int x, int y);
void calcular (void);
void copia (void);
void visualizar (void);
void gotoxy (int x, int y);
void textcolor (int color);
Y en el archivo .c, reemplazar todo esto por la inclusin de la librera, incluyendo la librera autocell.h
al comienzo del archivo.c:

#include "autocell.h" // atencin " " y no < >

Los smbolos < y > para la inclusin de libreras requieren que la librera est en las carpetas include
del compilador. En cambio, las comillas dobles requieren que la librera se encuentre en la carpeta del
proyecto del programa.

7. Repartir el cdigo en varios archivos C

Nuestro programa no necesita una descomposicin en varios archivos C, pero podemos hacerlo a ttulo
de ejemplo.

Descomponer el programa en varios archivos requiere una descomposicin por unidad de tratamiento.
Esta descomposicin debe tener un sentido: el objetivo es navegar ms fcilmente por el cdigo. Piense
que escribir programas de varios miles de lneas, o varias decenas o centenares de miles de lneas. A
esta escala, los programas se vuelven como autnticas ciudades y se necesitan mapas muy detallados
para navegar por ellas. Por lo tanto, hay que encontrar una arquitectura con fundamento cuando se trate
de descomponer el cdigo en archivos. Para nuestro programa de ejemplo, podemos poner lo que
concierne al clculo en un archivo, lo que concierne a la visualizacin en otro y la inicializacin en un
tercer archivo.

Con ello, dejamos que el desarrollo del proyecto resulte ms sencillo. Por ejemplo, puede tener varios
tipos de visualizacin, varios tipos de clculo y varias inicializaciones posibles, todo controlado por
una interfaz en el main().

Tcnicamente, para tener varios archivos, basta con crear estos archivos en su entorno de desarrollo e
integrarlos en el proyecto. A continuacin, solo faltar incluir su librera o sus libreras y escribir el
cdigo deseado.

Observacin:

Cuando hay varios archivos .c con varios include a la misma librera, para que el compilador solo
ejecute el cdigo una vez, es recomendable aadir el siguiente conjunto de directivas al comienzo del
header:

#ifndef ARCHIVO_H // por convencin se pone el nombre del


// archivo.h en maysculas
#define ARCHIVO_H

// poner sus datos aqu


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>

#define TX 80
#define TY 50

int MAT[TY][TX];
int SAV[TY][TX];

void inicializar_matrices(void);
int contar_vecinos (int x, int y);
void calcular (void);
void copia (void);
void visualizar (void);
void gotoxy (int x, int y);
void textcolor (int color);

#endif // para cerrar


De este modo, la inclusin solo se producir una sola vez, incluso si se especifica en varios archivos .c.
En cada archivo C del proyecto del autmata, podemos escribir:

#include "autocelular .h"


Otra observacin: cuando se declara una variable global en una librera, puede haber un posible
problema de redefinicin de esta variable en cada inclusin de la librera en el momento de la
compilacin. En este caso, la variable debe declararse una sola vez en un solo archivo C y exportarse
en la librera mediante la palabra clave extern. Por ejemplo, para el autmata, las matrices pueden tener
este problema. Por lo tanto, hay que declararlas en global, normalmente en el archivo del main:

#include "autocelular.h"

int MAT[TY][TX];
int SAV[TY][TX];

int main(int argc, char *argv[])


{
int fin=0;

printf("Accin: pulsar sobre cualquier tecla\n"


"Para salir: q");

inicializar_matrices(); // inicializacin una vez al inicio

while(fin!=q){ // bucle principal

if(kbhit()){ // si se pulsa una tecla, recuperar


fin=getch(); // la tecla pulsada para controlar
// el fin
visualizar(); // motor de organizacin
calcular();
copia();
}
}
return 0;
}
Y declararlas de nuevo en la librera, precedidas de la instruccin extern:

#ifndef ARCHIVO_H
#define ARCHIVO_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>

#define TX 80
#define TY 50

extern int MAT[TY][TX];


extern int SAV[TY][TX];

void inicializar_matrices(void);
int contar_vecinos (int x, int y);
void calcular (void);
void copia (void);
void visualizar (void);
void gotoxy (int x, int y);
void textcolor (int color);

#endif
8. Puesta en prctica: estructuracin de un programa

a. Simulacin de un incendio forestal

Inspirndose en el autmata celular presentado anteriormente, simular un fuego forestal. Una vez que el
espacio que representa el bosque se ha formado, el fuego comienza por ejemplo en un extremo y se
propaga pasando de rbol a rbol cuando hay rboles en contacto. Se detiene en caso contrario. Solo
hay que tener en cuenta cuatro direcciones: norte, sur, este y oeste. Es un programa corto, de cuatro
funciones como mximo.

b. Tristus y alegrus

Nos encontramos en un mundo que se reparte entre los tristus (personalidades tristes y negativas, que
siempre ven lo que sale mal y reactivan por sistema los problemas que no tienen solucin) y los
alegrus (siempre de buen humor, un poco simples y que se ren por nada). Un tristus puede llegar a
entristecer a un alegrus y este acabar convirtindose en un tristus si no puede rerse. De igual manera,
un alegrus puede llegar a hacer rer progresivamente a un tristus, con lo que se transforma entonces en
alegrus.

Simular un mundo repartido entre tristus (un color fro) y alegrus (un color clido). Queremos ver
aparecer una lnea fluctuante entre las dos esferas, o varias si hay ms territorios...

c. Simulacin de un ataque de microbios en la sangre

En el microscopio podemos ver en la sangre de un individuo unos microbios luchando con los glbulos
blancos del sistema inmunitario. Segn diferentes parmetros, la enfermedad se extiende o no se
extiende. Crear una simulacin simplificada del proceso en 2D.

d. Bancos de peces, movimientos de poblacin

Encontrar un medio para simular la organizacin colectiva del movimiento de un banco de peces.

e. Eleccin presidencial

Nos encontramos en el periodo de campaa electoral. Todo el mundo se pregunta a cul de los dos
candidatos destinar el voto.

Hay:

Los que tienen la idea dominante de su entorno prximo.


Los que, al contrario, se rebelan contra las ideas de sus seres prximos y se decantan por la idea
contraria a la de la mayora de su entorno.
Los que estn indecisos y adoptan de forma imprevisible uno u otro de los puntos de vista.
Los que se abstienen porque no pueden decidirse debido a que las opiniones que los rodean son
opuestas y de la misma intensidad.
Los que se abstienen porque no quieren bajo ningn concepto a uno u otro.
Los que se encierran en sus convicciones, sean cuales sean las ideas de alrededor, pero evolucionan a
medida que va avanzando el tiempo.
El objetivo es hacer una simulacin grfica 2D de la evolucin de la opinin de la sociedad da tras da
durante 3 meses antes de las elecciones. Despus de haber propuesto una estructura de datos, establecer
el principio del algoritmo del motor, listar las principales funciones que se deben implementar y
escribir el programa para probarlo.

f. Oruga

Implementar una oruga que se desplace por la pantalla con las flechas del teclado. Usar siempre el
mismo proceso de diseo; para empezar, discernir entre qu estructura de datos usar: qu hace la
oruga? Qu la distingue de un jugador normal? De qu est formada? De qu forma se puede
representar y codificar?

A continuacin, detectar las grandes fases del programa. En una primera etapa, implementar una oruga
sencilla con tamao fijo. Aadir a continuacin un mecanismo que la haga alargarse. Aadir, incluso,
un control para impedir que vaya. Es un programa con cinco funciones como mximo.
g. Sistema de vida artificial, colonias de hormigas

Simular las interacciones entre varias colonias de hormigas. Las hormigas artificiales necesitan
alimentarse y al final hay NUM colonias concurrentes de hormigas en un solo mundo. Escribir un
programa por fases. El mundo puede aadirse desde el inicio o en cualquier otra fase:

1) Hormiga sola:

Determinar la estructura de datos para una hormiga.


Escribir y comprobar una funcin de inicializacin para una hormiga.
Escribir y comprobar la funcin de visualizacin y borrado para una hormiga.
Escribir y comprobar una funcin para mover una hormiga.
2) Una colonia de hormigas:

Determinar la estructura de datos para una colonia de NUM hormigas.


Escribir y comprobar una funcin de inicializacin para toda la colonia.
Escribir una funcin para el movimiento de toda la colonia.
Escribir y comprobar una funcin de visualizacin y borrado de la colonia.
Imaginar un tipo de relacin entre las hormigas que influya por ejemplo en el movimiento de la
colonia.
3) Varias colonias de hormigas:

Determinar la estructura de datos de NUM colonias de hormigas.


Escribir y comprobar una funcin de inicializacin para todas las colonias.
Escribir una funcin para el movimiento de todas las colonias.
Escribir y comprobar una funcin de visualizacin y borrado de las colonias.
Escribir una o varias funciones para gestionar las relaciones entre colonias.
4) Aadir un mundo en el que las hormigas progresen:

Determinar la estructura de datos para el mundo de las hormigas.


Inicializar y mostrar el mundo de las hormigas.
Escribir una o varias funciones para gestionar la relacin de una hormiga con el mundo.
Escribir una o varias funciones para gestionar las relaciones de todas las hormigas con el mundo.
h. Botones y pginas

Se trata de crear un sistema para la gestin de sus botones en un programa de consola. Escribir este
programa en papel. Sin embargo, se puede hacer este programa en una mquina utilizando la librera
creaco proporcionada en la seccin de descargas.

Se define un botn para una posicin, dos colores, uno o dos textos (tablas de char). Puede haber varias
pginas en el programa. El nmero de botones y de pginas lo elige usted para hacer pruebas. La
carga de las pginas se efecta usando las flechas izquierda y derecha:

if (key_pressed(VK_LEFT))
cambiar_pag_actual(); // hacia la izquierda
if (key_pressed(VK_RIGHT))
cambiar_pag_actual(); // hacia la derecha
La funcin cambiar_pag() ya viene dada. Incrementa o decrementa una variable global: int pag_actual;
y tambin se encarga de la visualizacin por pantalla de la pgina seleccionada.
Tambin dispone del ratn de la siguiente forma:

la variable mouse_x da su posicin horizontal


la variable mouse_y da su posicin vertical
La funcin clic_pressed(int numClic) indica si hay clic; por ejemplo:

if (clic_pressed( 1 )==1)
printf("clic izquierdo pulsado\n");
if (clic_pressed( 2 )==1)
printf("clic derecho pulsado\n");
Cuando se pulsa un botn, llamar a una funcin accion(botonNum) segn el nmero del botn
activado.

1) Tratar un elemento nicamente:

Estructura de datos, inicializacin, visualizacin, accin.


2) Tratar un conjunto de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un solo elemento.
3) Tratar varios conjuntos de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un conjunto de elementos.
i. Paneles de madera y almacenes

Un aserradero dispone de varios almacenes en los que guarda sus lotes de paneles de madera. El
nmero de almacenes y el nmero de paneles son elegidos por usted (por ejemplo, cuatro almacenes:
norte, sur, este y oeste, y 10000 paneles como mximo por almacn).

Un panel se define por su altura, su anchura, su grosor y un tipo de madera (cuatro variedades: abeto,
fresno, olivo, roble).

Crear una base de datos simple en consola para la gestin del almacenamiento de todos los paneles. El
proyecto puede hacerse sobre el papel.

1) Tratar un elemento nicamente:

Estructura de datos, inicializacin, visualizacin, accin.


2) Tratar un conjunto de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un solo elemento.
3) Tratar varios conjuntos de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un conjunto de elementos.
j. Nenuphs et clans
En un mundo microscpico descubierto por casualidad en un meteorito cado en el Sahara, viven unas
extraas criaturas fluorescentes con forma de pequeos nenfares llamadas Nenufs. El nenuf es
mvil y coloreado. Vive en la superficie de las piedras duras y tiene solo dos dimensiones. Por la noche
produce una msica cuntica inaudible para nosotros. El nenuf vive en clanes y puede haber varios
clanes en la misma piedra. Segn una teora reciente, los nenufs y su msica actan sobre lo
infinitamente pequeo y seran el origen de las piedras preciosas...

El objetivo es hacer una simulacin simplificada en la ventana de la consola de la vida de los nenufs.
Para comenzar, habra que poder visualizar varios clanes.

1) Tratar un elemento nicamente:

Estructura de datos, inicializacin, visualizacin, accin.


2) Tratar un conjunto de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un solo elemento.
3) Tratar varios conjuntos de elementos:

Estructura de datos, inicializacin, visualizacin, accin, utilizando las funciones implementadas para
un conjunto de elementos.
k. Nieve 1

Escribir un programa que muestre copos de nieve cayendo desde lo alto de la ventana de la consola.
Los copos pueden mostrar un mensaje mientras descienden, por ejemplo Feliz ao! si es la poca
adecuada.

El primer punto consiste en definir la estructura de datos: qu hace un copo? cmo puede aparecer en
nuestro entorno de texto? En una palabra: con qu aspecto, con qu codificar un copo? Todos los
copos?

A continuacin, detectar las grandes fases del programa. Es un programa corto. Hay bsicamente tres
funciones que implementar e invocar desde el main().

l. Nieve 2

Escribir un programa en el que los copos de nieve caigan desde lo alto de la ventana de consola pero, a
diferencia de lo que suceda en el proyecto 1, aqu los copos se acumulen en la parte de abajo. El
problema es que, llegado un cierto momento, no cabrn ms en la zona de visualizacin. Encontrar una
solucin para gestionar esta acumulacin de modo que la nieve pueda seguir cayendo sin parar.

m. Nieve 3

Siguiendo con el tema de los copos de nieve que caen por pantalla, hay obstculos en el camino que
siguen los copos. Estn obligados o bien a pararse o bien a intentar encontrar la salida cuando es
posible para llegar finalmente al suelo:

images/03ri07.png
n. Arkanoid sencillo

La plataforma es una sola casilla en la parte inferior de la pantalla (o sobre un borde). Se desplaza con
las teclas. Una tecla permite enviar bolas. Si un ladrillo es tocado por una bola, desaparece. La pelota
no rebota. En cada disparo la base lanza una nueva pelota. El objetivo es hacer desaparecer todos los
ladrillos y se mostrar un mensaje de xito al final.

o. Arkanoid gur

Arkanoid ms completo. La base tiene una dimensin, por ejemplo tres casillas. La pelota ahora ya
rebota con los obstculos que va encontrando. Cuando toca un ladrillo, este desaparece. La base debe
atrapar la bola de nuevo para que no se pierda. Se gana cuando se han destruido todos los ladrillos con
el nmero de bolas disponibles.

p. Space invaders sencillo

Los enemigos estn quietos. El jugador se desplaza horizontalmente en la parte de abajo de la pantalla
y dispara a los enemigos. Cuando los enemigos son alcanzados por un disparo, desaparecen.

q. Space invaders avanzado

Los enemigos estn siempre quietos, pero tienen caractersticas de defensa y ataque. Algunos duran
ms antes de desaparecer, tienen que ser alcanzados varias veces. Otros pueden disparar.

r. Space invaders gur

Los enemigos se mueven lateralmente y avanzan poco a poco contra el jugador...

s. Pacman sencillo

Un jugador avanza en un mundo.

t. Pacman avanzado

El mundo en el que se encuentra el jugador tambin es frecuentado por enemigos. No hay interaccin
entre ellos, pero no pueden pasar unos por encima de otros. Si un enemigo encuentra al jugador, cambia
a otra direccin. Lo mismo sucede cuando dos enemigos se encuentran.

u. Pacman gur

El jugador est armado y puede eliminar a enemigos a medida que se los va encontrando. Algunas
veces algunos enemigos pueden contraatacar.

v. Juego de los espejos

El objetivo es el de realizar un circuito de espejos con un rayo lser. Hay que tirar el rayo al primer
espejo y prepararlo todo para que el rayo se vaya reflejando por todos los espejos en el orden que estn
dispuestos. Cada vez que el usuario pulse [Enter], aparece un pequeo espejo y con las flechas se
conduce al sitio deseado. Cuando se ha montado una red, si el usuario pulsa [Espacio], el can lser
aparecer en el borde de la pantalla o en otro lugar. El jugador coloca el lser encarado al primer espejo
y dispara. Si el rayo consigue recorrer todo el circuito, el jugador gana. Hay varias formas de abordar el
escenario de este programa.

w. Simulador de ftbol

El objetivo es entrar en la programacin de un juego de ftbol. Hay que realizar diversos niveles y
etapas:

Un jugador y un baln.
Dos jugadores y un baln.
Un jugador contra el ordenador.
Un jugador y un equipo.
Un jugador y un equipo y un baln.
Dos jugadores y un equipo y un baln.
Un jugador y su equipo contra el ordenador.
Avanzar progresivamente eligiendo las etapas segn su nivel.

Principios de un puntero
1. Qu es un puntero?

a. Memoria RAM

Todos los objetos informticos que hemos explicado, ya sean variables, funciones, tablas, tuplas, etc.,
corresponden a un registro localizado en alguna parte de la memoria principal, denominada tambin
memoria RAM. En pocas palabras, cada objetivo tiene una direccin en la memoria principal. Esta
direccin es un nmero entero y corresponde a una ubicacin de la memoria.

La memoria de 32 bits se construye sobre la base de un entrelazado de filas de 4 bytes consecutivos. A


partir del byte, que es la unidad de memoria direccionable ms pequea (el byte son 8 bits y
corresponde en C al tipo char), la memoria se forma con estas palabras de cuatro bytes, 32 bits, cuya
direccin siempre es un mltiplo de cuatro, generalmente en hexadecimal. A continuacin, se muestra
una representacin de este principio partiendo, por ejemplo, de la direccin 100 con una progresin
de 4 en 4 mostrada en este caso en decimal para simplificar:

images/04ri01.png
En un programa, cuando se declara una variable cualquiera, se le asigna automticamente una direccin
de memoria en la compilacin. Sean por ejemplo tres variables en un programa:

char C=255;
int A=256, B=129;
Supongamos que la direccin de C es 100, la direccin de A podra ser 104 y la direccin de B 116, lo
que resultara en memoria:

images/04ri02.png
La variable B tiene por valor 129 y se encuentra en la direccin 116.

La variable A tiene por valor 256 y se encuentra en la direccin 104.


La variable C tiene por valor 255 y se encuentra en la direccin 100.

Cada nuevo objeto comienza siempre en una nueva de palabra. Solo los objetos de tipo char (1 byte) o
a veces short (2 bytes) pueden tener direcciones de bytes intermedios. Esta es la razn por la que la
direccin de la variable entera A est en la nueva palabra, la siguiente a la direccin de la variable de
tipo char C. Los tres bytes de las direcciones 101, 102 y 103 no se utilizan.

b. Una variable puntero

Un puntero es una variable que toma por valor direcciones de memoria. De este modo, una variable
puntero permite acceder al contenido de la direccin de memoria que contiene. La variable puntero se
codifica en cuatro bytes y, como cualquier otra variable, tiene su propia direccin de memoria.

c. Cuatro operadores

Para utilizar direcciones de memoria, hay dos operaciones bsicas:

Obtener una direccin de memoria.


Acceder a una direccin de memoria.
El operador de referencia: &, direccin de, permite obtener una direccin de memoria (lo que se
utiliza con la funcin scanf()).

El operador de indireccin: * , asterisco, permite acceder a una direccin de memoria. Hay otros dos
operadores para acceder a direcciones de memoria que son realmente abreviaciones de escritura: el
operador -> flecha, que permite acceder a los campos de una tupla a partir de su direccin de
memoria, y el operador [ ] corchete, que permite acceder a los elementos de una tabla a partir de la
direccin del primer elemento.

d. Tres utilizaciones bsicas de los punteros

Los punteros forman parte de una de las herramientas ms potentes del lenguaje C. Hay tres casos de
uso de punteros:

Asignacin dinmica de tablas

Con un puntero se puede reservar un espacio de memoria contiguo de cualquier tamao durante el
funcionamiento del programa y usar este bloque como una tabla. De este modo, el primer uso de los
punteros consiste en poder obtener dinmicamente tablas de cualquier tamao y de cualquier tipo (para
ms detalles, consultar la seccin Asignacin dinmica de tablas).

Punteros como parmetros de funcin

Con un puntero se puede acceder a una variable mediante su direccin de memoria. Por este motivo,
con un puntero como parmetro de una funcin, se puede trasladar a una funcin la direccin de
memoria de una variable y en la funcin acceder a esta variable y modificar su valor a travs de su
direccin de memoria. Es decir, que con un puntero se puede transformar un parmetro de entrada en
un parmetro de entrada/salida (para ms detalles, consultar la seccin Punteros como parmetros de
funcin).
Estructuras de datos compuestas (listas encadenadas, rboles, grafos)

Con los punteros se pueden elaborar estructuras de datos complejas que no existen como tales en el
lenguaje: las listas encadenadas, los rboles y los grafos. Estas grandes figuras de la programacin y la
algortmica se pueden construir dinmicamente en C mediante el uso de punteros. (Hay un captulo
dedicado a las listas.)

2. Declarar un puntero en un programa

Para declarar un puntero, es decir, para tener un puntero en un programa, hay que escribir:

El tipo del objeto al que apuntar.


El operador * (a la izquierda del nombre del puntero).
Un nombre (identificador) para el puntero.
Por ejemplo:

char *c; // declara un puntero a char


int *i, *j; // declara dos punteros a int
float *f1,*f2; // declara dos punteros a float
c es un puntero de tipo char*: puede tener direcciones de char.

i y j son dos punteros de tipo int*: pueden contener direcciones de int.

f1 y f2 son dos punteros de tipo float*: pueden contener direcciones de float.

Con tuplas es idntico:

typedef struct player{ // definicin del tipo player


int x,y;
int dx,dy;
}player;

player *p; // declara un puntero a player


p es un puntero de tipo player* y p puede contener direcciones de tuplas player.

3. Funcionamiento de los cuatro operadores

a. Operador de direccin: &

El operador & colocado a la izquierda de un objeto cualquiera en un programa devuelve su direccin.


Por ejemplo:

int k;
&k // esta expresin vale la direccin en memoria de la
// variable k
Cualquier direccin es un valor de puntero: si k es un objeto de tipo T, entonces &k es de tipo puntero a
T.
Observacin:

Cabe decir que k debe ser lo que se llama un lvalue. Es decir, una expresin que tiene una direccin de
memoria accesible, en general una expresin a la que se le pueda asignar un valor. En efecto, si i es un
int, &i es la direccin de i que apunta i. Pero una expresin como &(i+1) con los parntesis no es
correcta sintcticamente, ya que (i+1) no se corresponde con una variable en memoria y, por tanto, no
es un lvalue. No es posible escribir algo como: (i+1)=77.

El siguiente programa permite mostrar las direcciones de memoria de varias variables utilizando el
operador & (y el formato %p de printf() reservado a valores de puntero, es decir, direcciones de
memoria):

#include <stdio.h>

int main()
{
char c;
int i,j;
struct {
int x,y;
float dx,dy;
}s1;
printf("la direccin de c es: %p\n", &c);
printf("la direccin de i es: %p\n", &i);
printf("la direccin de j es: %p\n", &j);
printf("la direccin de s1 es: %p\n", &s1);
return 0;
}
Las expresiones, &c, &i, &j y &s1 corresponden respectivamente a las direcciones de los objetos c, i, j
y s1. Este programa permite visualizarlas.

b. Operador asterisco: *

El operador * puesto a la izquierda de un puntero que contiene la direccin de memoria de un objeto


devuelve el objeto que se encuentra en esa direccin. Este operador permite acceder al objeto mediante
su direccin y modificarlo. Por ejemplo:

int i;
int* ptr = &i;
Si ptr vale la direccin del entero i, entonces *ptr e i corresponden a la misma ubicacin en memoria:
*ptr e i son lo mismo.

El siguiente programa permite visualizar la adecuacin existente, por el hecho de usar el operador *,
entre un objeto cualquiera y un puntero que contiene su direccin:

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

int main()
{
int i = 23; // 1
int *ptr;

ptr = &i; // 2
printf("%d, ",*ptr);

*ptr = 55; // 2
printf("%d, ",i);

i = 777; // 3
printf("%d.",*ptr);
return 0;
}
El programa imprime por pantalla: 23, 55, 777.

(1) Declaraciones del entero i que vale 23 y del puntero a entero ptr.

(2) La direccin de i, es decir, la expresin &i, se asigna al puntero ptr.

A partir de este momento, ptr contiene la direccin de la variable i. *ptr e i corresponden a la misma
ubicacin de memoria. i y *ptr son idnticos, el mismo objeto. De este modo, mostrar *ptr es mostrar i,
el valor 23.

(3) El valor 55 se asigna a *ptr, es decir, a i e i ahora vale 55.

(4) El valor 777 se asigna a la variable i y el valor de la expresin *ptr tambin es 777.

c. Operador flecha: ->

El operador flecha -> afecta nicamente a las tuplas. La flecha sirve simplemente para facilitar la
escritura, es la contraccin de los operadores asterisco y punto utilizados juntos.

Sea la definicin de un tipo de tupla llamado enemigo:

typedef struct{
int vida;
float x,y,dx,dy;
int color;
} enemigo;
Dos variables de tipo enemigo:

enemigo S1,S2;
Una variable puntero de tipo enemigo*:

enemigo*p;
con

p=&S1;
Si se usa el operador asterisco para acceder a los campos de la estructura S1 a partir del puntero p, se
obtiene:

(*p).vida=1;
(*p).x=rand()%80;
(*p).y=rand()%25;
etc.
Como se ha visto anteriormente, el puntero p contiene la direccin de S1 y la expresin *p es
equivalente a S1. *p puede usarse como S1 con el operador punto para acceder a los campos de la
tupla. Pero atencin: hay que aadir unos parntesis porque el operador punto es prioritario en relacin
con el operador asterisco. En efecto, la expresin *p.vida equivalente a *(p.vida) significara, por un
lado, que p es una tupla y no un puntero y, por otro, que el campo vida es un puntero, lo cual no es el
caso.

Para evitar errores y facilitar el uso de punteros a tupla la expresin p-> es una notacin abreviada de
(*p). Para acceder a los campos de la tupla enemigo, se escribe simplemente:

p->vida=1;
p->x=rand()%80;
p->y=rand()%25;
etc.
d. Operador corchete: [ ]

Una tabla es la direccin de memoria del primer entero:

int tab[50]; // tab vale &tab[0]


Debido a que una tabla es una direccin de memoria, se puede asignar a un puntero:

int *p;
p=tab;
lo que significa que tab y p son equivalentes y se puede usar p en vez de tab:

int i;
for (i=0; i<50; i++)
p[i] = rand()%256;
y podramos escribir tambin:

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


*(p+i) = rand()%256;
La expresin (p+i) vale la direccin de memoria de p, comienzo de tabla, ms un desplazamiento de
i*sizeof(int) bytes en memoria; es la direccin de los elementos siguientes. Para i=0 es la direccin de
la tabla, el elemento 0; para i=1 es la direccin del elemento siguiente que est cuatro bytes ms
adelante; para i=2 es la direccin del elemento que est ocho bytes ms adelante, etc. De este modo,
*(p+i) permite acceder a cada elemento i de la tabla.

Sin embargo, esta segunda forma de escritura se utiliza poco. Generalmente, se usan los operadores
corchetes; p[i] es una contraccin de *(p+i).
Como vemos, se pueden usar operadores aritmticos con los punteros. Pero tenga mucho cuidado: est
expresamente prohibido acceder a posiciones de memoria no reservadas debido al peligro de cuelgues o
de comportamientos inesperados del programa.

e. Prioridad de los cuatro operadores

La flecha y el punto para acceder a los campos de una tupla, el operador corchete para la indexacin de
la tabla y los parntesis para la llamada a la funcin son los de prioridad ms alta. La direccin, que
devuelve la referencia en memoria de un objeto, y el asterisco, que permite acceder a un objeto
mediante su direccin, son justamente del nivel inferior (ver Anexo - Prioridad y asociatividad de
operadores).

4. Asignacin dinmica de memoria

a. La funcin malloc()

Hemos visto casos en los que un puntero recibe como valor la direccin de una variable previamente
declarada. Sin embargo, un puntero tambin permite obtener dinmicamente, durante el
funcionamiento del programa, una direccin de memoria asignada para una variable de cualquier tipo.
Para ello, el lenguaje C nos proporciona una funcin importante:

void* malloc(size_t tam);


Esta funcin asigna un bloque de memoria de tam bytes (el tamao se pasa por parmetro) y devuelve
la direccin de memoria de la zona asignada (o NULL si no hay suficiente memoria). El void* es un
puntero genrico, es decir, que puede funcionar con cualquier tipo de objeto. El funcionamiento del
sistema garantiza que la direccin asignada est correctamente reservada para el objeto solicitado por el
programa y que en ningn caso se usar para otros menesteres. El tamao de una zona de memoria para
un objeto cualquiera de tipo T se obtiene simplemente con el operador sizeof (T). De este modo, si ptr
es un puntero de tipo T*, la llamada a la funcin es:

ptr = malloc (sizeof( T ));


Y se tendr en ptr la direccin de una zona de memoria de tamao suficiente reservada para un objeto
de tipo T. Por ejemplo:

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

int main()
{
int*iptr;
float* tab[10]; // tabla de punteros a float
int i;

iptr=malloc(sizeof(int));
*iptr=45;
printf("*iptr, en la direccin %p vale: %d\n", iptr, *iptr);

srand(time(NULL));
for(i=0; i<10; i++){
tab[i]=malloc(sizeof(float));
*tab[i]= (rand() / (float)RAND_MAX)*5;
printf("*tab[%d] en la direccin %p vale: %.2f\n",
i,tab[i], *tab[i]);
}
return 0;
}
En este programa hay un puntero a entero y una tabla de punteros a float. Al empezar, se asigna una
direccin de memoria para la variable puntero a entero iptr y el valor 45 se asigna en esta direccin de
memoria. Se muestran la direccin obtenida por iptr y el valor asignado a esta direccin. A
continuacin, se asigna una direccin de memoria a cada uno de los punteros de la tabla de punteros. A
cada una de las ubicaciones de memoria correspondientes se le asigna un valor aleatorio en coma
flotante entre 0 y 5. La direccin de cada puntero, as como el valor aleatorio obtenido de cada puntero,
se muestran por pantalla.

b. Liberar la memoria asignada: la funcin free()

La memoria asignada queda reservada, es decir, inutilizable para otro fin en el programa. Por este
motivo es importante liberar la memoria cuando el objeto asignado ya no se usa en el programa, para
dejar esta memoria disponible de nuevo. Esta operacin, inversa a la asignacin, se realiza con una
llamada a la funcin:

void free(void*p);
Por ejemplo, para liberar la memoria asignada al puntero anterior, se debe utilizar la siguiente llamada:

free(ptr);
En C, durante el funcionamiento de un programa, no hay un garbage collector que haga el trabajo de
forma automtica, como en Java o en C#, por ejemplo. El riesgo que se corre es agotar la memoria
RAM disponible y que el programa se quede sin memoria para poder funcionar en un momento dado.
Esto es posible en particular con pequeos objetos en robtica o similares. Por ejemplo, en el siguiente
programa:

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

int main()
{
double*p[500000];
int i;
int fin=0;
do{
printf("\n\n");

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


p[i]=malloc(sizeof(double)) ; // 1

for (i=0; i<500000; i++) // 2


free(p[i]) ;
printf("Jugar de nuevo? (s/n)\n");
fin=getch();

}while(fin!=n);
return 0;
}
En cada iteracin de bucle se asignan 500000 direcciones de memoria (1) para un total de 4 millones de
bytes (500000*8 bytes). Si la memoria no se libera, cada iteracin ocupa an ms memoria, y as hasta
que el programa se bloquea. En cambio, si la memoria asignada se libera (2), el programa puede
funcionar indefinidamente.

Digamos que es una cuestin del da a da del programador de C: cuando el programa finaliza, debe
poner especial atencin en que ese programa deje la memoria en el estado en que la encontr
inicialmente. En la salida del programa, por norma, hay que desasignar toda la memoria previamente
asignada.

c. El puntero genrico void*

La funcin malloc(), explicada anteriormente, devuelve un puntero de tipo void*.

El puntero void* es un puntero de tipo indefinido, su uso principal consiste en utilizarlo como un
puntero llamado genrico, es decir, un puntero independiente de cualquier tipo en particular. Es solo
una direccin de memoria para un espacio contiguo de memoria de cualquier tamao. Con tal puntero
se puede manipular una direccin de memoria de un objeto cuyo tipo se ignora.

Es el caso concreto de la funcin malloc(), que se usa para cualquier tipo de objeto: int*, char*, float*,
double*, as como para direcciones de memoria de estructuras de datos creadas por el programador. La
funcin debe poder asignar memoria para cualquier tipo de objeto. Es por ello por lo que la funcin
malloc() devuelve un puntero genrico, es decir, la direccin de una zona de memoria contigua del
tamao en bytes pasados por parmetro a la funcin.

No se puede usar void* con el operador * sin cast en un programa

Todas las operaciones con el operador * (asterisco) aplicado a un puntero genrico void* necesitan un
cast para poderse realizar; por ejemplo:

int toto=55;
void* ptr = &toto;
printf("toto=%d\n",*((int*)ptr)); // esta instruccin funciona con el cast
printf( "toto=%d\n", *ptr); // si no hay cast, se produce un error en
// la compilacin.
d. El valor NULL

NULL se define en <stddef.h>. Es un valor especial de puntero que, de hecho, vale 0. C garantiza que 0
no ser nunca una direccin usable para un objeto cualquiera. Y 0, que no es por lo tanto una direccin
de memoria, es el nico caso en el que un entero puede asignarse a un puntero. Se trata de hecho de una
conversin del tipo de la forma ( (void*) 0).

Por ello, es frecuente encontrar comparaciones de un puntero con el valor NULL. Por ejemplo, si la
funcin malloc() no puede asignar suficiente memoria, devuelve NULL y se puede garantizar que el
programa seguir funcionando incluso si la memoria es insuficiente, con una comprobacin del valor
de retorno:

ptr=(int*)malloc(sizeof(int));
if (ptr!=NULL){
printf("malloc ha tenido xito, el puntero se puede usar\n");
*ptr=rand()%300;
}
else
printf("memoria insuficiente,"
"el puntero no se puede usar\n");
Por otro lado, con mucha frecuencia es necesario inicializar los punteros a NULL en su declaracin en
un programa para evitar acceder por descuido a zonas de memoria no reservadas (los valores residuales
albergados en un puntero sin inicializar), lo que cuelga el programa.

5. Atencin a la validez de una direccin de memoria

a. Validez de una direccin de memoria

La asignacin dinmica requiere mucha atencin por parte del programador. Es l quien se encarga de
la asignacin y de la liberacin de la memoria.

En efecto, cuando una tabla esttica o cualquier tipo de variable se declara en un programa, el espacio
de memoria requerido se reserva automticamente por el compilador en el momento de la compilacin.
Por este motivo, se puede escribir en este espacio y utilizar la tabla o la variable. En cambio, esto no
sucede con un puntero.

Cuando un puntero se declara en el programa, no contiene la direccin de memoria de un bloque


reservado. Lo que realmente contiene es cualquier valor, una direccin aleatoria, lo que haba en
memoria en ese instante y, si se intenta escribir en una direccin de memoria no reservada, el programa
se cuelga o se aborta la ejecucin repentinamente con un mensaje de error. Por ejemplo:

#include <stdio.h>

int main()
{
char*ptr;
char tab[80];
printf("entre una frase:\n");
fgets(tab,80,stdin);
printf("tab: %s\n",tab);

printf("entre otra frase:\n");


fgets(ptr,80,stdin); // ERROR!! el programa sale
printf("ptr: %s\n",ptr);
return 0;
}
El puntero ptr no contiene una direccin vlida, no se le ha hecho una asignacin, la direccin que
contiene no es accesible en escritura. Intentar acceder a l provoca un error de ejecucin. La dificultad
de este tipo de problemas es que el cdigo compila a la perfeccin. Este tipo de errores no son
detectados por el compilador.

En cambio, si por ejemplo asignamos a ptr la direccin de la tabla tab, funciona:

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

int main()
{
char*ptr;
char tab[80];
printf("entre una frase:\n");
fgets(tab,80,stdin);
printf("tab : %s\n",tab);

ptr=tab;
printf("entre otra frase:\n");
fgets(ptr,80,stdin); // ok
printf("ptr: %s",ptr); //
printf("tab: %s\n",tab); // ptr y tab son idnticos
return 0;
}
ptr toma la direccin de tab, que est debidamente reservada por la mquina para la tabla tab. Entonces
ptr y tab designan el mismo espacio de memoria; escribir a partir de ptr es escribir a partir de tab. Es la
misma direccin de memoria, la misma ubicacin en la memoria. Por lo tanto, atencin, porque en este
caso ptr y tab son dos accesos para el mismo bloque de memoria, y no dos bloques distintos.

Otra solucin consiste en asignar dinmicamente un segundo bloque de memoria y copiarle el


contenido del bloque tab (ver seccin Asignacin dinmica de tablas):

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

int main()
{
char*ptr;
char tab[80];
int i;
printf("entre una frase:\n");
fgets(tab,80,stdin);

// asignacin de un nuevo bloque


ptr=(char*)malloc(sizeof(char)*strlen(tab)+1);

// copia de tab
strcpy(ptr,tab);

// modificacin de la copia
for(i=0; i<strlen(ptr); i++)
ptr[i]++;

// ptr y tab son distintos


printf("ptr: %s\n",ptr);
printf("tab: %s\n",tab);
return 0;
}
Esta vez, ptr y tab son dos bloques distintos y cada uno de ellos corresponde a una direccin de
memoria vlida.

b. Por qu hay que realizar casting con el retorno de las funciones de asignacin?

El retorno de la funcin malloc(), por ejemplo, puede ser asignado a cualquier tipo de puntero sin
ningn control. Por ejemplo:

char c;
double *d;

d=&c;
provoca un aviso warning en la compilacin: asignacin de puntero incompatible. &c es de tipo
char* y d es de tipo double*. Es mejor tener cuidado, porque el puntero d adquiere la direccin de un
char, es decir, el espacio de memoria asignado de un solo byte. Si escribimos fuera del espacio
reservado, el programa se cuelga:

*d=512; // provoca el cuelgue del programa.


En cambio, si escribimos:

double *d;
d=malloc(sizeof(char)); // atencin, error invisible
La situacin es idntica. El espacio de memoria reservado es de un solo byte cuando un double
realmente espera 8 bytes. Esto queda inadvertido, ya que no se muestra ningn mensaje de error ni de
aviso por parte del compilador; pasa discretamente y puede convertirse en un bug dificilsimo de
encontrar que puede provocar el cuelgue del programa de vez en cuando.

As, para beneficiarse de un mensaje de aviso sobre la compatibilidad del retorno void* de funcin
malloc() con el puntero destino, es mejor realizar siempre el casting del void* al tipo deseado. Este cast
se realiza colocando entre parntesis el tipo de puntero deseado a la izquierda de la funcin. De este
modo, si en nuestro ejemplo escribimos:

double *d;
// retorno de malloc transformndolo a char*:
d=(char*)malloc(sizeof(char)); // entonces el error queda
// visible en la compilacin
Se produce un mensaje de advertencia warning por el hecho de la incompatibilidad entre el puntero d
double* y la asignacin para un char*. Por otro lado, esta prctica obliga a estar atento a la llamada de
malloc() realizada: el tamao solicitado debe estar acorde con el tipo de dato devuelto.

6. Caso de las tablas de punteros

a. Una estructura de datos muy til

La tabla de punteros no permite almacenar objetos, sino direcciones de memoria de objetos. Por
ejemplo, sea la tupla trol:

typedef struct trol{


int x,y;
int color;
}t_trol;
Podemos definir una tabla esttica de punteros, que es una tabla normal que contiene punteros, en este
caso de tipo t_trol*.

#define NUMMAX 100

t_trol* tab[NUMMAX]; // una tabla de punteros


Para utilizar una tabla de punteros, hay que velar por que cada puntero contenga una direccin de
memoria vlida:

for (i=0; i<NUMMAX; i++){

tab[i]=(t_trol*)malloc(sizeof(t_trol)); // asignacin de memoria

// Cada elemento es un puntero de la tupla:


tab[i]->x=rand()%800;
tab[i]->y=rand()%600;
tab[i]->color=rand()%256;
}
Por otro lado, es una tabla esttica y se comporta como tal en parmetros de funcin:

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

void inicializacion(t_trol* t[])


{
int i;
for (i=0; i<NUMMAX; i++){

t[i]=(t_trol*)malloc(sizeof(t_trol));
t[i]->x=rand()%800;
t[i]->y=rand()%600;
t[i]->color=rand()%256;
}
}
void mostrar(t_trol*t[])
{
int i;
for (i =0; i<NUMMAX; i++){
printf("%4d %4d %4d\n",t[i]->x,t[i]->y,t[i]->color);
}
}
int main()
{
t_trol* ALL[NUMMAX];

inicializacion(ALL);
mostrar(ALL);
return 0;
}
Utilizar direcciones de memoria de objetos, ms que los objetos directamente, es bueno sobre todo para
los parmetros de las funciones asociadas a los tratamientos de estos objetos. El hecho de poder pasar
la direccin de memoria por parmetro a la funcin transforma la entrada en salida: se puede escribir en
la direccin pasada (ver seccin Punteros como parmetro de funciones - Paso por referencia).

b. Una tabla de cadenas de caracteres

Las cadenas de caracteres son tablas de caracteres y terminan con el carcter \0. A menudo se usan los
punteros char* para representarlas y, por lo tanto, contienen la direccin del comienzo de la tabla.

Lo interesante es entonces poder formar conjuntos de cadenas de caracteres ms fcilmente. Una lista
de palabras se puede convertir en una tabla de punteros de caracteres. Por ejemplo:

char* lista_pal[]={"titi",
"toto practica bicicleta",
"tutu",
"tata goes to the sea",
"fin" };
Con una matriz de char:

char mat_pal[5][30]={"titi",
"toto practica bicicleta",
"tutu",
"tata goes to the sea",
"fin" };
No es lo mismo en memoria. La matriz de char es menos flexible. En el caso de la tabla de punteros a
carcter, el tamao de memoria no es exactamente el del nmero de caracteres. En el de la matriz de
caracteres, que es una tabla de tablas de caracteres, se tiene un bloque de memoria de 5*100 bytes
reservado sea cual sea el tamao de las palabras, y las palabras no pueden exceder los 100 caracteres.

Cuando el tamao de la tabla de char* no se hace explcito, por ejemplo en el caso de una lista muy
larga de palabras o frases, el problema es saber el final de la tabla. Para lista_pal y mat_pal, la palabra
"fin" indica que se trata de la ltima palabra de la tabla y ambas pueden recorrerse de la siguiente
forma:

int i;
for (i=0; strcmp("fin", lista_pal[i]) != 0 ; i++)
printf("%s\n",lista_pal[i]);
La funcin strcmp() compara dos cadenas de caracteres y devuelve un valor negativo, cero o positivo
segn si la primera es menor, igual o superior respectivamente en orden lexicogrfico.

En nuestro ejemplo, el bucle continuar hasta llegar al ndice de la cadena "fin" (el ndice i es igual a
4). La funcin strcmp() devolver entonces un 0, la prueba se evala como falsa y el bucle se detiene.

Otro mtodo consiste en utilizar la direccin 0 de memoria, el valor NULL. La siguiente forma permite
obtener un centinela a NULL en una tabla de char*:

((char*)0)
El valor 0, de tipo int, con casting a char* se acepta en una tabla de char* como una cadena de
caracteres. La lista de palabras se puede escribir de la siguiente forma:

char* lista_pal[ ] ={"titi",


"toto practica bicicleta",
"tutu",
"tata goes to the sea",
((char*)0) };
y la comprobacin del bucle pasa a ser:

int i;
for (i=0; lista_pal[i] != NULL ; i++)
printf("%s\n",lista_pal[ i ]);
c. Utilizar los argumentos de lnea de comandos

Se pueden pasar argumentos a los parmetros del main en el momento de la ejecucin de la aplicacin
mediante una ventana de consola (intrprete de comandos). Los parmetros del main(), cuando puede
haber argumentos, son:

int main(int argc, char* argv[])


{
(...)
return 0 ;
}
Toda la informacin se transmite mediante la tabla de cadena de caracteres argv y el entero argc
proporciona el nmero de cadenas transmitidas. Siempre hay al menos una cadena en el ndice 0, es el
nombre del programa, y argc siempre vale como mnimo 1.

Ahora ya podemos construir aplicaciones que reciben parmetros en su inicio. Por ejemplo:

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

int main(int argc, char*argv[])


{
int i;
printf("nombre del programa: %s\n",argv[0]);
if (argc>1)
for (i=1;i<argc; i++ )
printf("param %d: %s\n",i,argv[i]);

// continuacin del programa que tendr o no


// en cuenta los argumentos recibidos.
return 0;
}
Este programa muestra su nombre y, adems, si se le han pasado argumentos, los muestra.

En Windows, se puede acceder al intrprete de comandos (una ventana de consola) yendo al men
Inicio, en la carpeta Accesorios. Para iniciar el ejecutable a partir del intrprete, hay que acceder al
ejecutable tecleando su ruta exacta desde la raz del disco. El comando cd permite desplazarse dentro
de la estructura de directorios: cd .. (dos puntos) se usa para subir un nivel y cd carpeta1 para descender
a la carpeta1. Lo mejor, para comenzar, es copiar su ejecutable a la raz de C, a continuacin subir hasta
la raz (cd .. hasta la raz) y llamar a su programa (en este caso, el programa se llama test.exe):

C:\> test.exe
Si se pulsa [Enter], y el programa est en el sitio adecuado, el programa se iniciar. Como no hay
argumentos, termina mostrando nicamente su nombre.

Los argumentos se colocan despus de la llamada al programa, por ejemplo:

C:\> test.exe tata usa la bicicleta


muestra:

nombre del programa: test.exe


param1: tata
param2: usa
param3: la
param4: bicicleta
Se realiza un desacople con los espacios. Para evitarlo, basta con agrupar los conjuntos de palabras con
comillas dobles:

C:\> test.exe "tata usa" "la bicicleta"


muestra:

nombre del programa: test.exe


param1: tata usa
param2: la bicicleta
Por supuesto, estas cadenas pueden contener nmeros y convertirse a continuacin en el programa
(utilizando las funciones estndar adecuadas atoi(), atof(), etc.).

El argumento puede ser un nombre de archivo que contenga mucha informacin para el funcionamiento
del programa, etc.

7. Experimento: conocimientos bsicos de punteros

/***************************************************************
Qu es un puntero?
Una variable que adquiere nicamente direcciones de memoria como
valor.

Para qu sirven los punteros?


Tres usos:
1) como parmetro de funcin: permite el paso de la direccin de
memoria de una variable y transformar la entrada en salida
(posibilidad de escribir en una direccin de memoria)

2) asignacin dinmica de objetos o de tablas de objeto

3) crear estructuras de datos dinmicas no nativas en C:


listas encadenadas, rboles, grafos

Cmo se usan?
Hay cuatro operadores asociados y tres funciones de asignacin
de memoria (para la asignacin dinmica)

Los cuatro operadores son:


1) "direccin de": & permite obtener la direccin de memoria de
una variable

2) "asterisco": * permite declarar un puntero y acceder a una


direccin de memoria

EJEMPLO:
int a;
int *ptr; // declaracin de un puntero a entero
ptr = &a; // ptr toma por valor la direccin de a
*/

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

int main()
{
int a;
int*ptr=&a;

a= 100;
*ptr=200;
printf("a : %d / *ptr : %d\n",a,*ptr);

return 0;
}

/***************************************************************
3) "flecha": -> permite acceder a un campo de una tupla mediante
un puntero

EJEMPLO:
struct test{
int x,y;
} t;

struct test *ptr;


ptr=&t;
ptr->x=450;
ptr->y=90;

equivale a: (*ptr).x=450 y (*ptr).y=90


la flecha es justo una contraccin, un elemento para facilitar la escritura
*/
/*
#include <stdio.h>
#include <stdlib.h>

int main()
{
struct test{
int x,y;
} t;
struct test *ptr;

ptr=&t;
ptr->x=450;
ptr->y=90;
printf("t.x=%d, t.y=%d\n", t.x, t.y);

return 0;
}
*/
/***************************************************************
4) "corchetes": [] es el operador de tabla, desde una
direccin de partida permite acceder a las direcciones de
distintos elementos de la tabla

EJEMPLO:

int tab[50];
int*ptr;
ptr=tab; // ptr toma la direccin de la tabla por valor
// que es la direccin del primer elemento de esta

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


ptr[i] = rand()%256;

// equivalente a:
for (i=0; i<50; i++)
*(ptr+i) = rand()%256;

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

int main()
{
int tab[10];
int*ptr;
int i;

ptr=tab;
for (i=0; i<10; i++){
ptr[i] = rand()%256;
printf("ptr[%d] = %d\n",i,ptr[i]);
}

// equivalente a:
for (i=0; i<10; i++){
*(ptr+i) = ptr[i]+1;
printf("*(ptr+%d) = %d\n",i,*(ptr+i));
}
return 0;
}
*/
8. Puesta en prctica: conocimientos bsicos de punteros

a. Declarar punteros y operar con ellos

Ejercicio 1

En un programa, declarar un entero, un double y un float. Asignarles valores aleatorios entre 600 y 700
con un valor decimal. Mostrar los valores. Modificar los valores de cada variable mediante su direccin
de memoria obtenida en un puntero y mostrar el nuevo resultado. Al finalizar, el programa pregunta al
usuario si hay que volver a empezar o salir.

Ejercicio 2

En un programa, declarar dos variables, asignar a cada una un valor e intercambiar los valores sin tocar
directamente las variables, sino pasando sus direcciones de memoria obtenidas en punteros. Mostrarlas
antes y despus de la modificacin. El programa finaliza si el usuario lo solicita.

Ejercicio 3
Sea una tupla persona que incluye datos tales como: nombre, apellidos, direccin, edad, fecha de
nacimiento, nacionalidad, trabajo y hobby. En un programa:

Definir el tipo.
Inicializar una tupla persona nicamente accediendo a ella a travs de su direccin de memoria.
Mostrar el resultado.
Volver a empezar o salir.
Modificar el programa para tener una tabla de num tuplas persona. Escribir una funcin de
inicializacin e inicializar la tabla (si puede ser con valores aleatorios, mejor). El usuario puede
modificar el elemento que desee siempre pasando la direccin mediante un puntero.

Ejercicio 4

En un programa, sea una tabla de enteros inicializados a 0:

El usuario entra un nmero de modificaciones.

Las modificaciones se ejecutan a continuacin en elementos seleccionados al azar, nicamente si el


valor del elemento es inferior a 10. Cada modificacin utiliza la direccin de memoria del elemento
seleccionado mediante un puntero a entero. Salir o volver a empezar.

b. Pruebas con tablas/punteros

Ejercicio 5

En un programa, inicializar una tabla esttica de 5 enteros con valores comprendidos entre 0 y 255 (en
la declaracin). A continuacin, obtener la direccin de la tabla con un puntero char* y mostrar la tabla
con el puntero de char. Qu sucede? Cuntos valores cree que pueden visualizarse? Qu bucle
permite recorrer todo el espacio de memoria de la tabla?

Ejercicio 6

Cul es el resultado de este programa?

#include <stdio.h>

int main()
{
int t[3];
int i, j;
int* ptr;

for (i=0; j=0; i<3; i++) // 1


t[i]=j++ +i;

for (i=0; i<3; i++) // 2


printf("%d ", t[i]);
putchar(\n);
for (i=0; i<3; i++) // 3
printf("%d ", *(t+i));
putchar(\n);

for (ptr=t; ptr<t+3; ptr++) // 4


printf("%d ", *ptr);
putchar(\n);

for (ptr=t+2; ptr>=t; ptr--) // 5


printf("%d ", *ptr);
putchar(\n);

return 0;
}
Ejercicio 7

En un programa se declara una tabla de 15 enteros pero sin utilizar un puntero. Escribir de dos formas
distintas, una con el operador corchete y otra con el operador asterisco *:

La inicializacin de la tabla con 12 valores aleatorios y 3 entrados por el usuario.


La visualizacin de la tabla.
La bsqueda del ms grande.
La bsqueda del ms pequeo.
La visualizacin de los resultados.
Ejercicio 8

En un programa se declara una tabla de 10 enteros.

Escribir una funcin de inicializacin de la tabla que la recorra con un puntero.


Escribir una funcin de visualizacin de la tabla que la recorra con un puntero.
Salir o volver a empezar.
Ejercicio 9

En un programa se declara una matriz de 20 * 15 enteros.

Escribir una funcin de inicializacin de la matriz en la que la matriz se recorra con un puntero. Los
valores sern aleatorios, excepto por una entrada del usuario cada 100 valores.
Escribir una funcin de visualizacin paralelamente recorrida por un puntero.
Salir o volver a empezar.
Ejercicio 10

En un programa:

Introducir una cadena de caracteres.


Escribir una funcin de visualizacin de la cadena recorrindola en sentido inverso con un puntero.
Salir o volver a empezar.
c. Conocimientos bsicos de asignacin dinmica
Ejercicio 11

En un programa:

Asignar memoria para un char, un entero y un float.


Inicializar con valores introducidos por el usuario.
Visualizar.
Salir si el usuario lo solicita (si no, volver a empezar, cuidado con la memoria!).
Ejercicio 12

En un programa:

Escribir una funcin de asignacin de memoria con control de errores para un entero.
Inicializar una tabla de 10 punteros a entero.
Asignar valores decrecientes a los enteros.
Mostrar los valores.
Salir si el usuario lo solicita (si no, volver a empezar, cuidado con la memoria!).
Ejercicio 13

En un programa, una entidad se define con un nombre, un carcter, un cdigo gentico almacenado en
una tabla de 4 enteros (4 secuencias), una posicin, un desplazamiento, un color y posiblemente otras
caractersticas:

Definir el tipo para la entidad.


Declarar dos punteros para dos entidades e inicializar con valores cada una de sus caractersticas.
Comparar las caractersticas de ambas entidades y mostrar el resultado.
Salir si el usuario lo solicita (si no, volver a empezar, cuidado con la memoria!).
Ejercicio 14

En un programa, retomar el ejemplo del tipo definido para una entidad del ejercicio 13 y:

Declarar dos punteros a entidad.


Escribir una funcin de inicializacin e inicializar cada entidad.
Escribir una funcin de visualizacin y mostrar cada entidad.
Escribir una funcin que visualice el nombre de la entidad con la caracterstica ms fuerte.
Ejercicio 15

En un programa, retomar el ejemplo del tipo definido para una entidad del ejercicio 13 y:

Declarar una tabla de punteros para NUM_MAX entidades.


Escribir una funcin de inicializacin e inicializar la tabla.
Escribir una funcin de visualizacin y visualizar la tabla.
Escribir una funcin que muestre el nombre de la entidad con la caracterstica ms fuerte.
d. Ojo con los errores

Ejercicio 16

Qu hace el programa siguiente? Hay errores? En caso afirmativo, cules? Cmo se pueden
corregir?
int main()
{
char*s1;
char s2[80];
int i;

fgets(s2,100,stdin);
strcpy(s1, s2);

s1=s2;
fgets(s2,100,stdin);
if (strcmp(s1,s2)==0)
printf("ambas frases son idnticas\n");
else
printf("son distintas\n");

s1="hola";
printf("s1: %s\n",s1);
strcpy(s2,"hace buen da, cojo mi bastn y mi sombrero con alegra");
printf("s2: %s\n",s2);

while(s2[i]){
s1[i]=s2[i];
i++;
}
printf("s1: %s\n",s1);
printf("s2: %s\n",s2);
return 0;
}
e. Tablas de cadenas

Ejercicio 17

Crear un juego de 52 cartas a partir de una lista de palabras. Al inicio, mostrar el juego ordenado por
colores. A continuacin, escribir una funcin de mezcla de cartas, otra de distribucin entre dos
jugadores y mostrar las cartas de ambos.

Ejercicio 18

Sea una lista de palabras:


[INCOMPLETO]
Asignacin dinmica de tablas

1. Asignar dinmicamente una tabla con un puntero

A continuacin se muestra una asignacin dinmica para un entero:

int *ptr;
ptr=(int*)malloc(sizeof(int) );
lo que es idntico a:

ptr=(int*)malloc(sizeof(int) * 1);
Si ptr contiene la direccin del primer elemento de la tabla de 1 elemento, se puede escribir
indiferentemente:

*ptr=10;
// o
ptr[0]=10;
Ambas notaciones son equivalentes.

Y para tener ms de un elemento en la tabla, basta con asignar ms memoria, proporcionalmente al


nmero de elementos. Por ejemplo, para tener una tabla de 16 enteros:

int *ptr
ptr=(int*)malloc(sizeof(int)*16);
El recorrido de la tabla es el habitual:

int i,
for (i=0; i<16; i++){
ptr[i]=rand()%100; // inicializacin
printf("ptr[%d]=%d",i, ptr[i]); // visualizacin
}
Tambin es posible obtener el tamao de la tabla de forma aleatoria:

int*ptr, tam;
tam=rand();
ptr=(int*)malloc(sizeof(int)*tam);
Si se teme que haya un desbordamiento de la memoria RAM en el momento de la asignacin, se puede
controlar el retorno de la funcin malloc(), que devuelve NULL si no se ha podido realizar la operacin
solicitada. Por ejemplo:

ptr=(int*)malloc(sizeof(int)*tam;
if ( ptr==NULL)
exit(EXIT_FAILURE);
En caso de error, el programa finaliza con EXIT_FAILURE como valor de retorno (EXIT_FAILURE
vale 1 e indica al sistema operativo que un problema se ha producido en la ejecucin del programa).

2. Asignar una matriz con un puntero de punteros

Qu es una matriz? Una matriz es una tabla de tablas de objetos de un tipo determinado. Por ejemplo,
una matriz de enteros:

int mat[4][7]
mat es una tabla de 4 tablas de 7 enteros. Debido a la equivalencia tabla-puntero, la matriz puede ser
reemplazada por un puntero de tablas, es decir, por un puntero de punteros, que se escribe de la
siguiente manera:

int**mat;
El primer nivel de puntero sirve para asignar una tabla de punteros. Es la tabla de las filas y tiene el
tamao del nmero de filas deseado. Por ejemplo, si se pretende crear una matriz de enteros de 4 filas
por 7 columnas, a continuacin se muestra cmo crear una tabla de punteros para hacer 4 filas:

// 1) una tabla de punteros

int**mat;
mat=(int**)malloc(sizeof(int*)*4);
Representada grficamente:

images/04ri03.png
Esta llamada de la funcin malloc() asigna un espacio de memoria para 4 punteros de enteros. Es una
tabla de 4 punteros de enteros. Ahora solo nos falta asignar la tabla correspondiente a cada fila segn el
nmero de columnas deseado. Si hay 7 columnas, se debe escribir:

// 2) cada puntero de la tabla se asigna en una tabla de int

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


mat[i]=(int*)malloc(sizeof(int)*7);
Representada grficamente:

images/04ri04.png

Es posible tener filas de distintos tamaos, pero en este caso hay que conservar el tamao de cada fila
en otra parte o tener un centinela en cada fila para que indique el final (por ejemplo, el carcter \0 de
las cadenas de caracteres).

El siguiente programa crea una matriz de tamao aleatorio y la inicializa con los valores 0 o 1:

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

int main(int argc, char *argv[])


{
int**mat; // 1
int tx,ty,i,j,fin=0;

srand(time(NULL));
do{

tx=1+rand()%30; // 2
ty=1+rand()%10;
mat=(int**)malloc(sizeof(int*)*ty);
for (i=0; i<ty; i++){
mat[i]=(int*)malloc(sizeof(int)*tx);

for (j=0; j<tx; j++){


mat[i][j]=rand()%2;
printf("%2d",mat[i][j]);
}
putchar(\n);
}

for (i=0; i<ty; i++) // 3


free(mat[i]);
free(mat);

printf("\nOtra matriz? (s/n)\n\n");


fin=getch();
}while(fin==o);

return 0;
}
(1) Declaracin de variables del programa. "mat" para la matriz, que es un puntero a punteros a entero,
y ty y tx para su nmero de filas y columnas.

(2) Inicializacin aleatoria de las variables tx y ty en los rangos respectivos de 1 a 30 y 1 a 10 y, a


continuacin:

Asignacin dinmica de la tabla de punteros de ty filas.


Asignacin dinmica de cada fila de la tabla con tx columnas.
(3) Destruccin de la matriz; el programa no usar ms esta matriz. El objetivo es solamente comprobar
la creacin dinmica de una matriz y la memoria que acaba de ser asignada se libera justo despus de la
creacin y la visualizacin de la matriz.

Tambin podramos mencionar la creacin dinmica de tablas de tres dimensiones, o n dimensiones. El


principio de asignacin sera exactamente el mismo. Por ejemplo, a continuacin se muestra un cubo de
10 matrices de 20 filas por 30 columnas de enteros:

int ***tab;
tab=(int***)malloc(sizeof(int**)*10);
for (z=0; z<10;z++){
tab[z]=(int**)malloc(sizeof(int*)*20);
for (y=0; y<20; y++)
tab[z][y]=(int*)malloc(sizeof(int)*30);
}
Sin embargo, no es muy frecuente. En general, son sobre todo las tablas de una o dos dimensiones las
que reciben una asignacin dinmica.

3. Diferencias entre tablas estticas y tablas dinmicas


El puntero es una variable que puede recibir diferentes direcciones correspondientes a diferentes
bloques de memoria. Una tabla esttica es constante. El tamao del bloque correspondiente y su
direccin se asignan automticamente y no pueden modificarse nunca.

Adems, el operador sizeof() aplicado a un puntero devuelve el tamao en bytes de la variable puntero
(4 bytes), mientras que si se aplica a una tabla esttica, devuelve el tamao en bytes de la tabla. Si es a
una tabla, el nmero de elementos puede hallarse de la siguiente manera:

int num_elementos = sizeof(a) / sizeof(a[0]);


La tabla (esttica) es un tipo en s. La conversin de una tabla a un puntero que apunta al primer
elemento de la tabla no tiene lugar cuando una expresin de tipo tabla es el operando de un operador
unario como sizeof. Es por este motivo por lo que sizeof(a) devuelve el tamao de a, y no un puntero al
primer elemento de a.

4. Otras funciones de asignacin de memoria dinmica

Aparte de la funcin malloc() anteriormente presentada, la librera estndar de C <stdlib.h>


proporciona dos funciones ms para asignar la memoria dinmicamente: son las funciones calloc() y
realloc().

a. Funcin calloc()

void * calloc(size_t num_elementos, size_t tam_elementos);


La funcin calloc() asigna un espacio de memoria del nmero de elementos multiplicado por el tamao
de un elemento: num_elementos*tam_elementos. El espacio asignado se inicializa con todos los bits a
cero y la funcin devuelve una direccin genrica void*. Ejemplo de uso:

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

int main()
{
int num,i;
int*tab;
printf("entrar el tamao de la tabla:\n"); // 1
scanf("%d",&num);
tab=(int*)calloc(num,sizeof(int)); // 2
for (i=0; i<num; i++){ // 3
tab[i]=rand()%256;
printf("tab[%d]=%d\n",i,tab[i]);
}
return 0;
}
(1) Se solicita al usuario que introduzca el tamao de la tabla obteniendo el valor con la funcin
scanf().

(2) Asignacin dinmica de la tabla segn el valor entrado.


(3) La tabla se inicializa a continuacin con valores aleatorios comprendidos entre 0 y 255. Se
muestran todos los valores.

b. Funcin realloc()

void* realloc(void*ptr, size_t nuevo_tam);


La funcin realloc() modifica el tamao del espacio de memoria apuntado por ptr. El puntero ptr debe
ser NULL, o bien debe contener la direccin de un espacio de memoria previamente asignado. El
parmetro nuevo_tam es el nuevo tamao del espacio de memoria. Si es ms pequeo, los datos de la
parte suprimida se eliminan y los de la parte restante se conservan. Si es ms grande, todos los datos
del espacio inicial de memoria se conservan y la parte aadida no se inicializa (contenido
indeterminado). Cuando la direccin inicial es NULL, se asigna un espacio de tamao nuevo_tam. La
funcin devuelve un puntero genrico; hay que hacer un casting en funcin del tipo deseado. Ejemplo
de uso:

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

int main()
{
int num,i,k;
int *tab=NULL; // 1

for (i=0; i<10; i++){ // 2


printf("entre un nuevo tamao de tabla:\n");
scanf("%d",&num);
tab=(int*)realloc(tab,sizeof(int)*num);
for (k=0; k<num; k++){ // 3
if(tab[k]!=k)
tab[k]=k;
printf("%d ",tab[k]);
}
}
return 0;
}
(1) Declaracin de variables, el puntero tab se pone a NULL.

(2) El programa reasigna diez veces el espacio de memoria apuntado por tab. Cada vez se solicita al
usuario que entre un nuevo tamao, se obtiene el valor introducido mediante la funcin scanf() y se
reasigna el espacio con realloc().

(3) Una vez el espacio de memoria ha sido reasignado, se inicializa. Cada elemento de la tabla toma por
valor su ndice correspondiente. La inicializacin de los elementos solo se produce para los elementos
del espacio de memoria aadido, cuyos valores sern probablemente distintos a los ndices de la tabla.

5. Puesta en prctica: asignacin dinmica de memoria

a. Asignar dinmicamente tablas


Ejercicio 1

Escribir un programa en el que:

El usuario entra el tamao para una tabla de enteros.


Una funcin asigna dinmicamente memoria para la tabla.
Una funcin inicializa la tabla con nmeros aleatorios crecientes.
Una funcin muestra la tabla.
Se permita salir o volver a comenzar (si se vuelve a comenzar hay que liberar la memoria).
Ejercicio 2

Considere la tupla t_tabla que contiene la direccin de una tabla y el nmero de elementos de la tabla:

typedef struct{
int num_elem; // nmero de elementos
int*tab; // tabla potencial
}t_tabla;
1) Escribir la funcin:

t_tabla asigna_mem_tabla (int n);


que crea una tabla de n elementos.

2) Escribir la funcin:

void destruye_tabla (t_tabla tab);


que libera la memoria ocupada por una tabla.

3) Escribir la funcin:

void inicializa_tabla (t_tabla tab);


que inicializa una tabla con valores.

4) Escribir la funcin:

void muestra_tabla (t_tabla tab);


que muestra el contenido de una tabla.

5) Escribir la funcin:

t_tabla copia_tabla (t_tabla tab);


que crea una nueva tabla de igual tamao que tab, pero inicializada con la copia de los valores de tab.

Escribir un programa con un men que permita invocar estas distintas operaciones.

Ejercicio 3

En un programa:

Escribir una funcin de entrada de una cadena de caracteres; la funcin no tiene parmetros.
Escribir una funcin de concatenacin de dos cadenas de caracteres; la funcin tiene dos parmetros
que son las cadenas que se concatenarn y devuelve una tercera cadena que es la concatenacin.
Introducir dos cadenas y mostrar la concatenacin.
Salir o volver a empezar (ojo con la memoria).
Ejercicio 4 (requiere conocer el uso de archivos)

Se propone realizar una funcin de carga de un archivo de texto en la memoria principal. El formato del
archivo es el siguiente:

- la primera fila tiene el nmero de elementos en el archivo,


- las siguientes filas contienen cada una de ellas un nmero real.
Por ejemplo:
3
4.98
123.76
45.99
Escribir la funcin de carga que reciba por parmetro una tabla del tamao exacto.
Escribir una funcin de visualizacin de la tabla.
Comprobar en un programa. Iniciar varias veces el programa modificando cada vez el contenido del
archivo de texto.
Ejercicio 5

En un programa:

Introducir una frase.


Pasar esta frase a una funcin que devuelva una de las palabras de la frase al azar.
Mostrar la frase y la palabra seleccionada.
Salir o volver a empezar.
b. Asignar memoria dinmicamente a matrices

Ejercicio 6

En un programa:

El usuario introduce las dos dimensiones de una matriz de enteros.


Una funcin asigna la memoria dinmicamente a la matriz.
Una funcin inicializa la matriz con nmeros aleatorios crecientes.
Una funcin muestra la matriz.
Salir o volver a comenzar (si se vuelve a comenzar, liberar la memoria).
Ejercicio 7

Considerar la tupla t_matriz siguiente, que contiene la direccin de una matriz, el nmero de filas y el
nmero de columnas:

typedef struct{
int ty, tx; // filas, columnas
int**tab; // matriz
}t_matriz;
1) Escribir la funcin:
t_matriz asigna_mem_matriz (int tx, int ty);
que crea una matriz de ty*tx elementos.

2) Escribir la funcin:

void destruye_matriz (t_matriz mat);


que libera la memoria ocupada por la matriz.

3) Escribir la funcin:

void inicializa_matriz (t_matriz mat);


que inicializa la matriz con valores.

4) Escribir la funcin:

void muestra_matriz (t_matriz mat);


que muestra el contenido de una matriz.

5) Escribir la funcin:

t_matriz copia_matriz (t_matriz mat);


que crea una nueva matriz de un tamao idntico al de mat e inicializa los valores de la copia con los
valores de mat, simplemente copindolos.

Escribir un programa con un men que permita realizar estas operaciones.

Ejercicio 8 (requiere conocer el uso de archivos)

Se propone realizar una funcin de carga de un archivo de texto en memoria principal. El formato del
archivo es el siguiente:

- la primera lnea tiene los nmeros de filas y de columnas de una matriz de char
- a continuacin cada lnea tiene num columnas de elementos
Por ejemplo:
3 4
abcd
efgh
ijkl
Escribir la funcin de carga que reciba por parmetro una matriz del tamao adecuado.
Escribir una funcin de visualizacin de la matriz.
Comprobar en un programa. Iniciar varias veces el programa modificando cada vez el contenido del
archivo de texto.
c. Asignacin dinmica de memoria con calloc() y realloc()

Ejercicio 9

En un programa, se forma una agenda de contactos que incluye nombre, apellidos, telfono y direccin.
Primera parte:

Definir un tipo para almacenar un contacto.


El usuario introduce el nmero de contactos que quiere aadir.
Asignar dinmicamente una tabla para almacenar los contactos. Al finalizar todos los contactos sern 0.
Escribir una funcin de inicializacin de un contacto y rellenar la tabla.
Mostrar la tabla rellenada.
Guardar en binario la tabla y su tamao (si conoce el uso de archivos).
Ejercicio 10 (requiere conocer el uso de archivos)

En un programa, retomar el tipo definido en el ejercicio 1. El objetivo esta vez es poder modificar la
base de datos de contactos registrada en un archivo binario, o bien aadiendo o bien quitando
contactos. El programa ofrece ahora un men con cuatro opciones:

Cargar: obtener toda la base de datos en una tabla del tamao adecuado.
Aadir: aumentar el tamao de la tabla y aadir un contacto.
Eliminar: elegir el contacto que se eliminar, eliminarlo y disminuir el tamao de la tabla.
Guardar: archivo binario, tabla y su tamao.
Ejercicio 11

En un programa, en el bucle principal, el tamao t de una tabla se da al azar:

Una funcin asigna memoria dinmicamente a una tabla de t punteros de enteros. La primera vez, todos
los punteros sern NULL, es decir, sern ((int*) 0).
Una funcin inicializa valores y otra funcin los muestra.
El programa finaliza o vuelve a empezar a decisin del usuario.

Punteros como parmetros de funcin

1. Paso por referencia

Los parmetros de funciones son variables locales a la funcin inicializados con valores en el momento
de la llamada a la propia funcin. Cuando se pasa una tabla por parmetro, el parmetro es un puntero
que toma por valor la direccin del primer elemento, que es al mismo tiempo la direccin del bloque de
memoria de la tabla. Esta seccin tiene como objetivo estudiar el paso de direcciones de memoria de
cualquier tipo de variable, y no solamente de tablas. Es lo que se llama un paso por referencia y se
trata de la referencia a una variable mediante su direccin de memoria. Tambin detallamos algunos
aspectos ms especficos relativos al paso de tablas por parmetro.

a. Caso general de una variable cualquiera

La funcin modif() tiene un parmetro al que se le asigna el valor 50:

void modif(int e1)


{
e1=50;
}
en el main(), una variable v se inicializa con el valor 10. Se llama a la funcin modif() y el valor de v se
asigna a su parmetro. El programa es el siguiente:

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

int main()
{
int v=10;
modif(v);
printf("v=%d \n", v);
return 0;
}
Qu imprime la funcin printf()?

La variable v, local al contexto de la llamada, vale siempre 10.

El parmetro de la funcin es otra variable, local a la funcin, a la que se le asigna un valor en el


momento de la llamada. Todas las modificaciones realizadas a esta variable local a la funcin
modifican nicamente esta misma variable.

En C, el paso de parmetros se realiza siempre por valor y es nicamente el valor de las variables lo
que se asigna a los parmetros de las funciones. No es la variable en s misma lo que pasa a ser el
parmetro de la funcin.

Sin embargo, si se utiliza un puntero como parmetro de funcin y se pasa como valor para el
parmetro la direccin de memoria de la variable, es posible modificar el valor de esta variable pasando
su direccin de memoria. Es un paso por referencia. Por ejemplo:

void modif( int *x)


{
*x=50;
}
La funcin modif() tiene ahora como parmetro un puntero a entero. Utilizando el operador *
(asterisco), la funcin modif() va a poder escribir en la direccin de memoria que se le ha pasado por
parmetro en el momento de la llamada. El programa quedara de la siguiente manera:

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

int main()
{
int v=10;

printf( "v=%d",v); // v vale 10


modif_( &v ); // paso de la direccin de v al parmetro x
printf( "v=%d",v); // v ahora vale 50
return 0;
}
b. Ejemplo: una funcin que devuelve la hora
El return permite el retorno de un solo valor, lo que no es siempre suficiente y puede complicar la
escritura de funciones. Lo interesante del paso por referencia es poder transformar parmetros de
entrada en parmetros de salida. Hay, por lo tanto, tantas salidas como se necesiten. A continuacin se
muestra como ejemplo una funcin que devuelve el tiempo en horas, minutos y segundos.

El tiempo universal se obtiene de la funcin:

time_t time(time_t*tmp) ;
Esta funcin devuelve el valor del tiempo segn una codificacin interna al sistema, o -1 si es
imposible obtenerlo. Este valor tambin se copia a la direccin del parmetro, excepto si este es NULL.
Este tiempo interno al sistema debe convertirse, por ejemplo, con la funcin:

struct tm* localtime(const time_t*tu)


Esta funcin convierte el valor de tiempo interno, obtenido con time(), a un formato de tiempo llamado
tiempo externo y descrito por la tupla tm presentada a continuacin. El tiempo interno se pasa en el
primer parmetro y la direccin de la tupla tm asignada dinmicamente, y correctamente inicializada,
se devuelve. Una tupla tm incluye:

struct tm{

int tm_year; // nmero de aos transcurridos desde 1900


int tm_mon; // nmero de meses transcurridos desde el inicio
// del ao
int tm_mday; // da del mes de 1 a 31
int tm_yday; // da del ao de 0 a 365
int tm_hour; // horas desde el inicio del da de 0 a 23
int tm_min; // minutos de 0 a 59 desde el inicio
// de la hora actual
int tm_sec; // segundos desde el inicio del minuto actual
// de 0 a 59
};
Ahora podemos escribir nuestra funcin para tener la hora:

void hms(int *h, int *m, int*s)


{
time_t tu; // para obtener el tiempo universal (interno)
struct tm *p; // para obtener el tiempo en formato legible

time(&tu); // paso por referencia de la variable tu


p = localtime(&tu); // paso por referencia de tu solo para lectura
// pero retorno de la direccin de una tupla
// obtenida dinmicamente en la funcin

*h = p->tm_hour; // escritura de la hora en la direccin de h


*m = p->tm_min; // escritura de los minutos en la direccin de m
*s = p->tm_sec; // escritura de los segundos en la direccin de s
}
y podemos probarla en un main():
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
int h, m, s;

hms(&h, &m, &s);


printf("%d : %d : %d\n",h,m,s);
return 0;
}
c. Paso por referencia de una tupla

Sea la tupla t_trol:

typedef struct trol{


int x,y;
int color;

}t_trol;
Se puede tener una funcin de inicializacin con un puntero de tipo t_trol* como parmetro:

void init(t_trol*p)
{
p->x=rand()%800;
p->y=rand()%600;
p->color=rand()%256;
}
Y en alguna parte, la llamada:

t_trol t;
init(&t); // paso de la direccin de la struct t_trol
En el caso de la tabla de punteros, no hay que olvidar asignar los punteros. En efecto, si se escribe por
ejemplo:

t_trol* ALL[10];
int i;
for (i=0; i<10; i++){
init(ALL[i]); // ERROR! ALL[i] NO ASIGNADO
}
provoca un error de ejecucin, ALL[i] no tiene una direccin de memoria reservada. Por lo tanto, hay
que aadir previamente la asignacin de memoria (sea en el mismo sitio, o en cualquier otra parte en el
programa):

for (i=0; i<10; i++){


ALL[i]=(t_trol*)malloc(sizeof(t_trol));
init(ALL[i]); // OK, ALL[i] ASIGNADO
}
d. Paso por referencia de una variable puntero

El puntero es una variable como cualquier otra y, como tal, se puede pasar un puntero por referencia, es
decir, se puede pasar como valor del parmetro la direccin de una variable puntero. La direccin de
una variable puntero es un puntero... de puntero. A continuacin se muestra un ejemplo de una funcin
de asignacin de memoria dinmica para una tabla de float que no utiliza el sistema de retorno:

void asigna_mem(float**f,int tam)


{
*f=(float*)malloc(sizeof(float)*tam);
}
y la llamada en un main():

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

int main()
{
float*tab;
int i;
asigna_mem(&tab, 10);
for (i=0; i<10; i++){
tab[i]= (rand()%10000)/100.0;
printf("%f ",tab[i]);
}
putchar(\n);

return 0;
}
Son exactamente las mismas operaciones que para todas las variables, excepto que la variable en
cuestin es un puntero y se usa a continuacin como puntero. De este modo, en la funcin
asigna_mem(), la expresin *f es de tipo float*, lo que permite una asignacin dinmica de una tabla de
float.

Otro ejemplo: queremos una funcin de asignacin de memoria para matrices de enteros que no use el
mecanismo de retorno. Una matriz es un puntero a punteros a int (int**) y la direccin de un puntero a
punteros es un puntero... a punteros a punteros, un int***:

void asigna_mem(int***m,int t1, int t2)


{
int i;
*m=(int**)malloc(sizeof(int*)*t1);
for (i=0; i<t1; i++)
(*m)[i]=(int*)malloc(sizeof(int)*t2);
}

Atencin! Los parntesis (*mat)[i] son necesarios porque el operador corchetes [ ] es prioritario sobre
el operador asterisco *. Sin parntesis *mat[i] se interpreta como *(mat[i]).
Para comprobar la asignacin dinmica realizada, a continuacin se muestra una funcin de
inicializacin y una funcin de visualizacin:

void inicializa(int**mat, int t1, int t2)


{
int i, j;
for (i=0; i<t1; i++)
for (j=0; j<t2; j++)
mat[i][j]=rand()%100;
}

void muestra(int**mat, int t1, int t2)


{
int i, j;
for (i=0; i<t1; i++){
for (j=0; j<t2; j++)
printf("%3d",mat[i][j]);
putchar(\n);
}
}
y las llamadas en un main():

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

int main()
{
int**mat;

asigna_mem(&mat,5,10); // direccin de mat pasada por referencia


inicializa(mat,5,10); // valor de mat (direccin de la matriz)
muestra(mat,5,10);
return 0;
}
ltimo ejemplo, retomemos nuestra tupla t_trol:

typedef struct trol{


int x,y;
int color;

}t_trol;
y modifiquemos la inicializacin de forma que integremos la asignacin de memoria del puntero
pasando el puntero por referencia:

void inicializa(t_trol**p)
{
*p=(t_trol*)malloc(sizeof(t_trol));
(*p)->x=rand()%800;
(*p)->y=rand()%600;
(*p)->color=rand()%256;
}
La expresin *p es de tipo t_trol* y los parntesis son necesarios alrededor de *p porque la flecha es
prioritaria sobre el asterisco. Sin parntesis:

*p->x es equivalente a *(p->x)


lo que supone que el campo x es de tipo puntero (y atencin al error si es realmente el caso).

La inicializacin de una tabla de punteros t_trol* puede ser objeto de una funcin aparte:

void inicializacin(t_trol* t[])


{
int i;
for (i=0; i<NUMMAX; i++)
inicializa(&t[i]); // paso de la direccin del puntero i en
// la tabla
}
A continuacin se muestra el programa completo:

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

#define NUMMAX 100


typedef struct trol{
int x,y;
int color;

}t_trol;

void inicializa(t_trol**p)
{
*p=(t_trol*)malloc(sizeof(t_trol));
(*p)->x=rand()%800;
(*p)->y=rand()%600;
(*p)->color=rand()%256;
}

void inicializacin(t_trol* t[])


{
int i;
for (i=0; i<NUMMAX; i++)
init(&t[i]);
}

void muestra(t_trol*t[])
{
int i;
for (i =0; i<NUMMAX; i++){
printf("%4d %4d %4d\n",t[i]->x,t[i]->y,t[i]->color);
}
}
int main()
{
t_trol* ALL[NUMMAX];

inicializa(ALL);
muestra(ALL);
return 0;
}
2. Tablas dinmicas por parmetro

En el caso de una tabla esttica como parmetro de funcin, solo la primera dimensin de la tabla se
converta a puntero. Por ejemplo, si se trata de una matriz de enteros, una tabla de dos dimensiones, la
matriz se converta en puntero de tablas de enteros y, por esta razn, el tamao de la segunda dimensin
deba especificarse. Este funcionamiento es idntico sea cual sea el nmero de dimensiones de la tabla
esttica que figura como parmetro de la funcin: solo la primera dimensin se convierte a puntero y el
resto de las dimensiones son el tipo del puntero; sus tamaos deben especificarse.

A continuacin se muestra una pequea prueba para ver las compatibilidades e incompatibilidades
entre tres formas de tablas bastante parecidas, aunque diferentes:

#define TY 3 // nmero de filas


#define TX 8 // nmero de columnas

char mat1[TY][TX]; // matriz esttica de char


char*mat2[TY]; // tabla esttica de punteros de char
char**mat3; // matriz dinmica de char O
// tabla dinmica de punteros a char
Escribir tres funciones idnticas de visualizacin con cada tabla distinta por parmetro:

void test1 ( char**tab )


void test2 ( char* tab[ ] )
void test3 ( char tab[ ][TX] )

/* contenido de las tres:


{
int x,y;
putchar(\n);
for (y=0;y<TY;y++){
putchar(\t);
for (x=0; x<TX; x++)
putchar(tab[y][x]);
putchar(\n);
}
putchar(\n);
}
Cmo se comporta la funcin test1 con los parmetros mat1, mat2 y mat3?

Cmo se comporta la funcin test2 con los parmetros mat1, mat2 y mat3?

Cmo se comporta la funcin test3 con los parmetros mat1, mat2 y mat3?

Obtenemos las compatibilidades e incompatibilidades siguientes:

Tipos pasados por parmetro


Tipos de parmetros
(char**tab)
(char* tab[ ])
(char tab[ ][TX])
char mat1[TY][TX]
NO
NO
OK
char*mat2[TY]
OK
OK
NO
char**mat3
OK
OK
NO
El siguiente programa permite comprobarlo:

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

int main(int argc, char *argv[])


{
// declaraciones de las tablas
char mat1[TY][TX];
char*mat2[TY];
char**mat3;
int y,x;

// las tablas se asignan memoria si fuera necesario y se inicializan


// con los mismos valores
for (y=0; y<TY; y++)
for (x=0; x<TX; x++)
mat1[y][x]=0+x;

for (y=0; y<TY; y++){


mat2[y]=(char*)malloc(sizeof(char)*TX);
for (x=0; x<TX; x++)
mat2[y][x]=0+x;
}
mat3=(char**)malloc(sizeof(char*)*TY);
for (y=0;y<TY;y++){
mat3[y]=(char*)malloc(sizeof(char)*TX);
for (x=0; x<TX; x++)
mat3[y][x]=0+x;
}

// primera serie de pruebas con mat1 como parmetro para cada funcin
printf("TEST 1 Paso de: char mat1[TY][TX] \n\n");

printf("llamada de la funcin f(char**tab) : "


"Warning y cuelgue\n");
//test1(mat1);
putchar(\n);
printf("llamada de la funcin f(char* tab[]) : "
"Warning y cuelgue\n");
// test2(mat1);
putchar(\n);
printf("llamada de la funcin f(char tab[][TX]): ok\n");
test3(mat1);

// serie 2 con mat2


printf("TEST 2 Paso de: char *mat2[TY] \n\n");
printf("llamada de la funcin f(char**tab) : ok\n");
test1(mat2);
printf("llamada de la funcin f(char* tab[]) : ok\n");
test2(mat2);
printf("llamada de la funcin f(char tab[][TX]) : "
"Warning y resultados inesperados\n\n");
test3(mat2);

// serie 3 con mat3


printf("TEST 3 Paso de: char**mat3 \n\n");
printf("llamada de la funcin f(char**tab) : ok\n");
test1(mat3);
printf("llamada de la funcin f(char* tab[]) : ok\n");
test2(mat3);
printf("llamada de la funcin f(char tab[][TX]): "
"Warning y resultados inesperados\n\n");
test3(mat3);
return 0;
}
3. Puesta en prctica: paso por referencia

a. Paso por referencia, conocimientos bsicos

Ejercicio 1
En un programa, una funcin inicializa dos enteros y un real con valores aleatorios. Otra funcin
muestra los valores obtenidos. El programa finaliza si lo solicita el usuario.

Ejercicio 2

En un programa, una funcin devuelve el cociente y el resto de la divisin de un entero p entre un


entero q; p y q se obtienen o bien de forma aleatoria o bien los entra el usuario. El programa finaliza si
lo solicita el usuario.

Ejercicio 3

En una funcin, los valores de dos variables pasadas por referencia se intercambian. Escribir un
programa con:

Introduccin de valores y visualizacin.


Intercambio de valores.
Visualizacin del resultado.
Salir o volver a empezar.
Ejercicio 4

Sea una tupla que incluya una posicin (nmero real), un desplazamiento (nmero real), una letra
(entero) y un color (entero). En un programa:

Definir un tipo y declarar dos tuplas.


Escribir una funcin que permite inicializar dos tuplas en una sola llamada (las tuplas se pasan por
referencia).
Escribir una funcin que permita intercambiar los contenidos de ambas tuplas.
Salir o volver a empezar.
Ejercicio 5

En un programa:

Escribir una funcin de asignacin dinmica de memoria para una tabla de n enteros (el tamao se
obtiene o bien por el usuario o bien de forma aleatoria).
Escribir una funcin de inicializacin con valores comprendidos entre un valor umbral bajo y un valor
umbral alto proporcionados como parmetros de la funcin.
Escribir una funcin de obtencin de los valores umbral (bajo y alto).
Escribir una funcin de visualizacin de una tabla de n elementos.
Escribir una funcin que permita al contexto de la llamada determinar los valores mximo y mnimo de
una tabla de enteros de tamao n pasada por parmetro. La funcin no devuelve nada. Los valores
deben poder obtenerse en el contexto de la llamada sin uso de variables globales.
Comprobar en un programa que se detenga cuando el usuario lo solicite.

b. Paso por referencia, operadores bit a bit

Ejercicio 6

Realizar en C una librera para programar microcontroladores. A continuacin, algunas de las funciones
que se deben escribir:
bit_clear()
Sintaxis: bit_clear(var,bit)
Tarea: poner a 0 el bit "bit" de la variable "var"
Ejemplo: bit_clear(a, 3); // pone a 0 el bit 3 de a
bit_set()
Sintaxis: bit_set(var, bit)
Tarea: poner a 1 el bit "bit" de la variable "var"
Ejemplo: bit_set(a,5); // pone a 1 el bit 5 de a
bit_test()
Sintaxis: bit_test(var, bit)
Tarea: prueba el estado del bit "bit" de la variable "var"
Ejemplo: a=2;
bit_test(a, 2); // devuelve 1, bit 2 de a igual a 1
get_bytet()
Sintaxis: get_byte(var,b)
Tarea: devuelve el valor del byte "b" de la variable "var"
Ejemplo: a=0xFF00;
get_oct(a,2); // devuelve 255
set_byte()
Sintaxis: set_byte(var,b,val)
Tarea: asigna "val" al byte "b" de la variable "var"
Ejemplo: a=0 ;
set_oct(&a,0,0xF); // asigna el valor decimal 15 al byte 0 de a
set_high()
Sintaxis: set_high(var,b)
Tarea: pone a 1 los bits del byte "b" de la variable "var"
Ejemplo: a=0
set_high(&a,3); // pone a 1 los 8 bits del ltimo byte de a
set_low()
Sintaxis: set_low(var,b)
Tarea: pone a 0 los bits del byte "b" de la variable "var"
Ejemplo: a=7654
set_low(&a,0); // pone a 0 los 8 bits del primer byte de a
rotate_left()
Sintaxis: rotate_left(var,b,n)
Tarea: rotacin izquierda de n posiciones del byte "b"
Ejemplo: a=512;
rotate_left(&a,2,3); // rota tres bits a la izquierda el byte 2 de a
rotate_right()
Sintaxis: rotate_right(var,b,n)
Tarea: rotacin derecha de n posiciones del byte "b"
Ejemplo: a=512;
rotate_right(&a,2,3); // rota tres bits a la derecha el byte 2 de a
c. Paso de punteros por referencia

Ejercicio 7

En un programa, en el bucle principal:


Los tamaos de una tabla de float y el tamao de una tabla de enteros se determinan al azar.
Una funcin asigna memoria dinmicamente a las dos tablas a la vez.
Una funcin inicializa ambas tablas.
Una funcin muestra ambas tablas.
Salir o volver a empezar. Atencin a la memoria.
Ejercicio 8

En un programa:

El usuario entra el tamao de una matriz.


La matriz se asigna dinmicamente en un procedimiento (sin retorno).
Una funcin inicializa la matriz con valores.
Una funcin muestra la matriz.
Salir o volver a empezar. Atencin a la memoria.
Ejercicio 9

En un programa, cuatro cadenas de caracteres se declaran de la siguiente forma:

char*s1,*s2,*s3,*s4;

Una funcin permite introducir las cuatro cadenas a la vez.


Una funcin permite visualizarlas una por una.
Salir o volver a comenzar.
Ejercicio 10

En un juego hay trols con una posicin (x,y) y un color.

Definir una tupla t_trol (ver en este captulo - secciones Principios de un puntero - Caso de las tablas de
punteros y Punteros como parmetros de funcin - Paso por referencia de una tupla, para typedef).
Escribir una funcin de asignacin de memoria dinmica para una tabla de t_trol de 5 dimensiones con
uso de return.
Escribir una funcin de asignacin de memoria dinmica para una tabla de t_trol de 5 dimensiones sin
uso de return.
A continuacin, siempre sin return, descomponer la asignacin de memoria escribiendo una funcin
para cada dimensin (para cada nueva dimensin llamar a la funcin de la dimensin anterior, como
una especie de cascada).

Escribir una funcin que asigne memoria dinmicamente a una tabla de una dimensin de t_trol cuyo
tamao viene dado por parmetro.
Escribir una funcin que asigne memoria dinmicamente a una tabla de dos dimensiones cuyos
tamaos vienen dados por parmetro.
Escribir una funcin que asigne memoria dinmicamente a una tabla de tres dimensiones cuyos
tamaos vienen dados por parmetro.
Escribir una funcin que asigne memoria dinmicamente a una tabla de cuatro dimensiones cuyos
tamaos vienen dados por parmetro.
Escribir una funcin que asigne memoria dinmicamente a una tabla de cinco dimensiones cuyos
tamaos vienen dados por parmetro.
En un main, dar un ejemplo de llamada que permita crear una tabla de t_trol de 5 dimensiones cada una
de ellas entrada por el usuario. Probar los programas y aadir inicializacin y visualizacin.

d. Paso de tablas dinmicas

Ejercicio 11

En un programa, se declara una tabla dinmica:

int *tab;
A partir de estos prototipos:

void asigna_mem1 (int**t, int tam);


void asigna_mem2 (int tam);
int* asigna_mem3 (int tam);
void inicializa1 (int t[], int tam);
void inicializa2 (int**t);
void inicializa3 (int*t);
int* inicializa4 (void);
Qu funciones pueden usar tab como argumento? Escribir las funciones designadas como vlidas y
realizar un programa de prueba. Dar las razones por las que las otras han sido rechazadas.

Ejercicio 12

En un programa, se declara una matriz dinmica:

int **mat;

A partir de estos prototipos:

void asigna_mem1 (int***m, int fil, int col);


int** asigna_mem2 (int fil, int col);
int* asigna_mem3 (int*m[],int fil,int col);
void inicializa1 (int m[][]);
void inicializa2 (int**m, int fil, int col);
void inicializa3 (int*t, int fil);
int** inicializa4 (void);
Qu funciones pueden usar tab como argumento? Escribir las funciones designadas como vlidas y
realizar un programa de prueba. Dar las razones por las que las otras han sido rechazadas.
Archivos (tipo FILE*)

1. Nociones bsicas

a. El tipo FILE*

En un programa C, un archivo siempre es una tupla de tipo FILE manipulada mediante su direccin con
un puntero FILE*. Para usar un archivo en un programa, o una funcin del programa, lo primero que
hay que hacer es declarar un FILE*:

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

int main()
{
FILE*f;
(...)
return 0;
}
b. Apertura y cierre de un archivo

En la librera estndar stdio.h, est la funcin:

FILE* fopen( const char* name ,const char* mode);


para abrir un archivo o crear un archivo inexistente. Esta funcin abre el archivo cuyo nombre, que
incluye la ruta de acceso, es dado al primer parmetro y segn el modo dado al segundo parmetro.
Ambos son cadenas de caracteres. Hay seis modos posibles: r, w, a, r+, w+, a+, y el carcter b aadido
a continuacin permite seleccionar una entrada/salida en modo binario.

A continuacin se muestra una tabla resumen de los distintos modos posibles:

Modo
Acceso
Posicionamiento para escritura
Si el archivo existe
Si el archivo no existe
r
Lectura
al comienzo
apertura
error
w
a
Escritura
Escritura
al comienzo
al final
inicializacin
apertura
creacin
creacin
r+
w+
a+
Lectura
y
escritura
al comienzo
al comienzo
al final
apertura
inicializacin
apertura
error
creacin
creacin
Sufijo b: aadir para las entradas/salidas binarias.

Atencin: la inicializacin de un archivo significa que se reduce a tamao cero, es decir, que todos sus
datos se perdern.

Para cerrar un archivo anteriormente abierto, la librera stdio.h proporciona la funcin

int fclose(FILE*);
Esta funcin cierra el archivo indicado mediante el puntero de su nico parmetro. Devuelve EOF si
hay error y cero si todo ha ido correctamente. EOF es un valor centinela que indica el final del archivo.

Ejemplo de apertura y cierre de archivo en modo binario:

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

int main()
{
FILE*f;

if (f=fopen("test.bin","rb")){
printf("el archivo binario test.bin existe\n");
fclose(f);
}
else{
printf("el archivo binario test.bin no existe\n");
if (f=fopen("test.bin","wb"))
{
printf("el archivo binario test.bin ha sido creado\n");
fclose(f);
}
else
printf("error al crear el archivo binario text.bin\n");
}
return 0;
}
Despus de la primera ejecucin, este programa crea el archivo binario "test.bin" localizado en el
mismo directorio que el del programa (o a nivel del proyecto durante el trabajo con un compilador).

c. Especificar una ruta de acceso

El nombre del archivo corresponde a la ruta de acceso desde la raz de un disco C:/(ruta absoluta) o
bien a la ruta de acceso desde el directorio en el que se encuentra el programa (ruta relativa). En el caso
de una ruta relativa, el punto seguido de una barra oblicua indica que se desciende desde el directorio
actual a los subdirectorios. Por ejemplo: "./ej1/parte 4/test.txt" significa a partir del directorio actual, en
la carpeta ej1, en la carpeta parte 4, el archivo test.txt. De modo inverso, para subir un directorio a
partir del directorio actual, hay que poner dos puntos seguidos. Por ejemplo: "../../" permite subir dos
directorios hacia arriba en el sistema de ficheros desde el directorio actual. Por supuesto, tambin se
pueden combinar ambos, subir uno o varios niveles y a continuacin descender para alcanzar el
archivo. Por ejemplo: "../../ej1/parte 4/test.txt" significa subir dos niveles y descender por la carpeta ej1
y a continuacin por parte 4 para alcanzar el archivo test.txt. Atencin: las carpetas deben existir y se
deben especificar en el sitio adecuado; de lo contrario, la ruta indicada no ser vlida.

Cabe destacar la existencia de la macro constante FILENAME_MAX que define el tamao mximo
que puede tener un nombre de archivo (ruta incluida) soportado por el sistema.

Ejemplo de ruta relativa:

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

int main(int argc, char *argv[])


{
FILE*f;
if ( (f=fopen("../../test.txt","w"))!=NULL)
printf("el archivo se ha creado 2 directorios encima del "
"directorio donde se encuentra el programa\n");
else
printf("error en la creacin del archivo\n");
return 0;
}
2. Archivos binarios

a. Escritura y lectura en modo binario

El uso de archivos binarios se basa nicamente en dos funciones. Una para la escritura y otra para la
lectura. Ambas funciones se encuentran en la librera estndar stdio.h.

Para escribir (guardar) datos:

size_t fwrite ( const void*, size_t, size_t, FILE* );


Esta funcin escribe, en el archivo especificado en p4, p3 objetos de tamao p2 en bytes, dispuestos a
partir de la direccin p1. Devuelve el nmero de objetos realmente escritos.

Para leer (cargar) los datos de un archivo:

size_t fread ( void*, size_t, size_t, FILE*);


Esta funcin lee como mximo p3 objetos de tamao p2 en bytes en el archivo especificado en p4 y los
copia a la direccin p1. Devuelve el nmero de objetos realmente ledos.

Ejemplo de uso:

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

#define NUM_PUNTOS 10

typedef struct punto{


int x,y;
}t_punto;

void inicializa (t_point t[]);


void muestra (t_point t[]);

int main()
{
FILE*f;
t_punto datos[NUM_PUNTOS];
t_punto obten[NUM_PUNTOS];

inicializa(datos);
muestra(datos);
printf("-----------------------------\n");

if (f=fopen("test.bin","wb+")){
fwrite(datos,sizeof(datos),1,f);
// mover el cursor de escritura/lectura al comienzo del archivo
rewind(f);

if (fread(obten,sizeof(t_punto),NUM_PUNTOS,f)== NUM_PUNTOS)
muestra(obten);
else
printf("error en la lectura del archivo binario\n");
}
else
printf("error en la creacin del archivo binario\n");

return 0;
}
void inicializa(t_punto t[])
{
int i;
for (i=0; i<NUM_PUNTOS; i++){
t[i].x=rand()%100;
t[i].y=rand()%100;
}
}

void muestra(t_punto t[])


{
int i;
for (i=0; i<NUM_PUNTOS; i++)
printf("t[%3d].x=%3d,.y=%3d\n",i,t[i].x,t[i].y);
}
b. Detectar el final del archivo binario

Un primer mtodo consiste en ir identificando cada elemento del archivo uno por uno hasta que fread()
devuelva 0, lo que significa que, en principio, se ha llegado al final del archivo (excepto si se produce
un error entre dos llamadas).

Por ejemplo, un bucle como:

t_punto obten[NB_POINT*2];
int ultimo=0;

(...)
while(fread(&obten[ultimo++],sizeof(t_punto),1,f))
;
Atencin, sin embargo, con no desbordar el tamao de la tabla que recibe los datos.

Otro mtodo consiste en utilizar la funcin de la librera estndar stdio.h:

int feof ( FILE*);


Esta funcin devuelve verdadero si la posicin actual en el archivo pasado por parmetro est al final y
falso en caso contrario. Puede usarse de dos formas: texto y binario.

Por ejemplo:

while(!feof(f))
fread(&obten[ultimo++],sizeof(t_punto),1,f);
c. Desplazamientos en un archivo

Para accesos directos a cualquier sitio del archivo, hay bsicamente tres funciones predefinidas en la
librera estndar stdio.h.

int fseek (FILE*, long, int);


Esta funcin permite modificar la posicin actual en el archivo especificado en p1 desde la posicin
que se especifica en p3 de la forma siguiente:
SEEK_SET a partir del comienzo del archivo
SEEK_CUR a partir de la posicin actual
SEEK_END a partir del final.
El segundo parmetro p2 indica un desplazamiento en bytes. Por ejemplo, la llamada:

fseek (miArchivo, 16, SEEK_SET);


desplaza la posicin actual a 16 bytes (16 caracteres) a partir del comienzo del archivo. En caso de
error, fseek devuelve un valor negativo y 0 si no hay error.

long ftell (FILE*);


Esta funcin devuelve la posicin actual asociada al archivo pasado por parmetro. Da error si el
tamao que se devuelve es mayor que LONG_MAX (en este caso hay que utilizar la funcin
fgetpos()).

void rewind ( FILE*);


Esta funcin mueve la posicin actual al comienzo del archivo especificado por parmetro. Sea f un
archivo abierto, la llamada

rewind(f); // es equivalente a fseek(f,0 SEEK_SET);


3. Escritura y lectura en modo texto

Algunas funciones presentes en este mdulo tambin estn presentes en el mdulo sobre las cadenas de
caracteres. All se usan con los archivos de entrada/salida estndar (stdin y stdout). Ahora se trata de
usarlas ms extensamente con todo tipo de archivos creados en un programa. En este mdulo hay otros
ejemplo de uso.

a. Detectar el final de un archivo, EOF y feof()

Sobre todo en modo texto, a menudo es necesario detectar el final de un archivo. Hay dos mtodos. El
primero es usando el valor EOF y el segundo con la funcin feof().

El carcter de final de archivo EOF ------- <stdio.h>


El final de los archivos de texto y binarios se marca con un valor definido por la macro constante EOF
definida en stdio.h. Este valor vale en general -1, pero puede variar segn el entorno. En las pruebas
que lo utilizan, es ms seguro utilizar EOF que -1.

La mayora de las funciones en modo texto utilizan el valor EOF para detectar el final del archivo. Este
valor se usa tambin como retorno de error, por ejemplo por la funcin fclose().

int feof ( FILE*) ------- <stdio.h>


Esta funcin devuelve verdadero si la posicin actual en el archivo pasado por parmetro est al final y
falso en caso contrario. Puede usarse para los dos modos, texto y binario.

b. Lectura/escritura de caracteres

int fgetc ( FILE*) ------- <stdio.h>


Esta funcin devuelve un carcter ledo del archivo especificado en el parmetro y EOF en caso de
error o de deteccin del final del archivo. La macro int getc(FILE*) es otra forma equivalente.
int fputc (int, FILE*) ------- <stdio.h>
Esta funcin escribe en el archivo especificado en p2 el carcter especificado en p1. Devuelve el
carcter p1; en caso de error devuelve EOF.

Ejemplo de uso

El siguiente programa abre en modo sola lectura el archivo entrada.txt. Este archivo existe y se
encuentra en el mismo directorio que el programa. Para realizar la prueba, se supone que tambin
contiene texto. Se abre o se crea, si todava no existe, un segundo archivo (salida.txt) en modo solo
escritura. Carcter a carcter se obtiene el contenido de entrada.txt y se copia en salida.txt y tambin en
stdout para comprobar visualmente el funcionamiento:

#include <stdio.h>
#include <stdlib.h>
#define ERROR(msg){\
printf("%s\n",msg);\
system("PAUSE");\
exit(EXIT_FAILURE);\
}

int main(int argc, char *argv[])


{
FILE *in, *out;
int c;

// apertura del archivo de entrada en modo solo lectura


in=fopen("entrada.txt","r");
if (in==NULL)
ERROR("apertura del archivo entrada.txt");

// apertura del archivo de salida en modo solo escritura


if ( (out=fopen("salida.txt","w"))==NULL)
ERROR("apertura del archivo salida.txt");

// lectura y escritura carcter a carcter


while ( (c=fgetc(in))!=EOF){
fputc(c,out);
fputc(c,stdout); // control en la ventana de consola
}
fclose(in);
fclose(out);
return 0;
}
c. Lectura/escritura de cadenas

char* fgets (char*, int, FILE*) ------- <stdio.h>


Esta funcin lee una cadena que se ha entrado en el archivo especificado en el parmetro p3 (en el caso
de la entrada estndar este archivo ser stdin) hasta un \n o hasta que se hayan ledo p2-1 caracteres,
incluido el \n final. Todos los caracteres ledos se colocarn a partir de la direccin especificada en p1.
Un \0 se aadir como ltimo carcter despus del \n en el caso de una lectura por la entrada
estndar stdin. La funcin devuelve la direccin p1 o NULL en caso de error.

int fputs(char*, FILE*) ------- <stdio.h>


Esta funcin permite escribir (copiar) la cadena pasada al parmetro p1 en el archivo pasado al
parmetro p2. Devuelve EOF si se produce un error.

Ejemplo de uso

Programa igual al anterior, pero cadena de caracteres a cadena de caracteres.

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

#define ERROR(msg){\
printf("%s\n",msg);\
system("PAUSE");\
exit(EXIT_FAILURE);\
}

int main(int argc, char *argv[])


{
FILE *in, *out;
char buf[1000];

// apertura del archivo de entrada en modo solo lectura


in=fopen("entrada.txt","r");
if (in==NULL)
ERROR("apertura del archivo entrada.txt");

// apertura del archivo de salida en modo solo escritura


if ( (out=fopen("salida.txt","w"))==NULL)
ERROR("apertura del archivo salida.txt");

// lectura y escritura con cadenas de caracteres


while ( fgets(buf,1000,in)!=NULL){
fputs(buf,out);
fputs(buf,stdout);
}
fclose(in);
fclose(out);
return 0;
}
d. Lectura/escritura con formato

int fscanf ( FILE*, const char*, ...) ------- <stdio.h>


Esta funcin analiza una serie de caracteres ledos del archivo especificado en p1. La divide en
tokens y convierte cada token en un valor. El desacople y la conversin se realizan segn la
especificacin de formato p2. Para cada especificacin de conversin que contenga, debe existir un
parmetro, pasado por referencia, en la lista de parmetros posibles que sigue a p2. Cada valor
resultante de una conversin se copia en la direccin del parmetro correspondiente. La funcin
devuelve el nmero de valores correctos encontrados teniendo en cuenta la adecuacin entre formatos y
las referencias de parmetros dados.

int fprintf ( FILE* const char*, ...) ------- <stdio.h>


Esta funcin da formato a la lista de parmetros posibles a continuacin de p2 segn la especificacin
de formato p2. La cadena de caracteres resultante se escribe en el archivo especificado en el parmetro
p1.

Ejemplo de uso

El mismo principio que en los programas anteriores, pero el archivo entrada.txt contiene ahora tres
nmeros, as como una serie de palabras para comprobar los formatos %d y %s. Este es el contenido:
10, 15, 4, toto participa en carreras.

Todo lo que se obtiene del archivo de entrada se copia en el archivo de salida y tambin en el archivo
de salida estndar stdout para visualizarlo en la consola.

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

#define ERROR(msg){\
printf("%s\n",msg);\
system("PAUSE");\
exit(EXIT_FAILURE);\
}

int main(int argc, char *argv[])


{
FILE *in, *out;
int a,b,num,i;
char buf[1000];

// apertura del archivo de entrada en modo solo lectura


in=fopen("entrada.txt","r");
if (in==NULL)
ERROR("apertura del archivo entrada.txt");

// apertura del archivo de salida en modo solo escritura


if ( (out=fopen("salida.txt","w"))==NULL)
ERROR("apertura del archivo salida.txt");

// lectura de los valores de nmeros con formato %d


fscanf(in,"%d, %d, %d, ",&a,&b, &num);

// escritura de los valores de nmeros en cadena de caracteres


// con formato %d
fprintf(out,"%d, %d, %d, ",a,b,num);

// equivalente a printf, control en la ventana de consola


fprintf(stdout,"%d, %d, %d,\n",a,b,num);

// recuperacin de num palabras


for (i=0; i<num; i++){
fscanf(in,"%s",buf);
fprintf(out,"%s",buf);
printf("%s ",buf); // control en la ventana de consola
}
return 0;
}
4. Copia de seguridad de elementos dinmicos

a. Guardar y cargar una tabla dinmica

Cuando hay que guardar datos obtenidos dinmicamente, el tamao de estos se puede guardar al
comienzo o al final del archivo, pero no es obligatorio. En cambio, cuando se cargan datos, es preciso
asignar la memoria necesaria de las variables a las que se asignarn los datos del archivo. Por ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct punto{
int x,y;
}t_punto;

void inicializa (t_punto**t,int num);


void muestra (t_punto t[],int num);

int main()
{
FILE*f;
t_punto*datos;
t_punto*obten=NULL;
int num;

srand(time(NULL));
if (f=fopen("test.bin","wb+")){
// asignacin de memoria e inicializacin de una tabla
// dinmica de tamao aleatorio
num=1+rand()%20;
inicializa(&datos,num);

// visualizacin de la tabla resultante


muestra(datos,num);
printf("-----------------------------\n");
// se guardan num tuplas de la tabla en el archivo
fwrite(data,sizeof(t_punto),num,f);

// recuperacin en una nueva tabla dinmica asignada


// separadamente
rewind(f);
num=0;
while(!feof(f)){
obten=(t_punto*)realloc(obten,sizeof(t_punto)*(num+1));
num+=fread(&obten[num],sizeof(t_punto),1,f);
}

// visualizacin del resultado


printf("load de %d struct t_punto\n",num);
muestra(obten,num);

// liberacin de la memoria asignada al final


free(datos);
free(obten);
// cierre del archivo
fclose(f);
}
else
printf("error en la creacin del archivo binario\n");
return 0;
}

void inicializa(t_punto**t, int num)


{
int i;
*t=(t_punto*)malloc(sizeof(t_punto)*num);
for (i=0; i<num; i++){
(*t)[i].x=rand()%100;
(*t)[i].y=rand()%100;
}
}

void muestra(t_punto t[],int num)


{
int i;
for (i=0; i<num; i++)
printf("t[%3d].x=%3d,.y=%3d\n",i,t[i].x,t[i].y);
}
b. Obtener datos mediante punteros

Todas las asignaciones dinmicas de memoria se pierden cuando el programa finaliza. Cuando se
guardan datos dinmicos en archivos, siempre hay que reasignar memoria en el momento de la
recuperacin de estos datos en el programa mediante punteros. Por ejemplo:
#include <stdio.h>
#include <stdlib.h>

int main()
{
FILE*f;
t_punto datos={10,20};
t_punto*obten;

if (f=fopen("test.bin","wb+")){
printf("x=%d, y=%d\n",datos.x, datos.y);

fwrite(&data,sizeof(t_punto),1,f);
rewind(f);

obten=(t_punto*)malloc(sizeof(t_punto));
fread(obten,sizeof(t_punto),1,f);

printf("x=%d, y=%d\n",obten->x, obten->y);


free(obten);
fclose(f);
}
else
printf("error en la creacin del archivo binario\n");
return 0;
}
5. Puesta en prctica: archivos

Ejercicio 1

En un programa, abrir en modo lectura un archivo cuyo nombre es introducido por el usuario. Indicar si
la operacin ha tenido xito. Si la operacin falla, el archivo no existe, crearlo e indicar el resultado.
Ejecutar el programa una primera vez, cerrarlo y volverlo a ejecutar por segunda vez para comprobar
que el archivo se haya creado correctamente. Ir al disco para ver dnde se encuentra el archivo. No
olvidar cerrar el archivo en el programa cuando ya no sea necesario mantenerlo abierto.

Ejercicio 2

Escribir un programa que abra o cree un archivo cuyo nombre, ubicacin y modo son introducidos por
el usuario. No olvidar cerrar el archivo cuando funcione y mostrar un mensaje al usuario.

Ejercicio 3

Un programa crea un archivo en una carpeta llamada test, en el directorio del programa. Comprobar
que el archivo se ha creado correctamente en el disco duro. Modificar el programa para abrir el archivo
en modo solo lectura y volver a ejecutar el programa. No olvidar cerrar el archivo.

Ejercicio 4
Con el bloc de notas crear un archivo y guardarlo en un directorio remoto al programa.

Con relacin al programa, hay que subir tres niveles y volver a descender otros cuatro para encontrar el
archivo. Escribir el programa que abra el archivo y lo cierre.

Ejercicio 5

Escribir un programa que pida al usuario que introduzca palabras una a una, que guarde estas palabras
al final de un archivo de texto y muestre todo el contenido del archivo. Inicialmente, el programa debe
crear el archivo si es que an no existe, pero no debe perder su contenido si ya exista. Comprobar este
punto ejecutando dos veces seguidas el programa.

Ejercicio 6

Escribir un programa que modifique el contenido de un archivo de texto de la siguiente manera:

Inicialmente el archivo de texto contiene en cada lnea una operacin aritmtica de dos enteros, por
ejemplo:

3+5

4-7

2*9

El programa debe interpretar correctamente el contenido de cada lnea y escribir el resultado de la


operacin en el mismo archivo, a continuacin de cada operacin:

3+5=8

4 - 7 = -3

2 * 9 = 18

Limitarse a las cuatro operaciones aritmticas bsicas (+, -, *, /) pero gestionar correctamente los
errores comunes:

Archivo inaccesible o inexistente.


Formato de archivo incorrecto.
Operacin aritmtica desconocida.
Ejercicio 7

Escribir un programa que a partir de un archivo de texto determine:

El nmero caracteres que contiene.


La cantidad de cada una de las letras del abecedario (solo se considerarn las minsculas).
El nmero de palabras.
El nmero de lneas.
Los finales de lnea no se cuentan como caracteres. Se admitir que dos palabras estn siempre
separadas por uno o ms de los caracteres siguientes:

Fin de lnea
Espacio
Puntuacin (, ; : ! . ?)
Parntesis ( )
Comillas dobles "
Apstrofo
Tambin se admitir, para simplificar, que ninguna palabra puede comenzar en una lnea y finalizar en
la siguiente.

Se aconseja realizar una funcin que permita decidir si un carcter dado, pasado por parmetro, es uno
de los separadores mencionados anteriormente. Devuelve 1 en caso afirmativo y 0 en caso contrario.

Ejercicio 8

Sea un archivo de texto creado con el bloc de notas. Escribir un programa que pueda ejecutar las
siguientes operaciones:

Guardar el contenido del archivo de texto en archivo binario.


Obtener los datos del archivo binario.
Modificar los datos.
Cada una de estas operaciones se controla con la visualizacin en la ventana de consola (al inicio el
contenido del archivo de texto, obtencin del archivo binario, cada nueva modificacin realizada sobre
las cadenas, cada nueva obtencin del archivo binario).

Ejercicio 9

Sea una matriz dinmica de nmeros aleatorios de num1*num2 en un programa; realizar las siguientes
funciones:

Una funcin de inicializacin con valores aleatorios.


Una funcin de visualizacin.
Dos funciones, una para guardar y otra para cargar, de la matriz entera.
Dos funciones, una para guardar y otra para cargar, de la matriz lnea a lnea.
Dos funciones, una para guardar y otra para cargar, de la matriz nmero a nmero.
Comprobar en un programa qu resultado da la eleccin entre estas operaciones mediante un men de
usuario. Comenzar por la inicializacin y la visualizacin, despus aadir guardar y cargar; comprobar
en cada fase del desarrollo.

Ejercicio 10

Crear un programa que permita introducir una palabra en el teclado y, a continuacin, guardarla en un
archivo antes de releerla en el archivo para mostrarla de nuevo. En este ejercicio, el archivo se abrir al
comienzo del main() y se cerrar al final del main(), sin aperturas despus de cierres mltiples.

Ejercicio 11
En un programa, escribir una frase con varias palabras en un archivo y a continuacin mostrar el
contenido del archivo al revs, letra a letra, comenzando por el final.

Ejercicio 12

En un programa, escribir una funcin que lea caracteres en un archivo de texto segn un salto
especificado por parmetro. Por ejemplo, 2 para una letra de cada dos, 3 para una letra de cada tres, 10
para una letra de cada 10, etc. Cuando el salto es positivo, avanza desde el comienzo del archivo hasta
el final; si el salto es negativo, se lee desde el final hasta el comienzo.

Ejercicio 13

Escribir una funcin de lectura de caracteres que pueda mostrar cualquier carcter de un archivo de
texto a partir de la posicin actual del archivo. El desplazamiento positivo o negativo en el archivo se
especifica por parmetro de esta funcin de visualizacin.

Ejercicio 14

Una almacn de artculos de deporte tiene una base de datos para gestionar su stock. Para cada artculo
hay que almacenar en memoria: el cdigo (valor entero), el nombre del producto, el precio unitario del
producto y el stock disponible (valor entero).

La base de datos es un archivo binario. Todos los datos de los artculos se almacenan artculo a artculo,
unos detrs de otros en el archivo.

En un programa, proponer al usuario las siguientes acciones:

Definir un tipo apropiado para identificar los artculos sabiendo que todas las operaciones se realizarn
en acceso directo sin almacenamiento en memoria principal (RAM).
Escribir una funcin de entrada de un nuevo artculo.
Escribir una funcin para guardar un artculo en la base de datos.
Escribir una funcin para visualizar un artculo cuyo cdigo se pasa por parmetro.
Escribir una funcin de visualizacin de la base de datos.
Escribir una funcin de bsqueda del cdigo de un artculo a partir de su nombre.
Escribir una funcin que permita a un usuario modificar un artculo cuyo cdigo se ha pasado por
parmetro.
Escribir una funcin de borrado de un artculo de la base de datos o bien por su cdigo o bien por su
nombre.
Ejercicio 15

Retomar el ejercicio 14, pero esta vez con la posibilidad de cargar la base de datos en el programa:

1) Aadir:

Una funcin de carga de la base de datos.


Una funcin de guardado de la base de datos.
2) Modificar el resto de las funciones consecuentemente.
Funciones recursivas
1. Qu es la recursividad?

A un concepto se le denomina recursivo cuando se contiene a s mismo en parte o si forma parte de su


propia definicin. La recursividad se sustenta en el razonamiento por recurrencia. Tpicamente se trata
de una serie cuyo trmino general se expresa a partir de trminos anteriores. Por ejemplo, el factorial de
un determinado nmero N es el producto de los nmeros enteros menores o iguales a este nmero N.
Se denota N! y por definicin el factorial de 0 es 1, lo que da:

0! = 1

1! = 1

2! = 1*2

3! = 1*2*3

()

N! = 1*2*3 .*(N-1)*N

La notacin general es:

N! = 1 si N = 0

N! = N*(N-1)! si N > 0

y claramente se puede ver que el factorial de N se define en funcin de s mismo (N-1)!; por lo tanto, es
un proceso recursivo.

2. Una funcin recursiva bsica

Una funcin recursiva es simplemente una funcin que se llama a s misma. Debido a ello, un
algoritmo recursivo jugar con los parmetros de entrada de la funcin, que se modificarn a cada
nueva llamada de la funcin en su propio cuerpo. Por ejemplo, en una ordenacin, al inicio tenemos un
conjunto D y la recursin se realiza en subconjuntos de D hasta que no haya ms subconjuntos
posibles.

La llamada de la funcin a s misma puede ser directa, por ejemplo una funcin f() que llama a f(), o
indirecta si f() llama a g() que llama a f(). Nos dedicaremos a la recursin directa en este mdulo.

En una funcin recursiva hay tres aspectos fundamentales:

La comprobacin de parada: cmo se detiene la sucesin de llamadas?


La descomposicin de los datos: con qu criterio se realizan las llamadas sucesivas?
El apilamiento y el desapilamiento en memoria de las llamadas sucesivas. Cada llamada tiene, en
efecto, sus propias variables locales en memoria (como si se tratase de llamadas a funciones
diferentes).
A continuacin se muestra un ejemplo de funcin recursiva:
#include <stdio.h>

void muestra(int n)
{
if (n>0)
muestra(n-1);
printf("%d ",n);
}
int main()
{
muestra(5);
return 0;
}
Qu imprime este programa?

El proceso puede explicarse as: mientras n es mayor que 0, se llama a muestra() de n-1. El proceso
completo puede descomponerse en tres etapas:

images/05ri07.png
De este modo, el programa muestra:

012345
Si se modifica ahora la funcin de la siguiente manera:

void muestra(int n)
{
printf("%d ",n); // impresin por pantalla al comienzo
if (n>0)
muestra(n-1);
}
El programa imprime entonces la serie decreciente:

543210
En efecto, la visualizacin por pantalla tiene lugar antes de la llamada recursiva, n se imprime al
principio y a continuacin se realiza la llamada con n-1.

Si printf() se invoca al comienzo y al final de la funcin:

void muestra(int n)
{
printf("%d ",n); // visualizacin al comienzo
if (n>0)
muestra(n-1);
printf("%d ",n); // y al final
}
La serie que aparece es:

543210012345
A travs de este ejemplo tan simple, el enlace entre iteracin (bucle) y recursividad es evidente. La
versin iterativa de la funcin muestra() es:

void muestra(int n)
{
int i;
for (i=0; i<=n; i++)
printf("%d ",i);
}
Un proceso recursivo siempre puede tericamente reemplazarse por un proceso iterativo asociado a la
gestin de una pila. Sin embargo, ambos procesos no son idnticos y existen funciones autnticamente
recursivas, que son muy complicadas de reemplazar por procesos iterativos. Hay diferencias en la
rapidez de ejecucin. Segn el caso, es ms rpida una u otra y no se puede generalizar.

3. Pila de llamadas y desbordamiento

Cuando se realiza una llamada a una funcin, se asigna un espacio de memoria para todos los datos de
la funcin. Este espacio se libera en el momento en que se pone fin a la ejecucin de la funcin, cuando
se vuelve al contexto de la llamada. Todas las funciones se almacenan en una pila en memoria. Una pila
funciona como una pila de platos cuando se lavan: los platos que se deben lavar se van apilando unos
encima de otros. A medida que van llegando, hay una persona que los lava comenzando por el de ms
arriba, lo que hace que el ltimo en llegar sea el primero en lavarse, Last In First Out (LIFO):

images/05ri08.png
De manera similar, cada funcin llamada se apila y se desapila al final de su ejecucin. De este modo,
cuando un programa est activo, la funcin main() se encuentra bajo en la pila y en la cima se
encuentra la funcin que se est ejecutando. La pila de las llamadas a funciones (call stack en ingls)
tiene un tamao mximo que es imposible sobrepasar para la mquina. La llamada de demasiadas
funciones provoca el desbordamiento de pila, que en general pone fin a la ejecucin con un mensaje de
error del sistema operativo.

Es un problema que puede pasar con funciones recursivas cuando el nmero de llamadas recursivas es
muy elevado. El siguiente programa hace una demostracin. La funcin recursiva desborda() no tiene
comprobacin de parada. Por lo tanto, se invocar indefinidamente hasta que el sistema ponga fin a la
ejecucin del programa debido a un desbordamiento de pila. Las llamadas se cuentan mediante una
variable pasada por referencia y el nmero de llamadas se imprime al principio:

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

void desborda(unsigned int*cmpt)


{
printf("%d\n",(*cmpt)++);
desborda(cmpt);
}

int main()
{
int cmpt=0
desborda(&cmpt);
return 0;
}
En mi equipo constato que hay 130160 llamadas antes del desbordamiento.

El nmero de llamadas recursivas de una funcin recursiva en su ejecucin da lo que se denomina la


profundidad de la recursin. Es la diferencia en un momento determinado entre el nmero de llamadas
desde el inicio de la recursin y el nmero de retornos realizados.

4. Devolver un valor

La gestin del mecanismo de retorno con una funcin recursiva requiere un poco de atencin. Sea una
funcin que suma todos los nmeros de 0 a n y devuelve el resultado. Por ejemplo, con n=5 el objetivo
es obtener 0+1+2+3+4+5 y devolver 15.

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

int add(int n)
{
int res=n;
if (n>0){
res+=add(n-1);
}
return res;
}

int main()
{
printf("res=%d",add(5));
return 0;
}
La escritura puede simplificarse. En efecto, la variable res finalmente solo copia la variable n, esta
puede aumentarse con el valor anterior a cada retorno y finalmente dar el resultado deseado:

int add(int n)
{
if (n>0){
n+=add(n-1);
}
return n;
}
Esta escritura puede afinarse ms an utilizando el operador condicional:

(comprobacin) ? valor si verdadero : valor si falso ;


int add(int n)
{
return (n>0) ? n + add(n-1) : 0;
}
Otro ejemplo: contar el nmero de restas de un valor a a un valor n, mientras n permanezca positivo (lo
que es una divisin entera de n entre a).

int contar(int n, int a)


{
if (n<a)
return 0;
return 1 + contar(n-a, a);
}
lo que se puede reducir a:

int cmpt(int n, int a)


{
contar n < a ? 0 : 1 + contar(n-a, a);
}
El resultado de la funcin contar() se obtiene en el desapilamiento; en cada desapilamiento se aade 1 a
la variable devuelta.

Pero el recuento de esta funcin podra tambin hacerse al apilamiento, mediante una variable
adicional, ya sea una variable global, una variable pasada por referencia o una variable local declarada
esttica.

Versin con variable global:

int res=0;

void contar(int n, int a)


{
if (n>=a){
res++;
contar(n-a,a);
}
}
Versin con paso por referencia:

void contar(int n, int a,int*res)


{
if (n>=a){
(*res)++;
contar(n-a,a,res);
}
}
Versin con variable esttica:

int contar(int n, int a)


{
static int res=0;
if (n>=a){
res++;
contar(n-a,a);
}
return res;
}
5. Representacin y anlisis de funcionamiento

A partir del momento en que hay varias llamadas recursivas a la funcin en el cuerpo de la funcin, se
vuelve difcil representar claramente su funcionamiento. Por ejemplo, qu imprime el programa
siguiente?

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

void p(int n)
{
if (n>0){
p(n-2);
printf("%3d",n);
p(n-1);
}
}

int main()
{
p(4);
return 0;
}
Hay dos formas de analizar una funcin de este tipo: el anlisis descendiente y el anlisis ascendiente.

a. Anlisis descendiente

Se comienza por estudiar la primera llamada que ha tenido lugar en el main(): p(4).

Con el valor 4, se ejecuta en p():

p(2); printf("%3d",4); p(3);


Es seguro que el nmero 4 se imprimir en algn momento. Hagamos lo mismo para las llamadas p(2)
y p(3) y las sucesivas... la representacin grfica adquirir el aspecto de un rbol:

images/05ri09.png
Las llamadas tienen lugar si n>0. Por lo tanto, no hay que llamar para n=-1 ni para n=0. Cada vez que
no hay ms llamadas recursivas, la serie de instrucciones de la llamada actual se ejecuta, es decir, se
muestra el valor de n. El rbol se debe leer de izquierda a derecha: muestra n despus de cada llamada a
la izquierda de p(0) o p(-1), lo que da:

2141321
Con este mtodo se ve que algunos subrboles se repiten; son las llamadas p(2) y p(1).
b. Anlisis ascendiente

Esta vez vamos a partir del caso inverso, desde abajo con la ltima llamada posible cuando n=1, e
iremos subiendo cada vez con el valor inmediatamente superior, con lo que se obtiene:

p(1): 1

p(2): p(0) 2 p(1), es decir, 2 1

p(3): p(1) 3 p(2), es decir, 1 3 2 1

p(4): p(2) 4 p(3), es decir, 2 1 4 1 3 2 1

6. Elegir entre iterativo o recursivo

Sea la funcin recursiva que permite sumar nmeros enteros al usuario:

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

void suma(int*s)
{
int n;
if (scanf("%d",&n)==1){
*s+=n;
suma(s);
}
}

int main()
{
int res=0;
printf("introducir nmeros, una letra para finalizar:\n");
suma(&res);
printf("resultado: %d\n",res);
return 0;
}
La llamada recursiva se realiza al final de la funcin; es muy fcil en este caso tener un proceso
iterativo y la recursividad realmente no tiene mucho sentido:

void suma(int*s)
{
int n;
while (scanf("%d",&n)==1)
*s+=n;
}
De forma general, la recursin se requiere cuando la funcin recursiva se llama a s misma varias veces
(como la funcin p() de la seccin Representacin y anlisis de funcionamiento). Por ejemplo, cuando
se trata de recorrer figuras complejas como rboles, grafos, o incluso buscar un rea en una matriz
partiendo de todas las direcciones, como tiene que hacer un desminador, por ejemplo.

Ejemplos clsicos de funciones recursivas

A continuacin se muestra un conjunto de ejemplos a menudo citados y muy interesantes para dominar
la recursividad y la escritura de funciones recursivas.

1. Clculos

a. Mostrar las cifras de un entero

Sea un entero de valor 45671. Se trata de mostrar sucesivamente los caracteres 4, 5, 6, 7, 1, y no el


valor del entero. Para ello, mientras el resultado sea superior a 0, se hace el mdulo 10 lo que da la
ltima cifra, se divide entre 10 y se vuelve a comenzar. En iterativo da:

void cifrasIter(int val)


{
while (val>0){
printf("%d_", val%10);
val/=10;
}
}
La versin recursiva es muy simple; basta con reemplazar el bucle por una llamada recursiva:

void cifras(int val)


{
if (val>0){
printf("%d_", val%10);
cifras(val/10);
}
}
b. Producto factorial

Un producto factorial es: n! = 1*2*3*...*n. Su definicin es:

n! = 1 si n=0

n! = n(n-1)! si n>0

Se trata de una funcin recurrente y se itera mientras n>0. A continuacin se muestra una versin de la
funcin iterativa:

int factIter (int n)


{
int res=1;
while (n>1)
res*=n--; // atencin decremento despus de la multiplicacin
return res;
}
En cierta forma, la versin recursiva puede parecer ms fcil y ms prxima a la definicin del clculo.
Si n es igual a 0, la funcin devuelve 1; si no, la funcin devuelve n*(n-1), lo que da:

int fact(int n)
{
if (n==0)
return 1;
else
return n*fact(n-1);
}
Se puede reducir a una escritura ms concisa con el operador condicional:

int fact(int n)
{
return n>1 ? n*fact(n-1) : 1;
}
c. Sucesin de Fibonacci

Se trata de una sucesin establecida de la siguiente manera:

Para n >= 0

fib(n) = n
fib(n) = fib(n-2) + fib(n-1)
si n = 0 o n = 1
si n > 1
Por ejemplo, para los valores de 0 a 8 tenemos:

n
0
1
2
3
4
5
6
7
8
fib(n)
0
1
1
2
3
5
8
13
21
A continuacin se muestra una versin iterativa de la funcin:

int fibIter(int n)
{
int i,res,f1,f2;

if (n<=1)
res=n;
else{
f2=0;
f1=1;
for (i=2; i<=n; i++){
res=f2+f1;
f2=f1;
f1=res;
}
}
return res;
}
La versin recursiva es ms natural:

int fib(int n)
{
if (n<=1)
return n;
else
return fib(n-2)+fib(n-1);
}
El operador condicional puede hacer la escritura ms concisa:

int fib(int n)
{
return n<=1 ? n : fib(n-2)+fib(n-1);
}
d. Cambio de base aritmtica de un nmero

De base 10 a base B para un nmero positivo y con 0 < B <= 16

En cada paso se realiza al nmero en base 10 un mdulo B y a continuacin se divide el nmero en


base 10 entre la base B. Para la versin iterativa, el resultado obtenido comienza por la unidad ms
pequea: se da al revs. Por ejemplo, para 160 en base 10 pasado en base 8, se obtienen sucesivamente
las cifras 0, 4 y 2 y hay que empezar leyendo el resultado por el final: 240. La solucin consiste en
apilar el resultado obtenido en cada etapa del clculo y, cuando el clculo termina, desapilar para
obtener en el orden adecuado cada una de las cifras.

El mtodo sera el siguiente:

void conversionIter(int val, int base)


{
int pila[100];
int cima=0;
while (val>0){
pila[cima++] = val % base;
val /= base;
}
for (--cima; cima>=0; cima--)
if(pila[cima]<=9)
printf("%d",pila[cima]);
else
printf("%c",pila[cima]-10+A);
}
La versin recursiva evita la gestin de la pila porque ya se apilan las llamadas sucesivas:

void conversion (int val, int base)


{
static char cifras[]="0123456789ABCDEF"; // de 0 a 15

if (val/base != 0)
conversion(val/base,base);
printf("%c",cifras[val%base]); // valor entre 0 y 15 incluidos
}
De base B a base 10 para un nmero positivo y sin letra para el hexadecimal

El principio es utilizar la notacin extendida que permite descomponer un nmero. Por ejemplo, 123 en
base 8 da en base 10 la suma:

1*82 + 2*81 + 3*80 = 83


La versin iterativa calcula cada paso comenzando por la derecha y los aade uno a uno. De este modo,
3*80 se obtiene haciendo (123%10)*1 (un nmero elevado a 0 siempre vale 1).

A continuacin:

2*81 se obtiene con ((123/10)%10)*8


1*82 se obtiene con ((12/10)%10)*8*8
En cada iteracin el nmero obtenido se aade al resultado anterior.

El principio siempre es el mismo independientemente del nmero y la base.

Para el algoritmo, lo importante es desasociar la divisin por 10 del resto obtenido con mdulo 10. Al
inicio el resultado es un mdulo 10. A continuacin, en cada iteracin el valor dado se divide entre 10
mientras sea mayor que 0. En cada iteracin tambin se calcula el mdulo 10 del nuevo valor obtenido.
La base se multiplica por s misma y el resultado se almacena a medida que se va avanzando en la
variable b. El producto en cada iteracin se aade al resultado final, con lo que se obtiene:

int convert10Iter(int val, int base)


{
int res,b,r;
b=1;
res=val%10;
while (val>0){
val=val/10;
r=val%10;
b*=base;
res+=r*b;
}
return res;
}
La versin recursiva es un poco ms concisa:

int convert10(int val, int base)


{
if (val/10 ==0)
return val%10;
else
return convert10(val/10,base)* base + val % 10;
}
e. Potencia

Para calcular xn, se multiplica n veces x por s misma, por ejemplo: 34=3*3*3*3. La funcin recursiva
correspondiente es:

int potencia(int x, int n)


{
if (n==0)
return 1;
else
return x*potencia(x,n-1);
}
o usando el operador condicional:

int potencia(int x, int n)


{
return (n==0) ? 1 :x*potencia(x,n-1);
}
El tiempo de clculo puede disminuirse calculando solo la mitad y multiplicndola por s misma; por
ejemplo, para 34 hay que hacer simplemente 32 y a continuacin 32 * 32. En caso de que el exponente
fuera impar, basta con multiplicar una vez ms por la base x, por ejemplo:

35 = 32 * 32 * 3

De este modo, para xn el clculo se limita a encontrar xn/2, multiplicarlo por s mismo y, si n es impar,
multiplicarlo una vez ms. Este principio puede ser implementado de forma recursiva de la siguiente
manera:

int potencia2(int x, int n)


{
int res;
if (n==0)
res = 1;
else{
res=potencia2(x, n/2);
if(n%2==0) // si par
res=res*res;
else
res=res*res*x;
}
return res;
}
f. MCD, algoritmo de Euclides

Sean dos nmeros enteros x e y, uno de los cuales por lo menos no es cero, mcd(x,y); es el nmero ms
grande por el que x e y son divisibles. Por ejemplo:

mcd(1000,600) = 200
mcd(-70,90) = 10
mcd(0,12) = 12
El algoritmo se basa en algunas observaciones:

1) mcd (x,y) = mcd (y,x)

2) Si d divide x e y, d divide tambin x - y, es la propiedad:


mcd (x,y) = mcd (x-y,x)

3) mcd(x,0) = x
El principio del algoritmo es ir restando del ms grande de ambos nmeros el ms pequeo. Por
ejemplo:

mcd(1900,700)
= mcd(1200,700)
= mcd(500,700)
= mcd(700,500)
= mcd(200,500)
= mcd(500,200)
= mcd(300,200)
= mcd(100,200)
= mcd(200,100)
= mcd(100,100)
= mcd(0,100)
= mcd(100,0)
= 100
// observacin 2
// observacin 2
// 1
// 2
// 1
// 2
// 1
// 2
// 2
// 2
// 1
// 3
De hecho, el resultado se obtiene cuando x e y son iguales. De este modo, mientras x e y sean
diferentes, en cada iteracin: si x es menor que y se intercambian x e y y a continuacin se resta y a x,
lo que da:

int mcd(int x, int y)


{
int t;
while (x != y){
if (x < y){
t = x;
x = y;
y = t;
}
x -= y;
}
return x;
}
Atencin: este algoritmo no tiene en cuenta los nmeros negativos ni el hecho de que x o y puedan ser
0.

Para disminuir el nmero de iteraciones, se puede utilizar el operador mdulo en vez de la resta. En
efecto, x%y es el resto de la divisin de x por y; por ejemplo, 1900%700 vale directamente 500 sin
tener que hacer 1900-700 y 1200-700.

Tambin cabe destacar que la expresin x%y es obligatoriamente inferior a y. Entonces se puede
escribir:

mcd(x,y) = mcd( x%y, y)


y a continuacin permutar, se puede escribir directamente:

mcd(x,y) = mcd( y, x%y)


y aplicar esta regla siempre que y sea diferente de 0.

Por ejemplo mcd(1900,700) solo dura 5 pasos:

mcd(1900,700)
= mcd(700,500)
= mcd(500,200)
= mcd(200,100)
= mcd(100,0)
= 100
Mientras y sea mayor que 0, se calcula m = x % y, y a continuacin se asigna y a x y m a y, lo que
genera para la versin iterativa un bucle de cuatro instrucciones:
int mcd(int x, int y)
{
int m;
while(y){
m=x%y;
x=y;
y=m;
}
return x;
}
La versin recursiva permite permutar directamente los parmetros (y pasado en x y x%y pasado en y);
de golpe la funcin resultante se simplifica:

int mcd(int x, int y)


{
if (y)
return mcd(y,x%y);
else
return x;
}
Y una sola lnea con el operador condicional:

int mcd(int x, int y)


{
return (y) ? mcd(y,x%y) : x;
}
2. Dibujos

a. Dibujo de una regla graduada: divide y vencers

Cuando se realiza una llamada recursiva, la funcin sigue siendo la misma, pero los parmetros
pasados cambian. El objetivo es dibujar una regla graduada del tipo:

images/05ri10.png
La graduacin se define por marcas regulares; en cada seccin hay una marca justo en medio de la
seccin, una marca ms pequea en cada cuarto de seccin, una marca an ms pequea en los octavos
de seccin, etc. Este ejercicio ilustra la tcnica recursiva divide y vencers usada a menudo para
problemas de optimizacin y algoritmos de ordenacin. Los datos se dividen y se tratan una sola vez
(no se pasan dos veces por el mismo sitio).

La graduacin se obtiene dibujando una raya de altura h siempre a la mitad m del segmento actual y
recursivamente dibujando una raya de altura h-1 a la mitad m/2 del segmento anterior. Se requiere una
funcin dibuja() para dibujar la raya vertical. Esta funcin depende del entorno. Recibe por parmetro
la posicin horizontal de la raya y su altura. En este ejemplo utilizamos simplemente una visualizacin
por consola del nmero correspondiente a la altura al sitio adecuado horizontalmente. Adems, cada
altura tiene su propio color para facilitar la lectura del resultado. La funcin requiere las funciones
gotoxy() y textcolor() proporcionadas en el anexo. La funcin es la siguiente:
void dibuja(int hpos, int h)
{
gotoxy(hpos,0);
textcolor(15-h);
printf("%d",h);
}
Las graduaciones de la regla se obtienen con la funcin:

void regla(int izquierda, int derecha, int altura)


{
int medio=(izquierda+derecha)/2;

if (altura>0){
trace(medio,altura);
regla(izquierda,medio,altura-1);
regla(medio,derecha,altura-1);
}
}
Si la altura no es cero, la funcin comienza a dibujar una raya de altura "altura" en medio del segmento
[izquierda, derecha] y a continuacin se invoca a s misma dos veces para "altura-1", una vez para el
segmento [izquierda, medio] y otra para el segmento [medio, derecha].

Tomemos por ejemplo un segmento de 0 a 8 y una altura de 3; al inicio realizamos la llamada:

regla( 0, 8, 3) y a continuacin:
dibuja (4,3)
regla(0,4,2)
dibuja (2,2)
regla (0,2,1)
dibuja (1,1)
regla (0,1,0)
regla (1,2,0)
regla (2,4,1)
dibuja (3,1)
regla (2,3,0)
regla (3,4,0)
regla (4,8,2)
dibuja (6,2)
regla (4,6,1)
dibuja (5,1)
regla (4,5,0)
regla (5,6,0)
regla (6,8,1)
dibuja (7,1)
regla (6,7,0)
regla (7,8,0)
Con la funcin regla() anterior, obtenemos para esta llamada:

1213121
Con las llamadas:

regla(0,16,4) obtenemos la serie: 1 2 1 3 1 2 1 4 1 2 1 3 1 2 1

regla(0,32,5) obtenemos:

1213121412131215121312141213121

Se distingue desde el punto de vista de las llamadas, as como del resultado, la forma de un rbol:

images/05ri11.png
Este rbol subyacente corresponde al rbol de llamadas. A continuacin se muestra, por ejemplo, el
rbol de llamadas para regla(0,8,3), que retoma el detalle de la reconstruccin de las llamadas
anteriores sin indicar el orden cronolgico del recorrido:

images/05ri12.png
Se trata de un rbol binario completo: de cada nodo del rbol salen dos ramas hasta sus extremidades,
llamadas hojas del rbol. El primer nodo es la raz del rbol.

El nmero total de nodos de un rbol binario completo est relacionado con su altura h: es 2h - 1.

En este caso, resulta prctico para obtener el tamao de nuestra regla en funcin de la altura que se le
ha dado: para una altura de 3, son necesarias 23-1 = 7 graduaciones, para una altura de 4 necesitamos
24-1 = 15 graduaciones, para una altura de 5, 25-1 = 31, etc.

El nmero total de hojas corresponde a 2h, el nmero de hojas por nivel es 2nivel con 0 para el nivel
raz.

La versin iterativa de la regla consiste en dibujar cada nivel del rbol comenzando por el de ms abajo
e irlos superponiendo uno tras otro. Por ejemplo, para regla(0,16,4) obtenemos:

images/05ri13.png
Los valores de graduacin (1,2,3,4) de cada nueva fila reemplazan los de la anterior, con lo que se
obtiene en una sola fila:

images/05ri14.png
void reglaIter(int g, int d, int h)
{
int i,paso,j,m;
for (i=1,paso=1,m=(g+d); i<=h; i++,paso*=2,m/=2)
for (j=0; j<m;j++)
trace((paso+paso*j)-1,i); // -1 para empezar al borde
}
b. Dibujar circunferencias

Con la recursin, a menudo es difcil separar lo muy fcil de lo muy difcil, y una descripcin recursiva
aparentemente elemental puede provocar la aparicin de figuras muy complejas, especialmente en el
dominio grfico.
Por ejemplo, ahora queremos, de forma similar a la regla graduada, dibujar circunferencias en vez de
rayas (que hemos reemplazado por nmeros anteriormente).

La funcin tiene como parmetros una posicin (x, y) y un radio r. Con estos parmetros dibuja una
circunferencia y a continuacin se llama a s misma recursivamente dos veces, una en (x-r/2,y) y otra
en (x+r/2). La comprobacin de parada revisa si r es menor que un valor dado. De este modo,
obtenemos la funcin:

void dosCircunferencias(int,x, int y, int r)


{
int r2=r/2;

if (r<5){
dibuja_circunferencia(x,y,r);
dosCircunferencias(x-r2,y,r2);
dosCircunferencias(x+r2,y,r2);
}
}
Esta vez es mejor pasar a modo grfico y utilizar un entorno como allegro o SDL, que proporcionan
funciones de diseo. A continuacin se muestra un ejemplo de la funcin recCircunferencias() definida
ms adelante. Esta funcin utiliza las funciones de la librera Allegro makecol() para el color y
circlefill() para dibujar el crculo. En este ejemplo, la llamada a la funcin se configura con la posicin
x, y al centro de la pantalla y un radio de 150 pxeles de la forma siguiente:

recCircunferencias(SCREEN_W/2,SCREEN_H/2,150);
images/05RI01.png
A continuacin se muestra la funcin completa:

void recCircunferencias(int x, int y, int r)


{
int r2=r/2;
int color;
if (r>5){
color=makecol(rand()%256,rand()%128, rand()%64);
circlefill(screen,x,y,r,color);
recCircunferencias(x-r2, y, r2);
recCircunferencias(x+r2, y, r2);
}
}
La funcin makecol() produce un color aleatorio y la funcin circlefill() dibuja un crculo (ver la
documentacin de Allegro o SDL para probar).

c. Dibujar cuadrados

Siguiendo el mismo principio que en los casos anteriores, dibujaremos cuadrados. Al empezar, se
dibuja un cuadrado a partir de una posicin x,y y de una mitad de lado. La funcin tiene tres
parmetros: la posicin x,y y el medio lado c. Se llama a s misma cuatro veces, una para cada esquina,
con un lado dividido por dos cada vez. La recursin se produce hasta que el lado sea menor que un
valor umbral:

void cuadrado(int x,int y,int c)


{
int cc=c/2
if (c>2){
// aqu dibujar el cuadrado de centro x,y y de medio lado c
cuadrado(x-c,y-c,cc);
cuadrado(x+c,y-c,cc);
cuadrado(x+c,y+c,cc);
cuadrado(x-c,y+c,cc);
}
}
Se pueden obtener figuras distintas segn la eleccin de colores y si se usa un color de relleno o no. El
primer ejemplo que se muestra a continuacin se obtiene con cuadrados con relleno blanco sobre fondo
negro, y el segundo con solo el borde de los cuadrados en negro sobre el fondo blanco:

images/05RI02.png
images/05RI03.png
La funcin que se muestra a continuacin utiliza la funcin rect, que dibuja el contorno de un
rectngulo del entorno allegro.

void cuadrado(int x, int y, int c)


{
int cc=c/2;
int color;
if (c>2){
color=makecol(rand()%256,rand()%128, rand()%64);
rect(screen,x-c,y-c,x+c,y+c,color);
cuadrado(x-c,y-c,cc);
cuadrado(x+c,y-c,cc);
cuadrado(x+c,y+c,cc);
cuadrado(x-c,y+c,cc);
}
}
d. Dibujar un rbol

El objetivo es obtener un dibujo como el que se muestra a continuacin:

images/05RI04.png
Para conseguirlo, se dibuja un segmento y, recursivamente, al final de cada segmento se dibujan n
segmentos nuevos. Cada segmento se dibuja a partir de una posicin x,y, de una longitud y de un
ngulo determinados, con lo que conseguimos una posicin rx, ry de llegada del segmento:

images/05ri15.png
La posicin de llegada del segmento, el final de una rama del rbol, ser la posicin de partida de otras
varias ramas. Por lo tanto, es importante obtener esta posicin de llegada del segmento para dibujar las
siguientes. Para una posicin x,y, las coordenadas del punto rx,ry se obtienen aplicando las frmulas
matemticas:
rx = x + longitud * coseno del ngulo
ry = y + longitud * seno del ngulo
A continuacin, la funcin que permite dibujar un segmento sin color y que requiere una funcin que
dibuje una raya proporcionada por el entorno de desarrollo grfico:

#define PI 3.1416
#define GRADO 2*PI/360.0
void segmento(int l,int x,int y,int angulo, int*rx,int*ry)
{
int i;
// obtencin de las coordenadas del punto de llegada
*rx = x + l*cos(angulo*GRADO);
*ry = y - l*sin(angulo*GRADO);

// varias rayas para recrear el espesor de las ramas


// en funcin de la longitud
for (i=0; i<l/15; i++)
dibujarRaya(x+i,y,*rx+i,*ry);
}
La posicin de llegada del segmento se transmite al contexto de la llamada mediante un paso por
referencia a los parmetros punteros int*rx, int*ry.

A continuacin se muestra la misma funcin adaptada al entorno de programacin grfica allegro:

void segmento(int l,int x,int y,int angulo, int*rx,int*ry)


{
int i,color;
*rx = x + l*cos(angulo*GRADO);
*ry = y - l*sin(angulo*GRADO);
color=makecol(rand()%32,rand()%256,rand()%128);
//color=makecol(0,0,0); // para color negro
for (i=0; i<l/15; i++)
line(screen,x+i,y,*rx+i,*ry,color);
}
Una vez se ha resuelto cmo dibujar el segmento, falta lo esencial: dibujar el rbol. Bsicamente, esta
nueva funcin dibujaArbol() dibuja un segmento de una altura dada y a partir de una posicin x,y y
segn un ngulo dados. Se obtiene la posicin de llegada del segmento, se disminuye la altura y, si la
altura es siempre mayor que un pxel, se llama recursivamente a s misma n veces. Si n es fijo, el
nmero de ramas ser siempre el mismo. Pero se puede introducir un valor aleatorio para que no haya
siempre el mismo nmero de ramas. Falta la cuestin del ngulo para dibujar un nuevo segmento en
cada nodo del rbol. La idea es repartir las direcciones entre -/2 y /2 o un abanico de 180 grados
dividido segn las ramas que hay que dibujar e inclinado cada vez 90 grados. Para la funcin base
siguiente, la altura h permanece fija y en cada paso disminuye un tercio. El nmero de ramas tambin
es fijo; en cada nodo siempre hay el mismo nmero de ramas.

La funcin es la siguiente:

void dibujaArbol(int h, int x,int y, int angulo)


{
int rx,ry,n,i,ev;

segmento(h,x,y,angulo,&rx,&ry);
h=h*2/3;
if (h>1){
n=3;
ev=180/n;
for (i=1; i<=n; i++)
dibujaArbol(h,rx,ry,angulo-90-ev/2+i*ev);
}
}
images/05RI05.png
En la siguiente versin, se han aadido desequilibrios para introducir asimetras: es el algoritmo para
obtener la primera imagen del rbol anterior:

void dibujaArbol(int h, int x,int y, int angulo)


{
int rx,ry,n,i,ev;
// asimetra aleatoria para el tamao de los segmentos
// (entre 0 y 10% ms de h)
h= h+0.1*(rand()%h);

segmento(h,x,y,angulo,&rx,&ry);
h=h*2/3;
if (h>1){
// el nmero de ramas est comprendido entre 1 y 7
n=1+rand()%8;
ev=180/n;
for (i=1; i<=n; i++)
dibujaArbol(h,rx,ry,angulo-90-ev/2+i*ev);
}
}
3. Crear juegos

a. Encontrar un camino en un laberinto

El laberinto est situado en un rectngulo con una apertura en el borde de salida. Una casilla del
laberinto corresponde al tesoro que hay que alcanzar. No hay ciclos en los caminos del laberinto.

images/05RI06.png
El objetivo es realizar un programa capaz de encontrar el tesoro buscando un camino a travs del
laberinto. Vamos a trabajar en modo consola para probarlo.

El laberinto

El rectngulo en el que se encuentra el laberinto es una matriz de enteros de TX por TY declarada en


global e inicializada con el diseo de un laberinto en el programa:
#define TX 26
#define TY 20

static int MAT[TY][TX]={

{0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,1,1,1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0},
{0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0},
{0,1,1,0,0,1,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0},
{0,1,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,0,0,1,0},
{0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,1,1,0},
{0,1,0,0,1,1,1,1,0,0,0,1,0,0,1,0,1,0,1,1,0,1,0,1,0,0},
{0,1,1,1,1,0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,0,0},
{0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,0,0,0,0,1,0,1,0,1,0,0},
{0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1,0},
{0,0,0,0,0,0,0,1,0,0,0,1,1,1,1,1,0,0,0,0,1,0,0,0,1,0},
{0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,1,0},
{0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0,1,1,1,0,1,0},
{0,1,1,1,1,0,0,0,0,0,0,0,1,0,2,0,0,1,1,1,0,0,1,0,1,0},
{0,0,0,0,1,0,0,1,1,1,1,1,1,0,1,0,0,0,0,1,0,0,1,1,1,0},
{0,0,0,0,1,0,0,1,0,0,0,0,1,1,1,1,0,0,1,1,1,1,1,0,0,0},
{0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,0},
{0,0,1,1,1,1,1,1,0,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0},
{0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} };
En la matriz, las posiciones con valor 0 corresponden a paredes, las posiciones con valor 1 a un
camino, la posicin con valor 4 es la salida y la posicin con valor 2 es el punto de llegada. Estos
nmeros nos permitirn determinar los colores para mostrar el laberinto, pero tambin el trayecto
realizado. El objetivo que hay que alcanzar y los muros se identifican con dos macros:

#define OBJETIVO 2 // para el color del tesoro


#define MURO 0 // para el color del muro
La matriz del laberinto se utiliza siempre como variable global en el resto del programa y nunca es un
parmetro de funcin.

Visualizar el laberinto

Para mostrar el laberinto en modo consola, utilizamos tres funciones proporcionadas por el entorno:
gotoxy() para desplazar el cursor en modo escritura a una posicin x,y de la ventana de la consola,
textcolor() para obtener un color entre los 16 posibles y putchar() para mostrar un carcter en la
posicin del cursor en la ventana de consola. gotoxy() y textcolor() se facilitan en el anexos, y putchar()
se encuentra en la librera estndar stdio.h.

La primera etapa consiste en mostrar un carcter en una posicin determinada y de un color


determinado. El carcter mostrado ser siempre un espacio; este carcter puede adquirir el color de
fondo (sin ninguna letra por encima). En modo consola el color se codifica en 4 bits, en los 4 primeros
bits del byte se encuentra el color de la letra y en los 4 siguientes el color de fondo. Para tener un color
de fondo entre 0 y 15, hay que desplazar el valor elegido entre 0 y 15 4 bits a la izquierda. A
continuacin se muestra la funcin de visualizacin de una posicin:
void visualizarPos(int x, int y, int color)
{
gotoxy(x,y);
textcolor( color<<4);
putchar( );
}
A continuacin, falta llamar a esta funcin para cada posicin de la matriz con objeto de dibujarla en la
ventana de consola. Cada posicin de la matriz contiene su color: 0 para negro, 1 para azul oscuro, 2
para verde oscuro y 4 para rojo oscuro. A continuacin se muestra la funcin de visualizacin del
laberinto:

void visualizarLab()
{
int x,y,color;
for (y=0; y<TY; y++)
for(x=0; x<TX; x++)
visualizarPos(x,y,MAT[y][x]);
}
Bsqueda del camino

Para que el camino se muestre progresivamente, debemos ralentizar la ejecucin. Para ello, se muestra
a continuacin una pequea funcin de espera cuya duracin se pasa por parmetro (en milisegundos,
pero esto depende de cada entorno):

void esperar(int duracion)


{
int start=clock();
while (clock()<start+duracion){}
}
Las direcciones posibles para la bsqueda son 0 para norte, 1 para este, 2 para sur y 3 para oeste. El
objetivo es encontrar la casilla OBJETIVO, colorear la bsqueda realizada y a continuacin el camino
que se ha encontrado. Con todo ello se obtiene en modo consola la siguiente funcin recursiva:

int camino (int x,int y,int olddir)


{
int i,nx,ny;

// 1
if (MAT[y][x]==OBJETIVO)
return 1;
// 2
if (MAT[y][x]!=MURO){
// 3
esperar(40);
visualizarPos(x,y,AZULCLARO);
// 4
for (i=0;i<4;i++)
if(i!= (olddir+2)%4 ){
// 5
nx=x+(i%2)*(2-i);
ny=y+((i+1)%2)*(i-1);
// 6
if (camino(nx,ny,i)){
// 7
esperar(45);
visualizarPos(x,y,ROJO);
return 1;
}
}
}
return 0;
}
(1) Es la comprobacin de parada de la funcin recursiva cuando el objetivo se ha conseguido:
devuelve 1.

(2) Si no, la posicin x,y debe estar sobre un camino; si est sobre un muro, no se realiza la llamada
recursiva y se devuelve 0.

(3) Si est sobre un camino, visualizar la posicin en azul claro. La funcin espera() provoca una
ralentizacin para dejar que la animacin de bsqueda sea visible (si no sera casi instantnea).

(4) A continuacin, salir en las 4 direcciones para continuar con la bsqueda. Sin embargo, hay que
evitar volver hacia atrs, es decir, evitar ir a la direccin de donde se vena (si se iba hacia el sur, hay
que evitar justo despus ir hacia el norte). Tambin cabe destacar la importancia del orden dado para la
bsqueda. En este algoritmo se prueba siempre en el mismo orden norte-este-sur-oeste. El simple hecho
de invertir este orden y seguir oeste-sur-este-norte genera una bsqueda muy diferente. Lo ideal es
tener siempre un orden diferente, es decir, una combinacin aleatoria de las cifras 0, 1, 2 y 3 en cada
iteracin. Este punto se explica un poco ms adelante con la creacin del laberinto.

(5) Aqu se trata de adquirir las nuevas coordenadas nx,ny en funcin de la direccin i. Cabe la
posibilidad de usar un switch y proceder caso a caso, pero, para una determinada direccin i, la frmula
(i%2)*(2-i) da -1, 0 o 1 para sumarse a la x y la frmula ((i+1)%2)*(i-1) da -1,0 o 1 para sumarse a la y.

(6) Llamada recursiva a la funcin camino() para la nueva posicin nx,ny precisando la direccin
tomada (lo que permite en 4 evitar el retorno hacia atrs). La funcin devuelve 0 si no se ha encontrado
nada y 1 si se ha encontrado el objetivo.

(7) Si el objetivo se ha alcanzado, la posicin encontrada pasa a estar de color rojo y la funcin
devuelve 1, de tal manera que el camino que ha llevado a la solucin pasa progresivamente a rojo desde
el final al punto de partida, a medida que se van desapilando las llamadas a la funcin involucradas.

b. Crear un laberinto

El principio para la creacin de un laberinto sin ciclos en los caminos es construyendo al inicio un
bloque de muros, es decir, una matriz de 0 (MURO). Se construye un camino de forma que nunca se
cruzar el camino ya construido. Para ello, se avanza en la galera destruyendo el muro de dos en dos
casillas en una de las cuatro direcciones posibles elegidas al azar. Cada vez las casillas adquieren el
valor 1, definido por la macro CAMINO. Solo las casillas en MURO son utilizables, lo que evita que se
pueda volver hacia atrs o cruzar un camino ya construido. La funcin es recursiva y, en cada etapa, la
funcin se llama a s misma cuatro veces, una para cada direccin. Entonces, lo importante es que las
llamadas no sean siempre en el mismo orden, de forma que el camino del laberinto sea impredecible.
Para ello, antes de empezar la funcin de creacin del laberinto, se explica cmo se obtienen series de
direcciones siempre diferentes.

Series de direcciones siempre diferentes

Cada serie de direcciones (0 para norte, 1 para este, 2 para sur y 3 para oeste) se almacena en una tabla
de cuatro enteros. Al inicio, se ponen en el orden de 0 a 3 y a continuacin se mezclan un poco, como si
fueran cartas. Obtendremos el siguiente mtodo:

void inicializa_dir(int d[4])


{
int i,i1,i2,tmp;
for (i=0; i<4; i++)
d[i]=i;

for (i=0; i<50;i++){


i1=rand()%4;
i2=rand()%4;
if(i1!=i2){
tmp=d[i1];
d[i1]=d[i2];
d[i2]=tmp;
}
}
}
El laberinto

La funcin tiene como parmetro la posicin x,y actual en el laberinto. Esta posicin se supone
candidata para formar parte del camino. Se empieza por convertirla en CAMINO. A partir de este
punto, una serie de direcciones se almacena en una tabla de cuatro posiciones con la funcin
inicializa_dir(). A continuacin, en un bucle for, un switch() se realiza sobre cada una de las posiciones
de la tabla. Para cada direccin se mirar a 2 unidades ms lejos (+ o -2 segn la direccin y en x o en
y) si es MURO, comprobando previamente que se sigue estando en la matriz. Cuando se validan ambas
condiciones, se reemplaza el MURO por el CAMINO en la casilla adyacente en la direccin dada y la
funcin se invoca recursivamente a partir de la siguiente posicin obtenida (+ o -2 segn la direccin y
en x o en y), con lo que se obtiene:

void generaLab(int x,int y)


{
int d[4];
int i;

MAT[y][x]=CAMINO;
inicializa_dir(d);
for (i=0; i<4; i++)
switch(d[i]){
case 0:
if (y>1 && MAT[y-2][x]== MURO){
MAT[y-1][x]= CAMINO;
generaLab(x,y-2);
}
break;
case 1:
if (x<TX-2 && MAT[y][x+2]== MURO){
MAT[y][x+1]= CAMINO;
generaLab(x+2,y);
}
break;
case 2:
if (y<TY-2 && MAT[y+2][x]== MURO){
MAT[y+1][x]= CAMINO;
generaLab(x,y+2);
}
break;
case 3:
if (x>1 && MAT[y][x-2]== MURO){
MAT[y][x-1]= CAMINO;
generaLab(x-2,y);
}
break;
}
}
4. Torres de Hani

Es un pequeo juego de reflexin, tpico ejemplo acadmico de recursividad. Al inicio, se tienen tres
pilares con discos de radios decrecientes apilados en el primer pilar. El objetivo es obtener la misma
pila en el tercer pilar desplazando uno a uno cada disco, de forma que nunca un disco est encima de
otro disco ms pequeo. Al comienzo la situacin es la siguiente:

images/05ri16.png
y se trata de llegar a:

images/05ri17.png
Hay que utilizar el pilar intermediario alternativamente con los dos otros para que nunca un disco est
encima de otro ms pequeo.

El juego puede resolverse con n discos y siempre tres pilares. Se trata de escribir el programa que
indique los decrementos sucesivos que hay que realizar... El algoritmo de las torres de Hani es
bastante fascinante. Es una funcin recursiva que tiene cuatro parmetros: el nmero de discos n, el
pilar inicial d, el intermediario i y el final a. Se pueden ver los movimientos con la siguiente funcin:

void desplazamiento(int n, int d, int i, int a)


{
if (n>0){
desplazamiento(n-1, d,a,i); // d hacia i con a intermediario

// d hacia a con i intermediario


printf("ir de %d a %d\n",d,a);
desplazamiento(n-1,i,d,a); // i hacia a con d intermediario
}
}
El principio es el siguiente:

images/05ri18.png
En esta etapa:

Dos discos se han desplazado de d hacia i pasando por a como intermediario.


Falta mover el ltimo disco en d hacia a (con i como intermediario).
A continuacin hay que desplazar los dos discos en i hacia a pasando por d como intermediario.
En resumen, tenemos:

images/05ri19.png
1: llamadas recursivas de d hacia i con a como intermediario.

2: si hay un solo disco, entonces llamadas de d hacia a con i como intermediario (final de pila de
llamadas recursivas).

3: llamadas recursivas de i hacia a con d como intermediario.

La funcin puede tambin escribirse (considerando las letras D, I, A ms que los nmeros 1, 2, 3
para los pilares):

void hanoi(int n,int d, int i, int a)


{
if (n==1)
printf("ir de %c a %c\n",d,a);
else{
hanoi(n-1,d, a, i);
hanoi(1,d, i, a);
hanoi(n-1,i, d, i);
}
}
Ejemplo de llamada en el main() para n=3:

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

int main()
{
hanoi(3, D, I, A);
return 0;
}
Observacin:
Con los nmeros 0, 1, 2 como nmeros de pilares d, i, a podemos en todo momento saber el nmero del
pilar intermediario con la frmula i = 3 - d - a.

Los tres movimientos pueden escribirse entonces:

1 : d hacia i,
2 : d hacia a,
3 : i hacia a,
llamadas recursivas hasta n==1
paso de llamada recursiva n == 1
llamadas recursivas hasta n==1
con i = 3 - d - a
con i = 3 - d - a
Esta frmula es necesaria para dibujar un rbol de llamadas de la funcin.

5. Quicksort de una tabla de nmeros

Los algoritmos de ordenacin han creado un espacio de trabajo y de investigacin en el mundo de la


informtica que fue objeto de muchos descubrimientos en sus inicios. Estos algoritmos pueden ser
tiles en programacin, sistemas y diversas otras reas low level; cada vez que se necesite ordenar y
organizar datos. Ahora se han convertido en clsicos y son muy interesantes de estudiar, aunque sea
raro tenerlos que implementar. Para ilustrar el uso de divide y vencers de la recursividad, hay que
estudiar el algoritmo quicksort aplicado a una tabla de nmeros.

Sea una tabla de enteros llena de valores; el objetivo es ordenarla en orden creciente. El principio del
quicksort es particionar recursivamente la tabla y organizar la ordenacin a medida que se van tratando
cada una de las partes obtenidas.

Al inicio se toma una valor clave, que es el valor del extremo izquierdo de la particin actual. El ndice
de este valor clave es el pivote a partir del cual la particin se ha realizado. A continuacin, se recorre
la particin subiendo simultneamente desde la izquierda hasta la derecha y bajando desde la derecha
hasta la izquierda. El proceso es alternativo: se avanza una nueva posicin i hacia la derecha y despus
se avanza una nueva posicin j hacia la izquierda:
[INCOMPLETO]
Puesta en prctica: recursividad

Ejercicio 1

Mostrar con una funcin recursiva todos los nmeros entre n y n introducidos por el usuario.

Ejercicio 2

Qu imprimen los programas siguientes?

// programa 1
void f(int n)
{
if (n>0){
f (n-3);
printf("%3d\n",n);
f (n-2);
}
}

int main()
{
f(6);
return 0;
}

// programa 2
void f(int n)
{
if (n>0){
f (n/10-100);
printf("%3d\n",n);
f (n/5-200);
}
}
int main()
{
f (10000);
return 0;
}
Ejercicio 3

Reemplazar las funciones f(), g(), h() mostradas a continuacin por variantes iterativas equivalentes
(ms o menos). Comparar en cada caso la cantidad de memoria usada y el tiempo de clculo necesario
para cada una de las variantes.

void f(void)
{
if (getchar() == )
f ();
}

void g(int n)
{
int i;
if (n>0){
scanf("%d",&i);
g(n-1);
printf("%d\n",i);
}
}

int h(int n)
{
return n<0 ? 0 : (n==0 ? 1 : h(n-1) + h(n-2));
}
Ejercicio 4

Escribir una funcin que reciba como parmetro un entero y devuelva la suma de sus cifras. Comparar
dos alternativas de solucin a este problema: una recursiva y otra iterativa.

Ejercicio 5

Escribir un programa que sirva para leer una sucesin de nmeros reales en coma flotante y muestre la
suma de los n primeros elementos de la serie (con n=1, 3, 6, 10, 15, etc.). Cualquier carcter no
numrico se interpretar como el seal de parada de esta serie de nmeros.

Ejercicio 6

Escribir un programa que sirva para leer una cifra decimal d. Mostrar cada uno de los enteros positivos
x menores que 100 que tienen d para x o para x2.

Ejercicio 7

Escribir una funcin recursiva que a partir de un entero n entrado por el usuario calcule la suma n + n-1
+ n-2... hasta n=0.

Ejercicio 8

Escribir una funcin recursiva que a partir de un entero n entrado por el usuario calcule la suma un = 1
+ 24 + 34 + 44 + + n4.

Ejercicio 9

De forma parecida a la sucesin de Fibonacci, escribir una funcin recursiva para calcular la siguiente
serie a partir de un n entrado por el usuario: f(n) = n-4 + n-3 + n-2 + n-1.

Ejercicio 10
De forma parecida al modelo del clculo factorial, escribir una funcin recursiva que calcule la
siguiente serie para un nmero n entrado por el usuario: f(n) = 2*n*n-1... para n>0.

Ejercicio 11: strlen recursivo

Escribir una versin recursiva de la funcin strlen() que devuelve la longitud de una cadena de
caracteres pasada por parmetro.

Ejercicio 12: detector de palndromos

Un palndromo es una palabra o frase que se lee igual del derecho que del revs (sin tener en cuenta los
espacios). Por ejemplo: radar, kayak, abccba o una frase Se van sus naves.. El usuario
introduce la palabra o frase y una funcin recursiva devuelve si son o no un palndromo la palabra o
frase entradas por el usuario.

Ejercicio 13: recorrido sistemtico de una matriz

Sea una matriz MAT de enteros de TX por TY; implementar el programa que muestre todos los
caminos directos posibles que partan de MAT[0][0] y lleguen a MAT[TY-1][TX-1] sin volver nunca
hacia atrs o hacer ciclos. Es decir, en el esquema, todos los trayectos que pueden ir directamente
avanzando hacia la derecha o bajando de la casilla gris de arriba a la izquierda a la celda de abajo a la
derecha:

images/05ri25.png
La visualizacin puede ser o bien grfica con allegro o conio, o en texto, siendo el trayecto una serie de
posiciones: (0,0)-(0,1)-(0,2)-(...,...)-(TX-1, TY-1).

Ejercicio 14: recorrido aleatorio de una matriz

Sea una matriz de TX por TY: escribir una funcin recursiva de relleno aleatorio. Cada posicin solo
puede visitarse una vez y sirve de partida para visitar otras posiciones.

Realizar una visualizacin grfica utilizando las funciones gotoxy() y textcolor() (ver Anexo) o con
allegro.

Ejercicio 15: degradado de color en un cuadrado

Se dispone de una paleta de colores estilo arcoris. Se da un color al azar en cada esquina A, B, C y D
de un cuadrado dibujado por pantalla. Despus se considera cada mediana de cada costado y el centro
O.

images/05ri26.png
Se calculan tambin los colores de los puntos I, J, K, L haciendo la media de los dos colores vecinos.
Despus se comienza de nuevo con los cuatro cuadrados obtenidos y as hasta que los cuadrados tengan
lados del orden de un pxel.

Realizar un programa de prueba, a poder ser en modo grfico con allegro.


Ejercicio 16: dibujo de cuadrados

Se parte de una posicin (x,y), por ejemplo el centro de la pantalla, y con un tamao de segmento s.
Escribir dos funciones que dibujen recursivamente 8 cuadrados repartidos de forma fractal los unos
alrededor de los otros. La primera funcin dibuja sus cuadrados a partir de un tamao mximo dado en
entrada y hacia el interior. La segunda funcin dibuja los cuadrados de forma extensiva, hacia el
exterior, hasta alcanzar el tamao mximo.

Ejercicio 17: dibujar una espiral

Escribir una funcin recursiva que permita dibujar una espiral rectangular. Al inicio, se dispone de una
longitud L para el primer segmento y del tamao mximo que un segmento puede alcanzar.

Ejercicio 18: colocar frutos en el rbol

Modificar la funcin recursiva dibujaArbol() de forma que se dibuje el segmento terminal en verde y se
inserten aleatoriamente frutos de color en el rbol.

Ejercicio 19: hacer un laberinto con crecimientos de camino

Modificar la funcin de creacin del laberinto para producir laberintos con crecimiento del camino.
Cmo escribir una funcin de bsqueda para un laberinto as?

Ejercicio 20: buscaminas bsico

El juego consiste en retirar minas ocultas repartidas por un terreno. El terreno es una rejilla. El jugador
hace clic en una casilla y si hay una mina en ella ha perdido. Si hay una mina o varias en las casillas
adyacentes, este nmero de minas se mostrar en la casilla pulsada. Si no hay minas, el programa
descubre automticamente toda la zona adyacente de casillas sin minas o con el nmero de minas
adyacentes. Cada una de estas casillas indicar cuntas minas adyacentes tienen. Sirvindose de estas
indicaciones, el jugador deber deducir dnde se encuentran las minas. Existe la posibilidad de poner
banderas para neutralizar una casilla...

El objetivo es implementar una base para el juego.


El terreno es una matriz de nmeros. Las casillas sin minas se ponen a 0 o indican cuntas minas tienen
alrededor; el resto tienen un valor MINA.
Escribir una funcin que inicialice el terreno con minas y todas las posiciones adyacentes.
Escribir una funcin de bsqueda que se invocar cuando el jugador haga clic en el terreno (en alguna
casilla de la matriz subyacente).
Ejercicio 21: recursividad y pila, visualizar el desplazamiento de los anillos de las torres de Hani

El objetivo es escribir una programa para visualizar el algoritmo de las torres de Hani, para un nmero
n de anillos con n >= 3. La recursividad deber eliminarse mediante el uso de pilas, una pila por pilar.
Los anillos se representan por enteros con el tamao del anillo.

Antes de empezar:

Por qu un pilar puede representarse con una pila?


Dibujar el rbol de llamadas del procedimiento hani para n=3.
Con su rbol de llamadas, dar el orden de desplazamiento de cada anillo de tal modo que se visualice
en el printf del procedimiento hani.
A continuacin:

Retomar el procedimiento hani pero representando cada pilar con una pila. El desplazamiento visual
del printf se reemplazar por un desplazamiento efectivo de un anillo entre dos pilas (pilares).

1) Escribir una funcin que muestre el contenido de una pila de anillos (1 anillo = 1 entero).

2) Declarar tres pilas que representen los tres pilares del juego y escribir una funcin que inicialice el
juego apilando los n anillos correctamente en uno de los pilares. Escribir tambin una funcin que
muestre el estado del juego en un momento dado.

3) Escribir una funcin que desplace los n anillos sobre uno de los pilares libres respetando las reglas
del juego. Implementar de algn modo la visualizacin del movimiento de cada anillo.

da:
actual != NULL y 13 >5 entonces
anterior=actual
actual=actual->sig

actual != NULL y 13 >10 entonces


anterior=actual
actual=actual->sig

actual != NULL y 13 >11 entonces


anterior=actual
actual=actual->sig

actual != NULL y 13 >16 FALSO: fin del bucle


anterior contiene la direccin del elemento 11
el enlace se puede realizar.
Si el elemento que se inserta encuentra un elemento que tiene el mismo valor que l (por ejemplo, si
insertramos 16), se tiene entonces:

actual != NULL y 16 >16 FALSO: fin del bucle


anterior contiene la direccin del elemento anterior
el enlace se realiza insertando el nuevo antes.
Pero hay un problema si el elemento que se inserta debe colocarse el primero, ya sea porque la lista est
vaca, ya sea porque su valor es menor que el del primero. En ambos casos, hay que insertar al
comienzo de la lista y modificar la cabecera de la lista, el puntero primero, incluso si el valor que se
inserta es igual al del primero. Por lo tanto, el algoritmo se traduce en dos posibilidades:

SI primero==NULL O elemento->valor <= primero->valor ENTONCES


insertar el elemento al comienzo
SI NO
actual = anterior = primero
MIENTRAS primero != NULL Y elemento->valor < actual->valor
anterior=actual
actual=actual->sig
FIN MIENTRAS
elemento->sig = actual
anterior->sig = elemento
FINSI
Primera versin sin paso por referencia:

t_elem* insertar1(t_elem*primero,t_elem*e)
{
t_elem*n,*ant;
// si lista vaca o se aade el primero
if(primero==NULL || e->val <= primero->val){ // atencin <=
e->sig=primero;
primero=e;
}
else{ // si no buscar al anterior, insertar despus
n=ant=primero;
while (n!=NULL && e->val > n->val){
ant=n;
n=n->sig;
}
e->sig=n;
ant->sig=e;
}
return primero;
}
Segunda versin con paso por referencia:

void insertar2(t_elem**primero,t_elem*e)
{
t_elem*n,*ant;
if(*primero==NULL) // si lista vaca
*primero=e;
else if ( e->val<(*primero)->val){ // si primero, aadir al principio
e->sig=*primero;
*primero=e;
}
else{ // si no buscar su ubicacin
n=ant=*primero;
while (n!=NULL && e->val>n->val){
ant=n;
n=n->sig;
}
e->sig=n;
ant->sig=e;
}
}
Las llamadas son idnticas a las otras funciones para aadir.

f. Recorrer la lista

Para recorrer una lista enlazada, basta con que un puntero vaya apuntando sucesivamente a la direccin
de cada elemento. Al comienzo apunta a la direccin del primer elemento, que es la direccin de la
lista, y a continuacin va adquiriendo la direccin del siguiente elemento de forma sucesiva:

images/06ri19.png
Para cada elemento, se puede acceder a sus datos. En la siguiente funcin, simplemente se muestran los
valores:

void recorrer(t_elem*primero)
{
if (primero==NULL)
printf("lista vaca");
else
while(primero!=NULL){
printf("%d%s--",primero->val,primero->s);
primero=primero->sig;
}
putchar(\n);
}
Recordatorio:

Atencin al hecho de que el puntero t_elem*primero, declarado como parmetro de la funcin, es una
variable local a la funcin. No depende del contexto de llamada excepto para el valor que se le ha
pasado en el momento de la llamada. Ejemplo:

int main()
{
t_elem* primero=NULL;
t_elem* nuevo;
int i;

// fabricar una lista de 10 elementos


for (i=0; i<10; i++){
nuevo=inicializar();
anadir_comienzo2(&primero,nuevo);
}
// mostrar la lista:
recorrer(primero);

return 0;
}
g. Eliminar un elemento al comienzo de la lista

La eliminacin del primer elemento requiere actualizar correctamente el valor del puntero primero que
indica siempre el comienzo de la lista, lo que da, si la lista no est vaca:
images/06ri20.PNG
La modificacin del puntero primero debe realmente repercutir sobre el primer puntero de la lista. De
igual modo que para las funciones que aaden un elemento, se debe devolver la nueva direccin de
comienzo o usar un paso por referencia para poder comunicar esta nueva direccin de comienzo al
contexto de la llamada. La versin presentada incorpora un paso por referencia del puntero primero:

void eliminar_comienzo(t_elem**primero)
{
t_elem*n;
if(*primero!=NULL){
n=*primero;
*primero=(*primero)->sig;
free(n);
}
}
Llamada:

eliminar_comienzo(&primero);
h. Eliminar un elemento segn un criterio

El objetivo esta vez es el de eliminar un elemento cualquiera de la lista en funcin de un cierto criterio.
El criterio aqu es que el campo val del elemento sea igual a un valor dado. La bsqueda se detiene si
un elemento responde al criterio y se borra nicamente el primer elemento encontrado si hay alguno.
Como para las eliminaciones anteriores, la lista no puede estar vaca. Hay que tener en cuenta el caso
en el que el elemento que se desea borrar sea el primero de la lista, y para el resto de los elementos hay
que disponer del elemento anterior al que se elimina para poder reconstruir la cadena.

Tomemos por ejemplo 20 como valor de bsqueda. Si un elemento tiene como valor entero 20, se borra
y finaliza. El principio es el siguiente:

1) Si es el primer elemento:

images/06ri21.png
eliminar el primer elemento como se ha indicado anteriormente

2) Si no, encontrar en la lista un elemento cuyo valor sea 20:

images/06ri22.png
3) borrarlo, lo que supone tener la direccin del anterior para restablecer la cadena con el elemento
siguiente al del elemento 20:

images/06ri23.png
La funcin recibe como parmetros la direccin de comienzo de la lista y el valor entero en el que se
basa el criterio de seleccin para eliminar elementos. El primer elemento que se encuentre con ese
valor en su campo val se eliminar de la lista. Como la direccin de la lista es susceptible de ser
modificada, la funcin devuelve esta direccin para que esta posible modificacin pueda transmitirse al
contexto de la llamada. Proponemos la funcin siguiente:
t_elem* elim_un_con_crit1(t_elem*primero,int val)
{
t_elem*n,*ant;
if(primero!=NULL){
if(prem->val==val){// si primero
n=primero;
primero=primero->sig;
free(n);
}
else{ // si no ver el resto
ant=primero;
n=primero->sig;
while(n != NULL ){
if (n->val==val){
ant->sig=n->sig;
free(n);
break;
}
ant=n;
n=n->sig;
}
}
}
return primero;
}
El algoritmo se resume as:

Si la lista no est vaca


Si se trata de eliminar el primero
- la direccin del primero se guarda en n
- el primero pasa a ser el siguiente del primero
- la direccin guardada en n se libera
Si no es el primero, buscar en el resto; para ello
- el anterior es el primero
- el actual es el siguiente del primero
- Mientras n!= NULL
- si se encuentra val
- crear enlace entre ant y siguiente de n
- liberar la memoria del elemento n
- provocar la salida del bucle
- si no, continuar la bsqueda
- anterior = n y
- n= siguiente
FinMientras
al finalizar devolver la direccin del primero, que puede haber cambiado.
A continuacin se muestra una versin con paso por referencia de la cabeza de la lista, sin mecanismo
de retorno:

void elim_un_con_crit2(t_elem**primero,int val)


{
t_elem*n,*ant;
if(*primero!=NULL){
if((*primero)->val==val){// si primero
n=*primero;
*primero=(*primero)->sig;
free(n);
}
else{ // el resto
ant=*primero;
n=(*primero)->sig;
while(n != NULL ){
if (n->val==val){
ant->sig=n->sig;
free(n);
break;
}
ant=n;
n=n->sig;
}
}
}
}
i. Destruir listas

Para destruir una lista hay que recorrer la lista, obtener la direccin del elemento actual, pasar al
siguiente y desasignar la memoria del elemento anterior. Al comienzo hay que comprobar que la lista
no est vaca. La cabecera de la lista se modifica; al finalizar debe ser NULL. Dos son las posibilidades
habituales: devolver el valor NULL al final y recuperar este valor en el contexto de la llamada o pasar
el puntero primero por referencia. Versin con paso por referencia:

void destruir_lista(t_elem**primero)
{
t_elem*n;
while(*primero!=NULL){
n=*primero;
*primero=(*primero)->sig;
free(n);
}
// aqu *primero vale NULL
}
El contenido del bucle while es idntico a la funcin de eliminacin del primer elemento presentada
anteriormente; podemos escribir, siempre con el paso por referencia del puntero primero:

void destruir_lista(t_elem**primero)
{
while(*primero!=NULL)
eliminar_comienzo(primero);
// aqu *primero vale NULL
}
j. Guardar listas

Escritura en un archivo

El objetivo es guardar una lista enlazada dinmica en un archivo binario, es decir, en lenguaje C,
usando la funcin estndar fwrite(). El principio es sencillo: si la lista no est vaca, hay que abrir un
archivo en modo escritura y copiar en l en orden cada elemento de la lista, con lo que obtenemos:

// se pasa por parmetro la direccin de una lista en la invocacin


void guardar_lista(t_elem *primero)
{
FILE*f;
// si lista no vaca
if(primero!=NULL){

// abrir un archivo binario en modo escritura: sufijo b


if((f=fopen("lista.bin","wb"))!=NULL){

// recorrer la lista hasta el final


while(primero!=NULL){

// copiar cada elemento


fwrite(primero,sizeof(t_elem),1,f);

// pasar al elemento siguiente


primero=primero->sig;
}
fclose(f); // cierre del archivo
}
else
printf("error de creacin del archivo\n");
}
else
printf("no se guarda una lista vaca\n");
}
Para guardar los datos de una lista enlazada dinmica en un archivo de texto, hay que pasar por cada
elemento y copiar el contenido de cada campo al archivo. Por ejemplo, se puede estructurar un
elemento por lnea con un separador que delimite cada campo. El separador puede ser un carcter de
puntuacin, como el punto y coma. Los elementos llegan en el orden de la lista y los datos se van
copiando gradualmente.

Lectura de un archivo

Para obtener en un programa una lista enlazada dinmica previamente guardada en un archivo binario,
hay que reconstruir la lista gradualmente. En efecto, las direcciones albergadas en los punteros ya no
estn asignadas y hay que reasignar memoria para toda la lista. La funcin siguiente requiere que haya
al menos un elemento guardado en el archivo y no puede haberse guardado una lista vaca (es decir,
justo la creacin del archivo sin contenido). La funcin devuelve la direccin de comienzo de la lista,
es decir, la direccin del primer elemento, la cabecera de la lista.

Para empezar, hay que abrir el archivo en el que se encuentra la lista guardada; a continuacin:

Asignar la memoria para el primer elemento primero.


Copiar el elemento guardado en el archivo en el primer elemento recreado primero con la funcin
fread().
Despus de esta primera etapa, y mientras haya elementos por recuperar en el archivo:

Recuperar en una variable t_elem e los valores del elemento siguiente: fread(&e, ...).
Asignar el siguiente p->sig del elemento actual p: p->sig=malloc(...).
El elemento siguiente se convierte en el elemento actual: p=p->sig.
Copiar los valores obtenidos en e en el elemento actual: *p=e;.
Poner sistemticamente a NULL el elemento siguiente: p->sig=NULL, esto es para tener de forma
sencilla un NULL al final.
Cerrar el archivo.
Devolver la direccin de la cabecera de la lista, la direccin del primer elemento.
Lo que se termina convirtiendo en la siguiente funcin:

t_elem* cargar_lista()
{
FILE*f;
t_elem*primero=NULL,*p,e;
if((f=fopen("lista.bin","rb"))!=NULL){
primero=malloc(sizeof(t_elem));
fread(primero,sizeof(t_elem),1,f);
p=primero;
while(fread(&e,sizeof(t_elem),1,f)){
p->sig=malloc(sizeof(t_elem));
p=p->sig;
*p=e;
p->sig=NULL;
}
fclose(f);
}
else
printf("error o archivo inexistente");
return primero;
}
3. Implementar una lista simple circular

a. Estructura de datos de una lista circular

La estructura de datos es la misma que para una lista no circular.

typedef struct elem{

int val; // 1: datos


char s[80];
struct elem*sig; // 2: para construir la lista

}t_elem;
b. Lista vaca

Como sucede con una lista no circular, una lista vaca es un puntero a NULL:

t_elem*actual=NULL;
c. Comienzo y fin de la lista

Una lista enlazada circular es una lista en la que el final apunta al principio.

Hay dos diferencias con las listas no circulares:

La direccin de la cadena es la direccin de cualquier elemento de la cadena. En vez de tener un


puntero al primer elemento, tenemos un puntero al elemento actual que contiene la direccin de
cualquier elemento de la cadena.
El final de la cadena es tambin la direccin de comienzo albergada en el puntero actual y el valor
NULL ya no es indicador del final.
d. Inicializar un elemento

Por defecto, cada elemento apunta a s mismo, el puntero sig apunta al propio elemento. De este modo,
la funcin de inicializacin es:

t_elem* inicializa_elem(int val, char s[])


{
t_elem*e;
e=(t_elem*)malloc(sizeof(t_elem));
e->val=val;
strcpy(e->s,s);
e->sig=e; // no es NULL, apunta a s mismo
return e;
}
e. Aadir un elemento

Para aadir un elemento, necesitamos que el puntero actual haga el papel del primero y, para no perder
la cadena, siempre debe tener una direccin vlida de un elemento de la cadena. Si la lista est vaca, su
valor cambiar. Es por ello por lo que se pasa por referencia, para no perder la modificacin realizada
en el mtodo. El algoritmo es el siguiente:

Si actual es NULL, significa que la lista est vaca; en este caso el nuevo elemento da su direccin a
actual.
Si no, hay al menos un elemento en la lista, o puede que ms. Entonces, el nuevo elemento se inserta
detrs del actual. El nuevo elemento adquiere como siguiente al siguiente del elemento actual y el
elemento actual adquiere como siguiente el nuevo elemento.
Con lo que obtenemos:

void anadir_sig(t_elem**actual,t_elem*e)
{
if (*actual==NULL) // en este caso se modifica actual
*actual=e;
else{
e->sig=(*actual)->sig; // no se modifica actual
(*actual)->sig=e;
}
}
f. Recorrer la lista

Si la lista no est vaca, para recorrer la lista a partir del elemento actual, necesitamos conservar la
direccin contenida en este elemento actual con objeto de poderlo reconocer y saber que el recorrido ha
terminado. No usaremos el puntero al actual para recorrer la lista, sino otro puntero p que adquiera el
valor del actual al comienzo. El valor de actual, por tanto, no se modifica. Si la lista est vaca,
mostraremos lista vaca; si no:

Un puntero adquiere la direccin de actual.


El bucle se ejecuta al menos una vez (hay al menos un elemento),
Mostramos los datos.
Pasamos al siguiente.
mientras el puntero no se encuentre con la direccin de actual.

Con lo que obtenemos:

void mostrar(t_elem* actual)


{
t_elem*p;
if (actual==NULL)
printf("lista vaca\n");
else{
p=actual;
do{
printf("%d%s--",p->val,p->s);
p=p->sig;
}while( p!=actual);
putchar(\n);
}
}
g. Eliminar un elemento

No podemos eliminar fcilmente el elemento actual en una lista simple porque hay que disponer del
elemento anterior para restablecer la cadena. Por tanto, vamos a eliminar el elemento siguiente al
actual. Algoritmo:

Para empezar, comprobar que la lista no est vaca.


A continuacin, obtener la direccin del siguiente (el que se eliminar).
Modificar el siguiente del actual, que es el siguiente del que se eliminar.
Si el elemento que se eliminar es el actual, es decir, si solo hay un elemento en la lista, entonces poner
el actual a NULL para indicar que se trata de una lista vaca.
En todo caso, liberar la memoria del elemento eliminado.
Con lo que obtenemos:

void eliminar_sig(t_elem**actual)
{
t_elem*e;
if (*actual!=NULL){
e=(*actual)->sig;
(*actual)->sig=e->sig;
if (*actual==e)
*actual=NULL;
free(e);
}
}
h. Destruir listas

Para destruir la lista, vamos a recorrer la lista de otro modo. Si la lista no est vaca, partimos del
segundo elemento hasta encontrar el primero. La direccin de cada elemento se elimina mediante el
puntero intermediario elim. Al finalizar el bucle, solo faltar el puntero actual y se liberar la direccin
que contiene:

void destruir_lista(t_elem**actual)
{
t_elem*p,*elim;
if (*actual!=NULL){
p=(*actual)->sig;
while (p!=*actual){
elim=p;
p=p->sig;
free(elim);
}
free(*actual);
*actual=NULL;
}
}
4. Implementar una lista doblemente enlazada

Una lista es doblemente enlazada cuando permite moverse en ambos sentidos de la forma siguiente:

images/06ri24.png
Vamos a implementar esta lista para experimentar los problemas que se nos pueden presentar.

a. Estructura de datos

El elemento se define con el tipo t_elem. Para los datos, como se ha hecho en los anteriores ejemplos,
tenemos un campo para un valor entero y un campo para una cadena de caracteres. Para la mecnica de
la lista enlazada, tiene dos punteros struct elem*, uno al elemento anterior y otro al elemento siguiente:

typedef struct elem{


// los datos
int val;
char s[80];
// mecnica de la cadena
struct elem* sig;
struct elem* ant;
}t_elem;
b. Lista vaca

Una lista vaca es un puntero primero a NULL:

t_elem*primero=NULL ;
c. Comienzo y final de la lista

El comienzo es el puntero primero que contiene la direccin del primer elemento. Posiblemente podra
ser interesante tener un puntero ltimo para el ltimo elemento, de forma que se pudiera entrar a la lista
por un lado u otro. En ambos sentidos, siempre se usa el principio del centinela a NULL.

d. Inicializar un elemento

La funcin de inicializacin es idntica para la lista enlazada dinmicamente simple que hemos visto
con anterioridad. Como es obvio, para una lista doblemente enlazada los dos punteros ant y sig se
ponen a NULL:

t_elem* inicializa_elem()
{
char* n[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};

t_elem*e=malloc(sizeof(t_elem));
e->val=rand()%26;
strcpy(e->s,n[rand()%26]);
e->sig=NULL;
e->ant=NULL;
return e;
}
e. Aadir un elemento al comienzo

El elemento e est al comienzo de la lista:

El que actualmente es el primero pasa a ser su siguiente: e->sig=*primero.


e pasa a ser su anterior: (*primero)->ant=e;.
El puntero primero pasa a ser e: *primero=e;.
void anadir_comienzo(t_elem**primero,t_elem*e)
{
if(*primero){ // si ya hay elementos
e->sig=*primero;
(*primero)->ant=e; // enlace al anterior
*primero=e;
}
else
*primero=e;
}
f. Aadir un elemento al final

Si la lista est vaca, el elemento e es el primero y hemos terminado: *primero=e

si no, hay que encontrar el ltimo elemento p (a menos que tengamos un puntero al ltimo) y aadir el
nuevo: el siguiente de p es e y el anterior de e es p:

void anadir_final(t_elem**primero,t_elem*e)
{
t_elem*p;
// si lista vaca
if (*primero==NULL)
*primero=e;
// si no buscar el ltimo elemento
else{
p=*primero;
while(p->sig!=NULL)
p=p->sig;
// y aadir el nuevo
p->sig=e;
e->ant=p;
}
}
g. Recorrer y mostrar la lista

Recorrer y mostrar se hace del mismo modo que con una lista simple: a partir del primer elemento
hasta el final con un puntero y mostrando los valores de cada elemento. Para recorrer la cadena hacia el
otro sentido, hay que tener un puntero en el ltimo elemento. Por supuesto, se le puede aadir a la
estructura de datos de la lista: son dos punteros, uno para el primero y otro para el ltimo. Solo faltara
entonces actualizarlo permanentemente en todas las funciones de adicin y eliminacin. A
continuacin, la funcin para mostrar la lista:

void mostrar_lista(t_elem*primero)
{
if (primero==NULL)
printf("lista vaca\n");
while(primero!=NULL){
printf("%d%s--",primero->val,primero->s);
primero=primero->sig;
}
putchar(\n);
}
h. Eliminar un elemento

Eliminar un elemento es ms sencillo con una lista doblemente enlazada que con una lista simple.
Basta con colocarse en el elemento deseado y restablecer los enlaces hacia el anterior si existe y al
siguiente si existe:

Si el elemento que se elimina tiene un siguiente, el anterior del siguiente pasa a ser el anterior del
elemento que se elimina.
Si el elemento que se elimina tiene un anterior, el siguiente del anterior pasa a ser el siguiente del
elemento que se elimina.
Si el elemento que se elimina es la cabecera de la lista, el primero de la lista pasa a ser el siguiente.
En la siguiente funcin no liberamos la memoria del elemento eliminado de la lista; su espacio en
memoria es siempre vlido y accesible:

void eliminar_elem(t_elem**primero,t_elem*e)
{
if (e->sig!=NULL)
e->sig->ant=e->ant;
if (e->ant!=NULL)
e->ant->sig=e->sig;
if (e==*primero)
*primero=e->sig;
}
i. Destruir la lista

Destruir una lista doblemente enlazada es idntico a destruir una lista simple. Se trata de ir pasando por
cada una de las direcciones de cada elemento y liberarlas sin impedir que ninguno de los elementos
pueda pasar al siguiente:

void destruir_lista(t_elem**primero)
{
t_elem*elim;
while (*primero!=NULL){
elim=*primero;
*primero=(*primero)->sig;
free(elim);
}
}
j. Copiar una lista

Copiar una lista dinmica, simple o doblemente enlazada significa reconstruir una lista en paralelo de la
lista copiada. No hay que utilizar las mismas direcciones de memoria porque si no sera la misma lista
operada por punteros diferentes. Para empezar, hay que crear el primer elemento. Es su direccin la que
indica el comienzo de la lista y la que se devuelve al finalizar la copia. Para cada elemento de la cadena
que se copia, hay que crear un nuevo elemento, asignarle los mismos valores y enlazarlo con el anterior
de la cadena-copia, pasar al siguiente elemento de la cadena-copia, es decir, al que se acaba de crear, y
pasar al siguiente de la cadena original hasta llegar al final de la cadena original. Entonces, devolver la
cabecera de la lista-copia, con lo que obtenemos:

t_elem* copiar_lista(t_elem*primero)
{
t_elem*primero2=NULL,*e,*p;
if (primero!=NULL){
// el primero para la nueva lista
primero2=(t_elem*)malloc(sizeof(t_elem)); // asignacin + copia
primero2->val=primero->val;
strcpy(primero2->s,primero->s);
primero->sig=primero2->ant=NULL;

//

Pilas

1. Principio de las pilas

a. Modelo de datos pila

La pila es una lista que almacena de forma ordenada los elementos. Se puede realizar el smil con una
pila de platos: los platos para lavar se apilan uno encima del otro a medida que van llegando. La
persona que los lava los va cogiendo por arriba, lo que hace que el ltimo en llegar sea el primero en
lavarse. De igual modo, en una pila informtica los elementos se aaden uno por uno segn su orden de
llegada y se retiran uno por uno partiendo del ltimo que lleg, que se llama la cima de la pila.

images/06ri35.PNG
Es la regla de: ltimo en entrar, primero en salir: LIFO (Last In First Out).

b. Implementacin esttica o dinmica

Desde el punto de vista de la implementacin, una pila es una lista sencilla que solo permite inserciones
y eliminaciones en la cabecera de la lista. Como sucede con las listas, se puede tener una pila dinmica
bajo demanda con la ayuda de punteros o una pila en memoria contigua realizada segn un tamao
predefinido al inicio con una tabla.

Asignacin dinmica de memoria

Representacin de una pila dinmica:

images/06ri36.PNG
Una pila dinmica no tiene tamao mximo, excepto por la limitacin de memoria RAM disponible.

Asignacin contigua de memoria

Representacin de una pila contigua:

images/06ri37.PNG
El tamao de una pila contigua requiere especificarse en un momento dado y gestionarse si se trata de
una tabla dinmica.

Tambin es posible tener una pila en un archivo. En este caso, la pila es contigua y no tiene lmite en
cuanto al tamao. Sin embargo, la escritura es ms pesada y los accesos a disco son ms lentos.

c. Primitivas de gestin de pilas

Las operaciones bsicas realizadas con pilas son las siguientes:

Inicializacin de una pila: crear una pila vaca.


Saber si una pila est vaca: devolver 1 si verdadero, 0 si falso.
Saber si una pila est llena: devolver 1 si verdadero, 0 si falso.
Leer cima: obtener los datos de la cima de la pila sin retirarla de la pila.
Apilar (push): aadir un nuevo elemento a la cima de la pila.
Desapilar (pop): retirar el elemento que est en la cima de la pila.
Vaciar la pila: retirar todos los elementos de la pila.
Destruir la pila: desasignar todo lo relacionado con la pila.
d. Aplicaciones importantes de las pilas

En sistemas las pilas se usan para implementar las llamadas a funciones: son las pilas de llamadas. En
particular, en las llamadas recursivas se forma una pila de recursin (este punto se aborda en el captulo
sobre la recursividad con el principio: recursividad = iteracin + pila). Se usan tambin en la
evaluacin de expresiones aritmticas; en cualquier caso modelizado en el que el orden de
entrada/salida de los datos corresponda al de una pila...

2. Implementacin de una pila dinmica

La implementacin de una pila no supone ningn problema en particular; es un caso simple de lista
enlazada con un conjunto reducido de funciones asociadas a su gestin.

a. Estructura de datos

Retomamos nuestro elemento base utilizado para el estudio de listas enlazadas. Hay dos campos de
datos, un entero val y una cadena de caracteres s, y adicionalmente un puntero a una tupla del mismo
tipo para la implementacin de la lista.

typedef struct elem{


int val;
char s[80];

struct elem*sig;
}t_elem;
Tabla en global de cadena s de caracteres para la inicializacin aleatoria del campo s:

char* S[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
b. Pila vaca, pila llena

Si una pila dinmica est vaca, significa que el puntero de la cima est a NULL. Para saberlo, basta
con hacer una comprobacin del valor del puntero de la cima. La funcin pila_vacia devuelve el
resultado de la comprobacin del valor del puntero de la cima, 1 si est vaca, 0 en caso contrario:
int pila_vacia(t_elem*cima)
{
return (cima==NULL);
}
La cuestin de la pila llena no tiene sentido con una lista dinmica. No hay un lmite terico
establecido. Sin embargo, en la prctica se puede sobrepasar la capacidad de la memoria de la mquina.
Pero es un caso lmite que no concierne a la pila. Ms bien es en el momento de la inicializacin de un
nuevo elemento cuando se debera comprobar si hay suficiente memoria para poderlo hacer.

c. Inicializacin

Como sucede con una lista normal, el puntero primero (puntero cima, en el caso de la pila) debe
ponerse a NULL si la lista est vaca o tener una direccin vlida si no est vaca. De este modo, al
inicio una pila vaca estar asignada a NULL.

A continuacin, a medida que se aaden elementos a una pila, cada nuevo elemento se inicializa como
hemos visto en el captulo anterior para una lista simple:

t_elem* inicializa_elem(int val, char s[])


{
t_elem*e;
e=(t_elem*)malloc(sizeof(t_elem));
e->val=val;
strcpy(e->s,s);
e->sig=NULL;
return e;
}
Por defecto, el siguiente se pone a NULL.

El retorno de malloc() puede comprobarse para saber si se ha producido un error en el momento de la


asignacin de memoria. El retorno vale NULL en caso de error, por ejemplo si no hay suficiente
memoria disponible.

d. Apilar

Apilar es aadir un elemento al comienzo de la lista:

void apilar(t_elem**p,t_elem*e)
{
e->sig=*p;
*p=e;
}
e. Obtener la cima

La cima de la pila est siempre apuntada por el puntero cima (el primero de la lista), que es la direccin
de la lista. Para leer la cima, basta con leer el primer valor de esta direccin.

f. Desapilar
Desapilar es retirar el primer elemento de la lista y borrarlo de ella. El primero de la lista pasa a ser
entonces el siguiente y el elemento retirado se devuelve al contexto de la llamada:

t_elem* desapilar(t_elem**p)
{
t_elem*e=NULL;

if(!pila_vacia(*p)){
e=*p;
*p=(*p)->sig;
}
return e;
}
g. Vaciar, destruir

Vaciar y destruir son idnticos en una pila dinmica. Se trata de desapilar uno a uno todos los
elementos de la pila y liberar la memoria que ocupan. Finalmente, es importante asignar de forma
correcta el puntero de la cima a NULL:

void destruir_pila(t_elem**p)
{
t_elem*e;
while (!pila_vacia(*p)){
e=desapilar(p);
free(e);
}
*p=NULL;
}
h. Mostrar

Las dos funciones siguientes de visualizacin no tienen nada que ver directamente con las pilas, pero
ambas permiten visualizar las acciones realizadas. La primera permite mostrar un solo elemento. Sirve
para ver un elemento desapilado. En esta funcin hay que tener cuidado con verificar que el elemento
que se muestre no sea NULL. Este puede ser el caso cuando la pila est vaca y, sin embargo, se ejecuta
la funcin desapilar:

void muestra_elem(t_elem*e)
{
if (e!=NULL)
printf("%d%s",e->val,e->s);
else
printf("no hay nign elemento que mostrar");
putchar(\n);
}
La segunda funcin muestra toda la pila, como cualquier lista:

void muestra_pila(t_elem*p)
{
if(p==NULL)
printf("pila vaca");
while(p!=NULL){
printf("%d%s",p->val,p->s);
p=p->sig;
}
putchar(\n);
}
i. Prueba en el main()

Las funciones anteriores se prueban en el siguiente men:

int menu()
{
int res=-1;
printf( "1: apilar y mostrar pila\n"
"2: desapilar y mostrar elemento\n"
"3: destruir pila y mostrar pila vaca\n"
);
scanf("%d",&res);
rewind(stdin);
return res;
}
La eleccin del usuario se implementa de la siguiente manera:

int main()
{
int fin=0,id=0;
t_elem*e,*p=NULL;
while(!fin){
switch(menu()){
case 1:
e=inicializa_elem(rand()%26,S[(id++)%26]);
apilar(&p,e);
muestra_pila(p);
break;
case 2:
e=desapilar(&p);
muestra_elem(e);
break;
case 3:
destruir_pila(&p);
muestra_pila(p);
break;
default: fin=1;
}
}
return 0;
}
3. Implementacin de una pila esttica (en una tabla)

a. Estructura de datos

Para este caso, optamos por una pila genrica susceptible de funcionar con cualquier tipo de dato. El
tamao del bloque se fija con una macro constante. El bloque es una tabla esttica de punteros
genricos void*. La pila funciona con una variable cima n asociada a la tabla. El tipo t_pila se define de
la siguiente manera:

#define NUMMAX 16 // tamao del bloque


typedef struct pila{
int n; // la cima
void* t[NUMMAX]; // el bloque para la pila
}t_pila;
El elemento es siempre el mismo, sin el puntero sig, intil en este caso de ejemplo:

typedef struct elem{


int val;
char s[80];
}t_elem;
Tabla en global para la inicializacin del campo s de los elementos:

char* S[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
b. Inicializacin

Inicializar la pila es asignar la memoria para una tupla t_pila y asignar a 0 la cima, lo que significa que
la pila est vaca. A continuacin, hay que devolver la direccin de la pila:

t_pila* inicializa_pila()
{
t_pila*p;
p=(t_pila*)malloc(sizeof(t_pila));
p->n=0;
return p;
}
Los elementos se inicializan aparte con la funcin siguiente, pero podramos tener cualquier otro tipo
de elemento para esta pila genrica:

t_elem* inicializa_elem(int val, char s[])


{
t_elem*e;
e=(t_elem*)malloc(sizeof(t_elem));
e->val=val;
strcpy(e->s,s);
return e;
}
c. Pila vaca, pila llena
Con una pila implementada en una tabla, hay que saber si la pila est llena en cada insercin y si est
vaca en cada borrado. La cima de la pila es el indicador. Si la cima vale 0, la pila est vaca; si la cima
vale NUMMAX, la pila est llena. Nos podemos contentar con hacer esta comprobacin cada vez que
sea necesario, pero los dos nombres de las funciones siguientes tienen la ventaja de formular ms
claramente esta pregunta en una comprobacin, haciendo el cdigo ms legible. Ambas funciones
devuelven como resultado 1 o 0, segn si la comprobacin es verdadera o falsa, respectivamente:

int pila_vacia(t_pila*p)
{
return (p->n==0);
}

int pila_llena(t_pila*p)
{
return (p->n==NUMMAX);
}
d. Apilar

Apilar es aadir al principio. Si la pila no est llena, la direccin e del elemento se asigna a la posicin
de la cima n de la pila y la cima avanza una posicin:

void apilar(t_pila*p,void*e)
{
if (!pila_llena(p)){
p->t[p->n]=e;
p->n++;
}
else
printf("pila llena\n");
}
e. Obtener la cima

Obtener la cima consiste en devolver la direccin del elemento en la cima de la pila sin desapilarlo, es
decir, sin retirar este elemento de la pila. No es posible si la pila est vaca:

void* cima(t_pila*p)
{
void*e=NULL;
if (!pila vacia(p))
e = p->t[p->n-1];
return e;
}
f. Desapilar

Desapilar consiste en retirar el elemento de la cima de la pila. Si la pila no est vaca, obtener la
direccin del elemento en la cima y decrementar la cima una posicin. Al finalizar, devolver la
direccin obtenida o NULL si la pila est vaca:

void* desapilar(t_pila*p)
{
void*e=NULL;
if (!pila_vacia(p)){
p->n--;
e=p->t[p->n];
}
return e;
}
g. Vaciar, destruir

Vaciar la pila es retirar todos sus elementos y liberar la memoria asignada para cada uno de ellos. Basta
con un bucle for para liberar la memoria de cada elemento y a continuacin poner la cima n de la pila
0:

void vaciar_pila(t_pila*p)
{
int i;
for (i=0; i<p->n; i++)
free(p->t[i]);
p->n=0;

}
Destruir la pila es vaciar la pila y adems liberar la memoria asignada por la pila en s misma. El
puntero t_pila* tiene que ponerse a NULL al finalizar:

void destruir_pila(t_pila**p)
{
vaciar_pila(*p);
free(*p);
*p=NULL;
}
h. Mostrar

Las funciones de visualizacin estn solo para comprobar el funcionamiento de la pila y ver los
elementos, pero no forman parte del funcionamiento de una pila. La funcin muestra_elem() permite
visualizar un elemento de tipo t_elem mediante una direccin de memoria t_elem*:

void muestra_elem(t_elem*e) // se permite el casting de void* a t_elem*


{
if (e!=NULL)
printf("%d%s--",e->val,e->s);
else
printf("elemento inexistente");
}
La visualizacin de la pila consiste en mostrar todos los elementos de la pila comenzando por la cima.
Se tienen en cuenta los casos de pila sin inicializar y pila vaca. Las direcciones void* se pasan al
parmetro de tipo t_elem* de la funcin muestra_elem(). De hecho, se hace el casting a t_elem*,
condicin obligatoria para tener acceso a los campos de estas tuplas.
void muestra_pila(t_pila*p)
{
int i;
if(p==NULL)
printf("pila sin inicializar");
else if (p->n<=0)
printf("pila vaca");
else{
for (i=p->n-1; i>=0; i--)
muestra_elem(p->t[i]); // casting de void* a t_elem*
}
putchar(\n);
}
i. Prueba en el main()

El men propone las siguientes acciones:

int menu()
{
int res=-1;
printf( "1: apilar y mostrar pila\n"
"2: desapilar y mostrar elemento\n"
"3: destruir pila y mostrar pila vaca\n"
);
scanf("%d",&res);
rewind(stdin);
return res;
}
Se implementan en el main() de la forma siguiente:

int main()
{
int fin=0,id=0;
t_elem*e;
t_pila*p=NULL;

p=inicializa_pila();
while(!fin){
switch(menu()){
case 1:
e=inicializa_elem(rand()%26,S[(id++)%26]);
apilar(p,e);
muestra_pila(p);
break;
case 2:
e=desapilar(p);
printf("elemento quitado:\n");
muestra_elem(e);
putchar(\n);
break;
case 3:
vaciar_pila(p);
muestra_pila(p);
break;
default: fin=1;
}
}
destruir_pila(&p);
return 0;
}
4. Puesta en prctica: pilas

Ejercicio 1

Escribir un programa que muestre una lista enlazada simple al revs usando una pila.

Ejercicio 2

Dar el contenido de la pila para cada operacin de la serie:

P*REG***UN*TA*FAC***IL****

Cada letra provoca una apilamiento y cada asterisco un desapilamiento.

Hacer lo mismo con la serie: EAS*Y*QUE***ST***IO*N***

Ejercicio 3

Un archivo de texto puede contener parntesis ( ), corchetes [ ] y llaves { }. Estos elementos pueden
estar anidados unos dentro de otros, por ejemplo: { a(bc[d])[{ef}(g)]}. Escribir una funcin que recorra
el archivo de texto y determine si el archivo tiene estos smbolos correctamente escritos. Es decir, los
parntesis, los corchetes y las llaves deben estar correctamente cerrados y anidados. Por ejemplo, ({]})
y ({)} no son correctos.

Ejercicio 4

Escribir un programa que, con la ayuda de una pila, evale la expresin aritmtica posfija:

5 11 9 + 2 12 * - 8 / *

Despus, con la ayuda de otra pila, transformar la expresin posfija en su expresin infija (operador
entre dos operandos, todo entre parntesis).

Ejercicio 5

Los internautas utilizan un software para navegar por Internet. Entre sus funciones, est la que permite
volver a una pgina a la que ya se ha accedido mediante su direccin URL (http://www...). Esta funcin
se activa mediante el icono que representa la flecha. Para realizar esta funcionalidad, el navegador
conserva un historial de las direcciones URL de cada pgina a la que se ha accedido, de forma que
pueda acceder de nuevo si fuera necesario. Las direcciones de las pginas web se guardan segn el
orden de acceso: de la ms reciente a la menos reciente. Despus de un cierto tiempo, este historial
puede contener la misma pgina web ms de una vez. Se desea evitar los duplicados: solo hay que
guardar en memoria una nica versin URL de cada pgina, todo ello preservando el orden de acceso
de la ms reciente a la ms antigua.

Programar una simulacin.

1) Qu estructura de datos se debe usar?

2) Comprobar con un programa que inicialice el historial de direcciones URL que contenga repetidos.

3) Cmo evitar los repetidos en el historial? Cul es la solucin para hacer referencia a todas las
pginas, incluidas las mltiples, sin guardar su direccin URL repetida?

Ejercicio 6

Sean tres pilas P1, P2 y P3, que pueden contener un nmero ilimitado de objetos.

Al comienzo, una serie de objetos de distintos tamaos pueden apilarse de forma desordenada en la
primera pila, P1. Esta pila no est sometida a ninguna restriccin: un objeto ms grande puede apilarse
encima de otro ms pequeo. Supondremos, por lo tanto, que la llamada al procedimiento
ApilarMasivo() apila de forma desordenada varios objetos en la pila P1.

Se desea ordenar estos objetos por tamao en la segunda pila, P2, que tiene una restriccin: un objeto
ms pequeo solo puede apilarse encima de un objeto mayor. Para ello, se desapilan los objetos de la
primera pila, P1, uno por uno y se apilan en la segunda pila, P2, respetando esta restriccin de orden de
apilamiento.

Si el tamao del objeto en la cima de P1 es mayor que el de la cima de P2, la tercera pila, P3, que acta
como intermediaria se vuelve imprescindible. Como P1, esta tercera pila, P3, no est sometida a
ninguna restriccin de orden de apilamiento.

images/06ri38.png
Escribir un programa de simulacin para n objetos entrados por el usuario.
Colas

1. Principios de las colas

a. Modelo de datos cola

La cola es una lista que almacena los elementos en un orden especfico. Es como la cola de caja de un
supermercado. Las personas llegan una a una y forman la cola, y se van yendo una a una en el orden de
llegada, despus de haber pagado. Hay una persona en la cabeza de la cola, la primera, y una persona
en el final de la cola, la ltima. Es el concepto de el primero en llegar, el primero en salir, First In
First Out, denominado FIFO en informtica:

images/06ri39.PNG
b. Implementacin esttica o dinmica

Una cola es una lista particular que utiliza los dos extremos de comienzo y fin, a diferencia de una pila,
que solo usa uno. La implementacin puede hacerse dinmica con punteros o contigua con una tabla.

Asignacin dinmica de memoria

Representacin de una cola dinmica:

images/06ri40.PNG
Hay que tener dos punteros para una cola. Uno para gestionar las entradas en la cola (ltimo) y otro
para gestionar las salidas (primero). Con memoria dinmica una cola no tiene tamao mximo, excepto
el de la memoria RAM disponible.

Asignacin contigua de memoria

Representacin de una cola contigua:

images/06ri41.png
La gestin de una cola contigua es menos fcil de lo que parece en un principio. Hay dos ndices: uno
para el primero y otro para el ltimo, pero hay que utilizar la tabla de forma circular. Los nuevos
elementos pueden colocarse al comienzo o al final, segn el espacio restante en la tabla. Si aadimos 5
entradas y borramos 2 en el grfico anterior, obtenemos:

images/06ri42.png
El tamao de una cola contigua hay que especificarlo en un momento dado y gestionarse si se trata de
una tabla dinmica. Es posible tener una cola en un archivo. En este caso, el archivo contiguo no tiene
lmites de tamao. Pero la escritura es ms pesada y los accesos a disco son ms lentos.

c. Primitivas de gestin de archivos

Las operaciones bsicas realizadas con colas son las siguientes:

Inicializacin de una cola: crear una cola vaca.


Saber si la cola est vaca: devolver 1 si verdadero, 0 en caso contrario.
Saber si la cola est llena: devolver 1 si verdadero, 0 en caso contrario.
Obtener primero: obtener los datos del primero sin quitarlo de la cola.
Encolar: aadir un nuevo elemento como ltimo de la cola.
Avanzar: retirar el primer elemento de la cola.
Vaciar la cola: retirar todos los elementos de la cola.
Destruir la cola: desasignar todo el espacio reservado relativo a la cola.
d. Aplicaciones importantes de las colas

En sistemas, las colas se usan para gestionar los procesos de espera de recursos de sistema o en la
lectura de algunos archivos multimedia (sonidos en particular). De forma general, intervienen en un
modelo que requiere una cola de espera: sistemas de reserva, gestin de pistas de aeropuerto, etc.

2. Implementacin de una cola dinmica

Una cola es una lista de la que conservamos permanentemente el primer elemento, que representa el
primero de la cola, y el ltimo elemento, que representa el ltimo de la cola. De este modo, una cola se
define con dos punteros, el primero para las salidas y el ltimo para las entradas. Aparte de esta
particularidad, es un caso normal de lista enlazada.

a. Estructura de datos

El elemento es siempre el mismo:

typedef struct elem{


int val;
char s[80];
struct elem*sig;
}t_elem;
Inicializado de forma aleatoria con la tabla global S de cadenas de caracteres para el campo s:

char* S[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
El tipo cola se define con una tupla que tiene dos punteros, primero, que gestiona las salidas, y
ultimo, que gestiona las entradas de elementos:

typedef struct cola{


t_elem*primero; // salida
t_elem*ultimo; // entrada
}t_cola;
b. Cola vaca, cola llena

Para saber si una cola est vaca, basta con comprobar si los punteros ultimo o primero estn a NULL.
En caso afirmativo, est vaca. Cabe decir que es imposible (salvo error en la programacin) que uno
de los dos est a NULL y el otro no:

int cola_vacia(t_cola*c)
{
return c->ultimo==NULL;
}
Tericamente no hay lmites (aparte de la memoria de la mquina) para el tamao mximo de una cola
dinmica y, por lo tanto, no tiene sentido la comprobacin de si est llena.

c. Inicializacin

La inicializacin de un elemento se lleva a cabo como en los casos anteriores: el campo sig por defecto
a NULL. La funcin recibe los valores que se asignan al elemento por parmetro y devuelve la
direccin vlida de un elemento inicializado:

t_elem* inicializa_elem(int val, char s[])


{
t_elem*e;
e=(t_elem*)malloc(sizeof(t_elem));
e->val=val;
strcpy(e->s,s);
e->sig=NULL;
return e;
}
Inicializar una cola es asignar la memoria necesaria para los dos punteros, primero y ultimo, de una
tupla t_cola y asignarles el valor NULL. La funcin devuelve la direccin vlida de una t_cola:

t_cola*inicializa_cola()
{
t_cola*f;
f=(t_cola*)malloc(sizeof(t_cola));
f->primero=f->ultimo=NULL;
return f;
}
d. Encolar

Encolar un elemento, es decir, aadir un nuevo elemento a la cola, es aadir un elemento despus del
ltimo, a nivel del puntero ultimo. La funcin recibe por parmetro la direccin de la cola y la
direccin supuestamente vlida del elemento que se encolar. La conexin es muy sencilla:

El siguiente del ltimo pasa a ser el nuevo.


El ltimo pasa a ser el nuevo.
Si la cola est vaca, el primero y el ltimo adquieren el mismo valor, el del nuevo elemento, lo que da:

void encolar(t_cola*c,t_elem*e)
{
// aadir a la cola (en ultimo)
if (cola_vacia(c))// si cola vaca
c->ultimo=f->primero=e;
else{ // si no aadir al final
c->ultimo->sig=e;
c->ultimo=e;
}
}
e. Obtener el primero, obtener el ltimo
Obtener los datos del primer o ltimo elemento es recuperar las direcciones de los punteros primero o
ultimo respectivamente, bajo la suposicin que la cola no est vaca. Las funciones devuelven la
direccin vlida del elemento o NULL si la cola est vaca:

t_elem* obtener_primero(t_cola*c)
{
t_elem*e=NULL;
if (!cola_vacia(c))
e=c->primero;
return e;
}

t_elem* obtener_ultimo(t_cola*c)
{
t_elem*e=NULL;
if (!cola_vacia(c))
e=c->ultimo;
return e;
}
f. Avanzar

Se trata de devolver la direccin del elemento que sale de la cola (el primero) y avanzar la cola (el
primero pasa a ser el siguiente). Si la cola est vaca, se devuelve NULL. Hay dos casos que se deben
tratar:

Si hay un solo elemento en la lista, devolver su direccin y poner los dos punteros primero y ultimo a
NULL.
Si hay ms, devolver la direccin del primero y avanzar; el primero pasa a ser la direccin del
siguiente.
Con lo que obtenemos:

t_elem* avanzar(t_cola*c)
{
t_elem*e=NULL;
if(!cola_vacia(c)){ // si no vaca quitar el primero (en primero)
if (c->primero==c->ultimo){// si un solo elemento
e=c->primero;
c->primero=c->ultimo=NULL;
}
else{ // si ms de uno
e=c->primero;
c->primero=c->primero->sig;
}
}
return e;
}
g. Vaciar, destruir

Vaciar la cola consiste en avanzar todos los elementos hasta que la cola se quede vaca y, cada vez que
se avanza un elemento, liberar la memoria asignada para ese elemento. La funcin es la siguiente:

void vaciar_cola(t_cola*c)
{
t_elem*sup;
while(!cola_vacia(c)){
sup=avanzar(c);
free(sup);
}
}
Destruir la cola es vaciar la cola y liberar la memoria asignada por la cola:

void destruir_cola(t_cola**c)
{
vaciar_cola(*c);
free(*c);
*f=NULL;
}
h. Mostrar

Tenemos dos funciones de visualizacin: una para mostrar un solo elemento y otra para mostrar la cola.
La funcin para mostrar un elemento es la siguiente (tiene en cuenta el caso en el que el elemento est a
NULL):

void muestra_elem(t_elem*e)
{
if (e!=NULL)
printf("%d%s",e->val,e->s);
else
printf("elemento inexistente");
putchar(\n);
}
La funcin para mostrar la cola tiene en cuenta el caso en el que la cola est vaca y, si no, muestra los
valores de cada elemento, empezando por el primero:

void muestra_cola(t_cola*c)
{
t_elem*e;
e=c->primero;
if(cola_vacia(c))
printf("cola vaca");
while(e!=NULL){
printf("%d%s--",e->val,e->s);
e=e->sig;
}
putchar(\n);
}
i. Prueba en el main()
En el main() las operaciones de prueba son las siguientes:

int menu()
{
int res=-1;
printf( "1: encolar y mostrar cola\n"
"2: avanzar y mostrar elemento y cola\n"
"3: vaciar cola y mostrar cola vaca\n"
);
scanf("%d",&res);
rewind(stdin);
return res;
}
A continuacin, la implementacin en el main():

int main()
{
int fin=0,id=0;
t_elem*e;
t_cola*c;

c=inicializa_cola();
while(!fin){
switch(menu()){
case 1:
e=inicializa_elem(rand()%26,S[(id++)%26]);
encolar(c,e);
muestra_cola(c);
break;
case 2:
e=avanzar(c);
muestra_elem(e);
muestra_cola(c);
break;
case 3:
vaciar_cola(c);
mostrar_cola(c);
break;
default: fin=1;
}
}
destruir_cola(&c);
return 0;
}
Observacin:

Durante todo el funcionamiento del programa, la cola est declarada e inicializada. Esto es as porque
se inicializa al inicio, justo antes del bucle de eventos, y no se puede destruir durante el
funcionamiento. Se destruye en la salida, cuando el programa finaliza su ejecucin. Si se decide poder
destruirla durante el funcionamiento del programa, entonces hay que aadir en todas las funciones que
utilicen un t_cola*c la comprobacin siguiente para evitar un acceso a una direccin de memoria no
vlido:

if (c!=NULL){

...

}
3. Implementacin de una cola esttica (en una tabla)

a. Estructura de datos

Nos volvemos a encontrar con el elemento elem para nuestras pruebas, pero esta vez el campo sig ha
desaparecido:

typedef struct elem{


int val;
char s[80];
}t_elem;
con la tabla de cadenas de caracteres S en global:

char* S[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
En cuanto a la cola, se agrupa su informacin en una tupla: un puntero de ndice para el primero de la
cola (primero), otro puntero de ndice para el ltimo (ultimo) y una tabla para los elementos de tamao
NUMMAX, definida en una macro constante. Hemos optado por una tabla de void* genrica, de forma
que pueda servir para cualquier tipo de objeto:

#define NUMMAX 8
typedef struct cola{
int primero,ultimo;
void*tab[NUMMAX];
}t_cola;
b. Cola vaca, cola llena

Hay que saber si la cola est vaca para avanzar u obtener el primero o el ltimo. La cola est vaca si el
primero tiene la misma posicin que el ltimo en la tabla, es decir, si primero==ultimo:

int cola_vacia(t_cola*c)
{
return c->ultimo==c->primero;
}
Necesitamos saber si la cola est llena cuando encolamos elementos porque el tamao es fijo, est
limitado a NUMMAX elementos. El principio es que la cola est llena si el siguiente es el primero en la
tabla circular, despus de una vuelta entera a la tabla partiendo desde cualquier posicin, es decir, si
(ultimo + 1) mdulo NUMMAX es igual a primero:

int cola_llena(t_cola*c)
{
return ((c->ultimo+1)%NUMMAX == c->primero);
}
c. Inicializacin

La inicializacin de la cola consiste en asignar la memoria para una tupla t_cola y devolver la direccin
del bloque. Los indicadores de posicin primero y ultimo se colocan al principio, ambos a 0.

t_cola* inicializa_cola()
{
t_cola*c;
c=(t_cola*)malloc(sizeof(t_cola));
c->primero=c->ultimo=0;
return c;
}
La inicializacin de un elemento no cambia:

t_elem* init_elem(int val, char s[])


{
t_elem*e;
e=(t_elem*)malloc(sizeof(t_elem));
e->val=val;
strcpy(e->s,s);
return e;
}
d. Encolar

Para encolar, si la cola no est llena, hay que aadir un elemento en el final de la cola y avanzar el
puntero de ndice ultimo en uno, sabiendo que se trata de una tabla circular (llegar al final significa
volver a empezar desde el principio).

void encolar(t_cola*c,void*e)
{
// aadir a la cola (en ultimo)
if (!cola_llena(c)){
c->tab[c->ultimo]=e;
c->ultimo=(c->ultimo+1)%NUMMAX;
}
}

ultimo est siempre avanzado una posicin en relacin con el ltimo elemento real albergado en la
tabla. Corresponde a la siguiente posicin libre para el prximo elemento que llegue a la cola. La
comprobacin de cola llena aade an una posicin ms para saber si la posicin que est despus es
diferente de primero, de modo que la ltima casilla de la tabla siempre quedar vaca y sirve de
centinela de fin. La solucin que consiste en decir que la cola est llena si ultimo == primero plantea
una serie de pequeos problemas que hacen que el cdigo sea ms pesado sin aportar nada de inters.

e. Obtener el primero, obtener el ltimo


Obtener el primero es recuperar la direccin del primer elemento de la cola sin cambiar la cola:

void* obtener_primero(t_cola*c)
{
void*e=NULL;
if (!cola_vacia(c))
e=c->tab[c->primero];
return e;
}
Obtener el ltimo es recuperar la direccin del ltimo elemento de la cola sin cambiar la cola:

void* obtener_ultimo(t_cola*c)
{
void*e=NULL;
int q;
if (!cola_vacia(c)){
q=(c->ultimo-1+NUMMAX)%NUMMAX; //control si ltimo a 0
e=c->tab[q];
}
return e;
}
f. Avanzar

Avanzar consiste, si la cola no est vaca, en obtener la direccin del primero y pasar el primero al
siguiente:

void*avanzar(t_cola*c)
{
void*e=NULL;
if(!cola_vacia(c)){ // si no vaca, retirar primero (en primero)
e=c->tab[c->primero];
c->primero=(c->primero+1)%NBMAX;
}
return e;
}
g. Vaciar, destruir

Vaciar la cola consiste en liberar la memoria para cada elemento y reinicializar los ndices primero y
ultimo a la misma posicin, 0 para simplificar.

void vaciar_cola(t_cola*c)
{
t_elem*sup;
while(!cola_vacia(c)){
sup=avanzar(c);
free(sup);
}
}
Destruir la cola es vaciar la cola y liberar la memoria asignada para la cola en s misma, sin olvidar
poner el puntero de la cola a NULL:

void destruir_cola(t_cola**c)
{
vaciar_cola(*c);
free(*c);
*c=NULL;
}
h. Mostrar

Para visualizar tenemos dos funciones, una para mostrar la cola y otra para mostrar un elemento. La
funcin para mostrar la cola muestra uno a uno todos los elementos de la cola con la funcin de
visualizacin del elemento. El casting es necesario al puntero genrico void* para acceder a los datos y
as realizar el paso de parmetros con t_elem*:

void muestra_cola(t_cola*c)
{
int e;
if(cola_vacia(c))
printf("cola vacia");
for (e=c->primero;e!=c->ultimo;e=(e+1)%NUMMAX)
muestra_elem(c->tab[e]);
putchar(\n);
}

void muestra_elem(t_elem*e)
{
if (e!=NULL)
printf("%d%s--",e->val,e->s);
else
printf("elemento inexistente");
}
i. Prueba en el main()

Las acciones propuestas son las mismas que en el caso anterior:

int menu()
{
int res=-1;
printf( "1: encolar y mostrar cola\n"
"2: avanzar y mostrar elemento y cola\n"
"3: vaciar cola y mostrar cola vaca\n"
);
scanf("%d",&res);
rewind(stdin);
return res;
}
De igual modo, es tambin lo mismo para las llamadas a funcin en el bucle de eventos del main():
int main()
{
int fin=0,id=0;
t_elem*e;
t_cola*c;

c=inicializa_cola();
while(!fin){
switch(menu()){
case 1:
e=inicializa_elem(rand()%26,S[(id++)%26]);
encolar(c,e);
muestra_cola(c);
break;
case 2:
e=avanzar(c);
muestra_elem(e);
putchar(\n);
muestra_cola(c);
break;
case 3:
vaciar_cola(c);
muestra_cola(c);
break;
default : fin=1;
}
}
destruir_cola(&c);
return 0;
}
4. Puesta en prctica: colas

Ejercicio 1

Dar el contenido de la cola para cada operacin de la serie: P*REG***UN*TA*FAC***IL****

Cada letra provoca que se encole la letra correspondiente y cada asterisco, un avance de la cola.

Ejercicio 2

En una estacin hay una ventanilla abierta. Los clientes van llegando a horas aleatorias y van formando
una cola. El intervalo entre la llegada de dos clientes sucesivos es un nmero aleatorio entre 0 e
INTERVALO_MAX en segundos o minutos. Cuando el vendedor ha acabado con un cliente, llama al
siguiente cliente y el tratamiento tiene una duracin entre 0 y DURACION_TRAT_MAX.

Escribir un programa de simulacin.

Ejercicio 3
Para simular un cruce de carreteras de sentido nico, se usan 3 colas c1, c2 y c3, representando
respectivamente los coches que llegan a las carreteras R1 y R2 y los coches que parten por R3. La
carretera R2 tiene un STOP, los coches de la cola c2 no pueden avanzar hasta que no hay ningn
vehculo en la carretera R1, es decir, en la cola c1.

images/06RI01.png
El algoritmo de simulacin utilizar un bucle infinito.

En cada iteracin, se realizar una llamada al procedimiento llegada(c1, c2) que simula la llegada de
uno o varios vehculos a las colas c1 y c2, modificando de este modo su estado en memoria.

Si se considera que las colas son infinitas, qu estructura de datos hay que elegir?
Supongamos que las colas no son infinitas. El tamao de nuestras colas est limitado a una variable
MAX introducida por el usuario que simboliza el nmero mximo de vehculos que puede absorber una
carretera y el procedimiento llegada(c1, c2) tiene en cuenta esta nueva hiptesis.
Programar una simulacin.

Ahora aadimos una nueva hiptesis a nuestro problema: el STOP se sigue respetando, pero el coche
de la carretera R2 puede ser prioritario en relacin con la carretera R1. Es decir, si la distancia entre el
primer vehculo de la ruta R1 y el cruce es considerada suficiente por el simulador, se preferir avanzar
c2 antes que c1. La velocidad de los coches se considera constante.

Modificar el programa anterior aadiendo este detalle.

Ejercicio 4

El objetivo de este ejercicio es escribir un programa que simule el desarrollo de una partida al juego de
la guerra.

Recordatorio de reglas:

Se dispone de un baraja de 32 cartas (4 palos y 8 nmeros de carta).


Cada carta tiene un palo (CORAZONES, DIAMANTES, TRBOLES o PICAS) y un nmero (SIETE,
OCHO, NUEVE, DIEZ, JOTA, REINA, REY o AS). Se utilizarn los cdigos ASCII para representar
los smbolos , , , . Por ejemplo, la reina de corazones se mostrara as: Q.
Las cartas se mezclan aleatoriamente al principio para a continuacin dividirse en dos grupos de 16
cartas. Se da un grupo a cada jugador. La partida puede comenzar. Cada jugador muestra la carta en la
cima de su montn. El jugador que tiene la carta de mayor valor se lleva ambas cartas y las pone debajo
de su montn. En caso de igualdad, ambas cartas se colocan en un montn de espera y cada jugador
saca una nueva carta de la cima de sus respectivos montones. La partida vuelve a empezar. El jugador
con la carta ms alta no solo se llevar las dos cartas en juego sino que tambin se llevar todas las que
haya en el montn de espera.

Perder el jugador que se quede sin cartas en su montn y el ganador ser el que tenga ms cartas al
final de la partida.

Definir las estructuras de datos adaptadas para una carta, un montn de cartas y una baraja de cartas.

Definir los jugadores. Comenzar con dos jugadores.


Cules son las acciones que hay que realizar y en qu orden?

Tratar la visualizacin del desarrollo de la partida.

A continuacin, generalizar para una partida entre num jugadores con un juego de cartas imaginario
con num cartas y num colores.

Ejercicio 5

Escribir un programa que transforme una expresin infija (con parntesis) en notacin posfija (polaca
inversa). La expresin siguiente:

3*(((12-3)/3)-1)

deber traducirse a:

3 12 3 - 3 / 1 - *

Los operadores vlidos son: +, -, *, /. El algoritmo lee una serie de caracteres y guarda el resultado de
la conversin en una cola que se muestra al final.

}t_nodo;

t_nodo*tab[NUMMAX];
El campo datos representa el campo asociado a los datos que se almacenan en el rbol; aqu es solo una
pequea cadena de caracteres para almacenar cadenas del tipo A, B, etc., para realizar pruebas.

Tabla de tuplas

images/06ri57.PNG
Con una estructura de datos de la forma:

#define NUMMAX 9
typedef struct nodo{
char datos[16];
int P1,P2,P3;
}t_nodo;

t_nodo tab[NUMMAX];
Con memoria dinmica usando punteros

images/06ri58.png
Con una estructura de datos de la forma:

typedef struct nodo{


char datos[16];
struct nodo*P1,*P2,*P3;
}t_nodo;
b. rbol binario

Sea el siguiente rbol:

images/06ri59.PNG
Es un rbol de cuatro niveles. El nmero mximo de nodos con cuatro niveles es 24-1 nodos, es decir,
16-1, 15 nodos. Para ello, faltara que todos los nodos del tercer nivel tuvieran dos hijos, uno a la
izquierda y otro a la derecha. A un rbol binario as se le llamara rbol binario completo.

Tabla de una dimensin

Un rbol binario puede representarse en una tabla de una dimensin con la siguiente regla:

La raz debe estar en el ndice 1 y para cualquier nodo en un ndice i de la tabla:

Su hijo izquierdo est en el ndice i*2 (si es una posicin vlida de la tabla).
Su hijo derecho est en el ndice i*2+1 (si es una posicin vlida de la tabla).
Para nuestro rbol anterior, tenemos la siguiente tabla:

images/06ri60.png
Probablemente, esta tabla ser dinmica en un programa, asignada en funcin del tamao mximo del
rbol. La estructura de datos podra ser:

typedef struct nodo{


char datos[16]; // datos del nodo
int i,d; // hijos izquierdo y derecho
}t_nodo;

t_nodo*tab; // tabla de nodos que se debe asignar dinmicamente


Construido usando punteros

Por supuesto, el rbol binario puede construirse dinmicamente con punteros. La estructura de datos es
entonces:

typedef struct nodo{


char datos[16]; // datos del nodo
struct nodo*i,*d; // hijos izquierdo y derecho
}t_nodo;

t_nodo*raiz; // direccin del rbol dado por el nodo raz


c. Estructuras de datos estticas o dinmicas

Como sucede con las listas enlazadas, hay dos maneras posibles para representar rboles.

En memoria contigua, es decir, en tabla o en un archivo teniendo como especialidad el almacenamiento


y la transmisin del rbol o de los datos del rbol. Los punteros pasan a ser entonces enteros, ndices o
indicadores de posicin de un nodo en el archivo. La estructura de datos de los rboles estticos utiliza
ndices:
typedef struct nodo{
(...) // datos del nodo

int i,d; // hijos izquierdo y derecho


}t_nodo;
En memoria dinmica teniendo como especialidad un rbol destinado a evolucionar, compatible con la
retirada y la insercin frecuentes de nodos. Los punteros son entonces punteros normales que contienen
la direccin de memoria de un nodo.
typedef struct nodo{
(...) // datos del nodo

struct nodo *i,*d; // hijos izquierdo y derecho


}t_nodo;

Controlar un rbol binario

1. Crear un rbol binario

Las funciones que proponemos a continuacin permiten crear rboles a partir de una descripcin o de
forma completamente aleatoria. Nuestro objetivo es utilizarlos para probar las funciones de gestin de
rboles.

a. Crear un rbol a partir de un esquema descriptivo

A continuacin se muestra el rbol que se desea crear:

images/06ri61.PNG
Crear el rbol a mano, con una funcin de creacin de nodos

Vamos a crear un rbol dinmico. Necesitaremos una tabla de caracteres para almacenar una cadena (o
tal vez solo un carcter, ya que hay que almacenar caracteres) y dos punteros, uno para el hijo izquierdo
y otro para el hijo derecho. La estructura de datos es la siguiente:

typedef struct nodo{


char dat[80];
struct nodo*i, *d;
}t_nodo;
A continuacin se muestra una funcin para inicializar un nodo del rbol:

t_nodo* CN(char s[],t_nodo*i, t_nodo*d)


{
t_nodo*n;
n=(t_nodo*)malloc(sizeof(t_nodo));
strcpy(n->s, s);
n->i=i;
n->d=d;
return n;
}
y la funcin para crear el rbol:

t_nodo* crearArbol1()
{
return CN("A",
CN("B",
CN("D",NULL,NULL),
CN("E",NULL,NULL)),
CN("C",
CN("F",
CN("H",NULL,NULL),
NULL),
CN("G",NULL,NULL))
);
}
Este mtodo no resulta prctico y adems es proclive a generar errores con los parntesis.

Crear el rbol utilizando su descripcin y una tabla

Otra posibilidad: todas las direcciones de los nodos del rbol se asignan y se almacenan en una tabla y
a continuacin se construye el rbol:

t_nodo* crearArbol2()
{
const int numnodos=9;
t_nodo**tab;
t_nodo*r;
int i;
// creacin de la tabla de punteros
tab=(t_nodo**)malloc(sizeof(t_nodo*)*numnodos);

// asignacin de memoria para las direcciones de los nodos


for (i=0; i<numnodos; i++)
tab[i]=(t_nodo*)malloc(sizeof(t_nodo));

// inicializacin de cada nodo en funcin del esquema del rbol


strcpy(tab[0]->dat,"A");
tab[0]->i=tab[1];
tab[0]->d=tab[2];

strcpy(tab[1]->dat,"B");
tab[1]->i=tab[3];
tab[1]->d=tab[4];

strcpy(tab[2]->dat,"C");
tab[2]->i=tab[5];
tab[2]->d=tab[6];
strcpy(tab[3]->dat,"D");
tab[3]->i=NULL;
tab[3]->d=NULL;

strcpy(tab[4]->dat,"E");
tab[4]->i=NULL;
tab[4]->d=NULL;

strcpy(tab[5]->dat,"F");
tab[5]->i=tab[7];
tab[5]->d=NULL;

strcpy(tab[6]->dat,"G");
tab[6]->i=NULL;
tab[6]->d=NULL;

strcpy(tab[7]->dat,"H");
tab[7]->i=NULL;
tab[7]->d=NULL;

// la raz r del rbol es el primer elemento


r=tab[0];
free(tab); // liberacin de la direccin de la tabla
return r; // retorno de la direccin del rbol
}
b. Crear un rbol a partir de los datos aleatorios de una tabla

Para hacer pruebas, puede ser interesante obtener fcilmente rboles con valores y tamaos distintos.
Para ello, vamos a escribir una funcin de generacin de rboles binarios.

El principio es muy fcil: tener una tabla, inicializarla con valores aleatorios y construir un rbol
binario utilizando la regla ya mencionada:

Poner la raz en el ndice 1 y para cualquier nodo en el ndice i de la tabla:

Su hijo izquierdo est en la posicin i*2 (si es una posicin vlida de la tabla).
Su hijo derecho est en la posicin i*2+1 (si es una posicin vlida de la tabla).
Por supuesto, podramos tomar los nodos al azar en la tabla y construir el rbol de este modo, pero lo
interesante de utilizar este principio es evitar tener que controlar cules son los nodos restantes
disponibles en la tabla para construir el rbol. En efecto, esto equivaldra a tomar en orden todos los
nodos de la tabla para formar el rbol. La tabla sera lo equivalente a un recorrido en amplitud del rbol
y la raz sera siempre el ndice 0.

El campo dat de cada nodo se inicializa con una cadena de caracteres elegida aleatoriamente entre las
siguientes:

char* S[]={"A","B","C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
rbol dinmico
El rbol dinmico es la direccin del primer nodo que es la raz del rbol. Todo el resto se puede
alcanzar mediante la red de punteros hijo izquierdo, hijo derecho.

t_nodo* crearArbol3()
{
t_nodo*r;
t_nodo**tab;
int i,k,tam;

// tamao aleatorio
tam=1+rand()%100; // min 1 max 100 nodos

// creacin de la tabla de punteros de nodos


tab=(t_nodo**)malloc(sizeof(t_nodo*)*tam);

// inicializacin aleatoria de los datos de los nodos


for(i=0;i<tam;i++){
tab[i]=(t_nodo*)malloc(sizeof(t_nodo));
strcpy(tab[i]->dat,S[rand()%26]);
}

// construccin del rbol, raz en tab[0]


for (i=0,k=0; i<tam; i++){
tab[i]->i=(++k<tam)?tab[k]: NULL;
tab[i]->d=(++k<tam)?tab[k]: NULL;
}
r=tab[0];
free(tab);
return r;
}
rbol esttico

El rbol esttico es de hecho una tabla de nodos y se define dentro mediante una red de ndices de hijo
izquierdo e hijo derecho. En la estructura de datos, los punteros a hijo izquierdo e hijo derecho son
ndices, es decir, nmeros enteros que corresponden a los ndices de la tabla:

typedef struct nodos{


char dat[80]; // datos
int i, d; // ndices
}t_nodos;
A continuacin se muestra el algoritmo de creacin de un rbol esttico que es sustancialmente igual al
de un rbol dinmico. La funcin devuelve la tabla de nodos e implcitamente la raz se encuentra en el
ndice 0:

t_nodos* crearArbol_stat()
{
t_nodos*t;
int num,i,k;
num=1+rand()%50;
t=(t_nodos*)malloc(sizeof(t_nodos)*num);
for(i=0,k=0; i<num; i++){
strcpy(t[i].dat,S[rand()%26]);
t[i].i=(++k<num)?k:-1;
t[i].d=(++k<num)?k:-1;
}
return t;
}
Si deseamos tener una raz en una posicin aleatoria, entonces deberemos pensar en devolver su
posicin mediante una variable pasada por referencia.

c. Crear un rbol insertando elementos ordenados

El objetivo es crear un rbol aadiendo progresivamente los nodos en l. Los nuevos nodos siempre se
aaden como hojas (los nodos que no tienen ningn hijo), no como nodos internos. Se reparten segn
los valores alfabticos. El algoritmo es el siguiente: partiendo desde la raz, si el valor lexicogrfico de
la letra del nodo nuevo es menor que el del nodo actual, seguir por el hijo izquierdo; si no, seguir por el
hijo derecho. As, hasta llegar a una hoja. Para poder enlazar el nuevo nodo, hay que conservar durante
el descenso la direccin del nodo anterior para tener la direccin de la hoja final en la que se colgar el
nuevo nodo, que a su vez se convertir en hoja:

void insertar(t_nodo**raiz,t_nodo*n)
{
t_nodo*x,*ant;
// si no hay raz, n se convierte en raz
if(*raiz==NULL)
*raiz=n;
// si no, descender hasta llegar a una hoja (hijos i y d a NULL)
else{
x=*raiz;// para descender
ant=NULL;// para conservar la posicin anterior
while(x!=NULL){
ant=x; // guarda la posicin anterior
x=(strcmp(n->dat,x->dat)<0)?x->i:x->d;// desciende segn n
}
// al final, enlazarse
if (strcmp(n->dat,ant->dat)<0)
ant->i=n;
else
ant->d=n;
}
}
2. Recorrer un rbol

El recorrido de un rbol es importante para poder consultar sus datos. Existen dos modos de recorrido
de rboles: el recorrido en profundidad y el recorrido en amplitud.

a. Recorrido en profundidad
Para el recorrido en profundidad, cada nodo se visita tres veces. De hecho, hay tres posibilidades para
consultar los datos de los nodos: al ir, en el primer retorno o en el segundo retorno, lo que, yendo de
izquierda a derecha, genera los tres recorridos siguientes:

preorden:
inorden:
postorden:
images/06ri62.png
images/06ri62a.png
images/06ri62b.png
Los datos se visitan en el primer encuentro con el nodo, antes de recorrer el subrbol izquierdo.
Los datos se visitan despus de recorrer el subrbol izquierdo, antes de recorrer el subrbol derecho.
Los datos se visitan despus del recorrido del subrbol izquierdo y del subrbol derecho.
Es el mismo principio que se aplica si se va al revs, empezando por la derecha. El recorrido ser
entonces en sentido contrario. En los siguientes ejemplos, presentaremos siempre el recorrido
izquierda-derecha.

Recorrido en profundidad con preorden

images/06ri63.PNG
Desde el punto de vista algortmico, hay dos posibilidades: un algoritmo recursivo y una versin
iterativa de este algoritmo. La versin iterativa usa una pila. Estos algoritmos son los mismos para un
rbol esttico o un rbol dinmico; simplemente cambia un poco la escritura. A continuacin
mostramos las dos versiones:

Algoritmo recursivo

Versin dinmica

void preorden(t_nodo*r)
{
if(r!=NULL){
printf("%s",r->dat);
preorden(r->i);
preorden(r->d);
}
}
Versin esttica

void preorden_s(t_nodos t[],int r)


{
if(r!=-1){
printf("%s",t[r].dat);
preorden_s(t,t[r].i);
preorden_s(t,t[r].d);
}
}
Algoritmo iterativo, gestin pila
preorden_iter(t_nodo*r)
{
apilar(r);
while(!pila_vacia()){
r=desapilar();
visitar(r);
// atencin inversin
// derecha primero
// y despus izquierda
if(r->d) apilar(r->d);
if(r->i) apilar(r->i);
}
}
La versin iterativa puede implementarse sin desarrollar aparte un mdulo de gestin de pilas. Una pila
es una tabla y una cima, y es posible obtener el tamao del rbol (la funcin que devuelve el tamao
del rbol, es decir, el nmero total de nodos que lo componen, se detalla en la seccin Obtener
propiedades de un rbol binario - Averiguar el tamao). Solo falta asignar memoria dinmica a una
tabla de nodos y gestionar el ndice que avanza de uno en uno si se aaden elementos o se decrementa
de uno en uno si se quitan elementos.

void preorden_iter2(t_nodo*r)
{
t_nodo**pila;
int n=0;
if(r==NULL)
printf("rbol inexistente");
else{
pila=(t_nodo**)malloc(sizeof(t_nodo*)*tam_din(r));
pila[n++]=r;
while(n>0){
r=pila[--n];
printf("%d ",r->val);
if (r->d)
pila[n++]=r->d;
if (r->i)
pila[n++]=r->i;
}
free(pila);
}
putchar(\n);
}
Recorrido en profundidad con inorden

images/06ri64.PNG
A continuacin se muestra el recorrido con inorden; las modificaciones que hay que realizar para un
rbol esttico son las mismas que hemos visto anteriormente para un recorrido con preorden.

Algoritmo recursivo
Versin dinmica

void inorden(t_nodo*r)
{
if(r!=NULL){
inorden(r->i);
printf("%s",r->dat);
inorden(r->d);
}
}
Algoritmo iterativo

En teora siempre es posible suprimir la recursividad para establecer una versin iterativa. Pero el
algoritmo iterativo de recorrido con inorden es ms difcil de implementar y raramente se usa.

Recorrido en profundidad con postorden

images/06ri65.PNG
A continuacin se muestra el algoritmo de recorrido con postorden; las modificaciones que hay que
realizar para la versin con un rbol esttico son siempre las mismas:

Algoritmo recursivo

Versin dinmica

void postorden(t_nodo*r)
{
if(r!=NULL){
postorden(r->i);
postorden(r->d);
printf("%s",r->dat);
}
}
Algoritmo iterativo

En este caso tambin existe una versin iterativa, pero casi nunca se usa y adems no es fcil de
implementar.

b. Recorrido en amplitud, por niveles

El recorrido en amplitud consiste en ir recorriendo los nodos por niveles desde la raz hasta las hojas y
cada nodo se visita una sola vez:

images/06ri66.png
Algoritmo iterativo solamente

El algoritmo es idntico a la versin iterativa del recorrido en profundidad con preorden, pero con el
uso de una cola. La cola de espera contiene al principio la raz. El elemento se extrae de la cola y se
aaden sus hijos hasta que se vace la cola:

Versin dinmica

void amplitud(t_nodo*r)
{
apilar(r);
while(!cola_vacia(r)){
r=avanzar();
visitar(r);
if (r->i) encolar(r->i);
if (r->d) encolar(r->d);
}
}
No es necesario desarrollar un mdulo entero de gestin de colas. Sabemos el tamao mximo de la
cola: es el tamao del rbol. Basta con asignar memoria dinmicamente a una tabla de punteros a nodo
y gestionar el ndice del primero para las salidas y el ndice del ltimo para las entradas.

void amplitud_iter(t_nodo*r)
{
t_nodo**cola;
int q=0,t=0;
if(r==NULL)
printf("rbol inexistente");
else{
cola=(t_nodo**)malloc(sizeof(t_nodo*)*tam_din(r));
cola[t++]=r;
while(q!=t){
r=cola[q++];
printf("%d ",r->val);
if (r->i)
cola[t++]=r->i;
if (r->d)
cola[t++]=r->d;
}
free(cola);
}
putchar(\n);
}
3. Mostrar el rbol

Se trata de visualizar el rbol simplemente para poder controlar su contenido.

a. Mostrar un rbol con indentacin

En este ejemplo se muestra la forma correcta de indentar un rbol mediante un recorrido en


profundidad con preorden. El rbol del ejemplo genera la siguiente visualizacin en consola:

images/06ri67.png
A continuacin se muestra el algoritmo con las versiones para rbol dinmico y para rbol esttico:

rbol dinmico

void mostrar_indent(t_nodo*r,int nivel)


{
int i;
if (r!=NULL){
for(i=0; i<nivel; i++) // 5 espacios por nivel
printf("%5s"," ");
printf("%s\n",r->dat);
mostrar_indent(r->i,nivel+1);
mostrar_indent(r->d,nivel+1);
}
}
rbol esttico

void mostrar_indent_est(t_nodos*t[],int r,int nivel)


{
if (r!=-1){
for(i=0; i<nivel; i++) // 5 espacios por nivel
printf("%5s"," ");
printf("%s",t[r].dat);
mostrar_indent_est(t,t[r].i,nivel+1);
mostrar_indent_est(t,t[r].d,nivel+1);
}
}
b. Dibujar el rbol sin los enlaces

Dibujar el rbol en forma de rbol, como en la parte izquierda de la imagen anterior, es una forma ms
natural y legible, pero para ello hay que ubicar los nodos en el espacio de la ventana de consola. Por lo
tanto, hay que disponer de las coordenadas horizontales y verticales adecuadas para cada nodo y de una
funcin que desplace el cursor en escritura a la posicin correcta en la pantalla. Se usa la funcin
gotoxy():

// desplazar el cursor en modo escritura en la ventana de consola:


void gotoxy(int x, int y)
{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
COORD i;
i.X = x;
i.Y = y;
SetConsoleCursorPosition (h, c);
}
Las funciones GetStdHandle() y SetConsoleCursorPosition () requieren el include de:

#include <windows.h>
rbol dinmico
A continuacin se muestra el algoritmo para el diseo de un rbol dinmico:

void dibujar_arbol(t_nodo*r, int*x, int y)


{
if (r!=NULL){
dibujar_arbol(r->i, x,y+2); // vertical
*x+=5; // horizontal
gotoxy(*x,y);
printf("%s",r->dat);
dibujar_arbol(r->d, x,y+2);
}
}
Al inicio, x e y se inicializan con los valores del borde izquierdo del rbol en x y de la posicin vertical
de partida en y.

4. Obtener propiedades de un rbol binario

A continuacin se muestra una serie de funciones que pueden ser tiles en diferentes circunstancias.
Todas son recursivas. Hemos utilizado un entero para los datos simblicos de los nodos y proponemos
cada vez una versin para un rbol dinmico y otra versin para un rbol esttico:

// rbol dinmico
typedef struct nodo{
int val;
struct nodo*i, *d;
}t_nodo;
// rbol esttico
typedef struct nodos{
int val;
int i, d;
}t_nodos;
a. Averiguar el tamao

A continuacin se muestra una funcin que devuelve el nmero total de nodos:

rbol dinmico

int tam_din(t_nodo*r)
{
int res=0;
if (r!=NULL)
res= 1 + tam_din(r->i) + tam_din(r->d);
return res;
}
rbol esttico

int tam_est(t_nodos t[],int r)


{
int res=0;
if (r!=-1)
res= 1 + tam_est(t,t[r].i) + tam_est(t,t[r].d);
return res;
}
b. Averiguar la altura

int max(int v1, int v2)


{
return (v1>v2)?v1:v2;
}
rbol dinmico

int altura_din(t_nodo*r)
{
int h=0;
if(r!=NULL)
h= 1 + max(altura_din(r->i),altura_din(r->d));
return h;
}
rbol esttico

int altura_est(t_nodos t[],int r)


{
int h=0;
if(r!=-1)
h= 1 + max(altura_est(t,t[r].i),altura_est(t,t[r].d));
return h;
}
c. Averiguar si un nodo es una hoja

rbol dinmico

int hoja_din(t_nodo*r)
{
return (r->i==NULL && r->d==NULL);
}
rbol esttico

int hoja_est(t_nodos t[],int r)


{
return (t[r].i==-1 && t[r].d==-1);
}
d. Contar el nmero de hojas de un rbol

rbol dinmico

int contarHojas_din(t_nodo*r)
{
int num=0;
if (r!=NULL){
if (hoja_din(r))
num=1;
else
num=contarHojas_din(r->i)+contarHojas_din(r->d);
}
return num;
}
rbol esttico

int contarHojas_est(t_nodos t[],int r)


{
int num=0;
if (r!=-1){
if (hoja_est(t,r))
num=1;
else
num = contarHojas_est(t,t[r].i)
+ contarHojas_est(t,t[r].d);
}
return num;
}
e. Listar todas las hojas

rbol dinmico

void listarHojas_din(t_nodo*r)
{
if (r!=NULL){
if (hoja_din(r))
printf("%d\n",r->val);
listarHojas_din(r->i);
listarHojas_din(r->d);
}
}
rbol esttico

void listarHojas_est(t_nodos t[],int r)


{
if (r!=-1){
if (hoja_est(t,r))
printf("%d\n",t[r].val);
listarHojas_est(t,t[r].i);
listarHojas_est(t,t[r].d);
}
}
f. Calcular la suma de valores de los nodos

rbol dinmico
int sumaVal_din(t_nodo*r)
{
int res=0;
if (r!=NULL)
res = sumaVal_din(r->i)
+ sumaVal_din(r->d)
+ r->val;
return res;
}
rbol esttico

int sumaVal_est(t_nodos t[],int r)


{
int res=0;
if (r!=-1)
res = sumaVal_est(t,t[r].i)
+ sumaVal_est(t,t[r].d)
+ t[r].val;
return res;
}
g. Comparar valores de los nodos del rbol

rbol dinmico

int maxVal_din(t_nodo*r)
{
int res=0;
if (r!=NULL){
res = max(maxVal_din(r->i),maxVal_din(r->d));
res = max(res,r->val);
}
return res;
}
rbol esttico

int maxVal_est(t_nodos t[],int r)


{
int res=0;
if (r!=-1){
res = max(maxVal_est(t,t[r].i),maxVal_est(t,t[r].d));
res = max(res,t[r].val);
}
return res;
}
h. Obtener un nodo del rbol a partir de un valor

rbol dinmico
t_nodo* buscaVal_din(t_nodo*r, int val)
{
t_nodo*res=NULL;
if (r!=NULL){
if (r->val==val)
res=r;
else{
res=buscaVal_din(r->i,val);
if (res==NULL)
res=buscaVal_din(r->d,val);
}
}
return res;
}
rbol esttico

int buscaVal_est(t_nodos t[],int r, int val)


{
int res=-1;
if (r!=-1){
if (t[r].val==val)
res=r;
else{
res=buscaVal_est(t,t[r].i,val);
if (res==-1)
res=buscaVal_est(t,t[r].d,val);
}
}
return res;
}
5. Clonar el rbol

rbol dinmico

t_nodo* clonaArbol_din(t_nodo*r)
{
t_nodo*res=NULL;
if (r!=NULL){
res=(t_nodo*)malloc(sizeof(t_nodo));
res->val=r->val;
res->i=clonaArbol_din(r->i);
res->d=clonaArbol_din(r->d);
}
return res;
}
rbol esttico

t_nodos* clonaArbol_est(t_nodos t[],int r)


{
t_nodos* res;
int tam;
tam=tam_est(t,r);
res=(t_nodos*)malloc(sizeof(t_nodos)*tam);
memcpy(res,t,sizeof(t_nodos)*tam);
return res;
}
6. Destruir el rbol

rbol dinmico

void destruirArbol_din(t_nodo**r)
{
if(*r!=NULL){
destruirArbol_din(&(*r)->i);
destruirArbol_din(&(*r)->d);
free(*r);
*r=NULL;
}
}
rbol esttico

void destruirArbol_est(t_nodos**r)
{
if(*r!=NULL){
free(*r);
*r=NULL;
}
}
7. Conversin esttica-dinmica de un rbol binario

La idea es poder pasar de un rbol dinmico a un rbol esttico y viceversa, de uno esttico a uno
dinmico. Estas operaciones de conversin son importantes si el programa utiliza un rbol dinmico y
tiene que guardarlo en disco. En efecto, no se puede guardar un rbol dinmico. Obligatoriamente debe
convertirse en esttico para guardarlo y en dinmico para cargarlo.

a. Conversin de un rbol esttico a uno dinmico

t_nodo* estADin(t_nodos*rs,int r)
{
t_nodo*rd=NULL;
if (r!=-1){
rd=(t_nodo*)malloc(sizeof(t_nodo));
rd->val=rs[r].val;
rd->i=estADin(rs,rs[r].i);
rd->d=estADin(rs,rs[r].d);
}
return rd;
}
b. Conversin de un rbol dinmico a uno esttico

El algoritmo para la conversin de un rbol dinmico a un rbol esttico se compone de las siguientes
cuatro operaciones:

1 - obtener el tamao del rbol dinmico.


2 - crear una tabla dinmica de igual tamao de nodos estticos.
3 - copiar los datos de cada nodo con la estructura del rbol.
4 - agrupar 1, 2 y 3 en una sola funcin.
Con lo que obtenemos:

int tam_din(t_nodo*rd)
{
int res=0;
if (rd!=NULL)
res= 1 + tam_din(rd->i) + tam_din(rd->d);
return res;
}

t_nodos*asignar_tab(t_nodo*rd)
{
t_nodos*rs=NULL;
if (rd)
rs=(t_nodos*)malloc(sizeof(t_nodos)*tam_din(rd));
return rs;
}
int copia_din_est(t_nodo*rd,t_nodos*rs,int *pos)
{
int res=-1;
if(rd!=NULL){
res=*pos;
(*pos)++;
rs[res].val=rd->val;
rs[res].i=copia_din_est(rd->i,rs,pos);
rs[res].d=copia_din_est(rd->d,rs,pos);
}
return res;
}

t_nodos* dinAEst(t_nodo*rd)
{
t_nodos*rs=NULL;
int pos=0;
rs=asignar_tab(rd);
copia_din_est(rd,rs,&pos);
return rs;
}
8. Guardar y cargar un rbol binario
a. Guardar un rbol dinmico

Si el rbol es dinmico, es necesario convertirlo en esttico para poder guardarlo debido a la prdida de
direcciones de memoria. La funcin de guardado invoca la conversin dinAEst().

void guardarArbol_din(t_nodo*rd)
{
FILE*f;
int num;
t_nodos*rs;
f=fopen("arbol.bin","wb");
if (f==NULL||rd==NULL)
printf("error con el archivo o rbol inexistente\n");
else{
rs=dinAEst(rd);
num=tam_din(rd);
fwrite(rs,sizeof(t_nodos),num,f);
fclose(f);
}
}
b. Cargar un rbol dinmico

Lo primero que hay que hacer es leer un nodo en una posicin determinada del archivo. Esta es la
utilidad de la funcin leerNodo() que se presenta a continuacin:

void leerNodo(FILE*f, int pos, t_nodos*enr)


{
fseek(f,sizeof(t_nodos)*pos,SEEK_SET);
fread(enr,sizeof(t_nodos),1,f);
}
A continuacin, hay que convertir en dinmico cada nodo obtenido del archivo. Lo realizaremos con la
funcin fileToDin():

t_nodo* fileToDin(FILE*f, int r)


{
t_nodo*n=NULL;
t_nodos enr;
if(r!=-1){
leerNodo(f,r,&enr);
n=(t_nodo*)malloc(sizeof(t_nodo));
n->val=enr.val;
n->i=fileToDin(f, enr.i);
n->d=fileToDin(f, enr.d);
}
return n;
}
La funcin cargaArbol_din() se encarga a continuacin de la gestin de la apertura y del cierre del
archivo en modo lectura y de la conversin del rbol a dinmico en el programa:
t_nodo* cargaArbol_din()
{
FILE*f;
t_nodo*rd=NULL;
if ((f=fopen("arbol.bin","rb"))==NULL)
printf("problema con la apertura del archivo\n");
else{
rd=fileToDin(f, 0);
fclose(f);
}
return rd;
}
9. rboles binarios en archivos

Lo que pretendemos es tener un rbol esttico en un archivo, y no en RAM. Los nodos se guardan
como en una tabla, unos tras otros, y se designan por su nmero de orden en el archivo.

a. Estructura de datos

Las posiciones en el archivo de los hijos izquierdo y derecho de un nodo se designan con valores
enteros, lo que genera la estructura de datos siguiente:

// nodo rbol esttico


typedef struct nodos{
int val;
int i, d; // posicin de los hijos de un nodo en el archivo
}t_nodos;
b. Lectura de un nodo a partir de su nmero de registro

Para acceder a un nodo a partir de su posicin en el archivo, hay que usar la funcin estndar fseek() y
desplazarse en el archivo a sizeof(t_nodos)*posicin. A continuacin, solo falta utilizar la funcin
estndar fread() para copiar el nodo deseado. Es lo que realiza la funcin leerNodo():

void leerNodo(FILE*f, int niesimo, t_nodos*enr)


{
fseek(f,sizeof(t_nodos)*niesimo,SEEK_SET);
fread(enr,sizeof(t_nodos),1,f);
}
c. Adaptacin de las funciones para rboles dinmicos o estticos

Todas las funciones definidas para rboles binarios pueden adaptarse para el uso de un rbol en un
archivo. A continuacin se muestra la funcin de visualizacin indentada de un rbol en un archivo a
partir de un recorrido en profundidad con preorden:

void mostrar_indent_arch(FILE*f, int r,int nivel)


{
t_nodos enr;
int i;
if (r!=-1){
leerNodo(f,r,&enr);
for (i=0; i<nivel; i++)
printf("%5s"," ");
printf("%d",enr.val);
mostrar_indent_arch(f,enr.i,nivel+1);
mostrar_indent_arch(f,enr.d,nivel+1);
}
}
En cada operacin con un nodo, hay que obtenerlo mediante el uso de un nodo auxiliar en RAM que
permite leer sus datos. Es la funcin de la variable t_nodos enr. Es lo que hay que aadir a todas las
funciones que hemos visto anteriormente para adaptarlas a la gestin de un rbol en un archivo.

10. Puesta en prctica: rbol binario

Ejercicio 1

Realizar un generador de rboles binarios DINMICOS que contengan nmeros aleatorios. Generar un
rbol. Recorrerlo en profundidad (con los tres tipos de orden). Recorrerlo en amplitud. En cada
operacin, mostrar el rbol. Obtener todas las propiedades del rbol (tamao, altura, nmero de hojas,
listar las hojas y suma de los nodos). Devolver un nodo segn un valor dado. Guardar el rbol en un
archivo. Destruir el rbol. Cargar el rbol.

Ejercicio 2

Realizar un generador de rboles binarios ESTTICOS que contengan nmeros aleatorios. Generar un
rbol. Recorrerlo en profundidad (con los tres tipos de orden). Recorrerlo en amplitud. En cada
operacin, mostrar el rbol. Obtener todas las propiedades del rbol (tamao, altura, nmero de hojas,
listar las hojas y suma de los nodos). Devolver un nodo segn un valor dado. Guardar el rbol en un
archivo. Destruir el rbol. Cargar el rbol.

Ejercicio 3

Realizar un generador de rboles binarios EN ARCHIVO que contengan nmeros aleatorios. Generar
un rbol. Recorrerlo en profundidad (con los tres tipos de orden). Recorrerlo en amplitud. En cada
operacin, mostrar el rbol. Obtener todas las propiedades del rbol (tamao, altura, nmero de hojas,
listar las hojas y suma de los nodos). Devolver un nodo segn un valor dado. Guardar el rbol en un
archivo. Destruir el rbol. Cargar el rbol.

Ejercicio 4

Representar el rbol genealgico de una familia (de su eleccin o inventada). Los datos pueden
proporcionarse mediante un archivo de texto del estilo:

Sonia: Pol, Catalina, Bernardo;

Pol: Isabel, Armando;

Catalina: Matas, Elosa, Mriam;


etc.

Ejercicio 5

Una expresin aritmtica como (14 * 5) - (7 / 10) puede adquirir la forma de un rbol. En un programa:

Construir un rbol a partir de una expresin aritmtica.


Escribir una funcin de evaluacin aritmtica a partir de un rbol y devolver el resultado.
Escribir una funcin que muestre la traduccin en expresin posfija.
Una expresin posfija (notacin polaca inversa) es del tipo:
5 17 6 - 4 * 2 + *
los parntesis implcitos son:
( 5 ( ( ( 17 6 - ) 4 * ) 2 + ) * )
el equivalente en notacin clsica infija es:
( ( ( 17 - 6 ) * 4 ) + 2 ) * 5
Escribir una funcin para construir un rbol a partir de una expresin posfija determinada.
Escribir una funcin que construya un rbol a partir de una expresin aritmtica infija clsica (con
parntesis).
Ejercicio 6

Sea la descripcin siguiente:

hombre: cabeza, cuello, tronco, brazos, piernas

cabeza: crneo, ojos, oreja, pelo, boca

tronco: abdomen, trax

trax: corazn, hgado, pulmones

pierna: muslo, gemelo, pie

pie: tobillo, dedos, taln

brazo: hombro, antebrazo, mano

mano: dedos

En un programa, crear un rbol n-ario. Despus, generar el rbol binario correspondiente.

Con el modelo de esta descripcin, crear el rbol de los componentes de un coche. Siempre con este
modelo, analizar un elemento o un objeto de su eleccin y representar el rbol correspondiente en un
programa.

Ejercicio 7

Simular la gestin de un torneo de tenis con la forma de un rbol binario. Los datos de cada partido se
deben conservar. Al finalizar el torneo, el partido de la final se debe encontrar en la raz del rbol.
Ejercicio 8

Sea la descripcin siguiente:

Tierra: Europa, Asia, frica, Amrica, Oceana

Europa: Francia, Espaa, Italia, Alemania, Blgica

Asia: China, India, Japn

frica: Burkina-Faso, Costa de Marfil

En un programa, implementar el rbol n-ario y despus el binario correspondiente.

Con este modelo, crear, por ejemplo, el mapa de sus sitios favoritos.

Ejercicio 9

Objetivos: recorrer y aadir en un rbol binario

La representacin de un dominio cuyos conceptos son jerrquicos, en forma de rbol binario, se puede
aplicar tambin a tareas de clasificacin/identificacin (animales, plantas, minerales...), as como a
tareas de diagnstico (diagnstico mdico, deteccin de averas...). Por ejemplo:

images/06RI02.png
En este ejemplo, el ordenador plantea preguntas para intentar descubrir el animal que est pensando.
Solo puede responder S o NO. Si se equivoca, le har una pregunta para caracterizar al animal que no
ha podido encontrar, lo que permite realizar, en cierta manera, un aprendizaje. Encontrar un animal
consiste en recorrer un rbol binario cuyos nodos son las preguntas, y las hojas, los animales. La fase
de aprendizaje consiste, si el usuario lo desea, en aadir el animal no encontrado en el rbol (ver el
registro de ejecucin en la siguiente pgina).

Para guardar un registro de este rbol binario, se almacenar en un archivo.

Hay dos formas de encarar el problema: en un archivo de acceso directo o en memoria principal. Para
empezar, es mejor que cree un rbol dinmico (aunque si lo prefiere puede optar por usar un rbol en
archivo). Podr guardar el rbol en un archivo gracias a un recorrido en amplitud del rbol.

Hay que:

Definir en C la estructura de datos para un nodo del rbol.

En qu condicin o condiciones se sabe diferenciar un nodo interno de una hoja?


Cul es el orden de recorrido (preorden, inorden, postorden) del rbol mostrado en la ejecucin de la
pgina siguiente? Justificar. Dar los dos otros rdenes de recorrido.
Escribir, comentar y probar las funciones siguientes respetando la trazabilidad de la ejecucin de la
pgina siguiente:
Visualizacin de un rbol de juego.
Recorrido de un rbol de juego planteando las preguntas al usuario.
Aprendizaje de un nuevo animal mediante una pregunta y una respuesta.
Cualquier otra funcin necesaria.
Implementar el almacenamiento y la carga del rbol en el programa.
Para la trazabilidad de la ejecucin, se han usado caracteres normales para mostrar lo que aparece por
la pantalla del programa; los comentarios estn en itlica y las respuestas tecleadas por el usuario estn
subrayadas.

images/06RI03.png

rboles binarios de bsqueda

1. Definicin

En un rbol binario de bsqueda, todos los elementos estn dotados de una clave y se ubican en el rbol
en funcin de esta clave. La clave hace que el orden del rbol sea visible y usable. La clave en general
es un nmero que se aade al elemento. Es gracias a este nmero que el elemento se puede almacenar e
identificar en una ubicacin determinada en el rbol. A continuacin, todos los elementos pueden
encontrarse rpidamente gracias a su clave, sin que estn obligados a recorrer todo el rbol de forma
sistemtica. Los elementos almacenados en el rbol estn de algn modo ordenados en funcin de su
clave. Es fcil aadir nuevos elementos o borrar elementos sin modificar el orden del rbol.

Las claves siguen las reglas siguientes:

Para cada nodo del rbol:

Las claves del subrbol izquierdo (SAI) son menores que la clave de la raz.
Las claves del subrbol derecho (SAD) son menores que la clave de la raz.
Todas las claves son diferentes, no hay claves repetidas.
Por ejemplo, la serie de claves 17, 3, 15, 9, 27, 44, 2, 50, 30 genera el rbol siguiente:

images/06ri68.PNG
El rbol anterior requiere que los elementos se introduzcan en el orden de la serie, ya que la forma del
rbol depende del orden de insercin:

images/06ri69.png
Observacin:

Sea cual sea el orden en el que se han entrado los elementos en el rbol, con su clave, un recorrido en
profundidad con inorden del rbol siempre devolver una serie creciente de las claves.

De los ejemplos anteriores, estas son las series:

2, 3, 9, 15, 17, 27, 30, 44, 50


y
10, 20, 30
2. Estructura de datos

Nuestro objetivo es implementar un rbol binario de bsqueda con sus funciones usuales de
tratamiento. Se trata de un rbol dinmico. La clave puede ser un entero, pero a menudo se integra al
comienzo de una cadena de caracteres y vamos a implementar esta alternativa para escribir funciones
del rbol. Utilizaremos la siguiente estructura de datos:

typedef struct nodo{


char*dat;
struct nodo*i,*d;
}t_nodo;
Para mostrar los datos de forma un poco ms grfica en el espacio de la ventana de consola,
utilizaremos las funciones proporcionadas en el anexo.

Todos los rboles se generan automticamente con datos aleatorios en esta seccin e impondremos un
lmite al tamao en nodos del rbol. Esta es la macro:

#define MAXnodos 15
Para visualizar el rbol con aspecto de rbol (pero sin los enlaces), la distancia vertical de los nodos se
fija con la macro:

#define SALTOY 2
3. Insertar un elemento en el rbol segn su clave

La insercin de un elemento siempre se efecta en la hoja del rbol, nunca al nivel de los nodos
internos. Para saber la ubicacin correcta donde se insertar, hay que descender en el rbol
orientndose en cada nodo en funcin de la clave. Si la clave del nuevo nodo es menor que la clave del
nodo actual del rbol, hay que ir hacia la izquierda; si no, hay que ir a la derecha, hasta llegar al nivel
de las hojas, el sitio correcto. La primera funcin que hay que escribir es una funcin que permita
comparar dos claves. Se usar cada vez que sea necesario buscar un elemento o una posicin en el
rbol segn una clave.

a. Comparar dos claves

La funcin recibe por parmetro las dos cadenas que contienen una clave y son solamente las claves lo
que se compara. Devuelve 0 si las claves son iguales, -1 si la primera es menor que la segunda y 1 si la
primera es mayor que la segunda. Para separar la clave del resto de la cadena y convertirla en entero,
hay varias posibilidades. Son las funciones strtol(), atol() y atoi(). Hemos elegido atoi(), que devuelve
el nmero entero en ascii del comienzo de la cadena en forma de entero numrico int. Ambos nmeros
se comparan a continuacin mediante el operador condicional:

int compararClave(char*dat1, char*dat2)// d1 comparada con d2


{
int d1,d2;
d1=atoi(dat1);
d2=atoi(dat2);
return(d1==d2)?0:((d1<d2)?-1:1);
}
b. Insertar un elemento en la ubicacin correcta

Para insertar en el rbol un elemento segn su clave, basta con partir de la raz y orientarse en cada
nodo en funcin de su clave comparndola con la del valor del nuevo nodo que se desea insertar. Si la
nueva clave es menor, hay que descender por el hijo izquierdo; si no, por el hijo derecho. No puede
haber una clave idntica, por definicin, en el rbol de bsqueda. El descenso contina hasta llegar a
una hoja, en el extremo inferior del rbol. Una vez se llega al sitio adecuado alcanzando un puntero a
NULL de la hoja, hay que crear un nodo, asignarle los datos y asignar la direccin de este nuevo nodo
al puntero de la hoja.

Al comienzo la funcin recibe por parmetro la raz del rbol. Como es susceptible de ser modificada,
se trata de un puntero pasado por referencia. Es la direccin del propio puntero la que se pasa por
parmetro, es decir, un puntero a puntero. La funcin es recursiva y este parmetro recibe
sucesivamente las direcciones de los punteros de los hijos izquierdos o derechos en funcin del camino
que sigue el descenso. Cuando el puntero contiene NULL, significa que se ha llegado y que se puede
asignar una direccin de memoria vlida a este puntero. Por otro lado, los datos se transmiten mediante
una cadena de caracteres char*dat.

void insertar(t_nodo**r,char*dat)
{
t_nodo*n;
int cmp;
if(*r==NULL){
n=(t_nodo*)malloc(sizeof(t_nodo));
n->dat=(char*)malloc(strlen(dat)+1); // +1 para \0
strcpy(n->dat,dat);
n->i=n->d=NULL;
*r=n;
}
// atencin: no tener 2 claves idnticas
else if( (cmp=compararClave(dat,(*r)->dat)) ==0)
printf("error: la clave %s ya existe\n",dat);
else if (cmp<0)
// paso de la direccin del puntero al hijo izquierdo
insertar(&(*r)->i,dat);
else
// paso de la direccin del puntero al hijo derecho
insertar(&(*r)->d,dat);
}
4. Buscar en un rbol un elemento segn su clave

Para buscar un elemento, basta con partir desde la raz con la clave del elemento que se desea buscar.
La funcin recibe por parmetro la raz y los datos que contienen la clave. La raz no se modificar;
solo se usa para realizar el recorrido. Por lo tanto, no es necesario pasarla por referencia. La funcin
devuelve la direccin del nodo que corresponde a la clave o NULL si esta clave no corresponde a
ningn elemento del rbol. La funcin es recursiva. Se llama a s misma hacia la izquierda o la derecha
mientras no se haya encontrado el nodo:

t_nodo* buscar(t_nodo*r, char*dat)


{
int cmp;
t_nodo*res=NULL;
if(r!=NULL){
if ((cmp=compararClave(dat,r->dat))==0)
res=r;
else if (cmp<0)
res=buscar(r->i, dat);
else
res=buscar(r->d,dat);
}
return res;
}
5. Eliminar un elemento del rbol de bsqueda

Eliminar una hoja de un rbol es bastante sencillo:

images/06ri70.PNG
Para eliminar el nodo 50, basta con poner el hijo derecho del nodo 44 a NULL. Tambin es sencillo
para un nodo que solo tenga un hijo. Por ejemplo:

images/06ri71.PNG
Para eliminar el nodo 15, basta con que el hijo derecho del nodo 7 apunte al nodo 9. Para eliminar el
nodo 27, hay que hacer que el hijo derecho del nodo 17 apunte al nodo 44. Estamos en la situacin de
una lista encadenada. Pero el borrado se complica si queremos eliminar un nodo que tenga dos hijos.
Por ejemplo, cmo eliminar el nodo 7?

images/06ri72.PNG
Qu hacer con los nodos 3 y 15? Cmo los enlazamos? La solucin consiste en:

Buscar al ms grande del subrbol izquierdo o al ms pequeo del subrbol derecho (5 o 9 para el nodo
7). Ellos sern necesariamente o una hoja o un nodo que solo tenga un nico hijo.
A continuacin, borrarlo conservando su valor.
Finalmente, copiar este valor al nodo que queremos eliminar.
a. Tres casos

En esta situacin tenemos tres casos. Queremos eliminar el nodo F, a la derecha de N en el esquema:

1) El nodo F no tiene hijo izquierdo:

images/06ri73.png
2) El nodo F no tiene hijo derecho:

images/06ri74.png
3) El nodo F tiene hijo izquierdo e hijo derecho:

images/06ri75.png
b. Funcin de bsqueda del nodo mayor

Para el caso n.3 necesitamos una funcin que devuelva el nodo ms grande del subrbol izquierdo o el
ms pequeo del subrbol derecho. Optamos por el ms grande del subrbol izquierdo. La funcin
devuelve este nodo y lo borra del rbol. Para obtenerlo, partimos al inicio hacia la izquierda y a
continuacin hay que ir hacia la derecha hasta caer en un NULL. Cuando la funcin ha encontrado este
nodo, lo devuelve mediante el mecanismo de retorno. Retorna NULL en caso de error.

Como la raz es susceptible de modificarse, se pasa por referencia. Debido a ello, el parmetro es un
puntero a puntero t_nodo**. La funcin es recursiva. En cada subllamada de la funcin, es la direccin
del puntero al hijo derecho del nodo actual la que se va pasando por parmetro. Las llamadas finalizan
cuando el puntero del hijo derecho del nodo actual apunta a NULL:

si ( (*r)->d==NULL ) entonces
*r da la direccin del nodo buscado
Esta direccin se devuelve y el puntero *r adquiere el valor del hijo izquierdo del subrbol, es decir,
que este nodo hijo derecho se borra del rbol. Esta direccin siempre ser vlida, escala toda la pila de
llamadas y se restituye en el contexto de la primera llamada de la funcin. A continuacin se muestra la
funcin:

t_nodo* devuelveNodoMax(t_nodo**r)
{
t_nodo*max=NULL;
if(*r!=NULL){
if((*r)->d==NULL){ // mximo encontrado
max=*r;
*r=(*r)->i;
}
else
max=devuelveNodoMax(&(*r)->d);
}
return max;
}
c. Funcin de borrado

La funcin de borrado es recursiva. Recibe la raz por parmetro y los datos con la clave del elemento
que se desea eliminar. Como la raz es susceptible de modificarse, se pasa por referencia. Es un puntero
a puntero t_nodo**.

El primer punto es encontrar el elemento que se desea eliminar del rbol y, para ello, hay que comparar
la clave del elemento que se desea eliminar con la del nodo actual. Si el resultado es menor que 0, la
funcin de borrado se llama a s misma yendo hacia la izquierda. Si por el contrario el resultado es
mayor que 0, la funcin de borrado se invoca a s misma yendo hacia la derecha. Si el resultado es igual
a 0, el nodo que se desea eliminar se ha encontrado (es *r) y debemos tratar uno de los tres casos
mencionados para el borrado de un nodo:

Si no tiene hijo derecho, considerar el hijo izquierdo como la continuacin del rbol.
Si no tiene hijo izquierdo, considerar el hijo derecho como la continuacin del rbol.
Si tiene hijo izquierdo e hijo derecho, entonces hay que eliminar al ms grande del subrbol izquierdo y
devolver su valor para copiarlo en (*r)->dat, es decir, en el elemento actual que se desea eliminar. Si la
cadena de caracteres dat es dinmica, hay que vigilar que sea suficientemente grande.
t_nodo* eliminaNodo(t_nodo**r,char*dat)
{
int cmp;
t_nodo*res=NULL;
if(*r!=NULL){
if ( (cmp=compararClave(dat,(*r)->dat))<0)
res=eliminaNodo(&(*r)->i,dat);
else if (cmp>0)
res=eliminaNodo(&(*r)->d,dat);
else{ // nodo encontrado, borrado
res=*r;
if (res->d==NULL)
*r=res->i;
else if (res->i==NULL)
*r=res->d;
else{ // si tiene un hijo izquierdo y un hijo derecho
// elimina el ms grande del subrbol izquierdo
res=devuelveNodoMax(&(*r)->i);
(*r)->dat=(char*)realloc((*r)->dat,strlen(res->dat)+1);
strcpy((*r)->dat,res->dat);
}
}
}
return res;
}
6. Listar todos los elementos del rbol (recorrido en amplitud)

La idea es recorrer una tabla que contenga todos los nodos del rbol almacenados por niveles en el
sentido de un recorrido en amplitud. El objetivo es obtener el tamao del rbol. A continuacin, hay
que asignar memoria para una tabla de t_nodo o de punteros t_nodo*. Optamos por la tabla de
punteros. El primer nodo del ndice 0 es la raz del rbol. A continuacin, se introducen los hijos
izquierdo y derecho de cada nodo que van entrando en cada tabla. Al finalizar se devuelve la direccin
de la tabla con memoria dinmica. La funcin es la siguiente:

t_nodo** listarArbol(t_nodo*r, int*numnodos)


{
int t,q;
t_nodo**tab=NULL;
if (*numnodos>0){
*numnodos=tam_din(r);
tab=(t_nodo**)malloc(sizeof(t_nodo*)*(*numnodos));
t=0;
q=1;
tab[t]=r; // raz al comienzo
while (q<*numnodos){
if (tab[t]->i !=NULL ) // si hay hijo izq. aadir
tab[q++]=tab[t]->i;
if (tab[t]->d != NULL) // si hay hijo der. aadir
tab[q++]=tab[t]->d;
t++; // pasar al siguiente
}
}
return tab;
}
La funcin para obtener el tamao, ya explicada, es la siguiente:

int tam_din(t_nodo*r)
{
int res=0;
if (r!=NULL)
res=1+tam_din(r->i)+tam_din(r->d);
return res;
}
Una vez se ha formado la lista, es sencillo mostrarla:

void muestra_lista(t_nodo*t[],int num)


{
int i;
for (i=0; i<num; i++){
printf("%s--",t[i]->dat);
}
putchar(\n);
}
o destruirla:

void destruirLista(t_nodo***l)
{
if(*l){
free(*l);
*l=NULL;
}
}

El borrado de la lista no borra el rbol. Ninguna de las direcciones de memoria de todos los elementos
del rbol se modifican.

7. Mostrar el rbol

La visualizacin del rbol con una correcta jerarqua se realiza a travs de un recorrido en profundidad
con inorden mediante dos variables, una para la posicin horizontal y otra para la posicin vertical. La
posicin horizontal depende del tamao de la cadena de caracteres de cada nodo. Adems, la
progresin horizontal no es regular ni constante, no hay ningn nodo en la misma posicin horizontal.
Por este motivo, es necesario un paso por referencia que permita almacenar progresivamente la antigua
posicin. La progresin vertical es relativa a cada contexto de llamada de la funcin segn el nivel de
anidacin de las llamadas recursivas. Se define por la macro SALTOY.

void muestraInorden(t_nodo*r, int*x, int y)


{
if (r!=NULL){
muestraInorden(r->i,x,y+SALTOY);
gotoxy(*x,y);
printf("%s",r->dat);
*x+=strlen(r->dat);//SALTOX;
muestraInorden(r->d,x,y+SALTOY);
}
}
La visualizacin completa del rbol requiere saber dnde se encuentra el cursor de escritura al
comienzo para la posicin vertical. La posicin horizontal se da: 0 respecto al extremo izquierdo. Se
muestra el rbol y despus se desplaza el cursor debajo del rbol hacia el extremo izquierdo y se
muestra la altura del rbol.

void muestraArbol(t_nodo*r)
{
int y=wherey()+1;
int x=0;
muestraInorden(r,&x,y);
gotoxy(0,y+(altura(r)*SALTOY));
printf("altura del rbol: %d\n",altura(r));
}
Recordatorio de la funcin que permite obtener la altura del rbol:

int altura(t_nodo*r)
{
int res=0;
if (r!=NULL)
res=1+max(altura(r->i),altura(r->d));
return res;
}
8. Prueba en el main()

El programa de prueba ofrece las siguientes acciones:

int menu()
{
int res=-1;
printf( "1: crear rbol de bsqueda y mostrarlo \n"
"2: buscar\n"
"3: borrar\n"
"4: listar\n"
);
scanf("%d",&res);
rewind(stdin);
return res;
}
A continuacin se muestra el main completo, con las llamadas a las funciones para las acciones
propuestas:

int main()
{
int fin=0,num;
t_nodo*r=NULL;
t_nodo*n=NULL;
t_nodo**t=NULL;
char clave[16];

srand(time(NULL));
while(!fin){
switch(menu()){
case 1 :
destruirArbol(&r);
r=crearArbol_Alea(1+rand()%MAXnodos);
muestraArbol(r);
break;

case 2 :
printf("introducir la clave del nodo que desea encontrar: \n");
fgets(clave,16,stdin);
n=buscar(r, clave);
if (n!=NULL)
printf("encontrado: %s\n",n->dat);
else
printf("no se ha encontrado en el rbol\n");
break;

case 3 :
printf("introducir la clave del nodo que desea eliminar: \n");
fgets(clave,16,stdin);
n=eliminaNodo(&r,clave);
if (n!=NULL){
free(n->dat);
free(n);
}
muestraArbol(r);
break;

case 4 :
t=listarArbol(r, &num);
muestra_lista(t,num);
destruirLista(&t);
break;

default : fin=1;
}
}
destruirArbol(&r);
return 0;
}
9. Puesta en prctica: rboles
Ejercicio 1

Generar un rbol binario de bsqueda. Buscar en su interior un elemento por su clave. Eliminar un
elemento. Mostrar el rbol. Calcular la suma de sus elementos. Listar todos los elementos del rbol, etc.

Ejercicio 2

Implementar un contador de apariciones de palabras a partir de un texto proporcionado por ejemplo en


un archivo de texto.

C incluido en C++
Todo lo que hemos visto sobre el lenguaje C en los captulos anteriores est incluido en C++, es decir:

Variables simples, char, short, int long, float, double, signed, unsigned
Operaciones aritmticas +, -, *, /, %
Operaciones bit a bit &, |, , ~
Operadores de comparacin <, >, <=, >=, !=, ==
Saltos condicionales if, if - else, if - else if - else
Caminos alternativos switch, goto
Comprobaciones multicondicionales &&, ||
Bucles while, do-while, for
Funciones, retorno y parmetros
Tuplas struct
Definicin de tipos con typedef
Tablas [ ]
Punteros &, ,, -> , [ ], malloc, free, NULL, void*
Los fundamentos y la sintaxis son rigurosamente los mismos. Un programa escrito en C puede
compilarse en C++.

Para comprobarlo, vamos a portar a C++ el programa del autmata celular que hemos trabajado en el
captulo Variables de conjunto (tuplas y tablas) - Estructuracin de un programa, estudio de un
autmata celular. El primer punto es implementar un proyecto C++ segn el entorno de desarrollo
(IDE) y el compilador con los que se trabaje.

1. Un proyecto de consola en C++

En CodeBlocks y Mingw32 seleccionamos un proyecto de consola. Por defecto, obtenemos el siguiente


main:

#include <iostream>
using namespace std;

int main()
{
cout << "Hello world!" << endl;
return 0;
}
No hay ninguna diferencia fundamental en la estructura del main(). Solo algunas variaciones a nivel de
inclusiones (#include) y la curiosa funcin de visualizacin cout<< <<endl que remplaza al clsico
printf() de C (su uso se presenta ms adelante). La extensin del archivo fuente pasa de punto c a punto
ccp.

En Visual C++ de Microsoft hay dos posibilidades para crear un proyecto de consola en C++. La
primera consiste en seleccionar un proyecto de consola en la ventana Nuevo Proyecto. Esta solucin
provoca la aparicin de un proyecto de consola con el C++ no estndar especfico de Microsoft. Para
permanecer en C++ estndar, que es el objetivo de este libro, hay que seleccionar un proyecto en
blanco, crear una pgina de cdigo cpp y copiar el cdigo anterior.

Si compilamos el proyecto, obtenemos una ventana de consola con Hello world! arriba a la izquierda
de la pantalla.

a. Librera <iostream>

El archivo de cabecera <iostream> es equivalente a <iostream.h>. El punto h ha desaparecido con la


norma ISO/IEC 14882-1998, pero ambas escrituras siguen funcionando an en la mayora de los
compiladores. Utilizaremos la versin sin punto h, la ms reciente.

Iostream es una de las muchas libreras estndar de C++. Permite la utilizacin de cout<< << y cin >>
>>, que en realidad son objetos.

Una librera C++ contiene, en efecto, no solo funciones y definiciones de tipos como en C, sino que
tambin, y sobre todo, clases. Las clases son especficas de la programacin orientada a objetos. La
dimensin del uso de objetos es la aportacin importante de C++. La abordaremos en la ltima parte de
este captulo.

b. La instruccin using namespace std;

Un espacio de nombres (namespace) permite agrupar en un solo nombre un conjunto de archivos de


cabecera (libreras) o elementos de cdigo fuente que pueden estar repartidos en varios archivos.
Practicaremos cmo crear sus propios espacios de nombres ms adelante.

La palabra clave using asociada a un espacio de nombres permite crear un atajo para acceder a los
elementos albergados en este conjunto.

El espacio de nombres std agrupa 50 archivos de cabecera formados por 18 archivos de cabecera de la
biblioteca C estndar y por otros 32 archivos, llamados cabeceras STL (Standard Template Library).
Puede encontrar la documentacin relacionada con este tema en Internet, por ejemplo en
http://www.cplusplus.com.

La instruccin using namespace std; permite incluir, de este modo, estas 50 libreras para poder
usarlas en el proyecto. Todas estas libreras son visibles en la carpeta dependencias externas de un
proyecto visual C++.

Es la norma ISO/IEC 14882-1998 la que especifica que los archivos C comiencen con la letra c. De
este modo, stdio.h pasa a ser cstdio.h, y los 32 archivos C++ pierden la extensin .h; iostream.h pasa a
ser iostream.
2. Un programa C compilado en C++

Para comprobar que C compila en C++, a continuacin usaremos el programa del autmata celular
estudiado anteriormente. Hemos modificado solo las matrices. Las hemos transformado de estticas a
dinmicas, lo que permitir comprobar el funcionamiento de la asignacin dinmica de memoria y de
los punteros. Adems, hemos aadido al autmata la posibilidad de cambiar de color con el tiempo.

Cada posicin conserva en memoria el nmero de veces que se ha activado (ha pasado a 1) y ahora es
este nmero el que le da el color a la posicin. Por este motivo, hay que utilizar dos variables para cada
posicin: una, como anteriormente, indica el estado de la posicin, y otra que indica el nmero de veces
que ha pasado a 1 y da su color. Hemos agrupado estos dos datos en una tupla que contiene dos enteros
y las matrices ahora son matrices de tuplas.

Para compilar en C++ este programa, basta con copiar-pegar este cdigo en el proyecto C++
conservando las cabeceras C++, con lo que obtenemos:

#include <iostream>
using namespace std;

#include <stdio.h>
#include <windows.h>
#include <conio.h>

typedef struct pos{


int val; // 0 o 1
int color;
}t_pos;

int TX;
int TY;

t_pos**MAT; //matrices dinmicas de tuplas


t_pos**SEV;

void inicializa_matriz (int tx, int ty);


void destruye_mat (void);
int cuenta_vecinos (int y, int x);
void calcular (void);
void copiar (void);
void mostrar (void);
void gotoxy (int x, int y);
void textcolor (int color);
/******************************************/
int main(int argc, char *argv[])
{
int fin=0;

printf("Accin: pulsar cualquier tecla\n"


"Salir: q");
inicializa_matriz(80,23);
while(fin!=q){

if(kbhit()){
fin=getch();
mostrar();
calcular();
copiar();
}
}
destruye_mat();

gotoxy(1,TY+1);
textcolor(16);
return 0;
}
/******************************************/
void inicializa_matriz(int tx, int ty)
{
int i ;
TX=tx;
TY=ty;
MAT=(t_pos**)malloc(sizeof(t_pos*)*TY);
SEV=(t_pos**)malloc(sizeof(t_pos*)*TY);
for (i=0; i<TY;i++){
MAT[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
SEV[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
memset(MAT[i],0,sizeof(t_pos)*TX);
memset(SEV[i],0,sizeof(t_pos)*TX);
}
MAT[TY/2][TX/2].val=1;
MAT[TY/2+1][TX/2].val=1;
MAT[TY/2][TX/2+1].val=1;
MAT[TY/2+1][TX/2+1].val=1;

MAT[TY/2][TX/2].color=1;
MAT[TY/2+1][TX/2].color=1;
MAT[TY/2][TX/2+1].color=1;
MAT[TY/2+1][TX/2+1].color=1;
}
/******************************************/
void destruye_mat()
{
int i;
for (i=0; i<TY; i++){
free(MAT[i]);
free(SEV[i]);
}
free(MAT);
free(SEV);
}
/******************************************/
void calcular()
{
int x,y,num_vecinos;

for (y=1; y<TY-1; y++){


for (x=1 ;x<TX-1 ;x++){
num_vecinos=cuenta_vecinos(y,x);
if (num_vecinos <2 || num_vecinos>3){
SEV[y][x].val=0;
SEV[y][x].color = MAT[y][x].color;
}
else{
SEV[y][x].val=1;
if (MAT[y][x].color<15)
SEV[y][x].color = MAT[y][x].color+1;
}
}
}
}
/******************************************/
int cuenta_vecinos(int y, int x)
{
int num=0;

if (MAT[y][x+1].val==1)
num++;
if (MAT[y-1][x+1].val==1)
num++;
if (MAT[y-1][x].val==1)
num++;
if (MAT[y-1][x-1].val==1)
num++;
if (MAT[y][x-1].val==1)
num++;
if (MAT[y+1][x-1].val==1)
num++;
if (MAT[y+1][x].val==1)
num++;
if (MAT[y+1][x+1].val==1)
num++;

return num;
}
/******************************************/
void copiar()
{
int i,j ;
for (j=0; j<TY; j++){
for (i=0 ;i<TX ;i++){
MAT[j][i]= SEV[j][i];
}
}
}
/******************************************/
void mostrar()
{
int x,y ;

for (y=0; y<TY; y++){


for (x=0 ;x<TX ;x++){
gotoxy(x+1,y+1);
textcolor(MAT[y][x].color*16);
putchar( );
}
}
}
/******************************************/
void gotoxy(int x, int y)
{
COORD c;

c.X = x;
c.Y = y;
SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), c);
}
/******************************************/
void textcolor (int color)
{
SetConsoleTextAttribute (GetStdHandle(STD_OUTPUT_HANDLE),
color);
}
/******************************************/
En CodeBlocks con Mingw32, el programa compila sin ningn problema ni warning y funciona muy
bien.

En Visual C++ hay dos warnings relacionados con las funciones kbhit() y getch() que se consideran
deprecated. Para evitar estos warnings debe rebautizar kbhit() como _kbhit() y getch() como
_getch().

Claramente, este programa permite demostrar que C est incluido en C++ y que siempre se puede
programar C en un entorno de C++. Sin embargo, esta afirmacin en sentido inverso no es verdad: un
programa en C++ quizs no pueda compilarse en C porque C++ es una extensin de C con novedades
sintcticas y de escritura.

Distinguimos dos niveles de aportaciones de C++:


Lo que facilita la escritura sin tocar ningn aspecto fundamental desde el punto de vista del diseo.
La dimensin de la programacin orientada a objetos, que modifica de forma muy notable el diseo.
Pasar a la programacin orientada a objetos es un poco como pasar de las 2D a las 3D en el dominio
grfico.

C aumentado en C++

En esta seccin vamos a explorar las aportaciones de C++ que modifican la escritura de C, pero que no
modifican el diseo del programa, es decir, sin clases ni objetos.

1. Entrada-salida en consola: cout y cin

a. Utilizar cout y cin

Las funciones printf() y scanf(), aunque se pueden usar siempre, son reemplazadas con mejoras por los
objetos cout y cin.

Con cout, podemos escribir en la ventana de consola. Por ejemplo:

cout<<"Hola!";
escribe Hola! en la ventana de consola. El operador << utilizado indica una salida (no se trata en
este contexto de un operador bit a bit de desplazamiento).

Con cin se puede asignar a una variable un valor tecleado por el usuario:

int i;
cin>>i;
En la ventana de consola, el cursor de escritura parpadea y espera a que el usuario introduzca un valor y
pulse [Intro] (tecla [Enter] o [Retroceso]). Si el usuario introduce un valor no conforme con el tipo de
la variable, la variable no se modifica. Esta vez es el operador >> el que se utiliza (no es un operador
bit a bit). Tambin cabe destacar la ausencia del operador direccin de & para la variable, a diferencia
de scanf().

Pero lo interesante de cout y cin es tambin la desaparicin de los formatos (%d, %f, %c, etc.). Ya no es
necesario preocuparse por el tipo de sus variables ni por el tipo de la cadena de formato. Por ejemplo:

#include <iostream> // no olvidarse de l para tener cout y cin


using namespace std;

int main(int argc, char *argv[])


{
int i=5;
float f;
char s[80];
cout<<"introducir un valor entero: "<<endl;
cin>>i;
cout<<"introducir un valor en coma flotante: "<<endl;
cin>>f;

cout<<"introducir un nombre:"<<endl;
cin>>s;

cout<<"usted ha introducido:"<<endl;
cout<<"entero "<<i<<\n<<"float "<<f<<endl<<"nombre "<<s<<endl;

return 0;
}
El usuario introduce un valor entero, uno en coma flotante y otro que es una cadena de caracteres. Los
valores introducidos por el usuario se muestran a continuacin:

cout<<"entero "<<i<<\n<<"float "<<f<<endl<<"nombre "<<s<<endl;


Esta lnea muestra cmo incluir distintos elementos de visualizacin simplemente separndolos con el
operador <<.

endl vale \n, y marca el final de lnea; es un retorno de carro (salto de lnea).

b. Instrucciones de formato en salida

La librera <iomanip> permite formatear el texto de la salida. En caso de error puede ser til. Para tener
acceso a ella, no hay que olvidar incluir <iomanip>.

Longitud mnima de visualizacin

Utilizamos la funcin void setw(int n);

Esta funcin especifica la longitud mnima de la visualizacin en caracteres (w de width). El nmero de


caracteres se proporciona por parmetro. El programa siguiente muestra una serie de nmeros
aleatorios comprendidos entre el 0 y el 99 en 5 lneas y 10 columnas correctamente alineadas:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
for(int j=0; j<5; j++){
for (int i=0; i<10; i++)
cout<<setw(3)<<rand()%100;
cout<<endl;
}
return 0;
}
Alineacin de salidas

Por defecto, la alineacin se hace a la derecha. Para una alineacin de caracteres a la izquierda,
utilizamos la llamada: setiosflags(ios::left);

Para volver a una alineacin a la derecha, utilizamos la llamada: setiosflags(ios::right);

Ejemplo: 5 lneas de 10 nmeros aleatorios comprendidos entre el 0 y el 99 que se muestran alineados a


la izquierda y en columnas de 4 caracteres:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
for(int j=0; j<5; j++){
for (int i=0; i<10; i++){
cout<<setw(4)<<setiosflags(ios::left);
cout<<rand()%100;
}
cout<<endl;
}
return 0;
}
Elegir un carcter de relleno

Puede ser til reemplazar los espacios por un carcter especfico. En este caso, utilizamos la funcin
void setfill(int c) especificando en c el carcter deseado. En el siguiente ejemplo se usa el punto:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
for(int j=0; j<5; j++){
for (int i=0; i<10; i++){
cout<<setw(4)<<setiosflags(ios::left)<<setfill(.);
cout<<rand()%100;
}
cout<<endl;
}
return 0;
}
Mostrar o no los ceros despus de la coma

Para no mostrar los ceros despus de la coma, utilizamos la funcin setiosflags() pasndole por
parmetro el valor ios::showbase.

Para conservar los ceros despus de la coma, pasamos por parmetro a setiosflags() el valor
ios::showpoint.
El siguiente programa muestra una serie de 10 float inicializados con valores enteros aleatorios entre 0
y 9:

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
//cout<<setiosflags(ios::showpoint); // 0 despus de la coma
cout<<setiosflags(ios::showbase); // sin 0 despus de la coma

for (int i=0; i<10; i++){


float f=rand()%10;
cout<<f<<endl;
}
return 0;
}
Mostrar el signo de los nmeros positivos

Para mostrar el signo de un nmero positivo, utilizamos la funcin setiosflags() pasndole por
parmetro el valor ios::showpos.

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
cout<<setiosflags(ios::showpos);
for (int i=0; i<10; i++){
cout<<rand()%100<<endl;
}
return 0;
}
2. Variables y constantes

a. Declaraciones ms giles

Se han establecido una serie de facilidades en las declaraciones de variables. Las variables ya no deben
declararse obligatoriamente en el comienzo del bloque, sino en cualquier parte del bloque. Esto es
particularmente til en un bucle for para la variable contador. La lnea:

for (int i=0; i<10; i++){ ... }


Se compila en C++ pero no en C.

b. Tipo bool
En C++ tenemos el tipo bool. Una variable de este tipo solo admite dos valores: 0 o 1, falso o
verdadero:

#include <iostream>
using namespace std;

int main()
{
bool b;
for (int i=0; i<10; i++){
b=rand()%10;
cout<<b<<endl;
}
return 0;
}
En el programa anterior, rand()%10 devuelve un valor de 0 a 9, pero el programa imprime 0 si b vale 0
y 1 para el resto de los valores.

c. Tipo de carcter unicode: wchar_t

Con la internacionalizacin de la informtica, ha aparecido el problema de la codificacin de caracteres


en gran cantidad de lenguajes escritos de la humanidad. Al inicio, con 8 bits, solo haba 256 letras
disponibles. Por ello, nos hemos pasado a una codificacin unicode de 16 bits o de 32 bits, con un
mnimo de 65.535 letras disponibles. Todos los alfabetos y smbolos conocidos de los escritos de la
humanidad ahora se pueden codificar, incluyendo los de lenguas antiguas, como el egipcio jeroglfico o
el hitita cuneiforme.

En C el tipo char original es de 8 bits. El tipo wchar_t de wide character (carcter ancho) se ha
aadido a continuacin, as como una librera asociada, la librera <wchar.h>. El tipo wchar_t se define
en la librera <stddef.h>.

En C++ el tipo wchar_t est completamente integrado. Sin embargo, en la prctica, a diferencia de C,
son los objetos string con la inclusin de <string> o los objetos wstring con la inclusin de <wstring>
los que se utilizan (y no las tablas de char o de wchar_t).

El siguiente programa muestra el tamao en bytes de un carcter wchar_t y su valor. A continuacin, se


recorre una cadena wchar_t* en char*, es decir, byte a byte:

#include <iostream>
using namespace std;

int main()
{
wchar_t c = a;
cout<<sizeof(c)<<" : "<<c<<endl;

wchar_t s[] = L"012345678";


char*ptr = (char*)s;
for (int i=0;i<18; i++) // 9 caracteres de 16 bits son 18 bytes
cout<<*(ptr+i);
cout<<endl;
return 0;
}
El prefijo L se usa para especificar que se trata de una cadena de caracteres de 16 bits. Este prefijo
tambin puede usarse para denotar que un carcter est escrito en 16 bits. Por ejemplo, la expresin
LA indica que A es un carcter de 16 bits.

d. Typedef intil para tuplas

En general, en C conviene eliminar la palabra clave struct de la definicin de una tupla usando un
typedef:

typedef struct test{


...
}t_est;
int main()
{
t_test t0;
...
}
En C++ ya no es necesaria esta operacin; podemos utilizar directamente el nombre del struct como un
tipo:

struct test{ // ya no es necesario typedef aqu


...
}t_est;
int main()
{
t_test t0;
...
}
e. Tipo referencia (puntero oculto)

Una variable de tipo referencia se declara con el operador & direccin de:

int &r
Tiene que contener la direccin de otra variable y es obligatorio inicializarla en su declaracin:

int a;
int &r = a;
Una vez que la variable de tipo referencia contiene una direccin de memoria de una variable, se puede
usar en su lugar como si fuesen sinnimos:

#include <iostream>
using namespace std;

int main()
{
int i;
int &r = i;
r=50;
cout<<r<<" : "<<i<<endl;
return 0;
}
En este ejemplo, la referencia r adquiere como valor la direccin del entero i; a continuacin se asigna
un valor directamente a la variable de la direccin de i, es decir, a i mediante la referencia r. r e i
designan indistintamente el mismo espacio de memoria. El programa muestra:

50 : 50

Con un puntero clsico, el programa anterior se escribe:

#include <iostream>
using namespace std;

int main()
{
int i;
int *r = &i;
*r = 50;
cout<< *r <<" : "<< i <<endl;
return 0;
}
Vemos que el tipo referencia permite eliminar el asterisco requerido en el uso de punteros.

Por supuesto, funciona con cualquier tipo de variable, por ejemplo con una tupla:

#include <iostream>
using namespace std;

int main()
{
struct test{
int i;
float f;
}t,&tr=t;

tr.i=rand()%10;
tr.f=rand()%10000/100.0;
cout<<tr.i<<" : "<<t.i<<endl;
cout<<tr.f<<" : "<<t.f<<endl;
return 0;
}
t es un struct test y tr es una referencia a t: tr y t pueden usarse indistintamente. Cabe decir que el
acceso a los campos de la tupla t mediante la referencia se realiza con el operador punto (y no la flecha
de los punteros).
Es interesante sobre todo como parmetro de funcin: el tipo referencia permite transformar las
entradas en salidas sin cargar el cdigo con operadores, cuyo olvido es fuente de errores a veces
difciles de encontrar.

#include <iostream>
using namespace std;

void modif(int&val)
{
val*=10;
}
int main()
{
int v=3;
modif(v);
cout<<v<<endl;
return 0;
}
El programa muestra 30. Es la direccin de memoria de la variable v lo que se pasa por parmetro.

A continuacin se muestra otro ejemplo con una tupla:

#include <iostream>
using namespace std;

struct t_test{
int i;
float f;
};

void inicializar(t_test&t)
{
t.i=rand()%10;
t.f=rand()%10000/100.0;
}

int main()
{
t_test tv;
srand(time(NULL));
inicializar(tv);
cout<<tv.i<<endl;
cout<<tv.f<<endl;
return 0;
}
La tupla tv est correctamente inicializada en el main despus de la llamada a la funcin inicializar().

Aparte del uso como parmetros de funcin, las referencias tambin se usan como alias o atajos a
objetos.

Una referencia es constante

Una referencia permanece constante y no se puede modificar su valor (la direccin de la variable
albergada) una vez que se ha realizado la primera asignacin:

#include <iostream>
using namespace std;

int main()
{
int a,b= 15 ;
int&r=a;
r=50;
r=b; // qu pasa aqu?
cout<<a<<" : "<<b<<endl;
return 0;
}
El programa muestra: 15 : 15

La instruccin r=b asigna el valor de b a la variable mediante su alias r, pero la direccin contenida en r
no se ha modificado.

Saber el valor de la referencia

Para tener la direccin contenida por la variable referencia, hay que usar el operador &:

#include <iostream>
using namespace std;

int main()
{
int i;
int&r=i;
printf("%p-%p\n",&i,&r);
return 0;
}
En este programa la expresin &r vale &i y designa la direccin de la variable i.

f. Tipo puntero, operadores new y delete

En C++, la funcin malloc() se reemplaza por el operador new y la funcin free() por el operador
delete. En efecto, new y delete ya no son funciones, sino operadores palabras clave del lenguaje. Se
usan para la asignacin de memoria dinmica. Por ejemplo, sea TIPO un tipo cualquiera; la instruccin:

TIPO* var = new TIPO;


asigna memoria dinmicamente para un objeto de tipo TIPO. En el otro sentido, para liberar la
memoria, la instruccin es:
delete var ;
Para obtener una tabla dinmica, la instruccin:

TIPO*tab = new TIPO[num];


siendo num un valor entero, asigna memoria dinmicamente para una tabla de num objetos de tipo
TIPO. El borrado se hace con:

delete[]tab;
Por ejemplo:

#include <iostream>
using namespace std;

int main()
{
float *v;
// asignacin de memoria dinmica para una variable simple
v=new float;
*v=1.5;
cout<<*v<<endl;
// liberacin v
delete v;

// asignacin de memoria dinmica para una tabla de 10 int


int*tab=new int[10];

for (int i=0; i<10; i++){


tab[i]=rand()%100; // inicializacin
cout<<tab[i]<<endl; // visualizacin
}
// liberacin de tab
delete tab;

return 0;
}
g. Constantes y punteros

En el caso de uso de punteros constantes, conviene diferenciar tres posibilidades.

1) El objeto apuntado es constante, pero no el puntero, que puede tener otro valor. La sintaxis es la
siguiente, por ejemplo con una cadena de caracteres:

const char *p="prueba de punteros y constantes";

p[2] = W; // no funciona
p = new char[10]; // funciona
2) El puntero es constante, pero no el objeto al que apunta:
// el puntero debe ser aqu objeto de una asignacin de memoria
// dinmica y la cadena debe ser copiada (ver observacin ms adelante)
char*tmp = "prueba de punteros y constantes";
char *const p = new char[strlen(tmp)+1];
strcpy(p, tmp);

p[2] = W; // funciona
p = new char[10]; // no funciona
3) El puntero y el objeto apuntado son constantes. Ni uno ni otro pueden cambiar de valor:

const char *const p = "prueba de punteros y constantes";

p[2] = W; // no funciona
p = new char[10]; // no funciona
Observacin:

Una cadena literal como prueba de punteros y constantes es una constante. La asignacin char*tmp =
"prueba de punteros y constantes"; no provoca un error en la compilacin y tmp no se considera como
constante, pero la direccin albergada en tmp es constante y en la ejecucin cualquier tentativa de
acceso provoca access violation y una salida abrupta del programa. Es por este motivo por lo que,
para poder comprobar el caso 2, hay que hacer una asignacin del puntero p y una copia de la cadena a
la direccin de p.

Por otro lado, hay que tener cuidado con estas cadenas literales en un programa. Especialmente, no
hay que olvidar que la memoria correspondiente se libera al final del bloque en el que se han declarado.

h. Constantes (const) y enumeracin (enum) mejor que #define

Aunque las macros #define funcionan perfectamente en C++, generalmente se recomienda


reemplazarlas, cuando es posible, por constantes o enumeraciones. Por ejemplo:

#define TX 40
#define TY 20
se reemplaza por:

const int TX = 40;


const int TY = 20;
La lista:

#define NORTE 0
#define ESTE 1
#define SUR 2
#define OESTE 3
se reemplaza por:

enum direccion{NORTE,ESTE,SUR,OESTE};
3. Conversiones de tipos

El operador de conversin (cast) de C se conserva. Pero por razones de seguridad del cdigo, la
conversin explcita entre tipos distintos se divide en cuatro partes cada una con un operador
especfico. Son los operadores static_cast<tipo>, const_cast<tipo>, reinterpret_cast<tipo> y
dynamic_cast<tipo>. Estos operadores son ms fciles de detectar en el cdigo e indican un tipo de
conversin.

a. static_cast<tipo>

A este operador de cast se le llama static porque la conversin de tipo se realiza en la compilacin, y no
durante el funcionamiento del programa. Es muy similar al operador cast de C. Gestiona las
conversiones entre variables de la misma familia (entre char, short, int, long, float y double, por
ejemplo). Aunque la mayora de las veces estas conversiones se hacen implcitamente. Para usarlo, hay
que reemplazar tipo por el tipo deseado y poner la expresin que se desea convertir a la derecha entre
parntesis:

int a;
static_cast<float>(a); // es float
Con este cast, el valor de a se considera como float.

El siguiente programa permite obtener un nmero aleatorio comprendido entre 0 y 1:

#include <iostream>
using namespace std;

int main()
{
float f;
f= static_cast<float>(rand())/RAND_MAX;
cout<<f<<endl;
return 0;
}
Recordemos que rand() devuelve un entero y RAND_MAX, el valor mximo, es tambin un entero.
Sin cast tendramos prcticamente siempre 0.

b. const_cast<tipo>

Este operador de conversin gestiona las conversiones cuando es necesario hacer desaparecer la
calificacin const de un miembro. Por ejemplo:

#include <iostream>
using namespace std;

int main()
{
const char*msg="cadena constante";
char*ptr = const_cast<char*>(msg);
cout<<ptr<<endl;
return 0;
}
c. reinterpret_cast<tipo>
Este operador de conversin permite conversiones entre variables de familias distintas, por ejemplo de
int a puntero o entre punteros cualesquiera, a menudo operaciones arriesgadas. Por ejemplo:

#include <iostream>
using namespace std;

int main()
{
int*i= reinterpret_cast<int*>(0xFF);
cout<<i<<endl;
return 0;
}
Este programa declara un puntero a entero y le asigna una direccin de memoria sin comprobar su
validez.

Sin embargo, este operador puede usarse en operaciones de bajo nivel. Examinemos por ejemplo los
bytes de una variable de tipo int transformndola en tabla de bytes:

#include <iostream>
using namespace std;

int main()
{
int f=0xDDCCBBAA;
unsigned char*ptr=reinterpret_cast<unsigned char*>(&f);
for (int i=0; i<sizeof(f);i++){
printf("%d-",ptr[i]);
}
return 0;
}
El programa imprime 170-187-204-221-

d. dynamic_cast<tipo>

A diferencia de los otros tres operadores de conversin, dynamic_cast usa los datos del tipo de un
objeto durante el funcionamiento del programa, y no con la compilacin. Su uso interesa en clases y
objetos. Se requiere en particular para convertir un puntero o una referencia de clase en un puntero o
una referencia de clase derivada. O incluso para convertir un objeto de herencia mltiple en un objeto
de una de sus clases base.

4. Funciones

Hay algunas diferencias respecto a C para las funciones en C++.

a. Funciones declaradas inline

Se trata de pequeas funciones optimizadas para que sean rpidas en la compilacin. Es el equivalente
a las funciones con #define en C. En general, su uso se reserva a pequeas funciones que se deben
ejecutar especialmente rpido. Para escribir estas funciones, hay que usar la palabra clave inline:

#include <iostream>
using namespace std;

inline int contador()


{
static int c=0;
return c++;
}

int main()
{
for (int i=0; i<50;i++)
cout<<contador()<<endl;
return 0;
}
b. Devolver una referencia

Una funcin puede devolver una referencia con la condicin de que no se trate de una variable local a
la funcin. Esta posibilidad puede usarse, por ejemplo, para ocultar una tabla.

Trampa que hay que evitar

Una funcin puede devolver una referencia, pero hay que tener cuidado con que la direccin de la
referencia siga siendo vlida despus de la ejecucin de la funcin. Este tipo de error provoca cuelgues
puntuales y puede ser difcil de detectar rpidamente. Por ejemplo:

#include <iostream>
using namespace std;

int& ref()
{
int i =0;
printf("%p\n",&i);
return i; // ERROR MUY PELIGROSO
}

int main()
{
int&r=ref();
printf("%p\n",&r);
r=100; // &i de ref() ya no est reservado
cout<<r<<endl;

return 0;
}
Este programa funciona en mi equipo con codeBlocks y con Visual C++. La nica indicacin que da el
compilador es un warning: reference to local variable i returned.
En efecto: un espacio de memoria se reserva para la variable i solo durante la ejecucin del bloque en
el que se declara. A continuacin, este espacio de memoria ya no est reservado y as seguir mientras
la direccin de memoria no se reasigne. A priori, no pasa nada y el programa funciona. Pero si esta
direccin se vuelve a asignar para otros usos, cuando se acceda a la referencia el programa se colgar.

Ocultar una tabla con una funcin

Por otro lado, el retorno de una referencia puede permitir ocultar una tabla con una funcin:

#include <iostream>
#include <iomanip>
using namespace std;

float tab[]={1.1,2.2,3.3,4.4,5.5};

void muestra()
{
for (int i=0; i<sizeof(tab)/sizeof(tab[0]); i++)
cout<<setw(4)<<tab[i];
cout<<endl;
}

float& ref(int i)
{
if (i>=0 && i < sizeof(tab)/sizeof(tab[0]))
return tab[i];
else
return tab[0];
}

int main()
{
muestra();
ref(1)=10; // asigna 10 a tab[1]
ref(2)=20; // asigna 20 a tab[2]
ref(3)=30; // asigna 30 a tab[3]
muestra();

ref(0)= ref(4); // asigna tab[4] a tab[0]


muestra();
return 0;
}
En este programa la tabla de float es global. La funcin muestra() permite mostrar el contenido. La
funcin ref() devuelve la referencia del elemento cuyo ndice se le ha pasado por parmetro. No es el
valor de tab[i] lo que se devuelve, sino que la referencia de retorno de la funcin se inicializa con la
direccin del elemento tab[i] y, por ello, equivale a tab[i]. Por este motivo, se puede escribir a
continuacin: ref(0)=10; por ejemplo.
Incluso una instruccin como ref(2)=ref(3); significa que el valor de tab[3], mediante su referencia
devuelta en la llamada ref(3), da su valor a tab[2] mediante su referencia devuelta por la llamada ref(2).

c. Sobrecarga de funciones

Un punto importante de C++ en relacin con C es la posibilidad de diferenciar funciones por el tipo y
el nmero de parmetros, y no solo por sus nombres. Funciones con parmetros distintos pueden tener
el mismo nombre. El valor de retorno no cuenta en la diferenciacin. Es el principio de la sobrecarga:
la funcin se reconoce en el momento de la llamada segn los valores que se le comunican.

#include <iostream>
using namespace std;

int test(int a)
{
cout<<"test(int a)"<<endl;
return a++;
}
/* Provoca un error en la compilacin
void test(int a)
{
cout<<"test(int a)"<<endl;
}
*/
int test (double a)
{
cout<<"test(double a)"<<endl;
return static_cast<int>(++a);
}
void test(char c, int i)
{
cout<<"test(char c, int i)"<<endl;
}

int main()
{
test(10);
test(15.15);
test(A,20);
return 0;
}
Este programa imprime por pantalla:

test(int a)

test(double a)

test(char c, int i)
d. Valores por defecto en parmetros

C++ permite dar valores por defecto a los parmetros de funcin. Los valores por defecto se asignan a
los parmetros pertinentes con la declaracin de la funcin:

#include <iostream>
using namespace std;

// declaracin de la funcin
float precioPVP(float m,float imp=21); // 21 por defecto

int main()
{
float p,imp;
cout<<"entre un precio sin impuestos"<<endl;
cin>>p;

// 1) con el impuesto por defecto:


cout<<"precio PVP: "<<precioPVP(p)<<" (impuestos del 21%)"<<endl;

// 2) con un nuevo impuesto:


cout<<"entre un precio y el porcentaje de impuestos aplicado:"<<endl;
cin>>p>>imp;
cout<<"precio PVP: "<<precioPVP(p,imp)<<" (impuestos del "<<imp<<"%)"<<endl;

system("PAUSE");
return 0;
}

// definicin de la funcin
float precioPVP(float precio,float imp)
{
if(imp>0)
precio += (precio*imp)/100;
return precio;

}
La cantidad por defecto del parmetro imp se especifica en la declaracin de la funcin. Si la funcin
se coloca antes del main(), entonces se especifica directamente en la definicin de la funcin (que hace
a la vez de declaracin de oficio).

El uso es sencillo: 1) sin precisar los impuestos, es el valor por defecto el que se usa, 2) si se especifica
un valor se sobreescribe el valor por defecto.

Observacin:

Atencin: los parmetros por defecto deben colocarse despus de los parmetros que no tienen un valor
por defecto; si no, se producen ambigedades que el compilador no puede resolver:
void func(int a=1, int b, int c=3); // produce un error
Cmo se resuelve la llamada:

func (12,15) // ?
Es a o b el parmetro que tiene el valor 12? b o c el que tiene el valor 15?

Para evitar estos errores, estas ambigedades deben eliminarse. Sea, por ejemplo, la funcin:

void func (int a, int b=2, int c=3);


Podemos tener llamadas como:

func(10); // a:10, b:2, c:3


func(10,20); // a:10, b:20, c:3
func(10,20,30); // a:10, b:20, c:30
e. Funciones genricas (template)

En C++, las funciones pueden tener parmetros de tipos genricos, es decir, parmetros cuyo tipo es
indiferente. En el momento de la llamada, los parmetros genricos de la funcin pueden recibir
valores de tipos diferentes: char, int, float, etc.

Para definir una funcin de este tipo, hay que anteponer a la funcin la expresin: template <class
nombreTipo>, donde nombreTipo es el nombre dado al tipo genrico. Por ejemplo:

template <class T> T func(T a, T b)


{
return (a<b) ? a : b;
}
La palabra clave class puede reemplazarse por typename y parece ms claro en este contexto, con lo
que obtenemos:

#include <iostream>
using namespace std;

template <typename T> T func(T a, T b)


{
return (a<b) ? a : b;
}
Es la notacin que usaremos ahora.

Esta funcin devuelve el valor mnimo de los dos que se han pasado por parmetro. T corresponde a un
tipo genrico. El tipo efectivo es el especificado en el momento de la llamada con los valores dados a
los parmetros:

int main()
{
// con float
cout<<func(1.5, 6.2)<<endl;

// con int
cout<<func(200, 100)<<endl;

// con char
cout<<func(z, a)<<endl;

return 0;
}
Todos los parmetros y el valor de retorno no tienen por qu ser genricos. Puede haber mezcla entre
variables genricas y variables tipadas. Por ejemplo, la funcin:

template<typename TIPO> void miFunc(int a, float b, TIPO c)


{
...
}
es una funcin que no devuelve nada y recibe por parmetro un int, un float y un genrico. Una funcin
declarada como:

template < typename TIPO> TIPO miFunc(char*s, TIPO b);


especifica una funcin que devuelve una variable de tipo genrico y recibe por parmetro una cadena
de caracteres y una variable de tipo genrico.

Una funcin puede utilizar varios tipos genricos. En este caso se declara cada uno entre los smbolos
menor que y mayor que de la siguiente manera:

#include <iostream>
using namespace std;

template <typename T,typename TT,typename TTT>


void func(T a, TT b,TTT c)
{
if (a<b && a<c)
cout<<a<<endl;
else if (b<a && b<c)
cout<<b<<endl;
else
cout<<c<<endl;
}
int main()
{
func(65.666, 6,a); // muestra 6
func(Z, 0.6,4); // muestra 0.6
func(80, A,78.8); // muestra A
func(10,20,30); // muestra 10
func(a,6.9,z); // muestra 6.9

return 0;
}
En este ejemplo, la funcin func() dispone de tres tipos genricos. Muestra el menor de los 3 valores.
Atencin: si una funcin utiliza uno o varios tipos genricos, tiene que tener al menos un parmetro
para cada tipo genrico. El valor de retorno no permite identificar un tipo. El tipo solo puede
identificarse en el momento de la llamada cuando se especifican valores a los parmetros.

f. Funciones como campos de tuplas

A ttulo indicativo, en C++ se pueden integrar funciones en tuplas:

#include <iostream>
using namespace std;

struct t_test{
int val;
void muestra()
{
cout<<val<<endl;
}

};

int main()
{
t_test v;
v.val=10;
v.muestra();
return 0;
}
En este ejemplo la struct t_test tiene dos campos: la variable val y una funcin para mostrar el valor de
la variable val. La funcin se define directamente con la definicin de la tupla t_test.

La funcin tambin puede definirse fuera, figurando solo su declaracin en la tupla:

#include <iostream>
using namespace std;

struct t_test{
int val;
void muestra();

};
// Atencin!
// se hace referencia a la tupla con t_test::
void t_test::muestra()
{
cout<<val<<endl;
}

int main()
{
t_test v;
v.val=10;
v.muestra();

return 0;
}
La definicin de la funcin requiere en este caso identificar la tupla en la que se ha declarado porque
tuplas distintas pueden tener funciones con el mismo nombre. Esta identificacin se hace con el nombre
de la tupla seguido de dos smbolos de dos puntos (el operador de resolucin de mbito, ::), todo ello
intercalado entre el tipo de retorno y el nombre de la funcin.

En C++ las posibilidades que ofrecen las tuplas se han ampliado considerablemente y tupla es, por as
decirlo, sinnimo de clase. Pero, en la prctica, es la nocin de clase la que se utiliza, y el uso de tuplas,
en la mayora de los casos, queda reservado en el marco de lo que era posible en C. La formulacin de
clase y del concepto asociado son los que dominan (ver seccin Clase y objeto).

5. Gestin de excepciones (bsica)

C++ dispone de una solucin para el tratamiento de errores de ejecucin en un programa. Es el


mecanismo de las excepciones (excepcin y error son sinnimos en este contexto). Este mecanismo
se gestiona con tres instrucciones integradas en el lenguaje: throw y try-catch.

a. Instruccin throw

La excepcin es un valor especfico, de cualquier tipo, que se utiliza para caracterizar un error en una
funcin o un bloque de instrucciones. Si el error se detecta con los medios adecuados, la ejecucin del
bloque se para y se devuelve este valor. Pero, a diferencia del clsico mecanismo de retorno, la
recuperacin de este valor requiere el uso de una instruccin de salto try-catch.

La siguiente funcin implementa el mecanismo de lanzamiento de excepciones throw.

void test()
{
switch(rand()%5){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
}
}
La llamada a la funcin rand() %5 da un valor aleatorio entre 0 y 4 incluidos.

Para 0 la instruccin throw lanza una excepcin con un char, el valor ascii a.

Para 1 la instruccin throw lanza una excepcin con un int, valor 1.


Para 2 la instruccin throw lanza una excepcin con un int, valor 2.

Para 3 la instruccin throw lanza una excepcin con un double, valor 2.2.

Para 4 la instruccin throw lanza una excepcin con un double, valor 4.4.

b. Instruccin de salto try-catch

Para poder recuperar e identificar la excepcin, hay que utilizar la instruccin de salto try-catch. La
parte de cdigo que se desea controlar se coloca en bloque try y, si se intercepta una excepcin, la
ejecucin salta directamente en un bloque catch correspondiente, diseado para el tratamiento de este
tipo de error, y no se ejecutarn las siguientes instrucciones. Cada bloque catch se identifica con una
variable del tipo de excepcin que trata.

#include <iostream>
using namespace std;

void test()
{
switch(rand()%5){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
}
}

int main()
{
srand(time(NULL));
try
{
// aqu va el cdigo susceptible de producir errores
test() ;
cout<<"lnea de cdigo que nunca se ejecuta"<<endl;
}
// a continuacin, se interceptan los errores segn sus tipos:
catch(char err)
{
cout<<"char: "<<err<<endl;
}
catch(int err)
{
cout<<"int: "<<err<<endl;
}
catch(double err)
{
cout<<"double: "<<err<<endl;
}

return 0;
}
En cada ejecucin del programa, la funcin test() desencadena, mediante la instruccin throw, un tipo
de error (char, int, double) y los errores pueden tratarse en el bloque catch correspondiente. En el
ejemplo, se muestra el tipo y el nmero de error.

c. Instruccin throw y llamadas de funciones

El valor lanzado al contexto anterior provocado por la instruccin throw puede ser la llamada a una
funcin; de hecho, el valor de retorno de esa llamada. Por ejemplo:

int err_int()
{
cout<<"err_int llamado: ";
return rand()%100;
}

void test()
{
switch(rand()%6){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
case 5 : throw err_int(); break; // llamada
}
}
La funcin err_int llamada con throw devuelve un int y se obtiene en el catch de tipo int.

La funcin llamada tambin puede devolver una tupla:

struct s_err{
int v;
float f;
};

s_err err_struct()
{
s_err s;
s.v=rand()%10+100;
s.f= (rand()%10000)/100.0;
return s;
}
void test()
{
switch(rand()%7){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
case 5 : throw err_int(); break;
case 6 : throw err_struct();break; // llamada
}
}
En este caso, para capturar una excepcin de este tipo, hay que aadir un catch del mismo tipo:

try
{
test() ;
cout<<"lnea que nunca se ejecutar"<<endl;
}
catch(char err)
{
cout<<"char: "<<err<<endl;
}
catch(int err)
{
cout<<"int: "<<err<<endl;
}
catch(double err)
{
cout<<"double: "<<err<<endl;
}
catch(s_err s) // nuevo catch para el nuevo tipo
{
cout <<"struct s_err: "<<s.v<<" y "<<s.f<<endl;
}
d. Instruccin throw sin valor de retorno

El uso de throw sin parmetros provoca la llamada de la funcin terminate(), que pone fin al programa
mediante la funcin abort(). Hemos colocado esta instruccin en el case por defecto de nuestra funcin
test(), para que con cualquier valor de excepcin desconocido finalice el programa:

void test()
{
switch(rand()%8){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
case 5 : throw err_int(); break;
case 6 : throw err_struct();break;
default : throw; break; // finaliza el programa
}
}
e. Especificar qu excepciones lanzan las funciones

Las funciones que lanzan excepciones pueden especificar los tipos de excepciones que lanzan en su
declaracin. Es til para el lector del programa.

#include <iostream>
using namespace std;

void test() throw(char,int,double, s_err); // en la declaracin

int main()
{
...

return 0;
}

void test() throw(char,int,double, s_err) // y en la definicin


{
switch(rand()%8){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 2 : throw (2); break;
case 3 : throw (2.2); break;
case 4 : throw (4.4); break;
case 5 : throw err_int(); break;
case 6 : throw err_struct();break;
//default : throw; break;
}
}

Atencin: si se ha olvidado en la declaracin un tipo de excepcin, no podr ser identificado incluso


aunque el bloque catch correspondiente exista, provocando la finalizacin del programa.

f. Excepcin no identificada

Cuando se lanza una excepcin mediante el uso de throw que no se ha identificado por ningn bloque
catch, se invoca a la funcin terminate. Esta funcin pone fin al programa llamando a la funcin
abort(). Por ejemplo:

#include <iostream>
using namespace std;

void test()
{
switch(rand()%5){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 3 : throw (2.2); break;
}
}

int main()
{
srand(time(NULL));

for (int i=0; i<50; i++) // 50 veces la prueba


try
{
test() ;
cout<<"lnea de cdigo que nunca se ejecutar"<<endl;
}
catch(char err)
{
cout<<"char: "<<err<<endl;
}
catch(int err)
{
cout<<"int: "<<err<<endl;
}
// el double queda sin identificar

return 0;
}
El programa finaliza cuando se produce una excepcin de tipo double, ya que esta ha dejado de estar
identificada.

g. Bloque catch(...) por defecto

El bloque catch seguido de (...) es el bloque catch por defecto que captura todas las excepciones no
identificadas por los bloques anteriores. Por ejemplo:

#include <iostream>
using namespace std;

void test()
{
switch(rand()%5){
case 0 : throw (a); break;
case 1 : throw (1); break;
case 3 : throw (2.2); break;
}
}

int main()
{
srand(time(NULL));

for (int i=0; i<50; i++) // 50 veces la prueba


try
{
test() ;
cout<<"lnea de cdigo que nunca se ejecutar"<<endl;
}
catch(char err)
{
cout<<"char: "<<err<<endl;
}
catch(int err)
{
cout<<"int: "<<err<<endl;
}
catch(...) // captura la excepcin de double
{
cout<<"error desconocido"<<endl;
}
return 0;
}
6. Espacios de nombres (namespace) y atajos (using)

Los espacios de nombres ofrecen la posibilidad de expresar en el programa agrupaciones lgicas,


unidades de accin. El cdigo va a poder ser mapeado por sectores de actividad de forma clara. Cuanto
ms largo sea un programa, ms se necesitar dividirlo con distintos espacios de nombres. Este reparto
de cdigo tambin puede corresponder a un trabajo de equipo, donde cada uno aporta al programa el
espacio de nombres que ha elaborado sobre un aspecto determinado.

Un espacio de nombres contiene cdigo, y el cdigo contenido solo es visible en este espacio. La
instruccin namespace permite escribir sus propios espacios de nombres. La sintaxis es la siguiente:

namespace miEspacio{ // el nombre del espacio de nombres

// aqu van las declaraciones y las definiciones de variables,


// funciones, objetos...

}
El acceso desde fuera del espacio de nombres se realiza de la forma siguiente:

<nombreEspacio>::<nombre del elemento del espacio de nombres>


Por ejemplo:

#include <iostream> // rand()


using namespace std; // cout, cin y endl;

namespace personal
{

int val;
int test(int num)
{
return rand()%num;
}
}

int main()
{
personal::val=10;
cout<<personal::val<<endl;
cout<<personal::test(10)<<endl;

return 0;
}
El espacio de nombres personal contiene una declaracin de variable y una funcin. En el main la
variable ser accesible con personal::val, y la funcin de igual modo con personal::test().

La funcin se declara y se define a la vez en el espacio de nombres, pero podra estar de otro modo. El
espacio de nombres puede contener solo la declaracin de la funcin y su definicin puede estar fuera.
En el mismo archivo quedara de la siguiente manera:

#include <iostream>
using namespace std;

namespace personal
{
int val;
int test(int num);
}

int main()
{
personal::val=10;
cout<<personal::val<<endl;
cout<<personal::test(10)<<endl;

return 0;
}

int personal::test(int num)


{
return rand()%num;
}
El paradigma de la programacin orientada a objetos de C++

Llegamos en este momento al corazn de la proposicin de C++, la nocin de objeto, considerada


como revolucionaria desde su llegada. En principio responde a dos objetivos esenciales en relacin con
el uso de C: asegurar el cdigo y facilitar su reutilizacin. Pero en la prctica el alcance de los objetos
se ha demostrado ms amplio. En efecto, podemos considerar que un objeto es un pequeo programa
autnomo en el interior de un programa ms grande formado por varios objetos relacionados unos con
otros. De esta manera, el diseo con objetos se convierte en un tema ms que estimulante; tanto, que el
objeto aparece al mismo tiempo que las redes. Es un modelo de datos que permite a los programas
dialogar entre s de forma natural.

1. Clase y objeto

Para comprobar que un objeto es un programa, vamos a transformar un programa en C, el autmata


celular con el que hemos comenzado este captulo, en un objeto. Esta mutacin pondr en evidencia
algunos aspectos importantes de este nuevo paradigma que es la programacin orientada a objetos.

a. Qu es una clase?

Podemos leer en el libro El lenguaje C++, de Bjarne Stroustrup, creador del lenguaje, que El objetivo
del diseo con clases en C++ es proporcionar a los programadores una herramienta de creacin de
nuevos tipos, tan fcil de usar como los tipos integrados [float, int, etc.].

La clase, como la tupla, es un tipo definido por el usuario. Como la tupla, permite agrupar variables de
cualquier tipo. Pero con las variables tambin ofrece la posibilidad de agrupar las funciones asociadas a
los tratamientos de estas variables. Los elementos de una clase se llaman miembros de la clase; los
atributos para las variables y los mtodos para las funciones.

b. Qu es un objeto?

Un objeto es simplemente una variable del tipo de la clase de la que depende. Como con una tupla, el
objeto permite acceder a sus atributos y a los mtodos que contiene. Antes de tener un objeto, hay que
definir una clase.

c. Definir una clase

La sintaxis es sencilla: la instruccin class, un nombre, un bloque de instrucciones y un punto y coma.


En el bloque de instrucciones, hay que colocar los atributos y los mtodos.

Las funciones se definen fuera de la clase, como se ha visto anteriormente para las funciones integradas
en tuplas. El nombre de la clase seguido del operador de resolucin de mbito (::) se indica entre el tipo
de retorno y el nombre de la funcin:

class test
{
// aqu declarar los atributos
int val;

// a continuacin (o antes) declarar los mtodos


void muestra();

} ; // punto y coma

// definicin de mtodos fuera con la especificacin de la clase


// de la que forman parte test::
void test::muestra()
{
cout<<val<<endl;
}
d. Declarar un objeto

Una vez se ha definido la clase, para obtener un objeto hay que declararlo como cualquier otra variable:

int main()
{
test a; // a es un objeto de tipo class test

}
Para acceder a sus atributos y a sus mtodos, se utiliza el operador punto, y la flecha en el caso de un
puntero, como para las tuplas:

#include <iostream>
using namespace std;

class test
{
int val;

void muestra();

};
void test::muestra()
{
cout<<val<<endl;
}

int main()
{

test a; // objeto
a.val=10; // acceso con punto
a.muestra();

test*ptr = new test; // puntero a objeto


ptr->val=20; // acceso con flecha
ptr->muestra();
return 0;
}
e. Permisos de acceso

Sin embargo, el cdigo anterior provoca un error de compilacin. El error es:

error : int test::val is private


En efecto, atendiendo a la encapsulacin de los datos, para garantizar ms seguridad en el cdigo, hay
tres posibles tipos de acceso a atributos y a mtodos. Se especifican con estas instrucciones: public,
protected y private.

public: acceso interno desde la propia clase, sus clases derivadas y desde un objeto.
protected: acceso interno a la clase y sus clases derivadas (ver herencia).
private: acceso interno a la clase exclusivamente.
El acceso por defecto es private, debido a este se produce el error en nuestro programa. Si queremos
poder acceder a la variable val y a la funcin muestra() desde un objeto declarado en el main() o en otro
lugar, hay que especificar que estos miembros son pblicos:

class test
{

public: // acceso public


int val;

void muestra(); // ...hasta que se especifique otro acceso

};
f. Un programa C mutado a clase y objeto

Retomemos el ejemplo del autmata celular estudiado anteriormente para implementar una clase. Es
muy sencillo; basta con recopilar las variables globales y las declaraciones de las funciones, la
definicin de la tupla puede estar fuera o integrada (si permanece fuera podr usarse en caso necesario
para otras definiciones de otras clases).

Con ello, obtenemos:

#include <iostream> // Atencin! No olvidar cabeceras!


using namespace std;

#include <conio.h> // para getch() y kbhit()


#include <windows.h> // para textcolor() y gotoxy()

typedef struct pos{


int val;
int color;
}t_pos;

class autocelular // definicin de clase para el autmata


{
private :
int TX;
int TY;

t_pos**MAT;
t_pos**SEV;

int cuenta_vecinos (int y, int x);

public :
void inicializa_matriz (int tx, int ty);
void calcular (void);
void copiar (void);
void mostrar (void);
void destruye_mat (void);
void gotoxy (int x, int y);
void textcolor (int color);
};
Hemos definido el acceso a algunos miembros como private, que es todo lo que incumbe al
funcionamiento interno del autmata: los tamaos de la matriz y la funcin cuenta_vecinos(), que
solo se invoca en la funcin calcular().

Los mtodos se definen a continuacin. Nada cambia, aparte de la cabecera de cada una de las
funciones, que tienen que hacer referencia a la clase de la que dependen:

/******************************************/
void autocelular::inicializa_matriz(int tx, int ty)
{
int i ;
TX=tx;
TY=ty;
MAT=(t_pos**)malloc(sizeof(t_pos*)*TY);
SEV=(t_pos**)malloc(sizeof(t_pos*)*TY);
for (i=0; i<TY;i++){
MAT[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
SEV[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
memset(MAT[i],0,sizeof(t_pos)*TX);
memset(SEV[i],0,sizeof(t_pos)*TX);
}
MAT[TY/2][TX/2].val=1;
MAT[TY/2+1][TX/2].val=1;
MAT[TY/2][TX/2+1].val=1;
MAT[TY/2+1][TX/2+1].val=1;

MAT[TY/2][TX/2].color=1;
MAT[TY/2+1][TX/2].color=1;
MAT[TY/2][TX/2+1].color=1;
MAT[TY/2+1][TX/2+1].color=1;
}
/******************************************/
void autocelular::destruye_mat()
{
int i;
for (i=0; i<TY; i++){
free(MAT[i]);
free(SEV[i]);
}
free(MAT);
free(SEV);
}
/******************************************/
void autocelular::calcular()
{
int x,y,num_vecinos;

for (y=1; y<TY-1; y++){


for (x=1 ;x<TX-1 ;x++){
num_vecinos=cuenta_vecinos(y,x);
if (num_vecinos <2 || num_vecinos>3){
SEV[y][x].val=0;
SEV[y][x].color = MAT[y][x].color;
}
else{
SEV[y][x].val=1;
if (MAT[y][x].color<15)
SEV[y][x].color = MAT[y][x].color+1;
}
}
}
}
/******************************************/
int autocelular::cuenta_vecinos(int y, int x)
{
int num=0;

if (MAT[y][x+1].val==1)
num++;
if (MAT[y-1][x+1].val==1)
num++;
if (MAT[y-1][x].val==1)
num++;
if (MAT[y-1][x-1].val==1)
num++;
if (MAT[y][x-1].val==1)
num++;
if (MAT[y+1][x-1].val==1)
num++;
if (MAT[y+1][x].val==1)
num++;
if (MAT[y+1][x+1].val==1)
num++;

return num;
}
/******************************************/
void autocelular::copiar()
{
int i,j ;
for (j=0; j<TY; j++){
for (i=0 ;i<TX ;i++){
MAT[j][i]= SEV[j][i];
}
}
}
/******************************************/
void autocelular::mostrar()
{
int x,y ;

for (y=0; y<TY; y++){


for (x=0 ;x<TX ;x++){
gotoxy(x,y);
textcolor(MAT[y][x].color*16);
putchar( );
}
}
}
/******************************************/
void autocelular::gotoxy(int x, int y)
{
COORD c;

c.X = x;
c.Y = y;
SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), c);
}
/******************************************/
void autocelular::textcolor (int color)
{
SetConsoleTextAttribute (GetStdHandle(STD_OUTPUT_HANDLE),
color);
}
/******************************************/
En el main(), la accin es similar, excepto que ahora se controla a partir de un objeto:

// aqu las cabeceras


// aqu la definicin de la class autocelular

// aqu la definicin de las funciones

int main()
{
int fin=0;
autocelular a;

printf("Accin: pulsar cualquier tecla\n"


"Salir: q");

a.inicializa_matriz(80,23);
while(fin!=q){

if(kbhit()){
fin=getch();
a.mostrar();
a.calcular();
a.copiar();
}
}
a.destruye_mat();

return 0;
}
Este aspecto es importante. Aunque no sea visible, el programa ha cambiado considerablemente:
nuestro autmata ya no constituye un programa completo, sino que se ha convertido en una variable de
un programa ms grande. De este modo, podemos, por ejemplo, tener fcilmente varios autmatas e
incluso tablas estticas o dinmicas de autmatas:

autocelular a,b,c;
autocelular tab[100];
autocelular *dyn = new autocelular[1+rand()%1000];
La gestin simultnea de varios autmatas eleva nuevas preguntas y requiere adaptaciones: cmo
inicializarlos de forma distinta? Cmo se muestran? Interactan entre ellos? Etc. Por otro lado,
tambin podemos aadir al programa otros tipos de objetos de otros autmatas para hacerlos funcionar
de forma conjunta. El programa est, sin duda, montado de tal manera que es ms potente con las
nuevas perspectivas que se le ha dado al main. Adems, la clase del autmata puede integrarse muy
fcilmente en otro programa con o sin modificaciones.

Pero sigamos con nuestro experimento. Ahora vamos a explorar el funcionamiento de una serie de
herramientas y conceptos bsicos asociados a la prctica de la programacin orientada a objetos.

2. Constructor

Un constructor es simplemente una funcin que se llama en la declaracin de un objeto y que permite
pasarle valores especficos. Para comenzar, vamos a aadir dos constructores al autmata anterior.
a. Parametrizar un objeto en su declaracin

La sintaxis del constructor es el nombre de la clase como una funcin, pero sin retorno. Puede definirse
dentro o fuera de la clase. El siguiente ejemplo muestra el constructor definido en la clase usando la
funcin inicializa_matriz().

class autocelular
{
(...)
// constructor: declaracin en la clase
public:
autocelular() { inicializa_matriz(80,20);}
(...)
}
El constructor tiene que ser public; si no, no ser accesible desde el objeto declarado.

En el main, la declaracin de un objeto autocelular desencadena automticamente una llamada a este


constructor:

int main()
{
autocelular a; // llamada al constructor. Sin parntesis despus de a.
(...)
}
El hecho de tener un constructor permite modificar la funcin inicializa_matriz(). A priori, ya no es
necesario acceder directamente a esta funcin por objetos de esta clase. Por lo tanto, la vamos a definir
como private.

Gracias a la sobrecarga de funciones, se pueden tener varios constructores en una clase. El constructor
se reconoce en la declaracin del objeto en funcin de los parmetros dados. A continuacin se muestra
un constructor complementario para la clase autocelular que permite especificar el tamao del
autmata. Esta vez, para variar, se declara en la clase y se define fuera de ella:

class autocelular
{
(...)

// constructor
public :
autocelular () { inicializa_matriz(80,20);}
autocelular (int tx, int ty);

(...)
}
// definicin del segundo constructor fuera de la clase
autocelular::autocelular(int tx, int ty)
{
inicializa_matriz(tx,ty);
}
En el main(), ahora existe la posibilidad de declarar objetos autocelulares de diferente tamao:

int main()
{
autocelular a;
autocelular b(8,8);
autocelular c(20,30);
(...)

}
Observacin:

El constructor llama a la funcin inicializa_matriz(), pero podemos trabajar sin ella y escribir el cdigo
en el constructor:

autocelular::autocelular(int tx, int ty)


{
int i ;
TX=tx;
TY=ty;
MAT=(t_pos**)malloc(sizeof(t_pos*)*TY);
SEV=(t_pos**)malloc(sizeof(t_pos*)*TY);
for (i=0; i<TY;i++){
MAT[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
SEV[i]=(t_pos*)malloc(sizeof(t_pos)*TX);
memset(MAT[i],0,sizeof(t_pos)*TX);
memset(SEV[i],0,sizeof(t_pos)*TX);
}
MAT[TY/2][TX/2].val=1;
MAT[TY/2+1][TX/2].val=1;
MAT[TY/2][TX/2+1].val=1;
MAT[TY/2+1][TX/2+1].val=1;

MAT[TY/2][TX/2].color=1;
MAT[TY/2+1][TX/2].color=1;
MAT[TY/2][TX/2+1].color=1;
MAT[TY/2+1][TX/2+1].color=1;
}
b. El puntero this

Si para el segundo constructor llamamos a los parmetros igual que los atributos:

class autocelular{

private :
int TX;
int TY;
(...)

public :

autocelular (int TX, int TY);


(...)
}
Entonces estas variables locales a la funcin enmascaran a los atributos de igual nombre y las
instrucciones:

autocelular::autocelular( int TX, int TY)


{
TX=TX; // sin efecto!
TY=TY;
(...)
}
no tienen efecto.

Para distinguir entre los parmetros y los atributos de igual nombre, debemos usar el puntero this de la
forma siguiente:

autocelular::autocelular( int TX, int TY)


{
this->TX=TX; // ok, los valores TX y TY se transmiten
this->TY=TY; // a los atributos TX y TY
(...)
}
El puntero this siempre contiene la direccin del objeto actual (*this es el objeto actual) y permite
acceder a cada uno de sus miembros mediante el operador flecha. Es un operador, una palabra clave del
lenguaje; resulta til en algunas situaciones (lo utilizamos ms adelante para el constructor por copia).

c. Constructor sin parmetros

Si no se define ningn constructor en una clase, existe un constructor por defecto que se limita a
reservar el espacio de memoria para el objeto y sus atributos, pero no los inicializa.

Puede ser til definir un constructor sin parmetros que inicialice las variables:

#include <iostream>
using namespace std;

class test
{
public:
float*tab;
int num;
// constructor pone a 0
test(){tab=NULL; num=0;}
};

int main()
{
test t;
cout<<t.tab<<endl;
cout<<t.num<<endl;
return 0;
}
La llamada al constructor sin parmetros en la declaracin del objeto se hace sin parntesis. Los
parntesis son implcitos y aadirlos provoca un error de compilacin.

Observacin:

Si se trata de un puntero al objeto, el objeto no reserva su memoria. Escribir:

int main()
{
test *ptr;
cout<<ptr->tab<<endl; // error en la ejecucin
cout<<ptr->num<<endl; // error en la ejecucin

return 0;
}
provoca la salida del programa (access violation). Por lo tanto, hay que aadir una asignacin de
memoria con el operador new seguido del tipo del objeto. En este caso, tambin se invoca al
constructor sin parmetros:

int main()
{
test *ptr = new test;
cout<<ptr->tab<<endl; // imprime 0
cout<<ptr->num<<endl; // imprime 0

return 0;
}
d. Constructores con parmetros

Tambin podemos definir un constructor con sus parmetros. Es lo que hemos hecho con el autmata
celular para parametrizar su tamao. Retomando el ejemplo anterior, el constructor asigna memoria a la
tabla de float con un tamao dado en la declaracin del objeto. Tambin hemos aadido dos mtodos,
uno para la inicializacin de la tabla y otro para su visualizacin:

#include <iostream>
using namespace std;

class test
{
public:
float*tab;
int num;
// constructor con parmetros
test(int num);

void inicializar();
void mostrar();
};
//
test::test(int num)
{
this->num = num;
tab=new float[num];
}
//
void test::inicializar()
{
for(int i=0; i<num;i++)
tab[i]=rand()%1000/100.0;
}
//
void test::mostrar()
{
for (int i=0; i<num; i++)
cout<<tab[i]<<endl;
}
//
int main()
{
test a(10);
a.inicializar();
a.mostrar();

return 0;
}

A partir del momento en el que se define un constructor, deja de existir el constructor por defecto. En el
programa anterior, por ejemplo, no es posible declarar un objeto sin parmetros. Para ello, hay que
redefinir un constructor sin parmetros.

e. Constructor y copia de objetos

Otra posibilidad de constructor consiste en inicializar un objeto con los valores de otro ya existente en
el programa. Para ello, hay un constructor por defecto que realiza una copia miembro a miembro. Este
constructor se utiliza con el operador de asignacin, como se muestra a continuacin.

#include <iostream>
using namespace std;
class M
{
public :
int val;

M(int v){val=v;} // constructor


void mostrar(){cout<<val<<endl;} // visualizacin
};
int main()
{
M m1(10), m2 = m1; // m2 inicializado con m1
m1.val=20; // modificacin de m1
m1.mostrar(); // imprime 20
m2.mostrar(); // imprime 10
return 0;
}
Sin embargo, tenga especial cuidado cuando haya punteros y datos dinmicos. Por ejemplo, retomando
la clase test anterior, podemos tener un objeto a con una tabla de 5 float y un objeto b copiado a partir
de a:

#include <iostream>
using namespace std;

class test
{
public:
float*tab;
int nb;
// constructor con parmetro
test(int num);

void inicializar();
void mostrar();
};

(. . . ) // definiciones de las funciones

int main()
{
test a(5);
test b = a;

(...)
return 0;
}
Cree disponer ahora de dos tablas de float? No, ya que la direccin de memoria de la tabla es lo que se
ha copiado en la tabla b. Los dos punteros apuntan al mismo espacio de memoria. Si inicializa la tabla
de b a continuacin, podr comprobar que la tabla de a adquiere los mismos valores que la tabla de b y
que, de hecho, solo hay una tabla:

#include <iostream>
using namespace std;

class test
{
public:
float*tab;
int num;
// constructor con parmetro
test(int num);

void inicializar();
void mostrar();
};
test::test(int num)
{
this->num = num;
tab=new float[num];
}
//
void test::inicializar()
{
for(int i=0; i<num;i++)
tab[i]=rand()%1000/100.0;
}
//
void test::mostrar()
{
for (int i=0; i<num; i++)
cout<<tab[i]<<endl;
}

int main()
{
test a(5);
test b = a;

b.inicializar();
cout<<"obj a:"<<endl;
a.mostrar();
cout<<"obj b:"<<endl;
b.mostrar();

return 0;
}
Para evitar este problema y tener dos objetos distintos, es necesario reescribir el constructor por copia.
Su forma, obligatoria, es siempre la misma:
nombreClase(referencia al objeto que se desea copiar);
De este modo, en la clase tendremos:

class test
{
public:
float*tab;
int num;

test(int num);
test(const test &original); // constructor por copia

void inicializar();
void mostrar();
};
y la definicin del constructor da:

test::test(const test &original)


{
// copia completa de original al objeto actual
memcpy(this,&original,sizeof(test));

// asignacin de memoria del puntero tab del objeto actual


tab=new float[original.num];

// copia de la tabla de original en la del objeto actual


memcpy(tab,original.tab,sizeof(float)*num);
}
Utilizamos la funcin estndar memcpy(), que permite copiar un bloque de memoria en otro (pero
tambin se puede copiar miembro a miembro). La primera llamada hace la misma copia que la del
constructor por copia por defecto. Todos los datos se copian. A continuacin hay que reasignar la
memoria de todos los datos dinmicos. En este caso, reasignamos la memoria de la tabla dinmica del
objeto actual.

En el main(), si volvemos a realizar la prueba:

int main()
{
test a(5);

test b = a;
b.inicializar();
cout<<"obj a:"<<endl;
a.mostrar();
cout<<"obj b:"<<endl;
b.mostrar();
return 0;
}
Comprobamos que ambas tablas son completamente distintas. La tabla de a contiene lo que hay en
memoria (no se ha inicializado) y la tabla de b, valores en coma flotante en el rango especificado.

De hecho, este es el funcionamiento del operador de asignacin que ha sido modificado. El siguiente
ejemplo lo demuestra:

int main()
{
test a(5);
a.inicializar();
cout<<"obj a:"<<endl;
a.mostrar();

test b(1);
b = a;
cout<<"obj b:"<<endl;
b.mostrar();

return 0;
}
Al inicio, el objeto a asigna dinmicamente en su declaracin una tabla de 5 float; esta tabla se
inicializa y se muestra. A continuacin, se declara un objeto b con una tabla de 1 float. Finalmente, el
objeto a se copia a b con el operador = de asignacin. Aqu no se trata del constructor por copia (ver la
seccin Sobrecarga de operadores).

f. Constructor con conversin

Se trata de un pequeo atajo que permite, en la declaracin de un objeto, inicializarlo usando el


operador de asignacin. Por ejemplo, siempre para nuestra clase test deseamos poder escribir:

test a = 5;
donde 5 es el tamao de la tabla dinmica. Para ello, debemos reemplazar el constructor test(int num);
que hemos visto anteriormente por este:

class test
{

public :
float*tab;
int num;

// constructor con conversin


test(int n) : num(n) {tab = new float[num];};

void inicializar();
void mostrar();
};
La cabecera del constructor va seguida de dos puntos, y a continuacin el nombre de la variable que se
quiere inicializar, con el valor deseado entre parntesis, y finalmente el bloque de instrucciones
complementarias, si procede.

Para definir un constructor as fuera de la clase, por ejemplo porque hay muchas instrucciones
complementarias, es preciso hacerlo de la siguiente manera:

class test
{
public:
float*tab;
int num;
// declaracin del constructor
test(int n);

void inicializar();
void mostrar();
};

// definicin del constructor con conversin


test::test(int n ) : num(n)
{
tab=new float[num];
}
En el main() podemos tener:

int main()
{
test a=5;
a.inicializar();
a.mostrar();

return 0;
}
3. Destructor

Figura inversa al constructor, el destructor sirve para liberar la memoria asignada. Solo puede haber
uno por clase. El destructor de un objeto se invoca automticamente al final del bloque en el que se ha
declarado. El destructor por defecto libera la memoria asignada por la mquina. Pero si se han realizado
operaciones con memoria dinmica, hay que escribir su propio destructor. La sintaxis es la siguiente:

~nombreClase(no hay parmetros);


Virgulilla seguida del nombre de la clase y sin parmetros. Por ejemplo, para el autmata celular
proponemos reemplazar la funcin destruye_mat() por un destructor que se invocar automticamente
al finalizar el programa:

class autocelular
{
public :
(...)
// declaracin del destructor
~autocelular();

(...)
};
// definicin
autocelular::~autocelular()
{
for (int i=0; i<TY; i++){
free(MAT[i]);
free(SEV[i]);
}
free(MAT);
free(SEV);
}
En la clase test, tambin podemos aadir un destructor debido a que hay una asignacin de memoria
dinmica. El programa completo es:

#include <iostream>
using namespace std;

class test
{
public:
float*tab;
int num;

test(int n) : nb(n){tab=new float[num];};

// destructor
~test() { free(tab);}

void inicializar();
void mostrar();
};
void test::inicializar()
{
for(int i=0; i<num;i++)
tab[i]=rand()%1000/100.0;
}
//
void test::mostrar()
{
for (int i=0; i<num; i++)
cout<<tab[i]<<endl;
}
int main()
{
test a=10;
a.inicializar();
a.mostrar();
a.~test();

return 0;
}
4. Clases y miembros static

a. Calificativo static en C

Recordemos que en C una variable static local a un bloque de instrucciones en una funcin no
desapareca de memoria al finalizar la ejecucin del bloque, y en la siguiente llamada a la funcin
podamos encontrar la variable con el ltimo valor que tena. Por ejemplo:

#include <iostream>
using namespace std;

int test()
{
static int v=0;
return v++;
}
int main()
{
for(int i=0; i<10; i++)
cout<<test()<<endl;
return 0;
}
Este pequeo programa imprime 0 1 2 3 4 5 6 7 8 9.

Otra propiedad, una variable static declarada en global en un archivo se reserva en ese archivo y no
existe fuera de l. Sucede lo mismo si se aplica a una funcin:

/* archivo test.cpp */

#include <iostream>
using namespace std;

static int VAL=0; // visible solamente en este archivo

static int test() // visible solamente en este archivo


{
static int v=0;
return v++;
}
(...)
Ambas propiedades son siempre aplicables en C++. Tambin pueden aplicarse a objetos.

b. Calificativo static y objetos


En C++, una clase no puede definirse como static; solo los atributos o mtodos de la clase pueden
hacerlo. En ese caso la instruccin static significa que se trata de un atributo o de un mtodo
independiente o comn a todos los objetos de la clase. Es decir, aunque no exista ningn objeto de esa
clase, este tipo de atributos y mtodos seguirn accesibles y, si hay uno o ms objetos, todos
compartirn esos mismos atributos o mtodos. Para acceder a un miembro static de una clase, hay que
escribir:

nombreClase::nombreMiembro
Por ejemplo:

#include <iostream>
using namespace std;

class CL
{
public :
int val;
static int s_val;
static void func();
void muestra() {cout<<s_val<<endl;}
};

// definicin de la funcin static de la clase CL


void CL::func()
{
cout<<"la funcin static"<<endl;
}

/* atencin:
es obligatorio inicializar las variables static de una clase fuera
de la definicin de la clase sin repetir la palabra clave static:
*/

int CL::s_val=0;

int main()
{
// no hay ningn objeto
CL::s_val=10;
cout<<CL::s_val<<endl; // muestra 10
CL::func(); // muestra "la funcin static"

// varios objetos
CL a,b;
a.s_val=20;
b.muestra(); // muestra 20

return 0;
}
Las variables static de una clase se inicializan fuera como variables globales. Sin objetos, podemos
comprobar que los atributos y los mtodos static de la clase CL sin embargo existen y que se pueden
usar. Con varios objetos de la clase CL, vemos que la variable s_val del ejemplo es comn a todos.

5. Sobrecarga de operadores

En C++ todos los operadores actuales:

aritmticos + - * / % combinados += -= *= /= %=
incremento ++ --
bit a bit << >> ~ | & combinados <<= >>= ~= |= = &=
de comparacin > < >= <= == !=
multicondicin && ||
punteros -> * [ ]
funcin ()
lista , (la coma)
pueden usarse en objetos y entonces pasan a considerarse como funciones construidas con la expresin:

operator op
donde operator es una palabra clave y op un operador; por ejemplo + es operator+, = es operator=, etc.
Una funcin operador puede declararse y llamarse de la misma forma que cualquier otra, el uso del
operador se convierte en un atajo para llamar explcitamente a esta funcin.

Obsrvense, por ejemplo, las tres formas siguientes:

objeto1 = objeto2 + objeto3 // si una redefinicin de +


objeto1 = operator+(objeto2,objeto3); // redefinicin fuera de la clase
objeto1 = objeto2.operator+(objeto3); // redefinicin dentro de la clase
Hay que distinguir estas dos posibilidades:

O bien la redefinicin se realiza fuera de la clase, en una funcin global.


O bien la redefinicin se realiza dentro de la clase.
a. Funcin operator global fuera de la clase

Sea una clase M sencilla; a continuacin, para los objetos de la clase M he aqu una redefinicin del
operador + en global, fuera de la clase:

#include <iostream>
using namespace std;

class M
{
public :
int val;

M(int v){val=v;} // constructor

void mostrar() {cout<<val<<endl;} // funcin miembro


};

M operator+(M m1,M m2) // funcin de redefinicin fuera de la clase


{
M m0(0);
m0.val = (m1.val+m2.val)/2;
return m0;
}
La funcin de redefinicin recibe por parmetro dos objetos de tipo M y devuelve un objeto de tipo M
cuyo campo val contiene la media de los atributos val de los dos parmetros.

En el main() vamos a realizar cuatro pruebas:

int main()
{
M a(10), b(20), c(30);

a = a+b; // 15
a.muestra();

a = c+b; // 25
a.muestra();
// (atencin: aqu a vale 25)
a = a+b+c; // a = (a+b)+c // (((25+20)/2) + 30)/2 = 26
a.muestra();

(a+b).muestra(); // 23

return 0;
}
Podemos comprobar que el funcionamiento del operador + asociado a objetos de tipo M produce el
resultado deseado. Atencin cuando hay ms de dos operandos: la prioridad funciona de izquierda a
derecha. Para la ltima prueba, el objeto devuelto no se almacena, sino que el tipo de la expresin es un
objeto de tipo M y como tal se puede acceder a sus miembros.

b. Funcin operator localizada en una clase

Llegamos al segundo caso, la redefinicin se realiza dentro de una clase. Ahora estamos limitados a
escribir una funcin con cero o un parmetros, ninguno ms, y la funcin puede tener o no retorno de
valor.

En el siguiente ejemplo, la funcin operator+ de la clase M no devuelve nada y siempre recibe por
parmetro un objeto M; la operacin que realiza, como anteriormente, calcula la media de los valores
del atributo val del objeto actual y del pasado por parmetro:

#include <iostream>
using namespace std;

class M
{
public :
int val;

M(int v){val=v;}

void operator+(M m);

void muestra() {cout<<val<<endl;}


};

// definicin de la funcin sin retorno del operador +


void M::operator+(M m)
{
val = (val+m.val)/2;
}

int main()
{
M a(10), b(20), c(30);

//a = a + b; // = no funciona si no hay retorno


// la expresin a+b no tiene valor
a+b; // 15
a.muestra();

a.operator+(b); // 17
a.muestra();

// a+b+c; // no funciona porque no hay retorno


// (el clculo no se puede descomponer)
return 0;
}
Comprobamos que:

Debido a que la expresin a+b no devuelve nada, no se puede utilizar el operador de asignacin =.
La instruccin a+b; realiza la operacin y el valor de a.val se modifica.
Esta instruccin es equivalente a a.operator+(b); que realiza la misma operacin.
La instruccin a+b+c; no puede descomponerse en (a+b)+c porque a+b no devuelve nada.
Segundo ejemplo, esta vez la funcin operator devuelve el objeto actual:

#include <iostream>
using namespace std;

class M
{
public :
int val;
M(int v){val=v;} // constructor

M operator+(M m);

void muestra() {cout<<val<<endl;}


};

// definicin con retorno de la funcin operator+


M M::operator+(M m)
{
val = (val+m.val)/2;
return *this;
}
El operador this es un puntero que contiene siempre la direccin del objeto actual. De este modo, *this
es el objeto actual y la funcin devuelve el objeto actual. A continuacin se muestran algunos ejemplos
en el main():

int main()
{
M a(10), b(20), c(30);

a = a+b;
a.muestra(); // 15

a+c;
a.muestra(); // 22

a.operator+(b);
a.muestra(); // 21

a = b+c;
a.muestra(); // 25
b.muestra(); // 25

a.operator+(b+c);
a.muestra(); // 26
b.muestra(); // 27

a=a+b+c; // (a+b)+c
a.muestra(); // 28
b.muestra(); // 27

return 0;
}
Podemos comprobar que:

Ahora puede usarse el operador de asignacin =.


La expresin a=a+b es equivalente a las expresiones a+b y a.operator(b).
Si se escribe a=b+c; a se modifica, pero tambin b. Es equivalente a la expresin a.operator+(b+c).
Por el contrario, en la expresin a+b+c solo se modifica a (el resultado de (a+b) se almacena en a y el
resultado de a+c se almacena tambin en a).
c. Funcin operator y datos dinmicos

En el caso de datos dinmicos, hay que vigilar que las asignaciones de memoria se realicen
correctamente y poner especial atencin en el hecho de que los punteros de objetos diferentes pueden
acabar apuntando a los mismos datos. Por ejemplo, a continuacin se muestra la sobrecarga del
operador + para concatenar cadenas de caracteres:

#include <iostream>
using namespace std;

class D
{
public :
char* s;

public :
D() {s = NULL;}
D(char*s);

void muestra();
D operator+(D &concat);
};

D::D(char*s)
{
this->s=new char[strlen(s)+1];
strcpy(this->s,s);
}

D D::operator+(D &a)
{
char*s0;
// atencin si la cadena a.s es null
if (a.s != NULL){

// atencin con el caso s1=s1+s1 por ejemplo

// (recordemos que &a con una referencia da


// la direccin contenida por a)

if (this==&a){
s0= new char[strlen(s)+1];
strcpy(s0,s);
}
else
s0=a.s;
s=(char*)realloc(s,strlen(s)+strlen(s0)+1);
strcat(s,s0);
}
return *this;
}

void D::muestra()
{
cout<<s<<endl;
}

int main()
{
D s0; // cadena a NULL
D s1("toto");
D s2("tata");

s1=s1+s0;
s1.muestra(); // toto

s1=s1+s2;
s1.muestra(); // tototata

s1=s1+s1; // tototatatototata
s1.muestra();

D s3("titi");
s1=s2+s3;
s1.muestra(); // tatatiti
s2.muestra(); // tatatiti

cout<< ((s1.s==s2.s)? "la misma cadena": "otra cadena")<<endl;


for(char*i=s1.s; *i!=\0;i++)
(*i)++;
// misma cadena
s1.muestra(); // ububujuj
s2.muestra(); // ububujuj

return 0;
}
Para la primera concatenacin, la funcin operator+ tiene en cuenta la suma de una cadena a NULL, es
decir, no se toca nada.

La suma de s2 con s1 es normal, la cadena de s2 se concatena a continuacin de la cadena


recientemente ampliada en memoria de s1.

La suma de s1 con s1 plantea un problema: hay que doblar el tamao de la cadena, pero antes hay que
guardar una copia de la cadena original para poderla aadir a continuacin.
Para la suma s1=s2+s3, el objeto s2 contiene la concatenacin y a continuacin el objeto s2 se copia en
el objeto s1. Sin embargo, es una copia miembro a miembro sencilla, de modo que el puntero s1.s
adquiere el valor de s2.s y, por consiguiente, ambos punteros apuntan a la misma cadena. Para
remediarlo, tambin hay que definir el operador de asignacin.

A continuacin se muestra una propuesta de sobrecarga del operador = aadido al programa anterior:

#include <iostream>
using namespace std;

class D
{
public :
char* s;

public :
D() {s = NULL;}
D(char*s);

void muestra();
D operator+(D &concat);
D operator=(D o);
};

D::D(char*s)
{
this->s=new char[strlen(s)+1];
strcpy(this->s,s);
}

D D::operator+(D &a)
{
char*s0;
// atencin si la cadena a.s es null
if (a.s != NULL){
// atencin con el caso s1=s1+s1 por ejemplo
if (this==&a){
s0= new char[strlen(s)+1];
strcpy(s0,s);
}
else
s0=a.s;
s=(char*)realloc(s,strlen(s)+strlen(s0)+1);
strcat(s,s0);
}
return *this;
}
D D::operator=(D o)
{
// atencin al caso s1=s1, si se trata de las mismas cadenas
if(s!=o.s ) {
// liberar la cadena del objeto actual
delete s;

// reasignar una cadena con el tamao adecuado


// para el objeto actual
s = new char[strlen(o.s)+1];

// copiar la cadena para el objeto actual


strcpy(s,o.s);
}
return *this;
}

void D::muestra()
{
cout<<s<<endl;
}

int main()
{
D s1("toto"),s2("tata"),s3("titi");

s1=s1;
s1.muestra(); // toto

s1=s2;
s1.muestra(); // tata

s1=s1+s1;
s1.muestra(); // tatatata

s1=s2+s3;
s1.muestra(); // tatatiti
s2.muestra(); // tatatiti
for(int i=0; s1.s[i]!=\0; i++){
s1.s[i]++;
s2.s[i]--;
}
s1.muestra(); // ububujuj
s2.muestra(); // ssshsh

system("PAUSE");
return 0;
}
La funcin operator= comienza verificando que la asignacin no se realiza sobre una misma cadena,
probablemente de un mismo objeto con una instruccin como s1 = s1;, por ejemplo. Por qu?

Debido a la lnea delete s en la funcin operator=. Si no se hace esta comprobacin, s se libera y


cualquier intento de acceso a s para copiarlo produce un error de ejecucin y la salida inmediata del
programa.

Para concluir, cuidado con el paso de valores a parmetros que provoquen copias miembro a miembro,
as como con el valor de retorno que se copia en la variable de recuperacin. La sobrecarga de
operadores plantea diferentes problemas segn los operadores y los datos que se manejan (dinmicos o
no, etc.). La implementacin puede volverse delicada y convertirse en una fuente de confusin.
Conviene ser prudente.

6. Clases genricas (template)

a. Principio

Hemos presentado los templates de funciones o funciones genricas llamadas a veces plantillas o
patrones en espaol. Estas funciones pueden usar diferentes tipos de variables; basta con detallarlos en
el momento de la llamada. El mismo principio puede aplicarse a las clases. Tpicamente, una clase pila,
por ejemplo, es capaz de producir pilas de cualquier tipo: int, float, tuplas, etc.

b. Sintaxis bsica

La sintaxis para definir una clase genrica es la siguiente. Sean T el nombre del tipo genrico y C el
nombre de la clase:

template <class T> class C { // miembros };


En la declaracin de un objeto, hay que especificar a continuacin el tipo o los tipos deseados:

C<int> e1;
En la clase, la declaracin de atributos y mtodos que utilizan el tipo genrico se hace con el nombre
dado al tipo genrico:

template <class T> class C


{
// atributos
int val;
T valGenerico;

// mtodos
void muestra();
T func(int a, T b);
};
Para la definicin de funciones fuera de la clase, hay que especificar que la funcin se parametriza con
el tipo genrico, como cualquier otra funcin genrica (ver seccin Funciones - Funciones genricas
(template)):

template<class T> ...


A continuacin hay que especificar el tipo del valor de retorno:
template<class T> void... // si nada
template<class T> int... // si int
template<class T> T... // si genrico
A continuacin, el nombre de la clase seguido del nombre del tipo genrico entre los smbolos < y
>:

template<class T> void C<T>...


template<class T> int C<T>...
template<class T> T C<T>...
A continuacin, el operador de resolucin de mbito (::) y el nombre de la funcin, seguido del nombre
del tipo genrico entre los smbolos < y >:

template<class T> void C<T> :: func1<T> ...


template<class T> int C<T> :: func2<T> ...
template<class T> T C<T> :: func3<T> ...
Y para finalizar, la lista de parmetros y el bloque de instrucciones, por ejemplo:

template<class T> void C<T> :: func1<T> ( ) { ... }


template<class T> int C<T> :: func2<T> (int a) { ... }
template<class T> T C<T> :: func3<T> (T a, float b) { ... }
c. Sintaxis de los constructores

En cuanto a los constructores, tenemos:

template<class T> C<T> :: C<T> ( ) { ... }


template<class T> C<T> :: C<T> (int a) { ... }
template<class T> C<T> :: C<T> (T a, float b) { ... }
Desaparecen el tipo de retorno y el nombre de la funcin, que se reemplaza por el nombre de la clase
con el sufijo del nombre del tipo genrico entre los smbolos < y >.

d. Sintaxis con varios tipos genricos

Si hay varios tipos genricos, hay que aplicar el mismo principio, pero con la lista de los nombres de
tipos genricos separados por una coma entre los smbolos < y >, generando la siguiente sintaxis
para una clase:

template <class T1, class T2, class Tn> class Cn


{
// miembros
};
Y para el resto hay que reemplazar:

<class T>
de la sintaxis bsica por:

<class T1, class T2, class T3> y <T> por <T1, T2, T3>
Para la declaracin de un objeto, hay que especificar cada uno de los tipos:
Cn<int, float,char> e;
e. Ejemplo de implementacin de una pila genrica

La pila se organiza en una tabla dinmica cuyo tamao se define en la declaracin mediante un
constructor. Tiene las primitivas bsicas y una funcin de visualizacin que permite ver el contenido de
la pila:

#include <iostream>
using namespace std;

template <class T> class pila


{
private :
int max;
int cima;
T *p;

public :
//
pila();
pila (int num);
//
bool pila_vacia() {return cima==0;}
bool pila_llena() {return cima==max;}
void apilar(T e);
T desapilar();
void mostrar();
};
Las definiciones [APARENTA INCOMPLETO]

Prioridad y asociatividad de operadores

Clasificacin del operador de mayor prioridad a menor prioridad:


Operadores Funcin del operador Asociatividad
. Acceso al campo de una estructura
1 [] Indizacin (acceso a los elementos de una tabla) izquierda
() Llamada a una funcin
++ Pre y postincremento
-- Pre y postdecremento
+- Signo ms y signo menos
(type) Cast
2 sizeof Tamao derecha
~ Complemento a 1
! NO, negacin
& Referencia (direccin de)
* Indireccin (asterisco)
3 */% Multiplicacin, divisin, mdulo izquierda
4 +- Suma y resta izquierda
5 << >> Desplazamientos izquierdo y derecho izquierda
<> Menor que y mayor que
6 izquierda
<= >= Menor o igual que y mayor o igual que
7 == != Igual y diferente izquierda
8 & Y bit a bit izquierda
9 O bit a bit izquierda
10 | O inclusivo bit a bit izquierda
11 && Conjuncin Y izquierda
12 || Disyuncin O izquierda
13 ?: Condicional izquierda
=
+=
-= Asignacin y
14 derecha
*= asignaciones combinadas
/=
%=
15 , evaluacin secuencial (coma de las listas) izquierda
Algunas herramientas de visualizacin en consola de Windows

El objetivo de esta seccin es proporcionar las funciones bsicas esenciales para la realizacin de
varios ejercicios del libro. Basta con que las copie y las integre en su cdigo.

Estas funciones se basan en la librera <windows.h>, que proporciona herramientas interesantes e


importantes para programar en consola en Windows. Para los que prefieren trabajar en Linux o en Mac,
debern encontrar el equivalente que existe en estos entornos.

La librera debe incluirse:

#include <windows.h>
Funcin gotoxy() para desplazar el cursor en escritura:

void gotoxy(int x, int y)


{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
COORD c;
c.X=x;
c.Y=y;
SetConsoleCursorPosition(h,c);
}
Funcin textcolor() para cambiar el color de fondo y el de escritura (letra) al mismo tiempo (fondo en
los 4 bits altos, escritura en los 4 bits bajos):

void textcolor(int color)


{
HANDLE h=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(h,color);
}
Funcin wherex para saber cul es la posicin horizontal del cursor en escritura:

int wherex ()
{
CONSOLE_SCREEN_BUFFER_INFO info;

GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&info);
return info.dwCursorPosition.X;
}
Funcin wherey() para saber cul es la posicin vertical del cursor en escritura:

int wherey ()
{
CONSOLE_SCREEN_BUFFER_INFO info;

GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&info);
return info.dwCursorPosition.Y;
}
Funcin clrscr() para borrar la ventana en un color a su eleccin:

void clrscr (int color)


{
DWORD written;

FillConsoleOutputAttribute ( GetStdHandle (STD_OUTPUT_HANDLE),


color, 2000, (COORD) {0, 0},&written);

FillConsoleOutputCharacter ( GetStdHandle
(STD_OUTPUT_HANDLE), ,
2000, (COORD) {0, 0}, &written);
gotoxy (0, 0);
}
Para gestionar el teclado, hay dos funciones indispensables en la librera conio.h.

#include <conio.h>
Son las funciones:

int kbhit() ;
devuelve verdadero si una tecla del teclado ha sido pulsada (cualquiera) y falso en caso contrario.

int getch()
devuelve la ltima tecla pulsada en valor ascii.

Librera C de creacin en modo consola

Al margen de lo anterior, un ao, un estudiante, Julien Batonnet, desarroll una librera de creacin en
modo consola ConLib que ha permitido mejorar considerablemente el nivel de los proyectos del
primer semestre. Esta librera se puede descargar en Internet en la direccin
http://libconlib.googlecode.com/svn/trunk/.

Pero para que los estudiantes de los aos siguientes puedan continuar aprovechando esta experiencia,
hemos comenzado a desarrollar otra librera por ahora menos sofisticada, pero que permite alcanzar un
cierto nmero de objetivos, especialmente tener ratn y una buena visualizacin grfica. Es la librera
creaco, que presentamos en esta seccin.

Estas dos libreras son de cdigo abierto y esperamos que su desarrollo contine. En efecto, ante el 3D
consideramos el modo consola como un modo de por s muy interesante en trminos grficos. Es un
modo que no impresiona y que retoma el Ascii art actual. Solo tiene letras y 16 colores para hacer
cualquier cosa! Es un desafo apasionante para cualquier persona a la que le guste la programacin.
Todo cabe en el escenario, la imaginacin y la escritura.

1. Descarga

Esta librera de funciones C est disponible en http://fdrouillon.free.fr y en el sitio web de Ediciones


ENI.
Se compone de dos archivos: creaco.c y creaco.h.

2. Instalacin

Copiar ambos archivos en la carpeta include del compilador.

3. Uso

Crear un proyecto de consola y aadir:

#include <creaco.c> // punto C


Si hay varios archivos C, aadir:

#include <creaco.h> // punto H


al comienzo del resto de los archivos C.

Atencin, hay un cierto nmero de libreras ya incluidas en creaco.h:

#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
4. Funciones disponibles

La versin bsica de esta librera ofrece funciones para manejar el cursor en escritura, el color y la
pantalla de la consola (ventana de consola).

a. Manejar el cursor en escritura

void gotoxy(int x, int y);


desplaza el cursor a una posicin en la ventana de consola. La funcin controla que se permanezca
dentro de la ventana de consola.

int wherex();
devuelve la posicin horizontal del cursor.

int wherey();
devuelve la posicin vertical del cursor.

void show_cursor(BOOL visible);


muestra u oculta el cursor segn si el parmetro visible es 1 (TRUE) o 0 (FALSE).

void size_cursor(unsigned int size);


permite modificar el tamao del cursor (entre 0 y 100). El parmetro size corresponde al porcentaje de
cubrimiento de la celda en donde se encuentra el cursor.

b. Manejar el color
void set_color(int color); // o textcolor(int color);
determina el color de fondo y el de la letra.

void set_bf_color(int backcolor, int forecolor);


determina de forma separada un color para el fondo y otro para la letra.

int get_back_color();
permite obtener el color de fondo activo.

int get_fore_color();
permite obtener el color de la letra activa.

void set_back_color(int color);


activa un color para el fondo.

void set_fore_Color(int color);


activa un color para la letra.

c. Algunas operaciones en la ventana de consola

BOOL resize_console(int width, int height);


redimensiona la consola (buffer de datos y ventana mostrada). width recibe el ancho deseado para la
ventana, height recibe el alto deseado para la ventana. La ventana que se muestra y el buffer de datos
tienen el mismo tamao (para tener un buffer de mayor tamao hay que modificar un poco la funcin).
Hay un tamao mnimo, que es de 80 caracteres de ancho por 25 de alto. Tambin hay un tamao
mximo determinado automticamente segn la resolucin de la pantalla y el tamao de los caracteres
(seleccionado directamente por el usuario en las propiedades de una ventana de consola abierta). La
funcin devuelve TRUE (1) en caso de xito y FALSE (0) en caso de error.

BOOL resize_console_max();
Este tamao depende del tamao y de la fuente seleccionadas por el usuario de la consola. La funcin
devuelve TRUE (1) en caso de xito y FALSE (0) en caso de error.

int screen_w();
devuelve el ancho mximo de la ventana teniendo en cuenta el tamao del buffer.

int screen_h();
devuelve la altura mxima de la ventana teniendo en cuenta el tamao del buffer.

COORD screen_size();
devuelve una struct COORD que contiene el ancho y la altura mximos de la ventana.

void title_console(char*title);
determina un ttulo para la ventana de consola. El ttulo deseado se proporciona en forma de cadena de
caracteres.

void fill_console(char asciiChar, int color);


rellena la ventana con el carcter y el color (de fondo y de letra) especificados.
void clear_console();
borra la consola en negro.

void test_screen_console_info(int x,int y,int color)


muestra datos relativos a la pantalla de la ventana de consola en una posicin y con un color
determinados.

d. Gestin de eventos

void poll_event() :
Obtencin de entradas.

BOOL key_pressed(int keyCode) :


Obtencin de las teclas pulsadas.

COORD get_mouse_pos() :
Obtencin de las posiciones h y v del ratn.

int mouse_x() :
Obtencin de la posicin v del ratn.

int mouse_y() :
Obtencin de la posicin h del ratn.

int mouse_w() :
Obtencin de la rueda del ratn.

BOOL clic_pressed(int button) :


Obtencin de los clics del ratn.

e. CHBITMAP: buffer de datos para visualizacin

CHBITMAP* create_chb (int width, int height)


asigna la memoria de un CHBITMAP segn una achura y una altura pasadas por parmetro.

CHBITMAP* free_chb(CHBITMAP*b)
libera la memoria anteriormente asignada de forma dinmica por un chbitmap.

void fill_part_chb(CHBITMAP*b, int x,int y,int w,int h,int


asciiChar, int color)
rellena el chbitmap entero o parcialmente con una letra y unos colores (letra, fondo) elegidos.

void fill_chb(CHBITMAP*b, int asciiChar, int color)


rellena el buffer con una letra y un color.

void clear_chb(CHBITMAP*b)
inicializa a 0 un chbitmap.
void copy_chb(CHBITMAP*src,CHBITMAP*dst,int srcX, int srcY,
int dstX,int dstY, int w, int h )
copia un chbitmap en otro total o parcialmente.

void draw_chb(CHBITMAP*dst,CHBITMAP*src, int x, int y)


copia un buffer en otro a partir de una posicin dada.

void show_to_console(CHBITMAP*b)
permite mostrar un chbitmap en la ventana de consola. La funcin WriteConsoleOutput respeta el
tamao del rectngulo de la ventana de consola y no escribe fuera.

CHBITMAP* MemScreen()
permite obtener la direccin del chbitmap _screen. Este chbitmap corresponde al espacio de la ventana
de consola accesible en escritura. Todas las visualizaciones tienen lugar en _screen y a continuacin
_screen se muestra en la ventana de consola.

void show_MemScreen()
muestra el chbitmap _screen en la ventana de consola.

void clear_MemScreen()
pone a 0 el chbitmap _screen sin tocar la ventana de consola.

f. CHTEXT: operaciones de texto en modo CHBITMAP

void draw_char(CHBITMAP*b, int x, int y, int color, int asciiChar)


muestra el carcter especificado en asciiChar con el color color en la posicin x, y en el CHBITMAP
b.

Por ahora, el proyecto creaio se encuentra en sus inicios. No hay todava documentacin ms precisa ni
cdigo para ilustrar cada posibilidad. Pero esperamos poder desarrollarlo prximamente.

Potrebbero piacerti anche