Está en la página 1de 98

ALGORTMICA

TEMA 0: Nocin de Algoritmos

0.1 Visin Histrica.


0.2 Diferencia entre el lenguaje algortmico y el informtico.
0.3 Planteamientos de Problemas.
0.4 Organigramas.
0.5 Traza de un Algoritmo.

TEMA 1: Composicin de Algoritmos

1.1 Cabecera (nombre del Algoritmo).


1.2 Seccin de datos (declaracin de variables).
1.2.1 Tipos (enteras, reales, etc.) / (Vector).
1.2.2 Variables y Constantes.

TEMA 2: Seccin del Cdigo

2.1 Bloque del Programa.


2.2 Procedimientos y Funciones.
2.2.1 Parmetros Formales y Actuales.
2.2.2 Variables Globales y Locales.

TEMA 3: Sentencias

3.1 Aritmticas y varias funciones.


3.2 Sentencias de Control.
3.2.1 Condicionales (Si y En Caso).
3.2.2 Bucles Mientras, Repetir y Para.
3.2.2.1 Bucle Mientras.
3.2.2.2 Bucle Repetir.
3.2.2.3 Bucle Para.

TEMA 4: Manejo de Archivos

4.1 Nociones de dispositivos de almacenamiento.


4.2 Ficheros de Acceso Secuencial.
4.2.1 Ejemplo de I/O en Ficheros Secuenciales.
4.3 Registros Estructurados.
4.4 Ficheros de Acceso Directo.
4.4.1 Ejemplo de I/O en Ficheros de Acceso Directo.
4.5 Ficheros de Acceso Indexado.
4.5.1 Ejemplo de I/O en Ficheros de Acceso Indexado.
4.6 Funcin de Hashing.
4.6.1 Gestin de las colisiones.
4.7 Esquema Bsico de los tres tipos de Organizacin.

TEMA 5: Problemas y Algoritmos importantes

5.1 Ordenacin por seleccin.


5.2 Bsqueda Secuencial o Lineal.
5.3 Bsqueda Dicotmica o Binaria.
5.4 Mezcla de ficheros Ordenados.

TUTOR DE C
TEMA 0: Introduccin

0.1 Orgenes del C


0.2 Caractersticas del lenguaje C
0.3 Uso del C

TEMA 1: Conceptos Bsicos

1.0 Introduccin
1.1 El programa HOLA MUNDO
1.2 Tipos de datos bsicos en C
1.3 Entrada de datos por teclado

TEMA 2: Control de Flujo del Programa

2.0 Introduccin
2.1 Expresiones Condicionales
2.1.1 La instruccin if ... else
2.2 Control del Flujo del Programa
2.2.0 Introduccin
2.2.1 Creaccin de bucles de ejecucin
2.2.1.0 Concepto de bucle
2.2.1.1 Bucle for
2.2.1.2 Bucle while
2.2.1.3 Bucle do ... while
2.2.1.4 Modificadores del flujo de programa
2.2.2 Mens de opciones.
2.2.2.1 Introduccin
2.2.2.2 Sentencia switch-case-default

TEMA 3: Estructuras de datos estticas

3.0 Introduccin
3.1 Matrices estticas
3.2 Tipos compuestos
3.2.0 Introduccin
3.2.1 Estructuras de datos

3.2.2 Estructuras solapadas (union)


3.2.3 Tipos definidos por el usuario

TEMA 4: Punteros y Funciones

4.0 Introduccin
4.1 Punteros
4.1.1 Qu son los punteros?
4.1.2 Operadores que actan sobre punteros
4.1.3 Punteros y matrices
4.1.4 Punteros y cadenas de caracteres
4.2 Funciones
4.2.1 Introduccin
4.2.2 Definicin de funciones
4.2.3 Ms sobre funciones

TEMA 5: Asignacin Dinmica de Memoria

5.0 Introduccin
5.1 Asignacin dinmica y esttica de memoria
5.2 Cmo se reserva memoria dinmicamente?
5.2.1 Reserva de memoria
5.2.2 Liberacin de memoria
5.2.3 Ventajas de la asignacin dinmica
5.3 Tipos de datos abstractos y encapsulado
5.3.0 Introduccin
5.3.1 Cdigo fuente, cdigo objeto. Libreras y enlazadores
5.3.2 Los ficheros de proyectos de Borland
5.3.4 Tipos Abstractos de Datos

TEMA 6: Entrada y salida (E/S) de datos

6.0 Introduccin
6.1 Entrada y salida (E/S)
6.1.1 E/S estndar
6.2 Flujos y ficheros.
6.3 Tipos de flujos: flujos de texto y flujos binarios.
6.4 Programas C con flujos.
6.5 Resumen de los apartados anteriores
6.6 Pasos para operar con ficheros
6.7 Sistema de ficheros tipo UNIX.
6.7.1 Descriptores de ficheros.

EJEMPLOS

BUSQUEDA BINARIA (BUSQUEDA DICOTOMICA)

PERMUTACIONES DE UN VECTOR
ORDENACION RAPIDA (QSORT)
LAS OCHO REINAS
SALTO DEL CABALLO
COPIA DE UN FICHERO (SIMILAR AL COPY DEL DOS)
COMPARACION DE FICHEROS
ORDENACION DE UN FICHERO (SIMILAR AL SORT DEL DOS)
SHELL CRONOMETRADO
VISUALIZACION DE 256 BYTES DE MEMORIA

TEMA 0: Nociones de Algoritmos


0.1 Visin Histrica.
Un algoritmo es el conjunto de operaciones y procedimientos
que deben seguirse para resolver un problema. La palabra "algoritmo"
deriva del nombre latinizado del gran matemtico rabe Mohamed Ibn
Moussa Al Kow Rizmi, el cual escribi sobre entre los aos 800 y 825
su obra Quitab Al Jabr Al Mugabala, donde se recoga el sistema de
numeracin hind y el concepto
del cero. Fue Fibonacci, el que
tradujo su obra al latn y la inici con las palabras: Algoritmi
dicit.
0.2 Diferencia entre el lenguaje algortmico y el informtico.
El lenguaje algortmico es aquel por medio del cual se
realiza un anlisis previo del problema a resolver y encontrar un
mtodo que permita resolverlo. El conjunto de todas las operaciones
a realizar, y el orden en el que deben efectuarse, se le denomina
algoritmo.
El lenguaje informtico es aquel por medio del cual dicho
algoritmo se codifica a un sistema comprensible por el ordenador o
computadora. Este tipo de lenguaje es ms cercano a la mquina que
al ser humano y podemos distinguir distintos tipos dependiendo de la
proximidad a la maquina. Se denomina lenguaje de alto nivel aquel
que es ms cercano a la comprensin humana y lenguaje de bajo nivel
a aquellos que son ms comprensibles por la mquina. En concreto,
nosotros vamos a estudiar un lenguaje en la frontera de uno de bajo
nivel. Es por ello que el 'C' es tan potente y rpido, pues las
funciones principales representan las funciones ms bsicas del
ordenador.
0.3 Planteamientos de Problemas.
Lo que pretende un algoritmo es sintetizar de alguna forma
una tarea, clculo o mecanismo antes de ser transcrito al ordenador.
Los pasos que hay que seguir son los siguientes:
-

Anlisis previo del problema.


Primera visin del mtodo de resolucin.
Descomposicin en mdulos.
(Programacin estructurada).
Bsqueda de soluciones parciales.
Ensamblaje de soluciones finales.

Ejemplo: Calcular las posibles races


grado: ax^2+bx+c=0

para una ecuacin de segundo

+-Algoritmo races
|
|
Variables reales a,b,c,x,y
|
|
Escribir "Introduzca los coeficientes de mayor a menor grado."
|
Leer a,b,c
|
| +-Si sqr(b)>= 4*a*c entonces
| |
x=(-b+sqrt(b^2-4*a*c))/2a
| +-Sino
| |
Escribir "No existen races reales."
| +-Finsi
|
+-Final
0.4 Organigramas.
Un organigrama o diagrama de flujos es una representacin
semigrfica del algoritmo en cuestin. Esto nos facilita la visin
descriptiva de la ejecucin del programa, as como la generacin de
la traza del algoritmo. Se denomina traza de un algoritmo a la
ejecucin manual de un programa obteniendo para cada paso un
resultado.
Smbolos generales:
*
*
*
*

Inicio y fin de un programa.


Operaciones de I/O , aritmticas y lgico-aritmticas.
Decisiones lgicas.
Flujo de la ejecucin.

0.5 Traza de un Algoritmo.


La traza de un Algoritmo se puede definir como la ejecucin
manual de forma secuencial de las sentencias que lo componen. As,
la traza del siguiente algoritmo es el valor que van adoptando las
variables a medida que se va ejecutando un programa.
+-Algoritmo Suma
|
|
Variable entera a,b
|
|
Escribir "Indique el primer sumando"
|
Leer a
|
Escribir "Indique el segundo sumando"
|
Leer b
|
c=a+b
|
Escribir "El resultado es: ";c
|
+-Final
+----------------------------+
|
T R A Z A
|
+------------+---------------+
| Comentario |
Valores
|
+------------+---------------+
| Leemos a: | a <- 4
|
| Leemos b: | b <- 5
|

| Calcula c: | c <- a+b <- 9 |


| Escribe c: | c <- 9
|
+------------+---------------+
La funcin principal que posee realizar la traza de un
algoritmo es la de comprobar que ste funciona correctamente o para
realizar la etapa de depuracin en la que se intenta corregir
errores, simplificar el algoritmo al mximo e incrementar su
eficacia y velocidad.

TEMA 1: Composicin de Algoritmos


Los algoritmos estn compuestos por diferentes partes, unas
relacionadas ntimamente con las otras, de tal forma que muchas
veces la no existencia de una provocara una confusin en el mismo.
Por ello es muy importante el saber las partes principales en las
que se divide los algoritmos y saber cuales son esenciales y cuales
no.
1.1 Cabecera (nombre del Algoritmo).
Al comenzar cualquier algoritmo, este debe ser bautizado, de
tal forma que tan solo leer la cabecera sepamos cual va a ser su
propsito.
Ejemplo:
+-Algoritmo Factorial
<- Cabecera
|
|
Variable entera a,b,c
|
Escribir "Introduzca el nmero a factorizar."
|
Leer a
|
b = 1
| +-Para c desde 2 hasta a hacer
|
b=b*c
| -FinPara
|
Escribir "El factorial es: ",b
+-Final
1.2 Seccin de datos (declaracin de variables).
Esta parte es esencial para cualquier algoritmo que trabaje
con variables. En esta seccin se va a declarar cuales son las
variables con las que vamos a trabajar y cuales son sus tipos.
1.2.1 Tipos.
El tipo de una variables define el contenido de sta, es
decir, indica cual va a ser el propsito de la variable.
Los tipos de datos estndar son:
-

Enteras
Reales
Carcter
Cadena
Lgicas

dbyte,

(Su
(Su
(Su
(Su
(Su

Existen
dword,

contenido ser un nmero entero)


contenido ser un nmero real)
contenido ser un carcter alfanumrico)
contenido ser un un conjunto de caracteres)
valor indica un hecho cierto o falso)
otros tipos de variables
etc. Que son variables

tales como byte, word,


ms encaminadas a la

programacin profesional.
Las variables se caracterizan pues poseen una jerarqua que
viene definida por el nmero de bytes que se asignan para cada una.
As un carcter posee un longitud de un byte, (donde se almacena un
nmero al que se le ha asociado mediante la norma ASCII) sin embargo
un entero posee dos byte. Sera lgico pensar que una variable
entera contuviera a un carcter y de hecho esto puede ser as, sin
embargo el mezclar tipos
de
variables
es impropio de una
programacin ordenada y elegante. Es decir, no se debe mezclar
tipos de variables a no ser que se produzca a travs de una funcin
de conversin de tipos (convertir un entero a una cadena y
viceversa).
En el programa anterior se observa la declaracin de
variables despus de la cabecera, que es el orden que debe seguirse
en la elaboracin de un algoritmo y en un programa informtico.
1.2.2 Variables y Constantes.
La principal diferencia entre variables y constantes es que
las primeras pueden variar a lo largo de la ejecucin del programa,
mientras que las segundas
permanecen constantes siempre. Las
constantes se declaran despus de la cabecera y antes de las
variables.
Ejemplo:
+-Algoritmo Circunferencia
|
|
Constante real PI=3.1416
|
Variable real r,c
|
|
Escribir "Introduzca el radio de la circunferencia"
|
Leer r
|
c=2*Pi*r
|
Escribir "Su longitud es: ",c
|
+-Final
Se define Vector como una variable cuya estructura es una
sucesin de elementos del mismo tipo. As una variable de cadena es
un vector de caracteres, ya que esta formado por una sucesin de
variables del
tipo carcter. As podemos
crear vectores de
diferentes tipos.
Ejemplo: Producto

escalar

de dos vectores en una base ortonormal.

+-Algoritmo Producto_Escalar
|
|
Vector entero a[1..3], b[1..3]
|
Variable entera c
|
|
Escribir "Introduzca el vector A (x,y,z)"
|
Leer a[1],a[2],a[3]
|
Escribir "Introduzca el vector B (x,y,z)"
|
Leer b[1],b[2],b[3]
|
|
c=a[1]*b[1]+a[2]*b[2]+a[3]*b[3]
|
|
Escribir "El producto escalar es: ",c

|
+-Final
De igual forma tenemos
vectores que se define como:

que

una

matriz

es un vector de

matriz entera Rotacional [1..3,1..3]


Hay que decir que el concepto de [Columnas,Filas] o [Filas,
Columnas] es ms bien arbitrario, ya que podemos adoptar el formato
que queramos, siempre y cuando lo mantengamos a lo largo del
programa.
TEMA 2: Seccin del Cdigo
Es esta seccin, la que se puede considerar como el corazn
del algoritmo. En ella van los procedimientos, las funciones y el
cuerpo del programa, dentro de los cuales van las sentencias que
indican los pasos a realizar por el programa.
2.1 Bloque del Programa.
El bloque del programa es como el centro neurlgico del
programa, desde l, se controla las entradas a los procedimientos y
funciones
principales (aunque
estos pueden
llamar a
otros
procedimientos y funciones secundarios). En el programa anterior se
representa como la siguiente parte:
+-Algoritmo Circunferencia
|
|
Constante real PI=3.1416
|
Variable real r,c
|
+---->Escribir "Introduzca el radio de la circunferencia"
Bloque | |
Leer r
del
| |
c=2*Pi*r
Programa+---->Escribir "Su longitud es: ",c
|
+-Final
2.2 Procedimientos y Funciones.
Tanto los procedimientos como las funciones son los mdulos
en los que se puede descomponer un algoritmo. Cada modulo se encarga
de realizar una operacin independiente de los restantes desde el
punto de vista funcional pero este puede estar relacionado con otros
procedimientos y funciones para el intercambio de valores de
variables. Hay
que decir, que cualquier
algoritmo se puede
transformar en un procedimiento para ser utilizado dentro de otro
algoritmo mayor. As, uno de los programas anteriores quedara de la
siguiente forma:
+-Procedimiento Factor(a,b) <- Parmetros Formales.
|
|
Parmetro real a,b
|
Variable entera c
|
|
b = 1
| +-Para c desde 2 hasta a hacer
| |
b=b*c

| +-FinPara
|
+-FinFactor
2.2.1 Parmetros Formales y Actuales.
Como hemos visto,
entre los procedimientos (funciones
tambin) y su entorno se producen una relacin en base a un
intercambio de valores de las variables. Estas variables reciben
nombres diferentes segn este en el cdigo Padre o en el cdigo
Hijo. Vamos a definir como cdigo Padre, aquel desde el cual se
llama a una subrutina y, el cdigo Hijo, la subrutina que estamos
llamando.
Parmetros Actuales son los que utiliza el programa Padre
para relacionarse con una subrutina en concreto, y parmetro
Formales son los que posee el programa Hijo y que lo relaciona con
el Padre.
+-Algoritmo Factorial
|
|
Variable real num,valor
|
|
Escribir "Introduzca el nmero a factorizar:"
|
Leer num
|
|
Factor(num,valor) <- Llama al Procedimiento Factor
|
|
|
|
+---+---> Parmetro Actuales.
|
|
Escribir "El factorial es: ",valor
|
+-Final
Obsrvese que los parmetros actuales y formales no tienen
por que llamarse de igual forma, sin embargo es condicin necesaria
que sean del mismo tipo y que estn en el mismo orden.
La transmisin de un parmetro como valor y no como
variable, hace que el programa Padre no reciba las posibles
modificaciones que pueda sufrir dicho parmetro dentro del cdigo
Hijo.
Ejemplo:
+-Algoritmo Factorial Constante
|
|
Variable real num,valor
|
|
Escribir "Introduzca el nmero a factorizar:"
|
Leer num
|
|
valor=0
|
Factor(num,3) <- Llama al Procedimiento Factor
|
|
Escribir "El factorial es: ",valor
|
+-Final
Se puede

observar claramente que

siempre que introduzcamos

cualquier nmero vamos a obtener que el factorial es 0, pues al


introducir la variable de forma numrica no se actualiza en el
procedimiento.
2.2.2 Variables Globales y Locales.
De igual forma que en el apartado anterior diferencibamos
entre parmetros formales y actuales, ahora vamos a realizar una
distincin entre variables globales y locales. Podemos definir
variable global como aquella que puede ser utilizada (leda,
modificada, etc.) a lo largo de todo el algoritmo principal y
tambin por cualquiera de los subalgoritmos (entindase funciones y
procedimientos) que componen el algoritmo en s. De igual forma, una
variable local, es aquella que slo puede ser referenciada dentro
del subalgoritmo en el cual ha sido declarada. Para simplificar,
podemos decir que las variables globales pueden ser referenciadas
desde cualquier parte del algoritmo mientras que las locales
nicamente sern referenciadas dentro del subalgoritmo al que
pertenece:
+--------------------+------------------------+
| Variables Locales | Subalgoritmo propio
|
| Variables Globales | Cualquier subalgoritmo |
+--------------------+------------------------+
TEMA 3: Sentencias
Las
sentencias
o
instrucciones
principalmente en tres grandes grupos:

se

pueden

dividir

- Sentencias Simples.
- Sentencias Compuestas.
- Sentencias de control del flujo del algoritmo.
Las Sentencias Simples son del tipo de:
- Asignacin de Variables y Constantes.
- Llamadas a Procedimientos y Funciones, dentro de estas ltimas
englobamos todas las funciones y procedimientos que conforman la
librera general de sentencias que veremos posteriormente.
Las Sentencias Compuestas:
- Son aquellas
Funciones.

que

estn

limitas

dentro

de

Procedimientos

Las Sentencias de Control de Flujo:


- Sentencias Reiterativas: Mientras, Repetir, Para.
- Sentencias Condicionales: Si, Case... of
3.1 Aritmticas y varias funciones.
Dentro de la asignaciones de variables juega un gran papel
los operadores y funciones matemticas, tales como:
+-------------+-----------------------------------------------+
| +
| Suma
|
| | Resta
|
| *
| Producto
|
| /
| Divisin (devuelve un valor real)
|

| div
| Divisin (devuelve un valor entero)
|
| mod
| Clculo del mdulo aritmtico.
|
| log
| Logaritmo en base 10
|
| ln
| Logaritmo neperiano
|
| exp
| Exponencial de un nmero
|
| pow
| Potencia de un nmero
|
| random
| Obtencin de un nmero aleatorio
|
| abs
| Obtenemos el valor absoluto de un nmero
|
| sqr
| Obtencin del cuadrado de un nmero
|
| sqrt
| Obtencin de la raz cuadrada
|
| sin,cos,tan | Funciones trigonomtricas
|
| chr/toascii | Obtenemos un carcter a partir de un nmero
|
| ord
| Obtenemos el nmero correspondiente al cdigo |
|
| ASCII
|
+-------------+-----------------------------------------------+
Lgicamente, existen
ms adelante.

ms funciones aritmticas

que veremos

3.2 Sentencias de Control.


Como ya dijimos en la
introduccin del tema 3, las
sentencias de control son aquellas que interrumpen la ejecucin
secuencial de las instrucciones de un algoritmo, permitiendo la
generacin de reiteraciones.
3.2.1 Condicionales (Si y Case).
La funcin SI, viene acompaada por una serie de elementos
que son los operadores relacionales y operadores booleanos.
Los operadores relacionales son los siguientes:
+----+-----------------+
| = | Igual a
|
| < | menor que
|
| > | mayor que
|
| <= | menor o igual a |
| >= | mayor o igual a |
| <> | distinto de
|
+----+-----------------+
Estos operadores nos van a servir generalmente para comparar
y comprobar los valores que toman
las variables a lo largo del
algoritmo, y dependiendo de los resultados, realizar una cosa u
otra.
Operadores booleanos:
Los operadores booleanos
compuestas. (AND, OR, XOR, NOT)
Ejemplo: Es
cierto?

la variable b

nos permiten

mayor que

12

generar condiciones

y la variable

Operador AND: (debe cumplirse las dos condiciones)


+--------------------------+
| true AND true -> true |
| true AND false -> false |
| false AND true -> false |

d igual a

| false AND false -> false |


+--------------------------+
Operador OR: (debe cumplirse una de las

dos condiciones)

+-------------------------+
| true OR true -> true |
| true OR false -> true |
| false OR true -> true |
| false OR false -> false |
+-------------------------+
Operador XOR: (se cumple cuando las dos condiciones son distintas)
+--------------------------+
| true XOR true -> false |
| true XOR false -> true |
| false XOR true -> true |
| false XOR false -> false |
+--------------------------+
Operador NOT: (niega el resultado de una condicion)
+--------------------+
| NOT true -> false |
| NOT false -> true |
+--------------------+
- La sentencias SI puede tener las siguientes estructuras:
+-Si (condicin) entonces
|
...
+-Sino
|
...
+-Finsi
Tambin puede aparecer en estructuras ms complejas:
+-Si (condicin1) entonces
| +-Si (condicin2) entonces
| |
...
| +-Finsi
+-Sino
|
...
| +-Si (condicin3) entonces
| |
...
| +-Sino
| |
...
| +-Finsi
+-Finsi
A este tipo de estructuras
encadenamiento de sentencias SI".

se le

denomina "anidamiento o

- En cuanto a la sentencia EN CASO hay que decir, que se trata de


una simplificacin de sentencias SI anidadas. De esta forma, sea una
variable A tenemos la siguiente estructura de Si encadenados.
+-Si (condicin1) entonces
|
accin1
+-Sino Si (condicin2) entonces

|
accin2
+-Sino Si (condicin3) entonces
|
accin3
+-Sino
|
accin4
+-Finsi
Con una estructura del tipo
resuelto de la siguiente forma:

EN CASO, tendramos el problema

+-En Caso de Variable


|
condicin1:
Accin1
|
condicin2:
Accin2
|
condicin3:
Accin3
+-En Otro Caso
|
Accin4
+-Fincaso
3.2.2 Bucles Mientras, Repetir y Para.
Las iteraciones son otro tipo de sentencias de control. Las
que veremos son las siguientes: Mientras, Repetir y Para. Siendo
este ltimo uno de los ms usados. La utilizacin de un bucle en un
programa permite la posibilidad de realizar iteraciones de secciones
de cdigo, evitando as andar con condiciones, etiquetas y la
sentencia GOTO. Esta ltima sentencia GOTO, hay que evitarla en por
todos los medios. Cualquier programa puede ser realizado sin tener
que utilizar dicha sentencia; ya que su uso crea confusin a la hora
de seguir la ejecucin secuencial de las sentencias de un algoritmo.
3.2.2.1 Bucle Mientras.
El bucle Mientras se caracteriza por ser utilizado cuando no
conocemos el nmero de iteraciones con antelacin. Es por ello que
nos ayudamos de una comprobacin o condicin para la entrada/salida
del mismo antes de realizar la ejecucin de la seccin del cdigo a
repetir; esto ltimo nos posibilita el caso de no efectuar ninguna
iteracin (iteracin=0).
+-Algoritmo Multiplicar (mult1,mult2,resul)
|
Parmetros reales mult1,mult2,resul
|
|
resul=0
| +-Mientras mult2>0 hacer
| |
resul=resul+mult1
| |
mult2=mult2-1
| +-Finmientras
+-Final
3.2.2.2 Bucle Repetir.
El bucle Repetir se caracteriza porque al igual que el
anterior no sabemos el nmero de iteraciones que debemos realizar,
es por ello que se apoya en condiciones para salir del mismo. Al
contrario que el anterior, la comprobacin va al final del bucle, de
esta forma, como mnimo siempre se produce una iteracin. Veamos el
algoritmo anterior utilizando el bucle repetir.
+-Algoritmo Multiplicar (mult1,mult2,resul)
|
Parmetros reales mult1,mult2,resul
|

|
resul=0
| +-Repetir
| |
resul=resul+mult1
| |
mult2=mult2-1
| +-Hasta que mult2<=0
+-Final

(o tambin

hasta que not (mult2 >0) )

Una forma muy sencilla para pasar un bucle Mientras a


Repetir, es hallando la condicin opuesta, o bien poniendo un not en
la comprobacin, negando as la condicin del mientras. Tambin se
puede hallar teniendo en cuenta el significado de los operadores
relacionales y booleanos.
+----------+----+
| NOT (<) | >= |
| NOT (<=) | > |
| NOT (>) | <= |
| NOT (>=) | < |
| NOT (=) | <> |
| NOT (<>) | = |
+----------+----+

* Negacin de lo operadores ralacionales.

3.2.2.3 Bucle Para


