Sei sulla pagina 1di 70

1

DIAPOSITIVAS DE "PROGRAMACIN"



GRADO EN INGENIERA EN TECNOLOGAS DE LA TELECOMUNICACIN
GRADO EN INGENIERA EN SISTEMAS DE TELECOMUNICACIN
GRADO EN INGENIERA ELECTRNICA DE COMUNICACIONES
GRADO EN INGENIERA TELEMTICA


Escuela Politcnica Superior
Universidad de Alcal

2

NDICE

SEMANA 1.1. REPASO DE PUNTEROS

CONCEPTOS FUNDAMENTALES
ERRORES TPICOS EN EL USO DE PUNTEROS
OPERACIONES CON PUNTEROS
PRIORIDADES CON LOS OPERADORES ++ Y --

SEMANA 1.2. REPASO DE PUNTEROS (II)

PUNTEROS GENRICOS
PUNTEROS Y ARRAYS DE DATOS
PUNTEROS A CADENAS DE CARACTERES
ARRAYS DE PUNTEROS
PUNTEROS A PUNTEROS
PUNTEROS A ESTRUCTURAS Y UNIONES

SEMANA 2.1. REPASO DE PUNTEROS (III)

ASIGNACIN DINMICA DE MEMORIA
PASO DE ARGUMENTOS POR REFERENCIA

SEMANA 2.2. MS SOBRE PUNTEROS

PASO DE UN ARRAY UNIDIMENSIONAL COMO ARGUMENTO A UNA FUNCIN
PASO DE UN ARRAY BIDIMENSIONAL COMO ARGUMENTO A UNA FUNCIN
PASO DE UN PUNTERO COMO ARGUMENTO A UNA FUNCIN
DATOS RETORNADOS POR UNA FUNCIN

SEMANA 3. PASO DE ESTRUCTURAS A FUNCIONES,
PUNTEROS A FUNCIONES

PASO DE ESTRUCTURAS Y UNIONES POR VALOR
PASO DE ESTRUCTURAS Y UNIONES POR REFERENCIA
PUNTEROS A FUNCIONES

SEMANA 4. RECURSIVIDAD, ARGUMENTOS EN LNEA DE
RDENES

FUNCIONES RECURSIVAS
ARGUMENTOS EN LNEA DE RDENES


3

SEMANA 5. MANEJO BSICO DE FICHEROS

CONCEPTOS FUNDAMENTALES SOBRE FICHEROS
FLUJO ASOCIADO A UN FICHERO
APERTURA DE UN FICHERO
CIERRE DE UN FICHERO
DETECCIN DE ERRORES DE ACCESO A UN FICHERO
DETECCIN DE FIN DE FICHERO
ANULACIN DE ERRORES DE ACCESO A FICHERO
IMPRIMIR EN PANTALLA MENSAJES DE ERROR
POSICIN DEL APUNTADOR DE LECTURA/ESCRITURA
CONTROL DEL BUFFER DEL FICHERO

SEMANA 6. LECTURA/GRABACIN DE DATOS EN FICHEROS

LECTURA Y GRABACIN DE DATOS CARCTER A CARCTER
GRABACIN DE CADENAS DE CARACTERES
LECTURA DE CADENAS DE CARACTERES
LECTURA Y GRABACIN DE DATOS CON FORMATO
GRABACIN DE REGISTROS
LECTURA DE REGISTROS

SEMANA 7. MS SOBRE FICHEROS

ELIMINACION DE FICHEROS
CAMBIAR EL NOMBRE DE FICHEROS
COPIAR FICHEROS
ESCRIBIR DATOS EN LA IMPRESORA
VER DIRECTORIOS DE FICHEROS
FICHEROS TEMPORALES

SEMANA 8. TIPOS DE ACCESO A FICHEROS

ACCESO SECUENCIAL A FICHEROS
ACCESO ALEATORIO A FICHEROS

SEMANA 9. APLICACIN PRCTICA DE FICHEROS

CREACIN DE UNA BASE DE DATOS SENCILLA

SEMANA 10. ESTRUCTURAS DINMICAS

CONCEPTOS GENERALES
LISTAS LINEALES
CREACIN DE UNA LISTA LINEAL SIMPLEMENTE ENLAZADA
INSERCIN DE UN ELEMENTO EN MITAD DE LA LISTA
ELIMINACIN DE ELEMENTOS DE LA LISTA
4

BSQUEDA DE ELEMENTOS EN LA LISTA
ORDENACIN DE LOS ELEMENTOS DE LA LISTA

SEMANA 11. ESTRUCTURAS DINMICAS (II)

PILAS
COLAS
LISTAS CIRCULARES
LISTAS DOBLEMENTE ENLAZADAS

SEMANA 12. RBOLES BINARIOS

CONCEPTOS GENERALES
RBOLES BINARIOS
RBOLES BINARIOS DE BSQUEDA
RECORRIDO DE RBOLES BINARIOS
BSQUEDA EN RBOLES BINARIOS DE BSQUEDA

SEMANA 13. LGORITMOS DE ORDENACIN DE DATOS

CONCEPTOS GENERALES
MTODO DE LA BURBUJA
MTODO DE SELECCIN
MTODO DE INSERCIN
MTODO RECURSIVO QUICKSORT
COMPARATIVA DE ALGORITMOS

SEMANA 14. LGORITMOS DE BSQUEDA DE DATOS

BSQUEDA SECUENCIAL
BSQUEDA BINARIA
ALGORITMOS HASH
PROCEDIMIENTO DE ALMACENAMIENTO HASH
PROCEDIMIENTO DE BSQUEDA HASH
PROCEDIMIENTO DE BORRADO HASH

5

1.1. REPASO DE PUNTEROS

CONCEPTOS FUNDAMENTALES:
Un puntero es una variable que contiene en su interior una direccin de memoria donde reside
un dato.
Este dato apuntado por un puntero puede ser de cualquier tipo bsico (char, short, int, long,
float, double) o derivado (array, struct, union,).
El tamao en bytes de un puntero es fijo (2 o 4 bytes, dependiendo del compilador y del
ordenador utilizados), independientemente del tipo de dato apuntado. Supondremos 4 bytes.
Una variable puntero se declara as: TipoApuntado *NombrePuntero;
Ejemplos: int *p; Puntero a dato int
double *q; Puntero a dato double
Una vez declarado, hay que cargarlo con la direccin de algn dato existente. Mientras no est
as inicializado, el puntero no puede ser utilizado.
Los operadores principales utilizados con punteros son dos: & (direccin de) y * (lo apuntado
por, operador de indireccin).
Ejemplos: int n=10, m; Variables n y m de tipo int, n inicializada con el valor 10
int *p; Puntero a dato int, puntero an no inicializado
p = &n; El puntero se carga con la direccin de la variable n
m = *p; La variable m se carga con lo apuntado por p (m=n) (m=10)

ERRORES TPICOS EN EL USO DE PUNTEROS:
Usar el operador indireccin (*) cuando el puntero no est an correctamente inicializado (no
sabemos dnde apunta), o bien est cargado con el valor NULL.
NULL es un puntero de valor 0 (constante simblica importante del compilador C).
Ejemplo: main()
{ int n=10, m; int *p=&n, *q, *r=NULL;
m=*p; Bien, m copia el valor de n
m=*q; Mal, no sabemos dnde apunta q
m=*r; Mal, r vale NULL, no se puede almacenar nada en esta direccin
}
Cargar un puntero de un tipo con la direccin de un dato de otro tipo.
Ejemplo: main()
{ double a=10.33, b; Dos variables de tipo double
int *p; Puntero a dato int
p=&a; p se carga con la direccin de a, el compilador genera mensaje de error
b=*p; b toma un valor impredecible (no 10.33), porque p es un puntero a int
}

