TEMA 1.16
FICHEROS DE ACCESO DIRECTO O ALEATORIO





1.16.1 - INTRODUCCIÓN A LOS FICHEROS DIRECTOS

Los ficheros secuenciales los usábamos de forma que era necesario leerlos línea por línea siempre desde el principio y sin saltar ninguna, y para guardarlos ocurría lo mismo. Ahora vamos a trabajar con ficheros de acceso directo o aleatorio, que estarán formados por "trozos" de igual tamaño, y por lo tanto el ordenador será capaz de saltar al registro que nosotros queramos para leerlo o escribirlo. Los registros se corresponden con la estructura de un tipo de datos definido por el usuario y para acceder a ellos únicamente se calcula su posición inicial a partir del número de registro y de su tamaño, que nos calcula la función LEN. No están separados de ninguna forma, ya no hablamos de líneas ni de caracteres de retorno de carro.

Otro concepto importante que se introduce con los ficheros directos es el de "Campo clave". El campo clave puede ser la posición dentro del archivo donde se guarda ese registro en concreto. Esto implica que el campo clave es único ya que no puede haber dos registros en la misma posición. Un ejemplo de campo clave podría ser el DNI en una lista de personas o el número de habitación en la lista de habitaciones de un hotel. Este dato no está escrito en la estructura de datos del archivo, sino que es determinado por la posición del registro dentro del archivo. Si usamos como campo clave un número muy grande como el DNI nos encontraremos con el inconveniente de que el archivo llegará a tener un tamaño muy grande ya que como veremos más adelante los registros intermedios aunque estén vacíos también existirán en el archivo. De la misma forma el campo clave es obligatorio de especificar y no lo podemos dejar en blanco, ya que no se sabría en que posición se guarda el registro.

Lo más inmediato es usar como campo clave un valor numérico bajo correlativo (1, 2, 3...) que el usuario vaya escribiendo, o bien que el programa lo calcule automáticamente imitando a los tipos de datos autonuméricos de las bases de datos.





1.16.2 - LECTURA Y ESCRITURA
EN FICHEROS DIRECTOS

Vamos con un ejemplo que define un tipo de datos compuesto de nombre y edad, y abre un fichero en modo RANDOM para almacenar en él los datos de personas que va escribiendo el usuario, al estilo de como lo haría una agenda.

TYPE TIPOPERSONA
	nombre AS STRING * 20
	edad AS INTEGER
END TYPE
DIM persona AS TIPOPERSONA
CLS
OPEN "agenda.dat" FOR RANDOM AS #1 LEN = LEN(persona)
n=0
DO
	n=n+1
	INPUT "Nombre: ", persona.nombre
	INPUT "Edad: ", persona.edad
	PUT #1, n, persona
	INPUT "¿Meter más datos (S/N)? ", respuesta$
LOOP UNTIL respuesta$="N"
CLOSE #1
PRINT n; "registros grabados en la agenda."

El resultado por pantalla podría ser este:

Nombre: Pepe
Edad: 22
¿Meter más datos (S/N)? S
Nombre: Paco
Edad: 25
¿Meter más datos (S/N)? S
Nombre: Ana
Edad: 28
¿Meter más datos (S/N)? N
3 registros grabados en la agenda.

Y el resultado en el sistema de archivos será que se ha creado un nuevo archivo llamado agenda.dat, si no existía de antes, que contiene los datos de tres personas. De la forma que está hecho el programa nuestros tres registros irían a parar siempre al principio del archivo. En el siguiente ejemplo veremos como leer los datos del archivo, ahora vamos a ver como funciona este programa.

Cuando abrimos un archivo en modo RANDOM si no existe se crea, y si existe no ocurre nada, ya que como veremos a continuación en él vamos a poder tanto leer como escribir registros.

Si al grabar un registro nos referimos a uno que ya existe, este se sobreescribirá entero, y si no existe el fichero aumentará de tamaño hasta permitir usar ese número de registro, añadiendo si es necesario más registros entre medio. La información que se almacenará en estos registros intermedios es indeterminada. Por ejemplo si tenemos un fichero con tres registros (1, 2 y 3) y escribimos en el registro 10 se almacenará nuestra información en el registro 10 y a la vez se crearán 6 registros más (4, 5, 6, 7, 8 y 9) llenos de información indeterminada.

El primer registro es siempre el 1 y el último posible dependerá del espacio disponible en disco.

Los ficheros que creemos de esta forma ya no los podremos editar con un editor de texto como el EDIT o el bloc de notas, ya que contienen una mezcla de letras y de valores numéricos que se pueden representar por caracteres especiales no imprimibles. También es necesario abrirlos siempre usando el mismo tipo de datos como "molde", ya que de no hacerlo así todos los registros se mezclarían y no nos serviría de nada.