Lo que caracteriza al bucle
Para es que ya sabemos con
antelacin el nmero de iteraciones a realizar. Es por ello que no
nos hace falta una comprobacin de salida y/o entrada. Tambin
existe la posibilidad de realizar 0 iteraciones, cuando la variable
secundaria es menor que la primaria. Otra caracterstica, es la
posibilidad de realizar incrementos de n en n en el contador del
bucle. Pasemos a ver el algoritmo anterior mediante un bucle Para.
+-Algoritmo Multiplicar (mult1,mult2,resul)
|
Parmetros reales mult1,mult2,resul
|
Variable Local loop
|
resul=0
| +-Para loop=1 hasta mult2 de incremento 1 hacer
| |
resul=resul+mult1
| |
mult2=mult2-1
| +-Finpara
+-Final
En este caso, la variable primaria es "loop" y la secundaria
es "mult2". Si esta ltima toma un valor inferior a la primaria,
entonces el bucle no se
realiza. Obsrvese que hemos puesto el
incremento= 1, este es el valor que posee el bucle Para por defecto,
es por ello que cuando el incremento es de 1 en 1, no se debe
especificar.
TEMA 4: Manejo de Archivos
Dentro de este apartado vamos a aprender que son y como
utilizar los archivos. Veremos cual es su finalidad, que tipos son
los ms comunes y cuales son sus caractersticas principales desde
el punto de vista de la organizacin de los datos que contiene.
Para comenzar podemos decir que un ordenador que no tiene la
posibilidad de almacenar sus programas y datos en un dispositivo de
almacenamiento, (ya sean discos, cintas, etc.) no es mas que una
calculadora.
En
la
actualidad,
cualquier
ordenador
posee
dispositivos de almacenamiento, ya sean internos (discos duros) o

externos (disquetes, cartuchos, cintas, etc.). La finalidad es


obvia, la de poder guardar los datos para su posterior recuperacin
y tratamientos de los mismos en otras sesiones.
Un comn error entre los principiantes es el de confundir la
memoria de tipo RAM con la capacidad de almacenamiento del disco que
acompaa al ordenador. La RAM (Memoria de Acceso Aleatorio) es
denominada memoria voltil, ya que una vez que se apaga el ordenador
la informacin que esta contena se pierde. La RAM se mide
generalmente en MegaBytes, aunque con el paso del tiempo la unidad
puede cambiar (1 Mb son 1024 Ks, a su vez 1 K es 1024 bytes, y
finalmente 1 Byte son 8 bits, siendo esta ltima la cantidad mnima
de informacin que puede procesar un computador). La capacidad de
los dispositivos de almacenamiento (entindase disquetes, discos
duros, cintas, CD-ROM, etc.) se mide en las mismas unidades, es por
ello que la gente suele confundir la memoria RAM de un ordenador con
la capacidad de almacenamiento que suele tener en un disco duro.
La memoria RAM es uno de los aspectos que limita la potencia
de un ordenador, cuanta ms RAM tengamos, mayores programas y datos
podremos almacenar en ella y menos accesos a los dispositivos de
almacenamiento tendremos que realizar.
Los archivos o ficheros se almacenan en los dispositivos de
almacenamiento,
para
como
dijimos
anteriormente puedan ser
recuperados sus datos en sesiones futuras.
De esta forma, podemos definir un fichero como un objeto
concebido para el almacenamiento permanente de la informacin.
Informacin que puede ser organizada de diferente forma dependiendo
del trato que le vayamos a dar.
4.1 Nociones de dispositivos de almacenamiento.
En el apartado anterior hemos aprendido que la informacin
puede ser volcada en dispositivos de almacenamiento permanente, a
los cuales nos hemos referido como disquetes, discos duros, cintas,
CD-ROM, etc.
Estos medios de almacenamiento se pueden diferenciar en el
modo en el que la informacin puede ser accesible.
As definimos dispositivos de almacenamiento o soportes
secuenciales
a aquellos
donde la
informacin es
accesible
secuencialmente, es decir, para leer o escribir un dato determinado,
antes tenemos que pasar por todos los datos que le preceden. De esta
forma, en una cinta magntica los datos se graban y leen uno detrs
de otro. Y no podemos ir directamente a uno en concreto sin pasar
antes por los dems.
De igual forma se define soportes direccionables a aquellos
donde
la
superficie
de
almacenamiento
es independiente e
individualmente direccionable, es decir, podemos acceder a un dato
en concreto sin tener que pasar antes por los dems. Este es el caso
de los discos. Por poner un ejemplo, en un disco de vinilo, podemos
elegir la cancin que queremos tan slo con poner el cabezal en el
comienzo de la misma. Sin embargo, en una cinta de casete, para or
una cancin determinada antes debemos pasar todas las que le
preceden.
En los

dispositivos de almacenamientos

hay que diferenciar

las direcciones en las que se almacenan los datos. De este modo


tenemos dos tipos de direcciones: direcciones absolutas o fsicas y
direcciones relativas o lgicas.
Las direcciones absolutas o fsicas son las direcciones con
las que juega el ordenador de forma fsica en el dispositivo, es
decir, cuando el ordenador se refiere a un dato en concreto lo esta
haciendo por el conocimiento de ciertos datos que identifican la
posicin fsica del dato en el soporte de almacenamiento. As, el
ordenador juega con parmetros tales como: unidad, cabeza, cara,
pista o cilindro y sector.
Por el contrario, las direcciones relativas o lgicas son
con las que vamos a jugar nosotros, una vez que abrimos el fichero
para realizar operaciones de Input/output (I/O) nos referimos al
dato con un ndice numrico, es decir, el registro nmero x.
4.2 Ficheros de Acceso Secuencial.
Un Fichero de Acceso Secuencial es aquel donde los registros
estn
ubicados
consecutivamente
sobre
un
dispositivo
de
almacenamiento. De tal forma que para acceder a un registro
determinado 'd' debemos pasar
obligatoriamente por todos los
registros que le preceden.
Suponiendo una cinta magntica,
debemos pasar antes por el 'a', 'b', 'c'.
---------------------+- a
b
c
d
...
| ---------------------+> cabeza lectora.

para

leer

registro 'd'

Para leer el registro 'd', la cabeza


lectora, deber pasar antes por los
que le preceden.

Un Fichero de Acceso Secuencial puede ser almacenado tanto


en un dispositivo de almacenamiento direccional como secuencial, sin
embargo los Ficheros de Acceso Directo e Indexado nicamente puede
ubicarse en dispositivos de almacenamiento direccional.
4.2.1 Ejemplo de I/O en Ficheros Secuenciales.
Pasemos a
ver un
secuenciales,
pero antes
utilizaremos.

ejemplo de
veamos las

como utilizar ficheros


sentencias bsicas que

- Abrir secuencial (variable)


Esta sentencia abre un fichero para el acceso secuencial.
Donde la variable contiene el nombre del fichero a abrir.
- Cerrar (variable)
Esta sentencia cierra un
el nombre del fichero a cerrar.

fichero donde la variable contiene

- Leer/Escribir/Reescribir (variable1,variable2)
Esta sentencia permite la
datos que contiene la variable2
determinado en la variable1.

lectura o re/escritura de los


en un fichero de nombre, el

- Iniciar operaciones de lectura y/o escritura en (variable)


Esta sentencia debe incluirse despus de abrir un fichero y
determina prcticamente el tipo de acceso que vamos a realizar, es
decir, si abrimos el fichero para leer y/o escribir.
- NO fin (variable)
Esta funcin devuelve el valor lgico 'true' si no se ha
encontrado el final del fichero; y devuelve el valor lgico 'false'
si se ha encontrado el final del fichero.
- Error (variable)
Esta funcin devuelve el cdigo del error producido al
realizar cualquier operacin anterior. Por defecto, la funcin
devuelve un cero cuando no se ha producido ningn error.
Ejemplo: En este ejemplo se intenta dar aun
de ficheros secuenciales.

visin general del uso

+-Algoritmo Copiar_fichero
|
|
Fichero de enteros Origen,Destino
|
Variable entera x
|
|
Escribir ("Indique el nombre del fichero Origen:");
|
Leer Origen
|
Escribir ("Indique el nombre del fichero Destino:")
|
Leer Destino
|
|
Abrir secuencial (Origen)
|
Iniciar lectura en (Origen)
|
Abrir secuencial (Destino)
|
Iniciar escritura en (Destino)
|
| +-Mientras (NO fin(Origen)) hacer
| |
Leer (Origen,x)
| |
Escribir (Destino,x)
| +-Finmientras
|
|
Escribir ("Fichero Copiado:")
|
|
Cerrar (Origen)
|
Cerrar (Destino)
|
+-Final
Nota: Obsrvese que la variable utilizada en las operaciones
de lectura y/o escritura, deben
ser del mismo tipo que la
declaracin del fichero.
4.3 Registros Estructurados.
Hasta ahora hemos visto como los registros eran de un tipo
nico, es decir, eran todos carcter, enteros, reales, etc. Sin
embargo, hay situaciones en las que debemos realizar agrupaciones de
tipos para formar un registro estructurado. As, un registro
estructurado esta formado por un conjunto de variables de diferentes
tipos que denominaremos campos. De este modo podemos decir que un

registro estructurado es un conjunto de campos.


Ejemplo:
+-Registro datos-alumno
|
Cadena nombre
|
Cadena apellido1
|
Cadena apellido2
|
Cadena NIF
|
Cadena curso
|
Cadena telefono
|
Cadena fecha_nac
|
...
+-Finregistro
De esta forma, podemos leer y escribir en un fichero
registros estructurados que nos permitirn un almacenamiento ms
lgico y ordenado de los datos.
Al generar un registro es como si estuvisemos definiendo un
nuevo tipo de variable independiente de las ya existente (enteras,
reales, cadena, etc.).
Este tipo de registros o record se utiliza para agrupar la
informacin que se desee volcar a un fichero determinado.
4.4 Ficheros de Acceso Directo.
Un Fichero de Acceso Directo (es tambin denominado de
acceso aleatorio) es aquel que se encuentra almacenado en un
dispositivo direccionable; y donde sus registros poseen un campo que
denominaremos campo clave y que identifica inequvocamente a cada
registro. En los ficheros de acceso directo el campo clave es el
nmero
del
registro
en el fichero.
As se establece una
correspondencia directa entre los valores del campo clave y las
direcciones lgicas en el soporte. Los registros se almacenan segn
el orden de entrada y no quedan ordenados.
De esta forma en un Fichero de Acceso Directo nos referimos
a un registro por medio de su posicin en este. As, podremos
obtener el reg nmero 4 sin pasar antes por los dems.
+-----+------------+
| Reg | D A T O
|
+-----+------------|
| 1 | LUIS
|
| 2 | CARLOS
|
| 3 | TERESA
|
| 4 | JOAQUIN
|
| 5 | INMA
|
| 6 | JOSE
|
+-----+------------+

Fjese
que los datos estn
almacenados en el orden en el
que han sido introducidos por el
usuario.
Accedemos a los datos por medio
del
valor de la posicin del
registro.

La tabla anterior se denomina tabla de acceso.


Esta tabla
relaciona de forma nica el nmero del registro con el registro
correspondiente, as el reg nmero 2 corresponde al dato Carlos.
4.4.1 Ejemplo de I/O en Ficheros de Acceso Directo.
Las sentencias que manejan los ficheros de acceso
son las mismas, slo que poseen el prefijo directo.

directo

Ejemplo:
+-Algoritmo Contador_de_registros.
|
|
Fichero de enteros F
|
Variable entera x,contador
|
|
Abrir directo (F)
|
Iniciar lectura (F)
|
contador = 0
| +-Mientras (NO fin(F)) hacer
| |
Leer directo(F,x)
| |
contador=contador+1
| +-Finmientras
|
Cerrar(F)
|
|
Escribir ("El fichero:";F;"posee:";contador;"reg.")
|
+-Final
4.5 Ficheros de Acceso Indexado.
Un Fichero de Acceso Indexado es aquel que se encuentra
almacenado en un dispositivo direccionable; y donde sus registros
poseen un campo que denominaremos campo clave principal y que
identifica inequvocamente a cada registro. La clave principal debe
ser aquel campo del registro estructurado que tenga siempre un valor
diferente a los ya introducidos, es decir, dentro del fichero
Indexado no puede haber dos registros con los campos clave principal
iguales.
Adems del campo clave principal pueden existir otros campos
claves secundarios que realizan la misma tarea que el campo clave,
sin embargo, sus valores pueden repetirse. En los Ficheros de Acceso
Indexado el campo clave puede ser cualquiera de los campos de un
registro estructurado. As se establece una correspondencia directa
entre los valores del campo clave y el propio registro al que
pertenece. Los registros se almacenan ordenados alfabticamente por
el campo clave, esto nos facilita la bsqueda y listados ordenados
por los distintas claves.
Para cada campo clave, el fichero genera una tabla, donde
dicha clave aparece ordenada alfabticamente y se relaciona con la
direccin de los datos.
De esta forma en un Fichero de Acceso Indexado nos referimos
a un registro por medio de alguna de las claves que posea el
fichero, tanto la principal como la secundaria. Es decir, leer el
registro cuya clave principal sea: 46.399.554, en este caso leera
el registro correspondiente a INMA. Tambin podramos haber dicho,
leer los registro cuya clave secundaria sea la Edad=16 y primero nos
leera el registro correspondiente a los datos de Luis y en la
siguiente peticin de lectura los datos de Teresa. La diferencia
entre clave principal y secundaria, est en que la clave principal
es nica (relacionando as inequvocamente al registro al que
pertenece) mientras que las claves principales puede ser iguales.
+------------+-------------------------------------+
|
Clave
|
Clave
Clave
Clave
|
| Principal | Secundaria
Secundaria Secundaria |

+--------------------------------------------------+
+---------+------------+------------+-------------+--------+
| (Direc) | (D.N.I.) | (Nombre) | (Provincia) | (Edad) |
+---------|------------|------------|-------------|--------+
|
1
| 55.366.546 | LUIS
| Las Palmas |
16
|
|
2
| 42.386.225 | CARLOS
| Salamanca
|
17
|
|
3
| 32.387.203 | TERESA
| Oviedo
|
16
|
|
4
| 46.399.554 | INMA
| Palencia
|
20
|
|
5
| 60.643.434 | JOAQUIN
| Salamanca
|
17
|
|
6
| 22.543.986 | JOSE
| Las Palmas |
23
|
+---------+------------+------------+-------------+--------+
Como podemos
indexado.

observar, esto sera un

ejemplo de un fichero

Para cada campo clave, el fichero genera una tabla, donde


dicha clave aparece ordenada alfabticamente y se relaciona con la
direccin de los datos. As las tablas para la clave principal (DNI)
y la clave secundaria (Nombre) seran:
+------------+---------+
| (D.N.I.)
| (Direc) |
+------------+---------|
| 22.543.986 |
6
|
| 32.387.203 |
3
|
| 42.386.225 |
2
|
| 46.399.554 |
4
|
| 55.366.546 |
1
|
| 60.643.434 |
6
|
+------------+---------+
+--------------------------+
| Tabla de Ac. Clave Princ.|
+--------------------------+

+------------+---------+
| (Nombre) | (Direc) |
+------------+---------+
| CARLOS
|
2
|
| INMA
|
4
|
| JOAQUIN
|
5
|
| JOSE
|
6
|
| LUIS
|
1
|
| TERESA
|
3
|
+------------+---------+
+---------------------------+
| Tabla de Ac. Clave Secund.|
+---------------------------+

Obsrvese
como
ambas
tablas
aparecen
ordenadas
alfabticamente (o de menor a mayor en el caso del DNI). Como ya
dijimos, esto nos da grandes
facilidades a la hora de realizar
bsquedas y/o listados ordenados.
4.5.1 Ejemplo de I/O en Ficheros de Acceso Indexado.
Pasemos a
indexados,
pero
utilizaremos.

ver un ejemplo de
antes
veamos
las

como utilizar ficheros


sentencias
bsicas que

- Abrir indexado (variable,KEY=...)


Esta sentencia abre un fichero para el acceso indexado.
Donde la variable contiene el nombre del fichero a abrir y en 'KEY='
ponemos los campos claves separados por comas, comenzando por el
campo clave principal.
- Cerrar (variable)
Esta sentencia cierra un
el nombre del fichero a cerrar.

fichero donde la variable contiene

- Leer/Escribir/Reescribir (variable1,KEY=,variable2)
datos

Esta sentencia permite la lectura o re/escritura de los


por medio de un
campo clave (principal o secundaria) que

debemos poner despus de 'KEY='. El contenido ser almacenado o


mandado por la variable2 en un fichero de nombre, el determinado en
la variable1.
- Iniciar operaciones de lectura y/o escritura en (variable)
Esta sentencia debe incluirse despus de abrir un fichero y
determina prcticamente el tipo de acceso que vamos a realizar, es
decir, si abrimos el fichero para leer y/o escribir.
- NO fin (variable)
Esta funcin devuelve el valor lgico 'true' si no se ha
encontrado el final del fichero; y devuelve el valor lgico 'false'
si se ha encontrado el final del fichero.
- Error (variable)
Esta funcin devuelve el cdigo del error producido al
realizar cualquier operacin anterior. Por defecto, la funcin
devuelve un cero cuando no se ha producido ningn error.
Las sentencias que manejan los ficheros de acceso Indexado
son las mismas que hemos utilizado en los ficheros secuenciales,
slo que poseen el prefijo Indexado y en las operaciones de lectura
y/o escrituras hay que indicar la clave ('KEY=').
Ejemplo:
+-Algoritmo Buscar_Persona.
|
| +-Registro estructurado datos_personales
| |
Variable cadena dni
| |
Variable cadena nombre
| |
Variable cadena tlf
| |
Variable cadena provincia
| +-Finregistro
|
|
Fichero de datos_personales F
|
Variable cadena documento
|
Variable de datos_personales dato
|
|
Escribir "Indique el DNI de la persona a buscar"
|
Leer documento
|
|
Abrir indexado (F,KEY=dni,nombre)
|
Iniciar lectura (F)
|
|
Leer (F,KEY=documento,dato)
| +-Si error(F)<>0 entonces
| |
Escribir "Ese registro no existe."
| +-Sino
| |
Escribir "
DNI: ";dato.dni
| |
Escribir "
Nombre: ";dato.nombre
| |
Escribir "
Tlf: ";dato.tlf
| |
Escribir "Provincia: ";dato.provincia
| +-Finsi
|
|
Cerrar (F)
+-Final

4.6 Funcin de Hashing.


Muchas veces surge el caso del que el fichero es tan grande
que la tabla no puede mantener una ordenacin eficaz debido a que
cuando introducimos un nuevo dato debe hacerse un espacio en la
misma para albergar a ste. Es por ello que recurrimos al "Hashing".
El "Hashing" consiste simplemente en relacionar la clave
principal con la direccin por medio de una frmula matemtica. As,
antes de introducir datos se crean unos espacios para albergar las
futuras modificaciones y adiciones de datos. Este mtodo crea una
serie de conjuntos llamados "Buckets". A cada Bucket le corresponde
un nmero que ser el que devuelva la frmula matemtica. A su vez
los Buckets poseen un nmero que determina la cantidad mxima de
claves que pueden almacenarse en l.
De esta manera cuando vamos a buscar el dato "Manolo" el
Hashing nos determina la posicin del conjunto (Buckets). En ese
conjunto habr otra serie de datos a los cuales les corresponde el
mismo valor de la funcin Hashing. La bsqueda ahora se har de
forma secuencial a lo largo del Bucket.
Veamos un ejemplo:
Bucket Clave Prin.
Claves Secundarias
+-----+------------+----------------------+
|
|
Manolo
|
. . .
|
|
+------------+----------------------+
| 35 |
Manuel
|
. . .
|
+-----+------------+----------------------+
+--| 104 |
Manuela |
. . .
|
| +-----+------------+----------------------+
| |
|
Natalia |
. . .
|
| |
+------------+----------------------+
| | 36 |
Naranjo |
. . .
|
| |
+------------+----------------------+
| |
|
Noelia
|
. . .
|
| +-----+------------+----------------------+
|
.
|
.
|
.
| +-----+------------+----------------------+
| |
|
Mark
|
. . .
|
| |
+------------+----------------------+
+->| 104 |
Miguel
|
. . .
|
|
+------------+----------------------+
|
|
Mara
|
. . .
|
+-----+------------+----------------------+

Al nmero 104
se le denomina
puntero
de
desbordamiento

4.6.1 Gestin de las colisiones.


En este mtodo parecen una serie de conflictos cuando las
claves son muy parecidas, como podemos observar para claves casi
idnticas, el Hashing nos devuelve el mismo Bucket. Esto implica que
el Bucket puede llenarse de datos; cuando esto ocurre la solucin
est en un puntero que existe en cada Bucket que determina el salto
a otro Bucket. As, cuando se llena el Bucket nmero 35, existe un
salto de ste al nmero 104 (otro Bucket) que posee datos del mismo
tipo, que tambin puede rebosarse y apuntar a otro Bucket secundario
y as sucesivamente.

Ahora es cuando surgen los problemas. Cuando un dato se


borra de un Bucket hay que reorganizar la informacin para no dejar
espacios en blanco dentro de la tabla. Esto se realiza por medio de
un empaquetamiento Packed. Sin embargo cuando se va a realizar
muchas modificaciones y/o borrados y el fichero es muy grande, es
aconsejable hacer una actualizacin de los datos del fichero.
4.7 Esquema Bsico de los tres tipos de Organizacin.
* Fichero de Acceso Secuencial:
- Almacenamiento en dispositivo secuencial o direccionable.
- No existe campos claves que relacione a algn registro.
- Los datos estn almacenados en el orden en el que han sido
introducidos.
- El acceso a los registros es nicamente secuencial.
* Fichero de Acceso Directo:
- Almacenamiento en dispositivo direccionable.
- Existe en los registros un campo denominado
que hace referencia inequvoca a dicho registro
nmero de registro.
- Los datos estn almacenados en el orden en el
introducidos.
- El acceso a los registros puede ser tanto
travs del campo clave como secuencial.

campo clave
a travs del
que han sido
aleatorio

* Fichero de Acceso Indexado:


- Almacenamiento en dispositivo direccionable.
- Existe en los registros un campo denominado campo clave
principal y campo clave secundario, que hacen referencia
inequvoca a dicho registro.
- Los datos estn almacenados en el orden alfabtico por el
campo clave.
- El acceso a los registros puede ser tanto aleatorio a
travs del campo clave como secuencial.
- El acceso Indexado-Secuencial permite el acceso como si se
tratara de un fichero secuencial, sin embargo, los datos no
saldrn en el orden en el que fueron introducidos sino en
orden alfabtico por el campo que estamos leyendo.
TEMA 5: Problemas y Algoritmos importantes
En este apartado vamos a ver una serie de algoritmos de gran
importancia en esta asignatura. El dominio de los siguientes
algoritmos nos da pie a enfrentarnos a una serie de algoritmos ms
complejos y cuyo cdigo depende de aquellos.
5.1 Ordenacin por seleccin.
+-Algoritmo Orden_seleccin
|
|
Constante entera n=...
|
Vector entero a(1..n)
|
Variable entera x,i,j,h,mayor
|
|
Escribir "Introduzca el vector a ordenar"

| +-Para h desde 1 hasta n hacer


| |
Leer a(h)
| +-Finpara
|
| +-Para k desde 1 hasta n-1 hacer
| |
mayor <- k
| | +-Para j desde k+1 hasta n hacer
| | | +-Si a(j)<a(i) entonces mayor <-j
| | | +-Finsi
| | +-Finpara
| |
x
<- a(j)
| |
a(j) <- a(i)
| |
a(i) <- x
| +-Finpara
|
Escribir a
+-Final
5.2 Bsqueda Secuencial o Lineal.
+-Algoritmo Busqueda_secuencial
|
|
Constante entera n=...
|
Vector entero a(1..n)
|
Variable entera x,i,j,h,mayor
|
|
Escribir "Introduzca el valor a buscar"
|
Leer x
|
|
k <- 0
| +-Repetir
| |
k <- k+1
| | +-Si a(k)=x entonces
| | |
Escribir "El dato: ";a;
| | |
"esta en la posicin: ";k
| | +-Finsi
| +-Hasta que (k=n) or (a(k)=x)
+-Final
5.3 Bsqueda Dicotmica o Binaria.
+-Algoritmo Bsqueda_dicotmica
|
|
Constante entera n= ...
|
Vector de enteros a(1..n)
|
Variable entera x,i,j,m,h
|
|
Escribir "Introduzca el vector a ordenar"
| +-Para h desde 1 hasta n hacer
| |
Leer a(h)
| +-Finpara
|
|
Escribir "Introduzca el valor a buscar"
|
Leer x
|
|
i <- 1
|
j <- n
|
| +-Repetir
| |
m <-(i+j) div 2

| | +-Si x < a(m) entonces


| | |
j <- m-1
| | +-Sino
| | |
i <- m+1
| | +-Finsi
| +-Hasta que (a(m)=x or i>j)
|
| +-Si i>j entonces
| |
Escribir "El dato a buscar no se encuentra."
| +-Finsi
|
+-Final
5.4 Mezcla de ficheros Ordenados.
+-Algoritmo mezcla
|
| +-Registro estructurado r
| |
variable cadena clave
| |
...
| +-Finregistro
|
|
Ficheros de r A,B,C
|
Variables enteras a,b
|
|
Abrir secuencial A,B,C
|
Iniciar lectura en A,B
|
Iniciar escritura en C
|
|
Leer (A,a)
|
Leer (B,b)
|
| +-Mientras (NO fin(A) AND NO fin(B)) hacer
| | +-Si a.clave < b.clave entonces
| | |
Escribir (C,a)
| | |
Leer (A,a)
| | +Sino
| | |
Escribir (C,b)
| | |
Leer (B,b)
| | +-Finsi
| +-Finmientras
|
| +-Mientras (NO fin(A)) hacer
| |
Escribir (C,a)
| |
Leer (A,a)
| +-Finmientras
|
| +-Mientras (NO fin(B)) hacer
| |
Escribir (C,b)
| |
Leer (B,b)
| +-Finmientras
|
|
Cerrar A,B,C
|
+-Final