OPERACIONES CON PUNTEROS:
Copiar un puntero en otro.
Ejemplo: main()
{ int a=10, *p, *q;
p = &a; q = p;
Sumar o restar a un puntero un nmero entero (til slo cuando apunta a un array de datos).
Ejemplo: main()
{ int x[100], *p=&x[3];
p = p+3; p apuntar a x[6]
p = p-2; p apuntar a x[4]
6

Incrementar (++) o decrementar (--) un puntero (til slo cuando apunta a un array de datos).
Ejemplo: main()
{ int x[100], *p=&x[3];
p++; ++p; p apuntar a x[5] ambos casos equivalen a p = p+1;
p--; --p; p volver a apuntar a x[3] ambos casos equivalen a p = p-1;
Restar entre s dos punteros de un mismo tipo (til slo cuando apuntan a un array de datos).
Ejemplo: main()
{ int x[100], n;
int *p=&x[3], *q=&x[0];
n = p-q; n valdr 3 (distancia en datos int entre p y q)
n = q-p; n valdr -3 (distancia en datos int entre q y p)
Para saber la distancia en n de bytes entre p y q:
n = (char *)p (char *)q; n valdr 3x4=12 (suponiendo datos int de 4 bytes)
Comparar entre s dos punteros (til slo cuando apuntan a un array de datos).
Ejemplo: main()
{ int x[100], n, *p=&x[3], *q=&x[0];
..
if (p>q) { .. }
if (p-n >= q+2) { .. }
if (q!=NULL && q<=p) { .. }

EJEMPLO-01.
#include <stdio.h>
main()
{ int x[10]={10,20,30,40,50,60,70}, b, *pa, *pb;
pa = &x[5]; //pa apunta a x[5]=60
b = *pa + 1; //b=61
b = *(pa + 1); //b=70
pb = &x[3]; //pb apunta a x[3]=40
b = pb-pa; //b=-2
*pb = 0; //x[3]=0
*pb += 2; //x[3] = x[3]+2 ===> x[3]=2
(*pb)--; //x[3]-- ===> x[3]=1
x[0] = *pb--; //x[0]=x[3] y luego pb se decrementa, apuntar a x[2]
x[2] = *--pb; //primero se decrementa pb (apuntar a x[1]), luego x[2]=*pb, o sea, x[2]=x[1]
if (pb > pa-5) //condicin cierta: pb apunta a x[1], pa-5 apunta a x[0]
b = 0; //b se pone a 0
}

PRIORIDADES CON LOS OPERADORES ++ Y --:
Los operadores de incremento (++) y decremento (--) de tipo prefijo tienen la misma prioridad
alta que los operadores "direccin de" (&) e indireccin (*). Si hay varios de estos operadores
en la misma expresin, se evalan de derecha a izquierda.
Los operadores ++ y -- de tipo sufijo tienen muy baja prioridad, y se evalan al final del todo,
incluso despus del operador de asignacin (=).
Los 4 posibles casos de convivencia del operador indireccin (*) e incremento/decremento de
alta o baja prioridad son los siguientes:
main()
{ int c, *p;
c = *p++; equivale a c = *p; p++; o bien c = *p; p = p+1;
c = *++p; equivale a ++p; c = *p; o bien p = p+1; c = *p;
c = ++*p; equivale a *p+=1; c = *p; o bien *p = *p+1; c = *p;
c = (*p)++; equivale a c = *p; *p+=1; o bien c = *p; *p = *p+1;
7

1.2. REPASO DE PUNTEROS (II)

PUNTEROS GENRICOS:
Un puntero "genrico" es aquel que puede apuntar a diversos tipos de datos a lo largo de la
ejecucin del programa. Se declara de la siguiente forma: void *NombrePuntero;
Una vez declarado, se puede copiar en l cualquier tipo de direccin, con un simple operador de
asignacin (=): NombrePuntero = &dato;
Una vez cargado, para acceder al dato apuntado, hay que utilizar el operador de "indireccin"
(*), pero haciendo uso de un operador "cast" de conversin explcita de tipo para punteros, que
indique el tipo de dato que est siendo apuntado actualmente por el puntero:
n = *(tipoDato *)NombrePuntero;

EJEMPLO-02.
// PUNTEROS GENRICOS
main()
{ int dato1 = 10, dato2; //datos int
float dato3 = 3.14, dato4; //datos float
void *punt; //puntero genrico
float *p = &dato3;
//......
punt = &dato1; //punt apunta a dato1, de tipo int
dato2 = *(int *)punt; //accedemos a dato1 a travs de punt, lo copiamos en dato2
//......
punt = &dato3; //punt apunta a dato3, de tipo float
dato4 = *(float *)punt; //accedemos a dato3 a travs de punt, lo copiamos en dato4
//......
punt = p; //bien, siempre se puede asignar a un puntero genrico otro puntero
p = punt; //no siempre bien, depende del compilador, puede generar error
p = (float *)punt; //siempre bien, haciendo uso de un operador cast
}

PUNTEROS Y ARRAYS DE DATOS:
Con un puntero se puede manipular cmodamente un array de datos, accediendo a todos sus
elementos. Hay varias sintaxis posibles para conseguirlo, pero la ms cmoda es aplicar al puntero
por su derecha un subndice de tipo int metido entre corchetes, igual que haramos con un array.

EJEMPLO-03.
// PUNTEROS Y ARRAYS DE DATOS
main()
{ int lista[] = {24,30,15,25,18}; //Array de 5 datos de tipo int
int i; //Variable para controlar el subndice del array
int *p = &lista[0]; //Puntero para manipular el array (tambin habra servido int *p=lista; )
for (i=0; i<5; i++) //Bucle de exploracin SIN puntero, con 2 posibilidades sintcticas
printf("%d ", lista[i]); //Esta es la sintaxis ms cmoda
printf("%d ", *(lista+i)); //Sintaxis aceptable, el valor de "lista" no se modifica
printf("%d ", *lista++); //Sintaxis NO aceptable, pretende alterar el valor de "lista"

for (i=0; i<5; i++) //Bucle de exploracin CON puntero, con 3 posibilidades sintcticas
printf("%d ", *(p+i));
printf("%d ", *p++); //Aqu el puntero se mueve para ir apuntando a los sucesivos datos
printf("%d ", p[i]); //Esta es la sintaxis ms cmoda, idntica al uso del array con corchetes
}

A todo puntero p se le puede aplicar un subndice i entre corchetes, el resultado es siempre acceder
al dato colocado i posiciones por encima de la apuntada por p: p[i] *(p+i)

8

PUNTEROS A CADENAS DE CARACTERES:
Se pueden declarar variables puntero para manipular arrays de datos de tipo char, o sea, cadenas de
caracteres. Estos punteros son modificables, y pueden pasar a apuntar a otras cadenas utilizando un
sencillo operador de asignacin (=), cosa que no se poda hacer con los arrays.

EJEMPLO-01.
// PUNTEROS A CADENAS
#include <stdio.h>
main()
{ char cad[40] = "Hola"; //cad es un array de datos char, el nombre "cad" es su direccin de inicio
char *pc = cad; //pc es un puntero variable, apuntando al inicio de cad
printf("%s", cad); //Imprime Hola
printf("%s", pc); //Imprime Hola
pc = "Adios"; //Bien, el puntero ha variado y apunta al comienzo de la constante Adios
printf("%s", pc); //Imprime Adios
cad = "Adios"; //MAL, cad no se puede cambiar con el operador de asignacin (=)
strcpy(cad,"Adios"); //Bien, hemos copiado en el array cad un nuevo contenido
printf("%s", cad); //Imprime Adios
printf("%c", pc[1]); //Imprime d
printf("%c", cad[1]); //Imprime d
pc++; //pc apunta a la d
printf("%s", pc); //Imprime dios
}

ARRAYS DE PUNTEROS:
Se puede declarar un array compuesto por elementos de tipo puntero, de la siguiente forma:
TipoDatoApuntado *NombreArray[NumElementos];
El nombre del array representa la direccin de memoria de comienzo del array.
Estos arrays de punteros se utilizan principalmente para manipular cmodamente un array de datos
de dos dimensiones.

EJEMPLO-02.
// ARRAY DE PUNTEROS
#include <stdio.h>
main()
{ float a[5][4]; //array de datos bidimensional a controlar
float *p[5]; //array de punteros asociado
int i,j; //indices para bucles

for (i=0; i<5; i++) //bucle para conectar los punteros con las filas del array de datos
p[i] = &a[i][0]; //tambin servira p[i]=a[i];

printf("Introduce los datos del array...\n");
for (i=0; i<5; i++) //bucle SIN hacer uso del array de punteros
for (j=0; j<4; j++)
scanf("%f", &a[i][j]); //con punteros sera scanf("%f", &p[i][j]);

printf("\nLos datos introducidos son...\n");
for (i=0; i<5; i++) //bucle CON utilizacin del array de punteros
{
for (j=0; j<4; j++)
printf("%g\t", p[i][j]); //otra posible sintaxis de p[i][j] es: *(*(p+i)+j)
printf("\n");
}
}


9

PUNTEROS A PUNTEROS:
Se puede declarar un puntero que apunte a otro puntero, de la siguiente forma:
TipoDatoApuntado **NombrePuntero;
De esta forma, podemos manipular el puntero simple apuntado, aplicando una indireccin (*) al
puntero doble, y podemos manipular el dato final apuntado aplicando una doble indireccin (**) al
puntero doble.
Ejemplos: main()
{ int n = 10, m; dos variables de tipo int
int *p = &n; un puntero simple apuntando a n
int **pp = &p; un puntero doble (puntero a puntero) apuntando a p
m = *p; accedemos a n a travs de p, m=10
m = **pp; de nuevo accedemos a n a travs de pp (doble indireccin), m=10
*pp = &m; hacemos que p pase a apuntar a m, lo mismo que p=&m;

Estos punteros se suelen usar para manipular cmodamente un array de punteros simples, que,
como hemos visto, sirve a su vez para manipular un array de datos de dos dimensiones.
Ejemplo: main()
{ char *pc[] = {"Jose", "Ana", "Alberto", "Sonia"};
char **q = pc;
printf("%s", q[0]); Imprime "Jose", igual que: printf("%s", pc[0]);
printf("%c", q[3][0]); Imprime "S", igual que: printf("%c", pc[3][0]);

PUNTEROS A ESTRUCTURAS Y UNIONES:
Se puede declarar un puntero que apunte a una variable de tipo estructura (struct) o de tipo union.
Para acceder a los campos de dichas estructuras/uniones a travs del puntero, se utiliza el operador
"flecha" (->), ya no el operador "punto" (.) que se utiliza con el nombre de la variable struct.
Este tipo de acceso mediante puntero es el nico modo posible cuando creamos variables de tipo
struct/union mediante asignacin dinmica de memoria.

EJEMPLO-03.
// PUNTEROS A ESTRUCTURAS
#include <stdio.h>
main()
{ struct fecha
{ unsigned int dd,mm,aa; }; //declaracin del tipo struct

struct fecha f = {25,12,2010}; //declaracin de la variable struct, e inicializacin de sus campos
struct fecha *p = &f; //declaracin de un puntero p que apunta a f

printf("La fecha es: %d-%d-%d", f.dd, f.mm, f.aa); //impresin a travs de la variable struct
printf("\nLa fecha es: %d-%d-%d", p->dd, p->mm, p->aa); //impresin a travs del puntero
}

10

2.1. REPASO DE PUNTEROS (III)

ASIGNACIN DINMICA DE MEMORIA:
Esta tcnica permite crear nuevas variables de memoria o arrays de cualquier tipo en tiempo de
ejecucin del programa, sin necesidad de haberlas declarado de antemano en el cdigo fuente. Slo
es necesario declarar una variable puntero para cada nueva variable o array de datos que queramos
crear. Esto se consigue principalmente con las funciones malloc y free:
#include <stdlib.h>
void *malloc(unsigned int Numbytes);
void free(void *Puntero);

La funcin malloc recibe como argumento el nmero de bytes consecutivos que queremos reservar
para nuestra nueva variable, realiza la bsqueda de tal espacio libre de la memoria, lo reserva y
devuelve la direccin de memoria de comienzo del mismo. Esta direccin debe ser almacenada en
una variable del tipo puntero adecuado (el tipo de dato de nuestra variable recin creada), y a travs
de tal puntero manipularemos la nueva variable.
Esta zona de memoria permanece reservada hasta que es liberada mediante la funcin free, que
recibe como argumento dicho puntero que la apunta. Si tal zona no es liberada mediante free, puede
quedar reservada incluso ms all de la finalizacin del programa, generando "lagunas de memoria"
que no pueden ser utilizadas por otras aplicaciones, hasta que el ordenador es reseteado.

EJEMPLO-01.
// ASIGNACION DINMICA DE MEMORIA
#include <stdio.h>
#include <stdlib.h>
main()
{ float *p; //crearemos una variable de datos de tipo float
struct fecha
{ int dd,mm,aa; } *q; //crearemos tambin una variable estructura

p = (float *)malloc(4); //reservamos memoria para la variable float
if (p==NULL)
{ printf("Error: No hay memoria suficiente."); exit(0); }

q = (struct fecha *)malloc(sizeof(struct fecha)); //reservamos memoria para la variable struct
if (q==NULL)
{ printf("Error: No hay memoria suficiente."); free(p); exit(0); }

printf("Introduzca la variable float:");
scanf("%f", p); //Introducimos por teclado la variable float

printf("Introduzca dia, mes y ao:");
scanf("%d %d %d", &q->dd, &q->mm, &q->aa); //Introducimos por teclado la variable struct

free(p); free(q); //liberamos la memoria antes de terminar el programa
}

Si con la funcin malloc reservamos espacio para varios datos sucesivos en la memoria, estaremos
creando un "array dinmico", cuyo tamao puede ser variado a lo largo de la ejecucin del
programa. Tal array ser manipulado con facilidad mediante el puntero mencionado anteriormente.

EJEMPLO-02.
// ARRAY DINAMICO
#include <stdio.h>
#include <stdlib.h>
11

main()
{ float *p; //crearemos un array dinmico de datos float
int numelem=0, i;
do
{ printf("Dime N de elementos del array a crear:");
scanf("%d", &numelem); //pedimos por teclado el n de elementos deseado
} while (numelem<1);

p = (float *)malloc(numelem*4); //reservamos memoria para el array dinmico
if (p==NULL)
{ printf("Error: No hay memoria suficiente."); exit(0); }

printf("Introduzca los datos del array:\n");
for (i=0; i<numelem; i++) //Introducimos por teclado los datos del array
scanf("%f", &p[i]);

printf("\nLos datos introducidos son:\n");
for (i=0; i<numelem; i++) //Imprimimos en pantalla los datos del array
printf("%g", p[i]);

free(p); //liberamos la memoria antes de terminar el programa
}

Existe la funcin especfica calloc para creacin de arrays dinmicos, y la funcin realloc para
modificacin del tamao en bytes de un array dinmico:
#include <stdlib.h>
void *calloc(unsigned int NumElementos, unsigned int TamElemento);
void *realloc(void *Puntero, unsigned int Numbytes);

La funcin calloc recibe dos argumentos, el n de elementos del array a crear, y el tamao en bytes
de uno de esos elementos. Reserva tantos bytes sucesivos como el producto de ambos argumentos,
y adems pone a ceros la zona de memoria recin reservada (cosa que no hace malloc).
La funcin realloc recibe dos argumentos, el puntero que apunta a la zona de memoria reservada ya
existente, y el nuevo tamao en n de bytes deseado para tal zona. Los datos ya almacenados en la
zona inicial no se pierden. Si el primer argumento (el puntero) vale NULL, realloc se comporta
igual que malloc.

EJEMPLO-03.
// REALLOC
// Toma de datos en un array dinmico de tamao creciente
#include <stdio.h>
#include <stdlib.h>
main()
{ int *p=NULL; //puntero maestro del array dinmico
int *paux; //puntero auxiliar
int i=0; //indice para bucle
int num=0; //N de datos actuales del array
int dato; //aqu leemos el dato desde el teclado

printf("Dame datos del array (Ctrl+Z = Fin):");

while (scanf("%d", &dato) != EOF) //leemos dato por teclado y comprobamos Ctrl+Z
{ num++; //incrementar n de elementos del array
paux = (int *)realloc(p, num*sizeof(int)); //ampliar espacio para el array
if (paux==NULL) //No hay memoria
{ num--; //decrementar n de elementos del array
printf("Memoria llena");
12

}
else
{ p=paux; //actualizar puntero maestro p
p[num-1]=dato; //copiar dato al array dinmico
}
}

printf("\nLos datos introducidos son:\n");
for (i=0; i<num; i++)
{ printf("%d ", p[i]); }

free(p); //liberar memoria antes de terminar el programa
}

PASO DE ARGUMENTOS POR REFERENCIA:
Un argumento de entrada a una funcin es pasado "por referencia" cuando a la funcin se le
pasa la direccin de memoria donde reside dicho argumento, haciendo uso del operador
"direccin de" (&).
Esta direccin debe ser recibida en la cabecera de la funcin mediante una variable de tipo
puntero.
Este tipo de paso de argumentos permite a la funcin acceder a la misma variable que ha sido
pasada como argumento, y no a una copia de dicha variable (tal como sucede en el paso de
argumentos "por valor"). Dicho acceso a la variable se hace a travs del puntero recibido, con el
operador indireccin (*).
En el paso de argumento "por valor", la funcin recibe una copia de la variable original, y no
hay acceso posible a dicha variable original.
Todo tipo de variable puede ser pasada como argumento en las dos formas, por valor y por
referencia, excepto los arrays, que nicamente pueden ser pasados por referencia (siempre se
pasa la direccin de comienzo del array).

EJEMPLO-04.
// PASO DE ARGUMENTOS POR REFERENCIA Y POR VALOR
// Programa para intercambiar los valores de dos variables
#include <stdio.h>
void intercambio1(int x, int y);
void intercambio2(int *x, int *y);

main()
{ int a=10, b=20; //queremos intercambiar a con b
intercambio1(a,b); //paso de argumentos por valor
printf("a=%d, b=%d",a,b); //imprime a=10, b=20 No se ha producido el intercambio
intercambio2(&a, &b); //paso de argumentos por referencia
printf("a=%d, b=%d",a,b); //imprime a=20, b=10 S se ha producido el intercambio
}

void intercambio1(int x, int y) //argumentos recibidos por valor, "x e y" son copias de "a y b"
{ int z; //variable intermedia necesaria en todo intercambio
z = x; x = y; y = z;
}
void intercambio2(int *x, int *y) //argumentos recibidos por referencia, "x e y" apuntan a "a y b"
{ int z; //variable intermedia necesaria en todo intercambio
z = *x; *x = *y; *y = z;
}

13

2.2. MS SOBRE PUNTEROS

PASO DE UN ARRAY UNIDIMENSIONAL COMO ARGUMENTO A UNA FUNCIN:
Un array unidimensional siempre es pasado "por referencia" como argumento a una funcin
(nunca por valor), aportando en la llamada a la funcin el nombre del array, nombre que
representa la direccin de inicio del array.
Esta direccin puede ser recibida en la cabecera de la funcin con dos posibles sintaxis: tipo
array (con corchetes []) o tipo puntero (con asterisco *). En ambos caso, la variable receptora
acta como un puntero variable que puede ser modificado.
El paso del nombre del array como argumento slo informa sobre su direccin de comienzo,
pero no sobre su tamao en n de elementos. Habitualmente hay que pasar tambin esta
informacin a la funcin, mediante un segundo argumento de entrada de tipo int.

EJEMPLO-01.
// PASO DE ARRAYS UNIDIMENSIONALES COMO ARGUMENTOS A FUNCIONES
// Programa para copiar un array en otro
#include <stdio.h>
void CopiaArray1(int x[], int y[], int num);
void CopiaArray2(int *x, int *y, int num);
void CopiaArray3(int *x, int *y, int num);

main()
{ int a[5]={24,30,15,25,18}; //Array de origen de la copia
int b[5]; //Array de destino de la copia
int numelem = sizeof(a)/sizeof(int); //n de elementos del array a
int i;

CopiaArray1(a, b, numelem); //las tres funciones hacen lo mismo
CopiaArray2(a, b, numelem); //de tres maneras diferentes
CopiaArray3(a, b, numelem);

printf("Los datos copiados en el array b son: ");
for (i=0; i<numelem; i++) printf("%d ",b[i]);
}

void CopiaArray1(int x[], int y[], int num) //arrays recibidos con sintaxis de array []
{ int i;
for (i=0; i<num; i++)
y[i] = x[i];
}
void CopiaArray2(int *x, int *y, int num) //arrays recibidos con sintaxis de puntero *
{ int i;
for (i=0; i<num; i++)
y[i] = x[i];
}
void CopiaArray3(int *x, int *y, int num) //arrays recibidos con sintaxis de puntero *
{ while (num)
{ *y++ = *x++; num--; }
}

PASO DE UN ARRAY BIDIMENSIONAL COMO ARGUMENTO A UNA FUNCIN:
Un array bidimensional o multidimensional siempre es pasado "por referencia" como argumento
a una funcin, aportando en la llamada a la funcin el nombre del array, que representa su
direccin de inicio.
Esta direccin puede ser recibida en la cabecera de la funcin con sintaxis tipo array (con
corchetes []) o tipo puntero (con asterisco *).
14

En el primer caso, se debe dejar en blanco la primera dimensin, y todas las dems sucesivas
deben ser indicadas con valores constantes (nmeros enteros o constantes simblicas).
El paso del nombre del array como argumento slo informa sobre su direccin de comienzo,
pero no sobre su tamao en n de elementos de sus varias dimensiones. Esta informacin debe
ser suministrada tambin a la funcin, mediante otros argumentos de entrada.

EJEMPLO-02.
// PASO DE ARRAYS BIDIMENSIONALES COMO ARGUMENTOS A FUNCIONES
// Programa para copiar un array bidimensional en otro

#include <stdio.h>
#define FILAS 3
#define COLS 2
void CopiaArray1(int x[][COLS], int y[][COLS]);
void CopiaArray2(int *x, int *y);
void CopiaArray3(int *x, int *y);

main()
{ int a[FILAS][COLS]={24,30,15,25,18,13}; //Array de origen de la copia
int b[FILAS][COLS]; //Array de destino de la copia
int i,j;

CopiaArray1(a, b); //las tres funciones hacen lo mismo
CopiaArray2((int *)a, (int *)b); //de maneras diferentes
CopiaArray3((int *)a, (int *)b);

printf("Los datos copiados en el array b son: ");
for (i=0; i<FILAS; i++)
for (j=0; j<COLS; j++)
printf("%d ",b[i][j]);
}

void CopiaArray1(int x[][COLS], int y[][COLS]) //arrays recibidos con sintaxis de array []
{ int i,j;
for (i=0; i<FILAS; i++)
for (j=0; j<COLS; j++)
y[i][j] = x[i][j];
}
void CopiaArray2(int *x, int *y) //arrays recibidos con sintaxis de puntero *
{ int i,j;
for (i=0; i<FILAS; i++)
for (j=0; j<COLS; j++)
y[i*COLS+j] = x[i*COLS+j]; //o bien: *y++ = *x++;
//NO se puede hacer: y[i][j] = x[i][j];
}
void CopiaArray3(int *x, int *y) //arrays recibidos con sintaxis de puntero *
{ int i;
for (i=0; i<FILAS*COLS; i++)
*y++ = *x++;
}

PASO DE UN PUNTERO COMO ARGUMENTO A UNA FUNCIN:
Una variable de tipo puntero puede ser pasada como argumento a una funcin bien "por valor" o
bien "por referencia".
Si se pasa por valor, la cabecera de la funcin recibe en una variable de tipo puntero una copia
del puntero original pasado en la llamada, y no hay acceso posible al puntero original.
15

Si se pasa por referencia, en la llamada a la funcin se aplica el operador "direccin de" (&) al
puntero que deseamos pasar, y en la cabecera de la funcin se recibe tal direccin en una
variable de tipo "puntero a puntero" (**). Mediante este puntero podemos acceder directamente
al puntero original, no a una copia del mismo.

EJEMPLO-03.
// PASO DE PUNTEROS COMO ARGUMENTOS A FUNCIONES
// Programa para encontrar el dato mayor de un array
#include <stdio.h>
int *buscaMayor1(int *x, int numelem);
void buscaMayor2(int *x, int numelem, int **pp);

main()
{ int a[]={24,30,15,25,38,23};
int *p; //aqu se guardar la direccin del elemento mayor del array

p = buscaMayor1(a, 6); //ambas funciones hacen lo mismo
buscaMayor2(a, 6, &p); //de dos maneras diferentes

printf("El dato mayor es: %d\n", *p);
printf("Se encuentra en la posicion del array: %d", p-a);
}

int *buscaMayor1(int *x, int numelem)
{ int i;
int posMayor=0; //indica el ndice del elemento mayor encontrado
for (i=1; i<numelem; i++)
if (x[i]>x[posMayor])
posMayor = i;
return x+posMayor; //retorna la direccin del elemento mayor del array
}
void buscaMayor2(int *x, int numelem, int **pp) //recibe el puntero p por referencia
{ int i, posMayor=0;
for (i=1; i<numelem; i++)
if (x[i]>x[posMayor])
posMayor = i;
*pp = x+posMayor; //guarda directamente en el puntero p de main() la direccin buscada
}

DATOS RETORNADOS POR UNA FUNCIN:
Una funcin puede retornar, mediante la sentencia "return xxx;", un nico resultado a la funcin
que la invoc, o bien no retornar ninguno (no existe sentencia return, o bien es "return;").
Si se retorna un resultado, este es normalmente un valor copia de alguna variable local de la
funcin, variable local que desaparecer de la memoria una vez concluida la funcin y devuelta
la copia de resultado.
Tambin una funcin puede devolver como resultado una direccin de memoria, pero en este
caso debemos asegurarnos que sea la direccin de una variable que permanezca en la memoria
despus de la finalizacin de la funcin (que no sea de una variable local normal).
Esto se puede conseguir devolviendo la direccin de una variable local "static" (que permanece
en la memoria durante toda la ejecucin del programa), o bien de una variable creada con
asignacin dinmica de memoria con la funcin "malloc"(que permanece en la memoria
mientras no sea liberada con la funcin free), o bien la direccin de una constante de cadena de
caracteres (que siempre est en la memoria formando parte del programa en ejecucin).

EJEMPLO-04.
// FUNCIN QUE RETORNA UNA DIRECCIN
16

// Funcin para devolver un mensaje habitual dependiendo de un cdigo numrico
#include <stdio.h>
char *msg(int cod);

main()
{ int i;
printf("Vamos a imprimir los 5 mensajes ms habituales de mis programas:\n\n");
for (i=0; i<5; i++)
printf("Mensaje %d: %s\n", i, msg(i));
}

char *msg(int cod)
{ static char txt[5][60] = {"Pulse una tecla para continuar...",
"Procesando datos, espere...",
"Espere, por favor...", //txt es un array local "static"
"Pulse ESC para abandonar...",
"Pulse una tecla para terminar."};
return txt[cod]; //retorna la direccin de comienzo de la fila del array dada por "cod"
}
17

3. PASO DE ESTRUCTURAS A FUNCIONES,
PUNTEROS A FUNCIONES

PASO DE ESTRUCTURAS Y UNIONES POR VALOR:
Una variable de tipo estructura o unin es pasada normalmente "por valor" como argumento
de entrada a una funcin, es decir, que la funcin recibe una copia de dicha variable y trabaja
con dicha copia.
La variable original no puede ser alcanzada desde dentro de la funcin.
Dentro de la funcin se utiliza el operador "punto" (.) para alcanzar los campos internos de la
variable estructura/unin de copia recibida (nombreVariable.nombreCampo).

PASO DE ESTRUCTURAS Y UNIONES POR REFERENCIA:
Se puede pasar una variable de tipo estructura o unin como argumento de entrada a una
funcin tambin "por referencia", es decir, que la funcin recibe la direccin de memoria
donde reside la variable original.
En la llamada a la funcin se utiliza el operador "direccin de" (&) para pasar la direccin de
la variable estructura/unin.
En la cabecera de la funcin se recibe dicha direccin en una variable de tipo puntero a
estructura/unin.
La variable original s puede ser alcanzada desde dentro de la funcin, a travs de dicho puntero.
Dentro de la funcin se utiliza el operador "flecha" (->) para alcanzar los campos internos de la
variable estructura/unin original (nombrePuntero->nombreCampo).

EJEMPLO-01.
// PASO DE ESTRUCTURAS POR VALOR Y POR REFERENCIA
// Programa para cargar desde teclado datos de consumo telefnico mensual
#include <stdio.h>
#define MAX 50 //n de elementos del array de estructuras
struct datotfno //declaracin del tipo estructura
{
int mes,anyo;
float euros;
};
void introducir(struct datotfno *tf); //declaraciones prototipo de funciones auxiliares
void imprimir(struct datotfno tf);

main()
{ int n,i;
struct datotfno tfno[MAX]; //array de estructuras
system("cls"); //borrar pantalla, en Unix es: system("clear");

printf("Introduzca los datos de consumo telefnico:\n\n");
for (i=0; i<MAX; i++) //bucle para introduccin de registros
{
printf("\nRegistro N %d:\n",i+1);
introducir(&tfno[i]); //estructura pasada por referencia
if (tfno[i].mes==0) break;
}
n=i; //guardamos el n mximo de registros intriducidos

printf("\nLos datos introducidos son:\n");
for (i=0; i<n; i++) //bucle de presentacin de datos en pantalla
{
imprimir(tfno[i]); //estructura pasada por valor
18

}
printf("\nPulse una tecla para terminar."); getch();
}

void introducir(struct datotfno *tf) //funcin para introduccin de datos por teclado
{
int scn;
do
{ printf("N de Mes (0=Terminar): "); scn=scanf("%d",&tf->mes); fflush(stdin); }
while (tf->mes<0 || tf->mes>12 || scn!=1);

if (tf->mes==0) return;

do
{ printf("Ao: "); scn=scanf("%d",&tf->anyo); fflush(stdin); }
while (tf->anyo<0 || scn!=1);

do
{ printf("Euros Telefono: "); scn=scanf("%f",&tf->euros); fflush(stdin); }
while (tf->euros<0 || scn!=1);
}
void imprimir(struct datotfno tf) //funcin para presentacin de datos en pantalla
{
printf("N de Mes: %d\tAo: %d\tConsumo: %.2f\n", tf.mes, tf.anyo, tf.euros);
}

PUNTEROS A FUNCIONES:
El nombre puro de una funcin (sin los parntesis) representa para el lenguaje C la direccin de
memoria de comienzo del cdigo mquina de dicha funcin.
Tal direccin puede ser almacenada en una variable de tipo "puntero a funcin", y a travs de
esta variable, se puede lanzar la ejecucin de la funcin apuntada y pasarle argumentos de
entrada.
La declaracin de una variable "puntero a funcin" se realiza as:
TipoDevuelto (*NombrePuntero)( );
donde TipoDevuelto es el tipo de dato que devuelve la funcin a la que apuntar este puntero, y
donde los parntesis extra alrededor del nombre del puntero y finales son imprescindibles.
Si entre los dos parntesis finales no se pone nada, indica que la funcin apuntada puede tener
cualquier cantidad y tipo de argumentos de entrada. Ejemplo:
int (*pf)( ); la funcin apuntada debe devolver un valor int, y puede tener cualquier
cantidad y tipo de argumentos de entrada.
Si entre dichos parntesis finales se ponen tipos de datos separados por comas, indicar que la
funcin apuntada debe tener forzosamente tales tipos y cantidad de argumentos de entrada, y en
ese orden. Esto quita flexibilidad y posibilidades a la variable puntero. Ejemplo:
int (*pf)(int, float); la funcin apuntada debe tener dos argumentos de entrada de tipos int
y float, y debe devolver un valor int.
Para cargar el puntero (una vez declarado) con la direccin de memoria de una funcin, basta
con asignarle su nombre puro. Ejemplo: pf = NombreFuncion;
Una vez cargado el puntero, para lanzar la ejecucin de la funcin apuntada y pasarle
argumentos de entrada caben dos sintaxis posibles:
n = pf(argum1, argum2); Equivale a: n = NombreFuncion(argum1, argum2);
n = (*pf)(argum1, argum2); Tambin equivale a lo mismo que antes
Evidentemente es ms cmoda la primera de las dos sintaxis.
Se puede declarar un array de punteros de este tipo, con la siguiente sintaxis:
TipoDevuelto (*NombrePuntero[NumElementos]) ( );
19


EJEMPLO-02.
// PUNTEROS A FUNCIONES
// Men de operaciones matemticas
#include <stdio.h>
#include <math.h>
#include <conio.h>

double cuadrado(double n1); //prototipos de las funciones auxiliares
double logaritmo(double n1);
double raiz(double n1);
int menu(void);

main()
{
double num=0; //operando
double resul; //resultado de la operacin
int op; //cdigo de operacin: 0=cuadrado, 1=logaritmo, 2=raiz

double (*pfunc[3])(); //array de punteros a funciones
pfunc[0] = cuadrado;
pfunc[1] = logaritmo;
pfunc[2] = raiz;
//tambin habra servido asi: double (*pfunc[3])()={cuadrado,logaritmo,raiz};

while (1)
{ op = menu(); //dibujar men y pedir por teclado la opcin deseada
if (op==-1) break; //salir del programa
printf("Introduzca numero: ");
scanf("%lf", &num);
resul = pfunc[op](num);
printf("El resultado es: %g\n", resul);
printf("Pulse una tecla..."); getch();
}
}

double cuadrado(double n) //funcin para obtener el cuadrado
{ return n*n; }

double logaritmo(double n) //funcin para obtener el logaritmo en base 10
{ return log10(n); }

double raiz(double n) //funcin para obtener la raiz cuadrada
{ return sqrt(n); }

int menu(void) //pintar menu de opciones en pantalla
{ int op=0;
do
{ system("cls"); //borrar pantalla, en Unix es: system("clear");
printf("\t1. Cuadrado.\n");
printf("\t2. Logaritmo.\n");
printf("\t3. Raz Cuadrada.\n");
printf("\t0. Salir.\n");
printf("\nSeleccione la operacin a realizar: ");
scanf("%d", &op); //pedir por teclado la opcin deseada
} while (op<0 || op>3);
return (op-1);
}
20

4. RECURSIVIDAD, ARGUMENTOS EN LNEA DE RDENES

FUNCIONES RECURSIVAS:
Una funcin "recursiva" es la que, en su ejecucin, se llama a s misma. Cada vez que se llama a
s misma, queda pendiente de terminar la ejecucin de la llamada anterior, y se incrementa en
una unidad el "nivel de recursin".
Toda funcin recursiva necesita tener algn argumento de entrada o variable interna que va
variando en cada nueva entrada a la funcin, hasta que se alcance cierta condicin de "fin de
recursin", momento a partir del cual comienzan a concluir las llamadas pendientes de la
funcin.
Cada nueva llamada a la funcin genera en la zona de memoria llamada "pila" ("stack") nuevas
instancias de las variables locales y argumentos de entrada de la funcin. Si se producen
demasiadas llamadas recursivas, esta "pila" puede llegar a llenarse, causando error de programa.
Esto es lo que sucede si la funcin est mal diseada y nunca se llega a la condicin de "fin de
recursin".
Se puede pedir al compilador (mediante parmetros de compilacin opcionales) que genere un
programa con un tamao de pila ms grande de lo normal, para soportar mejor la recursividad.
Una funcin recursiva ser buena si el nivel mximo de recursin (nmero mximo de veces
sucesivas que la funcin se llama a s misma hasta alcanzar la condicin de finalizacin) no es
elevado.
Conviene aplicar soluciones recursivas solamente cuando el problema a resolver se puede
definir claramente de forma recursiva. En los dems casos (la mayora), conviene utilizar
soluciones "iterativas" (mediante bucles), que no demandan tanta capacidad de memoria.

EJEMPLO-01:
// CALCULO RECURSIVO DEL FACTORIAL DE UN NUMERO
#include <stdio.h>
unsigned long factorial(int);

main()
{
int numero; //numero de entrada
unsigned long fact; //resultado del factorial

do
{
printf("Dame un numero entero positivo: ");
scanf("%d", &numero);
} while (numero<0 || numero>25); //el factorial de 26 supera la capacidad de un unsigned long

fact = factorial(numero);
printf("\nEl factorial de %d es %ld\n", numero, fact);
}

unsigned long factorial(int n)
{
if (n==0)
return 1;
else
return n*factorial(n-1);
}




21

EJEMPLO-02:
// CUENTA ASCENDENTE Y DESCENDENTE, RECURSIVA E ITERATIVA
#include <stdio.h>
void ascend(int);
void descend(int);

main()
{
int i, numero=0;
do
{ printf("Dame numero positivo:"); scanf("%d", &numero);
} while (numero<=0);

printf("\nCuenta ascendente recursiva: ");
ascend(numero);

printf("\nCuenta descendente recursiva: ");
descend(numero);

printf("\nCuenta ascendente iterativa: ");
for (i=0; i<=numero; i++) printf("%d ", i);

printf("\nCuenta descendente iterativa: ");
for (i=numero; i>=0; i--) printf("%d ", i);
}

void ascend(int n)
{ if (n>0) ascend(n-1);
printf("%d ", n);
}

void descend(int n)
{ printf("%d ", n);
if (n>0) descend(n-1);
}

EJEMPLO-03:
// BUSQUEDA BINARIA RECURSIVA
#include <stdio.h>
int busquedaB(float [], float, int, int);

main()
{
float lista[] = {3, 6.5, 9, 12, 15.3, 18, 21, 24.2, 27, 30, 33, 36, 40, 45, 49, 53, 56, 60};
int numelem = sizeof(lista)/sizeof(float);
int posicion;
float valor;
printf("Introduzca el valor a buscar: ");
scanf("%f", &valor);
posicion = busquedaB(lista, valor, 0, numelem-1);
if (posicion==-1) printf("\nEl valor %g no est en la lista.\n", valor);
else printf("\nLa posicion de %g es la %d\n", valor, posicion+1);
}

int busquedaB(float lista[], float val, int inf, int sup)
{
int mitad = (inf+sup)/2;
if (inf>=sup && lista[inf]!=val)
return -1;
if (lista[mitad]==val)
22

return mitad;
if (val>lista[mitad]) return busquedaB(lista, val, mitad+1, sup);
else return busquedaB(lista, val, inf, mitad-1);
}

ARGUMENTOS EN LNEA DE RDENES:
Un programa de lenguaje C puede recibir argumentos de entrada desde la lnea de comandos del
sistema operativo, al ponerse en marcha el programa. Para conseguirlo, la cabecera de la
funcin main() debe ser la siguiente:
int main(int argc, char *argv[]); o bien su equivalente: int main(int argc, char **argv);
Las variables argc y argv son suministradas desde el sistema operativo. La variable argc indica
el n de argumentos pasados desde la lnea de comandos, mas uno.
La variable argv representa un array de punteros a cadenas de caracteres suministradas por el
sistema operativo:
-- argv[0] apunta a una cadena que contiene el nombre completo del programa ejecutable,
incluyendo su extensin (.exe) y la ruta de directorios donde se encuentra.
-- argv[1] apunta a la cadena correspondiente al primer argumento pasado desde la lnea de
comandos (si existe).
-- argv[2] apunta a la cadena correspondiente al segundo argumento (si existe), Y
sucesivamente con los dems argumentos (no hay lmite a la cantidad de argumentos
pasados).
Tras el ltimo puntero argv[n] se incluye un puntero final de valor NULL.
Todos los argumentos de la lnea de comandos son pasados como cadenas de caracteres. Si
deseamos obtener su valor numrico, tendremos que aplicarles funciones conversoras tales
como atoi() (para convertir a un valor entero) o atof() (para convertir a un valor real con
decimales).
En la lnea de comandos, los distintos argumentos de entrada deben ir separados por un espacio
en blanco. Si no hay espacios se considera un nico argumento.
La funcin main() puede retornar un dato int al sistema operativo, con el que informar del xito
o no de la ejecucin del programa. Si main() no incluye sentencia return(n) o exit(n) para
devolver tal resultado, devolver automticamente un valor 0.

EJEMPLO-04:
// ARGUMENTOS EN LA LINEA DE ORDENES

void main(int argc, char *argv[])
{ int i;
if (argc<2)
printf("No hay argumentos para el programa %s\n", argv[0]);
else
{
printf("Nombre del programa: %s\n\n", argv[0]);
printf("Argumentos pasados: %d\n\n", argc-1);
for (i=1; i<argc; i++)
printf("Argumento %d: %s\tValor numrico entero: %d\n", i, argv[i], atoi(argv[i]));
}
}

EJEMPLO-05:
// ARGUMENTOS EN LINEA DE COMANDOS
// Men de operaciones matemticas con argumentos en lnea de comandos
// Argumento -oX => Indica que la opcin de men elegida es la X
// Argumento -nXXX => Indica que el operando elegido es el XXX

#include <stdio.h>
23

#include <math.h>
#include <conio.h>

double cuadrado(double n1); //prototipos de las funciones auxiliares
double logaritmo(double n1);
double raiz(double n1);
int menu(void);

main(int argc, char *argv[])
{
double num=0; //operando de entrada
int op; //cdigo de operacin: 0=cuadrado, 1=logaritmo, 2=raiz
double (*pfunc[3])()={cuadrado,logaritmo,raiz}; //array de punteros a funciones
int hayop=0; //indica si se ha introducido argumento -o
int haynum=0; //indica si se ha introducido argumento -n

for (i=1; i<argc; i++)
if (argv[i][0]=='-' && tolower(argv[i][1])=='o') //si el argumento i-simo es -o
{ op = atoi(argv[i]+2); //obtener el valor numrico entero despus del -o
if (op>0 && op<4) hayop = 1; }
else if (argv[i][0]=='-' && tolower(argv[i][1])=='n') //si el argumento i-simo es -n
{ num = atof(argv[i]+2); //obtener el valor numrico real despus del -n
if (num!=0) haynum = 1; }

do
{ if (!hayop)
{ op = menu(); //dibujar men y pedir por teclado la opcin deseada
if (op==-1) break; //salir del programa
}
if (!haynum)
{ printf("Introduzca numero (X=Finalizar): "); //Introducir el operando
if (scanf("%lf", &num)!=1) break; //salir del programa
}
printf("El resultado es: %g\n", pfunc[op](num)); //Presentar el resultado en pantalla
printf("Pulse una tecla..."); getch();
} while (!hayop || !haynum);
}

double cuadrado(double n) //funcin para obtener el cuadrado
{ return n*n; }
double logaritmo(double n) //funcin para obtener el logaritmo en base 10
{ return log10(n); }
double raiz(double n) //funcin para obtener la raiz cuadrada
{ return sqrt(n); }

int menu(void) //pintar menu de opciones en pantalla
{ int op=0;
do
{ system("cls"); //borrar pantalla, en Unix es: system("clear");
printf("\t1. Cuadrado.\n");
printf("\t2. Logaritmo.\n");
printf("\t3. Raz Cuadrada.\n");
printf("\t0. Finalizar programa.\n");
printf("\nSeleccione la operacin a realizar: ");
scanf("%d", &op); //pedir por teclado la opcin deseada
} while (op<0 || op>3);
return (op-1);
}

24

5. MANEJO BSICO DE FICHEROS

CONCEPTOS FUNDAMENTALES SOBRE FICHEROS:
Un fichero o archivo es una coleccin de informacin que almacenamos en un soporte
magntico u ptico, para poderla recuperar y manipular en cualquier momento.
Un fichero viene identificado por un nombre que suele tener dos partes: el nombre propio y la
extensin, ambas separadas por un punto.
Los ficheros se pueden agrupar en muchos tipos distintos: ficheros ejecutables (extensiones
.exe, .com y .bat en sistemas Windows), ficheros de texto (.txt, .doc, .lst), ficheros fuente (.c,
.h), ficheros de grficos (.jpg, .pdf, .tif, .bin), ficheros de aplicacin (.xls, .ppt), ficheros de
base de datos (.dat, .dbf), y muchos tipos ms.
Los ficheros de tipo "base de datos" almacenan su informacin en sucesivos "registros" de la
misma longitud, compuestos en su interior por "campos" de diversos tipos capaces de
almacenar informacin numrica y alfabtica.
Para manipular un fichero desde lenguaje C, primero hay que "abrirlo" (funcin fopen). Luego
podemos realizar dos tipos de operaciones: leer datos o grabar datos en el fichero. Finalmente
hay que "cerrar" el fichero (funcin fclose).
Al abrir un fichero, se crea un "apuntador de lectura/escritura" que indica la posicin del
prximo byte del fichero que se va a acceder (leer o grabar). Una vez ledo o grabado un byte, el
apuntador se mueve automticamente al siguiente byte del fichero. Existen funciones para
mover este apuntador a cualquier sitio del fichero donde se desee leer o grabar.
Todo fichero tiene una "marca de fin de fichero" despus del ltimo byte de contenido del
mismo, marca que no ocupa espacio en el disco (es reconocida por el sistema operativo).
Una vez abierto un fichero, puede ser accedido en dos modos principales: con acceso
"secuencial" (se acceden los bytes uno tras otro consecutivamente desde el principio del
fichero) o con acceso "aleatorio" (se acceden los bytes en cualquier orden no consecutivo).

FLUJO ASOCIADO A UN FICHERO:
Al abrir un fichero, se crean en la memoria una serie de variables que sirven para facilitar su
manipulacin y hacerla ms independiente del tipo de dispositivo fsico utilizado. Este conjunto
de variables recibe el nombre de "flujo" de control del fichero, y acta como intermediario
entre el programa C y el disco magntico donde reside el fichero, al realizar operaciones de
lectura y grabacin de datos
En lenguaje C, un flujo consta principalmente de una variable estructura (struct) de tipo FILE
de pocos bytes, y una zona ms amplia de memoria intermedia llamada "buffer" (suele ser de
512 bytes, aunque este tamao puede ser variado). Con la ayuda del buffer se acelera
notablemente el acceso a posiciones cercanas del disco magntico, y la ejecucin de los
programas es ms rpida.

EJEMPLO-01:
// CAMPOS DE UNA ESTRUCTURA TIPO FILE
// declaraciones incluidas en el fichero stdio.h
struct _iobuf
{
char *_ptr; //Puntero a la posicin del buffer donde se realizar la prxima lectura/escritura
int _cnt; //Contador de bytes que quedan por leer o grabar en el buffer
char *_base; //Direccin de inicio del buffer
char _flag; //Datos de modo de acceso y errores producidos
char _file;
int _charbuf;
int _bufsiz; //Tamao del buffer en n de bytes
25

char *_tmpfname;
};
typedef struct _iobuf FILE;

// PUNTEROS A FICHEROS ESTANDAR DEL SISTEMA
extern FILE _iob[];
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
#define stdaux (&_iob[3])
#define stdprn (&_iob[4])

Al comenzar la ejecucin de todo programa, el sistema operativo abre automticamente algunos
flujos de control de ficheros estandar: stdin (fichero estandar de entrada, el teclado), stdout
(fichero estandar de salida, la pantalla) y stderr (fichero estandar para emitir mensajes de error,
la pantalla). Puede haber ms ficheros estandar, dependiendo del sistema operativo: stdaux
(puerto estandar serie), stdprn (puerto estandar paralelo).

APERTURA DE UN FICHERO:
La funcin principal para abrir un fichero en C es fopen. Su prototipo es:
FILE *fopen(char *NombreFichero, char *ModoApertura);
fopen necesita dos argumentos de entrada de tipo cadena de caracteres: el nombre del fichero
(incluyendo ruta de directorios si es necesario) y el "modo de apertura" del fichero.
fopen devuelve la direccin de memoria del flujo de control del fichero recin abierto, direccin
que debe ser guardada en una variable de tipo "puntero a FILE". Si hay error de apertura, se
devuelve el puntero NULL.
Los "modos de apertura" posibles son los siguientes:
-- "r" Apertura slo para leer. Si el fichero no existe, se produce error de apertura (se
devuelve NULL). El apuntador de L/E se coloca al principio del fichero.
-- "w" Apertura slo para grabar. Si el fichero no existe se crea. Si el fichero existe, se
destruye para comenzar de nuevo. El apuntador de L/E queda al principio del fichero.
-- "a" Apertura para grabar al final del fichero existente. Si el fichero no existe se crea. El
apuntador de L/E se coloca al final del fichero.
-- "r+" Igual que el modo "r", pero el fichero se puede leer y grabar, una vez abierto.
-- "w+" Igual que el modo "w", pero el fichero se puede leer y grabar, una vez abierto.
-- "a+" Igual que el modo "a", pero el fichero se puede leer y grabar, una vez abierto.
En sistemas Windows (no necesario en Unix), a estos modos se puede aadir la letra "t" o "b"
(por ejemplo, w+b, at,). La letra "t" (modo texto) hace que el carcter "nueva linea" (\n)
se grabe y se lea en el fichero mediante la pareja de bytes de valor 13+10, comportamiento
necesario para ficheros de texto. La letra "b" (modo binario) desactiva esa conversin, hace que
el carcter "nueva linea" se grabe y lea en el fichero mediante un nico byte de valor 10,
comportamiento necesario para ficheros no-texto. Si no se pone nada, se entiende modo texto.

CIERRE DE UN FICHERO:
La funcin para cerrar un fichero una vez utilizado es fclose. Su prototipo es:
int fclose(FILE *puntero);
fclose recibe como argumento el puntero a FILE de control del fichero a cerrar. Devuelve el
valor entero 0 si se ejecuta correctamente, y el valor EOF (-1) si ha habido error.
fclose termina de grabar en disco los datos pendientes que estaban en el buffer, limpia el buffer
y elimina de memoria todas las variables del flujo de control del fichero, liberando recursos para
que otros ficheros puedan ser abiertos.
26

Si no se cierra un fichero con fclose, se cerrar automticamente al terminar la ejecucin del
programa.

EJEMPLO-02:
// FUNCIONES FOPEN Y FCLOSE
#include <stdio.h>

main()
{ FILE *pf;
pf = fopen("C:\datos.txt", "w+");
if (pf==NULL)
{ printf("ERROR de apertura del fichero."); exit(1); }

//... operaciones de lectura y grabacion del fichero

fclose(pf);
}

DETECCIN DE ERRORES DE ACCESO A UN FICHERO:
Una vez abierto un fichero, si en las operaciones de lectura y grabacin de datos se produce un
error, puede ser detectado mediante la funcin ferror, cuyo prototipo es:
int ferror(FILE *puntero);
ferror devuelve un valor 0 si no ha ocurrido error, y un valor distinto de cero si se produjo
error. Se utiliza como valor cierto/falso: if (ferror(pf)) .
Una vez producido un error, ferror permanece a valor cierto indefinidamente hasta que se cierra
el fichero o hasta que se ejecuta la instruccin clearerr o rewind.

DETECCIN DE FIN DE FICHERO:
Una vez abierto un fichero, si en una operacin de lectura de datos se alcanza y se lee la "marca
de fin de fichero", se activa la funcin feof, cuyo prototipo es:
int feof(FILE *puntero);
feof devuelve un valor 0 si no se ha ledo el final del fichero, y un valor distinto de cero si se ha
ledo. Se utiliza como valor cierto/falso: if (feof(pf)) .
Una vez activada feof, permanece a valor cierto indefinidamente hasta que se cierra el fichero o
hasta que se ejecuta la instruccin clearerr o rewind.

ANULACIN DE ERRORES DE ACCESO A FICHERO:
Mientras estn activos alguno de los indicadores ferror o feof, cualquier intento de operacin
de lectura o de grabacin de datos en el fichero ser fallido.
Para desactivar los indicadores de error de fichero (ferror) y de fin de fichero (feof) se utiliza la
funcin clearerr, cuyo prototipo es: void clearerr(FILE *puntero);

EJEMPLO-03:
// FUNCIONES FERROR, FEOF Y CLEARERR
#include <stdio.h>

main()
{ FILE *pf;
pf = fopen("C:\datos.txt", "r");
if (pf==NULL)
{ printf("ERROR: fichero no existe."); exit(1); }

fprintf(pf, "Hola"); //operacin de grabacin incompatible con el modo "r", producir error
if (ferror(pf))
27

{ printf("ERROR: dato no puede grabarse.");
clearerr(pf);
}

while (!feof(pf) && !ferror(pf)) //bucle de lectura secuencial del fichero
{
//Operacion de lectura de un dato
}

if (feof(pf)) printf("Fin de fichero alcanzado.");
else if (ferror(pf)) printf("Error de acceso al fichero.");

fclose(pf);
}

IMPRIMIR EN PANTALLA MENSAJES DE ERROR:
Para obtener en pantalla mayor informacin sobre el error que se ha producido, utilizamos la
funcin perror, cuyo prototipo es: void perror(char *mensaje);
perror recibe como argumento una cadena de caracteres, y la imprime en la pantalla (fichero
estandar para mensajes de error, stderr) seguida de "dos puntos" (:) y seguida del mensaje de
error dado por el sistema operativo (en ingls), terminando con un avance de lnea (\n).

EJEMPLO-04:
// FUNCION PERROR
#include <stdio.h>

main()
{ FILE *pf;
pf = fopen("C:\datos.txt", "r");
if (pf==NULL)
{ perror("ERROR en datos.txt"); exit(1); }

fprintf(pf, "Hola"); //operacin de grabacin incompatible con el modo "r", producir error
if (ferror(pf))
{ perror("ERROR al grabar");
clearerr(pf);
}

while (!feof(pf) && !ferror(pf)) //bucle de lectura secuencial del fichero
{
//Operacion de lectura de un dato
}

if (feof(pf))
perror("Fin de fichero");
else if (ferror(pf))
perror("ERROR");

fclose(pf);
}

POSICIN DEL APUNTADOR DE LECTURA/ESCRITURA:
Todo fichero abierto tiene un "apuntador de L/E" que indica el byte en el cual se realizar la
prxima operacin de lectura o grabacin en el fichero. Cuando tal operacin es realizada, el
apuntador es empujado automticamente hacia delante para apuntar al siguiente byte que ser
ledo o grabado.
28

Para conocer en qu posicin se encuentra actualmente el apuntador de L/E utilizamos la
funcin ftell, cuyo prototipo es: long ftell(FILE *puntero);
Esta funcin devuelve el nmero de byte donde est el apuntador, contando desde el principio
del fichero, o bien el valor -1 si hay un error. La posicin del primer byte del fichero es la n 0.
Para mover el apuntador de L/E a la posicin deseada del fichero, donde se realizar la siguiente
operacin de lectura/grabacin, utilizamos la funcin fseek, cuyo prototipo es:
int fseek(FILE *puntero, long desplaz, int posinicio);
La funcin fseek mueve el apuntador a una posicin desplazada "desplaz" bytes desde la
posicin de inicio "posinicio". Esta posicin de inicio puede tener 3 valores posibles, dados por
3 constantes simblicas (en maysculas):
-- SEEK_SET (valor 0): Desde el principio del fichero.
-- SEEK_CUR (valor 1): Desde la posicin actual del apuntador de L/E.
-- SEEK_END (valor 2): Desde el final del fichero.
fseek devuelve 0 si se ejecuta correctamente, y distinto de 0 si hay error.
Para mover el apuntador de L/E al comienzo del fichero tenemos la funcin rewind, cuyo
prototipo es: void rewind(FILE *puntero);
rewind(pf) hace lo mismo que fseek(pf, 0, SEEK_SET) pero adems desactiva los
indicadores de error ferror y feof, cosa que no hace la funcin fseek.

EJEMPLO-05:
// FUNCIONES PARA POSICIONAMIENTO DEL APUNTADOR DE L/E
#include <stdio.h>

main()
{
FILE *pf;
pf = fopen("Prog06-Ejemplo06.c", "r");

fseek(pf, 0, SEEK_END); //mueve el apuntador al final del fichero, sobre la marca de "fin de
fichero"
//estando al final del fichero, ftell nos dira el tamao del mismo
printf("El tamao del fichero es: %ld", ftell(pf));

fseek(pf, -10, SEEK_CUR); //retrocede el apuntador 10 bytes desde la posicin actual
fseek(pf, 10, SEEK_SET); //mueve el apuntador al byte n 10 del fichero (el primer byte es el 0)
fseek(pf, 15, SEEK_CUR); //mueve el apuntador al byte n 25 del fichero (avanza 15 bytes)
fseek(pf, 0, SEEK_SET); //mueve el apuntador al comienzo del fichero
rewind(pf); //igual que el anterior, pero poniendo a cero ferror y feof
fseek(pf, -10, SEEK_SET); //no es posible, mueve el apuntador al comienzo del fichero, no causa error
fseek(pf, 10, SEEK_END); //no es posible, mueve el apuntador al final del fichero, no causa error
fclose(pf);
}

CONTROL DEL BUFFER DEL FICHERO:
La funcin fflush graba en el fichero especificado el contenido del buffer que estaba pendiente
de ser grabado, y vaca el buffer. Si el fichero esta abierto nicamente para leer, el buffer
simplemente se vaca. Su prototipo es: int fflush(FILE *pf);
La funcin devuelve 0 si se ejecut correctamente, y un EOF (-1) en caso contrario.
29

6. LECTURA/GRABACIN DE DATOS EN FICHEROS

LECTURA Y GRABACIN DE DATOS CARCTER A CARCTER:
Una vez abierto un fichero, sus datos pueden ser grabados y ledos byte a byte, con las
funciones fputc y fgetc.
La funcin fputc tiene como prototipo: int fputc(int car, FILE *puntero);
Esta funcin graba el primer byte del argumento car en la posicin actual del apuntador de L/E,
avanza el apuntador al siguiente byte y devuelve como resultado el carcter escrito, o bien un
valor EOF (-1) si ha ocurrido un error.
La funcin fgetc tiene como prototipo: int fgetc(FILE *puntero);
Esta funcin lee el byte que se encuentra en la posicin actual del apuntador de L/E, avanza el
apuntador al siguiente byte y devuelve como resultado el carcter ledo, o bien un valor EOF (-
1) si ha ocurrido un error.
En ambas funciones no conviene usar el dato devuelto EOF como detector de error en la
operacin, ya que el dato grabado o ledo correctamente puede ser de valor -1 (EOF). Hay que
utilizar la funcin ferror o feof para detectar los posibles errores.

EJEMPLO-01:
// FUNCIONES FPUTC Y FGETC
#include <stdio.h>

main()
{ FILE *pf;
char cad[200], cad2[200];
int i=0;

printf("Introduce un texto a grabar:\n"); gets(cad);

if ((pf=fopen("texto", "w+"))==NULL)
{ perror("ERROR al abrir el fichero"); exit(1); }

while (cad[i]!=0 && ferror(pf)==0) //sirve tambin: while (cad[i] && !ferror(pf))
{ fputc(cad[i], pf); i++; }
if (ferror(pf))
perror("ERROR al grabar en el fichero");

rewind(pf); i=0; //apuntador de L/E al principio del fichero y desactivacin de ferror

while (!feof(pf) && !ferror(pf))
{ cad2[i] = fgetc(pf); i++; }
cad2[i-1]=0; //caracter nulo de final de cadena
if (ferror(pf))
perror("ERROR al leer el fichero");

printf("El texto grabado es: %s", cad2);
fclose(pf);
}

GRABACIN DE CADENAS DE CARACTERES:
Una vez abierto un fichero, sus datos pueden ser grabados y ledos en bloques de cadenas de
caracteres, con las funciones fputs y fgets.
La funcin fputs tiene como prototipo: int fputs(char *cad, FILE *puntero);
Esta funcin graba los caracteres que hay en la cadena cad en la posicin actual del apuntador
de L/E (sin incluir el carcter nulo de final de cadena), avanza el apuntador de L/E en el fichero
30

y devuelve como resultado un nmero no negativo, o bien un valor EOF (-1) si ha ocurrido un
error.
Para recuperar posteriormente las cadenas as grabadas, conviene grabar el carcter "nueva
lnea" (\n) despus de cada cadena, para que acte como separador:
while (gets(cadena)!=NULL) { fputs(cadena, pf); fputc('\n', pf); }

LECTURA DE CADENAS DE CARACTERES:
La funcin fgets tiene como prototipo: char *fgets(char *cad, int nmax, FILE *puntero);
Esta funcin lee desde el fichero la cadena de caracteres que se encuentra en la posicin actual
del apuntador de L/E, la almacena en la variable de memoria cad (aportando el carcter nulo
final \0), avanza el apuntador de L/E en el fichero y devuelve como resultado un puntero a la
variable de memoria recin leda, o bien un valor NULL (puntero 0) si ha ocurrido un error o se
ha ledo la marca de fin de fichero.
El argumento nmax coincidir habitualmente con el tamao declarado para el array de char de
la cadena cad.
La cadena leda en el fichero termina ante 3 posibles situaciones: cuando se lee el carcter
"nueva lnea" (\n, este carcter pasa a la variable de memoria), o bien cuando se lee una
cantidad de bytes dada por nmax-1 (hay que dejar un byte para el carcter \0), o bien cuando se
lee la marca de fin de fichero.
La funcin de ficheros fgets reemplaza ventajosamente a la funcin gets de lectura de cadenas
desde teclado, ya que permite limitar la cantidad mxima de caracteres asignados a la variable
de memoria y evitar su desbordamiento:
char cad[10];
gets(cad); Permite leer ms de 10 caracteres desde el teclado sobre la variable cad
fgets(cad, 10, stdin); Slo permite leer 9 caracteres desde el fichero teclado (stdin)

EJEMPLO-02:
// FUNCIONES FPUTS Y FGETS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 200

main()
{ FILE *pf;
char cad[N], nomfi[13];

printf("Introduce nombre del fichero a grabar: ");
fgets(nomfi, 13, stdin); //leer hasta 12 caracteres
fflush(stdin); //borrar posibles caracteres de ms escritos
if (nomfi[strlen(nomfi)-1]=='\n') //eliminar posible caracter final \n
nomfi[strlen(nomfi)-1] = 0;

if ((pf=fopen(nomfi, "w+"))==NULL) //abrir fichero
{ perror("ERROR al abrir el fichero %s", nomfi); exit(1); }

printf("Introduzca textos terminados con ENTER; pulse CTRL+Z para terminar\n\n");
while (fgets(cad, N, stdin)!=NULL) //leer desde el teclado, mximo N-1 caracteres
{ fputs(cad, pf); fflush(stdin); //grabar en el fichero y borrar buffer del teclado
if (cad[strlen(cad)-1]!='\n') fputc('\n',pf); //grabar \n si falta
if (ferror(pf)) { perror("Error al grabar en el fichero"); break; }
}

rewind(pf); //apuntador de L/E al principio del fichero y desactivacin de ferror
31


printf("\nLos textos ledos son:\n\n");
while (fgets(cad, N, pf)!=NULL) //leer desde el fichero hasta el fin de fichero
printf("%s", cad);
if (ferror(pf)) perror("ERROR al leer en el fichero");

fclose(pf);
}

LECTURA Y GRABACIN DE DATOS CON FORMATO:
Una vez abierto un fichero, sus datos pueden ser grabados y ledos con los formatos utilizados
por las funciones printf y scanf, mediante las funciones fprintf y fscanf.
La funcin fprintf tiene la siguiente declaracin prototipo:
int fprintf(FILE *pf, char *formato, );
Acta de forma idntica a la funcin printf, imprimiendo datos de varios tipos y con diversos
formatos, pero en lugar de dirigirlos hacia la pantalla los graba en el fichero especificado por el
puntero pf, en el lugar donde se encuentre su apuntador de L/E. Despus de la cadena
"formato" pueden venir ms variables y expresiones, cuyos valores se grabarn en el fichero.
La funcin fprintf devuelve un entero que es la cantidad de bytes grabados en el fichero.
Si el puntero a fichero es stdout (la pantalla), fprintf es equivalente a printf:
printf("n = %d", n); equivale a fprintf(stdout, "n = %d", n);
La funcin fscanf tiene la siguiente declaracin prototipo:
int fscanf(FILE *pf, char *formato, );
Acta de forma idntica a la funcin scanf, leyendo desde el fichero (ya no desde el teclado)
datos de varios tipos y con diversos formatos, y guardndolos en las variables de memoria
especificadas, que son pasadas por referencia (con &) despus de la cadena "formato". La
funcin fscanf devuelve un entero que es la cantidad de datos correctamente ledos y asignados
a variables de memoria.
Si el puntero a fichero es stdin (el tyeclado), fscanf es equivalente a scanf:
scanf("%d %d", &n, &m); equivale a fscanf(stdin, "%d %d", &n, &m);

EJEMPLO-03:
// FUNCIONES FPRINTF Y FSCANF

#include <stdio.h>

main()
{ FILE *pf;
char cad[200];
int i=1;
long a=99999;
float b=3.14;

if ((pf=fopen("datos", "r"))==NULL)
{ //El fichero no existe, lo abrimos en modo escritura
if ((pf=fopen("datos", "w"))==NULL)
{ perror("ERROR al abrir el fichero"); exit(1); }
fprintf(pf, "Linea %d: %7ld %9.2f\n", i, a, b);
fprintf(pf, "Linea %d: %7ld %9.2f\n", ++i, a/2, b*2);
printf("El fichero no existe, lo creamos. Ejecute de nuevo el programa.");
}
else
{ //El fichero ya existe, queda abierto en modo lectura
printf("Contenido de texto del fichero:\n");
fgets(cad, 200, pf); printf("%s", cad);
32

fgets(cad, 200, pf); printf("%s", cad);
rewind(pf);
printf("\nVariables leidas con fscanf:\n");
fscanf(pf, "%s %d: %ld %f", cad, &i, &a, &b);
printf("cad=%s i=%d a=%ld b=%.2f\n", cad, i, a, b);
fscanf(pf, "%s %d: %ld %f", cad, &i, &a, &b);
printf("cad=%s i=%d a=%ld b=%.2f\n", cad, i, a, b);
}
fclose(pf);
}

/*
RESULTADO EN PANTALLA:

El fichero no existe, lo creamos. Ejecute de nuevo el programa.

Contenido de texto del fichero:
Linea 1: 99999 3.14
Linea 2: 49999 6.28

Variables leidas con fscanf:
cad=Linea i=1 a=99999 b=3.14
cad=Linea i=2 a=49999 b=6.28
*/

GRABACIN DE REGISTROS:
Una vez abierto un fichero, sus datos pueden ser grabados y ledos en bloques de tipo "registro"
(variables de tipo struct formadas por varios campos) mediante las funciones fwrite y fread.
La funcin fwrite tiene la siguiente declaracin prototipo:
typedef unsigned int size_t;
size_t fwrite(void *dirinicio, size_t tamelem, size_t numelems; FILE *pf);
La funcin fwrite est pensada para transferir de memoria a fichero un array de elementos
(tantos como "numelems") y cada elemento de "tamelem" bytes de tamao. Realiza una copia
exacta de la zona de memoria que comienza en la direccin "dirinicio" y que ocupa un n de
bytes dado por el producto "tamelem x numelems", sobre el fichero "pf" en el lugar donde se
encuentra actualmente su apuntador de L/E.
Esta funcin fwrite puede sustituir a algunas de las funciones de grabacin vistas hasta ahora:
char car, cadena[40];
fputc(car, pf); equivale a: fwrite(&car, 1, 1, pf);
fputs(cadena, pf); equivale a: fwrite(cadena, strlen(cadena), 1, pf);

LECTURA DE REGISTROS:
La funcin fread es muy similar a fwrite, y tiene la siguiente declaracin prototipo:
size_t fread(void *dirinicio, size_t tamelem, size_t numelems; FILE *pf);
La funcin fread est pensada para transferir de fichero a memoria un array de elementos
(tantos como "numelems") y cada elemento de "tamelem" bytes de tamao. Realiza una copia
exacta de los bytes del fichero "pf" (donde se encuentra actualmente su apuntador de L/E)
sobre la zona de memoria que comienza en la direccin "dirinicio" y que ocupa un n de bytes
dado por el producto "tamelem x numelems".
Esta funcin fread puede sustituir a algunas de las funciones de lectura vistas hasta ahora:
char car, cadena[40];
car = fgetc(pf); equivale a: fread(&car, 1, 1, pf);
fgets(cadena, sizeof(cadena), pf); equivale a: fread(cadena, sizeof(cadena)-1, 1, pf);

33

EJEMPLO-04:
// FUNCIONES FWRITE Y FREAD
// Programa para cargar desde teclado datos de consumo telefnico mensual
#include <stdio.h>
#define MAX 50 //n de elementos del array de estructuras
struct datotfno //declaracin del tipo estructura
{ int mes,anyo;
float euros;
};
void introducir(struct datotfno *tf); //declaraciones prototipo de funciones auxiliares
void imprimir(struct datotfno tf);

main()
{ int n=0,i;
FILE *pf;
struct datotfno tfno[MAX]; //array de estructuras
for (i=0; i<MAX; i++)
tfno[i].mes = tfno[i].anyo = tfno[i].euros = 0; //poner a 0 todos los campos del array

if ((pf=fopen("datostf.dat","r+"))==NULL)
//El fichero no existe, lo creamos
if ((pf=fopen("datostf.dat", "w+"))==NULL) { printf("ERROR de apertura"); exit(1); }
else
{ //El fichero existe, leemos sus registros
fread(tfno, sizeof(struct datotfno), MAX, pf); //leer todos los registros en el fichero
for (n=0; n<MAX && tfno[n].mes!=0; n++); //obtener numero n de registros introducidos
}

system("cls"); //borrar pantalla, en Unix es: system("clear");
printf("Introduzca los datos de consumo telefnico:\n\n")
for (i=n; i<MAX; i++) //bucle para introduccin de registros
{ printf("\nRegistro N %d:\n",i+1);
introducir(&tfno[i]); //estructura pasada por referencia
if (tfno[i].mes==0) break;
}
n=i; //guardamos el n mximo de registros introducidos
printf("\nLos datos introducidos son:\n");
for (i=0; i<n; i++) //bucle de presentacin de datos en pantalla
imprimir(tfno[i]); //estructura pasada por valor
printf("\nPulse una tecla para terminar."); getch();
rewind(pf);
fwrite(tfno, sizeof(struct datotfno), MAX, pf); //grabar todos los registros en el fichero
fclose(pf);
}

void introducir(struct datotfno *tf) //funcin para introduccin de datos por teclado
{ int scn;
do
{ printf("N de Mes (0=Terminar): "); scn=scanf("%d",&tf->mes); fflush(stdin); }
while (tf->mes<0 || tf->mes>12 || scn!=1);
if (tf->mes==0) return;
do
{ printf("Ao: "); scn=scanf("%d",&tf->anyo); fflush(stdin); }
while (tf->anyo<0 || scn!=1);
do
{ printf("Euros Telefono: "); scn=scanf("%f",&tf->euros); fflush(stdin); }
while (tf->euros<0 || scn!=1);
}
void imprimir(struct datotfno tf) //funcin para presentacin de datos en pantalla
{ printf("N de Mes: %d\tAo: %d\tConsumo: %.2f\n", tf.mes, tf.anyo, tf.euros); }
34

7. MS SOBRE FICHEROS

ELIMINACION DE FICHEROS:
Para eliminar un nico fichero existe la funcin remove, cuyo prototipo es:
int remove(char *nombre);
Recibe como argumento una cadena de caracteres con el nombre del fichero a eliminar (puede
incluir la ruta de directorios donde se encuentra). Devuelve 0 si la operacin tuvo xito, y
distinto de 0 en caso contrario (quiz porque el fichero no exista). El fichero a eliminar no debe
estar abierto con fopen.
Para eliminar varios ficheros utilizando los caracteres comodn del sistema operativo, se utiliza
la funcin system, que invocar al comando de eliminacin de ficheros "delete" en sistemas
Windows o "rm" en sistemas Unix. Ejemplo: system ("delete c:\*.exe");

CAMBIAR EL NOMBRE DE FICHEROS:
Para renombrar un nico fichero existe la funcin rename, cuyo prototipo es:
int rename(char *antiguo, char *nuevo);
Recibe como argumento dos cadenas de caracteres con el nombre del fichero antiguo (puede
incluir la ruta de directorios donde se encuentra) y el nuevo nombre deseado. Devuelve 0 si la
operacin tuvo xito, y distinto de 0 en caso contrario. El fichero a renombrar no debe estar
abierto con fopen.
Para renombrar varios ficheros utilizando los caracteres comodn del sistema operativo, se
utiliza la funcin system, que invocar al comando de cambio de nombre de ficheros "rename"
en sistemas Windows o comando "mv" en Unix. Ejemplo: system ("rename c:\*.txt *.dat");

COPIAR FICHEROS:
Para copiar un fichero no existe funcin estandar de C. Para copiar uno o varios ficheros
utilizando los caracteres comodn del sistema operativo, se utiliza la funcin system, que
invocar al comando de copia de ficheros "copy" en sistemas Windows o comando "cp" en
Unix. Ejemplo: system ("copy c:\fich.txt c:\fich2.txt >nul");

EJEMPLO-01.
// BORRAR, RENOMBRAR Y COPIAR FICHEROS
#include <stdio.h>
int existefich(char *cad);
int copiafich(char *orig, char *dest);
int renamefich(char *orig, char *nuevo);
int borrafich(char *nomfic);

main()
{ char nom[20];

printf("Nombre del fichero a procesar: "); gets(nom); //debe ser un fichero existente
if (!nom[0]) exit(1); //si pulsamos Enter sin escribir nada
if (!existefich(nom)) { printf("Fichero %s no encontrado", nom); getch(); exit(1); }

if (copiafich(nom, "fich2.txt")) { printf("Fichero %s copiado\n", nom); getch(); }
else { printf("No se pudo copiar el fichero %s", nom); getch(); exit(1); }

if (rename(nom, "fich0.txt")==0) { printf("Fichero %s renombrado\n", nom); getch(); }
//equivale a: if (renamefich(nom, "fich0.txt")) { ...... }
else { printf("No se pudo renombrar el fichero %s", nom); getch(); exit(1); }

if (remove("fich0.txt")==0) { printf("Fichero fich0 eliminado\n"); getch(); }
//equivale a: if (borrafich("fich0.txt")) { ...... }
35

else { printf("No se pudo eliminar el fichero fich0"); getch(); exit(1); }

if (rename("fich2.txt", nom)==0) { printf("Fichero fich2 renombrado\n"); getch(); }
else { printf("No se pudo renombrar el fichero fich2"); getch(); exit(1); }
}

int existefich(char *cad) //devuelve 1 si el fichero "cad" indicado existe, 0 en caso contrario
{ FILE *pf;
if ((pf=fopen(cad, "r"))==NULL) return 0;
else { fclose(pf); return 1; }
}
int copiafich(char *orig, char *dest) //copia el fichero "orig" en "dest", devuelve 1 si hay exito
{ char *p;
if (!existefich(orig)) return 0; //si no existe el fichero origen, abandonamos
p = (char *)malloc(12+strlen(orig)+strlen(dest)); //reserva dinmica de memoria
if (p==NULL) { printf("No hay memoria"); return 0; }
strcpy(p, "copy "); strcat(p, orig); strcat(p, " "); //composicin del comando a ejecutar
strcat(p, dest); strcat(p, " >nul");
system(p); //El comando queda as: copy fichero.dat fich2.txt >nul
//Para Unix debera ser: cp fichero.dat fich2.txt
free(p); //liberar la memoria reservada
return existefich(dest); //comprobamos si existe el fichero copia
}
int renamefich(char *orig, char *nuevo) //renombra fichero "orig" con el nombre "nuevo",
//devuelve 1 si bien (para Windows)
{ char *p;
if (!existefich(orig)) return 0; //si no existe el fichero origen, abandonamos
p = (char *)malloc(9+strlen(orig)+strlen(nuevo)); //reserva dinmica de memoria
if (p==NULL) { printf("No hay memoria"); return 0; }
strcpy(p, "rename "); strcat(p, orig); strcat(p, " "); strcat(p, nuevo); //composicin del comando
system(p); //El comando queda as: rename fich1.txt fich2.txt
//Para Unix debera ser: mv fich1.txt fich2.txt
free(p); //liberar la memoria reservada
return (!existefich(orig) && existefich(nuevo)); //comprobamos la existencia de los ficheros
}
int borrafich(char *nomfic) //borra el fichero "nomfic", devuelve 1 si hay xito
{ char *p;
if (!existefich(nomfic)) return 0; //si no existe el fichero origen, abandonamos
p = (char *)malloc(8+strlen(nomfic)); //reserva dinmica de memoria
if (p==NULL) { printf("No hay memoria"); return 0; }
strcpy(p, "delete "); strcat(p, nomfic); //composicin del comando
system(p); //El comando queda as: delete fich.txt
//Para Unix debera ser: rm fich.txt
free(p); //liberar la memoria reservada
return !existefich(nomfic); //comprobamos si an existe el fichero borrado
}

ESCRIBIR DATOS EN LA IMPRESORA:
Para enviar datos hacia la impresora hay que usar como flujo de salida el fichero estandar
"stdprn" correspondiente al "puerto paralelo" de la impresora (si el ordenador dispone de l), o
bien crear un flujo de salida ("w") dirigido hacia el dispositivo al que est conectado la
impresora, usando el nombre de dispositivo utilizado por el sistema operativo:
-- En Windows: "lpt1", "lpt2", "prn", "com1", "com2", "com3", etc.
-- En Unix: "/dev/lp0", "/dev/lp1", etc.
Ejemplo: FILE *pfs;
if ((pfs = fopen("lpt1", "w"))==NULL) printf("Impresora no disponible");
else { fprintf(pfs, "Este texto se escribe en la impresora \f"); fclose(pfs); }

36

EJEMPLO-02:
// USO DE LA IMPRESORA
// Impresin de un fichero de texto

#include <stdio.h>

main()
{ FILE *pfe, *pfs;
char cad[20], car, com[30];

if ((pfs=fopen("lpt1", "w"))==NULL) //abrimos el dispositivo "lpt1" (impresora puerto paralelo)
{ perror("Impresora no disponible"); exit(1); }

printf("Nombre del fichero a imprimir: "); gets(cad);
if (cad[0]==0) exit(1); //Si pulsamos Enter sin escribir nada

if ((pfe=fopen(cad, "r"))==NULL) //abrimos el fichero a imprimir
{ perror("Fichero no encontrado"); fclose(pfs); exit(1); }

while (car=fgetc(pfe), !feof(pfe) && !ferror(pfe)) //leemos el fichero byte a byte
fputc(car, pfs); //imprimimos en la impresora byte a byte
if (ferror(pfe))
perror("ERROR al leer el fichero");
else
printf("El fichero ha sido enviado a la impresora\n");
fclose(pfe); fclose(pfs); getch();

//Otra forma de imprimir un fichero utilizando la funcin system
strcpy(com, "copy "); strcat(com, cad); strcat(com," lpt1 >nul"); //componemos el comando
system(com); //El comando queda as: copy fichero.txt lpt1 >nul
printf("\nEl fichero se ha imprimido de nuevo");
getch();
}

VER DIRECTORIOS DE FICHEROS:
No existe una funcin estandar de C para obtener informacin de los directorios y ficheros
existentes en los discos, dado que los diversos sistemas operativos los manipulan de diferentes
maneras.
Una forma de conseguirlo es utilizar la funcin system para invocar al comando de
visualizacin de directorios del sistema operativo utilizado (comando "dir" en Windows o "ls"
en Unix) y redirigir la salida de pantalla (>) hacia un fichero de texto que luego abriremos para
leerlo y procesarlo. Ejemplo: system("dir *.c > dir.txt");

EJEMPLO-03:
// VER DIRECTORIOS EN PANTALLA
#include <stdio.h>

main()
{ char nom[30], *p, lin[100];
FILE *pf;

printf("Indique directorio a visualizar: "); gets(nom);
if (nom[0]==0) exit(1); //si pulsamos Enter sin escribir nada
//supongamos que hemos tecleado: C:\DATOS\*.*

p = (char *)malloc(15+strlen(nom)); //reservamos memoria dinmicamente
if (p==NULL) { printf("No hay memoria"); exit(1); }

37

strcpy(p, "dir "); strcat(p, nom); strcat(p, " > dir.txt"); //componemos el comando a ejecutar
system(p); //El comando ha quedado as: dir C:\DATOS\*.* > dir.txt
free(p); //liberamos la zona reservada dinmicamente

if ((pf=fopen("dir.txt", "r"))==NULL) //abrimos el fichero de texto dir.txt
{ printf("El fichero dir.txt no ha sido creado"); exit(1); }

printf("El contenido del directorio es:\n\n");
while (fgets(lin, 100, pf)!=NULL) //leemos el fichero linea a linea
{ printf("%s", lin); lin[0]=0; } //imprimimos en pantalla linea a linea
if (lin[0]!=0) printf("%s\n", lin); //si la ltima lnea leda tena contenido

fclose(pf); getch(); //cerrar fichero y esperar tecla
remove("dir.txt"); //borrar el fichero de texto
}

FICHEROS TEMPORALES:
La funcin tmpfile crea un fichero temporal que es automticamente eliminado cuando el
fichero es cerrado o cuando el programa termina normalmente. El fichero temporal es abierto en
modo "w+b" (lectura/grabacin en modo binario), y el nombre dado al fichero en el disco es
aleatorio. Este tipo de ficheros es til cuando deseamos grabar en disco cierta informacin
transitoria que no queremos que permanezca ms all de la finalizacin del programa.
Su prototipo es: FILE *tmpfile(void); No necesita argumento de entrada. Devuelve
NULL si no se pudo crear el fichero, distinto de NULL si la creacin tuvo xito. Ejemplo:
FILE *pf;
if ((pf = tmpfile())==NULL) printf("No se puede abrir el fichero temporal");

38

8. TIPOS DE ACCESO A FICHEROS

ACCESO SECUENCIAL A FICHEROS:
Es el tipo ms simple de acceso a un fichero: los datos que se escriben en el fichero son
colocados automticamente una tras otro, y cuando se leen, se comienza desde el primero y se
leen consecutivamente hasta alcanzar el final del fichero.
Es el tipo de acceso habitual para ficheros de texto, en los que se escribe toda la informacin
desde el principio hasta el final con algn tipo de bucle y se lee de la misma forma. El bucle de
lectura siempre utilizar como condicin de finalizacin la funcin feof: while (!feof(pf) )

EJEMPLO-01:
// ACCESO SECUENCIAL A REGISTROS
// Programa para cargar desde teclado datos de consumo telefnico mensual

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

struct datotfno //declaracin del tipo estructura
{
int mes,anyo;
float euros;
};

int introducir(struct datotfno *tf);
void imprimir(FILE *pf);
char sino(char *mensaje);

main()
{
int n, numregs=0, tamreg=sizeof(struct datotfno);
struct datotfno dato;
FILE *pf;

system("cls"); //borrar pantalla
if ((pf=fopen("datostf.dat","r+"))==NULL)
//El fichero no existe, lo creamos
if ((pf=fopen("datostf.dat", "w+"))==NULL) { printf("ERROR de apertura"); exit(1); }
else
{ //El fichero existe
fseek(pf,0,SEEK_END); //Apuntador L/E al final del fichero
numregs = ftell(pf)/tamreg; //obtenemos el n de registros del fichero
printf("Hay %d registros en el fichero. ",numregs);
if (sino("Desea listarlos?")=='S') imprimir(pf); //si se responde 'S' a la pregunta
}

if (sino("Desea introducir datos?")=='S')
{ printf("Introduzca los datos de consumo telefnico:\n");
do //bucle para introduccin de registros de forma secuencial
{ printf("\nRegistro N %d:\n",numregs+1);
if (n=introducir(&dato)) //instruccin de asignacin, no de comparacin
{ fwrite(&dato, tamreg, 1, pf); numregs++; } //grabar registro e incrementar numregs
} while (n); //continua mientras n!=0
}

if (sino("Desea listar todos los registros?")=='S') imprimir(pf);
printf("\nPulse una tecla para terminar."); getch();
fclose(pf);
39

}

int introducir(struct datotfno *tf) //funcin para introduccin de datos por teclado
{
int scn;
do
{ printf("N de Mes (0=Terminar): "); scn=scanf("%d",&tf->mes); fflush(stdin); }
while (tf->mes<0 || tf->mes>12 || scn!=1);

if (tf->mes==0) return 0; //retorna 0 para terminar

do
{ printf("Ao: "); scn=scanf("%d",&tf->anyo); fflush(stdin); }
while (tf->anyo<0 || scn!=1);

do
{ printf("Euros Telefono: "); scn=scanf("%f",&tf->euros); fflush(stdin); }
while (tf->euros<0 || scn!=1);
return 1; //retorna 1 para continuar
}

void imprimir(FILE *pf) //funcin para presentacin de datos en pantalla con acceso secuencial
{
struct datotfno d;
int n=0;
system("cls");
rewind(pf); //Apuntador de L/E al principio del fichero
fread(&d, sizeof(d), 1, pf); //leer registro
while (!feof(pf) && !ferror(pf)) //preguntar por indicadores de error
{ printf("\nN de Mes: %d\tAo: %d\tConsumo: %.2f", d.mes, d.anyo, d.euros);
if (++n>=23) //Controlamos n de linea en pantalla para evitar "scrolling"
{ printf("\nPulse una tecla para continuar..."); getch(); n=0; system("cls"); }
fread(&d, sizeof(d), 1, pf); //leer registro
}
clearerr(pf); printf("\n\n"); //desactivar indicadores de error
}

char sino(char *mensaje) //funcin para leer respuesta por teclado de tipo S/N
{
char resp;
printf("%s (S/N): ", mensaje); //imprimir el mensaje
do
resp = toupper(getch()); //leer tecla y convertirla a maysculas
while (resp!='S' && resp!='N');
printf("%c", resp); //ver respuesta en pantalla
return resp; //retornamos el caracter ledo
}

ACCESO ALEATORIO A FICHEROS:
Con acceso aleatorio los datos se escriben y se leen en el fichero en cualquier orden,
posicionando el apuntador de Lectura/Escritura en el lugar deseado del fichero y a continuacin
realizando la operacin de grabacin o lectura. Una vez realizada, el apuntador avanza
automticamente hasta el byte siguiente al ltimo grabado o ledo.
Es el tipo de acceso habitual para ficheros de bases de datos, en los que la informacin est
almacenada en forma de registros consecutivos de igual longitud, que contienen en su
interior campos de diversos tipos (el mismo concepto que las variables de tipo struct). Se
maneja aqu el concepto de nmero de registro identificativo de cada registro, comenzando
por el primer registro del fichero de n 0.
40

Se utiliza intensivamente la funcin fseek para posicionar el apuntador de L/E, y la funcin ftell
para obtener el n de byte donde se encuentra dicho apuntador:

long ftell(FILE *puntero);
Esta funcin devuelve el nmero de byte donde est el apuntador, contando desde el principio
del fichero, o bien el valor -1 si hay un error. La posicin del primer byte del fichero es la n 0.

int fseek(FILE *puntero, long desplaz, int posinicio);
La funcin fseek mueve el apuntador a una posicin desplazada "desplaz" bytes desde la
posicin de inicio "posinicio". Esta posicin de inicio puede tener 3 valores posibles, dados por
3 constantes simblicas (en maysculas):
-- SEEK_SET (valor 0): Desde el principio del fichero.
-- SEEK_CUR (valor 1): Desde la posicin actual del apuntador de L/E.
-- SEEK_END (valor 2): Desde el final del fichero.

EJEMPLO-02:
// ACCESO ALEATORIO A REGISTROS
// Programa para cargar desde teclado datos de consumo telefnico mensual

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

void verregistro(void); //Prototipos de funciones auxiliares
void nuevoreg(void);
void modireg(void);
int introducir(void);
void primero(void);
void ultimo(void);
void avanzar(void);
void iranumero(void);

struct datotfno //declaracin del tipo estructura (registro)
{
int mes,anyo;
float euros;
};

//Variables globales:
int numregs=0; //N total de registros del fichero
int regnum=0; //N del registrro actual
int tamreg=sizeof(struct datotfno); //Tamao en bytes de un registro
struct datotfno dato; //Variable tipo registro para toma de datos
FILE *pf;

main()
{
int menu=0, salir=0;
system("cls"); //borrar pantalla
if ((pf=fopen("DATOSTF.DAT","r+"))==NULL)
{ //El fichero no existe, lo creamos
if ((pf=fopen("DATOSTF.DAT", "w+"))==NULL) { printf("ERROR de apertura"); exit(1); }
}
else
{ //El fichero existe
fseek(pf,0,SEEK_END); //Apuntador L/E al final del fichero
numregs = (int)ftell(pf)/tamreg; //obtenemos el n de registros del fichero
41

rewind(pf); //Nos posicionamos en el primer registro
}
if (numregs==0) nuevoreg();

while (!salir)
{
system("cls");
verregistro();
printf("MENU DE OPCIONES:\n\n");
printf("1. Introducir Nuevos Registros\n");
printf("2. Modificar Registro Actual\n");
printf("3. Ir al Primer Registro\n");
printf("4. Ir al Ultimo Registro\n");
printf("5. Avanzar Registros\n");
printf("6. Ir a Registro N\n");
printf("0. Salir\n");

do
{ printf("\nElija opcin: "); scanf("%d",&menu); fflush(stdin); }
while (menu<0 || menu>6);

switch (menu)
{ case 1: nuevoreg(); break;
case 2: modireg(); break;
case 3: primero(); break;
case 4: ultimo(); break;
case 5: avanzar(); break;
case 6: iranumero(); break;
case 0: salir=1; break;
}
}
fclose(pf);
}

void verregistro(void)
{
fread(&dato, tamreg, 1, pf); //Leer registro actual
fseek(pf,-tamreg,SEEK_CUR); //Volver a la posicin inicial
printf("Hay %d registros en el fichero.\n",numregs);
printf("N registro actual: %d\n",regnum);
printf("Datos del registro actual:\n");
printf(" N Mes: %d Ao: %d Euros Tfno: %g\n\n", dato.mes, dato.anyo, dato.euros);
}

void nuevoreg(void)
{
int n;
fseek(pf,0,SEEK_END); //Apuntador L/E al final del fichero
system("cls");
printf("Introduzca los datos de consumo telefnico:\n");
do //bucle para introduccin de registros de forma secuencial
{ printf("\nRegistro N %d:\n",numregs);
if (n=introducir()) //instruccin de asignacin, no de comparacin
{ fwrite(&dato, tamreg, 1, pf); numregs++; } //grabar registro e incrementar numregs
} while (n); //continua mientras n!=0
rewind(pf); regnum=0; //Apuntador L/E al primer registro del fichero
}

void modireg(void)
{
42

system("cls");
printf("Introduzca los datos de consumo telefnico:\n");
printf("\nRegistro N %d:\n",regnum);
if (introducir())
{ fwrite(&dato, tamreg, 1, pf); //grabar registro
fseek(pf,-tamreg,SEEK_CUR); //volver a su posicin de inicio
}
}

int introducir(void) //funcin para introduccin de datos por teclado
{
int scn;
do
{ printf("N de Mes (0=Terminar): "); scn=scanf("%d",&dato.mes); fflush(stdin); }
while (dato.mes<0 || dato.mes>12 || scn!=1);

if (dato.mes==0) return 0; //retorna 0 para terminar

do
{ printf("Ao: "); scn=scanf("%d",&dato.anyo); fflush(stdin); }
while (dato.anyo<0 || scn!=1);

do
{ printf("Euros Telefono: "); scn=scanf("%f",&dato.euros); fflush(stdin); }
while (dato.euros<0 || scn!=1);
return 1; //retorna 1 para continuar
}

void primero(void)
{
rewind(pf); regnum=0;
}

void ultimo(void)
{
regnum=numregs-1; fseek(pf,regnum*tamreg,SEEK_SET);
}

void avanzar(void)
{
int num=0;
printf("\n\nN de registros a avanzar (positivo o negativo): ");
scanf("%d", &num); fflush(stdin);
regnum+=num;
if (regnum >= numregs) regnum=numregs-1;
else if (regnum < 0) regnum=0;
fseek(pf,regnum*tamreg,SEEK_SET);
}

void iranumero(void)
{
int num=0;
do
{ printf("\n\nIr al N de Registro (0-%d): ",numregs-1); scanf("%d", &num); fflush(stdin); }
while (num<0 || num>=numregs);
fseek(pf,num*tamreg,SEEK_SET); regnum=num;
}
43

9. APLICACIN PRCTICA DE FICHEROS

CREACIN DE UNA BASE DE DATOS SENCILLA:
Vamos a disear una aplicacin de base de datos de Diccionario Espaol-Ingls que maneje dos
ficheros paralelos, uno para almacenar las palabras espaolas y el otro para las palabras inglesas.
Cada registro en ambos ficheros consta de un nico campo de tipo texto de 21 bytes para almacenar
una palabra (20 caracteres + nulo). Las palabras equivalentes en ingls y espaol estarn
almacenadas en el mismo n de registro de su fichero.
La aplicacin tendr un men principal desde el cual se realizarn las operaciones habituales de
mantenimiento de ficheros: altas de nuevos registros, modificacin o eliminacin de registros,
bsqueda de palabras en espaol o en ingls, ordenacin alfabtica de los registros en espaol o
ingls y listado del diccionario completo en pantalla.

// ACCESO ALEATORIO A FICHEROS
// Diccionario Espaol-Ingles / Ingles-Espaol con acceso directo

#define ANCHO 21
#define setpos(a,b) _settextposition(a,b)
//funcin para posicionar el cursor de texto en la fila a, columna b
#include <stdio.h>
#include <graph.h>

int menudicc(void); //prototipos de funciones
void anadir(void);
void modificar(void);
void eliminar(void);
void listar(void);
void ordenar(FILE *p1, FILE *p2);
int buscar(FILE *p1, FILE *p2, int espanol);
void leecad(int,int,char *);
void rellenablancos(char *cad);

char *msg="Pulse una tecla..."; //puntero global a mensaje de texto habitual
FILE *pf1, *pf2; //punteros a FILE globales

main()
{
int menu,numdatos,i;
if ((pf1=fopen("spanish.dat","r+"))==NULL)
{ pf1=fopen("spanish.dat","w+"); } //abrir el fichero para no perder los datos ya existentes
/*Apertura alternativa:
if ((pf1=fopen("spanish.dat","a+"))!=NULL) rewind(pf1); */

if ((pf2=fopen("english.dat","r+"))==NULL)
{ pf2=fopen("english.dat","w+"); }
/*Apertura alternativa:
if ((pf2=fopen("english.dat","a+"))!=NULL) rewind(pf2); */

if (pf1==NULL || pf2==NULL)
{ printf("ERROR de acceso a ficheros. %s",msg); getch(); exit(1); }
fseek(pf1,0,SEEK_END); numdatos = ftell(pf1)/ANCHO; //averiguar el n de registros que tenemos
printf("Hay %d palabras existentes en disco. %s", numdatos, msg);
getch();

do
{ menu = menudicc(); //llamada a la funcin de men del programa
switch (menu)
44

{
case 1: anadir(); break;
case 2: modificar(); break;
case 3: eliminar(); break;
case 4: listar(); break;
case 5: ordenar(pf1,pf2); break;
case 6: ordenar(pf2,pf1); break;
case 7: buscar(pf1,pf2,1); printf("\n%s",msg); getch(); break;
case 8: buscar(pf2,pf1,0); printf("\n%s",msg); getch(); break;
}
clearerr(pf1); clearerr(pf2); //desactivar marcas feof y ferror
} while (menu>0);

fclose(pf1); fclose(pf2);
}

int menudicc(void) //muestra en pantalla el men y retorna la tecla numrica pulsada
{
int m;
system("cls");
setpos(8,30); printf("1. Aadir Palabras.");
setpos(9,30); printf("2. Modificar Palabra.");
setpos(10,30); printf("3. Eliminar Palabra.");
setpos(11,30); printf("4. Listar Diccionario.");
setpos(12,30); printf("5. Ordenar en Espaol.");
setpos(13,30); printf("6. Ordenar en Ingles.");
setpos(14,30); printf("7. Buscar en Espaol.");
setpos(15,30); printf("8. Buscar en Ingles.");
setpos(16,30); printf("0. Salir.");
setpos(19,30); printf("Elija Opcion:");
do
{ setpos(19,44); m = getche()-'0'; }
while (m<0 || m>8);
system("cls");
return m;
}

void anadir(void) //Aade parejas de palabras al final del diccionario
{
int linea=3,i;
char esp[ANCHO],ing[ANCHO];
fseek(pf1,0,SEEK_END); fseek(pf2,0,SEEK_END); //Apuntadores L/E al final del fichero
printf("Introduzca parejas de palabras (VACIO = Terminar)...");
while (1)
{
setpos(linea,1); printf("Palabra Espaola: ");
setpos(linea,41); printf("Palabra Inglesa: ");
leecad(linea,19,esp); if (strcmp(esp,"")==0) break; //lee palabra espaola y mira si est vaca
leecad(linea,58,ing); if (strcmp(ing,"")==0) break; //lee palabra inglesa y comprueba si est vaca
rellenablancos(esp); fwrite(esp,ANCHO,1,pf1); //graba la palabra espaola en minsculas
rellenablancos(ing); fwrite(ing,ANCHO,1,pf2); //graba la palabra inglesa en minsculas
linea++; if (linea==25) { linea=1; system("cls"); } //evita sobrepasar la linea 25 de pantalla
}
}

int buscar(FILE *p1, FILE *p2, int espanol) //Busca una palabra, bien en espaol o en ingls
{
int ret=0;
char cad[ANCHO],esp[ANCHO],ing[ANCHO];
printf("Introduzca palabra %s: ",espanol?"espaola":"inglesa");
45

leecad(1,41,cad); //tomar por teclado la palabra buscada
if (strcmp(cad,"")!=0) //que no est vaca
{
strlwr(cad); //pasar la palabra buscada a minsculas
printf("\nBuscando...");
rewind(p1); //buscar desde el principio del fichero
fread(esp,ANCHO,1,p1); //leer registro en la cadena esp
while (!feof(p1) && !ferror(p1)) //bucle de bsqueda secuencial
{ if (strcmp(cad,esp)==0) break; //comparar esp con la cadena buscada
fread(esp,ANCHO,1,p1);
}
setpos(4,1);
if (ferror(p1))
{ printf("ERROR de lectura en fichero. %s",msg); getch(); }
else if (feof(p1))
{ printf("ERROR: Palabra no encontrada. %s",msg); getch(); }
else
{
fseek(p1,-ANCHO,SEEK_CUR); //retroceder L/E un registro en el fichero de bsqueda
fseek(p2,ftell(p1),SEEK_SET); //colocar en la misma posicin el L/E del 2 fichero
fread(ing,ANCHO,1,p2); //leer registro del 2 fichero en la cadena ing
fseek(p2,-ANCHO,SEEK_CUR); //retroceder L/E un registro en el 2 fichero
if (espanol) //presentar ambas palabras en pantalla
printf("Palabra Espaola: %s\t\tPalabra Inglesa: %s\n",esp,ing);
else
printf("Palabra Inglesa: %s\t\tPalabra Espaola: %s\n",esp,ing);
ret=1;
}
}
return ret; //retorna 1 si encontr la palabra buscada, 0 en caso contrario
}

void modificar(void) //modifica una pareja de palabras (espaola, inglesa o ambas)
{
char cad[ANCHO];
if (buscar(pf1, pf2, 1)) //llama a la funcin buscar para localizar la palabra espaola a modificar
{
setpos(6,1); printf("Nueva Palabra Espaola:"); leecad(6,25,cad); //pide nueva palabra por teclado
if (strcmp(cad,"")!=0) //si no est vaca
{ rellenablancos(cad); fwrite(cad,ANCHO,1,pf1); } //graba la palabra en minsculas sin basura
setpos(7,1); printf("Nueva Palabra Inglesa:"); leecad(7,25,cad); //pide la nueva palabra por teclado
if (strcmp(cad,"")!=0) //si no esta vaca
{ rellenablancos(cad); fwrite(cad,ANCHO,1,pf2); } //graba la palabra en minsculas sin basura
}
}

void eliminar(void) //elimina una pareja de palabras, grabando un "nulo" como byte inicial
{
char rsp;
if (buscar(pf1, pf2, 1)) //llama a la funcin buscar para localizar la palabra espaola a eliminar
{
setpos(6,1); printf("Eliminar Palabra. SEGURO? (S/N): "); //pedir confirmacin de eliminacin
do
rsp=toupper(getch());
while (rsp!='S' && rsp!='N');
printf("%c\n",rsp); //mostrar respuesta en la pantalla
if (rsp=='S') //si respondimos que SI
{
fputc('\0',pf1); fputc('\0',pf2); //poner marca de fin de cadena como byte 1 de ambos registros
}
46

}
}

void listar(void) //muestra en pantalla todo el diccionario
{
int linea=3; //indicador de linea de pantalla
char esp[ANCHO],ing[ANCHO],car=0;
printf("LISTADO DEL DICCIONARIO:");
rewind(pf1); rewind(pf2); //desde el principio de ambos ficheros
fread(esp,ANCHO,1,pf1); fread(ing,ANCHO,1,pf2); //leer registro en ambos ficheros
while (!feof(pf1) && !ferror(pf1) && !feof(pf2) && !ferror(pf2) && car!=27) //bucle secuencial
{
if (strcmp(esp,"")!=0) //si no es un registro "eliminado"
{
setpos(linea,1); printf("Palabra Espaola: %s",esp); //imprimir palabra espaola
setpos(linea,41); printf("Palabra Inglesa: %s",ing); //imprimir palabra inglesa
linea++; //incrementar contador de lneas de pantalla
}
if (linea==24) //si es la linea 24 esperamos tecla y borramos pantalla
{ printf("\n%s (ESC=Fin)",msg); car=getch(); linea=1; system("cls"); }
fread(esp,ANCHO,1,pf1); fread(ing,ANCHO,1,pf2); //leer registro en ambos ficheros
}
if (car!=27) //si la tecla pulsada fue ESC
{ printf("\n%s",msg); getch(); } //ultima pulsacion de tecla y finalizamos
}

void ordenar(FILE *p1,FILE *p2) //ordena alfabticamente, bien en espaol o en ingles (no ambas)
{ //ordena por el fichero maestro "p1" pasado como primer argumento
int x,j,cambio,numdatos;
char cad1[ANCHO],cad2[ANCHO];
printf("Ordenando...\n");
fseek(p1,0,SEEK_END); numdatos = ftell(p1)/ANCHO; //averiguar el n de registros que hay
for (x=numdatos-1; x>0; x--) //metodo de ordenacin de "la burbuja"
{
cambio=0; //indicador de "intercambio realizado"
for (j=0; j<x; j++) //j indica el n de registro actual del fichero maestro
{
fseek(p1,j*ANCHO,SEEK_SET); //colocar L/E del fichero maestro al principio del registro j
fread(cad1,ANCHO,1,p1); //leemos en cad1 el registro j, y en cad2 el j+1
fread(cad2,ANCHO,1,p1);
if (strcmp(cad1,cad2) > 0) //si cad1 es mayor alfabticamente que cad2
{
cambio=1;
fseek(p1,j*ANCHO,SEEK_SET); //volver a colocar L/E del fichero maestro en el registro j
fwrite(cad2,ANCHO,1,p1); //grabamos los dos registros intercambiados (cad2+cad1)
fwrite(cad1,ANCHO,1,p1);
fseek(p2,j*ANCHO,SEEK_SET); //colocar L/E del 2 fichero en registro j
fread(cad1,ANCHO,1,p2); //leemos en cad1 el registro j, y en cad2 el j+1
fread(cad2,ANCHO,1,p2);
fseek(p2,j*ANCHO,SEEK_SET); //volver a colocar L/E del 2 fichero en el registro j
fwrite(cad2,ANCHO,1,p2); //grabamos los dos registros intercambiados (cad2+cad1)
fwrite(cad1,ANCHO,1,p2);
}
}
if (!cambio) break; //si no ha habido ningn cambio, terminamos
}
printf("Ordenacin realizada. %s", msg); getch();
}

void leecad(int fila,int columna,char *cadena)
47

{ //lee por teclado la "cadena", mostrandolo en la posicin de pantalla dada por fila y columna
setpos(fila, columna);
fgets(cadena, ANCHO, stdin); //N mximo de caracteres ledos = ANCHO-1
if (cadena[strlen(cadena)-1]=='\n') //eliminar el caracter \n final
cadena[strlen(cadena)-1] = 0;
}

void rellenablancos(char *cad) //rellena con blancos finales la cadena para quitar posible "basura"
{ int i;
strlwr(cad); //convierte texto a minusculas
for (i=strlen(cad)+1; i<ANCHO; i++) cad[i]=' '; //rellena con blancos
}
48

10. ESTRUCTURAS DINMICAS

CONCEPTOS GENERALES:
Las estructuras dinmicas son variables de memoria que cambian de tamao a lo largo de la
ejecucin del programa.
Estan formadas por elementos de memoria aislados conectados unos con otros mediante
punteros. Existir un puntero principal externo que apunte al comienzo de esta cadena de
elementos.
Cada elemento de memoria se va creando mediante asignacin dinmica de memoria, con la
funcin malloc. Al finalizar el programa, debern ser todos eliminados mediante la funcin
free.
Las estructuras dinmicas ms utilizadas son: las listas lineales, las pilas, las colas, las listas
doblemente enlazadas, las listas circulares y los rboles binarios.

LISTAS LINEALES:
Las listas lineales estan formadas por elementos de tipo variable struct no consecutivos en la
memoria.
Cada elemento contendr uno o ms campos de datos, y adems al menos un campo de tipo
puntero que servir para apuntar al siguiente elemento de la cadena (contendr la direccin de
memoria donde se encuentra almacenado dicho elemento siguiente).
El ltimo elemento de la cadena contendr un valor NULL en dicho puntero, indicando que ya
no existe ningn elemento siguiente.
Existir un puntero externo que apuntar en todo momento al primer elemento de la cadena.
Si cada elemento tiene un nico puntero que apunta al siguiente elemento de la cadena, la lista
se denomina "simplemente enlazada". Si cada elemento tiene dos punteros, uno al elemento
siguiente de la cadena y otro al elemento anterior, se denomina "doblemente enlazada".
En una lista simplemente enlazada, la exploracin de la cadena de elementos se realizar de
forma secuencial nicamente en una direccin, desde el primer elemento hasta el ltimo.
En estas listas lineales se pueden realizar tareas tales como aadir nuevos elementos en
cualquier punto de la cadena, eliminarlos, recorrerlos secuencialmente, buscar un dato en su
interior, etc.

CREACIN DE UNA LISTA LINEAL SIMPLEMENTE ENLAZADA:

EJEMPLO-01:
#include <stdio.h>
#include <stdlib.h>

// Declaraciones globales
typedef struct s //declaracin del tipo struct, elemento de la cadena
{ int num1; float num2;
struct s *siguiente;
} elemento;
elemento *lista = NULL; //declaracin del puntero maestro global
void insertar(int x, float y); //Funciones prototipo
void imprimir(void);
void borrar(void);

main( )
{ int x; float y;
printf("Introduzca datos: ");
while (1)
{ printf("\nx = "); if (scanf("%d",&x)!=1) break;
49

printf("y = "); if (scanf("%f",&y)!=1) break;
insertar(x,y);
}
printf("\nLos datos introducidos son:\n");
imprimir();
borrar();
}

void insertar(int x, float y) //Introducir nuevo elemento al principio de la lista
{ elemento *q = (elemento *)malloc(sizeof(elemento));
if (q==NULL)
{ printf("Falta memoria"); getch(); }
else
{ q->num1=x; q->num2=y; q->siguiente=lista; lista=q; }
}

void imprimir(void) //Escribir todos los elementos de la lista
{ elemento *q=lista;
while (q!=NULL)
{ printf("x = %d, y = %g\n",q->num1,q->num2); q = q->siguiente; }
}

void borrar(void) //Borrar todos los elementos de la lista
{ elemento *q=lista;
while (q!=NULL)
{ lista = lista->siguiente; free(q); q=lista; }
}

INSERCIN DE UN ELEMENTO EN MITAD DE LA LISTA:

EJEMPLO-02:
// Insertar un nuevo elemento despus de uno dado por el puntero "p"
void insertarsig(elemento *p, int x, float y)
{ elemento *q = NuevoElemento();
if (q!=NULL)
{ q->num1=x; q->num2=y;
q->siguiente=p->siguiente; p->siguiente=q;
}
}

//Insertar un nuevo elemento antes de uno dado por el puntero "p"
void insertarant(elemento *p, int x, float y)
{ elemento *q = NuevoElemento();
if (q)
{ *q = *p; p->num1=x; p->num2=y; p->siguiente=q; }
}

//Crear nuevo elemento de la lista
elemento *NuevoElemento(void)
{ elemento *q = (elemento *)malloc(sizeof(elemento));
if (q==NULL) { printf("Falta memoria"); getch(); }
return q;
}

ELIMINACIN DE ELEMENTOS DE LA LISTA:

EJEMPLO-03:
//Eliminar un elemento dado, si no es el ltimo de la lista
void eliminar(elemento *p)
{ elemento *q = p->siguiente;
50

if (q!=NULL) { *p = *q; free(q); }
}

//Eliminar el elemento siguiente a uno dado
void eliminarsig(elemento *p)
{ elemento *q = p->siguiente;
if (q) { p->siguiente = q->siguiente; free(q); }
}

//Eliminacin general
void eliminar(void)
{ int x,scn; char rsp;
elemento *p,*q;
printf("Dame el valor de X a eliminar: "); scn=scanf("%d", &x);
if (scn==1)
{ q=p=lista;
while (q!=NULL && q->num1!=x)
{ p=q; q=q->siguiente; } // q=elemento actual, p apuntar siempre al elemento anterior a q
if (q!=NULL)
{ printf("Eliminar valor %d. SEGURO? (S/N): ", x);
rsp = toupper(getchar());
if (rsp=='S')
{ p->siguiente = q->siguiente;
if (q==lista) lista=lista->siguiente;
free(q);
}
}
else { printf("Valor no encontrado. %s", msg); getchar(); }
}
}

BSQUEDA DE ELEMENTOS EN LA LISTA:

EJEMPLO-04:
//Buscar un elemento de la lista
void buscar(void)
{ int num; elemento *q=lista;
printf("Dame valor de X a buscar:");
if (scanf("%d", &num)!=1) return;
while (q!=NULL && q->num1!=num) q=q->siguiente;
if (q==NULL) printf("Dato no encontrado");
else printf("El valor de Y asociado a X=%d es: %g", num, q->num2);
}

ORDENACIN DE LOS ELEMENTOS DE LA LISTA:

EJEMPLO-05:
//Ordenar ascendentemente los elementos de la lista
{ int cambio=1, x; elemento *p,*q;
while (cambio)
{ cambio=0; q=lista;
while (q!=NULL)
{ p=q->siguiente;
if (p!=NULL && q->num1 > p->num1)
{ x=q->num1; q->num1=p->num1; p->num1=x; cambio=1; }
q=q->siguiente;
}
}
printf("Los datos han sido ordenados."); getchar();
}
51

11. ESTRUCTURAS DINMICAS (II)

PILAS:
Una pila es un sistema de almacenamiento de datos en el cual el ltimo dato que lleg por la
entrada es el primero que sale (parecido al comportamiento de una pila de platos). En
informtica se denomina almacenamiento tipo LIFO (Last Input First Output).
Podemos construir una pila mediante una lista lineal simplemente enlazada, en la cual todas las
inserciones de nuevos datos y todas las extracciones se realizan por un mismo extremo de la
lista (el de comienzo de la lista enlazada), llamado cima de la pila.
Para controlar la pila basta con tener un puntero maestro externo que apunte a dicho extremo de
la lista.
La operacin de introduccin de un dato en la pila suele denominarse push, y la operacin de
extraccin de un dato suele denominarse pop.
Al extraer un dato de la lista (el primero de la cadena), tal dato es eliminado y el puntero
maestro pasa a apuntar al siguiente elemento de la cadena, que se convierte en la nueva cima de
la pila.
Cuando el puntero maestro vale NULL, indica que la pila est vaca.

EJEMPLO-01:
/* Creacion de una pila mediante una lista enlazada */

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

/* Funciones prototipo */
void push(char dato);
char pop(void);
void *NuevoElem(size_t numbytes);

/* Declaraciones globales */
typedef struct n
{ char num;
struct n *siguiente;
} numero;
numero *pila = NULL;

/* Programa principal */
main()
{ char m;
push(13); push(22); push(33);
m = pop(); printf("%d ",m);
m = pop(); printf("%d ",m);
while (pila!=NULL) m = pop(); //Vaciar toda la pila
}

/* Introducir dato en la pila */
void push(char dato)
{ numero *q = (numero *)NuevoElem(sizeof(numero));
if (q!=NULL)
{ q->siguiente = pila; q->num = dato; pila = q; }
}

/* Extraer dato de la pila */
char pop(void)
{ numero *q; char x;
if (pila==NULL) { printf("Lista vaca"); getch(); return EOF; }
52

x = pila->num; q = pila; pila = pila->siguiente; free(q);
return x;
}

/* Crear nuevo elemento de la pila */
void *NuevoElem(size_t numbytes)
{ void *q = (void *)malloc(numbytes);
if (q==NULL) { printf("Falta memoria"); getch(); }
return q;
}

COLAS:
Una cola es un sistema de almacenamiento de datos en el cual el primer dato que lleg por la
entrada es el primero que sale (parecido al comportamiento de una cola en un cine). En
informtica se denomina almacenamiento tipo FIFO (First Input First Output).
Podemos construir una cola mediante una lista lineal simplemente enlazada, en la cual las
inserciones de nuevos datos se realizan por un extremo de la lista y las extracciones se realizan
por el extremo contrario de la lista.
Para controlar la cola se necesitan dos punteros maestros, uno que apunte al extremo de entrada
de la lista enlazada y otro que apunte al extremo de salida.
La operacin de introduccin de un dato en la cola suele denominarse push, y la operacin de
extraccin de un dato suele denominarse pop.
Al extraer un dato de la cola, tal dato es eliminado y el puntero maestro pasa a apuntar al
siguiente elemento de la cadena.
Cuando ambos punteros maestros valen NULL, significa que la cola est vaca.

EJEMPLO-02:
/* Creacion de una cola mediante una lista enlazada */

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

/* Prototipos de funciones */
void push(char dato);
char pop(void);
void *NuevoElem(size_t numbytes);

/* Declaraciones globales */
typedef struct n
{ char num;
struct n *siguiente;
} numero;
numero *inicio = NULL, *final = NULL;

/* Programa principal */
main()
{ char m;
push(13); push(22); push(33);
m = pop(); printf("%d ",m);
m = pop(); printf("%d ",m);
while (inicio!=NULL) m = pop(); // Vaciar toda la cola
}

/* Introducir dato en la cola */
void push(char dato)
{ numero *q = (numero *)NuevoElem(sizeof(numero));
if (q!=NULL)
53

{ if (final!=NULL) final->siguiente = q;
final = q; final->num = dato; final->siguiente = NULL;
if (inicio==NULL) inicio = q;
}
}

/* Extraer dato de la cola */
char pop(void)
{ numero *q; char x;
if (inicio==NULL)
{ printf("Cola vaca"); getch(); return EOF; }
x = inicio->num; q = inicio; inicio = inicio->siguiente; free(q);
if (inicio==NULL) final = NULL;
return x;
}

/* Crear nuevo elemento de la cola */
void *NuevoElem(size_t numbytes)
{ void *q = (void *)malloc(numbytes);
if (q==NULL) { printf("Falta memoria"); getch(); }
return q;
}

LISTAS CIRCULARES:
Una lista circular es una lista lineal en la cual el ltimo elemento de la cadena apunta al primer
elemento de la misma, formando un anillo de circulacin continua.
Para controlar esta lista se necesita un puntero maestro externo que apunte a cualquier elemento
del anillo.

EJEMPLO-03:
/* Creacion de una lista circular mediante una lista enlazada */

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

/* Prototipos de funciones */
void *NuevoElem(size_t numbytes);

/* Declaraciones globales */
typedef struct n
{ int num;
struct n *siguiente;
} numero;
numero *lista = NULL;

/* Programa principal */
main()
{ numero *q; int dato;
printf("Introduzca nmeros (CTRL+Z = Fin)\n\n");
while (1)
{ printf("Numero: "); scanf("%d",&dato); if (feof(stdin)) break;
q = (numero *)NuevoElem(sizeof(numero)); q->num = dato;
if (lista==NULL)
{ q->siguiente = q; lista = q;}
else
{ q->siguiente = lista->siguiente; lista->siguiente = q; }
}
printf("\nLos elementos de la lista son:\n"); q = lista;
do
54

{ printf("%d ",q->num); q = q->siguiente; }
while (q!=lista);
printf("\nEliminando la lista...\n");
while (lista!=NULL)
{ q = lista->siguiente;
if (q==lista)
{ free(lista); lista = NULL; }
else
{ *lista = *q; free(q); }
}
printf("Lista eliminada.");
}

/* Creacion de un nuevo elemento de la lista */
void *NuevoElem(size_t numbytes)
{ void *q = (void *)malloc(numbytes);
if (q==NULL) { printf("Falta memoria"); getch(); }
return q;
}

LISTAS DOBLEMENTE ENLAZADAS:
Una lista doblemente enlazada es aquella en la que cada elemento de la cadena tiene un puntero
que apunta al elemento siguiente, y otro puntero que apunta al elemento anterior.
Este tipo de listas permite su recorrido en las dos direcciones posibles: del primero al ltimo, o
bien del ltimo al primero.
Para controlar estas listas se necesita un puntero maestro externo que apunte al primer elemento
de la lista enlazada.

EJEMPLO-04:
/* Creacion de una lista doblemente enlazada */

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

/* Prototipos de funciones */
void *NuevoElem(size_t numbytes);

/* Declaraciones globales */
typedef struct n
{ struct n *anterior;
int num;
struct n *siguiente;
} numero;
numero *lista = NULL;

/* Programa principal */
main()
{ numero *q; int dato;
printf("Introduzca nmeros (CTRL+Z = Fin)\n\n");
while (1)
{ printf("Numero: "); if (scanf("%d",&dato)==EOF) break;
q = (numero *)NuevoElem(sizeof(numero)); q->num = dato;
q->siguiente = lista; q->anterior = NULL;
if (lista!=NULL) lista->anterior = q;
lista = q;
}
printf("\nLos datos de la lista son:\n"); q = lista;
while (q!=NULL)
{ printf("%d ",q->num); q = q->siguiente; }
55

printf("\nEliminando la lista...\n");
while (lista!=NULL)
{ q = lista; lista = lista->siguiente; free(q); }
printf("Lista eliminada.");
}

/* Creacion de un nuevo elemento de la lista */
void *NuevoElem(size_t numbytes)
{ void *q = (void *)malloc(numbytes);
if (q==NULL) { printf("Falta memoria"); getch(); }
return q;
}

/* Eliminacin de un elemento cualquiera de la lista */
void eliminar(numero *p)
{ numero *ant = p->anterior, *sig = p->siguiente;
if (ant!=NULL) ant->siguiente = p->siguiente;
if (sig!=NULL) sig->anterior = p->anterior;
if (p==lista) lista = p->siguiente;
free(p);
}
56

12. RBOLES BINARIOS

CONCEPTOS GENERALES:
Un rbol es una estructura de almacenamiento de datos formada por un conjunto de "nodos"
unidos entre s mediante "ramas".
Cada nodo puede almacenar uno o ms datos, de los cuales uno es el principal llamado "clave".
Cada nodo almacena tambin tantos punteros como ramas salgan de l, punteros que apuntan a
otros nodos del rbol.
El nodo primario del rbol se llama nodo "raz", y de l salen ramas que conectan con otros
nodos, de los cuales salen ms ramas. Finalmente, un nodo del cual no sale ninguna rama se
llama nodo "terminal".
Un rbol se puede definir de forma recursiva como "el conjunto formado por un nodo raz del
cual parten ramas que conectan con otros nodos, que a su vez son rboles". Es por ello que la
forma ms sencilla de programar una estructura de rbol es mediante funciones recursivas.
El nmero de ramas que salen de un nodo se denomina "grado" del nodo.
La distancia en nmero de ramas que separa un nodo del nodo raz primario del rbol se
denomina "nivel" del nodo.
El mximo "nivel" alcanzado por alguno de los nodos del rbol se denomina "altura" o
"profundidad" del rbol.

RBOLES BINARIOS:
Un rbol binario es aquel cuyos nodos tienen como mximo grado 2, es decir, que como
mximo salen 2 ramas de cada nodo (pueden salir 2 ramas, una o ninguna).
Se dice entonces que cada nodo tiene un "subrbol derecho" y un "subrbol izquierdo".
Cada nodo puede programarse mediante una variable de tipo "struct" que contenga uno o ms
campos de dato, y un par de campos de tipo puntero que apunten uno al nodo del subrbol
izquierdo y otro al nodo del subrbol derecho:
struct datos
{ int datoclave;
struct datos *izqdo;
struct datos *dcho;
};
Un valor NULL en alguno de dichos dos punteros indica que no existe el subrbol
correspondiente. Un nodo "terminal" ser aquel con valor NULL en ambos punteros.
El rbol se gobierna mediante un puntero externo que apunta al nodo raz primario del rbol:
struct datos *raiz;
Si el puntero raz vale NULL significa que el rbol est vaco y no contiene todava ningn
nodo.

RBOLES BINARIOS DE BSQUEDA:
Un "rbol binario de bsqueda" es aquel cuyos nodos se almacenan de forma ordenada de
acuerdo con la siguiente regla: "para todo nodo N, todas las claves de los nodos del subrbol
izquierdo son menores que la clave del nodo N, y todas las claves de los nodos del subrbol
derecho son mayores que la clave del nodo N".
El rbol binario de bsqueda es el tipo de almacenamiento ms til cuando de partida no se
conoce la cantidad de datos a almacenar, los datos se reciben en orden aleatorio y deben ser
almacenados de forma ordenada, y la velocidad de bsqueda de los datos almacenados debe ser
elevada.

57

EJEMPLO-01:
/* Programa que almacena los nmeros recibidos por el teclado y cuenta cuntas veces
repetidas se ha recibido cada uno */

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

/* Declaraciones globales */
typedef struct datos //tipo struct de cada nodo del rbol
{ int clave, contador;
struct datos *izqdo, *dcho;
} nodo;
nodo *raiz=NULL; //puntero al nodo raz primario del rbol

/* funciones prototipo */
void creararbol(void);
nodo *anadir(int num, nodo *r);
void borrararbol(nodo *r);

/* Programa principal */
main( )
{ system(cls);
creararbol( ); //creacin del rbol
/* Tareas a realizar con el rbol */
borrararbol(raiz); //eliminacin de todos los nodos del rbol
}

void creararbol(void)
{ int numero,c;
while (1) //bucle para creacin del rbol
{ printf(Nmero: );
c=scanf(%d,&numero); //leer nmero desde el teclado
if (c==0) continue; //si se ha tecleado un dato incorrecto
if (c==EOF) break; //si se ha pulsado CTRL+Z
raz = anadir(numero, raiz); //insertar nuevo nmero en el rbol
}

nodo *anadir(int num, nodo *r) //funcin recursiva
{ if (r==NULL)
{ r = (nodo *)malloc(sizeof(nodo)); //se crea nuevo nodo y se carga de datos
if (r==NULL)
{ printf (Falta Memoria.); getch(); }
else
{ r->clave = num; r->contador = 1; r->izqdo = r->dcho = NULL; }
}
else
{ if (num < r->clave) //si el nmero es menor que la clave del nodo actual
r->izqdo = anadir(num, r->izqdo);
else if (num > r->clave) //si el nmero es mayor que la clave del nodo actual
r->dcho = anadir(num, r->dcho);
else //si el nmero es igual que la clave del nodo actual
r->contador++;
}
return r;
}

void borrararbol(nodo *r) //funcin recursiva
{ if (r!=NULL)
{ borrararbol(r>izgdo); borrararbol(r->dcho); free(r); }
}
58


RECORRIDO DE RBOLES BINARIOS:
Una tarea habitual con los rboles binarios, una vez construdos y cargados de datos, es
"recorrerlos", es decir, visitar todos y cada uno de los nodos del rbol, visitando cada uno una
nica vez.
Existen tres modos diferentes de recorrido estandar de un rbol binario: en PreOrden, en
InOrden y en PostOrden.
Recorrido en PreOrden (secuencia RID): para todo nodo, se visita primero el nodo raz (R),
despus el subrbol izquierdo (I), y despus el subrbol derecho (D). Cabe tambin la variante
de recorrido con secuencia RDI (Raz-Derecho-Izquierdo).
Recorrido en InOrden (secuencia IRD): para todo nodo, se visita primero el subrbol
izquierdo (I), luego el propio nodo raz (R), y despus el subrbol derecho (D). Este es el tipo de
recorrido necesario para obtener los datos ordenados en orden ascendente. Cabe tambin la
variante de recorrido con secuencia DRI (Derecho-Raz-Izquierdo), que da lugar a un recorrido
ordenado en orden descendente.
Recorrido en PostOrden (secuencia IDR): para todo nodo, se visita primero el subrbol
izquierdo (I), luego el subrbol derecho (D) y finalmente el propio nodo raz (R). Cabe tambin
la variante de recorrido con secuencia DIR (Derecho-Izquierdo-Raz). Este es el tipo de
recorrido necesario para eliminar todo el rbol, antes de terminar el programa.

EJEMPLO-02:
/* Recorridos en un arbol binario */
#include <stdio.h>
#include <stdlib.h>

/* Declaraciones globales */
typedef struct datos //tipo struct de cada nodo del rbol
{ int clave, contador;
struct datos *izqdo, *dcho;
} nodo;
nodo *raiz=NULL; //puntero al nodo raz primario del rbol

/* funciones prototipo */
void creararbol(void);
nodo *anadir(int num, nodo *r);
void preorden(nodo *r);
void inorden(nodo *r);
void postorden(nodo *r);
void borrararbol(nodo *r);

/* Programa principal */
main( )
{ system(cls);
creararbol( ); //creacin del rbol
/* Tareas a realizar con el rbol */
printf(\n\nRecorrido en PREORDEN:\n); preorden(raiz);
printf(\n\nRecorrido en INORDEN:\n); inorden(raiz);
printf(\n\nRecorrido en POSTORDEN:\n); postorden(raiz);
borrararbol(raiz); //eliminacin de todos los nodos del rbol
}

void preorden(nodo *r) //funcin recursiva
{ if (r!=NULL)
{ printf("%d ",r->clave); preorden(r->izqdo); preorden(r->dcho); }
}

59

void inorden(nodo *r) //funcin recursiva
{ if (r!=NULL)
{ inorden(r>izqdo);
printf(El n %d se ha introducido %d veces\n,r->clave,r->contador);
inorden (r>dcho);
}
}

void postorden(nodo *r) //funcin recursiva
{ if (r!=NULL)
{ postorden(r->izqdo); postorden(r->dcho); printf(%d ,r->clave); }
}

BSQUEDA EN UN RBOL BINARIO DE BSQUEDA:
La bsqueda de un dato en un rbol binario de bsqueda se realiza fcilmente de forma
recursiva, aplicando la definicin de este tipo de rboles.

EJEMPLO-03:
/* Bsqueda en un arbol binario de bsqueda */
#include <stdio.h>
#include <stdlib.h>
typedef struct datos //tipo struct de cada nodo del rbol
{ int clave, contador;
struct datos *izqdo, *dcho;
} nodo;
nodo *raiz=NULL; //puntero al nodo raz primario del rbol

void creararbol(void);
nodo *anadir(int num, nodo *r);
nodo *buscar(int dato, nodo *r);
void borrararbol(nodo *r);

main( )
{ int clv=0; nodo *p;
system(cls);
creararbol( );
/* Tareas a realizar con el rbol */
printf("Indique la clave del nodo a localizar: "); scanf("%d", &clv);
p = buscar(clv, raiz);
if (p==NULL)
printf("No existe esta clave en el rbol");
else
printf("La clave %d ha sido introducida %d veces", p->clave, p->contador);
borrararbol(raiz);
}

nodo *buscar(int dato, nodo *r) //funcin recursiva
{ if (r==NULL) return r; //dato no existe
else if (dato > r->clave) return buscar(dato, r->dcho); //buscar en subarbol derecho
else if (dato < r->clave) return buscar(dato, r->izqdo); //buscar en subarbol izquierdo
else return r; //dato encontrado
}
60

13. ALGORITMOS DE ORDENACIN DE DATOS

CONCEPTOS GENERALES:
La clasificacin u ordenacin de los datos de una lista de menor a mayor (o viceversa) es un
proceso sumamente frecuente en cualquier aplicacin informtica.
Existen varios algoritmos de ordenacin posibles. El mejor ser aquel que consiga la ordenacin
en el menor tiempo posible, es decir, el que realice la tarea ms rpidamente. Cuanto menor sea
el nmero de intercambios de datos realizados, mayor ser la velocidad del proceso.
Estudiaremos estos mtodos aplicndolos a un array numrico de N elementos, cuyos datos
queremos ordenar de menor a mayor (el elemento menor en el subndice [0], y el mayor en el
subndice [N-1]).

MTODO DE LA BURBUJA:
Consiste en dar N-1 pasadas sucesivas por el array de datos a ordenar.
En cada pasada, se comienza desde el elemento de subndice ms bajo [0], comparndolo con el
elemento de subndice siguiente [1]. Si ambos elementos estan desordenados (el primero es
mayor que el segundo), se intercambian. Si estan bien ordenados, se dejan como estan.
Luego se comparan los elementos de subndice [1] y [2], luego [2] con [3], y as sucesivamente
hasta comparar el dato penltimo con el ltimo.
Al final de esta primera pasada, el ltimo elemento del array quedar bien colocado (contendr
el dato mayor de todo el array).
Luego se repite el proceso en una segunda pasada, pero trabajando slo con el resto del array
menos en ltimo elemento. Al terminar esta 2 pasada, el penltimo elemento del array quedar
bien colocado.
Se contina con el resto de pasadas hasta completar N-1 vueltas.
Existe una versin mejorada de este algoritmo, en la cual se incluye un detector que nos informa
si NO se ha realizado ninguna operacin de intercambio a lo largo de la pasada n-sima en la
que nos encontramos. Si esto sucede significa que el array est ya completamente ordenado y no
tenemos que continuar con las siguientes pasadas. Podremos dar por concluido el proceso
anticipadamente, ganando en velocidad de proceso.

EJEMPLO-01:
// Metodo de la Burbuja
#include <stdio.h>
#define N 4 //Nmero de elementos del array

main()
{ int a[N]={17,10,12,5}; //array a ordenar
int n; //n de pasadas que faltan por realizar
int cambio=1; //detector de intercambio en una pasada
int aux; //auxiliar para realizar intercambio
int i;
for (n=N-1; n>0 && cambio; n--) //bucle de pasadas que faltan
{ cambio=0; //poner cambio a falso
for (i=0; i<n; i++)
if (a[i]>a[i+1]) //si estan desordenados...
{ aux=a[i]; a[i]=a[i+1]; a[i+1]=aux; //intercambiar
cambio=1; //poner cambio a cierto
}
}
printf("El array esta ordenado");
}

61

MTODO DE SELECCIN:
Consiste en dar N-1 pasadas sucesivas por el array de datos a ordenar.
En cada pasada, se localiza desde el elemento de subndice ms bajo [0] dnde se encuentra el
elemento mayor del array. Una vez localizado, se intercambia con el ltimo elemento del array,
quedando este ya bien colocado.
Luego se repite el proceso en una segunda pasada, pero trabajando slo con el resto del array
menos en ltimo elemento. Al terminar esta 2 pasada, el penltimo elemento del array quedar
bien colocado.
Se contina con el resto de pasadas hasta completar N-1 vueltas.

EJEMPLO-02:
// Metodo de Seleccin
#include <stdio.h>
#define N 4 //Nmero de elementos del array
main()
{ int a[N]={17,10,12,5}; //array a ordenar
int n; //n de pasada actual
int max; //indice del dato mayor
int aux; //auxiliar para realizar intercambio
int i;
for (n=N-1; n>0; n--) //bucle de pasadas que faltan
{ max=0; //suponemos el mayor en la posicin 0
for (i=1; i<=n; i++)
if (a[i]>a[max]) max=i; //detectamos indice del dato mayor
if (max!=n)
{ aux=a[n]; a[n]=a[max]; a[max]=aux; } //intercambiar a[n] con a[max]
}
printf("El array esta ordenado");
}

MTODO DE INSERCIN:
Consiste en dar N-1 pasadas sucesivas por el array de datos a ordenar.
En la primera pasada se ordenan los dos primeros elementos del array.
En la segunda pasada se inserta el tercer elemento en el lugar adecuado entre los dos primeros,
desplazando los elementos superiores.
En la tercera pasada se inserta el cuarto elemento en el lugar adecuado entre los tres primeros,
desplazando los elementos superiores.
Se contina con el resto de pasadas hasta completar N-1 vueltas.

EJEMPLO-03:
// Metodo de Insercin
#include <stdio.h>
#define N 4 //Nmero de elementos del array
main()
{ int a[N]={17,10,12,5}; //array a ordenar
int i; //indice del elemento actual a insertar
int j; //indice de los elementos anteriores
int aux; //guarda copia del elemento actual a insertar
for (i=1; i<N; i++) //bucle de elementos actuales a insertar
{ aux=a[i]; //copiamos elemento actual a insertar
j = i-1; //empezamos insercin desde el elemento anterior
while (j>=0 && aux<a[j]) { a[j+1] = a[j]; j--; } //desplazamos elementos hacia la derecha
a[j+1]=aux; //insertamos dato en el lugar adecuado
}
printf("El array esta ordenado");
}
62

MTODO RECURSIVO QUICKSORT:
Es el mtodo ms rpido de ordenacin, con mucha diferencia. Al ser un mtodo recursivo,
puede requerir una capacidad elevada de la "pila" del sistema (zona de memoria donde se
almacenan las variables locales), si la cantidad de elementos a ordenar es elevada.
Se selecciona como referencia el valor del elemento mitad del array a ordenar.
Se colocan todos los valores menores de dicho valor de referencia en las posiciones bajas del
array, y todos los elementos mayores o iguales en las posiciones altas del array. El array queda
dividido en dos partes (datos menores y datos mayores o iguales).
Se repite el proceso con la parte baja del array (la que contiene aquellos elementos menores), y
luego con la parte alta del array (la que contiene aquellos elementos mayores o iguales),
dividiendo en cada paso cada subarray en dos nuevas partes.
La recursin termina cuando la porcin de array a ordenar consta de un nico elemento.

EJEMPLO-04:
// Metodo Quicksort
#include <stdio.h>
#define N 4 //Nmero de elementos del array
void qsort(int *lista, int numelem); //declaraciones prototipo
void qs(int *lista, int posinf, int possup);

main()
{ int a[N]={17,10,12,5}; //array a ordenar
qsort(a, N); //llamada a la funcin de ordenacin
printf("El array esta ordenado");
}

void qsort(int *lista, int numelem) //funcin de ordenacin
{ qs(lista, 0, numelem-1); } //llamada a la funcin recursiva

void qs(int *lista, int posinf, int possup) //funcin recursiva
{ int izq; //indice del elemento izquierdo a intercambiar
int der; //indice del elemento derecho a intercambiar
int mitad; //valor del elemento mitad del array
int aux; //auxiliar para realizar intercambio
izq=posinf; //izq comienza desde el extremo inferior del array
der=possup; //der comienza desde el extremo superior del array
mitad = lista[(izq+der)/2];
do
{ //bucle para aumentar "izq" hasta encontrar dato mayor o igual que "mitad"
while (lista[izq]<mitad && izq<possup) izq++;

//bucle para disminuir "der" hasta encontrar dato menor o igual que "mitad"
while (lista[der]>mitad && der>posinf) der--;

if (izq<=der)
{ if (izq!=der) { aux=lista[izq]; lista[izq]=lista[der]; lista[der]=aux; } //intercambiar datos "izq" y "der"
izq++; der--;
}
} while (izq<=der); //continuar bsqueda hasta que izq>der

//repetir proceso con la parte baja del array
if (posinf<der) qs(lista, posinf, der);

//repetir proceso con la parte alta del array
if (possup>izq) qs(lista, izq, possup);
}
63

EJEMPLO-05:

64

COMPARATIVA DE ALGORITMOS:
Al comprobar el funcionamiento de estos 4 algoritmos sobre un array de 10000 nmeros
aleatorios de tipo float, ordenndolo 10 veces consecutivas, los resultados han sido los
siguientes:
Mtodo de la burbuja: 13950 milisegundos.
Mtodo de seleccin: 8570 milisegundos.
Mtodo de insercin: 5000 milisegundos.
Mtodo Quicksort: 50 milisegundos.

EJEMPLO-06:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define N 10000 //Nmero de elementos del array

long test(void (*pfunc)());
void burbuja(float *a, int numelem);
void seleccion(float *a, int numelem);
void insercion(float *a, int numelem);
void qsort(float *lista, int numelem);
void qs(float *lista, int posinf, int possup);

float a[N]; //array a ordenar

main()
{
printf("Tiempo por mtodo de la Burbuja: %ld ms\n", test(burbuja));
printf("Tiempo por mtodo de Seleccion: %ld ms\n", test(seleccion));
printf("Tiempo por mtodo de Insercion: %ld ms\n", test(insercion));
printf("Tiempo por mtodo Quicksort: %ld ms\n", test(qsort));
printf("Pulse una tecla..."); getch();
}

long test(void (*pfunc)()) //devuelve milisegundos
{ int i,j; long tinic,trand;
tinic=clock(); //tomar tiempo inicial de carga
for (i=0; i<N; i++) a[i]=rand(); //cargar array con numeros aleatorios
trand = clock()-tinic; //calcular tiempo total de carga
tinic = clock(); //tomar tiempo inicial de ordenaciones
for (j=0; j<10; j++) //bucle de 10 ordenaciones
{ for (i=0; i<N; i++) a[i]=rand(); //cargar array
pfunc(a, N); //llamar a funcion de ordenacion
}
return clock()-tinic-10*trand; //retornar tiempo empleado en ordenaciones
}

void burbuja(float *a, int numelem)
{ int n,i; //n de pasadas que faltan por realizar
int cambio=1; //detector de intercambio en una pasada
float aux; //auxiliar para realizar intercambio
for (n=numelem-1; n>0 && cambio; n--) //bucle de pasadas que faltan
{ cambio=0; //poner cambio a falso
for (i=0; i<n; i++)
if (a[i]>a[i+1]) //si estan desordenados...
{ aux=a[i]; a[i]=a[i+1]; a[i+1]=aux; //intercambiar
cambio=1; //poner cambio a cierto
}
}
}
65


void seleccion(float *a, int numelem)
{ int n,i; //n de pasada actual
int max; //indice del dato mayor
float aux; //auxiliar para realizar intercambio
for (n=numelem-1; n>0; n--) //bucle de pasadas que faltan
{ max=0; //suponemos el mayor en la posicin 0
for (i=1; i<=n; i++)
if (a[i]>a[max]) max=i; //detectamos indice del dato mayor
if (max!=n)
{ aux=a[n]; a[n]=a[max]; a[max]=aux; } //intercambiar a[n] con a[max]
}
}

void insercion(float *a, int numelem)
{ int i; //indice del elemento actual a insertar
int j; //indice de los elementos anteriores
float aux; //guarda copia del elemento actual a insertar
for (i=1; i<N; i++) //bucle de elementos actuales a insertar
{ aux=a[i]; //copiamos elemento actual a insertar
j = i-1; //empezamos insercin desde el elemento anterior
while (j>=0 && aux<a[j])
{ a[j+1] = a[j]; j--; } //desplazamos elementos hacia la derecha
a[j+1]=aux; //insertamos dato en el lugar adecuado
}
}

void qsort(float *lista, int numelem) //funcin de ordenacin
{
qs(lista, 0, numelem-1); //llamada a la funcin recursiva
}

void qs(float *lista, int posinf, int possup) //funcin recursiva
{ int izq; //indice del elemento izquierdo a intercambiar
int der; //indice del elemento derecho a intercambiar
float mitad; //valor del elemento mitad del array
float aux; //auxiliar para realizar intercambio
izq=posinf; //izq comienza desde el extremo inferior del array
der=possup; //der comienza desde el extremo superior del array
mitad = lista[(izq+der)/2];
do
{ //bucle para aumentar "izq" hasta encontrar dato mayor o igual que "mitad"
while (lista[izq]<mitad && izq<possup) izq++;

//bucle para disminuir "der" hasta encontrar dato menor o igual que "mitad"
while (lista[der]>mitad && der>posinf) der--;

if (izq<=der)
{ //intercambiar los datos de posicion "izq" y "der"
if (izq<der) { aux=lista[izq]; lista[izq]=lista[der]; lista[der]=aux; }
izq++; der--;
}
} while (izq<=der); //continuar bsqueda hasta que izq>der

//repetir proceso con la parte baja del array
if (posinf<der) qs(lista, posinf, der);

//repetir proceso con la parte alta del array
if (possup>izq) qs(lista, izq, possup);
}
66

14. ALGORITMOS DE BSQUEDA DE DATOS

BSQUEDA SECUENCIAL:
Es el mtodo de bsqueda mas sencillo pero menos eficiente.
Se trata de comparar el valor buscado con todos y cada uno de los valores de la lista, del
primero al ltimo, hasta que se encuentra coincidencia o se acaba la lista.
Esta lista no necesita estar ordenada.

EJEMPLO-01:
#include <stdio.h>
#define N 500
int BusquedaSec(int a[ ], int numelem, int dato);

main()
{
int a[N], dato=0, i;

/* Proceso de carga de datos del array */

printf("Indique el dato a buscar: "); scanf("%d", &dato);
i = BusquedaSec(a, N, dato);
if (i==N) printf("Dato no encontrado");
else printf("Dato %d encontrado en la posicin %d", dato, i);
}

int BusquedaSec(int a[ ], int numelem, int dato)
{
int i;
for (i=0; i<numelem && a[i]!=dato; i++);
return i;
}

BSQUEDA BINARIA:
La bsqueda binaria es un mtodo eficiente que se puede aplicar nicamente a listas ordenadas.
Consiste en comparar el dato buscado con el elemento central de la lista. Si el dato buscado es
mayor que el central, se repite la misma bsqueda sobre la mitad superior de la lista. Si el dato
buscado es menor que el central, se repite la busqueda sobre la mitad inferior de la lista. El
proceso se repite hasta que la porcin de lista explorada tiene uno o ningn elemento.
Se puede programar de forma recursiva (ya visto anteriormente) o de forma iterativa (mediante
bucle).

EJEMPLO-02:
#include <stdio.h>
#define N 500

main()
{ int a[N], dato=0, i;

/* Proceso de carga de datos del array y ordenacin ascendente de los mismos */

printf("Indique el dato a buscar: "); scanf("%d", &dato);
i = BusquedaBin(a, N, dato);
if (i==-1) printf("Dato no encontrado");
else printf("Dato %d encontrado en la posicin %d", dato, i);
}

67

int BusquedaBin(int a[ ], int numelem, int dato)
{ int mitad, inf=0, sup=numelem-1;
if (numelem<=0) return -1;
do
{ mitad=(inf+sup)/2;
if (dato>a[mitad]) inf=mitad+1;
else sup=mitad-1;
} while (a[mitad]!=dato && inf<=sup);
if (a[mitad]==dato) return mitad;
else return -1;
}

ALGORITMOS HASH:
Son mtodos de bsqueda muy rpida que permiten localizar los datos dentro de una amplia
lista con un escaso nmero de accesos a la misma, sin que la lista tenga que estar ordenada.
El mtodo general consiste en utilizar un array de punteros llamado matriz Hash, punteros
que apuntarn a la ubicacin en memoria de cada elemento de datos que queramos almacenar
mediante tcnicas de asignacin dinmica (funcin malloc). La bsqueda de los datos se
realiza por medio de dicha matriz.
El nmero de elementos de dicha matriz (L) debe ser mayor que el nmero mximo estimado de
datos que queremos almacenar (N), al menos el doble, y debe ser un nmero primo.
El n medio de accesos sobre la matriz para buscar un dato cualquiera viene dado por la
frmula: (2-k)/(2-2k) donde k es el cociente N/L. Por ejemplo, si el n de elementos de la
matriz es el doble del n de datos (k=1/2), el n medio de accesos es de slo 1,5 veces.
En los datos a almacenar debe existir un campo Clave identificativo de cada dato. Esta Clave
debe ser numrica y no repetible. Si la Clave es alfabtica, debe ser transformada a nmero no
repetible mediante algn algoritmo.

PROCEDIMIENTO DE ALMACENAMIENTO HASH:
El procedimiento de almacenamiento (funcin HashIn()) consiste en obtener a partir de la
Clave del dato a almacenar un nmero entero H mediante la frmula siguiente: H = Clave % L
(resto de la divisin entre la Clave y el n de elementos de la matriz Hash). Este nmero H
(valor de 0 a L-1) indica la posicin del elemento de la matriz de punteros Hash donde se
anotar la direccin de memoria del dato a almacenar (direccin obtenida mediante malloc).
Puede suceder colisin, cuando dos Claves diferentes dan lugar a un mismo nmero H. El
nmero de colisiones es escaso si L es un nmero primo y es al menos el doble de N.
Dependiendo del modo en que se resuelven las colisiones, existen varias modalidades de
algoritmos Hash.
En el mtodo Hash Abierto, cuando sucede colisin incrementamos el n H (H++) hasta que
encontremos un puntero Hash vaci (valor NULL), en el cual insertaremos la direccin del dato
a almacenar.
En el mtodo Hash con Desbordamiento, cuando sucede colisin se almacenan las claves
que producen colisin en otra matriz de punteros Hash llamada Matriz de Desbordamiento,
en la que se almacenan las direcciones de dichas claves de forma consecutiva desde el primer
elemento de la matriz.
En el mtodo "Hash Enlazado", se aade a cada elemento de datos un nuevo campo puntero
que sirva para apuntar mediante lista enlazada al siguiente elemento de datos que produjo
colisin.



68

PROCEDIMIENTO DE BSQUEDA HASH:
El procedimiento de bsqueda de datos ya almacenados (funcin HashOut()) consiste en
obtener, a partir de la Clave del dato a buscar, el nmero entero H mediante la misma frmula
H = Clave % L, y acudir a la posicin H de la matriz de punteros Hash para tomar la direccin
de memoria del dato almacenado.
Si en esa direccin no se encuentra la Clave buscada (hubo colisin al almacenarla), la bsqueda
del dato correcto depende del mtodo Hash utilizado.
Con el mtodo Hash Abierto incrementamos H y pasamos al siguiente puntero Hash, hasta que
encontremos la Clave buscada o un puntero NULL, que significar que la clave no existe.
Con el mtodo Hash con Desbordamiento pasaremos a explorar la matriz Hash de
Desbordamiento secuencialmente desde su primer elemento, hasta que demos con la clave
buscada o un puntero NULL.
Con el mtodo "Hash Enlazado" seguimos la lista enlazada de datos a partir del dato actual,
hasta dar con la clave buscada o lleguemos al final de la lista.

PROCEDIMIENTO DE BORRADO HASH:
Para eliminar un dato dado por su Clave, no basta con suprimir de la matriz Hash el puntero de
posicin H=Clave%L, ponindolo a valor NULL. Con ello podramos perder tambin los
dems datos de colisin asociados a esa misma posicin.
Lo que se hace es aadir a cada elemento de datos un nuevo campo de tipo char llamado, por
ejemplo, "Eliminado", campo que marcamos a valor "S" para indicar que este dato esta
borrado. De esta forma no se pierde su Clave y los dems datos de colisin son alcanzables.
Las funciones HashIn y HashOut debern tener en cuenta la existencia de este campo, para
realizar bien su tarea.

EJEMPLO-03:
/* Busqueda rpida mediante algoritmo Hash Abierto */
/* Almacenar DNI y Nombre de todos los alumnos de la Escuela EPS */

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define N 5000 //N mximo estimado de datos a almacenar (n de alumnos de la EPS)

typedef struct
{ long dni; //dato clave de bsqueda, no repetible, identificativo de un alumno
char nombre[50];
char eliminado;
} tipoAlumno; //tipo de estructura contenedora de datos del alumno

int numeroPrimo(int num); //calcula el primer n primo por encima del valor num
int IniciarMatrizHash(void); //crea la matriz Hash de punteros, y la pone a cero
int HashIn(tipoAlumno *p); //funcin de introduccin de un nuevo dato en la matriz Hash
tipoAlumno *HashOut(long dni); //funcin de bsqueda de un dato en la matriz Hash
void BorrarMatrizHash(void); //borrar todos los elementos creados mediante malloc

//Variables globales:
tipoAlumno **mHash = NULL; //puntero de control de la matriz Hash (es una matriz de punteros)
int L; //n de elementos de la matriz Hash

main()
{ tipoAlumno *p=NULL; //puntero auxiliar
tipoAlumno alum, alumVacio={0, ""};
char resp; int i;
L = numeroPrimo(2*N); //calculamos el n de elementos ms adecuado para la matriz Hash
69


if (!IniciarMatrizHash()) exit(1); //Crear la matriz Hash

/* Introduccin de datos desde el teclado */
system("cls");
printf("N de elementos del array Hash: %d\n", L);
printf("Introduzca datos de alumnos. Para terminar, DNI = 0\n");
while (1)
{ alum = alumVacio; //limpiar la variable alum
printf("\nDNI: "); scanf("%ld", &alum.dni); fflush(stdin); //leer DNI
if (alum.dni==0) break; //si DNI=0 salimos del bucle
printf("Nombre: "); gets(alum.nombre); //leer Nombre
p = (tipoAlumno *)malloc(sizeof(tipoAlumno)); //crear struct de tipo tipoAlumno
if (p==NULL) printf("\nERROR: Memoria Insuficiente\n");
else
{
*p = alum; //copiar estructura alum en *p
if (!HashIn(p)) free(p); //insertar elemento en la matriz Hash
}
}

/* Impresion de los datos */
system("cls");
printf("Los datos introducidos son:\n\n");
for (i=0; i<L; i++)
if (mHash[i]!=NULL && mHash[i]->eliminado!='S')
printf("DNI: %ld\tNombre: %s\n", mHash[i]->dni, mHash[i]->nombre);

/* Busqueda de datos usando la matriz Hash */
printf("\n\nBusqueda de datos. Para terminar, DNI = 0\n");
while (1)
{ printf("\nDNI: "); scanf("%ld", &alum.dni); fflush(stdin); //leer DNI
if (alum.dni==0) break; //si DNI=0 salimos del bucle
p = HashOut(alum.dni); //buscar DNI mediante la matriz Hash
if (p==NULL) printf("Este DNI no existe\n");
else
{ printf("El nombre del alumno es: %s\n", p->nombre);
do
{ printf("Eliminar este dato? (S/N): "); resp=toupper(getche()); }
while (resp!='S' && resp!='N');
if (resp=='S') p->eliminado='S';
}
}
BorrarMatrizHash(); //borrar de memoria la matriz Hash y todos los datos de alumnos
}

int numeroPrimo(int num) //calcula el primer n primo por encima del valor num
{ int primo=0; //valor cierto o falso
int i;
int r=(int)sqrt((double)num); //lmite mximo del divisor
if (num%2==0) num++; //si el n es par se pasa al impar siguiente
while (!primo)
{ primo=1;
for (i=3; i<=r && primo; i+=2) //bucle de divisores impares para num, hasta como mximo r
if (num%i==0) primo=0;
if (!primo) num+=2; //se pasa al n impar siguiente
}
return num;
}

70

int IniciarMatrizHash(void) //crea la matriz Hash de punteros, y la pone a cero
{ int i=0;
mHash = (tipoAlumno **)malloc(L * sizeof(tipoAlumno *)); //reservar espacio para el array Hash
if (mHash==NULL)
printf("\nERROR: Memoria Insuficiente\n");
else
for (i=0; i<L; i++) mHash[i]=NULL; //poner a NULL todos los punteros
return i; //retorna valor cierto o falso
}

int HashIn(tipoAlumno *p) //funcin de introduccin de un nuevo dato en la matriz Hash
{ int H; //indice de acceso del array Hash
int conta=0; //contador de incrementos del indice
int insertado=0; //valor cierto o falso
H = p->dni % L; //clculo del indice de acceso al dato
while (conta < L) //bucle de incrementos del indice
{ if (mHash[H]==NULL || mHash[H]->eliminado=='S') //elemento vaco, se inserta el dato
{ mHash[H]=p; insertado=1; break; }
else if (mHash[H]->dni==p->dni) //clave repetida
{ printf("ERROR: clave duplicada\n"); insertado=0; break; }
else //elemento no vaco, se incrementa el indice
{ conta++; H++; if (H==L) H=0; }
}
if (conta==L) //no ha habido ningun elemento vaco
{ printf("ERROR: la matriz esta llena\n"); insertado=0; }
return insertado; //retorna cierto o falso
}

tipoAlumno *HashOut(long dni) //funcin de bsqueda de un dato en la matriz Hash
{ int H; //indice de acceso del array Hash
int conta=0; //contador de incrementos del indice
int encontrado=0; //valor cierto o falso
H = dni % L; //clculo del indice de acceso al dato
while (!encontrado && conta<L)
{ if (mHash[H]==NULL) return NULL; //si encontramos un elemento vaco
if (mHash[H]->dni==dni && mHash[H]->eliminado!='S') //si encontramos el elemento buscado
encontrado=1;
else
{ conta++; H++; if (H==L) H=0; } //incrementamos el indice de acceso
}
if (conta==L) return NULL; //si la matriz esta llena y no hemos encontrado el dato buscado
return mHash[H]; //retornamos la direccin del elemento encontrado
}

void BorrarMatrizHash(void) //borrar todos los elementos creados mediante malloc
{ int i;
for (i=0; i<L; i++) free(mHash[i]); //borrar cada dato de los alumnos
free(mHash); //borrar la matriz Hash de punteros
}

Potrebbero piacerti anche