Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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.
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.
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
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++.
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
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!
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).
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.
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:
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.
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 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.
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/.
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).
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.
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.
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.
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
a. Datos e instrucciones
Para escribir un programa, disponemos en todos los lenguajes de dos grandes categoras de
herramientas:
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.
5. Primer 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:
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.
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:
El texto entre comillas dobles se denomina cadena de caracteres y el punto y coma indica una
instruccin.
Ejercicio 1
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
Ejercicio 4
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.
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:
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.
Ejercicio 1
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:
// 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:
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.
#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 +, -, *, /, %
#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
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:
#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 4: a vale 7
Tambin hay que dominar los operadores ++ y --. Se utilizan muy a menudo. Se usan a la izquierda o a
la derecha de su operando.
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.
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.
#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;
}
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.
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.
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.
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:
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.
#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.
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
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 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
p+3
c+1
p+c
3*p+5*c
6.8 /7 + a * 560
Ejercicio 2
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
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:
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
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
#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;
}
a. Principio de pseudoaleatoriedad
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:
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:
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;
(...)
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);
#include <stdio.h>
#include <stdlib.h>
int main()
{
int val;
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:
int main()
{
int partida=time(NULL); // inicializacin en la declaracin de la variable
srand(partida);
(...)
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));
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);
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.
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:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
float prueba;
srand(time(NULL));
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:
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 &
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:
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
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;
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:
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:
Ejercicio 1
#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;
//--------------------------------------------------------
// 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);
// fin prog
return 0;
a. Definicin
#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;
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:
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:
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.
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.
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:
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
#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;
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
#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
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.
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.
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.
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.
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.
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,
y si no
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.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
******************************************************************
2 : SALTOS CONDICIONALES DE BLOQUES / 3 posibilidades
Forma 1:
*****************************************************************/
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);
//OBSERVACIN:
//si solo hay una instruccin, las { } no son necesarias
return 0;
}
/*****************************************************************
SALTOS CONDICIONALES DE BLOQUES
Forma 2:
*****************************************************************/
/*
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:
*****************************************************************/
/*
//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
Ejercicio 4
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.
Caminos alternativos
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:
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.
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);
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;
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.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/*****************************************************************
CONMUTACIN
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
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
Ejercicio 3
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.
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)
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.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int papel, declarar;
return 0;
}
Si el viajero tiene sus papeles en orden y no tiene nada que declarar, puede pasar.
E1 && E2 && E3 && E4 es verdadera si TODAS son verdaderas, falsa en caso contrario
Ejemplo:
Cuando se encuentra una expresin miembro falsa, la expresin entera es falsa y no se evalan las
expresiones restantes.
#include <stdio.h>
#include <stdlib.h>
int main()
{
char harina, sal, huevos, leche, gruyere;
2. Disyuncin O, operador ||
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.
#include <stdio.h>
#include <stdlib.h>
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.
E1 || E2 || E3 || E4
es verdadera si al menos una es verdadera
es falsa si todas son falsas
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char 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
La prioridad ms baja est justo por debajo de la del operador condicional y de las asignaciones (ver
anexo 1).
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...
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
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
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:
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).
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:
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.
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:
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.
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?
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
int i;
for (i=0; i<3; i++)
printf("i=%d\n",i);
El orden de ejecucin se descompone en:
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:
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,
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
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.
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.
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.
Ejercicio 1
Implementar un programa que muestre, para cada valor de la tabla ascii (de 0 a 255), el carcter
correspondiente.
Ejercicio 2
Ejercicio 5
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
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:
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
Ejercicio 15
Escribir un programa que convierta un entero natural en cifras romanas, usando la antigua notacin.
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
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:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int selec, res;
do{ // 1
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.
(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.
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).
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.
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.
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.
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);
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:
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 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.
Ejercicio 1
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
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.
Tambin indica el total de colores acertados, en este caso 2 colores: rojo y amarillo.
En cada partida el ordenador crea una nueva combinacin oculta y responde a los intentos del jugador.
Escribir el programa.
Ejercicio 11
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:
{ apertura bloque
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.
Las funciones se escriben en archivos fuente de C, antes del mtodo main() o de cualquier otro.
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.
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");
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:
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.
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);
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));
int main()
{
srand(time(NULL));
printf ("retorno de hola2() : %d\n",hora2());
return 0;
}
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:
#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?
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.
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.
#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).
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>
(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.
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>
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:
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.
(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);
(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.
Ejercicio 1
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
#include <stdio.h>
#include <stdlib.h>
int main()
{
mostrar_hola(5);
return 0;
}
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
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.
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
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:
Ejercicio 14
Escribir una funcin que recibe por parmetro dos nmeros en coma flotante y un carcter que 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
/*****************************************************
*****************************************************/
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);
}
/*****************************************************
*****************************************************/
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.
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.
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.
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.
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;
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.
Ejercicio 2
Ejercicio 3
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...).
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:
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:
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 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.
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).
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 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:
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:
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:
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:
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)).
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
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
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:
#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.
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 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:
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
};
// inicializacin tupla
e=init();
// 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;
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
Ejercicio 3
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.
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:
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.
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{
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:
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:
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:
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:
#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:
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]).
Ejercicio 1
Definir un tipo para la tupla: struct pix { int x; int y; int color }.
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
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.
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).
#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.
Para acceder a un elemento de la tabla, hay que utilizar el operador corchete y el ndice del elemento
deseado, por ejemplo:
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!
Por ejemplo:
int main()
{
float test[5];
Se puede inicializar una tabla en el momento de su declaracin con valores constantes. A continuacin
se muestran los distintos casos posibles:
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.
En la inicializacin es tedioso. Por ejemplo, en una tabla de 5000 enteros, para asignar a todos sus
elementos el valor 10:
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:
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
Se puede tener una tabla de varias dimensiones. Basta con especificar, para cada dimensin, su tamao
con el operador corchetes.
A una tabla de dos dimensiones tambin se le llama matriz. A continuacin se muestra un ejemplo de
matriz de float:
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.
images/03ri04.png
En negro, la posicin mat[3][5].
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
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:
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:
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:
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;
int cubo[10][10][10];
int y,x,z;
#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.
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).
*****************************************************************/
#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];
//-------------------------------------------------------------
// 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.
// 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);
}
Ejercicio 1
Ejercicio 2
2) Para cada ndice, comparar los valores de ambas tablas y almacenar la diferencia exacta en una
tercera tabla.
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
Ejercicio 6
Escribir un programa que declare una tabla de 543*896*531 elementos de un tipo a su eleccin.
La tabla se inicializa en la declaracin (dos o tres valores nicamente). Mostrar tres posiciones elegidas
al azar.
Ejercicio 7
Inicializar esta tabla almacenando en cada posicin el nmero de la posicin (1, 2, 3...).
Mostrar el resultado.
Ejercicio 8
Ejercicio 10
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
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.
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:
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:
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];
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.
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():
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
*****************************************************************/
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
a. Cadenas de caracteres
Ejercicio 1
Hola:
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
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.
Ejercicio 8
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.
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.
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?
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
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);
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);
(. . .)
}
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.
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:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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;
}
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.
Ejercicio 1
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
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.
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:
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:
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.
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.
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:
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
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>
/********************************************************
********************************************************/
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
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:
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.
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:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int tab[10];
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>
int main()
{
int mat[25][10]; // 25 filas por 10 columnas
init_matriz(mat,25,10);
return 0;
}
El parmetro tambin puede escribirse:
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>
#include <stdio.h>
#include <stdlib.h>
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?
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).
#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
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.
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
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 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.
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.
*****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
///********************************************************
///********************************************************
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
*****************************************************************/
/*
#define NUMMAX_1 20
#define NUMMAX_2 NUMMAX_1*2
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;
*****************************************************************/
/*
#define DIM1 10
#define DIM2 15
///********************************************************
///********************************************************
int main()
{
int dd[DIM1][DIM2]; // tabla 2D local a main()
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
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:
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.
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:
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
Ejercicio 8
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.c>
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).
Ejercicio 10
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:
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
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.
Gestin de variables
1. Visibilidad de variables
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:
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().
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.
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.
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.
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
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.
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.
Ejercicio 1
#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
#include<stdio.h>
int f1(int);
void f2(void);
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
##include<stdio.h>
#define LOW 0
#define HIGH 5
#define CHANGE 2
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);
}
Sea la funcin:
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.
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:
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.
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.
a. Funcin de inicializacin
A continuacin, la misma funcin, con la posibilidad de transmisin de distintas matrices del mismo
tamao (TY y TX) por parmetro:
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 ;
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;
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
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);
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.
#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:
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.
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:
#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);
#include "autocelular.h"
int MAT[TY][TX];
int SAV[TY][TX];
#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
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
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...
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.
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:
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:
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:
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.
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.
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.
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.
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.
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.
s. Pacman sencillo
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.
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.
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.
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.
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
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.
Los punteros forman parte de una de las herramientas ms potentes del lenguaje C. Hay tres casos de
uso de punteros:
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).
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.)
Para declarar un puntero, es decir, para tener un puntero en un programa, hay que escribir:
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: *
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.
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.
(4) El valor 777 se asigna a la variable i y el valor de la expresin *ptr tambin es 777.
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.
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: [ ]
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:
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.
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).
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:
#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.
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");
}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.
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.
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.
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.
#include <stdio.h>
int main()
{
char*ptr;
char tab[80];
printf("entre una frase:\n");
fgets(tab,80,stdin);
printf("tab: %s\n",tab);
#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.
#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);
// copia de tab
strcpy(ptr,tab);
// modificacin de la copia
for(i=0; i<strlen(ptr); i++)
ptr[i]++;
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:
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.
La tabla de punteros no permite almacenar objetos, sino direcciones de memoria de objetos. Por
ejemplo, sea la tupla trol:
#include <stdio.h>
#include <stdlib.h>
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).
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:
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:
Ahora ya podemos construir aplicaciones que reciben parmetros en su inicio. Por ejemplo:
#include <stdio.h>
#include <stdlib.h>
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.
El argumento puede ser un nombre de archivo que contenga mucha informacin para el funcionamiento
del programa, etc.
/***************************************************************
Qu es un puntero?
Una variable que adquiere nicamente direcciones de memoria como
valor.
Cmo se usan?
Hay cuatro operadores asociados y tres funciones de asignacin
de memoria (para la asignacin dinmica)
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;
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
// 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
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
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
#include <stdio.h>
int main()
{
int t[3];
int i, j;
int* ptr;
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 *:
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:
En un programa:
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:
En un programa, retomar el ejemplo del tipo definido para una entidad del ejercicio 13 y:
En un programa, retomar el ejemplo del tipo definido para una entidad del ejercicio 13 y:
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
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.
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).
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:
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:
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>
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);
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.
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.
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:
a. Funcin calloc()
#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().
b. Funcin realloc()
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num,i,k;
int *tab=NULL; // 1
(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.
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:
2) Escribir la funcin:
3) Escribir la funcin:
4) Escribir la funcin:
5) Escribir la funcin:
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:
En un programa:
Ejercicio 6
En un programa:
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:
3) Escribir la funcin:
4) Escribir la funcin:
5) Escribir la funcin:
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:
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
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.
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.
#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()?
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:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int v=10;
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{
int main()
{
int h, m, s;
}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):
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:
#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***:
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:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int**mat;
}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:
La inicializacin de una tabla de punteros t_trol* puede ser objeto de una funcin aparte:
#include <stdio.h>
#include <stdlib.h>
}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 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:
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?
#include <stdio.h>
#include <stdlib.h>
// primera serie de pruebas con mat1 como parmetro para cada funcin
printf("TEST 1 Paso de: char mat1[TY][TX] \n\n");
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
Ejercicio 3
En una funcin, los valores de dos variables pasadas por referencia se intercambian. Escribir un
programa con:
Sea una tupla que incluya una posicin (nmero real), un desplazamiento (nmero real), una letra
(entero) y un color (entero). En un programa:
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.
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:
char*s1,*s2,*s3,*s4;
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.
Ejercicio 11
int *tab;
A partir de estos prototipos:
Ejercicio 12
int **mat;
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
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.
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.
#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).
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.
#include <stdio.h>
#include <stdlib.h>
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.
Ejemplo de uso:
#include <stdio.h>
#include <stdlib.h>
#define NUM_PUNTOS 10
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;
}
}
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).
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.
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.
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.
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().
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().
b. Lectura/escritura de caracteres
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);\
}
Ejemplo de uso
#include <stdio.h>
#include <stdlib.h>
#define ERROR(msg){\
printf("%s\n",msg);\
system("PAUSE");\
exit(EXIT_FAILURE);\
}
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);\
}
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;
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);
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);
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
Inicialmente el archivo de texto contiene en cada lnea una operacin aritmtica de dos enteros, por
ejemplo:
3+5
4-7
2*9
3+5=8
4 - 7 = -3
2 * 9 = 18
Limitarse a las cuatro operaciones aritmticas bsicas (+, -, *, /) pero gestionar correctamente los
errores comunes:
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:
Ejercicio 9
Sea una matriz dinmica de nmeros aleatorios de num1*num2 en un programa; realizar las siguientes
funciones:
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.
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:
0! = 1
1! = 1
2! = 1*2
3! = 1*2*3
()
N! = 1*2*3 .*(N-1)*N
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.
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.
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.
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.
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>
int main()
{
int cmpt=0
desborda(&cmpt);
return 0;
}
En mi equipo constato que hay 130160 llamadas antes del desbordamiento.
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:
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.
int res=0;
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).
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
#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.
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
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 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
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
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:
A continuacin:
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:
Para calcular xn, se multiplica n veces x por s misma, por ejemplo: 34=3*3*3*3. La funcin recursiva
correspondiente es:
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:
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:
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:
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(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:
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:
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].
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,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:
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:
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:
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.
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);
La funcin es la siguiente:
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:
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
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
{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:
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.
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):
// 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.
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:
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:
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:
images/05ri18.png
En esta etapa:
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).
La funcin puede tambin escribirse (considerando las letras D, I, A ms que los nmeros 1, 2, 3
para los pilares):
#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.
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.
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
// 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.
Escribir una versin recursiva de la funcin strlen() que devuelve la longitud de una cadena de
caracteres pasada por parmetro.
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.
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).
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.
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.
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.
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.
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.
Modificar la funcin de creacin del laberinto para producir laberintos con crecimiento del camino.
Cmo escribir una funcin de bsqueda para un laberinto as?
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 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:
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
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;
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
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:
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:
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:
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
}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.
Por defecto, cada elemento apunta a s mismo, el puntero sig apunta al propio elemento. De este modo,
la funcin de inicializacin es:
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:
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:
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:
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
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
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).
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.
images/06ri36.PNG
Una pila dinmica no tiene tamao mximo, excepto por la limitacin de memoria RAM disponible.
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.
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...
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.
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:
d. Apilar
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()
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:
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:
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*:
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
P*REG***UN*TA*FAC***IL****
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.
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
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.
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.
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.
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.
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
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:
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_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:
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:
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:
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.
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()
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
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.
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.
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:
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.
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:
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.
Un rbol binario puede representarse en una tabla de una dimensin con la siguiente regla:
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:
Por supuesto, el rbol binario puede construirse dinmicamente con punteros. La estructura de datos es
entonces:
Como sucede con las listas enlazadas, hay dos maneras posibles para representar rboles.
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.
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:
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.
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);
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;
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:
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
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:
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.
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.
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_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.
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.
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
images/06ri67.png
A continuacin se muestra el algoritmo con las versiones para rbol dinmico y para rbol esttico:
rbol dinmico
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():
#include <windows.h>
rbol dinmico
A continuacin se muestra el algoritmo para el diseo de un rbol dinmico:
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
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 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
rbol dinmico
int hoja_din(t_nodo*r)
{
return (r->i==NULL && r->d==NULL);
}
rbol esttico
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
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
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
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
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
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
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.
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:
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:
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:
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():
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:
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:
Ejercicio 5
Una expresin aritmtica como (14 * 5) - (7 / 10) puede adquirir la forma de un rbol. En un programa:
mano: dedos
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
Con este modelo, crear, por ejemplo, el mapa de sus sitios favoritos.
Ejercicio 9
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).
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:
images/06RI03.png
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 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.
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:
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.
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:
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:
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:
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:
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 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 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()
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
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.
#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>
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.
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>
int TX;
int TY;
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;
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 ;
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.
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.
Las funciones printf() y scanf(), aunque se pueden usar siempre, son reemplazadas con mejoras por los
objetos cout y cin.
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:
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:
endl vale \n, y marca el final de lnea; es un retorno de carro (salto de lnea).
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>.
#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);
#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
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:
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.
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).
#include <iostream>
using namespace std;
int main()
{
wchar_t c = a;
cout<<sizeof(c)<<" : "<<c<<endl;
En general, en C conviene eliminar la palabra clave struct de la definicin de una tupla usando un
typedef:
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
#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.
#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 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.
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.
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:
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;
return 0;
}
g. Constantes y punteros
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:
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:
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.
#define TX 40
#define TY 20
se reemplaza por:
#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.
#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
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;
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.
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.
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();
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;
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:
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:
#include <iostream>
using namespace std;
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:
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;
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.
#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.
#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).
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.
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 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.
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.
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.
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;
int main()
{
...
return 0;
}
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));
return 0;
}
El programa finaliza cuando se produce una excepcin de tipo double, ya que esta ha dejado de estar
identificada.
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));
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:
}
El acceso desde fuera del espacio de nombres se realiza de la forma siguiente:
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;
}
1. Clase y objeto
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.
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;
} ; // punto y coma
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();
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
{
};
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).
t_pos**MAT;
t_pos**SEV;
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;
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 ;
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:
int main()
{
int fin=0;
autocelular a;
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.
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:
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 :
Para distinguir entre los parmetros y los atributos de igual nombre, debemos usar el puntero this de la
forma siguiente:
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:
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.
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;
#include <iostream>
using namespace std;
class test
{
public:
float*tab;
int nb;
// constructor con parmetro
test(int num);
void inicializar();
void mostrar();
};
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:
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).
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;
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();
};
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:
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;
// 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;
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;}
};
/* 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
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.
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;
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.
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;}
int main()
{
M a(10), b(20), c(30);
a.operator+(b); // 17
a.muestra();
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);
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:
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){
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
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 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;
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?
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.
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:
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:
// 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)):
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:
<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;
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]
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.
#include <windows.h>
Funcin gotoxy() para desplazar el 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:
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.
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
2. Instalacin
3. Uso
#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).
int wherex();
devuelve la posicin horizontal del cursor.
int wherey();
devuelve la posicin vertical del cursor.
b. Manejar el color
void set_color(int color); // o textcolor(int color);
determina el color de fondo y el de 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.
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.
d. Gestin de eventos
void poll_event() :
Obtencin de entradas.
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.
CHBITMAP* free_chb(CHBITMAP*b)
libera la memoria anteriormente asignada de forma dinmica por un chbitmap.
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 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.
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.