TEMA 0: Introduccin
0.1 Orgenes del C
El lenguaje C
cuando trabajaba, junto
operativo UNIX.

fue inventado por Dennis Ritchie en 1972


con Ken Thompson, en el diseo del sistema

El lenguaje C deriva del lenguaje B de Thompson, el cual, a


su vez, deriva del lenguaje BCPL desarrollado por Martin Richards.
Durante muchos aos el estndar de C fue la versin
proporcionada con el sistema operativo UNIX versin 5. Pero pronto
empezaron a surgir muchas implementaciones del C a raz de la
popularidad creciente de los microordenadores. Por este motivo, se
hizo necesario definir un C estndar que est representado hoy por
el ANSI C.
0.2 Caractersticas del lenguaje C
Algunas caractersticas del lenguaje C son las siguientes:
- Es un lenguaje de propsito general. Este lenguaje se ha utilizado
para el desarrollo de aplicaciones tan dispares como: hojas de
clculos, gestores de bases
de datos, compiladores, sistemas
operativos, ...
- Es un lenguaje de medio nivel. Este lenguaje permite programar a
alto nivel (pensando a nivel lgico y no en la mquina fsica) y a
bajo nivel (con lo que se puede obtener la mxima eficiencia y un
control absoluto de cuanto sucede en el interior del ordenador).
- Es un lenguaje porttil. Los
programas
fcilmente transportables a otros sistemas.

escritos

en

son

- Es un lenguaje potente y eficiente. Usando C, un programador puede


casi alcanzar la eficiencia del cdigo ensamblador junto con la
estructura del Algol o Pascal.
Como desventajas habra que resear que es ms complicado de
aprender que otros lenguajes como Pascal o Basic y que requiere una
cierta experiencia para poder aprovecharlo a fondo.
0.3 Uso del C
Los pasos a seguir desde el momento que se comienza a
escribir el programa C hasta que se ejecuta son los siguientes:
1.2.3.4.-

Escribirlo en un editor.
Compilarlo en un compilador.
Enlazarlo en un enlazador.
Ejecutarlo.

Paso 1: ESCRIBIRLO
El programa se puede escribir en cualquier editor que genere
ficheros de texto estndar, esto es, que los ficheros generados no
incluyan cdigos de control y caracteres no imprimibles.

Estos ficheros que contienen cdigo C se llaman ficheros


fuentes. Los ficheros fuentes son aquellos que contienen cdigo
fuente, es decir, ficheros con texto que el usuario puede leer y que
son utilizados como entrada al compilador de C.
Los programas pequeos suelen ocupar un solo fichero fuente;
pero a medida que el programa crece, se va haciendo necesario
distribuirlo en ms ficheos fuentes.
Paso 2: COMPILARLO
El compilador produce ficheros objetos a partir de los
ficheros fuentes. Los ficheros
objetos son los ficheros que
contienen cdigo objeto, es decir, ficheros con cdigo mquina
(nmero binarios que tiene significado para el microprocesador) y
que son utilizados como entrada al enlazador.
La extensin de estos ficheros es OBJ, aunque tambin los
hay con extensin LIB. A estos ltimos se les llama tambin ficheros
de librera o biblioteca; contienen cdigo mquina perteneciente a
cdigo compilado suministrado por el compilador.
Paso 3: ENLAZARLO
El enlazador
ficheros objetos.

produce un fichero ejecutable

a partir de los

Los ficheros ejecutables son aquellos que contienen cdigo


mquina y se pueden ejecutar directamente por el sistema operativo.
La extensin estos ficheros es EXE o COM.
Al proceso de enlazado tambin se le suele llamar el proceso
de linkado.
Paso 4: EJECUTARLO
El programa se puede ejecutar simplemente tecleando su
nombre desde la lnea de comandos del sistema operativo.
ESQUEMA
Los pasos anteriores se resumen en el siguiente esquema:
f1.c

---->

f1.obj

f2.c

---->

f2.obj

.
.
.
fn.c

.
.
.
---->

fn.obj
f1.lib
f2.lib
.
.
.

---------+
|
---------|
|
|
|
|
|
---------|
|---------------> f.exe
---------|
|
---------+
|
|
|
|

fm.lib

|
---------+

Hoy da los compiladores de C son muy sofisticados e


incluyen entornos integrados desde los cuales editamos, compilamos,
enlazamos, y podemos realizar una multitud de servicios ms.
En algunos de ellos se pueden realizar los pasos de
compilado, enlazado y ejecutado con la pulsacin de una sola tecla.
En programacin, la experiencia es el gran maestro. Por ello
es conveniente empezar a hacer programas en C cuanto antes.
TEMA 1 : Conceptos bsicos
1.0 Introduccin
En este segundo tema se describir la estructura bsica de
un programa en lenguaje C as como la forma de visualizar distintos
tipos de datos en pantalla. Se introducirn los conceptos de tipos
de datos bsicos y su utilidad.
1.1 El programa HOLA MUNDO
Este programa se ha convertido en un clsico dentro de los
libros de programacin. Simplemente muestra en pantalla el mensaje
HOLA MUNDO, esto que puede parecer muy tonto es algo fundamental
puesto que si no sabemos imprimir mensajes datos en la pantalla
difcilmente nuestro programa se podr comunicar con el usuario que
lo utilice.
Mostraremos el programa y a continuacin describiremos cada
una de las instrucciones que lo forman.
/* Programa : HOLA MUNDO */
#include <stdio.h>
main()
{

printf ("\nHola mundo");

}
Como podemos observar se trata de un programa muy sencillo.
La primera lnea es lo que se conoce como un comentario, un mensaje
que el programa aade al cdigo del programa para explicar o aclarar
su funcionamiento o el de una parte de l. Los comentarios se pueden
situar en cualquier parte de nuestro cdigo y se considerar como
comentarios cualquier mensaje que se encuentre entre los caracteres
/* y */.
Los "/*" y "*/" no son caracteres, sino smbolos o banderas.
La siguiente lnea es lo que se conoce como directiva del
preprocesador, todos
los compiladores de
C disponen de
un
preprocesador, un programa que examina el cdigo antes de compilarlo
y que permite modificarlo de cara al compilador en distintos
sentidos. En temas posteriores trataremos en profundidad estas
directivas del preprocesador, pero para el desarrollo de los temas
anteriores a este debemos conocer al menos la directiva #include.
Las directivas se caracterizan por comenzar con el carcter # y se
deben incluir al comienzo de la lnea aunque es probable que esto

dependa de la implementacin del compilador con el que estemos