Ahora vamos a ver otro ejemplo de programa que lea el fichero que acabamos de crear con el programa anterior.

TYPE TIPOPERSONA
	nombre AS STRING * 20
	edad AS INTEGER
END TYPE
DIM registro AS TIPOPERSONA
CLS
OPEN "agenda.dat" FOR RANDOM AS #1 LEN = LEN(registro)
numeroRegistros = LOF(#1) / LEN(registro)
FOR n = 1 TO numeroRegistros
	GET #1, n, registro
	PRINT persona.nombre,  persona.edad
NEXT
CLOSE #1

El resultado por pantalla podría ser este:

Pepe   22
Paco	25
Ana	28

Y en el fichero no se produciría ningún cambio porque no hay ninguna instrucción de escritura.

Como se puede ver el programa es parecido al anterior. Hemos definido un tipo de datos que en esta ocasión se llama registro en vez de persona pero tiene exactamente la misma estructura y a continuación abrimos el fichero de la misma forma.

Podemos calcular el número de registros que tiene un fichero usando la función LEN que, como ya sabemos, nos da el tamaño del registro, y la función LOF (Length Of File) que nos devuelve el tamaño en bytes del fichero abierto cuyo descriptor le pasemos como parámetro (con o sin #) usando la fórmula "Tamaño del fichero dividido por tamaño del registro". De esta forma si el fichero tiene por ejemplo 200 bytes y cada registro mide 40 bytes obtenemos como resultado que hay 5 registros. Como no hay caracteres retornos de carro ni nada de eso las cuentas siempre salen exactas.

Una vez que sabemos cuantos registros hay podemos escribirlos en pantalla sencillamente usando un bucle FOR. Para leer información del fichero usamos la instrucción GET cuya forma de uso es similar a la de la instrucción PUT que ya hemos visto. Tras ejecutar la instrucción GET el contenido del registro queda almacenado en la estructura de la variable.

Si leemos más allá del final del fichero no ocurre nada, a diferencia de lo que pasaba con los ficheros secuenciales, pero la información que se nos da es indeterminada, en el mejor de los casos ceros y espacios en blanco. De todas formas lo correcto es calcular cual es el ultimo registro como hemos hecho antes y no leer nada más allá para evitar errores lógicos y posibles confusiones. También nos hará falta conocer siempre el número de registros para insertar los nuevos a continuación y no sobreescribiendo los ya existentes como hace nuestro primer ejemplo que no es nada práctico. Esto lo veremos en los siguientes apartados dedicados a lo que se conoce como "Mantenimiento de ficheros"





1.16.3 - MANTENIMIENTO
DE FICHEROS DIRECTOS





1.16.3.1 - INTRODUCCIÓN
AL MANTENIMIENTO DE FICHEROS

En el apartado anterior teníamos un programa para meter datos en un fichero, y otro para listar todos los datos que acabábamos de meter, y para complicarlo todo si ejecutábamos otra vez el primer programa como siempre empezábamos por el registro 1 seguro que sobreescribíamos datos. También para borrar información sólo podíamos borrar todos los datos a la vez borrando el fichero desde el sistema operativo y empezando con uno nuevo vacío.

Esta sería una forma muy mala de trabajar con una base de datos de una agenda. Para manejar datos se han definido cuatro operaciones básicas: Altas, bajas, modificaciones y consultas, además de la elaboración de listados y la compactación del fichero. En estos apartados veremos unas breves (muy breves) indicaciones de como conseguir que nuestros programas de QBasic puedan hacer esto con un fichero de acceso directo.





1.16.3.2 - ALTAS. AÑADIR REGISTROS

En nuestro ejemplo de la sección anterior veíamos como añadir registros a un archivo directo con la instrucción PUT. Lo hacíamos muy bien, solo que siempre empezábamos por la primera posición y de esta forma cada vez que ejecutamos el programa sobreescribíamos datos y perdíamos información.

La idea de todo esto es conseguir poder añadir información al fichero cada vez que ejecutemos el programa, en este caso pediremos al usuario que escriba la posición (campo clave principal) donde quiere guardar el nuevo registro.

Veamos como ejemplo un procedimiento SUB que pide al usuario el número de registro, el nombre y la edad de la persona y los añade al final del fichero. Suponemos que la estructura de TIPOPERSONA está definida en el programa principal y que el fichero también está ya abierto y tiene el descriptor #1.

'Procedimiento ALTA
'Añade un nuevo registro a agenda.dat
SUB Alta()
	DIM nuevaPersona AS TIPOPERSONA
	DIM registro AS INTEGER
	INPUT "Número de registro: ", registro
	INPUT "Nombre: ", nuevaPersona.nombre
	INPUT "Edad: ", nuevaPersona.edad
	PUT #1, registro, nuevaPersona
END SUB

Si es la primera vez que usamos el programa y el fichero no existe, se creará uno nuevo. Los datos que hemos introducido se grabarán en la posición que indiquemos como número de registro o campo clave.

Si esta posición de registro ya estaba ocupada por otros datos, la información se sobreescribirá y se perderá la anterior. Si es una posición vacía más allá del final del archivo, el tamaño crecerá creandose registros hasta alcanzar la posición especificada donde se grabará nuestra información. El contenido de estos registros intermedios es indeterminado, normalmente restos de datos previamente descartados existentes en esa posición del disco o en memoria, y no lo podemos usar para nada. Este comportamiento de que el fichero vaya creciendo hace que si usamos como campo clave un número muy grande como un DNI, el fichero aumente de tamaño considerablemente.

Otros algoritmos más elaborados nos avisarán si el campo clave ya está ocupado con información, nos mostrarán la información que contiene para darnos la posibilidad de sobreescribirla o de elegir otro campo clave, etc...





1.16.3.3 - CONSULTAS.
BÚSQUEDA SECUENCIAL DE REGISTROS

Una vez que tenemos información en el registro nos puede interesar buscar un nombre, por ejemplo, y que nos salga en la pantalla los otros datos de la persona, en nuestro ejemplo sólo la edad.

Para hacer esto hay complicados mecanismos de búsqueda muy rápida suponiendo que nuestro fichero esté ordenado. Como no es el caso, la única forma de encontrar algo es ir mirando registro por registro hasta que encontremos lo que buscamos o se acabe el fichero.

Vamos con un ejemplo que pregunta al usuario el nombre de la persona que quiere buscar y recorre el fichero hasta encontrarla o hasta que se acaba. Suponemos que ya está abierto el archivo y definido el tipo de datos registro, igual que en los ejemplos anteriores.

numregs = (LOF(1) / LEN(persona))
INPUT "Nombre a buscar: ", nombreBuscado$
n = 1
DO
	GET #1, n, persona
	n = n + 1
LOOP UNTIL RTRIM$(persona.nombre)=nombreBuscado$ OR n>numregs
IF n <= numregS THEN
	PRINT "Registro...:"; n-1
	PRINT "Nombre.....: "; persona.nombre
	PRINT "Edad.......:"; persona.edad
ELSE
	PRINT "No se ha encontrado"
END IF

Lo primero que hacemos es calcular cuantos registros tiene el fichero para ver asta donde podemos llegar. Después pedimos al usuario que escriba el nombre que quiere encontrar y lo guardamos en una variable del tipo adecuado, en este caso cadena.

En el siguiente bloque vamos recorriendo el fichero desde el primer registro usando un contador hasta que encontremos el nombre buscado o bien el contador llegue a superar el número de registros del fichero. Para hacer la comparación usamos la función RTRIM$ que lo que hace es quitar los espacios en blanco que hay a la derecha del nombre grabado en el registro porque como al definir el registro tenemos que usar cadenas de longitud fija los valores devueltos siempre contienen espacios al final para rellenar la cadena.

Cuando salimos del bucle lo que hacemos es comprobar el contador con un IF. Si se ha superado el límite es que no se ha encontrado, y en caso contrario es que se ha encontrado y podemos mostrar en pantalla todos los datos de la persona que siguen estando almacenados en la variable de registro, ya que después no se ha leído nada más. También incluimos el número del registro usando el contador menos uno, ya que antes de salir del bucle se sumó uno.

Si hay dos personas con el mismo nombre este código sólo encontrará la primera. Podemos hacer consultas por otros campos, o bien combinar varios campos con solo modificar la condición del bloque repetitivo que usamos para recorrer el archivo. Si queremos poder mostrar más de un registro encontrado habría que ir guardando los datos encontrados en un array del mismo tipo para después mostrarlos en una lista. Habría que limitar el número de registros mostrados al tamaño del array, igual que hace Windows 9x con las búsquedas de archivos y carpetas.





1.16.3.4 - MODIFICACIONES DE REGISTROS

Al trabajar con ficheros no nos podemos limitar a permitirle al usuario añadir datos, también hay que dejarle hacer modificaciones, ya que la información puede cambiar o el usuario se puede equivocar al escribir los datos y tendrá que corregirlos. La forma de hacer una modificación consiste en sobreescribir un registro completo del fichero con la nueva información.

Una forma muy fácil de hacer esto es preguntar al usuario qué registro quiere modificar, mostrarle los datos a ver si de verdad son estos y a continuación pedirle la nueva información y grabarla en esa posición del fichero sobreescribiendo a la anterior.

Vamos a ver como sería (Archivos ya abiertos y tipos ya definidos):

numregS = (LOF(1) / LEN(persona))
INPUT "Número de registro a modificar: ", nummodif%
IF nummodif% < 1 OR nummodif% > numregS THEN
	PRINT "Número de registro incorrecto"
ELSE
	GET #1, nummodif%, persona
	PRINT "Nombre.....: "; persona.nombre
	PRINT "Edad.......:"; persona.edad
	PRINT "¿Es este el registro que quieres modificar? (S/N)"
	WHILE INKEY$ <> "": WEND 'Para limpiar INKEY$
	DO
		tecla$ = UCASE$(INKEY$)
	LOOP UNTIL tecla$ = "S" OR tecla$ = "N"
	IF tecla$ = "S" THEN
		PRINT "Escribe los nuevos datos..."
		INPUT "Nombre: ", persona.nombre
		INPUT "Edad: ", persona.edad
		PUT #1, nummodif%, persona
		PRINT "Los nuevos datos han sustituido a los anteriores"
	END IF
END IF

Lo primero es pedir al usuario que escriba el número del registro, este número lo habrá averiguado en una consulta que haya hecho antes, por ejemplo. El número que escriba lo guardamos en una variable de tipo entero, ya que los registros nunca van a opder llevar decimales. A continuación comprobamos que el registro existe en el fichero, su número es mayor que 0 y no mayor que el número máximo de registros que hemos calculado al principio. Sólo si es así seguimos, si no damos un mensaje de error y terminamos.

Si el registro existe lo mostramos en la pantalla y le preguntamos si es este el que quiere modificar. Si pulsa la S mayúscula le pedimos los nuevos datos y los grabamos en el fichero en la misma posición soreescribiendo los datos antiguos.

Al hacer modificaciones el tamaño del fichero no cambia.

El uso de la función INKEY$ para hacer preguntas tipo Sí o No al usuario lo veremos detalladamente en uno de los temas de ampliación del curso.





1.16.3.5 - BAJAS. BORRAR REGISTROS

En este modelo de programación de ficheros de acceso aleatorio no existe una forma específica de borrar información de un fichero, por esto lo que hacemos es simplemente sobreescribir el registro rellenándolo con espacios en blanco o valores cero, según corresponda, para que la información no aparezca en las consultas ni en los listados.

Vamos con un ejemplo de como hacerlo, casi igual al de las modificaciones.

numregS = (LOF(1) / LEN(persona))
INPUT "Número de registro a borrar: ", numborra%
IF numborra% < 1 OR numborra% > numregS THEN
	PRINT "Número de registro incorrecto"
ELSE
	GET #1, numborra%, persona
	PRINT "Nombre.....: "; persona.nombre
	PRINT "Edad.......:"; persona.edad
	PRINT "¿Es este el registro que quieres borrar? (S/N)"
	WHILE INKEY$ <> "": WEND 'Para limpiar INKEY$
	DO
		tecla$ = UCASE$(INKEY$)
	LOOP UNTIL tecla$ = "S" OR tecla$ = "N"
	IF tecla$ = "S" THEN
		persona.nombre = SPACE$(LEN(persona.nombre))
		persona.edad = 0
		PUT #1, numborra%, persona
		PRINT "Registro borrado"
	END IF
END IF

Como se puede ver, lo único que cambia es que en los mensajes en vez de decir "modificar" dice "borrar" y que sobreescribimos el registro con blancos y ceros en vez de con la información que escribe el usuario.

Lo único más extraño es que para asignar espacios en blanco al nombre en vez de hacer

persona.nombre = "                    "

que sería lo más fácil, usamos la función SPACE$ que nos devuelve una cadena de espacios en blanco, y para calcular cuantos espacios usamos la función LEN que nos devuelve el tamaño del dato miembro. De esta forma no nos tenemos que acordar de cuanto largo era ese dato y si modificamos este tamaño durante el desarrollo del programa no tenemos que modificar esta instrucción, evitando posibles errores.

Visto esto se puede comprender que el tamaño de los ficheros directos nunca se va a reducir, ni aunque borremos registros. Para solucionar este problema lo que podríamos hacer en algunos casos será compactar el fichero, en el apartado siguiente.





1.16.3.6 - COMPACTACIÓN DE FICHEROS DIRECTOS

Como hemos visto, al borrar registros el tamaño del fichero nunca se reduce, con lo que nuestro fichero va a crecer y crecer siempre pudiendo contener muchos registros vacíos que lo único que hacen es ocupar sitio y hacer que todo vaya un poco más lento.

Para solucionar este problema en las bases de datos se ha definido una tarea de mantenimiento que consiste en "compactar" los ficheros. No tiene nada que ver con lo que hacen programas como WinZip, esto es "comprimir" ficheros y la forma de programar estas operaciones es terriblemente más complicada.

Este proceso de compactación lo que va a hacer es agrupar los registros todos juntos al principios del archivo. Esto implica que se van a modificar los campos calve ya que cambiará la posición donde está almacenado cada registro. Si el campo clave es algo significativo como por ejemplo el número de socio de un club, número de habitación de un hotel, etc... no deberemos hacer este proceso de compactación ya que cambiarán los campos claves y destruiremos la información. Solo lo deberemos hacer en casos donde el campo clave no sea significativo para nosotros y no importe que cambie.

Imaginemos que en la vida real tenemos una agenda de papel llena de datos de personas de los cuales unos sirven y otros están tachados porque son incorrectos o se han quedado obsoletos. Además de llevar siempre un tocho de hojas muy gordo, a la hora de buscar algo tendremos que pasar siempre muchas hojas, perdiendo más tiempo del necesario. Una forma muy cómoda de solucionar este problema sería conseguir una agenda nueva para quedarnos sólo con los datos que queremos conservar. Para hacer esto recorreríamos la agenda vieja y solo iríamos copiando en la nueva los datos que no están tachados. Al final tiramos la vieja y nos quedamos con la nueva para seguir usándola a partir de ahora.

Este trozo de código hace justamente eso mismo con nuestro fichero informático de la base de datos. Suponemos que el fichero de nuestra agenda ya esta abierto y los tipos de datos definidos.

numregs = (LOF(1) / LEN(registro))
OPEN "Agenda.tmp" FOR RANDOM AS #2 LEN = LEN(registro)
n1 = 1
n2 = 1
WHILE n1 <= numregS
	GET #1, n1, registro
	IF registro.nombre <> SPACE$(LEN(registro.nombre)) OR registro.edad <> 0 THEN
		PUT #2, n2, registro
		n2 = n2 + 1
	END IF
	n1 = n1 + 1
WEND

CLOSE
	
KILL "Agenda.dat"
NAME "Agenda.tmp" AS "Agenda.dat"
  
OPEN "Agenda.dat" FOR RANDOM AS #1 LEN = LEN(registro)
'Seguimos con el programa...

Vamos a ver como funciona esto:

Esta operación de mantenimiento la tendrá que ejecutar el usuario de vez en cuando, especialmente después de haber borrado muchos registros. En programas mucho más sofisticados se podría hacer que se ejecute cada x días o tras haber borrado un determinado número de registros.





1.16.3.7 - ELABORACIÓN DE LISTADOS DE DATOS

Los listados son una parte muy útil de todo programa que trabaje con datos, para que el usuario pueda ver toda la información a la vez y no registro por registro. En los programas modernos se suele hablar de "informes" para referirse a listados con un determinado formato que presentan los registros que cumplen cierta condición.

Aquí nos limitaremos a escribir en la pantalla un listado encolumnado de todos los registros que haya en nuestro fichero. La forma de dibujar un listado en la pantalla es bastante sencilla en principio. Bastaría con recorrer el fichero y escribir cada registro en una línea de la pantalla, pero surgen algunos problemas que vamos a ir solucionando más adelante.

Vamos con un ejemplo, de lo más cutre, que recorre el fichero y escribe su contenido línea por línea todo seguido. Suponemos que ya tenemos abierto nuestro fichero y definido el tipo registro, igual que en los ejemplos anteriores.

CLS
numregs=LOF(1) - LEN(persona)
c=1
WHILE c <= numregs
	GET #1, c, persona
	PRINT c; persona.nombre; persona.edad
	c=c+1
WEND

El resultado por pantalla podría ser:

1Pepe                25
2                    0
3                    0
4Manolo García García32
5Paco                19
6                    0
7                    0
8                    0
9Ana                 40
10Juan Carlos         28
11Fernando            21

Lo primero, como siempre, calcular cuantos registros tiene el fichero y después irlos leyendo y sacando por pantalla desde dentro de un bloque MIENTRAS. Se podía haber hecho con un PARA, pero como después vamos a tener que hacerlo con un MIENTRAS de todas formas, mejor empezar así.

El listado aparece más o menos encolumnado porque las variables de cadena (nombre) se devuelven con espacios en blanco detrás hasta completar su longitud total, pero los enteros no ocuparían siempre lo mismo y nos estropearían el encolumnado, cosa que pasaría a partir del registro 10 donde el nombre sería empujado un lugar hacia la derecha. Más adelante iremos solucionando esto.

Como se puede ver en el resultado del ejemplo anterior, han aparecido también los registros borrados (Sin nombre y con edad=0). Para evitar esto lo que hacemos comprobar con una condición justo antes de escribir cada línea que el registro no ha sido borrado. Sería así:

CLS
numregs=LOF(1) - LEN(persona)
c=1
WHILE c <= numregs
	GET #1, c, persona
	IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR persona.edad>0 THEN
		PRINT c; persona.nombre; persona.edad
	END IF
	c=c+1
WEND

Y el resultado podría ser:

1Pepe                25
4Manolo García García32
5Paco                19
9Ana                 40
10Juan Carlos         28
11Fernando            21

Vemos que ya no aparecen los registros que fueron borrados. La condición que hacemos es que el nombre (texto) no sea todo espacios o que la edad sea mayor que cero. Ahora sólo aparecen los registros que nos interesan, pero todavía se ve muy feo vamos a dibujarle un encabezado a la lista y dejar un poco de espacio entre las columnas para que no quede tan junto.

CLS
numregs=LOF(1) - LEN(persona)
c=1
PRINT "Registro     Nombre               Edad"
PRINT "============ ==================== ===="
WHILE c <= numregs
	GET #1, c, persona
	IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR persona.edad>0 THEN
		PRINT c, persona.nombre; " "; persona.edad
	END IF
	c=c+1
WEND

Y el resultado, más bonito sería:

Registro     Nombre               Edad
============ ==================== ====
1            Pepe                 25
4            Manolo García García 32
5            Paco                 19
9            Ana                  40
10           Juan Carlos          28
11           Fernando             21

Dibujamos el encabezado una vez al principio. La línea con guiones o signos igual nos puede servir para orientarnos con los tamaños de los campos. Dentro del bucle en la instrucción PRINT si separamos las variables por comas se pasa a la siguiente posición de tabulación y nos evitamos el problema del diferente número de cifras, aunque se desperdicia mucho espacio porque las posiciones de tabulación an QBasic miden 13 caracteres. Podemos meter espacios entre comillas para separar los campos. En este caso como hay muy pocos campos y hemos podido dejar los números enteros para el final ha salido medio bien, pero en casos más complicados sería necesario usar la instrucción TAB para definir las columnas. Veamos como se haría.

CLS
numregs=LOF(1) - LEN(persona)
c=1
PRINT "Reg"; TAB(5); "Nombre"; TAB(26); "Edad"
PRINT "==="; TAB(5); "===================="; TAB(26); "===="
WHILE c <= numregs
	GET #1, c, persona
	IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR persona.edad>0 THEN
		PRINT c; TAB(5); persona.nombre; TAB(26); persona.edad
	END IF
	c=c+1
WEND

Y el resultado, todavía mejor.

Reg Nombre               Edad
=== ==================== ====
1   Pepe                 25
4   Manolo García García 32
5   Paco                 19
9   Ana                  40
10  Juan Carlos          28
11  Fernando             21

El parámetro que le pasamos a la orden TAB debe ser un entero que indica la posición a la que saltará el cursor dentro de la línea actual. Recordemos que cada línea tiene 80 caracteres de ancho y que la primera posición por la izquierda es la 1. La orden TAB se utiliza como argumento de la orden PRINT, igual que si fuera una función, solo que en vez de devolver un valor lo que hace es llevar al cursor hasta la posición especificada. En el encabezado nos podíamos ahorrar las ordenes TAB y poner directamente los espacios, porque como todo es constante no va a variar nada. Para saber las posiciones lo más cómodo es ir probando hasta que cada cosa caiga en su sitio, debajo de su título.

Ya se empieza a parecer a un listado. Siempre habrá que ajustar las columnas según nuestras necesidades y si no caben de ancho la única solución posible con lo que sabemos hasta ahora es no escribir todos los campos.

Lo último que vamos a solucionar es el terrible problema de que si tenemos muchos registros el listado avanzará rápidamente y solo veremos los últimos veintitantos registros que caben en la pantalla. Pasa lo mismo que con la orden DIR de MS-DOS si no ponemos el modificador /P. Para conseguir esto lo que hacemos es usar un segundo contador que vamos incrementando en cada línea y cuanto veamos que llega a 20 lo ponemos a cero, y hacemos una pausa, cuando el usuario pulse una tecla borramos la pantalla, dibujamos el encabezado de nuevo y seguimos. Vamos con el ejemplo:

CLS
numregs=LOF(1) - LEN(persona)
c=1
c2=1
PRINT "Reg Nombre               Edad"
PRINT "=== ==================== ===="
WHILE c <= numregs
	GET #1, c, persona
	IF persona.nombre<>SPACE$(LEN(persona.nombre)) OR persona.edad>0 THEN
		PRINT c; TAB(5); persona.nombre; TAB(26); persona.edad
	END IF
	c=c+1
	c2=c2+1
	IF c2>20 THEN
		c2=1
		PRINT "Pulsa cualquier tecla para seguir..."
		WHILE INKEY$<>"":WEND
		DO:LOOP WHILE INKEY$=""
		CLS
		PRINT "Reg Nombre               Edad"
		PRINT "=== ==================== ===="
	END IF
WEND

El resultado, si hay menos de 20 registros sería igual que antes, y si hay más sería que se van viendo de 20 en 20.

Este ejemplo se puede mejorar para que las instrucciones donde se dibuja el encabezado sólo aparezcan una vez, pero aquí no nos vamos a complicar tanto. De todas formas en el tema de manejo de la pantalla de texto que hay en la sección de temas de ampliación de este curso aparen las instrucciones a usar para hacer listados desplazables parecidos a los de Windows que son mucho más "bonitos" y no tienen estos problemas.





1.16.4 - BREVE IDEA SOBRE BASES DE DATOS

En informática se conoce como base de datos a un mecanismo capaz de almacenar información para después poder recuperarla y manejarla con facilidad y precisión.

Visto así, los archivos de ejemplo de agendas que hemos creado en los apartados anteriores son bases de datos, pero cuando hablamos de bases de datos pensamos en programas que manejan gran cantidad de información de forma muy estructurada y que permiten realizar consultas y operaciones de mantenimiento muy complejas y fiables.

Al hablar de bases de datos hay que distinguir dos cosas muy importantes. Por un lado tenemos la base de datos en sí, que es la información grabada en los discos, y por otro lado tenemos el Sistema de Gestión de Bases de Datos (SGBD o DBMS en inglés) que es el programa encargado de manejar toda la información. Es lo que sería un programa como MS Access o MySQL.

Las bases de datos que manejan estos programas casi nunca contienen una sola tabla, es decir, un solo archivo de datos, sino que contienen muchas tablas relacionadas entre si. Por eso se habla de bases de datos relacionales. El hecho de que las tablas estén relacionadas unas con otras implica que al hacer un cambio en una de ellas, posiblemente se vean afectadas otras para que la información siga teniendo consistencia. También estos sistemas de bases de datos usan muchos mecanismos como índices o algoritmos de ordenación para conseguir que el acceso a la información durante las consultas sea un proceso mucho más rápido y completamente fiable.

Nosotros, en nuestros programas estamos integrando una pequeñísima parte de los mecanismos que usan los SGBD para poder manejar la información de nuestros archivos, pero conforme nuestras necesidades aumenten y ya usemos otros lenguajes de programación, será mucho más sencillo usar los servicios de un auténtico SGBD para manejar nuestra información, en vez de programarlo nosotros. Esto quiere decir que el uso de lo que hemos visto en este tema de archivos de acceso directo se limitará a el mantenimiento de pequeños y sencillos conjuntos de datos cuando sea más comodo manejarlos de forma directa que de forma secuencial, pero desde el momento en que intervienen varios archivos de datos necesitaremos usar las funciones de uno de estos sofisticados SGBDs integrados en los modernos lenguajes de programación.





1.16.5 - OTRAS INSTRUCCIONES
RELACIONADAS CON FICHEROS

Ya hemos visto como crear ficheros y leer o modificar la información almacenada en los mismos usando diferentes métodos de programación. Para terminar el tema de ficheros es necesario conocer algunas instrucciones que tiene QBasic para trabajar con ficheros enteros que haya en el sistema de archivos. Estas instrucciones nos ayudarán a trabajar con los ficheros sin tener que recurrir a ejecutar ordenes del sistema operativo.

Vamos con ellas.

CHDIR ruta$

Cambia el directorio activo. El directorio activo es el que usará QBasic para buscar los archivos que tiene que abrir o para crearlos si no se especifica una ruta en la instrucción OPEN. Puede ser difícil determinar el directorio activo, por lo que conviene especificar uno con esta orden antes de abrir o crear ficheros.

Si ejecutamos QBasic desde un icono de acceso directo de Windows el directorio activo será el que aparece en el cuadro "Carpeta de trabajo" de la sección "Programa" de las propiedades del acceso directo.

Si ejecutamos QBasic desde MS-DOS el directorio activo será donde nos encontrábamos cuando entramos en QBasic.

La ruta que hay que especificar en esta instrucción puede ser un literal entre comillas o una variable de cadena que contenga la ruta adecuada. Si el directorio no existe se producirá un error de tiempo de ejecución.

Hay que recordar que estamos en MS-DOS y por lo tanto solo podemos trabajar con nombres de directorios y archivos de no más de 8 caracteres y sin espacios, por lo tanto los directorios "Archivos de Programas" y "Mis documentos" tendrán nombres parecidos a "ARCHIV~1" y "MISDOC~1". Para asegurarse de cuales son estos nombres hay que mirar la casilla "Nombre MS-DOS" de la ventana de propiedades del directorio correspondiente en Windows.

Para escribir el carácter "Gurruño" (~) hay que pulsar la tecla "Alternativa" y simultáneamente escribir con el teclado numérico la cifra 126, correspondiente a su código ASCII.

La siguiente orden nos permite crear un directorio:

MKDIR ruta$

El directorio que creemos ya estará disponible en el sistema de archivos para usarlo desde nuestro programa o desde cualquier otro. Si la ruta que especificamos ya existe se producirá un error de tiempo de ejecución.

Es importante comprender que al crear un directorio este no pasa a ser el directorio activo. Para hacerlo habrá que usar una instrucción CHDIR.

Ahora vamos a ver como borrar un directorio existente:

RMDIR ruta$

Esta instrucción borra el directorio que le pasemos como parámetro. Si no existe se producirá un error. También se producirá el mismo error si el directorio no está vacío, es decir, contiene algún archivo o más subdirectorios. Habrá que borrarlos antes como veremos más adelante. Normalmente debemos tener mucho cuidado con lo que borramos desde nuestros programas para no hacer ningún estropicio con la información que tenemos guardada en los discos.

Podemos ver los ficheros y subdirectorios de un directorio con la siguiente instrucción:

FILES

Esta instrucción trabaja de forma parecida a la orden "DIR /W" de MS-DOS. Nos dibujará un listado a cuatro columnas con el contenido de un directorio, encabezada por el nombre del propio directorio y terminada por el espacio en bytes disponible en el disco. Los subdirectorios se identifican porque llevan a la derecha el rótulo "< DIR >".

Esta instrucción nos puede resultar algo útil en los programas que hemos hecho hasta ahora ya que la información se escribe en pantalla una línea debajo de otra, pero cuando hagamos diseños de pantallas más elaborados ya no será recomendable usar esta instrucción porque el listado de directorio se saltará todas nuestras especificaciones de pantalla, incluso ahora si el listado es muy largo sólo se ven las últimas líneas.

Si usamos la orden FILES sin parámetros se mostrarán todos los ficheros y subdirectorios del directorio activo, pero podemos especificar rutas, nombres de archivos y caracterescomodín (? y *) para adecuar el listado a nuestras necesidades. Veamos unos cuantos ejemplos:

FILES "*.txt"

Lista todos los ficheros con extensión TXT que haya en el directorio activo.

FILES "JUEGO.*"

Lista todos los ficheros que se llamen JUEGO, sea cual sea su extensión, del directorio activo.

FILES "C:\BASIC\PROGS\*.*"

Lista todos lo que haya en el directorio BASIC\PROGS de la unidad C:.

FILES "Imagen?.BMP"

Lista todos los archivos cuyo nombre empiece por "Imagen" y vaya seguido por exactamente un carácter, su extensión sea BMP y stén en el directorio activo. Aparecerían por ejemplo los archivos Imagen2.BMP o Imagen7.BMP, pero nunca el archivo Imagen14.BMP ya que contiene dos números.

Si la orden FILES no encuentra nada que listar se produce un error de tiempo de ejecución. Si listamos un directorio vacío no se producirá ningún error.

Ya hemos visto como borrar directorios, ahora vamos a ver como borrar archivos:

KILL Archivo$

Esta instrucción borra archivos, podemos usar una ruta y/o los comodines * y ?, cosa que puede ser peligrosa porque se borrarían varios ficheros a la vez, una vez más recomendar prudencia al usar esta orden para evitar estropicios irreparables, ya que en MS-DOS normalmente no tenemos papelera de reciclaje.

Si no especificamos una ruta se entenderá que estamos trabajando sobre el directorio activo. Si el archivo que queremos borrar no existe se producirá un error de tiempo de ejecución.

No confundir esta instrucción con la orden KILL de Linux que detiene procesos, es decir, obliga a terminar la ejecución de un programa pero no borra nada del disco.

Por último vamos a ver como cambiar el nombre un fichero:

NAME viejo$ AS nuevo$

viejo$ es el nombre actual del archivo que queremos renombrar, que debe existir para que no se produzcan errores.

nuevo$ tiene que ser el nuevo nombre que le vamos a dar al archivo, y que no debe existir todavía.

Por ejemplo, para cambiar el nombre al archivo base.dat del directorio actual por base.bak tendríamos que usar la instrucción

NAME base.dat AS base.bak

En el nombre del archivo ya existente (la primera parte antes de AS) podemos especificar una ruta de directorios.

Para copiar o mover archivos no tenemos una instrucción específica en QBasic. Tendremos que crearnos un modulo que lo abra en modo binario y lo copie carácter por carácter en un archivo nuevo en la otra ubicación, si lo que queríamos era moverlo después habrá que borrar el original. Para hacer esto y otras cosas más complicadas puede ser más cómodo llamar a la orden correspondiente de MS-DOS como se verá en temas posteriores.













CuRSo De iNTRoDuCCióN a La PRoGRaMaCióN CoN QBaSiC
© 2004 Juan M. González