trabajando. La directiva include permite aadir a nuestro cdigo
algn fichero de texto, de tal forma que la directiva es sustituida
por el texto que contiene el fichero indicado. En general los
ficheros que acompaan a esta directiva son ficheros .H (Header Cabecera), en los que se incluyen definiciones de funciones que
deseamos utilizar
en nuestros programas,
constantes o tipos
complejos de datos.
La librera stdio.h (STandarD Input/Output) con tiene las
funciones estndar de entrada salida, y en ella se encuentra la
funcin printf que utilizamos en nuestro programa. Como se observa
en el cdigo el nombre de la funcin a incluir debe ir entre los
caracteres <...>. A medida que vayan surgiendo iremos indicando las
funciones estndar que deberan incorporar todos los compiladores C
y cual es su fichero de definicin .H.
En la siguiente lnea nos encontramos con main(). Esto
indica que aqu comienza nuestro programa, en realidad estamos
definiendo una funcin (esto se indica con los parntesis al final
de la lnea) pero esto lo discutiremos en temas posteriores. La
funcin main (principal en ingls) siempre debe existir y contendr
el programa principal.
Finalmente nos encontramos
el programa principal, una
sentencia printf entre llaves ({, }). Las llaves en C representan
bloques, y encierran un conjunto de sentencias o instrucciones (lo
que el computador ejecutar), considerando todas ellas como una
sola, permitiendo una definicin homognea de los distintos bloques
que constituyen el programa. En nuestro caso tenemos un slo bloque
que no es ni ms ni menos que el programa principal, que en nuestro
caso est compuesto por una sola sentencia (la lnea que contiene el
printf). Como se observa en el cdigo las sentencias en C deben
terminar con un punto y coma (;), #include <stdio.h> y main() no son
sentencias, dan informacin sobre la estructura del programa, y por
tanto no finalizan con un punto y coma.
NOTA: La razn de que la lnea "main()" no tenga un punto y
coma ";" al final es debido a que la sentencia en s termina al
cerrar el corchete, no en que dicha lnea proporcione informacin
sobre la estructura del programa.
De hecho, si el "main()"
constituyese una lnea de prototipo tendra un ";" al final.
La funcin printf permite visualizar datos formateados en
pantalla, es decir, permite indicar un formato como si de un impreso
o formulario se tratase indicando donde se deben visualizar cada
uno. En el siguiente tema cuando se introduzcan los tipos bsicos de
datos se comprender mejor sto. Por ahora slo nos interesa conocer
que printf visualiza mensajes en pantalla.
El mensaje debe ir entre comillas dobles (") y dentro de las
comillas se puede mostrar cualquier secuencia de caracteres. El
formato de esta funcin para este segundo tema ser:
printf ("mensaje");
En el siguiente tema, cuando expliquemos
instruccin, ampliaremos esta definicin.

en profundidad la

En nuestro programa observamos que el mensaje de texto que


visualiza la instruccin printf comienza con los caracteres \n.

Estos caracteres nos permiten algunas funciones especiales para


controlar la forma de visualizar los mensajes, la ms utilizada en
\n que significa nueva lnea, as nuestra sentencia printf ("\nHOLA
MUNDO"); mover el cursor (la posicin de la pantalla donde
actualmente se visualizan los datos) a una nueva lnea situndolo a
la izquierda de la pantalla y visualizar el mensaje HOLA MUNDO.
Para finalizar con este punto se indicar las secuencias
antecedidas por \ que se pueden incluir en una instruccin printf:
+-------------------------+------+
| Nueva lnea
| \n
|
| Tabulador horizontal
| \t
|
| Tabulador vertical
| \v
|
| Backspace (<-)
| \b
|
| Retorno de carro
| \r
|
| Avance de pgina
| \f
|
| Pitido (alerta)
| \a
|
| Caracter \
| \\
|
| Caracter ?
| \?
|
| Caracter '
| \'
|
| Caracter "
| \"
|
| Nmero Octal (ooo)
| \ooo |
| Nmero Hexadecimal (hh) | \xhh |
+-------------------------+------+
Algunos comentarios sobre estos cdigos. En primer lugar el
primer grupo (hasta carcter \), eran utilizados para mover el
cursor en terminales. Los terminales podan ser una pantalla o una
impresora, esta es la razn por la que nos encontramos cosas como
avance de pgina o retorno de carro. Los caracteres \ ? ' " son
especiales puesto que se utilizan dentro del mensaje a visualizar
para indicar como se visualiza, o sea, si escribimos \ el compilador
buscar el siguiente carcter y si es alguno de los anteriores los
visualiza sino corresponde con ninguno simplemente lo ignora, con lo
cual no podramos visualizar el carcter \. Otro tanto sucede con
las comillas puesto que para C, las comillas representan una cadena
de caracteres, si escribimos " en nuestro mensaje se considerar que
ste termina ah con lo que lo que se encuentre despus no tendr
sentido para el compilador.
Por ltimo Nmero octal y Nmero hexadecimal nos permite
introducir directamente el cdigo numrico en octal o hexadecimal
del carcter que deseemos visualizar, dependiendo del que est
activo en nuestro computador. En general el cdigo utilizado para
representar internamente los caracteres por los computadores es el
cdigo ASCII (American Standard Code for Information Interchange).
En este segundo tema hemos aprendido a escribir programas
que visualicen en pantalla mensajes utilizando la funcin estndar
de la librera stdio. Antes de terminar unos breves comentarios. El
lenguaje C diferencia entre maysculas y minsculas con lo cual
main,
MAIN, MaIn,
seran identificadores
distintos para el
compilador, en general en C se suele escribir todo en minsculas a
excepcin de los mensajes a visualizar (cuyo uso depende del
programador) y de las constantes, esto no tiene por que hacerse as
pero digamos que se trata de una tradicin de la programacin en C.
Las separaciones entre lneas tambin son arbitrarias y su nica
funcin es facilitar la legibilidad del cdigo, sirviendo de
separadores entre fragmentos de programa relacionados entre s. Y
esto es todo por ahora.

1.2 Tipos de datos bsicos del C


La
mayora de
los programas
realizan algo
til y
generalmente para ello es necesario trabajar con grandes cantidades
de datos, si queremos realizar un programa que nos calcule un
sistema de ecuaciones tenemos que indicar cuales son las ecuaciones
para que el programa las resuelva. Por tanto un programa estar
constituido por una serie de datos y una serie de sentencias o
instrucciones que le dicen lo que tiene que hacer con esos datos.
Los lenguajes de programacin disponen de una serie de tipos de
datos bsicos, y proporcionan herramientas para crear estructuras a
medida que faciliten el acceso a la informacin. As en nuestro caso
ficticio de resolver un sistema de ecuaciones podemos almacenar los
coeficientes de cada ecuacin con lo que utilizaramos como tipo de
dato los nmeros, si plantesemos el problema desde un punto de
vista matricial nos interesara tener un tipo de datos matriz y lo
ideal sera tener un tipo de datos ecuacin. En este apartado
describiremos los tipos bsicos que proporciona el lenguaje C y
dejaremos para temas posteriores la declaracin de tipos complejos.
Estos tipos bsicos son los siguientes:
+--------+---------------------------------------+
| int
| Representa nmeros enteros.
|
| float | Representa nmeros decimales.
|
| double | Representa nmeros decimales de mayor |
|
| precisin.
|
| char
| Representa caracteres.
|
+--------+---------------------------------------+
Aunque el tipo char represente caracteres internamente para
el computador no es ms que un nmero comprendido entre 0 y 255 que
identifica un caracter dentro de el cdigo especificado para tal
propsito en el sistema en el que nos encontremos trabajando. El
cdigo ms utilizado para este tipo de representacin es el ASCII ya
mencionado anteriormente.
NOTA: Segn la mquina, el compilador empleado y las opciones
de compilacin activas, "char" puede interpretarse con signo o sin
signo. Esto es, de -128 a 127 o desde 0 a 255. Si se requiere una
representacin que no dependa de las opciones del compilador, etc.,
se puede poner "signed char" o "unsigned char", segn el caso.
Como decamos antes el ordenador debe de disponer de los
datos necesarios para resolver el problema para el que lo queramos
programar. Difcilmente se podra resolver un sistema de ecuaciones
si no se dispone de stas. Para ello podemos definir variables. Las
variables almacenan valores de un tipo especificado y en ellas
almacenamos los datos de nuestro problema, se denominan variables
por que su valor puede cambiar a lo largo del programa. Para
referenciar una variable especificada es necesario que la podamos
identificar para ello se utiliza un nombre o identificador que no es
ms que una secuencia de caracteres, esta secuencia no puede
contener caracteres espaoles (acentos y ees), ni caracteres que
tengan alguna funcin especial en el C como por ejemplo los
caracteres que representan operaciones matemticas +, -, etc...,
tampoco pueden contener espacios por lo que se suele utilizar el
carcter subrayado (_) si el identificador que deseamos asignarle a
nuestra variable est formado por varias palabras, pero en general

con los caracteres y nmeros tenemos suficiente para dar nombres


autoexplicativos, aunque los nmeros no pueden comenzar el nombre de
una variable. Las variables se suelen escribir con letra minscula
aunque no es necesario y es aconsejable que los nombres sean auto
explicativos para que resulte ms sencillo leer el programa (es
mejor llamar resultado a una variable que x).
Las variables se declaran indicando el tipo que van a tener
seguido de su identificador y terminando la lnea con un punto y
coma. Algunos ejemplos:
int
numero;
/* nmero no sera un nombre vlido */
float mi_variable;
char letra;
Si necesitamos declarar varias variables de un mismo tipo se
pueden incluir en la misma lnea todos los nombres que deseemos
separndolos por una coma:
int
float

numero1,numero2,numero3;
coordenada_x,coordenada_y;

El compilador tiene que conocer las variables que va ha


utilizar cada bloque para reservarles sitio, por ello las variables
se suelen declarar al principio de cada bloque. Puesto que an no
sabemos como definir funciones nuestro programa slo dispone de un
bloque (el main()) con lo que nuestras variables deben de declararse
al comienzo del main() dentro del bloque, es decir, inmediatamente a
continuacin de la llave abierta ({). Un ejemplo:
NOTA: Aunque el prrafo anterior da a entender que se puede
declarar una variable en cualquier momento, el estndar ANSI C
obliga a realizar las declaraciones al principio de cada bloque. En
el caso se variables globales la sintaxis es ms flexible, para
poder utilizar el "Scope" en nuestro provecho.
main()
{
int numero;
numero =20;
}
Podemos tambin declarar variables fuera del bloque main().
Estas variables se conocen como variables globales y cualquier
funcin puede acceder a ellas, como slo tenemos una funcin (main)
en este caso nos dara igual declarar las variables dentro o fuera
de main.
De poco nos serviran estos datos numricos si no pudisemos
realizar operaciones con ellos, el lenguaje C permite realizar las
operaciones bsicas con estas variables de tipo numrico, estas son:
+---+---------------------------------------------+
| + | para indicar suma
|
| - | para indicar resta
|
| * | para indicar producto
|
| / | para indicar divisin
|
| % | para indicar mdulo (resto divisin entera) |
+---+---------------------------------------------+

Podemos combinar estas operaciones en la forma que nos


plazca con variables o constantes (podemos operar variables con
nmeros fijos) y utilizar los parntesis, caracteres ( y ) para
indicar precedencia de las operaciones como lo haramos en una
expresin matemtica normal. En principio slo podemos realizar
operaciones con variables que sean del mismo tipo, aunque en general
podemos operar los tipos float y double con tipos int o incluso
char, en principio no podramos almacenar un valor float (un nmero
real) en una variable int (entera), para ello tendramos que
convertir ese nmero real en entero de alguna forma. Podemos
convertir tipos bsicos utilizando la facilidad del C conocida como
cast. Esta facilidad simplemente consiste en indicar antes de una
variable o constante el tipo al que lo deseamos convertir entre
parntesis y el compilador se encargar del resto. Un ejemplo :
NOTA: El C tambin define la conversin automtica de tipos.
float
int

a;
b;

b=30;
a=(float)b;
Para ejemplificar todo esto vamos a realizar un programa que
nos calcule el espacio recorrido por un mvil con velocidad uniforme
durante un tiempo determinado. El programa sera algo as:
#include <stdio.h>
main()
{
float

e,v,t;

v = 30; /* Velocidad del mvil en Km/h */


t = 5; /* Tiempo durante el cual se mueve */
e = v*t;
printf ("\nVelocidad : %f\nTiempo : %f",v,t);
printf ("\nEspacio recorrido : %f",e);
}
Este programa calcula el espacio recorrido por un mvil en
movimiento uniforme a una velocidad indicada por la variable v,
durante un tiempo indicado por la variable t. Lo ms interesante de
este programa es que hemos utilizado la funcin printf para
visualizar valores de variables.
Como
decamos ms
arriba la
funcin printf permite
visualizar mensajes
formateados, es decir, en
la cadena de
caracteres entre comillas dentro del parntesis nos indica la forma
en la que se visualizarn los datos. Ya hablamos de los caracteres
especiales como \n, ahora nos ocuparemos de la visualizacin de las
variables.
Cuando deseamos visualizar una variable debemos indicarlo en
la cadena de formateo (la secuencia de caracteres entre comillas)
mediante el caracter % seguido de un caracter que nos indica el tipo
de dato a visualizar. Estos tipos son los siguiente:
+-----+------------------------------------------------+
| d,i | Entero en notacin decimal con signo.
|

| o | Entero notacin octal sin signo.


|
| x,X | Entero en notacin hexadecimal sin signo.
|
| u | Entero en notacin decimal sin signo.
|
| c | Entero como caracter simple.
|
| s | Cadena de caracteres.
|
| f | Tipo double ( float) de la forma [-]mmmm.ddd. |
| e,E | Tipo double ( float) en notacin cientfica o |
|
| exponencial [-]m.dddde+-xx [-]m.ddddE+-xx.
|
| p | Tipo puntero.
|
+-----+------------------------------------------------+
Podemos as mismo indicar el nmero de cifras a visualizar,
intercalando entre el % y la letra que identifica el tipo de dato un
nmero. Si indicamos dos nmeros separados por un punto, el primer
nmero indica el nmero total de caracteres a visualizar y el
segundo el nmero de cifras decimales que queremos que se muestren,
obviamente este formato slo se utilizar con tipo float o double.
Algunos ejemplos:
printf ("%f",numero);
Visualiza un nmero real en el formato normal, parte entera
y parte decimal separadas por un punto.
printf ("%5.2f",numero);
Visualiza un nmero entero en el mismo formato que la
anterior, pero slo visualizando 5 cifras y siendo dos de ellas
reservadas para la parte decimal.
Hasta ahora hemos visto como decir a la funcin printf el
formato en el que debe visualizar los datos, pero no le hemos dicho
que datos debe visualizar. Lo que se escribir en el lugar indicado
por el % est especificado a continuacin de la cadena de formateo
entre comillas, separando cada una de ellas por comas. El primer %
coincide con el primer parmetro despus de las comillas, el segundo
con el segundo y as sucesivamente. De esta forma un programa como
este:
#include <stdio.h>
main()
{
int
i;
float a,b;
i = 10;
a = 30.456;
b = 678.12;
printf ("\nvar1:%d var2:%6.2f var3:%6.1f",i,a,b);
}
tendr como salida:
var1:10 var2:30.46 var3:678,1
Como se puede observar en el ejemplo si la precisin
especificada en la cadena de formateo es menor que la real del
nmero, la funcin printf aproxima a la precisin especificada.
1.3 Entrada de datos por teclado

El programa anterior para el clculo de el espacio funciona


correctamente, pero cada vez que deseemos calcular un nuevo espacio
debemos dar valores a las variables v y t y recompilar nuestro
programa. Sera estupendo poder leer por teclado los datos de la
velocidad y del tiempo y as permitir a nuestro programa trabajar
con cualquier valor de velocidad y tiempo. Aqu es donde realmente
se comprende de donde viene el nombre de variables.
Para leer los datos por teclado se utiliza la funcin scanf
cuya definicin se encuentra tambin en el fichero stdio.h. El
formato es idntico al de printf utilizando una cadena de formateo
con los caracteres % y la letra que indica el tipo, ahora del dato
que vamos a leer, seguido de la variable en la que deseamos que se
lea antecedida por el carcter &. Nuestro programa del mvil se
convierte ahora en:
#include <stdio.h>
main()
{
float

v,t,e;

printf ("\nDime la velocidad de el mvil:");


scanf ("%f",&v);
printf ("\Dime el tiempo :");
scanf ("%f",&t);
e = v*t;
printf ("\nUn mvil desplazndose a %5.2f Km/h durante
%5.2f horas, recorre una distancia de %5.2f Km",v,t,e);
}
Ahora cada vez que el programa se ejecute nos pedir que
introduzcamos los valores de la velocidad y el tiempo y nos
proporcionar el espacio que recorreramos, evitando de esta forma
la necesidad de recompilar el programa cada vez que deseemos
realizar un nuevo clculo.
TEMA 2: Control de Flujo de programa
2.0 Introduccin
En tema anterior aprendimos a trabajar con variables, leer
sus valores por teclado, visualizarlas en pantalla y realizar
operaciones elementales con ellas.
Los programas que escribimos hasta ahora se ejecutaban
secuencialmente, es
decir, instruccin tras
instruccin, sin
posibilidad de volver a ejecutar una instruccin ya ejecutada o
evitar la ejecucin de un grupo de instrucciones si se dan unas
caractersticas determinadas.
En este tercer tema se describirn las instrucciones que nos
permiten escribir programas que no se ejecuten de una forma
secuencial en el sentido explicado en el prrafo anterior.
2.1 Expresiones condicionales.
En

ciertas ocasiones

nos puede

interesar que

un programa

llegado a un punto de su ejecucin vuelva hacia atrs y se ejecute


de nuevo, pero lo que en general nos interesar ser que este
regreso a una lnea de terminada de nuestro cdigo se realice si se
cumple una cierta condicin. Por esta razn es necesario explicar,
antes de comenzar con las instrucciones propiamente dichas de
control de flujo de programa, como le indicamos al ordenador que
deseamos evaluar una condicin.
Las expresiones que nos permiten realizar sto reciben el
nombre de expresiones condicionales o booleanas. Estas expresiones
slo pueden tomar dos valores: VERDADERO (TRUE) o FALSO (FALSE). En
general un valor de 0 indica que la expresin es falsa y un valor
distinto de 0 indica que la expresin es verdadera.
Como hemos indicado se trata de expresiones condicionales, y
anlogamente
a las
expresiones aritmticas
podemos comparar
variables entre s, constantes entre s (lo cual no es muy til
puesto que si conocemos los dos valores ya sabemos la relacin que
existe entre
ambas constantes) y
por supuesto variables
y
constantes. Adems podemos agrupar condiciones entre s formando
expresiones ms complejas y ayudarnos de los parntesis para indicar
el orden de evaluacin. Los operadores condicionales son:
==
!=
>
<
>=
<=

Representa igualdad.
Representa desigualdad
Mayor que.
Menor que.
Mayor o igual que.
Menor o igual que.

Podemos encadenar distintas expresiones condicionales, las


cuales deben de ir entre
parntesis (comparamos de dos en dos)
utilizando los operadores:
&&
||

Y lgico.
O lgico.

Veamos un ejemplo de expresin condicional


(a==2)||((b>=0)&&(b<=20))
la expresin ser cierta si la variable a es igual a dos o
si la variable b tiene un valor comprendido entre 0 y 20.
2.1.1 La instruccin if... else.
En ingls if significa si condicional, por ejemplo, si
llueve me llevar el paraguas, else significa sino, sino llueve me
ir a la playa. Este es el significado que poseen en programacin.
Su sintaxis es:
if (condicin) instruccin;else instruccin;
NOTA: La sintaxis real del
bloque else bloque.

IF es la siguiente: if (condicin)

Un programa ejemplo nos indicar su funcionamiento con


claridad. Supongamos que deseamos dividir dos nmeros. El nmero por
el que dividimos no puede ser cero, sto nos dara un valor de
infinito, provocando un error en el ordenador. Por tanto antes de

dividir deberamos de
sera algo como sto:

comprobar si el divisor es

cero. El programa

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

dividendo,divisor;

printf ("\nDime el dividendo:");


scanf ("%f",ndo);
printf ("\nDime el divisor:");
scanf ("%f",&divisor);
if (divisor==0)
printf ("\nNo podemos dividir un nmero por 0");
else
printf ("\nEl resultado es: %f",dividendo/divisor);
}
Como en todas los comandos del lenguaje C una instruccin,
en general, puede ser solamente una o un conjunto de ellas incluidas
entre llaves.
Por ltimo el lenguaje C dispone de un operador ternario (de
tres elementos) que permite construir determinadas estructuras
if-else, en concreto toma un valor u otro dependiendo de una
expresin condicional. Su sintaxis es:
exp1 ? exp2 : exp3
Si exp1 es cierta la expresin tomar el valor exp2, sino
tomar el valor exp3. Un ejemplo de su utilizacin:
/* La variable z toma el valor mximo entre a y b */
z = ( (a>b) ? a : b);
Como se puede observar se trata de una secuencia if else
pero muy concreta, probablemente el compilador generar un cdigo
mucho ms eficiente para este tipo de secuencia de ah su inclusin
en el juego de operadores del C.
A continuacin se describirn las instrucciones que nos
permiten controlar el flujo de programa, en las cuales tendremos que
utilizar expresiones condicionales continuamente, por lo cual no
insistiremos ms en este tema.
2.2 Control del flujo de programa
2.2.0 Introduccin
A estas alturas el lector ya debera conocer lo que es el
flujo de programa. El flujo de programa es la secuencia de
instrucciones que un programa ejecuta desde su comienzo hasta que
finaliza. En principio la ejecucin es secuencial, comienza con la
primera instruccin y termina con la ltima. Sin embargo es comn
que nos interese que nuestro programa no termine con la ltima de
las instrucciones (si por ejemplo no podemos abrir un fichero y la
funcin del programa es modificar ese fichero, el programa no
debera realizar ninguna operacin y terminar al detectar el error),

o puede que nos interese que un grupo de instrucciones se ejecute


repetidamente hasta que le indiquemos que pare. Todo esto se puede
conseguir con las instrucciones que se describirn a continuacin.
2.2.1 Creacin de bucles de ejecucin.
2.2.1.0 Concepto de bucle
En la introduccin ya se ha mencionado lo que es un bucle.
Una secuencia de instrucciones que se repite un nmero determinado
de veces o hasta que se cumplan unas determinadas condiciones.
Los bucles son extremadamente
algunos ejemplos son:

tiles en nuestros programas,

* Lectura/Visualizacin de un nmero determinado


como por ejemplo una matriz.

de datos,

* A veces se hace necesario introducir esperas en nuestros


programas ya sea por trabajar con un perifrico lento o
simplemente por ralentizar su ejecucin. Los primeros se
llaman bucles de espera activa y los segundo bucles
vacos.
* En aplicaciones
grficas
rellenado de polgonos.

como

trazado

de

lneas

* Lectura de datos de un fichero...


A
continuacin
describiremos
las
opciones
que nos
proporciona el lenguaje de programacin C para crear y gestionar los
bucles.
2.2.1.1 Bucle for
La primera opcin de que disponemos es el bucle for. Este
tipo de instruccin se halla presente en la mayora de los lenguajes
de programacin estructurados, y permite repetir una instruccin o
conjunto de instrucciones un nmero determinado de veces. Su
sintaxis es como sigue:
for (exp1;exp2;exp3) instruccin;
exp1 es una expresin que slo se ejecuta una vez al
principio del bucle. El bucle for suele utilizarse en combinacin
con un contador. Un contador es una variable que lleva la cuenta de
las veces que se han ejecutado las instrucciones sobre las que acta
el comando for. Por tanto exp1 suele contener una expresin que nos
permite inicializar ese contador generalmente a 0 aunque eso depende
de para qu deseemos utilizar el bucle.
exp2 es la expresin que nos indica cuando debe finalizar el
bucle, por tanto se tratar de una expresin condicional. Su
interpretacin
sera
algo
como;
repite
la instruccin (o
instrucciones) mientras se cumpla exp2. Esta expresin se evaluar
en cada ciclo del bucle para determinar si se debe realizar una
nueva iteracin.
NOTA:

Hay que

recordar que

exp2 se

evala al principio del

bucle, y no
NINGUNA vez.

al final.

Por tanto

es posible

no ejecutar el bucle

exp3 es una expresin que se ejecuta en cada iteracin.


Puesto que como ya indicamos el bucle for se utiliza junto a un
contador, exp3 en general contiene una instruccin que actualiza
nuestro contador.
Por tanto en un bucle
diferenciadas:

con contador distinguimos tres partes

* La inicializacin del contador (exp1).


* La condicin de fin de bucle (exp2).
* Y la actualizacin del contador (exp3).
El bucle for esta especialmente pensado para realizar bucles
basados en contadores. Se puede utilizar en bucle del tipo "repite
esto hasta que se pulse una tecla", pero para estos tenemos
instrucciones ms apropiadas. Veamos unos ejemplos que nos permitan
comprender ms fcilmente el funcionamiento del comando for.
Ejemplo 1: Contar hasta diez.
#include <stdio.h>
main()
{
int i; /* Esta variable la utilizaremos como contador*/
for (i=0;i<10;i++) printf ("\n%d",i);
}
Este programa mostrar en pantalla numeros de 0 a 9 (diez en
total). exp1 inicializa nuestro contador que en este caso es una
variable de tipo entero, con el valor 0, exp2 nos dice que nuestra
instruccin (la funcin printf) debe repetirse mientras el contador
sea menor que diez y finalmente exp3 indica que el contador debe de
incrementarse en una unidad en cada ciclo del bucle.
Nos podra interesar contar desde diez hasta 1, en este caso
el comando for debera de ser:
for (i=10;i>0;i--) printf ("\n%d",i);
Ejemplo 2: Visualizar dos tablas de multiplicar en pantalla.
#include <stdio.h>
main()
{
int
int

i;
tabla1,tabla2;

tabla1 = 2; /* Primero la tabla del dos */


tabla2 = 5; /* y a continuacin la tabla del cinco*/
for (i=1;i<11;i++)
{
printf ("\n %2dx%2d=%3d",tabla1,i,tabla1*i);

printf ("
}

%2dx%2d=%3d",tabla2,i,tabla2*i);

El ejemplo es anlogo al anterior, pero en este caso


visualizamos valores desde uno a diez, en lugar de visualizarlos de
0 a 9. En este ejemplo, el bucle acta sobre un conjunto de
instrucciones, no sobre una sola, por tanto debemos introducirlas
entre las llaves para indicar al compilador que la instruccin for
acta sobre las dos instrucciones. Estamos considerando todo lo que
se encuentre entre las llaves como una sola instruccin.
Para terminar con los bucles de tipo for un leve comentario
sobre los bucles aadados, simplemente lo que se hace es incluir un
bucle dentro de otro. Supongamos que deseamos introducir los datos
de nuestro jugadores preferidos por teclado para almacenarlos en el
ordenador, y que de cada jugador queremos almacenar por ejemplo, su
nacionalidad, su peso y su altura. En este caso nos sera til un
bucle anidado. El bucle exterior nos contara jugadores, mientras
que para cada jugador tendramos otro bucle que nos leyera los tres
datos que necesitamos. Si tenemos veinte jugadores preferidos,
incluiramos una unas instrucciones como estas:
for (i=0;i<20;i++)
{
printf ("Jugador preferido %d",i);
for (j=0;j<3;j++)
{
leo caracterstica j;
la almaceno donde sea;
}
}
Nada ms en lo que a bucles de tipo for respecta. A
continuacin veremos las otras estructuras que nos proporciona el
lenguaje C para la realizacin de bucles.
2.2.1.2 Bucles while.
La sintaxis de este bucle ser algo as:
while (exp2) instruccin;
En ingls while significa mientras, por tanto la lnea
anterior
significara
mientras
de
cumpla
exp2 ejecuta la
instruccin. Obviamente la instruccin que ejecutemos debe de
permitir que en algn caso se cumpla exp2, de lo contraro el
ordenador permanecera eternamente ejecutando instruccin. Tambin
es evidente que exp2 debe ser una expresin condicional. Como vemos
este tipo de bucles no est orientado a contadores, es mucho ms
genrico, sin embargo se puede utilizar de forma anloga a for. Con
la nomenclatura utilizada anteriormente tendramos algo como sto:
exp1;
while (exp2)
{
instruccin;
exp3;
}

Con este esquema se hace


patente la utilidad de la
instruccin for para bucles con contador puesto que se "centraliza"
todo el
proceso de gestin
del contador (inicializacin
y
actualizacin) en una sola instruccin. Un error comn al escribir
un bucle con contador con una estructura while es olvidar introducir
exp3, con lo cual nunca se cumple exp2 y nunca salimos del bucle. De
nuevo un bucle infinito aunque a veces nos interesa tener un bucle
infinito. La forma ms sencilla de realizar un bucle infinito es con
la expresin:
while (1) instruccin;
Como indicamos exp2 es una expresin condicional y para
estas expresiones un valor distinto de 0 es verdadero por tanto un 1
es siempre cierto y no hay manera de salir del bucle puesto que es
una constante y ninguna modificacin de variables por parte de
instruccin tendra repercusiones sobre ella.
Los bucle while son tiles en aplicaciones como; lee datos
de este fichero mientras no llegues al final muestra estos datos
en la pantalla mientras no pulse una tecla.
Una peculiaridad de esta instruccin es que puede no
ejecutarse nunca la instruccin afectada por el while. Algunos
ejemplos:
Ejemplo 1: Contar hasta diez.
#include <stdio.h>
main()
{
int

i;

i = 0;
while (i<10)
{
printf ("\n%d",i);
i++;
}

El primer valor que visualizaremos ser el 0. Cuando i tenga


el valor nueve la condicin i<10 todava se cumple por lo que
entraremos en el bucle de
nuevo, visualizaremos el nueve e
incrementamos i con lo que pasa a tener el valor 10 momento en el
cual se vuelve a evaluar la expresin i<10 que en este caso sera
falsa y no volveramos a entrar en el bloque de instrucciones (las
que estn entre llaves). As visualizamos nueve nmero de 0 a 9 como
antes. Si incluysemos una instruccin para visualizar el valor de i
antes de abandonar el programa (justo antes de las ltimas llaves el
valor que se mostrara sera 10.
Ejemplo 2: Lee nmeros enteros hasta que se
valor hasta que se introduzca el valor 0.
#include <stdio.h>
main()
{
int

numero;

introduzca el

numero = 10;
while (numero!=0)
{
printf ("\nDime un nmero:");
scanf ("%d",&numero);
}
}
En este ejemplo tenemos que introducir en la variable nmero
un valor distinto de cero antes de entrar en el bucle, puesto que en
principio al declarar una variable el valor de sta no est
determinado y podra valer cero, en cuyo caso nunca se ejecutara el
bucle. Esta es la misin de la instruccin numero = 10;.
2.2.1.3 Bucle do.. while
Su funcionamiento es anlogo al anterior, con la nica
salvedad de que la condicin ahora se evala despus de ejecutar la
instruccin su sintaxis sera:
do instruccin while (exp2);
Si en el ejemplo anterior utilizamos esta estructura no
sera necesario actualizar numero con un valor distinto de cero,
puesto que antes de comprobar si es cero leeramos el valor.
#include <stdio.h>
main()
{
int

numero;

do
{

printf ("\nDime un numero :");


scanf ("%d",&numero);
} while (numero !=0);
La diferencia fundamental con la instruccin anterior es que
esta estructura ejecuta la instruccin sobre la que acta al menos
una vez.
2.2.1.4 Modificadores del flujo de programa.
Puede que en ciertas ocasiones no nos interese que si se da
alguna condicin
slo se ejecute un
conjunto de todas las
instrucciones sobre las que acta el bucle o simplemente salir del
bucle antes de llegar a la condicin que hayamos indicado en el
comando for o en while. Esto es posible mediante dos modificadores:
continue y break.
El primero de ellos, continue, permite volver a reevaluar la
condicin de salida, es decir, despus de ejecutar continue la
siguiente instruccin que se ejecutar
ser el for o el while.
Veamos un ejemplo de aplicacin para comprender mejor como funciona
este comando.
#include <stdio.h>

main()
{
int
int

numero;
contador;

contador =0;
do
{
printf ("\nIntroduce el nmero %2d:",contador);
scanf ("%d",&numero);
if ((numero<0)||(numero>20)) continue;
contador++;
} while (contador<50);
}
Este programa lee nmeros en una variable hasta un mximo de
50, alcanzado este mximo el programa termina. Adems si el nmero
no est entre 0 y 20 (si es menor que 0 o mayor que 20) vuelve a
pedir que lo introduzcamos. El comando continue en la instruccin if
obliga al programa a saltar a la instruccin while donde se vuelve a
evaluar la condicin, sin pasar por la lnea en la que se incrementa
el contador. De esta forma se nos vuelve a pedir el mismo nmero y
la entrada incorrecta no es tenida en cuenta.
La funcin de break es ligeramente distinta no salta a la
instruccin en la que se evala la condicin sino que simplemente
abandona el bucle y contina la ejecucin en la lnea inmediatamente
siguiente al bucle.
#include <stdio.h>
main()
{
int

i;

for (i=0;i<20;i++)
{
if (i==5) break;
printf ("\n%d",i);
}
printf ("\n\n%d",i);
}
La salida de este programa sera algo como esto:
0
1
2
3
4
5
Y con esto terminamos todo lo relacionado con los bucles en
lenguaje C. Los bucles son una estructura bsica y es necesario
utilizarla en la inmensa mayora de los programas.
2.2.2 Mens de Opciones.

2.2.2.1 Introduccin
La mayora de los programas permiten realizar una serie de
operaciones sobre datos. Lo ideal sera poder indicarle al ordenador
que operacin deseamos realizar sobre estos datos en lenguaje
natural. Por ejemplo, para una base de datos nos podra interesar
decirle al ordenador: "Buscame todas la fichas de libros que traten
sobre informtica" "Borra de la base de datos el libro tal".
Existen en la actualidad herramientas de este tipo, pero an distan
bastante del lenguaje natural, por ello una de las formas ms
sencillas de indicar al ordenador la operacin que deseamos realizar
es utilizar un men de opciones.
La otra solucin comnmente adoptada es una lnea de
comandos, es decir, escribimos una frase en un lenguaje muy reducido
indicando al ordenador lo que deseamos hacer (de una forma similar a
como lo hacemos en MS-DOS). Esta solucin tiene la desventaja de
tener que aprender complicados comandos y distintas secuencias para
distintos programas.
Un men nos muestra en pantalla todas las opciones que
podemos realizar con nuestro programa de forma que no es necesario
que el
usuario conozca ninguna serie
de ordenes complejas,
simplificando por tanto el uso de los programas por parte de los
usuarios.
2.2.2.2 Sentencia switch-case-default
La mayora de los lenguajes de alto nivel disponen de alguna
instruccin que permite gestionar el valor de una variable de una
forma estructurada y sencilla, permitiendo por tanto la creacin de
mens de programa de una forma sencilla. En lenguaje C esta
instruccin es switch-case-default. Veamos un ejemplo de como
funciona mediante un pequeo programita.
#include <stdio.h>
main()
{
int
printf
printf
printf
printf
printf

opcion;
("\nEjemplo de Men de Programa");
("\n1.-Cargar fichero de datos");
("\n2.-Almacenar fichero de datos");
("\n3.-Modificar datos");
("\n4.-Salir");

printf ("\n\nDime tu opcin :");scanf ("%d",&opcion);


switch (opcion)
{
case 1:
/* Cdigo para cargar fichero de datos*/
break;
case 2:
/* Cdigo para almacenar datos */
break;
case 3:
/* Cdigo para modificar datos */

break;
case 4:
/* Salir del programa */
exit (0);
default :
printf ("\nSu opcin no est disponible");
printf ("\nIntntelo con otra");
}
Del ejemplo se deduce fcilmente el funcionamiento de esta
secuencia. El comando switch (var) realizar una bifurcacin o salto
a la lnea indicada por la variable var. Si var vale 2, el programa
se seguir ejecutando a partir de la lnea marcada con case 2. Todos
los separadores case estn separador por un comando break, ya que de
no ser as la ejecucin seguira lineal hasta encontrar la llave que
termina el comando switch. La palabra clave default indica el cdigo
que se debera ejecutar si el valor de la variable especificada en
el switch no corresponde con ninguno de los indicados por los case
dentro del switch. As en nuestro ejemplo si opcion tiene un valor
distinto de 1, 2, 3 4, se mostrar en pantalla un mensaje
indicando que la opcin indicada no es correcta. La sintaxis de esta
estructura sera:
switch (variable)
{
case valor1-variable:
cdigo asociado;
case valor2-variable:
cdigo asociado;
.
.
case valorN-variable:
cdigo asociado;
default:
cdigo asociado;
}
Dentro del cdigo asociado a cada opcin se debern incluir
las instrucciones break adecuadas. Ya se explic el funcionamiento
de break y continue cuando se habl de bucles. Su funcionamiento es
anlogo para los comandos for, while, do-while, y switch.
Un fragmento de
completo sera:

cdigo para

la imprementacin

de un men

while (Opcion!=0)
{
/* Secuencia de printfs que muestran en pantalla
el men. En este caso la opcin 0 debera ser salir */
switch (opcion)
{
/* Secuencia de cases */
default :
/* Mostrar mensaje de error */
}
}
Por su puesto las aplicaciones del comando switch van mucho
ms all de la simple creacin de mens. Puede ser utilizada en
cualquier aplicacin en la que se necesite realizar distintas
operaciones dependiendo de un valor. Un ejemplo sencillo podra ser

un un programa que imprimiese textos en impresora. Podramos marcar


en el texto mediante una secuencia especial como debera ser impreso
el texto a continuacin. Por ejemplo:
@N
@n
@C,@c @S,@s etc...

Activa Negrita.
Desactiva Negrita.
Activa/desactiva cursiva.
idem sibrayado

Leeramos estos valores provenientes del teclado o de un


fichero y con algo de procesamiento y una instruccin switch con
dicha variable, en cada case enviaramos a la impresora la secuencia
adecuada para realizar cada una de la opciones.
TEMA 3 : Estructuras de datos estticas
3.0 Introduccin.
En este tema se describirn las herramientas que proporciona
el lenguaje C para trabajar con tipos y estructuras de datos,
flexibilizando de esta forma la creacin de programas por parte del
programador.
3.1 Matrices Estticas.
La matriz es una estructura de datos bsica dentro de los
lenguajes de programacin y conceptualmente son identicas a sus
homnimas matemticas. Por tanto una matriz es un conjunto de datos
de un tamao definido que se encuentran consecutivos en memoria y en
la que es posible el acceso al elemento que deseemos simplemente con
indicar su posicin. La declaracin de una matriz en lenguaje C es
como sigue:
tipo_de_dato identificador[tamao1][tamao2]...;
Dnde :
tipo_de_dato: Es el tipo de datos que contendr la matriz.
Hasta ahora slo conocemos los tipos bsicos de datos; int, float,
double, char. Posteriormente veremos como definir nuestros propios
tipos de datos.
identificador: Es el nombre que le damos a la variable
matriz y po el cual la referenciaremos en nuestro programa.
[tamao] : Indica el nmero de elementos de tipo
tipo_de_datos contendr la matriz identificador. Si definimos dos
tamaos [tamao1][tamao2] nuestra matriz ser bidimensional.
Algunas declaraciones de matrices seran:
/* Matriz de nmeros reales de 10x10 */
float
matriz[10][10];
/* Matriz tridimensional de nmeros enteros 20x20x10 */
int
Tridimensional[20][20][10];
Como ya se supondr el acceso a cada elemento de la matriz
se realiza especificando su posicin, pero sta comienza a contarse
desde el valor 0, es decir, la primera matriz que hemos definido

(matriz) tendr elementos desde el [0][0] al [9][9]. Esto puede


causar algunos mal entendidos cuando se trabaja con matrices
estticas. Por ejemplo:
a = matriz [2][1];
/* A toma el valor del elemeto (2,1) comenzando a contar
desde 0 o del (3,2) si consideramos que el primer valor de la matriz
es el (1,1) */
tridimensional [5][16][1] = 67;
/* Introduce el valor 67 en
especificada */
Las variables
de
declaraciones,
se pueden
declaracin, ayudndose de
inicializaciones mltiples.

la

entrada

de

la matriz

tipo matriz como


el resto de las
inicializar en
el momento
de su
las llaves ({}) para la inclusin de

int matriz[2][3] = {
{ 1,2,3 },
{ 4,5,6 }
};
Estas lneas nos declararan una matriz llamada "matriz" de
2x3 elementos inicializada con los valores indicados. Las matrices
son extremadamente tiles para trabajar con multitud de problemas
matemticos que se formulan de esta forma o para mantener tablas de
datos a los que se accede con frecuencia y por tanto su referencia
tiene que ser muy rpida. Supongamos que estamos desarrollando un
programa para dibujar objetos en
tres dimensiones y ms que la
exactitud de la representacin (aunque esto es muy relativo), nos
interesa
la
velocidad.
En
la
representacin
de
objetos
tridimensionales se hace continua
referencia a las funciones
trigonomtricas seno y coseno. El clculo de un seno y un coseno
puede llevar
bastante tiempo as que
antes de comenzar la
representacin calculamos todos los senos y cosenos que necesitemos
(por ejemplo con una resolucin de 1 grado -360 valores-) cada vez
que necesitemos uno de estos valores accedemos a la matriz en lugar
de llamar a la funcin que nos lo calcula. Veamos como sera nuestro
programa (las funciones sin y
cos se encuentran en la librera
estandar math.h y sus paramtros estn en radianes).
#include <stdio.h>
#include <math.h>
main()
{
float
float
int

senos[360]; /* Almacenamos senos */


cosenos[360];
i;

/* Inicializamos las matrices */


for (i=0;i<360;i++)
{
seno[i]
= sin (3.14159*i/180);
coseno[i] = cos (3.14159*i/180);
}
printf ("\nEl coseno de 30 es : %f",coseno[30]);
printf ("\nEl seno de 30 es
: %f",seno[30]);
}

3.2 Tipos compuestos


3.2.0 Introduccin
En muchas ocasiones nos interesara disponer de variables
compuestas de otras variables y trabajar con ellas como si se
tratasen de una sola. Un ejemplo tpico es una ficha de datos de una
agenda. Necesitaramos una variable que nos almacenase el nombre,
otra variable que nos almacenase la direccin, otra para el telfono
y as sucesivamente para todos los datos que deseemos mantener.
Podramos disponer de una variable para cada campo (cada una de las
informaciones que componen nuestra ficha) pero esto resultara un
tanto engorroso a la hora de su programacin.
El lenguaje C dispone de mecanismos para trabajar con
variables compuestas de otras variables con suma facilidad. Existen
dos tipos bsicos: estructuras y uniones.
3.2.1 Estructuras de datos.
Se trata de la forma ms versatil de trabajar con fichas de
informacin. Veamos como se definen y posteriormente comentaremos
todos los aspectos relevantes de ellas.
struct [Nombre_de_la_estructura]
{
tipo1
campo1;
tipo2
campo2;
.
.
tipoN
campoN;
} [variable];
La palabra clave struct define una estructura. Por tratarse
de un tipo de datos puede utilizarse directamente para definir una
variable. La variable aparece entre corchetes puesto que puede ser
omitida. Si se especifica una variable, estaremos definiendo una
variable cuyo tipo ser la estructura que la precede. Si la variable
no
es indicada
definimos un
nuevo tipo
de datos (struct
Nombre_de_la_estructura), que podremos utilizar posteriormente. Si
es el nombre de la estructura lo que se omite, tendremos que
especificar obligatoriamente una variable que tendr esa estructura
y no podremos definir otras variables con esa estructura sin tener
que volver a especificar todos
los campos. Lo que se encuentra
dentro de las llaves es una definicin tpica de variables con su
tipo y su identificador. Todo esto puede parecer un poco confuso
pero lo aclararemos con unos ejemplos.
struct punto
{
float
x;
float
y;
int
color;
} punto_de_fuga;
Aqu estamos definiendo una variable llamada punto_de_fuga
cuyo tipo es una estructura de datos formada por tres campos y a la
que hemos llamado punto. Dos de ellos son de tipo float y

representan las coordenadas del punto, el tercer valor es un entero


que indica el color de ese punto. En este caso hemos definido una
variable y una estructura. Al disponer de un identificador para esta
ltima podemos definir nuevas variables de esta estructura.
struct punto
struct punto

origen1;
final1;

Donde origen1 y final1 son variables de tipo struct punto


que
hemos definido
anteriormente. Si
en la
definicin de
punto_de_fuga no se hubiese includo un identificador para la
estructura (en este caso el identificador es punto), no podramos
definir nuevas variables con esa estructura ya que no estara
identificada por ningn nombre.
Tambin podramos haber excludo el nombre de la variable
(punto_de_fuga). En este caso
lo que definiramos sera una
estructura llamada punto que pasara a ser un nuevo tipo disponible
por el usuario. As los tipos de variables de que dispondramos
ahora seran:
int
float
double
char
struct punto
Por tanto podramos definir cualquier
tipos o incluso definir matriz de estos tipos.
struct punto

variable con

estos

matriz_de_puntos[30];

As estaramos definiendo una matriz de 30 elementos en la


que cada elemento es una struct punto con sus tres campos.
Lo que ahora nos interesa es saber como referenciar esos
campos y acceder o modificar,
por tanto la informacin que
contienen. Esto se consigue separando el identificador del campo de
la variable mediante un punto. As:
punto_de_fuga.x = 0;
punto_de_fuga.y = 0;
punto_de_fuga.color = 10;
inicializa la cada uno de los campos de la variable punto de
fuga con sus valores correspondientes. Est claro que para acceder a
los campos necesitamos alguna variable cuyo tipo sea nuestra
estructura. Si no tenemos variable no tenemos informacin (sera
como hacer int = 6).
En el caso de la matriz tenemos tantas variables de tipo
struct punto como las indicadas,
puesto que el punto separa el
nombre de la variable del campo al que queremos acceder, la forma de
modificar una entrada de la matriz sera:
matriz_de_puntos[4].x = 6;
matriz_de_puntos.x[4] = 6; /* No sera correcto */
Esta
ltima declaracin
estructura de un tipo como:

se

podra

utilizar

con

una

struct otra
{
float
x[10];
} matriz_de_puntos;
Con lo cual accederamos al cuarto elemento del campo x de
matriz_de_puntos que es una variable de tipo struct otra constituida
por una matriz de diez floats.
Para terminar con la declaracin struct indicar que es
posible la declaracin de estructuras anidadas, es decir, un campo
de una estructura puede ser otra estructura.
struct vector
{
float
float
float
};

x;
y;
z;

struct poligono_cuadrado
{
struct vector
p1;
struct vector
p2;
struct vector
p3;
struct vecto
p4;
};
struct cubo
{
struct poligono_cuadrado
int
struct vector
};
struct cubo

cara[6];
color;
posicion;

mi_cubo;

Hemos declarado una variable (mi_cubo) de tipo struct cubo


que es una estructura conteniendo un valor entero que nos indica el
color de nuestro objeto, una variable de tipo struct vector
(posicion) indicando la posicin del objeto en un espacio de tres
dimensiones (posicion tiene tres campos x,y,z por tratarse de una
struct vector) y una matriz de seis elemento en la que cada elemento
es un struct poligono_cuadrado, el cual est formado por cuadro
vectores que indican los cuatro vrtices del cuadrado en 3D. Para
aceder a todos los campos de esta variable necesitaramos sentencias
del tipo.
mi_cubo.color = 0;
mi_cubo.posicion.x = 3;
mi_cubo.posicion.y = 2;
mi_cubo.posicion.z = 6;
mi_cubo.cara[0].p1.x = 5;
/* Ahora acedemos a la coordenada 0 del tercer polgono
de la cara 0 de mi_cubo*/
mi_cubo.cara[0].p3.z = 6;
....
3.2.2 Estructuras solapadas. union

La definicin de una union es analoga a la definicin de una


estructura. La diferencia entre ambas es que los campos que
especifiquemos en una union ocupan todos la misma posicion de
memoria. Cuando se declara una union se reserva espacio para poder
almacenar el campo de mayor tamao de los declarados y como ya se
dijo todos los campos ocupan la misma posicin en la memoria. Veamos
un ejemplo.
union ejemplo
{
char
int
} mi_var;

caracter;
entero;

mi_var es una variable cuyo tipo es union ejemplo, y el


acceso a cada campo de los definidos se realiza igual que en las
struct mediante la utilizacin de un punto. Hasta aqu nada nuevo lo
que sucede es que carcter y entero (los dos campos) ocupan la misma
posicin de memoria. As:
mi_var.entero = 0; /* Como el tipo int ocupa ms que el tipo
char ponemos a 0 toda la union */
mi_var.caracter = 'A'; /* El cdigo ASCII de A es 65, por
tanto ahora mi_var.entero = 65 */
mi_var.entero = 0x00f10;
Esta ltima instruccin introduce un valor en hexadecimal en
la variable mi_var.entero. El cdigo hexadecimal se representa en C
anteponiendo al nmero los caracteres 0x. Para comprender lo que
realiza esta instruccin veamos un poco como el ordenador representa
los nmero internamente.
Todos hemos oido alguna vez que el ordenador slo entiende
ceros y unos, pues bien, lo nico que significa sto es que el
ordenador cuenta en base dos en lugar de hacerlo en base diez como
nosotros. Cuando contamos en base diez comenzamos en 0 y al llegar a
nueve aadimos una unidad a la izquierda para indicar que llegamos a
las centenas y as consecutivamente. Cada cifra de un nmero en base
diez representa esa cifra multiplicada por una potencia de diez que
depende de la posicin del dgito. Es lo que se llama descomposicin
factorial de un nmero.
63452 = 6*10^4+3*10^3+4*10^2+5*10^1+2*10^0=
= 60000+3000+400+50+2
Como nuestro ordenador en lugar de contar de diez en diez
cuenta de dos en dos cada cifra es una potencia de dos. El sistema
de numeracin en base dos se denomina sistema binario.
b100101 = 1*2^5+0*2^4+0*2^3+1*2^2+0*2^1+1*2^0=
= 32 + 0 + 0 + 4 + 1 = 37
As es como representa el
ordenador el nmero 37 en su
sistema binario. Cada una de las cifras de un nmero binario se
denomina BIT (BInary digiT) y los ordenadores los suelen agrupar el
grupos de 8. As 8 bits se denomina un byte, 16bits seran 2 bytes y
se denomina word o palabra y as sucesivamente.
El mayor nmero que podramos representar
byte (8bits) sera:

en binario con 1

b11111111 = 255
Este es el tamao que el lenguaje C asigna al tipo char, que
slo puede representar 256 valores distintos, desde 0 a 255. El tipo
int short suele ocupar una palabra es decir, 16 bits. As con 16
bits el mayor nmero que podemos representar es:
b1111111111111111 = 65535
NOTA: El tamao asociado a cada tipo de
especfico de cada compilador/ordenador. No debera
supuesto...

datos es muy
darse nada por

Los nmeros en binario rpidamente se hacen muy largos por


ello se utilizan otros sistemas de numeracin que permitan una
escritura ms compacta sin perter la informacin binaria en gran
medida. Esto sistemas son en general sistemas con bases que son
potencias de dos. As tenemos el sistema octal (base 8) y el sistema
hexadecimal (base 16). Este ltimo es el ms ampliamente usado,
disponemos de 16 cifras de 0 a F(15) y la caracterstica ms
importante de este sistema es que cada cifra hexadecimal, representa
cuatro bits binarios, con lo cual el paso de un sistema al otro es
extremadamente fcil.
Volvamos ahora a la instruccin anteriormente indicada
mi_var.entero = 0x00f10;
Si pasamos este nmero a binario obtenemos:
0 -> 0000
f -> 1111 -> 15 en decimal
1 -> 0001 -> 1 en decimal
0f10 <-> 0000111100010000 -> 3856 en decimal
Como dijimos anteriormente un char ocupa 8 bits y un int
ocupa 16, como la union los solapa tendramos un esquema en la
memoria del ordenador como ste:
int
char

0000111100010000
00010000

-> 3856
-> 65 ('A')

As mi_var.caracter contendr el valor A, pero mi_var.entero


contendr el valor 3856.
NOTA: Como ya se indic en la nota anterior, el tamao
asignado a cada tipo depende del ordenador y del compilador. Adems,
algunos ordenadores almacenan los nmeros en formato Bajo/Alto (los
8 bits e Intel) y otros en formato Alto/Bajo (Motorola, Sparc,
etc.).
Este tipo de estructura se suele utilizar en aplicaciones a
bajo nivel en la que es necesario poder utilizar este tipo de
solapamiento de bits. Como ya se habr podido comprobar para
compremder mnimamente como funciona esto es necesario bajar mucho
al nivel de la mquina con la consiguiente complicacin de la
explicacin.

3.2.3 Tipos definidos por el usuario.


Con las palabras clave struct y union, podemos definir
nuevos tipos de variables pero tenemos que indicar estos tipos con
todo su nombre, es decir, struct mi_struct. El lenguaje C dispone de
un comando que nos permite dar el nombre que nosotros deseemos a
cualquier tipo de variable. El comando es typedef y su forma de
utilizacin es como sigue:
typedef tipo nuevo_tipo
Algunos ejemplos para aclarar las cosas:
typedef unsigned char
typedef struct cubo

BYTE;
HEXAHEDRO;

As con estas definiciones una declaracin de las siguientes


variables:
BYTE
HEXAEDRO

var1,var2;
var3;

Sera equivalente a:
unsigned char
struct cubo

var1,var2;
var3;

TEMA 4 : Punteros y funciones


4.0 Introduccin
En este tema estudiaremos el tipo de dato ms importante
dentro del lenguaje C. Los punteros. Absolutamente todos los datos
en C pueden ser tratados como punteros y por ello este lenguaje
proporciona una serie de importantes herramientas para trabajar con
ellos.
Adems introduciremos el
concepto de funcin asociado
estrechamente a la llamada programacin modular que nos permite
crear un programa mucho ms claro y fcil de corregir a la hora de
encontrar errores.
4.1 Punteros
4.1.1 Qu son los punteros ?
Como su nombre indica un puntero es algo que apunta, es
decir, nos indica dnde se encuentra una cierta cosa. Supongamos
(como otras tantas veces) que disponemos de un gran archivo en el
que
almacenamos
informes.
Este
fichero
est
dividido en
compartimientos, cada uno de los cuales contiene uno de nuestros
informes (esto sera equivalente a las variables con las que hemos
trabajado hasta ahora -informes-, la cuales contienen informacin, y
el archivo representa la memoria de nuestro ordenador, obviamente
las variables se almacenan en la memoria). Sin embargo otros
compartimientos no contienen informes, sino que lo que contienen es
una nota que nos dice dnde est ese informe.
vez,

Supongamos que como mximo trabajamos con tres informes a la


digamos que no nos gusta leer demasiado, y reservamos, por

tanto, tres compartimientos en los indicamos en que compartimiento


se encuentran esos tres informes. Estos tres compartimientos seran
nuestros punteros y como ocupan un compartimiento en el archivo
(nuestra memoria) son realmente variables, pero variables muy
especiales. Estas variables punteros ocupan siempre un tamao fijo,
simplemente contienen el nmero de compartimiento en el que se
encuentra la informacin. No contienen la informacin en s.
Si en nuestro archivo pudisemos almacenar un mximo de
20.000 hojas, esta sera la capacidad de nuestra memoria (unos 19
Kilobytes). Estas hojas de nuestros informes las agruparamos de
distintas formas. Quiz un informe slo ocupe 5 pginas mientras que
otro puede ocupar 100. Podemos ver esto como los distintos tipos de
datos del C, es lgico
pensar que necesitamos ms espacio para
almacenar un nmero real que uno entero o que una matriz de 20x20
elemento. Estos son nuestro informes en nuestro archivo. Sin embargo
los punteros siempre ocupan lo mismo, en nuestro ejemplo nos
llegara con
una pgina para poder
escribir el nmero del
compartimiento en el que se encuentra el inicio del informe.
As en nuestro supuesto de que slo trabajemos con tres
informes a la vez, dispondramos de tres compartimientos en los que
indicaramos dnde se encuentran esos informes que buscamos y de
esta forma cuando terminemos con ellos y deseemos trabajar con otros
slo tendremos que cambiar el contenido de esos tres compartimientos
diciendo donde se encuentran los nuevos informes. De esta forma no
es necesario reservar unos compartimientos para trabajar y cada vez
que cambiemos
de trabajo llevar
los informes viejos
a su
compartimiento anterior y traer
los nuevos informes a estos
compartimientos.
Esto es lo que en programacin se conoce como referencia
indirecta o indirecin. Accedemos a la informacin a travs de un
puntero que nos dice dnde se encuentra sta. Y a grandes rasgos
sto son los punteros, referencias indirectas a datos en la memoria
del ordenador.
Los punteros en C son muy importantes puesto que su
utilizacin es bsica para la realizacin de numerosas operaciones.
Entre ellas: paso de parmetros que deseamos sean modificados,
tratamiento de estructuras dinmicas de datos (sto es, variables
que no se declaran en el programa y se crean durante la ejecucin
del programa), cadenas de caracteres ...
4.1.2 Operadores que actan sobre punteros.
El lenguaje C
proporciona dos operadores relacionados
directamente con los punteros. El primero de ellos es el operador &.
Ya hemos visto este operador antes en las llamadas a la funcin
scanf, posteriormente explicaremos por que la funcin scanf necesita
ser llamada con el operador &.
El operador &, es un operador unario, es decir, acta sobre
un slo operando. Este operando tiene que ser obligatoriamente una
estructura direccionable, es decir, que se encuentre en la memoria
del ordenador. Estas estructuras son fundamentalmente las variables
y las funciones, de las que hablaremos posteriormente. Decimos que
slo se puede aplicar sobre estructuras direccionables porque su
funcin es devolver la posicin de memoria en la que se encuentra
dicha estructura. En nuestro ejemplo nos indicara cual sera el

compartimiento en el que se encuentra el informe que le indiquemos.


El segundo operador es el *. Tambin se trata de un operador
unario como el anterior y su funcin en este caso es la de permitir
el acceso al contenido de la posicin indicada por un puntero. En
nuestro ejemplo el operador * nos permitira leer o escribir el
informe al que apunta uno de nuestros compartimientos punteros.
Adems el carcter * se utiliza para declarar punteros los cuales
como ya dijimos tienen que ser declarados (tienen su propio
compartimiento en el archivo).
Por supuesto el operador * debe ser aplicado sobre un
puntero,
mientras que
el operador
& sobre
una estructura
direccionable (variable o funcin).
Veamos un ejemplo de su
utilizacin:
main ()
{
int x,y;
int *px;

/* Variables de tipo entero */


/* Puntero a una variable de tipo entero */

/* Leemos la direccin -compartimiento- de la variable


-informe- x mediante & y lo almacenamos en la variable puntero
px */
px = &x;
/* px contiene la direccin en la que se encuentra x */
/* Utilizando el operador *, podemos acceder a su
informacin. *px representa ahora el valor de la variable x */
*px = 10;
/* Ahora x contiene el valor 10 */
y = 15;
/* Si ahora hacemos que nuestro puntero apunte a la
variable y utilizando de nuevo el operador & */
px = &y;
/* El valor que ahora toma *px ser el valor de y puesto
que es el compartimiento al que ahora estamos apuntando */
*px = 125; /* Ahora y contiene el valor 125 */
x = *px
/* Ahora x contiene tambin 125 */
}
Como hemos visto en este ejemplo es exactamente igual
acceder a una variable que utilizar un puntero que apunte a ella
(hacemos que apunte a ella mediante el operador &) junto con el
operador *.
Pero el lenguaje C an ofrece otra herramienta ms para
trabajar con punteros. Es lo
que se suele llamar aritmtica de
punteros. Este tema lo trataremos en profundidad en el siguiente
apartado.
4.1.3 Punteros y matrices
Ya hemos hablado de las matrices en el tema anterior. Se
trataba de un conjunto de un nmero de terminado de variables de un
mismo tipo que se referenciaban con un nombre comn seguido de su
posicin entre corchetes con relacin al primer elemento. Todas las
entradas de una matriz estn consecutivas en memoria, por eso es muy
sencillo acceder
al elemento que
queramos en cada
momento
simplemente indicando su posicin. Slo se le suma a la posicin
inicial ese
ndice que indicamos.
Es un ejemplo
que casa
perfectamente con nuestro ejemplo de los informes, cada informe

podra ser considerado como una matriz de tantos elementos como


pginas tenga el informe y en los que cada uno de ellos es un tipo
de datos llamado pgina.
Las matrices son realmente punteros al inicio de una zona
consecutiva de los elementos indicados en su declaracin, por lo
cual podemos acceder a la matriz utilizando los corchetes como ya
vimos o utilizando el operador *.
elemento[i] <=> *(elemento +i)
Como ya se ha comentado todos los punteros ocupan lo mismo
en memoria, el espacio suficiente para contener una direccin, sin
embargo cuando se declaran es necesario indicar cual es el tipo de
datos al que van a apuntar (entero, real, alguna estructura definida
por el usuario). En nuestro ejemplo tendramos un tipo de puntero
por cada tipo de informe distinto, un puntero para informes de una
pgina, otro puntero para informes de 2 pginas y as sucesivamente.
En principio esto es irrelevante por que una direccin de memoria es
una direccin de memoria, independientemente de lo que contenga con
lo cual no sera necesario
declarar ningn tipo, pero esta
informacin es necesaria para implementar la aritmtica de punteros
que ejemplificaremos a continuacin.
Supongamos que hemos definido un tipo de datos en nuestro
programa que fuese pgina, si cada pgina puede contener 80
caracteres de ancho por 25 de alto, podra ser algo como sto:
typedef char pgina[80][25];
Y supongamos tambin que
slo tenemos tres
informes, de 1 pgina, de 5 pginas y de 25 pginas:
typedef pgina
typedef pgina
typedef pgina
Y en
nuestro
siguientes variables:
main()
{
pgina
informe1
informe2
informe3

tipos

de

informe1;
informe2[5];
informe3[25];
programa

principal

hemos

declarado

las

*punt_pgina;
i1[10],*punt1;
i3[5],*punt2;
i4[15],*punt3;

....
Por tanto disponemos de un puntero a pginas y tres
punteros, uno para cada tipo de informe y tres matrices de distintos
tipos de informes que nos permiten almacenar en nuestro archivo un
mximo de 30 informes (10 de 1 pgina, 5 de 5 pginas y 15 de 25
pginas).
Supongamos que en el programa principal se llenan esas
matrices con datos (por teclado o leyendo de un fichero, por
ejemplo) y realizamos las siguientes operaciones:
punt_pgina = (pgina *) &i4[0];
punt3
= (informe3 *)&i4[0];

Los cast (que comentamos en el tema 1) convierten las


direcciones al tipo apropiado, las direcciones que contendrn
punt_pgina y
punt3 sern exactamente
iguales, apuntarn al
principio del primer informe de tipo3. Sin embargo punt_pgina es un
puntero de tipo pgina y punt3 es un puntero de tipo informe3, qu
significa sto?. Si ejecutsemos una instruccin como sta:
punt_pgina = punt_pgina + 5;
punt_pgina pasara a apuntar a la quinta pgina del primer
informe de tipo 3 (i4[0]), puesto que punt_pgina es un puntero de
paginas. Mientras que si la operacin fuese:
punt3 = punt3 + 5;
punt3 pasara a apuntar a el quinto informe de tipo 3
(i4[5]), puesto que punt3 es un puntero a informes de tipo tres. Si
ahora realizsemos la operacin:
punt_pgina = (pgina *)punt3;
Ahora punt pgina apuntara a la primera pgina del quinto
informe de tipo 3. En
esto consiste la aritmtica de punteros,
cuando se realiza una operacin aritmtica sobre un puntero las
unidades de sta son el tipo que se le ha asociado a dicho puntero.
Si el puntero es de tipo pgina operamos con pginas, si es de tipo
informes operamos con informes. Es evidente que un informe de tipo 3
y una pgina tienen distintos tamaos (un informe de tipo 3 son 25
pginas por definicin).
Como hemos visto las matrices se pueden considerar como
punteros y las operaciones con esos punteros depende del tipo
asociado al puntero, adems es muy recomendable utilizar el cast
cuando se realizan conversiones de un tipo de puntero a otro.
4.1.4 Punteros y cadenas de caracteres
Como su propio nombre indica una cadena de caracteres es
precisamente eso un conjunto consecutivo de caracteres. Como ya
habamos comentado los caracteres se codifican utilizando el cdigo
ASCII que asigna un nmero desde 0 hasta 255 a cada uno de los
smbolos representables en nuestro
ordenador. Las cadenas de
caracteres utilizan el valor 0 ('\0') para indicar su final. A este
tipo de codificacin se le ha llamado alguna vez ASCIIZ (la Z es de
zero).
Las cadenas de caracteres se representan entre comillas
dobles (") y los caracteres simples, como ya habamos indicado con
comillas simples ('). Puesto que son un conjunto consecutivo de
caracteres la forma de definirlas es como una matriz de caracteres.
char

identificador[tamao_de_la_cadena];

Y por
ser en esencia una
matriz todo lo comentado
anteriormente para matrices y punteros puede ser aplicado a ellas.
As la siguiente definicin constituye tambin una cadena de
caracteres:
char

*identificador;

La diferencia entre ambas declaraciones es que la primera


reserva una zona de memoria de tamao_de_la_cadena para almacenar el
mensaje que deseemos mientras que la segunda slo genera un puntero.
La primer por tratarse de una matriz siempre tiene un puntero
asociado al inicio del bloque del tamao especificado. Podemos
tratar a las cadenas como punteros a caracteres (char *) pero
tenemos que recordar siempre que un puntero no contiene informacin
slo nos indica dnde se encuentra sta, por tanto con la segunda
definicin no podramos hacer gran cosa puesto que no tenemos
memoria reservada para ninguna informacin. Veamos un ejemplo para
comprender
mejor
la
diferencia
entra
ambas declaraciones.
Utilizaremos dos funciones especiales de stdio.h para trabajar con
cadenas. Estas son puts y gets que definiramos como un printf y un
scanf exclusivo para cadenas.
#include
main()
{
char
char
char

<stdio.h>
cadena1[10];
cadena2[10];
*cadena;

gets(cadena1); /* Leemos un texto por teclado y lo


almacenamos en cadena 1 */
gets(cadena2); /* Idem cadena2 */
puts (cadena1); /* Lo mostramos en pantalla */
puts (cadena2);
cadena = cadena1; /* cadena que slo es un puntero ahora
apunta a cadena1 en donde tenemos 10 caracteres reservados por la
definicin */
puts (cadena); /* Mostrara en pantalla el mensaje
contenido en cadena1 */
cadena = cadena2; /* Ahora cadena apunta a la segunda
matriz de caracteres */
gets(cadena);
/* Cuando llenos sobre cadena ahora
estamos leyendo sobre cadena2, debido al efecto de la instruccin
anterior */
puts(cadena2); /* SI imprimimos ahora cadena2 la pantalla
nos mostrar la cadena que acabamos de leer por teclado */
}
En el programa vemos como utilizamos cadena que solamente es
un puntero para apuntar a distintas zonas de memoria y utilizar
cadena1 o cadena2 como destino de nuestras operaciones. Como podemos
ver cuando cambiamos el valor de cadena a cadena1 o cadena2 no
utilizamos el operador de direccin &, puesto que como ya hemos
dicho una matriz es en s un puntero (si slo indicamos su nombre) y
por tanto una matriz o cadena de caracteres sigue siendo un puntero,
con lo cual los dos miembros de la igualdad son del mismo tipo y por
tanto no hay ningn problema.
4.2 Funciones
4.2.1 Introduccin
Hasta

el momento

hemos utilizado

ya numerosas

funciones,

como printf o scanf, las cuales forman parte de la librera estndar


de entrada/salida (stdio.h). Sin embargo el lenguaje C nos permite
definir nuestras propias funciones, es decir, podemos aadir al
lenguaje tantos comandos como deseemos.
Las funciones son bsicas en el desarrollo de un programa
cuyo tamao sea considerable, puesto que en este tipo de programas
es comn que se repitan fragmentos de cdigo, los cuales se pueden
incluir en una funcin con el consiguiente ahorro de memoria. Por
otra parte el uso de funciones divide un programa de gran tamao en
subprogramas
ms
pequeos
(las
funciones),
facilitando su
comprensin, as como la correccin de errores.
Cuando llamamos
a una funcin
desde nuestra funcin
principal main() o desde otra funcin lo que estamos haciendo
realmente es un salto o bifurcacin al cdigo que le hayamos
asignado, en cierto modo es una forma de modificar el flujo de
control del programa como lo hacamos con los comandos while y for.
4.2.2 Definicin de funciones
Ya hemos visto cual es la estructura general de una funcin
puesto que nuestro programa principal, main() no es otra cosa que
una funcin. Veamos cual es el esquema genrico:
tipo_a_devolver identificador (tipo1 parmetro1, tipo2 ...)
{
tipo1
Variable_Local1;
tipo2
Variable_Local2;
...
Cdigo de la funcin
return valor del tipo valor a devolver;
}
Lo primero con lo que nos encontramos es la cabecera de la
funcin.
Esta
cabecera
est
formada
por
una serie de
declaraciones. En primer lugar el tipo_a_devolver.
Todas las funciones tienen la posibilidad de devolver un
valor, aunque pueden no hacerlo. Si definimos una funcin que nos
calcula el coseno de un cierto ngulo nos interesara que nuestra
funcin devolviese ese valor. Si por el contrario nuestra funcin
realiza el proceso de borrar la pantalla no existira ningn valor
que nos interesase conocer sobre esa funcin. Si no se especifica
ningn parmetro el compilador supondr que nuestra funcin
devuelve un valor entero (int).
A continuacin nos encontramos con el identificador de la
funcin, es decir, el nombre con el que la vamos a referenciar en
nuestro programas, seguido de una lista de parmetros entre
parntesis y separados por comas sobre los que actuar el cdigo
que escribamos para esa funcin. En el caso de la funcin coseno a
la que antes aludamos, el parmetro sera el ngulo calculamos el
coseno de un cierto ngulo que en cada llamada a la funcin
probablemente
sea distinto.
Vase la
importancia de
los
parmetros, si no pudisemos definir un parmetro para nuestra
funcin coseno, tendramos que definir una funcin para cada
ngulo, en la que obviamente no indicaramos ningn parmetro.

A continuacin nos encontramos el cuerpo de la funcin. En


primer lugar declaramos las variables locales de esa funcin.
Estas variables solamente podrn ser accedidas dentro de la
funcin, esto es, entre las llaves ({}). Los nombres de las
variables locales pueden ser los mismos en distintas funciones
puesto que slo son accesibles dentro de ellas. As si estamos
acostumbrados a utilizar una variable entera llamada i como
contador en nuestro bucles, podemos definir en distintas funciones
esta variable y utilizarla dentro de cada funcin sin que haya
interferencias entre las distintas funciones.
Con respecto al cdigo de la funcin, pues simplemente se
trata de un programa como todos los que hemos estado haciendo
hasta ahora.
La instruccin return del final puede omitirse si la
funcin no devuelve ningn valor, su cometido es simplemente
indicar que valor tomara esa funcin con los parmetros que le
hemos pasado. En otros lenguajes las funciones que no devuelven
valores se conocen como procedimientos.
Veamos un ejemplo de definicin de una funcin.
int
{
int

busca_elemento (int *vector,int valor,int longitud)


i;

for (i=0;i<longitud;i++)
if (vector[i] == valor) break;
return i;
}
Esta funcin busca un valor en un vector de nmeros
enteros y devuelve el ndice dentro de la matriz de la entrada de
sta que lo contiene. Puesto que devuelve el ndice de la matriz
supondremos en principio un valor de retorno entero para ese
ndice. Los parmetros que debe conocer la funcin son: la matriz
en la que buscar, el valor que debemos buscar y la longitud de la
matriz. Podramos haber realizado una funcin a medida para que
utilizase una matriz de un nmero determinado de elementos (int
vector[100], por ejemplo) y ahorrar el parmetro longitud, sin
embargo con la definicin
que hemos hecho nuestra funcin
funcionar con matrices de cualquier longitud de enteros.
Hemos declarado adems una variable local que es necesaria
para la realizacin del bucle actuando como contador y conteniendo
adems el ndice dentro de la matriz que buscamos. Si la entrada i
de nuestro vector corresponde con valor, salimos del bucle y la
variable i contiene ese valor de la entrada, el cual es devuelto
por la funcin mediante la instruccin return. Ahora veremos como
utilizaramos esta funcin desde un programa:
main ()
{
int
matriz1[20];
int
matriz2[30];
int
indice,dato;
/* Aqu realizaramos alguna operacin sobre las

matrices como por ejemplo inicializarlas */


indice = busca_elemento (matriz1,10,20);
....
dato = 15;
indice = busca_elemento (matriz2,dato,30);
.....
}
Como vemos en las llamadas a nuestra funcin podemos
utilizar tanto variables o constantes como parmetros.
4.2.3 Ms sobre funciones
Cuando el valor que retornan las funciones no es entero,
es necesario que el compilador sepa de antemano su tipo por lo
cual es necesario aadir al comienzo del programa lo que se llaman
prototipos. Los prototipos simplemente son una predeclaracin de
la funcin, solo indican el tipo que devuelve, su nombre y los
tipos de los parmetros, no es necesario indicar un identificador
para los parmetros. Un prototipo para la funcin anterior sera:
int busca_elemento (int *, int, int);
Los fichero .h que se incluyen con la directiva del
procesador #include, contienen entre otras cosas los prototipos de
las funciones a las que nos dan acceso.
Para finalizar con las funciones vamos a explicar como
pasar parmetros que deseamos que la funcin modifique. Cuando
pasamos parmetros a una funcin sta realiza una copia de los
valores de stos en una zona de memoria propia, con lo cual la
funcin trabaja con estas copias de los valores y no hay peligro
de que se modifique la variable original con la que llamamos a la
funcin, forzando de esta forma a utilizar el valor retornado por
la funcin como parmetro. Sin embargo es posible que nos interese
que nuestra funcin nos devuelva ms de una valor o que uno de los
parmetros con los que lo llamamos se modifique en funcin de las
operaciones realizadas por la funcin. En este caso tenemos que
pasar los parmetros como punteros.
Cuando pasamos los valores como punteros la funcin
realiza una copia de los valores de los parmetros de las
funciones en su zona propia de memoria, pero en este caso el valor
que pasamos no es un valor en s, sino que es una direccin de
memoria en la que se encuentra ese valor que deseamos se
modifique, es decir, creamos un puntero que apunta a la posicin
que deseamos modificar, con lo cual tenemos acceso a esos valores.
Veamos un ejemplo tpico de parmetros que deben modificarse, este
es la funcin swap(a,b) cuya misin es intercambiar los valores de
los dos parmetros, es decir, el parmetro a toma el valor del
parmetro b y viceversa. La primera codificacin que se nos ocurre
sera sta:
swap (int a,int b)
{
int
t;
t = a;

a = b;
b = t;
}
Y nuestro programa principal podra ser algo como sto:
main ()
{
int
c,d;
c = 5;
d = 7;
swap (c,d);
}
Veamos que pasa en la memoria de nuestro ordenador.
-Funcin main()
-Espacio para la variable c (Posicin de memoria x)
-Espacio para la variable d (Posicin de memoria y)
-Inicializacin de las variables
-swap(c,d)
-Fin de main()
-Funcin swap
-Cdigo de la funcin swap
-Espacio privado para almacenar los parmetros (Posicin
de memoria z)
En este ltimo compartimiento es dnde almacenamos los
valores de nuestros parmetros que sern respectivamente 5 y 7.
Despus de la ejecucin de swap en esta zona de memoria los
valores
estn intercambiados,
nuestro parmetro
a que se
corresponde con la variable c en la llamada a swap contendr el
valor 7 y el parmetro b correspondiente a d en la funcin main
contendr el valor 5. Esto es lo que se encuentra almacenado en la
zona privada de memoria de la funcin. Con este esquema cuando la
funcin swap termina su ejecucin y se devuelve el control al
programa principal main, los valores de c y d no han cambiado,
puesto que los compartimientos o posiciones de memoria x e y no
han sido tocados por la funcin swap, la cual slo ha actuado
sobre el compartimiento z.
Si declaramos ahora nuestra funcin swap como sigue:
swap (int *p1,int *p2)
{
int
t;
t = *p1;
*p1 = *p2;
*p2 = t;
}

/*Metemos en t el contenido de p1 */
/* Contenido de p1 = contenido de p2 */

Tendremos el mismo esquema de nuestra memoria que antes


pero en lugar de almacenar en la zona privada de la funcin swap
para los parmetros los valores 5 y 7 tenemos almacenados en ella
los compartimientos en los que se encuentran, sto es, hemos
almacenado las posiciones x e y en lugar de 5 y 7. De esta forma
accedemos mediante un puntero a las variables c y d del programa
principal que se encuentran en las posiciones x e y modificndolas

directamente as que al regresar al programa principal sus valores


se encuentran ahora intercambiados.
En resumen, cuando deseemos que una funcin modifique el
valor de uno de los parmetros con los que es llamada debemos
pasar un puntero a esa variable en lugar del valor de esa
variable. Es evidente que si implementamos nuestra funcin de esta
forma, los parmetros jams podrn ser constantes, puesto que
difcilmente podramos modificar el valor de una constante.
TEMA 5 : Asignacin dinmica de memoria
5.0 Introduccin
En este tema estudiaremos las posibilidades que ofrece el
lenguaje C a la hora de trabajar dinmicamente con la memoria
dentro de nuestros programas, esto es, reservar y liberar bloques
de memoria dentro de un programa.
Adems en este tema se introducir el concepto de tipo
abstracto de dato y la forma de dividir un gran programa en otros
ms pequeos.
5.1

Asignacin dinmica y esttica de memoria.

Hasta este momento solamente hemos realizado asignaciones


estticas del programa, y ms concretamente estas asignaciones
estticas no eran otras que las declaraciones de variables en
nuestro programa. Cuando declaramos una variable se reserva la
memoria
suficiente para
contener la
informacin que debe
almacenar. Esta memoria permanece asignada a la variable hasta que
termine la ejecucin del programa (funcin main). Realmente las
variables locales de las funciones se crean cuando stas son
llamadas pero nosotros no tenemos control sobre esa memoria, el
compilador genera el cdigo para esta operacin automticamente.
En este
sentido las variables
locales estn asociadas
a
asignaciones de memoria dinmicas, puesto que se crean y destruyen
durante la ejecucin del programa.
As entendemos por asignaciones de memoria dinmica,
aquellas que son creadas por nuestro programa mientras se estn
ejecutando y que por tanto, cuya gestin debe ser realizada por el
programador.
5.2 Cmo se reserva memoria dinmicamente?
El lenguaje C dispone, como ya indicamos con anterioridad,
de una serie de libreras de funciones estndar. El fichero de
cabeceras stdlib.h contiene las declaraciones de dos funciones que
nos permiten reservar memoria, as como otra funcin que nos
permite liberarla.
5.2.1 Reserva de memoria
Las dos funciones que nos permiten reservar memoria son:
malloc (cantidad_de_memoria);
calloc (nmero_de_elementos, tamao_de_cada_elemento);
Estas dos funciones reservan la memoria especificada y nos

devuelven un puntero a la zona en cuestin. Si no se ha podido


reservar el tamao de la memoria especificado devuelve un puntero
con el valor 0 o NULL. El tipo del puntero es, en principio void,
es decir, un puntero a cualquier cosa. Por tanto, a la hora de
ejecutar ests funciones es aconsejable realizar una operacin
cast (de conversin de tipo) de cara a la utilizacin de la
aritmtica de punteros a la que aludamos anteriormente. Los
compiladores
modernos
suelen
realizar
esta
conversin
automticamente.
Antes de indicar como deben utilizarse las susodichas
funciones tenemos que comentar el operador sizeof. Este operador
es imprescindible a la hora de realizar programas portables, es
decir, programas que puedan ejecutarse en cualquier mquina que
disponga de un compilador de C.
El operador sizeof(tipo_de_dato), nos devuelve el tamao
que ocupa en memoria un cierto tipo de dato, de esta manera,
podemos escribir programas independientes del tamao de los datos
y de la longitud de palabra de la mquina. En resumen si no
utilizamos este operador en conjuncin con las conversiones de
tipo cast probablemente nuestro programa slo funciones en el
ordenador sobre el que lo hemos programado.
Por ejemplo, el los sistemas PC, la memoria est orientada
a bytes y un entero ocupa 2 posiciones de memoria, sin embargo
puede que en otro sistema la mquina est orientada a palabras
(conjuntos de 2 bytes, aunque en general una mquina orientada a
palabras tambin puede acceder a bytes) y por tanto el tamao de
un entero sera de 1 posicin de memoria, suponiendo que ambas
mquinas definan la misma precisin para este tipo.
Con todo lo mencionado anteriormente veamos un ejemplo de
un programa que reserva dinmicamente memoria para algn dato.
#include <stdlib.h>
#include <stdio.h>
main()
{
int
float

*p_int;
*mat;

p_int = (int *) malloc(sizeof(int));


mat
= (float *)calloc(20,sizeof(float));
if ((p_int==NULL)||(mat==NULL))
{
printf ("\nNo hay memoria");
exit(1);
}
/* Aqu iran las operaciones sobre los datos */
/* Aqu ira el cdigo que libera la memoria */
}
Este programa declara dos variables que son punteros a un
a un float. A estos punteros se le asigna una zona de
para el primero se reserva memoria para almacenar una
entera y en el segundo se crea una matriz de veinte

entero y
memoria,
variable

elementos cada uno de ellos un float. Obsrvese el uso de los


operadores cast para modificar el tipo del puntero devuelto por
malloc y calloc, as como la utilizacin del operador sizeof.
Como se puede observar no resulta rentable la declaracin
de una variable simple (un entero, por ejemplo, como en el
programa anterior) dinmicamente, en primer lugar por que aunque
la variable slo se utilice en una pequea parte del programa,
compensa tener menos memoria (2 bytes para un entero) que incluir
todo el cdigo de llamada a malloc y comprobacin de que la
asignacin fue correcta (sto seguro que ocupa ms de dos bytes).
En segundo lugar tenemos que trabajar con un puntero con lo cual
el programa ya aparece un poco ms engorroso puesto que para las
lecturas y asignaciones de las variables tenemos que utilizar el
operador *.
Para termina un breve comentario sobre las funciones
anteriormente descritas. Bsicamente da lo mismo utilizar malloc y
calloc para reservar memoria es equivalente:
mat = (float *)calloc (20,sizeof(float));
mat = (float *)malloc (20*sizeof(float));
La diferencia fundamental es que, a la hora de definir
matrices dinmicas calloc es mucho ms claro y adems inicializa
todos los elementos de la matriz a cero. Ntese tambin que puesto
que las matrices se referencian como un puntero la asignacin
dinmica de una matriz nos permite acceder a sus elementos con
instrucciones de la forma:
NOTA: En realidad existen algunas diferencias al trabajar
sobre mquinas con alineamiento de palabras.
mat[0] = 5;
mat[2] = mat[1]*mat[6]/67;
Con lo cual el comentario sobre lo engorroso que resultaba
trabajar con un puntero a una variable simple, en el caso de las
matrices dinmicas no existe diferencia alguna con una declaracin
normal de matrices.
5.2.2 Liberacin de la memoria.
La funcin que nos permite liberar la memoria asignada con
malloc y calloc es free(puntero), donde puntero es el puntero
devuelto por malloc o calloc.
En nuestro ejemplo anterior, podemos ahora escribir
cdigo etiquetado como : /* Ahora ira el cdigo que libera
memoria */

el
la

free (p_int);
free(mat);
Hay que tener cuidado a la hora de liberar la memoria.
Tenemos que liberar todos los bloque que hemos asignado, con lo
cual siempre debemos tener almacenados los punteros al principio
de la zona que reservamos. Si mientras actuamos sobre los datos
modificamos el valor del puntero al inicio de la zona reservada,
la funcin free probablemente no podr liberar el bloque de

memoria.
5.2.3 Ventajas de la asignacin dinmica.
Vamos a exponer un ejemplos
en el que se aprecie
claramente la utilidad de la asignacin dinmica de memoria.
Supongamos que deseamos programar una serie de funciones
para trabajar con matrices. Una primera solucin sera definir una
estructura de datos matriz, compuesta por una matriz y sus
dimensiones puesto que nos
interesa que nuestras funciones
trabajen con matrices de cualquier tamao. Por tanto la matriz
dentro de la estructura tendr
el tamao mximo de todas las
matrices con las que queramos trabajar y como tenemos almacenadas
las dimensiones trabajaremos con una matriz de cualquier tamao
pero tendremos reservada memoria para una matriz de tamao mximo.
Estamos desperdiciando memoria. Una definicin de este tipo sera:
typedef struct
{
float
int
} MATRIZ;

mat[1000][1000];
ancho,alto;

En principio esta es la nica forma de definir nuestro


tipo de datos. Con esta definicin una matriz 3x3 ocupar
1000x1000 floats al igual que una matriz 50x50.
Sin embargo podemos asignar memoria dinmicamente a la
matriz y reservar slo el tamao que nos hace falta. La estructura
sera sta.
struct mat
{
float
int

*datos;
ancho,alto;

};
typedef struct mat *MATRIZ;
El tipo MATRIZ ahora debe ser un puntero puesto que
tenemos que reservar memoria para la estructura que contiene la
matriz en s y las dimensiones. Una funcin que nos crease una
matriz sera algo as:
MATRIZ
{
MATRIZ

inic_matriz (int x,int y)


temp;

temp = (MATRIZ) malloc (sizeof(struct mat));


temp->ancho = x;
temp->alto = y;
temp->datos = (float *) malloc (sizeof(float)*x*y);
return temp;
}
En esta funcin se ha obviado el cdigo que comprueba si
la asignacin ha sido correcta. Veamos como se organizan los datos
con estas estructuras.

temp------->datos---------->x*x elementos
ancho,alto
Esta estructura puede parecer en principio demasiado
compleja, pero veremos en el siguiente captulo que es muy til en
el encapsulado de los datos.
En nuestro programa
declararamos algo as:
main()
{
MATRIZ

principal, para

utilizar la

matriz

a;

a = inic_matriz (3,3);
...
borrar_matriz(a);
}
Dnde borrar_matriz(a) libera la memoria reservada en
inic_matriz,
teniendo
en
cuenta
que
se
realizaron dos
asignaciones, una para la estructura mat y otra para la matriz en
s.
Otra definicin posible del problema podra ser as.
typedef struct mat MATRIZ;
void inic_matriz (MATRIZ *p,int x,int y)
{
p->ancho = x;
p->alto = y;
p->datos = (float *)malloc(sizeof(float)*x*y);
}
sto:

Con

este esquema

main()
{
MATRIZ

el programa

principal sera

algo como

a;

inic_matriz (&a,3,3);
.....
borrar_matriz (&a);
}
Con
este
esquema
el
acceso
a
la matriz sera
*(a.datos+x+y*a.ancho), idntico al anterior sustituyendo los
puntos por flechas ->. En el siguiente captulo se justificar la
utilizacin de la primera forma de implementacin frente a esta
segunda. En realidad se trata de la misma implementacin salvo que
en la primera el tipo MATRIZ es un puntero a una estructura, por
tanto es necesario primero reservar memoria para poder utilizar la
estructura, mientras que en la segunda implementacin, el tipo
MATRIZ es ya en si la estructura, con lo cual el compilador ya
reserva la memoria necesaria. En el primer ejemplo MATRIZ a;

define a como un puntero a una estructura struct mat, mientras que


en la segunda MATRIZ a; define a como una variable cuyo tipo es
struct mat.
5.3 Tipos de datos abstractos y encapsulado.
5.3.0 Introduccin
En el apartado anterior se mostraba un ejemplo de la
definicin de un nuevo tipo de datos. En general un nuevo tipo de
datos es simplemente una estructura que contiene informacin, la
cual es organizada de una forma determinada dependiendo de cual
ser su aplicacin.
Podemos adems de crear la estructura de datos asociar al
nuevo tipo una serie de funciones que nos permitan actuar sobre
esos datos, de forma que el usuario final que utilice nuestra
estructura de datos no necesite saber como est organizada
internamente. A grandes rasgos sto es lo que se entiende por
encapsulamiento de la informacin. El usuario dispone de un tipo
de datos abstracto, puesto que
no tiene acceso directo a esa
informacin y por ello se hace necesaria la utilizacin de
funciones que nos permitan procesar esa informacin.
Esta organizacin tiene numerosas ventajas. En primer
lugar la depuracin de programas se hace ms sencilla, puesto que
si se produce un error mientras trabajamos con nuestro tipo
abstracto, necesariamente ese error se produce dentro de las
funciones asociadas a l y no en otro lugar. Adems resulta
sencilla la modificacin de la implementacin de las funciones o
incluso de la estructura de datos asociada a nuestro tipo, sin
necesidad de modificar los programas que utilizan nuestro tipo
abstracto.
Estamos
aumentando
la
modularidad
de nuestra
programacin.
La definicin de tipos abstractos es la base de la
programacin orientada a objetos. Este tipo de programacin
denomina los tipos abstractos como clases, una clase es una
estructura de datos y una
serie de funciones asociadas a esa
estructura de datos. Adems en general disponen de otras opciones
tales como sobrecarga de operadores, herencia de clases, etc... En
la actualidad el estndar en programacin orientada a objetos es
el C++. Otros lenguajes como el C o el Mdula II no estn
orientados a objetos pero
disponen de facilidades para la
definicin de tipos abstractos de datos.
5.3.1 Cdigo fuente, cdigo objeto. Libreras y enlazadores
Los programas que hemos esta realizando hasta el momento
se conocen como cdigo fuente, son ficheros de texto cuyas lneas
contienen
instrucciones
de
un
determinado
lenguaje
de
programacin.
El ordenador
slo puede
ejecutar un nmero
determinado de instrucciones, en general muy sencillas, por ello
necesitamos utilizar un compilador para poder ejecutar nuestros
programas. El compilador traduce
nuestro cdigo fuente (un
programa en C, por ejemplo) a un cdigo objeto que la mquina
puede reconocer. En principio este cdigo tal y como es generado
por el compilador no puede ser ejecutado por el sistema operativo,
puesto que en nuestro cdigo fuente hacemos referencia a funciones

como printf o malloc que nosotros no hemos definido. Estas


funciones se encuentran ya compiladas en forma de cdigo objeto y
agrupadas en lo que se conoce como una librera.
As se hace necesaria la utilizacin de otro programa
conocido como enlazador o linker. El enlazador toma como entrada
una serie de mdulos objeto o libreras y enlaza todos los mdulos
utilizados por el programa en
un slo programa que pueda ser
ejecutado por el sistema operativo.
De todo esto se deduce que resulta muy interesante
disponer de libreras para distintas operaciones que pueden ser
utilizadas en cualquiera de nuestros programas. Adems a medida
que los programas se van haciendo cada vez ms grandes es
recomendable dividir el programa en varios ficheros que contengan,
cada uno de ellos, funciones que guarden cierta relacin o que
acten sobre un determinado tipo de datos, siendo sta la
finalidad que perseguimos en este captulo.
5.3.2 Los ficheros de proyectos de Borland
Los compiladores de lenguaje C de Borland ofrecen un
entorno integrado para el desarrollo de programas. Nos ofrece un
editor para escribir nuestro cdigo fuente, un compilador para
obtener el cdigo objeto y un enlazador para construir el programa
ejecutable final.
NOTA: esto es slo aplicable a compiladores C con entornos
integrados. Por consiguiente, no es aplicable al GCC, pero
conviene saber algo del tema para posteriores trabajos con otros
compiladores. Aqu se hace referencia al compilador Borland C.
Hasta ahora nuestros programas eran muy sencillos y
ocupaban un slo fichero, con lo que el enlazador slo tena que
vrselas con las libreras del sistema. En este captulo nuestros
programas van a estar formados por varios ficheros y por ello
tendremos que utilizar los ficheros de proyectos.
Estos ficheros simplemente contienen los nombres de los
ficheros que son necesarios para crear el programa final. Se
pueden crear utilizando la opcin Proyect del men principal del
compilador y seleccionando posteriormente la opcin Open. Una
ventana aparecer en pantalla y en ella tendremos que introducir
los nombres de los ficheros que compondrn nuestro programa.
En la lnea inferior de la pantalla siempre aparece una
ayuda con las teclas que se pueden utilizar para trabajar en la
ventana que se encuentre activa. La tecla INSERT nos permite
seleccionar los ficheros a incluir mediante un selector de
ficheros semejante al que aparece al cargar un programa.
5.3.4 Tipos abstractos de datos.
Ya hemos explicado el concepto de tipo abstracto de datos
en la introduccin de este captulo, y en esta seccin nos
centraremos en el modo de implementacin.
Fundamentalmente vamos a tener tres ficheros:

1.-El programa principal: Este programa utilizar el tipo


de
datos
abstractos
que
implementaremos
como un mdulo
independiente. Adems
debe incluir un fichero
.H con las
definiciones de los tipos abstractos y de las funciones de gestin
de los datos.
2.-El fichero de cabecera conteniendo la definicin del
tipo abstracto y de los prototipos de las funciones.
3.-La estructura
real del tipo de
permanecer oculta al programa principal, y
funciones definidas para trabajar con el tipo.

datos, la cual
el cdigo de las

Nuestro tipo abstracto no es tal. En el fichero 3, el que


implementa las funciones y tipos de datos, nos encontramos con una
definicin de un tipo de dato como podra ser el utilizado en el
ejemplo de las matrices. Sin embargo en el programa principal no
tendremos acceso a los distintos campos de esa estructura de
datos, sta es la razn de que se llamen tipos abstractos. La
mejor forma para expresar un tipo abstracto es el tipo void. void
representa cualquier cosa, de esta manera para el programa
principal cualquier tipo abstracto ser un puntero a void. La
necesidad de que se trate con punteros, deriva del hecho de que
tenemos que mantener las estructuras de datos ocultas, lo que nos
obliga a asignar dinmicamente las variables del tipo abstracto,
hacindose necesario el uso de punteros.
Veremos un ejemplo de implementacin de un tipo abstracto
de datos y lo comentaremos. El tipo que implementaremos ser el
tipo vector y definiremos una serie de funciones para el trabajo
con vectores.
Comenzaremos con el fichero que implementa
de datos y las funciones asociadas a l.

el nuevo tipo

VECTOR.C
#include<stdio.h>
#include<stdlib.h>
#define VECTOR_C
struct vector
{
float
int
};

*componentes;
dimension;

typedef struct vector *VECTOR;


#include "vector.h"
VECTOR crear_vector(int dimension)
{
VECTOR temp;
temp=(VECTOR)malloc(sizeof( struct vector));
if (temp==0)
{
printf("\nno hay sitio en memoria");exit(1);
}
temp->dimension=dimension;

temp->componentes=(float *)calloc(dimension,sizeof(float));
if (temp->componentes==0)
{
printf("\nno hay sitio en memoria");exit(1);
}
return temp;
}
void borrar_vector(VECTOR vec)
{
free(vec->componentes);
free(vec);
}
float leer_componente(VECTOR vec,int componente)
{
float x;
x=*(vec->componentes+componente);
return x ;
}
void escribir_componente(VECTOR vec,int componente,float valor)
{
*(vec->componentes+componente)=valor;
}
void suma(VECTOR uno,VECTOR dos,VECTOR tres)
{
int i;
if (uno->dimension!=dos->dimension) printf("\nerror");
else
if (uno->dimension!=tres->dimension) printf("\nerror");
else
for(i=0;i<uno->dimension;i++)
*(tres->componentes+i)=*(dos->componentes+i)+
*(uno->componentes+i);
}
En primer lugar se indican las librera que necesita
utilizar el fichero en s. En este caso la stdlib para las
asignaciones y liberaciones de memoria, y la stdio para sacar
mensajes de error por pantalla.
A continuacin definimos un tipo de datos para trabajar
con vectores, se trata de un vector y un entero que nos indica la
longitud, y finalmente el tipo de dato abstracto VECTOR como un
puntero a esta estructura. Como vemos dentro del fichero VECTOR.C
el dato VECTOR est perfectamente definido y la ocultacin de ste
se realiza en el fichero VECTOR.H.
En el fichero VECTOR.C podemos dividir las funciones en
varios tipos. En primer lugar estn las funciones crear_vector y
borrar_vector, las cuales se encargan de reservar y liberar la
memoria necesaria para cada elemento. Por otra parte tenemos las
funciones leer_componente y escribir_componente que nos permiten
acceder a la estructura de datos, dependiendo del tipo de datos
puede que no sea necesario que el programador acceda a los datos
en la estructura. Finalmente tenemos las funciones que realizan
operaciones con los vectores, sumar_vectores.

Vemos que antes de que comience el cdigo de las funciones


tenemos un include de el fichero VECTOR.H, esta lnea lo que hace
es incorporar los prototipos de las funciones para poder llevar a
cabo la compilacin, ya se explic la funcin de los prototipos de
funciones. Adems al principio de el fichero se ha definido un
constante VECTOR_C, en el fichero .H veremos su utilidad.
VECTOR.H
#ifndef VECTOR_H
#define VECTOR_H
#ifndef VECTOR_C
typedef void *VECTOR;
#endif
VECTOR crear_vector(int);
void borrar_vector(VECTOR);
float leer_componente(VECTOR,int);
void escribir_componente(VECTOR,int,float);
void suma(VECTOR,VECTOR,VECTOR);
#endif
En primer lugar se comprueba si se ha definido la
constante VECTOR_H, para evitar incluir el fichero ms de una vez.
Si est definida se ignora el fichero, si no est definida la
define y comprueba si el fichero que llama a VECTOR.H es VECTOR.C
mediante la constante VECTOR_C que se defina al comienzo del
fichero VECTOR.C. Si no est definida se declara el tipo abstracto
VECTOR como un puntero a void, eliminando de esta forma cualquier
informacin sobre la forma de el tipo asociado a VECTOR en
VECTOR.C. Si est definida no se realiza la definicin para evitar
una redeclaracin de VECTOR dentro de VECTOR.C. Finalmente se
encuentran los prototipos de las funciones asociadas al tipo
vector.
Hasta el
momento slo conocamos
la directiva del
preprocesador #include. En este ejemplo se incluye dos ms, cuyo
significado es evidente. La directiva #define permite definir una
constante en general realiza una macro sustitucin, vemoslo con
unos ejemplos:
#define PI

3.14159

Esta sentencia simplemente


define una constante, en
cualquier parte de el programa en la que aparezca PI, este smbolo
se sustituir por 3.14159.
#define COMP

vector->datos

Con esta definicin podramos escribir instrucciones como:


*(COMP+1) = 10;
Las directivas #ifndef y #endif van siempre asociadas
sealando
bloques de
programa y
permiten la
compilacin
condicional de bloques de programa. Significan algo como:
Si no est definida tal cosa compila (#ifndef)

esto
Hasta aqu (#endif)
Finalmente
veamos
como
utilizaramos
abstracto en un programa, para sumar dos vectores.

nuestro

tipo

#include"stdio.h"
#include"vector.h"
main()
{
VECTOR x,y,z;
int i;
x=crear_vector(3);
y=crear_vector(3);
z=crear_vector(3);
printf ("\n");
for(i=0;i<3;i++)
escribir_componente(x,i,i);
for(i=0;i<3;i++)
escribir_componente(y,i,i+2);
suma(x,y,z);
for(i=0;i<3;i++)
{
printf("\n%f + %f = %f",leer_componente(x,i),
leer_componente(y,i),leer_componente(z,i));
}
borrar_vector (x);
borrar_vector (y);
borrar_vector (z);
}
TEMA 6 : Entrada y salida (E/S) de datos
6.0 Introduccin
El objetivo de este tema es hacer un estudio completo en
todo lo referente a la entrada y, salida (E/S) en C, estudiando
tambin los dos sistemas de ficheros que existen en este lenguaje.
6.1 Entrada y salida (E/S)
Las operaciones de entrada y salida (abreviadamente E/S) no
forman parte del lenguaje C propiamente dicho, sino que estn en una
biblioteca o
librera: <stdio.h>. Todo
programa que utilice
funciones de entrada y salida estndar deber contener la lnea:
#include <stdio.h>
6.1.1 E/S estndar
Por defecto, la entrada estndar es el teclado y la salida
estndar es la pantalla o monitor. Hay dos formas bsicas de cambiar
la entrada y la salida estndar:
1. Con los smbolos de redireccin (<, >, <<, >>) o de tubera (|)
del sistema operativo al ejecutar el programa desde la lnea de
rdenes.

2. Con determinadas funciones y variables que se encuentran en la


librera <stdio.h> en el cdigo fuente del programa.
6.2 Flujos y ficheros
Hay dos conceptos muy importantes en C relacionados con la
E/S: flujos (streams, en ingls) y ficheros. Los flujos son
sucesiones de caracteres a travs de los cuales realizamos las
operaciones de E/S. Para el programador todos los flujos son
iguales. Para el C (en general para el sistema operativo) un fichero
es un concepto lgico que puede ser aplicado a cualquier cosa desde
ficheros de discos a terminales. A cada fichero se le asigna un
flujo al realizar la operacin de apertura sobre l. Para el
programador un fichero es un dispositivo externo capaz de una E/S.
Todos los ficheros no son iguales pero todos los flujos s. Esto
supone una gran simplificacin para el usuario, ya que slo tiene
que pensar en trminos de flujo y no de dispositivos concretos. Por
ejemplo, si el usuario hace: printf ("mensaje"); sabe que mensaje
se escribe en el flujo estndar de salida, ya sea la pantalla, un
fichero de disco, una cinta, ...
6.3 Tipos de flujos: flujos de texto y flujos binarios
Cuando hemos dicho que todos los flujos son iguales, es
cierto que lo son en su utilizacin por parte del programador, pero
en realidad, podemos distinguir dos tipos:
- Flujos de texto: son una sucesin de caracteres originado en
lneas que finalizan con un carcter de nueva-lnea. En estos
flujos puede no haber una relacin de uno a uno entre los
caracteres que son escritos (ledos) y los del dispositivo
externo, por ejemplo, una nueva-lnea puede transformarse en un
par de caracteres (un retorno de carro y un carcter de salto de
lnea).
- Flujos
binarios: son
flujos
de
bytes que
tienen una
correspondencia uno a uno con los que estn almacenados en el
dispositivo externo. Esto es, no se presentan desplazamientos de
caracteres. Adems el nmero de bytes escritos (ledos) es el
mismo que los almacenados en el dispositivo externo.
Esta diferencia de flujos es importante tenerla en cuenta al
leer ficheros de discos. Supongamos que tenemos un fichero de disco
con 7 caracteres donde el cuarto carcter es el carcter fin de
fichero (en sistema operativo DOS es el carcter con cdigo ASCII
26). Si abrimos el fichero en modo texto, slo podemos leer los 3
primeros caracteres, sin embargo, si lo abrimos en modo binario,
leeremos los 7 caracteres ya que el carcter con cdigo ASCII 26 es
un carcter como cualquier otro.
6.4 Programas C con flujos
Al principio de la ejecucin de
flujos de tipo texto predefinidos:
stdin : dispositivo de entrada estndar
stdout: dispositivo de salida estndar

un programa C se abren tres

stderr: dispositivo de salidad de error estndar


Al finalizar el programa, bien volviendo de la funcin main
al sistema operativo o bien por una llamada a exit(), todos los
ficheros se cierran automticamente. No se cerrarn si el programa
termina a travs de una llamada a abort() o abortando el programa.
Estos
tres
explictamente.

ficheros

no

pueden

abrirse

ni

cerrarse

6.5 Resumen de lo anterior


Como todo lo que acabamos de decir puede resultar un poco
confuso a las personas que tienen poca experiencia en C, vamos a
hacer un pequeo resumen en trminos generales:
1. En C, cualquier cosa externa de la que podemos
podemos escribir datos es un fichero.

leer o en la que

2. El programador escribe (lee) datos en estos ficheros a travs de


los flujos de cada fichero. De esta forma el programador escribe
(lee) los datos de la misma forma en todos los tipos de ficheros
independientemente del tipo de fichero que sea.
3. Aunque conceptualmente todos los flujos son iguales, en realidad
hay dos tipos: flujos de texto y flujos binarios.
4. Hay
tres
flujos
de
texto
predefinidos
que
se abren
automticamente al principio del programa: stdin, stdout y
stderr. Estos tres flujos se cierran automticamente al final del
programa.
6.6 Pasos para operar con un fichero
Los pasos a realizar
fichero son los siguientes:

para

realizar

operaciones

con un

1) Crear un nombre interno de fichero. Esto se hace en C declarando


un puntero de fichero (o puntero a fichero). Un puntero de fichero
es una variable puntero que apunta a una estructura llamada FILE.
Esta estructura est definida en el fichero stdio.h y contiene toda
la informacin necesaria para poder trabajar con un fichero. El
contenido de esta estructura es dependiente de la implementacin de
C y del sistema, y no es interesante saberlo para el programador.
Ejemplo:
FILE *pf; /* pf es un puntero de fichero */
2) Abrir el fichero. Esto se hace con la funcin fopen()
prototipo se encuentra en el fichero stdio.h y es:

cuyo

FILE *fopen (char *nombre_fichero, char *modo);


Si el fichero con nombre
devuelve NULL.

nombre_fichero no se

puede abrir

El parmetro nombre_fichero puede contener la ruta completa


de fichero pero teniendo en cuenta que la barra invertida (\) hay
que repetirla en una cadena de caracteres.

Los valores vlidos para el parmeto modo son:


+-------+-------------------------------------------------+
| Modo | Interpretacin
|
+-------+-------------------------------------------------+
| "r"
| Abrir un fichero texto para lectura
|
| "w"
| Crear un fichero texto para escritura
|
| "a"
| Aadir a un fichero texto
|
| "rb" | Abrir un fichero binario para lectura
|
| "wb" | Crear un fichero binario para escritura
|
| "ab" | Aadir a un fichero binario
|
| "r+" | Abrir un fichero texto para lectura/escritura
|
| "w+" | Crear un fichero texto para lectura/escritura
|
| "a+" | Abrir un fichero texto para lectura/escritura
|
| "rb+" | Abrir un fichero binario para lectura/escritura |
| "wb+" | Crear un fichero binario para lectura/escritura |
| "ab+" | Abrir un fichero binario para lectura/escritura |
+-------+-------------------------------------------------+
Si se utiliza fopen() para abrir un fichero para escritura,
entonces cualquier fichero que exista con ese nombre es borrado y se
comienza con un fichero nuevo. Si no existe un fichero con ese
nombre, entonces se crea uno. Si lo que se quiere es aadir al final
del fichero, se debe utilizar el modo "a". Si no existe el fichero,
devuelve error. Abrir un fichero para operaciones de lectura
necesita que el fichero exista. Si no existe devuelve error.
Finalmente,
si
se
abre
un
fichero
para
operaciones de
lectura/escritura, no se borra en caso de existir. Sin embargo, si
no existe se crea.
Ejemplo:
FILE *pf;
pf = fopen ("c:\\autoexec.bat", "r");
if (pf == NULL) /* siempre se debe hacer esta comprobacin*/
{
puts ("No se puede abrir fichero.");
exit (1);
}
3) Realizar las operaciones deseadas con el fichero como pueden ser
la escritura en l y la lectura de l. Las funciones que disponemos
para hacer esto las veremos un poco ms adelante.
4) Cerrar el fichero. Aunque el C cierra automticamente todos los
ficheros abiertos al terminar el programa, es muy aconsejable
cerrarlos explcitamente. Esto se hace con la funcin fclose() cuyo
prototipo es:
int fclose (FILE *pf);
La funcin fclose() cierra el
pf y vuelca su buffer.

fichero asociado con el flujo

Si fclose() se ejecuta correctamente devuelve el valor 0. La


comproba cin del valor devuelto no se hace muchas veces porque no
suele fallar.
Ejemplo:

FILE *pf;
if ((pf = fopen ("prueba", "rb")) == NULL)
{
puts ("Error al intentar abrir el fichero.");
exit (1);
}
/* ... */
if (fclose (pf) != 0)
{
puts ("Error al intentar cerrar el fichero.");
exit (1);
}
Resumen de los 4 pasos para la manipulacin de un fichero:
---------------------------------------------------------1) Declarar un puntero de fichero.
FILE *pf;
2) Abrirlo el fichero.
if ((pf = fopen ("nombre_fichero", "modo_apertura")) == NULL)
error ();
else
/* ... */
3) Realizar las operaciones deseadas con el fichero.
/* En las siguientes ventanas veremos las
funciones que tenemos para ello. */
4) Cerrar el fichero.
if (fclose (pf) != 0)
error ();
else
/* ... */
6.7 Sistema de ficheros tipo UNIX
Debido a que el lenguaje C se desarroll inicialmente bajo
el sistema operativo UNIX, se cre un segundo sistema de ficheros. A
este sistema se le llama E/S sin buffer, E/S de bajo nivel o E/S
tipo UNIX. Hoy en da, este sistema de ficheros est totalmente en
desuso y se considera obsoleto, adems el nuevo estndar ANSI ha
decidido no estandarizar el sistema de E/S sin buffer tipo UNIX. Por
todo lo dicho no se puede recomendar este sistema a los nuevos
programadores C. Sin embargo, todava existen programas que lo usan
y es soportado por la mayora de los compiladores de C. As que
incluimos una breve explicacin al respecto.
6.7.1 Descriptores de ficheros
A diferencia del sistema de E/S de alto nivel, el sistema de
bajo nivel no utiliza punteros a ficheros de tipo FILE; el sistema
de bajo nivel utiliza descriptores de fichero de tipo int. A cada
fichero se le asocia un nmero (su descriptor de fichero).
Hay tres descriptores de fichero predefinidos:
0
1
2

entrada estndar
salida estndar
salida de errores estndar

Los cuatro pasos para la manipulacin de ficheros con E/S de


alto nivel que vimos antes se transforman en la E/S de bajo nivel en
los siguientes pasos:
1) Declarar un descriptor de fichero para el fichero a abrir.
int fd;
2) Abrir el fichero.
if ((fd = open (nombre_fichero, modo_apertura)) = -1)
{
printf ("Error al intentar abrir el fichero.\n");
exit (1);
}
3) Manipular el
para ello.

fichero con

4) Cerrar el fichero.
close (fd);

las funciones

que tenemos disponible

EJEMPLOS
/*
+----------------------------------------+
| BUSQUEDA BINARIA (BUSQUEDA DICOTOMICA) |
+----------------------------------------+
En este programa se realiza una bsqueda binaria (llamada
tambin dicotmica) en un vector ordenado de nmeros enteros.
Este algoritmo es
vlido exclusivamente para vectores
ordenados y consiste en comparar en primer lugar con la componente
central del vector, y si no es igual al valor buscado se reduce el
intervalo de bsqueda a la mitad derecha o izquierda segn donde
pueda encontrarse el valor a buscar. El algoritmo termina si se
encuentra el valor buscado o si el tamao del intervalo de bsqueda
queda anulado.
En los casos en que existan repeticiones en el vector, del
valor buscado, este algoritmo obtendr uno de ellos aleatoriamente
segn los lugares que
ocupen, los cuales necesariamente son
consecutivos.
*/
#include <stdio.h>

/* printf (), getchar () */

void main (void)


{
int vector [100];

/* declaracin de un vector de 100


elementos int */
int x,
/* contiene valor a buscar */
encontrado,
/* variable que slo toma dos valores:
cierto (1) o falso (0) */
i, centro, izquierda, derecha; /* ndices de vector */
/* Escritura de la cabecera. */
printf ("BUSQUEDA BINARIA EN UN VECTOR ORDENADO:");
/* Relleno aleatorio de los 100 componentes del vector. A cada
componente se le asigna un valor equivalente al doble del ndice
que ocupan en
el vector. A la variable
x se le asigna
arbitrariamente la mitad de lo que vale la variable i al salir del
bucle. */
for (i = 0; i <= 99; i++)
vector[i] = i * 2;
x = i / 2;
/* Escritura en pantalla del vector ordenado y valor a buscar. */
printf ("\n\nVector:\n");
for (i = 0; i < 100; i++)
printf ("%d\t", vector[i]);
printf ("\nValor a buscar: %d", x);
/* Bsqueda binaria en vector ordenado. */
encontrado = 0;
izquierda = 0;
derecha = 99;
while (! encontrado && izquierda <= derecha)
{

centro = (izquierda + derecha) / 2;


if (x < vector[centro])
derecha = centro - 1;
else if (x > vector[centro])
izquierda = centro + 1;
else
encontrado = 1;
}

/* Escritura en pantalla del vector ordenado. */


if (encontrado)
printf ("\n\nValor %d encontrado en ndice %d (recordar que"
" empieza en 0).", x, centro);
else
printf ("\n\nNo existe el valor %d en el vector.", x);
/* Espera la pulsacin de la tecla RETURN para finalizar el
programa. */
getch ();

/*
+----------------------------+
| PERMUTACIONES DE UN VECTOR |
+----------------------------+
Este programa lee los elementos de un vector y escribe
todas las permutaciones (combinaciones sin repeticin) posibles de
los elementos introducidos en el vector.
El nmero de permutaciones
por el factorial de n.

de n elementos est determinado

Observaciones sobre el programa:


1) En el primer elemento del vector (ndice 0) se encuentra el
nmero de elementos a permutar. As pues, los n elementos a permutar
estn en entre los ndices 1 y n el vector.
2) En algunos sitios del programa aparece la expresin:
&vector[i] que puede parecer a simple un poco rara para el que no
est acostumbrado al C. Expliqumosla: Al ser cada elmento del
vector de tipo int, vector[i] se puede considerar como una variable
de tipo int, y por lo tanto, podemos hacer con la expresin
vector[i] lo que con cualquier variable de tipo entero, y no de
estas operaciones es obtener su direccin. Hemos dicho varias veces
que el nombre del vector es un puntero al primer elemento, as la
siguiente expresin sera cierta: vector == &vector[0], es decir,
que vector y &vector[0] es equivalente.
3) Los nombres de las variables punteros, por convencin,
suelen empezar con p; por este motivo he llamado a los parmetros de
la funcin intercambiar p1 y p2.
*/
/* Ficheros a incluir: */

#include <stdio.h> /* printf (), puts (), putchar (), scanf () */


#include <conio.h> /* getch ()
*/
#include <stdlib.h> /* exit ()
*/
/* Macros: */
#define NUMERO_MAXIMO_ELEMENTOS_VECTOR 100
#define en(x,x1,x2) ((x) >= (x1) && (x) <= (x2))
/* Declaracin de las funciones: */
void
void
void
void

rellenar_vector (int vect[]);


escribir_vector (int vect[]);
permutar (int v[], int m);
intercambiar (int *p1, int *p2);

/* Definicin de las funciones: */


void main (void)
{
int vector [NUMERO_MAXIMO_ELEMENTOS_VECTOR+1];
/* +1 porque el primer elemento contiene el nmero de elementos a
permutar */
puts ("PERMUTACIONES");
rellenar_vector (vector);
permutar (vector, vector[0]);

printf ("\nPulsa cualquier tecla para finalizar.");


getch ();

void rellenar_vector (int vect[])


{
register int i;
do
{
printf ("\nIntroduzca nmero de elementos de vector (1-%d): ",
NUMERO_MAXIMO_ELEMENTOS_VECTOR);
scanf ("%d", &vect[0]);
} while (! en (vect[0], 1, NUMERO_MAXIMO_ELEMENTOS_VECTOR));
printf ("\nIntroduce los %d elementos a permutar: ", vect[0]);
for (i = 1; i <= vect[0]; i++)
scanf ("%d", &vect[i]);
putchar ('\n');
}
void escribir_vector (int vect[])
{
static unsigned int numero_de_permutacion = 0;
register int i;
/* cada 24 movimientos se para la escritura */
if (++numero_de_permutacion % 24 == 0)
{
printf ("Pulse la tecla ESCAPE para salir o cualquier otra "
"tecla para continuar.");
/* el cdigo ASCII 27 corresponde a la tecla ESCAPE */

if (getch () == 27)
exit (0);
/* con estas tres funciones se borra el mensaje anterior */
putchar ('\r');
printf ("
"
");
putchar ('\r');

"

}
printf ("Permutacin %2u: ", numero_de_permutacion);
for (i = 1; i <= vect[0]; i++)
printf ("%d ", vect[i]);
putchar ('\n');
}
void permutar (int v[], int m)
{
register int i;
if (m > 1)
for (i = 1; i <= m; i++)
{
intercambiar (&v[i], &v[m]);
permutar (v, m-1);
intercambiar (&v[i], &v[m]);
}
else
escribir_vector (v);
}
void intercambiar (int *p1, int *p2)
{
int aux;
aux = *p1;
*p1 = *p2;
*p2 = aux;
}

/*
+---------------------------+
| ORDENACION RAPIDA (QSORT) |
+---------------------------+
En este programa se realiza
vector de nmeros enteros.
El

mtodo de

la ordenacin creciente

ordenacin seguido

ha sido

de un

el de ordenacin

rpida.
La ordenacin rpida, inventada y nombrada por C.A.R. Hoare,
est considerada como el mejor algoritmo de ordenacin disponible
actualmente. Est basada en la
ordenacin por el mtodo de
intercambio.
La ordenacin rpida se basa en la idea de las particiones.
El procedimiento general es seleccionar un valor llamado comparando
y entonces dividir el array en dos partes. En un lado todos los
elementos mayores o iguales al valor de particin y en otro todos

los elementos menores que el valor. Este proceso


parte restante hasta que el array est ordenado.

se repite en cada

Como se puede ver, este proceso es esencialmente recursivo


por naturaleza y, de hecho, las implementaciones ms claras de la
ordenacin rpida es por algoritmos recursivos.
La seleccin del valor comparado se puede obtener de dos
formas. Se puede seleccionar aleatoriamente o haciendo la media de
un pequeo conjunto de valores
tomados del array. Para una
ordenacin
ptima es
mejor seleccionar
un valor
que est
precisamente en medio del rango de valores. Sin embargo, esto no es
fcil de hacer en la mayora de los conjuntos de datos. En el caso
peor, el valor escogido est en un extremo. Incluso en este, la
ordenacin rpida todava funciona bien. La versin de la ordenacin
rpida que sigue selecciona el elemento mitad del array. Aunque no
siempre ser una buena eleccin, la ordenacin sigue funcionanado
correctamente.
Ejemplo:

*/

Secuencia inicial
8
2
5
3
9
Elemento comparando: 5
Primer paso
3
2
5
8
9
Ahora se ordena con el mismo procedimiento los vectores '3 2' y
'8 9'

#include <stdio.h> /* printf () */


#include <stdlib.h> /* rand ()
*/
#include <conio.h> /* getch () */
#define NUM_ELEMENTOS_VECTOR 100
void ordenar (int vect[], int ind_izq, int ind_der);
void main (void)
{
int vector [NUM_ELEMENTOS_VECTOR]; /* declaracin de un vector de
elementos int */
register int i; /* declaracin de una variable registro de tipo
entera */
/* Escritura de la cabecera. */
printf ("ORDENACION DE UN VECTOR DESORDENADO POR EL METODO DE "
"ORDENACION RAPIDA:");
/* Relleno aleatorio de los 100 componentes del vector. A cada
componente se le asigna un valor aleatorio entre 0 y 999. Las
componentes del vector van desde la 0 hasta la
NUM_ELEMENTOS_VECTOR-1 inclusive. */
for (i = 0; i < NUM_ELEMENTOS_VECTOR; i++)
vector[i] = rand () % 1000;
/* Escritura en pantalla del vector desordenado. */
printf ("\n
Vector desordenado:\n");
for (i = 0; i < NUM_ELEMENTOS_VECTOR; i++)
printf ("%d\t", vector[i]);

/* Ordenacin del vector por el mtodo de insercin directa. */


ordenar (vector, 0, NUM_ELEMENTOS_VECTOR-1);
/* Escritura en pantalla del vector ordenado. */
printf ("\n Vector ordenado:\n");
for (i = 0; i < NUM_ELEMENTOS_VECTOR; i++)
printf ("%d\t", vector[i]);

/* Espera la pulsacin de cualquier tecla para finalizar el


programa. */
getch ();

void ordenar (int vect[], int ind_izq, int ind_der)


{ /* vect: vector, ind_izq: ndice izquierdo, ind_der: ndice
derecho */
register int i, j; /* variables ndice del vector */
int elem; /* contiene un elemento del vector */
i = ind_izq;
j = ind_der;
elem = vect[(ind_izq+ind_der)/2];
do
{

while (vect[i] < elem && j < ind_der) /* recorrido del vector
hacia la derecha */
i++;
while (elem < vect[j] && j > ind_izq) /* recorrido del vector
hacia la izquierda */
j--;
if (i <= j) /* intercambiar */
{
int aux; /* variable auxiliar */
aux = vect[i];
vect[i] = vect[j];
vect[j] = aux;
i++;
j--;
}
} while (i <= j);
if (ind_izq < j)
ordenar (vect, ind_izq, j);
if (i < ind_der)
ordenar (vect, i, ind_der);

/*
+-----------------+
| LAS OCHO REINAS |
+-----------------+
El problema de las ocho reinas consiste en situar ocho
reinas en un tablero de ajedrez, de forma que ninguna reina pueda
actuar sobre cualquiera de las otras.

El pseudocdigo de la funcin ensayar() es el siguiente:


<PRINCIPIO ensayar> (i: entero)
inicializar el conjunto de posiciones de la reina i-sima
+-REPETIR hacer la seleccin siguiente
| +-SI segura ENTONCES
| | poner reina
| | +-SI i < 8 ENTONCES
| | | LLAMAR ensayar (i + 1)
| | | +-SI no acertado ENTONCES
| | | |
quitar reina
| | | +-FINSI
| | +-FINSI
| +-FINSI
+-HASTA acertada O no hay ms posiciones
<FIN>
Observaciones sobre el cdigo:
1) Estudiar la funcin ensayar() a partir de este pseudocdigo.
2) Vectores utilizados:
int posiciones_en_columna[8];
BOOLEAN reina_en_fila[8];
BOOLEAN reina_en_diagonal_normal[15];
BOOLEAN reina_en_diagonal_inversa[15];

RANGO:
RANGO:
RANGO:
RANGO:

1..8
1..8
-7..7
2..16

En C, el primer elemento de cada vector tiene ndice 0, esto


es fcil solucionarlo con las siguientes macros:
#define
#define
#define
#define

c(i) posiciones_en_columna[(i)-1]
f(i) reina_en_fila[(i)-1]
dn(i) reina_en_diagonal_normal[(i)+7]
di(i) reina_en_diagonal_inversa[(i)-2]

Significado de los vectores:


c(i) : la posicin de la reina en la columna i
f(j) : indicativo de que no hay reina en la fila j-sima
dn(k): indicativo de que no hay reina en la diagonal normal
(\) k-sima
di(k): indicativo de que no hay reina en la diagonal
invertida (/) k-sima
Dado que se sabe, por las reglas del ajedrez, que una reina
acta sobre todas las piezas situadas en la misma columna, fila o
diagonal del tablero se deduce que cada columna puede contener una y
slo una reina, y que la eleccin de la situacin de la reina
i-sima puede restringirse a los cuadros de la columna i. Por tanto,
el parmetro i se convierte en el ndice de columna, y por ello el
proceso de seleccin de posiciones queda limitado a los ocho
posibles valores del ndice de fila j.
A partir
pseudocdigo es:

de

estos

datos,

la

lnea

poner

reina

c (i) = j; f (j) = di (i + j) = dn (i - j) = FALSE;


y la lnea quitar reina del pseudocdigo:

del

f (j) = di (i + j) = dn (i - j) = TRUE;
y la condicin segura del pseudocdigo:
f (i) && di (i + j) && dn (i - j)
*/
/* Ficheros a incluir: */
#include <stdio.h>
#include <conio.h>

/* printf () */
/* getch () */

/* Macros: */
#define BOOLEAN int
#define TRUE
1
#define FALSE
0
/* Variables globales: */
BOOLEAN acertado;
int posiciones_en_columna[8];
BOOLEAN reina_en_fila[8];
BOOLEAN reina_en_diagonal_normal[15];
BOOLEAN reina_en_diagonal_inversa[15];
#define c(i) posiciones_en_columna[(i)-1]
/* rango de ndice: 1..8 */
#define f(i) reina_en_fila[(i)-1]
/* rango de ndice: 1..8 */
#define dn(i) reina_en_diagonal_normal[(i)+7]
/* rango de ndice: -7..7 */
#define di(i) reina_en_diagonal_inversa[(i)-2]
/* rango de ndice: 2..16 */
/* Prototipos de las funciones: */
void proceso (void);
void ensayar (int i);
/* Definiciones de las funciones: */
void main (void)
{
printf ("\n\nPROBLEMA DE LAS OCHO REINAS:\n ");
proceso ();
printf ("\n\nPulsa cualquier tecla para finalizar. ");
getch ();
}
void proceso (void)
{
register int i,j;
for (i = 1; i <= 8; i++)
f (i) = TRUE;
for (i = 2; i <= 16; i++)
di (i) = TRUE;
for (i = -7; i <= 7; i++)
dn (i) = TRUE;

ensayar (1);

if (acertado)
for (printf ("\n\nLA SOLUCION ES:\n\n"), i = 1; i <= 8; i++)
{
for (j = 1; j <= 8; j++)
printf ("%2d", c (j) == i ? 1 : 0);
printf ("\n");
}
else
printf ("\n\nNO HAY SOLUCION.\n");

void ensayar (int i)


{
int j = 0;
do
{

j++;
acertado = FALSE;
if (f (j) && di (i + j) && dn (i - j))
{
c (i) = j;
f (j) = di (i + j) = dn (i - j) = FALSE;
if (i < 8)
{
ensayar (i + 1);
if (! acertado)
f (j) = di (i + j) = dn (i - j) = TRUE;
}
else
acertado = TRUE;
}
} while (! acertado && j != 8);

/*
+-------------------+
| SALTO DEL CABALLO |
+-------------------+
Este programa realiza lo siguiente: Se da un tablero de nxn
con n*n cuadros. Un caballo -que puede moverse segn las reglas del
ajedrez- se sita en el
cuadro de coordenadas (x0,y0). Se pide
encontrar, si existe, un recubrimiento del tablero completo, o sea,
calcular un circuito de n*n-1 movimientos de forma que cada cuadro
del tablero sea visitado exactamente una vez.
La solucin a este problema
tanteo sistemtico (intento y error).
La funcin ms importante
el siguiente:

est basado

en el

mtodo de

es ensayar() cuyo pseudocdigo es

<PRINCIPIO ensayar>
REPETIR seleccionar el nuevo candidato de la lista de futuros
movimientos
SI aceptable ENTONCES
SI tablero no lleno ENTONCES
LLAMAR ensayar nuevo movimiento
SI no acertado ENTONCES
borrar la anotacin anterior
FINSI
FINSI
FINSI
HASTA movimiento acertado O no hay ms posibilidades
<FIN>
Observaciones sobre el cdigo:
1) Estudiar la funcin ensayar() a partir de este pseudocdigo.
2) El mtodo utilizado para obtener el movimiento del caballo de
(x1,y1) hasta
(x2,y2) es sumar
a (x1,y1) los
vectores de
diferencias.
Los vectores de diferencia dif_x y dif_y contienen la
diferencia de la coordenada x e y respectivamente desde la posicin
actual del caballo.
Vese con el siguiente tablero:
0

C representa la posicin del caballo; los nmeros del 1 al 8


respresentan los 8 posibles movimientos. El primer movimiento se
obtiene: x2 = x1 + 2; y2 = y1 + 1;
3) La macro tab() se utiliza para trabajar con los ndices de 1
a n en la matriz del tablero en vez de con los ndices reales 0 a
n-1.
4) La condicin tablero no lleno se expresa mediante i < n*n
donde i es el nmero de movimiento del caballo actual y n la
dimensin del tablero.
5) El significado
matriz es:

de las

asignaciones a

los elementos

de la

tab (x, y) = 0; /* el cuadro <x,y> no ha sido visitado */


tab (x, y) = i; /* el cuador <x,y> ha sido visitado en el
movimiento i-simo (1 i n*n) */
NOTA: Con un dimensin de la matriz superior a 4, el proceso de
encontrar la solucin es muy lento. Por eso se ha puesto el lmite
en 8 aunque ya con este nmero el proceso es superlento (en trminos

de media, ya que puede dar la casualidad de


solucin en los primeros intentos).
*/

que se encuentre la

/* Ficheros a incluir: */
#include <stdio.h> /* printf () */
#include <conio.h> /* getch () */
#include <stdlib.h> /* exit () */
/* Macros: */
#define NUM_MOVIMIENTOS_POSIBLES 8
#define NMAXIMO 8
#define BOOLEAN int
#define TRUE
1
#define FALSE
0
#define ESC 27
#define en(x,x1,x2) ((x) >= (x1) && (x) <= (x2))
#define tab(i,j) tablero[(i)-1][(j)-1]

/* tab(1,1) es en realidad
tablero[0][0] */

/* Variables globales: */
int n, tablero[NMAXIMO][NMAXIMO];
BOOLEAN movimiento_acertado;
int dif_x [NUM_MOVIMIENTOS_POSIBLES] =
{ 2, 1, -1, -2, -2, -1, 1, 2 },
dif_y [NUM_MOVIMIENTOS_POSIBLES] =
{ 1, 2, 2, 1, -1, -2, -2, -1 };
/* Prototipos de las funciones: */
void proceso (void);
void ensayar (int i, int x, int y);
/* Definiciones de las funciones: */
void main (void)
{
do
{
printf ("\n\nVUELTA DEL CABALLO:\n ");
proceso ();
printf ("\nPulsa cualquier tecla para repetir "
"o ESC para salir. ");
} while (getch () != ESC);
}
void proceso (void)
{
register int i, j;
int x0, y0;
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
tab (i, j) = 0;

printf ("\nIntroduce dimensin del tablero (1 n %d, n > 4 es "


"muy lento): ", NMAXIMO);
do
{
n = getch () - '0';
} while (! en (n, 1, NMAXIMO));
putch (n + '0');
printf ("\nFila inicial (1 x %d): ", n);
do
{
x0 = getch () - '0';
} while (! en (x0, 1, n));
putch (x0 + '0');
printf ("\nColumna inicial (1 y %d): ", n);
do
{
y0 = getch () - '0';
} while (! en (y0, 1, n));
putch (y0 + '0');
tab (x0, y0) = 1;
printf ("\n\n");
ensayar (2, x0, y0);

if (movimiento_acertado)
for (printf ("\n\nLA SOLUCION ES:\n
{
for (j = 1; j <= n; j++)
printf ("%2d ", tab (i, j));
printf ("\n ");
}
else
printf ("\n\nNO HAY SOLUCION.\n");

"), i = 1; i <= n; i++)

void ensayar (int i, int x1, int y1)


{
int movimientos_realizados = 0;
int x2, y2;
const ncuadrado = n * n;
static long unsigned num_movimientos_caballo = 0;
do

{
movimiento_acertado = FALSE;
x2 = x1 + dif_x[movimientos_realizados];
y2 = y1 + dif_y[movimientos_realizados];
movimientos_realizados++;
if (kbhit ())
if (getch () == ESC)
exit (1);
printf ("Nmero de movimientos del caballo (ESC para salir): "
"%ld\r", ++num_movimientos_caballo);
if (en (x2, 1, n) && en (y2, 1, n) && tab (x2, y2) == 0)
{
tab (x2, y2) = i;
if (i < ncuadrado)
{

ensayar (i+1, x2, y2);


if (! movimiento_acertado)
tab (x2, y2) = 0;
}
else
movimiento_acertado = TRUE;

}
} while (! movimiento_acertado &&
movimientos_realizados != NUM_MOVIMIENTOS_POSIBLES);
}

/*
+-----------------------------------------------+
| COPIA DE UN FICHERO (SIMILAR AL COPY DEL DOS) |
+-----------------------------------------------+
Este
programa
copia
un
fichero
a
otro
fichero.
Funcionalmente este programa es similar al copy del DOS o al cat del
UNIX. Si el fichero de salida es con, se est haciendo un type del
DOS. Si el fichero de entrada es con, recuerda que el carcter fin
de fichero se escribe con CTRL-Z en el DOS.
La funcin strcpy() copia el string pasado como segundo
argumento en el string pasado como primer argumento. La funcin
toupper() devuelve el carcter pasado como argumento en mayscula.
*/
#include <stdio.h>

/* printf (), gets (), fprintf (), stderr, EOF,


fopen (), fclose (), fgetc (), fputc (), NULL */

#include <conio.h> /* getch () */


#include <string.h> /* strcpy () */
#include <ctype.h> /* toupper () */
#define BOOLEAN int
#define TRUE
1
#define FALSE
0
#define ESC 27
#define ENTER '\r'
#define NUMMAXCARACTERES 255
void main (void)
{
BOOLEAN salir = FALSE;
char nombre_fichero_entrada[NUMMAXCARACTERES],
nombre_fichero_salida[NUMMAXCARACTERES];
FILE *pfe, *pfs; /* punteros a fichero de entrada y fichero de
salida resp. */
char ch;
while (! salir)
{
puts ("\n\nCOPIAR FICHERO CON EL SISTEMA DE FICHEROS DE ALTO
NIVEL:\n");

printf ("Introduzca nombre de fichero de entrada (ENTER o CON "


"para teclado): ");
gets (nombre_fichero_entrada);
if (*nombre_fichero_entrada == '\0')
strcpy (nombre_fichero_entrada, "con");
printf ("Introduzca nombre de fichero de salida (ENTER o CON "
"para pantalla): ");
gets (nombre_fichero_salida);
if (*nombre_fichero_salida == '\0')
strcpy (nombre_fichero_salida, "con");
if ((pfe = fopen (nombre_fichero_entrada, "r")) == NULL)
fprintf (stderr, "\nERROR: No es posible abrir el fichero de "
"entrada %s.\n", nombre_fichero_entrada);
else if ((pfs = fopen (nombre_fichero_salida, "w")) == NULL)
{
fprintf (stderr, "\nERROR: No es posible abrir el fichero "
"de salida %s.\n", nombre_fichero_salida);
fclose (pfe);
}
else
{
int c;
while ((c = fgetc (pfe)) != EOF)
fputc (c, pfs);
fclose (pfe);
fclose (pfs);
}
printf ("\n\nDesea copiar otro fichero (S o ENTER: S; N o "
"ESC: No)? ");
do
{
ch = getch ();
} while (ch != ENTER && toupper (ch) != 'S' &&
ch != ESC && toupper (ch) != 'N');
salir = ch == ESC || toupper (ch) == 'N';
}

/*
+-------------------------+
| COMPARACION DE FICHEROS |
+-------------------------+
Este programa
iguales o distintos.
*/

compara

dos

ficheros

determina

si son

#include <stdio.h> /* printf (), gets (), fprintf (), stderr, EOF,
fopen (), fclose (), fgets (), fputs (), NULL */
#include <conio.h> /* getch () */
#include <string.h> /* strcpy (), strstr () */
#include <ctype.h> /* toupper () */
#define BOOLEAN int

#define TRUE
#define FALSE

1
0

#define ESC 27
#define ENTER '\r'
#define NUMMAXCARACTERES 255
void main (void)
{
BOOLEAN salir = FALSE;
char nombre_fichero_1[NUMMAXCARACTERES],
nombre_fichero_2[NUMMAXCARACTERES];
FILE *pf1, *pf2; /* punteros a fichero de entrada y fichero de
salida resp. */
char ch;
while (! salir)
{
puts ("\n\nCOMPARAR DOS FICHEROS:\n");
printf ("Introduzca nombre de primer fichero (ENTER o CON para "
"teclado): ");
gets (nombre_fichero_1);
if (*nombre_fichero_1 == '\0')
strcpy (nombre_fichero_1, "con");
printf ("Introduzca nombre de segundo fichero (ENTER o CON "
"para teclado): ");
gets (nombre_fichero_2);
if (*nombre_fichero_2 == '\0')
strcpy (nombre_fichero_2, "con");
if ((pf1 = fopen (nombre_fichero_1, "r")) == NULL)
fprintf (stderr, "\nERROR: No es posible abrir el fichero "
"%s.\n", nombre_fichero_1);
else if ((pf2 = fopen (nombre_fichero_2, "r")) == NULL)
{
fprintf (stderr, "\nERROR: No es posible abrir el fichero "
%"s.\n", nombre_fichero_2);
fclose (pf1);
}
else
{
int ch1, ch2;
while ((ch1 = fgetc (pf1)) == (ch2 = fgetc (pf2)) && ch1
!= EOF) ;
printf ("\nLos dos ficheros comparados son %s.\n", feof
(pf1) && feof (pf2) ? "iguales" : "distintos");

fclose (pf1);
fclose (pf2);

printf ("\n\nDesea comparar otros dos ficheros (S o ENTER: "


"S; N o ESC: No)? ");
do
{
ch = getch ();
} while (ch != ENTER && toupper (ch) != 'S' &&
ch != ESC && toupper (ch) != 'N');

salir = ch == ESC || toupper (ch) == 'N';


}

/*
+----------------------------------------------------+
| ORDENACION DE UN FICHERO (SIMILAR AL SORT DEL DOS) |
+----------------------------------------------------+
Este programa lee las lneas de un fichero de entrada (que
puede ser la consola) y escribe las lneas ordenadas en un fichero
de salida (que puede ser la consola).
*/
#include <stdio.h> /* printf (), gets (), FILE, fopen (), fclose (),
NULL, fprintf (), fgets () */
#include <stdlib.h> /* exit () */
#include <alloc.h> /* malloc (), free () */
#include <conio.h> /* getch () */
#include <string.h> /* strcmp (), strcpy () */
#include <ctype.h> /* toupper () */
#define
#define
#define
#define
#define
#define

BOOLEAN int
TRUE
1
FALSE
0
ESC 27
ENTER '\r' /* no vale '\n' */
NUMMAXCARACTERES 255

struct nodo_arbol
{
char linea[NUMMAXCARACTERES];
struct nodo_arbol *pizq, *pder;
};
void
void
void
void
void

inicializar_arbol (struct nodo_arbol **parb);


hacer_nodo_arbol (struct nodo_arbol **pa);
insertar_en_arbol (struct nodo_arbol **parb, char *lin);
imprimir_arbol (struct nodo_arbol *parb, FILE *pfichsal);
liberar_arbol (struct nodo_arbol **parb);

void main (void)


{
BOOLEAN salir = FALSE;
char nombre_fichero_entrada[NUMMAXCARACTERES],
nombre_fichero_salida[NUMMAXCARACTERES],
linea_fichero[NUMMAXCARACTERES];
FILE *pfe, *pfs; /* punteros a fichero de entrada y fichero de
salida resp. */
char ch;
struct nodo_arbol *parbol;
while (! salir)
{
puts ("\n\nORDENACION DE FICHEROS.\n");
printf ("Introduzca nombre de fichero de entrada (ENTER o CON "

"para teclado): ");


gets (nombre_fichero_entrada);
if (*nombre_fichero_entrada == '\0')
strcpy (nombre_fichero_entrada, "con");
printf ("Introduzca nombre de fichero de salida (ENTER o CON "
"para pantalla): ");
gets (nombre_fichero_salida);
if (*nombre_fichero_salida == '\0')
strcpy (nombre_fichero_salida, "con");
if ((pfe = fopen (nombre_fichero_entrada, "r")) == NULL)
printf ("\nERROR: No es posible abrir el fichero de entrada "
"%s.\n", nombre_fichero_entrada);
else if ((pfs = fopen (nombre_fichero_salida, "w")) == NULL) {
printf ("\nERROR: No es posible abrir el fichero de salida "
"%s.\n", nombre_fichero_salida);
fclose (pfe); }
else { inicializar_arbol (&parbol);

while (fgets (linea_fichero, sizeof (linea_fichero), pfe) !=


NULL)
insertar_en_arbol (&parbol, linea_fichero);
imprimir_arbol (parbol, pfs);
fclose (pfe);
fclose (pfs);
liberar_arbol (&parbol);

printf ("\n\nDesea ordenar otro fichero (S o ENTER: S; N o "


"ESC: No)? ");
do
{
ch = getch ();
} while (ch != ENTER && toupper (ch) != 'S' &&
ch != ESC && toupper (ch) != 'N');
salir = ch == ESC || toupper (ch) == 'N';

}
void inicializar_arbol (struct nodo_arbol **parb)
{
*parb = NULL;
}
void hacer_nodo_arbol (struct nodo_arbol **pa)
{
if ((*pa = (struct nodo_arbol *) malloc (sizeof (struct
nodo_arbol))) == NULL)
{
printf ("\nERROR: Memoria insuficiente.");
exit (1);
}
}
void insertar_en_arbol (struct nodo_arbol **parb, char *lin)
{
struct nodo_arbol *p, *pant, *pa;
hacer_nodo_arbol (&pa);
strcpy (pa->linea, lin);
pa->pizq = pa->pder = NULL;

p = *parb;
pant = NULL;
while (p != NULL)
{
pant = p;
if (strcmp (p->linea, lin) > 0)
p = p->pizq;
else
p = p->pder;
}

if (pant == NULL)
*parb = pa;
else if (strcmp (pant->linea, lin) > 0)
pant->pizq = pa;
else
pant->pder = pa;

void imprimir_arbol (struct nodo_arbol *parb, FILE *pfichsal)


{
if (parb != NULL)
{
imprimir_arbol (parb->pizq, pfichsal);
fprintf (pfichsal, parb->linea);
imprimir_arbol (parb->pder, pfichsal);
}
}
void liberar_arbol (struct nodo_arbol **parb)
{
if (*parb != NULL)
{
liberar_arbol (&(*parb)->pizq);
liberar_arbol (&(*parb)->pder);
free (*parb);
}
}

/*
+--------------------+
| SHELL CRONOMETRADO |
+--------------------+
Este programa ejecuta otro programa cronomentrando el tiempo
empleado en la ejecucin del segundo. El programa a ejecutar puede
ser un programa ejecutable con extensin .EXE o .COM, un fichero por
lotes .BAT o un comando interno del DOS como dir. Adems dicho
programa se puede ejecutar con argumentos.
El main() que est entre comentarios es la versin que toma
los datos desde la lnea de rdenes del sistema operativo en vez de
preguntar por ellos con las funciones de entrada estndar como se
hace en el main() actual.
*/

#include
#include
#include
#include
#include

<process.h> /* system () */
<stdio.h>
/* puts (), printf () */
<time.h>
/* time_t, difftime (), time () */
<string.h> /* strcat () */
<conio.h> /* getch () */

/*
void main (int argc, char **argv);
*/
void main (void);
void ejecutar (char *);
/*
void main (int argc, char **argv)
{
if (argc == 1)
puts ("\nEste programa cronometra el tiempo empleado al ejecutar
el programa\npasado como argumento.\n");
else
{
int cont;
char nombre_programa_a_ejecutar[256] = { 0 };
for (cont = 1; cont < argc; cont++)
{
if (*nombre_programa_a_ejecutar)
strcat (nombre_programa_a_ejecutar, " ");
strcat (nombre_programa_a_ejecutar, *(argv+cont));
}
ejecutar (nombre_programa_a_ejecutar);
}
}
*/
void main (void)
{
char nombre_programa_a_ejecutar[256] = { 0 };
printf ("\nIntroduzca nombre de programa a ejecutar: ");
do
{
gets (nombre_programa_a_ejecutar);
} while (!*nombre_programa_a_ejecutar);
ejecutar (nombre_programa_a_ejecutar);
getch ();
}
void ejecutar (char *nombre_programa)
{
int valor_devuelto;
time_t tiempo1, tiempo2;
tiempo1 = time (NULL);
valor_devuelto = system (nombre_programa);
tiempo2 = time (NULL);
if (valor_devuelto == -1)
printf ("\nError al intentar ejecutar el programa \"%s\".",
nombre_programa);
else
{
int horas, minutos, segundos;

unsigned segundos_totales = (unsigned) difftime (tiempo2,


tiempo1);
segundos = segundos_totales % 60;
minutos = (segundos_totales / 60) % 60;
horas = (segundos_totales / 3600) % 24;
printf ("\nTiempo empleado en la ejecucin del programa "
"\"%s\":\n", nombre_programa);
if (horas)
printf ("%d hor%s ", horas, horas == 1 ? "a" : "as");
if (horas || minutos)
printf ("%d minut%s ", minutos, minutos == 1 ? "o" : "os");
printf ("%d segund%s", segundos, segundos == 1 ? "o" : "os");
}
puts ("");
}

/*
+---------------------------------------+
| VISUALIZACION DE 256 BYTES DE MEMORIA |
+---------------------------------------+
Este programa visualiza 256 bytes de memoria a partir de la
direccin de comienzo especificada (en hexadecimal).
*/
#include <stdio.h> /* printf (), scanf () */
#include <conio.h> /* getch () */
void main (void)
{
register int i;
unsigned char ch, *p;
printf ("VISUALIZAR MEMORIA.\n\n");
printf ("Direccin de comienzo (en hex): ");
scanf ("%p%*c", &p);

printf ("\n%p: ", p); /* imprime direccin */


for (i = 1; i <= 256; i++)
{
ch = *p;
printf ("%02x ", ch); /* visualiza en hexadecimal */
p++;
if (! (i % 16)) /* cada 16 bytes salta a la lnea siguiente */
{
printf ("\n");
if (i != 256)
printf ("%p: ", p); /* imprime direccin */
}
}
getch ();